1use std::collections::BTreeMap;
2
3use ab_glyph::{Font as _, OutlinedGlyph, PxScale};
4use emath::{GuiRounding as _, OrderedFloat, Vec2, vec2};
5
6use crate::{
7 TextureAtlas,
8 text::{
9 FontTweak,
10 fonts::{CachedFamily, FontFaceKey},
11 },
12};
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
18pub struct UvRect {
19 pub offset: Vec2,
21
22 pub size: Vec2,
25
26 pub min: [u16; 2],
28
29 pub max: [u16; 2],
31}
32
33impl UvRect {
34 pub fn is_nothing(&self) -> bool {
35 self.min == self.max
36 }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40pub struct GlyphInfo {
41 pub(crate) id: Option<ab_glyph::GlyphId>,
47
48 pub advance_width_unscaled: OrderedFloat<f32>,
50}
51
52impl GlyphInfo {
53 pub const INVISIBLE: Self = Self {
55 id: None,
56 advance_width_unscaled: OrderedFloat(0.0),
57 };
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
69pub(super) enum SubpixelBin {
70 #[default]
71 Zero,
72 One,
73 Two,
74 Three,
75}
76
77impl SubpixelBin {
78 fn new(pos: f32) -> (i32, Self) {
80 let trunc = pos as i32;
81 let fract = pos - trunc as f32;
82
83 #[expect(clippy::collapsible_else_if)]
84 if pos.is_sign_negative() {
85 if fract > -0.125 {
86 (trunc, Self::Zero)
87 } else if fract > -0.375 {
88 (trunc - 1, Self::Three)
89 } else if fract > -0.625 {
90 (trunc - 1, Self::Two)
91 } else if fract > -0.875 {
92 (trunc - 1, Self::One)
93 } else {
94 (trunc - 1, Self::Zero)
95 }
96 } else {
97 if fract < 0.125 {
98 (trunc, Self::Zero)
99 } else if fract < 0.375 {
100 (trunc, Self::One)
101 } else if fract < 0.625 {
102 (trunc, Self::Two)
103 } else if fract < 0.875 {
104 (trunc, Self::Three)
105 } else {
106 (trunc + 1, Self::Zero)
107 }
108 }
109 }
110
111 pub fn as_float(&self) -> f32 {
112 match self {
113 Self::Zero => 0.0,
114 Self::One => 0.25,
115 Self::Two => 0.5,
116 Self::Three => 0.75,
117 }
118 }
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, Default)]
122pub struct GlyphAllocation {
123 pub(crate) id: ab_glyph::GlyphId,
128
129 pub advance_width_px: f32,
131
132 pub uv_rect: UvRect,
134}
135
136#[derive(Hash, PartialEq, Eq)]
137struct GlyphCacheKey(u64);
138
139impl nohash_hasher::IsEnabled for GlyphCacheKey {}
140
141impl GlyphCacheKey {
142 fn new(glyph_id: ab_glyph::GlyphId, metrics: &ScaledMetrics, bin: SubpixelBin) -> Self {
143 let ScaledMetrics {
144 pixels_per_point,
145 px_scale_factor,
146 ..
147 } = *metrics;
148 debug_assert!(
149 0.0 < pixels_per_point && pixels_per_point.is_finite(),
150 "Bad pixels_per_point {pixels_per_point}"
151 );
152 debug_assert!(
153 0.0 < px_scale_factor && px_scale_factor.is_finite(),
154 "Bad px_scale_factor: {px_scale_factor}"
155 );
156 Self(crate::util::hash((
157 glyph_id,
158 pixels_per_point.to_bits(),
159 px_scale_factor.to_bits(),
160 bin,
161 )))
162 }
163}
164
165pub struct FontImpl {
170 name: String,
171 ab_glyph_font: ab_glyph::FontArc,
172 tweak: FontTweak,
173 glyph_info_cache: ahash::HashMap<char, GlyphInfo>,
174 glyph_alloc_cache: ahash::HashMap<GlyphCacheKey, GlyphAllocation>,
175}
176
177trait FontExt {
178 fn px_scale_factor(&self, scale: f32) -> f32;
179}
180
181impl<T> FontExt for T
182where
183 T: ab_glyph::Font,
184{
185 fn px_scale_factor(&self, scale: f32) -> f32 {
186 let units_per_em = self.units_per_em().unwrap_or_else(|| {
187 panic!("The font unit size exceeds the expected range (16..=16384)")
188 });
189 scale / units_per_em
190 }
191}
192
193impl FontImpl {
194 pub fn new(name: String, ab_glyph_font: ab_glyph::FontArc, tweak: FontTweak) -> Self {
195 Self {
196 name,
197 ab_glyph_font,
198 tweak,
199 glyph_info_cache: Default::default(),
200 glyph_alloc_cache: Default::default(),
201 }
202 }
203
204 fn ignore_character(&self, chr: char) -> bool {
208 use crate::text::FontDefinitions;
209
210 if !FontDefinitions::builtin_font_names().contains(&self.name.as_str()) {
211 return false;
212 }
213
214 matches!(
215 chr,
216 '\u{534d}' | '\u{5350}' |
218
219 '\u{E0FF}' | '\u{EFFD}' | '\u{F0FF}' | '\u{F200}'
221 )
222 }
223
224 fn characters(&self) -> impl Iterator<Item = char> + '_ {
226 self.ab_glyph_font
227 .codepoint_ids()
228 .map(|(_, chr)| chr)
229 .filter(|&chr| !self.ignore_character(chr))
230 }
231
232 pub(super) fn glyph_info(&mut self, c: char) -> Option<GlyphInfo> {
234 if let Some(glyph_info) = self.glyph_info_cache.get(&c) {
235 return Some(*glyph_info);
236 }
237
238 if self.ignore_character(c) {
239 return None; }
241
242 if c == '\t'
243 && let Some(space) = self.glyph_info(' ')
244 {
245 let glyph_info = GlyphInfo {
246 advance_width_unscaled: (crate::text::TAB_SIZE as f32
247 * space.advance_width_unscaled.0)
248 .into(),
249 ..space
250 };
251 self.glyph_info_cache.insert(c, glyph_info);
252 return Some(glyph_info);
253 }
254
255 if c == '\u{2009}' {
256 if let Some(space) = self.glyph_info(' ') {
261 let em = self.ab_glyph_font.units_per_em().unwrap_or(1.0);
262 let advance_width = f32::min(em / 6.0, space.advance_width_unscaled.0 * 0.5); let glyph_info = GlyphInfo {
264 advance_width_unscaled: advance_width.into(),
265 ..space
266 };
267 self.glyph_info_cache.insert(c, glyph_info);
268 return Some(glyph_info);
269 }
270 }
271
272 if invisible_char(c) {
273 let glyph_info = GlyphInfo::INVISIBLE;
274 self.glyph_info_cache.insert(c, glyph_info);
275 return Some(glyph_info);
276 }
277
278 let glyph_id = self.ab_glyph_font.glyph_id(c);
280
281 if glyph_id.0 == 0 {
282 None } else {
284 let glyph_info = GlyphInfo {
285 id: Some(glyph_id),
286 advance_width_unscaled: self.ab_glyph_font.h_advance_unscaled(glyph_id).into(),
287 };
288 self.glyph_info_cache.insert(c, glyph_info);
289 Some(glyph_info)
290 }
291 }
292
293 #[inline]
294 pub(super) fn pair_kerning_pixels(
295 &self,
296 metrics: &ScaledMetrics,
297 last_glyph_id: ab_glyph::GlyphId,
298 glyph_id: ab_glyph::GlyphId,
299 ) -> f32 {
300 self.ab_glyph_font.kern_unscaled(last_glyph_id, glyph_id) * metrics.px_scale_factor
301 }
302
303 #[inline]
304 pub fn pair_kerning(
305 &self,
306 metrics: &ScaledMetrics,
307 last_glyph_id: ab_glyph::GlyphId,
308 glyph_id: ab_glyph::GlyphId,
309 ) -> f32 {
310 self.pair_kerning_pixels(metrics, last_glyph_id, glyph_id) / metrics.pixels_per_point
311 }
312
313 #[inline(always)]
314 pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
315 let pt_scale_factor = self
316 .ab_glyph_font
317 .px_scale_factor(font_size * self.tweak.scale);
318 let ascent = (self.ab_glyph_font.ascent_unscaled() * pt_scale_factor).round_ui();
319 let descent = (self.ab_glyph_font.descent_unscaled() * pt_scale_factor).round_ui();
320 let line_gap = (self.ab_glyph_font.line_gap_unscaled() * pt_scale_factor).round_ui();
321
322 let scale = font_size * self.tweak.scale * pixels_per_point;
323 let px_scale_factor = self.ab_glyph_font.px_scale_factor(scale);
324
325 let y_offset_in_points = ((font_size * self.tweak.scale * self.tweak.y_offset_factor)
326 + self.tweak.y_offset)
327 .round_ui();
328
329 ScaledMetrics {
330 pixels_per_point,
331 px_scale_factor,
332 y_offset_in_points,
333 ascent,
334 row_height: ascent - descent + line_gap,
335 }
336 }
337
338 pub fn allocate_glyph(
339 &mut self,
340 atlas: &mut TextureAtlas,
341 metrics: &ScaledMetrics,
342 glyph_info: GlyphInfo,
343 chr: char,
344 h_pos: f32,
345 ) -> (GlyphAllocation, i32) {
346 let advance_width_px = glyph_info.advance_width_unscaled.0 * metrics.px_scale_factor;
347
348 let Some(glyph_id) = glyph_info.id else {
349 return (GlyphAllocation::default(), h_pos as i32);
351 };
352
353 let (h_pos_round, bin) = if is_cjk(chr) {
356 (h_pos.round() as i32, SubpixelBin::Zero)
357 } else {
358 SubpixelBin::new(h_pos)
359 };
360
361 let entry = match self
362 .glyph_alloc_cache
363 .entry(GlyphCacheKey::new(glyph_id, metrics, bin))
364 {
365 std::collections::hash_map::Entry::Occupied(glyph_alloc) => {
366 let mut glyph_alloc = *glyph_alloc.get();
367 glyph_alloc.advance_width_px = advance_width_px; return (glyph_alloc, h_pos_round);
369 }
370 std::collections::hash_map::Entry::Vacant(entry) => entry,
371 };
372
373 debug_assert!(glyph_id.0 != 0, "Can't allocate glyph for id 0");
374
375 let uv_rect = self.ab_glyph_font.outline(glyph_id).map(|outline| {
376 let glyph = ab_glyph::Glyph {
377 id: glyph_id,
378 scale: PxScale::from(0.0),
382 position: ab_glyph::Point {
383 x: bin.as_float(),
384 y: 0.0,
385 },
386 };
387 let outlined = OutlinedGlyph::new(
388 glyph,
389 outline,
390 ab_glyph::PxScaleFactor {
391 horizontal: metrics.px_scale_factor,
392 vertical: metrics.px_scale_factor,
393 },
394 );
395 let bb = outlined.px_bounds();
396 let glyph_width = bb.width() as usize;
397 let glyph_height = bb.height() as usize;
398 if glyph_width == 0 || glyph_height == 0 {
399 UvRect::default()
400 } else {
401 let glyph_pos = {
402 let text_alpha_from_coverage = atlas.text_alpha_from_coverage;
403 let (glyph_pos, image) = atlas.allocate((glyph_width, glyph_height));
404 outlined.draw(|x, y, v| {
405 if 0.0 < v {
406 let px = glyph_pos.0 + x as usize;
407 let py = glyph_pos.1 + y as usize;
408 image[(px, py)] = text_alpha_from_coverage.color_from_coverage(v);
409 }
410 });
411 glyph_pos
412 };
413
414 let offset_in_pixels = vec2(bb.min.x, bb.min.y);
415 let offset = offset_in_pixels / metrics.pixels_per_point
416 + metrics.y_offset_in_points * Vec2::Y;
417 UvRect {
418 offset,
419 size: vec2(glyph_width as f32, glyph_height as f32) / metrics.pixels_per_point,
420 min: [glyph_pos.0 as u16, glyph_pos.1 as u16],
421 max: [
422 (glyph_pos.0 + glyph_width) as u16,
423 (glyph_pos.1 + glyph_height) as u16,
424 ],
425 }
426 }
427 });
428 let uv_rect = uv_rect.unwrap_or_default();
429
430 let allocation = GlyphAllocation {
431 id: glyph_id,
432 advance_width_px,
433 uv_rect,
434 };
435 entry.insert(allocation);
436 (allocation, h_pos_round)
437 }
438}
439
440pub struct Font<'a> {
443 pub(super) fonts_by_id: &'a mut nohash_hasher::IntMap<FontFaceKey, FontImpl>,
444 pub(super) cached_family: &'a mut CachedFamily,
445 pub(super) atlas: &'a mut TextureAtlas,
446}
447
448impl Font<'_> {
449 pub fn preload_characters(&mut self, s: &str) {
450 for c in s.chars() {
451 self.glyph_info(c);
452 }
453 }
454
455 pub fn characters(&mut self) -> &BTreeMap<char, Vec<String>> {
457 self.cached_family.characters.get_or_insert_with(|| {
458 let mut characters: BTreeMap<char, Vec<String>> = Default::default();
459 for font_id in &self.cached_family.fonts {
460 let font = self.fonts_by_id.get(font_id).expect("Nonexistent font ID");
461 for chr in font.characters() {
462 characters.entry(chr).or_default().push(font.name.clone());
463 }
464 }
465 characters
466 })
467 }
468
469 pub fn scaled_metrics(&self, pixels_per_point: f32, font_size: f32) -> ScaledMetrics {
470 self.cached_family
471 .fonts
472 .first()
473 .and_then(|key| self.fonts_by_id.get(key))
474 .map(|font_impl| font_impl.scaled_metrics(pixels_per_point, font_size))
475 .unwrap_or_default()
476 }
477
478 pub fn glyph_width(&mut self, c: char, font_size: f32) -> f32 {
480 let (key, glyph_info) = self.glyph_info(c);
481 if let Some(font) = &self.fonts_by_id.get(&key) {
482 glyph_info.advance_width_unscaled.0 * font.ab_glyph_font.px_scale_factor(font_size)
483 } else {
484 0.0
485 }
486 }
487
488 pub fn has_glyph(&mut self, c: char) -> bool {
490 self.glyph_info(c) != self.cached_family.replacement_glyph }
492
493 pub fn has_glyphs(&mut self, s: &str) -> bool {
495 s.chars().all(|c| self.has_glyph(c))
496 }
497
498 pub(crate) fn glyph_info(&mut self, c: char) -> (FontFaceKey, GlyphInfo) {
500 if let Some(font_index_glyph_info) = self.cached_family.glyph_info_cache.get(&c) {
501 return *font_index_glyph_info;
502 }
503
504 let font_index_glyph_info = self
505 .cached_family
506 .glyph_info_no_cache_or_fallback(c, self.fonts_by_id);
507 let font_index_glyph_info =
508 font_index_glyph_info.unwrap_or(self.cached_family.replacement_glyph);
509 self.cached_family
510 .glyph_info_cache
511 .insert(c, font_index_glyph_info);
512 font_index_glyph_info
513 }
514}
515
516#[derive(Clone, Copy, Debug, PartialEq, Default)]
518pub struct ScaledMetrics {
519 pub pixels_per_point: f32,
521
522 pub px_scale_factor: f32,
526
527 pub y_offset_in_points: f32,
529
530 pub ascent: f32,
534
535 pub row_height: f32,
539}
540
541#[inline]
545fn invisible_char(c: char) -> bool {
546 if c == '\r' {
547 return true;
549 }
550
551 matches!(
558 c,
559 '\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}' )
586}
587
588#[inline]
589pub(super) fn is_cjk_ideograph(c: char) -> bool {
590 ('\u{4E00}' <= c && c <= '\u{9FFF}')
591 || ('\u{3400}' <= c && c <= '\u{4DBF}')
592 || ('\u{2B740}' <= c && c <= '\u{2B81F}')
593}
594
595#[inline]
596pub(super) fn is_kana(c: char) -> bool {
597 ('\u{3040}' <= c && c <= '\u{309F}') || ('\u{30A0}' <= c && c <= '\u{30FF}') }
600
601#[inline]
602pub(super) fn is_cjk(c: char) -> bool {
603 is_cjk_ideograph(c) || is_kana(c)
605}
606
607#[inline]
608pub(super) fn is_cjk_break_allowed(c: char) -> bool {
609 !")]ïœăăăăăăăăă'\"ïœ Â»ăœăŸăŒăĄăŁă„ă§ă©ăăŁă„ă§ăźă”ă¶ăăă
ăăăŁăă
ăăăăă°ă±ăČăłăŽă”ă¶ă·ăžăčășă»ăŒăœăŸăżă
ă»âă âă?!âŒâââă»ă:;,ă.".contains(c)
611}