@@ -28,19 +28,21 @@ using ift::dep_graph::Traversal;
2828namespace ift ::encoder {
2929
3030DependencyClosure::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
7676Status 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(
247244StatusOr<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+
302388void 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
346432Status 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
376474Status 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
399518void 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
0 commit comments