Repository navigation
Editor: imported pages bring their structure elements (#183) - #226
Merged
Merged
Conversation
The /BBox of an annotation's appearance stream is mapped onto its /Rect, and the border of a field lies on the edge of that box. With the box off the pixels, the exact clip and the border's edge, both antialiased, lightened the edge pixels twice: a field Editor.SetFields wrote was drawn with other edges than the same value shown from a FormState, which is not clipped. The clip of an annotation's appearance is now rounded outward to whole device pixels when it stays on the axes, as PDFium, MuPDF, Poppler and Ghostscript clip rectangles. The accuracy report against PDFium is unchanged. overlayFile puts its fields off the pixels, which the test held them to. Fixes #222.
The first fix of #222 rounded the clip of every annotation's appearance outward to whole pixels. That made the render of an appearance stream differ from the page it is flattened to, whose content clips exactly: the corpus API run reported 41 files of pdf.js whose flattened first page differed, by up to 4.9 % of its pixels. The other way round: the appearance cera generates for a widget is clipped to the widget's box exactly, as the stream Editor.SetFields writes for it and the form Editor.Flatten draws are. The render with a FormState, the saved file and the flattened page are drawn alike at any scale; the border of a field off the pixels is lighter on its outer edge in all three, as the appearance streams of documents are. The corpus API report and the accuracy report against PDFium are the same as on main.
ImportOptions.Structure brings the elements of the source's structure tree whose content is on imported pages, with the elements above them; an element left without content is dropped. Where the result has a tree, the elements follow its own in a Part under its Document element (or its root): the source's Document becomes the Part, or one is made around its elements, with the source's /Lang where it differs. A result without a tree takes the first one brought as its own. The keys of the parent tree are numbered anew after the result's: pages (/StructParents), their annotations (/StructParent) and the form XObjects they draw. A page imported twice is tagged once, its first copy; a form only pages that did not come draw as tagged content loses its keys. Marked content and object references are mapped to the copies. Role maps are merged, a type the result maps otherwise written as what the source means by it; a class the result defines otherwise is renamed Cls_2, an identifier the result has id_2; namespaces are joined. /MarkInfo says /Marked. The corpus API run gets a step: the pages looked at, imported with their structure into the document itself, keep their marked content in the tree. Over the pdf.js corpus it finds no difference. Fixes #183.
This was referenced Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #183. Stacked on #225 (merge that first; this PR then shows only its own commit).
API
What is brought (
internal/pdfedit/structure.go)/StructTreeRootwith content on an imported page (marked content, MCRs, OBJRs), with the elements above them; an element left without content is dropped./Partunder its single/Documentelement, or under its root when it has none: the source's own/Documentbecomes thePart, or aPartis made around its top elements. ThePartgets the source catalogue's/Langwhere it differs from the result's. A result without a tree (NewEditor, or an untagged edited document) takes the first tree brought as its own; further sources becomeParts under itsDocument./StructParentsof pages,/StructParentof their annotations and/StructParents//StructParentof the form XObjects they draw are renumbered from the result's/ParentTreeNextKey; the parent tree is written anew as one flat number tree (the result's entries plus the new ones). A page imported twice is tagged once, its first copy (the second copy and its annotations have no keys). A form XObject that only pages which did not come draw as tagged content loses its keys rather than keep a dangling one./Pg, MCR/Stm, OBJR/Obj(the first copy of the annotation) and/Refare mapped to the copies; references to elements that are not kept become null or are left out./RoleMapmerged; where the result maps a type otherwise, the copied elements are written with what the source means by it (Heading→H1under a result that mapsHeading→P)./ClassMapmerged, a class defined otherwise renamedCls_2(in/Ctoo)./IDTreerebuilt with the kept elements, an identifier the result has renamedid_2./Namespacesjoined./MarkInfogets/Marked true, the result's other entries kept.Deviation from the issue's text: the parent tree is written as one flat
/Numsarray instead of being extended in place; number trees cannot take more entries without their/Limitsbeing rebuilt, as for the destinations tree in #135.Tests
TestImportStructure: into a tagged document (role, class and ID conflicts,Langof the part), into an untagged one, into a new one, from two documents into a new one; save and update. Every case checks the tree asDocument.Structurereads it and the parent tree against the elements (parentTreeErrors: every page, annotation and form key leads to the elements that name its content, every entry is used, keys below/ParentTreeNextKey).TestImportStructureTwice,TestImportStructureOptimised(object streams, linearised, pruned, joined, compressed),TestImportNoStructure(unchanged default).cmd/corpus run -api) gets the step import structure: the pages looked at, imported with their structure into the document itself (opened a second time), must keep as much marked content in the tree as the originals. pdf.js corpus, 3 pages per file: no difference; only the refusals every other save step has (signed, no assembly permission).go test ./...,-tags cera_nogyregreen locally; lint clean but the CRLF noise.Docs:
docs/features.mdupdated (editor list, "not done" list, tagged PDF row).