1use super::{DrawnMetrics, HintOptions, ScaleError};
7use crate::{
8 font::Font,
9 outline::glyf::{
10 ClearType, ClearTypeState, DropoutControl, HintError, HintInstance, HintOutline,
11 HintTables, RasterPolicy,
12 },
13};
14use alloc::sync::Arc;
15use core::{
16 mem::{align_of, size_of},
17 ops::Range,
18};
19use raw::model::Error;
20use raw::{
21 mem::{take_slice, with_scratch},
22 model::{
23 _font_interop,
24 glyph::outline::{glyf::GlyfGlyph, PathContourStart, PathSink},
25 Tables,
26 },
27 tables::{
28 glyf::{
29 outline::{Buffers, GlyphZone, Hinter, Scale26Dot6},
30 PointFlags, PHANTOM_POINT_COUNT,
31 },
32 head::Flags as HeadFlags,
33 },
34 types::{F26Dot6, Fixed, GlyphId, Point},
35 TableProvider,
36};
37use yoke::Yoke;
38
39#[derive(Default)]
41pub(super) struct TrueTypeHinter {
42 instance: HintInstance,
43 tables: Option<Yoke<HintTables<'static>, Arc<Tables>>>,
46 scale: Option<Scale26Dot6>,
47 ppem: f32,
50 pedantic: bool,
51 hdmx_widths: Option<Range<usize>>,
53 scratch_lengths: ScratchLengths,
54}
55
56#[derive(Copy, Clone, Default)]
58struct ScratchLengths {
59 stack: usize,
60 cvt: usize,
61 storage: usize,
62 twilight: usize,
63}
64
65impl ScratchLengths {
66 fn packed_len(&self, points: usize) -> usize {
69 let point = size_of::<Point<F26Dot6>>();
70 points * point
71 + (self.stack + self.cvt + self.storage) * size_of::<i32>()
72 + self.twilight * (2 * point + size_of::<PointFlags>())
73 + 7 * align_of::<Point<F26Dot6>>()
75 }
76
77 fn carve<'a>(&self, buf: &'a mut [u8], points: usize) -> Option<HintScratch<'a>> {
82 let (original_scaled, rest) = take_slice::<Point<F26Dot6>>(buf, points)?;
83 let (twilight_scaled, rest) = take_slice::<Point<F26Dot6>>(rest, self.twilight)?;
84 let (twilight_original_scaled, rest) = take_slice::<Point<F26Dot6>>(rest, self.twilight)?;
85 let (stack, rest) = take_slice::<i32>(rest, self.stack)?;
86 let (cvt, rest) = take_slice::<i32>(rest, self.cvt)?;
87 let (storage, rest) = take_slice::<i32>(rest, self.storage)?;
88 let (twilight_flags, _) = take_slice::<PointFlags>(rest, self.twilight)?;
89 Some(HintScratch {
90 original_scaled,
91 stack,
92 cvt,
93 storage,
94 twilight_scaled,
95 twilight_original_scaled,
96 twilight_flags,
97 })
98 }
99}
100
101struct HintScratch<'a> {
106 original_scaled: &'a mut [Point<F26Dot6>],
108 stack: &'a mut [i32],
109 cvt: &'a mut [i32],
110 storage: &'a mut [i32],
111 twilight_scaled: &'a mut [Point<F26Dot6>],
112 twilight_original_scaled: &'a mut [Point<F26Dot6>],
113 twilight_flags: &'a mut [PointFlags],
114}
115
116impl TrueTypeHinter {
117 pub(super) fn reconfigure(
119 &mut self,
120 font: &Font,
121 ppem: f32,
122 hinting: &HintOptions,
123 ) -> Result<(), ScaleError> {
124 let host = _font_interop::_table_cache_host(font).ok_or(Error::MissingResource)?;
127 let hint_tables = self
128 .tables
129 .insert(Yoke::attach_to_cart(host, |tables| hint_tables(tables)))
130 .get();
131 let tables = font.tables();
132 let units_per_em = font.units_per_em();
133 let fractional_sizes = !tables
137 .head()
138 .map(|head| head.flags().contains(HeadFlags::FORCE_INTEGER_PPEM))
139 .unwrap_or_default();
140 let ppem = if fractional_sizes {
141 ppem
142 } else {
143 F26Dot6::from_f64(ppem as f64).round().to_f32()
144 };
145 let scale = Scale26Dot6::new(Some(ppem), units_per_em);
146 let scale_bits = scale.to_bits();
147 let rounded_ppem =
151 ((Fixed::from_bits(scale_bits) * Fixed::from_bits(units_per_em as i32)).to_bits() + 32)
152 >> 6;
153 let policy = RasterPolicy::resolve(&tables, rounded_ppem.clamp(0, u16::MAX as i32) as u16);
154 let mode = if hinting.classic_cleartype() {
155 ClearType::Classic
156 } else {
157 ClearType::Enhanced
158 };
159 let cleartype = ClearTypeState::new(mode, font.is_hint_reliant(), policy);
160 self.instance.reconfigure(
161 hint_tables,
162 scale_bits,
163 rounded_ppem,
164 hinting.outline_target(),
165 font.normalized_coords(),
166 cleartype,
167 )?;
168 self.scale = Some(scale);
169 self.ppem = ppem;
170 self.pedantic = hinting.pedantic();
171 let fixed_pitch = tables.post().is_ok_and(|post| post.is_fixed_pitch() != 0);
174 self.hdmx_widths = (!fixed_pitch)
175 .then(|| hdmx_widths(tables, rounded_ppem))
176 .flatten();
177 self.scratch_lengths = ScratchLengths {
178 stack: hint_tables.max_stack_elements as usize,
179 cvt: hint_tables.cvt.len(),
180 storage: hint_tables.max_storage as usize,
181 twilight: hint_tables.max_twilight_points as usize,
182 };
183 Ok(())
184 }
185
186 pub(super) fn is_enabled(&self) -> bool {
191 self.scale.is_some() && self.instance.is_enabled()
192 }
193
194 pub(super) fn program_unhinted_ppem(&self) -> Option<f32> {
202 (self.scale.is_some() && self.instance.is_disabled_by_program()).then_some(self.ppem)
203 }
204
205 #[cfg(test)]
207 pub(super) fn backward_compatibility(&self) -> bool {
208 self.instance.backward_compatibility()
209 }
210
211 #[cfg(test)]
213 pub(super) fn simulate_program_disabling_hinting(&mut self) {
214 self.instance.simulate_prep_flag_suppress_hinting();
215 }
216
217 pub(super) fn ppem(&self) -> Option<f32> {
219 self.scale.is_some().then_some(self.ppem)
220 }
221
222 pub(super) fn raster_policy(&self) -> RasterPolicy {
223 self.instance.raster_policy()
224 }
225
226 pub(super) fn required_memory_size(&self, outline: &GlyfGlyph<'_>) -> usize {
230 let lengths = outline.plan().buffer_lengths::<Scale26Dot6>();
231 lengths.packed_len::<Scale26Dot6>()
232 + self.scratch_lengths.packed_len(lengths.points as usize)
233 }
234
235 pub(super) fn draw(
236 &self,
237 outline: &GlyfGlyph<'_>,
238 memory: Option<&mut [u8]>,
239 sink: &mut dyn PathSink,
240 ) -> Result<DrawnMetrics, ScaleError> {
241 match memory {
242 Some(buf) => self.draw_in(outline, buf, sink),
243 None => with_scratch(self.required_memory_size(outline), |buf| {
244 self.draw_in(outline, buf, sink)
245 }),
246 }
247 }
248
249 fn draw_in(
250 &self,
251 outline: &GlyfGlyph<'_>,
252 buf: &mut [u8],
253 mut sink: &mut dyn PathSink,
254 ) -> Result<DrawnMetrics, ScaleError> {
255 let (Some(tables), Some(scale)) = (&self.tables, &self.scale) else {
256 return Err(Error::MissingResource.into());
257 };
258 let insufficient = ScaleError::InsufficientMemory {
259 required: self.required_memory_size(outline),
260 supplied: buf.len(),
261 };
262 let tables = tables.get();
263 let glyph = outline.glyph();
264 let font = outline.font();
265 let plan = outline.plan();
266 let lengths = plan.buffer_lengths::<Scale26Dot6>();
267 let loader_len = lengths.packed_len::<Scale26Dot6>();
268 if buf.len() < loader_len {
269 return Err(insufficient);
270 }
271 let (loader_buf, rest) = buf.split_at_mut(loader_len);
272 let buffers = Buffers::from_bytes(loader_buf, &lengths).map_err(|error| match error {
273 Error::InsufficientMemory => insufficient.clone(),
274 error => error.into(),
275 })?;
276 let scratch = self
277 .scratch_lengths
278 .carve(rest, lengths.points as usize)
279 .ok_or_else(|| insufficient.clone())?;
280 let mut hinter = FontHinter {
281 instance: &self.instance,
282 tables,
283 scratch,
284 pedantic: self.pedantic,
285 dropout_control: self.instance.dropout_control(),
288 error: None,
289 };
290 let loaded = plan
291 .load(&font, scale, buffers, Some(&mut hinter))
292 .map_err(|error| match hinter.error.take() {
293 Some(error) => ScaleError::Hinting(error),
294 None if error == Error::InsufficientMemory => insufficient.clone(),
295 None => error.into(),
296 })?;
297 let hdmx_width = if self.instance.backward_compatibility() {
302 None
303 } else {
304 self.hdmx_width(font.tables(), glyph)
305 };
306 let advance = hdmx_width
307 .map(F26Dot6::from_i32)
308 .unwrap_or_else(|| loaded.adjusted_advance_width());
309 if !loaded.to_path(PathContourStart::ScanBackward, &mut sink) {
310 return Err(Error::Malformed.into());
311 }
312 let mut metrics = DrawnMetrics::default();
313 metrics.h_advance = Some(advance.round().to_f32());
316 metrics.x_bearing = Some(loaded.adjusted_lsb().to_f32());
317 metrics.has_overlaps = plan.has_overlaps;
318 metrics.dropout_control = hinter.dropout_control;
319 Ok(metrics)
320 }
321
322 fn hdmx_width(&self, tables: &Tables, glyph: GlyphId) -> Option<i32> {
324 let widths = tables.hdmx_data()?.get(self.hdmx_widths.clone()?)?;
325 Some(*widths.get(glyph.to_u32() as usize)? as i32)
326 }
327}
328
329struct FontHinter<'a> {
333 instance: &'a HintInstance,
334 tables: &'a HintTables<'a>,
335 scratch: HintScratch<'a>,
336 pedantic: bool,
337 dropout_control: DropoutControl,
338 error: Option<HintError>,
341}
342
343impl Hinter<F26Dot6> for FontHinter<'_> {
344 fn backward_compatibility(&self) -> bool {
345 self.instance.backward_compatibility()
346 }
347
348 fn round_composite_x_offset(&self) -> bool {
349 self.instance.round_composite_x_offset()
350 }
351
352 fn hint(&mut self, zone: &mut GlyphZone<'_, F26Dot6>) -> Result<(), HintError> {
353 let original_scaled = &mut self.scratch.original_scaled[..zone.points.len()];
358 original_scaled.copy_from_slice(zone.points);
359 let phantom_start = original_scaled.len() - PHANTOM_POINT_COUNT;
360 original_scaled[phantom_start..].copy_from_slice(&zone.phantom[..]);
361 let mut input = HintOutline {
362 glyph_id: zone.glyph,
363 unscaled: zone.unscaled,
364 scaled: zone.points,
365 original_scaled,
366 flags: zone.flags,
367 contours: zone.contours,
368 bytecode: zone.bytecode,
369 phantom: &mut zone.phantom[..],
370 stack: &mut *self.scratch.stack,
371 cvt: &mut *self.scratch.cvt,
372 storage: &mut *self.scratch.storage,
373 twilight_scaled: &mut *self.scratch.twilight_scaled,
374 twilight_original_scaled: &mut *self.scratch.twilight_original_scaled,
375 twilight_flags: &mut *self.scratch.twilight_flags,
376 is_composite: zone.is_composite,
377 coords: zone.coords,
378 };
379 match self.instance.hint(self.tables, &mut input, self.pedantic) {
380 Ok(dropout_control) => {
381 self.dropout_control = dropout_control;
382 Ok(())
383 }
384 Err(error) => {
386 self.error = Some(error.clone());
387 Err(error)
388 }
389 }
390 }
391}
392
393pub(super) fn hint_tables(tables: &Tables) -> HintTables<'_> {
395 let maxp = tables.selected_maxp().ok();
396 HintTables {
397 fpgm: tables.fpgm().unwrap_or_default(),
398 prep: tables.prep().unwrap_or_default(),
399 cvt: tables.cvt().unwrap_or_default(),
400 cvar: tables.cvar().ok(),
401 max_function_defs: maxp
402 .as_ref()
403 .and_then(|maxp| maxp.max_function_defs())
404 .unwrap_or_default(),
405 max_instruction_defs: maxp
406 .as_ref()
407 .and_then(|maxp| maxp.max_instruction_defs())
408 .unwrap_or_default(),
409 max_twilight_points: maxp
412 .as_ref()
413 .and_then(|maxp| maxp.max_twilight_points())
414 .unwrap_or_default()
415 .saturating_add(4),
416 max_stack_elements: maxp
419 .as_ref()
420 .and_then(|maxp| maxp.max_stack_elements())
421 .unwrap_or_default()
422 .saturating_add(32),
423 max_storage: maxp
424 .as_ref()
425 .and_then(|maxp| maxp.max_storage())
426 .unwrap_or_default(),
427 axis_count: tables
428 .fvar()
429 .map(|fvar| fvar.axis_count())
430 .unwrap_or_default(),
431 }
432}
433
434pub(super) fn prefer_interpreter(tables: &Tables) -> bool {
441 let max_instructions = tables
442 .selected_maxp()
443 .ok()
444 .and_then(|maxp| maxp.max_size_of_instructions())
445 .unwrap_or_default();
446 !(max_instructions == 0
447 && tables.fpgm().unwrap_or_default().is_empty()
448 && tables.prep().unwrap_or_default().is_empty())
449}
450
451fn hdmx_widths(tables: &Tables, ppem: i32) -> Option<Range<usize>> {
457 let size = u8::try_from(ppem).ok()?;
460 let data = tables.hdmx_data()?;
461 let widths = tables.hdmx().ok()?.record_for_size(size)?.widths();
462 let start = (widths.as_ptr() as usize).checked_sub(data.as_ptr() as usize)?;
464 let end = start.checked_add(widths.len())?;
465 (end <= data.len()).then_some(start..end)
466}