Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Unreleased

- Fix text with `horizontalScaling` being wrapped, indented, aligned and laid out in columns as if the `width`, `indent` and `columnGap` were scaled too, so scaled text no longer runs past the right edge or stops short of it, and fix `align: 'justify'`, the decorations and `boundsOfString()` counting the word spacing of scaled text at 100%
- 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`
Expand Down
13 changes: 4 additions & 9 deletions lib/line_wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,16 @@ class LineWrapper extends EventEmitter {
super();
this.document = document;
this.horizontalScaling = options.horizontalScaling || 100;
this.indent = ((options.indent || 0) * this.horizontalScaling) / 100;
this.indent = options.indent || 0;
this.indentAllLines = options.indentAllLines || false;
this.characterSpacing =
((options.characterSpacing || 0) * this.horizontalScaling) / 100;
this.wordSpacing =
((options.wordSpacing === 0) * this.horizontalScaling) / 100;
this.columns = options.columns || 1;
this.columnGap =
((options.columnGap != null ? options.columnGap : 18) *
this.horizontalScaling) /
100; // 1/4 inch
this.columnGap = options.columnGap != null ? options.columnGap : 18; // 1/4 inch
this.lineWidth =
((options.width * this.horizontalScaling) / 100 -
this.columnGap * (this.columns - 1)) /
this.columns;
(options.width - this.columnGap * (this.columns - 1)) / this.columns;
this.spaceLeft = this.lineWidth;
this.startX = this.document.x;
this.startY = this.document.y;
Expand Down Expand Up @@ -207,7 +202,7 @@ class LineWrapper extends EventEmitter {
// override options from previous continued fragments
this.horizontalScaling = options.horizontalScaling || 100;
if (options.indent != null) {
this.indent = (options.indent * this.horizontalScaling) / 100;
this.indent = options.indent;
}
if (options.characterSpacing != null) {
this.characterSpacing =
Expand Down
17 changes: 12 additions & 5 deletions lib/mixins/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export default {
({ x, y } = this);
const lineGap = options.lineGap ?? this._lineGap ?? 0;
const lineHeight = this.currentLineHeight(true) + lineGap;
const horizontalScaling = options.horizontalScaling || 100;
let contentWidth = 0;
// Convert text to a string
string = String(string ?? '');
Expand Down Expand Up @@ -167,7 +168,9 @@ export default {
const spaceWidth = this.widthOfString(' ') + characterSpacing;
wordSpacing = Math.max(
0,
(options.lineWidth - textWidth) / Math.max(1, words.length - 1) -
((options.lineWidth - textWidth) /
Math.max(1, words.length - 1)) *
(100 / horizontalScaling) -
spaceWidth,
);
}
Expand All @@ -176,7 +179,7 @@ export default {
contentWidth = Math.max(
contentWidth,
this.widthOfString(text, options) +
wordSpacing * (options.wordCount - 1),
(wordSpacing * (options.wordCount - 1) * horizontalScaling) / 100,
);
}
});
Expand All @@ -189,7 +192,9 @@ export default {
// whitespace runs were collapsed above, so each gap is one character
const trimmed = line.trim();
const space = /\s/g;
while (space.test(trimmed)) lineWidth += options.wordSpacing;
while (space.test(trimmed)) {
lineWidth += (options.wordSpacing * horizontalScaling) / 100;
}
}
this.y += lineHeight;
contentWidth = Math.max(contentWidth, lineWidth);
Expand Down Expand Up @@ -503,9 +508,11 @@ export default {
words = text.trim().split(/\s+/);
textWidth = this.widthOfString(text.replace(/\s+/g, ''), options);
var spaceWidth = this.widthOfString(' ') + characterSpacing;
// the gap is in page units, but word spacing is scaled by Tz
wordSpacing = Math.max(
0,
(options.lineWidth - textWidth) / Math.max(1, words.length - 1) -
((options.lineWidth - textWidth) / Math.max(1, words.length - 1)) *
(100 / horizontalScaling) -
spaceWidth,
);
break;
Expand Down Expand Up @@ -554,7 +561,7 @@ export default {
options.wordCount ?? (text.trim() ? text.trim().split(/\s+/).length : 0);
const renderedWidth =
measuredWidth +
wordSpacing * (measuredWordCount - 1) +
(wordSpacing * (measuredWordCount - 1) * horizontalScaling) / 100 +
characterSpacing * (text.length - 1);

// create link annotations if the link option is given
Expand Down
57 changes: 57 additions & 0 deletions tests/unit/text.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,9 @@ Q
['center', { characterSpacing: 4 }, '182.628', '317.372'],
['center', { wordSpacing: 5 }, '205.628', '294.372'],
['right', { wordSpacing: 5 }, '361.256', '450'],
['right', { horizontalScaling: 80 }, '387.00', '450'],
['center', { horizontalScaling: 120 }, '202.75', '297.24'],
['right', { horizontalScaling: 120, wordSpacing: 5 }, '343.50', '450'],
])('%s aligned text with %o', (align, spacing, start, end) => {
const docData = logData(document);

Expand All @@ -289,6 +292,60 @@ Q
),
]);
});
test.each([
['wrapped', { width: 400 }],
['lineBreak false', { lineBreak: false }],
])(
'bounds of %s text with horizontalScaling and wordSpacing',
(_name, options) => {
const bounds = document.boundsOfString('Hello big world', 50, 50, {
...options,
horizontalScaling: 80,
wordSpacing: 6,
});

// the glyphs and both word gaps are scaled to 80%
expect(bounds.width.toFixed(2)).toBe('72.60');
},
);

test.each([50, 150])(
'wraps text with horizontalScaling %i at the given width',
(horizontalScaling) => {
const bounds = document.boundsOfString(
'The quick brown fox jumps over the lazy dog while the five boxing wizards jump quickly',
50,
50,
{ width: 200, horizontalScaling },
);

expect(bounds.width).toBeLessThanOrEqual(200);
expect(bounds.width).toBeGreaterThan(180);
},
);

test('justified text with horizontalScaling fills the line', () => {
const docData = logData(document);
const text =
'The quick brown fox jumps over the lazy dog while the five boxing wizards jump quickly';
const options = {
width: 200,
align: 'justify',
underline: true,
horizontalScaling: 120,
};
const bounds = document.boundsOfString(text, 50, 50, options);

document.text(text, 50, 50, options);
document.end();

// the first line reaches the right edge, plus its trailing space
expect(bounds.width.toFixed(2)).toBe('204.00');
expect(docData).toContainChunk([
'stream',
/50 [\d.]+ m\n254\.00\d* [\d.]+ l/,
]);
});
});

describe('text with structure parent links', () => {
Expand Down
Loading