summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGfx
diff options
context:
space:
mode:
authorAndrew Kaster <akaster@serenityos.org>2021-07-26 00:08:22 -0600
committerLinus Groh <mail@linusgroh.de>2021-07-29 21:46:25 +0100
commitaf9be6e093c571aa9afbbfc6276572bba0aa2ff4 (patch)
tree2ef679858ad2015d3c16a836c12913583be10a6a /Userland/Libraries/LibGfx
parent08ddfb30d76844efafc82f5727991b78cd16cc6b (diff)
downloadserenity-af9be6e093c571aa9afbbfc6276572bba0aa2ff4.zip
LibTTF/LibGfx: Remove circular dependency by merging LibTTF into LibGfx
LibTTF has a concrete dependency on LibGfx for things like Gfx::Bitmap, and LibGfx has a concrete dependency in the TTF::Font class in Gfx::FontDatabase. This circular dependency works fine for Serenity and Lagom Linux builds of the two libraries. It also works fine for static library builds on Lagom macOS builds. However, future changes will make Lagom use shared libraries, and circular library dependencies are not tolerated in macOS.
Diffstat (limited to 'Userland/Libraries/LibGfx')
-rw-r--r--Userland/Libraries/LibGfx/CMakeLists.txt5
-rw-r--r--Userland/Libraries/LibGfx/FontDatabase.cpp2
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp154
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h111
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp578
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Font.h160
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp503
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h156
-rw-r--r--Userland/Libraries/LibGfx/TrueTypeFont/Tables.h197
-rw-r--r--Userland/Libraries/LibGfx/Typeface.h2
10 files changed, 1865 insertions, 3 deletions
diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt
index fc9b6a42e5..b16f1caf50 100644
--- a/Userland/Libraries/LibGfx/CMakeLists.txt
+++ b/Userland/Libraries/LibGfx/CMakeLists.txt
@@ -33,9 +33,12 @@ set(SOURCES
TextDirection.cpp
TextLayout.cpp
Triangle.cpp
+ TrueTypeFont/Font.cpp
+ TrueTypeFont/Glyf.cpp
+ TrueTypeFont/Cmap.cpp
Typeface.cpp
WindowTheme.cpp
)
serenity_lib(LibGfx gfx)
-target_link_libraries(LibGfx LibM LibCompress LibCore LibTTF)
+target_link_libraries(LibGfx LibM LibCompress LibCore LibTextCodec)
diff --git a/Userland/Libraries/LibGfx/FontDatabase.cpp b/Userland/Libraries/LibGfx/FontDatabase.cpp
index 555212f170..68b16547f0 100644
--- a/Userland/Libraries/LibGfx/FontDatabase.cpp
+++ b/Userland/Libraries/LibGfx/FontDatabase.cpp
@@ -9,8 +9,8 @@
#include <LibCore/DirIterator.h>
#include <LibGfx/Font.h>
#include <LibGfx/FontDatabase.h>
+#include <LibGfx/TrueTypeFont/Font.h>
#include <LibGfx/Typeface.h>
-#include <LibTTF/Font.h>
#include <stdlib.h>
namespace Gfx {
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp b/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp
new file mode 100644
index 0000000000..cd5d281466
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Optional.h>
+#include <LibGfx/TrueTypeFont/Cmap.h>
+
+namespace TTF {
+
+extern u16 be_u16(u8 const*);
+extern u32 be_u32(u8 const*);
+extern i16 be_i16(u8 const*);
+
+Cmap::Subtable::Platform Cmap::Subtable::platform_id() const
+{
+ switch (m_raw_platform_id) {
+ case 0:
+ return Platform::Unicode;
+ case 1:
+ return Platform::Macintosh;
+ case 3:
+ return Platform::Windows;
+ case 4:
+ return Platform::Custom;
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+Cmap::Subtable::Format Cmap::Subtable::format() const
+{
+ switch (be_u16(m_slice.offset_pointer(0))) {
+ case 0:
+ return Format::ByteEncoding;
+ case 2:
+ return Format::HighByte;
+ case 4:
+ return Format::SegmentToDelta;
+ case 6:
+ return Format::TrimmedTable;
+ case 8:
+ return Format::Mixed16And32;
+ case 10:
+ return Format::TrimmedArray;
+ case 12:
+ return Format::SegmentedCoverage;
+ case 13:
+ return Format::ManyToOneRange;
+ case 14:
+ return Format::UnicodeVariationSequences;
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+u32 Cmap::num_subtables() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::NumTables));
+}
+
+Optional<Cmap::Subtable> Cmap::subtable(u32 index) const
+{
+ if (index >= num_subtables()) {
+ return {};
+ }
+ u32 record_offset = (u32)Sizes::TableHeader + index * (u32)Sizes::EncodingRecord;
+ u16 platform_id = be_u16(m_slice.offset_pointer(record_offset));
+ u16 encoding_id = be_u16(m_slice.offset_pointer(record_offset + (u32)Offsets::EncodingRecord_EncodingID));
+ u32 subtable_offset = be_u32(m_slice.offset_pointer(record_offset + (u32)Offsets::EncodingRecord_Offset));
+ VERIFY(subtable_offset < m_slice.size());
+ auto subtable_slice = ReadonlyBytes(m_slice.offset_pointer(subtable_offset), m_slice.size() - subtable_offset);
+ return Subtable(subtable_slice, platform_id, encoding_id);
+}
+
+// FIXME: This only handles formats 4 (SegmentToDelta) and 12 (SegmentedCoverage) for now.
+u32 Cmap::Subtable::glyph_id_for_code_point(u32 code_point) const
+{
+ switch (format()) {
+ case Format::SegmentToDelta:
+ return glyph_id_for_code_point_table_4(code_point);
+ case Format::SegmentedCoverage:
+ return glyph_id_for_code_point_table_12(code_point);
+ default:
+ return 0;
+ }
+}
+
+u32 Cmap::Subtable::glyph_id_for_code_point_table_4(u32 code_point) const
+{
+ u32 segcount_x2 = be_u16(m_slice.offset_pointer((u32)Table4Offsets::SegCountX2));
+ if (m_slice.size() < segcount_x2 * (u32)Table4Sizes::NonConstMultiplier + (u32)Table4Sizes::Constant) {
+ return 0;
+ }
+ for (u32 offset = 0; offset < segcount_x2; offset += 2) {
+ u32 end_code_point = be_u16(m_slice.offset_pointer((u32)Table4Offsets::EndConstBase + offset));
+ if (code_point > end_code_point) {
+ continue;
+ }
+ u32 start_code_point = be_u16(m_slice.offset_pointer((u32)Table4Offsets::StartConstBase + segcount_x2 + offset));
+ if (code_point < start_code_point) {
+ break;
+ }
+ u32 delta = be_u16(m_slice.offset_pointer((u32)Table4Offsets::DeltaConstBase + segcount_x2 * 2 + offset));
+ u32 range = be_u16(m_slice.offset_pointer((u32)Table4Offsets::RangeConstBase + segcount_x2 * 3 + offset));
+ if (range == 0) {
+ return (code_point + delta) & 0xffff;
+ }
+ u32 glyph_offset = (u32)Table4Offsets::GlyphOffsetConstBase + segcount_x2 * 3 + offset + range + (code_point - start_code_point) * 2;
+ VERIFY(glyph_offset + 2 <= m_slice.size());
+ return (be_u16(m_slice.offset_pointer(glyph_offset)) + delta) & 0xffff;
+ }
+ return 0;
+}
+
+u32 Cmap::Subtable::glyph_id_for_code_point_table_12(u32 code_point) const
+{
+ u32 num_groups = be_u32(m_slice.offset_pointer((u32)Table12Offsets::NumGroups));
+ VERIFY(m_slice.size() >= (u32)Table12Sizes::Header + (u32)Table12Sizes::Record * num_groups);
+ for (u32 offset = 0; offset < num_groups * (u32)Table12Sizes::Record; offset += (u32)Table12Sizes::Record) {
+ u32 start_code_point = be_u32(m_slice.offset_pointer((u32)Table12Offsets::Record_StartCode + offset));
+ if (code_point < start_code_point) {
+ break;
+ }
+ u32 end_code_point = be_u32(m_slice.offset_pointer((u32)Table12Offsets::Record_EndCode + offset));
+ if (code_point > end_code_point) {
+ continue;
+ }
+ u32 glyph_offset = be_u32(m_slice.offset_pointer((u32)Table12Offsets::Record_StartGlyph + offset));
+ return code_point - start_code_point + glyph_offset;
+ }
+ return 0;
+}
+
+u32 Cmap::glyph_id_for_code_point(u32 code_point) const
+{
+ auto opt_subtable = subtable(m_active_index);
+ if (!opt_subtable.has_value()) {
+ return 0;
+ }
+ auto subtable = opt_subtable.value();
+ return subtable.glyph_id_for_code_point(code_point);
+}
+
+Optional<Cmap> Cmap::from_slice(ReadonlyBytes const& slice)
+{
+ if (slice.size() < (size_t)Sizes::TableHeader) {
+ return {};
+ }
+ return Cmap(slice);
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h b/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h
new file mode 100644
index 0000000000..3b8dc16b0d
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <stdint.h>
+
+namespace TTF {
+
+class Cmap {
+public:
+ class Subtable {
+ public:
+ enum class Platform {
+ Unicode = 0,
+ Macintosh = 1,
+ Windows = 3,
+ Custom = 4,
+ };
+ enum class Format {
+ ByteEncoding = 0,
+ HighByte = 2,
+ SegmentToDelta = 4,
+ TrimmedTable = 6,
+ Mixed16And32 = 8,
+ TrimmedArray = 10,
+ SegmentedCoverage = 12,
+ ManyToOneRange = 13,
+ UnicodeVariationSequences = 14,
+ };
+ enum class WindowsEncoding {
+ UnicodeBMP = 1,
+ UnicodeFullRepertoire = 10,
+ };
+
+ Subtable(ReadonlyBytes const& slice, u16 platform_id, u16 encoding_id)
+ : m_slice(slice)
+ , m_raw_platform_id(platform_id)
+ , m_encoding_id(encoding_id)
+ {
+ }
+ // Returns 0 if glyph not found. This corresponds to the "missing glyph"
+ u32 glyph_id_for_code_point(u32 code_point) const;
+ Platform platform_id() const;
+ u16 encoding_id() const { return m_encoding_id; }
+ Format format() const;
+
+ private:
+ enum class Table4Offsets {
+ SegCountX2 = 6,
+ EndConstBase = 14,
+ StartConstBase = 16,
+ DeltaConstBase = 16,
+ RangeConstBase = 16,
+ GlyphOffsetConstBase = 16,
+ };
+ enum class Table4Sizes {
+ Constant = 16,
+ NonConstMultiplier = 4,
+ };
+ enum class Table12Offsets {
+ NumGroups = 12,
+ Record_StartCode = 16,
+ Record_EndCode = 20,
+ Record_StartGlyph = 24,
+ };
+ enum class Table12Sizes {
+ Header = 16,
+ Record = 12,
+ };
+
+ u32 glyph_id_for_code_point_table_4(u32 code_point) const;
+ u32 glyph_id_for_code_point_table_12(u32 code_point) const;
+
+ ReadonlyBytes m_slice;
+ u16 m_raw_platform_id { 0 };
+ u16 m_encoding_id { 0 };
+ };
+
+ static Optional<Cmap> from_slice(ReadonlyBytes const&);
+ u32 num_subtables() const;
+ Optional<Subtable> subtable(u32 index) const;
+ void set_active_index(u32 index) { m_active_index = index; }
+ // Returns 0 if glyph not found. This corresponds to the "missing glyph"
+ u32 glyph_id_for_code_point(u32 code_point) const;
+
+private:
+ enum class Offsets {
+ NumTables = 2,
+ EncodingRecord_EncodingID = 2,
+ EncodingRecord_Offset = 4,
+ };
+ enum class Sizes {
+ TableHeader = 4,
+ EncodingRecord = 8,
+ };
+
+ Cmap(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+ u32 m_active_index { UINT32_MAX };
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp b/Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp
new file mode 100644
index 0000000000..b60290bec4
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp
@@ -0,0 +1,578 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ * Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Checked.h>
+#include <AK/MappedFile.h>
+#include <AK/Utf32View.h>
+#include <AK/Utf8View.h>
+#include <LibCore/File.h>
+#include <LibGfx/FontDatabase.h>
+#include <LibGfx/TrueTypeFont/Cmap.h>
+#include <LibGfx/TrueTypeFont/Font.h>
+#include <LibGfx/TrueTypeFont/Glyf.h>
+#include <LibGfx/TrueTypeFont/Tables.h>
+#include <LibTextCodec/Decoder.h>
+#include <math.h>
+#include <sys/mman.h>
+
+namespace TTF {
+
+u16 be_u16(u8 const*);
+u32 be_u32(u8 const*);
+i16 be_i16(u8 const*);
+float be_fword(u8 const*);
+u32 tag_from_str(char const*);
+
+u16 be_u16(u8 const* ptr)
+{
+ return (((u16)ptr[0]) << 8) | ((u16)ptr[1]);
+}
+
+u32 be_u32(u8 const* ptr)
+{
+ return (((u32)ptr[0]) << 24) | (((u32)ptr[1]) << 16) | (((u32)ptr[2]) << 8) | ((u32)ptr[3]);
+}
+
+i16 be_i16(u8 const* ptr)
+{
+ return (((i16)ptr[0]) << 8) | ((i16)ptr[1]);
+}
+
+float be_fword(u8 const* ptr)
+{
+ return (float)be_i16(ptr) / (float)(1 << 14);
+}
+
+u32 tag_from_str(char const* str)
+{
+ return be_u32((u8 const*)str);
+}
+
+Optional<Head> Head::from_slice(ReadonlyBytes const& slice)
+{
+ if (slice.size() < (size_t)Sizes::Table) {
+ return {};
+ }
+ return Head(slice);
+}
+
+u16 Head::units_per_em() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::UnitsPerEM));
+}
+
+i16 Head::xmin() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::XMin));
+}
+
+i16 Head::ymin() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::YMin));
+}
+
+i16 Head::xmax() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::XMax));
+}
+
+i16 Head::ymax() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::YMax));
+}
+
+u16 Head::lowest_recommended_ppem() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::LowestRecPPEM));
+}
+
+IndexToLocFormat Head::index_to_loc_format() const
+{
+ i16 raw = be_i16(m_slice.offset_pointer((u32)Offsets::IndexToLocFormat));
+ switch (raw) {
+ case 0:
+ return IndexToLocFormat::Offset16;
+ case 1:
+ return IndexToLocFormat::Offset32;
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+Optional<Hhea> Hhea::from_slice(ReadonlyBytes const& slice)
+{
+ if (slice.size() < (size_t)Sizes::Table) {
+ return {};
+ }
+ return Hhea(slice);
+}
+
+i16 Hhea::ascender() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::Ascender));
+}
+
+i16 Hhea::descender() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::Descender));
+}
+
+i16 Hhea::line_gap() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::LineGap));
+}
+
+u16 Hhea::advance_width_max() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::AdvanceWidthMax));
+}
+
+u16 Hhea::number_of_h_metrics() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::NumberOfHMetrics));
+}
+
+Optional<Maxp> Maxp::from_slice(ReadonlyBytes const& slice)
+{
+ if (slice.size() < (size_t)Sizes::TableV0p5) {
+ return {};
+ }
+ return Maxp(slice);
+}
+
+u16 Maxp::num_glyphs() const
+{
+ return be_u16(m_slice.offset_pointer((u32)Offsets::NumGlyphs));
+}
+
+Optional<Hmtx> Hmtx::from_slice(ReadonlyBytes const& slice, u32 num_glyphs, u32 number_of_h_metrics)
+{
+ if (slice.size() < number_of_h_metrics * (u32)Sizes::LongHorMetric + (num_glyphs - number_of_h_metrics) * (u32)Sizes::LeftSideBearing) {
+ return {};
+ }
+ return Hmtx(slice, num_glyphs, number_of_h_metrics);
+}
+
+Optional<Name> Name::from_slice(ReadonlyBytes const& slice)
+{
+ return Name(slice);
+}
+
+String Name::string_for_id(NameId id) const
+{
+ auto num_entries = be_u16(m_slice.offset_pointer(2));
+ auto string_offset = be_u16(m_slice.offset_pointer(4));
+
+ Vector<int> valid_ids;
+
+ for (int i = 0; i < num_entries; ++i) {
+ auto this_id = be_u16(m_slice.offset_pointer(6 + i * 12 + 6));
+ if (this_id == (u16)id)
+ valid_ids.append(i);
+ }
+
+ if (valid_ids.is_empty())
+ return String::empty();
+
+ auto it = valid_ids.find_if([this](auto const& i) {
+ // check if font has naming table for en-US language id
+ auto platform = be_u16(m_slice.offset_pointer(6 + i * 12 + 0));
+ auto language_id = be_u16(m_slice.offset_pointer(6 + i * 12 + 4));
+ return (platform == (u16)Platform::Macintosh && language_id == (u16)MacintoshLanguage::English)
+ || (platform == (u16)Platform::Windows && language_id == (u16)WindowsLanguage::EnglishUnitedStates);
+ });
+ auto i = it != valid_ids.end() ? *it : valid_ids.first();
+
+ auto platform = be_u16(m_slice.offset_pointer(6 + i * 12 + 0));
+ auto length = be_u16(m_slice.offset_pointer(6 + i * 12 + 8));
+ auto offset = be_u16(m_slice.offset_pointer(6 + i * 12 + 10));
+
+ if (platform == (u16)Platform::Windows) {
+ static auto& decoder = *TextCodec::decoder_for("utf-16be");
+ return decoder.to_utf8(StringView { (char const*)m_slice.offset_pointer(string_offset + offset), length });
+ }
+
+ return String((char const*)m_slice.offset_pointer(string_offset + offset), length);
+}
+
+GlyphHorizontalMetrics Hmtx::get_glyph_horizontal_metrics(u32 glyph_id) const
+{
+ VERIFY(glyph_id < m_num_glyphs);
+ if (glyph_id < m_number_of_h_metrics) {
+ auto offset = glyph_id * (u32)Sizes::LongHorMetric;
+ u16 advance_width = be_u16(m_slice.offset_pointer(offset));
+ i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset + 2));
+ return GlyphHorizontalMetrics {
+ .advance_width = advance_width,
+ .left_side_bearing = left_side_bearing,
+ };
+ }
+ auto offset = m_number_of_h_metrics * (u32)Sizes::LongHorMetric + (glyph_id - m_number_of_h_metrics) * (u32)Sizes::LeftSideBearing;
+ u16 advance_width = be_u16(m_slice.offset_pointer((m_number_of_h_metrics - 1) * (u32)Sizes::LongHorMetric));
+ i16 left_side_bearing = be_i16(m_slice.offset_pointer(offset));
+ return GlyphHorizontalMetrics {
+ .advance_width = advance_width,
+ .left_side_bearing = left_side_bearing,
+ };
+}
+
+Result<NonnullRefPtr<Font>, String> Font::try_load_from_file(String path, unsigned index)
+{
+ auto file_or_error = MappedFile::map(path);
+ if (file_or_error.is_error())
+ return String { file_or_error.error().string() };
+
+ auto& file = *file_or_error.value();
+ auto result = try_load_from_externally_owned_memory(file.bytes(), index);
+ if (result.is_error())
+ return result.error();
+ auto& font = *result.value();
+ font.m_mapped_file = file_or_error.release_value();
+ return result;
+}
+
+Result<NonnullRefPtr<Font>, String> Font::try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned index)
+{
+ if (buffer.size() < 4)
+ return String { "Font file too small" };
+
+ u32 tag = be_u32(buffer.data());
+ if (tag == tag_from_str("ttcf")) {
+ // It's a font collection
+ if (buffer.size() < (u32)Sizes::TTCHeaderV1 + sizeof(u32) * (index + 1))
+ return String { "Font file too small" };
+
+ u32 offset = be_u32(buffer.offset_pointer((u32)Sizes::TTCHeaderV1 + sizeof(u32) * index));
+ return try_load_from_offset(move(buffer), offset);
+ }
+ if (tag == tag_from_str("OTTO"))
+ return String { "CFF fonts not supported yet" };
+
+ if (tag != 0x00010000)
+ return String { "Not a valid font" };
+
+ return try_load_from_offset(move(buffer), 0);
+}
+
+// FIXME: "loca" and "glyf" are not available for CFF fonts.
+Result<NonnullRefPtr<Font>, String> Font::try_load_from_offset(ReadonlyBytes buffer, u32 offset)
+{
+ if (Checked<u32>::addition_would_overflow(offset, (u32)Sizes::OffsetTable))
+ return String { "Invalid offset in font header" };
+
+ if (buffer.size() < offset + (u32)Sizes::OffsetTable)
+ return String { "Font file too small" };
+
+ Optional<ReadonlyBytes> opt_head_slice = {};
+ Optional<ReadonlyBytes> opt_name_slice = {};
+ Optional<ReadonlyBytes> opt_hhea_slice = {};
+ Optional<ReadonlyBytes> opt_maxp_slice = {};
+ Optional<ReadonlyBytes> opt_hmtx_slice = {};
+ Optional<ReadonlyBytes> opt_cmap_slice = {};
+ Optional<ReadonlyBytes> opt_loca_slice = {};
+ Optional<ReadonlyBytes> opt_glyf_slice = {};
+ Optional<ReadonlyBytes> opt_os2_slice = {};
+
+ Optional<Head> opt_head = {};
+ Optional<Name> opt_name = {};
+ Optional<Hhea> opt_hhea = {};
+ Optional<Maxp> opt_maxp = {};
+ Optional<Hmtx> opt_hmtx = {};
+ Optional<Cmap> opt_cmap = {};
+ Optional<Loca> opt_loca = {};
+ Optional<OS2> opt_os2 = {};
+
+ auto num_tables = be_u16(buffer.offset_pointer(offset + (u32)Offsets::NumTables));
+ if (buffer.size() < offset + (u32)Sizes::OffsetTable + num_tables * (u32)Sizes::TableRecord)
+ return String { "Font file too small" };
+
+ for (auto i = 0; i < num_tables; i++) {
+ u32 record_offset = offset + (u32)Sizes::OffsetTable + i * (u32)Sizes::TableRecord;
+ u32 tag = be_u32(buffer.offset_pointer(record_offset));
+ u32 table_offset = be_u32(buffer.offset_pointer(record_offset + (u32)Offsets::TableRecord_Offset));
+ u32 table_length = be_u32(buffer.offset_pointer(record_offset + (u32)Offsets::TableRecord_Length));
+
+ if (Checked<u32>::addition_would_overflow(table_offset, table_length))
+ return String { "Invalid table offset/length in font." };
+
+ if (buffer.size() < table_offset + table_length)
+ return String { "Font file too small" };
+
+ auto buffer_here = ReadonlyBytes(buffer.offset_pointer(table_offset), table_length);
+
+ // Get the table offsets we need.
+ if (tag == tag_from_str("head")) {
+ opt_head_slice = buffer_here;
+ } else if (tag == tag_from_str("name")) {
+ opt_name_slice = buffer_here;
+ } else if (tag == tag_from_str("hhea")) {
+ opt_hhea_slice = buffer_here;
+ } else if (tag == tag_from_str("maxp")) {
+ opt_maxp_slice = buffer_here;
+ } else if (tag == tag_from_str("hmtx")) {
+ opt_hmtx_slice = buffer_here;
+ } else if (tag == tag_from_str("cmap")) {
+ opt_cmap_slice = buffer_here;
+ } else if (tag == tag_from_str("loca")) {
+ opt_loca_slice = buffer_here;
+ } else if (tag == tag_from_str("glyf")) {
+ opt_glyf_slice = buffer_here;
+ } else if (tag == tag_from_str("OS/2")) {
+ opt_os2_slice = buffer_here;
+ }
+ }
+
+ if (!opt_head_slice.has_value() || !(opt_head = Head::from_slice(opt_head_slice.value())).has_value())
+ return String { "Could not load Head" };
+ auto head = opt_head.value();
+
+ if (!opt_name_slice.has_value() || !(opt_name = Name::from_slice(opt_name_slice.value())).has_value())
+ return String { "Could not load Name" };
+ auto name = opt_name.value();
+
+ if (!opt_hhea_slice.has_value() || !(opt_hhea = Hhea::from_slice(opt_hhea_slice.value())).has_value())
+ return String { "Could not load Hhea" };
+ auto hhea = opt_hhea.value();
+
+ if (!opt_maxp_slice.has_value() || !(opt_maxp = Maxp::from_slice(opt_maxp_slice.value())).has_value())
+ return String { "Could not load Maxp" };
+ auto maxp = opt_maxp.value();
+
+ if (!opt_hmtx_slice.has_value() || !(opt_hmtx = Hmtx::from_slice(opt_hmtx_slice.value(), maxp.num_glyphs(), hhea.number_of_h_metrics())).has_value())
+ return String { "Could not load Hmtx" };
+ auto hmtx = opt_hmtx.value();
+
+ if (!opt_cmap_slice.has_value() || !(opt_cmap = Cmap::from_slice(opt_cmap_slice.value())).has_value())
+ return String { "Could not load Cmap" };
+ auto cmap = opt_cmap.value();
+
+ if (!opt_loca_slice.has_value() || !(opt_loca = Loca::from_slice(opt_loca_slice.value(), maxp.num_glyphs(), head.index_to_loc_format())).has_value())
+ return String { "Could not load Loca" };
+ auto loca = opt_loca.value();
+
+ if (!opt_glyf_slice.has_value())
+ return String { "Could not load Glyf" };
+ auto glyf = Glyf(opt_glyf_slice.value());
+
+ if (!opt_os2_slice.has_value())
+ return String { "Could not load OS/2" };
+ auto os2 = OS2(opt_os2_slice.value());
+
+ // Select cmap table. FIXME: Do this better. Right now, just looks for platform "Windows"
+ // and corresponding encoding "Unicode full repertoire", or failing that, "Unicode BMP"
+ for (u32 i = 0; i < cmap.num_subtables(); i++) {
+ auto opt_subtable = cmap.subtable(i);
+ if (!opt_subtable.has_value()) {
+ continue;
+ }
+ auto subtable = opt_subtable.value();
+ if (subtable.platform_id() == Cmap::Subtable::Platform::Windows) {
+ if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeFullRepertoire) {
+ cmap.set_active_index(i);
+ break;
+ }
+ if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeBMP) {
+ cmap.set_active_index(i);
+ break;
+ }
+ }
+ }
+
+ return adopt_ref(*new Font(move(buffer), move(head), move(name), move(hhea), move(maxp), move(hmtx), move(cmap), move(loca), move(glyf), move(os2)));
+}
+
+ScaledFontMetrics Font::metrics(float x_scale, float y_scale) const
+{
+ auto ascender = m_hhea.ascender() * y_scale;
+ auto descender = m_hhea.descender() * y_scale;
+ auto line_gap = m_hhea.line_gap() * y_scale;
+ auto advance_width_max = m_hhea.advance_width_max() * x_scale;
+ return ScaledFontMetrics {
+ .ascender = (int)roundf(ascender),
+ .descender = (int)roundf(descender),
+ .line_gap = (int)roundf(line_gap),
+ .advance_width_max = (int)roundf(advance_width_max),
+ };
+}
+
+// FIXME: "loca" and "glyf" are not available for CFF fonts.
+ScaledGlyphMetrics Font::glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const
+{
+ if (glyph_id >= glyph_count()) {
+ glyph_id = 0;
+ }
+ auto horizontal_metrics = m_hmtx.get_glyph_horizontal_metrics(glyph_id);
+ auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
+ auto glyph = m_glyf.glyph(glyph_offset);
+ int ascender = glyph.ascender();
+ int descender = glyph.descender();
+ return ScaledGlyphMetrics {
+ .ascender = (int)roundf(ascender * y_scale),
+ .descender = (int)roundf(descender * y_scale),
+ .advance_width = (int)roundf(horizontal_metrics.advance_width * x_scale),
+ .left_side_bearing = (int)roundf(horizontal_metrics.left_side_bearing * x_scale),
+ };
+}
+
+// FIXME: "loca" and "glyf" are not available for CFF fonts.
+RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const
+{
+ if (glyph_id >= glyph_count()) {
+ glyph_id = 0;
+ }
+ auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
+ auto glyph = m_glyf.glyph(glyph_offset);
+ return glyph.rasterize(m_os2.typographic_ascender(), m_os2.typographic_descender(), x_scale, y_scale, [&](u16 glyph_id) {
+ if (glyph_id >= glyph_count()) {
+ glyph_id = 0;
+ }
+ auto glyph_offset = m_loca.get_glyph_offset(glyph_id);
+ return m_glyf.glyph(glyph_offset);
+ });
+}
+
+u32 Font::glyph_count() const
+{
+ return m_maxp.num_glyphs();
+}
+
+u16 Font::units_per_em() const
+{
+ return m_head.units_per_em();
+}
+
+String Font::family() const
+{
+ auto string = m_name.typographic_family_name();
+ if (!string.is_empty())
+ return string;
+ return m_name.family_name();
+}
+
+String Font::variant() const
+{
+ auto string = m_name.typographic_subfamily_name();
+ if (!string.is_empty())
+ return string;
+ return m_name.subfamily_name();
+}
+
+u16 Font::weight() const
+{
+ // FIXME: This is pretty naive, read weight from the actual font table(s)
+ auto variant_name = variant();
+
+ if (variant_name == "Thin")
+ return 100;
+ if (variant_name == "Extra Light")
+ return 200;
+ if (variant_name == "Light")
+ return 300;
+ if (variant_name == "Regular")
+ return 400;
+ if (variant_name == "Medium")
+ return 500;
+ if (variant_name == "Semi Bold")
+ return 600;
+ if (variant_name == "Bold")
+ return 700;
+ if (variant_name == "Extra Bold")
+ return 800;
+ if (variant_name == "Black")
+ return 900;
+ if (variant_name == "Extra Black")
+ return 950;
+
+ return 400;
+}
+
+bool Font::is_fixed_width() const
+{
+ // FIXME: Read this information from the font file itself.
+ // FIXME: Although, it appears some application do similar hacks
+ return glyph_metrics(glyph_id_for_code_point('.'), 1, 1).advance_width == glyph_metrics(glyph_id_for_code_point('X'), 1, 1).advance_width;
+}
+
+int ScaledFont::width(StringView const& view) const { return unicode_view_width(Utf8View(view)); }
+int ScaledFont::width(Utf8View const& view) const { return unicode_view_width(view); }
+int ScaledFont::width(Utf32View const& view) const { return unicode_view_width(view); }
+
+template<typename T>
+ALWAYS_INLINE int ScaledFont::unicode_view_width(T const& view) const
+{
+ if (view.is_empty())
+ return 0;
+ int width = 0;
+ int longest_width = 0;
+ for (auto code_point : view) {
+ if (code_point == '\n' || code_point == '\r') {
+ longest_width = max(width, longest_width);
+ width = 0;
+ continue;
+ }
+ u32 glyph_id = glyph_id_for_code_point(code_point);
+ auto metrics = glyph_metrics(glyph_id);
+ width += metrics.advance_width;
+ }
+ longest_width = max(width, longest_width);
+ return longest_width;
+}
+
+RefPtr<Gfx::Bitmap> ScaledFont::rasterize_glyph(u32 glyph_id) const
+{
+ auto glyph_iterator = m_cached_glyph_bitmaps.find(glyph_id);
+ if (glyph_iterator != m_cached_glyph_bitmaps.end())
+ return glyph_iterator->value;
+
+ auto glyph_bitmap = m_font->rasterize_glyph(glyph_id, m_x_scale, m_y_scale);
+ m_cached_glyph_bitmaps.set(glyph_id, glyph_bitmap);
+ return glyph_bitmap;
+}
+
+Gfx::Glyph ScaledFont::glyph(u32 code_point) const
+{
+ auto id = glyph_id_for_code_point(code_point);
+ auto bitmap = rasterize_glyph(id);
+ auto metrics = glyph_metrics(id);
+ return Gfx::Glyph(bitmap, metrics.left_side_bearing, metrics.advance_width, metrics.ascender);
+}
+
+u8 ScaledFont::glyph_width(size_t code_point) const
+{
+ auto id = glyph_id_for_code_point(code_point);
+ auto metrics = glyph_metrics(id);
+ return metrics.advance_width;
+}
+
+int ScaledFont::glyph_or_emoji_width(u32 code_point) const
+{
+ auto id = glyph_id_for_code_point(code_point);
+ auto metrics = glyph_metrics(id);
+ return metrics.advance_width;
+}
+
+u8 ScaledFont::glyph_fixed_width() const
+{
+ return glyph_metrics(glyph_id_for_code_point(' ')).advance_width;
+}
+
+i16 OS2::typographic_ascender() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::TypographicAscender));
+}
+
+i16 OS2::typographic_descender() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::TypographicDescender));
+}
+
+i16 OS2::typographic_line_gap() const
+{
+ return be_i16(m_slice.offset_pointer((u32)Offsets::TypographicLineGap));
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Font.h b/Userland/Libraries/LibGfx/TrueTypeFont/Font.h
new file mode 100644
index 0000000000..81a294f70e
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Font.h
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/HashMap.h>
+#include <AK/Noncopyable.h>
+#include <AK/RefCounted.h>
+#include <AK/StringView.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/Font.h>
+#include <LibGfx/Size.h>
+#include <LibGfx/TrueTypeFont/Cmap.h>
+#include <LibGfx/TrueTypeFont/Glyf.h>
+#include <LibGfx/TrueTypeFont/Tables.h>
+
+#define POINTS_PER_INCH 72.0f
+#define DEFAULT_DPI 96
+
+namespace TTF {
+
+struct ScaledFontMetrics {
+ int ascender;
+ int descender;
+ int line_gap;
+ int advance_width_max;
+
+ int height() const
+ {
+ return ascender - descender;
+ }
+};
+
+struct ScaledGlyphMetrics {
+ int ascender;
+ int descender;
+ int advance_width;
+ int left_side_bearing;
+};
+
+class Font : public RefCounted<Font> {
+ AK_MAKE_NONCOPYABLE(Font);
+
+public:
+ static Result<NonnullRefPtr<Font>, String> try_load_from_file(String path, unsigned index = 0);
+ static Result<NonnullRefPtr<Font>, String> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0);
+
+ ScaledFontMetrics metrics(float x_scale, float y_scale) const;
+ ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const;
+ RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale) const;
+ u32 glyph_count() const;
+ u16 units_per_em() const;
+ u32 glyph_id_for_code_point(u32 code_point) const { return m_cmap.glyph_id_for_code_point(code_point); }
+ String family() const;
+ String variant() const;
+ u16 weight() const;
+ bool is_fixed_width() const;
+
+private:
+ enum class Offsets {
+ NumTables = 4,
+ TableRecord_Offset = 8,
+ TableRecord_Length = 12,
+ };
+ enum class Sizes {
+ TTCHeaderV1 = 12,
+ OffsetTable = 12,
+ TableRecord = 16,
+ };
+
+ static Result<NonnullRefPtr<Font>, String> try_load_from_offset(ReadonlyBytes, unsigned index = 0);
+
+ Font(ReadonlyBytes bytes, Head&& head, Name&& name, Hhea&& hhea, Maxp&& maxp, Hmtx&& hmtx, Cmap&& cmap, Loca&& loca, Glyf&& glyf, OS2&& os2)
+ : m_buffer(move(bytes))
+ , m_head(move(head))
+ , m_name(move(name))
+ , m_hhea(move(hhea))
+ , m_maxp(move(maxp))
+ , m_hmtx(move(hmtx))
+ , m_loca(move(loca))
+ , m_glyf(move(glyf))
+ , m_cmap(move(cmap))
+ , m_os2(move(os2))
+ {
+ }
+
+ RefPtr<MappedFile> m_mapped_file;
+
+ ReadonlyBytes m_buffer;
+
+ // These are stateful wrappers around non-owning slices
+ Head m_head;
+ Name m_name;
+ Hhea m_hhea;
+ Maxp m_maxp;
+ Hmtx m_hmtx;
+ Loca m_loca;
+ Glyf m_glyf;
+ Cmap m_cmap;
+ OS2 m_os2;
+};
+
+class ScaledFont : public Gfx::Font {
+public:
+ ScaledFont(NonnullRefPtr<TTF::Font> font, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI)
+ : m_font(move(font))
+ , m_point_width(point_width)
+ , m_point_height(point_height)
+ {
+ float units_per_em = m_font->units_per_em();
+ m_x_scale = (point_width * dpi_x) / (POINTS_PER_INCH * units_per_em);
+ m_y_scale = (point_height * dpi_y) / (POINTS_PER_INCH * units_per_em);
+ }
+ u32 glyph_id_for_code_point(u32 code_point) const { return m_font->glyph_id_for_code_point(code_point); }
+ ScaledFontMetrics metrics() const { return m_font->metrics(m_x_scale, m_y_scale); }
+ ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale); }
+ RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id) const;
+
+ // Gfx::Font implementation
+ virtual NonnullRefPtr<Font> clone() const override { return *this; } // FIXME: clone() should not need to be implemented
+ virtual u8 presentation_size() const override { return m_point_height; }
+ virtual u16 weight() const override { return m_font->weight(); }
+ virtual Gfx::Glyph glyph(u32 code_point) const override;
+ virtual bool contains_glyph(u32 code_point) const override { return m_font->glyph_id_for_code_point(code_point) > 0; }
+ virtual u8 glyph_width(size_t ch) const override;
+ virtual int glyph_or_emoji_width(u32 code_point) const override;
+ virtual u8 glyph_height() const override { return m_point_height; }
+ virtual int x_height() const override { return m_point_height; } // FIXME: Read from font
+ virtual u8 min_glyph_width() const override { return 1; } // FIXME: Read from font
+ virtual u8 max_glyph_width() const override { return m_point_width; } // FIXME: Read from font
+ virtual u8 glyph_fixed_width() const override;
+ virtual u8 baseline() const override { return m_point_height; } // FIXME: Read from font
+ virtual u8 mean_line() const override { return m_point_height; } // FIXME: Read from font
+ virtual int width(StringView const&) const override;
+ virtual int width(Utf8View const&) const override;
+ virtual int width(Utf32View const&) const override;
+ virtual String name() const override { return String::formatted("{} {}", family(), variant()); }
+ virtual bool is_fixed_width() const override { return m_font->is_fixed_width(); }
+ virtual u8 glyph_spacing() const override { return m_x_scale; } // FIXME: Read from font
+ virtual size_t glyph_count() const override { return m_font->glyph_count(); }
+ virtual String family() const override { return m_font->family(); }
+ virtual String variant() const override { return m_font->variant(); }
+ virtual String qualified_name() const override { return String::formatted("{} {} {}", family(), presentation_size(), weight()); }
+
+private:
+ NonnullRefPtr<TTF::Font> m_font;
+ float m_x_scale { 0.0f };
+ float m_y_scale { 0.0f };
+ float m_point_width { 0.0f };
+ float m_point_height { 0.0f };
+ mutable HashMap<u32, RefPtr<Gfx::Bitmap>> m_cached_glyph_bitmaps;
+
+ template<typename T>
+ int unicode_view_width(T const& view) const;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp b/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp
new file mode 100644
index 0000000000..7cdd39eb84
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibGfx/Path.h>
+#include <LibGfx/Point.h>
+#include <LibGfx/TrueTypeFont/Glyf.h>
+
+namespace TTF {
+
+extern u16 be_u16(u8 const* ptr);
+extern u32 be_u32(u8 const* ptr);
+extern i16 be_i16(u8 const* ptr);
+extern float be_fword(u8 const* ptr);
+
+enum class SimpleGlyfFlags {
+ // From spec.
+ OnCurve = 0x01,
+ XShortVector = 0x02,
+ YShortVector = 0x04,
+ RepeatFlag = 0x08,
+ XIsSameOrPositiveXShortVector = 0x10,
+ YIsSameOrPositiveYShortVector = 0x20,
+ // Combinations
+ XMask = 0x12,
+ YMask = 0x24,
+ XLongVector = 0x00,
+ YLongVector = 0x00,
+ XNegativeShortVector = 0x02,
+ YNegativeShortVector = 0x04,
+ XPositiveShortVector = 0x12,
+ YPositiveShortVector = 0x24,
+};
+
+enum class CompositeGlyfFlags {
+ Arg1AndArg2AreWords = 0x0001,
+ ArgsAreXYValues = 0x0002,
+ RoundXYToGrid = 0x0004,
+ WeHaveAScale = 0x0008,
+ MoreComponents = 0x0020,
+ WeHaveAnXAndYScale = 0x0040,
+ WeHaveATwoByTwo = 0x0080,
+ WeHaveInstructions = 0x0100,
+ UseMyMetrics = 0x0200,
+ OverlapCompound = 0x0400, // Not relevant - can overlap without this set
+ ScaledComponentOffset = 0x0800,
+ UnscaledComponentOffset = 0x1000,
+};
+
+class PointIterator {
+public:
+ struct Item {
+ bool on_curve;
+ Gfx::FloatPoint point;
+ };
+
+ PointIterator(ReadonlyBytes const& slice, u16 num_points, u32 flags_offset, u32 x_offset, u32 y_offset, Gfx::AffineTransform affine)
+ : m_slice(slice)
+ , m_points_remaining(num_points)
+ , m_flags_offset(flags_offset)
+ , m_x_offset(x_offset)
+ , m_y_offset(y_offset)
+ , m_affine(affine)
+ {
+ }
+
+ Optional<Item> next()
+ {
+ if (m_points_remaining == 0) {
+ return {};
+ }
+ if (m_flags_remaining > 0) {
+ m_flags_remaining--;
+ } else {
+ m_flag = m_slice[m_flags_offset++];
+ if (m_flag & (u8)SimpleGlyfFlags::RepeatFlag) {
+ m_flags_remaining = m_slice[m_flags_offset++];
+ }
+ }
+ switch (m_flag & (u8)SimpleGlyfFlags::XMask) {
+ case (u8)SimpleGlyfFlags::XLongVector:
+ m_last_point.set_x(m_last_point.x() + be_i16(m_slice.offset_pointer(m_x_offset)));
+ m_x_offset += 2;
+ break;
+ case (u8)SimpleGlyfFlags::XNegativeShortVector:
+ m_last_point.set_x(m_last_point.x() - m_slice[m_x_offset++]);
+ break;
+ case (u8)SimpleGlyfFlags::XPositiveShortVector:
+ m_last_point.set_x(m_last_point.x() + m_slice[m_x_offset++]);
+ break;
+ default:
+ break;
+ }
+ switch (m_flag & (u8)SimpleGlyfFlags::YMask) {
+ case (u8)SimpleGlyfFlags::YLongVector:
+ m_last_point.set_y(m_last_point.y() + be_i16(m_slice.offset_pointer(m_y_offset)));
+ m_y_offset += 2;
+ break;
+ case (u8)SimpleGlyfFlags::YNegativeShortVector:
+ m_last_point.set_y(m_last_point.y() - m_slice[m_y_offset++]);
+ break;
+ case (u8)SimpleGlyfFlags::YPositiveShortVector:
+ m_last_point.set_y(m_last_point.y() + m_slice[m_y_offset++]);
+ break;
+ default:
+ break;
+ }
+ m_points_remaining--;
+ Item ret = {
+ .on_curve = (m_flag & (u8)SimpleGlyfFlags::OnCurve) != 0,
+ .point = m_affine.map(m_last_point),
+ };
+ return ret;
+ }
+
+private:
+ ReadonlyBytes m_slice;
+ u16 m_points_remaining;
+ u8 m_flag { 0 };
+ Gfx::FloatPoint m_last_point = { 0.0f, 0.0f };
+ u32 m_flags_remaining = { 0 };
+ u32 m_flags_offset;
+ u32 m_x_offset;
+ u32 m_y_offset;
+ Gfx::AffineTransform m_affine;
+};
+
+Optional<Glyf::Glyph::ComponentIterator::Item> Glyf::Glyph::ComponentIterator::next()
+{
+ if (!m_has_more) {
+ return {};
+ }
+ u16 flags = be_u16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ u16 glyph_id = be_u16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ i16 arg1 = 0, arg2 = 0;
+ if (flags & (u16)CompositeGlyfFlags::Arg1AndArg2AreWords) {
+ arg1 = be_i16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ arg2 = be_i16(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ } else {
+ arg1 = (i8)m_slice[m_offset++];
+ arg2 = (i8)m_slice[m_offset++];
+ }
+ float a = 1.0, b = 0.0, c = 0.0, d = 1.0, e = 0.0, f = 0.0;
+ if (flags & (u16)CompositeGlyfFlags::WeHaveATwoByTwo) {
+ a = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ b = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ c = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ d = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ } else if (flags & (u16)CompositeGlyfFlags::WeHaveAnXAndYScale) {
+ a = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ d = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ } else if (flags & (u16)CompositeGlyfFlags::WeHaveAScale) {
+ a = be_fword(m_slice.offset_pointer(m_offset));
+ m_offset += 2;
+ d = a;
+ }
+ // FIXME: Handle UseMyMetrics, ScaledComponentOffset, UnscaledComponentOffset, non-ArgsAreXYValues
+ if (flags & (u16)CompositeGlyfFlags::ArgsAreXYValues) {
+ e = arg1;
+ f = arg2;
+ } else {
+ TODO();
+ }
+ if (flags & (u16)CompositeGlyfFlags::UseMyMetrics) {
+ // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this.
+ }
+ if (flags & (u16)CompositeGlyfFlags::ScaledComponentOffset) {
+ TODO();
+ }
+ if (flags & (u16)CompositeGlyfFlags::UnscaledComponentOffset) {
+ TODO();
+ }
+ m_has_more = (flags & (u16)CompositeGlyfFlags::MoreComponents);
+ return Item {
+ .glyph_id = glyph_id,
+ .affine = Gfx::AffineTransform(a, b, c, d, e, f),
+ };
+}
+
+Rasterizer::Rasterizer(Gfx::IntSize size)
+ : m_size(size)
+{
+ m_data.resize(m_size.width() * m_size.height());
+ for (int i = 0; i < m_size.width() * m_size.height(); i++) {
+ m_data[i] = 0.0f;
+ }
+}
+
+void Rasterizer::draw_path(Gfx::Path& path)
+{
+ for (auto& line : path.split_lines()) {
+ draw_line(line.from, line.to);
+ }
+}
+
+RefPtr<Gfx::Bitmap> Rasterizer::accumulate()
+{
+ auto bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, m_size);
+ if (!bitmap)
+ return {};
+ Color base_color = Color::from_rgb(0xffffff);
+ for (int y = 0; y < m_size.height(); y++) {
+ float accumulator = 0.0;
+ for (int x = 0; x < m_size.width(); x++) {
+ accumulator += m_data[y * m_size.width() + x];
+ float value = accumulator;
+ if (value < 0.0f) {
+ value = -value;
+ }
+ if (value > 1.0f) {
+ value = 1.0;
+ }
+ u8 alpha = value * 255.0f;
+ bitmap->set_pixel(x, y, base_color.with_alpha(alpha));
+ }
+ }
+ return bitmap;
+}
+
+void Rasterizer::draw_line(Gfx::FloatPoint p0, Gfx::FloatPoint p1)
+{
+ // FIXME: Shift x and y according to dy/dx
+ if (p0.x() < 0.0f) {
+ p0.set_x(roundf(p0.x()));
+ }
+ if (p0.y() < 0.0f) {
+ p0.set_y(roundf(p0.y()));
+ }
+ if (p1.x() < 0.0f) {
+ p1.set_x(roundf(p1.x()));
+ }
+ if (p1.y() < 0.0f) {
+ p1.set_y(roundf(p1.y()));
+ }
+
+ if (!(p0.x() >= 0.0f && p0.y() >= 0.0f && p0.x() <= m_size.width() && p0.y() <= m_size.height())) {
+ dbgln("!P0({},{})", p0.x(), p0.y());
+ return;
+ }
+
+ if (!(p1.x() >= 0.0f && p1.y() >= 0.0f && p1.x() <= m_size.width() && p1.y() <= m_size.height())) {
+ dbgln("!P1({},{})", p1.x(), p1.y());
+ return;
+ }
+
+ VERIFY(p0.x() >= 0.0f && p0.y() >= 0.0f && p0.x() <= m_size.width() && p0.y() <= m_size.height());
+ VERIFY(p1.x() >= 0.0f && p1.y() >= 0.0f && p1.x() <= m_size.width() && p1.y() <= m_size.height());
+
+ // If we're on the same Y, there's no need to draw
+ if (p0.y() == p1.y()) {
+ return;
+ }
+
+ float direction = -1.0;
+ if (p1.y() < p0.y()) {
+ direction = 1.0;
+ auto tmp = p0;
+ p0 = p1;
+ p1 = tmp;
+ }
+
+ float dxdy = (p1.x() - p0.x()) / (p1.y() - p0.y());
+ u32 y0 = floorf(p0.y());
+ u32 y1 = ceilf(p1.y());
+ float x_cur = p0.x();
+
+ for (u32 y = y0; y < y1; y++) {
+ u32 line_offset = m_size.width() * y;
+
+ float dy = min(y + 1.0f, p1.y()) - max((float)y, p0.y());
+ float directed_dy = dy * direction;
+ float x_next = x_cur + dy * dxdy;
+ if (x_next < 0.0f) {
+ x_next = 0.0f;
+ }
+ float x0 = x_cur;
+ float x1 = x_next;
+ if (x1 < x0) {
+ x1 = x_cur;
+ x0 = x_next;
+ }
+ float x0_floor = floorf(x0);
+ float x1_ceil = ceilf(x1);
+ u32 x0i = x0_floor;
+
+ if (x1_ceil <= x0_floor + 1.0f) {
+ // If x0 and x1 are within the same pixel, then area to the right is (1 - (mid(x0, x1) - x0_floor)) * dy
+ float area = ((x0 + x1) * 0.5f) - x0_floor;
+ m_data[line_offset + x0i] += directed_dy * (1.0f - area);
+ m_data[line_offset + x0i + 1] += directed_dy * area;
+ } else {
+ float dydx = 1.0f / dxdy;
+ if (dydx < 0)
+ dydx = -dydx;
+
+ float x0_right = 1.0f - (x0 - x0_floor);
+ u32 x1_floor_i = floorf(x1);
+ float area_upto_here = 0.5f * x0_right * x0_right * dydx;
+ m_data[line_offset + x0i] += direction * area_upto_here;
+ for (u32 x = x0i + 1; x < x1_floor_i; x++) {
+ m_data[line_offset + x] += direction * dydx;
+ area_upto_here += dydx;
+ }
+ float remaining_area = (dy - area_upto_here);
+ m_data[line_offset + x1_floor_i] += direction * remaining_area;
+ }
+
+ x_cur = x_next;
+ }
+}
+
+Optional<Loca> Loca::from_slice(ReadonlyBytes const& slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
+{
+ switch (index_to_loc_format) {
+ case IndexToLocFormat::Offset16:
+ if (slice.size() < num_glyphs * 2) {
+ return {};
+ }
+ break;
+ case IndexToLocFormat::Offset32:
+ if (slice.size() < num_glyphs * 4) {
+ return {};
+ }
+ break;
+ }
+ return Loca(slice, num_glyphs, index_to_loc_format);
+}
+
+u32 Loca::get_glyph_offset(u32 glyph_id) const
+{
+ VERIFY(glyph_id < m_num_glyphs);
+ switch (m_index_to_loc_format) {
+ case IndexToLocFormat::Offset16:
+ return ((u32)be_u16(m_slice.offset_pointer(glyph_id * 2))) * 2;
+ case IndexToLocFormat::Offset32:
+ return be_u32(m_slice.offset_pointer(glyph_id * 4));
+ default:
+ VERIFY_NOT_REACHED();
+ }
+}
+
+static void get_ttglyph_offsets(ReadonlyBytes const& slice, u32 num_points, u32 flags_offset, u32* x_offset, u32* y_offset)
+{
+ u32 flags_size = 0;
+ u32 x_size = 0;
+ u32 repeat_count;
+ while (num_points > 0) {
+ u8 flag = slice[flags_offset + flags_size];
+ if (flag & (u8)SimpleGlyfFlags::RepeatFlag) {
+ flags_size++;
+ repeat_count = slice[flags_offset + flags_size] + 1;
+ } else {
+ repeat_count = 1;
+ }
+ flags_size++;
+ switch (flag & (u8)SimpleGlyfFlags::XMask) {
+ case (u8)SimpleGlyfFlags::XLongVector:
+ x_size += repeat_count * 2;
+ break;
+ case (u8)SimpleGlyfFlags::XNegativeShortVector:
+ case (u8)SimpleGlyfFlags::XPositiveShortVector:
+ x_size += repeat_count;
+ break;
+ default:
+ break;
+ }
+ num_points -= repeat_count;
+ }
+ *x_offset = flags_offset + flags_size;
+ *y_offset = *x_offset + x_size;
+}
+
+void Glyf::Glyph::rasterize_impl(Rasterizer& rasterizer, Gfx::AffineTransform const& transform) const
+{
+ // Get offset for flags, x, and y.
+ u16 num_points = be_u16(m_slice.offset_pointer((m_num_contours - 1) * 2)) + 1;
+ u16 num_instructions = be_u16(m_slice.offset_pointer(m_num_contours * 2));
+ u32 flags_offset = m_num_contours * 2 + 2 + num_instructions;
+ u32 x_offset = 0;
+ u32 y_offset = 0;
+ get_ttglyph_offsets(m_slice, num_points, flags_offset, &x_offset, &y_offset);
+
+ // Prepare to render glyph.
+ Gfx::Path path;
+ PointIterator point_iterator(m_slice, num_points, flags_offset, x_offset, y_offset, transform);
+
+ int last_contour_end = -1;
+ i32 contour_index = 0;
+ u32 contour_size = 0;
+ Optional<Gfx::FloatPoint> contour_start = {};
+ Optional<Gfx::FloatPoint> last_offcurve_point = {};
+
+ // Render glyph
+ while (true) {
+ if (!contour_start.has_value()) {
+ if (contour_index >= m_num_contours) {
+ break;
+ }
+ int current_contour_end = be_u16(m_slice.offset_pointer(contour_index++ * 2));
+ contour_size = current_contour_end - last_contour_end;
+ last_contour_end = current_contour_end;
+ auto opt_item = point_iterator.next();
+ VERIFY(opt_item.has_value());
+ contour_start = opt_item.value().point;
+ path.move_to(contour_start.value());
+ contour_size--;
+ } else if (!last_offcurve_point.has_value()) {
+ if (contour_size > 0) {
+ auto opt_item = point_iterator.next();
+ // FIXME: Should we draw a line to the first point here?
+ if (!opt_item.has_value()) {
+ break;
+ }
+ auto item = opt_item.value();
+ contour_size--;
+ if (item.on_curve) {
+ path.line_to(item.point);
+ } else if (contour_size > 0) {
+ auto opt_next_item = point_iterator.next();
+ // FIXME: Should we draw a quadratic bezier to the first point here?
+ if (!opt_next_item.has_value()) {
+ break;
+ }
+ auto next_item = opt_next_item.value();
+ contour_size--;
+ if (next_item.on_curve) {
+ path.quadratic_bezier_curve_to(item.point, next_item.point);
+ } else {
+ auto mid_point = (item.point + next_item.point) * 0.5f;
+ path.quadratic_bezier_curve_to(item.point, mid_point);
+ last_offcurve_point = next_item.point;
+ }
+ } else {
+ path.quadratic_bezier_curve_to(item.point, contour_start.value());
+ contour_start = {};
+ }
+ } else {
+ path.line_to(contour_start.value());
+ contour_start = {};
+ }
+ } else {
+ auto point0 = last_offcurve_point.value();
+ last_offcurve_point = {};
+ if (contour_size > 0) {
+ auto opt_item = point_iterator.next();
+ // FIXME: Should we draw a quadratic bezier to the first point here?
+ if (!opt_item.has_value()) {
+ break;
+ }
+ auto item = opt_item.value();
+ contour_size--;
+ if (item.on_curve) {
+ path.quadratic_bezier_curve_to(point0, item.point);
+ } else {
+ auto mid_point = (point0 + item.point) * 0.5f;
+ path.quadratic_bezier_curve_to(point0, mid_point);
+ last_offcurve_point = item.point;
+ }
+ } else {
+ path.quadratic_bezier_curve_to(point0, contour_start.value());
+ contour_start = {};
+ }
+ }
+ }
+
+ rasterizer.draw_path(path);
+}
+
+RefPtr<Gfx::Bitmap> Glyf::Glyph::rasterize_simple(i16 font_ascender, i16 font_descender, float x_scale, float y_scale) const
+{
+ u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 2;
+ u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 2;
+ Rasterizer rasterizer(Gfx::IntSize(width, height));
+ auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -font_ascender);
+ rasterize_impl(rasterizer, affine);
+ return rasterizer.accumulate();
+}
+
+Glyf::Glyph Glyf::glyph(u32 offset) const
+{
+ VERIFY(m_slice.size() >= offset + (u32)Sizes::GlyphHeader);
+ i16 num_contours = be_i16(m_slice.offset_pointer(offset));
+ i16 xmin = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::XMin));
+ i16 ymin = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::YMin));
+ i16 xmax = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::XMax));
+ i16 ymax = be_i16(m_slice.offset_pointer(offset + (u32)Offsets::YMax));
+ auto slice = ReadonlyBytes(m_slice.offset_pointer(offset + (u32)Sizes::GlyphHeader), m_slice.size() - offset - (u32)Sizes::GlyphHeader);
+ return Glyph(slice, xmin, ymin, xmax, ymax, num_contours);
+}
+
+}
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h b/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h
new file mode 100644
index 0000000000..2d34aaacb7
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+#include <AK/Vector.h>
+#include <LibGfx/AffineTransform.h>
+#include <LibGfx/Bitmap.h>
+#include <LibGfx/TrueTypeFont/Tables.h>
+#include <math.h>
+
+namespace TTF {
+
+class Rasterizer {
+public:
+ Rasterizer(Gfx::IntSize);
+ void draw_path(Gfx::Path&);
+ RefPtr<Gfx::Bitmap> accumulate();
+
+private:
+ void draw_line(Gfx::FloatPoint, Gfx::FloatPoint);
+
+ Gfx::IntSize m_size;
+ Vector<float> m_data;
+};
+
+class Loca {
+public:
+ static Optional<Loca> from_slice(ReadonlyBytes const&, u32 num_glyphs, IndexToLocFormat);
+ u32 get_glyph_offset(u32 glyph_id) const;
+
+private:
+ Loca(ReadonlyBytes const& slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
+ : m_slice(slice)
+ , m_num_glyphs(num_glyphs)
+ , m_index_to_loc_format(index_to_loc_format)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+ u32 m_num_glyphs { 0 };
+ IndexToLocFormat m_index_to_loc_format;
+};
+
+class Glyf {
+public:
+ class Glyph {
+ public:
+ Glyph(ReadonlyBytes const& slice, i16 xmin, i16 ymin, i16 xmax, i16 ymax, i16 num_contours = -1)
+ : m_xmin(xmin)
+ , m_ymin(ymin)
+ , m_xmax(xmax)
+ , m_ymax(ymax)
+ , m_num_contours(num_contours)
+ , m_slice(slice)
+ {
+ if (m_num_contours >= 0) {
+ m_type = Type::Simple;
+ }
+ }
+ template<typename GlyphCb>
+ RefPtr<Gfx::Bitmap> rasterize(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const
+ {
+ switch (m_type) {
+ case Type::Simple:
+ return rasterize_simple(font_ascender, font_descender, x_scale, y_scale);
+ case Type::Composite:
+ return rasterize_composite(font_ascender, font_descender, x_scale, y_scale, glyph_callback);
+ }
+ VERIFY_NOT_REACHED();
+ }
+ int ascender() const { return m_ymax; }
+ int descender() const { return m_ymin; }
+
+ private:
+ enum class Type {
+ Simple,
+ Composite,
+ };
+
+ class ComponentIterator {
+ public:
+ struct Item {
+ u16 glyph_id;
+ Gfx::AffineTransform affine;
+ };
+
+ ComponentIterator(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+ Optional<Item> next();
+
+ private:
+ ReadonlyBytes m_slice;
+ bool m_has_more { true };
+ u32 m_offset { 0 };
+ };
+
+ void rasterize_impl(Rasterizer&, Gfx::AffineTransform const&) const;
+ RefPtr<Gfx::Bitmap> rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale) const;
+ template<typename GlyphCb>
+ RefPtr<Gfx::Bitmap> rasterize_composite(i16 font_ascender, i16 font_descender, float x_scale, float y_scale, GlyphCb glyph_callback) const
+ {
+ u32 width = (u32)(ceilf((m_xmax - m_xmin) * x_scale)) + 1;
+ u32 height = (u32)(ceilf((font_ascender - font_descender) * y_scale)) + 1;
+ Rasterizer rasterizer(Gfx::IntSize(width, height));
+ auto affine = Gfx::AffineTransform().scale(x_scale, -y_scale).translate(-m_xmin, -font_ascender);
+ ComponentIterator component_iterator(m_slice);
+ while (true) {
+ auto opt_item = component_iterator.next();
+ if (!opt_item.has_value()) {
+ break;
+ }
+ auto item = opt_item.value();
+ auto affine_here = affine.multiply(item.affine);
+ auto glyph = glyph_callback(item.glyph_id);
+ glyph.rasterize_impl(rasterizer, affine_here);
+ }
+ return rasterizer.accumulate();
+ }
+
+ Type m_type { Type::Composite };
+ i16 m_xmin { 0 };
+ i16 m_ymin { 0 };
+ i16 m_xmax { 0 };
+ i16 m_ymax { 0 };
+ i16 m_num_contours { -1 };
+ ReadonlyBytes m_slice;
+ };
+
+ Glyf(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+ Glyph glyph(u32 offset) const;
+
+private:
+ enum class Offsets {
+ XMin = 2,
+ YMin = 4,
+ XMax = 6,
+ YMax = 8,
+ };
+ enum class Sizes {
+ GlyphHeader = 10,
+ };
+
+ ReadonlyBytes m_slice;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Tables.h b/Userland/Libraries/LibGfx/TrueTypeFont/Tables.h
new file mode 100644
index 0000000000..2c7a5a7d29
--- /dev/null
+++ b/Userland/Libraries/LibGfx/TrueTypeFont/Tables.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Span.h>
+
+namespace TTF {
+
+enum class IndexToLocFormat {
+ Offset16,
+ Offset32,
+};
+
+class Head {
+public:
+ static Optional<Head> from_slice(ReadonlyBytes const&);
+ u16 units_per_em() const;
+ i16 xmin() const;
+ i16 ymin() const;
+ i16 xmax() const;
+ i16 ymax() const;
+ u16 lowest_recommended_ppem() const;
+ IndexToLocFormat index_to_loc_format() const;
+
+private:
+ enum class Offsets {
+ UnitsPerEM = 18,
+ XMin = 36,
+ YMin = 38,
+ XMax = 40,
+ YMax = 42,
+ LowestRecPPEM = 46,
+ IndexToLocFormat = 50,
+ };
+ enum class Sizes {
+ Table = 54,
+ };
+
+ Head(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+};
+
+class Hhea {
+public:
+ static Optional<Hhea> from_slice(ReadonlyBytes const&);
+ i16 ascender() const;
+ i16 descender() const;
+ i16 line_gap() const;
+ u16 advance_width_max() const;
+ u16 number_of_h_metrics() const;
+
+private:
+ enum class Offsets {
+ Ascender = 4,
+ Descender = 6,
+ LineGap = 8,
+ AdvanceWidthMax = 10,
+ NumberOfHMetrics = 34,
+ };
+ enum class Sizes {
+ Table = 36,
+ };
+
+ Hhea(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+};
+
+class Maxp {
+public:
+ static Optional<Maxp> from_slice(ReadonlyBytes const&);
+ u16 num_glyphs() const;
+
+private:
+ enum class Offsets {
+ NumGlyphs = 4
+ };
+ enum class Sizes {
+ TableV0p5 = 6,
+ };
+
+ Maxp(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+};
+
+struct GlyphHorizontalMetrics {
+ u16 advance_width;
+ i16 left_side_bearing;
+};
+
+class Hmtx {
+public:
+ static Optional<Hmtx> from_slice(ReadonlyBytes const&, u32 num_glyphs, u32 number_of_h_metrics);
+ GlyphHorizontalMetrics get_glyph_horizontal_metrics(u32 glyph_id) const;
+
+private:
+ enum class Sizes {
+ LongHorMetric = 4,
+ LeftSideBearing = 2
+ };
+
+ Hmtx(ReadonlyBytes const& slice, u32 num_glyphs, u32 number_of_h_metrics)
+ : m_slice(slice)
+ , m_num_glyphs(num_glyphs)
+ , m_number_of_h_metrics(number_of_h_metrics)
+ {
+ }
+
+ ReadonlyBytes m_slice;
+ u32 m_num_glyphs { 0 };
+ u32 m_number_of_h_metrics { 0 };
+};
+
+class OS2 {
+public:
+ enum class Offsets {
+ TypographicAscender = 68,
+ TypographicDescender = 70,
+ TypographicLineGap = 72,
+ };
+
+ i16 typographic_ascender() const;
+ i16 typographic_descender() const;
+ i16 typographic_line_gap() const;
+
+ explicit OS2(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+
+private:
+ ReadonlyBytes m_slice;
+};
+
+class Name {
+public:
+ enum class Platform {
+ Unicode = 0,
+ Macintosh = 1,
+ Windows = 3,
+ };
+ enum class MacintoshLanguage {
+ English = 0,
+ };
+ enum class WindowsLanguage {
+ EnglishUnitedStates = 0x0409,
+ };
+ static Optional<Name> from_slice(ReadonlyBytes const&);
+
+ String family_name() const { return string_for_id(NameId::FamilyName); }
+ String subfamily_name() const { return string_for_id(NameId::SubfamilyName); }
+ String typographic_family_name() const { return string_for_id(NameId::TypographicFamilyName); }
+ String typographic_subfamily_name() const { return string_for_id(NameId::TypographicSubfamilyName); }
+
+private:
+ enum class NameId {
+ Copyright = 0,
+ FamilyName = 1,
+ SubfamilyName = 2,
+ UniqueIdentifier = 3,
+ FullName = 4,
+ VersionString = 5,
+ PostscriptName = 6,
+ Trademark = 7,
+ Manufacturer = 8,
+ Designer = 9,
+ Description = 10,
+ TypographicFamilyName = 16,
+ TypographicSubfamilyName = 17,
+ };
+
+ Name(ReadonlyBytes const& slice)
+ : m_slice(slice)
+ {
+ }
+
+ String string_for_id(NameId id) const;
+
+ ReadonlyBytes m_slice;
+};
+
+}
diff --git a/Userland/Libraries/LibGfx/Typeface.h b/Userland/Libraries/LibGfx/Typeface.h
index 8d12b59c39..ae3a235b81 100644
--- a/Userland/Libraries/LibGfx/Typeface.h
+++ b/Userland/Libraries/LibGfx/Typeface.h
@@ -12,7 +12,7 @@
#include <AK/Vector.h>
#include <LibGfx/BitmapFont.h>
#include <LibGfx/Font.h>
-#include <LibTTF/Font.h>
+#include <LibGfx/TrueTypeFont/Font.h>
namespace Gfx {