PDF File Documentation


Summary

A Portable Document Format file is a fixed-layout document that stores text, fonts, vector graphics and images together so a page prints and displays the same on any device. Adobe created it in 1993 and handed it to ISO in 2008 as ISO 32000. Its extension is .pdf and its MIME type is application/pdf. Internally it is a tree of numbered objects indexed by a cross-reference table.

Technical details

FeatureValue
Full namePortable Document Format
File extension.pdf
MIME typeapplication/pdf
Format typePage-description document (binary, object/xref structure)
DeveloperAdobe Systems (now an ISO open standard)
Introduced1993
StandardISO 32000-1:2008; ISO 32000-2:2020 (PDF 2.0)
Open standardYes — published as ISO 32000, no single vendor controls it
Byte order / structureHeader, body of objects, cross-reference table, trailer
Object typesBoolean, number, string, name, array, dictionary, stream, null
Compression filtersFlateDecode, LZWDecode, DCTDecode (JPEG), RunLengthDecode
EncryptionRC4 (40–128-bit) and AES (128/256-bit) via the /Encrypt dictionary
FontsType 1, TrueType, Type 0 (composite/CID); embeddable
Magic number25 50 44 46 2D ("%PDF-")
End marker%%EOF
Digital signaturesYes — appended via incremental update
Archival / print profilesPDF/A, PDF/X, PDF/UA
Related extensions.ps .eps .ai .fdf .xfdf
Specificationiso.org/standard/75839.html
File signature (magic bytes)
25 50 44 46 2D

Offset 0, 5 bytes. In ASCII this reads %PDF-, immediately followed by the version, for example %PDF-1.7 or %PDF-2.0. A well-formed file usually places a second comment line of four bytes > 127 right after the header, marking the file as binary so text-mode transfers do not corrupt it. Every valid PDF ends with the marker %%EOF, and a reader locates the document by reading backwards from that marker.

What is a PDF file?

PDF stands for Portable Document Format. It is a page-description format: instead of storing a flowing document that reflows to fit a window, it stores finished pages with their text placement, fonts, vector graphics and images fixed in position. Adobe created it in 1993, building on the company’s earlier print language PostScript, so that a document would print and display identically on any machine. Adobe published the specification openly for years and handed it to the International Organization for Standardization in 2008 as ISO 32000-1. PDF 2.0 (ISO 32000-2) followed in 2017 and was republished in 2020. Because it is an open standard, no single vendor controls it and dozens of independent readers and libraries exist.

Under the surface a PDF is not a stream of formatting codes but a small database. It is a set of numbered objects (pages, fonts, images, the instructions that draw each page) indexed by a cross-reference table and described by a trailer at the very end of the file. The sections below walk through that structure in the order a file is laid out and, where it matters, the order a reader actually parses it.

The %PDF header line and the %%EOF marker

A PDF opens with a comment line naming the version: the five bytes %PDF- (hex 25 50 44 46 2D) followed by a version number such as 1.7 or 2.0. The % character introduces a comment in PDF syntax, so the header is technically a comment that a parser reads as a magic number. Producers that emit binary content usually write a second comment line immediately after, containing at least four bytes with values above 127. That line has no meaning to the parser; it exists so that any tool inspecting the file treats it as binary and does not mangle the bytes with newline translation during an FTP or email transfer.

The file ends with the literal marker %%EOF. That marker is more than decoration. Just before it sits a startxref keyword and a byte offset, and a reader uses those to find the cross-reference table without scanning the whole file. This is why a PDF is parsed from the end: the map to every object lives near the tail, not the head.

The object model: eight object types

Everything between the header and the trailer is built from eight basic object types. Six are simple values and two (streams and the null object aside) are containers.

TypeSyntaxHolds
Booleantrue / falseA flag
Number42, -3.14Integer or real
String(Hello) or <48656C6C6F>Literal or hex text
Name/Type, /MediaBoxAn atomic keyword token
Array[0 0 612 792]An ordered list of objects
Dictionary<< /Key value >>Key–value pairs, keys are names
Streamdictionary + streamendstreamArbitrary (often compressed) byte data
NullnullThe absence of a value

Any object can be made indirect, meaning it is given a number and can be referenced from elsewhere. An indirect object is written as an object number, a generation number, the keyword obj, the object’s value, then endobj. A reference to it is written as N G R (number, generation, the letter R). This indirection is what turns a flat file into a graph: a page dictionary does not embed its font, it points at the font object by reference, and many pages can share one font object.

1 0 obj                                  % object 1, generation 0
<< /Type /Catalog /Pages 2 0 R >>        % /Pages points to object 2
endobj

2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj

3 0 obj
<< /Type /Page
   /Parent 2 0 R
   /MediaBox [0 0 612 792]              % US Letter, in points (1/72 inch)
   /Resources << /Font << /F1 4 0 R >> >>
   /Contents 5 0 R >>                    % page-drawing instructions
endobj

Streams, /Length and /Filter

