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(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 | |
268 | fn 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 | |
289 | fn 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 | |
405 | pub fn kern(_: &ShapePlan, _: &Face, _: &mut Buffer) { |
406 | // STUB: this is deprecated in HarfBuzz |
407 | } |
408 | |
409 | pub 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? |
490 | fn 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 | |