1use read_fonts::types::GlyphId;
2
3use super::buffer::{hb_buffer_t, GlyphPosition};
4use super::face::GlyphExtents;
5use super::font_funcs::FontFuncsDispatch;
6use super::ot_shape_plan::hb_ot_shape_plan_t;
7use super::unicode::*;
8use super::{hb_font_t, Direction};
9
10struct FallbackShapeContext<'a, 'x, 'u> {
11 plan: &'a hb_ot_shape_plan_t,
12 face: &'a hb_font_t<'a>,
13 buffer: &'x mut hb_buffer_t,
14 adjust_offsets_when_zeroing: bool,
15 font_funcs: &'x mut FontFuncsDispatch<'a, 'u>,
16}
17
18fn recategorize_combining_class(u: u32, mut class: u8) -> u8 {
19 use modified_combining_class as mcc;
20
21 if class >= 200 {
22 return class;
23 }
24
25 if u & !0xFF == 0x0E00 {
27 if class == 0 {
28 match u {
29 0x0E31 | 0x0E34 | 0x0E35 | 0x0E36 | 0x0E37 | 0x0E47 | 0x0E4C | 0x0E4D | 0x0E4E => {
30 class = combining_class::AboveRight;
31 }
32
33 0x0EB1 | 0x0EB4 | 0x0EB5 | 0x0EB6 | 0x0EB7 | 0x0EBB | 0x0ECC | 0x0ECD => {
34 class = combining_class::Above;
35 }
36
37 0x0EBC => class = combining_class::Below,
38
39 _ => {}
40 }
41 } else {
42 if u == 0x0E3A {
44 class = combining_class::BelowRight;
45 }
46 }
47 }
48
49 match class {
50 mcc::CCC10 => combining_class::Below, mcc::CCC11 => combining_class::Below, mcc::CCC12 => combining_class::Below, mcc::CCC13 => combining_class::Below, mcc::CCC14 => combining_class::Below, mcc::CCC15 => combining_class::Below, mcc::CCC16 => combining_class::Below, mcc::CCC17 => combining_class::Below, mcc::CCC18 => combining_class::Below, mcc::CCC20 => combining_class::Below, mcc::CCC22 => combining_class::Below, mcc::CCC23 => combining_class::AttachedAbove, mcc::CCC24 => combining_class::AboveRight, mcc::CCC25 => combining_class::AboveLeft, mcc::CCC19 => combining_class::AboveLeft, mcc::CCC26 => combining_class::Above, mcc::CCC21 => class, mcc::CCC27 => combining_class::Above, mcc::CCC28 => combining_class::Above, mcc::CCC30 => combining_class::Above, mcc::CCC31 => combining_class::Above, mcc::CCC33 => combining_class::Above, mcc::CCC34 => combining_class::Above, mcc::CCC35 => combining_class::Above, mcc::CCC36 => combining_class::Above, mcc::CCC29 => combining_class::Below, mcc::CCC32 => combining_class::Below, mcc::CCC103 => combining_class::BelowRight, mcc::CCC107 => combining_class::AboveRight, mcc::CCC118 => combining_class::Below, mcc::CCC122 => combining_class::Above, mcc::CCC129 => combining_class::Below, mcc::CCC130 => combining_class::Above, mcc::CCC132 => combining_class::Below, _ => class,
95 }
96}
97
98pub fn _hb_ot_shape_fallback_mark_position_recategorize_marks(
99 _: &hb_ot_shape_plan_t,
100 _: &hb_font_t,
101 buffer: &mut hb_buffer_t,
102) {
103 let len = buffer.len;
104 for info in &mut buffer.info[..len] {
105 if info.general_category() == GeneralCategory::NON_SPACING_MARK {
106 let mut class = info.modified_combining_class();
107 class = recategorize_combining_class(info.glyph_id, class);
108 info.set_modified_combining_class(class);
109 }
110 }
111}
112
113fn zero_mark_advances(
114 buffer: &mut hb_buffer_t,
115 start: usize,
116 end: usize,
117 adjust_offsets_when_zeroing: bool,
118) {
119 for (info, pos) in buffer.info[start..end]
120 .iter()
121 .zip(&mut buffer.pos[start..end])
122 {
123 if info.general_category() == GeneralCategory::NON_SPACING_MARK {
124 if adjust_offsets_when_zeroing {
125 pos.x_offset -= pos.x_advance;
126 pos.y_offset -= pos.y_advance;
127 }
128 pos.x_advance = 0;
129 pos.y_advance = 0;
130 }
131 }
132}
133
134fn position_mark(
135 face: &hb_font_t,
136 direction: Direction,
137 font_funcs: &mut FontFuncsDispatch<'_, '_>,
138 glyph: GlyphId,
139 pos: &mut GlyphPosition,
140 base_extents: &mut GlyphExtents,
141 combining_class: u8,
142) {
143 let Some(mark_extents) = font_funcs.extents(glyph) else {
144 return;
145 };
146
147 let y_gap = font_funcs.scale().scale_y(face.units_per_em as i32 / 16);
148 pos.x_offset = 0;
149 pos.y_offset = 0;
150
151 match combining_class {
155 combining_class::DoubleBelow | combining_class::DoubleAbove
156 if direction.is_horizontal() =>
157 {
158 pos.x_offset = pos
159 .x_offset
160 .saturating_add(base_extents.x_bearing)
161 .saturating_add(if direction.is_forward() {
162 base_extents.width
163 } else {
164 0
165 })
166 .saturating_sub(mark_extents.width / 2)
167 .saturating_sub(mark_extents.x_bearing);
168 }
169
170 combining_class::AttachedBelowLeft
171 | combining_class::BelowLeft
172 | combining_class::AboveLeft => {
173 pos.x_offset = pos
175 .x_offset
176 .saturating_add(base_extents.x_bearing)
177 .saturating_sub(mark_extents.x_bearing);
178 }
179
180 combining_class::AttachedAboveRight
181 | combining_class::BelowRight
182 | combining_class::AboveRight => {
183 pos.x_offset = pos
185 .x_offset
186 .saturating_add(base_extents.x_bearing)
187 .saturating_add(base_extents.width)
188 .saturating_sub(mark_extents.width)
189 .saturating_sub(mark_extents.x_bearing);
190 }
191
192 combining_class::AttachedBelow
193 | combining_class::AttachedAbove
194 | combining_class::Below
195 | combining_class::Above
196 | _ => {
197 pos.x_offset = pos
199 .x_offset
200 .saturating_add(base_extents.x_bearing)
201 .saturating_add((base_extents.width.saturating_sub(mark_extents.width)) / 2)
202 .saturating_sub(mark_extents.x_bearing);
203 }
204 }
205
206 let is_attached = matches!(
207 combining_class,
208 combining_class::AttachedBelowLeft
209 | combining_class::AttachedBelow
210 | combining_class::AttachedAbove
211 | combining_class::AttachedAboveRight
212 );
213
214 match combining_class {
216 combining_class::DoubleBelow
217 | combining_class::BelowLeft
218 | combining_class::Below
219 | combining_class::BelowRight
220 | combining_class::AttachedBelowLeft
221 | combining_class::AttachedBelow => {
222 if !is_attached {
223 base_extents.height = base_extents.height.saturating_sub(y_gap);
225 }
226
227 pos.y_offset = base_extents
228 .y_bearing
229 .saturating_add(base_extents.height)
230 .saturating_sub(mark_extents.y_bearing);
231
232 if (y_gap > 0) == (pos.y_offset > 0) {
234 base_extents.height = base_extents.height.saturating_sub(pos.y_offset);
235 pos.y_offset = 0;
236 }
237
238 base_extents.height = base_extents.height.saturating_add(mark_extents.height);
239 }
240
241 combining_class::DoubleAbove
242 | combining_class::AboveLeft
243 | combining_class::Above
244 | combining_class::AboveRight
245 | combining_class::AttachedAbove
246 | combining_class::AttachedAboveRight => {
247 if !is_attached {
248 base_extents.y_bearing = base_extents.y_bearing.saturating_add(y_gap);
250 base_extents.height = base_extents.height.saturating_sub(y_gap);
251 }
252
253 pos.y_offset = base_extents
254 .y_bearing
255 .saturating_sub(mark_extents.y_bearing.saturating_add(mark_extents.height));
256
257 if (y_gap > 0) != (pos.y_offset > 0) {
259 let correction = -pos.y_offset / 2;
260 base_extents.y_bearing = base_extents.y_bearing.saturating_add(correction);
261 base_extents.height = base_extents.height.saturating_sub(correction);
262 pos.y_offset = pos.y_offset.saturating_add(correction);
263 }
264
265 base_extents.y_bearing = base_extents.y_bearing.saturating_sub(mark_extents.height);
266 base_extents.height = base_extents.height.saturating_add(mark_extents.height);
267 }
268
269 _ => {}
270 }
271}
272
273fn position_around_base(ctx: &mut FallbackShapeContext, base: usize, end: usize) {
274 let mut horizontal_dir = Direction::Invalid;
275 let face = ctx.face;
276 ctx.buffer.unsafe_to_break(Some(base), Some(end));
277
278 let base_info = ctx.buffer.info[base];
279 let base_pos = ctx.buffer.pos[base];
280 let base_glyph = base_info.as_glyph();
281
282 let Some(mut base_extents) = ctx.font_funcs.extents(base_glyph) else {
283 zero_mark_advances(ctx.buffer, base + 1, end, ctx.adjust_offsets_when_zeroing);
284 return;
285 };
286
287 base_extents.y_bearing += base_pos.y_offset;
288 base_extents.x_bearing = 0;
289
290 base_extents.width = ctx.font_funcs.advance_width(base_glyph);
294
295 let lig_id = base_info.lig_id() as u32;
296 let num_lig_components = base_info.lig_num_comps() as i32;
297
298 let mut x_offset = 0;
299 let mut y_offset = 0;
300 if ctx.buffer.direction.is_forward() {
301 x_offset -= base_pos.x_advance;
302 y_offset -= base_pos.y_advance;
303 }
304
305 let mut last_lig_component: i32 = -1;
306 let mut last_combining_class: u8 = 255;
307 let mut component_extents = base_extents;
308 let mut cluster_extents = base_extents;
309 let direction = ctx.buffer.direction;
310 let font_funcs = &mut *ctx.font_funcs;
311
312 for (info, pos) in ctx.buffer.info[base + 1..end]
313 .iter()
314 .zip(&mut ctx.buffer.pos[base + 1..end])
315 {
316 if info.modified_combining_class() != 0 {
317 if num_lig_components > 1 {
318 let this_lig_id = info.lig_id() as u32;
319 let mut this_lig_component = info.lig_comp() as i32 - 1;
320
321 if lig_id == 0 || lig_id != this_lig_id || this_lig_component >= num_lig_components
323 {
324 this_lig_component = num_lig_components - 1;
325 }
326
327 if last_lig_component != this_lig_component {
328 last_lig_component = this_lig_component;
329 last_combining_class = 255;
330 component_extents = base_extents;
331
332 if horizontal_dir == Direction::Invalid {
333 horizontal_dir = if ctx.plan.direction.is_horizontal() {
334 ctx.plan.direction
335 } else {
336 ctx.plan
337 .script
338 .and_then(Direction::from_script)
339 .unwrap_or(Direction::LeftToRight)
340 };
341 }
342
343 component_extents.x_bearing += (if horizontal_dir == Direction::LeftToRight {
344 this_lig_component
345 } else {
346 num_lig_components - 1 - this_lig_component
347 } * component_extents.width)
348 / num_lig_components;
349
350 component_extents.width /= num_lig_components;
351 }
352 }
353
354 let this_combining_class = info.modified_combining_class();
355 if last_combining_class != this_combining_class {
356 last_combining_class = this_combining_class;
357 cluster_extents = component_extents;
358 }
359
360 position_mark(
361 face,
362 direction,
363 font_funcs,
364 info.as_glyph(),
365 pos,
366 &mut cluster_extents,
367 this_combining_class,
368 );
369
370 pos.x_advance = 0;
371 pos.y_advance = 0;
372 pos.x_offset += x_offset;
373 pos.y_offset += y_offset;
374 } else {
375 if direction.is_forward() {
376 x_offset -= pos.x_advance;
377 y_offset -= pos.y_advance;
378 } else {
379 x_offset += pos.x_advance;
380 y_offset += pos.y_advance;
381 }
382 }
383 }
384}
385
386fn position_cluster_impl(ctx: &mut FallbackShapeContext, start: usize, end: usize) {
387 let mut i = start;
389 while i < end {
390 if !ctx.buffer.info[i].is_unicode_mark() {
391 let mut j = i + 1;
393 while j < end
394 && (ctx.buffer.info[j].is_hidden()
395 || ctx.buffer.info[j].is_default_ignorable()
396 || ctx.buffer.info[j].is_unicode_mark())
397 {
398 j += 1;
399 }
400
401 position_around_base(ctx, i, j);
402 i = j - 1;
403 }
404 i += 1;
405 }
406}
407
408#[inline(always)]
409fn position_cluster(ctx: &mut FallbackShapeContext, start: usize, end: usize) {
410 if end - start < 2 {
411 return;
412 }
413
414 position_cluster_impl(ctx, start, end);
415}
416
417pub fn position_marks<'a, 'x>(
418 plan: &'a hb_ot_shape_plan_t,
419 face: &'a hb_font_t<'a>,
420 buffer: &'x mut hb_buffer_t,
421 adjust_offsets_when_zeroing: bool,
422 font_funcs: &'x mut FontFuncsDispatch<'a, '_>,
423) {
424 let mut ctx = FallbackShapeContext {
425 plan,
426 face,
427 buffer,
428 adjust_offsets_when_zeroing,
429 font_funcs,
430 };
431
432 ctx.buffer.assert_gsubgpos_vars();
433
434 let mut start = 0;
435 let len = ctx.buffer.len;
436 for i in 1..len {
437 if !ctx.buffer.info[i].is_unicode_mark()
438 && !ctx.buffer.info[i].is_hidden()
439 && !ctx.buffer.info[i].is_default_ignorable()
440 {
441 position_cluster(&mut ctx, start, i);
442 start = i;
443 }
444 }
445
446 position_cluster(&mut ctx, start, len);
447}
448
449pub fn _hb_ot_shape_fallback_kern(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) {
450 }
452
453pub fn _hb_ot_shape_fallback_spaces<'a, 'x>(
454 plan: &'a hb_ot_shape_plan_t,
455 face: &'a hb_font_t<'a>,
456 buffer: &'x mut hb_buffer_t,
457 font_funcs: &'x mut FontFuncsDispatch<'a, '_>,
458) {
459 use super::unicode::hb_unicode_funcs_t as t;
460
461 let _ = plan;
462 let len = buffer.len;
463 let horizontal = buffer.direction.is_horizontal();
464 let scale = *font_funcs.scale();
465 for (info, pos) in buffer.info[..len].iter().zip(&mut buffer.pos[..len]) {
466 if info.is_unicode_space() && !info.ligated() {
467 let space_type = info.unicode_space_fallback_type();
468 match space_type {
469 t::SPACE_EM
470 | t::SPACE_EM_2
471 | t::SPACE_EM_3
472 | t::SPACE_EM_4
473 | t::SPACE_EM_5
474 | t::SPACE_EM_6
475 | t::SPACE_EM_16 => {
476 let length =
477 (face.units_per_em as i32 + (space_type as i32) / 2) / space_type as i32;
478 if horizontal {
479 pos.x_advance = scale.scale_x(length);
480 } else {
481 pos.y_advance = -scale.scale_y(length);
482 }
483 }
484
485 t::SPACE_4_EM_18 => {
486 let length = ((face.units_per_em as i64) * 4 / 18) as i32;
487 if horizontal {
488 pos.x_advance = scale.scale_x(length);
489 } else {
490 pos.y_advance = -scale.scale_y(length);
491 }
492 }
493
494 t::SPACE_FIGURE => {
495 for u in '0'..='9' {
496 if let Some(glyph) = font_funcs.nominal_glyph(u as u32) {
497 if horizontal {
498 pos.x_advance = font_funcs.advance_width(glyph);
499 } else {
500 pos.y_advance = font_funcs.advance_height(glyph);
501 }
502 break;
503 }
504 }
505 }
506
507 t::SPACE_PUNCTUATION => {
508 let punct = font_funcs
509 .nominal_glyph('.' as u32)
510 .or_else(|| font_funcs.nominal_glyph(',' as u32));
511
512 if let Some(glyph) = punct {
513 if horizontal {
514 pos.x_advance = font_funcs.advance_width(glyph);
515 } else {
516 pos.y_advance = font_funcs.advance_height(glyph);
517 }
518 }
519 }
520
521 t::SPACE_NARROW => {
522 if horizontal {
528 pos.x_advance /= 2;
529 } else {
530 pos.y_advance /= 2;
531 }
532 }
533
534 _ => {}
535 }
536 }
537 }
538}