summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2023-03-04 20:52:59 +0100
committerAndreas Kling <kling@serenityos.org>2023-03-06 10:52:55 +0100
commit924d23353ef633c7b013afb1d62075df2cf5c9a9 (patch)
tree9da2c45f4e15da5bd65117b5758c978c034b56c7 /Userland
parente8cc1a43733e17e5f32053abf7058a731bab763e (diff)
downloadserenity-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.cpp168
-rw-r--r--Userland/Libraries/LibGfx/Font/OpenType/Font.h13
-rw-r--r--Userland/Libraries/LibGfx/Font/ScaledFont.h2
-rw-r--r--Userland/Libraries/LibGfx/Font/VectorFont.h2
-rw-r--r--Userland/Libraries/LibGfx/Font/WOFF/Font.h2
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(); }