diff --git a/CHANGELOG.md b/CHANGELOG.md index 4527a6a7..7bab8503 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Unreleased +- Fix wrapped text with `align: 'center'` sitting left of the middle of the line when `characterSpacing` or `wordSpacing` is set, and with `align: 'right'` running past the right edge by the `wordSpacing` of each gap between words - Fix `boundsOfString()` with `lineBreak: false` leaving out `wordSpacing`, so its width now matches the rendered text and its underline - 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 diff --git a/lib/mixins/text.js b/lib/mixins/text.js index 78c872d7..70062e13 100644 --- a/lib/mixins/text.js +++ b/lib/mixins/text.js @@ -480,11 +480,22 @@ export default { switch (align) { case 'right': textWidth = this.widthOfString(text.replace(/\s+$/, ''), options); + if (wordSpacing) { + const gaps = text.trim().split(/\s+/).length - 1; + textWidth += (wordSpacing * gaps * horizontalScaling) / 100; + } x += options.lineWidth - textWidth; break; case 'center': - x += options.lineWidth / 2 - options.textWidth / 2; + // the wrapper's textWidth also counts the spacing after the last + // word and the word spacing a second time + textWidth = + options.textWidth - + ((characterSpacing + wordSpacing * options.wordCount) * + horizontalScaling) / + 100; + x += options.lineWidth / 2 - textWidth / 2; break; case 'justify': diff --git a/tests/unit/text.spec.js b/tests/unit/text.spec.js index bb7a572f..cd5df975 100644 --- a/tests/unit/text.spec.js +++ b/tests/unit/text.spec.js @@ -263,6 +263,32 @@ Q /50 [\d.]+ m\n138\.74\d* [\d.]+ l/, ]); }); + + test.each([ + ['center', { characterSpacing: 4 }, '182.628', '317.372'], + ['center', { wordSpacing: 5 }, '205.628', '294.372'], + ['right', { wordSpacing: 5 }, '361.256', '450'], + ])('%s aligned text with %o', (align, spacing, start, end) => { + const docData = logData(document); + + document.text('Hello big world', 50, 50, { + width: 400, + align, + underline: true, + ...spacing, + }); + document.end(); + + // the underline spans the drawn text, so it sits in the middle of the + // line or ends at its right edge + const escape = (n) => n.replace('.', '\\.'); + expect(docData).toContainChunk([ + 'stream', + new RegExp( + `${escape(start)}\\d* [\\d.]+ m\n${escape(end)}\\d* [\\d.]+ l`, + ), + ]); + }); }); describe('text with structure parent links', () => {