diff --git a/src/core/annotation.js b/src/core/annotation.js index 0eee92ed5..5dfbb3400 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -2572,11 +2572,7 @@ class WidgetAnnotation extends Annotation { } _getTextWidth(text, font) { - return ( - font - .charsToGlyphs(text) - .reduce((width, glyph) => width + glyph.width, 0) / 1000 - ); + return Math.sumPrecise(font.charsToGlyphs(text).map(g => g.width)) / 1000; } _computeFontSize(height, width, text, font, lineCount) { diff --git a/src/core/crypto.js b/src/core/crypto.js index a6cc5e8bf..cfff2b8db 100644 --- a/src/core/crypto.js +++ b/src/core/crypto.js @@ -720,7 +720,7 @@ class PDF20 extends PDFBase { // The number is e0 + 256 * e1 + 256^2 * e2... and 256 % 3 === 1, hence // the powers of 256 are === 1 modulo 3 and finally the number modulo 3 // is equal to the remainder modulo 3 of the sum of the e_n. - const remainder = e.slice(0, 16).reduce((a, b) => a + b, 0) % 3; + const remainder = Math.sumPrecise(e.slice(0, 16)) % 3; if (remainder === 0) { k = calculateSHA256(e, 0, e.length); } else if (remainder === 1) { diff --git a/src/core/glyf.js b/src/core/glyf.js index d5ddb3306..06bde6482 100644 --- a/src/core/glyf.js +++ b/src/core/glyf.js @@ -81,11 +81,10 @@ class GlyfTable { } getSize() { - return this.glyphs.reduce((a, g) => { - const size = g.getSize(); + return Math.sumPrecise( // Round to next multiple of 4 if needed. - return a + ((size + 3) & ~3); - }, 0); + this.glyphs.map(g => (g.getSize() + 3) & ~3) + ); } write() { @@ -169,7 +168,7 @@ class Glyph { } const size = this.simple ? this.simple.getSize() - : this.composites.reduce((a, c) => a + c.getSize(), 0); + : Math.sumPrecise(this.composites.map(c => c.getSize())); return this.header.getSize() + size; } diff --git a/src/core/writer.js b/src/core/writer.js index cdb43d6bd..bdeb8d0d6 100644 --- a/src/core/writer.js +++ b/src/core/writer.js @@ -184,7 +184,7 @@ function computeMD5(filesize, xrefInfo) { filesize.toString(), ...Object.values(xrefInfo.info), ]; - const md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0); + const md5BufferLen = Math.sumPrecise(md5Buffer.map(str => str.length)); const array = new Uint8Array(md5BufferLen); let offset = 0; @@ -352,7 +352,7 @@ async function getXRefStreamTable( newXref.set("W", sizes); computeIDs(baseOffset, xrefInfo, newXref); - const structSize = sizes.reduce((a, x) => a + x, 0); + const structSize = Math.sumPrecise(sizes); const data = new Uint8Array(structSize * xrefTableData.length); const stream = new Stream(data); stream.dict = newXref; @@ -467,10 +467,8 @@ async function incrementalUpdate({ ? getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer) : getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer)); - const totalLength = buffer.reduce( - (a, str) => a + str.length, - originalData.length - ); + const totalLength = + originalData.length + Math.sumPrecise(buffer.map(str => str.length)); const array = new Uint8Array(totalLength); // Original data diff --git a/src/core/xfa/html_utils.js b/src/core/xfa/html_utils.js index 71aaf83c1..615505ec3 100644 --- a/src/core/xfa/html_utils.js +++ b/src/core/xfa/html_utils.js @@ -86,14 +86,15 @@ const converters = { const colSpan = node.colSpan; let w; if (colSpan === -1) { - w = extra.columnWidths - .slice(extra.currentColumn) - .reduce((a, x) => a + x, 0); + w = Math.sumPrecise(extra.columnWidths.slice(extra.currentColumn)); extra.currentColumn = 0; } else { - w = extra.columnWidths - .slice(extra.currentColumn, extra.currentColumn + colSpan) - .reduce((a, x) => a + x, 0); + w = Math.sumPrecise( + extra.columnWidths.slice( + extra.currentColumn, + extra.currentColumn + colSpan + ) + ); extra.currentColumn = (extra.currentColumn + node.colSpan) % extra.columnWidths.length; } @@ -328,13 +329,14 @@ function fixDimensions(node) { const colSpan = node.colSpan; let width; if (colSpan === -1) { - width = extra.columnWidths - .slice(extra.currentColumn) - .reduce((a, w) => a + w, 0); + width = Math.sumPrecise(extra.columnWidths.slice(extra.currentColumn)); } else { - width = extra.columnWidths - .slice(extra.currentColumn, extra.currentColumn + colSpan) - .reduce((a, w) => a + w, 0); + width = Math.sumPrecise( + extra.columnWidths.slice( + extra.currentColumn, + extra.currentColumn + colSpan + ) + ); } if (!isNaN(width)) { node.w = width; @@ -348,7 +350,7 @@ function fixDimensions(node) { if (node.layout === "table") { if (node.w === "" && Array.isArray(node.columnWidths)) { - node.w = node.columnWidths.reduce((a, x) => a + x, 0); + node.w = Math.sumPrecise(node.columnWidths); } } } diff --git a/src/core/xfa/layout.js b/src/core/xfa/layout.js index a7cdfd9b0..ce5142e6f 100644 --- a/src/core/xfa/layout.js +++ b/src/core/xfa/layout.js @@ -183,9 +183,9 @@ function getAvailableSpace(node) { }; case "rl-row": case "row": - const width = node[$extra].columnWidths - .slice(node[$extra].currentColumn) - .reduce((a, x) => a + x); + const width = Math.sumPrecise( + node[$extra].columnWidths.slice(node[$extra].currentColumn) + ); return { width, height: availableSpace.height - marginH }; case "table": case "tb": diff --git a/src/display/editor/freetext.js b/src/display/editor/freetext.js index e17b5c348..cb37fd131 100644 --- a/src/display/editor/freetext.js +++ b/src/display/editor/freetext.js @@ -718,7 +718,7 @@ class FreeTextEditor extends AnnotationEditor { // Set the caret at the right position. const newRange = new Range(); - let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0); + let beforeLength = Math.sumPrecise(bufferBefore.map(line => line.length)); for (const { firstChild } of this.editorDiv.childNodes) { // Each child is either a div with a text node or a br element. if (firstChild.nodeType === Node.TEXT_NODE) { diff --git a/src/shared/util.js b/src/shared/util.js index 936cd9ebf..b36a7383a 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -1130,6 +1130,16 @@ if ( }; } +// TODO: Remove this once the `javascript.options.experimental.math_sumprecise` +// preference is removed from Firefox. +if (typeof Math.sumPrecise !== "function") { + // Note that this isn't a "proper" polyfill, but since we're only using it to + // replace `Array.prototype.reduce()` invocations it should be fine. + Math.sumPrecise = function (numbers) { + return numbers.reduce((a, b) => a + b, 0); + }; +} + if ( typeof PDFJSDev !== "undefined" && !PDFJSDev.test("SKIP_BABEL") && diff --git a/test/unit/pdf_find_controller_spec.js b/test/unit/pdf_find_controller_spec.js index 76ac0485f..c4d18925c 100644 --- a/test/unit/pdf_find_controller_spec.js +++ b/test/unit/pdf_find_controller_spec.js @@ -128,7 +128,7 @@ function testSearch({ } } - const totalMatches = matchesPerPage.reduce((a, b) => a + b); + const totalMatches = Math.sumPrecise(matchesPerPage); if (updateFindControlState) { eventBus.on(