Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions scripts/test-native-cff-semantics.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ function testCffStructureAndType2Outlines() {
assert.deepEqual(flex.bounds, [0, 0, 90, 50]);
}

function testBaseFontBlendIsNotMultipleMaster() {
// Top DICT 12 23 is BaseFontBlend, a delta left behind by Multiple Master
// tooling. It carries no interpolation state and no blend axes: the glyphs
// stay exactly the ones in CharStrings. Rejecting it as a Multiple Master
// font discards documents whose outlines are perfectly ordinary.
const baseline = NativeCffFont.parse(buildCffFixture());
const blended = NativeCffFont.parse(buildCffFixture({
topDictExtra: concat(dictInteger(408), dictInteger(-397), Uint8Array.of(12, 23))
}));
assert.equal(blended.numGlyphs, baseline.numGlyphs);
assert.deepEqual(blended.glyphNames, baseline.glyphNames);
assert.deepEqual(
blended.getGlyphOutline(1).commands,
baseline.getGlyphOutline(1).commands
);
}

function testFontMatrixNormalization() {
const font = NativeCffFont.parse(buildCffFixture({
fontMatrix: [0.002, 0, 0, 0.003, 0.01, -0.02]
Expand Down Expand Up @@ -224,7 +241,8 @@ function buildCffFixture(options = {}) {
assert.equal(privateDictionary.length, 18);
const localSubrsIndex = cffIndex(localSubrs);
const fontMatrix = options.fontMatrix ?? null;
const placeholderTop = topDictionary(0, 0, 0, privateDictionary.length, 0, fontMatrix);
const topDictExtra = options.topDictExtra ?? new Uint8Array();
const placeholderTop = topDictionary(0, 0, 0, privateDictionary.length, 0, fontMatrix, topDictExtra);
const placeholderTopIndex = cffIndex([placeholderTop]);
const prefixLength = header.length + nameIndex.length + placeholderTopIndex.length +
stringIndex.length + globalSubrsIndex.length;
Expand All @@ -238,7 +256,8 @@ function buildCffFixture(options = {}) {
charStringsOffset,
privateDictionary.length,
privateOffset,
fontMatrix
fontMatrix,
topDictExtra
);
assert.equal(top.length, placeholderTop.length);

Expand All @@ -257,12 +276,13 @@ function buildCffFixture(options = {}) {
);
}

function topDictionary(charset, encoding, charStrings, privateSize, privateOffset, fontMatrix) {
function topDictionary(charset, encoding, charStrings, privateSize, privateOffset, fontMatrix, extra = new Uint8Array()) {
const matrix = fontMatrix === null
? new Uint8Array()
: concat(...fontMatrix.map((value) => dictReal(value)), Uint8Array.of(12, 7));
return concat(
matrix,
extra,
dictInteger(charset), Uint8Array.of(15),
dictInteger(encoding), Uint8Array.of(16),
dictInteger(charStrings), Uint8Array.of(17),
Expand Down Expand Up @@ -381,6 +401,7 @@ function expectPdf(callback, code, message) {

testCffStructureAndType2Outlines();
testFontMatrixNormalization();
testBaseFontBlendIsNotMultipleMaster();
await testPdfFontSelectionIsSeparateFromToUnicode();
testMalformedProgramsAndLimits();
testDeprecatedPdfDotsectionCompatibility();
Expand Down
8 changes: 6 additions & 2 deletions src/pdf/nativeCff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,13 @@ export class NativeCffFont {
"cff-cid-not-supported"
);
}
if (top.has(dictOperator(12, 20)) || top.has(dictOperator(12, 23))) {
// 12 20 is SyntheticBase: those glyphs are defined against another font and
// cannot be read from this CharStrings INDEX alone. 12 23 is BaseFontBlend,
// a delta left behind by Multiple Master tooling that carries no blend axes
// and no interpolation state, so a font holding it is read normally.
if (top.has(dictOperator(12, 20))) {
throw cffUnsupported(
"Synthetic or Multiple Master CFF fonts are not supported.",
"Synthetic CFF fonts are not supported.",
"cff-synthetic-not-supported"
);
}
Expand Down