diff --git a/lib/document.coffee b/lib/document.coffee index c0db7268b..275af2290 100644 --- a/lib/document.coffee +++ b/lib/document.coffee @@ -14,7 +14,7 @@ class PDFDocument extends stream.Readable super # PDF version - @version = 1.3 + @version = 1.4 # Whether streams should be compressed @compress = @options.compress ? yes diff --git a/lib/image.coffee b/lib/image.coffee index 5078c4ab2..8458d20ae 100644 --- a/lib/image.coffee +++ b/lib/image.coffee @@ -8,27 +8,37 @@ Data = require './data' JPEG = require './image/jpeg' PNG = require './image/png' +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) -> - if Buffer.isBuffer(src) - data = src - else if src instanceof ArrayBuffer - data = new Buffer(new Uint8Array(src)) - else - if match = /^data:.+;base64,(.*)$/.exec(src) - data = new Buffer(match[1], 'base64') + + @open: (src, label, alphaSrc) -> + data = toBuffer src + + 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) + return new JPEG(data, label, alpha) + + else if PNG.is(data) + return new PNG(data, label, alpha) 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 + throw new Error 'Unknown image format.' + +module.exports = PDFImage diff --git a/lib/image/jpeg.coffee b/lib/image/jpeg.coffee index c7cebf8c1..03d330b89 100644 --- a/lib/image/jpeg.coffee +++ b/lib/image/jpeg.coffee @@ -1,41 +1,50 @@ fs = require 'fs' -class JPEG - MARKERS = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC5, 0xFFC6, 0xFFC7, - 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF] - - 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) - - throw "Invalid JPEG." unless marker in MARKERS - pos += 2 +MARKERS = [0xFFC0, 0xFFC1, 0xFFC2, 0xFFC3, 0xFFC5, 0xFFC6, 0xFFC7, + 0xFFC8, 0xFFC9, 0xFFCA, 0xFFCB, 0xFFCC, 0xFFCD, 0xFFCE, 0xFFCF] - @bits = @data[pos++] - @height = @data.readUInt16BE(pos) - pos += 2 +getMetaInfo = (data, result = {}) -> + if data.readUInt16BE(0) isnt 0xFFD8 + throw "SOI not found in JPEG" - @width = @data.readUInt16BE(pos) + pos = 2 + while pos < data.length + marker = data.readUInt16BE(pos) pos += 2 + break if marker in MARKERS + pos += data.readUInt16BE(pos) - channels = @data[pos++] - @colorSpace = switch channels - when 1 then 'DeviceGray' - when 3 then 'DeviceRGB' - when 4 then 'DeviceCMYK' - + throw "Invalid JPEG." unless marker in MARKERS + pos += 2 + + result.bits = data[pos++] + result.height = data.readUInt16BE(pos) + pos += 2 + + result.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, @alpha) -> + getMetaInfo @data, @ @obj = null - + embed: (document) -> return if @obj - + @obj = document.ref Type: 'XObject' Subtype: 'Image' @@ -44,16 +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 @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 11090ed5d..b9a268642 100644 --- a/lib/image/png.coffee +++ b/lib/image/png.coffee @@ -2,7 +2,12 @@ zlib = require 'zlib' PNG = require 'png-js' class PNGImage - constructor: (data, @label) -> + + # 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, @alpha) -> @image = new PNG(data) @width = @image.width @height = @image.height @@ -73,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' diff --git a/lib/mixins/images.coffee b/lib/mixins/images.coffee index e532194f8..69728eb36 100644 --- a/lib/mixins/images.coffee +++ b/lib/mixins/images.coffee @@ -4,7 +4,7 @@ module.exports = initImages: -> @_imageRegistry = {} @_imageCount = 0 - + image: (src, x, y, options = {}) -> if typeof x is 'object' options = x @@ -20,7 +20,7 @@ module.exports = if src.width and src.height image = src else - image = @openImage src + image = @openImage src, options.alpha unless image.obj image.embed this @@ -77,7 +77,7 @@ module.exports = 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() @@ -87,12 +87,12 @@ module.exports = return this - openImage: (src) -> + openImage: (src, alpha) -> if typeof src is 'string' image = @_imageRegistry[src] if not image - image = PDFImage.open src, 'I' + (++@_imageCount) + image = PDFImage.open src, 'I' + (++@_imageCount), alpha if typeof src is 'string' @_imageRegistry[src] = image