From aad217a55ea6cfd4557aa7331bb01760ec7e5adc Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Sat, 20 May 2017 12:45:11 -0700 Subject: [PATCH] successful png test in test5 --- pitfall/pdfkit/lib/image/png.coffee | 10 + .../pdfkit/node_modules/png-js/png-node.js | 4 + pitfall/pdfkit/node_modules/png-js/png.coffee | 4 + pitfall/pdfkit/node_modules/png-js/png.js | 458 ------------------ pitfall/pitfall/document.rkt | 15 +- pitfall/pitfall/helper.rkt | 11 +- pitfall/pitfall/png-reader.rkt | 55 +++ pitfall/pitfall/png.rkt | 60 ++- pitfall/pitfall/reference.rkt | 8 +- pitfall/pitfall/test/test5.coffee | 8 +- pitfall/pitfall/test/test5.pdf | Bin 2268 -> 2244 bytes pitfall/pitfall/test/test5.rkt | 12 +- pitfall/pitfall/test/test5c.pdf | Bin 2234 -> 2223 bytes pitfall/pitfall/test/test5crkt copy.pdf | Bin 0 -> 2223 bytes pitfall/pitfall/test/test5crkt.pdf | Bin 0 -> 2223 bytes pitfall/pitfall/test/test5rkt copy.pdf | Bin 0 -> 2244 bytes pitfall/pitfall/test/test5rkt.pdf | Bin 5717 -> 2244 bytes 17 files changed, 131 insertions(+), 514 deletions(-) delete mode 100644 pitfall/pdfkit/node_modules/png-js/png.js create mode 100644 pitfall/pitfall/png-reader.rkt create mode 100644 pitfall/pitfall/test/test5crkt copy.pdf create mode 100644 pitfall/pitfall/test/test5rkt copy.pdf diff --git a/pitfall/pdfkit/lib/image/png.coffee b/pitfall/pdfkit/lib/image/png.coffee index 11090ed5..e5d3f088 100644 --- a/pitfall/pdfkit/lib/image/png.coffee +++ b/pitfall/pdfkit/lib/image/png.coffee @@ -3,10 +3,15 @@ PNG = require 'png-js' class PNGImage constructor: (data, @label) -> + 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) @obj = null embed: (@document) -> @@ -43,12 +48,14 @@ class PNGImage # For PNG color types 0, 2 and 3, the transparency data is stored in # a dedicated PNG chunk. 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. val = @image.transparency.greyscale @obj.data['Mask'] = [val, val] else if @image.transparency.rgb + console.log("transparency.rgb") # Use Color Key Masking (spec section 4.8.5) # An array with N elements, where N is two times the number of color components. rgb = @image.transparency.rgb @@ -59,11 +66,13 @@ class PNGImage @obj.data['Mask'] = mask else if @image.transparency.indexed + console.log("transparency.indexed") # Create a transparency SMask for the image based on the data # in the PLTE and tRNS sections. See below for details on SMasks. @loadIndexedAlphaChannel() else if @image.hasAlphaChannel + console.log("alphachannel") # 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. @@ -88,6 +97,7 @@ class PNGImage @obj.data['SMask'] = sMask # add the actual image data + console.log(@imgData) @obj.end @imgData # free memory diff --git a/pitfall/pdfkit/node_modules/png-js/png-node.js b/pitfall/pdfkit/node_modules/png-js/png-node.js index 76a5a34d..313e553e 100644 --- a/pitfall/pdfkit/node_modules/png-js/png-node.js +++ b/pitfall/pdfkit/node_modules/png-js/png-node.js @@ -56,6 +56,8 @@ this.text = {}; while (true) { chunkSize = this.readUInt32(); + console.log("chunkSize") + console.log(chunkSize) section = ((function() { var _i, _results; _results = []; @@ -64,6 +66,8 @@ } return _results; }).call(this)).join(''); + console.log("section") + console.log(section) switch (section) { case 'IHDR': this.width = this.readUInt32(); diff --git a/pitfall/pdfkit/node_modules/png-js/png.coffee b/pitfall/pdfkit/node_modules/png-js/png.coffee index 80605640..2bcf3774 100644 --- a/pitfall/pdfkit/node_modules/png-js/png.coffee +++ b/pitfall/pdfkit/node_modules/png-js/png.coffee @@ -145,6 +145,8 @@ class PNG when 1 then 'DeviceGray' when 3 then 'DeviceRGB' + console.log("imgdata") + console.log(@imgData) @imgData = new Uint8Array @imgData return @@ -156,6 +158,8 @@ class PNG if @pos > @data.length throw new Error "Incomplete or corrupt PNG file" + + console.log("done parsing PNG") return diff --git a/pitfall/pdfkit/node_modules/png-js/png.js b/pitfall/pdfkit/node_modules/png-js/png.js deleted file mode 100644 index e3308ad0..00000000 --- a/pitfall/pdfkit/node_modules/png-js/png.js +++ /dev/null @@ -1,458 +0,0 @@ -// Generated by CoffeeScript 1.4.0 - -/* -# MIT LICENSE -# Copyright (c) 2011 Devon Govett -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this -# software and associated documentation files (the "Software"), to deal in the Software -# without restriction, including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -# to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or -# substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING -# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - - -(function() { - var PNG; - - PNG = (function() { - var APNG_BLEND_OP_OVER, APNG_BLEND_OP_SOURCE, APNG_DISPOSE_OP_BACKGROUND, APNG_DISPOSE_OP_NONE, APNG_DISPOSE_OP_PREVIOUS, makeImage, scratchCanvas, scratchCtx; - - PNG.load = function(url, canvas, callback) { - var xhr, - _this = this; - if (typeof canvas === 'function') { - callback = canvas; - } - xhr = new XMLHttpRequest; - xhr.open("GET", url, true); - xhr.responseType = "arraybuffer"; - xhr.onload = function() { - var data, png; - data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer); - png = new PNG(data); - if (typeof (canvas != null ? canvas.getContext : void 0) === 'function') { - png.render(canvas); - } - return typeof callback === "function" ? callback(png) : void 0; - }; - return xhr.send(null); - }; - - APNG_DISPOSE_OP_NONE = 0; - - APNG_DISPOSE_OP_BACKGROUND = 1; - - APNG_DISPOSE_OP_PREVIOUS = 2; - - APNG_BLEND_OP_SOURCE = 0; - - APNG_BLEND_OP_OVER = 1; - - function PNG(data) { - var chunkSize, colors, delayDen, delayNum, frame, i, index, key, section, short, text, _i, _j, _ref; - this.data = data; - this.pos = 8; - this.palette = []; - this.imgData = []; - this.transparency = {}; - this.animation = null; - this.text = {}; - frame = null; - while (true) { - chunkSize = this.readUInt32(); - section = ((function() { - var _i, _results; - _results = []; - for (i = _i = 0; _i < 4; i = ++_i) { - _results.push(String.fromCharCode(this.data[this.pos++])); - } - return _results; - }).call(this)).join(''); - switch (section) { - case 'IHDR': - this.width = this.readUInt32(); - this.height = this.readUInt32(); - this.bits = this.data[this.pos++]; - this.colorType = this.data[this.pos++]; - this.compressionMethod = this.data[this.pos++]; - this.filterMethod = this.data[this.pos++]; - this.interlaceMethod = this.data[this.pos++]; - break; - case 'acTL': - this.animation = { - numFrames: this.readUInt32(), - numPlays: this.readUInt32() || Infinity, - frames: [] - }; - break; - case 'PLTE': - this.palette = this.read(chunkSize); - break; - case 'fcTL': - if (frame) { - this.animation.frames.push(frame); - } - this.pos += 4; - frame = { - width: this.readUInt32(), - height: this.readUInt32(), - xOffset: this.readUInt32(), - yOffset: this.readUInt32() - }; - delayNum = this.readUInt16(); - delayDen = this.readUInt16() || 100; - frame.delay = 1000 * delayNum / delayDen; - frame.disposeOp = this.data[this.pos++]; - frame.blendOp = this.data[this.pos++]; - frame.data = []; - break; - case 'IDAT': - case 'fdAT': - if (section === 'fdAT') { - this.pos += 4; - chunkSize -= 4; - } - data = (frame != null ? frame.data : void 0) || this.imgData; - for (i = _i = 0; 0 <= chunkSize ? _i < chunkSize : _i > chunkSize; i = 0 <= chunkSize ? ++_i : --_i) { - data.push(this.data[this.pos++]); - } - break; - case 'tRNS': - this.transparency = {}; - switch (this.colorType) { - case 3: - this.transparency.indexed = this.read(chunkSize); - short = 255 - this.transparency.indexed.length; - if (short > 0) { - for (i = _j = 0; 0 <= short ? _j < short : _j > short; i = 0 <= short ? ++_j : --_j) { - this.transparency.indexed.push(255); - } - } - break; - case 0: - this.transparency.grayscale = this.read(chunkSize)[0]; - break; - case 2: - this.transparency.rgb = this.read(chunkSize); - } - break; - case 'tEXt': - text = this.read(chunkSize); - index = text.indexOf(0); - key = String.fromCharCode.apply(String, text.slice(0, index)); - this.text[key] = String.fromCharCode.apply(String, text.slice(index + 1)); - break; - case 'IEND': - if (frame) { - this.animation.frames.push(frame); - } - this.colors = (function() { - switch (this.colorType) { - case 0: - case 3: - case 4: - return 1; - case 2: - case 6: - return 3; - } - }).call(this); - this.hasAlphaChannel = (_ref = this.colorType) === 4 || _ref === 6; - colors = this.colors + (this.hasAlphaChannel ? 1 : 0); - this.pixelBitlength = this.bits * colors; - this.colorSpace = (function() { - switch (this.colors) { - case 1: - return 'DeviceGray'; - case 3: - return 'DeviceRGB'; - } - }).call(this); - this.imgData = new Uint8Array(this.imgData); - return; - default: - this.pos += chunkSize; - } - this.pos += 4; - if (this.pos > this.data.length) { - throw new Error("Incomplete or corrupt PNG file"); - } - } - return; - } - - PNG.prototype.read = function(bytes) { - var i, _i, _results; - _results = []; - for (i = _i = 0; 0 <= bytes ? _i < bytes : _i > bytes; i = 0 <= bytes ? ++_i : --_i) { - _results.push(this.data[this.pos++]); - } - return _results; - }; - - PNG.prototype.readUInt32 = function() { - var b1, b2, b3, b4; - b1 = this.data[this.pos++] << 24; - b2 = this.data[this.pos++] << 16; - b3 = this.data[this.pos++] << 8; - b4 = this.data[this.pos++]; - return b1 | b2 | b3 | b4; - }; - - PNG.prototype.readUInt16 = function() { - var b1, b2; - b1 = this.data[this.pos++] << 8; - b2 = this.data[this.pos++]; - return b1 | b2; - }; - - PNG.prototype.decodePixels = function(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; - if (data == null) { - data = this.imgData; - } - if (data.length === 0) { - return new Uint8Array(0); - } - data = new FlateStream(data); - data = data.getBytes(); - pixelBytes = this.pixelBitlength / 8; - scanlineLength = pixelBytes * this.width; - pixels = new Uint8Array(scanlineLength * this.height); - length = data.length; - row = 0; - pos = 0; - c = 0; - while (pos < length) { - switch (data[pos++]) { - case 0: - for (i = _i = 0; _i < scanlineLength; i = _i += 1) { - pixels[c++] = data[pos++]; - } - break; - case 1: - for (i = _j = 0; _j < scanlineLength; i = _j += 1) { - byte = data[pos++]; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - pixels[c++] = (byte + left) % 256; - } - break; - case 2: - for (i = _k = 0; _k < scanlineLength; i = _k += 1) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; - pixels[c++] = (upper + byte) % 256; - } - break; - case 3: - for (i = _l = 0; _l < scanlineLength; i = _l += 1) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - upper = row && pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; - pixels[c++] = (byte + Math.floor((left + upper) / 2)) % 256; - } - break; - case 4: - for (i = _m = 0; _m < scanlineLength; i = _m += 1) { - byte = data[pos++]; - col = (i - (i % pixelBytes)) / pixelBytes; - left = i < pixelBytes ? 0 : pixels[c - pixelBytes]; - if (row === 0) { - upper = upperLeft = 0; - } else { - upper = pixels[(row - 1) * scanlineLength + col * pixelBytes + (i % pixelBytes)]; - upperLeft = col && pixels[(row - 1) * scanlineLength + (col - 1) * pixelBytes + (i % pixelBytes)]; - } - p = left + upper - upperLeft; - pa = Math.abs(p - left); - pb = Math.abs(p - upper); - pc = Math.abs(p - upperLeft); - if (pa <= pb && pa <= pc) { - paeth = left; - } else if (pb <= pc) { - paeth = upper; - } else { - paeth = upperLeft; - } - pixels[c++] = (byte + paeth) % 256; - } - break; - default: - throw new Error("Invalid filter algorithm: " + data[pos - 1]); - } - row++; - } - return pixels; - }; - - PNG.prototype.decodePalette = function() { - var c, i, length, palette, pos, ret, transparency, _i, _ref, _ref1; - palette = this.palette; - transparency = this.transparency.indexed || []; - ret = new Uint8Array((transparency.length || 0) + palette.length); - pos = 0; - length = palette.length; - c = 0; - for (i = _i = 0, _ref = palette.length; _i < _ref; i = _i += 3) { - ret[pos++] = palette[i]; - ret[pos++] = palette[i + 1]; - ret[pos++] = palette[i + 2]; - ret[pos++] = (_ref1 = transparency[c++]) != null ? _ref1 : 255; - } - return ret; - }; - - PNG.prototype.copyToImageData = function(imageData, pixels) { - var alpha, colors, data, i, input, j, k, length, palette, v, _ref; - colors = this.colors; - palette = null; - alpha = this.hasAlphaChannel; - if (this.palette.length) { - palette = (_ref = this._decodedPalette) != null ? _ref : this._decodedPalette = this.decodePalette(); - colors = 4; - alpha = true; - } - data = imageData.data; - length = data.length; - input = palette || pixels; - i = j = 0; - if (colors === 1) { - while (i < length) { - k = palette ? pixels[i / 4] * 4 : j; - v = input[k++]; - data[i++] = v; - data[i++] = v; - data[i++] = v; - data[i++] = alpha ? input[k++] : 255; - j = k; - } - } else { - while (i < length) { - k = palette ? pixels[i / 4] * 4 : j; - data[i++] = input[k++]; - data[i++] = input[k++]; - data[i++] = input[k++]; - data[i++] = alpha ? input[k++] : 255; - j = k; - } - } - }; - - PNG.prototype.decode = function() { - var ret; - ret = new Uint8Array(this.width * this.height * 4); - this.copyToImageData(ret, this.decodePixels()); - return ret; - }; - - scratchCanvas = document.createElement('canvas'); - - scratchCtx = scratchCanvas.getContext('2d'); - - makeImage = function(imageData) { - var img; - scratchCtx.width = imageData.width; - scratchCtx.height = imageData.height; - scratchCtx.clearRect(0, 0, imageData.width, imageData.height); - scratchCtx.putImageData(imageData, 0, 0); - img = new Image; - img.src = scratchCanvas.toDataURL(); - return img; - }; - - PNG.prototype.decodeFrames = function(ctx) { - var frame, i, imageData, pixels, _i, _len, _ref, _results; - if (!this.animation) { - return; - } - _ref = this.animation.frames; - _results = []; - for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { - frame = _ref[i]; - imageData = ctx.createImageData(frame.width, frame.height); - pixels = this.decodePixels(new Uint8Array(frame.data)); - this.copyToImageData(imageData, pixels); - frame.imageData = imageData; - _results.push(frame.image = makeImage(imageData)); - } - return _results; - }; - - PNG.prototype.renderFrame = function(ctx, number) { - var frame, frames, prev; - frames = this.animation.frames; - frame = frames[number]; - prev = frames[number - 1]; - if (number === 0) { - ctx.clearRect(0, 0, this.width, this.height); - } - if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_BACKGROUND) { - ctx.clearRect(prev.xOffset, prev.yOffset, prev.width, prev.height); - } else if ((prev != null ? prev.disposeOp : void 0) === APNG_DISPOSE_OP_PREVIOUS) { - ctx.putImageData(prev.imageData, prev.xOffset, prev.yOffset); - } - if (frame.blendOp === APNG_BLEND_OP_SOURCE) { - ctx.clearRect(frame.xOffset, frame.yOffset, frame.width, frame.height); - } - return ctx.drawImage(frame.image, frame.xOffset, frame.yOffset); - }; - - PNG.prototype.animate = function(ctx) { - var doFrame, frameNumber, frames, numFrames, numPlays, _ref, - _this = this; - frameNumber = 0; - _ref = this.animation, numFrames = _ref.numFrames, frames = _ref.frames, numPlays = _ref.numPlays; - return (doFrame = function() { - var f, frame; - f = frameNumber++ % numFrames; - frame = frames[f]; - _this.renderFrame(ctx, f); - if (numFrames > 1 && frameNumber / numFrames < numPlays) { - return _this.animation._timeout = setTimeout(doFrame, frame.delay); - } - })(); - }; - - PNG.prototype.stopAnimation = function() { - var _ref; - return clearTimeout((_ref = this.animation) != null ? _ref._timeout : void 0); - }; - - PNG.prototype.render = function(canvas) { - var ctx, data; - if (canvas._png) { - canvas._png.stopAnimation(); - } - canvas._png = this; - canvas.width = this.width; - canvas.height = this.height; - ctx = canvas.getContext("2d"); - if (this.animation) { - this.decodeFrames(ctx); - return this.animate(ctx); - } else { - data = ctx.createImageData(this.width, this.height); - this.copyToImageData(data, this.decodePixels()); - return ctx.putImageData(data, 0, 0); - } - }; - - return PNG; - - })(); - - window.PNG = PNG; - -}).call(this); diff --git a/pitfall/pitfall/document.rkt b/pitfall/pitfall/document.rkt index e8a8de96..d0871c75 100644 --- a/pitfall/pitfall/document.rkt +++ b/pitfall/pitfall/document.rkt @@ -127,7 +127,7 @@ (define/contract (_refEnd this ref) ((is-a?/c PDFReference) . ->m . void?) - (report* (· ref id) (· this _offsets)) + #;(report* (· ref id) (· this _offsets)) (hash-set! (· this _offsets) (· ref id) (· ref offset))) @@ -138,8 +138,8 @@ (define/contract (end this) ; called from source file to finish doc (->m void?) - (report* 'start-end) - (report* (· this _offsets)) + #;(report* 'start-end) + #;(report* (· this _offsets)) (flushPages this) (define _info (ref this)) @@ -147,18 +147,19 @@ ;; upgrade string literal to String struct (hash-set! (· _info payload) key (if (string? val) (String val) val))) - (report* (· this _offsets)) + + #;(report* (· this _offsets)) (· _info end) (for ([font (in-hash-values (· this _fontFamilies))]) (· font finalize)) - (report* (· this _offsets)) + #;(report* (· this _offsets)) (· this _root end) - (report* (· this _offsets)) + #;(report* (· this _offsets)) (· this _root payload Pages end) - (report* (· this _offsets)) + #;(report* (· this _offsets)) ;; generate xref (define xref-offset (· this _offset)) diff --git a/pitfall/pitfall/helper.rkt b/pitfall/pitfall/helper.rkt index d8eac67f..21e81968 100644 --- a/pitfall/pitfall/helper.rkt +++ b/pitfall/pitfall/helper.rkt @@ -1,5 +1,5 @@ #lang racket/base -(require (for-syntax racket/base racket/syntax) racket/class sugar/list racket/list (only-in br/list push! pop!) racket/string) +(require (for-syntax racket/base racket/syntax) racket/class sugar/list racket/list (only-in br/list push! pop!) racket/string racket/format) (provide (all-defined-out) push! pop!) (define-syntax (· stx) @@ -168,4 +168,11 @@ (define-syntax-rule (define-subclass CLASS-ID (SUBCLASS-ID INIT-FIELD ...) . EXPRS) (define SUBCLASS-ID (class CLASS-ID - (init-field INIT-FIELD ...) . EXPRS))) \ No newline at end of file + (init-field INIT-FIELD ...) . EXPRS))) + +(define (bytes->hex bstr) + (map (λ (b) (string->symbol (string-append (if (< b 16) + "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 new file mode 100644 index 00000000..6553faf1 --- /dev/null +++ b/pitfall/pitfall/png-reader.rkt @@ -0,0 +1,55 @@ +#lang pitfall/racket +(provide read-png) + +#| +Grab key chunks from PNG. Doesn't require heavy lifting from libpng. +|# + +(define/contract (read-png ip-or-bytes) + ((or/c input-port? bytes?) . -> . hash?) + (define png (make-hasheq)) + (parameterize ([current-input-port (if (input-port? ip-or-bytes) + ip-or-bytes + (open-input-bytes ip-or-bytes))]) + (define header (read-bytes 8)) + (let loop () + (cond + [(eof-object? (peek-byte)) png] + [else + (define chunk-size (readUInt32)) + (define chunk-name (read-bytes 4)) + (case chunk-name + [(#"IHDR") (hash-set*! png 'width (readUInt32) + 'height (readUInt32) + 'bits (read-byte) + 'colorType (read-byte) + 'compressionMethod (read-byte) + 'filterMethod (read-byte) + '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)] + [(#"tEXt") (read-bytes chunk-size)] + [(#"IEND") (hash-set! png 'colors + (case (hash-ref png 'colorType) + [(0 3 4) 1] + [(2 6) 3])) + (hash-set! png 'hasAlphaChannel (member (hash-ref png 'colorType) '(4 6))) + (define colors (+ (hash-ref png 'colors) (if (hash-ref png 'hasAlphaChannel) 1 0))) + (hash-set! png 'pixelBitlength (* (hash-ref png 'bits) colors)) + (hash-set! png 'colorSpace + (case (hash-ref png 'colors) + [(1) "DeviceGray"] + [(3) "DeviceRGB"]))] + [else (read-bytes chunk-size)]) + (read-bytes 4) ; skip crc + (loop)])))) + +(define (readUInt32) + (integer-bytes->integer (read-bytes 4) #t #t)) + +(module+ test + (require rackunit) + (check-equal? + (read-png (open-input-file "test/assets/test.png")) + (read-png (file->bytes "test/assets/test.png")))) \ No newline at end of file diff --git a/pitfall/pitfall/png.rkt b/pitfall/pitfall/png.rkt index a86758b7..99d78db3 100644 --- a/pitfall/pitfall/png.rkt +++ b/pitfall/pitfall/png.rkt @@ -1,24 +1,20 @@ #lang pitfall/racket -(require racket/draw/unsafe/png racket/draw/private/bitmap) +(require "png-reader.rkt") (provide PNG) (define-subclass object% (PNG data label) (super-new) - (field [image (make-object bitmap% (open-input-bytes data) 'png)] - [width (· image get-width)] - [height (· image get-height)] - [imgData data] + (field [image (read-png data)] + [width (· image width)] + [height (· image height)] + [imgData (· image imgData)] [obj #f] - [document #f]) ; for `embed` + [document #f]) (as-methods - embed - finalize)) + embed)) - -(define png-grayscale 1) -(define png-color 3) (define/contract (embed this doc-in) (object? . ->m . void?) (set-field! document this doc-in) @@ -28,34 +24,34 @@ (send (· this document) ref (mhash 'Type "XObject" 'Subtype "Image" - 'BitsPerComponent (· this image get-depth) + 'BitsPerComponent (· this image bits) 'Width (· this width) 'Height (· this height) 'Filter "FlateDecode"))) - (define params (mhash)) - (unless (· this image has-alpha-channel?) - (set! params (send (· this document) ref (mhash 'Predictor 15 - 'Colors (if (· this image is-color?) - png-color - png-grayscale) - 'BitsPerComponent (· this image get-depth) - 'Columns (· this width))))) + (unless (· this image hasAlphaChannel) + (define params (send (· this document) ref (mhash 'Predictor 15 + 'Colors (· this image colors) + 'BitsPerComponent (· this image bits) + 'Columns (· this width)))) + (hash-set! (· this obj payload) 'DecodeParms params) + (send params end)) - - (hash-set! (· this obj payload) 'DecodeParms params) - (send params end) + (cond + [(hash-ref (· this image) 'palette #f) + ;; embed the color palette in the PDF as an object stream + (define palette-ref (· this document ref)) + (send palette-ref end (· this image palette)) - (send this finalize) + ;; build the color space array for the image + (hash-set! (· this object payload) 'Colorspace + (list "Indexed" "DeviceRGB" (sub1 (bytes-length (· this image palette))) palette-ref))] + [else (hash-set! (· this obj payload) 'ColorSpace "DeviceRGB")]) - #;(error 'stop-in-png:embed))) + -(define (finalize this) - ;; add the actual image data - (send (· this obj) end (· this imgData))) + ;; todo: transparency & alpha channel shit -#;(module+ test - (define data (file->bytes "test/assets/test.png")) - (define bm (make-object bitmap% (open-input-bytes data) 'png)) - bm) + ;; embed the actual image data + (send (· this obj) end (· this imgData)))) diff --git a/pitfall/pitfall/reference.rkt b/pitfall/pitfall/reference.rkt index 9860a38a..0f43161d 100644 --- a/pitfall/pitfall/reference.rkt +++ b/pitfall/pitfall/reference.rkt @@ -26,11 +26,11 @@ (when chunk (send this write chunk)) - (report* 'end! (· this id)) + #;(report* 'end! (· this id)) (define bstrs-to-write (let ([current-bstrs (reverse (· this byte-strings))]) - (if (and (or (compress-streams?) - (equal? (hash-ref (· this payload) 'Filter #f) "FlateDecode")) + (if (and (compress-streams?) + (not (hash-ref (· this payload) 'Filter #f)) (got-byte-strings? current-bstrs)) (let ([deflated-chunk (deflate (apply bytes-append current-bstrs))]) (hash-set! (· this payload) 'Filter "FlateDecode") @@ -53,7 +53,7 @@ (doc_write "\nendstream")) (doc_write "endobj")) - (report (· this id)) + #;(report (· this id)) (send this-doc _refEnd this)) diff --git a/pitfall/pitfall/test/test5.coffee b/pitfall/pitfall/test/test5.coffee index 0ab93a18..f01098e7 100644 --- a/pitfall/pitfall/test/test5.coffee +++ b/pitfall/pitfall/test/test5.coffee @@ -5,7 +5,7 @@ make = (doc) -> # Set the font, draw some text, and embed an image doc.font('Times-Italic') .fontSize(25) - .text('Some text with an embedded font!', 100, 100, lineBreak: no) + .text('Some fantastic text!', 100, 100, lineBreak: no) .image('death.png', 100, 160, width: 412) doc.end() @@ -14,7 +14,7 @@ doc = new PDFDocument({compress: no}) doc.pipe(fs.createWriteStream('test5.pdf')) make doc -#doc = new PDFDocument({compress: yes}) -#doc.pipe(fs.createWriteStream('test5c.pdf')) -#make doc +doc = new PDFDocument({compress: yes}) +doc.pipe(fs.createWriteStream('test5c.pdf')) +make doc diff --git a/pitfall/pitfall/test/test5.pdf b/pitfall/pitfall/test/test5.pdf index 042173e52c4a13ee381862674fd0870ac21426da..6e8d1bd07eb49f7d967e035943128c52807a9b49 100644 GIT binary patch delta 114 zcmca3ctmi+ZdN99gUNeY6BW$N%nZ#^%}vY<&5ePirJ1pj0gyJeQ!p}6uraro9LCnq zW@u?*Vq~!SH5((Nn1Q8&0SGAMDR6-q28I@929udM{Fs1L5Qi0$nZ@Kj4p&YyGcHwC ISARDy0ERgly8r+H delta 138 zcmX>ict>!`2qb|bW@c$-sgv#4`q>PCGA5RrFS9W+iWwU#7=VC6o&pz`VPI%sVKVs@yB`yf Zvf{8}vNW1p#^K6oX~3nb>gw;t1psghAnE`B diff --git a/pitfall/pitfall/test/test5.rkt b/pitfall/pitfall/test/test5.rkt index 0e754ff3..ac7e3ed3 100644 --- a/pitfall/pitfall/test/test5.rkt +++ b/pitfall/pitfall/test/test5.rkt @@ -4,13 +4,11 @@ (send* doc [font "Times-Italic"] [fontSize 25] - [text "Some text with an embedded font!" 100 100 (hash 'lineBreak #f)] - [image "death.png" 100 160 (hash 'width 412)] - #;[image "assets/test.jpeg" 190 400 (hash 'height 300)])) + [text "Some fantastic text!" 100 100 (hash 'lineBreak #f)] + [image "death.png" 100 160 (hash 'width 412)])) (define-runtime-path this "test5rkt.pdf") -(make-doc this #f proc #:test #f) - -#;(define-runtime-path that "test5crkt.pdf") -#;(make-doc that #t proc) +(make-doc this #f proc #:test #t) +(define-runtime-path that "test5crkt.pdf") +(make-doc that #t proc) diff --git a/pitfall/pitfall/test/test5c.pdf b/pitfall/pitfall/test/test5c.pdf index b332743f291bf4220adac4623dfe706731732b94..a947322fb815e554ce0eac8c7eb681f2f06374ac 100644 GIT binary patch delta 171 zcmV;c0960F5w8)j@C61jIW#mfFq8HL?*cM7k**e#{RRzxjj;*>K@3Fue8v2LUXtBS zTsW=}6|@t!6D#$)L;U~7tlG)I%rFc*0f$x+=2dZ9=tc6kZQE}Y2s6y{x@tjNXgDZ( zo9Qx^WV79^D4Uts!?TH}@M=jpvP@9M=o`-}6ZndDqJ5)tK(pu<+e?QO|DcDXVE2s6 Z8%ty&LJDQGlm{IF2{bhdB_%~qMhYOfO#}b{ delta 181 zcmV;m080O_5xNnu@C61iH8VLdF_ZQM?*cP9k**e#{RRzxj-~7J8BXdHeb^l~sg2(zaOUx=?pA zZfd66kas@&)3Zq=&m)`g5Pc-<;RRlA;_4Hakuxx1Ksb)u30WOk?p!Z)taL@0r1q`T jM0=j&Q>i-%D<4)w-r|EN!n2159{~w9HVP#rMNdWw`P)ua diff --git a/pitfall/pitfall/test/test5crkt copy.pdf b/pitfall/pitfall/test/test5crkt copy.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1af043e4353a8a19c89d291d44164793412536a7 GIT binary patch literal 2223 zcmZve4^-3k7RM1CE1Bb=$c7I&giUssQ`5iDI;Nuqq!y%>3Y$=&wrL5rO-%x;9C#`! z6~^-qj7?>OZZM`GY*WDrj(KFjCT>g-P_Y6ke+q0M=mQ2EUQ%Foy5yWD{U-O`-|znJ z?{niV4h&wySjz&uXUGHKf^-nwkpTGn1CSVlm572sF_6IlAOWgDF&t!B?-I2-9Awe~ zh>sAs7{&ysR*&jn9RYHI_3Hsxr=*407WG3AgnHh zTDh9^KO09Z$Fw*|O;SRQ6DKfOt_8SDyd&O69|k%ndH$h`#SP`5*INSZG_96q*AF&7MmKIdwuMOwI^H8 zuD)5RZNBW_>-No1)J`wu@1E}+X}@RZd29H3@in)?O$a|@NI$H|qIv1ZkJAEtX>~`U z#!kp9W;U#ueq`)O_IS~Gg?zHcZa@BZ-*<=Oj;7r?z{rb}T27j#k5!D__`aiFYDsqO z_4Upz$vhJR{63t@?!<>B{oELO!nT#iUv^AvzM{Q$>Vf0BKb&ctu!Xtl3kD{WcFLPR zuVq!n=y*p9UU>To}pJHh~2()*Af+Ypw`q| z9qQn;Q06E;R-6%eeM1}CGumC(eQ=ex59@8-P1Q^ek@9U;Wnq4=z6Ypj%q=frH(IcZ zrz&^0+cqXkn;MS9-*qHoZ4j&AgnHosD`o;Le+S@sn;r$l9 zhxXp;He`EfeQn|Xu7OhH6KvXip`ajT-E{ZZXjpdYXp4IDr+QU~xoOXz6H^;J7A^jU z!pz(bo&yfY1~}war+CD$?sZ54<7gtLfN_vLD+o{>K|UveHv2z}WSyLk8o+2eDflv& zpdXJZo9B|4V#rglQ9kA=IfpEeBBqc5Qp~fm1qvo3Sk+)A==+x{#!y8BOn}i430@GA z!Uh6_tc>E%T?KPjq0A;CHOl#jbM#{{xd8M}EPV<#!>90nzXj|A76(qI)J^T(o^1|` z;g&nQo_DEqdFx4)U}~oR%$=VvU!yPfUto+}+Ou_6bYIZO1#LUJjK;b)T9ptG9nbYi z&a*H-R-QAL`Q)9rReLVutKkG8?`){JzR$gBmCJZ$(if?P_`B!&UTqf{m(7@rg>hq9 zjqW1lYy9U|h-Who0SR9ML3M#-8vuO**D-j0bUK4Pt;e=ENw!mOkp0I;ER>3bQc@w) zr%Y6k-QzFm>8emkp3f0qj>FakfTV~P#@7f5xdu@HP>_ynH;7IRlHEiXpu>@Q!KPzS zt%z(avmJ?YVzUlRn;2A4;PVAG`?f$%#zECK22}mc4@!h6aWI-HQVCgE0yL4T&-p-B zZ#ISo3|0YjkUEJ_1RG YsX1F~SdJ0aEHHR%4&dz_6dnxxA0G~8_y7O^ literal 0 HcmV?d00001 diff --git a/pitfall/pitfall/test/test5crkt.pdf b/pitfall/pitfall/test/test5crkt.pdf index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1af043e4353a8a19c89d291d44164793412536a7 100644 GIT binary patch literal 2223 zcmZve4^-3k7RM1CE1Bb=$c7I&giUssQ`5iDI;Nuqq!y%>3Y$=&wrL5rO-%x;9C#`! z6~^-qj7?>OZZM`GY*WDrj(KFjCT>g-P_Y6ke+q0M=mQ2EUQ%Foy5yWD{U-O`-|znJ z?{niV4h&wySjz&uXUGHKf^-nwkpTGn1CSVlm572sF_6IlAOWgDF&t!B?-I2-9Awe~ zh>sAs7{&ysR*&jn9RYHI_3Hsxr=*407WG3AgnHh zTDh9^KO09Z$Fw*|O;SRQ6DKfOt_8SDyd&O69|k%ndH$h`#SP`5*INSZG_96q*AF&7MmKIdwuMOwI^H8 zuD)5RZNBW_>-No1)J`wu@1E}+X}@RZd29H3@in)?O$a|@NI$H|qIv1ZkJAEtX>~`U z#!kp9W;U#ueq`)O_IS~Gg?zHcZa@BZ-*<=Oj;7r?z{rb}T27j#k5!D__`aiFYDsqO z_4Upz$vhJR{63t@?!<>B{oELO!nT#iUv^AvzM{Q$>Vf0BKb&ctu!Xtl3kD{WcFLPR zuVq!n=y*p9UU>To}pJHh~2()*Af+Ypw`q| z9qQn;Q06E;R-6%eeM1}CGumC(eQ=ex59@8-P1Q^ek@9U;Wnq4=z6Ypj%q=frH(IcZ zrz&^0+cqXkn;MS9-*qHoZ4j&AgnHosD`o;Le+S@sn;r$l9 zhxXp;He`EfeQn|Xu7OhH6KvXip`ajT-E{ZZXjpdYXp4IDr+QU~xoOXz6H^;J7A^jU z!pz(bo&yfY1~}war+CD$?sZ54<7gtLfN_vLD+o{>K|UveHv2z}WSyLk8o+2eDflv& zpdXJZo9B|4V#rglQ9kA=IfpEeBBqc5Qp~fm1qvo3Sk+)A==+x{#!y8BOn}i430@GA z!Uh6_tc>E%T?KPjq0A;CHOl#jbM#{{xd8M}EPV<#!>90nzXj|A76(qI)J^T(o^1|` z;g&nQo_DEqdFx4)U}~oR%$=VvU!yPfUto+}+Ou_6bYIZO1#LUJjK;b)T9ptG9nbYi z&a*H-R-QAL`Q)9rReLVutKkG8?`){JzR$gBmCJZ$(if?P_`B!&UTqf{m(7@rg>hq9 zjqW1lYy9U|h-Who0SR9ML3M#-8vuO**D-j0bUK4Pt;e=ENw!mOkp0I;ER>3bQc@w) zr%Y6k-QzFm>8emkp3f0qj>FakfTV~P#@7f5xdu@HP>_ynH;7IRlHEiXpu>@Q!KPzS zt%z(avmJ?YVzUlRn;2A4;PVAG`?f$%#zECK22}mc4@!h6aWI-HQVCgE0yL4T&-p-B zZ#ISo3|0YjkUEJ_1RG YsX1F~SdJ0aEHHR%4&dz_6dnxxA0G~8_y7O^ literal 0 HcmV?d00001 diff --git a/pitfall/pitfall/test/test5rkt copy.pdf b/pitfall/pitfall/test/test5rkt copy.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ca62972b87b8d48aebbbc6fc52c27ff43a216440 GIT binary patch literal 2244 zcmZuz4OA568YVS@sR$zrQw|>`9r7wOvp=)D#>H5EWLc1PS)s%Y*WKB5aCc{&8EEBb zDk>;>{DC<6a|5~%DX4Y5fhk5Ey+}z#ihza)s6Y!8D0+pIL*FdB%E4yOneW?Y=KX)a z=bd0xRLnv=REPwR!wZoD4A867k+3iXRWYQ2(sMKea4~{L(q@`rfzY|juvl3jzz|eU zajc4DB58|_wvtv3NRjYxgtQuXC6YYuQq(lHhm@Ik71KUt0;brwWAGdSs8`c{;0X5b&zG}eARK0hHR_8-UgzTFUH?|j|4Kmy?fQ(>|eLxhgNAE2TR8GRo%bzO~-kSBWqSK z5nNc2cQh9HeQ2YolO0m`NmpX&E9UKg-aDh|tmVhUH@z4A;p-(%m|mFkRR4p_wYtVn z&x=k7s_?k<(j$Z3mEz5<>nsLwMY8ep!}W7=ld|z^^_E#)vCUY;DbsYRXHQG5dI`{_ z*6xwjXSY$w+0wgLsp3^iFJs^7rwZFzhPUcM?pfmn?<~e!3ju}PscD(pu$s5Z5 z{xea(JZ_rljl;I+xpkGBdP}08zr3s3_c_0u>rLfH0v3NaL-_LyQ+rX`EoRQ3N7!7h z>64?HcRyagR=u2Dzb`WB&Y2-?msjqE1$DKwFjTta6ORiy_|w%V_R_R&CJYW$?p$#bgWDZj6bWUJhtS_sxX3W z=Sq5O;yrz)YrR$b%6BGTT--|c40qRcZ=W9=B794B**IRnZTQcw>e8ZKTMts(PL5KRD)w3UO;A)!tDPk>yf zlhbyPia~(D1wbMbXx+S|;yL6ovGF?GEa{>wz%!--2R!FFxdn}Z7My5|01%H*6+`Qj zNDic;kaz*AA?+MMog5{1t&y%(p`B!r_tI_A#eTR1o5W#!oETWhFVJzIw;=0~Co#Fk z1>yk5V3-n7s7xRR8ViC#FZrDjNevSV2_s=3!~%>U2%I1#A_A8P;ax@u1sGh#;gE(w zm_#ZO3Gi@$X@MpViPj*?pivP_ozoS5!y6GxobK?+iK5{vPnJyv1>C_!LPq8&+8PCC z0|<#)gv%rthQpupTJa_%eEtnMltNSrjZ&e32+XM=Lk~yFBj|Cd_{_U?%XKX2tO}~3 zEF`;7!RgGD9zmn6a0XFU6M!Se8ewHAH!x|8@bd{yn~5>SE16UrK47;EzEfCt*G;?~ zse^WC( yqod3u$3lsY!DvRV}c1~;^!#T5n%LoDq4vtQYLH+{4+(F*} literal 0 HcmV?d00001 diff --git a/pitfall/pitfall/test/test5rkt.pdf b/pitfall/pitfall/test/test5rkt.pdf index b3a70dc334ac3ec5f2d2560d96034519216177be..ca62972b87b8d48aebbbc6fc52c27ff43a216440 100644 GIT binary patch delta 1222 zcmcbrb3|}L0Hejkpm044F8%P#l#&buBLgmdpVYi`Ak)&+kW1e=KPSH^xF9h(RYBh+ zwJb9^HOSp*;vv0y3qjT3$owZR0%h{=^5Tmt*7;2GZS&AxJHg?!V#Nd2il_5GbsXXL z_po6QauR&AFr`>$H|NaLk3as%*nT@VSol`i>X6vj_}ciX7r*b*#%@0fA3>xF1CKLqc34X)2$Z6*$E*pXD#?UaqIU>d+#3$d08d;+)Q=q zs;)I2TyHE%PYXI4Dsw0bANVd~2~wTNPD%5aq8`BpIcsc#ZHn+ZdjsRq39hede>lyi21G) ztFMJ@y~eh;?x)u6rB{|^PC7b!?Yb@UI~JU%+PZQT*Cq2~1`qxCjxQ(=726sX6m}t` z{=)n!zCcTX$zExtGaci%1qNPkGk(VHBe`|LZBBOAU3cBGSGy`G?vd!qk?sGupaG9{Pit_g{pUYQ*fnp!#0Ir!6_53vt9+Urm09Nm|1tY_gqm*G^KA?vnH6Y}%( z-rv7_W651r!7oZ@RTvU-=KHiyS@6|tF`G!hB;!7ft5a|0KE7HcxHhc({r&qf@$x>0 zf-)Hz+eBBTDYP73@O-xq8y|BlXTZc|onaU4ujfDe{PgJ4RxMQxV{6MRY5yme)NJh8 zws_w2f@fU&>Q7Hyx60)7%c7ktw{~9t+`fDJ^ziHT@psz)K6sPD^ylF`t*b8|YWQ86 zT)6&qr*G7hrH^`w&P(z+?V91_3xiuHGiLeez@R#&a5Rn*S&sR`2J1Bm!rr1y)|>2+Wz|O7haN?CCqe4 zFYM!+9}g1uRegG*angH@Z}IQEeO;3-zkKlXvU20>W=HX7l_oF#eteWRJ7w*~%ZdLp zj5u~X-uQYxK4#w8zEE!mUw&wQSs|K{76)h#n8;mVlum!Gq<6onHiU=s;j>n7XTfp BE$09L delta 4722 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 Qib6#s0GynP+Dd@`1H5z>%