Skip to content

Knuth-Plass: keep only the best active node per line and fitness class - #57

Merged
davidnmbond merged 2 commits into
mainfrom
fix/knuth-plass-active-node-dedup
Oct 13, 2026
Merged

davidnmbond merged 2 commits into
mainfrom
fix/knuth-plass-active-node-dedup

Conversation

@davidnmbond

Copy link
Copy Markdown
Contributor

Problem

KnuthPlassAlgorithm.FindBreaks added every candidate break node at every legal breakpoint. The comment said "keep only the best per fitness class", but nothing did. While lines were too loose (accepted with +10000 demerits), the active set roughly doubled at each breakpoint.

Seen in Magic Suite's ReportMagic Files viewer (/files/pageSvg, MS-23775) on test2, 4.7.526: a 746 KB DOCX (32 paragraphs, 59 images) took 122–189 s to render, and the user got an nginx 504.

Measured on Windows against 1.0.289 (indicative only; production runs on Linux):

  • ToSvgPages took 188–197 s on every render, nearly all of it page 2 (six short paragraphs, no images).
  • Peak active nodes: 1.1–1.6 M for a 21-breakpoint paragraph, 26.2 M for 25.
  • Stack samples all sat in KnuthPlassAlgorithm.FindBreaks ← RenderCommandEmitter.BuildWrappedLines.

Fix

Keep only the lowest-demerit node per (line, fitness class) at each breakpoint. On equal demerits the later candidate wins (<=). With <, one line on page 1 broke after a different word, so <= is what keeps the output unchanged.

Results (same document)

ToSvgPages
Before 188–197 s
After 0.05–0.15 s

All 13 SVG pages are byte-identical before and after (SHA-256 per page). Peak active nodes after the fix: 133–232.

Tests

  • Added FindBreaks_LongWordWrappedParagraph_CompletesQuickly: a 40-word paragraph shaped like BuildWrapItems output, which must finish within 10 s.
  • Red-checked: it times out (System.TimeoutException, 10 s) on the unfixed algorithm and passes in about 1 s with the fix.
  • Full suite: 2,159 passed, 0 failed (local, Release).

Not in this PR (possible follow-ups)

  • RenderCommandEmitter breaks every paragraph again at SVG time instead of reusing the layout's line breaks.
  • SKTypeface.FromFamilyName is called once per token, with no cache.
  • DocxRenderer.RenderAsync and ToSvgPages can't be cancelled.

🤖 Generated with Claude Code

FindBreaks added every candidate node at every legal breakpoint, so while
lines were too loose the active set doubled per breakpoint. One 25-breakpoint
paragraph reached about 26 million nodes, and a 13-page DOCX took about 190 s
to render to SVG (MS-23775, MS-27476 on Magic Suite test2).

Keep the lowest-demerit node per (line, fitness class); on a tie the later
candidate wins, which keeps the rendered output byte-identical for that
document. Adds a regression test that times out without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codacy-production

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 4 complexity · 0 duplication

Metric Results
Complexity 4
Duplication 0

View in Codacy

AI Reviewer: run a review on demand. To trigger the first review automatically, go to your organization or repository integration settings. AI can make mistakes. Always validate suggestions.

Run reviewer

TIP This summary will be updated as you push new changes.

Since its target moved from net10.0-windows to net10.0 (e4358eb, 2026-10-04),
the Word COM interop calls fail CA1416 on the Linux CI runner, and the build
job has failed on main ever since, which also blocks publishing. Declaring
SupportedOSPlatform("windows") keeps the net10.0 target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@davidnmbond

Copy link
Copy Markdown
Contributor Author

Added a second commit so CI can build: main has failed the build job since e4358eb (2026-10-04). That commit moved PanoramicData.Render.ReferenceGenerator from net10.0-windows to net10.0, so its Word COM calls fail CA1416 on the Linux runner. The project now declares SupportedOSPlatform("windows") as an assembly attribute and keeps the net10.0 target. publish needs build, so without this no package can be released.

Not fixed here: the coverage job also fails on main, with "Option '--coverage-settings' has invalid arguments: Provided settings file doesn't exist or is invalid." coverage.config is at the repo root, so the relative path probably resolves against the test executable's folder. It doesn't block publishing.

🤖 Generated with Claude Code

@davidnmbond
davidnmbond merged commit 48243c4 into main Oct 13, 2026
6 of 7 checks passed
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.

1 participant