mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-01 15:51:00 +02:00
Merge pull request #21343 from calixteman/issue9437
Clamp out-of-range BlueScale to Adobe's valid window
This commit is contained in:
commit
c7a32c3db6
@ -30,6 +30,7 @@ import {
|
||||
} from "./charsets.js";
|
||||
import { ExpertEncoding, StandardEncoding } from "./encodings.js";
|
||||
import { DataBuilder } from "./data_builder.js";
|
||||
import { MathClamp } from "../shared/math_clamp.js";
|
||||
|
||||
// Maximum subroutine call depth of type 2 charstrings. Matches OTS.
|
||||
const MAX_SUBR_NESTING = 10;
|
||||
@ -865,6 +866,43 @@ class CFFParser {
|
||||
// 15289), hence we just reset it to its default value.
|
||||
privateDict.setByName("ExpansionFactor", DEFAULT_EXPANSION_FACTOR);
|
||||
}
|
||||
if (blueScale > 0) {
|
||||
// Adobe's font validator (AFDKO, see `absfont.cpp`) flags BlueScale as
|
||||
// out-of-range when `BlueScale * maxZoneHeight` is below 0.5 or above 1.
|
||||
// The Type 2 hinting engine in coretype/FreeType disables the lower
|
||||
// clamp at render time because library fonts with small zones and a
|
||||
// default BlueScale (0.039625) trip the threshold even though they
|
||||
// render correctly. To avoid changing those fonts here, only apply
|
||||
// the lower clamp when BlueScale is also smaller than the default,
|
||||
// i.e. when the font genuinely deviates from the standard value.
|
||||
// The upper clamp matches what FreeType already enforces (psblues.c)
|
||||
// and is safe to apply unconditionally.
|
||||
let maxZoneHeight = 0;
|
||||
for (const zones of [
|
||||
privateDict.getByName("BlueValues"),
|
||||
privateDict.getByName("OtherBlues"),
|
||||
]) {
|
||||
if (!zones) {
|
||||
continue;
|
||||
}
|
||||
// BlueValues/OtherBlues are stored as deltas where the odd-indexed
|
||||
// entries are the heights of each zone.
|
||||
for (let i = 1; i < zones.length; i += 2) {
|
||||
if (zones[i] > maxZoneHeight) {
|
||||
maxZoneHeight = zones[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxZoneHeight > 0) {
|
||||
const minBlueScale =
|
||||
blueScale < DEFAULT_BLUE_SCALE ? 0.5 / maxZoneHeight : -Infinity;
|
||||
const maxBlueScale = 1 / maxZoneHeight;
|
||||
const clamped = MathClamp(blueScale, minBlueScale, maxBlueScale);
|
||||
if (clamped !== blueScale) {
|
||||
privateDict.setByName("BlueScale", clamped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the Subrs index also since it's relative to the private dict.
|
||||
if (!privateDict.getByName("Subrs")) {
|
||||
|
||||
@ -269,6 +269,94 @@ describe("CFFParser", function () {
|
||||
expect(privateDict.getByName("ExpansionFactor")).toEqual(0.06);
|
||||
});
|
||||
|
||||
it("clamps a too-small BlueScale up to 0.5 / maxZoneHeight", function () {
|
||||
cff.topDict.privateDict = new CFFPrivateDict(cff.strings);
|
||||
// Zones (deltas): heights are the odd-indexed entries (all 20 here).
|
||||
cff.topDict.privateDict.setByName(
|
||||
"BlueValues",
|
||||
[-20, 20, 530, 20, 220, 20, 30, 20]
|
||||
);
|
||||
cff.topDict.privateDict.setByName("OtherBlues", [-270, 20]);
|
||||
cff.topDict.privateDict.setByName("BlueScale", 0.016666999);
|
||||
cff.topDict.setByName("Private", [0, 0]);
|
||||
const fontDataWithSmallBlueScale = new CFFCompiler(cff).compile();
|
||||
|
||||
const reparsedCff = new CFFParser(
|
||||
new Stream(fontDataWithSmallBlueScale),
|
||||
{},
|
||||
SEAC_ANALYSIS_ENABLED
|
||||
).parse();
|
||||
|
||||
// maxZoneHeight = 20 -> minBlueScale = 0.5 / 20 = 0.025.
|
||||
expect(reparsedCff.topDict.privateDict.getByName("BlueScale")).toEqual(
|
||||
0.025
|
||||
);
|
||||
});
|
||||
|
||||
it("clamps a too-large BlueScale down to 1 / maxZoneHeight", function () {
|
||||
cff.topDict.privateDict = new CFFPrivateDict(cff.strings);
|
||||
cff.topDict.privateDict.setByName(
|
||||
"BlueValues",
|
||||
[-20, 20, 530, 20, 220, 20, 30, 20]
|
||||
);
|
||||
cff.topDict.privateDict.setByName("BlueScale", 0.1);
|
||||
cff.topDict.setByName("Private", [0, 0]);
|
||||
const fontDataWithLargeBlueScale = new CFFCompiler(cff).compile();
|
||||
|
||||
const reparsedCff = new CFFParser(
|
||||
new Stream(fontDataWithLargeBlueScale),
|
||||
{},
|
||||
SEAC_ANALYSIS_ENABLED
|
||||
).parse();
|
||||
|
||||
// maxZoneHeight = 20 -> maxBlueScale = 1 / 20 = 0.05.
|
||||
expect(reparsedCff.topDict.privateDict.getByName("BlueScale")).toEqual(
|
||||
0.05
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves a BlueScale that is already inside the valid range", function () {
|
||||
cff.topDict.privateDict = new CFFPrivateDict(cff.strings);
|
||||
cff.topDict.privateDict.setByName(
|
||||
"BlueValues",
|
||||
[-20, 20, 530, 20, 220, 20, 30, 20]
|
||||
);
|
||||
cff.topDict.privateDict.setByName("BlueScale", 0.039625);
|
||||
cff.topDict.setByName("Private", [0, 0]);
|
||||
const fontDataWithNormalBlueScale = new CFFCompiler(cff).compile();
|
||||
|
||||
const reparsedCff = new CFFParser(
|
||||
new Stream(fontDataWithNormalBlueScale),
|
||||
{},
|
||||
SEAC_ANALYSIS_ENABLED
|
||||
).parse();
|
||||
|
||||
expect(reparsedCff.topDict.privateDict.getByName("BlueScale")).toEqual(
|
||||
0.039625
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves the default BlueScale even when zones are very small", function () {
|
||||
// Foundry fonts (e.g. Eurostile LT Std Medium, maxZoneHeight 6) ship the
|
||||
// default BlueScale of 0.039625 together with small zones; that combination
|
||||
// technically violates AFDKO's lower bound but is the rendered intent.
|
||||
cff.topDict.privateDict = new CFFPrivateDict(cff.strings);
|
||||
cff.topDict.privateDict.setByName("BlueValues", [-12, 6, 530, 6]);
|
||||
cff.topDict.privateDict.setByName("BlueScale", 0.039625);
|
||||
cff.topDict.setByName("Private", [0, 0]);
|
||||
const fontDataDefaultBlueScale = new CFFCompiler(cff).compile();
|
||||
|
||||
const reparsedCff = new CFFParser(
|
||||
new Stream(fontDataDefaultBlueScale),
|
||||
{},
|
||||
SEAC_ANALYSIS_ENABLED
|
||||
).parse();
|
||||
|
||||
expect(reparsedCff.topDict.privateDict.getByName("BlueScale")).toEqual(
|
||||
0.039625
|
||||
);
|
||||
});
|
||||
|
||||
it("refuses to add topDict key with invalid value (bug 1068432)", function () {
|
||||
const topDict = cff.topDict;
|
||||
const defaultValue = topDict.getByName("UnderlinePosition");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user