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
6 changes: 3 additions & 3 deletions common/font_helper_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ TEST_F(FontHelperTest, GvarData) {

data = FontHelper::GvarData(roboto_vf.get(), 5);
ASSERT_TRUE(data.ok()) << data.status();
ASSERT_EQ(data->size(), 250);
ASSERT_EQ(data->size(), 252);
const uint8_t expected[11] = {0x80, 0x06, 0x00, 0x2c, 0x00, 0x2a,
0x00, 0x02, 0x00, 0x26, 0x00};
0x00, 0x04, 0x00, 0x26, 0x00};
string_view expected_str((const char*)expected, 11);
ASSERT_EQ(data->substr(0, 11), expected_str);
}
Expand Down Expand Up @@ -448,7 +448,7 @@ TEST_F(FontHelperTest, GvarSharedTupleCount) {
}

TEST_F(FontHelperTest, GvarData_NotFound) {
auto data = FontHelper::GvarData(roboto_vf.get(), 1300);
auto data = FontHelper::GvarData(roboto_vf.get(), 1400);
ASSERT_TRUE(absl::IsNotFound(data.status())) << data.status();
}

Expand Down
Binary file modified common/testdata/Roboto[wdth,wght].ttf
Binary file not shown.
126 changes: 102 additions & 24 deletions ift/dep_graph/dependency_graph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -113,21 +113,24 @@ class PendingEdge {
return edge;
}

