1mod deltas;
4mod hint;
5mod memory;
6mod outline;
7
8#[cfg(feature = "libm")]
9#[allow(unused_imports)]
10use core_maths::CoreFloat;
11
12pub use hint::{HintError, HintInstance, HintOutline};
13pub use outline::{Outline, ScaledOutline};
14use raw::{FontRef, ReadError};
15
16use super::{DrawError, GlyphHMetrics, Hinting};
17use crate::GLYF_COMPOSITE_RECURSION_LIMIT;
18use memory::{FreeTypeOutlineMemory, HarfBuzzOutlineMemory};
19
20use read_fonts::{
21 tables::{
22 glyf::{
23 Anchor, CompositeGlyph, CompositeGlyphFlags, Glyf, Glyph, PointMarker, SimpleGlyph,
24 },
25 gvar::Gvar,
26 hdmx::Hdmx,
27 loca::Loca,
28 },
29 types::{F26Dot6, F2Dot14, Fixed, GlyphId, Point, Tag},
30 TableProvider,
31};
32
33pub const PHANTOM_POINT_COUNT: usize = 4;
35
36#[derive(Clone)]
38pub struct Outlines<'a> {
39 pub(crate) font: FontRef<'a>,
40 pub(crate) glyph_metrics: GlyphHMetrics<'a>,
41 loca: Loca<'a>,
42 glyf: Glyf<'a>,
43 gvar: Option<Gvar<'a>>,
44 hdmx: Option<Hdmx<'a>>,
45 fpgm: &'a [u8],
46 prep: &'a [u8],
47 cvt_len: u32,
48 max_function_defs: u16,
49 max_instruction_defs: u16,
50 max_twilight_points: u16,
51 max_stack_elements: u16,
52 max_storage: u16,
53 glyph_count: u16,
54 units_per_em: u16,
55 os2_vmetrics: [i16; 2],
56 prefer_interpreter: bool,
57 pub(crate) fractional_size_hinting: bool,
58}
59
60impl<'a> Outlines<'a> {
61 pub fn new(font: &FontRef<'a>) -> Option<Self> {
62 let head = font.head().ok()?;
63 let fractional_size_hinting = !head
66 .flags()
67 .contains(read_fonts::tables::head::Flags::FORCE_INTEGER_PPEM);
68 let loca = font.loca(Some(head.index_to_loc_format() == 1)).ok()?;
69 let glyf = font.glyf().ok()?;
70 let glyph_metrics = GlyphHMetrics::new(font)?;
71 let (
72 glyph_count,
73 max_function_defs,
74 max_instruction_defs,
75 max_twilight_points,
76 max_stack_elements,
77 max_storage,
78 max_instructions,
79 ) = font
80 .maxp()
81 .map(|maxp| {
82 (
83 maxp.num_glyphs(),
84 maxp.max_function_defs().unwrap_or_default(),
85 maxp.max_instruction_defs().unwrap_or_default(),
86 maxp.max_twilight_points()
89 .unwrap_or_default()
90 .saturating_add(4),
91 maxp.max_stack_elements()
94 .unwrap_or_default()
95 .saturating_add(32),
96 maxp.max_storage().unwrap_or_default(),
97 maxp.max_size_of_instructions().unwrap_or_default(),
98 )
99 })
100 .unwrap_or_default();
101 let os2_vmetrics = font
102 .os2()
103 .map(|os2| [os2.s_typo_ascender(), os2.s_typo_descender()])
104 .unwrap_or_default();
105 let fpgm = font
106 .data_for_tag(Tag::new(b"fpgm"))
107 .unwrap_or_default()
108 .as_bytes();
109 let prep = font
110 .data_for_tag(Tag::new(b"prep"))
111 .unwrap_or_default()
112 .as_bytes();
113 let prefer_interpreter = !(max_instructions == 0 && fpgm.is_empty() && prep.is_empty());
116 let cvt_len = font.cvt().map(|cvt| cvt.len() as u32).unwrap_or_default();
117 Some(Self {
118 font: font.clone(),
119 glyph_metrics,
120 loca,
121 glyf,
122 gvar: font.gvar().ok(),
123 hdmx: font.hdmx().ok(),
124 fpgm,
125 prep,
126 cvt_len,
127 max_function_defs,
128 max_instruction_defs,
129 max_twilight_points,
130 max_stack_elements,
131 max_storage,
132 glyph_count,
133 units_per_em: font.head().ok()?.units_per_em(),
134 os2_vmetrics,
135 prefer_interpreter,
136 fractional_size_hinting,
137 })
138 }
139
140 pub fn units_per_em(&self) -> u16 {
141 self.units_per_em
142 }
143
144 pub fn glyph_count(&self) -> usize {
145 self.glyph_count as usize
146 }
147
148 pub fn prefer_interpreter(&self) -> bool {
149 self.prefer_interpreter
150 }
151
152 pub fn outline(&self, glyph_id: GlyphId) -> Result<Outline<'a>, DrawError> {
153 let mut outline = Outline {
154 glyph_id,
155 has_variations: self.gvar.is_some(),
156 ..Default::default()
157 };
158 let glyph = self.loca.get_glyf(glyph_id, &self.glyf)?;
159 if let Some(glyph) = glyph.as_ref() {
160 self.outline_rec(glyph, &mut outline, 0, 0)?;
161 }
162 outline.points += PHANTOM_POINT_COUNT;
163 outline.max_stack = self.max_stack_elements as usize;
164 outline.cvt_count = self.cvt_len as usize;
165 outline.storage_count = self.max_storage as usize;
166 outline.max_twilight_points = self.max_twilight_points as usize;
167 outline.glyph = glyph;
168 Ok(outline)
169 }
170
171 pub fn compute_scale(&self, ppem: Option<f32>) -> Scale26Dot6 {
172 Scale26Dot6::new(ppem, self.units_per_em)
173 }
174
175 pub fn compute_hinted_scale(&self, ppem: Option<f32>) -> Scale26Dot6 {
176 if let Some(ppem) = ppem {
177 if !self.fractional_size_hinting {
178 return self.compute_scale(Some(F26Dot6::from_f64(ppem as f64).round().to_f32()));
183 }
184 }
185 self.compute_scale(ppem)
186 }
187}
188
189impl Outlines<'_> {
190 fn outline_rec(
191 &self,
192 glyph: &Glyph,
193 outline: &mut Outline,
194 component_depth: usize,
195 recurse_depth: usize,
196 ) -> Result<(), DrawError> {
197 if recurse_depth > GLYF_COMPOSITE_RECURSION_LIMIT {
198 return Err(DrawError::RecursionLimitExceeded(outline.glyph_id));
199 }
200 match glyph {
201 Glyph::Simple(simple) => {
202 let num_points = simple.num_points();
203 let num_points_with_phantom = num_points + PHANTOM_POINT_COUNT;
204 outline.max_simple_points = outline.max_simple_points.max(num_points_with_phantom);
205 outline.points += num_points;
206 outline.contours += simple.end_pts_of_contours().len();
207 outline.has_hinting = outline.has_hinting || simple.instruction_length() != 0;
208 outline.max_other_points = outline.max_other_points.max(num_points_with_phantom);
209 outline.has_overlaps |= simple.has_overlapping_contours();
210 }
211 Glyph::Composite(composite) => {
212 let (mut count, instructions) = composite.count_and_instructions();
213 count += PHANTOM_POINT_COUNT;
214 let point_base = outline.points;
215 for (component, flags) in composite.component_glyphs_and_flags() {
216 outline.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND);
217 let component_glyph = self.loca.get_glyf(component.into(), &self.glyf)?;
218 let Some(component_glyph) = component_glyph else {
219 continue;
220 };
221 self.outline_rec(
222 &component_glyph,
223 outline,
224 component_depth + count,
225 recurse_depth + 1,
226 )?;
227 }
228 let has_hinting = !instructions.unwrap_or_default().is_empty();
229 if has_hinting {
230 let num_points_in_composite = outline.points - point_base + PHANTOM_POINT_COUNT;
233 outline.max_other_points =
234 outline.max_other_points.max(num_points_in_composite);
235 }
236 outline.max_component_delta_stack = outline
237 .max_component_delta_stack
238 .max(component_depth + count);
239 outline.has_hinting = outline.has_hinting || has_hinting;
240 }
241 }
242 Ok(())
243 }
244
245 fn hdmx_width(&self, ppem: f32, glyph_id: GlyphId) -> Option<u8> {
246 let hdmx = self.hdmx.as_ref()?;
247 let ppem_u8 = ppem as u8;
248 if ppem_u8 as f32 == ppem {
250 hdmx.record_for_size(ppem_u8)?
252 .widths
253 .get(glyph_id.to_u32() as usize)
254 .copied()
255 } else {
256 None
257 }
258 }
259}
260
261trait Scaler {
262 fn outlines(&self) -> &Outlines<'_>;
263 fn setup_phantom_points(
264 &mut self,
265 bounds: [i16; 4],
266 lsb: i32,
267 advance: i32,
268 tsb: i32,
269 vadvance: i32,
270 );
271 fn load_empty(&mut self, glyph_id: GlyphId) -> Result<(), DrawError>;
272 fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), DrawError>;
273 fn load_composite(
274 &mut self,
275 glyph: &CompositeGlyph,
276 glyph_id: GlyphId,
277 recurse_depth: usize,
278 ) -> Result<(), DrawError>;
279
280 fn load(
281 &mut self,
282 glyph: &Option<Glyph>,
283 glyph_id: GlyphId,
284 recurse_depth: usize,
285 ) -> Result<(), DrawError> {
286 if recurse_depth > GLYF_COMPOSITE_RECURSION_LIMIT {
287 return Err(DrawError::RecursionLimitExceeded(glyph_id));
288 }
289 let bounds = match &glyph {
290 Some(glyph) => [glyph.x_min(), glyph.x_max(), glyph.y_min(), glyph.y_max()],
291 _ => [0; 4],
292 };
293 let outlines = self.outlines();
294 let lsb = outlines.glyph_metrics.lsb(glyph_id, &[]);
295 let advance = outlines.glyph_metrics.advance_width(glyph_id, &[]);
296 let [ascent, descent] = outlines.os2_vmetrics.map(|x| x as i32);
297 let tsb = ascent - bounds[3] as i32;
298 let vadvance = ascent - descent;
299 self.setup_phantom_points(bounds, lsb, advance, tsb, vadvance);
300 match glyph {
301 Some(Glyph::Simple(simple)) => self.load_simple(simple, glyph_id),
302 Some(Glyph::Composite(composite)) => {
303 self.load_composite(composite, glyph_id, recurse_depth)
304 }
305 None => self.load_empty(glyph_id),
306 }
307 }
308}
309
310pub(crate) struct HarfBuzzScaler<'a> {
312 outlines: &'a Outlines<'a>,
313 memory: HarfBuzzOutlineMemory<'a>,
314 coords: &'a [F2Dot14],
315 point_count: usize,
316 contour_count: usize,
317 component_delta_count: usize,
318 ppem: f32,
319 scale: f32,
320 phantom: [Point<f32>; PHANTOM_POINT_COUNT],
326}
327
328impl<'a> HarfBuzzScaler<'a> {
329 pub(crate) fn unhinted(
330 outlines: &'a Outlines<'a>,
331 outline: &'a Outline,
332 buf: &'a mut [u8],
333 ppem: Option<f32>,
334 coords: &'a [F2Dot14],
335 ) -> Result<Self, DrawError> {
336 outline.ensure_point_count_limit()?;
337 let scale = if outlines.units_per_em == 0 {
338 1.0
339 } else {
340 ppem.map(|ppem| ppem / outlines.units_per_em as f32)
341 .unwrap_or(1.0)
342 };
343 let memory =
344 HarfBuzzOutlineMemory::new(outline, buf).ok_or(DrawError::InsufficientMemory)?;
345 Ok(Self {
346 outlines,
347 memory,
348 coords,
349 point_count: 0,
350 contour_count: 0,
351 component_delta_count: 0,
352 ppem: ppem.unwrap_or_default(),
353 scale,
354 phantom: Default::default(),
355 })
356 }
357
358 pub(crate) fn scale(
359 mut self,
360 glyph: &Option<Glyph>,
361 glyph_id: GlyphId,
362 ) -> Result<ScaledOutline<'a, f32>, DrawError> {
363 self.load(glyph, glyph_id, 0)?;
364 Ok(ScaledOutline::new(
365 &mut self.memory.points[..self.point_count],
366 self.phantom,
367 &mut self.memory.flags[..self.point_count],
368 &mut self.memory.contours[..self.contour_count],
369 self.outlines.hdmx_width(self.ppem, glyph_id),
370 ))
371 }
372}
373
374#[derive(Copy, Clone)]
376pub(crate) struct Scale26Dot6 {
377 scale: Fixed,
378 is_scaled: bool,
380}
381
382impl Scale26Dot6 {
383 fn new(ppem: Option<f32>, units_per_em: u16) -> Self {
384 if let Some(ppem) = ppem {
385 if units_per_em > 0 {
386 return Self {
387 scale: Fixed::from_bits((ppem * 64.) as i32)
388 / Fixed::from_bits(units_per_em as i32),
389 is_scaled: true,
390 };
391 }
392 }
393 Self {
394 scale: Fixed::from_bits(0x10000),
395 is_scaled: false,
396 }
397 }
398
399 fn apply(&self, value: i32) -> F26Dot6 {
400 F26Dot6::from_bits((Fixed::from_bits(value) * self.scale).to_bits())
401 }
402
403 fn apply_point(&self, value: Point<i32>) -> Point<F26Dot6> {
404 Point::new(self.apply(value.x), self.apply(value.y))
405 }
406
407 fn mul(&self, value: F26Dot6) -> F26Dot6 {
408 F26Dot6::from_bits((Fixed::from_bits(value.to_bits()) * self.scale).to_bits())
409 }
410
411 fn mul_point(&self, value: Point<F26Dot6>) -> Point<F26Dot6> {
412 Point::new(self.mul(value.x), self.mul(value.y))
413 }
414
415 pub(crate) fn to_bits(self) -> i32 {
416 self.scale.to_bits()
417 }
418}
419
420pub(crate) struct FreeTypeScaler<'a> {
422 outlines: &'a Outlines<'a>,
423 memory: FreeTypeOutlineMemory<'a>,
424 coords: &'a [F2Dot14],
425 point_count: usize,
426 contour_count: usize,
427 component_delta_count: usize,
428 ppem: f32,
429 scale: Scale26Dot6,
430 is_hinted: bool,
431 pedantic_hinting: bool,
432 phantom: [Point<F26Dot6>; PHANTOM_POINT_COUNT],
438 hinter: Option<&'a HintInstance>,
439}
440
441impl<'a> FreeTypeScaler<'a> {
442 pub(crate) fn unhinted(
443 outlines: &'a Outlines<'a>,
444 outline: &'a Outline,
445 buf: &'a mut [u8],
446 ppem: Option<f32>,
447 coords: &'a [F2Dot14],
448 ) -> Result<Self, DrawError> {
449 outline.ensure_point_count_limit()?;
450 let scale = outlines.compute_scale(ppem);
451 let memory = FreeTypeOutlineMemory::new(outline, buf, Hinting::None)
452 .ok_or(DrawError::InsufficientMemory)?;
453 Ok(Self {
454 outlines,
455 memory,
456 coords,
457 point_count: 0,
458 contour_count: 0,
459 component_delta_count: 0,
460 ppem: ppem.unwrap_or_default(),
461 scale,
462 is_hinted: false,
463 pedantic_hinting: false,
464 phantom: Default::default(),
465 hinter: None,
466 })
467 }
468
469 pub(crate) fn hinted(
470 outlines: &'a Outlines<'a>,
471 outline: &'a Outline,
472 buf: &'a mut [u8],
473 ppem: Option<f32>,
474 coords: &'a [F2Dot14],
475 hinter: &'a HintInstance,
476 pedantic_hinting: bool,
477 ) -> Result<Self, DrawError> {
478 outline.ensure_point_count_limit()?;
479 let scale = outlines.compute_hinted_scale(ppem);
480 let memory = FreeTypeOutlineMemory::new(outline, buf, Hinting::Embedded)
481 .ok_or(DrawError::InsufficientMemory)?;
482 Ok(Self {
483 outlines,
484 memory,
485 coords,
486 point_count: 0,
487 contour_count: 0,
488 component_delta_count: 0,
489 ppem: ppem.unwrap_or_default(),
490 scale,
491 is_hinted: scale.is_scaled,
493 pedantic_hinting,
494 phantom: Default::default(),
495 hinter: Some(hinter),
496 })
497 }
498
499 pub(crate) fn scale(
500 mut self,
501 glyph: &Option<Glyph>,
502 glyph_id: GlyphId,
503 ) -> Result<ScaledOutline<'a, F26Dot6>, DrawError> {
504 self.load(glyph, glyph_id, 0)?;
505 let hdmx_width = if self.is_hinted
509 && self
510 .hinter
511 .as_ref()
512 .map(|hinter| !hinter.backward_compatibility())
513 .unwrap_or(true)
514 {
515 self.outlines.hdmx_width(self.ppem, glyph_id)
516 } else {
517 None
518 };
519 Ok(ScaledOutline::new(
520 &mut self.memory.scaled[..self.point_count],
521 self.phantom,
522 &mut self.memory.flags[..self.point_count],
523 &mut self.memory.contours[..self.contour_count],
524 hdmx_width,
525 ))
526 }
527}
528
529impl Scaler for FreeTypeScaler<'_> {
530 fn setup_phantom_points(
531 &mut self,
532 bounds: [i16; 4],
533 lsb: i32,
534 advance: i32,
535 tsb: i32,
536 vadvance: i32,
537 ) {
538 self.phantom[0].x = F26Dot6::from_bits(bounds[0] as i32 - lsb);
542 self.phantom[0].y = F26Dot6::ZERO;
543 self.phantom[1].x = self.phantom[0].x + F26Dot6::from_bits(advance);
544 self.phantom[1].y = F26Dot6::ZERO;
545 self.phantom[2].x = F26Dot6::ZERO;
547 self.phantom[2].y = F26Dot6::from_bits(bounds[3] as i32 + tsb);
548 self.phantom[3].x = F26Dot6::ZERO;
549 self.phantom[3].y = self.phantom[2].y - F26Dot6::from_bits(vadvance);
550 }
551
552 fn outlines(&self) -> &Outlines<'_> {
553 self.outlines
554 }
555
556 fn load_empty(&mut self, glyph_id: GlyphId) -> Result<(), DrawError> {
557 let scale = self.scale;
560 let mut unscaled = self.phantom.map(|point| point.map(|x| x.to_bits()));
561 if self.outlines.gvar.is_some() && !self.coords.is_empty() {
562 if let Ok(Some(deltas)) = self.outlines.gvar.as_ref().unwrap().phantom_point_deltas(
563 &self.outlines.glyf,
564 &self.outlines.loca,
565 self.coords,
566 glyph_id,
567 ) {
568 unscaled[0] += deltas[0].map(Fixed::to_i32);
569 unscaled[1] += deltas[1].map(Fixed::to_i32);
570 }
571 }
572 if self.scale.is_scaled {
573 for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
574 *phantom = scale.apply_point(*unscaled);
575 }
576 } else {
577 for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
578 *phantom = unscaled.map(F26Dot6::from_i32);
579 }
580 }
581 Ok(())
582 }
583
584 fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), DrawError> {
585 use DrawError::InsufficientMemory;
586 let points_start = self.point_count;
588 let point_count = glyph.num_points();
589 let phantom_start = point_count;
590 let points_end = points_start + point_count + PHANTOM_POINT_COUNT;
591 let point_range = points_start..points_end;
592 let other_points_end = point_count + PHANTOM_POINT_COUNT;
593 let scaled = self
595 .memory
596 .scaled
597 .get_mut(point_range.clone())
598 .ok_or(InsufficientMemory)?;
599 let flags = self
600 .memory
601 .flags
602 .get_mut(point_range)
603 .ok_or(InsufficientMemory)?;
604 let unscaled = self
608 .memory
609 .unscaled
610 .get_mut(..other_points_end)
611 .ok_or(InsufficientMemory)?;
612 glyph.read_points_fast(&mut unscaled[..point_count], &mut flags[..point_count])?;
615 let contours_start = self.contour_count;
617 let contour_end_pts = glyph.end_pts_of_contours();
618 let contour_count = contour_end_pts.len();
619 let contours_end = contours_start + contour_count;
620 let contours = self
621 .memory
622 .contours
623 .get_mut(contours_start..contours_end)
624 .ok_or(InsufficientMemory)?;
625 let mut last_end_pt = 0;
628 for (end_pt, contour) in contour_end_pts.iter().zip(contours.iter_mut()) {
629 let end_pt = end_pt.get();
630 if end_pt < last_end_pt {
631 return Err(ReadError::MalformedData(
632 "unordered contour end points in TrueType glyph",
633 )
634 .into());
635 }
636 last_end_pt = end_pt;
637 *contour = end_pt;
638 }
639 self.point_count += point_count;
641 self.contour_count += contour_count;
642 for (i, phantom) in self.phantom.iter().enumerate() {
644 unscaled[phantom_start + i] = phantom.map(|x| x.to_bits());
645 flags[phantom_start + i] = Default::default();
646 }
647 let mut have_deltas = false;
648 if self.outlines.gvar.is_some() && !self.coords.is_empty() {
649 let gvar = self.outlines.gvar.as_ref().unwrap();
650 let glyph = deltas::SimpleGlyph {
651 points: &mut unscaled[..],
652 flags: &mut flags[..],
653 contours,
654 };
655 let deltas = self
656 .memory
657 .deltas
658 .get_mut(..point_count + PHANTOM_POINT_COUNT)
659 .ok_or(InsufficientMemory)?;
660 let iup_buffer = self
661 .memory
662 .iup_buffer
663 .get_mut(..point_count + PHANTOM_POINT_COUNT)
664 .ok_or(InsufficientMemory)?;
665 if deltas::simple_glyph(gvar, glyph_id, self.coords, glyph, iup_buffer, deltas).is_ok()
666 {
667 have_deltas = true;
668 }
669 }
670 let ins = glyph.instructions();
671 let is_hinted = self.is_hinted;
672 if self.scale.is_scaled {
673 let scale = self.scale;
674 if have_deltas {
675 for ((point, unscaled), delta) in scaled
676 .iter_mut()
677 .zip(unscaled.iter())
678 .zip(self.memory.deltas.iter())
679 {
680 let delta = delta.map(Fixed::to_f26dot6);
681 let scaled = scale.mul_point(unscaled.map(F26Dot6::from_i32) + delta);
682 *point = scaled.map(|v| F26Dot6::from_bits(v.to_i32()));
685 }
686 if self.outlines.glyph_metrics.hvar.is_some() {
690 for ((point, unscaled), delta) in scaled[phantom_start..]
691 .iter_mut()
692 .zip(&unscaled[phantom_start..])
693 .zip(&self.memory.deltas[phantom_start..])
694 {
695 let delta = delta.map(Fixed::to_i32).map(F26Dot6::from_i32);
696 let scaled = scale.mul_point(unscaled.map(F26Dot6::from_i32) + delta);
697 *point = scaled.map(|v| F26Dot6::from_bits(v.to_i32()));
698 }
699 }
700 if is_hinted {
701 for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) {
704 *unscaled += delta.map(Fixed::to_i32);
705 }
706 }
707 } else {
708 for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter_mut()) {
709 *point = scale.apply_point(*unscaled);
710 }
711 }
712 } else {
713 if have_deltas {
714 for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) {
716 *unscaled += delta.map(Fixed::to_i32);
717 }
718 }
719 for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) {
721 *point = unscaled.map(F26Dot6::from_i32);
722 }
723 }
724 self.phantom.copy_from_slice(&scaled[phantom_start..]);
726 if let (Some(hinter), true) = (self.hinter.as_ref(), is_hinted) {
727 if !ins.is_empty() {
728 let original_scaled = self
730 .memory
731 .original_scaled
732 .get_mut(..other_points_end)
733 .ok_or(InsufficientMemory)?;
734 original_scaled.copy_from_slice(scaled);
735 for point in &mut scaled[phantom_start..] {
737 point.x = point.x.round();
738 point.y = point.y.round();
739 }
740 let mut input = HintOutline {
741 glyph_id,
742 unscaled,
743 scaled,
744 original_scaled,
745 flags,
746 contours,
747 bytecode: ins,
748 phantom: &mut self.phantom,
749 stack: self.memory.stack,
750 cvt: self.memory.cvt,
751 storage: self.memory.storage,
752 twilight_scaled: self.memory.twilight_scaled,
753 twilight_original_scaled: self.memory.twilight_original_scaled,
754 twilight_flags: self.memory.twilight_flags,
755 is_composite: false,
756 coords: self.coords,
757 };
758 let hint_res = hinter.hint(self.outlines, &mut input, self.pedantic_hinting);
759 if let (Err(e), true) = (hint_res, self.pedantic_hinting) {
760 return Err(e)?;
761 }
762 } else if !hinter.backward_compatibility() {
763 for (scaled, phantom) in scaled[phantom_start..].iter().zip(&mut self.phantom) {
771 *phantom = scaled.map(|x| x.round());
772 }
773 }
774 }
775 if points_start != 0 {
776 for contour_end in contours.iter_mut() {
778 *contour_end += points_start as u16;
779 }
780 }
781 Ok(())
782 }
783
784 fn load_composite(
785 &mut self,
786 glyph: &CompositeGlyph,
787 glyph_id: GlyphId,
788 recurse_depth: usize,
789 ) -> Result<(), DrawError> {
790 use DrawError::InsufficientMemory;
791 let scale = self.scale;
792 let point_base = self.point_count;
794 let contour_base = self.contour_count;
795 let mut have_deltas = false;
798 let delta_base = self.component_delta_count;
799 if self.outlines.gvar.is_some() && !self.coords.is_empty() {
800 let gvar = self.outlines.gvar.as_ref().unwrap();
801 let count = glyph.components().count() + PHANTOM_POINT_COUNT;
802 let deltas = self
803 .memory
804 .composite_deltas
805 .get_mut(delta_base..delta_base + count)
806 .ok_or(InsufficientMemory)?;
807 if deltas::composite_glyph(gvar, glyph_id, self.coords, &mut deltas[..]).is_ok() {
808 for (phantom, delta) in self
810 .phantom
811 .iter_mut()
812 .zip(&deltas[deltas.len() - PHANTOM_POINT_COUNT..])
813 {
814 *phantom += delta.map(Fixed::to_i32).map(F26Dot6::from_bits);
815 }
816 have_deltas = true;
817 }
818 self.component_delta_count += count;
819 }
820 if self.scale.is_scaled {
821 for point in self.phantom.iter_mut() {
822 *point = scale.mul_point(*point);
823 }
824 } else {
825 for point in self.phantom.iter_mut() {
826 *point = point.map(|x| F26Dot6::from_i32(x.to_bits()));
827 }
828 }
829 for (i, component) in glyph.components().enumerate() {
830 let phantom = self.phantom;
833 let start_point = self.point_count;
835 let component_glyph = self
836 .outlines
837 .loca
838 .get_glyf(component.glyph.into(), &self.outlines.glyf)?;
839 self.load(&component_glyph, component.glyph.into(), recurse_depth + 1)?;
840 let end_point = self.point_count;
841 if !component
842 .flags
843 .contains(CompositeGlyphFlags::USE_MY_METRICS)
844 {
845 self.phantom = phantom;
848 }
849 fn scale_component(x: F2Dot14) -> Fixed {
851 Fixed::from_bits(x.to_bits() as i32 * 4)
852 }
853 let xform = &component.transform;
854 let xx = scale_component(xform.xx);
855 let yx = scale_component(xform.yx);
856 let xy = scale_component(xform.xy);
857 let yy = scale_component(xform.yy);
858 let have_xform = component.flags.intersects(
859 CompositeGlyphFlags::WE_HAVE_A_SCALE
860 | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE
861 | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO,
862 );
863 if have_xform {
864 let scaled = &mut self.memory.scaled[start_point..end_point];
865 if self.scale.is_scaled {
866 for point in scaled {
867 let p = point.map(|c| Fixed::from_bits(c.to_bits()));
868 let x = p.x * xx + p.y * xy;
869 let y = p.x * yx + p.y * yy;
870 let [x, y] = [x, y].map(|c| F26Dot6::from_bits(c.to_bits()));
871 point.x = x;
872 point.y = y;
873 }
874 } else {
875 for point in scaled {
876 let unscaled = point.map(|c| Fixed::from_bits(c.to_i32()));
879 let x = unscaled.x * xx + unscaled.y * xy;
880 let y = unscaled.x * yx + unscaled.y * yy;
881 *point = Point::new(x, y).map(|c| F26Dot6::from_i32(c.to_bits()));
882 }
883 }
884 }
885 let anchor_offset = match component.anchor {
886 Anchor::Offset { x, y } => {
887 let (mut x, mut y) = (x as i32, y as i32);
888 if have_xform
889 && component.flags
890 & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
891 | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
892 == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
893 {
894 fn hypot(a: Fixed, b: Fixed) -> Fixed {
898 let a = a.to_bits().abs();
899 let b = b.to_bits().abs();
900 Fixed::from_bits(if a > b {
901 a + ((3 * b) >> 3)
902 } else {
903 b + ((3 * a) >> 3)
904 })
905 }
906 x = (Fixed::from_bits(x) * hypot(xx, xy)).to_bits();
908 y = (Fixed::from_bits(y) * hypot(yy, yx)).to_bits();
909 }
910 if have_deltas {
911 let delta = self
912 .memory
913 .composite_deltas
914 .get(delta_base + i)
915 .copied()
916 .unwrap_or_default();
917 x += delta.x.to_i32();
920 y += delta.y.to_i32();
921 }
922 if scale.is_scaled {
923 let mut offset = scale.apply_point(Point::new(x, y));
924 if self.is_hinted
925 && component
926 .flags
927 .contains(CompositeGlyphFlags::ROUND_XY_TO_GRID)
928 {
929 offset.y = offset.y.round();
931 }
932 offset
933 } else {
934 Point::new(x, y).map(F26Dot6::from_i32)
935 }
936 }
937 Anchor::Point { base, component } => {
938 let (base_offset, component_offset) = (base as usize, component as usize);
939 let base_point = self
940 .memory
941 .scaled
942 .get(point_base + base_offset)
943 .ok_or(DrawError::InvalidAnchorPoint(glyph_id, base))?;
944 let component_point = self
945 .memory
946 .scaled
947 .get(start_point + component_offset)
948 .ok_or(DrawError::InvalidAnchorPoint(glyph_id, component))?;
949 *base_point - *component_point
950 }
951 };
952 if anchor_offset.x != F26Dot6::ZERO || anchor_offset.y != F26Dot6::ZERO {
953 for point in &mut self.memory.scaled[start_point..end_point] {
954 *point += anchor_offset;
955 }
956 }
957 }
958 if have_deltas {
959 self.component_delta_count = delta_base;
960 }
961 if let (Some(hinter), true) = (self.hinter.as_ref(), self.is_hinted) {
962 let ins = glyph.instructions().unwrap_or_default();
963 if !ins.is_empty() {
964 let start_point = point_base;
967 let end_point = self.point_count + PHANTOM_POINT_COUNT;
968 let point_range = start_point..end_point;
969 let phantom_start = point_range.len() - PHANTOM_POINT_COUNT;
970 let scaled = &mut self.memory.scaled[point_range.clone()];
971 let flags = self
972 .memory
973 .flags
974 .get_mut(point_range.clone())
975 .ok_or(InsufficientMemory)?;
976 for (i, phantom) in self.phantom.iter().enumerate() {
978 scaled[phantom_start + i] = *phantom;
979 flags[phantom_start + i] = Default::default();
980 }
981 let other_points_end = point_range.len();
982 let unscaled = self
983 .memory
984 .unscaled
985 .get_mut(..other_points_end)
986 .ok_or(InsufficientMemory)?;
987 for (scaled, unscaled) in scaled.iter().zip(unscaled.iter_mut()) {
988 *unscaled = scaled.map(|x| x.to_bits());
989 }
990 let original_scaled = self
991 .memory
992 .original_scaled
993 .get_mut(..other_points_end)
994 .ok_or(InsufficientMemory)?;
995 original_scaled.copy_from_slice(scaled);
996 let contours = self
997 .memory
998 .contours
999 .get_mut(contour_base..self.contour_count)
1000 .ok_or(InsufficientMemory)?;
1001 for p in &mut scaled[phantom_start..] {
1003 p.x = p.x.round();
1004 p.y = p.y.round();
1005 }
1006 for flag in flags.iter_mut() {
1008 flag.clear_marker(PointMarker::TOUCHED);
1009 }
1010 if point_base != 0 {
1013 let delta = point_base as u16;
1014 for contour in contours.iter_mut() {
1015 *contour -= delta;
1016 }
1017 }
1018 let mut input = HintOutline {
1019 glyph_id,
1020 unscaled,
1021 scaled,
1022 original_scaled,
1023 flags,
1024 contours,
1025 bytecode: ins,
1026 phantom: &mut self.phantom,
1027 stack: self.memory.stack,
1028 cvt: self.memory.cvt,
1029 storage: self.memory.storage,
1030 twilight_scaled: self.memory.twilight_scaled,
1031 twilight_original_scaled: self.memory.twilight_original_scaled,
1032 twilight_flags: self.memory.twilight_flags,
1033 is_composite: true,
1034 coords: self.coords,
1035 };
1036 let hint_res = hinter.hint(self.outlines, &mut input, self.pedantic_hinting);
1037 if let (Err(e), true) = (hint_res, self.pedantic_hinting) {
1038 return Err(e)?;
1039 }
1040 if point_base != 0 {
1042 let delta = point_base as u16;
1043 for contour in contours.iter_mut() {
1044 *contour += delta;
1045 }
1046 }
1047 }
1048 }
1049 Ok(())
1050 }
1051}
1052
1053impl Scaler for HarfBuzzScaler<'_> {
1054 fn setup_phantom_points(
1055 &mut self,
1056 bounds: [i16; 4],
1057 lsb: i32,
1058 advance: i32,
1059 tsb: i32,
1060 vadvance: i32,
1061 ) {
1062 self.phantom[0].x = bounds[0] as f32 - lsb as f32;
1065 self.phantom[0].y = 0.0;
1066 self.phantom[1].x = self.phantom[0].x + advance as f32;
1067 self.phantom[1].y = 0.0;
1068 self.phantom[2].x = 0.0;
1070 self.phantom[2].y = bounds[3] as f32 + tsb as f32;
1071 self.phantom[3].x = 0.0;
1072 self.phantom[3].y = self.phantom[2].y - vadvance as f32;
1073 }
1074
1075 fn outlines(&self) -> &Outlines<'_> {
1076 self.outlines
1077 }
1078
1079 fn load_empty(&mut self, glyph_id: GlyphId) -> Result<(), DrawError> {
1080 let scale = self.scale;
1083 let mut unscaled = self.phantom;
1084 if self.outlines.glyph_metrics.hvar.is_none()
1085 && self.outlines.gvar.is_some()
1086 && !self.coords.is_empty()
1087 {
1088 if let Ok(Some(deltas)) = self.outlines.gvar.as_ref().unwrap().phantom_point_deltas(
1089 &self.outlines.glyf,
1090 &self.outlines.loca,
1091 self.coords,
1092 glyph_id,
1093 ) {
1094 unscaled[0] += deltas[0].map(Fixed::to_f32);
1095 unscaled[1] += deltas[1].map(Fixed::to_f32);
1096 }
1097 }
1098 for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
1099 *phantom = *unscaled * scale;
1100 }
1101 Ok(())
1102 }
1103
1104 fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), DrawError> {
1105 use DrawError::InsufficientMemory;
1106 let points_start = self.point_count;
1108 let point_count = glyph.num_points();
1109 let phantom_start = point_count;
1110 let points_end = points_start + point_count + PHANTOM_POINT_COUNT;
1111 let point_range = points_start..points_end;
1112 let points = self
1114 .memory
1115 .points
1116 .get_mut(point_range.clone())
1117 .ok_or(InsufficientMemory)?;
1118 let flags = self
1119 .memory
1120 .flags
1121 .get_mut(point_range)
1122 .ok_or(InsufficientMemory)?;
1123 glyph.read_points_fast(&mut points[..point_count], &mut flags[..point_count])?;
1124 let contours_start = self.contour_count;
1126 let contour_end_pts = glyph.end_pts_of_contours();
1127 let contour_count = contour_end_pts.len();
1128 let contours_end = contours_start + contour_count;
1129 let contours = self
1130 .memory
1131 .contours
1132 .get_mut(contours_start..contours_end)
1133 .ok_or(InsufficientMemory)?;
1134 for (end_pt, contour) in contour_end_pts.iter().zip(contours.iter_mut()) {
1136 *contour = end_pt.get();
1137 }
1138 self.point_count += point_count;
1140 self.contour_count += contour_count;
1141 for (i, phantom) in self.phantom.iter().enumerate() {
1143 points[phantom_start + i] = *phantom;
1144 flags[phantom_start + i] = Default::default();
1145 }
1146 if self.outlines.gvar.is_some() && !self.coords.is_empty() {
1148 let gvar = self.outlines.gvar.as_ref().unwrap();
1149 let glyph = deltas::SimpleGlyph {
1150 points: &mut points[..],
1151 flags: &mut flags[..],
1152 contours,
1153 };
1154 let deltas = self
1155 .memory
1156 .deltas
1157 .get_mut(..point_count + PHANTOM_POINT_COUNT)
1158 .ok_or(InsufficientMemory)?;
1159 let iup_buffer = self
1160 .memory
1161 .iup_buffer
1162 .get_mut(..point_count + PHANTOM_POINT_COUNT)
1163 .ok_or(InsufficientMemory)?;
1164 if deltas::simple_glyph(gvar, glyph_id, self.coords, glyph, iup_buffer, deltas).is_ok()
1165 {
1166 for (point, delta) in points.iter_mut().zip(deltas) {
1167 *point += *delta;
1168 }
1169 }
1170 }
1171 if self.scale != 1.0 {
1173 for point in points.iter_mut() {
1174 *point *= self.scale;
1175 }
1176 }
1177
1178 if points_start != 0 {
1179 for contour_end in contours.iter_mut() {
1181 *contour_end += points_start as u16;
1182 }
1183 }
1184 Ok(())
1185 }
1186
1187 fn load_composite(
1188 &mut self,
1189 glyph: &CompositeGlyph,
1190 glyph_id: GlyphId,
1191 recurse_depth: usize,
1192 ) -> Result<(), DrawError> {
1193 use DrawError::InsufficientMemory;
1194 let scale = self.scale;
1195 let point_base = self.point_count;
1197 let mut have_deltas = false;
1200 let delta_base = self.component_delta_count;
1201 if self.outlines.gvar.is_some() && !self.coords.is_empty() {
1202 let gvar = self.outlines.gvar.as_ref().unwrap();
1203 let count = glyph.components().count() + PHANTOM_POINT_COUNT;
1204 let deltas = self
1205 .memory
1206 .composite_deltas
1207 .get_mut(delta_base..delta_base + count)
1208 .ok_or(InsufficientMemory)?;
1209 if deltas::composite_glyph(gvar, glyph_id, self.coords, &mut deltas[..]).is_ok() {
1210 for (phantom, delta) in self
1212 .phantom
1213 .iter_mut()
1214 .zip(&deltas[deltas.len() - PHANTOM_POINT_COUNT..])
1215 {
1216 *phantom += *delta;
1217 }
1218 have_deltas = true;
1219 }
1220 self.component_delta_count += count;
1221 }
1222 if scale != 1.0 {
1223 for point in self.phantom.iter_mut() {
1224 *point *= scale;
1225 }
1226 }
1227 for (i, component) in glyph.components().enumerate() {
1228 let phantom = self.phantom;
1231 let start_point = self.point_count;
1233 let component_glyph = self
1234 .outlines
1235 .loca
1236 .get_glyf(component.glyph.into(), &self.outlines.glyf)?;
1237 self.load(&component_glyph, component.glyph.into(), recurse_depth + 1)?;
1238 let end_point = self.point_count;
1239 if !component
1240 .flags
1241 .contains(CompositeGlyphFlags::USE_MY_METRICS)
1242 {
1243 self.phantom = phantom;
1246 }
1247 let have_xform = component.flags.intersects(
1248 CompositeGlyphFlags::WE_HAVE_A_SCALE
1249 | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE
1250 | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO,
1251 );
1252 let mut transform = if have_xform {
1253 let xform = &component.transform;
1254 [
1255 xform.xx,
1256 xform.yx,
1257 xform.xy,
1258 xform.yy,
1259 F2Dot14::ZERO,
1260 F2Dot14::ZERO,
1261 ]
1262 .map(|x| x.to_f32())
1263 } else {
1264 [1.0, 0.0, 0.0, 1.0, 0.0, 0.0] };
1266
1267 let anchor_offset = match component.anchor {
1268 Anchor::Offset { x, y } => {
1269 let (mut x, mut y) = (x as f32, y as f32);
1270 if have_xform
1271 && component.flags
1272 & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
1273 | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
1274 == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
1275 {
1276 x *= hypot(transform[0], transform[2]);
1279 y *= hypot(transform[1], transform[3]);
1280 }
1281 Point::new(x, y)
1282 + self
1283 .memory
1284 .composite_deltas
1285 .get(delta_base + i)
1286 .copied()
1287 .unwrap_or_default()
1288 }
1289 Anchor::Point { base, component } => {
1290 let (base_offset, component_offset) = (base as usize, component as usize);
1291 let base_point = self
1292 .memory
1293 .points
1294 .get(point_base + base_offset)
1295 .ok_or(DrawError::InvalidAnchorPoint(glyph_id, base))?;
1296 let component_point = self
1297 .memory
1298 .points
1299 .get(start_point + component_offset)
1300 .ok_or(DrawError::InvalidAnchorPoint(glyph_id, component))?;
1301 *base_point - *component_point
1302 }
1303 };
1304 transform[4] = anchor_offset.x;
1305 transform[5] = anchor_offset.y;
1306
1307 let points = &mut self.memory.points[start_point..end_point];
1308 for point in points.iter_mut() {
1309 *point = map_point(transform, *point);
1310 }
1311 }
1312 if have_deltas {
1313 self.component_delta_count = delta_base;
1314 }
1315 Ok(())
1316 }
1317}
1318
1319fn hypot(x: f32, y: f32) -> f32 {
1321 x.hypot(y)
1322}
1323
1324fn map_point(transform: [f32; 6], p: Point<f32>) -> Point<f32> {
1325 Point {
1326 x: transform[0] * p.x + transform[2] * p.y + transform[4],
1327 y: transform[1] * p.x + transform[3] * p.y + transform[5],
1328 }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333 use super::*;
1334 use crate::MetadataProvider;
1335 use raw::{
1336 tables::{
1337 glyf::{CompositeGlyphFlags, Glyf, SimpleGlyphFlags},
1338 loca::Loca,
1339 },
1340 FontRead, FontRef, TableProvider,
1341 };
1342
1343 #[test]
1344 fn overlap_flags() {
1345 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1346 let scaler = Outlines::new(&font).unwrap();
1347 let glyph_count = font.maxp().unwrap().num_glyphs();
1348 let expected_gids_with_overlap = vec![2, 3];
1351 assert_eq!(
1352 expected_gids_with_overlap,
1353 (0..glyph_count)
1354 .filter(|gid| scaler.outline(GlyphId::from(*gid)).unwrap().has_overlaps)
1355 .collect::<Vec<_>>()
1356 );
1357 }
1358
1359 #[test]
1360 fn interpreter_preference() {
1361 let font = FontRef::new(font_test_data::COLRV0V1).unwrap();
1363 let outlines = Outlines::new(&font).unwrap();
1364 assert!(!outlines.prefer_interpreter());
1366 let font = FontRef::new(font_test_data::TTHINT_SUBSET).unwrap();
1368 let outlines = Outlines::new(&font).unwrap();
1369 assert!(outlines.prefer_interpreter());
1371 }
1372
1373 #[test]
1374 fn empty_glyph_advance() {
1375 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1376 let outlines = Outlines::new(&font).unwrap();
1377 let coords = [F2Dot14::from_f32(0.5)];
1378 let ppem = Some(24.0);
1379 let gid = font.charmap().map(' ').unwrap();
1380 let outline = outlines.outline(gid).unwrap();
1381 assert!(outline.glyph.is_none());
1383 let mut buf = [0u8; 128];
1384 let scaler =
1385 FreeTypeScaler::unhinted(&outlines, &outline, &mut buf, ppem, &coords).unwrap();
1386 let scaled = scaler.scale(&outline.glyph, gid).unwrap();
1387 let advance = scaled.adjusted_advance_width();
1388 assert!(advance != F26Dot6::ZERO);
1389 }
1390
1391 #[test]
1392 fn empty_glyphs_have_phantom_points_too() {
1393 let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1394 let outlines = Outlines::new(&font).unwrap();
1395 let gid = font.charmap().map(' ').unwrap();
1396 let outline = outlines.outline(gid).unwrap();
1397 assert!(outline.glyph.is_none());
1398 assert_eq!(outline.points, PHANTOM_POINT_COUNT);
1399 }
1400
1401 #[test]
1404 fn composite_with_too_many_points() {
1405 let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1406 let mut outlines = Outlines::new(&font).unwrap();
1407 let mut glyf_buf = font_test_data::bebuffer::BeBuffer::new();
1410 let simple_glyph_point_count = 40000;
1413 glyf_buf = glyf_buf.push(1u16); glyf_buf = glyf_buf.extend([0i16; 4]); glyf_buf = glyf_buf.push((simple_glyph_point_count - 1) as u16); glyf_buf = glyf_buf.push(0u16); for _ in 0..simple_glyph_point_count {
1418 glyf_buf =
1419 glyf_buf.push(SimpleGlyphFlags::X_SHORT_VECTOR | SimpleGlyphFlags::Y_SHORT_VECTOR);
1420 }
1421 for _ in 0..simple_glyph_point_count * 2 {
1423 glyf_buf = glyf_buf.push(0u8);
1424 }
1425 let glyph0_end = glyf_buf.len();
1426 glyf_buf = glyf_buf.push(-1i16); glyf_buf = glyf_buf.extend([0i16; 4]); for i in 0..2 {
1430 let flags = if i == 0 {
1431 CompositeGlyphFlags::MORE_COMPONENTS | CompositeGlyphFlags::ARGS_ARE_XY_VALUES
1432 } else {
1433 CompositeGlyphFlags::ARGS_ARE_XY_VALUES
1434 };
1435 glyf_buf = glyf_buf.push(flags); glyf_buf = glyf_buf.push(0u16); glyf_buf = glyf_buf.extend([0u8; 2]); }
1439 let glyph1_end = glyf_buf.len();
1440 outlines.glyf = Glyf::read(glyf_buf.data().into()).unwrap();
1441 let mut loca_buf = font_test_data::bebuffer::BeBuffer::new();
1443 loca_buf = loca_buf.extend([0u32, glyph0_end as u32, glyph1_end as u32]);
1444 outlines.loca = Loca::read(loca_buf.data().into(), true).unwrap();
1445 let gid = GlyphId::new(1);
1446 let outline = outlines.outline(gid).unwrap();
1447 let mut mem_buf = vec![0u8; outline.required_buffer_size(Default::default())];
1448 assert!(outline.points > u16::MAX as usize);
1450 let result = FreeTypeScaler::unhinted(&outlines, &outline, &mut mem_buf, None, &[]);
1451 assert!(matches!(result, Err(DrawError::TooManyPoints(_))));
1453 }
1454
1455 #[test]
1456 fn fractional_size_hinting() {
1457 let font = FontRef::from_index(font_test_data::TINOS_SUBSET, 0).unwrap();
1458 let outlines = Outlines::new(&font).unwrap();
1459 assert!(!outlines.fractional_size_hinting);
1461 for size in [10.0, 10.2, 10.5, 10.8, 11.0] {
1464 assert_eq!(
1465 outlines.compute_hinted_scale(Some(size)).scale,
1466 outlines.compute_hinted_scale(Some(size.round())).scale
1467 );
1468 }
1469 }
1470}