diff --git a/CHANGELOG.md b/CHANGELOG.md index dfa5638a..daca229d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Unreleased +- Fix the underline, strike, link and goTo of wrapped text, and `boundsOfString()` with a `width`, counting `characterSpacing` and `wordSpacing` twice, so they no longer run past the text and match the same text drawn with `lineBreak: false` - Fix the Node build throwing once a bundler inlines it into an application bundle, `Cannot find module '#standard-fonts/Helvetica'` on the first document from an ESM bundle and `Invalid URL` while importing a CommonJS one, because the standard font metrics and the PDF/A ICC profile were resolved relative to pdfkit's own package directory at import time. Both are now resolved on first use, a standard font that cannot be loaded names itself and the fix in its error, and `registerStdFonts` is exported from the Node build as it already was from the browser build, so a bundle can register the fonts it uses from `pdfkit/standard-fonts/`. The Node build now requires the standard fonts by a path relative to the built file, which replaces the `#standard-fonts/*` import mapping and lets an ESM bundle load them from a copy of `js/standard-fonts` next to it. Fixes #1801 - Fix `doc.list()` drawing the label of a `numbered` or `lettered` item with the line wrapper's own options object, which threw `unsupported number: NaN` for `align` `center` and `right`, applied a word spacing derived from the previous line to the label for `align` `justify`, and sized the underline, strike, link and goTo of every label after the first from the previous item's text - Add a `hidden` option to form annotation methods, for a field that should start hidden (e.g. one an interactive action reveals later) instead of the usual default of visible and printable diff --git a/lib/mixins/text.js b/lib/mixins/text.js index 38d54c8d..7c42b5e3 100644 --- a/lib/mixins/text.js +++ b/lib/mixins/text.js @@ -175,9 +175,8 @@ export default { // calculate the actual rendered width of the string after word and character spacing contentWidth = Math.max( contentWidth, - options.textWidth + - wordSpacing * (options.wordCount - 1) + - characterSpacing * (text.length - 1), + this.widthOfString(text, options) + + wordSpacing * (options.wordCount - 1), ); } }); @@ -531,9 +530,8 @@ export default { } // calculate the actual rendered width of the string after word and character spacing - // the wrapper supplies textWidth and wordCount; measure them directly when it did not run + // the wrapper's textWidth already includes both, so measure the text itself const measuredWidth = - options.textWidth ?? this.widthOfString(text, options) - characterSpacing * (text.length - 1); const measuredWordCount = options.wordCount ?? (text.trim() ? text.trim().split(/\s+/).length : 0); diff --git a/tests/unit/text.spec.js b/tests/unit/text.spec.js index 6609ab9b..d883bc5a 100644 --- a/tests/unit/text.spec.js +++ b/tests/unit/text.spec.js @@ -218,6 +218,30 @@ Q expect(docData).toContainText({ text: 'no line break' }); }); + + test.each([ + ['characterSpacing', { characterSpacing: 4 }, '184.74'], + ['wordSpacing', { wordSpacing: 5 }, '138.74'], + ])('underline of wrapped text with %s', (_name, spacing, end) => { + const docData = logData(document); + const options = { width: 400, underline: true, ...spacing }; + const bounds = document.boundsOfString( + 'Hello big world', + 50, + 50, + options, + ); + + document.text('Hello big world', 50, 50, options); + document.end(); + + // the same width as with lineBreak false, with each spacing counted once + expect(bounds.width.toFixed(2)).toBe((end - 50).toFixed(2)); + expect(docData).toContainChunk([ + 'stream', + new RegExp(`50 [\\d.]+ m\n${end.replace('.', '\\.')}\\d* [\\d.]+ l`), + ]); + }); }); describe('text with structure parent links', () => {