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; +}; + +}