A stream is a dictionary followed by the keyword stream, a run of raw bytes, and endstream. Streams carry everything bulky: page content, embedded font programs, images, and (in newer files) the cross-reference table itself. The dictionary describes the bytes. Its /Length entry gives the number of bytes between stream and endstream, so a parser knows exactly where the data ends without searching. Its optional /Filter entry names the decoders that must be applied, in order, to recover the original data.

5 0 obj
<< /Length 68 /Filter /FlateDecode >>
stream
...68 bytes of zlib/deflate-compressed content-stream operators...
endstream
endobj

Common filters are FlateDecode (the zlib/DEFLATE algorithm, the workhorse for text and general data), LZWDecode (the older Lempel–Ziv–Welch scheme), DCTDecode (baseline JPEG, so a JPEG can be dropped into a PDF with no re-encoding), CCITTFaxDecode for bilevel scans, and RunLengthDecode. Filters can be chained: an image might list [/ASCII85Decode /FlateDecode] so the bytes are first un-ASCII85ed, then inflated.

The cross-reference table: xref entries

The cross-reference table is the index that lets a reader jump straight to any object by byte offset rather than scanning. A classic (pre-1.5) xref begins with the keyword xref, then one or more subsections. Each subsection starts with two integers, the first object number and the count, then that many entries. Every entry is exactly 20 bytes so a reader can seek to an object’s entry arithmetically.

xref
0 6                       % subsection: 6 entries starting at object 0
0000000000 65535 f
0000000015 00000 n
0000000074 00000 n
0000000131 00000 n
0000000278 00000 n
0000000371 00000 n
trailer
<< /Size 6 /Root 1 0 R /Info 7 0 R
   /ID [<a1b2...> <a1b2...>] >>
startxref
492
%%EOF

The layout of one entry is nnnnnnnnnn ggggg k\r\n: a 10-digit byte offset, a space, a 5-digit generation number, a space, a one-letter keyword, and a two-byte end-of-line. The keyword is n for an in-use object (the ten digits are its offset from the start of the file) or f for a free object (the digits point to the next free object, forming a free list). Object 0 is always free and carries generation 65535 as the head of that list. The two trailing bytes are mandatory, which is what fixes each entry at 20 bytes.

From PDF 1.5 the table can instead be a cross-reference stream, an object with /Type /XRef whose compressed binary body encodes the same offsets in a compact form. This was added so that many small objects could be packed together inside object streams and compressed as a group, shrinking files that contain thousands of tiny objects. A file may use the classic table, the stream form, or a hybrid that carries both for older readers.

The trailer and reading a PDF backwards

After the xref comes the trailer keyword and a dictionary that ties the document together. Its key entries are /Size (the total number of objects, so the largest object number plus one), /Root (a reference to the document catalog, the entry point to all content), /Info (an optional reference to the metadata dictionary with title, author and dates), /ID (a pair of byte strings identifying the file), and /Prev (the byte offset of a previous xref section, used by incremental updates, described below). If the document is encrypted, /Encrypt also appears here.

A reader opens the file by seeking to the end, scanning back to find startxref, reading the byte offset printed after it, and jumping to the cross-reference table at that offset. From the table it reads the trailer, follows /Root to the catalog, and only then begins loading the objects it actually needs. Nothing forces it to read the body top to bottom. That end-first design is what makes a PDF fast to open and cheap to append to.

The document catalog and the page tree

The object named by /Root is the document catalog, a dictionary with /Type /Catalog. Its /Pages entry references the root of the page tree. The page tree is a balanced tree of two node kinds. Intermediate nodes have /Type /Pages with a /Kids array of child nodes and a /Count of the leaves beneath them. Leaf nodes have /Type /Page and describe one physical page.

A page dictionary carries the geometry and content pointers. /MediaBox is a four-number array giving the page rectangle in points (1 point = 1/72 inch), so [0 0 612 792] is US Letter. /Contents references the content stream (or an array of streams) that draws the page, and /Resources lists the fonts, images and colour spaces that content refers to by name. Other boxes such as /CropBox refine what area is displayed or printed.

Content streams and the page-drawing operators

The bytes a page’s /Contents stream decompresses to are a sequence of operands followed by operators, a tiny stack-based drawing language descended from PostScript. Text is drawn inside a BT (begin text) / ET (end text) block: Tf selects a font and size, Td sets the text position, and Tj shows a string. Graphics use their own operators: m moves to a point, l adds a line, re appends a rectangle, and cm concatenates a transformation matrix onto the coordinate system.

BT
  /F1 24 Tf        % set font F1 at 24 pt
  72 700 Td        % move text position to (72, 700)
  (Hello world) Tj % paint the string
ET

Because the content stream only names resources (/F1 above), the same operators mean nothing without the page’s /Resources dictionary to resolve /F1 to a real font object. Coordinates start at the bottom-left corner and grow upward, the opposite of most screen systems, another inheritance from PostScript and its single-page sibling EPS.

Fonts: Type 1, TrueType and Type 0 embedding

