diff options
Diffstat (limited to 'apps/files_pdfviewer/js/pdfjs/src/evaluator.js')
-rwxr-xr-x | apps/files_pdfviewer/js/pdfjs/src/evaluator.js | 857 |
1 files changed, 857 insertions, 0 deletions
diff --git a/apps/files_pdfviewer/js/pdfjs/src/evaluator.js b/apps/files_pdfviewer/js/pdfjs/src/evaluator.js new file mode 100755 index 00000000000..a863a531ec5 --- /dev/null +++ b/apps/files_pdfviewer/js/pdfjs/src/evaluator.js @@ -0,0 +1,857 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +var PartialEvaluator = (function partialEvaluator() { + function constructor(xref, handler, uniquePrefix) { + this.state = new EvalState(); + this.stateStack = []; + + this.xref = xref; + this.handler = handler; + this.uniquePrefix = uniquePrefix; + this.objIdCounter = 0; + } + + var OP_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', + ID: 'beginImageData', + EI: 'endInlineImage', + + // XObjects + Do: 'paintXObject', + + // Marked content + MP: 'markPoint', + DP: 'markPointProps', + BMC: 'beginMarkedContent', + BDC: 'beginMarkedContentProps', + EMC: 'endMarkedContent', + + // Compatibility + BX: 'beginCompat', + EX: 'endCompat' + }; + + constructor.prototype = { + getIRQueue: function partialEvaluatorGetIRQueue(stream, resources, + queue, dependency) { + + var self = this; + var xref = this.xref; + var handler = this.handler; + var uniquePrefix = this.uniquePrefix; + + function insertDependency(depList) { + fnArray.push('dependency'); + argsArray.push(depList); + for (var i = 0, ii = depList.length; i < ii; i++) { + var dep = depList[i]; + if (dependency.indexOf(dep) == -1) { + dependency.push(depList[i]); + } + } + } + + function handleSetFont(fontName, fontRef) { + var loadedName = null; + + var fontRes = resources.get('Font'); + + // TODO: TOASK: Is it possible to get here? If so, what does + // args[0].name should be like??? + assert(fontRes, 'fontRes not available'); + + fontRes = xref.fetchIfRef(fontRes); + fontRef = fontRef || fontRes.get(fontName); + var font = xref.fetchIfRef(fontRef); + assertWellFormed(isDict(font)); + if (!font.translated) { + font.translated = self.translateFont(font, xref, resources, + dependency); + if (font.translated) { + // keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page + loadedName = 'font_' + uniquePrefix + (++self.objIdCounter); + font.translated.properties.loadedName = loadedName; + font.loadedName = loadedName; + + var translated = font.translated; + handler.send('obj', [ + loadedName, + 'Font', + translated.name, + translated.file, + translated.properties + ]); + } + } + loadedName = loadedName || font.loadedName; + + // Ensure the font is ready before the font is set + // and later on used for drawing. + // TODO: This should get insert to the IRQueue only once per + // page. + insertDependency([loadedName]); + return loadedName; + } + + function buildPaintImageXObject(image, inline) { + var dict = image.dict; + var w = dict.get('Width', 'W'); + var h = dict.get('Height', 'H'); + + if (image instanceof JpegStream && image.isNative) { + var objId = 'img_' + uniquePrefix + (++self.objIdCounter); + handler.send('obj', [objId, 'JpegStream', image.getIR()]); + + // Add the dependency on the image object. + insertDependency([objId]); + + // The normal fn. + fn = 'paintJpegXObject'; + args = [objId, w, h]; + + return; + } + + // Needs to be rendered ourself. + + // Figure out if the image has an imageMask. + var imageMask = dict.get('ImageMask', 'IM') || false; + + // If there is no imageMask, create the PDFImage and a lot + // of image processing can be done here. + if (!imageMask) { + var imageObj = new PDFImage(xref, resources, image, inline); + + if (imageObj.imageMask) { + throw 'Can\'t handle this in the web worker :/'; + } + + var imgData = { + width: w, + height: h, + data: new Uint8Array(w * h * 4) + }; + var pixels = imgData.data; + imageObj.fillRgbaBuffer(pixels, imageObj.decode); + + fn = 'paintImageXObject'; + args = [imgData]; + return; + } + + // This depends on a tmpCanvas beeing filled with the + // current fillStyle, such that processing the pixel + // data can't be done here. Instead of creating a + // complete PDFImage, only read the information needed + // for later. + fn = 'paintImageMaskXObject'; + + var width = dict.get('Width', 'W'); + var height = dict.get('Height', 'H'); + var bitStrideLength = (width + 7) >> 3; + var imgArray = image.getBytes(bitStrideLength * height); + var decode = dict.get('Decode', 'D'); + var inverseDecode = !!decode && decode[0] > 0; + + args = [imgArray, inverseDecode, width, height]; + } + + uniquePrefix = uniquePrefix || ''; + if (!queue.argsArray) { + queue.argsArray = []; + } + if (!queue.fnArray) { + queue.fnArray = []; + } + + var fnArray = queue.fnArray, argsArray = queue.argsArray; + var dependencyArray = dependency || []; + + resources = xref.fetchIfRef(resources) || new Dict(); + var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); + var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict(); + var parser = new Parser(new Lexer(stream), false); + var res = resources; + var args = [], obj; + var getObjBt = function getObjBt() { + parser = this.oldParser; + return { name: 'BT' }; + }; + var TILING_PATTERN = 1, SHADING_PATTERN = 2; + + while (!isEOF(obj = parser.getObj())) { + if (isCmd(obj)) { + var cmd = obj.cmd; + var fn = OP_MAP[cmd]; + if (!fn) { + // invalid content command, trying to recover + if (cmd.substr(-2) == 'BT') { + fn = OP_MAP[cmd.substr(0, cmd.length - 2)]; + // feeding 'BT' on next interation + parser = { + getObj: getObjBt, + oldParser: parser + }; + } + } + assertWellFormed(fn, 'Unknown command "' + cmd + '"'); + // TODO figure out how to type-check vararg functions + + if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) { + // Use the IR version for setStroke/FillColorN. + fn += '_IR'; + + // compile tiling patterns + var patternName = args[args.length - 1]; + // SCN/scn applies patterns along with normal colors + if (isName(patternName)) { + var pattern = xref.fetchIfRef(patterns.get(patternName.name)); + if (pattern) { + var dict = isStream(pattern) ? pattern.dict : pattern; + var typeNum = dict.get('PatternType'); + + if (typeNum == TILING_PATTERN) { + // Create an IR of the pattern code. + var depIdx = dependencyArray.length; + var queueObj = {}; + var codeIR = this.getIRQueue(pattern, dict.get('Resources'), + queueObj, dependencyArray); + + // Add the dependencies that are required to execute the + // codeIR. + insertDependency(dependencyArray.slice(depIdx)); + + args = TilingPattern.getIR(codeIR, dict, args); + } + else if (typeNum == SHADING_PATTERN) { + var shading = xref.fetchIfRef(dict.get('Shading')); + var matrix = dict.get('Matrix'); + var pattern = Pattern.parseShading(shading, matrix, xref, res, + null /*ctx*/); + args = pattern.getIR(); + } else { + error('Unkown PatternType ' + typeNum); + } + } + } + } else 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) { + var matrix = xobj.dict.get('Matrix'); + var bbox = xobj.dict.get('BBox'); + + fnArray.push('paintFormXObjectBegin'); + argsArray.push([matrix, bbox]); + + // This adds the IRQueue of the xObj to the current queue. + var depIdx = dependencyArray.length; + + this.getIRQueue(xobj, xobj.dict.get('Resources'), queue, + dependencyArray); + + // Add the dependencies that are required to execute the + // codeIR. + insertDependency(dependencyArray.slice(depIdx)); + + fn = 'paintFormXObjectEnd'; + args = []; + } else if ('Image' == type.name) { + buildPaintImageXObject(xobj, false); + } else { + error('Unhandled XObject subtype ' + type.name); + } + } + } else if (cmd == 'Tf') { // eagerly collect all fonts + args[0] = handleSetFont(args[0].name); + } else if (cmd == 'EI') { + buildPaintImageXObject(args[0], true); + } + + switch (fn) { + // Parse the ColorSpace data to a raw format. + case 'setFillColorSpace': + case 'setStrokeColorSpace': + args = [ColorSpace.parseToIR(args[0], xref, resources)]; + break; + case 'shadingFill': + var shadingRes = xref.fetchIfRef(res.get('Shading')); + if (!shadingRes) + error('No shading resource found'); + + var shading = xref.fetchIfRef(shadingRes.get(args[0].name)); + if (!shading) + error('No shading object found'); + + var shadingFill = Pattern.parseShading(shading, null, xref, res, + null); + var patternIR = shadingFill.getIR(); + args = [patternIR]; + fn = 'shadingFill'; + break; + case 'setGState': + var dictName = args[0]; + var extGState = xref.fetchIfRef(resources.get('ExtGState')); + + if (!isDict(extGState) || !extGState.has(dictName.name)) + break; + + var gsState = xref.fetchIfRef(extGState.get(dictName.name)); + + // This array holds the converted/processed state data. + var gsStateObj = []; + + gsState.forEach( + function canvasGraphicsSetGStateForEach(key, value) { + switch (key) { + case 'Type': + break; + case 'LW': + case 'LC': + case 'LJ': + case 'ML': + case 'D': + case 'RI': + case 'FL': + case 'CA': + case 'ca': + gsStateObj.push([key, value]); + break; + case 'Font': + gsStateObj.push([ + 'Font', + handleSetFont(null, value[0]), + value[1] + ]); + break; + case 'OP': + case 'op': + case 'OPM': + case 'BG': + case 'BG2': + case 'UCR': + case 'UCR2': + case 'TR': + case 'TR2': + case 'HT': + case 'SM': + case 'SA': + case 'BM': + case 'SMask': + case 'AIS': + case 'TK': + TODO('graphic state operator ' + key); + break; + default: + warn('Unknown graphic state operator ' + key); + break; + } + } + ); + args = [gsStateObj]; + break; + } // switch + + fnArray.push(fn); + argsArray.push(args); + args = []; + } else if (obj != null) { + assertWellFormed(args.length <= 33, 'Too many arguments'); + args.push(obj); + } + } + + return { + fnArray: fnArray, + argsArray: argsArray + }; + }, + + extractDataStructures: function + partialEvaluatorExtractDataStructures(dict, baseDict, + xref, properties) { + // 9.10.2 + var toUnicode = dict.get('ToUnicode') || + baseDict.get('ToUnicode'); + if (toUnicode) + properties.toUnicode = this.readToUnicode(toUnicode, xref); + + if (properties.composite) { + // CIDSystemInfo helps to match CID to glyphs + var cidSystemInfo = xref.fetchIfRef(dict.get('CIDSystemInfo')); + if (isDict(cidSystemInfo)) { + properties.cidSystemInfo = { + registry: cidSystemInfo.get('Registry'), + ordering: cidSystemInfo.get('Ordering'), + supplement: cidSystemInfo.get('Supplement') + }; + } + + var cidToGidMap = xref.fetchIfRef(dict.get('CIDToGIDMap')); + if (isStream(cidToGidMap)) + properties.cidToGidMap = this.readCidToGidMap(cidToGidMap); + } + + var differences = []; + var baseEncoding = Encodings.StandardEncoding; + var hasEncoding = dict.has('Encoding'); + if (hasEncoding) { + var encoding = xref.fetchIfRef(dict.get('Encoding')); + if (isDict(encoding)) { + var baseName = encoding.get('BaseEncoding'); + if (baseName) + baseEncoding = Encodings[baseName.name]; + + // Load the differences between the base and original + if (encoding.has('Differences')) { + var diffEncoding = encoding.get('Differences'); + var index = 0; + for (var j = 0, jj = diffEncoding.length; j < jj; j++) { + var data = diffEncoding[j]; + if (isNum(data)) + index = data; + else + differences[index++] = data.name; + } + } + } else if (isName(encoding)) { + baseEncoding = Encodings[encoding.name]; + } else { + error('Encoding is not a Name nor a Dict'); + } + } + properties.differences = differences; + properties.baseEncoding = baseEncoding; + properties.hasEncoding = hasEncoding; + }, + + readToUnicode: + function partialEvaluatorReadToUnicode(toUnicode, xref) { + var cmapObj = xref.fetchIfRef(toUnicode); + var charToUnicode = []; + if (isName(cmapObj)) { + var isIdentityMap = cmapObj.name.substr(0, 9) == 'Identity-'; + if (!isIdentityMap) + error('ToUnicode file cmap translation not implemented'); + } else if (isStream(cmapObj)) { + var tokens = []; + var token = ''; + var beginArrayToken = {}; + + var cmap = cmapObj.getBytes(cmapObj.length); + for (var i = 0, ii = cmap.length; i < ii; i++) { + var byte = cmap[i]; + if (byte == 0x20 || byte == 0x0D || byte == 0x0A || + byte == 0x3C || byte == 0x5B || byte == 0x5D) { + switch (token) { + case 'usecmap': + error('usecmap is not implemented'); + break; + + case 'beginbfchar': + case 'beginbfrange': + case 'begincidchar': + case 'begincidrange': + token = ''; + tokens = []; + break; + + case 'endcidrange': + case 'endbfrange': + for (var j = 0, jj = tokens.length; j < jj; j += 3) { + var startRange = tokens[j]; + var endRange = tokens[j + 1]; + var code = tokens[j + 2]; + while (startRange <= endRange) { + charToUnicode[startRange] = code++; + ++startRange; + } + } + break; + + case 'endcidchar': + case 'endbfchar': + for (var j = 0, jj = tokens.length; j < jj; j += 2) { + var index = tokens[j]; + var code = tokens[j + 1]; + charToUnicode[index] = code; + } + break; + + case '': + break; + + default: + if (token[0] >= '0' && token[0] <= '9') + token = parseInt(token, 10); // a number + tokens.push(token); + token = ''; + } + switch (byte) { + case 0x5B: + // begin list parsing + tokens.push(beginArrayToken); + break; + case 0x5D: + // collect array items + var items = [], item; + while (tokens.length && + (item = tokens.pop()) != beginArrayToken) + items.unshift(item); + tokens.push(items); + break; + } + } else if (byte == 0x3E) { + if (token.length) { + // parsing hex number + tokens.push(parseInt(token, 16)); + token = ''; + } + } else { + token += String.fromCharCode(byte); + } + } + } + return charToUnicode; + }, + readCidToGidMap: + function partialEvaluatorReadCidToGidMap(cidToGidStream) { + // Extract the encoding from the CIDToGIDMap + var glyphsData = cidToGidStream.getBytes(); + + // Set encoding 0 to later verify the font has an encoding + var result = []; + for (var j = 0, jj = glyphsData.length; j < jj; j++) { + var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; + if (glyphID == 0) + continue; + + var code = j >> 1; + result[code] = glyphID; + } + return result; + }, + + extractWidths: function partialEvaluatorWidths(dict, + xref, + descriptor, + properties) { + var glyphsWidths = []; + var defaultWidth = 0; + if (properties.composite) { + defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000; + + var widths = xref.fetchIfRef(dict.get('W')); + if (widths) { + var start = 0, end = 0; + for (var i = 0, ii = widths.length; i < ii; i++) { + var code = widths[i]; + if (isArray(code)) { + for (var j = 0, jj = code.length; j < jj; j++) + glyphsWidths[start++] = code[j]; + start = 0; + } else if (start) { + var width = widths[++i]; + for (var j = start; j <= code; j++) + glyphsWidths[j] = width; + start = 0; + } else { + start = code; + } + } + } + } else { + var firstChar = properties.firstChar; + var widths = xref.fetchIfRef(dict.get('Widths')); + if (widths) { + var j = firstChar; + for (var i = 0, ii = widths.length; i < ii; i++) + glyphsWidths[j++] = widths[i]; + defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0; + } else { + // Trying get the BaseFont metrics (see comment above). + var baseFontName = dict.get('BaseFont'); + if (isName(baseFontName)) { + var metrics = this.getBaseFontMetrics(baseFontName.name); + + glyphsWidths = metrics.widths; + defaultWidth = metrics.defaultWidth; + } + } + } + + properties.defaultWidth = defaultWidth; + properties.widths = glyphsWidths; + }, + + getBaseFontMetrics: function getBaseFontMetrics(name) { + var defaultWidth = 0, widths = []; + var glyphWidths = Metrics[stdFontMap[name] || name]; + if (isNum(glyphWidths)) { + defaultWidth = glyphWidths; + } else { + widths = glyphWidths; + } + + return { + defaultWidth: defaultWidth, + widths: widths + }; + }, + + translateFont: function partialEvaluatorTranslateFont(dict, xref, resources, + dependency) { + var baseDict = dict; + var type = dict.get('Subtype'); + assertWellFormed(isName(type), 'invalid font Subtype'); + + var composite = false; + if (type.name == 'Type0') { + // If font is a composite + // - get the descendant font + // - set the type according to the descendant font + // - get the FontDescriptor from the descendant font + var df = dict.get('DescendantFonts'); + if (!df) + return null; + + if (isRef(df)) + df = xref.fetch(df); + + dict = xref.fetchIfRef(isRef(df) ? df : df[0]); + + type = dict.get('Subtype'); + assertWellFormed(isName(type), 'invalid font Subtype'); + composite = true; + } + var maxCharIndex = composite ? 0xFFFF : 0xFF; + + var descriptor = xref.fetchIfRef(dict.get('FontDescriptor')); + if (!descriptor) { + if (type.name == 'Type3') { + // FontDescriptor is only required for Type3 fonts when the document + // is a tagged pdf. Create a barbebones one to get by. + descriptor = new Dict(); + descriptor.set('FontName', new Name(type.name)); + } else { + // Before PDF 1.5 if the font was one of the base 14 fonts, having a + // FontDescriptor was not required. + // This case is here for compatibility. + var baseFontName = dict.get('BaseFont'); + if (!isName(baseFontName)) + return null; + + // Using base font name as a font name. + baseFontName = baseFontName.name.replace(/[,_]/g, '-'); + var metrics = this.getBaseFontMetrics(baseFontName); + + var properties = { + type: type.name, + widths: metrics.widths, + defaultWidth: metrics.defaultWidth, + firstChar: 0, + lastChar: maxCharIndex + }; + this.extractDataStructures(dict, dict, xref, properties); + + return { + name: baseFontName, + dict: baseDict, + properties: properties + }; + } + + } + + // According to the spec if 'FontDescriptor' is declared, 'FirstChar', + // 'LastChar' and 'Widths' should exists too, but some PDF encoders seems + // to ignore this rule when a variant of a standart font is used. + // TODO Fill the width array depending on which of the base font this is + // a variant. + var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0; + var lastChar = xref.fetchIfRef(dict.get('LastChar')) || maxCharIndex; + var fontName = xref.fetchIfRef(descriptor.get('FontName')); + assertWellFormed(isName(fontName), 'invalid font name'); + + var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3'); + if (fontFile) { + fontFile = xref.fetchIfRef(fontFile); + if (fontFile.dict) { + var subtype = fontFile.dict.get('Subtype'); + if (subtype) + subtype = subtype.name; + + var length1 = fontFile.dict.get('Length1'); + if (!isInt(length1)) + length1 = xref.fetchIfRef(length1); + + var length2 = fontFile.dict.get('Length2'); + if (!isInt(length2)) + length2 = xref.fetchIfRef(length2); + } + } + + var properties = { + type: type.name, + subtype: subtype, + file: fontFile, + length1: length1, + length2: length2, + composite: composite, + fixedPitch: false, + fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX, + firstChar: firstChar || 0, + lastChar: lastChar || maxCharIndex, + 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'), + coded: false + }; + this.extractWidths(dict, xref, descriptor, properties); + this.extractDataStructures(dict, baseDict, xref, properties); + + if (type.name === 'Type3') { + properties.coded = true; + var charProcs = xref.fetchIfRef(dict.get('CharProcs')); + var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources; + properties.resources = fontResources; + properties.charProcIRQueues = {}; + for (var key in charProcs.map) { + var glyphStream = xref.fetchIfRef(charProcs.map[key]); + var queueObj = {}; + properties.charProcIRQueues[key] = + this.getIRQueue(glyphStream, fontResources, queueObj, dependency); + } + } + + return { + name: fontName.name, + dict: baseDict, + file: fontFile, + properties: properties + }; + } + }; + + return constructor; +})(); + +var EvalState = (function evalState() { + function constructor() { + // Are soft masks and alpha values shapes or opacities? + this.alphaIsShape = false; + this.fontSize = 0; + this.textMatrix = IDENTITY_MATRIX; + this.leading = 0; + // Start of text line (in text coordinates) + this.lineX = 0; + this.lineY = 0; + // Character and word spacing + this.charSpacing = 0; + this.wordSpacing = 0; + this.textHScale = 1; + // Color spaces + this.fillColorSpace = null; + this.strokeColorSpace = null; + } + constructor.prototype = { + }; + return constructor; +})(); + |