From 76a439124db97a9d75400a831cac7c9601485a8c Mon Sep 17 00:00:00 2001 From: Nico Weber Date: Fri, 16 Feb 2024 19:50:30 -0500 Subject: [PATCH] LibGfx/OpenType: Make it possible to provide an external cmap If this is passed in, we don't use the cmap table from the font, but use the external lookup object instead. This conveniently happens to create a place where we can put all the Cmap configuration logic that was a bit spread out before. No behavior change yet. --- .../Libraries/LibGfx/Font/OpenType/Font.cpp | 140 +++++++++++------- .../Libraries/LibGfx/Font/OpenType/Font.h | 14 +- 2 files changed, 96 insertions(+), 58 deletions(-) diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp index 14115ab3a4..5ad33cb1bd 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp @@ -24,6 +24,86 @@ namespace OpenType { +namespace { + +class CmapCharCodeToGlyphIndex : public CharCodeToGlyphIndex { +public: + static ErrorOr> from_slice(Optional); + + virtual u32 glyph_id_for_code_point(u32 code_point) const override; + +private: + explicit CmapCharCodeToGlyphIndex(Cmap cmap) + : m_cmap(cmap) + { + } + + Cmap m_cmap; +}; + +ErrorOr> CmapCharCodeToGlyphIndex::from_slice(Optional opt_cmap_slice) +{ + if (!opt_cmap_slice.has_value()) + return Error::from_string_literal("Font is missing Cmap"); + + auto cmap = TRY(Cmap::from_slice(opt_cmap_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" + Optional active_cmap_index; + 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"); + + /* NOTE: The encoding records are sorted first by platform ID, then by encoding ID. + This means that the Windows platform will take precedence over Macintosh, which is + usually what we want here. */ + if (platform.value() == Cmap::Subtable::Platform::Unicode) { + if (subtable.encoding_id() == (u16)Cmap::Subtable::UnicodeEncoding::Unicode2_0_FullRepertoire) { + // "Encoding ID 3 should be used in conjunction with 'cmap' subtable formats 4 or 6." + active_cmap_index = i; + break; + } + if (subtable.encoding_id() == (u16)Cmap::Subtable::UnicodeEncoding::Unicode2_0_BMP_Only) { + // "Encoding ID 4 should be used in conjunction with subtable formats 10 or 12." + active_cmap_index = i; + break; + } + } else if (platform.value() == Cmap::Subtable::Platform::Windows) { + if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeFullRepertoire) { + active_cmap_index = i; + break; + } + if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeBMP) { + active_cmap_index = i; + break; + } + } else if (platform.value() == Cmap::Subtable::Platform::Macintosh) { + active_cmap_index = i; + // Intentionally no `break` so that Windows (value 3) wins over Macintosh (value 1). + } + } + if (!active_cmap_index.has_value()) + return Error::from_string_literal("No suitable cmap subtable found"); + TRY(cmap.subtable(active_cmap_index.value()).value().validate_format_can_be_read()); + cmap.set_active_index(active_cmap_index.value()); + + return adopt_nonnull_own_or_enomem(new CmapCharCodeToGlyphIndex(cmap)); +} + +u32 CmapCharCodeToGlyphIndex::glyph_id_for_code_point(u32 code_point) const +{ + return m_cmap.glyph_id_for_code_point(code_point); +} + +} + // https://learn.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header struct [[gnu::packed]] TTCHeaderV1 { Tag ttc_tag; // Font Collection ID string: 'ttcf' (used for fonts with CFF or CFF2 outlines as well as TrueType outlines) @@ -76,7 +156,7 @@ ErrorOr> Font::try_load_from_resource(Core::Resource const& return font; } -ErrorOr> Font::try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned index) +ErrorOr> Font::try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned index, OwnPtr external_cmap) { FixedMemoryStream stream { buffer }; @@ -92,7 +172,7 @@ ErrorOr> Font::try_load_from_externally_owned_memory(Readonl TRY(stream.seek(ttc_header_v1->table_directory_offsets + sizeof(u32) * index, SeekMode::SetPosition)); auto offset = TRY(stream.read_value>()); - return try_load_from_offset(buffer, offset); + return try_load_from_offset(buffer, offset, move(external_cmap)); } if (tag == Tag("OTTO")) return Error::from_string_literal("CFF fonts not supported yet"); @@ -100,11 +180,11 @@ ErrorOr> Font::try_load_from_externally_owned_memory(Readonl if (tag.to_u32() != 0x00010000 && tag != Tag("true")) return Error::from_string_literal("Not a valid font"); - return try_load_from_offset(buffer, 0); + return try_load_from_offset(buffer, 0, move(external_cmap)); } // FIXME: "loca" and "glyf" are not available for CFF fonts. -ErrorOr> Font::try_load_from_offset(ReadonlyBytes buffer, u32 offset) +ErrorOr> Font::try_load_from_offset(ReadonlyBytes buffer, u32 offset, OwnPtr external_cmap) { FixedMemoryStream stream { buffer }; TRY(stream.seek(offset, AK::SeekMode::SetPosition)); @@ -192,9 +272,7 @@ ErrorOr> Font::try_load_from_offset(ReadonlyBytes buffer, u3 return Error::from_string_literal("Font is missing Hmtx"); auto hmtx = TRY(Hmtx::from_slice(opt_hmtx_slice.value(), maxp.num_glyphs(), hhea.number_of_h_metrics())); - if (!opt_cmap_slice.has_value()) - return Error::from_string_literal("Font is missing Cmap"); - auto cmap = TRY(Cmap::from_slice(opt_cmap_slice.value())); + NonnullOwnPtr cmap = external_cmap ? external_cmap.release_nonnull() : TRY(CmapCharCodeToGlyphIndex::from_slice(opt_cmap_slice.value())); Optional loca; if (opt_loca_slice.has_value()) @@ -221,52 +299,6 @@ ErrorOr> Font::try_load_from_offset(ReadonlyBytes buffer, u3 if (opt_prep_slice.has_value()) prep = Prep(opt_prep_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" - Optional active_cmap_index; - 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"); - - /* NOTE: The encoding records are sorted first by platform ID, then by encoding ID. - This means that the Windows platform will take precedence over Macintosh, which is - usually what we want here. */ - if (platform.value() == Cmap::Subtable::Platform::Unicode) { - if (subtable.encoding_id() == (u16)Cmap::Subtable::UnicodeEncoding::Unicode2_0_FullRepertoire) { - // "Encoding ID 3 should be used in conjunction with 'cmap' subtable formats 4 or 6." - active_cmap_index = i; - break; - } - if (subtable.encoding_id() == (u16)Cmap::Subtable::UnicodeEncoding::Unicode2_0_BMP_Only) { - // "Encoding ID 4 should be used in conjunction with subtable formats 10 or 12." - active_cmap_index = i; - break; - } - } else if (platform.value() == Cmap::Subtable::Platform::Windows) { - if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeFullRepertoire) { - active_cmap_index = i; - break; - } - if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeBMP) { - active_cmap_index = i; - break; - } - } else if (platform.value() == Cmap::Subtable::Platform::Macintosh) { - active_cmap_index = i; - // Intentionally no `break` so that Windows (value 3) wins over Macintosh (value 1). - } - } - if (!active_cmap_index.has_value()) - return Error::from_string_literal("No suitable cmap subtable found"); - TRY(cmap.subtable(active_cmap_index.value()).value().validate_format_can_be_read()); - cmap.set_active_index(active_cmap_index.value()); - return adopt_ref(*new Font( move(head), move(name), @@ -644,7 +676,7 @@ void Font::populate_glyph_page(GlyphPage& glyph_page, size_t page_index) const u32 first_code_point = page_index * GlyphPage::glyphs_per_page; for (size_t i = 0; i < GlyphPage::glyphs_per_page; ++i) { u32 code_point = first_code_point + i; - glyph_page.glyph_ids[i] = m_cmap.glyph_id_for_code_point(code_point); + glyph_page.glyph_ids[i] = m_cmap->glyph_id_for_code_point(code_point); } } bool Font::has_color_bitmaps() const diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.h b/Userland/Libraries/LibGfx/Font/OpenType/Font.h index a424277101..a37cfa56c3 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.h +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.h @@ -20,12 +20,18 @@ namespace OpenType { +class CharCodeToGlyphIndex { +public: + virtual ~CharCodeToGlyphIndex() = default; + virtual u32 glyph_id_for_code_point(u32) const = 0; +}; + class Font : public Gfx::VectorFont { AK_MAKE_NONCOPYABLE(Font); public: static ErrorOr> try_load_from_resource(Core::Resource const&, unsigned index = 0); - static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0); + static ErrorOr> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0, OwnPtr external_cmap = {}); virtual Gfx::ScaledFontMetrics metrics(float x_scale, float y_scale) const override; virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override; @@ -68,7 +74,7 @@ private: EmbeddedBitmapData embedded_bitmap_data_for_glyph(u32 glyph_id) const; - static ErrorOr> try_load_from_offset(ReadonlyBytes, unsigned index = 0); + static ErrorOr> try_load_from_offset(ReadonlyBytes, unsigned index, OwnPtr external_cmap); Font( Head&& head, @@ -76,7 +82,7 @@ private: Hhea&& hhea, Maxp&& maxp, Hmtx&& hmtx, - Cmap&& cmap, + NonnullOwnPtr cmap, Optional&& loca, Optional&& glyf, Optional os2, @@ -114,7 +120,7 @@ private: Hmtx m_hmtx; Optional m_loca; Optional m_glyf; - Cmap m_cmap; + NonnullOwnPtr m_cmap; Optional m_os2; Optional m_kern; Optional m_fpgm;