mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 23:20:20 +00:00
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:
parent
99f64139d0
commit
a9d5a99568
Notes:
github-actions[bot]
2024-09-05 17:22:50 +00:00
Author: https://github.com/kalenikaliaksandr Commit: https://github.com/LadybirdBrowser/ladybird/commit/a9d5a99568b Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1278 Reviewed-by: https://github.com/trflynn89
40 changed files with 309 additions and 3201 deletions
22
AK/LsanSuppressions.h
Normal file
22
AK/LsanSuppressions.h
Normal 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
|
|
@ -523,7 +523,6 @@ if (BUILD_TESTING)
|
||||||
list(APPEND TEST_DIRECTORIES
|
list(APPEND TEST_DIRECTORIES
|
||||||
LibGfx
|
LibGfx
|
||||||
LibMedia
|
LibMedia
|
||||||
LibTTF
|
|
||||||
LibWeb
|
LibWeb
|
||||||
LibWebView
|
LibWebView
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -36,7 +36,6 @@ set(FUZZER_TARGETS
|
||||||
Tar
|
Tar
|
||||||
TextDecoder
|
TextDecoder
|
||||||
TIFFLoader
|
TIFFLoader
|
||||||
TTF
|
|
||||||
TinyVGLoader
|
TinyVGLoader
|
||||||
URL
|
URL
|
||||||
WasmParser
|
WasmParser
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
set(TEST_SOURCES
|
|
||||||
TestCmap.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
foreach(source IN LISTS TEST_SOURCES)
|
|
||||||
serenity_test("${source}" LibTTF LIBS LibGfx)
|
|
||||||
endforeach()
|
|
|
@ -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());
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
BlockContainer <html> at (0,0) content-size 800x37 [BFC] 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
|
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
|
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 200x19] baseline: 14.296875
|
||||||
BlockContainer <input> at (9,9) content-size 191.875x19 inline-block [BFC] children: not-inline
|
BlockContainer <input> at (9,9) content-size 200x19 inline-block [BFC] children: not-inline
|
||||||
Box <div> at (11,10) content-size 187.875x17 flex-container(row) [FFC] 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 187.875x17 flex-item [BFC] children: 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
|
frag 0 from TextNode start: 0, length: 11, rect: [11,10 91.953125x17] baseline: 13.296875
|
||||||
"Hello World"
|
"Hello World"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
@ -13,7 +13,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x37]
|
PaintableWithLines (BlockContainer<HTML>) [0,0 800x37]
|
||||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
||||||
PaintableWithLines (BlockContainer<INPUT>) [8,8 193.875x21]
|
PaintableWithLines (BlockContainer<INPUT>) [8,8 202x21]
|
||||||
PaintableBox (Box<DIV>) [9,9 191.875x19]
|
PaintableBox (Box<DIV>) [9,9 200x19]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [11,10 187.875x17]
|
PaintableWithLines (BlockContainer<DIV>) [11,10 196x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
|
|
|
@ -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
|
frag 0 from TextNode start: 0, length: 4, rect: [8,8 28.40625x17] baseline: 13.296875
|
||||||
"well"
|
"well"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
BlockContainer <(anonymous)> at (46,8) content-size 36.84375x17 flex-item [BFC] children: inline
|
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,8 36.84375x17] baseline: 13.296875
|
frag 0 from TextNode start: 0, length: 5, rect: [46.40625,8 36.84375x17] baseline: 13.296875
|
||||||
"hello"
|
"hello"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
BlockContainer <(anonymous)> at (92.4375,8) content-size 55.359375x17 flex-item [BFC] children: inline
|
BlockContainer <(anonymous)> at (93.25,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
|
frag 0 from TextNode start: 0, length: 7, rect: [93.25,8 55.359375x17] baseline: 13.296875
|
||||||
"friends"
|
"friends"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableBox (Box<DIV>.foo) [8,8 784x17]
|
PaintableBox (Box<DIV>.foo) [8,8 784x17]
|
||||||
PaintableWithLines (BlockContainer(anonymous)) [8,8 28.40625x17]
|
PaintableWithLines (BlockContainer(anonymous)) [8,8 28.40625x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
PaintableWithLines (BlockContainer(anonymous)) [46,8 36.84375x17]
|
PaintableWithLines (BlockContainer(anonymous)) [46.40625,8 36.84375x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
PaintableWithLines (BlockContainer(anonymous)) [92.4375,8 55.359375x17]
|
PaintableWithLines (BlockContainer(anonymous)) [93.25,8 55.359375x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
BlockContainer <html> at (0,0) content-size 800x16 [BFC] children: not-inline
|
BlockContainer <html> at (0,0) content-size 800x17 [BFC] children: not-inline
|
||||||
BlockContainer <body> at (8,8) content-size 784x0 children: not-inline
|
BlockContainer <body> at (8,8) content-size 784x1 children: not-inline
|
||||||
BlockContainer <div> at (8,8) content-size 784x0 children: inline
|
BlockContainer <div> at (8,8) content-size 784x1 children: inline
|
||||||
frag 0 from TextNode start: 0, length: 21, rect: [8,8 0x0] baseline: 0
|
frag 0 from TextNode start: 0, length: 21, rect: [8,8 0x1] baseline: 0.796875
|
||||||
"should not be visible"
|
"should not be visible"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
|
||||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x16]
|
PaintableWithLines (BlockContainer<HTML>) [0,0 800x17]
|
||||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x0]
|
PaintableWithLines (BlockContainer<BODY>) [8,8 784x1]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [8,8 784x0]
|
PaintableWithLines (BlockContainer<DIV>) [8,8 784x1]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] 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
|
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
|
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 198x19] baseline: 14.296875
|
||||||
BlockContainer <input> at (9,9) content-size 189.875x19 inline-block [BFC] children: not-inline
|
BlockContainer <input> at (9,9) content-size 198x19 inline-block [BFC] children: not-inline
|
||||||
Box <div> at (11,10) content-size 185.875x17 flex-container(row) [FFC] 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 185.875x17 flex-item [BFC] children: 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
|
frag 0 from TextNode start: 0, length: 7, rect: [11,10 55.6875x17] baseline: 13.296875
|
||||||
"120.png"
|
"120.png"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
@ -14,7 +14,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
||||||
PaintableWithLines (BlockContainer<INPUT>) [8,8 191.875x21]
|
PaintableWithLines (BlockContainer<INPUT>) [8,8 200x21]
|
||||||
PaintableBox (Box<DIV>) [9,9 189.875x19]
|
PaintableBox (Box<DIV>) [9,9 198x19]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [11,10 185.875x17]
|
PaintableWithLines (BlockContainer<DIV>) [11,10 194x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] 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
|
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
|
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 198x19] baseline: 14.296875
|
||||||
BlockContainer <input> at (9,9) content-size 189.875x19 inline-block [BFC] children: not-inline
|
BlockContainer <input> at (9,9) content-size 198x19 inline-block [BFC] children: not-inline
|
||||||
Box <div> at (11,10) content-size 185.875x17 flex-container(row) [FFC] 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 185.875x17 flex-item [BFC] children: 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
|
frag 0 from TextNode start: 0, length: 7, rect: [11,10 61.890625x17] baseline: 13.296875
|
||||||
"hunter2"
|
"hunter2"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
@ -14,7 +14,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
||||||
PaintableWithLines (BlockContainer<INPUT>) [8,8 191.875x21]
|
PaintableWithLines (BlockContainer<INPUT>) [8,8 200x21]
|
||||||
PaintableBox (Box<DIV>) [9,9 189.875x19]
|
PaintableBox (Box<DIV>) [9,9 198x19]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [11,10 185.875x17]
|
PaintableWithLines (BlockContainer<DIV>) [11,10 194x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
BlockContainer <html> at (0,0) content-size 800x600 [BFC] 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
|
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
|
frag 0 from BlockContainer start: 0, length: 0, rect: [9,9 198x19] baseline: 14.296875
|
||||||
BlockContainer <input> at (9,9) content-size 189.875x19 inline-block [BFC] children: not-inline
|
BlockContainer <input> at (9,9) content-size 198x19 inline-block [BFC] children: not-inline
|
||||||
Box <div> at (11,10) content-size 185.875x17 flex-container(row) [FFC] 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 185.875x17 flex-item [BFC] children: 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
|
frag 0 from TextNode start: 0, length: 7, rect: [11,10 55.5625x17] baseline: 13.296875
|
||||||
"*******"
|
"*******"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
@ -14,7 +14,7 @@ Viewport <#document> at (0,0) content-size 800x600 children: not-inline
|
||||||
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
PaintableWithLines (BlockContainer<BODY>) [8,8 784x21]
|
||||||
PaintableWithLines (BlockContainer<INPUT>) [8,8 191.875x21]
|
PaintableWithLines (BlockContainer<INPUT>) [8,8 200x21]
|
||||||
PaintableBox (Box<DIV>) [9,9 189.875x19]
|
PaintableBox (Box<DIV>) [9,9 198x19]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [11,10 185.875x17]
|
PaintableWithLines (BlockContainer<DIV>) [11,10 194x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
|
|
|
@ -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 <html> at (0,0) content-size 800x600 [BFC] children: not-inline
|
||||||
BlockContainer <body> at (8,8) content-size 784x34 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
|
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>
|
TextNode <#text>
|
||||||
BlockContainer <textarea#textarea> at (11,11) content-size 185.875x28 inline-block [BFC] children: not-inline
|
BlockContainer <textarea#textarea> at (11,11) content-size 194x28 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 194x17 children: not-inline
|
||||||
BlockContainer <div> at (11,11) content-size 185.875x17 children: 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
|
frag 0 from TextNode start: 0, length: 14, rect: [11,11 108.453125x17] baseline: 13.296875
|
||||||
"Original value"
|
"Original value"
|
||||||
TextNode <#text>
|
TextNode <#text>
|
||||||
|
@ -19,8 +19,8 @@ ViewportPaintable (Viewport<#document>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
PaintableWithLines (BlockContainer<HTML>) [0,0 800x600]
|
||||||
PaintableWithLines (BlockContainer<BODY>) [8,8 784x34] overflow: [8,8 784x50]
|
PaintableWithLines (BlockContainer<BODY>) [8,8 784x34] overflow: [8,8 784x50]
|
||||||
PaintableWithLines (BlockContainer<FORM>#form) [8,8 784x34]
|
PaintableWithLines (BlockContainer<FORM>#form) [8,8 784x34]
|
||||||
PaintableWithLines (BlockContainer<TEXTAREA>#textarea) [8,8 191.875x34]
|
PaintableWithLines (BlockContainer<TEXTAREA>#textarea) [8,8 200x34]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [11,11 185.875x17]
|
PaintableWithLines (BlockContainer<DIV>) [11,11 194x17]
|
||||||
PaintableWithLines (BlockContainer<DIV>) [11,11 185.875x17]
|
PaintableWithLines (BlockContainer<DIV>) [11,11 194x17]
|
||||||
TextPaintable (TextNode<#text>)
|
TextPaintable (TextNode<#text>)
|
||||||
PaintableWithLines (BlockContainer(anonymous)) [8,58 784x0]
|
PaintableWithLines (BlockContainer(anonymous)) [8,58 784x0]
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
|
@ -15,11 +15,6 @@ set(SOURCES
|
||||||
Font/Font.cpp
|
Font/Font.cpp
|
||||||
Font/FontData.cpp
|
Font/FontData.cpp
|
||||||
Font/FontDatabase.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/ScaledFont.cpp
|
||||||
Font/ScaledFontSkia.cpp
|
Font/ScaledFontSkia.cpp
|
||||||
Font/Typeface.cpp
|
Font/Typeface.cpp
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
#include <LibCore/Resource.h>
|
#include <LibCore/Resource.h>
|
||||||
#include <LibGfx/Font/Font.h>
|
#include <LibGfx/Font/Font.h>
|
||||||
#include <LibGfx/Font/FontDatabase.h>
|
#include <LibGfx/Font/FontDatabase.h>
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
#include <LibGfx/Font/WOFF/Loader.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());
|
auto path = LexicalPath(uri.bytes_as_string_view());
|
||||||
if (path.has_extension(".ttf"sv)) {
|
if (path.has_extension(".ttf"sv)) {
|
||||||
// FIXME: What about .otf
|
// 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 font = font_or_error.release_value();
|
||||||
auto& family = m_private->typeface_by_family.ensure(font->family(), [] {
|
auto& family = m_private->typeface_by_family.ensure(font->family(), [] {
|
||||||
return Vector<NonnullRefPtr<Typeface>> {};
|
return Vector<NonnullRefPtr<Typeface>> {};
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 };
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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; }
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
|
@ -4,11 +4,17 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <AK/TypeCasts.h>
|
||||||
#include <AK/Utf8View.h>
|
#include <AK/Utf8View.h>
|
||||||
#include <LibGfx/Font/Emoji.h>
|
#include <LibGfx/Font/Emoji.h>
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
|
#include <LibGfx/Font/TypefaceSkia.h>
|
||||||
#include <LibGfx/TextLayout.h>
|
#include <LibGfx/TextLayout.h>
|
||||||
|
|
||||||
|
#include <core/SkFont.h>
|
||||||
|
#include <core/SkFontMetrics.h>
|
||||||
|
#include <core/SkFontTypes.h>
|
||||||
|
|
||||||
namespace Gfx {
|
namespace Gfx {
|
||||||
|
|
||||||
ScaledFont::ScaledFont(NonnullRefPtr<Typeface> typeface, float point_width, float point_height, unsigned dpi_x, unsigned dpi_y)
|
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_width(point_width)
|
||||||
, m_point_height(point_height)
|
, 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_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);
|
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 = m_point_height * (DEFAULT_DPI / POINTS_PER_INCH);
|
||||||
m_pixel_size_rounded_up = static_cast<int>(ceilf(m_pixel_size));
|
m_pixel_size_rounded_up = static_cast<int>(ceilf(m_pixel_size));
|
||||||
|
|
||||||
m_pixel_metrics = Gfx::FontPixelMetrics {
|
auto const* sk_typeface = verify_cast<TypefaceSkia>(*m_typeface).sk_typeface();
|
||||||
.size = (float)pixel_size(),
|
SkFont const font { sk_ref_sp(sk_typeface), m_pixel_size };
|
||||||
.x_height = metrics.x_height,
|
|
||||||
.advance_of_ascii_zero = (float)glyph_width('0'),
|
SkFontMetrics skMetrics;
|
||||||
.ascent = metrics.ascender,
|
font.getMetrics(&skMetrics);
|
||||||
.descent = metrics.descender,
|
|
||||||
.line_gap = metrics.line_gap,
|
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); }
|
float ScaledFont::width(StringView view) const { return measure_text_width(Utf8View(view), *this); }
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace Gfx {
|
||||||
class ScaledFont final : public Gfx::Font {
|
class ScaledFont final : public Gfx::Font {
|
||||||
public:
|
public:
|
||||||
ScaledFont(NonnullRefPtr<Typeface>, float point_width, float point_height, unsigned dpi_x = DEFAULT_DPI, unsigned dpi_y = DEFAULT_DPI);
|
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
|
// ^Gfx::Font
|
||||||
virtual float point_size() const override;
|
virtual float point_size() const override;
|
||||||
|
|
|
@ -4,18 +4,18 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* 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 <core/SkFont.h>
|
||||||
|
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
|
||||||
|
|
||||||
namespace Gfx {
|
namespace Gfx {
|
||||||
|
|
||||||
SkFont ScaledFont::skia_font(float scale) const
|
SkFont ScaledFont::skia_font(float scale) const
|
||||||
{
|
{
|
||||||
auto const& typeface = this->typeface().skia_typeface();
|
auto const& sk_typeface = verify_cast<TypefaceSkia>(*m_typeface).sk_typeface();
|
||||||
return SkFont { sk_ref_sp(typeface.ptr()), pixel_size() * scale };
|
return SkFont { sk_ref_sp(sk_typeface), pixel_size() * scale };
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,33 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define AK_DONT_REPLACE_STD
|
|
||||||
|
|
||||||
#include <core/SkTypeface.h>
|
#include <core/SkTypeface.h>
|
||||||
#include <harfbuzz/hb.h>
|
#include <harfbuzz/hb.h>
|
||||||
|
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
#include <LibGfx/Font/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
|
#include <LibGfx/Font/TypefaceSkia.h>
|
||||||
|
|
||||||
namespace Gfx {
|
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() = default;
|
||||||
|
|
||||||
Typeface::~Typeface()
|
Typeface::~Typeface()
|
||||||
|
|
|
@ -36,18 +36,13 @@ struct ScaledFontMetrics {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScaledGlyphMetrics {
|
|
||||||
float ascender;
|
|
||||||
float descender;
|
|
||||||
float advance_width;
|
|
||||||
float left_side_bearing;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Typeface : public RefCounted<Typeface> {
|
class Typeface : public RefCounted<Typeface> {
|
||||||
public:
|
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 ~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 u32 glyph_count() const = 0;
|
||||||
virtual u16 units_per_em() const = 0;
|
virtual u16 units_per_em() const = 0;
|
||||||
|
@ -59,7 +54,6 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const;
|
[[nodiscard]] NonnullRefPtr<ScaledFont> scaled_font(float point_size) const;
|
||||||
|
|
||||||
RefPtr<SkTypeface> const& skia_typeface() const;
|
|
||||||
hb_face_t* harfbuzz_typeface() const;
|
hb_face_t* harfbuzz_typeface() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -69,8 +63,9 @@ protected:
|
||||||
virtual unsigned ttc_index() const = 0;
|
virtual unsigned ttc_index() const = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
OwnPtr<Gfx::FontData> m_font_data;
|
||||||
|
|
||||||
mutable HashMap<float, NonnullRefPtr<ScaledFont>> m_scaled_fonts;
|
mutable HashMap<float, NonnullRefPtr<ScaledFont>> m_scaled_fonts;
|
||||||
mutable RefPtr<SkTypeface> m_skia_typeface;
|
|
||||||
mutable hb_blob_t* m_harfbuzz_blob { nullptr };
|
mutable hb_blob_t* m_harfbuzz_blob { nullptr };
|
||||||
mutable hb_face_t* m_harfbuzz_face { nullptr };
|
mutable hb_face_t* m_harfbuzz_face { nullptr };
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,14 +4,15 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define AK_DONT_REPLACE_STD
|
#include <AK/LsanSuppressions.h>
|
||||||
|
|
||||||
#include <LibGfx/Font/FontDatabase.h>
|
#include <LibGfx/Font/FontDatabase.h>
|
||||||
#include <LibGfx/Font/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
|
#include <LibGfx/Font/TypefaceSkia.h>
|
||||||
|
|
||||||
#include <core/SkData.h>
|
#include <core/SkData.h>
|
||||||
#include <core/SkFontMgr.h>
|
#include <core/SkFontMgr.h>
|
||||||
#include <core/SkTypeface.h>
|
#include <include/core/SkRefCnt.h>
|
||||||
|
#include <include/core/SkTypeface.h>
|
||||||
#ifndef AK_OS_ANDROID
|
#ifndef AK_OS_ANDROID
|
||||||
# include <ports/SkFontMgr_fontconfig.h>
|
# include <ports/SkFontMgr_fontconfig.h>
|
||||||
#else
|
#else
|
||||||
|
@ -26,29 +27,94 @@ namespace Gfx {
|
||||||
|
|
||||||
static sk_sp<SkFontMgr> s_font_manager;
|
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) {
|
if (!s_font_manager) {
|
||||||
#ifdef AK_OS_MACOS
|
#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);
|
s_font_manager = SkFontMgr_New_CoreText(nullptr);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifndef AK_OS_ANDROID
|
#ifndef AK_OS_ANDROID
|
||||||
if (!s_font_manager)
|
if (!s_font_manager) {
|
||||||
s_font_manager = SkFontMgr_New_FontConfig(nullptr);
|
s_font_manager = SkFontMgr_New_FontConfig(nullptr);
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
s_font_manager = SkFontMgr_New_Android(nullptr);
|
s_font_manager = SkFontMgr_New_Android(nullptr);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_skia_typeface) {
|
auto data = SkData::MakeWithoutCopy(buffer.data(), buffer.size());
|
||||||
auto data = SkData::MakeWithoutCopy(buffer().data(), buffer().size());
|
auto skia_typeface = s_font_manager->makeFromData(data, ttc_index);
|
||||||
auto skia_typeface = s_font_manager->makeFromData(data, ttc_index());
|
|
||||||
if (!skia_typeface)
|
if (!skia_typeface) {
|
||||||
VERIFY_NOT_REACHED();
|
return Error::from_string_literal("Failed to load typeface from buffer");
|
||||||
m_skia_typeface = *skia_typeface;
|
}
|
||||||
|
|
||||||
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
43
Userland/Libraries/LibGfx/Font/TypefaceSkia.h
Normal file
43
Userland/Libraries/LibGfx/Font/TypefaceSkia.h
Normal 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 };
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -9,8 +9,8 @@
|
||||||
#include <AK/IntegralMath.h>
|
#include <AK/IntegralMath.h>
|
||||||
#include <LibCompress/Zlib.h>
|
#include <LibCompress/Zlib.h>
|
||||||
#include <LibCore/Resource.h>
|
#include <LibCore/Resource.h>
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
|
||||||
#include <LibGfx/Font/WOFF/Loader.h>
|
#include <LibGfx/Font/WOFF/Loader.h>
|
||||||
|
#include <LibGfx/FourCC.h>
|
||||||
|
|
||||||
namespace WOFF {
|
namespace WOFF {
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ static_assert(AssertSize<Header, 44>());
|
||||||
|
|
||||||
// https://www.w3.org/TR/WOFF/#TableDirectory
|
// https://www.w3.org/TR/WOFF/#TableDirectory
|
||||||
struct [[gnu::packed]] TableDirectoryEntry {
|
struct [[gnu::packed]] TableDirectoryEntry {
|
||||||
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> offset; // Offset to the data, from beginning of WOFF file.
|
||||||
BigEndian<u32> comp_length; // Length of the compressed data, excluding padding.
|
BigEndian<u32> comp_length; // Length of the compressed data, excluding padding.
|
||||||
BigEndian<u32> orig_length; // Length of the uncompressed table, excluding padding.
|
BigEndian<u32> orig_length; // Length of the uncompressed table, excluding padding.
|
||||||
|
@ -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));
|
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);
|
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);
|
FixedMemoryStream stream(buffer);
|
||||||
auto header = TRY(stream.read_value<Header>());
|
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.)
|
// (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.
|
// 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())
|
if (header.length > buffer.size())
|
||||||
return Error::from_string_literal("Invalid WOFF length");
|
return Error::from_string_literal("Invalid WOFF length");
|
||||||
if (header.num_tables == 0 || header.num_tables > NumericLimits<u16>::max() / 16)
|
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));
|
auto font_buffer = TRY(ByteBuffer::create_zeroed(header.total_sfnt_size));
|
||||||
|
|
||||||
u16 search_range = pow_2_less_than_or_equal(header.num_tables);
|
u16 search_range = pow_2_less_than_or_equal(header.num_tables);
|
||||||
OpenType::TableDirectory table_directory {
|
TableDirectory table_directory {
|
||||||
.sfnt_version = header.flavor,
|
.sfnt_version = header.flavor,
|
||||||
.num_tables = header.num_tables,
|
.num_tables = header.num_tables,
|
||||||
.search_range = search_range * 16,
|
.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));
|
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) {
|
for (size_t i = 0; i < header.num_tables; ++i) {
|
||||||
auto entry = TRY(stream.read_value<TableDirectoryEntry>());
|
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);
|
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);
|
size_t table_directory_offset = sizeof(TableDirectory) + i * sizeof(TableRecord);
|
||||||
OpenType::TableRecord table_record {
|
TableRecord table_record {
|
||||||
.table_tag = entry.tag,
|
.table_tag = entry.tag,
|
||||||
.checksum = entry.orig_checksum,
|
.checksum = entry.orig_checksum,
|
||||||
.offset = font_buffer_offset,
|
.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");
|
return Error::from_string_literal("Invalid WOFF total sfnt size");
|
||||||
|
|
||||||
auto font_data = Gfx::FontData::create_from_byte_buffer(move(font_buffer));
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
|
|
||||||
namespace WOFF {
|
namespace WOFF {
|
||||||
|
|
||||||
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_resource(Core::Resource const&, unsigned index = 0);
|
ErrorOr<NonnullRefPtr<Gfx::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_externally_owned_memory(ReadonlyBytes bytes, unsigned index = 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
#include <LibGfx/Font/WOFF2/Loader.h>
|
#include <LibGfx/Font/WOFF2/Loader.h>
|
||||||
#include <woff2/decode.h>
|
#include <woff2/decode.h>
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ private:
|
||||||
ByteBuffer& m_buffer;
|
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 ttf_buffer = TRY(ByteBuffer::create_uninitialized(0));
|
||||||
auto output = WOFF2ByteBufferOut { ttf_buffer };
|
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 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;
|
return input_font;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
|
|
||||||
#include <AK/OwnPtr.h>
|
#include <AK/OwnPtr.h>
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
|
|
||||||
namespace WOFF2 {
|
namespace WOFF2 {
|
||||||
|
|
||||||
ErrorOr<NonnullRefPtr<OpenType::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes);
|
ErrorOr<NonnullRefPtr<Gfx::Typeface>> try_load_from_externally_owned_memory(ReadonlyBytes);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibCore/Promise.h>
|
#include <LibCore/Promise.h>
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
|
||||||
#include <LibGfx/Font/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
#include <LibGfx/Font/WOFF/Loader.h>
|
#include <LibGfx/Font/WOFF/Loader.h>
|
||||||
#include <LibGfx/Font/WOFF2/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] {
|
Platform::EventLoopPlugin::the().deferred_invoke([&data, promise] {
|
||||||
// FIXME: This should be de-duplicated with StyleComputer::FontLoader::try_load_font
|
// 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.
|
// 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()) {
|
if (!ttf.is_error()) {
|
||||||
promise->resolve(ttf.release_value());
|
promise->resolve(ttf.release_value());
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
#include <LibGfx/Font/Font.h>
|
#include <LibGfx/Font/Font.h>
|
||||||
#include <LibGfx/Font/FontDatabase.h>
|
#include <LibGfx/Font/FontDatabase.h>
|
||||||
#include <LibGfx/Font/FontStyleMapping.h>
|
#include <LibGfx/Font/FontStyleMapping.h>
|
||||||
#include <LibGfx/Font/OpenType/Typeface.h>
|
|
||||||
#include <LibGfx/Font/ScaledFont.h>
|
#include <LibGfx/Font/ScaledFont.h>
|
||||||
#include <LibGfx/Font/Typeface.h>
|
#include <LibGfx/Font/Typeface.h>
|
||||||
#include <LibGfx/Font/WOFF/Loader.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.has_value()) {
|
||||||
if (mime_type->essence() == "font/ttf"sv || mime_type->essence() == "application/x-font-ttf"sv) {
|
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;
|
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())
|
if (!ttf.is_error())
|
||||||
return ttf.release_value();
|
return ttf.release_value();
|
||||||
return Error::from_string_literal("Automatic format detection failed");
|
return Error::from_string_literal("Automatic format detection failed");
|
||||||
|
|
Loading…
Reference in a new issue