diff --git a/pitfall/pdfkit/lib/document.coffee b/pitfall/pdfkit/lib/document.coffee index 0493d95f..a1e750e5 100644 --- a/pitfall/pdfkit/lib/document.coffee +++ b/pitfall/pdfkit/lib/document.coffee @@ -145,11 +145,12 @@ class PDFDocument extends stream.Readable return this _refEnd: (ref) -> - console.log(ref.id) - console.log(@_offsets) + #console.log(ref.id) + #console.log(@_offsets) @_offsets[ref.id - 1] = ref.offset if --@_waiting is 0 and @_ended - console.log("finalize") ; @_finalize() + #console.log("finalize") ; + @_finalize() @_ended = false write: (filename, fn) -> @@ -173,8 +174,8 @@ class PDFDocument extends stream.Readable ' end: -> - console.log("start document end") - console.log(@_offsets) + #console.log("start document end") + #console.log(@_offsets) @flushPages() @_info = @ref() for key, val of @info @@ -182,25 +183,26 @@ class PDFDocument extends stream.Readable val = new String val @_info.data[key] = val - console.log(@_offsets) + #console.log(@_offsets) @_info.end() for name, font of @_fontFamilies font.finalize() - console.log(@_offsets) + #console.log(@_offsets) @_root.end() - console.log(@_offsets) + #console.log(@_offsets) @_root.data.Pages.end() - console.log(@_offsets) + #console.log(@_offsets) if @_waiting is 0 - console.log(@_offsets.length) - console.log("finalize2") ; @_finalize() + #console.log(@_offsets.length) + #console.log("finalize2") ; @_finalize() else - console.log("ended is true") ; @_ended = true + #console.log("ended is true") ; + @_ended = true _finalize: (fn) -> # generate xref diff --git a/pitfall/pdfkit/lib/image/png.coffee b/pitfall/pdfkit/lib/image/png.coffee index e5d3f088..340c88c1 100644 --- a/pitfall/pdfkit/lib/image/png.coffee +++ b/pitfall/pdfkit/lib/image/png.coffee @@ -3,15 +3,15 @@ PNG = require 'png-js' class PNGImage constructor: (data, @label) -> - console.log("raw data") - console.log(data.slice(0, 20)) + #console.log("raw data") + #console.log(data.slice(0, 20)) @image = new PNG(data) @width = @image.width @height = @image.height @imgData = @image.imgData - console.log("result from png-js") - console.log(@imgData.slice(0, 20)) - console.log(@image) + #console.log("result from png-js") + #console.log(@imgData.slice(0, 20)) + #console.log(@image) @obj = null embed: (@document) -> @@ -47,7 +47,7 @@ class PNGImage # For PNG color types 0, 2 and 3, the transparency data is stored in # a dedicated PNG chunk. - if @image.transparency.grayscale + if @image.transparency.grayscale console.log("transparency.grayscale") # Use Color Key Masking (spec section 4.8.5) # An array with N elements, where N is two times the number of color components. @@ -72,7 +72,7 @@ class PNGImage @loadIndexedAlphaChannel() else if @image.hasAlphaChannel - console.log("alphachannel") + console.log("got alphachannel " + @image.colorType + " in png.coffee") # For PNG color types 4 and 6, the transparency data is stored as a alpha # channel mixed in with the main image data. Separate this data out into an # SMask object and store it separately in the PDF. @@ -97,7 +97,7 @@ class PNGImage @obj.data['SMask'] = sMask # add the actual image data - console.log(@imgData) + #console.log(@imgData) @obj.end @imgData # free memory @@ -105,6 +105,7 @@ class PNGImage @imgData = null splitAlphaChannel: -> + console.log("start splitAlphaChannel in png.coffee") @image.decodePixels (pixels) => colorByteSize = @image.colors * @image.bits / 8 pixelCount = @width * @height diff --git a/pitfall/pdfkit/lib/reference.coffee b/pitfall/pdfkit/lib/reference.coffee index b4295faf..18a6c7f8 100644 --- a/pitfall/pdfkit/lib/reference.coffee +++ b/pitfall/pdfkit/lib/reference.coffee @@ -48,7 +48,7 @@ class PDFReference extends stream.Writable end: (chunk) -> super - console.log("end! " + @id) + #console.log("end! " + @id) # console.log(@chunks) if @deflate @deflate.end() @@ -75,7 +75,7 @@ class PDFReference extends stream.Writable @document._write '\nendstream' @document._write 'endobj' - console.log(@id) + #console.log(@id) @document._refEnd(this) toString: -> diff --git a/pitfall/pdfkit/node_modules/pako/lib/inflate.js b/pitfall/pdfkit/node_modules/pako/lib/inflate.js index 0ed4999d..5f684883 100644 --- a/pitfall/pdfkit/node_modules/pako/lib/inflate.js +++ b/pitfall/pdfkit/node_modules/pako/lib/inflate.js @@ -376,6 +376,7 @@ Inflate.prototype.onEnd = function (status) { * ``` **/ function inflate(input, options) { + console.log("inflating"); var inflator = new Inflate(options); inflator.push(input, true); diff --git a/pitfall/pdfkit/node_modules/pako/lib/zlib/inflate.js b/pitfall/pdfkit/node_modules/pako/lib/zlib/inflate.js index 3be8b62e..bea70b5e 100644 --- a/pitfall/pdfkit/node_modules/pako/lib/zlib/inflate.js +++ b/pitfall/pdfkit/node_modules/pako/lib/zlib/inflate.js @@ -361,6 +361,8 @@ function updatewindow(strm, src, end, copy) { } function inflate(strm, flush) { + + console.log("inflating big time"); var state; var input, output; // input/output buffers var next; /* next input INDEX */ diff --git a/pitfall/pdfkit/node_modules/png-js/png-node.coffee b/pitfall/pdfkit/node_modules/png-js/png-node.coffee index 10e4b2fa..50b19820 100644 --- a/pitfall/pdfkit/node_modules/png-js/png-node.coffee +++ b/pitfall/pdfkit/node_modules/png-js/png-node.coffee @@ -140,6 +140,9 @@ module.exports = class PNG pixelBytes = @pixelBitlength / 8 scanlineLength = pixelBytes * @width + console.log("pixelBytes="+ pixelBytes) + console.log("scanlineLength="+ scanlineLength) + pixels = new Buffer(scanlineLength * @height) length = data.length row = 0 diff --git a/pitfall/pdfkit/node_modules/png-js/png-node.js b/pitfall/pdfkit/node_modules/png-js/png-node.js index 313e553e..a8d69d8b 100644 --- a/pitfall/pdfkit/node_modules/png-js/png-node.js +++ b/pitfall/pdfkit/node_modules/png-js/png-node.js @@ -56,8 +56,8 @@ this.text = {}; while (true) { chunkSize = this.readUInt32(); - console.log("chunkSize") - console.log(chunkSize) + //console.log("chunkSize") + //console.log(chunkSize) section = ((function() { var _i, _results; _results = []; @@ -66,8 +66,8 @@ } return _results; }).call(this)).join(''); - console.log("section") - console.log(section) + //console.log("section") + //console.log(section) switch (section) { case 'IHDR': this.width = this.readUInt32(); @@ -173,6 +173,7 @@ }; PNG.prototype.decodePixels = function(fn) { + console.log("start decodePixels in png-node.js") var _this = this; return zlib.inflate(this.imgData, function(err, data) { var byte, c, col, i, left, length, p, pa, paeth, pb, pc, pixelBytes, pixels, pos, row, scanlineLength, upper, upperLeft, _i, _j, _k, _l, _m; @@ -181,6 +182,9 @@ } pixelBytes = _this.pixelBitlength / 8; scanlineLength = pixelBytes * _this.width; + console.log("pixelBytes="+ pixelBytes); + console.log("scanlineLength="+ scanlineLength); + pixels = new Buffer(scanlineLength * _this.height); length = data.length; row = 0; diff --git a/pitfall/pdfkit/node_modules/png-js/png.coffee b/pitfall/pdfkit/node_modules/png-js/png.coffee index 2bcf3774..fdcc5b23 100644 --- a/pitfall/pdfkit/node_modules/png-js/png.coffee +++ b/pitfall/pdfkit/node_modules/png-js/png.coffee @@ -145,8 +145,8 @@ class PNG when 1 then 'DeviceGray' when 3 then 'DeviceRGB' - console.log("imgdata") - console.log(@imgData) + #console.log("imgdata") + #console.log(@imgData) @imgData = new Uint8Array @imgData return @@ -159,7 +159,7 @@ class PNG if @pos > @data.length throw new Error "Incomplete or corrupt PNG file" - console.log("done parsing PNG") + #console.log("done parsing PNG") return diff --git a/pitfall/pitfall/helper.rkt b/pitfall/pitfall/helper.rkt index 21e81968..0d0dd829 100644 --- a/pitfall/pitfall/helper.rkt +++ b/pitfall/pitfall/helper.rkt @@ -74,6 +74,11 @@ [(_ field o expr) #'(begin (set-field! field o (+ (get-field field o) expr)) (get-field field o))])) +(define-syntax (increment! stx) + (syntax-case stx () + [(_ id) #'(increment! id 1)] + [(_ id expr) + #'(begin (set! id (+ id expr)) id)])) (module+ test @@ -172,7 +177,7 @@ (define (bytes->hex bstr) (map (λ (b) (string->symbol (string-append (if (< b 16) - "x0" "x") (~r b #:base 16)))) (bytes->list bstr))) + "x0" "x") (~r b #:base 16)))) (bytes->list bstr))) (module+ test (check-equal? (bytes->hex #"PNG") '(x50 x4e x47))) \ No newline at end of file diff --git a/pitfall/pitfall/png-reader.rkt b/pitfall/pitfall/png-reader.rkt index 8a4ca416..3faf45c8 100644 --- a/pitfall/pitfall/png-reader.rkt +++ b/pitfall/pitfall/png-reader.rkt @@ -1,5 +1,6 @@ #lang pitfall/racket -(provide read-png) +(require "zlib.rkt") +(provide read-png decodePixels) #| Grab key chunks from PNG. Doesn't require heavy lifting from libpng. @@ -29,12 +30,40 @@ Grab key chunks from PNG. Doesn't require heavy lifting from libpng. 'interlaceMethod (read-byte))] [(#"PLTE") (hash-set*! png 'palette (read-bytes chunk-size))] [(#"IDAT") (hash-set*! png 'imgData (read-bytes chunk-size))] - [(#"tRNS") (read-bytes chunk-size)] ; todo implement this section - [(#"tEXt") (read-bytes chunk-size)] ; todo implement this section + [(#"tRNS") + ;; This chunk can only occur once and it must occur after the + ;; PLTE chunk and before the IDAT chunk. + (define transparency (mhash)) + (case (hash-ref png 'colorType (λ () (error 'read-png "PNG file is loco"))) + [(3) + ;; Indexed color, RGB. Each byte in this chunk is an alpha for + ;; the palette index in the PLTE ("palette") chunk up until the + ;; last non-opaque entry. Set up an array, stretching over all + ;; palette entries which will be 0 (opaque) or 1 (transparent). + (hash-set! transparency 'indexed + (append (read-bytes chunk-size) + (make-list (min 0 (- 255 chunk-size)) 255)))] + [(0) + ;; Greyscale. Corresponding to entries in the PLTE chunk. + ;; Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1] + (hash-set! transparency 'grayscale (bytes-ref (read-bytes chunk-size) 0))] + [(2) + ;; True color with proper alpha channel. + (hash-set! transparency 'rgb (read-bytes chunk-size))]) + (report (hash-set! png 'transparency transparency))] + [(#"tEXt") + (define text (read-bytes chunk-size)) + #| + text = @read(chunkSize) + index = text.indexOf(0) + key = String.fromCharCode text.slice(0, index)... + @text[key] = String.fromCharCode text.slice(index + 1)... +|# + 42] [(#"IEND") (define color-value (case (hash-ref png 'colorType) [(0 3 4) 1] [(2 6) 3])) - (define alpha-value (member (hash-ref png 'colorType) '(4 6))) + (define alpha-value (and (member (hash-ref png 'colorType) '(4 6)) (hash-ref png 'colorType))) (hash-set*! png 'colors color-value 'hasAlphaChannel alpha-value @@ -46,6 +75,100 @@ Grab key chunks from PNG. Doesn't require heavy lifting from libpng. (read-bytes 4) ; skip crc (loop)])))) +(define/contract (decodePixels imgData pixelBitLength width height fn) + (bytes? number? number? number? procedure? . -> . any/c) + (define data (inflate imgData)) + (define pixelBytes (/ pixelBitLength 8)) + (define scanlineLength (* pixelBytes width)) + + (define pixels (make-bytes (* scanlineLength height))) + (define length (bytes-length data)) + (define row 0) + (define pos 0) + (define c 0) + + (report* width height) + (parameterize ([current-input-port (open-input-bytes data)]) + (for/fold ([_ #f]) ([row (in-naturals)] + #:break (eof-object? (peek-byte))) + (report row) + (define b (read-byte)) + (case b + [(0) ; none + (for ([i (in-range scanlineLength)]) + (define b (read-byte)) + (bytes-set! pixels c b) + (increment! c))] + [(1) ; sub + (for ([i (in-range scanlineLength)]) + (define byte (read-byte)) + (define left (if (< i pixelBytes) + 0 + (bytes-ref pixels (- c pixelBytes)))) + (bytes-set! pixels c (modulo (+ byte left) 256)) + (increment! c))] + [(2) ; up + (for ([i (in-range scanlineLength)]) + (define byte (read-byte)) + (define col ((i . - . (modulo i pixelBytes)) . / . pixelBytes)) + (define upper (if (zero? row) + row + (+ (* (bytes-ref pixels (sub1 row)) scanlineLength) + (* col pixelBytes) + (modulo i pixelBytes)))) + (bytes-set! pixels c (modulo (+ upper byte) 256)) + (increment! c))] + [(3) ; average + (for ([i (in-range scanlineLength)]) + (define byte (read-byte)) + (define col ((i . - . (modulo i pixelBytes)) . / . pixelBytes)) + (define left (if (< i pixelBytes) + 0 + (bytes-ref pixels (- c pixelBytes)))) + (define upper (if (zero? row) + row + (+ (* (bytes-ref pixels (sub1 row)) scanlineLength) + (* col pixelBytes) + (modulo i pixelBytes)))) + (bytes-set! pixels c (modulo (+ byte (floor (/ (+ left upper) 2))) 256)) + (increment! c))] + [(4) ; paeth + (for ([i (in-range scanlineLength)]) + (define byte (read-byte)) + (define col ((i . - . (modulo i pixelBytes)) . / . pixelBytes)) + (define left (if (< i pixelBytes) + 0 + (bytes-ref pixels (- c pixelBytes)))) + (match-define (list upper upperLeft) + (cond + [(zero? row) (list 0 0)] + [else (define upper (+ (* (bytes-ref pixels (sub1 row)) scanlineLength) + (* col pixelBytes) + (modulo i pixelBytes))) + (define upperLeft (+ (* (bytes-ref pixels (sub1 row)) scanlineLength) + (* (sub1 col) pixelBytes) + (modulo i pixelBytes))) + (list upper upperLeft)])) + + (define p (+ left upper (- upperLeft))) + (define pa (abs (- p left))) + (define pb (abs (- p upper))) + (define pc (abs (- p upperLeft))) + + (define paeth (cond + [((pa . <= . pb) . and . (pa . <= . pc)) left] + [(pb . <= . pc) upper] + [else upperLeft])) + + (bytes-set! pixels c (modulo (+ byte paeth) 256)) + (increment! c) + + )] + [else (error 'invalid-filter-algorithm (format "~a" b))]))) + (report (bytes-length pixels)) + (fn pixels)) + + (define (read-32bit-integer) (define signed #f) (define big-endian #t) (integer-bytes->integer (read-bytes 4) signed big-endian)) diff --git a/pitfall/pitfall/png.rkt b/pitfall/pitfall/png.rkt index a8c250f5..c0fdd6d0 100644 --- a/pitfall/pitfall/png.rkt +++ b/pitfall/pitfall/png.rkt @@ -1,21 +1,27 @@ #lang pitfall/racket -(require "png-reader.rkt") +(require "png-reader.rkt" "zlib.rkt") (provide PNG) -(define-subclass object% (PNG data label) +(define-subclass object% (PNG data [label #f]) (super-new) (field [image (read-png data)] + [pixelBitlength (· image pixelBitlength)] [width (· image width)] [height (· image height)] [imgData (· image imgData)] + [document #f] + [alphaChannel #f] [obj #f]) (as-methods - embed)) + embed + finalize + splitAlphaChannel)) (define/contract (embed this doc-in) (object? . ->m . void?) + (set-field! document this doc-in) (unless (· this obj) (set-field! obj this @@ -29,9 +35,9 @@ (unless (· this image hasAlphaChannel) (define params (send doc-in ref (mhash 'Predictor 15 - 'Colors (· this image colors) - 'BitsPerComponent (· this image bits) - 'Columns (· this width)))) + 'Colors (· this image colors) + 'BitsPerComponent (· this image bits) + 'Columns (· this width)))) (hash-set! (· this obj payload) 'DecodeParms params) (send params end)) @@ -46,10 +52,80 @@ (list "Indexed" "DeviceRGB" (sub1 (bytes-length (· this image palette))) palette-ref))] [else (hash-set! (· this obj payload) 'ColorSpace "DeviceRGB")]) - + + (cond + [(hash-ref (· this image) 'transparency #f) + (cond + [(hash-ref (hash-ref (· this image) 'transparency) 'grayscale #f) + ] + [(hash-ref (hash-ref (· this image) 'transparency) 'rgb #f) + ] + [(hash-ref (hash-ref (· this image) 'transparency) 'indexed #f) + ])] + [(hash-ref (· this image) 'hasAlphaChannel #f) + ;; For PNG color types 4 and 6, the transparency data is stored as a alpha + ;; channel mixed in with the main image data. Separate this data out into an + ;; SMask object and store it separately in the PDF.] + (· this splitAlphaChannel)] + [else (· this finalize)]))) + +(define/contract (finalize this) + (->m void?) + (when (· this alphaChannel) + (define sMask + (send (· this document) ref + (mhash 'Type "XObject" + 'Subtype "Image" + 'Height (· this height) + 'With (· this width) + 'BitsPerComponent 8 + 'Filter "FlateDecode" + 'ColorSpace "DeviceGray" + 'Decode '(0 1)))) + (send sMask end (· this alphaChannel)) + (hash-set! (· this obj payload) 'SMask sMask)) + + ;; embed the actual image data + (send (· this obj) end (· this imgData))) + +(define/contract (splitAlphaChannel this) + (->m void?) + (define (pixel-proc pixels) + (define colorByteSize (* (· this image colors) (/ (· this image bits) 8))) + (define pixelCount (* (· this width) (· this height))) + (define imgData (make-bytes (* pixelCount colorByteSize))) + (define alphaChannel (make-bytes pixelCount)) + + (define i 0) + (define p 0) + (define a 0) + (define len (bytes-length pixels)) + + (for ([idx (in-naturals)] + #:when (< i len)) + (bytes-set! imgData p (bytes-ref pixels i)) + (increment! p) (increment! i) + (bytes-set! imgData p (bytes-ref pixels i)) + (increment! p) (increment! i) + (bytes-set! imgData p (bytes-ref pixels i)) + (increment! p) (increment! i) + (bytes-set! alphaChannel a (bytes-ref pixels i)) + (increment! a) (increment! i)) + + (define done 0) + (set-field! imgData this (deflate imgData)) + (increment! done) + (when (= done 2) (· this finalize)) + + (set-field! alphaChannel this (deflate alphaChannel)) + (increment! done) + (when (= done 2) (· this finalize)) + - ;; todo: transparency & alpha channel shit - ;; embed the actual image data - (send (· this obj) end (· this imgData)))) + ) + (decodePixels (· this imgData) (· this pixelBitlength) (· this width) (· this height) pixel-proc)) +(module+ test + (define pic (make-object PNG (file->bytes "test/assets/test.png"))) + (splitAlphaChannel pic)) \ No newline at end of file diff --git a/pitfall/pitfall/test/test5rkt.pdf b/pitfall/pitfall/test/test5rkt.pdf index ca62972b..2846683a 100644 Binary files a/pitfall/pitfall/test/test5rkt.pdf and b/pitfall/pitfall/test/test5rkt.pdf differ diff --git a/pitfall/pitfall/test/test5 copy.coffee b/pitfall/pitfall/test/test8.coffee similarity index 51% rename from pitfall/pitfall/test/test5 copy.coffee rename to pitfall/pitfall/test/test8.coffee index 3321c810..3f435eb2 100644 --- a/pitfall/pitfall/test/test5 copy.coffee +++ b/pitfall/pitfall/test/test8.coffee @@ -3,18 +3,18 @@ fs = require 'fs' make = (doc) -> # Set the font, draw some text, and embed an image - doc.font('Times-Italic') + doc.font('Helvetica-Bold') .fontSize(25) - .text('Here comes a JPEG!', 100, 100, lineBreak: no) - .image('assets/test.jpeg', 100, 160, width: 412) + .text('Another fantastic pic', 100, 100, lineBreak: no) + .image('assets/test.png', 100, 160, width: 412) doc.end() doc = new PDFDocument({compress: no}) -doc.pipe(fs.createWriteStream('test5.pdf')) +doc.pipe(fs.createWriteStream('test8.pdf')) make doc doc = new PDFDocument({compress: yes}) -doc.pipe(fs.createWriteStream('test5c.pdf')) +doc.pipe(fs.createWriteStream('test8c.pdf')) make doc diff --git a/pitfall/pitfall/test/test8.pdf b/pitfall/pitfall/test/test8.pdf new file mode 100644 index 00000000..40e92791 Binary files /dev/null and b/pitfall/pitfall/test/test8.pdf differ diff --git a/pitfall/pitfall/test/test8.rkt b/pitfall/pitfall/test/test8.rkt new file mode 100644 index 00000000..8a4e7e74 --- /dev/null +++ b/pitfall/pitfall/test/test8.rkt @@ -0,0 +1,16 @@ +#lang pitfall/pdftest + +(define-runtime-path pic "assets/test.png") + +(define (proc doc) + (send* doc + [font "Helvetica-Bold"] + [fontSize 25] + [text "Another fantastic pic" 100 100 (hash 'lineBreak #f)] + [image pic 100 160 (hash 'width 412)])) + +(define-runtime-path this "test8rkt.pdf") +(make-doc this #f proc #:test #f) + +#;(define-runtime-path that "test8crkt.pdf") +#;(make-doc that #t proc) diff --git a/pitfall/pitfall/test/test8c.pdf b/pitfall/pitfall/test/test8c.pdf new file mode 100644 index 00000000..3eb23888 Binary files /dev/null and b/pitfall/pitfall/test/test8c.pdf differ diff --git a/pitfall/pitfall/test/test8rkt.pdf b/pitfall/pitfall/test/test8rkt.pdf new file mode 100644 index 00000000..e69de29b