diff --git a/browser/extensions/pdfjs/README.mozilla b/browser/extensions/pdfjs/README.mozilla index 1febf64dd15..1493ab28628 100644 --- a/browser/extensions/pdfjs/README.mozilla +++ b/browser/extensions/pdfjs/README.mozilla @@ -1,4 +1,4 @@ This is the pdf.js project output, https://github.com/mozilla/pdf.js -Current extension version is: 0.7.82 +Current extension version is: 0.7.180 diff --git a/browser/extensions/pdfjs/components/PdfStreamConverter.js b/browser/extensions/pdfjs/components/PdfStreamConverter.js index 3a25f0df5da..79e6a0aac61 100644 --- a/browser/extensions/pdfjs/components/PdfStreamConverter.js +++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js @@ -201,9 +201,10 @@ PdfDataListener.prototype = { }; // All the priviledged actions. -function ChromeActions(domWindow, dataListener) { +function ChromeActions(domWindow, dataListener, contentDispositionFilename) { this.domWindow = domWindow; this.dataListener = dataListener; + this.contentDispositionFilename = contentDispositionFilename; } ChromeActions.prototype = { @@ -232,6 +233,7 @@ ChromeActions.prototype = { return docIsPrivate; }, download: function(data, sendResponse) { + var self = this; var originalUrl = data.originalUrl; // The data may not be downloaded so we need just retry getting the pdf with // the original url. @@ -259,9 +261,13 @@ ChromeActions.prototype = { // so the filename will be correct. let channel = Cc['@mozilla.org/network/input-stream-channel;1']. createInstance(Ci.nsIInputStreamChannel); + channel.QueryInterface(Ci.nsIChannel); + channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT; + if (self.contentDispositionFilename) { + channel.contentDispositionFilename = self.contentDispositionFilename; + } channel.setURI(originalUri); channel.contentStream = aInputStream; - channel.QueryInterface(Ci.nsIChannel); if ('nsIPrivateBrowsingChannel' in Ci && channel instanceof Ci.nsIPrivateBrowsingChannel) { channel.setPrivate(docIsPrivate); @@ -583,6 +589,10 @@ PdfStreamConverter.prototype = { // Creating storage for PDF data var contentLength = aRequest.contentLength; var dataListener = new PdfDataListener(contentLength); + var contentDispositionFilename; + try { + contentDispositionFilename = aRequest.contentDispositionFilename; + } catch (e) {} this.dataListener = dataListener; this.binaryStream = Cc['@mozilla.org/binaryinputstream;1'] .createInstance(Ci.nsIBinaryInputStream); @@ -613,7 +623,8 @@ PdfStreamConverter.prototype = { var domWindow = getDOMWindow(channel); // Double check the url is still the correct one. if (domWindow.document.documentURIObject.equals(aRequest.URI)) { - let actions = new ChromeActions(domWindow, dataListener); + let actions = new ChromeActions(domWindow, dataListener, + contentDispositionFilename); let requestListener = new RequestListener(actions); domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { requestListener.receive(event); diff --git a/browser/extensions/pdfjs/content/build/pdf.js b/browser/extensions/pdfjs/content/build/pdf.js index 18456051b63..f9365429dbf 100644 --- a/browser/extensions/pdfjs/content/build/pdf.js +++ b/browser/extensions/pdfjs/content/build/pdf.js @@ -16,8 +16,8 @@ */ var PDFJS = {}; -PDFJS.version = '0.7.82'; -PDFJS.build = 'd467790'; +PDFJS.version = '0.7.180'; +PDFJS.build = '3699c31'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it @@ -39,6 +39,10 @@ PDFJS.build = 'd467790'; * See the License for the specific language governing permissions and * limitations under the License. */ +/* globals assertWellFormed, calculateMD5, Catalog, error, info, isArray, + isArrayBuffer, isDict, isName, isStream, isString, Lexer, + Linearization, NullStream, PartialEvaluator, shadow, Stream, + StreamsSequenceStream, stringToPDFString, TODO, Util, warn, XRef */ 'use strict'; @@ -97,7 +101,7 @@ function getPdf(arg, callback) { calledErrorBack = true; params.error(); } - } + }; } xhr.onreadystatechange = function getPdfOnreadystatechange(e) { @@ -177,7 +181,7 @@ var Page = (function PageClosure() { get rotate() { var rotate = this.inheritPageProp('Rotate') || 0; // Normalize rotation so it's a multiple of 90 and between 0 and 270 - if (rotate % 90 != 0) { + if (rotate % 90 !== 0) { rotate = 0; } else if (rotate >= 360) { rotate = rotate % 360; @@ -556,7 +560,8 @@ var PDFDocument = (function PDFDocumentClosure() { }, getDocumentInfo: function PDFDocument_getDocumentInfo() { var docInfo = { - PDFFormatVersion: this.pdfFormatVersion + PDFFormatVersion: this.pdfFormatVersion, + IsAcroFormPresent: !!this.acroForm }; if (this.xref.trailer.has('Info')) { var infoDict = this.xref.trailer.get('Info'); @@ -767,6 +772,18 @@ var InvalidPDFException = (function InvalidPDFExceptionClosure() { return InvalidPDFException; })(); +var MissingPDFException = (function MissingPDFExceptionClosure() { + function MissingPDFException(msg) { + this.name = 'MissingPDFException'; + this.message = msg; + } + + MissingPDFException.prototype = new Error(); + MissingPDFException.constructor = MissingPDFException; + + return MissingPDFException; +})(); + function bytesToString(bytes) { var str = ''; var length = bytes.length; @@ -833,7 +850,7 @@ var Util = PDFJS.Util = (function UtilClosure() { m[3] * v[0] + m[4] * v[1] + m[5] * v[2], m[6] * v[0] + m[7] * v[1] + m[8] * v[2] ]; - } + }; // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) // For coordinate systems whose origin lies in the bottom-left, this @@ -850,7 +867,7 @@ var Util = PDFJS.Util = (function UtilClosure() { r[3] = rect[1]; } return r; - } + }; // Returns a rectangle [x1, y1, x2, y2] corresponding to the // intersection of rect1 and rect2. If no intersection, returns 'false' @@ -858,7 +875,7 @@ var Util = PDFJS.Util = (function UtilClosure() { Util.intersect = function Util_intersect(rect1, rect2) { function compare(a, b) { return a - b; - }; + } // Order points along the axes var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare), @@ -918,15 +935,15 @@ var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { case 270: rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; break; - case 360: - case 0: + //case 360: + //case 0: default: rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; break; } var offsetCanvasX, offsetCanvasY; var width, height; - if (rotateA == 0) { + if (rotateA === 0) { offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; width = Math.abs(viewBox[3] - viewBox[1]) * scale; @@ -1048,11 +1065,13 @@ function isArray(v) { } function isStream(v) { - return typeof v == 'object' && v != null && ('getChar' in v); + return typeof v == 'object' && v !== null && v !== undefined && + ('getChar' in v); } function isArrayBuffer(v) { - return typeof v == 'object' && v != null && ('byteLength' in v); + return typeof v == 'object' && v !== null && v !== undefined && + ('byteLength' in v); } function isRef(v) { @@ -1095,8 +1114,9 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { this.name = name; this.isRejected = false; this.error = null; + this.exception = null; // If you build a promise and pass in some data it's already resolved. - if (data != null) { + if (data !== null && data !== undefined) { this.isResolved = true; this._data = data; this.hasData = true; @@ -1107,7 +1127,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { this.callbacks = []; this.errbacks = []; this.progressbacks = []; - }; + } /** * Builds a promise that is resolved when all the passed in promises are * resolved. @@ -1203,6 +1223,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { this.isRejected = true; this.error = reason || null; + this.exception = exception || null; var errbacks = this.errbacks; for (var i = 0, ii = errbacks.length; i < ii; i++) { @@ -1221,7 +1242,8 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { callback.call(null, data); } else if (this.isRejected && errback) { var error = this.error; - errback.call(null, error); + var exception = this.exception; + errback.call(null, error, exception); } else { this.callbacks.push(callback); if (errback) @@ -1564,7 +1586,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { promise.reject(error); else promise.resolve(); - }; + } var continueCallback = params.continueCallback; // Once the operatorList and fonts are loaded, do the actual rendering. @@ -1576,7 +1598,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { } var gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, - this.objs, params.textLayer); + this.objs, !this.pageInfo.disableTextLayer && params.textLayer); try { this.display(gfx, params.viewport, complete, continueCallback); } catch (e) { @@ -1665,7 +1687,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var continueWrapper; if (continueCallback) - continueWrapper = function() { continueCallback(next); } + continueWrapper = function() { continueCallback(next); }; else continueWrapper = next; @@ -1829,6 +1851,10 @@ var WorkerTransport = (function WorkerTransportClosure() { this.workerReadyPromise.reject(data.exception.name, data.exception); }, this); + messageHandler.on('MissingPDF', function transportMissingPDF(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + messageHandler.on('UnknownError', function transportUnknownError(data) { this.workerReadyPromise.reject(data.exception.message, data.exception); }, this); @@ -2022,6 +2048,8 @@ function addContextCurrentTransform(ctx) { // If the context doesn't expose a `mozCurrentTransform`, add a JS based on. if (!ctx.mozCurrentTransform) { // Store the original context + ctx._scaleX = ctx._scaleX || 1.0; + ctx._scaleY = ctx._scaleY || 1.0; ctx._originalSave = ctx.save; ctx._originalRestore = ctx.restore; ctx._originalRotate = ctx.rotate; @@ -2029,7 +2057,7 @@ function addContextCurrentTransform(ctx) { ctx._originalTranslate = ctx.translate; ctx._originalTransform = ctx.transform; - ctx._transformMatrix = [1, 0, 0, 1, 0, 0]; + ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0]; ctx._transformStack = []; Object.defineProperty(ctx, 'mozCurrentTransform', { @@ -2212,7 +2240,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { buf = imgArray[imgArrayPos++]; mask = 128; } - if (!(buf & mask) == inverseDecode) { + if (!(buf & mask) === inverseDecode) { buffer[bufferPos] = 0; } bufferPos += 4; @@ -2906,7 +2934,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { var lineWidth = current.lineWidth; var a1 = current.textMatrix[0], b1 = current.textMatrix[1]; var scale = Math.sqrt(a1 * a1 + b1 * b1); - if (scale == 0 || lineWidth == 0) + if (scale === 0 || lineWidth === 0) lineWidth = this.getSinglePixelWidth(); else lineWidth /= scale; @@ -3521,7 +3549,7 @@ var Dict = (function DictClosure() { callback(key, this.get(key)); } }; - }; + } return Dict; })(); @@ -3863,7 +3891,7 @@ var XRef = (function XRefClosure() { for (j = 0; j < typeFieldWidth; ++j) type = (type << 8) | stream.getByte(); // if type field is absent, its default value = 1 - if (typeFieldWidth == 0) + if (typeFieldWidth === 0) type = 1; for (j = 0; j < offsetFieldWidth; ++j) offset = (offset << 8) | stream.getByte(); @@ -4099,7 +4127,7 @@ var XRef = (function XRefClosure() { } if (!isCmd(obj3, 'obj')) { // some bad pdfs use "obj1234" and really mean 1234 - if (obj3.cmd.indexOf('obj') == 0) { + if (obj3.cmd.indexOf('obj') === 0) { num = parseInt(obj3.cmd.substring(3), 10); if (!isNaN(num)) return num; @@ -4353,7 +4381,7 @@ var PDFFunction = (function PDFFunctionClosure() { return this.constructInterpolatedFromIR(IR); case CONSTRUCT_STICHED: return this.constructStichedFromIR(IR); - case CONSTRUCT_POSTSCRIPT: + //case CONSTRUCT_POSTSCRIPT: default: return this.constructPostScriptFromIR(IR); } @@ -4506,7 +4534,7 @@ var PDFFunction = (function PDFFunctionClosure() { } return y; - } + }; }, constructInterpolated: function PDFFunction_constructInterpolated(str, @@ -4543,7 +4571,7 @@ var PDFFunction = (function PDFFunctionClosure() { return out; - } + }; }, constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { @@ -5278,7 +5306,8 @@ var CIDToUnicodeMaps = { 174, 0, 0, {f: 2, c: 178}, 181, 185, {f: 3, c: 188}, {f: 6, c: 192}, {f: 16, c: 199}, 0, {f: 6, c: 217}, {f: 6, c: 224}, {f: 16, c: 231}, 0, {f: 7, c: 249}, 352, 376, 381, [773, 8254], 353, 8482, 382, 0, 8194, - {s: 91}, 65512, {s: 3}, {f: 63, c: 65377}, {s: 243}, [8195, 12288], + {f: 59, c: 33}, 165, {f: 31, c: 93}, 65512, {f: 2, c: 125}, 0, + {f: 63, c: 65377}, {s: 243}, [8195, 12288], {f: 2, c: 12289}, 65292, 65294, 12539, {f: 2, c: 65306}, 65311, 65281, {f: 2, c: 12443}, 180, 65344, 168, 65342, 65507, 65343, {f: 2, c: 12541}, {f: 2, c: 12445}, 12291, 20189, {f: 3, c: 12293}, 12540, 8213, 8208, 65295, @@ -6030,9 +6059,9 @@ var CIDToUnicodeMaps = { 37854, 37880, 37937, 37957, 37960, 38290, 0, 64041, 38557, 38575, 38707, 38715, 38723, 38733, 38735, [12205, 38737], 0, 38999, 39013, {f: 2, c: 64042}, 39207, 64044, 39326, 39502, 39641, 39644, 39797, 39794, - 39823, 39857, 39867, 39936, 40304, 40299, 64045, 40473, 40657, {s: 636}, - 8364, 8486, 0, 0, 64256, {f: 2, c: 64259}, 257, 299, 363, 275, 333, 256, - 298, 362, 274, 332, {f: 4, c: 8539}, {f: 2, c: 8531}, 8304, + 39823, 39857, 39867, 39936, 40304, 40299, 64045, 40473, 40657, 0, 92, + {s: 634}, 8364, 8486, 0, 0, 64256, {f: 2, c: 64259}, 257, 299, 363, 275, + 333, 256, 298, 362, 274, 332, {f: 4, c: 8539}, {f: 2, c: 8531}, 8304, {f: 6, c: 8308}, {f: 10, c: 8320}, 461, 282, 0, 7868, 463, 0, 296, 465, 0, 467, 366, 360, 462, 283, 0, 7869, 464, 0, 297, 466, 0, 468, 367, 361, 593, 8049, 8048, 509, 0, 596, 0, 0, 601, 0, 0, 602, 0, 0, 603, 8051, 8050, 0, @@ -12420,7 +12449,7 @@ var ColorSpace = (function ColorSpaceClosure() { return true; } for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] != 0 || decode[i + 1] != 1) + if (decode[i] !== 0 || decode[i + 1] != 1) return false; } return true; @@ -13468,7 +13497,7 @@ var LabCS = (function LabCSClosure() { this.bmin = -100; this.bmax = 100; } - }; + } // Function g(x) from spec function fn_g(x) { @@ -13902,7 +13931,7 @@ var AES128Cipher = (function AES128CipherClosure() { this.buffer = buffer; this.bufferLength = bufferLength; this.iv = iv; - if (result.length == 0) + if (result.length === 0) return new Uint8Array([]); if (result.length == 1) return result[0]; @@ -14082,12 +14111,13 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { this.algorithm = algorithm; var keyLength = dict.get('Length') || 40; if (!isInt(keyLength) || - keyLength < 40 || (keyLength % 8) != 0) + keyLength < 40 || (keyLength % 8) !== 0) error('invalid key length'); // prepare keys var ownerPassword = stringToBytes(dict.get('O')); var userPassword = stringToBytes(dict.get('U')); var flags = dict.get('P'); + this.disableTextLayer = !(flags & 16); var revision = dict.get('R'); var encryptMetadata = algorithm == 4 && // meaningful when V is 4 dict.get('EncryptMetadata') !== false; // makes true as default value @@ -14147,7 +14177,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { function buildCipherConstructor(cf, name, num, gen, key) { var cryptFilter = cf.get(name.name); var cfm; - if (cryptFilter != null) + if (cryptFilter !== null && cryptFilter !== undefined) cfm = cryptFilter.get('CFM'); if (!cfm || cfm.name == 'None') { return function cipherTransformFactoryBuildCipherConstructorNone() { @@ -14718,7 +14748,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { fnArray.push(fn); argsArray.push(args); args = []; - } else if (obj != null) { + } else if (obj !== null && obj !== undefined) { args.push(obj instanceof Dict ? obj.getAll() : obj); assertWellFormed(args.length <= 33, 'Too many arguments'); } @@ -14904,7 +14934,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { case 'Tj': chunk += fontCharsToUnicode(args[0], font); break; - case "'": + case '\'': // For search, adding a extra white space for line breaks would be // better here, but that causes too much spaces in the // text-selection divs. @@ -14972,7 +15002,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } args = []; - } else if (obj != null) { + } else if (obj !== null && obj !== undefined) { assertWellFormed(args.length <= 33, 'Too many arguments'); args.push(obj); } @@ -15186,7 +15216,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var result = []; for (var j = 0, jj = glyphsData.length; j < jj; j++) { var glyphID = (glyphsData[j++] << 8) | glyphsData[j]; - if (glyphID == 0) + if (glyphID === 0) continue; var code = j >> 1; @@ -15206,20 +15236,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var widths = dict.get('W'); if (widths) { - var start = 0, end = 0; for (var i = 0, ii = widths.length; i < ii; i++) { + var start = widths[i++]; var code = xref.fetchIfRef(widths[i]); if (isArray(code)) { for (var j = 0, jj = code.length; j < jj; j++) glyphsWidths[start++] = code[j]; - start = 0; - } else if (start) { + } else { var width = widths[++i]; for (var j = start; j <= code; j++) glyphsWidths[j] = width; - start = 0; - } else { - start = code; } } } @@ -15389,6 +15415,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } var fontNameStr = fontName && fontName.name; + // 9.7.6.1 + if (type.name == 'CIDFontType0') { + var cidEncoding = baseDict.get('Encoding'); + if (isName(cidEncoding)) { + fontNameStr = fontNameStr + '-' + cidEncoding.name; + } + } var baseFontStr = baseFont && baseFont.name; if (fontNameStr !== baseFontStr) { warn('The FontDescriptor\'s FontName is "' + fontNameStr + @@ -15482,7 +15515,8 @@ var SYMBOLIC_FONT_GLYPH_OFFSET = 0xF000; // except for Type 3 fonts var PDF_GLYPH_SPACE_UNITS = 1000; -// Until hinting is fully supported this constant can be used +// Hinting is currently disabled due to unknown problems on windows +// in tracemonkey and various other pdfs with type1 fonts. var HINTING_ENABLED = false; var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; @@ -15865,11 +15899,27 @@ var CMapConverterList = { '90ms-RKSJ-H': sjisToUnicode, '90ms-RKSJ-V': sjisToUnicode, '90msp-RKSJ-H': sjisToUnicode, - '90msp-RKSJ-V': sjisToUnicode + '90msp-RKSJ-V': sjisToUnicode, + 'GBK-EUC-H': gbkToUnicode +}; + +// CMaps using Hankaku (Halfwidth) Latin glyphs instead of proportional one. +// We need to distinguish them to get correct widths from CIDFont dicts. +var HalfwidthCMaps = { + 'H': true, + 'V': true, + 'EUC-H': true, + 'EUC-V': true, + '90ms-RKSJ-H': true, + '90ms-RKSJ-V': true, + 'UniJIS-UCS2-HW-H': true, + 'UniJIS-UCS2-HW-V': true }; var decodeBytes; if (typeof TextDecoder !== 'undefined') { + // The encodings supported by TextDecoder can be found at: + // http://encoding.spec.whatwg.org/#concept-encoding-get decodeBytes = function(bytes, encoding) { return new TextDecoder(encoding).decode(bytes); }; @@ -15899,6 +15949,10 @@ function sjisToUnicode(str) { return decodeBytes(stringToBytes(str), 'shift_jis'); } +function gbkToUnicode(str) { + return decodeBytes(stringToBytes(str), 'gbk'); +} + // Some characters, e.g. copyrightserif, mapped to the private use area and // might not be displayed using standard fonts. Mapping/hacking well-known chars // to the similar equivalents in the normal characters range. @@ -17674,7 +17728,7 @@ var Font = (function FontClosure() { this.encoding = properties.baseEncoding; this.loading = true; - }; + } var numFonts = 0; function getUniqueName() { @@ -17687,7 +17741,7 @@ var Font = (function FontClosure() { array[i] = str.charCodeAt(i); return array; - }; + } function arrayToString(arr) { var str = ''; @@ -17695,16 +17749,16 @@ var Font = (function FontClosure() { str += String.fromCharCode(arr[i]); return str; - }; + } function int16(bytes) { return (bytes[0] << 8) + (bytes[1] & 0xff); - }; + } function int32(bytes) { return (bytes[0] << 24) + (bytes[1] << 16) + (bytes[2] << 8) + (bytes[3] & 0xff); - }; + } function getMaxPower2(number) { var maxPower = 0; @@ -17718,26 +17772,26 @@ var Font = (function FontClosure() { for (var i = 1; i < maxPower; i++) value *= 2; return value; - }; + } function string16(value) { return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); - }; + } function safeString16(value) { // clamp value to the 16-bit int range value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value; return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); - }; + } function string32(value) { return String.fromCharCode((value >> 24) & 0xff) + String.fromCharCode((value >> 16) & 0xff) + String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); - }; + } function createOpenTypeHeader(sfnt, file, numTables) { // Windows hates the Mac TrueType sfnt version number @@ -17763,7 +17817,7 @@ var Font = (function FontClosure() { file.file += header; file.virtualOffset += header.length; - }; + } function createTableEntry(file, tag, data) { // offset @@ -17789,7 +17843,7 @@ var Font = (function FontClosure() { string32(offset) + string32(length)); file.file += tableEntry; file.virtualOffset += data.length; - }; + } function getRanges(glyphs) { // Array.sort() sorts by characters, not numerically, so convert to an @@ -17818,9 +17872,9 @@ var Font = (function FontClosure() { } return ranges; - }; + } - function createCMapTable(glyphs, deltas) { + function createCmapTable(glyphs, deltas) { var ranges = getRanges(glyphs); var numTables = 1; @@ -17895,7 +17949,7 @@ var Font = (function FontClosure() { '\x00\x04' + // format string16(format314.length + 4) + // length format314); - }; + } function validateOS2Table(os2) { var stream = new Stream(os2.data); @@ -18018,7 +18072,7 @@ var Font = (function FontClosure() { string16(0) + // usDefaultChar string16(firstCharIndex || properties.firstChar) + // usBreakChar '\x00\x03'; // usMaxContext - }; + } function createPostTable(properties) { var angle = Math.floor(properties.italicAngle * (Math.pow(2, 16))); @@ -18031,7 +18085,7 @@ var Font = (function FontClosure() { '\x00\x00\x00\x00' + // maxMemType42 '\x00\x00\x00\x00' + // minMemType1 '\x00\x00\x00\x00'; // maxMemType1 - }; + } function createNameTable(name, proto) { if (!proto) { @@ -18096,6 +18150,37 @@ var Font = (function FontClosure() { return nameTable; } + // Normalize the charcodes in the cmap table into unicode values + // that will work with the (3, 1) cmap table we will write out. + function cmapCharcodeToUnicode(charcode, symbolic, platformId, encodingId) { + var unicode; + if (symbolic) { + // These codes will be shifted into the range + // SYMBOLIC_FONT_GLYPH_OFFSET to (SYMBOLIC_FONT_GLYPH_OFFSET + 0xFF) + // so that they are not in the control character range that could + // be displayed as spaces by browsers. + if (platformId === 3 && encodingId === 0 || + platformId === 1 && encodingId === 0) { + unicode = SYMBOLIC_FONT_GLYPH_OFFSET | (charcode & 0xFF); + } + } else { + if (platformId === 3 && encodingId === 1) { + // A (3, 1) table is alredy unicode (Microsoft Unicode format) + unicode = charcode; + } else if (platformId === 1 && encodingId === 0) { + // TODO(mack): Should apply the changes to convert the + // MacRomanEncoding to Mac OS Roman encoding in 9.6.6.4 + // table 115 of the pdf spec + var glyphName = Encodings.MacRomanEncoding[charcode]; + if (glyphName) { + unicode = GlyphsUnicode[glyphName]; + } + } + } + return unicode; + } + + Font.prototype = { name: null, font: null, @@ -18143,7 +18228,7 @@ var Font = (function FontClosure() { offset: offset, data: data }; - }; + } function readOpenTypeHeader(ttf) { return { @@ -18153,7 +18238,7 @@ var Font = (function FontClosure() { entrySelector: int16(ttf.getBytes(2)), rangeShift: int16(ttf.getBytes(2)) }; - }; + } function createGlyphNameMap(glyphs, ids, properties) { var glyphNames = properties.glyphNames; @@ -18178,177 +18263,218 @@ var Font = (function FontClosure() { properties.baseEncoding = encoding; } - function readCMapTable(cmap, font) { + /** + * Read the appropriate subtable from the cmap according to 9.6.6.4 from + * PDF spec + */ + function readCmapTable(cmap, font, hasEncoding, isSymbolicFont) { var start = (font.start ? font.start : 0) + cmap.offset; font.pos = start; var version = int16(font.getBytes(2)); - var numRecords = int16(font.getBytes(2)); + var numTables = int16(font.getBytes(2)); - var records = []; - for (var i = 0; i < numRecords; i++) { - records.push({ - platformID: int16(font.getBytes(2)), - encodingID: int16(font.getBytes(2)), - offset: int32(font.getBytes(4)) - }); - } + var potentialTable; + var foundPreferredTable; + // There's an order of preference in terms of which cmap subtable we + // want to use. So scan through them to find our preferred table. + for (var i = 0; i < numTables; i++) { + var platformId = int16(font.getBytes(2)); + var encodingId = int16(font.getBytes(2)); + var offset = int32(font.getBytes(4)); + var useTable = false; + var canBreak = false; - // Check that table are sorted by platformID then encodingID, - records.sort(function fontReadCMapTableSort(a, b) { - return ((a.platformID << 16) + a.encodingID) - - ((b.platformID << 16) + b.encodingID); - }); - - var tables = [records[0]]; - for (var i = 1; i < numRecords; i++) { - // The sanitizer will drop the font if 2 tables have the same - // platformID and the same encodingID, this will be correct for - // most cases but if the font has been made for Mac it could - // exist a few platformID: 1, encodingID: 0 but with a different - // language field and that's correct. But the sanitizer does not - // seem to support this case. - var current = records[i]; - var previous = records[i - 1]; - if (((current.platformID << 16) + current.encodingID) <= - ((previous.platformID << 16) + previous.encodingID)) - continue; - tables.push(current); - } - - var missing = numRecords - tables.length; - if (missing) { - numRecords = tables.length; - var data = string16(version) + string16(numRecords); - - for (var i = 0; i < numRecords; i++) { - var table = tables[i]; - data += string16(table.platformID) + - string16(table.encodingID) + - string32(table.offset); + // The following block implements the following from the spec: + // + // When the font has no Encoding entry, or the font descriptor’s + // Symbolic flag is set (in which case the Encoding entry + // is ignored), this shall occur: + // - If the font contains a (3, 0) subtable, the range of + // - Otherwise, the (1, 0) subtable will be used. + // Otherwise, if the font does have an encoding: + // - Use the (3, 1) cmap subtable + // - Otherwise, use the (1, 0) subtable if present + // + // The following diverges slightly from the above spec in order + // to handle the case that hasEncoding and isSymbolicFont are both + // true. In this, based on the ordering of the rules in the spec, + // my interpretation is that we should be acting as if the font is + // symbolic. + // + // However, in this case, the test pdf 'preistabelle.pdf' + // is interpreting this case as a non-symbolic font. In this case + // though, 'presitabelle.pdf' does contain a (3, 1) table and does + // not contain a (3, 0) table which indicates it is non-symbolic. + // + // Thus, I am using this heurisitic of looking at which table is + // found to truly determine whether or not the font is symbolic. + // That is, if the specific symbolic/non-symbolic font specific + // tables (3, 0) or (3, 1) is found, that information is used for + // deciding if the font is symbolic or not. + // + // TODO(mack): This section needs some more thought on whether the + // heuristic is good enough. For now, it passes all the regression + // tests. + if (isSymbolicFont && platformId === 3 && encodingId === 0) { + useTable = true; + canBreak = true; + foundPreferredTable = true; + } else if (hasEncoding && platformId === 3 && encodingId === 1) { + useTable = true; + canBreak = true; + foundPreferredTable = true; + // Update the isSymbolicFont based on this heuristic + isSymbolicFont = false; + } else if (platformId === 1 && encodingId === 0 && + !foundPreferredTable) { + useTable = true; + foundPreferredTable = true; + } else if (!potentialTable) { + // We will use an arbitrary table if we cannot find a preferred + // table + useTable = true; } - for (var i = 0, ii = data.length; i < ii; i++) - cmap.data[i] = data.charCodeAt(i); + if (useTable) { + potentialTable = { + platformId: platformId, + encodingId: encodingId, + offset: offset, + isSymbolicFont: isSymbolicFont + }; + } + if (canBreak) { + break; + } } - for (var i = 0; i < numRecords; i++) { - var table = tables[i]; - font.pos = start + table.offset; + if (!potentialTable) { + error('Could not find a cmap table'); + return; + } - var format = int16(font.getBytes(2)); - var length = int16(font.getBytes(2)); - var language = int16(font.getBytes(2)); + if (!foundPreferredTable) { + warn('Did not find a cmap of suitable format. Interpreting (' + + potentialTable.platformId + ', ' + potentialTable.encodingId + + ') as (3, 1) table'); + potentialTable.platformId = 3; + potentialTable.encodingId = 1; + } - 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 ids = []; - for (var j = 0; j < 256; j++) { - var index = font.getByte(); - if (index) { - glyphs.push({ unicode: j, code: j }); - ids.push(index); - } + font.pos = start + potentialTable.offset; + var format = int16(font.getBytes(2)); + var length = int16(font.getBytes(2)); + var language = int16(font.getBytes(2)); + + var hasShortCmap = false; + var mappings = []; + + // TODO(mack): refactor this cmap subtable reading logic out + if (format === 0) { + for (var j = 0; j < 256; j++) { + var index = font.getByte(); + if (!index) { + continue; } - return { - glyphs: glyphs, - ids: ids, - hasShortCmap: true - }; - } 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)); + mappings.push({ + charcode: j, + glyphId: index + }); + } + hasShortCmap = true; + } 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; } - for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].delta = int16(font.getBytes(2)); - } + var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex); + segment.offsetIndex = offsetIndex; + offsetsCount = Math.max(offsetsCount, offsetIndex + + segment.end - segment.start + 1); + } - var offsetsCount = 0; - for (segIndex = 0; segIndex < segCount; segIndex++) { - var segment = segments[segIndex]; - var rangeOffset = int16(font.getBytes(2)); - if (!rangeOffset) { - segment.offsetIndex = -1; + var offsets = []; + for (var j = 0; j < offsetsCount; j++) { + offsets.push(int16(font.getBytes(2))); + } + + 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 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; - - glyphs.push({ unicode: j, code: j }); - ids.push(glyphCode); + var glyphId = offsetIndex < 0 ? j : + offsets[offsetIndex + j - start]; + glyphId = (glyphId + delta) & 0xFFFF; + if (glyphId === 0) { + continue; } + mappings.push({ + charcode: j, + glyphId: glyphId + }); } - - return { - glyphs: glyphs, - ids: 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 - // table. (This looks weird, so I can have missed something), this - // works on Linux but seems to fails on Mac so let's rewrite the - // cmap table to a 3-1-4 style - var firstCode = int16(font.getBytes(2)); - var entryCount = int16(font.getBytes(2)); - - var glyphs = []; - var ids = []; - for (var j = 0; j < entryCount; j++) { - var glyphCode = int16(font.getBytes(2)); - var code = firstCode + j; - - glyphs.push({ unicode: code, code: code }); - ids.push(glyphCode); - } - - return { - glyphs: glyphs, - ids: 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 + // table. (This looks weird, so I can have missed something), this + // works on Linux but seems to fails on Mac so let's rewrite the + // cmap table to a 3-1-4 style + var firstCode = int16(font.getBytes(2)); + var entryCount = int16(font.getBytes(2)); + + var glyphs = []; + var ids = []; + for (var j = 0; j < entryCount; j++) { + var glyphId = int16(font.getBytes(2)); + var charcode = firstCode + j; + + mappings.push({ + charcode: charcode, + glyphId: glyphId + }); + } + } else { + error('cmap table has unsupported format: ' + format); } - error('Unsupported cmap table format'); - }; + + return { + platformId: potentialTable.platformId, + encodingId: potentialTable.encodingId, + isSymbolicFont: potentialTable.isSymbolicFont, + mappings: mappings, + hasShortCmap: hasShortCmap + }; + } function sanitizeMetrics(font, header, metrics, numGlyphs) { if (!header) { @@ -18362,19 +18488,29 @@ var Font = (function FontClosure() { font.pos += header.length - 2; var numOfMetrics = int16(font.getBytes(2)); + if (numOfMetrics > numGlyphs) { + info('The numOfMetrics (' + numOfMetrics + ') should not be ' + + 'greater than the numGlyphs (' + numGlyphs + ')'); + // Reduce numOfMetrics if it is greater than numGlyphs + numOfMetrics = numGlyphs; + header.data[34] = (numOfMetrics & 0xff00) >> 8; + header.data[35] = numOfMetrics & 0x00ff; + } + var numOfSidebearings = numGlyphs - numOfMetrics; var numMissing = numOfSidebearings - - ((hmtx.length - numOfMetrics * 4) >> 1); + ((metrics.length - numOfMetrics * 4) >> 1); + if (numMissing > 0) { font.pos = (font.start ? font.start : 0) + metrics.offset; var entries = ''; - for (var i = 0, ii = hmtx.length; i < ii; i++) + for (var i = 0, ii = metrics.length; i < ii; i++) entries += String.fromCharCode(font.getByte()); for (var i = 0; i < numMissing; i++) entries += '\x00\x00'; metrics.data = stringToArray(entries); } - }; + } function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart) { if (sourceEnd - sourceStart <= 12) { @@ -18431,6 +18567,50 @@ var Font = (function FontClosure() { return glyf.length; } + function sanitizeHead(head, numGlyphs, locaLength) { + var data = head.data; + + // Validate version: + // Should always be 0x00010000 + var version = int32([data[0], data[1], data[2], data[3]]); + if (version >> 16 !== 1) { + info('Attempting to fix invalid version in head table: ' + version); + data[0] = 0; + data[1] = 1; + data[2] = 0; + data[3] = 0; + } + + var indexToLocFormat = int16([data[50], data[51]]); + if (indexToLocFormat < 0 || indexToLocFormat > 1) { + info('Attempting to fix invalid indexToLocFormat in head table: ' + + indexToLocFormat); + + // The value of indexToLocFormat should be 0 if the loca table + // consists of short offsets, and should be 1 if the loca table + // consists of long offsets. + // + // The number of entries in the loca table should be numGlyphs + 1. + // + // Using this information, we can work backwards to deduce if the + // size of each offset in the loca table, and thus figure out the + // appropriate value for indexToLocFormat. + + var numGlyphsPlusOne = numGlyphs + 1; + if (locaLength === numGlyphsPlusOne << 1) { + // 0x0000 indicates the loca table consists of short offsets + data[50] = 0; + data[51] = 0; + } else if (locaLength === numGlyphsPlusOne << 2) { + // 0x0001 indicates the loca table consists of long offsets + data[50] = 0; + data[51] = 1; + } else { + warn('Could not fix indexToLocFormat: ' + indexToLocFormat); + } + } + } + function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong) { var itemSize, itemDecode, itemEncode; @@ -18480,7 +18660,7 @@ var Font = (function FontClosure() { startOffset = endOffset; } - if (writeOffset == 0) { + if (writeOffset === 0) { // glyf table cannot be empty -- redoing the glyf and loca tables // to have single glyph with one point var simpleGlyph = new Uint8Array( @@ -18607,7 +18787,7 @@ var Font = (function FontClosure() { offset: int16(font.getBytes(2)) }; // using only Macintosh and Windows platform/encoding names - if ((r.platform == 1 && r.encoding == 0 && r.language == 0) || + if ((r.platform == 1 && r.encoding === 0 && r.language === 0) || (r.platform == 3 && r.encoding == 1 && r.language == 0x409)) { records.push(r); } @@ -18639,7 +18819,7 @@ var Font = (function FontClosure() { var data = os2Table.data; // usWinAscent == 0 makes font unreadable by windows var usWinAscent = (data[74] << 8) | data[75]; - if (usWinAscent == 0) + if (usWinAscent === 0) return false; return true; @@ -18855,6 +19035,10 @@ var Font = (function FontClosure() { sanitizeTTPrograms(fpgm, prep); + if (head) { + sanitizeHead(head, numGlyphs, loca.length); + } + var isGlyphLocationsLong = int16([head.data[50], head.data[51]]); if (head && loca && glyf) { sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong); @@ -18866,7 +19050,7 @@ var Font = (function FontClosure() { // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth // Sometimes it's 0. That needs to be fixed - if (hhea.data[10] == 0 && hhea.data[11] == 0) { + if (hhea.data[10] === 0 && hhea.data[11] === 0) { hhea.data[10] = 0xFF; hhea.data[11] = 0xFF; } @@ -18948,175 +19132,131 @@ var Font = (function FontClosure() { ids.push(i); } } else { - var cmapTable = readCMapTable(cmap, font); + this.useToFontChar = true; + // Most of the following logic in this code branch is based on the + // 9.6.6.4 of the PDF spec. - glyphs = cmapTable.glyphs; - ids = cmapTable.ids; + // TODO(mack): + // We are using this.hasEncoding to mean that the encoding is either + // MacRomanEncoding or WinAnsiEncoding (following spec in 9.6.6.4), + // but this.hasEncoding is currently true for any encodings on the + // Encodings object (e.g. MacExpertEncoding). So should consider using + // better check for this. + var cmapTable = readCmapTable(cmap, font, this.hasEncoding, + this.isSymbolicFont); - var hasShortCmap = !!cmapTable.hasShortCmap; + // TODO(mack): If the (3, 0) cmap table used, then the font is + // symbolic. The range of charcodes in the cmap table should be + // one of the following: + // -> 0x0000 - 0x00FF + // -> 0xF000 - 0xF0FF + // -> 0xF100 - 0xF1FF + // -> 0xF200 - 0xF2FF + // If it is not, we should change not consider this a symbolic font + this.isSymbolicFont = cmapTable.isSymbolicFont; + + var cmapPlatformId = cmapTable.platformId; + var cmapEncodingId = cmapTable.encodingId; + var cmapMappings = cmapTable.mappings; + var cmapMappingsLength = cmapMappings.length; + var glyphs = []; + var ids = []; + for (var i = 0; i < cmapMappingsLength; ++i) { + var cmapMapping = cmapMappings[i]; + var charcode = cmapMapping.charcode; + var unicode = cmapCharcodeToUnicode(charcode, this.isSymbolicFont, + cmapPlatformId, cmapEncodingId); + + if (!unicode) { + // TODO(mack): gotta check if skipping mappings where we cannot find + // a unicode is the correct behaviour + continue; + } + glyphs.push({ + code: charcode, + unicode: unicode + }); + ids.push(cmapMapping.glyphId); + } + + var hasShortCmap = cmapTable.hasShortCmap; var toFontChar = this.toFontChar; if (hasShortCmap && ids.length == numGlyphs) { // Fixes the short cmap tables -- some generators use incorrect // glyph id. - for (var i = 0, ii = ids.length; i < ii; i++) + for (var i = 0, ii = ids.length; i < ii; i++) { ids[i] = i; - } - - var unusedUnicode = CMAP_GLYPH_OFFSET; - var glyphNames = properties.glyphNames || []; - var encoding = properties.baseEncoding; - var differences = properties.differences; - if (toFontChar && toFontChar.length > 0) { - // checking if cmap is just identity map - var isIdentity = true; - for (var i = 0, ii = glyphs.length; i < ii; i++) { - if (glyphs[i].unicode != i + 1) { - isIdentity = false; - break; - } - } - // if it is, replacing with meaningful toUnicode values - if (isIdentity && !this.isSymbolicFont) { - var usedUnicodes = [], unassignedUnicodeItems = []; - for (var i = 0, ii = glyphs.length; i < ii; i++) { - var unicode = toFontChar[i + 1]; - if (!unicode || typeof unicode !== 'number' || - unicode in usedUnicodes) { - unassignedUnicodeItems.push(i); - continue; - } - glyphs[i].unicode = unicode; - usedUnicodes[unicode] = true; - } - for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) { - var i = unassignedUnicodeItems[j]; - while (unusedUnicode in usedUnicodes) - unusedUnicode++; - var cid = i + 1; - // override only if unicode mapping is not specified - if (!(cid in toFontChar)) - toFontChar[cid] = unusedUnicode; - glyphs[i].unicode = unusedUnicode++; - } - this.useToFontChar = true; } } - // remove glyph references outside range of avaialable glyphs or empty - var glyphsRemoved = 0; - for (var i = ids.length - 1; i >= 0; i--) { - if (ids[i] < numGlyphs && - (!emptyGlyphIds[ids[i]] || this.isSymbolicFont)) - continue; - ids.splice(i, 1); - glyphs.splice(i, 1); - glyphsRemoved++; - } - - // checking if it's a "true" symbolic font + // Rewrite the whole toFontChar dictionary with a new one using the + // information from the mappings in the cmap table. + var newToFontChar = []; if (this.isSymbolicFont) { - var minUnicode = 0xFFFF, maxUnicode = 0; for (var i = 0, ii = glyphs.length; i < ii; i++) { - var unicode = glyphs[i].unicode; - minUnicode = Math.min(minUnicode, unicode); - maxUnicode = Math.max(maxUnicode, unicode); + var glyph = glyphs[i]; + // For (3, 0) cmap tables: + // The charcode key being stored in toFontChar is the lower byte + // of the two-byte charcodes of the cmap table since according to + // the spec: 'each byte from the string shall be prepended with the + // high byte of the range [of charcodes in the cmap table], to form + // a two-byte character, which shall be used to select the + // associated glyph description from the subtable'. + // + // For (1, 0) cmap tables: + // 'single bytes from the string shall be used to look up the + // associated glyph descriptions from the subtable'. This means + // charcodes in the cmap will be single bytes, so no-op since + // glyph.code & 0xFF === glyph.code + newToFontChar[glyph.code & 0xFF] = glyph.unicode; } - // high byte must be the same for min and max unicodes - if ((maxUnicode & 0xFF00) != (minUnicode & 0xFF00)) - this.isSymbolicFont = false; - } + } else { - // heuristics: if removed more than 5 glyphs encoding WinAnsiEncoding - // does not set properly (broken PDFs have about 100 removed glyphs) - if (glyphsRemoved > 5) { - warn('Switching TrueType encoding to MacRomanEncoding for ' + - this.name + ' font'); - encoding = Encodings.MacRomanEncoding; - } + var encoding = properties.baseEncoding; + var differences = properties.differences; - if (hasShortCmap && this.hasEncoding && !this.isSymbolicFont) { - // Re-encode short map encoding to unicode -- that simplifies the - // resolution of MacRoman encoded glyphs logic for TrueType fonts: - // copying all characters to private use area, all mapping all known - // glyphs to the unicodes. The glyphs and ids arrays will grow. - var usedUnicodes = []; - for (var i = 0, ii = glyphs.length; i < ii; i++) { - var code = glyphs[i].unicode; - var gid = ids[i]; - glyphs[i].unicode += CMAP_GLYPH_OFFSET; - toFontChar[code] = glyphs[i].unicode; + // TODO(mack): check if it is necessary to shift control characters + // for non-symbolic fonts so that browsers dont't render them using + // space characters - var glyphName = glyphNames[gid] || encoding[code]; - if (glyphName in GlyphsUnicode) { - var unicode = GlyphsUnicode[glyphName]; - if (unicode in usedUnicodes) - continue; - - usedUnicodes[unicode] = true; - glyphs.push({ - unicode: unicode, - code: glyphs[i].code - }); - ids.push(gid); - toFontChar[code] = unicode; + var glyphCodeMapping = cmapTable.glyphCodeMapping; + for (var charcode = 0; charcode < encoding.length; ++charcode) { + if (!encoding.hasOwnProperty(charcode)) { + continue; } - } - this.useToFontChar = true; - } else if (!this.isSymbolicFont && (this.hasEncoding || - properties.glyphNames || differences.length > 0)) { - // Re-encode cmap encoding to unicode, based on the 'post' table data - // diffrence array or base encoding - var reverseMap = []; - for (var i = 0, ii = glyphs.length; i < ii; i++) - reverseMap[glyphs[i].unicode] = i; - var newGlyphUnicodes = []; - for (var i = 0, ii = glyphs.length; i < ii; i++) { - var code = glyphs[i].unicode; - var changeCode = false; - var gid = ids[i]; + // Since the cmap table that we will be writing out is a (3, 1) + // unicode table, in this section we will rewrites the charcodes + // in the pdf into unicodes - var glyphName = glyphNames[gid]; + var glyphName = encoding[charcode]; + // A nonsymbolic font should not have a Differences array, but + // if it does have one, we should still use it + if (charcode in differences) { + glyphName = differences[charcode]; + } + + // Finally, any undefined entries in the table shall be filled + // using StandardEncoding if (!glyphName) { - glyphName = differences[code] || encoding[code]; - changeCode = true; + glyphName = Encodings.StandardEncoding[charcode]; } - if (glyphName in GlyphsUnicode) { - var unicode = GlyphsUnicode[glyphName]; - if (!unicode || reverseMap[unicode] === i) - continue; // unknown glyph name or in its own place - newGlyphUnicodes[i] = unicode; - if (changeCode) - toFontChar[code] = unicode; - delete reverseMap[code]; - } - } - for (var index in newGlyphUnicodes) { - if (newGlyphUnicodes.hasOwnProperty(index)) { - var unicode = newGlyphUnicodes[index]; - if (reverseMap[unicode]) { - // avoiding assigning to the same unicode - glyphs[index].unicode = unusedUnicode++; - continue; - } - glyphs[index].unicode = unicode; - reverseMap[unicode] = index; - } - } - this.useToFontChar = true; - } + // TODO(mack): Handle the case that the glyph name cannot be + // mapped as specified, in which case the glyph name shall be + // looked up in the font program's 'post' table (if one is + // present) and the associated glyph id shall be used. + // + // For now, we're just using the '.notdef' glyph name in this + // case. + glyphName = glyphName || '.notdef'; - // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range. - if (this.isSymbolicFont) { - for (var i = 0, ii = glyphs.length; i < ii; i++) { - var code = glyphs[i].unicode & 0xFF; - var fontCharCode = SYMBOLIC_FONT_GLYPH_OFFSET | code; - glyphs[i].unicode = toFontChar[code] = fontCharCode; + var unicode = GlyphsUnicode[glyphName]; + newToFontChar[charcode] = unicode; } - this.useToFontChar = true; } + this.toFontChar = toFontChar = newToFontChar; createGlyphNameMap(glyphs, ids, properties); this.glyphNameMap = properties.glyphNameMap; @@ -19129,7 +19269,7 @@ var Font = (function FontClosure() { } // Converting glyphs and ids into font's cmap table - cmap.data = createCMapTable(glyphs, ids); + cmap.data = createCmapTable(glyphs, ids); var unicodeIsEnabled = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { unicodeIsEnabled[glyphs[i].unicode] = true; @@ -19273,7 +19413,7 @@ var Font = (function FontClosure() { 'OS/2': stringToArray(createOS2Table(properties, charstrings)), // Character to glyphs mapping - 'cmap': createCMapTable(charstrings.slice(), + 'cmap': createCmapTable(charstrings.slice(), ('glyphIds' in font) ? font.glyphIds : null), // Font header @@ -19374,7 +19514,7 @@ var Font = (function FontClosure() { var firstChar = properties.firstChar, lastChar = properties.lastChar; var map = []; if (properties.composite) { - var isIdentityMap = this.cidToUnicode.length == 0; + var isIdentityMap = this.cidToUnicode.length === 0; for (var i = firstChar, ii = lastChar; i <= ii; i++) { // TODO missing map the character according font's CMap var cid = i; @@ -19410,14 +19550,18 @@ var Font = (function FontClosure() { if (!cidToUnicode) return; // identity encoding + var cidEncoding = properties.cidEncoding; + var overwrite = HalfwidthCMaps[cidEncoding]; var cid = 1, i, j, k, ii; for (i = 0, ii = cidToUnicode.length; i < ii; ++i) { var unicode = cidToUnicode[i]; if (isArray(unicode)) { var length = unicode.length; for (j = 0; j < length; j++) { - cidToUnicodeMap[cid] = unicode[j]; - unicodeToCIDMap[unicode[j]] = cid; + cidToUnicodeMap[cid] = k = unicode[j]; + if (!unicodeToCIDMap[k] || overwrite) { + unicodeToCIDMap[k] = cid; + } } cid++; } else if (typeof unicode === 'object') { @@ -19426,7 +19570,9 @@ var Font = (function FontClosure() { k = unicode.c; for (j = 0; j < fillLength; ++j) { cidToUnicodeMap[cid] = k; - unicodeToCIDMap[k] = cid; + if (!unicodeToCIDMap[k] || overwrite) { + unicodeToCIDMap[k] = cid; + } cid++; k++; } @@ -19434,19 +19580,24 @@ var Font = (function FontClosure() { cid += unicode.s; } else if (unicode) { cidToUnicodeMap[cid] = unicode; - unicodeToCIDMap[unicode] = cid; + if (!unicodeToCIDMap[unicode] || overwrite) { + unicodeToCIDMap[unicode] = cid; + } cid++; } else cid++; } - var cidEncoding = properties.cidEncoding; - if (cidEncoding && cidEncoding.indexOf('Identity-') !== 0) { + if (!cidEncoding) { + return; + } + if (cidEncoding.indexOf('Identity-') !== 0) { // input is already Unicode for non-Identity CMap encodings. - // However, Unicode-to-CID conversion is needed - // regardless of the CMap encoding. So we can't reset - // unicodeToCID. this.cidToUnicode = []; + } else { + // We don't have to do reverse conversions if the string is + // already CID. + this.unicodeToCID = []; } }, @@ -19460,7 +19611,7 @@ var Font = (function FontClosure() { // Add the font-face rule to the document var url = ('url(data:' + this.mimetype + ';base64,' + window.btoa(data) + ');'); - var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; + var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}'; var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); if (!styleElement) { @@ -19504,7 +19655,7 @@ var Font = (function FontClosure() { if (!charcode && 'toUnicode' in this) charcode = this.toUnicode.indexOf(glyphUnicode); // setting it to unicode if negative or undefined - if (!(charcode > 0)) + if (charcode <= 0) charcode = glyphUnicode; // trying to get width via charcode width = this.widths[charcode]; @@ -19681,12 +19832,323 @@ var ErrorFont = (function ErrorFontClosure() { return ErrorFont; })(); -var CallothersubrCmd = (function CallothersubrCmdClosure() { - function CallothersubrCmd(index) { - this.index = index; +/* + * CharStrings are encoded following the the CharString Encoding sequence + * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. + * The value in a byte indicates a command, a number, or subsequent bytes + * that are to be interpreted in a special way. + * + * CharString Number Encoding: + * A CharString byte containing the values from 32 through 255 inclusive + * indicate an integer. These values are decoded in four ranges. + * + * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, + * indicate the integer v - 139. Thus, the integer values from -107 through + * 107 inclusive may be encoded in single byte. + * + * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * [(v - 247) x 256] + w + 108 + * + * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * -[(v - 251) * 256] - w - 108 + * + * 4. A CharString containing the value 255 indicates that the next 4 bytes + * are a two complement signed integer. The first of these bytes contains the + * highest order bits, the second byte contains the next higher order bits + * and the fourth byte contain the lowest order bits. + * + * + * CharString Command Encoding: + * CharStrings commands are encoded in 1 or 2 bytes. + * + * Single byte commands are encoded in 1 byte that contains a value between + * 0 and 31 inclusive. + * If a command byte contains the value 12, then the value in the next byte + * indicates a command. This "escape" mechanism allows many extra commands + * to be encoded and this encoding technique helps to minimize the length of + * the charStrings. + */ +var Type1CharString = (function Type1CharStringClosure() { + var COMMAND_MAP = { + 'hstem': [1], + 'vstem': [3], + 'vmoveto': [4], + 'rlineto': [5], + 'hlineto': [6], + 'vlineto': [7], + 'rrcurveto': [8], + 'callsubr': [10], + 'flex': [12, 35], + 'drop' : [12, 18], + 'endchar': [14], + 'rmoveto': [21], + 'hmoveto': [22], + 'vhcurveto': [30], + 'hvcurveto': [31] + }; + + function Type1CharString() { + this.width = 0; + this.lsb = 0; + this.flexing = false; + this.output = []; + this.stack = []; } - return CallothersubrCmd; + Type1CharString.prototype = { + convert: function Type1CharString_convert(encoded, subrs) { + var count = encoded.length; + var error = false; + for (var i = 0; i < count; i++) { + var value = encoded[i]; + if (value < 32) { + if (value === 12) { + value = (value << 8) + encoded[++i]; + } + switch (value) { + case 1: // hstem + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + error = this.executeCommand(2, COMMAND_MAP.hstem); + break; + case 3: // vstem + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + error = this.executeCommand(2, COMMAND_MAP.vstem); + break; + case 4: // vmoveto + if (this.flexing) { + if (this.stack.length < 1) { + error = true; + break; + } + // Add the dx for flex and but also swap the values so they are + // the right order. + var dy = this.stack.pop(); + this.stack.push(0, dy); + break; + } + error = this.executeCommand(1, COMMAND_MAP.vmoveto); + break; + case 5: // rlineto + error = this.executeCommand(2, COMMAND_MAP.rlineto); + break; + case 6: // hlineto + error = this.executeCommand(1, COMMAND_MAP.hlineto); + break; + case 7: // vlineto + error = this.executeCommand(1, COMMAND_MAP.vlineto); + break; + case 8: // rrcurveto + error = this.executeCommand(6, COMMAND_MAP.rrcurveto); + break; + case 9: // closepath + // closepath is a Type1 command that does not take argument and is + // useless in Type2 and it can simply be ignored. + this.stack = []; + break; + case 10: // callsubr + if (this.stack.length < 1) { + error = true; + break; + } + var subrNumber = this.stack.pop(); + error = this.convert(subrs[subrNumber], subrs); + break; + case 11: // return + return error; + case 13: // hsbw + if (this.stack.length < 2) { + error = true; + break; + } + // To convert to type2 we have to move the width value to the + // first part of the charstring and then use hmoveto with lsb. + var wx = this.stack.pop(); + var sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(sbx); + error = this.executeCommand(1, COMMAND_MAP.hmoveto); + break; + case 14: // endchar + this.output.push(COMMAND_MAP.endchar[0]); + break; + case 21: // rmoveto + if (this.flexing) { + break; + } + error = this.executeCommand(2, COMMAND_MAP.rmoveto); + break; + case 22: // hmoveto + if (this.flexing) { + // Add the dy for flex. + this.stack.push(0); + break; + } + error = this.executeCommand(1, COMMAND_MAP.hmoveto); + break; + case 30: // vhcurveto + error = this.executeCommand(4, COMMAND_MAP.vhcurveto); + break; + case 31: // hvcurveto + error = this.executeCommand(4, COMMAND_MAP.hvcurveto); + break; + case (12 << 8) + 0: // dotsection + // dotsection is a Type1 command to specify some hinting feature + // for dots that do not take a parameter and it can safely be + // ignored for Type2. + this.stack = []; + break; + case (12 << 8) + 1: // vstem3 + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + // [vh]stem3 are Type1 only and Type2 supports [vh]stem with + // multiple parameters, so instead of returning [vh]stem3 take a + // shortcut and return [vhstem] instead. + error = this.executeCommand(2, COMMAND_MAP.vstem); + break; + case (12 << 8) + 2: // hstem3 + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + // See vstem3. + error = this.executeCommand(2, COMMAND_MAP.hstem); + break; + case (12 << 8) + 6: // seac + // seac is like type 2's special endchar but it doesn't use the + // first argument asb, so remove it. + error = this.executeCommand(4, COMMAND_MAP.endchar); + break; + case (12 << 8) + 7: // sbw + if (this.stack.length < 4) { + error = true; + break; + } + // To convert to type2 we have to move the width value to the + // first part of the charstring and then use rmoveto with + // (dx, dy). The height argument will not be used for vmtx and + // vhea tables reconstruction -- ignoring it. + var wy = this.stack.pop(); + var wx = this.stack.pop(); + var sby = this.stack.pop(); + var sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(sbx, sby); + error = this.executeCommand(2, COMMAND_MAP.rmoveto); + break; + case (12 << 8) + 12: // div + if (this.stack.length < 2) { + error = true; + break; + } + var num2 = this.stack.pop(); + var num1 = this.stack.pop(); + this.stack.push(num1 / num2); + break; + case (12 << 8) + 16: // callothersubr + if (this.stack.length < 2) { + error = true; + break; + } + var subrNumber = this.stack.pop(); + var numArgs = this.stack.pop(); + if (subrNumber === 0 && numArgs === 3) { + var flexArgs = this.stack.splice(this.stack.length - 17, 17); + this.stack.push( + flexArgs[2] + flexArgs[0], // bcp1x + rpx + flexArgs[3] + flexArgs[1], // bcp1y + rpy + flexArgs[4], // bcp2x + flexArgs[5], // bcp2y + flexArgs[6], // p2x + flexArgs[7], // p2y + flexArgs[8], // bcp3x + flexArgs[9], // bcp3y + flexArgs[10], // bcp4x + flexArgs[11], // bcp4y + flexArgs[12], // p3x + flexArgs[13], // p3y + flexArgs[14] // flexDepth + // 15 = finalx unused by flex + // 16 = finaly unused by flex + ); + error = this.executeCommand(13, COMMAND_MAP.flex, true); + this.flexing = false; + this.stack.push(flexArgs[15], flexArgs[16]); + } else if (subrNumber === 1 && numArgs === 0) { + this.flexing = true; + } + break; + case (12 << 8) + 17: // pop + // Ignore this since it is only used with othersubr. + break; + case (12 << 8) + 33: // setcurrentpoint + // Ignore for now. + this.stack = []; + break; + default: + warn('Unknown type 1 charstring command of "' + value + '"'); + break; + } + if (error) { + break; + } + continue; + } else if (value <= 246) { + value = value - 139; + } else if (value <= 250) { + value = ((value - 247) * 256) + encoded[++i] + 108; + } else if (value <= 254) { + value = -((value - 251) * 256) - encoded[++i] - 108; + } else { + value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | + (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0; + } + this.stack.push(value); + } + return error; + }, + + executeCommand: function(howManyArgs, command, keepStack) { + var stackLength = this.stack.length; + if (howManyArgs > stackLength) { + return true; + } + var start = stackLength - howManyArgs; + for (var i = start; i < stackLength; i++) { + var value = this.stack[i]; + if (value === (value | 0)) { // int + this.output.push(28, (value >> 8) & 0xff, value & 0xff); + } else { // fixed point + value = (65536 * value) | 0; + this.output.push(255, + (value >> 24) & 0xFF, + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF); + } + } + this.output.push.apply(this.output, command); + if (keepStack) { + this.stack.splice(start, howManyArgs); + } else { + this.stack = []; + } + return false; + } + }; + + return Type1CharString; })(); /* @@ -19717,338 +20179,6 @@ var Type1Parser = function type1Parser() { return decryptedString.slice(discardNumber); } - /* - * CharStrings are encoded following the the CharString Encoding sequence - * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. - * The value in a byte indicates a command, a number, or subsequent bytes - * that are to be interpreted in a special way. - * - * CharString Number Encoding: - * A CharString byte containing the values from 32 through 255 inclusive - * indicate an integer. These values are decoded in four ranges. - * - * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, - * indicate the integer v - 139. Thus, the integer values from -107 through - * 107 inclusive may be encoded in single byte. - * - * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, - * indicates an integer involving the next byte, w, according to the formula: - * [(v - 247) x 256] + w + 108 - * - * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, - * indicates an integer involving the next byte, w, according to the formula: - * -[(v - 251) * 256] - w - 108 - * - * 4. A CharString containing the value 255 indicates that the next 4 bytes - * are a two complement signed integer. The first of these bytes contains the - * highest order bits, the second byte contains the next higher order bits - * and the fourth byte contain the lowest order bits. - * - * - * CharString Command Encoding: - * CharStrings commands are encoded in 1 or 2 bytes. - * - * Single byte commands are encoded in 1 byte that contains a value between - * 0 and 31 inclusive. - * If a command byte contains the value 12, then the value in the next byte - * indicates a command. This "escape" mechanism allows many extra commands - * to be encoded and this encoding technique helps to minimize the length of - * the charStrings. - */ - var charStringDictionary = { - '1': 'hstem', - '3': 'vstem', - '4': 'vmoveto', - '5': 'rlineto', - '6': 'hlineto', - '7': 'vlineto', - '8': 'rrcurveto', - - // closepath is a Type1 command that do not take argument and is useless - // in Type2 and it can simply be ignored. - '9': null, // closepath - - '10': 'callsubr', - - // return is normally used inside sub-routines to tells to the execution - // flow that it can be back to normal. - // During the translation process Type1 charstrings will be flattened and - // sub-routines will be embedded directly into the charstring directly, so - // this can be ignored safely. - '11': 'return', - - '12': { - // dotsection is a Type1 command to specify some hinting feature for dots - // that do not take a parameter and it can safely be ignored for Type2. - '0': null, // dotsection - - // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple - // parameters, so instead of returning [vh]stem3 take a shortcut and - // return [vhstem] instead. - '1': 'vstem', - '2': 'hstem', - - '6': 'endchar', // seac - // Type1 only command with command not (yet) built-in ,throw an error - '7': -1, // sbw - - '10': 'add', - '11': 'sub', - '12': 'div', - - // callothersubr is a mechanism to make calls on the postscript - // interpreter, this is not supported by Type2 charstring but hopefully - // most of the default commands can be ignored safely. - '16': 'callothersubr', - - '17': 'pop', - - // setcurrentpoint sets the current point to x, y without performing a - // moveto (this is a one shot positionning command). This is used only - // with the return of an OtherSubrs call. - // TODO Implement the OtherSubrs charstring embedding and replace this - // call by a no-op, like 2 'pop' commands for example. - '33': null // setcurrentpoint - }, - '13': 'hsbw', - '14': 'endchar', - '21': 'rmoveto', - '22': 'hmoveto', - '30': 'vhcurveto', - '31': 'hvcurveto' - }; - - var ESCAPE_CMD = 12; - - // Breaks up the stack by arguments and also calculates the value. - function breakUpArgs(stack, numArgs) { - var args = []; - var index = stack.length - 1; - for (var i = 0; i < numArgs; i++) { - if (index < 0) { - args.unshift({ arg: [0], - value: 0, - offset: 0 }); - warn('Malformed charstring stack: not enough values on stack.'); - continue; - } - var token = stack[index]; - if (token === 'div') { - var a = stack[index - 2]; - var b = stack[index - 1]; - if (!isInt(a) || !isInt(b)) { - warn('Malformed charsting stack: expected ints on stack for div.'); - a = 0; - b = 1; - } - args.unshift({ arg: [a, b, 'div'], - value: a / b, - offset: index - 2 }); - index -= 3; - } else if (isInt(token)) { - args.unshift({ arg: stack.slice(index, index + 1), - value: token, - offset: index }); - index--; - } else { - warn('Malformed charsting stack: found bad token ' + token + '.'); - } - } - return args; - } - - function decodeCharString(array) { - var charstring = []; - var lsb = 0; - var width = 0; - var flexing = false; - - var value = ''; - var count = array.length; - for (var i = 0; i < count; i++) { - value = array[i]; - - if (value < 32) { - var command = null; - if (value == ESCAPE_CMD) { - var escape = array[++i]; - - // TODO Clean this code - if (escape == 16) { - var index = charstring.pop(); - var argc = charstring.pop(); - for (var j = 0; j < argc; j++) - charstring.push('drop'); - - // If the flex mechanism is not used in a font program, Adobe - // states that entries 0, 1 and 2 can simply be replaced by - // {}, which means that we can simply ignore them. - if (index < 3) { - continue; - } - - // This is the same things about hint replacement, if it is not used - // entry 3 can be replaced by {3} - // TODO support hint replacment - if (index == 3) { - charstring.push(3); - i++; - continue; - } - - assert(argc == 0, 'callothersubr with arguments is not supported'); - charstring.push(new CallothersubrCmd(index)); - continue; - } else if (escape == 7) { // sbw - var args = breakUpArgs(charstring, 4); - var arg0 = args[0]; - var arg1 = args[1]; - var arg2 = args[2]; - lsb = arg0.value; - width = arg2.value; - // To convert to type2 we have to move the width value to the first - // part of the charstring and then use rmoveto with (dx, dy). - // The height argument will not be used for vmtx and vhea tables - // reconstruction -- ignoring it. - charstring = arg2.arg; - charstring = charstring.concat(arg0.arg, arg1.arg); - charstring.push('rmoveto'); - continue; - } else if (escape == 17 || escape == 33) { - // pop or setcurrentpoint commands can be ignored - // since we are not doing callothersubr - continue; - } else if (escape == 6) { - // seac is like type 2's special endchar but it doesn't use the - // first argument asb, so remove it. - var args = breakUpArgs(charstring, 5); - var arg0 = args[0]; - charstring.splice(arg0.offset, arg0.arg.length); - } else if (!HINTING_ENABLED && (escape == 1 || escape == 2)) { - charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop'); - continue; - } - - command = charStringDictionary['12'][escape]; - } else { - if (value == 13) { // hsbw - var args = breakUpArgs(charstring, 2); - var arg0 = args[0]; - var arg1 = args[1]; - lsb = arg0.value; - width = arg1.value; - // To convert to type2 we have to move the width value to the first - // part of the charstring and then use hmoveto with lsb. - charstring = arg1.arg; - charstring = charstring.concat(arg0.arg); - charstring.push('hmoveto'); - continue; - } else if (value == 10) { // callsubr - if (charstring[charstring.length - 1] < 3) { // subr #0..2 - // XXX: According to the spec if flex or hinting is not used then - // subroutines 0-3 can actually be anything defined by the font, - // so we really shouldn't be doing flex here but when - // callothersubr 0-2 is used. There hasn't been a real world - // example of this yet so we'll keep doing it here. - var subrNumber = charstring.pop(); - switch (subrNumber) { - case 1: - flexing = true; // prepare for flex coordinates - break; - case 0: - var flexArgs = breakUpArgs(charstring, 17); - - // removing all flex arguments from the stack - charstring.splice(flexArgs[0].offset, - charstring.length - flexArgs[0].offset); - - charstring = charstring.concat( - flexArgs[0].arg, // bcp1x + - flexArgs[2].arg, // rpx - ['add'], - flexArgs[1].arg, // bcp1y + - flexArgs[3].arg, // rpy - ['add'], - flexArgs[4].arg, // bcp2x - flexArgs[5].arg, // bcp2y - flexArgs[6].arg, // p2x - flexArgs[7].arg, // p2y - flexArgs[8].arg, // bcp3x - flexArgs[9].arg, // bcp3y - flexArgs[10].arg, // bcp4x - flexArgs[11].arg, // bcp4y - flexArgs[12].arg, // p3x - flexArgs[13].arg, // p3y - flexArgs[14].arg, // flexDepth - // 15 = finalx unused by flex - // 16 = finaly unused by flex - ['flex'] - ); - - flexing = false; - break; - } - continue; - } - } else if (value == 21 && flexing) { // rmoveto - continue; // ignoring rmoveto - } else if (value == 22 && flexing) { // hmoveto - // Add the dy for flex. - charstring.push(0); - continue; // ignoring hmoveto - } else if (value == 4 && flexing) { // vmoveto - // Insert the dx for flex before dy. - var flexArgs = breakUpArgs(charstring, 1); - charstring.splice(flexArgs[0].offset, 0, 0); - continue; // ignoring vmoveto - } else if (!HINTING_ENABLED && (value == 1 || value == 3)) { - charstring.push('drop', 'drop'); - continue; - } - command = charStringDictionary[value]; - } - - // Some charstring commands are meaningless in Type2 and will return - // a null, let's just ignored them - if (!command && i < count) { - continue; - } else if (!command) { - break; - } else if (command == -1) { - 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; - } else if (value <= 246) { - value = value - 139; - } else if (value <= 250) { - value = ((value - 247) * 256) + array[++i] + 108; - } else if (value <= 254) { - value = -((value - 251) * 256) - array[++i] - 108; - } else { - value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 | - (array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0; - } - - charstring.push(value); - } - - return { charstring: charstring, width: width, lsb: lsb }; - } - /* * Returns an object containing a Subrs array and a CharStrings * array extracted from and eexec encrypted block of data @@ -20116,6 +20246,7 @@ var Type1Parser = function type1Parser() { eexecStr += String.fromCharCode(eexec[i]); var glyphsSection = false, subrsSection = false; + var subrs = [], charstrings = []; var program = { subrs: [], charstrings: [], @@ -20151,17 +20282,14 @@ var Type1Parser = function type1Parser() { var data = eexec.slice(i, i + length); var lenIV = program.properties.privateData['lenIV']; var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV); - var str = decodeCharString(encoded); if (glyphsSection) { - program.charstrings.push({ + charstrings.push({ glyph: glyph, - data: str.charstring, - lsb: str.lsb, - width: str.width + encoded: encoded }); } else { - program.subrs.push(str.charstring); + subrs.push(encoded); } i += length; token = ''; @@ -20193,12 +20321,11 @@ var Type1Parser = function type1Parser() { var data = eexec.slice(i + 1, i + 1 + length); var lenIV = program.properties.privateData['lenIV']; var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV); - var str = decodeCharString(encoded); i = i + 1 + length; t = getToken(); // read in 'NP' if (t == 'noaccess') getToken(); // read in 'put' - program.subrs[index] = str.charstring; + subrs[index] = encoded; } break; case '/BlueValues': @@ -20208,7 +20335,7 @@ var Type1Parser = function type1Parser() { var blueArray = readNumberArray(eexecStr, i + 1); // *Blue* values may contain invalid data: disables reading of // those values when hinting is disabled. - if (blueArray.length > 0 && (blueArray.length % 2) == 0 && + if (blueArray.length > 0 && (blueArray.length % 2) === 0 && HINTING_ENABLED) { program.properties.privateData[token.substring(1)] = blueArray; } @@ -20245,6 +20372,26 @@ var Type1Parser = function type1Parser() { } } + for (var i = 0; i < charstrings.length; i++) { + var glyph = charstrings[i].glyph; + var encoded = charstrings[i].encoded; + var charString = new Type1CharString(); + var error = charString.convert(encoded, subrs); + var output = charString.output; + if (error) { + // It seems when FreeType encounters an error while evaluating a glyph + // that it completely ignores the glyph so we'll mimic that behaviour + // here and put an endchar to make the validator happy. + output = [14]; + } + program.charstrings.push({ + glyph: glyph, + data: output, + lsb: charString.lsb, + width: charString.width + }); + } + return program; }; @@ -20436,14 +20583,11 @@ Type1Font.prototype = { }, getType2Charstrings: function Type1Font_getType2Charstrings( - type1Subrs) { + type1Charstrings) { var type2Charstrings = []; - var count = type1Subrs.length; - var type1Charstrings = []; - for (var i = 0; i < count; i++) - type1Charstrings.push(type1Subrs[i].charstring.slice()); - for (var i = 0; i < count; i++) - type2Charstrings.push(this.flattenCharstring(type1Charstrings, i)); + for (var i = 0, ii = type1Charstrings.length; i < ii; i++) { + type2Charstrings.push(type1Charstrings[i].charstring); + } return type2Charstrings; }, @@ -20463,81 +20607,12 @@ Type1Font.prototype = { type2Subrs.push([0x0B]); for (var i = 0; i < count; i++) { - type2Subrs.push(this.flattenCharstring(type1Subrs, i)); + type2Subrs.push(type1Subrs[i]); } return type2Subrs; }, - /* - * Flatten the commands by interpreting the postscript code and replacing - * every 'callsubr', 'callothersubr' by the real commands. - */ - commandsMap: { - 'hstem': 1, - 'vstem': 3, - 'vmoveto': 4, - 'rlineto': 5, - 'hlineto': 6, - 'vlineto': 7, - 'rrcurveto': 8, - 'callsubr': 10, - 'return': 11, - 'add': [12, 10], - 'sub': [12, 11], - 'div': [12, 12], - 'exch': [12, 28], - 'flex': [12, 35], - 'drop' : [12, 18], - 'endchar': 14, - 'rmoveto': 21, - 'hmoveto': 22, - 'vhcurveto': 30, - 'hvcurveto': 31 - }, - - flattenCharstring: function Type1Font_flattenCharstring(charstrings, index) { - var charstring = charstrings[index]; - if (!charstring) - return [0x0B]; - var map = this.commandsMap; - // charstring changes size - can't cache .length in loop - for (var i = 0; i < charstring.length; i++) { - var command = charstring[i]; - if (typeof command === 'string') { - var cmd = map[command]; - assert(cmd, 'Unknow command: ' + command); - - if (isArray(cmd)) - charstring.splice(i++, 1, cmd[0], cmd[1]); - else - charstring[i] = cmd; - } else if (command instanceof CallothersubrCmd) { - var otherSubrCharstring = charstrings[command.index]; - if (otherSubrCharstring) { - var lastCommand = otherSubrCharstring.indexOf('return'); - if (lastCommand >= 0) - otherSubrCharstring = otherSubrCharstring.slice(0, lastCommand); - charstring.splice.apply(charstring, - [i, 1].concat(otherSubrCharstring)); - } else - charstring.splice(i, 1); // ignoring empty subr call - i--; - } else { - // Type1 charstring use a division for number above 32000 - if (command > 32000) { - var divisor = charstring[i + 1]; - command /= divisor; - charstring.splice(i, 3, 28, (command >> 8) & 0xff, command & 0xff); - } else { - charstring.splice(i, 1, 28, (command >> 8) & 0xff, command & 0xff); - } - i += 2; - } - } - return charstring; - }, - wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) { var cff = new CFF(); cff.header = new CFFHeader(1, 0, 4, 4); @@ -20545,11 +20620,13 @@ Type1Font.prototype = { cff.names = [name]; var topDict = new CFFTopDict(); - topDict.setByName('version', 0); - topDict.setByName('Notice', 1); - topDict.setByName('FullName', 2); - topDict.setByName('FamilyName', 3); - topDict.setByName('Weight', 4); + // CFF strings IDs 0...390 are predefined names, so refering + // to entries in our own String INDEX starts at SID 391. + topDict.setByName('version', 391); + topDict.setByName('Notice', 392); + topDict.setByName('FullName', 393); + topDict.setByName('FamilyName', 394); + topDict.setByName('Weight', 395); topDict.setByName('Encoding', null); // placeholder topDict.setByName('FontMatrix', properties.fontMatrix); topDict.setByName('FontBBox', properties.bbox); @@ -20632,7 +20709,7 @@ var CFFFont = (function CFFFontClosure() { this.properties = properties; var parser = new CFFParser(file, properties); - var cff = parser.parse(true); + var cff = parser.parse(); var compiler = new CFFCompiler(cff); this.readExtra(cff); try { @@ -20719,9 +20796,9 @@ var CFFFont = (function CFFFontClosure() { var CFFParser = (function CFFParserClosure() { var CharstringValidationData = [ null, - { id: 'hstem', min: 2, resetStack: true }, + { id: 'hstem', min: 2, resetStack: true, stem: true }, null, - { id: 'vstem', min: 2, resetStack: true }, + { id: 'vstem', min: 2, resetStack: true, stem: true }, { id: 'vmoveto', min: 1, resetStack: true }, { id: 'rlineto', min: 2, resetStack: true }, { id: 'hlineto', min: 1, resetStack: true }, @@ -20729,19 +20806,19 @@ var CFFParser = (function CFFParserClosure() { { id: 'rrcurveto', min: 6, resetStack: true }, null, { id: 'callsubr', min: 1, undefStack: true }, - { id: 'return', min: 0, resetStack: true }, + { id: 'return', min: 0, undefStack: true }, null, // 12 null, null, // endchar null, null, null, - { id: 'hstemhm', min: 2, resetStack: true }, + { id: 'hstemhm', min: 2, resetStack: true, stem: true }, null, // hintmask null, // cntrmask { id: 'rmoveto', min: 2, resetStack: true }, { id: 'hmoveto', min: 1, resetStack: true }, - { id: 'vstemhm', min: 2, resetStack: true }, + { id: 'vstemhm', min: 2, resetStack: true, stem: true }, { id: 'rcurveline', min: 8, resetStack: true }, { id: 'rlinecurve', min: 8, resetStack: true }, { id: 'vvcurveto', min: 4, resetStack: true }, @@ -20757,7 +20834,7 @@ var CFFParser = (function CFFParserClosure() { null, { id: 'and', min: 2, stackDelta: -1 }, { id: 'or', min: 2, stackDelta: -1 }, - { id: 'not', min: 2, stackDelta: -1 }, + { id: 'not', min: 1, stackDelta: 0 }, null, null, null, @@ -20797,7 +20874,7 @@ var CFFParser = (function CFFParserClosure() { this.properties = properties; } CFFParser.prototype = { - parse: function CFFParser_parse(normalizeCIDData) { + parse: function CFFParser_parse() { var properties = this.properties; var cff = new CFF(); this.cff = cff; @@ -20865,30 +20942,6 @@ var CFFParser = (function CFFParserClosure() { cff.charset = charset; cff.encoding = encoding; - if (!cff.isCIDFont || !normalizeCIDData) - return cff; - - // DirectWrite does not like CID fonts data. Trying to convert/flatten - // the font data and remove CID properties. - if (cff.fdArray.length !== 1) { - warn('Unable to normalize CID font in CFF data -- using font as is'); - return cff; - } - - var fontDict = cff.fdArray[0]; - // Make the sanitizer happy and remove anything that is only for CID - // fonts. - fontDict.setByKey(17, topDict.getByName('CharStrings')); - fontDict.removeByName('CIDFontVersion'); - fontDict.removeByName('CIDFontRevision'); - fontDict.removeByName('CIDFontType'); - fontDict.removeByName('CIDCount'); - fontDict.removeByName('UIDBase'); - cff.topDict = fontDict; - cff.isCIDFont = false; - delete cff.fdArray; - delete cff.fdSelect; - return cff; }, parseHeader: function CFFParser_parseHeader() { @@ -20898,7 +20951,7 @@ var CFFParser = (function CFFParserClosure() { while (bytes[offset] != 1) ++offset; - if (offset != 0) { + if (offset !== 0) { info('cff data is shifted'); bytes = bytes.subarray(offset); this.bytes = bytes; @@ -20988,7 +21041,7 @@ var CFFParser = (function CFFParserClosure() { var start = pos; var end = pos; - if (count != 0) { + if (count !== 0) { var offsetSize = bytes[pos++]; // add 1 for offset to determine size of last object var startPos = pos + ((count + 1) * offsetSize) - 1; @@ -21045,8 +21098,8 @@ var CFFParser = (function CFFParserClosure() { } return strings; }, - createDict: function CFFParser_createDict(type, dict, strings) { - var cffDict = new type(strings); + createDict: function CFFParser_createDict(Type, dict, strings) { + var cffDict = new Type(strings); var types = cffDict.types; for (var i = 0, ii = dict.length; i < ii; ++i) { @@ -21074,7 +21127,7 @@ var CFFParser = (function CFFParserClosure() { var validationCommand = null; if (value == 12) { var q = data[j++]; - if (q == 0) { + if (q === 0) { // The CFF specification state that the 'dotsection' command // (12, 0) is deprecated and treated as a no-op, but all Type2 // charstrings processors should support them. Unfortunately @@ -21101,9 +21154,6 @@ var CFFParser = (function CFFParserClosure() { } else if (value == 255) { // number (32 bit) j += 4; stackSize++; - } else if (value == 18 || value == 23) { - hints += stackSize >> 1; - validationCommand = CharstringValidationData[value]; } else if (value == 19 || value == 20) { hints += stackSize >> 1; j += (hints + 7) >> 3; // skipping right amount of hints flag data @@ -21112,6 +21162,9 @@ var CFFParser = (function CFFParserClosure() { validationCommand = CharstringValidationData[value]; } if (validationCommand) { + if (validationCommand.stem) { + hints += stackSize >> 1; + } if ('min' in validationCommand) { if (!undefStack && stackSize < validationCommand.min) { warn('Not enough parameters for ' + validationCommand.id + @@ -21178,7 +21231,7 @@ var CFFParser = (function CFFParserClosure() { privateDict.subrsIndex = subrsIndex.obj; }, parseCharsets: function CFFParser_parseCharsets(pos, length, strings, cid) { - if (pos == 0) { + if (pos === 0) { return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset); } else if (pos == 1) { @@ -21249,7 +21302,7 @@ var CFFParser = (function CFFParserClosure() { } } - if (pos == 0 || pos == 1) { + if (pos === 0 || pos == 1) { predefined = true; format = pos; var baseEncoding = pos ? Encodings.ExpertEncoding : @@ -21502,7 +21555,7 @@ var CFFTopDict = (function CFFTopDictClosure() { [[12, 5], 'PaintType', 'num', 0], [[12, 6], 'CharstringType', 'num', 2], [[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num', 'num', 'num'], - [.001, 0, 0, .001, 0, 0]], + [0.001, 0, 0, 0.001, 0, 0]], [13, 'UniqueID', 'num', null], [5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]], [[12, 8], 'StrokeWidth', 'num', 0], @@ -21519,9 +21572,12 @@ var CFFTopDict = (function CFFTopDictClosure() { [[12, 33], 'CIDFontType', 'num', 0], [[12, 34], 'CIDCount', 'num', 8720], [[12, 35], 'UIDBase', 'num', null], - [[12, 36], 'FDArray', 'offset', null], + // XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes + // before FDArray. [[12, 37], 'FDSelect', 'offset', null], - [[12, 38], 'FontName', 'sid', null]]; + [[12, 36], 'FDArray', 'offset', null], + [[12, 38], 'FontName', 'sid', null] + ]; var tables = null; function CFFTopDict(strings) { if (tables === null) @@ -21669,7 +21725,7 @@ var CFFCompiler = (function CFFCompilerClosure() { array[i] = str.charCodeAt(i); return array; - }; + } function CFFCompiler(cff) { this.cff = cff; } @@ -21692,7 +21748,9 @@ var CFFCompiler = (function CFFCompilerClosure() { var nameIndex = this.compileNameIndex(cff.names); output.add(nameIndex); - var compiled = this.compileTopDicts([cff.topDict], output.length); + var compiled = this.compileTopDicts([cff.topDict], + output.length, + cff.isCIDFont); output.add(compiled.output); var topDictTracker = compiled.trackers[0]; @@ -21735,8 +21793,9 @@ var CFFCompiler = (function CFFCompilerClosure() { topDictTracker.setEntryLocation('FDSelect', [output.length], output); var fdSelect = this.compileFDSelect(cff.fdSelect.raw); output.add(fdSelect); - - var compiled = this.compileTopDicts(cff.fdArray, output.length); + // It is unclear if the sub font dictionary can have CID related + // dictionary keys, but the sanitizer doesn't like them so remove them. + var compiled = this.compileTopDicts(cff.fdArray, output.length, true); topDictTracker.setEntryLocation('FDArray', [output.length], output); output.add(compiled.output); var fontDictTrackers = compiled.trackers; @@ -21749,7 +21808,7 @@ var CFFCompiler = (function CFFCompilerClosure() { return output.data; }, encodeNumber: function CFFCompiler_encodeNumber(value) { - if (parseFloat(value) == parseInt(value) && !isNaN(value)) // isInt + if (parseFloat(value) == parseInt(value, 10) && !isNaN(value)) // isInt return this.encodeInteger(value); else return this.encodeFloat(value); @@ -21811,11 +21870,20 @@ var CFFCompiler = (function CFFCompilerClosure() { nameIndex.add(stringToArray(names[i])); return this.compileIndex(nameIndex); }, - compileTopDicts: function CFFCompiler_compileTopDicts(dicts, length) { + compileTopDicts: function CFFCompiler_compileTopDicts(dicts, + length, + removeCidKeys) { var fontDictTrackers = []; var fdArrayIndex = new CFFIndex(); for (var i = 0, ii = dicts.length; i < ii; ++i) { var fontDict = dicts[i]; + if (removeCidKeys) { + fontDict.removeByName('CIDFontVersion'); + fontDict.removeByName('CIDFontRevision'); + fontDict.removeByName('CIDFontType'); + fontDict.removeByName('CIDCount'); + fontDict.removeByName('UIDBase'); + } var fontDictTracker = new CFFOffsetTracker(); var fontDictData = this.compileDict(fontDict, fontDictTracker); fontDictTrackers.push(fontDictTracker); @@ -21940,7 +22008,7 @@ var CFFCompiler = (function CFFCompilerClosure() { // If there is no object, just create an index. This technically // should just be [0, 0] but OTS has an issue with that. - if (count == 0) + if (count === 0) return [0, 0, 0]; var data = [(count >> 8) & 0xFF, count & 0xff]; @@ -26461,7 +26529,7 @@ var PDFImage = (function PDFImageClosure() { var buf = 0; for (var i = 0, ii = length; i < ii; ++i) { - if (i % rowComps == 0) { + if (i % rowComps === 0) { mask = 0; buf = 0; } else { @@ -26479,7 +26547,7 @@ var PDFImage = (function PDFImageClosure() { // The general case that handles all other bpc values. var bits = 0, buf = 0; for (var i = 0, ii = length; i < ii; ++i) { - if (i % rowComps == 0) { + if (i % rowComps === 0) { buf = 0; bits = 0; } @@ -26575,7 +26643,7 @@ var PDFImage = (function PDFImageClosure() { buf = imgArray[imgArrayPos++]; mask = 128; } - if (!(buf & mask) == inverseDecode) { + if (!(buf & mask) === inverseDecode) { buffer[bufferPos] = 0; } bufferPos += 4; @@ -29724,7 +29792,8 @@ var Parser = (function ParserClosure() { // searching for the /EI\s/ var state = 0, ch; - while (state != 4 && (ch = stream.getByte()) != null) { + while (state != 4 && + (ch = stream.getByte()) !== null && ch !== undefined) { switch (ch) { case 0x20: case 0x0D: @@ -29955,7 +30024,7 @@ var Lexer = (function LexerClosure() { str += ch; break; case ')': - if (--numParen == 0) { + if (--numParen === 0) { done = true; } else { str += ch; @@ -30067,13 +30136,13 @@ var Lexer = (function LexerClosure() { if (isFirstHex) { firstDigit = toHexDigit(ch); if (firstDigit === -1) { - warn("Ignoring invalid character '" + ch + "' in hex string"); + warn('Ignoring invalid character "' + ch + '" in hex string'); continue; } } else { secondDigit = toHexDigit(ch); if (secondDigit === -1) { - warn("Ignoring invalid character '" + ch + "' in hex string"); + warn('Ignoring invalid character "' + ch + '" in hex string'); continue; } str += String.fromCharCode((firstDigit << 4) | secondDigit); @@ -30131,6 +30200,7 @@ var Lexer = (function LexerClosure() { stream.skip(); return Cmd.get('>>'); } + return Cmd.get(ch); case '{': case '}': return Cmd.get(ch); @@ -30854,7 +30924,7 @@ var StreamsSequenceStream = (function StreamsSequenceStreamClosure() { function streamSequenceStreamReadBlock() { var streams = this.streams; - if (streams.length == 0) { + if (streams.length === 0) { this.eof = true; return; } @@ -30974,7 +31044,7 @@ var FlateStream = (function FlateStreamClosure() { error('Invalid header in flate stream: ' + cmf + ', ' + flg); if ((cmf & 0x0f) != 0x08) error('Unknown compression method in flate stream: ' + cmf + ', ' + flg); - if ((((cmf << 8) + flg) % 31) != 0) + if ((((cmf << 8) + flg) % 31) !== 0) error('Bad FCHECK in flate stream: ' + cmf + ', ' + flg); if (flg & 0x20) error('FDICT bit set in flate stream: ' + cmf + ', ' + flg); @@ -31028,7 +31098,7 @@ var FlateStream = (function FlateStreamClosure() { var code = codes[codeBuf & ((1 << maxLen) - 1)]; var codeLen = code >> 16; var codeVal = code & 0xffff; - if (codeSize == 0 || codeSize < codeLen || codeLen == 0) + if (codeSize === 0 || codeSize < codeLen || codeLen === 0) error('Bad encoding in flate stream'); this.codeBuf = (codeBuf >> codeLen); this.codeSize = (codeSize - codeLen); @@ -31082,7 +31152,7 @@ var FlateStream = (function FlateStreamClosure() { this.eof = true; hdr >>= 1; - if (hdr == 0) { // uncompressed block + if (hdr === 0) { // uncompressed block var bytes = this.bytes; var bytesPos = this.bytesPos; var b; @@ -31315,7 +31385,7 @@ var PredictorStream = (function PredictorStreamClosure() { var buffer = this.ensureBuffer(bufferLength + rowBytes); var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength); - if (prevRow.length == 0) + if (prevRow.length === 0) prevRow = new Uint8Array(rowBytes); var j = bufferLength; @@ -31400,10 +31470,10 @@ var JpegStream = (function JpegStreamClosure() { // Looking for APP14, 'Adobe' for (var i = 0; i < maxBytesScanned; ++i) { if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE && - bytes[i + 2] == 0x00 && bytes[i + 3] == 0x0E && + bytes[i + 2] === 0x00 && bytes[i + 3] == 0x0E && bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 && bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 && - bytes[i + 8] == 0x65 && bytes[i + 9] == 0x00) + bytes[i + 8] == 0x65 && bytes[i + 9] === 0x00) return true; // scanning until frame tag if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0) @@ -31660,7 +31730,7 @@ var DecryptStream = (function DecryptStreamClosure() { DecryptStream.prototype.readBlock = function DecryptStream_readBlock() { var chunk = this.str.getBytes(chunkSize); - if (!chunk || chunk.length == 0) { + if (!chunk || chunk.length === 0) { this.eof = true; return; } @@ -32302,7 +32372,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.columns = params.get('Columns') || 1728; this.rows = params.get('Rows') || 0; var eoblock = params.get('EndOfBlock'); - if (eoblock == null) + if (eoblock === null || eoblock === undefined) eoblock = true; this.eoblock = eoblock; this.black = params.get('BlackIs1') || false; @@ -32321,7 +32391,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.buf = EOF; var code1; - while ((code1 = this.lookBits(12)) == 0) { + while ((code1 = this.lookBits(12)) === 0) { this.eatBits(1); } if (code1 == 1) { @@ -32405,7 +32475,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { var refPos, blackPixels, bits; - if (this.outputBits == 0) { + if (this.outputBits === 0) { if (this.eof) return null; @@ -32579,7 +32649,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { this.eof = true; } else { code1 = this.lookBits(12); - while (code1 == 0) { + while (code1 === 0) { this.eatBits(1); code1 = this.lookBits(12); } @@ -32647,7 +32717,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (this.outputBits >= 8) { this.buf = (this.codingPos & 1) ? 0 : 0xFF; this.outputBits -= 8; - if (this.outputBits == 0 && codingLine[this.codingPos] < columns) { + if (this.outputBits === 0 && codingLine[this.codingPos] < columns) { this.codingPos++; this.outputBits = (codingLine[this.codingPos] - codingLine[this.codingPos - 1]); @@ -32746,7 +32816,7 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { if (code == EOF) return 1; - if ((code >> 5) == 0) + if ((code >> 5) === 0) p = whiteTable1[code]; else p = whiteTable2[code >> 3]; @@ -32777,9 +32847,9 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { code = this.lookBits(13); if (code == EOF) return 1; - if ((code >> 7) == 0) + if ((code >> 7) === 0) p = blackTable1[code]; - else if ((code >> 9) == 0 && (code >> 7) != 0) + else if ((code >> 9) === 0 && (code >> 7) !== 0) p = blackTable2[(code >> 1) - 64]; else p = blackTable3[code >> 7]; @@ -32809,8 +32879,8 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() { CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) { var c; while (this.inputBits < n) { - if ((c = this.str.getByte()) == null) { - if (this.inputBits == 0) + if ((c = this.str.getByte()) === null || c === undefined) { + if (this.inputBits === 0) return EOF; return ((this.inputBuf << (n - this.inputBits)) & (0xFFFF >> (16 - n))); @@ -32863,7 +32933,7 @@ var LZWStream = (function LZWStreamClosure() { var cachedData = this.cachedData; while (bitsCached < n) { var c = this.str.getByte(); - if (c == null) { + if (c === null || c === undefined) { this.eof = true; return null; } @@ -33082,6 +33152,12 @@ var WorkerMessageHandler = { exception: e }); + return; + } else if (e instanceof MissingPDFException) { + handler.send('MissingPDF', { + exception: e + }); + return; } else { handler.send('UnknownError', { @@ -33137,8 +33213,15 @@ var WorkerMessageHandler = { }); }, error: function getPDFError(e) { - handler.send('DocError', 'Unexpected server response of ' + - e.target.status + '.'); + if (e.target.status == 404) { + handler.send('MissingPDF', { + exception: new MissingPDFException( + 'Missing PDF \"' + source.url + '\".')}); + } else { + handler.send('DocError', 'Unexpected server response (' + + e.target.status + ') while retrieving PDF \"' + + source.url + '\".'); + } }, headers: source.httpHeaders }, @@ -33150,11 +33233,13 @@ var WorkerMessageHandler = { handler.on('GetPageRequest', function wphSetupGetPage(data) { var pageNumber = data.pageIndex + 1; var pdfPage = pdfModel.getPage(pageNumber); + var encrypt = pdfModel.xref.encrypt; var page = { pageIndex: data.pageIndex, rotate: pdfPage.rotate, ref: pdfPage.ref, - view: pdfPage.view + view: pdfPage.view, + disableTextLayer: encrypt ? encrypt.disableTextLayer : false }; handler.send('GetPage', {pageInfo: page}); }); @@ -33191,19 +33276,21 @@ var WorkerMessageHandler = { var minimumStackMessage = 'worker.js: while trying to getPage() and getOperatorList()'; + var wrappedException; + // Turn the error into an obj that can be serialized if (typeof e === 'string') { - e = { + wrappedException = { message: e, stack: minimumStackMessage }; } else if (typeof e === 'object') { - e = { + wrappedException = { message: e.message || e.toString(), stack: e.stack || minimumStackMessage }; } else { - e = { + wrappedException = { message: 'Unknown exception type: ' + (typeof e), stack: minimumStackMessage }; @@ -33211,7 +33298,7 @@ var WorkerMessageHandler = { handler.send('PageError', { pageNum: pageNum, - error: e + error: wrappedException }); return; } @@ -33223,7 +33310,7 @@ var WorkerMessageHandler = { var fonts = {}; for (var i = 0, ii = dependency.length; i < ii; i++) { var dep = dependency[i]; - if (dep.indexOf('g_font_') == 0) { + if (dep.indexOf('g_font_') === 0) { fonts[dep] = true; } } @@ -33280,7 +33367,7 @@ var workerConsole = { timeEnd: function timeEnd(name) { var time = consoleTimer[name]; - if (time == null) { + if (!time) { error('Unkown timer name ' + name); } this.log('Timer:', name, Date.now() - time); @@ -33333,7 +33420,7 @@ var JpxImage = (function JpxImageClosure() { xhr.send(null); }, parse: function JpxImage_parse(data) { - function ReadUint(data, offset, bytes) { + function readUint(data, offset, bytes) { var n = 0; for (var i = 0; i < bytes; i++) n = n * 256 + (data[offset + i] & 0xFF); @@ -33342,15 +33429,15 @@ var JpxImage = (function JpxImageClosure() { var position = 0, length = data.length; while (position < length) { var headerSize = 8; - var lbox = ReadUint(data, position, 4); - var tbox = ReadUint(data, position + 4, 4); + var lbox = readUint(data, position, 4); + var tbox = readUint(data, position + 4, 4); position += headerSize; if (lbox == 1) { - lbox = ReadUint(data, position, 8); + lbox = readUint(data, position, 8); position += 8; headerSize += 8; } - if (lbox == 0) + if (lbox === 0) lbox = length - position + headerSize; if (lbox < headerSize) error('JPX error: Invalid box field size'); @@ -33579,7 +33666,7 @@ var JpxImage = (function JpxImageClosure() { tile.partsCount = data[position + 9]; context.mainHeader = false; - if (tile.partIndex == 0) { + if (tile.partIndex === 0) { // reset component specific settings tile.COD = context.COD; tile.COC = context.COC.slice(0); // clone of the global COC @@ -33590,7 +33677,7 @@ var JpxImage = (function JpxImageClosure() { break; case 0xFF93: // Start of data (SOD) var tile = context.currentTile; - if (tile.partIndex == 0) { + if (tile.partIndex === 0) { initializeTile(context, tile.index); buildPackets(context); } @@ -33921,7 +34008,7 @@ var JpxImage = (function JpxImageClosure() { resolutions.push(resolution); var subband; - if (r == 0) { + if (r === 0) { // one sub-band (LL) with last decomposition subband = {}; subband.type = 'LL'; @@ -34023,7 +34110,7 @@ var JpxImage = (function JpxImageClosure() { } function readCodingpasses() { var value = readBits(1); - if (value == 0) + if (value === 0) return 1; value = (value << 1) | readBits(1); if (value == 0x02) @@ -34143,7 +34230,7 @@ var JpxImage = (function JpxImageClosure() { var codeblock = codeblocks[i]; var blockWidth = codeblock.tbx1_ - codeblock.tbx0_; var blockHeight = codeblock.tby1_ - codeblock.tby0_; - if (blockWidth == 0 || blockHeight == 0) + if (blockWidth === 0 || blockHeight === 0) continue; if (!('data' in codeblock)) continue; @@ -34195,7 +34282,7 @@ var JpxImage = (function JpxImageClosure() { var n = (bitModel.coefficentsSign[position] ? -1 : 1) * bitModel.coefficentsMagnitude[position]; var nb = bitModel.bitsDecoded[position], correction; - if (transformation == 0 || mb > nb) { + if (transformation === 0 || mb > nb) { // use r only if transformation is irreversible or // not all bitplanes were decoded for reversible transformation n += n < 0 ? n - r : n > 0 ? n + r : 0; @@ -34264,7 +34351,7 @@ var JpxImage = (function JpxImageClosure() { } var transformation = codingStyleParameters.transformation; - var transform = transformation == 0 ? new IrreversibleTransform() : + var transform = transformation === 0 ? new IrreversibleTransform() : new ReversibleTransform(); var result = transform.calculate(subbandCoefficients, component.tcx0, component.tcy0); @@ -34594,7 +34681,7 @@ var JpxImage = (function JpxImageClosure() { return d; } else { this.chigh -= qeIcx; - if ((this.a & 0x8000) == 0) { + if ((this.a & 0x8000) === 0) { var d = this.exchangeMps(cx); this.renormD(); return d; @@ -34605,14 +34692,14 @@ var JpxImage = (function JpxImageClosure() { }, renormD: function ArithmeticDecoder_renormD() { do { - if (this.ct == 0) + if (this.ct === 0) this.byteIn(); this.a <<= 1; this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); this.clow = (this.clow << 1) & 0xFFFF; this.ct--; - } while ((this.a & 0x8000) == 0); + } while ((this.a & 0x8000) === 0); }, exchangeMps: function ArithmeticDecoder_exchangeMps(cx) { var d; @@ -34848,12 +34935,12 @@ var JpxImage = (function JpxImageClosure() { // significant but not those that have just become if (!coefficentsMagnitude[index] || - (processingFlags[index] & processedMask) != 0) + (processingFlags[index] & processedMask) !== 0) continue; var contextLabel = 16; if ((processingFlags[index] & - firstMagnitudeBitMask) != 0) { + firstMagnitudeBitMask) !== 0) { processingFlags[i * width + j] ^= firstMagnitudeBitMask; // first refinement var significance = neighborsSignificance[index]; @@ -34894,14 +34981,14 @@ var JpxImage = (function JpxImageClosure() { // using the property: labels[neighborsSignificance[index]] == 0 // when neighborsSignificance[index] == 0 var allEmpty = i0 + 3 < height && - processingFlags[index0] == 0 && - processingFlags[index0 + oneRowDown] == 0 && - processingFlags[index0 + twoRowsDown] == 0 && - processingFlags[index0 + threeRowsDown] == 0 && - neighborsSignificance[index0] == 0 && - neighborsSignificance[index0 + oneRowDown] == 0 && - neighborsSignificance[index0 + twoRowsDown] == 0 && - neighborsSignificance[index0 + threeRowsDown] == 0; + processingFlags[index0] === 0 && + processingFlags[index0 + oneRowDown] === 0 && + processingFlags[index0 + twoRowsDown] === 0 && + processingFlags[index0 + threeRowsDown] === 0 && + neighborsSignificance[index0] === 0 && + neighborsSignificance[index0 + oneRowDown] === 0 && + neighborsSignificance[index0 + twoRowsDown] === 0 && + neighborsSignificance[index0 + threeRowsDown] === 0; var i1 = 0, index = index0; var cx, i; if (allEmpty) { @@ -34937,7 +35024,7 @@ var JpxImage = (function JpxImageClosure() { break; if (coefficentsMagnitude[index] || - (processingFlags[index] & processedMask) != 0) + (processingFlags[index] & processedMask) !== 0) continue; var contextLabel = labels[neighborsSignificance[index]]; @@ -35023,7 +35110,7 @@ var JpxImage = (function JpxImageClosure() { for (var v = 0; v < height; v++) { if (width == 1) { // if width = 1, when u0 even keep items as is, when odd divide by 2 - if ((u0 % 1) != 0) { + if ((u0 % 1) !== 0) { items[v * width] /= 2; } continue; @@ -35058,7 +35145,7 @@ var JpxImage = (function JpxImageClosure() { for (var u = 0; u < width; u++) { if (height == 1) { // if height = 1, when v0 even keep items as is, when odd divide by 2 - if ((v0 % 1) != 0) { + if ((v0 % 1) !== 0) { items[u] /= 2; } continue; @@ -35285,7 +35372,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { return d; } else { this.chigh -= qeIcx; - if ((this.a & 0x8000) == 0) { + if ((this.a & 0x8000) === 0) { var d = this.exchangeMps(cx); this.renormD(); return d; @@ -35296,14 +35383,14 @@ var Jbig2Image = (function Jbig2ImageClosure() { }, renormD: function ArithmeticDecoder_renormD() { do { - if (this.ct == 0) + if (this.ct === 0) this.byteIn(); this.a <<= 1; this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1); this.clow = (this.clow << 1) & 0xFFFF; this.ct--; - } while ((this.a & 0x8000) == 0); + } while ((this.a & 0x8000) === 0); }, exchangeMps: function ArithmeticDecoder_exchangeMps(cx) { var d; @@ -35423,7 +35510,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { break; default: v = v * 2 + bit; - if (--toRead == 0) + if (--toRead === 0) state = 0; continue; } @@ -35599,7 +35686,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { offsetX, offsetY, prediction, at, decodingContext) { var codingTemplate = RefinementTemplates[templateIndex].coding; - if (templateIndex == 0) + if (templateIndex === 0) codingTemplate = codingTemplate.concat([at[0]]); var codingTemplateLength = codingTemplate.length; var codingTemplateX = new Int32Array(codingTemplateLength); @@ -35609,7 +35696,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { codingTemplateY[k] = codingTemplate[k].y; } var referenceTemplate = RefinementTemplates[templateIndex].reference; - if (templateIndex == 0) + if (templateIndex === 0) referenceTemplate = referenceTemplate.concat([at[1]]); var referenceTemplateLength = referenceTemplate.length; var referenceTemplateX = new Int32Array(referenceTemplateLength); @@ -35692,7 +35779,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { var totalWidth = 0; while (true) { var deltaWidth = decodeInteger(contextCache, 'IADW', decoder); // 6.5.7 - if (deltaWidth == null) + if (deltaWidth === null) break; // OOB currentWidth += deltaWidth; totalWidth += currentWidth; @@ -35819,7 +35906,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { i++; var deltaS = decodeInteger(contextCache, 'IADS', decoder); // 6.4.8 - if (deltaS == null) + if (deltaS === null) break; // OOB currentS += deltaS + dsOffset; } while (true); @@ -35941,7 +36028,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; position += 2; if (!dictionary.huffman) { - var atLength = dictionary.template == 0 ? 4 : 1; + var atLength = dictionary.template === 0 ? 4 : 1; var at = []; for (var i = 0; i < atLength; i++) { at.push({ @@ -36027,7 +36114,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; genericRegion.prediction = !!(genericRegionSegmentFlags & 8); if (!genericRegion.mmr) { - var atLength = genericRegion.template == 0 ? 4 : 1; + var atLength = genericRegion.template === 0 ? 4 : 1; var at = []; for (var i = 0; i < atLength; i++) { at.push({ @@ -36290,11 +36377,11 @@ var bidi = PDFJS.bidi = (function bidiClosure() { ]; function isOdd(i) { - return (i & 1) != 0; + return (i & 1) !== 0; } function isEven(i) { - return (i & 1) == 0; + return (i & 1) === 0; } function findUnequal(arr, start, value) { @@ -36368,8 +36455,8 @@ var bidi = PDFJS.bidi = (function bidiClosure() { function bidi(str, startLevel) { var isLTR = true; var strLength = str.length; - if (strLength == 0) - return new BidiResult(str, ltr); + if (strLength === 0) + return new BidiResult(str, isLTR); // get types, fill arrays @@ -36402,7 +36489,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // if there are no rtl characters then no bidi needed // if less than 30% chars are rtl then string is primarily ltr // if more than 30% chars are rtl then string is primarily rtl - if (numBidi == 0) { + if (numBidi === 0) { isLTR = true; return new BidiResult(str, isLTR); } diff --git a/browser/extensions/pdfjs/content/web/l10n.js b/browser/extensions/pdfjs/content/web/l10n.js index df20ff57718..1258f077731 100644 --- a/browser/extensions/pdfjs/content/web/l10n.js +++ b/browser/extensions/pdfjs/content/web/l10n.js @@ -94,20 +94,15 @@ // get a localized string get: translateString, - // get|set the document language and direction - get language() { - return { - // get|set the document language (ISO-639-1) - get code() { return gLanguage; }, + // get the document language + getLanguage: function() { return gLanguage; }, - // get the direction (ltr|rtl) of the current language - get direction() { - // http://www.w3.org/International/questions/qa-scripts - // Arabic, Hebrew, Farsi, Pashto, Urdu - var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; - return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; - } - }; + // get the direction (ltr|rtl) of the current language + getDirection: function() { + // http://www.w3.org/International/questions/qa-scripts + // Arabic, Hebrew, Farsi, Pashto, Urdu + var rtlList = ['ar', 'he', 'fa', 'ps', 'ur']; + return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr'; } }; })(this); diff --git a/browser/extensions/pdfjs/content/web/viewer.css b/browser/extensions/pdfjs/content/web/viewer.css index c735810625c..0a034e2a7f6 100644 --- a/browser/extensions/pdfjs/content/web/viewer.css +++ b/browser/extensions/pdfjs/content/web/viewer.css @@ -665,7 +665,6 @@ html[dir='rtl'] .dropdownToolbarButton { } .dropdownToolbarButton { - min-width: 120px; max-width: 120px; padding: 3px 2px 2px; overflow: hidden; diff --git a/browser/extensions/pdfjs/content/web/viewer.html b/browser/extensions/pdfjs/content/web/viewer.html index 08b59e19cb3..ab5dfbb40b3 100644 --- a/browser/extensions/pdfjs/content/web/viewer.html +++ b/browser/extensions/pdfjs/content/web/viewer.html @@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> - + @@ -81,8 +81,8 @@ limitations under the License.
-