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(Some(base), Some(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 = GlyphExtents::default();
164 if !face.glyph_extents(base_glyph, &mut base_extents) {
165 zero_mark_advances(buffer, base + 1, end, adjust_offsets_when_zeroing);
166 return;
167 };
168
169 base_extents.y_bearing += base_pos.y_offset;
170 base_extents.x_bearing = 0;
171
172 // Use horizontal advance for horizontal positioning.
173 // Generally a better idea. Also works for zero-ink glyphs. See:
174 // https://github.com/harfbuzz/harfbuzz/issues/1532
175 base_extents.width = face.glyph_h_advance(base_glyph) as i32;
176
177 let lig_id = base_info.lig_id() as u32;
178 let num_lig_components = base_info.lig_num_comps() as i32;
179
180 let mut x_offset = 0;
181 let mut y_offset = 0;
182 if buffer.direction.is_forward() {
183 x_offset -= base_pos.x_advance;
184 y_offset -= base_pos.y_advance;
185 }
186
187 let mut last_lig_component: i32 = -1;
188 let mut last_combining_class: u8 = 255;
189 let mut component_extents = base_extents;
190 let mut cluster_extents = base_extents;
191
192 for (info, pos) in buffer.info[base + 1..end]
193 .iter()
194 .zip(&mut buffer.pos[base + 1..end])
195 {
196 if info.modified_combining_class() != 0 {
197 if num_lig_components > 1 {
198 let this_lig_id = info.lig_id() as u32;
199 let mut this_lig_component = info.lig_comp() as i32 - 1;
200
201 // Conditions for attaching to the last component.
202 if lig_id == 0 || lig_id != this_lig_id || this_lig_component >= num_lig_components
203 {
204 this_lig_component = num_lig_components - 1;
205 }
206
207 if last_lig_component != this_lig_component {
208 last_lig_component = this_lig_component;
209 last_combining_class = 255;
210 component_extents = base_extents;
211
212 if horizontal_dir == Direction::Invalid {
213 horizontal_dir = if plan.direction.is_horizontal() {
214 plan.direction
215 } else {
216 plan.script
217 .and_then(Direction::from_script)
218 .unwrap_or(Direction::LeftToRight)
219 };
220 }
221
222 component_extents.x_bearing += (if horizontal_dir == Direction::LeftToRight {
223 this_lig_component
224 } else {
225 num_lig_components - 1 - this_lig_component
226 } * component_extents.width)
227 / num_lig_components;
228
229 component_extents.width /= num_lig_components;
230 }
231 }
232
233 let this_combining_class = info.modified_combining_class();
234 if last_combining_class != this_combining_class {
235 last_combining_class = this_combining_class;
236 cluster_extents = component_extents;
237 }
238
239 position_mark(
240 plan,
241 face,
242 buffer.direction,
243 info.as_glyph(),
244 pos,
245 &mut cluster_extents,
246 conv_combining_class(this_combining_class),
247 );
248
249 pos.x_advance = 0;
250 pos.y_advance = 0;
251 pos.x_offset += x_offset;
252 pos.y_offset += y_offset;
253 } else {
254 if buffer.direction.is_forward() {
255 x_offset -= pos.x_advance;
256 y_offset -= pos.y_advance;
257 } else {
258 x_offset += pos.x_advance;
259 y_offset += pos.y_advance;
260 }
261 }
262 }
263}
264
265fn zero_mark_advances(
266 buffer: &mut Buffer,
267 start: usize,
268 end: usize,
269 adjust_offsets_when_zeroing: bool,
270) {
271 for (info: &GlyphInfo, pos: &mut GlyphPosition) in bufferIter<'_, GlyphInfo>.info[start..end]
272 .iter()
273 .zip(&mut buffer.pos[start..end])
274 {
275 if info.general_category() == GeneralCategory::NonspacingMark {
276 if adjust_offsets_when_zeroing {
277 pos.x_offset -= pos.x_advance;
278 pos.y_offset -= pos.y_advance;
279 }
280 pos.x_advance = 0;
281 pos.y_advance = 0;
282 }
283 }
284}
285
286fn position_mark(
287 _: &ShapePlan,
288 face: &Face,
289 direction: Direction,
290 glyph: GlyphId,
291 pos: &mut GlyphPosition,
292 base_extents: &mut GlyphExtents,
293 combining_class: CanonicalCombiningClass,
294) {
295 use CanonicalCombiningClass as Class;
296
297 let mut mark_extents = GlyphExtents::default();
298 if !face.glyph_extents(glyph, &mut mark_extents) {
299 return;
300 };
301
302 let y_gap = face.units_per_em as i32 / 16;
303 pos.x_offset = 0;
304 pos.y_offset = 0;
305
306 // We don't position LEFT and RIGHT marks.
307
308 // X positioning
309 match combining_class {
310 Class::DoubleBelow | Class::DoubleAbove if direction.is_horizontal() => {
311 pos.x_offset += base_extents.x_bearing
312 + if direction.is_forward() {
313 base_extents.width
314 } else {
315 0
316 }
317 - mark_extents.width / 2
318 - mark_extents.x_bearing;
319 }
320
321 Class::AttachedBelowLeft | Class::BelowLeft | Class::AboveLeft => {
322 // Left align.
323 pos.x_offset += base_extents.x_bearing - mark_extents.x_bearing;
324 }
325
326 Class::AttachedAboveRight | Class::BelowRight | Class::AboveRight => {
327 // Right align.
328 pos.x_offset += base_extents.x_bearing + base_extents.width
329 - mark_extents.width
330 - mark_extents.x_bearing;
331 }
332
333 Class::AttachedBelow | Class::AttachedAbove | Class::Below | Class::Above | _ => {
334 // Center align.
335 pos.x_offset += base_extents.x_bearing + (base_extents.width - mark_extents.width) / 2
336 - mark_extents.x_bearing;
337 }
338 }
339
340 let is_attached = matches!(
341 combining_class,
342 Class::AttachedBelowLeft
343 | Class::AttachedBelow
344 | Class::AttachedAbove
345 | Class::AttachedAboveRight
346 );
347
348 // Y positioning.
349 match combining_class {
350 Class::DoubleBelow
351 | Class::BelowLeft
352 | Class::Below
353 | Class::BelowRight
354 | Class::AttachedBelowLeft
355 | Class::AttachedBelow => {
356 if !is_attached {
357 // Add gap.
358 base_extents.height -= y_gap;
359 }
360
361 pos.y_offset = base_extents.y_bearing + base_extents.height - mark_extents.y_bearing;
362
363 // Never shift up "below" marks.
364 if (y_gap > 0) == (pos.y_offset > 0) {
365 base_extents.height -= pos.y_offset;
366 pos.y_offset = 0;
367 }
368
369 base_extents.height += mark_extents.height;
370 }
371
372 Class::DoubleAbove
373 | Class::AboveLeft
374 | Class::Above
375 | Class::AboveRight
376 | Class::AttachedAbove
377 | Class::AttachedAboveRight => {
378 if !is_attached {
379 // Add gap.
380 base_extents.y_bearing += y_gap;
381 base_extents.height -= y_gap;
382 }
383
384 pos.y_offset = base_extents.y_bearing - (mark_extents.y_bearing + mark_extents.height);
385
386 // Don't shift down "above" marks too much.
387 if (y_gap > 0) != (pos.y_offset > 0) {
388 let correction = -pos.y_offset / 2;
389 base_extents.y_bearing += correction;
390 base_extents.height -= correction;
391 pos.y_offset += correction;
392 }
393
394 base_extents.y_bearing -= mark_extents.height;
395 base_extents.height += mark_extents.height;
396 }
397
398 _ => {}
399 }
400}
401
402pub fn kern(_: &ShapePlan, _: &Face, _: &mut Buffer) {
403 // STUB: this is deprecated in HarfBuzz
404}
405
406pub fn adjust_spaces(_: &ShapePlan, face: &Face, buffer: &mut Buffer) {
407 let len = buffer.len;
408 let horizontal = buffer.direction.is_horizontal();
409 for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
410 let space_type = match info.space_fallback() {
411 Some(fallback) if !info.is_ligated() => fallback,
412 _ => continue,
413 };
414
415 match space_type {
416 space::SPACE_EM
417 | space::SPACE_EM_2
418 | space::SPACE_EM_3
419 | space::SPACE_EM_4
420 | space::SPACE_EM_5
421 | space::SPACE_EM_6
422 | space::SPACE_EM_16 => {
423 let length =
424 (face.units_per_em as i32 + (space_type as i32) / 2) / space_type as i32;
425 if horizontal {
426 pos.x_advance = length;
427 } else {
428 pos.y_advance = -length;
429 }
430 }
431
432 space::SPACE_4_EM_18 => {
433 let length = ((face.units_per_em as i64) * 4 / 18) as i32;
434 if horizontal {
435 pos.x_advance = length
436 } else {
437 pos.y_advance = -length;
438 }
439 }
440
441 space::SPACE_FIGURE => {
442 for u in '0'..='9' {
443 if let Some(glyph) = face.glyph_index(u as u32) {
444 if horizontal {
445 pos.x_advance = face.glyph_h_advance(glyph) as i32;
446 } else {
447 pos.y_advance = face.glyph_v_advance(glyph);
448 }
449 break;
450 }
451 }
452 }
453
454 space::SPACE_PUNCTUATION => {
455 let punct = face
456 .glyph_index('.' as u32)
457 .or_else(|| face.glyph_index(',' as u32));
458
459 if let Some(glyph) = punct {
460 if horizontal {
461 pos.x_advance = face.glyph_h_advance(glyph) as i32;
462 } else {
463 pos.y_advance = face.glyph_v_advance(glyph);
464 }
465 }
466 }
467
468 space::SPACE_NARROW => {
469 // Half-space?
470 // Unicode doc https://unicode.org/charts/PDF/U2000.pdf says ~1/4 or 1/5 of EM.
471 // However, in my testing, many fonts have their regular space being about that
472 // size. To me, a percentage of the space width makes more sense. Half is as
473 // good as any.
474 if horizontal {
475 pos.x_advance /= 2;
476 } else {
477 pos.y_advance /= 2;
478 }
479 }
480
481 _ => {}
482 }
483 }
484}
485
486// TODO: can we cast directly?
487fn conv_combining_class(n: u8) -> CanonicalCombiningClass {
488 use CanonicalCombiningClass as Class;
489 match n {
490 1 => Class::Overlay,
491 6 => Class::HanReading,
492 7 => Class::Nukta,
493 8 => Class::KanaVoicing,
494 9 => Class::Virama,
495 10 => Class::CCC10,
496 11 => Class::CCC11,
497 12 => Class::CCC12,
498 13 => Class::CCC13,
499 14 => Class::CCC14,
500 15 => Class::CCC15,
501 16 => Class::CCC16,
502 17 => Class::CCC17,
503 18 => Class::CCC18,
504 19 => Class::CCC19,
505 20 => Class::CCC20,
506 21 => Class::CCC21,
507 22 => Class::CCC22,
508 23 => Class::CCC23,
509 24 => Class::CCC24,
510 25 => Class::CCC25,
511 26 => Class::CCC26,
512 27 => Class::CCC27,
513 28 => Class::CCC28,
514 29 => Class::CCC29,
515 30 => Class::CCC30,
516 31 => Class::CCC31,
517 32 => Class::CCC32,
518 33 => Class::CCC33,
519 34 => Class::CCC34,
520 35 => Class::CCC35,
521 36 => Class::CCC36,
522 84 => Class::CCC84,
523 91 => Class::CCC91,
524 103 => Class::CCC103,
525 107 => Class::CCC107,
526 118 => Class::CCC118,
527 122 => Class::CCC122,
528 129 => Class::CCC129,
529 130 => Class::CCC130,
530 132 => Class::CCC132,
531 200 => Class::AttachedBelowLeft,
532 202 => Class::AttachedBelow,
533 214 => Class::AttachedAbove,
534 216 => Class::AttachedAboveRight,
535 218 => Class::BelowLeft,
536 220 => Class::Below,
537 222 => Class::BelowRight,
538 224 => Class::Left,
539 226 => Class::Right,
540 228 => Class::AboveLeft,
541 230 => Class::Above,
542 232 => Class::AboveRight,
543 233 => Class::DoubleBelow,
544 234 => Class::DoubleAbove,
545 240 => Class::IotaSubscript,
546 _ => Class::NotReordered,
547 }
548}
549