From 416585f75a49d6ce4544168dd853955a3a1a7026 Mon Sep 17 00:00:00 2001 From: Rodrigo Tobar Date: Sat, 7 Jan 2023 12:21:25 +0800 Subject: [PATCH] LibPDF: Add new Type1FontProgram base class We are planning to add support for CFF fonts to read Type1 fonts, and therefore much of the logic already found in PS1FontProgram will be useful for representing the Type1 fonts read from CFF. This commit moves the PS1-independent bits of PS1FontProgram into a new Type1FontProgram base class that can be used as the base for CFF-based Type1 fonts in the future. The Type1Font class uses this new type now instead of storing a PS1FontProgram pointer. While doing this refactoring I also took care of making some minor adjustments to the PS1FontProgram API, namely: * Its create() method is static and returns a NonnullRefPtr. * Many (all?) of the parse_* methods are now static. * Added const where possible. Notably, the Type1FontProgram also contains at the moment the code that parses the CharString data from the PS1 program. This logic is very similar in CFF files, so after some minor adjustments later on it should be possible to reuse most of it. --- Userland/Libraries/LibPDF/CMakeLists.txt | 1 + .../Libraries/LibPDF/Fonts/PS1FontProgram.cpp | 434 +----------------- .../Libraries/LibPDF/Fonts/PS1FontProgram.h | 61 +-- Userland/Libraries/LibPDF/Fonts/Type1Font.cpp | 4 +- Userland/Libraries/LibPDF/Fonts/Type1Font.h | 4 +- .../LibPDF/Fonts/Type1FontProgram.cpp | 423 +++++++++++++++++ .../Libraries/LibPDF/Fonts/Type1FontProgram.h | 84 ++++ 7 files changed, 535 insertions(+), 476 deletions(-) create mode 100644 Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp create mode 100644 Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h diff --git a/Userland/Libraries/LibPDF/CMakeLists.txt b/Userland/Libraries/LibPDF/CMakeLists.txt index 27106da02f..dc34aca20e 100644 --- a/Userland/Libraries/LibPDF/CMakeLists.txt +++ b/Userland/Libraries/LibPDF/CMakeLists.txt @@ -11,6 +11,7 @@ set(SOURCES Fonts/TrueTypeFont.cpp Fonts/Type0Font.cpp Fonts/Type1Font.cpp + Fonts/Type1FontProgram.cpp Interpolation.cpp ObjectDerivatives.cpp Parser.cpp diff --git a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp index 8a08359fe8..ec2de34fb7 100644 --- a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp @@ -14,38 +14,7 @@ namespace PDF { -enum Command { - HStem = 1, - VStem = 3, - VMoveTo, - RLineTo, - HLineTo, - VLineTo, - RRCurveTo, - ClosePath, - CallSubr, - Return, - Extended, - HSbW, - EndChar, - RMoveTo = 21, - HMoveTo, - VHCurveTo = 30, - HVCurveTo -}; - -enum ExtendedCommand { - DotSection, - VStem3, - HStem3, - Seac = 6, - Div = 12, - CallOtherSubr = 16, - Pop, - SetCurrentPoint = 33, -}; - -PDFErrorOr PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr encoding, size_t cleartext_length, size_t encrypted_length) +PDFErrorOr> PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtr encoding, size_t cleartext_length, size_t encrypted_length) { Reader reader(bytes); if (reader.remaining() == 0) @@ -58,13 +27,14 @@ PDFErrorOr PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtrset_encoding(move(encoding)); } else { if (TRY(parse_word(reader)) == "StandardEncoding") { - m_encoding = Encoding::standard_encoding(); + font_program->set_encoding(Encoding::standard_encoding()); } else { HashMap descriptors; @@ -78,385 +48,21 @@ PDFErrorOr PS1FontProgram::create(ReadonlyBytes const& bytes, RefPtrset_encoding(TRY(Encoding::create(descriptors))); } } bool found_font_matrix = seek_name(reader, "FontMatrix"); if (found_font_matrix) { auto array = TRY(parse_number_array(reader, 6)); - m_font_matrix = { array[0], array[1], array[2], array[3], array[4], array[5] }; + font_program->set_font_matrix({ array[0], array[1], array[2], array[3], array[4], array[5] }); } else { - m_font_matrix = { 0.001f, 0.0f, 0.0f, 0.001f, 0.0f, 0.0f }; + font_program->set_font_matrix({ 0.001f, 0.0f, 0.0f, 0.001f, 0.0f, 0.0f }); } auto decrypted = TRY(decrypt(reader.bytes().slice(cleartext_length, encrypted_length), 55665, 4)); - return parse_encrypted_portion(decrypted); -} - -RefPtr PS1FontProgram::rasterize_glyph(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset) -{ - auto path = build_char(char_code, width, subpixel_offset); - auto bounding_box = path.bounding_box().size(); - - u32 w = (u32)ceilf(bounding_box.width()) + 2; - u32 h = (u32)ceilf(bounding_box.height()) + 2; - - Gfx::PathRasterizer rasterizer(Gfx::IntSize(w, h)); - rasterizer.draw_path(path); - return rasterizer.accumulate(); -} - -Gfx::Path PS1FontProgram::build_char(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset) -{ - auto maybe_glyph = m_glyph_map.get(char_code); - if (!maybe_glyph.has_value()) - return {}; - - auto& glyph = maybe_glyph.value(); - auto transform = Gfx::AffineTransform() - .translate(subpixel_offset.to_float_point()) - .multiply(glyph_transform_to_device_space(glyph, width)); - - // Translate such that the top-left point is at [0, 0]. - auto bounding_box = glyph.path.bounding_box(); - Gfx::FloatPoint translation(-bounding_box.x(), -(bounding_box.y() + bounding_box.height())); - transform.translate(translation); - - return glyph.path.copy_transformed(transform); -} - -Gfx::FloatPoint PS1FontProgram::glyph_translation(u32 char_code, float width) const -{ - auto maybe_glyph = m_glyph_map.get(char_code); - if (!maybe_glyph.has_value()) - return {}; - - auto& glyph = maybe_glyph.value(); - auto transform = glyph_transform_to_device_space(glyph, width); - - // Undo the translation we applied earlier. - auto bounding_box = glyph.path.bounding_box(); - Gfx::FloatPoint translation(bounding_box.x(), bounding_box.y() + bounding_box.height()); - - return transform.map(translation); -} - -Gfx::AffineTransform PS1FontProgram::glyph_transform_to_device_space(Glyph const& glyph, float width) const -{ - auto scale = width / (m_font_matrix.a() * glyph.width + m_font_matrix.e()); - auto transform = m_font_matrix; - - // Convert character space to device space. - transform.scale(scale, -scale); - - return transform; -} - -PDFErrorOr PS1FontProgram::parse_glyph(ReadonlyBytes const& data, GlyphParserState& state) -{ - auto push = [&](float value) -> PDFErrorOr { - if (state.sp >= state.stack.size()) - return error("Operand stack overflow"); - state.stack[state.sp++] = value; - return {}; - }; - - auto pop = [&]() -> float { - return state.sp ? state.stack[--state.sp] : 0.0f; - }; - - auto& path = state.glyph.path; - - // Parse the stream of parameters and commands that make up a glyph outline. - for (size_t i = 0; i < data.size(); ++i) { - auto require = [&](unsigned num) -> PDFErrorOr { - if (i + num >= data.size()) - return error("Malformed glyph outline definition"); - return {}; - }; - - int v = data[i]; - if (v == 255) { - TRY(require(4)); - int a = data[++i]; - int b = data[++i]; - int c = data[++i]; - int d = data[++i]; - TRY(push((a << 24) + (b << 16) + (c << 8) + d)); - } else if (v >= 251) { - TRY(require(1)); - auto w = data[++i]; - TRY(push(-((v - 251) * 256) - w - 108)); - } else if (v >= 247) { - TRY(require(1)); - auto w = data[++i]; - TRY(push(((v - 247) * 256) + w + 108)); - } else if (v >= 32) { - TRY(push(v - 139)); - } else { - // Not a parameter but a command byte. - switch (v) { - case HStem: - case VStem: - state.sp = 0; - break; - - case VMoveTo: { - auto dy = pop(); - - state.point.translate_by(0.0f, dy); - - if (state.flex_feature) { - state.flex_sequence[state.flex_index++] = state.point.x(); - state.flex_sequence[state.flex_index++] = state.point.y(); - } else { - path.move_to(state.point); - } - state.sp = 0; - break; - } - - case RLineTo: { - auto dy = pop(); - auto dx = pop(); - - state.point.translate_by(dx, dy); - path.line_to(state.point); - state.sp = 0; - break; - } - - case HLineTo: { - auto dx = pop(); - - state.point.translate_by(dx, 0.0f); - path.line_to(state.point); - state.sp = 0; - break; - } - - case VLineTo: { - auto dy = pop(); - - state.point.translate_by(0.0f, dy); - path.line_to(state.point); - state.sp = 0; - break; - } - - case RRCurveTo: { - auto dy3 = pop(); - auto dx3 = pop(); - auto dy2 = pop(); - auto dx2 = pop(); - auto dy1 = pop(); - auto dx1 = pop(); - - auto& point = state.point; - - path.cubic_bezier_curve_to( - point + Gfx::FloatPoint(dx1, dy1), - point + Gfx::FloatPoint(dx1 + dx2, dy1 + dy2), - point + Gfx::FloatPoint(dx1 + dx2 + dx3, dy1 + dy2 + dy3)); - - point.translate_by(dx1 + dx2 + dx3, dy1 + dy2 + dy3); - state.sp = 0; - break; - } - - case ClosePath: - path.close(); - state.sp = 0; - break; - - case CallSubr: { - auto subr_number = pop(); - if (static_cast(subr_number) >= m_subroutines.size()) - return error("Subroutine index out of range"); - - // Subroutines 0-2 handle the flex feature. - if (subr_number == 0) { - if (state.flex_index != 14) - break; - - auto& flex = state.flex_sequence; - - path.cubic_bezier_curve_to( - { flex[2], flex[3] }, - { flex[4], flex[5] }, - { flex[6], flex[7] }); - path.cubic_bezier_curve_to( - { flex[8], flex[9] }, - { flex[10], flex[11] }, - { flex[12], flex[13] }); - - state.flex_feature = false; - state.sp = 0; - } else if (subr_number == 1) { - state.flex_feature = true; - state.flex_index = 0; - state.sp = 0; - } else if (subr_number == 2) { - state.sp = 0; - } else { - auto subr = m_subroutines[subr_number]; - if (subr.is_empty()) - return error("Empty subroutine"); - - TRY(parse_glyph(subr, state)); - } - break; - } - - case Return: - break; - - case Extended: { - TRY(require(1)); - switch (data[++i]) { - case DotSection: - case VStem3: - case HStem3: - case Seac: - // FIXME: Do something with these? - state.sp = 0; - break; - - case Div: { - auto num2 = pop(); - auto num1 = pop(); - - TRY(push(num2 ? num1 / num2 : 0.0f)); - break; - } - - case CallOtherSubr: { - auto othersubr_number = pop(); - auto n = static_cast(pop()); - - if (othersubr_number == 0) { - state.postscript_stack[state.postscript_sp++] = pop(); - state.postscript_stack[state.postscript_sp++] = pop(); - pop(); - } else if (othersubr_number == 3) { - state.postscript_stack[state.postscript_sp++] = 3; - } else { - for (int i = 0; i < n; ++i) - state.postscript_stack[state.postscript_sp++] = pop(); - } - - (void)othersubr_number; - break; - } - - case Pop: - TRY(push(state.postscript_stack[--state.postscript_sp])); - break; - - case SetCurrentPoint: { - auto y = pop(); - auto x = pop(); - - state.point = { x, y }; - path.move_to(state.point); - state.sp = 0; - break; - } - - default: - return error(DeprecatedString::formatted("Unhandled command: 12 {}", data[i])); - } - break; - } - - case HSbW: { - auto wx = pop(); - auto sbx = pop(); - - state.glyph.width = wx; - state.point = { sbx, 0.0f }; - state.sp = 0; - break; - } - - case EndChar: - break; - - case RMoveTo: { - auto dy = pop(); - auto dx = pop(); - - state.point.translate_by(dx, dy); - - if (state.flex_feature) { - state.flex_sequence[state.flex_index++] = state.point.x(); - state.flex_sequence[state.flex_index++] = state.point.y(); - } else { - path.move_to(state.point); - } - state.sp = 0; - break; - } - - case HMoveTo: { - auto dx = pop(); - - state.point.translate_by(dx, 0.0f); - - if (state.flex_feature) { - state.flex_sequence[state.flex_index++] = state.point.x(); - state.flex_sequence[state.flex_index++] = state.point.y(); - } else { - path.move_to(state.point); - } - state.sp = 0; - break; - } - - case VHCurveTo: { - auto dx3 = pop(); - auto dy2 = pop(); - auto dx2 = pop(); - auto dy1 = pop(); - - auto& point = state.point; - - path.cubic_bezier_curve_to( - point + Gfx::FloatPoint(0.0f, dy1), - point + Gfx::FloatPoint(dx2, dy1 + dy2), - point + Gfx::FloatPoint(dx2 + dx3, dy1 + dy2)); - - point.translate_by(dx2 + dx3, dy1 + dy2); - state.sp = 0; - break; - } - - case HVCurveTo: { - auto dy3 = pop(); - auto dy2 = pop(); - auto dx2 = pop(); - auto dx1 = pop(); - - auto& point = state.point; - - path.cubic_bezier_curve_to( - point + Gfx::FloatPoint(dx1, 0.0f), - point + Gfx::FloatPoint(dx1 + dx2, dy2), - point + Gfx::FloatPoint(dx1 + dx2, dy2 + dy3)); - - point.translate_by(dx1 + dx2, dy2 + dy3); - state.sp = 0; - break; - } - - default: - return error(DeprecatedString::formatted("Unhandled command: {}", v)); - } - } - } - - return state.glyph; + TRY(font_program->parse_encrypted_portion(decrypted)); + return font_program; } PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffer) @@ -468,7 +74,7 @@ PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe if (!seek_name(reader, "Subrs")) return error("Missing subroutine array"); - m_subroutines = TRY(parse_subroutines(reader)); + auto subroutines = TRY(parse_subroutines(reader)); if (!seek_name(reader, "CharStrings")) return error("Missing char strings array"); @@ -486,10 +92,10 @@ PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe if (rd == "-|" || rd == "RD") { auto line = TRY(decrypt(reader.bytes().slice(reader.offset(), encrypted_size), m_encryption_key, m_lenIV)); reader.move_by(encrypted_size); - auto name_mapping = m_encoding->name_mapping(); + auto name_mapping = encoding()->name_mapping(); auto char_code = name_mapping.ensure(word.substring_view(1)); GlyphParserState state; - m_glyph_map.set(char_code, TRY(parse_glyph(line, state))); + TRY(add_glyph(char_code, TRY(parse_glyph(line, subroutines, state)))); } } } @@ -497,7 +103,7 @@ PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffe return {}; } -PDFErrorOr> PS1FontProgram::parse_subroutines(Reader& reader) +PDFErrorOr> PS1FontProgram::parse_subroutines(Reader& reader) const { if (!reader.matches_number()) return error("Expected array length"); @@ -634,18 +240,4 @@ bool PS1FontProgram::seek_name(Reader& reader, DeprecatedString const& name) return false; } -Error PS1FontProgram::error( - DeprecatedString const& message -#ifdef PDF_DEBUG - , - SourceLocation loc -#endif -) -{ -#ifdef PDF_DEBUG - dbgln("\033[31m{} Type 1 font error: {}\033[0m", loc, message); -#endif - - return Error { Error::Type::MalformedPDF, message }; -} } diff --git a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h index d6711fa48a..b1980c1a6a 100644 --- a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h @@ -11,72 +11,31 @@ #include #include #include +#include namespace PDF { class Reader; class Encoding; -class PS1FontProgram : public RefCounted { +class PS1FontProgram : public Type1FontProgram { public: - PDFErrorOr create(ReadonlyBytes const&, RefPtr, size_t cleartext_length, size_t encrypted_length); - - RefPtr rasterize_glyph(u32 char_code, float width, Gfx::GlyphSubpixelOffset); - Gfx::Path build_char(u32 char_code, float width, Gfx::GlyphSubpixelOffset); - - RefPtr encoding() const { return m_encoding; } - Gfx::FloatPoint glyph_translation(u32 char_code, float width) const; + static PDFErrorOr> create(ReadonlyBytes const&, RefPtr, size_t cleartext_length, size_t encrypted_length); private: - struct Glyph { - Gfx::Path path; - float width; - }; - - struct GlyphParserState { - Glyph glyph; - - Gfx::FloatPoint point; - - bool flex_feature { false }; - size_t flex_index; - Array flex_sequence; - - size_t sp { 0 }; - Array stack; - - size_t postscript_sp { 0 }; - Array postscript_stack; - }; - Gfx::AffineTransform glyph_transform_to_device_space(Glyph const&, float width) const; - PDFErrorOr parse_glyph(ReadonlyBytes const&, GlyphParserState&); PDFErrorOr parse_encrypted_portion(ByteBuffer const&); - PDFErrorOr> parse_subroutines(Reader&); - PDFErrorOr> parse_number_array(Reader&, size_t length); - PDFErrorOr parse_word(Reader&); - PDFErrorOr parse_float(Reader&); - PDFErrorOr parse_int(Reader&); + PDFErrorOr> parse_subroutines(Reader&) const; + static PDFErrorOr> parse_number_array(Reader&, size_t length); + static PDFErrorOr parse_word(Reader&); + static PDFErrorOr parse_float(Reader&); + static PDFErrorOr parse_int(Reader&); - PDFErrorOr decrypt(ReadonlyBytes const&, u16 key, size_t skip); - bool seek_name(Reader&, DeprecatedString const&); + static PDFErrorOr decrypt(ReadonlyBytes const&, u16 key, size_t skip); + static bool seek_name(Reader&, DeprecatedString const&); - static Error error( - DeprecatedString const& message -#ifdef PDF_DEBUG - , - SourceLocation loc = SourceLocation::current() -#endif - ); - - Vector m_subroutines; Vector m_character_names; - HashMap m_glyph_map; - - Gfx::AffineTransform m_font_matrix; - - RefPtr m_encoding; u16 m_encryption_key { 4330 }; int m_lenIV { 4 }; diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp index 50df919e55..76ff822a52 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace PDF { @@ -30,8 +31,7 @@ PDFErrorOr Type1Font::parse_data(Document* document, NonnullRef auto length1 = TRY(document->resolve(font_file_dict->get_value(CommonNames::Length1))).get(); auto length2 = TRY(document->resolve(font_file_dict->get_value(CommonNames::Length2))).get(); - data.font_program = adopt_ref(*new PS1FontProgram()); - TRY(data.font_program->create(font_file_stream->bytes(), data.encoding, length1, length2)); + data.font_program = TRY(PS1FontProgram::create(font_file_stream->bytes(), data.encoding, length1, length2)); if (!data.encoding) data.encoding = data.font_program->encoding(); diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.h b/Userland/Libraries/LibPDF/Fonts/Type1Font.h index a8cd4057a1..7a5224bc18 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.h +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.h @@ -8,14 +8,14 @@ #include #include -#include +#include namespace PDF { class Type1Font : public PDFFont { public: struct Data : PDFFont::CommonData { - RefPtr font_program; + RefPtr font_program; }; static PDFErrorOr parse_data(Document*, NonnullRefPtr font_dict, float font_size); diff --git a/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp new file mode 100644 index 0000000000..17336da39f --- /dev/null +++ b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.cpp @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2023, Rodrigo Tobar . + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace PDF { + +enum Command { + HStem = 1, + VStem = 3, + VMoveTo, + RLineTo, + HLineTo, + VLineTo, + RRCurveTo, + ClosePath, + CallSubr, + Return, + Extended, + HSbW, + EndChar, + RMoveTo = 21, + HMoveTo, + VHCurveTo = 30, + HVCurveTo +}; + +enum ExtendedCommand { + DotSection, + VStem3, + HStem3, + Seac = 6, + Div = 12, + CallOtherSubr = 16, + Pop, + SetCurrentPoint = 33, +}; + +RefPtr Type1FontProgram::rasterize_glyph(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset) +{ + auto path = build_char(char_code, width, subpixel_offset); + auto bounding_box = path.bounding_box().size(); + + u32 w = (u32)ceilf(bounding_box.width()) + 2; + u32 h = (u32)ceilf(bounding_box.height()) + 2; + + Gfx::PathRasterizer rasterizer(Gfx::IntSize(w, h)); + rasterizer.draw_path(path); + return rasterizer.accumulate(); +} + +Gfx::Path Type1FontProgram::build_char(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset) +{ + auto maybe_glyph = m_glyph_map.get(char_code); + if (!maybe_glyph.has_value()) + return {}; + + auto& glyph = maybe_glyph.value(); + auto transform = Gfx::AffineTransform() + .translate(subpixel_offset.to_float_point()) + .multiply(glyph_transform_to_device_space(glyph, width)); + + // Translate such that the top-left point is at [0, 0]. + auto bounding_box = glyph.path.bounding_box(); + Gfx::FloatPoint translation(-bounding_box.x(), -(bounding_box.y() + bounding_box.height())); + transform.translate(translation); + + return glyph.path.copy_transformed(transform); +} + +Gfx::FloatPoint Type1FontProgram::glyph_translation(u32 char_code, float width) const +{ + auto maybe_glyph = m_glyph_map.get(char_code); + if (!maybe_glyph.has_value()) + return {}; + + auto& glyph = maybe_glyph.value(); + auto transform = glyph_transform_to_device_space(glyph, width); + + // Undo the translation we applied earlier. + auto bounding_box = glyph.path.bounding_box(); + Gfx::FloatPoint translation(bounding_box.x(), bounding_box.y() + bounding_box.height()); + + return transform.map(translation); +} + +Gfx::AffineTransform Type1FontProgram::glyph_transform_to_device_space(Glyph const& glyph, float width) const +{ + auto scale = width / (m_font_matrix.a() * glyph.width + m_font_matrix.e()); + auto transform = m_font_matrix; + + // Convert character space to device space. + transform.scale(scale, -scale); + + return transform; +} + +PDFErrorOr Type1FontProgram::parse_glyph(ReadonlyBytes const& data, Vector const& subroutines, GlyphParserState& state) +{ + auto push = [&](float value) -> PDFErrorOr { + if (state.sp >= state.stack.size()) + return error("Operand stack overflow"); + state.stack[state.sp++] = value; + return {}; + }; + + auto pop = [&]() -> float { + return state.sp ? state.stack[--state.sp] : 0.0f; + }; + + auto& path = state.glyph.path; + + // Parse the stream of parameters and commands that make up a glyph outline. + for (size_t i = 0; i < data.size(); ++i) { + auto require = [&](unsigned num) -> PDFErrorOr { + if (i + num >= data.size()) + return error("Malformed glyph outline definition"); + return {}; + }; + + int v = data[i]; + if (v == 255) { + TRY(require(4)); + int a = data[++i]; + int b = data[++i]; + int c = data[++i]; + int d = data[++i]; + TRY(push((a << 24) + (b << 16) + (c << 8) + d)); + } else if (v >= 251) { + TRY(require(1)); + auto w = data[++i]; + TRY(push(-((v - 251) * 256) - w - 108)); + } else if (v >= 247) { + TRY(require(1)); + auto w = data[++i]; + TRY(push(((v - 247) * 256) + w + 108)); + } else if (v >= 32) { + TRY(push(v - 139)); + } else { + // Not a parameter but a command byte. + switch (v) { + case HStem: + case VStem: + state.sp = 0; + break; + + case VMoveTo: { + auto dy = pop(); + + state.point.translate_by(0.0f, dy); + + if (state.flex_feature) { + state.flex_sequence[state.flex_index++] = state.point.x(); + state.flex_sequence[state.flex_index++] = state.point.y(); + } else { + path.move_to(state.point); + } + state.sp = 0; + break; + } + + case RLineTo: { + auto dy = pop(); + auto dx = pop(); + + state.point.translate_by(dx, dy); + path.line_to(state.point); + state.sp = 0; + break; + } + + case HLineTo: { + auto dx = pop(); + + state.point.translate_by(dx, 0.0f); + path.line_to(state.point); + state.sp = 0; + break; + } + + case VLineTo: { + auto dy = pop(); + + state.point.translate_by(0.0f, dy); + path.line_to(state.point); + state.sp = 0; + break; + } + + case RRCurveTo: { + auto dy3 = pop(); + auto dx3 = pop(); + auto dy2 = pop(); + auto dx2 = pop(); + auto dy1 = pop(); + auto dx1 = pop(); + + auto& point = state.point; + + path.cubic_bezier_curve_to( + point + Gfx::FloatPoint(dx1, dy1), + point + Gfx::FloatPoint(dx1 + dx2, dy1 + dy2), + point + Gfx::FloatPoint(dx1 + dx2 + dx3, dy1 + dy2 + dy3)); + + point.translate_by(dx1 + dx2 + dx3, dy1 + dy2 + dy3); + state.sp = 0; + break; + } + + case ClosePath: + path.close(); + state.sp = 0; + break; + + case CallSubr: { + auto subr_number = pop(); + if (static_cast(subr_number) >= subroutines.size()) + return error("Subroutine index out of range"); + + // Subroutines 0-2 handle the flex feature. + if (subr_number == 0) { + if (state.flex_index != 14) + break; + + auto& flex = state.flex_sequence; + + path.cubic_bezier_curve_to( + { flex[2], flex[3] }, + { flex[4], flex[5] }, + { flex[6], flex[7] }); + path.cubic_bezier_curve_to( + { flex[8], flex[9] }, + { flex[10], flex[11] }, + { flex[12], flex[13] }); + + state.flex_feature = false; + state.sp = 0; + } else if (subr_number == 1) { + state.flex_feature = true; + state.flex_index = 0; + state.sp = 0; + } else if (subr_number == 2) { + state.sp = 0; + } else { + auto subr = subroutines[subr_number]; + if (subr.is_empty()) + return error("Empty subroutine"); + + TRY(parse_glyph(subr, subroutines, state)); + } + break; + } + + case Return: + break; + + case Extended: { + TRY(require(1)); + switch (data[++i]) { + case DotSection: + case VStem3: + case HStem3: + case Seac: + // FIXME: Do something with these? + state.sp = 0; + break; + + case Div: { + auto num2 = pop(); + auto num1 = pop(); + + TRY(push(num2 ? num1 / num2 : 0.0f)); + break; + } + + case CallOtherSubr: { + auto othersubr_number = pop(); + auto n = static_cast(pop()); + + if (othersubr_number == 0) { + state.postscript_stack[state.postscript_sp++] = pop(); + state.postscript_stack[state.postscript_sp++] = pop(); + pop(); + } else if (othersubr_number == 3) { + state.postscript_stack[state.postscript_sp++] = 3; + } else { + for (int i = 0; i < n; ++i) + state.postscript_stack[state.postscript_sp++] = pop(); + } + + (void)othersubr_number; + break; + } + + case Pop: + TRY(push(state.postscript_stack[--state.postscript_sp])); + break; + + case SetCurrentPoint: { + auto y = pop(); + auto x = pop(); + + state.point = { x, y }; + path.move_to(state.point); + state.sp = 0; + break; + } + + default: + return error(DeprecatedString::formatted("Unhandled command: 12 {}", data[i])); + } + break; + } + + case HSbW: { + auto wx = pop(); + auto sbx = pop(); + + state.glyph.width = wx; + state.point = { sbx, 0.0f }; + state.sp = 0; + break; + } + + case EndChar: + break; + + case RMoveTo: { + auto dy = pop(); + auto dx = pop(); + + state.point.translate_by(dx, dy); + + if (state.flex_feature) { + state.flex_sequence[state.flex_index++] = state.point.x(); + state.flex_sequence[state.flex_index++] = state.point.y(); + } else { + path.move_to(state.point); + } + state.sp = 0; + break; + } + + case HMoveTo: { + auto dx = pop(); + + state.point.translate_by(dx, 0.0f); + + if (state.flex_feature) { + state.flex_sequence[state.flex_index++] = state.point.x(); + state.flex_sequence[state.flex_index++] = state.point.y(); + } else { + path.move_to(state.point); + } + state.sp = 0; + break; + } + + case VHCurveTo: { + auto dx3 = pop(); + auto dy2 = pop(); + auto dx2 = pop(); + auto dy1 = pop(); + + auto& point = state.point; + + path.cubic_bezier_curve_to( + point + Gfx::FloatPoint(0.0f, dy1), + point + Gfx::FloatPoint(dx2, dy1 + dy2), + point + Gfx::FloatPoint(dx2 + dx3, dy1 + dy2)); + + point.translate_by(dx2 + dx3, dy1 + dy2); + state.sp = 0; + break; + } + + case HVCurveTo: { + auto dy3 = pop(); + auto dy2 = pop(); + auto dx2 = pop(); + auto dx1 = pop(); + + auto& point = state.point; + + path.cubic_bezier_curve_to( + point + Gfx::FloatPoint(dx1, 0.0f), + point + Gfx::FloatPoint(dx1 + dx2, dy2), + point + Gfx::FloatPoint(dx1 + dx2, dy2 + dy3)); + + point.translate_by(dx1 + dx2, dy2 + dy3); + state.sp = 0; + break; + } + + default: + return error(DeprecatedString::formatted("Unhandled command: {}", v)); + } + } + } + + return state.glyph; +} + +Error Type1FontProgram::error( + DeprecatedString const& message +#ifdef PDF_DEBUG + , + SourceLocation loc +#endif +) +{ +#ifdef PDF_DEBUG + dbgln("\033[31m{} Type 1 font error: {}\033[0m", loc, message); +#endif + + return Error { Error::Type::MalformedPDF, message }; +} + +} diff --git a/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h new file mode 100644 index 0000000000..f9a41ccbab --- /dev/null +++ b/Userland/Libraries/LibPDF/Fonts/Type1FontProgram.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023, Rodrigo Tobar . + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace PDF { + +class Encoding; + +class Type1FontProgram : public RefCounted { + +public: + RefPtr rasterize_glyph(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset); + Gfx::FloatPoint glyph_translation(u32 char_code, float width) const; + RefPtr encoding() const { return m_encoding; } + +protected: + struct Glyph { + Gfx::Path path; + float width; + }; + + struct GlyphParserState { + Glyph glyph; + + Gfx::FloatPoint point; + + bool flex_feature { false }; + size_t flex_index; + Array flex_sequence; + + size_t sp { 0 }; + Array stack; + + size_t postscript_sp { 0 }; + Array postscript_stack; + }; + + static PDFErrorOr parse_glyph(ReadonlyBytes const&, Vector const&, GlyphParserState&); + + static Error error( + DeprecatedString const& message +#ifdef PDF_DEBUG + , + SourceLocation loc = SourceLocation::current() +#endif + ); + + void set_encoding(RefPtr&& encoding) + { + m_encoding = move(encoding); + } + + void set_font_matrix(Gfx::AffineTransform&& font_matrix) + { + m_font_matrix = move(font_matrix); + } + + PDFErrorOr add_glyph(u16 char_code, Glyph&& glyph) + { + TRY(m_glyph_map.try_set(char_code, glyph)); + return {}; + } + +private: + HashMap m_glyph_map; + Gfx::AffineTransform m_font_matrix; + RefPtr m_encoding; + + Gfx::Path build_char(u32 char_code, float width, Gfx::GlyphSubpixelOffset subpixel_offset); + Gfx::AffineTransform glyph_transform_to_device_space(Glyph const& glyph, float width) const; +}; + +}