diff --git a/CHANGELOG.md b/CHANGELOG.md index a085df2a3..46007d17c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ ## pdfkit changelog +### [v0.15.2] - 2024-12-15 + +- Fix index not counting when rendering ordered lists (#1517) +- Fix PDF/A3 compliance of attachments +- Fix CIDSet generation only for PDF/A1 subset +- Fix missing acroform font dictionary +- Fix modify time comparison check equality embedded files + ### [v0.15.1] - 2024-10-30 - Fix browserify transform sRGB_IEC61966_2_1.icc file diff --git a/docs/attachments.md b/docs/attachments.md index e4bb12fa9..78ff1d680 100644 --- a/docs/attachments.md +++ b/docs/attachments.md @@ -24,6 +24,7 @@ There are a few other options for `doc.file`: * `hidden` - if true, do not show file in the list of embedded files * `creationDate` - override the date and time the file was created * `modifiedDate` - override the date and time the file was last updated +* `relationship` - relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'. If you are attaching a file from your file system, creationDate and modifiedDate will be set to the source file's creationDate and modifiedDate. diff --git a/examples/attachment.pdf b/examples/attachment.pdf index f04188cdd..289c79662 100644 Binary files a/examples/attachment.pdf and b/examples/attachment.pdf differ diff --git a/examples/form.pdf b/examples/form.pdf index cb9e26132..3f5045e57 100644 Binary files a/examples/form.pdf and b/examples/form.pdf differ diff --git a/examples/kitchen-sink-accessible.pdf b/examples/kitchen-sink-accessible.pdf index d7ab8dd69..c8ebca72c 100644 Binary files a/examples/kitchen-sink-accessible.pdf and b/examples/kitchen-sink-accessible.pdf differ diff --git a/examples/kitchen-sink.pdf b/examples/kitchen-sink.pdf index 2ecb99e36..f3b7e931a 100644 Binary files a/examples/kitchen-sink.pdf and b/examples/kitchen-sink.pdf differ diff --git a/examples/text-link.pdf b/examples/text-link.pdf index ec5151208..176923072 100644 Binary files a/examples/text-link.pdf and b/examples/text-link.pdf differ diff --git a/lib/font/embedded.js b/lib/font/embedded.js index 0eddfe0e9..30fb540e9 100644 --- a/lib/font/embedded.js +++ b/lib/font/embedded.js @@ -183,7 +183,7 @@ class EmbeddedFont extends PDFFont { descriptor.data.FontFile2 = fontFile; } - if (this.document.subset) { + if (this.document.subset && this.document.subset === 1) { const CIDSet = Buffer.from('FFFFFFFFC0', 'hex'); const CIDSetRef = this.document.ref(); CIDSetRef.write(CIDSet); diff --git a/lib/mixins/acroform.js b/lib/mixins/acroform.js index 41064eda4..f07dbfd96 100644 --- a/lib/mixins/acroform.js +++ b/lib/mixins/acroform.js @@ -328,7 +328,7 @@ export default { _resolveFont(options) { // add current font to document-level AcroForm dict if necessary - if (this._acroform.fonts[this._font.id] === null) { + if (this._acroform.fonts[this._font.id] == null) { this._acroform.fonts[this._font.id] = this._font.ref(); } diff --git a/lib/mixins/attachments.js b/lib/mixins/attachments.js index 412f95637..f3a8179cb 100644 --- a/lib/mixins/attachments.js +++ b/lib/mixins/attachments.js @@ -12,10 +12,12 @@ export default { * * options.hidden: if true, do not add attachment to EmbeddedFiles dictionary. Useful for file attachment annotations * * options.creationDate: override creation date * * options.modifiedDate: override modified date + * * options.relationship: Relationship between the PDF document and its attached file. Can be 'Alternative', 'Data', 'Source', 'Supplement' or 'Unspecified'. * @returns filespec reference */ file(src, options = {}) { options.name = options.name || src; + options.relationship = options.relationship || 'Unspecified'; const refBody = { Type: 'EmbeddedFile', @@ -85,6 +87,7 @@ export default { // add filespec for embedded file const fileSpecBody = { Type: 'Filespec', + AFRelationship: options.relationship, F: new String(options.name), EF: { F: ref }, UF: new String(options.name) @@ -99,6 +102,13 @@ export default { this.addNamedEmbeddedFile(options.name, filespec); } + // Add file to the catalogue to be PDF/A3 compliant + if (this._root.data.AF) { + this._root.data.AF.push(filespec); + } else { + this._root.data.AF = [filespec]; + } + return filespec; } }; @@ -110,6 +120,7 @@ function isEqual(a, b) { a.Params.CheckSum.toString() === b.Params.CheckSum.toString() && a.Params.Size === b.Params.Size && a.Params.CreationDate.getTime() === b.Params.CreationDate.getTime() && - a.Params.ModDate.getTime() === b.Params.ModDate.getTime() + ((a.Params.ModDate === undefined && b.Params.ModDate === undefined) || + a.Params.ModDate.getTime() === b.Params.ModDate.getTime()) ); } diff --git a/lib/mixins/text.js b/lib/mixins/text.js index d78f44656..ad4273d01 100644 --- a/lib/mixins/text.js +++ b/lib/mixins/text.js @@ -153,12 +153,11 @@ export default { } }; - const drawListItem = function(listItem) { + const drawListItem = function(listItem, i) { wrapper = new LineWrapper(this, options); wrapper.on('line', this._line); level = 1; - let i = 0; wrapper.once('firstLine', () => { let item, itemType, labelType, bodyType; if (options.structParent) { @@ -225,7 +224,7 @@ export default { for (let i = 0; i < items.length; i++) { - drawListItem.call(this, items[i]); + drawListItem.call(this, items[i], i); } return this; diff --git a/package.json b/package.json index f78e6bfca..9cd2e8bbe 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "document", "vector" ], - "version": "0.15.1", + "version": "0.15.2", "homepage": "http://pdfkit.org/", "author": { "name": "Devon Govett", diff --git a/tests/unit/attachments.spec.js b/tests/unit/attachments.spec.js index 86bc8b2ae..c5419b81c 100644 --- a/tests/unit/attachments.spec.js +++ b/tests/unit/attachments.spec.js @@ -54,6 +54,7 @@ describe('file', () => { `9 0 obj`, `<< /Type /Filespec +/AFRelationship /Unspecified /F (file.txt) /EF << /F 8 0 R @@ -112,6 +113,7 @@ describe('file', () => { `9 0 obj`, `<< /Type /Filespec +/AFRelationship /Unspecified /F (file.txt) /EF << /F 8 0 R diff --git a/tests/unit/pdfa1.spec.js b/tests/unit/pdfa1.spec.js index 1091c0408..a97b76254 100644 --- a/tests/unit/pdfa1.spec.js +++ b/tests/unit/pdfa1.spec.js @@ -84,5 +84,24 @@ describe('PDF/A-1', () => { expect(metadata).toContain('pdfaid:conformance>A'); }); + + test('font data contains CIDSet', () => { + let options = { + autoFirstPage: false, + pdfVersion: '1.4', + subset: 'PDF/A-1a' + }; + let doc = new PDFDocument(options); + const data = logData(doc); + doc.addPage(); + doc.registerFont('Roboto', 'tests/fonts/Roboto-Regular.ttf'); + doc.font('Roboto'); + doc.text('Text'); + doc.end(); + + let fontDescriptor = data[data.length-41]; + + expect(fontDescriptor).toContain('/CIDSet'); + }); }); \ No newline at end of file diff --git a/tests/unit/pdfa2.spec.js b/tests/unit/pdfa2.spec.js index 7a6942525..f00df0339 100644 --- a/tests/unit/pdfa2.spec.js +++ b/tests/unit/pdfa2.spec.js @@ -84,5 +84,24 @@ describe('PDF/A-2', () => { expect(metadata).toContain('pdfaid:conformance>A'); }); + + test('font data NOT contains CIDSet', () => { + let options = { + autoFirstPage: false, + pdfVersion: '1.4', + subset: 'PDF/A-2a' + }; + let doc = new PDFDocument(options); + const data = logData(doc); + doc.addPage(); + doc.registerFont('Roboto', 'tests/fonts/Roboto-Regular.ttf'); + doc.font('Roboto'); + doc.text('Text'); + doc.end(); + + let fontDescriptor = data[data.length-41]; + + expect(fontDescriptor).not.toContain('/CIDSet'); + }); }); \ No newline at end of file diff --git a/tests/unit/pdfa3.spec.js b/tests/unit/pdfa3.spec.js index d93bc280a..05a7e9261 100644 --- a/tests/unit/pdfa3.spec.js +++ b/tests/unit/pdfa3.spec.js @@ -84,5 +84,24 @@ describe('PDF/A-3', () => { expect(metadata).toContain('pdfaid:conformance>A'); }); - + + test('font data NOT contains CIDSet', () => { + let options = { + autoFirstPage: false, + pdfVersion: '1.4', + subset: 'PDF/A-3a' + }; + let doc = new PDFDocument(options); + const data = logData(doc); + doc.addPage(); + doc.registerFont('Roboto', 'tests/fonts/Roboto-Regular.ttf'); + doc.font('Roboto'); + doc.text('Text'); + doc.end(); + + let fontDescriptor = data[data.length-41]; + + expect(fontDescriptor).not.toContain('/CIDSet'); + }); + }); \ No newline at end of file diff --git a/tests/unit/toContainChunk/index.js b/tests/unit/toContainChunk/index.js index 1eec21706..bf1fc0b80 100644 --- a/tests/unit/toContainChunk/index.js +++ b/tests/unit/toContainChunk/index.js @@ -1,4 +1,4 @@ -import diff from 'jest-diff'; +import { diff } from 'jest-diff'; const buildMessage = (utils, data, chunk, headIndex) => { let message; diff --git a/tests/visual/__image_snapshots__/text-spec-js-text-list-lettered-1-snap.png b/tests/visual/__image_snapshots__/text-spec-js-text-list-lettered-1-snap.png new file mode 100644 index 000000000..27deba7ec Binary files /dev/null and b/tests/visual/__image_snapshots__/text-spec-js-text-list-lettered-1-snap.png differ diff --git a/tests/visual/__image_snapshots__/text-spec-js-text-list-numbered-1-snap.png b/tests/visual/__image_snapshots__/text-spec-js-text-list-numbered-1-snap.png new file mode 100644 index 000000000..3ad335ad8 Binary files /dev/null and b/tests/visual/__image_snapshots__/text-spec-js-text-list-numbered-1-snap.png differ diff --git a/tests/visual/__image_snapshots__/text-spec-js-text-list-with-sub-list-ordered-1-snap.png b/tests/visual/__image_snapshots__/text-spec-js-text-list-with-sub-list-ordered-1-snap.png new file mode 100644 index 000000000..eed6dea1f Binary files /dev/null and b/tests/visual/__image_snapshots__/text-spec-js-text-list-with-sub-list-ordered-1-snap.png differ diff --git a/tests/visual/__image_snapshots__/text-spec-js-text-list-with-sub-list-unordered-1-snap.png b/tests/visual/__image_snapshots__/text-spec-js-text-list-with-sub-list-unordered-1-snap.png new file mode 100644 index 000000000..f458f0d57 Binary files /dev/null and b/tests/visual/__image_snapshots__/text-spec-js-text-list-with-sub-list-unordered-1-snap.png differ diff --git a/tests/visual/text.spec.js b/tests/visual/text.spec.js index 4c504df08..b44d2cd2c 100644 --- a/tests/visual/text.spec.js +++ b/tests/visual/text.spec.js @@ -62,6 +62,34 @@ describe('text', function() { return runDocTest(function(doc) { doc.font('tests/fonts/Roboto-Regular.ttf'); doc.list(['Foo\nBar', 'Foo\rBar', 'Foo\r\nBar'], [100, 150]); + }); + }); + + test('list (numbered)', function() { + return runDocTest(function(doc) { + doc.font('tests/fonts/Roboto-Regular.ttf'); + doc.fillColor('#000').list(['One', 'Two', 'Three'], 100, 150, {listType: 'numbered'}); + }); + }); + + test('list (lettered)', function() { + return runDocTest(function(doc) { + doc.font('tests/fonts/Roboto-Regular.ttf'); + doc.fillColor('#000').list(['One', 'Two', 'Three'], 100, 150, {listType: 'lettered'}); + }); + }); + + test('list with sub-list (unordered)', function() { + return runDocTest(function(doc) { + doc.font('tests/fonts/Roboto-Regular.ttf'); + doc.fillColor('#000').list(['One', ['One.One', 'One.Two'], 'Three'], 100, 150); + }) + }) + + test('list with sub-list (ordered)', function() { + return runDocTest(function(doc) { + doc.font('tests/fonts/Roboto-Regular.ttf'); + doc.fillColor('#000').list(['One', ['One.One', 'One.Two'], 'Three'], 100, 150, {listType: 'numbered'}); }) }) });