From: sbarman Date: Wed, 6 Jul 2011 17:36:49 +0000 (-0700) Subject: Merge branch 'master' into patterncs X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=9d182ec9ef9d686df436d3bb07ca0d034fdf36de;p=pdf.js.git Merge branch 'master' into patterncs Conflicts: pdf.js --- 9d182ec9ef9d686df436d3bb07ca0d034fdf36de diff --cc pdf.js index 4033330,7d98e00..14f8017 --- a/pdf.js +++ b/pdf.js @@@ -3294,1154 -3346,1089 +3346,1157 @@@ 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) { - 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 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 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 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.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.xref.fetchIfRef(this.res.get('Font')); + if (!IsDict(font)) + return; - var font = Fonts.lookup(this.current.fontName); - if (font && font.properties.textMatrix) - this.ctx.transform.apply(this.ctx, font.properties.textMatrix); + font = font.get(fontRef.name); + font = this.xref.fetchIfRef(font); + if (!font) + return; - this.ctx.fillText(text, 0, 0); - this.current.x += Fonts.measureText(text); - } + var fontName = ''; + var fontDescriptor = font.get('FontDescriptor'); + if (fontDescriptor && fontDescriptor.num) { + var fontDescriptor = this.xref.fetchIfRef(fontDescriptor); + fontName = fontDescriptor.get('FontName').name.replace('+', '_'); + } - 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(); - var color = cs.getRgb(arguments); - this.setFillRGBColor.apply(this, color); - }, - setFillColorN: function(/*...*/) { - var cs = this.getFillColorSpace(); - - if (cs.name == "Pattern") { - var patternName = arguments[0]; - this.setFillPattern(patternName); - } else { - // TODO real impl - this.setFillColor.apply(this, arguments); - } - }, - setFillPattern: function(patternName) { - if (!IsName(patternName)) - error("Bad args to getPattern"); - - 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 dict = IsStream(pattern) ? pattern.dict : pattern; - - var types = [null, this.setTilingPattern, - this.setShadingPattern]; - - var typeNum = dict.get("PatternType"); - var patternFn = types[typeNum]; - if (!patternFn) - error("Unhandled pattern type"); - patternFn.call(this, pattern, dict); - }, - setShadingPattern: function(pattern, dict) { - var matrix = dict.get("Matrix"); - - var inv = [0,0,0,0,0,0]; - var det = 1 / (matrix[0] * matrix[3] - matrix[1] * matrix[2]); - inv[0] = matrix[3] * det; - inv[1] = -matrix[1] * det; - inv[2] = -matrix[2] * det; - inv[3] = matrix[0] * det; - inv[4] = det * (matrix[2] * matrix[5] - matrix[3] * matrix[4]); - inv[5] = det * (matrix[1] * matrix[4] - matrix[0] * matrix[5]); - - this.transform.apply(this, matrix); - var shading = this.getShading(pattern.get("Shading")); - this.restore(); - this.ctx.fillStyle = shading; - - // HACK to get the gradient to show at the right location. If - // removed, the gradient will show at the pre-transform coordinates. - this.ctx.fillRect(0,0,0,0); - this.transform.apply(this, inv); - }, - setTilingPattern: function(pattern, dict) { - 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 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"); - } + if (!fontName) { + // TODO: fontDescriptor is not available, fallback to default font + fontName = 'sans-serif'; + } - TODO("TilingType"); + this.current.fontName = fontName; + this.current.fontSize = size; - var matrix = dict.get("Matrix") || IDENTITY_MATRIX; + 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 bbox = dict.get("BBox"); - var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + 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); ++ this.current.strokeColorSpace = ++ ColorSpace.parse(space, this.xref, this.res); + }, + setFillColorSpace: function(space) { - this.current.fillColorSpace = - ColorSpace.parse(space, this.xref, this.res); ++ 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') { ++ 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); - } ++ this.setFillPattern(patternName); + } else { + // TODO real impl + this.setFillColor.apply(this, arguments); + } + }, - tilingFill: function(pattern) { ++ setFillPattern: function(patternName) { ++ if (!IsName(patternName)) ++ error("Bad args to getPattern"); ++ ++ 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 dict = IsStream(pattern) ? pattern.dict : pattern; ++ ++ var types = [null, this.setTilingPattern, ++ this.setShadingPattern]; ++ ++ var typeNum = dict.get("PatternType"); ++ var patternFn = types[typeNum]; ++ if (!patternFn) ++ error("Unhandled pattern type"); ++ patternFn.call(this, pattern, dict); ++ }, ++ setShadingPattern: function(pattern, dict) { ++ var matrix = dict.get("Matrix"); ++ ++ var inv = [0,0,0,0,0,0]; ++ var det = 1 / (matrix[0] * matrix[3] - matrix[1] * matrix[2]); ++ inv[0] = matrix[3] * det; ++ inv[1] = -matrix[1] * det; ++ inv[2] = -matrix[2] * det; ++ inv[3] = matrix[0] * det; ++ inv[4] = det * (matrix[2] * matrix[5] - matrix[3] * matrix[4]); ++ inv[5] = det * (matrix[1] * matrix[4] - matrix[0] * matrix[5]); ++ ++ this.transform.apply(this, matrix); ++ var shading = this.getShading(pattern.get("Shading")); ++ this.restore(); ++ this.ctx.fillStyle = shading; ++ ++ // HACK to get the gradient to show at the right location. If ++ // removed, the gradient will show at the pre-transform coordinates. ++ this.ctx.fillRect(0,0,0,0); ++ this.transform.apply(this, inv); ++ }, ++ setTilingPattern: function(pattern, dict) { + 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 xstep = dict.get("XStep"); - var ystep = dict.get("YStep"); + TODO('TilingType'); - // 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 matrix = dict.get('Matrix') || IDENTITY_MATRIX; - var width = botRight[0] - topLeft[0]; - var height = botRight[1] - topLeft[1]; + var bbox = dict.get('BBox'); + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; - // TODO: hack to avoid OOM, remove then pattern code is fixed - if (Math.abs(width) > 8192 || Math.abs(height) > 8192) - return false; + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); - var tmpCanvas = new this.ScratchCanvas(width, height); + // 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); - // set the new canvas element context as the graphics context - var tmpCtx = tmpCanvas.getContext("2d"); - var savedCtx = ctx; - this.ctx = tmpCtx; + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - 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); - } + // TODO: hack to avoid OOM, remove then pattern code is fixed + if (Math.abs(width) > 8192 || Math.abs(height) > 8192) + return false; - // move the top left corner of bounding box to [0,0] - matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); + var tmpCanvas = new this.ScratchCanvas(width, height); - this.transform.apply(this, matrix); + // set the new canvas element context as the graphics context + var tmpCtx = tmpCanvas.getContext('2d'); + var savedCtx = ctx; + this.ctx = tmpCtx; - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } + // 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); + } - + // move the top left corner of bounding box to [0,0] + matrix = multiply(matrix, [1, 0, 0, 1, -topLeft[0], -topLeft[1]]); - + this.transform.apply(this, matrix); + + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } - 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"); - this.ctx.fillStyle = this.ctx.createPattern(tmpCanvas, "repeat"); - }, - 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(shadingName) { - 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(shadingName.name)); - if (!shading) - error("No shading object found"); - - var shadingFill = this.getShading(shading); - - this.save(); - this.ctx.fillStyle = shadingFill; - - // 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); - - this.restore(); - }, - getShading: function(shading) { - shading = this.xref.fetchIfRef(shading); - - var bbox = shading.get("BBox"); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } + 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) { ++ shadingFill: function(shadingName) { + var xref = this.xref; + var res = this.res; + - var shadingRes = xref.fetchIfRef(res.get('Shading')); ++ var shadingRes = xref.fetchIfRef(res.get("Shading")); + if (!shadingRes) - error('No shading resource found'); ++ error("No shading resource found"); + - var shading = xref.fetchIfRef(shadingRes.get(entryRef.name)); ++ var shading = xref.fetchIfRef(shadingRes.get(shadingName.name)); + if (!shading) - error('No shading object found'); ++ error("No shading object found"); ++ ++ var shadingFill = this.getShading(shading); + + this.save(); ++ this.ctx.fillStyle = shadingFill; + - var bbox = shading.get('BBox'); ++ // 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); ++ ++ this.restore(); ++ }, ++ getShading: function(shading) { ++ shading = this.xref.fetchIfRef(shading); ++ ++ 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"); - cs = ColorSpace.parse(cs, this.xref, this.res); - - var types = [null, - null, - this.getAxialShading, - this.getRadialShading]; - - var typeNum = shading.get("ShadingType"); - var shadingFn = types[typeNum]; - if (!shadingFn) - error("Unknown or NYI type of shading '"+ typeNum +"'"); - return shadingFn.call(this, shading, cs); - }, - getAxialShading: function(sh, cs) { - var coordsArr = sh.get("Coords"); - var x0 = coordsArr[0], y0 = coordsArr[1], - x1 = coordsArr[2], y1 = coordsArr[3]; - var cs = shading.get2('ColorSpace', 'CS'); - TODO('shading-fill color space'); -- - var t0 = 0.0, t1 = 1.0; - if (sh.has("Domain")) { - var domainArr = sh.get("Domain"); - t0 = domainArr[0], t1 = domainArr[1]; - } - var background = shading.get('Background'); ++ var background = shading.get("Background"); + if (background) - TODO('handle background colors'); ++ TODO("handle background colors"); + - var types = [null, - this.fillFunctionShading, - this.fillAxialShading, - this.fillRadialShading]; - - var typeNum = shading.get('ShadingType'); - var fillFn = types[typeNum]; - if (!fillFn) - error("Unknown or NYI type of shading '" + typeNum + "'"); - fillFn.apply(this, [shading]); ++ var cs = shading.get2("ColorSpace", "CS"); ++ cs = ColorSpace.parse(cs, this.xref, this.res); + - this.restore(); - }, - - fillAxialShading: function(sh) { - var coordsArr = sh.get('Coords'); ++ var types = [null, ++ null, ++ this.getAxialShading, ++ this.getRadialShading]; ++ ++ var typeNum = shading.get("ShadingType"); ++ var shadingFn = types[typeNum]; ++ if (!shadingFn) ++ error("Unknown or NYI type of shading '"+ typeNum +"'"); ++ return shadingFn.call(this, shading, cs); ++ }, ++ getAxialShading: function(sh, cs) { ++ var coordsArr = sh.get("Coords"); + var x0 = coordsArr[0], y0 = coordsArr[1], - x1 = coordsArr[2], y1 = coordsArr[3]; ++ x1 = coordsArr[2], y1 = coordsArr[3]; + + var t0 = 0.0, t1 = 1.0; + if (sh.has('Domain')) { + var domainArr = sh.get('Domain'); + t0 = domainArr[0], t1 = domainArr[1]; + } - 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; - var diff = t1 - t0; - - for (var i = t0; i <= t1; i += step) { - var color = fn.func([i]); - var rgbColor = cs.getRgb(color); - gradient.addColorStop((i - t0) / diff, - this.makeCssRgb.apply(this, rgbColor)); - } + var extendStart = false, extendEnd = false; - if (sh.has('Extend')) { - var extendArr = sh.get('Extend'); ++ if (sh.has("Extend")) { ++ var extendArr = sh.get("Extend"); + extendStart = extendArr[0], extendEnd = extendArr[1]; - TODO('Support extend'); ++ TODO("Support extend"); + } - var fnObj = sh.get('Function'); ++ var fnObj = sh.get("Function"); + fnObj = this.xref.fetchIfRef(fnObj); + if (IsArray(fnObj)) - error('No support for array of functions'); ++ error("No support for array of functions"); + else if (!IsPDFFunction(fnObj)) - error('Invalid function'); ++ 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; ++ var diff = t1 - t0; + + for (var i = t0; i <= t1; i += step) { - var c = fn.func([i]); - gradient.addColorStop(i, this.makeCssRgb.apply(this, c)); ++ var color = fn.func([i]); ++ var rgbColor = cs.getRgb(color); ++ gradient.addColorStop((i - t0) / diff, ++ this.makeCssRgb.apply(this, rgbColor)); + } - return gradient; - }, - getRadialShading: function(sh, cs) { - var coordsArr = sh.get("Coords"); - var x0 = coordsArr[0], y0 = coordsArr[1], - r0 = coordsArr[2]; - var x1 = coordsArr[3], y1 = coordsArr[4], - r1 = coordsArr[5]; - this.ctx.fillStyle = gradient; -- - var t0 = 0.0, t1 = 1.0; - if (sh.has("Domain")) { - var domainArr = sh.get("Domain"); - t0 = domainArr[0], t1 = domainArr[1]; - } - // 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); ++ return gradient; + }, ++ getRadialShading: function(sh, cs) { ++ var coordsArr = sh.get("Coords"); ++ var x0 = coordsArr[0], y0 = coordsArr[1], r0 = coordsArr[2]; ++ var x1 = coordsArr[3], y1 = coordsArr[4], r1 = coordsArr[5]; ++ ++ var t0 = 0.0, t1 = 1.0; ++ if (sh.has("Domain")) { ++ var domainArr = sh.get("Domain"); ++ t0 = domainArr[0], t1 = domainArr[1]; ++ } + - 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.createRadialGradient(x0, y0, r0, x1, y1, r1); - - // 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; - var diff = t1 - t0; - - for (var i = t0; i <= t1; i += step) { - var color = fn.func([i]); - var rgbColor = cs.getRgb(color); - gradient.addColorStop((i - t0) / diff, - this.makeCssRgb.apply(this, rgbColor)); - } ++ 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.createRadialGradient(x0, y0, r0, x1, y1, r1); + - fillRadialShading: function(sh) { - TODO('radial shading'); ++ // 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; ++ var diff = t1 - t0; ++ ++ for (var i = t0; i <= t1; i += step) { ++ var color = fn.func([i]); ++ var rgbColor = cs.getRgb(color); ++ gradient.addColorStop((i - t0) / diff, ++ this.makeCssRgb.apply(this, rgbColor)); ++ } + - return gradient; - }, - - // 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"); - } ++ return gradient; + }, + + // 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 opi = xobj.dict.get("OPI"); - if (opi) { - TODO("opi for xobject"); - } + var oc = xobj.dict.get('OC'); + if (oc) { + TODO('oc 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 opi = xobj.dict.get('OPI'); + if (opi) { + TODO('opi for xobject'); + } - paintFormXObject: function(ref, stream) { - this.save(); + 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 matrix = stream.dict.get("Matrix"); - if (matrix && IsArray(matrix) && 6 == matrix.length) - this.transform.apply(this, matrix); + paintFormXObject: function(ref, stream) { + this.save(); - var bbox = stream.dict.get("BBox"); - if (bbox && IsArray(bbox) && 4 == bbox.length) { - this.rectangle.apply(this, bbox); - this.clip(); - this.endPath(); - } + var matrix = stream.dict.get('Matrix'); + if (matrix && IsArray(matrix) && 6 == matrix.length) + this.transform.apply(this, matrix); - 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 bbox = stream.dict.get('BBox'); + if (bbox && IsArray(bbox) && 4 == bbox.length) { + this.rectangle.apply(this, bbox); + this.clip(); + this.endPath(); + } - 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.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; + } - return constructor; + 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; + } + }; + + return constructor; })(); var ColorSpace = (function() { @@@ -4633,283 -4620,236 +4688,283 @@@ var DeviceGrayCS = (function() })(); var DeviceRgbCS = (function() { - function constructor() { - this.name = "DeviceRGB"; - this.numComps = 3; - this.defaultColor = [0, 0, 0]; + function constructor() { + this.name = 'DeviceRGB'; + this.numComps = 3; + this.defaultColor = [0, 0, 0]; + } + constructor.prototype = { - getRgb: function graycs_getRgb(color) { ++ getRgb: function rgbcs_getRgb(color) { + return color; + }, - getRgbBuffer: function graycs_getRgbBuffer(input) { ++ getRgbBuffer: function rgbcs_getRgbBuffer(input) { + return input; } - constructor.prototype = { - getRgb: function rgbcs_getRgb(color) { - return color; - }, - getRgbBuffer: function rgbcs_getRgbBuffer(input) { - return input; - } - }; - return constructor; + }; + return constructor; })(); var DeviceCmykCS = (function() { - function constructor() { - this.name = "DeviceCMYK"; - this.numComps = 4; - this.defaultColor = [0, 0, 0, 1]; + function constructor() { + this.name = 'DeviceCMYK'; + this.numComps = 4; + this.defaultColor = [0, 0, 0, 1]; + } + constructor.prototype = { - getRgb: function graycs_getRgb(color) { - var c = color[0], y = color[1], m = color[2], k = color[3]; - var ri = (1 - Math.min(1, c * (1 - k) + k)) | 0; - var gi = (1 - Math.min(1, m * (1 - k) + k)) | 0; - var bi = (1 - Math.min(1, y * (1 - k) + k)) | 0; - return [ri, gi, bi]; - }, - getRgbBuffer: function graycs_getRgbBuffer(colorBuf) { - error('conversion from rgb to cmyk not implemented for images'); ++ getRgb: function cmykcs_getRgb(color) { ++ var c = color[0], m = color[1], y = color[2], k = color[3]; ++ var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k; ++ ++ var x, r, g, b; ++ // this is a matrix multiplication, unrolled for performance ++ // code is taken from the poppler implementation ++ x = c1 * m1 * y1 * k1; // 0 0 0 0 ++ r = g = b = x; ++ x = c1 * m1 * y1 * k; // 0 0 0 1 ++ r += 0.1373 * x; ++ g += 0.1216 * x; ++ b += 0.1255 * x; ++ x = c1 * m1 * y * k1; // 0 0 1 0 ++ r += x; ++ g += 0.9490 * x; ++ x = c1 * m1 * y * k; // 0 0 1 1 ++ r += 0.1098 * x; ++ g += 0.1020 * x; ++ x = c1 * m * y1 * k1; // 0 1 0 0 ++ r += 0.9255 * x; ++ b += 0.5490 * x; ++ x = c1 * m * y1 * k; // 0 1 0 1 ++ r += 0.1412 * x; ++ x = c1 * m * y * k1; // 0 1 1 0 ++ r += 0.9294 * x; ++ g += 0.1098 * x; ++ b += 0.1412 * x; ++ x = c1 * m * y * k; // 0 1 1 1 ++ r += 0.1333 * x; ++ x = c * m1 * y1 * k1; // 1 0 0 0 ++ g += 0.6784 * x; ++ b += 0.9373 * x; ++ x = c * m1 * y1 * k; // 1 0 0 1 ++ g += 0.0588 * x; ++ b += 0.1412 * x; ++ x = c * m1 * y * k1; // 1 0 1 0 ++ g += 0.6510 * x; ++ b += 0.3137 * x; ++ x = c * m1 * y * k; // 1 0 1 1 ++ g += 0.0745 * x; ++ x = c * m * y1 * k1; // 1 1 0 0 ++ r += 0.1804 * x; ++ g += 0.1922 * x; ++ b += 0.5725 * x; ++ x = c * m * y1 * k; // 1 1 0 1 ++ b += 0.0078 * x; ++ x = c * m * y * k1; // 1 1 1 0 ++ r += 0.2118 * x; ++ g += 0.2119 * x; ++ b += 0.2235 * x; ++ ++ return [r, g, b]; ++ }, ++ getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) { ++ error("conversion from rgb to cmyk not implemented for images"); + return colorBuf; } - constructor.prototype = { - getRgb: function cmykcs_getRgb(color) { - var c = color[0], m = color[1], y = color[2], k = color[3]; - var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k; - - var x, r, g, b; - // this is a matrix multiplication, unrolled for performance - // code is taken from the poppler implementation - x = c1 * m1 * y1 * k1; // 0 0 0 0 - r = g = b = x; - x = c1 * m1 * y1 * k; // 0 0 0 1 - r += 0.1373 * x; - g += 0.1216 * x; - b += 0.1255 * x; - x = c1 * m1 * y * k1; // 0 0 1 0 - r += x; - g += 0.9490 * x; - x = c1 * m1 * y * k; // 0 0 1 1 - r += 0.1098 * x; - g += 0.1020 * x; - x = c1 * m * y1 * k1; // 0 1 0 0 - r += 0.9255 * x; - b += 0.5490 * x; - x = c1 * m * y1 * k; // 0 1 0 1 - r += 0.1412 * x; - x = c1 * m * y * k1; // 0 1 1 0 - r += 0.9294 * x; - g += 0.1098 * x; - b += 0.1412 * x; - x = c1 * m * y * k; // 0 1 1 1 - r += 0.1333 * x; - x = c * m1 * y1 * k1; // 1 0 0 0 - g += 0.6784 * x; - b += 0.9373 * x; - x = c * m1 * y1 * k; // 1 0 0 1 - g += 0.0588 * x; - b += 0.1412 * x; - x = c * m1 * y * k1; // 1 0 1 0 - g += 0.6510 * x; - b += 0.3137 * x; - x = c * m1 * y * k; // 1 0 1 1 - g += 0.0745 * x; - x = c * m * y1 * k1; // 1 1 0 0 - r += 0.1804 * x; - g += 0.1922 * x; - b += 0.5725 * x; - x = c * m * y1 * k; // 1 1 0 1 - b += 0.0078 * x; - x = c * m * y * k1; // 1 1 1 0 - r += 0.2118 * x; - g += 0.2119 * x; - b += 0.2235 * x; - - return [r, g, b]; - }, - getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf) { - error("conversion from rgb to cmyk not implemented for images"); - return colorBuf; - } - }; - return constructor; + }; + return constructor; })(); var PDFImage = (function() { - function constructor(xref, res, image, inline) { - this.image = image; - if (image.getParams) { - // JPX/JPEG2000 streams directly contain bits per component - // and color space mode information. - TODO("get params from actual stream"); - // var bits = ... - // var colorspace = ... + function constructor(xref, res, image, inline) { + this.image = image; + if (image.getParams) { + // JPX/JPEG2000 streams directly contain bits per component + // and color space mode information. + TODO('get params from actual stream'); + // var bits = ... + // var colorspace = ... + } + // TODO cache rendered images? + + var dict = image.dict; + this.width = dict.get2('Width', 'W'); + this.height = dict.get2('Height', 'H'); + + if (this.width < 1 || this.height < 1) - error('Invalid image width or height'); ++ error('Invalid image width or height'); + - this.interpolate = dict.get2('Interpolate', 'I') || false; ++this.interpolate = dict.get2('Interpolate', 'I') || false; + this.imageMask = dict.get2('ImageMask', 'IM') || false; + + var bitsPerComponent = image.bitsPerComponent; + if (!bitsPerComponent) { + bitsPerComponent = dict.get2('BitsPerComponent', 'BPC'); + if (!bitsPerComponent) { + if (this.imageMask) + bitsPerComponent = 1; + else + error('Bits per component missing in image'); + } + } + this.bpc = bitsPerComponent; + + var colorSpace = dict.get2('ColorSpace', 'CS'); + this.colorSpace = ColorSpace.parse(colorSpace, xref, res); + + this.numComps = this.colorSpace.numComps; + this.decode = dict.get2('Decode', 'D'); + + var mask = xref.fetchIfRef(image.dict.get('Mask')); + var smask = xref.fetchIfRef(image.dict.get('SMask')); + + if (mask) { + TODO('masked images'); + } else if (smask) { + this.smask = new PDFImage(xref, res, smask); + } + }; + + constructor.prototype = { + getComponents: function getComponents(buffer) { + var bpc = this.bpc; + if (bpc == 8) + return buffer; + + var width = this.width; + var height = this.height; + var numComps = this.numComps; + + var length = width * height; + var bufferPos = 0; + var output = new Uint8Array(length); + + if (bpc == 1) { + var rowComps = width * numComps; + var mask = 0; + var buf = 0; + + for (var i = 0, ii = length; i < ii; ++i) { + if (i % rowComps == 0) { + mask = 0; + buf = 0; + } else { + mask >>= 1; + } + + if (mask <= 0) { + buf = buffer[bufferPos++]; + mask = 128; + } + + var t = buf & mask; + if (t == 0) + output[i] = 0; + else + output[i] = 255; } - // TODO cache rendered images? - - var dict = image.dict; - this.width = dict.get2("Width", "W"); - this.height = dict.get2("Height", "H"); - - if (this.width < 1 || this.height < 1) - error("Invalid image width or height"); - - this.interpolate = dict.get2("Interpolate", "I") || false; - this.imageMask = dict.get2("ImageMask", "IM") || false; - - var bitsPerComponent = image.bitsPerComponent; - if (!bitsPerComponent) { - bitsPerComponent = dict.get2("BitsPerComponent", "BPC"); - if (!bitsPerComponent) { - if (this.imageMask) - bitsPerComponent = 1; - else - error("Bits per component missing in image"); - } + } else { + var rowComps = width * numComps; + var bits = 0; + var buf = 0; + + for (var i = 0, ii = length; i < ii; ++i) { + while (bits < bpc) { + buf = (buf << 8) | buffer[bufferPos++]; + bits += 8; + } + var remainingBits = bits - bpc; + var ret = buf >> remainingBits; + + if (i % rowComps == 0) { + buf = 0; + bits = 0; + } else { + buf = buf & ((1 << remainingBits) - 1); + bits = remainingBits; + } + output[i] = Math.round(255 * ret / ((1 << bpc) - 1)); } - this.bpc = bitsPerComponent; + } + return this.colorSpace.getRbaBuffer(output); + }, + getOpacity: function getOpacity() { + var smask = this.smask; + var width = this.width; + var height = this.height; + var buf = new Uint8Array(width * height); + + if (smask) { + var sw = smask.width; + var sh = smask.height; + if (sw != this.width || sh != this.height) + error('smask dimensions do not match image dimensions'); + + smask.fillGrayBuffer(buf); + return buf; + } else { + for (var i = 0, ii = width * height; i < ii; ++i) + buf[i] = 255; + } + return buf; + }, + fillRgbaBuffer: function fillRgbaBuffer(buffer) { + var numComps = this.numComps; + var width = this.width; + var height = this.height; + var bpc = this.bpc; + + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); + + var comps = this.getComponents(imgArray); + var compsPos = 0; + var opacity = this.getOpacity(); + var opacityPos = 0; + var length = width * height * 4; + + switch (numComps) { + case 1: + for (var i = 0; i < length; i += 4) { + var p = comps[compsPos++]; + buffer[i] = p; + buffer[i + 1] = p; + buffer[i + 2] = p; + buffer[i + 3] = opacity[opacityPos++]; + } + break; + case 3: + for (var i = 0; i < length; i += 4) { + buffer[i] = comps[compsPos++]; + buffer[i + 1] = comps[compsPos++]; + buffer[i + 2] = comps[compsPos++]; + buffer[i + 3] = opacity[opacityPos++]; + } + break; + default: + TODO('Images with ' + numComps + ' components per pixel'); + } + }, + fillGrayBuffer: function fillGrayBuffer(buffer) { + var numComps = this.numComps; + if (numComps != 1) + error('Reading gray scale from a color image'); - var colorSpace = dict.get2("ColorSpace", "CS"); - this.colorSpace = ColorSpace.parse(colorSpace, xref, res); + var width = this.width; + var height = this.height; + var bpc = this.bpc; - this.numComps = this.colorSpace.numComps; - this.decode = dict.get2("Decode", "D"); + // rows start at byte boundary; + var rowBytes = (width * numComps * bpc + 7) >> 3; + var imgArray = this.image.getBytes(height * rowBytes); - var mask = xref.fetchIfRef(image.dict.get("Mask")); - var smask = xref.fetchIfRef(image.dict.get("SMask")); + var comps = this.getComponents(imgArray); + var length = width * height; - if (mask) { - TODO("masked images"); - } else if (smask) { - this.smask = new PDFImage(xref, res, smask); - } - }; - - constructor.prototype = { - getComponents: function getComponents(buffer) { - var bpc = this.bpc; - if (bpc == 8) - return buffer; - - var width = this.width; - var height = this.height; - var numComps = this.numComps; - - var length = width * height; - var bufferPos = 0; - var output = new Uint8Array(length); - - if (bpc == 1) { - var rowComps = width * numComps; - var mask = 0; - var buf = 0; - - for (var i = 0, ii = length; i < ii; ++i) { - if (i % rowComps == 0) { - mask = 0; - buf = 0; - } else { - mask >>= 1; - } - - if (mask <= 0) { - buf = buffer[bufferPos++]; - mask = 128; - } - - var t = buf & mask; - if (t == 0) - output[i] = 0; - else - output[i] = 255; - } - } else { - var rowComps = width * numComps; - var bits = 0; - var buf = 0; - - for (var i = 0, ii = length; i < ii; ++i) { - while (bits < bpc) { - buf = (buf << 8) | buffer[bufferPos++]; - bits += 8; - } - var remainingBits = bits - bpc; - var ret = buf >> remainingBits; - - if (i % rowComps == 0) { - buf = 0; - bits = 0; - } else { - buf = buf & ((1 << remainingBits) - 1); - bits = remainingBits; - } - output[i] = Math.round(255 * ret / ((1 << bpc) - 1)); - } - } - return this.colorSpace.getRbaBuffer(output); - }, - getOpacity: function getOpacity() { - var smask = this.smask; - var width = this.width; - var height = this.height; - var buf = new Uint8Array(width * height); - - if (smask) { - var sw = smask.width; - var sh = smask.height; - if (sw != this.width || sh != this.height) - error("smask dimensions do not match image dimensions"); - - smask.fillGrayBuffer(buf); - return buf; - } else { - for (var i = 0, ii = width * height; i < ii; ++i) - buf[i] = 255; - } - return buf; - }, - fillRgbaBuffer: function fillRgbaBuffer(buffer) { - var numComps = this.numComps; - var width = this.width; - var height = this.height; - var bpc = this.bpc; - - // rows start at byte boundary; - var rowBytes = (width * numComps * bpc + 7) >> 3; - var imgArray = this.image.getBytes(height * rowBytes); - - var comps = this.getComponents(imgArray); - var compsPos = 0; - var opacity = this.getOpacity(); - var opacityPos = 0; - var length = width * height * 4; - - switch (numComps) { - case 1: - for (var i = 0; i < length; i += 4) { - var p = comps[compsPos++]; - buffer[i] = p; - buffer[i+1] = p; - buffer[i+2] = p; - buffer[i+3] = opacity[opacityPos++]; - } - break; - case 3: - for (var i = 0; i < length; i += 4) { - buffer[i] = comps[compsPos++]; - buffer[i+1] = comps[compsPos++]; - buffer[i+2] = comps[compsPos++]; - buffer[i+3] = opacity[opacityPos++]; - } - break; - default: - TODO("Images with "+ numComps + " components per pixel"); - } - }, - fillGrayBuffer: function fillGrayBuffer(buffer) { - var numComps = this.numComps; - if (numComps != 1) - error("Reading gray scale from a color image"); - - var width = this.width; - var height = this.height; - var bpc = this.bpc; - - // rows start at byte boundary; - var rowBytes = (width * numComps * bpc + 7) >> 3; - var imgArray = this.image.getBytes(height * rowBytes); - - var comps = this.getComponents(imgArray); - var length = width * height; - - for (var i = 0; i < length; ++i) - buffer[i] = comps[i]; - }, - }; - return constructor; + for (var i = 0; i < length; ++i) + buffer[i] = comps[i]; + } + }; + return constructor; })(); var PDFFunction = (function() {