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