1 | use ttf_parser::GlyphId; |
2 | |
3 | use crate::buffer::{Buffer, GlyphPosition}; |
4 | use crate::face::GlyphExtents; |
5 | use crate::plan::ShapePlan; |
6 | use crate::unicode::{modified_combining_class, space, CanonicalCombiningClass, GeneralCategory}; |
7 | use crate::{Direction, Face}; |
8 | |
9 | pub 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 | |
20 | fn 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 | |
101 | pub 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 | |
119 | fn 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 | |
148 | fn 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 | |
265 | fn 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 | |
286 | fn 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 | |
402 | pub fn kern(_: &ShapePlan, _: &Face, _: &mut Buffer) { |
403 | // STUB: this is deprecated in HarfBuzz |
404 | } |
405 | |
406 | pub 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? |
487 | fn 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 | |