Skip to content

Commit

Permalink
LibGfx: Remove bit casting in OpenType Kern table after construction
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
AtkinsSJ authored and awesomekling committed Nov 8, 2023
1 parent 1519290 commit 3c7d654
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 57 deletions.
97 changes: 48 additions & 49 deletions Userland/Libraries/LibGfx/Font/OpenType/Tables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,61 +239,67 @@ 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)
return Error::from_string_literal("Unsupported kern table version");
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
{
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)) {
Expand All @@ -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();
Expand All @@ -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);
Expand All @@ -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;
});
Expand Down
44 changes: 36 additions & 8 deletions Userland/Libraries/LibGfx/Font/OpenType/Tables.h
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,6 @@ class Kern {
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;
Expand Down Expand Up @@ -454,18 +453,28 @@ class Kern {
};
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
Expand Down Expand Up @@ -764,3 +773,22 @@ class GPOS {
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; }
};
}

0 comments on commit 3c7d654

Please sign in to comment.