/* * Copyright (c) 2022, Luke Wilde * Copyright (c) 2023, Andreas Kling * Copyright (c) 2023, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include // The following is an implementation of the WOFF2 specification. // https://www.w3.org/TR/WOFF2/ namespace WOFF2 { // 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; static constexpr size_t SFNT_TABLE_SIZE = 16; [[maybe_unused]] static ErrorOr read_255_u_short(FixedMemoryStream& stream) { constexpr u8 one_more_byte_code_1 = 255; constexpr u8 one_more_byte_code_2 = 254; constexpr u8 word_code = 253; constexpr u8 lowest_u_code = 253; constexpr u16 lowest_u_code_multiplied_by_2 = lowest_u_code * 2; auto code = TRY(stream.read_value()); if (code == word_code) { return TRY(stream.read_value>()); } if (code == one_more_byte_code_1) { u16 final_value = TRY(stream.read_value()); final_value += lowest_u_code; return final_value; } if (code == one_more_byte_code_2) { u16 final_value = TRY(stream.read_value()); final_value += lowest_u_code_multiplied_by_2; return final_value; } return code; } static ErrorOr read_uint_base_128(SeekableStream& stream) { u32 accumulator = 0; for (u8 i = 0; i < 5; ++i) { u8 const next_byte = TRY(stream.read_value()); if (i == 0 && next_byte == 0x80) return Error::from_string_literal("UIntBase128 type contains a leading zero"); if (accumulator & 0xfe000000) return Error::from_string_literal("UIntBase128 type exceeds the length of a u32"); accumulator = (accumulator << 7) | (next_byte & 0x7F); if ((next_byte & 0x80) == 0) return accumulator; } return Error::from_string_literal("UIntBase128 type is larger than 5 bytes"); } static i16 be_i16(u8 const* ptr) { return (((i16)ptr[0]) << 8) | ((i16)ptr[1]); } static u16 pow_2_less_than_or_equal(u16 x) { u16 result = 1; while (result < x) result <<= 1; return result; } enum class TransformationVersion { Version0, Version1, Version2, Version3, }; struct TableDirectoryEntry { TransformationVersion transformation_version { TransformationVersion::Version0 }; DeprecatedString tag; u32 original_length { 0 }; Optional transform_length; u32 tag_to_u32() const { VERIFY(tag.length() == 4); return (static_cast(tag[0]) << 24) | (static_cast(tag[1]) << 16) | (static_cast(tag[2]) << 8) | static_cast(tag[3]); } bool has_transformation() const { return transform_length.has_value(); } }; // NOTE: Any tags less than 4 characters long are padded with spaces at the end. static constexpr Array known_tag_names = { "cmap"sv, "head"sv, "hhea"sv, "hmtx"sv, "maxp"sv, "name"sv, "OS/2"sv, "post"sv, "cvt "sv, "fpgm"sv, "glyf"sv, "loca"sv, "prep"sv, "CFF "sv, "VORG"sv, "EBDT"sv, "EBLC"sv, "gasp"sv, "hdmx"sv, "kern"sv, "LTSH"sv, "PCLT"sv, "VDMX"sv, "vhea"sv, "vmtx"sv, "BASE"sv, "GDEF"sv, "GPOS"sv, "GSUB"sv, "EBSC"sv, "JSTF"sv, "MATH"sv, "CBDT"sv, "CBLC"sv, "COLR"sv, "CPAL"sv, "SVG "sv, "sbix"sv, "acnt"sv, "avar"sv, "bdat"sv, "bloc"sv, "bsln"sv, "cvar"sv, "fdsc"sv, "feat"sv, "fmtx"sv, "fvar"sv, "gvar"sv, "hsty"sv, "just"sv, "lcar"sv, "mort"sv, "morx"sv, "opbd"sv, "prop"sv, "trak"sv, "Zapf"sv, "Silf"sv, "Glat"sv, "Gloc"sv, "Feat"sv, "Sill"sv, }; struct CoordinateTripletEncoding { u8 byte_count { 0 }; u8 x_bits { 0 }; u8 y_bits { 0 }; Optional delta_x; Optional delta_y; Optional positive_x; Optional positive_y; }; // https://www.w3.org/TR/WOFF2/#triplet_decoding // 5.2. Decoding of variable-length X and Y coordinates static CoordinateTripletEncoding const coordinate_triplet_encodings[128] = { { 2, 0, 8, {}, 0, {}, false }, // 0 { 2, 0, 8, {}, 0, {}, true }, // 1 { 2, 0, 8, {}, 256, {}, false }, // 2 { 2, 0, 8, {}, 256, {}, true }, // 3 { 2, 0, 8, {}, 512, {}, false }, // 4 { 2, 0, 8, {}, 512, {}, true }, // 5 { 2, 0, 8, {}, 768, {}, false }, // 6 { 2, 0, 8, {}, 768, {}, true }, // 7 { 2, 0, 8, {}, 1024, {}, false }, // 8 { 2, 0, 8, {}, 1024, {}, true }, // 9 { 2, 8, 0, 0, {}, false, {} }, // 10 { 2, 8, 0, 0, {}, true, {} }, // 11 { 2, 8, 0, 256, {}, false, {} }, // 12 { 2, 8, 0, 256, {}, true, {} }, // 13 { 2, 8, 0, 512, {}, false, {} }, // 14 { 2, 8, 0, 512, {}, true, {} }, // 15 { 2, 8, 0, 768, {}, false, {} }, // 16 { 2, 8, 0, 768, {}, true, {} }, // 17 { 2, 8, 0, 1024, {}, false, {} }, // 18 { 2, 8, 0, 1024, {}, true, {} }, // 19 { 2, 4, 4, 1, 1, false, false }, // 20 { 2, 4, 4, 1, 1, true, false }, // 21 { 2, 4, 4, 1, 1, false, true }, // 22 { 2, 4, 4, 1, 1, true, true }, // 23 { 2, 4, 4, 1, 17, false, false }, // 24 { 2, 4, 4, 1, 17, true, false }, // 25 { 2, 4, 4, 1, 17, false, true }, // 26 { 2, 4, 4, 1, 17, true, true }, // 27 { 2, 4, 4, 1, 33, false, false }, // 28 { 2, 4, 4, 1, 33, true, false }, // 29 { 2, 4, 4, 1, 33, false, true }, // 30 { 2, 4, 4, 1, 33, true, true }, // 31 { 2, 4, 4, 1, 49, false, false }, // 32 { 2, 4, 4, 1, 49, true, false }, // 33 { 2, 4, 4, 1, 49, false, true }, // 34 { 2, 4, 4, 1, 49, true, true }, // 35 { 2, 4, 4, 17, 1, false, false }, // 36 { 2, 4, 4, 17, 1, true, false }, // 37 { 2, 4, 4, 17, 1, false, true }, // 38 { 2, 4, 4, 17, 1, true, true }, // 39 { 2, 4, 4, 17, 17, false, false }, // 40 { 2, 4, 4, 17, 17, true, false }, // 41 { 2, 4, 4, 17, 17, false, true }, // 42 { 2, 4, 4, 17, 17, true, true }, // 43 { 2, 4, 4, 17, 33, false, false }, // 44 { 2, 4, 4, 17, 33, true, false }, // 45 { 2, 4, 4, 17, 33, false, true }, // 46 { 2, 4, 4, 17, 33, true, true }, // 47 { 2, 4, 4, 17, 49, false, false }, // 48 { 2, 4, 4, 17, 49, true, false }, // 49 { 2, 4, 4, 17, 49, false, true }, // 50 { 2, 4, 4, 17, 49, true, true }, // 51 { 2, 4, 4, 33, 1, false, false }, // 52 { 2, 4, 4, 33, 1, true, false }, // 53 { 2, 4, 4, 33, 1, false, true }, // 54 { 2, 4, 4, 33, 1, true, true }, // 55 { 2, 4, 4, 33, 17, false, false }, // 56 { 2, 4, 4, 33, 17, true, false }, // 57 { 2, 4, 4, 33, 17, false, true }, // 58 { 2, 4, 4, 33, 17, true, true }, // 59 { 2, 4, 4, 33, 33, false, false }, // 60 { 2, 4, 4, 33, 33, true, false }, // 61 { 2, 4, 4, 33, 33, false, true }, // 62 { 2, 4, 4, 33, 33, true, true }, // 63 { 2, 4, 4, 33, 49, false, false }, // 64 { 2, 4, 4, 33, 49, true, false }, // 65 { 2, 4, 4, 33, 49, false, true }, // 66 { 2, 4, 4, 33, 49, true, true }, // 67 { 2, 4, 4, 49, 1, false, false }, // 68 { 2, 4, 4, 49, 1, true, false }, // 69 { 2, 4, 4, 49, 1, false, true }, // 70 { 2, 4, 4, 49, 1, true, true }, // 71 { 2, 4, 4, 49, 17, false, false }, // 72 { 2, 4, 4, 49, 17, true, false }, // 73 { 2, 4, 4, 49, 17, false, true }, // 74 { 2, 4, 4, 49, 17, true, true }, // 75 { 2, 4, 4, 49, 33, false, false }, // 76 { 2, 4, 4, 49, 33, true, false }, // 77 { 2, 4, 4, 49, 33, false, true }, // 78 { 2, 4, 4, 49, 33, true, true }, // 79 { 2, 4, 4, 49, 49, false, false }, // 80 { 2, 4, 4, 49, 49, true, false }, // 81 { 2, 4, 4, 49, 49, false, true }, // 82 { 2, 4, 4, 49, 49, true, true }, // 83 { 3, 8, 8, 1, 1, false, false }, // 84 { 3, 8, 8, 1, 1, true, false }, // 85 { 3, 8, 8, 1, 1, false, true }, // 86 { 3, 8, 8, 1, 1, true, true }, // 87 { 3, 8, 8, 1, 257, false, false }, // 88 { 3, 8, 8, 1, 257, true, false }, // 89 { 3, 8, 8, 1, 257, false, true }, // 90 { 3, 8, 8, 1, 257, true, true }, // 91 { 3, 8, 8, 1, 513, false, false }, // 92 { 3, 8, 8, 1, 513, true, false }, // 93 { 3, 8, 8, 1, 513, false, true }, // 94 { 3, 8, 8, 1, 513, true, true }, // 95 { 3, 8, 8, 257, 1, false, false }, // 96 { 3, 8, 8, 257, 1, true, false }, // 97 { 3, 8, 8, 257, 1, false, true }, // 98 { 3, 8, 8, 257, 1, true, true }, // 99 { 3, 8, 8, 257, 257, false, false }, // 100 { 3, 8, 8, 257, 257, true, false }, // 101 { 3, 8, 8, 257, 257, false, true }, // 102 { 3, 8, 8, 257, 257, true, true }, // 103 { 3, 8, 8, 257, 513, false, false }, // 104 { 3, 8, 8, 257, 513, true, false }, // 105 { 3, 8, 8, 257, 513, false, true }, // 106 { 3, 8, 8, 257, 513, true, true }, // 107 { 3, 8, 8, 513, 1, false, false }, // 108 { 3, 8, 8, 513, 1, true, false }, // 109 { 3, 8, 8, 513, 1, false, true }, // 110 { 3, 8, 8, 513, 1, true, true }, // 111 { 3, 8, 8, 513, 257, false, false }, // 112 { 3, 8, 8, 513, 257, true, false }, // 113 { 3, 8, 8, 513, 257, false, true }, // 114 { 3, 8, 8, 513, 257, true, true }, // 115 { 3, 8, 8, 513, 513, false, false }, // 116 { 3, 8, 8, 513, 513, true, false }, // 117 { 3, 8, 8, 513, 513, false, true }, // 118 { 3, 8, 8, 513, 513, true, true }, // 119 { 4, 12, 12, 0, 0, false, false }, // 120 { 4, 12, 12, 0, 0, true, false }, // 121 { 4, 12, 12, 0, 0, false, true }, // 122 { 4, 12, 12, 0, 0, true, true }, // 123 { 5, 16, 16, 0, 0, false, false }, // 124 { 5, 16, 16, 0, 0, true, false }, // 125 { 5, 16, 16, 0, 0, false, true }, // 126 { 5, 16, 16, 0, 0, true, true }, // 127 }; struct FontPoint { i16 x { 0 }; i16 y { 0 }; bool on_curve { false }; }; static ErrorOr> retrieve_points_of_simple_glyph(FixedMemoryStream& flags_stream, FixedMemoryStream& glyph_stream, u16 number_of_points) { Vector points; TRY(points.try_ensure_capacity(number_of_points)); i16 x = 0; i16 y = 0; for (u32 point = 0; point < number_of_points; ++point) { u8 flags = TRY(flags_stream.read_value()); bool on_curve = (flags & 0x80) == 0; u8 coordinate_triplet_index = flags & 0x7F; auto const& coordinate_triplet_encoding = coordinate_triplet_encodings[coordinate_triplet_index]; // The byte_count in the array accounts for the flags, but we already read them in from a different stream. u8 const byte_count_not_including_flags = coordinate_triplet_encoding.byte_count - 1; u8 point_coordinates_buffer[4]; Bytes point_coordinates { point_coordinates_buffer, byte_count_not_including_flags }; TRY(glyph_stream.read_until_filled(point_coordinates)); int delta_x = 0; int delta_y = 0; switch (coordinate_triplet_encoding.x_bits) { case 0: break; case 4: delta_x = static_cast(point_coordinates[0] >> 4); break; case 8: delta_x = static_cast(point_coordinates[0]); break; case 12: delta_x = (static_cast(point_coordinates[0]) << 4) | (static_cast(point_coordinates[1]) >> 4); break; case 16: delta_x = be_i16(point_coordinates.data()); break; default: VERIFY_NOT_REACHED(); } switch (coordinate_triplet_encoding.y_bits) { case 0: break; case 4: delta_y = static_cast(point_coordinates[0] & 0x0f); break; case 8: delta_y = byte_count_not_including_flags == 2 ? static_cast(point_coordinates[1]) : static_cast(point_coordinates[0]); break; case 12: delta_y = (static_cast(point_coordinates[1] & 0x0f) << 8) | static_cast(point_coordinates[2]); break; case 16: delta_y = be_i16(point_coordinates.offset(2)); break; default: VERIFY_NOT_REACHED(); } if (coordinate_triplet_encoding.delta_x.has_value()) { if (Checked::addition_would_overflow(delta_x, coordinate_triplet_encoding.delta_x.value())) return Error::from_string_literal("EOVERFLOW 3"); delta_x += coordinate_triplet_encoding.delta_x.value(); } if (coordinate_triplet_encoding.delta_y.has_value()) { if (Checked::addition_would_overflow(delta_y, coordinate_triplet_encoding.delta_y.value())) return Error::from_string_literal("EOVERFLOW 4"); delta_y += coordinate_triplet_encoding.delta_y.value(); } if (coordinate_triplet_encoding.positive_x.has_value() && !coordinate_triplet_encoding.positive_x.value()) delta_x = -delta_x; if (coordinate_triplet_encoding.positive_y.has_value() && !coordinate_triplet_encoding.positive_y.value()) delta_y = -delta_y; if (Checked::addition_would_overflow(x, delta_x)) return Error::from_string_literal("EOVERFLOW 5"); if (Checked::addition_would_overflow(y, delta_y)) return Error::from_string_literal("EOVERFLOW 6"); x += delta_x; y += delta_y; points.unchecked_append(FontPoint { .x = x, .y = y, .on_curve = on_curve }); } return points; } // https://www.w3.org/TR/WOFF2/#glyf_table_format struct [[gnu::packed]] TransformedGlyfTable { BigEndian reserved; // = 0x0000 BigEndian option_flags; // Bit 0: if set, indicates the presence of the overlapSimpleBitmap[] bit array. // Bits 1-15: Reserved. BigEndian num_glyphs; // Number of glyphs BigEndian index_format; // Offset format for loca table, should be consistent with indexToLocFormat of the // original head table (see [OFF] specification) BigEndian n_contour_stream_size; // Size of nContour stream in bytes BigEndian n_points_stream_size; // Size of nPoints stream in bytes BigEndian flag_stream_size; // Size of flag stream in bytes BigEndian glyph_stream_size; // Size of glyph stream in bytes (a stream of variable-length encoded values, see // description below) BigEndian composite_stream_size; // Size of composite stream in bytes (a stream of variable-length encoded values, // see description below) BigEndian bbox_stream_size; // Size of bbox data in bytes representing combined length of bboxBitmap // (a packed bit array) and bboxStream (a stream of Int16 values) BigEndian instruction_stream_size; // Size of instruction stream (a stream of UInt8 values) // Other fields are variable-length, and so are not represented in this struct: // Int16 nContourStream[] Stream of Int16 values representing number of contours for each glyph record // 255UInt16 nPointsStream[] Stream of values representing number of outline points for each contour in glyph records // UInt8 flagStream[] Stream of UInt8 values representing flag values for each outline point. // Vary glyphStream[] Stream of bytes representing point coordinate values using variable length encoding // format (defined in subclause 5.2) // Vary compositeStream[] Stream of bytes representing component flag values and associated composite glyph data // UInt8 bboxBitmap[] Bitmap (a numGlyphs-long bit array) indicating explicit bounding boxes // Int16 bboxStream[] Stream of Int16 values representing glyph bounding box data // UInt8 instructionStream[] Stream of UInt8 values representing a set of instructions for each corresponding glyph // UInt8 overlapSimpleBitmap[] A numGlyphs-long bit array that provides values for the overlap flag [bit 6] for each // simple glyph. (Flag values for composite glyphs are already encoded as part of the // compositeStream[]). }; static_assert(AssertSize()); } template<> class AK::Traits : public GenericTraits { public: static constexpr bool is_trivially_serializable() { return true; } }; namespace WOFF2 { enum class LocaElementSize { TwoBytes, FourBytes, }; struct GlyfAndLocaTableBuffers { ByteBuffer glyf_table; ByteBuffer loca_table; }; enum SimpleGlyphFlags : u8 { OnCurve = 0x01, XShortVector = 0x02, YShortVector = 0x04, RepeatFlag = 0x08, XIsSameOrPositiveXShortVector = 0x10, YIsSameOrPositiveYShortVector = 0x20, }; static ErrorOr create_glyf_and_loca_tables_from_transformed_glyf_table(FixedMemoryStream& table_stream) { auto header = TRY(table_stream.read_value()); auto loca_element_size = header.index_format == 0 ? LocaElementSize::TwoBytes : LocaElementSize::FourBytes; size_t table_size = TRY(table_stream.size()); u64 total_size_of_streams = header.n_contour_stream_size; total_size_of_streams += header.n_points_stream_size; total_size_of_streams += header.flag_stream_size; total_size_of_streams += header.glyph_stream_size; total_size_of_streams += header.composite_stream_size; total_size_of_streams += header.bbox_stream_size; total_size_of_streams += header.instruction_stream_size; if (table_size < total_size_of_streams) return Error::from_string_literal("Not enough data to read in streams of transformed glyf table"); auto number_of_contours_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.n_contour_stream_size))); auto number_of_points_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.n_points_stream_size))); auto flag_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.flag_stream_size))); auto glyph_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.glyph_stream_size))); auto composite_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.composite_stream_size))); size_t bounding_box_bitmap_length = ((header.num_glyphs + 31) >> 5) << 2; auto bounding_box_bitmap_memory_stream = FixedMemoryStream(TRY(table_stream.read_in_place(bounding_box_bitmap_length))); auto bounding_box_bitmap_bit_stream = BigEndianInputBitStream { MaybeOwned(bounding_box_bitmap_memory_stream) }; if (header.bbox_stream_size < bounding_box_bitmap_length) return Error::from_string_literal("Not enough data to read bounding box stream of transformed glyf table"); auto bounding_box_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.bbox_stream_size - bounding_box_bitmap_length))); auto instruction_stream = FixedMemoryStream(TRY(table_stream.read_in_place(header.instruction_stream_size))); ByteBuffer reconstructed_glyf_table; Vector loca_indexes; auto append_u16 = [&](BigEndian value) -> ErrorOr { return reconstructed_glyf_table.try_append(&value, sizeof(value)); }; auto append_i16 = [&](BigEndian value) -> ErrorOr { return reconstructed_glyf_table.try_append(&value, sizeof(value)); }; auto append_bytes = [&](ReadonlyBytes bytes) -> ErrorOr { return reconstructed_glyf_table.try_append(bytes); }; for (size_t glyph_index = 0; glyph_index < header.num_glyphs; ++glyph_index) { size_t starting_glyf_table_size = reconstructed_glyf_table.size(); bool has_bounding_box = TRY(bounding_box_bitmap_bit_stream.read_bit()); auto number_of_contours = TRY(number_of_contours_stream.read_value>()); if (number_of_contours == 0) { // Empty glyph // Reconstruction of an empty glyph (when nContour = 0) is a simple step // that involves incrementing the glyph record count and creating a new entry in the loca table // where loca[n] = loca[n-1]. // If the bboxBitmap flag indicates that the bounding box values are explicitly encoded in the bboxStream // the decoder MUST reject WOFF2 file as invalid. if (has_bounding_box) return Error::from_string_literal("Empty glyphs cannot have an explicit bounding box"); } else if (number_of_contours < 0) { // Decoding of Composite Glyphs [[maybe_unused]] i16 bounding_box_x_min = 0; [[maybe_unused]] i16 bounding_box_y_min = 0; [[maybe_unused]] i16 bounding_box_x_max = 0; [[maybe_unused]] i16 bounding_box_y_max = 0; if (has_bounding_box) { bounding_box_x_min = TRY(bounding_box_stream.read_value>()); bounding_box_y_min = TRY(bounding_box_stream.read_value>()); bounding_box_x_max = TRY(bounding_box_stream.read_value>()); bounding_box_y_max = TRY(bounding_box_stream.read_value>()); } TRY(append_i16(number_of_contours)); TRY(append_i16(bounding_box_x_min)); TRY(append_i16(bounding_box_y_min)); TRY(append_i16(bounding_box_x_max)); TRY(append_i16(bounding_box_y_max)); bool have_instructions = false; u16 flags = to_underlying(OpenType::Glyf::CompositeFlags::MoreComponents); while (flags & to_underlying(OpenType::Glyf::CompositeFlags::MoreComponents)) { // 1a. Read a UInt16 from compositeStream. This is interpreted as a component flag word as in the TrueType spec. // Based on the flag values, there are between 4 and 14 additional argument bytes, // interpreted as glyph index, arg1, arg2, and optional scale or affine matrix. flags = TRY(composite_stream.read_value>()); if (flags & to_underlying(OpenType::Glyf::CompositeFlags::WeHaveInstructions)) { have_instructions = true; } // 2a. Read the number of argument bytes as determined in step 1a from the composite stream, // and store these in the reconstructed glyph. // If the flag word read in step 1a has the FLAG_MORE_COMPONENTS bit (bit 5) set, go back to step 1a. size_t argument_byte_count = 2; if (flags & to_underlying(OpenType::Glyf::CompositeFlags::Arg1AndArg2AreWords)) { argument_byte_count += 4; } else { argument_byte_count += 2; } if (flags & to_underlying(OpenType::Glyf::CompositeFlags::WeHaveAScale)) { argument_byte_count += 2; } else if (flags & to_underlying(OpenType::Glyf::CompositeFlags::WeHaveAnXAndYScale)) { argument_byte_count += 4; } else if (flags & to_underlying(OpenType::Glyf::CompositeFlags::WeHaveATwoByTwo)) { argument_byte_count += 8; } TRY(append_u16(flags)); TRY(reconstructed_glyf_table.try_append(TRY(composite_stream.read_in_place(argument_byte_count)))); } if (have_instructions) { auto number_of_instructions = TRY(read_255_u_short(glyph_stream)); TRY(append_u16(number_of_instructions)); if (number_of_instructions) TRY(reconstructed_glyf_table.try_append(TRY(instruction_stream.read_in_place(number_of_instructions)))); } } else if (number_of_contours > 0) { // Decoding of Simple Glyphs // For a simple glyph (when nContour > 0), the process continues as follows: // Each of these is the number of points of that contour. // Convert this into the endPtsOfContours[] array by computing the cumulative sum, then subtracting one. Vector end_points_of_contours; size_t number_of_points = 0; for (size_t contour_index = 0; contour_index < static_cast(number_of_contours); ++contour_index) { size_t number_of_points_for_this_contour = TRY(read_255_u_short(number_of_points_stream)); if (Checked::addition_would_overflow(number_of_points, number_of_points_for_this_contour)) return Error::from_string_literal("EOVERFLOW 1"); number_of_points += number_of_points_for_this_contour; if (number_of_points == 0) return Error::from_string_literal("EOVERFLOW 2"); TRY(end_points_of_contours.try_append(number_of_points - 1)); } auto points = TRY(retrieve_points_of_simple_glyph(flag_stream, glyph_stream, number_of_points)); auto instruction_size = TRY(read_255_u_short(glyph_stream)); auto instructions_buffer = TRY(ByteBuffer::create_zeroed(instruction_size)); if (instruction_size != 0) TRY(instruction_stream.read_until_filled(instructions_buffer)); i16 bounding_box_x_min = 0; i16 bounding_box_y_min = 0; i16 bounding_box_x_max = 0; i16 bounding_box_y_max = 0; if (has_bounding_box) { bounding_box_x_min = TRY(bounding_box_stream.read_value>()); bounding_box_y_min = TRY(bounding_box_stream.read_value>()); bounding_box_x_max = TRY(bounding_box_stream.read_value>()); bounding_box_y_max = TRY(bounding_box_stream.read_value>()); } else { for (size_t point_index = 0; point_index < points.size(); ++point_index) { auto& point = points.at(point_index); if (point_index == 0) { bounding_box_x_min = bounding_box_x_max = point.x; bounding_box_y_min = bounding_box_y_max = point.y; continue; } bounding_box_x_min = min(bounding_box_x_min, point.x); bounding_box_x_max = max(bounding_box_x_max, point.x); bounding_box_y_min = min(bounding_box_y_min, point.y); bounding_box_y_max = max(bounding_box_y_max, point.y); } } TRY(append_i16(number_of_contours)); TRY(append_i16(bounding_box_x_min)); TRY(append_i16(bounding_box_y_min)); TRY(append_i16(bounding_box_x_max)); TRY(append_i16(bounding_box_y_max)); for (auto end_point : end_points_of_contours) TRY(append_u16(end_point)); TRY(append_u16(instruction_size)); if (instruction_size != 0) TRY(append_bytes(instructions_buffer)); Vector relative_points; TRY(relative_points.try_ensure_capacity(points.size())); { i16 previous_point_x = 0; i16 previous_point_y = 0; for (auto& point : points) { i16 x = point.x - previous_point_x; i16 y = point.y - previous_point_y; relative_points.unchecked_append({ x, y, point.on_curve }); previous_point_x = point.x; previous_point_y = point.y; } } Optional last_flags; u8 repeat_count = 0; for (auto& point : relative_points) { u8 flags = 0; if (point.on_curve) flags |= SimpleGlyphFlags::OnCurve; if (point.x == 0) { flags |= SimpleGlyphFlags::XIsSameOrPositiveXShortVector; } else if (point.x > -256 && point.x < 256) { flags |= SimpleGlyphFlags::XShortVector; if (point.x > 0) flags |= SimpleGlyphFlags::XIsSameOrPositiveXShortVector; } if (point.y == 0) { flags |= SimpleGlyphFlags::YIsSameOrPositiveYShortVector; } else if (point.y > -256 && point.y < 256) { flags |= SimpleGlyphFlags::YShortVector; if (point.y > 0) flags |= SimpleGlyphFlags::YIsSameOrPositiveYShortVector; } if (last_flags.has_value() && flags == last_flags.value() && repeat_count != 0xff) { // NOTE: Update the previous entry to say it's repeating. reconstructed_glyf_table[reconstructed_glyf_table.size() - 1] |= SimpleGlyphFlags::RepeatFlag; ++repeat_count; } else { if (repeat_count != 0) { TRY(reconstructed_glyf_table.try_append(repeat_count)); repeat_count = 0; } TRY(reconstructed_glyf_table.try_append(flags)); } last_flags = flags; } if (repeat_count != 0) { TRY(reconstructed_glyf_table.try_append(repeat_count)); } for (auto& point : relative_points) { if (point.x == 0) { // No need to write to the table. } else if (point.x > -256 && point.x < 256) { TRY(reconstructed_glyf_table.try_append(abs(point.x))); } else { TRY(append_i16(point.x)); } } for (auto& point : relative_points) { if (point.y == 0) { // No need to write to the table. } else if (point.y > -256 && point.y < 256) { TRY(reconstructed_glyf_table.try_append(abs(point.y))); } else { TRY(append_i16(point.y)); } } } // NOTE: Make sure each glyph starts on a 4-byte boundary. // I haven't found the spec text for this, but it matches other implementations. while (reconstructed_glyf_table.size() % 4 != 0) { TRY(reconstructed_glyf_table.try_append(0)); } TRY(loca_indexes.try_append(starting_glyf_table_size)); } TRY(loca_indexes.try_append(reconstructed_glyf_table.size())); size_t loca_element_size_in_bytes = loca_element_size == LocaElementSize::TwoBytes ? sizeof(u16) : sizeof(u32); size_t loca_table_buffer_size = loca_indexes.size() * loca_element_size_in_bytes; ByteBuffer loca_table_buffer; TRY(loca_table_buffer.try_ensure_capacity(loca_table_buffer_size)); for (auto loca_index : loca_indexes) { if (loca_element_size == LocaElementSize::TwoBytes) { auto value = BigEndian(loca_index >> 1); loca_table_buffer.append({ &value, sizeof(value) }); } else { auto value = BigEndian(loca_index); loca_table_buffer.append({ &value, sizeof(value) }); } } return GlyfAndLocaTableBuffers { .glyf_table = move(reconstructed_glyf_table), .loca_table = move(loca_table_buffer) }; } ErrorOr> Font::try_load_from_file(StringView path) { auto woff2_file_stream = TRY(Core::File::open(path, Core::File::OpenMode::Read)); return try_load_from_externally_owned_memory(*woff2_file_stream); } ErrorOr> Font::try_load_from_externally_owned_memory(ReadonlyBytes bytes) { FixedMemoryStream stream(bytes); return try_load_from_externally_owned_memory(stream); } ErrorOr> Font::try_load_from_externally_owned_memory(SeekableStream& stream) { 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. 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. static constexpr size_t MAX_BUFFER_SIZE = 10 * MiB; if (header.length > TRY(stream.size())) return Error::from_string_literal("Invalid WOFF length"); if (header.total_compressed_size > MAX_BUFFER_SIZE) return Error::from_string_literal("Compressed font is more than 10 MiB"); if (header.meta_length == 0 && header.meta_offset != 0) return Error::from_string_literal("Invalid WOFF meta block offset"); if (header.priv_length == 0 && header.priv_offset != 0) return Error::from_string_literal("Invalid WOFF private block offset"); 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_size = clamp(header.total_sfnt_size, sizeof(OpenType::TableDirectory) + header.num_tables * sizeof(TableDirectoryEntry), MAX_BUFFER_SIZE); auto font_buffer = TRY(ByteBuffer::create_zeroed(font_buffer_size)); u16 search_range = pow_2_less_than_or_equal(header.num_tables); OpenType::TableDirectory table_directory { .sfnt_version = header.flavor, .num_tables = header.num_tables, .search_range = search_range * 16, .entry_selector = log2(search_range), .range_shift = header.num_tables * 16 - search_range * 16, }; font_buffer.overwrite(0, &table_directory, sizeof(table_directory)); Vector table_entries; 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 < header.num_tables; ++table_entry_index) { TableDirectoryEntry table_directory_entry; u8 const flags_byte = TRY(stream.read_value()); switch ((flags_byte & 0xC0) >> 6) { case 0: table_directory_entry.transformation_version = TransformationVersion::Version0; break; case 1: table_directory_entry.transformation_version = TransformationVersion::Version1; break; case 2: table_directory_entry.transformation_version = TransformationVersion::Version2; break; case 3: table_directory_entry.transformation_version = TransformationVersion::Version3; break; default: VERIFY_NOT_REACHED(); } u8 tag_number = flags_byte & 0x3F; if (tag_number != 0x3F) { table_directory_entry.tag = known_tag_names[tag_number]; } else { u8 tag_buffer[5] {}; TRY(stream.read_until_filled(Bytes { tag_buffer, 4 })); table_directory_entry.tag = StringView { tag_buffer, 4 }; } VERIFY(table_directory_entry.tag.length() == 4); table_directory_entry.original_length = TRY(read_uint_base_128(stream)); bool needs_to_read_transform_length = false; if (table_directory_entry.tag.is_one_of("glyf"sv, "loca"sv)) needs_to_read_transform_length = table_directory_entry.transformation_version == TransformationVersion::Version0; else needs_to_read_transform_length = table_directory_entry.transformation_version != TransformationVersion::Version0; if (needs_to_read_transform_length) { u32 transform_length = TRY(read_uint_base_128(stream)); table_directory_entry.transform_length = transform_length; total_length_of_all_tables += transform_length; } else { total_length_of_all_tables += table_directory_entry.original_length; } table_entries.unchecked_append(move(table_directory_entry)); } // FIXME: Read in collection header and entries. auto glyf_table = table_entries.find_if([](TableDirectoryEntry const& entry) { return entry.tag == "glyf"sv; }); auto loca_table = table_entries.find_if([](TableDirectoryEntry const& entry) { return entry.tag == "loca"sv; }); // "In other words, both glyf and loca tables must either be present in their transformed format or with null transform applied to both tables." if (glyf_table.is_end() != loca_table.is_end()) return Error::from_string_literal("Must have both 'loca' and 'glyf' tables if one of them is present"); if (!glyf_table.is_end() && !loca_table.is_end()) { if (glyf_table->transformation_version != loca_table->transformation_version) return Error::from_string_literal("The 'loca' and 'glyf' tables must have the same transformation version"); } if (!loca_table.is_end()) { if (loca_table->has_transformation() && loca_table->transform_length.value() != 0) return Error::from_string_literal("Transformed 'loca' table must have a transform length of 0"); } 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() != 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); auto brotli_stream = Compress::BrotliDecompressionStream { MaybeOwned(compressed_stream) }; auto decompressed_table_data = TRY(brotli_stream.read_until_eof()); if (decompressed_table_data.size() != total_length_of_all_tables) 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 + header.num_tables * SFNT_TABLE_SIZE; Optional glyf_and_loca_buffer; 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; auto table_buffer = TRY(ByteBuffer::create_zeroed(length_to_read)); auto table_bytes = TRY(decompressed_data_stream.read_some(table_buffer)); if (table_bytes.size() != length_to_read) return Error::from_string_literal("Not enough data to read decompressed table"); size_t table_directory_offset = SFNT_HEADER_SIZE + table_entry_index * SFNT_TABLE_SIZE; if (table_entry.has_transformation()) { if (table_entry.tag == "glyf"sv) { auto table_stream = FixedMemoryStream(table_bytes); glyf_and_loca_buffer = TRY(create_glyf_and_loca_tables_from_transformed_glyf_table(table_stream)); constexpr u32 GLYF_TAG = 0x676C7966; if (font_buffer.size() < (font_buffer_offset + glyf_and_loca_buffer->glyf_table.size())) TRY(font_buffer.try_resize(font_buffer_offset + glyf_and_loca_buffer->glyf_table.size())); OpenType::TableRecord table_record { .table_tag = GLYF_TAG, .checksum = 0, // FIXME: WOFF2 does not give us the original checksum. .offset = font_buffer_offset, .length = glyf_and_loca_buffer->glyf_table.size(), }; font_buffer.overwrite(table_directory_offset, &table_record, sizeof(table_record)); font_buffer.overwrite(font_buffer_offset, glyf_and_loca_buffer->glyf_table.data(), glyf_and_loca_buffer->glyf_table.size()); font_buffer_offset += glyf_and_loca_buffer->glyf_table.size(); } else if (table_entry.tag == "loca"sv) { // FIXME: Handle loca table coming before glyf table in input? VERIFY(glyf_and_loca_buffer.has_value()); if (font_buffer.size() < (font_buffer_offset + glyf_and_loca_buffer->loca_table.size())) TRY(font_buffer.try_resize(font_buffer_offset + glyf_and_loca_buffer->loca_table.size())); constexpr u32 LOCA_TAG = 0x6C6F6361; OpenType::TableRecord table_record { .table_tag = LOCA_TAG, .checksum = 0, // FIXME: WOFF2 does not give us the original checksum. .offset = font_buffer_offset, .length = glyf_and_loca_buffer->loca_table.size(), }; font_buffer.overwrite(table_directory_offset, &table_record, sizeof(table_record)); font_buffer.overwrite(font_buffer_offset, glyf_and_loca_buffer->loca_table.data(), glyf_and_loca_buffer->loca_table.size()); font_buffer_offset += glyf_and_loca_buffer->loca_table.size(); } else if (table_entry.tag == "hmtx"sv) { return Error::from_string_literal("Decoding transformed hmtx table not yet supported"); } else { return Error::from_string_literal("Unknown transformation"); } } else { OpenType::TableRecord table_record { .table_tag = table_entry.tag_to_u32(), .checksum = 0, // FIXME: WOFF2 does not give us the original checksum. .offset = font_buffer_offset, .length = length_to_read, }; font_buffer.overwrite(table_directory_offset, &table_record, sizeof(table_record)); if (font_buffer.size() < (font_buffer_offset + length_to_read)) TRY(font_buffer.try_resize(font_buffer_offset + length_to_read)); font_buffer.overwrite(font_buffer_offset, table_buffer.data(), length_to_read); font_buffer_offset += length_to_read; } } auto input_font = TRY(OpenType::Font::try_load_from_externally_owned_memory(font_buffer.bytes())); return adopt_ref(*new Font(input_font, move(font_buffer))); } }