From 6f8fd91f225c9eb3e5faeda1aaf6cee1e1f12cc9 Mon Sep 17 00:00:00 2001 From: Simon Wanner Date: Sat, 9 Apr 2022 08:52:59 +0200 Subject: LibGfx: Move TTF files from TrueTypeFont/ to Font/TrueType/ --- Userland/Libraries/LibGfx/CMakeLists.txt | 32 +- Userland/Libraries/LibGfx/Font/TrueType/Cmap.cpp | 158 +++++ Userland/Libraries/LibGfx/Font/TrueType/Cmap.h | 111 ++++ Userland/Libraries/LibGfx/Font/TrueType/Font.cpp | 766 +++++++++++++++++++++++ Userland/Libraries/LibGfx/Font/TrueType/Font.h | 170 +++++ Userland/Libraries/LibGfx/Font/TrueType/Glyf.cpp | 504 +++++++++++++++ Userland/Libraries/LibGfx/Font/TrueType/Glyf.h | 156 +++++ Userland/Libraries/LibGfx/Font/TrueType/Tables.h | 230 +++++++ Userland/Libraries/LibGfx/FontDatabase.cpp | 2 +- Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp | 158 ----- Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h | 111 ---- Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp | 766 ----------------------- Userland/Libraries/LibGfx/TrueTypeFont/Font.h | 170 ----- Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp | 504 --------------- Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h | 156 ----- Userland/Libraries/LibGfx/TrueTypeFont/Tables.h | 230 ------- Userland/Libraries/LibGfx/Typeface.h | 2 +- Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h | 2 +- Userland/Libraries/LibWeb/CSS/StyleComputer.cpp | 2 +- 19 files changed, 2115 insertions(+), 2115 deletions(-) create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Cmap.cpp create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Cmap.h create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Font.cpp create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Font.h create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Glyf.cpp create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Glyf.h create mode 100644 Userland/Libraries/LibGfx/Font/TrueType/Tables.h delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Font.h delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h delete mode 100644 Userland/Libraries/LibGfx/TrueTypeFont/Tables.h (limited to 'Userland') diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index ad6bddf71d..ff1aeb44da 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -29,22 +29,22 @@ set(SOURCES PNGLoader.cpp PNGWriter.cpp PPMLoader.cpp - Point.cpp - QOILoader.cpp - Rect.cpp - ShareableBitmap.cpp - Size.cpp - StylePainter.cpp - SystemTheme.cpp - TextDirection.cpp - TextLayout.cpp - Triangle.cpp - TrueTypeFont/Font.cpp - TrueTypeFont/Glyf.cpp - TrueTypeFont/Cmap.cpp - Typeface.cpp - WindowTheme.cpp -) + Point.cpp + QOILoader.cpp + Rect.cpp + ShareableBitmap.cpp + Size.cpp + StylePainter.cpp + SystemTheme.cpp + TextDirection.cpp + TextLayout.cpp + Triangle.cpp + Font/TrueType/Font.cpp + Font/TrueType/Glyf.cpp + Font/TrueType/Cmap.cpp + Typeface.cpp + WindowTheme.cpp + ) serenity_lib(LibGfx gfx) target_link_libraries(LibGfx LibM LibCompress LibCore LibTextCodec LibIPC) diff --git a/Userland/Libraries/LibGfx/Font/TrueType/Cmap.cpp b/Userland/Libraries/LibGfx/Font/TrueType/Cmap.cpp new file mode 100644 index 0000000000..f37eea9691 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Cmap.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace TTF { + +extern u16 be_u16(u8 const*); +extern u32 be_u32(u8 const*); +extern i16 be_i16(u8 const*); + +Optional 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: + return {}; + } +} + +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(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)); + if (subtable_offset >= m_slice.size()) + return {}; + 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; + + u32 segcount = segcount_x2 / 2; + u32 l = 0, r = segcount - 1; + while (l < r) { + u32 mid = l + (r - l) / 2; + u32 end_code_point_at_mid = be_u16(m_slice.offset_pointer((u32)Table4Offsets::EndConstBase + (mid * 2))); + if (code_point <= end_code_point_at_mid) + r = mid; + else + l = mid + 1; + } + + u32 offset = l * 2; + u32 start_code_point = be_u16(m_slice.offset_pointer((u32)Table4Offsets::StartConstBase + segcount_x2 + offset)); + if (start_code_point > code_point) + return 0; + + 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; +} + +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::from_slice(ReadonlyBytes slice) +{ + if (slice.size() < (size_t)Sizes::TableHeader) + return {}; + return Cmap(slice); +} + +} diff --git a/Userland/Libraries/LibGfx/Font/TrueType/Cmap.h b/Userland/Libraries/LibGfx/Font/TrueType/Cmap.h new file mode 100644 index 0000000000..64eea73662 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Cmap.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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 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; + Optional 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 from_slice(ReadonlyBytes); + u32 num_subtables() const; + Optional 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 slice) + : m_slice(slice) + { + } + + ReadonlyBytes m_slice; + u32 m_active_index { UINT32_MAX }; +}; + +} diff --git a/Userland/Libraries/LibGfx/Font/TrueType/Font.cpp b/Userland/Libraries/LibGfx/Font/TrueType/Font.cpp new file mode 100644 index 0000000000..2cf2dea9d0 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Font.cpp @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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::from_slice(ReadonlyBytes 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::style() const +{ + return be_u16(m_slice.offset_pointer((u32)Offsets::Style)); +} + +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::from_slice(ReadonlyBytes 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::from_slice(ReadonlyBytes 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::from_slice(ReadonlyBytes 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::from_slice(ReadonlyBytes slice) +{ + return Name(slice); +} + +ErrorOr Kern::from_slice(ReadonlyBytes slice) +{ + if (slice.size() < sizeof(u32)) + return Error::from_string_literal("Invalid kern table header"sv); + + // We only support the old (2x u16) version of the header + auto version = be_u16(slice.data()); + auto number_of_subtables = be_u16(slice.offset(sizeof(u16))); + if (version != 0) + return Error::from_string_literal("Unsupported kern table version"sv); + if (number_of_subtables == 0) + return Error::from_string_literal("Kern table does not contain any subtables"sv); + + // Read all subtable offsets + auto subtable_offsets = TRY(FixedArray::try_create(number_of_subtables)); + size_t offset = 2 * sizeof(u16); + for (size_t i = 0; i < number_of_subtables; ++i) { + if (slice.size() < offset + Sizes::SubtableHeader) + return Error::from_string_literal("Invalid kern subtable header"sv); + + subtable_offsets[i] = offset; + auto subtable_size = be_u16(slice.offset(offset + sizeof(u16))); + offset += subtable_size; + } + + return Kern(slice, move(subtable_offsets)); +} + +i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const +{ + VERIFY(left_glyph_id > 0 && right_glyph_id > 0); + + i16 glyph_kerning = 0; + for (auto subtable_offset : m_subtable_offsets) { + auto subtable_slice = m_slice.slice(subtable_offset); + + auto version = be_u16(subtable_slice.data()); + auto length = be_u16(subtable_slice.offset(sizeof(u16))); + auto coverage = be_u16(subtable_slice.offset(2 * sizeof(u16))); + + if (version != 0) { + dbgln("TTF::Kern: unsupported subtable version {}", version); + continue; + } + + if (subtable_slice.size() < length) { + dbgln("TTF::Kern: subtable has an invalid size {}", length); + continue; + } + + auto is_horizontal = (coverage & (1 << 0)) > 0; + auto is_minimum = (coverage & (1 << 1)) > 0; + auto is_cross_stream = (coverage & (1 << 2)) > 0; + auto is_override = (coverage & (1 << 3)) > 0; + auto reserved_bits = (coverage & 0xF0); + auto format = (coverage & 0xFF00) >> 8; + + // FIXME: implement support for these features + if (!is_horizontal || is_minimum || is_cross_stream || (reserved_bits > 0)) { + dbgln("TTF::Kern: FIXME: implement missing feature support for subtable"); + continue; + } + + // FIXME: implement support for subtable formats other than 0 + Optional subtable_kerning; + switch (format) { + case 0: + subtable_kerning = read_glyph_kerning_format0(subtable_slice.slice(Sizes::SubtableHeader), left_glyph_id, right_glyph_id); + break; + default: + dbgln("TTF::Kern: FIXME: subtable format {} is unsupported", format); + continue; + } + if (!subtable_kerning.has_value()) + continue; + auto kerning_value = subtable_kerning.release_value(); + + if (is_override) + glyph_kerning = kerning_value; + else + glyph_kerning += kerning_value; + } + return glyph_kerning; +} + +Optional Kern::read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_glyph_id, u16 right_glyph_id) +{ + if (slice.size() < 4 * sizeof(u16)) + return {}; + + u16 number_of_pairs = be_u16(slice.data()); + u16 search_range = be_u16(slice.offset_pointer(sizeof(u16))); + u16 entry_selector = be_u16(slice.offset_pointer(2 * sizeof(u16))); + u16 range_shift = be_u16(slice.offset_pointer(3 * sizeof(u16))); + + // Sanity checks for this table format + auto pairs_in_search_range = search_range / Sizes::Format0Entry; + if (number_of_pairs == 0) + return {}; + if (pairs_in_search_range > number_of_pairs) + return {}; + if ((1 << entry_selector) * Sizes::Format0Entry != search_range) + return {}; + if ((number_of_pairs - pairs_in_search_range) * Sizes::Format0Entry != range_shift) + return {}; + + // FIXME: implement a possibly slightly more efficient binary search using the parameters above + auto search_slice = slice.slice(4 * sizeof(u16)); + size_t left_idx = 0; + size_t right_idx = number_of_pairs - 1; + for (auto i = 0; i < 16; ++i) { + size_t pivot_idx = (left_idx + right_idx) / 2; + + u16 pivot_left_glyph_id = be_u16(search_slice.offset(pivot_idx * Sizes::Format0Entry + 0)); + u16 pivot_right_glyph_id = be_u16(search_slice.offset(pivot_idx * Sizes::Format0Entry + 2)); + + // Match + if (pivot_left_glyph_id == left_glyph_id && pivot_right_glyph_id == right_glyph_id) + return be_i16(search_slice.offset(pivot_idx * Sizes::Format0Entry + 4)); + + // Narrow search area + if (pivot_left_glyph_id < left_glyph_id || (pivot_left_glyph_id == left_glyph_id && pivot_right_glyph_id < right_glyph_id)) + left_idx = pivot_idx + 1; + else if (pivot_idx == left_idx) + break; + else + right_idx = pivot_idx - 1; + } + return 0; +} + +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 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, + }; +} + +ErrorOr> Font::try_load_from_file(String path, unsigned index) +{ + auto file = TRY(Core::MappedFile::map(path)); + auto font = TRY(try_load_from_externally_owned_memory(file->bytes(), index)); + font->m_mapped_file = move(file); + return font; +} + +ErrorOr> Font::try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned index) +{ + if (buffer.size() < 4) + return Error::from_string_literal("Font file too small"sv); + + 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 Error::from_string_literal("Font file too small"sv); + + u32 offset = be_u32(buffer.offset_pointer((u32)Sizes::TTCHeaderV1 + sizeof(u32) * index)); + return try_load_from_offset(buffer, offset); + } + if (tag == tag_from_str("OTTO")) + return Error::from_string_literal("CFF fonts not supported yet"sv); + + if (tag != 0x00010000) + return Error::from_string_literal("Not a valid font"sv); + + return try_load_from_offset(buffer, 0); +} + +// FIXME: "loca" and "glyf" are not available for CFF fonts. +ErrorOr> Font::try_load_from_offset(ReadonlyBytes buffer, u32 offset) +{ + if (Checked::addition_would_overflow(offset, (u32)Sizes::OffsetTable)) + return Error::from_string_literal("Invalid offset in font header"sv); + + if (buffer.size() < offset + (u32)Sizes::OffsetTable) + return Error::from_string_literal("Font file too small"sv); + + Optional opt_head_slice = {}; + Optional opt_name_slice = {}; + Optional opt_hhea_slice = {}; + Optional opt_maxp_slice = {}; + Optional opt_hmtx_slice = {}; + Optional opt_cmap_slice = {}; + Optional opt_loca_slice = {}; + Optional opt_glyf_slice = {}; + Optional opt_os2_slice = {}; + Optional opt_kern_slice = {}; + + Optional opt_head = {}; + Optional opt_name = {}; + Optional opt_hhea = {}; + Optional opt_maxp = {}; + Optional opt_hmtx = {}; + Optional opt_cmap = {}; + Optional opt_loca = {}; + Optional opt_os2 = {}; + Optional opt_kern = {}; + + 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 Error::from_string_literal("Font file too small"sv); + + 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::addition_would_overflow(table_offset, table_length)) + return Error::from_string_literal("Invalid table offset or length in font"sv); + + if (buffer.size() < table_offset + table_length) + return Error::from_string_literal("Font file too small"sv); + + 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; + } else if (tag == tag_from_str("kern")) { + opt_kern_slice = buffer_here; + } + } + + if (!opt_head_slice.has_value() || !(opt_head = Head::from_slice(opt_head_slice.value())).has_value()) + return Error::from_string_literal("Could not load Head"sv); + auto head = opt_head.value(); + + if (!opt_name_slice.has_value() || !(opt_name = Name::from_slice(opt_name_slice.value())).has_value()) + return Error::from_string_literal("Could not load Name"sv); + auto name = opt_name.value(); + + if (!opt_hhea_slice.has_value() || !(opt_hhea = Hhea::from_slice(opt_hhea_slice.value())).has_value()) + return Error::from_string_literal("Could not load Hhea"sv); + auto hhea = opt_hhea.value(); + + if (!opt_maxp_slice.has_value() || !(opt_maxp = Maxp::from_slice(opt_maxp_slice.value())).has_value()) + return Error::from_string_literal("Could not load Maxp"sv); + 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 Error::from_string_literal("Could not load Hmtx"sv); + auto hmtx = opt_hmtx.value(); + + if (!opt_cmap_slice.has_value() || !(opt_cmap = Cmap::from_slice(opt_cmap_slice.value())).has_value()) + return Error::from_string_literal("Could not load Cmap"sv); + 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 Error::from_string_literal("Could not load Loca"sv); + auto loca = opt_loca.value(); + + if (!opt_glyf_slice.has_value()) + return Error::from_string_literal("Could not load Glyf"sv); + auto glyf = Glyf(opt_glyf_slice.value()); + + if (!opt_os2_slice.has_value()) + return Error::from_string_literal("Could not load OS/2"sv); + auto os2 = OS2(opt_os2_slice.value()); + + Optional kern {}; + if (opt_kern_slice.has_value()) + kern = TRY(Kern::from_slice(opt_kern_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(); + auto platform = subtable.platform_id(); + if (!platform.has_value()) + return Error::from_string_literal("Invalid Platform ID"sv); + + if (platform.value() == 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), move(kern))); +} + +ScaledFontMetrics Font::metrics([[maybe_unused]] 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; + + return ScaledFontMetrics { + .ascender = ascender, + .descender = descender, + .line_gap = line_gap, + }; +} + +// 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), + }; +} + +float Font::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const +{ + if (!m_kern.has_value()) + return 0.f; + return m_kern->get_glyph_kerning(left_glyph_id, right_glyph_id) * x_scale; +} + +// FIXME: "loca" and "glyf" are not available for CFF fonts. +RefPtr 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_hhea.ascender(), m_hhea.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 +{ + constexpr u16 bold_bit { 1 }; + if (m_os2.weight_class()) + return m_os2.weight_class(); + if (m_head.style() & bold_bit) + return 700; + + return 400; +} + +u8 Font::slope() const +{ + // https://docs.microsoft.com/en-us/typography/opentype/spec/os2 + constexpr u16 italic_selection_bit { 1 }; + constexpr u16 oblique_selection_bit { 512 }; + // https://docs.microsoft.com/en-us/typography/opentype/spec/head + constexpr u16 italic_style_bit { 2 }; + + if (m_os2.selection() & oblique_selection_bit) + return 2; + if (m_os2.selection() & italic_selection_bit) + return 1; + if (m_head.style() & italic_style_bit) + return 1; + + return 0; +} + +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 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 +ALWAYS_INLINE int ScaledFont::unicode_view_width(T const& view) const +{ + if (view.is_empty()) + return 0; + int width = 0; + int longest_width = 0; + u32 last_code_point = 0; + for (auto code_point : view) { + if (code_point == '\n' || code_point == '\r') { + longest_width = max(width, longest_width); + width = 0; + last_code_point = code_point; + continue; + } + u32 glyph_id = glyph_id_for_code_point(code_point); + auto kerning = glyphs_horizontal_kerning(last_code_point, code_point); + width += kerning + glyph_metrics(glyph_id).advance_width; + last_code_point = code_point; + } + longest_width = max(width, longest_width); + return longest_width; +} + +RefPtr 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(u32 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; +} + +float ScaledFont::glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const +{ + if (left_code_point == 0 || right_code_point == 0) + return 0.f; + + auto left_glyph_id = glyph_id_for_code_point(left_code_point); + auto right_glyph_id = glyph_id_for_code_point(right_code_point); + if (left_glyph_id == 0 || right_glyph_id == 0) + return 0.f; + + return m_font->glyphs_horizontal_kerning(left_glyph_id, right_glyph_id, m_x_scale); +} + +u8 ScaledFont::glyph_fixed_width() const +{ + return glyph_metrics(glyph_id_for_code_point(' ')).advance_width; +} + +Gfx::FontPixelMetrics ScaledFont::pixel_metrics() const +{ + auto metrics = m_font->metrics(m_x_scale, m_y_scale); + + return Gfx::FontPixelMetrics { + .size = (float)pixel_size(), + .x_height = (float)x_height(), + .advance_of_ascii_zero = (float)glyph_width('0'), + .glyph_spacing = (float)glyph_spacing(), + .ascent = metrics.ascender, + .descent = -metrics.descender, + .line_gap = metrics.line_gap, + }; +} + +u16 OS2::weight_class() const +{ + return be_u16(m_slice.offset_pointer((u32)Offsets::WeightClass)); +} + +u16 OS2::selection() const +{ + return be_u16(m_slice.offset_pointer((u32)Offsets::Selection)); +} + +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/Font/TrueType/Font.h b/Userland/Libraries/LibGfx/Font/TrueType/Font.h new file mode 100644 index 0000000000..6e037188e5 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Font.h @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define POINTS_PER_INCH 72.0f +#define DEFAULT_DPI 96 + +namespace TTF { + +struct ScaledFontMetrics { + float ascender { 0 }; + float descender { 0 }; + float line_gap { 0 }; + + int height() const + { + return ascender - descender; + } +}; + +struct ScaledGlyphMetrics { + int ascender; + int descender; + int advance_width; + int left_side_bearing; +}; + +class Font : public RefCounted { + AK_MAKE_NONCOPYABLE(Font); + +public: + static ErrorOr> try_load_from_file(String path, unsigned index = 0); + static ErrorOr> 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; + float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const; + RefPtr 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; + u8 slope() 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 ErrorOr> 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, Optional&& kern) + : 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)) + , m_kern(move(kern)) + { + } + + RefPtr 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; + Optional m_kern; +}; + +class ScaledFont : public Gfx::Font { +public: + ScaledFont(NonnullRefPtr 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 rasterize_glyph(u32 glyph_id) const; + + // ^Gfx::Font + virtual NonnullRefPtr clone() const override { return *this; } // FIXME: clone() should not need to be implemented + virtual u8 presentation_size() const override { return m_point_height; } + virtual int pixel_size() const override { return m_point_height * 1.33333333f; } + virtual float point_size() const override { return m_point_height; } + virtual Gfx::FontPixelMetrics pixel_metrics() const override; + virtual u8 slope() const override { return m_font->slope(); } + 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(u32 code_point) const override; + virtual int glyph_or_emoji_width(u32 code_point) const override; + virtual float glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const override; + virtual int preferred_line_height() const override { return metrics().height() + metrics().line_gap; } + 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 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 0; } + 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(), slope()); } + virtual String human_readable_name() const override { return String::formatted("{} {} {}", family(), variant(), presentation_size()); } + +private: + NonnullRefPtr 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> m_cached_glyph_bitmaps; + + template + int unicode_view_width(T const& view) const; +}; + +} diff --git a/Userland/Libraries/LibGfx/Font/TrueType/Glyf.cpp b/Userland/Libraries/LibGfx/Font/TrueType/Glyf.cpp new file mode 100644 index 0000000000..1b25f9c644 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Glyf.cpp @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +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 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 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::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 { + // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this. + } + 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) { + // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this. + } + if (flags & (u16)CompositeGlyfFlags::UnscaledComponentOffset) { + // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this. + } + 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 Rasterizer::accumulate() +{ + auto bitmap_or_error = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, m_size); + if (bitmap_or_error.is_error()) + return {}; + auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors(); + 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::from_slice(ReadonlyBytes 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 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 contour_start = {}; + Optional 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 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/Font/TrueType/Glyf.h b/Userland/Libraries/LibGfx/Font/TrueType/Glyf.h new file mode 100644 index 0000000000..ef4de03ac0 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Glyf.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace TTF { + +class Rasterizer { +public: + Rasterizer(Gfx::IntSize); + void draw_path(Gfx::Path&); + RefPtr accumulate(); + +private: + void draw_line(Gfx::FloatPoint, Gfx::FloatPoint); + + Gfx::IntSize m_size; + Vector m_data; +}; + +class Loca { +public: + static Optional from_slice(ReadonlyBytes, u32 num_glyphs, IndexToLocFormat); + u32 get_glyph_offset(u32 glyph_id) const; + +private: + Loca(ReadonlyBytes 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 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 + RefPtr 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 slice) + : m_slice(slice) + { + } + Optional next(); + + private: + ReadonlyBytes m_slice; + bool m_has_more { true }; + u32 m_offset { 0 }; + }; + + void rasterize_impl(Rasterizer&, Gfx::AffineTransform const&) const; + RefPtr rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale) const; + template + RefPtr 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 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/Font/TrueType/Tables.h b/Userland/Libraries/LibGfx/Font/TrueType/Tables.h new file mode 100644 index 0000000000..73cbf998f9 --- /dev/null +++ b/Userland/Libraries/LibGfx/Font/TrueType/Tables.h @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2020, Srimanta Barua + * Copyright (c) 2022, Jelle Raaijmakers + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace TTF { + +enum class IndexToLocFormat { + Offset16, + Offset32, +}; + +class Head { +public: + static Optional from_slice(ReadonlyBytes); + u16 units_per_em() const; + i16 xmin() const; + i16 ymin() const; + i16 xmax() const; + i16 ymax() const; + u16 style() 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, + Style = 44, + LowestRecPPEM = 46, + IndexToLocFormat = 50, + }; + enum class Sizes { + Table = 54, + }; + + Head(ReadonlyBytes slice) + : m_slice(slice) + { + } + + ReadonlyBytes m_slice; +}; + +class Hhea { +public: + static Optional from_slice(ReadonlyBytes); + 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 slice) + : m_slice(slice) + { + } + + ReadonlyBytes m_slice; +}; + +class Maxp { +public: + static Optional from_slice(ReadonlyBytes); + u16 num_glyphs() const; + +private: + enum class Offsets { + NumGlyphs = 4 + }; + enum class Sizes { + TableV0p5 = 6, + }; + + Maxp(ReadonlyBytes slice) + : m_slice(slice) + { + } + + ReadonlyBytes m_slice; +}; + +struct GlyphHorizontalMetrics { + u16 advance_width; + i16 left_side_bearing; +}; + +class Hmtx { +public: + static Optional from_slice(ReadonlyBytes, 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 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 { + WeightClass = 4, + Selection = 62, + TypographicAscender = 68, + TypographicDescender = 70, + TypographicLineGap = 72, + }; + + u16 weight_class() const; + u16 selection() const; + i16 typographic_ascender() const; + i16 typographic_descender() const; + i16 typographic_line_gap() const; + + explicit OS2(ReadonlyBytes 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 from_slice(ReadonlyBytes); + + 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 slice) + : m_slice(slice) + { + } + + String string_for_id(NameId id) const; + + ReadonlyBytes m_slice; +}; + +class Kern { +public: + static ErrorOr from_slice(ReadonlyBytes); + i16 get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const; + +private: + enum Sizes : size_t { + SubtableHeader = 6, + Format0Entry = 6, + }; + + Kern(ReadonlyBytes slice, FixedArray subtable_offsets) + : m_slice(slice) + , m_subtable_offsets(move(subtable_offsets)) + { + } + + static Optional read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_glyph_id, u16 right_glyph_id); + + ReadonlyBytes m_slice; + FixedArray m_subtable_offsets; +}; + +} diff --git a/Userland/Libraries/LibGfx/FontDatabase.cpp b/Userland/Libraries/LibGfx/FontDatabase.cpp index 9a7645a17f..22c867301b 100644 --- a/Userland/Libraries/LibGfx/FontDatabase.cpp +++ b/Userland/Libraries/LibGfx/FontDatabase.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp b/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp deleted file mode 100644 index e2e360c276..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.cpp +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include - -namespace TTF { - -extern u16 be_u16(u8 const*); -extern u32 be_u32(u8 const*); -extern i16 be_i16(u8 const*); - -Optional 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: - return {}; - } -} - -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(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)); - if (subtable_offset >= m_slice.size()) - return {}; - 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; - - u32 segcount = segcount_x2 / 2; - u32 l = 0, r = segcount - 1; - while (l < r) { - u32 mid = l + (r - l) / 2; - u32 end_code_point_at_mid = be_u16(m_slice.offset_pointer((u32)Table4Offsets::EndConstBase + (mid * 2))); - if (code_point <= end_code_point_at_mid) - r = mid; - else - l = mid + 1; - } - - u32 offset = l * 2; - u32 start_code_point = be_u16(m_slice.offset_pointer((u32)Table4Offsets::StartConstBase + segcount_x2 + offset)); - if (start_code_point > code_point) - return 0; - - 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; -} - -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::from_slice(ReadonlyBytes 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 deleted file mode 100644 index 64eea73662..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Cmap.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include - -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 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; - Optional 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 from_slice(ReadonlyBytes); - u32 num_subtables() const; - Optional 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 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 deleted file mode 100644 index f6d43eb052..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Font.cpp +++ /dev/null @@ -1,766 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * Copyright (c) 2021, Andreas Kling - * Copyright (c) 2022, Jelle Raaijmakers - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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::from_slice(ReadonlyBytes 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::style() const -{ - return be_u16(m_slice.offset_pointer((u32)Offsets::Style)); -} - -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::from_slice(ReadonlyBytes 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::from_slice(ReadonlyBytes 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::from_slice(ReadonlyBytes 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::from_slice(ReadonlyBytes slice) -{ - return Name(slice); -} - -ErrorOr Kern::from_slice(ReadonlyBytes slice) -{ - if (slice.size() < sizeof(u32)) - return Error::from_string_literal("Invalid kern table header"sv); - - // We only support the old (2x u16) version of the header - auto version = be_u16(slice.data()); - auto number_of_subtables = be_u16(slice.offset(sizeof(u16))); - if (version != 0) - return Error::from_string_literal("Unsupported kern table version"sv); - if (number_of_subtables == 0) - return Error::from_string_literal("Kern table does not contain any subtables"sv); - - // Read all subtable offsets - auto subtable_offsets = TRY(FixedArray::try_create(number_of_subtables)); - size_t offset = 2 * sizeof(u16); - for (size_t i = 0; i < number_of_subtables; ++i) { - if (slice.size() < offset + Sizes::SubtableHeader) - return Error::from_string_literal("Invalid kern subtable header"sv); - - subtable_offsets[i] = offset; - auto subtable_size = be_u16(slice.offset(offset + sizeof(u16))); - offset += subtable_size; - } - - return Kern(slice, move(subtable_offsets)); -} - -i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const -{ - VERIFY(left_glyph_id > 0 && right_glyph_id > 0); - - i16 glyph_kerning = 0; - for (auto subtable_offset : m_subtable_offsets) { - auto subtable_slice = m_slice.slice(subtable_offset); - - auto version = be_u16(subtable_slice.data()); - auto length = be_u16(subtable_slice.offset(sizeof(u16))); - auto coverage = be_u16(subtable_slice.offset(2 * sizeof(u16))); - - if (version != 0) { - dbgln("TTF::Kern: unsupported subtable version {}", version); - continue; - } - - if (subtable_slice.size() < length) { - dbgln("TTF::Kern: subtable has an invalid size {}", length); - continue; - } - - auto is_horizontal = (coverage & (1 << 0)) > 0; - auto is_minimum = (coverage & (1 << 1)) > 0; - auto is_cross_stream = (coverage & (1 << 2)) > 0; - auto is_override = (coverage & (1 << 3)) > 0; - auto reserved_bits = (coverage & 0xF0); - auto format = (coverage & 0xFF00) >> 8; - - // FIXME: implement support for these features - if (!is_horizontal || is_minimum || is_cross_stream || (reserved_bits > 0)) { - dbgln("TTF::Kern: FIXME: implement missing feature support for subtable"); - continue; - } - - // FIXME: implement support for subtable formats other than 0 - Optional subtable_kerning; - switch (format) { - case 0: - subtable_kerning = read_glyph_kerning_format0(subtable_slice.slice(Sizes::SubtableHeader), left_glyph_id, right_glyph_id); - break; - default: - dbgln("TTF::Kern: FIXME: subtable format {} is unsupported", format); - continue; - } - if (!subtable_kerning.has_value()) - continue; - auto kerning_value = subtable_kerning.release_value(); - - if (is_override) - glyph_kerning = kerning_value; - else - glyph_kerning += kerning_value; - } - return glyph_kerning; -} - -Optional Kern::read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_glyph_id, u16 right_glyph_id) -{ - if (slice.size() < 4 * sizeof(u16)) - return {}; - - u16 number_of_pairs = be_u16(slice.data()); - u16 search_range = be_u16(slice.offset_pointer(sizeof(u16))); - u16 entry_selector = be_u16(slice.offset_pointer(2 * sizeof(u16))); - u16 range_shift = be_u16(slice.offset_pointer(3 * sizeof(u16))); - - // Sanity checks for this table format - auto pairs_in_search_range = search_range / Sizes::Format0Entry; - if (number_of_pairs == 0) - return {}; - if (pairs_in_search_range > number_of_pairs) - return {}; - if ((1 << entry_selector) * Sizes::Format0Entry != search_range) - return {}; - if ((number_of_pairs - pairs_in_search_range) * Sizes::Format0Entry != range_shift) - return {}; - - // FIXME: implement a possibly slightly more efficient binary search using the parameters above - auto search_slice = slice.slice(4 * sizeof(u16)); - size_t left_idx = 0; - size_t right_idx = number_of_pairs - 1; - for (auto i = 0; i < 16; ++i) { - size_t pivot_idx = (left_idx + right_idx) / 2; - - u16 pivot_left_glyph_id = be_u16(search_slice.offset(pivot_idx * Sizes::Format0Entry + 0)); - u16 pivot_right_glyph_id = be_u16(search_slice.offset(pivot_idx * Sizes::Format0Entry + 2)); - - // Match - if (pivot_left_glyph_id == left_glyph_id && pivot_right_glyph_id == right_glyph_id) - return be_i16(search_slice.offset(pivot_idx * Sizes::Format0Entry + 4)); - - // Narrow search area - if (pivot_left_glyph_id < left_glyph_id || (pivot_left_glyph_id == left_glyph_id && pivot_right_glyph_id < right_glyph_id)) - left_idx = pivot_idx + 1; - else if (pivot_idx == left_idx) - break; - else - right_idx = pivot_idx - 1; - } - return 0; -} - -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 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, - }; -} - -ErrorOr> Font::try_load_from_file(String path, unsigned index) -{ - auto file = TRY(Core::MappedFile::map(path)); - auto font = TRY(try_load_from_externally_owned_memory(file->bytes(), index)); - font->m_mapped_file = move(file); - return font; -} - -ErrorOr> Font::try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned index) -{ - if (buffer.size() < 4) - return Error::from_string_literal("Font file too small"sv); - - 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 Error::from_string_literal("Font file too small"sv); - - u32 offset = be_u32(buffer.offset_pointer((u32)Sizes::TTCHeaderV1 + sizeof(u32) * index)); - return try_load_from_offset(buffer, offset); - } - if (tag == tag_from_str("OTTO")) - return Error::from_string_literal("CFF fonts not supported yet"sv); - - if (tag != 0x00010000) - return Error::from_string_literal("Not a valid font"sv); - - return try_load_from_offset(buffer, 0); -} - -// FIXME: "loca" and "glyf" are not available for CFF fonts. -ErrorOr> Font::try_load_from_offset(ReadonlyBytes buffer, u32 offset) -{ - if (Checked::addition_would_overflow(offset, (u32)Sizes::OffsetTable)) - return Error::from_string_literal("Invalid offset in font header"sv); - - if (buffer.size() < offset + (u32)Sizes::OffsetTable) - return Error::from_string_literal("Font file too small"sv); - - Optional opt_head_slice = {}; - Optional opt_name_slice = {}; - Optional opt_hhea_slice = {}; - Optional opt_maxp_slice = {}; - Optional opt_hmtx_slice = {}; - Optional opt_cmap_slice = {}; - Optional opt_loca_slice = {}; - Optional opt_glyf_slice = {}; - Optional opt_os2_slice = {}; - Optional opt_kern_slice = {}; - - Optional opt_head = {}; - Optional opt_name = {}; - Optional opt_hhea = {}; - Optional opt_maxp = {}; - Optional opt_hmtx = {}; - Optional opt_cmap = {}; - Optional opt_loca = {}; - Optional opt_os2 = {}; - Optional opt_kern = {}; - - 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 Error::from_string_literal("Font file too small"sv); - - 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::addition_would_overflow(table_offset, table_length)) - return Error::from_string_literal("Invalid table offset or length in font"sv); - - if (buffer.size() < table_offset + table_length) - return Error::from_string_literal("Font file too small"sv); - - 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; - } else if (tag == tag_from_str("kern")) { - opt_kern_slice = buffer_here; - } - } - - if (!opt_head_slice.has_value() || !(opt_head = Head::from_slice(opt_head_slice.value())).has_value()) - return Error::from_string_literal("Could not load Head"sv); - auto head = opt_head.value(); - - if (!opt_name_slice.has_value() || !(opt_name = Name::from_slice(opt_name_slice.value())).has_value()) - return Error::from_string_literal("Could not load Name"sv); - auto name = opt_name.value(); - - if (!opt_hhea_slice.has_value() || !(opt_hhea = Hhea::from_slice(opt_hhea_slice.value())).has_value()) - return Error::from_string_literal("Could not load Hhea"sv); - auto hhea = opt_hhea.value(); - - if (!opt_maxp_slice.has_value() || !(opt_maxp = Maxp::from_slice(opt_maxp_slice.value())).has_value()) - return Error::from_string_literal("Could not load Maxp"sv); - 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 Error::from_string_literal("Could not load Hmtx"sv); - auto hmtx = opt_hmtx.value(); - - if (!opt_cmap_slice.has_value() || !(opt_cmap = Cmap::from_slice(opt_cmap_slice.value())).has_value()) - return Error::from_string_literal("Could not load Cmap"sv); - 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 Error::from_string_literal("Could not load Loca"sv); - auto loca = opt_loca.value(); - - if (!opt_glyf_slice.has_value()) - return Error::from_string_literal("Could not load Glyf"sv); - auto glyf = Glyf(opt_glyf_slice.value()); - - if (!opt_os2_slice.has_value()) - return Error::from_string_literal("Could not load OS/2"sv); - auto os2 = OS2(opt_os2_slice.value()); - - Optional kern {}; - if (opt_kern_slice.has_value()) - kern = TRY(Kern::from_slice(opt_kern_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(); - auto platform = subtable.platform_id(); - if (!platform.has_value()) - return Error::from_string_literal("Invalid Platform ID"sv); - - if (platform.value() == 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), move(kern))); -} - -ScaledFontMetrics Font::metrics([[maybe_unused]] 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; - - return ScaledFontMetrics { - .ascender = ascender, - .descender = descender, - .line_gap = line_gap, - }; -} - -// 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), - }; -} - -float Font::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const -{ - if (!m_kern.has_value()) - return 0.f; - return m_kern->get_glyph_kerning(left_glyph_id, right_glyph_id) * x_scale; -} - -// FIXME: "loca" and "glyf" are not available for CFF fonts. -RefPtr 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_hhea.ascender(), m_hhea.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 -{ - constexpr u16 bold_bit { 1 }; - if (m_os2.weight_class()) - return m_os2.weight_class(); - if (m_head.style() & bold_bit) - return 700; - - return 400; -} - -u8 Font::slope() const -{ - // https://docs.microsoft.com/en-us/typography/opentype/spec/os2 - constexpr u16 italic_selection_bit { 1 }; - constexpr u16 oblique_selection_bit { 512 }; - // https://docs.microsoft.com/en-us/typography/opentype/spec/head - constexpr u16 italic_style_bit { 2 }; - - if (m_os2.selection() & oblique_selection_bit) - return 2; - if (m_os2.selection() & italic_selection_bit) - return 1; - if (m_head.style() & italic_style_bit) - return 1; - - return 0; -} - -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 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 -ALWAYS_INLINE int ScaledFont::unicode_view_width(T const& view) const -{ - if (view.is_empty()) - return 0; - int width = 0; - int longest_width = 0; - u32 last_code_point = 0; - for (auto code_point : view) { - if (code_point == '\n' || code_point == '\r') { - longest_width = max(width, longest_width); - width = 0; - last_code_point = code_point; - continue; - } - u32 glyph_id = glyph_id_for_code_point(code_point); - auto kerning = glyphs_horizontal_kerning(last_code_point, code_point); - width += kerning + glyph_metrics(glyph_id).advance_width; - last_code_point = code_point; - } - longest_width = max(width, longest_width); - return longest_width; -} - -RefPtr 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(u32 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; -} - -float ScaledFont::glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const -{ - if (left_code_point == 0 || right_code_point == 0) - return 0.f; - - auto left_glyph_id = glyph_id_for_code_point(left_code_point); - auto right_glyph_id = glyph_id_for_code_point(right_code_point); - if (left_glyph_id == 0 || right_glyph_id == 0) - return 0.f; - - return m_font->glyphs_horizontal_kerning(left_glyph_id, right_glyph_id, m_x_scale); -} - -u8 ScaledFont::glyph_fixed_width() const -{ - return glyph_metrics(glyph_id_for_code_point(' ')).advance_width; -} - -Gfx::FontPixelMetrics ScaledFont::pixel_metrics() const -{ - auto metrics = m_font->metrics(m_x_scale, m_y_scale); - - return Gfx::FontPixelMetrics { - .size = (float)pixel_size(), - .x_height = (float)x_height(), - .advance_of_ascii_zero = (float)glyph_width('0'), - .glyph_spacing = (float)glyph_spacing(), - .ascent = metrics.ascender, - .descent = -metrics.descender, - .line_gap = metrics.line_gap, - }; -} - -u16 OS2::weight_class() const -{ - return be_u16(m_slice.offset_pointer((u32)Offsets::WeightClass)); -} - -u16 OS2::selection() const -{ - return be_u16(m_slice.offset_pointer((u32)Offsets::Selection)); -} - -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 deleted file mode 100644 index 40262aa1d1..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Font.h +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define POINTS_PER_INCH 72.0f -#define DEFAULT_DPI 96 - -namespace TTF { - -struct ScaledFontMetrics { - float ascender { 0 }; - float descender { 0 }; - float line_gap { 0 }; - - int height() const - { - return ascender - descender; - } -}; - -struct ScaledGlyphMetrics { - int ascender; - int descender; - int advance_width; - int left_side_bearing; -}; - -class Font : public RefCounted { - AK_MAKE_NONCOPYABLE(Font); - -public: - static ErrorOr> try_load_from_file(String path, unsigned index = 0); - static ErrorOr> 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; - float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const; - RefPtr 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; - u8 slope() 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 ErrorOr> 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, Optional&& kern) - : 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)) - , m_kern(move(kern)) - { - } - - RefPtr 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; - Optional m_kern; -}; - -class ScaledFont : public Gfx::Font { -public: - ScaledFont(NonnullRefPtr 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 rasterize_glyph(u32 glyph_id) const; - - // ^Gfx::Font - virtual NonnullRefPtr clone() const override { return *this; } // FIXME: clone() should not need to be implemented - virtual u8 presentation_size() const override { return m_point_height; } - virtual int pixel_size() const override { return m_point_height * 1.33333333f; } - virtual float point_size() const override { return m_point_height; } - virtual Gfx::FontPixelMetrics pixel_metrics() const override; - virtual u8 slope() const override { return m_font->slope(); } - 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(u32 code_point) const override; - virtual int glyph_or_emoji_width(u32 code_point) const override; - virtual float glyphs_horizontal_kerning(u32 left_code_point, u32 right_code_point) const override; - virtual int preferred_line_height() const override { return metrics().height() + metrics().line_gap; } - 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 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 0; } - 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(), slope()); } - virtual String human_readable_name() const override { return String::formatted("{} {} {}", family(), variant(), presentation_size()); } - -private: - NonnullRefPtr 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> m_cached_glyph_bitmaps; - - template - int unicode_view_width(T const& view) const; -}; - -} diff --git a/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp b/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp deleted file mode 100644 index e2063ef25c..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.cpp +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include - -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 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 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::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 { - // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this. - } - 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) { - // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this. - } - if (flags & (u16)CompositeGlyfFlags::UnscaledComponentOffset) { - // FIXME: Implement this. There's no TODO() here since many fonts work just fine without this. - } - 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 Rasterizer::accumulate() -{ - auto bitmap_or_error = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRA8888, m_size); - if (bitmap_or_error.is_error()) - return {}; - auto bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors(); - 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::from_slice(ReadonlyBytes 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 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 contour_start = {}; - Optional 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 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 deleted file mode 100644 index 5863df25e8..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Glyf.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace TTF { - -class Rasterizer { -public: - Rasterizer(Gfx::IntSize); - void draw_path(Gfx::Path&); - RefPtr accumulate(); - -private: - void draw_line(Gfx::FloatPoint, Gfx::FloatPoint); - - Gfx::IntSize m_size; - Vector m_data; -}; - -class Loca { -public: - static Optional from_slice(ReadonlyBytes, u32 num_glyphs, IndexToLocFormat); - u32 get_glyph_offset(u32 glyph_id) const; - -private: - Loca(ReadonlyBytes 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 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 - RefPtr 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 slice) - : m_slice(slice) - { - } - Optional next(); - - private: - ReadonlyBytes m_slice; - bool m_has_more { true }; - u32 m_offset { 0 }; - }; - - void rasterize_impl(Rasterizer&, Gfx::AffineTransform const&) const; - RefPtr rasterize_simple(i16 ascender, i16 descender, float x_scale, float y_scale) const; - template - RefPtr 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 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 deleted file mode 100644 index 73cbf998f9..0000000000 --- a/Userland/Libraries/LibGfx/TrueTypeFont/Tables.h +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 2020, Srimanta Barua - * Copyright (c) 2022, Jelle Raaijmakers - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include - -namespace TTF { - -enum class IndexToLocFormat { - Offset16, - Offset32, -}; - -class Head { -public: - static Optional from_slice(ReadonlyBytes); - u16 units_per_em() const; - i16 xmin() const; - i16 ymin() const; - i16 xmax() const; - i16 ymax() const; - u16 style() 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, - Style = 44, - LowestRecPPEM = 46, - IndexToLocFormat = 50, - }; - enum class Sizes { - Table = 54, - }; - - Head(ReadonlyBytes slice) - : m_slice(slice) - { - } - - ReadonlyBytes m_slice; -}; - -class Hhea { -public: - static Optional from_slice(ReadonlyBytes); - 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 slice) - : m_slice(slice) - { - } - - ReadonlyBytes m_slice; -}; - -class Maxp { -public: - static Optional from_slice(ReadonlyBytes); - u16 num_glyphs() const; - -private: - enum class Offsets { - NumGlyphs = 4 - }; - enum class Sizes { - TableV0p5 = 6, - }; - - Maxp(ReadonlyBytes slice) - : m_slice(slice) - { - } - - ReadonlyBytes m_slice; -}; - -struct GlyphHorizontalMetrics { - u16 advance_width; - i16 left_side_bearing; -}; - -class Hmtx { -public: - static Optional from_slice(ReadonlyBytes, 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 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 { - WeightClass = 4, - Selection = 62, - TypographicAscender = 68, - TypographicDescender = 70, - TypographicLineGap = 72, - }; - - u16 weight_class() const; - u16 selection() const; - i16 typographic_ascender() const; - i16 typographic_descender() const; - i16 typographic_line_gap() const; - - explicit OS2(ReadonlyBytes 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 from_slice(ReadonlyBytes); - - 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 slice) - : m_slice(slice) - { - } - - String string_for_id(NameId id) const; - - ReadonlyBytes m_slice; -}; - -class Kern { -public: - static ErrorOr from_slice(ReadonlyBytes); - i16 get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const; - -private: - enum Sizes : size_t { - SubtableHeader = 6, - Format0Entry = 6, - }; - - Kern(ReadonlyBytes slice, FixedArray subtable_offsets) - : m_slice(slice) - , m_subtable_offsets(move(subtable_offsets)) - { - } - - static Optional read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_glyph_id, u16 right_glyph_id); - - ReadonlyBytes m_slice; - FixedArray m_subtable_offsets; -}; - -} diff --git a/Userland/Libraries/LibGfx/Typeface.h b/Userland/Libraries/LibGfx/Typeface.h index fb2600862d..052baf98d3 100644 --- a/Userland/Libraries/LibGfx/Typeface.h +++ b/Userland/Libraries/LibGfx/Typeface.h @@ -12,7 +12,7 @@ #include #include #include -#include +#include namespace Gfx { diff --git a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h index 87c0f6f86e..ffef41717e 100644 --- a/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h +++ b/Userland/Libraries/LibPDF/Fonts/TrueTypeFont.h @@ -6,7 +6,7 @@ #pragma once -#include +#include #include namespace PDF { diff --git a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp index d3074d4c5a..1e0dff24ad 100644 --- a/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp +++ b/Userland/Libraries/LibWeb/CSS/StyleComputer.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include -- cgit v1.2.3