Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ift/common/font_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ struct CompareTableOffsets {
class FontHelper {
public:
constexpr static hb_tag_t kIFT = HB_TAG('I', 'F', 'T', ' ');
constexpr static hb_tag_t kIFTX = HB_TAG('I', 'F', 'T', 'X');
constexpr static hb_tag_t kLoca = HB_TAG('l', 'o', 'c', 'a');
constexpr static hb_tag_t kGlyf = HB_TAG('g', 'l', 'y', 'f');
constexpr static hb_tag_t kHead = HB_TAG('h', 'e', 'a', 'd');
Expand Down
12 changes: 12 additions & 0 deletions ift/config/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ cc_library(
]
)

cc_test(
name = "config_compiler_test",
size = "small",
srcs = [
"config_compiler_test.cc",
],
deps = [
":config_compiler",
"@googletest//:gtest_main",
],
)

cc_test(
name = "auto_segmenter_config_test",
size = "small",
Expand Down
13 changes: 12 additions & 1 deletion ift/config/config_compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ static StatusOr<design_space_t> ToDesignSpace(const DesignSpace& proto) {
return result;
}

static ActivationCondition FromProto(const ActivationConditionProto& condition) {
static ActivationCondition FromProto(
const ActivationConditionProto& condition) {
// TODO(garretrieger): once glyph segmentation activation conditions can
// support features copy those here.
std::vector<SegmentSet> groups;
Expand Down Expand Up @@ -150,6 +151,16 @@ Status ConfigCompiler::Configure(const SegmentationPlan& plan,
}
compiler.SetUsePrefetchLists(plan.use_prefetch_lists());

if (plan.has_advanced_settings()) {
const auto& advanced = plan.advanced_settings();
if (!advanced.override_url_template_prefix().empty()) {
std::vector<uint8_t> prefix(
advanced.override_url_template_prefix().begin(),
advanced.override_url_template_prefix().end());
compiler.SetOverrideUrlTemplatePrefix(prefix);
}
}

// Check for unsupported settings
if (plan.include_all_segment_patches()) {
return absl::UnimplementedError(
Expand Down
28 changes: 28 additions & 0 deletions ift/config/config_compiler_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "ift/config/config_compiler.h"

#include <cstdint>
#include <vector>

#include "gtest/gtest.h"
#include "ift/config/segmentation_plan.pb.h"
#include "ift/encoder/compiler.h"

using ift::encoder::Compiler;

namespace ift::config {

TEST(ConfigCompilerTest, ConfigureOverrideUrlTemplatePrefix) {
Compiler compiler;
SegmentationPlan plan;
std::string prefix_str = "https://example.com/";
plan.mutable_advanced_settings()->set_override_url_template_prefix(
prefix_str);

absl::Status status = ConfigCompiler::Configure(plan, compiler);
ASSERT_TRUE(status.ok()) << status;

std::vector<uint8_t> expected(prefix_str.begin(), prefix_str.end());
EXPECT_EQ(compiler.override_url_template_prefix(), expected);
}

} // namespace ift::config
22 changes: 21 additions & 1 deletion ift/config/segmentation_plan.proto
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,27 @@ message SegmentationPlan {
// for in a single jump.
repeated DesignSpace non_glyph_design_space_segmentation = 15;

// next = 17
// These settings are for advanced usage and typically shouldn't need to be configured.
AdvancedSettings advanced_settings = 17;

// next = 18
}

message AdvancedSettings {
// In the output font the URL template byte string is replaced with these bytes.
//
// The template bytes should not include the patch file extension (.ift_tk, .ift_gk),
// as that will be added automatically during compilation based on the type of patch.
//
// If unset the compiler uses the byte string [128] as the URL template based which sets the
// patch file name to the id32 value for each patch.
//
// URL template specification: https://w3c.github.io/IFT/Overview.html#url-templates
// It's up to the caller to ensure the provided byte string is a valid URL template.
//
// Note: patches will be output to directory specified by the provided template.
// it's up to the caller to ensure the location exists.
bytes override_url_template_prefix = 1;
}

// Activated when at least one set in every group is matched and all required_features match.
Expand Down
12 changes: 6 additions & 6 deletions ift/dep_graph/dependency_graph_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,8 @@ TEST_F(DependencyGraphTest, StronglyConnectedComponents_TopologicalSorting) {
EXPECT_LT(liga_f, gffi);
}

TEST_F(DependencyGraphTest, StronglyConnectedComponents_TopologicalSorting_InitFontFilter) {
TEST_F(DependencyGraphTest,
StronglyConnectedComponents_TopologicalSorting_InitFontFilter) {
SubsetDefinition liga;
liga.feature_tags = {HB_TAG('l', 'i', 'g', 'a')};
SubsetDefinition dlig;
Expand All @@ -550,7 +551,8 @@ TEST_F(DependencyGraphTest, StronglyConnectedComponents_TopologicalSorting_InitF
Node::Feature(HB_TAG('d', 'l', 'i', 'g'))));
}

TEST_F(DependencyGraphTest, StronglyConnectedComponents_TopologicalSorting_InitFontFeatures) {
TEST_F(DependencyGraphTest,
StronglyConnectedComponents_TopologicalSorting_InitFontFeatures) {
Reconfigure(WithDefaultFeatures({}),
{
/* 0 */ {{'a'}, ProbabilityBound::Zero()},
Expand Down Expand Up @@ -599,10 +601,8 @@ TEST_F(DependencyGraphTest,
uint32_t num_cycles = 0;
for (const auto& scc : *sccs_or) {
if (scc.size() > 1) {
EXPECT_THAT(scc, UnorderedElementsAre(
Node::Glyph(129 /* AE */),
Node::Glyph(811 /* AEacute */)
));
EXPECT_THAT(scc, UnorderedElementsAre(Node::Glyph(129 /* AE */),
Node::Glyph(811 /* AEacute */)));
num_cycles++;
}
}
Expand Down
20 changes: 16 additions & 4 deletions ift/encoder/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ class Compiler {

void SetWoff2Encode(bool value) { this->woff2_encode_ = value; }

void SetOverrideUrlTemplatePrefix(const std::vector<uint8_t>& prefix) {
override_url_template_prefix_ = prefix;
}

const std::vector<uint8_t>& override_url_template_prefix() const {
return override_url_template_prefix_;
}

/*
* Adds a segmentation of glyph data.
*
Expand Down Expand Up @@ -247,17 +255,20 @@ class Compiler {
constexpr uint8_t insert_id_op_code = 128;

std::vector<uint8_t> out;
if (!override_url_template_prefix_.empty()) {
out = override_url_template_prefix_;
} else {
out.push_back(insert_id_op_code);
}

if (patch_set_id == 0) {
// patch_set_id 0 is always used for table keyed patches
out.push_back(insert_id_op_code);
AppendLiteralToTemplate(".ift_tk", out);
return out;
}

// All other ids are for glyph keyed.
AppendLiteralToTemplate(absl::StrCat(patch_set_id, "_"), out);
out.push_back(insert_id_op_code);
AppendLiteralToTemplate(".ift_gk", out);
AppendLiteralToTemplate(absl::StrCat(".", patch_set_id, ".ift_gk"), out);
return out;
}

Expand Down Expand Up @@ -364,6 +375,7 @@ class Compiler {
uint32_t next_id_ = 0;
bool use_prefetch_lists_ = false;
bool woff2_encode_ = false;
std::vector<uint8_t> override_url_template_prefix_;

struct ProcessingContext {
ProcessingContext(uint32_t next_id)
Expand Down
38 changes: 34 additions & 4 deletions ift/encoder/compiler_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -938,11 +938,11 @@ TEST_F(CompilerTest, Encode_ComplicatedActivationConditions) {
0x0, 0x0, 0x0, 0x0, // compat id[3]
0x03, // default patch format = glyph keyed
0x00, 0x00, 0x07, // entry count = 7
0x00, 0x00, 0x00, 0x2F, // entries offset
0x00, 0x00, 0x00, 0x2E, // entries offset
0x00, 0x00, 0x00, 0x00, // string data offset (NULL)

0x00, 0x0C, // uri template length
2, '1', '_', 128, 7, '.', 'i', 'f', 't', '_', 'g', 'k', // uri template
0x00, 0x0B, // uri template length
128, 9, '.', '1', '.', 'i', 'f', 't', '_', 'g', 'k', // uri template

// entry[0] {{2}} -> 2,
0b00010100, // format (id delta, code points no bias)
Expand Down Expand Up @@ -983,7 +983,7 @@ TEST_F(CompilerTest, Encode_ComplicatedActivationConditions) {
0xff, 0xff, 0xfe // delta -1, id = 6
};

ASSERT_EQ(ift_table.span(), absl::Span<const uint8_t>(expected_format2, 99));
ASSERT_EQ(ift_table.span(), absl::Span<const uint8_t>(expected_format2, 98));
}

TEST_F(CompilerTest, RoundTripWoff2) {
Expand All @@ -1000,4 +1000,34 @@ TEST_F(CompilerTest, RoundTripWoff2_Fails) {
ASSERT_TRUE(absl::IsInternal(ttf.status())) << ttf.status();
}

TEST_F(CompilerTest, UrlTemplateOverride) {
Compiler compiler;
hb_face_t* face = font.reference_face();
compiler.SetFace(face);

auto s = compiler.SetInitSubset(IntSet{'a', 'd'});
ASSERT_TRUE(s.ok()) << s;
compiler.AddNonGlyphDataSegment(IntSet{'b', 'c'});

// 3 (length) + 'p' + 'r' + 'e' + 128 (id opcode)
std::vector<uint8_t> prefix = {3, 'p', 'r', 'e', 133};
compiler.SetOverrideUrlTemplatePrefix(prefix);

auto encoding = compiler.Compile();
hb_face_destroy(face);

ASSERT_TRUE(encoding.ok()) << encoding.status();
auto encoded_face = encoding->init_font.face();

auto ift_table = FontHelper::TableData(encoded_face.get(), FontHelper::kIFT);
if (ift_table.empty()) {
ift_table = FontHelper::TableData(encoded_face.get(), FontHelper::kIFTX);
}
ASSERT_FALSE(ift_table.empty());

std::string expected_template = {3, 'p', 'r', 'e', (char)133, 7, '.',
'i', 'f', 't', '_', 't', 'k'};
ASSERT_TRUE(ift_table.str().find(expected_template) != std::string::npos);
}

} // namespace ift::encoder
19 changes: 10 additions & 9 deletions ift/encoder/dependency_closure_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -317,9 +317,10 @@ TEST_F(DependencyClosureTest, ExtractAllGlyphConditions_FullFont) {
uint32_t parenleft = cp_to_seg.at('(');
uint32_t parenright = cp_to_seg.at(')');

EXPECT_EQ(conditions->at(12 /* parenleft */).ToString(),
// '(' is accesible from either '(' or ')' due to unicode mirroring.
absl::StrCat("if ((s", parenleft, " OR s", parenright,")) then p0"));
EXPECT_EQ(
conditions->at(12 /* parenleft */).ToString(),
// '(' is accesible from either '(' or ')' due to unicode mirroring.
absl::StrCat("if ((s", parenleft, " OR s", parenright, ")) then p0"));

// small caps AE is accesible via numerous pathways and forms a complex
// composite condition (smcp, c2sc, and glyph component substitutions)
Expand All @@ -329,12 +330,12 @@ TEST_F(DependencyClosureTest, ExtractAllGlyphConditions_FullFont) {
uint32_t aeacute = cp_to_seg.at(0x1FD);
uint32_t smcp = layout_to_seg.at(HB_TAG('s', 'm', 'c', 'p'));
uint32_t c2sc = layout_to_seg.at(HB_TAG('c', '2', 's', 'c'));
EXPECT_EQ(conditions->at(627 /* small caps AE */).ToString(),
absl::StrCat(
"if ((s", AE, " OR s", ae," OR s", AEacute, " OR s", aeacute, ") ",
"AND (s", AE, " OR s", AEacute, " OR s", smcp, ") ",
"AND (s", ae," OR s", aeacute," OR s", c2sc,") ",
"AND (s", c2sc," OR s", smcp, ")) then p0"));
EXPECT_EQ(
conditions->at(627 /* small caps AE */).ToString(),
absl::StrCat("if ((s", AE, " OR s", ae, " OR s", AEacute, " OR s",
aeacute, ") ", "AND (s", AE, " OR s", AEacute, " OR s", smcp,
") ", "AND (s", ae, " OR s", aeacute, " OR s", c2sc, ") ",
"AND (s", c2sc, " OR s", smcp, ")) then p0"));
}

TEST_F(DependencyClosureTest, ExtractAllGlyphConditions_PhaseCycle) {
Expand Down
59 changes: 55 additions & 4 deletions ift/integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,7 @@ TEST_F(IntegrationTest,
ASSERT_TRUE(extended.ok()) << extended.status();
auto extended_face = extended->face();

auto expected_uris = btree_set<std::string>{"18.ift_tk", "2_0C.ift_gk"};
auto expected_uris = btree_set<std::string>{"18.ift_tk", "0C.2.ift_gk"};
ASSERT_EQ(fetched_uris, expected_uris);

ASSERT_TRUE(GvarHasLongOffsets(*extended));
Expand Down Expand Up @@ -1508,7 +1508,7 @@ TEST_F(IntegrationTest,
auto extended_face = extended->face();

auto expected_uris =
btree_set<std::string>{"0O.ift_tk", "18.ift_tk", "2_0C.ift_gk"};
btree_set<std::string>{"0O.ift_tk", "18.ift_tk", "0C.2.ift_gk"};
ASSERT_EQ(fetched_uris, expected_uris);

ASSERT_TRUE(GvarHasLongOffsets(*extended));
Expand Down Expand Up @@ -1568,8 +1568,8 @@ TEST_F(IntegrationTest, MixedMode_DesignSpaceAugmentation_DropsUnusedPatches) {
&fetched_uris);

// correspond to ids 3, 4, 6, d
btree_set<std::string> expected_uris{"0S.ift_tk", "20.ift_tk", "2_0C.ift_gk",
"2_0G.ift_gk"};
btree_set<std::string> expected_uris{"0S.ift_tk", "20.ift_tk", "0C.2.ift_gk",
"0G.2.ift_gk"};

ASSERT_EQ(fetched_uris, expected_uris);

Expand Down Expand Up @@ -1800,4 +1800,55 @@ TEST_F(IntegrationTest, MixedMode_Cff2) {
FontHelper::Cff2Data(desubroutinized_face.get(), 35).span());
}

TEST_F(IntegrationTest, UrlTemplateOverride) {
Compiler compiler;
auto init_gids = InitEncoderForMixedMode(compiler);
ASSERT_TRUE(init_gids.ok()) << init_gids.status();

auto face = noto_sans_jp_.face();
auto segment_0 = FontHelper::GidsToUnicodes(face.get(), *init_gids);
auto segment_1 = FontHelper::GidsToUnicodes(face.get(), TestSegment1());

auto sc = compiler.SetInitSubset(segment_0);
compiler.AddNonGlyphDataSegment(segment_1);
sc.Update(compiler.AddGlyphDataPatchCondition(
PatchMap::Entry(segment_1, 1, PatchEncoding::GLYPH_KEYED)));
ASSERT_TRUE(sc.ok()) << sc;

// 4 (length) + 'p' + 'a' + 't' + 'h' + 128 (id opcode)
std::vector<uint8_t> prefix = {4, 'p', 'a', 't', 'h', 128};
compiler.SetOverrideUrlTemplatePrefix(prefix);

auto encoding = compiler.Compile();
ASSERT_TRUE(encoding.ok()) << encoding.status();

// Verify the keys in the patches map.
for (const auto& [uri, data] : encoding->patches) {
EXPECT_THAT(uri, testing::StartsWith("path"));
}

// Request codepoint extension
btree_set<std::string> fetched_uris;
auto extended = ExtendWithDesignSpace(*encoding, {chunk1_cp}, {}, {},
&fetched_uris, 1, 2);
ASSERT_TRUE(extended.ok()) << extended.status();

// Should have fetched one table keyed and one glyph keyed patch.
// Table keyed: path<id>.ift_tk
// Glyph keyed: path<id>.ift_gk
ASSERT_EQ(fetched_uris.size(), 2);
bool found_tk = false;
bool found_gk = false;
for (const std::string& uri : fetched_uris) {
EXPECT_THAT(uri, testing::StartsWith("path"));
if (uri.find(".ift_tk") != std::string::npos) {
found_tk = true;
} else if (uri.find(".ift_gk") != std::string::npos) {
found_gk = true;
}
}
EXPECT_TRUE(found_tk);
EXPECT_TRUE(found_gk);
}

} // namespace ift
Loading