Skip to content

Commit 88b0ed6

Browse files
committed
Add a method to compute a topological sorting of DependencyGraph.
1 parent a93d49c commit 88b0ed6

File tree

10 files changed

+321
-75
lines changed

10 files changed

+321
-75
lines changed

ift/common/font_helper.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,17 @@ class FontHelper {
217217
static std::string ToString(hb_tag_t tag);
218218
static hb_tag_t ToTag(const std::string& tag);
219219

220+
static absl::StatusOr<hb_codepoint_t> GetNominalGlyph(
221+
hb_face_t* face, hb_codepoint_t codepoint) {
222+
hb_font_unique_ptr font = make_hb_font(hb_font_create(face));
223+
hb_codepoint_t gid = 0;
224+
if (!hb_font_get_nominal_glyph(font.get(), codepoint, &gid)) {
225+
return absl::NotFoundError(
226+
absl::StrCat("No nominal glyph for codepoint: ", codepoint));
227+
}
228+
return gid;
229+
}
230+
220231
private:
221232
template <int num_bits, typename int_type_t>
222233
static void WriteInt(int_type_t value, std::string& out) {

ift/common/font_helper_test.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,25 @@ TEST_F(FontHelperTest, GidsToUnicodes) {
667667
(CodepointSet{0x33, 0x2F64, 0x7528}));
668668
}
669669

670+
TEST_F(FontHelperTest, GetNominalGlyph) {
671+
auto g_a = FontHelper::GetNominalGlyph(roboto.get(), 'a');
672+
ASSERT_TRUE(g_a.ok());
673+
EXPECT_EQ(*g_a, 69);
674+
675+
auto g_b = FontHelper::GetNominalGlyph(roboto.get(), 'b');
676+
ASSERT_TRUE(g_b.ok());
677+
EXPECT_EQ(*g_b, 70);
678+
679+
auto g_fi = FontHelper::GetNominalGlyph(roboto.get(), 0xfb01 /* fi */);
680+
ASSERT_TRUE(g_fi.ok());
681+
EXPECT_EQ(*g_fi, 444);
682+
683+
auto g_invalid =
684+
FontHelper::GetNominalGlyph(roboto.get(), 0xffff /* invalid */);
685+
EXPECT_FALSE(g_invalid.ok());
686+
EXPECT_TRUE(absl::IsNotFound(g_invalid.status()));
687+
}
688+
670689
// TODO test BuildFont...
671690

672691
} // namespace ift::common

ift/dep_graph/dependency_graph.cc

Lines changed: 148 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#include "ift/dep_graph/dependency_graph.h"
22

33
#include <cstdint>
4+
#include <functional>
45
#include <optional>
56
#include <utility>
7+
#include <vector>
68

79
#include "absl/log/log.h"
810
#include "ift/common/font_data.h"
@@ -82,7 +84,7 @@ static StatusOr<GlyphSet> GetContextSet(
8284
}
8385

