From 6ccaaff854b185c891c975fc85c4bce668106d79 Mon Sep 17 00:00:00 2001 From: CorvusCorrax Date: Fri, 8 Sep 2017 10:29:36 +0200 Subject: [PATCH 01/39] remove stream extension from reference --- lib/font/embedded.coffee | 3 ++- lib/reference.coffee | 37 ++++++++++++++++--------------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/lib/font/embedded.coffee b/lib/font/embedded.coffee index f374e3ce5..0397581fb 100644 --- a/lib/font/embedded.coffee +++ b/lib/font/embedded.coffee @@ -88,7 +88,8 @@ class EmbeddedFont extends PDFFont if isCFF fontFile.data.Subtype = 'CIDFontType0C' - @subset.encodeStream().pipe(fontFile) + @subset.encodeStream().on 'data', (data) -> + fontFile.write(data); familyClass = (@font['OS/2']?.sFamilyClass or 0) >> 8 flags = 0 diff --git a/lib/reference.coffee b/lib/reference.coffee index 20b05f434..9fc44a97e 100644 --- a/lib/reference.coffee +++ b/lib/reference.coffee @@ -6,68 +6,63 @@ By Devon Govett zlib = require 'zlib' stream = require 'stream' -class PDFReference extends stream.Writable +class PDFReference constructor: (@document, @id, @data = {}) -> - super decodeStrings: no @gen = 0 @deflate = null @compress = @document.compress and not @data.Filter @uncompressedLength = 0 @chunks = [] - + initDeflate: -> @data.Filter = 'FlateDecode' - + @deflate = zlib.createDeflate() @deflate.on 'data', (chunk) => @chunks.push chunk @data.Length += chunk.length - + @deflate.on 'end', @finalize - - _write: (chunk, encoding, callback) -> + + write: (chunk, encoding, callback) -> unless Buffer.isBuffer(chunk) chunk = new Buffer(chunk + '\n', 'binary') - + @uncompressedLength += chunk.length @data.Length ?= 0 - + if @compress @initDeflate() if not @deflate @deflate.write chunk else @chunks.push chunk @data.Length += chunk.length - - callback() - + end: (chunk) -> - super - if @deflate @deflate.end() else @finalize() - + finalize: => @offset = @document._offset - + @document._write "#{@id} #{@gen} obj" @document._write PDFObject.convert(@data) - + if @chunks.length @document._write 'stream' for chunk in @chunks @document._write chunk - + @chunks.length = 0 # free up memory @document._write '\nendstream' - + @document._write 'endobj' @document._refEnd(this) - + toString: -> return "#{@id} #{@gen} R" - + module.exports = PDFReference PDFObject = require './object' From f19b455a448d1ea44220b8e121c26494c525348e Mon Sep 17 00:00:00 2001 From: CorvusCorrax Date: Fri, 8 Sep 2017 11:07:29 +0200 Subject: [PATCH 02/39] Fix font --- lib/font/embedded.coffee | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/font/embedded.coffee b/lib/font/embedded.coffee index 0397581fb..101e0dfcf 100644 --- a/lib/font/embedded.coffee +++ b/lib/font/embedded.coffee @@ -88,8 +88,12 @@ class EmbeddedFont extends PDFFont if isCFF fontFile.data.Subtype = 'CIDFontType0C' - @subset.encodeStream().on 'data', (data) -> - fontFile.write(data); + @subset.encodeStream() + .on 'data', (data) -> + fontFile.write(data) + + .on 'end', () -> + fontFile.end() familyClass = (@font['OS/2']?.sFamilyClass or 0) >> 8 flags = 0 From fd4392d7f941fd1ab021a39cc20e89730235a9ff Mon Sep 17 00:00:00 2001 From: CorvusCorrax Date: Wed, 13 Sep 2017 09:12:57 +0200 Subject: [PATCH 03/39] Use buffer instead of multiple writes --- lib/document.coffee | 22 +++++++++++++---- lib/reference.coffee | 59 +++++++++++++++++--------------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/lib/document.coffee b/lib/document.coffee index c0db7268b..aed787c3f 100644 --- a/lib/document.coffee +++ b/lib/document.coffee @@ -27,13 +27,25 @@ class PDFDocument extends stream.Readable @_waiting = 0 @_ended = false @_offset = 0 + Pages = @ref + Type: 'Pages' + Count: 0 + Kids: new Buffer('') + + Pages.finalize = () -> + @offset = @document._offset; + @document._write(@id + " " + @gen + " obj"); + @document._write('<<'); + @document._write('/Type /Pages'); + @document._write('/Count ' + @data.Count); + @document._write('/Kids [' + @data.Kids.slice(0,-1).toString() + ']'); + @document._write('>>'); + @document._write('endobj'); + return @document._refEnd(@); @_root = @ref Type: 'Catalog' - Pages: @ref - Type: 'Pages' - Count: 0 - Kids: [] + Pages: Pages # The current page @page = null @@ -88,7 +100,7 @@ class PDFDocument extends stream.Readable # add the page to the object store pages = @_root.data.Pages.data - pages.Kids.push @page.dictionary + pages.Kids = Buffer.concat([pages.Kids, new Buffer(@page.dictionary + ' ')]); pages.Count++ # reset x and y coordinates diff --git a/lib/reference.coffee b/lib/reference.coffee index 9fc44a97e..616ca611c 100644 --- a/lib/reference.coffee +++ b/lib/reference.coffee @@ -4,63 +4,50 @@ By Devon Govett ### zlib = require 'zlib' -stream = require 'stream' class PDFReference constructor: (@document, @id, @data = {}) -> @gen = 0 - @deflate = null @compress = @document.compress and not @data.Filter @uncompressedLength = 0 - @chunks = [] + @buffer = new Buffer('') - initDeflate: -> - @data.Filter = 'FlateDecode' - - @deflate = zlib.createDeflate() - @deflate.on 'data', (chunk) => - @chunks.push chunk - @data.Length += chunk.length - - @deflate.on 'end', @finalize - - write: (chunk, encoding, callback) -> + write: (chunk) -> unless Buffer.isBuffer(chunk) chunk = new Buffer(chunk + '\n', 'binary') @uncompressedLength += chunk.length @data.Length ?= 0 - + @buffer = Buffer.concat([@buffer, chunk]) + @data.Length += chunk.length if @compress - @initDeflate() if not @deflate - @deflate.write chunk - else - @chunks.push chunk - @data.Length += chunk.length + @data.Filter = 'FlateDecode' end: (chunk) -> - if @deflate - @deflate.end() - else - @finalize() + if chunk + @write(chunk) + @finalize() finalize: => - @offset = @document._offset - - @document._write "#{@id} #{@gen} obj" - @document._write PDFObject.convert(@data) + setTimeout () => + @offset = @document._offset - if @chunks.length - @document._write 'stream' - for chunk in @chunks - @document._write chunk + @document._write "#{@id} #{@gen} obj" + @document._write PDFObject.convert(@data) - @chunks.length = 0 # free up memory - @document._write '\nendstream' + if @buffer.length + if @compress + @buffer = zlib.deflateSync(@buffer) + @data.Length = @buffer.length + @document._write 'stream' + @document._write @buffer - @document._write 'endobj' - @document._refEnd(this) + @buffer.length = 0 # free up memory + @document._write '\nendstream' + @document._write 'endobj' + @document._refEnd(this) + , 0 toString: -> return "#{@id} #{@gen} R" From 98c4489204c8f168bf51bd84d262d6547e8e1010 Mon Sep 17 00:00:00 2001 From: CorvusCorrax Date: Tue, 19 Sep 2017 11:14:46 +0200 Subject: [PATCH 04/39] use buffer concat only once --- lib/document.coffee | 6 +++--- lib/reference.coffee | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/document.coffee b/lib/document.coffee index aed787c3f..8d2a4468a 100644 --- a/lib/document.coffee +++ b/lib/document.coffee @@ -30,7 +30,7 @@ class PDFDocument extends stream.Readable Pages = @ref Type: 'Pages' Count: 0 - Kids: new Buffer('') + Kids: [] Pages.finalize = () -> @offset = @document._offset; @@ -38,7 +38,7 @@ class PDFDocument extends stream.Readable @document._write('<<'); @document._write('/Type /Pages'); @document._write('/Count ' + @data.Count); - @document._write('/Kids [' + @data.Kids.slice(0,-1).toString() + ']'); + @document._write('/Kids [' + Buffer.concat(@data.Kids).slice(0,-1).toString() + ']'); @document._write('>>'); @document._write('endobj'); return @document._refEnd(@); @@ -100,7 +100,7 @@ class PDFDocument extends stream.Readable # add the page to the object store pages = @_root.data.Pages.data - pages.Kids = Buffer.concat([pages.Kids, new Buffer(@page.dictionary + ' ')]); + pages.Kids.push(new Buffer(@page.dictionary + ' ')) pages.Count++ # reset x and y coordinates diff --git a/lib/reference.coffee b/lib/reference.coffee index 616ca611c..f69e05ec2 100644 --- a/lib/reference.coffee +++ b/lib/reference.coffee @@ -10,7 +10,7 @@ class PDFReference @gen = 0 @compress = @document.compress and not @data.Filter @uncompressedLength = 0 - @buffer = new Buffer('') + @buffer = [] write: (chunk) -> unless Buffer.isBuffer(chunk) @@ -18,7 +18,7 @@ class PDFReference @uncompressedLength += chunk.length @data.Length ?= 0 - @buffer = Buffer.concat([@buffer, chunk]) + @buffer.push(chunk) @data.Length += chunk.length if @compress @data.Filter = 'FlateDecode' @@ -36,6 +36,7 @@ class PDFReference @document._write PDFObject.convert(@data) if @buffer.length + @buffer = Buffer.concat(@buffer) if @compress @buffer = zlib.deflateSync(@buffer) @data.Length = @buffer.length From 797a1609d7d4353708c700e143e27dbfbcc334f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Mon, 6 Aug 2018 21:24:27 -0300 Subject: [PATCH 05/39] Create embed tag name using a deterministic algorithm based on font id --- lib/font/embedded.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/font/embedded.coffee b/lib/font/embedded.coffee index 7a65e1bea..63cf8ba6c 100644 --- a/lib/font/embedded.coffee +++ b/lib/font/embedded.coffee @@ -100,8 +100,8 @@ class EmbeddedFont extends PDFFont flags |= 1 << 3 if familyClass is 10 flags |= 1 << 6 if @font.head.macStyle.italic - # generate a random tag (6 uppercase letters. 65 is the char code for 'A') - tag = (String.fromCharCode Math.random() * 26 + 65 for i in [0...6]).join '' + # generate a tag (6 uppercase letters. 16 is the char code offset from '1' to 'A'. 74 will map to 'Z') + tag = (String.fromCharCode (@id.charCodeAt(i) or 74) + 16 for i in [1...7]).join '' name = tag + '+' + @font.postscriptName bbox = @font.bbox From 569b53f1fa22b2ac80955fe6eeef58eeea160190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luiz=20Am=C3=A9rico?= Date: Wed, 8 Aug 2018 17:53:02 -0300 Subject: [PATCH 06/39] Add integration tests (check for generated pdf). Covers examples distributed by pdfkit and pdfmake --- package.json | 13 +- tests/__snapshots__/text.spec.js.snap | Bin 0 -> 5410 bytes tests/__snapshots__/vector.spec.js.snap | Bin 0 -> 60448 bytes tests/fonts/Roboto-Italic.ttf | Bin 0 -> 161484 bytes tests/fonts/Roboto-Medium.ttf | Bin 0 -> 162588 bytes tests/fonts/Roboto-MediumItalic.ttf | Bin 0 -> 165636 bytes tests/fonts/Roboto-Regular.ttf | Bin 0 -> 162876 bytes tests/helpers.js | 24 + tests/images/bee.jpg | Bin 0 -> 31870 bytes tests/images/bee.js | 8 + tests/images/bee.png | Bin 0 -> 47832 bytes tests/images/sampleImage.jpg | Bin 0 -> 63966 bytes .../__snapshots__/absolute.spec.js.snap | Bin 0 -> 74054 bytes .../__snapshots__/background.spec.js.snap | Bin 0 -> 71956 bytes .../pdfmake/__snapshots__/basics.spec.js.snap | Bin 0 -> 1719 bytes .../__snapshots__/columns_simple.spec.js.snap | Bin 0 -> 52338 bytes .../pdfmake/__snapshots__/images.spec.js.snap | Bin 0 -> 174026 bytes .../pdfmake/__snapshots__/lists.spec.js.snap | Bin 0 -> 43417 bytes tests/pdfmake/absolute.spec.js | 733 ++ tests/pdfmake/background.spec.js | 1119 ++ tests/pdfmake/basics.spec.js | 170 + tests/pdfmake/columns_simple.spec.js | 10807 ++++++++++++++++ tests/pdfmake/images.spec.js | 619 + tests/pdfmake/lists.spec.js | 10257 +++++++++++++++ tests/text.spec.js | 32 + tests/vector.spec.js | 50 + 26 files changed, 23830 insertions(+), 2 deletions(-) create mode 100644 tests/__snapshots__/text.spec.js.snap create mode 100644 tests/__snapshots__/vector.spec.js.snap create mode 100644 tests/fonts/Roboto-Italic.ttf create mode 100644 tests/fonts/Roboto-Medium.ttf create mode 100644 tests/fonts/Roboto-MediumItalic.ttf create mode 100644 tests/fonts/Roboto-Regular.ttf create mode 100644 tests/helpers.js create mode 100644 tests/images/bee.jpg create mode 100644 tests/images/bee.js create mode 100644 tests/images/bee.png create mode 100644 tests/images/sampleImage.jpg create mode 100644 tests/pdfmake/__snapshots__/absolute.spec.js.snap create mode 100644 tests/pdfmake/__snapshots__/background.spec.js.snap create mode 100644 tests/pdfmake/__snapshots__/basics.spec.js.snap create mode 100644 tests/pdfmake/__snapshots__/columns_simple.spec.js.snap create mode 100644 tests/pdfmake/__snapshots__/images.spec.js.snap create mode 100644 tests/pdfmake/__snapshots__/lists.spec.js.snap create mode 100644 tests/pdfmake/absolute.spec.js create mode 100644 tests/pdfmake/background.spec.js create mode 100644 tests/pdfmake/basics.spec.js create mode 100644 tests/pdfmake/columns_simple.spec.js create mode 100644 tests/pdfmake/images.spec.js create mode 100644 tests/pdfmake/lists.spec.js create mode 100644 tests/text.spec.js create mode 100644 tests/vector.spec.js diff --git a/package.json b/package.json index de204d27a..7434b1551 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "exorcist": "^0.1.5", "iconv-lite": "^0.4.13", "jade": "~1.1.5", + "jest": "^23.4.2", "markdown": "~0.5.0" }, "dependencies": { @@ -41,7 +42,8 @@ }, "scripts": { "prepublish": "make js", - "postpublish": "make clean" + "postpublish": "make clean", + "test": "jest -i" }, "main": "js/document", "browserify": { @@ -51,5 +53,12 @@ }, "engine": [ "node >= v0.10.0" - ] + ], + "jest": { + "testPathIgnorePatterns": [ + "/node_modules/", + "/demo/" + ], + "testURL": "http://localhost/" + } } diff --git a/tests/__snapshots__/text.spec.js.snap b/tests/__snapshots__/text.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..4daef1a6550359c1e3b64895d0a63bec8a626fec GIT binary patch literal 5410 zcmeHL+iw(A7zZkso)QXzh=AolFSbB;W_G)`wiJ3>S=!QeTW)S!yWKtAPT9^ZJJYhD zg@l9zAB>4nd=O(ue3VBYcrZSYm>6CZsxd|%d@?@yFZlcB?2TOzNbrenzT5fE?K@}Y z{LVM4DEx%5Y(8eh($<`5^Cj&tpR?_>)vhQ>({v?M%Ir+{=->!rBAYfdwiTVWMb_rA zl%6yegkf`SdXgXE)9gUwNMEQ`b9q?fKi_>@Am-yLH)rNqXD3s}FQo-nMq)|9ATv&P zR7aRHBocb8$ISAm3WrZ~bAQl1$&_Bxu+hxoZL(cNSmt6TE-dbK99>;Z7zrAm%K3Rl zGG=^C*gUErCa#QA#7cMp^Y-L)Z%&6XM+kFp3NNH_e}2^Bz=1~xPAtYnhBss20o`sv zgc%XD%?gih8htTa@aDdDx2gqH2-W$$)53~l({%Zu<>pW@OzDYP!U;<`E>cUv*5fgz zj4jUC&IVFTjVb*`+)U_3k}GF)qua3byrX0SPjQ0ctnQ*S_egqUb}VHk>8T{Kw;CEm zN=BjbqhUJj=Rr&?U=Fr_z5_{11qH#EPeUwQ(m^djZ-c}A)qPO)KYg)#oAg4` zh`)SBo4;-it!L0jBOL>S&P(NQrC4cGC9Ql29g_Bzt5Rp6lG22nlU6g-1b!{DNM}3u zPUKE`HX~-4%7aWECX(Fe^?JN~mgjwKJ{&8j#whwU9OE*dCs^6e>&7cv)=txc6+Tr1 z-jucTAeM5UTP>N_&cyVT$T(0qre79Vh7vIWyL2D{f?;FUIJzdHlnr^g{0rnlC^0OLBK;UiDe-4sjCjL160K9nfdM zwFGQo;$xkVUEl%{B)9d^MZ?!aEEg&WF1LU;=NKvq2GwkUAm|^Wd&43a?Er5FyZ-#- zODGUsTR8~dCq0CVk_dC_!5;AP7(~^?!~v*=p{qla(6Y2QX~G84FpOrPQ1%c;(g2d) zFbXCGoZLdySrukz8NvqBWdc&TeYon0sH$ms2nCE)z^bne$wV3Cd4rX3>GfB(^FIz6 zAJbR!SC02Ms8#)yP}}AQew4-M36#WA0l!#pCH^{RwhVxSZufrxa7srCy_yEEroqpX z276Thvp|_Vwvh8+b!#!#l@%wG4KOGf15qX63ngK;pbWS_|1CB%yaPoLrNo2@r9{Bk zSxcBPCEe=Y(rVb0SLf2UiRl1g9ccL8M{$7E#5C;YwDf6Gjpj;ccen_KC#Lo0KW-d^ z)d&tRybgH*46;rqAQf4FBqW{$6J~*yv{TY3ljknN!Q5)i+@@WWNP^C0TYBrHxlDLS zF}H$gGQv>mu?9@5l|(u8IHCK4+wR;4MwEK4T!SEJfB)gfr2hKmCgd3~==4x4^#jpO zF?Hnk+p+|CxxpA{`t_^v9_h(K9i%9Zk>FKW67@jx1zZLTYC<{NiCq>1cPYN zLt1F*FHH=cKE{;MFtZ!_h?QVP`s=r*ha!|z|5KqjNEqx)K65RO< zV->Mi5&L-%TT}75_Vhn41P)^dq2960>*@4^CZ&Shr?c;`L%YE1SFVrmd2mnOPcZBM79F;p$}(I4)>xY7@y_D1V00*5*po$wq)CgrxECGdqfM7t~pqMg~Oe*v7$|3m-) literal 0 HcmV?d00001 diff --git a/tests/__snapshots__/vector.spec.js.snap b/tests/__snapshots__/vector.spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..6366f6036fdf7dd8e41965ea67ecaad8ca218cd9 GIT binary patch literal 60448 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 z