diff --git a/README.md b/README.md index f3500ae4d..7e5d2eeb3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # pdf.js - + ## Overview @@ -205,3 +205,4 @@ a "PDF Reference" from Adobe: Recommended chapters to read: "2. Overview", "3.4 File Structure", "4.1 Graphics Objects" that lists the PDF commands. + diff --git a/src/canvas.js b/src/canvas.js index 475dbac08..e42f53cf1 100644 --- a/src/canvas.js +++ b/src/canvas.js @@ -638,17 +638,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { geometry.hScale = tr[0] - bl[0]; geometry.vScale = tr[1] - bl[1]; } - var spaceGlyph = font.charsToGlyphs(' '); - - // Hack (sometimes space is not encoded) - if (spaceGlyph.length === 0 || spaceGlyph[0].width === 0) - spaceGlyph = font.charsToGlyphs('i'); - - // Fallback - if (spaceGlyph.length === 0 || spaceGlyph[0].width === 0) - spaceGlyph = [{width: 0}]; - - geometry.spaceWidth = spaceGlyph[0].width; + geometry.spaceWidth = font.spaceWidth; return geometry; }, @@ -687,13 +677,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var textSelection = textLayer && !skipTextSelection ? true : false; var textRenderingMode = current.textRenderingMode; - if (textSelection) { - ctx.save(); - this.applyTextTransforms(); - text.geom = this.getTextGeometry(); - ctx.restore(); - } - // Type3 fonts - each glyph is a "mini-PDF" if (font.coded) { ctx.save(); @@ -701,6 +684,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { ctx.translate(current.x, current.y); ctx.scale(textHScale, 1); + + if (textSelection) { + this.save(); + ctx.scale(1, -1); + text.geom = this.getTextGeometry(); + this.restore(); + } for (var i = 0; i < glyphsLength; ++i) { var glyph = glyphs[i]; @@ -720,7 +710,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var width = transformed[0] * fontSize + charSpacing; ctx.translate(width, 0); - current.x += width * textHScale2; + current.x += width * textHScale; text.str += glyph.unicode; text.length++; @@ -730,6 +720,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { } else { ctx.save(); this.applyTextTransforms(); + if (textSelection) + text.geom = this.getTextGeometry(); var width = 0; for (var i = 0; i < glyphsLength; ++i) { @@ -780,18 +772,26 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { showSpacedText: function canvasGraphicsShowSpacedText(arr) { var ctx = this.ctx; var current = this.current; + var font = current.font; var fontSize = current.fontSize; - var textHScale2 = current.textHScale * - (current.font.fontMatrix || IDENTITY_MATRIX)[0]; + var textHScale = current.textHScale; + if (!font.coded) + textHScale *= (font.fontMatrix || IDENTITY_MATRIX)[0]; var arrLength = arr.length; var textLayer = this.textLayer; - var font = current.font; var text = {str: '', length: 0, canvasWidth: 0, geom: {}}; var textSelection = textLayer ? true : false; if (textSelection) { ctx.save(); - this.applyTextTransforms(); + // Type3 fonts - each glyph is a "mini-PDF" (see also showText) + if (font.coded) { + ctx.transform.apply(ctx, current.textMatrix); + ctx.scale(1, -1); + ctx.translate(current.x, -1 * current.y); + ctx.scale(textHScale, 1); + } else + this.applyTextTransforms(); text.geom = this.getTextGeometry(); ctx.restore(); } @@ -799,7 +799,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { for (var i = 0; i < arrLength; ++i) { var e = arr[i]; if (isNum(e)) { - var spacingLength = -e * 0.001 * fontSize * textHScale2; + var spacingLength = -e * 0.001 * fontSize * textHScale; current.x += spacingLength; if (textSelection) { @@ -807,9 +807,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { text.canvasWidth += spacingLength; if (e < 0 && text.geom.spaceWidth > 0) { // avoid div by zero var numFakeSpaces = Math.round(-e / text.geom.spaceWidth); - for (var j = 0; j < numFakeSpaces; ++j) + if (numFakeSpaces > 0) { text.str += ' '; - text.length += numFakeSpaces > 0 ? 1 : 0; + text.length++; + } } } } else if (isString(e)) { @@ -1105,9 +1106,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { paintImageXObject: function canvasGraphicsPaintImageXObject(objId) { var imgData = this.objs.get(objId); - if (!imgData) { + if (!imgData) error('Dependent image isn\'t ready yet'); - } + this.save(); var ctx = this.ctx; var w = imgData.width; diff --git a/src/core.js b/src/core.js index dc44edb76..a6effd2bd 100644 --- a/src/core.js +++ b/src/core.js @@ -515,8 +515,7 @@ var PDFDoc = (function PDFDocClosure() { // Tell the worker the file it was created from. messageHandler.send('workerSrc', workerSrc); - messageHandler.on('test', function pdfDocTest(message) { - var supportTypedArray = message.data; + messageHandler.on('test', function pdfDocTest(supportTypedArray) { if (supportTypedArray) { this.worker = worker; this.setupMessageHandler(messageHandler); @@ -554,8 +553,7 @@ var PDFDoc = (function PDFDocClosure() { setupMessageHandler: function(messageHandler) { this.messageHandler = messageHandler; - messageHandler.on('page', function pdfDocPage(message) { - var data = message.data; + messageHandler.on('page', function pdfDocPage(data) { var pageNum = data.pageNum; var page = this.pageCache[pageNum]; var depFonts = data.depFonts; @@ -563,8 +561,7 @@ var PDFDoc = (function PDFDocClosure() { page.startRenderingFromIRQueue(data.IRQueue, depFonts); }, this); - messageHandler.on('obj', function pdfDocObj(message) { - var data = message.data; + messageHandler.on('obj', function pdfDocObj(data) { var id = data[0]; var type = data[1]; @@ -601,8 +598,7 @@ var PDFDoc = (function PDFDocClosure() { } }, this); - messageHandler.on('font_ready', function pdfDocFontReady(message) { - var data = message.data; + messageHandler.on('font_ready', function pdfDocFontReady(data) { var id = data[0]; var font = new FontShape(data[1]); @@ -614,8 +610,7 @@ var PDFDoc = (function PDFDocClosure() { } }.bind(this)); - messageHandler.on('page_error', function pdfDocError(message) { - var data = message.data; + messageHandler.on('page_error', function pdfDocError(data) { var page = this.pageCache[data.pageNum]; if (page.callback) page.callback(data.error); @@ -623,9 +618,9 @@ var PDFDoc = (function PDFDocClosure() { throw data.error; }, this); - messageHandler.on('jpeg_decode', function(message) { - var imageData = message.data[0]; - var components = message.data[1]; + messageHandler.on('jpeg_decode', function(data, promise) { + var imageData = data[0]; + var components = data[1]; if (components != 3 && components != 1) error('Only 3 component or 1 component can be returned'); @@ -652,7 +647,7 @@ var PDFDoc = (function PDFDocClosure() { buf[j] = data[i]; } } - message.reply({ data: buf, width: width, height: height}); + promise.resolve({ data: buf, width: width, height: height}); }).bind(this); var src = 'data:image/jpeg;base64,' + window.btoa(imageData); img.src = src; diff --git a/src/fonts.js b/src/fonts.js index de8a76dcd..83ce4abaa 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -2101,6 +2101,37 @@ var Font = (function FontClosure() { return rule; }, + get spaceWidth() { + // trying to estimate space character width + var possibleSpaceReplacements = ['space', 'minus', 'one', 'i']; + var width; + for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) { + var glyphName = possibleSpaceReplacements[i]; + // if possible, getting width by glyph name + if (glyphName in this.widths) { + width = this.widths[glyphName]; + break; + } + var glyphUnicode = GlyphsUnicode[glyphName]; + // finding the charcode via unicodeToCID map + var charcode = 0; + if (this.composite) + charcode = this.unicodeToCID[glyphUnicode]; + // ... via toUnicode map + if (!charcode && 'toUnicode' in this) + charcode = this.toUnicode.indexOf(glyphUnicode); + // setting it to unicode if negative or undefined + if (!(charcode > 0)) + charcode = glyphUnicode; + // trying to get width via charcode + width = this.widths[charcode]; + if (width) + break; // the non-zero width found + } + width = (width || this.defaultWidth) * this.widthMultiplier; + return shadow(this, 'spaceWidth', width); + }, + charToGlyph: function fonts_charToGlyph(charcode) { var unicode, width, codeIRQueue; diff --git a/src/image.js b/src/image.js index d00ae3590..59f177dc4 100644 --- a/src/image.js +++ b/src/image.js @@ -239,7 +239,6 @@ var PDFImage = (function PDFImageClosure() { bits = remainingBits; } } - if(decodeMap) console.timeEnd('getComps'); return output; }, getOpacity: function getOpacity() { diff --git a/src/parser.js b/src/parser.js index 6ffae0b1c..695438379 100644 --- a/src/parser.js +++ b/src/parser.js @@ -249,7 +249,7 @@ var Parser = (function ParserClosure() { if (name == 'CCITTFaxDecode' || name == 'CCF') { return new CCITTFaxStream(stream, params); } - TODO('filter "' + name + '" not supported yet'); + warn('filter "' + name + '" not supported yet'); return stream; } }; diff --git a/src/worker.js b/src/worker.js index c0496015a..c18de65ad 100644 --- a/src/worker.js +++ b/src/worker.js @@ -3,41 +3,6 @@ 'use strict'; -/** - * A wrapper for data to facilitate adding functionality to messages. - */ -function Message(data) { - this.data = data; - this.allowsReply = false; - this.combObj; - this.id; -} -Message.prototype = { - /** - * Reply to the action handler that sent the message. - */ - reply: function messageReply(data) { - if (!this.allowsReply) - error('This message does not accept replies.'); - - this.combObj.postMessage({ - isReply: true, - callbackId: this.id, - data: data - }); - }, - /** - * Setup the message to allow a reply. - * @param {Object} combObj The handler that has a postMessage function. - * @param {String} id The id to identify this message. - */ - setupReply: function setupReply(combObj, id) { - this.allowsReply = true; - this.combObj = combObj; - this.id = id; - } -}; - function MessageHandler(name, comObj) { this.name = name; this.comObj = comObj; @@ -65,11 +30,19 @@ function MessageHandler(name, comObj) { } } else if (data.action in ah) { var action = ah[data.action]; - var message = new Message(data.data); - if (data.callbackId) - message.setupReply(comObj, data.callbackId); - - action[0].call(action[1], message); + if (data.callbackId) { + var promise = new Promise(); + promise.then(function(resolvedData) { + comObj.postMessage({ + isReply: true, + callbackId: data.callbackId, + data: resolvedData + }); + }); + action[0].call(action[1], data.data, promise); + } else { + action[0].call(action[1], data.data); + } } else { throw 'Unkown action from worker: ' + data.action; } @@ -108,8 +81,7 @@ var WorkerMessageHandler = { setup: function wphSetup(handler) { var pdfDoc = null; - handler.on('test', function wphSetupTest(message) { - var data = message.data; + handler.on('test', function wphSetupTest(data) { handler.send('test', data instanceof Uint8Array); }); @@ -120,15 +92,13 @@ var WorkerMessageHandler = { // undefined action `workerSrc`. }); - handler.on('doc', function wphSetupDoc(message) { - var data = message.data; + handler.on('doc', function wphSetupDoc(data) { // Create only the model of the PDFDoc, which is enough for // processing the content of the pdf. pdfDoc = new PDFDocModel(new Stream(data)); }); - handler.on('page_request', function wphSetupPageRequest(message) { - var pageNum = message.data; + handler.on('page_request', function wphSetupPageRequest(pageNum) { pageNum = parseInt(pageNum); @@ -177,8 +147,7 @@ var WorkerMessageHandler = { }); }, this); - handler.on('font', function wphSetupFont(message) { - var data = message.data; + handler.on('font', function wphSetupFont(data) { var objId = data[0]; var name = data[1]; var file = data[2]; diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index d3caa968a..23ba6340e 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -19,3 +19,4 @@ !issue840.pdf !scan-bad.pdf !freeculture.pdf +!issue918.pdf diff --git a/test/pdfs/issue918.pdf b/test/pdfs/issue918.pdf new file mode 100644 index 000000000..ac1a9c37f Binary files /dev/null and b/test/pdfs/issue918.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index aab838807..7469db678 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -349,6 +349,12 @@ "pageLimit": 3, "type": "eq" }, + { "id": "issue918", + "file": "pdfs/issue918.pdf", + "md5": "d582cc0f2592ae82936589ced2a47e55", + "rounds": 1, + "type": "eq" + }, { "id": "aboutstacks", "file": "pdfs/aboutstacks.pdf", "md5": "6e7c8416a293ba2d83bc8dd20c6ccf51", diff --git a/web/viewer.js b/web/viewer.js index bdcac09d5..daf0174ab 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -537,7 +537,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight, ]; if (scale && scale !== PDFView.currentScale) - PDFView.setScale(scale, true); + PDFView.parseScale(scale, true); setTimeout(function pageViewScrollIntoViewRelayout() { // letting page to re-layout before scrolling