return constructor;
})();
+var JpegStreamIR = (function() {
+ function JpegStreamIR(objId, IR, objs) {
+ var src = 'data:image/jpeg;base64,' + window.btoa(IR);
+
+ // create DOM image
+ var img = new Image();
+ img.onload = (function() {
+ this.loaded = true;
+
+ objs.resolve(objId, this);
+
+ if (this.onLoad)
+ this.onLoad();
+ }).bind(this);
+ img.src = src;
+ this.domImage = img;
+ }
+
+ JpegStreamIR.prototype = {
+ getImage: function() {
+ return this.domImage;
+ }
+ }
+
+ return JpegStreamIR;
+})()
+
// A JpegStream can't be read directly. We use the platform to render
// the underlying JPEG data for us.
- var JpegStream = (function() {
- function isYcckImage(bytes) {
+ var JpegStream = (function jpegStream() {
+ function isAdobeImage(bytes) {
var maxBytesScanned = Math.max(bytes.length - 16, 1024);
- // Looking for APP14, 'Adobe' and transform = 2
+ // Looking for APP14, 'Adobe'
for (var i = 0; i < maxBytesScanned; ++i) {
if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE &&
bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E &&
// TODO: per poppler, some images may have "junk" before that
// need to be removed
this.dict = dict;
-
- if (isYcckImage(bytes))
- bytes = fixYcckImage(bytes);
- img.src = 'data:image/jpeg;base64,' + window.btoa(bytesToString(bytes));
- this.domImage = img;
+ if (isAdobeImage(bytes))
+ bytes = fixAdobeImage(bytes);
+
+ // create DOM image
+ var img = new Image();
+ img.onload = (function jpegStreamOnload() {
+ this.loaded = true;
+ if (this.onLoad)
+ this.onLoad();
+ }).bind(this);
+ this.src = bytesToString(bytes);
}
constructor.prototype = {
- getImage: function jpegStreamGetImage() {
- return this.domImage;
+ getIR: function() {
+ return this.src;
},
-
- getChar: function() {
+ getChar: function jpegStreamGetChar() {
error('internal error: getChar is not valid on JpegStream');
}
};
return constructor;
})();
- var DecryptStream = (function() {
-// Simple object to track the loading images
-// Initialy for every that is in loading call imageLoading()
-// and, when images onload is fired, call imageLoaded()
-// When all images are loaded, the onLoad event is fired.
-var ImagesLoader = (function imagesLoader() {
- function constructor() {
- this.loading = 0;
- }
-
- constructor.prototype = {
- imageLoading: function imagesLoaderImageLoading() {
- ++this.loading;
- },
-
- imageLoaded: function imagesLoaderImageLoaded() {
- if (--this.loading == 0 && this.onLoad) {
- this.onLoad();
- delete this.onLoad;
- }
- },
-
- bind: function imagesLoaderBind(jpegStream) {
- if (jpegStream.loaded || jpegStream.onLoad)
- return;
- this.imageLoading();
- jpegStream.onLoad = this.imageLoaded.bind(this);
- },
-
- notifyOnLoad: function imagesLoaderNotifyOnLoad(callback) {
- if (this.loading == 0)
- callback();
- this.onLoad = callback;
- }
- };
-
- return constructor;
-})();
-
+ var DecryptStream = (function decryptStream() {
function constructor(str, decrypt) {
this.str = str;
this.dict = str.dict;
}
return shadow(this, 'rotate', rotate);
},
- startRendering: function pageStartRendering(canvasCtx, continuation) {
+
- startRenderingFromIRQueue: function(gfx, IRQueue, fonts, continuation) {
++ startRenderingFromIRQueue: function startRenderingFromIRQueue(gfx, IRQueue, fonts, continuation) {
var self = this;
- var stats = self.stats;
- stats.compile = stats.fonts = stats.render = 0;
+ this.IRQueue = IRQueue;
+
- var displayContinuation = function() {
++ var displayContinuation = function pageDisplayContinuation() {
+ console.log('--display--');
+
- var gfx = new CanvasGraphics(canvasCtx);
- var fonts = [];
- var images = new ImagesLoader();
-
- this.compile(gfx, fonts, images);
- stats.compile = Date.now();
-
- var displayContinuation = function pageDisplayContinuation() {
// Always defer call to display() to work around bug in
// Firefox error reporting from XHR callbacks.
- setTimeout(function() {
+ setTimeout(function pageSetTimeout() {
var exc = null;
try {
- self.display(gfx);
- stats.render = Date.now();
+ self.display(gfx, continuation);
} catch (e) {
exc = e.toString();
+ continuation(exc);
+ throw e;
}
- if (continuation) continuation(exc);
});
};
-
- var fontObjs = FontLoader.bind(
- fonts,
- function pageFontObjs() {
- stats.fonts = Date.now();
- images.notifyOnLoad(function pageNotifyOnLoad() {
- stats.images = Date.now();
- displayContinuation();
- });
- });
-
- for (var i = 0, ii = fonts.length; i < ii; ++i)
- fonts[i].dict.fontObj = fontObjs[i];
+
+ this.ensureFonts(fonts, function() {
+ displayContinuation();
+ });
},
-
- compile: function pageCompile(gfx, fonts, images) {
- if (this.code) {
+ getIRQueue: function(handler, dependency) {
+ if (this.IRQueue) {
// content was compiled
- return;
+ return this.IRQueue;
}
var xref = this.xref;
width: this.width,
height: this.height,
rotate: this.rotate });
- gfx.execute(this.code, xref, resources);
- gfx.endDrawing();
+
+ var startIdx = 0;
+ var length = this.IRQueue.fnArray.length;
+ var IRQueue = this.IRQueue;
+
+ var self = this;
+ var startTime = Date.now();
+ function next() {
+ startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
+ if (startIdx == length) {
+ self.stats.render = Date.now();
+ console.log("page=%d - executeIRQueue: time=%dms",
+ self.pageNumber + 1, self.stats.render - startTime);
+ callback();
+ }
+ }
+ next();
},
- rotatePoint: function(x, y) {
+ rotatePoint: function pageRotatePoint(x, y) {
var rotate = this.rotate;
switch (rotate) {
case 180:
return constructor;
})();
- var PartialEvaluator = (function() {
+var FontsMap = {};
+var FontLoadedCounter = 0;
+
+var objIdCounter = 0;
+
+ var PartialEvaluator = (function partialEvaluator() {
function constructor() {
this.state = new EvalState();
this.stateStack = [];
};
constructor.prototype = {
- getIRQueue: function(stream, xref, resources, queue, handler,
- evaluate: function partialEvaluatorEvaluate(stream, xref, resources, fonts,
- images) {
++ getIRQueue: function partialEvaluatorGetIRQueue(stream, xref, resources, queue, handler,
+ uniquePrefix, dependency) {
+
+ function insertDependency(depList) {
+ fnArray.push("dependency");
+ argsArray.push(depList);
+ for (var i = 0; i < depList.length; i++) {
+ var dep = depList[i];
+ if (dependency.indexOf(dep) == -1) {
+ dependency.push(depList[i]);
+ }
+ }
+ }
+
+ function buildPaintImageXObject(image, inline) {
+ var dict = image.dict;
+ var w = dict.get('Width', 'W');
+ var h = dict.get('Height', 'H');
+
+ if (image instanceof JpegStream) {
+ var objId = 'img_' + ++objIdCounter;
+ handler.send("obj", [objId, "JpegStream", image.getIR()]);
+
+ // Add the dependency on the image object.
+ insertDependency([objId]);
+
+ // The normal fn.
+ fn = 'paintJpegXObject';
+ args = [ objId, w, h ];
+ } else {
+ // 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
+ // of image processing can be done here.
+ if (!imageMask) {
+ var imageObj = new PDFImage(xref, resources, image, inline);
+
+ if (imageObj.imageMask) {
+ throw "Can't handle this in the web worker :/";
+ }
+
+ var imgData = {
+ width: w,
+ height: h,
+ data: new Uint8Array(w * h * 4)
+ };
+ var pixels = imgData.data;
+ imageObj.fillRgbaBuffer(pixels, imageObj.decode);
+
+ fn = "paintImageXObject";
+ args = [ imgData ];
+ } else /* imageMask == true */ {
+ // This depends on a tmpCanvas beeing filled with the
+ // current fillStyle, such that processing the pixel
+ // data can't be done here. Instead of creating a
+ // complete PDFImage, only read the information needed
+ // for later.
+ fn = "paintImageMaskXObject";
+
+ var width = dict.get('Width', 'W');
+ 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;
+
+ args = [ imgArray, inverseDecode, width, height ];
+ }
+ }
+ }
+
+ uniquePrefix = uniquePrefix || "";
+ if (!queue.argsArray) {
+ queue.argsArray = []
+ }
+ if (!queue.fnArray) {
+ queue.fnArray = [];
+ }
+
+ var fnArray = queue.fnArray, argsArray = queue.argsArray;
+ var dependency = dependency || [];
+
resources = xref.fetchIfRef(resources) || new Dict();
var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict();
var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict();
var parser = new Parser(new Lexer(stream), false);
- var args = [], argsArray = [], fnArray = [], obj;
+ var args = [], obj;
+ var res = resources;
- while (!IsEOF(obj = parser.getObj())) {
- if (IsCmd(obj)) {
+ while (!isEOF(obj = parser.getObj())) {
+ if (isCmd(obj)) {
var cmd = obj.cmd;
var fn = OP_MAP[cmd];
+ if (!fn) {
+ // invalid content command, trying to recover
+ if (cmd.substr(-2) == 'BT') {
+ fn = OP_MAP[cmd.substr(0, cmd.length - 2)];
+ // feeding 'BT' on next interation
+ parser = {
+ getObj: function() {
+ parser = this.oldParser;
+ return { name: 'BT' };
+ },
+ oldParser: parser
+ };
+ }
+ }
assertWellFormed(fn, "Unknown command '" + cmd + "'");
// TODO figure out how to type-check vararg functions
// compile tiling patterns
var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors
- if (IsName(patternName)) {
+ if (isName(patternName)) {
var pattern = xref.fetchIfRef(patterns.get(patternName.name));
if (pattern) {
- var dict = IsStream(pattern) ? pattern.dict : pattern;
+ var dict = isStream(pattern) ? pattern.dict : pattern;
var typeNum = dict.get('PatternType');
+
+ // Type1 is TilingPattern
if (typeNum == 1) {
- patternName.code = this.evaluate(pattern, xref,
- dict.get('Resources'),
- fonts, images);
+ // Create an IR of the pattern code.
+ var depIdx = dependency.length;
+ var codeIR = this.getIRQueue(pattern, xref,
+ dict.get('Resources'), {}, handler,
+ uniquePrefix, dependency);
+
+ // Add the dependencies that are required to execute the
+ // codeIR.
+ insertDependency(dependency.slice(depIdx));
+
+ args = TilingPattern.getIR(codeIR, dict, args);
+ }
+ // Type2 is ShadingPattern.
+ else if (typeNum == 2) {
+ var shading = xref.fetchIfRef(dict.get('Shading'));
+ var matrix = dict.get('Matrix');
+ var pattern = Pattern.parseShading(shading, matrix, xref, res, null /*ctx*/);
+ args = pattern.getIR();
+ } else {
+ error("Unkown PatternType " + typeNum);
}
}
}
var fontRes = resources.get('Font');
if (fontRes) {
fontRes = xref.fetchIfRef(fontRes);
- var font = xref.fetchIfRef(fontRes.get(args[0].name));
+ var font = xref.fetchIfRef(fontRes.get(fontName));
- assertWellFormed(IsDict(font));
+ assertWellFormed(isDict(font));
if (!font.translated) {
font.translated = this.translateFont(font, xref, resources);
- if (fonts && font.translated) {
+ if (font.translated) {
// keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page
- fonts.push(font.translated);
+ var loadedName = "font_" + uniquePrefix + (FontLoadedCounter++);
+ font.translated.properties.loadedName = loadedName;
+ FontsMap[loadedName] = font;
+
+ handler.send("obj", [
+ loadedName,
+ "Font",
+ font.translated.name,
+ font.translated.file,
+ font.translated.properties
+ ]);
}
}
+ args[0].name = font.translated.properties.loadedName;
+
+ // Ensure the font is ready before the font is set
+ // and later on used for drawing.
+ // TODO: This should get insert to the IRQueue only once per
+ // page.
+ insertDependency([font.translated.properties.loadedName]);
+ } else {
+ // TODO: TOASK: Is it possible to get here? If so, what does
+ // args[0].name should be like???
}
+ } else if (cmd == 'EI') {
+ buildPaintImageXObject(args[0], true);
+ }
+
+ // Transform some cmds.
+ switch (fn) {
+ // Parse the ColorSpace data to a raw format.
+ case "setFillColorSpace":
+ case "setStrokeColorSpace":
+ args = [ ColorSpace.parseToIR(args[0], xref, resources) ];
+ break;
+ case "shadingFill":
+ var shadingRes = xref.fetchIfRef(res.get('Shading'));
+ if (!shadingRes)
+ error('No shading resource found');
+
+ var shading = xref.fetchIfRef(shadingRes.get(args[0].name));
+ if (!shading)
+ error('No shading object found');
+
+ var shadingFill = Pattern.parseShading(shading, null, xref, res, /* ctx */ null);
+ var patternIR = shadingFill.getIR();
+
+ args = [ patternIR ];
+ fn = "shadingFill";
+
+ break;
}
fnArray.push(fn);
return canvas;
}
- var CanvasGraphics = (function() {
+ var CanvasGraphics = (function canvasGraphics() {
- function constructor(canvasCtx, imageCanvas) {
+ // Defines the time the executeIRQueue gone be executing
+ // before it stops and shedules a continue of execution.
+ var kExecutionTime = 50;
+ // Number of IR commands to execute before checking
+ // if we execute longer then `kExecutionTime`.
+ var kExecutionTimeCheck = 500;
+
+ function constructor(canvasCtx, objs) {
this.ctx = canvasCtx;
this.current = new CanvasExtraState();
this.stateStack = [];
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
},
- executeIRQueue: function(codeIR, executionStartIdx, continueCallback) {
- compile: function canvasGraphicsCompile(stream, xref, resources, fonts,
- images) {
- var pe = new PartialEvaluator();
- return pe.evaluate(stream, xref, resources, fonts, images);
- },
++ executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
++ executionStartIdx, continueCallback) {
+ var argsArray = codeIR.argsArray;
+ var fnArray = codeIR.fnArray;
+ var i = executionStartIdx || 0;
+ var argsArrayLen = argsArray.length;
+
+ var executionEndIdx;
+ var startTime = Date.now();
- execute: function canvasGraphicsExecute(code, xref, resources) {
- resources = xref.fetchIfRef(resources) || new Dict();
- var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs;
- this.xref = xref;
- this.res = resources || new Dict();
- this.xobjs = xref.fetchIfRef(this.res.get('XObject')) || new Dict();
+ var objs = this.objs;
- code(this);
+ do {
+ executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck);
+
+ for (i; i < executionEndIdx; i++) {
+ if (fnArray[i] !== "dependency") {
+ this[fnArray[i]].apply(this, argsArray[i]);
+ } else {
+ var deps = argsArray[i];
+ for (var n = 0; n < deps.length; n++) {
+ var depObjId = deps[n];
+
+ // If the promise isn't resolved yet, add the continueCallback
+ // to the promise and bail out.
+ if (!objs.isResolved(depObjId)) {
+ objs.get(depObjId, continueCallback);
+ return i;
+ }
+ }
+ }
+ }
+
+ // If the entire IRQueue was executed, stop as were done.
+ if (i == argsArrayLen) {
+ return i;
+ }
+ // If the execution took longer then a certain amount of time, shedule
+ // to continue exeution after a short delay.
+ // However, this is only possible if a 'continueCallback' is passed in.
+ else if (continueCallback &&
+ (Date.now() - startTime) > kExecutionTime) {
+ setTimeout(continueCallback, 0);
+ return i;
+ }
- this.xobjs = savedXobjs;
- this.res = savedRes;
- this.xref = savedXref;
+ // If the IRQueue isn't executed completly yet OR the execution time
+ // was short enough, do another execution round.
+ } while (true);
},
- endDrawing: function() {
+ endDrawing: function canvasGraphicsEndDrawing() {
this.ctx.restore();
},
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
},
- endText: function() {
+ endText: function canvasGraphicsEndText() {
},
- setCharSpacing: function(spacing) {
+ setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) {
this.current.charSpacing = spacing;
},
- setWordSpacing: function(spacing) {
+ setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) {
this.current.wordSpacing = spacing;
},
- setHScale: function(scale) {
+ setHScale: function canvasGraphicsSetHScale(scale) {
this.current.textHScale = scale / 100;
},
- setLeading: function(leading) {
+ setLeading: function canvasGraphicsSetLeading(leading) {
this.current.leading = -leading;
},
- setFont: function(fontRef, size) {
+ setFont: function canvasGraphicsSetFont(fontRef, size) {
- var font;
- // the tf command uses a name, but graphics state uses a reference
- if (isName(fontRef)) {
- font = this.xref.fetchIfRef(this.res.get('Font'));
- if (!isDict(font))
- return;
-
- font = font.get(fontRef.name);
- } else if (isRef(fontRef)) {
- font = fontRef;
- }
- font = this.xref.fetchIfRef(font);
- if (!font)
- error('Referenced font is not found');
-
- var fontObj = font.fontObj;
+ // Lookup the fontObj using fontRef only.
+ var fontRefName = fontRef.name;
+ var fontObj = this.objs.get(fontRefName);
+
+ if (!fontObj) {
+ throw "Can't find font for " + fontRefName;
+ }
+
- var name = fontObj.loadedName;
- if (!name) {
- // TODO: fontDescriptor is not available, fallback to default font
- name = 'sans-serif';
- }
++ var name = fontObj.loadedName || 'sans-serif';
+
this.current.font = fontObj;
this.current.fontSize = size;
-- var name = fontObj.loadedName || 'sans-serif';
if (this.ctx.$setFont) {
this.ctx.$setFont(name, size);
} else {
- var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
- (fontObj.bold ? 'bold' : 'normal');
-
- var italic = fontObj.italic ? 'italic' : 'normal';
- var serif = fontObj.serif ? 'serif' : 'sans-serif';
- var typeface = '"' + name + '", ' + serif;
- var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface;
- this.ctx.font = rule;
+ this.ctx.font = fontObj.getRule(size);
}
},
- setTextRenderingMode: function(mode) {
+ setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) {
TODO('text rendering mode: ' + mode);
},
- setTextRise: function(rise) {
+ setTextRise: function canvasGraphicsSetTextRise(rise) {
TODO('text rise: ' + rise);
},
- moveText: function(x, y) {
+ moveText: function canvasGraphicsMoveText(x, y) {
this.current.x = this.current.lineX += x;
this.current.y = this.current.lineY += y;
if (this.ctx.$setCurrentX) {
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
},
- nextLine: function() {
+ nextLine: function canvasGraphicsNextLine() {
this.moveText(0, this.current.leading);
},
- showText: function(text) {
++
+ showText: function canvasGraphicsShowText(text) {
+ // If the current font isn't supported, we can't display the text and
+ // bail out.
+ if (!this.current.font.supported) {
+ return;
+ }
-
++
var ctx = this.ctx;
var current = this.current;
- var originalText = text;
-
- ctx.save();
- ctx.transform.apply(ctx, current.textMatrix);
- ctx.scale(1, -1);
-
- ctx.translate(current.x, -1 * current.y);
-
var font = current.font;
- if (font) {
- ctx.transform.apply(ctx, font.textMatrix || IDENTITY_MATRIX);
- text = font.charsToUnicode(text);
- }
-
- var composite = font.composite;
- var encoding = font.encoding;
+ var glyphs = font.charsToGlyphs(text);
var fontSize = current.fontSize;
var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing;
var textHScale = current.textHScale;
- ctx.scale(1 / textHScale, 1);
-
- var width = 0;
- for (var i = 0; i < text.length; i++) {
- if (composite) {
- var position = i * 2 + 1;
- var charcode = (originalText.charCodeAt(position - 1) << 8) +
- originalText.charCodeAt(position);
- } else {
- var charcode = originalText.charCodeAt(i);
+ var glyphsLength = glyphs.length;
+ if (font.coded) {
+ ctx.save();
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.translate(current.x, current.y);
+
+ var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
+ ctx.scale(1 / textHScale, 1);
+ for (var i = 0; i < glyphsLength; ++i) {
+
+ var glyph = glyphs[i];
+ if (glyph === null) {
+ // word break
+ this.ctx.translate(wordSpacing, 0);
+ continue;
+ }
+
+ this.save();
+ ctx.scale(fontSize, fontSize);
+ ctx.transform.apply(ctx, fontMatrix);
+ this.execute(glyph.code, this.xref, font.resources);
+ this.restore();
+
+ var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
+ var width = transformed[0] * fontSize + charSpacing;
+
+ ctx.translate(width, 0);
+ current.x += width;
+
}
+ ctx.restore();
+ } else {
+ ctx.save();
+ ctx.transform.apply(ctx, current.textMatrix);
+ ctx.scale(1, -1);
+ ctx.translate(current.x, -1 * current.y);
+ ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
+
+ ctx.scale(1 / textHScale, 1);
+
+ var width = 0;
+ for (var i = 0; i < glyphsLength; ++i) {
+ var glyph = glyphs[i];
+ if (glyph === null) {
+ // word break
+ width += wordSpacing;
+ continue;
+ }
- var charWidth = font.encoding[charcode].width * fontSize * 0.001;
- charWidth += charSpacing;
- if (charcode == 32)
- charWidth += wordSpacing;
+ var unicode = glyph.unicode;
+ var char = (unicode >= 0x10000) ?
+ String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
+ 0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
- ctx.fillText(text.charAt(i), 0, 0);
- ctx.translate(charWidth, 0);
- width += charWidth;
- }
- current.x += width;
+ ctx.fillText(char, width, 0);
+ width += glyph.width * fontSize * 0.001 + charSpacing;
+ }
+ current.x += width;
- this.ctx.restore();
+ ctx.restore();
+ }
},
- showSpacedText: function(arr) {
++
+ showSpacedText: function canvasGraphicsShowSpacedText(arr) {
+ // If the current font isn't supported, we can't display the text and
+ // bail out.
+ if (!this.current.font.supported) {
+ return;
+ }
+
- for (var i = 0; i < arr.length; ++i) {
+ var ctx = this.ctx;
+ var current = this.current;
+ var fontSize = current.fontSize;
+ var textHScale = current.textHScale;
+ var arrLength = arr.length;
+ for (var i = 0; i < arrLength; ++i) {
var e = arr[i];
- if (IsNum(e)) {
- if (this.ctx.$addCurrentX) {
- this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize);
+ if (isNum(e)) {
+ if (ctx.$addCurrentX) {
+ ctx.$addCurrentX(-e * 0.001 * fontSize);
} else {
- this.current.x -= e * 0.001 * this.current.fontSize *
- this.current.textHScale;
+ current.x -= e * 0.001 * fontSize * textHScale;
}
- } else if (IsString(e)) {
+ } else if (isString(e)) {
this.showText(e);
} else {
- malformed('TJ array element ' + e + " isn't string or num");
+ malformed('TJ array element ' + e + ' is not string or num');
}
}
},
},
// Color
- setStrokeColorSpace: function(raw) {
- setStrokeColorSpace: function canvasGraphicsSetStrokeColorSpace(space) {
++ setStrokeColorSpace: function canvasGraphicsSetStrokeColorSpacefunction(raw) {
this.current.strokeColorSpace =
- ColorSpace.parse(space, this.xref, this.res);
+ ColorSpace.fromIR(raw);
},
- setFillColorSpace: function(raw) {
- setFillColorSpace: function canvasGraphicsSetFillColorSpace(space) {
++ setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) {
this.current.fillColorSpace =
- ColorSpace.parse(space, this.xref, this.res);
+ ColorSpace.fromIR(raw);
},
- setStrokeColor: function(/*...*/) {
+ setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) {
var cs = this.current.strokeColorSpace;
var color = cs.getRgb(arguments);
this.setStrokeRGBColor.apply(this, color);
},
- setStrokeColorN: function canvasGraphicsSetStrokeColorN(/*...*/) {
+ getColorN_IR_Pattern: function(IR, cs) {
+ if (IR[0] == "TilingPatternIR") {
+ // First, build the `color` var like it's done in the
+ // Pattern.prototype.parse function.
+ var args = IR[1];
+ var base = cs.base;
+ var color;
+ if (base) {
+ var baseComps = base.numComps;
+
+ color = [];
+ for (var i = 0; i < baseComps; ++i)
+ color.push(args[i]);
+
+ color = base.getRgb(color);
+ }
+
+ // Build the pattern based on the IR data.
+ var pattern = new TilingPatternIR(IR, color, this.ctx, this.objs);
+ } else if (IR[0] == "RadialAxialShading" || IR[0] == "DummyShading") {
+ var pattern = Pattern.shadingFromIR(this.ctx, IR);
+ } else {
+ throw "Unkown IR type";
+ }
+ return pattern;
+ },
- setStrokeColorN_IR: function(/*...*/) {
++ setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) {
var cs = this.current.strokeColorSpace;
if (cs.name == 'Pattern') {
var color = cs.getRgb(arguments);
this.setFillRGBColor.apply(this, color);
},
- setFillColorN_IR: function(/*...*/) {
- setFillColorN: function canvasGraphicsSetFillColorN(/*...*/) {
++ setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) {
var cs = this.current.fillColorSpace;
if (cs.name == 'Pattern') {
this.current.fillColor = color;
},
- shadingFill: function(patternIR) {
- // Shading
- shadingFill: function canvasGraphicsShadingFill(shadingName) {
- var xref = this.xref;
- var res = this.res;
++ shadingFill: function canvasGraphicsShadingFill(patternIR) {
var ctx = this.ctx;
-
- var shadingRes = xref.fetchIfRef(res.get('Shading'));
- if (!shadingRes)
- error('No shading resource found');
-
- var shading = xref.fetchIfRef(shadingRes.get(shadingName.name));
- if (!shading)
- error('No shading object found');
-
- var shadingFill = Pattern.parseShading(shading, null, xref, res, ctx);
-
+
this.save();
- ctx.fillStyle = shadingFill.getPattern();
+ ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR);
var inv = ctx.mozCurrentTransformInverse;
if (inv) {
},
// Images
- beginInlineImage: function() {
+ beginInlineImage: function canvasGraphicsBeginInlineImage() {
error('Should not call beginInlineImage');
},
- beginImageData: function() {
+ beginImageData: function canvasGraphicsBeginImageData() {
error('Should not call beginImageData');
},
- endInlineImage: function canvasGraphicsEndInlineImage(image) {
- this.paintImageXObject(null, image, true);
- },
-
- // XObjects
- paintXObject: function canvasGraphicsPaintXObject(obj) {
- var xobj = this.xobjs.get(obj.name);
- if (!xobj)
- return;
- xobj = this.xref.fetchIfRef(xobj);
- assertWellFormed(isStream(xobj), 'XObject should be a stream');
-
- var oc = xobj.dict.get('OC');
- if (oc) {
- TODO('oc for xobject');
- }
-
- var opi = xobj.dict.get('OPI');
- if (opi) {
- TODO('opi for xobject');
- }
-
- var type = xobj.dict.get('Subtype');
- assertWellFormed(isName(type), 'XObject should have a Name subtype');
- if ('Image' == type.name) {
- this.paintImageXObject(obj, xobj, false);
- } else if ('Form' == type.name) {
- this.paintFormXObject(obj, xobj);
- } else if ('PS' == type.name) {
- warn('(deprecated) PostScript XObjects are not supported');
- } else {
- malformed('Unknown XObject subtype ' + type.name);
- }
- },
-
- paintFormXObject: function canvasGraphicsPaintFormXObject(ref, stream) {
+
- paintFormXObjectBegin: function(matrix, bbox) {
++ paintFormXObjectBegin: function canvasGraphicsPaintFormXObject(matrix, bbox) {
this.save();
- if (matrix && IsArray(matrix) && 6 == matrix.length)
- var matrix = stream.dict.get('Matrix');
+ if (matrix && isArray(matrix) && 6 == matrix.length)
this.transform.apply(this, matrix);
- if (bbox && IsArray(bbox) && 4 == bbox.length) {
- this.rectangle.apply(this, bbox);
- var bbox = stream.dict.get('BBox');
+ if (bbox && isArray(bbox) && 4 == bbox.length) {
+ var width = bbox[2] - bbox[0];
+ var height = bbox[3] - bbox[1];
+ this.rectangle(bbox[0], bbox[1], width, height);
this.clip();
this.endPath();
}
this.restore();
},
+ paintImageXObject: function(imgData) {
+ this.save();
+
+ var ctx = this.ctx;
+ var w = imgData.width;
+ var h = imgData.height;
+ // scale the image to the unit square
+ ctx.scale(1 / w, -1 / h);
+
+ var tmpCanvas = new this.ScratchCanvas(w, h);
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var tmpImgData;
+
+ // Deactivating this for now until we have feature detection.
+ // if (isGecko) {
+ // tmpImgData = imgData;
+ // } else {
+ tmpImgData = tmpCtx.getImageData(0, 0, w, h);
+
+ // Copy over the imageData.
+ var tmpImgDataPixels = tmpImgData.data;
+ var len = tmpImgDataPixels.length;
+
+ // TODO: There got to be a better way to copy an ImageData array
+ // then coping over all the bytes one by one :/
+ while (len--)
+ tmpImgDataPixels[len] = imgData.data[len];
+ // }
+
+ tmpCtx.putImageData(tmpImgData, 0, 0);
+ ctx.drawImage(tmpCanvas, 0, -h);
+ this.restore();
+ },
+
// Marked content
- markPoint: function(tag) {
+ markPoint: function canvasGraphicsMarkPoint(tag) {
TODO('Marked content');
},
- markPointProps: function(tag, properties) {
+ markPointProps: function canvasGraphicsMarkPointProps(tag, properties) {
TODO('Marked content');
},
- beginMarkedContent: function(tag) {
+ beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) {
TODO('Marked content');
},
- beginMarkedContentProps: function(tag, properties) {
+ beginMarkedContentProps:
+ function canvasGraphicsBeginMarkedContentProps(tag, properties) {
TODO('Marked content');
},
- endMarkedContent: function() {
+ endMarkedContent: function canvasGraphicsEndMarkedContent() {
TODO('Marked content');
},
};
constructor.parse = function colorspace_parse(cs, xref, res) {
- if (IsArray(IR)) {
+ var IR = constructor.parseToIR(cs, xref, res, true);
+ if (!(IR instanceof SeparationCS)) {
+ return constructor.fromIR(IR);
+ } else {
+ return IR
+ }
+ };
+
+ constructor.fromIR = function(IR) {
+ var name;
- if (IsName(cs)) {
++ if (isArray(IR)) {
+ name = IR[0];
+ } else {
+ name = IR;
+ }
+
+ switch (name) {
+ case "DeviceGrayCS":
+ return new DeviceGrayCS();
+ case "DeviceRgbCS":
+ return new DeviceRgbCS();
+ case "DeviceCmykCS":
+ return new DeviceCmykCS();
+ case "PatternCS":
+ var baseCS = IR[1];
+ if (baseCS == null) {
+ return new PatternCS(null);
+ } else {
+ return new PatternCS(ColorSpace.fromIR(baseCS));
+ }
+ case "IndexedCS":
+ var baseCS = IR[1];
+ var hiVal = IR[2];
+ var lookup = IR[3];
+ return new IndexedCS(ColorSpace.fromIR(baseCS), hiVal, lookup)
+ case "SeparationCS":
+ var alt = IR[1];
+ var tintFnIR = IR[2];
+
+ return new SeparationCS(
+ ColorSpace.fromIR(alt),
+ PDFFunction.fromIR(tintFnIR)
+ );
+ default:
+ error("Unkown name " + name);
+ }
+ return null;
+ }
+
+ constructor.parseToIR = function colorspace_parse(cs, xref, res, parseOnly) {
- var colorSpaces = xref.fetchIfRef(res.get('ColorSpace'));
+ if (isName(cs)) {
- if (IsDict(colorSpaces)) {
+ var colorSpaces = res.get('ColorSpace');
+ if (isDict(colorSpaces)) {
var refcs = colorSpaces.get(cs.name);
if (refcs)
cs = refcs;
this.mode = mode;
switch (mode) {
- case 'DeviceGray':
- case 'G':
- return new DeviceGrayCS();
- case 'DeviceRGB':
- case 'RGB':
- return new DeviceRgbCS();
- case 'DeviceCMYK':
- case 'CMYK':
- return new DeviceCmykCS();
- case 'Pattern':
- return new PatternCS(null);
- default:
- error('unrecognized colorspace ' + mode);
+ case 'DeviceGray':
+ case 'G':
+ return "DeviceGrayCS";
+ case 'DeviceRGB':
+ case 'RGB':
+ return "DeviceRgbCS";
+ case 'DeviceCMYK':
+ case 'CMYK':
+ return "DeviceCmykCS";
+ case 'Pattern':
+ return ["PatternCS", null];
+ default:
+ error('unrecognized colorspace ' + mode);
}
- } else if (IsArray(cs)) {
+ } else if (isArray(cs)) {
var mode = cs[0].name;
this.mode = mode;
}
};
- constructor.shadingFromIR = function(ctx, raw) {
- constructor.parse = function pattern_parse(args, cs, xref, res, ctx) {
- var length = args.length;
-
- var patternName = args[length - 1];
- if (!isName(patternName))
- error('Bad args to getPattern: ' + patternName);
-
- var patternRes = xref.fetchIfRef(res.get('Pattern'));
- if (!patternRes)
- error('Unable to find pattern resource');
-
- var pattern = xref.fetchIfRef(patternRes.get(patternName.name));
- var dict = isStream(pattern) ? pattern.dict : pattern;
- var typeNum = dict.get('PatternType');
-
- switch (typeNum) {
- case 1:
- var base = cs.base;
- var color;
- if (base) {
- var baseComps = base.numComps;
-
- color = [];
- for (var i = 0; i < baseComps; ++i)
- color.push(args[i]);
-
- color = base.getRgb(color);
- }
- var code = patternName.code;
- return new TilingPattern(pattern, code, dict, color, xref, ctx);
- case 2:
- var shading = xref.fetchIfRef(dict.get('Shading'));
- var matrix = dict.get('Matrix');
- return Pattern.parseShading(shading, matrix, xref, res, ctx);
- default:
- error('Unknown type of pattern: ' + typeNum);
- }
- return null;
- };
++ constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) {
+ var obj = window[raw[0]];
+ return obj.fromIR(ctx, raw);
+ }
constructor.parseShading = function pattern_shading(shading, matrix,
xref, res, ctx) {
var fnObj = dict.get('Function');
fnObj = xref.fetchIfRef(fnObj);
- if (IsArray(fnObj))
+ if (isArray(fnObj))
error('No support for array of functions');
- else if (!IsPDFFunction(fnObj))
+ else if (!isPDFFunction(fnObj))
error('Invalid function');
- var fn = new PDFFunction(xref, fnObj);
+ var fn = PDFFunction.parse(xref, fnObj);
// 10 samples seems good enough for now, but probably won't work
// if there are sharp color changes. Ideally, we would implement
this.colorStops = colorStops;
}
+ constructor.fromIR = function(ctx, raw) {
+ var type = raw[1];
+ var colorStops = raw[2];
+ var p0 = raw[3];
+ var p1 = raw[4];
+ var r0 = raw[5];
+ var r1 = raw[6];
+
+ var curMatrix = ctx.mozCurrentTransform;
+ if (curMatrix) {
+ var userMatrix = ctx.mozCurrentTransformInverse;
+
+ p0 = Util.applyTransform(p0, curMatrix);
+ p0 = Util.applyTransform(p0, userMatrix);
+
+ p1 = Util.applyTransform(p1, curMatrix);
+ p1 = Util.applyTransform(p1, userMatrix);
+ }
+
+ if (type == 2)
+ var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
+ else if (type == 3)
+ var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
+
+ for (var i = 0, ii = colorStops.length; i < ii; ++i) {
+ var c = colorStops[i];
+ grad.addColorStop(c[0], c[1]);
+ }
+ return grad;
+ }
+
constructor.prototype = {
- getIR: function() {
- getPattern: function radialAxialShadingGetPattern() {
++ getIR: function RadialAxialShading_getIR() {
var coordsArr = this.coordsArr;
var type = this.shadingType;
- var p0, p1, r0, r1;
if (type == 2) {
- p0 = [coordsArr[0], coordsArr[1]];
- p1 = [coordsArr[2], coordsArr[3]];
+ var p0 = [coordsArr[0], coordsArr[1]];
+ var p1 = [coordsArr[2], coordsArr[3]];
+ var r0 = null;
+ var r1 = null;
} else if (type == 3) {
- p0 = [coordsArr[0], coordsArr[1]];
- p1 = [coordsArr[3], coordsArr[4]];
- r0 = coordsArr[2];
- r1 = coordsArr[5];
+ var p0 = [coordsArr[0], coordsArr[1]];
+ var p1 = [coordsArr[3], coordsArr[4]];
+ var r0 = coordsArr[2];
+ var r1 = coordsArr[5];
} else {
error('getPattern type unknown: ' + type);
}
return constructor;
})();
- var TilingPatternIR = (function() {
-var TilingPattern = (function tilingPattern() {
++var TilingPatternIR = (function tilingPattern() {
var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
- function constructor(pattern, code, dict, color, xref, ctx) {
- function multiply(m, tm) {
- var a = m[0] * tm[0] + m[1] * tm[2];
- var b = m[0] * tm[1] + m[1] * tm[3];
- var c = m[2] * tm[0] + m[3] * tm[2];
- var d = m[2] * tm[1] + m[3] * tm[3];
- var e = m[4] * tm[0] + m[5] * tm[2] + tm[4];
- var f = m[4] * tm[1] + m[5] * tm[3] + tm[5];
- return [a, b, c, d, e, f];
- }
-
- TODO('TilingType');
-
- this.matrix = dict.get('Matrix');
- this.curMatrix = ctx.mozCurrentTransform;
- this.invMatrix = ctx.mozCurrentTransformInverse;
- this.ctx = ctx;
- this.type = 'Pattern';
-
- var bbox = dict.get('BBox');
- var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
-
- var xstep = dict.get('XStep');
- var ystep = dict.get('YStep');
-
- var topLeft = [x0, y0];
- // we want the canvas to be as large as the step size
- var botRight = [x0 + xstep, y0 + ystep];
-
- var width = botRight[0] - topLeft[0];
- var height = botRight[1] - topLeft[1];
-
- // TODO: hack to avoid OOM, we would idealy compute the tiling
- // pattern to be only as large as the acual size in device space
- // This could be computed with .mozCurrentTransform, but still
- // needs to be implemented
- while (Math.abs(width) > 512 || Math.abs(height) > 512) {
- width = 512;
- height = 512;
- }
+ function TilingPatternIR(IR, color, ctx, objs) {
+ // "Unfolding" the IR.
+ var IRQueue = IR[2];
+ this.matrix = IR[3];
+ var bbox = IR[4];
+ var xstep = IR[5];
+ var ystep = IR[6];
+ var paintType = IR[7];
- var tmpCanvas = new ScratchCanvas(width, height);
+ //
+ TODO('TilingType');
- // set the new canvas element context as the graphics context
- var tmpCtx = tmpCanvas.getContext('2d');
- var graphics = new CanvasGraphics(tmpCtx);
-
- var paintType = dict.get('PaintType');
- switch (paintType) {
- case PAINT_TYPE_COLORED:
- tmpCtx.fillStyle = ctx.fillStyle;
- tmpCtx.strokeStyle = ctx.strokeStyle;
- break;
- case PAINT_TYPE_UNCOLORED:
- color = Util.makeCssRgb.apply(this, color);
- tmpCtx.fillStyle = color;
- tmpCtx.strokeStyle = color;
- break;
- default:
- error('Unsupported paint type: ' + paintType);
- }
+ this.curMatrix = ctx.mozCurrentTransform;
+ this.invMatrix = ctx.mozCurrentTransformInverse;
+ this.ctx = ctx;
+ this.type = 'Pattern';
- var scale = [width / xstep, height / ystep];
- this.scale = scale;
+ var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
+ var topLeft = [x0, y0];
+ // we want the canvas to be as large as the step size
+ var botRight = [x0 + xstep, y0 + ystep];
+
+ var width = botRight[0] - topLeft[0];
+ var height = botRight[1] - topLeft[1];
+
+ // TODO: hack to avoid OOM, we would idealy compute the tiling
+ // pattern to be only as large as the acual size in device space
+ // This could be computed with .mozCurrentTransform, but still
+ // needs to be implemented
+ while (Math.abs(width) > 512 || Math.abs(height) > 512) {
+ width = 512;
+ height = 512;
+ }
- // transform coordinates to pattern space
- var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
- var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
- graphics.transform.apply(graphics, tmpScale);
- graphics.transform.apply(graphics, tmpTranslate);
+ var tmpCanvas = new ScratchCanvas(width, height);
+
+ // set the new canvas element context as the graphics context
+ var tmpCtx = tmpCanvas.getContext('2d');
+ var graphics = new CanvasGraphics(tmpCtx, objs);
+
+ switch (paintType) {
+ case PAINT_TYPE_COLORED:
+ tmpCtx.fillStyle = ctx.fillStyle;
+ tmpCtx.strokeStyle = ctx.strokeStyle;
+ break;
+ case PAINT_TYPE_UNCOLORED:
+ color = Util.makeCssRgb.apply(this, color);
+ tmpCtx.fillStyle = color;
+ tmpCtx.strokeStyle = color;
+ break;
+ default:
+ error('Unsupported paint type: ' + paintType);
+ }
- if (bbox && isArray(bbox) && 4 == bbox.length) {
- var bboxWidth = bbox[2] - bbox[0];
- var bboxHeight = bbox[3] - bbox[1];
- graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight);
- graphics.clip();
- graphics.endPath();
- }
+ var scale = [width / xstep, height / ystep];
+ this.scale = scale;
+
+ // transform coordinates to pattern space
+ var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
+ var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
+ graphics.transform.apply(graphics, tmpScale);
+ graphics.transform.apply(graphics, tmpTranslate);
+
- if (bbox && IsArray(bbox) && 4 == bbox.length) {
- graphics.rectangle.apply(graphics, bbox);
++ if (bbox && isArray(bbox) && 4 == bbox.length) {
++ var bboxWidth = bbox[2] - bbox[0];
++ var bboxHeight = bbox[3] - bbox[1];
++ graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight);
+ graphics.clip();
+ graphics.endPath();
+ }
- var res = xref.fetchIfRef(dict.get('Resources'));
- graphics.execute(code, xref, res);
+ graphics.executeIRQueue(IRQueue);
- this.canvas = tmpCanvas;
+ this.canvas = tmpCanvas;
}
- constructor.prototype = {
+ TilingPatternIR.prototype = {
getPattern: function tiling_getPattern() {
var matrix = this.matrix;
var curMatrix = this.curMatrix;
return ctx.createPattern(this.canvas, 'repeat');
}
- };
- return constructor;
+ }
+
+ return TilingPatternIR;
})();
+var TilingPattern = {
+ getIR: function(codeIR, dict, args) {
+ var matrix = dict.get('Matrix');
+ var bbox = dict.get('BBox');
+ var xstep = dict.get('XStep');
+ var ystep = dict.get('YStep');
+ var paintType = dict.get('PaintType');
+
+ return ["TilingPatternIR", args, codeIR, matrix, bbox, xstep, ystep, paintType];
+ }
+};
- var PDFImage = (function() {
+ var PDFImage = (function pdfImage() {
function constructor(xref, res, image, inline) {
this.image = image;
if (image.getParams) {
}
return output;
- };
- },
- getSampleArray: function pdfFunctionGetSampleArray(size, outputSize, bps,
- str) {
- var length = 1;
- for (var i = 0; i < size.length; i++)
- length *= size[i];
- length *= outputSize;
-
- var array = [];
- var codeSize = 0;
- var codeBuf = 0;
-
- var strBytes = str.getBytes((length * bps + 7) / 8);
- var strIdx = 0;
- for (var i = 0; i < length; i++) {
- var b;
- while (codeSize < bps) {
- codeBuf <<= 8;
- codeBuf |= strBytes[strIdx++];
- codeSize += 8;
- }
- codeSize -= bps;
- array.push(codeBuf >> codeSize);
- codeBuf &= (1 << codeSize) - 1;
}
- return array;
},
- constructInterpolated: function pdfFunctionConstructInterpolated(str,
- dict) {
+
- constructInterpolated: function(str, dict) {
++ constructInterpolated: function pdfFunctionConstructInterpolated(str, dict) {
var c0 = dict.get('C0') || [0];
var c1 = dict.get('C1') || [1];
var n = dict.get('N');
for (var i = 0; i < length; ++i)
diff.push(c1[i] - c0[i]);
- this.func = function pdfFunctionConstructInterpolatedFunc(args) {
+ return [ CONSTRUCT_INTERPOLATED, c0, diff, n, i ];
+ },
+
- constructInterpolatedFromIR: function(IR) {
++ constructInterpolatedFromIR: function pdfFunctionconstructInterpolatedFromIR(IR) {
+ var c0 = IR[1];
+ var diff = IR[2];
+ var n = IR[3];
+ var i = IR[4];
+
+ var length = diff.length;
+
+ return function(args) {
var x = args[0];
var out = [];
out.push(c0[j] + (x^n * diff[i]));
return out;
- };
+
+ }
},
- constructStiched: function(fn, dict, xref) {
+
+ constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) {
var domain = dict.get('Domain');
var range = dict.get('Range');
var bounds = dict.get('Bounds');
var encode = dict.get('Encode');
- this.func = function pdfFunctionConstructStichedFunc(args) {
- var clip = function pdfFunctionConstructStichedFuncClip(v, min, max) {
+ return [ CONSTRUCT_STICHED, domain, bounds, encoding, fns ];
+ },
+
- constructStichedFromIR: function(IR) {
++ constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) {
+ var domain = IR[1];
+ var bounds = IR[2];
+ var encoding = IR[3];
+ var fnsIR = IR[4];
+ var fns = [];
+
+ for (var i = 0; i < fnsIR.length; i++) {
+ fns.push(PDFFunction.fromIR(fnsIR[i]));
+ }
+
+ return function(args) {
+ var clip = function(v, min, max) {
if (v > max)
v = max;
else if (v < min)
var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
// call the appropropriate function
- return fns[i].func([v2]);
+ return fns[i]([v2]);
};
},
- constructPostScript: function() {
+
- constructPostScriptFromIR: function() {
+ constructPostScript: function pdfFunctionConstructPostScript() {
+ return [ CONSTRUCT_POSTSCRIPT ];
+ },
+
++ constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() {
TODO('unhandled type of function');
- this.func = function pdfFunctionConstructPostScriptFunc() {
+ return function() {
return [255, 105, 180];
};
}
--- /dev/null
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+// Set this to true if you want to use workers.
+var useWorker = false;
+
+var WorkerPage = (function() {
+ function constructor(workerPDF, page, objs) {
+ this.workerPDF = workerPDF;
+ this.page = page;
+ this.objs = objs;
+
+ this.ref = page.ref;
+ }
+
+ constructor.prototype = {
+ get width() {
+ return this.page.width;
+ },
+
+ get height() {
+ return this.page.height;
+ },
+
+ get stats() {
+ return this.page.stats;
+ },
+
++ get view() {
++ return this.page.view;
++ },
++
+ startRendering: function(ctx, callback, errback) {
+ this.ctx = ctx;
+ this.callback = callback;
+ // TODO: Place the worker magic HERE.
+ // this.page.startRendering(ctx, callback, errback);
+
+ this.startRenderingTime = Date.now();
+ this.workerPDF.startRendering(this)
+ },
+
+ startRenderingFromIRQueue: function(IRQueue, fonts) {
+ var gfx = new CanvasGraphics(this.ctx, this.objs);
+
+ var startTime = Date.now();
+ var callback = function(err) {
+ var pageNum = this.page.pageNumber + 1;
+ console.log("page=%d - rendering time: time=%dms",
+ pageNum, Date.now() - startTime);
+ console.log("page=%d - total time: time=%dms",
+ pageNum, Date.now() - this.startRenderingTime);
+
+ this.callback(err);
+ }.bind(this);
+ this.page.startRenderingFromIRQueue(gfx, IRQueue, fonts, callback);
+ },
+
+ getLinks: function() {
+ return this.page.getLinks();
+ }
+ };
+
+ return constructor;
+})();
+
+var PDFObjects = (function() {
+ function PDFObjects() {
+ this.objs = {};
+ }
+
+ PDFObjects.prototype = {
+ objs: null,
+
+ /**
+ * Ensures there is an object defined for `objId`. Stores `data` on the
+ * object *if* it is created.
+ */
+ ensureObj: function(objId, data) {
+ if (!this.objs[objId]) {
+ return this.objs[objId] = new Promise(objId, data);
+ } else {
+ return this.objs[objId];
+ }
+ },
+
+ /**
+ * If called *without* callback, this returns the data of `objId` but the
+ * object needs to be resolved. If it isn't, this function throws.
+ *
+ * If called *with* a callback, the callback is called with the data of the
+ * object once the object is resolved. That means, if you call this
+ * function and the object is already resolved, the callback gets called
+ * right away.
+ */
+ get: function(objId, callback) {
+ // If there is a callback, then the get can be async and the object is
+ // not required to be resolved right now
+ if (callback) {
+ this.ensureObj(objId).then(callback);
+ }
+ // If there isn't a callback, the user expects to get the resolved data
+ // directly.
+ else {
+ var obj = this.objs[objId];
+
+ // If there isn't an object yet or the object isn't resolved, then the
+ // data isn't ready yet!
+ if (!obj || !obj.isResolved) {
+ throw "Requesting object that isn't resolved yet " + objId;
+ }
+ // Direct access.
+ else {
+ return obj.data;
+ }
+ }
+ },
+
+ /**
+ * Resolves the object `objId` with optional `data`.
+ */
+ resolve: function(objId, data) {
+ var objs = this.objs;
+
+ // In case there is a promise already on this object, just resolve it.
+ if (objs[objId]) {
+ objs[objId].resolve(data);
+ } else {
+ this.ensureObj(objId, data);
+ }
+ },
+
+ onData: function(objId, callback) {
+ this.ensureObj(objId).onData(callback);
+ },
+
+ isResolved: function(objId) {
+ var objs = this.objs;
+ if (!objs[objId]) {
+ return false;
+ } else {
+ return objs[objId].isResolved;
+ }
+ },
+
+ hasData: function(objId) {
+ var objs = this.objs;
+ if (!objs[objId]) {
+ return false;
+ } else {
+ return objs[objId].hasData;
+ }
+ },
+
+ /**
+ * Sets the data of an object but *doesn't* resolve it.
+ */
+ setData: function(objId, data) {
+ // Watchout! If you call `this.ensureObj(objId, data)` you'll gone create
+ // a *resolved* promise which shouldn't be the case!
+ this.ensureObj(objId).data = data;
+ }
+ }
+ return PDFObjects;
+})();
+
+
+/**
+ * "Promise" object.
+ */
+var Promise = (function() {
+ var EMPTY_PROMISE = {};
+
+ /**
+ * If `data` is passed in this constructor, the promise is created resolved.
+ * If there isn't data, it isn't resolved at the beginning.
+ */
+ function Promise(name, data) {
+ this.name = name;
+ // If you build a promise and pass in some data it's already resolved.
+ if (data != null) {
+ this.isResolved = true;
+ this._data = data;
+ this.hasData = true;
+ } else {
+ this.isResolved = false;
+ this._data = EMPTY_PROMISE;
+ }
+ this.callbacks = [];
+ };
+
+ Promise.prototype = {
+ hasData: false,
+
+ set data(data) {
+ if (data === undefined) {
+ return;
+ }
+ if (this._data !== EMPTY_PROMISE) {
+ throw "Promise " + this.name + ": Cannot set the data of a promise twice";
+ }
+ this._data = data;
+ this.hasData = true;
+
+ if (this.onDataCallback) {
+ this.onDataCallback(data);
+ }
+ },
+
+ get data() {
+ if (this._data === EMPTY_PROMISE) {
+ throw "Promise " + this.name + ": Cannot get data that isn't set";
+ }
+ return this._data;
+ },
+
+ onData: function(callback) {
+ if (this._data !== EMPTY_PROMISE) {
+ callback(this._data);
+ } else {
+ this.onDataCallback = callback;
+ }
+ },
+
+ resolve: function(data) {
+ if (this.isResolved) {
+ throw "A Promise can be resolved only once " + this.name;
+ }
+
+ this.isResolved = true;
+ this.data = data;
+ var callbacks = this.callbacks;
+
+ for (var i = 0; i < callbacks.length; i++) {
+ callbacks[i].call(null, data);
+ }
+ },
+
+ then: function(callback) {
+ if (!callback) {
+ throw "Requiring callback" + this.name;
+ }
+
+ // If the promise is already resolved, call the callback directly.
+ if (this.isResolved) {
+ var data = this.data;
+ callback.call(null, data);
+ } else {
+ this.callbacks.push(callback);
+ }
+ }
+ }
+ return Promise;
+})();
+
+var WorkerPDFDoc = (function() {
+ function constructor(data) {
+
+ this.data = data;
+ this.stream = new Stream(data);
+ this.pdf = new PDFDoc(this.stream);
+
+ this.catalog = this.pdf.catalog;
+ this.objs = new PDFObjects();
+
+ this.pageCache = [];
+
+ if (useWorker) {
+ var worker = this.worker = new Worker("../worker/pdf_worker_loader.js");
+ } else {
+ // If we don't use a worker, just post/sendMessage to the main thread.
+ var worker = {
+ postMessage: function(obj) {
+ worker.onmessage({data: obj});
+ }
+ }
+ }
+
+ this.fontsLoading = {};
+
+ var processorHandler = this.processorHandler = new MessageHandler("main", worker);
+ processorHandler.on("page", function(data) {
+ var pageNum = data.pageNum;
+ var page = this.pageCache[pageNum];
+
+ // DepFonts are all fonts are required to render the page. `fontsToLoad`
+ // are all the fonts that are required to render the page AND that
+ // aren't loaded on the page yet.
+ var depFonts = data.depFonts;
+ var objs = this.objs;
+ var fontsToLoad = [];
+ var fontsLoading = this.fontsLoading;
+ // The `i` for the checkFontData is stored here to keep the state in
+ // the closure.
+ var i = 0;
+
+
+ function checkFontData() {
+ // Check if all fontObjs have been processed. If not, shedule a
+ // callback that is called once the data arrives and that checks
+ // the next fonts.
+ for (i; i < depFonts.length; i++) {
+ var fontName = depFonts[i];
+ if (!objs.hasData(fontName)) {
+ console.log('need to wait for fontData', fontName);
+ objs.onData(fontName, checkFontData);
+ return;
+ } else if (!objs.isResolved(fontName)) {
+ fontsToLoad.push(fontName);
+ }
+ }
+
+ // There can be edge cases where two pages wait for one font and then
+ // call startRenderingFromIRQueue twice with the same font. That makes
+ // the font getting loaded twice and throw an error later as the font
+ // promise gets resolved twice.
+ // This prevents thats fonts are loaded really only once.
+ for (var j = 0; j < fontsToLoad.length; j++) {
+ var fontName = fontsToLoad[j];
+ if (fontsLoading[fontName]) {
+ fontsToLoad.splice(j, 1);
+ j--;
+ } else {
+ fontsLoading[fontName] = true;
+ }
+ }
+
+ // At this point, all font data ia loaded. Start the actuall rendering.
+ page.startRenderingFromIRQueue(data.IRQueue, fontsToLoad);
+ }
+
+ checkFontData();
+ }, this);
+
+ processorHandler.on("obj", function(data) {
+ var objId = data[0];
+ var objType = data[1];
+
+ switch (objType) {
+ case "JpegStream":
+ var IR = data[2];
+ new JpegStreamIR(objId, IR, this.objs);
+ console.log('got image');
+ break;
+ case "Font":
+ var name = data[2];
+ var file = data[3];
+ var properties = data[4];
+
+ processorHandler.send("font", [objId, name, file, properties]);
+ break;
+ default:
+ throw "Got unkown object type " + objType;
+ }
+ }, this);
+
+ processorHandler.on('font_ready', function(data) {
+ var objId = data[0];
+ var fontObj = new FontShape(data[1]);
+
+ console.log('got fontData', objId);
+
+ // If there is no string, then there is nothing to attach to the DOM.
+ if (!fontObj.str) {
+ this.objs.resolve(objId, fontObj);
+ } else {
+ this.objs.setData(objId, fontObj);
+ }
+ }.bind(this));
+
+ if (!useWorker) {
+ // If the main thread is our worker, setup the handling for the messages
+ // the main thread sends to it self.
+ WorkerProcessorHandler.setup(processorHandler);
+ }
+
+ processorHandler.send("doc", data);
+ }
+
+ constructor.prototype = {
+ get numPages() {
+ return this.pdf.numPages;
+ },
+
+ startRendering: function(page) {
+ this.processorHandler.send("page_request", page.page.pageNumber + 1);
+ },
+
+ getPage: function(n) {
+ if (this.pageCache[n]) {
+ return this.pageCache[n];
+ }
+
+ var page = this.pdf.getPage(n);
+ // Add a reference to the objects such that Page can forward the reference
+ // to the CanvasGraphics and so on.
+ page.objs = this.objs;
+ return this.pageCache[n] = new WorkerPage(this, page, this.objs);
+ },
+
+ destroy: function() {
+ console.log("destroy worker");
+ if (this.worker) {
+ this.worker.terminate();
+ }
+ if (this.fontWorker) {
+ this.fontWorker.terminate();
+ }
+
+ for (var n in this.pageCache) {
+ delete this.pageCache[n];
+ }
+ delete this.data;
+ delete this.stream;
+ delete this.pdf;
+ delete this.catalog;
+ }
+ };
+
+ return constructor;
+})();