1
0
mirror of https://github.com/SerenityOS/serenity synced 2024-07-09 13:47:16 +00:00

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)
{
if (slice.size() < sizeof(Header))
return Error::from_string_literal("Invalid kern table header");
FixedMemoryStream stream { slice };
// 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 number_of_subtables = header.n_tables;
if (version != 0)
@ -251,18 +250,41 @@ ErrorOr<Kern> Kern::from_slice(ReadonlyBytes slice)
if (number_of_subtables == 0)
return Error::from_string_literal("Kern table does not contain any subtables");
// Read all subtable offsets
auto subtable_offsets = TRY(FixedArray<size_t>::create(number_of_subtables));
size_t offset = sizeof(Header);
// Read subtables
Vector<Subtable> subtables;
TRY(subtables.try_ensure_capacity(number_of_subtables));
for (size_t i = 0; i < number_of_subtables; ++i) {
if (slice.size() < offset + sizeof(SubtableHeader))
return Error::from_string_literal("Invalid kern subtable header");
auto const& subtable_header = *bit_cast<SubtableHeader const*>(slice.offset_pointer(offset));
subtable_offsets[i] = offset;
offset += subtable_header.length;
auto const& subtable_header = *TRY(stream.read_in_place<SubtableHeader const>());
if (subtable_header.version != 0)
return Error::from_string_literal("Unsupported Kern subtable version");
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
@ -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);
i16 glyph_kerning = 0;
for (auto subtable_offset : m_subtable_offsets) {
auto subtable_slice = m_slice.slice(subtable_offset);
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;
}
for (auto const& subtable : m_subtables) {
auto coverage = subtable.header.coverage;
auto is_horizontal = (coverage & (1 << 0)) > 0;
auto is_minimum = (coverage & (1 << 1)) > 0;
auto is_cross_stream = (coverage & (1 << 2)) > 0;
auto is_override = (coverage & (1 << 3)) > 0;
auto reserved_bits = (coverage & 0xF0);
auto format = (coverage & 0xFF00) >> 8;
// FIXME: implement support for these features
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
Optional<i16> subtable_kerning;
switch (format) {
case 0:
subtable_kerning = read_glyph_kerning_format0(subtable_slice.slice(sizeof(SubtableHeader)), left_glyph_id, right_glyph_id);
break;
default:
dbgln("OpenType::Kern: FIXME: subtable format {} is unsupported", format);
continue;
}
subtable.table.visit(
[&](Format0Table const& format0) {
subtable_kerning = read_glyph_kerning_format0(format0, left_glyph_id, right_glyph_id);
},
[&](auto&) {});
if (!subtable_kerning.has_value())
continue;
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;
}
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))
return {};
auto const& format0 = *bit_cast<Format0 const*>(slice.data());
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;
u16 number_of_pairs = format0.header.n_pairs;
u16 search_range = format0.header.search_range;
u16 entry_selector = format0.header.entry_selector;
u16 range_shift = format0.header.range_shift;
// Sanity checks for this table format
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 {};
// 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.
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);
return needle - as_u32;
});

View File

@ -424,7 +424,6 @@ public:
static ErrorOr<Kern> from_slice(ReadonlyBytes);
i16 get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const;
private:
struct [[gnu::packed]] Header {
BigEndian<u16> version;
BigEndian<u16> n_tables;
@ -454,18 +453,28 @@ private:
};
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)
: m_slice(slice)
, m_subtable_offsets(move(subtable_offsets))
Kern(Header const& header, Vector<Subtable> subtables)
: m_header(header)
, 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;
FixedArray<size_t> m_subtable_offsets;
Header const& m_header;
Vector<Subtable> const m_subtables;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/eblc
@ -764,3 +773,22 @@ private:
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; }
};
}