1use crate::model::Error;
15use bytemuck::NoUninit;
16
17use crate::{
18 tables::{
19 glyf::{PointCoord, PointFlags, SimpleGlyph, Transform, PHANTOM_POINT_COUNT},
20 gvar::{DeltaBuffers, GlyphVariationData},
21 },
22 types::{F26Dot6, F2Dot14, Fixed, Point},
23 ReadError,
24};
25
26pub trait Scale: Sized {
32 type Coord: PointCoord + NoUninit;
34 type Delta: PointCoord + NoUninit;
36
37 const NEEDS_UNSCALED: bool;
41
42 fn is_scaled(&self) -> bool;
45
46 fn phantom_from_font_units(&self, value: i32) -> Self::Coord;
51
52 fn coord_to_hinting_raw(&self, value: Self::Coord) -> i32;
60
61 fn scale_phantom(&self, point: Point<Self::Coord>) -> Point<Self::Coord>;
63
64 fn add_phantom_delta(
66 &self,
67 point: Point<Self::Coord>,
68 delta: Point<Self::Delta>,
69 ) -> Point<Self::Coord>;
70
71 fn round_phantom(&self, point: Point<Self::Coord>) -> Point<Self::Coord>;
73
74 fn load_empty_phantom(
83 &self,
84 phantom: &mut [Point<Self::Coord>; PHANTOM_POINT_COUNT],
85 deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
86 );
87
88 fn load_simple_points(
94 &self,
95 pass: SimplePass<'_, '_, Self>,
96 ) -> Result<[Point<Self::Coord>; PHANTOM_POINT_COUNT], Error>;
97
98 fn transform_points(&self, points: &mut [Point<Self::Coord>], transform: &Transform);
106
107 fn component_offset(
116 &self,
117 offset: Point<i32>,
118 transform: &Transform,
119 scale_offset: bool,
120 delta: Option<Point<Self::Delta>>,
121 rounding: OffsetRounding,
122 ) -> Point<Self::Coord>;
123}
124
125#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
132pub struct OffsetRounding {
133 pub x: bool,
134 pub y: bool,
135}
136
137pub struct SimplePass<'a, 'v, S: Scale> {
139 pub glyph: &'a SimpleGlyph<'a>,
141 pub scaled: &'a mut [Point<S::Coord>],
143 pub unscaled: &'a mut [Point<i32>],
145 pub flags: &'a mut [PointFlags],
147 pub contours: &'a [u16],
149 pub phantom: [Point<S::Coord>; PHANTOM_POINT_COUNT],
151 pub deltas: DeltaBuffers<'a, S::Delta>,
153 pub var_data: Option<GlyphVariationData<'v>>,
158 pub varies: bool,
163 pub coords: &'a [F2Dot14],
165 pub gvar_scalars: &'a [Fixed],
167 pub has_hvar: bool,
170 pub is_hinted: bool,
172}
173
174#[derive(Copy, Clone, Debug)]
180pub struct Scale26Dot6 {
181 scale: Fixed,
182 is_scaled: bool,
183}
184
185impl Scale26Dot6 {
186 pub fn new(ppem: Option<f32>, units_per_em: u16) -> Self {
190 if let Some(ppem) = ppem {
191 if units_per_em > 0 {
192 return Self {
193 scale: Fixed::from_bits((ppem * 64.) as i32)
194 / Fixed::from_bits(units_per_em as i32),
195 is_scaled: true,
196 };
197 }
198 }
199 Self {
200 scale: Fixed::from_bits(0x10000),
201 is_scaled: false,
202 }
203 }
204
205 pub fn to_bits(self) -> i32 {
207 self.scale.to_bits()
208 }
209
210 fn mul(&self, value: i32) -> F26Dot6 {
214 F26Dot6::from_bits((Fixed::from_bits(value) * self.scale).to_bits())
215 }
216
217 fn mul_point(&self, value: Point<i32>) -> Point<F26Dot6> {
218 Point::new(self.mul(value.x), self.mul(value.y))
219 }
220
221 fn mul_point_with_delta(
226 &self,
227 font_units: Point<i32>,
228 delta: Point<F26Dot6>,
229 ) -> Point<F26Dot6> {
230 let scaled =
231 self.mul_point((font_units.map(F26Dot6::from_i32) + delta).map(|v| v.to_bits()));
232 scaled.map(|v| F26Dot6::from_bits(v.to_i32()))
233 }
234}
235
236impl Scale for Scale26Dot6 {
237 type Coord = F26Dot6;
238 type Delta = Fixed;
239
240 const NEEDS_UNSCALED: bool = true;
241
242 fn is_scaled(&self) -> bool {
243 self.is_scaled
244 }
245
246 fn phantom_from_font_units(&self, value: i32) -> F26Dot6 {
247 F26Dot6::from_bits(value)
248 }
249
250 fn coord_to_hinting_raw(&self, value: F26Dot6) -> i32 {
251 value.to_bits()
252 }
253
254 fn scale_phantom(&self, point: Point<F26Dot6>) -> Point<F26Dot6> {
255 if self.is_scaled {
256 self.mul_point(point.map(|v| v.to_bits()))
257 } else {
258 point.map(|v| F26Dot6::from_i32(v.to_bits()))
259 }
260 }
261
262 fn add_phantom_delta(&self, point: Point<F26Dot6>, delta: Point<Fixed>) -> Point<F26Dot6> {
263 point + delta.map(Fixed::to_i32).map(F26Dot6::from_bits)
265 }
266
267 fn round_phantom(&self, point: Point<F26Dot6>) -> Point<F26Dot6> {
268 point.map(|v| v.round())
269 }
270
271 fn load_empty_phantom(
272 &self,
273 phantom: &mut [Point<F26Dot6>; PHANTOM_POINT_COUNT],
274 deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
275 ) {
276 let mut font_units = phantom.map(|point| point.map(|v| v.to_bits()));
277 if let Some(deltas) = deltas {
278 font_units[0] += deltas[0].map(Fixed::to_i32);
279 font_units[1] += deltas[1].map(Fixed::to_i32);
280 }
281 for (out, font_units) in phantom.iter_mut().zip(&font_units) {
282 *out = if self.is_scaled {
283 self.mul_point(*font_units)
284 } else {
285 font_units.map(F26Dot6::from_i32)
286 };
287 }
288 }
289
290 fn load_simple_points(
291 &self,
292 pass: SimplePass<'_, '_, Self>,
293 ) -> Result<[Point<F26Dot6>; PHANTOM_POINT_COUNT], Error> {
294 let SimplePass {
295 glyph,
296 scaled,
297 unscaled,
298 flags,
299 contours,
300 phantom,
301 mut deltas,
302 var_data,
303 varies,
304 coords,
305 gvar_scalars,
306 has_hvar,
307 is_hinted,
308 } = pass;
309 let phantom_start = glyph.num_points();
310 let raw_phantom = phantom.map(|point| point.map(|v| v.to_bits()));
312 read_points_and_phantom(glyph, unscaled, flags, &raw_phantom)?;
313 let have_deltas = varies
314 && simple_deltas(
315 var_data,
316 (coords, gvar_scalars),
317 unscaled,
318 flags,
319 contours,
320 &mut deltas,
321 );
322 if self.is_scaled {
323 if have_deltas {
324 for ((point, unscaled), delta) in scaled
325 .iter_mut()
326 .zip(unscaled.iter())
327 .zip(deltas.deltas.iter())
328 {
329 *point = self.mul_point_with_delta(*unscaled, delta.map(Fixed::to_f26dot6));
330 }
331 if has_hvar {
335 for ((point, unscaled), delta) in scaled[phantom_start..]
336 .iter_mut()
337 .zip(&unscaled[phantom_start..])
338 .zip(&deltas.deltas[phantom_start..])
339 {
340 let delta = delta.map(Fixed::to_i32).map(F26Dot6::from_i32);
342 *point = self.mul_point_with_delta(*unscaled, delta);
343 }
344 }
345 if is_hinted {
346 fold_deltas_into_font_units(unscaled, deltas.deltas);
349 }
350 } else {
351 for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) {
352 *point = self.mul_point(*unscaled);
353 }
354 }
355 } else {
356 if have_deltas {
357 fold_deltas_into_font_units(unscaled, deltas.deltas);
358 }
359 for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) {
361 *point = unscaled.map(F26Dot6::from_i32);
362 }
363 }
364 let mut out = [Point::default(); PHANTOM_POINT_COUNT];
365 out.copy_from_slice(&scaled[phantom_start..]);
366 Ok(out)
367 }
368
369 fn transform_points(&self, points: &mut [Point<F26Dot6>], transform: &Transform) {
370 let t = FixedTransform::new(transform);
371 if self.is_scaled {
372 for point in points.iter_mut() {
373 let p = point.map(|c| Fixed::from_bits(c.to_bits()));
374 *point = Point::new(p.x * t.xx + p.y * t.xy, p.x * t.yx + p.y * t.yy)
375 .map(|c| F26Dot6::from_bits(c.to_bits()));
376 }
377 } else {
378 for point in points.iter_mut() {
381 let p = point.map(|c| Fixed::from_bits(c.to_i32()));
382 *point = Point::new(p.x * t.xx + p.y * t.xy, p.x * t.yx + p.y * t.yy)
383 .map(|c| F26Dot6::from_i32(c.to_bits()));
384 }
385 }
386 }
387
388 fn component_offset(
389 &self,
390 offset: Point<i32>,
391 transform: &Transform,
392 scale_offset: bool,
393 delta: Option<Point<Fixed>>,
394 rounding: OffsetRounding,
395 ) -> Point<F26Dot6> {
396 let (mut x, mut y) = (offset.x, offset.y);
397 if scale_offset {
398 let t = FixedTransform::new(transform);
399 x = (Fixed::from_bits(x) * hypot_fixed(t.xx, t.xy)).to_bits();
402 y = (Fixed::from_bits(y) * hypot_fixed(t.yy, t.yx)).to_bits();
403 }
404 if let Some(delta) = delta {
405 x += delta.x.to_i32();
408 y += delta.y.to_i32();
409 }
410 if self.is_scaled {
411 let mut offset = self.mul_point(Point::new(x, y));
412 if rounding.x {
413 offset.x = offset.x.round();
414 }
415 if rounding.y {
416 offset.y = offset.y.round();
417 }
418 offset
419 } else {
420 Point::new(x, y).map(F26Dot6::from_i32)
421 }
422 }
423}
424
425#[derive(Copy, Clone, Debug)]
432pub struct ScaleF32 {
433 scale: f32,
434}
435
436impl ScaleF32 {
437 pub fn new(ppem: Option<f32>, units_per_em: u16) -> Self {
439 let scale = if units_per_em == 0 {
440 1.0
441 } else {
442 ppem.map(|ppem| ppem / units_per_em as f32).unwrap_or(1.0)
443 };
444 Self { scale }
445 }
446}
447
448impl Scale for ScaleF32 {
449 type Coord = f32;
450 type Delta = f32;
451
452 const NEEDS_UNSCALED: bool = false;
453
454 fn is_scaled(&self) -> bool {
455 self.scale != 1.0
456 }
457
458 fn phantom_from_font_units(&self, value: i32) -> f32 {
459 value as f32
460 }
461
462 fn coord_to_hinting_raw(&self, value: f32) -> i32 {
463 value as i32
464 }
465
466 fn scale_phantom(&self, point: Point<f32>) -> Point<f32> {
467 if self.scale != 1.0 {
468 point * self.scale
469 } else {
470 point
471 }
472 }
473
474 fn add_phantom_delta(&self, point: Point<f32>, delta: Point<f32>) -> Point<f32> {
475 point + delta
476 }
477
478 fn round_phantom(&self, point: Point<f32>) -> Point<f32> {
479 point
480 }
481
482 fn load_empty_phantom(
483 &self,
484 phantom: &mut [Point<f32>; PHANTOM_POINT_COUNT],
485 deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
486 ) {
487 let mut font_units = *phantom;
488 if let Some(deltas) = deltas {
490 font_units[0] += deltas[0].map(Fixed::to_f32);
491 font_units[1] += deltas[1].map(Fixed::to_f32);
492 }
493 for (out, font_units) in phantom.iter_mut().zip(&font_units) {
494 *out = *font_units * self.scale;
495 }
496 }
497
498 fn load_simple_points(
499 &self,
500 pass: SimplePass<'_, '_, Self>,
501 ) -> Result<[Point<f32>; PHANTOM_POINT_COUNT], Error> {
502 let SimplePass {
503 glyph,
504 scaled,
505 flags,
506 contours,
507 phantom,
508 mut deltas,
509 var_data,
510 varies,
511 coords,
512 gvar_scalars,
513 ..
514 } = pass;
515 let phantom_start = glyph.num_points();
518 read_points_and_phantom(glyph, scaled, flags, &phantom)?;
519 if varies
520 && simple_deltas(
521 var_data,
522 (coords, gvar_scalars),
523 scaled,
524 flags,
525 contours,
526 &mut deltas,
527 )
528 {
529 for (point, delta) in scaled.iter_mut().zip(deltas.deltas.iter()) {
530 *point += *delta;
531 }
532 }
533 if self.scale != 1.0 {
534 for point in scaled.iter_mut() {
535 *point *= self.scale;
536 }
537 }
538 let mut out = [Point::default(); PHANTOM_POINT_COUNT];
539 out.copy_from_slice(&scaled[phantom_start..]);
540 Ok(out)
541 }
542
543 fn transform_points(&self, points: &mut [Point<f32>], transform: &Transform) {
544 let (xx, yx, xy, yy) = (
545 transform.xx.to_f32(),
546 transform.yx.to_f32(),
547 transform.xy.to_f32(),
548 transform.yy.to_f32(),
549 );
550 for point in points.iter_mut() {
551 *point = Point::new(point.x * xx + point.y * xy, point.x * yx + point.y * yy);
552 }
553 }
554
555 fn component_offset(
556 &self,
557 offset: Point<i32>,
558 transform: &Transform,
559 scale_offset: bool,
560 delta: Option<Point<f32>>,
561 _rounding: OffsetRounding,
562 ) -> Point<f32> {
563 let (mut x, mut y) = (offset.x as f32, offset.y as f32);
564 if scale_offset {
565 fn hypot(a: f32, b: f32) -> f32 {
567 #[cfg(not(feature = "libm"))]
568 {
569 a.hypot(b)
570 }
571 #[cfg(feature = "libm")]
572 {
573 #[allow(unused_imports)]
576 use core_maths::CoreFloat;
577 a.hypot(b)
578 }
579 }
580 x *= hypot(transform.xx.to_f32(), transform.xy.to_f32());
582 y *= hypot(transform.yx.to_f32(), transform.yy.to_f32());
583 }
584 let mut offset = Point::new(x, y);
585 if let Some(delta) = delta {
586 offset += delta;
588 }
589 offset * self.scale
592 }
593}
594
595#[derive(Copy, Clone, Debug, Default)]
609pub struct Unscaled;
610
611impl Scale for Unscaled {
612 type Coord = i32;
613 type Delta = Fixed;
614
615 const NEEDS_UNSCALED: bool = false;
618
619 fn is_scaled(&self) -> bool {
620 false
621 }
622
623 fn phantom_from_font_units(&self, value: i32) -> i32 {
624 value
625 }
626
627 fn coord_to_hinting_raw(&self, value: i32) -> i32 {
628 value
629 }
630
631 fn scale_phantom(&self, point: Point<i32>) -> Point<i32> {
632 point
633 }
634
635 fn add_phantom_delta(&self, point: Point<i32>, delta: Point<Fixed>) -> Point<i32> {
636 point + delta.map(Fixed::to_i32)
637 }
638
639 fn round_phantom(&self, point: Point<i32>) -> Point<i32> {
640 point
641 }
642
643 fn load_empty_phantom(
644 &self,
645 phantom: &mut [Point<i32>; PHANTOM_POINT_COUNT],
646 deltas: Option<[Point<Fixed>; PHANTOM_POINT_COUNT]>,
647 ) {
648 if let Some(deltas) = deltas {
649 phantom[0] += deltas[0].map(Fixed::to_i32);
650 phantom[1] += deltas[1].map(Fixed::to_i32);
651 }
652 }
653
654 fn load_simple_points(
655 &self,
656 pass: SimplePass<'_, '_, Self>,
657 ) -> Result<[Point<i32>; PHANTOM_POINT_COUNT], Error> {
658 let SimplePass {
659 glyph,
660 scaled,
661 flags,
662 contours,
663 phantom,
664 mut deltas,
665 var_data,
666 varies,
667 coords,
668 gvar_scalars,
669 ..
670 } = pass;
671 let phantom_start = glyph.num_points();
673 read_points_and_phantom(glyph, scaled, flags, &phantom)?;
674 let have_deltas = varies
675 && simple_deltas(
676 var_data,
677 (coords, gvar_scalars),
678 scaled,
679 flags,
680 contours,
681 &mut deltas,
682 );
683 if have_deltas {
684 fold_deltas_into_font_units(scaled, deltas.deltas);
685 }
686 let mut out = [Point::default(); PHANTOM_POINT_COUNT];
687 out.copy_from_slice(&scaled[phantom_start..]);
688 Ok(out)
689 }
690
691 fn transform_points(&self, points: &mut [Point<i32>], transform: &Transform) {
692 let t = FixedTransform::new(transform);
693 for point in points.iter_mut() {
696 let p = point.map(Fixed::from_bits);
697 *point =
698 Point::new(p.x * t.xx + p.y * t.xy, p.x * t.yx + p.y * t.yy).map(|c| c.to_bits());
699 }
700 }
701
702 fn component_offset(
703 &self,
704 offset: Point<i32>,
705 transform: &Transform,
706 scale_offset: bool,
707 delta: Option<Point<Fixed>>,
708 _rounding: OffsetRounding,
709 ) -> Point<i32> {
710 let mut offset = offset;
711 if scale_offset {
712 let t = FixedTransform::new(transform);
713 offset = Point::new(
714 (Fixed::from_bits(offset.x) * hypot_fixed(t.xx, t.xy)).to_bits(),
715 (Fixed::from_bits(offset.y) * hypot_fixed(t.yy, t.yx)).to_bits(),
716 );
717 }
718 if let Some(delta) = delta {
719 offset += delta.map(Fixed::to_i32);
720 }
721 offset
722 }
723}
724
725fn read_points_and_phantom<C: PointCoord>(
737 glyph: &SimpleGlyph,
738 dest: &mut [Point<C>],
739 flags: &mut [PointFlags],
740 phantom: &[Point<C>; PHANTOM_POINT_COUNT],
741) -> Result<(), ReadError> {
742 let point_count = glyph.num_points();
743 glyph.read_points_fast(&mut dest[..point_count], &mut flags[..point_count])?;
744 for (i, phantom) in phantom.iter().enumerate() {
745 dest[point_count + i] = *phantom;
746 flags[point_count + i] = Default::default();
747 }
748 Ok(())
749}
750
751fn fold_deltas_into_font_units(font_units: &mut [Point<i32>], deltas: &[Point<Fixed>]) {
754 for (point, delta) in font_units.iter_mut().zip(deltas) {
755 *point += delta.map(Fixed::to_i32);
756 }
757}
758
759struct FixedTransform {
761 xx: Fixed,
762 yx: Fixed,
763 xy: Fixed,
764 yy: Fixed,
765}
766
767impl FixedTransform {
768 fn new(transform: &Transform) -> Self {
769 fn widen(x: F2Dot14) -> Fixed {
770 Fixed::from_bits(x.to_bits() as i32 * 4)
771 }
772 Self {
773 xx: widen(transform.xx),
774 yx: widen(transform.yx),
775 xy: widen(transform.xy),
776 yy: widen(transform.yy),
777 }
778 }
779}
780
781fn hypot_fixed(a: Fixed, b: Fixed) -> Fixed {
789 let a = a.to_bits().abs();
790 let b = b.to_bits().abs();
791 Fixed::from_bits(if a > b {
792 a + ((3 * b) >> 3)
793 } else {
794 b + ((3 * a) >> 3)
795 })
796}
797
798fn simple_deltas<C, D>(
807 var_data: Option<GlyphVariationData<'_>>,
808 (coords, gvar_scalars): (&[F2Dot14], &[Fixed]),
809 points: &[Point<C>],
810 flags: &mut [PointFlags],
811 contours: &[u16],
812 buffers: &mut DeltaBuffers<'_, D>,
813) -> bool
814where
815 C: PointCoord,
816 D: PointCoord + From<C>,
817{
818 match var_data {
819 Some(var_data) => var_data
820 .simple_deltas_with_scalars(coords, gvar_scalars, points, flags, contours, buffers)
821 .is_some(),
822 None => {
823 for delta in buffers.deltas.iter_mut() {
824 *delta = Default::default();
825 }
826 true
827 }
828 }
829}
830
831#[cfg(test)]
832mod tests {
833 use super::super::testing::build;
834 use super::super::{OutlineContext, OutlinePlan, OutlineTables};
835 use super::*;
836 use crate::tables::loca::LocaGlyph;
837 use crate::{tables::glyf::Glyph, types::GlyphId, FontRef};
838 use alloc::{vec, vec::Vec};
839
840 #[test]
842 fn unscaled_simple_glyph_is_font_units() {
843 let scale = Scale26Dot6::new(None, 1000);
844 let built = build(font_test_data::GLYF_COMPONENTS, 1, &scale);
845 let (points, contours) = (built.points, built.contours);
846 assert_eq!(contours, vec![3]);
847 assert_eq!(points.len(), 4);
848 for point in &points {
849 assert_eq!(point.x.to_bits() & 0x3F, 0, "expected whole font units");
850 }
851 }
852
853 fn is_composite(gid: u32) -> bool {
855 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
856 let context = OutlineTables::new(&font).unwrap();
857 matches!(
858 context.glyph(GlyphId::new(gid)).unwrap().into_glyph(),
859 Some(Glyph::Composite(_))
860 )
861 }
862
863 #[test]
867 fn modes_agree_without_a_size() {
868 for gid in [0, 1, 5, 7] {
869 let slack = i32::from(is_composite(gid));
870 let fixed = build(
871 font_test_data::GLYF_COMPONENTS,
872 gid,
873 &Scale26Dot6::new(None, 1000),
874 );
875 let float = build(
876 font_test_data::GLYF_COMPONENTS,
877 gid,
878 &ScaleF32::new(None, 1000),
879 );
880 assert_eq!(fixed.contours, float.contours, "gid {gid}");
881 assert_eq!(fixed.points.len(), float.points.len(), "gid {gid}");
882 for (a, b) in fixed.points.iter().zip(&float.points) {
883 assert!(
884 ((a.x.to_bits() >> 6) - b.x as i32).abs() <= slack,
885 "gid {gid}"
886 );
887 assert!(
888 ((a.y.to_bits() >> 6) - b.y as i32).abs() <= slack,
889 "gid {gid}"
890 );
891 }
892 }
893 }
894
895 #[test]
898 fn modes_agree_on_phantom_points() {
899 for gid in [0, 1, 5, 7] {
900 let fixed = build(
901 font_test_data::GLYF_COMPONENTS,
902 gid,
903 &Scale26Dot6::new(None, 1000),
904 );
905 let float = build(
906 font_test_data::GLYF_COMPONENTS,
907 gid,
908 &ScaleF32::new(None, 1000),
909 );
910 for (a, b) in fixed.phantom.iter().zip(&float.phantom) {
911 assert_eq!(a.x.to_bits() >> 6, b.x as i32, "gid {gid}");
912 assert_eq!(a.y.to_bits() >> 6, b.y as i32, "gid {gid}");
913 }
914 }
915 }
916
917 #[test]
928 fn component_offset_stays_far_inside_i32() {
929 const BOUND: i32 = 1 << 18;
930 let extremes = [i16::MIN, i16::MAX];
931 let mut worst = 0i32;
932 for x in extremes {
933 for y in extremes {
934 for basis in extremes {
935 let transform = Transform {
936 xx: F2Dot14::from_bits(basis),
937 yx: F2Dot14::from_bits(basis),
938 xy: F2Dot14::from_bits(basis),
939 yy: F2Dot14::from_bits(basis),
940 };
941 let offset = Point::new(x as i32, y as i32);
942 let delta = Point::new(Fixed::from_bits(i32::MAX), Fixed::from_bits(i32::MIN));
943 for scale_offset in [false, true] {
944 let raw = Unscaled.component_offset(
945 offset,
946 &transform,
947 scale_offset,
948 Some(delta),
949 OffsetRounding::default(),
950 );
951 worst = worst.max(raw.x.abs()).max(raw.y.abs());
952 let fixed = Scale26Dot6::new(Some(2048.0), 16).component_offset(
954 offset,
955 &transform,
956 scale_offset,
957 Some(delta),
958 OffsetRounding { x: true, y: true },
959 );
960 assert!(fixed.x.to_bits().abs() < i32::MAX / 2);
961 assert!(fixed.y.to_bits().abs() < i32::MAX / 2);
962 let float = ScaleF32::new(Some(2048.0), 16).component_offset(
963 offset,
964 &transform,
965 scale_offset,
966 Some(Point::new(f32::from(i16::MAX), f32::from(i16::MIN))),
967 OffsetRounding::default(),
968 );
969 assert!(float.x.is_finite() && float.y.is_finite());
970 }
971 }
972 }
973 }
974 assert!(worst < BOUND, "worst offset was {worst}");
975 }
976
977 #[test]
980 fn a_scale_knows_whether_it_has_a_size() {
981 assert!(!Scale26Dot6::new(None, 1000).is_scaled());
982 assert!(Scale26Dot6::new(Some(16.0), 1000).is_scaled());
983 assert!(!ScaleF32::new(None, 1000).is_scaled());
984 assert!(ScaleF32::new(Some(16.0), 1000).is_scaled());
985 assert!(!Unscaled.is_scaled());
986 }
987
988 #[test]
991 fn unscaled_output_is_the_glyf_coordinates() {
992 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
993 let context = OutlineTables::new(&font).unwrap();
994 let Some(LocaGlyph::Glyph(Glyph::Simple(simple))) = context.glyph(GlyphId::new(1)) else {
995 panic!("glyph 1 is simple");
996 };
997 let raw: Vec<_> = simple.points().map(|point| (point.x, point.y)).collect();
998 let built = build(font_test_data::GLYF_COMPONENTS, 1, &Unscaled);
999 assert_eq!(built.points.len(), raw.len());
1000 for (point, (x, y)) in built.points.iter().zip(&raw) {
1001 assert_eq!((point.x, point.y), (i32::from(*x), i32::from(*y)));
1002 }
1003 }
1004
1005 #[test]
1008 fn unscaled_needs_no_second_buffer() {
1009 const { assert!(!Unscaled::NEEDS_UNSCALED) };
1010 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1011 let context = OutlineTables::new(&font).unwrap();
1012 let plan = OutlinePlan::new(&context, GlyphId::new(1)).unwrap();
1013 assert_eq!(plan.buffer_lengths::<Unscaled>().unscaled, 0);
1014 }
1015
1016 #[test]
1020 fn scaling_is_proportional() {
1021 for gid in [1, 5] {
1022 let small = build(
1023 font_test_data::GLYF_COMPONENTS,
1024 gid,
1025 &Scale26Dot6::new(Some(16.0), 1000),
1026 )
1027 .points;
1028 let large = build(
1029 font_test_data::GLYF_COMPONENTS,
1030 gid,
1031 &Scale26Dot6::new(Some(32.0), 1000),
1032 )
1033 .points;
1034 let slack = 1 + i32::from(is_composite(gid));
1037 for (s, l) in small.iter().zip(&large) {
1038 assert!(
1039 (l.x.to_bits() - 2 * s.x.to_bits()).abs() <= slack,
1040 "gid {gid}"
1041 );
1042 assert!(
1043 (l.y.to_bits() - 2 * s.y.to_bits()).abs() <= slack,
1044 "gid {gid}"
1045 );
1046 }
1047 }
1048 }
1049}