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