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 raw(&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_raw(&self) -> Option<&harfrust::Font> {
1094 self.0.harfrust_font.as_ref()
1095 }
1096
1097 pub fn bytes(&self) -> &FontBytes {
1099 &self.0.data
1100 }
1101 pub fn index(&self) -> u32 {
1103 self.0.face_index
1104 }
1105
1106 pub fn display_name(&self) -> &FontName {
1108 &self.0.display_name
1109 }
1110
1111 pub fn family_name(&self) -> &FontName {
1113 &self.0.family_name
1114 }
1115
1116 pub fn postscript_name(&self) -> Option<&str> {
1118 self.0.postscript_name.as_deref()
1119 }
1120
1121 pub fn style(&self) -> FontStyle {
1123 self.0.style
1124 }
1125
1126 pub fn weight(&self) -> FontWeight {
1128 self.0.weight
1129 }
1130
1131 pub fn stretch(&self) -> FontStretch {
1133 self.0.stretch
1134 }
1135
1136 pub fn is_monospace(&self) -> bool {
1138 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1139 }
1140
1141 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1150 let key = FontInstanceKey::new(font_size, &variations);
1151 let mut m = self.0.m.lock();
1152 if !m.unregistered {
1153 m.instances
1154 .entry(key)
1155 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1156 .clone()
1157 } else {
1158 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1159 Font::new(self.clone(), font_size, variations)
1160 }
1161 }
1162
1163 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1165 let mut synth = FontSynthesis::DISABLED;
1166
1167 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1168 synth |= FontSynthesis::OBLIQUE;
1170 }
1171 if weight > self.weight() {
1172 synth |= FontSynthesis::BOLD;
1175 }
1176
1177 synth
1178 }
1179
1180 pub fn is_cached(&self) -> bool {
1184 !self.0.m.lock().unregistered
1185 }
1186
1187 pub fn color_palettes(&self) -> ColorPalettes<'_> {
1191 match self.raw() {
1192 Some(ttf) => ColorPalettes::new(ttf),
1193 None => ColorPalettes::empty(),
1194 }
1195 }
1196
1197 pub fn color_glyphs(&self) -> ColorGlyphs<'_> {
1201 match self.raw() {
1202 Some(ttf) => ColorGlyphs::new(ttf),
1203 None => ColorGlyphs::empty(),
1204 }
1205 }
1206
1207 pub fn has_ligatures(&self) -> bool {
1209 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1210 }
1211
1212 pub fn has_ligature_caret_offsets(&self) -> bool {
1217 !self.0.lig_carets.is_empty()
1218 }
1219
1220 pub fn has_raster_images(&self) -> bool {
1222 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1223 }
1224
1225 pub fn has_svg_images(&self) -> bool {
1227 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1228 }
1229}
1230
1231#[derive(Clone)]
1237pub struct Font(Arc<LoadedFont>);
1238struct LoadedFont {
1239 face: FontFace,
1240 size: Px,
1241 variations: RFontVariations,
1242 metrics: FontMetrics,
1243 render_keys: Mutex<Vec<RenderFont>>,
1244 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1245 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1246}
1247impl fmt::Debug for Font {
1248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1249 f.debug_struct("Font")
1250 .field("face", &self.0.face)
1251 .field("size", &self.0.size)
1252 .field("metrics", &self.0.metrics)
1253 .field("render_keys.len()", &self.0.render_keys.lock().len())
1254 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1255 .field("word_cache.len()", &self.0.word_cache.read().len())
1256 .finish()
1257 }
1258}
1259impl PartialEq for Font {
1260 fn eq(&self, other: &Self) -> bool {
1261 Arc::ptr_eq(&self.0, &other.0)
1262 }
1263}
1264impl Eq for Font {}
1265impl Font {
1266 const SMALL_WORD_LEN: usize = 8;
1267
1268 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1269 if s.len() <= Self::SMALL_WORD_LEN {
1270 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1271 a[..s.len()].copy_from_slice(s.as_bytes());
1272 Some(a)
1273 } else {
1274 None
1275 }
1276 }
1277
1278 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1279 let metrics = match face.raw() {
1280 Some(f) => FontMetrics::new(&f, size),
1281 None => FontMetrics::empty(),
1282 };
1283
1284 Font(Arc::new(LoadedFont {
1285 metrics,
1286 face,
1287 size,
1288 variations,
1289 render_keys: Mutex::new(vec![]),
1290 small_word_cache: RwLock::default(),
1291 word_cache: RwLock::default(),
1292 }))
1293 }
1294
1295 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1296 let _span = tracing::trace_span!("Font::render_font").entered();
1297
1298 let mut render_keys = self.0.render_keys.lock();
1299 for r in render_keys.iter() {
1300 if &r.renderer == renderer && r.synthesis == synthesis {
1301 return r.font_id;
1302 }
1303 }
1304
1305 let font_key = self.0.face.render_face(renderer);
1306
1307 let mut opt = zng_view_api::font::FontOptions::default();
1308 opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1309 opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1310 let variations = self.0.variations.iter().map(|v| (v.tag.to_be_bytes(), v.value)).collect();
1311
1312 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1313 Ok(k) => k,
1314 Err(_) => {
1315 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1316 return zng_view_api::font::FontId::INVALID;
1317 }
1318 };
1319
1320 render_keys.push(RenderFont::new(renderer, synthesis, key));
1321
1322 key
1323 }
1324
1325 pub fn face(&self) -> &FontFace {
1327 &self.0.face
1328 }
1329
1330 pub fn size(&self) -> Px {
1334 self.0.size
1335 }
1336
1337 pub fn variations(&self) -> &RFontVariations {
1339 &self.0.variations
1340 }
1341
1342 pub fn metrics(&self) -> &FontMetrics {
1344 &self.0.metrics
1345 }
1346
1347 pub fn ligature_caret_offsets(
1353 &self,
1354 lig: zng_view_api::font::GlyphIndex,
1355 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1356 self.0.face.0.lig_carets.carets(lig).iter().map(move |&o| match o {
1357 ligature_util::LigatureCaret::Coordinate(o) => {
1358 let size_scale = 1.0 / self.0.metrics.units_per_em as f32 * self.0.size.0 as f32;
1359 o as f32 * size_scale
1360 }
1361 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1362 if let Some(f) = self.face().raw() {
1363 struct Search {
1364 i: u16,
1365 s: u16,
1366 x: f32,
1367 }
1368 impl Search {
1369 fn check(&mut self, x: f32) {
1370 self.s = self.s.saturating_add(1);
1371 if self.s == self.i {
1372 self.x = x;
1373 }
1374 }
1375 }
1376 impl skrifa::outline::OutlinePen for Search {
1377 fn move_to(&mut self, x: f32, _y: f32) {
1378 self.check(x);
1379 }
1380
1381 fn line_to(&mut self, x: f32, _y: f32) {
1382 self.check(x);
1383 }
1384
1385 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1386 self.check(x)
1387 }
1388
1389 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1390 self.check(x);
1391 }
1392
1393 fn close(&mut self) {}
1394 }
1395 let mut search = Search { i, s: 0, x: 0.0 };
1396 if let Some(o) = f.outline_glyphs().get(skrifa::GlyphId::new(lig))
1397 && o.draw(
1398 skrifa::outline::DrawSettings::unhinted(
1399 skrifa::instance::Size::new(self.size().0 as f32),
1400 skrifa::instance::LocationRef::default(),
1401 ),
1402 &mut search,
1403 )
1404 .is_ok()
1405 && search.s >= search.i
1406 {
1407 return search.x;
1408 }
1409 }
1410 0.0
1411 }
1412 })
1413 }
1414}
1415impl zng_app::render::Font for Font {
1416 fn is_empty_fallback(&self) -> bool {
1417 self.face().is_empty()
1418 }
1419
1420 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1421 self.render_font(renderer, synthesis)
1422 }
1423}
1424
1425#[derive(Debug, Clone)]
1429pub struct FontFaceList {
1430 fonts: Box<[FontFace]>,
1431 requested_style: FontStyle,
1432 requested_weight: FontWeight,
1433 requested_stretch: FontStretch,
1434}
1435impl FontFaceList {
1436 pub fn empty() -> Self {
1438 Self {
1439 fonts: Box::new([FontFace::empty()]),
1440 requested_style: FontStyle::Normal,
1441 requested_weight: FontWeight::NORMAL,
1442 requested_stretch: FontStretch::NORMAL,
1443 }
1444 }
1445
1446 pub fn requested_style(&self) -> FontStyle {
1448 self.requested_style
1449 }
1450
1451 pub fn requested_weight(&self) -> FontWeight {
1453 self.requested_weight
1454 }
1455
1456 pub fn requested_stretch(&self) -> FontStretch {
1458 self.requested_stretch
1459 }
1460
1461 pub fn best(&self) -> &FontFace {
1463 &self.fonts[0]
1464 }
1465
1466 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1468 if let Some(face) = self.fonts.get(face_index) {
1469 face.synthesis_for(self.requested_style, self.requested_weight)
1470 } else {
1471 FontSynthesis::DISABLED
1472 }
1473 }
1474
1475 pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1477 self.fonts.iter()
1478 }
1479
1480 pub fn len(&self) -> usize {
1484 self.fonts.len()
1485 }
1486
1487 pub fn is_empty(&self) -> bool {
1489 self.fonts[0].is_empty() && self.fonts.len() == 1
1490 }
1491
1492 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1496 FontList {
1497 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1498 requested_style: self.requested_style,
1499 requested_weight: self.requested_weight,
1500 requested_stretch: self.requested_stretch,
1501 }
1502 }
1503}
1504impl PartialEq for FontFaceList {
1505 fn eq(&self, other: &Self) -> bool {
1507 self.requested_style == other.requested_style
1508 && self.requested_weight == other.requested_weight
1509 && self.requested_stretch == other.requested_stretch
1510 && self.fonts.len() == other.fonts.len()
1511 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1512 }
1513}
1514impl Eq for FontFaceList {}
1515impl std::ops::Deref for FontFaceList {
1516 type Target = [FontFace];
1517
1518 fn deref(&self) -> &Self::Target {
1519 &self.fonts
1520 }
1521}
1522impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1523 type Item = &'a FontFace;
1524
1525 type IntoIter = std::slice::Iter<'a, FontFace>;
1526
1527 fn into_iter(self) -> Self::IntoIter {
1528 self.iter()
1529 }
1530}
1531impl std::ops::Index<usize> for FontFaceList {
1532 type Output = FontFace;
1533
1534 fn index(&self, index: usize) -> &Self::Output {
1535 &self.fonts[index]
1536 }
1537}
1538
1539#[derive(Debug, Clone)]
1541pub struct FontList {
1542 fonts: Box<[Font]>,
1543 requested_style: FontStyle,
1544 requested_weight: FontWeight,
1545 requested_stretch: FontStretch,
1546}
1547#[expect(clippy::len_without_is_empty)] impl FontList {
1549 pub fn best(&self) -> &Font {
1551 &self.fonts[0]
1552 }
1553
1554 pub fn requested_size(&self) -> Px {
1556 self.fonts[0].size()
1557 }
1558
1559 pub fn requested_style(&self) -> FontStyle {
1561 self.requested_style
1562 }
1563
1564 pub fn requested_weight(&self) -> FontWeight {
1566 self.requested_weight
1567 }
1568
1569 pub fn requested_stretch(&self) -> FontStretch {
1571 self.requested_stretch
1572 }
1573
1574 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1576 if let Some(font) = self.fonts.get(font_index) {
1577 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1578 } else {
1579 FontSynthesis::DISABLED
1580 }
1581 }
1582
1583 pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1585 self.fonts.iter()
1586 }
1587
1588 pub fn len(&self) -> usize {
1592 self.fonts.len()
1593 }
1594
1595 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1597 if self.len() != faces.len() {
1598 return false;
1599 }
1600
1601 for (font, face) in self.iter().zip(faces.iter()) {
1602 if font.face() != face {
1603 return false;
1604 }
1605 }
1606
1607 true
1608 }
1609}
1610impl PartialEq for FontList {
1611 fn eq(&self, other: &Self) -> bool {
1613 self.requested_style == other.requested_style
1614 && self.requested_weight == other.requested_weight
1615 && self.requested_stretch == other.requested_stretch
1616 && self.fonts.len() == other.fonts.len()
1617 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1618 }
1619}
1620impl Eq for FontList {}
1621impl std::ops::Deref for FontList {
1622 type Target = [Font];
1623
1624 fn deref(&self) -> &Self::Target {
1625 &self.fonts
1626 }
1627}
1628impl<'a> std::iter::IntoIterator for &'a FontList {
1629 type Item = &'a Font;
1630
1631 type IntoIter = std::slice::Iter<'a, Font>;
1632
1633 fn into_iter(self) -> Self::IntoIter {
1634 self.iter()
1635 }
1636}
1637impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1638 type Output = I::Output;
1639
1640 fn index(&self, index: I) -> &I::Output {
1641 &self.fonts[index]
1642 }
1643}
1644
1645struct FontFaceLoader {
1646 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1647
1648 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1649 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1650}
1651struct SystemFontFace {
1652 properties: (FontStyle, FontWeight, FontStretch),
1653 result: ResponseVar<Option<FontFace>>,
1654}
1655struct FontFaceListQuery {
1656 properties: (FontStyle, FontWeight, FontStretch),
1657 lang: Lang,
1658 result: ResponseVar<FontFaceList>,
1659}
1660impl FontFaceLoader {
1661 fn new() -> Self {
1662 FontFaceLoader {
1663 custom_fonts: HashMap::new(),
1664 system_fonts_cache: HashMap::new(),
1665 list_cache: HashMap::new(),
1666 }
1667 }
1668
1669 fn on_view_process_respawn(&mut self) {
1670 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1671 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1672 let mut m = face.0.m.lock();
1673 m.render_ids.clear();
1674 for inst in m.instances.values() {
1675 inst.0.render_keys.lock().clear();
1676 }
1677 }
1678 }
1679
1680 fn on_refresh(&mut self) {
1681 for (_, sys_family) in self.system_fonts_cache.drain() {
1682 for sys_font in sys_family {
1683 sys_font.result.with(|r| {
1684 if let Some(Some(face)) = r.done() {
1685 face.on_refresh();
1686 }
1687 });
1688 }
1689 }
1690 }
1691 fn on_prune(&mut self) {
1692 self.system_fonts_cache.retain(|_, v| {
1693 v.retain(|sff| {
1694 if sff.result.strong_count() == 1 {
1695 sff.result.with(|r| {
1696 match r.done() {
1697 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1701 })
1702 } else {
1703 true
1705 }
1706 });
1707 !v.is_empty()
1708 });
1709
1710 self.list_cache.clear();
1711 }
1712
1713 fn try_list(
1714 &self,
1715 families: &[FontName],
1716 style: FontStyle,
1717 weight: FontWeight,
1718 stretch: FontStretch,
1719 lang: &Lang,
1720 ) -> Option<ResponseVar<FontFaceList>> {
1721 if let Some(queries) = self.list_cache.get(families) {
1722 for q in queries {
1723 if q.properties == (style, weight, stretch) && &q.lang == lang {
1724 return Some(q.result.clone());
1725 }
1726 }
1727 }
1728 None
1729 }
1730
1731 fn load_list(
1732 &mut self,
1733 families: &[FontName],
1734 style: FontStyle,
1735 weight: FontWeight,
1736 stretch: FontStretch,
1737 lang: &Lang,
1738 ) -> ResponseVar<FontFaceList> {
1739 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1740 return r;
1741 }
1742
1743 let resolved = GenericFonts {}.resolve_list(families, lang);
1744 let families = resolved.as_ref().map(|n| &***n).unwrap_or(families);
1745 let mut list = Vec::with_capacity(families.len() + 1);
1746 let mut pending = vec![];
1747
1748 {
1749 let fallback = [GenericFonts {}.fallback(lang)];
1750 let mut used = HashSet::with_capacity(families.len());
1751 for name in families.iter().chain(&fallback) {
1752 if !used.insert(name) {
1753 continue;
1754 }
1755
1756 let face = self.load_resolved(name, style, weight, stretch);
1757 if face.is_done() {
1758 if let Some(face) = face.rsp().unwrap() {
1759 list.push(face);
1760 }
1761 } else {
1762 pending.push((list.len(), face));
1763 }
1764 }
1765 }
1766
1767 let r = if pending.is_empty() {
1768 if list.is_empty() {
1769 tracing::error!(target: "font_loading", "failed to load fallback font");
1770 list.push(FontFace::empty());
1771 }
1772 response_done_var(FontFaceList {
1773 fonts: list.into_boxed_slice(),
1774 requested_style: style,
1775 requested_weight: weight,
1776 requested_stretch: stretch,
1777 })
1778 } else {
1779 task::respond(async move {
1780 for (i, pending) in pending.into_iter().rev() {
1781 if let Some(rsp) = pending.wait_rsp().await {
1782 list.insert(i, rsp);
1783 }
1784 }
1785
1786 if list.is_empty() {
1787 tracing::error!(target: "font_loading", "failed to load fallback font");
1788 list.push(FontFace::empty());
1789 }
1790
1791 FontFaceList {
1792 fonts: list.into_boxed_slice(),
1793 requested_style: style,
1794 requested_weight: weight,
1795 requested_stretch: stretch,
1796 }
1797 })
1798 };
1799
1800 self.list_cache
1801 .entry(families.iter().cloned().collect())
1802 .or_insert_with(|| Vec::with_capacity(1))
1803 .push(FontFaceListQuery {
1804 properties: (style, weight, stretch),
1805 lang: lang.clone(),
1806 result: r.clone(),
1807 });
1808
1809 r
1810 }
1811
1812 fn try_resolved(
1814 &self,
1815 font_name: &FontName,
1816 style: FontStyle,
1817 weight: FontWeight,
1818 stretch: FontStretch,
1819 ) -> Option<ResponseVar<Option<FontFace>>> {
1820 if let Some(custom_family) = self.custom_fonts.get(font_name) {
1821 let custom = Self::match_custom(custom_family, style, weight, stretch);
1822 return Some(response_done_var(Some(custom)));
1823 }
1824
1825 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
1826 for sys_face in cached_sys_family.iter() {
1827 if sys_face.properties == (style, weight, stretch) {
1828 return Some(sys_face.result.clone());
1829 }
1830 }
1831 }
1832
1833 None
1834 }
1835
1836 fn load_resolved(
1838 &mut self,
1839 font_name: &FontName,
1840 style: FontStyle,
1841 weight: FontWeight,
1842 stretch: FontStretch,
1843 ) -> ResponseVar<Option<FontFace>> {
1844 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
1845 return cached;
1846 }
1847
1848 let load = task::wait(clmv!(font_name, || {
1849 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
1850 Some(h) => h,
1851 None => {
1852 #[cfg(debug_assertions)]
1853 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
1854
1855 #[cfg(debug_assertions)]
1856 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
1857 tracing::debug!(r#"font "{font_name}" not found"#);
1858 }
1859
1860 return None;
1861 }
1862 };
1863 match FontFace::load(bytes, face_index) {
1864 Ok(f) => Some(f),
1865 Err(FontLoadingError::UnknownFormat) => None,
1866 Err(e) => {
1867 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
1868 None
1869 }
1870 }
1871 }));
1872 let result = task::respond(async_clmv!(font_name, {
1873 match task::with_deadline(load, 10.secs()).await {
1874 Ok(r) => r,
1875 Err(_) => {
1876 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
1877 None
1878 }
1879 }
1880 }));
1881
1882 self.system_fonts_cache
1883 .entry(font_name.clone())
1884 .or_insert_with(|| Vec::with_capacity(1))
1885 .push(SystemFontFace {
1886 properties: (style, weight, stretch),
1887 result: result.clone(),
1888 });
1889
1890 result
1891 }
1892
1893 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontBytes, u32)> {
1894 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
1895 match query_util::best(font_name, style, weight, stretch) {
1896 Ok(r) => r,
1897 Err(e) => {
1898 tracing::error!("cannot get `{font_name}` system font, {e}");
1899 None
1900 }
1901 }
1902 }
1903
1904 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
1905 if faces.len() == 1 {
1906 return faces[0].clone();
1908 }
1909
1910 let mut set = Vec::with_capacity(faces.len());
1911 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
1918 |s| s > FontStretch::NORMAL
1919 } else {
1920 |s| s <= FontStretch::NORMAL
1921 };
1922 for face in faces {
1923 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
1924 if wrong_side(face.stretch()) {
1925 dist += f32::MAX as f64 + 1.0;
1926 }
1927
1928 if set.is_empty() {
1929 set.push(face);
1930 set_dist = dist;
1931 } else if dist < set_dist {
1932 set_dist = dist;
1934 set.clear();
1935 set.push(face);
1936 } else if (dist - set_dist).abs() < 0.0001 {
1937 set.push(face);
1939 }
1940 }
1941 if set.len() == 1 {
1942 return set[0].clone();
1943 }
1944
1945 let style_pref = match style {
1950 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
1951 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
1952 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
1953 };
1954 let mut best_style = style_pref.len();
1955 for face in &set {
1956 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
1957 if i < best_style {
1958 best_style = i;
1959 }
1960 }
1961 set.retain(|f| f.style() == style_pref[best_style]);
1962 if set.len() == 1 {
1963 return set[0].clone();
1964 }
1965
1966 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
1974 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1976 if face.weight() < weight {
1978 *dist += 100.0;
1980 } else if face.weight().0 > 500.0 {
1981 *dist += 600.0;
1983 }
1984 }
1985 } else if weight.0 < 400.0 {
1986 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1988 if face.weight() > weight {
1989 *dist += weight.0 as f64;
1990 }
1991 }
1992 } else {
1993 debug_assert!(weight.0 > 500.0);
1994 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1996 if face.weight() < weight {
1997 *dist += f32::MAX as f64;
1998 }
1999 }
2000 };
2001
2002 let mut best = set[0];
2003 let mut best_dist = f64::MAX;
2004
2005 for face in &set {
2006 let mut dist = (face.weight().0 - weight.0).abs() as f64;
2007
2008 add_penalty(face, weight, &mut dist);
2009
2010 if dist < best_dist {
2011 best_dist = dist;
2012 best = face;
2013 }
2014 }
2015
2016 best.clone()
2017 }
2018}
2019
2020struct RenderFontFace {
2021 renderer: ViewRenderer,
2022 face_id: zng_view_api::font::FontFaceId,
2023}
2024impl RenderFontFace {
2025 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2026 RenderFontFace {
2027 renderer: renderer.clone(),
2028 face_id,
2029 }
2030 }
2031}
2032impl Drop for RenderFontFace {
2033 fn drop(&mut self) {
2034 let _ = self.renderer.delete_font_face(self.face_id);
2036 }
2037}
2038
2039struct RenderFont {
2040 renderer: ViewRenderer,
2041 synthesis: FontSynthesis,
2042 font_id: zng_view_api::font::FontId,
2043}
2044impl RenderFont {
2045 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2046 RenderFont {
2047 renderer: renderer.clone(),
2048 synthesis,
2049 font_id,
2050 }
2051 }
2052}
2053impl Drop for RenderFont {
2054 fn drop(&mut self) {
2055 let _ = self.renderer.delete_font(self.font_id);
2057 }
2058}
2059
2060app_local! {
2061 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2062}
2063
2064struct GenericFontsService {
2065 serif: LangMap<FontName>,
2066 sans_serif: LangMap<FontName>,
2067 monospace: LangMap<FontName>,
2068 cursive: LangMap<FontName>,
2069 fantasy: LangMap<FontName>,
2070 fallback: LangMap<FontName>,
2071 system_ui: LangMap<FontNames>,
2072}
2073impl GenericFontsService {
2074 fn new() -> Self {
2075 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2076 let mut f = LangMap::with_capacity(1);
2077 f.insert(lang!(und), name.into());
2078 f
2079 }
2080
2081 let serif = "serif";
2082 let sans_serif = "sans-serif";
2083 let monospace = "monospace";
2084 let cursive = "cursive";
2085 let fantasy = "fantasy";
2086 let fallback = if cfg!(windows) {
2087 "Segoe UI Symbol"
2088 } else if cfg!(target_os = "linux") {
2089 "Standard Symbols PS"
2090 } else {
2091 "sans-serif"
2092 };
2093
2094 let mut system_ui = LangMap::with_capacity(5);
2095
2096 if cfg!(windows) {
2097 system_ui.insert(
2098 lang!("zh-Hans"),
2099 ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into(),
2100 );
2101 system_ui.insert(
2102 lang!("zh-Hant"),
2103 ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into(),
2104 );
2105 system_ui.insert(
2106 lang!("ja"),
2107 ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into(),
2108 );
2109 system_ui.insert(
2110 lang!("ko"),
2111 ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into(),
2112 );
2113 for lang in [
2114 lang!("hi"),
2115 lang!("bn"),
2116 lang!("te"),
2117 lang!("as"),
2118 lang!("gu"),
2119 lang!("kn"),
2120 lang!("mr"),
2121 lang!("ne"),
2122 lang!("or"),
2123 lang!("pa"),
2124 lang!("si"),
2125 ] {
2126 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Mangal", "Segoe Ui Emoji", "sans-serif"].into());
2127 }
2128 system_ui.insert(lang!("am"), ["Segoe UI", "Nyala", "Ebrima", "Segoe Ui Emoji", "sans-serif"].into());
2129 system_ui.insert(
2130 lang!("km"),
2131 ["Segoe UI", "Khmer UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2132 );
2133 system_ui.insert(
2134 lang!("lo"),
2135 ["Segoe UI", "lao UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2136 );
2137 system_ui.insert(lang!("th"), ["Segoe UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into());
2138 for lang in [lang!("ml"), lang!("ta")] {
2139 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Segoe Ui Emoji", "sans-serif"].into());
2140 }
2141 system_ui.insert(lang!("my"), ["Segoe UI", "Myanmar Text", "Segoe Ui Emoji", "sans-serif"].into());
2142
2143 system_ui.insert(lang!(und), ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into());
2144 } else if cfg!(target_os = "macos") {
2145 system_ui.insert(
2146 lang!("zh-Hans"),
2147 ["system-ui", "PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into(),
2148 );
2149 system_ui.insert(
2150 lang!("zh-Hant"),
2151 ["system-ui", "PingFang TC", "Apple Color Emoji", "sans-serif"].into(),
2152 );
2153 system_ui.insert(
2154 lang!("ja"),
2155 [
2156 "system-ui",
2157 "Hiragino Sans",
2158 "Hiragino Kaku Gothic ProN",
2159 "Apple Color Emoji",
2160 "sans-serif",
2161 ]
2162 .into(),
2163 );
2164 system_ui.insert(
2165 lang!("ko"),
2166 ["system-ui", "Apple SD Gothic Neo", "NanumGothic", "Apple Color Emoji", "sans-serif"].into(),
2167 );
2168
2169 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2170 system_ui.insert(
2171 lang,
2172 [
2173 "system-ui",
2174 "Kohinoor Devanagari",
2175 "Devanagari Sangam MN",
2176 "Apple Color Emoji",
2177 "sans-serif",
2178 ]
2179 .into(),
2180 );
2181 }
2182 for lang in [lang!("bn"), lang!("as")] {
2183 system_ui.insert(
2184 lang,
2185 [
2186 "system-ui",
2187 "Kohinoor Bangla",
2188 "Bangla Sangam MN",
2189 "Apple Color Emoji",
2190 "sans-serif",
2191 ]
2192 .into(),
2193 );
2194 }
2195 system_ui.insert(
2196 lang!("te"),
2197 [
2198 "system-ui",
2199 "Kohinoor Telugu",
2200 "Telugu Sangam MN",
2201 "Apple Color Emoji",
2202 "sans-serif",
2203 ]
2204 .into(),
2205 );
2206 system_ui.insert(
2207 lang!("gu"),
2208 [
2209 "system-ui",
2210 "Kohinoor Gujarati",
2211 "Gujarati Sangam MN",
2212 "Apple Color Emoji",
2213 "sans-serif",
2214 ]
2215 .into(),
2216 );
2217 system_ui.insert(
2218 lang!("kn"),
2219 ["system-ui", "Kannada Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2220 );
2221 system_ui.insert(
2222 lang!("or"),
2223 ["system-ui", "Oriya Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2224 );
2225 system_ui.insert(
2226 lang!("pa"),
2227 ["system-ui", "Mukta Mahee", "Gurmukhi Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2228 );
2229 system_ui.insert(
2230 lang!("si"),
2231 ["system-ui", "Sinhala Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2232 );
2233
2234 system_ui.insert(lang!("am"), ["system-ui", "Kefa", "Apple Color Emoji", "sans-serif"].into());
2235 system_ui.insert(
2236 lang!("km"),
2237 ["system-ui", "Khmer Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2238 );
2239 system_ui.insert(
2240 lang!("lo"),
2241 ["system-ui", "Lao Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2242 );
2243 system_ui.insert(
2244 lang!("th"),
2245 ["system-ui", "Thonburi", "Ayuthaya", "Apple Color Emoji", "sans-serif"].into(),
2246 );
2247 system_ui.insert(
2248 lang!("my"),
2249 ["system-ui", "Myanmar Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2250 );
2251
2252 system_ui.insert(
2253 lang!("ml"),
2254 ["system-ui", "Malayalam Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2255 );
2256 system_ui.insert(
2257 lang!("ta"),
2258 ["system-ui", "Kohinoor Tamil", "Tamil Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2259 );
2260
2261 for lang in [lang!("ar"), lang!("fa"), lang!("ps")] {
2262 system_ui.insert(lang, ["system-ui", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into());
2263 }
2264
2265 system_ui.insert(lang!("he"), ["system-ui", "Arial Hebrew", "Apple Color Emoji", "sans-serif"].into());
2266
2267 system_ui.insert(lang!("hy"), ["system-ui", "Mshtakan", "Apple Color Emoji", "sans-serif"].into());
2268
2269 system_ui.insert(
2270 lang!("ka"),
2271 ["system-ui", "Helvetica Neue", "Apple Color Emoji", "sans-serif"].into(),
2272 );
2273
2274 system_ui.insert(
2275 lang!("ur"),
2276 ["system-ui", "SF Arabic", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into(),
2277 );
2278
2279 system_ui.insert(
2280 lang!(und),
2281 ["system-ui", "Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into(),
2282 );
2283 } else if cfg!(target_os = "linux") {
2284 system_ui.insert(
2285 lang!("zh-Hans"),
2286 [
2287 "system-ui",
2288 "Ubuntu",
2289 "Noto Sans CJK SC",
2290 "Source Han Sans SC",
2291 "Noto Color Emoji",
2292 "sans-serif",
2293 ]
2294 .into(),
2295 );
2296 system_ui.insert(
2297 lang!("zh-Hant"),
2298 [
2299 "system-ui",
2300 "Ubuntu",
2301 "Noto Sans CJK TC",
2302 "Source Han Sans TC",
2303 "Noto Color Emoji",
2304 "sans-serif",
2305 ]
2306 .into(),
2307 );
2308 system_ui.insert(
2309 lang!("ja"),
2310 [
2311 "system-ui",
2312 "Ubuntu",
2313 "Noto Sans CJK JP",
2314 "Source Han Sans JP",
2315 "Noto Color Emoji",
2316 "sans-serif",
2317 ]
2318 .into(),
2319 );
2320 system_ui.insert(
2321 lang!("ko"),
2322 [
2323 "system-ui",
2324 "Ubuntu",
2325 "Noto Sans CJK KR",
2326 "Source Han Sans KR",
2327 "UnDotum",
2328 "Noto Color Emoji",
2329 "sans-serif",
2330 ]
2331 .into(),
2332 );
2333
2334 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2335 system_ui.insert(
2336 lang,
2337 [
2338 "system-ui",
2339 "Ubuntu",
2340 "Noto Sans Devanagari",
2341 "Lohit Devanagari",
2342 "Noto Color Emoji",
2343 "sans-serif",
2344 ]
2345 .into(),
2346 );
2347 }
2348 for lang in [lang!("bn"), lang!("as")] {
2349 system_ui.insert(
2350 lang,
2351 [
2352 "system-ui",
2353 "Ubuntu",
2354 "Noto Sans Bengali",
2355 "Lohit Bengali",
2356 "Noto Color Emoji",
2357 "sans-serif",
2358 ]
2359 .into(),
2360 );
2361 }
2362 system_ui.insert(
2363 lang!("te"),
2364 [
2365 "system-ui",
2366 "Ubuntu",
2367 "Noto Sans Telugu",
2368 "Lohit Telugu",
2369 "Noto Color Emoji",
2370 "sans-serif",
2371 ]
2372 .into(),
2373 );
2374 system_ui.insert(
2375 lang!("gu"),
2376 [
2377 "system-ui",
2378 "Ubuntu",
2379 "Noto Sans Gujarati",
2380 "Lohit Gujarati",
2381 "Noto Color Emoji",
2382 "sans-serif",
2383 ]
2384 .into(),
2385 );
2386 system_ui.insert(
2387 lang!("kn"),
2388 [
2389 "system-ui",
2390 "Ubuntu",
2391 "Noto Sans Kannada",
2392 "Lohit Kannada",
2393 "Noto Color Emoji",
2394 "sans-serif",
2395 ]
2396 .into(),
2397 );
2398 system_ui.insert(
2399 lang!("or"),
2400 [
2401 "system-ui",
2402 "Ubuntu",
2403 "Noto Sans Oriya",
2404 "Lohit Odia",
2405 "Noto Color Emoji",
2406 "sans-serif",
2407 ]
2408 .into(),
2409 );
2410 system_ui.insert(
2411 lang!("pa"),
2412 [
2413 "system-ui",
2414 "Ubuntu",
2415 "Noto Sans Gurmukhi",
2416 "Lohit Gurmukhi",
2417 "Noto Color Emoji",
2418 "sans-serif",
2419 ]
2420 .into(),
2421 );
2422 system_ui.insert(
2423 lang!("si"),
2424 [
2425 "system-ui",
2426 "Ubuntu",
2427 "Noto Sans Sinhala",
2428 "LKLUG",
2429 "Noto Color Emoji",
2430 "sans-serif",
2431 ]
2432 .into(),
2433 );
2434
2435 system_ui.insert(
2436 lang!("am"),
2437 [
2438 "system-ui",
2439 "Ubuntu",
2440 "Noto Sans Ethiopic",
2441 "Abyssinica SIL",
2442 "Noto Color Emoji",
2443 "sans-serif",
2444 ]
2445 .into(),
2446 );
2447 system_ui.insert(
2448 lang!("km"),
2449 [
2450 "system-ui",
2451 "Ubuntu",
2452 "Noto Sans Khmer",
2453 "Hanuman",
2454 "Noto Color Emoji",
2455 "sans-serif",
2456 ]
2457 .into(),
2458 );
2459 system_ui.insert(
2460 lang!("lo"),
2461 [
2462 "system-ui",
2463 "Ubuntu",
2464 "Noto Sans Lao",
2465 "Phetsarath OT",
2466 "Noto Color Emoji",
2467 "sans-serif",
2468 ]
2469 .into(),
2470 );
2471 system_ui.insert(
2472 lang!("th"),
2473 [
2474 "system-ui",
2475 "Ubuntu",
2476 "Noto Sans Thai",
2477 "Kinnari",
2478 "Garuda",
2479 "Noto Color Emoji",
2480 "sans-serif",
2481 ]
2482 .into(),
2483 );
2484 system_ui.insert(
2485 lang!("my"),
2486 [
2487 "system-ui",
2488 "Ubuntu",
2489 "Noto Sans Myanmar",
2490 "Padauk",
2491 "Noto Color Emoji",
2492 "sans-serif",
2493 ]
2494 .into(),
2495 );
2496
2497 system_ui.insert(
2498 lang!("ml"),
2499 [
2500 "system-ui",
2501 "Ubuntu",
2502 "Noto Sans Malayalam",
2503 "Lohit Malayalam",
2504 "Noto Color Emoji",
2505 "sans-serif",
2506 ]
2507 .into(),
2508 );
2509 system_ui.insert(
2510 lang!("ta"),
2511 [
2512 "system-ui",
2513 "Ubuntu",
2514 "Noto Sans Tamil",
2515 "Lohit Tamil",
2516 "Noto Color Emoji",
2517 "sans-serif",
2518 ]
2519 .into(),
2520 );
2521
2522 for lang in [lang!("ar"), lang!("fa"), lang!("ps"), lang!("ur")] {
2523 system_ui.insert(lang, ["system-ui", "Noto Sans Arabic", "Noto Color Emoji", "sans-serif"].into());
2524 }
2525
2526 system_ui.insert(
2527 lang!("he"),
2528 ["system-ui", "Noto Sans Hebrew", "Noto Color Emoji", "sans-serif"].into(),
2529 );
2530
2531 system_ui.insert(
2532 lang!("hy"),
2533 ["system-ui", "Noto Sans Armenian", "Noto Color Emoji", "sans-serif"].into(),
2534 );
2535
2536 system_ui.insert(lang!("ka"), ["system-ui", "DejaVu Sans", "Noto Color Emoji", "sans-serif"].into());
2537
2538 system_ui.insert(
2539 lang!("ur"),
2540 [
2541 "system-ui",
2542 "Noto Naskh Arabic",
2543 "Noto Sans Arabic",
2544 "Noto Color Emoji",
2545 "sans-serif",
2546 ]
2547 .into(),
2548 );
2549
2550 system_ui.insert(
2551 lang!(und),
2552 ["system-ui", "Ubuntu", "Droid Sans", "Noto Sans", "Noto Color Emoji", "sans-serif"].into(),
2553 );
2554 } else {
2555 system_ui.insert(lang!(und), ["system-ui", "sans-serif"].into());
2556 }
2557
2558 GenericFontsService {
2559 serif: default(serif),
2560 sans_serif: default(sans_serif),
2561 monospace: default(monospace),
2562 cursive: default(cursive),
2563 fantasy: default(fantasy),
2564
2565 system_ui,
2566
2567 fallback: default(fallback),
2568 }
2569 }
2570}
2571
2572#[non_exhaustive]
2583pub struct GenericFonts {}
2584macro_rules! impl_fallback_accessors {
2585 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2586 #[doc = "Gets the *"$name_str "* font for the given language."]
2587 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2591
2592 pub fn $name(&self, lang: &Lang) -> FontName {
2593 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2594 }
2595
2596 #[doc = "Sets the *"$name_str "* font for the given language."]
2597 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2602 self.[<set_ $name _impl>](lang, font_name.into());
2603 }
2604 fn [<set_ $name _impl>](&self, lang: Lang, font_name: FontName) {
2605 UPDATES.once_update("GenericFonts.set", move || {
2606 GENERIC_FONTS_SV.write().$name.insert(lang.clone(), font_name);
2607 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2608 });
2609 }
2610 })+};
2611}
2612impl GenericFonts {
2613 #[rustfmt::skip] impl_fallback_accessors! {
2615 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2616 }
2617
2618 pub fn system_ui(&self, lang: &Lang) -> FontNames {
2624 GENERIC_FONTS_SV.read().system_ui.get(lang).unwrap().clone()
2625 }
2626
2627 pub fn set_system_ui(&self, lang: Lang, font_names: impl Into<FontNames>) {
2633 self.set_system_ui_impl(lang, font_names.into())
2634 }
2635 fn set_system_ui_impl(&self, lang: Lang, font_names: FontNames) {
2636 UPDATES.once_update("GenericFonts.set_system_ui", move || {
2637 GENERIC_FONTS_SV.write().system_ui.insert(lang.clone(), font_names);
2638 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::system_ui(), lang)));
2639 });
2640 }
2641
2642 pub fn fallback(&self, lang: &Lang) -> FontName {
2646 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2647 }
2648
2649 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2655 self.set_fallback_impl(lang, font_name.into());
2656 }
2657 fn set_fallback_impl(&self, lang: Lang, font_name: FontName) {
2658 UPDATES.once_update("GenericFonts.set", move || {
2659 GENERIC_FONTS_SV.write().fallback.insert(lang.clone(), font_name);
2660 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang)));
2661 });
2662 }
2663
2664 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2672 match &**name {
2673 "serif" => Some(self.serif(lang)),
2674 "sans-serif" => Some(self.sans_serif(lang)),
2675 "monospace" => Some(self.monospace(lang)),
2676 "cursive" => Some(self.cursive(lang)),
2677 "fantasy" => Some(self.fantasy(lang)),
2678 _ => None,
2679 }
2680 }
2681
2682 pub fn resolve_list(&self, names: &[FontName], lang: &Lang) -> Option<FontNames> {
2686 if names
2687 .iter()
2688 .any(|n| ["system-ui", "serif", "sans-serif", "monospace", "cursive", "fantasy"].contains(&&**n))
2689 {
2690 let mut r = FontNames(Vec::with_capacity(names.len()));
2691 for name in names {
2692 match self.resolve(name, lang) {
2693 Some(n) => r.push(n),
2694 None => {
2695 if name == "system-ui" {
2696 r.extend(self.system_ui(lang));
2697 } else {
2698 r.push(name.clone())
2699 }
2700 }
2701 }
2702 }
2703 Some(r)
2704 } else {
2705 None
2706 }
2707 }
2708}
2709
2710#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2711pub(crate) enum WeakFontBytes {
2712 Ipc(WeakIpcBytes),
2713 Arc(std::sync::Weak<Vec<u8>>),
2714 Static(&'static [u8]),
2715 Mmap(std::sync::Weak<SystemFontBytes>),
2716}
2717#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2718impl WeakFontBytes {
2719 pub(crate) fn upgrade(&self) -> Option<FontBytes> {
2720 match self {
2721 WeakFontBytes::Ipc(weak) => Some(FontBytes(FontBytesImpl::Ipc(weak.upgrade()?))),
2722 WeakFontBytes::Arc(weak) => Some(FontBytes(FontBytesImpl::Arc(weak.upgrade()?))),
2723 WeakFontBytes::Static(b) => Some(FontBytes(FontBytesImpl::Static(b))),
2724 WeakFontBytes::Mmap(weak) => Some(FontBytes(FontBytesImpl::System(weak.upgrade()?))),
2725 }
2726 }
2727
2728 pub(crate) fn strong_count(&self) -> usize {
2729 match self {
2730 WeakFontBytes::Ipc(weak) => weak.strong_count(),
2731 WeakFontBytes::Arc(weak) => weak.strong_count(),
2732 WeakFontBytes::Static(_) => 1,
2733 WeakFontBytes::Mmap(weak) => weak.strong_count(),
2734 }
2735 }
2736}
2737
2738struct SystemFontBytes {
2739 path: std::path::PathBuf,
2740 mmap: IpcBytes,
2741}
2742
2743#[derive(Clone)]
2744enum FontBytesImpl {
2745 Ipc(IpcBytes),
2747 Arc(Arc<Vec<u8>>),
2748 Static(&'static [u8]),
2749 System(Arc<SystemFontBytes>),
2750}
2751#[derive(Clone)]
2753pub struct FontBytes(FontBytesImpl);
2754impl FontBytes {
2755 pub fn from_ipc(bytes: IpcBytes) -> Self {
2757 Self(FontBytesImpl::Ipc(bytes))
2758 }
2759
2760 pub fn from_vec(bytes: Vec<u8>) -> io::Result<Self> {
2762 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_vec_blocking(bytes)?)))
2763 }
2764
2765 pub fn from_static(bytes: &'static [u8]) -> Self {
2767 Self(FontBytesImpl::Static(bytes))
2768 }
2769
2770 pub fn from_arc(bytes: Arc<Vec<u8>>) -> Self {
2774 Self(FontBytesImpl::Arc(bytes))
2775 }
2776
2777 pub fn from_file(path: PathBuf) -> io::Result<Self> {
2779 let path = dunce::canonicalize(path)?;
2780
2781 #[cfg(windows)]
2782 {
2783 use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemWindowsDirectoryW};
2784 let mut buffer = [0u16; MAX_PATH as usize];
2785 let len = unsafe { GetSystemWindowsDirectoryW(Some(&mut buffer)) };
2787 let fonts_dir = String::from_utf16_lossy(&buffer[..len as usize]);
2788 if path.starts_with(fonts_dir) {
2790 return unsafe { load_from_system(path) };
2792 }
2793 }
2794 #[cfg(target_os = "macos")]
2795 if path.starts_with("/System/Library/Fonts/") || path.starts_with("/Library/Fonts/") {
2796 return unsafe { load_from_system(path) };
2798 }
2799 #[cfg(target_os = "android")]
2800 if path.starts_with("/system/fonts/") || path.starts_with("/system/font/") || path.starts_with("/system/product/fonts/") {
2801 return unsafe { load_from_system(path) };
2803 }
2804 #[cfg(unix)]
2805 if path.starts_with("/usr/share/fonts/") {
2806 return unsafe { load_from_system(path) };
2808 }
2809
2810 #[cfg(ipc)]
2811 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2812 let mmap = unsafe { IpcBytes::open_memmap_blocking(path.clone(), None) }?;
2814 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2815 }
2816
2817 #[cfg(all(not(ipc), not(target_arch = "wasm32")))]
2818 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2819 let mmap = IpcBytes::from_path_blocking(&path)?;
2820 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2821 }
2822
2823 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_path_blocking(&path)?)))
2824 }
2825
2826 #[cfg(ipc)]
2833 pub unsafe fn from_file_mmap(path: PathBuf) -> std::io::Result<Self> {
2834 let ipc = unsafe { IpcBytes::open_memmap_blocking(path, None) }?;
2836 Ok(Self(FontBytesImpl::Ipc(ipc)))
2837 }
2838
2839 #[cfg(ipc)]
2843 pub fn mmap_path(&self) -> Option<&std::path::Path> {
2844 if let FontBytesImpl::System(m) = &self.0 {
2845 Some(&m.path)
2846 } else {
2847 None
2848 }
2849 }
2850
2851 pub fn to_ipc(&self) -> io::Result<IpcFontBytes> {
2853 Ok(if let FontBytesImpl::System(m) = &self.0 {
2854 IpcFontBytes::System(m.path.clone())
2855 } else {
2856 IpcFontBytes::Bytes(self.to_ipc_bytes()?)
2857 })
2858 }
2859
2860 pub fn to_ipc_bytes(&self) -> io::Result<IpcBytes> {
2862 match &self.0 {
2863 FontBytesImpl::Ipc(b) => Ok(b.clone()),
2864 FontBytesImpl::Arc(b) => IpcBytes::from_slice_blocking(b),
2865 FontBytesImpl::Static(b) => IpcBytes::from_slice_blocking(b),
2866 FontBytesImpl::System(m) => IpcBytes::from_slice_blocking(&m.mmap[..]),
2867 }
2868 }
2869
2870 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2871 pub(crate) fn downgrade(&self) -> WeakFontBytes {
2872 match &self.0 {
2873 FontBytesImpl::Ipc(ipc) => WeakFontBytes::Ipc(ipc.downgrade()),
2874 FontBytesImpl::Arc(arc) => WeakFontBytes::Arc(Arc::downgrade(arc)),
2875 FontBytesImpl::Static(b) => WeakFontBytes::Static(b),
2876 FontBytesImpl::System(arc) => WeakFontBytes::Mmap(Arc::downgrade(arc)),
2877 }
2878 }
2879
2880 pub(crate) fn as_harfrust_font(&self) -> read_fonts::model::Source {
2881 use read_fonts::model::Blob;
2882 match &self.0 {
2883 FontBytesImpl::Ipc(b) => Blob::Shared(b.clone().into()),
2884 FontBytesImpl::Arc(b) => Blob::Shared(b.clone()),
2885 FontBytesImpl::Static(b) => Blob::Static(b),
2886 FontBytesImpl::System(b) => Blob::Shared(b.mmap.clone().into()),
2887 }
2888 .into()
2889 }
2890}
2891impl std::ops::Deref for FontBytes {
2892 type Target = [u8];
2893
2894 fn deref(&self) -> &Self::Target {
2895 match &self.0 {
2896 FontBytesImpl::Ipc(b) => &b[..],
2897 FontBytesImpl::Arc(b) => &b[..],
2898 FontBytesImpl::Static(b) => b,
2899 FontBytesImpl::System(m) => &m.mmap[..],
2900 }
2901 }
2902}
2903impl fmt::Debug for FontBytes {
2904 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2905 let mut b = f.debug_struct("FontBytes");
2906 b.field(
2907 ".kind",
2908 &match &self.0 {
2909 FontBytesImpl::Ipc(_) => "IpcBytes",
2910 FontBytesImpl::Arc(_) => "Arc",
2911 FontBytesImpl::Static(_) => "Static",
2912 FontBytesImpl::System(_) => "Mmap",
2913 },
2914 );
2915 b.field(".len", &(self.len() as u64).bytes());
2916 if let FontBytesImpl::System(m) = &self.0 {
2917 b.field(".path", &m.path);
2918 }
2919
2920 b.finish()
2921 }
2922}
2923
2924#[derive(Debug, Clone)]
2925enum FontSource {
2926 File(PathBuf, u32),
2927 Memory(FontBytes, u32),
2928 Alias(FontName),
2929}
2930
2931#[derive(Debug, Clone)]
2933pub struct CustomFont {
2934 name: FontName,
2935 source: FontSource,
2936 stretch: FontStretch,
2937 style: FontStyle,
2938 weight: FontWeight,
2939}
2940impl CustomFont {
2941 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2949 CustomFont {
2950 name: name.into(),
2951 source: FontSource::File(path.into(), font_index),
2952 stretch: FontStretch::NORMAL,
2953 style: FontStyle::Normal,
2954 weight: FontWeight::NORMAL,
2955 }
2956 }
2957
2958 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontBytes, font_index: u32) -> Self {
2966 CustomFont {
2967 name: name.into(),
2968 source: FontSource::Memory(data, font_index),
2969 stretch: FontStretch::NORMAL,
2970 style: FontStyle::Normal,
2971 weight: FontWeight::NORMAL,
2972 }
2973 }
2974
2975 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2981 CustomFont {
2982 name: name.into(),
2983 source: FontSource::Alias(other_font.into()),
2984 stretch: FontStretch::NORMAL,
2985 style: FontStyle::Normal,
2986 weight: FontWeight::NORMAL,
2987 }
2988 }
2989
2990 pub fn stretch(mut self, stretch: FontStretch) -> Self {
2994 self.stretch = stretch;
2995 self
2996 }
2997
2998 pub fn style(mut self, style: FontStyle) -> Self {
3002 self.style = style;
3003 self
3004 }
3005
3006 pub fn weight(mut self, weight: FontWeight) -> Self {
3010 self.weight = weight;
3011 self
3012 }
3013}
3014
3015#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
3019#[serde(transparent)]
3020pub struct FontStretch(pub f32);
3021impl fmt::Debug for FontStretch {
3022 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3023 let name = self.name();
3024 if name.is_empty() {
3025 f.debug_tuple("FontStretch").field(&self.0).finish()
3026 } else {
3027 if f.alternate() {
3028 write!(f, "FontStretch::")?;
3029 }
3030 write!(f, "{name}")
3031 }
3032 }
3033}
3034impl PartialOrd for FontStretch {
3035 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3036 Some(self.cmp(other))
3037 }
3038}
3039impl Ord for FontStretch {
3040 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3041 about_eq_ord(self.0, other.0, EQ_GRANULARITY)
3042 }
3043}
3044impl PartialEq for FontStretch {
3045 fn eq(&self, other: &Self) -> bool {
3046 about_eq(self.0, other.0, EQ_GRANULARITY)
3047 }
3048}
3049impl Eq for FontStretch {}
3050impl std::hash::Hash for FontStretch {
3051 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3052 about_eq_hash(self.0, EQ_GRANULARITY, state)
3053 }
3054}
3055impl Default for FontStretch {
3056 fn default() -> FontStretch {
3057 FontStretch::NORMAL
3058 }
3059}
3060impl FontStretch {
3061 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
3063 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
3065 pub const CONDENSED: FontStretch = FontStretch(0.75);
3067 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
3069 pub const NORMAL: FontStretch = FontStretch(1.0);
3071 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
3073 pub const EXPANDED: FontStretch = FontStretch(1.25);
3075 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
3077 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
3079
3080 pub fn name(self) -> &'static str {
3082 macro_rules! name {
3083 ($($CONST:ident;)+) => {$(
3084 if self == Self::$CONST {
3085 return stringify!($CONST);
3086 }
3087 )+}
3088 }
3089 name! {
3090 ULTRA_CONDENSED;
3091 EXTRA_CONDENSED;
3092 CONDENSED;
3093 SEMI_CONDENSED;
3094 NORMAL;
3095 SEMI_EXPANDED;
3096 EXPANDED;
3097 EXTRA_EXPANDED;
3098 ULTRA_EXPANDED;
3099 }
3100 ""
3101 }
3102}
3103impl_from_and_into_var! {
3104 fn from(fct: Factor) -> FontStretch {
3105 FontStretch(fct.0)
3106 }
3107 fn from(pct: FactorPercent) -> FontStretch {
3108 FontStretch(pct.fct().0)
3109 }
3110 fn from(fct: f32) -> FontStretch {
3111 FontStretch(fct)
3112 }
3113}
3114impl From<skrifa::attribute::Stretch> for FontStretch {
3115 fn from(value: skrifa::attribute::Stretch) -> Self {
3116 FontStretch(value.ratio())
3117 }
3118}
3119impl From<FontStretch> for skrifa::attribute::Stretch {
3120 fn from(value: FontStretch) -> Self {
3121 skrifa::attribute::Stretch::new(value.0)
3122 }
3123}
3124
3125#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
3127pub enum FontStyle {
3128 #[default]
3130 Normal,
3131 Italic,
3133 Oblique,
3135}
3136impl fmt::Debug for FontStyle {
3137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3138 if f.alternate() {
3139 write!(f, "FontStyle::")?;
3140 }
3141 match self {
3142 Self::Normal => write!(f, "Normal"),
3143 Self::Italic => write!(f, "Italic"),
3144 Self::Oblique => write!(f, "Oblique"),
3145 }
3146 }
3147}
3148impl From<skrifa::attribute::Style> for FontStyle {
3149 fn from(value: skrifa::attribute::Style) -> Self {
3150 use skrifa::attribute::Style::*;
3151 match value {
3152 Normal => FontStyle::Normal,
3153 Italic => FontStyle::Italic,
3154 Oblique(_) => FontStyle::Oblique,
3155 }
3156 }
3157}
3158
3159impl From<FontStyle> for skrifa::attribute::Style {
3160 fn from(value: FontStyle) -> Self {
3161 match value {
3162 FontStyle::Normal => Self::Normal,
3163 FontStyle::Italic => Self::Italic,
3164 FontStyle::Oblique => Self::Oblique(None),
3165 }
3166 }
3167}
3168
3169#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
3172pub struct FontWeight(pub f32);
3173impl Default for FontWeight {
3174 fn default() -> FontWeight {
3175 FontWeight::NORMAL
3176 }
3177}
3178impl fmt::Debug for FontWeight {
3179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3180 let name = self.name();
3181 if name.is_empty() {
3182 f.debug_tuple("FontWeight").field(&self.0).finish()
3183 } else {
3184 if f.alternate() {
3185 write!(f, "FontWeight::")?;
3186 }
3187 write!(f, "{name}")
3188 }
3189 }
3190}
3191impl PartialOrd for FontWeight {
3192 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3193 Some(self.cmp(other))
3194 }
3195}
3196impl Ord for FontWeight {
3197 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3198 about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
3199 }
3200}
3201impl PartialEq for FontWeight {
3202 fn eq(&self, other: &Self) -> bool {
3203 about_eq(self.0, other.0, EQ_GRANULARITY_100)
3204 }
3205}
3206impl Eq for FontWeight {}
3207impl std::hash::Hash for FontWeight {
3208 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3209 about_eq_hash(self.0, EQ_GRANULARITY_100, state)
3210 }
3211}
3212impl FontWeight {
3213 pub const THIN: FontWeight = FontWeight(100.0);
3215 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
3217 pub const LIGHT: FontWeight = FontWeight(300.0);
3219 pub const NORMAL: FontWeight = FontWeight(400.0);
3221 pub const MEDIUM: FontWeight = FontWeight(500.0);
3223 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
3225 pub const BOLD: FontWeight = FontWeight(700.0);
3227 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
3229 pub const BLACK: FontWeight = FontWeight(900.0);
3231
3232 pub fn name(self) -> &'static str {
3234 macro_rules! name {
3235 ($($CONST:ident;)+) => {$(
3236 if self == Self::$CONST {
3237 return stringify!($CONST);
3238 }
3239 )+}
3240 }
3241 name! {
3242 THIN;
3243 EXTRA_LIGHT;
3244 LIGHT;
3245 NORMAL;
3246 MEDIUM;
3247 SEMIBOLD;
3248 BOLD;
3249 EXTRA_BOLD;
3250 BLACK;
3251 }
3252 ""
3253 }
3254}
3255impl_from_and_into_var! {
3256 fn from(weight: u32) -> FontWeight {
3257 FontWeight(weight as f32)
3258 }
3259 fn from(weight: f32) -> FontWeight {
3260 FontWeight(weight)
3261 }
3262}
3263impl From<skrifa::attribute::Weight> for FontWeight {
3264 fn from(value: skrifa::attribute::Weight) -> Self {
3265 FontWeight(value.value())
3266 }
3267}
3268impl From<FontWeight> for skrifa::attribute::Weight {
3269 fn from(value: FontWeight) -> Self {
3270 skrifa::attribute::Weight::new(value.0)
3271 }
3272}
3273
3274#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3276pub enum LineBreak {
3277 Auto,
3279 Loose,
3281 Normal,
3283 Strict,
3285 Anywhere,
3287}
3288impl Default for LineBreak {
3289 fn default() -> Self {
3291 LineBreak::Auto
3292 }
3293}
3294impl fmt::Debug for LineBreak {
3295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3296 if f.alternate() {
3297 write!(f, "LineBreak::")?;
3298 }
3299 match self {
3300 LineBreak::Auto => write!(f, "Auto"),
3301 LineBreak::Loose => write!(f, "Loose"),
3302 LineBreak::Normal => write!(f, "Normal"),
3303 LineBreak::Strict => write!(f, "Strict"),
3304 LineBreak::Anywhere => write!(f, "Anywhere"),
3305 }
3306 }
3307}
3308
3309#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3314#[non_exhaustive]
3315pub enum ParagraphBreak {
3316 #[default]
3318 None,
3319 Line,
3321}
3322impl fmt::Debug for ParagraphBreak {
3323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3324 if f.alternate() {
3325 write!(f, "ParagraphBreak::")?;
3326 }
3327 match self {
3328 ParagraphBreak::None => write!(f, "None"),
3329 ParagraphBreak::Line => write!(f, "Line"),
3330 }
3331 }
3332}
3333
3334#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3336pub enum Hyphens {
3337 None,
3339 Manual,
3344 Auto,
3346}
3347impl Default for Hyphens {
3348 fn default() -> Self {
3350 Hyphens::Auto
3351 }
3352}
3353impl fmt::Debug for Hyphens {
3354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3355 if f.alternate() {
3356 write!(f, "Hyphens::")?;
3357 }
3358 match self {
3359 Hyphens::None => write!(f, "None"),
3360 Hyphens::Manual => write!(f, "Manual"),
3361 Hyphens::Auto => write!(f, "Auto"),
3362 }
3363 }
3364}
3365
3366#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3372pub enum WordBreak {
3373 Normal,
3375 BreakAll,
3377 KeepAll,
3379}
3380impl Default for WordBreak {
3381 fn default() -> Self {
3383 WordBreak::Normal
3384 }
3385}
3386impl fmt::Debug for WordBreak {
3387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3388 if f.alternate() {
3389 write!(f, "WordBreak::")?;
3390 }
3391 match self {
3392 WordBreak::Normal => write!(f, "Normal"),
3393 WordBreak::BreakAll => write!(f, "BreakAll"),
3394 WordBreak::KeepAll => write!(f, "KeepAll"),
3395 }
3396 }
3397}
3398
3399#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3401pub enum Justify {
3402 Auto,
3406 InterWord,
3408 InterLetter,
3410}
3411impl Default for Justify {
3412 fn default() -> Self {
3414 Justify::Auto
3415 }
3416}
3417impl Justify {
3418 pub fn resolve(self, lang: &Lang) -> Self {
3420 match self {
3421 Self::Auto => match lang.language.as_str() {
3422 "zh" | "ja" | "ko" => Self::InterLetter,
3423 _ => Self::InterWord,
3424 },
3425 m => m,
3426 }
3427 }
3428}
3429impl fmt::Debug for Justify {
3430 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3431 if f.alternate() {
3432 write!(f, "Justify::")?;
3433 }
3434 match self {
3435 Justify::Auto => write!(f, "Auto"),
3436 Justify::InterWord => write!(f, "InterWord"),
3437 Justify::InterLetter => write!(f, "InterLetter"),
3438 }
3439 }
3440}
3441
3442#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3444#[non_exhaustive]
3445pub struct FontMetrics {
3446 pub ascent: Px,
3448
3449 pub descent: Px,
3455
3456 pub line_gap: Px,
3458
3459 pub underline_position: Px,
3462
3463 pub underline_thickness: Px,
3465
3466 pub cap_height: Px,
3468
3469 pub x_height: Px,
3471
3472 pub bounds: PxRect,
3476
3477 units_per_em: u16,
3478}
3479impl FontMetrics {
3480 pub fn line_height(&self) -> Px {
3482 self.ascent - self.descent + self.line_gap
3483 }
3484
3485 fn new(f: &skrifa::FontRef, size: Px) -> Self {
3486 let m = f.metrics(skrifa::instance::Size::new(size.0 as f32), skrifa::instance::LocationRef::default());
3487 let u = m.underline.unwrap_or_default();
3488 let b = m.bounds.unwrap_or_default();
3489 let scale = size.0 as f32 / m.units_per_em as f32;
3490 let line_gap = self::line_gap(f) as f32 * scale;
3491 fn f32_to_px(v: f32) -> Px {
3492 euclid::point2::<f32, ()>(v, v).cast().x
3494 }
3495 Self {
3496 ascent: f32_to_px(m.ascent),
3497 descent: f32_to_px(m.descent),
3498 line_gap: f32_to_px(line_gap),
3499 underline_position: f32_to_px(u.offset),
3500 underline_thickness: f32_to_px(u.thickness),
3501 cap_height: f32_to_px(m.cap_height.unwrap_or(0.0)),
3502 x_height: f32_to_px(m.x_height.unwrap_or(0.0)),
3503 bounds: euclid::rect(b.x_min, b.y_min, b.x_max - b.x_min, b.y_max - b.y_min).cast(),
3504
3505 units_per_em: m.units_per_em,
3506 }
3507 }
3508
3509 fn empty() -> Self {
3510 Self {
3511 ascent: Px(0),
3512 descent: Px(0),
3513 line_gap: Px(0),
3514 underline_position: Px(0),
3515 underline_thickness: Px(0),
3516 cap_height: Px(0),
3517 x_height: Px(0),
3518 bounds: euclid::Rect::zero(),
3519 units_per_em: 0,
3520 }
3521 }
3522}
3523fn line_gap(f: &skrifa::FontRef) -> i16 {
3524 use read_fonts::TableProvider as _;
3526 if let Ok(os2) = f.os2() {
3527 let use_typographic_metrics = os2.version() >= 4
3528 && os2
3529 .fs_selection()
3530 .contains(read_fonts::tables::os2::SelectionFlags::USE_TYPO_METRICS);
3531 if use_typographic_metrics {
3532 return os2.s_typo_line_gap();
3533 }
3534 }
3535 if let Ok(hrea) = f.hhea() {
3536 if hrea.ascender().to_i16() == 0
3537 && hrea.descender().to_i16() == 0
3538 && let Ok(os2) = f.os2()
3539 {
3540 return if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 {
3541 return os2.s_typo_line_gap();
3542 } else {
3543 0
3544 };
3545 }
3546 return hrea.line_gap().to_i16();
3547 }
3548 0
3549}
3550
3551#[derive(Clone)]
3553pub enum TextTransformFn {
3554 None,
3556 Uppercase,
3558 Lowercase,
3560 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3562}
3563impl TextTransformFn {
3564 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3568 match self {
3569 TextTransformFn::None => Cow::Borrowed(text),
3570 TextTransformFn::Uppercase => {
3571 if text.chars().any(|c| !c.is_uppercase()) {
3572 Cow::Owned(text.to_uppercase().into())
3573 } else {
3574 Cow::Borrowed(text)
3575 }
3576 }
3577 TextTransformFn::Lowercase => {
3578 if text.chars().any(|c| !c.is_lowercase()) {
3579 Cow::Owned(text.to_lowercase().into())
3580 } else {
3581 Cow::Borrowed(text)
3582 }
3583 }
3584 TextTransformFn::Custom(fn_) => fn_(text),
3585 }
3586 }
3587
3588 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3590 TextTransformFn::Custom(Arc::new(fn_))
3591 }
3592}
3593impl fmt::Debug for TextTransformFn {
3594 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3595 if f.alternate() {
3596 write!(f, "TextTransformFn::")?;
3597 }
3598 match self {
3599 TextTransformFn::None => write!(f, "None"),
3600 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3601 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3602 TextTransformFn::Custom(_) => write!(f, "Custom"),
3603 }
3604 }
3605}
3606impl PartialEq for TextTransformFn {
3607 fn eq(&self, other: &Self) -> bool {
3608 match (self, other) {
3609 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3610 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3611 }
3612 }
3613}
3614
3615#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3617pub enum WhiteSpace {
3618 #[default]
3620 Preserve,
3621 Merge,
3624 MergeParagraph,
3627 MergeAll,
3629}
3630impl WhiteSpace {
3631 pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3635 match self {
3636 WhiteSpace::Preserve => Cow::Borrowed(text),
3637 WhiteSpace::Merge => {
3638 let mut prev_i = 0;
3640 for line in text.split_inclusive('\n') {
3641 let line_exclusive = line.trim_end_matches('\n').trim_end_matches('\r');
3643 let line_trim = line_exclusive.trim();
3644 let mut merge = line_trim.len() != line_exclusive.len() || line_trim.is_empty();
3645
3646 if !merge {
3648 let mut prev_is_space = true; for c in line.chars() {
3650 let is_space = c.is_whitespace();
3651 if prev_is_space && is_space {
3652 merge = true;
3653 break;
3654 }
3655 prev_is_space = is_space;
3656 }
3657 }
3658
3659 if !merge {
3660 prev_i += line.len();
3661 continue;
3662 }
3663
3664 let mut out = String::with_capacity(text.len() - 1);
3666 out.push_str(&text[..prev_i]);
3667
3668 let mut chars = text[prev_i..].chars();
3669 let mut prev_is_space = true;
3670 let mut prev_is_break = true;
3671 while let Some(c) = chars.next() {
3672 if c == '\r'
3673 && let Some(nc) = chars.next()
3674 {
3675 if nc == '\n' {
3676 if !prev_is_break && !out.is_empty() {
3677 out.push('\n');
3678 }
3679 prev_is_break = true;
3680 prev_is_space = true;
3681 } else {
3682 out.push(c);
3683 out.push(nc);
3684 prev_is_break = false;
3685 prev_is_space = nc.is_whitespace();
3686 }
3687 } else if c == '\n' {
3688 if !prev_is_break && !out.is_empty() {
3689 out.push('\n');
3690 }
3691 prev_is_break = true;
3692 prev_is_space = true;
3693 } else if c.is_whitespace() {
3694 if prev_is_space {
3695 continue;
3696 }
3697 out.push(' ');
3698 prev_is_space = true;
3699 } else {
3700 out.push(c);
3701 prev_is_space = false;
3702 prev_is_break = false;
3703 }
3704 }
3705
3706 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3708 out.truncate(i + c.len_utf8());
3709 }
3710
3711 return Cow::Owned(out.into());
3712 }
3713 Cow::Borrowed(text)
3714 }
3715 WhiteSpace::MergeParagraph => {
3716 let mut merge = text.contains('\n') || text.chars().last().unwrap_or('\0').is_whitespace();
3719 if !merge {
3720 let mut prev_is_space = true;
3721 for c in text.chars() {
3722 let is_space = c.is_whitespace();
3723 if prev_is_space && is_space {
3724 merge = true;
3725 break;
3726 }
3727 prev_is_space = is_space;
3728 }
3729 }
3730
3731 if merge {
3732 let mut out = String::with_capacity(text.len());
3733 let mut prev_is_break = false;
3734 for line in text.lines() {
3735 let line = line.trim();
3736 let is_break = line.is_empty();
3737 if !prev_is_break && is_break && !out.is_empty() {
3738 out.push('\n');
3739 }
3740 if !prev_is_break && !is_break && !out.is_empty() {
3741 out.push(' ');
3742 }
3743 prev_is_break = is_break;
3744
3745 let mut prev_is_space = false;
3746 for c in line.chars() {
3747 let is_space = c.is_whitespace();
3748 if is_space {
3749 if !prev_is_space {
3750 out.push(' ');
3751 }
3752 } else {
3753 out.push(c);
3754 }
3755 prev_is_space = is_space;
3756 }
3757 }
3758
3759 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3761 out.truncate(i + c.len_utf8());
3762 }
3763
3764 return Cow::Owned(out.into());
3765 }
3766 Cow::Borrowed(text)
3767 }
3768 WhiteSpace::MergeAll => {
3769 let mut prev_i = 0;
3771 let mut prev_is_space = true; for (i, c) in text.char_indices() {
3773 let is_space = c.is_whitespace();
3774 if prev_is_space && is_space || c == '\n' {
3775 if !prev_is_space {
3776 debug_assert_eq!(c, '\n');
3777 prev_i += c.len_utf8();
3778 prev_is_space = true;
3779 }
3780 let mut out = String::with_capacity(text.len() - 1);
3782 out.push_str(&text[..prev_i]);
3784 if !out.is_empty() {
3785 out.push(' ');
3786 }
3787 for c in text[(i + c.len_utf8())..].chars() {
3789 let is_space = c.is_whitespace();
3790 if prev_is_space && is_space {
3791 continue;
3792 }
3793 out.push(if is_space { ' ' } else { c });
3794 prev_is_space = is_space;
3795 }
3796
3797 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3799 out.truncate(i + c.len_utf8());
3800 }
3801
3802 return Cow::Owned(out.into());
3803 }
3804 prev_i = i;
3805 prev_is_space = is_space;
3806 }
3807
3808 let out = text.trim_end();
3812 if out.len() != text.len() {
3813 return Cow::Owned(Txt::from_str(out));
3814 }
3815
3816 Cow::Borrowed(text)
3817 }
3818 }
3819 }
3820}
3821impl fmt::Debug for WhiteSpace {
3822 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3823 if f.alternate() {
3824 write!(f, "WhiteSpace::")?;
3825 }
3826 match self {
3827 WhiteSpace::Preserve => write!(f, "Preserve"),
3828 WhiteSpace::Merge => write!(f, "Merge"),
3829 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3830 WhiteSpace::MergeParagraph => write!(f, "MergeParagraph"),
3831 }
3832 }
3833}
3834
3835#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3837pub struct CaretIndex {
3838 pub index: usize,
3842 pub line: usize,
3852}
3853
3854impl PartialEq for CaretIndex {
3855 fn eq(&self, other: &Self) -> bool {
3856 self.index == other.index
3857 }
3858}
3859impl Eq for CaretIndex {}
3860impl CaretIndex {
3861 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3863}
3864impl PartialOrd for CaretIndex {
3865 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3866 Some(self.cmp(other))
3867 }
3868}
3869impl Ord for CaretIndex {
3870 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3871 self.index.cmp(&other.index)
3872 }
3873}
3874impl_from_and_into_var! {
3875 fn from(index: usize) -> CaretIndex {
3876 CaretIndex { index, line: 0 }
3877 }
3878}
3879
3880#[derive(Debug, Clone)]
3882#[non_exhaustive]
3883pub enum FontLoadingError {
3884 UnknownFormat,
3886 NoSuchFontInCollection,
3891 Parse(read_fonts::ReadError),
3893 NoFilesystem,
3896 Io(Arc<std::io::Error>),
3898}
3899impl PartialEq for FontLoadingError {
3900 fn eq(&self, other: &Self) -> bool {
3901 match (self, other) {
3902 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3903 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3904 }
3905 }
3906}
3907impl From<std::io::Error> for FontLoadingError {
3908 fn from(error: std::io::Error) -> FontLoadingError {
3909 Self::Io(Arc::new(error))
3910 }
3911}
3912impl fmt::Display for FontLoadingError {
3913 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3914 match self {
3915 Self::UnknownFormat => write!(f, "unknown format"),
3916 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3917 Self::NoFilesystem => write!(f, "no filesystem present"),
3918 Self::Parse(e) => fmt::Display::fmt(e, f),
3919 Self::Io(e) => fmt::Display::fmt(e, f),
3920 }
3921 }
3922}
3923impl std::error::Error for FontLoadingError {
3924 fn cause(&self) -> Option<&dyn std::error::Error> {
3925 match self {
3926 FontLoadingError::Parse(e) => Some(e),
3927 FontLoadingError::Io(e) => Some(e),
3928 _ => None,
3929 }
3930 }
3931}
3932
3933#[cfg(test)]
3934mod tests {
3935 use zng_app::APP;
3936
3937 use super::*;
3938
3939 #[test]
3940 fn generic_fonts_default() {
3941 let _app = APP.minimal().run_headless(false);
3942
3943 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3944 }
3945
3946 #[test]
3947 fn generic_fonts_fallback() {
3948 let _app = APP.minimal().run_headless(false);
3949
3950 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3951 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3952 }
3953
3954 #[test]
3955 fn generic_fonts_get1() {
3956 let mut app = APP.minimal().run_headless(false);
3957 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3958 app.update(false).assert_wait();
3959
3960 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3961 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3962 }
3963
3964 #[test]
3965 fn generic_fonts_get2() {
3966 let mut app = APP.minimal().run_headless(false);
3967 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3968 app.update(false).assert_wait();
3969
3970 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3971 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3972 }
3973
3974 #[test]
3975 fn generic_fonts_get_best() {
3976 let mut app = APP.minimal().run_headless(false);
3977 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3978 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3979 app.update(false).assert_wait();
3980
3981 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3982 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3983 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3984 }
3985
3986 #[test]
3987 fn generic_fonts_get_no_lang_match() {
3988 let mut app = APP.minimal().run_headless(false);
3989 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3990 app.update(false).assert_wait();
3991
3992 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3993 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3994 }
3995
3996 #[test]
3997 fn white_space_merge() {
3998 macro_rules! test {
3999 ($input:tt, $output:tt) => {
4000 let input = Txt::from($input);
4001 let output = WhiteSpace::Merge.transform(&input);
4002 assert_eq!($output, output.as_str());
4003
4004 let input = input.replace('\n', "\r\n");
4005 let output = WhiteSpace::Merge.transform(&Txt::from(input)).replace("\r\n", "\n");
4006 assert_eq!($output, output.as_str());
4007 };
4008 }
4009 test!("a b\n\nc", "a b\nc");
4010 test!("a b\nc", "a b\nc");
4011 test!(" a b\nc\n \n", "a b\nc");
4012 test!(" \n a b\nc", "a b\nc");
4013 test!("a\n \nb", "a\nb");
4014 }
4015
4016 #[test]
4017 fn white_space_merge_paragraph() {
4018 macro_rules! test {
4019 ($input:tt, $output:tt) => {
4020 let input = Txt::from($input);
4021 let output = WhiteSpace::MergeParagraph.transform(&input);
4022 assert_eq!($output, output.as_str());
4023
4024 let input = input.replace('\n', "\r\n");
4025 let output = WhiteSpace::MergeParagraph.transform(&Txt::from(input)).replace("\r\n", "\n");
4026 assert_eq!($output, output.as_str());
4027 };
4028 }
4029 test!("a b\n\nc", "a b\nc");
4030 test!("a b\nc", "a b c");
4031 test!(" a b\nc\n \n", "a b c");
4032 test!(" \n a b\nc", "a b c");
4033 test!("a\n \nb", "a\nb");
4034 }
4035
4036 #[test]
4037 fn white_space_merge_all() {
4038 macro_rules! test {
4039 ($input:tt, $output:tt) => {
4040 let input = Txt::from($input);
4041 let output = WhiteSpace::MergeAll.transform(&input);
4042 assert_eq!($output, output.as_str());
4043 };
4044 }
4045 test!("a b\n\nc", "a b c");
4046 test!("a b\nc", "a b c");
4047 test!(" a b\nc\n \n", "a b c");
4048 test!(" \n a b\nc", "a b c");
4049 test!("a\n \nb", "a b");
4050 }
4051}