LibGfx+LibWeb: Replace remaining OpenType implementation with Skia

This change should move us forward toward emoji support, as we are no
longer limited by our own OpenType implementation, which was failing
to parse the TrueType Collection format used to store emoji fonts
(at least on macOS).
This commit is contained in:
Aliaksandr Kalenik 2024-09-04 17:29:01 +02:00 committed by Alexander Kalenik
parent 99f64139d0
commit a9d5a99568
Notes: github-actions[bot] 2024-09-05 17:22:50 +00:00
40 changed files with 309 additions and 3201 deletions

22
AK/LsanSuppressions.h Normal file
View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Platform.h>
#ifdef HAS_ADDRESS_SANITIZER
extern "C" {
char const* __lsan_default_suppressions();
char const* __lsan_default_suppressions()
{
// Both Skia and Chromium suppress false positive FontConfig leaks
// https://github.com/google/skia/blob/main/tools/LsanSuppressions.cpp#L20
// https://chromium.googlesource.com/chromium/src/build/+/master/sanitizers/lsan_suppressions.cc#25
return "leak:FcPatternObjectInsertElt";
}
}
#endif

View file

@ -523,7 +523,6 @@ if (BUILD_TESTING)
list(APPEND TEST_DIRECTORIES
LibGfx
LibMedia
LibTTF
LibWeb
LibWebView
)

View file

@ -1,16 +0,0 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Font/OpenType/Typeface.h>
#include <stddef.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(u8 const* data, size_t size)
{
AK::set_debug_enabled(false);
(void)OpenType::Typeface::try_load_from_externally_owned_memory({ data, size });
return 0;
}

View file

@ -36,7 +36,6 @@ set(FUZZER_TARGETS
Tar
TextDecoder
TIFFLoader
TTF
TinyVGLoader
URL
WasmParser

View file

@ -1,7 +0,0 @@
set(TEST_SOURCES
TestCmap.cpp
)
foreach(source IN LISTS TEST_SOURCES)
serenity_test("${source}" LibTTF LIBS LibGfx)
endforeach()

View file

@ -1,88 +0,0 @@
/*
* Copyright (c) 2022, Nico Weber <thakis@chromium.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Font/OpenType/Cmap.h>
#include <LibTest/TestCase.h>
TEST_CASE(test_cmap_format_4)
{
// clang-format off
// Big endian.
Array<u8, 52> const cmap_table =
{
// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#cmap-header
0, 0, // uint16 version
0, 1, // uint16 numTables
// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#encoding-records-and-encodings
0, 0, // uint16 platformID, 0 means "Unicode"
0, 3, // uint16 encodingID, 3 means "BMP only" for platformID==0.
0, 0, 0, 12, // Offset32 to encoding subtable.
// https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values
0, 4, // uint16 format = 4
0, 42, // uint16 length in bytes
0, 0, // uint16 language, must be 0
0, 6, // segCount * 2
0, 4, // searchRange
0, 1, // entrySelector
0, 2, // rangeShift
// endCode array, last entry must be 0xffff.
0, 128,
1, 0,
0xff, 0xff,
0, 0, // uint16 reservedPad
// startCode array
0, 16,
1, 0,
0xff, 0xff,
// delta array
0, 0,
0, 10,
0, 0,
// glyphID array
0, 0,
0, 0,
0, 0,
};
// clang-format on
auto cmap = OpenType::Cmap::from_slice(cmap_table.span()).value();
cmap.set_active_index(0);
// Format 4 can't handle code points > 0xffff.
// First range is 16..128.
EXPECT_EQ(cmap.glyph_id_for_code_point(15), 0u);
EXPECT_EQ(cmap.glyph_id_for_code_point(16), 16u);
EXPECT_EQ(cmap.glyph_id_for_code_point(128), 128u);
EXPECT_EQ(cmap.glyph_id_for_code_point(129), 0u);
// Second range is 256..256, with delta 10.
EXPECT_EQ(cmap.glyph_id_for_code_point(255), 0u);
EXPECT_EQ(cmap.glyph_id_for_code_point(256), 266u);
EXPECT_EQ(cmap.glyph_id_for_code_point(257), 0u);
// Third range is 0xffff..0xffff.
// From https://docs.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values:
// "the final start code and endCode values must be 0xFFFF. This segment need not contain any valid mappings.
// (It can just map the single character code 0xFFFF to missingGlyph). However, the segment must be present."
// FIXME: Make OpenType::Cmap::from_slice() reject inputs where this isn't true.
EXPECT_EQ(cmap.glyph_id_for_code_point(0xfeff), 0u);
EXPECT_EQ(cmap.glyph_id_for_code_point(0xffff), 0xffffu);
EXPECT_EQ(cmap.glyph_id_for_code_point(0x1'0000), 0u);
// Set the number of subtables to a value, where the record offset for the last subtable is greater than the
// total table size. We should not crash if a Cmap table is truncated in this way.
auto malformed_cmap_table = cmap_table;
malformed_cmap_table[3] = 13;
auto cmap_with_invalid_subtable_offset = OpenType::Cmap::from_slice(malformed_cmap_table.span()).value();
EXPECT(!cmap_with_invalid_subtable_offset.subtable(12).has_value());
}

View file

@ -1,10 +1,10 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x37 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x21 children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 191.875x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 191.875x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 187.875x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 187.875x17 flex-item [BFC] children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 200x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 200x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 196x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 196x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 11, rect: [11,10 91.953125x17] baseline: 13.296875
"Hello World"
TextNode <#text>
@ -13,7 +13,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x37]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
PaintableWithLines (BlockContainer<INPUT>) [8,8 193.875x21]
PaintableBox (Box<DIV>) [9,9 191.875x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 187.875x17]
PaintableWithLines (BlockContainer<INPUT>) [8,8 202x21]
PaintableBox (Box<DIV>) [9,9 200x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 196x17]
TextPaintable (TextNode<#text>)

View file

@ -6,12 +6,12 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
frag 0 from TextNode start: 0, length: 4, rect: [8,8 28.40625x17] baseline: 13.296875
"well"
TextNode <#text>
BlockContainer <(anonymous)> at (46,8) content-size 36.84375x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 5, rect: [46,8 36.84375x17] baseline: 13.296875
BlockContainer <(anonymous)> at (46.40625,8) content-size 36.84375x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 5, rect: [46.40625,8 36.84375x17] baseline: 13.296875
"hello"
TextNode <#text>
BlockContainer <(anonymous)> at (92.4375,8) content-size 55.359375x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 7, rect: [92.4375,8 55.359375x17] baseline: 13.296875
BlockContainer <(anonymous)> at (93.25,8) content-size 55.359375x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 7, rect: [93.25,8 55.359375x17] baseline: 13.296875
"friends"
TextNode <#text>
@ -21,7 +21,7 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableBox (Box<DIV>.foo) [8,8 784x17]
PaintableWithLines (BlockContainer(anonymous)) [8,8 28.40625x17]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [46,8 36.84375x17]
PaintableWithLines (BlockContainer(anonymous)) [46.40625,8 36.84375x17]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [92.4375,8 55.359375x17]
PaintableWithLines (BlockContainer(anonymous)) [93.25,8 55.359375x17]
TextPaintable (TextNode<#text>)

View file

@ -1,13 +1,13 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x16 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x0 children: not-inline
BlockContainer <div> at (8,8) content-size 784x0 children: inline
frag 0 from TextNode start: 0, length: 21, rect: [8,8 0x0] baseline: 0
BlockContainer <html> at (0,0) content-size 800x17 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x1 children: not-inline
BlockContainer <div> at (8,8) content-size 784x1 children: inline
frag 0 from TextNode start: 0, length: 21, rect: [8,8 0x1] baseline: 0.796875
"should not be visible"
TextNode <#text>
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x16]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x0]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x0]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x17]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x1]
PaintableWithLines (BlockContainer<DIV>) [8,8 784x1]
TextPaintable (TextNode<#text>)

View file

@ -1,10 +1,10 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x21 children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 189.875x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 189.875x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 185.875x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 185.875x17 flex-item [BFC] children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 198x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 198x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 194x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 194x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 7, rect: [11,10 55.6875x17] baseline: 13.296875
"120.png"
TextNode <#text>
@ -14,7 +14,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
PaintableWithLines (BlockContainer<INPUT>) [8,8 191.875x21]
PaintableBox (Box<DIV>) [9,9 189.875x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 185.875x17]
PaintableWithLines (BlockContainer<INPUT>) [8,8 200x21]
PaintableBox (Box<DIV>) [9,9 198x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 194x17]
TextPaintable (TextNode<#text>)

View file

@ -1,10 +1,10 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x21 children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 189.875x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 189.875x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 185.875x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 185.875x17 flex-item [BFC] children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 198x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 198x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 194x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 194x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 7, rect: [11,10 61.890625x17] baseline: 13.296875
"hunter2"
TextNode <#text>
@ -14,7 +14,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
PaintableWithLines (BlockContainer<INPUT>) [8,8 191.875x21]
PaintableBox (Box<DIV>) [9,9 189.875x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 185.875x17]
PaintableWithLines (BlockContainer<INPUT>) [8,8 200x21]
PaintableBox (Box<DIV>) [9,9 198x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 194x17]
TextPaintable (TextNode<#text>)

View file

@ -1,10 +1,10 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x21 children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 189.875x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 189.875x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 185.875x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 185.875x17 flex-item [BFC] children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 198x19] baseline: 14.296875
BlockContainer <input> at (9,9) content-size 198x19 inline-block [BFC] children: not-inline
Box <div> at (11,10) content-size 194x17 flex-container(row) [FFC] children: not-inline
BlockContainer <div> at (11,10) content-size 194x17 flex-item [BFC] children: inline
frag 0 from TextNode start: 0, length: 7, rect: [11,10 55.5625x17] baseline: 13.296875
"*******"
TextNode <#text>
@ -14,7 +14,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
PaintableWithLines (BlockContainer<INPUT>) [8,8 191.875x21]
PaintableBox (Box<DIV>) [9,9 189.875x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 185.875x17]
PaintableWithLines (BlockContainer<INPUT>) [8,8 200x21]
PaintableBox (Box<DIV>) [9,9 198x19]
PaintableWithLines (BlockContainer<DIV>) [11,10 194x17]
TextPaintable (TextNode<#text>)

View file

@ -2,11 +2,11 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x34 children: not-inline
BlockContainer <form#form> at (8,8) content-size 784x34 children: inline
frag 0 from BlockContainer start: 0, length: 0, rect: [11,11 185.875x28] baseline: 16.296875
frag 0 from BlockContainer start: 0, length: 0, rect: [11,11 194x28] baseline: 16.296875
TextNode <#text>
BlockContainer <textarea#textarea> at (11,11) content-size 185.875x28 inline-block [BFC] children: not-inline
BlockContainer <div> at (11,11) content-size 185.875x17 children: not-inline
BlockContainer <div> at (11,11) content-size 185.875x17 children: inline
BlockContainer <textarea#textarea> at (11,11) content-size 194x28 inline-block [BFC] children: not-inline
BlockContainer <div> at (11,11) content-size 194x17 children: not-inline
BlockContainer <div> at (11,11) content-size 194x17 children: inline
frag 0 from TextNode start: 0, length: 14, rect: [11,11 108.453125x17] baseline: 13.296875
"Original value"
TextNode <#text>
@ -19,8 +19,8 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
PaintableWithLines (BlockContainer<BODY>) [8,8 784x34] overflow: [8,8 784x50]
PaintableWithLines (BlockContainer<FORM>#form) [8,8 784x34]
PaintableWithLines (BlockContainer<TEXTAREA>#textarea) [8,8 191.875x34]
PaintableWithLines (BlockContainer<DIV>) [11,11 185.875x17]
PaintableWithLines (BlockContainer<DIV>) [11,11 185.875x17]
PaintableWithLines (BlockContainer<TEXTAREA>#textarea) [8,8 200x34]
PaintableWithLines (BlockContainer<DIV>) [11,11 194x17]
PaintableWithLines (BlockContainer<DIV>) [11,11 194x17]
TextPaintable (TextNode<#text>)
PaintableWithLines (BlockContainer(anonymous)) [8,58 784x0]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -15,11 +15,6 @@ set(SOURCES
Font/Font.cpp
Font/FontData.cpp
Font/FontDatabase.cpp
Font/OpenType/Cmap.cpp
Font/OpenType/Glyf.cpp
Font/OpenType/Hinting/Opcodes.cpp
Font/OpenType/Tables.cpp
Font/OpenType/Typeface.cpp
Font/ScaledFont.cpp
Font/ScaledFontSkia.cpp
Font/Typeface.cpp

View file

@ -11,7 +11,6 @@
#include <LibCore/Resource.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/WOFF/Loader.h>
@ -55,7 +54,7 @@ void FontDatabase::load_all_fonts_from_uri(StringView uri)
auto path = LexicalPath(uri.bytes_as_string_view());
if (path.has_extension(".ttf"sv)) {
// FIXME: What about .otf
if (auto font_or_error = OpenType::Typeface::try_load_from_resource(resource); !font_or_error.is_error()) {
if (auto font_or_error = Typeface::try_load_from_resource(resource); !font_or_error.is_error()) {
auto font = font_or_error.release_value();
auto& family = m_private->typeface_by_family.ensure(font->family(), [] {
return Vector<NonnullRefPtr<Typeface>> {};

View file

@ -1,216 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Error.h>
#include <AK/Format.h>
#include <AK/Optional.h>
#include <LibGfx/Font/OpenType/Cmap.h>
namespace OpenType {
extern u16 be_u16(u8 const*);
extern u32 be_u32(u8 const*);
extern i16 be_i16(u8 const*);
Optional<Cmap::Subtable::Platform> Cmap::Subtable::platform_id() const
{
switch (m_raw_platform_id) {
case 0:
return Platform::Unicode;
case 1:
return Platform::Macintosh;
case 3:
return Platform::Windows;
case 4:
return Platform::Custom;
default:
return {};
}
}
Cmap::Subtable::Format Cmap::Subtable::format() const
{
switch (be_u16(m_slice.offset(0))) {
case 0:
return Format::ByteEncoding;
case 2:
return Format::HighByte;
case 4:
return Format::SegmentToDelta;
case 6:
return Format::TrimmedTable;
case 8:
return Format::Mixed16And32;
case 10:
return Format::TrimmedArray;
case 12:
return Format::SegmentedCoverage;
case 13:
return Format::ManyToOneRange;
case 14:
return Format::UnicodeVariationSequences;
default:
VERIFY_NOT_REACHED();
}
}
u32 Cmap::num_subtables() const
{
return be_u16(m_slice.offset((u32)Offsets::NumTables));
}
Optional<Cmap::Subtable> Cmap::subtable(u32 index) const
{
if (index >= num_subtables()) {
return {};
}
u32 record_offset = (u32)Sizes::TableHeader + index * (u32)Sizes::EncodingRecord;
if (record_offset + (u32)Offsets::EncodingRecord_Offset + sizeof(u32) > m_slice.size())
return {};
u16 platform_id = be_u16(m_slice.offset(record_offset));
u16 encoding_id = be_u16(m_slice.offset(record_offset + (u32)Offsets::EncodingRecord_EncodingID));
u32 subtable_offset = be_u32(m_slice.offset(record_offset + (u32)Offsets::EncodingRecord_Offset));
if (subtable_offset >= m_slice.size())
return {};
auto subtable_slice = ReadonlyBytes(m_slice.offset(subtable_offset), m_slice.size() - subtable_offset);
return Subtable(subtable_slice, platform_id, encoding_id);
}
ErrorOr<void> Cmap::validate_active_cmap_format() const
{
auto opt_subtable = subtable(m_active_index);
VERIFY(opt_subtable.has_value());
return opt_subtable.value().validate_format_can_be_read();
}
ErrorOr<void> Cmap::Subtable::validate_format_can_be_read() const
{
// Keep in sync with switch in glyph_id_for_code_point().
switch (format()) {
case Format::ByteEncoding:
case Format::SegmentToDelta:
case Format::TrimmedTable:
case Format::SegmentedCoverage:
return {};
default:
return Error::from_string_view("Unimplemented cmap format"sv);
}
}
// FIXME: Implement the missing formats.
u32 Cmap::Subtable::glyph_id_for_code_point(u32 code_point) const
{
// Keep in sync with switch in validate_format_can_be_read().
switch (format()) {
case Format::ByteEncoding:
return glyph_id_for_code_point_table_0(code_point);
case Format::SegmentToDelta:
return glyph_id_for_code_point_table_4(code_point);
case Format::TrimmedTable:
return glyph_id_for_code_point_table_6(code_point);
case Format::SegmentedCoverage:
return glyph_id_for_code_point_table_12(code_point);
default:
dbgln("OpenType Cmap: Unimplemented format {}", (int)format());
return 0;
}
}
u32 Cmap::Subtable::glyph_id_for_code_point_table_0(u32 code_point) const
{
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-0-byte-encoding-table
if (code_point > 255)
return 0;
return m_slice.at((u32)Table0Offsets::GlyphIdArray + code_point);
}
u32 Cmap::Subtable::glyph_id_for_code_point_table_4(u32 code_point) const
{
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-4-segment-mapping-to-delta-values
u32 segcount_x2 = be_u16(m_slice.offset((u32)Table4Offsets::SegCountX2));
if (m_slice.size() < segcount_x2 * (u32)Table4Sizes::NonConstMultiplier + (u32)Table4Sizes::Constant)
return 0;
u32 segcount = segcount_x2 / 2;
u32 l = 0, r = segcount - 1;
while (l < r) {
u32 mid = l + (r - l) / 2;
u32 end_code_point_at_mid = be_u16(m_slice.offset((u32)Table4Offsets::EndConstBase + (mid * 2)));
if (code_point <= end_code_point_at_mid)
r = mid;
else
l = mid + 1;
}
u32 offset = l * 2;
u32 start_code_point = be_u16(m_slice.offset((u32)Table4Offsets::StartConstBase + segcount_x2 + offset));
if (start_code_point > code_point)
return 0;
u32 delta = be_u16(m_slice.offset((u32)Table4Offsets::DeltaConstBase + segcount_x2 * 2 + offset));
u32 range = be_u16(m_slice.offset((u32)Table4Offsets::RangeConstBase + segcount_x2 * 3 + offset));
if (range == 0)
return (code_point + delta) & 0xffff;
u32 glyph_offset = (u32)Table4Offsets::GlyphOffsetConstBase + segcount_x2 * 3 + offset + range + (code_point - start_code_point) * 2;
VERIFY(glyph_offset + 2 <= m_slice.size());
return (be_u16(m_slice.offset(glyph_offset)) + delta) & 0xffff;
}
u32 Cmap::Subtable::glyph_id_for_code_point_table_6(u32 code_point) const
{
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-6-trimmed-table-mapping
u32 first_code = be_u16(m_slice.offset((u32)Table6Offsets::FirstCode));
if (code_point < first_code)
return 0;
u32 entry_count = be_u16(m_slice.offset((u32)Table6Offsets::EntryCount));
u32 code_offset = code_point - first_code;
if (code_offset >= entry_count)
return 0;
return be_u16(m_slice.offset((u32)Table6Offsets::GlyphIdArray + code_offset * 2));
}
u32 Cmap::Subtable::glyph_id_for_code_point_table_12(u32 code_point) const
{
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap#format-12-segmented-coverage
u32 num_groups = be_u32(m_slice.offset((u32)Table12Offsets::NumGroups));
VERIFY(m_slice.size() >= (u32)Table12Sizes::Header + (u32)Table12Sizes::Record * num_groups);
for (u32 offset = 0; offset < num_groups * (u32)Table12Sizes::Record; offset += (u32)Table12Sizes::Record) {
u32 start_code_point = be_u32(m_slice.offset((u32)Table12Offsets::Record_StartCode + offset));
if (code_point < start_code_point)
break;
u32 end_code_point = be_u32(m_slice.offset((u32)Table12Offsets::Record_EndCode + offset));
if (code_point > end_code_point)
continue;
u32 glyph_offset = be_u32(m_slice.offset((u32)Table12Offsets::Record_StartGlyph + offset));
return code_point - start_code_point + glyph_offset;
}
return 0;
}
u32 Cmap::glyph_id_for_code_point(u32 code_point) const
{
auto opt_subtable = subtable(m_active_index);
if (!opt_subtable.has_value())
return 0;
auto subtable = opt_subtable.value();
return subtable.glyph_id_for_code_point(code_point);
}
ErrorOr<Cmap> Cmap::from_slice(ReadonlyBytes slice)
{
if (slice.size() < (size_t)Sizes::TableHeader)
return Error::from_string_literal("Could not load Cmap: Not enough data");
return Cmap(slice);
}
}

View file

@ -1,137 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
#include <stdint.h>
namespace OpenType {
// https://learn.microsoft.com/en-us/typography/opentype/spec/cmap
// cmap — Character to Glyph Index Mapping Table
class Cmap {
public:
class Subtable {
public:
enum class Platform {
Unicode = 0,
Macintosh = 1,
Windows = 3,
Custom = 4,
};
enum class Format {
ByteEncoding = 0,
HighByte = 2,
SegmentToDelta = 4,
TrimmedTable = 6,
Mixed16And32 = 8,
TrimmedArray = 10,
SegmentedCoverage = 12,
ManyToOneRange = 13,
UnicodeVariationSequences = 14,
};
enum class UnicodeEncoding {
DeprecatedUnicode1_0 = 0,
DeprecatedUnicode1_1 = 1,
DeprecatedISO10646 = 2,
Unicode2_0_BMP_Only = 3,
Unicode2_0_FullRepertoire = 4,
UnicodeVariationSequences = 5, // "for use with subtable format 14"
UnicodeFullRepertoire = 6, // "for use with subtable format 13"
};
enum class WindowsEncoding {
UnicodeBMP = 1,
UnicodeFullRepertoire = 10,
};
Subtable(ReadonlyBytes slice, u16 platform_id, u16 encoding_id)
: m_slice(slice)
, m_raw_platform_id(platform_id)
, m_encoding_id(encoding_id)
{
}
ErrorOr<void> validate_format_can_be_read() const;
// Returns 0 if glyph not found. This corresponds to the "missing glyph"
u32 glyph_id_for_code_point(u32 code_point) const;
Optional<Platform> platform_id() const;
u16 encoding_id() const { return m_encoding_id; }
Format format() const;
private:
enum class Table0Offsets {
GlyphIdArray = 6
};
enum class Table4Offsets {
SegCountX2 = 6,
EndConstBase = 14,
StartConstBase = 16,
DeltaConstBase = 16,
RangeConstBase = 16,
GlyphOffsetConstBase = 16,
};
enum class Table4Sizes {
Constant = 16,
NonConstMultiplier = 4,
};
enum class Table6Offsets {
FirstCode = 6,
EntryCount = 8,
GlyphIdArray = 10
};
enum class Table12Offsets {
NumGroups = 12,
Record_StartCode = 16,
Record_EndCode = 20,
Record_StartGlyph = 24,
};
enum class Table12Sizes {
Header = 16,
Record = 12,
};
u32 glyph_id_for_code_point_table_0(u32 code_point) const;
u32 glyph_id_for_code_point_table_4(u32 code_point) const;
u32 glyph_id_for_code_point_table_6(u32 code_point) const;
u32 glyph_id_for_code_point_table_12(u32 code_point) const;
ReadonlyBytes m_slice;
u16 m_raw_platform_id { 0 };
u16 m_encoding_id { 0 };
};
static ErrorOr<Cmap> from_slice(ReadonlyBytes);
u32 num_subtables() const;
Optional<Subtable> subtable(u32 index) const;
void set_active_index(u32 index) { m_active_index = index; }
ErrorOr<void> validate_active_cmap_format() const;
// Returns 0 if glyph not found. This corresponds to the "missing glyph"
u32 glyph_id_for_code_point(u32 code_point) const;
private:
enum class Offsets {
NumTables = 2,
EncodingRecord_EncodingID = 2,
EncodingRecord_Offset = 4,
};
enum class Sizes {
TableHeader = 4,
EncodingRecord = 8,
};
Cmap(ReadonlyBytes slice)
: m_slice(slice)
{
}
ReadonlyBytes m_slice;
u32 m_active_index { UINT32_MAX };
};
}

View file

@ -1,74 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
* Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
* Copyright (c) 2023, Lukas Affolter <git@lukasach.dev>
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Endian.h>
#include <AK/Types.h>
#include <LibGfx/FourCC.h>
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types
namespace OpenType {
using Uint8 = u8;
using Int8 = i8;
using Uint16 = BigEndian<u16>;
using Int16 = BigEndian<i16>;
// FIXME: Uint24
using Uint32 = BigEndian<u32>;
using Int32 = BigEndian<i32>;
struct [[gnu::packed]] Fixed {
BigEndian<u16> integer;
BigEndian<u16> fraction;
};
static_assert(AssertSize<Fixed, 4>());
using FWord = BigEndian<i16>;
using UFWord = BigEndian<u16>;
// FIXME: F2Dot14
struct [[gnu::packed]] LongDateTime {
BigEndian<u64> value;
};
static_assert(AssertSize<LongDateTime, 8>());
using Tag = Gfx::FourCC;
using Offset16 = BigEndian<u16>;
// FIXME: Offset24
using Offset32 = BigEndian<u32>;
struct [[gnu::packed]] Version16Dot16 {
BigEndian<u16> major;
BigEndian<u16> minor;
};
static_assert(AssertSize<Version16Dot16, 4>());
}
namespace AK {
template<>
struct Traits<OpenType::Fixed> : public DefaultTraits<OpenType::Fixed> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::LongDateTime> : public DefaultTraits<OpenType::LongDateTime> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Tag> : public DefaultTraits<OpenType::Tag> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Version16Dot16> : public DefaultTraits<OpenType::Version16Dot16> {
static constexpr bool is_trivially_serializable() { return true; }
};
}

View file

@ -1,59 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Font/OpenType/Glyf.h>
#include <LibGfx/Point.h>
namespace OpenType {
extern u16 be_u16(u8 const* ptr);
extern u32 be_u32(u8 const* ptr);
ErrorOr<Loca> Loca::from_slice(ReadonlyBytes slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
{
switch (index_to_loc_format) {
case IndexToLocFormat::Offset16:
if (slice.size() < num_glyphs * 2)
return Error::from_string_literal("Could not load Loca: Not enough data");
break;
case IndexToLocFormat::Offset32:
if (slice.size() < num_glyphs * 4)
return Error::from_string_literal("Could not load Loca: Not enough data");
break;
}
return Loca(slice, num_glyphs, index_to_loc_format);
}
u32 Loca::get_glyph_offset(u32 glyph_id) const
{
// NOTE: The value of n is numGlyphs + 1.
VERIFY(glyph_id <= m_num_glyphs);
switch (m_index_to_loc_format) {
case IndexToLocFormat::Offset16:
return ((u32)be_u16(m_slice.offset(glyph_id * 2))) * 2;
case IndexToLocFormat::Offset32:
return be_u32(m_slice.offset(glyph_id * 4));
default:
VERIFY_NOT_REACHED();
}
}
Optional<Glyf::Glyph> Glyf::glyph(u32 offset) const
{
if (offset + sizeof(GlyphHeader) > m_slice.size())
return {};
VERIFY(m_slice.size() >= offset + sizeof(GlyphHeader));
auto const& glyph_header = *bit_cast<GlyphHeader const*>(m_slice.offset(offset));
i16 num_contours = glyph_header.number_of_contours;
i16 xmin = glyph_header.x_min;
i16 ymin = glyph_header.y_min;
i16 xmax = glyph_header.x_max;
i16 ymax = glyph_header.y_max;
auto slice = m_slice.slice(offset + sizeof(GlyphHeader));
return Glyph(slice, xmin, ymin, xmax, ymax, num_contours);
}
}

View file

@ -1,99 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Endian.h>
#include <AK/Span.h>
#include <AK/Vector.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/OpenType/Tables.h>
#include <LibGfx/Size.h>
#include <math.h>
namespace OpenType {
// https://learn.microsoft.com/en-us/typography/opentype/spec/loca
// loca: Index to Location
class Loca {
public:
static ErrorOr<Loca> from_slice(ReadonlyBytes, u32 num_glyphs, IndexToLocFormat);
u32 get_glyph_offset(u32 glyph_id) const;
private:
Loca(ReadonlyBytes slice, u32 num_glyphs, IndexToLocFormat index_to_loc_format)
: m_slice(slice)
, m_num_glyphs(num_glyphs)
, m_index_to_loc_format(index_to_loc_format)
{
}
ReadonlyBytes m_slice;
u32 m_num_glyphs { 0 };
IndexToLocFormat m_index_to_loc_format;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/glyf
// glyf: Glyph Data
class Glyf {
public:
class Glyph {
public:
Glyph(ReadonlyBytes slice, i16 xmin, i16 ymin, i16 xmax, i16 ymax, i16 num_contours = -1)
: m_xmin(xmin)
, m_ymin(ymin)
, m_xmax(xmax)
, m_ymax(ymax)
, m_num_contours(num_contours)
, m_slice(slice)
{
if (m_num_contours >= 0) {
m_type = Type::Simple;
}
}
i16 xmax() const { return m_xmax; }
i16 xmin() const { return m_xmin; }
int ascender() const { return m_ymax; }
int descender() const { return m_ymin; }
private:
enum class Type {
Simple,
Composite,
};
Type m_type { Type::Composite };
i16 m_xmin { 0 };
i16 m_ymin { 0 };
i16 m_xmax { 0 };
i16 m_ymax { 0 };
i16 m_num_contours { -1 };
ReadonlyBytes m_slice;
};
Glyf(ReadonlyBytes slice)
: m_slice(slice)
{
}
Optional<Glyph> glyph(u32 offset) const;
private:
// https://learn.microsoft.com/en-us/typography/opentype/spec/glyf#glyph-headers
struct [[gnu::packed]] GlyphHeader {
BigEndian<i16> number_of_contours;
BigEndian<i16> x_min;
BigEndian<i16> y_min;
BigEndian<i16> x_max;
BigEndian<i16> y_max;
};
static_assert(AssertSize<GlyphHeader, 10>());
ReadonlyBytes m_slice;
};
}

View file

@ -1,136 +0,0 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/IntegralMath.h>
#include <AK/ScopeGuard.h>
#include <LibGfx/Font/OpenType/Hinting/Opcodes.h>
namespace OpenType::Hinting {
StringView opcode_mnemonic(Opcode opcode)
{
switch (to_underlying(opcode)) {
#define __ENUMERATE_OPENTYPE_OPCODES(mnemonic, range_start, range_end) \
case range_start ... range_end: \
return #mnemonic##sv;
ENUMERATE_OPENTYPE_OPCODES
#undef __ENUMERATE_OPENTYPE_OPCODES
}
VERIFY_NOT_REACHED();
}
static constexpr u8 flag_bit_count(Opcode opcode)
{
switch (to_underlying(opcode)) {
#define __ENUMERATE_OPENTYPE_OPCODES(mnemonic, range_start, range_end) \
case range_start ... range_end: \
return AK::ceil_log2(range_end - range_start);
ENUMERATE_OPENTYPE_OPCODES
#undef __ENUMERATE_OPENTYPE_OPCODES
}
VERIFY_NOT_REACHED();
}
Instruction::Instruction(Opcode opcode, ReadonlyBytes values)
: m_opcode(opcode)
, m_values(values)
, m_flag_bits(flag_bit_count(opcode))
{
}
bool Instruction::a() const
{
return (to_underlying(m_opcode) >> (m_flag_bits - 1)) & 1;
}
bool Instruction::b() const
{
return (to_underlying(m_opcode) >> (m_flag_bits - 2)) & 1;
}
bool Instruction::c() const
{
return (to_underlying(m_opcode) >> (m_flag_bits - 3)) & 1;
}
bool Instruction::d() const
{
return (to_underlying(m_opcode) >> (m_flag_bits - 4)) & 1;
}
bool Instruction::e() const
{
return (to_underlying(m_opcode) >> (m_flag_bits - 5)) & 1;
}
void InstructionStream::process_next_instruction()
{
auto opcode = static_cast<Opcode>(next_byte());
auto& stream = *this;
m_handler.before_operation(stream, opcode);
ScopeGuard after = [&, this]() mutable {
m_handler.after_operation(stream, opcode);
};
// The PUSH instructions are handled specially as they take their values from the instruction stream.
switch (opcode) {
case Opcode::NPUSHB: {
auto n = next_byte();
auto values = take_n_bytes(n);
return m_handler.handle_NPUSHB({ { opcode, values }, stream });
}
case Opcode::NPUSHW: {
auto n = next_byte();
auto values = take_n_bytes(n * 2);
return m_handler.handle_NPUSHW({ { opcode, values }, stream });
}
case Opcode::PUSHB... Opcode::PUSHB_MAX: {
auto n = (to_underlying(opcode) & 0b111) + 1;
auto values = take_n_bytes(n);
return m_handler.handle_PUSHB({ { opcode, values }, stream });
}
case Opcode::PUSHW... Opcode::PUSHW_MAX: {
auto n = (to_underlying(opcode) & 0b111) + 1;
auto values = take_n_bytes(n * 2);
return m_handler.handle_PUSHB({ { opcode, values }, stream });
}
default:
break;
}
switch (to_underlying(opcode)) {
#define __ENUMERATE_OPENTYPE_OPCODES(mnemonic, range_start, range_end) \
case range_start ... range_end: \
return m_handler.handle_##mnemonic({ { opcode }, stream });
ENUMERATE_OPENTYPE_OPCODES
#undef __ENUMERATE_OPENTYPE_OPCODES
}
VERIFY_NOT_REACHED();
}
u8 InstructionStream::next_byte()
{
VERIFY(!at_end());
return m_bytes[m_byte_index++];
}
ReadonlyBytes InstructionStream::take_n_bytes(size_t n)
{
VERIFY(m_byte_index + n < m_bytes.size());
auto bytes = m_bytes.slice(m_byte_index, n);
m_byte_index += n;
return bytes;
}
bool InstructionStream::at_end() const
{
return m_byte_index >= m_bytes.size();
}
void InstructionStream::jump_to_next(Opcode)
{
TODO();
}
}

View file

@ -1,240 +0,0 @@
/*
* Copyright (c) 2023, MacDue <macdue@dueutil.tech>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Span.h>
#include <AK/StringView.h>
namespace OpenType::Hinting {
#define ENUMERATE_OPENTYPE_OPCODES \
/* Pushing data onto the interpreter stack: */ \
__ENUMERATE_OPENTYPE_OPCODES(NPUSHB, 0x40, 0x40) \
__ENUMERATE_OPENTYPE_OPCODES(NPUSHW, 0x41, 0x41) \
__ENUMERATE_OPENTYPE_OPCODES(PUSHB, 0xB0, 0xB7) \
__ENUMERATE_OPENTYPE_OPCODES(PUSHW, 0xB8, 0xBF) \
/* Managing the Storage Area */ \
__ENUMERATE_OPENTYPE_OPCODES(RS, 0x43, 0x43) \
__ENUMERATE_OPENTYPE_OPCODES(WS, 0x42, 0x42) \
/* Managing the Control Value Table */ \
__ENUMERATE_OPENTYPE_OPCODES(WCVTP, 0x44, 0x44) \
__ENUMERATE_OPENTYPE_OPCODES(WCVTF, 0x70, 0x70) \
__ENUMERATE_OPENTYPE_OPCODES(RCVT, 0x45, 0x45) \
/* Managing the Graphics State */ \
__ENUMERATE_OPENTYPE_OPCODES(SVTCA, 0x00, 0x01) \
__ENUMERATE_OPENTYPE_OPCODES(SPVTCA, 0x02, 0x03) \
__ENUMERATE_OPENTYPE_OPCODES(SFVTCA, 0x04, 0x05) \
__ENUMERATE_OPENTYPE_OPCODES(SPVTL, 0x06, 0x07) \
__ENUMERATE_OPENTYPE_OPCODES(SFVTL, 0x08, 0x09) \
__ENUMERATE_OPENTYPE_OPCODES(SFVTPV, 0x0E, 0x0E) \
__ENUMERATE_OPENTYPE_OPCODES(SDPVTL, 0x86, 0x87) \
__ENUMERATE_OPENTYPE_OPCODES(SPVFS, 0x0A, 0x0A) \
__ENUMERATE_OPENTYPE_OPCODES(SFVFS, 0x0B, 0x0B) \
__ENUMERATE_OPENTYPE_OPCODES(GPV, 0x0C, 0x0C) \
__ENUMERATE_OPENTYPE_OPCODES(GFV, 0x0D, 0x0D) \
__ENUMERATE_OPENTYPE_OPCODES(SRP0, 0x10, 0x10) \
__ENUMERATE_OPENTYPE_OPCODES(SRP1, 0x11, 0x11) \
__ENUMERATE_OPENTYPE_OPCODES(SRP2, 0x12, 0x12) \
__ENUMERATE_OPENTYPE_OPCODES(SZP0, 0x13, 0x13) \
__ENUMERATE_OPENTYPE_OPCODES(SZP1, 0x14, 0x14) \
__ENUMERATE_OPENTYPE_OPCODES(SZP2, 0x15, 0x15) \
__ENUMERATE_OPENTYPE_OPCODES(SZPS, 0x16, 0x16) \
__ENUMERATE_OPENTYPE_OPCODES(RTHG, 0x19, 0x19) \
__ENUMERATE_OPENTYPE_OPCODES(RTG, 0x18, 0x18) \
__ENUMERATE_OPENTYPE_OPCODES(RTDG, 0x3D, 0x3D) \
__ENUMERATE_OPENTYPE_OPCODES(RDTG, 0x7D, 0x7D) \
__ENUMERATE_OPENTYPE_OPCODES(RUTG, 0x7C, 0x7C) \
__ENUMERATE_OPENTYPE_OPCODES(ROFF, 0x7A, 0x7A) \
__ENUMERATE_OPENTYPE_OPCODES(SROUND, 0x76, 0x76) \
__ENUMERATE_OPENTYPE_OPCODES(S45ROUND, 0x77, 0x77) \
__ENUMERATE_OPENTYPE_OPCODES(SLOOP, 0x17, 0x17) \
__ENUMERATE_OPENTYPE_OPCODES(SMD, 0x1A, 0x1A) \
__ENUMERATE_OPENTYPE_OPCODES(INSTCTRL, 0x8E, 0x8E) \
__ENUMERATE_OPENTYPE_OPCODES(SCANCTRL, 0x85, 0x85) \
__ENUMERATE_OPENTYPE_OPCODES(SCANTYPE, 0x8D, 0x8D) \
__ENUMERATE_OPENTYPE_OPCODES(SCVTCI, 0x1D, 0x1D) \
__ENUMERATE_OPENTYPE_OPCODES(SSWCI, 0x1E, 0x1E) \
__ENUMERATE_OPENTYPE_OPCODES(SSW, 0x1F, 0x1F) \
__ENUMERATE_OPENTYPE_OPCODES(FLIPON, 0x4D, 0x4D) \
__ENUMERATE_OPENTYPE_OPCODES(FLIPOFF, 0x4E, 0x4E) \
__ENUMERATE_OPENTYPE_OPCODES(SANGW, 0x7E, 0x7E) \
__ENUMERATE_OPENTYPE_OPCODES(SDB, 0x5E, 0x5E) \
__ENUMERATE_OPENTYPE_OPCODES(SDS, 0x5F, 0x5F) \
/* Reading and writing data */ \
__ENUMERATE_OPENTYPE_OPCODES(GC, 0x46, 0x47) \
__ENUMERATE_OPENTYPE_OPCODES(SCFS, 0x48, 0x48) \
__ENUMERATE_OPENTYPE_OPCODES(MD, 0x49, 0x4A) \
__ENUMERATE_OPENTYPE_OPCODES(MPPEM, 0x4B, 0x4B) \
__ENUMERATE_OPENTYPE_OPCODES(MPS, 0x4C, 0x4C) \
/* Managing outlines */ \
__ENUMERATE_OPENTYPE_OPCODES(FLIPPT, 0x80, 0x80) \
__ENUMERATE_OPENTYPE_OPCODES(FLIPRGON, 0x81, 0x81) \
__ENUMERATE_OPENTYPE_OPCODES(FLIPRGOFF, 0x82, 0x82) \
__ENUMERATE_OPENTYPE_OPCODES(SHP, 0x32, 0x33) \
__ENUMERATE_OPENTYPE_OPCODES(SHC, 0x34, 0x35) \
__ENUMERATE_OPENTYPE_OPCODES(SHZ, 0x36, 0x37) \
__ENUMERATE_OPENTYPE_OPCODES(SHPIX, 0x38, 0x38) \
__ENUMERATE_OPENTYPE_OPCODES(MSIRP, 0x3A, 0x3B) \
__ENUMERATE_OPENTYPE_OPCODES(MDAP, 0x2E, 0x2F) \
__ENUMERATE_OPENTYPE_OPCODES(MIAP, 0x3E, 0x3F) \
__ENUMERATE_OPENTYPE_OPCODES(MDRP, 0xC0, 0xDF) \
__ENUMERATE_OPENTYPE_OPCODES(MIRP, 0xE0, 0xFF) \
__ENUMERATE_OPENTYPE_OPCODES(ALIGNRP, 0x3C, 0x3C) \
__ENUMERATE_OPENTYPE_OPCODES(ISECT, 0x0F, 0x0F) \
__ENUMERATE_OPENTYPE_OPCODES(ALIGNPTS, 0x27, 0x27) \
__ENUMERATE_OPENTYPE_OPCODES(IP, 0x39, 0x39) \
__ENUMERATE_OPENTYPE_OPCODES(UTP, 0x29, 0x29) \
__ENUMERATE_OPENTYPE_OPCODES(IUP, 0x30, 0x31) \
/* Managing exceptions */ \
__ENUMERATE_OPENTYPE_OPCODES(DELTAP1, 0x5D, 0x5D) \
__ENUMERATE_OPENTYPE_OPCODES(DELTAP2, 0x71, 0x71) \
__ENUMERATE_OPENTYPE_OPCODES(DELTAP3, 0x72, 0x72) \
__ENUMERATE_OPENTYPE_OPCODES(DELTAC1, 0x73, 0x73) \
__ENUMERATE_OPENTYPE_OPCODES(DELTAC2, 0x74, 0x74) \
__ENUMERATE_OPENTYPE_OPCODES(DELTAC3, 0x75, 0x75) \
/* Managing the stack */ \
__ENUMERATE_OPENTYPE_OPCODES(DUP, 0x20, 0x20) \
__ENUMERATE_OPENTYPE_OPCODES(POP, 0x21, 0x21) \
__ENUMERATE_OPENTYPE_OPCODES(CLEAR, 0x22, 0x22) \
__ENUMERATE_OPENTYPE_OPCODES(SWAP, 0x23, 0x23) \
__ENUMERATE_OPENTYPE_OPCODES(DEPTH, 0x24, 0x24) \
__ENUMERATE_OPENTYPE_OPCODES(CINDEX, 0x25, 0x25) \
__ENUMERATE_OPENTYPE_OPCODES(MINDEX, 0x26, 0x26) \
__ENUMERATE_OPENTYPE_OPCODES(ROLL, 0x8a, 0x8a) \
/* Managing the flow of control */ \
__ENUMERATE_OPENTYPE_OPCODES(IF, 0x58, 0x58) \
__ENUMERATE_OPENTYPE_OPCODES(ELSE, 0x1B, 0x1B) \
__ENUMERATE_OPENTYPE_OPCODES(EIF, 0x59, 0x59) \
__ENUMERATE_OPENTYPE_OPCODES(JROT, 0x78, 0x78) \
__ENUMERATE_OPENTYPE_OPCODES(JMPR, 0x1C, 0x1C) \
__ENUMERATE_OPENTYPE_OPCODES(JROF, 0x79, 0x79) \
/* Logical functions */ \
__ENUMERATE_OPENTYPE_OPCODES(LT, 0x50, 0x50) \
__ENUMERATE_OPENTYPE_OPCODES(LTEQ, 0x51, 0x51) \
__ENUMERATE_OPENTYPE_OPCODES(GT, 0x52, 0x52) \
__ENUMERATE_OPENTYPE_OPCODES(GTEQ, 0x53, 0x53) \
__ENUMERATE_OPENTYPE_OPCODES(EQ, 0x54, 0x54) \
__ENUMERATE_OPENTYPE_OPCODES(NEQ, 0x55, 0x55) \
__ENUMERATE_OPENTYPE_OPCODES(ODD, 0x56, 0x56) \
__ENUMERATE_OPENTYPE_OPCODES(EVEN, 0x57, 0x57) \
__ENUMERATE_OPENTYPE_OPCODES(AND, 0x5A, 0x5A) \
__ENUMERATE_OPENTYPE_OPCODES(OR, 0x5B, 0x5B) \
__ENUMERATE_OPENTYPE_OPCODES(NOT, 0x5C, 0x5C) \
/* Arithmetic and math instructions */ \
__ENUMERATE_OPENTYPE_OPCODES(ADD, 0x60, 0x60) \
__ENUMERATE_OPENTYPE_OPCODES(SUB, 0x61, 0x61) \
__ENUMERATE_OPENTYPE_OPCODES(DIV, 0x62, 0x62) \
__ENUMERATE_OPENTYPE_OPCODES(MUL, 0x63, 0x63) \
__ENUMERATE_OPENTYPE_OPCODES(ABS, 0x64, 0x64) \
__ENUMERATE_OPENTYPE_OPCODES(NEG, 0x65, 0x65) \
__ENUMERATE_OPENTYPE_OPCODES(FLOOR, 0x66, 0x66) \
__ENUMERATE_OPENTYPE_OPCODES(CEILING, 0x67, 0x67) \
__ENUMERATE_OPENTYPE_OPCODES(MAX, 0x8B, 0x8B) \
__ENUMERATE_OPENTYPE_OPCODES(MIN, 0x8C, 0x8C) \
/* Compensating for the engine characteristics */ \
__ENUMERATE_OPENTYPE_OPCODES(ROUND, 0x68, 0x6B) \
__ENUMERATE_OPENTYPE_OPCODES(NROUND, 0x6C, 0x6F) \
/* Defining and using functions and instructions */ \
__ENUMERATE_OPENTYPE_OPCODES(FDEF, 0x2C, 0x2C) \
__ENUMERATE_OPENTYPE_OPCODES(ENDF, 0x2D, 0x2D) \
__ENUMERATE_OPENTYPE_OPCODES(CALL, 0x2B, 0x2B) \
__ENUMERATE_OPENTYPE_OPCODES(LOOPCALL, 0x2A, 0x2A) \
__ENUMERATE_OPENTYPE_OPCODES(IDEF, 0x89, 0x89) \
/* Debugging */ \
__ENUMERATE_OPENTYPE_OPCODES(DEBUG, 0x4F, 0x4F) \
/* Miscellaneous instructions */ \
__ENUMERATE_OPENTYPE_OPCODES(GETINFO, 0x88, 0x88) \
__ENUMERATE_OPENTYPE_OPCODES(GETVARIATION, 0x91, 0x91)
// https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions
enum class Opcode : u8 {
#define __ENUMERATE_OPENTYPE_OPCODES(mnemonic, range_start, range_end) \
mnemonic = range_start, \
mnemonic##_MAX = range_end,
ENUMERATE_OPENTYPE_OPCODES
#undef __ENUMERATE_OPENTYPE_OPCODES
};
struct Instruction {
bool a() const;
bool b() const;
bool c() const;
bool d() const;
bool e() const;
u8 flag_bits() const { return m_flag_bits; }
Opcode opcode() const { return m_opcode; }
ReadonlyBytes values() const { return m_values; }
Instruction(Opcode opcode, ReadonlyBytes values = {});
private:
Opcode m_opcode;
ReadonlyBytes m_values;
u8 m_flag_bits;
};
StringView opcode_mnemonic(Opcode);
struct InstructionHandler;
struct InstructionStream {
InstructionStream(InstructionHandler& handler, ReadonlyBytes bytes)
: m_handler { handler }
, m_bytes(bytes)
{
}
bool at_end() const;
void process_next_instruction();
void jump_to_next(Opcode);
size_t current_position() const { return m_byte_index; }
size_t length() const { return m_bytes.size(); }
struct Context {
Context(Instruction instruction, InstructionStream& stream)
: m_instruction(instruction)
, m_stream(stream)
{
}
Instruction instruction() { return m_instruction; }
InstructionStream& stream() { return m_stream; }
private:
Instruction m_instruction;
InstructionStream& m_stream;
};
private:
u8 next_byte();
ReadonlyBytes take_n_bytes(size_t n);
InstructionHandler& m_handler;
ReadonlyBytes m_bytes;
size_t m_byte_index { 0 };
};
struct InstructionHandler {
using Context = InstructionStream::Context;
virtual void default_handler(Context) = 0;
virtual void before_operation(InstructionStream&, Opcode) { }
virtual void after_operation(InstructionStream&, Opcode) { }
#define __ENUMERATE_OPENTYPE_OPCODES(mnemonic, _, __) \
virtual void handle_##mnemonic(Context context) \
{ \
default_handler(context); \
}
ENUMERATE_OPENTYPE_OPCODES
#undef __ENUMERATE_OPENTYPE_OPCODES
virtual ~InstructionHandler() = default;
};
}

View file

@ -1,701 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
* Copyright (c) 2023, Lukas Affolter <git@lukasach.dev>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/BinarySearch.h>
#include <AK/BuiltinWrappers.h>
#include <AK/Debug.h>
#include <AK/MemoryStream.h>
#include <LibGfx/Font/OpenType/Tables.h>
#include <LibTextCodec/Decoder.h>
namespace OpenType {
ErrorOr<Head> Head::from_slice(ReadonlyBytes slice)
{
if (slice.size() < sizeof(FontHeaderTable))
return Error::from_string_literal("Could not load Head: Not enough data");
auto const& font_header_table = *bit_cast<FontHeaderTable const*>(slice.data());
static constexpr u32 HEADER_TABLE_MAGIC_NUMBER = 0x5F0F3CF5;
if (font_header_table.major_version != 1)
return Error::from_string_literal("Unknown major version. Expected 1");
if (font_header_table.minor_version != 0)
return Error::from_string_literal("Unknown minor version. Expected 0");
if (font_header_table.magic_number != HEADER_TABLE_MAGIC_NUMBER)
return Error::from_string_literal("Invalid magic number");
if (font_header_table.index_to_loc_format != 0 && font_header_table.index_to_loc_format != 1)
return Error::from_string_literal("Invalid IndexToLocFormat value");
return Head(font_header_table);
}
u16 Head::units_per_em() const
{
return m_data.units_per_em;
}
i16 Head::xmin() const
{
return m_data.x_min;
}
i16 Head::ymin() const
{
return m_data.y_min;
}
i16 Head::xmax() const
{
return m_data.x_max;
}
i16 Head::ymax() const
{
return m_data.y_max;
}
u16 Head::style() const
{
return m_data.mac_style;
}
u16 Head::lowest_recommended_ppem() const
{
return m_data.lowest_rec_ppem;
}
IndexToLocFormat Head::index_to_loc_format() const
{
switch (m_data.index_to_loc_format) {
case 0:
return IndexToLocFormat::Offset16;
case 1:
return IndexToLocFormat::Offset32;
default:
VERIFY_NOT_REACHED();
}
}
ErrorOr<Hhea> Hhea::from_slice(ReadonlyBytes slice)
{
if (slice.size() < sizeof(HorizontalHeaderTable))
return Error::from_string_literal("Could not load Hhea: Not enough data");
auto const& horizontal_header_table = *bit_cast<HorizontalHeaderTable const*>(slice.data());
return Hhea(horizontal_header_table);
}
i16 Hhea::ascender() const
{
return m_data.ascender;
}
i16 Hhea::descender() const
{
return m_data.descender;
}
i16 Hhea::line_gap() const
{
return m_data.line_gap;
}
u16 Hhea::advance_width_max() const
{
return m_data.advance_width_max;
}
u16 Hhea::number_of_h_metrics() const
{
return m_data.number_of_h_metrics;
}
ErrorOr<Maxp> Maxp::from_slice(ReadonlyBytes slice)
{
// All Maximum Profile tables begin with a version.
if (slice.size() < sizeof(Version16Dot16))
return Error::from_string_literal("Could not load Maxp: Not enough data");
Version16Dot16 const& version = *bit_cast<Version16Dot16 const*>(slice.data());
if (version.major == 0 && version.minor == 5) {
if (slice.size() < sizeof(Version0_5))
return Error::from_string_literal("Could not load Maxp: Not enough data");
return Maxp(bit_cast<Version0_5 const*>(slice.data()));
}
if (version.major == 1 && version.minor == 0) {
if (slice.size() < sizeof(Version1_0))
return Error::from_string_literal("Could not load Maxp: Not enough data");
return Maxp(bit_cast<Version1_0 const*>(slice.data()));
}
return Error::from_string_literal("Could not load Maxp: Unrecognized version");
}
u16 Maxp::num_glyphs() const
{
return m_data.visit([](auto const* any) { return any->num_glyphs; });
}
ErrorOr<Hmtx> Hmtx::from_slice(ReadonlyBytes slice, u32 num_glyphs, u32 number_of_h_metrics)
{
if (slice.size() < number_of_h_metrics * sizeof(LongHorMetric) + (num_glyphs - number_of_h_metrics) * sizeof(Int16))
return Error::from_string_literal("Could not load Hmtx: Not enough data");
// The Horizontal Metrics table is LongHorMetric[number_of_h_metrics] followed by Int16[num_glyphs - number_of_h_metrics];
ReadonlySpan<LongHorMetric> long_hor_metrics { bit_cast<LongHorMetric*>(slice.data()), number_of_h_metrics };
ReadonlySpan<Int16> left_side_bearings {};
auto number_of_left_side_bearings = num_glyphs - number_of_h_metrics;
if (number_of_left_side_bearings > 0) {
left_side_bearings = {
bit_cast<Int16*>(slice.offset(number_of_h_metrics * sizeof(LongHorMetric))),
number_of_left_side_bearings
};
}
return Hmtx(long_hor_metrics, left_side_bearings);
}
GlyphHorizontalMetrics Hmtx::get_glyph_horizontal_metrics(u32 glyph_id) const
{
VERIFY(glyph_id < m_long_hor_metrics.size() + m_left_side_bearings.size());
if (glyph_id < m_long_hor_metrics.size()) {
return GlyphHorizontalMetrics {
.advance_width = m_long_hor_metrics[glyph_id].advance_width,
.left_side_bearing = m_long_hor_metrics[glyph_id].lsb,
};
}
return GlyphHorizontalMetrics {
.advance_width = m_long_hor_metrics.last().advance_width,
.left_side_bearing = m_left_side_bearings[glyph_id - m_long_hor_metrics.size()],
};
}
ErrorOr<Name> Name::from_slice(ReadonlyBytes slice)
{
// FIXME: Support version 1 table too.
if (slice.size() < sizeof(NamingTableVersion0))
return Error::from_string_literal("Could not load Name: Not enough data");
auto& naming_table = *bit_cast<NamingTableVersion0 const*>(slice.data());
auto name_record_data_size = naming_table.count * sizeof(NameRecord);
if (slice.size() < sizeof(NamingTableVersion0) + name_record_data_size)
return Error::from_string_literal("Could not load Name: Not enough data");
ReadonlySpan<NameRecord> name_records = { bit_cast<NameRecord const*>(slice.offset_pointer(sizeof(NamingTableVersion0))), naming_table.count };
if (slice.size() < naming_table.storage_offset)
return Error::from_string_literal("Could not load Name: Not enough data");
ReadonlyBytes string_data = slice.slice(naming_table.storage_offset);
return Name(naming_table, name_records, string_data);
}
String Name::string_for_id(NameId id) const
{
auto const count = m_naming_table.count;
Vector<int> valid_ids;
for (size_t i = 0; i < count; ++i) {
auto this_id = m_name_records[i].name_id;
if (this_id == to_underlying(id))
valid_ids.append(i);
}
if (valid_ids.is_empty())
return String {};
auto it = valid_ids.find_if([this](auto const& i) {
// check if font has naming table for en-US language id
auto const& name_record = m_name_records[i];
auto const platform_id = name_record.platform_id;
auto const language_id = name_record.language_id;
return (platform_id == to_underlying(Platform::Macintosh) && language_id == to_underlying(MacintoshLanguage::English))
|| (platform_id == to_underlying(Platform::Windows) && language_id == to_underlying(WindowsLanguage::EnglishUnitedStates));
});
auto i = it != valid_ids.end() ? *it : valid_ids.first();
auto const& name_record = m_name_records[i];
auto const platform_id = name_record.platform_id;
auto const length = name_record.length;
auto const offset = name_record.string_offset;
auto const name_bytes = m_string_data.slice(offset, length);
if (platform_id == to_underlying(Platform::Windows)) {
static auto& decoder = *TextCodec::decoder_for("utf-16be"sv);
return decoder.to_utf8(name_bytes).release_value_but_fixme_should_propagate_errors();
}
auto maybe_name = String::from_utf8(name_bytes);
if (maybe_name.is_error()) {
static auto& decoder = *TextCodec::decoder_for("utf-16be"sv);
maybe_name = decoder.to_utf8(name_bytes);
if (!maybe_name.is_error())
return maybe_name.release_value_but_fixme_should_propagate_errors();
dbgln("OpenType::Name: Failed to decode name string as UTF-8 or UTF-16BE");
return String {};
}
return maybe_name.release_value();
}
ErrorOr<Kern> Kern::from_slice(ReadonlyBytes slice)
{
FixedMemoryStream stream { slice };
// We only support the old (2x u16) version of the header
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 subtables
Vector<Subtable> subtables;
TRY(subtables.try_ensure_capacity(number_of_subtables));
for (size_t i = 0; i < number_of_subtables; ++i) {
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>(format0_header.n_pairs));
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(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 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);
// FIXME: implement support for these features
if (!is_horizontal || is_minimum || is_cross_stream || (reserved_bits > 0)) {
dbgln("OpenType::Kern: FIXME: implement missing feature support for subtable");
continue;
}
// FIXME: implement support for subtable formats other than 0
Optional<i16> subtable_kerning;
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();
if (is_override)
glyph_kerning = kerning_value;
else
glyph_kerning += kerning_value;
}
return glyph_kerning;
}
Optional<i16> Kern::read_glyph_kerning_format0(Format0Table const& format0, u16 left_glyph_id, u16 right_glyph_id)
{
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);
if (number_of_pairs == 0)
return {};
if (pairs_in_search_range > number_of_pairs)
return {};
if ((1 << entry_selector) * sizeof(Format0Pair) != search_range)
return {};
if ((number_of_pairs - pairs_in_search_range) * sizeof(Format0Pair) != range_shift)
return {};
// FIXME: implement a possibly slightly more efficient binary search using the parameters above
// 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(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;
});
if (!pair)
return 0;
return pair->value;
}
ErrorOr<OS2> OS2::from_slice(ReadonlyBytes slice)
{
// All OS2 tables begin with a version.
if (slice.size() < sizeof(BigEndian<u16>))
return Error::from_string_literal("Could not load OS2: Not enough data");
u16 version = *bit_cast<BigEndian<u16> const*>(slice.data());
// NOTE: We assume that this table only ever has new fields added to the end in future versions.
switch (version) {
case 0: {
if (slice.size() < sizeof(Version0))
return Error::from_string_literal("Could not load OS2 v0: Not enough data");
return OS2(bit_cast<Version0 const*>(slice.data()));
}
case 1: {
if (slice.size() < sizeof(Version1))
return Error::from_string_literal("Could not load OS2 v1: Not enough data");
return OS2(bit_cast<Version1 const*>(slice.data()));
}
case 2:
default: {
if (slice.size() < sizeof(Version2))
return Error::from_string_literal("Could not load OS2 v2: Not enough data");
return OS2(bit_cast<Version2 const*>(slice.data()));
}
}
}
u16 OS2::weight_class() const
{
return m_data.visit([](auto* any) { return any->us_weight_class; });
}
u16 OS2::width_class() const
{
return m_data.visit([](auto* any) { return any->us_width_class; });
}
u16 OS2::selection() const
{
return m_data.visit([](auto* any) { return any->fs_selection; });
}
i16 OS2::typographic_ascender() const
{
return m_data.visit([](auto* any) { return any->s_typo_ascender; });
}
i16 OS2::typographic_descender() const
{
return m_data.visit([](auto* any) { return any->s_typo_descender; });
}
i16 OS2::typographic_line_gap() const
{
return m_data.visit([](auto* any) { return any->s_typo_line_gap; });
}
bool OS2::use_typographic_metrics() const
{
return m_data.visit([](auto* any) { return any->fs_selection & 0x80; });
}
Optional<i16> OS2::x_height() const
{
return m_data.visit(
[]<typename T>
requires(requires { T::sx_height; })(T * data) -> Optional<i16> {
return data->sx_height;
},
[](auto*) { return Optional<i16>(); });
}
ErrorOr<GPOS> GPOS::from_slice(ReadonlyBytes slice)
{
FixedMemoryStream stream { slice };
auto const& header = *TRY(stream.read_in_place<Version1_0 const>());
// FIXME: Detect version 1.1 and support the extra FeatureVariations table.
TRY(stream.seek(header.script_list_offset, SeekMode::SetPosition));
auto const& script_list = *TRY(stream.read_in_place<ScriptList const>());
auto script_records = TRY(stream.read_in_place<ScriptRecord const>(script_list.script_count));
TRY(stream.seek(header.feature_list_offset, SeekMode::SetPosition));
auto const& feature_list = *TRY(stream.read_in_place<FeatureList const>());
auto feature_records = TRY(stream.read_in_place<FeatureRecord const>(feature_list.feature_count));
TRY(stream.seek(header.lookup_list_offset, SeekMode::SetPosition));
auto const& lookup_list = *TRY(stream.read_in_place<LookupList const>());
auto lookup_records = TRY(stream.read_in_place<Offset16 const>(lookup_list.lookup_count));
return GPOS { slice, header, script_list, script_records, feature_list, feature_records, lookup_list, lookup_records };
}
Optional<i16> GPOS::glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const
{
auto read_value_record = [&](u16 value_format, FixedMemoryStream& stream) -> ValueRecord {
ValueRecord value_record;
if (value_format & to_underlying(ValueFormat::X_PLACEMENT))
value_record.x_placement = stream.read_value<BigEndian<i16>>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::Y_PLACEMENT))
value_record.y_placement = stream.read_value<BigEndian<i16>>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::X_ADVANCE))
value_record.x_advance = stream.read_value<BigEndian<i16>>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::Y_ADVANCE))
value_record.y_advance = stream.read_value<BigEndian<i16>>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::X_PLACEMENT_DEVICE))
value_record.x_placement_device_offset = stream.read_value<Offset16>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::Y_PLACEMENT_DEVICE))
value_record.y_placement_device_offset = stream.read_value<Offset16>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::X_ADVANCE_DEVICE))
value_record.x_advance_device_offset = stream.read_value<Offset16>().release_value_but_fixme_should_propagate_errors();
if (value_format & to_underlying(ValueFormat::Y_ADVANCE_DEVICE))
value_record.y_advance_device_offset = stream.read_value<Offset16>().release_value_but_fixme_should_propagate_errors();
return value_record;
};
dbgln_if(OPENTYPE_GPOS_DEBUG, "GPOS header:");
dbgln_if(OPENTYPE_GPOS_DEBUG, " Version: {}.{}", m_header.major_version, m_header.minor_version);
dbgln_if(OPENTYPE_GPOS_DEBUG, " Feature list offset: {}", m_header.feature_list_offset);
// FIXME: Make sure everything is bounds-checked appropriately.
auto feature_list_slice = m_slice.slice(m_header.feature_list_offset);
auto lookup_list_slice = m_slice.slice(m_header.lookup_list_offset);
Optional<Offset16> kern_feature_offset;
for (auto const& feature_record : m_feature_records) {
if (feature_record.feature_tag == Tag("kern")) {
kern_feature_offset = feature_record.feature_offset;
break;
}
}
if (!kern_feature_offset.has_value()) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "No 'kern' feature found in GPOS table");
return {};
}
auto feature_slice = feature_list_slice.slice(kern_feature_offset.value());
auto const& feature = *bit_cast<Feature const*>(feature_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, "Feature:");
dbgln_if(OPENTYPE_GPOS_DEBUG, " featureParamsOffset: {}", feature.feature_params_offset);
dbgln_if(OPENTYPE_GPOS_DEBUG, " lookupIndexCount: {}", feature.lookup_index_count);
for (size_t i = 0; i < feature.lookup_index_count; ++i) {
auto lookup_index = feature.lookup_list_indices[i];
dbgln_if(OPENTYPE_GPOS_DEBUG, "Lookup index: {}", lookup_index);
auto lookup_slice = lookup_list_slice.slice(m_lookup_offsets[lookup_index]);
auto const& lookup = *bit_cast<Lookup const*>(lookup_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, "Lookup:");
dbgln_if(OPENTYPE_GPOS_DEBUG, " lookupType: {}", lookup.lookup_type);
dbgln_if(OPENTYPE_GPOS_DEBUG, " lookupFlag: {}", lookup.lookup_flag);
dbgln_if(OPENTYPE_GPOS_DEBUG, " subtableCount: {}", lookup.subtable_count);
// NOTE: We only support lookup type 2 (Pair adjustment) at the moment.
if (lookup.lookup_type != 2) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "FIXME: Implement GPOS lookup type {}", lookup.lookup_type);
continue;
}
for (size_t j = 0; j < lookup.subtable_count; ++j) {
auto pair_pos_format_offset = lookup.subtable_offsets[j];
auto pair_pos_format_slice = lookup_slice.slice(pair_pos_format_offset);
auto const& pair_pos_format = *bit_cast<BigEndian<u16> const*>(pair_pos_format_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, "PairPosFormat{}", pair_pos_format);
if (pair_pos_format == 1) {
auto const& pair_pos_format1 = *bit_cast<GPOS::PairPosFormat1 const*>(pair_pos_format_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, " posFormat: {}", pair_pos_format1.pos_format);
dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat1: {}", pair_pos_format1.value_format1);
dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat2: {}", pair_pos_format1.value_format2);
dbgln_if(OPENTYPE_GPOS_DEBUG, " pairSetCount: {}", pair_pos_format1.pair_set_count);
auto get_coverage_index = [&](u16 glyph_id, Offset16 coverage_format_offset) -> Optional<u16> {
auto coverage_format_slice = pair_pos_format_slice.slice(coverage_format_offset);
auto const& coverage_format = *bit_cast<BigEndian<u16> const*>(coverage_format_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, "Coverage table format: {}", coverage_format);
if (coverage_format == 1) {
auto const& coverage_format1 = *bit_cast<CoverageFormat1 const*>(coverage_format_slice.data());
for (size_t k = 0; k < coverage_format1.glyph_count; ++k)
if (coverage_format1.glyph_array[k] == glyph_id)
return k;
dbgln_if(OPENTYPE_GPOS_DEBUG, "Glyph ID {} not covered", glyph_id);
return {};
}
else if (coverage_format == 2) {
auto const& coverage_format2 = *bit_cast<CoverageFormat2 const*>(coverage_format_slice.data());
for (size_t k = 0; k < coverage_format2.range_count; ++k) {
auto range_record = coverage_format2.range_records[k];
if ((range_record.start_glyph_id <= glyph_id) && (glyph_id <= range_record.end_glyph_id))
return range_record.start_coverage_index + glyph_id - range_record.start_glyph_id;
}
dbgln_if(OPENTYPE_GPOS_DEBUG, "Glyph ID {} not covered", glyph_id);
return {};
}
dbgln_if(OPENTYPE_GPOS_DEBUG, "No valid coverage table for format {}", coverage_format);
return {};
};
auto coverage_index = get_coverage_index(left_glyph_id, pair_pos_format1.coverage_offset);
if (!coverage_index.has_value()) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "Glyph ID not covered by table");
continue;
}
size_t value1_size = popcount(static_cast<u32>(pair_pos_format1.value_format1 & 0xff)) * sizeof(u16);
size_t value2_size = popcount(static_cast<u32>(pair_pos_format1.value_format2 & 0xff)) * sizeof(u16);
dbgln_if(OPENTYPE_GPOS_DEBUG, "ValueSizes: {}, {}", value1_size, value2_size);
// Manually iterate over the PairSet table, as the size of each PairValueRecord is not known at compile time.
auto pair_set_offset = pair_pos_format1.pair_set_offsets[coverage_index.value()];
auto pair_set_slice = pair_pos_format_slice.slice(pair_set_offset);
FixedMemoryStream stream(pair_set_slice);
auto pair_value_count = stream.read_value<BigEndian<u16>>().release_value_but_fixme_should_propagate_errors();
bool found_matching_glyph = false;
for (size_t k = 0; k < pair_value_count; ++k) {
auto second_glyph = stream.read_value<BigEndian<u16>>().release_value_but_fixme_should_propagate_errors();
if (right_glyph_id == second_glyph) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "Found matching second glyph {}", second_glyph);
found_matching_glyph = true;
break;
}
(void)stream.discard(value1_size + value2_size).release_value_but_fixme_should_propagate_errors();
}
if (!found_matching_glyph) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "Did not find second glyph matching {}", right_glyph_id);
continue;
}
[[maybe_unused]] auto value_record1 = read_value_record(pair_pos_format1.value_format1, stream);
[[maybe_unused]] auto value_record2 = read_value_record(pair_pos_format1.value_format2, stream);
dbgln_if(OPENTYPE_GPOS_DEBUG, "Returning x advance {}", value_record1.x_advance);
return value_record1.x_advance;
}
else if (pair_pos_format == 2) {
auto const& pair_pos_format2 = *bit_cast<GPOS::PairPosFormat2 const*>(pair_pos_format_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, " posFormat: {}", pair_pos_format2.pos_format);
dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat1: {}", pair_pos_format2.value_format1);
dbgln_if(OPENTYPE_GPOS_DEBUG, " valueFormat2: {}", pair_pos_format2.value_format2);
dbgln_if(OPENTYPE_GPOS_DEBUG, " class1Count: {}", pair_pos_format2.class1_count);
dbgln_if(OPENTYPE_GPOS_DEBUG, " class2Count: {}", pair_pos_format2.class2_count);
auto get_class = [&](u16 glyph_id, Offset16 glyph_def_offset) -> Optional<u16> {
auto class_def_format_slice = pair_pos_format_slice.slice(glyph_def_offset);
auto const& class_def_format = *bit_cast<BigEndian<u16> const*>(class_def_format_slice.data());
if (class_def_format == 1) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "FIXME: Implement ClassDefFormat1");
return {};
}
auto const& class_def_format2 = *bit_cast<ClassDefFormat2 const*>(class_def_format_slice.data());
dbgln_if(OPENTYPE_GPOS_DEBUG, "ClassDefFormat2:");
dbgln_if(OPENTYPE_GPOS_DEBUG, " classFormat: {}", class_def_format2.class_format);
dbgln_if(OPENTYPE_GPOS_DEBUG, " classRangeCount: {}", class_def_format2.class_range_count);
for (size_t i = 0; i < class_def_format2.class_range_count; ++i) {
auto const& range = class_def_format2.class_range_records[i];
if (glyph_id >= range.start_glyph_id && glyph_id <= range.end_glyph_id) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "Found class {} for glyph ID {}", range.class_, glyph_id);
return range.class_;
}
}
dbgln_if(OPENTYPE_GPOS_DEBUG, "No class found for glyph {}", glyph_id);
return {};
};
auto left_class = get_class(left_glyph_id, pair_pos_format2.class_def1_offset);
auto right_class = get_class(right_glyph_id, pair_pos_format2.class_def2_offset);
if (!left_class.has_value() || !right_class.has_value()) {
dbgln_if(OPENTYPE_GPOS_DEBUG, "Need glyph class for both sides");
continue;
}
dbgln_if(OPENTYPE_GPOS_DEBUG, "Classes: {}, {}", left_class.value(), right_class.value());
size_t value1_size = popcount(static_cast<u32>(pair_pos_format2.value_format1 & 0xff)) * sizeof(u16);
size_t value2_size = popcount(static_cast<u32>(pair_pos_format2.value_format2 & 0xff)) * sizeof(u16);
dbgln_if(OPENTYPE_GPOS_DEBUG, "ValueSizes: {}, {}", value1_size, value2_size);
size_t class2_record_size = value1_size + value2_size;
dbgln_if(OPENTYPE_GPOS_DEBUG, "Class2RecordSize: {}", class2_record_size);
size_t class1_record_size = pair_pos_format2.class2_count * class2_record_size;
dbgln_if(OPENTYPE_GPOS_DEBUG, "Class1RecordSize: {}", class1_record_size);
size_t item_offset = (left_class.value() * class1_record_size) + (right_class.value() * class2_record_size);
dbgln_if(OPENTYPE_GPOS_DEBUG, "Item offset: {}", item_offset);
auto item_slice = pair_pos_format_slice.slice(sizeof(PairPosFormat2) + item_offset);
FixedMemoryStream stream(item_slice);
[[maybe_unused]] auto value_record1 = read_value_record(pair_pos_format2.value_format1, stream);
[[maybe_unused]] auto value_record2 = read_value_record(pair_pos_format2.value_format2, stream);
dbgln_if(OPENTYPE_GPOS_DEBUG, "Returning x advance {}", value_record1.x_advance);
return value_record1.x_advance;
}
}
}
(void)left_glyph_id;
(void)right_glyph_id;
return {};
}
}

