1#![expect(clippy::mem_forget)]
2
3use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
4use self_cell::self_cell;
5use skrifa::{
6 MetadataProvider as _,
7 raw::{TableProvider as _, tables::kern::SubtableKind},
8};
9use std::collections::BTreeMap;
10use vello_cpu::{color, kurbo};
11
12use crate::{
13 TextOptions, TextureAtlas,
14 text::{
15 FontTweak, VariationCoords,
16 fonts::{Blob, CachedFamily, FontFaceKey},
17 },
18};
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
24pub struct UvRect {
25 pub offset: Vec2,
27
28 pub size: Vec2,
31
32 pub min: [u16; 2],
34
35 pub max: [u16; 2],
37}
38
39impl UvRect {
40 pub fn is_nothing(&self) -> bool {
41 self.min == self.max
42 }
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46pub struct GlyphInfo {
47 pub(crate) id: Option<skrifa::GlyphId>,
53
54 pub advance_width_unscaled: OrderedFloat<f32>,
56}
57
58impl GlyphInfo {
59 pub const INVISIBLE: Self = Self {
61 id: None,
62 advance_width_unscaled: OrderedFloat(0.0),
63 };
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
75pub(super) enum SubpixelBin {
76 #[default]
77 Zero,
78 One,
79 Two,
80 Three,
81}
82
83impl SubpixelBin {
84 fn new(pos: f32) -> (i32, Self) {
86 let trunc = pos as i32;
87 let fract = pos - trunc as f32;
88
89 #[expect(clippy::collapsible_else_if)]
90 if pos.is_sign_negative() {
91 if fract > -0.125 {
92 (trunc, Self::Zero)
93 } else if fract > -0.375 {
94 (trunc - 1, Self::Three)
95 } else if fract > -0.625 {
96 (trunc - 1, Self::Two)
97 } else if fract > -0.875 {
98 (trunc - 1, Self::One)
99 } else {
100 (trunc - 1, Self::Zero)
101 }
102 } else {
103 if fract < 0.125 {
104 (trunc, Self::Zero)
105 } else if fract < 0.375 {
106 (trunc, Self::One)
107 } else if fract < 0.625 {
108 (trunc, Self::Two)
109 } else if fract < 0.875 {
110 (trunc, Self::Three)
111 } else {
112 (trunc + 1, Self::Zero)
113 }
114 }
115 }
116
117 pub fn as_float(&self) -> f32 {
118 match self {
119 Self::Zero => 0.0,
120 Self::One => 0.25,
121 Self::Two => 0.5,
122 Self::Three => 0.75,
123 }
124 }
125}
126
127#[derive(Clone, Copy, Debug, PartialEq, Default)]
128pub struct GlyphAllocation {
129 pub(crate) id: skrifa::GlyphId,
134
135 pub advance_width_px: f32,
137
138 pub uv_rect: UvRect,
140}
141
142#[derive(Hash, PartialEq, Eq)]
143struct GlyphCacheKey(u64);
144
145impl nohash_hasher::IsEnabled for GlyphCacheKey {}
146
147impl GlyphCacheKey {
148 fn new(glyph_id: skrifa::GlyphId, metrics: &StyledMetrics, bin: SubpixelBin) -> Self {
149 let StyledMetrics {
150 pixels_per_point,
151 px_scale_factor,
152 ..
153 } = *metrics;
154 debug_assert!(
155 0.0 < pixels_per_point && pixels_per_point.is_finite(),
156 "Bad pixels_per_point {pixels_per_point}"
157 );
158 debug_assert!(
159 0.0 < px_scale_factor && px_scale_factor.is_finite(),
160 "Bad px_scale_factor: {px_scale_factor}"
161 );
162 Self(crate::util::hash((
163 glyph_id,
164 pixels_per_point.to_bits(),
165 px_scale_factor.to_bits(),
166 bin,
167 )))
168 }
169}
170
171struct DependentFontData<'a> {
174 skrifa: skrifa::FontRef<'a>,
175 charmap: skrifa::charmap::Charmap<'a>,
176 outline_glyphs: skrifa::outline::OutlineGlyphCollection<'a>,
177 metrics: skrifa::metrics::Metrics,
178 glyph_metrics: skrifa::metrics::GlyphMetrics<'a>,
179 hinting_instance: Option<skrifa::outline::HintingInstance>,
180}
181
182self_cell! {
183 struct FontCell {
184 owner: Blob,
185
186 #[covariant]
187 dependent: DependentFontData,
188 }
189}
190
191impl FontCell {
192 fn px_scale_factor(&self, scale: f32) -> f32 {
193 let units_per_em = self.borrow_dependent().metrics.units_per_em as f32;
194 scale / units_per_em
195 }
196
197 fn allocate_glyph_uncached(
198 &mut self,
199 atlas: &mut TextureAtlas,
200 metrics: &StyledMetrics,
201 glyph_info: &GlyphInfo,
202 bin: SubpixelBin,
203 location: skrifa::instance::LocationRef<'_>,
204 ) -> Option<GlyphAllocation> {
205 let glyph_id = glyph_info.id?;
206
207 debug_assert!(
208 glyph_id != skrifa::GlyphId::NOTDEF,
209 "Can't allocate glyph for id 0"
210 );
211
212 let mut path = kurbo::BezPath::new();
213 let mut pen = VelloPen {
214 path: &mut path,
215 x_offset: bin.as_float() as f64,
216 };
217
218 self.with_dependent_mut(|_, font_data| {
219 let outline = font_data.outline_glyphs.get(glyph_id)?;
220
221 if let Some(hinting_instance) = &mut font_data.hinting_instance {
222 let size = skrifa::instance::Size::new(metrics.scale);
223 if hinting_instance.size() != size {
224 hinting_instance
225 .reconfigure(
226 &font_data.outline_glyphs,
227 size,
228 location,
229 skrifa::outline::Target::Smooth {
230 mode: skrifa::outline::SmoothMode::Normal,
231 symmetric_rendering: true,
232 preserve_linear_metrics: true,
233 },
234 )
235 .ok()?;
236 }
237 let draw_settings = skrifa::outline::DrawSettings::hinted(hinting_instance, false);
238 outline.draw(draw_settings, &mut pen).ok()?;
239 } else {
240 let draw_settings = skrifa::outline::DrawSettings::unhinted(
241 skrifa::instance::Size::new(metrics.scale),
242 location,
243 );
244 outline.draw(draw_settings, &mut pen).ok()?;
245 }
246
247 Some(())
248 })?;
249
250 let bounds = path.control_box().expand();
251 let width = bounds.width() as u16;
252 let height = bounds.height() as u16;
253
254 let mut ctx = vello_cpu::RenderContext::new(width, height);
255 ctx.set_transform(kurbo::Affine::translate((-bounds.x0, -bounds.y0)));
256 ctx.set_paint(color::OpaqueColor::<color::Srgb>::WHITE);
257 ctx.fill_path(&path);
258 let mut dest = vello_cpu::Pixmap::new(width, height);
259 ctx.render_to_pixmap(&mut dest);
260 let uv_rect = if width == 0 || height == 0 {
261 UvRect::default()
262 } else {
263 let glyph_pos = {
264 let alpha_from_coverage = atlas.options().alpha_from_coverage;
265 let (glyph_pos, image) = atlas.allocate((width as usize, height as usize));
266 let pixels = dest.data_as_u8_slice();
267 for y in 0..height as usize {
268 for x in 0..width as usize {
269 image[(x + glyph_pos.0, y + glyph_pos.1)] = alpha_from_coverage
270 .color_from_coverage(
271 pixels[((y * width as usize) + x) * 4 + 3] as f32 / 255.0,
272 );
273 }
274 }
275 glyph_pos
276 };
277 let offset_in_pixels = vec2(bounds.x0 as f32, bounds.y0 as f32);
278 let offset =
279 offset_in_pixels / metrics.pixels_per_point + metrics.y_offset_in_points * Vec2::Y;
280 UvRect {
281 offset,
282 size: vec2(width as f32, height as f32) / metrics.pixels_per_point,
283 min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
284 max: [
285 (glyph_pos.0 + width as usize) as u16,
286 (glyph_pos.1 + height as usize) as u16,
287 ],
288 }
289 };
290
291 Some(GlyphAllocation {
292 id: glyph_id,
293 advance_width_px: glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor,
294 uv_rect,
295 })
296 }
297}
298
299struct VelloPen<'a> {
300 path: &'a mut kurbo::BezPath,
301 x_offset: f64,
302}
303
304impl skrifa::outline::OutlinePen for VelloPen<'_> {
305 fn move_to(&mut self, x: f32, y: f32) {
306 self.path.move_to((x as f64 + self.x_offset, -y as f64));
307 }
308
309 fn line_to(&mut self, x: f32, y: f32) {
310 self.path.line_to((x as f64 + self.x_offset, -y as f64));
311 }
312
313 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
314 self.path.quad_to(
315 (cx0 as f64 + self.x_offset, -cy0 as f64),
316 (x as f64 + self.x_offset, -y as f64),
317 );
318 }
319
320 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
321 self.path.curve_to(
322 (cx0 as f64 + self.x_offset, -cy0 as f64),
323 (cx1 as f64 + self.x_offset, -cy1 as f64),
324 (x as f64 + self.x_offset, -y as f64),
325 );
326 }
327
328 fn close(&mut self) {
329 self.path.close_path();
330 }
331}
332
333pub struct FontFace {
336 name: String,
337 font: FontCell,
338 tweak: FontTweak,
339
340 glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
341 glyph_alloc_cache: ahash::HashMap<GlyphCacheKey, GlyphAllocation>,
342}
343
344impl FontFace {
345 pub fn new(
346 options: TextOptions,
347 name: String,
348 font_data: Blob,
349 index: u32,
350 tweak: FontTweak,
351 ) -> Result<Self, Box<dyn std::error::Error>> {
352 let font = FontCell::try_new(font_data, |font_data| {
353 let skrifa_font =
354 skrifa::FontRef::from_index(AsRef::<[u8]>::as_ref(font_data.as_ref()), index)?;
355
356 let charmap = skrifa_font.charmap();
357 let glyphs = skrifa_font.outline_glyphs();
358
359 let metrics = skrifa_font.metrics(
363 skrifa::instance::Size::unscaled(),
364 skrifa::instance::LocationRef::default(),
365 );
366 let glyph_metrics = skrifa_font.glyph_metrics(
367 skrifa::instance::Size::unscaled(),
368 skrifa::instance::LocationRef::default(),
369 );
370
371 let hinting_enabled = tweak.hinting_override.unwrap_or(options.font_hinting);
372 let hinting_instance = hinting_enabled
373 .then(|| {
374 skrifa::outline::HintingInstance::new(
377 &glyphs,
378 skrifa::instance::Size::unscaled(),
379 skrifa::instance::LocationRef::default(),
380 skrifa::outline::Target::default(),
381 )
382 .ok()
383 })
384 .flatten();
385
386 Ok::<DependentFontData<'_>, Box<dyn std::error::Error>>(DependentFontData {
387 skrifa: skrifa_font,
388 charmap,
389 outline_glyphs: glyphs,
390 metrics,
391 glyph_metrics,
392 hinting_instance,
393 })
394 })?;
395
396 Ok(Self {
397 name,
398 font,
399 tweak,
400 glyph_info_cache: Default::default(),
401 glyph_alloc_cache: Default::default(),
402 })
403 }
404
405 fn ignore_character(&self, chr: char) -> bool {
409 use crate::text::FontDefinitions;
410
411 if !FontDefinitions::builtin_font_names().contains(&self.name.as_str()) {
412 return false;
413 }
414
415 matches!(
416 chr,
417 '\u{534d}' | '\u{5350}' |
419
420 '\u{E0FF}' | '\u{EFFD}' | '\u{F0FF}' | '\u{F200}'
422 )
423 }
424
425 fn characters(&self) -> impl Iterator<Item = char> + '_ {
427 self.font
428 .borrow_dependent()
429 .charmap
430 .mappings()
431 .filter_map(|(chr, _)| char::from_u32(chr).filter(|c| !self.ignore_character(*c)))
432 }
433
434 pub(super) fn glyph_info(&mut self, c: char) -> Option<GlyphInfo> {
436 if let Some(glyph_info) = self.glyph_info_cache.get(&c) {
437 return Some(*glyph_info);
438 }
439
440 if self.ignore_character(c) {
441 return None; }
443
444 if c == '\t'
445 && let Some(space) = self.glyph_info(' ')
446 {
447 let glyph_info = GlyphInfo {
448 advance_width_unscaled: (crate::text::TAB_SIZE as f32
449 * space.advance_width_unscaled.0)
450 .into(),
451 ..space
452 };
453 self.glyph_info_cache.insert(c, glyph_info);
454 return Some(glyph_info);
455 }
456
457 if c == '\u{2009}' {
458 if let Some(space) = self.glyph_info(' ') {
463 let em = self.font.borrow_dependent().metrics.units_per_em as f32;
464 let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); let glyph_info = GlyphInfo {
466 advance_width_unscaled: advance_width.into(),
467 ..space
468 };
469 self.glyph_info_cache.insert(c, glyph_info);
470 return Some(glyph_info);
471 }
472 }
473
474 if invisible_char(c) {
475 let glyph_info = GlyphInfo::INVISIBLE;
476 self.glyph_info_cache.insert(c, glyph_info);
477 return Some(glyph_info);
478 }
479
480 let font_data = self.font.borrow_dependent();
481
482 let glyph_id = font_data
484 .charmap
485 .map(c)
486 .filter(|id| *id != skrifa::GlyphId::NOTDEF)?;
487
488 let glyph_info = GlyphInfo {
489 id: Some(glyph_id),
490 advance_width_unscaled: font_data
491 .glyph_metrics
492 .advance_width(glyph_id)
493 .unwrap_or_default()
494 .into(),
495 };
496 self.glyph_info_cache.insert(c, glyph_info);
497 Some(glyph_info)
498 }
499
500 #[inline]
501 pub(super) fn pair_kerning_pixels(
502 &self,
503 metrics: &StyledMetrics,
504 last_glyph_id: skrifa::GlyphId,
505 glyph_id: skrifa::GlyphId,
506 ) -> f32 {
507 let skrifa_font = &self.font.borrow_dependent().skrifa;
508 let Ok(kern) = skrifa_font.kern() else {
509 return 0.0;
510 };
511 kern.subtables()
512 .find_map(|st| match st.ok()?.kind().ok()? {
513 SubtableKind::Format0(table_ref) => table_ref.kerning(last_glyph_id, glyph_id),
514 SubtableKind::Format1(_) => None,
515 SubtableKind::Format2(subtable2) => subtable2.kerning(last_glyph_id, glyph_id),
516 SubtableKind::Format3(table_ref) => table_ref.kerning(last_glyph_id, glyph_id),
517 })
518 .unwrap_or_default() as f32
519 * metrics.px_scale_factor
520 }
521
522 #[inline]
523 pub fn pair_kerning(
524 &self,
525 metrics: &StyledMetrics,
526 last_glyph_id: skrifa::GlyphId,
527 glyph_id: skrifa::GlyphId,
528 ) -> f32 {
529 self.pair_kerning_pixels(metrics, last_glyph_id, glyph_id) / metrics.pixels_per_point
530 }
531
532 #[inline(always)]
533 pub fn styled_metrics(
534 &self,
535 pixels_per_point: f32,
536 font_size: f32,
537 coords: &VariationCoords,
538 ) -> StyledMetrics {
539 let pt_scale_factor = self.font.px_scale_factor(font_size * self.tweak.scale);
540 let font_data = self.font.borrow_dependent();
541 let ascent = (font_data.metrics.ascent * pt_scale_factor).round_ui();
542 let descent = (font_data.metrics.descent * pt_scale_factor).round_ui();
543 let line_gap = (font_data.metrics.leading * pt_scale_factor).round_ui();
544
545 let scale = font_size * self.tweak.scale * pixels_per_point;
546 let px_scale_factor = self.font.px_scale_factor(scale);
547
548 let y_offset_in_points = ((font_size * self.tweak.scale * self.tweak.y_offset_factor)
549 + self.tweak.y_offset)
550 .round_ui();
551
552 let axes = font_data.skrifa.axes();
553 let settings = self
556 .tweak
557 .coords
558 .as_ref()
559 .iter()
560 .chain(coords.as_ref().iter());
561 let location = axes.location(settings);
562
563 StyledMetrics {
564 pixels_per_point,
565 px_scale_factor,
566 scale,
567 y_offset_in_points,
568 ascent,
569 row_height: ascent - descent + line_gap,
570 location,
571 }
572 }
573
574 pub fn allocate_glyph(
575 &mut self,
576 atlas: &mut TextureAtlas,
577 metrics: &StyledMetrics,
578 glyph_info: GlyphInfo,
579 chr: char,
580 h_pos: f32,
581 ) -> (GlyphAllocation, i32) {
582 let advance_width_px = glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor;
583
584 let Some(glyph_id) = glyph_info.id else {
585 return (GlyphAllocation::default(), h_pos as i32);
587 };
588
589 let (h_pos_round, bin) = if is_cjk(chr) {
592 (h_pos.round() as i32, SubpixelBin::Zero)
593 } else {
594 SubpixelBin::new(h_pos)
595 };
596
597 let entry = match self
598 .glyph_alloc_cache
599 .entry(GlyphCacheKey::new(glyph_id, metrics, bin))
600 {
601 std::collections::hash_map::Entry::Occupied(glyph_alloc) => {
602 let mut glyph_alloc = *glyph_alloc.get();
603 glyph_alloc.advance_width_px = advance_width_px; return (glyph_alloc, h_pos_round);
605 }
606 std::collections::hash_map::Entry::Vacant(entry) => entry,
607 };
608
609 let allocation = self
610 .font
611 .allocate_glyph_uncached(atlas, metrics, &glyph_info, bin, (&metrics.location).into())
612 .unwrap_or_default();
613
614 entry.insert(allocation);
615 (allocation, h_pos_round)
616 }
617}
618
619pub struct Font<'a> {
622 pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap<FontFaceKey, FontFace>,
623 pub(super) cached_family: &'a mut CachedFamily,
624 pub(super) atlas: &'a mut TextureAtlas,
625}
626
627impl Font<'_> {
628 pub fn preload_characters(&mut self, s: &str) {
629 for c in s.chars() {
630 self.glyph_info(c);
631 }
632 }
633
634 pub fn characters(&mut self) -> &BTreeMap<char, Vec<String>> {
636 self.cached_family.characters.get_or_insert_with(|| {
637 let mut characters: BTreeMap<char, Vec<String>> = Default::default();
638 for font_id in &self.cached_family.fonts {
639 let font = self.fonts_by_id.get(font_id).expect("Nonexistent font ID");
640 for chr in font.characters() {
641 characters.entry(chr).or_default().push(font.name.clone());
642 }
643 }
644 characters
645 })
646 }
647
648 pub fn styled_metrics(
649 &self,
650 pixels_per_point: f32,
651 font_size: f32,
652 coords: &VariationCoords,
653 ) -> StyledMetrics {
654 self.cached_family
655 .fonts
656 .first()
657 .and_then(|key| self.fonts_by_id.get(key))
658 .map(|font_face| font_face.styled_metrics(pixels_per_point, font_size, coords))
659 .unwrap_or_default()
660 }
661
662 pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 {
664 let (key, glyph_info) = self.glyph_info(c);
665 if let Some(font) = &self.fonts_by_id.get(&key) {
666 glyph_info.advance_width_unscaled.0 * font.font.px_scale_factor(font_size)
667 } else {
668 0.0
669 }
670 }
671
672 pub fn has_glyph(&mut self, c: char) -> bool {
674 self.glyph_info(c) != self.cached_family.replacement_glyph }
676
677 pub fn has_glyphs(&mut self, s: &str) -> bool {
679 s.chars().all(|c| self.has_glyph(c))
680 }
681
682 pub(crate) fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) {
684 if let Some(font_index_glyph_info) = self.cached_family.glyph_info_cache.get(&c) {
685 return *font_index_glyph_info;
686 }
687
688 let font_index_glyph_info = self
689 .cached_family
690 .glyph_info_no_cache_or_fallback(c, self.fonts_by_id);
691 let font_index_glyph_info =
692 font_index_glyph_info.unwrap_or(self.cached_family.replacement_glyph);
693 self.cached_family
694 .glyph_info_cache
695 .insert(c, font_index_glyph_info);
696 font_index_glyph_info
697 }
698}
699
700#[derive(Clone, Debug, PartialEq, Default)]
702pub struct StyledMetrics {
703 pub pixels_per_point: f32,
705
706 pub px_scale_factor: f32,
710
711 pub scale: f32,
713
714 pub y_offset_in_points: f32,
716
717 pub ascent: f32,
721
722 pub row_height: f32,
726
727 pub location: skrifa::instance::Location,
729}
730
731#[inline]
735fn invisible_char(c: char) -> bool {
736 if c == '\r' {
737 return true;
739 }
740
741 matches!(
748 c,
749 '\u{200B}' | '\u{200C}' | '\u{200D}' | '\u{200E}' | '\u{200F}' | '\u{202A}' | '\u{202B}' | '\u{202C}' | '\u{202D}' | '\u{202E}' | '\u{2060}' | '\u{2061}' | '\u{2062}' | '\u{2063}' | '\u{2064}' | '\u{2066}' | '\u{2067}' | '\u{2068}' | '\u{2069}' | '\u{206A}' | '\u{206B}' | '\u{206C}' | '\u{206D}' | '\u{206E}' | '\u{206F}' | '\u{FEFF}' )
776}
777
778#[inline]
779pub(super) fn is_cjk_ideograph(c: char) -> bool {
780 ('\u{4E00}' <= c && c <= '\u{9FFF}')
781 || ('\u{3400}' <= c && c <= '\u{4DBF}')
782 || ('\u{2B740}' <= c && c <= '\u{2B81F}')
783}
784
785#[inline]
786pub(super) fn is_kana(c: char) -> bool {
787 ('\u{3040}' <= c && c <= '\u{309F}') || ('\u{30A0}' <= c && c <= '\u{30FF}') }
790
791#[inline]
792pub(super) fn is_cjk(c: char) -> bool {
793 is_cjk_ideograph(c) || is_kana(c)
795}
796
797#[inline]
798pub(super) fn is_cjk_break_allowed(c: char) -> bool {
799 !")]ïœăăăăăăăăă'\"ïœ Â»ăœăŸăŒăĄăŁă„ă§ă©ăăŁă„ă§ăźă”ă¶ăăă
ăăăŁăă
ăăăăă°ă±ăČăłăŽă”ă¶ă·ăžăčășă»ăŒăœăŸăżă
ă»âă âă?!âŒâââă»ă:;,ă.".contains(c)
801}