Sfoglia il codice sorgente

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.
Andreas Kling 2 anni fa
parent
commit
924d23353e

+ 166 - 2
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;
+        });
+}
 }

+ 12 - 1
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,

+ 1 - 1
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

+ 1 - 1
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;

+ 1 - 1
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(); }