| 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 | |