From b73b434f8020c465192500e3d2bf0459f9de1d15 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Sun, 22 Oct 2023 13:35:49 +0100 Subject: [PATCH] LibGfx: Use a Header struct when reading WOFF2 font data --- Userland/Libraries/LibGfx/Font/WOFF2/Font.cpp | 83 +++++++++++-------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/Userland/Libraries/LibGfx/Font/WOFF2/Font.cpp b/Userland/Libraries/LibGfx/Font/WOFF2/Font.cpp index beec7d508b..3761f765d6 100644 --- a/Userland/Libraries/LibGfx/Font/WOFF2/Font.cpp +++ b/Userland/Libraries/LibGfx/Font/WOFF2/Font.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2022, Luke Wilde * Copyright (c) 2023, Andreas Kling + * Copyright (c) 2023, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ @@ -18,7 +19,36 @@ namespace WOFF2 { -static constexpr size_t WOFF2_HEADER_SIZE_IN_BYTES = 48; +// https://www.w3.org/TR/WOFF2/#woff20Header +struct [[gnu::packed]] Header { + BigEndian signature; // 0x774F4632 'wOF2' + BigEndian flavor; // The "sfnt version" of the input font. + BigEndian length; // Total size of the WOFF file. + BigEndian num_tables; // Number of entries in directory of font tables. + BigEndian reserved; // Reserved; set to 0. + BigEndian total_sfnt_size; // Total size needed for the uncompressed font data, including the sfnt header, + // directory, and font tables (including padding). + BigEndian total_compressed_size; // Total length of the compressed data block. + BigEndian major_version; // Major version of the WOFF file. + BigEndian minor_version; // Minor version of the WOFF file. + BigEndian meta_offset; // Offset to metadata block, from beginning of WOFF file. + BigEndian meta_length; // Length of compressed metadata block. + BigEndian meta_orig_length; // Uncompressed size of metadata block. + BigEndian priv_offset; // Offset to private data block, from beginning of WOFF file. + BigEndian priv_length; // Length of private data block. +}; +static_assert(AssertSize()); + +} + +template<> +class AK::Traits : public GenericTraits { +public: + static constexpr bool is_trivially_serializable() { return true; } +}; + +namespace WOFF2 { + static constexpr u32 WOFF2_SIGNATURE = 0x774F4632; static constexpr u32 TTCF_SIGNAURE = 0x74746366; static constexpr size_t SFNT_HEADER_SIZE = 12; @@ -862,64 +892,47 @@ ErrorOr> Font::try_load_from_externally_owned_memory(Readonl ErrorOr> Font::try_load_from_externally_owned_memory(SeekableStream& stream) { - auto stream_size = TRY(stream.size()); - - u8 header_buffer[WOFF2_HEADER_SIZE_IN_BYTES] {}; - TRY(stream.read_until_filled(header_buffer)); - auto header = ReadonlyBytes { header_buffer }; + auto header = TRY(stream.read_value
()); // The signature field in the WOFF2 header MUST contain the value of 0x774F4632 ('wOF2'), which distinguishes it from WOFF 1.0 files. // If the field does not contain this value, user agents MUST reject the file as invalid. - u32 signature = be_u32(header.data()); - if (signature != WOFF2_SIGNATURE) + if (header.signature != WOFF2_SIGNATURE) return Error::from_string_literal("Invalid WOFF2 signature"); // The interpretation of the WOFF2 Header is the same as the WOFF Header in [WOFF1], with the addition of one new totalCompressedSize field. // NOTE: See WOFF/Font.cpp for more comments about this. - u32 flavor = be_u32(header.offset(4)); // The "sfnt version" of the input font. - u32 length = be_u32(header.offset(8)); // Total size of the WOFF file. - u16 num_tables = be_u16(header.offset(12)); // Number of entries in directory of font tables. - // Skip: reserved - u32 total_sfnt_size = be_u32(header.offset(16)); // Total size needed for the uncompressed font data, including the sfnt header, directory, and font tables (including padding). - u32 total_compressed_size = be_u32(header.offset(20)); // Total length of the compressed data block. - // Skip: major_version, minor_version - u32 meta_offset = be_u32(header.offset(28)); // Offset to metadata block, from beginning of WOFF file. - u32 meta_length = be_u32(header.offset(32)); // Length of compressed metadata block. - // Skip: meta_orig_length - u32 priv_offset = be_u32(header.offset(40)); // Offset to private data block, from beginning of WOFF file. - u32 priv_length = be_u32(header.offset(44)); // Length of private data block. - if (length > stream_size) + if (header.length > TRY(stream.size())) return Error::from_string_literal("Invalid WOFF length"); - 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 (flavor == TTCF_SIGNAURE) + if (header.flavor == TTCF_SIGNAURE) return Error::from_string_literal("Font collections not yet supported"); // NOTE: "The "totalSfntSize" value in the WOFF2 Header is intended to be used for reference purposes only. It may represent the size of the uncompressed input font file, // but if the transformed 'glyf' and 'loca' tables are present, the uncompressed size of the reconstructed tables and the total decompressed font size may differ // substantially from the original total size specified in the WOFF2 Header." // We use it as an initial size of the font buffer and extend it as necessary. - 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 constexpr size_t OFFSET_TABLE_SIZE_IN_BYTES = 12; TRY(font_buffer.try_ensure_capacity(OFFSET_TABLE_SIZE_IN_BYTES)); - 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, 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); Vector table_entries; - TRY(table_entries.try_ensure_capacity(num_tables)); + TRY(table_entries.try_ensure_capacity(header.num_tables)); u64 total_length_of_all_tables = 0; - for (size_t table_entry_index = 0; table_entry_index < num_tables; ++table_entry_index) { + for (size_t table_entry_index = 0; table_entry_index < header.num_tables; ++table_entry_index) { TableDirectoryEntry table_directory_entry; u8 const flags_byte = TRY(stream.read_value()); @@ -994,9 +1007,9 @@ ErrorOr> Font::try_load_from_externally_owned_memory(Seekabl return Error::from_string_literal("Transformed 'loca' table must have a transform length of 0"); } - auto compressed_bytes_read_buffer = TRY(ByteBuffer::create_zeroed(total_compressed_size)); + auto compressed_bytes_read_buffer = TRY(ByteBuffer::create_zeroed(header.total_compressed_size)); auto compressed_bytes = TRY(stream.read_some(compressed_bytes_read_buffer)); - if (compressed_bytes.size() != total_compressed_size) + if (compressed_bytes.size() != header.total_compressed_size) return Error::from_string_literal("Not enough data to read in the reported size of the compressed data"); auto compressed_stream = FixedMemoryStream(compressed_bytes); @@ -1006,9 +1019,9 @@ ErrorOr> Font::try_load_from_externally_owned_memory(Seekabl return Error::from_string_literal("Size of the decompressed data is not equal to the total of the reported lengths of each table"); auto decompressed_data_stream = FixedMemoryStream(decompressed_table_data.bytes()); - size_t font_buffer_offset = SFNT_HEADER_SIZE + num_tables * SFNT_TABLE_SIZE; + size_t font_buffer_offset = SFNT_HEADER_SIZE + header.num_tables * SFNT_TABLE_SIZE; Optional glyf_and_loca_buffer; - for (size_t table_entry_index = 0; table_entry_index < num_tables; ++table_entry_index) { + for (size_t table_entry_index = 0; table_entry_index < header.num_tables; ++table_entry_index) { auto& table_entry = table_entries.at(table_entry_index); u32 length_to_read = table_entry.has_transformation() ? table_entry.transform_length.value() : table_entry.original_length;