Skip to content

fix(text-wrapper): prevent boundary word duplication in line wrapping - #81

Merged
Cadons merged 8 commits into
mainfrom
80-text-wrapping-duplicates-content-when-a-multi-word-textcell-needs-to-wrap-regression-vs-b386ee8
Aug 16, 2026
Merged

Cadons merged 8 commits into
mainfrom
80-text-wrapping-duplicates-content-when-a-multi-word-textcell-needs-to-wrap-regression-vs-b386ee8

Conversation

@Cadons

@Cadons Cadons commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Three regressions found and fixed while re-validating this branch against main (b386ee8).

fix(text-wrapper) — duplicated boundary word when wrapping multi-word text

DocraftLoomTextWrapper::wrap_paragraph built the wrap candidate with
current_line.append(" ").append(word). std::string::append mutates the
object in place and returns a reference to itself, so this silently
overwrote current_line as a side effect of building candidate — not
just candidate. When the candidate didn't fit and the code fell back to
lines.push_back(current_line), current_line had already been
overwritten to include the overflowing word, so that word was pushed on
the current line and reprocessed onto the next one. Any <Text> (bare
or inside a table <Cell>) with 2+ words whose full width exceeded its
box duplicated the boundary word in the rendered PDF — a silent content
corruption, not just a layout glitch.

Fixed by building candidate without mutating current_line
(current_line + " " + word).

fix(loom-table) — table columns overflowing available width

resolve_table_column_widths gave every flexible column a share of the
full available_width even when another column in the same table had
an explicit width, instead of deducting that explicit width first. E.g.
with available_width = 200 and one column pinned to 150, the other
(flexible, weight 1) column used to get 200/2 = 100 instead of the 50
actually left over — total 250 > 200, silently overflowing the
page/body margin.

Flexible columns now only divide what's left after explicit-width columns
are deducted, and only those flexible columns are rescaled to close any
gap left by natural-width floors.

fix(loom-rectangle) — text/content overflowing rectangle bounds

visit(DocraftLoomRectangle*) in the rendering processor painted children
with no clip at all, unlike the equivalent visit(DocraftLoomCanvas*). A
child whose computed size exceeded the rectangle's own frame (e.g. a
<Text> whose own explicit width overrides the width relayed by its
parent <Rectangle>) painted past the rectangle's border instead of
being contained by it.

Now clipped the same way Canvas already is.

Verification

All three verified end-to-end via docraft_tool + rendered PDF (visual
before/after comparison for the wrapping duplication and the rectangle
clip), plus new/updated regression tests:

  • DocraftLoomTextWrapperTest.* (new — the wrapper had no dedicated unit
    tests before this)
  • DocraftLoomTableTest.ExplicitWidthIsRespectedAndRemainderRedistributed
    (updated — it previously asserted the buggy dilution as expected
    behavior)
  • DocraftLoomRectangleTest.RenderingClipsChildrenToRectangleBoundsBracketedBySaveRestore
    (new — Rectangle's rendering pass had no test coverage before this)

Full suite: 430/430 passing.

Cadons and others added 4 commits August 16, 2026 17:21
resolve_table_column_widths gave every flexible column a share of the
FULL available_width even when another column had an explicit width,
instead of deducting that explicit width first. The resolved columns'
total could then exceed available_width, silently pushing the table
past the page/body margin.

Flexible columns now divide only what's left after explicit-width
columns are deducted, and only the flexible columns are rescaled to
close any gap left by their natural-width floors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
visit(DocraftLoomRectangle*) painted children with no clip at all,
unlike the equivalent visit(DocraftLoomCanvas*). A child whose computed
size exceeded the rectangle's own frame (e.g. a Text node whose own
explicit wrap_width overrides the width relayed by its parent
rectangle) painted past the rectangle's edges instead of being
contained by it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
visit(Rectangle*) and visit(Canvas*) each hand-rolled the same
save_state()/clip_rectangle()/paint children/restore_state() sequence
(flagged as duplication by SonarQube). Extracted into a shared helper,
mirroring how draw_container_background() already factors out the
background paint sequence shared by container node visitors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .github/workflows/docs-build.yml Fixed
Comment thread .github/workflows/docs-deploy.yml Fixed
Comment thread .github/workflows/docs.yml Fixed
@sonarqubecloud

sonarqubecloud Bot commented Aug 16, 2026

Copy link
Copy Markdown

Quality Gate Passed Quality Gate passed

Issues
2 New issues
1 Accepted issue

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@Cadons
Cadons merged commit 0153d04 into main Aug 16, 2026
10 checks passed
@Cadons
Cadons deleted the 80-text-wrapping-duplicates-content-when-a-multi-word-textcell-needs-to-wrap-regression-vs-b386ee8 branch August 31, 2026 21:02
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.

Text wrapping duplicates content when a multi-word Text/Cell needs to wrap (regression vs b386ee8)

2 participants