A font in a PDF is a dictionary describing how to map character codes to glyphs. The main simple font types are /Type1 (PostScript Type 1 outlines, including the 14 standard fonts every reader is assumed to have) and /TrueType. For scripts with large character sets or multi-byte encodings, PDF uses /Type0 composite fonts backed by a CIDFont, which map multi-byte codes to glyph identifiers.

The font program itself, the actual glyph outlines, can be embedded as a stream referenced by a FontFile (Type 1), FontFile2 (TrueType) or FontFile3 (compact CFF/OpenType) entry in the font descriptor. Embedding matters because a PDF is meant to look identical everywhere: if the font is not embedded and the reader’s machine lacks it, the reader substitutes a similar font and line breaks and spacing can shift. Archival profiles such as PDF/A require embedding for exactly this reason. A related consequence: a composite font needs a /ToUnicode map to tell a reader which Unicode character each glyph code represents. Without it, glyphs still draw correctly but copied text comes out as garbage, which is one reason some PDFs render fine yet refuse to yield clean copyable text.

Incremental updates and how signatures append

A PDF can be changed without rewriting it. An incremental update appends new and replacement objects to the end of the file, followed by a new cross-reference section and a new trailer, then a fresh %%EOF. The new trailer’s /Prev entry holds the byte offset of the previous xref, so a reader building its object map walks the chain of xref sections from newest to oldest, with later definitions of an object number overriding earlier ones. The original bytes are untouched.

This mechanism is how digital signatures work. Signing a PDF appends a signature dictionary and a new xref/trailer that covers the whole file up to that point; the signed byte range is recorded, so any later edit is itself another incremental update that a verifier can detect as a change made after signing. It is also why the same file can accumulate several signatures, each appended over the last, each verifiable against the state of the document when it was applied.

/Encrypt: RC4, AES and the two passwords

Encrypted PDFs are described by an /Encrypt dictionary referenced from the trailer. It names a security handler and the algorithm: older files use RC4 with 40- to 128-bit keys, while modern files use AES at 128 or 256 bits. Strings and streams in the body are encrypted with a key derived from the document, but the structure (the xref, the trailer, object numbers) stays readable so a reader can still navigate the file before decrypting content.

The standard handler supports two passwords with different roles. The user password (open password) is required to open and read the document at all. The owner password controls permissions such as printing, copying text or modifying the file; a reader that knows only the user password enforces those restrictions. Because the permission flags are honoured by the reader rather than cryptographically enforced against the content, owner-password restrictions are advisory and easily stripped, whereas a document locked with a user password is genuinely encrypted.

Security: auto-run actions, JavaScript and embedded files

A PDF is not inert. The format can carry actions that run when a document opens or when the user interacts with it, and attackers have used these for years. The catalog’s /OpenAction entry names an action to perform the moment the file opens, and an /AA (additional actions) dictionary can attach actions to page-open, document-close and field events. If the action type is a JavaScript action, the reader runs the code in /JS under a /JavaScript action dictionary. Malicious documents commonly place a JavaScript /OpenAction so their payload executes with no click at all, often to trigger a memory-corruption bug in the reader’s JavaScript engine or its image and font parsers.

Two more vectors matter. A /Launch action asks the reader to run an external program or open a file, historically abused to start a shell or helper application. And a PDF can carry embedded files: a stream with /Type /EmbeddedFile referenced through an /EF entry on a file-specification object, which lets an attacker smuggle a second payload inside an innocent-looking document and have an action or the user extract and run it. The parser itself is also an attack surface, because a deliberately malformed object, stream length or filter can drive a decoder off its rails before any script runs.

Mitigations are concrete. Keep the reader updated, since most real-world PDF exploits target a patched parser or scripting bug. Disable JavaScript in the reader if you do not need forms that rely on it, which neutralises the whole /JS class of attacks. Treat unexpected documents that prompt you to enable content, run a launch action or click a link the way you would treat any untrusted attachment. Opening a PDF in a sandboxed viewer, including the ones built into modern browsers, limits what a successful exploit can reach.

Frequently asked questions

Why is a PDF parsed from the end?

The map to every object, the cross-reference table, lives near the end of the file, and the last thing before %%EOF is startxref plus the byte offset of that table. A reader seeks to the end, reads that offset, jumps to the table, reads the trailer, follows /Root to the catalog, and loads only the objects it needs. Reading top to bottom is never required.

What is an incremental update?

It is a way to change a PDF by appending. New or replacement objects, a new xref section and a new trailer are added at the end, and the new trailer’s /Prev points at the previous xref. Readers chain through those sections, newest first. Digital signatures rely on this: each signature is appended over the prior state, so any later edit is a detectable further update.

Why can’t I copy text from some PDFs?

Two reasons. If the page is a scanned image there is no text layer at all, only a picture, so there is nothing to select until OCR adds a text layer. If the page does have text but copies as gibberish, the font is missing a /ToUnicode map, so the reader knows which glyph to draw but not which Unicode character it represents.

References