1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 06:37:43 +00:00

LibGfx: Use FourCC as OpenType::Tag type

The one deviation from the spec here is to use this in the WOFF
TableDirectoryEntry's tag field. However, *not* making that a Tag made
a lot of things more complicated than they need to be.
This commit is contained in:
Sam Atkins 2023-11-20 17:04:56 +00:00 committed by Andreas Kling
parent 0423225290
commit 54d0aafff0
5 changed files with 97 additions and 127 deletions

View file

@ -11,6 +11,7 @@
#include <AK/Endian.h> #include <AK/Endian.h>
#include <AK/Types.h> #include <AK/Types.h>
#include <LibGfx/FourCC.h>
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types // https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types
namespace OpenType { namespace OpenType {
@ -39,7 +40,7 @@ struct [[gnu::packed]] LongDateTime {
}; };
static_assert(AssertSize<LongDateTime, 8>()); static_assert(AssertSize<LongDateTime, 8>());
using Tag = BigEndian<u32>; using Tag = Gfx::FourCC;
using Offset16 = BigEndian<u16>; using Offset16 = BigEndian<u16>;
// FIXME: Offset24 // FIXME: Offset24

View file

@ -48,7 +48,6 @@ u16 be_u16(u8 const*);
u32 be_u32(u8 const*); u32 be_u32(u8 const*);
i16 be_i16(u8 const*); i16 be_i16(u8 const*);
float be_fword(u8 const*); float be_fword(u8 const*);
u32 tag_from_str(char const*);
u16 be_u16(u8 const* ptr) u16 be_u16(u8 const* ptr)
{ {
@ -70,11 +69,6 @@ float be_fword(u8 const* ptr)
return (float)be_i16(ptr) / (float)(1 << 14); return (float)be_i16(ptr) / (float)(1 << 14);
} }
u32 tag_from_str(char const* str)
{
return be_u32((u8 const*)str);
}
ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_resource(Core::Resource const& resource, unsigned index) ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_resource(Core::Resource const& resource, unsigned index)
{ {
auto font = TRY(try_load_from_externally_owned_memory(resource.data(), index)); auto font = TRY(try_load_from_externally_owned_memory(resource.data(), index));
@ -87,7 +81,7 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Readonl
FixedMemoryStream stream { buffer }; FixedMemoryStream stream { buffer };
auto tag = TRY(stream.read_value<Tag>()); auto tag = TRY(stream.read_value<Tag>());
if (tag == tag_from_str("ttcf")) { if (tag == Tag("ttcf")) {
// It's a font collection // It's a font collection
TRY(stream.seek(0, SeekMode::SetPosition)); TRY(stream.seek(0, SeekMode::SetPosition));
auto ttc_header_v1 = TRY(stream.read_in_place<TTCHeaderV1>()); auto ttc_header_v1 = TRY(stream.read_in_place<TTCHeaderV1>());
@ -100,10 +94,10 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Readonl
auto offset = TRY(stream.read_value<BigEndian<u32>>()); auto offset = TRY(stream.read_value<BigEndian<u32>>());
return try_load_from_offset(buffer, offset); return try_load_from_offset(buffer, offset);
} }
if (tag == tag_from_str("OTTO")) if (tag == Tag("OTTO"))
return Error::from_string_literal("CFF fonts not supported yet"); return Error::from_string_literal("CFF fonts not supported yet");
if (tag != 0x00010000 && tag != tag_from_str("true")) if (tag.to_u32() != 0x00010000 && tag != Tag("true"))
return Error::from_string_literal("Not a valid font"); return Error::from_string_literal("Not a valid font");
return try_load_from_offset(buffer, 0); return try_load_from_offset(buffer, 0);
@ -145,35 +139,35 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_offset(ReadonlyBytes buffer, u3
auto buffer_here = buffer.slice(table_record.offset, table_record.length); auto buffer_here = buffer.slice(table_record.offset, table_record.length);
// Get the table offsets we need. // Get the table offsets we need.
if (table_record.table_tag == tag_from_str("head")) { if (table_record.table_tag == Tag("head")) {
opt_head_slice = buffer_here; opt_head_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("name")) { } else if (table_record.table_tag == Tag("name")) {
opt_name_slice = buffer_here; opt_name_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("hhea")) { } else if (table_record.table_tag == Tag("hhea")) {
opt_hhea_slice = buffer_here; opt_hhea_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("maxp")) { } else if (table_record.table_tag == Tag("maxp")) {
opt_maxp_slice = buffer_here; opt_maxp_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("hmtx")) { } else if (table_record.table_tag == Tag("hmtx")) {
opt_hmtx_slice = buffer_here; opt_hmtx_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("cmap")) { } else if (table_record.table_tag == Tag("cmap")) {
opt_cmap_slice = buffer_here; opt_cmap_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("loca")) { } else if (table_record.table_tag == Tag("loca")) {
opt_loca_slice = buffer_here; opt_loca_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("glyf")) { } else if (table_record.table_tag == Tag("glyf")) {
opt_glyf_slice = buffer_here; opt_glyf_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("OS/2")) { } else if (table_record.table_tag == Tag("OS/2")) {
opt_os2_slice = buffer_here; opt_os2_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("kern")) { } else if (table_record.table_tag == Tag("kern")) {
opt_kern_slice = buffer_here; opt_kern_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("fpgm")) { } else if (table_record.table_tag == Tag("fpgm")) {
opt_fpgm_slice = buffer_here; opt_fpgm_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("prep")) { } else if (table_record.table_tag == Tag("prep")) {
opt_prep_slice = buffer_here; opt_prep_slice = buffer_here;
} else if (table_record.table_tag == tag_from_str("CBLC")) { } else if (table_record.table_tag == Tag("CBLC")) {
cblc = TRY(CBLC::from_slice(buffer_here)); cblc = TRY(CBLC::from_slice(buffer_here));
} else if (table_record.table_tag == tag_from_str("CBDT")) { } else if (table_record.table_tag == Tag("CBDT")) {
cbdt = TRY(CBDT::from_slice(buffer_here)); cbdt = TRY(CBDT::from_slice(buffer_here));
} else if (table_record.table_tag == tag_from_str("GPOS")) { } else if (table_record.table_tag == Tag("GPOS")) {
gpos = TRY(GPOS::from_slice(buffer_here)); gpos = TRY(GPOS::from_slice(buffer_here));
} }
} }

View file

@ -16,16 +16,6 @@
namespace OpenType { namespace OpenType {
static u32 be_u32(u8 const* ptr)
{
return (((u32)ptr[0]) << 24) | (((u32)ptr[1]) << 16) | (((u32)ptr[2]) << 8) | ((u32)ptr[3]);
}
static u32 tag_from_str(char const* str)
{
return be_u32((u8 const*)str);
}
ErrorOr<Head> Head::from_slice(ReadonlyBytes slice) ErrorOr<Head> Head::from_slice(ReadonlyBytes slice)
{ {
if (slice.size() < sizeof(FontHeaderTable)) if (slice.size() < sizeof(FontHeaderTable))
@ -576,7 +566,7 @@ Optional<i16> GPOS::glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const
Optional<Offset16> kern_feature_offset; Optional<Offset16> kern_feature_offset;
for (auto const& feature_record : m_feature_records) { for (auto const& feature_record : m_feature_records) {
if (feature_record.feature_tag == tag_from_str("kern")) { if (feature_record.feature_tag == Tag("kern")) {
kern_feature_offset = feature_record.feature_offset; kern_feature_offset = feature_record.feature_offset;
break; break;
} }

View file

@ -36,7 +36,7 @@ static_assert(AssertSize<Header, 44>());
// https://www.w3.org/TR/WOFF/#TableDirectory // https://www.w3.org/TR/WOFF/#TableDirectory
struct [[gnu::packed]] TableDirectoryEntry { struct [[gnu::packed]] TableDirectoryEntry {
BigEndian<u32> tag; // 4-byte sfnt table identifier. OpenType::Tag tag; // 4-byte sfnt table identifier.
BigEndian<u32> offset; // Offset to the data, from beginning of WOFF file. 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> comp_length; // Length of the compressed data, excluding padding.
BigEndian<u32> orig_length; // Length of the uncompressed table, excluding padding. BigEndian<u32> orig_length; // Length of the uncompressed table, excluding padding.

View file

@ -125,19 +125,10 @@ enum class TransformationVersion {
struct TableDirectoryEntry { struct TableDirectoryEntry {
TransformationVersion transformation_version { TransformationVersion::Version0 }; TransformationVersion transformation_version { TransformationVersion::Version0 };
DeprecatedString tag; OpenType::Tag tag;
u32 original_length { 0 }; u32 original_length { 0 };
Optional<u32> transform_length; Optional<u32> transform_length;
u32 tag_to_u32() const
{
VERIFY(tag.length() == 4);
return (static_cast<u8>(tag[0]) << 24)
| (static_cast<u8>(tag[1]) << 16)
| (static_cast<u8>(tag[2]) << 8)
| static_cast<u8>(tag[3]);
}
bool has_transformation() const bool has_transformation() const
{ {
return transform_length.has_value(); return transform_length.has_value();
@ -145,70 +136,70 @@ struct TableDirectoryEntry {
}; };
// NOTE: Any tags less than 4 characters long are padded with spaces at the end. // NOTE: Any tags less than 4 characters long are padded with spaces at the end.
static constexpr Array<StringView, 63> known_tag_names = { static constexpr Array<OpenType::Tag, 63> known_tag_names = {
"cmap"sv, OpenType::Tag("cmap"),
"head"sv, OpenType::Tag("head"),
"hhea"sv, OpenType::Tag("hhea"),
"hmtx"sv, OpenType::Tag("hmtx"),
"maxp"sv, OpenType::Tag("maxp"),
"name"sv, OpenType::Tag("name"),
"OS/2"sv, OpenType::Tag("OS/2"),
"post"sv, OpenType::Tag("post"),
"cvt "sv, OpenType::Tag("cvt "),
"fpgm"sv, OpenType::Tag("fpgm"),
"glyf"sv, OpenType::Tag("glyf"),
"loca"sv, OpenType::Tag("loca"),
"prep"sv, OpenType::Tag("prep"),
"CFF "sv, OpenType::Tag("CFF "),
"VORG"sv, OpenType::Tag("VORG"),
"EBDT"sv, OpenType::Tag("EBDT"),
"EBLC"sv, OpenType::Tag("EBLC"),
"gasp"sv, OpenType::Tag("gasp"),
"hdmx"sv, OpenType::Tag("hdmx"),
"kern"sv, OpenType::Tag("kern"),
"LTSH"sv, OpenType::Tag("LTSH"),
"PCLT"sv, OpenType::Tag("PCLT"),
"VDMX"sv, OpenType::Tag("VDMX"),
"vhea"sv, OpenType::Tag("vhea"),
"vmtx"sv, OpenType::Tag("vmtx"),
"BASE"sv, OpenType::Tag("BASE"),
"GDEF"sv, OpenType::Tag("GDEF"),
"GPOS"sv, OpenType::Tag("GPOS"),
"GSUB"sv, OpenType::Tag("GSUB"),
"EBSC"sv, OpenType::Tag("EBSC"),
"JSTF"sv, OpenType::Tag("JSTF"),
"MATH"sv, OpenType::Tag("MATH"),
"CBDT"sv, OpenType::Tag("CBDT"),
"CBLC"sv, OpenType::Tag("CBLC"),
"COLR"sv, OpenType::Tag("COLR"),
"CPAL"sv, OpenType::Tag("CPAL"),
"SVG "sv, OpenType::Tag("SVG "),
"sbix"sv, OpenType::Tag("sbix"),
"acnt"sv, OpenType::Tag("acnt"),
"avar"sv, OpenType::Tag("avar"),
"bdat"sv, OpenType::Tag("bdat"),
"bloc"sv, OpenType::Tag("bloc"),
"bsln"sv, OpenType::Tag("bsln"),
"cvar"sv, OpenType::Tag("cvar"),
"fdsc"sv, OpenType::Tag("fdsc"),
"feat"sv, OpenType::Tag("feat"),
"fmtx"sv, OpenType::Tag("fmtx"),
"fvar"sv, OpenType::Tag("fvar"),
"gvar"sv, OpenType::Tag("gvar"),
"hsty"sv, OpenType::Tag("hsty"),
"just"sv, OpenType::Tag("just"),
"lcar"sv, OpenType::Tag("lcar"),
"mort"sv, OpenType::Tag("mort"),
"morx"sv, OpenType::Tag("morx"),
"opbd"sv, OpenType::Tag("opbd"),
"prop"sv, OpenType::Tag("prop"),
"trak"sv, OpenType::Tag("trak"),
"Zapf"sv, OpenType::Tag("Zapf"),
"Silf"sv, OpenType::Tag("Silf"),
"Glat"sv, OpenType::Tag("Glat"),
"Gloc"sv, OpenType::Tag("Gloc"),
"Feat"sv, OpenType::Tag("Feat"),
"Sill"sv, OpenType::Tag("Sill"),
}; };
struct CoordinateTripletEncoding { struct CoordinateTripletEncoding {
@ -916,16 +907,13 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Seekabl
if (tag_number != 0x3F) { if (tag_number != 0x3F) {
table_directory_entry.tag = known_tag_names[tag_number]; table_directory_entry.tag = known_tag_names[tag_number];
} else { } else {
u8 tag_buffer[5] {}; table_directory_entry.tag = TRY(stream.read_value<OpenType::Tag>());
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)); table_directory_entry.original_length = TRY(read_uint_base_128(stream));
bool needs_to_read_transform_length = false; bool needs_to_read_transform_length = false;
if (table_directory_entry.tag.is_one_of("glyf"sv, "loca"sv)) if (table_directory_entry.tag == OpenType::Tag("glyf") || table_directory_entry.tag == OpenType::Tag("loca"))
needs_to_read_transform_length = table_directory_entry.transformation_version == TransformationVersion::Version0; needs_to_read_transform_length = table_directory_entry.transformation_version == TransformationVersion::Version0;
else else
needs_to_read_transform_length = table_directory_entry.transformation_version != TransformationVersion::Version0; needs_to_read_transform_length = table_directory_entry.transformation_version != TransformationVersion::Version0;
@ -944,11 +932,11 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Seekabl
// FIXME: Read in collection header and entries. // FIXME: Read in collection header and entries.
auto glyf_table = table_entries.find_if([](TableDirectoryEntry const& entry) { auto glyf_table = table_entries.find_if([](TableDirectoryEntry const& entry) {
return entry.tag == "glyf"sv; return entry.tag == OpenType::Tag("glyf");
}); });
auto loca_table = table_entries.find_if([](TableDirectoryEntry const& entry) { auto loca_table = table_entries.find_if([](TableDirectoryEntry const& entry) {
return entry.tag == "loca"sv; return entry.tag == OpenType::Tag("loca");
}); });
// "In other words, both glyf and loca tables must either be present in their transformed format or with null transform applied to both tables." // "In other words, both glyf and loca tables must either be present in their transformed format or with null transform applied to both tables."
@ -991,17 +979,15 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Seekabl
size_t table_directory_offset = SFNT_HEADER_SIZE + table_entry_index * SFNT_TABLE_SIZE; size_t table_directory_offset = SFNT_HEADER_SIZE + table_entry_index * SFNT_TABLE_SIZE;
if (table_entry.has_transformation()) { if (table_entry.has_transformation()) {
if (table_entry.tag == "glyf"sv) { if (table_entry.tag == OpenType::Tag("glyf")) {
auto table_stream = FixedMemoryStream(table_bytes); auto table_stream = FixedMemoryStream(table_bytes);
glyf_and_loca_buffer = TRY(create_glyf_and_loca_tables_from_transformed_glyf_table(table_stream)); 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())) 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())); TRY(font_buffer.try_resize(font_buffer_offset + glyf_and_loca_buffer->glyf_table.size()));
OpenType::TableRecord table_record { OpenType::TableRecord table_record {
.table_tag = GLYF_TAG, .table_tag = table_entry.tag,
.checksum = 0, // FIXME: WOFF2 does not give us the original checksum. .checksum = 0, // FIXME: WOFF2 does not give us the original checksum.
.offset = font_buffer_offset, .offset = font_buffer_offset,
.length = glyf_and_loca_buffer->glyf_table.size(), .length = glyf_and_loca_buffer->glyf_table.size(),
@ -1010,15 +996,14 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Seekabl
font_buffer.overwrite(font_buffer_offset, glyf_and_loca_buffer->glyf_table.data(), glyf_and_loca_buffer->glyf_table.size()); 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(); font_buffer_offset += glyf_and_loca_buffer->glyf_table.size();
} else if (table_entry.tag == "loca"sv) { } else if (table_entry.tag == OpenType::Tag("loca")) {
// FIXME: Handle loca table coming before glyf table in input? // FIXME: Handle loca table coming before glyf table in input?
VERIFY(glyf_and_loca_buffer.has_value()); VERIFY(glyf_and_loca_buffer.has_value());
if (font_buffer.size() < (font_buffer_offset + glyf_and_loca_buffer->loca_table.size())) 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())); TRY(font_buffer.try_resize(font_buffer_offset + glyf_and_loca_buffer->loca_table.size()));
constexpr u32 LOCA_TAG = 0x6C6F6361;
OpenType::TableRecord table_record { OpenType::TableRecord table_record {
.table_tag = LOCA_TAG, .table_tag = table_entry.tag,
.checksum = 0, // FIXME: WOFF2 does not give us the original checksum. .checksum = 0, // FIXME: WOFF2 does not give us the original checksum.
.offset = font_buffer_offset, .offset = font_buffer_offset,
.length = glyf_and_loca_buffer->loca_table.size(), .length = glyf_and_loca_buffer->loca_table.size(),
@ -1027,14 +1012,14 @@ ErrorOr<NonnullRefPtr<Font>> Font::try_load_from_externally_owned_memory(Seekabl
font_buffer.overwrite(font_buffer_offset, glyf_and_loca_buffer->loca_table.data(), glyf_and_loca_buffer->loca_table.size()); 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(); font_buffer_offset += glyf_and_loca_buffer->loca_table.size();
} else if (table_entry.tag == "hmtx"sv) { } else if (table_entry.tag == OpenType::Tag("hmtx")) {
return Error::from_string_literal("Decoding transformed hmtx table not yet supported"); return Error::from_string_literal("Decoding transformed hmtx table not yet supported");
} else { } else {
return Error::from_string_literal("Unknown transformation"); return Error::from_string_literal("Unknown transformation");
} }
} else { } else {
OpenType::TableRecord table_record { OpenType::TableRecord table_record {
.table_tag = table_entry.tag_to_u32(), .table_tag = table_entry.tag,
.checksum = 0, // FIXME: WOFF2 does not give us the original checksum. .checksum = 0, // FIXME: WOFF2 does not give us the original checksum.
.offset = font_buffer_offset, .offset = font_buffer_offset,
.length = length_to_read, .length = length_to_read,