Skip to content

PDF Text Operators

Marc Jones edited this page Dec 22, 2025 · 1 revision

PDF Text Operators

This page documents the PDF operators used to render text, their syntax, and how they're handled in our codebase.

References

Text Object Structure

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

Text-Showing Operators

These are the only operators that actually render glyphs on the page.

Tj - Show String

Syntax: (string) Tj

The simplest text-showing operator. Shows a single string at the current text position.

(Hello World) Tj

Behavior:

  1. 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
  2. For space characters (code 32), also add word spacing

Our Implementation: ContentStreamParser.cs:ParseOperator() handles Tj by creating a TextOperation with the string content.

TJ - Show Array (with Kerning)

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.

' - Quote (Newline + Show)

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.

" - Double Quote (Spacing + Newline + Show)

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.

String Encoding in Operators

The strings in text-showing operators can be encoded in several ways:

Literal Strings

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)

Hexadecimal Strings

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.

Text State Operators

These operators modify text state but don't show text themselves.

Tf - Set Font and Size

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.

Td - Move Text Position

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.

TD - Move + Set Leading

Syntax: tx ty TD

Equivalent to:

-ty TL                  % Set leading to -ty
tx ty Td                % Move to position

Tm - Set Text Matrix

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)

T* - Move to Next Line

Syntax: T*

Equivalent to 0 -TL Td where TL is the current leading value.

Tc - Character Spacing

Syntax: spacing Tc

Additional space between characters (in unscaled text space units).

Tw - Word Spacing

Syntax: spacing Tw

Additional space for the space character (code 32) only.

Tz - Horizontal Scaling

Syntax: percent Tz

Horizontal scaling as percentage (100 = normal).

50 Tz                   % Condense to 50%
150 Tz                  % Expand to 150%

TL - Text Leading

Syntax: leading TL

Distance between baselines for multi-line text (used by T*, ', ").

Tr - Text Rendering Mode

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!

Ts - Text Rise

Syntax: rise Ts

Vertical offset from baseline (for superscript/subscript).

Real-World Examples

Simple PDF Generator Output

Our TestPdfGenerator.CreateSimpleTextPdf() produces:

BT
/F1 12 Tf
100 700 Td
(Hello World) Tj
ET

Professional PDF (Word, InDesign, etc.)

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

Scanned Document with OCR

Contains invisible text layer:

BT
/F1 12 Tf
3 Tr                    % Invisible mode!
100 700 Td
(Scanned text here) Tj
ET

Codebase Locations

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

Lessons Learned

The TJ Array Sign Convention

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.

Invisible Text (Tr=3) Must Be Redacted

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.

Quote Operators Are Rare But Must Work

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

Clone this wiki locally