Skip to content

fix(html): a selection's end keeps its handle in a pdf view - #917

Merged
andiwand merged 1 commit into
mainfrom
fix/a-selection-end-keeps-its-handle
Sep 20, 2026
Merged

andiwand merged 1 commit into
mainfrom
fix/a-selection-end-keeps-its-handle

Conversation

@andiwand

@andiwand andiwand commented Sep 20, 2026

Copy link
Copy Markdown
Member

Marking text in a pdf left the selection one handle, so the mark could not be adjusted from that side.

Why

The selection layer's runs (.sr) carried overflow:hidden, which makes them scroll containers. A run's box is the pdf's own advance, and the substitute system font is often wider than it, so the text spilled and was clipped. Measured on the reported document: scrollWidth 71 against clientWidth 64, and the selection rectangle ended at x=339 while the run's box ended at x=334. A webview draws no handle for an end it cannot see.

Overriding overflow:visible live on the device brought the second handle back, which is what pointed at the clip.

The catch, and what it costs

The clip was also carrying the y alignment: an inline-block baseline-aligns on its bottom margin edge only while it is a scroll container. Dropping the clip alone moved every run down - measured on the device, 126.9 becoming 130.5 - which would slide the invisible layer off the glyphs it has to sit on.

overflow:clip does not help: it is not a scroll container either, so it aligns like visible while still clipping.

So the alignment is now stated rather than inherited. .t sets font-size:0, so the strut is zero-height and sits on the baseline; with vertical-align:bottom every run's bottom edge lands on that baseline, whatever the run's height - which is what the bottom margin edge was doing.

.sg, the gap spacer, keeps its clip: it holds a space, so no handle can land in it.

Checked

On a Pixel 9 Pro, over the reported document, with the two rules swapped on the same live page: all 23 runs of the page sit at the same position to the pixel (maxDelta: 0).

The selection layer's runs were scroll containers, so a system font wider
than the pdf advance was clipped. A webview draws no handle for an end it
cannot see, so marking such a run left one handle, and the mark could not be
adjusted from that side.

Nothing clips a run now. The spill is invisible text either way - the layer's
color is transparent. The clip did carry the y alignment, because an
inline-block baseline-aligns on its bottom margin edge only when it is a
scroll container, so `vertical-align:bottom` states that alignment instead:
the strut is zero-height and sits on the baseline, so a run's bottom edge
lands there whatever the run's height.

Measured on a Pixel 9 Pro, over the reported document: every run of the page
sits where the clipped rule put it, to the pixel.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FJJdfqpnVCKBNHXjAVxSou
@andiwand
andiwand force-pushed the fix/a-selection-end-keeps-its-handle branch from bff0097 to 0234031 Compare September 20, 2026 14:41
@andiwand
andiwand merged commit 2a6149c into main Sep 20, 2026
25 checks passed
@andiwand
andiwand deleted the fix/a-selection-end-keeps-its-handle branch September 20, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant