/src/harfbuzz/src/graph/ligature-graph.hh
Line | Count | Source |
1 | | |
2 | | /* |
3 | | * Copyright © 2025 Google, Inc. |
4 | | * |
5 | | * This is part of HarfBuzz, a text shaping library. |
6 | | * |
7 | | * Permission is hereby granted, without written agreement and without |
8 | | * license or royalty fees, to use, copy, modify, and distribute this |
9 | | * software and its documentation for any purpose, provided that the |
10 | | * above copyright notice and the following two paragraphs appear in |
11 | | * all copies of this software. |
12 | | * |
13 | | * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR |
14 | | * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES |
15 | | * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN |
16 | | * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH |
17 | | * DAMAGE. |
18 | | * |
19 | | * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, |
20 | | * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND |
21 | | * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS |
22 | | * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO |
23 | | * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. |
24 | | * |
25 | | * Google Author(s): Garret Rieger |
26 | | */ |
27 | | |
28 | | #ifndef GRAPH_LIGATURE_GRAPH_HH |
29 | | #define GRAPH_LIGATURE_GRAPH_HH |
30 | | |
31 | | #include "graph.hh" |
32 | | #include "../OT/Layout/GSUB/LigatureSubst.hh" |
33 | | #include "../OT/Layout/GSUB/LigatureSubstFormat1.hh" |
34 | | #include "../OT/Layout/GSUB/LigatureSet.hh" |
35 | | #include "../hb-ot-dual.hh" |
36 | | #include <algorithm> |
37 | | #include <utility> |
38 | | |
39 | | namespace graph { |
40 | | |
41 | | struct LigatureSet : public OT::Layout::GSUB_impl::LigatureSet<SmallTypes> |
42 | | { |
43 | | graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const |
44 | 0 | { |
45 | 0 | size_t vertex_len = vertex.table_size (); |
46 | 0 | if (unlikely (vertex_len < OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size)) return Err(SANITIZE_FAILURE); |
47 | 0 | hb_barrier (); |
48 | |
|
49 | 0 | size_t total_len = ligature.get_size() + OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size - ligature.len.get_size(); |
50 | 0 | if (unlikely (vertex_len < total_len)) { |
51 | 0 | return Err(SANITIZE_FAILURE); |
52 | 0 | } |
53 | 0 | return Ok(); |
54 | 0 | } |
55 | | }; |
56 | | |
57 | | struct LigatureSubstFormat1 : public OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes> |
58 | | { |
59 | | graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const |
60 | 0 | { |
61 | 0 | size_t vertex_len = vertex.table_size (); |
62 | 0 | unsigned min_size = OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>::min_size; |
63 | 0 | if (unlikely (vertex_len < min_size)) return Err(SANITIZE_FAILURE); |
64 | 0 | hb_barrier (); |
65 | |
|
66 | 0 | if (unlikely (vertex_len < min_size + ligatureSet.get_size() - ligatureSet.len.get_size())) |
67 | 0 | return Err(SANITIZE_FAILURE); |
68 | 0 | return Ok(); |
69 | 0 | } |
70 | | |
71 | | graph_result_t<hb_vector_t<unsigned>> split_subtables (gsubgpos_graph_context_t& c, |
72 | | unsigned this_index) |
73 | 0 | { |
74 | 0 | TRY_ASSIGN (auto split_points, compute_split_points(c, this_index)); |
75 | 0 | if (!split_points) |
76 | 0 | return Ok(hb_vector_t<unsigned> ()); |
77 | | |
78 | 0 | TRY_ASSIGN (unsigned total_ligas, total_number_ligas(c, this_index)); |
79 | 0 | TRY_ASSIGN (hb_vector_t<unsigned> counts, liga_counts(c, this_index)); |
80 | 0 | split_context_t split_context { |
81 | 0 | c, |
82 | 0 | this, |
83 | 0 | this_index, |
84 | 0 | total_ligas, |
85 | 0 | std::move (counts), |
86 | 0 | }; |
87 | 0 | return actuate_subtable_split<split_context_t> (split_context, split_points); |
88 | 0 | } |
89 | | |
90 | | private: |
91 | 0 | graph_result_t<unsigned> total_number_ligas(gsubgpos_graph_context_t& c, unsigned this_index) const { |
92 | 0 | unsigned total = 0; |
93 | 0 | for (unsigned i = 0; i < ligatureSet.len; i++) |
94 | 0 | { |
95 | 0 | TRY_ASSIGN (auto liga_set, c.graph.as_table<LigatureSet>(this_index, &ligatureSet[i])); |
96 | 0 | total += liga_set.table->ligature.len; |
97 | 0 | } |
98 | 0 | return Ok(total); |
99 | 0 | } |
100 | | |
101 | 0 | graph_result_t<hb_vector_t<unsigned>> liga_counts(gsubgpos_graph_context_t& c, unsigned this_index) const { |
102 | 0 | hb_vector_t<unsigned> result; |
103 | 0 | for (unsigned i = 0; i < ligatureSet.len; i++) |
104 | 0 | { |
105 | 0 | TRY_ASSIGN (auto liga_set, c.graph.as_table<LigatureSet>(this_index, &ligatureSet[i])); |
106 | 0 | result.push(!liga_set.table ? 0 : liga_set.table->ligature.len); |
107 | 0 | } |
108 | 0 | return graph_result_t<hb_vector_t<unsigned>>::from(std::move(result), ALLOCATION_FAILURE); |
109 | 0 | } |
110 | | |
111 | | template <graph_t::vertex_mutability_t mutability> |
112 | 0 | graph_result_t<hb_vector_t<unsigned>> ligature_index_to_object_id(const graph_t::vertex_and_table_t<LigatureSet, mutability>& liga_set) const { |
113 | 0 | hb_vector_t<unsigned> map; |
114 | 0 | if (!map.resize_exact(liga_set.table->ligature.len)) return Err(ALLOCATION_FAILURE); |
115 | | |
116 | 0 | for (unsigned i = 0; i < map.length; i++) { |
117 | 0 | map[i] = HB_GRAPH_INVALID; |
118 | 0 | } |
119 | |
|
120 | 0 | for (const auto& l : liga_set.vertex->obj ().real_links) { |
121 | 0 | if (l.position < 2) continue; |
122 | 0 | unsigned array_index = (l.position - 2) / 2; |
123 | 0 | map[array_index] = l.objidx; |
124 | 0 | } |
125 | 0 | return map; |
126 | 0 | } Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::LigatureSubstFormat1::ligature_index_to_object_id<(graph::graph_t::vertex_mutability_t)0>(graph::graph_t::vertex_and_table_t<graph::LigatureSet, (graph::graph_t::vertex_mutability_t)0> const&) const Unexecuted instantiation: hb_result_t<hb_vector_t<unsigned int, false>, graph::graph_error_t> graph::LigatureSubstFormat1::ligature_index_to_object_id<(graph::graph_t::vertex_mutability_t)1>(graph::graph_t::vertex_and_table_t<graph::LigatureSet, (graph::graph_t::vertex_mutability_t)1> const&) const |
127 | | |
128 | | graph_result_t<hb_vector_t<unsigned>> compute_split_points(gsubgpos_graph_context_t& c, |
129 | | unsigned this_index) const |
130 | 0 | { |
131 | | // For ligature subst coverage is always packed last, and as a result is where an overflow |
132 | | // will happen if there is one, so we can check the estimate length of the |
133 | | // LigatureSubstFormat1 -> Coverage offset length which is the sum of all data in the |
134 | | // retained sub graph except for the coverage table itself. |
135 | 0 | const unsigned base_size = OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>::min_size; |
136 | 0 | unsigned accumulated = base_size; |
137 | |
|
138 | 0 | unsigned ligature_index = 0; |
139 | 0 | hb_vector_t<unsigned> split_points; |
140 | 0 | for (unsigned i = 0; i < ligatureSet.len; i++) |
141 | 0 | { |
142 | 0 | accumulated += OT::HBUINT16::static_size; // for ligature set offset |
143 | 0 | accumulated += OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size; // for ligature set table |
144 | |
|
145 | 0 | TRY_ASSIGN (auto liga_set, c.graph.as_table<LigatureSet>(this_index, &ligatureSet[i])); |
146 | | |
147 | | // Finding the object id associated with an array index is O(n) |
148 | | // so to avoid O(n^2), precompute the mapping by scanning through |
149 | | // all links |
150 | 0 | TRY_ASSIGN (auto index_to_id, ligature_index_to_object_id (liga_set)); |
151 | | |
152 | | // Split points and liga_counts () must count the same offset slots, |
153 | | // including null offsets. |
154 | 0 | for (unsigned j = 0; j < liga_set.table->ligature.len; j++, ligature_index++) |
155 | 0 | { |
156 | 0 | const unsigned liga_id = index_to_id[j]; |
157 | 0 | if (liga_id == HB_GRAPH_INVALID) continue; // no outgoing link, ignore |
158 | 0 | const unsigned liga_size = c.graph.vertices_[liga_id].table_size (); |
159 | |
|
160 | 0 | accumulated += OT::HBUINT16::static_size; // for ligature offset |
161 | 0 | accumulated += liga_size; // for the ligature table |
162 | |
|
163 | 0 | if (accumulated >= (1 << 16)) |
164 | 0 | { |
165 | 0 | split_points.push(ligature_index); |
166 | | // We're going to split such that the current ligature will be in the new sub table. |
167 | | // That means we'll have one ligature subst (base_base), one ligature set, and one liga table |
168 | 0 | accumulated = base_size + // for liga subst subtable |
169 | 0 | (OT::HBUINT16::static_size * 2) + // for liga set and liga offset |
170 | 0 | OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size + // for liga set subtable |
171 | 0 | liga_size; // for liga sub table |
172 | 0 | } |
173 | 0 | } |
174 | 0 | } |
175 | | |
176 | 0 | return split_points; |
177 | 0 | } |
178 | | |
179 | | struct split_context_t |
180 | | { |
181 | | gsubgpos_graph_context_t& c; |
182 | | LigatureSubstFormat1* thiz; |
183 | | unsigned this_index; |
184 | | unsigned original_count_; |
185 | | hb_vector_t<unsigned> liga_counts; |
186 | | |
187 | | unsigned original_count () |
188 | 0 | { |
189 | 0 | return original_count_; |
190 | 0 | } |
191 | | |
192 | | graph_result_t<unsigned> clone_range (unsigned start, unsigned end) |
193 | 0 | { |
194 | 0 | return thiz->clone_range (c, this_index, liga_counts, start, end); |
195 | 0 | } |
196 | | |
197 | | graph_result_t<void> shrink (unsigned count) |
198 | 0 | { |
199 | 0 | return thiz->shrink (c, this_index, original_count(), liga_counts, count); |
200 | 0 | } |
201 | | }; |
202 | | |
203 | 0 | graph_result_t<hb_pair_t<unsigned, LigatureSet*>> new_liga_set(gsubgpos_graph_context_t& c, unsigned count) const { |
204 | 0 | unsigned prime_size = OT::Layout::GSUB_impl::LigatureSet<SmallTypes>::min_size |
205 | 0 | + count * SmallTypes::size; |
206 | |
|
207 | 0 | TRY_ASSIGN (unsigned prime_id, c.create_node (prime_size)); |
208 | |
|
209 | 0 | LigatureSet* prime = (LigatureSet*) c.graph.object (prime_id).head; |
210 | 0 | prime->ligature.len = count; |
211 | 0 | return Ok(hb_pair(prime_id, prime)); |
212 | 0 | } |
213 | | |
214 | | void clear_virtual_links (gsubgpos_graph_context_t& c, unsigned node_index) const |
215 | 0 | { |
216 | 0 | auto& v = c.graph.vertices_[node_index]; |
217 | 0 | for (const auto& l : v.obj ().virtual_links) |
218 | 0 | { |
219 | 0 | auto& child = c.graph.vertices_[l.objidx]; |
220 | 0 | child.remove_parent(node_index); |
221 | 0 | } |
222 | 0 | v.clear_virtual_links (); |
223 | 0 | } |
224 | | |
225 | 0 | graph_result_t<void> add_virtual_link(gsubgpos_graph_context_t& c, unsigned from, unsigned to) const { |
226 | 0 | if (unlikely (from >= c.graph.vertices_.length || to >= c.graph.vertices_.length)) return Err(OUT_OF_BOUNDS); |
227 | 0 | TRY(c.graph.vertices_[to].add_parent (from, true)); |
228 | 0 | TRY(c.graph.vertices_[from].add_virtual_link (to)); |
229 | 0 | return Ok(); |
230 | 0 | } |
231 | | |
232 | | hb_pair_t<unsigned, unsigned> current_liga_set_bounds (gsubgpos_graph_context_t& c, |
233 | | unsigned liga_set_index, |
234 | | const hb_serialize_context_t::object_t& liga_set) const |
235 | 0 | { |
236 | | // Finds the actual liga indices present in the liga set currently. Takes |
237 | | // into account those that have been removed by processing. |
238 | 0 | unsigned min_index = HB_GRAPH_INVALID; |
239 | 0 | unsigned max_index = 0; |
240 | 0 | for (const auto& l : liga_set.real_links) { |
241 | 0 | if (l.position < 2) continue; |
242 | | |
243 | 0 | unsigned liga_index = (l.position - 2) / 2; |
244 | 0 | min_index = hb_min(min_index, liga_index); |
245 | 0 | max_index = hb_max(max_index, liga_index); |
246 | 0 | } |
247 | 0 | return hb_pair(min_index, max_index + 1); |
248 | 0 | } |
249 | | |
250 | | void compact_liga_set (gsubgpos_graph_context_t& c, LigatureSet* table, graph_t::vertex_t* v) const |
251 | 0 | { |
252 | 0 | const auto& obj = v->obj (); |
253 | 0 | if (table->ligature.len <= obj.real_links.length) return; |
254 | | |
255 | | // compact the remaining linked liga offsets into a continous array and shrink the node as needed. |
256 | 0 | unsigned to_remove = table->ligature.len - obj.real_links.length; |
257 | 0 | unsigned new_position = SmallTypes::size; |
258 | 0 | v->sort_real_links (); // for this to work we need to process links in order of position. |
259 | 0 | for (auto& l : v->real_links_writer ()) |
260 | 0 | { |
261 | 0 | l.position = new_position; |
262 | 0 | new_position += SmallTypes::size; |
263 | 0 | } |
264 | |
|
265 | 0 | table->ligature.len = obj.real_links.length; |
266 | 0 | v->shrink_buffer (to_remove * SmallTypes::size); |
267 | 0 | } |
268 | | |
269 | | graph_result_t<unsigned> clone_range (gsubgpos_graph_context_t& c, |
270 | | unsigned this_index, |
271 | | hb_vector_t<unsigned> liga_counts, |
272 | | unsigned start, unsigned end) const |
273 | 0 | { |
274 | 0 | DEBUG_MSG (SUBSET_REPACK, nullptr, |
275 | 0 | " Cloning LigatureSubstFormat1 (%u) range [%u, %u).", this_index, start, end); |
276 | | |
277 | | // Create an oversized new liga subst, we'll adjust the size down later. We don't know |
278 | | // the final size until we process it but we also need it to exist while we're processing |
279 | | // so that nodes can be moved to it as needed. |
280 | 0 | unsigned prime_size = OT::Layout::GSUB_impl::LigatureSubstFormat1_2<SmallTypes>::min_size |
281 | 0 | + ligatureSet.get_size() - ligatureSet.len.get_size(); |
282 | |
|
283 | 0 | TRY_ASSIGN (unsigned liga_subst_prime_id, c.create_node (prime_size)); |
284 | |
|
285 | 0 | LigatureSubstFormat1* liga_subst_prime = (LigatureSubstFormat1*) c.graph.object (liga_subst_prime_id).head; |
286 | 0 | liga_subst_prime->format = this->format; |
287 | 0 | liga_subst_prime->ligatureSet.len = this->ligatureSet.len; |
288 | | |
289 | | // Create a place holder coverage prime id since we need to add virtual links to it while |
290 | | // generating liga and liga sets. Afterwards it will be updated to have the correct coverage. |
291 | 0 | TRY_ASSIGN (unsigned coverage_id, c.graph.index_for_offset (this_index, &coverage)); |
292 | 0 | TRY_ASSIGN (unsigned coverage_prime_id, c.graph.duplicate(coverage_id)); |
293 | 0 | auto& coverage_prime_vertex = c.graph.vertices_[coverage_prime_id]; |
294 | |
|
295 | 0 | TRY(c.graph.vertices_[liga_subst_prime_id].add_real_link (SmallTypes::size, coverage_prime_id, 2)); |
296 | 0 | TRY(coverage_prime_vertex.add_parent (liga_subst_prime_id, false)); |
297 | | |
298 | | // Locate all liga sets with ligas between start and end. |
299 | | // Clone or move them as needed. |
300 | 0 | unsigned count = 0; |
301 | 0 | unsigned liga_set_count = 0; |
302 | 0 | unsigned liga_set_start = HB_GRAPH_INVALID; |
303 | 0 | unsigned liga_set_end = 0; // inclusive |
304 | 0 | for (unsigned i = 0; i < liga_counts.length; i++) |
305 | 0 | { |
306 | 0 | unsigned num_ligas = liga_counts[i]; |
307 | |
|
308 | 0 | unsigned current_start = count; |
309 | 0 | unsigned current_end = count + num_ligas; |
310 | |
|
311 | 0 | if (current_start >= end || start >= current_end) { |
312 | | // No intersection, so just skip |
313 | 0 | count += num_ligas; |
314 | 0 | continue; |
315 | 0 | } |
316 | | |
317 | 0 | TRY_ASSIGN (auto liga_set, c.graph.as_mutable_table<LigatureSet>(this_index, &ligatureSet[i])); |
318 | |
|
319 | 0 | unsigned liga_set_index = liga_set.index; |
320 | | // Bounds may need to be adjusted if some ligas have been previously removed. |
321 | 0 | hb_pair_t<unsigned, unsigned> liga_bounds = current_liga_set_bounds(c, liga_set_index, liga_set.vertex->obj ()); |
322 | 0 | current_start = hb_max(count + liga_bounds.first, current_start); |
323 | 0 | current_end = hb_min(count + liga_bounds.second, current_end); |
324 | |
|
325 | 0 | unsigned liga_set_prime_id; |
326 | 0 | if (current_start >= start && current_end <= end) { |
327 | | // This liga set is fully contined within [start, end) |
328 | | // We can move the entire ligaset to the new liga subset object. |
329 | 0 | liga_set_end = i; |
330 | 0 | if (i < liga_set_start) liga_set_start = i; |
331 | 0 | TRY_ASSIGN (liga_set_prime_id, c.graph.move_child<> (this_index, |
332 | 0 | &ligatureSet[i], |
333 | 0 | liga_subst_prime_id, |
334 | 0 | &liga_subst_prime->ligatureSet[liga_set_count++])); |
335 | 0 | compact_liga_set (c, liga_set.table, liga_set.vertex); |
336 | 0 | } |
337 | 0 | else |
338 | 0 | { |
339 | | // This liga set partially overlaps [start, end). We'll need to create |
340 | | // a new liga set sub table and move the intersecting ligas to it. |
341 | 0 | unsigned start_index = hb_max(start, current_start) - count; |
342 | 0 | unsigned end_index = hb_min(end, current_end) - count; |
343 | | // Narrowing to the remaining links may leave an inverted range. |
344 | 0 | if (unlikely (end_index < start_index)) |
345 | 0 | return Err(INVALID_ARGUMENT); |
346 | 0 | unsigned liga_count = end_index - start_index; |
347 | 0 | TRY_ASSIGN (auto result, new_liga_set(c, liga_count)); |
348 | 0 | liga_set_prime_id = result.first; |
349 | |
|
350 | 0 | TRY (c.graph.move_children<OT::Offset16>( |
351 | 0 | liga_set_index, |
352 | 0 | 2 + start_index * 2, |
353 | 0 | 2 + end_index * 2, |
354 | 0 | liga_set_prime_id, |
355 | 0 | 2)); |
356 | | |
357 | 0 | liga_set_end = i; |
358 | 0 | if (i < liga_set_start) liga_set_start = i; |
359 | 0 | TRY (c.graph.add_link(&liga_subst_prime->ligatureSet[liga_set_count++], liga_subst_prime_id, liga_set_prime_id)); |
360 | 0 | } |
361 | | |
362 | | // The new liga and all children set needs to have a virtual link to the new coverage table: |
363 | 0 | const auto& liga_set_prime = c.graph.vertices_[liga_set_prime_id].obj (); |
364 | 0 | clear_virtual_links(c, liga_set_prime_id); |
365 | 0 | TRY(add_virtual_link(c, liga_set_prime_id, coverage_prime_id)); |
366 | 0 | for (const auto& l : liga_set_prime.real_links) { |
367 | 0 | clear_virtual_links(c, l.objidx); |
368 | 0 | TRY(add_virtual_link(c, l.objidx, coverage_prime_id)); |
369 | 0 | } |
370 | | |
371 | 0 | count += num_ligas; |
372 | 0 | } |
373 | | |
374 | 0 | c.graph.vertices_[liga_subst_prime_id].shrink_buffer ((liga_subst_prime->ligatureSet.len - liga_set_count) * SmallTypes::size); |
375 | 0 | liga_subst_prime->ligatureSet.len = liga_set_count; |
376 | |
|
377 | 0 | TRY (Coverage::filter_coverage (c, |
378 | 0 | coverage_prime_id, |
379 | 0 | liga_set_start, liga_set_end + 1)); |
380 | | |
381 | 0 | return Ok(liga_subst_prime_id); |
382 | 0 | } |
383 | | |
384 | | graph_result_t<void> shrink (gsubgpos_graph_context_t& c, |
385 | | unsigned this_index, |
386 | | unsigned old_count, |
387 | | hb_vector_t<unsigned> liga_counts, |
388 | | unsigned count) |
389 | 0 | { |
390 | 0 | DEBUG_MSG (SUBSET_REPACK, nullptr, |
391 | 0 | " Shrinking LigatureSubstFormat1 (%u) to [0, %u).", |
392 | 0 | this_index, |
393 | 0 | count); |
394 | 0 | if (count >= old_count) |
395 | 0 | return Ok(); |
396 | | |
397 | 0 | hb_set_t retained_indices; |
398 | 0 | unsigned new_liga_set_count = 0; |
399 | 0 | for (unsigned i = 0; i < liga_counts.length; i++) |
400 | 0 | { |
401 | 0 | TRY_ASSIGN (auto liga_set, c.graph.as_mutable_table<LigatureSet>(this_index, &ligatureSet[i])); |
402 | | |
403 | | // We need the virtual links to coverage removed from all descendants on this liga subst. |
404 | | // If any are left when we try to mutate the coverage table later it will be unnessecarily |
405 | | // duplicated. Code later on will re-add the virtual links as needed (via retained_indices). |
406 | 0 | clear_virtual_links(c, liga_set.index); |
407 | 0 | retained_indices.add(liga_set.index); |
408 | |
|
409 | 0 | TRY_ASSIGN (auto index_to_id, ligature_index_to_object_id (liga_set)); |
410 | |
|
411 | 0 | for (unsigned i = 0; i < liga_set.table->ligature.len; i++) { |
412 | 0 | unsigned liga_index = index_to_id[i]; |
413 | 0 | if (liga_index != HB_GRAPH_INVALID) { |
414 | 0 | clear_virtual_links(c, liga_index); |
415 | 0 | retained_indices.add(liga_index); |
416 | 0 | } |
417 | 0 | } |
418 | |
|
419 | 0 | unsigned num_ligas = liga_counts[i]; |
420 | 0 | if (num_ligas >= count) { |
421 | | // drop the trailing liga's from this set and all subsequent liga sets |
422 | 0 | unsigned num_ligas_to_remove = num_ligas - count; |
423 | 0 | new_liga_set_count = i + 1; |
424 | 0 | c.graph.vertices_[liga_set.index].shrink_buffer (num_ligas_to_remove * SmallTypes::size); |
425 | 0 | liga_set.table->ligature.len = count; |
426 | 0 | break; |
427 | 0 | } else { |
428 | 0 | count -= num_ligas; |
429 | 0 | } |
430 | 0 | } |
431 | | |
432 | | // Adjust liga set array |
433 | 0 | auto& this_vertex = c.graph.vertices_[this_index]; |
434 | 0 | this_vertex.shrink_buffer ((ligatureSet.len - new_liga_set_count) * SmallTypes::size); |
435 | 0 | ligatureSet.len = new_liga_set_count; |
436 | | |
437 | | // Coverage matches the number of liga sets so rebuild as needed |
438 | 0 | TRY_ASSIGN (unsigned coverage_idx, c.graph.index_for_offset (this_index, &this->coverage)); |
439 | |
|
440 | 0 | auto& coverage_v = c.graph.vertices_[coverage_idx]; |
441 | 0 | unsigned coverage_size = coverage_v.table_size (); |
442 | 0 | const Coverage* coverage_table = (const Coverage*) coverage_v.obj ().head; |
443 | |
|
444 | 0 | if (coverage_v.is_shared ()) |
445 | 0 | { |
446 | 0 | TRY_ASSIGN (coverage_idx, c.graph.remap_child (this_index, coverage_idx)); |
447 | 0 | } |
448 | | |
449 | 0 | for (unsigned i : retained_indices.iter()) |
450 | 0 | TRY(add_virtual_link(c, i, coverage_idx)); |
451 | | |
452 | 0 | auto new_coverage = |
453 | 0 | + hb_zip (coverage_table->iter (), hb_range ()) |
454 | 0 | | hb_filter ([&] (hb_pair_t<unsigned, unsigned> p) { |
455 | 0 | return p.second < new_liga_set_count; |
456 | 0 | }) |
457 | 0 | | hb_map_retains_sorting (hb_first) |
458 | 0 | ; |
459 | |
|
460 | 0 | return Coverage::make_coverage (c, new_coverage, coverage_idx, coverage_size); |
461 | 0 | } |
462 | | }; |
463 | | |
464 | | struct LigatureSubst : public OT::Layout::GSUB_impl::LigatureSubst |
465 | | { |
466 | | |
467 | | graph_result_t<hb_vector_t<unsigned>> split_subtables (gsubgpos_graph_context_t& c, |
468 | | unsigned this_index) |
469 | 0 | { |
470 | 0 | switch (u.format.v) { |
471 | 0 | case 1: |
472 | 0 | hb_barrier (); |
473 | 0 | return ((LigatureSubstFormat1*)(&u.format1))->split_subtables (c, this_index); |
474 | 0 | #ifndef HB_NO_BEYOND_64K |
475 | 0 | case 2: HB_FALLTHROUGH; |
476 | | // 24-bit LigatureSubst already has widened offsets; leave it unsplit. |
477 | 0 | #endif |
478 | 0 | default: |
479 | 0 | return Ok(hb_vector_t<unsigned> ()); |
480 | 0 | } |
481 | 0 | } |
482 | | |
483 | | graph_result_t<void> sanitize (const graph_t::vertex_t& vertex) const |
484 | 0 | { |
485 | 0 | size_t vertex_len = vertex.table_size (); |
486 | 0 | if (unlikely (vertex_len < u.format.v.get_size ())) return Err(SANITIZE_FAILURE); |
487 | 0 | hb_barrier (); |
488 | |
|
489 | 0 | switch (u.format.v) { |
490 | 0 | case 1: |
491 | 0 | hb_barrier (); |
492 | 0 | return ((LigatureSubstFormat1*)(&u.format1))->sanitize (vertex); |
493 | 0 | #ifndef HB_NO_BEYOND_64K |
494 | 0 | case 2: HB_FALLTHROUGH; |
495 | 0 | #endif |
496 | 0 | default: |
497 | | // We don't handle format 2 here. |
498 | 0 | return Err(SANITIZE_FAILURE); |
499 | 0 | } |
500 | 0 | } |
501 | | }; |
502 | | |
503 | | } |
504 | | |
505 | | #endif // GRAPH_LIGATURE_GRAPH_HH |