Regression found against commit b386ee8 (PR #79, "weight common attribute silently no-ops outside Layout orientation=\"horizontal\""), which introduced a new dedicated text-wrapping module (docraft_loom_text_wrapper.cc/.h). Found while re-validating five previously-fixed issues against the current main for an unrelated test-document suite — this one is new, not one of those five.
Summary
When a <Text> (bare, or inside a <Cell>) has two or more space-separated words and its natural (unwrapped) width exceeds its box width, the wrap-boundary word can be printed complete on one line and then repeated — reprocessed as if it were unplaced — on the following line(s), instead of being cleanly split once. The result is corrupted, misleading duplicate text in the rendered PDF, not just a layout/cosmetic issue.
A single word alone (no other word on that Text) wraps correctly via character-level breaking, no duplication. The bug requires ≥2 words.
Minimal reproducer
<Document>
<Body margin_left="40" margin_top="20">
<Text font_size="9" width="56">A. Balasubramanian</Text>
<Blank height="10"/>
<Text font_size="9" width="56">Balasubramanian</Text>
<Blank height="10"/>
<Text font_size="9" width="56">The quick brown fox jumps</Text>
<Blank height="10"/>
<Text font_size="9" width="200">A. Balasubramanian</Text>
<Blank height="10"/>
<Text font_size="9" width="30">Balasubramanian</Text>
</Body>
</Document>
docraft_tool dup.craft dup.pdf
pdftotext -layout dup.pdf -
Output (pdftotext -layout, matches the visual PDF — not an extraction artefact, confirmed by rendering to PNG at 150 dpi):
A. Balasubramanian <- case 1: two words, width=56 (too narrow)
Balasubrama ink shows the FULL first word AND a
nian second, partial copy of it
Balasubramanian <- case 2: one word alone, width=56 (control)
clean single wrap, no duplication -- correct
The quick brown <- case 3: five short words, width=56, no
brown fox jumps single word individually exceeds width
jumps "brown" and "jumps" both duplicated
A. Balasubramanian <- case 4: two words, width=200 (fits) -- correct
Balasubramanian <- case 5: one word alone, width=30 (forces
Balasu 3-way char split) -- clean, no duplication,
brama confirms single-word char-splitting itself
nian is fine
Cases 2 and 5 (single word, any width) are correct. Cases 1 and 3 (≥2 words, wrap required) both duplicate. Case 3 is the more concerning variant, since it shows this is not limited to the long-unbreakable-word / character-splitting fallback path — it also corrupts normal multi-word wrapping where no individual word is even close to the box width.
Also note in case 1/3 the first line does not appear to respect width at all ("A. Balasubramanian" and "The quick brown" both print wider than the declared box) — the constraint seems to only kick in on the second pass, which then re-includes content already placed by the first.
Real-world trigger
Found via a landscape <Table> column (<Cell width="…">) holding a Firstinitial. Surname value ("R. Balasubramanian") one row-owner field among six on a reconciliation report. Every other owner name in the same dataset was short enough to fit its column without wrapping and rendered fine — only the one row whose content genuinely needed to wrap corrupted. This will affect any data-driven table (names, addresses, free-text descriptions) where cell content length varies and only some rows are long enough to require a wrap.
Impact
Rated above the five just-fixed issues on the "silent corruption" axis: earlier issues either failed loudly (good) or dropped/warned about missing content. This one actively fabricates incorrect text in the output PDF with exit code 0 and no diagnostic — a document can render "successfully" while showing garbled duplicated names/words, which is worse for a compliance or financial document than an honest failure.
Environment
docraft_tool, built from main at b386ee8 (macOS, AppleClang 21, Release, -DBUILD_TESTS=ON, 426/426 existing unit tests pass)
- Confirmed both as a bare
<Text width="…"> and inside a <Table><Cell width="…">
Regression found against commit
b386ee8(PR #79, "weight common attribute silently no-ops outsideLayout orientation=\"horizontal\""), which introduced a new dedicated text-wrapping module (docraft_loom_text_wrapper.cc/.h). Found while re-validating five previously-fixed issues against the currentmainfor an unrelated test-document suite — this one is new, not one of those five.Summary
When a
<Text>(bare, or inside a<Cell>) has two or more space-separated words and its natural (unwrapped) width exceeds its box width, the wrap-boundary word can be printed complete on one line and then repeated — reprocessed as if it were unplaced — on the following line(s), instead of being cleanly split once. The result is corrupted, misleading duplicate text in the rendered PDF, not just a layout/cosmetic issue.A single word alone (no other word on that
Text) wraps correctly via character-level breaking, no duplication. The bug requires ≥2 words.Minimal reproducer
Output (
pdftotext -layout, matches the visual PDF — not an extraction artefact, confirmed by rendering to PNG at 150 dpi):Cases 2 and 5 (single word, any width) are correct. Cases 1 and 3 (≥2 words, wrap required) both duplicate. Case 3 is the more concerning variant, since it shows this is not limited to the long-unbreakable-word / character-splitting fallback path — it also corrupts normal multi-word wrapping where no individual word is even close to the box width.
Also note in case 1/3 the first line does not appear to respect
widthat all ("A. Balasubramanian"and"The quick brown"both print wider than the declared box) — the constraint seems to only kick in on the second pass, which then re-includes content already placed by the first.Real-world trigger
Found via a landscape
<Table>column (<Cell width="…">) holding aFirstinitial. Surnamevalue ("R. Balasubramanian") one row-owner field among six on a reconciliation report. Every other owner name in the same dataset was short enough to fit its column without wrapping and rendered fine — only the one row whose content genuinely needed to wrap corrupted. This will affect any data-driven table (names, addresses, free-text descriptions) where cell content length varies and only some rows are long enough to require a wrap.Impact
Rated above the five just-fixed issues on the "silent corruption" axis: earlier issues either failed loudly (good) or dropped/warned about missing content. This one actively fabricates incorrect text in the output PDF with exit code 0 and no diagnostic — a document can render "successfully" while showing garbled duplicated names/words, which is worse for a compliance or financial document than an honest failure.
Environment
docraft_tool, built frommainatb386ee8(macOS, AppleClang 21, Release,-DBUILD_TESTS=ON, 426/426 existing unit tests pass)<Text width="…">and inside a<Table><Cell width="…">