diff --git a/Userland/Libraries/LibPDF/CMakeLists.txt b/Userland/Libraries/LibPDF/CMakeLists.txt index a5a6519012..a9973e6599 100644 --- a/Userland/Libraries/LibPDF/CMakeLists.txt +++ b/Userland/Libraries/LibPDF/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES Encryption.cpp Filter.cpp Fonts/PDFFont.cpp + Fonts/PS1FontProgram.cpp Fonts/TrueTypeFont.cpp Fonts/Type0Font.cpp Fonts/Type1Font.cpp diff --git a/Userland/Libraries/LibPDF/CommonNames.h b/Userland/Libraries/LibPDF/CommonNames.h index a436d1b36e..6f9b613935 100644 --- a/Userland/Libraries/LibPDF/CommonNames.h +++ b/Userland/Libraries/LibPDF/CommonNames.h @@ -61,7 +61,7 @@ A(Font) \ A(FontDescriptor) \ A(FontFamily) \ - A(FontFile1) \ + A(FontFile) \ A(FontFile2) \ A(FontFile3) \ A(Gamma) \ @@ -82,6 +82,9 @@ A(Last) \ A(LastChar) \ A(Length) \ + A(Length1) \ + A(Length2) \ + A(Length3) \ A(Linearized) \ A(ML) \ A(Matrix) \ diff --git a/Userland/Libraries/LibPDF/Encoding.cpp b/Userland/Libraries/LibPDF/Encoding.cpp index ad02369f2e..e8cd5c7ba0 100644 --- a/Userland/Libraries/LibPDF/Encoding.cpp +++ b/Userland/Libraries/LibPDF/Encoding.cpp @@ -11,6 +11,17 @@ namespace PDF { +PDFErrorOr> Encoding::create(HashMap descriptors) +{ + auto encoding = adopt_ref(*new Encoding()); + encoding->m_descriptors = descriptors; + + for (auto& descriptor : descriptors) + encoding->m_name_mapping.set(descriptor.value.name, descriptor.value.code_point); + + return encoding; +} + PDFErrorOr> Encoding::from_object(Document* document, NonnullRefPtr const& obj) { if (obj->is()) { @@ -37,8 +48,9 @@ PDFErrorOr> Encoding::from_object(Document* document, No } auto encoding = adopt_ref(*new Encoding()); - for (auto& [code_point, descriptor] : base_encoding->descriptors()) - encoding->m_descriptors.set(code_point, descriptor); + + encoding->m_descriptors = base_encoding->descriptors(); + encoding->m_name_mapping = base_encoding->name_mapping(); auto differences_array = TRY(dict->get_array(document, CommonNames::Differences)); diff --git a/Userland/Libraries/LibPDF/Encoding.h b/Userland/Libraries/LibPDF/Encoding.h index 6e925430c8..3b176cee5a 100644 --- a/Userland/Libraries/LibPDF/Encoding.h +++ b/Userland/Libraries/LibPDF/Encoding.h @@ -632,6 +632,7 @@ struct CharDescriptor { class Encoding : public RefCounted { public: + static PDFErrorOr> create(HashMap descriptors); static PDFErrorOr> from_object(Document*, NonnullRefPtr const&); static NonnullRefPtr standard_encoding(); @@ -642,6 +643,7 @@ public: static NonnullRefPtr zapf_encoding(); HashMap const& descriptors() const { return m_descriptors; } + HashMap const& name_mapping() const { return m_name_mapping; } CharDescriptor const& get_char_code_descriptor(u16 char_code) const; diff --git a/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp new file mode 100644 index 0000000000..b4d15f1291 --- /dev/null +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp @@ -0,0 +1,599 @@ +/* + * Copyright (c) 2022, Julian Offenhäuser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#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, + Div = 12, + CallOtherSubr = 16, + Pop, + SetCurrentPoint = 33, +}; + +PDFErrorOr PS1FontProgram::parse(ReadonlyBytes const& bytes, size_t cleartext_length, size_t encrypted_length) +{ + Reader reader(bytes); + if (reader.remaining() == 0) + return error("Empty font program"); + + reader.move_to(0); + if (reader.remaining() < 2 || !reader.matches("%!")) + return error("Not a font program"); + + if (!seek_name(reader, CommonNames::Encoding)) + return error("Missing encoding array"); + + if (TRY(parse_word(reader)) == "StandardEncoding") { + m_encoding = Encoding::standard_encoding(); + } else { + HashMap descriptors; + + while (reader.remaining()) { + auto word = TRY(parse_word(reader)); + if (word == "readonly") { + break; + } else if (word == "dup") { + u32 code_point = TRY(parse_int(reader)); + auto name = TRY(parse_word(reader)); + descriptors.set(code_point, { name.starts_with('/') ? name.substring_view(1) : name.view(), code_point }); + } + } + m_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] }; + } else { + m_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); +} + +Gfx::Path PS1FontProgram::build_char(u32 code_point, Gfx::FloatPoint const& point, float width) +{ + if (!m_glyph_map.contains(code_point)) + return {}; + + auto glyph = m_glyph_map.get(code_point).value(); + 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); + transform.set_translation(point); + + return glyph.path.copy_transformed(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: + // 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(String::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(String::formatted("Unhandled command: {}", v)); + } + } + } + + return state.glyph; +} + +PDFErrorOr PS1FontProgram::parse_encrypted_portion(ByteBuffer const& buffer) +{ + Reader reader(buffer); + + if (seek_name(reader, "lenIV")) + m_lenIV = TRY(parse_int(reader)); + + if (!seek_name(reader, "Subrs")) + return error("Missing subroutine array"); + m_subroutines = TRY(parse_subroutines(reader)); + + if (!seek_name(reader, "CharStrings")) + return error("Missing char strings array"); + + while (reader.remaining()) { + auto word = TRY(parse_word(reader)); + VERIFY(!word.is_empty()); + + if (word == "end") + break; + + if (word[0] == '/') { + auto encrypted_size = TRY(parse_int(reader)); + auto rd = TRY(parse_word(reader)); + 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 code_point = name_mapping.ensure(word.substring_view(1)); + GlyphParserState state; + m_glyph_map.set(code_point, TRY(parse_glyph(line, state))); + } + } + } + + return {}; +} + +PDFErrorOr> PS1FontProgram::parse_subroutines(Reader& reader) +{ + if (!reader.matches_number()) + return error("Expected array length"); + + auto length = TRY(parse_int(reader)); + VERIFY(length <= 1024); + + Vector array; + TRY(array.try_resize(length)); + + while (reader.remaining()) { + auto word = TRY(parse_word(reader)); + if (word.is_empty()) + VERIFY(0); + + if (word == "dup") { + auto index = TRY(parse_int(reader)); + auto entry = TRY(parse_word(reader)); + + if (entry.is_empty()) + return error("Empty array entry"); + + if (index >= length) + return error("Array index out of bounds"); + + if (isdigit(entry[0])) { + auto maybe_encrypted_size = entry.to_int(); + if (!maybe_encrypted_size.has_value()) + return error("Malformed array"); + auto rd = TRY(parse_word(reader)); + if (rd == "-|" || rd == "RD") { + array[index] = TRY(decrypt(reader.bytes().slice(reader.offset(), maybe_encrypted_size.value()), m_encryption_key, m_lenIV)); + reader.move_by(maybe_encrypted_size.value()); + } + } else { + array[index] = TRY(ByteBuffer::copy(entry.bytes())); + } + } else if (word == "index") { + break; + } + } + + return array; +} + +PDFErrorOr> PS1FontProgram::parse_number_array(Reader& reader, size_t length) +{ + Vector array; + TRY(array.try_resize(length)); + + reader.consume_whitespace(); + + if (!reader.consume('[')) + return error("Expected array to start with '['"); + + reader.consume_whitespace(); + + for (size_t i = 0; i < length; ++i) + array.at(i) = TRY(parse_float(reader)); + + if (!reader.consume(']')) + return error("Expected array to end with ']'"); + + return array; +} + +PDFErrorOr PS1FontProgram::parse_word(Reader& reader) +{ + reader.consume_whitespace(); + + auto start = reader.offset(); + reader.move_while([&](char c) { + return !reader.matches_whitespace() && c != '[' && c != ']'; + }); + auto end = reader.offset(); + + if (reader.matches_whitespace()) + reader.consume(); + + return StringView(reader.bytes().data() + start, end - start); +} + +PDFErrorOr PS1FontProgram::parse_float(Reader& reader) +{ + auto word = TRY(parse_word(reader)); + return strtof(String(word).characters(), nullptr); +} + +PDFErrorOr PS1FontProgram::parse_int(Reader& reader) +{ + auto maybe_int = TRY(parse_word(reader)).to_int(); + if (!maybe_int.has_value()) + return error("Invalid int"); + return maybe_int.value(); +} + +PDFErrorOr PS1FontProgram::decrypt(ReadonlyBytes const& encrypted, u16 key, size_t skip) +{ + auto decrypted = TRY(ByteBuffer::create_uninitialized(encrypted.size() - skip)); + + u16 R = key; + u16 c1 = 52845; + u16 c2 = 22719; + + for (size_t i = 0; i < encrypted.size(); ++i) { + u8 C = encrypted[i]; + u8 P = C ^ (R >> 8); + R = (C + R) * c1 + c2; + if (i >= skip) + decrypted[i - skip] = P; + } + + return decrypted; +} + +bool PS1FontProgram::seek_name(Reader& reader, String const& name) +{ + auto start = reader.offset(); + + reader.move_to(0); + while (reader.remaining()) { + if (reader.consume('/') && reader.matches(name.characters())) { + // Skip name + reader.move_while([&](char) { + return reader.matches_regular_character(); + }); + reader.consume_whitespace(); + return true; + } + } + + // Jump back to where we started + reader.move_to(start); + return false; +} + +Error PS1FontProgram::error( + String 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 new file mode 100644 index 0000000000..b119f2f19b --- /dev/null +++ b/Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022, Julian Offenhäuser + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace PDF { + +class Reader; +class Encoding; + +class PS1FontProgram : public RefCounted { +public: + PDFErrorOr parse(ReadonlyBytes const&, size_t cleartext_length, size_t encrypted_length); + + Gfx::Path build_char(u32 code_point, Gfx::FloatPoint const& point, float width); + + RefPtr encoding() const { return m_encoding; } + +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; + }; + + 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 decrypt(ReadonlyBytes const&, u16 key, size_t skip); + bool seek_name(Reader&, String const&); + + static Error error( + String 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 4e8327f640..ac15a8837f 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp @@ -1,9 +1,11 @@ /* * Copyright (c) 2022, Matthew Olsson + * Copyright (c) 2022, Julian Offenhäuser * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include @@ -44,9 +46,8 @@ PDFErrorOr Type1Font::parse_data(Document* document, NonnullRef if (is_standard_latin_font(base_font)) { // FIXME: The spec doesn't specify what the encoding should be in this case encoding = Encoding::standard_encoding(); - } else { - TODO(); } + // Otherwise, use the built-in encoding of the font program. } RefPtr to_unicode; @@ -67,8 +68,23 @@ PDFErrorOr Type1Font::parse_data(Document* document, NonnullRef auto descriptor = MUST(dict->get_dict(document, CommonNames::FontDescriptor)); if (descriptor->contains(CommonNames::MissingWidth)) missing_width = descriptor->get_value(CommonNames::MissingWidth).to_int(); + if (!descriptor->contains(CommonNames::FontFile)) + return Type1Font::Data { {}, to_unicode, encoding.release_nonnull(), move(widths), missing_width, true }; - return Type1Font::Data { to_unicode, encoding.release_nonnull(), move(widths), missing_width, false }; + auto font_file_stream = TRY(descriptor->get_stream(document, CommonNames::FontFile)); + auto font_file_dict = font_file_stream->dict(); + + if (!font_file_dict->contains(CommonNames::Length1, CommonNames::Length2)) + return Error { Error::Type::Parse, "Embedded type 1 font is incomplete" }; + + auto length1 = font_file_dict->get_value(CommonNames::Length1).get(); + auto length2 = font_file_dict->get_value(CommonNames::Length2).get(); + + auto font_program = adopt_ref(*new PS1FontProgram()); + TRY(font_program->parse(font_file_stream->bytes(), length1, length2)); + encoding = font_program->encoding(); + + return Type1Font::Data { font_program, to_unicode, encoding.release_nonnull(), move(widths), missing_width, false }; } PDFErrorOr> Type1Font::create(Document* document, NonnullRefPtr dict) @@ -104,4 +120,13 @@ float Type1Font::get_char_width(u16 char_code, float) const return static_cast(width) / 1000.0f; } +void Type1Font::draw_glyph(Gfx::Painter& painter, Gfx::IntPoint const& point, float width, u32 code_point, Color color) +{ + // FIXME: Make a glyph cache + if (m_data.font_program) { + auto path = m_data.font_program->build_char(code_point, { point.x(), point.y() }, width); + Gfx::AntiAliasingPainter aa_painter(painter); + aa_painter.fill_path(path, color, Gfx::Painter::WindingRule::EvenOdd); + } +} } diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.h b/Userland/Libraries/LibPDF/Fonts/Type1Font.h index 75acb2739d..cc44e3076e 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.h +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.h @@ -8,6 +8,7 @@ #include #include +#include namespace PDF { @@ -15,6 +16,7 @@ class Type1Font : public PDFFont { public: // Also used by TrueTypeFont, which is very similar to Type1 struct Data { + RefPtr font_program; RefPtr to_unicode; NonnullRefPtr encoding; HashMap widths; @@ -32,7 +34,7 @@ public: u32 char_code_to_code_point(u16 char_code) const override; float get_char_width(u16 char_code, float font_size) const override; - void draw_glyph(Gfx::Painter&, Gfx::IntPoint const&, float, u32, Color) override {}; + void draw_glyph(Gfx::Painter& painter, Gfx::IntPoint const& point, float width, u32 code_point, Color color) override; Type type() const override { return PDFFont::Type::Type1; } diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index 79b33f7332..48d7d10239 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -643,25 +643,37 @@ void Renderer::show_text(String const& string) { auto& text_rendering_matrix = calculate_text_rendering_matrix(); + auto font_type = text_state().font->type(); auto font_size = text_rendering_matrix.x_scale() * text_state().font_size; - auto font_size_int = static_cast(text_rendering_matrix.x_scale() * text_state().font_size); - auto font = Gfx::FontDatabase::the().get(text_state().font_family, text_state().font_variant, font_size_int); - VERIFY(font); auto glyph_position = text_rendering_matrix.map(Gfx::FloatPoint { 0.0f, 0.0f }); - // Account for the reversed font baseline - glyph_position.set_y(glyph_position.y() - static_cast(font->baseline())); + + RefPtr font; + + // For types other than Type 1 and the standard 14 fonts, use Liberation Serif for now + if (font_type != PDFFont::Type::Type1 || text_state().font->is_standard_font()) { + font = Gfx::FontDatabase::the().get(text_state().font_family, text_state().font_variant, font_size); + VERIFY(font); + + // Account for the reversed font baseline + glyph_position.set_y(glyph_position.y() - static_cast(font->baseline())); + } auto original_position = glyph_position; for (auto char_code : string.bytes()) { auto code_point = text_state().font->char_code_to_code_point(char_code); auto char_width = text_state().font->get_char_width(char_code, font_size); - - if (code_point != 0x20) - m_painter.draw_glyph(glyph_position.to_type(), code_point, *font, state().paint_color); - auto glyph_width = char_width * font_size; + + if (code_point != 0x20) { + if (font.is_null()) { + text_state().font->draw_glyph(m_painter, glyph_position.to_type(), glyph_width, code_point, state().paint_color); + } else { + m_painter.draw_glyph(glyph_position.to_type(), code_point, *font, state().paint_color); + } + } + auto tx = glyph_width; tx += text_state().character_spacing;