LibGfx: Remove bit casting in OpenType Kern table after construction

Do more checks at load time, including categorizing the subtables and
producing our own directory of them.

The format for Kern is a little complicated, so use a Stream instead of
manual offsets.
This commit is contained in:
Sam Atkins 2023-11-02 11:08:35 +00:00 committed by Andreas Kling
parent 1519290989
commit 3c7d654182
2 changed files with 84 additions and 57 deletions

View file

@ -239,11 +239,10 @@ String Name::string_for_id(NameId id) const
ErrorOr<Kern> Kern::from_slice(ReadonlyBytes slice) ErrorOr<Kern> Kern::from_slice(ReadonlyBytes slice)
{ {
if (slice.size() < sizeof(Header)) FixedMemoryStream stream { slice };
return Error::from_string_literal("Invalid kern table header");
// We only support the old (2x u16) version of the header // We only support the old (2x u16) version of the header
auto const& header = *bit_cast<Header const*>(slice.data()); auto const& header = *TRY(stream.read_in_place<Header const>());
auto version = header.version; auto version = header.version;
auto number_of_subtables = header.n_tables; auto number_of_subtables = header.n_tables;
if (version != 0) if (version != 0)
@ -251,18 +250,41 @@ ErrorOr<Kern> Kern::from_slice(ReadonlyBytes slice)
if (number_of_subtables == 0) if (number_of_subtables == 0)
return Error::from_string_literal("Kern table does not contain any subtables"); return Error::from_string_literal("Kern table does not contain any subtables");
// Read all subtable offsets // Read subtables
auto subtable_offsets = TRY(FixedArray<size_t>::create(number_of_subtables)); Vector<Subtable> subtables;
size_t offset = sizeof(Header); TRY(subtables.try_ensure_capacity(number_of_subtables));
for (size_t i = 0; i < number_of_subtables; ++i) { for (size_t i = 0; i < number_of_subtables; ++i) {
if (slice.size() < offset + sizeof(SubtableHeader)) auto const& subtable_header = *TRY(stream.read_in_place<SubtableHeader const>());
return Error::from_string_literal("Invalid kern subtable header");
auto const& subtable_header = *bit_cast<SubtableHeader const*>(slice.offset_pointer(offset)); if (subtable_header.version != 0)
subtable_offsets[i] = offset; return Error::from_string_literal("Unsupported Kern subtable version");
offset += subtable_header.length;
if (stream.remaining() + sizeof(SubtableHeader) < subtable_header.length)
return Error::from_string_literal("Kern subtable is truncated");
auto subtable_format = (subtable_header.coverage & 0xFF00) >> 8;
if (subtable_format == 0) {
auto const& format0_header = *TRY(stream.read_in_place<Format0 const>());
auto pairs = TRY(stream.read_in_place<Format0Pair const>(5));
subtables.append(Subtable {
.header = subtable_header,
.table = Format0Table {
.header = format0_header,
.pairs = pairs,
},
});
} else {
dbgln("OpenType::Kern: FIXME: subtable format {} is unsupported", subtable_format);
TRY(stream.discard(subtable_header.length - sizeof(SubtableHeader)));
subtables.append(Subtable {
.header = subtable_header,
.table = UnsupportedTable {},
});
}
} }
return Kern(slice, move(subtable_offsets)); return Kern(header, move(subtables));
} }
i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const
@ -270,30 +292,14 @@ i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const
VERIFY(left_glyph_id > 0 && right_glyph_id > 0); VERIFY(left_glyph_id > 0 && right_glyph_id > 0);
i16 glyph_kerning = 0; i16 glyph_kerning = 0;
for (auto subtable_offset : m_subtable_offsets) { for (auto const& subtable : m_subtables) {
auto subtable_slice = m_slice.slice(subtable_offset); auto coverage = subtable.header.coverage;
auto const& subtable_header = *bit_cast<SubtableHeader const*>(subtable_slice.data());
auto version = subtable_header.version;
auto length = subtable_header.length;
auto coverage = subtable_header.coverage;
if (version != 0) {
dbgln("OpenType::Kern: unsupported subtable version {}", version);
continue;
}
if (subtable_slice.size() < length) {
dbgln("OpenType::Kern: subtable has an invalid size {}", length);
continue;
}
auto is_horizontal = (coverage & (1 << 0)) > 0; auto is_horizontal = (coverage & (1 << 0)) > 0;
auto is_minimum = (coverage & (1 << 1)) > 0; auto is_minimum = (coverage & (1 << 1)) > 0;
auto is_cross_stream = (coverage & (1 << 2)) > 0; auto is_cross_stream = (coverage & (1 << 2)) > 0;
auto is_override = (coverage & (1 << 3)) > 0; auto is_override = (coverage & (1 << 3)) > 0;
auto reserved_bits = (coverage & 0xF0); auto reserved_bits = (coverage & 0xF0);
auto format = (coverage & 0xFF00) >> 8;
// FIXME: implement support for these features // FIXME: implement support for these features
if (!is_horizontal || is_minimum || is_cross_stream || (reserved_bits > 0)) { if (!is_horizontal || is_minimum || is_cross_stream || (reserved_bits > 0)) {
@ -303,14 +309,12 @@ i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const
// FIXME: implement support for subtable formats other than 0 // FIXME: implement support for subtable formats other than 0
Optional<i16> subtable_kerning; Optional<i16> subtable_kerning;
switch (format) { subtable.table.visit(
case 0: [&](Format0Table const& format0) {
subtable_kerning = read_glyph_kerning_format0(subtable_slice.slice(sizeof(SubtableHeader)), left_glyph_id, right_glyph_id); subtable_kerning = read_glyph_kerning_format0(format0, left_glyph_id, right_glyph_id);
break; },
default: [&](auto&) {});
dbgln("OpenType::Kern: FIXME: subtable format {} is unsupported", format);
continue;
}
if (!subtable_kerning.has_value()) if (!subtable_kerning.has_value())
continue; continue;
auto kerning_value = subtable_kerning.release_value(); auto kerning_value = subtable_kerning.release_value();
@ -323,16 +327,12 @@ i16 Kern::get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const
return glyph_kerning; return glyph_kerning;
} }
Optional<i16> Kern::read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_glyph_id, u16 right_glyph_id) Optional<i16> Kern::read_glyph_kerning_format0(Format0Table const& format0, u16 left_glyph_id, u16 right_glyph_id)
{ {
if (slice.size() < sizeof(Format0)) u16 number_of_pairs = format0.header.n_pairs;
return {}; u16 search_range = format0.header.search_range;
u16 entry_selector = format0.header.entry_selector;
auto const& format0 = *bit_cast<Format0 const*>(slice.data()); u16 range_shift = format0.header.range_shift;
u16 number_of_pairs = format0.n_pairs;
u16 search_range = format0.search_range;
u16 entry_selector = format0.entry_selector;
u16 range_shift = format0.range_shift;
// Sanity checks for this table format // Sanity checks for this table format
auto pairs_in_search_range = search_range / sizeof(Format0Pair); auto pairs_in_search_range = search_range / sizeof(Format0Pair);
@ -346,11 +346,10 @@ Optional<i16> Kern::read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_gly
return {}; return {};
// FIXME: implement a possibly slightly more efficient binary search using the parameters above // FIXME: implement a possibly slightly more efficient binary search using the parameters above
ReadonlySpan<Format0Pair> pairs { bit_cast<Format0Pair const*>(slice.slice(sizeof(Format0)).data()), number_of_pairs };
// The left and right halves of the kerning pair make an unsigned 32-bit number, which is then used to order the kerning pairs numerically. // The left and right halves of the kerning pair make an unsigned 32-bit number, which is then used to order the kerning pairs numerically.
auto needle = (static_cast<u32>(left_glyph_id) << 16u) | static_cast<u32>(right_glyph_id); auto needle = (static_cast<u32>(left_glyph_id) << 16u) | static_cast<u32>(right_glyph_id);
auto* pair = binary_search(pairs, nullptr, nullptr, [&](void*, Format0Pair const& pair) { auto* pair = binary_search(format0.pairs, nullptr, nullptr, [&](void*, Format0Pair const& pair) {
auto as_u32 = (static_cast<u32>(pair.left) << 16u) | static_cast<u32>(pair.right); auto as_u32 = (static_cast<u32>(pair.left) << 16u) | static_cast<u32>(pair.right);
return needle - as_u32; return needle - as_u32;
}); });

View file

@ -424,7 +424,6 @@ public:
static ErrorOr<Kern> from_slice(ReadonlyBytes); static ErrorOr<Kern> from_slice(ReadonlyBytes);
i16 get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const; i16 get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const;
private:
struct [[gnu::packed]] Header { struct [[gnu::packed]] Header {
BigEndian<u16> version; BigEndian<u16> version;
BigEndian<u16> n_tables; BigEndian<u16> n_tables;
@ -454,18 +453,28 @@ private:
}; };
static_assert(AssertSize<Format0Pair, 6>()); static_assert(AssertSize<Format0Pair, 6>());
Header const& header() const { return *bit_cast<Header const*>(m_slice.data()); } private:
// Non-spec structs for easier reference
struct Format0Table {
Format0 const& header;
ReadonlySpan<Format0Pair> pairs;
};
struct UnsupportedTable { };
struct Subtable {
SubtableHeader const& header;
Variant<Format0Table, UnsupportedTable> table;
};
Kern(ReadonlyBytes slice, FixedArray<size_t> subtable_offsets) Kern(Header const& header, Vector<Subtable> subtables)
: m_slice(slice) : m_header(header)
, m_subtable_offsets(move(subtable_offsets)) , m_subtables(move(subtables))
{ {
} }
static Optional<i16> read_glyph_kerning_format0(ReadonlyBytes slice, u16 left_glyph_id, u16 right_glyph_id); static Optional<i16> read_glyph_kerning_format0(Format0Table const& format0, u16 left_glyph_id, u16 right_glyph_id);
ReadonlyBytes m_slice; Header const& m_header;
FixedArray<size_t> m_subtable_offsets; Vector<Subtable> const m_subtables;
}; };
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc // https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
@ -764,3 +773,22 @@ private:
ReadonlyBytes m_slice; ReadonlyBytes m_slice;
}; };
} }
namespace AK {
template<>
struct Traits<OpenType::Kern::Header const> : public GenericTraits<OpenType::Kern::Header const> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Kern::SubtableHeader const> : public GenericTraits<OpenType::Kern::SubtableHeader const> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Kern::Format0 const> : public GenericTraits<OpenType::Kern::Format0 const> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Kern::Format0Pair const> : public GenericTraits<OpenType::Kern::Format0Pair const> {
static constexpr bool is_trivially_serializable() { return true; }
};
}