feat(pdf): write markup and ink annotations - #847
Merged
Conversation
`write_text_markup` for `/Highlight`, `/Underline`, `/StrikeOut` and `/Squiggly`, `write_ink` for `/Ink`, and `append_page_annotations` to put them on a page — rewriting the `/Annots` array itself where the page holds it indirectly. Each carries the appearance stream it paints through, since our own renderer draws annotations only from `/AP /N` and viewers disagree on the ones they synthesize. Only the highlight blends Multiply: it is a wash over the text where the others are marks on top of it (11.6.4.1). Ink strokes are smoothed Catmull-Rom into the cubic beziers PDF has. The quad order is upper-left, upper-right, lower-left, lower-right. 12.5.6.10 says counterclockwise; rendered without an appearance, that order comes out a twisted quad and this one comes out a rectangle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018e3PEzyU2oAFSzsEoWsSmz
`/CA` already applies to an annotation's appearance as a whole (12.5.2), and our own extractor composites it as a group — so the `ca`/`CA` the appearance stream set in its own `/ExtGState` applied the opacity a second time. The state now carries only the highlight's `/BM /Multiply`, and nothing else needs one. Also binds the header-declared functions to `odr::internal`, folds the two `box_of` overloads into one, and drops the lambda parameter the test never used, which broke the `-Werror` builds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147n68S7LNAv9KynLaGgXN4
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.
🤖 Generated with Claude Code
Phases 2 and 3 of
docs/design/pdf-annotation.md, on top of the writer from #845. First PR of four; the public API, browser layer and bindings follow.pdf/pdf_annotation.{hpp,cpp}:write_text_markup—/Highlight,/Underline,/StrikeOut,/Squigglywrite_ink—/Ink, strokes smoothed Catmull-Rom → the cubic beziers PDF hasappend_page_annotations— puts them on a page, rewriting the/Annotsarray itself where the page holds it indirectly rather than rewriting the pageEvery annotation carries its own appearance stream (decision 3): our renderer draws annotations only from
/AP /N, and viewers disagree about the ones they synthesize. Only the highlight blendsMultiply— it is a wash over the text, where the others are marks drawn on top of it (11.6.4.1)./QuadPointsordering — settledThe doc has carried this as unverified since the spike, because an annotation with an
/APnever has its quad points read, so no oracle we had could test the ordering.Resolved by writing two files with the same visual rectangle and no appearance, forcing the viewer to synthesize one:
So the Z-order every implementation writes is right and the spec's literal order is wrong, and the comment in the code now says so with evidence behind it. Worth knowing for anyone repeating this: CoreGraphics synthesizes no appearance at all, so it is no oracle for this question.
Verified
Eight assertion tests: round trips for highlight and ink, all four markup subtypes, the blend-mode split,
/Rectas the union of quads, the ink box grown by the stroke width,/Tand/Contentsomitted when empty (and a(in/Contentssurviving escaping), several annotations sharing one page rewrite, and appending tostyle-various-1.pdf's existing/Annots.End to end on a real file with all five kinds at once:
qpdf --checkclean, CoreGraphics paints every one with the text showing through the highlight, and odr's own renderer emits the highlight as<path fill="rgb(255,230,51)" style="mix-blend-mode:multiply">and the ink asCbeziers — the self-verifying round trip the design predicted.18 annotation/writer tests pass;
-Werrorclean; clang-tidy clean.No consumer-visible change yet — the public API is the next PR — so no changelog entry.