From 676a1c80de8f18ed8bd34011866e238cb4898d76 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Tue, 4 Oct 2016 16:11:27 +0200 Subject: [PATCH 1/8] Increase PDF version 1.3 -> 1.4 --- lib/document.coffee | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/document.coffee b/lib/document.coffee index 2dac405a2..275af2290 100644 --- a/lib/document.coffee +++ b/lib/document.coffee @@ -12,13 +12,13 @@ PDFPage = require './page' class PDFDocument extends stream.Readable constructor: (@options = {}) -> super - + # PDF version - @version = 1.3 - + @version = 1.4 + # Whether streams should be compressed @compress = @options.compress ? yes - + @_pageBuffer = [] @_pageBufferStart = 0 @@ -27,24 +27,24 @@ class PDFDocument extends stream.Readable @_waiting = 0 @_ended = false @_offset = 0 - + @_root = @ref Type: 'Catalog' Pages: @ref Type: 'Pages' Count: 0 Kids: [] - + # The current page @page = null - + # Initialize mixins @initColor() @initVector() @initFonts() @initText() @initImages() - + # Initialize the metadata @info = Producer: 'PDFKit' @@ -54,22 +54,22 @@ class PDFDocument extends stream.Readable if @options.info for key, val of @options.info @info[key] = val - + # Write the header # PDF version @_write "%PDF-#{@version}" # 4 binary chars, as recommended by the spec @_write "%\xFF\xFF\xFF\xFF" - + # Add the first page if @options.autoFirstPage isnt false @addPage() - + mixin = (methods) => for name, method of methods this::[name] = method - + # Load mixins mixin require './mixins/color' mixin require './mixins/vector' @@ -77,7 +77,7 @@ class PDFDocument extends stream.Readable mixin require './mixins/text' mixin require './mixins/images' mixin require './mixins/annotations' - + addPage: (options = @options) -> # end the current page if needed @flushPages() unless @options.bufferPages @@ -90,27 +90,27 @@ class PDFDocument extends stream.Readable pages = @_root.data.Pages.data pages.Kids.push @page.dictionary pages.Count++ - + # reset x and y coordinates @x = @page.margins.left @y = @page.margins.top - + # flip PDF coordinate system so that the origin is in # the top left rather than the bottom left @_ctm = [1, 0, 0, 1, 0, 0] @transform 1, 0, 0, -1, 0, @page.height @emit('pageAdded') - + return this - bufferedPageRange: -> + bufferedPageRange: -> return { start: @_pageBufferStart, count: @_pageBuffer.length } switchToPage: (n) -> unless page = @_pageBuffer[n - @_pageBufferStart] throw new Error "switchToPage(#{n}) out of bounds, current buffer covers pages #{@_pageBufferStart} to #{@_pageBufferStart + @_pageBuffer.length - 1}" - + @page = page flushPages: -> @@ -121,7 +121,7 @@ class PDFDocument extends stream.Readable @_pageBufferStart += pages.length for page in pages page.end() - + return ref: (data) -> @@ -129,95 +129,95 @@ class PDFDocument extends stream.Readable @_offsets.push null # placeholder for this object's offset once it is finalized @_waiting++ return ref - + _read: -> # do nothing, but this method is required by node - + _write: (data) -> unless Buffer.isBuffer(data) data = new Buffer(data + '\n', 'binary') - + @push data @_offset += data.length - + addContent: (data) -> @page.write data return this - + _refEnd: (ref) -> @_offsets[ref.id - 1] = ref.offset if --@_waiting is 0 and @_ended @_finalize() @_ended = false - + write: (filename, fn) -> # print a deprecation warning with a stacktrace err = new Error ' PDFDocument#write is deprecated, and will be removed in a future version of PDFKit. Please pipe the document into a Node stream. ' - + console.warn err.stack - + @pipe fs.createWriteStream(filename) @end() @once 'end', fn - + output: (fn) -> # more difficult to support this. It would involve concatenating all the buffers together throw new Error ' PDFDocument#output is deprecated, and has been removed from PDFKit. Please pipe the document into a Node stream. ' - + end: -> @flushPages() @_info = @ref() for key, val of @info if typeof val is 'string' val = new String val - + @_info.data[key] = val - + @_info.end() - + for name, font of @_fontFamilies font.finalize() - + @_root.end() @_root.data.Pages.end() - + if @_waiting is 0 @_finalize() else @_ended = true - - _finalize: (fn) -> + + _finalize: (fn) -> # generate xref xRefOffset = @_offset @_write "xref" @_write "0 #{@_offsets.length + 1}" @_write "0000000000 65535 f " - + for offset in @_offsets offset = ('0000000000' + offset).slice(-10) @_write offset + ' 00000 n ' - + # trailer @_write 'trailer' @_write PDFObject.convert Size: @_offsets.length + 1 Root: @_root Info: @_info - + @_write 'startxref' @_write "#{xRefOffset}" @_write '%%EOF' # end the stream @push null - + toString: -> "[object PDFDocument]" - + module.exports = PDFDocument From 0928525e2c51af8acd00c5acb974b10bb79285d9 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Tue, 4 Oct 2016 16:13:46 +0200 Subject: [PATCH 2/8] Extract static method from PDFImage.open() --- lib/image.coffee | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/image.coffee b/lib/image.coffee index 5078c4ab2..17af49ffb 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -9,26 +9,28 @@ JPEG = require './image/jpeg' PNG = require './image/png' class PDFImage - @open: (src, label) -> + + @toBuffer: (src) -> if Buffer.isBuffer(src) - data = src + src else if src instanceof ArrayBuffer - data = new Buffer(new Uint8Array(src)) + new Buffer(new Uint8Array(src)) + else if match = /^data:.+;base64,(.*)$/.exec(src) + new Buffer(match[1], 'base64') else - if match = /^data:.+;base64,(.*)$/.exec(src) - data = new Buffer(match[1], 'base64') + fs.readFileSync src + + @open: (src, label) -> + data = @toBuffer src + return unless data - else - data = fs.readFileSync src - return unless data - if data[0] is 0xff and data[1] is 0xd8 return new JPEG(data, label) - + else if data[0] is 0x89 and data.toString('ascii', 1, 4) is 'PNG' return new PNG(data, label) - + else throw new Error 'Unknown image format.' - -module.exports = PDFImage \ No newline at end of file + +module.exports = PDFImage From 5e5d0c8040a3750360499ea90a576715a896d353 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Tue, 4 Oct 2016 16:31:32 +0200 Subject: [PATCH 3/8] Extract static methods from PDFImage.open() --- lib/image.coffee | 14 +++++++------- lib/image/jpeg.coffee | 22 +++++++++++++--------- lib/image/png.coffee | 5 +++++ 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/image.coffee b/lib/image.coffee index 17af49ffb..c1766cb00 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -22,15 +22,15 @@ class PDFImage @open: (src, label) -> data = @toBuffer src - return unless data + if data - if data[0] is 0xff and data[1] is 0xd8 - return new JPEG(data, label) + if JPEG.is(data) + return new JPEG(data, label) - else if data[0] is 0x89 and data.toString('ascii', 1, 4) is 'PNG' - return new PNG(data, label) + else if PNG.is(data) + return new PNG(data, label) - else - throw new Error 'Unknown image format.' + else + throw new Error 'Unknown image format.' module.exports = PDFImage diff --git a/lib/image/jpeg.coffee b/lib/image/jpeg.coffee index c7cebf8c1..8feb7fcc8 100644 --- a/lib/image/jpeg.coffee +++ b/lib/image/jpeg.coffee @@ -4,10 +4,14 @@ class JPEG MARKERS = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC5, 0xFFC6, 0xFFC7, 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF] + # Returns true if the given data buffer has a JPEG signature + @is: (data) -> + data[0] is 0xff and data[1] is 0xd8 + constructor: (@data, @label) -> if @data.readUInt16BE(0) isnt 0xFFD8 throw "SOI not found in JPEG" - + pos = 2 while pos < @data.length marker = @data.readUInt16BE(pos) @@ -30,12 +34,12 @@ class JPEG when 1 then 'DeviceGray' when 3 then 'DeviceRGB' when 4 then 'DeviceCMYK' - + @obj = null - + embed: (document) -> return if @obj - + @obj = document.ref Type: 'XObject' Subtype: 'Image' @@ -44,16 +48,16 @@ class JPEG Height: @height ColorSpace: @colorSpace Filter: 'DCTDecode' - + # add extra decode params for CMYK images. By swapping the # min and max values from the default, we invert the colors. See - # section 4.8.4 of the spec. + # section 4.8.4 of the spec. if @colorSpace is 'DeviceCMYK' @obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - + @obj.end @data - + # free memory @data = null - + module.exports = JPEG diff --git a/lib/image/png.coffee b/lib/image/png.coffee index 11090ed5d..8c4ee07e4 100644 --- a/lib/image/png.coffee +++ b/lib/image/png.coffee @@ -2,6 +2,11 @@ zlib = require 'zlib' PNG = require 'png-js' class PNGImage + + # Returns true if the given data buffer has a PNG signature + @is: (data) -> + data[0] is 0x89 and data.toString('ascii', 1, 4) is 'PNG' + constructor: (data, @label) -> @image = new PNG(data) @width = @image.width From 63959a8b38bc6bb442c3d577748e6171ed8e9443 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Tue, 4 Oct 2016 18:15:31 +0200 Subject: [PATCH 4/8] Add optional alpha mask for JPG images --- lib/image.coffee | 5 ++- lib/image/jpeg.coffee | 85 +++++++++++++++++++++++++++------------- lib/mixins/images.coffee | 28 ++++++------- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/lib/image.coffee b/lib/image.coffee index c1766cb00..683d4c8c1 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -20,12 +20,13 @@ class PDFImage else fs.readFileSync src - @open: (src, label) -> + @open: (src, label, alphaSrc) -> data = @toBuffer src if data if JPEG.is(data) - return new JPEG(data, label) + alphaData = @toBuffer alphaSrc if alphaSrc + return new JPEG(data, label, alphaData) else if PNG.is(data) return new PNG(data, label) diff --git a/lib/image/jpeg.coffee b/lib/image/jpeg.coffee index 8feb7fcc8..a9703e89b 100644 --- a/lib/image/jpeg.coffee +++ b/lib/image/jpeg.coffee @@ -1,39 +1,50 @@ fs = require 'fs' -class JPEG - MARKERS = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC5, 0xFFC6, 0xFFC7, - 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF] +MARKERS = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC5, 0xFFC6, 0xFFC7, + 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF] - # Returns true if the given data buffer has a JPEG signature - @is: (data) -> - data[0] is 0xff and data[1] is 0xd8 +getMetaInfo = (data, result = {}) -> + if data.readUInt16BE(0) isnt 0xFFD8 + throw "SOI not found in JPEG" - constructor: (@data, @label) -> - if @data.readUInt16BE(0) isnt 0xFFD8 - throw "SOI not found in JPEG" + pos = 2 + while pos < data.length + marker = data.readUInt16BE(pos) + pos += 2 + break if marker in MARKERS + pos += data.readUInt16BE(pos) - pos = 2 - while pos < @data.length - marker = @data.readUInt16BE(pos) - pos += 2 - break if marker in MARKERS - pos += @data.readUInt16BE(pos) + throw "Invalid JPEG." unless marker in MARKERS + pos += 2 - throw "Invalid JPEG." unless marker in MARKERS - pos += 2 + result.bits = data[pos++] + result.height = data.readUInt16BE(pos) + pos += 2 - @bits = @data[pos++] - @height = @data.readUInt16BE(pos) - pos += 2 + result.width = data.readUInt16BE(pos) + pos += 2 - @width = @data.readUInt16BE(pos) - pos += 2 + channels = data[pos++] + result.colorSpace = switch channels + when 1 then 'DeviceGray' + when 3 then 'DeviceRGB' + when 4 then 'DeviceCMYK' + + result + +class JPEG + + # Returns true if the given data buffer has a JPEG signature + @is: (data) -> + data[0] is 0xff and data[1] is 0xd8 + + constructor: (@data, @label, @alphaData) -> + getMetaInfo @data, @ + if @alphaData + @alphaMeta = getMetaInfo @alphaData + if @alphaMeta.colorSpace != 'DeviceGray' + throw new Error('Alpha mask must be a gray JPEG image') - channels = @data[pos++] - @colorSpace = switch channels - when 1 then 'DeviceGray' - when 3 then 'DeviceRGB' - when 4 then 'DeviceCMYK' @obj = null @@ -55,6 +66,26 @@ class JPEG if @colorSpace is 'DeviceCMYK' @obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] + if @alphaData + + sMask = document.ref + Type: 'XObject' + Subtype: 'Image' + Height: @alphaMeta.height + Width: @alphaMeta.width + BitsPerComponent: 8 + Filter: 'DCTDecode' + ColorSpace: 'DeviceGray' + Decode: [0, 1] + + sMask.end @alphaData + + # free memory + @alphaData = null + + @obj.data['SMask'] = sMask + + @obj.end @data # free memory diff --git a/lib/mixins/images.coffee b/lib/mixins/images.coffee index 1458b3c32..a96463df4 100644 --- a/lib/mixins/images.coffee +++ b/lib/mixins/images.coffee @@ -4,29 +4,29 @@ module.exports = initImages: -> @_imageRegistry = {} @_imageCount = 0 - + image: (src, x, y, options = {}) -> if typeof x is 'object' options = x x = null - + x = x ? options.x ? @x y = y ? options.y ? @y - + unless Buffer.isBuffer(src) image = @_imageRegistry[src] - + if not image - image = PDFImage.open src, 'I' + (++@_imageCount) + image = PDFImage.open src, 'I' + (++@_imageCount), options.alpha image.embed this unless Buffer.isBuffer(src) @_imageRegistry[src] = image - + @page.xobjects[image.label] ?= image.obj w = options.width or image.width h = options.height or image.height - + if options.width and not options.height wp = w / image.width w = image.width * wp @@ -51,25 +51,25 @@ module.exports = else h = bh w = bh * ip - + if options.align is 'center' x = x + bw / 2 - w / 2 - + else if options.align is 'right' x = x + bw - w - + if options.valign is 'center' y = y + bh / 2 - h / 2 - + else if options.valign is 'bottom' y = y + bh - h - - # Set the current y position to below the image if it is in the document flow + + # Set the current y position to below the image if it is in the document flow @y += h if @y is y @save() @transform w, 0, 0, -h, x, y + h @addContent "/#{image.label} Do" @restore() - + return this From e8e91b30d814d0a636bd35eb95ef14f16ccfbf59 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Wed, 5 Oct 2016 14:45:33 +0200 Subject: [PATCH 5/8] Remove duplicate empty line --- lib/image/jpeg.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/image/jpeg.coffee b/lib/image/jpeg.coffee index a9703e89b..e8c355ac2 100644 --- a/lib/image/jpeg.coffee +++ b/lib/image/jpeg.coffee @@ -45,7 +45,6 @@ class JPEG if @alphaMeta.colorSpace != 'DeviceGray' throw new Error('Alpha mask must be a gray JPEG image') - @obj = null embed: (document) -> @@ -85,7 +84,6 @@ class JPEG @obj.data['SMask'] = sMask - @obj.end @data # free memory From 6e652f94b5e592460bba447463af989a048aa0d2 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Wed, 5 Oct 2016 15:04:24 +0200 Subject: [PATCH 6/8] Change some white spaces back to master version --- lib/document.coffee | 80 ++++++++++++++++++++-------------------- lib/image.coffee | 4 +- lib/image/jpeg.coffee | 16 ++++---- lib/mixins/images.coffee | 26 ++++++------- 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/lib/document.coffee b/lib/document.coffee index 275af2290..8c8642bbf 100644 --- a/lib/document.coffee +++ b/lib/document.coffee @@ -12,13 +12,13 @@ PDFPage = require './page' class PDFDocument extends stream.Readable constructor: (@options = {}) -> super - + # PDF version @version = 1.4 - + # Whether streams should be compressed @compress = @options.compress ? yes - + @_pageBuffer = [] @_pageBufferStart = 0 @@ -27,24 +27,24 @@ class PDFDocument extends stream.Readable @_waiting = 0 @_ended = false @_offset = 0 - + @_root = @ref Type: 'Catalog' Pages: @ref Type: 'Pages' Count: 0 Kids: [] - + # The current page @page = null - + # Initialize mixins @initColor() @initVector() @initFonts() @initText() @initImages() - + # Initialize the metadata @info = Producer: 'PDFKit' @@ -54,22 +54,22 @@ class PDFDocument extends stream.Readable if @options.info for key, val of @options.info @info[key] = val - + # Write the header # PDF version @_write "%PDF-#{@version}" # 4 binary chars, as recommended by the spec @_write "%\xFF\xFF\xFF\xFF" - + # Add the first page if @options.autoFirstPage isnt false @addPage() - + mixin = (methods) => for name, method of methods this::[name] = method - + # Load mixins mixin require './mixins/color' mixin require './mixins/vector' @@ -77,7 +77,7 @@ class PDFDocument extends stream.Readable mixin require './mixins/text' mixin require './mixins/images' mixin require './mixins/annotations' - + addPage: (options = @options) -> # end the current page if needed @flushPages() unless @options.bufferPages @@ -90,27 +90,27 @@ class PDFDocument extends stream.Readable pages = @_root.data.Pages.data pages.Kids.push @page.dictionary pages.Count++ - + # reset x and y coordinates @x = @page.margins.left @y = @page.margins.top - + # flip PDF coordinate system so that the origin is in # the top left rather than the bottom left @_ctm = [1, 0, 0, 1, 0, 0] @transform 1, 0, 0, -1, 0, @page.height @emit('pageAdded') - + return this - bufferedPageRange: -> + bufferedPageRange: -> return { start: @_pageBufferStart, count: @_pageBuffer.length } switchToPage: (n) -> unless page = @_pageBuffer[n - @_pageBufferStart] throw new Error "switchToPage(#{n}) out of bounds, current buffer covers pages #{@_pageBufferStart} to #{@_pageBufferStart + @_pageBuffer.length - 1}" - + @page = page flushPages: -> @@ -121,7 +121,7 @@ class PDFDocument extends stream.Readable @_pageBufferStart += pages.length for page in pages page.end() - + return ref: (data) -> @@ -129,95 +129,95 @@ class PDFDocument extends stream.Readable @_offsets.push null # placeholder for this object's offset once it is finalized @_waiting++ return ref - + _read: -> # do nothing, but this method is required by node - + _write: (data) -> unless Buffer.isBuffer(data) data = new Buffer(data + '\n', 'binary') - + @push data @_offset += data.length - + addContent: (data) -> @page.write data return this - + _refEnd: (ref) -> @_offsets[ref.id - 1] = ref.offset if --@_waiting is 0 and @_ended @_finalize() @_ended = false - + write: (filename, fn) -> # print a deprecation warning with a stacktrace err = new Error ' PDFDocument#write is deprecated, and will be removed in a future version of PDFKit. Please pipe the document into a Node stream. ' - + console.warn err.stack - + @pipe fs.createWriteStream(filename) @end() @once 'end', fn - + output: (fn) -> # more difficult to support this. It would involve concatenating all the buffers together throw new Error ' PDFDocument#output is deprecated, and has been removed from PDFKit. Please pipe the document into a Node stream. ' - + end: -> @flushPages() @_info = @ref() for key, val of @info if typeof val is 'string' val = new String val - + @_info.data[key] = val - + @_info.end() - + for name, font of @_fontFamilies font.finalize() - + @_root.end() @_root.data.Pages.end() - + if @_waiting is 0 @_finalize() else @_ended = true - - _finalize: (fn) -> + + _finalize: (fn) -> # generate xref xRefOffset = @_offset @_write "xref" @_write "0 #{@_offsets.length + 1}" @_write "0000000000 65535 f " - + for offset in @_offsets offset = ('0000000000' + offset).slice(-10) @_write offset + ' 00000 n ' - + # trailer @_write 'trailer' @_write PDFObject.convert Size: @_offsets.length + 1 Root: @_root Info: @_info - + @_write 'startxref' @_write "#{xRefOffset}" @_write '%%EOF' # end the stream @push null - + toString: -> "[object PDFDocument]" - + module.exports = PDFDocument diff --git a/lib/image.coffee b/lib/image.coffee index 683d4c8c1..d6a29739d 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -33,5 +33,5 @@ class PDFImage else throw new Error 'Unknown image format.' - -module.exports = PDFImage + +module.exports = PDFImage \ No newline at end of file diff --git a/lib/image/jpeg.coffee b/lib/image/jpeg.coffee index e8c355ac2..e731647a5 100644 --- a/lib/image/jpeg.coffee +++ b/lib/image/jpeg.coffee @@ -44,12 +44,12 @@ class JPEG @alphaMeta = getMetaInfo @alphaData if @alphaMeta.colorSpace != 'DeviceGray' throw new Error('Alpha mask must be a gray JPEG image') - + @obj = null - + embed: (document) -> return if @obj - + @obj = document.ref Type: 'XObject' Subtype: 'Image' @@ -58,13 +58,13 @@ class JPEG Height: @height ColorSpace: @colorSpace Filter: 'DCTDecode' - + # add extra decode params for CMYK images. By swapping the # min and max values from the default, we invert the colors. See - # section 4.8.4 of the spec. + # section 4.8.4 of the spec. if @colorSpace is 'DeviceCMYK' @obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - + if @alphaData sMask = document.ref @@ -85,8 +85,8 @@ class JPEG @obj.data['SMask'] = sMask @obj.end @data - + # free memory @data = null - + module.exports = JPEG diff --git a/lib/mixins/images.coffee b/lib/mixins/images.coffee index a96463df4..40cf4105b 100644 --- a/lib/mixins/images.coffee +++ b/lib/mixins/images.coffee @@ -4,29 +4,29 @@ module.exports = initImages: -> @_imageRegistry = {} @_imageCount = 0 - + image: (src, x, y, options = {}) -> if typeof x is 'object' options = x x = null - + x = x ? options.x ? @x y = y ? options.y ? @y - + unless Buffer.isBuffer(src) image = @_imageRegistry[src] - + if not image image = PDFImage.open src, 'I' + (++@_imageCount), options.alpha image.embed this unless Buffer.isBuffer(src) @_imageRegistry[src] = image - + @page.xobjects[image.label] ?= image.obj w = options.width or image.width h = options.height or image.height - + if options.width and not options.height wp = w / image.width w = image.width * wp @@ -51,25 +51,25 @@ module.exports = else h = bh w = bh * ip - + if options.align is 'center' x = x + bw / 2 - w / 2 - + else if options.align is 'right' x = x + bw - w - + if options.valign is 'center' y = y + bh / 2 - h / 2 - + else if options.valign is 'bottom' y = y + bh - h - - # Set the current y position to below the image if it is in the document flow + + # Set the current y position to below the image if it is in the document flow @y += h if @y is y @save() @transform w, 0, 0, -h, x, y + h @addContent "/#{image.label} Do" @restore() - + return this From 2df5f0438776c091b3a042107aafe9f73578415d Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Tue, 15 Nov 2016 14:01:51 +0100 Subject: [PATCH 7/8] Extract private method --- lib/image.coffee | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/image.coffee b/lib/image.coffee index d6a29739d..3508a0720 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -8,24 +8,24 @@ Data = require './data' JPEG = require './image/jpeg' PNG = require './image/png' -class PDFImage +toBuffer = (src) -> + if Buffer.isBuffer(src) + src + else if src instanceof ArrayBuffer + new Buffer(new Uint8Array(src)) + else if match = /^data:.+;base64,(.*)$/.exec(src) + new Buffer(match[1], 'base64') + else + fs.readFileSync src - @toBuffer: (src) -> - if Buffer.isBuffer(src) - src - else if src instanceof ArrayBuffer - new Buffer(new Uint8Array(src)) - else if match = /^data:.+;base64,(.*)$/.exec(src) - new Buffer(match[1], 'base64') - else - fs.readFileSync src +class PDFImage @open: (src, label, alphaSrc) -> - data = @toBuffer src + data = toBuffer src if data if JPEG.is(data) - alphaData = @toBuffer alphaSrc if alphaSrc + alphaData = toBuffer alphaSrc if alphaSrc return new JPEG(data, label, alphaData) else if PNG.is(data) @@ -33,5 +33,5 @@ class PDFImage else throw new Error 'Unknown image format.' - -module.exports = PDFImage \ No newline at end of file + +module.exports = PDFImage From 774fb44589f413ac5bce76223d106f79cde93db9 Mon Sep 17 00:00:00 2001 From: Hendrik Helwich Date: Tue, 15 Nov 2016 14:47:20 +0100 Subject: [PATCH 8/8] Implement optional alpha channel also for PNG images --- lib/image.coffee | 15 +++++++++++---- lib/image/jpeg.coffee | 42 +++++++++++------------------------------- lib/image/png.coffee | 7 +++++-- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/lib/image.coffee b/lib/image.coffee index 3508a0720..8458d20ae 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -22,14 +22,21 @@ class PDFImage @open: (src, label, alphaSrc) -> data = toBuffer src - if data + if alphaSrc + alphaData = toBuffer alphaSrc + if not JPEG.is alphaData + throw Error 'Alpha mask must be a gray JPEG image' + alpha = new JPEG alphaData + if alpha.colorSpace != 'DeviceGray' + throw Error 'Alpha mask must be a gray JPEG image' + + if data if JPEG.is(data) - alphaData = toBuffer alphaSrc if alphaSrc - return new JPEG(data, label, alphaData) + return new JPEG(data, label, alpha) else if PNG.is(data) - return new PNG(data, label) + return new PNG(data, label, alpha) else throw new Error 'Unknown image format.' diff --git a/lib/image/jpeg.coffee b/lib/image/jpeg.coffee index e731647a5..03d330b89 100644 --- a/lib/image/jpeg.coffee +++ b/lib/image/jpeg.coffee @@ -38,18 +38,13 @@ class JPEG @is: (data) -> data[0] is 0xff and data[1] is 0xd8 - constructor: (@data, @label, @alphaData) -> + constructor: (@data, @label, @alpha) -> getMetaInfo @data, @ - if @alphaData - @alphaMeta = getMetaInfo @alphaData - if @alphaMeta.colorSpace != 'DeviceGray' - throw new Error('Alpha mask must be a gray JPEG image') - @obj = null - + embed: (document) -> return if @obj - + @obj = document.ref Type: 'XObject' Subtype: 'Image' @@ -58,35 +53,20 @@ class JPEG Height: @height ColorSpace: @colorSpace Filter: 'DCTDecode' - + # add extra decode params for CMYK images. By swapping the # min and max values from the default, we invert the colors. See - # section 4.8.4 of the spec. + # section 4.8.4 of the spec. if @colorSpace is 'DeviceCMYK' @obj.data['Decode'] = [1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0] - - if @alphaData - - sMask = document.ref - Type: 'XObject' - Subtype: 'Image' - Height: @alphaMeta.height - Width: @alphaMeta.width - BitsPerComponent: 8 - Filter: 'DCTDecode' - ColorSpace: 'DeviceGray' - Decode: [0, 1] - - sMask.end @alphaData - - # free memory - @alphaData = null - - @obj.data['SMask'] = sMask + if @alpha + @alpha.embed(document) + @obj.data['SMask'] = @alpha.obj + @obj.end @data - + # free memory @data = null - + module.exports = JPEG diff --git a/lib/image/png.coffee b/lib/image/png.coffee index 8c4ee07e4..b9a268642 100644 --- a/lib/image/png.coffee +++ b/lib/image/png.coffee @@ -7,7 +7,7 @@ class PNGImage @is: (data) -> data[0] is 0x89 and data.toString('ascii', 1, 4) is 'PNG' - constructor: (data, @label) -> + constructor: (data, @label, @alpha) -> @image = new PNG(data) @width = @image.width @height = @image.height @@ -78,7 +78,10 @@ class PNGImage @finalize() finalize: -> - if @alphaChannel + if @alpha + @alpha.embed(@document) + @obj.data['SMask'] = @alpha.obj + else if @alphaChannel sMask = @document.ref Type: 'XObject' Subtype: 'Image'