Skip to content

pdf: search marks sit about three letters right of the hit in WebKit #1279

Open

Description

@andiwand

Found by the iOS session while it moved OpenDocument.ios to 7.6.0 (opendocument-app/OpenDocument.ios#219). iPhone 18 Pro simulator, iOS 27.0, default dual layer.

Repro:

  1. Make the file: soffice --headless --convert-to pdf book.txt, where book.txt holds 60 chapters of 39 lines like "This is line 1 of chapter 3. The quick brown fox jumps over the lazy dog."
  2. Search chapter 3.

What happens: the count is right (440 in that file), and the search reaches pages that have not loaded. But each yellow mark covers "pter 3. ", not "chapter 3": it starts about three characters late and runs past the end. Page 1 shows it too, so lazy loading is not the cause. The marks are opaque, so they also hide the letters they cover.

What I measured in Chrome: the mark is where it belongs. In the 38-page file, the first "chapter 1" mark starts at x=230, and the monospaced visible text puts that word at about 229. The .sel run is <span class="sr f0 w1">. It sets its text in the substitute face sf, sans-serif and stretches the run to the PDF's width (w1). So a character's place in the run depends on the metrics of the substitute font. My guess: in WebKit that face resolves to another font, or WebKit treats one of its descriptors differently, so positions drift within the run. Not checked in WebKit yet.

To do:

  • Measure the mark against the .vis glyphs in WebKit (Safari or the simulator).
  • Compare the computed font of the .sr run in WebKit and Chrome.
  • Then fix the metrics, or position the mark from the visible glyphs.
  • Separately: the highlight could be translucent, or mix-blend-mode: multiply, so it never hides the text below it.

Not compared with 7.5.2: there, the same search did not jump to a hit at all.

Activity

  1. andiwand commented on Oct 25, 2026

    @andiwand
    MemberAuthor

    Measured in WebKit by the iOS session (iOS 27.0 simulator, WKWebView, AppleWebKit/605.1.15), core 7.6.0, book.pdf, odr.search('chapter 1.'), the first mark on page 1:

    • The mark: "chapter 1.", x 123.85, w 25.72 (viewport px, page fitted to the width).
    • Its .sr span: sr f0 w2, 73 characters ("This is line 1 of chapter 1. The quick brown fox jumps over the lazy dog."), x 43.36, w 275.76.
      • computed: font-family: sf, sans-serif, 24px, display: inline-block, width 1051.26px (.w2 = 788.47pt), white-space: pre, text-align-last: justify, word-spacing: 0
      • parent .t: font-size: 0, transform: matrix(0.555556, 0, 0, 0.555556, 0, 0)
    • The face: @font-face { font-family: sf; src: local("Arial"), local("Helvetica"), local("Liberation Sans"), local("DejaVu Sans"), local("Nimbus Sans"); size-adjust: 50%; } is loaded.

    What that means. Relative to the .sr box, the mark starts at 29.2 % and is 9.3 % wide. The hit is characters 18 to 28 of 73, so in the monospaced line it starts at 24.7 % and is 13.7 % wide. The substitute's letters are narrower than the PDF's, and the justification puts all the extra width into the 14 spaces. So each word sits right of its glyphs in the visible layer, and the shift grows along the line.

    Why Chrome looks right: not known yet. In Chrome the first "chapter 1" mark starts at x=230, and the monospaced text puts it at about 229. Chrome may resolve sf to a face with other widths, or it may not justify a white-space: pre run the same way. Worth checking before a fix.

    Directions for a fix:

    • spread the extra width per character instead of per space: a letter-spacing from the PDF's advance widths, or text-justify: inter-character where a browser supports it
    • or make the substitute's average advance match the PDF font's, through size-adjust per font, so that justification has little to spread
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions