Skip to content

Carry the reading order across, not just the pages - #3

Merged
tannevaled merged 1 commit into
mainfrom
bump-fax-and-structure
Aug 27, 2026
Merged

Carry the reading order across, not just the pages#3
tannevaled merged 1 commit into
mainfrom
bump-fax-and-structure

Conversation

@tannevaled

@tannevaled tannevaled commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

ops v0.6.0 → v0.7.0, reader v0.4.2 → v0.5.0. No code change.

A structure tree is the order a screen reader reads a document in. It is not the
order the marks sit on the page, and nothing else in the file says it.
Plan.Build was writing a document without one, so a tagged form put through a
shared edit came out untagged: still legible to somebody looking at it, and no
longer navigable by somebody who cannot.

Measured

Two binaries, each verified with go version -m. The harness puts every page
of a file into a plan, rotates the first page, adds a bookmark, builds, writes,
reads the result back and reports what the rebuilt catalogue says:

old  ops v0.6.0   reader v0.4.2   pdffont v0.3.0   forms v0.2.1
new  ops v0.7.0   reader v0.5.0   pdffont v0.3.0   forms v0.2.1

Corpus: all 2 268 forms in /Users/Shared/pdfforms. Prevalence is over the
1 633 real documents.

of the 1 633 real documents
source carries a /StructTreeRoot 1 021
rebuilt document carries one, ops v0.6.0 0
rebuilt document carries one, ops v0.7.0 1 014
top-level structure elements carried across 3 095

One catalogue, verbatim, ca-cra/gst190-fill-26e.pdf:

before: catalog-keys=AcroForm,Lang,MarkInfo,Metadata,Outlines,Pages,Type,ViewerPreferences
        structtree=absent
after : catalog-keys=AcroForm,Lang,MarkInfo,Metadata,Outlines,Pages,StructTreeRoot,Type,ViewerPreferences
        structtree=present keys=IDTree,K,ParentTree,ParentTreeNextKey,RoleMap,Type toplevel=1 rolemap=5

It is the whole tree and not an empty shell: the ParentTree, the RoleMap and
the IDTree that make the elements mean anything all come across with it.

Over all 2 268 files: 1 096 build a different document, 1 096 of them larger, 0
smaller
, and no file changed whether it opens or whether it builds.

The seven

1 021 sources carry a root and 1 014 come across, so seven do not. All seven have
a root with no /K — a ClassMap, a ParentTree, a RoleMap, and no tree
under them:

uk-govuk/apply-for-help-with-court-and-tribunal-fees__CY_EX160_0625_save.pdf
   source: present keys=ClassMap,ParentTree,ParentTreeNextKey,RoleMap,Type toplevel=0 rolemap=5
   built : absent

There is nothing in them to carry. That is the same shape as an AcroForm
dictionary with no fields in it, which this family already declines to treat as a
form. So 1 014 of 1 014 actual trees are preserved, and I would not want the
other seven "fixed" by writing an empty root into the output.

Not measured

Wall clock and peak memory. Three other corpus jobs were running on this machine,
so a timing figure would be fiction. The output diff is unaffected by load.

Gates

go vet, gofmt, exact 100% statement coverage, nine cross-compile targets, all
pass locally with GOWORK=off CGO_ENABLED=0.

ops v0.6.0 -> v0.7.0, which keeps /StructTreeRoot; reader v0.4.2 -> v0.5.0.

A structure tree is the order a screen reader reads a document in. It is
not the order the marks sit on the page, and nothing else in the file says
it. Plan.Build was writing a document without one, so a tagged form put
through a shared edit came out untagged: still legible to somebody looking
at it, and no longer navigable by somebody who cannot.

Measured over the 1 633 real documents in /Users/Shared/pdfforms, both
binaries verified with `go version -m`:

  1 021 source documents carry a /StructTreeRoot.
  ops v0.6.0 carried  0 of them into the rebuilt document.
  ops v0.7.0 carries  1 014.
  3 095 top-level structure elements come across, with the ParentTree,
  the RoleMap, the ClassMap and the IDTree that make them mean anything.

The seven not carried across all have a root with no /K -- a ClassMap and
a RoleMap and no tree under them. There is nothing in them to carry, which
is the same shape as an AcroForm dictionary with no fields, so 1 014 of
1 014 actual trees are preserved.

Over all 2 268 files, 1 096 build a different document and 0 build a worse
one; no file changed whether it opens or builds.
@tannevaled
tannevaled merged commit dc53551 into main Aug 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant