From 7f038536fbe8a307d1a3e1439839241b0cedf168 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 28 Oct 2011 20:38:31 -0500 Subject: [PATCH 01/35] Migration of the 'encoding-1' branch (ref #674) --- src/canvas.js | 2 +- src/evaluator.js | 482 +++++++++++++----------------- src/fonts.js | 751 ++++++++++++++++++++++++++++++++--------------- 3 files changed, 717 insertions(+), 518 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index b7045dc39..d22023776 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -445,7 +445,7 @@ var CanvasGraphics = (function canvasGraphics() { this.save(); ctx.scale(fontSize, fontSize); ctx.transform.apply(ctx, fontMatrix); - this.executeIRQueue(glyph.IRQueue); + this.executeIRQueue(glyph.codeIRQueue); this.restore(); var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); diff --git a/src/evaluator.js b/src/evaluator.js index 48e12c83d..bb2efaa14 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -459,15 +459,178 @@ var PartialEvaluator = (function partialEvaluator() { }; }, - extractEncoding: function partialEvaluatorExtractEncoding(dict, - xref, - properties) { - var type = properties.type, encoding; - if (properties.composite) { - var defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000; - properties.defaultWidth = defaultWidth; + 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; j < diffEncoding.length; 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)) { + 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; i < cmap.length; 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; j < tokens.length; 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; j < tokens.length; 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; j < glyphsData.length; 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 glyphsWidths = {}; var widths = xref.fetchIfRef(dict.get('W')); if (widths) { var start = 0, end = 0; @@ -487,246 +650,41 @@ var PartialEvaluator = (function partialEvaluator() { } } } - properties.widths = glyphsWidths; - - // Glyph ids are big-endian 2-byte values - encoding = properties.encoding; - - // CIDSystemInfo might help to match width and glyphs - var cidSystemInfo = dict.get('CIDSystemInfo'); - if (isDict(cidSystemInfo)) { - properties.cidSystemInfo = { - registry: cidSystemInfo.get('Registry'), - ordering: cidSystemInfo.get('Ordering'), - supplement: cidSystemInfo.get('Supplement') - }; - } - - var cidToGidMap = dict.get('CIDToGIDMap'); - if (!cidToGidMap || !isRef(cidToGidMap)) { - - - return Object.create(GlyphsUnicode); - } - - // Extract the encoding from the CIDToGIDMap - var glyphsStream = xref.fetchIfRef(cidToGidMap); - var glyphsData = glyphsStream.getBytes(0); - - // Set encoding 0 to later verify the font has an encoding - encoding[0] = { unicode: 0, width: 0 }; - for (var j = 0; j < glyphsData.length; j++) { - var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; - if (glyphID == 0) - continue; - - var code = j >> 1; - var width = glyphsWidths[code]; - encoding[code] = { - unicode: glyphID, - width: isNum(width) ? width : defaultWidth - }; - } - - return Object.create(GlyphsUnicode); - } - - var differences = properties.differences; - var map = properties.encoding; - var baseEncoding = null; - if (dict.has('Encoding')) { - encoding = xref.fetchIfRef(dict.get('Encoding')); - if (isDict(encoding)) { - var baseName = encoding.get('BaseEncoding'); - if (baseName) - baseEncoding = Encodings[baseName.name].slice(); - - // Load the differences between the base and original - if (encoding.has('Differences')) { - var diffEncoding = encoding.get('Differences'); - var index = 0; - for (var j = 0; j < diffEncoding.length; j++) { - var data = diffEncoding[j]; - if (isNum(data)) - index = data; - else - differences[index++] = data.name; - } - } - } else if (isName(encoding)) { - baseEncoding = Encodings[encoding.name].slice(); + } else { + var firstChar = properties.firstChar; + var widths = xref.fetchIfRef(dict.get('Widths')); + if (widths) { + for (var i = 0, j = firstChar; i < widths.length; i++, j++) + glyphsWidths[j] = widths[i]; + defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0; } else { - error('Encoding is not a Name nor a Dict'); - } - } + // Trying get the BaseFont metrics (see comment above). + var baseFontName = dict.get('BaseFont'); + if (isName(baseFontName)) { + var metrics = this.getBaseFontMetrics(baseFontName.name); - if (!baseEncoding) { - switch (type) { - case 'TrueType': - baseEncoding = Encodings.WinAnsiEncoding.slice(); - break; - case 'Type1': - case 'Type3': - baseEncoding = Encodings.StandardEncoding.slice(); - break; - default: - warn('Unknown type of font: ' + type); - baseEncoding = []; - break; - } - } - - // merge in the differences - var firstChar = properties.firstChar; - var lastChar = properties.lastChar; - var widths = properties.widths || []; - var glyphs = {}; - for (var i = firstChar; i <= lastChar; i++) { - var glyph = differences[i]; - var replaceGlyph = true; - if (!glyph) { - glyph = baseEncoding[i] || i; - replaceGlyph = false; - } - var index = GlyphsUnicode[glyph] || i; - var width = widths[i] || widths[glyph]; - map[i] = { - unicode: index, - width: isNum(width) ? width : properties.defaultWidth - }; - - if (replaceGlyph || !glyphs[glyph]) - glyphs[glyph] = map[i]; - if (replaceGlyph || !glyphs[index]) - glyphs[index] = map[i]; - - // If there is no file, the character mapping can't be modified - // but this is unlikely that there is any standard encoding with - // chars below 0x1f, so that's fine. - if (!properties.file) - continue; - - if (index <= 0x1f || (index >= 127 && index <= 255)) - map[i].unicode += kCmapGlyphOffset; - } - - if (type == 'TrueType' && dict.has('ToUnicode') && differences) { - var cmapObj = dict.get('ToUnicode'); - if (isRef(cmapObj)) { - cmapObj = xref.fetch(cmapObj); - } - if (isName(cmapObj)) { - 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; i < cmap.length; 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; j < tokens.length; j += 3) { - var startRange = tokens[j]; - var endRange = tokens[j + 1]; - var code = tokens[j + 2]; - while (startRange < endRange) { - var mapping = map[startRange] || {}; - mapping.unicode = code++; - map[startRange] = mapping; - ++startRange; - } - } - break; - - case 'endcidchar': - case 'endbfchar': - for (var j = 0; j < tokens.length; j += 2) { - var index = tokens[j]; - var code = tokens[j + 1]; - var mapping = map[index] || {}; - mapping.unicode = code; - map[index] = mapping; - } - 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); - } + glyphsWidths = metrics.widths; + defaultWidth = metrics.defaultWidth; } } } - return glyphs; + + properties.defaultWidth = defaultWidth; + properties.widths = glyphsWidths; }, - getBaseFontMetricsAndMap: function getBaseFontMetricsAndMap(name) { - var map = {}; - if (/^Symbol(-?(Bold|Italic))*$/.test(name)) { - // special case for symbols - var encoding = Encodings.symbolsEncoding.slice(); - for (var i = 0, n = encoding.length, j; i < n; i++) { - if (!(j = encoding[i])) - continue; - map[i] = GlyphsUnicode[j] || 0; - } - } - - var defaultWidth = 0; - var widths = Metrics[stdFontMap[name] || name]; - if (isNum(widths)) { - defaultWidth = widths; - widths = null; + 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 || [], - map: map + widths: widths }; }, @@ -755,6 +713,7 @@ var PartialEvaluator = (function partialEvaluator() { assertWellFormed(isName(type), 'invalid font Subtype'); composite = true; } + var maxCharIndex = composite ? 0xFFFF : 0xFF; var descriptor = xref.fetchIfRef(dict.get('FontDescriptor')); if (!descriptor) { @@ -773,18 +732,16 @@ var PartialEvaluator = (function partialEvaluator() { // Using base font name as a font name. baseFontName = baseFontName.name.replace(/[,_]/g, '-'); - var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName); + var metrics = this.getBaseFontMetrics(baseFontName); var properties = { type: type.name, - encoding: metricsAndMap.map, - differences: [], - widths: metricsAndMap.widths, - defaultWidth: metricsAndMap.defaultWidth, + widths: metrics.widths, + defaultWidth: metrics.defaultWidth, firstChar: 0, - lastChar: 256 + lastChar: maxCharIndex }; - this.extractEncoding(dict, xref, properties); + this.extractDataStructures(dict, dict, xref, properties); return { name: baseFontName, @@ -801,26 +758,7 @@ var PartialEvaluator = (function partialEvaluator() { // 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')) || 256; - var defaultWidth = 0; - var glyphWidths = {}; - var encoding = {}; - var widths = xref.fetchIfRef(dict.get('Widths')); - if (widths) { - for (var i = 0, j = firstChar; i < widths.length; i++, j++) - glyphWidths[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 metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName.name); - - glyphWidths = metricsAndMap.widths; - defaultWidth = metricsAndMap.defaultWidth; - encoding = metricsAndMap.map; - } - } + var lastChar = xref.fetchIfRef(dict.get('LastChar')) || maxCharIndex; var fontName = xref.fetchIfRef(descriptor.get('FontName')); assertWellFormed(isName(fontName), 'invalid font name'); @@ -853,34 +791,30 @@ var PartialEvaluator = (function partialEvaluator() { fixedPitch: false, fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX, firstChar: firstChar || 0, - lastChar: lastChar || 256, + lastChar: lastChar || maxCharIndex, bbox: descriptor.get('FontBBox'), ascent: descriptor.get('Ascent'), descent: descriptor.get('Descent'), xHeight: descriptor.get('XHeight'), capHeight: descriptor.get('CapHeight'), - defaultWidth: defaultWidth, flags: descriptor.get('Flags'), italicAngle: descriptor.get('ItalicAngle'), - differences: [], - widths: glyphWidths, - encoding: encoding, coded: false }; - properties.glyphs = this.extractEncoding(dict, xref, properties); + 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.glyphs[key].IRQueue = this.getIRQueue(glyphStream, - fontResources, - queueObj, - dependency); + properties.charProcIRQueues[key] = + this.getIRQueue(glyphStream, fontResources, queueObj, dependency); } } diff --git a/src/fonts.js b/src/fonts.js index b027b766a..202481449 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -672,6 +672,44 @@ var UnicodeRanges = [ { 'begin': 0x1F030, 'end': 0x1F09F } // Domino Tiles ]; +var MacStandardGlyphOrdering = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', + 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft', + 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', + 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', + 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b', + 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', + 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', + 'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', + 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis', + 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', + 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex', + 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet', + 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', + 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', + 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi', + 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', + 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin', + 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis', + 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', + 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright', + 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', + 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex', + 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', + 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', + 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', + 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron', + 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', + 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', + 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter', + 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', + 'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + function getUnicodeRangeFor(value) { for (var i = 0; i < UnicodeRanges.length; i++) { var range = UnicodeRanges[i]; @@ -681,6 +719,16 @@ function getUnicodeRangeFor(value) { return -1; } +function adaptUnicode(unicode) { + return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ? + unicode + kCmapGlyphOffset : unicode; +} + +function isAdaptedUnicode(unicode) { + return unicode >= kCmapGlyphOffset && + unicode < kCmapGlyphOffset + kSizeOfGlyphArea; +} + /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -692,8 +740,8 @@ function getUnicodeRangeFor(value) { var Font = (function Font() { var constructor = function font_constructor(name, file, properties) { this.name = name; - this.encoding = properties.encoding; this.coded = properties.coded; + this.charProcIRQueues = properties.charProcIRQueues; this.resources = properties.resources; this.sizes = []; @@ -702,6 +750,9 @@ var Font = (function Font() { names = names.split(/[-,_]/g)[0]; this.serif = serifFonts[names] || (name.search(/serif/gi) != -1); + var type = properties.type; + this.type = type; + // If the font is to be ignored, register it like an already loaded font // to avoid the cost of waiting for it be be loaded by the platform. if (properties.ignore) { @@ -709,12 +760,19 @@ var Font = (function Font() { this.loading = false; return; } + + this.differences = properties.differences; + this.widths = properties.widths; + this.defaultWidth = properties.defaultWidth; + this.composite = properties.composite; + this.toUnicode = properties.toUnicode; + this.fontMatrix = properties.fontMatrix; if (properties.type == 'Type3') return; - // Trying to fix encoding using glyph widths and CIDSystemInfo. - this.fixWidths(properties); + // Trying to fix encoding using glyph CIDSystemInfo. + this.loadCidToUnicode(properties); if (!file) { // The file data is not specified. Trying to fix the font name @@ -730,15 +788,14 @@ var Font = (function Font() { // name ArialBlack for example will be replaced by Helvetica. this.black = (name.search(/Black/g) != -1); - this.defaultWidth = properties.defaultWidth; + this.encoding = properties.baseEncoding; + this.noUnicodeAdaptation = true; this.loadedName = fontName.split('-')[0]; - this.composite = properties.composite; this.loading = false; return; } var data; - var type = properties.type; switch (type) { case 'Type1': case 'CIDFontType0': @@ -767,11 +824,11 @@ var Font = (function Font() { } this.data = data; - this.type = type; this.fontMatrix = properties.fontMatrix; - this.defaultWidth = properties.defaultWidth; + this.encoding = properties.baseEncoding; + this.hasShortCmap = properties.hasShortCmap; + this.hasEncoding = properties.hasEncoding; this.loadedName = getUniqueName(); - this.composite = properties.composite; this.loading = true; }; @@ -987,7 +1044,7 @@ var Font = (function Font() { format314); }; - function createOS2Table(properties, override) { + function createOS2Table(properties, charstrings, override) { override = override || { unitsPerEm: 0, yMax: 0, @@ -1004,26 +1061,31 @@ var Font = (function Font() { var firstCharIndex = null; var lastCharIndex = 0; - var encoding = properties.encoding; - for (var index in encoding) { - var code = encoding[index].unicode; - if (firstCharIndex > code || !firstCharIndex) - firstCharIndex = code; - if (lastCharIndex < code) - lastCharIndex = code; + if (charstrings) { + for (var i = 0; i < charstrings.length; ++i) { + var code = charstrings[i].unicode; + if (firstCharIndex > code || !firstCharIndex) + firstCharIndex = code; + if (lastCharIndex < code) + lastCharIndex = code; - var position = getUnicodeRangeFor(code); - if (position < 32) { - ulUnicodeRange1 |= 1 << position; - } else if (position < 64) { - ulUnicodeRange2 |= 1 << position - 32; - } else if (position < 96) { - ulUnicodeRange3 |= 1 << position - 64; - } else if (position < 123) { - ulUnicodeRange4 |= 1 << position - 96; - } else { - error('Unicode ranges Bits > 123 are reserved for internal usage'); + var position = getUnicodeRangeFor(code); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + error('Unicode ranges Bits > 123 are reserved for internal usage'); + } } + } else { + // TODO + firstCharIndex = 0; + lastCharIndex = 255; } var unitsPerEm = override.unitsPerEm || kPDFGlyphSpaceUnits; @@ -1208,6 +1270,29 @@ var Font = (function Font() { }; }; + function createGlyphNameMap(glyphs, ids, properties) { + var glyphNames = properties.glyphNames; + if (!glyphNames) { + properties.glyphNameMap = {}; + return; + } + var glyphsLength = glyphs.length; + var glyphNameMap = {}; + var encoding = []; + for (var i = 0; i < glyphsLength; ++i) { + var glyphName = glyphNames[ids[i]]; + if (!glyphName) + continue; + var unicode = glyphs[i].unicode; + glyphNameMap[glyphName] = unicode; + var code = glyphs[i].code; + encoding[code] = glyphName; + } + properties.glyphNameMap = glyphNameMap; + if (!properties.hasEncoding) + properties.baseEncoding = encoding; + } + function replaceCMapTable(cmap, font, properties) { var start = (font.start ? font.start : 0) + cmap.offset; font.pos = start; @@ -1262,7 +1347,6 @@ var Font = (function Font() { cmap.data[i] = data.charCodeAt(i); } - var encoding = properties.encoding; for (var i = 0; i < numRecords; i++) { var table = tables[i]; font.pos = start + table.offset; @@ -1271,29 +1355,88 @@ var Font = (function Font() { var length = int16(font.getBytes(2)); var language = int16(font.getBytes(2)); - if (format == 4) { - return cmap.data; - } else if (format == 0) { + if (format == 0) { // Characters below 0x20 are controls characters that are hardcoded // into the platform so if some characters in the font are assigned // under this limit they will not be displayed so let's rewrite the // CMap. var glyphs = []; - var deltas = []; + var ids = []; for (var j = 0; j < 256; j++) { var index = font.getByte(); if (index) { - deltas.push(index); - - var unicode = j + kCmapGlyphOffset; - var mapping = encoding[j] || {}; - mapping.unicode = unicode; - encoding[j] = mapping; - glyphs.push({ unicode: unicode }); + var unicode = adaptUnicode(j); + glyphs.push({ unicode: unicode, code: j }); + ids.push(index); } } - return cmap.data = createCMapTable(glyphs, deltas); + properties.hasShortCmap = true; + + createGlyphNameMap(glyphs, ids, properties); + return cmap.data = createCMapTable(glyphs, ids); + } else if (format == 4) { + // re-creating the table in format 4 since the encoding + // might be changed + var segCount = (int16(font.getBytes(2)) >> 1); + font.getBytes(6); // skipping range fields + var segIndex, segments = []; + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments.push({ end: int16(font.getBytes(2)) }); + } + font.getBytes(2); + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments[segIndex].start = int16(font.getBytes(2)); + } + + for (segIndex = 0; segIndex < segCount; segIndex++) { + segments[segIndex].delta = int16(font.getBytes(2)); + } + + var offsetsCount = 0; + for (segIndex = 0; segIndex < segCount; segIndex++) { + var segment = segments[segIndex]; + var rangeOffset = int16(font.getBytes(2)); + if (!rangeOffset) { + segment.offsetIndex = -1; + continue; + } + + var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); + segment.offsetIndex = offsetIndex; + offsetsCount = Math.max(offsetsCount, offsetIndex + + segment.end - segment.start + 1); + } + + var offsets = []; + for (var j = 0; j < offsetsCount; j++) + offsets.push(int16(font.getBytes(2))); + + var glyphs = [], ids = []; + + for (segIndex = 0; segIndex < segCount; segIndex++) { + var segment = segments[segIndex]; + var start = segment.start, end = segment.end; + var delta = segment.delta, offsetIndex = segment.offsetIndex; + + for (var j = start; j <= end; j++) { + if (j == 0xFFFF) + continue; + + var glyphCode = offsetIndex < 0 ? j : + offsets[offsetIndex + j - start]; + glyphCode = (glyphCode + delta) & 0xFFFF; + if (glyphCode == 0) + continue; + + var unicode = adaptUnicode(j); + glyphs.push({ unicode: unicode, code: j }); + ids.push(glyphCode); + } + } + + createGlyphNameMap(glyphs, ids, properties); + return cmap.data = createCMapTable(glyphs, ids); } else if (format == 6) { // Format 6 is a 2-bytes dense mapping, which means the font data // lives glue together even if they are pretty far in the unicode @@ -1305,15 +1448,15 @@ var Font = (function Font() { var glyphs = []; var ids = []; - for (var j = 0; j < firstCode + entryCount; j++) { - var code = (j >= firstCode) ? int16(font.getBytes(2)) : j; - glyphs.push({ unicode: j + kCmapGlyphOffset }); - ids.push(code); - - var mapping = encoding[j] || {}; - mapping.unicode = glyphs[j].unicode; - encoding[j] = mapping; + for (var j = 0; j < entryCount; j++) { + var glyphCode = int16(font.getBytes(2)); + var code = firstCode + j; + var unicode = adaptUnicode(code); + glyphs.push({ unicode: unicode, code: code }); + ids.push(glyphCode); } + + createGlyphNameMap(glyphs, ids, properties); return cmap.data = createCMapTable(glyphs, ids); } } @@ -1396,6 +1539,52 @@ var Font = (function Font() { } } + function readGlyphNameMap(post, properties) { + var start = (font.start ? font.start : 0) + post.offset; + font.pos = start; + + var length = post.length, end = start + length; + var version = int32(font.getBytes(4)); + // skip rest to the tables + font.getBytes(28); + + var glyphNames; + switch (version) { + case 0x00010000: + glyphNames = MacStandardGlyphOrdering; + break; + case 0x00020000: + var numGlyphs = int16(font.getBytes(2)); + var glyphNameIndexes = []; + for (var i = 0; i < numGlyphs; ++i) + glyphNameIndexes.push(int16(font.getBytes(2))); + var customNames = []; + while (font.pos < end) { + var stringLength = font.getByte(); + var string = ''; + for (var i = 0; i < stringLength; ++i) + string += font.getChar(); + customNames.push(string); + } + glyphNames = []; + for (var i = 0; i < numGlyphs; ++i) { + var j = glyphNameIndexes[i]; + if (j < 258) { + glyphNames.push(MacStandardGlyphOrdering[j]); + continue; + } + glyphNames.push(customNames[j - 258]); + } + break; + case 0x00030000: + break; + default: + warn('Unknown/unsupported post table version ' + version); + break; + } + properties.glyphNames = glyphNames; + } + // Check that required tables are present var requiredTables = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'post']; @@ -1403,7 +1592,7 @@ var Font = (function Font() { var header = readOpenTypeHeader(font); var numTables = header.numTables; - var cmap, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf; + var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf; var tables = []; for (var i = 0; i < numTables; i++) { var table = readTableEntry(font); @@ -1411,6 +1600,8 @@ var Font = (function Font() { if (index != -1) { if (table.tag == 'cmap') cmap = table; + else if (table.tag == 'post') + post = table; else if (table.tag == 'maxp') maxp = table; else if (table.tag == 'hhea') @@ -1461,7 +1652,7 @@ var Font = (function Font() { tables.push({ tag: 'OS/2', - data: stringToArray(createOS2Table(properties, override)) + data: stringToArray(createOS2Table(properties, null, override)) }); } @@ -1486,6 +1677,11 @@ var Font = (function Font() { hhea.data[11] = 0xFF; } + // The 'post' table has glyphs names. + if (post) { + readGlyphNameMap(post, properties); + } + // Replace the old CMAP table with a shiny new one if (properties.type == 'CIDFontType2') { // Type2 composite fonts map characters directly to glyphs so the cmap @@ -1503,28 +1699,17 @@ var Font = (function Font() { tables.push(cmap); } - var encoding = properties.encoding, i; - - // offsetting glyphs to avoid problematic unicode ranges - for (i in encoding) { - if (encoding.hasOwnProperty(i)) { - var unicode = encoding[i].unicode; - if (unicode <= 0x1f || - (unicode >= 127 && unicode < kSizeOfGlyphArea)) - encoding[i].unicode += kCmapGlyphOffset; - } - } - var glyphs = []; for (i = 1; i < numGlyphs; i++) { - glyphs.push({ - unicode: i <= 0x1f || (i >= 127 && i < kSizeOfGlyphArea) ? - i + kCmapGlyphOffset : i - }); + if (isAdaptedUnicode(i)) + continue; + + glyphs.push({ unicode: adaptUnicode(i) }); } cmap.data = createCMapTable(glyphs); } else { replaceCMapTable(cmap, font, properties); + this.glyphNameMap = properties.glyphNameMap; } // Rewrite the 'post' table if needed @@ -1598,12 +1783,29 @@ var Font = (function Font() { var charstrings = font.charstrings; properties.fixedPitch = isFixedPitch(charstrings); + var glyphNameMap = {}; + for (var i = 0; i < charstrings.length; ++i) { + var charstring = charstrings[i]; + glyphNameMap[charstring.glyph] = charstring.unicode; + } + this.glyphNameMap = glyphNameMap; + + if (!properties.hasEncoding && (properties.subtype == 'Type1C' || + properties.subtype == 'CIDFontType0C')) { + var encoding = []; + for (var i = 0; i < charstrings.length; ++i) { + var charstring = charstrings[i]; + encoding[charstring.code] = charstring.glyph; + } + properties.baseEncoding = encoding; + } + var fields = { // PostScript Font Program 'CFF ': font.data, // OS/2 and Windows Specific metrics - 'OS/2': stringToArray(createOS2Table(properties)), + 'OS/2': stringToArray(createOS2Table(properties, charstrings)), // Character to glyphs mapping 'cmap': createCMapTable(charstrings.slice(), @@ -1657,9 +1859,8 @@ var Font = (function Font() { // Horizontal metrics 'hmtx': (function fontFieldsHmtx() { var hmtx = '\x00\x00\x00\x00'; // Fake .notdef - for (var i = 0; i < charstrings.length; i++) { + for (var i = 0; i < charstrings.length; i++) hmtx += string16(charstrings[i].width) + string16(0); - } return stringToArray(hmtx); })(), @@ -1688,82 +1889,48 @@ var Font = (function Font() { return stringToArray(otf.file); }, - fixWidths: function font_fixWidths(properties) { - if (properties.type !== 'CIDFontType0' && - properties.type !== 'CIDFontType2') - return; - - var encoding = properties.encoding; - if (encoding[0]) + loadCidToUnicode: function font_loadCidToUnicode(properties) { + if (properties.cidToGidMap) { + this.cidToUnicode = properties.cidToGidMap; return; - var glyphsWidths = properties.widths; - if (!glyphsWidths) + } + + if (!properties.cidSystemInfo) return; - var defaultWidth = properties.defaultWidth; + var cidToUnicodeMap = []; + this.cidToUnicode = cidToUnicodeMap; + var cidSystemInfo = properties.cidSystemInfo; var cidToUnicode; if (cidSystemInfo) { cidToUnicode = CIDToUnicodeMaps[ cidSystemInfo.registry + '-' + cidSystemInfo.ordering]; } - if (!cidToUnicode) { - // the font is directly characters to glyphs with no encoding - // so create an identity encoding - for (i = 0; i < 0xD800; i++) { - var width = glyphsWidths[i]; - encoding[i] = { - unicode: i, - width: isNum(width) ? width : defaultWidth - }; - } - // skipping surrogates + 256-user defined - for (i = 0xE100; i <= 0xFFFF; i++) { - var width = glyphsWidths[i]; - encoding[i] = { - unicode: i, - width: isNum(width) ? width : defaultWidth - }; - } - return; - } - encoding[0] = { unicode: 0, width: 0 }; + if (!cidToUnicode) + return; // identity encoding + var glyph = 1, i, j, k; for (i = 0; i < cidToUnicode.length; ++i) { var unicode = cidToUnicode[i]; - var width; if (isArray(unicode)) { var length = unicode.length; - width = glyphsWidths[glyph]; - for (j = 0; j < length; j++) { - k = unicode[j]; - encoding[k] = { - unicode: k, - width: isNum(width) ? width : defaultWidth - }; - } + for (j = 0; j < length; j++) + cidToUnicodeMap[unicode[j]] = glyph; glyph++; } else if (typeof unicode === 'object') { var fillLength = unicode.f; if (fillLength) { k = unicode.c; for (j = 0; j < fillLength; ++j) { - width = glyphsWidths[glyph++]; - encoding[k] = { - unicode: k, - width: isNum(width) ? width : defaultWidth - }; + cidToUnicodeMap[k] = glyph++; k++; } } else glyph += unicode.s; } else if (unicode) { - width = glyphsWidths[glyph++]; - encoding[unicode] = { - unicode: unicode, - width: isNum(width) ? width : defaultWidth - }; + cidToUnicodeMap[unicode] = glyph++; } else glyph++; } @@ -1798,6 +1965,74 @@ var Font = (function Font() { return rule; }, + charToGlyph: function fonts_charToGlyph(charcode) { + var unicode, width, codeIRQueue; + + var width = this.widths[charcode]; + + switch (this.type) { + case 'CIDFontType0': + if (this.noUnicodeAdaptation) { + width = this.widths[this.cidToUnicode[charcode]]; + unicode = charcode; + break; + } + unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode); + break; + case 'CIDFontType2': + if (this.noUnicodeAdaptation) { + width = this.widths[this.cidToUnicode[charcode]]; + unicode = charcode; + break; + } + unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode); + break; + case 'Type1': + var glyphName = this.differences[charcode] || this.encoding[charcode]; + if (this.noUnicodeAdaptation) { + width = this.widths[glyphName]; + unicode = GlyphsUnicode[glyphName] || charcode; + break; + } + unicode = this.glyphNameMap[glyphName] || + adaptUnicode(GlyphsUnicode[glyphName] || charcode); + break; + case 'Type3': + var glyphName = this.differences[charcode] || this.encoding[charcode]; + codeIRQueue = this.charProcIRQueues[glyphName]; + unicode = charcode; + break; + case 'TrueType': + var glyphName = this.differences[charcode] || this.encoding[charcode]; + if (!glyphName) + glyphName = Encodings.StandardEncoding[charcode]; + if (!this.hasEncoding) { + width = this.widths[charcode] || this.widths[glyphName]; + unicode = this.noUnicodeAdaptation ? + charcode : adaptUnicode(charcode); + break; + } + if (this.hasShortCmap) { + var j = Encodings.MacRomanEncoding.indexOf(glyphName); + unicode = j >= 0 ? adaptUnicode(j) : + this.glyphNameMap[glyphName]; + } else { + unicode = glyphName in GlyphsUnicode ? + adaptUnicode(GlyphsUnicode[glyphName]) : + this.glyphNameMap[glyphName]; + } + break; + default: + warn('Unsupported font type: ' + this.type); + break; + } + return { + unicode: unicode, + width: isNum(width) ? width : this.defaultWidth, + codeIRQueue: codeIRQueue + }; + }, + charsToGlyphs: function fonts_chars2Glyphs(chars) { var charsCache = this.charsCache; var glyphs; @@ -1813,11 +2048,6 @@ var Font = (function Font() { if (!charsCache) charsCache = this.charsCache = Object.create(null); - // translate the string using the font's encoding - var encoding = this.encoding; - if (!encoding) - return chars; - glyphs = []; if (this.composite) { @@ -1829,14 +2059,7 @@ var Font = (function Font() { // loop should never end on the last byte for (var i = 0; i < length; i++) { var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]); - var glyph = encoding[charcode]; - if ('undefined' == typeof(glyph)) { - warn('Unencoded charcode ' + charcode); - glyph = { - unicode: charcode, - width: this.defaultWidth - }; - } + var glyph = this.charToGlyph(charcode); glyphs.push(glyph); // placing null after each word break charcode (ASCII SPACE) if (charcode == 0x20) @@ -1846,14 +2069,7 @@ var Font = (function Font() { else { for (var i = 0; i < chars.length; ++i) { var charcode = chars.charCodeAt(i); - var glyph = encoding[charcode]; - if ('undefined' == typeof(glyph)) { - warn('Unencoded charcode ' + charcode); - glyph = { - unicode: charcode, - width: this.defaultWidth - }; - } + var glyph = this.charToGlyph(charcode); glyphs.push(glyph); if (charcode == 0x20) glyphs.push(null); @@ -2107,6 +2323,17 @@ var Type1Parser = function type1Parser() { warn('Support for Type1 command ' + value + ' (' + escape + ') is not implemented in charstring: ' + charstring); + if (value == 12) { + // we know how to ignore only some the Type1 commands + switch (escape) { + case 7: + charstring.push('drop', 'drop', 'drop', 'drop'); + continue; + case 8: + charstring.push('drop'); + continue; + } + } } value = command; @@ -2327,24 +2554,30 @@ var Type1Parser = function type1Parser() { properties.fontMatrix = matrix; break; case '/Encoding': - var size = parseInt(getToken(), 10); - getToken(); // read in 'array' + var encodingArg = getToken(); + var encoding; + if (!/^\d+$/.test(encodingArg)) { + // encoding name is specified + encoding = Encodings[encodingArg]; + } else { + encoding = []; + var size = parseInt(encodingArg, 10); + getToken(); // read in 'array' - for (var j = 0; j < size; j++) { - var token = getToken(); - if (token == 'dup') { - var index = parseInt(getToken(), 10); - var glyph = getToken(); - - if ('undefined' == typeof(properties.differences[index])) { - var mapping = properties.encoding[index] || {}; - mapping.unicode = GlyphsUnicode[glyph] || index; - properties.glyphs[glyph] = properties.encoding[index] = - mapping; + for (var j = 0; j < size; j++) { + var token = getToken(); + if (token == 'dup') { + var index = parseInt(getToken(), 10); + var glyph = getToken(); + encoding[index] = glyph; + getToken(); // read the in 'put' } - getToken(); // read the in 'put' } } + if (!properties.hasEncoding && encoding) { + properties.baseEncoding = encoding; + break; + } break; } token = ''; @@ -2487,46 +2720,38 @@ CFF.prototype = { }, encodeNumber: function cff_encodeNumber(value) { + // some of the fonts has ouf-of-range values + // they are just arithmetic overflows + // make sanitizer happy + value |= 0; if (value >= -32768 && value <= 32767) { return '\x1c' + String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); - } else if (value >= (-2147483648) && value <= 2147483647) { + } else { return '\x1d' + String.fromCharCode((value >> 24) & 0xFF) + String.fromCharCode((value >> 16) & 0xFF) + String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); } - error('Value: ' + value + ' is not allowed'); - return null; }, getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs, properties) { var charstrings = []; - var missings = []; - for (var i = 0; i < glyphs.length; i++) { - var glyph = glyphs[i]; - var mapping = properties.glyphs[glyph.glyph]; - if (!mapping) { - if (glyph.glyph != '.notdef') - missings.push(glyph.glyph); - } else { - charstrings.push({ - glyph: glyph.glyph, - unicode: mapping.unicode, - charstring: glyph.data, - width: glyph.width, - lsb: glyph.lsb - }); - } + var item = glyphs[i]; + charstrings.push({ + glyph: item.glyph, + unicode: adaptUnicode(i), + gid: i, + charstring: item.data, + width: item.width, + lsb: item.lsb + }); } - if (missings.length) - warn(missings + ' does not have unicode in the glyphs dictionary'); - charstrings.sort(function charstrings_sort(a, b) { return a.unicode - b.unicode; }); @@ -2807,6 +3032,20 @@ var Type2CFF = (function type2CFF() { var encoding = this.parseEncoding(topDict.Encoding, properties, strings, charset); + var charset, encoding; + var isCIDFont = properties.subtype == 'CIDFontType0C'; + if (isCIDFont) { + charset = []; + charset.length = charStrings.length; + encoding = this.parseCidMap(topDict.charset, + charStrings.length); + } else { + charset = this.parseCharsets(topDict.charset, + charStrings.length, strings); + encoding = this.parseEncoding(topDict.Encoding, properties, + strings, charset); + } + // The font sanitizer does not support CFF encoding with a // supplement, since the encoding is not really use to map // between gid to glyph, let's overwrite what is declared in @@ -2863,80 +3102,46 @@ var Type2CFF = (function type2CFF() { getCharStrings: function cff_charstrings(charsets, encoding, privateDict, properties) { - var defaultWidth = privateDict['defaultWidthX']; var charstrings = []; - var firstChar = properties.firstChar; - var glyphMap = {}; + var unicodeUsed = []; + var unassignedUnicodeItems = []; for (var i = 0; i < charsets.length; i++) { var glyph = charsets[i]; + var encodingFound = false; for (var charcode in encoding) { - if (encoding[charcode] == i) - glyphMap[glyph] = charcode | 0; + if (encoding[charcode] == i) { + var code = charcode | 0; + charstrings.push({ + unicode: adaptUnicode(code), + code: code, + gid: i, + glyph: glyph + }); + unicodeUsed[code] = true; + encodingFound = true; + break; + } + } + if (!encodingFound) { + unassignedUnicodeItems.push(i); } } - var differences = properties.differences; - for (var i = 0; i < differences.length; ++i) { - var glyph = differences[i]; - if (!glyph) - continue; - var oldGlyph = charsets[i]; - if (oldGlyph) - delete glyphMap[oldGlyph]; - glyphMap[differences[i]] = i; - } - - var glyphs = properties.glyphs; - for (var i = 1; i < charsets.length; i++) { - var glyph = charsets[i]; - var code = glyphMap[glyph] || 0; - - var mapping = glyphs[code] || glyphs[glyph] || { width: defaultWidth }; - var unicode = mapping.unicode; - - if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255)) - unicode += kCmapGlyphOffset; - - var width = (mapping.hasOwnProperty('width') && isNum(mapping.width)) ? - mapping.width : defaultWidth; - properties.encoding[code] = { - unicode: unicode, - width: width - }; - + var nextUnusedUnicode = 0x21; + for (var j = 0; j < unassignedUnicodeItems.length; ++j) { + var i = unassignedUnicodeItems[j]; + // giving unicode value anyway + while (unicodeUsed[nextUnusedUnicode]) + nextUnusedUnicode++; + var code = nextUnusedUnicode++; charstrings.push({ - unicode: unicode, - width: width, + unicode: adaptUnicode(code), code: code, - gid: i + gid: i, + glyph: charsets[i] }); } - // sort the array by the unicode value - charstrings.sort(function type2CFFGetCharStringsSort(a, b) { - return a.unicode - b.unicode; - }); - - // remove duplicates -- they might appear during selection: - // properties.glyphs[code] || properties.glyphs[glyph] - var nextUnusedUnicode = kCmapGlyphOffset + 0x0020; - var lastUnicode = charstrings[0].unicode, wasModified = false; - for (var i = 1; i < charstrings.length; ++i) { - if (lastUnicode != charstrings[i].unicode) { - lastUnicode = charstrings[i].unicode; - continue; - } - // duplicate found -- keeping the item that has - // different code and unicode, that one created - // as result of modification of the base encoding - var duplicateIndex = - charstrings[i].unicode == charstrings[i].code ? i : i - 1; - charstrings[duplicateIndex].unicode = nextUnusedUnicode++; - wasModified = true; - } - if (!wasModified) - return charstrings; - // sort the array by the unicode value (again) charstrings.sort(function type2CFFGetCharStringsSort(a, b) { return a.unicode - b.unicode; @@ -2964,8 +3169,8 @@ var Type2CFF = (function type2CFF() { if (pos == 0 || pos == 1) { var gid = 1; - var baseEncoding = pos ? Encodings.ExpertEncoding.slice() : - Encodings.StandardEncoding.slice(); + var baseEncoding = pos ? Encodings.ExpertEncoding : + Encodings.StandardEncoding; for (var i = 0; i < charset.length; i++) { var index = baseEncoding.indexOf(charset[i]); if (index != -1) @@ -2985,8 +3190,8 @@ var Type2CFF = (function type2CFF() { var gid = 1; for (var i = 0; i < rangesCount; i++) { var start = bytes[pos++]; - var count = bytes[pos++]; - for (var j = start; j <= start + count; j++) + var left = bytes[pos++]; + for (var j = start; j <= start + left; j++) encoding[j] = gid++; } break; @@ -3047,6 +3252,46 @@ var Type2CFF = (function type2CFF() { } return charset; }, + + parseCidMap: function cff_parsecharsets(pos, length) { + var bytes = this.bytes; + var format = bytes[pos++]; + + var encoding = {}; + var map = {encoding: encoding}; + + encoding[0] = 0; + + var gid = 1; + switch (format) { + case 0: + while (gid < length) { + var cid = (bytes[pos++] << 8) | bytes[pos++]; + encoding[cid] = gid++; + } + break; + case 1: + while (gid < length) { + var cid = (bytes[pos++] << 8) | bytes[pos++]; + var count = bytes[pos++]; + for (var i = 0; i <= count; i++) + encoding[cid++] = gid++; + } + break; + case 2: + while (gid < length) { + var cid = (bytes[pos++] << 8) | bytes[pos++]; + var count = (bytes[pos++] << 8) | bytes[pos++]; + for (var i = 0; i <= count; i++) + encoding[cid++] = gid++; + } + break; + default: + error('Unknown charset format'); + } + return map; + }, + getPrivDict: function cff_getprivdict(baseDict, strings) { var dict = {}; @@ -3108,6 +3353,17 @@ var Type2CFF = (function type2CFF() { case 18: dict['Private'] = value; break; + case 3102: + case 3103: + case 3104: + case 3105: + case 3106: + case 3107: + case 3108: + case 3109: + case 3110: + dict['cidOperatorPresent'] = true; + break; default: TODO('interpret top dict key'); } @@ -3220,6 +3476,15 @@ var Type2CFF = (function type2CFF() { } var b = (b << 8) | op; } + if (!operands.length && b == 8 && + dict[pos + 1] == 9) { + // no operands for FamilyBlues, removing the key + // and next one is FamilyOtherBlues - skipping them + // also replacing FamilyBlues to pass sanitizer + dict[pos] = 139; + pos += 2; + continue; + } entries.push([b, operands]); operands = []; ++pos; From 38372ee2f215191c88c1fc538835942e59be8c0c Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 29 Oct 2011 08:45:29 -0500 Subject: [PATCH 02/35] TrueType encoding for files without font data --- src/fonts.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 202481449..6202b3279 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -766,6 +766,7 @@ var Font = (function Font() { this.defaultWidth = properties.defaultWidth; this.composite = properties.composite; this.toUnicode = properties.toUnicode; + this.hasEncoding = properties.hasEncoding; this.fontMatrix = properties.fontMatrix; if (properties.type == 'Type3') @@ -827,7 +828,6 @@ var Font = (function Font() { this.fontMatrix = properties.fontMatrix; this.encoding = properties.baseEncoding; this.hasShortCmap = properties.hasShortCmap; - this.hasEncoding = properties.hasEncoding; this.loadedName = getUniqueName(); this.loading = true; }; @@ -2006,10 +2006,14 @@ var Font = (function Font() { var glyphName = this.differences[charcode] || this.encoding[charcode]; if (!glyphName) glyphName = Encodings.StandardEncoding[charcode]; + if (!isNum(width)) + width = this.widths[glyphName]; + if (this.noUnicodeAdaptation) { + unicode = GlyphsUnicode[glyphName] || charcode; + break; + } if (!this.hasEncoding) { - width = this.widths[charcode] || this.widths[glyphName]; - unicode = this.noUnicodeAdaptation ? - charcode : adaptUnicode(charcode); + unicode = adaptUnicode(charcode); break; } if (this.hasShortCmap) { From 343e174951ce6745d4761627882dcfd4e658111e Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 29 Oct 2011 09:29:19 -0500 Subject: [PATCH 03/35] Change cmap encoding for Type 1 --- src/fonts.js | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 6202b3279..5d70bf5e4 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -729,6 +729,12 @@ function isAdaptedUnicode(unicode) { unicode < kCmapGlyphOffset + kSizeOfGlyphArea; } +function isSpecialUnicode(unicode) { + return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) || + unicode >= kCmapGlyphOffset && + unicode < kCmapGlyphOffset + kSizeOfGlyphArea; +} + /** * 'Font' is the class the outside world should use, it encapsulate all the font * decoding logics whatever type it is (assuming the font type is supported). @@ -2744,11 +2750,28 @@ CFF.prototype = { getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs, properties) { var charstrings = []; - for (var i = 0; i < glyphs.length; i++) { + var reverseMapping = {}; + var encoding = properties.baseEncoding; + var differences = properties.differences; + var i, length; + for (i = 0, length = encoding.length; i < length; ++i) { + if (encoding[i] && !isSpecialUnicode(i)) + reverseMapping[encoding[i]] = i; + } + for (i = 0, length = differences.length; i < length; ++i) { + if (differences[i] && !isSpecialUnicode(i)) + reverseMapping[differences[i]] = i; + } + reverseMapping['.notdef'] = 0; + var unusedUnicode = kCmapGlyphOffset; + for (i = 0, length = glyphs.length; i < length; i++) { var item = glyphs[i]; + var glyphName = item.glyph; + var unicode = glyphName in reverseMapping ? + reverseMapping[glyphName] : unusedUnicode++; charstrings.push({ - glyph: item.glyph, - unicode: adaptUnicode(i), + glyph: glyphName, + unicode: unicode, gid: i, charstring: item.data, width: item.width, From a1d2c1c6d34ea6a7f4f66a228429e0941335b97e Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 29 Oct 2011 14:19:13 -0500 Subject: [PATCH 04/35] Fix Type1 width; TrueType special characters --- src/fonts.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 5d70bf5e4..a70d9ff4f 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1996,7 +1996,8 @@ var Font = (function Font() { case 'Type1': var glyphName = this.differences[charcode] || this.encoding[charcode]; if (this.noUnicodeAdaptation) { - width = this.widths[glyphName]; + if (!isNum(width)) + width = this.widths[glyphName]; unicode = GlyphsUnicode[glyphName] || charcode; break; } @@ -2024,7 +2025,7 @@ var Font = (function Font() { } if (this.hasShortCmap) { var j = Encodings.MacRomanEncoding.indexOf(glyphName); - unicode = j >= 0 ? adaptUnicode(j) : + unicode = j >= 0 && !isSpecialUnicode(j) ? j : this.glyphNameMap[glyphName]; } else { unicode = glyphName in GlyphsUnicode ? From aaad824f772c529ee54757661925581a72c5af42 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 29 Oct 2011 21:55:43 -0500 Subject: [PATCH 05/35] Fixing duplicates in cmap --- src/fonts.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index a70d9ff4f..62650b10f 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -1432,7 +1432,7 @@ var Font = (function Font() { var glyphCode = offsetIndex < 0 ? j : offsets[offsetIndex + j - start]; glyphCode = (glyphCode + delta) & 0xFFFF; - if (glyphCode == 0) + if (glyphCode == 0 || isAdaptedUnicode(j)) continue; var unicode = adaptUnicode(j); @@ -1457,6 +1457,9 @@ var Font = (function Font() { for (var j = 0; j < entryCount; j++) { var glyphCode = int16(font.getBytes(2)); var code = firstCode + j; + if (isAdaptedUnicode(glyphCode)) + continue; + var unicode = adaptUnicode(code); glyphs.push({ unicode: unicode, code: code }); ids.push(glyphCode); @@ -2754,14 +2757,23 @@ CFF.prototype = { var reverseMapping = {}; var encoding = properties.baseEncoding; var differences = properties.differences; - var i, length; + var usedIn = []; + var i, length, glyphName; for (i = 0, length = encoding.length; i < length; ++i) { - if (encoding[i] && !isSpecialUnicode(i)) - reverseMapping[encoding[i]] = i; + glyphName = encoding[i]; + if (!glyphName || isSpecialUnicode(i)) + continue; + reverseMapping[glyphName] = i; + usedIn[i] = glyphName; } for (i = 0, length = differences.length; i < length; ++i) { - if (differences[i] && !isSpecialUnicode(i)) - reverseMapping[differences[i]] = i; + glyphName = differences[i]; + if (!glyphName || isSpecialUnicode(i)) + continue; + if (usedIn[i]) + delete reverseMapping[usedIn[i]]; + reverseMapping[glyphName] = i; + usedIn[i] = glyphName; } reverseMapping['.notdef'] = 0; var unusedUnicode = kCmapGlyphOffset; From 5d347b0ddbbc4c7731798402bade6a65ef64f929 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sun, 30 Oct 2011 23:32:06 -0500 Subject: [PATCH 06/35] removing differences from the cmap --- src/fonts.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 62650b10f..c3fcb3c8a 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -2756,24 +2756,12 @@ CFF.prototype = { var charstrings = []; var reverseMapping = {}; var encoding = properties.baseEncoding; - var differences = properties.differences; - var usedIn = []; var i, length, glyphName; for (i = 0, length = encoding.length; i < length; ++i) { glyphName = encoding[i]; if (!glyphName || isSpecialUnicode(i)) continue; reverseMapping[glyphName] = i; - usedIn[i] = glyphName; - } - for (i = 0, length = differences.length; i < length; ++i) { - glyphName = differences[i]; - if (!glyphName || isSpecialUnicode(i)) - continue; - if (usedIn[i]) - delete reverseMapping[usedIn[i]]; - reverseMapping[glyphName] = i; - usedIn[i] = glyphName; } reverseMapping['.notdef'] = 0; var unusedUnicode = kCmapGlyphOffset; From a12419c08469f71e910db864a55a904ea9fff473 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 Nov 2011 08:41:36 -0800 Subject: [PATCH 07/35] Initial devicen colorspace support. --- src/colorspace.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/colorspace.js b/src/colorspace.js index 946a1bdf4..a75b66f0c 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -137,8 +137,14 @@ var ColorSpace = (function colorSpaceColorSpace() { var alt = ColorSpace.parseToIR(cs[2], xref, res); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); return ['SeparationCS', alt, tintFnIR]; - case 'Lab': case 'DeviceN': + if (cs.length > 4) + TODO('devicen color with n channels'); + debugger; + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ['SeparationCS', alt, tintFnIR]; + case 'Lab': default: error('unimplemented color space object "' + mode + '"'); } From 99254891e6b7a08aeb76748af83ab50762a043c7 Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Thu, 10 Nov 2011 18:33:45 +0100 Subject: [PATCH 08/35] Disable work on firefox using feature detection --- src/core.js | 18 +++++++++++------- test/driver.js | 5 +++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/core.js b/src/core.js index 5e97763ab..43c059b56 100644 --- a/src/core.js +++ b/src/core.js @@ -15,10 +15,6 @@ if (!globalScope.PDFJS) { globalScope.PDFJS = {}; } -// Temporarily disabling workers until 'localhost' FF bugfix lands: -// https://bugzilla.mozilla.org/show_bug.cgi?id=683280 -globalScope.PDFJS.disableWorker = true; - // getPdf() // Convenience function to perform binary Ajax GET // Usage: getPdf('http://...', callback) @@ -471,6 +467,7 @@ var PDFDoc = (function pdfDoc() { this.objs = new PDFObjects(); this.pageCache = []; + this.fontsLoading = {}; this.workerReadyPromise = new Promise('workerReady'); // If worker support isn't disabled explicit and the browser has worker @@ -484,7 +481,16 @@ var PDFDoc = (function pdfDoc() { throw 'No PDFJS.workerSrc specified'; } - var worker = new Worker(workerSrc); + var worker + try { + worker = new Worker(workerSrc); + } catch (e) { + // Some versions of FF can't create a worker on localhost, see: + // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 + globalScope.PDFJS.disableWorker = true; + this.setupFakeWorker(); + return; + } var messageHandler = new MessageHandler('main', worker); @@ -505,8 +511,6 @@ var PDFDoc = (function pdfDoc() { } else { this.setupFakeWorker(); } - - this.fontsLoading = {}; } constructor.prototype = { diff --git a/test/driver.js b/test/driver.js index 16375c30b..e84b7c8e0 100644 --- a/test/driver.js +++ b/test/driver.js @@ -7,6 +7,11 @@ 'use strict'; +// Disable worker support for running test as +// https://github.com/mozilla/pdf.js/pull/764#issuecomment-2638944 +// "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1." +PDFJS.disableWorker = true; + var appPath, browser, canvas, currentTaskIdx, manifest, stdout; var inFlightRequests = 0; From 2988b33dcc6e924d9eca83c0ffec56dbb3a2b0e1 Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Thu, 10 Nov 2011 21:09:05 +0200 Subject: [PATCH 09/35] Fix jslint warnings in pattern.js. Also refactor few small issues. --- src/pattern.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pattern.js b/src/pattern.js index 2a31fec4a..72d13d896 100644 --- a/src/pattern.js +++ b/src/pattern.js @@ -19,10 +19,10 @@ var Pattern = (function patternPattern() { constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) { return Shadings[raw[0]].fromIR(ctx, raw); - } + }; - constructor.parseShading = function pattern_shading(shading, matrix, - xref, res, ctx) { + constructor.parseShading = function pattern_shading(shading, matrix, xref, + res, ctx) { var dict = isStream(shading) ? shading.dict : shading; var type = dict.get('ShadingType'); @@ -116,17 +116,18 @@ Shadings.RadialAxial = (function radialAxialShading() { p1 = Util.applyTransform(p1, userMatrix); } + var grad; if (type == 2) - var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); else if (type == 3) - var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); + grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); for (var i = 0, ii = colorStops.length; i < ii; ++i) { var c = colorStops[i]; grad.addColorStop(c[0], c[1]); } return grad; - } + }; constructor.prototype = { getIR: function radialAxialShadingGetIR() { @@ -166,7 +167,7 @@ Shadings.Dummy = (function dummyShading() { constructor.fromIR = function dummyShadingFromIR() { return 'hotpink'; - } + }; constructor.prototype = { getIR: function dummyShadingGetIR() { @@ -242,9 +243,9 @@ var TilingPattern = (function tilingPattern() { graphics.transform.apply(graphics, tmpTranslate); if (bbox && isArray(bbox) && 4 == bbox.length) { - var bboxWidth = bbox[2] - bbox[0]; - var bboxHeight = bbox[3] - bbox[1]; - graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight); + var bboxWidth = x1 - x0; + var bboxHeight = y1 - y0; + graphics.rectangle(x0, y0, bboxWidth, bboxHeight); graphics.clip(); graphics.endPath(); } @@ -264,7 +265,7 @@ var TilingPattern = (function tilingPattern() { return [ 'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType ]; - } + }; TilingPattern.prototype = { getPattern: function tiling_getPattern() { From c198ec432335956ef564fae1a5e9e9c51bfbe53a Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 Nov 2011 14:06:42 -0800 Subject: [PATCH 10/35] Makes new alternate colorspace which handles separation and device. --- src/colorspace.js | 53 ++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/colorspace.js b/src/colorspace.js index a75b66f0c..953bd1c2e 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -24,7 +24,7 @@ var ColorSpace = (function colorSpaceColorSpace() { constructor.parse = function colorSpaceParse(cs, xref, res) { var IR = constructor.parseToIR(cs, xref, res); - if (IR instanceof SeparationCS) + if (IR instanceof AlternateCS) return IR; return constructor.fromIR(IR); @@ -50,11 +50,12 @@ var ColorSpace = (function colorSpaceColorSpace() { var hiVal = IR[2]; var lookup = IR[3]; return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); - case 'SeparationCS': - var alt = IR[1]; - var tintFnIR = IR[2]; + case 'AlternateCS': + var numComps = IR[1]; + var alt = IR[2]; + var tintFnIR = IR[3]; - return new SeparationCS(ColorSpace.fromIR(alt), + return new AlternateCS(numComps, ColorSpace.fromIR(alt), PDFFunction.fromIR(tintFnIR)); default: error('Unkown name ' + name); @@ -134,16 +135,16 @@ var ColorSpace = (function colorSpaceColorSpace() { var lookup = xref.fetchIfRef(cs[3]); return ['IndexedCS', baseIndexedCS, hiVal, lookup]; case 'Separation': - var alt = ColorSpace.parseToIR(cs[2], xref, res); - var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['SeparationCS', alt, tintFnIR]; case 'DeviceN': - if (cs.length > 4) - TODO('devicen color with n channels'); - debugger; + var name = cs[1]; + var numComps = 1; + if (isName(name)) + numComps = 1; + else if (isArray(name)) + numComps = name.length; var alt = ColorSpace.parseToIR(cs[2], xref, res); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['SeparationCS', alt, tintFnIR]; + return ['AlternateCS', numComps, alt, tintFnIR]; case 'Lab': default: error('unimplemented color space object "' + mode + '"'); @@ -157,33 +158,37 @@ var ColorSpace = (function colorSpaceColorSpace() { return constructor; })(); -var SeparationCS = (function separationCS() { - function constructor(base, tintFn) { - this.name = 'Separation'; - this.numComps = 1; +var AlternateCS = (function alternateCS() { + function constructor(numComps, base, tintFn) { + this.name = 'Alternate'; + this.numComps = numComps; this.defaultColor = [1]; this.base = base; this.tintFn = tintFn; } constructor.prototype = { - getRgb: function sepcs_getRgb(color) { + getRgb: function altcs_getRgb(color) { var tinted = this.tintFn(color); return this.base.getRgb(tinted); }, - getRgbBuffer: function sepcs_getRgbBuffer(input, bits) { + getRgbBuffer: function altcs_getRgbBuffer(input, bits) { var tintFn = this.tintFn; var base = this.base; var scale = 1 / ((1 << bits) - 1); var length = input.length; var pos = 0; - var numComps = base.numComps; - var baseBuf = new Uint8Array(numComps * length); + var baseNumComps = base.numComps; + var baseBuf = new Uint8Array(baseNumComps * length); + var numComps = this.numComps; + var scaled = new Array(numComps); - for (var i = 0; i < length; ++i) { - var scaled = input[i] * scale; - var tinted = tintFn([scaled]); - for (var j = 0; j < numComps; ++j) + for (var i = 0; i < length; i += numComps) { + for (var z = 0; z < numComps; ++z) + scaled[z] = input[i + z] * scale; + + var tinted = tintFn(scaled); + for (var j = 0; j < baseNumComps; ++j) baseBuf[pos++] = 255 * tinted[j]; } return base.getRgbBuffer(baseBuf, 8); From 74004b23bba4daa4955476559abcf3a44acb54e3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Thu, 10 Nov 2011 14:23:58 -0800 Subject: [PATCH 11/35] Add comments, fix default color. --- src/colorspace.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/colorspace.js b/src/colorspace.js index 953bd1c2e..b369d0f88 100644 --- a/src/colorspace.js +++ b/src/colorspace.js @@ -158,11 +158,19 @@ var ColorSpace = (function colorSpaceColorSpace() { return constructor; })(); +/** + * Alternate color space handles both Separation and DeviceN color spaces. A + * Separation color space is actually just a DeviceN with one color component. + * Both color spaces use a tinting function to convert colors to a base color + * space. + */ var AlternateCS = (function alternateCS() { function constructor(numComps, base, tintFn) { this.name = 'Alternate'; this.numComps = numComps; - this.defaultColor = [1]; + this.defaultColor = []; + for (var i = 0; i < numComps; ++i) + this.defaultColor.push(1); this.base = base; this.tintFn = tintFn; } From 543e3377de27e9281fd7bef52f9fd9945ef74b08 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Fri, 11 Nov 2011 14:44:47 -0800 Subject: [PATCH 12/35] Adding multi dimensional interpolation and test file. --- src/function.js | 142 ++++++++++++++++++++++++++-------------- test/pdfs/.gitignore | 1 + test/pdfs/devicen.pdf | Bin 0 -> 9550 bytes test/test_manifest.json | 7 ++ 4 files changed, 100 insertions(+), 50 deletions(-) create mode 100644 test/pdfs/devicen.pdf diff --git a/src/function.js b/src/function.js index 80c5a5460..2f9b050dd 100644 --- a/src/function.js +++ b/src/function.js @@ -20,6 +20,7 @@ var PDFFunction = (function pdfFunction() { var array = []; var codeSize = 0; var codeBuf = 0; + var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); var strBytes = str.getBytes((length * bps + 7) / 8); var strIdx = 0; @@ -30,7 +31,7 @@ var PDFFunction = (function pdfFunction() { codeSize += 8; } codeSize -= bps; - array.push(codeBuf >> codeSize); + array.push((codeBuf >> codeSize) * sampleMul); codeBuf &= (1 << codeSize) - 1; } return array; @@ -76,6 +77,17 @@ var PDFFunction = (function pdfFunction() { }, constructSampled: function pdfFunctionConstructSampled(str, dict) { + function toMultiArray(arr) { + var inputLength = arr.length; + var outputLength = arr.length / 2; + var out = new Array(outputLength); + var index = 0; + for (var i = 0; i < inputLength; i += 2) { + out[index] = [arr[i], arr[i + 1]]; + ++index; + } + return out; + } var domain = dict.get('Domain'); var range = dict.get('Range'); @@ -85,9 +97,8 @@ var PDFFunction = (function pdfFunction() { var inputSize = domain.length / 2; var outputSize = range.length / 2; - if (inputSize != 1) - error('No support for multi-variable inputs to functions: ' + - inputSize); + domain = toMultiArray(domain); + range = toMultiArray(range); var size = dict.get('Size'); var bps = dict.get('BitsPerSample'); @@ -105,15 +116,36 @@ var PDFFunction = (function pdfFunction() { encode.push(size[i] - 1); } } + encode = toMultiArray(encode); + var decode = dict.get('Decode'); if (!decode) decode = range; + else + decode = toMultiArray(decode); + + // Precalc the multipliers + var inputMul = new Float64Array(inputSize); + for (var i = 0; i < inputSize; ++i) { + inputMul[i] = (encode[i][1] - encode[i][0]) / + (domain[i][1] - domain[i][0]); + } + + var idxMul = new Int32Array(inputSize); + idxMul[0] = outputSize; + for (i = 1; i < inputSize; ++i) { + idxMul[i] = idxMul[i - 1] * size[i - 1]; + } + + var nSamples = outputSize; + for (i = 0; i < inputSize; ++i) + nSamples *= size[i]; var samples = this.getSampleArray(size, outputSize, bps, str); return [ CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, - outputSize, bps, range + outputSize, bps, range, inputMul, idxMul, nSamples ]; }, @@ -127,64 +159,74 @@ var PDFFunction = (function pdfFunction() { var outputSize = IR[7]; var bps = IR[8]; var range = IR[9]; + var inputMul = IR[10]; + var idxMul = IR[11]; + var nSamples = IR[12]; return function constructSampledFromIRResult(args) { - var clip = function constructSampledFromIRClip(v, min, max) { - if (v > max) - v = max; - else if (v < min) - v = min; - return v; - }; - if (inputSize != args.length) error('Incorrect number of arguments: ' + inputSize + ' != ' + args.length); + // Most of the below is a port of Poppler's implementation. + // TODO: There's a few other ways to do multilinear interpolation such + // as piecewise, which is much faster but an approximation. + var out = new Float64Array(outputSize); + var x; + var e = new Array(inputSize); + var efrac0 = new Float64Array(inputSize); + var efrac1 = new Float64Array(inputSize); + var sBuf = new Float64Array(1 << inputSize); + var i, j, k, idx, t; - for (var i = 0; i < inputSize; i++) { - var i2 = i * 2; - - // clip to the domain - var v = clip(args[i], domain[i2], domain[i2 + 1]); - - // encode - v = encode[i2] + ((v - domain[i2]) * - (encode[i2 + 1] - encode[i2]) / - (domain[i2 + 1] - domain[i2])); - - // clip to the size - args[i] = clip(v, 0, size[i] - 1); + // map input values into sample array + for (i = 0; i < inputSize; ++i) { + x = (args[i] - domain[i][0]) * inputMul[i] + encode[i][0]; + if (x < 0) { + x = 0; + } else if (x > size[i] - 1) { + x = size[i] - 1; + } + e[i] = [Math.floor(x), 0]; + if ((e[i][1] = e[i][0] + 1) >= size[i]) { + // this happens if in[i] = domain[i][1] + e[i][1] = e[i][0]; + } + efrac1[i] = x - e[i][0]; + efrac0[i] = 1 - efrac1[i]; } - // interpolate to table - TODO('Multi-dimensional interpolation'); - var floor = Math.floor(args[0]); - var ceil = Math.ceil(args[0]); - var scale = args[0] - floor; + // for each output, do m-linear interpolation + for (i = 0; i < outputSize; ++i) { - floor *= outputSize; - ceil *= outputSize; - - var output = [], v = 0; - for (var i = 0; i < outputSize; ++i) { - if (ceil == floor) { - v = samples[ceil + i]; - } else { - var low = samples[floor + i]; - var high = samples[ceil + i]; - v = low * scale + high * (1 - scale); + // pull 2^m values out of the sample array + for (j = 0; j < (1 << inputSize); ++j) { + idx = i; + for (k = 0, t = j; k < inputSize; ++k, t >>= 1) { + idx += idxMul[k] * (e[k][t & 1]); + } + if (idx >= 0 && idx < nSamples) { + sBuf[j] = samples[idx]; + } else { + sBuf[j] = 0; // TODO Investigate if this is what Adobe does + } } - var i2 = i * 2; - // decode - v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) / - ((1 << bps) - 1)); + // do m sets of interpolations + for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) { + for (k = 0; k < t; k += 2) { + sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1]; + } + } - // clip to the domain - output.push(clip(v, range[i2], range[i2 + 1])); + // map output value to range + out[i] = (sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0]); + if (out[i] < range[i][0]) { + out[i] = range[i][0]; + } else if (out[i] > range[i][1]) { + out[i] = range[i][1]; + } } - - return output; + return out; } }, diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 443cb155a..ac3dabb70 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -14,4 +14,5 @@ !sizes.pdf !close-path-bug.pdf !alphatrans.pdf +!devicen.pdf diff --git a/test/pdfs/devicen.pdf b/test/pdfs/devicen.pdf new file mode 100644 index 0000000000000000000000000000000000000000..53ef9e5d5174b4d333c81ef8f79d22d17a5fc001 GIT binary patch literal 9550 zcmcIq2|Sct+aI#a5)wU@v5tLa7-Ps_7<+cgQZmfgw`Q@XY(xG*OY0s#DB2%y3c z>!6NQLAnr-FdQ&A9!@|Iln?~Gs|sFOSp}hrB%qKQ>aI!z7X%Kaif~au!U=#a0*P02 zb;Td0TKVH#2w-Zp4haZ9sza94a}*d3|7E&!&8QaC zF&(J%)6vn!k*K$j9|So16R_>ZKh3a&01p>}Db+Jez!<7eBoKVTP;GAt0dVujnD8HD z|GNt+UO#5{Yw?v;{+S)W(*b|3fu&W8Gx|$dTySLl(f9e zHnIz;1benTVi}sSmv;E(+`#}9Cf`)Lz@!DbY;H#$lI`F{an*cIlcejuqK?yNd|y2~ zNaDJ1cJq?^L(NJ;wckbdWM!Z65k)yGejQcMCyqnAG-lXv0{&`BWjvzg4UZVHorQCb z<5x(Te3vroSi|V}K;3r`_gMa<8x^{r(=-K$1jSIv?@L{6=y&8S^|gP|Ur|{_`F{dI zMq047RUmKYDZd9ZEcbQVm8Of+2D1|#$sEIp4dHu^@Y;;;`{ z&rCN{q0FU}eK>4!VAncq@g=H?_vyLJeJK4*Q@6q=tHP@rsvCi;tCP#Y3KS8O0HeiWlr${k|Hf#YR2R9+tZz@HIZkSck`f_v#L-SKKfrDs;d!ig8E zV{I&nhazrEyJc$U!QOgg9Dd55iV`bE1r8ObuA`2%j}~-ax0w4n!E@81+mlA?m1I3e z8rnI^;Z~;sF^+sk3+f^d9XRrt{jU@@h9?E$Lv?x$_BOuF6Z1mpQV8n=3F)PqDGX6C z9m(>PS9{a*?ei{r{6&-A^ezKcIOZIb9Ho1$rA7OcaQ}hRpUbZ{nHNqydUEk$Z5HM$ ztDleN!eKN^co1)S@8`SK1M;ARa=PZV7YxNptZZK zp!oriWfj}Ah^hDTsTUX(-zYzunff~MnECUD)r`iIyhEi|;xFRoorrmjz9%2Qx1*)a zp_QDbk#*w8h&aGWdy12`6@!Rnd;k`F9!^`q(}hv(1EH@o%I64@BZMl1ylL+{9pKwl z-*VEQGa>4H3-cR&f1wl1Wf2ZeTnD3KoEX&U%fU>dnBB3gb{O^~+2{11beyHQ;$keX z$z2D@L|7;BDr(F3v0P_dj5%{nu+R79MxVt?Ix>45T^PLyh9hS+=Ms$e2~NnHvA(6T zg6~{})wDnmb4jGvv}6spBO^yk-W!dNuoJWz9LJ&{Gcj-EXgT+{>I>`<5!DknP_v9p z5edoP`$~@?*#fUg+^rga1S6TO-^H2|OSIU%H+ROJ>q>lg|Ng4IuXSVkkG{mcM}*L4 zYP$Ai-#GE7=DcPKjWVWGYK*h=lX=Dfk&rf<-mtHubypa zD{b4=HrHm{R^MjFbr!F?CaRk>V5oQ@Vo-NXdrbdx0{Jq+B*e5ZS+MKdb2_4ko$(5o zFe5cxj7R=^iw>zyseRHK^hMN-)+{m$jYbU|dj_lI;tT%Dpzy76U`hnN>4FLYms@t&UV zxKM#FIW)|)B(=ogmDWp$A9>}?R)47>ChS~Tw$5zL@mW-uN79duDzs8w7}XG0 zSXTHpb_ZV3f6g3a=}2E5v*9pB=hu$XBD!pr$ zU3n6J|2Fg8v+hR>nL*kq*G{1!0@9fg?>}FyX`9}lVYKbVO3$LH^VoJ zK5z)suqU&7#?Zt(7Wfu-*#&O97a5(|;VR%Gcu^qn+yMcIz%f>_ac}-u=v(1 zP2^2@P3TXShj60Xgi?hZ(2eh-(NXF>Xnl2PNQ+~Oqi1=k+)TW_Pw*}vhe?kwZ{pPW(s#7N@rpmw<>X(hKuCzfZI z(W7Q0XVPb)?|j|Dev;V>_`A5ZfCY5V`JUCs*+-t&yN~9+qaUVUj1)Va-+He#^Yrcb zxqW{2>PLC32W`k5t`N$>Wa>lW67*JJjNu#KHzP0UL4+P|tSf!HkM_eoR9_CD_I;dj}d_qMmb?4bQv z$gSSm(%U{E!Lw^eUk`rh{-8Q4qDk~rd!=dpqxsCh!;)naivtsxAqhEPeScx3k=>Vj;#ccy*tbu2;tpLrRj@uI zLyuSd7MtqfK65iP-Z`PuUrgGsK4N2F_kDJ7(tAQ(HRZDzD4)jRTYAl9@ykhV`aX>U?)zA8CV(pQQtttpc)yH?Iy9$x{fpj0eY zI#mweGQQP%8+E&^N}?*`4)>k7YMScc>Nj`Y?@rg))%4dI)wb3hsJm0IP+xRUsCms{Lg=30-nj^DSvKiFo`_Nd*sy{kj7<9;Wmv#AT+b?<@N zgW7IX_uU?3PxV9Phj)6Fd+$6#KB|7K^0=l?wXd#Uy}xllbD-r3_DRQ};b6~@=}`Z$ z&G5+6BTuJC@FOory+_}U1&n$ADoBpc6LHd0lvSF0?+O^ zUm)e7CM~}eYkQDM<^-Y@&d1-Iu>CW_b`tAAO$xsM0d0RIZ%WF*WBxy;cfUWt3#8%r zK^Qu0YLYgyM9Qy^iHWJzP2YV=ew=%_nUlHrley_Fxb|9bv-NWcZpNe=rl&ke&yY^P zP@I0nBR%ca)OlNjXzQ-%BT~_p*Q4#-qYu5DB9w?5-M|}_@fa0b8E~@BSA#JQw zOr9Voy$~D^lGsO+INHtGhs-&8``9P{Vt_ulpfGv?+HR>ZWC`sZP?-FSPxwKy&}g!7 zyM@q@g>Y}b(Bxk-TXU^aeXdoUuQ9KrSj{>}{(Dcp;}FV*$=-$^Ai*FcxXL>i^d0*6 zbQ06e3g&u$rXqjlyKk7v-=Jw`^m9wP3zxgEWpo!zbm!gc-oT#eX|ql29Z9^8O6;jg z>Sacg z6r5l+PWar6K-r8?){J1I8w_EeMj1^DQ%xh)riBKl1%Aq6hVND#b~*524)thGZDme% zYA#f7{#juKc$t2wnKw!O`R-!vGf8~U-(9`DmF#?kkA5tHHy14>NSJvL#9sE9?Yz}T z>o)8Waja!N)*g>N^jwGs%r0QL*XnqxxnZiMN2-NgYUp%$_!}{0IJXM(MTGQ4B>zR_ zTE=~`yDt^T*VY)<3hW$1R3Sy zjS9%!`QF{=But;wLF1A(<6Lp$8~MhCc;l58_FVn9W6ttZT=Juq@{@}4<3x^F7{mU2 zG32Zm{DPSBfSA%Pv5g$&cR6S4oMjuiWNR&D8x&>hi5w*`zWt?;fLX}#3lPcxgmeqC zG2Nq?72)W#w~LNFLd|J+EbYl~UZ*)m#MYrRCOFIp>u`iTIJk@7^7L+zO5xeB=LN;u zYhWx@{PNgPbG7c)i*GZ8-+vYUq-jMNGB+8r^ck`^IuwcnNofh7@P)qn3q9-$ebfrQ zLZC1ZI}?+p;^9EHr)2r30fJ8{4f|xmSpl}l4eE#?bY!b^AY`3YdJYhs$Sfvpu{ilk zaq3v{#k%5+R_3Hg+qdqr>jP4~12V4%q$dYLX&9)`JhX36w!eMHzMf)#cipjzCV&$i zch^EVv0tFHUnsL*uwh>`NHigWVf03MhiBQaXZfSWvME+$E$$Z>?$?86a|pAgDzgQ$ z*($xvR%HUasOg^6SPWKRmbE{61d|U|_|`WsJhKH`?vzPl7NDgx^KsID1<9X+z4O~R z<@CXU^Ft)XiPb=ec*a`fzUIA8mpU}9zjU6q$mw+#8H*aQ;LZBVsJ_+oiShj7-d4HZ z?vmaP@7@)88m3)ZG_%bjfvk7QvUdaix=X3QD+T@z&{_pkPX)_T1@m}CXb+t>y?;cP z|K%cG`$8N0B9pPgNe2|CI*2nmmND2agAZID1MkO14k1#K87c#h8;%i%}$H*G;~BxIYcGQ>zY*e3J=~J45^iYT0o7Ml{w;%#iJncrQEBrM^4t8otE1n3i?Pjk!L7ra zXF8=%%w(<1Pp!-~u56@f%KgCV8q?!9EURyrpS=lX73HO;V`!!UZav9auTrO8U2J@{ zk!B#r&9t|fUi3b}B5U4eYTl}GeuLLUPJl@W#H8I8m5Uy~*)U!dHm-TD=XxBDkHJ}F z508Wem&6_(kv&`@I37tHS5{TnD&^?%gyCk4MNTwPG~f1+W5|M9t#4UZYOT!St%$w; z*P59Wd9P?zdu7_+Jv7*DZ*z0YoOQyS7%gc0IQvn=yh8Ta&oEjoGa>bbwBCM6HE6k90?GI);FAs_I=e9+wkM=0@Gq6&!o7##_N)v z`dwLvB4)nuKgL2UjqqGCSV6cRmU;Hc2rNglT=8j&Y}fkiqQAUt@UWe2c3HZ}H`M^W z?ixP&&WC%5>R%}lCqJt(Y%U!Bn$fU2RT;Q)G;~AwbA%>1ToKG;%ic(S)aceZ8XS;0 zc?u5Vq@ibtns$l^>Z|=SB^bCcNeSGVr-`MLl-k>RT4Ju#bos9X&Th+blc!oh2paad z7?AUx;A$i7s_N>Qf$fF0O#f)z~ zLVX-xX=qYtt~C?i6oi^hT-+E$Z|>7>18LFmNbQb{qMya6n6^DY?Je3Gh*bwAH`5hp z*HqxYj`VT|E1J!}YO)pO3zuxhyyeuA6NQqOu$d)Rl+Vy?111L}}rzitRG)D`^DImwm5qIzM-L-%z3nQ^L z5SErZg=MDo!dn*$m8Ca**!#qlu47iS~1ayglMvD%fz z_uj_xC5w4t&+}Pfbge;$norY?dzPJ&vo@>ZB48O)NAz{MebdIC4;-^$Fu`E@SO+ov zVQs-Q?q^3F6J$R`R!z%DvA9Mi8THE!+od@Su5s#orSSp2*y$$BGaUCcYflc}ViY_i zp={7@am@N6aov7j+6uB&P>=5>R`R&P;2sy25w9xShvQ2t&8<7op#bQ-2GH?8k`U?g zf=N|KcV#_Q{^M&G0rl*J>#;BLm0V>)wKU%_(wQ?v)AqDnQp{6SbFqGC$+Tw|Xpeag zjES8$mXV-qLvDYWUWyRq&=QAXia8bJ_%+i6hx!Fwp0~+VvPyA})FvAg{*t zdn`X=%Z9Y+QV^1xDkY|Nz3n6rTBJeAOBy!(2zq)9)SOXwgL{EKN6a}CZn;eF(PCrH z^N>y?HCF=Q&S8K%#(#4sjmhbnqt|GZ!p#f&^JlL^Zy46v1CbS)=pSRzA(Lz&;K69T{!{5L%EPy5yy%g*S?ceYERT+b!VSv*fG_ii~RJU9Ib!M$|lsM*u;-{D!= zpSSSR?R`9(Gku>(ik3stes2yl{>i+~Wz{d2H%$xn1cO8)n2k>R;~sph%tB{nnQ59e zo&OymP4rZjpQc|^`P$iNP@Wd^Jl3GHAdv5Jn}~&eg0$1Pdu954Ah>FZ?=Q6v z&DdA08I1)1!R4e(B~t@sW@5@dBh7Bt;oE{f9)8syF>eG=C~*f2&e} z-iZOb;=D;zCC$nN3{!vs`Y+VlQjscsxZ)_@bs7~1~vA;Q`=DmzwK6lN=5oUumh6Yt`AgP?T2Y+I+X}F)$-!S) zy{ZagTT%R}$Es@v0ji=GphY?p+&p~I5D3@k!;M3_^$ms}NIbmk=)( zRXhaj984yV&=6G>C5WbmIuY-xW(g>mK|bETBsJg)4RI%v{nen*z`#I-KqUn~q8k*h zs;UZwDMA$$7bNdX5`+9`=VAM+nJ-BJN5wg@Kw|J>Ri?+P?mg8m!@xc*OufQi~pj-Vk_hRDNI0oKL1mZhP9_=>bTw z-%q?fsE(_713!VGAwlx^?>8ri#tv-Mp;Y2}duV(=*=`Em(cgCU4&46dNcvNV-rJFLd;kCd literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 8085506a2..4d2c859b5 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -268,5 +268,12 @@ "link": false, "rounds": 1, "type": "eq" + }, + { "id": "devicen", + "file": "pdfs/devicen.pdf", + "md5": "b9dbf00ec6bf02fcbefbb40332713aad", + "link": false, + "rounds": 1, + "type": "eq" } ] From 37b4ceb098f0b832d7556989ed55e220a6498827 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 12 Nov 2011 11:10:49 -0600 Subject: [PATCH 13/35] fix lint error --- src/core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index 43c059b56..c5f88b280 100644 --- a/src/core.js +++ b/src/core.js @@ -481,7 +481,7 @@ var PDFDoc = (function pdfDoc() { throw 'No PDFJS.workerSrc specified'; } - var worker + var worker; try { worker = new Worker(workerSrc); } catch (e) { From 0499eb58be706c0913de2187f8c18679cfd2c59f Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 12 Nov 2011 11:27:49 -0600 Subject: [PATCH 14/35] Warn (not fail) during MD5 verification, new MD5 for intelisa and f1040 --- test/test.py | 4 +--- test/test_manifest.json | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test/test.py b/test/test.py index 65def5d8e..2eb144f9d 100644 --- a/test/test.py +++ b/test/test.py @@ -363,9 +363,7 @@ def setUp(options): manifestList = json.load(mf) downloadLinkedPDFs(manifestList) - - if not verifyPDFs(manifestList): - raise Exception('ERROR: failed to verify pdfs.') + verifyPDFs(manifestList) for b in testBrowsers: State.taskResults[b.name] = { } diff --git a/test/test_manifest.json b/test/test_manifest.json index 8085506a2..0bac41d34 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -19,7 +19,7 @@ }, { "id": "intelisa-load", "file": "pdfs/intelisa.pdf", - "md5": "f3ed5487d1afa34d8b77c0c734a95c79", + "md5": "f5712097d29287a97f1278839814f682", "link": true, "rounds": 1, "type": "load" @@ -194,7 +194,7 @@ }, { "id": "f1040", "file": "pdfs/f1040.pdf", - "md5": "7323b50c6d28d959b8b4b92c469b2469", + "md5": "b59272ce19b4a0c5808c8861441b0741", "link": true, "rounds": 1, "type": "load" From c2dd4523421c620da8fa8872bfc6719948710091 Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 13 Nov 2011 21:39:56 +0200 Subject: [PATCH 15/35] Create first unit test in Jasmine unit test framework. To run the unit test open pdf.js/test/unit/unit_test.html in your browser. This requires that https://github.com/pivotal/jasmine is cloned to the same directory level as pdf.js. --- test/unit/spec/obj_spec.js | 16 ++++++++++++ test/unit/unit_test.html | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/unit/spec/obj_spec.js create mode 100644 test/unit/unit_test.html diff --git a/test/unit/spec/obj_spec.js b/test/unit/spec/obj_spec.js new file mode 100644 index 000000000..b79a21ed5 --- /dev/null +++ b/test/unit/spec/obj_spec.js @@ -0,0 +1,16 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +'use strict'; + +describe("obj", function() { + + describe("Name", function() { + it("should retain the given name", function() { + var givenName = "My Name"; + var name = new Name(givenName); + expect(name.name).toEqual(givenName); + }); + }); +}); + diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html new file mode 100644 index 000000000..cedd65c7a --- /dev/null +++ b/test/unit/unit_test.html @@ -0,0 +1,51 @@ + + + + pdf.js unit test + + + + + + + + + + + + + + + + + + + From effddf39eafb2022b12bef5f46d32b21f8f4ea2a Mon Sep 17 00:00:00 2001 From: Vivien Nicolas <21@vingtetun.org> Date: Mon, 14 Nov 2011 11:58:21 +0100 Subject: [PATCH 16/35] Bump up firefox version in install.rdf --- extensions/firefox/install.rdf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/firefox/install.rdf b/extensions/firefox/install.rdf index 26b2192b6..952d55fbf 100644 --- a/extensions/firefox/install.rdf +++ b/extensions/firefox/install.rdf @@ -12,7 +12,7 @@ {ec8030f7-c20a-464f-9b0e-13a3a9e97384} 6.0 - 10.0.* + 11.0.* true From a182431ac584fe1c59d46ef15dfd55561ac391f9 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 14 Nov 2011 13:59:11 -0500 Subject: [PATCH 17/35] Fixing lint --- src/core.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.js b/src/core.js index 43c059b56..c5f88b280 100644 --- a/src/core.js +++ b/src/core.js @@ -481,7 +481,7 @@ var PDFDoc = (function pdfDoc() { throw 'No PDFJS.workerSrc specified'; } - var worker + var worker; try { worker = new Worker(workerSrc); } catch (e) { From 39651856839b207ee0a605ec9585acd8e2a155bb Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Mon, 14 Nov 2011 21:08:36 -0600 Subject: [PATCH 18/35] change the ERROR to WARNING; a message saying how to resolve these warnings --- test/test.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/test.py b/test/test.py index 2eb144f9d..256200587 100644 --- a/test/test.py +++ b/test/test.py @@ -323,18 +323,18 @@ def verifyPDFs(manifestList): if os.access(f, os.R_OK): fileMd5 = hashlib.md5(open(f, 'rb').read()).hexdigest() if 'md5' not in item: - print 'ERROR: Missing md5 for file "' + f + '".', + print 'WARNING: Missing md5 for file "' + f + '".', print 'Hash for current file is "' + fileMd5 + '"' error = True continue md5 = item['md5'] if fileMd5 != md5: - print 'ERROR: MD5 of file "' + f + '" does not match file.', + print 'WARNING: MD5 of file "' + f + '" does not match file.', print 'Expected "' + md5 + '" computed "' + fileMd5 + '"' error = True continue else: - print 'ERROR: Unable to open file for reading "' + f + '".' + print 'WARNING: Unable to open file for reading "' + f + '".' error = True return not error @@ -363,7 +363,10 @@ def setUp(options): manifestList = json.load(mf) downloadLinkedPDFs(manifestList) - verifyPDFs(manifestList) + + if not verifyPDFs(manifestList): + print 'Unable to verify the checksum for the files that are used for testing.' + print 'Please re-download the files, or adjust the MD5 checksum in the manifest for the files listed above.\n' for b in testBrowsers: State.taskResults[b.name] = { } From 9830cb935e28d7cf338652252449894d89a41bad Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 15 Nov 2011 14:43:05 -0800 Subject: [PATCH 19/35] Fix two memory leaks. --- test/driver.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/driver.js b/test/driver.js index e84b7c8e0..4613f1962 100644 --- a/test/driver.js +++ b/test/driver.js @@ -56,23 +56,31 @@ function load() { } function cleanup() { - var styleSheet = document.styleSheets[0]; - if (styleSheet) { - while (styleSheet.cssRules.length > 0) - styleSheet.deleteRule(0); + // Clear out all the stylesheets since a new one is created for each font. + while (document.styleSheets.length > 0) { + var styleSheet = document.styleSheets[0]; + if (styleSheet) { + while (styleSheet.cssRules.length > 0) + styleSheet.deleteRule(0); + } + var parent = styleSheet.ownerNode.parentNode; + parent.removeChild(styleSheet.ownerNode); } var guard = document.getElementById('content-end'); var body = document.body; while (body.lastChild !== guard) body.removeChild(body.lastChild); + + // Wipe out the link to the pdfdoc so it can be GC'ed. + for (var i = 0; i < manifest.length; i++) { + if (manifest[i].pdfDoc) { + manifest[i].pdfDoc.destroy(); + delete manifest[i].pdfDoc + } + } } function nextTask() { - // If there is a pdfDoc on the last task executed, destroy it to free memory. - if (task && task.pdfDoc) { - task.pdfDoc.destroy(); - delete task.pdfDoc; - } cleanup(); if (currentTaskIdx == manifest.length) { From 29c21843d621700aa34a3c3ca76ce5f8d0afb72a Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 15 Nov 2011 15:45:37 -0800 Subject: [PATCH 20/35] Nits --- test/driver.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/driver.js b/test/driver.js index 4613f1962..c11cecf56 100644 --- a/test/driver.js +++ b/test/driver.js @@ -59,12 +59,10 @@ function cleanup() { // Clear out all the stylesheets since a new one is created for each font. while (document.styleSheets.length > 0) { var styleSheet = document.styleSheets[0]; - if (styleSheet) { - while (styleSheet.cssRules.length > 0) - styleSheet.deleteRule(0); - } - var parent = styleSheet.ownerNode.parentNode; - parent.removeChild(styleSheet.ownerNode); + while (styleSheet.cssRules.length > 0) + styleSheet.deleteRule(0); + var ownerNode = styleSheet.ownerNode; + ownerNode.parentNode.removeChild(ownerNode); } var guard = document.getElementById('content-end'); var body = document.body; @@ -75,7 +73,7 @@ function cleanup() { for (var i = 0; i < manifest.length; i++) { if (manifest[i].pdfDoc) { manifest[i].pdfDoc.destroy(); - delete manifest[i].pdfDoc + delete manifest[i].pdfDoc; } } } From ef58ccd284634e83e8ea82ec2af2773a27a5fe49 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Tue, 15 Nov 2011 18:23:45 -0600 Subject: [PATCH 21/35] Issue #644: bypassing identity cmap translation loading; resetting color space when stroke/fill color set --- src/canvas.js | 18 ++++++++++++++++++ src/evaluator.js | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/canvas.js b/src/canvas.js index 9759a8deb..a7c0cc93b 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -594,27 +594,45 @@ var CanvasGraphics = (function canvasGraphics() { } }, setStrokeGray: function canvasGraphicsSetStrokeGray(gray) { + if (!(this.current.strokeColorSpace instanceof DeviceGrayCS)) + this.current.strokeColorSpace = new DeviceGrayCS(); + this.setStrokeRGBColor(gray, gray, gray); }, setFillGray: function canvasGraphicsSetFillGray(gray) { + if (!(this.current.fillColorSpace instanceof DeviceGrayCS)) + this.current.fillColorSpace = new DeviceGrayCS(); + this.setFillRGBColor(gray, gray, gray); }, setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) { + if (!(this.current.strokeColorSpace instanceof DeviceRgbCS)) + this.current.strokeColorSpace = new DeviceRgbCS(); + var color = Util.makeCssRgb(r, g, b); this.ctx.strokeStyle = color; this.current.strokeColor = color; }, setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) { + if (!(this.current.fillColorSpace instanceof DeviceRgbCS)) + this.current.fillColorSpace = new DeviceRgbCS(); + var color = Util.makeCssRgb(r, g, b); this.ctx.fillStyle = color; this.current.fillColor = color; }, setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) { + if (!(this.current.strokeColorSpace instanceof DeviceCmykCS)) + this.current.strokeColorSpace = new DeviceCmykCS(); + var color = Util.makeCssCmyk(c, m, y, k); this.ctx.strokeStyle = color; this.current.strokeColor = color; }, setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) { + if (!(this.current.fillColorSpace instanceof DeviceCmykCS)) + this.current.fillColorSpace = new DeviceCmykCS(); + var color = Util.makeCssCmyk(c, m, y, k); this.ctx.fillStyle = color; this.current.fillColor = color; diff --git a/src/evaluator.js b/src/evaluator.js index 619a633b2..1cb8fe39f 100644 --- a/src/evaluator.js +++ b/src/evaluator.js @@ -522,7 +522,9 @@ var PartialEvaluator = (function partialEvaluator() { var cmapObj = xref.fetchIfRef(toUnicode); var charToUnicode = []; if (isName(cmapObj)) { - error('ToUnicode file cmap translation not implemented'); + 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 = ''; From 7a2301dc951c56da91b863796d76e67070be4bc3 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Tue, 15 Nov 2011 20:16:22 -0600 Subject: [PATCH 22/35] Inline setXXXRGBColor calls --- src/canvas.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/canvas.js b/src/canvas.js index a7c0cc93b..9b3ed0ba9 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -546,7 +546,9 @@ var CanvasGraphics = (function canvasGraphics() { setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) { var cs = this.current.strokeColorSpace; var color = cs.getRgb(arguments); - this.setStrokeRGBColor.apply(this, color); + var color = Util.makeCssRgb.apply(null, cs.getRgb(arguments)); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; }, getColorN_IR_Pattern: function canvasGraphicsGetColorN_IR_Pattern(IR, cs) { if (IR[0] == 'TilingPattern') { @@ -581,8 +583,9 @@ var CanvasGraphics = (function canvasGraphics() { }, setFillColor: function canvasGraphicsSetFillColor(/*...*/) { var cs = this.current.fillColorSpace; - var color = cs.getRgb(arguments); - this.setFillRGBColor.apply(this, color); + var color = Util.makeCssRgb.apply(null, cs.getRgb(arguments)); + this.ctx.fillStyle = color; + this.current.fillColor = color; }, setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) { var cs = this.current.fillColorSpace; @@ -597,13 +600,17 @@ var CanvasGraphics = (function canvasGraphics() { if (!(this.current.strokeColorSpace instanceof DeviceGrayCS)) this.current.strokeColorSpace = new DeviceGrayCS(); - this.setStrokeRGBColor(gray, gray, gray); + var color = Util.makeCssRgb(gray, gray, gray); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; }, setFillGray: function canvasGraphicsSetFillGray(gray) { if (!(this.current.fillColorSpace instanceof DeviceGrayCS)) this.current.fillColorSpace = new DeviceGrayCS(); - this.setFillRGBColor(gray, gray, gray); + var color = Util.makeCssRgb(gray, gray, gray); + this.ctx.fillStyle = color; + this.current.fillColor = color; }, setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) { if (!(this.current.strokeColorSpace instanceof DeviceRgbCS)) From ab5808bb67fccb0f1e2a5845f122000940f26fd7 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Wed, 16 Nov 2011 13:54:36 -0500 Subject: [PATCH 23/35] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index deb925601..4ea613ce0 100644 --- a/README.md +++ b/README.md @@ -152,9 +152,9 @@ See the bot repo for details: ## Additional resources -Our demo site is here: +Gallery of user projects and modifications: -+ http://mozilla.github.com/pdf.js/web/viewer.html ++ https://github.com/mozilla/pdf.js/wiki/Gallery-of-user-projects-and-modifications You can read more about pdf.js here: From 1d04bd2dbbcf119c5fb8b173b3b129ed9c169746 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Wed, 16 Nov 2011 15:27:28 -0500 Subject: [PATCH 24/35] Added link to Corpus Report --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4ea613ce0..97db68d36 100644 --- a/README.md +++ b/README.md @@ -95,9 +95,17 @@ workings of PDF and pdf.js: ## Contributing pdf.js is a community-driven project, so contributors are always welcome. -Simply fork our repo and contribute away. A great place to start is our -[open issues](https://github.com/mozilla/pdf.js/issues). For better consistency and -long-term stability, please do look around the code and try to follow our conventions. +Simply fork our repo and contribute away. Good starting places for picking +a bug are the top error messages and TODOs in our corpus report: + ++ http://people.mozilla.com/~bdahl/corpusreport/test/ref/ + +and of course our open Github issues: + ++ https://github.com/mozilla/pdf.js/issues + +For better consistency and long-term stability, please do look around the +code and try to follow our conventions. More information about the contributor process can be found on the [contributor wiki page](https://github.com/mozilla/pdf.js/wiki/Contributing). From 5e0d704b127c9a927c03a1944213fadaff6f0567 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Thu, 17 Nov 2011 15:45:33 -0500 Subject: [PATCH 25/35] Macro for generating version information --- Makefile | 1 + src/pdf.js | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3e385b175..2cc886091 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ bundle: | $(BUILD_DIR) @cd src; \ cat $(PDF_JS_FILES) > all_files.tmp; \ sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \ + sed -i '' "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%H" -n 1`/" ../$(BUILD_TARGET); \ rm -f *.tmp; \ cd .. diff --git a/src/pdf.js b/src/pdf.js index 51f606548..1a551d300 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -7,8 +7,9 @@ var PDFJS = {}; // Use strict in our context only - users might not want it 'use strict'; + PDFJS.build = "PDFJSSCRIPT_BUNDLE_VER"; + // Files are inserted below - see Makefile /* PDFJSSCRIPT_INCLUDE_ALL */ }).call((typeof window === 'undefined') ? this : window); - From 385b6df2b48c3b18a8f92f9f0cb2abf3e8e46fcd Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Fri, 18 Nov 2011 13:54:27 -0500 Subject: [PATCH 26/35] Linting --- src/pdf.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pdf.js b/src/pdf.js index 1a551d300..1042a651b 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -7,7 +7,7 @@ var PDFJS = {}; // Use strict in our context only - users might not want it 'use strict'; - PDFJS.build = "PDFJSSCRIPT_BUNDLE_VER"; + PDFJS.build = 'PDFJSSCRIPT_BUNDLE_VER'; // Files are inserted below - see Makefile /* PDFJSSCRIPT_INCLUDE_ALL */ From 624a7a74ea9e27c46100a7064235cfb6e0b6ec5e Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 20 Nov 2011 21:16:47 +0200 Subject: [PATCH 27/35] Add jasmine files to external-directory. --- external/jasmine/jasmine-html.js | 676 +++++++ external/jasmine/jasmine.css | 81 + external/jasmine/jasmine.js | 2476 ++++++++++++++++++++++++++ external/jasmine/jasmine_favicon.png | Bin 0 -> 905 bytes test/unit/{spec => }/obj_spec.js | 2 +- test/unit/unit_test.html | 10 +- 6 files changed, 3239 insertions(+), 6 deletions(-) create mode 100644 external/jasmine/jasmine-html.js create mode 100644 external/jasmine/jasmine.css create mode 100644 external/jasmine/jasmine.js create mode 100644 external/jasmine/jasmine_favicon.png rename test/unit/{spec => }/obj_spec.js (92%) diff --git a/external/jasmine/jasmine-html.js b/external/jasmine/jasmine-html.js new file mode 100644 index 000000000..3de4e8a5f --- /dev/null +++ b/external/jasmine/jasmine-html.js @@ -0,0 +1,676 @@ +jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter = function(_doc) { + var self = this; + var doc = _doc || window.document; + + var reporterView; + + var dom = {}; + + // Jasmine Reporter Public Interface + self.logRunningSpecs = false; + + self.reportRunnerStarting = function(runner) { + var specs = runner.specs() || []; + + if (specs.length == 0) { + return; + } + + createReporterDom(runner.env.versionString()); + doc.body.appendChild(dom.reporter); + + reporterView = new jasmine.HtmlReporter.ReporterView(dom); + reporterView.addSpecs(specs, self.specFilter); + }; + + self.reportRunnerResults = function(runner) { + reporterView.complete(); + }; + + self.reportSuiteResults = function(suite) { + reporterView.suiteComplete(suite); + }; + + self.reportSpecStarting = function(spec) { + if (self.logRunningSpecs) { + self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } + }; + + self.reportSpecResults = function(spec) { + reporterView.specComplete(spec); + }; + + self.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } + }; + + self.specFilter = function(spec) { + if (!focusedSpecName()) { + return true; + } + + return spec.getFullName().indexOf(focusedSpecName()) === 0; + }; + + return self; + + function focusedSpecName() { + var specName; + + (function memoizeFocusedSpec() { + if (specName) { + return; + } + + var paramMap = []; + var params = doc.location.search.substring(1).split('&'); + + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + specName = paramMap.spec; + })(); + + return specName; + } + + function createReporterDom(version) { + dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, + dom.banner = self.createDom('div', { className: 'banner' }, + self.createDom('span', { className: 'title' }, "Jasmine "), + self.createDom('span', { className: 'version' }, version)), + + dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), + dom.alert = self.createDom('div', {className: 'alert'}), + dom.results = self.createDom('div', {className: 'results'}, + dom.summary = self.createDom('div', { className: 'summary' }), + dom.details = self.createDom('div', { id: 'details' })) + ); + } +}; +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporterHelpers = {}; + +jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { + el.appendChild(child); + } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { + var results = child.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + + return status; +}; + +jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { + var parentDiv = this.dom.summary; + var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; + var parent = child[parentSuite]; + + if (parent) { + if (typeof this.views.suites[parent.id] == 'undefined') { + this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); + } + parentDiv = this.views.suites[parent.id].element; + } + + parentDiv.appendChild(childElement); +}; + + +jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { + for(var fn in jasmine.HtmlReporterHelpers) { + ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; + } +}; + +jasmine.HtmlReporter.ReporterView = function(dom) { + this.startedAt = new Date(); + this.runningSpecCount = 0; + this.completeSpecCount = 0; + this.passedCount = 0; + this.failedCount = 0; + this.skippedCount = 0; + + this.createResultsMenu = function() { + this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, + this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), + ' | ', + this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); + + this.summaryMenuItem.onclick = function() { + dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); + }; + + this.detailsMenuItem.onclick = function() { + showDetails(); + }; + }; + + this.addSpecs = function(specs, specFilter) { + this.totalSpecCount = specs.length; + + this.views = { + specs: {}, + suites: {} + }; + + for (var i = 0; i < specs.length; i++) { + var spec = specs[i]; + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); + if (specFilter(spec)) { + this.runningSpecCount++; + } + } + }; + + this.specComplete = function(spec) { + this.completeSpecCount++; + + if (isUndefined(this.views.specs[spec.id])) { + this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); + } + + var specView = this.views.specs[spec.id]; + + switch (specView.status()) { + case 'passed': + this.passedCount++; + break; + + case 'failed': + this.failedCount++; + break; + + case 'skipped': + this.skippedCount++; + break; + } + + specView.refresh(); + this.refresh(); + }; + + this.suiteComplete = function(suite) { + var suiteView = this.views.suites[suite.id]; + if (isUndefined(suiteView)) { + return; + } + suiteView.refresh(); + }; + + this.refresh = function() { + + if (isUndefined(this.resultsMenu)) { + this.createResultsMenu(); + } + + // currently running UI + if (isUndefined(this.runningAlert)) { + this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"}); + dom.alert.appendChild(this.runningAlert); + } + this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); + + // skipped specs UI + if (isUndefined(this.skippedAlert)) { + this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"}); + } + + this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.skippedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.skippedAlert); + } + + // passing specs UI + if (isUndefined(this.passedAlert)) { + this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"}); + } + this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); + + // failing specs UI + if (isUndefined(this.failedAlert)) { + this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); + } + this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); + + if (this.failedCount === 1 && isDefined(dom.alert)) { + dom.alert.appendChild(this.failedAlert); + dom.alert.appendChild(this.resultsMenu); + } + + // summary info + this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); + this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; + }; + + this.complete = function() { + dom.alert.removeChild(this.runningAlert); + + this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; + + if (this.failedCount === 0) { + dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); + } else { + showDetails(); + } + + dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); + }; + + return this; + + function showDetails() { + if (dom.reporter.className.search(/showDetails/) === -1) { + dom.reporter.className += " showDetails"; + } + } + + function isUndefined(obj) { + return typeof obj === 'undefined'; + } + + function isDefined(obj) { + return !isUndefined(obj); + } + + function specPluralizedFor(count) { + var str = count + " spec"; + if (count > 1) { + str += "s" + } + return str; + } + +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); + + +jasmine.HtmlReporter.SpecView = function(spec, dom, views) { + this.spec = spec; + this.dom = dom; + this.views = views; + + this.symbol = this.createDom('li', { className: 'pending' }); + this.dom.symbolSummary.appendChild(this.symbol); + + this.summary = this.createDom('div', { className: 'specSummary' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.description) + ); + + this.detail = this.createDom('div', { className: 'specDetail' }, + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(this.spec.getFullName()), + title: this.spec.getFullName() + }, this.spec.getFullName()) + ); +}; + +jasmine.HtmlReporter.SpecView.prototype.status = function() { + return this.getSpecStatus(this.spec); +}; + +jasmine.HtmlReporter.SpecView.prototype.refresh = function() { + this.symbol.className = this.status(); + + switch (this.status()) { + case 'skipped': + break; + + case 'passed': + this.appendSummaryToSuiteDiv(); + break; + + case 'failed': + this.appendSummaryToSuiteDiv(); + this.appendFailureDetail(); + break; + } +}; + +jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { + this.summary.className += ' ' + this.status(); + this.appendToSummary(this.spec, this.summary); +}; + +jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { + this.detail.className += ' ' + this.status(); + + var resultItems = this.spec.results().getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + this.detail.appendChild(messagesDiv); + this.dom.details.appendChild(this.detail); + } +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { + this.suite = suite; + this.dom = dom; + this.views = views; + + this.element = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description) + ); + + this.appendToSummary(this.suite, this.element); +}; + +jasmine.HtmlReporter.SuiteView.prototype.status = function() { + return this.getSpecStatus(this.suite); +}; + +jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { + this.element.className += " " + this.status(); +}; + +jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); + +/* @deprecated Use jasmine.HtmlReporter instead + */ +jasmine.TrivialReporter = function(doc) { + this.document = doc || document; + this.suiteDivs = {}; + this.logRunningSpecs = false; +}; + +jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { + var el = document.createElement(type); + + for (var i = 2; i < arguments.length; i++) { + var child = arguments[i]; + + if (typeof child === 'string') { + el.appendChild(document.createTextNode(child)); + } else { + if (child) { el.appendChild(child); } + } + } + + for (var attr in attrs) { + if (attr == "className") { + el[attr] = attrs[attr]; + } else { + el.setAttribute(attr, attrs[attr]); + } + } + + return el; +}; + +jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { + var showPassed, showSkipped; + + this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, + this.createDom('div', { className: 'banner' }, + this.createDom('div', { className: 'logo' }, + this.createDom('span', { className: 'title' }, "Jasmine"), + this.createDom('span', { className: 'version' }, runner.env.versionString())), + this.createDom('div', { className: 'options' }, + "Show ", + showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), + showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), + this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") + ) + ), + + this.runnerDiv = this.createDom('div', { className: 'runner running' }, + this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), + this.runnerMessageSpan = this.createDom('span', {}, "Running..."), + this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) + ); + + this.document.body.appendChild(this.outerDiv); + + var suites = runner.suites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + var suiteDiv = this.createDom('div', { className: 'suite' }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), + this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); + this.suiteDivs[suite.id] = suiteDiv; + var parentDiv = this.outerDiv; + if (suite.parentSuite) { + parentDiv = this.suiteDivs[suite.parentSuite.id]; + } + parentDiv.appendChild(suiteDiv); + } + + this.startedAt = new Date(); + + var self = this; + showPassed.onclick = function(evt) { + if (showPassed.checked) { + self.outerDiv.className += ' show-passed'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); + } + }; + + showSkipped.onclick = function(evt) { + if (showSkipped.checked) { + self.outerDiv.className += ' show-skipped'; + } else { + self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); + } + }; +}; + +jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { + var results = runner.results(); + var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; + this.runnerDiv.setAttribute("class", className); + //do it twice for IE + this.runnerDiv.setAttribute("className", className); + var specs = runner.specs(); + var specCount = 0; + for (var i = 0; i < specs.length; i++) { + if (this.specFilter(specs[i])) { + specCount++; + } + } + var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); + message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; + this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); + + this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); +}; + +jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { + var results = suite.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.totalCount === 0) { // todo: change this to check results.skipped + status = 'skipped'; + } + this.suiteDivs[suite.id].className += " " + status; +}; + +jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { + if (this.logRunningSpecs) { + this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); + } +}; + +jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { + var results = spec.results(); + var status = results.passed() ? 'passed' : 'failed'; + if (results.skipped) { + status = 'skipped'; + } + var specDiv = this.createDom('div', { className: 'spec ' + status }, + this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), + this.createDom('a', { + className: 'description', + href: '?spec=' + encodeURIComponent(spec.getFullName()), + title: spec.getFullName() + }, spec.description)); + + + var resultItems = results.getItems(); + var messagesDiv = this.createDom('div', { className: 'messages' }); + for (var i = 0; i < resultItems.length; i++) { + var result = resultItems[i]; + + if (result.type == 'log') { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); + } else if (result.type == 'expect' && result.passed && !result.passed()) { + messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); + + if (result.trace.stack) { + messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); + } + } + } + + if (messagesDiv.childNodes.length > 0) { + specDiv.appendChild(messagesDiv); + } + + this.suiteDivs[spec.suite.id].appendChild(specDiv); +}; + +jasmine.TrivialReporter.prototype.log = function() { + var console = jasmine.getGlobal().console; + if (console && console.log) { + if (console.log.apply) { + console.log.apply(console, arguments); + } else { + console.log(arguments); // ie fix: console.log.apply doesn't exist on ie + } + } +}; + +jasmine.TrivialReporter.prototype.getLocation = function() { + return this.document.location; +}; + +jasmine.TrivialReporter.prototype.specFilter = function(spec) { + var paramMap = {}; + var params = this.getLocation().search.substring(1).split('&'); + for (var i = 0; i < params.length; i++) { + var p = params[i].split('='); + paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); + } + + if (!paramMap.spec) { + return true; + } + return spec.getFullName().indexOf(paramMap.spec) === 0; +}; diff --git a/external/jasmine/jasmine.css b/external/jasmine/jasmine.css new file mode 100644 index 000000000..826e57531 --- /dev/null +++ b/external/jasmine/jasmine.css @@ -0,0 +1,81 @@ +body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } + +#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } +#HTMLReporter a { text-decoration: none; } +#HTMLReporter a:hover { text-decoration: underline; } +#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } +#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } +#HTMLReporter #jasmine_content { position: fixed; right: 100%; } +#HTMLReporter .version { color: #aaaaaa; } +#HTMLReporter .banner { margin-top: 14px; } +#HTMLReporter .duration { color: #aaaaaa; float: right; } +#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } +#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } +#HTMLReporter .symbolSummary li.passed { font-size: 14px; } +#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } +#HTMLReporter .symbolSummary li.failed { line-height: 9px; } +#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } +#HTMLReporter .symbolSummary li.skipped { font-size: 14px; } +#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } +#HTMLReporter .symbolSummary li.pending { line-height: 11px; } +#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } +#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } +#HTMLReporter .runningAlert { background-color: #666666; } +#HTMLReporter .skippedAlert { background-color: #aaaaaa; } +#HTMLReporter .skippedAlert:first-child { background-color: #333333; } +#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } +#HTMLReporter .passingAlert { background-color: #a6b779; } +#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } +#HTMLReporter .failingAlert { background-color: #cf867e; } +#HTMLReporter .failingAlert:first-child { background-color: #b03911; } +#HTMLReporter .results { margin-top: 14px; } +#HTMLReporter #details { display: none; } +#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } +#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } +#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } +#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter.showDetails .summary { display: none; } +#HTMLReporter.showDetails #details { display: block; } +#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } +#HTMLReporter .summary { margin-top: 14px; } +#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } +#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } +#HTMLReporter .summary .specSummary.failed a { color: #b03911; } +#HTMLReporter .description + .suite { margin-top: 0; } +#HTMLReporter .suite { margin-top: 14px; } +#HTMLReporter .suite a { color: #333333; } +#HTMLReporter #details .specDetail { margin-bottom: 28px; } +#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } +#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } +#HTMLReporter .resultMessage span.result { display: block; } +#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } + +#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } +#TrivialReporter a:visited, #TrivialReporter a { color: #303; } +#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } +#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } +#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } +#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } +#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } +#TrivialReporter .runner.running { background-color: yellow; } +#TrivialReporter .options { text-align: right; font-size: .8em; } +#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } +#TrivialReporter .suite .suite { margin: 5px; } +#TrivialReporter .suite.passed { background-color: #dfd; } +#TrivialReporter .suite.failed { background-color: #fdd; } +#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } +#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } +#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } +#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } +#TrivialReporter .spec.skipped { background-color: #bbb; } +#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } +#TrivialReporter .passed { background-color: #cfc; display: none; } +#TrivialReporter .failed { background-color: #fbb; } +#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } +#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } +#TrivialReporter .resultMessage .mismatch { color: black; } +#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } +#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } +#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } +#TrivialReporter #jasmine_content { position: fixed; right: 100%; } +#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } diff --git a/external/jasmine/jasmine.js b/external/jasmine/jasmine.js new file mode 100644 index 000000000..8bba9262d --- /dev/null +++ b/external/jasmine/jasmine.js @@ -0,0 +1,2476 @@ +var isCommonJS = typeof window == "undefined"; + +/** + * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework. + * + * @namespace + */ +var jasmine = {}; +if (isCommonJS) exports.jasmine = jasmine; +/** + * @private + */ +jasmine.unimplementedMethod_ = function() { + throw new Error("unimplemented method"); +}; + +/** + * Use jasmine.undefined instead of undefined, since undefined is just + * a plain old variable and may be redefined by somebody else. + * + * @private + */ +jasmine.undefined = jasmine.___undefined___; + +/** + * Show diagnostic messages in the console if set to true + * + */ +jasmine.VERBOSE = false; + +/** + * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed. + * + */ +jasmine.DEFAULT_UPDATE_INTERVAL = 250; + +/** + * Default timeout interval in milliseconds for waitsFor() blocks. + */ +jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; + +jasmine.getGlobal = function() { + function getGlobal() { + return this; + } + + return getGlobal(); +}; + +/** + * Allows for bound functions to be compared. Internal use only. + * + * @ignore + * @private + * @param base {Object} bound 'this' for the function + * @param name {Function} function to find + */ +jasmine.bindOriginal_ = function(base, name) { + var original = base[name]; + if (original.apply) { + return function() { + return original.apply(base, arguments); + }; + } else { + // IE support + return jasmine.getGlobal()[name]; + } +}; + +jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout'); +jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout'); +jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval'); +jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval'); + +jasmine.MessageResult = function(values) { + this.type = 'log'; + this.values = values; + this.trace = new Error(); // todo: test better +}; + +jasmine.MessageResult.prototype.toString = function() { + var text = ""; + for (var i = 0; i < this.values.length; i++) { + if (i > 0) text += " "; + if (jasmine.isString_(this.values[i])) { + text += this.values[i]; + } else { + text += jasmine.pp(this.values[i]); + } + } + return text; +}; + +jasmine.ExpectationResult = function(params) { + this.type = 'expect'; + this.matcherName = params.matcherName; + this.passed_ = params.passed; + this.expected = params.expected; + this.actual = params.actual; + this.message = this.passed_ ? 'Passed.' : params.message; + + var trace = (params.trace || new Error(this.message)); + this.trace = this.passed_ ? '' : trace; +}; + +jasmine.ExpectationResult.prototype.toString = function () { + return this.message; +}; + +jasmine.ExpectationResult.prototype.passed = function () { + return this.passed_; +}; + +/** + * Getter for the Jasmine environment. Ensures one gets created + */ +jasmine.getEnv = function() { + var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env(); + return env; +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isArray_ = function(value) { + return jasmine.isA_("Array", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isString_ = function(value) { + return jasmine.isA_("String", value); +}; + +/** + * @ignore + * @private + * @param value + * @returns {Boolean} + */ +jasmine.isNumber_ = function(value) { + return jasmine.isA_("Number", value); +}; + +/** + * @ignore + * @private + * @param {String} typeName + * @param value + * @returns {Boolean} + */ +jasmine.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; +}; + +/** + * Pretty printer for expecations. Takes any object and turns it into a human-readable string. + * + * @param value {Object} an object to be outputted + * @returns {String} + */ +jasmine.pp = function(value) { + var stringPrettyPrinter = new jasmine.StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; +}; + +/** + * Returns true if the object is a DOM Node. + * + * @param {Object} obj object to check + * @returns {Boolean} + */ +jasmine.isDomNode = function(obj) { + return obj.nodeType > 0; +}; + +/** + * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter. + * + * @example + * // don't care about which function is passed in, as long as it's a function + * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function)); + * + * @param {Class} clazz + * @returns matchable object of the type clazz + */ +jasmine.any = function(clazz) { + return new jasmine.Matchers.Any(clazz); +}; + +/** + * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks. + * + * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine + * expectation syntax. Spies can be checked if they were called or not and what the calling params were. + * + * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs). + * + * Spies are torn down at the end of every spec. + * + * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj. + * + * @example + * // a stub + * var myStub = jasmine.createSpy('myStub'); // can be used anywhere + * + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // actual foo.not will not be called, execution stops + * spyOn(foo, 'not'); + + // foo.not spied upon, execution will continue to implementation + * spyOn(foo, 'not').andCallThrough(); + * + * // fake example + * var foo = { + * not: function(bool) { return !bool; } + * } + * + * // foo.not(val) will return val + * spyOn(foo, 'not').andCallFake(function(value) {return value;}); + * + * // mock example + * foo.not(7 == 7); + * expect(foo.not).toHaveBeenCalled(); + * expect(foo.not).toHaveBeenCalledWith(true); + * + * @constructor + * @see spyOn, jasmine.createSpy, jasmine.createSpyObj + * @param {String} name + */ +jasmine.Spy = function(name) { + /** + * The name of the spy, if provided. + */ + this.identity = name || 'unknown'; + /** + * Is this Object a spy? + */ + this.isSpy = true; + /** + * The actual function this spy stubs. + */ + this.plan = function() { + }; + /** + * Tracking of the most recent call to the spy. + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy.mostRecentCall.args = [1, 2]; + */ + this.mostRecentCall = {}; + + /** + * Holds arguments for each call to the spy, indexed by call count + * @example + * var mySpy = jasmine.createSpy('foo'); + * mySpy(1, 2); + * mySpy(7, 8); + * mySpy.mostRecentCall.args = [7, 8]; + * mySpy.argsForCall[0] = [1, 2]; + * mySpy.argsForCall[1] = [7, 8]; + */ + this.argsForCall = []; + this.calls = []; +}; + +/** + * Tells a spy to call through to the actual implemenatation. + * + * @example + * var foo = { + * bar: function() { // do some stuff } + * } + * + * // defining a spy on an existing property: foo.bar + * spyOn(foo, 'bar').andCallThrough(); + */ +jasmine.Spy.prototype.andCallThrough = function() { + this.plan = this.originalValue; + return this; +}; + +/** + * For setting the return value of a spy. + * + * @example + * // defining a spy from scratch: foo() returns 'baz' + * var foo = jasmine.createSpy('spy on foo').andReturn('baz'); + * + * // defining a spy on an existing property: foo.bar() returns 'baz' + * spyOn(foo, 'bar').andReturn('baz'); + * + * @param {Object} value + */ +jasmine.Spy.prototype.andReturn = function(value) { + this.plan = function() { + return value; + }; + return this; +}; + +/** + * For throwing an exception when a spy is called. + * + * @example + * // defining a spy from scratch: foo() throws an exception w/ message 'ouch' + * var foo = jasmine.createSpy('spy on foo').andThrow('baz'); + * + * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch' + * spyOn(foo, 'bar').andThrow('baz'); + * + * @param {String} exceptionMsg + */ +jasmine.Spy.prototype.andThrow = function(exceptionMsg) { + this.plan = function() { + throw exceptionMsg; + }; + return this; +}; + +/** + * Calls an alternate implementation when a spy is called. + * + * @example + * var baz = function() { + * // do some stuff, return something + * } + * // defining a spy from scratch: foo() calls the function baz + * var foo = jasmine.createSpy('spy on foo').andCall(baz); + * + * // defining a spy on an existing property: foo.bar() calls an anonymnous function + * spyOn(foo, 'bar').andCall(function() { return 'baz';} ); + * + * @param {Function} fakeFunc + */ +jasmine.Spy.prototype.andCallFake = function(fakeFunc) { + this.plan = fakeFunc; + return this; +}; + +/** + * Resets all of a spy's the tracking variables so that it can be used again. + * + * @example + * spyOn(foo, 'bar'); + * + * foo.bar(); + * + * expect(foo.bar.callCount).toEqual(1); + * + * foo.bar.reset(); + * + * expect(foo.bar.callCount).toEqual(0); + */ +jasmine.Spy.prototype.reset = function() { + this.wasCalled = false; + this.callCount = 0; + this.argsForCall = []; + this.calls = []; + this.mostRecentCall = {}; +}; + +jasmine.createSpy = function(name) { + + var spyObj = function() { + spyObj.wasCalled = true; + spyObj.callCount++; + var args = jasmine.util.argsToArray(arguments); + spyObj.mostRecentCall.object = this; + spyObj.mostRecentCall.args = args; + spyObj.argsForCall.push(args); + spyObj.calls.push({object: this, args: args}); + return spyObj.plan.apply(this, arguments); + }; + + var spy = new jasmine.Spy(name); + + for (var prop in spy) { + spyObj[prop] = spy[prop]; + } + + spyObj.reset(); + + return spyObj; +}; + +/** + * Determines whether an object is a spy. + * + * @param {jasmine.Spy|Object} putativeSpy + * @returns {Boolean} + */ +jasmine.isSpy = function(putativeSpy) { + return putativeSpy && putativeSpy.isSpy; +}; + +/** + * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something + * large in one call. + * + * @param {String} baseName name of spy class + * @param {Array} methodNames array of names of methods to make spies + */ +jasmine.createSpyObj = function(baseName, methodNames) { + if (!jasmine.isArray_(methodNames) || methodNames.length === 0) { + throw new Error('createSpyObj requires a non-empty array of method names to create spies for'); + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]); + } + return obj; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the current spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.log = function() { + var spec = jasmine.getEnv().currentSpec; + spec.log.apply(spec, arguments); +}; + +/** + * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy. + * + * @example + * // spy example + * var foo = { + * not: function(bool) { return !bool; } + * } + * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops + * + * @see jasmine.createSpy + * @param obj + * @param methodName + * @returns a Jasmine spy that can be chained with all spy methods + */ +var spyOn = function(obj, methodName) { + return jasmine.getEnv().currentSpec.spyOn(obj, methodName); +}; +if (isCommonJS) exports.spyOn = spyOn; + +/** + * Creates a Jasmine spec that will be added to the current suite. + * + * // TODO: pending tests + * + * @example + * it('should be true', function() { + * expect(true).toEqual(true); + * }); + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var it = function(desc, func) { + return jasmine.getEnv().it(desc, func); +}; +if (isCommonJS) exports.it = it; + +/** + * Creates a disabled Jasmine spec. + * + * A convenience method that allows existing specs to be disabled temporarily during development. + * + * @param {String} desc description of this specification + * @param {Function} func defines the preconditions and expectations of the spec + */ +var xit = function(desc, func) { + return jasmine.getEnv().xit(desc, func); +}; +if (isCommonJS) exports.xit = xit; + +/** + * Starts a chain for a Jasmine expectation. + * + * It is passed an Object that is the actual value and should chain to one of the many + * jasmine.Matchers functions. + * + * @param {Object} actual Actual value to test against and expected value + */ +var expect = function(actual) { + return jasmine.getEnv().currentSpec.expect(actual); +}; +if (isCommonJS) exports.expect = expect; + +/** + * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs. + * + * @param {Function} func Function that defines part of a jasmine spec. + */ +var runs = function(func) { + jasmine.getEnv().currentSpec.runs(func); +}; +if (isCommonJS) exports.runs = runs; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +var waits = function(timeout) { + jasmine.getEnv().currentSpec.waits(timeout); +}; +if (isCommonJS) exports.waits = waits; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments); +}; +if (isCommonJS) exports.waitsFor = waitsFor; + +/** + * A function that is called before each spec in a suite. + * + * Used for spec setup, including validating assumptions. + * + * @param {Function} beforeEachFunction + */ +var beforeEach = function(beforeEachFunction) { + jasmine.getEnv().beforeEach(beforeEachFunction); +}; +if (isCommonJS) exports.beforeEach = beforeEach; + +/** + * A function that is called after each spec in a suite. + * + * Used for restoring any state that is hijacked during spec execution. + * + * @param {Function} afterEachFunction + */ +var afterEach = function(afterEachFunction) { + jasmine.getEnv().afterEach(afterEachFunction); +}; +if (isCommonJS) exports.afterEach = afterEach; + +/** + * Defines a suite of specifications. + * + * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared + * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization + * of setup in some tests. + * + * @example + * // TODO: a simple suite + * + * // TODO: a simple suite with a nested describe block + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var describe = function(description, specDefinitions) { + return jasmine.getEnv().describe(description, specDefinitions); +}; +if (isCommonJS) exports.describe = describe; + +/** + * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development. + * + * @param {String} description A string, usually the class under test. + * @param {Function} specDefinitions function that defines several specs. + */ +var xdescribe = function(description, specDefinitions) { + return jasmine.getEnv().xdescribe(description, specDefinitions); +}; +if (isCommonJS) exports.xdescribe = xdescribe; + + +// Provide the XMLHttpRequest class for IE 5.x-6.x: +jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() { + function tryIt(f) { + try { + return f(); + } catch(e) { + } + return null; + } + + var xhr = tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.6.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP.3.0"); + }) || + tryIt(function() { + return new ActiveXObject("Msxml2.XMLHTTP"); + }) || + tryIt(function() { + return new ActiveXObject("Microsoft.XMLHTTP"); + }); + + if (!xhr) throw new Error("This browser does not support XMLHttpRequest."); + + return xhr; +} : XMLHttpRequest; +/** + * @namespace + */ +jasmine.util = {}; + +/** + * Declare that a child class inherit it's prototype from the parent class. + * + * @private + * @param {Function} childClass + * @param {Function} parentClass + */ +jasmine.util.inherit = function(childClass, parentClass) { + /** + * @private + */ + var subclass = function() { + }; + subclass.prototype = parentClass.prototype; + childClass.prototype = new subclass(); +}; + +jasmine.util.formatException = function(e) { + var lineNumber; + if (e.line) { + lineNumber = e.line; + } + else if (e.lineNumber) { + lineNumber = e.lineNumber; + } + + var file; + + if (e.sourceURL) { + file = e.sourceURL; + } + else if (e.fileName) { + file = e.fileName; + } + + var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString(); + + if (file && lineNumber) { + message += ' in ' + file + ' (line ' + lineNumber + ')'; + } + + return message; +}; + +jasmine.util.htmlEscape = function(str) { + if (!str) return str; + return str.replace(/&/g, '&') + .replace(//g, '>'); +}; + +jasmine.util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]); + return arrayOfArgs; +}; + +jasmine.util.extend = function(destination, source) { + for (var property in source) destination[property] = source[property]; + return destination; +}; + +/** + * Environment for Jasmine + * + * @constructor + */ +jasmine.Env = function() { + this.currentSpec = null; + this.currentSuite = null; + this.currentRunner_ = new jasmine.Runner(this); + + this.reporter = new jasmine.MultiReporter(); + + this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL; + this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL; + this.lastUpdate = 0; + this.specFilter = function() { + return true; + }; + + this.nextSpecId_ = 0; + this.nextSuiteId_ = 0; + this.equalityTesters_ = []; + + // wrap matchers + this.matchersClass = function() { + jasmine.Matchers.apply(this, arguments); + }; + jasmine.util.inherit(this.matchersClass, jasmine.Matchers); + + jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass); +}; + + +jasmine.Env.prototype.setTimeout = jasmine.setTimeout; +jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout; +jasmine.Env.prototype.setInterval = jasmine.setInterval; +jasmine.Env.prototype.clearInterval = jasmine.clearInterval; + +/** + * @returns an object containing jasmine version build info, if set. + */ +jasmine.Env.prototype.version = function () { + if (jasmine.version_) { + return jasmine.version_; + } else { + throw new Error('Version not set'); + } +}; + +/** + * @returns string containing jasmine version build info, if set. + */ +jasmine.Env.prototype.versionString = function() { + if (!jasmine.version_) { + return "version unknown"; + } + + var version = this.version(); + var versionString = version.major + "." + version.minor + "." + version.build; + if (version.release_candidate) { + versionString += ".rc" + version.release_candidate; + } + versionString += " revision " + version.revision; + return versionString; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSpecId = function () { + return this.nextSpecId_++; +}; + +/** + * @returns a sequential integer starting at 0 + */ +jasmine.Env.prototype.nextSuiteId = function () { + return this.nextSuiteId_++; +}; + +/** + * Register a reporter to receive status updates from Jasmine. + * @param {jasmine.Reporter} reporter An object which will receive status updates. + */ +jasmine.Env.prototype.addReporter = function(reporter) { + this.reporter.addReporter(reporter); +}; + +jasmine.Env.prototype.execute = function() { + this.currentRunner_.execute(); +}; + +jasmine.Env.prototype.describe = function(description, specDefinitions) { + var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite); + + var parentSuite = this.currentSuite; + if (parentSuite) { + parentSuite.add(suite); + } else { + this.currentRunner_.add(suite); + } + + this.currentSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch(e) { + declarationError = e; + } + + if (declarationError) { + this.it("encountered a declaration exception", function() { + throw declarationError; + }); + } + + this.currentSuite = parentSuite; + + return suite; +}; + +jasmine.Env.prototype.beforeEach = function(beforeEachFunction) { + if (this.currentSuite) { + this.currentSuite.beforeEach(beforeEachFunction); + } else { + this.currentRunner_.beforeEach(beforeEachFunction); + } +}; + +jasmine.Env.prototype.currentRunner = function () { + return this.currentRunner_; +}; + +jasmine.Env.prototype.afterEach = function(afterEachFunction) { + if (this.currentSuite) { + this.currentSuite.afterEach(afterEachFunction); + } else { + this.currentRunner_.afterEach(afterEachFunction); + } + +}; + +jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) { + return { + execute: function() { + } + }; +}; + +jasmine.Env.prototype.it = function(description, func) { + var spec = new jasmine.Spec(this, this.currentSuite, description); + this.currentSuite.add(spec); + this.currentSpec = spec; + + if (func) { + spec.runs(func); + } + + return spec; +}; + +jasmine.Env.prototype.xit = function(desc, func) { + return { + id: this.nextSpecId(), + runs: function() { + } + }; +}; + +jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) { + if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) { + return true; + } + + a.__Jasmine_been_here_before__ = b; + b.__Jasmine_been_here_before__ = a; + + var hasKey = function(obj, keyName) { + return obj !== null && obj[keyName] !== jasmine.undefined; + }; + + for (var property in b) { + if (!hasKey(a, property) && hasKey(b, property)) { + mismatchKeys.push("expected has key '" + property + "', but missing from actual."); + } + } + for (property in a) { + if (!hasKey(b, property) && hasKey(a, property)) { + mismatchKeys.push("expected missing key '" + property + "', but present in actual."); + } + } + for (property in b) { + if (property == '__Jasmine_been_here_before__') continue; + if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) { + mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual."); + } + } + + if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) { + mismatchValues.push("arrays were not the same length"); + } + + delete a.__Jasmine_been_here_before__; + delete b.__Jasmine_been_here_before__; + return (mismatchKeys.length === 0 && mismatchValues.length === 0); +}; + +jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) { + mismatchKeys = mismatchKeys || []; + mismatchValues = mismatchValues || []; + + for (var i = 0; i < this.equalityTesters_.length; i++) { + var equalityTester = this.equalityTesters_[i]; + var result = equalityTester(a, b, this, mismatchKeys, mismatchValues); + if (result !== jasmine.undefined) return result; + } + + if (a === b) return true; + + if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) { + return (a == jasmine.undefined && b == jasmine.undefined); + } + + if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) { + return a === b; + } + + if (a instanceof Date && b instanceof Date) { + return a.getTime() == b.getTime(); + } + + if (a instanceof jasmine.Matchers.Any) { + return a.matches(b); + } + + if (b instanceof jasmine.Matchers.Any) { + return b.matches(a); + } + + if (jasmine.isString_(a) && jasmine.isString_(b)) { + return (a == b); + } + + if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) { + return (a == b); + } + + if (typeof a === "object" && typeof b === "object") { + return this.compareObjects_(a, b, mismatchKeys, mismatchValues); + } + + //Straight check + return (a === b); +}; + +jasmine.Env.prototype.contains_ = function(haystack, needle) { + if (jasmine.isArray_(haystack)) { + for (var i = 0; i < haystack.length; i++) { + if (this.equals_(haystack[i], needle)) return true; + } + return false; + } + return haystack.indexOf(needle) >= 0; +}; + +jasmine.Env.prototype.addEqualityTester = function(equalityTester) { + this.equalityTesters_.push(equalityTester); +}; +/** No-op base class for Jasmine reporters. + * + * @constructor + */ +jasmine.Reporter = function() { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerStarting = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportRunnerResults = function(runner) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecStarting = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.reportSpecResults = function(spec) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.Reporter.prototype.log = function(str) { +}; + +/** + * Blocks are functions with executable code that make up a spec. + * + * @constructor + * @param {jasmine.Env} env + * @param {Function} func + * @param {jasmine.Spec} spec + */ +jasmine.Block = function(env, func, spec) { + this.env = env; + this.func = func; + this.spec = spec; +}; + +jasmine.Block.prototype.execute = function(onComplete) { + try { + this.func.apply(this.spec); + } catch (e) { + this.spec.fail(e); + } + onComplete(); +}; +/** JavaScript API reporter. + * + * @constructor + */ +jasmine.JsApiReporter = function() { + this.started = false; + this.finished = false; + this.suites_ = []; + this.results_ = {}; +}; + +jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) { + this.started = true; + var suites = runner.topLevelSuites(); + for (var i = 0; i < suites.length; i++) { + var suite = suites[i]; + this.suites_.push(this.summarize_(suite)); + } +}; + +jasmine.JsApiReporter.prototype.suites = function() { + return this.suites_; +}; + +jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) { + var isSuite = suiteOrSpec instanceof jasmine.Suite; + var summary = { + id: suiteOrSpec.id, + name: suiteOrSpec.description, + type: isSuite ? 'suite' : 'spec', + children: [] + }; + + if (isSuite) { + var children = suiteOrSpec.children(); + for (var i = 0; i < children.length; i++) { + summary.children.push(this.summarize_(children[i])); + } + } + return summary; +}; + +jasmine.JsApiReporter.prototype.results = function() { + return this.results_; +}; + +jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) { + return this.results_[specId]; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) { + this.finished = true; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) { +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) { + this.results_[spec.id] = { + messages: spec.results().getItems(), + result: spec.results().failedCount > 0 ? "failed" : "passed" + }; +}; + +//noinspection JSUnusedLocalSymbols +jasmine.JsApiReporter.prototype.log = function(str) { +}; + +jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){ + var results = {}; + for (var i = 0; i < specIds.length; i++) { + var specId = specIds[i]; + results[specId] = this.summarizeResult_(this.results_[specId]); + } + return results; +}; + +jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){ + var summaryMessages = []; + var messagesLength = result.messages.length; + for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) { + var resultMessage = result.messages[messageIndex]; + summaryMessages.push({ + text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined, + passed: resultMessage.passed ? resultMessage.passed() : true, + type: resultMessage.type, + message: resultMessage.message, + trace: { + stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined + } + }); + } + + return { + result : result.result, + messages : summaryMessages + }; +}; + +/** + * @constructor + * @param {jasmine.Env} env + * @param actual + * @param {jasmine.Spec} spec + */ +jasmine.Matchers = function(env, actual, spec, opt_isNot) { + this.env = env; + this.actual = actual; + this.spec = spec; + this.isNot = opt_isNot || false; + this.reportWasCalled_ = false; +}; + +// todo: @deprecated as of Jasmine 0.11, remove soon [xw] +jasmine.Matchers.pp = function(str) { + throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!"); +}; + +// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw] +jasmine.Matchers.prototype.report = function(result, failing_message, details) { + throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs"); +}; + +jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) { + for (var methodName in prototype) { + if (methodName == 'report') continue; + var orig = prototype[methodName]; + matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig); + } +}; + +jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) { + return function() { + var matcherArgs = jasmine.util.argsToArray(arguments); + var result = matcherFunction.apply(this, arguments); + + if (this.isNot) { + result = !result; + } + + if (this.reportWasCalled_) return result; + + var message; + if (!result) { + if (this.message) { + message = this.message.apply(this, arguments); + if (jasmine.isArray_(message)) { + message = message[this.isNot ? 1 : 0]; + } + } else { + var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate; + if (matcherArgs.length > 0) { + for (var i = 0; i < matcherArgs.length; i++) { + if (i > 0) message += ","; + message += " " + jasmine.pp(matcherArgs[i]); + } + } + message += "."; + } + } + var expectationResult = new jasmine.ExpectationResult({ + matcherName: matcherName, + passed: result, + expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0], + actual: this.actual, + message: message + }); + this.spec.addMatcherResult(expectationResult); + return jasmine.undefined; + }; +}; + + + + +/** + * toBe: compares the actual to the expected using === + * @param expected + */ +jasmine.Matchers.prototype.toBe = function(expected) { + return this.actual === expected; +}; + +/** + * toNotBe: compares the actual to the expected using !== + * @param expected + * @deprecated as of 1.0. Use not.toBe() instead. + */ +jasmine.Matchers.prototype.toNotBe = function(expected) { + return this.actual !== expected; +}; + +/** + * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc. + * + * @param expected + */ +jasmine.Matchers.prototype.toEqual = function(expected) { + return this.env.equals_(this.actual, expected); +}; + +/** + * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual + * @param expected + * @deprecated as of 1.0. Use not.toNotEqual() instead. + */ +jasmine.Matchers.prototype.toNotEqual = function(expected) { + return !this.env.equals_(this.actual, expected); +}; + +/** + * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes + * a pattern or a String. + * + * @param expected + */ +jasmine.Matchers.prototype.toMatch = function(expected) { + return new RegExp(expected).test(this.actual); +}; + +/** + * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch + * @param expected + * @deprecated as of 1.0. Use not.toMatch() instead. + */ +jasmine.Matchers.prototype.toNotMatch = function(expected) { + return !(new RegExp(expected).test(this.actual)); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeDefined = function() { + return (this.actual !== jasmine.undefined); +}; + +/** + * Matcher that compares the actual to jasmine.undefined. + */ +jasmine.Matchers.prototype.toBeUndefined = function() { + return (this.actual === jasmine.undefined); +}; + +/** + * Matcher that compares the actual to null. + */ +jasmine.Matchers.prototype.toBeNull = function() { + return (this.actual === null); +}; + +/** + * Matcher that boolean not-nots the actual. + */ +jasmine.Matchers.prototype.toBeTruthy = function() { + return !!this.actual; +}; + + +/** + * Matcher that boolean nots the actual. + */ +jasmine.Matchers.prototype.toBeFalsy = function() { + return !this.actual; +}; + + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called. + */ +jasmine.Matchers.prototype.toHaveBeenCalled = function() { + if (arguments.length > 0) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to have been called.", + "Expected spy " + this.actual.identity + " not to have been called." + ]; + }; + + return this.actual.wasCalled; +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */ +jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was not called. + * + * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead + */ +jasmine.Matchers.prototype.wasNotCalled = function() { + if (arguments.length > 0) { + throw new Error('wasNotCalled does not take arguments'); + } + + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy " + this.actual.identity + " to not have been called.", + "Expected spy " + this.actual.identity + " to have been called." + ]; + }; + + return !this.actual.wasCalled; +}; + +/** + * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters. + * + * @example + * + */ +jasmine.Matchers.prototype.toHaveBeenCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + this.message = function() { + if (this.actual.callCount === 0) { + // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw] + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.", + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was." + ]; + } else { + return [ + "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall), + "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall) + ]; + } + }; + + return this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith; + +/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */ +jasmine.Matchers.prototype.wasNotCalledWith = function() { + var expectedArgs = jasmine.util.argsToArray(arguments); + if (!jasmine.isSpy(this.actual)) { + throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.'); + } + + this.message = function() { + return [ + "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was", + "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was" + ]; + }; + + return !this.env.contains_(this.actual.argsForCall, expectedArgs); +}; + +/** + * Matcher that checks that the expected item is an element in the actual Array. + * + * @param {Object} expected + */ +jasmine.Matchers.prototype.toContain = function(expected) { + return this.env.contains_(this.actual, expected); +}; + +/** + * Matcher that checks that the expected item is NOT an element in the actual Array. + * + * @param {Object} expected + * @deprecated as of 1.0. Use not.toNotContain() instead. + */ +jasmine.Matchers.prototype.toNotContain = function(expected) { + return !this.env.contains_(this.actual, expected); +}; + +jasmine.Matchers.prototype.toBeLessThan = function(expected) { + return this.actual < expected; +}; + +jasmine.Matchers.prototype.toBeGreaterThan = function(expected) { + return this.actual > expected; +}; + +/** + * Matcher that checks that the expected item is equal to the actual item + * up to a given level of decimal precision (default 2). + * + * @param {Number} expected + * @param {Number} precision + */ +jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) { + if (!(precision === 0)) { + precision = precision || 2; + } + var multiplier = Math.pow(10, precision); + var actual = Math.round(this.actual * multiplier); + expected = Math.round(expected * multiplier); + return expected == actual; +}; + +/** + * Matcher that checks that the expected exception was thrown by the actual. + * + * @param {String} expected + */ +jasmine.Matchers.prototype.toThrow = function(expected) { + var result = false; + var exception; + if (typeof this.actual != 'function') { + throw new Error('Actual is not a function'); + } + try { + this.actual(); + } catch (e) { + exception = e; + } + if (exception) { + result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected)); + } + + var not = this.isNot ? "not " : ""; + + this.message = function() { + if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) { + return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' '); + } else { + return "Expected function to throw an exception."; + } + }; + + return result; +}; + +jasmine.Matchers.Any = function(expectedClass) { + this.expectedClass = expectedClass; +}; + +jasmine.Matchers.Any.prototype.matches = function(other) { + if (this.expectedClass == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedClass == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedClass == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedClass == Object) { + return typeof other == 'object'; + } + + return other instanceof this.expectedClass; +}; + +jasmine.Matchers.Any.prototype.toString = function() { + return ''; +}; + +/** + * @constructor + */ +jasmine.MultiReporter = function() { + this.subReporters_ = []; +}; +jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter); + +jasmine.MultiReporter.prototype.addReporter = function(reporter) { + this.subReporters_.push(reporter); +}; + +(function() { + var functionNames = [ + "reportRunnerStarting", + "reportRunnerResults", + "reportSuiteResults", + "reportSpecStarting", + "reportSpecResults", + "log" + ]; + for (var i = 0; i < functionNames.length; i++) { + var functionName = functionNames[i]; + jasmine.MultiReporter.prototype[functionName] = (function(functionName) { + return function() { + for (var j = 0; j < this.subReporters_.length; j++) { + var subReporter = this.subReporters_[j]; + if (subReporter[functionName]) { + subReporter[functionName].apply(subReporter, arguments); + } + } + }; + })(functionName); + } +})(); +/** + * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults + * + * @constructor + */ +jasmine.NestedResults = function() { + /** + * The total count of results + */ + this.totalCount = 0; + /** + * Number of passed results + */ + this.passedCount = 0; + /** + * Number of failed results + */ + this.failedCount = 0; + /** + * Was this suite/spec skipped? + */ + this.skipped = false; + /** + * @ignore + */ + this.items_ = []; +}; + +/** + * Roll up the result counts. + * + * @param result + */ +jasmine.NestedResults.prototype.rollupCounts = function(result) { + this.totalCount += result.totalCount; + this.passedCount += result.passedCount; + this.failedCount += result.failedCount; +}; + +/** + * Adds a log message. + * @param values Array of message parts which will be concatenated later. + */ +jasmine.NestedResults.prototype.log = function(values) { + this.items_.push(new jasmine.MessageResult(values)); +}; + +/** + * Getter for the results: message & results. + */ +jasmine.NestedResults.prototype.getItems = function() { + return this.items_; +}; + +/** + * Adds a result, tracking counts (total, passed, & failed) + * @param {jasmine.ExpectationResult|jasmine.NestedResults} result + */ +jasmine.NestedResults.prototype.addResult = function(result) { + if (result.type != 'log') { + if (result.items_) { + this.rollupCounts(result); + } else { + this.totalCount++; + if (result.passed()) { + this.passedCount++; + } else { + this.failedCount++; + } + } + } + this.items_.push(result); +}; + +/** + * @returns {Boolean} True if everything below passed + */ +jasmine.NestedResults.prototype.passed = function() { + return this.passedCount === this.totalCount; +}; +/** + * Base class for pretty printing for expectation results. + */ +jasmine.PrettyPrinter = function() { + this.ppNestLevel_ = 0; +}; + +/** + * Formats a value in a nice, human-readable string. + * + * @param value + */ +jasmine.PrettyPrinter.prototype.format = function(value) { + if (this.ppNestLevel_ > 40) { + throw new Error('jasmine.PrettyPrinter: format() nested too deeply!'); + } + + this.ppNestLevel_++; + try { + if (value === jasmine.undefined) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === jasmine.getGlobal()) { + this.emitScalar(''); + } else if (value instanceof jasmine.Matchers.Any) { + this.emitScalar(value.toString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (jasmine.isSpy(value)) { + this.emitScalar("spy on " + value.identity); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (value.__Jasmine_been_here_before__) { + this.emitScalar(''); + } else if (jasmine.isArray_(value) || typeof value == 'object') { + value.__Jasmine_been_here_before__ = true; + if (jasmine.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + delete value.__Jasmine_been_here_before__; + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } +}; + +jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (property == '__Jasmine_been_here_before__') continue; + fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined && + obj.__lookupGetter__(property) !== null) : false); + } +}; + +jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_; +jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_; + +jasmine.StringPrettyPrinter = function() { + jasmine.PrettyPrinter.call(this); + + this.string = ''; +}; +jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter); + +jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); +}; + +jasmine.StringPrettyPrinter.prototype.emitString = function(value) { + this.append("'" + value + "'"); +}; + +jasmine.StringPrettyPrinter.prototype.emitArray = function(array) { + this.append('[ '); + for (var i = 0; i < array.length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + this.append(' ]'); +}; + +jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) { + var self = this; + this.append('{ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.append(property); + self.append(' : '); + if (isGetter) { + self.append(''); + } else { + self.format(obj[property]); + } + }); + + this.append(' }'); +}; + +jasmine.StringPrettyPrinter.prototype.append = function(value) { + this.string += value; +}; +jasmine.Queue = function(env) { + this.env = env; + this.blocks = []; + this.running = false; + this.index = 0; + this.offset = 0; + this.abort = false; +}; + +jasmine.Queue.prototype.addBefore = function(block) { + this.blocks.unshift(block); +}; + +jasmine.Queue.prototype.add = function(block) { + this.blocks.push(block); +}; + +jasmine.Queue.prototype.insertNext = function(block) { + this.blocks.splice((this.index + this.offset + 1), 0, block); + this.offset++; +}; + +jasmine.Queue.prototype.start = function(onComplete) { + this.running = true; + this.onComplete = onComplete; + this.next_(); +}; + +jasmine.Queue.prototype.isRunning = function() { + return this.running; +}; + +jasmine.Queue.LOOP_DONT_RECURSE = true; + +jasmine.Queue.prototype.next_ = function() { + var self = this; + var goAgain = true; + + while (goAgain) { + goAgain = false; + + if (self.index < self.blocks.length && !this.abort) { + var calledSynchronously = true; + var completedSynchronously = false; + + var onComplete = function () { + if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) { + completedSynchronously = true; + return; + } + + if (self.blocks[self.index].abort) { + self.abort = true; + } + + self.offset = 0; + self.index++; + + var now = new Date().getTime(); + if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) { + self.env.lastUpdate = now; + self.env.setTimeout(function() { + self.next_(); + }, 0); + } else { + if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) { + goAgain = true; + } else { + self.next_(); + } + } + }; + self.blocks[self.index].execute(onComplete); + + calledSynchronously = false; + if (completedSynchronously) { + onComplete(); + } + + } else { + self.running = false; + if (self.onComplete) { + self.onComplete(); + } + } + } +}; + +jasmine.Queue.prototype.results = function() { + var results = new jasmine.NestedResults(); + for (var i = 0; i < this.blocks.length; i++) { + if (this.blocks[i].results) { + results.addResult(this.blocks[i].results()); + } + } + return results; +}; + + +/** + * Runner + * + * @constructor + * @param {jasmine.Env} env + */ +jasmine.Runner = function(env) { + var self = this; + self.env = env; + self.queue = new jasmine.Queue(env); + self.before_ = []; + self.after_ = []; + self.suites_ = []; +}; + +jasmine.Runner.prototype.execute = function() { + var self = this; + if (self.env.reporter.reportRunnerStarting) { + self.env.reporter.reportRunnerStarting(this); + } + self.queue.start(function () { + self.finishCallback(); + }); +}; + +jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.splice(0,0,beforeEachFunction); +}; + +jasmine.Runner.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.splice(0,0,afterEachFunction); +}; + + +jasmine.Runner.prototype.finishCallback = function() { + this.env.reporter.reportRunnerResults(this); +}; + +jasmine.Runner.prototype.addSuite = function(suite) { + this.suites_.push(suite); +}; + +jasmine.Runner.prototype.add = function(block) { + if (block instanceof jasmine.Suite) { + this.addSuite(block); + } + this.queue.add(block); +}; + +jasmine.Runner.prototype.specs = function () { + var suites = this.suites(); + var specs = []; + for (var i = 0; i < suites.length; i++) { + specs = specs.concat(suites[i].specs()); + } + return specs; +}; + +jasmine.Runner.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Runner.prototype.topLevelSuites = function() { + var topLevelSuites = []; + for (var i = 0; i < this.suites_.length; i++) { + if (!this.suites_[i].parentSuite) { + topLevelSuites.push(this.suites_[i]); + } + } + return topLevelSuites; +}; + +jasmine.Runner.prototype.results = function() { + return this.queue.results(); +}; +/** + * Internal representation of a Jasmine specification, or test. + * + * @constructor + * @param {jasmine.Env} env + * @param {jasmine.Suite} suite + * @param {String} description + */ +jasmine.Spec = function(env, suite, description) { + if (!env) { + throw new Error('jasmine.Env() required'); + } + if (!suite) { + throw new Error('jasmine.Suite() required'); + } + var spec = this; + spec.id = env.nextSpecId ? env.nextSpecId() : null; + spec.env = env; + spec.suite = suite; + spec.description = description; + spec.queue = new jasmine.Queue(env); + + spec.afterCallbacks = []; + spec.spies_ = []; + + spec.results_ = new jasmine.NestedResults(); + spec.results_.description = description; + spec.matchersClass = null; +}; + +jasmine.Spec.prototype.getFullName = function() { + return this.suite.getFullName() + ' ' + this.description + '.'; +}; + + +jasmine.Spec.prototype.results = function() { + return this.results_; +}; + +/** + * All parameters are pretty-printed and concatenated together, then written to the spec's output. + * + * Be careful not to leave calls to jasmine.log in production code. + */ +jasmine.Spec.prototype.log = function() { + return this.results_.log(arguments); +}; + +jasmine.Spec.prototype.runs = function (func) { + var block = new jasmine.Block(this.env, func, this); + this.addToQueue(block); + return this; +}; + +jasmine.Spec.prototype.addToQueue = function (block) { + if (this.queue.isRunning()) { + this.queue.insertNext(block); + } else { + this.queue.add(block); + } +}; + +/** + * @param {jasmine.ExpectationResult} result + */ +jasmine.Spec.prototype.addMatcherResult = function(result) { + this.results_.addResult(result); +}; + +jasmine.Spec.prototype.expect = function(actual) { + var positive = new (this.getMatchersClass_())(this.env, actual, this); + positive.not = new (this.getMatchersClass_())(this.env, actual, this, true); + return positive; +}; + +/** + * Waits a fixed time period before moving to the next block. + * + * @deprecated Use waitsFor() instead + * @param {Number} timeout milliseconds to wait + */ +jasmine.Spec.prototype.waits = function(timeout) { + var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this); + this.addToQueue(waitsFunc); + return this; +}; + +/** + * Waits for the latchFunction to return true before proceeding to the next block. + * + * @param {Function} latchFunction + * @param {String} optional_timeoutMessage + * @param {Number} optional_timeout + */ +jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) { + var latchFunction_ = null; + var optional_timeoutMessage_ = null; + var optional_timeout_ = null; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + switch (typeof arg) { + case 'function': + latchFunction_ = arg; + break; + case 'string': + optional_timeoutMessage_ = arg; + break; + case 'number': + optional_timeout_ = arg; + break; + } + } + + var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this); + this.addToQueue(waitsForFunc); + return this; +}; + +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception', + trace: { stack: e.stack } + }); + this.results_.addResult(expectationResult); +}; + +jasmine.Spec.prototype.getMatchersClass_ = function() { + return this.matchersClass || this.env.matchersClass; +}; + +jasmine.Spec.prototype.addMatchers = function(matchersPrototype) { + var parent = this.getMatchersClass_(); + var newMatchersClass = function() { + parent.apply(this, arguments); + }; + jasmine.util.inherit(newMatchersClass, parent); + jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass); + this.matchersClass = newMatchersClass; +}; + +jasmine.Spec.prototype.finishCallback = function() { + this.env.reporter.reportSpecResults(this); +}; + +jasmine.Spec.prototype.finish = function(onComplete) { + this.removeAllSpies(); + this.finishCallback(); + if (onComplete) { + onComplete(); + } +}; + +jasmine.Spec.prototype.after = function(doAfter) { + if (this.queue.isRunning()) { + this.queue.add(new jasmine.Block(this.env, doAfter, this)); + } else { + this.afterCallbacks.unshift(doAfter); + } +}; + +jasmine.Spec.prototype.execute = function(onComplete) { + var spec = this; + if (!spec.env.specFilter(spec)) { + spec.results_.skipped = true; + spec.finish(onComplete); + return; + } + + this.env.reporter.reportSpecStarting(this); + + spec.env.currentSpec = spec; + + spec.addBeforesAndAftersToQueue(); + + spec.queue.start(function () { + spec.finish(onComplete); + }); +}; + +jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() { + var runner = this.env.currentRunner(); + var i; + + for (var suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this)); + } + } + for (i = 0; i < runner.before_.length; i++) { + this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this)); + } + for (i = 0; i < this.afterCallbacks.length; i++) { + this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this)); + } + for (suite = this.suite; suite; suite = suite.parentSuite) { + for (i = 0; i < suite.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, suite.after_[i], this)); + } + } + for (i = 0; i < runner.after_.length; i++) { + this.queue.add(new jasmine.Block(this.env, runner.after_[i], this)); + } +}; + +jasmine.Spec.prototype.explodes = function() { + throw 'explodes function should not have been called'; +}; + +jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) { + if (obj == jasmine.undefined) { + throw "spyOn could not find an object to spy upon for " + methodName + "()"; + } + + if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) { + throw methodName + '() method does not exist'; + } + + if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) { + throw new Error(methodName + ' has already been spied upon'); + } + + var spyObj = jasmine.createSpy(methodName); + + this.spies_.push(spyObj); + spyObj.baseObj = obj; + spyObj.methodName = methodName; + spyObj.originalValue = obj[methodName]; + + obj[methodName] = spyObj; + + return spyObj; +}; + +jasmine.Spec.prototype.removeAllSpies = function() { + for (var i = 0; i < this.spies_.length; i++) { + var spy = this.spies_[i]; + spy.baseObj[spy.methodName] = spy.originalValue; + } + this.spies_ = []; +}; + +/** + * Internal representation of a Jasmine suite. + * + * @constructor + * @param {jasmine.Env} env + * @param {String} description + * @param {Function} specDefinitions + * @param {jasmine.Suite} parentSuite + */ +jasmine.Suite = function(env, description, specDefinitions, parentSuite) { + var self = this; + self.id = env.nextSuiteId ? env.nextSuiteId() : null; + self.description = description; + self.queue = new jasmine.Queue(env); + self.parentSuite = parentSuite; + self.env = env; + self.before_ = []; + self.after_ = []; + self.children_ = []; + self.suites_ = []; + self.specs_ = []; +}; + +jasmine.Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + return fullName; +}; + +jasmine.Suite.prototype.finish = function(onComplete) { + this.env.reporter.reportSuiteResults(this); + this.finished = true; + if (typeof(onComplete) == 'function') { + onComplete(); + } +}; + +jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) { + beforeEachFunction.typeName = 'beforeEach'; + this.before_.unshift(beforeEachFunction); +}; + +jasmine.Suite.prototype.afterEach = function(afterEachFunction) { + afterEachFunction.typeName = 'afterEach'; + this.after_.unshift(afterEachFunction); +}; + +jasmine.Suite.prototype.results = function() { + return this.queue.results(); +}; + +jasmine.Suite.prototype.add = function(suiteOrSpec) { + this.children_.push(suiteOrSpec); + if (suiteOrSpec instanceof jasmine.Suite) { + this.suites_.push(suiteOrSpec); + this.env.currentRunner().addSuite(suiteOrSpec); + } else { + this.specs_.push(suiteOrSpec); + } + this.queue.add(suiteOrSpec); +}; + +jasmine.Suite.prototype.specs = function() { + return this.specs_; +}; + +jasmine.Suite.prototype.suites = function() { + return this.suites_; +}; + +jasmine.Suite.prototype.children = function() { + return this.children_; +}; + +jasmine.Suite.prototype.execute = function(onComplete) { + var self = this; + this.queue.start(function () { + self.finish(onComplete); + }); +}; +jasmine.WaitsBlock = function(env, timeout, spec) { + this.timeout = timeout; + jasmine.Block.call(this, env, null, spec); +}; + +jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block); + +jasmine.WaitsBlock.prototype.execute = function (onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...'); + } + this.env.setTimeout(function () { + onComplete(); + }, this.timeout); +}; +/** + * A block which waits for some condition to become true, with timeout. + * + * @constructor + * @extends jasmine.Block + * @param {jasmine.Env} env The Jasmine environment. + * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true. + * @param {Function} latchFunction A function which returns true when the desired condition has been met. + * @param {String} message The message to display if the desired condition hasn't been met within the given time period. + * @param {jasmine.Spec} spec The Jasmine spec. + */ +jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) { + this.timeout = timeout || env.defaultTimeoutInterval; + this.latchFunction = latchFunction; + this.message = message; + this.totalTimeSpentWaitingForLatch = 0; + jasmine.Block.call(this, env, null, spec); +}; +jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block); + +jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10; + +jasmine.WaitsForBlock.prototype.execute = function(onComplete) { + if (jasmine.VERBOSE) { + this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen')); + } + var latchFunctionResult; + try { + latchFunctionResult = this.latchFunction.apply(this.spec); + } catch (e) { + this.spec.fail(e); + onComplete(); + return; + } + + if (latchFunctionResult) { + onComplete(); + } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) { + var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen'); + this.spec.fail({ + name: 'timeout', + message: message + }); + + this.abort = true; + onComplete(); + } else { + this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT; + var self = this; + this.env.setTimeout(function() { + self.execute(onComplete); + }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT); + } +}; +// Mock setTimeout, clearTimeout +// Contributed by Pivotal Computer Systems, www.pivotalsf.com + +jasmine.FakeTimer = function() { + this.reset(); + + var self = this; + self.setTimeout = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false); + return self.timeoutsMade; + }; + + self.setInterval = function(funcToCall, millis) { + self.timeoutsMade++; + self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true); + return self.timeoutsMade; + }; + + self.clearTimeout = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + + self.clearInterval = function(timeoutKey) { + self.scheduledFunctions[timeoutKey] = jasmine.undefined; + }; + +}; + +jasmine.FakeTimer.prototype.reset = function() { + this.timeoutsMade = 0; + this.scheduledFunctions = {}; + this.nowMillis = 0; +}; + +jasmine.FakeTimer.prototype.tick = function(millis) { + var oldMillis = this.nowMillis; + var newMillis = oldMillis + millis; + this.runFunctionsWithinRange(oldMillis, newMillis); + this.nowMillis = newMillis; +}; + +jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) { + var scheduledFunc; + var funcsToRun = []; + for (var timeoutKey in this.scheduledFunctions) { + scheduledFunc = this.scheduledFunctions[timeoutKey]; + if (scheduledFunc != jasmine.undefined && + scheduledFunc.runAtMillis >= oldMillis && + scheduledFunc.runAtMillis <= nowMillis) { + funcsToRun.push(scheduledFunc); + this.scheduledFunctions[timeoutKey] = jasmine.undefined; + } + } + + if (funcsToRun.length > 0) { + funcsToRun.sort(function(a, b) { + return a.runAtMillis - b.runAtMillis; + }); + for (var i = 0; i < funcsToRun.length; ++i) { + try { + var funcToRun = funcsToRun[i]; + this.nowMillis = funcToRun.runAtMillis; + funcToRun.funcToCall(); + if (funcToRun.recurring) { + this.scheduleFunction(funcToRun.timeoutKey, + funcToRun.funcToCall, + funcToRun.millis, + true); + } + } catch(e) { + } + } + this.runFunctionsWithinRange(oldMillis, nowMillis); + } +}; + +jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) { + this.scheduledFunctions[timeoutKey] = { + runAtMillis: this.nowMillis + millis, + funcToCall: funcToCall, + recurring: recurring, + timeoutKey: timeoutKey, + millis: millis + }; +}; + +/** + * @namespace + */ +jasmine.Clock = { + defaultFakeTimer: new jasmine.FakeTimer(), + + reset: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.reset(); + }, + + tick: function(millis) { + jasmine.Clock.assertInstalled(); + jasmine.Clock.defaultFakeTimer.tick(millis); + }, + + runFunctionsWithinRange: function(oldMillis, nowMillis) { + jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis); + }, + + scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) { + jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring); + }, + + useMock: function() { + if (!jasmine.Clock.isInstalled()) { + var spec = jasmine.getEnv().currentSpec; + spec.after(jasmine.Clock.uninstallMock); + + jasmine.Clock.installMock(); + } + }, + + installMock: function() { + jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer; + }, + + uninstallMock: function() { + jasmine.Clock.assertInstalled(); + jasmine.Clock.installed = jasmine.Clock.real; + }, + + real: { + setTimeout: jasmine.getGlobal().setTimeout, + clearTimeout: jasmine.getGlobal().clearTimeout, + setInterval: jasmine.getGlobal().setInterval, + clearInterval: jasmine.getGlobal().clearInterval + }, + + assertInstalled: function() { + if (!jasmine.Clock.isInstalled()) { + throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()"); + } + }, + + isInstalled: function() { + return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer; + }, + + installed: null +}; +jasmine.Clock.installed = jasmine.Clock.real; + +//else for IE support +jasmine.getGlobal().setTimeout = function(funcToCall, millis) { + if (jasmine.Clock.installed.setTimeout.apply) { + return jasmine.Clock.installed.setTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.setTimeout(funcToCall, millis); + } +}; + +jasmine.getGlobal().setInterval = function(funcToCall, millis) { + if (jasmine.Clock.installed.setInterval.apply) { + return jasmine.Clock.installed.setInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.setInterval(funcToCall, millis); + } +}; + +jasmine.getGlobal().clearTimeout = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearTimeout.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearTimeout(timeoutKey); + } +}; + +jasmine.getGlobal().clearInterval = function(timeoutKey) { + if (jasmine.Clock.installed.clearTimeout.apply) { + return jasmine.Clock.installed.clearInterval.apply(this, arguments); + } else { + return jasmine.Clock.installed.clearInterval(timeoutKey); + } +}; + +jasmine.version_= { + "major": 1, + "minor": 1, + "build": 0, + "revision": 1320442951 +}; diff --git a/external/jasmine/jasmine_favicon.png b/external/jasmine/jasmine_favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..218f3b43713598fa5a3e78b57aceb909c33f46df GIT binary patch literal 905 zcmV;419tq0P)Px#AY({UO#lFTCIA3{ga82g0001h=l}q9FaQARU;qF* zm;eA5aGbhPJOBUy24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_0008u zNkl3{fod28|PjmA)7fYg4w8-(2my9xtBGOs}K`n&t1VzxMO^X)M zrW+Ln1udc?q6TP)z5gAjt)P&D!M$+HJK#x<`xnD030zwD?KrxxY!2tlA zGc-58?0D7SsT)7Km=v+tNVNUk`?s@;^OxCF)y6P}_mL;~7;S<@b|MzmKq)m8l@yky zT1~ECpxZw@64!nkI34QLiUsA%i%N>-$&zGYR7WJyi9ERMyS(%kf z7A_r)X>!90&m(FwDQZ>q;+nOa*KR2+E6Fz)QwU=W1Oyo*4>_qlm|~joa|{4_A_3W8 z#FFZzRp-xMIx5a7D_Fj3&#r^TbIY@cND1d0f*^qDIs{!pw!IWGQ_%l4#ASm_D5Vet z0%ek7^)@xPihX_G0&hIc9*14ca=D!8oG}vW?H%~w^F?f_s>zU|fKrNJXJ_d6{v!t( zpEoqMws_yQws>3o?VW8Txq~#->dJG^ELW5irR!s`(_JvD^6;r+ho~eIK@ia8_lH(h zt*-p?CFC1_h2MV=?jP){uW!7WjLjCaO&c1D+tf582!XEaoB#xWAYcN5f$sLtf$koW zQs{{>)ZTq?FC6|J_%n}AWbiFK(Bo-%^-{H`*)E(ucjo-r%SYm)W5f6tN=xz=S646E fNXW#U{x?4WXWJ pdf.js unit test - - + + - - + + - + From 1b0ccad5b0f6ba813600cdf90639f86179cd917a Mon Sep 17 00:00:00 2001 From: Kalervo Kujala Date: Sun, 20 Nov 2011 21:29:54 +0200 Subject: [PATCH 28/35] Add jasmine license file. --- external/jasmine/MIT.LICENSE | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 external/jasmine/MIT.LICENSE diff --git a/external/jasmine/MIT.LICENSE b/external/jasmine/MIT.LICENSE new file mode 100644 index 000000000..7c435baae --- /dev/null +++ b/external/jasmine/MIT.LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2008-2011 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 4c15c985244aadad2ef794894d8052abf7ebf4af Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 21 Nov 2011 09:17:44 -0500 Subject: [PATCH 29/35] test --- test/test_manifest.json | 258 ---------------------------------------- 1 file changed, 258 deletions(-) diff --git a/test/test_manifest.json b/test/test_manifest.json index 0bac41d34..8b93334e5 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -10,263 +10,5 @@ "md5": "9a192d8b1a7dc652a19835f6f08098bd", "rounds": 2, "type": "fbf" - }, - { "id": "html5-canvas-cheat-sheet-load", - "file": "pdfs/canvas.pdf", - "md5": "59510028561daf62e00bf9f6f066b033", - "rounds": 1, - "type": "load" - }, - { "id": "intelisa-load", - "file": "pdfs/intelisa.pdf", - "md5": "f5712097d29287a97f1278839814f682", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "pdfspec-load", - "file": "pdfs/pdf.pdf", - "md5": "dbdb23c939d2be09b43126c3c56060c7", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "shavian-load", - "file": "pdfs/shavian.pdf", - "md5": "4fabf0a03e82693007435020bc446f9b", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "sizes", - "file": "pdfs/sizes.pdf", - "md5": "c101ba7b44aee36048e1ac7b98f302ea", - "rounds": 1, - "type": "eq" - }, - { "id": "openweb-cover", - "file": "pdfs/openweb_tm-PRINT.pdf", - "md5": "53f611dfc19ddfb50554c21c4af465c0", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "plusminus", - "file": "pdfs/Test-plusminus.pdf", - "md5": "1ec7ade5b95ac9aaba3a618af28d34c7", - "rounds": 1, - "type": "eq" - }, - { "id": "openoffice-pdf", - "file": "pdfs/DiwanProfile.pdf", - "md5": "55d0c6a1a6d26c9ec9dcecaa7a471e0e", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "openofficecidtruetype-pdf", - "file": "pdfs/arial_unicode_en_cidfont.pdf", - "md5": "03591cdf20214fb0b2dd5e5c3dd32d8c", - "rounds": 1, - "type": "load" - }, - { "id": "openofficearabiccidtruetype-pdf", - "file": "pdfs/arial_unicode_ab_cidfont.pdf", - "md5": "35090fa7d29e7196ae3421812e554988", - "rounds": 1, - "type": "load" - }, - { "id": "arabiccidtruetype-pdf", - "file": "pdfs/ArabicCIDTrueType.pdf", - "md5": "d66dbd18bdb572d3ac8b88b32de2ece6", - "rounds": 1, - "type": "load" - }, - { "id": "complexttffont-pdf", - "file": "pdfs/complex_ttf_font.pdf", - "md5": "76de93f9116b01b693bf8583b3e76d91", - "rounds": 1, - "type": "load" - }, - { "id": "thuluthfont-pdf", - "file": "pdfs/ThuluthFeatures.pdf", - "md5": "b7e18bf7a3d6a9c82aefa12d721072fc", - "rounds": 1, - "type": "eq" - }, - { "id": "wnv_chinese-pdf", - "file": "pdfs/wnv_chinese.pdf", - "md5": "db682638e68391125e8982d3c984841e", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "i9-pdf", - "file": "pdfs/i9.pdf", - "md5": "ba7cd54fdff083bb389295bc0415f6c5", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "hmm-pdf", - "file": "pdfs/hmm.pdf", - "md5": "e08467e60101ee5f4a59716e86db6dc9", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "rotation", - "file": "pdfs/rotation.pdf", - "md5": "4fb25ada00ce7528569d9791c14decf5", - "rounds": 1, - "type": "eq" - }, - { "id": "ecma262-pdf", - "file": "pdfs/ecma262.pdf", - "md5": "763ead98f535578842891e5574e0af0f", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "jai-pdf", - "file": "pdfs/jai.pdf", - "md5": "1f5dd128c3757420a881a155f2f8ace3", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "cable", - "file": "pdfs/cable.pdf", - "md5": "09a41b9a759d60c698228224ab85b46d", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "pdkids", - "file": "pdfs/pdkids.pdf", - "md5": "278982bf016dbe46d2066f9245d9b3e6", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "artofwar", - "file": "pdfs/artofwar.pdf", - "md5": "7bdd51c327b74f1f7abdd90eedb2f912", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "wdsg_fitc", - "file": "pdfs/wdsg_fitc.pdf", - "md5": "5bb1c2b83705d4cdfc43197ee74f07f9", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "unix01", - "file": "pdfs/unix01.pdf", - "md5": "2742999f0bf9b9c035dbb0736096e220", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "fit11-talk", - "file": "pdfs/fit11-talk.pdf", - "md5": "eb7b224107205db4fea9f7df0185f77d", - "link": true, - "rounds": 1, - "skipPages": [12,31], - "type": "eq" - }, - { "id": "fips197", - "file": "pdfs/fips197.pdf", - "md5": "374800cf78ce4b4abd02cd10a856b57f", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "txt2pdf", - "file": "pdfs/txt2pdf.pdf", - "md5": "02cefa0f5e8d96313bb05163b2f88c8c", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "f1040", - "file": "pdfs/f1040.pdf", - "md5": "b59272ce19b4a0c5808c8861441b0741", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "hudsonsurvey", - "file": "pdfs/hudsonsurvey.pdf", - "md5": "bf0e6576a7b6c2fe7485bce1b78e006f", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "extgstate", - "file": "pdfs/extgstate.pdf", - "md5": "001bb4ec04463a01d93aad748361f049", - "link": false, - "rounds": 1, - "type": "eq" - }, - { "id": "usmanm-bad", - "file": "pdfs/usmanm-bad.pdf", - "md5": "38afb822433aaf07fc8f54807cd4f61a", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "vesta-bad", - "file": "pdfs/vesta.pdf", - "md5": "0afebc109b7c17b95619ea3fab5eafe6", - "link": true, - "rounds": 1, - "type": "load" - }, - { "id": "ibwa-bad", - "file": "pdfs/ibwa-bad.pdf", - "md5": "6ca059d32b74ac2688ae06f727fee755", - "link": true, - "rounds": 1, - "skipPages": [ 16 ], - "type": "load" - }, - { "id": "tcpdf_033", - "file": "pdfs/tcpdf_033.pdf", - "md5": "861294df58d185aae80919173f2732ff", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "pal-o47", - "file": "pdfs/pal-o47.pdf", - "md5": "81ae15e539e89f0f0b41169d923b611b", - "link": true, - "rounds": 1, - "type": "eq" - }, - { "id": "simpletype3font", - "file": "pdfs/simpletype3font.pdf", - "md5": "b374c7543920840c61999e9e86939f99", - "link": false, - "rounds": 1, - "type": "eq" - }, - { "id": "close-path-bug", - "file": "pdfs/close-path-bug.pdf", - "md5": "48dd17ef58393857d2d038d33699cac5", - "rounds": 1, - "type": "eq" - }, - { "id": "alphatrans", - "file": "pdfs/alphatrans.pdf", - "md5": "5ca2d3da0c5f20b3a5a14e895ad24b65", - "link": false, - "rounds": 1, - "type": "eq" } ] From f00e3ff154641cb23a9ac8f5a8c684bda5933d82 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 21 Nov 2011 09:26:19 -0500 Subject: [PATCH 30/35] blah --- README.md | 2 +- test/test_manifest.json | 258 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 97db68d36..443410d66 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdf.js - + ## Overview diff --git a/test/test_manifest.json b/test/test_manifest.json index 8b93334e5..0bac41d34 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -10,5 +10,263 @@ "md5": "9a192d8b1a7dc652a19835f6f08098bd", "rounds": 2, "type": "fbf" + }, + { "id": "html5-canvas-cheat-sheet-load", + "file": "pdfs/canvas.pdf", + "md5": "59510028561daf62e00bf9f6f066b033", + "rounds": 1, + "type": "load" + }, + { "id": "intelisa-load", + "file": "pdfs/intelisa.pdf", + "md5": "f5712097d29287a97f1278839814f682", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "pdfspec-load", + "file": "pdfs/pdf.pdf", + "md5": "dbdb23c939d2be09b43126c3c56060c7", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "shavian-load", + "file": "pdfs/shavian.pdf", + "md5": "4fabf0a03e82693007435020bc446f9b", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "sizes", + "file": "pdfs/sizes.pdf", + "md5": "c101ba7b44aee36048e1ac7b98f302ea", + "rounds": 1, + "type": "eq" + }, + { "id": "openweb-cover", + "file": "pdfs/openweb_tm-PRINT.pdf", + "md5": "53f611dfc19ddfb50554c21c4af465c0", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "plusminus", + "file": "pdfs/Test-plusminus.pdf", + "md5": "1ec7ade5b95ac9aaba3a618af28d34c7", + "rounds": 1, + "type": "eq" + }, + { "id": "openoffice-pdf", + "file": "pdfs/DiwanProfile.pdf", + "md5": "55d0c6a1a6d26c9ec9dcecaa7a471e0e", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "openofficecidtruetype-pdf", + "file": "pdfs/arial_unicode_en_cidfont.pdf", + "md5": "03591cdf20214fb0b2dd5e5c3dd32d8c", + "rounds": 1, + "type": "load" + }, + { "id": "openofficearabiccidtruetype-pdf", + "file": "pdfs/arial_unicode_ab_cidfont.pdf", + "md5": "35090fa7d29e7196ae3421812e554988", + "rounds": 1, + "type": "load" + }, + { "id": "arabiccidtruetype-pdf", + "file": "pdfs/ArabicCIDTrueType.pdf", + "md5": "d66dbd18bdb572d3ac8b88b32de2ece6", + "rounds": 1, + "type": "load" + }, + { "id": "complexttffont-pdf", + "file": "pdfs/complex_ttf_font.pdf", + "md5": "76de93f9116b01b693bf8583b3e76d91", + "rounds": 1, + "type": "load" + }, + { "id": "thuluthfont-pdf", + "file": "pdfs/ThuluthFeatures.pdf", + "md5": "b7e18bf7a3d6a9c82aefa12d721072fc", + "rounds": 1, + "type": "eq" + }, + { "id": "wnv_chinese-pdf", + "file": "pdfs/wnv_chinese.pdf", + "md5": "db682638e68391125e8982d3c984841e", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "i9-pdf", + "file": "pdfs/i9.pdf", + "md5": "ba7cd54fdff083bb389295bc0415f6c5", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "hmm-pdf", + "file": "pdfs/hmm.pdf", + "md5": "e08467e60101ee5f4a59716e86db6dc9", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "rotation", + "file": "pdfs/rotation.pdf", + "md5": "4fb25ada00ce7528569d9791c14decf5", + "rounds": 1, + "type": "eq" + }, + { "id": "ecma262-pdf", + "file": "pdfs/ecma262.pdf", + "md5": "763ead98f535578842891e5574e0af0f", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "jai-pdf", + "file": "pdfs/jai.pdf", + "md5": "1f5dd128c3757420a881a155f2f8ace3", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "cable", + "file": "pdfs/cable.pdf", + "md5": "09a41b9a759d60c698228224ab85b46d", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "pdkids", + "file": "pdfs/pdkids.pdf", + "md5": "278982bf016dbe46d2066f9245d9b3e6", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "artofwar", + "file": "pdfs/artofwar.pdf", + "md5": "7bdd51c327b74f1f7abdd90eedb2f912", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "wdsg_fitc", + "file": "pdfs/wdsg_fitc.pdf", + "md5": "5bb1c2b83705d4cdfc43197ee74f07f9", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "unix01", + "file": "pdfs/unix01.pdf", + "md5": "2742999f0bf9b9c035dbb0736096e220", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "fit11-talk", + "file": "pdfs/fit11-talk.pdf", + "md5": "eb7b224107205db4fea9f7df0185f77d", + "link": true, + "rounds": 1, + "skipPages": [12,31], + "type": "eq" + }, + { "id": "fips197", + "file": "pdfs/fips197.pdf", + "md5": "374800cf78ce4b4abd02cd10a856b57f", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "txt2pdf", + "file": "pdfs/txt2pdf.pdf", + "md5": "02cefa0f5e8d96313bb05163b2f88c8c", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "f1040", + "file": "pdfs/f1040.pdf", + "md5": "b59272ce19b4a0c5808c8861441b0741", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "hudsonsurvey", + "file": "pdfs/hudsonsurvey.pdf", + "md5": "bf0e6576a7b6c2fe7485bce1b78e006f", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "extgstate", + "file": "pdfs/extgstate.pdf", + "md5": "001bb4ec04463a01d93aad748361f049", + "link": false, + "rounds": 1, + "type": "eq" + }, + { "id": "usmanm-bad", + "file": "pdfs/usmanm-bad.pdf", + "md5": "38afb822433aaf07fc8f54807cd4f61a", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "vesta-bad", + "file": "pdfs/vesta.pdf", + "md5": "0afebc109b7c17b95619ea3fab5eafe6", + "link": true, + "rounds": 1, + "type": "load" + }, + { "id": "ibwa-bad", + "file": "pdfs/ibwa-bad.pdf", + "md5": "6ca059d32b74ac2688ae06f727fee755", + "link": true, + "rounds": 1, + "skipPages": [ 16 ], + "type": "load" + }, + { "id": "tcpdf_033", + "file": "pdfs/tcpdf_033.pdf", + "md5": "861294df58d185aae80919173f2732ff", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "pal-o47", + "file": "pdfs/pal-o47.pdf", + "md5": "81ae15e539e89f0f0b41169d923b611b", + "link": true, + "rounds": 1, + "type": "eq" + }, + { "id": "simpletype3font", + "file": "pdfs/simpletype3font.pdf", + "md5": "b374c7543920840c61999e9e86939f99", + "link": false, + "rounds": 1, + "type": "eq" + }, + { "id": "close-path-bug", + "file": "pdfs/close-path-bug.pdf", + "md5": "48dd17ef58393857d2d038d33699cac5", + "rounds": 1, + "type": "eq" + }, + { "id": "alphatrans", + "file": "pdfs/alphatrans.pdf", + "md5": "5ca2d3da0c5f20b3a5a14e895ad24b65", + "link": false, + "rounds": 1, + "type": "eq" } ] From 1e642231134635a46a8b57ba91253cb62d1cc635 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 21 Nov 2011 13:44:30 -0500 Subject: [PATCH 31/35] Removed fbf from test_manifest --- README.md | 2 +- test/test_manifest.json | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 443410d66..97db68d36 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdf.js - + ## Overview diff --git a/test/test_manifest.json b/test/test_manifest.json index 0bac41d34..ab5821218 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -5,12 +5,6 @@ "rounds": 1, "type": "eq" }, - { "id": "tracemonkey-fbf", - "file": "pdfs/tracemonkey.pdf", - "md5": "9a192d8b1a7dc652a19835f6f08098bd", - "rounds": 2, - "type": "fbf" - }, { "id": "html5-canvas-cheat-sheet-load", "file": "pdfs/canvas.pdf", "md5": "59510028561daf62e00bf9f6f066b033", From 408dd5d403502e1977c1f16f329abb42a284fce3 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 21 Nov 2011 17:20:23 -0800 Subject: [PATCH 32/35] Replace devicen file with an example we can include. --- test/pdfs/devicen.pdf | Bin 9550 -> 15751 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/pdfs/devicen.pdf b/test/pdfs/devicen.pdf index 53ef9e5d5174b4d333c81ef8f79d22d17a5fc001..d20c884eafe16dd18b095c3546845c25fdc3f529 100644 GIT binary patch delta 4102 zcmd5y1=%=9e29bugb;!h!XcKNL`Xq^H~@;sCFGC;AS80&C4nOTuIlNT zT}J^V4zyO&RsXyG|EvG+{l%xA`22Gx4r@A6kl%Z3u3pEjfYHeHy*7>5e7mM9imsTX zOf;ez#@2%5`@#IxgT>N=3wYJHb4db^N{C4mrCL%dM-=7A0zMh|c5E?Fc=NtHPd_^U z8P~J@^Wmcl3wYCwIGeZGj%zWGERMp3Mq`cv8zM2L5glh z$D8S5l||I15p6WCog8 zuo|pWGA<$^5^XOPn<)c5ym}I~{D2{2Q7u`DLQq*-BwAJ1st+PnAqrNiST##Tt7&FU zEu-uyHVUw+2ewmNZ+FqSApF4=!uc8cL__i?B5f90fjdf*jZzib^W$i#SWIo?j|_Pv zOgvS*{puI*J8*Yy2Bhl1s!ej-fE<6!uH!wl%v>F(K4yH1SW^r8=>IMjNG2TTsp3bI zJ#hfJCa}X&JTy|=loUu&c@wfk)Ea=Nj`s;oPg+kofF;-2&D04g!JkStiCH5`O(3eK zDAIfVg%Z;&v7V)rbfVd%s_l>xvrW@Bbi1c9 zsL@Tz?};0kDCjfL;&9&M8igz8>Q^q#zwiI}{gI?lKRO;!Ynw#~`u$SzJT&UL0xZJ_ z#?Npv1kC9WXj*~a1#BZy0pfbz_fa}1*6~T&XUA!e@ervwAw-&3i$T$XA;JB=m@tVL zF{z=+(DS_ra1bI=6&kI;A1(Xi(+cd$L`7y*Jp(Rnm)=A@NdwT#Jgu|Pj{}Q^=^*vF zA8f!WS-RO!5Fx4UMgw<}arDqe1Z@&H52YM&hz2}(-B0OAk<%?b)^sL{qh}GfoAdY> z<2zt-(}ABpTEOjfu-EY{Xro>qmM7a?K=_1!RFN6*(M{mEz!&;zdd79UMcX~3W)Y?` zF5`B1`pk^?G&Py`uFE49;4X()+YB5soK0y`DK{Ef6${5MJ$ZTQ;48{)kN@o(uYCC1 z+piwI)W6mF@uB2_mzS?R{l?j1>F5K`U3}xs>qlOF=dFc9TRYqD-}-ZKZQ;V@@Y$EX zwD|bzw|$lT<;DA7RzC3kqo2F?+3)=JN589o>-UG$1Jz@H|K*JjT0i~a3l~=I`R?*- zpJdyAee&Sy#`Yf$7|+~(=r`Bze!`-MzgGC{9j{++|K!fEy!O2tU;e@^&%E%DTOX@* zmp`5Gd!GNn*YCJ=?VA@n-CsTZ$A2Dtp1<>vpTpT0+WYQ*22B zq>QsLbTOBrKEt8pTo5lr$_nnpfe#BETXB$9VHY-Kob&>Jn|VDNz_x;O%a%Xdl`;D; zKp7m_^&qvTilK7>#JJ~2coBYJ=;DC&-4Jdev+&m5wKS`)Z>QxK<}7G*oKp<7&($HIAq}mVi-;$tx;J{7ktvLZ)0*Gr0JmF@3_@+Qvvx8d`{~f8_k=mV!HYj&;traP= zB4t))WL9#SPBg$Lq`NgCp5QepzcwSkmPzDl!}LpU#CP$UEbuAm>XcMHHKTee*Lg%T zHwS*;VYmfD0BVnim`YtL*$JIdhL1G3SeWKEs7M557%rqN1k@Go7-mwN%_xIFhDuk; zxKhTQk#Tbw28r;%T>}1mDep^pe@5Py0l^f>Oxx~`n`+23#8NSqit&tMoNMe#CR|lW z!|sH9Mh>-(m&g${ZGYo{IdcUVgz%F;sB9RTfq zY@H}-OI_9teYw>tcXYE!I^~L1B~5+VG?iAnaR&bRW;@V7G{|%TK1qW}Ci);TG!s&K L_;6>fJ6HG@(Uc+1 delta 7 OcmZp#KIgT;PZa Date: Mon, 21 Nov 2011 17:23:54 -0800 Subject: [PATCH 33/35] Address Yury's comment. --- src/function.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/function.js b/src/function.js index 2f9b050dd..ef24736c1 100644 --- a/src/function.js +++ b/src/function.js @@ -20,6 +20,7 @@ var PDFFunction = (function pdfFunction() { var array = []; var codeSize = 0; var codeBuf = 0; + // 32 is a valid bps so shifting won't work var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); var strBytes = str.getBytes((length * bps + 7) / 8); From a191511c18048b71d3e785fa4ba30d53d47d25d4 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Mon, 21 Nov 2011 23:00:45 -0600 Subject: [PATCH 34/35] test fbf --- test/test_manifest.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test_manifest.json b/test/test_manifest.json index ab5821218..0bac41d34 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -5,6 +5,12 @@ "rounds": 1, "type": "eq" }, + { "id": "tracemonkey-fbf", + "file": "pdfs/tracemonkey.pdf", + "md5": "9a192d8b1a7dc652a19835f6f08098bd", + "rounds": 2, + "type": "fbf" + }, { "id": "html5-canvas-cheat-sheet-load", "file": "pdfs/canvas.pdf", "md5": "59510028561daf62e00bf9f6f066b033", From f90cdcf0cdb9bf3fbfa29d01fb77b52d8640db3c Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 22 Nov 2011 09:06:53 -0800 Subject: [PATCH 35/35] Update md5. --- test/test_manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_manifest.json b/test/test_manifest.json index 4d2c859b5..23d28bf0c 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -271,7 +271,7 @@ }, { "id": "devicen", "file": "pdfs/devicen.pdf", - "md5": "b9dbf00ec6bf02fcbefbb40332713aad", + "md5": "aac6a91725435d1376c6ff492dc5cb75", "link": false, "rounds": 1, "type": "eq"