Repository navigation
Conversation
…on adjustments PDFPainter.drawText takes one of two paths. With no position adjustments, or DX-only ones, drawTextWithDX writes the word as one TJ array and sets Tc to the letter spacing, which the viewer adds after every glyph. With general adjustments (a GPOS kern is an x-advance adjustment, so IFUtil.isDPOnlyDX is false) drawTextWithDP writes each glyph with its own Td and Tj and advanced by the glyph width plus the adjustment. It set Tc too, but Tc acts only between glyphs inside a TJ array; with one glyph per Tj and an explicit Td before each, the letter spacing never reached the next glyph. The layout is consistent with the first path: TextLayoutManager puts the letter spaces into the word's elements and its area, and addMappingAreas computes the word-space adjustment on the assumption that the renderer adds the character spacing "even to the last character of a word and to space characters". So a letter-spaced word in a font that positions was measured with its letter spaces and painted without them, and the gap to the next word absorbed the difference: negative letter spacing opens the gap, positive closes it, to the point of overprinting. The letter spacing is now added to the advance on this path, for every glyph, spaces and the last one included, which is what Tc does on the other. Java2DPainter and PCLPainter already add it per glyph in their dp loops, PSPainter applies it through ATJ, AFPPainter converts dp to dx; only the PDF painter was missing it. Measured on this branch, Arimo 11pt, kerning on, "During <fo:inline letter-spacing="-0.417pt">repair,</fo:inline> if however", per-glyph steps read back with mutool draw -F stext: before r>e 3.663 e>p 6.116 p>a 6.116 a>i 6.116 i>r 2.442 r>, 3.058 comma>space 3.047 after r>e 3.246 e>p 5.699 p>a 5.699 a>i 5.699 i>r 2.025 r>, 2.641 comma>space 5.549 After: each step is the advance less 0.417, and r>, carries the kern (-55/1000 em) too. PDFPainterTestCase.testDrawDpTextKeepsLetterSpacing: two zero-width glyphs, a kern of -100 and a letter spacing of 500; the second Td must be 0.4, not -0.1. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ath too GlyphMapping.processWordNoMapping adds letterSpaceIPD x count to a word's width. For a font with GSUB or GPOS tables processWordMapping is used instead. Since FOP-2722 it returns the same letter-space count, but it adds nothing to the width (it was not passed letterSpaceIPD). TextLayoutManager breaks lines on that width while the painter spaces every glyph, so letter-spaced text in an OpenType font was measured short and overran the line. processWordMapping now adds the counted spaces to the width, as the plain path does. GlyphMappingTestCase lays out "word" in DejaVuLGCSerif with no letter spacing and with 3pt; the widths must differ by the three counted letter spaces (they differed by 0). Andreas L. Delmelle traced FOP-2349 to the same "[TBD] - handle letter spacing" in 2015. This keeps FOP-2722's character count rather than counting glyphs. Stacked on FOP-3344: without it, text on the position-adjustments path is not painted with its letter spacing at all, so lines measured correctly would look short. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
plutext
added a commit
to plutext/docx4j
that referenced
this pull request
Oct 3, 2026
…rule leaders on a FOP without setRuleStyle(int) Apache main's FOP-2722 gives GlyphMapping.processWordMapping (the complex-script path) a letter-space count without adding the spaces to the width. fixLetterSpaces trusted the count, so on such a FOP it added one letter space where n were missing; on the fork's merge of Apache main (fop/CR-009) 51 corpus documents regressed, documents without w:spacing included, since docx4j's width fixups write letter-spacing of their own. The fork's CR-010 adds the width and declares letter-space-width (upstream FOP-2349, apache/xmlgraphics-fop#118), but an Apache release may carry FOP-2722 alone. So fixLetterSpaces now takes the spaces already in the width from the path the word took (the font performs substitution or positioning, the test doGlyphMapping makes) and the capability: none on the complex-script path without it, the count otherwise (WordLineLayoutManager.lettersInWidth). Apache's FOP-3325 replaces area.inline.Leader.setRuleStyle(int) with setRuleStyle(BorderStyle); the fork keeps the int form (capability rule-style-int). LBP.setRuleStyle calls it directly there and otherwise finds the setter once by reflection, the int form first, so a rule leader is drawn on FOP 2.11, the fork, or a FOP with FOP-3325. FopCapabilities gains RULE_STYLE_INT and LETTER_SPACE_WIDTH; FopHooks resolves them. Tests: export-fo 235/0 on 2.11-docx4j.2, on Apache FOP 2.11 (-Papache-fop) and on 2.11-docx4j.4-SNAPSHOT (CR-010). Corpora and probes against the 2.11-docx4j.2 baseline: 0 changed with this change on 2.11-docx4j.2, and on 2.11-docx4j.4 with CR-010. On a simulated FOP-2722-only renderer (2.11-docx4j.4 with the bare merge's GlyphMapping and without letter-space-width), 17.3.0 changes 51 documents (real 20, real2 17, real3 9, probes 5) and this change none; spacing-char 0.2857 -> 0.7857, kern-title 0.9250 -> 1.0000, as on 2.11-docx4j.2. Not exercised: the BorderStyle branch (no FOP at hand has FOP-3325 without the int form). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Addresses FOP-2349, reported by Matthias Reischenbacher in 2014. Andreas L. Delmelle traced it in 2015 to
GlyphMapping.processWordMapping's[TBD] - handle letter spacingand sketched this approach. It follows FOP-2722, which gaveprocessWordMappingthe letter-space count but not the width.Stacked on #113 (FOP-3344), which paints letter spacing on the position-adjustments path; the first commit here is #113's. Without #113, a GPOS-kerned font paints no letter spacing at all, so correctly measured lines would look short. Review #113 first; this PR's own change is its last commit.
What goes wrong
GlyphMapping.processWordNoMappingcounts a word's letter spaces and addsletterSpaceIPD × countto its width. For a font with GSUB or GPOS tablesprocessWordMappingis used instead. Since FOP-2722 it returns the same count, but it adds nothing to the width; it is not even passedletterSpaceIPD. The renderer spaces every glyph on either path, so letter-spaced text in any OpenType font is measured short and its lines overrun.Measured on
mainwith #113: Carlito 12pt, 3pt letter spacing, a 260pt line. Complex scripts on: 4 lines, ending up to 23pt past the margin. With-nocs: 5 lines, all inside it.The change
processWordMappingtakesletterSpaceIPDand adds the counted letter spaces to the width, as the plain path does. After it, the complex-script path breaks that paragraph into the same 5 lines as-nocs. The line ends match to within 0.4pt, the GPOS kerningmainapplies even withkerning="false"(FOP-3343, #112). With-nocsnothing moves: every glyph is at the same position as before.Combining marks
On FOP-2349 Glenn Adams objected to a sketch that counted glyphs: this method exists for combining marks, where several glyphs make one character. This change does not count glyphs. It takes FOP-2722's character count, which FOP already uses, and makes the width agree with it. The painter still spaces each glyph, so the two disagree wherever substitution changes the number of glyphs (a ligature, a decomposition) or marks are present. That is unchanged by this PR and not measured here.
Tests
GlyphMappingTestCase.testLetterSpacesInTheWidthOfAWordInAFontWithLayoutTables: "word" in DejaVuLGCSerif with no letter spacing and with 3pt; the widths must differ by the three counted letter spaces. Before the change they differ by 0.🤖 Generated with Claude Code