diff --git a/Makefile b/Makefile index 9b2817fc4..a6f3ba3a4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ REPO = git@github.com:andreasgal/pdf.js.git BUILD_DIR := build -DEFAULT_BROWSERS := test/resources/browser_manifests/browser_manifest.json -DEFAULT_TESTS := test/test_manifest.json +DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json +DEFAULT_TESTS := test_manifest.json # Let folks define custom rules for their clones. -include local.mk @@ -43,7 +43,7 @@ PDF_BROWSERS := $(DEFAULT_BROWSERS) endif browser-test: - @if [ ! -f "$(PDF_BROWSERS)" ]; then \ + @if [ ! -f "test/$(PDF_BROWSERS)" ]; then \ echo "Browser manifest file $(PDF_BROWSERS) does not exist."; \ echo "Try copying one of the examples" \ "in test/resources/browser_manifests/"; \ @@ -52,8 +52,8 @@ browser-test: cd test; \ python test.py --reftest \ - --browserManifestFile=$(abspath $(PDF_BROWSERS)) \ - --manifestFile=$(abspath $(PDF_TESTS)) + --browserManifestFile=$(PDF_BROWSERS) \ + --manifestFile=$(PDF_TESTS) # make shell-test # diff --git a/fonts.js b/fonts.js index 5b020e899..1f0a6a211 100755 --- a/fonts.js +++ b/fonts.js @@ -542,21 +542,12 @@ var Font = (function() { '\x00\x01' + // encodingID string32(4 + numTables * 8); // start of the table record - var headerSize = (12 * 2 + (ranges.length * 5 * 2)); var segCount = ranges.length + 1; var segCount2 = segCount * 2; var searchRange = getMaxPower2(segCount) * 2; var searchEntry = Math.log(segCount) / Math.log(2); var rangeShift = 2 * segCount - searchRange; - var format314 = '\x00\x04' + // format - string16(headerSize) + // length - '\x00\x00' + // language - string16(segCount2) + - string16(searchRange) + - string16(searchEntry) + - string16(rangeShift); - // Fill up the 4 parallel arrays describing the segments. var startCount = ''; var endCount = ''; @@ -568,7 +559,7 @@ var Font = (function() { var range = ranges[i]; var start = range[0]; var end = range[1]; - var delta = (bias - start) % 0xffff; + var delta = (bias - start) & 0xffff; bias += (end - start + 1); startCount += string16(start); @@ -585,10 +576,19 @@ var Font = (function() { startCount += '\xFF\xFF'; idDeltas += '\x00\x01'; idRangeOffsets += '\x00\x00'; - format314 += endCount + '\x00\x00' + startCount + - idDeltas + idRangeOffsets + glyphsIds; - return stringToArray(cmap + format314); + var format314 = '\x00\x00' + // language + string16(segCount2) + + string16(searchRange) + + string16(searchEntry) + + string16(rangeShift) + + endCount + '\x00\x00' + startCount + + idDeltas + idRangeOffsets + glyphsIds; + + return stringToArray(cmap + + '\x00\x04' + // format + string16(format314.length + 4) + // length + format314); }; function createOS2Table(properties) { @@ -705,6 +705,9 @@ var Font = (function() { var data = file.getBytes(length); file.pos = previousPosition; + if (tag == 'head') + data[8] = data[9] = data[10] = data[11] = 0; // clearing checksum adjustment + return { tag: tag, checksum: checksum, @@ -907,11 +910,6 @@ var Font = (function() { convert: function font_convert(fontName, font, properties) { function createNameTable(name) { - // All the strings of the name table should be an odd number - // of bytes - if (name.length % 2) - name = name.slice(0, name.length - 1); - var strings = [ 'Original licence', // 0.Copyright name, // 1.Font family @@ -919,7 +917,7 @@ var Font = (function() { 'uniqueID', // 3.Unique ID name, // 4.Full font name 'Version 0.11', // 5.Version - 'Unknown', // 6.Postscript name + '', // 6.Postscript name 'Unknown', // 7.Trademark 'Unknown', // 8.Manufacturer 'Unknown' // 9.Designer @@ -958,7 +956,7 @@ var Font = (function() { platforms[i] + // platform ID encodings[i] + // encoding ID languages[i] + // language ID - string16(i) + // name ID + string16(j) + // name ID string16(str.length) + string16(strOffset); nameTable += nameRecord; @@ -1133,6 +1131,12 @@ var Font = (function() { for (var i = 0; i < chars.length; ++i) { var charcode = chars.charCodeAt(i); var unicode = encoding[charcode]; + if ('undefined' == typeof(unicode)) { + // FIXME/issue 233: we're hitting this in test/pdf/sizes.pdf + // at the moment, for unknown reasons. + warn('Unencoded charcode '+ charcode); + unicode = charcode; + } // Check if the glyph has already been converted if (!IsNum(unicode)) @@ -1388,6 +1392,18 @@ var Type1Parser = function() { return array; }; + function readNumber(str, index) { + while (str[index++] == ' ') + ; + var start = index; + + var count = 0; + while (str[index++] != ' ') + count++; + + return parseFloat(str.substr(start, count) || 0); + }; + this.extractFontProgram = function t1_extractFontProgram(stream) { var eexec = decrypt(stream, kEexecEncryptionKey, 4); var eexecStr = ''; @@ -1399,8 +1415,7 @@ var Type1Parser = function() { subrs: [], charstrings: [], properties: { - stemSnapH: [0, 0], - stemSnapV: [0, 0] + 'private': {} } }; @@ -1442,17 +1457,24 @@ var Type1Parser = function() { case '/Subrs': subrsSection = true; break; - case '/StdHW': - program.properties.stdHW = readNumberArray(eexecStr, i + 2)[0]; - break; - case '/StdVW': - program.properties.stdVW = readNumberArray(eexecStr, i + 2)[0]; - break; + case '/BlueValues': + case '/OtherBlues': + case '/FamilyBlues': + case '/FamilyOtherBlues': case '/StemSnapH': - program.properties.stemSnapH = readNumberArray(eexecStr, i + 2); - break; case '/StemSnapV': - program.properties.stemSnapV = readNumberArray(eexecStr, i + 2); + program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2); + break; + case '/StdHW': + case '/StdVW': + program.properties.private[token.substring(1)] = readNumberArray(eexecStr, i + 2)[0]; + break; + case '/BlueShift': + case '/BlueFuzz': + case '/BlueScale': + case '/LanguageGroup': + case '/ExpansionFactor': + program.properties.private[token.substring(1)] = readNumber(eexecStr, i + 1); break; } } else if (c == '/') { @@ -1642,7 +1664,7 @@ CFF.prototype = { return '\x1c' + String.fromCharCode((value >> 8) & 0xFF) + String.fromCharCode(value & 0xFF); - } else if (value >= (-2147483647-1) && value <= 2147483647) { + } else if (value >= (-2147483648) && value <= 2147483647) { return '\xff' + String.fromCharCode((value >>> 24) & 0xFF) + String.fromCharCode((value >> 16) & 0xFF) + @@ -1769,10 +1791,10 @@ CFF.prototype = { var dict = '\x00\x01\x01\x01\x30' + '\xf8\x1b\x00' + // version - '\xf8\x1b\x01' + // Notice - '\xf8\x1b\x02' + // FullName - '\xf8\x1b\x03' + // FamilyName - '\xf8\x1b\x04' + // Weight + '\xf8\x1c\x01' + // Notice + '\xf8\x1d\x02' + // FullName + '\xf8\x1e\x03' + // FamilyName + '\xf8\x1f\x04' + // Weight '\x1c\x00\x00\x10'; // Encoding var boundingBox = properties.bbox; @@ -1791,7 +1813,7 @@ CFF.prototype = { dict += self.encodeNumber(offset) + '\x11'; // Charstrings dict += self.encodeNumber(fields.private.length); - var offset = offset + fields.charstrings.length; + offset = offset + fields.charstrings.length; dict += self.encodeNumber(offset) + '\x12'; // Private return dict; @@ -1835,19 +1857,33 @@ CFF.prototype = { 'private': (function(self) { var data = '\x8b\x14' + // defaultWidth - '\x8b\x15' + // nominalWidth - self.encodeNumber(properties.stdHW || 0) + '\x0a' + // StdHW - self.encodeNumber(properties.stdVW || 0) + '\x0b'; // StdVW + '\x8b\x15'; // nominalWidth + var fieldMap = { + BlueValues: '\x06', + OtherBlues: '\x07', + FamilyBlues: '\x08', + FamilyOtherBlues: '\x09', + StemSnapH: '\x0c\x0c', + StemSnapV: '\x0c\x0d', + BlueShift: '\x0c\x0a', + BlueFuzz: '\x0c\x0b', + BlueScale: '\x0c\x09', + LanguageGroup: '\x0c\x11', + ExpansionFactor: '\x0c\x18' + }; + for (var field in fieldMap) { + if (!properties.private.hasOwnProperty(field)) continue; + var value = properties.private[field]; - var stemH = properties.stemSnapH; - for (var i = 0; i < stemH.length; i++) - data += self.encodeNumber(stemH[i]); - data += '\x0c\x0c'; // StemSnapH - - var stemV = properties.stemSnapV; - for (var i = 0; i < stemV.length; i++) - data += self.encodeNumber(stemV[i]); - data += '\x0c\x0d'; // StemSnapV + if (IsArray(value)) { + data += self.encodeNumber(value[0]); + for (var i = 1; i < value.length; i++) + data += self.encodeNumber(value[i] - value[i - 1]); + } else { + data += self.encodeNumber(value); + } + data += fieldMap[field]; + } data += self.encodeNumber(data.length + 4) + '\x13'; // Subrs offset diff --git a/pdf.js b/pdf.js index f61707587..a62045079 100644 --- a/pdf.js +++ b/pdf.js @@ -44,7 +44,7 @@ function assertWellFormed(cond, msg) { } function shadow(obj, prop, value) { - Object.defineProperty(obj, prop, { value: value, enumerable: true }); + Object.defineProperty(obj, prop, { value: value, enumerable: true, configurable: true, writable: false }); return value; } @@ -2923,9 +2923,15 @@ var XRef = (function() { var Page = (function() { function constructor(xref, pageNumber, pageDict) { - this.xref = xref; this.pageNumber = pageNumber; this.pageDict = pageDict; + this.stats = { + create: Date.now(), + compile: 0.0, + fonts: 0.0, + render: 0.0, + }; + this.xref = xref; } constructor.prototype = { @@ -2954,6 +2960,32 @@ var Page = (function() { return shadow(this, 'mediaBox', ((IsArray(obj) && obj.length == 4) ? obj : null)); }, + startRendering: function(canvasCtx, continuation) { + var self = this; + var stats = self.stats; + stats.compile = stats.fonts = stats.render = 0; + + var gfx = new CanvasGraphics(canvasCtx); + var fonts = [ ]; + + this.compile(gfx, fonts); + stats.compile = Date.now(); + + FontLoader.bind( + fonts, + function() { + stats.fonts = Date.now(); + // Always defer call to display() to work around bug in + // Firefox error reporting from XHR callbacks. + setTimeout(function () { + self.display(gfx); + stats.render = Date.now(); + continuation(); + }); + }); + }, + + compile: function(gfx, fonts) { if (this.code) { // content was compiled @@ -3385,18 +3417,13 @@ var Encodings = { var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; -// contexts store most of the state we need natively. -// However, PDF needs a bit more state, which we store here. -var CanvasExtraState = (function() { +var EvalState = (function() { function constructor() { // Are soft masks and alpha values shapes or opacities? this.alphaIsShape = false; this.fontSize = 0; this.textMatrix = IDENTITY_MATRIX; this.leading = 0; - // Current point (in user coordinates) - this.x = 0; - this.y = 0; // Start of text line (in text coordinates) this.lineX = 0; this.lineY = 0; @@ -3413,126 +3440,187 @@ var CanvasExtraState = (function() { return constructor; })(); -function ScratchCanvas(width, height) { - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - return canvas; -} - -var CanvasGraphics = (function() { - function constructor(canvasCtx, imageCanvas) { - this.ctx = canvasCtx; - this.current = new CanvasExtraState(); - this.stateStack = []; - this.pendingClip = null; - this.res = null; - this.xobjs = null; - this.ScratchCanvas = imageCanvas || ScratchCanvas; +var PartialEvaluator = (function() { + function constructor() { + this.state = new EvalState(); + this.stateStack = [ ]; } - var LINE_CAP_STYLES = ['butt', 'round', 'square']; - var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; - var NORMAL_CLIP = {}; - var EO_CLIP = {}; + var OP_MAP = { + // Graphics state + w: 'setLineWidth', + J: 'setLineCap', + j: 'setLineJoin', + M: 'setMiterLimit', + d: 'setDash', + ri: 'setRenderingIntent', + i: 'setFlatness', + gs: 'setGState', + q: 'save', + Q: 'restore', + cm: 'transform', - // Used for tiling patterns - var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + // Path + m: 'moveTo', + l: 'lineTo', + c: 'curveTo', + v: 'curveTo2', + y: 'curveTo3', + h: 'closePath', + re: 'rectangle', + S: 'stroke', + s: 'closeStroke', + f: 'fill', + F: 'fill', + 'f*': 'eoFill', + B: 'fillStroke', + 'B*': 'eoFillStroke', + b: 'closeFillStroke', + 'b*': 'closeEOFillStroke', + n: 'endPath', + + // Clipping + W: 'clip', + 'W*': 'eoClip', + + // Text + BT: 'beginText', + ET: 'endText', + Tc: 'setCharSpacing', + Tw: 'setWordSpacing', + Tz: 'setHScale', + TL: 'setLeading', + Tf: 'setFont', + Tr: 'setTextRenderingMode', + Ts: 'setTextRise', + Td: 'moveText', + TD: 'setLeadingMoveText', + Tm: 'setTextMatrix', + 'T*': 'nextLine', + Tj: 'showText', + TJ: 'showSpacedText', + "'": 'nextLineShowText', + '"': 'nextLineSetSpacingShowText', + + // Type3 fonts + d0: 'setCharWidth', + d1: 'setCharWidthAndBounds', + + // Color + CS: 'setStrokeColorSpace', + cs: 'setFillColorSpace', + SC: 'setStrokeColor', + SCN: 'setStrokeColorN', + sc: 'setFillColor', + scn: 'setFillColorN', + G: 'setStrokeGray', + g: 'setFillGray', + RG: 'setStrokeRGBColor', + rg: 'setFillRGBColor', + K: 'setStrokeCMYKColor', + k: 'setFillCMYKColor', + + // Shading + sh: 'shadingFill', + + // Images + BI: 'beginInlineImage', + + // XObjects + Do: 'paintXObject', + + // Marked content + MP: 'markPoint', + DP: 'markPointProps', + BMC: 'beginMarkedContent', + BDC: 'beginMarkedContentProps', + EMC: 'endMarkedContent', + + // Compatibility + BX: 'beginCompat', + EX: 'endCompat' + }; constructor.prototype = { - map: { - // Graphics state - w: 'setLineWidth', - J: 'setLineCap', - j: 'setLineJoin', - M: 'setMiterLimit', - d: 'setDash', - ri: 'setRenderingIntent', - i: 'setFlatness', - gs: 'setGState', - q: 'save', - Q: 'restore', - cm: 'transform', + eval: function(stream, xref, resources, fonts) { + resources = xref.fetchIfRef(resources) || new Dict(); + var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); - // Path - m: 'moveTo', - l: 'lineTo', - c: 'curveTo', - v: 'curveTo2', - y: 'curveTo3', - h: 'closePath', - re: 'rectangle', - S: 'stroke', - s: 'closeStroke', - f: 'fill', - F: 'fill', - 'f*': 'eoFill', - B: 'fillStroke', - 'B*': 'eoFillStroke', - b: 'closeFillStroke', - 'b*': 'closeEOFillStroke', - n: 'endPath', + var parser = new Parser(new Lexer(stream), false); + var objpool = []; - // Clipping - W: 'clip', - 'W*': 'eoClip', + function emitArg(arg) { + if (typeof arg == 'object' || typeof arg == 'string') { + var index = objpool.length; + objpool[index] = arg; + return 'objpool[' + index + ']'; + } + return arg; + } - // Text - BT: 'beginText', - ET: 'endText', - Tc: 'setCharSpacing', - Tw: 'setWordSpacing', - Tz: 'setHScale', - TL: 'setLeading', - Tf: 'setFont', - Tr: 'setTextRenderingMode', - Ts: 'setTextRise', - Td: 'moveText', - TD: 'setLeadingMoveText', - Tm: 'setTextMatrix', - 'T*': 'nextLine', - Tj: 'showText', - TJ: 'showSpacedText', - "'": 'nextLineShowText', - '"': 'nextLineSetSpacingShowText', + var src = ''; - // Type3 fonts - d0: 'setCharWidth', - d1: 'setCharWidthAndBounds', + var args = []; + var obj; + while (!IsEOF(obj = parser.getObj())) { + if (IsCmd(obj)) { + var cmd = obj.cmd; + var fn = OP_MAP[cmd]; + assertWellFormed(fn, "Unknown command '" + cmd + "'"); + // TODO figure out how to type-check vararg functions - // Color - CS: 'setStrokeColorSpace', - cs: 'setFillColorSpace', - SC: 'setStrokeColor', - SCN: 'setStrokeColorN', - sc: 'setFillColor', - scn: 'setFillColorN', - G: 'setStrokeGray', - g: 'setFillGray', - RG: 'setStrokeRGBColor', - rg: 'setFillRGBColor', - K: 'setStrokeCMYKColor', - k: 'setFillCMYKColor', + if (cmd == 'Do' && !args[0].code) { // eagerly compile XForm objects + var name = args[0].name; + var xobj = xobjs.get(name); + if (xobj) { + xobj = xref.fetchIfRef(xobj); + assertWellFormed(IsStream(xobj), 'XObject should be a stream'); - // Shading - sh: 'shadingFill', + var type = xobj.dict.get('Subtype'); + assertWellFormed( + IsName(type), + 'XObject should have a Name subtype' + ); - // Images - BI: 'beginInlineImage', + if ('Form' == type.name) { + args[0].code = this.eval(xobj, + xref, + xobj.dict.get('Resources'), + fonts); + } + } + } else if (cmd == 'Tf') { // eagerly collect all fonts + var fontRes = resources.get('Font'); + if (fontRes) { + fontRes = xref.fetchIfRef(fontRes); + var font = xref.fetchIfRef(fontRes.get(args[0].name)); + assertWellFormed(IsDict(font)); + if (!font.translated) { + font.translated = this.translateFont(font, xref, resources); + if (fonts && font.translated) { + // keep track of each font we translated so the caller can + // load them asynchronously before calling display on a page + fonts.push(font.translated); + } + } + } + } - // XObjects - Do: 'paintXObject', + src += 'this.'; + src += fn; + src += '('; + src += args.map(emitArg).join(','); + src += ');\n'; - // Marked content - MP: 'markPoint', - DP: 'markPointProps', - BMC: 'beginMarkedContent', - BDC: 'beginMarkedContentProps', - EMC: 'endMarkedContent', + args.length = 0; + } else { + assertWellFormed(args.length <= 33, 'Too many arguments'); + args.push(obj); + } + } - // Compatibility - BX: 'beginCompat', - EX: 'endCompat' + var fn = Function('objpool', src); + return function(gfx) { fn.call(gfx, objpool); }; }, translateFont: function(fontDict, xref, resources) { @@ -3697,7 +3785,66 @@ var CanvasGraphics = (function() { properties: properties }; }, + }; + return constructor; +})(); + +// contexts store most of the state we need natively. +// However, PDF needs a bit more state, which we store here. +var CanvasExtraState = (function() { + function constructor() { + // Are soft masks and alpha values shapes or opacities? + this.alphaIsShape = false; + this.fontSize = 0; + this.textMatrix = IDENTITY_MATRIX; + this.leading = 0; + // Current point (in user coordinates) + this.x = 0; + this.y = 0; + // Start of text line (in text coordinates) + this.lineX = 0; + this.lineY = 0; + // Character and word spacing + this.charSpace = 0; + this.wordSpace = 0; + this.textHScale = 100; + // Color spaces + this.fillColorSpace = null; + this.strokeColorSpace = null; + } + constructor.prototype = { + }; + return constructor; +})(); + +function ScratchCanvas(width, height) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +} + +var CanvasGraphics = (function() { + function constructor(canvasCtx, imageCanvas) { + this.ctx = canvasCtx; + this.current = new CanvasExtraState(); + this.stateStack = []; + this.pendingClip = null; + this.res = null; + this.xobjs = null; + this.ScratchCanvas = imageCanvas || ScratchCanvas; + } + + var LINE_CAP_STYLES = ['butt', 'round', 'square']; + var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; + + // Used for tiling patterns + var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2; + + constructor.prototype = { beginDrawing: function(mediaBox) { var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height; this.ctx.save(); @@ -3705,6 +3852,11 @@ var CanvasGraphics = (function() { this.ctx.translate(0, -mediaBox.height); }, + compile: function(stream, xref, resources, fonts) { + var pe = new PartialEvaluator(); + return pe.eval(stream, xref, resources, fonts); + }, + execute: function(code, xref, resources) { resources = xref.fetchIfRef(resources) || new Dict(); var savedXref = this.xref, savedRes = this.res, savedXobjs = this.xobjs; @@ -3719,88 +3871,6 @@ var CanvasGraphics = (function() { this.xref = savedXref; }, - compile: function(stream, xref, resources, fonts) { - resources = xref.fetchIfRef(resources) || new Dict(); - var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict(); - - var parser = new Parser(new Lexer(stream), false); - var objpool = []; - - function emitArg(arg) { - if (typeof arg == 'object' || typeof arg == 'string') { - var index = objpool.length; - objpool[index] = arg; - return 'objpool[' + index + ']'; - } - return arg; - } - - var src = ''; - - var args = []; - var map = this.map; - var obj; - while (!IsEOF(obj = parser.getObj())) { - if (IsCmd(obj)) { - var cmd = obj.cmd; - var fn = map[cmd]; - assertWellFormed(fn, "Unknown command '" + cmd + "'"); - // TODO figure out how to type-check vararg functions - - if (cmd == 'Do' && !args[0].code) { // eagerly compile XForm objects - var name = args[0].name; - var xobj = xobjs.get(name); - if (xobj) { - xobj = xref.fetchIfRef(xobj); - assertWellFormed(IsStream(xobj), 'XObject should be a stream'); - - var type = xobj.dict.get('Subtype'); - assertWellFormed( - IsName(type), - 'XObject should have a Name subtype' - ); - - if ('Form' == type.name) { - args[0].code = this.compile(xobj, - xref, - xobj.dict.get('Resources'), - fonts); - } - } - } else if (cmd == 'Tf') { // eagerly collect all fonts - var fontRes = resources.get('Font'); - if (fontRes) { - fontRes = xref.fetchIfRef(fontRes); - var font = xref.fetchIfRef(fontRes.get(args[0].name)); - assertWellFormed(IsDict(font)); - if (!font.translated) { - font.translated = this.translateFont(font, xref, resources); - if (fonts && font.translated) { - // keep track of each font we translated so the caller can - // load them asynchronously before calling display on a page - fonts.push(font.translated); - } - } - } - } - - src += 'this.'; - src += fn; - src += '('; - src += args.map(emitArg).join(','); - src += ');\n'; - - args.length = 0; - } else { - assertWellFormed(args.length <= 33, 'Too many arguments'); - args.push(obj); - } - } - - var fn = Function('objpool', src); - return function(gfx) { fn.call(gfx, objpool); }; - }, - endDrawing: function() { this.ctx.restore(); }, @@ -4349,8 +4419,8 @@ var CanvasGraphics = (function() { // Most likely we will not implement other types of shading // unless the browser supports them if (!shadingFn) { - TODO("Unknown or NYI type of shading '"+ typeNum +"'"); - return this.makeCssRgb(0, 0, 0); + warn("Unknown or NYI type of shading '"+ typeNum +"'"); + return 'hotpink'; } return shadingFn.call(this, shading, cs); @@ -5310,13 +5380,16 @@ var PDFFunction = (function() { return array; }, constructInterpolated: function() { - error('unhandled type of function'); + TODO('unhandled type of function'); + this.func = function () { return [ 255, 105, 180 ]; } }, constructStiched: function() { - error('unhandled type of function'); + TODO('unhandled type of function'); + this.func = function () { return [ 255, 105, 180 ]; } }, constructPostScript: function() { - error('unhandled type of function'); + TODO('unhandled type of function'); + this.func = function () { return [ 255, 105, 180 ]; } } }; diff --git a/test/driver.js b/test/driver.js index b4c2c64e0..e397f108b 100644 --- a/test/driver.js +++ b/test/driver.js @@ -1,8 +1,10 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + /* * A Test Driver for PDF.js */ - var appPath, browser, canvas, currentTaskIdx, manifest, stdout; function queryParams() { @@ -21,16 +23,16 @@ function load() { browser = params.browser; manifestFile = params.manifestFile; appPath = params.path; - + canvas = document.createElement("canvas"); canvas.mozOpaque = true; stdout = document.getElementById("stdout"); - + log("load...\n"); log("Harness thinks this browser is '"+ browser + "' with path " + appPath + "\n"); log("Fetching manifest "+ manifestFile +"..."); - + var r = new XMLHttpRequest(); r.open("GET", manifestFile, false); r.onreadystatechange = function(e) { @@ -61,15 +63,15 @@ function nextTask() { if (r.readyState == 4) { var data = r.mozResponseArrayBuffer || r.mozResponse || r.responseArrayBuffer || r.response; - + try { task.pdfDoc = new PDFDoc(new Stream(data)); } catch(e) { failure = 'load PDF doc: '+ e.toString(); } - + task.pageNum = 1, nextPage(task, failure); - } + } }; r.send(null); } @@ -92,25 +94,13 @@ function nextPage(task, loadError) { var failure = loadError || ''; var ctx = null; - var fonts; - var gfx = null; var page = null; - if (!failure) { - log(" loading page "+ task.pageNum +"... "); - ctx = canvas.getContext("2d"); - fonts = []; try { - gfx = new CanvasGraphics(ctx); + log(" loading page "+ task.pageNum +"... "); + ctx = canvas.getContext("2d"); page = task.pdfDoc.getPage(task.pageNum); - page.compile(gfx, fonts); - } catch(e) { - failure = 'compile: '+ e.toString(); - } - } - if (!failure) { - try { var pdfToCssUnitsCoef = 96.0 / 72.0; // using mediaBox for the canvas size var pageWidth = (page.mediaBox[2] - page.mediaBox[0]); @@ -118,42 +108,28 @@ function nextPage(task, loadError) { canvas.width = pageWidth * pdfToCssUnitsCoef; canvas.height = pageHeight * pdfToCssUnitsCoef; clear(ctx); + + page.startRendering( + ctx, + function() { snapshotCurrentPage(page, task, failure); }); } catch(e) { failure = 'page setup: '+ e.toString(); } } - if (!failure) { - try { - FontLoader.bind(fonts, function() { - snapshotCurrentPage(gfx, page, task, failure); - }); - } catch(e) { - failure = 'fonts: '+ e.toString(); - } - } - if (failure) { // Skip right to snapshotting if there was a failure, since the // fonts might be in an inconsistent state. - snapshotCurrentPage(gfx, page, task, failure); + snapshotCurrentPage(page, task, failure); } } -function snapshotCurrentPage(gfx, page, task, failure) { +function snapshotCurrentPage(page, task, failure) { log("done, snapshotting... "); - - if (!failure) { - try { - page.display(gfx); - } catch(e) { - failure = 'render: '+ e.toString(); - } - } sendTaskResult(canvas.toDataURL("image/png"), task, failure); log("done"+ (failure ? " (failed!: "+ failure +")" : "") +"\n"); - + // Set up the next request backoff = (inFlightRequests > 0) ? inFlightRequests * 10 : 0; setTimeout(function() { diff --git a/test/test.py b/test/test.py index 59c10bdff..b61ba816b 100644 --- a/test/test.py +++ b/test/test.py @@ -189,7 +189,7 @@ class BaseBrowserCommand(object): self._fixupMacPath() if not os.path.exists(self.path): - throw("Path to browser '%s' does not exist." % self.path) + raise Exception("Path to browser '%s' does not exist." % self.path) def setup(self): self.tempDir = tempfile.mkdtemp() diff --git a/web/images/buttons.png b/web/images/buttons.png index 3357b47d6..b96b9e141 100644 Binary files a/web/images/buttons.png and b/web/images/buttons.png differ diff --git a/web/images/source/Buttons.psd.zip b/web/images/source/Buttons.psd.zip index 528e6ee3c..39745f540 100644 Binary files a/web/images/source/Buttons.psd.zip and b/web/images/source/Buttons.psd.zip differ diff --git a/web/images/source/FileButton.psd.zip b/web/images/source/FileButton.psd.zip deleted file mode 100644 index 1f2b51cee..000000000 Binary files a/web/images/source/FileButton.psd.zip and /dev/null differ diff --git a/web/multi_page_viewer.css b/web/multi_page_viewer.css index 17b2537be..76bf8cd1e 100644 --- a/web/multi_page_viewer.css +++ b/web/multi_page_viewer.css @@ -16,6 +16,7 @@ canvas { span { font-size: 0.8em; + text-shadow: 0px 1px 0px #fff; } .control { @@ -31,12 +32,12 @@ span { height: 20px; padding: 0px; margin: 0px 2px 0px 0px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); } .control > select { @@ -45,12 +46,12 @@ span { height: 22px; padding: 2px 0px 0px; margin: 0px 0px 1px; - border-radius: 4px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); - -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25); + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.3); } .control > span { @@ -96,6 +97,8 @@ span { #controls { background-color: #eee; + background: -moz-linear-gradient(center bottom, #ddd 0%, #fff 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff)); border-bottom: 1px solid #666; padding: 4px 0px 0px 8px; position: fixed; @@ -114,58 +117,121 @@ span { -webkit-user-select: text; } -#previousPageButton { - background: url('images/buttons.png') no-repeat 0px -23px; +button { + background-color: #ddd; + background: -moz-linear-gradient(center bottom, #c3c3c3 0%, #f3f3f3 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #c3c3c3), color-stop(1.0, #f3f3f3)); + border: 1px solid #4d4d4d; cursor: default; - display: inline-block; float: left; - margin: 0px; + margin: 0px 0px 1px; + width: 29px; + height: 22px; + border-radius: 3px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; +} + +button:disabled { + background-color: #eee; + background: -moz-linear-gradient(center bottom, #ddd 0%, #fff 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff)); +} + +button:disabled > span { + opacity: 0.3; + -moz-opacity: 0.3; + -webkit-opacity: 0.3; +} + +button.down { + background-color: #777; + background: -moz-linear-gradient(center bottom, #888 0%, #555 100%); + background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #888), color-stop(1.0, #555)); + box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.8); + -moz-box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.8); + -webkit-box-shadow: inset 0px 0px 2px rgba(0, 0, 0, 0.8); +} + +#previousPageButton { width: 28px; - height: 23px; + border-right: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-topright: 0px; + -moz-border-radius-bottomright: 0px; + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; } -#previousPageButton.down { - background: url('images/buttons.png') no-repeat 0px -46px; -} - -#previousPageButton.disabled { +#previousPageButton > span { background: url('images/buttons.png') no-repeat 0px 0px; + display: inline-block; + width: 19px; + height: 19px; } #nextPageButton { - background: url('images/buttons.png') no-repeat -28px -23px; - cursor: default; - display: inline-block; - float: left; - margin: 0px; width: 28px; - height: 23px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + -moz-border-radius-topleft: 0px; + -moz-border-radius-bottomleft: 0px; + -webkit-border-top-left-radius: 0px; + -webkit-border-bottom-left-radius: 0px; } -#nextPageButton.down { - background: url('images/buttons.png') no-repeat -28px -46px; +#nextPageButton > span { + background: url('images/buttons.png') no-repeat -19px 0px; + display: inline-block; + width: 19px; + height: 19px; } -#nextPageButton.disabled { - background: url('images/buttons.png') no-repeat -28px 0px; +#singleLayoutButton { + width: 28px; + border-right: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-topright: 0px; + -moz-border-radius-bottomright: 0px; + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; +} + +#singleLayoutButton > span { + background: url('images/buttons.png') no-repeat -57px 0px; + display: inline-block; + width: 19px; + height: 19px; +} + +#splitLayoutButton { + width: 28px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + -moz-border-radius-topleft: 0px; + -moz-border-radius-bottomleft: 0px; + -webkit-border-top-left-radius: 0px; + -webkit-border-bottom-left-radius: 0px; +} + +#splitLayoutButton > span { + background: url('images/buttons.png') no-repeat -76px 0px; + display: inline-block; + width: 19px; + height: 19px; } #openFileButton { - background: url('images/buttons.png') no-repeat -56px -23px; - cursor: default; + margin-left: 3px; +} + +#openFileButton > span { + background: url('images/buttons.png') no-repeat -38px 0px; display: inline-block; - float: left; - margin: 0px 0px 0px 3px; - width: 29px; - height: 23px; -} - -#openFileButton.down { - background: url('images/buttons.png') no-repeat -56px -46px; -} - -#openFileButton.disabled { - background: url('images/buttons.png') no-repeat -56px 0px; + width: 19px; + height: 19px; } #fileInput { diff --git a/web/multi_page_viewer.html b/web/multi_page_viewer.html index 70a7ea6b3..00dac6484 100644 --- a/web/multi_page_viewer.html +++ b/web/multi_page_viewer.html @@ -14,8 +14,8 @@
- - + + Previous/Next @@ -35,8 +35,18 @@ Zoom + + + + - + Open File diff --git a/web/multi_page_viewer.js b/web/multi_page_viewer.js index 04e4d1766..3e7e122e0 100644 --- a/web/multi_page_viewer.js +++ b/web/multi_page_viewer.js @@ -43,7 +43,7 @@ var PDFViewer = { lastPagesDrawn: [], visiblePages: function() { - const pageBottomMargin = 10; + var pageBottomMargin = 10; var windowTop = window.pageYOffset; var windowBottom = window.pageYOffset + window.innerHeight; @@ -123,14 +123,7 @@ var PDFViewer = { ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - var gfx = new CanvasGraphics(ctx); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - page.compile(gfx, fonts); - - FontLoader.bind(fonts, function() { page.display(gfx); }); + page.startRendering(ctx, function() { }); } }, @@ -183,14 +176,7 @@ var PDFViewer = { ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - var gfx = new CanvasGraphics(ctx); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - page.compile(gfx, fonts); - - FontLoader.bind(fonts, function() { page.display(gfx); }); + page.startRendering(ctx, function() { }); } }, @@ -243,10 +229,8 @@ var PDFViewer = { setTimeout(window.onscroll, 0); document.location.hash = PDFViewer.pageNumber; - PDFViewer.previousPageButton.className = - (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = - (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + PDFViewer.previousPageButton.disabled = (PDFViewer.pageNumber === 1); + PDFViewer.nextPageButton.disabled = (PDFViewer.pageNumber === PDFViewer.numberOfPages); } }, @@ -333,10 +317,8 @@ var PDFViewer = { }).bind(this), 500); } - PDFViewer.previousPageButton.className = - (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = - (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + PDFViewer.previousPageButton.disabled = (PDFViewer.pageNumber === 1); + PDFViewer.nextPageButton.disabled = (PDFViewer.pageNumber === PDFViewer.numberOfPages); } }; @@ -404,42 +386,30 @@ window.onload = function() { PDFViewer.previousPageButton = document.getElementById('previousPageButton'); PDFViewer.previousPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToPreviousPage(); - } + PDFViewer.goToPreviousPage(); }; PDFViewer.previousPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + this.className = 'down'; }; PDFViewer.previousPageButton.onmouseup = function(evt) { - this.className = - (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.previousPageButton.onmouseout = function(evt) { - this.className = - (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.nextPageButton = document.getElementById('nextPageButton'); PDFViewer.nextPageButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.goToNextPage(); - } + PDFViewer.goToNextPage(); }; PDFViewer.nextPageButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + this.className = 'down'; }; PDFViewer.nextPageButton.onmouseup = function(evt) { - this.className = - (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.nextPageButton.onmouseout = function(evt) { - this.className = - (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.scaleSelect = document.getElementById('scaleSelect'); @@ -450,22 +420,16 @@ window.onload = function() { if (window.File && window.FileReader && window.FileList && window.Blob) { var openFileButton = document.getElementById('openFileButton'); openFileButton.onclick = function(evt) { - if (this.className.indexOf('disabled') === -1) { - PDFViewer.fileInput.click(); - } + PDFViewer.fileInput.click(); }; openFileButton.onmousedown = function(evt) { - if (this.className.indexOf('disabled') === -1) { - this.className = 'down'; - } + this.className = 'down'; }; openFileButton.onmouseup = function(evt) { - this.className = - (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; openFileButton.onmouseout = function(evt) { - this.className = - (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + this.className = ''; }; PDFViewer.fileInput = document.getElementById('fileInput'); @@ -540,10 +504,8 @@ window.onload = function() { // Update the page number input with the current page number. if (!PDFViewer.willJumpToPage && visiblePages.length > 0) { PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0]; - PDFViewer.previousPageButton.className = - (PDFViewer.pageNumber === 1) ? 'disabled' : ''; - PDFViewer.nextPageButton.className = - (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : ''; + PDFViewer.previousPageButton.disabled = (PDFViewer.pageNumber === 1); + PDFViewer.nextPageButton.disabled = (PDFViewer.pageNumber === PDFViewer.numberOfPages); } else { PDFViewer.willJumpToPage = false; } diff --git a/web/viewer.js b/web/viewer.js index 770ddb039..0e7cd59db 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -72,29 +72,15 @@ function displayPage(num) { ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.restore(); - var gfx = new CanvasGraphics(ctx); - - // page.compile will collect all fonts for us, once we have loaded them - // we can trigger the actual page rendering with page.display - var fonts = []; - page.compile(gfx, fonts); - var t2 = Date.now(); - - function displayPage() { - var t3 = Date.now(); - - page.display(gfx); - - var t4 = Date.now(); - - var infoDisplay = document.getElementById('info'); - infoDisplay.innerHTML = 'Time to load/compile/fonts/render: ' + - (t1 - t0) + '/' + (t2 - t1) + '/' + (t3 - t2) + '/' + (t4 - t3) + ' ms'; - } - - // Always defer call to displayPage() to work around bug in - // Firefox error reporting from XHR callbacks. - FontLoader.bind(fonts, function() { setTimeout(displayPage, 0); }); + page.startRendering( + ctx, + function() { + var infoDisplay = document.getElementById('info'); + var stats = page.stats; + var t2 = stats.compile, t3 = stats.fonts, t4 = stats.render; + infoDisplay.innerHTML = 'Time to load/compile/fonts/render: ' + + (t1 - t0) + '/' + (t2 - t1) + '/' + (t3 - t2) + '/' + (t4 - t3) + ' ms'; + }); } function nextPage() {