PDF snapshot format
A PDF snapshot is UTF-8 text with LF line endings and no byte-order mark, saved as {name}.verified.pdfua.txt.
# pdfua-snapshot v1
document
marked=yes lang=en-US display-doc-title=yes pages=3
...
structure
Document lang=en-US
H1 p1 "Trail Table"
Figure p1 alt="Park logo" images=[3f9c0a1b2d4e5f60]
Table p2-3 summary="Trail details"
TR
TH1 p2 scope=Column "Trail"
TH2 p2 scope=Column "Length"
TR
TD p2 headers=[TH1] "Ridgeline"
TD p2-3 headers=[TH2] "Cedars"
untagged
p1 "Stray text"
p1 images=1 paths=2
fonts
"Arial" TrueType embedded=yes to-unicode=yes glyphs=3f9c0a1b2d4e5f60
layout
p1 text tagged at=72,720 width=85 font="Arial" size=18 color=gray:0 "Trail Table"
p1 text untagged at=72,90 width=38 font="Arial" size=9 color=gray:0 mode=invisible "Stray text"
p1 image tagged at=72,600 size=120x40 pixels=600x200 hash=3f9c0a1b2d4e5f60
p1 path artifact at=72,60 size=468x1 stroke=cmyk:0,0,0,1 line-width=0.5
Sections
Sections always appear in this order. Each can be left out with PdfSection.
| Section | Contents |
|---|---|
document |
Whether the PDF is tagged, suspects=yes when it says its tags may be unreliable, and parent-tree=missing when elements own content but there's no parent tree, or parent-tree-problems=[duplicate-keys,unsorted-keys,next-key-too-low] listing what's wrong with the parent tree's keys; its language, whether the viewer shows the title, page count, and how many objects it refers to that couldn't be read, such as one missing from the file (unreadable-objects=2), when any |
settings |
PDF version, page mode and layout, what happens when the file opens, whether it has JavaScript (in document-level scripts, the open action, document actions, form fields, links and other annotations, page actions or bookmarks) or document actions, encryption and what it permits; viewer preferences, optional-content layers and their configurations (layer-config), embedded files (both-names=no when one isn't named both as F and as UF, and page= for one attached through a comment), and how many reference XObjects the pages' resources and annotation appearances hold (reference-xobjects), when any do |
metadata |
Title, author, subject, keywords; whether dates, producer and creator are present; PDF/UA identifier and revision (pdfuaid-part=2 pdfuaid-rev=2024); XMP title, creator, description, keywords, subject, language and PDF/A conformance, or xmp=unreadable; each PDF Declaration the XMP makes, such as Well-Tagged PDF, with whether it gives claim data (declaration "http://pdfa.org/declarations/wtpdf#accessibility1.0" claim-data=yes), recorded but not checked; every other Info entry (such as a company name or classification label) |
role-map |
Each custom tag type and the standard type it maps to, then the custom types each PDF 2.0 namespace maps, with their namespaces (Tip -> Aside ns="https://example.com/tags" mapped-ns=pdf2) |
pages |
Size (named when standard, such as letter or a4), rotation, tab order; crop, bleed, trim and art boxes, user unit, transparency group and a hash of the page's default color spaces and those of the forms it draws (default-colors=), when any are set; how many artifacts sit inside tagged content (artifacts-in-tagged) and how many hold tagged content (artifacts-holding-tagged), when any do; how many marked-content sequences reuse an MCID from earlier in the same content stream, as when a form with MCIDs is drawn twice (repeated-mcids), when any do; pages that couldn't be read, with a hash of what they hold (p3 unreadable hash=3f9c0a1b2d4e5f60); page label ranges |
bookmarks |
The outline tree, with each bookmark's target: a page and the view it opens at, a web address, another file, the address a form is submitted to, or the type of another action it runs, with what it acts on (action-target="Print": a Named action's name, the fields a ResetForm or Hide action names, or a hash of a JavaScript action's script). Also whether it opens with its children hidden (closed=yes), and its title's color and style (color=#ff0000, style=bold). Or unreadable when it nests more than 256 deep, loops back on itself or has an item without a title |
structure |
One line per structure element, in reading order: type, mapped type, pages, the PDF 2.0 namespace its type is in when it has one other than PDF 1.7's (ns=pdf2, ns=mathml, or any other namespace's URI in quotes) and the mapped type's when that differs (mapped-ns=pdf2), language, alt text, actual text, expansion, title, table attributes (scope, headers, spans, summary), list numbering, a form field's role (field-role=checkbox), whether set on the element or through the class map, the files PDF 2.0 lets it carry, such as a formula's MathML (associated-files=[Supplement:3f9c0a1b2d4e5f60:"formula.mml"]: how each relates, a hash of its contents and its name), any of those table, list and form attributes under the wrong attribute owner (attribute-owners=[Scope:Layout]), a hash of the attributes from /A and the class map that PDiffy doesn't read (other-attributes=), links, the hashes of the images it draws (images=[3f9c0a1b2d4e5f60], the same hash as its layout line), the language, replacement text, alt text and expansion that marked content inside it sets (an empty one as ""), each with the text it covers unless that's all of the element's text (span-lang=[fr-FR:"bonjour"], span-expansion=["Mount":"Mt."]), how many of its marked-content references name no page, or name something that isn't one (mcids-without-page, whose text can't be found), how many point into a form XObject (mcids-in-xobject), how many child elements are nested more than 256 deep and left out (unread-children), how many of its own words come before each child when a child comes before some of them (children-after-words=[2], so a link moved within its sentence shows; left out of a page-limited snapshot that drops one of the element's children), parent=missing when it has no parent entry, parent=wrong when it names an element other than the one that lists it, or parent=unlisted when its parent doesn't list it, so only the parent tree reaches it (such an element follows the rest of the tree, at the top level), id=duplicate when another element has the same ID or id=missing for a Note without one, how many of the children it lists that the tree already reached elsewhere (shared-children; each such child shows only where it was reached first), the marked content and annotations it owns that the parent tree gives to another element or to none (parent-tree=[p2:7->P], or p2:xobject:0->none for marked content inside a form XObject), and the element's text |
artifacts |
Text marked as an artifact (headers, footers, page numbers), per page, with its kind when it gives one (type=Pagination subtype=Footer) |
untagged |
Text that is neither tagged nor marked as an artifact, one line per line of text (and tagged text that no structure element points to, one line per marked-content ID), and a count of such images and paths per page, and of shadings when there are any (shadings=1) |
fonts |
Each font the pages draw with, and each font a text, combo box, list box or signature field's appearance, or a FreeText annotation's, draws with: name, type, whether it's embedded, whether it has a Unicode map, a hash of the outlines its embedded program draws for the text's codes (glyphs=), the character codes its text uses that don't map to Unicode (that text can't be copied, searched or read aloud), those that show the missing-glyph box, and what PDF/UA forbids in its dictionaries and embedded program |
annotations |
Every annotation (links, comments, form widgets and so on), page by page in the order each page lists them: type, tagged or untagged, rectangle, flags (such as print or hidden), how it looks (its border style and width, color, highlight mode, a widget's caption, border color and background, and a hash of its appearance: border=solid:0 color=#0000ff appearance=3f9c0a1b2d4e5f60), where a link goes (a web address, or a page and the view it opens at, or another file, or the address a form is submitted to, or another action's type and what it acts on: action=Named action-target="NextPage"), contents and accessible name, and for a Screen annotation each media clip it can play: its content type and alternate text (media-type=video/mp4 media-alt="Trail tour", then media-type-2= and media-alt-2= for a second clip), also on the structure line of the element that tags it |
forms |
A form line for settings that apply to the whole form, when it sets any: the order calculate scripts run in (calculation-order=["trip.tax","trip.total"]), whether the viewer must draw the fields itself (need-appearances=yes) and an XFA form (xfa=static or xfa=dynamic). Then every form field: type, full name (address.city), page, accessible name, value, options, maximum length, flags (such as required or read-only), default value, the value each check box or radio button widget sends (exports=["Yes"]), alignment when it isn't left (align=center), the font, size and color of typed text (text-font="Helvetica" text-size=auto text-color=gray:0, from the widget, the field or the form) and the field's JavaScript by when it runs, as a hash (scripts=format:1a2b3c4d5e6f7081). A signed signature field prints signed=yes, the signature's format (subfilter=), whether the signed bytes run to the end of the file (covers-file=no after an update saved since signing), whether a reason, location and signing time are given (reason=(present)), and what a certifying signature still allows (certification=form-filling). The signature's bytes and signing time aren't recorded, so signing the same file again gives the same snapshot |
layout |
Every text run, image, painted vector path and sh shading, page by page, each labeled tagged, artifact or untagged. Text runs: position, width along the baseline (width=, which character and word spacing and horizontal scaling change), font, size, color (and the outline's color, stroke=, for text that's both filled and outlined), render mode, angle. A part of a line that's raised or lowered by half a point or more, such as a superscript, is a run of its own. Images: position, size, pixel size, a hash of the pixels and of what decides how they show (a palette or color profile, bit depth, Decode array, masks and smoothing), and for an image mask (an image that's only a shape), the color it paints with (fill=). Paths: bounding box, a hash of their outline measured from the box's corner and, for a filled path, its fill rule (shape=, left out for one horizontal or vertical line, or one rectangle drawn with re with its sides parallel to the page's edges, which the box describes), fill, stroke and line width, and, when they aren't PDF's defaults, the outline's dash pattern (dash=3,2, with dash-offset= when the line starts partway into the pattern), ends (cap=round or cap=square), corners (join=round or join=bevel) and, for mitered corners, how far a sharp corner may reach (miter-limit=4). Shadings painted with the sh operator (a gradient or mesh that fills the clipping area without a path): the area they fill (the clipping area's bounding box, cut to the shading's own box when it has one, or the visible page when nothing clips) and a hash of what they draw. Any of them that a graphics state makes see-through, blends or masks also prints an opacity below 1 for each part it paints (opacity=0.1 for the fill, stroke-opacity= for the outline), a blend mode other than normal (blend=multiply), a rendering intent other than PDF's default, relative colorimetric (intent=perceptual), and a hash of the soft masks that fade it, including what they draw (soft-mask=3f9c0a1b2d4e5f60). Any of them inside a transparency group (a form with /Group) that is itself drawn see-through also gets the group's opacity on top of its own, the group's blend mode when it doesn't set its own (or group-blend=multiply beside its own when it does, listing nested groups' modes innermost first), and the group's soft mask along with its own, so a watermark faded as a group prints opacity=. Any of them that a clipping path, or the box of the form it's drawn in, leaves out entirely prints clipped=yes. A clipping path counts in its real shape, with curves traced in small straight steps. Text drawn in a clipping mode (4 to 7) clips to its letters' shapes, or to their boxes when PDiffy can't read the font's outlines, as with a standard font that isn't embedded. Any of them entirely covered by something opaque drawn later prints covered=yes. Only a shape filled with a plain color or an image without a mask counts as covering, and not when it's in a layer. It covers only what lies wholly inside its real shape. Any of them drawn inside optional-content marked content (/OC ... BDC, a layer a viewer can hide), or by an image or form that puts itself in a layer with its own /OC entry, also lists its layers by name, outermost first (layers=["Draft"]) |
checks |
Only with Check(PdfCheckSet): what the checks find, one line per finding with its rule, page and path (FigureAltText p2 structure > Document > Figure[2]), sorted, or (none) |
Rules
Header
- The first line is the format version. See Format changes for when it changes.
- When anything is ignored, the next lines list it:
# ignored sections: ...and# ignored fields: .... Other options add a line after them:Tolerateadds# tolerances: ...(such as# tolerances: positions 1pt),OnlyPagesadds# only pages: ..., andCheckadds# checks: ...with the rule set and how many rules it has (such as# checks: latest, 42 rules). Comparing two snapshots applies the tolerances their header lists.
Lines and keys
- An empty section prints
(none)and an ignored one prints(ignored), so something disappearing shows up as a change. - Two spaces of indent per nesting level. Keys appear in a fixed order and are left out when they have no value.
- Text is quoted and normalized to Unicode NFC, so a letter typed with a separate accent prints as one letter.
"and\are escaped, a tab and a line break print as\tand\n, and characters a reader can't see or tell apart print as\uXXXX: control and format characters (such as the soft hyphen and zero-width and direction marks), private-use characters, line and paragraph separators, spaces other than U+0020 (such as a no-break space), U+FFFD to U+FFFF and variation selectors. - Pages print as
p1, orp2-3for an element that spans pages. - Table header cells that other cells refer to get names numbered within each table (
TH1,TH2) instead of the PDF's internal IDs. A cell that names a header in another table also names that table, with tables numbered in reading order:headers=[table1:TH1], orno-table:TH1for a header outside every table.
Layout
- Positions and sizes are whole points from the page's bottom-left corner, and a text run's width is in whole points. Font sizes and line widths are rounded to 0.1 pt.
- Colors name their color space:
gray:0.5,#ff0000(RGB),cmyk:0,0,0,1, orpattern:shading:3f9c0a1b2d4e5f60/pattern:tiling:...for gradients and tiled fills, with a hash of what the pattern draws. A path, text or image mask painted with an uncolored tile, which has no color of its own, adds the color it's painted in (pattern:tiling:3f9c0a1b2d4e5f60:#ff0000). A color given in a color space other than a device or pattern one, such as a spot color or an ICC-based one, starts with that space's family and a hash of its definition (separation:3f9c0a1b2d4e5f60:cmyk:0.8,0,1,0), so renaming a spot color or changing a profile shows even when the device color stays the same. So does the color of an uncolored tile painted in such a space through a pattern space like[/Pattern [/Separation ...]](separation:3f9c0a1b2d4e5f60:pattern:tiling:...:gray:0). intent=appears only for content whose rendering intent isn't PDF's default, relative colorimetric (intent=perceptual).mode=appears only for text that isn't drawn the usual way (invisible,stroke,fill-stroke, ...).angle=appears only for text that doesn't run across the page: the angle in degrees, counterclockwise, such asangle=90for text that reads upward. (A page'srotate=is the PDF's own setting, which turns clockwise.)fill=,stroke=and a path'sline-width=appear only for what the path actually paints.- An image's hash is taken from its decoded pixels where PDiffy can decode them, so changing only how it's compressed, such as Flate-compressing an uncompressed image, doesn't change it. A JPEG or JPEG 2000 image is hashed as stored, since PDiffy doesn't decode either format. Re-encoding one almost always changes its pixels anyway, but a re-encoding that keeps them, such as one that only strips metadata, still changes the hash.
- Annotation rectangles use the same convention: the bottom-left corner and size, in whole points.
Links and bookmarks
- A link that goes to a page prints
dest=p3(p?when its page reference isn't a page), the view it opens the page at (view=XYZ,Fit,FitHand so on) and the view's positions in whole points (view-at=72,720, with-for a position the viewer keeps as it is), and an XYZ view's zoom when it sets one (zoom=2). The positions belong toPdfDetail.Positions, so they can be tolerated or ignored. - A named destination is looked up, so it prints like any other. One the PDF doesn't define, or one in another file,
prints
dest-name="...". - A link to another file prints
file="maps.pdf", with the page it opens there foraction=GoToRandaction=GoToE. For an embedded file (action=GoToE),file=is its name, or a path such asdata.pdf/inner.pdffor a file inside another embedded file, with..standing for the file that holds this one. A file an attachment comment holds prints as the comment's page and either its place among the page's annotations or its name:p1#0orp1#chart. - A bookmark prints
-> p2and the view it opens at, oruri="...", orfile="..."withdest=for the page it opens in that file, ordest-name="..."like a link. A bookmark that runs another kind of action prints its type (action=Named) and, for a Named, ResetForm, Hide or JavaScript action, what it acts on (action-target="Print", afterexcept:orshow:when a ResetForm or Hide action is reversed); a SubmitForm action prints the address it sends the form to asfile=. One with no target prints just its title. A bookmark whose children are hidden until it's opened printsclosed=yes, one whose title isn't black printscolor=, and one whose title isn't plain printsstyle=bold,italicorbold-italic. - The open action reads its destination the same way, and prints it in the PDF's order inside
open-action="...": the page, the view, its positions and an XYZ view's zoom (GoTo p1 XYZ 72 720 2). A name the PDF doesn't define prints asnamed intro. Another action prints its type, then what it acts on, as on a bookmark:Named NextPage. The positions aren't left out withPdfDetail.Positions, since the whole value belongs toPdfDetail.OpenAction.
Fonts
glyphs=3f9c0a1b2d4e5f60is a hash of the outlines an embedded TrueType, Type 1, CFF or OpenType program draws for the codes of the font's drawn text, each hashed with its code. A font swapped for one with the same name and widths that draws other shapes changes it; a program subset again with its glyphs renumbered but drawing the same shapes doesn't. A CID font whose encoding isn't Identity-H or Identity-V hashes every glyph its program has, each with its CID, since PDiffy doesn't match its codes to CIDs through any other CMap. It's left out for a font that isn't embedded (its shapes depend on where the PDF is opened), for a font whose text is all invisible, and for a program PDiffy can't read or whose outlines it can't draw.unmapped=[0041-0043,0050]lists hex character codes, with runs shortened to first-last.- A code counts as mapped when the font's ToUnicode map gives it text other than U+0000, U+FEFF or U+FFFE. When the map leaves the code out, a simple font also maps it when its encoding gives it a standard glyph name, and a CID font when it uses a predefined CMap other than Identity-H or Identity-V over one of Adobe's character collections (Japanese, Chinese or Korean).
missing-glyphs=[01]lists the codes that show the font's .notdef glyph, the empty box drawn for a character the font lacks: a simple font's encoding names the code.notdef, or a CID font with an Identity encoding picks CID 0 or maps the code to glyph 0. An embedded TrueType, Type 1, CFF or OpenType program adds the codes whose glyph it lacks.problems=[cid-system-info]lists what PDF/UA forbids in a Type 0 or TrueType font's dictionaries, onePdfFontProblemeach:cid-system-info,cid-to-gid-map,cmap-not-embedded,cmap-writing-mode,cmap-reference,true-type-encodingandsymbolic-encoding, and from an embedded TrueType, Type 1, CFF or OpenType program,glyph-widths,cid-set,char-set,true-type-cmap,symbolic-cmapandunreadable-program.- Text inside form XObjects counts too, under each font it's drawn with.
Text
Text is normalized to Unicode NFC, with runs of spaces, tabs and line breaks collapsed to one space and trimmed. Other spaces inside the text, such as a no-break space, are kept. A space is added between two pieces of text when the baseline changes or the gap is wider than 0.2 em beyond the line's letter spacing, which is read from its narrowest gaps. The same rule joins the pieces of an element's text, so a word a PDF splits across two tagged pieces stays one word. Pieces on different pages, and a piece that starts well to the left of where the last one ended on the same line, are always separated by a space.
What's left out on purpose
These change without changing what anyone reading the document gets:
- Object numbers, compression, object order, stream lengths and cross-reference layout.
- MCIDs and structure element IDs; references are resolved to names.
- Font subset prefixes:
ABCDEF+Arialis recorded asArial. - Dates, document IDs and the producer string: only whether they're present.
ShowProducerAndCreator()prints the producer and creator.
Damaged files
A page that can't be loaded (for example, one with a damaged font) comes back marked unreadable, and its snapshot line
says so, instead of the whole read failing. The same goes for an attachment whose contents can't be decoded, for XMP
metadata that isn't well-formed XML or has a DTD (xmp=unreadable), and for bookmarks where an item has no title. An
unreadable page's line also prints a hash of what decides how the page draws, as the file stores it, including its
content streams, its resources and what they reach, so a change to the page still fails the test (p3 unreadable hash=3f9c0a1b2d4e5f60). An image that can't be decoded is hashed as stored, and a reference to an object the file
doesn't have, or can't find, reads as nothing, as the PDF standard says. The document line counts such objects
(unreadable-objects=2), so one going missing still shows. A reference to object 0, a number no object can have,
isn't counted: when qpdf linearizes a file, it writes one in place of a reference to an object it drops. A file that
can't be opened at all throws InvalidDataException.
PDiffy also reads structure elements and bookmarks only 256 levels deep, so a file built to overflow the stack can't
crash the test run. An element whose children are deeper gets unread-children=N, and their content counts as untagged.
Bookmarks nested deeper, or that loop back on themselves, print unreadable. A Flate-compressed stream that would
decompress to more than 256 MB isn't decompressed. If it holds a page's content, the page comes back unreadable; if it's
an image, the image is hashed as stored; if it's XMP metadata, the snapshot prints xmp=unreadable. See
Limitations for what PDiffy can't guard against.