PDF Content Stream
The sequence of operators and operands in a PDF page description that instructs the viewer how to paint text, graphics, and images on a page. Content streams are the low-level language that defines all visible PDF content.
A PDF content stream is a stream object (a sequence of bytes) that contains the drawing instructions for a single page, form XObject, Type 3 glyph, pattern tile, or other PDF graphical element. When a PDF viewer renders a page, it executes the operators in the page's content stream — painting text, images, and graphics onto the output device.
Content streams use a stack-based, postfix syntax inherited from PostScript. Operators are one- to three-character names; their operands (numbers, strings, names) precede them on the operand stack. The stream is encoded as a sequence of tokens and is typically compressed using Flate (zlib) compression to reduce file size.
Unlike PostScript, PDF content streams are not Turing-complete — there are no loops, conditionals, or subroutine calls in the core graphics language. PDF's programmability comes from JavaScript (in interactive documents) and from the page model (multiple pages, form XObjects for reusable content), not from the content stream itself. This deliberate limitation makes PDF content streams safe to execute in sandboxed viewers without risk of infinite loops or arbitrary code execution.
Content stream syntax
A PDF content stream is syntactically simple. Each instruction consists of zero or more operands followed by an operator:
<code>BT</code> <code> /Helvetica 12 Tf</code> <code> 100 700 Td</code> <code> (Hello, World!) Tj</code> <code>ET</code>
In this example: <code>BT</code> (Begin Text) starts a text block; <code>/Helvetica 12 Tf</code> selects the font named "Helvetica" from the resource dictionary at 12 units; <code>100 700 Td</code> moves the text position to coordinates (100, 700) in the page's default coordinate system; <code>(Hello, World!) Tj</code> shows the string; <code>ET</code> (End Text) ends the text block.
Coordinates are in the page's user space, which by default has the origin at the bottom-left corner of the page, with x increasing to the right and y increasing upward. This is opposite to screen coordinate systems (where y increases downward) — a source of confusion when converting between PDF page coordinates and CSS/screen coordinates in web-based PDF editors.
<strong>Token types in content streams:</strong> - <strong>Integer and real numbers</strong>: <code>100</code>, <code>0.5</code>, <code>-3.14</code> - <strong>Strings</strong>: <code>(literal string)</code> or <code><hex string></code> - <strong>Names</strong>: <code>/Helvetica</code>, <code>/DeviceRGB</code> - <strong>Operators</strong>: <code>Tj</code>, <code>Td</code>, <code>re</code>, <code>f</code>, <code>q</code>, <code>Q</code> - <strong>Arrays</strong>: <code>[200 0 0 200 0 0]</code> (used for the TJ operator's glyph array)
Inline images (using the <code>BI/ID/EI</code> operators) embed image data directly in the content stream rather than using a separate XObject. This is appropriate for small images; larger images should use Image XObjects referenced by the <code>Do</code> operator.
Operator categories
PDF content stream operators are grouped into functional categories:
<strong>Graphics state operators</strong> — Modify the current graphics state: <code>q</code> (save state), <code>Q</code> (restore state), <code>cm</code> (concatenate matrix — multiply the current transformation matrix), <code>w</code> (line width), <code>J</code> (line cap), <code>j</code> (line join), <code>M</code> (miter limit), <code>d</code> (dash pattern), <code>ri</code> (rendering intent), <code>i</code> (flatness), <code>gs</code> (set extended graphics state from ExtGState resource).
<strong>Path construction operators</strong> — Build paths before painting: <code>m</code> (move to), <code>l</code> (line to), <code>c</code> (Bézier curve to), <code>v</code> / <code>y</code> (partial Bézier curve), <code>h</code> (close path), <code>re</code> (rectangle).
<strong>Path painting operators</strong> — Stroke, fill, or clip: <code>S</code> (stroke), <code>s</code> (close and stroke), <code>f</code> / <code>F</code> (fill, nonzero winding), <code>f*</code> (fill, even-odd), <code>B</code> (fill and stroke), <code>b</code> (close, fill, stroke), <code>n</code> (end path without painting — used for clip).
<strong>Clipping path operators</strong> — <code>W</code> (set clipping path, nonzero), <code>W*</code> (set clipping path, even-odd). These must be followed by a path-painting operator or <code>n</code>.
<strong>Text operators</strong> — <code>BT</code> / <code>ET</code> (begin/end text block), <code>Tf</code> (font and size), <code>Td</code> / <code>TD</code> (move text position), <code>Tm</code> (set text matrix), <code>T*</code> (next line), <code>Tj</code> (show string), <code>TJ</code> (show string with individual glyph positioning), <code>'</code> (move to next line and show), <code>"</code> (move, set spacing, show), <code>Tc</code> / <code>Tw</code> / <code>Tz</code> / <code>TL</code> / <code>Tr</code> / <code>Ts</code> (character/word spacing, horizontal scaling, leading, rendering mode, rise).
<strong>Colour operators</strong> — <code>CS</code> / <code>cs</code> (set colour space for stroke/fill), <code>SC</code> / <code>sc</code> (set colour for stroke/fill), <code>SCN</code> / <code>scn</code> (set colour with additional parameters), <code>G</code> / <code>g</code> (DeviceGray stroke/fill), <code>RG</code> / <code>rg</code> (DeviceRGB stroke/fill), <code>K</code> / <code>k</code> (DeviceCMYK stroke/fill).
<strong>XObject and inline image operators</strong> — <code>Do</code> (invoke named XObject — image, form, or PostScript XObject), <code>BI</code> / <code>ID</code> / <code>EI</code> (inline image begin/image data/end).
<strong>Marked content operators</strong> — <code>BMC</code>, <code>BDC</code>, <code>EMC</code>, <code>MP</code>, <code>DP</code> — for accessibility tagging and optional content. See <a href="https://pdftheguide.com/glossary/pdf-marked-content" target="_blank" rel="noopener">PDF Marked Content</a> for details.
Content streams and the page rendering model
A page's content is specified by its /Contents entry, which can be:
- A single stream object reference - An array of stream object references (concatenated in order to form a single logical content stream)
Multiple content streams in the /Contents array are logically treated as a single stream — they share the graphics state. This design allows PDF generators to produce separate content streams for different parts of a page (text layer, annotations layer, background graphics) and assemble them into a single page without full reprocessing.
<strong>Form XObjects as reusable content streams</strong> — A Form XObject (distinct from form fields) is a named content stream referenced in the page's /XObject resource dictionary. The <code>Do</code> operator invokes it, embedding its content at the current transformation matrix position. Form XObjects encapsulate a self-contained piece of graphics with its own resources and transformation. They are used for: repeated elements (logos, backgrounds), reference XObjects (imported from other PDFs), and as the container for Type 3 font glyphs.
<strong>Content stream isolation</strong> — The graphics state is shared across content streams in a page's /Contents array, but Form XObjects have their own graphics state context. The <code>q</code>/<code>Q</code> operators save and restore the graphics state stack — a <code>Do</code> invocation of a Form XObject effectively runs the form's stream within a <code>q</code>/<code>Q</code> bracket.
<strong>Resource dictionary</strong> — Content streams reference resources (fonts, images, colour spaces, extended graphics states) by name. The resource dictionary (/Resources) on the page (or Form XObject) maps those names to the actual resource objects. This indirection allows the same resource to be shared across multiple pages and allows content streams to be self-describing.
Frequently asked questions
What is the difference between a PDF content stream and a PostScript program?
A PDF content stream and a PostScript program use similar operator syntax (both use postfix notation with named operators) but differ fundamentally in capabilities. PostScript is a Turing-complete programming language with conditionals, loops, recursion, and arbitrary computation. PDF content streams are not Turing-complete: they have no conditionals, no loops, no subroutine definitions. The operators in a PDF content stream are purely graphical — draw a path, paint text, invoke an XObject. This limitation is deliberate: it makes PDF content streams safe to execute in sandboxed viewers without risk of infinite loops, arbitrary file access, or other security risks associated with executing an arbitrary program. Adobe JavaScript (a separate feature of interactive PDF) provides programmability, but it is explicitly opt-out-able and sandboxed.
Why does PDF use bottom-left origin coordinates in content streams?
PDF inherited its coordinate system from PostScript, which in turn inherited it from mathematical convention: origin at bottom-left, y increasing upward. This matches the way mathematicians draw graphs and the way typographers traditionally described type (baseline at the bottom, ascenders going up). In contrast, screen coordinate systems (CSS, HTML canvas, most GUI frameworks) place the origin at the top-left with y increasing downward — matching the way text flows on a screen from top to bottom. When building web-based PDF editors (like this tool), coordinate conversion is essential: a click at CSS position (x, y) on a rendered page must be converted to PDF user space (x', height − y') to correctly place annotations and other overlaid content.
Can a PDF page have more than one content stream?
Yes. A page's /Contents entry can be an array of indirect references to multiple stream objects. These streams are concatenated in order to form a single logical content stream — the graphics state is shared across all of them. This design allows PDF generators to assemble pages from pre-built content blocks without reprocessing: a document background stream, a text content stream, and an annotations stream can each be produced separately and assembled into a page's /Contents array. The viewer treats them as a single stream. One practical implication: if the first stream ends mid-path construction and the second stream continues the path, this works correctly — the path state is part of the shared graphics state.