Adil Allawi 14 years ago
parent
commit
c93e7c9c87
  1. 6
      src/canvas.js
  2. 39
      src/core.js
  3. 84
      src/evaluator.js
  4. 74
      src/image.js
  5. 28
      src/util.js
  6. 47
      src/worker.js
  7. 1
      test/pdfs/aboutstacks.pdf.link
  8. 7
      test/test_manifest.json
  9. 2
      web/viewer.js

6
src/canvas.js

@ -1114,7 +1114,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.restore(); this.restore();
}, },
paintImageXObject: function canvasGraphicsPaintImageXObject(imgData) { paintImageXObject: function canvasGraphicsPaintImageXObject(objId) {
var imgData = this.objs.get(objId);
if (!imgData) {
error('Dependent image isn\'t ready yet');
}
this.save(); this.save();
var ctx = this.ctx; var ctx = this.ctx;
var w = imgData.width; var w = imgData.width;

39
src/core.js

@ -570,6 +570,10 @@ var PDFDoc = (function PDFDocClosure() {
var imageData = data[2]; var imageData = data[2];
loadJpegStream(id, imageData, this.objs); loadJpegStream(id, imageData, this.objs);
break; break;
case 'Image':
var imageData = data[2];
this.objs.resolve(id, imageData);
break;
case 'Font': case 'Font':
var name = data[2]; var name = data[2];
var file = data[3]; var file = data[3];
@ -614,6 +618,41 @@ var PDFDoc = (function PDFDocClosure() {
throw data.error; throw data.error;
}, this); }, this);
messageHandler.on('jpeg_decode', function(data, promise) {
var imageData = data[0];
var components = data[1];
if (components != 3 && components != 1)
error('Only 3 component or 1 component can be returned');
var img = new Image();
img.onload = (function jpegImageLoaderOnload() {
var width = img.width;
var height = img.height;
var size = width * height;
var rgbaLength = size * 4;
var buf = new Uint8Array(size * components);
var tmpCanvas = new ScratchCanvas(width, height);
var tmpCtx = tmpCanvas.getContext('2d');
tmpCtx.drawImage(img, 0, 0);
var data = tmpCtx.getImageData(0, 0, width, height).data;
if (components == 3) {
for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
buf[j] = data[i];
buf[j + 1] = data[i + 1];
buf[j + 2] = data[i + 2];
}
} else if (components == 1) {
for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
buf[j] = data[i];
}
}
promise.resolve({ data: buf, width: width, height: height});
}).bind(this);
var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
img.src = src;
});
setTimeout(function pdfDocFontReadySetTimeout() { setTimeout(function pdfDocFontReadySetTimeout() {
messageHandler.send('doc', this.data); messageHandler.send('doc', this.data);
this.workerReadyPromise.resolve(true); this.workerReadyPromise.resolve(true);

84
src/evaluator.js

@ -184,62 +184,52 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var w = dict.get('Width', 'W'); var w = dict.get('Width', 'W');
var h = dict.get('Height', 'H'); var h = dict.get('Height', 'H');
if (image instanceof JpegStream && image.isNative) { var imageMask = dict.get('ImageMask', 'IM') || false;
var objId = 'img_' + uniquePrefix + (++self.objIdCounter); if (imageMask) {
handler.send('obj', [objId, 'JpegStream', image.getIR()]); // This depends on a tmpCanvas beeing filled with the
// current fillStyle, such that processing the pixel
// Add the dependency on the image object. // data can't be done here. Instead of creating a
insertDependency([objId]); // complete PDFImage, only read the information needed
// for later.
// The normal fn.
fn = 'paintJpegXObject'; var width = dict.get('Width', 'W');
args = [objId, w, h]; var height = dict.get('Height', 'H');
var bitStrideLength = (width + 7) >> 3;
var imgArray = image.getBytes(bitStrideLength * height);
var decode = dict.get('Decode', 'D');
var inverseDecode = !!decode && decode[0] > 0;
fn = 'paintImageMaskXObject';
args = [imgArray, inverseDecode, width, height];
return; return;
} }
// Needs to be rendered ourself.
// Figure out if the image has an imageMask.
var imageMask = dict.get('ImageMask', 'IM') || false;
// If there is no imageMask, create the PDFImage and a lot // If there is no imageMask, create the PDFImage and a lot
// of image processing can be done here. // of image processing can be done here.
if (!imageMask) { var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
var imageObj = new PDFImage(xref, resources, image, inline); insertDependency([objId]);
args = [objId, w, h];
if (imageObj.imageMask) {
throw 'Can\'t handle this in the web worker :/';
}
var imgData = { var softMask = dict.get('SMask', 'IM') || false;
width: w, if (!softMask && image instanceof JpegStream && image.isNative) {
height: h, // These JPEGs don't need any more processing so we can just send it.
data: new Uint8Array(w * h * 4) fn = 'paintJpegXObject';
}; handler.send('obj', [objId, 'JpegStream', image.getIR()]);
var pixels = imgData.data;
imageObj.fillRgbaBuffer(pixels, imageObj.decode);
fn = 'paintImageXObject';
args = [imgData];
return; return;
} }
// This depends on a tmpCanvas beeing filled with the fn = 'paintImageXObject';
// current fillStyle, such that processing the pixel
// data can't be done here. Instead of creating a PDFImage.buildImage(function(imageObj) {
// complete PDFImage, only read the information needed var imgData = {
// for later. width: w,
fn = 'paintImageMaskXObject'; height: h,
data: new Uint8Array(w * h * 4)
var width = dict.get('Width', 'W'); };
var height = dict.get('Height', 'H'); var pixels = imgData.data;
var bitStrideLength = (width + 7) >> 3; imageObj.fillRgbaBuffer(pixels, imageObj.decode);
var imgArray = image.getBytes(bitStrideLength * height); handler.send('obj', [objId, 'Image', imgData]);
var decode = dict.get('Decode', 'D'); }, handler, xref, resources, image, inline);
var inverseDecode = !!decode && decode[0] > 0;
args = [imgArray, inverseDecode, width, height];
} }
uniquePrefix = uniquePrefix || ''; uniquePrefix = uniquePrefix || '';

