Skip to content

feat(textkit): use best-fit line breaking for long text#3421

Open
wojtekmaj wants to merge 2 commits into
diegomura:masterfrom
wojtekmaj:wmaj/textkit-long-text-best-fit
Open

feat(textkit): use best-fit line breaking for long text#3421
wojtekmaj wants to merge 2 commits into
diegomura:masterfrom
wojtekmaj:wmaj/textkit-long-text-best-fit

Conversation

@wojtekmaj
Copy link
Copy Markdown
Contributor

@wojtekmaj wojtekmaj commented May 12, 2026

This changes textkit's default auto line-breaking behavior for long non-justified paragraphs: short/justified text continues to use Knuth-Plass, while long ragged text uses the existing best-fit breaker to avoid expensive global optimization. It also exposes lineBreakStrategy so callers can force auto, best-fit, or knuth-plass.

Why: in a downstream PDF generator, customer-editable body copy can contain very long paragraphs. Profiling showed the hot path was textkit's Knuth-Plass active-node search. For a 50k-character paragraph, the linebreaker built ~20,650 syllables and ~30,431 nodes; node creation took ~6ms, but Knuth-Plass took ~769ms and the heap jumped to ~277MB.

Measured downstream with an equivalent best-fit shortcut for long non-justified text:

Baseline, 200k chars: ~31.3s wall, ~43.5s CPU, ~3.38GB maxRSS
Best-fit long-text strategy: ~2.3s wall, ~3.0s CPU, ~0.5GB maxRSS

Together with #3420, this improves performance in the same downstream PDF generator by:

Baseline, 50k chars: ~2.4s wall, ~3.0s CPU, ~563MB maxRSS
With #3420 + #3421: ~713ms wall, ~1.11s CPU, ~291MB maxRSS

Baseline, 100k chars: ~8.1s wall, ~10.1s CPU, ~1.18GB maxRSS
With #3420 + #3421: ~1.21s wall, ~1.73s CPU, ~423MB maxRSS

Baseline, 200k chars: ~31.3s wall, ~43.5s CPU, ~3.38GB maxRSS
With #3420 + #3421: ~2.28s wall, ~2.96s CPU, ~513MB maxRSS

The generated PDF size was essentially unchanged (~180KB), so the win comes from avoiding intermediate layout work rather than output compression.

Copilot AI review requested due to automatic review settings May 12, 2026 11:32
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 12, 2026

🦋 Changeset detected

Latest commit: 8fc992e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@react-pdf/layout Minor
@react-pdf/renderer Minor
@react-pdf/textkit Minor
@react-pdf/types Minor
@react-pdf/render Patch
@react-pdf/math Major
@react-pdf/mermaid Major
next-14 Patch
next-15 Patch
@react-pdf/vite-example Patch
@react-pdf/font Patch
@react-pdf/stylesheet Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@wojtekmaj wojtekmaj force-pushed the wmaj/textkit-long-text-best-fit branch from 24e5722 to afa73c4 Compare May 12, 2026 11:34
@wojtekmaj wojtekmaj changed the title Use best-fit line breaking for long text feat(textkit): use best-fit line breaking for long text May 12, 2026
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates @react-pdf/textkit line breaking to avoid expensive Knuth–Plass optimization for very long, non-justified paragraphs by default, while also exposing a lineBreakStrategy option so callers can explicitly select auto, best-fit, or knuth-plass.

Changes:

  • Add lineBreakStrategy to public typing surfaces (@react-pdf/types, @react-pdf/layout, @react-pdf/renderer, and textkit’s LayoutOptions).
  • Update textkit’s linebreaker so auto uses best-fit for long ragged text, while keeping Knuth–Plass for justified text.
  • Add tests and README documentation for the new behavior and option.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/textkit/src/engines/linebreaker/index.ts Adds auto strategy selection logic (best-fit for long non-justified text) and plumbs lineBreakStrategy through the engine.
packages/textkit/src/types.ts Extends LayoutOptions with lineBreakStrategy.
packages/textkit/tests/engines/linebreaker.test.ts Adds coverage intended to validate auto strategy selection for long text and justified text.
packages/textkit/README.md Documents the new auto behavior and the lineBreakStrategy option.
packages/layout/src/text/layoutText.ts Passes lineBreakStrategy from Text node props into textkit layout options.
packages/layout/src/svg/layoutText.ts Passes lineBreakStrategy through for SVG text layout options.
packages/layout/src/types/text.ts Exposes lineBreakStrategy on layout-side TextProps.
packages/renderer/index.d.ts Exposes lineBreakStrategy on renderer-side text props typings/docs.
packages/types/node.d.ts Exposes lineBreakStrategy in shared public TextProps typings.
.changeset/tiny-hats-brake.md Declares patch releases for the affected packages and describes the change.
Comments suppressed due to low confidence (1)

packages/textkit/src/engines/linebreaker/index.ts:152

  • The function-level JSDoc says this engine "Performs Knuth & Plass" with fallback to best-fit, but the new shouldUseBestFit path can select best-fit as the primary algorithm (skipping Knuth‑Plass entirely). Updating this comment would prevent confusion for future maintainers.
/**
 * Performs Knuth & Plass line breaking algorithm
 * Fallbacks to best fit algorithm if latter not successful
 *

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/textkit/tests/engines/linebreaker.test.ts Outdated
Comment thread packages/textkit/src/engines/linebreaker/index.ts
Comment thread packages/textkit/README.md Outdated
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.

2 participants