Skip to content

Fix doc.text() throwing NaN with lineBreak false and underline/strike/link/goTo - #1784

Merged
blikblum merged 1 commit into
foliojs:masterfrom
youdie006:fix/401-linebreak-false-nan
Aug 27, 2026
Merged

Fix doc.text() throwing NaN with lineBreak false and underline/strike/link/goTo#1784
blikblum merged 1 commit into
foliojs:masterfrom
youdie006:fix/401-linebreak-false-nan

Conversation

@youdie006

Copy link
Copy Markdown
Contributor

What kind of change does this PR introduce?

Bug fix. Fixes #401.

doc.text(str, x, y, { lineBreak: false, underline: true }) throws Error: unsupported number: NaN. The same happens with strike, link and goTo — anything that reaches _fragment's rendered-width calculation.

The chain: _initOptions only assigns result.width when result.lineBreak !== false (lib/mixins/text.js:421), and _text only constructs a LineWrapper if (options.width) (lib/mixins/text.js:82). But options.textWidth and options.wordCount are written only inside the wrapper (lib/line_wrapper.js:238-239), so with lineBreak: false they stay undefined and

const renderedWidth =
  options.textWidth + wordSpacing * (options.wordCount - 1) + characterSpacing * (text.length - 1);

evaluates to NaN. That reaches lineTo() and then PDFObject.number, whose range guard n > -1e21 && n < 1e21 is false for NaN, so it throws (lib/object.js:164-170).

The 2015 report describes this as invisible text rather than a crash, because number() did not throw back then. The title understates what it does today.

The fix supplies only the two missing inputs, leaving the wrapper's formula alone. _line 80 lines up already handles the no-wrapper case this way — this.x += this.widthOfString(text, options) (lib/mixins/text.js:449) — as does boundsOfString in its non-wrapped branch (lib/mixins/text.js:186). So the library already knows how to measure an unwrapped fragment; _fragment was just missing the fallback.

The values are local consts and are never assigned back to options, so continued-text state and _textOptions inheritance are untouched. I grepped the other readers of that state before changing it — line_wrapper.js:238-239 (the writer), line_wrapper.js:356, text.js:178-179 (inside a wrapper callback) and text.js:475 (guarded by if (options.width)) — none of them see a different value than before.

Verification. Built from master (c6f57c6) rather than testing the published package, since this area has moved recently.

Before the change, four public-API combinations throw and three controls pass:

FAIL  lineBreak:false + underline -> unsupported number: NaN
FAIL  lineBreak:false + strike    -> unsupported number: NaN
FAIL  lineBreak:false + link      -> unsupported number: NaN
FAIL  lineBreak:false + goTo      -> unsupported number: NaN
OK    underline alone / lineBreak:false alone / lineBreak:false + width + underline

After it, all seven pass. The four new cases in tests/unit/text.spec.js are verified red-green: with lib/mixins/text.js reverted and the tests kept, all four fail with unsupported number: NaN.

Full unit suite: 432 passing on unmodified master, 436 with this change — the delta is exactly the four new cases, with no existing test changing state. yarn lint is clean.

Checklist:

  • Unit Tests
  • Documentation — N/A, this is a crash fix with no public API or behaviour change for existing working input
  • Update CHANGELOG.md
  • Ready to be merged

Credit to @ashelley for the report.

Disclosure: I use AI assistance in my work, and I review and verify everything before it goes out. The runs above are my own.

@blikblum

Copy link
Copy Markdown
Member

Many thanks

@blikblum
blikblum merged commit e04b677 into foliojs:master Aug 27, 2026
3 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.

lineBreak: false and strike: true or underline:true causes text to not be visible. (0.71)

2 participants