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.
This commit is contained in:
Andreas Kling 2023-03-04 20:52:59 +01:00
parent e8cc1a4373
commit 924d23353e
Notes: sideshowbarker 2024-07-17 06:51:48 +09:00
5 changed files with 181 additions and 6 deletions

View file

@ -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;
});
}
}

View file

@ -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,

View file

@ -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

View file

@ -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;

View file

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