74
src/image.js

@ -4,7 +4,27 @@
'use strict'; 'use strict';
var PDFImage = (function PDFImageClosure() { var PDFImage = (function PDFImageClosure() {
function PDFImage(xref, res, image, inline) { /**
* 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, promise) {
if (image instanceof JpegStream && image.isNative) {
// 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;
handler.send('jpeg_decode', [image.getIR(), numComps], function(message) {
var data = message.data;
var stream = new Stream(data, 0, data.length, image.dict);
promise.resolve(stream);
});
} else {
promise.resolve(image);
}
}
function PDFImage(xref, res, image, inline, smask) {
this.image = image; this.image = image;
if (image.getParams) { if (image.getParams) {
// JPX/JPEG2000 streams directly contain bits per component // JPX/JPEG2000 streams directly contain bits per component
@ -51,14 +71,37 @@ var PDFImage = (function PDFImageClosure() {
this.decode = dict.get('Decode', 'D'); this.decode = dict.get('Decode', 'D');
var mask = xref.fetchIfRef(dict.get('Mask')); var mask = xref.fetchIfRef(dict.get('Mask'));
var smask = xref.fetchIfRef(dict.get('SMask'));
if (mask) { if (mask) {
TODO('masked images'); TODO('masked images');
} else if (smask) { } else if (smask) {
this.smask = new PDFImage(xref, res, smask); this.smask = new PDFImage(xref, res, smask, false);
} }
} }
/**
* Handles processing of image data and calls the callback with an argument
* of a PDFImage when the image is ready to be used.
*/
PDFImage.buildImage = function buildImage(callback, handler, xref, res,
image, inline) {
var imageDataPromise = new Promise();
var smaskPromise = new Promise();
// The image data and smask data may not be ready yet, wait till both are
// resolved.
Promise.all([imageDataPromise, smaskPromise]).then(function(results) {
var imageData = results[0], smaskData = results[1];
var image = new PDFImage(xref, res, imageData, inline, smaskData);
callback(image);
});
handleImageData(handler, xref, res, image, imageDataPromise);
var smask = xref.fetchIfRef(image.dict.get('SMask'));
if (smask)
handleImageData(handler, xref, res, smask, smaskPromise);
else
smaskPromise.resolve(null);
};
PDFImage.prototype = { PDFImage.prototype = {
getComponents: function getComponents(buffer, decodeMap) { getComponents: function getComponents(buffer, decodeMap) {
@ -130,18 +173,6 @@ var PDFImage = (function PDFImageClosure() {
var buf = new Uint8Array(width * height); var buf = new Uint8Array(width * height);
if (smask) { if (smask) {
if (smask.image.src) {
// smask is a DOM image
var tempCanvas = new ScratchCanvas(width, height);
var tempCtx = tempCanvas.getContext('2d');
var domImage = smask.image;
tempCtx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
0, 0, width, height);
var data = tempCtx.getImageData(0, 0, width, height).data;
for (var i = 0, j = 0, ii = width * height; i < ii; ++i, j += 4)
buf[i] = data[j]; // getting first component value
return buf;
}
var sw = smask.width; var sw = smask.width;
var sh = smask.height; var sh = smask.height;
if (sw != this.width || sh != this.height) if (sw != this.width || sh != this.height)
@ -159,8 +190,7 @@ var PDFImage = (function PDFImageClosure() {
applyStencilMask: function applyStencilMask(buffer, inverseDecode) { applyStencilMask: function applyStencilMask(buffer, inverseDecode) {
var width = this.width, height = this.height; var width = this.width, height = this.height;
var bitStrideLength = (width + 7) >> 3; var bitStrideLength = (width + 7) >> 3;
this.image.reset(); var imgArray = this.getImageBytes(bitStrideLength * height);
var imgArray = this.image.getBytes(bitStrideLength * height);
var imgArrayPos = 0; var imgArrayPos = 0;
var i, j, mask, buf; var i, j, mask, buf;
// removing making non-masked pixels transparent // removing making non-masked pixels transparent
@ -188,8 +218,7 @@ var PDFImage = (function PDFImageClosure() {
// rows start at byte boundary; // rows start at byte boundary;
var rowBytes = (width * numComps * bpc + 7) >> 3; var rowBytes = (width * numComps * bpc + 7) >> 3;
this.image.reset(); var imgArray = this.getImageBytes(height * rowBytes);
var imgArray = this.image.getBytes(height * rowBytes);
var comps = this.colorSpace.getRgbBuffer( var comps = this.colorSpace.getRgbBuffer(
this.getComponents(imgArray, decodeMap), bpc); this.getComponents(imgArray, decodeMap), bpc);
@ -216,14 +245,17 @@ var PDFImage = (function PDFImageClosure() {
// rows start at byte boundary; // rows start at byte boundary;
var rowBytes = (width * numComps * bpc + 7) >> 3; var rowBytes = (width * numComps * bpc + 7) >> 3;
this.image.reset(); var imgArray = this.getImageBytes(height * rowBytes);
var imgArray = this.image.getBytes(height * rowBytes);
var comps = this.getComponents(imgArray); var comps = this.getComponents(imgArray);
var length = width * height; var length = width * height;
for (var i = 0; i < length; ++i) for (var i = 0; i < length; ++i)
buffer[i] = comps[i]; buffer[i] = comps[i];
},
getImageBytes: function getImageBytes(length) {
this.image.reset();
return this.image.getBytes(length);
} }
}; };
return PDFImage; return PDFImage;

28
src/util.js

@ -217,7 +217,33 @@ var Promise = (function PromiseClosure() {
} }
this.callbacks = []; this.callbacks = [];
}; };
/**
* Builds a promise that is resolved when all the passed in promises are
* resolved.
* @param {Promise[]} promises Array of promises to wait for.
* @return {Promise} New dependant promise.
*/
Promise.all = function(promises) {
var deferred = new Promise();
var unresolved = promises.length;
var results = [];
if (unresolved === 0) {
deferred.resolve(results);
return deferred;
}
for (var i = 0; i < unresolved; ++i) {
var promise = promises[i];
promise.then((function(i) {
return function(value) {
results[i] = value;
unresolved--;
if (unresolved === 0)
deferred.resolve(results);
};
})(i));
}
return deferred;
};
Promise.prototype = { Promise.prototype = {
hasData: false, hasData: false,

47
src/worker.js

@ -6,6 +6,8 @@
function MessageHandler(name, comObj) { function MessageHandler(name, comObj) {
this.name = name; this.name = name;
this.comObj = comObj; this.comObj = comObj;
this.callbackIndex = 1;
var callbacks = this.callbacks = {};
var ah = this.actionHandler = {}; var ah = this.actionHandler = {};
ah['console_log'] = [function ahConsoleLog(data) { ah['console_log'] = [function ahConsoleLog(data) {
@ -14,11 +16,33 @@ function MessageHandler(name, comObj) {
ah['console_error'] = [function ahConsoleError(data) { ah['console_error'] = [function ahConsoleError(data) {
console.error.apply(console, data); console.error.apply(console, data);
}]; }];
comObj.onmessage = function messageHandlerComObjOnMessage(event) { comObj.onmessage = function messageHandlerComObjOnMessage(event) {
var data = event.data; var data = event.data;
if (data.action in ah) { if (data.isReply) {
var callbackId = data.callbackId;
if (data.callbackId in callbacks) {
var callback = callbacks[callbackId];
delete callbacks[callbackId];
callback(data.data);
} else {
throw 'Cannot resolve callback ' + callbackId;
}
} else if (data.action in ah) {
var action = ah[data.action]; var action = ah[data.action];
action[0].call(action[1], data.data); if (data.callbackId) {
var promise = new Promise();
promise.then(function(resolvedData) {
comObj.postMessage({
isReply: true,
callbackId: data.callbackId,
data: resolvedData
});
});
action[0].call(action[1], data.data, promise);
} else {
action[0].call(action[1], data.data);
}
} else { } else {
throw 'Unkown action from worker: ' + data.action; throw 'Unkown action from worker: ' + data.action;
} }
@ -33,12 +57,23 @@ MessageHandler.prototype = {
} }
ah[actionName] = [handler, scope]; ah[actionName] = [handler, scope];
}, },
/**
send: function messageHandlerSend(actionName, data) { * Sends a message to the comObj to invoke the action with the supplied data.
this.comObj.postMessage({ * @param {String} actionName Action to call.
* @param {JSON} data JSON data to send.
* @param {function} [callback] Optional callback that will handle a reply.
*/
send: function messageHandlerSend(actionName, data, callback) {
var message = {
action: actionName, action: actionName,
data: data data: data
}); };
if (callback) {
var callbackId = this.callbackIndex++;
this.callbacks[callbackId] = callback;
message.callbackId = callbackId;
}
this.comObj.postMessage(message);
} }
}; };

1
test/pdfs/aboutstacks.pdf.link

@ -0,0 +1 @@
http://greenhousechallenge.org/media/item/313/38/About-Stacks.pdf

7
test/test_manifest.json

@ -354,5 +354,12 @@
"md5": "d582cc0f2592ae82936589ced2a47e55", "md5": "d582cc0f2592ae82936589ced2a47e55",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
},
{ "id": "aboutstacks",
"file": "pdfs/aboutstacks.pdf",
"md5": "6e7c8416a293ba2d83bc8dd20c6ccf51",
"rounds": 1,
"link": true,
"type": "eq"
} }
] ]

2
web/viewer.js

@ -537,7 +537,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
]; ];
if (scale && scale !== PDFView.currentScale) if (scale && scale !== PDFView.currentScale)
PDFView.setScale(scale, true); PDFView.parseScale(scale, true);
setTimeout(function pageViewScrollIntoViewRelayout() { setTimeout(function pageViewScrollIntoViewRelayout() {
// letting page to re-layout before scrolling // letting page to re-layout before scrolling

Loading…
Cancel
Save