From 83ff34d0573f217603c969d89f480dfd0139a9e8 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Fri, 10 Nov 2023 15:54:55 +0000 Subject: [PATCH] [maxp] Attempt to improve fixup of bad 'maxp' version numbers. If the version number is not explicitly v0.5 (0x00005000), try to read it as v1.0. (The version field will be corrected to one of the valid versions during serialization.) This handles cases where the table was intended to be v1.0 (for a font with TrueType data), but the version field was set incorrectly (e.g. to 0x00000100, as seen in https://github.com/mozilla/pdf.js/issues/16839). Also make the max_zones fixup more general, as the example font from the above issue has max_zones=512(!). Correcting it to 2 should be harmless. --- src/maxp.cc | 76 ++++++++++++++++++++++++++++++----------------------- src/ots.cc | 10 +++++++ 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/src/maxp.cc b/src/maxp.cc index 232e4a98..90f53b0e 100644 --- a/src/maxp.cc +++ b/src/maxp.cc @@ -29,39 +29,49 @@ bool OpenTypeMAXP::Parse(const uint8_t *data, size_t length) { return Error("numGlyphs is 0"); } - if (version >> 16 == 1) { - this->version_1 = true; - if (!table.ReadU16(&this->max_points) || - !table.ReadU16(&this->max_contours) || - !table.ReadU16(&this->max_c_points) || - !table.ReadU16(&this->max_c_contours) || - !table.ReadU16(&this->max_zones) || - !table.ReadU16(&this->max_t_points) || - !table.ReadU16(&this->max_storage) || - !table.ReadU16(&this->max_fdefs) || - !table.ReadU16(&this->max_idefs) || - !table.ReadU16(&this->max_stack) || - !table.ReadU16(&this->max_size_glyf_instructions) || - !table.ReadU16(&this->max_c_components) || - !table.ReadU16(&this->max_c_depth)) { - return Error("Failed to read version 1 table data"); - } - - if (this->max_zones == 0) { - // workaround for ipa*.ttf Japanese fonts. - Warning("Bad maxZones: %u", this->max_zones); - this->max_zones = 1; - } else if (this->max_zones == 3) { - // workaround for Ecolier-*.ttf fonts. - Warning("Bad maxZones: %u", this->max_zones); - this->max_zones = 2; - } - - if ((this->max_zones != 1) && (this->max_zones != 2)) { - return Error("Bad maxZones: %u", this->max_zones); - } - } else { - this->version_1 = false; + this->version_1 = false; + + // Per https://learn.microsoft.com/en-gb/typography/opentype/spec/maxp, + // the only two 'maxp' version numbers are 0.5 (for CFF/CFF2) and 1.0 + // (for TrueType). + // If it's version 0.5, there is nothing more to read. + if (version == 0x00005000) { + return true; + } + + if (version != 0x00010000) { + Warning("Unexpected version 0x%08x; attempting to read as version 1.0", + version); + } + + // Otherwise, try to read the version 1.0 fields: + if (!table.ReadU16(&this->max_points) || + !table.ReadU16(&this->max_contours) || + !table.ReadU16(&this->max_c_points) || + !table.ReadU16(&this->max_c_contours) || + !table.ReadU16(&this->max_zones) || + !table.ReadU16(&this->max_t_points) || + !table.ReadU16(&this->max_storage) || + !table.ReadU16(&this->max_fdefs) || + !table.ReadU16(&this->max_idefs) || + !table.ReadU16(&this->max_stack) || + !table.ReadU16(&this->max_size_glyf_instructions) || + !table.ReadU16(&this->max_c_components) || + !table.ReadU16(&this->max_c_depth)) { + Warning("Failed to read version 1.0 fields, downgrading to version 0.5"); + return true; + } + + this->version_1 = true; + + if (this->max_zones < 1) { + // workaround for ipa*.ttf Japanese fonts. + Warning("Bad maxZones: %u", this->max_zones); + this->max_zones = 1; + } else if (this->max_zones > 2) { + // workaround for Ecolier-*.ttf fonts and bad fonts in some PDFs + Warning("Bad maxZones: %u", this->max_zones); + this->max_zones = 2; } return true; diff --git a/src/ots.cc b/src/ots.cc index cefd390a..ed22b8cc 100644 --- a/src/ots.cc +++ b/src/ots.cc @@ -762,12 +762,18 @@ bool ProcessGeneric(ots::FontFile *header, ots::Table *loca = font->GetTable(OTS_TAG_LOCA); ots::Table *cff = font->GetTable(OTS_TAG_CFF); ots::Table *cff2 = font->GetTable(OTS_TAG_CFF2); + ots::OpenTypeMAXP *maxp = static_cast( + font->GetTypedTable(OTS_TAG_MAXP)); if (glyf && loca) { if (font->version != 0x000010000) { OTS_WARNING_MSG_HDR("wrong sfntVersion for glyph data"); font->version = 0x000010000; } + if (!maxp->version_1) { + return OTS_FAILURE_MSG_TAG("wrong maxp version for glyph data", + OTS_TAG_MAXP); + } if (cff) cff->Drop("font contains both CFF and glyf/loca tables"); if (cff2) @@ -781,6 +787,10 @@ bool ProcessGeneric(ots::FontFile *header, glyf->Drop("font contains both CFF and glyf tables"); if (loca) loca->Drop("font contains both CFF and loca tables"); + if (maxp->version_1) { + OTS_WARNING_MSG_HDR("fixing incorrect maxp version for CFF font"); + maxp->version_1 = false; + } } else if (font->GetTable(OTS_TAG('C','B','D','T')) && font->GetTable(OTS_TAG('C','B','L','C'))) { // We don't sanitize bitmap tables, but don’t reject bitmap-only fonts if