| 1 | use ttf_parser::GlyphId; |
| 2 | |
| 3 | use super::buffer::{hb_buffer_t, GlyphPosition}; |
| 4 | use super::face::hb_glyph_extents_t; |
| 5 | use super::ot_layout::*; |
| 6 | use super::ot_shape_plan::hb_ot_shape_plan_t; |
| 7 | use super::unicode::*; |
| 8 | use super::{hb_font_t, Direction}; |
| 9 | |
| 10 | fn recategorize_combining_class(u: u32, mut class: u8) -> u8 { |
| 11 | use modified_combining_class as mcc; |
| 12 | use CanonicalCombiningClass as Class; |
| 13 | |
| 14 | if class >= 200 { |
| 15 | return class; |
| 16 | } |
| 17 | |
| 18 | // Thai / Lao need some per-character work. |
| 19 | if u & !0xFF == 0x0E00 { |
| 20 | if class == 0 { |
| 21 | match u { |
| 22 | 0x0E31 | 0x0E34 | 0x0E35 | 0x0E36 | 0x0E37 | 0x0E47 | 0x0E4C | 0x0E4D | 0x0E4E => { |
| 23 | class = Class::AboveRight as u8 |
| 24 | } |
| 25 | |
| 26 | 0x0EB1 | 0x0EB4 | 0x0EB5 | 0x0EB6 | 0x0EB7 | 0x0EBB | 0x0ECC | 0x0ECD => { |
| 27 | class = Class::Above as u8 |
| 28 | } |
| 29 | |
| 30 | 0x0EBC => class = Class::Below as u8, |
| 31 | |
| 32 | _ => {} |
| 33 | } |
| 34 | } else { |
| 35 | // Thai virama is below-right |
| 36 | if u == 0x0E3A { |
| 37 | class = Class::BelowRight as u8; |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | match class { |
| 43 | // Hebrew |
| 44 | mcc::CCC10 => Class::Below as u8, // sheva |
| 45 | mcc::CCC11 => Class::Below as u8, // hataf segol |
| 46 | mcc::CCC12 => Class::Below as u8, // hataf patah |
| 47 | mcc::CCC13 => Class::Below as u8, // hataf qamats |
| 48 | mcc::CCC14 => Class::Below as u8, // hiriq |
| 49 | mcc::CCC15 => Class::Below as u8, // tsere |
| 50 | mcc::CCC16 => Class::Below as u8, // segol |
| 51 | mcc::CCC17 => Class::Below as u8, // patah |
| 52 | mcc::CCC18 => Class::Below as u8, // qamats & qamats qatan |
| 53 | mcc::CCC20 => Class::Below as u8, // qubuts |
| 54 | mcc::CCC22 => Class::Below as u8, // meteg |
| 55 | mcc::CCC23 => Class::AttachedAbove as u8, // rafe |
| 56 | mcc::CCC24 => Class::AboveRight as u8, // shin dot |
| 57 | mcc::CCC25 => Class::AboveLeft as u8, // sin dot |
| 58 | mcc::CCC19 => Class::AboveLeft as u8, // holam & holam haser for vav |
| 59 | mcc::CCC26 => Class::Above as u8, // point varika |
| 60 | mcc::CCC21 => class, // dagesh |
| 61 | |
| 62 | // Arabic and Syriac |
| 63 | mcc::CCC27 => Class::Above as u8, // fathatan |
| 64 | mcc::CCC28 => Class::Above as u8, // dammatan |
| 65 | mcc::CCC30 => Class::Above as u8, // fatha |
| 66 | mcc::CCC31 => Class::Above as u8, // damma |
| 67 | mcc::CCC33 => Class::Above as u8, // shadda |
| 68 | mcc::CCC34 => Class::Above as u8, // sukun |
| 69 | mcc::CCC35 => Class::Above as u8, // superscript alef |
| 70 | mcc::CCC36 => Class::Above as u8, // superscript alaph |
| 71 | mcc::CCC29 => Class::Below as u8, // kasratan |
| 72 | mcc::CCC32 => Class::Below as u8, // kasra |
| 73 | |
| 74 | // Thai |
| 75 | mcc::CCC103 => Class::BelowRight as u8, // sara u / sara uu |
| 76 | mcc::CCC107 => Class::AboveRight as u8, // mai |
| 77 | |
| 78 | // Lao |
| 79 | mcc::CCC118 => Class::Below as u8, // sign u / sign uu |
| 80 | mcc::CCC122 => Class::Above as u8, // mai |
| 81 | |
| 82 | // Tibetian |
| 83 | mcc::CCC129 => Class::Below as u8, // sign aa |
| 84 | mcc::CCC130 => Class::Above as u8, // sign i |
| 85 | mcc::CCC132 => Class::Below as u8, // sign u |
| 86 | |
| 87 | _ => class, |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | pub fn _hb_ot_shape_fallback_mark_position_recategorize_marks( |
| 92 | _: &hb_ot_shape_plan_t, |
| 93 | _: &hb_font_t, |
| 94 | buffer: &mut hb_buffer_t, |
| 95 | ) { |
| 96 | let len: usize = buffer.len; |
| 97 | for info: &mut hb_glyph_info_t in &mut buffer.info[..len] { |
| 98 | if _hb_glyph_info_get_general_category(info) |
| 99 | == hb_unicode_general_category_t::NonspacingMark |
| 100 | { |
| 101 | let mut class: u8 = _hb_glyph_info_get_modified_combining_class(info); |
| 102 | class = recategorize_combining_class(u:info.glyph_id, class); |
| 103 | _hb_glyph_info_set_modified_combining_class(info, class); |
| 104 | } |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | fn zero_mark_advances( |
| 109 | buffer: &mut hb_buffer_t, |
| 110 | start: usize, |
| 111 | end: usize, |
| 112 | adjust_offsets_when_zeroing: bool, |
| 113 | ) { |
| 114 | for (info: &hb_glyph_info_t, pos: &mut GlyphPosition) in bufferIter<'_, hb_glyph_info_t>.info[start..end] |
| 115 | .iter() |
| 116 | .zip(&mut buffer.pos[start..end]) |
| 117 | { |
| 118 | if _hb_glyph_info_get_general_category(info) |
| 119 | == hb_unicode_general_category_t::NonspacingMark |
| 120 | { |
| 121 | if adjust_offsets_when_zeroing { |
| 122 | pos.x_offset -= pos.x_advance; |
| 123 | pos.y_offset -= pos.y_advance; |
| 124 | } |
| 125 | pos.x_advance = 0; |
| 126 | pos.y_advance = 0; |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | fn position_mark( |
| 132 | _: &hb_ot_shape_plan_t, |
| 133 | face: &hb_font_t, |
| 134 | direction: Direction, |
| 135 | glyph: GlyphId, |
| 136 | pos: &mut GlyphPosition, |
| 137 | base_extents: &mut hb_glyph_extents_t, |
| 138 | combining_class: CanonicalCombiningClass, |
| 139 | ) { |
| 140 | use CanonicalCombiningClass as Class; |
| 141 | |
| 142 | let mut mark_extents = hb_glyph_extents_t::default(); |
| 143 | if !face.glyph_extents(glyph, &mut mark_extents) { |
| 144 | return; |
| 145 | }; |
| 146 | |
| 147 | let y_gap = face.units_per_em as i32 / 16; |
| 148 | pos.x_offset = 0; |
| 149 | pos.y_offset = 0; |
| 150 | |
| 151 | // We don't position LEFT and RIGHT marks. |
| 152 | |
| 153 | // X positioning |
| 154 | match combining_class { |
| 155 | Class::DoubleBelow | Class::DoubleAbove if direction.is_horizontal() => { |
| 156 | pos.x_offset += base_extents.x_bearing |
| 157 | + if direction.is_forward() { |
| 158 | base_extents.width |
| 159 | } else { |
| 160 | 0 |
| 161 | } |
| 162 | - mark_extents.width / 2 |
| 163 | - mark_extents.x_bearing; |
| 164 | } |
| 165 | |
| 166 | Class::AttachedBelowLeft | Class::BelowLeft | Class::AboveLeft => { |
| 167 | // Left align. |
| 168 | pos.x_offset += base_extents.x_bearing - mark_extents.x_bearing; |
| 169 | } |
| 170 | |
| 171 | Class::AttachedAboveRight | Class::BelowRight | Class::AboveRight => { |
| 172 | // Right align. |
| 173 | pos.x_offset += base_extents.x_bearing + base_extents.width |
| 174 | - mark_extents.width |
| 175 | - mark_extents.x_bearing; |
| 176 | } |
| 177 | |
| 178 | Class::AttachedBelow | Class::AttachedAbove | Class::Below | Class::Above | _ => { |
| 179 | // Center align. |
| 180 | pos.x_offset += base_extents.x_bearing + (base_extents.width - mark_extents.width) / 2 |
| 181 | - mark_extents.x_bearing; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | let is_attached = matches!( |
| 186 | combining_class, |
| 187 | Class::AttachedBelowLeft |
| 188 | | Class::AttachedBelow |
| 189 | | Class::AttachedAbove |
| 190 | | Class::AttachedAboveRight |
| 191 | ); |
| 192 | |
| 193 | // Y positioning. |
| 194 | match combining_class { |
| 195 | Class::DoubleBelow |
| 196 | | Class::BelowLeft |
| 197 | | Class::Below |
| 198 | | Class::BelowRight |
| 199 | | Class::AttachedBelowLeft |
| 200 | | Class::AttachedBelow => { |
| 201 | if !is_attached { |
| 202 | // Add gap. |
| 203 | base_extents.height -= y_gap; |
| 204 | } |
| 205 | |
| 206 | pos.y_offset = base_extents.y_bearing + base_extents.height - mark_extents.y_bearing; |
| 207 | |
| 208 | // Never shift up "below" marks. |
| 209 | if (y_gap > 0) == (pos.y_offset > 0) { |
| 210 | base_extents.height -= pos.y_offset; |
| 211 | pos.y_offset = 0; |
| 212 | } |
| 213 | |
| 214 | base_extents.height += mark_extents.height; |
| 215 | } |
| 216 | |
| 217 | Class::DoubleAbove |
| 218 | | Class::AboveLeft |
| 219 | | Class::Above |
| 220 | | Class::AboveRight |
| 221 | | Class::AttachedAbove |
| 222 | | Class::AttachedAboveRight => { |
| 223 | if !is_attached { |
| 224 | // Add gap. |
| 225 | base_extents.y_bearing += y_gap; |
| 226 | base_extents.height -= y_gap; |
| 227 | } |
| 228 | |
| 229 | pos.y_offset = base_extents.y_bearing - (mark_extents.y_bearing + mark_extents.height); |
| 230 | |
| 231 | // Don't shift down "above" marks too much. |
| 232 | if (y_gap > 0) != (pos.y_offset > 0) { |
| 233 | let correction = -pos.y_offset / 2; |
| 234 | base_extents.y_bearing += correction; |
| 235 | base_extents.height -= correction; |
| 236 | pos.y_offset += correction; |
| 237 | } |
| 238 | |
| 239 | base_extents.y_bearing -= mark_extents.height; |
| 240 | base_extents.height += mark_extents.height; |
| 241 | } |
| 242 | |
| 243 | _ => {} |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | fn position_around_base( |
| 248 | plan: &hb_ot_shape_plan_t, |
| 249 | face: &hb_font_t, |
| 250 | buffer: &mut hb_buffer_t, |
| 251 | base: usize, |
| 252 | end: usize, |
| 253 | adjust_offsets_when_zeroing: bool, |
| 254 | ) { |
| 255 | let mut horizontal_dir = Direction::Invalid; |
| 256 | buffer.unsafe_to_break(Some(base), Some(end)); |
| 257 | |
| 258 | let base_info = &buffer.info[base]; |
| 259 | let base_pos = &buffer.pos[base]; |
| 260 | let base_glyph = base_info.as_glyph(); |
| 261 | |
| 262 | let mut base_extents = hb_glyph_extents_t::default(); |
| 263 | if !face.glyph_extents(base_glyph, &mut base_extents) { |
| 264 | zero_mark_advances(buffer, base + 1, end, adjust_offsets_when_zeroing); |
| 265 | return; |
| 266 | }; |
| 267 | |
| 268 | base_extents.y_bearing += base_pos.y_offset; |
| 269 | base_extents.x_bearing = 0; |
| 270 | |
| 271 | // Use horizontal advance for horizontal positioning. |
| 272 | // Generally a better idea. Also works for zero-ink glyphs. See: |
| 273 | // https://github.com/harfbuzz/harfbuzz/issues/1532 |
| 274 | base_extents.width = face.glyph_h_advance(base_glyph); |
| 275 | |
| 276 | let lig_id = _hb_glyph_info_get_lig_id(base_info) as u32; |
| 277 | let num_lig_components = _hb_glyph_info_get_lig_num_comps(base_info) as i32; |
| 278 | |
| 279 | let mut x_offset = 0; |
| 280 | let mut y_offset = 0; |
| 281 | if buffer.direction.is_forward() { |
| 282 | x_offset -= base_pos.x_advance; |
| 283 | y_offset -= base_pos.y_advance; |
| 284 | } |
| 285 | |
| 286 | let mut last_lig_component: i32 = -1; |
| 287 | let mut last_combining_class: u8 = 255; |
| 288 | let mut component_extents = base_extents; |
| 289 | let mut cluster_extents = base_extents; |
| 290 | |
| 291 | for (info, pos) in buffer.info[base + 1..end] |
| 292 | .iter() |
| 293 | .zip(&mut buffer.pos[base + 1..end]) |
| 294 | { |
| 295 | if _hb_glyph_info_get_modified_combining_class(info) != 0 { |
| 296 | if num_lig_components > 1 { |
| 297 | let this_lig_id = _hb_glyph_info_get_lig_id(info) as u32; |
| 298 | let mut this_lig_component = _hb_glyph_info_get_lig_comp(info) as i32 - 1; |
| 299 | |
| 300 | // Conditions for attaching to the last component. |
| 301 | if lig_id == 0 || lig_id != this_lig_id || this_lig_component >= num_lig_components |
| 302 | { |
| 303 | this_lig_component = num_lig_components - 1; |
| 304 | } |
| 305 | |
| 306 | if last_lig_component != this_lig_component { |
| 307 | last_lig_component = this_lig_component; |
| 308 | last_combining_class = 255; |
| 309 | component_extents = base_extents; |
| 310 | |
| 311 | if horizontal_dir == Direction::Invalid { |
| 312 | horizontal_dir = if plan.direction.is_horizontal() { |
| 313 | plan.direction |
| 314 | } else { |
| 315 | plan.script |
| 316 | .and_then(Direction::from_script) |
| 317 | .unwrap_or(Direction::LeftToRight) |
| 318 | }; |
| 319 | } |
| 320 | |
| 321 | component_extents.x_bearing += (if horizontal_dir == Direction::LeftToRight { |
| 322 | this_lig_component |
| 323 | } else { |
| 324 | num_lig_components - 1 - this_lig_component |
| 325 | } * component_extents.width) |
| 326 | / num_lig_components; |
| 327 | |
| 328 | component_extents.width /= num_lig_components; |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | let this_combining_class = _hb_glyph_info_get_modified_combining_class(info); |
| 333 | if last_combining_class != this_combining_class { |
| 334 | last_combining_class = this_combining_class; |
| 335 | cluster_extents = component_extents; |
| 336 | } |
| 337 | |
| 338 | position_mark( |
| 339 | plan, |
| 340 | face, |
| 341 | buffer.direction, |
| 342 | info.as_glyph(), |
| 343 | pos, |
| 344 | &mut cluster_extents, |
| 345 | conv_combining_class(this_combining_class), |
| 346 | ); |
| 347 | |
| 348 | pos.x_advance = 0; |
| 349 | pos.y_advance = 0; |
| 350 | pos.x_offset += x_offset; |
| 351 | pos.y_offset += y_offset; |
| 352 | } else { |
| 353 | if buffer.direction.is_forward() { |
| 354 | x_offset -= pos.x_advance; |
| 355 | y_offset -= pos.y_advance; |
| 356 | } else { |
| 357 | x_offset += pos.x_advance; |
| 358 | y_offset += pos.y_advance; |
| 359 | } |
| 360 | } |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | fn position_cluster( |
| 365 | plan: &hb_ot_shape_plan_t, |
| 366 | face: &hb_font_t, |
| 367 | buffer: &mut hb_buffer_t, |
| 368 | start: usize, |
| 369 | end: usize, |
| 370 | adjust_offsets_when_zeroing: bool, |
| 371 | ) { |
| 372 | if end - start < 2 { |
| 373 | return; |
| 374 | } |
| 375 | |
| 376 | // Find the base glyph |
| 377 | let mut i: usize = start; |
| 378 | while i < end { |
| 379 | if !_hb_glyph_info_is_unicode_mark(&buffer.info[i]) { |
| 380 | // Find mark glyphs |
| 381 | let mut j: usize = i + 1; |
| 382 | while j < end && _hb_glyph_info_is_unicode_mark(&buffer.info[j]) { |
| 383 | j += 1; |
| 384 | } |
| 385 | |
| 386 | position_around_base(plan, face, buffer, base:i, end:j, adjust_offsets_when_zeroing); |
| 387 | i = j - 1; |
| 388 | } |
| 389 | i += 1; |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | pub fn position_marks( |
| 394 | plan: &hb_ot_shape_plan_t, |
| 395 | face: &hb_font_t, |
| 396 | buffer: &mut hb_buffer_t, |
| 397 | adjust_offsets_when_zeroing: bool, |
| 398 | ) { |
| 399 | let mut start: usize = 0; |
| 400 | let len: usize = buffer.len; |
| 401 | for i: usize in 1..len { |
| 402 | if !_hb_glyph_info_is_unicode_mark(&buffer.info[i]) { |
| 403 | position_cluster(plan, face, buffer, start, end:i, adjust_offsets_when_zeroing); |
| 404 | start = i; |
| 405 | } |
| 406 | } |
| 407 | |
| 408 | position_cluster(plan, face, buffer, start, end:len, adjust_offsets_when_zeroing); |
| 409 | } |
| 410 | |
| 411 | pub fn _hb_ot_shape_fallback_kern(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) { |
| 412 | // STUB: this is deprecated in HarfBuzz |
| 413 | } |
| 414 | |
| 415 | pub fn _hb_ot_shape_fallback_spaces( |
| 416 | _: &hb_ot_shape_plan_t, |
| 417 | face: &hb_font_t, |
| 418 | buffer: &mut hb_buffer_t, |
| 419 | ) { |
| 420 | use super::unicode::hb_unicode_funcs_t as t; |
| 421 | |
| 422 | let len = buffer.len; |
| 423 | let horizontal = buffer.direction.is_horizontal(); |
| 424 | for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) { |
| 425 | if _hb_glyph_info_is_unicode_space(info) && !_hb_glyph_info_ligated(info) { |
| 426 | let space_type = _hb_glyph_info_get_unicode_space_fallback_type(info); |
| 427 | match space_type { |
| 428 | t::SPACE_EM |
| 429 | | t::SPACE_EM_2 |
| 430 | | t::SPACE_EM_3 |
| 431 | | t::SPACE_EM_4 |
| 432 | | t::SPACE_EM_5 |
| 433 | | t::SPACE_EM_6 |
| 434 | | t::SPACE_EM_16 => { |
| 435 | let length = |
| 436 | (face.units_per_em as i32 + (space_type as i32) / 2) / space_type as i32; |
| 437 | if horizontal { |
| 438 | pos.x_advance = length; |
| 439 | } else { |
| 440 | pos.y_advance = -length; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | t::SPACE_4_EM_18 => { |
| 445 | let length = ((face.units_per_em as i64) * 4 / 18) as i32; |
| 446 | if horizontal { |
| 447 | pos.x_advance = length |
| 448 | } else { |
| 449 | pos.y_advance = -length; |
| 450 | } |
| 451 | } |
| 452 | |
| 453 | t::SPACE_FIGURE => { |
| 454 | for u in '0' ..='9' { |
| 455 | if let Some(glyph) = face.get_nominal_glyph(u as u32) { |
| 456 | if horizontal { |
| 457 | pos.x_advance = face.glyph_h_advance(glyph); |
| 458 | } else { |
| 459 | pos.y_advance = face.glyph_v_advance(glyph); |
| 460 | } |
| 461 | break; |
| 462 | } |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | t::SPACE_PUNCTUATION => { |
| 467 | let punct = face |
| 468 | .get_nominal_glyph('.' as u32) |
| 469 | .or_else(|| face.get_nominal_glyph(',' as u32)); |
| 470 | |
| 471 | if let Some(glyph) = punct { |
| 472 | if horizontal { |
| 473 | pos.x_advance = face.glyph_h_advance(glyph); |
| 474 | } else { |
| 475 | pos.y_advance = face.glyph_v_advance(glyph); |
| 476 | } |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | t::SPACE_NARROW => { |
| 481 | // Half-space? |
| 482 | // Unicode doc https://unicode.org/charts/PDF/U2000.pdf says ~1/4 or 1/5 of EM. |
| 483 | // However, in my testing, many fonts have their regular space being about that |
| 484 | // size. To me, a percentage of the space width makes more sense. Half is as |
| 485 | // good as any. |
| 486 | if horizontal { |
| 487 | pos.x_advance /= 2; |
| 488 | } else { |
| 489 | pos.y_advance /= 2; |
| 490 | } |
| 491 | } |
| 492 | |
| 493 | _ => {} |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | // TODO: can we cast directly? |
| 500 | fn conv_combining_class(n: u8) -> CanonicalCombiningClass { |
| 501 | use CanonicalCombiningClass as Class; |
| 502 | match n { |
| 503 | 1 => Class::Overlay, |
| 504 | 6 => Class::HanReading, |
| 505 | 7 => Class::Nukta, |
| 506 | 8 => Class::KanaVoicing, |
| 507 | 9 => Class::Virama, |
| 508 | 10 => Class::CCC10, |
| 509 | 11 => Class::CCC11, |
| 510 | 12 => Class::CCC12, |
| 511 | 13 => Class::CCC13, |
| 512 | 14 => Class::CCC14, |
| 513 | 15 => Class::CCC15, |
| 514 | 16 => Class::CCC16, |
| 515 | 17 => Class::CCC17, |
| 516 | 18 => Class::CCC18, |
| 517 | 19 => Class::CCC19, |
| 518 | 20 => Class::CCC20, |
| 519 | 21 => Class::CCC21, |
| 520 | 22 => Class::CCC22, |
| 521 | 23 => Class::CCC23, |
| 522 | 24 => Class::CCC24, |
| 523 | 25 => Class::CCC25, |
| 524 | 26 => Class::CCC26, |
| 525 | 27 => Class::CCC27, |
| 526 | 28 => Class::CCC28, |
| 527 | 29 => Class::CCC29, |
| 528 | 30 => Class::CCC30, |
| 529 | 31 => Class::CCC31, |
| 530 | 32 => Class::CCC32, |
| 531 | 33 => Class::CCC33, |
| 532 | 34 => Class::CCC34, |
| 533 | 35 => Class::CCC35, |
| 534 | 36 => Class::CCC36, |
| 535 | 84 => Class::CCC84, |
| 536 | 91 => Class::CCC91, |
| 537 | 103 => Class::CCC103, |
| 538 | 107 => Class::CCC107, |
| 539 | 118 => Class::CCC118, |
| 540 | 122 => Class::CCC122, |
| 541 | 129 => Class::CCC129, |
| 542 | 130 => Class::CCC130, |
| 543 | 132 => Class::CCC132, |
| 544 | 200 => Class::AttachedBelowLeft, |
| 545 | 202 => Class::AttachedBelow, |
| 546 | 214 => Class::AttachedAbove, |
| 547 | 216 => Class::AttachedAboveRight, |
| 548 | 218 => Class::BelowLeft, |
| 549 | 220 => Class::Below, |
| 550 | 222 => Class::BelowRight, |
| 551 | 224 => Class::Left, |
| 552 | 226 => Class::Right, |
| 553 | 228 => Class::AboveLeft, |
| 554 | 230 => Class::Above, |
| 555 | 232 => Class::AboveRight, |
| 556 | 233 => Class::DoubleBelow, |
| 557 | 234 => Class::DoubleAbove, |
| 558 | 240 => Class::IotaSubscript, |
| 559 | _ => Class::NotReordered, |
| 560 | } |
| 561 | } |
| 562 | |