1use font_awl::FontProvider;
2use rapidhash::{HashMapExt, RapidHashMap, fast::RapidHasher};
3use skrifa::MetadataProvider;
4use skrifa::outline::OutlinePen;
5use std::sync::OnceLock;
6
7pub mod fallback;
8pub mod fallback_data;
9pub mod unresolved;
10
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::{
13 collections::{BTreeMap, HashMap, HashSet},
14 hash::{Hash, Hasher},
15 sync::{Arc, Mutex},
16};
17use unicode_segmentation::UnicodeSegmentation;
18
19static FRAME_COUNTER: AtomicU64 = AtomicU64::new(0);
20static FONT_GENERATION: AtomicU64 = AtomicU64::new(0);
21static FALLBACK_DIRTY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
22
23pub fn begin_frame() {
24 FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
25}
26
27pub fn current_frame() -> u64 {
28 FRAME_COUNTER.load(Ordering::Relaxed)
29}
30
31pub fn font_generation() -> u64 {
32 FONT_GENERATION.load(Ordering::Relaxed)
33}
34
35pub fn take_fallback_dirty() -> bool {
36 FALLBACK_DIRTY.swap(false, Ordering::SeqCst)
39}
40
41const GLYPH_CACHE_CAP: usize = 4096;
42const GLYPH_CACHE_BYTES_CAP: usize = 64 * 1024 * 1024;
43const MAX_RASTER_BYTES: usize = 16 * 1024 * 1024;
44const WRAP_CACHE_CAP: usize = 1024;
45const ELLIP_CACHE_CAP: usize = 2048;
46const SHAPE_CACHE_CAP: usize = 1024;
47const LEGACY_SHAPE_CACHE_CAP: usize = 512;
48const RASTER_MIN_PX: f32 = 0.25;
49const RASTER_MAX_PX: f32 = 4096.0;
50const RASTER_QUANTUM: f32 = 1.0 / 4.0;
51const MAX_RASTER_DIMENSION: u32 = 4_096;
52const VERTICAL_METRICS_CACHE_CAP: usize = 256;
53const OUTLINE_CACHE_CAP: usize = 2048;
54
55#[derive(Hash, Eq, PartialEq)]
56struct MetricsCacheKey {
57 text: Arc<str>,
58 px_bits: u32,
59 family: Option<Arc<str>>,
60 font_weight: u16,
61 font_style: u8,
62 letter_spacing_bits: u32,
63 variation: Option<Arc<str>>,
64 generation: u64,
65}
66
67#[derive(Hash, Eq, PartialEq)]
68struct WrapCacheKey {
69 text: Arc<str>,
70 px_bits: u32,
71 max_width_bits: u32,
72 max_lines: Option<usize>,
73 family: Option<Arc<str>>,
74 soft_wrap: bool,
75 font_weight: u16,
76 font_style: u8,
77 letter_spacing_bits: u32,
78 variation: Option<Arc<str>>,
79 generation: u64,
80}
81
82#[derive(Hash, Eq, PartialEq)]
83struct EllipCacheKey {
84 text: Arc<str>,
85 px_bits: u32,
86 max_width_bits: u32,
87 family: Option<Arc<str>>,
88 font_weight: u16,
89 font_style: u8,
90 letter_spacing_bits: u32,
91 variation: Option<Arc<str>>,
92 generation: u64,
93}
94
95static METRICS_LRU: OnceLock<Mutex<Lru<MetricsCacheKey, Arc<TextMetrics>>>> = OnceLock::new();
96fn metrics_cache() -> &'static Mutex<Lru<MetricsCacheKey, Arc<TextMetrics>>> {
97 METRICS_LRU.get_or_init(|| Mutex::new(Lru::new(4096)))
98}
99
100struct LruNode<K, V> {
101 key: Arc<K>,
102 value: V,
103 newer: Option<usize>,
104 older: Option<usize>,
105}
106
107struct Lru<K, V> {
108 map: RapidHashMap<Arc<K>, usize>,
109 nodes: Vec<Option<LruNode<K, V>>>,
110 free: Vec<usize>,
111 head: Option<usize>,
112 tail: Option<usize>,
113 cap: usize,
114 len: usize,
115}
116
117impl<K: std::hash::Hash + Eq, V> Lru<K, V> {
118 fn new(cap: usize) -> Self {
119 Self {
120 map: RapidHashMap::new(),
121 nodes: Vec::new(),
122 free: Vec::new(),
123 head: None,
124 tail: None,
125 cap,
126 len: 0,
127 }
128 }
129
130 fn node(&self, index: usize) -> Option<&LruNode<K, V>> {
131 self.nodes.get(index).and_then(Option::as_ref)
132 }
133
134 fn node_mut(&mut self, index: usize) -> Option<&mut LruNode<K, V>> {
135 self.nodes.get_mut(index).and_then(Option::as_mut)
136 }
137
138 fn unlink(&mut self, index: usize) {
139 let Some((newer, older)) = self.node(index).map(|node| (node.newer, node.older)) else {
140 return;
141 };
142 if let Some(newer) = newer {
143 if let Some(node) = self.node_mut(newer) {
144 node.older = older;
145 }
146 } else {
147 self.head = older;
148 }
149 if let Some(older) = older {
150 if let Some(node) = self.node_mut(older) {
151 node.newer = newer;
152 }
153 } else {
154 self.tail = newer;
155 }
156 if let Some(node) = self.node_mut(index) {
157 node.newer = None;
158 node.older = None;
159 }
160 }
161
162 fn link_head(&mut self, index: usize) {
163 let head = self.head;
164 if let Some(node) = self.node_mut(index) {
165 node.newer = None;
166 node.older = head;
167 }
168 if let Some(head) = self.head
169 && let Some(node) = self.node_mut(head)
170 {
171 node.newer = Some(index);
172 }
173 self.head = Some(index);
174 if self.tail.is_none() {
175 self.tail = Some(index);
176 }
177 }
178
179 fn touch(&mut self, index: usize) {
180 if self.head == Some(index) {
181 return;
182 }
183 self.unlink(index);
184 self.link_head(index);
185 }
186
187 fn get(&mut self, key: &K) -> Option<&V> {
188 let index = *self.map.get(key)?;
189 self.touch(index);
190 self.node(index).map(|node| &node.value)
191 }
192
193 fn get_mut(&mut self, key: &K) -> Option<&mut V> {
194 let index = *self.map.get(key)?;
195 self.touch(index);
196 self.node_mut(index).map(|node| &mut node.value)
197 }
198
199 fn put(&mut self, key: K, value: V) {
200 if let Some(index) = self.map.get(&key).copied() {
201 if let Some(node) = self.node_mut(index) {
202 node.value = value;
203 }
204 self.touch(index);
205 return;
206 }
207 if self.cap == 0 {
208 return;
209 }
210 while self.len >= self.cap
211 && let Some(victim) = self.tail
212 {
213 self.remove_index(victim);
214 }
215 let key = Arc::new(key);
216 let index = if let Some(index) = self.free.pop() {
217 self.nodes[index] = Some(LruNode {
218 key: key.clone(),
219 value,
220 newer: None,
221 older: None,
222 });
223 index
224 } else {
225 let index = self.nodes.len();
226 self.nodes.push(Some(LruNode {
227 key: key.clone(),
228 value,
229 newer: None,
230 older: None,
231 }));
232 index
233 };
234 self.map.insert(key, index);
235 self.link_head(index);
236 self.len += 1;
237 }
238
239 fn remove_index(&mut self, index: usize) {
240 let Some(node) = self.nodes.get_mut(index).and_then(Option::as_mut) else {
241 return;
242 };
243 let key = node.key.clone();
244 self.unlink(index);
245 self.nodes[index] = None;
246 self.map.remove(&key);
247 self.free.push(index);
248 self.len = self.len.saturating_sub(1);
249 }
250
251 fn clear_both(&mut self) {
252 self.map.clear();
253 self.nodes.clear();
254 self.free.clear();
255 self.head = None;
256 self.tail = None;
257 self.len = 0;
258 }
259}
260
261static WRAP_LRU: OnceLock<Mutex<Lru<WrapCacheKey, Arc<(Vec<String>, bool)>>>> = OnceLock::new();
262
263static WRAP_RANGES_LRU: OnceLock<Mutex<Lru<WrapCacheKey, Arc<(Vec<(usize, usize)>, bool)>>>> =
264 OnceLock::new();
265
266static ELLIP_LRU: OnceLock<Mutex<Lru<EllipCacheKey, Arc<String>>>> = OnceLock::new();
267
268fn wrap_cache() -> &'static Mutex<Lru<WrapCacheKey, Arc<(Vec<String>, bool)>>> {
269 WRAP_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
270}
271
272fn wrap_ranges_cache() -> &'static Mutex<Lru<WrapCacheKey, Arc<(Vec<(usize, usize)>, bool)>>> {
273 WRAP_RANGES_LRU.get_or_init(|| Mutex::new(Lru::new(WRAP_CACHE_CAP)))
274}
275
276fn ellip_cache() -> &'static Mutex<Lru<EllipCacheKey, Arc<String>>> {
277 ELLIP_LRU.get_or_init(|| Mutex::new(Lru::new(ELLIP_CACHE_CAP)))
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
281pub struct GlyphKey(pub u64);
282
283#[derive(Clone, Debug, PartialEq, Eq, Hash)]
288pub struct CacheKey {
289 pub font_id: u64,
290 pub glyph_id: u32,
291 pub font_size_bits: u32,
292 pub variation: Option<Arc<str>>,
293}
294
295pub fn canonical_variation(settings: Option<&str>) -> Option<Arc<str>> {
300 let settings = settings?;
301 let mut parsed: Vec<(u32, u32)> = parley::setting::FontVariation::parse_css_list(settings)
302 .filter_map(Result::ok)
303 .map(|setting| {
304 (
305 u32::from_be_bytes(setting.tag.to_bytes()),
306 setting.value.to_bits(),
307 )
308 })
309 .collect();
310 if parsed.is_empty() {
311 return None;
312 }
313 parsed.sort_by_key(|(tag, _)| *tag);
316 let mut merged: Vec<(u32, u32)> = Vec::with_capacity(parsed.len());
317 for entry in parsed {
318 match merged.last_mut() {
319 Some(last) if last.0 == entry.0 => *last = entry,
320 _ => merged.push(entry),
321 }
322 }
323 let mut canonical = String::with_capacity(merged.len() * 16);
324 for (tag, bits) in merged {
325 canonical.push_str(&format!("{tag:08x}{bits:08x}"));
326 }
327 Some(Arc::from(canonical.as_str()))
328}
329
330const VARIATION_ENTRY_CHARS: usize = 16;
333
334type VariationKey = (u64, u32, u32, Option<Arc<str>>);
337
338type OutlineKey = (u64, u32, Option<Arc<str>>);
341
342pub(crate) fn decode_variation(canonical: Option<&str>) -> Vec<(skrifa::Tag, f32)> {
349 let Some(canonical) = canonical else {
350 return Vec::new();
351 };
352 let bytes = canonical.as_bytes();
353 let mut out = Vec::with_capacity(bytes.len() / VARIATION_ENTRY_CHARS);
354 for chunk in bytes.chunks(VARIATION_ENTRY_CHARS) {
355 if chunk.len() < VARIATION_ENTRY_CHARS {
356 break;
357 }
358 let Ok(tag_text) = std::str::from_utf8(&chunk[..8]) else {
361 continue;
362 };
363 let Ok(tag) = u32::from_str_radix(tag_text, 16) else {
364 continue;
365 };
366 let Ok(value_text) = std::str::from_utf8(&chunk[8..VARIATION_ENTRY_CHARS]) else {
367 continue;
368 };
369 let Ok(bits) = u32::from_str_radix(value_text, 16) else {
370 continue;
371 };
372 out.push((
373 skrifa::Tag::from_be_bytes(tag.to_be_bytes()),
374 f32::from_bits(bits),
375 ));
376 }
377 out
378}
379
380fn swash_settings(canonical: Option<&str>) -> Vec<swash::Setting<f32>> {
382 decode_variation(canonical)
383 .into_iter()
384 .map(|(tag, value)| swash::Setting {
385 tag: u32::from_be_bytes(tag.to_be_bytes()),
386 value,
387 })
388 .collect()
389}
390
391static GLYPH_ID_WARNED: OnceLock<Mutex<std::collections::HashSet<(u64, u32)>>> = OnceLock::new();
392
393fn swash_glyph_id(font_id: u64, glyph_id: u32) -> u16 {
400 match u16::try_from(glyph_id) {
401 Ok(v) => v,
402 Err(_) => {
403 let warned =
404 GLYPH_ID_WARNED.get_or_init(|| Mutex::new(std::collections::HashSet::new()));
405 let is_new = warned
406 .lock()
407 .map(|mut g| g.insert((font_id, glyph_id)))
408 .unwrap_or(false);
409 if is_new {
410 log::warn!(
411 "glyph id {} (font {}) exceeds u16::MAX; falling back to .notdef (0)",
412 glyph_id,
413 font_id
414 );
415 }
416 0
417 }
418 }
419}
420
421#[derive(Clone, Debug)]
423pub enum Command {
424 MoveTo(f32, f32),
425 LineTo(f32, f32),
426 QuadTo(f32, f32, f32, f32),
427 CurveTo(f32, f32, f32, f32, f32, f32),
428 Close,
429}
430
431#[derive(Clone)]
432pub struct ShapedGlyph {
433 pub key: GlyphKey,
434 pub cache_key: CacheKey,
435 pub font_id: u64,
436 pub glyph_id: u32,
437 pub px: f32,
438 pub x: f32,
439 pub y: f32,
440 pub w: f32,
441 pub h: f32,
442 pub bearing_x: f32,
443 pub bearing_y: f32,
444 pub advance: f32,
445 pub rasterized: bool,
446}
447
448pub use swash::scale::image::Content as SwashContent;
449
450pub struct GlyphBitmap {
451 pub key: GlyphKey,
452 pub w: u32,
453 pub h: u32,
454 pub content: SwashContent,
455 pub data: Vec<u8>,
456}
457
458struct FontRecord {
459 id: u64,
460 data_bytes: Arc<[u8]>,
461 face_index: u32,
462}
463
464struct Engine {
465 font_cx: parley::FontContext,
466 layout_cx: parley::LayoutContext<()>,
467 swash_cx: swash::scale::ScaleContext,
468 key_map: RapidHashMap<GlyphKey, (u64, u32)>,
469 font_registry: Vec<FontRecord>,
470 font_registry_index: RapidHashMap<(u64, u32), u64>,
471 next_font_id: u64,
472 glyph_cache:
475 RapidHashMap<VariationKey, (u32, u32, i32, i32, swash::scale::image::Content, Vec<u8>)>,
476 glyph_cache_bytes: usize,
477 outline_cache: Lru<OutlineKey, Arc<[Command]>>,
478 ascent_cache: RapidHashMap<(Option<Arc<str>>, u16, u32, u64), (f32, f32)>,
481}
482
483fn normalized_raster_px(px: f32) -> Option<f32> {
484 if !px.is_finite() || px <= 0.0 {
485 return None;
486 }
487 let clamped = px.clamp(RASTER_MIN_PX, RASTER_MAX_PX);
488 let quantized = (clamped / RASTER_QUANTUM).round() * RASTER_QUANTUM;
489 Some(quantized.clamp(RASTER_MIN_PX, RASTER_MAX_PX))
490}
491
492impl Engine {
493 fn ensure_font(&mut self, fd: &parley::FontData) -> u64 {
494 let blob_id = fd.data.id();
495 let lookup_key = (blob_id, fd.index);
496 if let Some(id) = self.font_registry_index.get(&lookup_key).copied() {
497 return id;
498 }
499 if let Some(existing) = self.font_registry.iter().find(|record| {
500 record.face_index == fd.index && record.data_bytes.as_ref() == fd.data.as_ref()
501 }) {
502 self.font_registry_index.insert(lookup_key, existing.id);
503 return existing.id;
504 }
505 let id = self.next_font_id;
506 self.next_font_id += 1;
507 let bytes: Arc<[u8]> = Arc::from(fd.data.as_ref());
508 log::debug!("[font] register id={} len={}", id, bytes.len());
509 self.font_registry.push(FontRecord {
510 id,
511 data_bytes: bytes,
512 face_index: fd.index,
513 });
514 self.font_registry_index.insert(lookup_key, id);
515 id
516 }
517
518 fn trim_glyph_cache(&mut self) {
519 while self.glyph_cache.len() > GLYPH_CACHE_CAP
520 || self.glyph_cache_bytes > GLYPH_CACHE_BYTES_CAP
521 {
522 let Some((key, value)) = self.glyph_cache.iter().next() else {
523 break;
524 };
525 let key = key.clone();
526 let bytes = value.5.len();
527 self.glyph_cache.remove(&key);
528 self.glyph_cache_bytes = self.glyph_cache_bytes.saturating_sub(bytes);
529 }
530 }
531
532 fn resolve_vertical_metrics(
536 &mut self,
537 font_family: Option<&str>,
538 font_weight: u16,
539 px: f32,
540 ) -> (f32, f32) {
541 let px = px.clamp(RASTER_MIN_PX, RASTER_MAX_PX);
542 let mut candidates: Vec<&str> = Vec::new();
543 match font_family {
544 Some("monospace") => candidates.push("JetBrains Mono"),
545 Some("sans-serif") => candidates.push("Open Sans"),
546 Some("emoji") => candidates.push("Noto Color Emoji"),
547 Some(other) => candidates.push(other),
548 None => {}
549 }
550 candidates.push("Open Sans");
551 for name in candidates {
552 if let Some(m) = self.metrics_for_family(name, font_weight, px) {
553 return m;
554 }
555 }
556 (px * 0.8, px * 0.2)
557 }
558
559 fn metrics_for_family(&mut self, name: &str, font_weight: u16, px: f32) -> Option<(f32, f32)> {
562 let info = self.font_cx.collection.family_by_name(name)?;
563 let target = font_weight as f32;
564 let mut best: Option<(f32, Arc<[u8]>, u32)> = None;
565 for font in info.fonts() {
566 let dist = (font.weight().value() - target).abs();
567 if best.as_ref().is_some_and(|(bd, _, _)| *bd <= dist) {
568 continue;
569 }
570 let bytes: Arc<[u8]> = match font.source().kind() {
571 parley::fontique::SourceKind::Memory(blob) => Arc::from(blob.as_ref()),
572 #[cfg(not(target_arch = "wasm32"))]
573 parley::fontique::SourceKind::Path(path) => Arc::from(std::fs::read(path).ok()?),
574 #[cfg(target_arch = "wasm32")]
575 parley::fontique::SourceKind::Path(_) => continue,
576 };
577 best = Some((dist, bytes, font.index()));
578 }
579 let (_, bytes, index) = best?;
580 let font = skrifa::FontRef::from_index(bytes.as_ref(), index).ok()?;
581 let metrics = font.metrics(
582 skrifa::instance::Size::new(px),
583 skrifa::instance::LocationRef::default(),
584 );
585 let limit = px * 8.0;
586 let ascent = if metrics.ascent.is_finite() {
587 metrics.ascent.max(0.0).min(limit)
588 } else {
589 0.0
590 };
591 let descent = if metrics.descent.is_finite() {
592 metrics.descent.abs().max(0.0).min(limit)
593 } else {
594 0.0
595 };
596 Some((ascent, descent))
597 }
598
599 fn raster_placement(
600 &mut self,
601 font_id: u64,
602 glyph_id: u32,
603 px: f32,
604 variation: Option<Arc<str>>,
605 ) -> Option<(f32, f32, f32, f32)> {
606 use swash::scale::{Render, Source, StrikeWith};
607 let px = normalized_raster_px(px)?;
608 let cache_key = (font_id, glyph_id, px.to_bits(), variation.clone());
609 if let Some(cached) = self.glyph_cache.get(&cache_key) {
610 log::debug!(
611 "[raster_placement] HIT fid={} gid={} px={} => {}x{} {}x{}",
612 font_id,
613 glyph_id,
614 px,
615 cached.0,
616 cached.1,
617 cached.2,
618 cached.3
619 );
620 return Some((
621 cached.0 as f32,
622 cached.1 as f32,
623 cached.2 as f32,
624 cached.3 as f32,
625 ));
626 }
627 let (data_bytes, face_index) = {
628 let record = self.font_registry.iter().find(|r| r.id == font_id)?;
629 (record.data_bytes.clone(), record.face_index)
630 };
631 let face_index = usize::try_from(face_index).ok()?;
632 let font = swash::FontRef::from_index(data_bytes.as_ref(), face_index)?;
633 let mut builder = self.swash_cx.builder(font).size(px).hint(true);
634 let settings = swash_settings(variation.as_deref());
635 if !settings.is_empty() {
636 builder = builder.variations(settings);
637 }
638 let mut scaler = builder.build();
639 let image = Render::new(&[
640 Source::Outline,
641 Source::ColorBitmap(StrikeWith::BestFit),
642 Source::ColorOutline(0),
643 ])
644 .render(&mut scaler, swash_glyph_id(font_id, glyph_id))?;
645 if image.placement.width > MAX_RASTER_DIMENSION
646 || image.placement.height > MAX_RASTER_DIMENSION
647 || image.data.len() > MAX_RASTER_BYTES
648 {
649 return None;
650 }
651 log::debug!(
652 "[raster_placement] MISS fid={} gid={} px={} => {}x{} {}x{}",
653 font_id,
654 glyph_id,
655 px,
656 image.placement.width,
657 image.placement.height,
658 image.placement.left,
659 image.placement.top
660 );
661 let width = image.placement.width;
662 let height = image.placement.height;
663 let left = image.placement.left;
664 let top = image.placement.top;
665 let data_len = image.data.len();
666 let previous = self.glyph_cache.insert(
667 cache_key,
668 (width, height, left, top, image.content, image.data),
669 );
670 if let Some(previous) = previous {
671 self.glyph_cache_bytes = self.glyph_cache_bytes.saturating_sub(previous.5.len());
672 }
673 self.glyph_cache_bytes = self.glyph_cache_bytes.saturating_add(data_len);
674 self.trim_glyph_cache();
675 Some((width as f32, height as f32, left as f32, top as f32))
676 }
677}
678
679static ENGINE: OnceLock<Mutex<Engine>> = OnceLock::new();
680
681pub static FONT_PROVIDER: OnceLock<Mutex<font_awl::Provider>> = OnceLock::new();
682#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
683static RETAINED_FONT_DATA: OnceLock<Mutex<Vec<Arc<[u8]>>>> = OnceLock::new();
684
685#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
686static WASM_FONT_CONTEXT_READY: std::sync::atomic::AtomicBool =
687 std::sync::atomic::AtomicBool::new(false);
688
689#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
690static WASM_FONT_INIT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
691
692#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
693static WEB_FONTS_LOADED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
694
695fn append_generic_family_once(
696 collection: &mut parley::fontique::Collection,
697 generic: parley::fontique::GenericFamily,
698 id: parley::fontique::FamilyId,
699) {
700 let mut families: Vec<parley::fontique::FamilyId> =
701 collection.generic_families(generic).collect();
702 if !families.contains(&id) {
703 families.push(id);
704 collection.set_generic_families(generic, families.into_iter());
705 }
706}
707
708fn configure_collection(collection: &mut parley::fontique::Collection) {
709 for name in [
710 "Noto Sans Symbols",
711 "Noto Sans Symbols 2",
712 "Material Symbols Outlined",
713 ] {
714 if let Some(info) = collection.family_by_name(name) {
715 append_generic_family_once(
716 collection,
717 parley::fontique::GenericFamily::SansSerif,
718 info.id(),
719 );
720 }
721 }
722 if let Some(info) = collection.family_by_name("Noto Color Emoji") {
723 append_generic_family_once(
724 collection,
725 parley::fontique::GenericFamily::Emoji,
726 info.id(),
727 );
728 }
729}
730
731fn font_blob(bytes: Arc<[u8]>) -> parley::fontique::Blob<u8> {
732 let data: Arc<dyn AsRef<[u8]> + Send + Sync> = Arc::new(bytes);
733 parley::fontique::Blob::new(data)
734}
735
736fn register_asset_if_missing(provider: &mut font_awl::Provider, bytes: &[u8]) {
737 if collection_font_data_families(provider.collection_mut(), bytes).is_empty() {
738 let blob = font_blob(Arc::from(bytes));
739 let families = provider.collection_mut().register_fonts(blob, None);
740 append_registered_families(provider.collection_mut(), &families);
741 }
742}
743
744fn init_provider_sync() -> font_awl::Provider {
745 let mut provider = font_awl::Provider::new();
746 provider.load_bundled_fonts();
747 static MATERIAL_SYMBOLS_TTF: &[u8] = include_bytes!("assets/MaterialSymbolsOutlined.ttf");
748 register_asset_if_missing(&mut provider, MATERIAL_SYMBOLS_TTF);
749 #[cfg(all(not(target_arch = "wasm32"), feature = "system-fonts"))]
754 if let Err(e) = provider.load_system_fonts_best_effort() {
755 log::warn!("font-awl: failed to load system fonts: {e}");
756 }
757 configure_collection(provider.collection_mut());
758 provider
759}
760
761fn provider() -> &'static Mutex<font_awl::Provider> {
762 FONT_PROVIDER.get_or_init(|| Mutex::new(init_provider_sync()))
763}
764
765fn init_engine_sync() -> Engine {
766 let mut font_cx = provider().lock().unwrap().new_parley_context();
767 configure_collection(&mut font_cx.collection);
768
769 Engine {
770 font_cx,
771 layout_cx: parley::LayoutContext::new(),
772 swash_cx: swash::scale::ScaleContext::new(),
773 key_map: RapidHashMap::new(),
774 font_registry: Vec::new(),
775 font_registry_index: RapidHashMap::new(),
776 next_font_id: 1,
777 glyph_cache: RapidHashMap::new(),
778 glyph_cache_bytes: 0,
779 outline_cache: Lru::new(OUTLINE_CACHE_CAP),
780 ascent_cache: RapidHashMap::new(),
781 }
782}
783
784#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
789pub fn web_fonts_loaded() -> bool {
790 WEB_FONTS_LOADED.load(Ordering::Acquire)
791}
792
793#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
794pub async fn init_fonts_wasm() {
795 if WEB_FONTS_LOADED.load(Ordering::Acquire) {
796 return;
797 }
798 if WASM_FONT_INIT
799 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
800 .is_err()
801 {
802 return;
803 }
804
805 let startup_attempt = !WASM_FONT_CONTEXT_READY.load(Ordering::Acquire);
806
807 let mut candidate = init_provider_sync();
808 let web_fonts = candidate.load_web_fonts().await;
809 if let Err(error) = &web_fonts {
810 if startup_attempt {
811 log::info!("font-awl: local fonts deferred to a user gesture: {error}");
812 } else {
813 log::warn!("font-awl: local font access failed: {error} (bundled fonts kept)");
814 }
815 }
816 restore_retained_font_data(candidate.collection_mut());
817 configure_collection(candidate.collection_mut());
818 {
819 let mut p = provider().lock().unwrap();
820 *p = candidate;
821 }
822 let font_cx = provider().lock().unwrap().new_parley_context();
823 if let Some(eng) = ENGINE.get() {
824 let mut eng = eng.lock().unwrap();
825 eng.font_cx = font_cx;
826 clear_caches_for_fallback_in(&mut eng);
827 } else {
828 drop(font_cx);
829 }
830 if web_fonts.is_ok() {
831 WEB_FONTS_LOADED.store(true, Ordering::Release);
832 }
833 WASM_FONT_CONTEXT_READY.store(true, Ordering::Release);
834 WASM_FONT_INIT.store(false, Ordering::Release);
835}
836
837fn engine() -> &'static Mutex<Engine> {
838 ENGINE.get_or_init(|| Mutex::new(init_engine_sync()))
839}
840
841pub fn register_font_data(bytes: &[u8]) {
842 let _ = register_font_data_if_usable(bytes);
843}
844
845fn font_source_bytes(font: &parley::fontique::FontInfo) -> Option<&[u8]> {
846 match font.source().kind() {
847 parley::fontique::SourceKind::Memory(blob) => Some(blob.as_ref()),
848 parley::fontique::SourceKind::Path(_) => None,
849 }
850}
851
852fn collection_font_data_families(
853 collection: &mut parley::fontique::Collection,
854 bytes: &[u8],
855) -> Vec<parley::fontique::FamilyId> {
856 let mut names: Vec<String> = collection.family_names().map(str::to_owned).collect();
857 names.sort_unstable();
858 let mut result = Vec::new();
859 for name in names {
860 let Some(info) = collection.family_by_name(&name) else {
861 continue;
862 };
863 if info
864 .fonts()
865 .iter()
866 .any(|font| font_source_bytes(font).is_some_and(|source| source == bytes))
867 {
868 result.push(info.id());
869 }
870 }
871 result
872}
873
874fn append_registered_families(
875 collection: &mut parley::fontique::Collection,
876 families: &[(parley::fontique::FamilyId, Vec<parley::fontique::FontInfo>)],
877) {
878 let mut ids: Vec<parley::fontique::FamilyId> = families.iter().map(|(id, _)| *id).collect();
879 ids.sort_unstable();
880 for id in ids {
881 let Some(name) = collection.family_name(id).map(str::to_owned) else {
882 continue;
883 };
884 let generic = if name.starts_with("Noto Color Emoji") {
885 parley::fontique::GenericFamily::Emoji
886 } else {
887 parley::fontique::GenericFamily::SansSerif
888 };
889 append_generic_family_once(collection, generic, id);
890 }
891}
892
893#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
894fn retain_font_data(bytes: Arc<[u8]>) {
895 let retained = RETAINED_FONT_DATA.get_or_init(|| Mutex::new(Vec::new()));
896 let Ok(mut retained) = retained.lock() else {
897 return;
898 };
899 if retained
900 .iter()
901 .all(|existing| existing.as_ref() != bytes.as_ref())
902 {
903 retained.push(bytes);
904 }
905}
906
907#[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
908fn restore_retained_font_data(collection: &mut parley::fontique::Collection) {
909 let retained = RETAINED_FONT_DATA.get().and_then(|retained| {
910 retained
911 .lock()
912 .ok()
913 .map(|retained| retained.as_slice().to_vec())
914 });
915 let Some(retained) = retained else {
916 return;
917 };
918 for bytes in retained {
919 if collection_font_data_families(collection, bytes.as_ref()).is_empty() {
920 let blob = font_blob(bytes);
921 let families = collection.register_fonts(blob, None);
922 append_registered_families(collection, &families);
923 }
924 }
925}
926
927pub(crate) fn register_font_data_if_usable(bytes: &[u8]) -> bool {
928 #[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
929 let mut retained_bytes = None;
930 let (font_cx, newly_registered) = {
931 let mut p = provider().lock().unwrap();
932 let existing = collection_font_data_families(p.collection_mut(), bytes);
933 if !existing.is_empty() {
934 for id in existing {
935 let is_emoji = p
936 .collection_mut()
937 .family_name(id)
938 .is_some_and(|name| name.starts_with("Noto Color Emoji"));
939 let generic = if is_emoji {
940 parley::fontique::GenericFamily::Emoji
941 } else {
942 parley::fontique::GenericFamily::SansSerif
943 };
944 append_generic_family_once(p.collection_mut(), generic, id);
945 }
946 (p.new_parley_context(), false)
947 } else {
948 let shared_bytes: Arc<[u8]> = Arc::from(bytes);
949 let blob = font_blob(shared_bytes.clone());
950 let families = p.collection_mut().register_fonts(blob, None);
951 if families.is_empty() {
952 return false;
953 }
954 #[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
955 {
956 retained_bytes = Some(shared_bytes);
957 }
958 append_registered_families(p.collection_mut(), &families);
959 configure_collection(p.collection_mut());
960 (p.new_parley_context(), true)
961 }
962 };
963
964 if newly_registered {
965 #[cfg(all(target_arch = "wasm32", feature = "local-fonts"))]
966 if let Some(bytes) = retained_bytes {
967 retain_font_data(bytes);
968 }
969 let mut eng = engine().lock().unwrap();
970 eng.font_cx = font_cx;
971 clear_caches_for_fallback_in(&mut eng);
972 }
973
974 true
975}
976
977pub(crate) fn clear_caches_for_fallback_in(eng: &mut Engine) {
978 clear_lru_caches();
979 eng.key_map.clear();
980 eng.font_registry.clear();
981 eng.font_registry_index.clear();
982 eng.glyph_cache.clear();
983 eng.glyph_cache_bytes = 0;
984 eng.outline_cache.clear_both();
985 eng.ascent_cache.clear();
986 bump_frame_for_fallback();
987}
988
989fn clear_lru_caches() {
990 if let Some(c) = METRICS_LRU.get()
991 && let Ok(mut g) = c.lock()
992 {
993 g.clear_both();
994 }
995 if let Some(c) = WRAP_LRU.get()
996 && let Ok(mut g) = c.lock()
997 {
998 g.clear_both();
999 }
1000 if let Some(c) = WRAP_RANGES_LRU.get()
1001 && let Ok(mut g) = c.lock()
1002 {
1003 g.clear_both();
1004 }
1005 if let Some(c) = ELLIP_LRU.get()
1006 && let Ok(mut g) = c.lock()
1007 {
1008 g.clear_both();
1009 }
1010 if let Some(c) = SHAPED_LRU.get()
1011 && let Ok(mut g) = c.lock()
1012 {
1013 g.clear_both();
1014 }
1015 if let Some(c) = LEGACY_SHAPED_LRU.get()
1016 && let Ok(mut g) = c.lock()
1017 {
1018 g.clear_both();
1019 }
1020}
1021
1022pub(crate) fn bump_frame_for_fallback() {
1023 FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
1024 FONT_GENERATION.fetch_add(1, Ordering::Relaxed);
1025 FALLBACK_DIRTY.store(true, Ordering::Relaxed);
1026}
1027
1028#[cfg(target_arch = "wasm32")]
1029pub fn ensure_web_fallback_initialized() {
1030 crate::fallback::wasm_fallback::ensure_fallback_initialized();
1031}
1032
1033#[cfg(not(target_arch = "wasm32"))]
1034pub fn ensure_web_fallback_initialized() {}
1035
1036pub fn load_font_file(path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
1040 let bytes = std::fs::read(path)?;
1041 register_font_data(&bytes);
1042 Ok(())
1043}
1044
1045pub fn primary_font_vertical_metrics(
1053 font_family: Option<&str>,
1054 font_weight: u16,
1055 px: f32,
1056) -> (f32, f32) {
1057 let Some(px) = normalized_raster_px(px) else {
1058 return (0.0, 0.0);
1059 };
1060 let key = (
1061 font_family.map(Arc::from),
1062 font_weight,
1063 px.to_bits(),
1064 font_generation(),
1065 );
1066 let mut eng = engine().lock().unwrap();
1067 if let Some(&m) = eng.ascent_cache.get(&key) {
1068 return m;
1069 }
1070 let m = eng.resolve_vertical_metrics(font_family, font_weight, px);
1071 if m.0.is_finite() && m.1.is_finite() && eng.ascent_cache.len() >= VERTICAL_METRICS_CACHE_CAP {
1072 eng.ascent_cache.clear();
1073 }
1074 eng.ascent_cache.insert(key, m);
1075 m
1076}
1077
1078pub fn font_family_name(bytes: &[u8]) -> Option<String> {
1083 use skrifa::string::StringId;
1084
1085 for face_index in 0..4096u32 {
1086 let Ok(font) = skrifa::FontRef::from_index(bytes, face_index) else {
1087 break;
1088 };
1089 let name = font
1090 .localized_strings(StringId::TYPOGRAPHIC_FAMILY_NAME)
1091 .english_or_first()
1092 .map(|s| s.to_string())
1093 .or_else(|| {
1094 font.localized_strings(StringId::FAMILY_NAME)
1095 .english_or_first()
1096 .map(|s| s.to_string())
1097 });
1098 if name.is_some() {
1099 return name;
1100 }
1101 }
1102 None
1103}
1104
1105fn key_from_pair(font_id: u64, glyph_id: u32) -> GlyphKey {
1106 let mut h = RapidHasher::default();
1107 font_id.hash(&mut h);
1108 glyph_id.hash(&mut h);
1109 GlyphKey(h.finish())
1110}
1111
1112#[cfg(target_arch = "wasm32")]
1113fn collect_unresolved_codepoints(layout: &parley::Layout<()>, text: &str) -> Vec<u32> {
1114 use parley::layout::PositionedLayoutItem;
1115 let mut out = Vec::new();
1116 let mut total_glyphs: usize = 0;
1117 for line in layout.lines() {
1118 for item in line.items() {
1119 let PositionedLayoutItem::GlyphRun(glyph_run) = item else {
1120 continue;
1121 };
1122 let run = glyph_run.run();
1123 for cluster in run.clusters() {
1125 let mut glyph_count = 0usize;
1126 let mut has_missing = false;
1127 for glyph in cluster.glyphs() {
1128 glyph_count += 1;
1129 has_missing |= glyph.id == 0;
1130 }
1131 total_glyphs += glyph_count;
1132 if has_missing {
1133 let range = cluster.text_range();
1134 let end = range.end.min(text.len());
1136 let start = range.start.min(end);
1137 if let Some(slice) = text.get(start..end) {
1138 for ch in slice.chars() {
1139 out.push(ch as u32);
1140 }
1141 }
1142 if range.start == range.end {
1144 }
1146 }
1147 }
1148 }
1149 }
1150 if out.is_empty() && !text.is_empty() && total_glyphs == 0 {
1151 if text.chars().any(|c| !c.is_whitespace()) {
1152 for ch in text.chars() {
1153 if !ch.is_whitespace() && ch != '\n' && ch != '\r' && ch != '\t' {
1154 out.push(ch as u32);
1155 }
1156 }
1157 }
1158 }
1159 out
1160}
1161
1162fn push_font_family<'a>(
1163 builder: &mut parley::RangedBuilder<'a, ()>,
1164 family: Option<&str>,
1165 text_len: usize,
1166) {
1167 use parley::style::{FontFamilyName, GenericFamily};
1168
1169 let mut names = Vec::with_capacity(10);
1170 match family {
1171 Some("monospace") => {
1172 names.push(FontFamilyName::named("JetBrains Mono"));
1173 names.push(GenericFamily::Monospace.into());
1174 }
1175 Some("sans-serif") => names.push(FontFamilyName::named("Open Sans")),
1176 Some("emoji") => names.push(FontFamilyName::named("Noto Color Emoji")),
1177 Some("serif") => names.push(GenericFamily::Serif.into()),
1178 Some("cursive") => names.push(GenericFamily::Cursive.into()),
1179 Some("fantasy") => names.push(GenericFamily::Fantasy.into()),
1180 Some("system-ui") => names.push(GenericFamily::SystemUi.into()),
1181 Some("math") => names.push(GenericFamily::Math.into()),
1182 Some(family) => names.push(FontFamilyName::named(family)),
1183 None => {}
1184 }
1185 names.push(GenericFamily::SansSerif.into());
1186 names.push(GenericFamily::Emoji.into());
1187 names.push(FontFamilyName::named("Noto Color Emoji"));
1188 names.push(FontFamilyName::named("Noto Sans Symbols 2"));
1189 names.push(FontFamilyName::named("Noto Sans Symbols2"));
1190 names.push(FontFamilyName::named("Noto Sans Symbols"));
1191 names.push(FontFamilyName::named("Material Symbols Outlined"));
1192 builder.push(names.as_slice(), 0..text_len);
1193}
1194
1195fn build_layout(
1196 eng: &mut Engine,
1197 text: &str,
1198 px: f32,
1199 line_height_ratio: f32,
1200 font_family: Option<&str>,
1201 font_weight: u16,
1202 font_style: u8,
1203 letter_spacing: f32,
1204 font_variation_settings: Option<&str>,
1205) -> parley::Layout<()> {
1206 use parley::FontWeight;
1207 use parley::style::StyleProperty;
1208
1209 let mut builder = eng
1210 .layout_cx
1211 .ranged_builder(&mut eng.font_cx, text, 1.0, true);
1212 builder.push_default(StyleProperty::FontSize(px));
1213 if line_height_ratio > 0.0 {
1214 builder.push_default(StyleProperty::LineHeight(
1215 parley::LineHeight::FontSizeRelative(line_height_ratio),
1216 ));
1217 }
1218 builder.push_default(StyleProperty::FontWeight(FontWeight::new(
1219 font_weight as f32,
1220 )));
1221 builder.push_default(StyleProperty::FontStyle(match font_style {
1222 1 => parley::FontStyle::Italic,
1223 _ => parley::FontStyle::Normal,
1224 }));
1225 builder.push_default(StyleProperty::LetterSpacing(letter_spacing));
1226 if let Some(settings) = font_variation_settings {
1227 builder.push_default(StyleProperty::FontVariations(
1228 parley::style::FontVariations::from(settings),
1229 ));
1230 }
1231 push_font_family(&mut builder, font_family, text.len());
1232 let mut layout = builder.build(text);
1233 layout.break_all_lines(None);
1234 layout.align(
1235 parley::Alignment::Start,
1236 parley::AlignmentOptions::default(),
1237 );
1238 layout
1239}
1240
1241#[cfg(target_arch = "wasm32")]
1242fn report_unresolved_codepoints(layout: &parley::Layout<()>, text: &str) {
1243 let unresolved = collect_unresolved_codepoints(layout, text);
1244 if unresolved.is_empty() {
1245 return;
1246 }
1247 let reg = crate::unresolved::web_unresolved_registry();
1248 if unresolved.iter().any(|cp| !reg.contains(*cp)) {
1249 crate::fallback::wasm_fallback::ensure_fallback_initialized();
1250 reg.add_unresolved_vec(unresolved);
1251 }
1252}
1253
1254#[cfg(not(target_arch = "wasm32"))]
1255fn report_unresolved_codepoints(_layout: &parley::Layout<()>, _text: &str) {}
1256
1257fn collect_shaped_layout(
1258 eng: &mut Engine,
1259 layout: parley::Layout<()>,
1260 px: f32,
1261 collect_runs: bool,
1262 variation: Option<Arc<str>>,
1263) -> (Vec<ShapedGlyph>, Vec<ShapedRun>) {
1264 use parley::layout::PositionedLayoutItem;
1265
1266 let raster_px = normalized_raster_px(px).unwrap_or(0.0);
1267 let mut glyphs = Vec::new();
1268 let mut runs = Vec::new();
1269 for line in layout.lines() {
1270 for item in line.items() {
1271 let PositionedLayoutItem::GlyphRun(glyph_run) = item else {
1272 continue;
1273 };
1274 let run = glyph_run.run();
1275 if collect_runs {
1276 runs.push(ShapedRun {
1277 text_range: run.text_range(),
1278 rtl: run.is_rtl(),
1279 synthesis: run.synthesis(),
1280 });
1281 }
1282 let font_data = run.font();
1283 let fid = eng.ensure_font(font_data);
1284 let run_cache_key = CacheKey {
1285 font_id: fid,
1286 glyph_id: 0,
1287 font_size_bits: raster_px.to_bits(),
1288 variation: variation.clone(),
1289 };
1290 log::debug!(
1291 "[shape] run: fid={} font_data_len={}",
1292 fid,
1293 font_data.data.as_ref().len()
1294 );
1295 for glyph in glyph_run.positioned_glyphs() {
1296 let gid = glyph.id;
1297 let key = key_from_pair(fid, gid);
1298 eng.key_map.insert(key, (fid, gid));
1299 let cache_key = CacheKey {
1300 glyph_id: gid,
1301 ..run_cache_key.clone()
1302 };
1303 glyphs.push(ShapedGlyph {
1304 key,
1305 cache_key,
1306 font_id: fid,
1307 glyph_id: gid,
1308 px,
1309 x: glyph.x,
1310 y: glyph.y,
1311 w: 0.0,
1312 h: 0.0,
1313 bearing_x: 0.0,
1314 bearing_y: 0.0,
1315 advance: glyph.advance,
1316 rasterized: false,
1317 });
1318 }
1319 }
1320 }
1321 (glyphs, runs)
1322}
1323
1324#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
1325pub enum TextDirection {
1326 #[default]
1327 Auto,
1328 Ltr,
1329 Rtl,
1330}
1331
1332#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
1333pub enum FontSynthesis {
1334 #[default]
1335 Unspecified,
1336 None,
1337 Weight,
1338 Style,
1339 All,
1340}
1341
1342#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1343pub enum ShapeCapabilityError {
1344 RequestedDirectionMismatch,
1345 SynthesisModeUnsupported,
1346}
1347
1348#[derive(Clone, Copy, Debug)]
1349pub struct ShapeOptions<'a> {
1350 pub font_family: Option<&'a str>,
1351 pub font_weight: u16,
1352 pub font_style: u8,
1353 pub letter_spacing: f32,
1354 pub font_variation_settings: Option<&'a str>,
1355 pub text_direction: TextDirection,
1356 pub font_synthesis: FontSynthesis,
1357}
1358
1359impl Default for ShapeOptions<'_> {
1360 fn default() -> Self {
1361 Self {
1362 font_family: None,
1363 font_weight: 400,
1364 font_style: 0,
1365 letter_spacing: 0.0,
1366 font_variation_settings: None,
1367 text_direction: TextDirection::Auto,
1368 font_synthesis: FontSynthesis::Unspecified,
1369 }
1370 }
1371}
1372
1373#[derive(Clone)]
1374pub struct ShapedRun {
1375 pub text_range: std::ops::Range<usize>,
1376 pub rtl: bool,
1377 pub synthesis: parley::fontique::Synthesis,
1378}
1379
1380#[derive(Clone)]
1381pub struct ShapedText {
1382 pub glyphs: Vec<ShapedGlyph>,
1383 pub requested_text_direction: TextDirection,
1384 pub resolved_text_direction: TextDirection,
1385 pub runs: Vec<ShapedRun>,
1386}
1387
1388#[derive(Clone)]
1389pub struct SharedShapedText {
1390 pub glyphs: Arc<[ShapedGlyph]>,
1391 pub requested_text_direction: TextDirection,
1392 pub resolved_text_direction: TextDirection,
1393 pub runs: Arc<[ShapedRun]>,
1394}
1395
1396pub type CachedShapedText = SharedShapedText;
1397pub type VectorShapedText = SharedShapedText;
1398pub type VectorShapedLine = SharedShapedText;
1399pub type ShapedLine = SharedShapedText;
1400
1401#[derive(Clone)]
1402struct ShapeCacheKey {
1403 text: Arc<str>,
1404 px_bits: u32,
1405 line_height_bits: u32,
1406 family: Option<Arc<str>>,
1407 font_weight: u16,
1408 font_style: u8,
1409 letter_spacing_bits: u32,
1410 variation: Option<Arc<str>>,
1411 generation: u64,
1412 text_direction: TextDirection,
1413 font_synthesis: FontSynthesis,
1414}
1415
1416impl ShapeCacheKey {
1417 fn new(
1418 text: &str,
1419 px: f32,
1420 line_height_ratio: f32,
1421 options: ShapeOptions<'_>,
1422 generation: u64,
1423 ) -> Self {
1424 Self {
1425 text: Arc::from(text),
1426 px_bits: px.to_bits(),
1427 line_height_bits: line_height_ratio.to_bits(),
1428 family: options.font_family.map(Arc::from),
1429 font_weight: options.font_weight,
1430 font_style: options.font_style,
1431 letter_spacing_bits: options.letter_spacing.to_bits(),
1432 variation: options.font_variation_settings.map(Arc::from),
1433 generation,
1434 text_direction: options.text_direction,
1435 font_synthesis: options.font_synthesis,
1436 }
1437 }
1438
1439 fn matches(
1440 &self,
1441 text: &str,
1442 px: f32,
1443 line_height_ratio: f32,
1444 options: ShapeOptions<'_>,
1445 ) -> bool {
1446 self.text.as_ref() == text
1447 && self.px_bits == px.to_bits()
1448 && self.line_height_bits == line_height_ratio.to_bits()
1449 && self.family.as_deref() == options.font_family
1450 && self.font_weight == options.font_weight
1451 && self.font_style == options.font_style
1452 && self.letter_spacing_bits == options.letter_spacing.to_bits()
1453 && self.variation.as_deref() == options.font_variation_settings
1454 && self.text_direction == options.text_direction
1455 && self.font_synthesis == options.font_synthesis
1456 }
1457}
1458
1459struct ShapeCacheEntry {
1460 key: ShapeCacheKey,
1461 value: Arc<SharedShapedText>,
1462}
1463
1464static SHAPED_LRU: OnceLock<Mutex<Lru<u64, Vec<ShapeCacheEntry>>>> = OnceLock::new();
1465
1466struct LegacyShapeCacheEntry {
1467 key: ShapeCacheKey,
1468 value: Arc<[ShapedGlyph]>,
1469}
1470
1471static LEGACY_SHAPED_LRU: OnceLock<Mutex<Lru<u64, Vec<LegacyShapeCacheEntry>>>> = OnceLock::new();
1472
1473fn shaped_cache() -> &'static Mutex<Lru<u64, Vec<ShapeCacheEntry>>> {
1474 SHAPED_LRU.get_or_init(|| Mutex::new(Lru::new(SHAPE_CACHE_CAP)))
1475}
1476
1477fn legacy_shaped_cache() -> &'static Mutex<Lru<u64, Vec<LegacyShapeCacheEntry>>> {
1478 LEGACY_SHAPED_LRU.get_or_init(|| Mutex::new(Lru::new(LEGACY_SHAPE_CACHE_CAP)))
1479}
1480
1481fn shape_fingerprint(
1482 text: &str,
1483 px: f32,
1484 line_height_ratio: f32,
1485 options: ShapeOptions<'_>,
1486 generation: u64,
1487) -> u64 {
1488 let mut hasher = RapidHasher::default();
1489 text.hash(&mut hasher);
1490 px.to_bits().hash(&mut hasher);
1491 line_height_ratio.to_bits().hash(&mut hasher);
1492 options.font_family.hash(&mut hasher);
1493 options.font_weight.hash(&mut hasher);
1494 options.font_style.hash(&mut hasher);
1495 options.letter_spacing.to_bits().hash(&mut hasher);
1496 options.font_variation_settings.hash(&mut hasher);
1497 options.text_direction.hash(&mut hasher);
1498 options.font_synthesis.hash(&mut hasher);
1499 generation.hash(&mut hasher);
1500 hasher.finish()
1501}
1502
1503fn cached_shaped(
1504 text: &str,
1505 px: f32,
1506 line_height_ratio: f32,
1507 options: ShapeOptions<'_>,
1508 generation: u64,
1509 fingerprint: u64,
1510) -> Option<Arc<SharedShapedText>> {
1511 let mut cache = shaped_cache().lock().ok()?;
1512 let entries = cache.get_mut(&fingerprint)?;
1513 let entry = entries.iter().find(|entry| {
1514 entry.key.generation == generation
1515 && entry.key.matches(text, px, line_height_ratio, options)
1516 })?;
1517 Some(entry.value.clone())
1518}
1519
1520fn cache_shaped(fingerprint: u64, key: ShapeCacheKey, value: Arc<SharedShapedText>) {
1521 let mut cache = shaped_cache().lock().unwrap();
1522 if let Some(entries) = cache.get_mut(&fingerprint) {
1523 if let Some(entry) = entries.iter_mut().find(|entry| {
1524 entry.key.text == key.text
1525 && entry.key.px_bits == key.px_bits
1526 && entry.key.line_height_bits == key.line_height_bits
1527 && entry.key.family == key.family
1528 && entry.key.font_weight == key.font_weight
1529 && entry.key.font_style == key.font_style
1530 && entry.key.letter_spacing_bits == key.letter_spacing_bits
1531 && entry.key.variation == key.variation
1532 && entry.key.text_direction == key.text_direction
1533 && entry.key.font_synthesis == key.font_synthesis
1534 }) {
1535 entry.key.generation = key.generation;
1536 entry.value = value;
1537 return;
1538 }
1539 entries.push(ShapeCacheEntry { key, value });
1540 return;
1541 }
1542 cache.put(fingerprint, vec![ShapeCacheEntry { key, value }]);
1543}
1544
1545fn cached_legacy_glyphs(
1546 text: &str,
1547 px: f32,
1548 line_height_ratio: f32,
1549 options: ShapeOptions<'_>,
1550 generation: u64,
1551 fingerprint: u64,
1552) -> Option<Arc<[ShapedGlyph]>> {
1553 let mut cache = legacy_shaped_cache().lock().ok()?;
1554 let entries = cache.get_mut(&fingerprint)?;
1555 let entry = entries.iter().find(|entry| {
1556 entry.key.generation == generation
1557 && entry.key.matches(text, px, line_height_ratio, options)
1558 })?;
1559 Some(entry.value.clone())
1560}
1561
1562fn cache_legacy_glyphs(fingerprint: u64, key: ShapeCacheKey, value: Arc<[ShapedGlyph]>) {
1563 let mut cache = legacy_shaped_cache().lock().unwrap();
1564 if let Some(entries) = cache.get_mut(&fingerprint) {
1565 if let Some(entry) = entries.iter_mut().find(|entry| {
1566 entry.key.text == key.text
1567 && entry.key.px_bits == key.px_bits
1568 && entry.key.line_height_bits == key.line_height_bits
1569 && entry.key.family == key.family
1570 && entry.key.font_weight == key.font_weight
1571 && entry.key.font_style == key.font_style
1572 && entry.key.letter_spacing_bits == key.letter_spacing_bits
1573 && entry.key.variation == key.variation
1574 && entry.key.text_direction == key.text_direction
1575 && entry.key.font_synthesis == key.font_synthesis
1576 }) {
1577 entry.key.generation = key.generation;
1578 entry.value = value;
1579 return;
1580 }
1581 entries.push(LegacyShapeCacheEntry { key, value });
1582 return;
1583 }
1584 cache.put(fingerprint, vec![LegacyShapeCacheEntry { key, value }]);
1585}
1586
1587fn synthesis_has_weight(synthesis: &parley::fontique::Synthesis) -> bool {
1588 synthesis.embolden()
1589 || synthesis
1590 .variation_settings()
1591 .iter()
1592 .any(|(tag, _)| tag.to_be_bytes() == *b"wght")
1593}
1594
1595fn synthesis_has_style(synthesis: &parley::fontique::Synthesis) -> bool {
1596 synthesis.skew().is_some()
1597 || synthesis.variation_settings().iter().any(|(tag, _)| {
1598 matches!(
1599 tag.to_be_bytes(),
1600 [b'i', b't', b'a', b'l'] | [b's', b'l', b'n', b't']
1601 )
1602 })
1603}
1604
1605fn validate_shape_options(
1606 options: ShapeOptions<'_>,
1607 resolved_direction: TextDirection,
1608 runs: &[ShapedRun],
1609) -> Result<(), ShapeCapabilityError> {
1610 if options.text_direction != TextDirection::Auto && options.text_direction != resolved_direction
1611 {
1612 return Err(ShapeCapabilityError::RequestedDirectionMismatch);
1613 }
1614 let invalid = match options.font_synthesis {
1615 FontSynthesis::Unspecified | FontSynthesis::All => false,
1616 FontSynthesis::None => runs.iter().any(|run| run.synthesis.any()),
1617 FontSynthesis::Weight => runs.iter().any(|run| synthesis_has_style(&run.synthesis)),
1618 FontSynthesis::Style => runs.iter().any(|run| synthesis_has_weight(&run.synthesis)),
1619 };
1620 if invalid {
1621 Err(ShapeCapabilityError::SynthesisModeUnsupported)
1622 } else {
1623 Ok(())
1624 }
1625}
1626
1627fn shape_vector_inner(
1628 text: &str,
1629 px: f32,
1630 line_height_ratio: f32,
1631 options: ShapeOptions<'_>,
1632 collect_runs: bool,
1633) -> Result<Arc<SharedShapedText>, ShapeCapabilityError> {
1634 let generation = font_generation();
1635 let fingerprint = shape_fingerprint(text, px, line_height_ratio, options, generation);
1636 if let Some(value) = cached_shaped(
1637 text,
1638 px,
1639 line_height_ratio,
1640 options,
1641 generation,
1642 fingerprint,
1643 ) {
1644 return Ok(value);
1645 }
1646 let mut eng = engine().lock().unwrap();
1647 let layout = build_layout(
1648 &mut eng,
1649 text,
1650 px,
1651 line_height_ratio,
1652 options.font_family,
1653 options.font_weight,
1654 options.font_style,
1655 options.letter_spacing,
1656 options.font_variation_settings,
1657 );
1658 report_unresolved_codepoints(&layout, text);
1659 let resolved_direction = if layout.is_rtl() {
1660 TextDirection::Rtl
1661 } else {
1662 TextDirection::Ltr
1663 };
1664 let variation = canonical_variation(options.font_variation_settings);
1665 let (glyphs, runs) = collect_shaped_layout(&mut eng, layout, px, collect_runs, variation);
1666 validate_shape_options(options, resolved_direction, &runs)?;
1667 let value = Arc::new(SharedShapedText {
1668 glyphs: Arc::from(glyphs.into_boxed_slice()),
1669 requested_text_direction: options.text_direction,
1670 resolved_text_direction: resolved_direction,
1671 runs: Arc::from(runs.into_boxed_slice()),
1672 });
1673 let key = ShapeCacheKey::new(text, px, line_height_ratio, options, generation);
1674 drop(eng);
1675 cache_shaped(fingerprint, key, value.clone());
1676 Ok(value)
1677}
1678
1679fn materialize_legacy_glyphs(
1680 shaped: &SharedShapedText,
1681 text: &str,
1682 px: f32,
1683 line_height_ratio: f32,
1684 options: ShapeOptions<'_>,
1685) -> Vec<ShapedGlyph> {
1686 let generation = font_generation();
1687 let fingerprint = shape_fingerprint(text, px, line_height_ratio, options, generation);
1688 if let Some(cached) = cached_legacy_glyphs(
1689 text,
1690 px,
1691 line_height_ratio,
1692 options,
1693 generation,
1694 fingerprint,
1695 ) {
1696 return cached.as_ref().to_vec();
1697 }
1698 let mut glyphs = shaped.glyphs.as_ref().to_vec();
1699 let mut eng = engine().lock().unwrap();
1700 for glyph in &mut glyphs {
1701 if let Some((width, height, left, top)) =
1702 eng.raster_placement(glyph.font_id, glyph.glyph_id, glyph.px, None)
1703 {
1704 glyph.w = width;
1705 glyph.h = height;
1706 glyph.bearing_x = left;
1707 glyph.bearing_y = top;
1708 glyph.rasterized = true;
1709 }
1710 }
1711 drop(eng);
1712 let value: Arc<[ShapedGlyph]> = Arc::from(glyphs.into_boxed_slice());
1713 cache_legacy_glyphs(
1714 fingerprint,
1715 ShapeCacheKey::new(text, px, line_height_ratio, options, generation),
1716 value.clone(),
1717 );
1718 value.as_ref().to_vec()
1719}
1720
1721pub fn shape_line_vector(
1722 text: &str,
1723 px: f32,
1724 line_height_ratio: f32,
1725 font_family: Option<&str>,
1726 font_weight: u16,
1727 font_style: u8,
1728 letter_spacing: f32,
1729 font_variation_settings: Option<&str>,
1730) -> Arc<SharedShapedText> {
1731 shape_vector_inner(
1732 text,
1733 px,
1734 line_height_ratio,
1735 ShapeOptions {
1736 font_family,
1737 font_weight,
1738 font_style,
1739 letter_spacing,
1740 font_variation_settings,
1741 ..ShapeOptions::default()
1742 },
1743 true,
1744 )
1745 .unwrap_or_else(|_| {
1746 Arc::new(SharedShapedText {
1747 glyphs: Arc::from(Vec::new()),
1748 requested_text_direction: TextDirection::Auto,
1749 resolved_text_direction: TextDirection::Ltr,
1750 runs: Arc::from(Vec::new()),
1751 })
1752 })
1753}
1754
1755pub fn shape_line_cached(
1756 text: &str,
1757 px: f32,
1758 line_height_ratio: f32,
1759 font_family: Option<&str>,
1760 font_weight: u16,
1761 font_style: u8,
1762 letter_spacing: f32,
1763 font_variation_settings: Option<&str>,
1764) -> Arc<SharedShapedText> {
1765 shape_line_vector(
1766 text,
1767 px,
1768 line_height_ratio,
1769 font_family,
1770 font_weight,
1771 font_style,
1772 letter_spacing,
1773 font_variation_settings,
1774 )
1775}
1776
1777pub fn shape_line_shared(
1778 text: &str,
1779 px: f32,
1780 line_height_ratio: f32,
1781 font_family: Option<&str>,
1782 font_weight: u16,
1783 font_style: u8,
1784 letter_spacing: f32,
1785 font_variation_settings: Option<&str>,
1786) -> Arc<SharedShapedText> {
1787 shape_line_vector(
1788 text,
1789 px,
1790 line_height_ratio,
1791 font_family,
1792 font_weight,
1793 font_style,
1794 letter_spacing,
1795 font_variation_settings,
1796 )
1797}
1798
1799pub fn shape_text_with_options_vector(
1800 text: &str,
1801 px: f32,
1802 line_height_ratio: f32,
1803 options: ShapeOptions<'_>,
1804) -> Result<Arc<SharedShapedText>, ShapeCapabilityError> {
1805 shape_vector_inner(text, px, line_height_ratio, options, true)
1806}
1807
1808pub fn shape_text_with_options_shared(
1809 text: &str,
1810 px: f32,
1811 line_height_ratio: f32,
1812 options: ShapeOptions<'_>,
1813) -> Result<Arc<SharedShapedText>, ShapeCapabilityError> {
1814 shape_text_with_options_vector(text, px, line_height_ratio, options)
1815}
1816
1817pub fn shape_text_with_options_cached(
1818 text: &str,
1819 px: f32,
1820 line_height_ratio: f32,
1821 options: ShapeOptions<'_>,
1822) -> Result<Arc<SharedShapedText>, ShapeCapabilityError> {
1823 shape_text_with_options_vector(text, px, line_height_ratio, options)
1824}
1825
1826pub fn shape_line(
1827 text: &str,
1828 px: f32,
1829 line_height_ratio: f32,
1830 font_family: Option<&str>,
1831 font_weight: u16,
1832 font_style: u8,
1833 letter_spacing: f32,
1834 font_variation_settings: Option<&str>,
1835) -> Vec<ShapedGlyph> {
1836 let options = ShapeOptions {
1837 font_family,
1838 font_weight,
1839 font_style,
1840 letter_spacing,
1841 font_variation_settings,
1842 ..ShapeOptions::default()
1843 };
1844 materialize_legacy_glyphs(
1845 &shape_line_vector(
1846 text,
1847 px,
1848 line_height_ratio,
1849 font_family,
1850 font_weight,
1851 font_style,
1852 letter_spacing,
1853 font_variation_settings,
1854 ),
1855 text,
1856 px,
1857 line_height_ratio,
1858 options,
1859 )
1860}
1861
1862pub fn shape_text_with_options(
1863 text: &str,
1864 px: f32,
1865 line_height_ratio: f32,
1866 options: ShapeOptions<'_>,
1867) -> Result<ShapedText, ShapeCapabilityError> {
1868 let shaped = shape_text_with_options_vector(text, px, line_height_ratio, options)?;
1869 Ok(ShapedText {
1870 glyphs: materialize_legacy_glyphs(&shaped, text, px, line_height_ratio, options),
1871 requested_text_direction: shaped.requested_text_direction,
1872 resolved_text_direction: shaped.resolved_text_direction,
1873 runs: shaped.runs.as_ref().to_vec(),
1874 })
1875}
1876
1877pub fn shape_line_with_options_vector(
1878 text: &str,
1879 px: f32,
1880 line_height_ratio: f32,
1881 options: ShapeOptions<'_>,
1882) -> Result<Arc<SharedShapedText>, ShapeCapabilityError> {
1883 shape_text_with_options_vector(text, px, line_height_ratio, options)
1884}
1885
1886pub fn shape_line_with_options_cached(
1887 text: &str,
1888 px: f32,
1889 line_height_ratio: f32,
1890 options: ShapeOptions<'_>,
1891) -> Result<Arc<SharedShapedText>, ShapeCapabilityError> {
1892 shape_line_with_options_vector(text, px, line_height_ratio, options)
1893}
1894
1895pub fn shape_line_with_options(
1896 text: &str,
1897 px: f32,
1898 line_height_ratio: f32,
1899 options: ShapeOptions<'_>,
1900) -> Result<Vec<ShapedGlyph>, ShapeCapabilityError> {
1901 shape_text_with_options(text, px, line_height_ratio, options).map(|shaped| shaped.glyphs)
1902}
1903
1904fn rasterize_locked(
1905 eng: &mut Engine,
1906 key: GlyphKey,
1907 cache_key: &CacheKey,
1908 px: f32,
1909) -> Option<GlyphBitmap> {
1910 use swash::scale::{Render, Source, StrikeWith};
1911 let fid = cache_key.font_id;
1912 let gid = cache_key.glyph_id;
1913 let raster_key = (fid, gid, px.to_bits(), cache_key.variation.clone());
1914 if let Some(cached) = eng.glyph_cache.get(&raster_key) {
1915 log::debug!(
1916 "[rasterize] HIT fid={} gid={} px={} => {}x{}",
1917 fid,
1918 gid,
1919 px,
1920 cached.0,
1921 cached.1
1922 );
1923 return Some(GlyphBitmap {
1924 key,
1925 w: cached.0,
1926 h: cached.1,
1927 content: cached.4,
1928 data: cached.5.clone(),
1929 });
1930 }
1931 let (data_bytes, face_index) = {
1932 let record = eng.font_registry.iter().find(|r| r.id == fid)?;
1933 (record.data_bytes.clone(), record.face_index)
1934 };
1935 let face_index = usize::try_from(face_index).ok()?;
1936 let font = swash::FontRef::from_index(data_bytes.as_ref(), face_index)?;
1937 let mut builder = eng.swash_cx.builder(font).size(px).hint(true);
1938 let settings = swash_settings(cache_key.variation.as_deref());
1939 if !settings.is_empty() {
1940 builder = builder.variations(settings);
1941 }
1942 let mut scaler = builder.build();
1943 let image = Render::new(&[
1944 Source::Outline,
1945 Source::ColorBitmap(StrikeWith::BestFit),
1946 Source::ColorOutline(0),
1947 ])
1948 .render(&mut scaler, swash_glyph_id(fid, gid))?;
1949 if image.placement.width > MAX_RASTER_DIMENSION
1950 || image.placement.height > MAX_RASTER_DIMENSION
1951 || image.data.len() > MAX_RASTER_BYTES
1952 {
1953 return None;
1954 }
1955 log::debug!(
1956 "[rasterize] MISS fid={} gid={} px={} => {}x{}",
1957 fid,
1958 gid,
1959 px,
1960 image.placement.width,
1961 image.placement.height
1962 );
1963 let bitmap = GlyphBitmap {
1964 key,
1965 w: image.placement.width,
1966 h: image.placement.height,
1967 content: image.content,
1968 data: image.data,
1969 };
1970 let data_len = bitmap.data.len();
1971 let previous = eng.glyph_cache.insert(
1972 raster_key,
1973 (
1974 bitmap.w,
1975 bitmap.h,
1976 image.placement.left,
1977 image.placement.top,
1978 bitmap.content,
1979 bitmap.data.clone(),
1980 ),
1981 );
1982 if let Some(previous) = previous {
1983 eng.glyph_cache_bytes = eng.glyph_cache_bytes.saturating_sub(previous.5.len());
1984 }
1985 eng.glyph_cache_bytes = eng.glyph_cache_bytes.saturating_add(data_len);
1986 eng.trim_glyph_cache();
1987 Some(bitmap)
1988}
1989
1990pub fn rasterize(key: GlyphKey, px: f32) -> Option<GlyphBitmap> {
1996 let mut eng = engine().lock().unwrap();
1997 let &(fid, gid) = eng.key_map.get(&key)?;
1998 let px = normalized_raster_px(px)?;
1999 let cache_key = CacheKey {
2000 font_id: fid,
2001 glyph_id: gid,
2002 font_size_bits: px.to_bits(),
2003 variation: None,
2004 };
2005 rasterize_locked(&mut eng, key, &cache_key, px)
2006}
2007
2008pub fn rasterize_cache_key(cache_key: &CacheKey) -> Option<GlyphBitmap> {
2009 let px = f32::from_bits(cache_key.font_size_bits);
2010 let normalized = normalized_raster_px(px)?;
2011 let mut eng = engine().lock().unwrap();
2012 rasterize_locked(
2013 &mut eng,
2014 key_from_pair(cache_key.font_id, cache_key.glyph_id),
2015 cache_key,
2016 normalized,
2017 )
2018}
2019
2020pub fn raster_placement(cache_key: &CacheKey) -> Option<(f32, f32, f32, f32)> {
2021 let px = f32::from_bits(cache_key.font_size_bits);
2022 let mut eng = engine().lock().unwrap();
2023 eng.raster_placement(
2024 cache_key.font_id,
2025 cache_key.glyph_id,
2026 px,
2027 cache_key.variation.clone(),
2028 )
2029}
2030
2031pub fn lookup_cache_key(key: GlyphKey, px: f32) -> Option<CacheKey> {
2037 let eng = engine().lock().unwrap();
2038 let &(fid, gid) = eng.key_map.get(&key)?;
2039 let px = normalized_raster_px(px)?;
2040 Some(CacheKey {
2041 font_id: fid,
2042 glyph_id: gid,
2043 font_size_bits: px.to_bits(),
2044 variation: None,
2045 })
2046}
2047
2048fn extract_outlines_for(
2049 data_bytes: &[u8],
2050 face_index: u32,
2051 glyph_id: u32,
2052 variation: Option<&str>,
2053) -> Option<Box<[Command]>> {
2054 let font = skrifa::FontRef::from_index(data_bytes, face_index).ok()?;
2055 let mut pen = OutlinePenCollector(Vec::new());
2056 let glyph = font.outline_glyphs().get(skrifa::GlyphId::new(glyph_id))?;
2057 let settings = decode_variation(variation);
2058 if settings.is_empty() {
2059 glyph
2060 .draw(skrifa::instance::Size::new(1.0), &mut pen)
2061 .ok()?;
2062 } else {
2063 let location = font.axes().location(settings);
2064 glyph
2065 .draw(
2066 skrifa::outline::DrawSettings::unhinted(
2067 skrifa::instance::Size::new(1.0),
2068 &location,
2069 ),
2070 &mut pen,
2071 )
2072 .ok()?;
2073 }
2074 Some(pen.0.into_boxed_slice())
2075}
2076
2077pub fn extract_outline_commands_shared(cache_key: &CacheKey) -> Option<Arc<[Command]>> {
2078 let cache_identity = (
2079 cache_key.font_id,
2080 cache_key.glyph_id,
2081 cache_key.variation.clone(),
2082 );
2083 if let Some(cached) = engine()
2084 .lock()
2085 .unwrap()
2086 .outline_cache
2087 .get(&cache_identity)
2088 .cloned()
2089 {
2090 return Some(cached);
2091 }
2092 let (data_bytes, face_index) = {
2093 let eng = engine().lock().unwrap();
2094 let record = eng
2095 .font_registry
2096 .iter()
2097 .find(|record| record.id == cache_key.font_id)?;
2098 (record.data_bytes.clone(), record.face_index)
2099 };
2100 let commands = extract_outlines_for(
2101 data_bytes.as_ref(),
2102 face_index,
2103 cache_key.glyph_id,
2104 cache_key.variation.as_deref(),
2105 )?;
2106 let commands: Arc<[Command]> = Arc::from(commands);
2107 engine()
2108 .lock()
2109 .unwrap()
2110 .outline_cache
2111 .put(cache_identity, commands.clone());
2112 Some(commands)
2113}
2114
2115pub fn extract_outline_commands(cache_key: &CacheKey) -> Option<Box<[Command]>> {
2116 extract_outline_commands_shared(cache_key)
2117 .map(|commands| commands.as_ref().to_vec().into_boxed_slice())
2118}
2119
2120pub fn extract_outline_commands_for(cache_key: &CacheKey) -> Option<Box<[Command]>> {
2121 extract_outline_commands(cache_key)
2122}
2123
2124pub fn lookup_and_extract_outline(key: GlyphKey, px: f32) -> Option<(CacheKey, Box<[Command]>)> {
2127 let (fid, gid) = {
2128 let eng = engine().lock().unwrap();
2129 *eng.key_map.get(&key)?
2130 };
2131 let px = normalized_raster_px(px)?;
2132 let ck = CacheKey {
2133 font_id: fid,
2134 glyph_id: gid,
2135 font_size_bits: px.to_bits(),
2136 variation: None,
2137 };
2138 let commands = extract_outline_commands_shared(&ck)?;
2139 Some((ck, commands.as_ref().to_vec().into_boxed_slice()))
2140}
2141
2142struct OutlinePenCollector(Vec<Command>);
2143
2144impl OutlinePen for OutlinePenCollector {
2145 fn move_to(&mut self, x: f32, y: f32) {
2146 self.0.push(Command::MoveTo(x, y));
2147 }
2148 fn line_to(&mut self, x: f32, y: f32) {
2149 self.0.push(Command::LineTo(x, y));
2150 }
2151 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
2152 self.0.push(Command::QuadTo(cx0, cy0, x, y));
2153 }
2154 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
2155 self.0.push(Command::CurveTo(cx0, cy0, cx1, cy1, x, y));
2156 }
2157 fn close(&mut self) {
2158 self.0.push(Command::Close);
2159 }
2160}
2161
2162#[derive(Clone)]
2163pub struct TextMetrics {
2164 pub positions: Vec<f32>,
2165 pub byte_offsets: Vec<usize>,
2166}
2167
2168struct SegmentExtrema {
2169 len: usize,
2170 base: usize,
2171 min: Vec<f32>,
2172 max: Vec<f32>,
2173 prefix_min: Vec<f32>,
2174 prefix_max: Vec<f32>,
2175 has_prefix: bool,
2176}
2177
2178impl SegmentExtrema {
2179 fn new(len: usize, has_prefix: bool) -> Self {
2180 let len = len.max(1);
2181 let base = len.next_power_of_two();
2182 Self {
2183 len,
2184 base,
2185 min: vec![f32::INFINITY; base * 2],
2186 max: vec![f32::NEG_INFINITY; base * 2],
2187 prefix_min: Vec::with_capacity(if has_prefix { len + 1 } else { 0 }),
2188 prefix_max: Vec::with_capacity(if has_prefix { len + 1 } else { 0 }),
2189 has_prefix,
2190 }
2191 }
2192
2193 fn set(&mut self, index: usize, value: f32) {
2194 if value.is_finite() && index < self.len {
2195 self.min[self.base + index] = value;
2196 self.max[self.base + index] = value;
2197 }
2198 }
2199
2200 fn apply(&mut self, start: usize, end: usize, min_value: f32, max_value: f32) {
2201 if start >= end {
2202 return;
2203 }
2204 let mut left = start.min(self.len) + self.base;
2205 let mut right = end.min(self.len) + self.base;
2206 while left < right {
2207 if left & 1 == 1 {
2208 self.min[left] = self.min[left].min(min_value);
2209 self.max[left] = self.max[left].max(max_value);
2210 left += 1;
2211 }
2212 if right & 1 == 1 {
2213 right -= 1;
2214 self.min[right] = self.min[right].min(min_value);
2215 self.max[right] = self.max[right].max(max_value);
2216 }
2217 left >>= 1;
2218 right >>= 1;
2219 }
2220 }
2221
2222 fn finish(&mut self) {
2223 for index in 1..self.base {
2224 let min_value = self.min[index];
2225 let max_value = self.max[index];
2226 if min_value.is_finite() {
2227 self.min[index * 2] = self.min[index * 2].min(min_value);
2228 self.min[index * 2 + 1] = self.min[index * 2 + 1].min(min_value);
2229 }
2230 if max_value.is_finite() {
2231 self.max[index * 2] = self.max[index * 2].max(max_value);
2232 self.max[index * 2 + 1] = self.max[index * 2 + 1].max(max_value);
2233 }
2234 }
2235 for index in (1..self.base).rev() {
2236 self.min[index] = self.min[index * 2].min(self.min[index * 2 + 1]);
2237 self.max[index] = self.max[index * 2].max(self.max[index * 2 + 1]);
2238 }
2239 if self.has_prefix {
2240 self.prefix_min.push(f32::INFINITY);
2241 self.prefix_max.push(f32::NEG_INFINITY);
2242 for index in 0..self.len {
2243 let value_min = self.min[self.base + index];
2244 let value_max = self.max[self.base + index];
2245 let previous_min = *self.prefix_min.last().unwrap_or(&f32::INFINITY);
2246 let previous_max = *self.prefix_max.last().unwrap_or(&f32::NEG_INFINITY);
2247 self.prefix_min.push(previous_min.min(value_min));
2248 self.prefix_max.push(previous_max.max(value_max));
2249 }
2250 }
2251 }
2252
2253 fn query(&self, start: usize, end: usize) -> (f32, f32) {
2254 let start = start.min(self.len);
2255 let end = end.min(self.len).max(start);
2256 if start == end {
2257 return (f32::INFINITY, f32::NEG_INFINITY);
2258 }
2259 if start == 0 && self.has_prefix {
2260 return (self.prefix_min[end], self.prefix_max[end]);
2261 }
2262 let mut left = start + self.base;
2263 let mut right = end + self.base;
2264 let mut min_value = f32::INFINITY;
2265 let mut max_value = f32::NEG_INFINITY;
2266 while left < right {
2267 if left & 1 == 1 {
2268 min_value = min_value.min(self.min[left]);
2269 max_value = max_value.max(self.max[left]);
2270 left += 1;
2271 }
2272 if right & 1 == 1 {
2273 right -= 1;
2274 min_value = min_value.min(self.min[right]);
2275 max_value = max_value.max(self.max[right]);
2276 }
2277 left >>= 1;
2278 right >>= 1;
2279 }
2280 (min_value, max_value)
2281 }
2282}
2283
2284struct RangeExtremum {
2285 positions: SegmentExtrema,
2286 extents: SegmentExtrema,
2287 coordinates: Vec<usize>,
2288}
2289
2290impl RangeExtremum {
2291 fn from_measurement(text: &str, metrics: &TextMetrics, extents: &[VisualExtent]) -> Self {
2292 let mut coordinates = metrics.byte_offsets.clone();
2293 coordinates.extend(text.char_indices().map(|(index, _)| index));
2294 coordinates.push(text.len());
2295 for extent in extents {
2296 coordinates.push(extent.start);
2297 coordinates.push(extent.end);
2298 }
2299 coordinates.sort_unstable();
2300 coordinates.dedup();
2301 let mut positions = SegmentExtrema::new(metrics.positions.len(), true);
2302 for (index, value) in metrics.positions.iter().copied().enumerate() {
2303 positions.set(index, value);
2304 }
2305 let mut range_extents = SegmentExtrema::new(coordinates.len().saturating_sub(1), false);
2306 for extent in extents {
2307 let left = extent.left.min(extent.right);
2308 let right = extent.left.max(extent.right);
2309 if !left.is_finite() || !right.is_finite() {
2310 continue;
2311 }
2312 let start = coordinates.partition_point(|byte| *byte < extent.start);
2313 let end = coordinates.partition_point(|byte| *byte < extent.end);
2314 range_extents.apply(start, end, left, right);
2315 }
2316 positions.finish();
2317 range_extents.finish();
2318 Self {
2319 positions,
2320 extents: range_extents,
2321 coordinates,
2322 }
2323 }
2324
2325 fn query_bytes(&self, offsets: &[usize], start: usize, end: usize) -> (f32, f32) {
2326 let position_start = offsets.partition_point(|byte| *byte < start);
2327 let position_end = offsets.partition_point(|byte| *byte <= end);
2328 let (position_min, position_max) = self.positions.query(position_start, position_end);
2329 let extent_start = self.coordinates.partition_point(|byte| *byte < start);
2330 let extent_end = self.coordinates.partition_point(|byte| *byte < end);
2331 let (extent_min, extent_max) = self.extents.query(extent_start, extent_end);
2332 (position_min.min(extent_min), position_max.max(extent_max))
2333 }
2334}
2335
2336#[derive(Clone, Copy)]
2337struct LogicalPart {
2338 start: usize,
2339 end: usize,
2340 advance: f32,
2341}
2342
2343struct LogicalGroup {
2344 start: usize,
2345 end: usize,
2346 first_part: Option<LogicalPart>,
2347 extra_parts: Vec<LogicalPart>,
2348 has_ligature_start: bool,
2349 advance: f32,
2350}
2351
2352impl LogicalGroup {
2353 fn parts(&self) -> impl Iterator<Item = &LogicalPart> {
2354 self.first_part.iter().chain(self.extra_parts.iter())
2355 }
2356}
2357
2358#[derive(Clone, Copy, PartialEq, Eq, Debug)]
2359struct AtomicRange {
2360 start: usize,
2361 end: usize,
2362}
2363
2364#[derive(Clone, Copy)]
2365struct VisualExtent {
2366 start: usize,
2367 end: usize,
2368 left: f32,
2369 right: f32,
2370}
2371
2372#[derive(Clone, Copy, PartialEq, Eq)]
2373enum EdgeKind {
2374 Start,
2375 Internal,
2376 End,
2377}
2378
2379#[derive(Clone, Copy)]
2380struct EdgeCandidate {
2381 byte: usize,
2382 position: f32,
2383 kind: EdgeKind,
2384}
2385
2386struct LayoutEdges {
2387 edges: Vec<(usize, f32)>,
2388 extents: Vec<VisualExtent>,
2389 atomic_ranges: Vec<AtomicRange>,
2390}
2391
2392struct LineMeasurement {
2393 metrics: TextMetrics,
2394 atomic_ranges: Vec<AtomicRange>,
2395 extrema: RangeExtremum,
2396}
2397
2398#[derive(Clone, Copy)]
2399struct HardLine {
2400 start: usize,
2401 end: usize,
2402 next_start: usize,
2403}
2404
2405fn hard_lines(text: &str) -> Vec<HardLine> {
2406 let mut lines = Vec::new();
2407 let mut start = 0usize;
2408 let mut chars = text.char_indices().peekable();
2409 while let Some((index, ch)) = chars.next() {
2410 let next_start = match ch {
2411 '\n' => Some(index + 1),
2412 '\u{2028}' | '\u{2029}' => Some(index + ch.len_utf8()),
2413 '\r' => {
2414 if text[index + 1..].starts_with('\n') {
2415 chars.next();
2416 Some(index + 2)
2417 } else {
2418 Some(index + 1)
2419 }
2420 }
2421 _ => None,
2422 };
2423 if let Some(next_start) = next_start {
2424 lines.push(HardLine {
2425 start,
2426 end: index,
2427 next_start,
2428 });
2429 start = next_start;
2430 }
2431 }
2432 lines.push(HardLine {
2433 start,
2434 end: text.len(),
2435 next_start: text.len(),
2436 });
2437 lines
2438}
2439
2440fn finish_logical_group(mut group: LogicalGroup) -> LogicalGroup {
2441 if let Some(part) = &mut group.first_part
2442 && !part.advance.is_finite()
2443 {
2444 part.advance = 0.0;
2445 }
2446 for part in &mut group.extra_parts {
2447 if !part.advance.is_finite() {
2448 part.advance = 0.0;
2449 }
2450 }
2451 group.advance = group.parts().map(|part| part.advance).sum();
2452 if !group.advance.is_finite() {
2453 group.advance = 0.0;
2454 }
2455 group.end = group.end.max(group.start);
2456 group
2457}
2458
2459fn logical_groups_for_run<B: parley::style::Brush>(
2460 run: parley::layout::Run<'_, B>,
2461) -> Vec<LogicalGroup> {
2462 let mut groups = Vec::new();
2463 let mut current: Option<LogicalGroup> = None;
2464 for cluster in run.clusters() {
2465 let range = cluster.text_range();
2466 let part = LogicalPart {
2467 start: range.start,
2468 end: range.end,
2469 advance: cluster.advance(),
2470 };
2471 let is_ligature = cluster.is_ligature_start() || cluster.is_ligature_continuation();
2472 if !is_ligature {
2473 if let Some(group) = current.take() {
2474 groups.push(finish_logical_group(group));
2475 }
2476 groups.push(finish_logical_group(LogicalGroup {
2477 start: part.start,
2478 end: part.end,
2479 first_part: Some(part),
2480 extra_parts: Vec::new(),
2481 has_ligature_start: false,
2482 advance: 0.0,
2483 }));
2484 continue;
2485 }
2486 let starts_new_group = cluster.is_ligature_start()
2487 && current
2488 .as_ref()
2489 .is_some_and(|group| group.has_ligature_start);
2490 if starts_new_group && let Some(group) = current.take() {
2491 groups.push(finish_logical_group(group));
2492 }
2493 let group = current.get_or_insert_with(|| LogicalGroup {
2494 start: part.start,
2495 end: part.end,
2496 first_part: None,
2497 extra_parts: Vec::new(),
2498 has_ligature_start: false,
2499 advance: 0.0,
2500 });
2501 group.start = group.start.min(part.start);
2502 group.end = group.end.max(part.end);
2503 if group.first_part.is_none() {
2504 group.first_part = Some(part);
2505 } else {
2506 group.extra_parts.push(part);
2507 }
2508 group.has_ligature_start |= cluster.is_ligature_start();
2509 }
2510 if let Some(group) = current {
2511 groups.push(finish_logical_group(group));
2512 }
2513 groups
2514}
2515
2516fn add_grapheme_edges(
2517 text: &str,
2518 range: std::ops::Range<usize>,
2519 start_x: f32,
2520 end_x: f32,
2521 candidates: &mut Vec<EdgeCandidate>,
2522) {
2523 let start = range.start.min(text.len());
2524 let end = range.end.min(text.len()).max(start);
2525 let Some(slice) = text.get(start..end) else {
2526 return;
2527 };
2528 let count = slice.graphemes(true).count();
2529 if count == 0 {
2530 candidates.push(EdgeCandidate {
2531 byte: start,
2532 position: start_x,
2533 kind: EdgeKind::Start,
2534 });
2535 candidates.push(EdgeCandidate {
2536 byte: end,
2537 position: end_x,
2538 kind: EdgeKind::End,
2539 });
2540 return;
2541 }
2542 candidates.push(EdgeCandidate {
2543 byte: start,
2544 position: start_x,
2545 kind: EdgeKind::Start,
2546 });
2547 for (index, (offset, grapheme)) in slice.grapheme_indices(true).enumerate() {
2548 let fraction = (index + 1) as f32 / count as f32;
2549 let position = start_x + (end_x - start_x) * fraction;
2550 let kind = if index + 1 == count {
2551 EdgeKind::End
2552 } else {
2553 EdgeKind::Internal
2554 };
2555 candidates.push(EdgeCandidate {
2556 byte: start + offset + grapheme.len(),
2557 position,
2558 kind,
2559 });
2560 }
2561}
2562
2563fn edge_priority(kind: EdgeKind) -> u8 {
2564 match kind {
2565 EdgeKind::Internal => 0,
2566 EdgeKind::Start => 1,
2567 EdgeKind::End => 2,
2568 }
2569}
2570
2571fn resolve_edge_candidates(
2572 mut candidates: Vec<EdgeCandidate>,
2573 text_len: usize,
2574) -> Vec<(usize, f32)> {
2575 candidates.sort_by_key(|candidate| candidate.byte);
2576 let mut edges = BTreeMap::new();
2577 for candidate in candidates {
2578 let replace = match edges.get(&candidate.byte) {
2579 None => true,
2580 Some((_, kind)) => edge_priority(candidate.kind) > edge_priority(*kind),
2581 };
2582 if replace {
2583 edges.insert(candidate.byte, (candidate.position, candidate.kind));
2584 }
2585 }
2586 edges.entry(0).or_insert((0.0, EdgeKind::Start));
2587 edges.entry(text_len).or_insert((0.0, EdgeKind::End));
2588 edges
2589 .into_iter()
2590 .map(|(byte, (position, _))| (byte, position))
2591 .collect()
2592}
2593
2594fn normalized_atomic_ranges(mut ranges: Vec<AtomicRange>) -> Vec<AtomicRange> {
2595 ranges.retain(|range| range.end > range.start);
2596 ranges.sort_by_key(|range| (range.start, range.end));
2597 let mut result: Vec<AtomicRange> = Vec::new();
2598 for range in ranges {
2599 if let Some(previous) = result.last_mut()
2600 && (range.start < previous.end || range.start == previous.start)
2601 {
2602 previous.end = previous.end.max(range.end);
2603 } else {
2604 result.push(range);
2605 }
2606 }
2607 result
2608}
2609
2610fn atomic_ranges_for_layout(text: &str, layout: &parley::Layout<()>) -> Vec<AtomicRange> {
2611 let mut clusters = Vec::new();
2612 let mut seen = HashSet::new();
2613 for line in layout.lines() {
2614 for run in line.runs() {
2615 for cluster in run.clusters() {
2616 let range = cluster.text_range();
2617 let candidate = (
2618 range.start.min(text.len()),
2619 range.end.min(text.len()),
2620 cluster.is_ligature_start(),
2621 cluster.is_ligature_continuation(),
2622 );
2623 if seen.insert(candidate) {
2624 clusters.push(candidate);
2625 }
2626 }
2627 }
2628 }
2629 clusters.sort_by_key(|(start, end, _, _)| (*start, *end));
2630 let mut result = Vec::new();
2631 let mut current: Option<AtomicRange> = None;
2632 let mut has_start = false;
2633 for (start, end, is_start, is_continuation) in clusters {
2634 if end <= start {
2635 continue;
2636 }
2637 if !is_start && !is_continuation {
2638 if let Some(range) = current.take() {
2639 result.push(range);
2640 }
2641 has_start = false;
2642 result.push(AtomicRange { start, end });
2643 continue;
2644 }
2645 if is_start
2646 && has_start
2647 && let Some(range) = current.take()
2648 {
2649 result.push(range);
2650 has_start = false;
2651 }
2652 let range = current.get_or_insert(AtomicRange { start, end });
2653 range.start = range.start.min(start);
2654 range.end = range.end.max(end);
2655 has_start |= is_start;
2656 }
2657 if let Some(range) = current {
2658 result.push(range);
2659 }
2660 normalized_atomic_ranges(result)
2661}
2662
2663fn layout_edges_for_layout(text: &str, layout: &parley::Layout<()>) -> LayoutEdges {
2664 let mut candidates = Vec::new();
2665 let mut extents = Vec::new();
2666
2667 for line in layout.lines() {
2668 let line_range = line.text_range();
2669 let line_start = line_range.start.min(text.len());
2670 let line_end = line_range.end.min(text.len()).max(line_start);
2671 candidates.push(EdgeCandidate {
2672 byte: line_start,
2673 position: 0.0,
2674 kind: EdgeKind::Internal,
2675 });
2676 if line_end > line_start {
2677 candidates.push(EdgeCandidate {
2678 byte: line_end,
2679 position: 0.0,
2680 kind: EdgeKind::Internal,
2681 });
2682 }
2683
2684 let inline_min = line.metrics().inline_min_coord;
2685 for run in line.runs() {
2686 let mut visual_clusters = HashMap::with_capacity(run.len());
2687 for cluster in run.visual_clusters() {
2688 let range = cluster.text_range();
2689 visual_clusters
2690 .entry((range.start, range.end))
2691 .or_insert_with(|| cluster.visual_offset());
2692 }
2693 let groups = logical_groups_for_run(run);
2694 for group in &groups {
2695 let mut left = f32::INFINITY;
2696 let mut right = f32::NEG_INFINITY;
2697 for part in group.parts() {
2698 let Some(offset) = visual_clusters
2699 .get(&(part.start, part.end))
2700 .copied()
2701 .flatten()
2702 else {
2703 continue;
2704 };
2705 let offset = offset + inline_min;
2706 let advance = if part.advance.is_finite() {
2707 part.advance
2708 } else {
2709 0.0
2710 };
2711 left = left.min(offset).min(offset + advance);
2712 right = right.max(offset).max(offset + advance);
2713 let (start_x, end_x) = if run.is_rtl() {
2714 (offset + advance, offset)
2715 } else {
2716 (offset, offset + advance)
2717 };
2718 add_grapheme_edges(text, part.start..part.end, start_x, end_x, &mut candidates);
2719 }
2720 if left.is_finite() && right.is_finite() {
2721 extents.push(VisualExtent {
2722 start: group.start.min(text.len()),
2723 end: group.end.min(text.len()),
2724 left,
2725 right,
2726 });
2727 }
2728 }
2729 }
2730 }
2731
2732 LayoutEdges {
2733 edges: resolve_edge_candidates(candidates, text.len()),
2734 extents,
2735 atomic_ranges: atomic_ranges_for_layout(text, layout),
2736 }
2737}
2738
2739fn visual_width(measurement: &LineMeasurement, start: usize, end: usize) -> f32 {
2740 let (min, max) = measurement
2741 .extrema
2742 .query_bytes(&measurement.metrics.byte_offsets, start, end);
2743 if min.is_finite() && max.is_finite() {
2744 (max - min).max(0.0)
2745 } else {
2746 0.0
2747 }
2748}
2749
2750fn edge_position(edges: &[(usize, f32)], byte: usize) -> f32 {
2751 match edges.binary_search_by_key(&byte, |edge| edge.0) {
2752 Ok(index) => edges[index].1,
2753 Err(0) => edges.first().map(|edge| edge.1).unwrap_or(0.0),
2754 Err(index) => edges
2755 .get(index.saturating_sub(1))
2756 .map(|edge| edge.1)
2757 .unwrap_or(0.0),
2758 }
2759}
2760
2761fn metrics_from_edges(text: &str, edges: &[(usize, f32)]) -> TextMetrics {
2762 let mut byte_offsets = Vec::with_capacity(text.graphemes(true).count() + 1);
2763 byte_offsets.push(0);
2764 for (byte, _) in text.grapheme_indices(true) {
2765 if byte != 0 {
2766 byte_offsets.push(byte);
2767 }
2768 }
2769 if *byte_offsets.last().unwrap_or(&0) != text.len() {
2770 byte_offsets.push(text.len());
2771 }
2772 let positions = byte_offsets
2773 .iter()
2774 .map(|byte| edge_position(edges, *byte))
2775 .collect();
2776 TextMetrics {
2777 positions,
2778 byte_offsets,
2779 }
2780}
2781
2782fn measure_line_with_engine(
2783 eng: &mut Engine,
2784 text: &str,
2785 px: f32,
2786 font_family: Option<&str>,
2787 font_weight: u16,
2788 font_style: u8,
2789 letter_spacing: f32,
2790 font_variation_settings: Option<&str>,
2791) -> LineMeasurement {
2792 let layout = build_layout(
2793 eng,
2794 text,
2795 px,
2796 0.0,
2797 font_family,
2798 font_weight,
2799 font_style,
2800 letter_spacing,
2801 font_variation_settings,
2802 );
2803 report_unresolved_codepoints(&layout, text);
2804 let layout_edges = layout_edges_for_layout(text, &layout);
2805 let metrics = metrics_from_edges(text, &layout_edges.edges);
2806 let extrema = RangeExtremum::from_measurement(text, &metrics, &layout_edges.extents);
2807 LineMeasurement {
2808 metrics,
2809 atomic_ranges: layout_edges.atomic_ranges,
2810 extrema,
2811 }
2812}
2813
2814fn measure_text_with_engine(
2815 eng: &mut Engine,
2816 text: &str,
2817 px: f32,
2818 font_family: Option<&str>,
2819 font_weight: u16,
2820 font_style: u8,
2821 letter_spacing: f32,
2822 font_variation_settings: Option<&str>,
2823) -> TextMetrics {
2824 let mut edges = BTreeMap::new();
2825 let hard_line_list = hard_lines(text);
2826 for line in &hard_line_list {
2827 let local_text = &text[line.start..line.end];
2828 let layout = build_layout(
2829 eng,
2830 local_text,
2831 px,
2832 0.0,
2833 font_family,
2834 font_weight,
2835 font_style,
2836 letter_spacing,
2837 font_variation_settings,
2838 );
2839 report_unresolved_codepoints(&layout, local_text);
2840 let local_edges = layout_edges_for_layout(local_text, &layout);
2841 for (byte, position) in local_edges.edges {
2842 edges.insert(line.start + byte, position);
2843 }
2844 }
2845 for line in &hard_line_list {
2846 if line.next_start > line.end {
2847 edges.insert(line.next_start, 0.0);
2848 }
2849 }
2850 let edges = edges.into_iter().collect::<Vec<_>>();
2851 metrics_from_edges(text, &edges)
2852}
2853
2854pub fn metrics_for_textfield_shared(
2855 text: &str,
2856 px: f32,
2857 font_family: Option<&str>,
2858 font_weight: u16,
2859 font_style: u8,
2860 letter_spacing: f32,
2861 font_variation_settings: Option<&str>,
2862) -> Arc<TextMetrics> {
2863 let key = MetricsCacheKey {
2864 text: Arc::from(text),
2865 px_bits: px.to_bits(),
2866 family: font_family.map(Arc::from),
2867 font_weight,
2868 font_style,
2869 letter_spacing_bits: letter_spacing.to_bits(),
2870 variation: font_variation_settings.map(Arc::from),
2871 generation: font_generation(),
2872 };
2873 if let Some(metrics) = metrics_cache().lock().unwrap().get(&key).cloned() {
2874 return metrics;
2875 }
2876 let metrics = {
2877 let mut eng = engine().lock().unwrap();
2878 measure_text_with_engine(
2879 &mut eng,
2880 text,
2881 px,
2882 font_family,
2883 font_weight,
2884 font_style,
2885 letter_spacing,
2886 font_variation_settings,
2887 )
2888 };
2889 let metrics = Arc::new(metrics);
2890 metrics_cache().lock().unwrap().put(key, metrics.clone());
2891 metrics
2892}
2893
2894pub fn metrics_for_textfield(
2895 text: &str,
2896 px: f32,
2897 font_family: Option<&str>,
2898 font_weight: u16,
2899 font_style: u8,
2900 letter_spacing: f32,
2901 font_variation_settings: Option<&str>,
2902) -> TextMetrics {
2903 (*metrics_for_textfield_shared(
2904 text,
2905 px,
2906 font_family,
2907 font_weight,
2908 font_style,
2909 letter_spacing,
2910 font_variation_settings,
2911 ))
2912 .clone()
2913}
2914
2915fn is_wrapping_space(ch: char) -> bool {
2916 ch.is_whitespace() && !matches!(ch, '\u{00A0}' | '\u{202F}' | '\u{2007}')
2917}
2918
2919fn trim_start_byte(text: &str, mut byte: usize) -> usize {
2920 byte = byte.min(text.len());
2921 while byte < text.len() {
2922 let ch = text[byte..].chars().next().unwrap();
2923 if !is_wrapping_space(ch) {
2924 break;
2925 }
2926 byte += ch.len_utf8();
2927 }
2928 byte
2929}
2930
2931fn trim_end_byte(text: &str, mut byte: usize) -> usize {
2932 byte = byte.min(text.len());
2933 while byte > 0 {
2934 let previous = text[..byte].char_indices().next_back();
2935 let Some((start, ch)) = previous else {
2936 break;
2937 };
2938 if !is_wrapping_space(ch) {
2939 break;
2940 }
2941 byte = start;
2942 }
2943 byte
2944}
2945
2946fn containing_atomic_range(ranges: &[AtomicRange], byte: usize) -> Option<AtomicRange> {
2947 let index = ranges.partition_point(|range| range.start < byte);
2948 let range = ranges.get(index.checked_sub(1)?)?;
2949 (range.start < byte && byte < range.end).then_some(*range)
2950}
2951
2952fn snap_start_to_atomic(ranges: &[AtomicRange], byte: usize) -> usize {
2953 containing_atomic_range(ranges, byte)
2954 .map(|range| range.start)
2955 .unwrap_or(byte)
2956}
2957
2958fn snap_end_to_atomic(ranges: &[AtomicRange], byte: usize) -> usize {
2959 containing_atomic_range(ranges, byte)
2960 .map(|range| range.end)
2961 .unwrap_or(byte)
2962}
2963
2964fn next_safe_boundary(measurement: &LineMeasurement, start: usize, limit: usize) -> usize {
2965 let offsets = &measurement.metrics.byte_offsets;
2966 let start = start.min(offsets.last().copied().unwrap_or(0));
2967 let limit = limit.min(offsets.last().copied().unwrap_or(0)).max(start);
2968 if start >= limit {
2969 return start;
2970 }
2971 let mut index = offsets.partition_point(|byte| *byte <= start);
2972 let mut next = offsets.get(index).copied().unwrap_or(limit);
2973 while next < limit {
2974 let Some(range) = containing_atomic_range(&measurement.atomic_ranges, next) else {
2975 break;
2976 };
2977 if range.end >= limit {
2978 return limit;
2979 }
2980 index = offsets.partition_point(|byte| *byte <= range.end);
2981 next = offsets.get(index).copied().unwrap_or(limit);
2982 }
2983 next.min(limit)
2984}
2985
2986fn push_trimmed_range_atomic(
2987 text: &str,
2988 start: usize,
2989 end: usize,
2990 ranges: &[AtomicRange],
2991 out: &mut Vec<(usize, usize)>,
2992) {
2993 let mut start = start.min(text.len());
2994 let mut end = end.min(text.len()).max(start);
2995 let whitespace_only = text.get(start..end).is_some_and(|slice| {
2996 slice
2997 .chars()
2998 .all(|ch| is_wrapping_space(ch) || matches!(ch, '\u{00A0}' | '\u{202F}' | '\u{2007}'))
2999 });
3000 if end == start {
3001 out.push((start, end));
3002 return;
3003 }
3004 if whitespace_only {
3005 out.push((start, end));
3006 return;
3007 }
3008 start = trim_start_byte(text, start);
3009 end = trim_end_byte(text, end.max(start));
3010 start = snap_start_to_atomic(ranges, start);
3011 end = snap_end_to_atomic(ranges, end).max(start);
3012 out.push((start, end));
3013}
3014
3015fn wrap_one_hard_line_ranges(
3016 text: &str,
3017 max_width: f32,
3018 max_lines: Option<usize>,
3019 measurement: &LineMeasurement,
3020) -> (Vec<(usize, usize)>, bool) {
3021 if text.is_empty() {
3022 return (vec![(0, 0)], false);
3023 }
3024 if max_lines == Some(0) {
3025 return (Vec::new(), true);
3026 }
3027 let atomic_ranges = &measurement.atomic_ranges;
3028 if visual_width(measurement, 0, text.len()) <= max_width + 0.5 {
3029 let mut out = Vec::new();
3030 push_trimmed_range_atomic(text, 0, text.len(), atomic_ranges, &mut out);
3031 return (out, false);
3032 }
3033
3034 let mut out = Vec::new();
3035 let mut truncated = false;
3036 let mut line_start = trim_start_byte(text, 0);
3037 line_start = snap_start_to_atomic(atomic_ranges, line_start);
3038 let mut last_fit = Some(line_start);
3039
3040 for (raw_token_start, token) in text.split_word_bound_indices() {
3041 let raw_token_start = raw_token_start.min(text.len());
3042 let raw_token_end = (raw_token_start + token.len()).min(text.len());
3043 let token_start = snap_start_to_atomic(atomic_ranges, raw_token_start);
3044 let token_end = snap_end_to_atomic(atomic_ranges, raw_token_end).max(token_start);
3045 if token_end <= line_start {
3046 continue;
3047 }
3048
3049 let mut content_end = trim_end_byte(text, token_end);
3050 content_end = snap_end_to_atomic(atomic_ranges, content_end);
3051 if content_end <= line_start {
3052 last_fit = Some(token_end);
3053 continue;
3054 }
3055
3056 if visual_width(measurement, line_start, content_end) <= max_width + 0.5 {
3057 last_fit = Some(token_end);
3058 continue;
3059 }
3060
3061 if let Some(fit) = last_fit.filter(|fit| *fit > line_start) {
3062 push_trimmed_range_atomic(text, line_start, fit, atomic_ranges, &mut out);
3063 if max_lines.is_some_and(|limit| out.len() >= limit) {
3064 truncated = fit < text.len();
3065 return (out, truncated);
3066 }
3067 line_start = trim_start_byte(text, fit);
3068 line_start = snap_start_to_atomic(atomic_ranges, line_start);
3069 last_fit = None;
3070 if token_end <= line_start {
3071 continue;
3072 }
3073 }
3074
3075 let mut remaining_start = line_start.max(token_start);
3076 while remaining_start < token_end {
3077 remaining_start = trim_start_byte(text, remaining_start);
3078 remaining_start = snap_start_to_atomic(atomic_ranges, remaining_start);
3079 if remaining_start >= token_end {
3080 break;
3081 }
3082 let mut cut = line_start;
3083 let mut probe = remaining_start;
3084 while probe < token_end {
3085 let next = next_safe_boundary(measurement, probe, token_end);
3086 if next <= probe {
3087 break;
3088 }
3089 if visual_width(measurement, line_start, next) <= max_width + 0.5 {
3090 cut = next;
3091 probe = next;
3092 } else {
3093 break;
3094 }
3095 }
3096 if cut <= line_start {
3097 cut = next_safe_boundary(measurement, remaining_start, token_end);
3098 }
3099 if cut <= line_start {
3100 break;
3101 }
3102 push_trimmed_range_atomic(text, line_start, cut, atomic_ranges, &mut out);
3103 line_start = cut;
3104 if max_lines.is_some_and(|limit| out.len() >= limit) {
3105 truncated = line_start < text.len();
3106 return (out, truncated);
3107 }
3108 line_start = trim_start_byte(text, line_start);
3109 line_start = snap_start_to_atomic(atomic_ranges, line_start);
3110 last_fit = None;
3111 remaining_start = line_start;
3112 }
3113 }
3114
3115 if line_start < text.len() && max_lines.is_none_or(|limit| out.len() < limit) {
3116 push_trimmed_range_atomic(text, line_start, text.len(), atomic_ranges, &mut out);
3117 }
3118 if out.is_empty() {
3119 out.push((text.len(), text.len()));
3120 }
3121 (out, truncated)
3122}
3123
3124fn wrap_cache_key(
3125 text: &str,
3126 px: f32,
3127 max_width: f32,
3128 max_lines: Option<usize>,
3129 soft_wrap: bool,
3130 font_family: Option<&str>,
3131 font_weight: u16,
3132 font_style: u8,
3133 letter_spacing: f32,
3134 font_variation_settings: Option<&str>,
3135) -> WrapCacheKey {
3136 WrapCacheKey {
3137 text: Arc::from(text),
3138 px_bits: px.to_bits(),
3139 max_width_bits: max_width.to_bits(),
3140 max_lines,
3141 family: font_family.map(Arc::from),
3142 soft_wrap,
3143 font_weight,
3144 font_style,
3145 letter_spacing_bits: letter_spacing.to_bits(),
3146 variation: font_variation_settings.map(Arc::from),
3147 generation: font_generation(),
3148 }
3149}
3150
3151pub fn wrap_lines_with_family(
3152 text: &str,
3153 px: f32,
3154 max_width: f32,
3155 max_lines: Option<usize>,
3156 soft_wrap: bool,
3157 font_family: Option<&str>,
3158 font_weight: u16,
3159 font_style: u8,
3160 letter_spacing: f32,
3161 font_variation_settings: Option<&str>,
3162) -> (Vec<String>, bool) {
3163 if text.is_empty() {
3164 return if max_lines == Some(0) {
3165 (Vec::new(), true)
3166 } else {
3167 (vec![String::new()], false)
3168 };
3169 }
3170 if max_width <= 0.0 && soft_wrap {
3171 return if max_lines == Some(0) {
3172 (Vec::new(), true)
3173 } else {
3174 (vec![String::new()], false)
3175 };
3176 }
3177 let key = wrap_cache_key(
3178 text,
3179 px,
3180 max_width,
3181 max_lines,
3182 soft_wrap,
3183 font_family,
3184 font_weight,
3185 font_style,
3186 letter_spacing,
3187 font_variation_settings,
3188 );
3189 if let Some(cached) = wrap_cache().lock().unwrap().get(&key).cloned() {
3190 return (*cached).clone();
3191 }
3192 let (ranges, truncated) = wrap_line_ranges_with_family(
3193 text,
3194 px,
3195 max_width,
3196 max_lines,
3197 soft_wrap,
3198 font_family,
3199 font_weight,
3200 font_style,
3201 letter_spacing,
3202 font_variation_settings,
3203 );
3204 let lines = ranges
3205 .iter()
3206 .map(|(start, end)| text.get(*start..*end).unwrap_or_default().to_string())
3207 .collect::<Vec<_>>();
3208 let result = Arc::new((lines, truncated));
3209 wrap_cache().lock().unwrap().put(key, result.clone());
3210 (result.0.clone(), result.1)
3211}
3212
3213pub fn wrap_lines(
3214 text: &str,
3215 px: f32,
3216 max_width: f32,
3217 max_lines: Option<usize>,
3218 soft_wrap: bool,
3219 font_weight: u16,
3220 font_style: u8,
3221 letter_spacing: f32,
3222 font_variation_settings: Option<&str>,
3223) -> (Vec<String>, bool) {
3224 wrap_lines_with_family(
3225 text,
3226 px,
3227 max_width,
3228 max_lines,
3229 soft_wrap,
3230 None,
3231 font_weight,
3232 font_style,
3233 letter_spacing,
3234 font_variation_settings,
3235 )
3236}
3237
3238pub fn wrap_lines_with_style(
3239 text: &str,
3240 px: f32,
3241 max_width: f32,
3242 max_lines: Option<usize>,
3243 soft_wrap: bool,
3244 font_family: Option<&str>,
3245 font_weight: u16,
3246 font_style: u8,
3247 letter_spacing: f32,
3248 font_variation_settings: Option<&str>,
3249) -> (Vec<String>, bool) {
3250 wrap_lines_with_family(
3251 text,
3252 px,
3253 max_width,
3254 max_lines,
3255 soft_wrap,
3256 font_family,
3257 font_weight,
3258 font_style,
3259 letter_spacing,
3260 font_variation_settings,
3261 )
3262}
3263
3264pub fn wrap_line_ranges_with_family(
3265 text: &str,
3266 px: f32,
3267 max_width: f32,
3268 max_lines: Option<usize>,
3269 soft_wrap: bool,
3270 font_family: Option<&str>,
3271 font_weight: u16,
3272 font_style: u8,
3273 letter_spacing: f32,
3274 font_variation_settings: Option<&str>,
3275) -> (Vec<(usize, usize)>, bool) {
3276 if text.is_empty() {
3277 return if max_lines == Some(0) {
3278 (Vec::new(), true)
3279 } else {
3280 (vec![(0, 0)], false)
3281 };
3282 }
3283 if max_width <= 0.0 && soft_wrap {
3284 return if max_lines == Some(0) {
3285 (Vec::new(), true)
3286 } else {
3287 (vec![(0, 0)], false)
3288 };
3289 }
3290 let key = wrap_cache_key(
3291 text,
3292 px,
3293 max_width,
3294 max_lines,
3295 soft_wrap,
3296 font_family,
3297 font_weight,
3298 font_style,
3299 letter_spacing,
3300 font_variation_settings,
3301 );
3302 if let Some(cached) = wrap_ranges_cache().lock().unwrap().get(&key).cloned() {
3303 return (*cached).clone();
3304 }
3305
3306 if !soft_wrap {
3307 let lines = hard_lines(text);
3308 let count = max_lines.map_or(lines.len(), |limit| limit.min(lines.len()));
3309 let truncated = count < lines.len();
3310 let result = (
3311 lines
3312 .into_iter()
3313 .take(count)
3314 .map(|line| (line.start, line.end))
3315 .collect(),
3316 truncated,
3317 );
3318 let result = Arc::new(result);
3319 wrap_ranges_cache().lock().unwrap().put(key, result.clone());
3320 return (result.0.clone(), result.1);
3321 }
3322
3323 let mut output = Vec::new();
3324 let mut truncated = false;
3325 let hard_line_list = hard_lines(text);
3326 let hard_line_count = hard_line_list.len();
3327 let mut eng = engine().lock().unwrap();
3328 for line in hard_line_list {
3329 if max_lines.is_some_and(|limit| output.len() >= limit) {
3330 truncated = output.len() < hard_line_count;
3331 break;
3332 }
3333 let remaining = max_lines.map(|limit| limit.saturating_sub(output.len()));
3334 let segment = &text[line.start..line.end];
3335 let measurement = measure_line_with_engine(
3336 &mut eng,
3337 segment,
3338 px,
3339 font_family,
3340 font_weight,
3341 font_style,
3342 letter_spacing,
3343 font_variation_settings,
3344 );
3345 let (ranges, segment_truncated) =
3346 wrap_one_hard_line_ranges(segment, max_width, remaining, &measurement);
3347 output.extend(
3348 ranges
3349 .into_iter()
3350 .map(|(start, end)| (line.start + start, line.start + end)),
3351 );
3352 if segment_truncated {
3353 truncated = true;
3354 break;
3355 }
3356 if max_lines.is_some_and(|limit| output.len() >= limit) && line.next_start < text.len() {
3357 truncated = true;
3358 break;
3359 }
3360 }
3361 drop(eng);
3362 let result = Arc::new((output, truncated));
3363 wrap_ranges_cache().lock().unwrap().put(key, result.clone());
3364 (result.0.clone(), result.1)
3365}
3366
3367pub fn wrap_line_ranges(
3368 text: &str,
3369 px: f32,
3370 max_width: f32,
3371 max_lines: Option<usize>,
3372 soft_wrap: bool,
3373 font_weight: u16,
3374 font_style: u8,
3375 letter_spacing: f32,
3376 font_variation_settings: Option<&str>,
3377) -> (Vec<(usize, usize)>, bool) {
3378 wrap_line_ranges_with_family(
3379 text,
3380 px,
3381 max_width,
3382 max_lines,
3383 soft_wrap,
3384 None,
3385 font_weight,
3386 font_style,
3387 letter_spacing,
3388 font_variation_settings,
3389 )
3390}
3391
3392pub fn wrap_line_ranges_with_style(
3393 text: &str,
3394 px: f32,
3395 max_width: f32,
3396 max_lines: Option<usize>,
3397 soft_wrap: bool,
3398 font_family: Option<&str>,
3399 font_weight: u16,
3400 font_style: u8,
3401 letter_spacing: f32,
3402 font_variation_settings: Option<&str>,
3403) -> (Vec<(usize, usize)>, bool) {
3404 wrap_line_ranges_with_family(
3405 text,
3406 px,
3407 max_width,
3408 max_lines,
3409 soft_wrap,
3410 font_family,
3411 font_weight,
3412 font_style,
3413 letter_spacing,
3414 font_variation_settings,
3415 )
3416}
3417
3418pub fn ellipsize_line_with_family(
3419 text: &str,
3420 px: f32,
3421 max_width: f32,
3422 font_family: Option<&str>,
3423 font_weight: u16,
3424 font_style: u8,
3425 letter_spacing: f32,
3426 font_variation_settings: Option<&str>,
3427) -> String {
3428 if text.is_empty() || max_width <= 0.0 {
3429 return String::new();
3430 }
3431 let key = EllipCacheKey {
3432 text: Arc::from(text),
3433 px_bits: px.to_bits(),
3434 max_width_bits: max_width.to_bits(),
3435 family: font_family.map(Arc::from),
3436 font_weight,
3437 font_style,
3438 letter_spacing_bits: letter_spacing.to_bits(),
3439 variation: font_variation_settings.map(Arc::from),
3440 generation: font_generation(),
3441 };
3442 if let Some(cached) = ellip_cache().lock().unwrap().get(&key).cloned() {
3443 return (*cached).clone();
3444 }
3445 let measurement = {
3446 let mut eng = engine().lock().unwrap();
3447 measure_line_with_engine(
3448 &mut eng,
3449 text,
3450 px,
3451 font_family,
3452 font_weight,
3453 font_style,
3454 letter_spacing,
3455 font_variation_settings,
3456 )
3457 };
3458 if visual_width(&measurement, 0, text.len()) <= max_width + 0.5 {
3459 let result = text.to_owned();
3460 ellip_cache()
3461 .lock()
3462 .unwrap()
3463 .put(key, Arc::new(result.clone()));
3464 return result;
3465 }
3466 let ellipsis_width = ellipsis_width_with_style(
3467 px,
3468 font_family,
3469 font_weight,
3470 font_style,
3471 letter_spacing,
3472 font_variation_settings,
3473 );
3474 if ellipsis_width >= max_width {
3475 let result = Arc::new(String::new());
3476 ellip_cache().lock().unwrap().put(key, result);
3477 return String::new();
3478 }
3479 let mut byte = 0usize;
3480 for (offset, grapheme) in text.grapheme_indices(true) {
3481 let candidate = offset + grapheme.len();
3482 if containing_atomic_range(&measurement.atomic_ranges, candidate).is_some() {
3483 continue;
3484 }
3485 if visual_width(&measurement, 0, candidate) + ellipsis_width <= max_width + 0.5 {
3486 byte = candidate;
3487 }
3488 }
3489 let mut result = String::with_capacity(byte + '…'.len_utf8());
3490 result.push_str(&text[..byte]);
3491 result.push('…');
3492 ellip_cache()
3493 .lock()
3494 .unwrap()
3495 .put(key, Arc::new(result.clone()));
3496 result
3497}
3498
3499pub fn ellipsize_line(
3500 text: &str,
3501 px: f32,
3502 max_width: f32,
3503 font_weight: u16,
3504 font_style: u8,
3505 letter_spacing: f32,
3506 font_variation_settings: Option<&str>,
3507) -> String {
3508 ellipsize_line_with_family(
3509 text,
3510 px,
3511 max_width,
3512 None,
3513 font_weight,
3514 font_style,
3515 letter_spacing,
3516 font_variation_settings,
3517 )
3518}
3519
3520pub fn ellipsize_line_with_style(
3521 text: &str,
3522 px: f32,
3523 max_width: f32,
3524 font_family: Option<&str>,
3525 font_weight: u16,
3526 font_style: u8,
3527 letter_spacing: f32,
3528 font_variation_settings: Option<&str>,
3529) -> String {
3530 ellipsize_line_with_family(
3531 text,
3532 px,
3533 max_width,
3534 font_family,
3535 font_weight,
3536 font_style,
3537 letter_spacing,
3538 font_variation_settings,
3539 )
3540}
3541
3542fn ellipsis_width_with_style(
3543 px: f32,
3544 font_family: Option<&str>,
3545 font_weight: u16,
3546 font_style: u8,
3547 letter_spacing: f32,
3548 font_variation_settings: Option<&str>,
3549) -> f32 {
3550 static ELLIP_W_LRU: OnceLock<
3551 Mutex<Lru<(u32, Option<Arc<str>>, u16, u8, u32, Option<Arc<str>>, u64), f32>>,
3552 > = OnceLock::new();
3553 static ELLIP_W_GEN: AtomicU64 = AtomicU64::new(0);
3554 let cache = ELLIP_W_LRU.get_or_init(|| Mutex::new(Lru::new(64)));
3555 let generation = font_generation();
3556 if ELLIP_W_GEN.load(Ordering::Relaxed) != generation {
3557 if let Ok(mut guard) = cache.lock() {
3558 guard.clear_both();
3559 }
3560 ELLIP_W_GEN.store(generation, Ordering::Relaxed);
3561 }
3562 let key = (
3563 px.to_bits(),
3564 font_family.map(Arc::from),
3565 font_weight,
3566 font_style,
3567 letter_spacing.to_bits(),
3568 font_variation_settings.map(Arc::from),
3569 generation,
3570 );
3571 if let Some(width) = cache.lock().unwrap().get(&key).copied() {
3572 return width;
3573 }
3574 let width = {
3575 let mut eng = engine().lock().unwrap();
3576 let measurement = measure_line_with_engine(
3577 &mut eng,
3578 "…",
3579 px,
3580 font_family,
3581 font_weight,
3582 font_style,
3583 letter_spacing,
3584 font_variation_settings,
3585 );
3586 visual_width(&measurement, 0, "…".len())
3587 };
3588 cache.lock().unwrap().put(key, width);
3589 width
3590}
3591
3592#[cfg(test)]
3593mod tests {
3594 use super::*;
3595
3596 #[test]
3597 fn variation_keys_are_order_independent_and_last_wins() {
3598 let a = canonical_variation(Some("\"wght\" 400, \"wdth\" 75")).expect("canonical key");
3599 let b = canonical_variation(Some("\"wdth\" 75 , \"wght\" 400")).expect("canonical key");
3600 assert_eq!(a, b, "axis order must not change the key");
3601 let last = canonical_variation(Some("\"wght\" 400, \"wght\" 700")).expect("canonical key");
3602 let single = canonical_variation(Some("\"wght\" 700")).expect("canonical key");
3603 assert_eq!(last, single, "a repeated axis resolves to its last value");
3604 assert_ne!(
3605 canonical_variation(Some("\"wght\" 400")),
3606 canonical_variation(Some("\"wght\" 700"))
3607 );
3608 assert!(canonical_variation(None).is_none());
3609 assert!(canonical_variation(Some("")).is_none());
3610 assert!(canonical_variation(Some("!!! nonsense")).is_none());
3611 }
3612
3613 #[test]
3614 fn variation_keys_round_trip_exactly() {
3615 for source in [
3616 "\"wght\" 700",
3617 "\"wght\" 250, \"wdth\" 75, \"slnt\" -12.5",
3618 "\"opsz\" 14.25",
3619 ] {
3620 let key = canonical_variation(Some(source)).expect("canonical key");
3621 let decoded = decode_variation(Some(&key));
3622 let names: Vec<String> = source
3626 .split(',')
3627 .filter_map(|entry| {
3628 let quoted = entry.trim().strip_prefix('"')?;
3629 quoted.split_once('"').map(|(tag, _)| tag.to_string())
3630 })
3631 .collect();
3632 let values: Vec<u32> = parley::setting::FontVariation::parse_css_list(source)
3633 .filter_map(Result::ok)
3634 .map(|setting| setting.value.to_bits())
3635 .collect();
3636 let mut expected: Vec<(String, u32)> = names.into_iter().zip(values).collect();
3637 expected.sort_by(|a, b| a.0.cmp(&b.0));
3638 let got: Vec<(String, u32)> = decoded
3639 .iter()
3640 .map(|(tag, value)| {
3641 let name = format!("{tag:?}");
3643 let name = name
3644 .trim_start_matches("Tag(")
3645 .trim_end_matches(')')
3646 .to_string();
3647 (name, value.to_bits())
3648 })
3649 .collect();
3650 assert_eq!(got, expected, "{source}");
3651 }
3652 }
3653
3654 #[test]
3658 fn malformed_variation_keys_are_ignored_not_fatal() {
3659 assert!(decode_variation(None).is_empty());
3660 assert!(decode_variation(Some("")).is_empty());
3661 for malformed in ["wght", "abc", "0123456789ab", "zzzz", "wght!!", "\"wght\""] {
3662 assert!(
3663 decode_variation(Some(malformed)).is_empty(),
3664 "{malformed:?} must decode to nothing"
3665 );
3666 }
3667 let valid = canonical_variation(Some("\"wght\" 700")).expect("canonical key");
3669 let padded = format!("{valid}abc");
3670 assert_eq!(decode_variation(Some(&padded)).len(), 1);
3671 }
3672
3673 #[test]
3674 fn bidi_caret_positions_follow_visual_order() {
3675 let text = "אבג";
3676 let metrics = metrics_for_textfield(text, 32.0, None, 400, 0, 0.0, None);
3677 assert!(metrics.positions.windows(2).all(|w| w[0] > w[1]));
3678 assert!(
3679 metrics
3680 .positions
3681 .iter()
3682 .all(|position| position.is_finite())
3683 );
3684
3685 let mixed = "אב abc";
3686 let metrics = metrics_for_textfield(mixed, 32.0, None, 400, 0, 0.0, None);
3687 assert!(metrics.positions[0] > metrics.positions[1]);
3688 assert!(metrics.positions[1] > metrics.positions[2]);
3689 assert!(metrics.positions[4] < metrics.positions[5]);
3690 assert!(metrics.positions[5] < metrics.positions[6]);
3691 }
3692
3693 #[test]
3694 fn utf8_layout_and_wrapping_do_not_panic() {
3695 let text = "e\u{301} 👩👩👧👦 🇺🇸 אב\u{00A0}界";
3696 let result = std::panic::catch_unwind(|| {
3697 let _ = metrics_for_textfield(text, 16.0, None, 400, 0, 0.0, None);
3698 let _ = wrap_line_ranges_with_family(
3699 text,
3700 16.0,
3701 12.0,
3702 Some(2),
3703 true,
3704 None,
3705 400,
3706 0,
3707 0.0,
3708 None,
3709 );
3710 });
3711 assert!(result.is_ok());
3712 }
3713}