static PendingEdge Gsub(hb_tag_t feature, glyph_id_t gid) {
PendingEdge edge(Node::Glyph(gid), GSUB);
static PendingEdge Gsub(glyph_id_t source_gid, hb_tag_t feature, glyph_id_t dest_gid) {
PendingEdge edge(Node::Glyph(dest_gid), GSUB);
edge.required_glyph_ = source_gid;
edge.required_feature_ = feature;
return edge;
}

static PendingEdge Ligature(hb_tag_t feature, glyph_id_t gid, hb_codepoint_t liga_set_index) {
PendingEdge edge(Node::Glyph(gid), GSUB);
static PendingEdge Ligature(glyph_id_t source_gid, hb_tag_t feature, glyph_id_t dest_gid, hb_codepoint_t liga_set_index) {
PendingEdge edge(Node::Glyph(dest_gid), GSUB);
edge.required_glyph_ = source_gid;
edge.required_feature_ = feature;
edge.required_liga_set_index_ = liga_set_index;
return edge;
}

static PendingEdge Context(hb_tag_t feature, glyph_id_t gid, hb_codepoint_t context_set_index) {
PendingEdge edge(Node::Glyph(gid), GSUB);
static PendingEdge Context(glyph_id_t source_gid, hb_tag_t feature, glyph_id_t dest_gid, hb_codepoint_t context_set_index) {
PendingEdge edge(Node::Glyph(dest_gid), GSUB);
edge.required_glyph_ = source_gid;
edge.required_feature_ = feature;
edge.required_context_set_index_ = context_set_index;
return edge;
Expand All @@ -146,6 +149,10 @@ class PendingEdge {
const flat_hash_set<hb_tag_t>& reached_features
) const {

if (required_glyph_.has_value() && !reached_glyphs.contains(*required_glyph_)) {
return false;
}

if (required_codepoints_.has_value() &&
(!reached_unicodes.contains(required_codepoints_->first) ||
!reached_unicodes.contains(required_codepoints_->second))) {
Expand Down Expand Up @@ -176,6 +183,7 @@ class PendingEdge {
Node dest_;
hb_tag_t table_tag_;

std::optional<glyph_id_t> required_glyph_ = std::nullopt;
std::optional<hb_tag_t> required_feature_ = std::nullopt;
std::optional<uint32_t> required_liga_set_index_ = std::nullopt;
std::optional<uint32_t> required_context_set_index_ = std::nullopt;
Expand Down Expand Up @@ -330,31 +338,31 @@ class TraversalContext {
return TraverseEdgeTo(dest, edge, cmap);
}

Status TraverseGsubEdgeTo(glyph_id_t gid, hb_tag_t feature) {
PendingEdge edge = PendingEdge::Gsub(feature, gid);
Node dest = Node::Glyph(gid);
Status TraverseGsubEdgeTo(glyph_id_t source_gid, glyph_id_t dest_gid, hb_tag_t feature) {
PendingEdge edge = PendingEdge::Gsub(source_gid, feature, dest_gid);
Node dest = Node::Glyph(dest_gid);
return TraverseEdgeTo(dest, edge, GSUB);
}

Status TraverseContextualEdgeTo(glyph_id_t gid, hb_tag_t feature, hb_codepoint_t context_set) {
Status TraverseContextualEdgeTo(glyph_id_t source_gid, glyph_id_t dest_gid, hb_tag_t feature, hb_codepoint_t context_set) {
if (!TRY(ContextSetSatisfied(depend, context_set, *full_closure))) {
// Not possible for this edge to be activated so it can be ignored.
return absl::OkStatus();
}

PendingEdge edge = PendingEdge::Context(feature, gid, context_set);
Node dest = Node::Glyph(gid);
PendingEdge edge = PendingEdge::Context(source_gid, feature, dest_gid, context_set);
Node dest = Node::Glyph(dest_gid);
return TraverseEdgeTo(dest, edge, GSUB);
}

Status TraverseLigatureEdgeTo(glyph_id_t gid, hb_tag_t feature, hb_codepoint_t liga_set_index) {
Status TraverseLigatureEdgeTo(glyph_id_t source_gid, glyph_id_t dest_gid, hb_tag_t feature, hb_codepoint_t liga_set_index) {
if (!TRY(LigaSetSatisfied(depend, liga_set_index, *full_closure))) {
// Not possible for this edge to be activated so it can be ignored.
return absl::OkStatus();
}

PendingEdge edge = PendingEdge::Ligature(feature, gid, liga_set_index);
Node dest = Node::Glyph(gid);
PendingEdge edge = PendingEdge::Ligature(source_gid, feature, dest_gid, liga_set_index);
Node dest = Node::Glyph(dest_gid);
return TraverseEdgeTo(dest, edge, GSUB);
}

Expand Down Expand Up @@ -501,11 +509,13 @@ StatusOr<Traversal> DependencyGraph::TraverseGraph(TraversalContext* context) co
HandleSegmentOutgoingEdges(next->Id(), context);
}

if (next->IsFeature()) {
TRYV(HandleFeatureOutgoingEdges(next->Id(), context));
}

if (next->IsInitFont()) {
HandleSubsetDefinitionOutgoingEdges(segmentation_info_->InitFontSegment(), context);
}

// Features don't have any outgoing edges
}

if (context->HasPendingEdges()) {
Expand Down Expand Up @@ -627,6 +637,14 @@ Status DependencyGraph::ClosureSubTraversal(
start_nodes.insert(Node::Glyph(gid));
}

if (table == GSUB) {
// For GSUB we also need to consider reached features as starting nodes
// since those have outgoing GSUB edges.
for (hb_tag_t feature : traversal_full.ReachedLayoutFeatures()) {
start_nodes.insert(Node::Feature(feature));
}
}

TraversalContext context = *base_context;
context.SetStartNodes(start_nodes);
context.table_filter = {table};
Expand Down Expand Up @@ -680,13 +698,7 @@ Status DependencyGraph::HandleGlyphOutgoingEdges(
&dep_gid, &layout_tag, &ligature_set, &context_set, nullptr /* flags */)) {
Node dest = Node::Glyph(dep_gid);
if (table_tag == HB_TAG('G', 'S', 'U', 'B')) {
if (context_set != HB_CODEPOINT_INVALID) {
TRYV(context->TraverseContextualEdgeTo(dep_gid, layout_tag, context_set));
} else if (ligature_set != HB_CODEPOINT_INVALID) {
TRYV(context->TraverseLigatureEdgeTo(dep_gid, layout_tag, ligature_set));
} else {
TRYV(context->TraverseGsubEdgeTo(dep_gid, layout_tag));
}
TRYV(HandleGsubGlyphOutgoingEdges(gid, dep_gid, layout_tag, ligature_set, context_set, context));
continue;
}

Expand All @@ -702,6 +714,44 @@ Status DependencyGraph::HandleGlyphOutgoingEdges(
return absl::OkStatus();
}

Status DependencyGraph::HandleGsubGlyphOutgoingEdges(
glyph_id_t source_gid,
glyph_id_t dest_gid,
hb_tag_t layout_tag,
hb_codepoint_t ligature_set,
hb_codepoint_t context_set,
TraversalContext* context
) const {
if (context_set != HB_CODEPOINT_INVALID) {
return context->TraverseContextualEdgeTo(source_gid, dest_gid, layout_tag, context_set);
} else if (ligature_set != HB_CODEPOINT_INVALID) {
return context->TraverseLigatureEdgeTo(source_gid, dest_gid, layout_tag, ligature_set);
} else {
return context->TraverseGsubEdgeTo(source_gid, dest_gid, layout_tag);
}
}

Status DependencyGraph::HandleFeatureOutgoingEdges(
hb_tag_t feature_tag,
TraversalContext* context
) const {
if (!context->table_filter.contains(GSUB)) {
// All feature edges are GSUB edges so we can skip
// this if GSUB is being filtered out.
return absl::OkStatus();
}

auto edges = layout_fature_implied_edges_.find(feature_tag);
if (edges == layout_fature_implied_edges_.end()) {
// No outgoing edges
return absl::OkStatus();
}
for (const auto& edge : edges->second) {
TRYV(HandleGsubGlyphOutgoingEdges(edge.source_gid, edge.dest_gid, feature_tag, edge.ligature_set, edge.context_set, context));
}
return absl::OkStatus();
}

void DependencyGraph::HandleSegmentOutgoingEdges(
segment_index_t id,
TraversalContext* context
Expand Down Expand Up @@ -827,4 +877,32 @@ flat_hash_map<hb_codepoint_t, std::vector<DependencyGraph::VariationSelectorEdge
return edges;
}

flat_hash_map<hb_tag_t, btree_set<DependencyGraph::LayoutFeatureEdge>> DependencyGraph::ComputeFeatureEdges() const {
flat_hash_map<hb_tag_t, btree_set<DependencyGraph::LayoutFeatureEdge>> edges;

for (glyph_id_t gid = 0; gid < hb_face_get_glyph_count(original_face_.get()); gid++) {
hb_codepoint_t index = 0;
hb_tag_t table_tag = HB_CODEPOINT_INVALID;
hb_codepoint_t dest_gid = HB_CODEPOINT_INVALID;
hb_tag_t layout_tag = HB_CODEPOINT_INVALID;
hb_codepoint_t ligature_set = HB_CODEPOINT_INVALID;
hb_codepoint_t context_set = HB_CODEPOINT_INVALID;
while (hb_depend_get_glyph_entry(dependency_graph_.get(), gid, index++, &table_tag,
&dest_gid, &layout_tag, &ligature_set, &context_set, nullptr /* flags */)) {
if (table_tag != GSUB || layout_tag == HB_CODEPOINT_INVALID) {
continue;
}

edges[layout_tag].insert(LayoutFeatureEdge {
.source_gid = gid,
.dest_gid = dest_gid,
.ligature_set = ligature_set,
.context_set = context_set,
});
}
}

return edges;
}

} // namespace ift::dep_graph
47 changes: 46 additions & 1 deletion ift/dep_graph/dependency_graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ class DependencyGraph {
full_feature_set_(full_feature_set),
unicode_to_gid_(UnicodeToGid(face)),
dependency_graph_(depend, &hb_depend_destroy),
variation_selector_implied_edges_(ComputeUVSEdges()) {}
variation_selector_implied_edges_(ComputeUVSEdges()),
layout_fature_implied_edges_(ComputeFeatureEdges())
{}

absl::StatusOr<Traversal> TraverseGraph(
TraversalContext* context
Expand All @@ -100,6 +102,20 @@ class DependencyGraph {
TraversalContext* context
) const;

absl::Status HandleGsubGlyphOutgoingEdges(
encoder::glyph_id_t source_gid,
encoder::glyph_id_t dest_gid,
hb_tag_t layout_tag,
hb_codepoint_t ligature_set,
hb_codepoint_t context_set,
TraversalContext* context
) const;

absl::Status HandleFeatureOutgoingEdges(
hb_tag_t feature_tag,
TraversalContext* context
) const;

void HandleSegmentOutgoingEdges(
encoder::segment_index_t id,
TraversalContext* context
Expand Down Expand Up @@ -136,9 +152,38 @@ class DependencyGraph {
hb_codepoint_t gid;
};

struct LayoutFeatureEdge {
hb_codepoint_t source_gid;
hb_codepoint_t dest_gid;
hb_codepoint_t ligature_set;
hb_codepoint_t context_set;

bool operator==(const LayoutFeatureEdge& other) const {
return source_gid == other.dest_gid &&
dest_gid == other.dest_gid &&
ligature_set == other.ligature_set &&
context_set == other.context_set;
}

bool operator<(const LayoutFeatureEdge& other) const {
if (source_gid != other.source_gid) {
return source_gid < other.source_gid;
}
if (dest_gid != other.dest_gid) {
return dest_gid < other.dest_gid;
}
if (ligature_set != other.ligature_set) {
return ligature_set < other.ligature_set;
}
return context_set < other.context_set;
}
};

absl::flat_hash_map<hb_codepoint_t, std::vector<VariationSelectorEdge>> ComputeUVSEdges() const;
absl::flat_hash_map<hb_tag_t, absl::btree_set<LayoutFeatureEdge>> ComputeFeatureEdges() const;

absl::flat_hash_map<hb_codepoint_t, std::vector<VariationSelectorEdge>> variation_selector_implied_edges_;
absl::flat_hash_map<hb_tag_t, absl::btree_set<LayoutFeatureEdge>> layout_fature_implied_edges_;
};

} // namespace ift::dep_graph
Expand Down
80 changes: 80 additions & 0 deletions ift/dep_graph/dependency_graph_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,86 @@ TEST_F(DependencyGraphTest, IgnoreUnreachable_Liga) {
ASSERT_FALSE(traversal.HasPendingEdges());
}

TEST_F(DependencyGraphTest, ImpliedFeatureEdge) {
SubsetDefinition c2sc;
c2sc.feature_tags = {HB_TAG('c', '2', 's', 'c')};
Reconfigure(WithDefaultFeatures({'A'}), {
{{'B'}, ProbabilityBound::Zero()},
{c2sc, ProbabilityBound::Zero()},
});

/* ### s0 ### */
auto traversal = graph.ClosureTraversal({
Node::Segment(0),
});
ASSERT_TRUE(traversal.ok()) << traversal.status();

ASSERT_EQ(traversal->ReachedGlyphs(), (GlyphSet {
38, /* B */
}));
ASSERT_TRUE(traversal->HasPendingEdges());

/* ### s1 ### */
traversal = graph.ClosureTraversal({
Node::Segment(1),
});
ASSERT_TRUE(traversal.ok()) << traversal.status();

ASSERT_EQ(traversal->ReachedGlyphs(), (GlyphSet {
563, /* smcap A */
}));
ASSERT_TRUE(traversal->HasPendingEdges());

/* ### s0 + s1 ### */
traversal = graph.ClosureTraversal({
Node::Segment(0),
Node::Segment(1),
});
ASSERT_TRUE(traversal.ok()) << traversal.status();

ASSERT_EQ(traversal->ReachedGlyphs(), (GlyphSet {
38, /* B */
562, /* smcap B */
563, /* smcap A */
}));
ASSERT_FALSE(traversal->HasPendingEdges());
}

TEST_F(DependencyGraphTest, ImpliedFeatureEdge_Liga) {
SubsetDefinition liga;
liga.feature_tags = {HB_TAG('l', 'i', 'g', 'a')};
Reconfigure({'f'}, {
{{'i'}, ProbabilityBound::Zero()},
{liga, ProbabilityBound::Zero()},
});

/* s0 constraints not satisfied */
auto traversal = graph.ClosureTraversal({
Node::Segment(0),
});
ASSERT_TRUE(traversal.ok()) << traversal.status();

ASSERT_EQ(traversal->ReachedGlyphs(), (GlyphSet {
77, /* i */
}));
ASSERT_TRUE(traversal->HasPendingEdges());

/* s0 + s1 all constraints satisfied */
traversal = graph.ClosureTraversal({
Node::Segment(0),
Node::Segment(1),
});
ASSERT_TRUE(traversal.ok()) << traversal.status();

ASSERT_EQ(traversal->ReachedGlyphs(), (GlyphSet {
/* f is in init */
77, /* i */
444, /* fi */
446, /* ffi */
}));
ASSERT_FALSE(traversal->HasPendingEdges());
}

// TODO(garretrieger):
// - basic math, CFF, and COLR tests.

Expand Down
Loading
Loading