mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-26 09:30:24 +00:00
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:
parent
e8cc1a4373
commit
924d23353e
Notes:
sideshowbarker
2024-07-17 06:51:48 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/924d23353e
5 changed files with 181 additions and 6 deletions
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); }
|
||||
|
|
Loading…
Reference in a new issue