1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 07:08:10 +00:00

LibGfx: Use a Stream to read WOFF font data

This lets us read structs directly from the data, instead of having to
construct them from manual offsets.
This commit is contained in:
Sam Atkins 2023-10-22 13:35:14 +01:00 committed by Andreas Kling
parent 122d847720
commit e7fe377501

View file

@ -1,5 +1,6 @@
/*
* Copyright (c) 2022, the SerenityOS developers.
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -13,28 +14,61 @@
namespace WOFF {
// https://www.w3.org/TR/WOFF/#WOFFHeader
struct [[gnu::packed]] Header {
BigEndian<u32> signature; // 0x774F4646 'wOFF'
BigEndian<u32> flavor; // The "sfnt version" of the input font.
BigEndian<u32> length; // Total size of the WOFF file.
BigEndian<u16> num_tables; // Number of entries in directory of font tables.
BigEndian<u16> reserved; // Reserved; set to zero.
BigEndian<u32> total_sfnt_size; // Total size needed for the uncompressed font data, including
// the sfnt header, directory, and font tables (including padding).
BigEndian<u16> major_version; // Major version of the WOFF file.
BigEndian<u16> minor_version; // Minor version of the WOFF file.
BigEndian<u32> meta_offset; // Offset to metadata block, from beginning of WOFF file.
BigEndian<u32> meta_length; // Length of compressed metadata block.
BigEndian<u32> meta_orig_length; // Uncompressed size of metadata block.
BigEndian<u32> priv_offset; // Offset to private data block, from beginning of WOFF file.
BigEndian<u32> priv_length; // Length of private data block.
};
static_assert(AssertSize<Header, 44>());
// https://www.w3.org/TR/WOFF/#TableDirectory
struct [[gnu::packed]] TableDirectoryEntry {
BigEndian<u32> tag; // 4-byte sfnt table identifier.
BigEndian<u32> offset; // Offset to the data, from beginning of WOFF file.
BigEndian<u32> comp_length; // Length of the compressed data, excluding padding.
BigEndian<u32> orig_length; // Length of the uncompressed table, excluding padding.
BigEndian<u32> orig_checksum; // Checksum of the uncompressed table.
};
static_assert(AssertSize<TableDirectoryEntry, 20>());
}
template<>
class AK::Traits<WOFF::Header> : public GenericTraits<WOFF::Header> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
class AK::Traits<WOFF::TableDirectoryEntry> : public GenericTraits<WOFF::TableDirectoryEntry> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
namespace WOFF {
static constexpr u32 WOFF_SIGNATURE = 0x774F4646;
static constexpr size_t WOFF_HEADER_SIZE = 44;
static constexpr size_t WOFF_TABLE_SIZE = 20;
static constexpr size_t SFNT_HEADER_SIZE = 12;
static constexpr size_t SFNT_TABLE_SIZE = 16;
static u16 be_u16(u8 const* ptr)
{
return (((u16)ptr[0]) << 8) | ((u16)ptr[1]);
}
static void be_u16(u8* ptr, u16 value)
{
ptr[0] = (value >> 8) & 0xff;
ptr[1] = value & 0xff;
}
static u32 be_u32(u8 const* ptr)
{
return (((u32)ptr[0]) << 24) | (((u32)ptr[1]) << 16) | (((u32)ptr[2]) << 8) | ((u32)ptr[3]);
}
static void be_u32(u8* ptr, u32 value)
{
ptr[0] = (value >> 24) & 0xff;
@ -59,13 +93,11 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_file(DeprecatedString path, uns
ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned int index)
{
// https://www.w3.org/TR/WOFF/#WOFFHeader
if (buffer.size() < WOFF_HEADER_SIZE)
return Error::from_string_literal("WOFF file too small");
FixedMemoryStream stream(buffer);
auto header = TRY(stream.read_value<Header>());
// The signature field in the WOFF header MUST contain the "magic number" 0x774F4646. If the field does not contain this value, user agents MUST reject the file as invalid.
u32 signature = be_u32(buffer.data());
if (signature != WOFF_SIGNATURE)
if (header.signature != WOFF_SIGNATURE)
return Error::from_string_literal("Invalid WOFF signature");
// The flavor field corresponds to the "sfnt version" field found at the beginning of an sfnt file,
// indicating the type of font data contained. Although only fonts of type 0x00010000 (the version number 1.0 as a 16.16 fixed-point value, indicating TrueType glyph data)
@ -74,73 +106,58 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Readonl
// indicating a WOFF-packaged version of a different sfnt flavor.
// (The value 0x74727565 'true' has been used for some TrueType-flavored fonts on Mac OS, for example.)
// Whether client software will actually support other types of sfnt font data is outside the scope of the WOFF specification, which simply describes how the sfnt is repackaged for Web use.
u32 flavor = be_u32(buffer.offset(4)); // The "sfnt version" of the input font.
u32 length = be_u32(buffer.offset(8)); // Total size of the WOFF file.
u16 num_tables = be_u16(buffer.offset(12)); // Number of entries in directory of font tables.
u16 reserved = be_u16(buffer.offset(14)); // Reserved; set to zero.
u32 total_sfnt_size = be_u32(buffer.offset(16)); // Total size needed for the uncompressed font data, including the sfnt header, directory, and font tables (including padding).
// Skip: major_version, minor_version
u32 meta_offset = be_u32(buffer.offset(24)); // Offset to metadata block, from beginning of WOFF file.
u32 meta_length = be_u32(buffer.offset(28)); // Length of compressed metadata block.
// Skip: meta_orig_length
u32 priv_offset = be_u32(buffer.offset(36)); // Offset to private data block, from beginning of WOFF file.
u32 priv_length = be_u32(buffer.offset(40)); // Length of private data block.
if (length > buffer.size())
if (header.length > buffer.size())
return Error::from_string_literal("Invalid WOFF length");
if (reserved != 0)
if (header.reserved != 0)
return Error::from_string_literal("Invalid WOFF reserved field");
if (meta_length == 0 && meta_offset != 0)
if (header.meta_length == 0 && header.meta_offset != 0)
return Error::from_string_literal("Invalid WOFF meta block offset");
if (priv_length == 0 && priv_offset != 0)
if (header.priv_length == 0 && header.priv_offset != 0)
return Error::from_string_literal("Invalid WOFF private block offset");
if (WOFF_HEADER_SIZE + num_tables * WOFF_TABLE_SIZE > length)
if (sizeof(Header) + header.num_tables * sizeof(TableDirectoryEntry) > header.length)
return Error::from_string_literal("Truncated WOFF table directory");
if (total_sfnt_size > 10 * MiB)
if (header.total_sfnt_size > 10 * MiB)
return Error::from_string_literal("Uncompressed font is more than 10 MiB");
auto font_buffer = TRY(ByteBuffer::create_zeroed(total_sfnt_size));
auto font_buffer = TRY(ByteBuffer::create_zeroed(header.total_sfnt_size));
// ISO-IEC 14496-22:2019 4.5.1 Offset table
u16 search_range = pow_2_less_than_or_equal(num_tables);
be_u32(font_buffer.data() + 0, flavor);
be_u16(font_buffer.data() + 4, num_tables);
u16 search_range = pow_2_less_than_or_equal(header.num_tables);
be_u32(font_buffer.data() + 0, header.flavor);
be_u16(font_buffer.data() + 4, header.num_tables);
be_u16(font_buffer.data() + 6, search_range * 16);
be_u16(font_buffer.data() + 8, AK::log2(search_range));
be_u16(font_buffer.data() + 10, num_tables * 16 - search_range * 16);
be_u16(font_buffer.data() + 10, header.num_tables * 16 - search_range * 16);
size_t font_buffer_offset = SFNT_HEADER_SIZE + num_tables * SFNT_TABLE_SIZE;
for (size_t i = 0; i < num_tables; ++i) {
size_t base_offset = WOFF_HEADER_SIZE + i * WOFF_TABLE_SIZE;
u32 tag = be_u32(buffer.offset(base_offset));
u32 offset = be_u32(buffer.offset(base_offset + 4));
u32 comp_length = be_u32(buffer.offset(base_offset + 8));
u32 orig_length = be_u32(buffer.offset(base_offset + 12));
u32 orig_checksum = be_u32(buffer.offset(base_offset + 16));
size_t font_buffer_offset = SFNT_HEADER_SIZE + header.num_tables * SFNT_TABLE_SIZE;
for (size_t i = 0; i < header.num_tables; ++i) {
auto entry = TRY(stream.read_value<TableDirectoryEntry>());
if ((size_t)offset + comp_length > length)
if ((size_t)entry.offset + entry.comp_length > header.length)
return Error::from_string_literal("Truncated WOFF table");
if (font_buffer_offset + orig_length > font_buffer.size())
if (font_buffer_offset + entry.orig_length > font_buffer.size())
return Error::from_string_literal("Uncompressed WOFF table too big");
if (comp_length < orig_length) {
auto compressed_data_stream = make<FixedMemoryStream>(buffer.slice(offset, comp_length));
if (entry.comp_length < entry.orig_length) {
auto compressed_data_stream = make<FixedMemoryStream>(buffer.slice(entry.offset, entry.comp_length));
auto decompressor = TRY(Compress::ZlibDecompressor::create(move(compressed_data_stream)));
auto decompressed = TRY(decompressor->read_until_eof());
if (orig_length != decompressed.size())
if (entry.orig_length != decompressed.size())
return Error::from_string_literal("Invalid decompressed WOFF table length");
font_buffer.overwrite(font_buffer_offset, decompressed.data(), orig_length);
font_buffer.overwrite(font_buffer_offset, decompressed.data(), entry.orig_length);
} else {
if (comp_length != orig_length)
if (entry.comp_length != entry.orig_length)
return Error::from_string_literal("Invalid uncompressed WOFF table length");
font_buffer.overwrite(font_buffer_offset, buffer.data() + offset, orig_length);
font_buffer.overwrite(font_buffer_offset, buffer.data() + entry.offset, entry.orig_length);
}
// ISO-IEC 14496-22:2019 4.5.2 Table directory
size_t table_directory_offset = SFNT_HEADER_SIZE + i * SFNT_TABLE_SIZE;
be_u32(font_buffer.data() + table_directory_offset, tag);
be_u32(font_buffer.data() + table_directory_offset + 4, orig_checksum);
be_u32(font_buffer.data() + table_directory_offset, entry.tag);
be_u32(font_buffer.data() + table_directory_offset + 4, entry.orig_checksum);
be_u32(font_buffer.data() + table_directory_offset + 8, font_buffer_offset);
be_u32(font_buffer.data() + table_directory_offset + 12, orig_length);
be_u32(font_buffer.data() + table_directory_offset + 12, entry.orig_length);
font_buffer_offset += orig_length;
font_buffer_offset += entry.orig_length;
}
auto input_font = TRY(OpenType::Font::try_load_from_externally_owned_memory(font_buffer.bytes(), index));