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.
This commit is contained in:
Julian Offenhäuser 2022-08-25 11:06:21 +02:00 committed by Andreas Kling
parent e6f29302a7
commit b14f0950a5
Notes: sideshowbarker 2024-07-17 05:40:58 +09:00
9 changed files with 752 additions and 16 deletions

View File

@ -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

View File

@ -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) \

View File

@ -11,6 +11,17 @@
namespace PDF {
PDFErrorOr<NonnullRefPtr<Encoding>> Encoding::create(HashMap<u16, CharDescriptor> 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<NonnullRefPtr<Encoding>> Encoding::from_object(Document* document, NonnullRefPtr<Object> const& obj)
{
if (obj->is<NameObject>()) {
@ -37,8 +48,9 @@ PDFErrorOr<NonnullRefPtr<Encoding>> 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));

View File

@ -632,6 +632,7 @@ struct CharDescriptor {
class Encoding : public RefCounted<Encoding> {
public:
static PDFErrorOr<NonnullRefPtr<Encoding>> create(HashMap<u16, CharDescriptor> descriptors);
static PDFErrorOr<NonnullRefPtr<Encoding>> from_object(Document*, NonnullRefPtr<Object> const&);
static NonnullRefPtr<Encoding> standard_encoding();
@ -642,6 +643,7 @@ public:
static NonnullRefPtr<Encoding> zapf_encoding();
HashMap<u16, CharDescriptor> const& descriptors() const { return m_descriptors; }
HashMap<String, u16> const& name_mapping() const { return m_name_mapping; }
CharDescriptor const& get_char_code_descriptor(u16 char_code) const;

View File

@ -0,0 +1,599 @@
/*
* Copyright (c) 2022, Julian Offenhäuser <offenhaeuser@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibPDF/CommonNames.h>
#include <LibPDF/Encoding.h>
#include <LibPDF/Fonts/PS1FontProgram.h>
#include <LibPDF/Reader.h>
#include <ctype.h>
#include <math.h>
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<void> 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<u16, CharDescriptor> 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::Glyph> PS1FontProgram::parse_glyph(ReadonlyBytes const& data, GlyphParserState& state)
{
auto push = [&](float value) -> PDFErrorOr<void> {
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<void> {
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<size_t>(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<int>(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<void> 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<Vector<ByteBuffer>> PS1FontProgram::parse_subroutines(Reader& reader)
{
if (!reader.matches_number())
return error("Expected array length");
auto length = TRY(parse_int(reader));
VERIFY(length <= 1024);
Vector<ByteBuffer> 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<Vector<float>> PS1FontProgram::parse_number_array(Reader& reader, size_t length)
{
Vector<float> 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<String> 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<float> PS1FontProgram::parse_float(Reader& reader)
{
auto word = TRY(parse_word(reader));
return strtof(String(word).characters(), nullptr);
}
PDFErrorOr<int> 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<ByteBuffer> 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 };
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright (c) 2022, Julian Offenhäuser <offenhaeuser@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Forward.h>
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Path.h>
#include <LibPDF/Error.h>
namespace PDF {
class Reader;
class Encoding;
class PS1FontProgram : public RefCounted<PS1FontProgram> {
public:
PDFErrorOr<void> 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> 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<float, 14> flex_sequence;
size_t sp { 0 };
Array<float, 24> stack;
size_t postscript_sp { 0 };
Array<float, 24> postscript_stack;
};
PDFErrorOr<Glyph> parse_glyph(ReadonlyBytes const&, GlyphParserState&);
PDFErrorOr<void> parse_encrypted_portion(ByteBuffer const&);
PDFErrorOr<Vector<ByteBuffer>> parse_subroutines(Reader&);
PDFErrorOr<Vector<float>> parse_number_array(Reader&, size_t length);
PDFErrorOr<String> parse_word(Reader&);
PDFErrorOr<float> parse_float(Reader&);
PDFErrorOr<int> parse_int(Reader&);
PDFErrorOr<ByteBuffer> 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<ByteBuffer> m_subroutines;
Vector<ByteBuffer> m_character_names;
HashMap<u16, Glyph> m_glyph_map;
Gfx::AffineTransform m_font_matrix;
RefPtr<Encoding> m_encoding;
u16 m_encryption_key { 4330 };
int m_lenIV { 4 };
};
}

View File

@ -1,9 +1,11 @@
/*
* 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/AntiAliasingPainter.h>
#include <LibPDF/CommonNames.h>
#include <LibPDF/Fonts/Type1Font.h>
@ -44,9 +46,8 @@ PDFErrorOr<Type1Font::Data> 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<StreamObject> to_unicode;
@ -67,8 +68,23 @@ PDFErrorOr<Type1Font::Data> 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<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 };
}
PDFErrorOr<NonnullRefPtr<Type1Font>> Type1Font::create(Document* document, NonnullRefPtr<DictObject> dict)
@ -104,4 +120,13 @@ float Type1Font::get_char_width(u16 char_code, float) const
return static_cast<float>(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);
}
}
}

View File

@ -8,6 +8,7 @@
#include <LibPDF/Encoding.h>
#include <LibPDF/Fonts/PDFFont.h>
#include <LibPDF/Fonts/PS1FontProgram.h>
namespace PDF {
@ -15,6 +16,7 @@ class Type1Font : public PDFFont {
public:
// Also used by TrueTypeFont, which is very similar to Type1
struct Data {
RefPtr<PS1FontProgram> font_program;
RefPtr<StreamObject> to_unicode;
NonnullRefPtr<Encoding> encoding;
HashMap<u16, u16> 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; }

View File

@ -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<int>(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<float>(font->baseline()));
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);
if (code_point != 0x20)
m_painter.draw_glyph(glyph_position.to_type<int>(), 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<int>(), glyph_width, code_point, state().paint_color);
} else {
m_painter.draw_glyph(glyph_position.to_type<int>(), code_point, *font, state().paint_color);
}
}
auto tx = glyph_width;
tx += text_state().character_spacing;