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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ set(ODR_SOURCE_FILES

"src/odr/internal/pdf/pdf_afm.cpp"
"src/odr/internal/pdf/pdf_afm_data.cpp"
"src/odr/internal/pdf/pdf_annotation.cpp"
"src/odr/internal/pdf/pdf_cid.cpp"
"src/odr/internal/pdf/pdf_cid_data.cpp"
"src/odr/internal/pdf/pdf_cmap.cpp"
Expand Down
41 changes: 19 additions & 22 deletions docs/design/pdf-annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Status: **underway.** This records the architecture for adding markup
annotations β€” text highlight and freehand drawing first β€” to an existing PDF,
the alternatives weighed, and the effort it costs. The format model is
validated against four viewers, and Phases 0 through 1 have landed: the
incremental writer works, the annotations it will carry are not written yet.
validated against four viewers, and Phases 0 through 3 have landed: the writer
appends, and the markup and ink annotations it carries are written.

Scope is **markup only**: draw on top of a page, highlight/underline/strike
text. Editing or removing the *existing* text of a PDF is explicitly out β€” that
Expand Down Expand Up @@ -166,10 +166,8 @@ Notes on the shape:
- **`quads` order is upper-left, upper-right, lower-left, lower-right.** The
spec's stated order (12.5.6.10) is counterclockwise; every implementation
writes the Z-order above, and `pdfAnnotate`'s documentation says as much
outright. Follow the implementations, and say so in a comment at the one place
that emits it. Still **unverified** β€” see *Validated against real viewers*:
an annotation carrying an `/AP` never has its `/QuadPoints` read, so a test
has to reach for a viewer that regenerates the appearance.
outright, as does Phase 2's appearance-less experiment. Follow the
implementations, and say so in a comment at the one place that emits it.
- **`delete` only names an annotation we wrote**, identified by the `/NM` we
minted. Deleting a foreign annotation is out of scope: we would have to prove
nothing else references it.
Expand Down Expand Up @@ -225,9 +223,8 @@ correctly (user-space y 700/688 arrived at page-box y 92/104).
Three things the spike did **not** settle, and Phase 1 and 2 owe tests for each:

- **QuadPoints ordering.** With an `/AP` present, the appearance is what every
one of those engines painted β€” the `/QuadPoints` were never consulted. The
ordering matters only to a viewer that regenerates the appearance, and to
text-selection semantics. The note below stands as a note.
one of those engines painted β€” the `/QuadPoints` were never consulted.
Settled in Phase 2 with an appearance-less annotation instead.
- **A page dictionary inside an object stream**, and **appending to a file
whose newest section is an xref stream.** The spike's fixture had neither;
Phase 1's tests cover both.
Expand Down Expand Up @@ -279,17 +276,22 @@ ghostscript, CoreGraphics and our own renderer all honour the new rotation
A page dictionary living inside an object stream is rewritten uncompressed in
the new section, the newer type-1 entry winning over the older type-2 one.

### Phase 2 β€” highlight (2 d, ~200 lines)
### Phases 2 and 3 β€” text markup and ink β€” **done** (#847)

Annotation dictionary + appearance builder + `/Annots` append. The test is the
round trip: write, re-open with `DocumentParser`, assert the appearance resolves
and the rendered page carries a `mix-blend-mode:multiply` rect at the expected
position.
`pdf/pdf_annotation.{hpp,cpp}`: `write_text_markup` covers `/Highlight`,
`/Underline`, `/StrikeOut` and `/Squiggly`; `write_ink` covers `/Ink`, its
strokes smoothed Catmull-Rom β†’ cubic bezier. `append_page_annotations` puts
them on the page, rewriting the `/Annots` array itself where it is indirect.

### Phase 3 β€” ink (1–2 d, ~150 lines)
Only the highlight multiplies (11.6.4.1) β€” it is a wash over the text, where
the others are marks drawn on top of it. Opacity rides on the annotation's
`/CA` alone, which a viewer applies to the whole appearance; setting `ca` in
the appearance's own state as well would square it.

Stroke smoothing (Catmull-Rom β†’ cubic bezier) into the appearance stream.
`/BS /W`, round caps/joins.
**`/QuadPoints` ordering is settled** against two appearance-less files that
force a viewer to synthesize one: ghostscript draws the Z-order as a clean
rectangle and 12.5.6.10's counterclockwise order as a twisted blob.
CoreGraphics synthesizes nothing at all, so it is no oracle here.

### Phase 4 β€” public API (1 d, ~130 lines)

Expand Down Expand Up @@ -368,8 +370,3 @@ Medium:
the `Decryptor` key accessor need to land in v1 after all?
- **Where does the pending-annotation state live across a reload** in the mobile
WebView β€” the browser only, or does the host persist the payload?
- **How do we test `/QuadPoints` ordering at all?** Every engine we have as an
oracle paints the `/AP` and ignores them. Options: write one annotation
*without* an appearance and see where a viewer puts it, or check what Acrobat
does with our file. Cheap either way, but it needs deciding before Phase 2
claims the ordering is right.
Loading
Loading