diff --git a/src/core/cff_parser.js b/src/core/cff_parser.js index 50b42ea2d5ea2..c6655de89ac59 100644 --- a/src/core/cff_parser.js +++ b/src/core/cff_parser.js @@ -108,6 +108,11 @@ const CFFStandardStrings = [ const NUM_STANDARD_CFF_STRINGS = 391; +const DEFAULT_BLUE_SCALE = 0.039625; +const DEFAULT_BLUE_SHIFT = 7; +const DEFAULT_BLUE_FUZZ = 1; +const DEFAULT_EXPANSION_FACTOR = 0.06; + const CharstringValidationData = [ /* 0 */ null, /* 1 */ { id: "hstem", min: 2, stackClearing: true, stem: true }, @@ -262,8 +267,16 @@ class CFFParser { properties.fontMatrix = fontMatrix; } - const fontBBox = topDict.getByName("FontBBox"); - if (fontBBox) { + let fontBBox = topDict.getByName("FontBBox"); + if (fontBBox?.every(coord => coord === 0) && properties.bbox) { + fontBBox = Util.normalizeRect( + properties.bbox.map(coord => + coord > 0x7fff && coord <= 0xffff ? coord - 0x10000 : coord + ) + ); + topDict.setByName("FontBBox", fontBBox); + } + if (fontBBox?.some(coord => coord !== 0)) { // adjusting ascent/descent properties.ascent = Math.max(fontBBox[3], fontBBox[1]); properties.descent = Math.min(fontBBox[1], fontBBox[3]); @@ -785,10 +798,28 @@ class CFFParser { ); parentDict.privateDict = privateDict; - if (privateDict.getByName("ExpansionFactor") === 0) { + const blueScale = privateDict.getByName("BlueScale"); + const blueShift = privateDict.getByName("BlueShift"); + const blueFuzz = privateDict.getByName("BlueFuzz"); + const expansionFactor = privateDict.getByName("ExpansionFactor"); + if ( + blueScale === 0 && + blueShift === 0 && + blueFuzz === 0 && + expansionFactor === 0 + ) { + // Ghostscript can fail to initialize Private DICT defaults before + // writing them, which leaves omitted blue zone values as explicit + // zeroes. This has been seen in FDArray entries. + privateDict.setByName("BlueScale", DEFAULT_BLUE_SCALE); + privateDict.setByName("BlueShift", DEFAULT_BLUE_SHIFT); + privateDict.setByName("BlueFuzz", DEFAULT_BLUE_FUZZ); + } + + if (expansionFactor === 0) { // Firefox doesn't render correctly such a font on Windows (see issue // 15289), hence we just reset it to its default value. - privateDict.setByName("ExpansionFactor", 0.06); + privateDict.setByName("ExpansionFactor", DEFAULT_EXPANSION_FACTOR); } // Parse the Subrs index also since it's relative to the private dict. @@ -1247,16 +1278,16 @@ const CFFPrivateDictLayout = [ [7, "OtherBlues", "delta", null], [8, "FamilyBlues", "delta", null], [9, "FamilyOtherBlues", "delta", null], - [[12, 9], "BlueScale", "num", 0.039625], - [[12, 10], "BlueShift", "num", 7], - [[12, 11], "BlueFuzz", "num", 1], + [[12, 9], "BlueScale", "num", DEFAULT_BLUE_SCALE], + [[12, 10], "BlueShift", "num", DEFAULT_BLUE_SHIFT], + [[12, 11], "BlueFuzz", "num", DEFAULT_BLUE_FUZZ], [10, "StdHW", "num", null], [11, "StdVW", "num", null], [[12, 12], "StemSnapH", "delta", null], [[12, 13], "StemSnapV", "delta", null], [[12, 14], "ForceBold", "num", 0], [[12, 17], "LanguageGroup", "num", 0], - [[12, 18], "ExpansionFactor", "num", 0.06], + [[12, 18], "ExpansionFactor", "num", DEFAULT_EXPANSION_FACTOR], [[12, 19], "initialRandomSeed", "num", 0], [20, "defaultWidthX", "num", 0], [21, "nominalWidthX", "num", 0], diff --git a/test/integration/document_properties_spec.mjs b/test/integration/document_properties_spec.mjs index 6a3dbb3e3363b..cb3d08db6cf68 100644 --- a/test/integration/document_properties_spec.mjs +++ b/test/integration/document_properties_spec.mjs @@ -14,6 +14,7 @@ */ import { closePages, FSI, loadAndWait, PDI } from "./test_utils.mjs"; +import fs from "fs/promises"; const FIELDS = [ "fileName", @@ -155,4 +156,68 @@ describe("PDFDocumentProperties", () => { ); }); }); + + describe("Document without contentLength", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("empty.pdf", ".textLayer .endOfContent"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must check that the document properties dialog has the correct information", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + // Open a binary PDF document, such that `contentLength` is undefined. + const base64 = await fs.readFile("./pdfs/clippath.pdf", { + encoding: "base64", + }); + + await page.evaluate(async b64 => { + await window.PDFViewerApplication.open({ + data: Uint8Array.fromBase64(b64), + }); + }, base64); + + await page.click("#secondaryToolbarToggleButton"); + await page.waitForSelector("#secondaryToolbar", { hidden: false }); + + await page.click("#documentProperties"); + await page.waitForSelector("#documentPropertiesDialog", { + hidden: false, + }); + + await page.waitForFunction( + `document.getElementById("fileSizeField").textContent !== "-"` + ); + const props = await getFieldProperties(page); + + expect(props).toEqual({ + fileName: "document.pdf", + fileSize: `${FSI}0.448${PDI} KB (${FSI}459${PDI} bytes)`, + title: "-", + author: "-", + subject: "-", + keywords: "-", + creationDate: "-", + modificationDate: "-", + creator: "-", + producer: "-", + version: "1.1", + pageCount: "1", + pageSize: `${FSI}2.78${PDI} × ${FSI}1.39${PDI} ${FSI}in${PDI} (${FSI}landscape${PDI})`, + linearized: "No", + }); + + await page.click("#documentPropertiesClose"); + await page.waitForSelector("#documentPropertiesDialog", { + hidden: true, + }); + }) + ); + }); + }); }); diff --git a/test/integration/highlight_editor_spec.mjs b/test/integration/highlight_editor_spec.mjs index 757ea2e744624..ec4e7d130185f 100644 --- a/test/integration/highlight_editor_spec.mjs +++ b/test/integration/highlight_editor_spec.mjs @@ -1726,10 +1726,6 @@ describe("Highlight Editor", () => { it("must check that an existing highlight is ignored on hovering", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - if (navigator.platform.includes("Win")) { - pending("Fails consistently on Windows (issue #20136)."); - } - await switchToHighlight(page); const rect = await getSpanRectFromText( diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index 4146fcd7e6295..2ef42d68e222b 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -268,13 +268,8 @@ function getSelector(id) { } async function getRect(page, selector) { - // In Chrome something is wrong when serializing a `DomRect`, - // so we extract the values and return them ourselves. await page.waitForSelector(selector, { visible: true }); - return page.$eval(selector, el => { - const { x, y, width, height } = el.getBoundingClientRect(); - return { x, y, width, height }; - }); + return (await page.$(selector)).boundingBox(); } function getQuerySelector(id) { diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index deb059ef139f4..5dd5b2d31f9b9 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -1403,6 +1403,10 @@ describe("PDF viewer", () => { ); }); + afterEach(async () => { + await closePages(pages); + }); + it("keeps the content under the pinch centre fixed on the screen", async () => { await Promise.all( pages.map(async ([browserName, page]) => { @@ -1610,6 +1614,10 @@ describe("PDF viewer", () => { ); }); + afterEach(async () => { + await closePages(pages); + }); + it("Check that the top right corner of the annotation is centered vertically", async () => { await Promise.all( pages.map(async ([browserName, page]) => { diff --git a/test/test.mjs b/test/test.mjs index fde169a818bed..d2adf6c3b6f50 100644 --- a/test/test.mjs +++ b/test/test.mjs @@ -992,7 +992,7 @@ async function startBrowser({ // calls can run before events triggered by the previous protocol calls had // a chance to be processed (essentially causing events to get lost). This // value gives Chrome a more similar execution speed as Firefox. - options.slowMo = 5; + options.slowMo = 3; // avoid crash options.args = ["--no-sandbox", "--disable-setuid-sandbox"]; diff --git a/test/unit/cff_parser_spec.js b/test/unit/cff_parser_spec.js index c58fd65519c60..e5ddd905b53e1 100644 --- a/test/unit/cff_parser_spec.js +++ b/test/unit/cff_parser_spec.js @@ -18,7 +18,9 @@ import { CFFCompiler, CFFFDSelect, CFFParser, + CFFPrivateDict, CFFStrings, + CFFTopDict, } from "../../src/core/cff_parser.js"; import { SEAC_ANALYSIS_ENABLED } from "../../src/core/fonts_utils.js"; import { Stream } from "../../src/core/stream.js"; @@ -112,6 +114,77 @@ describe("CFFParser", function () { expect(topDict.getByName("Private")).toEqual([45, 102]); }); + it("ignores an empty FontBBox when adjusting ascent/descent", function () { + cff.topDict.setByName("FontBBox", [0, 0, 0, 0]); + const fontDataWithEmptyBBox = new CFFCompiler(cff).compile(); + + const properties = { + ascent: 800, + descent: -200, + }; + new CFFParser( + new Stream(fontDataWithEmptyBBox), + properties, + SEAC_ANALYSIS_ENABLED + ).parse(); + + expect(properties.ascent).toEqual(800); + expect(properties.descent).toEqual(-200); + expect(properties.ascentScaled).toBeUndefined(); + }); + + it("repairs an empty FontBBox from font descriptor data", function () { + cff.topDict.setByName("FontBBox", [0, 0, 0, 0]); + const fontDataWithEmptyBBox = new CFFCompiler(cff).compile(); + + const properties = { + bbox: [2974, -300, 64236, 900], + }; + const reparsedCff = new CFFParser( + new Stream(fontDataWithEmptyBBox), + properties, + SEAC_ANALYSIS_ENABLED + ).parse(); + + expect(reparsedCff.topDict.getByName("FontBBox")).toEqual([ + -1300, -300, 2974, 900, + ]); + expect(properties.ascent).toEqual(900); + expect(properties.descent).toEqual(-300); + expect(properties.ascentScaled).toEqual(true); + }); + + it("repairs likely Ghostscript-zeroed FDArray private defaults", function () { + cff.isCIDFont = true; + cff.topDict.setByName("ROS", [0, 0, 0]); + cff.topDict.setByName("FDSelect", 0); + cff.topDict.setByName("FDArray", 0); + + const fdDict = new CFFTopDict(cff.strings); + fdDict.setByName("Private", [0, 0]); + fdDict.privateDict = new CFFPrivateDict(cff.strings); + fdDict.privateDict.setByName("BlueScale", 0); + fdDict.privateDict.setByName("BlueShift", 0); + fdDict.privateDict.setByName("BlueFuzz", 0); + fdDict.privateDict.setByName("ExpansionFactor", 0); + + cff.fdArray = [fdDict]; + cff.fdSelect = new CFFFDSelect(0, Array(cff.charStrings.count).fill(0)); + const fontDataWithBrokenFDPrivate = new CFFCompiler(cff).compile(); + + const reparsedCff = new CFFParser( + new Stream(fontDataWithBrokenFDPrivate), + {}, + SEAC_ANALYSIS_ENABLED + ).parse(); + const privateDict = reparsedCff.fdArray[0].privateDict; + + expect(privateDict.getByName("BlueScale")).toEqual(0.039625); + expect(privateDict.getByName("BlueShift")).toEqual(7); + expect(privateDict.getByName("BlueFuzz")).toEqual(1); + expect(privateDict.getByName("ExpansionFactor")).toEqual(0.06); + }); + it("refuses to add topDict key with invalid value (bug 1068432)", function () { const topDict = cff.topDict; const defaultValue = topDict.getByName("UnderlinePosition");