1 | use super::aat_layout::*; |
2 | use super::aat_map::{hb_aat_map_builder_t, hb_aat_map_t, range_flags_t}; |
3 | use super::buffer::{hb_buffer_t, UnicodeProps}; |
4 | use super::{hb_font_t, hb_glyph_info_t}; |
5 | use crate::hb::aat_layout_common::hb_aat_apply_context_t; |
6 | use crate::hb::ot_layout::MAX_CONTEXT_LENGTH; |
7 | use alloc::vec; |
8 | use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32}; |
9 | |
10 | // TODO: Use set_digest, similarly to how it's used in harfbuzz. |
11 | |
12 | // Chain::compile_flags in harfbuzz |
13 | pub fn compile_flags( |
14 | face: &hb_font_t, |
15 | builder: &hb_aat_map_builder_t, |
16 | map: &mut hb_aat_map_t, |
17 | ) -> Option<()> { |
18 | let has_feature = |kind: u16, setting: u16| { |
19 | builder |
20 | .current_features |
21 | .binary_search_by(|probe| { |
22 | if probe.kind != kind { |
23 | probe.kind.cmp(&kind) |
24 | } else { |
25 | probe.setting.cmp(&setting) |
26 | } |
27 | }) |
28 | .is_ok() |
29 | }; |
30 | |
31 | let chains = face.tables().morx.as_ref()?.chains; |
32 | let chain_len = chains.into_iter().count(); |
33 | map.chain_flags.resize(chain_len, vec![]); |
34 | |
35 | for (chain, chain_flags) in chains.into_iter().zip(map.chain_flags.iter_mut()) { |
36 | let mut flags = chain.default_flags; |
37 | for feature in chain.features { |
38 | // Check whether this type/setting pair was requested in the map, |
39 | // and if so, apply its flags. |
40 | |
41 | if has_feature(feature.kind, feature.setting) { |
42 | flags &= feature.disable_flags; |
43 | flags |= feature.enable_flags; |
44 | } else if feature.kind == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE as u16 |
45 | && feature.setting == u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS) |
46 | { |
47 | // Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342 |
48 | let ok = has_feature( |
49 | HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE as u16, |
50 | u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS), |
51 | ); |
52 | if ok { |
53 | flags &= feature.disable_flags; |
54 | flags |= feature.enable_flags; |
55 | } |
56 | } |
57 | // TODO: Port the following commit: https://github.com/harfbuzz/harfbuzz/commit/2124ad890 |
58 | } |
59 | |
60 | chain_flags.push(range_flags_t { |
61 | flags, |
62 | cluster_first: builder.range_first as u32, |
63 | cluster_last: builder.range_last as u32, |
64 | }); |
65 | } |
66 | |
67 | Some(()) |
68 | } |
69 | |
70 | // Chain::apply in harfbuzz |
71 | pub fn apply<'a>(c: &mut hb_aat_apply_context_t<'a>, map: &'a mut hb_aat_map_t) -> Option<()> { |
72 | c.buffer.unsafe_to_concat(None, None); |
73 | |
74 | let chains = c.face.tables().morx.as_ref()?.chains; |
75 | let chain_len = chains.into_iter().count(); |
76 | map.chain_flags.resize(chain_len, vec![]); |
77 | |
78 | for (chain, chain_flags) in chains.into_iter().zip(map.chain_flags.iter_mut()) { |
79 | c.range_flags = Some(chain_flags.as_mut_slice()); |
80 | for subtable in chain.subtables { |
81 | if let Some(range_flags) = c.range_flags.as_ref() { |
82 | if range_flags.len() == 1 && (subtable.feature_flags & range_flags[0].flags == 0) { |
83 | continue; |
84 | } |
85 | } |
86 | |
87 | c.subtable_flags = subtable.feature_flags; |
88 | |
89 | if !subtable.coverage.is_all_directions() |
90 | && c.buffer.direction.is_vertical() != subtable.coverage.is_vertical() |
91 | { |
92 | continue; |
93 | } |
94 | |
95 | // Buffer contents is always in logical direction. Determine if |
96 | // we need to reverse before applying this subtable. We reverse |
97 | // back after if we did reverse indeed. |
98 | // |
99 | // Quoting the spec: |
100 | // """ |
101 | // Bits 28 and 30 of the coverage field control the order in which |
102 | // glyphs are processed when the subtable is run by the layout engine. |
103 | // Bit 28 is used to indicate if the glyph processing direction is |
104 | // the same as logical order or layout order. Bit 30 is used to |
105 | // indicate whether glyphs are processed forwards or backwards within |
106 | // that order. |
107 | // |
108 | // Bit 30 Bit 28 Interpretation for Horizontal Text |
109 | // 0 0 The subtable is processed in layout order |
110 | // (the same order as the glyphs, which is |
111 | // always left-to-right). |
112 | // 1 0 The subtable is processed in reverse layout order |
113 | // (the order opposite that of the glyphs, which is |
114 | // always right-to-left). |
115 | // 0 1 The subtable is processed in logical order |
116 | // (the same order as the characters, which may be |
117 | // left-to-right or right-to-left). |
118 | // 1 1 The subtable is processed in reverse logical order |
119 | // (the order opposite that of the characters, which |
120 | // may be right-to-left or left-to-right). |
121 | |
122 | let reverse = if subtable.coverage.is_logical() { |
123 | subtable.coverage.is_backwards() |
124 | } else { |
125 | subtable.coverage.is_backwards() != c.buffer.direction.is_backward() |
126 | }; |
127 | |
128 | if reverse { |
129 | c.buffer.reverse(); |
130 | } |
131 | |
132 | apply_subtable(&subtable.kind, c); |
133 | |
134 | if reverse { |
135 | c.buffer.reverse(); |
136 | } |
137 | } |
138 | } |
139 | |
140 | Some(()) |
141 | } |
142 | |
143 | trait driver_context_t<T: FromData> { |
144 | fn in_place(&self) -> bool; |
145 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool; |
146 | fn is_actionable( |
147 | &self, |
148 | entry: &apple_layout::GenericStateEntry<T>, |
149 | buffer: &hb_buffer_t, |
150 | ) -> bool; |
151 | fn transition( |
152 | &mut self, |
153 | entry: &apple_layout::GenericStateEntry<T>, |
154 | buffer: &mut hb_buffer_t, |
155 | ) -> Option<()>; |
156 | } |
157 | |
158 | const START_OF_TEXT: u16 = 0; |
159 | |
160 | fn drive<T: FromData>( |
161 | machine: &apple_layout::ExtendedStateTable<T>, |
162 | c: &mut dyn driver_context_t<T>, |
163 | ac: &mut hb_aat_apply_context_t, |
164 | ) { |
165 | if !c.in_place() { |
166 | ac.buffer.clear_output(); |
167 | } |
168 | |
169 | let mut state = START_OF_TEXT; |
170 | let mut last_range = ac.range_flags.as_ref().and_then(|rf| { |
171 | if rf.len() > 1 { |
172 | rf.first().map(|_| 0usize) |
173 | } else { |
174 | // If there's only one range, we already checked the flag. |
175 | None |
176 | } |
177 | }); |
178 | ac.buffer.idx = 0; |
179 | loop { |
180 | // This block copied from NoncontextualSubtable::apply. Keep in sync. |
181 | if let Some(range_flags) = ac.range_flags.as_ref() { |
182 | if let Some(last_range) = last_range.as_mut() { |
183 | let mut range = *last_range; |
184 | if ac.buffer.idx < ac.buffer.len { |
185 | let cluster = ac.buffer.cur(0).cluster; |
186 | while cluster < range_flags[range].cluster_first { |
187 | range -= 1; |
188 | } |
189 | |
190 | while cluster > range_flags[range].cluster_last { |
191 | range += 1; |
192 | } |
193 | |
194 | *last_range = range; |
195 | } |
196 | |
197 | if range_flags[range].flags & ac.subtable_flags == 0 { |
198 | if ac.buffer.idx == ac.buffer.len || !ac.buffer.successful { |
199 | break; |
200 | } |
201 | |
202 | state = START_OF_TEXT; |
203 | |
204 | ac.buffer.next_glyph(); |
205 | continue; |
206 | } |
207 | } |
208 | } |
209 | |
210 | let class = if ac.buffer.idx < ac.buffer.len { |
211 | machine.class(ac.buffer.cur(0).as_glyph()).unwrap_or(1) |
212 | } else { |
213 | u16::from(apple_layout::class::END_OF_TEXT) |
214 | }; |
215 | |
216 | let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) { |
217 | Some(v) => v, |
218 | None => break, |
219 | }; |
220 | |
221 | let next_state = entry.new_state; |
222 | |
223 | // Conditions under which it's guaranteed safe-to-break before current glyph: |
224 | // |
225 | // 1. There was no action in this transition; and |
226 | // |
227 | // 2. If we break before current glyph, the results will be the same. That |
228 | // is guaranteed if: |
229 | // |
230 | // 2a. We were already in start-of-text state; or |
231 | // |
232 | // 2b. We are epsilon-transitioning to start-of-text state; or |
233 | // |
234 | // 2c. Starting from start-of-text state seeing current glyph: |
235 | // |
236 | // 2c'. There won't be any actions; and |
237 | // |
238 | // 2c". We would end up in the same state that we were going to end up |
239 | // in now, including whether epsilon-transitioning. |
240 | // |
241 | // and |
242 | // |
243 | // 3. If we break before current glyph, there won't be any end-of-text action |
244 | // after previous glyph. |
245 | // |
246 | // This triples the transitions we need to look up, but is worth returning |
247 | // granular unsafe-to-break results. See eg.: |
248 | // |
249 | // https://github.com/harfbuzz/harfbuzz/issues/2860 |
250 | |
251 | let is_safe_to_break_extra = || { |
252 | // 2c |
253 | let wouldbe_entry = match machine.entry(START_OF_TEXT, class) { |
254 | Some(v) => v, |
255 | None => return false, |
256 | }; |
257 | |
258 | // 2c' |
259 | if c.is_actionable(&wouldbe_entry, ac.buffer) { |
260 | return false; |
261 | } |
262 | |
263 | // 2c" |
264 | next_state == wouldbe_entry.new_state |
265 | && c.can_advance(&entry) == c.can_advance(&wouldbe_entry) |
266 | }; |
267 | |
268 | let is_safe_to_break = || { |
269 | // 1 |
270 | if c.is_actionable(&entry, ac.buffer) { |
271 | return false; |
272 | } |
273 | |
274 | // 2 |
275 | let ok = state == START_OF_TEXT |
276 | || (!c.can_advance(&entry) && next_state == START_OF_TEXT) |
277 | || is_safe_to_break_extra(); |
278 | if !ok { |
279 | return false; |
280 | } |
281 | |
282 | // 3 |
283 | let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT)) |
284 | { |
285 | Some(v) => v, |
286 | None => return false, |
287 | }; |
288 | !c.is_actionable(&end_entry, ac.buffer) |
289 | }; |
290 | |
291 | if !is_safe_to_break() && ac.buffer.backtrack_len() > 0 && ac.buffer.idx < ac.buffer.len { |
292 | ac.buffer.unsafe_to_break_from_outbuffer( |
293 | Some(ac.buffer.backtrack_len() - 1), |
294 | Some(ac.buffer.idx + 1), |
295 | ); |
296 | } |
297 | |
298 | c.transition(&entry, ac.buffer); |
299 | |
300 | state = next_state; |
301 | |
302 | if ac.buffer.idx >= ac.buffer.len || !ac.buffer.successful { |
303 | break; |
304 | } |
305 | |
306 | if c.can_advance(&entry) { |
307 | ac.buffer.next_glyph(); |
308 | } else { |
309 | if ac.buffer.max_ops <= 0 { |
310 | ac.buffer.next_glyph(); |
311 | } |
312 | ac.buffer.max_ops -= 1; |
313 | } |
314 | } |
315 | |
316 | if !c.in_place() { |
317 | ac.buffer.sync(); |
318 | } |
319 | } |
320 | |
321 | fn apply_subtable(kind: &morx::SubtableKind, ac: &mut hb_aat_apply_context_t) { |
322 | match kind { |
323 | morx::SubtableKind::Rearrangement(ref table) => { |
324 | let mut c = RearrangementCtx { start: 0, end: 0 }; |
325 | |
326 | drive::<()>(table, &mut c, ac); |
327 | } |
328 | morx::SubtableKind::Contextual(ref table) => { |
329 | let mut c = ContextualCtx { |
330 | mark_set: false, |
331 | face_if_has_glyph_classes: |
332 | matches!(ac.face.tables().gdef, Some(gdef) if gdef.has_glyph_classes()) |
333 | .then_some(ac.face), |
334 | mark: 0, |
335 | table, |
336 | }; |
337 | |
338 | drive::<morx::ContextualEntryData>(&table.state, &mut c, ac); |
339 | } |
340 | morx::SubtableKind::Ligature(ref table) => { |
341 | let mut c = LigatureCtx { |
342 | table, |
343 | match_length: 0, |
344 | match_positions: [0; LIGATURE_MAX_MATCHES], |
345 | }; |
346 | |
347 | drive::<u16>(&table.state, &mut c, ac); |
348 | } |
349 | morx::SubtableKind::NonContextual(ref lookup) => { |
350 | let face_if_has_glyph_classes = |
351 | matches!(ac.face.tables().gdef, Some(gdef) if gdef.has_glyph_classes()) |
352 | .then_some(ac.face); |
353 | |
354 | let mut last_range = ac.range_flags.as_ref().and_then(|rf| { |
355 | if rf.len() > 1 { |
356 | rf.first().map(|_| 0usize) |
357 | } else { |
358 | // If there's only one range, we already checked the flag. |
359 | None |
360 | } |
361 | }); |
362 | |
363 | for info in 0..ac.buffer.len { |
364 | // This block copied from StateTableDriver::drive. Keep in sync. |
365 | if let Some(range_flags) = ac.range_flags.as_ref() { |
366 | if let Some(last_range) = last_range.as_mut() { |
367 | let mut range = *last_range; |
368 | if ac.buffer.idx < ac.buffer.len { |
369 | // We need to access info |
370 | let cluster = ac.buffer.cur(0).cluster; |
371 | while cluster < range_flags[range].cluster_first { |
372 | range -= 1; |
373 | } |
374 | |
375 | while cluster > range_flags[range].cluster_last { |
376 | range += 1; |
377 | } |
378 | |
379 | *last_range = range; |
380 | } |
381 | |
382 | if range_flags[range].flags & ac.subtable_flags == 0 { |
383 | continue; |
384 | } |
385 | } |
386 | } |
387 | |
388 | let info = &mut ac.buffer.info[info]; |
389 | if let Some(replacement) = lookup.value(info.as_glyph()) { |
390 | info.glyph_id = u32::from(replacement); |
391 | if let Some(face) = face_if_has_glyph_classes { |
392 | info.set_glyph_props(face.glyph_props(GlyphId(replacement))); |
393 | } |
394 | } |
395 | } |
396 | } |
397 | morx::SubtableKind::Insertion(ref table) => { |
398 | let mut c = InsertionCtx { |
399 | mark: 0, |
400 | glyphs: table.glyphs, |
401 | }; |
402 | |
403 | drive::<morx::InsertionEntryData>(&table.state, &mut c, ac); |
404 | } |
405 | } |
406 | } |
407 | |
408 | struct RearrangementCtx { |
409 | start: usize, |
410 | end: usize, |
411 | } |
412 | |
413 | impl RearrangementCtx { |
414 | const MARK_FIRST: u16 = 0x8000; |
415 | const DONT_ADVANCE: u16 = 0x4000; |
416 | const MARK_LAST: u16 = 0x2000; |
417 | const VERB: u16 = 0x000F; |
418 | } |
419 | |
420 | impl driver_context_t<()> for RearrangementCtx { |
421 | fn in_place(&self) -> bool { |
422 | true |
423 | } |
424 | |
425 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool { |
426 | entry.flags & Self::DONT_ADVANCE == 0 |
427 | } |
428 | |
429 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &hb_buffer_t) -> bool { |
430 | entry.flags & Self::VERB != 0 && self.start < self.end |
431 | } |
432 | |
433 | fn transition( |
434 | &mut self, |
435 | entry: &apple_layout::GenericStateEntry<()>, |
436 | buffer: &mut hb_buffer_t, |
437 | ) -> Option<()> { |
438 | let flags = entry.flags; |
439 | |
440 | if flags & Self::MARK_FIRST != 0 { |
441 | self.start = buffer.idx; |
442 | } |
443 | |
444 | if flags & Self::MARK_LAST != 0 { |
445 | self.end = (buffer.idx + 1).min(buffer.len); |
446 | } |
447 | |
448 | if flags & Self::VERB != 0 && self.start < self.end { |
449 | // The following map has two nibbles, for start-side |
450 | // and end-side. Values of 0,1,2 mean move that many |
451 | // to the other side. Value of 3 means move 2 and |
452 | // flip them. |
453 | const MAP: [u8; 16] = [ |
454 | 0x00, // 0 no change |
455 | 0x10, // 1 Ax => xA |
456 | 0x01, // 2 xD => Dx |
457 | 0x11, // 3 AxD => DxA |
458 | 0x20, // 4 ABx => xAB |
459 | 0x30, // 5 ABx => xBA |
460 | 0x02, // 6 xCD => CDx |
461 | 0x03, // 7 xCD => DCx |
462 | 0x12, // 8 AxCD => CDxA |
463 | 0x13, // 9 AxCD => DCxA |
464 | 0x21, // 10 ABxD => DxAB |
465 | 0x31, // 11 ABxD => DxBA |
466 | 0x22, // 12 ABxCD => CDxAB |
467 | 0x32, // 13 ABxCD => CDxBA |
468 | 0x23, // 14 ABxCD => DCxAB |
469 | 0x33, // 15 ABxCD => DCxBA |
470 | ]; |
471 | |
472 | let m = MAP[usize::from(flags & Self::VERB)]; |
473 | let l = 2.min(m >> 4) as usize; |
474 | let r = 2.min(m & 0x0F) as usize; |
475 | let reverse_l = 3 == (m >> 4); |
476 | let reverse_r = 3 == (m & 0x0F); |
477 | |
478 | if (self.end - self.start >= l + r) && (self.end - self.start <= MAX_CONTEXT_LENGTH) { |
479 | buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len)); |
480 | buffer.merge_clusters(self.start, self.end); |
481 | |
482 | let mut buf = [hb_glyph_info_t::default(); 4]; |
483 | |
484 | for (i, glyph_info) in buf[..l].iter_mut().enumerate() { |
485 | *glyph_info = buffer.info[self.start + i]; |
486 | } |
487 | |
488 | for i in 0..r { |
489 | buf[i + 2] = buffer.info[self.end - r + i]; |
490 | } |
491 | |
492 | if l > r { |
493 | for i in 0..(self.end - self.start - l - r) { |
494 | buffer.info[self.start + r + i] = buffer.info[self.start + l + i]; |
495 | } |
496 | } else if l < r { |
497 | for i in (0..(self.end - self.start - l - r)).rev() { |
498 | buffer.info[self.start + r + i] = buffer.info[self.start + l + i]; |
499 | } |
500 | } |
501 | |
502 | for i in 0..r { |
503 | buffer.info[self.start + i] = buf[2 + i]; |
504 | } |
505 | |
506 | for i in 0..l { |
507 | buffer.info[self.end - l + i] = buf[i]; |
508 | } |
509 | |
510 | if reverse_l { |
511 | buffer.info.swap(self.end - 1, self.end - 2); |
512 | } |
513 | |
514 | if reverse_r { |
515 | buffer.info.swap(self.start, self.start + 1); |
516 | } |
517 | } |
518 | } |
519 | |
520 | Some(()) |
521 | } |
522 | } |
523 | |
524 | struct ContextualCtx<'a> { |
525 | mark_set: bool, |
526 | face_if_has_glyph_classes: Option<&'a hb_font_t<'a>>, |
527 | mark: usize, |
528 | table: &'a morx::ContextualSubtable<'a>, |
529 | } |
530 | |
531 | impl ContextualCtx<'_> { |
532 | const SET_MARK: u16 = 0x8000; |
533 | const DONT_ADVANCE: u16 = 0x4000; |
534 | } |
535 | |
536 | impl driver_context_t<morx::ContextualEntryData> for ContextualCtx<'_> { |
537 | fn in_place(&self) -> bool { |
538 | true |
539 | } |
540 | |
541 | fn can_advance( |
542 | &self, |
543 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
544 | ) -> bool { |
545 | entry.flags & Self::DONT_ADVANCE == 0 |
546 | } |
547 | |
548 | fn is_actionable( |
549 | &self, |
550 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
551 | buffer: &hb_buffer_t, |
552 | ) -> bool { |
553 | if buffer.idx == buffer.len && !self.mark_set { |
554 | return false; |
555 | } |
556 | |
557 | entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF |
558 | } |
559 | |
560 | fn transition( |
561 | &mut self, |
562 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
563 | buffer: &mut hb_buffer_t, |
564 | ) -> Option<()> { |
565 | // Looks like CoreText applies neither mark nor current substitution for |
566 | // end-of-text if mark was not explicitly set. |
567 | if buffer.idx == buffer.len && !self.mark_set { |
568 | return Some(()); |
569 | } |
570 | |
571 | let mut replacement = None; |
572 | |
573 | if entry.extra.mark_index != 0xFFFF { |
574 | let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?; |
575 | replacement = lookup.value(buffer.info[self.mark].as_glyph()); |
576 | } |
577 | |
578 | if let Some(replacement) = replacement { |
579 | buffer.unsafe_to_break(Some(self.mark), Some((buffer.idx + 1).min(buffer.len))); |
580 | buffer.info[self.mark].glyph_id = u32::from(replacement); |
581 | |
582 | if let Some(face) = self.face_if_has_glyph_classes { |
583 | buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement))); |
584 | } |
585 | } |
586 | |
587 | replacement = None; |
588 | let idx = buffer.idx.min(buffer.len - 1); |
589 | if entry.extra.current_index != 0xFFFF { |
590 | let lookup = self.table.lookup(u32::from(entry.extra.current_index))?; |
591 | replacement = lookup.value(buffer.info[idx].as_glyph()); |
592 | } |
593 | |
594 | if let Some(replacement) = replacement { |
595 | buffer.info[idx].glyph_id = u32::from(replacement); |
596 | |
597 | if let Some(face) = self.face_if_has_glyph_classes { |
598 | buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement))); |
599 | } |
600 | } |
601 | |
602 | if entry.flags & Self::SET_MARK != 0 { |
603 | self.mark_set = true; |
604 | self.mark = buffer.idx; |
605 | } |
606 | |
607 | Some(()) |
608 | } |
609 | } |
610 | |
611 | struct InsertionCtx<'a> { |
612 | mark: u32, |
613 | glyphs: LazyArray32<'a, GlyphId>, |
614 | } |
615 | |
616 | impl InsertionCtx<'_> { |
617 | const SET_MARK: u16 = 0x8000; |
618 | const DONT_ADVANCE: u16 = 0x4000; |
619 | const CURRENT_INSERT_BEFORE: u16 = 0x0800; |
620 | const MARKED_INSERT_BEFORE: u16 = 0x0400; |
621 | const CURRENT_INSERT_COUNT: u16 = 0x03E0; |
622 | const MARKED_INSERT_COUNT: u16 = 0x001F; |
623 | } |
624 | |
625 | impl driver_context_t<morx::InsertionEntryData> for InsertionCtx<'_> { |
626 | fn in_place(&self) -> bool { |
627 | false |
628 | } |
629 | |
630 | fn can_advance( |
631 | &self, |
632 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
633 | ) -> bool { |
634 | entry.flags & Self::DONT_ADVANCE == 0 |
635 | } |
636 | |
637 | fn is_actionable( |
638 | &self, |
639 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
640 | _: &hb_buffer_t, |
641 | ) -> bool { |
642 | (entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0) |
643 | && (entry.extra.current_insert_index != 0xFFFF |
644 | || entry.extra.marked_insert_index != 0xFFFF) |
645 | } |
646 | |
647 | fn transition( |
648 | &mut self, |
649 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
650 | buffer: &mut hb_buffer_t, |
651 | ) -> Option<()> { |
652 | let flags = entry.flags; |
653 | let mark_loc = buffer.out_len; |
654 | |
655 | if entry.extra.marked_insert_index != 0xFFFF { |
656 | let count = flags & Self::MARKED_INSERT_COUNT; |
657 | buffer.max_ops -= i32::from(count); |
658 | if buffer.max_ops <= 0 { |
659 | return Some(()); |
660 | } |
661 | |
662 | let start = entry.extra.marked_insert_index; |
663 | let before = flags & Self::MARKED_INSERT_BEFORE != 0; |
664 | |
665 | let end = buffer.out_len; |
666 | buffer.move_to(self.mark as usize); |
667 | |
668 | if buffer.idx < buffer.len && !before { |
669 | buffer.copy_glyph(); |
670 | } |
671 | |
672 | // TODO We ignore KashidaLike setting. |
673 | for i in 0..count { |
674 | let i = u32::from(start + i); |
675 | buffer.output_glyph(u32::from(self.glyphs.get(i)?.0)); |
676 | } |
677 | |
678 | if buffer.idx < buffer.len && !before { |
679 | buffer.skip_glyph(); |
680 | } |
681 | |
682 | buffer.move_to(end + usize::from(count)); |
683 | |
684 | buffer.unsafe_to_break_from_outbuffer( |
685 | Some(self.mark as usize), |
686 | Some((buffer.idx + 1).min(buffer.len)), |
687 | ); |
688 | } |
689 | |
690 | if flags & Self::SET_MARK != 0 { |
691 | self.mark = mark_loc as u32; |
692 | } |
693 | |
694 | if entry.extra.current_insert_index != 0xFFFF { |
695 | let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5; |
696 | buffer.max_ops -= i32::from(count); |
697 | if buffer.max_ops < 0 { |
698 | return Some(()); |
699 | } |
700 | |
701 | let start = entry.extra.current_insert_index; |
702 | let before = flags & Self::CURRENT_INSERT_BEFORE != 0; |
703 | let end = buffer.out_len; |
704 | |
705 | if buffer.idx < buffer.len && !before { |
706 | buffer.copy_glyph(); |
707 | } |
708 | |
709 | // TODO We ignore KashidaLike setting. |
710 | for i in 0..count { |
711 | let i = u32::from(start + i); |
712 | buffer.output_glyph(u32::from(self.glyphs.get(i)?.0)); |
713 | } |
714 | |
715 | if buffer.idx < buffer.len && !before { |
716 | buffer.skip_glyph(); |
717 | } |
718 | |
719 | // Humm. Not sure where to move to. There's this wording under |
720 | // DontAdvance flag: |
721 | // |
722 | // "If set, don't update the glyph index before going to the new state. |
723 | // This does not mean that the glyph pointed to is the same one as |
724 | // before. If you've made insertions immediately downstream of the |
725 | // current glyph, the next glyph processed would in fact be the first |
726 | // one inserted." |
727 | // |
728 | // This suggests that if DontAdvance is NOT set, we should move to |
729 | // end+count. If it *was*, then move to end, such that newly inserted |
730 | // glyphs are now visible. |
731 | // |
732 | // https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417 |
733 | buffer.move_to(if flags & Self::DONT_ADVANCE != 0 { |
734 | end |
735 | } else { |
736 | end + usize::from(count) |
737 | }); |
738 | } |
739 | |
740 | Some(()) |
741 | } |
742 | } |
743 | |
744 | const LIGATURE_MAX_MATCHES: usize = 64; |
745 | |
746 | struct LigatureCtx<'a> { |
747 | table: &'a morx::LigatureSubtable<'a>, |
748 | match_length: usize, |
749 | match_positions: [usize; LIGATURE_MAX_MATCHES], |
750 | } |
751 | |
752 | impl LigatureCtx<'_> { |
753 | const SET_COMPONENT: u16 = 0x8000; |
754 | const DONT_ADVANCE: u16 = 0x4000; |
755 | const PERFORM_ACTION: u16 = 0x2000; |
756 | |
757 | const LIG_ACTION_LAST: u32 = 0x80000000; |
758 | const LIG_ACTION_STORE: u32 = 0x40000000; |
759 | const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF; |
760 | } |
761 | |
762 | impl driver_context_t<u16> for LigatureCtx<'_> { |
763 | fn in_place(&self) -> bool { |
764 | false |
765 | } |
766 | |
767 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool { |
768 | entry.flags & Self::DONT_ADVANCE == 0 |
769 | } |
770 | |
771 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &hb_buffer_t) -> bool { |
772 | entry.flags & Self::PERFORM_ACTION != 0 |
773 | } |
774 | |
775 | fn transition( |
776 | &mut self, |
777 | entry: &apple_layout::GenericStateEntry<u16>, |
778 | buffer: &mut hb_buffer_t, |
779 | ) -> Option<()> { |
780 | if entry.flags & Self::SET_COMPONENT != 0 { |
781 | // Never mark same index twice, in case DONT_ADVANCE was used... |
782 | if self.match_length != 0 |
783 | && self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] |
784 | == buffer.out_len |
785 | { |
786 | self.match_length -= 1; |
787 | } |
788 | |
789 | self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len; |
790 | self.match_length += 1; |
791 | } |
792 | |
793 | if entry.flags & Self::PERFORM_ACTION != 0 { |
794 | let end = buffer.out_len; |
795 | |
796 | if self.match_length == 0 { |
797 | return Some(()); |
798 | } |
799 | |
800 | if buffer.idx >= buffer.len { |
801 | return Some(()); // TODO: Work on previous instead? |
802 | } |
803 | |
804 | let mut cursor = self.match_length; |
805 | |
806 | let mut ligature_actions_index = entry.extra; |
807 | let mut ligature_idx = 0; |
808 | loop { |
809 | if cursor == 0 { |
810 | // Stack underflow. Clear the stack. |
811 | self.match_length = 0; |
812 | break; |
813 | } |
814 | |
815 | cursor -= 1; |
816 | buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]); |
817 | |
818 | // We cannot use ? in this loop, because we must call |
819 | // buffer.move_to(end) in the end. |
820 | let action = match self |
821 | .table |
822 | .ligature_actions |
823 | .get(u32::from(ligature_actions_index)) |
824 | { |
825 | Some(v) => v, |
826 | None => break, |
827 | }; |
828 | |
829 | let mut uoffset = action & Self::LIG_ACTION_OFFSET; |
830 | if uoffset & 0x20000000 != 0 { |
831 | uoffset |= 0xC0000000; // Sign-extend. |
832 | } |
833 | |
834 | let offset = uoffset as i32; |
835 | let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32; |
836 | ligature_idx += match self.table.components.get(component_idx) { |
837 | Some(v) => v, |
838 | None => break, |
839 | }; |
840 | |
841 | if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 { |
842 | let lig = match self.table.ligatures.get(u32::from(ligature_idx)) { |
843 | Some(v) => v, |
844 | None => break, |
845 | }; |
846 | |
847 | buffer.replace_glyph(u32::from(lig.0)); |
848 | |
849 | let lig_end = |
850 | self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1; |
851 | // Now go and delete all subsequent components. |
852 | while self.match_length - 1 > cursor { |
853 | self.match_length -= 1; |
854 | buffer.move_to( |
855 | self.match_positions[self.match_length % LIGATURE_MAX_MATCHES], |
856 | ); |
857 | let cur_unicode = buffer.cur(0).unicode_props(); |
858 | buffer |
859 | .cur_mut(0) |
860 | .set_unicode_props(cur_unicode | UnicodeProps::IGNORABLE.bits()); |
861 | buffer.replace_glyph(0xFFFF); |
862 | } |
863 | |
864 | buffer.move_to(lig_end); |
865 | buffer.merge_out_clusters( |
866 | self.match_positions[cursor % LIGATURE_MAX_MATCHES], |
867 | buffer.out_len, |
868 | ); |
869 | } |
870 | |
871 | ligature_actions_index += 1; |
872 | |
873 | if action & Self::LIG_ACTION_LAST != 0 { |
874 | break; |
875 | } |
876 | } |
877 | |
878 | buffer.move_to(end); |
879 | } |
880 | |
881 | Some(()) |
882 | } |
883 | } |
884 | |