View file

@ -1,660 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
* Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
* Copyright (c) 2023, Lukas Affolter <git@lukasach.dev>
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/ByteString.h>
#include <AK/Endian.h>
#include <AK/Error.h>
#include <AK/FixedArray.h>
#include <AK/Forward.h>
#include <AK/Span.h>
#include <AK/String.h>
#include <LibGfx/Font/OpenType/DataTypes.h>
namespace OpenType {
enum class IndexToLocFormat {
Offset16,
Offset32,
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
// Table Directory (known as the Offset Table in ISO-IEC 14496-22:2019)
struct [[gnu::packed]] TableDirectory {
Uint32 sfnt_version;
Uint16 num_tables; // Number of tables.
Uint16 search_range; // (Maximum power of 2 <= numTables) x 16.
Uint16 entry_selector; // Log2(maximum power of 2 <= numTables).
Uint16 range_shift; // NumTables x 16 - searchRange.
};
static_assert(AssertSize<TableDirectory, 12>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
struct [[gnu::packed]] TableRecord {
Tag table_tag; // Table identifier.
Uint32 checksum; // CheckSum for this table.
Offset32 offset; // Offset from beginning of TrueType font file.
Uint32 length; // Length of this table.
};
static_assert(AssertSize<TableRecord, 16>());
}
template<>
class AK::Traits<OpenType::TableDirectory> : public DefaultTraits<OpenType::TableDirectory> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
class AK::Traits<OpenType::TableRecord> : public DefaultTraits<OpenType::TableRecord> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
namespace OpenType {
// https://learn.microsoft.com/en-us/typography/opentype/spec/head
// head: Font Header Table
class Head {
public:
static ErrorOr<Head> from_slice(ReadonlyBytes);
u16 units_per_em() const;
i16 xmin() const;
i16 ymin() const;
i16 xmax() const;
i16 ymax() const;
u16 style() const;
u16 lowest_recommended_ppem() const;
IndexToLocFormat index_to_loc_format() const;
private:
struct [[gnu::packed]] FontHeaderTable {
Uint16 major_version;
Uint16 minor_version;
Fixed font_revision;
Uint32 checksum_adjustment;
Uint32 magic_number;
Uint16 flags;
Uint16 units_per_em;
LongDateTime created;
LongDateTime modified;
Int16 x_min;
Int16 y_min;
Int16 x_max;
Int16 y_max;
Uint16 mac_style;
Uint16 lowest_rec_ppem;
Int16 font_direction_hint;
Int16 index_to_loc_format;
Int16 glyph_data_format;
};
static_assert(AssertSize<FontHeaderTable, 54>());
Head(FontHeaderTable const& font_header_table)
: m_data(font_header_table)
{
}
FontHeaderTable const& m_data;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/hhea
// hhea - Horizontal Header Table
class Hhea {
public:
static ErrorOr<Hhea> from_slice(ReadonlyBytes);
i16 ascender() const;
i16 descender() const;
i16 line_gap() const;
u16 advance_width_max() const;
u16 number_of_h_metrics() const;
private:
struct [[gnu::packed]] HorizontalHeaderTable {
Uint16 major_version;
Uint16 minor_version;
FWord ascender;
FWord descender;
FWord line_gap;
UFWord advance_width_max;
FWord min_left_side_bearing;
FWord min_right_side_bearing;
FWord x_max_extent;
Int16 caret_slope_rise;
Int16 caret_slope_run;
Int16 caret_offset;
Int16 reserved[4];
Int16 metric_data_format;
Uint16 number_of_h_metrics;
};
static_assert(AssertSize<HorizontalHeaderTable, 36>());
Hhea(HorizontalHeaderTable const& horizontal_header_table)
: m_data(horizontal_header_table)
{
}
HorizontalHeaderTable const& m_data;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/maxp
// Maxp: Maximum Profile
class Maxp {
public:
static ErrorOr<Maxp> from_slice(ReadonlyBytes);
u16 num_glyphs() const;
private:
struct [[gnu::packed]] Version0_5 {
Version16Dot16 version;
Uint16 num_glyphs;
};
static_assert(AssertSize<Version0_5, 6>());
struct [[gnu::packed]] Version1_0 : Version0_5 {
Uint16 max_points;
Uint16 max_contours;
Uint16 max_composite_points;
Uint16 max_composite_contours;
Uint16 max_zones;
Uint16 max_twilight_points;
Uint16 max_storage;
Uint16 max_function_defs;
Uint16 max_instruction_defs;
Uint16 max_stack_elements;
Uint16 max_size_of_instructions;
Uint16 max_component_elements;
Uint16 max_component_depths;
};
static_assert(AssertSize<Version1_0, 32>());
Maxp(Variant<Version0_5 const*, Version1_0 const*> data)
: m_data(move(data))
{
VERIFY(m_data.visit([](auto const* any) { return any != nullptr; }));
}
// NOTE: Whichever pointer is present is non-null, but Variant can't contain references.
Variant<Version0_5 const*, Version1_0 const*> m_data;
};
struct GlyphHorizontalMetrics {
u16 advance_width;
i16 left_side_bearing;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/hmtx
// hmtx: Horizontal Metrics Table
class Hmtx {
public:
static ErrorOr<Hmtx> from_slice(ReadonlyBytes, u32 num_glyphs, u32 number_of_h_metrics);
GlyphHorizontalMetrics get_glyph_horizontal_metrics(u32 glyph_id) const;
private:
struct [[gnu::packed]] LongHorMetric {
Uint16 advance_width;
Int16 lsb;
};
static_assert(AssertSize<LongHorMetric, 4>());
Hmtx(ReadonlySpan<LongHorMetric> long_hor_metrics, ReadonlySpan<Int16> left_side_bearings)
: m_long_hor_metrics(long_hor_metrics)
, m_left_side_bearings(left_side_bearings)
{
}
ReadonlySpan<LongHorMetric> m_long_hor_metrics;
ReadonlySpan<Int16> m_left_side_bearings;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/os2
// OS/2: OS/2 and Windows Metrics Table
class OS2 {
public:
static ErrorOr<OS2> from_slice(ReadonlyBytes);
u16 weight_class() const;
u16 width_class() const;
u16 selection() const;
i16 typographic_ascender() const;
i16 typographic_descender() const;
i16 typographic_line_gap() const;
bool use_typographic_metrics() const;
[[nodiscard]] Optional<i16> x_height() const;
private:
struct [[gnu::packed]] Version0 {
Uint16 version;
Int16 avg_char_width;
Uint16 us_weight_class;
Uint16 us_width_class;
Uint16 fs_type;
Int16 y_subscript_x_size;
Int16 y_subscript_y_size;
Int16 y_subscript_x_offset;
Int16 y_subscript_y_offset;
Int16 y_superscript_x_size;
Int16 y_superscript_y_size;
Int16 y_superscript_x_offset;
Int16 y_superscript_y_offset;
Int16 y_strikeout_size;
Int16 y_strikeout_position;
Int16 s_family_class;
Uint8 panose[10];
Uint32 ul_unicode_range1;
Uint32 ul_unicode_range2;
Uint32 ul_unicode_range3;
Uint32 ul_unicode_range4;
Tag ach_vend_id;
Uint16 fs_selection;
Uint16 fs_first_char_index;
Uint16 us_last_char_index;
Int16 s_typo_ascender;
Int16 s_typo_descender;
Int16 s_typo_line_gap;
Uint16 us_win_ascent;
Uint16 us_win_descent;
};
static_assert(AssertSize<Version0, 78>());
struct [[gnu::packed]] Version1 : Version0 {
Uint32 ul_code_page_range1;
Uint32 ul_code_page_range2;
};
static_assert(AssertSize<Version1, 86>());
struct [[gnu::packed]] Version2 : Version1 {
Int16 sx_height;
Int16 s_cap_height;
Uint16 us_default_char;
Uint16 us_break_char;
Uint16 us_max_context;
};
static_assert(AssertSize<Version2, 96>());
explicit OS2(Variant<Version0 const*, Version1 const*, Version2 const*> data)
: m_data(move(data))
{
}
Variant<Version0 const*, Version1 const*, Version2 const*> m_data;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/name
// name: Naming Table
class Name {
public:
enum class Platform : u16 {
Unicode = 0,
Macintosh = 1,
Windows = 3,
};
enum class MacintoshLanguage : u16 {
English = 0,
};
enum class WindowsLanguage : u16 {
EnglishUnitedStates = 0x0409,
};
static ErrorOr<Name> from_slice(ReadonlyBytes);
String family_name() const { return string_for_id(NameId::FamilyName); }
String subfamily_name() const { return string_for_id(NameId::SubfamilyName); }
String typographic_family_name() const { return string_for_id(NameId::TypographicFamilyName); }
String typographic_subfamily_name() const { return string_for_id(NameId::TypographicSubfamilyName); }
private:
// https://learn.microsoft.com/en-us/typography/opentype/spec/name#name-records
struct [[gnu::packed]] NameRecord {
Uint16 platform_id;
Uint16 encoding_id;
Uint16 language_id;
Uint16 name_id;
Uint16 length;
Offset16 string_offset;
};
static_assert(AssertSize<NameRecord, 12>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/name#naming-table-version-0
struct [[gnu::packed]] NamingTableVersion0 {
Uint16 version;
Uint16 count;
Offset16 storage_offset;
// NameRecords are stored in a separate span.
// NameRecord name_record[0];
};
static_assert(AssertSize<NamingTableVersion0, 6>());
enum class NameId : u16 {
Copyright = 0,
FamilyName = 1,
SubfamilyName = 2,
UniqueIdentifier = 3,
FullName = 4,
VersionString = 5,
PostscriptName = 6,
Trademark = 7,
Manufacturer = 8,
Designer = 9,
Description = 10,
TypographicFamilyName = 16,
TypographicSubfamilyName = 17,
};
Name(NamingTableVersion0 const& naming_table, ReadonlySpan<NameRecord> name_records, ReadonlyBytes string_data)
: m_naming_table(naming_table)
, m_name_records(name_records)
, m_string_data(string_data)
{
}
[[nodiscard]] String string_for_id(NameId) const;
NamingTableVersion0 const& m_naming_table;
ReadonlySpan<NameRecord> m_name_records;
ReadonlyBytes m_string_data;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/kern
// kern - Kerning
class Kern {
public:
static ErrorOr<Kern> from_slice(ReadonlyBytes);
i16 get_glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const;
struct [[gnu::packed]] Header {
Uint16 version;
Uint16 n_tables;
};
static_assert(AssertSize<Header, 4>());
struct [[gnu::packed]] SubtableHeader {
Uint16 version;
Uint16 length;
Uint16 coverage;
};
static_assert(AssertSize<SubtableHeader, 6>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/kern#format-0
struct [[gnu::packed]] Format0 {
Uint16 n_pairs;
Uint16 search_range;
Uint16 entry_selector;
Uint16 range_shift;
};
static_assert(AssertSize<Format0, 8>());
struct [[gnu::packed]] Format0Pair {
Uint16 left;
Uint16 right;
FWord value;
};
static_assert(AssertSize<Format0Pair, 6>());
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(Header const& header, Vector<Subtable> subtables)
: m_header(header)
, m_subtables(move(subtables))
{
}
static Optional<i16> read_glyph_kerning_format0(Format0Table const& format0, u16 left_glyph_id, u16 right_glyph_id);
Header const& m_header;
Vector<Subtable> const m_subtables;
};
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#feature-list-table
struct [[gnu::packed]] FeatureRecord {
Tag feature_tag;
Offset16 feature_offset;
};
static_assert(AssertSize<FeatureRecord, 6>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#feature-list-table
struct [[gnu::packed]] FeatureList {
Uint16 feature_count;
FeatureRecord feature_records[];
};
static_assert(AssertSize<FeatureList, 2>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#feature-table
struct [[gnu::packed]] Feature {
Offset16 feature_params_offset;
Uint16 lookup_index_count;
Uint16 lookup_list_indices[];
};
static_assert(AssertSize<Feature, 4>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#lookup-table
struct [[gnu::packed]] Lookup {
Uint16 lookup_type;
Uint16 lookup_flag;
Uint16 subtable_count;
Offset16 subtable_offsets[];
};
static_assert(AssertSize<Lookup, 6>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#lookup-list-table
struct [[gnu::packed]] LookupList {
Uint16 lookup_count;
Offset16 lookup_offsets[];
};
static_assert(AssertSize<LookupList, 2>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-1
struct [[gnu::packed]] CoverageFormat1 {
Uint16 coverage_format;
Uint16 glyph_count;
Uint16 glyph_array[];
};
static_assert(AssertSize<CoverageFormat1, 4>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2
struct [[gnu::packed]] RangeRecord {
Uint16 start_glyph_id;
Uint16 end_glyph_id;
Uint16 start_coverage_index;
};
static_assert(AssertSize<RangeRecord, 6>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-format-2
struct [[gnu::packed]] CoverageFormat2 {
Uint16 coverage_format;
Uint16 range_count;
RangeRecord range_records[];
};
static_assert(AssertSize<CoverageFormat2, 4>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2
struct [[gnu::packed]] ClassRangeRecord {
Uint16 start_glyph_id;
Uint16 end_glyph_id;
Uint16 class_;
};
static_assert(AssertSize<ClassRangeRecord, 6>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table-format-2
struct [[gnu::packed]] ClassDefFormat2 {
Uint16 class_format;
Uint16 class_range_count;
ClassRangeRecord class_range_records[];
};
static_assert(AssertSize<ClassDefFormat2, 4>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#script-list-table-and-script-record
struct [[gnu::packed]] ScriptRecord {
Tag script_tag;
Offset16 script_offset;
};
static_assert(AssertSize<ScriptRecord, 6>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/chapter2#script-list-table-and-script-record
struct [[gnu::packed]] ScriptList {
Uint16 script_count;
ScriptRecord script_records[];
};
static_assert(AssertSize<ScriptList, 2>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#gpos-header
class GPOS {
public:
// https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#gpos-header
struct [[gnu::packed]] Version1_0 {
Uint16 major_version;
Uint16 minor_version;
Offset16 script_list_offset;
Offset16 feature_list_offset;
Offset16 lookup_list_offset;
};
static_assert(AssertSize<Version1_0, 10>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-1-adjustments-for-glyph-pairs
struct [[gnu::packed]] PairPosFormat1 {
Uint16 pos_format;
Offset16 coverage_offset;
Uint16 value_format1;
Uint16 value_format2;
Uint16 pair_set_count;
Offset16 pair_set_offsets[];
};
static_assert(AssertSize<PairPosFormat1, 10>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#value-record
struct [[gnu::packed]] ValueRecord {
Int16 x_placement;
Int16 y_placement;
Int16 x_advance;
Int16 y_advance;
Offset16 x_placement_device_offset;
Offset16 y_placement_device_offset;
Offset16 x_advance_device_offset;
Offset16 y_advance_device_offset;
};
static_assert(AssertSize<ValueRecord, 16>());
// https://learn.microsoft.com/en-us/typography/opentype/spec/gpos#pair-adjustment-positioning-format-2-class-pair-adjustment
struct [[gnu::packed]] PairPosFormat2 {
Uint16 pos_format;
Offset16 coverage_offset;
Uint16 value_format1;
Uint16 value_format2;
Offset16 class_def1_offset;
Offset16 class_def2_offset;
Uint16 class1_count;
Uint16 class2_count;
};
static_assert(AssertSize<PairPosFormat2, 16>());
enum class ValueFormat : u16 {
X_PLACEMENT = 0x0001,
Y_PLACEMENT = 0x0002,
X_ADVANCE = 0x0004,
Y_ADVANCE = 0x0008,
X_PLACEMENT_DEVICE = 0x0010,
Y_PLACEMENT_DEVICE = 0x0020,
X_ADVANCE_DEVICE = 0x0040,
Y_ADVANCE_DEVICE = 0x0080,
};
Optional<i16> glyph_kerning(u16 left_glyph_id, u16 right_glyph_id) const;
static ErrorOr<GPOS> from_slice(ReadonlyBytes);
private:
GPOS(ReadonlyBytes slice, Version1_0 const& header,
ScriptList const& script_list, ReadonlySpan<ScriptRecord> script_records,
FeatureList const& feature_list, ReadonlySpan<FeatureRecord> feature_records,
LookupList const& lookup_list, ReadonlySpan<Offset16> lookup_offsets)
: m_slice(slice)
, m_header(header)
, m_script_list(script_list)
, m_script_records(script_records)
, m_feature_list(feature_list)
, m_feature_records(feature_records)
, m_lookup_list(lookup_list)
, m_lookup_offsets(lookup_offsets)
{
}
ReadonlyBytes m_slice;
Version1_0 const& m_header;
ScriptList const& m_script_list;
ReadonlySpan<ScriptRecord> m_script_records;
FeatureList const& m_feature_list;
ReadonlySpan<FeatureRecord> m_feature_records;
LookupList const& m_lookup_list;
ReadonlySpan<Offset16> m_lookup_offsets;
};
}
namespace AK {
template<>
struct Traits<OpenType::Kern::Header> : public DefaultTraits<OpenType::Kern::Header> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Kern::SubtableHeader> : public DefaultTraits<OpenType::Kern::SubtableHeader> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Kern::Format0> : public DefaultTraits<OpenType::Kern::Format0> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::Kern::Format0Pair> : public DefaultTraits<OpenType::Kern::Format0Pair> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::GPOS::Version1_0> : public DefaultTraits<OpenType::GPOS::Version1_0> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::FeatureList> : public DefaultTraits<OpenType::FeatureList> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::FeatureRecord> : public DefaultTraits<OpenType::FeatureRecord> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::LookupList> : public DefaultTraits<OpenType::LookupList> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::ScriptList> : public DefaultTraits<OpenType::ScriptList> {
static constexpr bool is_trivially_serializable() { return true; }
};
template<>
struct Traits<OpenType::ScriptRecord> : public DefaultTraits<OpenType::ScriptRecord> {
static constexpr bool is_trivially_serializable() { return true; }
};
}

View file

@ -1,489 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Jelle Raaijmakers <jelle@gmta.nl>
* Copyright (c) 2023, Lukas Affolter <git@lukasach.dev>
* Copyright (c) 2023, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/MemoryStream.h>
#include <AK/Try.h>
#include <LibCore/MappedFile.h>
#include <LibCore/Resource.h>
#include <LibGfx/AntiAliasingPainter.h>
#include <LibGfx/DeprecatedPainter.h>
#include <LibGfx/Font/OpenType/Cmap.h>
#include <LibGfx/Font/OpenType/Glyf.h>
#include <LibGfx/Font/OpenType/Tables.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/ImageFormats/PNGLoader.h>
#include <math.h>
#include <sys/mman.h>
namespace OpenType {
namespace {
class CmapCharCodeToGlyphIndex : public CharCodeToGlyphIndex {
public:
static ErrorOr<NonnullOwnPtr<CharCodeToGlyphIndex>> from_slice(Optional<ReadonlyBytes>);
virtual u32 glyph_id_for_code_point(u32 code_point) const override;
private:
explicit CmapCharCodeToGlyphIndex(Cmap cmap)
: m_cmap(cmap)
{
}
Cmap m_cmap;
};
ErrorOr<NonnullOwnPtr<CharCodeToGlyphIndex>> CmapCharCodeToGlyphIndex::from_slice(Optional<ReadonlyBytes> opt_cmap_slice)
{
if (!opt_cmap_slice.has_value())
return Error::from_string_literal("Font is missing Cmap");
auto cmap = TRY(Cmap::from_slice(opt_cmap_slice.value()));
// Select cmap table. FIXME: Do this better. Right now, just looks for platform "Windows"
// and corresponding encoding "Unicode full repertoire", or failing that, "Unicode BMP"
Optional<u32> active_cmap_index;
for (u32 i = 0; i < cmap.num_subtables(); i++) {
auto opt_subtable = cmap.subtable(i);
if (!opt_subtable.has_value()) {
continue;
}
auto subtable = opt_subtable.value();
auto platform = subtable.platform_id();
if (!platform.has_value())
return Error::from_string_literal("Invalid Platform ID");
/* NOTE: The encoding records are sorted first by platform ID, then by encoding ID.
This means that the Windows platform will take precedence over Macintosh, which is
usually what we want here. */
if (platform.value() == Cmap::Subtable::Platform::Unicode) {
if (subtable.encoding_id() == (u16)Cmap::Subtable::UnicodeEncoding::Unicode2_0_FullRepertoire) {
// "Encoding ID 3 should be used in conjunction with 'cmap' subtable formats 4 or 6."
active_cmap_index = i;
break;
}
if (subtable.encoding_id() == (u16)Cmap::Subtable::UnicodeEncoding::Unicode2_0_BMP_Only) {
// "Encoding ID 4 should be used in conjunction with subtable formats 10 or 12."
active_cmap_index = i;
break;
}
} else if (platform.value() == Cmap::Subtable::Platform::Windows) {
if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeFullRepertoire) {
active_cmap_index = i;
break;
}
if (subtable.encoding_id() == (u16)Cmap::Subtable::WindowsEncoding::UnicodeBMP) {
active_cmap_index = i;
break;
}
} else if (platform.value() == Cmap::Subtable::Platform::Macintosh) {
active_cmap_index = i;
// Intentionally no `break` so that Windows (value 3) wins over Macintosh (value 1).
}
}
if (!active_cmap_index.has_value())
return Error::from_string_literal("No suitable cmap subtable found");
TRY(cmap.subtable(active_cmap_index.value()).value().validate_format_can_be_read());
cmap.set_active_index(active_cmap_index.value());
return adopt_nonnull_own_or_enomem(new CmapCharCodeToGlyphIndex(cmap));
}
u32 CmapCharCodeToGlyphIndex::glyph_id_for_code_point(u32 code_point) const
{
return m_cmap.glyph_id_for_code_point(code_point);
}
}
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#ttc-header
struct [[gnu::packed]] TTCHeaderV1 {
Tag ttc_tag; // Font Collection ID string: 'ttcf' (used for fonts with CFF or CFF2 outlines as well as TrueType outlines)
BigEndian<u16> major_version; // Major version of the TTC Header, = 1.
BigEndian<u16> minor_version; // Minor version of the TTC Header, = 0.
BigEndian<u32> num_fonts; // Number of fonts in TTC
Offset32 table_directory_offsets; // Array of offsets to the TableDirectory for each font from the beginning of the file
};
static_assert(AssertSize<TTCHeaderV1, 16>());
}
template<>
class AK::Traits<OpenType::TTCHeaderV1> : public DefaultTraits<OpenType::TTCHeaderV1> {
public:
static constexpr bool is_trivially_serializable() { return true; }
};
namespace OpenType {
u16 be_u16(u8 const*);
u32 be_u32(u8 const*);
i16 be_i16(u8 const*);
u16 be_u16(u8 const* ptr)
{
return (((u16)ptr[0]) << 8) | ((u16)ptr[1]);
}
u32 be_u32(u8 const* ptr)
{
return (((u32)ptr[0]) << 24) | (((u32)ptr[1]) << 16) | (((u32)ptr[2]) << 8) | ((u32)ptr[3]);
}
i16 be_i16(u8 const* ptr)
{
return (((i16)ptr[0]) << 8) | ((i16)ptr[1]);
}
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_resource(Core::Resource const& resource, unsigned index)
{
auto font_data = Gfx::FontData::create_from_resource(resource);
return try_load_from_font_data(move(font_data), { .index = index });
}
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_font_data(NonnullOwnPtr<Gfx::FontData> font_data, Options options)
{
auto font = TRY(try_load_from_externally_owned_memory(font_data->bytes(), move(options)));
font->m_font_data = move(font_data);
return font;
}
static ErrorOr<Tag> read_tag(ReadonlyBytes buffer)
{
FixedMemoryStream stream { buffer };
return stream.read_value<Tag>();
}
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_externally_owned_memory(ReadonlyBytes buffer, Options options)
{
auto tag = TRY(read_tag(buffer));
if (tag == HeaderTag_FontCollection) {
// It's a font collection
FixedMemoryStream stream { buffer };
auto ttc_header_v1 = TRY(stream.read_in_place<TTCHeaderV1>());
// FIXME: Check for major_version == 2.
if (options.index >= ttc_header_v1->num_fonts)
return Error::from_string_literal("Requested font index is too large");
TRY(stream.seek(ttc_header_v1->table_directory_offsets + sizeof(u32) * options.index, SeekMode::SetPosition));
auto offset = TRY(stream.read_value<BigEndian<u32>>());
return try_load_from_offset(buffer, offset, move(options));
}
if (tag == HeaderTag_CFFOutlines)
return Error::from_string_literal("CFF fonts not supported yet");
if (tag != HeaderTag_TrueTypeOutlines && tag != HeaderTag_TrueTypeOutlinesApple)
return Error::from_string_literal("Not a valid font");
return try_load_from_offset(buffer, 0, move(options));
}
static ErrorOr<void> for_each_table_record(ReadonlyBytes buffer, u32 offset, Function<ErrorOr<void>(Tag, ReadonlyBytes)> callback)
{
FixedMemoryStream stream { buffer };
TRY(stream.seek(offset, AK::SeekMode::SetPosition));
auto& table_directory = *TRY(stream.read_in_place<TableDirectory const>());
for (auto i = 0; i < table_directory.num_tables; i++) {
auto& table_record = *TRY(stream.read_in_place<TableRecord const>());
if (Checked<u32>::addition_would_overflow(static_cast<u32>(table_record.offset), static_cast<u32>(table_record.length)))
return Error::from_string_literal("Invalid table offset or length in font");
if (buffer.size() < table_record.offset + table_record.length)
return Error::from_string_literal("Font file too small");
auto buffer_here = buffer.slice(table_record.offset, table_record.length);
TRY(callback(table_record.table_tag, buffer_here));
}
return {};
}
// FIXME: "loca" and "glyf" are not available for CFF fonts.
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_offset(ReadonlyBytes buffer, u32 offset, Options options)
{
Optional<ReadonlyBytes> opt_head_slice = {};
Optional<ReadonlyBytes> opt_name_slice = {};
Optional<ReadonlyBytes> opt_hhea_slice = {};
Optional<ReadonlyBytes> opt_maxp_slice = {};
Optional<ReadonlyBytes> opt_hmtx_slice = {};
Optional<ReadonlyBytes> opt_cmap_slice = {};
Optional<ReadonlyBytes> opt_loca_slice = {};
Optional<ReadonlyBytes> opt_glyf_slice = {};
Optional<ReadonlyBytes> opt_os2_slice = {};
Optional<ReadonlyBytes> opt_kern_slice = {};
Optional<GPOS> gpos;
TRY(for_each_table_record(buffer, offset, [&](Tag table_tag, ReadonlyBytes tag_buffer) -> ErrorOr<void> {
// Get the table offsets we need.
if (table_tag == Tag("head")) {
opt_head_slice = tag_buffer;
} else if (table_tag == Tag("name")) {
opt_name_slice = tag_buffer;
} else if (table_tag == Tag("hhea")) {
opt_hhea_slice = tag_buffer;
} else if (table_tag == Tag("maxp")) {
opt_maxp_slice = tag_buffer;
} else if (table_tag == Tag("hmtx")) {
opt_hmtx_slice = tag_buffer;
} else if (table_tag == Tag("cmap")) {
opt_cmap_slice = tag_buffer;
} else if (table_tag == Tag("loca")) {
opt_loca_slice = tag_buffer;
} else if (table_tag == Tag("glyf")) {
opt_glyf_slice = tag_buffer;
} else if (table_tag == Tag("OS/2")) {
opt_os2_slice = tag_buffer;
} else if (table_tag == Tag("kern")) {
opt_kern_slice = tag_buffer;
} else if (table_tag == Tag("GPOS")) {
gpos = TRY(GPOS::from_slice(tag_buffer));
}
return {};
}));
if (!opt_head_slice.has_value())
return Error::from_string_literal("Font is missing Head");
auto head = TRY(Head::from_slice(opt_head_slice.value()));
Optional<Name> name;
if (!(options.skip_tables & Options::SkipTables::Name)) {
if (!opt_name_slice.has_value())
return Error::from_string_literal("Font is missing Name");
name = TRY(Name::from_slice(opt_name_slice.value()));
}
if (!opt_hhea_slice.has_value())
return Error::from_string_literal("Font is missing Hhea");
auto hhea = TRY(Hhea::from_slice(opt_hhea_slice.value()));
if (!opt_maxp_slice.has_value())
return Error::from_string_literal("Font is missing Maxp");
auto maxp = TRY(Maxp::from_slice(opt_maxp_slice.value()));
bool can_omit_hmtx = (options.skip_tables & Options::SkipTables::Hmtx);
Optional<Hmtx> hmtx;
if (opt_hmtx_slice.has_value()) {
auto hmtx_or_error = Hmtx::from_slice(opt_hmtx_slice.value(), maxp.num_glyphs(), hhea.number_of_h_metrics());
if (!hmtx_or_error.is_error())
hmtx = hmtx_or_error.release_value();
else if (!can_omit_hmtx)
return hmtx_or_error.release_error();
} else if (!can_omit_hmtx) {
return Error::from_string_literal("Font is missing Hmtx");
}
if (!options.external_cmap && !opt_cmap_slice.has_value())
return Error::from_string_literal("Font is missing Cmap");
NonnullOwnPtr<CharCodeToGlyphIndex> cmap = options.external_cmap ? options.external_cmap.release_nonnull() : TRY(CmapCharCodeToGlyphIndex::from_slice(opt_cmap_slice.value()));
Optional<Loca> loca;
if (opt_loca_slice.has_value())
loca = TRY(Loca::from_slice(opt_loca_slice.value(), maxp.num_glyphs(), head.index_to_loc_format()));
Optional<Glyf> glyf;
if (opt_glyf_slice.has_value()) {
glyf = Glyf(opt_glyf_slice.value());
}
Optional<OS2> os2;
if (opt_os2_slice.has_value()) {
auto os2_or_error = OS2::from_slice(opt_os2_slice.value());
if (!os2_or_error.is_error())
os2 = os2_or_error.release_value();
else if (!(options.skip_tables & Options::SkipTables::OS2))
return os2_or_error.release_error();
}
Optional<Kern> kern {};
if (opt_kern_slice.has_value())
kern = TRY(Kern::from_slice(opt_kern_slice.value()));
return adopt_ref(*new Typeface(
move(head),
move(name),
move(hhea),
move(maxp),
move(hmtx),
move(cmap),
move(loca),
move(glyf),
move(os2),
move(kern),
move(gpos),
buffer.slice(offset),
options.index));
}
Gfx::ScaledFontMetrics Typeface::metrics([[maybe_unused]] float x_scale, float y_scale) const
{
i16 raw_ascender;
i16 raw_descender;
i16 raw_line_gap;
Optional<i16> x_height;
if (m_os2.has_value() && m_os2->use_typographic_metrics()) {
raw_ascender = m_os2->typographic_ascender();
raw_descender = m_os2->typographic_descender();
raw_line_gap = m_os2->typographic_line_gap();
x_height = m_os2->x_height();
} else {
raw_ascender = m_hhea.ascender();
raw_descender = m_hhea.descender();
raw_line_gap = m_hhea.line_gap();
}
if (!x_height.has_value()) {
x_height = glyph_metrics(glyph_id_for_code_point('x'), 1, 1, 1, 1).ascender;
}
return Gfx::ScaledFontMetrics {
.ascender = static_cast<float>(raw_ascender) * y_scale,
.descender = -static_cast<float>(raw_descender) * y_scale,
.line_gap = static_cast<float>(raw_line_gap) * y_scale,
.x_height = static_cast<float>(x_height.value()) * y_scale,
};
}
Gfx::ScaledGlyphMetrics Typeface::glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float, float) const
{
if (!m_loca.has_value() || !m_glyf.has_value() || !m_hmtx.has_value()) {
return Gfx::ScaledGlyphMetrics {};
}
if (glyph_id >= glyph_count()) {
glyph_id = 0;
}
auto horizontal_metrics = m_hmtx->get_glyph_horizontal_metrics(glyph_id);
auto glyph_offset = m_loca->get_glyph_offset(glyph_id);
auto glyph = m_glyf->glyph(glyph_offset);
return Gfx::ScaledGlyphMetrics {
.ascender = glyph.has_value() ? static_cast<float>(glyph->ascender()) * y_scale : 0,
.descender = glyph.has_value() ? static_cast<float>(glyph->descender()) * y_scale : 0,
.advance_width = static_cast<float>(horizontal_metrics.advance_width) * x_scale,
.left_side_bearing = static_cast<float>(horizontal_metrics.left_side_bearing) * x_scale,
};
}
u32 Typeface::glyph_count() const
{
return m_maxp.num_glyphs();
}
u16 Typeface::units_per_em() const
{
return m_head.units_per_em();
}
String Typeface::family() const
{
if (!m_name.has_value())
return {};
if (!m_family.has_value()) {
m_family = [&] {
auto string = m_name->typographic_family_name();
if (!string.is_empty())
return string;
return m_name->family_name();
}();
}
return *m_family;
}
u16 Typeface::weight() const
{
if (!m_weight.has_value()) {
m_weight = [&]() -> u16 {
constexpr u16 bold_bit { 1 };
if (m_os2.has_value() && m_os2->weight_class())
return m_os2->weight_class();
if (m_head.style() & bold_bit)
return 700;
return 400;
}();
}
return *m_weight;
}
u16 Typeface::width() const
{
if (!m_width.has_value()) {
m_width = [&]() -> u16 {
if (m_os2.has_value())
return m_os2->width_class();
return Gfx::FontWidth::Normal;
}();
}
return *m_width;
}
u8 Typeface::slope() const
{
if (!m_slope.has_value()) {
m_slope = [&]() -> u8 {
// https://docs.microsoft.com/en-us/typography/opentype/spec/os2
constexpr u16 italic_selection_bit { 1 };
constexpr u16 oblique_selection_bit { 512 };
// https://docs.microsoft.com/en-us/typography/opentype/spec/head
constexpr u16 italic_style_bit { 2 };
if (m_os2.has_value() && m_os2->selection() & oblique_selection_bit)
return 2;
if (m_os2.has_value() && m_os2->selection() & italic_selection_bit)
return 1;
if (m_head.style() & italic_style_bit)
return 1;
return 0;
}();
}
return *m_slope;
}
u32 Typeface::glyph_id_for_code_point(u32 code_point) const
{
return glyph_page(code_point / GlyphPage::glyphs_per_page).glyph_ids[code_point % GlyphPage::glyphs_per_page];
}
Typeface::GlyphPage const& Typeface::glyph_page(size_t page_index) const
{
if (page_index == 0) {
if (!m_glyph_page_zero) {
m_glyph_page_zero = make<GlyphPage>();
populate_glyph_page(*m_glyph_page_zero, 0);
}
return *m_glyph_page_zero;
}
if (auto it = m_glyph_pages.find(page_index); it != m_glyph_pages.end()) {
return *it->value;
}
auto glyph_page = make<GlyphPage>();
populate_glyph_page(*glyph_page, page_index);
auto const* glyph_page_ptr = glyph_page.ptr();
m_glyph_pages.set(page_index, move(glyph_page));
return *glyph_page_ptr;
}
void Typeface::populate_glyph_page(GlyphPage& glyph_page, size_t page_index) const
{
u32 first_code_point = page_index * GlyphPage::glyphs_per_page;
for (size_t i = 0; i < GlyphPage::glyphs_per_page; ++i) {
u32 code_point = first_code_point + i;
glyph_page.glyph_ids[i] = m_cmap->glyph_id_for_code_point(code_point);
}
}
}

View file

@ -1,156 +0,0 @@
/*
* Copyright (c) 2020, Srimanta Barua <srimanta.barua1@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Noncopyable.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/OwnPtr.h>
#include <AK/RefCounted.h>
#include <AK/StringView.h>
#include <LibCore/Resource.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/OpenType/Cmap.h>
#include <LibGfx/Font/OpenType/Glyf.h>
#include <LibGfx/Font/OpenType/Tables.h>
#include <LibGfx/Font/Typeface.h>
namespace OpenType {
class CharCodeToGlyphIndex {
public:
virtual ~CharCodeToGlyphIndex() = default;
virtual u32 glyph_id_for_code_point(u32) const = 0;
};
// This is not a nested struct to work around https://llvm.org/PR36684
struct FontOptions {
unsigned index { 0 };
OwnPtr<CharCodeToGlyphIndex> external_cmap {};
enum SkipTables {
// If set, do not try to read the 'name' table. family() and variant() will return empty strings.
Name = 1 << 0,
// If set, tolerate a missing or broken 'hmtx' table. This will make glyph_metrics() return 0 for everyting and is_fixed_width() return true.
Hmtx = 1 << 1,
// If set, tolerate a missing or broken 'OS/2' table. metrics(), resolve_ascender_and_descender(), weight(), width(), and slope() will return different values.
OS2 = 1 << 2,
};
u32 skip_tables { 0 };
};
class Typeface : public Gfx::Typeface {
AK_MAKE_NONCOPYABLE(Typeface);
public:
using Options = FontOptions;
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_resource(Core::Resource const&, unsigned index = 0);
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_font_data(NonnullOwnPtr<Gfx::FontData>, Options options = {});
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes bytes, Options options = {});
virtual Gfx::ScaledFontMetrics metrics(float x_scale, float y_scale) const override;
virtual Gfx::ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const override;
virtual u32 glyph_count() const override;
virtual u16 units_per_em() const override;
virtual u32 glyph_id_for_code_point(u32 code_point) const override;
virtual String family() const override;
virtual u16 weight() const override;
virtual u16 width() const override;
virtual u8 slope() const override;
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff
// "OpenType fonts that contain TrueType outlines should use the value of 0x00010000 for the sfntVersion.
// OpenType fonts containing CFF data (version 1 or 2) should use 0x4F54544F ('OTTO', when re-interpreted as a Tag) for sfntVersion.
// Note: The Apple specification for TrueType fonts allows for 'true' and 'typ1' for sfnt version.
// These version tags should not be used for OpenType fonts."
// "Font Collection ID string: 'ttcf' (used for fonts with CFF or CFF2 outlines as well as TrueType outlines)"
// The old Apple TrueType spec said "Fonts with TrueType outlines produced for OS X or iOS only are encouraged to use 'true'",
// so 'true' is somewhat common, especially in PDFs.
static constexpr Tag HeaderTag_TrueTypeOutlines = Tag::from_u32(0x00010000);
static constexpr Tag HeaderTag_TrueTypeOutlinesApple = Tag { "true" };
static constexpr Tag HeaderTag_CFFOutlines = Tag { "OTTO" };
static constexpr Tag HeaderTag_FontCollection = Tag { "ttcf" };
protected:
virtual ReadonlyBytes buffer() const override { return m_buffer; }
virtual unsigned ttc_index() const override { return m_ttc_index; }
private:
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_offset(ReadonlyBytes, u32 offset, Options options);
Typeface(
Head&& head,
Optional<Name>&& name,
Hhea&& hhea,
Maxp&& maxp,
Optional<Hmtx>&& hmtx,
NonnullOwnPtr<CharCodeToGlyphIndex> cmap,
Optional<Loca>&& loca,
Optional<Glyf>&& glyf,
Optional<OS2> os2,
Optional<Kern>&& kern,
Optional<GPOS> gpos,
ReadonlyBytes buffer,
unsigned ttc_index)
: m_buffer(buffer)
, m_ttc_index(ttc_index)
, m_head(move(head))
, m_name(move(name))
, m_hhea(move(hhea))
, m_maxp(move(maxp))
, m_hmtx(move(hmtx))
, m_loca(move(loca))
, m_glyf(move(glyf))
, m_cmap(move(cmap))
, m_os2(move(os2))
, m_kern(move(kern))
, m_gpos(move(gpos))
{
}
OwnPtr<Gfx::FontData> m_font_data;
ReadonlyBytes m_buffer;
unsigned m_ttc_index { 0 };
// These are stateful wrappers around non-owning slices
Head m_head;
Optional<Name> m_name;
Hhea m_hhea;
Maxp m_maxp;
Optional<Hmtx> m_hmtx;
Optional<Loca> m_loca;
Optional<Glyf> m_glyf;
NonnullOwnPtr<CharCodeToGlyphIndex> m_cmap;
Optional<OS2> m_os2;
Optional<Kern> m_kern;
Optional<GPOS> m_gpos;
// This cache stores information per code point.
// It's segmented into pages with data about 256 code points each.
struct GlyphPage {
static constexpr size_t glyphs_per_page = 256;
u32 glyph_ids[glyphs_per_page];
};
// Fast cache for GlyphPage #0 (code points 0-255) to avoid hash lookups for all of ASCII and Latin-1.
OwnPtr<GlyphPage> mutable m_glyph_page_zero;
HashMap<size_t, NonnullOwnPtr<GlyphPage>> mutable m_glyph_pages;
Optional<String> mutable m_family;
Optional<u16> mutable m_width;
Optional<u16> mutable m_weight;
Optional<u8> mutable m_slope;
GlyphPage const& glyph_page(size_t page_index) const;
void populate_glyph_page(GlyphPage&, size_t page_index) const;
};
}

View file

@ -4,11 +4,17 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/TypeCasts.h>
#include <AK/Utf8View.h>
#include <LibGfx/Font/Emoji.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/TypefaceSkia.h>
#include <LibGfx/TextLayout.h>
#include <core/SkFont.h>
#include <core/SkFontMetrics.h>
#include <core/SkFontTypes.h>
namespace Gfx {
ScaledFont::ScaledFont(NonnullRefPtr<Typeface> typeface, float point_width, float point_height, unsigned dpi_x, unsigned dpi_y)
@ -16,23 +22,41 @@ ScaledFont::ScaledFont(NonnullRefPtr<Typeface> typeface, float point_width, floa
, m_point_width(point_width)
, m_point_height(point_height)
{
float units_per_em = m_typeface->units_per_em();
float const units_per_em = m_typeface->units_per_em();
m_x_scale = (point_width * dpi_x) / (POINTS_PER_INCH * units_per_em);
m_y_scale = (point_height * dpi_y) / (POINTS_PER_INCH * units_per_em);
auto metrics = m_typeface->metrics(m_x_scale, m_y_scale);
m_pixel_size = m_point_height * (DEFAULT_DPI / POINTS_PER_INCH);
m_pixel_size_rounded_up = static_cast<int>(ceilf(m_pixel_size));
m_pixel_metrics = Gfx::FontPixelMetrics {
.size = (float)pixel_size(),
.x_height = metrics.x_height,
.advance_of_ascii_zero = (float)glyph_width('0'),
.ascent = metrics.ascender,
.descent = metrics.descender,
.line_gap = metrics.line_gap,
};
auto const* sk_typeface = verify_cast<TypefaceSkia>(*m_typeface).sk_typeface();
SkFont const font { sk_ref_sp(sk_typeface), m_pixel_size };
SkFontMetrics skMetrics;
font.getMetrics(&skMetrics);
FontPixelMetrics metrics;
metrics.size = font.getSize();
metrics.x_height = skMetrics.fXHeight;
metrics.advance_of_ascii_zero = font.measureText("0", 1, SkTextEncoding::kUTF8);
metrics.ascent = -skMetrics.fAscent;
metrics.descent = skMetrics.fDescent;
metrics.line_gap = skMetrics.fLeading;
m_pixel_metrics = metrics;
}
ScaledFontMetrics ScaledFont::metrics() const
{
SkFontMetrics sk_metrics;
skia_font(1).getMetrics(&sk_metrics);
ScaledFontMetrics metrics;
metrics.ascender = -sk_metrics.fAscent;
metrics.descender = sk_metrics.fDescent;
metrics.line_gap = sk_metrics.fLeading;
metrics.x_height = sk_metrics.fXHeight;
return metrics;
}
float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this); }

View file

@ -19,7 +19,7 @@ namespace Gfx {
class ScaledFont final : public Gfx::Font {
public:
ScaledFont(NonnullRefPtr<Typeface>, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI);
ScaledFontMetrics metrics() const { return m_typeface->metrics(m_x_scale, m_y_scale); }
ScaledFontMetrics metrics() const;
// ^Gfx::Font
virtual float point_size() const override;

View file

@ -4,18 +4,18 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <AK/TypeCasts.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/TypefaceSkia.h>
#include <core/SkFont.h>
#include <LibGfx/Font/ScaledFont.h>
namespace Gfx {
SkFont ScaledFont::skia_font(float scale) const
{
auto const& typeface = this->typeface().skia_typeface();
return SkFont { sk_ref_sp(typeface.ptr()), pixel_size() * scale };
auto const& sk_typeface = verify_cast<TypefaceSkia>(*m_typeface).sk_typeface();
return SkFont { sk_ref_sp(sk_typeface), pixel_size() * scale };
}
}

View file

@ -4,16 +4,33 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <core/SkTypeface.h>
#include <harfbuzz/hb.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/TypefaceSkia.h>
namespace Gfx {
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_resource(Core::Resource const& resource, int ttc_index)
{
auto font_data = Gfx::FontData::create_from_resource(resource);
return try_load_from_font_data(move(font_data), ttc_index);
}
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_font_data(NonnullOwnPtr<Gfx::FontData> font_data, int ttc_index)
{
auto typeface = TRY(try_load_from_externally_owned_memory(font_data->bytes(), ttc_index));
typeface->m_font_data = move(font_data);
return typeface;
}
ErrorOr<NonnullRefPtr<Typeface>> Typeface::try_load_from_externally_owned_memory(ReadonlyBytes bytes, int ttc_index)
{
return TypefaceSkia::load_from_buffer(bytes, ttc_index);
}
Typeface::Typeface() = default;
Typeface::~Typeface()

View file

@ -36,18 +36,13 @@ struct ScaledFontMetrics {
}
};
struct ScaledGlyphMetrics {
float ascender;
float descender;
float advance_width;
float left_side_bearing;
};
class Typeface : public RefCounted<Typeface> {
public:
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_resource(Core::Resource const&, int ttc_index = 0);
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_font_data(NonnullOwnPtr<Gfx::FontData>, int ttc_index = 0);
static ErrorOr<NonnullRefPtr<Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes bytes, int ttc_index = 0);
virtual ~Typeface();
virtual ScaledFontMetrics metrics(float x_scale, float y_scale) const = 0;
virtual ScaledGlyphMetrics glyph_metrics(u32 glyph_id, float x_scale, float y_scale, float point_width, float point_height) const = 0;
virtual u32 glyph_count() const = 0;
virtual u16 units_per_em() const = 0;
@ -59,7 +54,6 @@ public:
[[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const;
RefPtr<SkTypeface> const& skia_typeface() const;
hb_face_t* harfbuzz_typeface() const;
protected:
@ -69,8 +63,9 @@ protected:
virtual unsigned ttc_index() const = 0;
private:
OwnPtr<Gfx::FontData> m_font_data;
mutable HashMap<float, NonnullRefPtr<ScaledFont>> m_scaled_fonts;
mutable RefPtr<SkTypeface> m_skia_typeface;
mutable hb_blob_t* m_harfbuzz_blob { nullptr };
mutable hb_face_t* m_harfbuzz_face { nullptr };
};

View file

@ -4,14 +4,15 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#define AK_DONT_REPLACE_STD
#include <AK/LsanSuppressions.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/TypefaceSkia.h>
#include <core/SkData.h>
#include <core/SkFontMgr.h>
#include <core/SkTypeface.h>
#include <include/core/SkRefCnt.h>
#include <include/core/SkTypeface.h>
#ifndef AK_OS_ANDROID
# include <ports/SkFontMgr_fontconfig.h>
#else
@ -26,29 +27,94 @@ namespace Gfx {
static sk_sp<SkFontMgr> s_font_manager;
RefPtr<SkTypeface> const& Typeface::skia_typeface() const
struct TypefaceSkia::Impl {
sk_sp<SkTypeface> skia_typeface;
};
ErrorOr<NonnullRefPtr<TypefaceSkia>> TypefaceSkia::load_from_buffer(AK::ReadonlyBytes buffer, int ttc_index)
{
if (!s_font_manager) {
#ifdef AK_OS_MACOS
if (!Gfx::FontDatabase::the().should_force_fontconfig())
if (!Gfx::FontDatabase::the().should_force_fontconfig()) {
s_font_manager = SkFontMgr_New_CoreText(nullptr);
}
#endif
#ifndef AK_OS_ANDROID
if (!s_font_manager)
if (!s_font_manager) {
s_font_manager = SkFontMgr_New_FontConfig(nullptr);
}
#else
s_font_manager = SkFontMgr_New_Android(nullptr);
#endif
}
if (!m_skia_typeface) {
auto data = SkData::MakeWithoutCopy(buffer().data(), buffer().size());
auto skia_typeface = s_font_manager->makeFromData(data, ttc_index());
if (!skia_typeface)
VERIFY_NOT_REACHED();
m_skia_typeface = *skia_typeface;
auto data = SkData::MakeWithoutCopy(buffer.data(), buffer.size());
auto skia_typeface = s_font_manager->makeFromData(data, ttc_index);
if (!skia_typeface) {
return Error::from_string_literal("Failed to load typeface from buffer");
}
return adopt_ref(*new TypefaceSkia { make<TypefaceSkia::Impl>(skia_typeface), buffer, ttc_index });
}
SkTypeface const* TypefaceSkia::sk_typeface() const
{
return impl().skia_typeface.get();
}
TypefaceSkia::TypefaceSkia(NonnullOwnPtr<Impl> impl, ReadonlyBytes buffer, int ttc_index)
: m_impl(move(impl))
, m_buffer(buffer)
, m_ttc_index(ttc_index) {
};
u32 TypefaceSkia::glyph_count() const
{
return impl().skia_typeface->countGlyphs();
}
u16 TypefaceSkia::units_per_em() const
{
return impl().skia_typeface->getUnitsPerEm();
}
u32 TypefaceSkia::glyph_id_for_code_point(u32 code_point) const
{
return impl().skia_typeface->unicharToGlyph(code_point);
}
String TypefaceSkia::family() const
{
SkString family_name;
impl().skia_typeface->getFamilyName(&family_name);
return String::from_utf8_without_validation(ReadonlyBytes { family_name.c_str(), family_name.size() });
}
u16 TypefaceSkia::weight() const
{
return impl().skia_typeface->fontStyle().weight();
}
u16 TypefaceSkia::width() const
{
return impl().skia_typeface->fontStyle().width();
}
u8 TypefaceSkia::slope() const
{
auto slant = impl().skia_typeface->fontStyle().slant();
switch (slant) {
case SkFontStyle::kUpright_Slant:
return 0;
case SkFontStyle::kItalic_Slant:
return 1;
case SkFontStyle::kOblique_Slant:
return 2;
default:
return 0;
}
return m_skia_typeface;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/Font/Typeface.h>
namespace Gfx {
class TypefaceSkia : public Gfx::Typeface {
AK_MAKE_NONCOPYABLE(TypefaceSkia);
public:
static ErrorOr<NonnullRefPtr<TypefaceSkia>> load_from_buffer(ReadonlyBytes, int index = 0);
virtual u32 glyph_count() const override;
virtual u16 units_per_em() const override;
virtual u32 glyph_id_for_code_point(u32 code_point) const override;
virtual String family() const override;
virtual u16 weight() const override;
virtual u16 width() const override;
virtual u8 slope() const override;
virtual ReadonlyBytes buffer() const override { return m_buffer; }
virtual unsigned ttc_index() const override { return m_ttc_index; }
SkTypeface const* sk_typeface() const;
private:
struct Impl;
Impl& impl() const { return *m_impl; }
NonnullOwnPtr<Impl> m_impl;
explicit TypefaceSkia(NonnullOwnPtr<Impl>, ReadonlyBytes, int ttc_index = 0);
ReadonlyBytes m_buffer;
unsigned m_ttc_index { 0 };
};
}

View file

@ -9,8 +9,8 @@
#include <AK/IntegralMath.h>
#include <LibCompress/Zlib.h>
#include <LibCore/Resource.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/WOFF/Loader.h>
#include <LibGfx/FourCC.h>
namespace WOFF {
@ -35,7 +35,7 @@ static_assert(AssertSize<Header, 44>());
// https://www.w3.org/TR/WOFF/#TableDirectory
struct [[gnu::packed]] TableDirectoryEntry {
OpenType::Tag tag; // 4-byte sfnt table identifier.
Gfx::FourCC 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.
@ -68,12 +68,41 @@ static u16 pow_2_less_than_or_equal(u16 x)
return 1 << (sizeof(u16) * 8 - count_leading_zeroes_safe<u16>(x - 1));
}
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_resource(Core::Resource const& resource, unsigned index)
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_resource(Core::Resource const& resource, unsigned index)
{
return try_load_from_externally_owned_memory(resource.data(), index);
}
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned int index)
using Uint8 = u8;
using Uint16 = BigEndian<u16>;
using Int16 = BigEndian<i16>;
using Uint32 = BigEndian<u32>;
using Int32 = BigEndian<i32>;
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
// Table Directory (known as the Offset Table in ISO-IEC 14496-22:2019)
struct [[gnu::packed]] TableDirectory {
Uint32 sfnt_version;
Uint16 num_tables; // Number of tables.
Uint16 search_range; // (Maximum power of 2 <= numTables) x 16.
Uint16 entry_selector; // Log2(maximum power of 2 <= numTables).
Uint16 range_shift; // NumTables x 16 - searchRange.
};
static_assert(AssertSize<TableDirectory, 12>());
using Offset16 = BigEndian<u16>;
using Offset32 = BigEndian<u32>;
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#table-directory
struct [[gnu::packed]] TableRecord {
Gfx::FourCC table_tag; // Table identifier.
Uint32 checksum; // CheckSum for this table.
Offset32 offset; // Offset from beginning of TrueType font file.
Uint32 length; // Length of this table.
};
static_assert(AssertSize<TableRecord, 16>());
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes buffer, unsigned int index)
{
FixedMemoryStream stream(buffer);
auto header = TRY(stream.read_value<Header>());
@ -89,7 +118,7 @@ ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory
// (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.
auto expected_total_sfnt_size = sizeof(OpenType::TableDirectory) + header.num_tables * 16;
auto expected_total_sfnt_size = sizeof(TableDirectory) + header.num_tables * 16;
if (header.length > buffer.size())
return Error::from_string_literal("Invalid WOFF length");
if (header.num_tables == 0 || header.num_tables > NumericLimits<u16>::max() / 16)
@ -109,7 +138,7 @@ ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory
auto font_buffer = TRY(ByteBuffer::create_zeroed(header.total_sfnt_size));
u16 search_range = pow_2_less_than_or_equal(header.num_tables);
OpenType::TableDirectory table_directory {
TableDirectory table_directory {
.sfnt_version = header.flavor,
.num_tables = header.num_tables,
.search_range = search_range * 16,
@ -118,7 +147,7 @@ ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory
};
font_buffer.overwrite(0, &table_directory, sizeof(table_directory));
size_t font_buffer_offset = sizeof(OpenType::TableDirectory) + header.num_tables * sizeof(OpenType::TableRecord);
size_t font_buffer_offset = sizeof(TableDirectory) + header.num_tables * sizeof(TableRecord);
for (size_t i = 0; i < header.num_tables; ++i) {
auto entry = TRY(stream.read_value<TableDirectoryEntry>());
@ -142,8 +171,8 @@ ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory
font_buffer.overwrite(font_buffer_offset, buffer.data() + entry.offset, entry.orig_length);
}
size_t table_directory_offset = sizeof(OpenType::TableDirectory) + i * sizeof(OpenType::TableRecord);
OpenType::TableRecord table_record {
size_t table_directory_offset = sizeof(TableDirectory) + i * sizeof(TableRecord);
TableRecord table_record {
.table_tag = entry.tag,
.checksum = entry.orig_checksum,
.offset = font_buffer_offset,
@ -158,7 +187,7 @@ ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory
return Error::from_string_literal("Invalid WOFF total sfnt size");
auto font_data = Gfx::FontData::create_from_byte_buffer(move(font_buffer));
return TRY(OpenType::Typeface::try_load_from_font_data(move(font_data), { .index = index }));
return TRY(Gfx::Typeface::try_load_from_font_data(move(font_data), index));
}
}

View file

@ -9,11 +9,11 @@
#include <AK/OwnPtr.h>
#include <AK/RefCounted.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/Typeface.h>
namespace WOFF {
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_resource(Core::Resource const&, unsigned index = 0);
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0);
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_resource(Core::Resource const&, unsigned index = 0);
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0);
}

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/WOFF2/Loader.h>
#include <woff2/decode.h>
@ -53,7 +53,7 @@ private:
ByteBuffer& m_buffer;
};
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes bytes)
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes bytes)
{
auto ttf_buffer = TRY(ByteBuffer::create_uninitialized(0));
auto output = WOFF2ByteBufferOut { ttf_buffer };
@ -63,7 +63,7 @@ ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory
}
auto font_data = Gfx::FontData::create_from_byte_buffer(move(ttf_buffer));
auto input_font = TRY(OpenType::Typeface::try_load_from_font_data(move(font_data)));
auto input_font = TRY(Gfx::Typeface::try_load_from_font_data(move(font_data)));
return input_font;
}

View file

@ -9,10 +9,10 @@
#include <AK/OwnPtr.h>
#include <AK/RefCounted.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/Typeface.h>
namespace WOFF2 {
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes);
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes);
}

View file

@ -5,7 +5,6 @@
*/
#include <LibCore/Promise.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/WOFF/Loader.h>
#include <LibGfx/Font/WOFF2/Loader.h>
@ -35,7 +34,7 @@ static NonnullRefPtr<Core::Promise<NonnullRefPtr<Gfx::Typeface>>> load_vector_fo
Platform::EventLoopPlugin::the().deferred_invoke([&data, promise] {
// FIXME: This should be de-duplicated with StyleComputer::FontLoader::try_load_font
// We don't have the luxury of knowing the MIME type, so we have to try all formats.
auto ttf = OpenType::Typeface::try_load_from_externally_owned_memory(data);
auto ttf = Gfx::Typeface::try_load_from_externally_owned_memory(data);
if (!ttf.is_error()) {
promise->resolve(ttf.release_value());
return;

View file

@ -19,7 +19,6 @@
#include <LibGfx/Font/Font.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/FontStyleMapping.h>
#include <LibGfx/Font/OpenType/Typeface.h>
#include <LibGfx/Font/ScaledFont.h>
#include <LibGfx/Font/Typeface.h>
#include <LibGfx/Font/WOFF/Loader.h>
@ -172,7 +171,7 @@ ErrorOr<NonnullRefPtr<Gfx::Typeface>> FontLoader::try_load_font()
}
if (mime_type.has_value()) {
if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv) {
if (auto result = OpenType::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) {
if (auto result = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data()); !result.is_error()) {
return result;
}
}
@ -188,7 +187,7 @@ ErrorOr<NonnullRefPtr<Gfx::Typeface>> FontLoader::try_load_font()
}
}
auto ttf = OpenType::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data());
auto ttf = Gfx::Typeface::try_load_from_externally_owned_memory(resource()->encoded_data());
if (!ttf.is_error())
return ttf.release_value();
return Error::from_string_literal("Automatic format detection failed");