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 std::{borrow::Cow, fmt, io, ops, path::PathBuf, slice::SliceIndex, sync::Arc};
31#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
32use zng_task::channel::WeakIpcBytes;
33
34#[macro_use]
35extern crate bitflags;
36
37pub mod font_features;
38
39mod query_util;
40
41mod emoji_util;
42pub use emoji_util::*;
43
44mod ligature_util;
45use ligature_util::*;
46
47mod unicode_bidi_util;
48
49mod segmenting;
50pub use segmenting::*;
51
52mod shaping;
53pub use shaping::*;
54use zng_clone_move::{async_clmv, clmv};
55
56mod hyphenation;
57pub use self::hyphenation::*;
58
59mod unit;
60pub use unit::*;
61
62use pastey::paste;
63use zng_app::{
64 event::{event, event_args},
65 render::FontSynthesis,
66 update::UPDATES,
67 view_process::{
68 VIEW_PROCESS_INITED_EVENT, ViewRenderer,
69 raw_events::{RAW_FONT_AA_CHANGED_EVENT, RAW_FONT_CHANGED_EVENT},
70 },
71};
72use zng_app_context::app_local;
73use zng_ext_l10n::{Lang, LangMap, lang};
74use zng_layout::unit::{
75 ByteUnits as _, EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorPercent, Px, PxPoint, PxRect, PxSize, TimeUnits as _, about_eq,
76 about_eq_hash, about_eq_ord, euclid,
77};
78use zng_task::parking_lot::{Mutex, RwLock};
79use zng_task::{self as task, channel::IpcBytes};
80use zng_txt::Txt;
81use zng_var::{IntoVar, ResponseVar, Var, animation::Transitionable, const_var, impl_from_and_into_var, response_done_var, response_var};
82use zng_view_api::{config::FontAntiAliasing, font::IpcFontBytes};
83
84#[derive(Clone)]
92pub struct FontName {
93 txt: Txt,
94 is_ascii: bool,
95}
96impl fmt::Debug for FontName {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 if f.alternate() {
99 f.debug_struct("FontName")
100 .field("txt", &self.txt)
101 .field("is_ascii", &self.is_ascii)
102 .finish()
103 } else {
104 write!(f, "{:?}", self.txt)
105 }
106 }
107}
108impl PartialEq for FontName {
109 fn eq(&self, other: &Self) -> bool {
110 self.unicase() == other.unicase()
111 }
112}
113impl Eq for FontName {}
114impl PartialEq<str> for FontName {
115 fn eq(&self, other: &str) -> bool {
116 self.unicase() == unicase::UniCase::<&str>::from(other)
117 }
118}
119impl std::hash::Hash for FontName {
120 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
121 std::hash::Hash::hash(&self.unicase(), state)
122 }
123}
124impl Ord for FontName {
125 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
126 if self == other {
127 return std::cmp::Ordering::Equal;
129 }
130 self.txt.cmp(&other.txt)
131 }
132}
133impl PartialOrd for FontName {
134 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
135 Some(self.cmp(other))
136 }
137}
138impl FontName {
139 fn unicase(&self) -> unicase::UniCase<&str> {
140 if self.is_ascii {
141 unicase::UniCase::ascii(self)
142 } else {
143 unicase::UniCase::unicode(self)
144 }
145 }
146
147 pub const fn from_static(name: &'static str) -> Self {
149 FontName {
150 txt: Txt::from_static(name),
151 is_ascii: {
152 let name_bytes = name.as_bytes();
154 let mut i = name_bytes.len();
155 let mut is_ascii = true;
156 while i > 0 {
157 i -= 1;
158 if !name_bytes[i].is_ascii() {
159 is_ascii = false;
160 break;
161 }
162 }
163 is_ascii
164 },
165 }
166 }
167
168 pub fn new(name: impl Into<Txt>) -> Self {
177 let txt = name.into();
178 FontName {
179 is_ascii: txt.is_ascii(),
180 txt,
181 }
182 }
183
184 pub fn serif() -> Self {
190 Self::new("serif")
191 }
192
193 pub fn sans_serif() -> Self {
200 Self::new("sans-serif")
201 }
202
203 pub fn monospace() -> Self {
209 Self::new("monospace")
210 }
211
212 pub fn cursive() -> Self {
219 Self::new("cursive")
220 }
221
222 pub fn fantasy() -> Self {
228 Self::new("fantasy")
229 }
230
231 pub fn system_ui() -> Self {
237 Self::new("system-ui")
238 }
239
240 pub fn name(&self) -> &str {
242 &self.txt
243 }
244
245 pub fn into_text(self) -> Txt {
249 self.txt
250 }
251}
252impl_from_and_into_var! {
253 fn from(s: &'static str) -> FontName {
254 FontName::new(s)
255 }
256 fn from(s: String) -> FontName {
257 FontName::new(s)
258 }
259 fn from(s: Cow<'static, str>) -> FontName {
260 FontName::new(s)
261 }
262 fn from(f: FontName) -> Txt {
263 f.into_text()
264 }
265}
266impl fmt::Display for FontName {
267 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268 f.write_str(self.name())
269 }
270}
271impl std::ops::Deref for FontName {
272 type Target = str;
273
274 fn deref(&self) -> &Self::Target {
275 self.txt.deref()
276 }
277}
278impl AsRef<str> for FontName {
279 fn as_ref(&self) -> &str {
280 self.txt.as_ref()
281 }
282}
283impl serde::Serialize for FontName {
284 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285 where
286 S: serde::Serializer,
287 {
288 self.txt.serialize(serializer)
289 }
290}
291impl<'de> serde::Deserialize<'de> for FontName {
292 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
293 where
294 D: serde::Deserializer<'de>,
295 {
296 Txt::deserialize(deserializer).map(FontName::new)
297 }
298}
299
300#[derive(Eq, PartialEq, Hash, Clone, serde::Serialize, serde::Deserialize)]
329#[serde(transparent)]
330pub struct FontNames(pub Vec<FontName>);
331impl FontNames {
332 pub fn empty() -> Self {
334 FontNames(vec![])
335 }
336
337 pub fn push(&mut self, font_name: impl Into<FontName>) {
339 self.0.push(font_name.into())
340 }
341}
342impl Default for FontNames {
343 fn default() -> Self {
344 FontName::system_ui().into()
345 }
346}
347impl fmt::Debug for FontNames {
348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
349 if f.alternate() {
350 f.debug_tuple("FontNames").field(&self.0).finish()
351 } else if self.0.is_empty() {
352 write!(f, "[]")
353 } else if self.0.len() == 1 {
354 write!(f, "{:?}", self.0[0])
355 } else {
356 write!(f, "[{:?}, ", self.0[0])?;
357 for name in &self.0[1..] {
358 write!(f, "{name:?}, ")?;
359 }
360 write!(f, "]")
361 }
362 }
363}
364impl fmt::Display for FontNames {
365 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
366 let mut iter = self.0.iter();
367
368 if let Some(name) = iter.next() {
369 write!(f, "{name}")?;
370 for name in iter {
371 write!(f, ", {name}")?;
372 }
373 }
374
375 Ok(())
376 }
377}
378impl_from_and_into_var! {
379 fn from(font_name: &'static str) -> FontNames {
380 FontNames(vec![FontName::new(font_name)])
381 }
382
383 fn from(font_name: String) -> FontNames {
384 FontNames(vec![FontName::new(font_name)])
385 }
386
387 fn from(font_name: Txt) -> FontNames {
388 FontNames(vec![FontName::new(font_name)])
389 }
390
391 fn from(font_names: Vec<FontName>) -> FontNames {
392 FontNames(font_names)
393 }
394
395 fn from(font_names: Vec<&'static str>) -> FontNames {
396 FontNames(font_names.into_iter().map(FontName::new).collect())
397 }
398
399 fn from(font_names: Vec<String>) -> FontNames {
400 FontNames(font_names.into_iter().map(FontName::new).collect())
401 }
402
403 fn from(font_name: FontName) -> FontNames {
404 FontNames(vec![font_name])
405 }
406}
407impl ops::Deref for FontNames {
408 type Target = Vec<FontName>;
409
410 fn deref(&self) -> &Self::Target {
411 &self.0
412 }
413}
414impl ops::DerefMut for FontNames {
415 fn deref_mut(&mut self) -> &mut Self::Target {
416 &mut self.0
417 }
418}
419impl std::iter::Extend<FontName> for FontNames {
420 fn extend<T: IntoIterator<Item = FontName>>(&mut self, iter: T) {
421 self.0.extend(iter)
422 }
423}
424impl IntoIterator for FontNames {
425 type Item = FontName;
426
427 type IntoIter = std::vec::IntoIter<FontName>;
428
429 fn into_iter(self) -> Self::IntoIter {
430 self.0.into_iter()
431 }
432}
433impl<const N: usize> From<[FontName; N]> for FontNames {
434 fn from(font_names: [FontName; N]) -> Self {
435 FontNames(font_names.into())
436 }
437}
438impl<const N: usize> IntoVar<FontNames> for [FontName; N] {
439 fn into_var(self) -> Var<FontNames> {
440 const_var(self.into())
441 }
442}
443impl<const N: usize> From<[&'static str; N]> for FontNames {
444 fn from(font_names: [&'static str; N]) -> Self {
445 FontNames(font_names.into_iter().map(FontName::new).collect())
446 }
447}
448impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
449 fn into_var(self) -> Var<FontNames> {
450 const_var(self.into())
451 }
452}
453impl<const N: usize> From<[String; N]> for FontNames {
454 fn from(font_names: [String; N]) -> Self {
455 FontNames(font_names.into_iter().map(FontName::new).collect())
456 }
457}
458impl<const N: usize> IntoVar<FontNames> for [String; N] {
459 fn into_var(self) -> Var<FontNames> {
460 const_var(self.into())
461 }
462}
463impl<const N: usize> From<[Txt; N]> for FontNames {
464 fn from(font_names: [Txt; N]) -> Self {
465 FontNames(font_names.into_iter().map(FontName::new).collect())
466 }
467}
468impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
469 fn into_var(self) -> Var<FontNames> {
470 const_var(self.into())
471 }
472}
473
474event! {
475 pub static FONT_CHANGED_EVENT: FontChangedArgs;
486}
487
488event_args! {
489 pub struct FontChangedArgs {
491 pub change: FontChange,
493
494 ..
495
496 fn is_in_target(&self, id: WidgetId) -> bool {
498 true
499 }
500 }
501}
502
503#[derive(Clone, Debug, PartialEq)]
505pub enum FontChange {
506 SystemFonts,
510
511 CustomFonts,
516
517 Refresh,
521
522 GenericFont(FontName, Lang),
528
529 Fallback(Lang),
531}
532
533app_local! {
534 static FONTS_SV: FontsService = FontsService::new();
535}
536
537struct FontsService {
538 loader: FontFaceLoader,
539}
540impl FontsService {
541 fn new() -> Self {
542 let s = FontsService {
543 loader: FontFaceLoader::new(),
544 };
545
546 RAW_FONT_CHANGED_EVENT
548 .hook(|args| {
549 FONT_CHANGED_EVENT.notify(FontChangedArgs::new(
550 args.timestamp,
551 args.propagation.clone(),
552 FontChange::SystemFonts,
553 ));
554 true
555 })
556 .perm();
557
558 FONT_CHANGED_EVENT
560 .hook(|_| {
561 let mut s = FONTS_SV.write();
562 s.loader.on_refresh();
563 true
564 })
565 .perm();
566
567 VIEW_PROCESS_INITED_EVENT
569 .hook(|args| {
570 if args.is_respawn {
571 FONTS_SV.write().loader.on_view_process_respawn();
572 }
573 true
574 })
575 .perm();
576
577 s
578 }
579}
580
581pub struct FONTS;
583impl FONTS {
584 pub fn refresh(&self) {
588 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
589 }
590
591 pub fn prune(&self) {
593 UPDATES.once_update("FONTS.prune", move || {
594 FONTS_SV.write().loader.on_prune();
595 });
596 }
597
598 pub fn generics(&self) -> &'static GenericFonts {
600 &GenericFonts {}
601 }
602
603 pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
612 let resp = task::respond(FontFace::load_custom(custom_font));
614
615 resp.hook(|args| {
617 if let Some(done) = args.value().done() {
618 if let Ok(face) = done {
619 let mut fonts = FONTS_SV.write();
620 let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
621 let existing = family
622 .iter()
623 .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
624
625 if let Some(i) = existing {
626 family[i] = face.clone();
627 } else {
628 family.push(face.clone());
629 }
630
631 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
632 }
633 false
634 } else {
635 true
636 }
637 })
638 .perm();
639
640 resp
641 }
642
643 pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
647 let (responder, response) = response_var();
648
649 UPDATES.once_update("FONTS.unregister", move || {
650 let mut fonts = FONTS_SV.write();
651 let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&custom_family) {
652 for removed in removed {
656 removed.on_refresh();
657 }
658
659 true
660 } else {
661 false
662 };
663 responder.respond(r);
664
665 if r {
666 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
667 }
668 });
669
670 response
671 }
672
673 pub fn list(
675 &self,
676 families: &[FontName],
677 style: FontStyle,
678 weight: FontWeight,
679 stretch: FontStretch,
680 lang: &Lang,
681 ) -> ResponseVar<FontFaceList> {
682 if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
684 tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) found cached");
685 return cached;
686 }
687 tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) not cached, load");
688 FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
690 }
691
692 pub fn find(
694 &self,
695 family: &FontName,
696 style: FontStyle,
697 weight: FontWeight,
698 stretch: FontStretch,
699 lang: &Lang,
700 ) -> ResponseVar<Option<FontFace>> {
701 let resolved = GenericFonts {}.resolve(family, lang);
702 let family = resolved.as_ref().unwrap_or(family);
703
704 if let Some(cached) = FONTS_SV.read().loader.try_resolved(family, style, weight, stretch) {
706 return cached;
707 }
708 FONTS_SV.write().loader.load_resolved(family, style, weight, stretch)
710 }
711
712 pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
714 self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
715 }
716
717 pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
719 self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
720 }
721
722 pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
724 self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
725 }
726
727 pub fn custom_fonts(&self) -> Vec<FontName> {
729 FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
730 }
731
732 pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
736 query_util::system_all()
737 }
738
739 pub fn system_font_aa(&self) -> Var<FontAntiAliasing> {
743 RAW_FONT_AA_CHANGED_EVENT.var_map(|a| Some(a.aa), || FontAntiAliasing::Default)
744 }
745}
746
747impl<'a> From<ttf_parser::Face<'a>> for FontFaceMetrics {
748 fn from(f: ttf_parser::Face<'a>) -> Self {
749 let underline = f
750 .underline_metrics()
751 .unwrap_or(ttf_parser::LineMetrics { position: 0, thickness: 0 });
752 FontFaceMetrics {
753 units_per_em: f.units_per_em() as _,
754 ascent: f.ascender() as f32,
755 descent: f.descender() as f32,
756 line_gap: f.line_gap() as f32,
757 underline_position: underline.position as f32,
758 underline_thickness: underline.thickness as f32,
759 cap_height: f.capital_height().unwrap_or(0) as f32,
760 x_height: f.x_height().unwrap_or(0) as f32,
761 bounds: euclid::rect(
762 f.global_bounding_box().x_min as f32,
763 f.global_bounding_box().x_max as f32,
764 f.global_bounding_box().width() as f32,
765 f.global_bounding_box().height() as f32,
766 ),
767 }
768 }
769}
770
771#[derive(PartialEq, Eq, Hash)]
772struct FontInstanceKey(Px, Box<[(ttf_parser::Tag, i32)]>);
773impl FontInstanceKey {
774 pub fn new(size: Px, variations: &[rustybuzz::Variation]) -> Self {
776 let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
777 FontInstanceKey(size, variations_key.into_boxed_slice())
778 }
779}
780
781#[derive(Clone)]
788pub struct FontFace(Arc<LoadedFontFace>);
789struct LoadedFontFace {
790 data: FontBytes,
791 face_index: u32,
792 display_name: FontName,
793 family_name: FontName,
794 postscript_name: Option<Txt>,
795 style: FontStyle,
796 weight: FontWeight,
797 stretch: FontStretch,
798 metrics: FontFaceMetrics,
799 lig_carets: LigatureCaretList,
800 flags: FontFaceFlags,
801 m: Mutex<FontFaceMut>,
802}
803bitflags! {
804 #[derive(Debug, Clone, Copy)]
805 struct FontFaceFlags: u8 {
806 const IS_MONOSPACE = 0b0000_0001;
807 const HAS_LIGATURES = 0b0000_0010;
808 const HAS_RASTER_IMAGES = 0b0000_0100;
809 const HAS_SVG_IMAGES = 0b0000_1000;
810 }
811}
812struct FontFaceMut {
813 instances: HashMap<FontInstanceKey, Font>,
814 render_ids: Vec<RenderFontFace>,
815 unregistered: bool,
816}
817
818impl fmt::Debug for FontFace {
819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820 let m = self.0.m.lock();
821 f.debug_struct("FontFace")
822 .field("display_name", &self.0.display_name)
823 .field("family_name", &self.0.family_name)
824 .field("postscript_name", &self.0.postscript_name)
825 .field("flags", &self.0.flags)
826 .field("style", &self.0.style)
827 .field("weight", &self.0.weight)
828 .field("stretch", &self.0.stretch)
829 .field("metrics", &self.0.metrics)
830 .field("instances.len()", &m.instances.len())
831 .field("render_keys.len()", &m.render_ids.len())
832 .field("unregistered", &m.unregistered)
833 .finish_non_exhaustive()
834 }
835}
836impl PartialEq for FontFace {
837 fn eq(&self, other: &Self) -> bool {
838 Arc::ptr_eq(&self.0, &other.0)
839 }
840}
841impl Eq for FontFace {}
842impl FontFace {
843 pub fn empty() -> Self {
845 FontFace(Arc::new(LoadedFontFace {
846 data: FontBytes::from_static(&[]),
847 face_index: 0,
848 display_name: FontName::from("<empty>"),
849 family_name: FontName::from("<empty>"),
850 postscript_name: None,
851 flags: FontFaceFlags::IS_MONOSPACE,
852 style: FontStyle::Normal,
853 weight: FontWeight::NORMAL,
854 stretch: FontStretch::NORMAL,
855 metrics: FontFaceMetrics {
857 units_per_em: 2048,
858 ascent: 1616.0,
859 descent: -432.0,
860 line_gap: 0.0,
861 underline_position: -205.0,
862 underline_thickness: 102.0,
863 cap_height: 1616.0,
864 x_height: 1616.0,
865 bounds: euclid::Box2D::new(euclid::point2(0.0, -432.0), euclid::point2(1291.0, 1616.0)).to_rect(),
867 },
868 lig_carets: LigatureCaretList::empty(),
869 m: Mutex::new(FontFaceMut {
870 instances: HashMap::default(),
871 render_ids: vec![],
872 unregistered: false,
873 }),
874 }))
875 }
876
877 pub fn is_empty(&self) -> bool {
879 self.0.data.is_empty()
880 }
881
882 async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
883 let bytes;
884 let mut face_index;
885
886 match custom_font.source {
887 FontSource::File(path, index) => {
888 bytes = task::wait(|| FontBytes::from_file(path)).await?;
889 face_index = index;
890 }
891 FontSource::Memory(arc, index) => {
892 bytes = arc;
893 face_index = index;
894 }
895 FontSource::Alias(other_font) => {
896 let result = FONTS_SV
897 .write()
898 .loader
899 .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
900 return match result.wait_rsp().await {
901 Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
902 data: other_font.0.data.clone(),
903 face_index: other_font.0.face_index,
904 display_name: custom_font.name.clone(),
905 family_name: custom_font.name,
906 postscript_name: None,
907 style: other_font.0.style,
908 weight: other_font.0.weight,
909 stretch: other_font.0.stretch,
910 metrics: other_font.0.metrics.clone(),
911 m: Mutex::new(FontFaceMut {
912 instances: Default::default(),
913 render_ids: Default::default(),
914 unregistered: Default::default(),
915 }),
916 lig_carets: other_font.0.lig_carets.clone(),
917 flags: other_font.0.flags,
918 }))),
919 None => Err(FontLoadingError::NoSuchFontInCollection),
920 };
921 }
922 }
923
924 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
925 Ok(f) => f,
926 Err(e) => {
927 match e {
928 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
930 e => return Err(FontLoadingError::Parse(e)),
931 }
932
933 match ttf_parser::Face::parse(&bytes, face_index) {
934 Ok(f) => f,
935 Err(_) => return Err(FontLoadingError::Parse(e)),
936 }
937 }
938 };
939
940 let has_ligatures = ttf_face.tables().gsub.is_some();
941 let lig_carets = if has_ligatures {
942 LigatureCaretList::empty()
943 } else {
944 LigatureCaretList::load(ttf_face.raw_face())?
945 };
946
947 let has_raster_images = {
949 let t = ttf_face.tables();
950 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
951 };
952
953 let mut flags = FontFaceFlags::empty();
954 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
955 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
956 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
957 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
958
959 Ok(FontFace(Arc::new(LoadedFontFace {
960 face_index,
961 display_name: custom_font.name.clone(),
962 family_name: custom_font.name,
963 postscript_name: None,
964 style: custom_font.style,
965 weight: custom_font.weight,
966 stretch: custom_font.stretch,
967 metrics: ttf_face.into(),
968 lig_carets,
969 m: Mutex::new(FontFaceMut {
970 instances: Default::default(),
971 render_ids: Default::default(),
972 unregistered: Default::default(),
973 }),
974 data: bytes,
975 flags,
976 })))
977 }
978
979 fn load(bytes: FontBytes, mut face_index: u32) -> Result<Self, FontLoadingError> {
980 let _span = tracing::trace_span!("FontFace::load").entered();
981
982 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
983 Ok(f) => f,
984 Err(e) => {
985 match e {
986 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
988 e => return Err(FontLoadingError::Parse(e)),
989 }
990
991 match ttf_parser::Face::parse(&bytes, face_index) {
992 Ok(f) => f,
993 Err(_) => return Err(FontLoadingError::Parse(e)),
994 }
995 }
996 };
997
998 let has_ligatures = ttf_face.tables().gsub.is_some();
999 let lig_carets = if has_ligatures {
1000 LigatureCaretList::empty()
1001 } else {
1002 LigatureCaretList::load(ttf_face.raw_face())?
1003 };
1004
1005 let mut display_name = None;
1006 let mut family_name = None;
1007 let mut postscript_name = None;
1008 let mut any_name = None::<String>;
1009 for name in ttf_face.names() {
1010 if let Some(n) = name.to_string() {
1011 match name.name_id {
1012 ttf_parser::name_id::FULL_NAME => display_name = Some(n),
1013 ttf_parser::name_id::FAMILY => family_name = Some(n),
1014 ttf_parser::name_id::POST_SCRIPT_NAME => postscript_name = Some(n),
1015 _ => match &mut any_name {
1016 Some(s) => {
1017 if n.len() > s.len() {
1018 *s = n;
1019 }
1020 }
1021 None => any_name = Some(n),
1022 },
1023 }
1024 }
1025 }
1026 let display_name = FontName::new(Txt::from_str(
1027 display_name
1028 .as_ref()
1029 .or(family_name.as_ref())
1030 .or(postscript_name.as_ref())
1031 .or(any_name.as_ref())
1032 .unwrap(),
1033 ));
1034 let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1035 let postscript_name = postscript_name.map(Txt::from);
1036
1037 if ttf_face.units_per_em() == 0 {
1038 tracing::debug!("font {display_name:?} units_per_em 0");
1040 return Err(FontLoadingError::UnknownFormat);
1041 }
1042
1043 let has_raster_images = {
1045 let t = ttf_face.tables();
1046 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1047 };
1048
1049 let mut flags = FontFaceFlags::empty();
1050 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1051 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1052 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1053 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1054
1055 Ok(FontFace(Arc::new(LoadedFontFace {
1056 face_index,
1057 family_name,
1058 display_name,
1059 postscript_name,
1060 style: ttf_face.style().into(),
1061 weight: ttf_face.weight().into(),
1062 stretch: ttf_face.width().into(),
1063 metrics: ttf_face.into(),
1064 lig_carets,
1065 m: Mutex::new(FontFaceMut {
1066 instances: Default::default(),
1067 render_ids: Default::default(),
1068 unregistered: Default::default(),
1069 }),
1070 data: bytes,
1071 flags,
1072 })))
1073 }
1074
1075 fn on_refresh(&self) {
1076 let mut m = self.0.m.lock();
1077 m.instances.clear();
1078 m.unregistered = true;
1079 }
1080
1081 fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1082 let mut m = self.0.m.lock();
1083 for r in m.render_ids.iter() {
1084 if &r.renderer == renderer {
1085 return r.face_id;
1086 }
1087 }
1088
1089 let data = match self.0.data.to_ipc() {
1090 Ok(d) => d,
1091 Err(e) => {
1092 tracing::error!("cannot allocate ipc font data, {e}");
1093 return zng_view_api::font::FontFaceId::INVALID;
1094 }
1095 };
1096
1097 let key = match renderer.add_font_face(data, self.0.face_index) {
1098 Ok(k) => k,
1099 Err(_) => {
1100 tracing::debug!("respawned calling `add_font`, will return dummy font key");
1101 return zng_view_api::font::FontFaceId::INVALID;
1102 }
1103 };
1104
1105 m.render_ids.push(RenderFontFace::new(renderer, key));
1106
1107 key
1108 }
1109
1110 pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1119 if self.is_empty() {
1120 None
1121 } else {
1122 Some(rustybuzz::Face::from_slice(&self.0.data, self.0.face_index).unwrap())
1123 }
1124 }
1125
1126 pub fn ttf(&self) -> Option<ttf_parser::Face<'_>> {
1135 if self.is_empty() {
1136 None
1137 } else {
1138 Some(ttf_parser::Face::parse(&self.0.data, self.0.face_index).unwrap())
1139 }
1140 }
1141
1142 pub fn bytes(&self) -> &FontBytes {
1144 &self.0.data
1145 }
1146 pub fn index(&self) -> u32 {
1148 self.0.face_index
1149 }
1150
1151 pub fn display_name(&self) -> &FontName {
1153 &self.0.display_name
1154 }
1155
1156 pub fn family_name(&self) -> &FontName {
1158 &self.0.family_name
1159 }
1160
1161 pub fn postscript_name(&self) -> Option<&str> {
1163 self.0.postscript_name.as_deref()
1164 }
1165
1166 pub fn style(&self) -> FontStyle {
1168 self.0.style
1169 }
1170
1171 pub fn weight(&self) -> FontWeight {
1173 self.0.weight
1174 }
1175
1176 pub fn stretch(&self) -> FontStretch {
1178 self.0.stretch
1179 }
1180
1181 pub fn is_monospace(&self) -> bool {
1183 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1184 }
1185
1186 pub fn metrics(&self) -> &FontFaceMetrics {
1188 &self.0.metrics
1189 }
1190
1191 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1200 let key = FontInstanceKey::new(font_size, &variations);
1201 let mut m = self.0.m.lock();
1202 if !m.unregistered {
1203 m.instances
1204 .entry(key)
1205 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1206 .clone()
1207 } else {
1208 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1209 Font::new(self.clone(), font_size, variations)
1210 }
1211 }
1212
1213 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1215 let mut synth = FontSynthesis::DISABLED;
1216
1217 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1218 synth |= FontSynthesis::OBLIQUE;
1220 }
1221 if weight > self.weight() {
1222 synth |= FontSynthesis::BOLD;
1225 }
1226
1227 synth
1228 }
1229
1230 pub fn is_cached(&self) -> bool {
1234 !self.0.m.lock().unregistered
1235 }
1236
1237 pub fn color_palettes(&self) -> ColorPalettes<'_> {
1241 match self.ttf() {
1242 Some(ttf) => ColorPalettes::new(*ttf.raw_face()),
1243 None => ColorPalettes::empty(),
1244 }
1245 }
1246
1247 pub fn color_glyphs(&self) -> ColorGlyphs<'_> {
1251 match self.ttf() {
1252 Some(ttf) => ColorGlyphs::new(*ttf.raw_face()),
1253 None => ColorGlyphs::empty(),
1254 }
1255 }
1256
1257 pub fn has_ligatures(&self) -> bool {
1259 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1260 }
1261
1262 pub fn has_ligature_caret_offsets(&self) -> bool {
1267 !self.0.lig_carets.is_empty()
1268 }
1269
1270 pub fn has_raster_images(&self) -> bool {
1272 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1273 }
1274
1275 pub fn has_svg_images(&self) -> bool {
1277 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1278 }
1279}
1280
1281#[derive(Clone)]
1287pub struct Font(Arc<LoadedFont>);
1288struct LoadedFont {
1289 face: FontFace,
1290 size: Px,
1291 variations: RFontVariations,
1292 metrics: FontMetrics,
1293 render_keys: Mutex<Vec<RenderFont>>,
1294 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1295 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1296}
1297impl fmt::Debug for Font {
1298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1299 f.debug_struct("Font")
1300 .field("face", &self.0.face)
1301 .field("size", &self.0.size)
1302 .field("metrics", &self.0.metrics)
1303 .field("render_keys.len()", &self.0.render_keys.lock().len())
1304 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1305 .field("word_cache.len()", &self.0.word_cache.read().len())
1306 .finish()
1307 }
1308}
1309impl PartialEq for Font {
1310 fn eq(&self, other: &Self) -> bool {
1311 Arc::ptr_eq(&self.0, &other.0)
1312 }
1313}
1314impl Eq for Font {}
1315impl Font {
1316 const SMALL_WORD_LEN: usize = 8;
1317
1318 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1319 if s.len() <= Self::SMALL_WORD_LEN {
1320 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1321 a[..s.len()].copy_from_slice(s.as_bytes());
1322 Some(a)
1323 } else {
1324 None
1325 }
1326 }
1327
1328 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1329 Font(Arc::new(LoadedFont {
1330 metrics: face.metrics().sized(size),
1331 face,
1332 size,
1333 variations,
1334 render_keys: Mutex::new(vec![]),
1335 small_word_cache: RwLock::default(),
1336 word_cache: RwLock::default(),
1337 }))
1338 }
1339
1340 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1341 let _span = tracing::trace_span!("Font::render_font").entered();
1342
1343 let mut render_keys = self.0.render_keys.lock();
1344 for r in render_keys.iter() {
1345 if &r.renderer == renderer && r.synthesis == synthesis {
1346 return r.font_id;
1347 }
1348 }
1349
1350 let font_key = self.0.face.render_face(renderer);
1351
1352 let mut opt = zng_view_api::font::FontOptions::default();
1353 opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1354 opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1355 let variations = self.0.variations.iter().map(|v| (v.tag.to_bytes(), v.value)).collect();
1356
1357 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1358 Ok(k) => k,
1359 Err(_) => {
1360 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1361 return zng_view_api::font::FontId::INVALID;
1362 }
1363 };
1364
1365 render_keys.push(RenderFont::new(renderer, synthesis, key));
1366
1367 key
1368 }
1369
1370 pub fn face(&self) -> &FontFace {
1372 &self.0.face
1373 }
1374
1375 pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1377 let ppem = self.0.size.0 as u16;
1378
1379 let mut font = self.0.face.harfbuzz()?;
1380
1381 font.set_pixels_per_em(Some((ppem, ppem)));
1382 font.set_variations(&self.0.variations);
1383
1384 Some(font)
1385 }
1386
1387 pub fn size(&self) -> Px {
1391 self.0.size
1392 }
1393
1394 pub fn variations(&self) -> &RFontVariations {
1396 &self.0.variations
1397 }
1398
1399 pub fn metrics(&self) -> &FontMetrics {
1401 &self.0.metrics
1402 }
1403
1404 pub fn ligature_caret_offsets(
1410 &self,
1411 lig: zng_view_api::font::GlyphIndex,
1412 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1413 let face = &self.0.face.0;
1414 face.lig_carets.carets(lig).iter().map(move |&o| match o {
1415 ligature_util::LigatureCaret::Coordinate(o) => {
1416 let size_scale = 1.0 / face.metrics.units_per_em as f32 * self.0.size.0 as f32;
1417 o as f32 * size_scale
1418 }
1419 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1420 if let Some(f) = self.harfbuzz() {
1421 struct Search {
1422 i: u16,
1423 s: u16,
1424 x: f32,
1425 }
1426 impl Search {
1427 fn check(&mut self, x: f32) {
1428 self.s = self.s.saturating_add(1);
1429 if self.s == self.i {
1430 self.x = x;
1431 }
1432 }
1433 }
1434 impl ttf_parser::OutlineBuilder for Search {
1435 fn move_to(&mut self, x: f32, _y: f32) {
1436 self.check(x);
1437 }
1438
1439 fn line_to(&mut self, x: f32, _y: f32) {
1440 self.check(x);
1441 }
1442
1443 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1444 self.check(x)
1445 }
1446
1447 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1448 self.check(x);
1449 }
1450
1451 fn close(&mut self) {}
1452 }
1453 let mut search = Search { i, s: 0, x: 0.0 };
1454 if f.outline_glyph(ttf_parser::GlyphId(lig as _), &mut search).is_some() && search.s >= search.i {
1455 return search.x * self.0.metrics.size_scale;
1456 }
1457 }
1458 0.0
1459 }
1460 })
1461 }
1462}
1463impl zng_app::render::Font for Font {
1464 fn is_empty_fallback(&self) -> bool {
1465 self.face().is_empty()
1466 }
1467
1468 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1469 self.render_font(renderer, synthesis)
1470 }
1471}
1472
1473#[derive(Debug, Clone)]
1477pub struct FontFaceList {
1478 fonts: Box<[FontFace]>,
1479 requested_style: FontStyle,
1480 requested_weight: FontWeight,
1481 requested_stretch: FontStretch,
1482}
1483impl FontFaceList {
1484 pub fn empty() -> Self {
1486 Self {
1487 fonts: Box::new([FontFace::empty()]),
1488 requested_style: FontStyle::Normal,
1489 requested_weight: FontWeight::NORMAL,
1490 requested_stretch: FontStretch::NORMAL,
1491 }
1492 }
1493
1494 pub fn requested_style(&self) -> FontStyle {
1496 self.requested_style
1497 }
1498
1499 pub fn requested_weight(&self) -> FontWeight {
1501 self.requested_weight
1502 }
1503
1504 pub fn requested_stretch(&self) -> FontStretch {
1506 self.requested_stretch
1507 }
1508
1509 pub fn best(&self) -> &FontFace {
1511 &self.fonts[0]
1512 }
1513
1514 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1516 if let Some(face) = self.fonts.get(face_index) {
1517 face.synthesis_for(self.requested_style, self.requested_weight)
1518 } else {
1519 FontSynthesis::DISABLED
1520 }
1521 }
1522
1523 pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1525 self.fonts.iter()
1526 }
1527
1528 pub fn len(&self) -> usize {
1532 self.fonts.len()
1533 }
1534
1535 pub fn is_empty(&self) -> bool {
1537 self.fonts[0].is_empty() && self.fonts.len() == 1
1538 }
1539
1540 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1544 FontList {
1545 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1546 requested_style: self.requested_style,
1547 requested_weight: self.requested_weight,
1548 requested_stretch: self.requested_stretch,
1549 }
1550 }
1551}
1552impl PartialEq for FontFaceList {
1553 fn eq(&self, other: &Self) -> bool {
1555 self.requested_style == other.requested_style
1556 && self.requested_weight == other.requested_weight
1557 && self.requested_stretch == other.requested_stretch
1558 && self.fonts.len() == other.fonts.len()
1559 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1560 }
1561}
1562impl Eq for FontFaceList {}
1563impl std::ops::Deref for FontFaceList {
1564 type Target = [FontFace];
1565
1566 fn deref(&self) -> &Self::Target {
1567 &self.fonts
1568 }
1569}
1570impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1571 type Item = &'a FontFace;
1572
1573 type IntoIter = std::slice::Iter<'a, FontFace>;
1574
1575 fn into_iter(self) -> Self::IntoIter {
1576 self.iter()
1577 }
1578}
1579impl std::ops::Index<usize> for FontFaceList {
1580 type Output = FontFace;
1581
1582 fn index(&self, index: usize) -> &Self::Output {
1583 &self.fonts[index]
1584 }
1585}
1586
1587#[derive(Debug, Clone)]
1589pub struct FontList {
1590 fonts: Box<[Font]>,
1591 requested_style: FontStyle,
1592 requested_weight: FontWeight,
1593 requested_stretch: FontStretch,
1594}
1595#[expect(clippy::len_without_is_empty)] impl FontList {
1597 pub fn best(&self) -> &Font {
1599 &self.fonts[0]
1600 }
1601
1602 pub fn requested_size(&self) -> Px {
1604 self.fonts[0].size()
1605 }
1606
1607 pub fn requested_style(&self) -> FontStyle {
1609 self.requested_style
1610 }
1611
1612 pub fn requested_weight(&self) -> FontWeight {
1614 self.requested_weight
1615 }
1616
1617 pub fn requested_stretch(&self) -> FontStretch {
1619 self.requested_stretch
1620 }
1621
1622 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1624 if let Some(font) = self.fonts.get(font_index) {
1625 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1626 } else {
1627 FontSynthesis::DISABLED
1628 }
1629 }
1630
1631 pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1633 self.fonts.iter()
1634 }
1635
1636 pub fn len(&self) -> usize {
1640 self.fonts.len()
1641 }
1642
1643 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1645 if self.len() != faces.len() {
1646 return false;
1647 }
1648
1649 for (font, face) in self.iter().zip(faces.iter()) {
1650 if font.face() != face {
1651 return false;
1652 }
1653 }
1654
1655 true
1656 }
1657}
1658impl PartialEq for FontList {
1659 fn eq(&self, other: &Self) -> bool {
1661 self.requested_style == other.requested_style
1662 && self.requested_weight == other.requested_weight
1663 && self.requested_stretch == other.requested_stretch
1664 && self.fonts.len() == other.fonts.len()
1665 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1666 }
1667}
1668impl Eq for FontList {}
1669impl std::ops::Deref for FontList {
1670 type Target = [Font];
1671
1672 fn deref(&self) -> &Self::Target {
1673 &self.fonts
1674 }
1675}
1676impl<'a> std::iter::IntoIterator for &'a FontList {
1677 type Item = &'a Font;
1678
1679 type IntoIter = std::slice::Iter<'a, Font>;
1680
1681 fn into_iter(self) -> Self::IntoIter {
1682 self.iter()
1683 }
1684}
1685impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1686 type Output = I::Output;
1687
1688 fn index(&self, index: I) -> &I::Output {
1689 &self.fonts[index]
1690 }
1691}
1692
1693struct FontFaceLoader {
1694 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1695
1696 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1697 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1698}
1699struct SystemFontFace {
1700 properties: (FontStyle, FontWeight, FontStretch),
1701 result: ResponseVar<Option<FontFace>>,
1702}
1703struct FontFaceListQuery {
1704 properties: (FontStyle, FontWeight, FontStretch),
1705 lang: Lang,
1706 result: ResponseVar<FontFaceList>,
1707}
1708impl FontFaceLoader {
1709 fn new() -> Self {
1710 FontFaceLoader {
1711 custom_fonts: HashMap::new(),
1712 system_fonts_cache: HashMap::new(),
1713 list_cache: HashMap::new(),
1714 }
1715 }
1716
1717 fn on_view_process_respawn(&mut self) {
1718 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1719 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1720 let mut m = face.0.m.lock();
1721 m.render_ids.clear();
1722 for inst in m.instances.values() {
1723 inst.0.render_keys.lock().clear();
1724 }
1725 }
1726 }
1727
1728 fn on_refresh(&mut self) {
1729 for (_, sys_family) in self.system_fonts_cache.drain() {
1730 for sys_font in sys_family {
1731 sys_font.result.with(|r| {
1732 if let Some(Some(face)) = r.done() {
1733 face.on_refresh();
1734 }
1735 });
1736 }
1737 }
1738 }
1739 fn on_prune(&mut self) {
1740 self.system_fonts_cache.retain(|_, v| {
1741 v.retain(|sff| {
1742 if sff.result.strong_count() == 1 {
1743 sff.result.with(|r| {
1744 match r.done() {
1745 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1749 })
1750 } else {
1751 true
1753 }
1754 });
1755 !v.is_empty()
1756 });
1757
1758 self.list_cache.clear();
1759 }
1760
1761 fn try_list(
1762 &self,
1763 families: &[FontName],
1764 style: FontStyle,
1765 weight: FontWeight,
1766 stretch: FontStretch,
1767 lang: &Lang,
1768 ) -> Option<ResponseVar<FontFaceList>> {
1769 if let Some(queries) = self.list_cache.get(families) {
1770 for q in queries {
1771 if q.properties == (style, weight, stretch) && &q.lang == lang {
1772 return Some(q.result.clone());
1773 }
1774 }
1775 }
1776 None
1777 }
1778
1779 fn load_list(
1780 &mut self,
1781 families: &[FontName],
1782 style: FontStyle,
1783 weight: FontWeight,
1784 stretch: FontStretch,
1785 lang: &Lang,
1786 ) -> ResponseVar<FontFaceList> {
1787 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1788 return r;
1789 }
1790
1791 let resolved = GenericFonts {}.resolve_list(families, lang);
1792 let families = resolved.as_ref().map(|n| &***n).unwrap_or(families);
1793 let mut list = Vec::with_capacity(families.len() + 1);
1794 let mut pending = vec![];
1795
1796 {
1797 let fallback = [GenericFonts {}.fallback(lang)];
1798 let mut used = HashSet::with_capacity(families.len());
1799 for name in families.iter().chain(&fallback) {
1800 if !used.insert(name) {
1801 continue;
1802 }
1803
1804 let face = self.load_resolved(name, style, weight, stretch);
1805 if face.is_done() {
1806 if let Some(face) = face.rsp().unwrap() {
1807 list.push(face);
1808 }
1809 } else {
1810 pending.push((list.len(), face));
1811 }
1812 }
1813 }
1814
1815 let r = if pending.is_empty() {
1816 if list.is_empty() {
1817 tracing::error!(target: "font_loading", "failed to load fallback font");
1818 list.push(FontFace::empty());
1819 }
1820 response_done_var(FontFaceList {
1821 fonts: list.into_boxed_slice(),
1822 requested_style: style,
1823 requested_weight: weight,
1824 requested_stretch: stretch,
1825 })
1826 } else {
1827 task::respond(async move {
1828 for (i, pending) in pending.into_iter().rev() {
1829 if let Some(rsp) = pending.wait_rsp().await {
1830 list.insert(i, rsp);
1831 }
1832 }
1833
1834 if list.is_empty() {
1835 tracing::error!(target: "font_loading", "failed to load fallback font");
1836 list.push(FontFace::empty());
1837 }
1838
1839 FontFaceList {
1840 fonts: list.into_boxed_slice(),
1841 requested_style: style,
1842 requested_weight: weight,
1843 requested_stretch: stretch,
1844 }
1845 })
1846 };
1847
1848 self.list_cache
1849 .entry(families.iter().cloned().collect())
1850 .or_insert_with(|| Vec::with_capacity(1))
1851 .push(FontFaceListQuery {
1852 properties: (style, weight, stretch),
1853 lang: lang.clone(),
1854 result: r.clone(),
1855 });
1856
1857 r
1858 }
1859
1860 fn try_resolved(
1862 &self,
1863 font_name: &FontName,
1864 style: FontStyle,
1865 weight: FontWeight,
1866 stretch: FontStretch,
1867 ) -> Option<ResponseVar<Option<FontFace>>> {
1868 if let Some(custom_family) = self.custom_fonts.get(font_name) {
1869 let custom = Self::match_custom(custom_family, style, weight, stretch);
1870 return Some(response_done_var(Some(custom)));
1871 }
1872
1873 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
1874 for sys_face in cached_sys_family.iter() {
1875 if sys_face.properties == (style, weight, stretch) {
1876 return Some(sys_face.result.clone());
1877 }
1878 }
1879 }
1880
1881 None
1882 }
1883
1884 fn load_resolved(
1886 &mut self,
1887 font_name: &FontName,
1888 style: FontStyle,
1889 weight: FontWeight,
1890 stretch: FontStretch,
1891 ) -> ResponseVar<Option<FontFace>> {
1892 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
1893 return cached;
1894 }
1895
1896 let load = task::wait(clmv!(font_name, || {
1897 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
1898 Some(h) => h,
1899 None => {
1900 #[cfg(debug_assertions)]
1901 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
1902
1903 #[cfg(debug_assertions)]
1904 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
1905 tracing::debug!(r#"font "{font_name}" not found"#);
1906 }
1907
1908 return None;
1909 }
1910 };
1911 match FontFace::load(bytes, face_index) {
1912 Ok(f) => Some(f),
1913 Err(FontLoadingError::UnknownFormat) => None,
1914 Err(e) => {
1915 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
1916 None
1917 }
1918 }
1919 }));
1920 let result = task::respond(async_clmv!(font_name, {
1921 match task::with_deadline(load, 10.secs()).await {
1922 Ok(r) => r,
1923 Err(_) => {
1924 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
1925 None
1926 }
1927 }
1928 }));
1929
1930 self.system_fonts_cache
1931 .entry(font_name.clone())
1932 .or_insert_with(|| Vec::with_capacity(1))
1933 .push(SystemFontFace {
1934 properties: (style, weight, stretch),
1935 result: result.clone(),
1936 });
1937
1938 result
1939 }
1940
1941 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontBytes, u32)> {
1942 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
1943 match query_util::best(font_name, style, weight, stretch) {
1944 Ok(r) => r,
1945 Err(e) => {
1946 tracing::error!("cannot get `{font_name}` system font, {e}");
1947 None
1948 }
1949 }
1950 }
1951
1952 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
1953 if faces.len() == 1 {
1954 return faces[0].clone();
1956 }
1957
1958 let mut set = Vec::with_capacity(faces.len());
1959 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
1966 |s| s > FontStretch::NORMAL
1967 } else {
1968 |s| s <= FontStretch::NORMAL
1969 };
1970 for face in faces {
1971 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
1972 if wrong_side(face.stretch()) {
1973 dist += f32::MAX as f64 + 1.0;
1974 }
1975
1976 if set.is_empty() {
1977 set.push(face);
1978 set_dist = dist;
1979 } else if dist < set_dist {
1980 set_dist = dist;
1982 set.clear();
1983 set.push(face);
1984 } else if (dist - set_dist).abs() < 0.0001 {
1985 set.push(face);
1987 }
1988 }
1989 if set.len() == 1 {
1990 return set[0].clone();
1991 }
1992
1993 let style_pref = match style {
1998 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
1999 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
2000 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
2001 };
2002 let mut best_style = style_pref.len();
2003 for face in &set {
2004 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
2005 if i < best_style {
2006 best_style = i;
2007 }
2008 }
2009 set.retain(|f| f.style() == style_pref[best_style]);
2010 if set.len() == 1 {
2011 return set[0].clone();
2012 }
2013
2014 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2022 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2024 if face.weight() < weight {
2026 *dist += 100.0;
2028 } else if face.weight().0 > 500.0 {
2029 *dist += 600.0;
2031 }
2032 }
2033 } else if weight.0 < 400.0 {
2034 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2036 if face.weight() > weight {
2037 *dist += weight.0 as f64;
2038 }
2039 }
2040 } else {
2041 debug_assert!(weight.0 > 500.0);
2042 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2044 if face.weight() < weight {
2045 *dist += f32::MAX as f64;
2046 }
2047 }
2048 };
2049
2050 let mut best = set[0];
2051 let mut best_dist = f64::MAX;
2052
2053 for face in &set {
2054 let mut dist = (face.weight().0 - weight.0).abs() as f64;
2055
2056 add_penalty(face, weight, &mut dist);
2057
2058 if dist < best_dist {
2059 best_dist = dist;
2060 best = face;
2061 }
2062 }
2063
2064 best.clone()
2065 }
2066}
2067
2068struct RenderFontFace {
2069 renderer: ViewRenderer,
2070 face_id: zng_view_api::font::FontFaceId,
2071}
2072impl RenderFontFace {
2073 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2074 RenderFontFace {
2075 renderer: renderer.clone(),
2076 face_id,
2077 }
2078 }
2079}
2080impl Drop for RenderFontFace {
2081 fn drop(&mut self) {
2082 let _ = self.renderer.delete_font_face(self.face_id);
2084 }
2085}
2086
2087struct RenderFont {
2088 renderer: ViewRenderer,
2089 synthesis: FontSynthesis,
2090 font_id: zng_view_api::font::FontId,
2091}
2092impl RenderFont {
2093 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2094 RenderFont {
2095 renderer: renderer.clone(),
2096 synthesis,
2097 font_id,
2098 }
2099 }
2100}
2101impl Drop for RenderFont {
2102 fn drop(&mut self) {
2103 let _ = self.renderer.delete_font(self.font_id);
2105 }
2106}
2107
2108app_local! {
2109 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2110}
2111
2112struct GenericFontsService {
2113 serif: LangMap<FontName>,
2114 sans_serif: LangMap<FontName>,
2115 monospace: LangMap<FontName>,
2116 cursive: LangMap<FontName>,
2117 fantasy: LangMap<FontName>,
2118 fallback: LangMap<FontName>,
2119 system_ui: LangMap<FontNames>,
2120}
2121impl GenericFontsService {
2122 fn new() -> Self {
2123 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2124 let mut f = LangMap::with_capacity(1);
2125 f.insert(lang!(und), name.into());
2126 f
2127 }
2128
2129 let serif = "serif";
2130 let sans_serif = "sans-serif";
2131 let monospace = "monospace";
2132 let cursive = "cursive";
2133 let fantasy = "fantasy";
2134 let fallback = if cfg!(windows) {
2135 "Segoe UI Symbol"
2136 } else if cfg!(target_os = "linux") {
2137 "Standard Symbols PS"
2138 } else {
2139 "sans-serif"
2140 };
2141
2142 let mut system_ui = LangMap::with_capacity(5);
2143
2144 if cfg!(windows) {
2145 system_ui.insert(
2146 lang!("zh-Hans"),
2147 ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into(),
2148 );
2149 system_ui.insert(
2150 lang!("zh-Hant"),
2151 ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into(),
2152 );
2153 system_ui.insert(
2154 lang!("ja"),
2155 ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into(),
2156 );
2157 system_ui.insert(
2158 lang!("ko"),
2159 ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into(),
2160 );
2161 for lang in [
2162 lang!("hi"),
2163 lang!("bn"),
2164 lang!("te"),
2165 lang!("as"),
2166 lang!("gu"),
2167 lang!("kn"),
2168 lang!("mr"),
2169 lang!("ne"),
2170 lang!("or"),
2171 lang!("pa"),
2172 lang!("si"),
2173 ] {
2174 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Mangal", "Segoe Ui Emoji", "sans-serif"].into());
2175 }
2176 system_ui.insert(lang!("am"), ["Segoe UI", "Nyala", "Ebrima", "Segoe Ui Emoji", "sans-serif"].into());
2177 system_ui.insert(
2178 lang!("km"),
2179 ["Segoe UI", "Khmer UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2180 );
2181 system_ui.insert(
2182 lang!("lo"),
2183 ["Segoe UI", "lao UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2184 );
2185 system_ui.insert(lang!("th"), ["Segoe UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into());
2186 for lang in [lang!("ml"), lang!("ta")] {
2187 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Segoe Ui Emoji", "sans-serif"].into());
2188 }
2189 system_ui.insert(lang!("my"), ["Segoe UI", "Myanmar Text", "Segoe Ui Emoji", "sans-serif"].into());
2190
2191 system_ui.insert(lang!(und), ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into());
2192 } else if cfg!(target_os = "macos") {
2193 system_ui.insert(
2194 lang!("zh-Hans"),
2195 ["system-ui", "PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into(),
2196 );
2197 system_ui.insert(
2198 lang!("zh-Hant"),
2199 ["system-ui", "PingFang TC", "Apple Color Emoji", "sans-serif"].into(),
2200 );
2201 system_ui.insert(
2202 lang!("ja"),
2203 [
2204 "system-ui",
2205 "Hiragino Sans",
2206 "Hiragino Kaku Gothic ProN",
2207 "Apple Color Emoji",
2208 "sans-serif",
2209 ]
2210 .into(),
2211 );
2212 system_ui.insert(
2213 lang!("ko"),
2214 ["system-ui", "Apple SD Gothic Neo", "NanumGothic", "Apple Color Emoji", "sans-serif"].into(),
2215 );
2216
2217 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2218 system_ui.insert(
2219 lang,
2220 [
2221 "system-ui",
2222 "Kohinoor Devanagari",
2223 "Devanagari Sangam MN",
2224 "Apple Color Emoji",
2225 "sans-serif",
2226 ]
2227 .into(),
2228 );
2229 }
2230 for lang in [lang!("bn"), lang!("as")] {
2231 system_ui.insert(
2232 lang,
2233 [
2234 "system-ui",
2235 "Kohinoor Bangla",
2236 "Bangla Sangam MN",
2237 "Apple Color Emoji",
2238 "sans-serif",
2239 ]
2240 .into(),
2241 );
2242 }
2243 system_ui.insert(
2244 lang!("te"),
2245 [
2246 "system-ui",
2247 "Kohinoor Telugu",
2248 "Telugu Sangam MN",
2249 "Apple Color Emoji",
2250 "sans-serif",
2251 ]
2252 .into(),
2253 );
2254 system_ui.insert(
2255 lang!("gu"),
2256 [
2257 "system-ui",
2258 "Kohinoor Gujarati",
2259 "Gujarati Sangam MN",
2260 "Apple Color Emoji",
2261 "sans-serif",
2262 ]
2263 .into(),
2264 );
2265 system_ui.insert(
2266 lang!("kn"),
2267 ["system-ui", "Kannada Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2268 );
2269 system_ui.insert(
2270 lang!("or"),
2271 ["system-ui", "Oriya Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2272 );
2273 system_ui.insert(
2274 lang!("pa"),
2275 ["system-ui", "Mukta Mahee", "Gurmukhi Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2276 );
2277 system_ui.insert(
2278 lang!("si"),
2279 ["system-ui", "Sinhala Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2280 );
2281
2282 system_ui.insert(lang!("am"), ["system-ui", "Kefa", "Apple Color Emoji", "sans-serif"].into());
2283 system_ui.insert(
2284 lang!("km"),
2285 ["system-ui", "Khmer Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2286 );
2287 system_ui.insert(
2288 lang!("lo"),
2289 ["system-ui", "Lao Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2290 );
2291 system_ui.insert(
2292 lang!("th"),
2293 ["system-ui", "Thonburi", "Ayuthaya", "Apple Color Emoji", "sans-serif"].into(),
2294 );
2295 system_ui.insert(
2296 lang!("my"),
2297 ["system-ui", "Myanmar Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2298 );
2299
2300 system_ui.insert(
2301 lang!("ml"),
2302 ["system-ui", "Malayalam Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2303 );
2304 system_ui.insert(
2305 lang!("ta"),
2306 ["system-ui", "Kohinoor Tamil", "Tamil Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2307 );
2308
2309 for lang in [lang!("ar"), lang!("fa"), lang!("ps")] {
2310 system_ui.insert(lang, ["system-ui", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into());
2311 }
2312
2313 system_ui.insert(lang!("he"), ["system-ui", "Arial Hebrew", "Apple Color Emoji", "sans-serif"].into());
2314
2315 system_ui.insert(lang!("hy"), ["system-ui", "Mshtakan", "Apple Color Emoji", "sans-serif"].into());
2316
2317 system_ui.insert(
2318 lang!("ka"),
2319 ["system-ui", "Helvetica Neue", "Apple Color Emoji", "sans-serif"].into(),
2320 );
2321
2322 system_ui.insert(
2323 lang!("ur"),
2324 ["system-ui", "SF Arabic", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into(),
2325 );
2326
2327 system_ui.insert(
2328 lang!(und),
2329 ["system-ui", "Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into(),
2330 );
2331 } else if cfg!(target_os = "linux") {
2332 system_ui.insert(
2333 lang!("zh-Hans"),
2334 [
2335 "system-ui",
2336 "Ubuntu",
2337 "Noto Sans CJK SC",
2338 "Source Han Sans SC",
2339 "Noto Color Emoji",
2340 "sans-serif",
2341 ]
2342 .into(),
2343 );
2344 system_ui.insert(
2345 lang!("zh-Hant"),
2346 [
2347 "system-ui",
2348 "Ubuntu",
2349 "Noto Sans CJK TC",
2350 "Source Han Sans TC",
2351 "Noto Color Emoji",
2352 "sans-serif",
2353 ]
2354 .into(),
2355 );
2356 system_ui.insert(
2357 lang!("ja"),
2358 [
2359 "system-ui",
2360 "Ubuntu",
2361 "Noto Sans CJK JP",
2362 "Source Han Sans JP",
2363 "Noto Color Emoji",
2364 "sans-serif",
2365 ]
2366 .into(),
2367 );
2368 system_ui.insert(
2369 lang!("ko"),
2370 [
2371 "system-ui",
2372 "Ubuntu",
2373 "Noto Sans CJK KR",
2374 "Source Han Sans KR",
2375 "UnDotum",
2376 "Noto Color Emoji",
2377 "sans-serif",
2378 ]
2379 .into(),
2380 );
2381
2382 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2383 system_ui.insert(
2384 lang,
2385 [
2386 "system-ui",
2387 "Ubuntu",
2388 "Noto Sans Devanagari",
2389 "Lohit Devanagari",
2390 "Noto Color Emoji",
2391 "sans-serif",
2392 ]
2393 .into(),
2394 );
2395 }
2396 for lang in [lang!("bn"), lang!("as")] {
2397 system_ui.insert(
2398 lang,
2399 [
2400 "system-ui",
2401 "Ubuntu",
2402 "Noto Sans Bengali",
2403 "Lohit Bengali",
2404 "Noto Color Emoji",
2405 "sans-serif",
2406 ]
2407 .into(),
2408 );
2409 }
2410 system_ui.insert(
2411 lang!("te"),
2412 [
2413 "system-ui",
2414 "Ubuntu",
2415 "Noto Sans Telugu",
2416 "Lohit Telugu",
2417 "Noto Color Emoji",
2418 "sans-serif",
2419 ]
2420 .into(),
2421 );
2422 system_ui.insert(
2423 lang!("gu"),
2424 [
2425 "system-ui",
2426 "Ubuntu",
2427 "Noto Sans Gujarati",
2428 "Lohit Gujarati",
2429 "Noto Color Emoji",
2430 "sans-serif",
2431 ]
2432 .into(),
2433 );
2434 system_ui.insert(
2435 lang!("kn"),
2436 [
2437 "system-ui",
2438 "Ubuntu",
2439 "Noto Sans Kannada",
2440 "Lohit Kannada",
2441 "Noto Color Emoji",
2442 "sans-serif",
2443 ]
2444 .into(),
2445 );
2446 system_ui.insert(
2447 lang!("or"),
2448 [
2449 "system-ui",
2450 "Ubuntu",
2451 "Noto Sans Oriya",
2452 "Lohit Odia",
2453 "Noto Color Emoji",
2454 "sans-serif",
2455 ]
2456 .into(),
2457 );
2458 system_ui.insert(
2459 lang!("pa"),
2460 [
2461 "system-ui",
2462 "Ubuntu",
2463 "Noto Sans Gurmukhi",
2464 "Lohit Gurmukhi",
2465 "Noto Color Emoji",
2466 "sans-serif",
2467 ]
2468 .into(),
2469 );
2470 system_ui.insert(
2471 lang!("si"),
2472 [
2473 "system-ui",
2474 "Ubuntu",
2475 "Noto Sans Sinhala",
2476 "LKLUG",
2477 "Noto Color Emoji",
2478 "sans-serif",
2479 ]
2480 .into(),
2481 );
2482
2483 system_ui.insert(
2484 lang!("am"),
2485 [
2486 "system-ui",
2487 "Ubuntu",
2488 "Noto Sans Ethiopic",
2489 "Abyssinica SIL",
2490 "Noto Color Emoji",
2491 "sans-serif",
2492 ]
2493 .into(),
2494 );
2495 system_ui.insert(
2496 lang!("km"),
2497 [
2498 "system-ui",
2499 "Ubuntu",
2500 "Noto Sans Khmer",
2501 "Hanuman",
2502 "Noto Color Emoji",
2503 "sans-serif",
2504 ]
2505 .into(),
2506 );
2507 system_ui.insert(
2508 lang!("lo"),
2509 [
2510 "system-ui",
2511 "Ubuntu",
2512 "Noto Sans Lao",
2513 "Phetsarath OT",
2514 "Noto Color Emoji",
2515 "sans-serif",
2516 ]
2517 .into(),
2518 );
2519 system_ui.insert(
2520 lang!("th"),
2521 [
2522 "system-ui",
2523 "Ubuntu",
2524 "Noto Sans Thai",
2525 "Kinnari",
2526 "Garuda",
2527 "Noto Color Emoji",
2528 "sans-serif",
2529 ]
2530 .into(),
2531 );
2532 system_ui.insert(
2533 lang!("my"),
2534 [
2535 "system-ui",
2536 "Ubuntu",
2537 "Noto Sans Myanmar",
2538 "Padauk",
2539 "Noto Color Emoji",
2540 "sans-serif",
2541 ]
2542 .into(),
2543 );
2544
2545 system_ui.insert(
2546 lang!("ml"),
2547 [
2548 "system-ui",
2549 "Ubuntu",
2550 "Noto Sans Malayalam",
2551 "Lohit Malayalam",
2552 "Noto Color Emoji",
2553 "sans-serif",
2554 ]
2555 .into(),
2556 );
2557 system_ui.insert(
2558 lang!("ta"),
2559 [
2560 "system-ui",
2561 "Ubuntu",
2562 "Noto Sans Tamil",
2563 "Lohit Tamil",
2564 "Noto Color Emoji",
2565 "sans-serif",
2566 ]
2567 .into(),
2568 );
2569
2570 for lang in [lang!("ar"), lang!("fa"), lang!("ps"), lang!("ur")] {
2571 system_ui.insert(lang, ["system-ui", "Noto Sans Arabic", "Noto Color Emoji", "sans-serif"].into());
2572 }
2573
2574 system_ui.insert(
2575 lang!("he"),
2576 ["system-ui", "Noto Sans Hebrew", "Noto Color Emoji", "sans-serif"].into(),
2577 );
2578
2579 system_ui.insert(
2580 lang!("hy"),
2581 ["system-ui", "Noto Sans Armenian", "Noto Color Emoji", "sans-serif"].into(),
2582 );
2583
2584 system_ui.insert(lang!("ka"), ["system-ui", "DejaVu Sans", "Noto Color Emoji", "sans-serif"].into());
2585
2586 system_ui.insert(
2587 lang!("ur"),
2588 [
2589 "system-ui",
2590 "Noto Naskh Arabic",
2591 "Noto Sans Arabic",
2592 "Noto Color Emoji",
2593 "sans-serif",
2594 ]
2595 .into(),
2596 );
2597
2598 system_ui.insert(
2599 lang!(und),
2600 ["system-ui", "Ubuntu", "Droid Sans", "Noto Sans", "Noto Color Emoji", "sans-serif"].into(),
2601 );
2602 } else {
2603 system_ui.insert(lang!(und), ["system-ui", "sans-serif"].into());
2604 }
2605
2606 GenericFontsService {
2607 serif: default(serif),
2608 sans_serif: default(sans_serif),
2609 monospace: default(monospace),
2610 cursive: default(cursive),
2611 fantasy: default(fantasy),
2612
2613 system_ui,
2614
2615 fallback: default(fallback),
2616 }
2617 }
2618}
2619
2620#[non_exhaustive]
2631pub struct GenericFonts {}
2632macro_rules! impl_fallback_accessors {
2633 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2634 #[doc = "Gets the *"$name_str "* font for the given language."]
2635 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2639
2640 pub fn $name(&self, lang: &Lang) -> FontName {
2641 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2642 }
2643
2644 #[doc = "Sets the *"$name_str "* font for the given language."]
2645 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2650 self.[<set_ $name _impl>](lang, font_name.into());
2651 }
2652 fn [<set_ $name _impl>](&self, lang: Lang, font_name: FontName) {
2653 UPDATES.once_update("GenericFonts.set", move || {
2654 GENERIC_FONTS_SV.write().$name.insert(lang.clone(), font_name);
2655 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2656 });
2657 }
2658 })+};
2659}
2660impl GenericFonts {
2661 #[rustfmt::skip] impl_fallback_accessors! {
2663 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2664 }
2665
2666 pub fn system_ui(&self, lang: &Lang) -> FontNames {
2672 GENERIC_FONTS_SV.read().system_ui.get(lang).unwrap().clone()
2673 }
2674
2675 pub fn set_system_ui(&self, lang: Lang, font_names: impl Into<FontNames>) {
2681 self.set_system_ui_impl(lang, font_names.into())
2682 }
2683 fn set_system_ui_impl(&self, lang: Lang, font_names: FontNames) {
2684 UPDATES.once_update("GenericFonts.set_system_ui", move || {
2685 GENERIC_FONTS_SV.write().system_ui.insert(lang.clone(), font_names);
2686 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::system_ui(), lang)));
2687 });
2688 }
2689
2690 pub fn fallback(&self, lang: &Lang) -> FontName {
2694 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2695 }
2696
2697 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2703 self.set_fallback_impl(lang, font_name.into());
2704 }
2705 fn set_fallback_impl(&self, lang: Lang, font_name: FontName) {
2706 UPDATES.once_update("GenericFonts.set", move || {
2707 GENERIC_FONTS_SV.write().fallback.insert(lang.clone(), font_name);
2708 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang)));
2709 });
2710 }
2711
2712 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2720 match &**name {
2721 "serif" => Some(self.serif(lang)),
2722 "sans-serif" => Some(self.sans_serif(lang)),
2723 "monospace" => Some(self.monospace(lang)),
2724 "cursive" => Some(self.cursive(lang)),
2725 "fantasy" => Some(self.fantasy(lang)),
2726 _ => None,
2727 }
2728 }
2729
2730 pub fn resolve_list(&self, names: &[FontName], lang: &Lang) -> Option<FontNames> {
2734 if names
2735 .iter()
2736 .any(|n| ["system-ui", "serif", "sans-serif", "monospace", "cursive", "fantasy"].contains(&&**n))
2737 {
2738 let mut r = FontNames(Vec::with_capacity(names.len()));
2739 for name in names {
2740 match self.resolve(name, lang) {
2741 Some(n) => r.push(n),
2742 None => {
2743 if name == "system-ui" {
2744 r.extend(self.system_ui(lang));
2745 } else {
2746 r.push(name.clone())
2747 }
2748 }
2749 }
2750 }
2751 Some(r)
2752 } else {
2753 None
2754 }
2755 }
2756}
2757
2758#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2759pub(crate) enum WeakFontBytes {
2760 Ipc(WeakIpcBytes),
2761 Arc(std::sync::Weak<Vec<u8>>),
2762 Static(&'static [u8]),
2763 Mmap(std::sync::Weak<SystemFontBytes>),
2764}
2765#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2766impl WeakFontBytes {
2767 pub(crate) fn upgrade(&self) -> Option<FontBytes> {
2768 match self {
2769 WeakFontBytes::Ipc(weak) => Some(FontBytes(FontBytesImpl::Ipc(weak.upgrade()?))),
2770 WeakFontBytes::Arc(weak) => Some(FontBytes(FontBytesImpl::Arc(weak.upgrade()?))),
2771 WeakFontBytes::Static(b) => Some(FontBytes(FontBytesImpl::Static(b))),
2772 WeakFontBytes::Mmap(weak) => Some(FontBytes(FontBytesImpl::System(weak.upgrade()?))),
2773 }
2774 }
2775
2776 pub(crate) fn strong_count(&self) -> usize {
2777 match self {
2778 WeakFontBytes::Ipc(weak) => weak.strong_count(),
2779 WeakFontBytes::Arc(weak) => weak.strong_count(),
2780 WeakFontBytes::Static(_) => 1,
2781 WeakFontBytes::Mmap(weak) => weak.strong_count(),
2782 }
2783 }
2784}
2785
2786struct SystemFontBytes {
2787 path: std::path::PathBuf,
2788 mmap: IpcBytes,
2789}
2790
2791#[derive(Clone)]
2792enum FontBytesImpl {
2793 Ipc(IpcBytes),
2795 Arc(Arc<Vec<u8>>),
2796 Static(&'static [u8]),
2797 System(Arc<SystemFontBytes>),
2798}
2799#[derive(Clone)]
2801pub struct FontBytes(FontBytesImpl);
2802impl FontBytes {
2803 pub fn from_ipc(bytes: IpcBytes) -> Self {
2805 Self(FontBytesImpl::Ipc(bytes))
2806 }
2807
2808 pub fn from_vec(bytes: Vec<u8>) -> io::Result<Self> {
2810 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_vec_blocking(bytes)?)))
2811 }
2812
2813 pub fn from_static(bytes: &'static [u8]) -> Self {
2815 Self(FontBytesImpl::Static(bytes))
2816 }
2817
2818 pub fn from_arc(bytes: Arc<Vec<u8>>) -> Self {
2822 Self(FontBytesImpl::Arc(bytes))
2823 }
2824
2825 pub fn from_file(path: PathBuf) -> io::Result<Self> {
2827 let path = dunce::canonicalize(path)?;
2828
2829 #[cfg(windows)]
2830 {
2831 use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemWindowsDirectoryW};
2832 let mut buffer = [0u16; MAX_PATH as usize];
2833 let len = unsafe { GetSystemWindowsDirectoryW(Some(&mut buffer)) };
2835 let fonts_dir = String::from_utf16_lossy(&buffer[..len as usize]);
2836 if path.starts_with(fonts_dir) {
2838 return unsafe { load_from_system(path) };
2840 }
2841 }
2842 #[cfg(target_os = "macos")]
2843 if path.starts_with("/System/Library/Fonts/") || path.starts_with("/Library/Fonts/") {
2844 return unsafe { load_from_system(path) };
2846 }
2847 #[cfg(target_os = "android")]
2848 if path.starts_with("/system/fonts/") || path.starts_with("/system/font/") || path.starts_with("/system/product/fonts/") {
2849 return unsafe { load_from_system(path) };
2851 }
2852 #[cfg(unix)]
2853 if path.starts_with("/usr/share/fonts/") {
2854 return unsafe { load_from_system(path) };
2856 }
2857
2858 #[cfg(ipc)]
2859 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2860 let mmap = unsafe { IpcBytes::open_memmap_blocking(path.clone(), None) }?;
2862 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2863 }
2864
2865 #[cfg(all(not(ipc), not(target_arch = "wasm32")))]
2866 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2867 let mmap = IpcBytes::from_path_blocking(&path)?;
2868 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2869 }
2870
2871 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_path_blocking(&path)?)))
2872 }
2873
2874 #[cfg(ipc)]
2881 pub unsafe fn from_file_mmap(path: PathBuf) -> std::io::Result<Self> {
2882 let ipc = unsafe { IpcBytes::open_memmap_blocking(path, None) }?;
2884 Ok(Self(FontBytesImpl::Ipc(ipc)))
2885 }
2886
2887 #[cfg(ipc)]
2891 pub fn mmap_path(&self) -> Option<&std::path::Path> {
2892 if let FontBytesImpl::System(m) = &self.0 {
2893 Some(&m.path)
2894 } else {
2895 None
2896 }
2897 }
2898
2899 pub fn to_ipc(&self) -> io::Result<IpcFontBytes> {
2901 Ok(if let FontBytesImpl::System(m) = &self.0 {
2902 IpcFontBytes::System(m.path.clone())
2903 } else {
2904 IpcFontBytes::Bytes(self.to_ipc_bytes()?)
2905 })
2906 }
2907
2908 pub fn to_ipc_bytes(&self) -> io::Result<IpcBytes> {
2910 match &self.0 {
2911 FontBytesImpl::Ipc(b) => Ok(b.clone()),
2912 FontBytesImpl::Arc(b) => IpcBytes::from_slice_blocking(b),
2913 FontBytesImpl::Static(b) => IpcBytes::from_slice_blocking(b),
2914 FontBytesImpl::System(m) => IpcBytes::from_slice_blocking(&m.mmap[..]),
2915 }
2916 }
2917
2918 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2919 pub(crate) fn downgrade(&self) -> WeakFontBytes {
2920 match &self.0 {
2921 FontBytesImpl::Ipc(ipc) => WeakFontBytes::Ipc(ipc.downgrade()),
2922 FontBytesImpl::Arc(arc) => WeakFontBytes::Arc(Arc::downgrade(arc)),
2923 FontBytesImpl::Static(b) => WeakFontBytes::Static(b),
2924 FontBytesImpl::System(arc) => WeakFontBytes::Mmap(Arc::downgrade(arc)),
2925 }
2926 }
2927}
2928impl std::ops::Deref for FontBytes {
2929 type Target = [u8];
2930
2931 fn deref(&self) -> &Self::Target {
2932 match &self.0 {
2933 FontBytesImpl::Ipc(b) => &b[..],
2934 FontBytesImpl::Arc(b) => &b[..],
2935 FontBytesImpl::Static(b) => b,
2936 FontBytesImpl::System(m) => &m.mmap[..],
2937 }
2938 }
2939}
2940impl fmt::Debug for FontBytes {
2941 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2942 let mut b = f.debug_struct("FontBytes");
2943 b.field(
2944 ".kind",
2945 &match &self.0 {
2946 FontBytesImpl::Ipc(_) => "IpcBytes",
2947 FontBytesImpl::Arc(_) => "Arc",
2948 FontBytesImpl::Static(_) => "Static",
2949 FontBytesImpl::System(_) => "Mmap",
2950 },
2951 );
2952 b.field(".len", &(self.len() as u64).bytes());
2953 if let FontBytesImpl::System(m) = &self.0 {
2954 b.field(".path", &m.path);
2955 }
2956
2957 b.finish()
2958 }
2959}
2960
2961#[derive(Debug, Clone)]
2962enum FontSource {
2963 File(PathBuf, u32),
2964 Memory(FontBytes, u32),
2965 Alias(FontName),
2966}
2967
2968#[derive(Debug, Clone)]
2970pub struct CustomFont {
2971 name: FontName,
2972 source: FontSource,
2973 stretch: FontStretch,
2974 style: FontStyle,
2975 weight: FontWeight,
2976}
2977impl CustomFont {
2978 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
2986 CustomFont {
2987 name: name.into(),
2988 source: FontSource::File(path.into(), font_index),
2989 stretch: FontStretch::NORMAL,
2990 style: FontStyle::Normal,
2991 weight: FontWeight::NORMAL,
2992 }
2993 }
2994
2995 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontBytes, font_index: u32) -> Self {
3003 CustomFont {
3004 name: name.into(),
3005 source: FontSource::Memory(data, font_index),
3006 stretch: FontStretch::NORMAL,
3007 style: FontStyle::Normal,
3008 weight: FontWeight::NORMAL,
3009 }
3010 }
3011
3012 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
3018 CustomFont {
3019 name: name.into(),
3020 source: FontSource::Alias(other_font.into()),
3021 stretch: FontStretch::NORMAL,
3022 style: FontStyle::Normal,
3023 weight: FontWeight::NORMAL,
3024 }
3025 }
3026
3027 pub fn stretch(mut self, stretch: FontStretch) -> Self {
3031 self.stretch = stretch;
3032 self
3033 }
3034
3035 pub fn style(mut self, style: FontStyle) -> Self {
3039 self.style = style;
3040 self
3041 }
3042
3043 pub fn weight(mut self, weight: FontWeight) -> Self {
3047 self.weight = weight;
3048 self
3049 }
3050}
3051
3052#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
3056#[serde(transparent)]
3057pub struct FontStretch(pub f32);
3058impl fmt::Debug for FontStretch {
3059 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3060 let name = self.name();
3061 if name.is_empty() {
3062 f.debug_tuple("FontStretch").field(&self.0).finish()
3063 } else {
3064 if f.alternate() {
3065 write!(f, "FontStretch::")?;
3066 }
3067 write!(f, "{name}")
3068 }
3069 }
3070}
3071impl PartialOrd for FontStretch {
3072 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3073 Some(self.cmp(other))
3074 }
3075}
3076impl Ord for FontStretch {
3077 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3078 about_eq_ord(self.0, other.0, EQ_GRANULARITY)
3079 }
3080}
3081impl PartialEq for FontStretch {
3082 fn eq(&self, other: &Self) -> bool {
3083 about_eq(self.0, other.0, EQ_GRANULARITY)
3084 }
3085}
3086impl Eq for FontStretch {}
3087impl std::hash::Hash for FontStretch {
3088 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3089 about_eq_hash(self.0, EQ_GRANULARITY, state)
3090 }
3091}
3092impl Default for FontStretch {
3093 fn default() -> FontStretch {
3094 FontStretch::NORMAL
3095 }
3096}
3097impl FontStretch {
3098 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
3100 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
3102 pub const CONDENSED: FontStretch = FontStretch(0.75);
3104 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
3106 pub const NORMAL: FontStretch = FontStretch(1.0);
3108 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
3110 pub const EXPANDED: FontStretch = FontStretch(1.25);
3112 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
3114 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
3116
3117 pub fn name(self) -> &'static str {
3119 macro_rules! name {
3120 ($($CONST:ident;)+) => {$(
3121 if self == Self::$CONST {
3122 return stringify!($CONST);
3123 }
3124 )+}
3125 }
3126 name! {
3127 ULTRA_CONDENSED;
3128 EXTRA_CONDENSED;
3129 CONDENSED;
3130 SEMI_CONDENSED;
3131 NORMAL;
3132 SEMI_EXPANDED;
3133 EXPANDED;
3134 EXTRA_EXPANDED;
3135 ULTRA_EXPANDED;
3136 }
3137 ""
3138 }
3139}
3140impl_from_and_into_var! {
3141 fn from(fct: Factor) -> FontStretch {
3142 FontStretch(fct.0)
3143 }
3144 fn from(pct: FactorPercent) -> FontStretch {
3145 FontStretch(pct.fct().0)
3146 }
3147 fn from(fct: f32) -> FontStretch {
3148 FontStretch(fct)
3149 }
3150}
3151impl From<ttf_parser::Width> for FontStretch {
3152 fn from(value: ttf_parser::Width) -> Self {
3153 use ttf_parser::Width::*;
3154 match value {
3155 UltraCondensed => FontStretch::ULTRA_CONDENSED,
3156 ExtraCondensed => FontStretch::EXTRA_CONDENSED,
3157 Condensed => FontStretch::CONDENSED,
3158 SemiCondensed => FontStretch::SEMI_CONDENSED,
3159 Normal => FontStretch::NORMAL,
3160 SemiExpanded => FontStretch::SEMI_EXPANDED,
3161 Expanded => FontStretch::EXPANDED,
3162 ExtraExpanded => FontStretch::EXTRA_EXPANDED,
3163 UltraExpanded => FontStretch::ULTRA_EXPANDED,
3164 }
3165 }
3166}
3167impl From<FontStretch> for ttf_parser::Width {
3168 fn from(value: FontStretch) -> Self {
3169 if value <= FontStretch::ULTRA_CONDENSED {
3170 ttf_parser::Width::UltraCondensed
3171 } else if value <= FontStretch::EXTRA_CONDENSED {
3172 ttf_parser::Width::ExtraCondensed
3173 } else if value <= FontStretch::CONDENSED {
3174 ttf_parser::Width::Condensed
3175 } else if value <= FontStretch::SEMI_CONDENSED {
3176 ttf_parser::Width::SemiCondensed
3177 } else if value <= FontStretch::NORMAL {
3178 ttf_parser::Width::Normal
3179 } else if value <= FontStretch::SEMI_EXPANDED {
3180 ttf_parser::Width::SemiExpanded
3181 } else if value <= FontStretch::EXPANDED {
3182 ttf_parser::Width::Expanded
3183 } else if value <= FontStretch::EXTRA_EXPANDED {
3184 ttf_parser::Width::ExtraExpanded
3185 } else {
3186 ttf_parser::Width::UltraExpanded
3187 }
3188 }
3189}
3190
3191#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
3193pub enum FontStyle {
3194 #[default]
3196 Normal,
3197 Italic,
3199 Oblique,
3201}
3202impl fmt::Debug for FontStyle {
3203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3204 if f.alternate() {
3205 write!(f, "FontStyle::")?;
3206 }
3207 match self {
3208 Self::Normal => write!(f, "Normal"),
3209 Self::Italic => write!(f, "Italic"),
3210 Self::Oblique => write!(f, "Oblique"),
3211 }
3212 }
3213}
3214impl From<ttf_parser::Style> for FontStyle {
3215 fn from(value: ttf_parser::Style) -> Self {
3216 use ttf_parser::Style::*;
3217 match value {
3218 Normal => FontStyle::Normal,
3219 Italic => FontStyle::Italic,
3220 Oblique => FontStyle::Oblique,
3221 }
3222 }
3223}
3224
3225impl From<FontStyle> for ttf_parser::Style {
3226 fn from(value: FontStyle) -> Self {
3227 match value {
3228 FontStyle::Normal => Self::Normal,
3229 FontStyle::Italic => Self::Italic,
3230 FontStyle::Oblique => Self::Oblique,
3231 }
3232 }
3233}
3234
3235#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
3238pub struct FontWeight(pub f32);
3239impl Default for FontWeight {
3240 fn default() -> FontWeight {
3241 FontWeight::NORMAL
3242 }
3243}
3244impl fmt::Debug for FontWeight {
3245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3246 let name = self.name();
3247 if name.is_empty() {
3248 f.debug_tuple("FontWeight").field(&self.0).finish()
3249 } else {
3250 if f.alternate() {
3251 write!(f, "FontWeight::")?;
3252 }
3253 write!(f, "{name}")
3254 }
3255 }
3256}
3257impl PartialOrd for FontWeight {
3258 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3259 Some(self.cmp(other))
3260 }
3261}
3262impl Ord for FontWeight {
3263 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3264 about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
3265 }
3266}
3267impl PartialEq for FontWeight {
3268 fn eq(&self, other: &Self) -> bool {
3269 about_eq(self.0, other.0, EQ_GRANULARITY_100)
3270 }
3271}
3272impl Eq for FontWeight {}
3273impl std::hash::Hash for FontWeight {
3274 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3275 about_eq_hash(self.0, EQ_GRANULARITY_100, state)
3276 }
3277}
3278impl FontWeight {
3279 pub const THIN: FontWeight = FontWeight(100.0);
3281 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
3283 pub const LIGHT: FontWeight = FontWeight(300.0);
3285 pub const NORMAL: FontWeight = FontWeight(400.0);
3287 pub const MEDIUM: FontWeight = FontWeight(500.0);
3289 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
3291 pub const BOLD: FontWeight = FontWeight(700.0);
3293 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
3295 pub const BLACK: FontWeight = FontWeight(900.0);
3297
3298 pub fn name(self) -> &'static str {
3300 macro_rules! name {
3301 ($($CONST:ident;)+) => {$(
3302 if self == Self::$CONST {
3303 return stringify!($CONST);
3304 }
3305 )+}
3306 }
3307 name! {
3308 THIN;
3309 EXTRA_LIGHT;
3310 LIGHT;
3311 NORMAL;
3312 MEDIUM;
3313 SEMIBOLD;
3314 BOLD;
3315 EXTRA_BOLD;
3316 BLACK;
3317 }
3318 ""
3319 }
3320}
3321impl_from_and_into_var! {
3322 fn from(weight: u32) -> FontWeight {
3323 FontWeight(weight as f32)
3324 }
3325 fn from(weight: f32) -> FontWeight {
3326 FontWeight(weight)
3327 }
3328}
3329impl From<ttf_parser::Weight> for FontWeight {
3330 fn from(value: ttf_parser::Weight) -> Self {
3331 use ttf_parser::Weight::*;
3332 match value {
3333 Thin => FontWeight::THIN,
3334 ExtraLight => FontWeight::EXTRA_LIGHT,
3335 Light => FontWeight::LIGHT,
3336 Normal => FontWeight::NORMAL,
3337 Medium => FontWeight::MEDIUM,
3338 SemiBold => FontWeight::SEMIBOLD,
3339 Bold => FontWeight::BOLD,
3340 ExtraBold => FontWeight::EXTRA_BOLD,
3341 Black => FontWeight::BLACK,
3342 Other(o) => FontWeight(o as f32),
3343 }
3344 }
3345}
3346impl From<FontWeight> for ttf_parser::Weight {
3347 fn from(value: FontWeight) -> Self {
3348 ttf_parser::Weight::from(value.0 as u16)
3349 }
3350}
3351
3352#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3354pub enum LineBreak {
3355 Auto,
3357 Loose,
3359 Normal,
3361 Strict,
3363 Anywhere,
3365}
3366impl Default for LineBreak {
3367 fn default() -> Self {
3369 LineBreak::Auto
3370 }
3371}
3372impl fmt::Debug for LineBreak {
3373 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3374 if f.alternate() {
3375 write!(f, "LineBreak::")?;
3376 }
3377 match self {
3378 LineBreak::Auto => write!(f, "Auto"),
3379 LineBreak::Loose => write!(f, "Loose"),
3380 LineBreak::Normal => write!(f, "Normal"),
3381 LineBreak::Strict => write!(f, "Strict"),
3382 LineBreak::Anywhere => write!(f, "Anywhere"),
3383 }
3384 }
3385}
3386
3387#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3392#[non_exhaustive]
3393pub enum ParagraphBreak {
3394 #[default]
3396 None,
3397 Line,
3399}
3400impl fmt::Debug for ParagraphBreak {
3401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3402 if f.alternate() {
3403 write!(f, "ParagraphBreak::")?;
3404 }
3405 match self {
3406 ParagraphBreak::None => write!(f, "None"),
3407 ParagraphBreak::Line => write!(f, "Line"),
3408 }
3409 }
3410}
3411
3412#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3414pub enum Hyphens {
3415 None,
3417 Manual,
3422 Auto,
3424}
3425impl Default for Hyphens {
3426 fn default() -> Self {
3428 Hyphens::Auto
3429 }
3430}
3431impl fmt::Debug for Hyphens {
3432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3433 if f.alternate() {
3434 write!(f, "Hyphens::")?;
3435 }
3436 match self {
3437 Hyphens::None => write!(f, "None"),
3438 Hyphens::Manual => write!(f, "Manual"),
3439 Hyphens::Auto => write!(f, "Auto"),
3440 }
3441 }
3442}
3443
3444#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3450pub enum WordBreak {
3451 Normal,
3453 BreakAll,
3455 KeepAll,
3457}
3458impl Default for WordBreak {
3459 fn default() -> Self {
3461 WordBreak::Normal
3462 }
3463}
3464impl fmt::Debug for WordBreak {
3465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3466 if f.alternate() {
3467 write!(f, "WordBreak::")?;
3468 }
3469 match self {
3470 WordBreak::Normal => write!(f, "Normal"),
3471 WordBreak::BreakAll => write!(f, "BreakAll"),
3472 WordBreak::KeepAll => write!(f, "KeepAll"),
3473 }
3474 }
3475}
3476
3477#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3479pub enum Justify {
3480 Auto,
3484 InterWord,
3486 InterLetter,
3488}
3489impl Default for Justify {
3490 fn default() -> Self {
3492 Justify::Auto
3493 }
3494}
3495impl Justify {
3496 pub fn resolve(self, lang: &Lang) -> Self {
3498 match self {
3499 Self::Auto => match lang.language.as_str() {
3500 "zh" | "ja" | "ko" => Self::InterLetter,
3501 _ => Self::InterWord,
3502 },
3503 m => m,
3504 }
3505 }
3506}
3507impl fmt::Debug for Justify {
3508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3509 if f.alternate() {
3510 write!(f, "Justify::")?;
3511 }
3512 match self {
3513 Justify::Auto => write!(f, "Auto"),
3514 Justify::InterWord => write!(f, "InterWord"),
3515 Justify::InterLetter => write!(f, "InterLetter"),
3516 }
3517 }
3518}
3519
3520#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3528#[non_exhaustive]
3529pub struct FontFaceMetrics {
3530 pub units_per_em: u32,
3534
3535 pub ascent: f32,
3537
3538 pub descent: f32,
3544
3545 pub line_gap: f32,
3547
3548 pub underline_position: f32,
3551
3552 pub underline_thickness: f32,
3554
3555 pub cap_height: f32,
3557
3558 pub x_height: f32,
3561
3562 pub bounds: euclid::Rect<f32, ()>,
3566}
3567impl FontFaceMetrics {
3568 pub fn sized(&self, font_size_px: Px) -> FontMetrics {
3570 let size_scale = 1.0 / self.units_per_em as f32 * font_size_px.0 as f32;
3571 let s = move |f: f32| Px((f * size_scale).round() as i32);
3572 FontMetrics {
3573 size_scale,
3574 ascent: s(self.ascent),
3575 descent: s(self.descent),
3576 line_gap: s(self.line_gap),
3577 underline_position: s(self.underline_position),
3578 underline_thickness: s(self.underline_thickness),
3579 cap_height: s(self.cap_height),
3580 x_height: (s(self.x_height)),
3581 bounds: {
3582 let b = self.bounds;
3583 PxRect::new(
3584 PxPoint::new(s(b.origin.x), s(b.origin.y)),
3585 PxSize::new(s(b.size.width), s(b.size.height)),
3586 )
3587 },
3588 }
3589 }
3590}
3591
3592#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3596#[non_exhaustive]
3597pub struct FontMetrics {
3598 pub size_scale: f32,
3600
3601 pub ascent: Px,
3603
3604 pub descent: Px,
3610
3611 pub line_gap: Px,
3613
3614 pub underline_position: Px,
3617
3618 pub underline_thickness: Px,
3620
3621 pub cap_height: Px,
3623
3624 pub x_height: Px,
3626
3627 pub bounds: PxRect,
3631}
3632impl FontMetrics {
3633 pub fn line_height(&self) -> Px {
3635 self.ascent - self.descent + self.line_gap
3636 }
3637}
3638
3639#[derive(Clone)]
3641pub enum TextTransformFn {
3642 None,
3644 Uppercase,
3646 Lowercase,
3648 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3650}
3651impl TextTransformFn {
3652 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3656 match self {
3657 TextTransformFn::None => Cow::Borrowed(text),
3658 TextTransformFn::Uppercase => {
3659 if text.chars().any(|c| !c.is_uppercase()) {
3660 Cow::Owned(text.to_uppercase().into())
3661 } else {
3662 Cow::Borrowed(text)
3663 }
3664 }
3665 TextTransformFn::Lowercase => {
3666 if text.chars().any(|c| !c.is_lowercase()) {
3667 Cow::Owned(text.to_lowercase().into())
3668 } else {
3669 Cow::Borrowed(text)
3670 }
3671 }
3672 TextTransformFn::Custom(fn_) => fn_(text),
3673 }
3674 }
3675
3676 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3678 TextTransformFn::Custom(Arc::new(fn_))
3679 }
3680}
3681impl fmt::Debug for TextTransformFn {
3682 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3683 if f.alternate() {
3684 write!(f, "TextTransformFn::")?;
3685 }
3686 match self {
3687 TextTransformFn::None => write!(f, "None"),
3688 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3689 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3690 TextTransformFn::Custom(_) => write!(f, "Custom"),
3691 }
3692 }
3693}
3694impl PartialEq for TextTransformFn {
3695 fn eq(&self, other: &Self) -> bool {
3696 match (self, other) {
3697 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3698 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3699 }
3700 }
3701}
3702
3703#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3705pub enum WhiteSpace {
3706 #[default]
3708 Preserve,
3709 Merge,
3712 MergeParagraph,
3715 MergeAll,
3717}
3718impl WhiteSpace {
3719 pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3723 match self {
3724 WhiteSpace::Preserve => Cow::Borrowed(text),
3725 WhiteSpace::Merge => {
3726 let mut prev_i = 0;
3728 for line in text.split_inclusive('\n') {
3729 let line_exclusive = line.trim_end_matches('\n').trim_end_matches('\r');
3731 let line_trim = line_exclusive.trim();
3732 let mut merge = line_trim.len() != line_exclusive.len() || line_trim.is_empty();
3733
3734 if !merge {
3736 let mut prev_is_space = true; for c in line.chars() {
3738 let is_space = c.is_whitespace();
3739 if prev_is_space && is_space {
3740 merge = true;
3741 break;
3742 }
3743 prev_is_space = is_space;
3744 }
3745 }
3746
3747 if !merge {
3748 prev_i += line.len();
3749 continue;
3750 }
3751
3752 let mut out = String::with_capacity(text.len() - 1);
3754 out.push_str(&text[..prev_i]);
3755
3756 let mut chars = text[prev_i..].chars();
3757 let mut prev_is_space = true;
3758 let mut prev_is_break = true;
3759 while let Some(c) = chars.next() {
3760 if c == '\r'
3761 && let Some(nc) = chars.next()
3762 {
3763 if nc == '\n' {
3764 if !prev_is_break && !out.is_empty() {
3765 out.push('\n');
3766 }
3767 prev_is_break = true;
3768 prev_is_space = true;
3769 } else {
3770 out.push(c);
3771 out.push(nc);
3772 prev_is_break = false;
3773 prev_is_space = nc.is_whitespace();
3774 }
3775 } else if c == '\n' {
3776 if !prev_is_break && !out.is_empty() {
3777 out.push('\n');
3778 }
3779 prev_is_break = true;
3780 prev_is_space = true;
3781 } else if c.is_whitespace() {
3782 if prev_is_space {
3783 continue;
3784 }
3785 out.push(' ');
3786 prev_is_space = true;
3787 } else {
3788 out.push(c);
3789 prev_is_space = false;
3790 prev_is_break = false;
3791 }
3792 }
3793
3794 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3796 out.truncate(i + c.len_utf8());
3797 }
3798
3799 return Cow::Owned(out.into());
3800 }
3801 Cow::Borrowed(text)
3802 }
3803 WhiteSpace::MergeParagraph => {
3804 let mut merge = text.contains('\n') || text.chars().last().unwrap_or('\0').is_whitespace();
3807 if !merge {
3808 let mut prev_is_space = true;
3809 for c in text.chars() {
3810 let is_space = c.is_whitespace();
3811 if prev_is_space && is_space {
3812 merge = true;
3813 break;
3814 }
3815 prev_is_space = is_space;
3816 }
3817 }
3818
3819 if merge {
3820 let mut out = String::with_capacity(text.len());
3821 let mut prev_is_break = false;
3822 for line in text.lines() {
3823 let line = line.trim();
3824 let is_break = line.is_empty();
3825 if !prev_is_break && is_break && !out.is_empty() {
3826 out.push('\n');
3827 }
3828 if !prev_is_break && !is_break && !out.is_empty() {
3829 out.push(' ');
3830 }
3831 prev_is_break = is_break;
3832
3833 let mut prev_is_space = false;
3834 for c in line.chars() {
3835 let is_space = c.is_whitespace();
3836 if is_space {
3837 if !prev_is_space {
3838 out.push(' ');
3839 }
3840 } else {
3841 out.push(c);
3842 }
3843 prev_is_space = is_space;
3844 }
3845 }
3846
3847 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3849 out.truncate(i + c.len_utf8());
3850 }
3851
3852 return Cow::Owned(out.into());
3853 }
3854 Cow::Borrowed(text)
3855 }
3856 WhiteSpace::MergeAll => {
3857 let mut prev_i = 0;
3859 let mut prev_is_space = true; for (i, c) in text.char_indices() {
3861 let is_space = c.is_whitespace();
3862 if prev_is_space && is_space || c == '\n' {
3863 if !prev_is_space {
3864 debug_assert_eq!(c, '\n');
3865 prev_i += c.len_utf8();
3866 prev_is_space = true;
3867 }
3868 let mut out = String::with_capacity(text.len() - 1);
3870 out.push_str(&text[..prev_i]);
3872 if !out.is_empty() {
3873 out.push(' ');
3874 }
3875 for c in text[(i + c.len_utf8())..].chars() {
3877 let is_space = c.is_whitespace();
3878 if prev_is_space && is_space {
3879 continue;
3880 }
3881 out.push(if is_space { ' ' } else { c });
3882 prev_is_space = is_space;
3883 }
3884
3885 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3887 out.truncate(i + c.len_utf8());
3888 }
3889
3890 return Cow::Owned(out.into());
3891 }
3892 prev_i = i;
3893 prev_is_space = is_space;
3894 }
3895
3896 let out = text.trim_end();
3900 if out.len() != text.len() {
3901 return Cow::Owned(Txt::from_str(out));
3902 }
3903
3904 Cow::Borrowed(text)
3905 }
3906 }
3907 }
3908}
3909impl fmt::Debug for WhiteSpace {
3910 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3911 if f.alternate() {
3912 write!(f, "WhiteSpace::")?;
3913 }
3914 match self {
3915 WhiteSpace::Preserve => write!(f, "Preserve"),
3916 WhiteSpace::Merge => write!(f, "Merge"),
3917 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3918 WhiteSpace::MergeParagraph => write!(f, "MergeParagraph"),
3919 }
3920 }
3921}
3922
3923#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3925pub struct CaretIndex {
3926 pub index: usize,
3930 pub line: usize,
3940}
3941
3942impl PartialEq for CaretIndex {
3943 fn eq(&self, other: &Self) -> bool {
3944 self.index == other.index
3945 }
3946}
3947impl Eq for CaretIndex {}
3948impl CaretIndex {
3949 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3951}
3952impl PartialOrd for CaretIndex {
3953 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3954 Some(self.cmp(other))
3955 }
3956}
3957impl Ord for CaretIndex {
3958 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3959 self.index.cmp(&other.index)
3960 }
3961}
3962impl_from_and_into_var! {
3963 fn from(index: usize) -> CaretIndex {
3964 CaretIndex { index, line: 0 }
3965 }
3966}
3967
3968#[derive(Debug, Clone)]
3970#[non_exhaustive]
3971pub enum FontLoadingError {
3972 UnknownFormat,
3974 NoSuchFontInCollection,
3979 Parse(ttf_parser::FaceParsingError),
3981 NoFilesystem,
3984 Io(Arc<std::io::Error>),
3986}
3987impl PartialEq for FontLoadingError {
3988 fn eq(&self, other: &Self) -> bool {
3989 match (self, other) {
3990 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
3991 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3992 }
3993 }
3994}
3995impl From<std::io::Error> for FontLoadingError {
3996 fn from(error: std::io::Error) -> FontLoadingError {
3997 Self::Io(Arc::new(error))
3998 }
3999}
4000impl fmt::Display for FontLoadingError {
4001 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4002 match self {
4003 Self::UnknownFormat => write!(f, "unknown format"),
4004 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
4005 Self::NoFilesystem => write!(f, "no filesystem present"),
4006 Self::Parse(e) => fmt::Display::fmt(e, f),
4007 Self::Io(e) => fmt::Display::fmt(e, f),
4008 }
4009 }
4010}
4011impl std::error::Error for FontLoadingError {
4012 fn cause(&self) -> Option<&dyn std::error::Error> {
4013 match self {
4014 FontLoadingError::Parse(e) => Some(e),
4015 FontLoadingError::Io(e) => Some(e),
4016 _ => None,
4017 }
4018 }
4019}
4020
4021#[cfg(test)]
4022mod tests {
4023 use zng_app::APP;
4024
4025 use super::*;
4026
4027 #[test]
4028 fn generic_fonts_default() {
4029 let _app = APP.minimal().run_headless(false);
4030
4031 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
4032 }
4033
4034 #[test]
4035 fn generic_fonts_fallback() {
4036 let _app = APP.minimal().run_headless(false);
4037
4038 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
4039 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
4040 }
4041
4042 #[test]
4043 fn generic_fonts_get1() {
4044 let mut app = APP.minimal().run_headless(false);
4045 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
4046 app.update(false).assert_wait();
4047
4048 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
4049 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
4050 }
4051
4052 #[test]
4053 fn generic_fonts_get2() {
4054 let mut app = APP.minimal().run_headless(false);
4055 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
4056 app.update(false).assert_wait();
4057
4058 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
4059 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
4060 }
4061
4062 #[test]
4063 fn generic_fonts_get_best() {
4064 let mut app = APP.minimal().run_headless(false);
4065 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
4066 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
4067 app.update(false).assert_wait();
4068
4069 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
4070 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
4071 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
4072 }
4073
4074 #[test]
4075 fn generic_fonts_get_no_lang_match() {
4076 let mut app = APP.minimal().run_headless(false);
4077 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
4078 app.update(false).assert_wait();
4079
4080 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
4081 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
4082 }
4083
4084 #[test]
4085 fn white_space_merge() {
4086 macro_rules! test {
4087 ($input:tt, $output:tt) => {
4088 let input = Txt::from($input);
4089 let output = WhiteSpace::Merge.transform(&input);
4090 assert_eq!($output, output.as_str());
4091
4092 let input = input.replace('\n', "\r\n");
4093 let output = WhiteSpace::Merge.transform(&Txt::from(input)).replace("\r\n", "\n");
4094 assert_eq!($output, output.as_str());
4095 };
4096 }
4097 test!("a b\n\nc", "a b\nc");
4098 test!("a b\nc", "a b\nc");
4099 test!(" a b\nc\n \n", "a b\nc");
4100 test!(" \n a b\nc", "a b\nc");
4101 test!("a\n \nb", "a\nb");
4102 }
4103
4104 #[test]
4105 fn white_space_merge_paragraph() {
4106 macro_rules! test {
4107 ($input:tt, $output:tt) => {
4108 let input = Txt::from($input);
4109 let output = WhiteSpace::MergeParagraph.transform(&input);
4110 assert_eq!($output, output.as_str());
4111
4112 let input = input.replace('\n', "\r\n");
4113 let output = WhiteSpace::MergeParagraph.transform(&Txt::from(input)).replace("\r\n", "\n");
4114 assert_eq!($output, output.as_str());
4115 };
4116 }
4117 test!("a b\n\nc", "a b\nc");
4118 test!("a b\nc", "a b c");
4119 test!(" a b\nc\n \n", "a b c");
4120 test!(" \n a b\nc", "a b c");
4121 test!("a\n \nb", "a\nb");
4122 }
4123
4124 #[test]
4125 fn white_space_merge_all() {
4126 macro_rules! test {
4127 ($input:tt, $output:tt) => {
4128 let input = Txt::from($input);
4129 let output = WhiteSpace::MergeAll.transform(&input);
4130 assert_eq!($output, output.as_str());
4131 };
4132 }
4133 test!("a b\n\nc", "a b c");
4134 test!("a b\nc", "a b c");
4135 test!(" a b\nc\n \n", "a b c");
4136 test!(" \n a b\nc", "a b c");
4137 test!("a\n \nb", "a b");
4138 }
4139}