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 `dash()` rejecting a length of `0`, e.g. `dash(0, { space: 2 })` or `dash([2, 0, 3])`, which the PDF specification allows as long as not every length is zero, so a dotted line can be drawn with a `round` line cap. A dash array of only zeros still throws. Fixes #1060
- Fix `doc.table()` parsing a font passed as binary data (e.g. registered with `registerFont(name, uint8Array)`) again for every cell, when it restores the font after measuring and drawing the cell, which made tables with such fonts slower. A font loaded from the same `Uint8Array` is now reused without parsing it again
- Add `lineBreak: 'whitespace'` to `doc.text()`, which breaks a line only at whitespace, a soft hyphen or a zero-width space. The Unicode line breaking rules the wrapper follows also allow a break after a hyphen or a slash, so `design-system` or `https://github.com/foliojs/pdfkit` could be cut at a line end, and a text extractor that joins the lines, such as a résumé parser, reads two words
- Add an `objectStreams` option, which packs the objects that are not streams into compressed object streams and writes a cross-reference stream instead of the xref table, making documents with many such objects, e.g. tagged ones, much smaller. It requires a `pdfVersion` of `1.5` or higher, and cannot be used with encryption or PDF/A-1. Fixes #1810
Expand Down
6 changes: 4 additions & 2 deletions docs/vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,10 @@ The output of this example looks like this.

The `dash` method allows you to create non-continuous dashed lines. It takes a
length specifying how long each dash should be, as well as an optional hash
describing the additional properties `space` and `phase`. Lengths must be positive
numbers; `dash` will throw if passed invalid lengths.
describing the additional properties `space` and `phase`. Lengths must be
non-negative numbers, and not all zero; `dash` will throw if passed invalid
lengths. A length of `0` draws a dot when used with a `round` line cap, e.g.
`doc.lineCap('round').dash(0, {space: 4})` for a dotted line.

The `space` option defines the length of the space between each dash, and the `phase` option
defines the starting point of the sequence of dashes. By default the `space`
Expand Down
10 changes: 8 additions & 2 deletions lib/mixins/vector.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,18 @@ export default {
length = [length, options.space || length];
}

const valid = length.every((x) => Number.isFinite(x) && x > 0);
// A dash array must be made of non-negative numbers that are not all
// zero; an all-zero array makes some viewers reject the file (#949).
// A zero-length dash is fine otherwise, e.g. [0 2] with a round line
// cap draws a dotted line.
const valid =
length.every((x) => Number.isFinite(x) && x >= 0) &&
(length.length === 0 || length.some((x) => x > 0));
if (!valid) {
throw new Error(
`dash(${JSON.stringify(originalLength)}, ${JSON.stringify(
options,
)}) invalid, lengths must be numeric and greater than zero`,
)}) invalid, lengths must be numeric and non-negative, and not all zero`,
);
}

Expand Down
40 changes: 34 additions & 6 deletions tests/unit/vector.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,28 @@ describe('Vector Graphics', () => {
]);
});

test('with zero length and space option', () => {
const docData = logData(document);
const vectorStream = Buffer.from(
'1 0 0 -1 0 792 cm\n50 20 m\n[0 2] 0 d\nS\n',
'binary',
);

document.moveTo(50, 20).dash(0, { space: 2 }).stroke();
document.end();

expect(docData).toContainChunk([
`5 0 obj`,
`<<
/Length 38
>>`,
`stream`,
vectorStream,
`\nendstream`,
`endobj`,
]);
});

test('with phase option', () => {
const docData = logData(document);
const vectorStream = Buffer.from(
Expand Down Expand Up @@ -117,23 +139,23 @@ describe('Vector Graphics', () => {
const doc = new PDFDocument();

expect(() => doc.dash(0)).toThrow(
'dash(0, {}) invalid, lengths must be numeric and greater than zero',
'dash(0, {}) invalid, lengths must be numeric and non-negative, and not all zero',
);
});

test('length -1 throws', () => {
const doc = new PDFDocument();

expect(() => doc.dash(-1)).toThrow(
'dash(-1, {}) invalid, lengths must be numeric and greater than zero',
'dash(-1, {}) invalid, lengths must be numeric and non-negative, and not all zero',
);
});

test('length null throws', () => {
const doc = new PDFDocument();

expect(() => doc.dash(null)).toThrow(
'dash(null, {}) invalid, lengths must be numeric and greater than zero',
'dash(null, {}) invalid, lengths must be numeric and non-negative, and not all zero',
);
});

Expand All @@ -143,11 +165,17 @@ describe('Vector Graphics', () => {
expect(() => doc.dash([2, 3])).not.toThrow();
});

test('length array containing zeros throws', () => {
test('length array containing zeros', () => {
const doc = new PDFDocument();

expect(() => doc.dash([2, 0, 3])).not.toThrow();
});

test('length array of only zeros throws', () => {
const doc = new PDFDocument();

expect(() => doc.dash([2, 0, 3])).toThrow(
'dash([2,0,3], {}) invalid, lengths must be numeric and greater than zero',
expect(() => doc.dash([0, 0])).toThrow(
'dash([0,0], {}) invalid, lengths must be numeric and non-negative, and not all zero',
);
});
});
Expand Down