diff options
author | Andreas Kling <kling@serenityos.org> | 2023-03-04 20:52:59 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2023-03-06 10:52:55 +0100 |
commit | 924d23353ef633c7b013afb1d62075df2cf5c9a9 (patch) | |
tree | 9da2c45f4e15da5bd65117b5758c978c034b56c7 /Userland | |
parent | e8cc1a43733e17e5f32053abf7058a731bab763e (diff) | |
download | serenity-924d23353ef633c7b013afb1d62075df2cf5c9a9.zip |
LibGfx/OpenType: Support one specific type of embedded color bitmaps
This patch adds support for index format 1 and image format 17.
This is enough to get Noto Color Emoji working.
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibGfx/Font/OpenType/Font.cpp | 168 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Font/OpenType/Font.h | 13 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Font/ScaledFont.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Font/VectorFont.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Font/WOFF/Font.h | 2 |
5 files changed, 181 insertions, 6 deletions
diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp index 4ad4debe58..ac36cbfa6b 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp @@ -14,6 +14,7 @@ #include <LibGfx/Font/OpenType/Font.h> #include <LibGfx/Font/OpenType/Glyf.h> #include <LibGfx/Font/OpenType/Tables.h> +#include <LibGfx/PNGLoader.h> #include <LibTextCodec/Decoder.h> #include <math.h> #include <sys/mman.h> @@ -584,8 +585,68 @@ Gfx::ScaledFontMetrics Font::metrics([[maybe_unused]] float x_scale, float y_sca }; } -Gfx::ScaledGlyphMetrics Font::glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const +Font::EmbeddedBitmapData Font::embedded_bitmap_data_for_glyph(u32 glyph_id) const { + if (!has_color_bitmaps()) + return Empty {}; + + u16 first_glyph_index {}; + u16 last_glyph_index {}; + auto maybe_index_subtable = m_cblc->index_subtable_for_glyph_id(glyph_id, first_glyph_index, last_glyph_index); + if (!maybe_index_subtable.has_value()) + return Empty {}; + + auto const& index_subtable = maybe_index_subtable.value(); + auto const& bitmap_size = m_cblc->bitmap_size_for_glyph_id(glyph_id).value(); + + if (index_subtable.index_format == 1) { + auto const& index_subtable1 = *bit_cast<EBLC::IndexSubTable1 const*>(&index_subtable); + size_t size_of_array = (last_glyph_index - first_glyph_index + 1) + 1; + auto sbit_offsets = ReadonlySpan<Offset32> { index_subtable1.sbit_offsets, size_of_array }; + auto sbit_offset = sbit_offsets[glyph_id - first_glyph_index]; + size_t glyph_data_offset = sbit_offset + index_subtable.image_data_offset; + + if (index_subtable.image_format == 17) { + return EmbeddedBitmapWithFormat17 { + .bitmap_size = bitmap_size, + .format17 = *bit_cast<CBDT::Format17 const*>(m_cbdt->bytes().slice(glyph_data_offset, size_of_array).data()), + }; + } + dbgln("FIXME: Implement OpenType embedded bitmap image format {}", index_subtable.image_format); + } else { + dbgln("FIXME: Implement OpenType embedded bitmap index format {}", index_subtable.index_format); + } + + return Empty {}; +} + +Gfx::ScaledGlyphMetrics Font::glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const +{ + auto embedded_bitmap_metrics = embedded_bitmap_data_for_glyph(glyph_id).visit( + [&](EmbeddedBitmapWithFormat17 const& data) -> Optional<Gfx::ScaledGlyphMetrics> { + // FIXME: This is a pretty ugly hack to work out new scale factors based on the relationship between + // the pixels-per-em values and the font point size. It appears that bitmaps are not in the same + // coordinate space as the head table's "units per em" value. + // There's definitely some cleaner way to do this. + float x_scale = (point_width * 1.3333333f) / static_cast<float>(data.bitmap_size.ppem_x); + float y_scale = (point_height * 1.3333333f) / static_cast<float>(data.bitmap_size.ppem_y); + + return Gfx::ScaledGlyphMetrics { + .ascender = static_cast<float>(data.bitmap_size.hori.ascender) * y_scale, + .descender = static_cast<float>(data.bitmap_size.hori.descender) * y_scale, + .advance_width = static_cast<float>(data.format17.glyph_metrics.advance) * x_scale, + .left_side_bearing = static_cast<float>(data.format17.glyph_metrics.bearing_x) * x_scale, + }; + }, + [&](Empty) -> Optional<Gfx::ScaledGlyphMetrics> { + // Unsupported format or no embedded bitmap for this glyph ID. + return {}; + }); + + if (embedded_bitmap_metrics.has_value()) { + return embedded_bitmap_metrics.release_value(); + } + if (!m_loca.has_value() || !m_glyf.has_value()) { return Gfx::ScaledGlyphMetrics {}; } @@ -613,6 +674,10 @@ float Font::glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, flo RefPtr<Gfx::Bitmap> Font::rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const { + if (auto bitmap = color_bitmap(glyph_id)) { + return bitmap; + } + if (!m_loca.has_value() || !m_glyf.has_value()) { return nullptr; } @@ -711,7 +776,7 @@ 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; + return glyph_metrics(glyph_id_for_code_point('.'), 1, 1, 1, 1).advance_width == glyph_metrics(glyph_id_for_code_point('X'), 1, 1, 1, 1).advance_width; } u16 OS2::weight_class() const @@ -808,4 +873,103 @@ void Font::populate_glyph_page(GlyphPage& glyph_page, size_t page_index) const } } +ErrorOr<CBLC> CBLC::from_slice(ReadonlyBytes slice) +{ + if (slice.size() < sizeof(CblcHeader)) + return Error::from_string_literal("CBLC table too small"); + auto const& header = *bit_cast<CblcHeader const*>(slice.data()); + + size_t num_sizes = header.num_sizes; + Checked<size_t> size_used_by_bitmap_sizes = num_sizes; + size_used_by_bitmap_sizes *= sizeof(BitmapSize); + if (size_used_by_bitmap_sizes.has_overflow()) + return Error::from_string_literal("Integer overflow in CBLC table"); + + Checked<size_t> total_size = sizeof(CblcHeader); + total_size += size_used_by_bitmap_sizes; + if (total_size.has_overflow()) + return Error::from_string_literal("Integer overflow in CBLC table"); + + if (slice.size() < total_size) + return Error::from_string_literal("CBLC table too small"); + + return CBLC { slice }; +} + +Optional<CBLC::BitmapSize const&> CBLC::bitmap_size_for_glyph_id(u32 glyph_id) const +{ + for (auto const& bitmap_size : this->bitmap_sizes()) { + if (glyph_id >= bitmap_size.start_glyph_index && glyph_id <= bitmap_size.end_glyph_index) { + return bitmap_size; + } + } + return {}; +} + +ErrorOr<CBDT> CBDT::from_slice(ReadonlyBytes slice) +{ + if (slice.size() < sizeof(CbdtHeader)) + return Error::from_string_literal("CBDT table too small"); + return CBDT { slice }; +} + +bool Font::has_color_bitmaps() const +{ + return m_cblc.has_value() && m_cbdt.has_value(); +} + +Optional<EBLC::IndexSubHeader const&> CBLC::index_subtable_for_glyph_id(u32 glyph_id, u16& first_glyph_index, u16& last_glyph_index) const +{ + auto maybe_bitmap_size = bitmap_size_for_glyph_id(glyph_id); + if (!maybe_bitmap_size.has_value()) { + return {}; + } + auto const& bitmap_size = maybe_bitmap_size.value(); + + Checked<size_t> required_size = static_cast<u32>(bitmap_size.index_subtable_array_offset); + required_size += bitmap_size.index_tables_size; + + if (m_slice.size() < required_size) { + dbgln("CBLC index subtable array goes out of bounds"); + return {}; + } + + auto index_subtables_slice = m_slice.slice(bitmap_size.index_subtable_array_offset, bitmap_size.index_tables_size); + ReadonlySpan<EBLC::IndexSubTableArray> index_subtable_arrays { + bit_cast<EBLC::IndexSubTableArray const*>(index_subtables_slice.data()), bitmap_size.number_of_index_subtables + }; + + EBLC::IndexSubTableArray const* index_subtable_array = nullptr; + for (auto const& array : index_subtable_arrays) { + if (glyph_id >= array.first_glyph_index && glyph_id <= array.last_glyph_index) + index_subtable_array = &array; + } + if (!index_subtable_array) { + return {}; + } + + auto index_subtable_slice = m_slice.slice(bitmap_size.index_subtable_array_offset + index_subtable_array->additional_offset_to_index_subtable); + first_glyph_index = index_subtable_array->first_glyph_index; + last_glyph_index = index_subtable_array->last_glyph_index; + return *bit_cast<EBLC::IndexSubHeader const*>(index_subtable_slice.data()); +} + +RefPtr<Gfx::Bitmap> Font::color_bitmap(u32 glyph_id) const +{ + return embedded_bitmap_data_for_glyph(glyph_id).visit( + [&](EmbeddedBitmapWithFormat17 const& data) -> RefPtr<Gfx::Bitmap> { + auto data_slice = ReadonlyBytes { data.format17.data, static_cast<u32>(data.format17.data_len) }; + auto decoder = Gfx::PNGImageDecoderPlugin::create(data_slice).release_value_but_fixme_should_propagate_errors(); + auto frame = decoder->frame(0); + if (frame.is_error()) { + dbgln("PNG decode failed"); + return nullptr; + } + return frame.value().image; + }, + [&](Empty) -> RefPtr<Gfx::Bitmap> { + // Unsupported format or no image for this glyph ID. + return nullptr; + }); +} } diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.h b/Userland/Libraries/LibGfx/Font/OpenType/Font.h index 6dc164d095..b542fc4d3e 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.h +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.h @@ -28,7 +28,7 @@ public: static ErrorOr<NonnullRefPtr<Font>> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0); 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) const override; + virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override; virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const override; virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset) const override; virtual u32 glyph_count() const override; @@ -47,6 +47,17 @@ public: Optional<ReadonlyBytes> glyph_program(u32 glyph_id) const; private: + RefPtr<Gfx::Bitmap> color_bitmap(u32 glyph_id) const; + + struct EmbeddedBitmapWithFormat17 { + CBLC::BitmapSize const& bitmap_size; + CBDT::Format17 const& format17; + }; + + using EmbeddedBitmapData = Variant<EmbeddedBitmapWithFormat17, Empty>; + + EmbeddedBitmapData embedded_bitmap_data_for_glyph(u32 glyph_id) const; + enum class Offsets { NumTables = 4, TableRecord_Offset = 8, diff --git a/Userland/Libraries/LibGfx/Font/ScaledFont.h b/Userland/Libraries/LibGfx/Font/ScaledFont.h index 7a0c37d8d5..7615665d14 100644 --- a/Userland/Libraries/LibGfx/Font/ScaledFont.h +++ b/Userland/Libraries/LibGfx/Font/ScaledFont.h @@ -29,7 +29,7 @@ public: ScaledFont(NonnullRefPtr<VectorFont>, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI); 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); } + ScaledGlyphMetrics glyph_metrics(u32 glyph_id) const { return m_font->glyph_metrics(glyph_id, m_x_scale, m_y_scale, m_point_width, m_point_height); } RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, GlyphSubpixelOffset) const; // ^Gfx::Font diff --git a/Userland/Libraries/LibGfx/Font/VectorFont.h b/Userland/Libraries/LibGfx/Font/VectorFont.h index 82d7317db5..06a5693488 100644 --- a/Userland/Libraries/LibGfx/Font/VectorFont.h +++ b/Userland/Libraries/LibGfx/Font/VectorFont.h @@ -35,7 +35,7 @@ class VectorFont : public RefCounted<VectorFont> { public: virtual ~VectorFont() { } virtual ScaledFontMetrics metrics(float x_scale, float y_scale) const = 0; - virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const = 0; + virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const = 0; virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const = 0; virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, GlyphSubpixelOffset) const = 0; virtual u32 glyph_count() const = 0; diff --git a/Userland/Libraries/LibGfx/Font/WOFF/Font.h b/Userland/Libraries/LibGfx/Font/WOFF/Font.h index c72db1b3a3..cec576368c 100644 --- a/Userland/Libraries/LibGfx/Font/WOFF/Font.h +++ b/Userland/Libraries/LibGfx/Font/WOFF/Font.h @@ -25,7 +25,7 @@ public: static ErrorOr<NonnullRefPtr<Font>> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0); virtual Gfx::ScaledFontMetrics metrics(float x_scale, float y_scale) const override { return m_input_font->metrics(x_scale, y_scale); } - virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale) const override { return m_input_font->glyph_metrics(glyph_id, x_scale, y_scale); } + virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override { return m_input_font->glyph_metrics(glyph_id, x_scale, y_scale, point_width, point_height); } virtual float glyphs_horizontal_kerning(u32 left_glyph_id, u32 right_glyph_id, float x_scale) const override { return m_input_font->glyphs_horizontal_kerning(left_glyph_id, right_glyph_id, x_scale); } virtual RefPtr<Gfx::Bitmap> rasterize_glyph(u32 glyph_id, float x_scale, float y_scale, Gfx::GlyphSubpixelOffset subpixel_offset) const override { return m_input_font->rasterize_glyph(glyph_id, x_scale, y_scale, subpixel_offset); } virtual u32 glyph_count() const override { return m_input_font->glyph_count(); } |