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 parking_lot::{Mutex, RwLock};
63use pastey::paste;
64use zng_app::{
65 event::{event, event_args},
66 render::FontSynthesis,
67 update::UPDATES,
68 view_process::{
69 VIEW_PROCESS_INITED_EVENT, ViewRenderer,
70 raw_events::{RAW_FONT_AA_CHANGED_EVENT, RAW_FONT_CHANGED_EVENT},
71 },
72};
73use zng_app_context::app_local;
74use zng_ext_l10n::{Lang, LangMap, lang};
75use zng_layout::unit::{
76 ByteUnits as _, EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorPercent, Px, PxPoint, PxRect, PxSize, TimeUnits as _, about_eq,
77 about_eq_hash, about_eq_ord, euclid,
78};
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 #[deprecated = "use `FONTS.generics().system_ui`"]
339 pub fn windows_ui(lang: &Lang) -> Self {
340 FONTS.generics().system_ui(lang)
341 }
342
343 #[deprecated = "use `FONTS.generics().system_ui`"]
345 pub fn mac_ui(lang: &Lang) -> Self {
346 FONTS.generics().system_ui(lang)
347 }
348
349 #[deprecated = "use `FONTS.generics().system_ui`"]
351 pub fn linux_ui(lang: &Lang) -> Self {
352 FONTS.generics().system_ui(lang)
353 }
354
355 #[deprecated = "use `FONTS.generics().system_ui`"]
357 pub fn system_ui(lang: &Lang) -> Self {
358 FONTS.generics().system_ui(lang)
359 }
360
361 pub fn push(&mut self, font_name: impl Into<FontName>) {
363 self.0.push(font_name.into())
364 }
365}
366impl Default for FontNames {
367 fn default() -> Self {
368 FontName::system_ui().into()
369 }
370}
371impl fmt::Debug for FontNames {
372 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373 if f.alternate() {
374 f.debug_tuple("FontNames").field(&self.0).finish()
375 } else if self.0.is_empty() {
376 write!(f, "[]")
377 } else if self.0.len() == 1 {
378 write!(f, "{:?}", self.0[0])
379 } else {
380 write!(f, "[{:?}, ", self.0[0])?;
381 for name in &self.0[1..] {
382 write!(f, "{name:?}, ")?;
383 }
384 write!(f, "]")
385 }
386 }
387}
388impl fmt::Display for FontNames {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 let mut iter = self.0.iter();
391
392 if let Some(name) = iter.next() {
393 write!(f, "{name}")?;
394 for name in iter {
395 write!(f, ", {name}")?;
396 }
397 }
398
399 Ok(())
400 }
401}
402impl_from_and_into_var! {
403 fn from(font_name: &'static str) -> FontNames {
404 FontNames(vec![FontName::new(font_name)])
405 }
406
407 fn from(font_name: String) -> FontNames {
408 FontNames(vec![FontName::new(font_name)])
409 }
410
411 fn from(font_name: Txt) -> FontNames {
412 FontNames(vec![FontName::new(font_name)])
413 }
414
415 fn from(font_names: Vec<FontName>) -> FontNames {
416 FontNames(font_names)
417 }
418
419 fn from(font_names: Vec<&'static str>) -> FontNames {
420 FontNames(font_names.into_iter().map(FontName::new).collect())
421 }
422
423 fn from(font_names: Vec<String>) -> FontNames {
424 FontNames(font_names.into_iter().map(FontName::new).collect())
425 }
426
427 fn from(font_name: FontName) -> FontNames {
428 FontNames(vec![font_name])
429 }
430}
431impl ops::Deref for FontNames {
432 type Target = Vec<FontName>;
433
434 fn deref(&self) -> &Self::Target {
435 &self.0
436 }
437}
438impl ops::DerefMut for FontNames {
439 fn deref_mut(&mut self) -> &mut Self::Target {
440 &mut self.0
441 }
442}
443impl std::iter::Extend<FontName> for FontNames {
444 fn extend<T: IntoIterator<Item = FontName>>(&mut self, iter: T) {
445 self.0.extend(iter)
446 }
447}
448impl IntoIterator for FontNames {
449 type Item = FontName;
450
451 type IntoIter = std::vec::IntoIter<FontName>;
452
453 fn into_iter(self) -> Self::IntoIter {
454 self.0.into_iter()
455 }
456}
457impl<const N: usize> From<[FontName; N]> for FontNames {
458 fn from(font_names: [FontName; N]) -> Self {
459 FontNames(font_names.into())
460 }
461}
462impl<const N: usize> IntoVar<FontNames> for [FontName; N] {
463 fn into_var(self) -> Var<FontNames> {
464 const_var(self.into())
465 }
466}
467impl<const N: usize> From<[&'static str; N]> for FontNames {
468 fn from(font_names: [&'static str; N]) -> Self {
469 FontNames(font_names.into_iter().map(FontName::new).collect())
470 }
471}
472impl<const N: usize> IntoVar<FontNames> for [&'static str; N] {
473 fn into_var(self) -> Var<FontNames> {
474 const_var(self.into())
475 }
476}
477impl<const N: usize> From<[String; N]> for FontNames {
478 fn from(font_names: [String; N]) -> Self {
479 FontNames(font_names.into_iter().map(FontName::new).collect())
480 }
481}
482impl<const N: usize> IntoVar<FontNames> for [String; N] {
483 fn into_var(self) -> Var<FontNames> {
484 const_var(self.into())
485 }
486}
487impl<const N: usize> From<[Txt; N]> for FontNames {
488 fn from(font_names: [Txt; N]) -> Self {
489 FontNames(font_names.into_iter().map(FontName::new).collect())
490 }
491}
492impl<const N: usize> IntoVar<FontNames> for [Txt; N] {
493 fn into_var(self) -> Var<FontNames> {
494 const_var(self.into())
495 }
496}
497
498event! {
499 pub static FONT_CHANGED_EVENT: FontChangedArgs;
510}
511
512event_args! {
513 pub struct FontChangedArgs {
515 pub change: FontChange,
517
518 ..
519
520 fn is_in_target(&self, id: WidgetId) -> bool {
522 true
523 }
524 }
525}
526
527#[derive(Clone, Debug, PartialEq)]
529pub enum FontChange {
530 SystemFonts,
534
535 CustomFonts,
540
541 Refresh,
545
546 GenericFont(FontName, Lang),
552
553 Fallback(Lang),
555}
556
557app_local! {
558 static FONTS_SV: FontsService = FontsService::new();
559}
560
561struct FontsService {
562 loader: FontFaceLoader,
563}
564impl FontsService {
565 fn new() -> Self {
566 let s = FontsService {
567 loader: FontFaceLoader::new(),
568 };
569
570 RAW_FONT_CHANGED_EVENT
572 .hook(|args| {
573 FONT_CHANGED_EVENT.notify(FontChangedArgs::new(
574 args.timestamp,
575 args.propagation.clone(),
576 FontChange::SystemFonts,
577 ));
578 true
579 })
580 .perm();
581
582 FONT_CHANGED_EVENT
584 .hook(|_| {
585 let mut s = FONTS_SV.write();
586 s.loader.on_refresh();
587 true
588 })
589 .perm();
590
591 VIEW_PROCESS_INITED_EVENT
593 .hook(|args| {
594 if args.is_respawn {
595 FONTS_SV.write().loader.on_view_process_respawn();
596 }
597 true
598 })
599 .perm();
600
601 s
602 }
603}
604
605pub struct FONTS;
607impl FONTS {
608 pub fn refresh(&self) {
612 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Refresh));
613 }
614
615 pub fn prune(&self) {
617 UPDATES.once_update("FONTS.prune", move || {
618 FONTS_SV.write().loader.on_prune();
619 });
620 }
621
622 pub fn generics(&self) -> &'static GenericFonts {
624 &GenericFonts {}
625 }
626
627 pub fn register(&self, custom_font: CustomFont) -> ResponseVar<Result<FontFace, FontLoadingError>> {
636 let resp = task::respond(FontFace::load_custom(custom_font));
638
639 resp.hook(|args| {
641 if let Some(done) = args.value().done() {
642 if let Ok(face) = done {
643 let mut fonts = FONTS_SV.write();
644 let family = fonts.loader.custom_fonts.entry(face.0.family_name.clone()).or_default();
645 let existing = family
646 .iter()
647 .position(|f| f.0.weight == face.0.weight && f.0.style == face.0.style && f.0.stretch == face.0.stretch);
648
649 if let Some(i) = existing {
650 family[i] = face.clone();
651 } else {
652 family.push(face.clone());
653 }
654
655 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
656 }
657 false
658 } else {
659 true
660 }
661 })
662 .perm();
663
664 resp
665 }
666
667 pub fn unregister(&self, custom_family: FontName) -> ResponseVar<bool> {
671 let (responder, response) = response_var();
672
673 UPDATES.once_update("FONTS.unregister", move || {
674 let mut fonts = FONTS_SV.write();
675 let r = if let Some(removed) = fonts.loader.custom_fonts.remove(&custom_family) {
676 for removed in removed {
680 removed.on_refresh();
681 }
682
683 true
684 } else {
685 false
686 };
687 responder.respond(r);
688
689 if r {
690 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::CustomFonts));
691 }
692 });
693
694 response
695 }
696
697 pub fn list(
699 &self,
700 families: &[FontName],
701 style: FontStyle,
702 weight: FontWeight,
703 stretch: FontStretch,
704 lang: &Lang,
705 ) -> ResponseVar<FontFaceList> {
706 if let Some(cached) = FONTS_SV.read().loader.try_list(families, style, weight, stretch, lang) {
708 tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) found cached");
709 return cached;
710 }
711 tracing::trace!("font list ({families:?} {style:?} {weight:?} {stretch:?} {lang:?}) not cached, load");
712 FONTS_SV.write().loader.load_list(families, style, weight, stretch, lang)
714 }
715
716 pub fn find(
718 &self,
719 family: &FontName,
720 style: FontStyle,
721 weight: FontWeight,
722 stretch: FontStretch,
723 lang: &Lang,
724 ) -> ResponseVar<Option<FontFace>> {
725 let resolved = GenericFonts {}.resolve(family, lang);
726 let family = resolved.as_ref().unwrap_or(family);
727
728 if let Some(cached) = FONTS_SV.read().loader.try_resolved(family, style, weight, stretch) {
730 return cached;
731 }
732 FONTS_SV.write().loader.load_resolved(family, style, weight, stretch)
734 }
735
736 pub fn normal(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
738 self.find(family, FontStyle::Normal, FontWeight::NORMAL, FontStretch::NORMAL, lang)
739 }
740
741 pub fn italic(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
743 self.find(family, FontStyle::Italic, FontWeight::NORMAL, FontStretch::NORMAL, lang)
744 }
745
746 pub fn bold(&self, family: &FontName, lang: &Lang) -> ResponseVar<Option<FontFace>> {
748 self.find(family, FontStyle::Normal, FontWeight::BOLD, FontStretch::NORMAL, lang)
749 }
750
751 pub fn custom_fonts(&self) -> Vec<FontName> {
753 FONTS_SV.read().loader.custom_fonts.keys().cloned().collect()
754 }
755
756 pub fn system_fonts(&self) -> ResponseVar<Vec<FontName>> {
760 query_util::system_all()
761 }
762
763 pub fn system_font_aa(&self) -> Var<FontAntiAliasing> {
767 RAW_FONT_AA_CHANGED_EVENT.var_map(|a| Some(a.aa), || FontAntiAliasing::Default)
768 }
769}
770
771impl<'a> From<ttf_parser::Face<'a>> for FontFaceMetrics {
772 fn from(f: ttf_parser::Face<'a>) -> Self {
773 let underline = f
774 .underline_metrics()
775 .unwrap_or(ttf_parser::LineMetrics { position: 0, thickness: 0 });
776 FontFaceMetrics {
777 units_per_em: f.units_per_em() as _,
778 ascent: f.ascender() as f32,
779 descent: f.descender() as f32,
780 line_gap: f.line_gap() as f32,
781 underline_position: underline.position as f32,
782 underline_thickness: underline.thickness as f32,
783 cap_height: f.capital_height().unwrap_or(0) as f32,
784 x_height: f.x_height().unwrap_or(0) as f32,
785 bounds: euclid::rect(
786 f.global_bounding_box().x_min as f32,
787 f.global_bounding_box().x_max as f32,
788 f.global_bounding_box().width() as f32,
789 f.global_bounding_box().height() as f32,
790 ),
791 }
792 }
793}
794
795#[derive(PartialEq, Eq, Hash)]
796struct FontInstanceKey(Px, Box<[(ttf_parser::Tag, i32)]>);
797impl FontInstanceKey {
798 pub fn new(size: Px, variations: &[rustybuzz::Variation]) -> Self {
800 let variations_key: Vec<_> = variations.iter().map(|p| (p.tag, (p.value * 1000.0) as i32)).collect();
801 FontInstanceKey(size, variations_key.into_boxed_slice())
802 }
803}
804
805#[derive(Clone)]
812pub struct FontFace(Arc<LoadedFontFace>);
813struct LoadedFontFace {
814 data: FontBytes,
815 face_index: u32,
816 display_name: FontName,
817 family_name: FontName,
818 postscript_name: Option<Txt>,
819 style: FontStyle,
820 weight: FontWeight,
821 stretch: FontStretch,
822 metrics: FontFaceMetrics,
823 lig_carets: LigatureCaretList,
824 flags: FontFaceFlags,
825 m: Mutex<FontFaceMut>,
826}
827bitflags! {
828 #[derive(Debug, Clone, Copy)]
829 struct FontFaceFlags: u8 {
830 const IS_MONOSPACE = 0b0000_0001;
831 const HAS_LIGATURES = 0b0000_0010;
832 const HAS_RASTER_IMAGES = 0b0000_0100;
833 const HAS_SVG_IMAGES = 0b0000_1000;
834 }
835}
836struct FontFaceMut {
837 instances: HashMap<FontInstanceKey, Font>,
838 render_ids: Vec<RenderFontFace>,
839 unregistered: bool,
840}
841
842impl fmt::Debug for FontFace {
843 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
844 let m = self.0.m.lock();
845 f.debug_struct("FontFace")
846 .field("display_name", &self.0.display_name)
847 .field("family_name", &self.0.family_name)
848 .field("postscript_name", &self.0.postscript_name)
849 .field("flags", &self.0.flags)
850 .field("style", &self.0.style)
851 .field("weight", &self.0.weight)
852 .field("stretch", &self.0.stretch)
853 .field("metrics", &self.0.metrics)
854 .field("instances.len()", &m.instances.len())
855 .field("render_keys.len()", &m.render_ids.len())
856 .field("unregistered", &m.unregistered)
857 .finish_non_exhaustive()
858 }
859}
860impl PartialEq for FontFace {
861 fn eq(&self, other: &Self) -> bool {
862 Arc::ptr_eq(&self.0, &other.0)
863 }
864}
865impl Eq for FontFace {}
866impl FontFace {
867 pub fn empty() -> Self {
869 FontFace(Arc::new(LoadedFontFace {
870 data: FontBytes::from_static(&[]),
871 face_index: 0,
872 display_name: FontName::from("<empty>"),
873 family_name: FontName::from("<empty>"),
874 postscript_name: None,
875 flags: FontFaceFlags::IS_MONOSPACE,
876 style: FontStyle::Normal,
877 weight: FontWeight::NORMAL,
878 stretch: FontStretch::NORMAL,
879 metrics: FontFaceMetrics {
881 units_per_em: 2048,
882 ascent: 1616.0,
883 descent: -432.0,
884 line_gap: 0.0,
885 underline_position: -205.0,
886 underline_thickness: 102.0,
887 cap_height: 1616.0,
888 x_height: 1616.0,
889 bounds: euclid::Box2D::new(euclid::point2(0.0, -432.0), euclid::point2(1291.0, 1616.0)).to_rect(),
891 },
892 lig_carets: LigatureCaretList::empty(),
893 m: Mutex::new(FontFaceMut {
894 instances: HashMap::default(),
895 render_ids: vec![],
896 unregistered: false,
897 }),
898 }))
899 }
900
901 pub fn is_empty(&self) -> bool {
903 self.0.data.is_empty()
904 }
905
906 async fn load_custom(custom_font: CustomFont) -> Result<Self, FontLoadingError> {
907 let bytes;
908 let mut face_index;
909
910 match custom_font.source {
911 FontSource::File(path, index) => {
912 bytes = task::wait(|| FontBytes::from_file(path)).await?;
913 face_index = index;
914 }
915 FontSource::Memory(arc, index) => {
916 bytes = arc;
917 face_index = index;
918 }
919 FontSource::Alias(other_font) => {
920 let result = FONTS_SV
921 .write()
922 .loader
923 .load_resolved(&other_font, custom_font.style, custom_font.weight, custom_font.stretch);
924 return match result.wait_rsp().await {
925 Some(other_font) => Ok(FontFace(Arc::new(LoadedFontFace {
926 data: other_font.0.data.clone(),
927 face_index: other_font.0.face_index,
928 display_name: custom_font.name.clone(),
929 family_name: custom_font.name,
930 postscript_name: None,
931 style: other_font.0.style,
932 weight: other_font.0.weight,
933 stretch: other_font.0.stretch,
934 metrics: other_font.0.metrics.clone(),
935 m: Mutex::new(FontFaceMut {
936 instances: Default::default(),
937 render_ids: Default::default(),
938 unregistered: Default::default(),
939 }),
940 lig_carets: other_font.0.lig_carets.clone(),
941 flags: other_font.0.flags,
942 }))),
943 None => Err(FontLoadingError::NoSuchFontInCollection),
944 };
945 }
946 }
947
948 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
949 Ok(f) => f,
950 Err(e) => {
951 match e {
952 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
954 e => return Err(FontLoadingError::Parse(e)),
955 }
956
957 match ttf_parser::Face::parse(&bytes, face_index) {
958 Ok(f) => f,
959 Err(_) => return Err(FontLoadingError::Parse(e)),
960 }
961 }
962 };
963
964 let has_ligatures = ttf_face.tables().gsub.is_some();
965 let lig_carets = if has_ligatures {
966 LigatureCaretList::empty()
967 } else {
968 LigatureCaretList::load(ttf_face.raw_face())?
969 };
970
971 let has_raster_images = {
973 let t = ttf_face.tables();
974 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
975 };
976
977 let mut flags = FontFaceFlags::empty();
978 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
979 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
980 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
981 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
982
983 Ok(FontFace(Arc::new(LoadedFontFace {
984 face_index,
985 display_name: custom_font.name.clone(),
986 family_name: custom_font.name,
987 postscript_name: None,
988 style: custom_font.style,
989 weight: custom_font.weight,
990 stretch: custom_font.stretch,
991 metrics: ttf_face.into(),
992 lig_carets,
993 m: Mutex::new(FontFaceMut {
994 instances: Default::default(),
995 render_ids: Default::default(),
996 unregistered: Default::default(),
997 }),
998 data: bytes,
999 flags,
1000 })))
1001 }
1002
1003 fn load(bytes: FontBytes, mut face_index: u32) -> Result<Self, FontLoadingError> {
1004 let _span = tracing::trace_span!("FontFace::load").entered();
1005
1006 let ttf_face = match ttf_parser::Face::parse(&bytes, face_index) {
1007 Ok(f) => f,
1008 Err(e) => {
1009 match e {
1010 ttf_parser::FaceParsingError::FaceIndexOutOfBounds => face_index = 0,
1012 e => return Err(FontLoadingError::Parse(e)),
1013 }
1014
1015 match ttf_parser::Face::parse(&bytes, face_index) {
1016 Ok(f) => f,
1017 Err(_) => return Err(FontLoadingError::Parse(e)),
1018 }
1019 }
1020 };
1021
1022 let has_ligatures = ttf_face.tables().gsub.is_some();
1023 let lig_carets = if has_ligatures {
1024 LigatureCaretList::empty()
1025 } else {
1026 LigatureCaretList::load(ttf_face.raw_face())?
1027 };
1028
1029 let mut display_name = None;
1030 let mut family_name = None;
1031 let mut postscript_name = None;
1032 let mut any_name = None::<String>;
1033 for name in ttf_face.names() {
1034 if let Some(n) = name.to_string() {
1035 match name.name_id {
1036 ttf_parser::name_id::FULL_NAME => display_name = Some(n),
1037 ttf_parser::name_id::FAMILY => family_name = Some(n),
1038 ttf_parser::name_id::POST_SCRIPT_NAME => postscript_name = Some(n),
1039 _ => match &mut any_name {
1040 Some(s) => {
1041 if n.len() > s.len() {
1042 *s = n;
1043 }
1044 }
1045 None => any_name = Some(n),
1046 },
1047 }
1048 }
1049 }
1050 let display_name = FontName::new(Txt::from_str(
1051 display_name
1052 .as_ref()
1053 .or(family_name.as_ref())
1054 .or(postscript_name.as_ref())
1055 .or(any_name.as_ref())
1056 .unwrap(),
1057 ));
1058 let family_name = family_name.map(FontName::from).unwrap_or_else(|| display_name.clone());
1059 let postscript_name = postscript_name.map(Txt::from);
1060
1061 if ttf_face.units_per_em() == 0 {
1062 tracing::debug!("font {display_name:?} units_per_em 0");
1064 return Err(FontLoadingError::UnknownFormat);
1065 }
1066
1067 let has_raster_images = {
1069 let t = ttf_face.tables();
1070 t.sbix.is_some() || t.bdat.is_some() || t.ebdt.is_some() || t.cbdt.is_some()
1071 };
1072
1073 let mut flags = FontFaceFlags::empty();
1074 flags.set(FontFaceFlags::IS_MONOSPACE, ttf_face.is_monospaced());
1075 flags.set(FontFaceFlags::HAS_LIGATURES, has_ligatures);
1076 flags.set(FontFaceFlags::HAS_RASTER_IMAGES, has_raster_images);
1077 flags.set(FontFaceFlags::HAS_SVG_IMAGES, ttf_face.tables().svg.is_some());
1078
1079 Ok(FontFace(Arc::new(LoadedFontFace {
1080 face_index,
1081 family_name,
1082 display_name,
1083 postscript_name,
1084 style: ttf_face.style().into(),
1085 weight: ttf_face.weight().into(),
1086 stretch: ttf_face.width().into(),
1087 metrics: ttf_face.into(),
1088 lig_carets,
1089 m: Mutex::new(FontFaceMut {
1090 instances: Default::default(),
1091 render_ids: Default::default(),
1092 unregistered: Default::default(),
1093 }),
1094 data: bytes,
1095 flags,
1096 })))
1097 }
1098
1099 fn on_refresh(&self) {
1100 let mut m = self.0.m.lock();
1101 m.instances.clear();
1102 m.unregistered = true;
1103 }
1104
1105 fn render_face(&self, renderer: &ViewRenderer) -> zng_view_api::font::FontFaceId {
1106 let mut m = self.0.m.lock();
1107 for r in m.render_ids.iter() {
1108 if &r.renderer == renderer {
1109 return r.face_id;
1110 }
1111 }
1112
1113 let data = match self.0.data.to_ipc() {
1114 Ok(d) => d,
1115 Err(e) => {
1116 tracing::error!("cannot allocate ipc font data, {e}");
1117 return zng_view_api::font::FontFaceId::INVALID;
1118 }
1119 };
1120
1121 let key = match renderer.add_font_face(data, self.0.face_index) {
1122 Ok(k) => k,
1123 Err(_) => {
1124 tracing::debug!("respawned calling `add_font`, will return dummy font key");
1125 return zng_view_api::font::FontFaceId::INVALID;
1126 }
1127 };
1128
1129 m.render_ids.push(RenderFontFace::new(renderer, key));
1130
1131 key
1132 }
1133
1134 pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1143 if self.is_empty() {
1144 None
1145 } else {
1146 Some(rustybuzz::Face::from_slice(&self.0.data, self.0.face_index).unwrap())
1147 }
1148 }
1149
1150 pub fn ttf(&self) -> Option<ttf_parser::Face<'_>> {
1159 if self.is_empty() {
1160 None
1161 } else {
1162 Some(ttf_parser::Face::parse(&self.0.data, self.0.face_index).unwrap())
1163 }
1164 }
1165
1166 pub fn bytes(&self) -> &FontBytes {
1168 &self.0.data
1169 }
1170 pub fn index(&self) -> u32 {
1172 self.0.face_index
1173 }
1174
1175 pub fn display_name(&self) -> &FontName {
1177 &self.0.display_name
1178 }
1179
1180 pub fn family_name(&self) -> &FontName {
1182 &self.0.family_name
1183 }
1184
1185 pub fn postscript_name(&self) -> Option<&str> {
1187 self.0.postscript_name.as_deref()
1188 }
1189
1190 pub fn style(&self) -> FontStyle {
1192 self.0.style
1193 }
1194
1195 pub fn weight(&self) -> FontWeight {
1197 self.0.weight
1198 }
1199
1200 pub fn stretch(&self) -> FontStretch {
1202 self.0.stretch
1203 }
1204
1205 pub fn is_monospace(&self) -> bool {
1207 self.0.flags.contains(FontFaceFlags::IS_MONOSPACE)
1208 }
1209
1210 pub fn metrics(&self) -> &FontFaceMetrics {
1212 &self.0.metrics
1213 }
1214
1215 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> Font {
1224 let key = FontInstanceKey::new(font_size, &variations);
1225 let mut m = self.0.m.lock();
1226 if !m.unregistered {
1227 m.instances
1228 .entry(key)
1229 .or_insert_with(|| Font::new(self.clone(), font_size, variations))
1230 .clone()
1231 } else {
1232 tracing::debug!(target: "font_loading", "creating font from unregistered `{}`, will not cache", self.0.display_name);
1233 Font::new(self.clone(), font_size, variations)
1234 }
1235 }
1236
1237 pub fn synthesis_for(&self, style: FontStyle, weight: FontWeight) -> FontSynthesis {
1239 let mut synth = FontSynthesis::DISABLED;
1240
1241 if style != FontStyle::Normal && self.style() == FontStyle::Normal {
1242 synth |= FontSynthesis::OBLIQUE;
1244 }
1245 if weight > self.weight() {
1246 synth |= FontSynthesis::BOLD;
1249 }
1250
1251 synth
1252 }
1253
1254 pub fn is_cached(&self) -> bool {
1258 !self.0.m.lock().unregistered
1259 }
1260
1261 pub fn color_palettes(&self) -> ColorPalettes<'_> {
1265 match self.ttf() {
1266 Some(ttf) => ColorPalettes::new(*ttf.raw_face()),
1267 None => ColorPalettes::empty(),
1268 }
1269 }
1270
1271 pub fn color_glyphs(&self) -> ColorGlyphs<'_> {
1275 match self.ttf() {
1276 Some(ttf) => ColorGlyphs::new(*ttf.raw_face()),
1277 None => ColorGlyphs::empty(),
1278 }
1279 }
1280
1281 pub fn has_ligatures(&self) -> bool {
1283 self.0.flags.contains(FontFaceFlags::HAS_LIGATURES)
1284 }
1285
1286 pub fn has_ligature_caret_offsets(&self) -> bool {
1291 !self.0.lig_carets.is_empty()
1292 }
1293
1294 pub fn has_raster_images(&self) -> bool {
1296 self.0.flags.contains(FontFaceFlags::HAS_RASTER_IMAGES)
1297 }
1298
1299 pub fn has_svg_images(&self) -> bool {
1301 self.0.flags.contains(FontFaceFlags::HAS_SVG_IMAGES)
1302 }
1303}
1304
1305#[derive(Clone)]
1311pub struct Font(Arc<LoadedFont>);
1312struct LoadedFont {
1313 face: FontFace,
1314 size: Px,
1315 variations: RFontVariations,
1316 metrics: FontMetrics,
1317 render_keys: Mutex<Vec<RenderFont>>,
1318 small_word_cache: RwLock<HashMap<WordCacheKey<[u8; Font::SMALL_WORD_LEN]>, ShapedSegmentData>>,
1319 word_cache: RwLock<HashMap<WordCacheKey<String>, ShapedSegmentData>>,
1320}
1321impl fmt::Debug for Font {
1322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1323 f.debug_struct("Font")
1324 .field("face", &self.0.face)
1325 .field("size", &self.0.size)
1326 .field("metrics", &self.0.metrics)
1327 .field("render_keys.len()", &self.0.render_keys.lock().len())
1328 .field("small_word_cache.len()", &self.0.small_word_cache.read().len())
1329 .field("word_cache.len()", &self.0.word_cache.read().len())
1330 .finish()
1331 }
1332}
1333impl PartialEq for Font {
1334 fn eq(&self, other: &Self) -> bool {
1335 Arc::ptr_eq(&self.0, &other.0)
1336 }
1337}
1338impl Eq for Font {}
1339impl Font {
1340 const SMALL_WORD_LEN: usize = 8;
1341
1342 fn to_small_word(s: &str) -> Option<[u8; Self::SMALL_WORD_LEN]> {
1343 if s.len() <= Self::SMALL_WORD_LEN {
1344 let mut a = [b'\0'; Self::SMALL_WORD_LEN];
1345 a[..s.len()].copy_from_slice(s.as_bytes());
1346 Some(a)
1347 } else {
1348 None
1349 }
1350 }
1351
1352 fn new(face: FontFace, size: Px, variations: RFontVariations) -> Self {
1353 Font(Arc::new(LoadedFont {
1354 metrics: face.metrics().sized(size),
1355 face,
1356 size,
1357 variations,
1358 render_keys: Mutex::new(vec![]),
1359 small_word_cache: RwLock::default(),
1360 word_cache: RwLock::default(),
1361 }))
1362 }
1363
1364 fn render_font(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1365 let _span = tracing::trace_span!("Font::render_font").entered();
1366
1367 let mut render_keys = self.0.render_keys.lock();
1368 for r in render_keys.iter() {
1369 if &r.renderer == renderer && r.synthesis == synthesis {
1370 return r.font_id;
1371 }
1372 }
1373
1374 let font_key = self.0.face.render_face(renderer);
1375
1376 let mut opt = zng_view_api::font::FontOptions::default();
1377 opt.synthetic_oblique = synthesis.contains(FontSynthesis::OBLIQUE);
1378 opt.synthetic_bold = synthesis.contains(FontSynthesis::BOLD);
1379 let variations = self.0.variations.iter().map(|v| (v.tag.to_bytes(), v.value)).collect();
1380
1381 let key = match renderer.add_font(font_key, self.0.size, opt, variations) {
1382 Ok(k) => k,
1383 Err(_) => {
1384 tracing::debug!("respawned calling `add_font_instance`, will return dummy font key");
1385 return zng_view_api::font::FontId::INVALID;
1386 }
1387 };
1388
1389 render_keys.push(RenderFont::new(renderer, synthesis, key));
1390
1391 key
1392 }
1393
1394 pub fn face(&self) -> &FontFace {
1396 &self.0.face
1397 }
1398
1399 pub fn harfbuzz(&self) -> Option<rustybuzz::Face<'_>> {
1401 let ppem = self.0.size.0 as u16;
1402
1403 let mut font = self.0.face.harfbuzz()?;
1404
1405 font.set_pixels_per_em(Some((ppem, ppem)));
1406 font.set_variations(&self.0.variations);
1407
1408 Some(font)
1409 }
1410
1411 pub fn size(&self) -> Px {
1415 self.0.size
1416 }
1417
1418 pub fn variations(&self) -> &RFontVariations {
1420 &self.0.variations
1421 }
1422
1423 pub fn metrics(&self) -> &FontMetrics {
1425 &self.0.metrics
1426 }
1427
1428 pub fn ligature_caret_offsets(
1434 &self,
1435 lig: zng_view_api::font::GlyphIndex,
1436 ) -> impl ExactSizeIterator<Item = f32> + DoubleEndedIterator + '_ {
1437 let face = &self.0.face.0;
1438 face.lig_carets.carets(lig).iter().map(move |&o| match o {
1439 ligature_util::LigatureCaret::Coordinate(o) => {
1440 let size_scale = 1.0 / face.metrics.units_per_em as f32 * self.0.size.0 as f32;
1441 o as f32 * size_scale
1442 }
1443 ligature_util::LigatureCaret::GlyphContourPoint(i) => {
1444 if let Some(f) = self.harfbuzz() {
1445 struct Search {
1446 i: u16,
1447 s: u16,
1448 x: f32,
1449 }
1450 impl Search {
1451 fn check(&mut self, x: f32) {
1452 self.s = self.s.saturating_add(1);
1453 if self.s == self.i {
1454 self.x = x;
1455 }
1456 }
1457 }
1458 impl ttf_parser::OutlineBuilder for Search {
1459 fn move_to(&mut self, x: f32, _y: f32) {
1460 self.check(x);
1461 }
1462
1463 fn line_to(&mut self, x: f32, _y: f32) {
1464 self.check(x);
1465 }
1466
1467 fn quad_to(&mut self, _x1: f32, _y1: f32, x: f32, _y: f32) {
1468 self.check(x)
1469 }
1470
1471 fn curve_to(&mut self, _x1: f32, _y1: f32, _x2: f32, _y2: f32, x: f32, _y: f32) {
1472 self.check(x);
1473 }
1474
1475 fn close(&mut self) {}
1476 }
1477 let mut search = Search { i, s: 0, x: 0.0 };
1478 if f.outline_glyph(ttf_parser::GlyphId(lig as _), &mut search).is_some() && search.s >= search.i {
1479 return search.x * self.0.metrics.size_scale;
1480 }
1481 }
1482 0.0
1483 }
1484 })
1485 }
1486}
1487impl zng_app::render::Font for Font {
1488 fn is_empty_fallback(&self) -> bool {
1489 self.face().is_empty()
1490 }
1491
1492 fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId {
1493 self.render_font(renderer, synthesis)
1494 }
1495}
1496
1497#[derive(Debug, Clone)]
1501pub struct FontFaceList {
1502 fonts: Box<[FontFace]>,
1503 requested_style: FontStyle,
1504 requested_weight: FontWeight,
1505 requested_stretch: FontStretch,
1506}
1507impl FontFaceList {
1508 pub fn empty() -> Self {
1510 Self {
1511 fonts: Box::new([FontFace::empty()]),
1512 requested_style: FontStyle::Normal,
1513 requested_weight: FontWeight::NORMAL,
1514 requested_stretch: FontStretch::NORMAL,
1515 }
1516 }
1517
1518 pub fn requested_style(&self) -> FontStyle {
1520 self.requested_style
1521 }
1522
1523 pub fn requested_weight(&self) -> FontWeight {
1525 self.requested_weight
1526 }
1527
1528 pub fn requested_stretch(&self) -> FontStretch {
1530 self.requested_stretch
1531 }
1532
1533 pub fn best(&self) -> &FontFace {
1535 &self.fonts[0]
1536 }
1537
1538 pub fn face_synthesis(&self, face_index: usize) -> FontSynthesis {
1540 if let Some(face) = self.fonts.get(face_index) {
1541 face.synthesis_for(self.requested_style, self.requested_weight)
1542 } else {
1543 FontSynthesis::DISABLED
1544 }
1545 }
1546
1547 pub fn iter(&self) -> std::slice::Iter<'_, FontFace> {
1549 self.fonts.iter()
1550 }
1551
1552 pub fn len(&self) -> usize {
1556 self.fonts.len()
1557 }
1558
1559 pub fn is_empty(&self) -> bool {
1561 self.fonts[0].is_empty() && self.fonts.len() == 1
1562 }
1563
1564 pub fn sized(&self, font_size: Px, variations: RFontVariations) -> FontList {
1568 FontList {
1569 fonts: self.fonts.iter().map(|f| f.sized(font_size, variations.clone())).collect(),
1570 requested_style: self.requested_style,
1571 requested_weight: self.requested_weight,
1572 requested_stretch: self.requested_stretch,
1573 }
1574 }
1575}
1576impl PartialEq for FontFaceList {
1577 fn eq(&self, other: &Self) -> bool {
1579 self.requested_style == other.requested_style
1580 && self.requested_weight == other.requested_weight
1581 && self.requested_stretch == other.requested_stretch
1582 && self.fonts.len() == other.fonts.len()
1583 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1584 }
1585}
1586impl Eq for FontFaceList {}
1587impl std::ops::Deref for FontFaceList {
1588 type Target = [FontFace];
1589
1590 fn deref(&self) -> &Self::Target {
1591 &self.fonts
1592 }
1593}
1594impl<'a> std::iter::IntoIterator for &'a FontFaceList {
1595 type Item = &'a FontFace;
1596
1597 type IntoIter = std::slice::Iter<'a, FontFace>;
1598
1599 fn into_iter(self) -> Self::IntoIter {
1600 self.iter()
1601 }
1602}
1603impl std::ops::Index<usize> for FontFaceList {
1604 type Output = FontFace;
1605
1606 fn index(&self, index: usize) -> &Self::Output {
1607 &self.fonts[index]
1608 }
1609}
1610
1611#[derive(Debug, Clone)]
1613pub struct FontList {
1614 fonts: Box<[Font]>,
1615 requested_style: FontStyle,
1616 requested_weight: FontWeight,
1617 requested_stretch: FontStretch,
1618}
1619#[expect(clippy::len_without_is_empty)] impl FontList {
1621 pub fn best(&self) -> &Font {
1623 &self.fonts[0]
1624 }
1625
1626 pub fn requested_size(&self) -> Px {
1628 self.fonts[0].size()
1629 }
1630
1631 pub fn requested_style(&self) -> FontStyle {
1633 self.requested_style
1634 }
1635
1636 pub fn requested_weight(&self) -> FontWeight {
1638 self.requested_weight
1639 }
1640
1641 pub fn requested_stretch(&self) -> FontStretch {
1643 self.requested_stretch
1644 }
1645
1646 pub fn face_synthesis(&self, font_index: usize) -> FontSynthesis {
1648 if let Some(font) = self.fonts.get(font_index) {
1649 font.0.face.synthesis_for(self.requested_style, self.requested_weight)
1650 } else {
1651 FontSynthesis::DISABLED
1652 }
1653 }
1654
1655 pub fn iter(&self) -> std::slice::Iter<'_, Font> {
1657 self.fonts.iter()
1658 }
1659
1660 pub fn len(&self) -> usize {
1664 self.fonts.len()
1665 }
1666
1667 pub fn is_sized_from(&self, faces: &FontFaceList) -> bool {
1669 if self.len() != faces.len() {
1670 return false;
1671 }
1672
1673 for (font, face) in self.iter().zip(faces.iter()) {
1674 if font.face() != face {
1675 return false;
1676 }
1677 }
1678
1679 true
1680 }
1681}
1682impl PartialEq for FontList {
1683 fn eq(&self, other: &Self) -> bool {
1685 self.requested_style == other.requested_style
1686 && self.requested_weight == other.requested_weight
1687 && self.requested_stretch == other.requested_stretch
1688 && self.fonts.len() == other.fonts.len()
1689 && self.fonts.iter().zip(other.fonts.iter()).all(|(a, b)| a == b)
1690 }
1691}
1692impl Eq for FontList {}
1693impl std::ops::Deref for FontList {
1694 type Target = [Font];
1695
1696 fn deref(&self) -> &Self::Target {
1697 &self.fonts
1698 }
1699}
1700impl<'a> std::iter::IntoIterator for &'a FontList {
1701 type Item = &'a Font;
1702
1703 type IntoIter = std::slice::Iter<'a, Font>;
1704
1705 fn into_iter(self) -> Self::IntoIter {
1706 self.iter()
1707 }
1708}
1709impl<I: SliceIndex<[Font]>> std::ops::Index<I> for FontList {
1710 type Output = I::Output;
1711
1712 fn index(&self, index: I) -> &I::Output {
1713 &self.fonts[index]
1714 }
1715}
1716
1717struct FontFaceLoader {
1718 custom_fonts: HashMap<FontName, Vec<FontFace>>,
1719
1720 system_fonts_cache: HashMap<FontName, Vec<SystemFontFace>>,
1721 list_cache: HashMap<Box<[FontName]>, Vec<FontFaceListQuery>>,
1722}
1723struct SystemFontFace {
1724 properties: (FontStyle, FontWeight, FontStretch),
1725 result: ResponseVar<Option<FontFace>>,
1726}
1727struct FontFaceListQuery {
1728 properties: (FontStyle, FontWeight, FontStretch),
1729 lang: Lang,
1730 result: ResponseVar<FontFaceList>,
1731}
1732impl FontFaceLoader {
1733 fn new() -> Self {
1734 FontFaceLoader {
1735 custom_fonts: HashMap::new(),
1736 system_fonts_cache: HashMap::new(),
1737 list_cache: HashMap::new(),
1738 }
1739 }
1740
1741 fn on_view_process_respawn(&mut self) {
1742 let sys_fonts = self.system_fonts_cache.values().flatten().filter_map(|f| f.result.rsp().flatten());
1743 for face in self.custom_fonts.values().flatten().cloned().chain(sys_fonts) {
1744 let mut m = face.0.m.lock();
1745 m.render_ids.clear();
1746 for inst in m.instances.values() {
1747 inst.0.render_keys.lock().clear();
1748 }
1749 }
1750 }
1751
1752 fn on_refresh(&mut self) {
1753 for (_, sys_family) in self.system_fonts_cache.drain() {
1754 for sys_font in sys_family {
1755 sys_font.result.with(|r| {
1756 if let Some(Some(face)) = r.done() {
1757 face.on_refresh();
1758 }
1759 });
1760 }
1761 }
1762 }
1763 fn on_prune(&mut self) {
1764 self.system_fonts_cache.retain(|_, v| {
1765 v.retain(|sff| {
1766 if sff.result.strong_count() == 1 {
1767 sff.result.with(|r| {
1768 match r.done() {
1769 Some(Some(face)) => Arc::strong_count(&face.0) > 1, Some(None) => false, None => true, }
1773 })
1774 } else {
1775 true
1777 }
1778 });
1779 !v.is_empty()
1780 });
1781
1782 self.list_cache.clear();
1783 }
1784
1785 fn try_list(
1786 &self,
1787 families: &[FontName],
1788 style: FontStyle,
1789 weight: FontWeight,
1790 stretch: FontStretch,
1791 lang: &Lang,
1792 ) -> Option<ResponseVar<FontFaceList>> {
1793 if let Some(queries) = self.list_cache.get(families) {
1794 for q in queries {
1795 if q.properties == (style, weight, stretch) && &q.lang == lang {
1796 return Some(q.result.clone());
1797 }
1798 }
1799 }
1800 None
1801 }
1802
1803 fn load_list(
1804 &mut self,
1805 families: &[FontName],
1806 style: FontStyle,
1807 weight: FontWeight,
1808 stretch: FontStretch,
1809 lang: &Lang,
1810 ) -> ResponseVar<FontFaceList> {
1811 if let Some(r) = self.try_list(families, style, weight, stretch, lang) {
1812 return r;
1813 }
1814
1815 let resolved = GenericFonts {}.resolve_list(families, lang);
1816 let families = resolved.as_ref().map(|n| &***n).unwrap_or(families);
1817 let mut list = Vec::with_capacity(families.len() + 1);
1818 let mut pending = vec![];
1819
1820 {
1821 let fallback = [GenericFonts {}.fallback(lang)];
1822 let mut used = HashSet::with_capacity(families.len());
1823 for name in families.iter().chain(&fallback) {
1824 if !used.insert(name) {
1825 continue;
1826 }
1827
1828 let face = self.load_resolved(name, style, weight, stretch);
1829 if face.is_done() {
1830 if let Some(face) = face.rsp().unwrap() {
1831 list.push(face);
1832 }
1833 } else {
1834 pending.push((list.len(), face));
1835 }
1836 }
1837 }
1838
1839 let r = if pending.is_empty() {
1840 if list.is_empty() {
1841 tracing::error!(target: "font_loading", "failed to load fallback font");
1842 list.push(FontFace::empty());
1843 }
1844 response_done_var(FontFaceList {
1845 fonts: list.into_boxed_slice(),
1846 requested_style: style,
1847 requested_weight: weight,
1848 requested_stretch: stretch,
1849 })
1850 } else {
1851 task::respond(async move {
1852 for (i, pending) in pending.into_iter().rev() {
1853 if let Some(rsp) = pending.wait_rsp().await {
1854 list.insert(i, rsp);
1855 }
1856 }
1857
1858 if list.is_empty() {
1859 tracing::error!(target: "font_loading", "failed to load fallback font");
1860 list.push(FontFace::empty());
1861 }
1862
1863 FontFaceList {
1864 fonts: list.into_boxed_slice(),
1865 requested_style: style,
1866 requested_weight: weight,
1867 requested_stretch: stretch,
1868 }
1869 })
1870 };
1871
1872 self.list_cache
1873 .entry(families.iter().cloned().collect())
1874 .or_insert_with(|| Vec::with_capacity(1))
1875 .push(FontFaceListQuery {
1876 properties: (style, weight, stretch),
1877 lang: lang.clone(),
1878 result: r.clone(),
1879 });
1880
1881 r
1882 }
1883
1884 fn try_resolved(
1886 &self,
1887 font_name: &FontName,
1888 style: FontStyle,
1889 weight: FontWeight,
1890 stretch: FontStretch,
1891 ) -> Option<ResponseVar<Option<FontFace>>> {
1892 if let Some(custom_family) = self.custom_fonts.get(font_name) {
1893 let custom = Self::match_custom(custom_family, style, weight, stretch);
1894 return Some(response_done_var(Some(custom)));
1895 }
1896
1897 if let Some(cached_sys_family) = self.system_fonts_cache.get(font_name) {
1898 for sys_face in cached_sys_family.iter() {
1899 if sys_face.properties == (style, weight, stretch) {
1900 return Some(sys_face.result.clone());
1901 }
1902 }
1903 }
1904
1905 None
1906 }
1907
1908 fn load_resolved(
1910 &mut self,
1911 font_name: &FontName,
1912 style: FontStyle,
1913 weight: FontWeight,
1914 stretch: FontStretch,
1915 ) -> ResponseVar<Option<FontFace>> {
1916 if let Some(cached) = self.try_resolved(font_name, style, weight, stretch) {
1917 return cached;
1918 }
1919
1920 let load = task::wait(clmv!(font_name, || {
1921 let (bytes, face_index) = match Self::get_system(&font_name, style, weight, stretch) {
1922 Some(h) => h,
1923 None => {
1924 #[cfg(debug_assertions)]
1925 static NOT_FOUND: Mutex<Option<HashSet<FontName>>> = Mutex::new(None);
1926
1927 #[cfg(debug_assertions)]
1928 if NOT_FOUND.lock().get_or_insert_with(HashSet::default).insert(font_name.clone()) {
1929 tracing::debug!(r#"font "{font_name}" not found"#);
1930 }
1931
1932 return None;
1933 }
1934 };
1935 match FontFace::load(bytes, face_index) {
1936 Ok(f) => Some(f),
1937 Err(FontLoadingError::UnknownFormat) => None,
1938 Err(e) => {
1939 tracing::error!(target: "font_loading", "failed to load system font, {e}\nquery: {:?}", (font_name, style, weight, stretch));
1940 None
1941 }
1942 }
1943 }));
1944 let result = task::respond(async_clmv!(font_name, {
1945 match task::with_deadline(load, 10.secs()).await {
1946 Ok(r) => r,
1947 Err(_) => {
1948 tracing::error!(target: "font_loading", "timeout loading {font_name:?}");
1949 None
1950 }
1951 }
1952 }));
1953
1954 self.system_fonts_cache
1955 .entry(font_name.clone())
1956 .or_insert_with(|| Vec::with_capacity(1))
1957 .push(SystemFontFace {
1958 properties: (style, weight, stretch),
1959 result: result.clone(),
1960 });
1961
1962 result
1963 }
1964
1965 fn get_system(font_name: &FontName, style: FontStyle, weight: FontWeight, stretch: FontStretch) -> Option<(FontBytes, u32)> {
1966 let _span = tracing::trace_span!("FontFaceLoader::get_system").entered();
1967 match query_util::best(font_name, style, weight, stretch) {
1968 Ok(r) => r,
1969 Err(e) => {
1970 tracing::error!("cannot get `{font_name}` system font, {e}");
1971 None
1972 }
1973 }
1974 }
1975
1976 fn match_custom(faces: &[FontFace], style: FontStyle, weight: FontWeight, stretch: FontStretch) -> FontFace {
1977 if faces.len() == 1 {
1978 return faces[0].clone();
1980 }
1981
1982 let mut set = Vec::with_capacity(faces.len());
1983 let mut set_dist = 0.0f64; let wrong_side = if stretch <= FontStretch::NORMAL {
1990 |s| s > FontStretch::NORMAL
1991 } else {
1992 |s| s <= FontStretch::NORMAL
1993 };
1994 for face in faces {
1995 let mut dist = (face.stretch().0 - stretch.0).abs() as f64;
1996 if wrong_side(face.stretch()) {
1997 dist += f32::MAX as f64 + 1.0;
1998 }
1999
2000 if set.is_empty() {
2001 set.push(face);
2002 set_dist = dist;
2003 } else if dist < set_dist {
2004 set_dist = dist;
2006 set.clear();
2007 set.push(face);
2008 } else if (dist - set_dist).abs() < 0.0001 {
2009 set.push(face);
2011 }
2012 }
2013 if set.len() == 1 {
2014 return set[0].clone();
2015 }
2016
2017 let style_pref = match style {
2022 FontStyle::Normal => [FontStyle::Normal, FontStyle::Oblique, FontStyle::Italic],
2023 FontStyle::Italic => [FontStyle::Italic, FontStyle::Oblique, FontStyle::Normal],
2024 FontStyle::Oblique => [FontStyle::Oblique, FontStyle::Italic, FontStyle::Normal],
2025 };
2026 let mut best_style = style_pref.len();
2027 for face in &set {
2028 let i = style_pref.iter().position(|&s| s == face.style()).unwrap();
2029 if i < best_style {
2030 best_style = i;
2031 }
2032 }
2033 set.retain(|f| f.style() == style_pref[best_style]);
2034 if set.len() == 1 {
2035 return set[0].clone();
2036 }
2037
2038 let add_penalty = if weight.0 >= 400.0 && weight.0 <= 500.0 {
2046 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2048 if face.weight() < weight {
2050 *dist += 100.0;
2052 } else if face.weight().0 > 500.0 {
2053 *dist += 600.0;
2055 }
2056 }
2057 } else if weight.0 < 400.0 {
2058 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2060 if face.weight() > weight {
2061 *dist += weight.0 as f64;
2062 }
2063 }
2064 } else {
2065 debug_assert!(weight.0 > 500.0);
2066 |face: &FontFace, weight: FontWeight, dist: &mut f64| {
2068 if face.weight() < weight {
2069 *dist += f32::MAX as f64;
2070 }
2071 }
2072 };
2073
2074 let mut best = set[0];
2075 let mut best_dist = f64::MAX;
2076
2077 for face in &set {
2078 let mut dist = (face.weight().0 - weight.0).abs() as f64;
2079
2080 add_penalty(face, weight, &mut dist);
2081
2082 if dist < best_dist {
2083 best_dist = dist;
2084 best = face;
2085 }
2086 }
2087
2088 best.clone()
2089 }
2090}
2091
2092struct RenderFontFace {
2093 renderer: ViewRenderer,
2094 face_id: zng_view_api::font::FontFaceId,
2095}
2096impl RenderFontFace {
2097 fn new(renderer: &ViewRenderer, face_id: zng_view_api::font::FontFaceId) -> Self {
2098 RenderFontFace {
2099 renderer: renderer.clone(),
2100 face_id,
2101 }
2102 }
2103}
2104impl Drop for RenderFontFace {
2105 fn drop(&mut self) {
2106 let _ = self.renderer.delete_font_face(self.face_id);
2108 }
2109}
2110
2111struct RenderFont {
2112 renderer: ViewRenderer,
2113 synthesis: FontSynthesis,
2114 font_id: zng_view_api::font::FontId,
2115}
2116impl RenderFont {
2117 fn new(renderer: &ViewRenderer, synthesis: FontSynthesis, font_id: zng_view_api::font::FontId) -> RenderFont {
2118 RenderFont {
2119 renderer: renderer.clone(),
2120 synthesis,
2121 font_id,
2122 }
2123 }
2124}
2125impl Drop for RenderFont {
2126 fn drop(&mut self) {
2127 let _ = self.renderer.delete_font(self.font_id);
2129 }
2130}
2131
2132app_local! {
2133 static GENERIC_FONTS_SV: GenericFontsService = GenericFontsService::new();
2134}
2135
2136struct GenericFontsService {
2137 serif: LangMap<FontName>,
2138 sans_serif: LangMap<FontName>,
2139 monospace: LangMap<FontName>,
2140 cursive: LangMap<FontName>,
2141 fantasy: LangMap<FontName>,
2142 fallback: LangMap<FontName>,
2143 system_ui: LangMap<FontNames>,
2144}
2145impl GenericFontsService {
2146 fn new() -> Self {
2147 fn default(name: impl Into<FontName>) -> LangMap<FontName> {
2148 let mut f = LangMap::with_capacity(1);
2149 f.insert(lang!(und), name.into());
2150 f
2151 }
2152
2153 let serif = "serif";
2154 let sans_serif = "sans-serif";
2155 let monospace = "monospace";
2156 let cursive = "cursive";
2157 let fantasy = "fantasy";
2158 let fallback = if cfg!(windows) {
2159 "Segoe UI Symbol"
2160 } else if cfg!(target_os = "linux") {
2161 "Standard Symbols PS"
2162 } else {
2163 "sans-serif"
2164 };
2165
2166 let mut system_ui = LangMap::with_capacity(5);
2167
2168 if cfg!(windows) {
2169 system_ui.insert(
2170 lang!("zh-Hans"),
2171 ["Segoe UI", "Microsoft YaHei", "Segoe Ui Emoji", "sans-serif"].into(),
2172 );
2173 system_ui.insert(
2174 lang!("zh-Hant"),
2175 ["Segoe UI", "Microsoft Jhenghei", "Segoe Ui Emoji", "sans-serif"].into(),
2176 );
2177 system_ui.insert(
2178 lang!("ja"),
2179 ["Segoe UI", "Yu Gothic UI", "Meiryo UI", "Segoe Ui Emoji", "sans-serif"].into(),
2180 );
2181 system_ui.insert(
2182 lang!("ko"),
2183 ["Segoe UI", "Malgun Gothic", "Dotom", "Segoe Ui Emoji", "sans-serif"].into(),
2184 );
2185 for lang in [
2186 lang!("hi"),
2187 lang!("bn"),
2188 lang!("te"),
2189 lang!("as"),
2190 lang!("gu"),
2191 lang!("kn"),
2192 lang!("mr"),
2193 lang!("ne"),
2194 lang!("or"),
2195 lang!("pa"),
2196 lang!("si"),
2197 ] {
2198 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Mangal", "Segoe Ui Emoji", "sans-serif"].into());
2199 }
2200 system_ui.insert(lang!("am"), ["Segoe UI", "Nyala", "Ebrima", "Segoe Ui Emoji", "sans-serif"].into());
2201 system_ui.insert(
2202 lang!("km"),
2203 ["Segoe UI", "Khmer UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2204 );
2205 system_ui.insert(
2206 lang!("lo"),
2207 ["Segoe UI", "lao UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into(),
2208 );
2209 system_ui.insert(lang!("th"), ["Segoe UI", "Leelawadee UI", "Segoe Ui Emoji", "sans-serif"].into());
2210 for lang in [lang!("ml"), lang!("ta")] {
2211 system_ui.insert(lang, ["Segoe UI", "Nirmala UI", "Segoe Ui Emoji", "sans-serif"].into());
2212 }
2213 system_ui.insert(lang!("my"), ["Segoe UI", "Myanmar Text", "Segoe Ui Emoji", "sans-serif"].into());
2214
2215 system_ui.insert(lang!(und), ["Segoe UI", "Segoe Ui Emoji", "sans-serif"].into());
2216 } else if cfg!(target_os = "macos") {
2217 system_ui.insert(
2218 lang!("zh-Hans"),
2219 ["system-ui", "PingFang SC", "Hiragino Sans GB", "Apple Color Emoji", "sans-serif"].into(),
2220 );
2221 system_ui.insert(
2222 lang!("zh-Hant"),
2223 ["system-ui", "PingFang TC", "Apple Color Emoji", "sans-serif"].into(),
2224 );
2225 system_ui.insert(
2226 lang!("ja"),
2227 [
2228 "system-ui",
2229 "Hiragino Sans",
2230 "Hiragino Kaku Gothic ProN",
2231 "Apple Color Emoji",
2232 "sans-serif",
2233 ]
2234 .into(),
2235 );
2236 system_ui.insert(
2237 lang!("ko"),
2238 ["system-ui", "Apple SD Gothic Neo", "NanumGothic", "Apple Color Emoji", "sans-serif"].into(),
2239 );
2240
2241 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2242 system_ui.insert(
2243 lang,
2244 [
2245 "system-ui",
2246 "Kohinoor Devanagari",
2247 "Devanagari Sangam MN",
2248 "Apple Color Emoji",
2249 "sans-serif",
2250 ]
2251 .into(),
2252 );
2253 }
2254 for lang in [lang!("bn"), lang!("as")] {
2255 system_ui.insert(
2256 lang,
2257 [
2258 "system-ui",
2259 "Kohinoor Bangla",
2260 "Bangla Sangam MN",
2261 "Apple Color Emoji",
2262 "sans-serif",
2263 ]
2264 .into(),
2265 );
2266 }
2267 system_ui.insert(
2268 lang!("te"),
2269 [
2270 "system-ui",
2271 "Kohinoor Telugu",
2272 "Telugu Sangam MN",
2273 "Apple Color Emoji",
2274 "sans-serif",
2275 ]
2276 .into(),
2277 );
2278 system_ui.insert(
2279 lang!("gu"),
2280 [
2281 "system-ui",
2282 "Kohinoor Gujarati",
2283 "Gujarati Sangam MN",
2284 "Apple Color Emoji",
2285 "sans-serif",
2286 ]
2287 .into(),
2288 );
2289 system_ui.insert(
2290 lang!("kn"),
2291 ["system-ui", "Kannada Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2292 );
2293 system_ui.insert(
2294 lang!("or"),
2295 ["system-ui", "Oriya Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2296 );
2297 system_ui.insert(
2298 lang!("pa"),
2299 ["system-ui", "Mukta Mahee", "Gurmukhi Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2300 );
2301 system_ui.insert(
2302 lang!("si"),
2303 ["system-ui", "Sinhala Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2304 );
2305
2306 system_ui.insert(lang!("am"), ["system-ui", "Kefa", "Apple Color Emoji", "sans-serif"].into());
2307 system_ui.insert(
2308 lang!("km"),
2309 ["system-ui", "Khmer Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2310 );
2311 system_ui.insert(
2312 lang!("lo"),
2313 ["system-ui", "Lao Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2314 );
2315 system_ui.insert(
2316 lang!("th"),
2317 ["system-ui", "Thonburi", "Ayuthaya", "Apple Color Emoji", "sans-serif"].into(),
2318 );
2319 system_ui.insert(
2320 lang!("my"),
2321 ["system-ui", "Myanmar Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2322 );
2323
2324 system_ui.insert(
2325 lang!("ml"),
2326 ["system-ui", "Malayalam Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2327 );
2328 system_ui.insert(
2329 lang!("ta"),
2330 ["system-ui", "Kohinoor Tamil", "Tamil Sangam MN", "Apple Color Emoji", "sans-serif"].into(),
2331 );
2332
2333 for lang in [lang!("ar"), lang!("fa"), lang!("ps")] {
2334 system_ui.insert(lang, ["system-ui", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into());
2335 }
2336
2337 system_ui.insert(lang!("he"), ["system-ui", "Arial Hebrew", "Apple Color Emoji", "sans-serif"].into());
2338
2339 system_ui.insert(lang!("hy"), ["system-ui", "Mshtakan", "Apple Color Emoji", "sans-serif"].into());
2340
2341 system_ui.insert(
2342 lang!("ka"),
2343 ["system-ui", "Helvetica Neue", "Apple Color Emoji", "sans-serif"].into(),
2344 );
2345
2346 system_ui.insert(
2347 lang!("ur"),
2348 ["system-ui", "SF Arabic", "Geeza Pro", "Apple Color Emoji", "sans-serif"].into(),
2349 );
2350
2351 system_ui.insert(
2352 lang!(und),
2353 ["system-ui", "Neue Helvetica", "Lucida Grande", "Apple Color Emoji", "sans-serif"].into(),
2354 );
2355 } else if cfg!(target_os = "linux") {
2356 system_ui.insert(
2357 lang!("zh-Hans"),
2358 [
2359 "system-ui",
2360 "Ubuntu",
2361 "Noto Sans CJK SC",
2362 "Source Han Sans SC",
2363 "Noto Color Emoji",
2364 "sans-serif",
2365 ]
2366 .into(),
2367 );
2368 system_ui.insert(
2369 lang!("zh-Hant"),
2370 [
2371 "system-ui",
2372 "Ubuntu",
2373 "Noto Sans CJK TC",
2374 "Source Han Sans TC",
2375 "Noto Color Emoji",
2376 "sans-serif",
2377 ]
2378 .into(),
2379 );
2380 system_ui.insert(
2381 lang!("ja"),
2382 [
2383 "system-ui",
2384 "Ubuntu",
2385 "Noto Sans CJK JP",
2386 "Source Han Sans JP",
2387 "Noto Color Emoji",
2388 "sans-serif",
2389 ]
2390 .into(),
2391 );
2392 system_ui.insert(
2393 lang!("ko"),
2394 [
2395 "system-ui",
2396 "Ubuntu",
2397 "Noto Sans CJK KR",
2398 "Source Han Sans KR",
2399 "UnDotum",
2400 "Noto Color Emoji",
2401 "sans-serif",
2402 ]
2403 .into(),
2404 );
2405
2406 for lang in [lang!("hi"), lang!("mr"), lang!("ne")] {
2407 system_ui.insert(
2408 lang,
2409 [
2410 "system-ui",
2411 "Ubuntu",
2412 "Noto Sans Devanagari",
2413 "Lohit Devanagari",
2414 "Noto Color Emoji",
2415 "sans-serif",
2416 ]
2417 .into(),
2418 );
2419 }
2420 for lang in [lang!("bn"), lang!("as")] {
2421 system_ui.insert(
2422 lang,
2423 [
2424 "system-ui",
2425 "Ubuntu",
2426 "Noto Sans Bengali",
2427 "Lohit Bengali",
2428 "Noto Color Emoji",
2429 "sans-serif",
2430 ]
2431 .into(),
2432 );
2433 }
2434 system_ui.insert(
2435 lang!("te"),
2436 [
2437 "system-ui",
2438 "Ubuntu",
2439 "Noto Sans Telugu",
2440 "Lohit Telugu",
2441 "Noto Color Emoji",
2442 "sans-serif",
2443 ]
2444 .into(),
2445 );
2446 system_ui.insert(
2447 lang!("gu"),
2448 [
2449 "system-ui",
2450 "Ubuntu",
2451 "Noto Sans Gujarati",
2452 "Lohit Gujarati",
2453 "Noto Color Emoji",
2454 "sans-serif",
2455 ]
2456 .into(),
2457 );
2458 system_ui.insert(
2459 lang!("kn"),
2460 [
2461 "system-ui",
2462 "Ubuntu",
2463 "Noto Sans Kannada",
2464 "Lohit Kannada",
2465 "Noto Color Emoji",
2466 "sans-serif",
2467 ]
2468 .into(),
2469 );
2470 system_ui.insert(
2471 lang!("or"),
2472 [
2473 "system-ui",
2474 "Ubuntu",
2475 "Noto Sans Oriya",
2476 "Lohit Odia",
2477 "Noto Color Emoji",
2478 "sans-serif",
2479 ]
2480 .into(),
2481 );
2482 system_ui.insert(
2483 lang!("pa"),
2484 [
2485 "system-ui",
2486 "Ubuntu",
2487 "Noto Sans Gurmukhi",
2488 "Lohit Gurmukhi",
2489 "Noto Color Emoji",
2490 "sans-serif",
2491 ]
2492 .into(),
2493 );
2494 system_ui.insert(
2495 lang!("si"),
2496 [
2497 "system-ui",
2498 "Ubuntu",
2499 "Noto Sans Sinhala",
2500 "LKLUG",
2501 "Noto Color Emoji",
2502 "sans-serif",
2503 ]
2504 .into(),
2505 );
2506
2507 system_ui.insert(
2508 lang!("am"),
2509 [
2510 "system-ui",
2511 "Ubuntu",
2512 "Noto Sans Ethiopic",
2513 "Abyssinica SIL",
2514 "Noto Color Emoji",
2515 "sans-serif",
2516 ]
2517 .into(),
2518 );
2519 system_ui.insert(
2520 lang!("km"),
2521 [
2522 "system-ui",
2523 "Ubuntu",
2524 "Noto Sans Khmer",
2525 "Hanuman",
2526 "Noto Color Emoji",
2527 "sans-serif",
2528 ]
2529 .into(),
2530 );
2531 system_ui.insert(
2532 lang!("lo"),
2533 [
2534 "system-ui",
2535 "Ubuntu",
2536 "Noto Sans Lao",
2537 "Phetsarath OT",
2538 "Noto Color Emoji",
2539 "sans-serif",
2540 ]
2541 .into(),
2542 );
2543 system_ui.insert(
2544 lang!("th"),
2545 [
2546 "system-ui",
2547 "Ubuntu",
2548 "Noto Sans Thai",
2549 "Kinnari",
2550 "Garuda",
2551 "Noto Color Emoji",
2552 "sans-serif",
2553 ]
2554 .into(),
2555 );
2556 system_ui.insert(
2557 lang!("my"),
2558 [
2559 "system-ui",
2560 "Ubuntu",
2561 "Noto Sans Myanmar",
2562 "Padauk",
2563 "Noto Color Emoji",
2564 "sans-serif",
2565 ]
2566 .into(),
2567 );
2568
2569 system_ui.insert(
2570 lang!("ml"),
2571 [
2572 "system-ui",
2573 "Ubuntu",
2574 "Noto Sans Malayalam",
2575 "Lohit Malayalam",
2576 "Noto Color Emoji",
2577 "sans-serif",
2578 ]
2579 .into(),
2580 );
2581 system_ui.insert(
2582 lang!("ta"),
2583 [
2584 "system-ui",
2585 "Ubuntu",
2586 "Noto Sans Tamil",
2587 "Lohit Tamil",
2588 "Noto Color Emoji",
2589 "sans-serif",
2590 ]
2591 .into(),
2592 );
2593
2594 for lang in [lang!("ar"), lang!("fa"), lang!("ps"), lang!("ur")] {
2595 system_ui.insert(lang, ["system-ui", "Noto Sans Arabic", "Noto Color Emoji", "sans-serif"].into());
2596 }
2597
2598 system_ui.insert(
2599 lang!("he"),
2600 ["system-ui", "Noto Sans Hebrew", "Noto Color Emoji", "sans-serif"].into(),
2601 );
2602
2603 system_ui.insert(
2604 lang!("hy"),
2605 ["system-ui", "Noto Sans Armenian", "Noto Color Emoji", "sans-serif"].into(),
2606 );
2607
2608 system_ui.insert(lang!("ka"), ["system-ui", "DejaVu Sans", "Noto Color Emoji", "sans-serif"].into());
2609
2610 system_ui.insert(
2611 lang!("ur"),
2612 [
2613 "system-ui",
2614 "Noto Naskh Arabic",
2615 "Noto Sans Arabic",
2616 "Noto Color Emoji",
2617 "sans-serif",
2618 ]
2619 .into(),
2620 );
2621
2622 system_ui.insert(
2623 lang!(und),
2624 ["system-ui", "Ubuntu", "Droid Sans", "Noto Sans", "Noto Color Emoji", "sans-serif"].into(),
2625 );
2626 } else {
2627 system_ui.insert(lang!(und), ["system-ui", "sans-serif"].into());
2628 }
2629
2630 GenericFontsService {
2631 serif: default(serif),
2632 sans_serif: default(sans_serif),
2633 monospace: default(monospace),
2634 cursive: default(cursive),
2635 fantasy: default(fantasy),
2636
2637 system_ui,
2638
2639 fallback: default(fallback),
2640 }
2641 }
2642}
2643
2644#[non_exhaustive]
2659pub struct GenericFonts {}
2660macro_rules! impl_fallback_accessors {
2661 ($($name:ident=$name_str:tt),+ $(,)?) => {$($crate::paste! {
2662 #[doc = "Gets the *"$name_str "* font for the given language."]
2663 #[doc = "Note that the returned name can still be the generic `\""$name_str "\"`, this delegates the resolution to the operating system."]
2667
2668 pub fn $name(&self, lang: &Lang) -> FontName {
2669 GENERIC_FONTS_SV.read().$name.get(lang).unwrap().clone()
2670 }
2671
2672 #[doc = "Sets the *"$name_str "* font for the given language."]
2673 pub fn [<set_ $name>]<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2678 self.[<set_ $name _impl>](lang, font_name.into());
2679 }
2680 fn [<set_ $name _impl>](&self, lang: Lang, font_name: FontName) {
2681 UPDATES.once_update("GenericFonts.set", move || {
2682 GENERIC_FONTS_SV.write().$name.insert(lang.clone(), font_name);
2683 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::$name(), lang)));
2684 });
2685 }
2686 })+};
2687}
2688impl GenericFonts {
2689 #[rustfmt::skip] impl_fallback_accessors! {
2691 serif="serif", sans_serif="sans-serif", monospace="monospace", cursive="cursive", fantasy="fantasy"
2692 }
2693
2694 pub fn system_ui(&self, lang: &Lang) -> FontNames {
2700 GENERIC_FONTS_SV.read().system_ui.get(lang).unwrap().clone()
2701 }
2702
2703 pub fn set_system_ui(&self, lang: Lang, font_names: impl Into<FontNames>) {
2709 self.set_system_ui_impl(lang, font_names.into())
2710 }
2711 fn set_system_ui_impl(&self, lang: Lang, font_names: FontNames) {
2712 UPDATES.once_update("GenericFonts.set_system_ui", move || {
2713 GENERIC_FONTS_SV.write().system_ui.insert(lang.clone(), font_names);
2714 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::GenericFont(FontName::system_ui(), lang)));
2715 });
2716 }
2717
2718 pub fn fallback(&self, lang: &Lang) -> FontName {
2722 GENERIC_FONTS_SV.read().fallback.get(lang).unwrap().clone()
2723 }
2724
2725 pub fn set_fallback<F: Into<FontName>>(&self, lang: Lang, font_name: F) {
2731 self.set_fallback_impl(lang, font_name.into());
2732 }
2733 fn set_fallback_impl(&self, lang: Lang, font_name: FontName) {
2734 UPDATES.once_update("GenericFonts.set", move || {
2735 GENERIC_FONTS_SV.write().fallback.insert(lang.clone(), font_name);
2736 FONT_CHANGED_EVENT.notify(FontChangedArgs::now(FontChange::Fallback(lang)));
2737 });
2738 }
2739
2740 pub fn resolve(&self, name: &FontName, lang: &Lang) -> Option<FontName> {
2748 match &**name {
2749 "serif" => Some(self.serif(lang)),
2750 "sans-serif" => Some(self.sans_serif(lang)),
2751 "monospace" => Some(self.monospace(lang)),
2752 "cursive" => Some(self.cursive(lang)),
2753 "fantasy" => Some(self.fantasy(lang)),
2754 _ => None,
2755 }
2756 }
2757
2758 pub fn resolve_list(&self, names: &[FontName], lang: &Lang) -> Option<FontNames> {
2762 if names
2763 .iter()
2764 .any(|n| ["system-ui", "serif", "sans-serif", "monospace", "cursive", "fantasy"].contains(&&**n))
2765 {
2766 let mut r = FontNames(Vec::with_capacity(names.len()));
2767 for name in names {
2768 match self.resolve(name, lang) {
2769 Some(n) => r.push(n),
2770 None => {
2771 if name == "system-ui" {
2772 r.extend(self.system_ui(lang));
2773 } else {
2774 r.push(name.clone())
2775 }
2776 }
2777 }
2778 }
2779 Some(r)
2780 } else {
2781 None
2782 }
2783 }
2784}
2785
2786#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2787pub(crate) enum WeakFontBytes {
2788 Ipc(WeakIpcBytes),
2789 Arc(std::sync::Weak<Vec<u8>>),
2790 Static(&'static [u8]),
2791 Mmap(std::sync::Weak<SystemFontBytes>),
2792}
2793#[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2794impl WeakFontBytes {
2795 pub(crate) fn upgrade(&self) -> Option<FontBytes> {
2796 match self {
2797 WeakFontBytes::Ipc(weak) => Some(FontBytes(FontBytesImpl::Ipc(weak.upgrade()?))),
2798 WeakFontBytes::Arc(weak) => Some(FontBytes(FontBytesImpl::Arc(weak.upgrade()?))),
2799 WeakFontBytes::Static(b) => Some(FontBytes(FontBytesImpl::Static(b))),
2800 WeakFontBytes::Mmap(weak) => Some(FontBytes(FontBytesImpl::System(weak.upgrade()?))),
2801 }
2802 }
2803
2804 pub(crate) fn strong_count(&self) -> usize {
2805 match self {
2806 WeakFontBytes::Ipc(weak) => weak.strong_count(),
2807 WeakFontBytes::Arc(weak) => weak.strong_count(),
2808 WeakFontBytes::Static(_) => 1,
2809 WeakFontBytes::Mmap(weak) => weak.strong_count(),
2810 }
2811 }
2812}
2813
2814struct SystemFontBytes {
2815 path: std::path::PathBuf,
2816 mmap: IpcBytes,
2817}
2818
2819#[derive(Clone)]
2820enum FontBytesImpl {
2821 Ipc(IpcBytes),
2823 Arc(Arc<Vec<u8>>),
2824 Static(&'static [u8]),
2825 System(Arc<SystemFontBytes>),
2826}
2827#[derive(Clone)]
2829pub struct FontBytes(FontBytesImpl);
2830impl FontBytes {
2831 pub fn from_ipc(bytes: IpcBytes) -> Self {
2833 Self(FontBytesImpl::Ipc(bytes))
2834 }
2835
2836 pub fn from_vec(bytes: Vec<u8>) -> io::Result<Self> {
2838 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_vec_blocking(bytes)?)))
2839 }
2840
2841 pub fn from_static(bytes: &'static [u8]) -> Self {
2843 Self(FontBytesImpl::Static(bytes))
2844 }
2845
2846 pub fn from_arc(bytes: Arc<Vec<u8>>) -> Self {
2850 Self(FontBytesImpl::Arc(bytes))
2851 }
2852
2853 pub fn from_file(path: PathBuf) -> io::Result<Self> {
2855 let path = dunce::canonicalize(path)?;
2856
2857 #[cfg(windows)]
2858 {
2859 use windows::Win32::{Foundation::MAX_PATH, System::SystemInformation::GetSystemWindowsDirectoryW};
2860 let mut buffer = [0u16; MAX_PATH as usize];
2861 let len = unsafe { GetSystemWindowsDirectoryW(Some(&mut buffer)) };
2863 let fonts_dir = String::from_utf16_lossy(&buffer[..len as usize]);
2864 if path.starts_with(fonts_dir) {
2866 return unsafe { load_from_system(path) };
2868 }
2869 }
2870 #[cfg(target_os = "macos")]
2871 if path.starts_with("/System/Library/Fonts/") || path.starts_with("/Library/Fonts/") {
2872 return unsafe { load_from_system(path) };
2874 }
2875 #[cfg(target_os = "android")]
2876 if path.starts_with("/system/fonts/") || path.starts_with("/system/font/") || path.starts_with("/system/product/fonts/") {
2877 return unsafe { load_from_system(path) };
2879 }
2880 #[cfg(unix)]
2881 if path.starts_with("/usr/share/fonts/") {
2882 return unsafe { load_from_system(path) };
2884 }
2885
2886 #[cfg(ipc)]
2887 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2888 let mmap = unsafe { IpcBytes::open_memmap_blocking(path.clone(), None) }?;
2890 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2891 }
2892
2893 #[cfg(all(not(ipc), not(target_arch = "wasm32")))]
2894 unsafe fn load_from_system(path: PathBuf) -> io::Result<FontBytes> {
2895 let mmap = IpcBytes::from_path_blocking(&path)?;
2896 Ok(FontBytes(FontBytesImpl::System(Arc::new(SystemFontBytes { path, mmap }))))
2897 }
2898
2899 Ok(Self(FontBytesImpl::Ipc(IpcBytes::from_path_blocking(&path)?)))
2900 }
2901
2902 #[cfg(ipc)]
2909 pub unsafe fn from_file_mmap(path: PathBuf) -> std::io::Result<Self> {
2910 let ipc = unsafe { IpcBytes::open_memmap_blocking(path, None) }?;
2912 Ok(Self(FontBytesImpl::Ipc(ipc)))
2913 }
2914
2915 #[cfg(ipc)]
2919 pub fn mmap_path(&self) -> Option<&std::path::Path> {
2920 if let FontBytesImpl::System(m) = &self.0 {
2921 Some(&m.path)
2922 } else {
2923 None
2924 }
2925 }
2926
2927 pub fn to_ipc(&self) -> io::Result<IpcFontBytes> {
2929 Ok(if let FontBytesImpl::System(m) = &self.0 {
2930 IpcFontBytes::System(m.path.clone())
2931 } else {
2932 IpcFontBytes::Bytes(self.to_ipc_bytes()?)
2933 })
2934 }
2935
2936 pub fn to_ipc_bytes(&self) -> io::Result<IpcBytes> {
2938 match &self.0 {
2939 FontBytesImpl::Ipc(b) => Ok(b.clone()),
2940 FontBytesImpl::Arc(b) => IpcBytes::from_slice_blocking(b),
2941 FontBytesImpl::Static(b) => IpcBytes::from_slice_blocking(b),
2942 FontBytesImpl::System(m) => IpcBytes::from_slice_blocking(&m.mmap[..]),
2943 }
2944 }
2945
2946 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
2947 pub(crate) fn downgrade(&self) -> WeakFontBytes {
2948 match &self.0 {
2949 FontBytesImpl::Ipc(ipc) => WeakFontBytes::Ipc(ipc.downgrade()),
2950 FontBytesImpl::Arc(arc) => WeakFontBytes::Arc(Arc::downgrade(arc)),
2951 FontBytesImpl::Static(b) => WeakFontBytes::Static(b),
2952 FontBytesImpl::System(arc) => WeakFontBytes::Mmap(Arc::downgrade(arc)),
2953 }
2954 }
2955}
2956impl std::ops::Deref for FontBytes {
2957 type Target = [u8];
2958
2959 fn deref(&self) -> &Self::Target {
2960 match &self.0 {
2961 FontBytesImpl::Ipc(b) => &b[..],
2962 FontBytesImpl::Arc(b) => &b[..],
2963 FontBytesImpl::Static(b) => b,
2964 FontBytesImpl::System(m) => &m.mmap[..],
2965 }
2966 }
2967}
2968impl fmt::Debug for FontBytes {
2969 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2970 let mut b = f.debug_struct("FontBytes");
2971 b.field(
2972 ".kind",
2973 &match &self.0 {
2974 FontBytesImpl::Ipc(_) => "IpcBytes",
2975 FontBytesImpl::Arc(_) => "Arc",
2976 FontBytesImpl::Static(_) => "Static",
2977 FontBytesImpl::System(_) => "Mmap",
2978 },
2979 );
2980 b.field(".len", &self.len().bytes());
2981 if let FontBytesImpl::System(m) = &self.0 {
2982 b.field(".path", &m.path);
2983 }
2984
2985 b.finish()
2986 }
2987}
2988
2989#[derive(Debug, Clone)]
2990enum FontSource {
2991 File(PathBuf, u32),
2992 Memory(FontBytes, u32),
2993 Alias(FontName),
2994}
2995
2996#[derive(Debug, Clone)]
2998pub struct CustomFont {
2999 name: FontName,
3000 source: FontSource,
3001 stretch: FontStretch,
3002 style: FontStyle,
3003 weight: FontWeight,
3004}
3005impl CustomFont {
3006 pub fn from_file<N: Into<FontName>, P: Into<PathBuf>>(name: N, path: P, font_index: u32) -> Self {
3014 CustomFont {
3015 name: name.into(),
3016 source: FontSource::File(path.into(), font_index),
3017 stretch: FontStretch::NORMAL,
3018 style: FontStyle::Normal,
3019 weight: FontWeight::NORMAL,
3020 }
3021 }
3022
3023 pub fn from_bytes<N: Into<FontName>>(name: N, data: FontBytes, font_index: u32) -> Self {
3031 CustomFont {
3032 name: name.into(),
3033 source: FontSource::Memory(data, font_index),
3034 stretch: FontStretch::NORMAL,
3035 style: FontStyle::Normal,
3036 weight: FontWeight::NORMAL,
3037 }
3038 }
3039
3040 pub fn from_other<N: Into<FontName>, O: Into<FontName>>(name: N, other_font: O) -> Self {
3046 CustomFont {
3047 name: name.into(),
3048 source: FontSource::Alias(other_font.into()),
3049 stretch: FontStretch::NORMAL,
3050 style: FontStyle::Normal,
3051 weight: FontWeight::NORMAL,
3052 }
3053 }
3054
3055 pub fn stretch(mut self, stretch: FontStretch) -> Self {
3059 self.stretch = stretch;
3060 self
3061 }
3062
3063 pub fn style(mut self, style: FontStyle) -> Self {
3067 self.style = style;
3068 self
3069 }
3070
3071 pub fn weight(mut self, weight: FontWeight) -> Self {
3075 self.weight = weight;
3076 self
3077 }
3078}
3079
3080#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, Transitionable)]
3084#[serde(transparent)]
3085pub struct FontStretch(pub f32);
3086impl fmt::Debug for FontStretch {
3087 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3088 let name = self.name();
3089 if name.is_empty() {
3090 f.debug_tuple("FontStretch").field(&self.0).finish()
3091 } else {
3092 if f.alternate() {
3093 write!(f, "FontStretch::")?;
3094 }
3095 write!(f, "{name}")
3096 }
3097 }
3098}
3099impl PartialOrd for FontStretch {
3100 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3101 Some(self.cmp(other))
3102 }
3103}
3104impl Ord for FontStretch {
3105 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3106 about_eq_ord(self.0, other.0, EQ_GRANULARITY)
3107 }
3108}
3109impl PartialEq for FontStretch {
3110 fn eq(&self, other: &Self) -> bool {
3111 about_eq(self.0, other.0, EQ_GRANULARITY)
3112 }
3113}
3114impl Eq for FontStretch {}
3115impl std::hash::Hash for FontStretch {
3116 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3117 about_eq_hash(self.0, EQ_GRANULARITY, state)
3118 }
3119}
3120impl Default for FontStretch {
3121 fn default() -> FontStretch {
3122 FontStretch::NORMAL
3123 }
3124}
3125impl FontStretch {
3126 pub const ULTRA_CONDENSED: FontStretch = FontStretch(0.5);
3128 pub const EXTRA_CONDENSED: FontStretch = FontStretch(0.625);
3130 pub const CONDENSED: FontStretch = FontStretch(0.75);
3132 pub const SEMI_CONDENSED: FontStretch = FontStretch(0.875);
3134 pub const NORMAL: FontStretch = FontStretch(1.0);
3136 pub const SEMI_EXPANDED: FontStretch = FontStretch(1.125);
3138 pub const EXPANDED: FontStretch = FontStretch(1.25);
3140 pub const EXTRA_EXPANDED: FontStretch = FontStretch(1.5);
3142 pub const ULTRA_EXPANDED: FontStretch = FontStretch(2.0);
3144
3145 pub fn name(self) -> &'static str {
3147 macro_rules! name {
3148 ($($CONST:ident;)+) => {$(
3149 if self == Self::$CONST {
3150 return stringify!($CONST);
3151 }
3152 )+}
3153 }
3154 name! {
3155 ULTRA_CONDENSED;
3156 EXTRA_CONDENSED;
3157 CONDENSED;
3158 SEMI_CONDENSED;
3159 NORMAL;
3160 SEMI_EXPANDED;
3161 EXPANDED;
3162 EXTRA_EXPANDED;
3163 ULTRA_EXPANDED;
3164 }
3165 ""
3166 }
3167}
3168impl_from_and_into_var! {
3169 fn from(fct: Factor) -> FontStretch {
3170 FontStretch(fct.0)
3171 }
3172 fn from(pct: FactorPercent) -> FontStretch {
3173 FontStretch(pct.fct().0)
3174 }
3175 fn from(fct: f32) -> FontStretch {
3176 FontStretch(fct)
3177 }
3178}
3179impl From<ttf_parser::Width> for FontStretch {
3180 fn from(value: ttf_parser::Width) -> Self {
3181 use ttf_parser::Width::*;
3182 match value {
3183 UltraCondensed => FontStretch::ULTRA_CONDENSED,
3184 ExtraCondensed => FontStretch::EXTRA_CONDENSED,
3185 Condensed => FontStretch::CONDENSED,
3186 SemiCondensed => FontStretch::SEMI_CONDENSED,
3187 Normal => FontStretch::NORMAL,
3188 SemiExpanded => FontStretch::SEMI_EXPANDED,
3189 Expanded => FontStretch::EXPANDED,
3190 ExtraExpanded => FontStretch::EXTRA_EXPANDED,
3191 UltraExpanded => FontStretch::ULTRA_EXPANDED,
3192 }
3193 }
3194}
3195impl From<FontStretch> for ttf_parser::Width {
3196 fn from(value: FontStretch) -> Self {
3197 if value <= FontStretch::ULTRA_CONDENSED {
3198 ttf_parser::Width::UltraCondensed
3199 } else if value <= FontStretch::EXTRA_CONDENSED {
3200 ttf_parser::Width::ExtraCondensed
3201 } else if value <= FontStretch::CONDENSED {
3202 ttf_parser::Width::Condensed
3203 } else if value <= FontStretch::SEMI_CONDENSED {
3204 ttf_parser::Width::SemiCondensed
3205 } else if value <= FontStretch::NORMAL {
3206 ttf_parser::Width::Normal
3207 } else if value <= FontStretch::SEMI_EXPANDED {
3208 ttf_parser::Width::SemiExpanded
3209 } else if value <= FontStretch::EXPANDED {
3210 ttf_parser::Width::Expanded
3211 } else if value <= FontStretch::EXTRA_EXPANDED {
3212 ttf_parser::Width::ExtraExpanded
3213 } else {
3214 ttf_parser::Width::UltraExpanded
3215 }
3216 }
3217}
3218
3219#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize)]
3221pub enum FontStyle {
3222 #[default]
3224 Normal,
3225 Italic,
3227 Oblique,
3229}
3230impl fmt::Debug for FontStyle {
3231 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3232 if f.alternate() {
3233 write!(f, "FontStyle::")?;
3234 }
3235 match self {
3236 Self::Normal => write!(f, "Normal"),
3237 Self::Italic => write!(f, "Italic"),
3238 Self::Oblique => write!(f, "Oblique"),
3239 }
3240 }
3241}
3242impl From<ttf_parser::Style> for FontStyle {
3243 fn from(value: ttf_parser::Style) -> Self {
3244 use ttf_parser::Style::*;
3245 match value {
3246 Normal => FontStyle::Normal,
3247 Italic => FontStyle::Italic,
3248 Oblique => FontStyle::Oblique,
3249 }
3250 }
3251}
3252
3253impl From<FontStyle> for ttf_parser::Style {
3254 fn from(value: FontStyle) -> Self {
3255 match value {
3256 FontStyle::Normal => Self::Normal,
3257 FontStyle::Italic => Self::Italic,
3258 FontStyle::Oblique => Self::Oblique,
3259 }
3260 }
3261}
3262
3263#[derive(Clone, Copy, Transitionable, serde::Serialize, serde::Deserialize)]
3266pub struct FontWeight(pub f32);
3267impl Default for FontWeight {
3268 fn default() -> FontWeight {
3269 FontWeight::NORMAL
3270 }
3271}
3272impl fmt::Debug for FontWeight {
3273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3274 let name = self.name();
3275 if name.is_empty() {
3276 f.debug_tuple("FontWeight").field(&self.0).finish()
3277 } else {
3278 if f.alternate() {
3279 write!(f, "FontWeight::")?;
3280 }
3281 write!(f, "{name}")
3282 }
3283 }
3284}
3285impl PartialOrd for FontWeight {
3286 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3287 Some(self.cmp(other))
3288 }
3289}
3290impl Ord for FontWeight {
3291 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3292 about_eq_ord(self.0, other.0, EQ_GRANULARITY_100)
3293 }
3294}
3295impl PartialEq for FontWeight {
3296 fn eq(&self, other: &Self) -> bool {
3297 about_eq(self.0, other.0, EQ_GRANULARITY_100)
3298 }
3299}
3300impl Eq for FontWeight {}
3301impl std::hash::Hash for FontWeight {
3302 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
3303 about_eq_hash(self.0, EQ_GRANULARITY_100, state)
3304 }
3305}
3306impl FontWeight {
3307 pub const THIN: FontWeight = FontWeight(100.0);
3309 pub const EXTRA_LIGHT: FontWeight = FontWeight(200.0);
3311 pub const LIGHT: FontWeight = FontWeight(300.0);
3313 pub const NORMAL: FontWeight = FontWeight(400.0);
3315 pub const MEDIUM: FontWeight = FontWeight(500.0);
3317 pub const SEMIBOLD: FontWeight = FontWeight(600.0);
3319 pub const BOLD: FontWeight = FontWeight(700.0);
3321 pub const EXTRA_BOLD: FontWeight = FontWeight(800.0);
3323 pub const BLACK: FontWeight = FontWeight(900.0);
3325
3326 pub fn name(self) -> &'static str {
3328 macro_rules! name {
3329 ($($CONST:ident;)+) => {$(
3330 if self == Self::$CONST {
3331 return stringify!($CONST);
3332 }
3333 )+}
3334 }
3335 name! {
3336 THIN;
3337 EXTRA_LIGHT;
3338 LIGHT;
3339 NORMAL;
3340 MEDIUM;
3341 SEMIBOLD;
3342 BOLD;
3343 EXTRA_BOLD;
3344 BLACK;
3345 }
3346 ""
3347 }
3348}
3349impl_from_and_into_var! {
3350 fn from(weight: u32) -> FontWeight {
3351 FontWeight(weight as f32)
3352 }
3353 fn from(weight: f32) -> FontWeight {
3354 FontWeight(weight)
3355 }
3356}
3357impl From<ttf_parser::Weight> for FontWeight {
3358 fn from(value: ttf_parser::Weight) -> Self {
3359 use ttf_parser::Weight::*;
3360 match value {
3361 Thin => FontWeight::THIN,
3362 ExtraLight => FontWeight::EXTRA_LIGHT,
3363 Light => FontWeight::LIGHT,
3364 Normal => FontWeight::NORMAL,
3365 Medium => FontWeight::MEDIUM,
3366 SemiBold => FontWeight::SEMIBOLD,
3367 Bold => FontWeight::BOLD,
3368 ExtraBold => FontWeight::EXTRA_BOLD,
3369 Black => FontWeight::BLACK,
3370 Other(o) => FontWeight(o as f32),
3371 }
3372 }
3373}
3374impl From<FontWeight> for ttf_parser::Weight {
3375 fn from(value: FontWeight) -> Self {
3376 ttf_parser::Weight::from(value.0 as u16)
3377 }
3378}
3379
3380#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3382pub enum LineBreak {
3383 Auto,
3385 Loose,
3387 Normal,
3389 Strict,
3391 Anywhere,
3393}
3394impl Default for LineBreak {
3395 fn default() -> Self {
3397 LineBreak::Auto
3398 }
3399}
3400impl fmt::Debug for LineBreak {
3401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3402 if f.alternate() {
3403 write!(f, "LineBreak::")?;
3404 }
3405 match self {
3406 LineBreak::Auto => write!(f, "Auto"),
3407 LineBreak::Loose => write!(f, "Loose"),
3408 LineBreak::Normal => write!(f, "Normal"),
3409 LineBreak::Strict => write!(f, "Strict"),
3410 LineBreak::Anywhere => write!(f, "Anywhere"),
3411 }
3412 }
3413}
3414
3415#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3420#[non_exhaustive]
3421pub enum ParagraphBreak {
3422 #[default]
3424 None,
3425 Line,
3427}
3428impl fmt::Debug for ParagraphBreak {
3429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3430 if f.alternate() {
3431 write!(f, "ParagraphBreak::")?;
3432 }
3433 match self {
3434 ParagraphBreak::None => write!(f, "None"),
3435 ParagraphBreak::Line => write!(f, "Line"),
3436 }
3437 }
3438}
3439
3440#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3442pub enum Hyphens {
3443 None,
3445 Manual,
3450 Auto,
3452}
3453impl Default for Hyphens {
3454 fn default() -> Self {
3456 Hyphens::Auto
3457 }
3458}
3459impl fmt::Debug for Hyphens {
3460 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3461 if f.alternate() {
3462 write!(f, "Hyphens::")?;
3463 }
3464 match self {
3465 Hyphens::None => write!(f, "None"),
3466 Hyphens::Manual => write!(f, "Manual"),
3467 Hyphens::Auto => write!(f, "Auto"),
3468 }
3469 }
3470}
3471
3472#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3478pub enum WordBreak {
3479 Normal,
3481 BreakAll,
3483 KeepAll,
3485}
3486impl Default for WordBreak {
3487 fn default() -> Self {
3489 WordBreak::Normal
3490 }
3491}
3492impl fmt::Debug for WordBreak {
3493 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3494 if f.alternate() {
3495 write!(f, "WordBreak::")?;
3496 }
3497 match self {
3498 WordBreak::Normal => write!(f, "Normal"),
3499 WordBreak::BreakAll => write!(f, "BreakAll"),
3500 WordBreak::KeepAll => write!(f, "KeepAll"),
3501 }
3502 }
3503}
3504
3505#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
3507pub enum Justify {
3508 Auto,
3512 InterWord,
3514 InterLetter,
3516}
3517impl Default for Justify {
3518 fn default() -> Self {
3520 Justify::Auto
3521 }
3522}
3523impl Justify {
3524 pub fn resolve(self, lang: &Lang) -> Self {
3526 match self {
3527 Self::Auto => match lang.language.as_str() {
3528 "zh" | "ja" | "ko" => Self::InterLetter,
3529 _ => Self::InterWord,
3530 },
3531 m => m,
3532 }
3533 }
3534}
3535impl fmt::Debug for Justify {
3536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3537 if f.alternate() {
3538 write!(f, "Justify::")?;
3539 }
3540 match self {
3541 Justify::Auto => write!(f, "Auto"),
3542 Justify::InterWord => write!(f, "InterWord"),
3543 Justify::InterLetter => write!(f, "InterLetter"),
3544 }
3545 }
3546}
3547
3548#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3556#[non_exhaustive]
3557pub struct FontFaceMetrics {
3558 pub units_per_em: u32,
3562
3563 pub ascent: f32,
3565
3566 pub descent: f32,
3572
3573 pub line_gap: f32,
3575
3576 pub underline_position: f32,
3579
3580 pub underline_thickness: f32,
3582
3583 pub cap_height: f32,
3585
3586 pub x_height: f32,
3589
3590 pub bounds: euclid::Rect<f32, ()>,
3594}
3595impl FontFaceMetrics {
3596 pub fn sized(&self, font_size_px: Px) -> FontMetrics {
3598 let size_scale = 1.0 / self.units_per_em as f32 * font_size_px.0 as f32;
3599 let s = move |f: f32| Px((f * size_scale).round() as i32);
3600 FontMetrics {
3601 size_scale,
3602 ascent: s(self.ascent),
3603 descent: s(self.descent),
3604 line_gap: s(self.line_gap),
3605 underline_position: s(self.underline_position),
3606 underline_thickness: s(self.underline_thickness),
3607 cap_height: s(self.cap_height),
3608 x_height: (s(self.x_height)),
3609 bounds: {
3610 let b = self.bounds;
3611 PxRect::new(
3612 PxPoint::new(s(b.origin.x), s(b.origin.y)),
3613 PxSize::new(s(b.size.width), s(b.size.height)),
3614 )
3615 },
3616 }
3617 }
3618}
3619
3620#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
3624#[non_exhaustive]
3625pub struct FontMetrics {
3626 pub size_scale: f32,
3628
3629 pub ascent: Px,
3631
3632 pub descent: Px,
3638
3639 pub line_gap: Px,
3641
3642 pub underline_position: Px,
3645
3646 pub underline_thickness: Px,
3648
3649 pub cap_height: Px,
3651
3652 pub x_height: Px,
3654
3655 pub bounds: PxRect,
3659}
3660impl FontMetrics {
3661 pub fn line_height(&self) -> Px {
3663 self.ascent - self.descent + self.line_gap
3664 }
3665}
3666
3667#[derive(Clone)]
3669pub enum TextTransformFn {
3670 None,
3672 Uppercase,
3674 Lowercase,
3676 Custom(Arc<dyn Fn(&Txt) -> Cow<Txt> + Send + Sync>),
3678}
3679impl TextTransformFn {
3680 pub fn transform<'t>(&self, text: &'t Txt) -> Cow<'t, Txt> {
3684 match self {
3685 TextTransformFn::None => Cow::Borrowed(text),
3686 TextTransformFn::Uppercase => {
3687 if text.chars().any(|c| !c.is_uppercase()) {
3688 Cow::Owned(text.to_uppercase().into())
3689 } else {
3690 Cow::Borrowed(text)
3691 }
3692 }
3693 TextTransformFn::Lowercase => {
3694 if text.chars().any(|c| !c.is_lowercase()) {
3695 Cow::Owned(text.to_lowercase().into())
3696 } else {
3697 Cow::Borrowed(text)
3698 }
3699 }
3700 TextTransformFn::Custom(fn_) => fn_(text),
3701 }
3702 }
3703
3704 pub fn custom(fn_: impl Fn(&Txt) -> Cow<Txt> + Send + Sync + 'static) -> Self {
3706 TextTransformFn::Custom(Arc::new(fn_))
3707 }
3708}
3709impl fmt::Debug for TextTransformFn {
3710 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3711 if f.alternate() {
3712 write!(f, "TextTransformFn::")?;
3713 }
3714 match self {
3715 TextTransformFn::None => write!(f, "None"),
3716 TextTransformFn::Uppercase => write!(f, "Uppercase"),
3717 TextTransformFn::Lowercase => write!(f, "Lowercase"),
3718 TextTransformFn::Custom(_) => write!(f, "Custom"),
3719 }
3720 }
3721}
3722impl PartialEq for TextTransformFn {
3723 fn eq(&self, other: &Self) -> bool {
3724 match (self, other) {
3725 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
3726 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
3727 }
3728 }
3729}
3730
3731#[derive(Default, Copy, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3733pub enum WhiteSpace {
3734 #[default]
3736 Preserve,
3737 Merge,
3740 MergeParagraph,
3743 MergeAll,
3745}
3746impl WhiteSpace {
3747 pub fn transform(self, text: &Txt) -> Cow<'_, Txt> {
3751 match self {
3752 WhiteSpace::Preserve => Cow::Borrowed(text),
3753 WhiteSpace::Merge => {
3754 let mut prev_i = 0;
3756 for line in text.split_inclusive('\n') {
3757 let line_exclusive = line.trim_end_matches('\n').trim_end_matches('\r');
3759 let line_trim = line_exclusive.trim();
3760 let mut merge = line_trim.len() != line_exclusive.len() || line_trim.is_empty();
3761
3762 if !merge {
3764 let mut prev_is_space = true; for c in line.chars() {
3766 let is_space = c.is_whitespace();
3767 if prev_is_space && is_space {
3768 merge = true;
3769 break;
3770 }
3771 prev_is_space = is_space;
3772 }
3773 }
3774
3775 if !merge {
3776 prev_i += line.len();
3777 continue;
3778 }
3779
3780 let mut out = String::with_capacity(text.len() - 1);
3782 out.push_str(&text[..prev_i]);
3783
3784 let mut chars = text[prev_i..].chars();
3785 let mut prev_is_space = true;
3786 let mut prev_is_break = true;
3787 while let Some(c) = chars.next() {
3788 if c == '\r'
3789 && let Some(nc) = chars.next()
3790 {
3791 if nc == '\n' {
3792 if !prev_is_break && !out.is_empty() {
3793 out.push('\n');
3794 }
3795 prev_is_break = true;
3796 prev_is_space = true;
3797 } else {
3798 out.push(c);
3799 out.push(nc);
3800 prev_is_break = false;
3801 prev_is_space = nc.is_whitespace();
3802 }
3803 } else if c == '\n' {
3804 if !prev_is_break && !out.is_empty() {
3805 out.push('\n');
3806 }
3807 prev_is_break = true;
3808 prev_is_space = true;
3809 } else if c.is_whitespace() {
3810 if prev_is_space {
3811 continue;
3812 }
3813 out.push(' ');
3814 prev_is_space = true;
3815 } else {
3816 out.push(c);
3817 prev_is_space = false;
3818 prev_is_break = false;
3819 }
3820 }
3821
3822 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3824 out.truncate(i + c.len_utf8());
3825 }
3826
3827 return Cow::Owned(out.into());
3828 }
3829 Cow::Borrowed(text)
3830 }
3831 WhiteSpace::MergeParagraph => {
3832 let mut merge = text.contains('\n') || text.chars().last().unwrap_or('\0').is_whitespace();
3835 if !merge {
3836 let mut prev_is_space = true;
3837 for c in text.chars() {
3838 let is_space = c.is_whitespace();
3839 if prev_is_space && is_space {
3840 merge = true;
3841 break;
3842 }
3843 prev_is_space = is_space;
3844 }
3845 }
3846
3847 if merge {
3848 let mut out = String::with_capacity(text.len());
3849 let mut prev_is_break = false;
3850 for line in text.lines() {
3851 let line = line.trim();
3852 let is_break = line.is_empty();
3853 if !prev_is_break && is_break && !out.is_empty() {
3854 out.push('\n');
3855 }
3856 if !prev_is_break && !is_break && !out.is_empty() {
3857 out.push(' ');
3858 }
3859 prev_is_break = is_break;
3860
3861 let mut prev_is_space = false;
3862 for c in line.chars() {
3863 let is_space = c.is_whitespace();
3864 if is_space {
3865 if !prev_is_space {
3866 out.push(' ');
3867 }
3868 } else {
3869 out.push(c);
3870 }
3871 prev_is_space = is_space;
3872 }
3873 }
3874
3875 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3877 out.truncate(i + c.len_utf8());
3878 }
3879
3880 return Cow::Owned(out.into());
3881 }
3882 Cow::Borrowed(text)
3883 }
3884 WhiteSpace::MergeAll => {
3885 let mut prev_i = 0;
3887 let mut prev_is_space = true; for (i, c) in text.char_indices() {
3889 let is_space = c.is_whitespace();
3890 if prev_is_space && is_space || c == '\n' {
3891 if !prev_is_space {
3892 debug_assert_eq!(c, '\n');
3893 prev_i += c.len_utf8();
3894 prev_is_space = true;
3895 }
3896 let mut out = String::with_capacity(text.len() - 1);
3898 out.push_str(&text[..prev_i]);
3900 if !out.is_empty() {
3901 out.push(' ');
3902 }
3903 for c in text[(i + c.len_utf8())..].chars() {
3905 let is_space = c.is_whitespace();
3906 if prev_is_space && is_space {
3907 continue;
3908 }
3909 out.push(if is_space { ' ' } else { c });
3910 prev_is_space = is_space;
3911 }
3912
3913 if let Some((i, c)) = out.char_indices().rev().find(|(_, c)| !c.is_whitespace()) {
3915 out.truncate(i + c.len_utf8());
3916 }
3917
3918 return Cow::Owned(out.into());
3919 }
3920 prev_i = i;
3921 prev_is_space = is_space;
3922 }
3923
3924 let out = text.trim_end();
3928 if out.len() != text.len() {
3929 return Cow::Owned(Txt::from_str(out));
3930 }
3931
3932 Cow::Borrowed(text)
3933 }
3934 }
3935 }
3936}
3937impl fmt::Debug for WhiteSpace {
3938 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3939 if f.alternate() {
3940 write!(f, "WhiteSpace::")?;
3941 }
3942 match self {
3943 WhiteSpace::Preserve => write!(f, "Preserve"),
3944 WhiteSpace::Merge => write!(f, "Merge"),
3945 WhiteSpace::MergeAll => write!(f, "MergeAll"),
3946 WhiteSpace::MergeParagraph => write!(f, "MergeParagraph"),
3947 }
3948 }
3949}
3950
3951#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
3953pub struct CaretIndex {
3954 pub index: usize,
3958 pub line: usize,
3968}
3969
3970impl PartialEq for CaretIndex {
3971 fn eq(&self, other: &Self) -> bool {
3972 self.index == other.index
3973 }
3974}
3975impl Eq for CaretIndex {}
3976impl CaretIndex {
3977 pub const ZERO: CaretIndex = CaretIndex { index: 0, line: 0 };
3979}
3980impl PartialOrd for CaretIndex {
3981 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
3982 Some(self.cmp(other))
3983 }
3984}
3985impl Ord for CaretIndex {
3986 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
3987 self.index.cmp(&other.index)
3988 }
3989}
3990
3991#[derive(Debug, Clone)]
3993#[non_exhaustive]
3994pub enum FontLoadingError {
3995 UnknownFormat,
3997 NoSuchFontInCollection,
4002 Parse(ttf_parser::FaceParsingError),
4004 NoFilesystem,
4007 Io(Arc<std::io::Error>),
4009}
4010impl PartialEq for FontLoadingError {
4011 fn eq(&self, other: &Self) -> bool {
4012 match (self, other) {
4013 (Self::Io(l0), Self::Io(r0)) => Arc::ptr_eq(l0, r0),
4014 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
4015 }
4016 }
4017}
4018impl From<std::io::Error> for FontLoadingError {
4019 fn from(error: std::io::Error) -> FontLoadingError {
4020 Self::Io(Arc::new(error))
4021 }
4022}
4023impl fmt::Display for FontLoadingError {
4024 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
4025 match self {
4026 Self::UnknownFormat => write!(f, "unknown format"),
4027 Self::NoSuchFontInCollection => write!(f, "no such font in the collection"),
4028 Self::NoFilesystem => write!(f, "no filesystem present"),
4029 Self::Parse(e) => fmt::Display::fmt(e, f),
4030 Self::Io(e) => fmt::Display::fmt(e, f),
4031 }
4032 }
4033}
4034impl std::error::Error for FontLoadingError {
4035 fn cause(&self) -> Option<&dyn std::error::Error> {
4036 match self {
4037 FontLoadingError::Parse(e) => Some(e),
4038 FontLoadingError::Io(e) => Some(e),
4039 _ => None,
4040 }
4041 }
4042}
4043
4044#[cfg(test)]
4045mod tests {
4046 use zng_app::APP;
4047
4048 use super::*;
4049
4050 #[test]
4051 fn generic_fonts_default() {
4052 let _app = APP.minimal().run_headless(false);
4053
4054 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(und)))
4055 }
4056
4057 #[test]
4058 fn generic_fonts_fallback() {
4059 let _app = APP.minimal().run_headless(false);
4060
4061 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(en_US)));
4062 assert_eq!(FontName::sans_serif(), GenericFonts {}.sans_serif(&lang!(es)));
4063 }
4064
4065 #[test]
4066 fn generic_fonts_get1() {
4067 let mut app = APP.minimal().run_headless(false);
4068 GenericFonts {}.set_sans_serif(lang!(en_US), "Test Value");
4069 app.update(false).assert_wait();
4070
4071 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
4072 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
4073 }
4074
4075 #[test]
4076 fn generic_fonts_get2() {
4077 let mut app = APP.minimal().run_headless(false);
4078 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
4079 app.update(false).assert_wait();
4080
4081 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Test Value");
4082 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
4083 }
4084
4085 #[test]
4086 fn generic_fonts_get_best() {
4087 let mut app = APP.minimal().run_headless(false);
4088 GenericFonts {}.set_sans_serif(lang!(en), "Test Value");
4089 GenericFonts {}.set_sans_serif(lang!(en_US), "Best");
4090 app.update(false).assert_wait();
4091
4092 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "Best");
4093 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en")), "Test Value");
4094 assert_eq!(&GenericFonts {}.sans_serif(&lang!("und")), "sans-serif");
4095 }
4096
4097 #[test]
4098 fn generic_fonts_get_no_lang_match() {
4099 let mut app = APP.minimal().run_headless(false);
4100 GenericFonts {}.set_sans_serif(lang!(es_US), "Test Value");
4101 app.update(false).assert_wait();
4102
4103 assert_eq!(&GenericFonts {}.sans_serif(&lang!("en-US")), "sans-serif");
4104 assert_eq!(&GenericFonts {}.sans_serif(&lang!("es")), "Test Value");
4105 }
4106
4107 #[test]
4108 fn white_space_merge() {
4109 macro_rules! test {
4110 ($input:tt, $output:tt) => {
4111 let input = Txt::from($input);
4112 let output = WhiteSpace::Merge.transform(&input);
4113 assert_eq!($output, output.as_str());
4114
4115 let input = input.replace('\n', "\r\n");
4116 let output = WhiteSpace::Merge.transform(&Txt::from(input)).replace("\r\n", "\n");
4117 assert_eq!($output, output.as_str());
4118 };
4119 }
4120 test!("a b\n\nc", "a b\nc");
4121 test!("a b\nc", "a b\nc");
4122 test!(" a b\nc\n \n", "a b\nc");
4123 test!(" \n a b\nc", "a b\nc");
4124 test!("a\n \nb", "a\nb");
4125 }
4126
4127 #[test]
4128 fn white_space_merge_paragraph() {
4129 macro_rules! test {
4130 ($input:tt, $output:tt) => {
4131 let input = Txt::from($input);
4132 let output = WhiteSpace::MergeParagraph.transform(&input);
4133 assert_eq!($output, output.as_str());
4134
4135 let input = input.replace('\n', "\r\n");
4136 let output = WhiteSpace::MergeParagraph.transform(&Txt::from(input)).replace("\r\n", "\n");
4137 assert_eq!($output, output.as_str());
4138 };
4139 }
4140 test!("a b\n\nc", "a b\nc");
4141 test!("a b\nc", "a b c");
4142 test!(" a b\nc\n \n", "a b c");
4143 test!(" \n a b\nc", "a b c");
4144 test!("a\n \nb", "a\nb");
4145 }
4146
4147 #[test]
4148 fn white_space_merge_all() {
4149 macro_rules! test {
4150 ($input:tt, $output:tt) => {
4151 let input = Txt::from($input);
4152 let output = WhiteSpace::MergeAll.transform(&input);
4153 assert_eq!($output, output.as_str());
4154 };
4155 }
4156 test!("a b\n\nc", "a b c");
4157 test!("a b\nc", "a b c");
4158 test!(" a b\nc\n \n", "a b c");
4159 test!(" \n a b\nc", "a b c");
4160 test!("a\n \nb", "a b");
4161 }
4162}