1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum PageRotation {
17 Upright,
19 Ccw,
22 Cw,
25}
26
27impl PageRotation {
28 pub(crate) fn baseline_rebase_degrees(self) -> f32 {
31 match self {
32 PageRotation::Upright => 0.0,
33 PageRotation::Ccw => -90.0,
34 PageRotation::Cw => 90.0,
35 }
36 }
37
38 pub(crate) fn rotate_point(self, x: f32, y: f32) -> (f32, f32) {
40 match self {
41 PageRotation::Upright => (x, y),
42 PageRotation::Ccw => (y, -x),
43 PageRotation::Cw => (-y, x),
44 }
45 }
46
47 pub(crate) fn rotate_box(self, x: &mut f32, y: &mut f32, width: &mut f32, height: &mut f32) {
53 let (x0, x1) = (x.min(*x + *width), x.max(*x + *width));
54 let (y0, y1) = (y.min(*y + *height), y.max(*y + *height));
55 let (new_x, new_y, new_width, new_height) = match self {
56 PageRotation::Upright => (x0, y0, x1 - x0, y1 - y0),
57 PageRotation::Ccw => (y0, -x1, y1 - y0, x1 - x0),
58 PageRotation::Cw => (-y1, x0, y1 - y0, x1 - x0),
59 };
60 *x = new_x;
61 *y = new_y;
62 *width = new_width;
63 *height = new_height;
64 }
65
66 pub(crate) fn unrotate_box(self, x: &mut f32, y: &mut f32, width: &mut f32, height: &mut f32) {
70 let (x0, x1) = (x.min(*x + *width), x.max(*x + *width));
71 let (y0, y1) = (y.min(*y + *height), y.max(*y + *height));
72 let (new_x, new_y, new_width, new_height) = match self {
73 PageRotation::Upright => (x0, y0, x1 - x0, y1 - y0),
74 PageRotation::Ccw => (-y1, x0, y1 - y0, x1 - x0),
78 PageRotation::Cw => (y0, -x1, y1 - y0, x1 - x0),
82 };
83 *x = new_x;
84 *y = new_y;
85 *width = new_width;
86 *height = new_height;
87 }
88}
89
90pub(crate) fn rise_adjusted(tm: &[f32; 6], rise: f32) -> [f32; 6] {
96 if rise == 0.0 {
97 return *tm;
98 }
99 [
100 tm[0],
101 tm[1],
102 tm[2],
103 tm[3],
104 tm[4] + rise * tm[2],
105 tm[5] + rise * tm[3],
106 ]
107}
108
109pub(crate) fn advanced_tm(tm: &[f32; 6], advance_ts: f32, horizontal_scale: f32) -> [f32; 6] {
113 let advance = advance_ts * horizontal_scale;
114 [
115 tm[0],
116 tm[1],
117 tm[2],
118 tm[3],
119 tm[4] + advance * tm[0],
120 tm[5] + advance * tm[1],
121 ]
122}
123
124#[derive(Debug, Clone, Copy, PartialEq)]
126pub(crate) struct RunGeometry {
127 pub(crate) x: f32,
128 pub(crate) y: f32,
129 pub(crate) width: f32,
130 pub(crate) height: f32,
131 pub(crate) rotation: f32,
132 pub(crate) advance_known: bool,
134}
135
136impl RunGeometry {
137 pub(crate) fn is_upright(&self) -> bool {
140 let rotation = self.rotation.rem_euclid(360.0);
141 rotation <= 45.0 || rotation >= 315.0
142 }
143}
144
145pub(crate) fn reading_direction(combined: &[f32; 6], font_size: f32) -> (f32, f32) {
181 if font_size < 0.0 {
182 (-combined[0], -combined[1])
183 } else {
184 (combined[0], combined[1])
185 }
186}
187
188pub(crate) fn scaled_run_geometry(
192 combined: &[f32; 6],
193 advance_ts: Option<f32>,
194 fallback_advance_ts: f32,
195 em: f32,
196 glyph_up_flipped: bool,
197 horizontal_scale: f32,
198) -> RunGeometry {
199 let mut reflected = *combined;
200 if horizontal_scale < 0.0 {
201 reflected[0] = -reflected[0];
202 reflected[1] = -reflected[1];
203 }
204 run_geometry(
205 &reflected,
206 advance_ts.map(|advance| advance * horizontal_scale.abs()),
207 fallback_advance_ts * horizontal_scale.abs(),
208 em,
209 glyph_up_flipped,
210 )
211}
212
213pub(crate) fn run_geometry(
215 combined: &[f32; 6],
216 advance_ts: Option<f32>,
217 fallback_advance_ts: f32,
218 em: f32,
219 glyph_up_flipped: bool,
220) -> RunGeometry {
221 let (x0, y0) = (combined[4], combined[5]);
222 let (advance, advance_known) = match advance_ts {
223 Some(advance) => (advance, true),
224 None => (fallback_advance_ts, false),
225 };
226 let reversed = em < 0.0;
232 let em = em.abs();
233 let (ax, ay) = (advance * combined[0], advance * combined[1]);
234 let axis_len = combined[0].hypot(combined[1]);
235 let (ux, uy) = if axis_len > f32::EPSILON {
236 let (px, py) = (-combined[1] / axis_len, combined[0] / axis_len);
243 let y_axis_perp = combined[2] * px + combined[3] * py;
244 let max_scale = axis_len.max(combined[2].hypot(combined[3]));
245 let em_perp = if y_axis_perp.abs() > f32::EPSILON && max_scale > f32::EPSILON {
246 em * y_axis_perp.abs() / max_scale
247 } else {
248 em
249 };
250 let y_axis_side = if y_axis_perp < 0.0 { -1.0 } else { 1.0 };
251 let side = if glyph_up_flipped != reversed {
252 -y_axis_side
253 } else {
254 y_axis_side
255 };
256 (px * em_perp * side, py * em_perp * side)
257 } else {
258 (
259 0.0,
260 if glyph_up_flipped != reversed {
261 -em
262 } else {
263 em
264 },
265 )
266 };
267 let (dir_x, dir_y) = if reversed {
276 (-combined[0], -combined[1])
277 } else {
278 (combined[0], combined[1])
279 };
280 let det = combined[0] * combined[3] - combined[1] * combined[2];
281 let rotation = if det < 0.0 {
282 let up_sign = if glyph_up_flipped != reversed {
283 -1.0
284 } else {
285 1.0
286 };
287 let (up_x, up_y) = (combined[2] * up_sign, combined[3] * up_sign);
288 baseline_rotation(up_y, -up_x)
289 } else {
290 baseline_rotation(dir_x, dir_y)
291 };
292 let xs = [x0, x0 + ax, x0 + ux, x0 + ax + ux];
293 let ys = [y0, y0 + ay, y0 + uy, y0 + ay + uy];
294 let x_min = xs.iter().copied().fold(f32::INFINITY, f32::min);
295 let x_max = xs.iter().copied().fold(f32::NEG_INFINITY, f32::max);
296 let y_min = ys.iter().copied().fold(f32::INFINITY, f32::min);
297 let y_max = ys.iter().copied().fold(f32::NEG_INFINITY, f32::max);
298 RunGeometry {
299 x: x_min,
300 y: y_min,
301 width: x_max - x_min,
302 height: y_max - y_min,
303 rotation,
304 advance_known,
305 }
306}
307
308pub(crate) fn estimated_advance_ts(text: &str, font_size_ts: f32) -> f32 {
315 estimated_advance_for_glyphs(text.chars().count(), font_size_ts)
316}
317
318pub(crate) fn estimated_advance_for_glyphs(glyphs: usize, font_size_ts: f32) -> f32 {
321 glyphs as f32 * 0.5 * font_size_ts
322}
323
324pub(crate) fn baseline_rotation(a: f32, b: f32) -> f32 {
329 if a == 0.0 && b == 0.0 {
330 return 0.0;
331 }
332 let degrees = normalize_degrees(b.atan2(a).to_degrees());
333 let whole = degrees.round();
334 if (degrees - whole).abs() < 0.01 {
335 normalize_degrees(whole)
336 } else {
337 degrees
338 }
339}
340
341pub(crate) fn normalize_degrees(degrees: f32) -> f32 {
343 let wrapped = degrees.rem_euclid(360.0);
344 if wrapped >= 360.0 || wrapped == 0.0 {
345 0.0
346 } else {
347 wrapped
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn unrotate_box_undoes_rotate_box() {
357 for rotation in [PageRotation::Upright, PageRotation::Ccw, PageRotation::Cw] {
358 for (x, y, w, h) in [
359 (188.0, 100.0, 12.0, 36.0),
360 (28.0, 420.0, 12.0, 200.0),
361 (-50.0, -20.0, 30.0, 10.0),
362 ] {
363 let (mut tx, mut ty, mut tw, mut th) = (x, y, w, h);
364 rotation.rotate_box(&mut tx, &mut ty, &mut tw, &mut th);
365 rotation.unrotate_box(&mut tx, &mut ty, &mut tw, &mut th);
366 assert_eq!((tx, ty, tw, th), (x, y, w, h), "{rotation:?}");
367 }
368 }
369 let (mut x, mut y, mut w, mut h) = (620.0, -28.0, -200.0, -12.0);
372 PageRotation::Ccw.unrotate_box(&mut x, &mut y, &mut w, &mut h);
373 assert_eq!((x, y, w, h), (28.0, 420.0, 12.0, 200.0));
374 let (mut x, mut y, mut w, mut h) = (-500.0, 312.0, -200.0, -12.0);
375 PageRotation::Cw.unrotate_box(&mut x, &mut y, &mut w, &mut h);
376 assert_eq!((x, y, w, h), (300.0, 500.0, 12.0, 200.0));
377 }
378
379 #[test]
380 fn page_rotation_turns_boxes_and_points_consistently() {
381 let (mut x, mut y, mut w, mut h) = (188.0, 100.0, 12.0, 36.0);
384 PageRotation::Ccw.rotate_box(&mut x, &mut y, &mut w, &mut h);
385 assert_eq!((x, y, w, h), (100.0, -200.0, 36.0, 12.0));
386 assert_eq!(
387 PageRotation::Ccw.rotate_point(200.0, 100.0),
388 (100.0, -200.0)
389 );
390 let (mut x, mut y, mut w, mut h) = (580.0, 664.0, 10.0, 36.0);
393 PageRotation::Cw.rotate_box(&mut x, &mut y, &mut w, &mut h);
394 assert_eq!((x, y, w, h), (-700.0, 580.0, 36.0, 10.0));
395 assert_eq!(PageRotation::Cw.rotate_point(580.0, 700.0), (-700.0, 580.0));
396 let (mut x, mut y, mut w, mut h) = (1.0, 2.0, 3.0, 4.0);
397 PageRotation::Upright.rotate_box(&mut x, &mut y, &mut w, &mut h);
398 assert_eq!((x, y, w, h), (1.0, 2.0, 3.0, 4.0));
399 assert_eq!(PageRotation::Ccw.baseline_rebase_degrees(), -90.0);
400 assert_eq!(PageRotation::Cw.baseline_rebase_degrees(), 90.0);
401 }
402
403 #[test]
404 fn page_rotation_normalises_negative_extents() {
405 for rotation in [PageRotation::Ccw, PageRotation::Cw] {
409 let (mut x, mut y, mut w, mut h) = (100.0, 200.0, -20.0, -10.0);
410 rotation.rotate_box(&mut x, &mut y, &mut w, &mut h);
411 let (mut nx, mut ny, mut nw, mut nh) = (80.0, 190.0, 20.0, 10.0);
412 rotation.rotate_box(&mut nx, &mut ny, &mut nw, &mut nh);
413 assert_eq!((x, y, w, h), (nx, ny, nw, nh), "{rotation:?}");
414 assert!(w > 0.0 && h > 0.0);
415 }
416 let (mut x, mut y, mut w, mut h) = (100.0, 200.0, -20.0, -10.0);
417 PageRotation::Ccw.rotate_box(&mut x, &mut y, &mut w, &mut h);
418 assert_eq!((x, y, w, h), (190.0, -100.0, 10.0, 20.0));
419 let (mut x, mut y, mut w, mut h) = (100.0, 200.0, -20.0, -10.0);
421 PageRotation::Upright.rotate_box(&mut x, &mut y, &mut w, &mut h);
422 assert_eq!((x, y, w, h), (80.0, 190.0, 20.0, 10.0));
423 }
424
425 #[test]
426 fn run_geometry_without_advance_keeps_em_width_for_vertical_runs() {
427 let vertical = run_geometry(&[0.0, 1.0, -1.0, 0.0, 100.0, 100.0], None, 0.0, 10.0, false);
430 assert_eq!(
431 (vertical.x, vertical.y, vertical.width, vertical.height),
432 (90.0, 100.0, 10.0, 0.0)
433 );
434 assert!(!vertical.advance_known);
435 let upright = run_geometry(&[1.0, 0.0, 0.0, 1.0, 100.0, 100.0], None, 0.0, 10.0, false);
436 assert_eq!(
437 (upright.x, upright.y, upright.width, upright.height),
438 (100.0, 100.0, 0.0, 10.0)
439 );
440 assert!(!upright.advance_known);
441 let zero = run_geometry(
443 &[1.0, 0.0, 0.0, 1.0, 100.0, 100.0],
444 Some(0.0),
445 0.0,
446 10.0,
447 false,
448 );
449 assert!(zero.advance_known);
450 assert_eq!((zero.width, zero.height), (0.0, 10.0));
451 }
452
453 #[test]
454 fn run_geometry_lays_the_fallback_advance_along_the_run() {
455 let upright = run_geometry(&[1.0, 0.0, 0.0, 1.0, 100.0, 100.0], None, 20.0, 10.0, false);
459 assert!(!upright.advance_known);
460 assert_eq!(
461 (upright.x, upright.y, upright.width, upright.height),
462 (100.0, 100.0, 20.0, 10.0)
463 );
464 let flipped = run_geometry(
465 &[-1.0, 0.0, 0.0, 1.0, 100.0, 100.0],
466 None,
467 20.0,
468 10.0,
469 false,
470 );
471 assert_eq!((flipped.x, flipped.width), (80.0, 20.0));
472 assert!(!flipped.advance_known);
473 let down = run_geometry(
474 &[0.0, -1.0, 1.0, 0.0, 100.0, 100.0],
475 None,
476 20.0,
477 10.0,
478 false,
479 );
480 assert_eq!(
481 (down.y, down.height, down.x, down.width),
482 (80.0, 20.0, 100.0, 10.0)
483 );
484 let up = run_geometry(
485 &[0.0, 1.0, -1.0, 0.0, 100.0, 100.0],
486 None,
487 20.0,
488 10.0,
489 false,
490 );
491 assert_eq!((up.y, up.height, up.x, up.width), (100.0, 20.0, 90.0, 10.0));
492 assert_eq!(estimated_advance_ts("abcd", 12.0), 24.0);
493 }
494
495 #[test]
496 fn run_geometry_takes_the_em_height_from_the_y_axis() {
497 let stretched = run_geometry(
500 &[2.0, 0.0, 0.0, 1.0, 0.0, 0.0],
501 Some(10.0),
502 0.0,
503 24.0,
504 false,
505 );
506 assert_eq!(
507 (stretched.width, stretched.height, stretched.rotation),
508 (20.0, 12.0, 0.0)
509 );
510 let tall = run_geometry(
512 &[1.0, 0.0, 0.0, 2.0, 0.0, 0.0],
513 Some(10.0),
514 0.0,
515 24.0,
516 false,
517 );
518 assert_eq!((tall.width, tall.height), (10.0, 24.0));
519 let turned = run_geometry(
521 &[0.0, 2.0, -2.0, 0.0, 0.0, 0.0],
522 Some(10.0),
523 0.0,
524 24.0,
525 false,
526 );
527 assert_eq!((turned.width, turned.height), (24.0, 20.0));
528 }
529
530 #[test]
531 fn run_geometry_picks_the_baseline_side_from_the_matrix_and_font() {
532 let dvips = run_geometry(
537 &[1.0, 0.0, 0.0, -1.0, 100.0, 500.0],
538 Some(30.0),
539 0.0,
540 10.0,
541 true,
542 );
543 assert_eq!(
544 (dvips.x, dvips.y, dvips.width, dvips.height, dvips.rotation),
545 (100.0, 500.0, 30.0, 10.0, 0.0)
546 );
547 let hanging = run_geometry(
550 &[1.0, 0.0, 0.0, -1.0, 100.0, 500.0],
551 Some(30.0),
552 0.0,
553 10.0,
554 false,
555 );
556 assert_eq!((hanging.y, hanging.height), (490.0, 10.0));
557 let mirrored = run_geometry(
561 &[-1.0, 0.0, 0.0, 1.0, 300.0, 500.0],
562 Some(30.0),
563 0.0,
564 10.0,
565 false,
566 );
567 assert_eq!(
568 (
569 mirrored.x,
570 mirrored.y,
571 mirrored.width,
572 mirrored.height,
573 mirrored.rotation
574 ),
575 (270.0, 500.0, 30.0, 10.0, 0.0)
576 );
577 let em = 10.0 * 0.34_f32.hypot(1.0);
581 let sheared = run_geometry(
582 &[1.0, 0.0, 0.34, 1.0, 100.0, 500.0],
583 Some(30.0),
584 0.0,
585 em,
586 false,
587 );
588 assert_eq!((sheared.x, sheared.y, sheared.width), (100.0, 500.0, 30.0));
589 assert!(
590 (sheared.height - 10.0).abs() < 1e-3,
591 "height = {}",
592 sheared.height
593 );
594 let ccw = run_geometry(
597 &[0.0, 1.0, -1.0, 0.0, 100.0, 100.0],
598 Some(30.0),
599 0.0,
600 10.0,
601 false,
602 );
603 assert_eq!((ccw.x, ccw.width), (90.0, 10.0));
604 let cw = run_geometry(
605 &[0.0, -1.0, 1.0, 0.0, 100.0, 100.0],
606 Some(30.0),
607 0.0,
608 10.0,
609 false,
610 );
611 assert_eq!((cw.x, cw.width), (100.0, 10.0));
612 }
613
614 #[test]
615 fn run_geometry_reports_glyph_orientation_for_reflected_matrices() {
616 let mirrored = run_geometry(
620 &[-1.0, 0.0, 0.0, 1.0, 300.0, 500.0],
621 Some(21.6),
622 0.0,
623 12.0,
624 false,
625 );
626 assert_eq!(mirrored.rotation, 0.0);
627 assert!((mirrored.x - 278.4).abs() < 1e-3 && mirrored.y == 500.0);
628 assert!((mirrored.width - 21.6).abs() < 1e-3 && mirrored.height == 12.0);
629
630 let dvips = run_geometry(
633 &[0.12, 0.0, 0.0, -0.12, 100.0, 700.0],
634 Some(300.0),
635 0.0,
636 12.0,
637 true,
638 );
639 assert_eq!(dvips.rotation, 0.0);
640 assert_eq!(dvips.y, 700.0);
641
642 let flipped = run_geometry(
645 &[1.0, 0.0, 0.0, -1.0, 100.0, 700.0],
646 Some(36.0),
647 0.0,
648 12.0,
649 false,
650 );
651 assert_eq!(flipped.rotation, 180.0);
652 assert_eq!((flipped.y, flipped.height), (688.0, 12.0));
653
654 let sideways = run_geometry(
657 &[0.0, 1.0, 1.0, 0.0, 100.0, 100.0],
658 Some(36.0),
659 0.0,
660 12.0,
661 false,
662 );
663 assert_eq!(sideways.rotation, 270.0);
664 }
665
666 #[test]
667 fn run_geometry_reads_the_turn_from_the_font_size_sign_not_the_advance() {
668 let turned = run_geometry(
672 &[1.0, 0.0, 0.0, 1.0, 100.0, 700.0],
673 Some(-36.0),
674 0.0,
675 -12.0,
676 false,
677 );
678 assert_eq!(turned.rotation, 180.0);
679 assert_eq!((turned.x, turned.width), (64.0, 36.0));
680 assert_eq!((turned.y, turned.height), (688.0, 12.0));
681 assert!(turned.advance_known);
682
683 let tight = run_geometry(
687 &[1.0, 0.0, 0.0, 1.0, 100.0, 700.0],
688 Some(-36.0),
689 0.0,
690 12.0,
691 false,
692 );
693 assert_eq!(tight.rotation, 0.0);
694 assert_eq!((tight.x, tight.width), (64.0, 36.0));
695 assert_eq!((tight.y, tight.height), (700.0, 12.0));
696 }
697
698 #[test]
699 fn baseline_rotation_reports_exact_cardinals_and_fractional_skews() {
700 assert_eq!(baseline_rotation(1.0, 0.0), 0.0);
701 assert_eq!(baseline_rotation(12.0, 0.0), 0.0);
702 assert_eq!(baseline_rotation(0.0, 1.0), 90.0);
703 assert_eq!(baseline_rotation(-1.0, 0.0), 180.0);
704 assert_eq!(baseline_rotation(0.0, -1.0), 270.0);
705 assert_eq!(baseline_rotation(0.0, 0.0), 0.0);
706 assert!((baseline_rotation(1.0, 1.0) - 45.0).abs() < 1e-3);
707 let skew = baseline_rotation(1.0, 0.01);
708 assert!(skew > 0.5 && skew < 0.6, "skew = {skew}");
709 assert_eq!(baseline_rotation(1.0, -1e-7), 0.0);
711 assert_eq!(normalize_degrees(-90.0), 270.0);
712 assert_eq!(normalize_degrees(360.0), 0.0);
713 }
714
715 #[test]
716 fn advanced_tm_carries_the_origin_along_the_scaled_baseline() {
717 let tm = [9.5, 0.0, 0.0, 9.5, 68.0, 418.0];
720 let moved = advanced_tm(&tm, 2.973, 1.0);
721 assert!((moved[4] - 96.2435).abs() < 1e-3, "{moved:?}");
722 assert_eq!(&moved[..4], &tm[..4]);
723 assert_eq!(moved[5], 418.0);
724 assert!((advanced_tm(&tm, 2.973, 0.5)[4] - 82.12175).abs() < 1e-3);
726 let turned = advanced_tm(&[0.0, 12.0, -12.0, 0.0, 100.0, 200.0], 2.0, 1.0);
727 assert_eq!((turned[4], turned[5]), (100.0, 224.0));
728 assert_eq!(advanced_tm(&tm, 0.0, 1.0), tm);
729 }
730}