1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
22#![expect(clippy::type_complexity)]
24#![warn(unused_extern_crates)]
25#![warn(missing_docs)]
26#![cfg_attr(not(ipc), allow(unused))]
27
28use font_features::RFontVariations;
29use hashbrown::{HashMap, HashSet};
30use skrifa::MetadataProvider;
31use std::{borrow::Cow, fmt, io, ops, path::PathBuf, slice::SliceIndex, sync::Arc};
32#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
33use zng_task::channel::WeakIpcBytes;
34
35#[macro_use]
36extern crate bitflags;
37
38pub mod font_features;
39
40mod query_util;
41
42mod emoji_util;
43pub use emoji_util::*;
44
45mod ligature_util;
46use ligature_util::*;
47
48mod unicode_bidi_util;
49
50mod segmenting;
51pub use segmenting::*;
52
53mod shaping;
54pub use shaping::*;
55use zng_clone_move::{async_clmv, clmv};
56
57mod hyphenation;
58pub use self::hyphenation::*;
59
60mod unit;
61pub use unit::*;
62
63use pastey::paste;
64use zng_app::{
65 event::{event, event_args},
66 render::FontSynthesis,
67 update::UPDATES,
68 view_process::{
69 VIEW_PROCESS_INITED_EVENT, ViewRenderer,
70 raw_events::{RAW_FONT_AA_CHANGED_EVENT, RAW_FONT_CHANGED_EVENT},
71 },
72};
73use zng_app_context::app_local;
74use zng_ext_l10n::{Lang, LangMap, lang};
75use zng_layout::unit::{
76 ByteUnits as _, EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorPercent, Px, PxRect, TimeUnits as _, about_eq, about_eq_hash,
77 about_eq_ord, euclid,
78};
79use zng_task::parking_lot::{Mutex, RwLock};
80use zng_task::{self as task, channel::IpcBytes};
81use zng_txt::{ToTxt, Txt};
82use zng_var::{IntoVar, ResponseVar, Var, animation::Transitionable, const_var, impl_from_and_into_var, response_done_var, response_var};
83use zng_view_api::{config::FontAntiAliasing, font::IpcFontBytes};
84
85#[derive(Clone)]
93pub struct FontName {
94 txt: Txt,
95 is_ascii: bool,
96}
97impl fmt::Debug for FontName {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 if f.alternate() {
100 f.debug_struct("FontName")
101 .field("txt", &self.txt)
102 .field("is_ascii", &self.is_ascii)
103 .finish()
104 } else {
105 write!(f, "{:?}", self.txt)
106 }
107 }
108}
109impl PartialEq for FontName {
110 fn eq(&self, other: &Self) -> bool {
111 self.unicase() == other.unicase()
112 }
113}
114impl Eq for FontName {}
115impl PartialEq<str> for FontName {
116 fn eq(&self, other: &str) -> bool {
117 self.unicase() == unicase::UniCase::<&str>::from(other)
118 }
119}
120impl std::hash::Hash for FontName {
121 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
122 std::hash::Hash::hash(&self.unicase(), state)
123 }
124}
125impl Ord for FontName {
126 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
127 if self == other {
128 return std::cmp::Ordering::Equal;
130 }
131 self.txt.cmp(&other.txt)
132 }
133}
134impl PartialOrd for FontName {
135 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
136 Some(self.cmp(other))
137 }
138}
139impl FontName {
140 fn unicase(&self) -> unicase::UniCase<&str> {
141 if self.is_ascii {
142 unicase::UniCase::ascii(self)
143 } else {
144 unicase::UniCase::unicode(self)
145 }
146 }
147
148 pub const fn from_static(name: &'static str) -> Self {
150 FontName {
151 txt: Txt::from_static(name),
152 is_ascii: {
153 let name_bytes = name.as_bytes();
155 let mut i = name_bytes.len();
156 let mut is_ascii = true;
157 while i > 0 {
158 i -= 1;
159 if !name_bytes[i].is_ascii() {
160 is_ascii = false;
161 break;
162 }
163 }
164 is_ascii
165 },
166 }
167 }
168
169 pub fn new(name: impl Into<Txt>) -> Self {
178 let txt = name.into();
179 FontName {
180 is_ascii: txt.is_ascii(),
181 txt,
182 }
183 }
184
185 pub fn serif() -> Self {
191 Self::new("serif")
192 }
193
194 pub fn sans_serif() -> Self {
201 Self::new("sans-serif")
202 }
203
204 pub fn monospace() -> Self {
210 Self::new("monospace")
211 }
212
213 pub fn cursive() -> Self {
220 Self::new("cursive")
221 }
222
223 pub fn fantasy() -> Self {
229 Self::new("fantasy")
230 }
231
232 pub fn system_ui() -> Self {
238 Self::new("system-ui")
239 }
240
241 pub fn name(&self) -> &str {
243 &self.txt
244 }
245
246 pub fn into_text(self) -> Txt {
250 self.txt
251 }
252}
253impl_from_and_into_var! {
254 fn from(s: &'static str) -> FontName {
255 FontName::new(s)
256 }
257 fn from(s: String) -> FontName {
258 FontName::new(s)
259 }
260 fn from(s: Cow<'static, str>) -> FontName {
261 FontName::new(s)
262 }
263 fn from(f: FontName) -> Txt {
264 f.into_text()
265 }
266 fn from(s: Txt) -> FontName {
267 FontName::new(s)
268 }
269}
270impl fmt::Display for FontName {
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 f.write_str(self.name())
273 }
274}
275impl std::ops::Deref for FontName {
276 type Target = str;
277
278 fn deref(&self) -> &Self::Target {
279 self.txt.deref()
280 }
281}
282impl AsRef<str> for FontName {
283 fn as_ref(&self) -> &str {
284 self.txt.as_ref()
285 }
286}
287impl serde::Serialize for FontName {
288 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
289 where
290 S: serde::Serializer,
291 {
292 self.txt.serialize(serializer)
293 }
294}
295impl<'de> serde::Deserialize<'de> for FontName {
296 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
297 where
298 D: serde::Deserializer<'de>,
299 {
300 Txt::deserialize(deserializer).map(FontName::new)
301 }
302}
303
304#[derive(Eq, PartialEq, Hash, Clone, serde::Serialize, serde::Deserialize)]
333#[serde(transparent)]
334pub struct FontNames(pub Vec<FontName>);
335impl FontNames {
336 pub fn empty() -> Self {
338 FontNames(vec![])
339 }
340
341 pub fn push(&mut self, font_name: impl Into<FontName>) {
343 self.0.push(font_name.into())
344 }
345}
346impl Default for FontNames {
347 fn default() -> Self {
348 FontName::system_ui().into()
349 }
350}
351impl fmt::Debug for FontNames {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 if f.alternate() {
354 f.debug_tuple("FontNames").field(&self.0).finish()
355 } else if self.0.is_empty() {
356 write!(f, "[]")
357 } else if self.0.len() == 1 {
358 write!(f, "{:?}", self.0[0])
359 } else {
360 write!(f, "[{:?}, ", self.0[0])?;
361 for name in &self.0[1..] {
362 write!(f, "{name:?}, ")?;
363 }
364 write!(f, "]")
365 }
366 }
367}
368impl fmt::Display for FontNames {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 let mut iter = self.0.iter();
371
372 if let Some(name) = iter.next() {
373 write!(f, "{name}")?;
374 for name in iter {
375 write!(f, ", {name}")?;
376 }
377 }
378
379 Ok(())
380 }
381}
382impl_from_and_into_var! {
383 fn from(font_name: &'static str) -> FontNames {
384 FontNames(vec![FontName::new(font_name)])
385 }
386
387 fn from(font_name: String) -> FontNames {
388 FontNames(vec![FontName::new(font_name)])
389 }
390
391 fn from(font_name: Txt) -> FontNames {
392 FontNames(vec![FontName::new(font_name)])
393 }
394
395 fn from(font_names: Vec<FontName>) -> FontNames {
396 FontNames(font_names)
397 }
398
399 fn from(font_names: Vec<&'static str>) -> FontNames {
400 FontNames(font_names.into_iter().map(FontName::new).collect())
401 }
402
403 fn from(font_names: Vec<String>) -> FontNames {
404 FontNames(font_names.into_iter().map(FontName::new).collect())
405 }
406
407 fn from(font_name: FontName) -> FontNames {
408 FontNames(vec![font_name])
409 }
410}
411impl ops::Deref for FontNames {
412 type Target = Vec<FontName>;
413
414 fn deref(&self) -> &Self::Target {
415 &self.0
416 }
417}
418impl ops::DerefMut for FontNames {
419 fn deref_mut(&mut self) -> &mut Self::Target {
420 &mut self.0
421 }
422}
423impl std::iter::Extend<FontName> for FontNames {
424 fn extend<T: IntoIterator<Item = FontName>>(&mut self, iter: T) {
425 self.0.extend(iter)
426 }
427}
428impl IntoIterator for FontNames {
429 type Item = FontName;
430
431 type IntoIter = std::vec::IntoIter<FontName>;
432
433 fn into_iter(self) -> Self::IntoIter {
434 self.0.into_iter()
435 }
436}
437impl<const N: usize> From<[FontName; N]> for FontNames {
438 fn from(font_names: [FontName; N]) -> Self {
439 FontNames(font_names.into())
440 }
441}
442impl<const N: usize> IntoVar<FontNames> for [FontName; N] {
443 fn into_var(self) -> Var<FontNames> {
444 const_var(self.into())
445 }
446}
447impl<const N: usize> From<[&'static str; N]> for FontNames {
448 fn from(font_names: [&'static str; N]) -> Self {
449 FontNames(font_names.into_iter().map(FontName::new).collect())
450 }
451}
452impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
453 fn into_var(self) -> Var<FontNames> {
454 const_var(self.into())
455 }
456}
457impl<const N: usize> From<[String; N]> for FontNames {
458 fn from(font_names: [String; N]) -> Self {
459 FontNames(font_names.into_iter().map(FontName::new).collect())
460 }
461}
462impl<const N: usize> IntoVar<FontNames> for [String; N] {
463 fn into_var(self) -> Var<FontNames> {
464 const_var(self.into())
465 }
466}
467impl<const N: usize> From<[Txt; N]> for FontNames {
468 fn from(font_names: [Txt; N]) -> Self {
469 FontNames(font_names.into_iter().map(FontName::new).collect())
470 }
471}
472impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
473 fn into_var(self) -> Var<FontNames> {
474 const_var(self.into())
475 }
476}
477
478event! {
479 pub static FONT_CHANGED_EVENT: FontChangedArgs;
490}
491
492event_args! {
493 pub struct FontChangedArgs {
495 pub change: FontChange,
497
498 ..
499
500 fn is_in_target(&self, id: WidgetId) -> bool {
502 true
503 }
504 }
505}
506
507#[derive(Clone, Debug, PartialEq)]
509pub enum FontChange {
510 SystemFonts,
514
515 CustomFonts,
520
521 Refresh,
525
526 GenericFont(FontName, Lang),
532
533 Fallback(Lang),
535}
536
537app_local! {
538 static FONTS_SV: FontsService = FontsService::new();
539}
540
541struct FontsService {
542 loader: FontFaceLoader,
543}
544impl FontsService {
545 fn new() -> Self {
546 let s = FontsService {
547 loader: FontFaceLoader::new(),
548 };
549
550 RAW_FONT_CHANGED_EVENT
552 .hook(|args| {
553 FONT_CHANGED_EVENT.notify(FontChangedArgs::new(
554 args.timestamp,
555 args.propagation.clone(),
556 FontChange::SystemFonts,
557 ));
558 true
559 })
560 .perm();
561
562 FONT_CHANGED_EVENT
564 .hook(|_| {
565 let mut s = FONTS_SV.write();
566 s.loader.on_refresh();
567 true
568 })
569 .perm();
570
571 VIEW_PROCESS_INITED_EVENT
573 .hook(|args| {
574 if args.is_respawn {
575 FONTS_SV.write().loader.on_view_process_respawn();
576 }
577 true
578 })
579 .perm();
580
581 s
582 }
583}
584
585pub struct FONTS;
587impl FONTS {
588 pub fn refresh(&self) {
592 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
593 }
594
595 pub fn prune(&self) {
597 UPDATES.once_update("FONTS.prune", move || {
598 FONTS_SV.write().loader.on_prune();
599 });
600 }
601
602 pub fn generics(&self) -> &'static GenericFonts {
604 &GenericFonts {}
605 }
606
607 pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
616 let resp = task::respond(FontFace::load_custom(custom_font));
618
619 resp.hook(|args| {
621 if let Some(done) = args.value().done() {
622 if let Ok(face) = done {
623 let mut fonts = FONTS_SV.write();
624 let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
625 let existing = family
626 .iter()
627 .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
628
629 if let Some(i) = existing {
630 family[i] = face.clone();
631 } else {
632 family.push(face.clone());
633 }
634
635 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
636 }
637 false
638 } else {
639 true
640 }
641 })
642 .perm();
643
644 resp
645 }
646
647 pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
651 let (responder, response) = response_var();
652
653 UPDATES.once_update("FONTS.unregister", move || {
654 let mut fonts = FONTS_SV.write();
655 let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&custom_family) {
656 for removed in removed {
660 removed.on_refresh();
661 }
662
663 true
664 } else {
665 false
666 };
667 responder.respond(r);
668
669 if r {
670 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
671 }
672 });
673
674 response
675 }
676
677 pub fn list(
679 &self,
680 families: &[FontName],
681 style: FontStyle,
682 weight: FontWeight,
683 stretch: FontStretch,
684 lang: &Lang,
685 ) -> ResponseVar<FontFaceList> {
686 if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
688 tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) found cached");
689 return cached;
690 }
691 tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) not cached, load");
692 FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
694 }
695
696 pub fn find(
698 &self,
699 family: &FontName,
700 style: FontStyle,
701 weight: FontWeight,
702 stretch: FontStretch,
703 lang: &Lang,
704 ) -> ResponseVar<Option<FontFace>> {
705 let resolved = GenericFonts {}.resolve(family, lang);
706 let family = resolved.as_ref().unwrap_or(family);
707
708 if let Some(cached) = FONTS_SV.read().loader.try_resolved(family, style, weight, stretch) {
710 return cached;
711 }
712 FONTS_SV.write().loader.load_resolved(family, style, weight, stretch)
714 }
715
716 pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
718 self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
719 }
720
721 pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
723 self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
724 }
725
726 pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
728 self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
729 }
730
731 pub fn custom_fonts(&self) -> Vec<FontName> {
733 FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
734 }
735
736 pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
740 query_util::system_all()
741 }
742
743 pub fn system_font_aa(&self) -> Var<FontAntiAliasing> {
747 RAW_FONT_AA_CHANGED_EVENT.var_map(|a| Some(a.aa), || FontAntiAliasing::Default)
748 }
749}
750
751#[derive(PartialEq, Eq, Hash)]
752struct FontInstanceKey(Px, Box<[(skrifa::Tag, i32)]>);
753impl FontInstanceKey {
754 pub(crate) fn new(size: Px, variations: &[harfrust::font::Variation]) -> Self {
756 let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
757 FontInstanceKey(size, variations_key.into_boxed_slice())
758 }
759}
760
761#[derive(Clone)]
768pub struct FontFace(Arc<LoadedFontFace>);
769struct LoadedFontFace {
770 data: FontBytes,
771 face_index: u32,
772 display_name: FontName,
773 family_name: FontName,
774 postscript_name: Option<Txt>,
775 style: FontStyle,
776 weight: FontWeight,
777 stretch: FontStretch,
778 lig_carets: LigatureCaretList,
779 flags: FontFaceFlags,
780 harfrust_font: Option<harfrust::Font>,
781 m: Mutex<FontFaceMut>,
782}
783bitflags! {
784 #[derive(Debug, Clone, Copy)]
785 struct FontFaceFlags: u8 {
786 const IS_MONOSPACE = 0b0000_0001;
787 const HAS_LIGATURES = 0b0000_0010;
788 const HAS_RASTER_IMAGES = 0b0000_0100;
789 const HAS_SVG_IMAGES = 0b0000_1000;
790 }
791}
792struct FontFaceMut {
793 instances: HashMap<FontInstanceKey, Font>,
794 render_ids: Vec<RenderFontFace>,
795 unregistered: bool,
796}
797
798impl fmt::Debug for FontFace {
799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800 let m = self.0.m.lock();
801 f.debug_struct("FontFace")
802 .field("display_name", &self.0.display_name)
803 .field("family_name", &self.0.family_name)
804 .field("postscript_name", &self.0.postscript_name)
805 .field("flags", &self.0.flags)
806 .field("style", &self.0.style)
807 .field("weight", &self.0.weight)
808 .field("stretch", &self.0.stretch)
809 .field("instances.len()", &m.instances.len())
810 .field("render_keys.len()", &m.render_ids.len())
811 .field("unregistered", &m.unregistered)
812 .finish_non_exhaustive()
813 }
814}
815impl PartialEq for FontFace {
816 fn eq(&self, other: &Self) -> bool {
817 Arc::ptr_eq(&self.0, &other.0)
818 }
819}
820impl Eq for FontFace {}
821impl FontFace {
822 pub fn empty() -> Self {
824 FontFace(Arc::new(LoadedFontFace {
825 data: FontBytes::from_static(&[]),
826 face_index: 0,
827 display_name: FontName::from("<empty>"),
828 family_name: FontName::from("<empty>"),
829 postscript_name: None,
830 flags: FontFaceFlags::IS_MONOSPACE,
831 style: FontStyle::Normal,
832 weight: FontWeight::NORMAL,
833 stretch: FontStretch::NORMAL,
834 lig_carets: LigatureCaretList::empty(),
835 harfrust_font: None,
836 m: Mutex::new(FontFaceMut {
837 instances: HashMap::default(),
838 render_ids: vec![],
839 unregistered: false,
840 }),
841 }))
842 }
843
844 pub fn is_empty(&self) -> bool {
846 self.0.data.is_empty()
847 }
848
849 async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
850 let bytes;
851 let mut face_index;
852
853 match custom_font.source {
854 FontSource::File(path, index) => {
855 bytes = task::wait(|| FontBytes::from_file(path)).await?;
856 face_index = index;
857 }
858 FontSource::Memory(arc, index) => {
859 bytes = arc;
860 face_index = index;
861 }
862 FontSource::Alias(other_font) => {
863 let result = FONTS_SV
864 .write()
865 .loader
866 .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
867 return match result.wait_rsp().await {
868 Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
869 data: other_font.0.data.clone(),
870 face_index: other_font.0.face_index,
871 display_name: custom_font.name.clone(),
872 family_name: custom_font.name,
873 postscript_name: None,
874 style: other_font.0.style,
875 weight: other_font.0.weight,
876 stretch: other_font.0.stretch,
877 harfrust_font: other_font.0.harfrust_font.clone(),
878 m: Mutex::new(FontFaceMut {
879 instances: Default::default(),
880 render_ids: Default::default(),
881 unregistered: Default::default(),
882 }),
883 lig_carets: other_font.0.lig_carets.clone(),
884 flags: other_font.0.flags,
885 }))),
886 None => Err(FontLoadingError::NoSuchFontInCollection),
887 };
888 }
889 }
890
891 let ttf_face = match skrifa::FontRef::from_index(&bytes, face_index) {
892 Ok(f) => f,
893 Err(e) => {
894 match e {
895 read_fonts::ReadError::InvalidCollectionIndex(_) if face_index != 0 => face_index = 0,
897 e => return Err(FontLoadingError::Parse(e)),
898 }
899
900 match skrifa::FontRef::from_index(&bytes, face_index) {
901 Ok(f) => f,
902 Err(_) => return Err(FontLoadingError::Parse(e)),
903 }
904 }
905 };
906 use read_fonts::TableProvider as _;
907
908 let has_ligatures = ttf_face.gsub().is_ok();
909 let lig_carets = if has_ligatures {
910 LigatureCaretList::empty()
911 } else {
912 LigatureCaretList::load(&ttf_face)?
913 };
914
915 let mut flags = FontFaceFlags::empty();
916 flags.set(
917 FontFaceFlags::IS_MONOSPACE,
918 ttf_face.post().map(|p| p.is_fixed_pitch() != 0).unwrap_or(false),
919 );
920 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
921 flags.set(
922 FontFaceFlags::HAS_RASTER_IMAGES,
923 ttf_face.sbix().is_ok() || ttf_face.ebdt().is_ok() || ttf_face.cbdt().is_ok(),
924 );
925 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.svg().is_ok());
926
927 let harfrust_font = harfrust::Font::new(bytes.as_harfrust_font(), face_index);
928
929 Ok(FontFace(Arc::new(LoadedFontFace {
930 face_index,
931 display_name: custom_font.name.clone(),
932 family_name: custom_font.name,
933 postscript_name: None,
934 style: custom_font.style,
935 weight: custom_font.weight,
936 stretch: custom_font.stretch,
937 lig_carets,
938 harfrust_font,
939 m: Mutex::new(FontFaceMut {
940 instances: Default::default(),
941 render_ids: Default::default(),
942 unregistered: Default::default(),
943 }),
944 data: bytes,
945 flags,
946 })))
947 }
948
949 fn load(bytes: FontBytes, mut face_index: u32) -> Result<Self, FontLoadingError> {
950 let _span = tracing::trace_span!("FontFace::load").entered();
951
952 let ttf_face = match skrifa::FontRef::from_index(&bytes, face_index) {
953 Ok(f) => f,
954 Err(e) => {
955 match e {
956 read_fonts::ReadError::InvalidCollectionIndex(_) if face_index != 0 => face_index = 0,
958 e => return Err(FontLoadingError::Parse(e)),
959 }
960
961 match skrifa::FontRef::from_index(&bytes, face_index) {
962 Ok(f) => f,
963 Err(_) => return Err(FontLoadingError::Parse(e)),
964 }
965 }
966 };
967 use read_fonts::TableProvider as _;
968
969 let has_ligatures = ttf_face.gsub().is_ok();
970 let lig_carets = if has_ligatures {
971 LigatureCaretList::empty()
972 } else {
973 LigatureCaretList::load(&ttf_face)?
974 };
975
976 let mut display_name = None;
977 let mut family_name = None;
978 let mut postscript_name = None;
979 let mut any_name = None::<Txt>;
980 if let Ok(name) = ttf_face.name() {
981 for record in name.name_record() {
982 let n = match record.string(name.string_data()) {
983 Ok(n) => n.to_txt(),
984 Err(_) => continue,
985 };
986 match record.name_id() {
987 read_fonts::tables::name::NameId::FULL_NAME => display_name = Some(n),
988 read_fonts::tables::name::NameId::FAMILY_NAME => family_name = Some(n),
989 read_fonts::tables::name::NameId::POSTSCRIPT_NAME => postscript_name = Some(n),
990 _ => {
991 if let Some(t) = &mut any_name {
992 if t.len() < n.len() {
993 *t = n;
994 }
995 } else {
996 any_name = Some(n)
997 }
998 }
999 }
1000 }
1001 }
1002 let display_name = FontName::new(
1003 display_name
1004 .clone()
1005 .or_else(|| family_name.clone())
1006 .or_else(|| postscript_name.clone())
1007 .or_else(|| any_name.clone())
1008 .unwrap_or_default(),
1009 );
1010 let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1011 let postscript_name = postscript_name;
1012
1013 let mut flags = FontFaceFlags::empty();
1014 flags.set(
1015 FontFaceFlags::IS_MONOSPACE,
1016 ttf_face.post().map(|p| p.is_fixed_pitch() != 0).unwrap_or(false),
1017 );
1018 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1019 flags.set(
1020 FontFaceFlags::HAS_RASTER_IMAGES,
1021 ttf_face.sbix().is_ok() || ttf_face.ebdt().is_ok() || ttf_face.cbdt().is_ok(),
1022 );
1023 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.svg().is_ok());
1024
1025 let attr = ttf_face.attributes();
1026
1027 let harfrust_font = harfrust::Font::new(bytes.as_harfrust_font(), face_index);
1028
1029 Ok(FontFace(Arc::new(LoadedFontFace {
1030 face_index,
1031 family_name,
1032 display_name,
1033 postscript_name,
1034 style: attr.style.into(),
1035 weight: attr.weight.into(),
1036 stretch: attr.stretch.into(),
1037 lig_carets,
1038 harfrust_font,
1039 m: Mutex::new(FontFaceMut {
1040 instances: Default::default(),
1041 render_ids: Default::default(),
1042 unregistered: Default::default(),
1043 }),
1044 data: bytes,
1045 flags,
1046 })))
1047 }
1048
1049 fn on_refresh(&self) {
1050 let mut m = self.0.m.lock();
1051 m.instances.clear();
1052 m.unregistered = true;
1053 }
1054
1055 fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1056 let mut m = self.0.m.lock();
1057 for r in m.render_ids.iter() {
1058 if &r.renderer == renderer {
1059 return r.face_id;
1060 }
1061 }
1062
1063 let data = match self.0.data.to_ipc() {
1064 Ok(d) => d,
1065 Err(e) => {
1066 tracing::error!("cannot allocate ipc font data, {e}");
1067 return zng_view_api::font::FontFaceId::INVALID;
1068 }
1069 };
1070
1071 let key = match renderer.add_font_face(data, self.0.face_index) {
1072 Ok(k) => k,
1073 Err(_) => {
1074 tracing::debug!("respawned calling `add_font`, will return dummy font key");
1075 return zng_view_api::font::FontFaceId::INVALID;
1076 }
1077 };
1078
1079 m.render_ids.push(RenderFontFace::new(renderer, key));
1080
1081 key
1082 }
1083
1084 pub(crate) fn read_fonts_ref(&self) -> Option<read_fonts::FontRef<'_>> {
1085 if self.is_empty() {
1086 None
1087 } else {
1088 Some(read_fonts::FontRef::from_index(&self.0.data, self.0.face_index).unwrap())
1089 }
1090 }
1091
1092 pub(crate) fn harfrust_font(&self) -> Option<&harfrust::Font> {
1093 self.0.harfrust_font.as_ref()
1094 }
1095
1096 pub fn bytes(&self) -> &FontBytes {
1098 &self.0.data
1099 }
1100 pub fn index(&self) -> u32 {
1102 self.0.face_index
1103 }
1104
1105 pub fn display_name(&self) -> &FontName {
1107 &self.0.display_name
1108 }
1109
1110 pub fn family_name(&self) -> &FontName {
1112 &self.0.family_name
1113 }
1114
1115 pub fn postscript_name(&self) -> Option<&str> {
1117 self.0.postscript_name.as_deref()
1118 }
1119
1120 pub fn style(&self) -> FontStyle {
1122 self.0.style
1123 }
1124
1125 pub fn weight(&self) -> FontWeight {
1127 self.0.weight
1128 }
1129
1130 pub fn stretch(&self) -> FontStretch {
1132 self.0.stretch
1133 }
1134
1135 pub fn is_monospace(&self) -> bool {
1137 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1138 }
1139
1140 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1149 let key = FontInstanceKey::new(font_size, &variations);
1150 let mut m = self.0.m.lock();
1151 if !m.unregistered {
1152 m.instances
1153 .entry(key)
1154 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1155 .clone()
1156 } else {
1157 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1158 Font::new(self.clone(), font_size, variations)
1159 }
1160 }
1161
1162 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1164 let mut synth = FontSynthesis::DISABLED;
1165
1166 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1167 synth |= FontSynthesis::OBLIQUE;
1169 }
1170 if weight > self.weight() {
1171 synth |= FontSynthesis::BOLD;
1174 }
1175
1176 synth
1177 }
1178
1179 pub fn is_cached(&self) -> bool {
1183 !self.0.m.lock().unregistered
1184 }
1185
1186 pub fn color_palettes(&self) -> ColorPalettes<'_> {
1190 match self.read_fonts_ref() {
1191 Some(ttf) => ColorPalettes::new(ttf),
1192 None => ColorPalettes::empty(),
1193 }
1194 }
1195
1196 pub fn color_glyphs(&self) -> ColorGlyphs<'_> {
1200 match self.read_fonts_ref() {
1201 Some(ttf) => ColorGlyphs::new(ttf),
1202 None => ColorGlyphs::empty(),
1203 }
1204 }
1205
1206 pub fn has_ligatures(&self) -> bool {
1208 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1209 }
1210
1211 pub fn has_ligature_caret_offsets(&self) -> bool {
1216 !self.0.lig_carets.is_empty()
1217 }
1218
1219 pub fn has_raster_images(&self) -> bool {
1221 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1222 }
1223
1224 pub fn has_svg_images(&self) -> bool {
1226 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1227 }
1228}
1229
1230#[derive(Clone)]
1236pub struct Font(Arc<LoadedFont>);
1237struct LoadedFont {
1238 face: FontFace,
1239 size: Px,
1240 variations: RFontVariations,
1241 metrics: FontMetrics,
1242 render_keys: Mutex<Vec<RenderFont>>,
1243 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1244 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1245}
1246impl fmt::Debug for Font {
1247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1248 f.debug_struct("Font")
1249 .field("face", &self.0.face)
1250 .field("size", &self.0.size)
1251 .field("metrics", &self.0.metrics)
1252 .field("render_keys.len()", &self.0.render_keys.lock().len())
1253 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1254 .field("word_cache.len()", &self.0.word_cache.read().len())
1255 .finish()
1256 }
1257}
1258impl PartialEq for Font {
1259 fn eq(&self, other: &Self) -> bool {
1260 Arc::ptr_eq(&self.0, &other.0)
1261 }
1262}
1263impl Eq for Font {}
1264impl Font {
1265 const SMALL_WORD_LEN: usize = 8;
1266
1267 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1268 if s.len() <= Self::SMALL_WORD_LEN {
1269 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1270 a[..s.len()].copy_from_slice(s.as_bytes());
1271 Some(a)
1272 } else {
1273 None
1274 }
1275 }
1276
1277 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1278 let metrics = match face.read_fonts_ref() {
1279 Some(f) => FontMetrics::new(&f, size),
1280 None => FontMetrics::empty(),
1281 };
1282
1283 Font(Arc::new(LoadedFont {
1284 metrics,
1285 face,
1286 size,
1287 variations,
1288 render_keys: Mutex::new(vec![]),
1289 small_word_cache: RwLock::default(),
1290 word_cache: RwLock::default(),
1291 }))
1292 }
1293
1294 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1295 let _span = tracing::trace_span!("Font::render_font").entered();
1296
1297 let mut render_keys = self.0.render_keys.lock();
1298 for r in render_keys.iter() {
1299 if &r.renderer == renderer && r.synthesis == synthesis {
1300 return r.font_id;
1301 }
1302 }
1303
1304 let font_key = self.0.face.render_face(renderer);
1305
1306 let mut opt = zng_view_api::font::FontOptions::default();
1307 opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1308 opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1309 let variations = self.0.variations.iter().map(|v| (v.tag.to_be_bytes(), v.value)).collect();
1310
1311 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1312 Ok(k) => k,
1313 Err(_) => {
1314 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1315 return zng_view_api::font::FontId::INVALID;
1316 }
1317 };
1318
1319 render_keys.push(RenderFont::new(renderer, synthesis, key));
1320
1321 key
1322 }
1323
1324 pub fn face(&self) -> &FontFace {
1326 &self.0.face
1327 }
1328
1329 pub fn size(&self) -> Px {
1333 self.0.size
1334 }
1335
1336 pub fn variations(&self) -> &RFontVariations {
1338 &self.0.variations
1339 }
1340
1341 pub fn metrics(&self) -> &FontMetrics {
1343 &self.0.metrics
1344 }
1345
1346 pub fn ligature_caret_offsets(
1352 &self,
1353 lig: zng_view_api::font::GlyphIndex,
1354 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1355 self.0.face.0.lig_carets.carets(lig).iter().map(move |&o| match o {
1356 ligature_util::LigatureCaret::Coordinate(o) => {
1357 let size_scale = 1.0 / self.0.metrics.units_per_em as f32 * self.0.size.0 as f32;
1358 o as f32 * size_scale
1359 }
1360 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1361 if let Some(f) = self.face().read_fonts_ref() {
1362 struct Search {
1363 i: u16,
1364 s: u16,
1365 x: f32,
1366 }
1367 impl Search {
1368 fn check(&mut self, x: f32) {
1369 self.s = self.s.saturating_add(1);
1370 if self.s == self.i {
1371 self.x = x;
1372 }
1373 }
1374 }
1375 impl skrifa::outline::OutlinePen for Search {
1376 fn move_to(&mut self, x: f32, _y: f32) {
1377 self.check(x);
1378 }
1379
1380 fn line_to(&mut self, x: f32, _y: f32) {
1381 self.check(x);
1382 }
1383
1384 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1385 self.check(x)
1386 }
1387
1388 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1389 self.check(x);
1390 }
1391
1392 fn close(&mut self) {}
1393 }
1394 let mut search = Search { i, s: 0, x: 0.0 };
1395 if let Some(o) = f.outline_glyphs().get(skrifa::GlyphId::new(lig))
1396 && o.draw(
1397 skrifa::outline::DrawSettings::unhinted(
1398 skrifa::instance::Size::new(self.size().0 as f32),
1399 skrifa::instance::LocationRef::default(),
1400 ),
1401 &mut search,
1402 )
1403 .is_ok()
1404 && search.s >= search.i
1405 {
1406 return search.x;
1407 }
1408 }
1409 0.0
1410 }
1411 })
1412 }
1413}
1414impl zng_app::render::Font for Font {
1415 fn is_empty_fallback(&self) -> bool {
1416 self.face().is_empty()
1417 }
1418
1419 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1420 self.render_font(renderer, synthesis)
1421 }
1422}
1423
1424#[derive(Debug, Clone)]
1428pub struct FontFaceList {
1429 fonts: Box<[FontFace]>,
1430 requested_style: FontStyle,
1431 requested_weight: FontWeight,
1432 requested_stretch: FontStretch,
1433}
1434impl FontFaceList {
1435 pub fn empty() -> Self {
1437 Self {
1438 fonts: Box::new([FontFace::empty()]),
1439 requested_style: FontStyle::Normal,
1440 requested_weight: FontWeight::NORMAL,
1441 requested_stretch: FontStretch::NORMAL,
1442 }
1443 }
1444
1445 pub fn requested_style(&self) -> FontStyle {
1447 self.requested_style
1448 }
1449
1450 pub fn requested_weight(&self) -> FontWeight {
1452 self.requested_weight
1453 }
1454
1455 pub fn requested_stretch(&self) -> FontStretch {
1457 self.requested_stretch
1458 }
1459
1460 pub fn best(&self) -> &FontFace {
1462 &self.fonts[0]
1463 }
1464
1465 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1467 if let Some(face) = self.fonts.get(face_index) {
1468 face.synthesis_for(self.requested_style, self.requested_weight)
1469 } else {
1470 FontSynthesis::DISABLED
1471 }
1472 }
1473
1474 pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1476 self.fonts.iter()
1477 }
1478
1479 pub fn len(&self) -> usize {
1483 self.fonts.len()
1484 }
1485
1486 pub fn is_empty(&self) -> bool {
1488 self.fonts[0].is_empty() && self.fonts.len() == 1
1489 }
1490
1491 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1495 FontList {
1496 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1497 requested_style: self.requested_style,
1498 requested_weight: self.requested_weight,
1499 requested_stretch: self.requested_stretch,
1500 }
1501 }
1502}
1503impl PartialEq for FontFaceList {
1504 fn eq(&self, other: &Self) -> bool {
1506 self.requested_style == other.requested_style
1507 && self.requested_weight == other.requested_weight
1508 && self.requested_stretch == other.requested_stretch
1509 && self.fonts.len() == other.fonts.len()
1510 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1511 }
1512}
1513impl Eq for FontFaceList {}
1514impl std::ops::Deref for FontFaceList {
1515 type Target = [FontFace];
1516
1517 fn deref(&self) -> &Self::Target {
1518 &self.fonts
1519 }
1520}
1521impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1522 type Item = &'a FontFace;
1523
1524 type IntoIter = std::slice::Iter<'a, FontFace>;
1525
1526 fn into_iter(self) -> Self::IntoIter {
1527 self.iter()
1528 }
1529}
1530impl std::ops::Index<usize> for FontFaceList {
1531 type Output = FontFace;
1532
1533 fn index(&self, index: usize) -> &Self::Output {
1534 &self.fonts[index]
1535 }
1536}
1537
1538#[derive(Debug, Clone)]
1540pub struct FontList {
1541 fonts: Box<[Font]>,
1542 requested_style: FontStyle,
1543 requested_weight: FontWeight,
1544 requested_stretch: FontStretch,
1545}
1546#[expect(clippy::len_without_is_empty)] impl FontList {
1548 pub fn best(&self) -> &Font {
1550 &self.fonts[0]
1551 }
1552
1553 pub fn requested_size(&self) -> Px {
1555 self.fonts[0].size()
1556 }
1557
1558 pub fn requested_style(&self) -> FontStyle {
1560 self.requested_style
1561 }
1562
1563 pub fn requested_weight(&self) -> FontWeight {
1565 self.requested_weight
1566 }
1567
1568 pub fn requested_stretch(&self) -> FontStretch {
1570 self.requested_stretch
1571 }
1572
1573 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1575 if let Some(font) = self.fonts.get(font_index) {
1576 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1577 } else {
1578 FontSynthesis::DISABLED
1579 }
1580 }
1581
1582 pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1584 self.fonts.iter()
1585 }
1586
1587 pub fn len(&self) -> usize {
1591 self.fonts.len()
1592 }
1593
1594 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1596 if self.len() != faces.len() {
1597 return false;
1598 }
1599
1600 for (font, face) in self.iter().zip(faces.iter()) {
1601 if font.face() != face {
1602 return false;
1603 }
1604 }
1605
1606 true
1607 }
1608}
1609impl PartialEq for FontList {
1610 fn eq(&self, other: &Self) -> bool {
1612 self.requested_style == other.requested_style
1613 && self.requested_weight == other.requested_weight
1614 && self.requested_stretch == other.requested_stretch
1615 && self.fonts.len() == other.fonts.len()
1616 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1617 }
1618}
1619impl Eq for FontList {}
1620impl std::ops::Deref for FontList {
1621 type Target = [Font];
1622
1623 fn deref(&self) -> &Self::Target {
1624 &self.fonts
1625 }
1626}
1627impl<'a> std::iter::IntoIterator for &'a FontList {
1628 type Item = &'a Font;
1629
1630 type IntoIter = std::slice::Iter<'a, Font>;
1631
1632 fn into_iter(self) -> Self::IntoIter {
1633 self.iter()
1634 }
1635}
1636impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1637 type Output = I::Output;
1638
1639 fn index(&self, index: I) -> &I::Output {
1640 &self.fonts[index]
1641 }
1642}
1643
1644struct FontFaceLoader {
1645 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1646
1647 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1648 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1649}
1650struct SystemFontFace {
1651 properties: (FontStyle, FontWeight, FontStretch),
1652 result: ResponseVar<Option<FontFace>>,
1653}
1654struct FontFaceListQuery {
1655 properties: (FontStyle, FontWeight, FontStretch),
1656 lang: Lang,
1657 result: ResponseVar<FontFaceList>,
1658}
1659impl FontFaceLoader {
1660 fn new() -> Self {
1661 FontFaceLoader {
1662 custom_fonts: HashMap::new(),
1663 system_fonts_cache: HashMap::new(),
1664 list_cache: HashMap::new(),
1665 }
1666 }
1667
1668 fn on_view_process_respawn(&mut self) {
1669 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1670 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1671 let mut m = face.0.m.lock();
1672 m.render_ids.clear();
1673 for inst in m.instances.values() {
1674 inst.0.render_keys.lock().clear();
1675 }
1676 }
1677 }
1678
1679 fn on_refresh(&mut self) {
1680 for (_, sys_family) in self.system_fonts_cache.drain() {
1681 for sys_font in sys_family {
1682 sys_font.result.with(|r| {
1683 if let Some(Some(face)) = r.done() {
1684 face.on_refresh();
1685 }
1686 });
1687 }
1688 }
1689 }
1690 fn on_prune(&mut self) {
1691 self.system_fonts_cache.retain(|_, v| {
1692 v.retain(|sff| {
1693 if sff.result.strong_count() == 1 {
1694 sff.result.with(|r| {
1695 match r.done() {
1696 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1700 })
1701 } else {
1702 true
1704 }
1705 });
1706 !v.is_empty()
1707 });
1708
1709 self.list_cache.clear();
1710 }
1711
1712 fn try_list(
1713 &self,
1714 families: &[FontName],
1715 style: FontStyle,
1716 weight: FontWeight,
1717 stretch: FontStretch,
1718 lang: &Lang,
1719 ) -> Option<ResponseVar<FontFaceList>> {
1720 if let Some(queries) = self.list_cache.get(families) {
1721 for q in queries {
1722 if q.properties == (style, weight, stretch) && &q.lang == lang {
1723 return Some(q.result.clone());
1724 }
1725 }
1726 }
1727 None
1728 }
1729
1730 fn load_list(
1731 &mut self,
1732 families: &[FontName],
1733 style: FontStyle,
1734 weight: FontWeight,
1735 stretch: FontStretch,
1736 lang: &Lang,
1737 ) -> ResponseVar<FontFaceList> {
1738 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1739 return r;
1740 }
1741
1742 let resolved = GenericFonts {}.resolve_list(families, lang);
1743 let families = resolved.as_ref().map(|n| &***n).unwrap_or(families);
1744 let mut list = Vec::with_capacity(families.len() + 1);
1745 let mut pending = vec![];
1746
1747 {
1748 let fallback = [GenericFonts {}.fallback(lang)];
1749 let mut used = HashSet::with_capacity(families.len());
1750 for name in families.iter().chain(&fallback) {
1751 if !used.insert(name) {
1752 continue;
1753 }
1754
1755 let face = self.load_resolved(name, style, weight, stretch);
1756 if face.is_done() {
1757 if let Some(face) = face.rsp().unwrap() {
1758 list.push(face);
1759 }
1760 } else {
1761 pending.push((list.len(), face));
1762 }
1763 }
1764 }
1765
1766 let r = if pending.is_empty() {
1767 if list.is_empty() {
1768 tracing::error!(target: "font_loading", "failed to load fallback font");
1769 list.push(FontFace::empty());
1770 }
1771 response_done_var(FontFaceList {
1772 fonts: list.into_boxed_slice(),
1773 requested_style: style,
1774 requested_weight: weight,
1775 requested_stretch: stretch,
1776 })
1777 } else {
1778 task::respond(async move {
1779 for (i, pending) in pending.into_iter().rev() {
1780 if let Some(rsp) = pending.wait_rsp().await {
1781 list.insert(i, rsp);
1782 }
1783 }
1784
1785 if list.is_empty() {
1786 tracing::error!(target: "font_loading", "failed to load fallback font");
1787 list.push(FontFace::empty());
1788 }
1789
1790 FontFaceList {
1791 fonts: list.into_boxed_slice(),
1792 requested_style: style,
1793 requested_weight: weight,
1794 requested_stretch: stretch,
1795 }
1796 })
1797 };
1798
1799 self.list_cache
1800 .entry(families.iter().cloned().collect())
1801 .or_insert_with(|| Vec::with_capacity(1))
1802 .push(FontFaceListQuery {
1803 properties: (style, weight, stretch),
1804 lang: lang.clone(),
1805 result: r.clone(),
1806 });
1807
1808 r
1809 }
1810
1811 fn try_resolved(
1813 &self,
1814 font_name: &FontName,
1815 style: FontStyle,
1816 weight: FontWeight,
1817 stretch: FontStretch,
1818 ) -> Option<ResponseVar<Option<FontFace>>> {
1819 if let Some(custom_family) = self.custom_fonts.get(font_name) {
1820 let custom = Self::match_custom(custom_family, style, weight, stretch);
1821 return Some(response_done_var(Some(custom)));
1822 }
1823
1824 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
1825 for sys_face in cached_sys_family.iter() {
1826 if sys_face.properties == (style, weight, stretch) {
1827 return Some(sys_face.result.clone());
1828 }
1829 }
1830 }
1831
1832 None
1833 }
1834
1835 fn load_resolved(
1837 &mut self,
1838 font_name: &FontName,
1839 style: FontStyle,
1840 weight: FontWeight,
1841 stretch: FontStretch,
1842 ) -> ResponseVar<Option<FontFace>> {
1843 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
1844 return cached;
1845 }
1846
1847 let load = task::wait(clmv!(font_name, || {
1848 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
1849 Some(h) => h,
1850 None => {
1851 #[cfg(debug_assertions)]
1852 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
1853
1854 #[cfg(debug_assertions)]
1855 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
1856 tracing::debug!(r#"font "{font_name}" not found"#);
1857 }
1858
1859 return None;
1860 }
1861 };
1862 match FontFace::load(bytes, face_index) {
1863 Ok(f) => Some(f),
1864 Err(FontLoadingError::UnknownFormat) => None,
1865 Err(e) => {
1866 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
1867 None
1868 }
1869 }
1870 }));
1871 let result = task::respond(async_clmv!(font_name, {
1872 match task::with_deadline(load, 10.secs()).await {
1873 Ok(r) => r,
1874 Err(_) => {
1875 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
1876 None
1877 }
1878 }
1879 }));
1880
1881 self.system_fonts_cache
1882 .entry(font_name.clone())
1883 .or_insert_with(|| Vec::with_capacity(1))
1884 .push(SystemFontFace {
1885 properties: (style, weight, stretch),
1886 result: result.clone(),
1887 });
1888
1889 result
1890 }
1891
1892 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontBytes, u32)> {
1893 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
1894 match query_util::best(font_name, style, weight, stretch) {
1895 Ok(r) => r,
1896 Err(e) => {
1897 tracing::error!("cannot get `{font_name}` system font, {e}");
1898 None
1899 }
1900 }
1901 }
1902
1903 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
1904 if faces.len() == 1 {
1905 return faces[0].clone();
1907 }
1908
1909 let mut set = Vec::with_capacity(faces.len());
1910 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
1917 |s| s > FontStretch::NORMAL
1918 } else {
1919 |s| s <= FontStretch::NORMAL
1920 };
1921 for face in faces {
1922 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
1923 if wrong_side(face.stretch()) {
1924 dist += f32::MAX as f64 + 1.0;
1925 }
1926
1927 if set.is_empty() {
1928 set.push(face);
1929 set_dist = dist;
1930 } else if dist < set_dist {
1931 set_dist = dist;
1933 set.clear();
1934 set.push(face);
1935 } else if (dist - set_dist).abs() < 0.0001 {
1936 set.push(face);
1938 }
1939 }
1940 if set.len() == 1 {
1941 return set[0].clone();
1942 }
1943
1944 let style_pref = match style {
1949 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
1950 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
1951 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
1952 };
1953 let mut best_style = style_pref.len();
1954 for face in &set {
1955 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
1956 if i < best_style {
1957 best_style = i;
1958 }
1959 }
1960 set.retain(|f| f.style() == style_pref[best_style]);
1961 if set.len() == 1 {
1962 return set[0].clone();
1963 }
1964
1965 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
1973 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1975 if face.weight() < weight {
1977 *dist += 100.0;
1979 } else if face.weight().0 > 500.0 {
1980 *dist += 600.0;
1982 }
1983 }
1984 } else if weight.0 < 400.0 {
1985 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1987 if face.weight() > weight {
1988 *dist += weight.0 as f64;
1989 }
1990 }
1991 } else {
1992 debug_assert!(weight.0 > 500.0);
1993 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1995 if face.weight() < weight {
1996 *dist += f32::MAX as f64;
1997 }
1998 }
1999 };
2000
2001 let mut best = set[0];
2002 let mut best_dist = f64::MAX;
2003
2004 for face in &set {
2005 let mut dist = (face.weight().0 - weight.0).abs() as f64;
2006
2007 add_penalty(face, weight, &mut dist);
2008
2009 if dist < best_dist {
2010 best_dist = dist;
2011 best = face;
2012 }
2013 }
2014
2015 best.clone()
2016 }
2017}
2018
2019struct RenderFontFace {
2020 renderer: ViewRenderer,
2021 face_id: zng_view_api::font::FontFaceId,
2022}
2023impl RenderFontFace {
2024 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2025 RenderFontFace {
2026 renderer: renderer.clone(),
2027 face_id,
2028 }
2029 }
2030}
2031impl Drop for RenderFontFace {
2032 fn drop(&mut self) {
2033 let _ = self.renderer.delete_font_face(self.face_id);
2035 }
2036}
2037
2038struct RenderFont {
2039 renderer: ViewRenderer,
2040 synthesis: FontSynthesis,
2041 font_id: zng_view_api::font::FontId,
2042}
2043impl RenderFont {
2044 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2045 RenderFont {
2046 renderer: renderer.clone(),
2047 synthesis,
2048 font_id,
2049 }
2050 }
2051}
2052impl Drop for RenderFont {
2053 fn drop(&mut self) {
2054 let _ = self.renderer.delete_font(self.font_id);
2056 }
2057}
2058
2059app_local! {
2060 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2061}
2062
2063struct GenericFontsService {
2064 serif: LangMap<FontName>,
2065 sans_serif: LangMap<FontName>,
2066 monospace: LangMap<FontName>,
2067 cursive: LangMap<FontName>,
2068 fantasy: LangMap<FontName>,
2069 fallback: LangMap<FontName>,
2070 system_ui: LangMap<FontNames>,
2071}
2072impl GenericFontsService {
2073 fn new() -> Self {
2074 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2075 let mut f = LangMap::with_capacity(1);
2076 f.insert(lang!(und), name.into());
2077 f
2078 }
2079
2080 let serif = "serif";
2081 let sans_serif = "sans-serif";
2082 let monospace = "monospace";
2083 let cursive = "cursive";
2084 let fantasy = "fantasy";
2085 let fallback = if cfg!(windows) {
2086 "Segoe UI Symbol"
2087 } else if cfg!(target_os = "linux") {
2088 "Standard Symbols PS"
2089 } else {
2090 "sans-serif"
2091 };
2092
2093 let mut system_ui = LangMap::with_capacity(5);
2094
2095 if cfg!(windows) {
2096 system_ui.insert(
2097 lang!("zh-Hans"),
2098 ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into(),
2099 );
2100 system_ui.insert(
2101 lang!("zh-Hant"),
2102 ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into(),
2103 );
2104 system_ui.insert(
2105 lang!("ja"),
2106 ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into(),
2107 );
2108 system_ui.insert(
2109 lang!("ko"),
2110 ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into(),
2111 );
2112 for lang in [
2113 lang!("hi"),
2114 lang!("bn"),
2115 lang!("te"),
2116 lang!("as"),
2117 lang!("gu"),
2118 lang!("kn"),
2119 lang!("mr"),
2120 lang!("ne"),
2121 lang!("or"),
2122 lang!("pa"),
2123 lang!("si"),
2124 ] {
2125 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Mangal", "Segoe Ui Emoji", "sans-serif"].into());
2126 }
2127 system_ui.insert(lang!("am"), ["Segoe UI", "Nyala", "Ebrima", "Segoe Ui Emoji", "sans-serif"].into());
2128 system_ui.insert(
2129 lang!("km"),
2130 ["Segoe UI", "Khmer UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2131 );
2132 system_ui.insert(
2133 lang!("lo"),
2134 ["Segoe UI", "lao UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2135 );
2136 system_ui.insert(lang!("th"), ["Segoe UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into());
2137 for lang in [lang!("ml"), lang!("ta")] {
2138 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Segoe Ui Emoji", "sans-serif"].into());
2139 }
2140 system_ui.insert(lang!("my"), ["Segoe UI", "Myanmar Text", "Segoe Ui Emoji", "sans-serif"].into());
2141
2142 system_ui.insert(lang!(und), ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into());
2143 } else if cfg!(target_os = "macos") {
2144 system_ui.insert(
2145 lang!("zh-Hans"),
2146 ["system-ui", "PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into(),
2147 );
2148 system_ui.insert(
2149 lang!("zh-Hant"),
2150 ["system-ui", "PingFang TC", "Apple Color Emoji", "sans-serif"].into(),
2151 );
2152 system_ui.insert(
2153 lang!("ja"),
2154 [
2155 "system-ui",
2156 "Hiragino Sans",
2157 "Hiragino Kaku Gothic ProN",
2158 "Apple Color Emoji",
2159 "sans-serif",
2160 ]
2161 .into(),
2162 );
2163 system_ui.insert(
2164 lang!("ko"),
2165 ["system-ui", "Apple SD Gothic Neo", "NanumGothic", "Apple Color Emoji", "sans-serif"].into(),
2166 );
2167
2168 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2169 system_ui.insert(
2170 lang,
2171 [
2172 "system-ui",
2173 "Kohinoor Devanagari",
2174 "Devanagari Sangam MN",
2175 "Apple Color Emoji",
2176 "sans-serif",
2177 ]
2178 .into(),
2179 );
2180 }
2181 for lang in [lang!("bn"), lang!("as")] {
2182 system_ui.insert(
2183 lang,
2184 [
2185 "system-ui",
2186 "Kohinoor Bangla",
2187 "Bangla Sangam MN",
2188 "Apple Color Emoji",
2189 "sans-serif",
2190 ]
2191 .into(),
2192 );
2193 }
2194 system_ui.insert(
2195 lang!("te"),
2196 [
2197 "system-ui",
2198 "Kohinoor Telugu",
2199 "Telugu Sangam MN",
2200 "Apple Color Emoji",
2201 "sans-serif",
2202 ]
2203 .into(),
2204 );
2205 system_ui.insert(
2206 lang!("gu"),
2207 [
2208 "system-ui",
2209 "Kohinoor Gujarati",
2210 "Gujarati Sangam MN",
2211 "Apple Color Emoji",
2212 "sans-serif",
2213 ]
2214 .into(),
2215 );
2216 system_ui.insert(
2217 lang!("kn"),
2218 ["system-ui", "Kannada Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2219 );
2220 system_ui.insert(
2221 lang!("or"),
2222 ["system-ui", "Oriya Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2223 );
2224 system_ui.insert(
2225 lang!("pa"),
2226 ["system-ui", "Mukta Mahee", "Gurmukhi Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2227 );
2228 system_ui.insert(
2229 lang!("si"),
2230 ["system-ui", "Sinhala Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2231 );
2232
2233 system_ui.insert(lang!("am"), ["system-ui", "Kefa", "Apple Color Emoji", "sans-serif"].into());
2234 system_ui.insert(
2235 lang!("km"),
2236 ["system-ui", "Khmer Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2237 );
2238 system_ui.insert(
2239 lang!("lo"),
2240 ["system-ui", "Lao Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2241 );
2242 system_ui.insert(
2243 lang!("th"),
2244 ["system-ui", "Thonburi", "Ayuthaya", "Apple Color Emoji", "sans-serif"].into(),
2245 );
2246 system_ui.insert(
2247 lang!("my"),
2248 ["system-ui", "Myanmar Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2249 );
2250
2251 system_ui.insert(
2252 lang!("ml"),
2253 ["system-ui", "Malayalam Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2254 );
2255 system_ui.insert(
2256 lang!("ta"),
2257 ["system-ui", "Kohinoor Tamil", "Tamil Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2258 );
2259
2260 for lang in [lang!("ar"), lang!("fa"), lang!("ps")] {
2261 system_ui.insert(lang, ["system-ui", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into());
2262 }
2263
2264 system_ui.insert(lang!("he"), ["system-ui", "Arial Hebrew", "Apple Color Emoji", "sans-serif"].into());
2265
2266 system_ui.insert(lang!("hy"), ["system-ui", "Mshtakan", "Apple Color Emoji", "sans-serif"].into());
2267
2268 system_ui.insert(
2269 lang!("ka"),
2270 ["system-ui", "Helvetica Neue", "Apple Color Emoji", "sans-serif"].into(),
2271 );
2272
2273 system_ui.insert(
2274 lang!("ur"),
2275 ["system-ui", "SF Arabic", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into(),
2276 );
2277
2278 system_ui.insert(
2279 lang!(und),
2280 ["system-ui", "Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into(),
2281 );
2282 } else if cfg!(target_os = "linux") {
2283 system_ui.insert(
2284 lang!("zh-Hans"),
2285 [
2286 "system-ui",
2287 "Ubuntu",
2288 "Noto Sans CJK SC",
2289 "Source Han Sans SC",
2290 "Noto Color Emoji",
2291 "sans-serif",
2292 ]
2293 .into(),
2294 );
2295 system_ui.insert(
2296 lang!("zh-Hant"),
2297 [
2298 "system-ui",
2299 "Ubuntu",
2300 "Noto Sans CJK TC",
2301 "Source Han Sans TC",
2302 "Noto Color Emoji",
2303 "sans-serif",
2304 ]
2305 .into(),
2306 );
2307 system_ui.insert(
2308 lang!("ja"),
2309 [
2310 "system-ui",
2311 "Ubuntu",
2312 "Noto Sans CJK JP",
2313 "Source Han Sans JP",
2314 "Noto Color Emoji",
2315 "sans-serif",
2316 ]
2317 .into(),
2318 );
2319 system_ui.insert(
2320 lang!("ko"),
2321 [
2322 "system-ui",
2323 "Ubuntu",
2324 "Noto Sans CJK KR",
2325 "Source Han Sans KR",
2326 "UnDotum",
2327 "Noto Color Emoji",
2328 "sans-serif",
2329 ]
2330 .into(),
2331 );
2332
2333 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2334 system_ui.insert(
2335 lang,
2336 [
2337 "system-ui",
2338 "Ubuntu",
2339 "Noto Sans Devanagari",
2340 "Lohit Devanagari",
2341 "Noto Color Emoji",
2342 "sans-serif",
2343 ]
2344 .into(),
2345 );
2346 }
2347 for lang in [lang!("bn"), lang!("as")] {
2348 system_ui.insert(
2349 lang,
2350 [
2351 "system-ui",
2352 "Ubuntu",
2353 "Noto Sans Bengali",
2354 "Lohit Bengali",
2355 "Noto Color Emoji",
2356 "sans-serif",
2357 ]
2358 .into(),
2359 );
2360 }
2361 system_ui.insert(
2362 lang!("te"),
2363 [
2364 "system-ui",
2365 "Ubuntu",
2366 "Noto Sans Telugu",
2367 "Lohit Telugu",
2368 "Noto Color Emoji",
2369 "sans-serif",
2370 ]
2371 .into(),
2372 );
2373 system_ui.insert(
2374 lang!("gu"),
2375 [
2376 "system-ui",
2377 "Ubuntu",
2378 "Noto Sans Gujarati",
2379 "Lohit Gujarati",
2380 "Noto Color Emoji",
2381 "sans-serif",
2382 ]
2383 .into(),
2384 );
2385 system_ui.insert(
2386 lang!("kn"),
2387 [
2388 "system-ui",
2389 "Ubuntu",
2390 "Noto Sans Kannada",
2391 "Lohit Kannada",
2392 "Noto Color Emoji",
2393 "sans-serif",
2394 ]
2395 .into(),
2396 );
2397 system_ui.insert(
2398 lang!("or"),
2399 [
2400 "system-ui",
2401 "Ubuntu",
2402 "Noto Sans Oriya",
2403 "Lohit Odia",
2404 "Noto Color Emoji",
2405 "sans-serif",
2406 ]
2407 .into(),
2408 );
2409 system_ui.insert(
2410 lang!("pa"),
2411 [
2412 "system-ui",
2413 "Ubuntu",
2414 "Noto Sans Gurmukhi",
2415 "Lohit Gurmukhi",
2416 "Noto Color Emoji",
2417 "sans-serif",
2418 ]
2419 .into(),
2420 );
2421 system_ui.insert(
2422 lang!("si"),
2423 [
2424 "system-ui",
2425 "Ubuntu",
2426 "Noto Sans Sinhala",
2427 "LKLUG",
2428 "Noto Color Emoji",
2429 "sans-serif",
2430 ]
2431 .into(),
2432 );
2433
2434 system_ui.insert(
2435 lang!("am"),
2436 [
2437 "system-ui",
2438 "Ubuntu",
2439 "Noto Sans Ethiopic",
2440 "Abyssinica SIL",
2441 "Noto Color Emoji",
2442 "sans-serif",
2443 ]
2444 .into(),
2445 );
2446 system_ui.insert(
2447 lang!("km"),
2448 [
2449 "system-ui",
2450 "Ubuntu",
2451 "Noto Sans Khmer",
2452 "Hanuman",
2453 "Noto Color Emoji",
2454 "sans-serif",
2455 ]
2456 .into(),
2457 );
2458 system_ui.insert(
2459 lang!("lo"),
2460 [
2461 "system-ui",
2462 "Ubuntu",
2463 "Noto Sans Lao",
2464 "Phetsarath OT",
2465 "Noto Color Emoji",
2466 "sans-serif",
2467 ]
2468 .into(),
2469 );
2470 system_ui.insert(
2471 lang!("th"),
2472 [
2473 "system-ui",
2474 "Ubuntu",
2475 "Noto Sans Thai",
2476 "Kinnari",
2477 "Garuda",
2478 "Noto Color Emoji",
2479 "sans-serif",
2480 ]
2481 .into(),
2482 );
2483 system_ui.insert(
2484 lang!("my"),
2485 [
2486 "system-ui",
2487 "Ubuntu",
2488 "Noto Sans Myanmar",
2489 "Padauk",
2490 "Noto Color Emoji",
2491 "sans-serif",
2492 ]
2493 .into(),
2494 );
2495
2496 system_ui.insert(
2497 lang!("ml"),
2498 [
2499 "system-ui",
2500 "Ubuntu",
2501 "Noto Sans Malayalam",
2502 "Lohit Malayalam",
2503 "Noto Color Emoji",
2504 "sans-serif",
2505 ]
2506 .into(),
2507 );
2508 system_ui.insert(
2509 lang!("ta"),
2510 [
2511 "system-ui",
2512 "Ubuntu",
2513 "Noto Sans Tamil",
2514 "Lohit Tamil",
2515 "Noto Color Emoji",
2516 "sans-serif",
2517 ]
2518 .into(),
2519 );
2520
2521 for lang in [lang!("ar"), lang!("fa"), lang!("ps"), lang!("ur")] {
2522 system_ui.insert(lang, ["system-ui", "Noto Sans Arabic", "Noto Color Emoji", "sans-serif"].into());
2523 }
2524
2525 system_ui.insert(
2526 lang!("he"),
2527 ["system-ui", "Noto Sans Hebrew", "Noto Color Emoji", "sans-serif"].into(),
2528 );
2529
2530 system_ui.insert(
2531 lang!("hy"),
2532 ["system-ui", "Noto Sans Armenian", "Noto Color Emoji", "sans-serif"].into(),
2533 );
2534
2535 system_ui.insert(lang!("ka"), ["system-ui", "DejaVu Sans", "Noto Color Emoji", "sans-serif"].into());
2536
2537 system_ui.insert(
2538 lang!("ur"),
2539 [
2540 "system-ui",
2541 "Noto Naskh Arabic",
2542 "Noto Sans Arabic",
2543 "Noto Color Emoji",
2544 "sans-serif",
2545 ]
2546 .into(),
2547 );
2548
2549 system_ui.insert(
2550 lang!(und),
2551 ["system-ui", "Ubuntu", "Droid Sans", "Noto Sans", "Noto Color Emoji", "sans-serif"].into(),
2552 );
2553 } else {
2554 system_ui.insert(lang!(und), ["system-ui", "sans-serif"].into());
2555 }
2556
2557 GenericFontsService {
2558 serif: default(serif),
2559 sans_serif: default(sans_serif),
2560 monospace: default(monospace),
2561 cursive: default(cursive),
2562 fantasy: default(fantasy),
2563
2564 system_ui,
2565
2566 fallback: default(fallback),
2567 }
2568 }
2569}
2570
2571#[non_exhaustive]
2582pub struct GenericFonts {}
2583macro_rules! impl_fallback_accessors {
2584 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2585 #[doc = "Gets the *"$name_str "* font for the given language."]
2586 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2590
2591 pub fn $name(&self, lang: &Lang) -> FontName {
2592 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2593 }
2594
2595 #[doc = "Sets the *"$name_str "* font for the given language."]
2596 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2601 self.[<set_ $name _impl>](lang, font_name.into());
2602 }
2603 fn [<set_ $name _impl>](&self, lang: Lang, font_name: FontName) {
2604 UPDATES.once_update("GenericFonts.set", move || {
2605 GENERIC_FONTS_SV.write().$name.insert(lang.clone(), font_name);
2606 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2607 });
2608 }
2609 })+};
2610}
2611impl GenericFonts {
2612 #[rustfmt::skip] impl_fallback_accessors! {
2614 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2615 }
2616
2617 pub fn system_ui(&self, lang: &Lang) -> FontNames {
2623 GENERIC_FONTS_SV.read().system_ui.get(lang).unwrap().clone()
2624 }
2625
2626 pub fn set_system_ui(&self, lang: Lang, font_names: impl Into<FontNames>) {
2632 self.set_system_ui_impl(lang, font_names.into())
2633 }
2634 fn set_system_ui_impl(&self, lang: Lang, font_names: FontNames) {
2635 UPDATES.once_update("GenericFonts.set_system_ui", move || {
2636 GENERIC_FONTS_SV.write().system_ui.insert(lang.clone(), font_names);
2637 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::system_ui(), lang)));
2638 });
2639 }
2640
2641 pub fn fallback(&self, lang: &Lang) -> FontName {
2645 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2646 }
2647
2648 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2654 self.set_fallback_impl(lang, font_name.into());
2655 }
2656 fn set_fallback_impl(&self, lang: Lang, font_name: FontName) {
2657 UPDATES.once_update("GenericFonts.set", move || {
2658 GENERIC_FONTS_SV.write().fallback.insert(lang.clone(), font_name);
2659 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang)));
2660 });
2661 }
2662
2663 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2671 match &**name {
2672 "serif" => Some(self.serif(lang)),
2673 "sans-serif" => Some(self.sans_serif(lang)),
2674 "monospace" => Some(self.monospace(lang)),
2675 "cursive" => Some(self.cursive(lang)),
2676 "fantasy" => Some(self.fantasy(lang)),
2677 _ => None,
2678 }
2679 }
2680
2681 pub fn resolve_list(&self, names: &[FontName], lang: &Lang) -> Option<FontNames> {
2685 if names
2686 .iter()
2687 .any(|n| ["system-ui", "serif", "sans-serif", "monospace", "cursive", "fantasy"].contains(&&**n))
2688 {
2689 let mut r = FontNames(Vec::with_capacity(names.len()));
2690 for name in names {
2691 match self.resolve(name, lang) {
2692 Some(n) => r.push(n),
2693 None => {
2694 if name == "system-ui" {
2695 r.extend(self.system_ui(lang));
2696 } else {
2697 r.push(name.clone())
2698 }
2699 }
2700 }
2701 }
2702 Some(r)
2703 } else {
2704 None
2705 }
2706 }
2707}
2708
2709#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2710pub(crate) enum WeakFontBytes {
2711 Ipc(WeakIpcBytes),
2712 Arc(std::sync::Weak<Vec<u8>>),
2713 Static(&'static [u8]),
2714 Mmap(std::sync::Weak<SystemFontBytes>),
2715}
2716#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2717impl WeakFontBytes {
2718 pub(crate) fn upgrade(&self) -> Option<FontBytes> {
2719 match self {
2720 WeakFontBytes::Ipc(weak) => Some(FontBytes(FontBytesImpl::Ipc(weak.upgrade()?))),
2721 WeakFontBytes::Arc(weak) => Some(FontBytes(FontBytesImpl::Arc(weak.upgrade()?))),
2722 WeakFontBytes::Static(b) => Some(FontBytes(FontBytesImpl::Static(b))),
2723 WeakFontBytes::Mmap(weak) => Some(FontBytes(FontBytesImpl::System(weak.upgrade()?))),
2724 }
2725 }
2726
2727 pub(crate) fn strong_count(&self) -> usize {
2728 match self {
2729 WeakFontBytes::Ipc(weak) => weak.strong_count(),
2730 WeakFontBytes::Arc(weak) => weak.strong_count(),
2731 WeakFontBytes::Static(_) => 1,
2732 WeakFontBytes::Mmap(weak) => weak.strong_count(),
2733 }
2734 }
2735}
2736
2737struct SystemFontBytes {
2738 path: std::path::PathBuf,
2739 mmap: IpcBytes,
2740}
2741
2742#[derive(Clone)]
2743enum FontBytesImpl {
2744 Ipc(IpcBytes),
2746 Arc(Arc<Vec<u8>>),
2747 Static(&'static [u8]),
2748 System(Arc<SystemFontBytes>),
2749}
2750#[derive(Clone)]
2752pub struct FontBytes(FontBytesImpl);
2753impl FontBytes {
2754 pub fn from_ipc(bytes: IpcBytes) -> Self {
2756 Self(FontBytesImpl::Ipc(bytes))
2757 }
2758
2759 pub fn from_vec(bytes: Vec<u8>) -> io::Result<Self> {
2761 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_vec_blocking(bytes)?)))
2762 }
2763
2764 pub fn from_static(bytes: &'static [u8]) -> Self {
2766 Self(FontBytesImpl::Static(bytes))
2767 }
2768
2769 pub fn from_arc(bytes: Arc<Vec<u8>>) -> Self {
2773 Self(FontBytesImpl::Arc(bytes))
2774 }
2775
2776 pub fn from_file(path: PathBuf) -> io::Result<Self> {
2778 let path = dunce::canonicalize(path)?;
2779
2780 #[cfg(windows)]
2781 {
2782 use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemWindowsDirectoryW};
2783 let mut buffer = [0u16; MAX_PATH as usize];
2784 let len = unsafe { GetSystemWindowsDirectoryW(Some(&mut buffer)) };
2786 let fonts_dir = String::from_utf16_lossy(&buffer[..len as usize]);
2787 if path.starts_with(fonts_dir) {
2789 return unsafe { load_from_system(path) };
2791 }
2792 }
2793 #[cfg(target_os = "macos")]
2794 if path.starts_with("/System/Library/Fonts/") || path.starts_with("/Library/Fonts/") {
2795 return unsafe { load_from_system(path) };
2797 }
2798 #[cfg(target_os = "android")]
2799 if path.starts_with("/system/fonts/") || path.starts_with("/system/font/") || path.starts_with("/system/product/fonts/") {
2800 return unsafe { load_from_system(path) };
2802 }
2803 #[cfg(unix)]
2804 if path.starts_with("/usr/share/fonts/") {
2805 return unsafe { load_from_system(path) };
2807 }
2808
2809 #[cfg(ipc)]
2810 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2811 let mmap = unsafe { IpcBytes::open_memmap_blocking(path.clone(), None) }?;
2813 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2814 }
2815
2816 #[cfg(all(not(ipc), not(target_arch = "wasm32")))]
2817 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2818 let mmap = IpcBytes::from_path_blocking(&path)?;
2819 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2820 }
2821
2822 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_path_blocking(&path)?)))
2823 }
2824
2825 #[cfg(ipc)]
2832 pub unsafe fn from_file_mmap(path: PathBuf) -> std::io::Result<Self> {
2833 let ipc = unsafe { IpcBytes::open_memmap_blocking(path, None) }?;
2835 Ok(Self(FontBytesImpl::Ipc(ipc)))
2836 }
2837
2838 #[cfg(ipc)]
2842 pub fn mmap_path(&self) -> Option<&std::path::Path> {
2843 if let FontBytesImpl::System(m) = &self.0 {
2844 Some(&m.path)
2845 } else {
2846 None
2847 }
2848 }
2849
2850 pub fn to_ipc(&self) -> io::Result<IpcFontBytes> {
2852 Ok(if let FontBytesImpl::System(m) = &self.0 {
2853 IpcFontBytes::System(m.path.clone())
2854 } else {
2855 IpcFontBytes::Bytes(self.to_ipc_bytes()?)
2856 })
2857 }
2858
2859 pub fn to_ipc_bytes(&self) -> io::Result<IpcBytes> {
2861 match &self.0 {
2862 FontBytesImpl::Ipc(b) => Ok(b.clone()),
2863 FontBytesImpl::Arc(b) => IpcBytes::from_slice_blocking(b),
2864 FontBytesImpl::Static(b) => IpcBytes::from_slice_blocking(b),
2865 FontBytesImpl::System(m) => IpcBytes::from_slice_blocking(&m.mmap[..]),
2866 }
2867 }
2868
2869 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2870 pub(crate) fn downgrade(&self) -> WeakFontBytes {
2871 match &self.0 {
2872 FontBytesImpl::Ipc(ipc) => WeakFontBytes::Ipc(ipc.downgrade()),
2873 FontBytesImpl::Arc(arc) => WeakFontBytes::Arc(Arc::downgrade(arc)),
2874 FontBytesImpl::Static(b) => WeakFontBytes::Static(b),
2875 FontBytesImpl::System(arc) => WeakFontBytes::Mmap(Arc::downgrade(arc)),
2876 }
2877 }
2878
2879 pub(crate) fn as_harfrust_font(&self) -> read_fonts::model::Source {
2880 use read_fonts::model::Blob;
2881 match &self.0 {
2882 FontBytesImpl::Ipc(b) => Blob::Shared(b.clone().into()),
2883 FontBytesImpl::Arc(b) => Blob::Shared(b.clone()),
2884 FontBytesImpl::Static(b) => Blob::Static(b),
2885 FontBytesImpl::System(b) => Blob::Shared(b.mmap.clone().into()),
2886 }
2887 .into()
2888 }
2889}
2890impl std::ops::Deref for FontBytes {
2891 type Target = [u8];
2892
2893 fn deref(&self) -> &Self::Target {
2894 match &self.0 {
2895 FontBytesImpl::Ipc(b) => &b[..],
2896 FontBytesImpl::Arc(b) => &b[..],
2897 FontBytesImpl::Static(b) => b,
2898 FontBytesImpl::System(m) => &m.mmap[..],
2899 }
2900 }
2901}
2902impl fmt::Debug for FontBytes {
2903 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2904 let mut b = f.debug_struct("FontBytes");
2905 b.field(
2906 ".kind",
2907 &match &self.0 {
2908 FontBytesImpl::Ipc(_) => "IpcBytes",
2909 FontBytesImpl::Arc(_) => "Arc",
2910 FontBytesImpl::Static(_) => "Static",
2911 FontBytesImpl::System(_) => "Mmap",
2912 },
2913 );
2914 b.field(".len", &(self.len() as u64).bytes());
2915 if let FontBytesImpl::System(m) = &self.0 {
2916 b.field(".path", &m.path);
2917 }
2918
2919 b.finish()
2920 }
2921}
2922
2923#[derive(Debug, Clone)]
2924enum FontSource {
2925 File(PathBuf, u32),
2926 Memory(FontBytes, u32),
2927 Alias(FontName),
2928}
2929
2930#[derive(Debug, Clone)]
2932pub struct CustomFont {
2933 name: FontName,
2934 source: FontSource,
2935 stretch: FontStretch,
2936 style: FontStyle,
2937 weight: FontWeight,
2938}
2939impl CustomFont {
2940 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2948 CustomFont {
2949 name: name.into(),
2950 source: FontSource::File(path.into(), font_index),
2951 stretch: FontStretch::NORMAL,
2952 style: FontStyle::Normal,
2953 weight: FontWeight::NORMAL,
2954 }
2955 }
2956
2957 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontBytes, font_index: u32) -> Self {
2965 CustomFont {
2966 name: name.into(),
2967 source: FontSource::Memory(data, font_index),
2968 stretch: FontStretch::NORMAL,
2969 style: FontStyle::Normal,
2970 weight: FontWeight::NORMAL,
2971 }
2972 }
2973
2974 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2980 CustomFont {
2981 name: name.into(),
2982 source: FontSource::Alias(other_font.into()),
2983 stretch: FontStretch::NORMAL,
2984 style: FontStyle::Normal,
2985 weight: FontWeight::NORMAL,
2986 }
2987 }
2988
2989 pub fn stretch(mut self, stretch: FontStretch) -> Self {
2993 self.stretch = stretch;
2994 self
2995 }
2996
2997 pub fn style(mut self, style: FontStyle) -> Self {
3001 self.style = style;
3002 self
3003 }
3004
3005 pub fn weight(mut self, weight: FontWeight) -> Self {
3009 self.weight = weight;
3010 self
3011 }
3012}
3013
3014#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
3018#[serde(transparent)]
3019pub struct FontStretch(pub f32);
3020impl fmt::Debug for FontStretch {
3021 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3022 let name = self.name();
3023 if name.is_empty() {
3024 f.debug_tuple("FontStretch").field(&self.0).finish()
3025 } else {
3026 if f.alternate() {
3027 write!(f, "FontStretch::")?;
3028 }
3029 write!(f, "{name}")
3030 }
3031 }
3032}
3033impl PartialOrd for FontStretch {
3034 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3035 Some(self.cmp(other))
3036 }
3037}
3038impl Ord for FontStretch {
3039 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3040 about_eq_ord(self.0, other.0, EQ_GRANULARITY)
3041 }
3042}
3043impl PartialEq for FontStretch {
3044 fn eq(&self, other: &Self) -> bool {
3045 about_eq(self.0, other.0, EQ_GRANULARITY)
3046 }
3047}
3048impl Eq for FontStretch {}
3049impl std::hash::Hash for FontStretch {
3050 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3051 about_eq_hash(self.0, EQ_GRANULARITY, state)
3052 }
3053}
3054impl Default for FontStretch {
3055 fn default() -> FontStretch {
3056 FontStretch::NORMAL
3057 }
3058}
3059impl FontStretch {
3060 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
3062 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
3064 pub const CONDENSED: FontStretch = FontStretch(0.75);
3066 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
3068 pub const NORMAL: FontStretch = FontStretch(1.0);
3070 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
3072 pub const EXPANDED: FontStretch = FontStretch(1.25);
3074 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
3076 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
3078
3079 pub fn name(self) -> &'static str {
3081 macro_rules! name {
3082 ($($CONST:ident;)+) => {$(
3083 if self == Self::$CONST {
3084 return stringify!($CONST);
3085 }
3086 )+}
3087 }
3088 name! {
3089 ULTRA_CONDENSED;
3090 EXTRA_CONDENSED;
3091 CONDENSED;
3092 SEMI_CONDENSED;
3093 NORMAL;
3094 SEMI_EXPANDED;
3095 EXPANDED;
3096 EXTRA_EXPANDED;
3097 ULTRA_EXPANDED;
3098 }
3099 ""
3100 }
3101}
3102impl_from_and_into_var! {
3103 fn from(fct: Factor) -> FontStretch {
3104 FontStretch(fct.0)
3105 }
3106 fn from(pct: FactorPercent) -> FontStretch {
3107 FontStretch(pct.fct().0)
3108 }
3109 fn from(fct: f32) -> FontStretch {
3110 FontStretch(fct)
3111 }
3112}
3113impl From<skrifa::attribute::Stretch> for FontStretch {
3114 fn from(value: skrifa::attribute::Stretch) -> Self {
3115 FontStretch(value.ratio())
3116 }
3117}
3118impl From<FontStretch> for skrifa::attribute::Stretch {
3119 fn from(value: FontStretch) -> Self {
3120 skrifa::attribute::Stretch::new(value.0)
3121 }
3122}
3123
3124#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
3126pub enum FontStyle {
3127 #[default]
3129 Normal,
3130 Italic,
3132 Oblique,
3134}
3135impl fmt::Debug for FontStyle {
3136 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3137 if f.alternate() {
3138 write!(f, "FontStyle::")?;
3139 }
3140 match self {
3141 Self::Normal => write!(f, "Normal"),
3142 Self::Italic => write!(f, "Italic"),
3143 Self::Oblique => write!(f, "Oblique"),
3144 }
3145 }
3146}
3147impl From<skrifa::attribute::Style> for FontStyle {
3148 fn from(value: skrifa::attribute::Style) -> Self {
3149 use skrifa::attribute::Style::*;
3150 match value {
3151 Normal => FontStyle::Normal,
3152 Italic => FontStyle::Italic,
3153 Oblique(_) => FontStyle::Oblique,
3154 }
3155 }
3156}
3157
3158impl From<FontStyle> for skrifa::attribute::Style {
3159 fn from(value: FontStyle) -> Self {
3160 match value {
3161 FontStyle::Normal => Self::Normal,
3162 FontStyle::Italic => Self::Italic,
3163 FontStyle::Oblique => Self::Oblique(None),
3164 }
3165 }
3166}
3167
3168#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
3171pub struct FontWeight(pub f32);
3172impl Default for FontWeight {
3173 fn default() -> FontWeight {
3174 FontWeight::NORMAL
3175 }
3176}
3177impl fmt::Debug for FontWeight {
3178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3179 let name = self.name();
3180 if name.is_empty() {
3181 f.debug_tuple("FontWeight").field(&self.0).finish()
3182 } else {
3183 if f.alternate() {
3184 write!(f, "FontWeight::")?;
3185 }
3186 write!(f, "{name}")
3187 }
3188 }
3189}
3190impl PartialOrd for FontWeight {
3191 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3192 Some(self.cmp(other))
3193 }
3194}
3195impl Ord for FontWeight {
3196 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3197 about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
3198 }
3199}
3200impl PartialEq for FontWeight {
3201 fn eq(&self, other: &Self) -> bool {
3202 about_eq(self.0, other.0, EQ_GRANULARITY_100)
3203 }
3204}
3205impl Eq for FontWeight {}
3206impl std::hash::Hash for FontWeight {
3207 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3208 about_eq_hash(self.0, EQ_GRANULARITY_100, state)
3209 }
3210}
3211impl FontWeight {
3212 pub const THIN: FontWeight = FontWeight(100.0);
3214 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
3216 pub const LIGHT: FontWeight = FontWeight(300.0);
3218 pub const NORMAL: FontWeight = FontWeight(400.0);
3220 pub const MEDIUM: FontWeight = FontWeight(500.0);
3222 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
3224 pub const BOLD: FontWeight = FontWeight(700.0);
3226 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
3228 pub const BLACK: FontWeight = FontWeight(900.0);
3230
3231 pub fn name(self) -> &'static str {
3233 macro_rules! name {
3234 ($($CONST:ident;)+) => {$(
3235 if self == Self::$CONST {
3236 return stringify!($CONST);
3237 }
3238 )+}
3239 }
3240 name! {
3241 THIN;
3242 EXTRA_LIGHT;
3243 LIGHT;
3244 NORMAL;
3245 MEDIUM;
3246 SEMIBOLD;
3247 BOLD;
3248 EXTRA_BOLD;
3249 BLACK;
3250 }
3251 ""
3252 }
3253}
3254impl_from_and_into_var! {
3255 fn from(weight: u32) -> FontWeight {
3256 FontWeight(weight as f32)
3257 }
3258 fn from(weight: f32) -> FontWeight {
3259 FontWeight(weight)
3260 }
3261}
3262impl From<skrifa::attribute::Weight> for FontWeight {
3263 fn from(value: skrifa::attribute::Weight) -> Self {
3264 FontWeight(value.value())
3265 }
3266}
3267impl From<FontWeight> for skrifa::attribute::Weight {
3268 fn from(value: FontWeight) -> Self {
3269 skrifa::attribute::Weight::new(value.0)
3270 }
3271}
3272
3273#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3275pub enum LineBreak {
3276 Auto,
3278 Loose,
3280 Normal,
3282 Strict,
3284 Anywhere,
3286}
3287impl Default for LineBreak {
3288 fn default() -> Self {
3290 LineBreak::Auto
3291 }
3292}
3293impl fmt::Debug for LineBreak {
3294 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3295 if f.alternate() {
3296 write!(f, "LineBreak::")?;
3297 }
3298 match self {
3299 LineBreak::Auto => write!(f, "Auto"),
3300 LineBreak::Loose => write!(f, "Loose"),
3301 LineBreak::Normal => write!(f, "Normal"),
3302 LineBreak::Strict => write!(f, "Strict"),
3303 LineBreak::Anywhere => write!(f, "Anywhere"),
3304 }
3305 }
3306}
3307
3308#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3313#[non_exhaustive]
3314pub enum ParagraphBreak {
3315 #[default]
3317 None,
3318 Line,
3320}
3321impl fmt::Debug for ParagraphBreak {
3322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3323 if f.alternate() {
3324 write!(f, "ParagraphBreak::")?;
3325 }
3326 match self {
3327 ParagraphBreak::None => write!(f, "None"),
3328 ParagraphBreak::Line => write!(f, "Line"),
3329 }
3330 }
3331}
3332
3333#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3335pub enum Hyphens {
3336 None,
3338 Manual,
3343 Auto,
3345}
3346impl Default for Hyphens {
3347 fn default() -> Self {
3349 Hyphens::Auto
3350 }
3351}
3352impl fmt::Debug for Hyphens {
3353 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3354 if f.alternate() {
3355 write!(f, "Hyphens::")?;
3356 }
3357 match self {
3358 Hyphens::None => write!(f, "None"),
3359 Hyphens::Manual => write!(f, "Manual"),
3360 Hyphens::Auto => write!(f, "Auto"),
3361 }
3362 }
3363}
3364
3365#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3371pub enum WordBreak {
3372 Normal,
3374 BreakAll,
3376 KeepAll,
3378}
3379impl Default for WordBreak {
3380 fn default() -> Self {
3382 WordBreak::Normal
3383 }
3384}
3385impl fmt::Debug for WordBreak {
3386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3387 if f.alternate() {
3388 write!(f, "WordBreak::")?;
3389 }
3390 match self {
3391 WordBreak::Normal => write!(f, "Normal"),
3392 WordBreak::BreakAll => write!(f, "BreakAll"),
3393 WordBreak::KeepAll => write!(f, "KeepAll"),
3394 }
3395 }
3396}
3397
3398#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3400pub enum Justify {
3401 Auto,
3405 InterWord,
3407 InterLetter,
3409}
3410impl Default for Justify {
3411 fn default() -> Self {
3413 Justify::Auto
3414 }
3415}
3416impl Justify {
3417 pub fn resolve(self, lang: &Lang) -> Self {
3419 match self {
3420 Self::Auto => match lang.language.as_str() {
3421 "zh" | "ja" | "ko" => Self::InterLetter,
3422 _ => Self::InterWord,
3423 },
3424 m => m,
3425 }
3426 }
3427}
3428impl fmt::Debug for Justify {
3429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3430 if f.alternate() {
3431 write!(f, "Justify::")?;
3432 }
3433 match self {
3434 Justify::Auto => write!(f, "Auto"),
3435 Justify::InterWord => write!(f, "InterWord"),
3436 Justify::InterLetter => write!(f, "InterLetter"),
3437 }
3438 }
3439}
3440
3441#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3443#[non_exhaustive]
3444pub struct FontMetrics {
3445 pub ascent: Px,
3447
3448 pub descent: Px,
3454
3455 pub line_gap: Px,
3457
3458 pub underline_position: Px,
3461
3462 pub underline_thickness: Px,
3464
3465 pub cap_height: Px,
3467
3468 pub x_height: Px,
3470
3471 pub bounds: PxRect,
3475
3476 units_per_em: u16,
3477}
3478impl FontMetrics {
3479 pub fn line_height(&self) -> Px {
3481 self.ascent - self.descent + self.line_gap
3482 }
3483
3484 fn new(f: &skrifa::FontRef, size: Px) -> Self {
3485 let m = f.metrics(skrifa::instance::Size::new(size.0 as f32), skrifa::instance::LocationRef::default());
3486 let u = m.underline.unwrap_or_default();
3487 let b = m.bounds.unwrap_or_default();
3488 let scale = size.0 as f32 / m.units_per_em as f32;
3489 let line_gap = self::line_gap(f) as f32 * scale;
3490 fn f32_to_px(v: f32) -> Px {
3491 euclid::point2::<f32, ()>(v, v).cast().x
3493 }
3494 Self {
3495 ascent: f32_to_px(m.ascent),
3496 descent: f32_to_px(m.descent),
3497 line_gap: f32_to_px(line_gap),
3498 underline_position: f32_to_px(u.offset),
3499 underline_thickness: f32_to_px(u.thickness),
3500 cap_height: f32_to_px(m.cap_height.unwrap_or(0.0)),
3501 x_height: f32_to_px(m.x_height.unwrap_or(0.0)),
3502 bounds: euclid::rect(b.x_min, b.y_min, b.x_max - b.x_min, b.y_max - b.y_min).cast(),
3503
3504 units_per_em: m.units_per_em,
3505 }
3506 }
3507
3508 fn empty() -> Self {
3509 Self {
3510 ascent: Px(0),
3511 descent: Px(0),
3512 line_gap: Px(0),
3513 underline_position: Px(0),
3514 underline_thickness: Px(0),
3515 cap_height: Px(0),
3516 x_height: Px(0),
3517 bounds: euclid::Rect::zero(),
3518 units_per_em: 0,
3519 }
3520 }
3521}
3522fn line_gap(f: &skrifa::FontRef) -> i16 {
3523 use read_fonts::TableProvider as _;
3525 if let Ok(os2) = f.os2() {
3526 let use_typographic_metrics = os2.version() >= 4
3527 && os2
3528 .fs_selection()
3529 .contains(read_fonts::tables::os2::SelectionFlags::USE_TYPO_METRICS);
3530 if use_typographic_metrics {
3531 return os2.s_typo_line_gap();
3532 }
3533 }
3534 if let Ok(hrea) = f.hhea() {
3535 if hrea.ascender().to_i16() == 0
3536 && hrea.descender().to_i16() == 0
3537 && let Ok(os2) = f.os2()
3538 {
3539 return if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 {
3540 return os2.s_typo_line_gap();
3541 } else {
3542 0
3543 };
3544 }
3545 return hrea.line_gap().to_i16();
3546 }
3547 0
3548}
3549
3550#[derive(Clone)]
3552pub enum TextTransformFn {
3553 None,
3555 Uppercase,
3557 Lowercase,
3559 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3561}
3562impl TextTransformFn {
3563 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3567 match self {
3568 TextTransformFn::None => Cow::Borrowed(text),
3569 TextTransformFn::Uppercase => {
3570 if text.chars().any(|c| !c.is_uppercase()) {
3571 Cow::Owned(text.to_uppercase().into())
3572 } else {
3573 Cow::Borrowed(text)
3574 }
3575 }
3576 TextTransformFn::Lowercase => {
3577 if text.chars().any(|c| !c.is_lowercase()) {
3578 Cow::Owned(text.to_lowercase().into())
3579 } else {
3580 Cow::Borrowed(text)
3581 }
3582 }
3583 TextTransformFn::Custom(fn_) => fn_(text),
3584 }
3585 }
3586
3587 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3589 TextTransformFn::Custom(Arc::new(fn_))
3590 }
3591}
3592impl fmt::Debug for TextTransformFn {
3593 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3594 if f.alternate() {
3595 write!(f, "TextTransformFn::")?;
3596 }
3597 match self {
3598 TextTransformFn::None => write!(f, "None"),
3599 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3600 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3601 TextTransformFn::Custom(_) => write!(f, "Custom"),
3602 }
3603 }
3604}
3605impl PartialEq for TextTransformFn {
3606 fn eq(&self, other: &Self) -> bool {
3607 match (self, other) {
3608 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3609 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3610 }
3611 }
3612}
3613
3614#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3616pub enum WhiteSpace {
3617 #[default]
3619 Preserve,
3620 Merge,
3623 MergeParagraph,
3626 MergeAll,
3628}
3629impl WhiteSpace {
3630 pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3634 match self {
3635 WhiteSpace::Preserve => Cow::Borrowed(text),
3636 WhiteSpace::Merge => {
3637 let mut prev_i = 0;
3639 for line in text.split_inclusive('\n') {
3640 let line_exclusive = line.trim_end_matches('\n').trim_end_matches('\r');
3642 let line_trim = line_exclusive.trim();
3643 let mut merge = line_trim.len() != line_exclusive.len() || line_trim.is_empty();
3644
3645 if !merge {
3647 let mut prev_is_space = true; for c in line.chars() {
3649 let is_space = c.is_whitespace();
3650 if prev_is_space && is_space {
3651 merge = true;
3652 break;
3653 }
3654 prev_is_space = is_space;
3655 }
3656 }
3657
3658 if !merge {
3659 prev_i += line.len();
3660 continue;
3661 }
3662
3663 let mut out = String::with_capacity(text.len() - 1);
3665 out.push_str(&text[..prev_i]);
3666
3667 let mut chars = text[prev_i..].chars();
3668 let mut prev_is_space = true;
3669 let mut prev_is_break = true;
3670 while let Some(c) = chars.next() {
3671 if c == '\r'
3672 && let Some(nc) = chars.next()
3673 {
3674 if nc == '\n' {
3675 if !prev_is_break && !out.is_empty() {
3676 out.push('\n');
3677 }
3678 prev_is_break = true;
3679 prev_is_space = true;
3680 } else {
3681 out.push(c);
3682 out.push(nc);
3683 prev_is_break = false;
3684 prev_is_space = nc.is_whitespace();
3685 }
3686 } else if c == '\n' {
3687 if !prev_is_break && !out.is_empty() {
3688 out.push('\n');
3689 }
3690 prev_is_break = true;
3691 prev_is_space = true;
3692 } else if c.is_whitespace() {
3693 if prev_is_space {
3694 continue;
3695 }
3696 out.push(' ');
3697 prev_is_space = true;
3698 } else {
3699 out.push(c);
3700 prev_is_space = false;
3701 prev_is_break = false;
3702 }
3703 }
3704
3705 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3707 out.truncate(i + c.len_utf8());
3708 }
3709
3710 return Cow::Owned(out.into());
3711 }
3712 Cow::Borrowed(text)
3713 }
3714 WhiteSpace::MergeParagraph => {
3715 let mut merge = text.contains('\n') || text.chars().last().unwrap_or('\0').is_whitespace();
3718 if !merge {
3719 let mut prev_is_space = true;
3720 for c in text.chars() {
3721 let is_space = c.is_whitespace();
3722 if prev_is_space && is_space {
3723 merge = true;
3724 break;
3725 }
3726 prev_is_space = is_space;
3727 }
3728 }
3729
3730 if merge {
3731 let mut out = String::with_capacity(text.len());
3732 let mut prev_is_break = false;
3733 for line in text.lines() {
3734 let line = line.trim();
3735 let is_break = line.is_empty();
3736 if !prev_is_break && is_break && !out.is_empty() {
3737 out.push('\n');
3738 }
3739 if !prev_is_break && !is_break && !out.is_empty() {
3740 out.push(' ');
3741 }
3742 prev_is_break = is_break;
3743
3744 let mut prev_is_space = false;
3745 for c in line.chars() {
3746 let is_space = c.is_whitespace();
3747 if is_space {
3748 if !prev_is_space {
3749 out.push(' ');
3750 }
3751 } else {
3752 out.push(c);
3753 }
3754 prev_is_space = is_space;
3755 }
3756 }
3757
3758 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3760 out.truncate(i + c.len_utf8());
3761 }
3762
3763 return Cow::Owned(out.into());
3764 }
3765 Cow::Borrowed(text)
3766 }
3767 WhiteSpace::MergeAll => {
3768 let mut prev_i = 0;
3770 let mut prev_is_space = true; for (i, c) in text.char_indices() {
3772 let is_space = c.is_whitespace();
3773 if prev_is_space && is_space || c == '\n' {
3774 if !prev_is_space {
3775 debug_assert_eq!(c, '\n');
3776 prev_i += c.len_utf8();
3777 prev_is_space = true;
3778 }
3779 let mut out = String::with_capacity(text.len() - 1);
3781 out.push_str(&text[..prev_i]);
3783 if !out.is_empty() {
3784 out.push(' ');
3785 }
3786 for c in text[(i + c.len_utf8())..].chars() {
3788 let is_space = c.is_whitespace();
3789 if prev_is_space && is_space {
3790 continue;
3791 }
3792 out.push(if is_space { ' ' } else { c });
3793 prev_is_space = is_space;
3794 }
3795
3796 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3798 out.truncate(i + c.len_utf8());
3799 }
3800
3801 return Cow::Owned(out.into());
3802 }
3803 prev_i = i;
3804 prev_is_space = is_space;
3805 }
3806
3807 let out = text.trim_end();
3811 if out.len() != text.len() {
3812 return Cow::Owned(Txt::from_str(out));
3813 }
3814
3815 Cow::Borrowed(text)
3816 }
3817 }
3818 }
3819}
3820impl fmt::Debug for WhiteSpace {
3821 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3822 if f.alternate() {
3823 write!(f, "WhiteSpace::")?;
3824 }
3825 match self {
3826 WhiteSpace::Preserve => write!(f, "Preserve"),
3827 WhiteSpace::Merge => write!(f, "Merge"),
3828 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3829 WhiteSpace::MergeParagraph => write!(f, "MergeParagraph"),
3830 }
3831 }
3832}
3833
3834#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3836pub struct CaretIndex {
3837 pub index: usize,
3841 pub line: usize,
3851}
3852
3853impl PartialEq for CaretIndex {
3854 fn eq(&self, other: &Self) -> bool {
3855 self.index == other.index
3856 }
3857}
3858impl Eq for CaretIndex {}
3859impl CaretIndex {
3860 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3862}
3863impl PartialOrd for CaretIndex {
3864 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3865 Some(self.cmp(other))
3866 }
3867}
3868impl Ord for CaretIndex {
3869 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3870 self.index.cmp(&other.index)
3871 }
3872}
3873impl_from_and_into_var! {
3874 fn from(index: usize) -> CaretIndex {
3875 CaretIndex { index, line: 0 }
3876 }
3877}
3878
3879#[derive(Debug, Clone)]
3881#[non_exhaustive]
3882pub enum FontLoadingError {
3883 UnknownFormat,
3885 NoSuchFontInCollection,
3890 Parse(read_fonts::ReadError),
3892 NoFilesystem,
3895 Io(Arc<std::io::Error>),
3897}
3898impl PartialEq for FontLoadingError {
3899 fn eq(&self, other: &Self) -> bool {
3900 match (self, other) {
3901 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3902 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3903 }
3904 }
3905}
3906impl From<std::io::Error> for FontLoadingError {
3907 fn from(error: std::io::Error) -> FontLoadingError {
3908 Self::Io(Arc::new(error))
3909 }
3910}
3911impl fmt::Display for FontLoadingError {
3912 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3913 match self {
3914 Self::UnknownFormat => write!(f, "unknown format"),
3915 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3916 Self::NoFilesystem => write!(f, "no filesystem present"),
3917 Self::Parse(e) => fmt::Display::fmt(e, f),
3918 Self::Io(e) => fmt::Display::fmt(e, f),
3919 }
3920 }
3921}
3922impl std::error::Error for FontLoadingError {
3923 fn cause(&self) -> Option<&dyn std::error::Error> {
3924 match self {
3925 FontLoadingError::Parse(e) => Some(e),
3926 FontLoadingError::Io(e) => Some(e),
3927 _ => None,
3928 }
3929 }
3930}
3931
3932#[cfg(test)]
3933mod tests {
3934 use zng_app::APP;
3935
3936 use super::*;
3937
3938 #[test]
3939 fn generic_fonts_default() {
3940 let _app = APP.minimal().run_headless(false);
3941
3942 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3943 }
3944
3945 #[test]
3946 fn generic_fonts_fallback() {
3947 let _app = APP.minimal().run_headless(false);
3948
3949 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3950 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3951 }
3952
3953 #[test]
3954 fn generic_fonts_get1() {
3955 let mut app = APP.minimal().run_headless(false);
3956 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3957 app.update(false).assert_wait();
3958
3959 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3960 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3961 }
3962
3963 #[test]
3964 fn generic_fonts_get2() {
3965 let mut app = APP.minimal().run_headless(false);
3966 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3967 app.update(false).assert_wait();
3968
3969 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3970 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3971 }
3972
3973 #[test]
3974 fn generic_fonts_get_best() {
3975 let mut app = APP.minimal().run_headless(false);
3976 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3977 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3978 app.update(false).assert_wait();
3979
3980 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3981 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3982 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3983 }
3984
3985 #[test]
3986 fn generic_fonts_get_no_lang_match() {
3987 let mut app = APP.minimal().run_headless(false);
3988 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3989 app.update(false).assert_wait();
3990
3991 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3992 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3993 }
3994
3995 #[test]
3996 fn white_space_merge() {
3997 macro_rules! test {
3998 ($input:tt, $output:tt) => {
3999 let input = Txt::from($input);
4000 let output = WhiteSpace::Merge.transform(&input);
4001 assert_eq!($output, output.as_str());
4002
4003 let input = input.replace('\n', "\r\n");
4004 let output = WhiteSpace::Merge.transform(&Txt::from(input)).replace("\r\n", "\n");
4005 assert_eq!($output, output.as_str());
4006 };
4007 }
4008 test!("a b\n\nc", "a b\nc");
4009 test!("a b\nc", "a b\nc");
4010 test!(" a b\nc\n \n", "a b\nc");
4011 test!(" \n a b\nc", "a b\nc");
4012 test!("a\n \nb", "a\nb");
4013 }
4014
4015 #[test]
4016 fn white_space_merge_paragraph() {
4017 macro_rules! test {
4018 ($input:tt, $output:tt) => {
4019 let input = Txt::from($input);
4020 let output = WhiteSpace::MergeParagraph.transform(&input);
4021 assert_eq!($output, output.as_str());
4022
4023 let input = input.replace('\n', "\r\n");
4024 let output = WhiteSpace::MergeParagraph.transform(&Txt::from(input)).replace("\r\n", "\n");
4025 assert_eq!($output, output.as_str());
4026 };
4027 }
4028 test!("a b\n\nc", "a b\nc");
4029 test!("a b\nc", "a b c");
4030 test!(" a b\nc\n \n", "a b c");
4031 test!(" \n a b\nc", "a b c");
4032 test!("a\n \nb", "a\nb");
4033 }
4034
4035 #[test]
4036 fn white_space_merge_all() {
4037 macro_rules! test {
4038 ($input:tt, $output:tt) => {
4039 let input = Txt::from($input);
4040 let output = WhiteSpace::MergeAll.transform(&input);
4041 assert_eq!($output, output.as_str());
4042 };
4043 }
4044 test!("a b\n\nc", "a b c");
4045 test!("a b\nc", "a b c");
4046 test!(" a b\nc\n \n", "a b c");
4047 test!(" \n a b\nc", "a b c");
4048 test!("a\n \nb", "a b");
4049 }
4050}