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;