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.
This commit is contained in:
Julian Offenhäuser 2022-11-22 19:46:29 +01:00 committed by Andreas Kling
parent e748a94f80
commit 9cb3b23377
Notes: sideshowbarker 2024-07-17 04:08:29 +09:00
10 changed files with 119 additions and 171 deletions

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Font/FontDatabase.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Fonts/PDFFont.h>
#include <LibPDF/Fonts/TrueTypeFont.h>
@ -29,10 +30,14 @@ static bool is_standard_latin_font(FlyString const& font)
"Courier-BoldOblique");
}
PDFErrorOr<void> PDFFont::CommonData::load_from_dict(Document* document, NonnullRefPtr<DictObject> dict)
PDFErrorOr<void> PDFFont::CommonData::load_from_dict(Document* document, NonnullRefPtr<DictObject> 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<void> PDFFont::CommonData::load_from_dict(Document* document, Nonnull
return {};
}
PDFErrorOr<NonnullRefPtr<PDFFont>> PDFFont::create(Document* document, NonnullRefPtr<DictObject> dict)
PDFErrorOr<NonnullRefPtr<PDFFont>> PDFFont::create(Document* document, NonnullRefPtr<DictObject> 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<String, String> 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 };
}
}

View file

@ -7,6 +7,7 @@
#pragma once
#include <AK/HashMap.h>
#include <AK/Tuple.h>
#include <LibGfx/Forward.h>
#include <LibPDF/Document.h>
#include <LibPDF/Encoding.h>
@ -23,21 +24,22 @@ public:
// This is used both by Type 1 and TrueType fonts.
struct CommonData {
RefPtr<Gfx::Font> font;
RefPtr<StreamObject> to_unicode;
RefPtr<Encoding> encoding;
HashMap<u16, u16> widths;
u16 missing_width;
bool is_standard_font;
PDFErrorOr<void> load_from_dict(Document*, NonnullRefPtr<DictObject>);
PDFErrorOr<void> load_from_dict(Document*, NonnullRefPtr<DictObject>, float font_size);
};
static PDFErrorOr<NonnullRefPtr<PDFFont>> create(Document*, NonnullRefPtr<DictObject>);
static PDFErrorOr<NonnullRefPtr<PDFFont>> create(Document*, NonnullRefPtr<DictObject>, 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<String, String> replacement_for_standard_latin_font(StringView);
bool m_is_standard_font { false };
};

View file

@ -1,38 +1,46 @@
/*
* Copyright (c) 2022, Matthew Olsson <mattco@serenityos.org>
* Copyright (c) 2022, Julian Offenhäuser <offenhaeuser@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/TrueType/Font.h>
#include <LibGfx/Painter.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Fonts/TrueTypeFont.h>
#include <LibPDF/Fonts/Type1Font.h>
namespace PDF {
PDFErrorOr<NonnullRefPtr<PDFFont>> TrueTypeFont::create(Document* document, NonnullRefPtr<DictObject> dict)
PDFErrorOr<PDFFont::CommonData> TrueTypeFont::parse_data(Document* document, NonnullRefPtr<DictObject> 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> ttf_font, Type1Font::Data data)
: m_ttf_font(ttf_font)
, m_data(data)
PDFErrorOr<NonnullRefPtr<PDFFont>> TrueTypeFont::create(Document* document, NonnullRefPtr<DictObject> 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<float>(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);
}
}

View file

@ -6,28 +6,30 @@
#pragma once
#include <AK/HashMap.h>
#include <LibGfx/Font/TrueType/Font.h>
#include <LibPDF/Fonts/Type1Font.h>
#include <LibPDF/Fonts/PDFFont.h>
namespace PDF {
class TrueTypeFont : public PDFFont {
public:
static PDFErrorOr<NonnullRefPtr<PDFFont>> create(Document*, NonnullRefPtr<DictObject>);
static PDFErrorOr<PDFFont::CommonData> parse_data(Document* document, NonnullRefPtr<DictObject> dict, float font_size);
TrueTypeFont(NonnullRefPtr<TTF::Font> ttf_font, Type1Font::Data);
static PDFErrorOr<NonnullRefPtr<PDFFont>> create(Document*, NonnullRefPtr<DictObject>, 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<TTF::Font> m_ttf_font;
Type1Font::Data m_data;
PDFFont::CommonData m_data;
};
}

View file

@ -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()) {

View file

@ -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 {};

View file

@ -5,91 +5,42 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/Painter.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Fonts/Type1Font.h>
namespace PDF {
static bool is_standard_latin_font(FlyString const& font)
PDFErrorOr<Type1Font::Data> Type1Font::parse_data(Document* document, NonnullRefPtr<DictObject> 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::Data> Type1Font::parse_data(Document* document, NonnullRefPtr<DictObject> 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> 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<int>();
auto length2 = font_file_dict->get_value(CommonNames::Length2).get<int>();
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<StreamObject> 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<int>();
auto last_char = dict->get_value(CommonNames::LastChar).get<int>();
auto widths_array = MUST(dict->get_array(document, CommonNames::Widths));
VERIFY(widths_array->size() == static_cast<size_t>(last_char - first_char + 1));
HashMap<u16, u16> 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<int>();
auto length2 = font_file_dict->get_value(CommonNames::Length2).get<int>();
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<NonnullRefPtr<Type1Font>> Type1Font::create(Document* document, NonnullRefPtr<DictObject> dict)
PDFErrorOr<NonnullRefPtr<Type1Font>> Type1Font::create(Document* document, NonnullRefPtr<DictObject> 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()) {

View file

@ -6,7 +6,6 @@
#pragma once
#include <LibPDF/Encoding.h>
#include <LibPDF/Fonts/PDFFont.h>
#include <LibPDF/Fonts/PS1FontProgram.h>
@ -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<PS1FontProgram> font_program;
RefPtr<StreamObject> to_unicode;
NonnullRefPtr<Encoding> encoding;
HashMap<u16, u16> widths;
u16 missing_width;
bool is_standard_font;
};
static PDFErrorOr<Data> parse_data(Document*, NonnullRefPtr<DictObject> font_dict);
static PDFErrorOr<Data> parse_data(Document*, NonnullRefPtr<DictObject> font_dict, float font_size);
static PDFErrorOr<NonnullRefPtr<Type1Font>> create(Document*, NonnullRefPtr<DictObject>);
static PDFErrorOr<NonnullRefPtr<Type1Font>> create(Document*, NonnullRefPtr<DictObject>, 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;

View file

@ -347,31 +347,13 @@ RENDERER_HANDLER(text_set_font)
auto target_font_name = MUST(m_document->resolve_to<NameObject>(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<Gfx::Font> 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<float>(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<int>(), glyph_width, code_point, state().paint_color);
} else {
m_painter.draw_glyph(glyph_position.to_type<int>(), code_point, *font, state().paint_color);
}
}
if (code_point != 0x20)
text_state().font->draw_glyph(m_painter, glyph_position.to_type<int>(), glyph_width, char_code, state().paint_color);
auto tx = glyph_width;
tx += text_state().character_spacing;

View file

@ -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<PDFFont> font;
TextRenderingMode rendering_mode { TextRenderingMode::Fill };
@ -221,8 +219,6 @@ struct Formatter<PDF::TextState> : Formatter<StringView> {
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);