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::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 m: Mutex<FontFaceMut>,
781}
782bitflags! {
783 #[derive(Debug, Clone, Copy)]
784 struct FontFaceFlags: u8 {
785 const IS_MONOSPACE = 0b0000_0001;
786 const HAS_LIGATURES = 0b0000_0010;
787 const HAS_RASTER_IMAGES = 0b0000_0100;
788 const HAS_SVG_IMAGES = 0b0000_1000;
789 }
790}
791struct FontFaceMut {
792 instances: HashMap<FontInstanceKey, Font>,
793 render_ids: Vec<RenderFontFace>,
794 unregistered: bool,
795}
796
797impl fmt::Debug for FontFace {
798 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
799 let m = self.0.m.lock();
800 f.debug_struct("FontFace")
801 .field("display_name", &self.0.display_name)
802 .field("family_name", &self.0.family_name)
803 .field("postscript_name", &self.0.postscript_name)
804 .field("flags", &self.0.flags)
805 .field("style", &self.0.style)
806 .field("weight", &self.0.weight)
807 .field("stretch", &self.0.stretch)
808 .field("instances.len()", &m.instances.len())
809 .field("render_keys.len()", &m.render_ids.len())
810 .field("unregistered", &m.unregistered)
811 .finish_non_exhaustive()
812 }
813}
814impl PartialEq for FontFace {
815 fn eq(&self, other: &Self) -> bool {
816 Arc::ptr_eq(&self.0, &other.0)
817 }
818}
819impl Eq for FontFace {}
820impl FontFace {
821 pub fn empty() -> Self {
823 FontFace(Arc::new(LoadedFontFace {
824 data: FontBytes::from_static(&[]),
825 face_index: 0,
826 display_name: FontName::from("<empty>"),
827 family_name: FontName::from("<empty>"),
828 postscript_name: None,
829 flags: FontFaceFlags::IS_MONOSPACE,
830 style: FontStyle::Normal,
831 weight: FontWeight::NORMAL,
832 stretch: FontStretch::NORMAL,
833 lig_carets: LigatureCaretList::empty(),
834 m: Mutex::new(FontFaceMut {
835 instances: HashMap::default(),
836 render_ids: vec![],
837 unregistered: false,
838 }),
839 }))
840 }
841
842 pub fn is_empty(&self) -> bool {
844 self.0.data.is_empty()
845 }
846
847 async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
848 let bytes;
849 let mut face_index;
850
851 match custom_font.source {
852 FontSource::File(path, index) => {
853 bytes = task::wait(|| FontBytes::from_file(path)).await?;
854 face_index = index;
855 }
856 FontSource::Memory(arc, index) => {
857 bytes = arc;
858 face_index = index;
859 }
860 FontSource::Alias(other_font) => {
861 let result = FONTS_SV
862 .write()
863 .loader
864 .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
865 return match result.wait_rsp().await {
866 Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
867 data: other_font.0.data.clone(),
868 face_index: other_font.0.face_index,
869 display_name: custom_font.name.clone(),
870 family_name: custom_font.name,
871 postscript_name: None,
872 style: other_font.0.style,
873 weight: other_font.0.weight,
874 stretch: other_font.0.stretch,
875 m: Mutex::new(FontFaceMut {
876 instances: Default::default(),
877 render_ids: Default::default(),
878 unregistered: Default::default(),
879 }),
880 lig_carets: other_font.0.lig_carets.clone(),
881 flags: other_font.0.flags,
882 }))),
883 None => Err(FontLoadingError::NoSuchFontInCollection),
884 };
885 }
886 }
887
888 let ttf_face = match skrifa::FontRef::from_index(&bytes, face_index) {
889 Ok(f) => f,
890 Err(e) => {
891 match e {
892 read_fonts::ReadError::InvalidCollectionIndex(_) if face_index != 0 => face_index = 0,
894 e => return Err(FontLoadingError::Parse(e)),
895 }
896
897 match skrifa::FontRef::from_index(&bytes, face_index) {
898 Ok(f) => f,
899 Err(_) => return Err(FontLoadingError::Parse(e)),
900 }
901 }
902 };
903 use read_fonts::TableProvider as _;
904
905 let has_ligatures = ttf_face.gsub().is_ok();
906 let lig_carets = if has_ligatures {
907 LigatureCaretList::empty()
908 } else {
909 LigatureCaretList::load(&ttf_face)?
910 };
911
912 let mut flags = FontFaceFlags::empty();
913 flags.set(
914 FontFaceFlags::IS_MONOSPACE,
915 ttf_face.post().map(|p| p.is_fixed_pitch() != 0).unwrap_or(false),
916 );
917 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
918 flags.set(
919 FontFaceFlags::HAS_RASTER_IMAGES,
920 ttf_face.sbix().is_ok() || ttf_face.ebdt().is_ok() || ttf_face.cbdt().is_ok(),
921 );
922 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.svg().is_ok());
923
924 Ok(FontFace(Arc::new(LoadedFontFace {
925 face_index,
926 display_name: custom_font.name.clone(),
927 family_name: custom_font.name,
928 postscript_name: None,
929 style: custom_font.style,
930 weight: custom_font.weight,
931 stretch: custom_font.stretch,
932 lig_carets,
933 m: Mutex::new(FontFaceMut {
934 instances: Default::default(),
935 render_ids: Default::default(),
936 unregistered: Default::default(),
937 }),
938 data: bytes,
939 flags,
940 })))
941 }
942
943 fn load(bytes: FontBytes, mut face_index: u32) -> Result<Self, FontLoadingError> {
944 let _span = tracing::trace_span!("FontFace::load").entered();
945
946 let ttf_face = match skrifa::FontRef::from_index(&bytes, face_index) {
947 Ok(f) => f,
948 Err(e) => {
949 match e {
950 read_fonts::ReadError::InvalidCollectionIndex(_) if face_index != 0 => face_index = 0,
952 e => return Err(FontLoadingError::Parse(e)),
953 }
954
955 match skrifa::FontRef::from_index(&bytes, face_index) {
956 Ok(f) => f,
957 Err(_) => return Err(FontLoadingError::Parse(e)),
958 }
959 }
960 };
961 use read_fonts::TableProvider as _;
962
963 let has_ligatures = ttf_face.gsub().is_ok();
964 let lig_carets = if has_ligatures {
965 LigatureCaretList::empty()
966 } else {
967 LigatureCaretList::load(&ttf_face)?
968 };
969
970 let mut display_name = None;
971 let mut family_name = None;
972 let mut postscript_name = None;
973 let mut any_name = None::<Txt>;
974 if let Ok(name) = ttf_face.name() {
975 for record in name.name_record() {
976 let n = match record.string(name.string_data()) {
977 Ok(n) => n.to_txt(),
978 Err(_) => continue,
979 };
980 match record.name_id() {
981 read_fonts::tables::name::NameId::FULL_NAME => display_name = Some(n),
982 read_fonts::tables::name::NameId::FAMILY_NAME => family_name = Some(n),
983 read_fonts::tables::name::NameId::POSTSCRIPT_NAME => postscript_name = Some(n),
984 _ => {
985 if let Some(t) = &mut any_name {
986 if t.len() < n.len() {
987 *t = n;
988 }
989 } else {
990 any_name = Some(n)
991 }
992 }
993 }
994 }
995 }
996 let display_name = FontName::new(
997 display_name
998 .clone()
999 .or_else(|| family_name.clone())
1000 .or_else(|| postscript_name.clone())
1001 .or_else(|| any_name.clone())
1002 .unwrap_or_default(),
1003 );
1004 let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1005 let postscript_name = postscript_name;
1006
1007 let mut flags = FontFaceFlags::empty();
1008 flags.set(
1009 FontFaceFlags::IS_MONOSPACE,
1010 ttf_face.post().map(|p| p.is_fixed_pitch() != 0).unwrap_or(false),
1011 );
1012 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1013 flags.set(
1014 FontFaceFlags::HAS_RASTER_IMAGES,
1015 ttf_face.sbix().is_ok() || ttf_face.ebdt().is_ok() || ttf_face.cbdt().is_ok(),
1016 );
1017 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.svg().is_ok());
1018
1019 let attr = ttf_face.attributes();
1020
1021 Ok(FontFace(Arc::new(LoadedFontFace {
1022 face_index,
1023 family_name,
1024 display_name,
1025 postscript_name,
1026 style: attr.style.into(),
1027 weight: attr.weight.into(),
1028 stretch: attr.stretch.into(),
1029 lig_carets,
1030 m: Mutex::new(FontFaceMut {
1031 instances: Default::default(),
1032 render_ids: Default::default(),
1033 unregistered: Default::default(),
1034 }),
1035 data: bytes,
1036 flags,
1037 })))
1038 }
1039
1040 fn on_refresh(&self) {
1041 let mut m = self.0.m.lock();
1042 m.instances.clear();
1043 m.unregistered = true;
1044 }
1045
1046 fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1047 let mut m = self.0.m.lock();
1048 for r in m.render_ids.iter() {
1049 if &r.renderer == renderer {
1050 return r.face_id;
1051 }
1052 }
1053
1054 let data = match self.0.data.to_ipc() {
1055 Ok(d) => d,
1056 Err(e) => {
1057 tracing::error!("cannot allocate ipc font data, {e}");
1058 return zng_view_api::font::FontFaceId::INVALID;
1059 }
1060 };
1061
1062 let key = match renderer.add_font_face(data, self.0.face_index) {
1063 Ok(k) => k,
1064 Err(_) => {
1065 tracing::debug!("respawned calling `add_font`, will return dummy font key");
1066 return zng_view_api::font::FontFaceId::INVALID;
1067 }
1068 };
1069
1070 m.render_ids.push(RenderFontFace::new(renderer, key));
1071
1072 key
1073 }
1074
1075 pub(crate) fn raw(&self) -> Option<harfrust::FontRef<'_>> {
1076 if self.is_empty() {
1077 None
1078 } else {
1079 Some(harfrust::FontRef::from_index(&self.0.data, self.0.face_index).unwrap())
1080 }
1081 }
1082
1083 pub fn bytes(&self) -> &FontBytes {
1085 &self.0.data
1086 }
1087 pub fn index(&self) -> u32 {
1089 self.0.face_index
1090 }
1091
1092 pub fn display_name(&self) -> &FontName {
1094 &self.0.display_name
1095 }
1096
1097 pub fn family_name(&self) -> &FontName {
1099 &self.0.family_name
1100 }
1101
1102 pub fn postscript_name(&self) -> Option<&str> {
1104 self.0.postscript_name.as_deref()
1105 }
1106
1107 pub fn style(&self) -> FontStyle {
1109 self.0.style
1110 }
1111
1112 pub fn weight(&self) -> FontWeight {
1114 self.0.weight
1115 }
1116
1117 pub fn stretch(&self) -> FontStretch {
1119 self.0.stretch
1120 }
1121
1122 pub fn is_monospace(&self) -> bool {
1124 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1125 }
1126
1127 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1136 let key = FontInstanceKey::new(font_size, &variations);
1137 let mut m = self.0.m.lock();
1138 if !m.unregistered {
1139 m.instances
1140 .entry(key)
1141 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1142 .clone()
1143 } else {
1144 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1145 Font::new(self.clone(), font_size, variations)
1146 }
1147 }
1148
1149 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1151 let mut synth = FontSynthesis::DISABLED;
1152
1153 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1154 synth |= FontSynthesis::OBLIQUE;
1156 }
1157 if weight > self.weight() {
1158 synth |= FontSynthesis::BOLD;
1161 }
1162
1163 synth
1164 }
1165
1166 pub fn is_cached(&self) -> bool {
1170 !self.0.m.lock().unregistered
1171 }
1172
1173 pub fn color_palettes(&self) -> ColorPalettes<'_> {
1177 match self.raw() {
1178 Some(ttf) => ColorPalettes::new(ttf),
1179 None => ColorPalettes::empty(),
1180 }
1181 }
1182
1183 pub fn color_glyphs(&self) -> ColorGlyphs<'_> {
1187 match self.raw() {
1188 Some(ttf) => ColorGlyphs::new(ttf),
1189 None => ColorGlyphs::empty(),
1190 }
1191 }
1192
1193 pub fn has_ligatures(&self) -> bool {
1195 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1196 }
1197
1198 pub fn has_ligature_caret_offsets(&self) -> bool {
1203 !self.0.lig_carets.is_empty()
1204 }
1205
1206 pub fn has_raster_images(&self) -> bool {
1208 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1209 }
1210
1211 pub fn has_svg_images(&self) -> bool {
1213 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1214 }
1215}
1216
1217#[derive(Clone)]
1223pub struct Font(Arc<LoadedFont>);
1224struct LoadedFont {
1225 face: FontFace,
1226 size: Px,
1227 variations: RFontVariations,
1228 metrics: FontMetrics,
1229 render_keys: Mutex<Vec<RenderFont>>,
1230 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1231 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1232 shaper_cache: Option<harfrust::ShaperData>,
1233}
1234impl fmt::Debug for Font {
1235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1236 f.debug_struct("Font")
1237 .field("face", &self.0.face)
1238 .field("size", &self.0.size)
1239 .field("metrics", &self.0.metrics)
1240 .field("render_keys.len()", &self.0.render_keys.lock().len())
1241 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1242 .field("word_cache.len()", &self.0.word_cache.read().len())
1243 .finish()
1244 }
1245}
1246impl PartialEq for Font {
1247 fn eq(&self, other: &Self) -> bool {
1248 Arc::ptr_eq(&self.0, &other.0)
1249 }
1250}
1251impl Eq for Font {}
1252impl Font {
1253 const SMALL_WORD_LEN: usize = 8;
1254
1255 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1256 if s.len() <= Self::SMALL_WORD_LEN {
1257 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1258 a[..s.len()].copy_from_slice(s.as_bytes());
1259 Some(a)
1260 } else {
1261 None
1262 }
1263 }
1264
1265 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1266 let (metrics, shaper_cache) = match face.raw() {
1267 Some(f) => (FontMetrics::new(&f, size), Some(harfrust::ShaperData::new(&f))),
1268 None => (FontMetrics::empty(), None),
1269 };
1270
1271 Font(Arc::new(LoadedFont {
1272 metrics,
1273 face,
1274 size,
1275 variations,
1276 render_keys: Mutex::new(vec![]),
1277 small_word_cache: RwLock::default(),
1278 word_cache: RwLock::default(),
1279 shaper_cache,
1280 }))
1281 }
1282
1283 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1284 let _span = tracing::trace_span!("Font::render_font").entered();
1285
1286 let mut render_keys = self.0.render_keys.lock();
1287 for r in render_keys.iter() {
1288 if &r.renderer == renderer && r.synthesis == synthesis {
1289 return r.font_id;
1290 }
1291 }
1292
1293 let font_key = self.0.face.render_face(renderer);
1294
1295 let mut opt = zng_view_api::font::FontOptions::default();
1296 opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1297 opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1298 let variations = self.0.variations.iter().map(|v| (v.tag.to_be_bytes(), v.value)).collect();
1299
1300 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1301 Ok(k) => k,
1302 Err(_) => {
1303 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1304 return zng_view_api::font::FontId::INVALID;
1305 }
1306 };
1307
1308 render_keys.push(RenderFont::new(renderer, synthesis, key));
1309
1310 key
1311 }
1312
1313 pub fn face(&self) -> &FontFace {
1315 &self.0.face
1316 }
1317
1318 pub fn size(&self) -> Px {
1322 self.0.size
1323 }
1324
1325 pub fn variations(&self) -> &RFontVariations {
1327 &self.0.variations
1328 }
1329
1330 pub fn metrics(&self) -> &FontMetrics {
1332 &self.0.metrics
1333 }
1334
1335 pub fn ligature_caret_offsets(
1341 &self,
1342 lig: zng_view_api::font::GlyphIndex,
1343 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1344 self.0.face.0.lig_carets.carets(lig).iter().map(move |&o| match o {
1345 ligature_util::LigatureCaret::Coordinate(o) => {
1346 let size_scale = 1.0 / self.0.metrics.units_per_em as f32 * self.0.size.0 as f32;
1347 o as f32 * size_scale
1348 }
1349 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1350 if let Some(f) = self.face().raw() {
1351 struct Search {
1352 i: u16,
1353 s: u16,
1354 x: f32,
1355 }
1356 impl Search {
1357 fn check(&mut self, x: f32) {
1358 self.s = self.s.saturating_add(1);
1359 if self.s == self.i {
1360 self.x = x;
1361 }
1362 }
1363 }
1364 impl skrifa::outline::OutlinePen for Search {
1365 fn move_to(&mut self, x: f32, _y: f32) {
1366 self.check(x);
1367 }
1368
1369 fn line_to(&mut self, x: f32, _y: f32) {
1370 self.check(x);
1371 }
1372
1373 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1374 self.check(x)
1375 }
1376
1377 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1378 self.check(x);
1379 }
1380
1381 fn close(&mut self) {}
1382 }
1383 let mut search = Search { i, s: 0, x: 0.0 };
1384 if let Some(o) = f.outline_glyphs().get(skrifa::GlyphId::new(lig))
1385 && o.draw(
1386 skrifa::outline::DrawSettings::unhinted(
1387 skrifa::instance::Size::new(self.size().0 as f32),
1388 skrifa::instance::LocationRef::default(),
1389 ),
1390 &mut search,
1391 )
1392 .is_ok()
1393 && search.s >= search.i
1394 {
1395 return search.x;
1396 }
1397 }
1398 0.0
1399 }
1400 })
1401 }
1402}
1403impl zng_app::render::Font for Font {
1404 fn is_empty_fallback(&self) -> bool {
1405 self.face().is_empty()
1406 }
1407
1408 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1409 self.render_font(renderer, synthesis)
1410 }
1411}
1412
1413#[derive(Debug, Clone)]
1417pub struct FontFaceList {
1418 fonts: Box<[FontFace]>,
1419 requested_style: FontStyle,
1420 requested_weight: FontWeight,
1421 requested_stretch: FontStretch,
1422}
1423impl FontFaceList {
1424 pub fn empty() -> Self {
1426 Self {
1427 fonts: Box::new([FontFace::empty()]),
1428 requested_style: FontStyle::Normal,
1429 requested_weight: FontWeight::NORMAL,
1430 requested_stretch: FontStretch::NORMAL,
1431 }
1432 }
1433
1434 pub fn requested_style(&self) -> FontStyle {
1436 self.requested_style
1437 }
1438
1439 pub fn requested_weight(&self) -> FontWeight {
1441 self.requested_weight
1442 }
1443
1444 pub fn requested_stretch(&self) -> FontStretch {
1446 self.requested_stretch
1447 }
1448
1449 pub fn best(&self) -> &FontFace {
1451 &self.fonts[0]
1452 }
1453
1454 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1456 if let Some(face) = self.fonts.get(face_index) {
1457 face.synthesis_for(self.requested_style, self.requested_weight)
1458 } else {
1459 FontSynthesis::DISABLED
1460 }
1461 }
1462
1463 pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1465 self.fonts.iter()
1466 }
1467
1468 pub fn len(&self) -> usize {
1472 self.fonts.len()
1473 }
1474
1475 pub fn is_empty(&self) -> bool {
1477 self.fonts[0].is_empty() && self.fonts.len() == 1
1478 }
1479
1480 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1484 FontList {
1485 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1486 requested_style: self.requested_style,
1487 requested_weight: self.requested_weight,
1488 requested_stretch: self.requested_stretch,
1489 }
1490 }
1491}
1492impl PartialEq for FontFaceList {
1493 fn eq(&self, other: &Self) -> bool {
1495 self.requested_style == other.requested_style
1496 && self.requested_weight == other.requested_weight
1497 && self.requested_stretch == other.requested_stretch
1498 && self.fonts.len() == other.fonts.len()
1499 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1500 }
1501}
1502impl Eq for FontFaceList {}
1503impl std::ops::Deref for FontFaceList {
1504 type Target = [FontFace];
1505
1506 fn deref(&self) -> &Self::Target {
1507 &self.fonts
1508 }
1509}
1510impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1511 type Item = &'a FontFace;
1512
1513 type IntoIter = std::slice::Iter<'a, FontFace>;
1514
1515 fn into_iter(self) -> Self::IntoIter {
1516 self.iter()
1517 }
1518}
1519impl std::ops::Index<usize> for FontFaceList {
1520 type Output = FontFace;
1521
1522 fn index(&self, index: usize) -> &Self::Output {
1523 &self.fonts[index]
1524 }
1525}
1526
1527#[derive(Debug, Clone)]
1529pub struct FontList {
1530 fonts: Box<[Font]>,
1531 requested_style: FontStyle,
1532 requested_weight: FontWeight,
1533 requested_stretch: FontStretch,
1534}
1535#[expect(clippy::len_without_is_empty)] impl FontList {
1537 pub fn best(&self) -> &Font {
1539 &self.fonts[0]
1540 }
1541
1542 pub fn requested_size(&self) -> Px {
1544 self.fonts[0].size()
1545 }
1546
1547 pub fn requested_style(&self) -> FontStyle {
1549 self.requested_style
1550 }
1551
1552 pub fn requested_weight(&self) -> FontWeight {
1554 self.requested_weight
1555 }
1556
1557 pub fn requested_stretch(&self) -> FontStretch {
1559 self.requested_stretch
1560 }
1561
1562 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1564 if let Some(font) = self.fonts.get(font_index) {
1565 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1566 } else {
1567 FontSynthesis::DISABLED
1568 }
1569 }
1570
1571 pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1573 self.fonts.iter()
1574 }
1575
1576 pub fn len(&self) -> usize {
1580 self.fonts.len()
1581 }
1582
1583 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1585 if self.len() != faces.len() {
1586 return false;
1587 }
1588
1589 for (font, face) in self.iter().zip(faces.iter()) {
1590 if font.face() != face {
1591 return false;
1592 }
1593 }
1594
1595 true
1596 }
1597}
1598impl PartialEq for FontList {
1599 fn eq(&self, other: &Self) -> bool {
1601 self.requested_style == other.requested_style
1602 && self.requested_weight == other.requested_weight
1603 && self.requested_stretch == other.requested_stretch
1604 && self.fonts.len() == other.fonts.len()
1605 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1606 }
1607}
1608impl Eq for FontList {}
1609impl std::ops::Deref for FontList {
1610 type Target = [Font];
1611
1612 fn deref(&self) -> &Self::Target {
1613 &self.fonts
1614 }
1615}
1616impl<'a> std::iter::IntoIterator for &'a FontList {
1617 type Item = &'a Font;
1618
1619 type IntoIter = std::slice::Iter<'a, Font>;
1620
1621 fn into_iter(self) -> Self::IntoIter {
1622 self.iter()
1623 }
1624}
1625impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1626 type Output = I::Output;
1627
1628 fn index(&self, index: I) -> &I::Output {
1629 &self.fonts[index]
1630 }
1631}
1632
1633struct FontFaceLoader {
1634 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1635
1636 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1637 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1638}
1639struct SystemFontFace {
1640 properties: (FontStyle, FontWeight, FontStretch),
1641 result: ResponseVar<Option<FontFace>>,
1642}
1643struct FontFaceListQuery {
1644 properties: (FontStyle, FontWeight, FontStretch),
1645 lang: Lang,
1646 result: ResponseVar<FontFaceList>,
1647}
1648impl FontFaceLoader {
1649 fn new() -> Self {
1650 FontFaceLoader {
1651 custom_fonts: HashMap::new(),
1652 system_fonts_cache: HashMap::new(),
1653 list_cache: HashMap::new(),
1654 }
1655 }
1656
1657 fn on_view_process_respawn(&mut self) {
1658 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1659 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1660 let mut m = face.0.m.lock();
1661 m.render_ids.clear();
1662 for inst in m.instances.values() {
1663 inst.0.render_keys.lock().clear();
1664 }
1665 }
1666 }
1667
1668 fn on_refresh(&mut self) {
1669 for (_, sys_family) in self.system_fonts_cache.drain() {
1670 for sys_font in sys_family {
1671 sys_font.result.with(|r| {
1672 if let Some(Some(face)) = r.done() {
1673 face.on_refresh();
1674 }
1675 });
1676 }
1677 }
1678 }
1679 fn on_prune(&mut self) {
1680 self.system_fonts_cache.retain(|_, v| {
1681 v.retain(|sff| {
1682 if sff.result.strong_count() == 1 {
1683 sff.result.with(|r| {
1684 match r.done() {
1685 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1689 })
1690 } else {
1691 true
1693 }
1694 });
1695 !v.is_empty()
1696 });
1697
1698 self.list_cache.clear();
1699 }
1700
1701 fn try_list(
1702 &self,
1703 families: &[FontName],
1704 style: FontStyle,
1705 weight: FontWeight,
1706 stretch: FontStretch,
1707 lang: &Lang,
1708 ) -> Option<ResponseVar<FontFaceList>> {
1709 if let Some(queries) = self.list_cache.get(families) {
1710 for q in queries {
1711 if q.properties == (style, weight, stretch) && &q.lang == lang {
1712 return Some(q.result.clone());
1713 }
1714 }
1715 }
1716 None
1717 }
1718
1719 fn load_list(
1720 &mut self,
1721 families: &[FontName],
1722 style: FontStyle,
1723 weight: FontWeight,
1724 stretch: FontStretch,
1725 lang: &Lang,
1726 ) -> ResponseVar<FontFaceList> {
1727 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1728 return r;
1729 }
1730
1731 let resolved = GenericFonts {}.resolve_list(families, lang);
1732 let families = resolved.as_ref().map(|n| &***n).unwrap_or(families);
1733 let mut list = Vec::with_capacity(families.len() + 1);
1734 let mut pending = vec![];
1735
1736 {
1737 let fallback = [GenericFonts {}.fallback(lang)];
1738 let mut used = HashSet::with_capacity(families.len());
1739 for name in families.iter().chain(&fallback) {
1740 if !used.insert(name) {
1741 continue;
1742 }
1743
1744 let face = self.load_resolved(name, style, weight, stretch);
1745 if face.is_done() {
1746 if let Some(face) = face.rsp().unwrap() {
1747 list.push(face);
1748 }
1749 } else {
1750 pending.push((list.len(), face));
1751 }
1752 }
1753 }
1754
1755 let r = if pending.is_empty() {
1756 if list.is_empty() {
1757 tracing::error!(target: "font_loading", "failed to load fallback font");
1758 list.push(FontFace::empty());
1759 }
1760 response_done_var(FontFaceList {
1761 fonts: list.into_boxed_slice(),
1762 requested_style: style,
1763 requested_weight: weight,
1764 requested_stretch: stretch,
1765 })
1766 } else {
1767 task::respond(async move {
1768 for (i, pending) in pending.into_iter().rev() {
1769 if let Some(rsp) = pending.wait_rsp().await {
1770 list.insert(i, rsp);
1771 }
1772 }
1773
1774 if list.is_empty() {
1775 tracing::error!(target: "font_loading", "failed to load fallback font");
1776 list.push(FontFace::empty());
1777 }
1778
1779 FontFaceList {
1780 fonts: list.into_boxed_slice(),
1781 requested_style: style,
1782 requested_weight: weight,
1783 requested_stretch: stretch,
1784 }
1785 })
1786 };
1787
1788 self.list_cache
1789 .entry(families.iter().cloned().collect())
1790 .or_insert_with(|| Vec::with_capacity(1))
1791 .push(FontFaceListQuery {
1792 properties: (style, weight, stretch),
1793 lang: lang.clone(),
1794 result: r.clone(),
1795 });
1796
1797 r
1798 }
1799
1800 fn try_resolved(
1802 &self,
1803 font_name: &FontName,
1804 style: FontStyle,
1805 weight: FontWeight,
1806 stretch: FontStretch,
1807 ) -> Option<ResponseVar<Option<FontFace>>> {
1808 if let Some(custom_family) = self.custom_fonts.get(font_name) {
1809 let custom = Self::match_custom(custom_family, style, weight, stretch);
1810 return Some(response_done_var(Some(custom)));
1811 }
1812
1813 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
1814 for sys_face in cached_sys_family.iter() {
1815 if sys_face.properties == (style, weight, stretch) {
1816 return Some(sys_face.result.clone());
1817 }
1818 }
1819 }
1820
1821 None
1822 }
1823
1824 fn load_resolved(
1826 &mut self,
1827 font_name: &FontName,
1828 style: FontStyle,
1829 weight: FontWeight,
1830 stretch: FontStretch,
1831 ) -> ResponseVar<Option<FontFace>> {
1832 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
1833 return cached;
1834 }
1835
1836 let load = task::wait(clmv!(font_name, || {
1837 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
1838 Some(h) => h,
1839 None => {
1840 #[cfg(debug_assertions)]
1841 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
1842
1843 #[cfg(debug_assertions)]
1844 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
1845 tracing::debug!(r#"font "{font_name}" not found"#);
1846 }
1847
1848 return None;
1849 }
1850 };
1851 match FontFace::load(bytes, face_index) {
1852 Ok(f) => Some(f),
1853 Err(FontLoadingError::UnknownFormat) => None,
1854 Err(e) => {
1855 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
1856 None
1857 }
1858 }
1859 }));
1860 let result = task::respond(async_clmv!(font_name, {
1861 match task::with_deadline(load, 10.secs()).await {
1862 Ok(r) => r,
1863 Err(_) => {
1864 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
1865 None
1866 }
1867 }
1868 }));
1869
1870 self.system_fonts_cache
1871 .entry(font_name.clone())
1872 .or_insert_with(|| Vec::with_capacity(1))
1873 .push(SystemFontFace {
1874 properties: (style, weight, stretch),
1875 result: result.clone(),
1876 });
1877
1878 result
1879 }
1880
1881 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontBytes, u32)> {
1882 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
1883 match query_util::best(font_name, style, weight, stretch) {
1884 Ok(r) => r,
1885 Err(e) => {
1886 tracing::error!("cannot get `{font_name}` system font, {e}");
1887 None
1888 }
1889 }
1890 }
1891
1892 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
1893 if faces.len() == 1 {
1894 return faces[0].clone();
1896 }
1897
1898 let mut set = Vec::with_capacity(faces.len());
1899 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
1906 |s| s > FontStretch::NORMAL
1907 } else {
1908 |s| s <= FontStretch::NORMAL
1909 };
1910 for face in faces {
1911 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
1912 if wrong_side(face.stretch()) {
1913 dist += f32::MAX as f64 + 1.0;
1914 }
1915
1916 if set.is_empty() {
1917 set.push(face);
1918 set_dist = dist;
1919 } else if dist < set_dist {
1920 set_dist = dist;
1922 set.clear();
1923 set.push(face);
1924 } else if (dist - set_dist).abs() < 0.0001 {
1925 set.push(face);
1927 }
1928 }
1929 if set.len() == 1 {
1930 return set[0].clone();
1931 }
1932
1933 let style_pref = match style {
1938 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
1939 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
1940 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
1941 };
1942 let mut best_style = style_pref.len();
1943 for face in &set {
1944 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
1945 if i < best_style {
1946 best_style = i;
1947 }
1948 }
1949 set.retain(|f| f.style() == style_pref[best_style]);
1950 if set.len() == 1 {
1951 return set[0].clone();
1952 }
1953
1954 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
1962 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1964 if face.weight() < weight {
1966 *dist += 100.0;
1968 } else if face.weight().0 > 500.0 {
1969 *dist += 600.0;
1971 }
1972 }
1973 } else if weight.0 < 400.0 {
1974 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1976 if face.weight() > weight {
1977 *dist += weight.0 as f64;
1978 }
1979 }
1980 } else {
1981 debug_assert!(weight.0 > 500.0);
1982 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
1984 if face.weight() < weight {
1985 *dist += f32::MAX as f64;
1986 }
1987 }
1988 };
1989
1990 let mut best = set[0];
1991 let mut best_dist = f64::MAX;
1992
1993 for face in &set {
1994 let mut dist = (face.weight().0 - weight.0).abs() as f64;
1995
1996 add_penalty(face, weight, &mut dist);
1997
1998 if dist < best_dist {
1999 best_dist = dist;
2000 best = face;
2001 }
2002 }
2003
2004 best.clone()
2005 }
2006}
2007
2008struct RenderFontFace {
2009 renderer: ViewRenderer,
2010 face_id: zng_view_api::font::FontFaceId,
2011}
2012impl RenderFontFace {
2013 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2014 RenderFontFace {
2015 renderer: renderer.clone(),
2016 face_id,
2017 }
2018 }
2019}
2020impl Drop for RenderFontFace {
2021 fn drop(&mut self) {
2022 let _ = self.renderer.delete_font_face(self.face_id);
2024 }
2025}
2026
2027struct RenderFont {
2028 renderer: ViewRenderer,
2029 synthesis: FontSynthesis,
2030 font_id: zng_view_api::font::FontId,
2031}
2032impl RenderFont {
2033 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2034 RenderFont {
2035 renderer: renderer.clone(),
2036 synthesis,
2037 font_id,
2038 }
2039 }
2040}
2041impl Drop for RenderFont {
2042 fn drop(&mut self) {
2043 let _ = self.renderer.delete_font(self.font_id);
2045 }
2046}
2047
2048app_local! {
2049 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2050}
2051
2052struct GenericFontsService {
2053 serif: LangMap<FontName>,
2054 sans_serif: LangMap<FontName>,
2055 monospace: LangMap<FontName>,
2056 cursive: LangMap<FontName>,
2057 fantasy: LangMap<FontName>,
2058 fallback: LangMap<FontName>,
2059 system_ui: LangMap<FontNames>,
2060}
2061impl GenericFontsService {
2062 fn new() -> Self {
2063 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2064 let mut f = LangMap::with_capacity(1);
2065 f.insert(lang!(und), name.into());
2066 f
2067 }
2068
2069 let serif = "serif";
2070 let sans_serif = "sans-serif";
2071 let monospace = "monospace";
2072 let cursive = "cursive";
2073 let fantasy = "fantasy";
2074 let fallback = if cfg!(windows) {
2075 "Segoe UI Symbol"
2076 } else if cfg!(target_os = "linux") {
2077 "Standard Symbols PS"
2078 } else {
2079 "sans-serif"
2080 };
2081
2082 let mut system_ui = LangMap::with_capacity(5);
2083
2084 if cfg!(windows) {
2085 system_ui.insert(
2086 lang!("zh-Hans"),
2087 ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into(),
2088 );
2089 system_ui.insert(
2090 lang!("zh-Hant"),
2091 ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into(),
2092 );
2093 system_ui.insert(
2094 lang!("ja"),
2095 ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into(),
2096 );
2097 system_ui.insert(
2098 lang!("ko"),
2099 ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into(),
2100 );
2101 for lang in [
2102 lang!("hi"),
2103 lang!("bn"),
2104 lang!("te"),
2105 lang!("as"),
2106 lang!("gu"),
2107 lang!("kn"),
2108 lang!("mr"),
2109 lang!("ne"),
2110 lang!("or"),
2111 lang!("pa"),
2112 lang!("si"),
2113 ] {
2114 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Mangal", "Segoe Ui Emoji", "sans-serif"].into());
2115 }
2116 system_ui.insert(lang!("am"), ["Segoe UI", "Nyala", "Ebrima", "Segoe Ui Emoji", "sans-serif"].into());
2117 system_ui.insert(
2118 lang!("km"),
2119 ["Segoe UI", "Khmer UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2120 );
2121 system_ui.insert(
2122 lang!("lo"),
2123 ["Segoe UI", "lao UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2124 );
2125 system_ui.insert(lang!("th"), ["Segoe UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into());
2126 for lang in [lang!("ml"), lang!("ta")] {
2127 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Segoe Ui Emoji", "sans-serif"].into());
2128 }
2129 system_ui.insert(lang!("my"), ["Segoe UI", "Myanmar Text", "Segoe Ui Emoji", "sans-serif"].into());
2130
2131 system_ui.insert(lang!(und), ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into());
2132 } else if cfg!(target_os = "macos") {
2133 system_ui.insert(
2134 lang!("zh-Hans"),
2135 ["system-ui", "PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into(),
2136 );
2137 system_ui.insert(
2138 lang!("zh-Hant"),
2139 ["system-ui", "PingFang TC", "Apple Color Emoji", "sans-serif"].into(),
2140 );
2141 system_ui.insert(
2142 lang!("ja"),
2143 [
2144 "system-ui",
2145 "Hiragino Sans",
2146 "Hiragino Kaku Gothic ProN",
2147 "Apple Color Emoji",
2148 "sans-serif",
2149 ]
2150 .into(),
2151 );
2152 system_ui.insert(
2153 lang!("ko"),
2154 ["system-ui", "Apple SD Gothic Neo", "NanumGothic", "Apple Color Emoji", "sans-serif"].into(),
2155 );
2156
2157 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2158 system_ui.insert(
2159 lang,
2160 [
2161 "system-ui",
2162 "Kohinoor Devanagari",
2163 "Devanagari Sangam MN",
2164 "Apple Color Emoji",
2165 "sans-serif",
2166 ]
2167 .into(),
2168 );
2169 }
2170 for lang in [lang!("bn"), lang!("as")] {
2171 system_ui.insert(
2172 lang,
2173 [
2174 "system-ui",
2175 "Kohinoor Bangla",
2176 "Bangla Sangam MN",
2177 "Apple Color Emoji",
2178 "sans-serif",
2179 ]
2180 .into(),
2181 );
2182 }
2183 system_ui.insert(
2184 lang!("te"),
2185 [
2186 "system-ui",
2187 "Kohinoor Telugu",
2188 "Telugu Sangam MN",
2189 "Apple Color Emoji",
2190 "sans-serif",
2191 ]
2192 .into(),
2193 );
2194 system_ui.insert(
2195 lang!("gu"),
2196 [
2197 "system-ui",
2198 "Kohinoor Gujarati",
2199 "Gujarati Sangam MN",
2200 "Apple Color Emoji",
2201 "sans-serif",
2202 ]
2203 .into(),
2204 );
2205 system_ui.insert(
2206 lang!("kn"),
2207 ["system-ui", "Kannada Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2208 );
2209 system_ui.insert(
2210 lang!("or"),
2211 ["system-ui", "Oriya Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2212 );
2213 system_ui.insert(
2214 lang!("pa"),
2215 ["system-ui", "Mukta Mahee", "Gurmukhi Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2216 );
2217 system_ui.insert(
2218 lang!("si"),
2219 ["system-ui", "Sinhala Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2220 );
2221
2222 system_ui.insert(lang!("am"), ["system-ui", "Kefa", "Apple Color Emoji", "sans-serif"].into());
2223 system_ui.insert(
2224 lang!("km"),
2225 ["system-ui", "Khmer Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2226 );
2227 system_ui.insert(
2228 lang!("lo"),
2229 ["system-ui", "Lao Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2230 );
2231 system_ui.insert(
2232 lang!("th"),
2233 ["system-ui", "Thonburi", "Ayuthaya", "Apple Color Emoji", "sans-serif"].into(),
2234 );
2235 system_ui.insert(
2236 lang!("my"),
2237 ["system-ui", "Myanmar Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2238 );
2239
2240 system_ui.insert(
2241 lang!("ml"),
2242 ["system-ui", "Malayalam Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2243 );
2244 system_ui.insert(
2245 lang!("ta"),
2246 ["system-ui", "Kohinoor Tamil", "Tamil Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2247 );
2248
2249 for lang in [lang!("ar"), lang!("fa"), lang!("ps")] {
2250 system_ui.insert(lang, ["system-ui", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into());
2251 }
2252
2253 system_ui.insert(lang!("he"), ["system-ui", "Arial Hebrew", "Apple Color Emoji", "sans-serif"].into());
2254
2255 system_ui.insert(lang!("hy"), ["system-ui", "Mshtakan", "Apple Color Emoji", "sans-serif"].into());
2256
2257 system_ui.insert(
2258 lang!("ka"),
2259 ["system-ui", "Helvetica Neue", "Apple Color Emoji", "sans-serif"].into(),
2260 );
2261
2262 system_ui.insert(
2263 lang!("ur"),
2264 ["system-ui", "SF Arabic", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into(),
2265 );
2266
2267 system_ui.insert(
2268 lang!(und),
2269 ["system-ui", "Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into(),
2270 );
2271 } else if cfg!(target_os = "linux") {
2272 system_ui.insert(
2273 lang!("zh-Hans"),
2274 [
2275 "system-ui",
2276 "Ubuntu",
2277 "Noto Sans CJK SC",
2278 "Source Han Sans SC",
2279 "Noto Color Emoji",
2280 "sans-serif",
2281 ]
2282 .into(),
2283 );
2284 system_ui.insert(
2285 lang!("zh-Hant"),
2286 [
2287 "system-ui",
2288 "Ubuntu",
2289 "Noto Sans CJK TC",
2290 "Source Han Sans TC",
2291 "Noto Color Emoji",
2292 "sans-serif",
2293 ]
2294 .into(),
2295 );
2296 system_ui.insert(
2297 lang!("ja"),
2298 [
2299 "system-ui",
2300 "Ubuntu",
2301 "Noto Sans CJK JP",
2302 "Source Han Sans JP",
2303 "Noto Color Emoji",
2304 "sans-serif",
2305 ]
2306 .into(),
2307 );
2308 system_ui.insert(
2309 lang!("ko"),
2310 [
2311 "system-ui",
2312 "Ubuntu",
2313 "Noto Sans CJK KR",
2314 "Source Han Sans KR",
2315 "UnDotum",
2316 "Noto Color Emoji",
2317 "sans-serif",
2318 ]
2319 .into(),
2320 );
2321
2322 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2323 system_ui.insert(
2324 lang,
2325 [
2326 "system-ui",
2327 "Ubuntu",
2328 "Noto Sans Devanagari",
2329 "Lohit Devanagari",
2330 "Noto Color Emoji",
2331 "sans-serif",
2332 ]
2333 .into(),
2334 );
2335 }
2336 for lang in [lang!("bn"), lang!("as")] {
2337 system_ui.insert(
2338 lang,
2339 [
2340 "system-ui",
2341 "Ubuntu",
2342 "Noto Sans Bengali",
2343 "Lohit Bengali",
2344 "Noto Color Emoji",
2345 "sans-serif",
2346 ]
2347 .into(),
2348 );
2349 }
2350 system_ui.insert(
2351 lang!("te"),
2352 [
2353 "system-ui",
2354 "Ubuntu",
2355 "Noto Sans Telugu",
2356 "Lohit Telugu",
2357 "Noto Color Emoji",
2358 "sans-serif",
2359 ]
2360 .into(),
2361 );
2362 system_ui.insert(
2363 lang!("gu"),
2364 [
2365 "system-ui",
2366 "Ubuntu",
2367 "Noto Sans Gujarati",
2368 "Lohit Gujarati",
2369 "Noto Color Emoji",
2370 "sans-serif",
2371 ]
2372 .into(),
2373 );
2374 system_ui.insert(
2375 lang!("kn"),
2376 [
2377 "system-ui",
2378 "Ubuntu",
2379 "Noto Sans Kannada",
2380 "Lohit Kannada",
2381 "Noto Color Emoji",
2382 "sans-serif",
2383 ]
2384 .into(),
2385 );
2386 system_ui.insert(
2387 lang!("or"),
2388 [
2389 "system-ui",
2390 "Ubuntu",
2391 "Noto Sans Oriya",
2392 "Lohit Odia",
2393 "Noto Color Emoji",
2394 "sans-serif",
2395 ]
2396 .into(),
2397 );
2398 system_ui.insert(
2399 lang!("pa"),
2400 [
2401 "system-ui",
2402 "Ubuntu",
2403 "Noto Sans Gurmukhi",
2404 "Lohit Gurmukhi",
2405 "Noto Color Emoji",
2406 "sans-serif",
2407 ]
2408 .into(),
2409 );
2410 system_ui.insert(
2411 lang!("si"),
2412 [
2413 "system-ui",
2414 "Ubuntu",
2415 "Noto Sans Sinhala",
2416 "LKLUG",
2417 "Noto Color Emoji",
2418 "sans-serif",
2419 ]
2420 .into(),
2421 );
2422
2423 system_ui.insert(
2424 lang!("am"),
2425 [
2426 "system-ui",
2427 "Ubuntu",
2428 "Noto Sans Ethiopic",
2429 "Abyssinica SIL",
2430 "Noto Color Emoji",
2431 "sans-serif",
2432 ]
2433 .into(),
2434 );
2435 system_ui.insert(
2436 lang!("km"),
2437 [
2438 "system-ui",
2439 "Ubuntu",
2440 "Noto Sans Khmer",
2441 "Hanuman",
2442 "Noto Color Emoji",
2443 "sans-serif",
2444 ]
2445 .into(),
2446 );
2447 system_ui.insert(
2448 lang!("lo"),
2449 [
2450 "system-ui",
2451 "Ubuntu",
2452 "Noto Sans Lao",
2453 "Phetsarath OT",
2454 "Noto Color Emoji",
2455 "sans-serif",
2456 ]
2457 .into(),
2458 );
2459 system_ui.insert(
2460 lang!("th"),
2461 [
2462 "system-ui",
2463 "Ubuntu",
2464 "Noto Sans Thai",
2465 "Kinnari",
2466 "Garuda",
2467 "Noto Color Emoji",
2468 "sans-serif",
2469 ]
2470 .into(),
2471 );
2472 system_ui.insert(
2473 lang!("my"),
2474 [
2475 "system-ui",
2476 "Ubuntu",
2477 "Noto Sans Myanmar",
2478 "Padauk",
2479 "Noto Color Emoji",
2480 "sans-serif",
2481 ]
2482 .into(),
2483 );
2484
2485 system_ui.insert(
2486 lang!("ml"),
2487 [
2488 "system-ui",
2489 "Ubuntu",
2490 "Noto Sans Malayalam",
2491 "Lohit Malayalam",
2492 "Noto Color Emoji",
2493 "sans-serif",
2494 ]
2495 .into(),
2496 );
2497 system_ui.insert(
2498 lang!("ta"),
2499 [
2500 "system-ui",
2501 "Ubuntu",
2502 "Noto Sans Tamil",
2503 "Lohit Tamil",
2504 "Noto Color Emoji",
2505 "sans-serif",
2506 ]
2507 .into(),
2508 );
2509
2510 for lang in [lang!("ar"), lang!("fa"), lang!("ps"), lang!("ur")] {
2511 system_ui.insert(lang, ["system-ui", "Noto Sans Arabic", "Noto Color Emoji", "sans-serif"].into());
2512 }
2513
2514 system_ui.insert(
2515 lang!("he"),
2516 ["system-ui", "Noto Sans Hebrew", "Noto Color Emoji", "sans-serif"].into(),
2517 );
2518
2519 system_ui.insert(
2520 lang!("hy"),
2521 ["system-ui", "Noto Sans Armenian", "Noto Color Emoji", "sans-serif"].into(),
2522 );
2523
2524 system_ui.insert(lang!("ka"), ["system-ui", "DejaVu Sans", "Noto Color Emoji", "sans-serif"].into());
2525
2526 system_ui.insert(
2527 lang!("ur"),
2528 [
2529 "system-ui",
2530 "Noto Naskh Arabic",
2531 "Noto Sans Arabic",
2532 "Noto Color Emoji",
2533 "sans-serif",
2534 ]
2535 .into(),
2536 );
2537
2538 system_ui.insert(
2539 lang!(und),
2540 ["system-ui", "Ubuntu", "Droid Sans", "Noto Sans", "Noto Color Emoji", "sans-serif"].into(),
2541 );
2542 } else {
2543 system_ui.insert(lang!(und), ["system-ui", "sans-serif"].into());
2544 }
2545
2546 GenericFontsService {
2547 serif: default(serif),
2548 sans_serif: default(sans_serif),
2549 monospace: default(monospace),
2550 cursive: default(cursive),
2551 fantasy: default(fantasy),
2552
2553 system_ui,
2554
2555 fallback: default(fallback),
2556 }
2557 }
2558}
2559
2560#[non_exhaustive]
2571pub struct GenericFonts {}
2572macro_rules! impl_fallback_accessors {
2573 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2574 #[doc = "Gets the *"$name_str "* font for the given language."]
2575 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2579
2580 pub fn $name(&self, lang: &Lang) -> FontName {
2581 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2582 }
2583
2584 #[doc = "Sets the *"$name_str "* font for the given language."]
2585 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2590 self.[<set_ $name _impl>](lang, font_name.into());
2591 }
2592 fn [<set_ $name _impl>](&self, lang: Lang, font_name: FontName) {
2593 UPDATES.once_update("GenericFonts.set", move || {
2594 GENERIC_FONTS_SV.write().$name.insert(lang.clone(), font_name);
2595 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2596 });
2597 }
2598 })+};
2599}
2600impl GenericFonts {
2601 #[rustfmt::skip] impl_fallback_accessors! {
2603 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2604 }
2605
2606 pub fn system_ui(&self, lang: &Lang) -> FontNames {
2612 GENERIC_FONTS_SV.read().system_ui.get(lang).unwrap().clone()
2613 }
2614
2615 pub fn set_system_ui(&self, lang: Lang, font_names: impl Into<FontNames>) {
2621 self.set_system_ui_impl(lang, font_names.into())
2622 }
2623 fn set_system_ui_impl(&self, lang: Lang, font_names: FontNames) {
2624 UPDATES.once_update("GenericFonts.set_system_ui", move || {
2625 GENERIC_FONTS_SV.write().system_ui.insert(lang.clone(), font_names);
2626 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::system_ui(), lang)));
2627 });
2628 }
2629
2630 pub fn fallback(&self, lang: &Lang) -> FontName {
2634 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2635 }
2636
2637 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2643 self.set_fallback_impl(lang, font_name.into());
2644 }
2645 fn set_fallback_impl(&self, lang: Lang, font_name: FontName) {
2646 UPDATES.once_update("GenericFonts.set", move || {
2647 GENERIC_FONTS_SV.write().fallback.insert(lang.clone(), font_name);
2648 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang)));
2649 });
2650 }
2651
2652 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2660 match &**name {
2661 "serif" => Some(self.serif(lang)),
2662 "sans-serif" => Some(self.sans_serif(lang)),
2663 "monospace" => Some(self.monospace(lang)),
2664 "cursive" => Some(self.cursive(lang)),
2665 "fantasy" => Some(self.fantasy(lang)),
2666 _ => None,
2667 }
2668 }
2669
2670 pub fn resolve_list(&self, names: &[FontName], lang: &Lang) -> Option<FontNames> {
2674 if names
2675 .iter()
2676 .any(|n| ["system-ui", "serif", "sans-serif", "monospace", "cursive", "fantasy"].contains(&&**n))
2677 {
2678 let mut r = FontNames(Vec::with_capacity(names.len()));
2679 for name in names {
2680 match self.resolve(name, lang) {
2681 Some(n) => r.push(n),
2682 None => {
2683 if name == "system-ui" {
2684 r.extend(self.system_ui(lang));
2685 } else {
2686 r.push(name.clone())
2687 }
2688 }
2689 }
2690 }
2691 Some(r)
2692 } else {
2693 None
2694 }
2695 }
2696}
2697
2698#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2699pub(crate) enum WeakFontBytes {
2700 Ipc(WeakIpcBytes),
2701 Arc(std::sync::Weak<Vec<u8>>),
2702 Static(&'static [u8]),
2703 Mmap(std::sync::Weak<SystemFontBytes>),
2704}
2705#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2706impl WeakFontBytes {
2707 pub(crate) fn upgrade(&self) -> Option<FontBytes> {
2708 match self {
2709 WeakFontBytes::Ipc(weak) => Some(FontBytes(FontBytesImpl::Ipc(weak.upgrade()?))),
2710 WeakFontBytes::Arc(weak) => Some(FontBytes(FontBytesImpl::Arc(weak.upgrade()?))),
2711 WeakFontBytes::Static(b) => Some(FontBytes(FontBytesImpl::Static(b))),
2712 WeakFontBytes::Mmap(weak) => Some(FontBytes(FontBytesImpl::System(weak.upgrade()?))),
2713 }
2714 }
2715
2716 pub(crate) fn strong_count(&self) -> usize {
2717 match self {
2718 WeakFontBytes::Ipc(weak) => weak.strong_count(),
2719 WeakFontBytes::Arc(weak) => weak.strong_count(),
2720 WeakFontBytes::Static(_) => 1,
2721 WeakFontBytes::Mmap(weak) => weak.strong_count(),
2722 }
2723 }
2724}
2725
2726struct SystemFontBytes {
2727 path: std::path::PathBuf,
2728 mmap: IpcBytes,
2729}
2730
2731#[derive(Clone)]
2732enum FontBytesImpl {
2733 Ipc(IpcBytes),
2735 Arc(Arc<Vec<u8>>),
2736 Static(&'static [u8]),
2737 System(Arc<SystemFontBytes>),
2738}
2739#[derive(Clone)]
2741pub struct FontBytes(FontBytesImpl);
2742impl FontBytes {
2743 pub fn from_ipc(bytes: IpcBytes) -> Self {
2745 Self(FontBytesImpl::Ipc(bytes))
2746 }
2747
2748 pub fn from_vec(bytes: Vec<u8>) -> io::Result<Self> {
2750 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_vec_blocking(bytes)?)))
2751 }
2752
2753 pub fn from_static(bytes: &'static [u8]) -> Self {
2755 Self(FontBytesImpl::Static(bytes))
2756 }
2757
2758 pub fn from_arc(bytes: Arc<Vec<u8>>) -> Self {
2762 Self(FontBytesImpl::Arc(bytes))
2763 }
2764
2765 pub fn from_file(path: PathBuf) -> io::Result<Self> {
2767 let path = dunce::canonicalize(path)?;
2768
2769 #[cfg(windows)]
2770 {
2771 use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemWindowsDirectoryW};
2772 let mut buffer = [0u16; MAX_PATH as usize];
2773 let len = unsafe { GetSystemWindowsDirectoryW(Some(&mut buffer)) };
2775 let fonts_dir = String::from_utf16_lossy(&buffer[..len as usize]);
2776 if path.starts_with(fonts_dir) {
2778 return unsafe { load_from_system(path) };
2780 }
2781 }
2782 #[cfg(target_os = "macos")]
2783 if path.starts_with("/System/Library/Fonts/") || path.starts_with("/Library/Fonts/") {
2784 return unsafe { load_from_system(path) };
2786 }
2787 #[cfg(target_os = "android")]
2788 if path.starts_with("/system/fonts/") || path.starts_with("/system/font/") || path.starts_with("/system/product/fonts/") {
2789 return unsafe { load_from_system(path) };
2791 }
2792 #[cfg(unix)]
2793 if path.starts_with("/usr/share/fonts/") {
2794 return unsafe { load_from_system(path) };
2796 }
2797
2798 #[cfg(ipc)]
2799 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2800 let mmap = unsafe { IpcBytes::open_memmap_blocking(path.clone(), None) }?;
2802 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2803 }
2804
2805 #[cfg(all(not(ipc), not(target_arch = "wasm32")))]
2806 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2807 let mmap = IpcBytes::from_path_blocking(&path)?;
2808 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2809 }
2810
2811 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_path_blocking(&path)?)))
2812 }
2813
2814 #[cfg(ipc)]
2821 pub unsafe fn from_file_mmap(path: PathBuf) -> std::io::Result<Self> {
2822 let ipc = unsafe { IpcBytes::open_memmap_blocking(path, None) }?;
2824 Ok(Self(FontBytesImpl::Ipc(ipc)))
2825 }
2826
2827 #[cfg(ipc)]
2831 pub fn mmap_path(&self) -> Option<&std::path::Path> {
2832 if let FontBytesImpl::System(m) = &self.0 {
2833 Some(&m.path)
2834 } else {
2835 None
2836 }
2837 }
2838
2839 pub fn to_ipc(&self) -> io::Result<IpcFontBytes> {
2841 Ok(if let FontBytesImpl::System(m) = &self.0 {
2842 IpcFontBytes::System(m.path.clone())
2843 } else {
2844 IpcFontBytes::Bytes(self.to_ipc_bytes()?)
2845 })
2846 }
2847
2848 pub fn to_ipc_bytes(&self) -> io::Result<IpcBytes> {
2850 match &self.0 {
2851 FontBytesImpl::Ipc(b) => Ok(b.clone()),
2852 FontBytesImpl::Arc(b) => IpcBytes::from_slice_blocking(b),
2853 FontBytesImpl::Static(b) => IpcBytes::from_slice_blocking(b),
2854 FontBytesImpl::System(m) => IpcBytes::from_slice_blocking(&m.mmap[..]),
2855 }
2856 }
2857
2858 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2859 pub(crate) fn downgrade(&self) -> WeakFontBytes {
2860 match &self.0 {
2861 FontBytesImpl::Ipc(ipc) => WeakFontBytes::Ipc(ipc.downgrade()),
2862 FontBytesImpl::Arc(arc) => WeakFontBytes::Arc(Arc::downgrade(arc)),
2863 FontBytesImpl::Static(b) => WeakFontBytes::Static(b),
2864 FontBytesImpl::System(arc) => WeakFontBytes::Mmap(Arc::downgrade(arc)),
2865 }
2866 }
2867}
2868impl std::ops::Deref for FontBytes {
2869 type Target = [u8];
2870
2871 fn deref(&self) -> &Self::Target {
2872 match &self.0 {
2873 FontBytesImpl::Ipc(b) => &b[..],
2874 FontBytesImpl::Arc(b) => &b[..],
2875 FontBytesImpl::Static(b) => b,
2876 FontBytesImpl::System(m) => &m.mmap[..],
2877 }
2878 }
2879}
2880impl fmt::Debug for FontBytes {
2881 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2882 let mut b = f.debug_struct("FontBytes");
2883 b.field(
2884 ".kind",
2885 &match &self.0 {
2886 FontBytesImpl::Ipc(_) => "IpcBytes",
2887 FontBytesImpl::Arc(_) => "Arc",
2888 FontBytesImpl::Static(_) => "Static",
2889 FontBytesImpl::System(_) => "Mmap",
2890 },
2891 );
2892 b.field(".len", &(self.len() as u64).bytes());
2893 if let FontBytesImpl::System(m) = &self.0 {
2894 b.field(".path", &m.path);
2895 }
2896
2897 b.finish()
2898 }
2899}
2900
2901#[derive(Debug, Clone)]
2902enum FontSource {
2903 File(PathBuf, u32),
2904 Memory(FontBytes, u32),
2905 Alias(FontName),
2906}
2907
2908#[derive(Debug, Clone)]
2910pub struct CustomFont {
2911 name: FontName,
2912 source: FontSource,
2913 stretch: FontStretch,
2914 style: FontStyle,
2915 weight: FontWeight,
2916}
2917impl CustomFont {
2918 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2926 CustomFont {
2927 name: name.into(),
2928 source: FontSource::File(path.into(), font_index),
2929 stretch: FontStretch::NORMAL,
2930 style: FontStyle::Normal,
2931 weight: FontWeight::NORMAL,
2932 }
2933 }
2934
2935 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontBytes, font_index: u32) -> Self {
2943 CustomFont {
2944 name: name.into(),
2945 source: FontSource::Memory(data, font_index),
2946 stretch: FontStretch::NORMAL,
2947 style: FontStyle::Normal,
2948 weight: FontWeight::NORMAL,
2949 }
2950 }
2951
2952 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
2958 CustomFont {
2959 name: name.into(),
2960 source: FontSource::Alias(other_font.into()),
2961 stretch: FontStretch::NORMAL,
2962 style: FontStyle::Normal,
2963 weight: FontWeight::NORMAL,
2964 }
2965 }
2966
2967 pub fn stretch(mut self, stretch: FontStretch) -> Self {
2971 self.stretch = stretch;
2972 self
2973 }
2974
2975 pub fn style(mut self, style: FontStyle) -> Self {
2979 self.style = style;
2980 self
2981 }
2982
2983 pub fn weight(mut self, weight: FontWeight) -> Self {
2987 self.weight = weight;
2988 self
2989 }
2990}
2991
2992#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
2996#[serde(transparent)]
2997pub struct FontStretch(pub f32);
2998impl fmt::Debug for FontStretch {
2999 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3000 let name = self.name();
3001 if name.is_empty() {
3002 f.debug_tuple("FontStretch").field(&self.0).finish()
3003 } else {
3004 if f.alternate() {
3005 write!(f, "FontStretch::")?;
3006 }
3007 write!(f, "{name}")
3008 }
3009 }
3010}
3011impl PartialOrd for FontStretch {
3012 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3013 Some(self.cmp(other))
3014 }
3015}
3016impl Ord for FontStretch {
3017 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3018 about_eq_ord(self.0, other.0, EQ_GRANULARITY)
3019 }
3020}
3021impl PartialEq for FontStretch {
3022 fn eq(&self, other: &Self) -> bool {
3023 about_eq(self.0, other.0, EQ_GRANULARITY)
3024 }
3025}
3026impl Eq for FontStretch {}
3027impl std::hash::Hash for FontStretch {
3028 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3029 about_eq_hash(self.0, EQ_GRANULARITY, state)
3030 }
3031}
3032impl Default for FontStretch {
3033 fn default() -> FontStretch {
3034 FontStretch::NORMAL
3035 }
3036}
3037impl FontStretch {
3038 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
3040 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
3042 pub const CONDENSED: FontStretch = FontStretch(0.75);
3044 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
3046 pub const NORMAL: FontStretch = FontStretch(1.0);
3048 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
3050 pub const EXPANDED: FontStretch = FontStretch(1.25);
3052 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
3054 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
3056
3057 pub fn name(self) -> &'static str {
3059 macro_rules! name {
3060 ($($CONST:ident;)+) => {$(
3061 if self == Self::$CONST {
3062 return stringify!($CONST);
3063 }
3064 )+}
3065 }
3066 name! {
3067 ULTRA_CONDENSED;
3068 EXTRA_CONDENSED;
3069 CONDENSED;
3070 SEMI_CONDENSED;
3071 NORMAL;
3072 SEMI_EXPANDED;
3073 EXPANDED;
3074 EXTRA_EXPANDED;
3075 ULTRA_EXPANDED;
3076 }
3077 ""
3078 }
3079}
3080impl_from_and_into_var! {
3081 fn from(fct: Factor) -> FontStretch {
3082 FontStretch(fct.0)
3083 }
3084 fn from(pct: FactorPercent) -> FontStretch {
3085 FontStretch(pct.fct().0)
3086 }
3087 fn from(fct: f32) -> FontStretch {
3088 FontStretch(fct)
3089 }
3090}
3091impl From<skrifa::attribute::Stretch> for FontStretch {
3092 fn from(value: skrifa::attribute::Stretch) -> Self {
3093 FontStretch(value.ratio())
3094 }
3095}
3096impl From<FontStretch> for skrifa::attribute::Stretch {
3097 fn from(value: FontStretch) -> Self {
3098 skrifa::attribute::Stretch::new(value.0)
3099 }
3100}
3101
3102#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
3104pub enum FontStyle {
3105 #[default]
3107 Normal,
3108 Italic,
3110 Oblique,
3112}
3113impl fmt::Debug for FontStyle {
3114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3115 if f.alternate() {
3116 write!(f, "FontStyle::")?;
3117 }
3118 match self {
3119 Self::Normal => write!(f, "Normal"),
3120 Self::Italic => write!(f, "Italic"),
3121 Self::Oblique => write!(f, "Oblique"),
3122 }
3123 }
3124}
3125impl From<skrifa::attribute::Style> for FontStyle {
3126 fn from(value: skrifa::attribute::Style) -> Self {
3127 use skrifa::attribute::Style::*;
3128 match value {
3129 Normal => FontStyle::Normal,
3130 Italic => FontStyle::Italic,
3131 Oblique(_) => FontStyle::Oblique,
3132 }
3133 }
3134}
3135
3136impl From<FontStyle> for skrifa::attribute::Style {
3137 fn from(value: FontStyle) -> Self {
3138 match value {
3139 FontStyle::Normal => Self::Normal,
3140 FontStyle::Italic => Self::Italic,
3141 FontStyle::Oblique => Self::Oblique(None),
3142 }
3143 }
3144}
3145
3146#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
3149pub struct FontWeight(pub f32);
3150impl Default for FontWeight {
3151 fn default() -> FontWeight {
3152 FontWeight::NORMAL
3153 }
3154}
3155impl fmt::Debug for FontWeight {
3156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3157 let name = self.name();
3158 if name.is_empty() {
3159 f.debug_tuple("FontWeight").field(&self.0).finish()
3160 } else {
3161 if f.alternate() {
3162 write!(f, "FontWeight::")?;
3163 }
3164 write!(f, "{name}")
3165 }
3166 }
3167}
3168impl PartialOrd for FontWeight {
3169 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3170 Some(self.cmp(other))
3171 }
3172}
3173impl Ord for FontWeight {
3174 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3175 about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
3176 }
3177}
3178impl PartialEq for FontWeight {
3179 fn eq(&self, other: &Self) -> bool {
3180 about_eq(self.0, other.0, EQ_GRANULARITY_100)
3181 }
3182}
3183impl Eq for FontWeight {}
3184impl std::hash::Hash for FontWeight {
3185 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3186 about_eq_hash(self.0, EQ_GRANULARITY_100, state)
3187 }
3188}
3189impl FontWeight {
3190 pub const THIN: FontWeight = FontWeight(100.0);
3192 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
3194 pub const LIGHT: FontWeight = FontWeight(300.0);
3196 pub const NORMAL: FontWeight = FontWeight(400.0);
3198 pub const MEDIUM: FontWeight = FontWeight(500.0);
3200 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
3202 pub const BOLD: FontWeight = FontWeight(700.0);
3204 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
3206 pub const BLACK: FontWeight = FontWeight(900.0);
3208
3209 pub fn name(self) -> &'static str {
3211 macro_rules! name {
3212 ($($CONST:ident;)+) => {$(
3213 if self == Self::$CONST {
3214 return stringify!($CONST);
3215 }
3216 )+}
3217 }
3218 name! {
3219 THIN;
3220 EXTRA_LIGHT;
3221 LIGHT;
3222 NORMAL;
3223 MEDIUM;
3224 SEMIBOLD;
3225 BOLD;
3226 EXTRA_BOLD;
3227 BLACK;
3228 }
3229 ""
3230 }
3231}
3232impl_from_and_into_var! {
3233 fn from(weight: u32) -> FontWeight {
3234 FontWeight(weight as f32)
3235 }
3236 fn from(weight: f32) -> FontWeight {
3237 FontWeight(weight)
3238 }
3239}
3240impl From<skrifa::attribute::Weight> for FontWeight {
3241 fn from(value: skrifa::attribute::Weight) -> Self {
3242 FontWeight(value.value())
3243 }
3244}
3245impl From<FontWeight> for skrifa::attribute::Weight {
3246 fn from(value: FontWeight) -> Self {
3247 skrifa::attribute::Weight::new(value.0)
3248 }
3249}
3250
3251#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3253pub enum LineBreak {
3254 Auto,
3256 Loose,
3258 Normal,
3260 Strict,
3262 Anywhere,
3264}
3265impl Default for LineBreak {
3266 fn default() -> Self {
3268 LineBreak::Auto
3269 }
3270}
3271impl fmt::Debug for LineBreak {
3272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3273 if f.alternate() {
3274 write!(f, "LineBreak::")?;
3275 }
3276 match self {
3277 LineBreak::Auto => write!(f, "Auto"),
3278 LineBreak::Loose => write!(f, "Loose"),
3279 LineBreak::Normal => write!(f, "Normal"),
3280 LineBreak::Strict => write!(f, "Strict"),
3281 LineBreak::Anywhere => write!(f, "Anywhere"),
3282 }
3283 }
3284}
3285
3286#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3291#[non_exhaustive]
3292pub enum ParagraphBreak {
3293 #[default]
3295 None,
3296 Line,
3298}
3299impl fmt::Debug for ParagraphBreak {
3300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3301 if f.alternate() {
3302 write!(f, "ParagraphBreak::")?;
3303 }
3304 match self {
3305 ParagraphBreak::None => write!(f, "None"),
3306 ParagraphBreak::Line => write!(f, "Line"),
3307 }
3308 }
3309}
3310
3311#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3313pub enum Hyphens {
3314 None,
3316 Manual,
3321 Auto,
3323}
3324impl Default for Hyphens {
3325 fn default() -> Self {
3327 Hyphens::Auto
3328 }
3329}
3330impl fmt::Debug for Hyphens {
3331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3332 if f.alternate() {
3333 write!(f, "Hyphens::")?;
3334 }
3335 match self {
3336 Hyphens::None => write!(f, "None"),
3337 Hyphens::Manual => write!(f, "Manual"),
3338 Hyphens::Auto => write!(f, "Auto"),
3339 }
3340 }
3341}
3342
3343#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3349pub enum WordBreak {
3350 Normal,
3352 BreakAll,
3354 KeepAll,
3356}
3357impl Default for WordBreak {
3358 fn default() -> Self {
3360 WordBreak::Normal
3361 }
3362}
3363impl fmt::Debug for WordBreak {
3364 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3365 if f.alternate() {
3366 write!(f, "WordBreak::")?;
3367 }
3368 match self {
3369 WordBreak::Normal => write!(f, "Normal"),
3370 WordBreak::BreakAll => write!(f, "BreakAll"),
3371 WordBreak::KeepAll => write!(f, "KeepAll"),
3372 }
3373 }
3374}
3375
3376#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3378pub enum Justify {
3379 Auto,
3383 InterWord,
3385 InterLetter,
3387}
3388impl Default for Justify {
3389 fn default() -> Self {
3391 Justify::Auto
3392 }
3393}
3394impl Justify {
3395 pub fn resolve(self, lang: &Lang) -> Self {
3397 match self {
3398 Self::Auto => match lang.language.as_str() {
3399 "zh" | "ja" | "ko" => Self::InterLetter,
3400 _ => Self::InterWord,
3401 },
3402 m => m,
3403 }
3404 }
3405}
3406impl fmt::Debug for Justify {
3407 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3408 if f.alternate() {
3409 write!(f, "Justify::")?;
3410 }
3411 match self {
3412 Justify::Auto => write!(f, "Auto"),
3413 Justify::InterWord => write!(f, "InterWord"),
3414 Justify::InterLetter => write!(f, "InterLetter"),
3415 }
3416 }
3417}
3418
3419#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3421#[non_exhaustive]
3422pub struct FontMetrics {
3423 pub ascent: Px,
3425
3426 pub descent: Px,
3432
3433 pub line_gap: Px,
3435
3436 pub underline_position: Px,
3439
3440 pub underline_thickness: Px,
3442
3443 pub cap_height: Px,
3445
3446 pub x_height: Px,
3448
3449 pub bounds: PxRect,
3453
3454 units_per_em: u16,
3455}
3456impl FontMetrics {
3457 pub fn line_height(&self) -> Px {
3459 self.ascent - self.descent + self.line_gap
3460 }
3461
3462 fn new(f: &skrifa::FontRef, size: Px) -> Self {
3463 let m = f.metrics(skrifa::instance::Size::new(size.0 as f32), skrifa::instance::LocationRef::default());
3464 let u = m.underline.unwrap_or_default();
3465 let b = m.bounds.unwrap_or_default();
3466 let scale = size.0 as f32 / m.units_per_em as f32;
3467 let line_gap = self::line_gap(f) as f32 * scale;
3468 fn f32_to_px(v: f32) -> Px {
3469 euclid::point2::<f32, ()>(v, v).cast().x
3471 }
3472 Self {
3473 ascent: f32_to_px(m.ascent),
3474 descent: f32_to_px(m.descent),
3475 line_gap: f32_to_px(line_gap),
3476 underline_position: f32_to_px(u.offset),
3477 underline_thickness: f32_to_px(u.thickness),
3478 cap_height: f32_to_px(m.cap_height.unwrap_or(0.0)),
3479 x_height: f32_to_px(m.x_height.unwrap_or(0.0)),
3480 bounds: euclid::rect(b.x_min, b.y_min, b.x_max - b.x_min, b.y_max - b.y_min).cast(),
3481
3482 units_per_em: m.units_per_em,
3483 }
3484 }
3485
3486 fn empty() -> Self {
3487 Self {
3488 ascent: Px(0),
3489 descent: Px(0),
3490 line_gap: Px(0),
3491 underline_position: Px(0),
3492 underline_thickness: Px(0),
3493 cap_height: Px(0),
3494 x_height: Px(0),
3495 bounds: euclid::Rect::zero(),
3496 units_per_em: 0,
3497 }
3498 }
3499}
3500fn line_gap(f: &skrifa::FontRef) -> i16 {
3501 use read_fonts::TableProvider as _;
3503 if let Ok(os2) = f.os2() {
3504 let use_typographic_metrics = os2.version() >= 4
3505 && os2
3506 .fs_selection()
3507 .contains(read_fonts::tables::os2::SelectionFlags::USE_TYPO_METRICS);
3508 if use_typographic_metrics {
3509 return os2.s_typo_line_gap();
3510 }
3511 }
3512 if let Ok(hrea) = f.hhea() {
3513 if hrea.ascender().to_i16() == 0
3514 && hrea.descender().to_i16() == 0
3515 && let Ok(os2) = f.os2()
3516 {
3517 return if os2.s_typo_ascender() != 0 || os2.s_typo_descender() != 0 {
3518 return os2.s_typo_line_gap();
3519 } else {
3520 0
3521 };
3522 }
3523 return hrea.line_gap().to_i16();
3524 }
3525 0
3526}
3527
3528#[derive(Clone)]
3530pub enum TextTransformFn {
3531 None,
3533 Uppercase,
3535 Lowercase,
3537 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3539}
3540impl TextTransformFn {
3541 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3545 match self {
3546 TextTransformFn::None => Cow::Borrowed(text),
3547 TextTransformFn::Uppercase => {
3548 if text.chars().any(|c| !c.is_uppercase()) {
3549 Cow::Owned(text.to_uppercase().into())
3550 } else {
3551 Cow::Borrowed(text)
3552 }
3553 }
3554 TextTransformFn::Lowercase => {
3555 if text.chars().any(|c| !c.is_lowercase()) {
3556 Cow::Owned(text.to_lowercase().into())
3557 } else {
3558 Cow::Borrowed(text)
3559 }
3560 }
3561 TextTransformFn::Custom(fn_) => fn_(text),
3562 }
3563 }
3564
3565 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3567 TextTransformFn::Custom(Arc::new(fn_))
3568 }
3569}
3570impl fmt::Debug for TextTransformFn {
3571 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3572 if f.alternate() {
3573 write!(f, "TextTransformFn::")?;
3574 }
3575 match self {
3576 TextTransformFn::None => write!(f, "None"),
3577 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3578 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3579 TextTransformFn::Custom(_) => write!(f, "Custom"),
3580 }
3581 }
3582}
3583impl PartialEq for TextTransformFn {
3584 fn eq(&self, other: &Self) -> bool {
3585 match (self, other) {
3586 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3587 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3588 }
3589 }
3590}
3591
3592#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3594pub enum WhiteSpace {
3595 #[default]
3597 Preserve,
3598 Merge,
3601 MergeParagraph,
3604 MergeAll,
3606}
3607impl WhiteSpace {
3608 pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3612 match self {
3613 WhiteSpace::Preserve => Cow::Borrowed(text),
3614 WhiteSpace::Merge => {
3615 let mut prev_i = 0;
3617 for line in text.split_inclusive('\n') {
3618 let line_exclusive = line.trim_end_matches('\n').trim_end_matches('\r');
3620 let line_trim = line_exclusive.trim();
3621 let mut merge = line_trim.len() != line_exclusive.len() || line_trim.is_empty();
3622
3623 if !merge {
3625 let mut prev_is_space = true; for c in line.chars() {
3627 let is_space = c.is_whitespace();
3628 if prev_is_space && is_space {
3629 merge = true;
3630 break;
3631 }
3632 prev_is_space = is_space;
3633 }
3634 }
3635
3636 if !merge {
3637 prev_i += line.len();
3638 continue;
3639 }
3640
3641 let mut out = String::with_capacity(text.len() - 1);
3643 out.push_str(&text[..prev_i]);
3644
3645 let mut chars = text[prev_i..].chars();
3646 let mut prev_is_space = true;
3647 let mut prev_is_break = true;
3648 while let Some(c) = chars.next() {
3649 if c == '\r'
3650 && let Some(nc) = chars.next()
3651 {
3652 if nc == '\n' {
3653 if !prev_is_break && !out.is_empty() {
3654 out.push('\n');
3655 }
3656 prev_is_break = true;
3657 prev_is_space = true;
3658 } else {
3659 out.push(c);
3660 out.push(nc);
3661 prev_is_break = false;
3662 prev_is_space = nc.is_whitespace();
3663 }
3664 } else if c == '\n' {
3665 if !prev_is_break && !out.is_empty() {
3666 out.push('\n');
3667 }
3668 prev_is_break = true;
3669 prev_is_space = true;
3670 } else if c.is_whitespace() {
3671 if prev_is_space {
3672 continue;
3673 }
3674 out.push(' ');
3675 prev_is_space = true;
3676 } else {
3677 out.push(c);
3678 prev_is_space = false;
3679 prev_is_break = false;
3680 }
3681 }
3682
3683 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3685 out.truncate(i + c.len_utf8());
3686 }
3687
3688 return Cow::Owned(out.into());
3689 }
3690 Cow::Borrowed(text)
3691 }
3692 WhiteSpace::MergeParagraph => {
3693 let mut merge = text.contains('\n') || text.chars().last().unwrap_or('\0').is_whitespace();
3696 if !merge {
3697 let mut prev_is_space = true;
3698 for c in text.chars() {
3699 let is_space = c.is_whitespace();
3700 if prev_is_space && is_space {
3701 merge = true;
3702 break;
3703 }
3704 prev_is_space = is_space;
3705 }
3706 }
3707
3708 if merge {
3709 let mut out = String::with_capacity(text.len());
3710 let mut prev_is_break = false;
3711 for line in text.lines() {
3712 let line = line.trim();
3713 let is_break = line.is_empty();
3714 if !prev_is_break && is_break && !out.is_empty() {
3715 out.push('\n');
3716 }
3717 if !prev_is_break && !is_break && !out.is_empty() {
3718 out.push(' ');
3719 }
3720 prev_is_break = is_break;
3721
3722 let mut prev_is_space = false;
3723 for c in line.chars() {
3724 let is_space = c.is_whitespace();
3725 if is_space {
3726 if !prev_is_space {
3727 out.push(' ');
3728 }
3729 } else {
3730 out.push(c);
3731 }
3732 prev_is_space = is_space;
3733 }
3734 }
3735
3736 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3738 out.truncate(i + c.len_utf8());
3739 }
3740
3741 return Cow::Owned(out.into());
3742 }
3743 Cow::Borrowed(text)
3744 }
3745 WhiteSpace::MergeAll => {
3746 let mut prev_i = 0;
3748 let mut prev_is_space = true; for (i, c) in text.char_indices() {
3750 let is_space = c.is_whitespace();
3751 if prev_is_space && is_space || c == '\n' {
3752 if !prev_is_space {
3753 debug_assert_eq!(c, '\n');
3754 prev_i += c.len_utf8();
3755 prev_is_space = true;
3756 }
3757 let mut out = String::with_capacity(text.len() - 1);
3759 out.push_str(&text[..prev_i]);
3761 if !out.is_empty() {
3762 out.push(' ');
3763 }
3764 for c in text[(i + c.len_utf8())..].chars() {
3766 let is_space = c.is_whitespace();
3767 if prev_is_space && is_space {
3768 continue;
3769 }
3770 out.push(if is_space { ' ' } else { c });
3771 prev_is_space = is_space;
3772 }
3773
3774 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3776 out.truncate(i + c.len_utf8());
3777 }
3778
3779 return Cow::Owned(out.into());
3780 }
3781 prev_i = i;
3782 prev_is_space = is_space;
3783 }
3784
3785 let out = text.trim_end();
3789 if out.len() != text.len() {
3790 return Cow::Owned(Txt::from_str(out));
3791 }
3792
3793 Cow::Borrowed(text)
3794 }
3795 }
3796 }
3797}
3798impl fmt::Debug for WhiteSpace {
3799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3800 if f.alternate() {
3801 write!(f, "WhiteSpace::")?;
3802 }
3803 match self {
3804 WhiteSpace::Preserve => write!(f, "Preserve"),
3805 WhiteSpace::Merge => write!(f, "Merge"),
3806 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3807 WhiteSpace::MergeParagraph => write!(f, "MergeParagraph"),
3808 }
3809 }
3810}
3811
3812#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3814pub struct CaretIndex {
3815 pub index: usize,
3819 pub line: usize,
3829}
3830
3831impl PartialEq for CaretIndex {
3832 fn eq(&self, other: &Self) -> bool {
3833 self.index == other.index
3834 }
3835}
3836impl Eq for CaretIndex {}
3837impl CaretIndex {
3838 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3840}
3841impl PartialOrd for CaretIndex {
3842 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3843 Some(self.cmp(other))
3844 }
3845}
3846impl Ord for CaretIndex {
3847 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3848 self.index.cmp(&other.index)
3849 }
3850}
3851impl_from_and_into_var! {
3852 fn from(index: usize) -> CaretIndex {
3853 CaretIndex { index, line: 0 }
3854 }
3855}
3856
3857#[derive(Debug, Clone)]
3859#[non_exhaustive]
3860pub enum FontLoadingError {
3861 UnknownFormat,
3863 NoSuchFontInCollection,
3868 Parse(read_fonts::ReadError),
3870 NoFilesystem,
3873 Io(Arc<std::io::Error>),
3875}
3876impl PartialEq for FontLoadingError {
3877 fn eq(&self, other: &Self) -> bool {
3878 match (self, other) {
3879 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3880 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3881 }
3882 }
3883}
3884impl From<std::io::Error> for FontLoadingError {
3885 fn from(error: std::io::Error) -> FontLoadingError {
3886 Self::Io(Arc::new(error))
3887 }
3888}
3889impl fmt::Display for FontLoadingError {
3890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3891 match self {
3892 Self::UnknownFormat => write!(f, "unknown format"),
3893 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
3894 Self::NoFilesystem => write!(f, "no filesystem present"),
3895 Self::Parse(e) => fmt::Display::fmt(e, f),
3896 Self::Io(e) => fmt::Display::fmt(e, f),
3897 }
3898 }
3899}
3900impl std::error::Error for FontLoadingError {
3901 fn cause(&self) -> Option<&dyn std::error::Error> {
3902 match self {
3903 FontLoadingError::Parse(e) => Some(e),
3904 FontLoadingError::Io(e) => Some(e),
3905 _ => None,
3906 }
3907 }
3908}
3909
3910#[cfg(test)]
3911mod tests {
3912 use zng_app::APP;
3913
3914 use super::*;
3915
3916 #[test]
3917 fn generic_fonts_default() {
3918 let _app = APP.minimal().run_headless(false);
3919
3920 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
3921 }
3922
3923 #[test]
3924 fn generic_fonts_fallback() {
3925 let _app = APP.minimal().run_headless(false);
3926
3927 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
3928 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
3929 }
3930
3931 #[test]
3932 fn generic_fonts_get1() {
3933 let mut app = APP.minimal().run_headless(false);
3934 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
3935 app.update(false).assert_wait();
3936
3937 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3938 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3939 }
3940
3941 #[test]
3942 fn generic_fonts_get2() {
3943 let mut app = APP.minimal().run_headless(false);
3944 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3945 app.update(false).assert_wait();
3946
3947 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
3948 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3949 }
3950
3951 #[test]
3952 fn generic_fonts_get_best() {
3953 let mut app = APP.minimal().run_headless(false);
3954 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
3955 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
3956 app.update(false).assert_wait();
3957
3958 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
3959 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
3960 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
3961 }
3962
3963 #[test]
3964 fn generic_fonts_get_no_lang_match() {
3965 let mut app = APP.minimal().run_headless(false);
3966 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
3967 app.update(false).assert_wait();
3968
3969 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
3970 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
3971 }
3972
3973 #[test]
3974 fn white_space_merge() {
3975 macro_rules! test {
3976 ($input:tt, $output:tt) => {
3977 let input = Txt::from($input);
3978 let output = WhiteSpace::Merge.transform(&input);
3979 assert_eq!($output, output.as_str());
3980
3981 let input = input.replace('\n', "\r\n");
3982 let output = WhiteSpace::Merge.transform(&Txt::from(input)).replace("\r\n", "\n");
3983 assert_eq!($output, output.as_str());
3984 };
3985 }
3986 test!("a b\n\nc", "a b\nc");
3987 test!("a b\nc", "a b\nc");
3988 test!(" a b\nc\n \n", "a b\nc");
3989 test!(" \n a b\nc", "a b\nc");
3990 test!("a\n \nb", "a\nb");
3991 }
3992
3993 #[test]
3994 fn white_space_merge_paragraph() {
3995 macro_rules! test {
3996 ($input:tt, $output:tt) => {
3997 let input = Txt::from($input);
3998 let output = WhiteSpace::MergeParagraph.transform(&input);
3999 assert_eq!($output, output.as_str());
4000
4001 let input = input.replace('\n', "\r\n");
4002 let output = WhiteSpace::MergeParagraph.transform(&Txt::from(input)).replace("\r\n", "\n");
4003 assert_eq!($output, output.as_str());
4004 };
4005 }
4006 test!("a b\n\nc", "a b\nc");
4007 test!("a b\nc", "a b c");
4008 test!(" a b\nc\n \n", "a b c");
4009 test!(" \n a b\nc", "a b c");
4010 test!("a\n \nb", "a\nb");
4011 }
4012
4013 #[test]
4014 fn white_space_merge_all() {
4015 macro_rules! test {
4016 ($input:tt, $output:tt) => {
4017 let input = Txt::from($input);
4018 let output = WhiteSpace::MergeAll.transform(&input);
4019 assert_eq!($output, output.as_str());
4020 };
4021 }
4022 test!("a b\n\nc", "a b c");
4023 test!("a b\nc", "a b c");
4024 test!(" a b\nc\n \n", "a b c");
4025 test!(" \n a b\nc", "a b c");
4026 test!("a\n \nb", "a b");
4027 }
4028}