From 83eca15e519d94243057a7de9decff8397dabbdf Mon Sep 17 00:00:00 2001 From: LukasACH Date: Tue, 21 Mar 2023 00:55:05 +0100 Subject: [PATCH] LibGfx/OpenType: Extend support for GPOS glyph positioning This patch extends the kerning support using the GPOS table to fonts which use PairPosFormat1. Previously, only PairPosFormat2 was supported. --- .../Libraries/LibGfx/Font/OpenType/Font.cpp | 218 +++++++++++------- .../Libraries/LibGfx/Font/OpenType/Tables.h | 56 +++++ 2 files changed, 191 insertions(+), 83 deletions(-) diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp index f2439bdaa6..a2ecbaef7d 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp +++ b/Userland/Libraries/LibGfx/Font/OpenType/Font.cpp @@ -1042,111 +1042,163 @@ Optional GPOS::glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const dbgln_if(OPENTYPE_GPOS_DEBUG, " subtableCount: {}", lookup.subtable_count); for (size_t j = 0; j < lookup.subtable_count; ++j) { - auto subtable_offset = lookup.subtable_offsets[j]; - auto subtable_slice = lookup_slice.slice(subtable_offset); + auto pair_pos_format_offset = lookup.subtable_offsets[j]; + auto pair_pos_format_slice = lookup_slice.slice(pair_pos_format_offset); - auto const& pair_pos_format = *bit_cast const*>(subtable_slice.data()); + auto const& pair_pos_format = *bit_cast const*>(pair_pos_format_slice.data()); dbgln_if(OPENTYPE_GPOS_DEBUG, "PairPosFormat{}", pair_pos_format); if (pair_pos_format == 1) { - dbgln_if(OPENTYPE_GPOS_DEBUG, "FIXME: Implement PairPosFormat1"); - continue; - } - auto const& pair_pos_format2 = *bit_cast(subtable_slice.data()); + auto const& pair_pos_format1 = *bit_cast(pair_pos_format_slice.data()); - dbgln_if(OPENTYPE_GPOS_DEBUG, " posFormat: {}", pair_pos_format2.pos_format); - dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat1: {}", pair_pos_format2.value_format1); - dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat2: {}", pair_pos_format2.value_format2); - dbgln_if(OPENTYPE_GPOS_DEBUG, " class1Count: {}", pair_pos_format2.class1_count); - dbgln_if(OPENTYPE_GPOS_DEBUG, " class2Count: {}", pair_pos_format2.class2_count); + dbgln_if(OPENTYPE_GPOS_DEBUG, " posFormat: {}", pair_pos_format1.pos_format); + dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat1: {}", pair_pos_format1.value_format1); + dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat2: {}", pair_pos_format1.value_format2); + dbgln_if(OPENTYPE_GPOS_DEBUG, " pairSetCount: {}", pair_pos_format1.pair_set_count); - auto get_class = [&](u16 glyph_id, Offset16 glyph_def_offset) -> Optional { - auto class_def_format_slice = subtable_slice.slice(glyph_def_offset); + if (pair_pos_format1.value_format1 == 0) + continue; - auto const& class_def_format = *bit_cast const*>(class_def_format_slice.data()); - if (class_def_format == 1) { - dbgln_if(OPENTYPE_GPOS_DEBUG, "FIXME: Implement ClassDefFormat1"); + auto get_coverage_index = [&](u16 glyph_id, Offset16 coverage_format_offset) -> Optional { + auto coverage_format_slice = pair_pos_format_slice.slice(coverage_format_offset); + auto const& coverage_format = *bit_cast const*>(coverage_format_slice.data()); + + dbgln_if(OPENTYPE_GPOS_DEBUG, "Coverage table format: {}", coverage_format); + + if (coverage_format == 1) { + auto const& coverage_format1 = *bit_cast(coverage_format_slice.data()); + + for (size_t k = 0; k < coverage_format1.glyph_count; ++k) + if (coverage_format1.glyph_array[k] == glyph_id) + return k; + + dbgln_if(OPENTYPE_GPOS_DEBUG, "Glyph ID {} not covered", glyph_id); + return {}; + } + + else if (coverage_format == 2) { + auto const& coverage_format2 = *bit_cast(coverage_format_slice.data()); + + for (size_t k = 0; k < coverage_format2.range_count; ++k) { + auto range_record = coverage_format2.range_records[k]; + if ((range_record.start_glyph_id <= glyph_id) && (glyph_id <= range_record.end_glyph_id)) + return range_record.start_coverage_index + glyph_id - range_record.start_glyph_id; + } + dbgln_if(OPENTYPE_GPOS_DEBUG, "Glyph ID {} not covered", glyph_id); + return {}; + } + + dbgln_if(OPENTYPE_GPOS_DEBUG, "No valid coverage table for format {}", coverage_format); + return {}; + }; + + auto coverage_index = get_coverage_index(left_glyph_id, pair_pos_format1.coverage_offset); + + if (!coverage_index.has_value()) { + dbgln_if(OPENTYPE_GPOS_DEBUG, "Glyph ID not covered by table"); return {}; } - auto const& class_def_format2 = *bit_cast(class_def_format_slice.data()); - dbgln_if(OPENTYPE_GPOS_DEBUG, "ClassDefFormat2:"); - dbgln_if(OPENTYPE_GPOS_DEBUG, " classFormat: {}", class_def_format2.class_format); - dbgln_if(OPENTYPE_GPOS_DEBUG, " classRangeCount: {}", class_def_format2.class_range_count); + auto pair_set_offset = pair_pos_format1.pair_set_offsets[coverage_index.value()]; + auto pair_set_slice = pair_pos_format_slice.slice(pair_set_offset); - for (size_t i = 0; i < class_def_format2.class_range_count; ++i) { - auto const& range = class_def_format2.class_range_records[i]; - if (glyph_id >= range.start_glyph_id && glyph_id <= range.end_glyph_id) { - dbgln_if(OPENTYPE_GPOS_DEBUG, "Found class {} for glyph ID {}", range.class_, glyph_id); - return range.class_; + auto const& pair_set = *bit_cast(pair_set_slice.data()); + + for (size_t k = 0; k < pair_set.pair_value_count; ++k) { + auto pair_value_record = pair_set.pair_value_records[k]; + if (right_glyph_id == pair_value_record.second_glyph) { + dbgln_if(OPENTYPE_GPOS_DEBUG, "Returning x advance {}", pair_value_record.value_record1.x_advance); + return pair_value_record.value_record1.x_advance; } } - - dbgln_if(OPENTYPE_GPOS_DEBUG, "No class found for glyph {}", glyph_id); - return {}; - }; - - auto left_class = get_class(left_glyph_id, pair_pos_format2.class_def1_offset); - auto right_class = get_class(right_glyph_id, pair_pos_format2.class_def2_offset); - - if (!left_class.has_value() || !right_class.has_value()) { - dbgln_if(OPENTYPE_GPOS_DEBUG, "Need glyph class for both sides"); - return {}; } - dbgln_if(OPENTYPE_GPOS_DEBUG, "Classes: {}, {}", left_class.value(), right_class.value()); + else if (pair_pos_format == 2) { + auto const& pair_pos_format2 = *bit_cast(pair_pos_format_slice.data()); - size_t value1_size = popcount(static_cast(pair_pos_format2.value_format1 & 0xff)) * sizeof(u16); - size_t value2_size = popcount(static_cast(pair_pos_format2.value_format2 & 0xff)) * sizeof(u16); - dbgln_if(OPENTYPE_GPOS_DEBUG, "ValueSizes: {}, {}", value1_size, value2_size); - size_t class2_record_size = value1_size + value2_size; - dbgln_if(OPENTYPE_GPOS_DEBUG, "Class2RecordSize: {}", class2_record_size); - size_t class1_record_size = pair_pos_format2.class2_count * class2_record_size; - dbgln_if(OPENTYPE_GPOS_DEBUG, "Class1RecordSize: {}", class1_record_size); - size_t item_offset = (left_class.value() * class1_record_size) + (right_class.value() * class2_record_size); - dbgln_if(OPENTYPE_GPOS_DEBUG, "Item offset: {}", item_offset); + dbgln_if(OPENTYPE_GPOS_DEBUG, " posFormat: {}", pair_pos_format2.pos_format); + dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat1: {}", pair_pos_format2.value_format1); + dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat2: {}", pair_pos_format2.value_format2); + dbgln_if(OPENTYPE_GPOS_DEBUG, " class1Count: {}", pair_pos_format2.class1_count); + dbgln_if(OPENTYPE_GPOS_DEBUG, " class2Count: {}", pair_pos_format2.class2_count); - auto item_slice = subtable_slice.slice(sizeof(PairPosFormat2) + item_offset); - FixedMemoryStream stream(item_slice); + auto get_class = [&](u16 glyph_id, Offset16 glyph_def_offset) -> Optional { + auto class_def_format_slice = pair_pos_format_slice.slice(glyph_def_offset); - struct ValueRecord { - i16 x_placement = 0; - i16 y_placement = 0; - i16 x_advance = 0; - i16 y_advance = 0; - i16 x_placement_device = 0; - i16 y_placement_device = 0; - i16 x_advance_device = 0; - i16 y_advance_device = 0; - }; + auto const& class_def_format = *bit_cast const*>(class_def_format_slice.data()); + if (class_def_format == 1) { + dbgln_if(OPENTYPE_GPOS_DEBUG, "FIXME: Implement ClassDefFormat1"); + return {}; + } - auto read_value_record = [&](u16 value_format) -> ValueRecord { - ValueRecord value_record; - if (value_format & static_cast(ValueFormat::X_PLACEMENT)) - value_record.x_placement = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::Y_PLACEMENT)) - value_record.y_placement = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::X_ADVANCE)) - value_record.x_advance = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::Y_ADVANCE)) - value_record.y_advance = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::X_PLACEMENT_DEVICE)) - value_record.x_placement_device = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::Y_PLACEMENT_DEVICE)) - value_record.y_placement_device = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::X_ADVANCE_DEVICE)) - value_record.x_advance_device = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - if (value_format & static_cast(ValueFormat::Y_ADVANCE_DEVICE)) - value_record.y_advance_device = stream.read_value>().release_value_but_fixme_should_propagate_errors(); - return value_record; - }; + auto const& class_def_format2 = *bit_cast(class_def_format_slice.data()); + dbgln_if(OPENTYPE_GPOS_DEBUG, "ClassDefFormat2:"); + dbgln_if(OPENTYPE_GPOS_DEBUG, " classFormat: {}", class_def_format2.class_format); + dbgln_if(OPENTYPE_GPOS_DEBUG, " classRangeCount: {}", class_def_format2.class_range_count); - [[maybe_unused]] auto value_record1 = read_value_record(pair_pos_format2.value_format1); - [[maybe_unused]] auto value_record2 = read_value_record(pair_pos_format2.value_format2); + for (size_t i = 0; i < class_def_format2.class_range_count; ++i) { + auto const& range = class_def_format2.class_range_records[i]; + if (glyph_id >= range.start_glyph_id && glyph_id <= range.end_glyph_id) { + dbgln_if(OPENTYPE_GPOS_DEBUG, "Found class {} for glyph ID {}", range.class_, glyph_id); + return range.class_; + } + } - dbgln_if(OPENTYPE_GPOS_DEBUG, "Returning x advance {}", value_record1.x_advance); - return value_record1.x_advance; + dbgln_if(OPENTYPE_GPOS_DEBUG, "No class found for glyph {}", glyph_id); + return {}; + }; + + auto left_class = get_class(left_glyph_id, pair_pos_format2.class_def1_offset); + auto right_class = get_class(right_glyph_id, pair_pos_format2.class_def2_offset); + + if (!left_class.has_value() || !right_class.has_value()) { + dbgln_if(OPENTYPE_GPOS_DEBUG, "Need glyph class for both sides"); + return {}; + } + + dbgln_if(OPENTYPE_GPOS_DEBUG, "Classes: {}, {}", left_class.value(), right_class.value()); + + size_t value1_size = popcount(static_cast(pair_pos_format2.value_format1 & 0xff)) * sizeof(u16); + size_t value2_size = popcount(static_cast(pair_pos_format2.value_format2 & 0xff)) * sizeof(u16); + dbgln_if(OPENTYPE_GPOS_DEBUG, "ValueSizes: {}, {}", value1_size, value2_size); + size_t class2_record_size = value1_size + value2_size; + dbgln_if(OPENTYPE_GPOS_DEBUG, "Class2RecordSize: {}", class2_record_size); + size_t class1_record_size = pair_pos_format2.class2_count * class2_record_size; + dbgln_if(OPENTYPE_GPOS_DEBUG, "Class1RecordSize: {}", class1_record_size); + size_t item_offset = (left_class.value() * class1_record_size) + (right_class.value() * class2_record_size); + dbgln_if(OPENTYPE_GPOS_DEBUG, "Item offset: {}", item_offset); + + auto item_slice = pair_pos_format_slice.slice(sizeof(PairPosFormat2) + item_offset); + FixedMemoryStream stream(item_slice); + + auto read_value_record = [&](u16 value_format) -> ValueRecord { + ValueRecord value_record; + if (value_format & static_cast(ValueFormat::X_PLACEMENT)) + value_record.x_placement = stream.read_value>().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::Y_PLACEMENT)) + value_record.y_placement = stream.read_value>().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::X_ADVANCE)) + value_record.x_advance = stream.read_value>().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::Y_ADVANCE)) + value_record.y_advance = stream.read_value>().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::X_PLACEMENT_DEVICE)) + value_record.x_placement_device_offset = stream.read_value().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::Y_PLACEMENT_DEVICE)) + value_record.y_placement_device_offset = stream.read_value().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::X_ADVANCE_DEVICE)) + value_record.x_advance_device_offset = stream.read_value().release_value_but_fixme_should_propagate_errors(); + if (value_format & static_cast(ValueFormat::Y_ADVANCE_DEVICE)) + value_record.y_advance_device_offset = stream.read_value().release_value_but_fixme_should_propagate_errors(); + return value_record; + }; + + [[maybe_unused]] auto value_record1 = read_value_record(pair_pos_format2.value_format1); + [[maybe_unused]] auto value_record2 = read_value_record(pair_pos_format2.value_format2); + + dbgln_if(OPENTYPE_GPOS_DEBUG, "Returning x advance {}", value_record1.x_advance); + return value_record1.x_advance; + } } } diff --git a/Userland/Libraries/LibGfx/Font/OpenType/Tables.h b/Userland/Libraries/LibGfx/Font/OpenType/Tables.h index 0e204fee06..0106d57a41 100644 --- a/Userland/Libraries/LibGfx/Font/OpenType/Tables.h +++ b/Userland/Libraries/LibGfx/Font/OpenType/Tables.h @@ -568,6 +568,27 @@ struct LookupList { Offset16 lookup_offsets[]; }; +// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1 +struct CoverageFormat1 { + BigEndian coverage_format; + BigEndian glyph_count; + BigEndian glyph_array[]; +}; + +// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2 +struct RangeRecord { + BigEndian start_glyph_id; + BigEndian end_glyph_id; + BigEndian start_coverage_index; +}; + +// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2 +struct CoverageFormat2 { + BigEndian coverage_format; + BigEndian range_count; + RangeRecord range_records[]; +}; + // https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2 struct ClassRangeRecord { BigEndian start_glyph_id; @@ -594,6 +615,41 @@ public: Offset16 lookup_list_offset; }; + // https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-1-adjustments-for-glyph-pairs + struct PairPosFormat1 { + BigEndian pos_format; + Offset16 coverage_offset; + BigEndian value_format1; + BigEndian value_format2; + BigEndian pair_set_count; + Offset16 pair_set_offsets[]; + }; + + // https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + struct ValueRecord { + BigEndian x_placement; + BigEndian y_placement; + BigEndian x_advance; + BigEndian y_advance; + Offset16 x_placement_device_offset; + Offset16 y_placement_device_offset; + Offset16 x_advance_device_offset; + Offset16 y_advance_device_offset; + }; + + // https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-1-adjustments-for-glyph-pairs + struct PairValueRecord { + BigEndian second_glyph; + ValueRecord value_record1; + ValueRecord value_record2; + }; + + // https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-1-adjustments-for-glyph-pairs + struct PairSet { + BigEndian pair_value_count; + PairValueRecord pair_value_records[]; + }; + // https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-2-class-pair-adjustment struct PairPosFormat2 { BigEndian pos_format;