Skip to content

Commit 8864af8

Browse files
committed
Scope reprocessing in ReassignInitSubset() utilizing the dependency graph.
We can use the depedency graph to exclude segments that cannot be affected by the init font definition change from being re-analyzed.
1 parent 352b796 commit 8864af8

16 files changed

+557
-181
lines changed

ift/dep_graph/dependency_graph.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ namespace ift::dep_graph {
3030
* Allows exploring glyph depedencies within a font.
3131
*/
3232
class DependencyGraph {
33-
// TODO XXXX some basic tests for this class.
3433
public:
3534
static absl::StatusOr<DependencyGraph> Create(
3635
const ift::encoder::RequestedSegmentationInformation* segmentation_info,

ift/dep_graph/dependency_graph_test.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ TEST_F(DependencyGraphTest, ContextGlyphTraversal) {
161161
ASSERT_EQ(traversal.ContextGlyphs(), (GlyphSet {}));
162162
}
163163

164-
// TODO XXXX more tests:
165-
// - default filtering.
166-
// - ...
164+
// TODO(garretrieger) we currently only have a few specialized tests, relyng primarily on DepedencyClosureTest
165+
// for coverage of DepedencyGraph functionality. We should add some basic tests here that test DepedencyGraph
166+
// core features in isolation.
167167

168168
} // namespace ift::dep_graph

ift/encoder/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ cc_library(
6565
"estimated_patch_size_cache.cc",
6666
"estimated_patch_size_cache.h",
6767
"glyph_condition_set.h",
68+
"glyph_condition_set.cc",
6869
"glyph_groupings.cc",
6970
"glyph_groupings.h",
7071
"invalidation_set.h",

ift/encoder/closure_glyph_segmenter_test.cc

Lines changed: 107 additions & 55 deletions
Large diffs are not rendered by default.

ift/encoder/dependency_closure.cc

Lines changed: 162 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@ using ift::dep_graph::Traversal;
2828
namespace ift::encoder {
2929

3030
DependencyClosure::AnalysisAccuracy DependencyClosure::TraversalAccuracy(const Traversal& traversal) const {
31-
// TODO(garretrieger): implement handling for these tables to allow them to be removed
32-
// from the disallowed list.
33-
// - cmap: needs special handling for UVS edges
31+
// TODO(garretrieger): there's several types of depedencies that we do not handle yet and as a result
32+
// consider inaccurate. Adding support for these will allow the dep graph to be used more widely:
33+
// - UVS edges. Simple case when all conditions are satisfied, more complex case is generating conjunctive
34+
// conditions from them.
35+
// - Ligatures: at least in simple non-nested cases we should be able to generate the corresponding conditions.
36+
// - Features: features create conjunctive conditions, we should be able to handle these.
3437

35-
// TODO XXXXXXX allow UVS edges if their conditions are fully satisfied.
3638
if (traversal.HasOnlyLigaConditionalGlyphs()) {
3739
// When liga glyphs are present and accurate analysis is still possible
3840
// if all of the liga glyphs have been reached.
3941
GlyphSet required_liga = traversal.RequiredLigaGlyphs();
4042
required_liga.subtract(traversal.ReachableGlyphs());
4143
required_liga.subtract(segmentation_info_->InitFontGlyphs());
4244
if (!required_liga.empty()) {
43-
// TODO XXXX if there are nested ligatures this may be too simplisitic. Since reachable glyphs
45+
// TODO(garretrieger): if there are nested ligatures this may be too simplisitic. Since reachable glyphs
4446
// might contain things are aren't actually accessible from the starting point (ie. ligature which
4547
// isn't triggerable from the starting set adds a glyph that unlocks it). Try and construct a
4648
// test case to demonstrate this.
@@ -53,8 +55,6 @@ DependencyClosure::AnalysisAccuracy DependencyClosure::TraversalAccuracy(const T
5355
}
5456

5557
for (hb_tag_t tag : traversal.ContextLayoutFeatures()) {
56-
// TODO XXXX broader feature support. For now to keep things simple only allow features in the init font
57-
// which are always enabled no matter what.
5858
if (!init_font_features_.contains(tag)) {
5959
return AnalysisAccuracy::INACCURATE;
6060
}
@@ -76,19 +76,16 @@ DependencyClosure::AnalysisAccuracy DependencyClosure::TraversalAccuracy(const T
7676
Status DependencyClosure::SegmentsChanged(bool init_font_change, const SegmentSet& segments) {
7777
VLOG(1) << "DependencyClosure::SegmentsChanged()";
7878

79-
if (init_font_change) {
80-
ClearReachabilityIndex();
81-
} else {
82-
TRYV(UpdateReachabilityIndex(segments));
83-
}
79+
TRYV(UpdateReachabilityIndex(segments));
8480

8581
if (!init_font_change && segmentation_info_->SegmentsAreDisjoint()) {
86-
// If the init font is changed and all segments are disjoint then there won't be any changes to incoming
82+
// If the init font is not changed and all segments are disjoint then there won't be any changes to incoming
8783
// edge counts as segment modifications will just shift outgoing edges around between segments.
8884
return absl::OkStatus();
8985
}
9086

91-
// TODO XXXXX can we do an incremental update of incoming_edge_counts_, and context
87+
// TODO(garretrieger): can we do an incremental update of incoming_edge_counts_, and context?
88+
// not high priority as this does not currently show up as problematic in profiles.
9289
btree_set<Node> nodes;
9390
for (segment_index_t s = 0; s < segmentation_info_->Segments().size(); s++) {
9491
if (segmentation_info_->Segments().at(s).Definition().Empty()) {
@@ -247,8 +244,9 @@ StatusOr<IntSet> DependencyClosure::InitFeatureSet(
247244
StatusOr<SegmentSet> DependencyClosure::SegmentsThatInteractWith(const GlyphSet& glyphs) {
248245
TRYV(EnsureReachabilityIndexPopulated());
249246

250-
// TODO XXXXX also need to check interactions with:
251-
// - features (including segments with features), treated as additional context items.
247+
// TODO(garretrieger): we can narrow the set by considering context glyphs per activated glyph
248+
// instead of just the whole set of context glyphs.
249+
252250
SegmentSet visited_segments;
253251
GlyphSet visited_glyphs;
254252
btree_set<hb_tag_t> visited_features;
@@ -299,6 +297,94 @@ StatusOr<SegmentSet> DependencyClosure::SegmentsThatInteractWith(const GlyphSet&
299297
return visited_segments;
300298
}
301299

300+
StatusOr<SegmentSet> DependencyClosure::SegmentInteractionGroup(const SegmentSet& segments) {
301+
TRYV(EnsureReachabilityIndexPopulated());
302+
303+
SegmentSet to_check = segments;
304+
SegmentSet visited;
305+
306+
SegmentSet init_font_group = InitFontConnections();
307+
if (segments.intersects(init_font_group)) {
308+
to_check.union_set(init_font_group);
309+
}
310+
311+
while(!to_check.empty()) {
312+
segment_index_t next = *to_check.begin();
313+
to_check.erase(next);
314+
visited.insert(next);
315+
316+
SegmentSet connected = ConnectedSegments(next);
317+
connected.subtract(visited);
318+
to_check.union_set(connected);
319+
}
320+
321+
return visited;
322+
}
323+
324+
SegmentSet DependencyClosure::ConnectedSegments(segment_index_t s) {
325+
// TODO(garretrieger): similar to what we do in SegmentsThatInteractWith we should keep a glyph and features
326+
// visited sets (we'll need one for context and one for reachable) to avoid unnecessary checks.
327+
// TODO(garretrieger): a narrower set of connections should be possible if we use context per glyph instead
328+
// of the full context glyph sets.
329+
SegmentSet connected;
330+
331+
for (glyph_id_t gid : glyphs_that_can_be_reached_.at(s)) {
332+
connected.union_set(segments_that_have_context_glyph_.at(gid));
333+
connected.union_set(segments_that_can_reach_.at(gid));
334+
}
335+
336+
for (glyph_id_t gid : segment_context_glyphs_.at(s)) {
337+
connected.union_set(segments_that_can_reach_.at(gid));
338+
}
339+
340+
for (hb_tag_t tag : features_that_can_be_reached_.at(s)) {
341+
for (segment_index_t s :segments_that_have_context_feature_.at(tag)) {
342+
connected.insert(s);
343+
}
344+
for (segment_index_t s :segments_that_can_reach_feature_.at(tag)) {
345+
connected.insert(s);
346+
}
347+
}
348+
349+
for (hb_tag_t tag : segment_context_features_.at(s)) {
350+
for (segment_index_t s :segments_that_can_reach_feature_.at(tag)) {
351+
connected.insert(s);
352+
}
353+
}
354+
355+
return connected;
356+
}
357+
358+
SegmentSet DependencyClosure::InitFontConnections() {
359+
SegmentSet connected;
360+
361+
for (glyph_id_t gid : init_font_reachable_glyphs_) {
362+
connected.union_set(segments_that_have_context_glyph_.at(gid));
363+
connected.union_set(segments_that_can_reach_.at(gid));
364+
}
365+
366+
for (glyph_id_t gid : init_font_context_glyphs_) {
367+
connected.union_set(segments_that_can_reach_.at(gid));
368+
}
369+
370+
for (hb_tag_t tag : init_font_features_) {
371+
for (segment_index_t s :segments_that_have_context_feature_.at(tag)) {
372+
connected.insert(s);
373+
}
374+
for (segment_index_t s :segments_that_can_reach_feature_.at(tag)) {
375+
connected.insert(s);
376+
}
377+
}
378+
379+
for (hb_tag_t tag : init_font_context_features_) {
380+
for (segment_index_t s :segments_that_can_reach_feature_.at(tag)) {
381+
connected.insert(s);
382+
}
383+
}
384+
385+
return connected;
386+
}
387+
302388
void DependencyClosure::ReachabilityInitFontAddToCheck(
303389
GlyphSet& visited_glyphs, btree_set<hb_tag_t>& visited_features,
304390
GlyphSet& to_check, btree_set<hb_tag_t>& features_to_check
@@ -344,36 +430,53 @@ Status DependencyClosure::ReachabilitySegmentsAddToCheck(
344430
}
345431

346432
Status DependencyClosure::EnsureReachabilityIndexPopulated() {
347-
if (!segments_that_can_reach_.empty() || !glyphs_that_can_be_reached_.empty()) {
433+
if (reachability_index_valid_) {
348434
return absl::OkStatus();
349435
}
350436
return UpdateReachabilityIndex(SegmentSet::all());
351437
}
352438

353-
Status DependencyClosure::UpdateReachabilityIndex(const common::SegmentSet& segments) {
354-
if (!segments_that_can_reach_.empty() || !glyphs_that_can_be_reached_.empty()) {
439+
Status DependencyClosure::UpdateReachabilityIndex(common::SegmentSet segments) {
440+
if (reachability_index_valid_) {
355441
// If indices have existing data, then we need to ensure prior entries for the
356442
// segments to be updated are cleared out.
357443
for (segment_index_t s : segments) {
358-
if (s < segmentation_info_->Segments().size()) {
359-
ClearReachabilityIndex(s);
360-
} else {
444+
if (s >= segmentation_info_->Segments().size()) {
361445
break;
362446
}
447+
ClearReachabilityIndex(s);
448+
}
449+
} else {
450+
// If the index isn't built yet then all segments need to be updated. Also
451+
// ensure that records exist for all glyphs and segments. This simplifies
452+
// code using the index since it can assume records exist.
453+
segments = SegmentSet::all();
454+
for (glyph_id_t gid : segmentation_info_->FullClosure()) {
455+
segments_that_can_reach_.insert(std::pair(gid, SegmentSet()));
456+
segments_that_have_context_glyph_.insert(std::pair(gid, SegmentSet {}));
457+
}
458+
for (hb_tag_t tag : graph_.FullFeatureSet()) {
459+
segments_that_can_reach_feature_.insert(std::pair(tag, SegmentSet {}));
460+
segments_that_have_context_feature_.insert(std::pair(tag, SegmentSet {}));
363461
}
364462
}
365463

366464
for (segment_index_t s : segments) {
367-
if (s < segmentation_info_->Segments().size()) {
368-
TRYV(UpdateReachabilityIndex(s));
369-
} else {
465+
if (s >= segmentation_info_->Segments().size()) {
370466
break;
371467
}
468+
TRYV(UpdateReachabilityIndex(s));
372469
}
470+
reachability_index_valid_ = true;
373471
return absl::OkStatus();
374472
}
375473

376474
Status DependencyClosure::UpdateReachabilityIndex(segment_index_t s) {
475+
glyphs_that_can_be_reached_.insert(std::pair(s, GlyphSet {}));
476+
segment_context_glyphs_.insert(std::pair(s, GlyphSet {}));
477+
features_that_can_be_reached_.insert(std::pair(s, btree_set<hb_tag_t> {}));
478+
segment_context_features_.insert(std::pair(s, btree_set<hb_tag_t> {}));
479+
377480
auto traversal = TRY(graph_.TraverseGraph(btree_set<Node> {Node::Segment(s)}));
378481

379482
for (glyph_id_t g : traversal.ReachableGlyphs()) {
@@ -386,6 +489,16 @@ Status DependencyClosure::UpdateReachabilityIndex(segment_index_t s) {
386489
features_that_can_be_reached_[s].insert(f);
387490
}
388491

492+
for (glyph_id_t g : traversal.ContextGlyphs()) {
493+
segments_that_have_context_glyph_[g].insert(s);
494+
segment_context_glyphs_[s].insert(g);
495+
}
496+
497+
for (hb_tag_t f : traversal.ContextLayoutFeatures()) {
498+
segments_that_have_context_feature_[f].insert(s);
499+
segment_context_features_[s].insert(f);
500+
}
501+
389502
return absl::OkStatus();
390503
}
391504

@@ -394,6 +507,12 @@ void DependencyClosure::ClearReachabilityIndex() {
394507
segments_that_can_reach_.clear();
395508
segments_that_can_reach_feature_.clear();
396509
features_that_can_be_reached_.clear();
510+
511+
segments_that_have_context_glyph_.clear();
512+
segment_context_glyphs_.clear();
513+
segments_that_have_context_feature_.clear();
514+
segment_context_features_.clear();
515+
reachability_index_valid_ = false;
397516
}
398517

399518
void DependencyClosure::ClearReachabilityIndex(segment_index_t segment) {
@@ -402,15 +521,31 @@ void DependencyClosure::ClearReachabilityIndex(segment_index_t segment) {
402521
for (glyph_id_t gid : glyphs->second) {
403522
segments_that_can_reach_[gid].erase(segment);
404523
}
405-
glyphs_that_can_be_reached_.erase(glyphs);
524+
glyphs->second.clear();
406525
}
407526

408527
auto features = features_that_can_be_reached_.find(segment);
409528
if (features != features_that_can_be_reached_.end()) {
410529
for (hb_tag_t tag : features->second) {
411530
segments_that_can_reach_feature_[tag].erase(segment);
412531
}
413-
features_that_can_be_reached_.erase(features);
532+
features->second.clear();
533+
}
534+
535+
glyphs = segment_context_glyphs_.find(segment);
536+
if (glyphs != segment_context_glyphs_.end()) {
537+
for (glyph_id_t gid : glyphs->second) {
538+
segments_that_have_context_glyph_[gid].erase(segment);
539+
}
540+
glyphs->second.clear();
541+
}
542+
543+
features = segment_context_features_.find(segment);
544+
if (features != segment_context_features_.end()) {
545+
for (hb_tag_t tag : features->second) {
546+
segments_that_have_context_feature_[tag].erase(segment);
547+
}
548+
features->second.clear();
414549
}
415550
}
416551

ift/encoder/dependency_closure.h

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class DependencyClosure {
8383
// may be overestimated.
8484
absl::StatusOr<common::SegmentSet> SegmentsThatInteractWith(const common::GlyphSet& glyphs);
8585

86+
absl::StatusOr<common::SegmentSet> SegmentInteractionGroup(const common::SegmentSet& segments);
87+
8688
uint64_t AccurateResults() const { return accurate_results_; }
8789
uint64_t InaccurateResults() const { return inaccurate_results_; }
8890

@@ -100,10 +102,6 @@ class DependencyClosure {
100102

101103
AnalysisAccuracy TraversalAccuracy(const dep_graph::Traversal& traversal) const;
102104

103-
static absl::StatusOr<common::IntSet> FullFeatureSet(
104-
const RequestedSegmentationInformation* segmentation_info,
105-
hb_face_t* face);
106-
107105
static absl::flat_hash_map<hb_codepoint_t, glyph_id_t> UnicodeToGid(
108106
hb_face_t* face);
109107

@@ -126,8 +124,11 @@ class DependencyClosure {
126124
absl::btree_set<hb_tag_t>& features_to_check
127125
) const;
128126

127+
common::SegmentSet ConnectedSegments(segment_index_t s);
128+
common::SegmentSet InitFontConnections();
129+
129130
absl::Status EnsureReachabilityIndexPopulated();
130-
absl::Status UpdateReachabilityIndex(const common::SegmentSet& segments);
131+
absl::Status UpdateReachabilityIndex(common::SegmentSet segments);
131132
absl::Status UpdateReachabilityIndex(segment_index_t segment);
132133
void ClearReachabilityIndex();
133134
void ClearReachabilityIndex(segment_index_t segment);
@@ -144,16 +145,16 @@ class DependencyClosure {
144145

145146
// Reachability indexes: these indexes are used to quickly locate segments reachable
146147
// from glyph and features (and in reverse as well).
148+
bool reachability_index_valid_ = false;
147149
absl::flat_hash_map<glyph_id_t, common::SegmentSet> segments_that_can_reach_;
148150
absl::flat_hash_map<segment_index_t, common::GlyphSet> glyphs_that_can_be_reached_;
149151
absl::flat_hash_map<hb_tag_t, common::SegmentSet> segments_that_can_reach_feature_;
150152
absl::flat_hash_map<segment_index_t, absl::btree_set<hb_tag_t>> features_that_can_be_reached_;
151153

152-
153-
154-
// TODO XXXXX cache segment -> context glyphs and utilize in SegmentsThatInteractWith()
155-
156-
154+
absl::flat_hash_map<glyph_id_t, common::SegmentSet> segments_that_have_context_glyph_;
155+
absl::flat_hash_map<segment_index_t, common::GlyphSet> segment_context_glyphs_;
156+
absl::flat_hash_map<hb_tag_t, common::SegmentSet> segments_that_have_context_feature_;
157+
absl::flat_hash_map<segment_index_t, absl::btree_set<hb_tag_t>> segment_context_features_;
157158

158159
uint64_t accurate_results_ = 0;
159160
uint64_t inaccurate_results_ = 0;

0 commit comments

Comments
 (0)