1use crate::{FaceRegistry, GlyphRasterizer, line_layout};
2use anyhow::{Context, Result, ensure};
3use gpui::{
4 Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, GlyphId, LineLayout, Pixels,
5 PlatformTextSystem, RenderGlyphParams, ShapedGlyph, ShapedRun, Size, TextRenderingMode, point,
6 px,
7};
8use parley::{
9 Alignment, AlignmentOptions, FontContext, FontFamily, FontFamilyName, Layout, LayoutContext,
10 StyleProperty,
11};
12use std::{
13 borrow::Cow,
14 collections::{HashMap, HashSet},
15 ops::Range,
16 sync::{Arc, Mutex, RwLock},
17};
18
19pub struct ParleyTextSystem {
22 state: Mutex<State>,
23 faces: RwLock<FaceRegistry>,
24 raster: Mutex<GlyphRasterizer>,
25 #[cfg(test)]
26 raster_calls: std::sync::atomic::AtomicUsize,
27 rejected: RejectedFace,
28}
29
30struct RejectedFace {
32 id: FontId,
33 metrics: FontMetrics,
34 advance: f32,
35}
36
37pub type LayoutErrorHandler = Arc<dyn Fn(&anyhow::Error) + Send + Sync>;
40
41struct State {
42 fonts: FontContext,
43 layout: LayoutContext<()>,
44 default_family: String,
45 emoji_families: Vec<String>,
47 bindings: HashMap<FontId, Font>,
50 aliases: HashMap<Font, HashMap<FontId, FontId>>,
52 descriptors: HashMap<Font, FontId>,
54 generation: u64,
55 missing_sink: Option<Arc<dyn gpui::MissingGlyphSink>>,
56 layout_error_handler: LayoutErrorHandler,
57 #[cfg(test)]
58 shape_calls: usize,
59}
60
61impl ParleyTextSystem {
62 pub fn new(mut fonts: FontContext, default_family: impl Into<String>) -> Result<Self> {
66 let default_family = default_family.into();
67 ensure!(
68 fonts.collection.family_by_name(&default_family).is_some(),
69 "default family is unavailable"
70 );
71 let mut state = State {
72 fonts,
73 default_family,
74 emoji_families: Vec::new(),
75 layout: LayoutContext::new(),
76 bindings: HashMap::new(),
77 aliases: HashMap::new(),
78 descriptors: HashMap::new(),
79 generation: 0,
80 missing_sink: None,
81 layout_error_handler: Arc::new(|error| eprintln!("Parley layout rejected: {error:#}")),
82 #[cfg(test)]
83 shape_calls: 0,
84 };
85 state.refresh_emoji_families();
86 let layout = state.shape(" ", px(16.0), &[])?;
89 let run = layout
90 .lines()
91 .next()
92 .context("missing default line")?
93 .runs()
94 .next()
95 .context("missing default face")?;
96 let mut faces = FaceRegistry::default();
97 let id = faces.register(&run)?;
98 let face = faces.get(id)?;
99 let metrics = face.metrics()?;
100 let advance = face.advance(GlyphId(0))?.width;
101 ensure!(
102 advance.is_finite() && advance > 0.0,
103 "invalid default .notdef advance"
104 );
105 let rejected = RejectedFace {
106 id,
107 metrics,
108 advance,
109 };
110 Ok(Self {
111 state: Mutex::new(state),
112 faces: RwLock::new(faces),
113 raster: Mutex::new(GlyphRasterizer::default()),
114 #[cfg(test)]
115 raster_calls: std::sync::atomic::AtomicUsize::new(0),
116 rejected,
117 })
118 }
119
120 fn face(&self, id: FontId) -> Result<Arc<crate::RegisteredFace>> {
123 self.faces.read().unwrap().shared(id)
124 }
125
126 pub fn set_layout_error_handler(&self, handler: LayoutErrorHandler) {
129 self.state.lock().unwrap().layout_error_handler = handler;
130 }
131
132 #[cfg(test)]
134 pub(crate) fn shape_calls(&self) -> usize {
135 self.state.lock().unwrap().shape_calls
136 }
137
138 pub(crate) fn font_generation(&self) -> u64 {
141 self.state.lock().unwrap().generation
142 }
143
144 pub(crate) fn report_layout_error(&self, error: &anyhow::Error) {
146 let handler = self.state.lock().unwrap().layout_error_handler.clone();
147 handler(error);
148 }
149
150 fn rejected_line(&self, text: &str, size: Pixels) -> LineLayout {
151 let size = if f32::from(size).is_finite() && size > px(0.0) {
152 size
153 } else {
154 px(16.0)
155 };
156 let count = text.chars().count();
157 let mut size = size;
158 let mut scale = f32::from(size) / self.rejected.metrics.units_per_em as f32;
159 let max_units = (self.rejected.advance * count as f32)
160 .max(self.rejected.metrics.ascent.abs())
161 .max(self.rejected.metrics.descent.abs());
162 if !(max_units * scale).is_finite() {
163 size = px(16.0);
164 scale = f32::from(size) / self.rejected.metrics.units_per_em as f32;
165 }
166 let advance = px(self.rejected.advance * scale);
167 let glyphs = text
168 .char_indices()
169 .enumerate()
170 .map(|(offset, (index, _))| ShapedGlyph {
171 id: GlyphId(0),
172 position: point(advance * offset as f32, px(0.0)),
173 index,
174 is_emoji: false,
175 })
176 .collect();
177 LineLayout {
178 font_size: size,
179 width: advance * count as f32,
180 ascent: px(self.rejected.metrics.ascent * scale),
181 descent: px(self.rejected.metrics.descent.abs() * scale),
182 runs: vec![ShapedRun {
183 font_id: self.rejected.id,
184 glyphs,
185 }],
186 len: text.len(),
187 }
188 }
189
190 pub fn try_layout_line(
194 &self,
195 text: &str,
196 size: Pixels,
197 runs: &[FontRun],
198 ) -> Result<LineLayout> {
199 let mut state = self.state.lock().unwrap();
200 let styles = state.styles(text, runs)?;
201 let layout = state.shape(text, size, &styles)?;
202 let result = line_layout(&layout, size, |run| {
203 self.faces.write().unwrap().register(run)
204 })?;
205 let sink = state.missing_sink.clone();
206 let mut missing = Vec::new();
207 if sink.is_some() && !text.is_empty() {
208 use skrifa::MetadataProvider;
209 for line in layout.lines() {
210 for run in line.runs() {
211 let font =
212 skrifa::FontRef::from_index(run.font().data.data(), run.font().index)?;
213 let class = if font
214 .metrics(
215 skrifa::instance::Size::unscaled(),
216 skrifa::instance::LocationRef::default(),
217 )
218 .is_monospace
219 {
220 gpui::FallbackFontClass::Monospace
221 } else {
222 gpui::FallbackFontClass::Proportional
223 };
224 for cluster in run.visual_clusters() {
225 if cluster.glyphs().any(|glyph| glyph.id == 0) {
226 missing.push(gpui::MissingGlyph::new(
227 text[cluster.text_range()].to_owned().into(),
228 class,
229 ));
230 }
231 }
232 }
233 }
234 }
235 drop(state); if let Some(sink) = sink
237 && !missing.is_empty()
238 {
239 sink.report(missing);
240 }
241 Ok(result)
242 }
243 pub fn prepare_paragraph(
248 &self,
249 text: &str,
250 size: Pixels,
251 runs: &[FontRun],
252 ) -> Result<crate::ParagraphShape> {
253 self.prepare_paragraph_with_options(text, size, runs, Default::default())
254 }
255
256 pub fn prepare_paragraph_with_options(
260 &self,
261 text: &str,
262 size: Pixels,
263 runs: &[FontRun],
264 options: crate::ParagraphOptions,
265 ) -> Result<crate::ParagraphShape> {
266 ensure!(
267 !text.contains(['\n', '\r', '\u{2029}']),
268 "expected one logical paragraph"
269 );
270 let mut state = self.state.lock().unwrap();
271 let styles = state.styles(text, runs)?;
272 let mut layout = state.build(text, size, &styles, options)?;
273 layout.break_all_lines(None);
274 layout.align(Alignment::Start, AlignmentOptions::default());
275 crate::validate_layout_geometry(&layout)?;
276 let mut faces = Vec::new();
277 let mut seen_faces = HashSet::new();
278 let mut face_runs = Vec::new();
279 let mut registry = self.faces.write().unwrap();
280 for line in layout.lines() {
281 for run in line.runs() {
282 let id = registry.register(&run)?;
283 face_runs.push((run.text_range(), id));
284 if seen_faces.insert(id) {
285 faces.push(id);
286 }
287 }
288 }
289 drop(registry);
290 face_runs.sort_by_key(|(range, _)| range.start);
291 Ok(crate::ParagraphShape::new(
292 Arc::from(text),
293 layout,
294 faces,
295 face_runs,
296 size,
297 ))
298 }
299}
300
301fn family_list<'a>(
303 primary: &'a str,
304 fallbacks: &'a [String],
305 emoji: &'a [String],
306) -> Vec<FontFamilyName<'a>> {
307 let mut families = Vec::with_capacity(1 + fallbacks.len() + emoji.len());
308 families.push(FontFamilyName::named(primary));
309 families.extend(fallbacks.iter().map(|name| FontFamilyName::named(name)));
310 for name in emoji {
311 let listed = name == primary || fallbacks.contains(name);
312 if !listed {
313 families.push(FontFamilyName::named(name));
314 }
315 }
316 families
317}
318
319impl State {
320 fn refresh_emoji_families(&mut self) {
324 let names: Vec<String> = self
325 .fonts
326 .collection
327 .family_names()
328 .filter(|name| name.to_lowercase().contains("emoji"))
329 .map(str::to_owned)
330 .collect();
331 let mut emoji: Vec<String> = names
332 .into_iter()
333 .filter(|name| {
334 self.fonts
335 .collection
336 .family_by_name(name)
337 .is_some_and(|family| {
338 family.fonts().iter().any(|font| {
339 font.load(None).is_some_and(|blob| {
340 crate::raster::color::ColorProbe::new(blob.data(), font.index())
341 .is_some()
342 })
343 })
344 })
345 })
346 .collect();
347 emoji.sort();
348 self.emoji_families = emoji;
349 }
350
351 fn styles(&self, text: &str, runs: &[FontRun]) -> Result<Vec<(Font, Range<usize>)>> {
352 let mut start: usize = 0;
353 let mut styles = Vec::with_capacity(runs.len());
354 for run in runs {
355 let end = start.checked_add(run.len).context("font run overflow")?;
356 ensure!(
357 end <= text.len() && text.is_char_boundary(start) && text.is_char_boundary(end),
358 "invalid font run range"
359 );
360 let font = self
361 .bindings
362 .get(&run.font_id)
363 .context("unknown requested font ID")?;
364 styles.push((font.clone(), start..end));
365 start = end;
366 }
367 ensure!(
368 runs.is_empty() || start == text.len(),
369 "font runs must cover the line"
370 );
371 Ok(styles)
372 }
373
374 fn build(
375 &mut self,
376 text: &str,
377 size: Pixels,
378 styles: &[(Font, Range<usize>)],
379 options: crate::ParagraphOptions,
380 ) -> Result<Layout<()>> {
381 ensure!(
382 f32::from(size).is_finite() && size > px(0.0),
383 "invalid font size"
384 );
385 #[cfg(test)]
386 {
387 self.shape_calls += 1;
388 }
389 let mut builder = self.layout.ranged_builder(&mut self.fonts, text, 1.0, true);
390 builder.set_base_direction(options.direction.into());
391 if let Some(language) = options.language {
392 builder.push_default(StyleProperty::Locale(Some(language.parley())));
393 }
394 if let Some(line_height) = options.line_height {
395 ensure!(
396 f32::from(line_height).is_finite() && line_height > px(0.0),
397 "invalid line height"
398 );
399 builder.push_default(StyleProperty::LineHeight(parley::LineHeight::Absolute(
400 f32::from(line_height),
401 )));
402 }
403 builder.push_default(StyleProperty::FontFamily(FontFamily::List(Cow::Owned(
404 family_list(&self.default_family, &[], &self.emoji_families),
405 ))));
406 builder.push_default(StyleProperty::FontSize(f32::from(size)));
407 for (font, range) in styles {
408 let primary = if font.family.as_ref() == ".SystemUIFont" {
409 &self.default_family
410 } else {
411 font.family.as_ref()
412 };
413 let fallbacks = font
414 .fallbacks
415 .as_ref()
416 .map(|f| f.fallback_list())
417 .unwrap_or(&[]);
418 let family = FontFamily::List(Cow::Owned(family_list(
419 primary,
420 fallbacks,
421 &self.emoji_families,
422 )));
423 builder.push(StyleProperty::FontFamily(family), range.clone());
424 builder.push(
425 StyleProperty::FontWeight(parley::FontWeight::new(font.weight.0)),
426 range.clone(),
427 );
428 let style = match font.style {
429 gpui::FontStyle::Normal => parley::FontStyle::Normal,
430 gpui::FontStyle::Italic => parley::FontStyle::Italic,
431 gpui::FontStyle::Oblique => parley::FontStyle::Oblique(None),
432 };
433 builder.push(StyleProperty::FontStyle(style), range.clone());
434 let variations = font
435 .variations
436 .settings()
437 .iter()
438 .map(|(tag, value)| {
439 Ok(parley::FontVariation::new(
440 parley::setting::Tag::parse(&tag.to_string())
441 .context("invalid axis tag")?,
442 *value,
443 ))
444 })
445 .collect::<Result<Vec<_>>>()?;
446 builder.push(
447 StyleProperty::FontVariations(parley::FontVariations::List(Cow::Owned(variations))),
448 range.clone(),
449 );
450 let features = font
451 .features
452 .tag_value_list()
453 .iter()
454 .map(|(tag, value)| {
455 Ok(parley::FontFeature::new(
456 parley::setting::Tag::parse(tag).context("invalid feature tag")?,
457 (*value).try_into()?,
458 ))
459 })
460 .collect::<Result<Vec<_>>>()?;
461 builder.push(
462 StyleProperty::FontFeatures(parley::FontFeatures::List(Cow::Owned(features))),
463 range.clone(),
464 );
465 }
466 Ok(builder.build(text))
467 }
468
469 fn shape(
470 &mut self,
471 text: &str,
472 size: Pixels,
473 styles: &[(Font, Range<usize>)],
474 ) -> Result<Layout<()>> {
475 ensure!(
476 !text.contains(['\n', '\r', '\u{2028}', '\u{2029}']),
477 "expected a single logical line"
478 );
479 let mut layout = self.build(text, size, styles, crate::ParagraphOptions::default())?;
480 layout.break_all_lines(None);
481 layout.align(Alignment::Start, AlignmentOptions::default());
482 Ok(layout)
483 }
484}
485
486impl PlatformTextSystem for ParleyTextSystem {
487 fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
488 let mut validation =
490 parley::fontique::Collection::new(parley::fontique::CollectionOptions {
491 shared: false,
492 system_fonts: false,
493 });
494 let blobs = fonts
495 .into_iter()
496 .map(|bytes| {
497 let blob = parley::fontique::Blob::new(Arc::new(bytes.into_owned()));
498 let registered = validation.register_fonts(blob.clone(), None);
499 ensure!(!registered.is_empty(), "invalid or unnamed font data");
500 let families = registered
501 .into_iter()
502 .map(|(id, fonts)| {
503 let name = validation
504 .family_name(id)
505 .context("missing validated family")?
506 .to_owned();
507 let indices = fonts.iter().map(|font| font.index()).collect::<Vec<_>>();
508 Ok((name, indices))
509 })
510 .collect::<Result<Vec<_>>>()?;
511 Ok((blob, families))
512 })
513 .collect::<Result<Vec<_>>>()?;
514 let mut state = self.state.lock().unwrap();
515 for (blob, families) in blobs {
516 let duplicate = families.iter().all(|(name, indices)| {
520 state.fonts.collection.family_by_name(name).is_some_and(|family| {
521 indices.iter().all(|index| family.fonts().iter().any(|font| {
522 font.index() == *index && matches!(font.source().kind(),
523 parley::fontique::SourceKind::Memory(data) if data.data() == blob.data())
524 }))
525 })
526 });
527 if !duplicate {
528 state.fonts.collection.register_fonts(blob, None);
529 }
530 }
531 state.refresh_emoji_families();
532 state.generation += 1;
533 state.descriptors.clear();
534 Ok(())
535 }
536
537 fn set_missing_glyph_sink(&self, sink: Option<Arc<dyn gpui::MissingGlyphSink>>) {
538 self.state.lock().unwrap().missing_sink = sink;
539 }
540
541 fn all_font_names(&self) -> Vec<String> {
542 self.state
543 .lock()
544 .unwrap()
545 .fonts
546 .collection
547 .family_names()
548 .map(str::to_owned)
549 .collect()
550 }
551
552 fn font_id(&self, descriptor: &Font) -> Result<FontId> {
553 let mut state = self.state.lock().unwrap();
554 if let Some(&id) = state.descriptors.get(descriptor) {
555 return Ok(id);
556 }
557 let family = if descriptor.family.as_ref() == ".SystemUIFont" {
558 state.default_family.clone()
559 } else {
560 descriptor.family.to_string()
561 };
562 ensure!(
563 state.fonts.collection.family_by_name(&family).is_some(),
564 "font family is unavailable: {family}"
565 );
566 let layout = state.shape(" ", px(16.0), &[(descriptor.clone(), 0..1)])?;
567 let line = layout
568 .lines()
569 .next()
570 .context("font probe produced no line")?;
571 let run = line.runs().next().context("font probe produced no face")?;
572 let resolved = self.faces.write().unwrap().register(&run)?;
573 let id = if let Some(&id) = state
574 .aliases
575 .get(descriptor)
576 .and_then(|ids| ids.get(&resolved))
577 {
578 id
579 } else {
580 let id = self.faces.write().unwrap().alias(resolved)?;
581 state.bindings.insert(id, descriptor.clone());
582 state
583 .aliases
584 .entry(descriptor.clone())
585 .or_default()
586 .insert(resolved, id);
587 id
588 };
589 state.descriptors.insert(descriptor.clone(), id);
590 Ok(id)
591 }
592
593 fn font_capabilities(&self) -> gpui::FontCapabilities {
594 gpui::FontCapabilities {
595 feature_introspection: true,
596 axis_introspection: true,
597 static_variations: true,
598 }
599 }
600
601 fn font_table(
602 &self,
603 id: FontId,
604 tag: gpui::FontTag,
605 ) -> Result<gpui::FontQuery<Option<Vec<u8>>>> {
606 let face = self.face(id)?;
607 let data = face.data();
608 let font = skrifa::FontRef::from_index(data.data.data(), data.index)?;
609 let tag = skrifa::Tag::new(tag.as_bytes());
610 let table = if font
611 .table_directory()
612 .table_records()
613 .iter()
614 .any(|record| record.tag() == tag)
615 {
616 Some(
617 font.table_data(tag)
618 .context("unreadable font table")?
619 .as_bytes()
620 .to_vec(),
621 )
622 } else {
623 None
624 };
625 Ok(gpui::FontQuery::Supported(table))
626 }
627
628 fn font_metrics(&self, id: FontId) -> FontMetrics {
629 self.face(id)
630 .and_then(|face| face.metrics())
631 .expect("invalid Parley font metrics")
632 }
633 fn typographic_bounds(&self, id: FontId, glyph: GlyphId) -> Result<Bounds<f32>> {
634 self.face(id)?.typographic_bounds(glyph)
635 }
636 fn advance(&self, id: FontId, glyph: GlyphId) -> Result<Size<f32>> {
637 self.face(id)?.advance(glyph)
638 }
639 fn glyph_for_char(&self, id: FontId, ch: char) -> Option<GlyphId> {
640 self.face(id).ok()?.glyph_for_char(ch)
641 }
642 fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
643 let face = self.face(params.font_id)?;
644 self.raster
645 .lock()
646 .unwrap()
647 .raster_bounds_for_face(&face, params)
648 }
649 fn rasterize_glyph(
650 &self,
651 params: &RenderGlyphParams,
652 bounds: Bounds<DevicePixels>,
653 ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
654 let face = self.face(params.font_id)?;
655 #[cfg(test)]
656 self.raster_calls
657 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
658 self.raster
659 .lock()
660 .unwrap()
661 .rasterize_face(&face, params, bounds)
662 }
663 fn layout_line(&self, text: &str, size: Pixels, runs: &[FontRun]) -> LineLayout {
664 match self.try_layout_line(text, size, runs) {
665 Ok(line) => line,
666 Err(error) => {
667 let handler = self.state.lock().unwrap().layout_error_handler.clone();
668 handler(&error);
669 self.rejected_line(text, size)
670 }
671 }
672 }
673 fn shape_paragraph(
674 &self,
675 request: gpui::ParagraphRequest<'_>,
676 ) -> Result<Arc<dyn gpui::PlatformParagraphShape>> {
677 Ok(Arc::new(self.prepare_paragraph(
679 request.text,
680 request.font_size,
681 request.runs,
682 )?))
683 }
684 fn recommended_rendering_mode(&self, _: FontId, _: Pixels) -> TextRenderingMode {
685 TextRenderingMode::Grayscale
686 }
687}
688
689#[cfg(test)]
690mod tests;
691
692#[cfg(test)]
693#[path = "paragraph/tests.rs"]
694mod paragraph_tests;