diff --git a/src/core/cff_parser.js b/src/core/cff_parser.js index 42502f5a6..9fbd85305 100644 --- a/src/core/cff_parser.js +++ b/src/core/cff_parser.js @@ -894,8 +894,17 @@ class CFFParser { } } if (maxZoneHeight > 0) { + // The lower bound of AFDKO's valid window is `0.5 / maxZoneHeight`. + // When that bound is itself above the default BlueScale the font simply + // has small zones (e.g. Eurostile LT Std, or the SofiaPro fonts shipped + // with a near-default 0.037): even the default 0.039625 would be + // flagged as out-of-range, so this is the rendered intent and forcing + // BlueScale up only misaligns/collapses overshooting glyphs (notably + // with macOS's Core Text rasterizer). Only apply the lower clamp when + // its target does not exceed the default. + const lowerBound = 0.5 / maxZoneHeight; const minBlueScale = - blueScale < DEFAULT_BLUE_SCALE ? 0.5 / maxZoneHeight : -Infinity; + lowerBound <= DEFAULT_BLUE_SCALE ? lowerBound : -Infinity; const maxBlueScale = 1 / maxZoneHeight; const clamped = MathClamp(blueScale, minBlueScale, maxBlueScale); if (clamped !== blueScale) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 9d265fbf6..87b54309b 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -937,3 +937,4 @@ !checkbox_no_appearance.pdf !opt_demo.pdf !bug1873345.pdf +!cff_bluescale_small_zones.pdf diff --git a/test/pdfs/cff_bluescale_small_zones.pdf b/test/pdfs/cff_bluescale_small_zones.pdf new file mode 100644 index 000000000..178ba2089 Binary files /dev/null and b/test/pdfs/cff_bluescale_small_zones.pdf differ diff --git a/test/unit/cff_parser_spec.js b/test/unit/cff_parser_spec.js index 36e913fc8..80ee917d5 100644 --- a/test/unit/cff_parser_spec.js +++ b/test/unit/cff_parser_spec.js @@ -22,6 +22,9 @@ import { CFFStrings, CFFTopDict, } from "../../src/core/cff_parser.js"; +import { DefaultFileReaderFactory, TEST_PDFS_PATH } from "./test_utils.js"; +import { PDFDocument } from "../../src/core/document.js"; +import { Ref } from "../../src/core/primitives.js"; import { SEAC_ANALYSIS_ENABLED } from "../../src/core/fonts_utils.js"; import { Stream } from "../../src/core/stream.js"; @@ -357,6 +360,35 @@ describe("CFFParser", function () { ); }); + it("preserves the BlueScale of an embedded CID font with small zones", async function () { + // The embedded CID-keyed CFF pairs a near-default BlueScale of 0.037 with + // 12-unit zones; clamping it up to the lower bound breaks rendering on + // macOS only, so it's guarded here rather than with a reference image. + const data = await DefaultFileReaderFactory.fetch({ + path: TEST_PDFS_PATH + "cff_bluescale_small_zones.pdf", + }); + const pdfManager = { + evaluatorOptions: { isOffscreenCanvasSupported: false }, + password: null, + }; + const pdfDocument = new PDFDocument(pdfManager, new Stream(data)); + pdfDocument.parseStartXRef(); + pdfDocument.xref.parse(); + + // Object 8 is the `/FontFile3` (`/CIDFontType0C`) stream in the fixture. + const fontProgram = pdfDocument.xref.fetch(Ref.get(8, 0)).getBytes(); + const embeddedCff = new CFFParser( + new Stream(fontProgram), + {}, + SEAC_ANALYSIS_ENABLED + ).parse(); + + expect(embeddedCff.isCIDFont).toEqual(true); + expect(embeddedCff.fdArray[0].privateDict.getByName("BlueScale")).toEqual( + 0.037 + ); + }); + it("refuses to add topDict key with invalid value (bug 1068432)", function () { const topDict = cff.topDict; const defaultValue = topDict.getByName("UnderlinePosition");