Skip to content

Commit 42d3930

Browse files
committed
Update GlyphConditionSet and GlyphGroupings to utilize ActivationCondition's internally.
Currently this introduces no behaviour changes, but will allow glyph condition set to work with composite conditions in addition to the purely conjunctive/disjunctive conditions it currently handles.
1 parent 0b8646b commit 42d3930

13 files changed

+347
-190
lines changed

ift/encoder/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ cc_library(
6363
"glyph_condition_set.cc",
6464
"glyph_groupings.cc",
6565
"glyph_groupings.h",
66+
"condition_to_glyphs_index.h",
6667
"invalidation_set.h",
6768
"merger.cc",
6869
"merger.h",

ift/encoder/activation_condition.cc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,14 @@ static void Simplify(std::vector<SegmentSet>& conditions) {
124124

125125
ActivationCondition ActivationCondition::And(const ActivationCondition& a,
126126
const ActivationCondition& b) {
127+
bool a_exclusive = a.IsExclusive() || a.IsAlwaysTrue();
128+
bool b_exclusive = b.IsExclusive() || b.IsAlwaysTrue();
129+
127130
ActivationCondition condition = a;
128131
condition.conditions_.insert(condition.conditions_.end(),
129132
b.conditions().begin(), b.conditions().end());
130133
Simplify(condition.conditions_);
131-
condition.is_exclusive_ = false;
134+
condition.is_exclusive_ = a_exclusive && b_exclusive && condition.IsUnitary();
132135
return condition;
133136
}
134137

@@ -247,8 +250,7 @@ bool ActivationCondition::operator<(const ActivationCondition& other) const {
247250
return !is_fallback_;
248251
}
249252

250-
// These two are equal
251-
return false;
253+
return encoding_ < other.encoding_;
252254
}
253255

254256
template <typename ProtoType>

ift/encoder/activation_condition.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,18 @@ class ActivationCondition {
177177

178178
ift::config::ActivationConditionProto ToConfigProto() const;
179179

180+
template <typename H>
181+
friend H AbslHashValue(H h, const ActivationCondition& c) {
182+
return H::combine(std::move(h), c.conditions_, c.activated_, c.is_fallback_,
183+
c.is_exclusive_, c.encoding_);
184+
}
185+
180186
bool operator<(const ActivationCondition& other) const;
181187

182188
bool operator==(const ActivationCondition& other) const {
183189
return conditions_ == other.conditions_ && activated_ == other.activated_ &&
184190
is_fallback_ == other.is_fallback_ &&
185-
is_exclusive_ == other.is_exclusive_;
191+
is_exclusive_ == other.is_exclusive_ && encoding_ == other.encoding_;
186192
}
187193

188194
bool operator!=(const ActivationCondition& other) const {

ift/encoder/activation_condition_test.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,26 @@ TEST(ActivationConditionTest, And) {
418418
EXPECT_EQ(combined_ba.activated(), 11);
419419
}
420420

421+
TEST(ActivationConditionTest, And_Exclusiveness) {
422+
auto tru = ActivationCondition::True(0);
423+
auto exc_a = ActivationCondition::exclusive_segment(10, 0);
424+
auto exc_b = ActivationCondition::exclusive_segment(20, 0);
425+
auto non_exc_b = ActivationCondition::and_segments({20}, 0);
426+
427+
// For and And combination to be exclusive the result must
428+
// be unitary and both inputs are exclusive (or one of the inputs
429+
// is always true).
430+
431+
ASSERT_TRUE(ActivationCondition::And(tru, exc_a).IsExclusive());
432+
ASSERT_TRUE(ActivationCondition::And(exc_a, tru).IsExclusive());
433+
434+
ASSERT_FALSE(ActivationCondition::And(exc_a, exc_b).IsExclusive());
435+
ASSERT_FALSE(ActivationCondition::And(exc_b, exc_a).IsExclusive());
436+
ASSERT_TRUE(ActivationCondition::And(exc_a, exc_a).IsExclusive());
437+
438+
ASSERT_FALSE(ActivationCondition::And(exc_b, non_exc_b).IsExclusive());
439+
}
440+
421441
TEST(ActivationConditionTest, And_Simplification) {
422442
auto a = ActivationCondition::or_segments({1, 2}, 10);
423443
auto b = ActivationCondition::or_segments({1}, 10);

ift/encoder/complex_condition_finder.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,12 @@ static btree_map<SegmentSet, GlyphSet> ExistingConditions(
259259
btree_map<glyph_id_t, SegmentSet>& glyph_to_conditions) {
260260
btree_map<SegmentSet, GlyphSet> existing_conditions;
261261
for (glyph_id_t gid : glyphs) {
262-
SegmentSet or_segments = glyph_condition_set.ConditionsFor(gid).or_segments;
263-
if (or_segments.empty()) {
262+
const auto& condition = glyph_condition_set.ConditionsFor(gid).activation();
263+
if (condition.IsExclusive() || !condition.IsPurelyDisjunctive()) {
264264
continue;
265265
}
266+
267+
SegmentSet or_segments = condition.TriggeringSegments();
266268
existing_conditions[or_segments].insert(gid);
267269
glyph_to_conditions[gid].union_set(or_segments);
268270
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#ifndef IFT_ENCODER_CONDITION_TO_GLYPHS_INDEX_H_
2+
#define IFT_ENCODER_CONDITION_TO_GLYPHS_INDEX_H_
3+
4+
#include <optional>
5+
6+
#include "absl/container/btree_map.h"
7+
#include "ift/encoder/activation_condition.h"
8+
#include "ift/encoder/types.h"
9+
10+
namespace ift::encoder {
11+
12+
// Stores a mapping from ActivationCondition to a set of glyphs.
13+
//
14+
// Also maintains the reverse mapping indices.
15+
class ConditionToGlyphsIndex {
16+
public:
17+
std::optional<ActivationCondition> Invalidate(glyph_id_t gid) {
18+
auto it = glyph_to_condition_.find(gid);
19+
if (it == glyph_to_condition_.end()) {
20+
return std::nullopt;
21+
}
22+
23+
ActivationCondition condition = it->second;
24+
25+
auto& glyphs = conditions_and_glyphs_.at(condition);
26+
glyphs.erase(gid);
27+
glyph_to_condition_.erase(gid);
28+
29+
if (glyphs.empty()) {
30+
Remove(condition);
31+
}
32+
return condition;
33+
}
34+
35+
void Remove(ActivationCondition condition) {
36+
auto it = conditions_and_glyphs_.find(condition);
37+
if (it == conditions_and_glyphs_.end()) {
38+
return;
39+
}
40+
41+
for (glyph_id_t gid : it->second) {
42+
glyph_to_condition_.erase(gid);
43+
}
44+
45+
conditions_and_glyphs_.erase(it);
46+
47+
for (segment_index_t s : condition.TriggeringSegments()) {
48+
auto it = triggering_segment_to_conditions_.find(s);
49+
if (it != triggering_segment_to_conditions_.end()) {
50+
it->second.erase(condition);
51+
if (it->second.empty()) {
52+
triggering_segment_to_conditions_.erase(it);
53+
}
54+
}
55+
}
56+
}
57+
58+
absl::Status Union(ActivationCondition condition, common::GlyphSet glyphs) {
59+
conditions_and_glyphs_[condition].union_set(glyphs);
60+
61+
for (segment_index_t s : condition.TriggeringSegments()) {
62+
triggering_segment_to_conditions_[s].insert(condition);
63+
}
64+
65+
for (glyph_id_t gid : glyphs) {
66+
auto [it, did_insert] =
67+
glyph_to_condition_.insert(std::pair(gid, condition));
68+
if (!did_insert && it->second != condition) {
69+
return absl::InternalError(
70+
"glyph_to_condition mapping does not match existing one.");
71+
}
72+
}
73+
74+
return absl::OkStatus();
75+
}
76+
77+
absl::Status Add(ActivationCondition condition, common::GlyphSet glyphs) {
78+
const auto& [new_value_it, did_insert] =
79+
conditions_and_glyphs_.insert(std::pair(condition, glyphs));
80+
81+
if (!did_insert) {
82+
// If there's an existing value it must match what we're trying to add
83+
if (!new_value_it->second.is_subset_of(glyphs)) {
84+
return absl::InternalError(absl::StrCat(
85+
"Trying to add a condition and glyph mapping (",
86+
condition.ToString(), " => ", glyphs.ToString(),
87+
") which "
88+
"would override an existing mapping (",
89+
new_value_it->first.ToString(), " => ",
90+
new_value_it->second.ToString(), ") to a different value."));
91+
}
92+
93+
// We allow overrides that only increase the glyph set.
94+
glyphs.subtract(new_value_it->second);
95+
new_value_it->second.union_set(glyphs);
96+
} else {
97+
for (segment_index_t s : condition.TriggeringSegments()) {
98+
triggering_segment_to_conditions_[s].insert(new_value_it->first);
99+
}
100+
}
101+
102+
for (glyph_id_t gid : glyphs) {
103+
bool did_insert =
104+
glyph_to_condition_.insert(std::pair(gid, new_value_it->first))
105+
.second;
106+
if (!did_insert) {
107+
return absl::InternalError(
108+
"Unexpected existing glyph to condition mapping.");
109+
}
110+
}
111+
112+
return absl::OkStatus();
113+
}
114+
115+
const absl::btree_map<ActivationCondition, common::GlyphSet>&
116+
ConditionsAndGlyphs() const {
117+
return conditions_and_glyphs_;
118+
}
119+
120+
const absl::flat_hash_map<glyph_id_t, ActivationCondition>& GlyphToCondition()
121+
const {
122+
return glyph_to_condition_;
123+
}
124+
125+
const absl::flat_hash_map<segment_index_t,
126+
absl::btree_set<ActivationCondition>>&
127+
TriggeringSegmentToConditions() const {
128+
return triggering_segment_to_conditions_;
129+
}
130+
131+
bool operator==(const ConditionToGlyphsIndex& other) const {
132+
return conditions_and_glyphs_ == other.conditions_and_glyphs_ &&
133+
glyph_to_condition_ == other.glyph_to_condition_ &&
134+
triggering_segment_to_conditions_ ==
135+
other.triggering_segment_to_conditions_;
136+
}
137+
138+
private:
139+
absl::btree_map<ActivationCondition, common::GlyphSet> conditions_and_glyphs_;
140+
absl::flat_hash_map<glyph_id_t, ActivationCondition> glyph_to_condition_;
141+
absl::flat_hash_map<segment_index_t, absl::btree_set<ActivationCondition>>
142+
triggering_segment_to_conditions_;
143+
};
144+
145+
} // namespace ift::encoder
146+
147+
#endif // IFT_ENCODER_CONDITION_TO_GLYPHS_INDEX_H_

ift/encoder/dependency_closure.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ DependencyClosure::AnalyzeSegmentInternal(
209209
auto inscope_nodes = SegmentsToAffectedNodeConditions(segments);
210210
GlyphSet inscope_glyphs;
211211
for (Node node : inscope_nodes) {
212-
if (node.IsGlyph() && segmentation_info_->NonInitFontGlyphs().contains(node.Id())) {
212+
if (node.IsGlyph() &&
213+
segmentation_info_->NonInitFontGlyphs().contains(node.Id())) {
213214
inscope_glyphs.insert(node.Id());
214215
}
215216
}

ift/encoder/dependency_closure.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ class DependencyClosure {
127127
#ifdef HB_DEPEND_API
128128

129129
// Extracts the full activations conditions (as specified by the dependency
130-
// graph) for all graph nodes. In some cases may overestimate activation conditions
131-
// versus real subsetting closure due to reliance on the dependency graph.
130+
// graph) for all graph nodes. In some cases may overestimate activation
131+
// conditions versus real subsetting closure due to reliance on the dependency
132+
// graph.
132133
absl::StatusOr<absl::flat_hash_map<dep_graph::Node, ActivationCondition>>
133134
ExtractAllNodeConditions() const;
134135

ift/encoder/glyph_condition_set.cc

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ void PrintTo(const GlyphConditionSet& set, std::ostream* os) {
99
*os << "Glyph Condition Set {" << std::endl;
1010
glyph_id_t gid = 0;
1111
for (const auto& c : set.gid_conditions_) {
12-
if (!c.and_segments.empty() || !c.or_segments.empty()) {
13-
*os << " g" << gid << ": ";
14-
*os << "OR " << c.or_segments.ToString();
15-
*os << ", AND " << c.and_segments.ToString();
16-
*os << std::endl;
12+
if (!c.activation().IsAlwaysTrue()) {
13+
*os << " g" << gid << ": " << c.activation().ToString() << std::endl;
1714
}
1815
gid++;
1916
}
@@ -22,9 +19,8 @@ void PrintTo(const GlyphConditionSet& set, std::ostream* os) {
2219

2320
static void PrintCondition(glyph_id_t gid, const GlyphConditions& condition,
2421
bool added) {
25-
VLOG(0) << (added ? "++ " : "-- ") << "g" << gid << ": OR "
26-
<< condition.or_segments.ToString() << ", "
27-
<< ": AND " << condition.or_segments.ToString();
22+
VLOG(0) << (added ? "++ " : "-- ") << "g" << gid << ": "
23+
<< condition.activation().ToString();
2824
}
2925

3026
void GlyphConditionSet::PrintDiff(const GlyphConditionSet& a,

ift/encoder/glyph_condition_set.h

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,52 @@
55

66
#include "absl/container/flat_hash_map.h"
77
#include "ift/common/int_set.h"
8+
#include "ift/encoder/activation_condition.h"
89
#include "ift/encoder/types.h"
910

1011
namespace ift::encoder {
1112

13+
class GlyphConditionSet;
14+
1215
/*
1316
* A set of conditions which activate a specific single glyph.
1417
*/
1518
class GlyphConditions {
19+
friend GlyphConditionSet;
20+
1621
public:
17-
GlyphConditions() : and_segments(), or_segments() {}
18-
ift::common::SegmentSet and_segments;
19-
ift::common::SegmentSet or_segments;
22+
GlyphConditions() : condition_(ActivationCondition::True(0)) {}
23+
24+
const ActivationCondition& activation() const { return condition_; }
2025

2126
bool operator==(const GlyphConditions& other) const {
22-
return other.and_segments == and_segments &&
23-
other.or_segments == or_segments;
27+
return condition_ == other.condition_;
2428
}
2529

26-
void RemoveSegments(const ift::common::SegmentSet& segments) {
27-
and_segments.subtract(segments);
28-
or_segments.subtract(segments);
30+
void RemoveSegments(const common::SegmentSet& segments) {
31+
bool is_exclusive = condition_.IsExclusive();
32+
std::vector<common::SegmentSet> new_conditions;
33+
for (const auto& sub_group : condition_.conditions()) {
34+
common::SegmentSet modified = sub_group;
35+
modified.subtract(segments);
36+
if (!modified.empty()) {
37+
new_conditions.push_back(modified);
38+
}
39+
}
40+
41+
if (new_conditions.empty()) {
42+
condition_ = ActivationCondition::True(0);
43+
} else if (is_exclusive && new_conditions.size() == 1 &&
44+
new_conditions.begin()->size() == 1) {
45+
condition_ = ActivationCondition::exclusive_segment(
46+
*new_conditions.begin()->min(), 0);
47+
} else {
48+
condition_ = ActivationCondition::composite_condition(new_conditions, 0);
49+
}
2950
}
51+
52+
private:
53+
ActivationCondition condition_;
3054
};
3155

3256
/*
@@ -44,12 +68,24 @@ class GlyphConditionSet {
4468
}
4569

4670
void AddAndCondition(glyph_id_t gid, segment_index_t segment) {
47-
gid_conditions_[gid].and_segments.insert(segment);
71+
auto& condition = gid_conditions_[gid].condition_;
72+
if (condition.IsAlwaysTrue()) {
73+
condition = ActivationCondition::exclusive_segment(segment, 0);
74+
} else {
75+
condition = ActivationCondition::And(
76+
condition, ActivationCondition::exclusive_segment(segment, 0));
77+
}
4878
segment_to_gid_conditions_[segment].insert(gid);
4979
}
5080

5181
void AddOrCondition(glyph_id_t gid, segment_index_t segment) {
52-
gid_conditions_[gid].or_segments.insert(segment);
82+
auto& condition = gid_conditions_[gid].condition_;
83+
if (condition.IsAlwaysTrue()) {
84+
condition = ActivationCondition::or_segments({segment}, 0);
85+
} else {
86+
condition = ActivationCondition::Or(
87+
condition, ActivationCondition::or_segments({segment}, 0));
88+
}
5389
segment_to_gid_conditions_[segment].insert(gid);
5490
}
5591

@@ -69,11 +105,9 @@ class GlyphConditionSet {
69105
void InvalidateGlyphInformation(const ift::common::GlyphSet& glyphs) {
70106
ift::common::SegmentSet touched;
71107
for (uint32_t gid : glyphs) {
72-
auto& condition = gid_conditions_[gid];
73-
touched.union_set(condition.and_segments);
74-
touched.union_set(condition.or_segments);
75-
condition.and_segments.clear();
76-
condition.or_segments.clear();
108+
auto& condition = gid_conditions_[gid].condition_;
109+
touched.union_set(condition.TriggeringSegments());
110+
condition = ActivationCondition::True(0);
77111
}
78112

79113
for (uint32_t segment_index : touched) {

0 commit comments

Comments
 (0)