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