feat(pdf): hand the annotation gesture policy to the viewer - #852
Merged
Conversation
`odr.annotation` acted on input by itself: arming a text tool marked whatever the reader selected next, and the ink overlay's `touch-action` was ours to declare. Both are the viewer's decision — mobile selects first and offers a toolbar, desktop arms a tool and drags — and a library that picks one marks text the reader only meant to select. `mark()` is now the trigger, and `setOptions` carries the rest: `markOnSelection` opts back into the automatic path, `inkPointerTypes` narrows which pointers draw, `touchAction` and `overscrollBehavior` say what a touch on an armed page does. Nothing is marked unless asked. A stroke now extends its own path and writes it once per frame, taking what `getCoalescedEvents` buffered, instead of rebuilding every pending annotation per `pointermove`: 4.9 ms per event with 15 marks pending became 0.054 ms, and no longer grows with what else is on the page. A quad's vertical extent comes from the selection layer's run rather than `Range.getClientRects()`. That rect is sized by whatever font the browser substituted for the layer — half the height of the run here, visibly shorter than the selection — and differs between machines, so the same pdf would not write the same `/QuadPoints` twice. The script outgrew msvc's 16380-byte cap on a string literal, which a crlf checkout reaches a line sooner, so an asset may carry a tail and a `consteval` guard now fails the build everywhere rather than on windows alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018e3PEzyU2oAFSzsEoWsSmz
andiwand
force-pushed
the
feat/pdf-annotation-gesture-policy
branch
from
September 6, 2026 20:42
17eeebf to
a8e4847
Compare
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
odr.annotationdecided things that are the viewer's to decide, and paid for astroke in redraws. Three changes, all inside the emitted script and style.
The host says when to mark
Arming a text tool used to mark whatever the reader selected next. The two
flows are both legitimate and the platforms disagree about which is right —
mobile selects first and offers a toolbar over the selection, desktop arms a
tool and drags — so picking one marks text the reader only meant to select. The
automatic path also has to take the selection with it (
removeAllRanges, orthe next
selectionchangemarks the same range again), which on a phone tearsthe range out from under the native selection handles mid-adjustment.
mark()is the explicit trigger now, and leaves the selection standing.setOptionscarries the rest:markOnSelectionfalsemark()inkPointerTypesnull(any)['pen','mouse']is palm rejectiontouchAction"none""pinch-zoom"keeps two-finger zoomoverscrollBehavior"contain"An unknown key throws rather than being ignored. Where the rendered view sits in
a native scroll container no css we emit can reach it, so the host freezes its
own scroller — it knows when to, having armed the tool. Nothing is added to the
api for that.
The default flip is not a released break (the feature is unreleased), but app
code that only calls
setToolnow needsmark()too.A stroke extends its path instead of redrawing the page
pointermoveappended a point and calledredraw(), which re-serialized everypending annotation on every page. It now appends to the live path's
dandschedules one dom write per frame, taking every sample
getCoalescedEvents()buffered; the full rebuild is kept for undo/remove/clear/resize. A second
pointer can no longer extend a stroke it did not start.
Measured in the rendered view, same page, before and after:
pointermoveThe second row is the point: the cost no longer grows with what else is on the
page. On a phone the old figure lands around 15–25 ms against an 8 ms budget at
120 Hz.
pointerupflushes synchronously, so a window whose rAF is throttledstill ends up with the whole path.
Also added, scoped to an armed page:
-webkit-touch-callout/user-select: none, so a long press cannot interrupt a stroke.A quad is the run's box, not the range's rect
The vertical extent came from
Range.getClientRects(), but the selection layeris transparent text in whatever font the browser substituted — that rect
measured 10.5 px against the run's 18.7 px box, so the highlight was visibly
shorter than the selection the reader was looking at. It is also not
reproducible: the same pdf on two machines with different fonts installed would
write different
/QuadPoints. The quad now follows the.srrun — the line therenderer laid out from the pdf's own metrics. A partly selected run still takes
its horizontal edges from the rects, clamped to the run.
Checks
test/browser/annotation/tests.htmlgoes from 26 to 41 checks — the explicittrigger, the options reaching the css, a rejected pointer type, the live path
growing in the node it started in, and the quad following the run box rather
than the substituted font's rect (the harness's
.sris now an inline-blockwith a taller line box, so the two geometries differ there as they do in a real
view). All pass, as do 387
odr_testcases overhtml.*,Pdf*and the pdfhtml-output suite.
Two things a reviewer should know:
regenerating. Not done here.
a device.
touch-actionis honoured by WKWebView (iOS 13+) and AndroidWebView, but the real check is a phone.
Rationale is recorded in
docs/design/pdf-annotation.mdas decisions 7–9.