Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,35 @@ form submission, the annotation types that exist to play or embed
something, and files associated with a page. A link to the web is not
executable and stays.

### What a screen reader follows

A tagged document says which run of marks on which page is a heading, a
paragraph, a table cell, the label of a form field. That is the reading order a
screen reader follows, and for a government form it is often what the law
requires — RGAA in France, Section 508 in the United States, EN 301 549 in the
EU. It cannot be copied across a rebuild: every part of it points into the
document, and the number tree that indexes it is keyed by a number each page
carries. So it is rebuilt, element by element, around the pages that survived,
and those numbers are handed out afresh. A page that kept the number it had in
a file it is no longer part of is a page a reader would look up and be told,
with every confidence, about somebody else's.

An element whose every page has gone is removed and its parent pruned; one with
some pages gone keeps the children that are left. An empty table cell is kept,
because the shape of a table is part of what it says. A page written twice
carries the structure once, on the first copy, since an element names one page.
Merging two files carries no tree at all: two files have two role maps in which
the same name may mean two different things, and there is no honest way to
choose between them.

Of **1 633 real government forms** from eighteen issuers, 1 021 carry a
structure tree. After a rotate **1 014 still do** — the seven that do not are
the seven whose tree was empty in the file we were given — and **991 of the
1 021 are identical down to the last element, mark and annotation reference**.
The thirty that differ are accounted for one by one in the commit that added
this. What cannot be carried is named in `catalogue.go` with the reason, rather
than disappearing quietly.

Text is drawn in the four faces every viewer already has — Helvetica,
Helvetica-Bold, Courier and Courier-Bold — so nothing is embedded and a
watermark costs about a kilobyte. Stamp text may say `{page}`, `{pages}`
Expand Down
14 changes: 12 additions & 2 deletions annots.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ var annotKeysRebuilt = map[reader.Name]bool{
"Dest": true, // remapped
"A": true, // remapped, and filtered when sanitising
"AA": true, // an annotation's own actions run without anyone asking

// The number under which an annotation is filed in the structure tree's
// parent tree is handed out afresh, for the same reason a page's is.
"StructParent": true,
}

// RemoveAnnotations drops every annotation: links, comments, form fields and
Expand Down Expand Up @@ -124,10 +128,16 @@ func newKeptAnnots() *keptAnnots {
return &keptAnnots{at: map[annotKey]reader.Ref{}, dict: map[reader.Ref]reader.Dict{}}
}

// add records one annotation that survived.
// add records one annotation that survived. When a page is written twice — a
// selection may ask for the same page more than once — the first copy is the
// one anything pointing at that annotation is pointed at, which is the same
// choice the destination map makes for the page itself and the copy the
// structure tree describes.
func (k *keptAnnots) add(src *reader.Document, was reader.Object, ref reader.Ref, dict reader.Dict) {
if old, ok := was.(reader.Ref); ok {
k.at[annotKey{src, old.Num}] = ref
if _, already := k.at[annotKey{src, old.Num}]; !already {
k.at[annotKey{src, old.Num}] = ref
}
}
k.dict[ref] = dict
k.order = append(k.order, ref)
Expand Down
33 changes: 25 additions & 8 deletions catalogue.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,37 @@ var sensitiveKeys = map[reader.Name]bool{"Metadata": true}
// rather than describing it, so copying one across a rebuild would leave it
// naming objects that are no longer there.
//
// - /StructTreeRoot, the marked-up structure a screen reader follows. Its
// elements name the page each belongs to and the numbered marks inside
// that page's content, and its parent tree is indexed by a number the page
// carries. Carrying it means rebuilding all three, and a structure tree
// that points at the wrong pages is worse than none: a reader would read
// the document aloud in the wrong order rather than fall back on the text.
// This is the one worth doing next.
// - /Names, the name trees: named destinations point at pages, embedded
// files travel with the document, and one of the trees is where a file
// keeps its JavaScript.
// - /Perms, which records what a signature allows. Every verb here rewrites
// the bytes the signature was taken over, so the signature is void and the
// permission it granted with it.
// - /OpenAction and /AA, which run when the document is opened.
//
// /StructTreeRoot, the marked-up structure a screen reader follows, is
// rebuilt: see structtree.go. Three parts of it are left out, and each is left
// out because it cannot be placed rather than because it is awkward.
//
// - A mark inside a stream that no surviving page draws. Such a mark is
// numbered within its own stream and filed under a key that stream
// carries, so it can only be carried when the stream is still drawn — and
// 109 of the corpus's 215 such marks named a stream that no page of the
// source drew either.
// - A structure element's /Ref, which names other structure elements. It
// cannot be answered while the rebuild is still deciding which of them
// survive, and copied as it stands it would drag a second copy of the
// source's tree — and of the source's pages behind it — into the file. No
// file in the corpus has one; it is PDF 2.0.
// - The structure of pages from more than one file. Two files have two
// trees, and two /RoleMap and /ClassMap dictionaries in which the same
// name may stand for two different things; a merged tree read through
// either one of them would describe the other file's pages wrongly, and
// there is no honest way to choose. Such a document keeps its pages and
// nothing above them, as it already did for the catalogue and the form.

// keepCatalogue carries across what the source document said about itself.
func (d *Doc) keepCatalogue(w *reader.Writer, catalog reader.Dict, kept *keptAnnots) {
func (d *Doc) keepCatalogue(w *reader.Writer, catalog reader.Dict, kept *keptAnnots, built []builtPage) {
src, ok := d.singleSource()
if !ok {
// Pages from several files have several catalogues, and there is no
Expand All @@ -77,6 +91,9 @@ func (d *Doc) keepCatalogue(w *reader.Writer, catalog reader.Dict, kept *keptAnn
if form := d.keepForm(w, src, source, kept); form != nil {
catalog["AcroForm"] = w.Add(form)
}
if tree := d.keepStructure(w, src, source, kept, built); tree != nil {
catalog["StructTreeRoot"] = tree
}
}

// singleSource is the one document every page was borrowed from, when there is
Expand Down
Loading
Loading