Skip to content

Commit c82db2c

Browse files
committed
Add ExtractAllGlyphConditions()
This extracts (according to the dependency graph) the full boolean conditions for each glyph in a font. Boolean conditions are represented in conjunctive normal form. Because this is dep graph based, may be a superset of the true subset closure conditions. Note: this implementation does not yet respect closure phases, that is planned to be added next.
1 parent 922b05b commit c82db2c

File tree

7 files changed

+522
-58
lines changed

7 files changed

+522
-58
lines changed

ift/dep_graph/dependency_graph.cc

Lines changed: 150 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#include "ift/dep_graph/dependency_graph.h"
22

33
#include <cstdint>
4-
#include <functional>
54
#include <optional>
65
#include <utility>
76
#include <vector>
@@ -192,12 +191,15 @@ class TraversalContext {
192191

193192
// Traverse an edge with no special context and/or additional information
194193
// other than table tag
195-
void TraverseEdgeTo(Node dest,
194+
void TraverseEdgeTo(Node source, Node dest,
196195
std::optional<hb_tag_t> table_tag = std::nullopt) {
197196
if (!ShouldFollow(dest, table_tag, std::nullopt)) {
198197
return;
199198
}
200199

200+
callback.VisitPending(PendingEdge::Disjunctive(
201+
source, dest, table_tag.value_or(HB_TAG(' ', ' ', ' ', ' '))));
202+
201203
if (table_tag.has_value()) {
202204
callback.Visit(dest, *table_tag);
203205
} else {
@@ -332,8 +334,15 @@ class TraversalContext {
332334
const PendingEdge& edge, const CodepointSet& reached_unicodes,
333335
const GlyphSet& reached_glyphs,
334336
const flat_hash_set<hb_tag_t>& reached_features) {
335-
if (edge.required_glyph.has_value() &&
336-
!reached_glyphs.contains(*edge.required_glyph)) {
337+
if (edge.source.IsFeature() &&
338+
!reached_features.contains(edge.source.Id())) {
339+
return false;
340+
}
341+
if (edge.source.IsUnicode() &&
342+
!reached_unicodes.contains(edge.source.Id())) {
343+
return false;
344+
}
345+
if (edge.source.IsGlyph() && !reached_glyphs.contains(edge.source.Id())) {
337346
return false;
338347
}
339348

@@ -436,7 +445,7 @@ bool DependencyGraph::ClosureState::Reached(Node node) {
436445
}
437446

438447
void DependencyGraph::ClosureState::SetStartNodes(
439-
const absl::btree_set<Node>& start) {
448+
const btree_set<Node>& start) {
440449
for (Node node : start) {
441450
Reached(node);
442451
}
@@ -445,6 +454,8 @@ void DependencyGraph::ClosureState::SetStartNodes(
445454
template <typename CallbackT>
446455
static Status DoTraversal(const PendingEdge& edge,
447456
TraversalContext<CallbackT>& context) {
457+
context.callback.VisitPending(edge);
458+
448459
if (edge.table_tag == GSUB && edge.required_feature.has_value()) {
449460
if (edge.required_context_set_index.has_value()) {
450461
GlyphSet context_glyphs =
@@ -514,21 +525,20 @@ absl::Status DependencyGraph::HandleOutgoingEdges(
514525
} else if (node.IsFeature()) {
515526
TRYV(HandleFeatureOutgoingEdges(node.Id(), context));
516527
} else if (node.IsInitFont()) {
517-
HandleSubsetDefinitionOutgoingEdges(segmentation_info_->InitFontSegment(),
518-
context);
528+
HandleSubsetDefinitionOutgoingEdges(
529+
node, segmentation_info_->InitFontSegment(), context);
519530
}
520531
return absl::OkStatus();
521532
}
522533

523534
StatusOr<std::vector<Node>> DependencyGraph::TopologicalSorting() const {
524535
struct TopoCallback {
525536
std::vector<Node> edges;
526-
void Visit(Node dest) { edges.push_back(dest); }
527-
void Visit(Node dest, hb_tag_t) { edges.push_back(dest); }
528-
void VisitGsub(Node dest, hb_tag_t) { edges.push_back(dest); }
529-
void VisitContextual(Node dest, hb_tag_t, ift::common::GlyphSet) {
530-
edges.push_back(dest);
531-
}
537+
void Visit(Node dest) {}
538+
void Visit(Node dest, hb_tag_t) {}
539+
void VisitGsub(Node dest, hb_tag_t) {}
540+
void VisitContextual(Node dest, hb_tag_t, ift::common::GlyphSet) {}
541+
void VisitPending(const PendingEdge& edge) { edges.push_back(edge.dest); }
532542
};
533543

534544
CodepointSet non_init_font_codepoints =
@@ -549,7 +559,7 @@ StatusOr<std::vector<Node>> DependencyGraph::TopologicalSorting() const {
549559
context.enforce_context = false;
550560
context.unicode_filter = &non_init_font_codepoints;
551561
context.glyph_filter = &non_init_font_glyphs;
552-
context.feature_filter = &non_init_font_features;
562+
context.feature_filter = &full_feature_set_;
553563

554564
// DFS topological sorting algorithm from:
555565
// https://en.wikipedia.org/wiki/Topological_sorting
@@ -659,7 +669,7 @@ StatusOr<Traversal> DependencyGraph::ClosureTraversal(
659669
}
660670

661671
StatusOr<Traversal> DependencyGraph::ClosureTraversal(
662-
const absl::btree_set<Node>& nodes, const GlyphSet* glyph_filter_ptr,
672+
const btree_set<Node>& nodes, const GlyphSet* glyph_filter_ptr,
663673
const CodepointSet* unicode_filter_ptr, bool enforce_context) const {
664674
// TODO(garretrieger): context edges don't have edges for each participating
665675
// glyph, so for full correctness in matching closure we should introduce
@@ -783,7 +793,7 @@ Status DependencyGraph::HandleUnicodeOutgoingEdges(
783793
{
784794
auto it = unicode_to_gid_.find(unicode);
785795
if (it != unicode_to_gid_.end()) {
786-
context->TraverseEdgeTo(Node::Glyph(it->second));
796+
context->TraverseEdgeTo(Node::Unicode(unicode), Node::Glyph(it->second));
787797
}
788798
}
789799

@@ -799,7 +809,7 @@ Status DependencyGraph::HandleUnicodeOutgoingEdges(
799809
auto unicode_funcs = hb_unicode_funcs_get_default();
800810
hb_codepoint_t mirror = hb_unicode_mirroring(unicode_funcs, unicode);
801811
if (mirror != unicode) {
802-
context->TraverseEdgeTo(Node::Unicode(mirror));
812+
context->TraverseEdgeTo(Node::Unicode(unicode), Node::Unicode(mirror));
803813
}
804814

805815
return absl::OkStatus();
@@ -830,7 +840,7 @@ Status DependencyGraph::HandleGlyphOutgoingEdges(
830840
continue;
831841
}
832842

833-
context->TraverseEdgeTo(dest, table_tag);
843+
context->TraverseEdgeTo(Node::Glyph(gid), dest, table_tag);
834844
}
835845

836846
auto it = context_glyph_implied_edges_.find(gid);
@@ -904,19 +914,20 @@ void DependencyGraph::HandleSegmentOutgoingEdges(
904914
}
905915

906916
const Segment& s = segmentation_info_->Segments().at(id);
907-
HandleSubsetDefinitionOutgoingEdges(s.Definition(), context);
917+
HandleSubsetDefinitionOutgoingEdges(Node::Segment(id), s.Definition(),
918+
context);
908919
}
909920

910921
template <typename CallbackT>
911922
void DependencyGraph::HandleSubsetDefinitionOutgoingEdges(
912-
const SubsetDefinition& subset_def,
923+
Node source, const SubsetDefinition& subset_def,
913924
TraversalContext<CallbackT>* context) const {
914925
for (hb_codepoint_t u : subset_def.codepoints) {
915-
context->TraverseEdgeTo(Node::Unicode(u));
926+
context->TraverseEdgeTo(source, Node::Unicode(u));
916927
}
917928

918929
for (hb_tag_t f : subset_def.feature_tags) {
919-
context->TraverseEdgeTo(Node::Feature(f));
930+
context->TraverseEdgeTo(source, Node::Feature(f));
920931
}
921932
}
922933

@@ -984,8 +995,9 @@ StatusOr<flat_hash_set<hb_tag_t>> DependencyGraph::InitFeatureSet(
984995
StatusOr<GlyphSet> DependencyGraph::RequiredGlyphsFor(
985996
const PendingEdge& edge) const {
986997
GlyphSet out;
987-
if (edge.required_glyph.has_value()) {
988-
out.insert(*edge.required_glyph);
998+
999+
if (edge.source.IsGlyph()) {
1000+
out.insert(edge.source.Id());
9891001
}
9901002

9911003
if (edge.required_liga_set_index.has_value()) {
@@ -1011,6 +1023,64 @@ StatusOr<GlyphSet> DependencyGraph::GetLigaSet(
10111023
return glyphs;
10121024
}
10131025

1026+
StatusOr<EdgeConditonsCnf> DependencyGraph::ExtractRequirements(
1027+
const PendingEdge& edge) const {
1028+
EdgeConditonsCnf requirements;
1029+
1030+
requirements.insert({edge.source});
1031+
1032+
if (edge.required_feature.has_value()) {
1033+
requirements.insert({Node::Feature(*edge.required_feature)});
1034+
}
1035+
1036+
if (edge.required_codepoints.has_value()) {
1037+
requirements.insert({Node::Unicode(edge.required_codepoints->first)});
1038+
requirements.insert({Node::Unicode(edge.required_codepoints->second)});
1039+
}
1040+
1041+
if (edge.required_liga_set_index.has_value()) {
1042+
GlyphSet gids = TRY(GetLigaSet(*edge.required_liga_set_index));
1043+
for (glyph_id_t gid : gids) {
1044+
requirements.insert({Node::Glyph(gid)});
1045+
}
1046+
}
1047+
1048+
// TODO(garretrieger): take contextual lookup flags into consideration. See
1049+
// dep graph documentation for more details. They can be used to
1050+
// decide if the resulting conditions are exact or an over approximation.
1051+
if (edge.required_context_set_index.has_value()) {
1052+
// the context set is actually a set of sets.
1053+
if (!hb_depend_get_set_from_index(dependency_graph_.get(),
1054+
*edge.required_context_set_index,
1055+
scratch_set_.get())) {
1056+
return absl::InternalError("Context set lookup failed.");
1057+
}
1058+
1059+
hb_codepoint_t set_id = HB_CODEPOINT_INVALID;
1060+
while (hb_set_next(scratch_set_.get(), &set_id)) {
1061+
btree_set<Node> req;
1062+
if (set_id < 0x80000000) {
1063+
req.insert(Node::Glyph(set_id));
1064+
} else {
1065+
hb_codepoint_t actual_set_id = set_id & 0x7FFFFFFF;
1066+
if (!hb_depend_get_set_from_index(dependency_graph_.get(),
1067+
actual_set_id,
1068+
scratch_set_aux_.get())) {
1069+
return absl::InternalError("Context sub set lookup failed.");
1070+
}
1071+
for (hb_codepoint_t gid : GlyphSet(scratch_set_aux_.get())) {
1072+
req.insert(Node::Glyph(gid));
1073+
}
1074+
}
1075+
if (!req.empty()) {
1076+
requirements.insert(std::move(req));
1077+
}
1078+
}
1079+
}
1080+
1081+
return requirements;
1082+
}
1083+
10141084
flat_hash_map<glyph_id_t, std::vector<DependencyGraph::LayoutFeatureEdge>>
10151085
DependencyGraph::ComputeContextGlyphEdges() const {
10161086
flat_hash_map<glyph_id_t, btree_set<LayoutFeatureEdge>> edges;
@@ -1119,6 +1189,62 @@ DependencyGraph::ComputeFeatureEdges() const {
11191189
return out;
11201190
}
11211191

1192+
StatusOr<flat_hash_map<Node, btree_set<EdgeConditonsCnf>>>
1193+
DependencyGraph::CollectIncomingEdges() const {
1194+
struct IncomingEdgeCollector {
1195+
flat_hash_map<Node, btree_set<EdgeConditonsCnf>>* incoming_edges = nullptr;
1196+
const DependencyGraph* graph = nullptr;
1197+
Status had_error = absl::OkStatus();
1198+
1199+
void Visit(Node dest) {}
1200+
void Visit(Node dest, hb_tag_t) {}
1201+
void VisitGsub(Node, hb_tag_t) {}
1202+
void VisitContextual(Node, hb_tag_t, ift::common::GlyphSet) {}
1203+
void VisitPending(const PendingEdge& pe) {
1204+
auto reqs = graph->ExtractRequirements(pe);
1205+
had_error.Update(reqs.status());
1206+
if (!reqs.ok()) {
1207+
return;
1208+
}
1209+
1210+
(*incoming_edges)[pe.dest].insert(std::move(*reqs));
1211+
}
1212+
};
1213+
1214+
flat_hash_map<Node, btree_set<EdgeConditonsCnf>> incoming_edges;
1215+
TraversalContext<IncomingEdgeCollector> context;
1216+
context.depend = dependency_graph_.get();
1217+
context.full_closure = &segmentation_info_->FullClosure();
1218+
context.feature_filter = &full_feature_set_;
1219+
context.glyph_filter = &segmentation_info_->FullClosure();
1220+
context.unicode_filter = &segmentation_info_->FullDefinition().codepoints;
1221+
context.enforce_context = false;
1222+
context.callback.incoming_edges = &incoming_edges;
1223+
context.callback.graph = this;
1224+
1225+
btree_set<Node> all_nodes;
1226+
all_nodes.insert(Node::InitFont());
1227+
for (segment_index_t s : segmentation_info_->NonEmptySegments()) {
1228+
all_nodes.insert(Node::Segment(s));
1229+
}
1230+
for (hb_codepoint_t u : segmentation_info_->FullDefinition().codepoints) {
1231+
all_nodes.insert(Node::Unicode(u));
1232+
}
1233+
for (hb_tag_t f : full_feature_set_) {
1234+
all_nodes.insert(Node::Feature(f));
1235+
}
1236+
for (glyph_id_t g : segmentation_info_->FullClosure()) {
1237+
all_nodes.insert(Node::Glyph(g));
1238+
}
1239+
1240+
for (Node n : all_nodes) {
1241+
TRYV(HandleOutgoingEdges(n, &context));
1242+
TRYV(context.callback.had_error);
1243+
}
1244+
1245+
return incoming_edges;
1246+
}
1247+
11221248
void PrintTo(const Node& node, std::ostream* os) { *os << node.ToString(); }
11231249

11241250
} // namespace ift::dep_graph

ift/dep_graph/dependency_graph.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ class RequestedSegmentationInformation;
2222

2323
namespace ift::dep_graph {
2424

25+
// Conditions for an edge in CNF form using node presence as inputs.
26+
// (node_1 or ...) and (node_i or ...)
27+
typedef absl::btree_set<absl::btree_set<Node>> EdgeConditonsCnf;
28+
2529
template <typename CallbackT>
2630
class TraversalContext;
2731

@@ -81,6 +85,14 @@ class DependencyGraph {
8185
// any nodes that are in the init font.
8286
absl::StatusOr<std::vector<Node>> TopologicalSorting() const;
8387

88+
// Computes the incoming edges for every node in the dependency graph, taking
89+
// into account all context requirements and implicit dependencies.
90+
//
91+
// The return value is a map from each node to each unique edge requirement.
92+
// Edge requirements are represented as a CNF expression over other nodes.
93+
absl::StatusOr<absl::flat_hash_map<Node, absl::btree_set<EdgeConditonsCnf>>>
94+
CollectIncomingEdges() const;
95+
8496
private:
8597
DependencyGraph(
8698
const ift::encoder::RequestedSegmentationInformation* segmentation_info,
@@ -105,6 +117,7 @@ class DependencyGraph {
105117
void VisitGsub(Node dest, hb_tag_t feature);
106118
void VisitContextual(Node dest, hb_tag_t feature,
107119
ift::common::GlyphSet context_glyphs);
120+
void VisitPending(const PendingEdge& edge) {}
108121

109122
std::optional<Node> GetNext();
110123
bool Reached(Node node);
@@ -146,12 +159,19 @@ class DependencyGraph {
146159

147160
template <typename CallbackT>
148161
void HandleSubsetDefinitionOutgoingEdges(
149-
const encoder::SubsetDefinition& subset_def,
162+
Node source, const encoder::SubsetDefinition& subset_def,
150163
TraversalContext<CallbackT>* context) const;
151164

152165
absl::StatusOr<ift::common::GlyphSet> GetLigaSet(
153166
hb_codepoint_t liga_set_id) const;
154167

168+
// Extracts the specific node dependencies required to satisfy a given
169+
// PendingEdge.
170+
//
171+
// Returns a list of requirements as CNF expression on node presence.
172+
absl::StatusOr<EdgeConditonsCnf> ExtractRequirements(
173+
const PendingEdge& edge) const;
174+
155175
static absl::StatusOr<absl::flat_hash_set<hb_tag_t>> FullFeatureSet(
156176
const ift::encoder::RequestedSegmentationInformation* segmentation_info,
157177
hb_face_t* face);
@@ -222,6 +242,7 @@ class DependencyGraph {
222242
context_glyph_implied_edges_;
223243

224244
common::hb_set_unique_ptr scratch_set_ = common::make_hb_set();
245+
common::hb_set_unique_ptr scratch_set_aux_ = common::make_hb_set();
225246
};
226247

227248
} // namespace ift::dep_graph

0 commit comments

Comments
 (0)