1use std::borrow::Cow;
2
3use densa_core::Color;
4use densa_text::{Font, FontBytes, FontError};
5use parley::fontique::{
6 Blob, Collection, CollectionOptions, FallbackKey, FamilyId, GenericFamily, Language, Script,
7 SourceCache,
8};
9use parley::{FontContext, FontData, FontFamily, LayoutContext, LineHeight, StyleProperty};
10use rustc_hash::FxHashMap;
11
12use crate::paragraph::{bytes_key, face_key, FaceKey, Paragraph};
13use crate::style::{
14 check_font_size, check_line_height, check_span, check_width, ParagraphError, ParagraphStyle,
15 Span,
16};
17
18const GENERICS: [GenericFamily; 9] = [
20 GenericFamily::Serif,
21 GenericFamily::SansSerif,
22 GenericFamily::Monospace,
23 GenericFamily::SystemUi,
24 GenericFamily::UiSerif,
25 GenericFamily::UiSansSerif,
26 GenericFamily::UiMonospace,
27 GenericFamily::UiRounded,
28 GenericFamily::Emoji,
29];
30
31const MONOSPACE: [GenericFamily; 2] = [GenericFamily::Monospace, GenericFamily::UiMonospace];
34
35const HAN_LOCALES: [&str; 4] = ["zh-TW", "zh-HK", "ja", "ko"];
37
38const SIMPLIFIED_CHINESE: [&str; 5] = [
42 "Microsoft YaHei UI",
43 "Microsoft YaHei",
44 "PingFang SC",
45 "Noto Sans CJK SC",
46 "Source Han Sans SC",
47];
48
49fn chain_han_fallbacks(collection: &mut Collection) {
54 let han = Script::from_bytes(*b"Hani");
55 let mut chain: Vec<FamilyId> = collection
56 .fallback_families(FallbackKey::new(han, None))
57 .collect();
58 let add = |chain: &mut Vec<FamilyId>, family: FamilyId| {
59 if !chain.contains(&family) {
60 chain.push(family);
61 }
62 };
63 for tag in HAN_LOCALES {
64 let Ok(locale) = Language::parse(tag) else {
65 continue;
66 };
67 let families: Vec<FamilyId> = collection
68 .fallback_families(FallbackKey::new(han, Some(&locale)))
69 .collect();
70 for family in families {
71 add(&mut chain, family);
72 }
73 }
74 for name in SIMPLIFIED_CHINESE {
75 if let Some(family) = collection.family_id(name) {
76 add(&mut chain, family);
77 }
78 }
79 collection.set_fallbacks(FallbackKey::new(han, None), chain.into_iter());
80}
81
82pub struct TextSystem {
89 fonts: FontContext,
90 layouts: LayoutContext<Color>,
91 faces: FxHashMap<FaceKey, Font>,
93}
94
95impl std::fmt::Debug for TextSystem {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("TextSystem")
99 .field("faces", &self.faces.len())
100 .finish_non_exhaustive()
101 }
102}
103
104impl TextSystem {
105 pub fn new() -> Result<Self, FontError> {
114 Self::build(cfg!(feature = "system-fonts"))
115 }
116
117 pub fn bundled() -> Result<Self, FontError> {
125 Self::build(false)
126 }
127
128 fn build(system_fonts: bool) -> Result<Self, FontError> {
129 let collection = Collection::new(CollectionOptions {
130 shared: false,
131 system_fonts,
132 });
133 let mut fonts = FontContext {
134 collection,
135 source_cache: SourceCache::default(),
136 };
137 let bundled = Font::bundled_mono()?;
138 let blob = Blob::new(bundled.shared_bytes());
139 let registered = fonts.collection.register_fonts(blob, None);
140 let Some(&(family, _)) = registered.first() else {
141 return Err(FontError::Parse);
142 };
143 let generics: &[GenericFamily] = if system_fonts { &MONOSPACE } else { &GENERICS };
144 for &generic in generics {
145 fonts
146 .collection
147 .set_generic_families(generic, std::iter::once(family));
148 }
149 if system_fonts {
150 chain_han_fallbacks(&mut fonts.collection);
151 }
152 let mut faces = FxHashMap::default();
153 faces.insert(bytes_key(bundled.bytes(), 0), bundled);
155 Ok(Self {
156 fonts,
157 layouts: LayoutContext::new(),
158 faces,
159 })
160 }
161
162 pub fn register_font(&mut self, data: FontBytes) -> Result<Vec<String>, FontError> {
167 let registered = self.fonts.collection.register_fonts(Blob::new(data), None);
168 if registered.is_empty() {
169 return Err(FontError::Parse);
170 }
171 let mut names = Vec::with_capacity(registered.len());
172 for (family, _) in ®istered {
173 if let Some(name) = self.fonts.collection.family_name(*family) {
175 names.push(name.to_owned());
176 }
177 }
178 Ok(names)
179 }
180
181 pub fn paragraph(
189 &mut self,
190 text: &str,
191 style: &ParagraphStyle,
192 spans: &[Span],
193 ) -> Result<Paragraph, ParagraphError> {
194 check_font_size(style.size_px)?;
195 check_line_height(style.line_height)?;
196 let max_width = check_width(style.max_width)?;
197 for span in spans {
198 check_span(text, span)?;
199 }
200
201 let mut builder = self
202 .layouts
203 .ranged_builder(&mut self.fonts, text, 1.0, true);
204 let family = FontFamily::Source(Cow::Borrowed(&style.family));
205 builder.push_default(StyleProperty::FontFamily(family));
206 builder.push_default(StyleProperty::FontSize(style.size_px));
207 let line_height = LineHeight::FontSizeRelative(style.line_height);
208 builder.push_default(StyleProperty::LineHeight(line_height));
209 builder.push_default(StyleProperty::Brush(style.color));
210 for span in spans {
211 if let Some(color) = span.color {
212 builder.push(StyleProperty::Brush(color), span.range.clone());
213 }
214 if let Some(size) = span.size_px {
215 builder.push(StyleProperty::FontSize(size), span.range.clone());
216 }
217 }
218 let layout = builder.build(text);
219
220 let faces = &mut self.faces;
221 let mut resolve = |data: &FontData| -> Option<Font> {
222 let key = face_key(data);
223 if let Some(font) = faces.get(&key) {
224 return Some(font.clone());
225 }
226 let (bytes, _) = data.data.clone().into_raw_parts();
227 let font = Font::from_shared(bytes, data.index).ok()?;
228 faces.insert(key, font.clone());
229 Some(font)
230 };
231 Paragraph::new(layout, text.len(), style.align, max_width, &mut resolve)
232 }
233}