From 6a75e453093a750d65ab867ce304e2e504ab35e1 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 11 Mar 2014 21:09:49 -0700 Subject: [PATCH 1/4] Allocate fewer objects when parsing 2 and 4 byte chunks. This is achieved by adding getBytes2() and getBytes4() to streams, and by changing int16() and int32() to take multiple scalar args instead of an array arg. --- src/core/chunked_stream.js | 14 ++++ src/core/fonts.js | 127 ++++++++++++++++++------------------- src/core/stream.js | 24 +++++++ 3 files changed, 101 insertions(+), 64 deletions(-) diff --git a/src/core/chunked_stream.js b/src/core/chunked_stream.js index 98e8d7216..461365da2 100644 --- a/src/core/chunked_stream.js +++ b/src/core/chunked_stream.js @@ -140,6 +140,20 @@ var ChunkedStream = (function ChunkedStreamClosure() { return this.bytes[this.pos++]; }, + getUint16: function ChunkedStream_getUint16() { + var b0 = this.getByte(); + var b1 = this.getByte(); + return (b0 << 8) + b1; + }, + + getUint32: function ChunkedStream_getUint32() { + var b0 = this.getByte(); + var b1 = this.getByte(); + var b2 = this.getByte(); + var b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + }, + // returns subarray of original buffer // should only be read getBytes: function ChunkedStream_getBytes(length) { diff --git a/src/core/fonts.js b/src/core/fonts.js index ba4bba527..370cf58ce 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -2312,13 +2312,12 @@ var Font = (function FontClosure() { return strBuf.join(''); } - function int16(bytes) { - return (bytes[0] << 8) + (bytes[1] & 0xff); + function int16(b0, b1) { + return (b0 << 8) + b1; } - function int32(bytes) { - return (bytes[0] << 24) + (bytes[1] << 16) + - (bytes[2] << 8) + (bytes[3] & 0xff); + function int32(b0, b1, b2, b3) { + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; } function getMaxPower2(number) { @@ -2397,8 +2396,8 @@ var Font = (function FontClosure() { // checksum var checksum = 0, n = data.length; for (var i = 0; i < n; i += 4) - checksum = (checksum + int32([data[i], data[i + 1], data[i + 2], - data[i + 3]])) | 0; + checksum = (checksum + int32(data[i], data[i + 1], data[i + 2], + data[i + 3])) | 0; var tableEntry = (tag + string32(checksum) + string32(offset) + string32(length)); @@ -2626,21 +2625,21 @@ var Font = (function FontClosure() { function validateOS2Table(os2) { var stream = new Stream(os2.data); - var version = int16(stream.getBytes(2)); + var version = stream.getUint16(); // TODO verify all OS/2 tables fields, but currently we validate only those // that give us issues stream.getBytes(60); // skipping type, misc sizes, panose, unicode ranges - var selection = int16(stream.getBytes(2)); + var selection = stream.getUint16(); if (version < 4 && (selection & 0x0300)) { return false; } - var firstChar = int16(stream.getBytes(2)); - var lastChar = int16(stream.getBytes(2)); + var firstChar = stream.getUint16(); + var lastChar = stream.getUint16(); if (firstChar > lastChar) { return false; } stream.getBytes(6); // skipping sTypoAscender/Descender/LineGap - var usWinAscent = int16(stream.getBytes(2)); + var usWinAscent = stream.getUint16(); if (usWinAscent === 0) { // makes font unreadable by windows return false; } @@ -2859,9 +2858,9 @@ var Font = (function FontClosure() { String.fromCharCode(tag[2]) + String.fromCharCode(tag[3]); - var checksum = int32(file.getBytes(4)); - var offset = int32(file.getBytes(4)); - var length = int32(file.getBytes(4)); + var checksum = file.getUint32(); + var offset = file.getUint32(); + var length = file.getUint32(); // Read the table associated data var previousPosition = file.pos; @@ -2888,10 +2887,10 @@ var Font = (function FontClosure() { function readOpenTypeHeader(ttf) { return { version: arrayToString(ttf.getBytes(4)), - numTables: int16(ttf.getBytes(2)), - searchRange: int16(ttf.getBytes(2)), - entrySelector: int16(ttf.getBytes(2)), - rangeShift: int16(ttf.getBytes(2)) + numTables: ttf.getUint16(), + searchRange: ttf.getUint16(), + entrySelector: ttf.getUint16(), + rangeShift: ttf.getUint16() }; } @@ -2903,8 +2902,8 @@ var Font = (function FontClosure() { var start = (font.start ? font.start : 0) + cmap.offset; font.pos = start; - var version = int16(font.getBytes(2)); - var numTables = int16(font.getBytes(2)); + var version = font.getUint16(); + var numTables = font.getUint16(); var potentialTable; var canBreak = false; @@ -2915,9 +2914,9 @@ var Font = (function FontClosure() { // The following takes advantage of the fact that the tables are sorted // to work. 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 platformId = font.getUint16(); + var encodingId = font.getUint16(); + var offset = font.getUint32(); var useTable = false; if (platformId == 1 && encodingId === 0) { @@ -2950,9 +2949,9 @@ var Font = (function FontClosure() { } font.pos = start + potentialTable.offset; - var format = int16(font.getBytes(2)); - var length = int16(font.getBytes(2)); - var language = int16(font.getBytes(2)); + var format = font.getUint16(); + var length = font.getUint16(); + var language = font.getUint16(); var hasShortCmap = false; var mappings = []; @@ -2973,25 +2972,25 @@ var Font = (function FontClosure() { } else if (format === 4) { // re-creating the table in format 4 since the encoding // might be changed - var segCount = (int16(font.getBytes(2)) >> 1); + var segCount = (font.getUint16() >> 1); font.getBytes(6); // skipping range fields var segIndex, segments = []; for (segIndex = 0; segIndex < segCount; segIndex++) { - segments.push({ end: int16(font.getBytes(2)) }); + segments.push({ end: font.getUint16() }); } - font.getBytes(2); + font.getUint16(); for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].start = int16(font.getBytes(2)); + segments[segIndex].start = font.getUint16(); } for (segIndex = 0; segIndex < segCount; segIndex++) { - segments[segIndex].delta = int16(font.getBytes(2)); + segments[segIndex].delta = font.getUint16(); } var offsetsCount = 0; for (segIndex = 0; segIndex < segCount; segIndex++) { var segment = segments[segIndex]; - var rangeOffset = int16(font.getBytes(2)); + var rangeOffset = font.getUint16(); if (!rangeOffset) { segment.offsetIndex = -1; continue; @@ -3005,7 +3004,7 @@ var Font = (function FontClosure() { var offsets = []; for (var j = 0; j < offsetsCount; j++) { - offsets.push(int16(font.getBytes(2))); + offsets.push(font.getUint16()); } for (segIndex = 0; segIndex < segCount; segIndex++) { @@ -3036,13 +3035,13 @@ var Font = (function FontClosure() { // 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 firstCode = font.getUint16(); + var entryCount = font.getUint16(); var glyphs = []; var ids = []; for (var j = 0; j < entryCount; j++) { - var glyphId = int16(font.getBytes(2)); + var glyphId = font.getUint16(); var charCode = firstCode + j; mappings.push({ @@ -3083,7 +3082,7 @@ var Font = (function FontClosure() { font.pos = (font.start ? font.start : 0) + header.offset; font.pos += header.length - 2; - var numOfMetrics = int16(font.getBytes(2)); + var numOfMetrics = font.getUint16(); if (numOfMetrics > numGlyphs) { info('The numOfMetrics (' + numOfMetrics + ') should not be ' + @@ -3187,7 +3186,7 @@ var Font = (function FontClosure() { // Validate version: // Should always be 0x00010000 - var version = int32([data[0], data[1], data[2], data[3]]); + 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; @@ -3196,7 +3195,7 @@ var Font = (function FontClosure() { data[3] = 0; } - var indexToLocFormat = int16([data[50], data[51]]); + var indexToLocFormat = int16(data[50], data[51]); if (indexToLocFormat < 0 || indexToLocFormat > 1) { info('Attempting to fix invalid indexToLocFormat in head table: ' + indexToLocFormat); @@ -3321,7 +3320,7 @@ var Font = (function FontClosure() { font.pos = start; var length = post.length, end = start + length; - var version = int32(font.getBytes(4)); + var version = font.getUint32(); // skip rest to the tables font.getBytes(28); @@ -3332,14 +3331,14 @@ var Font = (function FontClosure() { glyphNames = MacStandardGlyphOrdering; break; case 0x00020000: - var numGlyphs = int16(font.getBytes(2)); + var numGlyphs = font.getUint16(); if (numGlyphs != maxpNumGlyphs) { valid = false; break; } var glyphNameIndexes = []; for (var i = 0; i < numGlyphs; ++i) { - var index = int16(font.getBytes(2)); + var index = font.getUint16(); if (index >= 32768) { valid = false; break; @@ -3385,25 +3384,25 @@ var Font = (function FontClosure() { var names = [[], []]; var length = nameTable.length, end = start + length; - var format = int16(font.getBytes(2)); + var format = font.getUint16(); var FORMAT_0_HEADER_LENGTH = 6; if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) { // unsupported name table format or table "too" small return names; } - var numRecords = int16(font.getBytes(2)); - var stringsStart = int16(font.getBytes(2)); + var numRecords = font.getUint16(); + var stringsStart = font.getUint16(); var records = []; var NAME_RECORD_LENGTH = 12; for (var i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) { var r = { - platform: int16(font.getBytes(2)), - encoding: int16(font.getBytes(2)), - language: int16(font.getBytes(2)), - name: int16(font.getBytes(2)), - length: int16(font.getBytes(2)), - offset: int16(font.getBytes(2)) + platform: font.getUint16(), + encoding: font.getUint16(), + language: font.getUint16(), + name: font.getUint16(), + length: font.getUint16(), + offset: font.getUint16() }; // using only Macintosh and Windows platform/encoding names if ((r.platform == 1 && r.encoding === 0 && r.language === 0) || @@ -3424,7 +3423,7 @@ var Font = (function FontClosure() { // unicode var str = ''; for (var j = 0, jj = record.length; j < jj; j += 2) { - str += String.fromCharCode(int16(font.getBytes(2))); + str += String.fromCharCode(font.getUint16()); } names[1][nameIndex] = str; } else { @@ -3719,19 +3718,19 @@ var Font = (function FontClosure() { } font.pos = (font.start || 0) + tables.maxp.offset; - var version = int32(font.getBytes(4)); - var numGlyphs = int16(font.getBytes(2)); + var version = font.getUint32(); + var numGlyphs = font.getUint16(); var maxFunctionDefs = 0; if (version >= 0x00010000 && tables.maxp.length >= 22) { // maxZones can be invalid font.pos += 8; - var maxZones = int16(font.getBytes(2)); + var maxZones = font.getUint16(); if (maxZones > 2) { // reset to 2 if font has invalid maxZones tables.maxp.data[14] = 0; tables.maxp.data[15] = 2; } font.pos += 4; - maxFunctionDefs = int16(font.getBytes(2)); + maxFunctionDefs = font.getUint16(); } var dupFirstEntry = false; @@ -3781,8 +3780,8 @@ var Font = (function FontClosure() { sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); if (isTrueType) { - var isGlyphLocationsLong = int16([tables.head.data[50], - tables.head.data[51]]); + var isGlyphLocationsLong = int16(tables.head.data[50], + tables.head.data[51]); sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry); } @@ -3926,11 +3925,11 @@ var Font = (function FontClosure() { // extract some more font properties from the OpenType head and // hhea tables; yMin and descent value are always negative var override = { - unitsPerEm: int16([tables.head.data[18], tables.head.data[19]]), - yMax: int16([tables.head.data[42], tables.head.data[43]]), - yMin: int16([tables.head.data[38], tables.head.data[39]]) - 0x10000, - ascent: int16([tables.hhea.data[4], tables.hhea.data[5]]), - descent: int16([tables.hhea.data[6], tables.hhea.data[7]]) - 0x10000 + unitsPerEm: int16(tables.head.data[18], tables.head.data[19]), + yMax: int16(tables.head.data[42], tables.head.data[43]), + yMin: int16(tables.head.data[38], tables.head.data[39]) - 0x10000, + ascent: int16(tables.hhea.data[4], tables.hhea.data[5]), + descent: int16(tables.hhea.data[6], tables.hhea.data[7]) - 0x10000 }; tables['OS/2'] = { diff --git a/src/core/stream.js b/src/core/stream.js index fe8337b34..3588b84bb 100644 --- a/src/core/stream.js +++ b/src/core/stream.js @@ -40,6 +40,18 @@ var Stream = (function StreamClosure() { return -1; return this.bytes[this.pos++]; }, + getUint16: function Stream_getUint16() { + var b0 = this.getByte(); + var b1 = this.getByte(); + return (b0 << 8) + b1; + }, + getUint32: function Stream_getUint32() { + var b0 = this.getByte(); + var b1 = this.getByte(); + var b2 = this.getByte(); + var b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + }, // returns subarray of original buffer // should only be read getBytes: function Stream_getBytes(length) { @@ -143,6 +155,18 @@ var DecodeStream = (function DecodeStreamClosure() { } return this.buffer[this.pos++]; }, + getUint16: function DecodeStream_getUint16() { + var b0 = this.getByte(); + var b1 = this.getByte(); + return (b0 << 8) + b1; + }, + getUint32: function DecodeStream_getUint32() { + var b0 = this.getByte(); + var b1 = this.getByte(); + var b2 = this.getByte(); + var b3 = this.getByte(); + return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3; + }, getBytes: function DecodeStream_getBytes(length) { var end, pos = this.pos; From 2e93a0cc9878c8493b146e76a08365e9afa3b505 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 11 Mar 2014 21:48:50 -0700 Subject: [PATCH 2/4] Avoid allocations in bidi(). --- src/core/bidi.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/bidi.js b/src/core/bidi.js index 6c28dc4b3..566ed6cb1 100644 --- a/src/core/bidi.js +++ b/src/core/bidi.js @@ -145,6 +145,11 @@ var bidi = PDFJS.bidi = (function bidiClosure() { this.dir = (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl')); } + // These are used in bidi(), which is called frequently. We re-use them on + // each call to avoid unnecessary allocations. + var chars = []; + var types = []; + function bidi(str, startLevel, vertical) { var isLTR = true; var strLength = str.length; @@ -153,8 +158,8 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } // Get types and fill arrays - var chars = []; - var types = []; + chars.length = 0; + types.length = 0; var numBidi = 0; for (var i = 0; i < strLength; ++i) { From 3759c11f424ed74315ea660b96e861db5fb4350a Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 12 Mar 2014 20:17:57 -0700 Subject: [PATCH 3/4] Avoid more allocations in bidi(). --- src/core/bidi.js | 14 ++++++++------ src/core/evaluator.js | 6 +----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/core/bidi.js b/src/core/bidi.js index 566ed6cb1..39a15bd8a 100644 --- a/src/core/bidi.js +++ b/src/core/bidi.js @@ -140,9 +140,11 @@ var bidi = PDFJS.bidi = (function bidiClosure() { } } - function BidiResult(str, isLTR, vertical) { - this.str = str; - this.dir = (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl')); + function createBidiText(str, isLTR, vertical) { + return { + str: str, + dir: (vertical ? 'ttb' : (isLTR ? 'ltr' : 'rtl')) + }; } // These are used in bidi(), which is called frequently. We re-use them on @@ -154,7 +156,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { var isLTR = true; var strLength = str.length; if (strLength === 0 || vertical) { - return new BidiResult(str, isLTR, vertical); + return createBidiText(str, isLTR, vertical); } // Get types and fill arrays @@ -188,7 +190,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { // - If more than 30% chars are rtl then string is primarily rtl if (numBidi === 0) { isLTR = true; - return new BidiResult(str, isLTR); + return createBidiText(str, isLTR); } if (startLevel == -1) { @@ -453,7 +455,7 @@ var bidi = PDFJS.bidi = (function bidiClosure() { result += ch; } } - return new BidiResult(result, isLTR); + return createBidiText(result, isLTR); } return bidi; diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 226532187..f9d877a37 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -812,11 +812,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (chunkBuf.length > 0) { var chunk = chunkBuf.join(''); - var bidiResult = PDFJS.bidi(chunk, -1, font.vertical); - var bidiText = { - str: bidiResult.str, - dir: bidiResult.dir - }; + var bidiText = PDFJS.bidi(chunk, -1, font.vertical); var renderParams = textState.calcRenderParams(preprocessor.ctm); var fontHeight = textState.fontSize * renderParams.vScale; var fontAscent = font.ascent ? font.ascent * fontHeight : From 6c698519cdb6d3fdcb561828f0ad52b3e45b114b Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Wed, 12 Mar 2014 20:49:46 -0700 Subject: [PATCH 4/4] Avoid allocations in executeCommand(). --- src/core/fonts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/fonts.js b/src/core/fonts.js index 370cf58ce..cd8ef2e71 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -4779,7 +4779,7 @@ var Type1CharString = (function Type1CharStringClosure() { if (keepStack) { this.stack.splice(start, howManyArgs); } else { - this.stack = []; + this.stack.length = 0; } return false; }