From 9cb3b23377316805168b079f9ed6c7dbfca95213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20Offenh=C3=A4user?= Date: Tue, 22 Nov 2022 19:46:29 +0100 Subject: [PATCH] LibPDF: Move all font handling to Type1Font and TrueTypeFont classes It was previously the job of the renderer to create fonts, load replacements for the standard 14 fonts and to pass the font size back to the PDFFont when asking for glyph widths. Now, the renderer tells the font its size at creation, as it doesn't change throughout the life of the font. The PDFFont itself is now responsible to decide whether or not it needs to use a replacement font, which still is Liberation Serif for now. This means that we can now render embedded TrueType fonts as well :^) It also makes the renderer's job much more simple and leads to a much cleaner API design. --- Userland/Libraries/LibPDF/Fonts/PDFFont.cpp | 35 ++++++- Userland/Libraries/LibPDF/Fonts/PDFFont.h | 10 +- .../Libraries/LibPDF/Fonts/TrueTypeFont.cpp | 60 +++++++----- .../Libraries/LibPDF/Fonts/TrueTypeFont.h | 16 ++-- Userland/Libraries/LibPDF/Fonts/Type0Font.cpp | 2 +- Userland/Libraries/LibPDF/Fonts/Type0Font.h | 2 +- Userland/Libraries/LibPDF/Fonts/Type1Font.cpp | 95 +++++-------------- Userland/Libraries/LibPDF/Fonts/Type1Font.h | 15 +-- Userland/Libraries/LibPDF/Renderer.cpp | 51 ++-------- Userland/Libraries/LibPDF/Renderer.h | 4 - 10 files changed, 119 insertions(+), 171 deletions(-) diff --git a/Userland/Libraries/LibPDF/Fonts/PDFFont.cpp b/Userland/Libraries/LibPDF/Fonts/PDFFont.cpp index 5a9711f132..a35cdb05ca 100644 --- a/Userland/Libraries/LibPDF/Fonts/PDFFont.cpp +++ b/Userland/Libraries/LibPDF/Fonts/PDFFont.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -29,10 +30,14 @@ static bool is_standard_latin_font(FlyString const& font) "Courier-BoldOblique"); } -PDFErrorOr PDFFont::CommonData::load_from_dict(Document* document, NonnullRefPtr dict) +PDFErrorOr PDFFont::CommonData::load_from_dict(Document* document, NonnullRefPtr dict, float font_size) { auto base_font = TRY(dict->get_name(document, CommonNames::BaseFont))->name(); - is_standard_font = is_standard_latin_font(base_font); + if ((is_standard_font = is_standard_latin_font(base_font))) { + auto replacement = replacement_for_standard_latin_font(base_font.to_lowercase()); + font = Gfx::FontDatabase::the().get(replacement.get<0>(), replacement.get<1>(), font_size); + VERIFY(font); + } if (dict->contains(CommonNames::Encoding)) { auto encoding_object = MUST(dict->get_object(document, CommonNames::Encoding)); @@ -67,19 +72,39 @@ PDFErrorOr PDFFont::CommonData::load_from_dict(Document* document, Nonnull return {}; } -PDFErrorOr> PDFFont::create(Document* document, NonnullRefPtr dict) +PDFErrorOr> PDFFont::create(Document* document, NonnullRefPtr dict, float font_size) { auto subtype = TRY(dict->get_name(document, CommonNames::Subtype))->name(); if (subtype == "Type0") return TRY(Type0Font::create(document, dict)); if (subtype == "Type1") - return TRY(Type1Font::create(document, dict)); + return TRY(Type1Font::create(document, dict, font_size)); if (subtype == "TrueType") - return TRY(TrueTypeFont::create(document, dict)); + return TRY(TrueTypeFont::create(document, dict, font_size)); dbgln("Unknown font subtype: {}", subtype); TODO(); } +Tuple PDFFont::replacement_for_standard_latin_font(StringView name) +{ + bool is_bold = name.contains("bold"sv); + bool is_italic = name.contains("italic"sv); + + String font_variant; + + if (is_bold && is_italic) { + font_variant = "BoldItalic"; + } else if (is_bold) { + font_variant = "Bold"; + } else if (is_italic) { + font_variant = "Italic"; + } else { + font_variant = "Regular"; + } + + return { "Liberation Serif", font_variant }; +} + } diff --git a/Userland/Libraries/LibPDF/Fonts/PDFFont.h b/Userland/Libraries/LibPDF/Fonts/PDFFont.h index 48823b918c..c6dc8f7509 100644 --- a/Userland/Libraries/LibPDF/Fonts/PDFFont.h +++ b/Userland/Libraries/LibPDF/Fonts/PDFFont.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include #include @@ -23,21 +24,22 @@ public: // This is used both by Type 1 and TrueType fonts. struct CommonData { + RefPtr font; RefPtr to_unicode; RefPtr encoding; HashMap widths; u16 missing_width; bool is_standard_font; - PDFErrorOr load_from_dict(Document*, NonnullRefPtr); + PDFErrorOr load_from_dict(Document*, NonnullRefPtr, float font_size); }; - static PDFErrorOr> create(Document*, NonnullRefPtr); + static PDFErrorOr> create(Document*, NonnullRefPtr, float font_size); virtual ~PDFFont() = default; virtual u32 char_code_to_code_point(u16 char_code) const = 0; - virtual float get_char_width(u16 char_code, float font_size) const = 0; + virtual float get_char_width(u16 char_code) const = 0; virtual void draw_glyph(Gfx::Painter& painter, Gfx::IntPoint const& point, float width, u32 char_code, Color color) = 0; @@ -45,6 +47,8 @@ public: virtual Type type() const = 0; protected: + static Tuple replacement_for_standard_latin_font(StringView); + bool m_is_standard_font { false }; }; diff --git a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp index 69ce4d3ac7..89b7cfc623 100644 --- a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp +++ b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.cpp @@ -1,38 +1,46 @@ /* * Copyright (c) 2022, Matthew Olsson + * Copyright (c) 2022, Julian Offenhäuser * * SPDX-License-Identifier: BSD-2-Clause */ #include +#include +#include #include #include -#include namespace PDF { -PDFErrorOr> TrueTypeFont::create(Document* document, NonnullRefPtr dict) +PDFErrorOr TrueTypeFont::parse_data(Document* document, NonnullRefPtr dict, float font_size) { - auto font_descriptor = TRY(dict->get_dict(document, CommonNames::FontDescriptor)); + PDFFont::CommonData data; + TRY(data.load_from_dict(document, dict, font_size)); - if (!dict->contains(CommonNames::FontFile2)) { - // FIXME: The TTF is one of the standard 14 fonts. These should be built into - // the system, and their attributes hardcoded. Until we have them, just - // treat this as a Type1 font (which are very similar to TTF fonts) - return TRY(Type1Font::create(document, dict)); + if (!data.is_standard_font) { + auto descriptor = MUST(dict->get_dict(document, CommonNames::FontDescriptor)); + if (!descriptor->contains(CommonNames::FontFile2)) + return data; + + auto font_file_stream = TRY(descriptor->get_stream(document, CommonNames::FontFile2)); + auto ttf_font = TRY(TTF::Font::try_load_from_externally_owned_memory(font_file_stream->bytes())); + data.font = adopt_ref(*new Gfx::ScaledFont(*ttf_font, font_size, font_size)); } - auto font_file = TRY(dict->get_stream(document, CommonNames::FontFile2)); - auto ttf_font = TRY(TTF::Font::try_load_from_externally_owned_memory(font_file->bytes())); - auto data = TRY(Type1Font::parse_data(document, dict)); - - return adopt_ref(*new TrueTypeFont(ttf_font, move(data))); + return data; } -TrueTypeFont::TrueTypeFont(NonnullRefPtr ttf_font, Type1Font::Data data) - : m_ttf_font(ttf_font) - , m_data(data) +PDFErrorOr> TrueTypeFont::create(Document* document, NonnullRefPtr dict, float font_size) { + auto data = TRY(parse_data(document, dict, font_size)); + return adopt_ref(*new TrueTypeFont(move(data))); +} + +TrueTypeFont::TrueTypeFont(PDFFont::CommonData data) + : m_data(data) +{ + m_is_standard_font = data.is_standard_font; } u32 TrueTypeFont::char_code_to_code_point(u16 char_code) const @@ -44,23 +52,27 @@ u32 TrueTypeFont::char_code_to_code_point(u16 char_code) const return descriptor.code_point; } -float TrueTypeFont::get_char_width(u16 char_code, float font_size) const +float TrueTypeFont::get_char_width(u16 char_code) const { u16 width; if (auto char_code_width = m_data.widths.get(char_code); char_code_width.has_value()) { width = char_code_width.value(); } else { // FIXME: Should we do something with m_data.missing_width here? - float units_per_em = m_ttf_font->units_per_em(); - auto scale = (font_size * DEFAULT_DPI) / (POINTS_PER_INCH * units_per_em); - - auto code_point = char_code_to_code_point(char_code); - auto id = m_ttf_font->glyph_id_for_code_point(code_point); - auto metrics = m_ttf_font->glyph_metrics(id, scale, scale); - width = metrics.advance_width; + width = m_data.font->glyph_width(char_code); } return static_cast(width) / 1000.0f; } +void TrueTypeFont::draw_glyph(Gfx::Painter& painter, Gfx::IntPoint const& point, float, u32 char_code, Color color) +{ + if (!m_data.font) + return; + + // Account for the reversed font baseline + auto position = point.translated(0, -m_data.font->baseline()); + painter.draw_glyph(position, char_code, *m_data.font, color); +} + } diff --git a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h index b2ffcbce75..68c3df28d4 100644 --- a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h +++ b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h @@ -6,28 +6,30 @@ #pragma once +#include #include -#include +#include namespace PDF { class TrueTypeFont : public PDFFont { public: - static PDFErrorOr> create(Document*, NonnullRefPtr); + static PDFErrorOr parse_data(Document* document, NonnullRefPtr dict, float font_size); - TrueTypeFont(NonnullRefPtr ttf_font, Type1Font::Data); + static PDFErrorOr> create(Document*, NonnullRefPtr, float font_size); + + TrueTypeFont(PDFFont::CommonData); ~TrueTypeFont() override = default; u32 char_code_to_code_point(u16 char_code) const override; - float get_char_width(u16 char_code, float font_size) const override; + float get_char_width(u16 char_code) const override; - void draw_glyph(Gfx::Painter&, Gfx::IntPoint const&, float, u32, Color) override {}; + void draw_glyph(Gfx::Painter&, Gfx::IntPoint const&, float, u32, Color) override; Type type() const override { return PDFFont::Type::TrueType; } private: - NonnullRefPtr m_ttf_font; - Type1Font::Data m_data; + PDFFont::CommonData m_data; }; } diff --git a/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp b/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp index dbebb02610..402cb702cf 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type0Font.cpp @@ -82,7 +82,7 @@ u32 Type0Font::char_code_to_code_point(u16 char_code) const return char_code; } -float Type0Font::get_char_width(u16 char_code, float) const +float Type0Font::get_char_width(u16 char_code) const { u16 width; if (auto char_code_width = m_widths.get(char_code); char_code_width.has_value()) { diff --git a/Userland/Libraries/LibPDF/Fonts/Type0Font.h b/Userland/Libraries/LibPDF/Fonts/Type0Font.h index 9221867d78..08561c60fd 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type0Font.h +++ b/Userland/Libraries/LibPDF/Fonts/Type0Font.h @@ -24,7 +24,7 @@ public: ~Type0Font() override = default; u32 char_code_to_code_point(u16 char_code) const override; - float get_char_width(u16 char_code, float font_size) const override; + float get_char_width(u16 char_code) const override; void draw_glyph(Gfx::Painter&, Gfx::IntPoint const&, float, u32, Color) override {}; diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp index a8b5afc0aa..bc4533bcc1 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.cpp @@ -5,91 +5,42 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include +#include #include #include namespace PDF { -static bool is_standard_latin_font(FlyString const& font) +PDFErrorOr Type1Font::parse_data(Document* document, NonnullRefPtr dict, float font_size) { - return font.is_one_of( - "Times-Roman", - "Helvetica", - "Courier", - "Times-Bold", - "Helvetica-Bold", - "Courier-Bold", - "Times-Italic", - "Helvetica-Oblique", - "Courier-Oblique", - "Times-BoldItalic", - "Helvetica-BoldOblique", - "Courier-BoldOblique"); -} + Type1Font::Data data; + TRY(data.load_from_dict(document, dict, font_size)); -PDFErrorOr Type1Font::parse_data(Document* document, NonnullRefPtr dict) -{ - // FIXME: "Required except for the standard 14 fonts"... - // "Beginning with PDF 1.5, the special treatment given to the standard 14 - // fonts is deprecated. [...] For backwards capability, conforming readers - // shall still provide the special treatment identifier for the standard - // 14 fonts." + if (!data.is_standard_font) { + auto descriptor = TRY(dict->get_dict(document, CommonNames::FontDescriptor)); + if (!descriptor->contains(CommonNames::FontFile)) + return data; - RefPtr encoding; + auto font_file_stream = TRY(descriptor->get_stream(document, CommonNames::FontFile)); + auto font_file_dict = font_file_stream->dict(); - if (dict->contains(CommonNames::Encoding)) { - auto encoding_object = MUST(dict->get_object(document, CommonNames::Encoding)); - encoding = TRY(Encoding::from_object(document, encoding_object)); - } else { - auto base_font = MUST(dict->get_name(document, CommonNames::BaseFont))->name(); - 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(); - } - // Otherwise, use the built-in encoding of the font program. + 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(); + + data.font_program = adopt_ref(*new PS1FontProgram()); + TRY(data.font_program->parse(font_file_stream->bytes(), length1, length2)); + data.encoding = data.font_program->encoding(); } - RefPtr to_unicode; - if (dict->contains(CommonNames::ToUnicode)) - to_unicode = MUST(dict->get_stream(document, CommonNames::ToUnicode)); - - auto first_char = dict->get_value(CommonNames::FirstChar).get(); - auto last_char = dict->get_value(CommonNames::LastChar).get(); - auto widths_array = MUST(dict->get_array(document, CommonNames::Widths)); - - VERIFY(widths_array->size() == static_cast(last_char - first_char + 1)); - - HashMap widths; - for (size_t i = 0; i < widths_array->size(); i++) - widths.set(first_char + i, widths_array->at(i).to_int()); - - u16 missing_width = 0; - 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 }; - - 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 }; + return data; } -PDFErrorOr> Type1Font::create(Document* document, NonnullRefPtr dict) +PDFErrorOr> Type1Font::create(Document* document, NonnullRefPtr dict, float font_size) { - auto data = TRY(Type1Font::parse_data(document, dict)); + auto data = TRY(Type1Font::parse_data(document, dict, font_size)); return adopt_ref(*new Type1Font(data)); } @@ -108,7 +59,7 @@ u32 Type1Font::char_code_to_code_point(u16 char_code) const return descriptor.code_point; } -float Type1Font::get_char_width(u16 char_code, float) const +float Type1Font::get_char_width(u16 char_code) const { u16 width; if (auto char_code_width = m_data.widths.get(char_code); char_code_width.has_value()) { diff --git a/Userland/Libraries/LibPDF/Fonts/Type1Font.h b/Userland/Libraries/LibPDF/Fonts/Type1Font.h index 6be048a69f..a9b07048f1 100644 --- a/Userland/Libraries/LibPDF/Fonts/Type1Font.h +++ b/Userland/Libraries/LibPDF/Fonts/Type1Font.h @@ -6,7 +6,6 @@ #pragma once -#include #include #include @@ -14,25 +13,19 @@ namespace PDF { class Type1Font : public PDFFont { public: - // Also used by TrueTypeFont, which is very similar to Type1 - struct Data { + struct Data : PDFFont::CommonData { RefPtr font_program; - RefPtr to_unicode; - NonnullRefPtr encoding; - HashMap widths; - u16 missing_width; - bool is_standard_font; }; - static PDFErrorOr parse_data(Document*, NonnullRefPtr font_dict); + static PDFErrorOr parse_data(Document*, NonnullRefPtr font_dict, float font_size); - static PDFErrorOr> create(Document*, NonnullRefPtr); + static PDFErrorOr> create(Document*, NonnullRefPtr, float font_size); Type1Font(Data); ~Type1Font() override = default; u32 char_code_to_code_point(u16 char_code) const override; - float get_char_width(u16 char_code, float font_size) const override; + float get_char_width(u16 char_code) const override; void draw_glyph(Gfx::Painter& painter, Gfx::IntPoint const& point, float width, u32 char_code, Color color) override; diff --git a/Userland/Libraries/LibPDF/Renderer.cpp b/Userland/Libraries/LibPDF/Renderer.cpp index b692be016e..df7b5b09f6 100644 --- a/Userland/Libraries/LibPDF/Renderer.cpp +++ b/Userland/Libraries/LibPDF/Renderer.cpp @@ -347,31 +347,13 @@ RENDERER_HANDLER(text_set_font) auto target_font_name = MUST(m_document->resolve_to(args[0]))->name(); auto fonts_dictionary = MUST(m_page.resources->get_dict(m_document, CommonNames::Font)); auto font_dictionary = MUST(fonts_dictionary->get_dict(m_document, target_font_name)); - auto font = TRY(PDFFont::create(m_document, font_dictionary)); - text_state().font = font; - - // FIXME: We do not yet have the standard 14 fonts, as some of them are not open fonts, - // so we just use LiberationSerif for everything - - auto font_name = MUST(font_dictionary->get_name(m_document, CommonNames::BaseFont))->name().to_lowercase(); - auto font_view = font_name.view(); - bool is_bold = font_view.contains("bold"sv); - bool is_italic = font_view.contains("italic"sv); - - String font_variant; - - if (is_bold && is_italic) { - font_variant = "BoldItalic"; - } else if (is_bold) { - font_variant = "Bold"; - } else if (is_italic) { - font_variant = "Italic"; - } else { - font_variant = "Regular"; - } text_state().font_size = args[1].to_float(); - text_state().font_variant = font_variant; + + auto& text_rendering_matrix = calculate_text_rendering_matrix(); + auto font_size = text_rendering_matrix.x_scale() * text_state().font_size; + auto font = TRY(PDFFont::create(m_document, font_dictionary, font_size)); + text_state().font = font; m_text_rendering_matrix_is_dirty = true; return {}; @@ -643,36 +625,19 @@ 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 glyph_position = text_rendering_matrix.map(Gfx::FloatPoint { 0.0f, 0.0f }); - 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); + auto char_width = text_state().font->get_char_width(char_code); 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); - } - } + if (code_point != 0x20) + text_state().font->draw_glyph(m_painter, glyph_position.to_type(), glyph_width, char_code, state().paint_color); auto tx = glyph_width; tx += text_state().character_spacing; diff --git a/Userland/Libraries/LibPDF/Renderer.h b/Userland/Libraries/LibPDF/Renderer.h index 75e57a80c3..9fb36c9d37 100644 --- a/Userland/Libraries/LibPDF/Renderer.h +++ b/Userland/Libraries/LibPDF/Renderer.h @@ -57,8 +57,6 @@ struct TextState { float word_spacing { 0.0f }; float horizontal_scaling { 1.0f }; float leading { 0.0f }; - FlyString font_family { "Liberation Serif" }; - String font_variant { "Regular" }; float font_size { 12.0f }; RefPtr font; TextRenderingMode rendering_mode { TextRenderingMode::Fill }; @@ -221,8 +219,6 @@ struct Formatter : Formatter { builder.appendff(" word_spacing={}\n", state.word_spacing); builder.appendff(" horizontal_scaling={}\n", state.horizontal_scaling); builder.appendff(" leading={}\n", state.leading); - builder.appendff(" font_family={}\n", state.font_family); - builder.appendff(" font_variant={}\n", state.font_variant); builder.appendff(" font_size={}\n", state.font_size); builder.appendff(" rendering_mode={}\n", state.rendering_mode); builder.appendff(" rise={}\n", state.rise);