You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
643 lines
21 KiB
643 lines
21 KiB
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ |
|
/* Copyright 2012 Mozilla Foundation |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
/* globals ColorSpace, DecodeStream, error, info, isArray, ImageKind, isStream, |
|
JpegStream, JpxImage, Name, Promise, Stream, warn */ |
|
|
|
'use strict'; |
|
|
|
var PDFImage = (function PDFImageClosure() { |
|
/** |
|
* Decode the image in the main thread if it supported. Resovles the promise |
|
* when the image data is ready. |
|
*/ |
|
function handleImageData(handler, xref, res, image) { |
|
if (image instanceof JpegStream && image.isNativelyDecodable(xref, res)) { |
|
// For natively supported jpegs send them to the main thread for decoding. |
|
var dict = image.dict; |
|
var colorSpace = dict.get('ColorSpace', 'CS'); |
|
colorSpace = ColorSpace.parse(colorSpace, xref, res); |
|
var numComps = colorSpace.numComps; |
|
var resolvePromise; |
|
handler.send('JpegDecode', [image.getIR(), numComps], function(message) { |
|
var data = message.data; |
|
var stream = new Stream(data, 0, data.length, image.dict); |
|
resolvePromise(stream); |
|
}); |
|
return new Promise(function (resolve) { |
|
resolvePromise = resolve; |
|
}); |
|
} else { |
|
return Promise.resolve(image); |
|
} |
|
} |
|
|
|
/** |
|
* Decode and clamp a value. The formula is different from the spec because we |
|
* don't decode to float range [0,1], we decode it in the [0,max] range. |
|
*/ |
|
function decodeAndClamp(value, addend, coefficient, max) { |
|
value = addend + value * coefficient; |
|
// Clamp the value to the range |
|
return (value < 0 ? 0 : (value > max ? max : value)); |
|
} |
|
|
|
function PDFImage(xref, res, image, inline, smask, mask, isMask) { |
|
this.image = image; |
|
var dict = image.dict; |
|
if (dict.has('Filter')) { |
|
var filter = dict.get('Filter').name; |
|
if (filter === 'JPXDecode') { |
|
var jpxImage = new JpxImage(); |
|
jpxImage.parseImageProperties(image.stream); |
|
image.stream.reset(); |
|
image.bitsPerComponent = jpxImage.bitsPerComponent; |
|
image.numComps = jpxImage.componentsCount; |
|
} else if (filter === 'JBIG2Decode') { |
|
image.bitsPerComponent = 1; |
|
image.numComps = 1; |
|
} |
|
} |
|
// TODO cache rendered images? |
|
|
|
this.width = dict.get('Width', 'W'); |
|
this.height = dict.get('Height', 'H'); |
|
|
|
if (this.width < 1 || this.height < 1) { |
|
error('Invalid image width: ' + this.width + ' or height: ' + |
|
this.height); |
|
} |
|
|
|
this.interpolate = dict.get('Interpolate', 'I') || false; |
|
this.imageMask = dict.get('ImageMask', 'IM') || false; |
|
this.matte = dict.get('Matte') || false; |
|
|
|
var bitsPerComponent = image.bitsPerComponent; |
|
if (!bitsPerComponent) { |
|
bitsPerComponent = dict.get('BitsPerComponent', 'BPC'); |
|
if (!bitsPerComponent) { |
|
if (this.imageMask) { |
|
bitsPerComponent = 1; |
|
} else { |
|
error('Bits per component missing in image: ' + this.imageMask); |
|
} |
|
} |
|
} |
|
this.bpc = bitsPerComponent; |
|
|
|
if (!this.imageMask) { |
|
var colorSpace = dict.get('ColorSpace', 'CS'); |
|
if (!colorSpace) { |
|
info('JPX images (which do not require color spaces)'); |
|
switch (image.numComps) { |
|
case 1: |
|
colorSpace = Name.get('DeviceGray'); |
|
break; |
|
case 3: |
|
colorSpace = Name.get('DeviceRGB'); |
|
break; |
|
case 4: |
|
colorSpace = Name.get('DeviceCMYK'); |
|
break; |
|
default: |
|
error('JPX images with ' + this.numComps + |
|
' color components not supported.'); |
|
} |
|
} |
|
this.colorSpace = ColorSpace.parse(colorSpace, xref, res); |
|
this.numComps = this.colorSpace.numComps; |
|
} |
|
|
|
this.decode = dict.get('Decode', 'D'); |
|
this.needsDecode = false; |
|
if (this.decode && |
|
((this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode)) || |
|
(isMask && !ColorSpace.isDefaultDecode(this.decode, 1)))) { |
|
this.needsDecode = true; |
|
// Do some preprocessing to avoid more math. |
|
var max = (1 << bitsPerComponent) - 1; |
|
this.decodeCoefficients = []; |
|
this.decodeAddends = []; |
|
for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) { |
|
var dmin = this.decode[i]; |
|
var dmax = this.decode[i + 1]; |
|
this.decodeCoefficients[j] = dmax - dmin; |
|
this.decodeAddends[j] = max * dmin; |
|
} |
|
} |
|
|
|
if (smask) { |
|
this.smask = new PDFImage(xref, res, smask, false); |
|
} else if (mask) { |
|
if (isStream(mask)) { |
|
this.mask = new PDFImage(xref, res, mask, false, null, null, true); |
|
} else { |
|
// Color key mask (just an array). |
|
this.mask = mask; |
|
} |
|
} |
|
} |
|
/** |
|
* Handles processing of image data and returns the Promise that is resolved |
|
* with a PDFImage when the image is ready to be used. |
|
*/ |
|
PDFImage.buildImage = function PDFImage_buildImage(handler, xref, |
|
res, image, inline) { |
|
var imagePromise = handleImageData(handler, xref, res, image); |
|
var smaskPromise; |
|
var maskPromise; |
|
|
|
var smask = image.dict.get('SMask'); |
|
var mask = image.dict.get('Mask'); |
|
|
|
if (smask) { |
|
smaskPromise = handleImageData(handler, xref, res, smask); |
|
maskPromise = Promise.resolve(null); |
|
} else { |
|
smaskPromise = Promise.resolve(null); |
|
if (mask) { |
|
if (isStream(mask)) { |
|
maskPromise = handleImageData(handler, xref, res, mask); |
|
} else if (isArray(mask)) { |
|
maskPromise = Promise.resolve(mask); |
|
} else { |
|
warn('Unsupported mask format.'); |
|
maskPromise = Promise.resolve(null); |
|
} |
|
} else { |
|
maskPromise = Promise.resolve(null); |
|
} |
|
} |
|
return Promise.all([imagePromise, smaskPromise, maskPromise]).then( |
|
function(results) { |
|
var imageData = results[0]; |
|
var smaskData = results[1]; |
|
var maskData = results[2]; |
|
return new PDFImage(xref, res, imageData, inline, smaskData, maskData); |
|
}); |
|
}; |
|
|
|
/** |
|
* Resize an image using the nearest neighbor algorithm. Currently only |
|
* supports one and three component images. |
|
* @param {TypedArray} pixels The original image with one component. |
|
* @param {Number} bpc Number of bits per component. |
|
* @param {Number} components Number of color components, 1 or 3 is supported. |
|
* @param {Number} w1 Original width. |
|
* @param {Number} h1 Original height. |
|
* @param {Number} w2 New width. |
|
* @param {Number} h2 New height. |
|
* @param {TypedArray} dest (Optional) The destination buffer. |
|
* @param {Number} alpha01 (Optional) Size reserved for the alpha channel. |
|
* @return {TypedArray} Resized image data. |
|
*/ |
|
PDFImage.resize = function PDFImage_resize(pixels, bpc, components, |
|
w1, h1, w2, h2, dest, alpha01) { |
|
|
|
if (components !== 1 && components !== 3) { |
|
error('Unsupported component count for resizing.'); |
|
} |
|
|
|
var length = w2 * h2 * components; |
|
var temp = dest ? dest : (bpc <= 8 ? new Uint8Array(length) : |
|
(bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length))); |
|
var xRatio = w1 / w2; |
|
var yRatio = h1 / h2; |
|
var i, j, py, newIndex = 0, oldIndex; |
|
var xScaled = new Uint16Array(w2); |
|
var w1Scanline = w1 * components; |
|
if (alpha01 !== 1) { |
|
alpha01 = 0; |
|
} |
|
|
|
for (j = 0; j < w2; j++) { |
|
xScaled[j] = Math.floor(j * xRatio) * components; |
|
} |
|
|
|
if (components === 1) { |
|
for (i = 0; i < h2; i++) { |
|
py = Math.floor(i * yRatio) * w1Scanline; |
|
for (j = 0; j < w2; j++) { |
|
oldIndex = py + xScaled[j]; |
|
temp[newIndex++] = pixels[oldIndex]; |
|
} |
|
} |
|
} else if (components === 3) { |
|
for (i = 0; i < h2; i++) { |
|
py = Math.floor(i * yRatio) * w1Scanline; |
|
for (j = 0; j < w2; j++) { |
|
oldIndex = py + xScaled[j]; |
|
temp[newIndex++] = pixels[oldIndex++]; |
|
temp[newIndex++] = pixels[oldIndex++]; |
|
temp[newIndex++] = pixels[oldIndex++]; |
|
newIndex += alpha01; |
|
} |
|
} |
|
} |
|
return temp; |
|
}; |
|
|
|
PDFImage.createMask = |
|
function PDFImage_createMask(imgArray, width, height, canTransfer, |
|
inverseDecode) { |
|
// If imgArray came from a DecodeStream, we're safe to transfer it. |
|
// Otherwise, copy it. |
|
var actualLength = imgArray.byteLength; |
|
var data; |
|
if (canTransfer) { |
|
data = imgArray; |
|
} else { |
|
data = new Uint8Array(actualLength); |
|
data.set(imgArray); |
|
} |
|
// Invert if necessary. It's safe to modify the array -- whether it's the |
|
// original or a copy, we're about to transfer it anyway, so nothing else |
|
// in this thread can be relying on its contents. |
|
if (inverseDecode) { |
|
for (var i = 0; i < actualLength; i++) { |
|
data[i] = ~data[i]; |
|
} |
|
} |
|
|
|
return {data: data, width: width, height: height}; |
|
}; |
|
|
|
PDFImage.prototype = { |
|
get drawWidth() { |
|
return Math.max(this.width, |
|
this.smask && this.smask.width || 0, |
|
this.mask && this.mask.width || 0); |
|
}, |
|
|
|
get drawHeight() { |
|
return Math.max(this.height, |
|
this.smask && this.smask.height || 0, |
|
this.mask && this.mask.height || 0); |
|
}, |
|
|
|
decodeBuffer: function PDFImage_decodeBuffer(buffer) { |
|
var bpc = this.bpc; |
|
var numComps = this.numComps; |
|
|
|
var decodeAddends = this.decodeAddends; |
|
var decodeCoefficients = this.decodeCoefficients; |
|
var max = (1 << bpc) - 1; |
|
var i, ii; |
|
|
|
if (bpc === 1) { |
|
// If the buffer needed decode that means it just needs to be inverted. |
|
for (i = 0, ii = buffer.length; i < ii; i++) { |
|
buffer[i] = +!(buffer[i]); |
|
} |
|
return; |
|
} |
|
var index = 0; |
|
for (i = 0, ii = this.width * this.height; i < ii; i++) { |
|
for (var j = 0; j < numComps; j++) { |
|
buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], |
|
decodeCoefficients[j], max); |
|
index++; |
|
} |
|
} |
|
}, |
|
|
|
getComponents: function PDFImage_getComponents(buffer) { |
|
var bpc = this.bpc; |
|
|
|
// This image doesn't require any extra work. |
|
if (bpc === 8) { |
|
return buffer; |
|
} |
|
|
|
var width = this.width; |
|
var height = this.height; |
|
var numComps = this.numComps; |
|
|
|
var length = width * height * numComps; |
|
var bufferPos = 0; |
|
var output = (bpc <= 8 ? new Uint8Array(length) : |
|
(bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length))); |
|
var rowComps = width * numComps; |
|
|
|
var max = (1 << bpc) - 1; |
|
var i = 0, ii, buf; |
|
|
|
if (bpc === 1) { |
|
// Optimization for reading 1 bpc images. |
|
var mask, loop1End, loop2End; |
|
for (var j = 0; j < height; j++) { |
|
loop1End = i + (rowComps & ~7); |
|
loop2End = i + rowComps; |
|
|
|
// unroll loop for all full bytes |
|
while (i < loop1End) { |
|
buf = buffer[bufferPos++]; |
|
output[i] = (buf >> 7) & 1; |
|
output[i + 1] = (buf >> 6) & 1; |
|
output[i + 2] = (buf >> 5) & 1; |
|
output[i + 3] = (buf >> 4) & 1; |
|
output[i + 4] = (buf >> 3) & 1; |
|
output[i + 5] = (buf >> 2) & 1; |
|
output[i + 6] = (buf >> 1) & 1; |
|
output[i + 7] = buf & 1; |
|
i += 8; |
|
} |
|
|
|
// handle remaing bits |
|
if (i < loop2End) { |
|
buf = buffer[bufferPos++]; |
|
mask = 128; |
|
while (i < loop2End) { |
|
output[i++] = +!!(buf & mask); |
|
mask >>= 1; |
|
} |
|
} |
|
} |
|
} else { |
|
// The general case that handles all other bpc values. |
|
var bits = 0; |
|
buf = 0; |
|
for (i = 0, ii = length; i < ii; ++i) { |
|
if (i % rowComps === 0) { |
|
buf = 0; |
|
bits = 0; |
|
} |
|
|
|
while (bits < bpc) { |
|
buf = (buf << 8) | buffer[bufferPos++]; |
|
bits += 8; |
|
} |
|
|
|
var remainingBits = bits - bpc; |
|
var value = buf >> remainingBits; |
|
output[i] = (value < 0 ? 0 : (value > max ? max : value)); |
|
buf = buf & ((1 << remainingBits) - 1); |
|
bits = remainingBits; |
|
} |
|
} |
|
return output; |
|
}, |
|
|
|
fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height, |
|
actualHeight, image) { |
|
var smask = this.smask; |
|
var mask = this.mask; |
|
var alphaBuf, sw, sh, i, ii, j; |
|
|
|
if (smask) { |
|
sw = smask.width; |
|
sh = smask.height; |
|
alphaBuf = new Uint8Array(sw * sh); |
|
smask.fillGrayBuffer(alphaBuf); |
|
if (sw != width || sh != height) { |
|
alphaBuf = PDFImage.resize(alphaBuf, smask.bpc, 1, sw, sh, width, |
|
height); |
|
} |
|
} else if (mask) { |
|
if (mask instanceof PDFImage) { |
|
sw = mask.width; |
|
sh = mask.height; |
|
alphaBuf = new Uint8Array(sw * sh); |
|
mask.numComps = 1; |
|
mask.fillGrayBuffer(alphaBuf); |
|
|
|
// Need to invert values in rgbaBuf |
|
for (i = 0, ii = sw * sh; i < ii; ++i) { |
|
alphaBuf[i] = 255 - alphaBuf[i]; |
|
} |
|
|
|
if (sw != width || sh != height) { |
|
alphaBuf = PDFImage.resize(alphaBuf, mask.bpc, 1, sw, sh, width, |
|
height); |
|
} |
|
} else if (isArray(mask)) { |
|
// Color key mask: if any of the compontents are outside the range |
|
// then they should be painted. |
|
alphaBuf = new Uint8Array(width * height); |
|
var numComps = this.numComps; |
|
for (i = 0, ii = width * height; i < ii; ++i) { |
|
var opacity = 0; |
|
var imageOffset = i * numComps; |
|
for (j = 0; j < numComps; ++j) { |
|
var color = image[imageOffset + j]; |
|
var maskOffset = j * 2; |
|
if (color < mask[maskOffset] || color > mask[maskOffset + 1]) { |
|
opacity = 255; |
|
break; |
|
} |
|
} |
|
alphaBuf[i] = opacity; |
|
} |
|
} else { |
|
error('Unknown mask format.'); |
|
} |
|
} |
|
|
|
if (alphaBuf) { |
|
for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { |
|
rgbaBuf[j] = alphaBuf[i]; |
|
} |
|
} else { |
|
// No mask. |
|
for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) { |
|
rgbaBuf[j] = 255; |
|
} |
|
} |
|
}, |
|
|
|
undoPreblend: function PDFImage_undoPreblend(buffer, width, height) { |
|
var matte = this.smask && this.smask.matte; |
|
if (!matte) { |
|
return; |
|
} |
|
var matteRgb = this.colorSpace.getRgb(matte, 0); |
|
var matteR = matteRgb[0]; |
|
var matteG = matteRgb[1]; |
|
var matteB = matteRgb[2]; |
|
var length = width * height * 4; |
|
var r, g, b; |
|
for (var i = 0; i < length; i += 4) { |
|
var alpha = buffer[i + 3]; |
|
if (alpha === 0) { |
|
// according formula we have to get Infinity in all components |
|
// making it white (typical paper color) should be okay |
|
buffer[i] = 255; |
|
buffer[i + 1] = 255; |
|
buffer[i + 2] = 255; |
|
continue; |
|
} |
|
var k = 255 / alpha; |
|
r = (buffer[i] - matteR) * k + matteR; |
|
g = (buffer[i + 1] - matteG) * k + matteG; |
|
b = (buffer[i + 2] - matteB) * k + matteB; |
|
buffer[i] = r <= 0 ? 0 : r >= 255 ? 255 : r | 0; |
|
buffer[i + 1] = g <= 0 ? 0 : g >= 255 ? 255 : g | 0; |
|
buffer[i + 2] = b <= 0 ? 0 : b >= 255 ? 255 : b | 0; |
|
} |
|
}, |
|
|
|
createImageData: function PDFImage_createImageData(forceRGBA) { |
|
var drawWidth = this.drawWidth; |
|
var drawHeight = this.drawHeight; |
|
var imgData = { // other fields are filled in below |
|
width: drawWidth, |
|
height: drawHeight |
|
}; |
|
|
|
var numComps = this.numComps; |
|
var originalWidth = this.width; |
|
var originalHeight = this.height; |
|
var bpc = this.bpc; |
|
|
|
// Rows start at byte boundary. |
|
var rowBytes = (originalWidth * numComps * bpc + 7) >> 3; |
|
var imgArray; |
|
|
|
if (!forceRGBA) { |
|
// If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image |
|
// without any complications, we pass a same-sized copy to the main |
|
// thread rather than expanding by 32x to RGBA form. This saves *lots* |
|
// of memory for many scanned documents. It's also much faster. |
|
// |
|
// Similarly, if it is a 24-bit-per pixel RGB image without any |
|
// complications, we avoid expanding by 1.333x to RGBA form. |
|
var kind; |
|
if (this.colorSpace.name === 'DeviceGray' && bpc === 1) { |
|
kind = ImageKind.GRAYSCALE_1BPP; |
|
} else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8) { |
|
kind = ImageKind.RGB_24BPP; |
|
} |
|
if (kind && !this.smask && !this.mask && !this.needsDecode && |
|
drawWidth === originalWidth && drawHeight === originalHeight) { |
|
imgData.kind = kind; |
|
|
|
imgArray = this.getImageBytes(originalHeight * rowBytes); |
|
// If imgArray came from a DecodeStream, we're safe to transfer it |
|
// (and thus neuter it) because it will constitute the entire |
|
// DecodeStream's data. But if it came from a Stream, we need to |
|
// copy it because it'll only be a portion of the Stream's data, and |
|
// the rest will be read later on. |
|
if (this.image instanceof DecodeStream) { |
|
imgData.data = imgArray; |
|
} else { |
|
var newArray = new Uint8Array(imgArray.length); |
|
newArray.set(imgArray); |
|
imgData.data = newArray; |
|
} |
|
return imgData; |
|
} |
|
} |
|
if (this.image instanceof JpegStream) { |
|
imgData.kind = ImageKind.RGB_24BPP; |
|
imgData.data = this.getImageBytes(originalHeight * rowBytes, |
|
drawWidth, drawHeight); |
|
return imgData; |
|
} |
|
|
|
imgArray = this.getImageBytes(originalHeight * rowBytes); |
|
// imgArray can be incomplete (e.g. after CCITT fax encoding). |
|
var actualHeight = 0 | (imgArray.length / rowBytes * |
|
drawHeight / originalHeight); |
|
|
|
var comps = this.getComponents(imgArray); |
|
|
|
// If opacity data is present, use RGBA_32BPP form. Otherwise, use the |
|
// more compact RGB_24BPP form if allowable. |
|
var alpha01, maybeUndoPreblend; |
|
if (!forceRGBA && !this.smask && !this.mask) { |
|
imgData.kind = ImageKind.RGB_24BPP; |
|
imgData.data = new Uint8Array(drawWidth * drawHeight * 3); |
|
alpha01 = 0; |
|
maybeUndoPreblend = false; |
|
} else { |
|
imgData.kind = ImageKind.RGBA_32BPP; |
|
imgData.data = new Uint8Array(drawWidth * drawHeight * 4); |
|
alpha01 = 1; |
|
maybeUndoPreblend = true; |
|
|
|
// Color key masking (opacity) must be performed before decoding. |
|
this.fillOpacity(imgData.data, drawWidth, drawHeight, actualHeight, |
|
comps); |
|
} |
|
|
|
if (this.needsDecode) { |
|
this.decodeBuffer(comps); |
|
} |
|
this.colorSpace.fillRgb(imgData.data, originalWidth, originalHeight, |
|
drawWidth, drawHeight, actualHeight, bpc, comps, |
|
alpha01); |
|
if (maybeUndoPreblend) { |
|
this.undoPreblend(imgData.data, drawWidth, actualHeight); |
|
} |
|
|
|
return imgData; |
|
}, |
|
|
|
fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) { |
|
var numComps = this.numComps; |
|
if (numComps != 1) { |
|
error('Reading gray scale from a color image: ' + numComps); |
|
} |
|
|
|
var width = this.width; |
|
var height = this.height; |
|
var bpc = this.bpc; |
|
|
|
// rows start at byte boundary |
|
var rowBytes = (width * numComps * bpc + 7) >> 3; |
|
var imgArray = this.getImageBytes(height * rowBytes); |
|
|
|
var comps = this.getComponents(imgArray); |
|
var i, length; |
|
|
|
if (bpc === 1) { |
|
// inline decoding (= inversion) for 1 bpc images |
|
length = width * height; |
|
if (this.needsDecode) { |
|
// invert and scale to {0, 255} |
|
for (i = 0; i < length; ++i) { |
|
buffer[i] = (comps[i] - 1) & 255; |
|
} |
|
} else { |
|
// scale to {0, 255} |
|
for (i = 0; i < length; ++i) { |
|
buffer[i] = (-comps[i]) & 255; |
|
} |
|
} |
|
return; |
|
} |
|
|
|
if (this.needsDecode) { |
|
this.decodeBuffer(comps); |
|
} |
|
length = width * height; |
|
// we aren't using a colorspace so we need to scale the value |
|
var scale = 255 / ((1 << bpc) - 1); |
|
for (i = 0; i < length; ++i) { |
|
buffer[i] = (scale * comps[i]) | 0; |
|
} |
|
}, |
|
|
|
getImageBytes: function PDFImage_getImageBytes(length, |
|
drawWidth, drawHeight) { |
|
this.image.reset(); |
|
this.image.drawWidth = drawWidth; |
|
this.image.drawHeight = drawHeight; |
|
return this.image.getBytes(length); |
|
} |
|
}; |
|
return PDFImage; |
|
})();
|
|
|