fix(pdf): rest glyph boxes on the real descender (row detection was systematically shifted) - #16
Conversation
Two independent bugs, both leaving every glyph box too high. First, the 14 standard fonts may omit /FontDescriptor for exactly the reason they may omit /Widths -- a consumer is expected to know their metrics -- so Ascent and Descent stayed 0 and a glyph box collapsed to [baseline, baseline+size] instead of resting on the descender. Bundles the AFM Ascent/Descent alongside the widths, generated from the same Adobe data pdfminer.six ships rather than transcribed. Symbol and ZapfDingbats are deliberately absent: their AFMs genuinely carry no Ascender/Descender and pdfminer reads them as 0, so substituting their FontBBox would break parity. Second, and it would have survived the first fix: descent was scaled by 0.001 but not by the font size. It is stored in /1000ths of an em, so reaching text space needs both factors -- the same two the advance width already gets. pdfminer does get_descent() * fontsize. Without it the descender was a fixed fraction of a point instead of a fraction of the glyph, so even a font that DID supply a descriptor was short by a factor of the font size. This is the vertical counterpart of the width fix and matters for the same structural reason: columns come from word X extents, rows come from word Y extents. A systematic vertical shift of ~20% of a font size can merge or split table rows, which corrupts the table an LLM then reasons over. Measured against pdfplumber on the golden fixtures, Y drift goes from 2.484pt at 12pt and 4.968pt at 24pt -- both exactly 0.207*size, matching Helvetica -207/1000 -- to 0.0000pt. The golden envelope drops from 6pt to 0.01pt, so both axes are now asserted at the same tolerance as page dimensions. Re-checked on 3M 2018 10-K: all 103 negatives still intact and the balance sheet rows unchanged.
Reviewer's GuideFixes glyph bounding box vertical positioning by supplying AFM-based ascent/descent metrics for standard-14 fonts and correctly scaling descent by font size, tightens golden position tolerances, and adds tests and documentation to pin metrics behavior and parity with pdfminer.six. Sequence diagram for applying standard-14 vertical metrics to glyph bounding boxessequenceDiagram
participant Reader
participant Font
participant Standard14VMetrics
participant Interpreter
participant GlyphBBox
Reader->>Font: readFont(ref)
alt Standard14 with missing FontDescriptor
Reader->>Standard14VMetrics: Standard14VMetrics(baseFont)
Standard14VMetrics-->>Reader: VMetrics(Ascent, Descent), ok
Reader->>Font: set Ascent, Descent from VMetrics
else Non-standard or Symbol/ZapfDingbats
Reader->>Font: keep existing Ascent, Descent
end
Interpreter->>Font: showString(s)
Interpreter->>Font: get Descent, CharWidth(cid)
Interpreter->>Interpreter: dxScale(fontSize)
Interpreter->>Interpreter: descent = font.Descent * 0.001 * fontSize
Interpreter->>Interpreter: adv = font.CharWidth(cid) * dxScale
Interpreter->>GlyphBBox: build [0, descent+rise, adv, descent+rise+fontSize]
Interpreter->>GlyphBBox: ApplyRect(combined, bbox)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reached
Next review available in: 51 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
The vertical counterpart of #9. Targets
maindirectly — the rest of this chain (#9, #13, #14, #15) has landed.Two independent bugs, both pushing every glyph box too high
1. Missing metrics. The 14 standard fonts may omit
/FontDescriptorfor exactly the reason they may omit/Widths— a consumer is expected to already know their metrics. SoAscent/Descentstayed 0 and a glyph's box collapsed to[baseline, baseline+size]instead of resting on the descender.2. Missing scale factor — and this one would have survived fixing the first. Descent is stored in /1000ths of an em, so reaching text space needs both
0.001and the font size, the same two factors the advance width already gets:pdfminer does
get_descent() * fontsize, whereget_descent()is alreadydescent/1000. Without the font size the descender was a fixed fraction of a point rather than a fraction of the glyph — so even a font that did supply a descriptor was short by a factor of the font size. At 12pt that is 12×.Why it matters structurally
Columns come from word X extents; rows come from word Y extents. A systematic vertical shift of ~20% of a font size can merge or split table rows — corrupting the table an LLM then reasons over. It also nudged citation-highlight overlays (#11) upward on the page.
Measurement
Against pdfplumber on the golden fixtures:
Both were exactly
0.207 × size— Helvetica's −207/1000 descender, which is what identified the cause.The golden envelope drops from 6pt to 0.01pt, so both axes are now asserted at the same tolerance as page dimensions. X was already 0.01 from #9; Y sat at 6pt explicitly flagged as documenting a defect rather than blessing it.
Data provenance
afm_vmetrics.gois generated from pdfminer.six'sfontmetrics.py— the same Adobe AFM data, and the implementation we measure parity against — not transcribed by hand.Symbol and ZapfDingbats are deliberately absent: their AFMs genuinely carry no Ascender/Descender entry, and pdfminer reads them as 0. Substituting their
FontBBoxwould be defensible but would diverge from the reference. A test pins the absence so it reads as a decision rather than an oversight.Verification
go build,go vet,go test ./... -count=1 -race— all green.TestDescentScalesWithFontSizeasserts the pre-fix formula does not coincide with the correct one at any tested size, so the test genuinely detects the regression rather than passing by accident.Closes HAL-510
Summary by Sourcery
Correct glyph vertical positioning using real descender metrics and tighten coordinate parity with pdfplumber.
Bug Fixes:
Enhancements:
Documentation:
Tests: