From 39073746216b8c427afd86596c81ed5406c1c71f Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Wed, 18 Oct 2023 09:27:58 -0400 Subject: [PATCH] LibPDF: Implement support for callgsubr in CFF font programs Font programs are bytecode programs defining glyphs. If several glyphs share a piece of outline, that opcode sequence can be put in a subroutine ("subr") table and the definition of those glyphs can then call that subroutine by number, to reduce file size. CFF fonts can in theory contain multiple fonts, and so there's a global subr table shared by all the fonts in one CFF, and a local per-fornt subr table. We used to only implement the local subr table, now we implement both. (We only support one font per CFF, and at least in PDF files, that's all that's ever used. So a global subr table isn't very useful. But the spec explicitly allows it -- "Global subroutines may be used in a FontSet even if it only contains one font." -- and it happens in practice.) --- Userland/Libraries/LibPDF/Fonts/CFF.cpp | 8 +++----- Userland/Libraries/LibPDF/Fonts/CFF.h | 2 +- Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp | 2 +- Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp | 12 +++++++++--- Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h | 2 +- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Userland/Libraries/LibPDF/Fonts/CFF.cpp b/Userland/Libraries/LibPDF/Fonts/CFF.cpp index 5169af8334..91c89393c7 100644 --- a/Userland/Libraries/LibPDF/Fonts/CFF.cpp +++ b/Userland/Libraries/LibPDF/Fonts/CFF.cpp @@ -221,11 +221,9 @@ PDFErrorOr> CFF::create(ReadonlyBytes const& cff_bytes, RefPt TRY(parse_index(reader, [&](ReadonlyBytes const& subroutine_bytes) -> PDFErrorOr { return TRY(global_subroutines.try_append(TRY(ByteBuffer::copy(subroutine_bytes)))); })); - if (!global_subroutines.is_empty()) - dbgln("CFF data contains Global subrs, which aren't implemented yet"); // FIXME // Create glyphs (now that we have the subroutines) and associate missing information to store them and their encoding - auto glyphs = TRY(parse_charstrings(Reader(cff_bytes.slice(charstrings_offset)), local_subroutines)); + auto glyphs = TRY(parse_charstrings(Reader(cff_bytes.slice(charstrings_offset)), local_subroutines, global_subroutines)); // CFF spec, "Table 16 Encoding ID" // FIXME: Only read this if the built-in encoding is actually needed? (ie. `if (!encoding)`) @@ -765,13 +763,13 @@ PDFErrorOr> CFF::parse_charset(Reader&& reader, size return names; } -PDFErrorOr> CFF::parse_charstrings(Reader&& reader, Vector const& subroutines) +PDFErrorOr> CFF::parse_charstrings(Reader&& reader, Vector const& local_subroutines, Vector const& global_subroutines) { // CFF spec, "14 CharStrings INDEX" Vector glyphs; TRY(parse_index(reader, [&](ReadonlyBytes const& charstring_data) -> PDFErrorOr { GlyphParserState state; - auto glyph = TRY(parse_glyph(charstring_data, subroutines, state, true)); + auto glyph = TRY(parse_glyph(charstring_data, local_subroutines, global_subroutines, state, true)); return TRY(glyphs.try_append(glyph)); })); return glyphs; diff --git a/Userland/Libraries/LibPDF/Fonts/CFF.h b/Userland/Libraries/LibPDF/Fonts/CFF.h index 11d0fb305a..fadb013c94 100644 --- a/Userland/Libraries/LibPDF/Fonts/CFF.h +++ b/Userland/Libraries/LibPDF/Fonts/CFF.h @@ -94,7 +94,7 @@ public: static PDFErrorOr> parse_strings(Reader&); - static PDFErrorOr> parse_charstrings(Reader&&, Vector const& subroutines); + static PDFErrorOr> parse_charstrings(Reader&&, Vector const& local_subroutines, Vector const& global_subroutines); static DeprecatedFlyString resolve_sid(SID, Vector const&); static PDFErrorOr> parse_charset(Reader&&, size_t, Vector const&); diff --git a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp index f46f4ab673..0a40383b1a 100644 --- a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp @@ -92,7 +92,7 @@ PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe reader.move_by(encrypted_size); auto glyph_name = word.substring_view(1); GlyphParserState state; - TRY(add_glyph(glyph_name, TRY(parse_glyph(line, subroutines, state, false)))); + TRY(add_glyph(glyph_name, TRY(parse_glyph(line, subroutines, {}, state, false)))); } } } diff --git a/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp index 8e0604f194..003ed0a150 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp @@ -34,7 +34,8 @@ enum Command { RLineCurve, VVCurveTo, HHCurveTo, - VHCurveTo = 30, + CallGsubr = 29, // Type 2 only + VHCurveTo, HVCurveTo }; @@ -133,7 +134,7 @@ void Type1FontProgram::consolidate_glyphs() } } -PDFErrorOr Type1FontProgram::parse_glyph(ReadonlyBytes const& data, Vector const& subroutines, GlyphParserState& state, bool is_type2) +PDFErrorOr Type1FontProgram::parse_glyph(ReadonlyBytes const& data, Vector const& local_subroutines, Vector const& global_subroutines, GlyphParserState& state, bool is_type2) { // Type 1 Font Format: https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf (Chapter 6: CharStrings dictionary) // Type 2 Charstring Format: https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf @@ -371,7 +372,12 @@ PDFErrorOr Type1FontProgram::parse_glyph(ReadonlyBytes state.sp = 0; break; + case CallGsubr: + if (!is_type2) + return error(DeprecatedString::formatted("CFF Gsubr only valid in type2 data")); + [[fallthrough]]; case CallSubr: { + Vector const& subroutines = v == CallSubr ? local_subroutines : global_subroutines; auto subr_number = pop(); if (is_type2) { @@ -420,7 +426,7 @@ PDFErrorOr Type1FontProgram::parse_glyph(ReadonlyBytes if (subr.is_empty()) return error("Empty subroutine"); - TRY(parse_glyph(subr, subroutines, state, is_type2)); + TRY(parse_glyph(subr, local_subroutines, global_subroutines, state, is_type2)); } break; } diff --git a/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h index dadc9c1546..714babb41d 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h +++ b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h @@ -81,7 +81,7 @@ protected: Array postscript_stack; }; - static PDFErrorOr parse_glyph(ReadonlyBytes const&, Vector const&, GlyphParserState&, bool is_type2); + static PDFErrorOr parse_glyph(ReadonlyBytes const&, Vector const& local_subroutines, Vector const& global_subroutines, GlyphParserState&, bool is_type2); static Error error( DeprecatedString const& message