Guide · Coordinates & anchors

PDF Positioning, hands on

Last updated: 2026-07-02

Where exactly does a stamp land when you say (x, y)? This page is the coordinate contract of rust-pdf, live: drag the sliders, flip the anchors, and watch both the page and the API call update. Everything here maps 1:1 to PlaceText, PlaceParagraph, MaskedText and DrawImage in all ten languages.

The contract in one line: units are points (1 pt = 1/72"), the origin is the bottom-left, y grows up, rotation is counter-clockwise about the anchor, and what y means is an explicit, documented choice (the VerticalAnchor).

1. Vertical anchors: what y means

Libraries disagree about the vertical meaning of y: a raw PDF text matrix anchors the baseline, the legacy engine's fixed-position layout lays text down from the top of its layout box, legacy PDF libraries hangs it from a rectangle edge. rust-pdf makes the choice explicit. Pick an anchor and watch the text move while the anchor point stays put.

Anchor playground

anchor (x, y) baseline ascender / descender (font geometry) layout line box (win metrics + half-leading)

Top/Bottom are clean font geometry (ascender and descender lines). LineTop/LineBottom reproduce the legacy engine's layout box (OS/2 win metrics plus a 0.21 em half-leading each side), calibrated to match fixed-position layout within a tenth of a point. The legacy layout engines model never leaks into the defaults.

2. Rotation pivots at the anchor

rotationDeg rotates counter-clockwise about the anchor. Offsets from alignment or vertical anchors rotate together with the text, so the anchor remains the same physical point of the text box at every angle. Spin it.

Pivot playground

30°

Note how with LineBottom the box-bottom point is the thing pinned to the dot: the baseline offset swings with the text instead of being pre-added to y. That is how legacy layout engines composes it, so ports line up at every angle.

3. Rotated scans: StampSpace

Scanned documents usually carry /Rotate 90. There are two honest ways to interpret your coordinates on such a page, and rust-pdf lets you pick per document instead of guessing.

Same call, two spaces (page has /Rotate 90)

Visible: coordinates in the page as displayed, the library compensates /Rotate, a 0° stamp reads upright on screen. Media: the raw PDF user space, nothing composed, which is what legacy raw-space drawing and fixed-position layout use. Porting coordinates computed for legacy PDF libraries? Use Media.

4. Paragraphs: wrapping, bottom-pinning and the ceiling

PlaceParagraph wraps by word inside a width. With the Bottom anchors the block is bottom-pinned: its bottom rests on y and it grows upward by the real content height. maxHeight is a ceiling, not a box to fill: overflowing lines are cut from the top and the last lines stay pinned. The call reports the consumed height.

Block anchor playground

none consumed: 0 pt

Watch the cut direction: with the bottom-pin, a too-small ceiling removes the first lines and keeps the last ones on y (footers, signature blocks). With Top, the cut removes the last lines below y − maxHeight.

5. MaskedText: a box, a mask, aligned text

MaskedText paints an opaque box and writes a value over it, the classic "cover the placeholder" move. The vertical alignment and the horizontal inset are explicit.

Box alignment playground

1.8 pt

Top matches the top line-alignment of rectangle-based text APIs; padding: 0 starts flush at the box edge like rectangle-based DrawString APIs. The default keeps a small inset of 0.15 × size.

6. Images: corner pivot or bounding box

A rotated image can be anchored two ways: Corner (default) keeps (x, y) as the image's own lower-left corner and the image sweeps around it; BoundingBox lands the rotated image's bounding box at (x, y), which is how legacy layout engines lays out rotated images.

Image anchor playground

35°

With BoundingBox the pixels always land at or above and to the right of the anchor: a 90° image occupies [x, x+height] × [y, y+width].

The same contract in every language

One Rust core, ten bindings, identical semantics. A signature block stamped at the bottom of a scanned page looks like this:

# pip install rustpdf
import rustpdf
from rustpdf import EditableDoc, StampSpace, VerticalAnchor, Align

doc = EditableDoc.load(open("scan.pdf", "rb").read())
doc.set_stamp_space(StampSpace.MEDIA)      # legacy-style raw coordinates
times = doc.add_font_file("Times New Roman.ttf")

# Bottom-pinned signature block: the block bottom rests on y = 96.
doc.place_paragraph(0, 72, 96, 220,
    "Digitally signed by Maria Silva\nReason: Approval\n2026-07-02 14:31 UTC",
    size=10, font_id=times, max_height=64,
    anchor=VerticalAnchor.LINE_BOTTOM)
open("signed-stamped.pdf", "wb").write(doc.to_bytes())
// dotnet add package RustPdf
using RustPdf;

using var doc = EditableDoc.Load(File.ReadAllBytes("scan.pdf"));
doc.StampSpace = StampSpace.Media;         // legacy-style raw coordinates
int times = doc.AddFontFile("Times New Roman.ttf");

// Bottom-pinned signature block: the block bottom rests on y = 96.
doc.PlaceParagraph(0, 72, 96, 220,
    "Digitally signed by Maria Silva\nReason: Approval\n2026-07-02 14:31 UTC",
    size: 10, fontId: times, maxHeight: 64,
    anchor: VerticalAnchor.LineBottom);
File.WriteAllBytes("signed-stamped.pdf", doc.ToBytes());
// go get github.com/rustpdf/rustpdf-go@latest
doc, _ := rustpdf.Load(data)
defer doc.Close()
doc.SetStampSpace(rustpdf.StampMedia)      // legacy-style raw coordinates
times, _ := doc.AddFontFile("Times New Roman.ttf")

// Bottom-pinned signature block: the block bottom rests on y = 96.
doc.PlaceParagraph(0, 72, 96, 220,
    "Digitally signed by Maria Silva\nReason: Approval\n2026-07-02 14:31 UTC",
    10, 0, 0, 0, rustpdf.AlignLeft, times,
    64, 1.0, rustpdf.AnchorLineBottom, 0)
out, _ := doc.Bytes()
// npm install rustpdf
const { EditableDoc, StampSpace, VerticalAnchor } = require("rustpdf");

const doc = EditableDoc.load(fs.readFileSync("scan.pdf"));
doc.setStampSpace(StampSpace.Media);       // legacy-style raw coordinates
const times = doc.addFontFile("Times New Roman.ttf");

// Bottom-pinned signature block: the block bottom rests on y = 96.
doc.placeParagraph(0, 72, 96, 220,
  "Digitally signed by Maria Silva\nReason: Approval\n2026-07-02 14:31 UTC",
  { size: 10, fontId: times, maxHeight: 64,
    anchor: VerticalAnchor.LineBottom });
fs.writeFileSync("signed-stamped.pdf", doc.toBytes());

Full API reference per language: Python, C#, Go, Node, Java, PHP, Ruby, Swift, Delphi.

Positioning FAQ

What does the y coordinate mean when I place text?

By default it is the text baseline. You can also anchor by the geometric top (ascender line), the geometric bottom (descender line), or the top and bottom of the layout line box (LineTop/LineBottom), which is the right pair when matching output from legacy layout engines's fixed-position layout.

Around which point does rotated content pivot?

Always the anchor (x, y). Alignment shifts and vertical-anchor offsets rotate with the text, so the anchor stays the same physical point of the box at any angle. Images pivot at their lower-left corner by default, or anchor the rotated bounding box with ImageAnchor.BoundingBox.

Why does my stamp land wrong on scanned pages?

Scans carry /Rotate. The default Visible space compensates it so coordinates match what you see. Code ported from legacy layout engines computes coordinates in the raw user space instead: set StampSpace.Media and your math is used untouched.

How do I bottom-align a block like legacy PDF engines's fixed-position layout?

PlaceParagraph with anchor: LineBottom: the block's bottom rests on y, maxHeight is a ceiling that cuts from the top, and the call returns the consumed height so you can stack blocks without re-measuring.

Positioning you can reason about

Basic generation and stamping are free. PDF/A, encryption, signatures, redaction and rendering are licensed features on the same core.

See pricing Read the docs

Related guides: Render PDF to image · Watermark a PDF · PAdES signatures