diff --git a/src/core/cff_parser.js b/src/core/cff_parser.js index c6655de89ac59..2a8935dd83e7c 100644 --- a/src/core/cff_parser.js +++ b/src/core/cff_parser.js @@ -787,6 +787,12 @@ class CFFParser { this.emptyPrivateDictionary(parentDict); return; } + // The Private DICT extends past the end of the font data, which means + // the embedded font is truncated; abort so the caller can substitute a + // system font instead of rendering blank glyphs (issue 7625). + if (offset + size > this.bytes.length) { + throw new FormatError("CFF Private DICT extends past end of font"); + } const privateDictEnd = offset + size; const dictData = this.bytes.subarray(offset, privateDictEnd); diff --git a/src/core/evaluator.js b/src/core/evaluator.js index d0cba1ddf55ce..6961d2b0021e0 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -4728,7 +4728,35 @@ class PartialEvaluator { const newProperties = await this.extractDataStructures(dict, properties); this.extractWidths(dict, descriptor, newProperties); - return new Font(fontName.name, fontFile, newProperties, this.options); + const font = new Font(fontName.name, fontFile, newProperties, this.options); + // The embedded font may have been too corrupt to parse, in which case + // we ended up in the fallback path without a substitution selected. + // Try the substitution map now so text renders in a font close to what + // the document asked for (issue 7625). + if ( + font.missingFile && + !font.systemFontInfo && + !isType3Font && + this.options.useSystemFonts + ) { + const standardFontName = getStandardFontName(fontName.name); + const substitution = getFontSubstitution( + this.systemFontCache, + this.idFactory, + this.options.standardFontDataUrl, + fontName.name, + standardFontName, + type + ); + if (substitution) { + if (substitution.guessFallback) { + substitution.guessFallback = false; + substitution.css += `,${font.fallbackName}`; + } + font.systemFontInfo = substitution; + } + } + return font; } static buildFontPaths(font, glyphs, handler, evaluatorOptions) { diff --git a/src/core/type1_parser.js b/src/core/type1_parser.js index 96343fbe8e285..843e671ad013b 100644 --- a/src/core/type1_parser.js +++ b/src/core/type1_parser.js @@ -566,6 +566,13 @@ class Type1Parser { }, }; let token, length, data, lenIV; + // Some fonts (e.g. those embedded in issue18548.pdf) define a second + // `/Subrs` and `/CharStrings` block that the PostScript runtime selects + // conditionally (e.g. high-resolution variants). Testing with other + // viewers shows that none of them actually use these conditional blocks, + // so we can "safely" ignore them. + let subrsParsed = false; + let charStringsParsed = false; while ((token = this.getToken()) !== null) { if (token !== "/") { continue; @@ -573,6 +580,10 @@ class Type1Parser { token = this.getToken(); switch (token) { case "CharStrings": + if (charStringsParsed) { + break; + } + charStringsParsed = true; // The number immediately following CharStrings must be greater or // equal to the number of CharStrings. this.getToken(); @@ -610,6 +621,10 @@ class Type1Parser { } break; case "Subrs": + if (subrsParsed) { + break; + } + subrsParsed = true; this.readInt(); // num this.getToken(); // read in 'array' while (this.getToken() === "dup") { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index bc6027dc6a5b4..16ebf27c2969e 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -922,3 +922,4 @@ !knockout_groups_test.pdf !issue18032.pdf !Embedded_font.pdf +!issue18548_reduced.pdf diff --git a/test/pdfs/issue18548_reduced.pdf b/test/pdfs/issue18548_reduced.pdf new file mode 100644 index 0000000000000..b38274d7c407e Binary files /dev/null and b/test/pdfs/issue18548_reduced.pdf differ diff --git a/test/pdfs/issue7625.pdf.link b/test/pdfs/issue7625.pdf.link new file mode 100644 index 0000000000000..4d75563e892ee --- /dev/null +++ b/test/pdfs/issue7625.pdf.link @@ -0,0 +1 @@ +https://github.com/mozilla/pdf.js/files/467169/Er.aestetik.en.loftestang.for.laering.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index bb6f59ab0640f..9c9153bdc3972 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -14303,5 +14303,22 @@ "md5": "b68dd5a3e6833d1af94e295fe1d60285", "rounds": 1, "type": "eq" + }, + { + "id": "issue18548_reduced", + "file": "pdfs/issue18548_reduced.pdf", + "md5": "39d15f7f810bd89a4e5858df9c75ca4e", + "rounds": 1, + "type": "eq" + }, + { + "id": "issue7625", + "file": "pdfs/issue7625.pdf", + "md5": "77ca0a41da767dca31ca45219e2ae202", + "link": true, + "rounds": 1, + "firstPage": 1, + "lastPage": 1, + "type": "eq" } ]