8486
// Tracks the details of an inprogress traversal.
85-
template<typename CallbackT>
87+
template <typename CallbackT>
8688
class TraversalContext {
8789
public:
8890
hb_depend_t* depend = nullptr;
@@ -173,7 +175,6 @@ class TraversalContext {
173175

174176
// Returns the next node to be visited.
175177

176-
177178
// This checks all pending edges and if any have their constraints satisfied
178179
// then they are traversed. Returns true if there are now more nodes in the
179180
// next queue.
@@ -363,11 +364,8 @@ class TraversalContext {
363364

364365
void Pending(PendingEdge edge) { pending_edges_.push_back(edge); }
365366

366-
bool ShouldFollow(
367-
Node node,
368-
std::optional<hb_tag_t> table_tag,
369-
std::optional<hb_tag_t> layout_feature) const {
370-
367+
bool ShouldFollow(Node node, std::optional<hb_tag_t> table_tag,
368+
std::optional<hb_tag_t> layout_feature) const {
371369
if (!node.Matches(node_type_filter)) {
372370
return false;
373371
}
@@ -412,7 +410,8 @@ void DependencyGraph::ClosureState::VisitGsub(Node dest, hb_tag_t feature) {
412410
Reached(dest);
413411
}
414412

415-
void DependencyGraph::ClosureState::VisitContextual(Node dest, hb_tag_t feature, GlyphSet context_glyphs) {
413+
void DependencyGraph::ClosureState::VisitContextual(Node dest, hb_tag_t feature,
414+
GlyphSet context_glyphs) {
416415
traversal.VisitContextual(dest, feature, context_glyphs);
417416
Reached(dest);
418417
}
@@ -436,21 +435,23 @@ bool DependencyGraph::ClosureState::Reached(Node node) {
436435
return true;
437436
}
438437

439-
void DependencyGraph::ClosureState::SetStartNodes(const absl::btree_set<Node>& start) {
438+
void DependencyGraph::ClosureState::SetStartNodes(
439+
const absl::btree_set<Node>& start) {
440440
for (Node node : start) {
441441
Reached(node);
442442
}
443443
}
444444

445-
template<typename CallbackT>
446-
static Status DoTraversal(const PendingEdge& edge, TraversalContext<CallbackT>& context) {
445+
template <typename CallbackT>
446+
static Status DoTraversal(const PendingEdge& edge,
447+
TraversalContext<CallbackT>& context) {
447448
if (edge.table_tag == GSUB && edge.required_feature.has_value()) {
448449
if (edge.required_context_set_index.has_value()) {
449450
GlyphSet context_glyphs =
450451
TRY(GetContextSet(context.depend, context.full_closure,
451452
*edge.required_context_set_index));
452453
context.callback.VisitContextual(edge.dest, *edge.required_feature,
453-
context_glyphs);
454+
context_glyphs);
454455
} else {
455456
context.callback.VisitGsub(edge.dest, *edge.required_feature);
456457
}
@@ -462,8 +463,9 @@ static Status DoTraversal(const PendingEdge& edge, TraversalContext<CallbackT>&
462463
return absl::OkStatus();
463464
}
464465

465-
template<typename CallbackT>
466-
StatusOr<bool> TraversalContext<CallbackT>::CheckPending(hb_depend_t* depend_graph) {
466+
template <typename CallbackT>
467+
StatusOr<bool> TraversalContext<CallbackT>::CheckPending(
468+
hb_depend_t* depend_graph) {
467469
bool did_work = false;
468470
auto it = pending_edges_.begin();
469471
while (it != pending_edges_.end()) {
@@ -482,9 +484,9 @@ StatusOr<bool> TraversalContext<CallbackT>::CheckPending(hb_depend_t* depend_gra
482484
return did_work;
483485
}
484486

485-
template<typename CallbackT>
487+
template <typename CallbackT>
486488
Status TraversalContext<CallbackT>::TraverseEdgeTo(Node dest, PendingEdge edge,
487-
hb_tag_t table_tag) {
489+
hb_tag_t table_tag) {
488490
if (!ShouldFollow(dest, table_tag, edge.required_feature)) {
489491
return absl::OkStatus();
490492
}
@@ -500,6 +502,123 @@ Status TraversalContext<CallbackT>::TraverseEdgeTo(Node dest, PendingEdge edge,
500502
return absl::OkStatus();
501503
}
502504

505+
template <typename CallbackT>
506+
absl::Status DependencyGraph::HandleOutgoingEdges(
507+
Node node, TraversalContext<CallbackT>* context) const {
508+
if (node.IsGlyph()) {
509+
TRYV(HandleGlyphOutgoingEdges(node.Id(), context));
510+
} else if (node.IsUnicode()) {
511+
TRYV(HandleUnicodeOutgoingEdges(node.Id(), context));
512+
} else if (node.IsSegment()) {
513+
HandleSegmentOutgoingEdges(node.Id(), context);
514+
} else if (node.IsFeature()) {
515+
TRYV(HandleFeatureOutgoingEdges(node.Id(), context));
516+
} else if (node.IsInitFont()) {
517+
HandleSubsetDefinitionOutgoingEdges(segmentation_info_->InitFontSegment(),
518+
context);
519+
}
520+
return absl::OkStatus();
521+
}
522+
523+
StatusOr<std::vector<Node>> DependencyGraph::TopologicalSorting() const {
524+
struct TopoCallback {
525+
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+
}
532+
};
533+
534+
CodepointSet non_init_font_codepoints =
535+
segmentation_info_->FullDefinition().codepoints;
536+
non_init_font_codepoints.subtract(
537+
segmentation_info_->InitFontSegment().codepoints);
538+
539+
flat_hash_set<hb_tag_t> non_init_font_features = full_feature_set_;
540+
for (hb_tag_t tag : segmentation_info_->InitFontSegment().feature_tags) {
541+
non_init_font_features.erase(tag);
542+
}
543+
544+
GlyphSet non_init_font_glyphs = segmentation_info_->NonInitFontGlyphs();
545+
546+
TraversalContext<TopoCallback> context;
547+
context.depend = dependency_graph_.get();
548+
context.full_closure = &segmentation_info_->FullClosure();
549+
context.enforce_context = false;
550+
context.unicode_filter = &non_init_font_codepoints;
551+
context.glyph_filter = &non_init_font_glyphs;
552+
context.feature_filter = &non_init_font_features;
553+
554+
// DFS topological sorting algorithm from:
555+
// https://en.wikipedia.org/wiki/Topological_sorting
556+
// Modified to use a stack instead of recursion.
557+
flat_hash_set<Node> visited;
558+
flat_hash_set<Node> visiting;
559+
std::vector<Node> post_order;
560+
std::vector<Node> dfs_stack;
561+
auto process_node = [&](Node start_node) -> Status {
562+
dfs_stack.push_back(start_node);
563+
while (!dfs_stack.empty()) {
564+
Node node = dfs_stack.back();
565+
if (visited.contains(node)) {
566+
dfs_stack.pop_back();
567+
continue;
568+
}
569+
if (visiting.contains(node)) {
570+
visiting.erase(node);
571+
visited.insert(node);
572+
post_order.push_back(node);
573+
dfs_stack.pop_back();
574+
continue;
575+
}
576+
577+
visiting.insert(node);
578+
context.callback.edges.clear();
579+
TRYV(HandleOutgoingEdges(node, &context));
580+
581+
std::vector<Node> current_edges = std::move(context.callback.edges);
582+
for (Node dest : current_edges) {
583+
if (visiting.contains(dest)) {
584+
return absl::InvalidArgumentError(
585+
absl::StrCat("Cycle detected during topological sort in "
586+
"dependency graph involving node: ",
587+
dest.ToString()));
588+
}
589+
if (!visited.contains(dest)) {
590+
dfs_stack.push_back(dest);
591+
}
592+
}
593+
}
594+
return absl::OkStatus();
595+
};
596+
597+
/* ### Run process_node for every possible, non init font node. #### */
598+
for (segment_index_t s : segmentation_info_->NonEmptySegments()) {
599+
TRYV(process_node(Node::Segment(s)));
600+
}
601+
602+
for (hb_codepoint_t u : non_init_font_codepoints) {
603+
TRYV(process_node(Node::Unicode(u)));
604+
}
605+
606+
for (hb_tag_t tag : non_init_font_features) {
607+
TRYV(process_node(Node::Feature(tag)));
608+
}
609+
610+
for (glyph_id_t gid : non_init_font_glyphs) {
611+
TRYV(process_node(Node::Glyph(gid)));
612+
}
613+
614+
std::vector<Node> topo_order;
615+
topo_order.reserve(post_order.size());
616+
for (auto it = post_order.rbegin(); it != post_order.rend(); ++it) {
617+
topo_order.push_back(*it);
618+
}
619+
620+
return topo_order;
621+
}
503622

504623
StatusOr<Traversal> DependencyGraph::TraverseGraph(
505624
TraversalContext<ClosureState>* context) const {
@@ -508,34 +627,16 @@ StatusOr<Traversal> DependencyGraph::TraverseGraph(
508627
while (true) {
509628
std::optional<Node> next = context->callback.GetNext();
510629
if (!next.has_value()) {
511-
if (TRY(context->CheckPending(dependency_graph_.get())) && !context->callback.next.empty()) {
630+
if (TRY(context->CheckPending(dependency_graph_.get())) &&
631+
!context->callback.next.empty()) {
512632
continue;
513633
} else {
514634
// nothing left to traverse.
515635
break;
516636
}
517637
}
518638

519-
if (next->IsGlyph()) {
520-
TRYV(HandleGlyphOutgoingEdges(next->Id(), context));
521-
}
522-
523-
if (next->IsUnicode()) {
524-
TRYV(HandleUnicodeOutgoingEdges(next->Id(), context));
525-
}
526-
527-
if (next->IsSegment()) {
528-
HandleSegmentOutgoingEdges(next->Id(), context);
529-
}
530-
531-
if (next->IsFeature()) {
532-
TRYV(HandleFeatureOutgoingEdges(next->Id(), context));
533-
}
534-
535-
if (next->IsInitFont()) {
536-
HandleSubsetDefinitionOutgoingEdges(segmentation_info_->InitFontSegment(),
537-
context);
538-
}
639+
TRYV(HandleOutgoingEdges(*next, context));
539640
}
540641

541642
for (const auto& edge : context->pending_edges()) {
@@ -676,7 +777,7 @@ Status DependencyGraph::ClosureSubTraversal(
676777
return absl::OkStatus();
677778
}
678779

679-
template<typename CallbackT>
780+
template <typename CallbackT>
680781
Status DependencyGraph::HandleUnicodeOutgoingEdges(
681782
hb_codepoint_t unicode, TraversalContext<CallbackT>* context) const {
682783
{
@@ -704,7 +805,7 @@ Status DependencyGraph::HandleUnicodeOutgoingEdges(
704805
return absl::OkStatus();
705806
}
706807

707-
template<typename CallbackT>
808+
template <typename CallbackT>
708809
Status DependencyGraph::HandleGlyphOutgoingEdges(
709810
glyph_id_t gid, TraversalContext<CallbackT>* context) const {
710811
hb_codepoint_t index = 0;
@@ -735,7 +836,7 @@ Status DependencyGraph::HandleGlyphOutgoingEdges(
735836
return absl::OkStatus();
736837
}
737838

738-
template<typename CallbackT>
839+
template <typename CallbackT>
739840
Status DependencyGraph::HandleGsubGlyphOutgoingEdges(
740841
glyph_id_t source_gid, glyph_id_t dest_gid, hb_tag_t layout_tag,
741842
hb_codepoint_t ligature_set, hb_codepoint_t context_set,
@@ -751,7 +852,7 @@ Status DependencyGraph::HandleGsubGlyphOutgoingEdges(
751852
}
752853
}
753854

754-
template<typename CallbackT>
855+
template <typename CallbackT>
755856
Status DependencyGraph::HandleFeatureOutgoingEdges(
756857
hb_tag_t feature_tag, TraversalContext<CallbackT>* context) const {
757858
if (!context->table_filter.contains(GSUB)) {
@@ -778,7 +879,7 @@ Status DependencyGraph::HandleFeatureOutgoingEdges(
778879
return absl::OkStatus();
779880
}
780881

781-
template<typename CallbackT>
882+
template <typename CallbackT>
782883
void DependencyGraph::HandleSegmentOutgoingEdges(
783884
segment_index_t id, TraversalContext<CallbackT>* context) const {
784885
if (id >= segmentation_info_->Segments().size()) {
@@ -790,9 +891,10 @@ void DependencyGraph::HandleSegmentOutgoingEdges(
790891
HandleSubsetDefinitionOutgoingEdges(s.Definition(), context);
791892
}
792893

793-
template<typename CallbackT>
894+
template <typename CallbackT>
794895
void DependencyGraph::HandleSubsetDefinitionOutgoingEdges(
795-
const SubsetDefinition& subset_def, TraversalContext<CallbackT>* context) const {
896+
const SubsetDefinition& subset_def,
897+
TraversalContext<CallbackT>* context) const {
796898
for (hb_codepoint_t u : subset_def.codepoints) {
797899
context->TraverseEdgeTo(Node::Unicode(u));
798900
}
@@ -969,4 +1071,6 @@ DependencyGraph::ComputeFeatureEdges() const {
9691071
return out;
9701072
}
9711073

1074+
void PrintTo(const Node& node, std::ostream* os) { *os << node.ToString(); }
1075+
9721076
} // namespace ift::dep_graph

0 commit comments

Comments
 (0)