Repository navigation
PDF Text Operators
This page documents the PDF operators used to render text, their syntax, and how they're handled in our codebase.
- PDF 32000-1:2008, Section 9.4 "Text Objects" and Section 9.4.3 "Text-Showing Operators"
- PDF Association Operators Cheat Sheet
- Syncfusion PDF Text Operators
All text operations must occur within a text object, delimited by BT (Begin Text) and ET (End Text) operators:
BT % Begin text object
/F1 12 Tf % Set font and size
100 700 Td % Move to position
(Hello World) Tj % Show text
ET % End text object
Important Rules (from PDF specification):
- No text operators are allowed outside a BT/ET pair
- BT/ET pairs cannot be nested
- A text object may contain multiple text-showing operators
- Text matrix is reset to identity when BT is executed
These are the only operators that actually render glyphs on the page.
Syntax: (string) Tj
The simplest text-showing operator. Shows a single string at the current text position.
(Hello World) Tj
Behavior:
- For each character code in the string:
- Look up glyph in current font
- Paint glyph at current position
- Update position by glyph width + character spacing
- For space characters (code 32), also add word spacing
Our Implementation: ContentStreamParser.cs:ParseOperator() handles Tj by creating a TextOperation with the string content.
Syntax: [element ...] TJ
Shows multiple strings with positioning adjustments (kerning). This is the most commonly used operator in professionally-generated PDFs.
[(H) -10 (ello) 20 (World)] TJ
Array Elements:
- String: Characters to show
- Number: Horizontal adjustment in thousandths of an em (negative = move right for horizontal text)
Behavior: The number represents a displacement in text space units. For horizontal writing:
- Positive number: Move LEFT (reduces spacing)
- Negative number: Move RIGHT (increases spacing)
This is counterintuitive! A value of -10 means "move 10/1000 em to the right" (add space).
Formula (for horizontal text):
tx = -(Tj / 1000) × Tfs × Th
Where:
-
Tj= the adjustment value -
Tfs= font size (from Tf operator) -
Th= horizontal scaling (from Tz operator, default 100%)
Our Implementation: ContentStreamParser.cs extracts the TJ array and processes each element. ContentStreamBuilder.cs reconstructs TJ arrays when rebuilding streams.
Syntax: (string) '
Equivalent to:
T*
(string) Tj
Moves to the next line (using current leading) and shows a string.
Our Implementation: Handled in ContentStreamParser.cs by updating text position using leading value before processing as text.
Syntax: aw ac (string) "
Equivalent to:
aw Tw % Set word spacing
ac Tc % Set character spacing
T* % Move to next line
(string) Tj % Show string
Parameters:
-
aw= word spacing -
ac= character spacing
Our Implementation: The spacing values are applied to text state before processing the string.
The strings in text-showing operators can be encoded in several ways:
Enclosed in parentheses:
(Hello World) % Simple ASCII
(Hello \(World\)) % Escaped parentheses
(Line1\nLine2) % With newline
(\101\102\103) % Octal codes (ABC)
Escape Sequences:
| Sequence | Meaning |
|---|---|
\n |
Newline (0x0A) |
\r |
Carriage return (0x0D) |
\t |
Tab (0x09) |
\b |
Backspace (0x08) |
\f |
Form feed (0x0C) |
\( |
Left parenthesis |
\) |
Right parenthesis |
\\ |
Backslash |
\ddd |
Octal character code (1-3 digits) |
Enclosed in angle brackets:
<48656C6C6F> % "Hello" in hex
<48 65 6C 6C 6F> % Same, whitespace ignored
Note: If odd number of hex digits, the final nibble is assumed to be 0.
These operators modify text state but don't show text themselves.
Syntax: /fontname size Tf
/F1 12 Tf % Use font F1 at 12 points
The font name must be defined in the page's /Resources dictionary.
Syntax: tx ty Td
Move to position (tx, ty) relative to current line start.
100 -14 Td % Move right 100, down 14 units
This also sets the line start to the new position.
Syntax: tx ty TD
Equivalent to:
-ty TL % Set leading to -ty
tx ty Td % Move to position
Syntax: a b c d e f Tm
Sets the text matrix absolutely (does not multiply with existing matrix).
1 0 0 1 100 700 Tm % Position at (100, 700), no scaling/rotation
0.9 0.1 -0.1 0.9 50 600 Tm % With rotation
The text matrix controls:
- Translation (e, f components)
- Scaling (a, d for x, y scaling)
- Rotation (combined a, b, c, d)
- Shearing (b, c components)
Syntax: T*
Equivalent to 0 -TL Td where TL is the current leading value.
Syntax: spacing Tc
Additional space between characters (in unscaled text space units).
Syntax: spacing Tw
Additional space for the space character (code 32) only.
Syntax: percent Tz
Horizontal scaling as percentage (100 = normal).
50 Tz % Condense to 50%
150 Tz % Expand to 150%
Syntax: leading TL
Distance between baselines for multi-line text (used by T*, ', ").
Syntax: mode Tr
| Mode | Effect | Visible | Extractable |
|---|---|---|---|
| 0 | Fill | Yes | Yes |
| 1 | Stroke | Yes | Yes |
| 2 | Fill + Stroke | Yes | Yes |
| 3 | Invisible | No | Yes |
| 4 | Fill + Clip | Yes | Yes |
| 5 | Stroke + Clip | Yes | Yes |
| 6 | Fill + Stroke + Clip | Yes | Yes |
| 7 | Clip only | No | Yes |
Critical for Redaction: Mode 3 (invisible) text is used for OCR layers. The text is NOT visible but IS extractable by text tools. This text MUST be redacted even though it's invisible!
Syntax: rise Ts
Vertical offset from baseline (for superscript/subscript).
Our TestPdfGenerator.CreateSimpleTextPdf() produces:
BT
/F1 12 Tf
100 700 Td
(Hello World) Tj
ET
Typically uses TJ for precise kerning:
BT
/F0 10.5 Tf
1 0 0 1 72 720 Tm
[(P)-10(r)-5(ofessional) 20 (T)15(ext)] TJ
ET
Contains invisible text layer:
BT
/F1 12 Tf
3 Tr % Invisible mode!
100 700 Td
(Scanned text here) Tj
ET
| File | Purpose |
|---|---|
ContentStreamParser.cs:ParseOperator() |
Parses text operators from content stream |
ContentStreamBuilder.cs |
Rebuilds operators after modification |
TextBoundsCalculator.cs |
Calculates glyph positions from operators |
TextOperation.cs |
Data model for parsed text operations |
Problem: We initially got kerning wrong because the sign convention is counterintuitive.
Solution: A negative value in TJ means "add space" (move right in LTR text). Our ContentStreamParser now correctly interprets this.
Problem: Early versions only redacted visible text, leaving OCR layers intact.
Solution: Check render mode and redact regardless of visibility. Test with scanned PDFs that have OCR layers.
Problem: The ' and " operators are rarely used in modern PDFs but appear in some legacy documents.
Solution: Handle them by expanding to their equivalent operations (T* + Tj with optional Tc/Tw).
Last updated: 2024-12-22