From: Rob Sayre Date: Wed, 6 Jul 2011 06:23:12 +0000 (-0700) Subject: Merge upstream. X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=ebeff10a9a44282dded3acaef6a7389037c700da;p=pdf.js.git Merge upstream. --- ebeff10a9a44282dded3acaef6a7389037c700da diff --cc pdf.js index 7d8e432,132d16e..620e98f --- a/pdf.js +++ b/pdf.js @@@ -3346,1089 -3294,1085 +3346,1089 @@@ function ScratchCanvas(width, height) } var CanvasGraphics = (function() { - function constructor(canvasCtx, imageCanvas) { - this.ctx = canvasCtx; - this.current = new CanvasExtraState(); - this.stateStack = [ ]; - this.pendingClip = null; - this.res = null; - this.xobjs = null; - this.ScratchCanvas = imageCanvas || ScratchCanvas; - } + function constructor(canvasCtx, imageCanvas) { + this.ctx = canvasCtx; + this.current = new CanvasExtraState(); + this.stateStack = []; + this.pendingClip = null; + this.res = null; + this.xobjs = null; + this.ScratchCanvas = imageCanvas || ScratchCanvas; + } - var LINE_CAP_STYLES = [ "butt", "round", "square" ]; - var LINE_JOIN_STYLES = [ "miter", "round", "bevel" ]; - var NORMAL_CLIP = {}; - var EO_CLIP = {}; - - // Used for tiling patterns - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; - - constructor.prototype = { - map: { - // Graphics state - w: "setLineWidth", - J: "setLineCap", - j: "setLineJoin", - M: "setMiterLimit", - d: "setDash", - ri: "setRenderingIntent", - i: "setFlatness", - gs: "setGState", - q: "save", - Q: "restore", - cm: "transform", - - // Path - m: "moveTo", - l: "lineTo", - c: "curveTo", - v: "curveTo2", - y: "curveTo3", - h: "closePath", - re: "rectangle", - S: "stroke", - s: "closeStroke", - f: "fill", - F: "fill", - "f*": "eoFill", - B: "fillStroke", - "B*": "eoFillStroke", - b: "closeFillStroke", - "b*": "closeEOFillStroke", - n: "endPath", - - // Clipping - W: "clip", - "W*": "eoClip", - - // Text - BT: "beginText", - ET: "endText", - Tc: "setCharSpacing", - Tw: "setWordSpacing", - Tz: "setHScale", - TL: "setLeading", - Tf: "setFont", - Tr: "setTextRenderingMode", - Ts: "setTextRise", - Td: "moveText", - TD: "setLeadingMoveText", - Tm: "setTextMatrix", - "T*": "nextLine", - Tj: "showText", - TJ: "showSpacedText", - "'": "nextLineShowText", - '"': "nextLineSetSpacingShowText", - - // Type3 fonts - d0: "setCharWidth", - d1: "setCharWidthAndBounds", - - // Color - CS: "setStrokeColorSpace", - cs: "setFillColorSpace", - SC: "setStrokeColor", - SCN: "setStrokeColorN", - sc: "setFillColor", - scn: "setFillColorN", - G: "setStrokeGray", - g: "setFillGray", - RG: "setStrokeRGBColor", - rg: "setFillRGBColor", - K: "setStrokeCMYKColor", - k: "setFillCMYKColor", - - // Shading - sh: "shadingFill", - - // Images - BI: "beginInlineImage", - - // XObjects - Do: "paintXObject", - - // Marked content - MP: "markPoint", - DP: "markPointProps", - BMC: "beginMarkedContent", - BDC: "beginMarkedContentProps", - EMC: "endMarkedContent", - - // Compatibility - BX: "beginCompat", - EX: "endCompat", - }, - - translateFont: function(fontDict, xref, resources) { - var fd = fontDict.get("FontDescriptor"); - if (!fd) - // XXX deprecated "special treatment" for standard - // fonts? What do we need to do here? - return null; - var descriptor = xref.fetch(fd); - - var fontName = descriptor.get("FontName"); - assertWellFormed(IsName(fontName), "invalid font name"); - fontName = fontName.name.replace("+", "_"); - - var fontFile = descriptor.get3("FontFile", "FontFile2", "FontFile3"); - if (!fontFile) - error("FontFile not found for font: " + fontName); - fontFile = xref.fetchIfRef(fontFile); - - var encodingMap = {}; - var charset = []; - if (fontDict.has("Encoding")) { - var encoding = xref.fetchIfRef(fontDict.get("Encoding")); - if (IsDict(encoding)) { - // Build a map between codes and glyphs - var differences = encoding.get("Differences"); - var index = 0; - for (var j = 0; j < differences.length; j++) { - var data = differences[j]; - IsNum(data) ? index = data : encodingMap[index++] = data; - } - - // Get the font charset if any - var charset = descriptor.get("CharSet"); - if (charset) { - assertWellFormed(IsString(charset), "invalid charset"); - charset = charset.split("/"); - charset.shift(); - } - } else if (IsName(encoding)) { - var encoding = Encodings[encoding.name]; - if (!encoding) - error("Unknown font encoding"); - - var index = 0; - for (var j = 0; j < encoding.length; j++) - encodingMap[index++] = GlyphsUnicode[encoding[j]]; - - var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); - var widths = xref.fetchIfRef(fontDict.get("Widths")); - assertWellFormed(IsArray(widths) && IsInt(firstChar), - "invalid font Widths or FirstChar"); - - for (var j = 0; j < widths.length; j++) { - if (widths[j]) - charset.push(encoding[j + firstChar]); - } - } - } else if (fontDict.has("ToUnicode")) { - encodingMap = {empty: true}; - var cmapObj = xref.fetchIfRef(fontDict.get("ToUnicode")); - if (IsName(cmapObj)) { - error("ToUnicode file cmap translation not implemented"); - } else if (IsStream(cmapObj)) { - var encoding = Encodings["WinAnsiEncoding"]; - var firstChar = xref.fetchIfRef(fontDict.get("FirstChar")); - - var tokens = []; - var token = ""; - - var cmap = cmapObj.getBytes(cmapObj.length); - for (var i =0; i < cmap.length; i++) { - var byte = cmap[i]; - if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { - switch (token) { - case "useCMap": - error("useCMap is not implemented"); - break; - - case "beginbfrange": - case "begincodespacerange": - token = ""; - tokens = []; - break; - - case "endcodespacerange": - TODO("Support CMap ranges"); - break; - - case "endbfrange": - for (var j = 0; j < tokens.length; j+=3) { - var startRange = parseInt("0x" + tokens[j]); - var endRange = parseInt("0x" + tokens[j+1]); - var code = parseInt("0x" + tokens[j+2]); - - for (var k = startRange; k <= endRange; k++) { - // The encoding mapping table will be filled - // later during the building phase - //encodingMap[k] = GlyphsUnicode[encoding[code]]; - charset.push(encoding[code++] || ".notdef"); - } - } - break; - - case "beginfbchar": - case "endfbchar": - error("fbchar parsing is not implemented"); - break; - - default: - if (token.length) { - tokens.push(token); - token = ""; - } - break; - } - } else if (byte == 0x5B || byte == 0x5D) { - error("CMAP list parsing is not implemented"); - } else { - token += String.fromCharCode(byte); - } - } - } - } + var LINE_CAP_STYLES = ['butt', 'round', 'square']; + var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; + + // Used for tiling patterns + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + + constructor.prototype = { + map: { + // Graphics state + w: 'setLineWidth', + J: 'setLineCap', + j: 'setLineJoin', + M: 'setMiterLimit', + d: 'setDash', + ri: 'setRenderingIntent', + i: 'setFlatness', + gs: 'setGState', + q: 'save', + Q: 'restore', + cm: 'transform', + + // Path + m: 'moveTo', + l: 'lineTo', + c: 'curveTo', + v: 'curveTo2', + y: 'curveTo3', + h: 'closePath', + re: 'rectangle', + S: 'stroke', + s: 'closeStroke', + f: 'fill', + F: 'fill', + 'f*': 'eoFill', + B: 'fillStroke', + 'B*': 'eoFillStroke', + b: 'closeFillStroke', + 'b*': 'closeEOFillStroke', + n: 'endPath', + + // Clipping + W: 'clip', + 'W*': 'eoClip', + + // Text + BT: 'beginText', + ET: 'endText', + Tc: 'setCharSpacing', + Tw: 'setWordSpacing', + Tz: 'setHScale', + TL: 'setLeading', + Tf: 'setFont', + Tr: 'setTextRenderingMode', + Ts: 'setTextRise', + Td: 'moveText', + TD: 'setLeadingMoveText', + Tm: 'setTextMatrix', + 'T*': 'nextLine', + Tj: 'showText', + TJ: 'showSpacedText', + "'": 'nextLineShowText', + '"': 'nextLineSetSpacingShowText', + + // Type3 fonts + d0: 'setCharWidth', + d1: 'setCharWidthAndBounds', + + // Color + CS: 'setStrokeColorSpace', + cs: 'setFillColorSpace', + SC: 'setStrokeColor', + SCN: 'setStrokeColorN', + sc: 'setFillColor', + scn: 'setFillColorN', + G: 'setStrokeGray', + g: 'setFillGray', + RG: 'setStrokeRGBColor', + rg: 'setFillRGBColor', + K: 'setStrokeCMYKColor', + k: 'setFillCMYKColor', + + // Shading + sh: 'shadingFill', + + // Images + BI: 'beginInlineImage', + + // XObjects + Do: 'paintXObject', + + // Marked content + MP: 'markPoint', + DP: 'markPointProps', + BMC: 'beginMarkedContent', + BDC: 'beginMarkedContentProps', + EMC: 'endMarkedContent', + + // Compatibility + BX: 'beginCompat', + EX: 'endCompat' + }, + + translateFont: function(fontDict, xref, resources) { + var fd = fontDict.get('FontDescriptor'); + if (!fd) + // XXX deprecated "special treatment" for standard + // fonts? What do we need to do here? + return null; + var descriptor = xref.fetch(fd); + + var fontName = descriptor.get('FontName'); + assertWellFormed(IsName(fontName), 'invalid font name'); + fontName = fontName.name.replace('+', '_'); + + var fontFile = descriptor.get3('FontFile', 'FontFile2', 'FontFile3'); + if (!fontFile) + error('FontFile not found for font: ' + fontName); + fontFile = xref.fetchIfRef(fontFile); + + var encodingMap = {}; + var charset = []; + if (fontDict.has('Encoding')) { + var encoding = xref.fetchIfRef(fontDict.get('Encoding')); + if (IsDict(encoding)) { + // Build a map between codes and glyphs + var differences = encoding.get('Differences'); + var index = 0; + for (var j = 0; j < differences.length; j++) { + var data = differences[j]; + IsNum(data) ? index = data : encodingMap[index++] = data; + } + + // Get the font charset if any + var charset = descriptor.get('CharSet'); + if (charset) { + assertWellFormed(IsString(charset), 'invalid charset'); + charset = charset.split('/'); + charset.shift(); + } + } else if (IsName(encoding)) { + var encoding = Encodings[encoding.name]; + if (!encoding) + error('Unknown font encoding'); + + var index = 0; + for (var j = 0; j < encoding.length; j++) + encodingMap[index++] = GlyphsUnicode[encoding[j]]; + + var firstChar = xref.fetchIfRef(fontDict.get('FirstChar')); + var widths = xref.fetchIfRef(fontDict.get('Widths')); + assertWellFormed(IsArray(widths) && IsInt(firstChar), + 'invalid font Widths or FirstChar'); + + for (var j = 0; j < widths.length; j++) { + if (widths[j]) + charset.push(encoding[j + firstChar]); + } + } + } else if (fontDict.has('ToUnicode')) { + encodingMap = {empty: true}; + var cmapObj = xref.fetchIfRef(fontDict.get('ToUnicode')); + if (IsName(cmapObj)) { + error('ToUnicode file cmap translation not implemented'); + } else if (IsStream(cmapObj)) { + var encoding = Encodings['WinAnsiEncoding']; + var firstChar = xref.fetchIfRef(fontDict.get('FirstChar')); + + var tokens = []; + var token = ''; + + var cmap = cmapObj.getBytes(cmapObj.length); + for (var i = 0; i < cmap.length; i++) { + var byte = cmap[i]; + if (byte == 0x20 || byte == 0x0A || byte == 0x3C || byte == 0x3E) { + switch (token) { + case 'useCMap': + error('useCMap is not implemented'); + break; - var subType = fontDict.get("Subtype"); - assertWellFormed(IsName(subType), "invalid font Subtype"); - - var properties = { - type: subType.name, - encoding: encodingMap, - charset: charset, - firstChar: fontDict.get("FirstChar"), - lastChar: fontDict.get("LastChar"), - bbox: descriptor.get("FontBBox"), - ascent: descriptor.get("Ascent"), - descent: descriptor.get("Descent"), - xHeight: descriptor.get("XHeight"), - capHeight: descriptor.get("CapHeight"), - flags: descriptor.get("Flags"), - italicAngle: descriptor.get("ItalicAngle"), - fixedPitch: false, - textMatrix: IDENTITY_MATRIX - }; - - return { - name: fontName, - file: fontFile, - properties: properties - } - }, - - beginDrawing: function(mediaBox) { - var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; - this.ctx.save(); - this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height); - this.ctx.translate(0, -mediaBox.height); - }, - - execute: function(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(); - - code(this); - - this.xobjs = savedXobjs; - this.res = savedRes; - this.xref = savedXref; - }, - - compile: function(stream, xref, resources, fonts) { - resources = xref.fetchIfRef(resources) || new Dict(); - var xobjs = xref.fetchIfRef(resources.get("XObject")) || new Dict(); - - var parser = new Parser(new Lexer(stream), false); - var objpool = []; - - function emitArg(arg) { - if (typeof arg == "object" || typeof arg == "string") { - var index = objpool.length; - objpool[index] = arg; - return "objpool[" + index + "]"; + case 'beginbfrange': + case 'begincodespacerange': + token = ''; + tokens = []; + break; + + case 'endcodespacerange': + TODO('Support CMap ranges'); + break; + + case 'endbfrange': + for (var j = 0; j < tokens.length; j += 3) { + var startRange = parseInt('0x' + tokens[j]); + var endRange = parseInt('0x' + tokens[j + 1]); + var code = parseInt('0x' + tokens[j + 2]); + + for (var k = startRange; k <= endRange; k++) { + // The encoding mapping table will be filled + // later during the building phase + //encodingMap[k] = GlyphsUnicode[encoding[code]]; + charset.push(encoding[code++] || '.notdef'); + } } - return arg; - } + break; + + case 'beginfbchar': + case 'endfbchar': + error('fbchar parsing is not implemented'); + break; - var src = ""; - - var args = []; - var map = this.map; - var obj; - while (!IsEOF(obj = parser.getObj())) { - if (IsCmd(obj)) { - var cmd = obj.cmd; - var fn = map[cmd]; - assertWellFormed(fn, "Unknown command '" + cmd + "'"); - // TODO figure out how to type-check vararg functions - - if (cmd == "Do" && !args[0].code) { // eagerly compile XForm objects - var name = args[0].name; - var xobj = xobjs.get(name); - if (xobj) { - xobj = xref.fetchIfRef(xobj); - assertWellFormed(IsStream(xobj), "XObject should be a stream"); - - var type = xobj.dict.get("Subtype"); - assertWellFormed(IsName(type), "XObject should have a Name subtype"); - - if ("Form" == type.name) { - args[0].code = this.compile(xobj, - xref, - xobj.dict.get("Resources"), - fonts); - } - } - } else if (cmd == "Tf") { // eagerly collect all fonts - var fontRes = resources.get("Font"); - if (fontRes) { - fontRes = xref.fetchIfRef(fontRes); - var font = xref.fetchIfRef(fontRes.get(args[0].name)); - assertWellFormed(IsDict(font)); - if (!font.translated) { - font.translated = this.translateFont(font, xref, resources); - if (fonts && 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); - } - } - } - } - - src += "this."; - src += fn; - src += "("; - src += args.map(emitArg).join(","); - src += ");\n"; - - args.length = 0; - } else { - assertWellFormed(args.length <= 33, "Too many arguments"); - args.push(obj); + default: + if (token.length) { + tokens.push(token); + token = ''; } + break; + } + } else if (byte == 0x5B || byte == 0x5D) { + error('CMAP list parsing is not implemented'); + } else { + token += String.fromCharCode(byte); } + } + } + } - var fn = Function("objpool", src); - return function (gfx) { fn.call(gfx, objpool); }; - }, - - endDrawing: function() { - this.ctx.restore(); - }, - - // Graphics state - setLineWidth: function(width) { - this.ctx.lineWidth = width; - }, - setLineCap: function(style) { - this.ctx.lineCap = LINE_CAP_STYLES[style]; - }, - setLineJoin: function(style) { - this.ctx.lineJoin = LINE_JOIN_STYLES[style]; - }, - setMiterLimit: function(limit) { - this.ctx.miterLimit = limit; - }, - setDash: function(dashArray, dashPhase) { - this.ctx.mozDash = dashArray; - this.ctx.mozDashOffset = dashPhase; - }, - setRenderingIntent: function(intent) { - TODO("set rendering intent"); - }, - setFlatness: function(flatness) { - TODO("set flatness"); - }, - setGState: function(dictName) { - TODO("set graphics state from dict"); - }, - save: function() { - this.ctx.save(); - if (this.ctx.$saveCurrentX) { - this.ctx.$saveCurrentX(); + var subType = fontDict.get('Subtype'); + assertWellFormed(IsName(subType), 'invalid font Subtype'); + + var properties = { + type: subType.name, + encoding: encodingMap, + charset: charset, + firstChar: fontDict.get('FirstChar'), + lastChar: fontDict.get('LastChar'), + bbox: descriptor.get('FontBBox'), + ascent: descriptor.get('Ascent'), + descent: descriptor.get('Descent'), + xHeight: descriptor.get('XHeight'), + capHeight: descriptor.get('CapHeight'), + flags: descriptor.get('Flags'), + italicAngle: descriptor.get('ItalicAngle'), + fixedPitch: false, + textMatrix: IDENTITY_MATRIX + }; + + return { + name: fontName, + file: fontFile, + properties: properties + }; + }, + + beginDrawing: function(mediaBox) { + var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; + this.ctx.save(); + this.ctx.scale(cw / mediaBox.width, -ch / mediaBox.height); + this.ctx.translate(0, -mediaBox.height); + }, + + execute: function(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(); + + code(this); + + this.xobjs = savedXobjs; + this.res = savedRes; + this.xref = savedXref; + }, + + compile: function(stream, xref, resources, fonts) { + resources = xref.fetchIfRef(resources) || new Dict(); + var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); + + var parser = new Parser(new Lexer(stream), false); + var objpool = []; + + function emitArg(arg) { + if (typeof arg == 'object' || typeof arg == 'string') { + var index = objpool.length; + objpool[index] = arg; + return 'objpool[' + index + ']'; + } + return arg; + } + + var src = ''; + + var args = []; + var map = this.map; + var obj; + while (!IsEOF(obj = parser.getObj())) { + if (IsCmd(obj)) { + var cmd = obj.cmd; + var fn = map[cmd]; + assertWellFormed(fn, "Unknown command '" + cmd + "'"); + // TODO figure out how to type-check vararg functions + + if (cmd == 'Do' && !args[0].code) { // eagerly compile XForm objects + var name = args[0].name; + var xobj = xobjs.get(name); + if (xobj) { + xobj = xref.fetchIfRef(xobj); + assertWellFormed(IsStream(xobj), 'XObject should be a stream'); + + var type = xobj.dict.get('Subtype'); + assertWellFormed( + IsName(type), + 'XObject should have a Name subtype' + ); + + if ('Form' == type.name) { + args[0].code = this.compile(xobj, + xref, + xobj.dict.get('Resources'), + fonts); + } } - this.stateStack.push(this.current); - this.current = new CanvasExtraState(); - }, - restore: function() { - var prev = this.stateStack.pop(); - if (prev) { - if (this.ctx.$restoreCurrentX) { - this.ctx.$restoreCurrentX(); + } else if (cmd == 'Tf') { // eagerly collect all fonts + var fontRes = resources.get('Font'); + if (fontRes) { + fontRes = xref.fetchIfRef(fontRes); + var font = xref.fetchIfRef(fontRes.get(args[0].name)); + assertWellFormed(IsDict(font)); + if (!font.translated) { + font.translated = this.translateFont(font, xref, resources); + if (fonts && 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); } - this.current = prev; - this.ctx.restore(); - } - }, - transform: function(a, b, c, d, e, f) { - this.ctx.transform(a, b, c, d, e, f); - }, - - // Path - moveTo: function(x, y) { - this.ctx.moveTo(x, y); - }, - lineTo: function(x, y) { - this.ctx.lineTo(x, y); - }, - curveTo: function(x1, y1, x2, y2, x3, y3) { - this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); - }, - curveTo2: function(x2, y2, x3, y3) { - TODO("'v' operator: need current point in gfx context"); - }, - curveTo3: function(x1, y1, x3, y3) { - this.curveTo(x1, y1, x3, y3, x3, y3); - }, - closePath: function() { - this.ctx.closePath(); - }, - rectangle: function(x, y, width, height) { - this.ctx.rect(x, y, width, height); - }, - stroke: function() { - this.ctx.stroke(); - this.consumePath(); - }, - closeStroke: function() { - this.closePath(); - this.stroke(); - }, - fill: function() { - this.ctx.fill(); - this.consumePath(); - }, - eoFill: function() { - var savedFillRule = this.setEOFillRule(); - this.fill(); - this.restoreFillRule(savedFillRule); - }, - fillStroke: function() { - this.ctx.fill(); - this.ctx.stroke(); - this.consumePath(); - }, - eoFillStroke: function() { - var savedFillRule = this.setEOFillRule(); - this.fillStroke(); - this.restoreFillRule(savedFillRule); - }, - closeFillStroke: function() { - return this.fillStroke(); - }, - closeEOFillStroke: function() { - var savedFillRule = this.setEOFillRule(); - this.fillStroke(); - this.restoreFillRule(savedFillRule); - }, - endPath: function() { - this.consumePath(); - }, - - // Clipping - clip: function() { - this.pendingClip = NORMAL_CLIP; - }, - eoClip: function() { - this.pendingClip = EO_CLIP; - }, - - // Text - beginText: function() { - this.current.textMatrix = IDENTITY_MATRIX; - if (this.ctx.$setCurrentX) { - this.ctx.$setCurrentX(0) - } - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - }, - endText: function() { - }, - setCharSpacing: function(spacing) { - this.ctx.charSpacing = spacing; - }, - setWordSpacing: function(spacing) { - this.ctx.wordSpacing = spacing; - }, - setHScale: function(scale) { - this.ctx.textHScale = (scale % 100) * 0.01; - }, - setLeading: function(leading) { - this.current.leading = leading; - }, - setFont: function(fontRef, size) { - var font = this.xref.fetchIfRef(this.res.get("Font")); - if (!IsDict(font)) - return; - - font = font.get(fontRef.name); - font = this.xref.fetchIfRef(font); - if (!font) - return; - - var fontName = ""; - var fontDescriptor = font.get("FontDescriptor"); - if (fontDescriptor && fontDescriptor.num) { - var fontDescriptor = this.xref.fetchIfRef(fontDescriptor); - fontName = fontDescriptor.get("FontName").name.replace("+", "_"); + } } + } - if (!fontName) { - // TODO: fontDescriptor is not available, fallback to default font - fontName = "sans-serif"; - } + src += 'this.'; + src += fn; + src += '('; + src += args.map(emitArg).join(','); + src += ');\n'; - this.current.fontName = fontName; - this.current.fontSize = size; + args.length = 0; + } else { + assertWellFormed(args.length <= 33, 'Too many arguments'); + args.push(obj); + } + } - if (this.ctx.$setFont) { - this.ctx.$setFont(fontName, size); - } else { - this.ctx.font = size + 'px "' + fontName + '"'; - Fonts.setActive(fontName, size); - } - }, - setTextRenderingMode: function(mode) { - TODO("text rendering mode"); - }, - setTextRise: function(rise) { - TODO("text rise"); - }, - moveText: function (x, y) { - this.current.x = this.current.lineX += x; - this.current.y = this.current.lineY += y; - if (this.ctx.$setCurrentX) { - this.ctx.$setCurrentX(this.current.x) - } - }, - setLeadingMoveText: function(x, y) { - this.setLeading(-y); - this.moveText(x, y); - }, - setTextMatrix: function(a, b, c, d, e, f) { - this.current.textMatrix = [ a, b, c, d, e, f ]; - - if (this.ctx.$setCurrentX) { - this.ctx.$setCurrentX(0) - } - this.current.x = this.current.lineX = 0; - this.current.y = this.current.lineY = 0; - }, - nextLine: function() { - this.moveText(0, this.current.leading); - }, - showText: function(text) { - // TODO: apply charSpacing, wordSpacing, textHScale - - this.ctx.save(); - this.ctx.transform.apply(this.ctx, this.current.textMatrix); - this.ctx.scale(1, -1); - - if (this.ctx.$showText) { - this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); - } else { - text = Fonts.charsToUnicode(text); - this.ctx.translate(this.current.x, -1 * this.current.y); + var fn = Function('objpool', src); + return function(gfx) { fn.call(gfx, objpool); }; + }, + + endDrawing: function() { + this.ctx.restore(); + }, + + // Graphics state + setLineWidth: function(width) { + this.ctx.lineWidth = width; + }, + setLineCap: function(style) { + this.ctx.lineCap = LINE_CAP_STYLES[style]; + }, + setLineJoin: function(style) { + this.ctx.lineJoin = LINE_JOIN_STYLES[style]; + }, + setMiterLimit: function(limit) { + this.ctx.miterLimit = limit; + }, + setDash: function(dashArray, dashPhase) { + this.ctx.mozDash = dashArray; + this.ctx.mozDashOffset = dashPhase; + }, + setRenderingIntent: function(intent) { + TODO('set rendering intent'); + }, + setFlatness: function(flatness) { + TODO('set flatness'); + }, + setGState: function(dictName) { + TODO('set graphics state from dict'); + }, + save: function() { + this.ctx.save(); + if (this.ctx.$saveCurrentX) { + this.ctx.$saveCurrentX(); + } + this.stateStack.push(this.current); + this.current = new CanvasExtraState(); + }, + restore: function() { + var prev = this.stateStack.pop(); + if (prev) { + if (this.ctx.$restoreCurrentX) { + this.ctx.$restoreCurrentX(); + } + this.current = prev; + this.ctx.restore(); + } + }, + transform: function(a, b, c, d, e, f) { + this.ctx.transform(a, b, c, d, e, f); + }, - ++ + // Path + moveTo: function(x, y) { + this.ctx.moveTo(x, y); + }, + lineTo: function(x, y) { + this.ctx.lineTo(x, y); + }, + curveTo: function(x1, y1, x2, y2, x3, y3) { + this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + }, + curveTo2: function(x2, y2, x3, y3) { + TODO("'v' operator: need current point in gfx context"); + }, + curveTo3: function(x1, y1, x3, y3) { + this.curveTo(x1, y1, x3, y3, x3, y3); + }, + closePath: function() { + this.ctx.closePath(); + }, + rectangle: function(x, y, width, height) { + this.ctx.rect(x, y, width, height); + }, + stroke: function() { + this.ctx.stroke(); + this.consumePath(); + }, + closeStroke: function() { + this.closePath(); + this.stroke(); + }, + fill: function() { + this.ctx.fill(); + this.consumePath(); + }, + eoFill: function() { + var savedFillRule = this.setEOFillRule(); + this.fill(); + this.restoreFillRule(savedFillRule); + }, + fillStroke: function() { + this.ctx.fill(); + this.ctx.stroke(); + this.consumePath(); + }, + eoFillStroke: function() { + var savedFillRule = this.setEOFillRule(); + this.fillStroke(); + this.restoreFillRule(savedFillRule); + }, + closeFillStroke: function() { + return this.fillStroke(); + }, + closeEOFillStroke: function() { + var savedFillRule = this.setEOFillRule(); + this.fillStroke(); + this.restoreFillRule(savedFillRule); + }, + endPath: function() { + this.consumePath(); + }, - ++ + // Clipping + clip: function() { + this.pendingClip = NORMAL_CLIP; + }, + eoClip: function() { + this.pendingClip = EO_CLIP; + }, - ++ + // Text + beginText: function() { + this.current.textMatrix = IDENTITY_MATRIX; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(0); + } + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + endText: function() { + }, + setCharSpacing: function(spacing) { + this.ctx.charSpacing = spacing; + }, + setWordSpacing: function(spacing) { + this.ctx.wordSpacing = spacing; + }, + setHScale: function(scale) { + this.ctx.textHScale = (scale % 100) * 0.01; + }, + setLeading: function(leading) { + this.current.leading = leading; + }, + setFont: function(fontRef, size) { - var font = this.res.get('Font'); ++ var font = this.xref.fetchIfRef(this.res.get("Font")); + if (!IsDict(font)) + return; - ++ + font = font.get(fontRef.name); + font = this.xref.fetchIfRef(font); + if (!font) + return; - - var fontName = ''; - var fontDescriptor = font.get('FontDescriptor'); ++ ++ var fontName = ""; ++ var fontDescriptor = font.get("FontDescriptor"); + if (fontDescriptor && fontDescriptor.num) { + var fontDescriptor = this.xref.fetchIfRef(fontDescriptor); - fontName = fontDescriptor.get('FontName').name.replace('+', '_'); ++ fontName = fontDescriptor.get("FontName").name.replace("+", "_"); + } - ++ + if (!fontName) { + // TODO: fontDescriptor is not available, fallback to default font + fontName = 'sans-serif'; + } - ++ + this.current.fontName = fontName; + this.current.fontSize = size; + + if (this.ctx.$setFont) { + this.ctx.$setFont(fontName, size); + } else { + this.ctx.font = size + 'px "' + fontName + '"'; + Fonts.setActive(fontName, size); + } + }, + setTextRenderingMode: function(mode) { + TODO('text rendering mode'); + }, + setTextRise: function(rise) { + TODO('text rise'); + }, + moveText: function(x, y) { + this.current.x = this.current.lineX += x; + this.current.y = this.current.lineY += y; + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(this.current.x); + } + }, + setLeadingMoveText: function(x, y) { + this.setLeading(-y); + this.moveText(x, y); + }, + setTextMatrix: function(a, b, c, d, e, f) { + this.current.textMatrix = [a, b, c, d, e, f]; + + if (this.ctx.$setCurrentX) { + this.ctx.$setCurrentX(0); + } + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + nextLine: function() { + this.moveText(0, this.current.leading); + }, + showText: function(text) { + // TODO: apply charSpacing, wordSpacing, textHScale + + this.ctx.save(); + this.ctx.transform.apply(this.ctx, this.current.textMatrix); + this.ctx.scale(1, -1); + + if (this.ctx.$showText) { + this.ctx.$showText(this.current.y, Fonts.charsToUnicode(text)); + } else { + text = Fonts.charsToUnicode(text); + this.ctx.translate(this.current.x, -1 * this.current.y); + + var font = Fonts.lookup(this.current.fontName); + if (font && font.properties.textMatrix) + this.ctx.transform.apply(this.ctx, font.properties.textMatrix); + + this.ctx.fillText(text, 0, 0); + this.current.x += Fonts.measureText(text); + } - var font = Fonts.lookup(this.current.fontName); - if (font && font.properties.textMatrix) - this.ctx.transform.apply(this.ctx, font.properties.textMatrix); + this.ctx.restore(); + }, + showSpacedText: function(arr) { + for (var i = 0; i < arr.length; ++i) { + var e = arr[i]; + if (IsNum(e)) { + if (this.ctx.$addCurrentX) { + this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize); + } else { + this.current.x -= e * 0.001 * this.current.fontSize; + } + } else if (IsString(e)) { + this.showText(e); + } else { + malformed('TJ array element ' + e + " isn't string or num"); + } + } + }, + nextLineShowText: function(text) { + this.nextLine(); + this.showText(text); + }, + nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) { + this.setWordSpacing(wordSpacing); + this.setCharSpacing(charSpacing); + this.nextLineShowText(text); + }, + + // Type3 fonts + setCharWidth: function(xWidth, yWidth) { + TODO("type 3 fonts ('d0' operator)"); + }, + setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) { + TODO("type 3 fonts ('d1' operator)"); + }, + + // Color + setStrokeColorSpace: function(space) { + this.current.strokeColorSpace = + ColorSpace.parse(space, this.xref, this.res); + }, + setFillColorSpace: function(space) { + this.current.fillColorSpace = + ColorSpace.parse(space, this.xref, this.res); + }, + setStrokeColor: function(/*...*/) { + var cs = this.getStrokeColorSpace(); + var color = cs.getRgb(arguments); + this.setStrokeRGBColor.apply(this, color); + }, + setStrokeColorN: function(/*...*/) { + // TODO real impl + TODO('check for special color spaces'); + this.setStrokeColor.apply(this, arguments); + }, + setFillColor: function(/*...*/) { + var cs = this.getFillColorSpace(); + if (cs.name == 'Pattern') { + TODO('implement Pattern fill'); + return; + } + var color = cs.getRgb(arguments); + this.setFillRGBColor.apply(this, color); + }, + setFillColorN: function(/*...*/) { + var cs = this.getFillColorSpace(); + + if (cs.name == 'Pattern') { + var patternName = arguments[0]; + if (IsName(patternName)) { + var xref = this.xref; + var patternRes = xref.fetchIfRef(this.res.get('Pattern')); + if (!patternRes) + error('Unable to find pattern resource'); + + var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); + var patternDict = IsStream(pattern) ? pattern.dict : pattern; + var types = [null, this.tilingFill, + function() { TODO('Shading Patterns'); }]; + var typeNum = patternDict.get('PatternType'); + var patternFn = types[typeNum]; + if (!patternFn) + error('Unhandled pattern type'); + patternFn.call(this, pattern, patternDict); + } + } else { + // TODO real impl + this.setFillColor.apply(this, arguments); + } + }, + tilingFill: function(pattern) { + function applyMatrix(point, m) { + var x = point[0] * m[0] + point[1] * m[2] + m[4]; + var y = point[0] * m[1] + point[1] * m[3] + m[5]; + return [x, y]; + }; + + 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]; + }; + + this.save(); + var dict = pattern.dict; + var ctx = this.ctx; + + var paintType = dict.get('PaintType'); + switch (paintType) { + case PAINT_TYPE_COLORED: + // should go to default for color space + ctx.fillStyle = this.makeCssRgb(1, 1, 1); + ctx.strokeStyle = this.makeCssRgb(0, 0, 0); + break; + case PAINT_TYPE_UNCOLORED: + default: + error('Unsupported paint type'); + } - this.ctx.fillText(text, 0, 0); - this.current.x += Fonts.measureText(text); - } + TODO('TilingType'); - this.ctx.restore(); - }, - showSpacedText: function(arr) { - for (var i = 0; i < arr.length; ++i) { - var e = arr[i]; - if (IsNum(e)) { - if (this.ctx.$addCurrentX) { - this.ctx.$addCurrentX(-e * 0.001 * this.current.fontSize) - } else { - this.current.x -= e * 0.001 * this.current.fontSize; - } - } else if (IsString(e)) { - this.showText(e); - } else { - malformed("TJ array element "+ e +" isn't string or num"); - } - } - }, - nextLineShowText: function(text) { - this.nextLine(); - this.showText(text); - }, - nextLineSetSpacingShowText: function(wordSpacing, charSpacing, text) { - this.setWordSpacing(wordSpacing); - this.setCharSpacing(charSpacing); - this.nextLineShowText(text); - }, - - // Type3 fonts - setCharWidth: function(xWidth, yWidth) { - TODO("type 3 fonts ('d0' operator)"); - }, - setCharWidthAndBounds: function(xWidth, yWidth, llx, lly, urx, ury) { - TODO("type 3 fonts ('d1' operator)"); - }, - - // Color - setStrokeColorSpace: function(space) { - this.current.strokeColorSpace = - ColorSpace.parse(space, this.xref, this.res); - }, - setFillColorSpace: function(space) { - this.current.fillColorSpace = - ColorSpace.parse(space, this.xref, this.res); - }, - setStrokeColor: function(/*...*/) { - var cs = this.getStrokeColorSpace(); - var color = cs.getRgb(arguments); - this.setStrokeRGBColor.apply(this, color); - }, - setStrokeColorN: function(/*...*/) { - // TODO real impl - TODO("check for special color spaces"); - this.setStrokeColor.apply(this, arguments); - }, - setFillColor: function(/*...*/) { - var cs = this.getFillColorSpace(); - if (cs.name == "Pattern") { - TODO("implement Pattern fill"); - return; - } - var color = cs.getRgb(arguments); - this.setFillRGBColor.apply(this, color); - }, - setFillColorN: function(/*...*/) { - var cs = this.getFillColorSpace(); - - if (cs.name == "Pattern") { - var patternName = arguments[0]; - if (IsName(patternName)) { - var xref = this.xref; - var patternRes = xref.fetchIfRef(this.res.get("Pattern")); - if (!patternRes) - error("Unable to find pattern resource"); - - var pattern = xref.fetchIfRef(patternRes.get(patternName.name)); - var patternDict = IsStream(pattern) ? pattern.dict : pattern; - var types = [null, this.tilingFill, - function() { TODO("Shading Patterns"); }]; - var typeNum = patternDict.get("PatternType"); - var patternFn = types[typeNum]; - if (!patternFn) - error("Unhandled pattern type"); - patternFn.call(this, pattern, patternDict); - } - } else { - // TODO real impl - this.setFillColor.apply(this, arguments); - } - }, - tilingFill: function(pattern) { - function applyMatrix(point, m) { - var x = point[0] * m[0] + point[1] * m[2] + m[4]; - var y = point[0] * m[1] + point[1] * m[3] + m[5]; - return [x,y]; - }; - - 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] - }; - - this.save(); - var dict = pattern.dict; - var ctx = this.ctx; - - var paintType = dict.get("PaintType"); - switch (paintType) { - case PAINT_TYPE_COLORED: - // should go to default for color space - ctx.fillStyle = this.makeCssRgb(1, 1, 1); - ctx.strokeStyle = this.makeCssRgb(0, 0, 0); - break; - case PAINT_TYPE_UNCOLORED: - default: - error("Unsupported paint type"); - } + var matrix = dict.get('Matrix') || IDENTITY_MATRIX; - TODO("TilingType"); + var bbox = dict.get('BBox'); + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; - var matrix = dict.get("Matrix") || IDENTITY_MATRIX; + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); - var bbox = dict.get("BBox"); - var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + // top left corner should correspond to the top left of the bbox + var topLeft = applyMatrix([x0, y0], matrix); + // we want the canvas to be as large as the step size + var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); - var xstep = dict.get("XStep"); - var ystep = dict.get("YStep"); + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; - // top left corner should correspond to the top left of the bbox - var topLeft = applyMatrix([x0,y0], matrix); - // we want the canvas to be as large as the step size - var botRight = applyMatrix([x0 + xstep, y0 + ystep], matrix); + // TODO: hack to avoid OOM, remove then pattern code is fixed + if (Math.abs(width) > 8192 || Math.abs(height) > 8192) + return false; - var width = botRight[0] - topLeft[0]; - var height = botRight[1] - topLeft[1]; + var tmpCanvas = new this.ScratchCanvas(width, height); - // TODO: hack to avoid OOM, remove then pattern code is fixed - if (Math.abs(width) > 8192 || Math.abs(height) > 8192) - return false; + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext('2d'); + var savedCtx = ctx; + this.ctx = tmpCtx; - var tmpCanvas = new this.ScratchCanvas(width, height); + // normalize transform matrix so each step + // takes up the entire tmpCanvas (need to remove white borders) + if (matrix[1] === 0 && matrix[2] === 0) { + matrix[0] = tmpCanvas.width / xstep; + matrix[3] = tmpCanvas.height / ystep; + topLeft = applyMatrix([x0, y0], matrix); + } - // set the new canvas element context as the graphics context - var tmpCtx = tmpCanvas.getContext("2d"); - var savedCtx = ctx; - this.ctx = tmpCtx; + // move the top left corner of bounding box to [0,0] + matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); - // normalize transform matrix so each step - // takes up the entire tmpCanvas (need to remove white borders) - if (matrix[1] === 0 && matrix[2] === 0) { - matrix[0] = tmpCanvas.width / xstep; - matrix[3] = tmpCanvas.height / ystep; - topLeft = applyMatrix([x0,y0], matrix); - } + this.transform.apply(this, matrix); - // move the top left corner of bounding box to [0,0] - matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } - this.transform.apply(this, matrix); + var xref = this.xref; + var res = xref.fetchIfRef(dict.get('Resources')); + if (!pattern.code) + pattern.code = this.compile(pattern, xref, res, []); + this.execute(pattern.code, xref, res); + + this.ctx = savedCtx; + this.restore(); + + TODO('Inverse pattern is painted'); + pattern = this.ctx.createPattern(tmpCanvas, 'repeat'); + this.ctx.fillStyle = pattern; + }, + setStrokeGray: function(gray) { + this.setStrokeRGBColor(gray, gray, gray); + }, + setFillGray: function(gray) { + this.setFillRGBColor(gray, gray, gray); + }, + setStrokeRGBColor: function(r, g, b) { + this.ctx.strokeStyle = this.makeCssRgb(r, g, b); + }, + setFillRGBColor: function(r, g, b) { + this.ctx.fillStyle = this.makeCssRgb(r, g, b); + }, + setStrokeCMYKColor: function(c, m, y, k) { + this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k); + }, + setFillCMYKColor: function(c, m, y, k) { + this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k); + }, + + // Shading + shadingFill: function(entryRef) { + var xref = this.xref; + var res = this.res; + + var shadingRes = xref.fetchIfRef(res.get('Shading')); + if (!shadingRes) + error('No shading resource found'); + + var shading = xref.fetchIfRef(shadingRes.get(entryRef.name)); + if (!shading) + error('No shading object found'); + + this.save(); + + var bbox = shading.get('BBox'); + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } + var cs = shading.get2('ColorSpace', 'CS'); + TODO('shading-fill color space'); - var xref = this.xref; - var res = xref.fetchIfRef(dict.get("Resources")); - if (!pattern.code) - pattern.code = this.compile(pattern, xref, res, []); - this.execute(pattern.code, xref, res); - - this.ctx = savedCtx; - this.restore(); - - TODO("Inverse pattern is painted"); - pattern = this.ctx.createPattern(tmpCanvas, "repeat"); - this.ctx.fillStyle = pattern; - }, - setStrokeGray: function(gray) { - this.setStrokeRGBColor(gray, gray, gray); - }, - setFillGray: function(gray) { - this.setFillRGBColor(gray, gray, gray); - }, - setStrokeRGBColor: function(r, g, b) { - this.ctx.strokeStyle = this.makeCssRgb(r, g, b); - }, - setFillRGBColor: function(r, g, b) { - this.ctx.fillStyle = this.makeCssRgb(r, g, b); - }, - setStrokeCMYKColor: function(c, m, y, k) { - this.ctx.strokeStyle = this.makeCssCmyk(c, m, y, k); - }, - setFillCMYKColor: function(c, m, y, k) { - this.ctx.fillStyle = this.makeCssCmyk(c, m, y, k); - }, - - // Shading - shadingFill: function(entryRef) { - var xref = this.xref; - var res = this.res; - - var shadingRes = xref.fetchIfRef(res.get("Shading")); - if (!shadingRes) - error("No shading resource found"); - - var shading = xref.fetchIfRef(shadingRes.get(entryRef.name)); - if (!shading) - error("No shading object found"); - - this.save(); - - var bbox = shading.get("BBox"); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } + var background = shading.get('Background'); + if (background) + TODO('handle background colors'); - var cs = shading.get2("ColorSpace", "CS"); - TODO("shading-fill color space"); + var types = [null, + this.fillFunctionShading, + this.fillAxialShading, + this.fillRadialShading]; - var background = shading.get("Background"); - if (background) - TODO("handle background colors"); + var typeNum = shading.get('ShadingType'); + var fillFn = types[typeNum]; + if (!fillFn) + error("Unknown or NYI type of shading '" + typeNum + "'"); + fillFn.apply(this, [shading]); - var types = [null, - this.fillFunctionShading, - this.fillAxialShading, - this.fillRadialShading]; + this.restore(); + }, - var typeNum = shading.get("ShadingType"); - var fillFn = types[typeNum]; - if (!fillFn) - error("Unknown or NYI type of shading '"+ typeNum +"'"); - fillFn.apply(this, [shading]); + fillAxialShading: function(sh) { + var coordsArr = sh.get('Coords'); + var x0 = coordsArr[0], y0 = coordsArr[1], + x1 = coordsArr[2], y1 = coordsArr[3]; - this.restore(); - }, + var t0 = 0.0, t1 = 1.0; + if (sh.has('Domain')) { + var domainArr = sh.get('Domain'); + t0 = domainArr[0], t1 = domainArr[1]; + } - fillAxialShading: function(sh) { - var coordsArr = sh.get("Coords"); - var x0 = coordsArr[0], y0 = coordsArr[1], - x1 = coordsArr[2], y1 = coordsArr[3]; + var extendStart = false, extendEnd = false; + if (sh.has('Extend')) { + var extendArr = sh.get('Extend'); + extendStart = extendArr[0], extendEnd = extendArr[1]; + TODO('Support extend'); + } + var fnObj = sh.get('Function'); + fnObj = this.xref.fetchIfRef(fnObj); + if (IsArray(fnObj)) + error('No support for array of functions'); + else if (!IsPDFFunction(fnObj)) + error('Invalid function'); + var fn = new PDFFunction(this.xref, fnObj); + + var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); + + // 10 samples seems good enough for now, but probably won't work + // if there are sharp color changes. Ideally, we would implement + // the spec faithfully and add lossless optimizations. + var step = (t1 - t0) / 10; + + for (var i = t0; i <= t1; i += step) { + var c = fn.func([i]); + gradient.addColorStop(i, this.makeCssRgb.apply(this, c)); + } - var t0 = 0.0, t1 = 1.0; - if (sh.has("Domain")) { - var domainArr = sh.get("Domain"); - t0 = domainArr[0], t1 = domainArr[1]; - } + this.ctx.fillStyle = gradient; + + // HACK to draw the gradient onto an infinite rectangle. + // PDF gradients are drawn across the entire image while + // Canvas only allows gradients to be drawn in a rectangle + // The following bug should allow us to remove this. + // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + }, + + fillRadialShading: function(sh) { + TODO('radial shading'); + }, + + // Images + beginInlineImage: function() { + TODO('inline images'); + error('(Stream will not be parsed properly, bailing now)'); + // Like an inline stream: + // - key/value pairs up to Cmd(ID) + // - then image data up to Cmd(EI) + }, + + // XObjects + paintXObject: function(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 extendStart = false, extendEnd = false; - if (sh.has("Extend")) { - var extendArr = sh.get("Extend"); - extendStart = extendArr[0], extendEnd = extendArr[1]; - TODO("Support extend"); - } - var fnObj = sh.get("Function"); - fnObj = this.xref.fetchIfRef(fnObj); - if (IsArray(fnObj)) - error("No support for array of functions"); - else if (!IsPDFFunction(fnObj)) - error("Invalid function"); - var fn = new PDFFunction(this.xref, fnObj); - - var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); - - // 10 samples seems good enough for now, but probably won't work - // if there are sharp color changes. Ideally, we would implement - // the spec faithfully and add lossless optimizations. - var step = (t1 - t0) / 10; - - for (var i = t0; i <= t1; i += step) { - var c = fn.func([i]); - gradient.addColorStop(i, this.makeCssRgb.apply(this, c)); - } + var oc = xobj.dict.get('OC'); + if (oc) { + TODO('oc for xobject'); + } - this.ctx.fillStyle = gradient; - - // HACK to draw the gradient onto an infinite rectangle. - // PDF gradients are drawn across the entire image while - // Canvas only allows gradients to be drawn in a rectangle - // The following bug should allow us to remove this. - // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 - this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); - }, - - fillRadialShading: function(sh) { - TODO("radial shading"); - }, - - // Images - beginInlineImage: function() { - TODO("inline images"); - error("(Stream will not be parsed properly, bailing now)"); - // Like an inline stream: - // - key/value pairs up to Cmd(ID) - // - then image data up to Cmd(EI) - }, - - // XObjects - paintXObject: function(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 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); + } + }, - 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(ref, stream) { + this.save(); - paintFormXObject: function(ref, stream) { - this.save(); + var matrix = stream.dict.get('Matrix'); + if (matrix && IsArray(matrix) && 6 == matrix.length) + this.transform.apply(this, matrix); - var matrix = stream.dict.get("Matrix"); - if (matrix && IsArray(matrix) && 6 == matrix.length) - this.transform.apply(this, matrix); + var bbox = stream.dict.get('BBox'); + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } - var bbox = stream.dict.get("BBox"); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } + this.execute(ref.code, this.xref, stream.dict.get('Resources')); + + this.restore(); + }, + + paintImageXObject: function(ref, image, inline) { + this.save(); + + var ctx = this.ctx; + var dict = image.dict; + var w = dict.get2('Width', 'W'); + var h = dict.get2('Height', 'H'); + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + // If the platform can render the image format directly, the + // stream has a getImage property which directly returns a + // suitable DOM Image object. + if (image.getImage) { + var domImage = image.getImage(); + ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, -h, w, h); + this.restore(); + return; + } - this.execute(ref.code, this.xref, stream.dict.get("Resources")); - - this.restore(); - }, - - paintImageXObject: function(ref, image, inline) { - this.save(); - - var ctx = this.ctx; - var dict = image.dict; - var w = dict.get2("Width", "W"); - var h = dict.get2("Height", "H"); - // scale the image to the unit square - ctx.scale(1/w, -1/h); - - // If the platform can render the image format directly, the - // stream has a getImage property which directly returns a - // suitable DOM Image object. - if (image.getImage) { - var domImage = image.getImage(); - ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, - 0, -h, w, h); - this.restore(); - return; - } + var imageObj = new PDFImage(this.xref, this.res, image, inline); + + var tmpCanvas = new this.ScratchCanvas(w, h); + var tmpCtx = tmpCanvas.getContext('2d'); + var imgData = tmpCtx.getImageData(0, 0, w, h); + var pixels = imgData.data; + + imageObj.fillRgbaBuffer(pixels); + + tmpCtx.putImageData(imgData, 0, 0); + ctx.drawImage(tmpCanvas, 0, -h); + this.restore(); + }, + + // Marked content + + markPoint: function(tag) { + TODO('Marked content'); + }, + markPointProps: function(tag, properties) { + TODO('Marked content'); + }, + beginMarkedContent: function(tag) { + TODO('Marked content'); + }, + beginMarkedContentProps: function(tag, properties) { + TODO('Marked content'); + }, + endMarkedContent: function() { + TODO('Marked content'); + }, + + // Compatibility + + beginCompat: function() { + TODO('ignore undefined operators (should we do that anyway?)'); + }, + endCompat: function() { + TODO('stop ignoring undefined operators'); + }, + + // Helper functions + + consumePath: function() { + if (this.pendingClip) { + var savedFillRule = null; + if (this.pendingClip == EO_CLIP) + savedFillRule = this.setEOFillRule(); + + this.ctx.clip(); - var imageObj = new PDFImage(this.xref, this.res, image, inline); - - var tmpCanvas = new this.ScratchCanvas(w, h); - var tmpCtx = tmpCanvas.getContext("2d"); - var imgData = tmpCtx.getImageData(0, 0, w, h); - var pixels = imgData.data; - - imageObj.fillRgbaBuffer(pixels); - - tmpCtx.putImageData(imgData, 0, 0); - ctx.drawImage(tmpCanvas, 0, -h); - this.restore(); - }, - - // Marked content - - markPoint: function(tag) { - TODO("Marked content"); - }, - markPointProps: function(tag, properties) { - TODO("Marked content"); - }, - beginMarkedContent: function(tag) { - TODO("Marked content"); - }, - beginMarkedContentProps: function(tag, properties) { - TODO("Marked content"); - }, - endMarkedContent: function() { - TODO("Marked content"); - }, - - // Compatibility - - beginCompat: function() { - TODO("ignore undefined operators (should we do that anyway?)"); - }, - endCompat: function() { - TODO("stop ignoring undefined operators"); - }, - - // Helper functions - - consumePath: function() { - if (this.pendingClip) { - var savedFillRule = null; - if (this.pendingClip == EO_CLIP) - savedFillRule = this.setEOFillRule(); - - this.ctx.clip(); - - this.pendingClip = null; - if (savedFillRule !== null) - this.restoreFillRule(savedFillRule); - } - this.ctx.beginPath(); - }, - makeCssRgb: function(r, g, b) { - var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; - return "rgb("+ ri +","+ gi +","+ bi +")"; - }, - makeCssCmyk: function(c, m, y, k) { - // while waiting on CSS's cmyk()... http://www.ilkeratalay.com/colorspacesfaq.php#rgb - var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0; - var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0; - var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0; - return "rgb("+ ri +","+ gi +","+ bi +")"; - }, - getFillColorSpace: function() { - var cs = this.current.fillColorSpace; - if (cs) - return cs; - - var states = this.stateStack; - var i = states.length - 1; - while (i >= 0 && !(cs = states[i].fillColorSpace)) - --i; - - if (cs) - return cs; - else - return new DeviceRgbCS(); - }, - getStrokeColorSpace: function() { - var cs = this.current.strokeColorSpace; - if (cs) - return cs; - - var states = this.stateStack; - var i = states.length - 1; - while (i >= 0 && !(cs = states[i].strokeColorSpace)) - --i; - - if (cs) - return cs; - else - return new DeviceRgbCS(); - }, - // We generally keep the canvas context set for - // nonzero-winding, and just set evenodd for the operations - // that need them. - setEOFillRule: function() { - var savedFillRule = this.ctx.mozFillRule; - this.ctx.mozFillRule = "evenodd"; - return savedFillRule; - }, - restoreFillRule: function(rule) { - this.ctx.mozFillRule = rule; - } - }; + this.pendingClip = null; + if (savedFillRule !== null) + this.restoreFillRule(savedFillRule); + } + this.ctx.beginPath(); + }, + makeCssRgb: function(r, g, b) { + var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; + return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; + }, + makeCssCmyk: function(c, m, y, k) { + // while waiting on CSS's cmyk()... + // http://www.ilkeratalay.com/colorspacesfaq.php#rgb + var ri = (255 * (1 - Math.min(1, c * (1 - k) + k))) | 0; + var gi = (255 * (1 - Math.min(1, m * (1 - k) + k))) | 0; + var bi = (255 * (1 - Math.min(1, y * (1 - k) + k))) | 0; + return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; + }, + getFillColorSpace: function() { + var cs = this.current.fillColorSpace; + if (cs) + return cs; + + var states = this.stateStack; + var i = states.length - 1; + while (i >= 0 && !(cs = states[i].fillColorSpace)) + --i; + + if (cs) + return cs; + else + return new DeviceRgbCS(); + }, + getStrokeColorSpace: function() { + var cs = this.current.strokeColorSpace; + if (cs) + return cs; + + var states = this.stateStack; + var i = states.length - 1; + while (i >= 0 && !(cs = states[i].strokeColorSpace)) + --i; + + if (cs) + return cs; + else + return new DeviceRgbCS(); + }, + // We generally keep the canvas context set for + // nonzero-winding, and just set evenodd for the operations + // that need them. + setEOFillRule: function() { + var savedFillRule = this.ctx.mozFillRule; + this.ctx.mozFillRule = 'evenodd'; + return savedFillRule; + }, + restoreFillRule: function(rule) { + this.ctx.mozFillRule = rule; + } + }; - return constructor; + return constructor; })(); var ColorSpace = (function() { diff --cc worker/client.js index 7e81405,359c69f..a00eab5 --- a/worker/client.js +++ b/worker/client.js @@@ -283,11 -282,10 +283,11 @@@ function WorkerPDFDoc(canvas) var base64 = window.btoa(data.raw); // Add the @font-face rule to the document - var url = "url(data:" + data.mimetype + ";base64," + base64 + ");"; - var rule = "@font-face { font-family:'" + data.fontName + "';src:" + url + "}"; + var url = 'url(data:' + data.mimetype + ';base64,' + base64 + ');'; + var rule = ("@font-face { font-family:'" + data.fontName + + "';src:" + url + '}'); var styleSheet = document.styleSheets[0]; - styleSheet.insertRule(rule, styleSheet.length); + styleSheet.insertRule(rule, styleSheet.cssRules.length); // Just adding the font-face to the DOM doesn't make it load. It // seems it's loaded once Gecko notices it's used. Therefore,