From b14f0950a56a90da085d065f78aba67f2503014d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Offenh=C3=A4user?= Date: Thu, 25 Aug 2022 11:06:21 +0200 Subject: [PATCH] LibPDF: Add very basic support for Adobe Type 1 font rendering Previously we would draw all text, no matter what font type, as Liberation Serif, which results in things like ugly character spacing. We now have partial support for drawing Type 1 glyphs, which are part of a PostScript font program. We completely ignore hinting for now, which results in ugly looking characters at low resolutions, but gain support for a large number of typefaces, including most of the default fonts used in TeX. --- Userland/Libraries/LibPDF/CMakeLists.txt | 1 + Userland/Libraries/LibPDF/CommonNames.h | 5 +- Userland/Libraries/LibPDF/Encoding.cpp | 16 +- Userland/Libraries/LibPDF/Encoding.h | 2 + .../Libraries/LibPDF/Fonts/PS1FontProgram.cpp | 599 ++++++++++++++++++ .../Libraries/LibPDF/Fonts/PS1FontProgram.h | 80 +++ Userland/Libraries/LibPDF/Fonts/Type1Font.cpp | 31 +- Userland/Libraries/LibPDF/Fonts/Type1Font.h | 4 +- Userland/Libraries/LibPDF/Renderer.cpp | 30 +- 9 files changed, 752 insertions(+), 16 deletions(-) create mode 100644 Userland/Libraries/LibPDF/Fonts/PS1FontProgram.cpp create mode 100644 Userland/Libraries/LibPDF/Fonts/PS1FontProgram.h 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;