From d4b5fc84b00650a9b666caca531df4d8190ea456 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 5 Oct 2026 00:53:28 +0200 Subject: [PATCH 1/2] test: share SFNT fixtures and consolidate reencode checks --- test/src/internal/font/font_file.cpp | 97 ++------------- test/src/internal/font/sfnt_font.cpp | 144 ++-------------------- test/src/internal/font/sfnt_test_util.hpp | 133 ++++++++++++++++++++ test/src/internal/font/sfnt_transform.cpp | 113 ++++------------- test/src/internal/pdf/pdf_font.cpp | 114 ++--------------- 5 files changed, 192 insertions(+), 409 deletions(-) create mode 100644 test/src/internal/font/sfnt_test_util.hpp diff --git a/test/src/internal/font/font_file.cpp b/test/src/internal/font/font_file.cpp index 8c6017682..9305637d4 100644 --- a/test/src/internal/font/font_file.cpp +++ b/test/src/internal/font/font_file.cpp @@ -1,3 +1,5 @@ +#include "sfnt_test_util.hpp" + #include #include @@ -25,95 +27,18 @@ using namespace odr::internal::font; namespace { -namespace bs = odr::internal::util::byte_string; - -std::string head_table() { - std::string t(54, '\0'); - t[18] = 0x03; // unitsPerEm = 1000 - t[19] = static_cast(0xe8); - return t; -} - -std::string maxp_table(std::uint16_t glyphs) { - std::string t; - bs::put_u32_be(t, 0x00010000); - bs::put_u16_be(t, glyphs); - t.resize(32, '\0'); - return t; -} - -std::string hhea_table(std::uint16_t metrics) { - std::string t(36, '\0'); - t[34] = static_cast(metrics >> 8); - t[35] = static_cast(metrics & 0xff); - return t; -} - -std::string hmtx_table(const std::vector &advances) { - std::string t; - for (const std::uint16_t a : advances) { - bs::put_u16_be(t, a); - bs::put_u16_be(t, 0); - } - return t; -} +using namespace odr::test::font; -std::string cmap_table() { - std::string sub; - bs::put_u16_be(sub, 4); - bs::put_u16_be(sub, 32); - bs::put_u16_be(sub, 0); - bs::put_u16_be(sub, 4); - bs::put_u16_be(sub, 0); - bs::put_u16_be(sub, 0); - bs::put_u16_be(sub, 0); - bs::put_u16_be(sub, 'C'); // endCode[0] - bs::put_u16_be(sub, 0xffff); // endCode[1] - bs::put_u16_be(sub, 0); - bs::put_u16_be(sub, 'A'); // startCode[0] - bs::put_u16_be(sub, 0xffff); // startCode[1] - bs::put_u16_be( - sub, static_cast(1 - 'A')); // idDelta[0]: A->1,B->2,C->3 - bs::put_u16_be(sub, 1); // idDelta[1] - bs::put_u16_be(sub, 0); - bs::put_u16_be(sub, 0); - - std::string t; - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 3); - bs::put_u16_be(t, 1); - bs::put_u32_be(t, 12); - t += sub; - return t; -} - -std::string name_table(const std::string &ascii) { - std::string strings; - for (const char c : ascii) { - bs::put_u16_be(strings, static_cast(c)); - } - std::string t; - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 18); - bs::put_u16_be(t, 3); - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 0x409); - bs::put_u16_be(t, 6); - bs::put_u16_be(t, static_cast(strings.size())); - bs::put_u16_be(t, 0); - t += strings; - return t; -} +namespace bs = odr::internal::util::byte_string; std::string sample_ttf() { - return build_sfnt(0x00010000, {{"cmap", cmap_table()}, - {"head", head_table()}, - {"hhea", hhea_table(4)}, - {"hmtx", hmtx_table({500, 600, 700, 800})}, - {"maxp", maxp_table(4)}, - {"name", name_table("TestFont")}}); + return build_sfnt(0x00010000, + {{"cmap", cmap_table(3, 1, cmap_format4('A', 3))}, + {"head", head_table()}, + {"hhea", hhea_table(4)}, + {"hmtx", hmtx_table({500, 600, 700, 800})}, + {"maxp", maxp_table(4)}, + {"name", name_table("TestFont")}}); } } // namespace diff --git a/test/src/internal/font/sfnt_font.cpp b/test/src/internal/font/sfnt_font.cpp index d167ea362..894f237c3 100644 --- a/test/src/internal/font/sfnt_font.cpp +++ b/test/src/internal/font/sfnt_font.cpp @@ -1,3 +1,5 @@ +#include "sfnt_test_util.hpp" + #include #include @@ -18,6 +20,8 @@ using namespace odr::internal::font::sfnt; namespace { +using namespace odr::test::font; + namespace bs = odr::internal::util::byte_string; /// Parse a font from its in-memory bytes. @@ -25,70 +29,6 @@ SfntFont sfnt_font_from_string(std::string bytes) { return SfntFont(std::move(bytes)); } -/// A `head` table: only unitsPerEm (offset 18) and the bbox (36..42) are read. -std::string head_table() { - std::string t(54, '\0'); - const auto u16 = [&](std::size_t o, std::uint16_t v) { - t[o] = static_cast(v >> 8); - t[o + 1] = static_cast(v & 0xff); - }; - u16(18, 1000); // unitsPerEm - u16(36, static_cast(-100)); // xMin - u16(38, static_cast(-200)); // yMin - u16(40, 900); // xMax - u16(42, 800); // yMax - return t; -} - -std::string maxp_table(const std::uint16_t glyphs) { - std::string t; - bs::put_u32_be(t, 0x00010000); // version 1.0 - bs::put_u16_be(t, glyphs); - t.resize(32, '\0'); - return t; -} - -std::string hhea_table(const std::uint16_t number_of_h_metrics) { - std::string t(36, '\0'); - t[34] = static_cast(number_of_h_metrics >> 8); - t[35] = static_cast(number_of_h_metrics & 0xff); - return t; -} - -std::string hmtx_table(const std::vector &advances) { - std::string t; - for (const std::uint16_t a : advances) { - bs::put_u16_be(t, a); // advanceWidth - bs::put_u16_be(t, 0); // leftSideBearing - } - return t; -} - -/// Format-4 subtable mapping the contiguous run [start, start+count) to glyph -/// ids [1, count] via a single idDelta segment, plus the required terminator. -std::string cmap_format4(const char16_t start, const std::uint16_t count) { - std::string t; - bs::put_u16_be(t, 4); // format - bs::put_u16_be(t, 32); // length - bs::put_u16_be(t, 0); // language - bs::put_u16_be(t, 4); // segCountX2 (2 segments) - bs::put_u16_be(t, 0); // searchRange - bs::put_u16_be(t, 0); // entrySelector - bs::put_u16_be(t, 0); // rangeShift - bs::put_u16_be(t, - static_cast(start + count - 1)); // endCode[0] - bs::put_u16_be(t, 0xffff); // endCode[1] - bs::put_u16_be(t, 0); // reservedPad - bs::put_u16_be(t, start); // startCode[0] - bs::put_u16_be(t, 0xffff); // startCode[1] - bs::put_u16_be( - t, static_cast(1 - start)); // idDelta[0] -> gid 1..count - bs::put_u16_be(t, 1); // idDelta[1] - bs::put_u16_be(t, 0); // idRangeOffset[0] - bs::put_u16_be(t, 0); // idRangeOffset[1] - return t; -} - /// Format-4 subtable mapping [start, start+count) to glyph ids [1, count] via a /// non-zero idRangeOffset[0] that indexes the glyphIdArray, plus the /// terminator. idRangeOffset[0] == 4 points at glyphIdArray[0] (past the one @@ -134,70 +74,10 @@ std::string cmap_format12(const char32_t start, const std::uint32_t count) { return t; } -std::string cmap_table(const std::uint16_t platform, - const std::uint16_t encoding, - const std::string &subtable) { - std::string t; - bs::put_u16_be(t, 0); // version - bs::put_u16_be(t, 1); // numTables - bs::put_u16_be(t, platform); - bs::put_u16_be(t, encoding); - bs::put_u32_be(t, 12); // offset to the single subtable - t += subtable; - return t; -} - -// A `name` table with a single PostScript-name (id 6) record, UTF-16BE. -std::string name_table(const std::string &ascii) { - std::string strings; - for (const char c : ascii) { - bs::put_u16_be(strings, static_cast(c)); - } - std::string t; - bs::put_u16_be(t, 0); // format - bs::put_u16_be(t, 1); // count - bs::put_u16_be(t, 18); // stringOffset (6 header + 12 record) - bs::put_u16_be(t, 3); // platformID (Windows) - bs::put_u16_be(t, 1); // encodingID - bs::put_u16_be(t, 0x409); // languageID - bs::put_u16_be(t, 6); // nameID (PostScript) - bs::put_u16_be(t, static_cast(strings.size())); - bs::put_u16_be(t, 0); // offset within string storage - t += strings; - return t; -} - -// Assemble a full SFNT from named tables, computing the table directory. -std::string -build_sfnt(const std::vector> &tables) { - const auto count = static_cast(tables.size()); - std::string out; - bs::put_u32_be(out, 0x00010000); // sfntVersion (TrueType) - bs::put_u16_be(out, count); - bs::put_u16_be(out, 0); // searchRange - bs::put_u16_be(out, 0); // entrySelector - bs::put_u16_be(out, 0); // rangeShift - - std::uint32_t offset = 12 + count * 16U; - std::string body; - for (const auto &[tag, data] : tables) { - out += tag; - bs::put_u32_be(out, 0); // checksum (not validated by the reader) - bs::put_u32_be(out, offset); - bs::put_u32_be(out, static_cast(data.size())); - body += data; - while (body.size() % 4 != 0) { - body += '\0'; - } - offset = 12 + count * 16U + static_cast(body.size()); - } - return out + body; -} - std::string sample_font(const std::string &cmap) { // Tables are stored in tag-sorted order, as required by the spec. - return build_sfnt({{"cmap", cmap}, - {"head", head_table()}, + return sfnt_bytes({{"cmap", cmap}, + {"head", head_table({-100, -200, 900, 800})}, {"hhea", hhea_table(5)}, {"hmtx", hmtx_table({500, 600, 700, 222, 333})}, {"maxp", maxp_table(5)}, @@ -294,8 +174,8 @@ TEST(SfntFont, write_pads_a_short_hmtx) { // Two longHorMetrics for five glyphs, and none of the three leftSideBearings // that must follow them. const SfntFont font = sfnt_font_from_string( - build_sfnt({{"cmap", cmap_table(3, 1, cmap_format4('A', 3))}, - {"head", head_table()}, + sfnt_bytes({{"cmap", cmap_table(3, 1, cmap_format4('A', 3))}, + {"head", head_table({-100, -200, 900, 800})}, {"hhea", hhea_table(2)}, {"hmtx", hmtx_table({500, 600})}, {"maxp", maxp_table(5)}})); @@ -328,15 +208,15 @@ TEST(SfntFont, rejects_invalid_headers_and_clips_quirky_ranges) { bs::write_u32_be(bytes, 20, static_cast(bytes.size() + 1)); EXPECT_THROW(SfntFont{bytes}, std::runtime_error); - std::string head = head_table(); + std::string head = head_table({-100, -200, 900, 800}); bs::write_u16_be(head, 18, 0); - EXPECT_THROW(SfntFont(build_sfnt({{"head", head}})), std::runtime_error); + EXPECT_THROW(SfntFont(sfnt_bytes({{"head", head}})), std::runtime_error); std::string name = name_table("A"); bs::write_u16_be(name, 14, 4); - EXPECT_EQ(SfntFont(build_sfnt({{"name", name}})).name(), "A"); + EXPECT_EQ(SfntFont(sfnt_bytes({{"name", name}})).name(), "A"); bs::write_u16_be(name, 14, 1); - EXPECT_EQ(SfntFont(build_sfnt({{"name", name}})).name(), ""); + EXPECT_EQ(SfntFont(sfnt_bytes({{"name", name}})).name(), ""); } TEST(SfntFont, drops_an_invalid_cmap_and_keeps_the_font) { diff --git a/test/src/internal/font/sfnt_test_util.hpp b/test/src/internal/font/sfnt_test_util.hpp new file mode 100644 index 000000000..f7411c384 --- /dev/null +++ b/test/src/internal/font/sfnt_test_util.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +namespace odr::test::font { + +namespace bs = odr::internal::util::byte_string; + +// Raw SFNT fixtures independent of the production serializers. + +inline std::string head_table(const odr::FontBBox bbox = {}) { + std::string table(54, '\0'); + bs::write_u16_be(table, 18, 1000); + bs::write_u16_be(table, 36, static_cast(bbox.x_min)); + bs::write_u16_be(table, 38, static_cast(bbox.y_min)); + bs::write_u16_be(table, 40, static_cast(bbox.x_max)); + bs::write_u16_be(table, 42, static_cast(bbox.y_max)); + return table; +} + +inline std::string maxp_table(const std::uint16_t glyphs) { + std::string t; + bs::put_u32_be(t, 0x00010000); // version 1.0 + bs::put_u16_be(t, glyphs); + t.resize(32, '\0'); + return t; +} + +inline std::string hhea_table(const std::uint16_t number_of_h_metrics) { + std::string t(36, '\0'); + t[34] = static_cast(number_of_h_metrics >> 8); + t[35] = static_cast(number_of_h_metrics & 0xff); + return t; +} + +inline std::string hmtx_table(const std::vector &advances) { + std::string t; + for (const std::uint16_t a : advances) { + bs::put_u16_be(t, a); // advanceWidth + bs::put_u16_be(t, 0); // leftSideBearing + } + return t; +} + +inline std::string cmap_format4(const char16_t start, + const std::uint16_t count) { + std::string t; + bs::put_u16_be(t, 4); // format + bs::put_u16_be(t, 32); // length + bs::put_u16_be(t, 0); // language + bs::put_u16_be(t, 4); // segCountX2 (2 segments) + bs::put_u16_be(t, 0); // searchRange + bs::put_u16_be(t, 0); // entrySelector + bs::put_u16_be(t, 0); // rangeShift + bs::put_u16_be(t, + static_cast(start + count - 1)); // endCode[0] + bs::put_u16_be(t, 0xffff); // endCode[1] + bs::put_u16_be(t, 0); // reservedPad + bs::put_u16_be(t, start); // startCode[0] + bs::put_u16_be(t, 0xffff); // startCode[1] + bs::put_u16_be( + t, static_cast(1 - start)); // idDelta[0] -> gid 1..count + bs::put_u16_be(t, 1); // idDelta[1] + bs::put_u16_be(t, 0); // idRangeOffset[0] + bs::put_u16_be(t, 0); // idRangeOffset[1] + return t; +} + +inline std::string cmap_table(const std::uint16_t platform, + const std::uint16_t encoding, + const std::string &subtable) { + std::string t; + bs::put_u16_be(t, 0); // version + bs::put_u16_be(t, 1); // numTables + bs::put_u16_be(t, platform); + bs::put_u16_be(t, encoding); + bs::put_u32_be(t, 12); // offset to the single subtable + t += subtable; + return t; +} + +inline std::string name_table(const std::string &ascii) { + std::string strings; + for (const char c : ascii) { + bs::put_u16_be(strings, static_cast(c)); + } + std::string t; + bs::put_u16_be(t, 0); // format + bs::put_u16_be(t, 1); // count + bs::put_u16_be(t, 18); // stringOffset (6 header + 12 record) + bs::put_u16_be(t, 3); // platformID (Windows) + bs::put_u16_be(t, 1); // encodingID + bs::put_u16_be(t, 0x409); // languageID + bs::put_u16_be(t, 6); // nameID (PostScript) + bs::put_u16_be(t, static_cast(strings.size())); + bs::put_u16_be(t, 0); // offset within string storage + t += strings; + return t; +} + +inline std::string +sfnt_bytes(const std::vector> &tables) { + const auto count = static_cast(tables.size()); + std::string out; + bs::put_u32_be(out, 0x00010000); // sfntVersion (TrueType) + bs::put_u16_be(out, count); + bs::put_u16_be(out, 0); // searchRange + bs::put_u16_be(out, 0); // entrySelector + bs::put_u16_be(out, 0); // rangeShift + + std::uint32_t offset = 12 + count * 16U; + std::string body; + for (const auto &[tag, data] : tables) { + out += tag; + bs::put_u32_be(out, 0); // checksum (not validated by the reader) + bs::put_u32_be(out, offset); + bs::put_u32_be(out, static_cast(data.size())); + body += data; + while (body.size() % 4 != 0) { + body += '\0'; + } + offset = 12 + count * 16U + static_cast(body.size()); + } + return out + body; +} + +} // namespace odr::test::font diff --git a/test/src/internal/font/sfnt_transform.cpp b/test/src/internal/font/sfnt_transform.cpp index 5e6f426d1..89dc76425 100644 --- a/test/src/internal/font/sfnt_transform.cpp +++ b/test/src/internal/font/sfnt_transform.cpp @@ -1,3 +1,5 @@ +#include "sfnt_test_util.hpp" + #include #include @@ -16,6 +18,8 @@ using namespace odr::internal::font; namespace { +using namespace odr::test::font; + namespace bs = odr::internal::util::byte_string; /// Parse a font from its in-memory bytes. @@ -30,56 +34,6 @@ std::string reencoded(std::string bytes) { return font.write(); } -std::string head_table() { - std::string t(54, '\0'); - t[18] = 0x03; // unitsPerEm = 1000 (0x03E8) - t[19] = static_cast(0xe8); - return t; -} - -std::string maxp_table(const std::uint16_t glyphs) { - std::string t; - bs::put_u32_be(t, 0x00010000); - bs::put_u16_be(t, glyphs); - t.resize(32, '\0'); - return t; -} - -std::string hhea_table(const std::uint16_t number_of_h_metrics) { - std::string t(36, '\0'); - t[34] = static_cast(number_of_h_metrics >> 8); - t[35] = static_cast(number_of_h_metrics & 0xff); - return t; -} - -std::string hmtx_table(const std::vector &advances) { - std::string t; - for (const std::uint16_t a : advances) { - bs::put_u16_be(t, a); - bs::put_u16_be(t, 0); - } - return t; -} - -std::string name_table(const std::string &ascii) { - std::string strings; - for (const char c : ascii) { - bs::put_u16_be(strings, static_cast(c)); - } - std::string t; - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 18); - bs::put_u16_be(t, 3); - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 0x409); - bs::put_u16_be(t, 6); - bs::put_u16_be(t, static_cast(strings.size())); - bs::put_u16_be(t, 0); - t += strings; - return t; -} - // A 3-glyph TrueType font (no original cmap — the re-encode supplies one), // assembled through the library's own serializer. std::string sample_font(std::uint16_t glyphs = 3) { @@ -184,38 +138,31 @@ TEST(SfntTransform, serialize_cmap_format12_round_trips_beyond_bmp) { EXPECT_EQ(parsed.glyph_for_code_point(0xf0002), 0); // gap after the run } -TEST(SfntTransform, reencode_mutates_the_font_in_place) { +TEST(SfntTransform, reencode_mutates_and_round_trips) { sfnt::SfntFont font = parse(sample_font()); reencode_to_pua(font); - - // The mutated object itself reflects the new map — accessors read cmap(). - EXPECT_EQ(font.glyph_for_code_point(pua_code_point(1)), 1); - EXPECT_EQ(font.glyph_for_code_point(pua_code_point(2)), 2); - EXPECT_EQ(font.code_point_for_glyph(1), pua_code_point(1)); - EXPECT_EQ(font.code_point_for_glyph(2), pua_code_point(2)); -} - -TEST(SfntTransform, reencode_maps_every_glyph_to_pua_after_round_trip) { - const sfnt::SfntFont parsed = parse(reencoded(sample_font())); - - EXPECT_EQ(parsed.glyph_for_code_point(pua_code_point(1)), 1); - EXPECT_EQ(parsed.glyph_for_code_point(pua_code_point(2)), 2); - EXPECT_EQ(parsed.code_point_for_glyph(1), pua_code_point(1)); - EXPECT_EQ(parsed.code_point_for_glyph(2), pua_code_point(2)); + const auto check = [](const sfnt::SfntFont &mapped) { + EXPECT_EQ(mapped.glyph_for_code_point(pua_code_point(1)), 1); + EXPECT_EQ(mapped.glyph_for_code_point(pua_code_point(2)), 2); + EXPECT_EQ(mapped.code_point_for_glyph(1), pua_code_point(1)); + EXPECT_EQ(mapped.code_point_for_glyph(2), pua_code_point(2)); + }; + check(font); + check(parse(font.write())); } -TEST(SfntTransform, reencode_with_extra_adds_real_unicode_beside_pua) { +TEST(SfntTransform, reencode_with_extra_mutates_and_round_trips) { sfnt::SfntFont font = parse(sample_font()); - // Real Unicode 'A' -> glyph 1 and 'B' -> glyph 2, alongside the PUA range. - reencode_to_pua(font, {{U'A', 1}, {U'B', 2}}); - - // Real-Unicode entries resolve to their glyphs ... - EXPECT_EQ(font.glyph_for_code_point('A'), 1); - EXPECT_EQ(font.glyph_for_code_point('B'), 2); - // ... and the PUA fallback for every glyph is still present. - EXPECT_EQ(font.glyph_for_code_point(pua_code_point(0)), 0); - EXPECT_EQ(font.glyph_for_code_point(pua_code_point(1)), 1); - EXPECT_EQ(font.glyph_for_code_point(pua_code_point(2)), 2); + reencode_to_pua(font, {{U'A', 1}, {U'Z', 2}}); + const auto check = [](const sfnt::SfntFont &mapped) { + EXPECT_EQ(mapped.glyph_for_code_point('A'), 1); + EXPECT_EQ(mapped.glyph_for_code_point('Z'), 2); + EXPECT_EQ(mapped.glyph_for_code_point(pua_code_point(0)), 0); + EXPECT_EQ(mapped.glyph_for_code_point(pua_code_point(1)), 1); + EXPECT_EQ(mapped.glyph_for_code_point(pua_code_point(2)), 2); + }; + check(font); + check(parse(font.write())); } TEST(SfntTransform, reencode_drops_extra_entries_past_glyph_count) { @@ -231,18 +178,6 @@ TEST(SfntTransform, reencode_drops_extra_entries_past_glyph_count) { EXPECT_EQ(font.glyph_for_code_point(pua_code_point(2)), 2); } -TEST(SfntTransform, reencode_with_extra_round_trips_through_write) { - sfnt::SfntFont font = parse(sample_font()); - // 'A' is adjacent to nothing; 'Z' forces a second cmap segment past the PUA. - reencode_to_pua(font, {{U'A', 1}, {U'Z', 2}}); - - const sfnt::SfntFont parsed = parse(font.write()); - EXPECT_EQ(parsed.glyph_for_code_point('A'), 1); - EXPECT_EQ(parsed.glyph_for_code_point('Z'), 2); - EXPECT_EQ(parsed.glyph_for_code_point(pua_code_point(1)), 1); - EXPECT_EQ(parsed.glyph_for_code_point(pua_code_point(2)), 2); -} - TEST(SfntTransform, write_preserves_passthrough_tables_and_checksum) { const std::string out = reencoded(sample_font()); diff --git a/test/src/internal/pdf/pdf_font.cpp b/test/src/internal/pdf/pdf_font.cpp index d1ac99317..d3d10f8e9 100644 --- a/test/src/internal/pdf/pdf_font.cpp +++ b/test/src/internal/pdf/pdf_font.cpp @@ -1,3 +1,5 @@ +#include "../font/sfnt_test_util.hpp" + #include #include #include @@ -21,110 +23,18 @@ using odr::internal::pdf::Font; namespace { -namespace bs = odr::internal::util::byte_string; - -// A compact SFNT builder, just enough for an `SfntFont` that maps a contiguous -// run of code points to glyph ids 1..n (mirrors the font unit test's builder). +using namespace odr::test::font; -std::string head_table() { - std::string t(54, '\0'); - t[18] = 0x03; - t[19] = static_cast(0xe8); // unitsPerEm = 1000 - return t; -} - -std::string maxp_table(const std::uint16_t glyphs) { - std::string t; - bs::put_u32_be(t, 0x00010000); - bs::put_u16_be(t, glyphs); - t.resize(32, '\0'); - return t; -} - -std::string hhea_table(const std::uint16_t h_metrics) { - std::string t(36, '\0'); - t[34] = static_cast(h_metrics >> 8); - t[35] = static_cast(h_metrics & 0xff); - return t; -} - -std::string hmtx_table(const std::uint16_t count) { - std::string t; - for (std::uint16_t i = 0; i < count; ++i) { - bs::put_u16_be(t, 500); - bs::put_u16_be(t, 0); - } - return t; -} - -/// Format-4 (3,1) subtable mapping [start, start+count) to glyph ids [1, -/// count]. -std::string cmap_format4(const char16_t start, const std::uint16_t count) { - std::string t; - bs::put_u16_be(t, 4); - bs::put_u16_be(t, 32); - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 4); // segCountX2 - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 0); - bs::put_u16_be(t, - static_cast(start + count - 1)); // endCode[0] - bs::put_u16_be(t, 0xffff); - bs::put_u16_be(t, 0); - bs::put_u16_be(t, start); // startCode[0] - bs::put_u16_be(t, 0xffff); - bs::put_u16_be(t, static_cast(1 - start)); // idDelta[0] - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 0); // idRangeOffset[0] - bs::put_u16_be(t, 0); - return t; -} - -std::string cmap_table(const std::string &subtable, - const std::uint16_t encoding = 1 /* Unicode BMP */) { - std::string t; - bs::put_u16_be(t, 0); - bs::put_u16_be(t, 1); - bs::put_u16_be(t, 3); // Windows - bs::put_u16_be(t, encoding); - bs::put_u32_be(t, 12); - t += subtable; - return t; -} - -std::string -build_sfnt(const std::vector> &tables) { - const auto count = static_cast(tables.size()); - std::string out; - bs::put_u32_be(out, 0x00010000); - bs::put_u16_be(out, count); - bs::put_u16_be(out, 0); - bs::put_u16_be(out, 0); - bs::put_u16_be(out, 0); - std::uint32_t offset = 12 + count * 16U; - std::string body; - for (const auto &[tag, data] : tables) { - out += tag; - bs::put_u32_be(out, 0); - bs::put_u32_be(out, offset); - bs::put_u32_be(out, static_cast(data.size())); - body += data; - while (body.size() % 4 != 0) { - body += '\0'; - } - offset = 12 + count * 16U + static_cast(body.size()); - } - return out + body; -} +namespace bs = odr::internal::util::byte_string; /// A 5-glyph font whose `cmap` maps 'A','B','C' (U+0041..0043) to gids 1,2,3. std::shared_ptr sample_font() { - std::string sfnt = build_sfnt({{"cmap", cmap_table(cmap_format4('A', 3))}, - {"head", head_table()}, - {"hhea", hhea_table(5)}, - {"hmtx", hmtx_table(5)}, - {"maxp", maxp_table(5)}}); + std::string sfnt = + sfnt_bytes({{"cmap", cmap_table(3, 1, cmap_format4('A', 3))}, + {"head", head_table()}, + {"hhea", hhea_table(5)}, + {"hmtx", hmtx_table(std::vector(5, 500))}, + {"maxp", maxp_table(5)}}); return std::make_shared(std::move(sfnt)); } @@ -217,10 +127,10 @@ TEST(PdfFont, simple_font_glyph_for_code_via_symbol_cmap) { // A (3,0) subtable keys byte code c at U+F000 + c (ISO 32000-1 9.6.6.4). Font font; font.embedded_font = std::make_shared( - build_sfnt({{"cmap", cmap_table(cmap_format4(0xf003, 2), 0)}, + sfnt_bytes({{"cmap", cmap_table(3, 0, cmap_format4(0xf003, 2))}, {"head", head_table()}, {"hhea", hhea_table(5)}, - {"hmtx", hmtx_table(5)}, + {"hmtx", hmtx_table(std::vector(5, 500))}, {"maxp", maxp_table(5)}})); EXPECT_EQ(font.glyph_for_code(3), 1); From d07b544210b24506c545182813930b818c0a8e03 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 5 Oct 2026 15:17:16 +0200 Subject: [PATCH 2/2] test: include the SFNT fixtures by their path The other test utilities, like type1_test_util.hpp, are included by their path below the library headers, not by a relative quoted path. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01Gb1fLafqqzehpqPuU6uBfn --- test/src/internal/font/font_file.cpp | 4 ++-- test/src/internal/font/sfnt_font.cpp | 4 ++-- test/src/internal/font/sfnt_transform.cpp | 4 ++-- test/src/internal/pdf/pdf_font.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/src/internal/font/font_file.cpp b/test/src/internal/font/font_file.cpp index 9305637d4..d89472f3d 100644 --- a/test/src/internal/font/font_file.cpp +++ b/test/src/internal/font/font_file.cpp @@ -1,5 +1,3 @@ -#include "sfnt_test_util.hpp" - #include #include @@ -11,6 +9,8 @@ #include #include +#include + #include #include diff --git a/test/src/internal/font/sfnt_font.cpp b/test/src/internal/font/sfnt_font.cpp index 894f237c3..f2227ddf4 100644 --- a/test/src/internal/font/sfnt_font.cpp +++ b/test/src/internal/font/sfnt_font.cpp @@ -1,10 +1,10 @@ -#include "sfnt_test_util.hpp" - #include #include #include +#include + #include #include diff --git a/test/src/internal/font/sfnt_transform.cpp b/test/src/internal/font/sfnt_transform.cpp index 89dc76425..d161baf70 100644 --- a/test/src/internal/font/sfnt_transform.cpp +++ b/test/src/internal/font/sfnt_transform.cpp @@ -1,10 +1,10 @@ -#include "sfnt_test_util.hpp" - #include #include #include +#include + #include #include diff --git a/test/src/internal/pdf/pdf_font.cpp b/test/src/internal/pdf/pdf_font.cpp index d3d10f8e9..99a198d01 100644 --- a/test/src/internal/pdf/pdf_font.cpp +++ b/test/src/internal/pdf/pdf_font.cpp @@ -1,5 +1,3 @@ -#include "../font/sfnt_test_util.hpp" - #include #include #include @@ -9,6 +7,8 @@ #include #include +#include + #include #include