1use std::cell::RefCell;
35use std::sync::Arc;
36
37use parley::fontique::Blob;
38use parley::style::{FontStack, FontStyle as ParleyFontStyle, FontWeight as ParleyFontWeight};
39use parley::{FontContext, Layout, LayoutContext, StyleProperty};
40
41use super::measurer::{FontMetrics, TextMeasurer, TextMetrics};
42
43pub struct FontBlob(pub(crate) Blob<u8>);
49
50impl From<&'static [u8]> for FontBlob {
51 fn from(data: &'static [u8]) -> Self {
54 FontBlob(Blob::new(Arc::new(data)))
55 }
56}
57
58impl From<Vec<u8>> for FontBlob {
59 fn from(data: Vec<u8>) -> Self {
61 FontBlob(Blob::from(data))
62 }
63}
64
65impl From<Arc<Vec<u8>>> for FontBlob {
66 fn from(data: Arc<Vec<u8>>) -> Self {
68 FontBlob(Blob::new(data))
69 }
70}
71
72#[derive(Debug, Clone)]
76pub struct FontQuery {
77 pub font_size: f32,
79 pub font_weight: u16,
81 pub font_style: ParleyFontStyle,
83 pub font_family: String,
85 pub letter_spacing: f32,
88 pub word_spacing: f32,
91}
92
93impl FontQuery {
94 #[must_use]
96 pub fn new(
97 font_size: f32,
98 font_weight: u16,
99 font_style: ParleyFontStyle,
100 font_family: String,
101 ) -> Self {
102 Self {
103 font_size,
104 font_weight,
105 font_style,
106 font_family,
107 letter_spacing: 0.0,
108 word_spacing: 0.0,
109 }
110 }
111
112 #[must_use]
114 pub fn from_size(font_size: f32) -> Self {
115 Self {
116 font_size,
117 font_weight: 400,
118 font_style: ParleyFontStyle::Normal,
119 font_family: "sans-serif".to_string(),
120 letter_spacing: 0.0,
121 word_spacing: 0.0,
122 }
123 }
124
125 #[must_use]
127 pub fn from_computed(cv: &style::properties::ComputedValues) -> Self {
128 let font = cv.get_font();
129 let fs = font.clone_font_size().computed_size().px();
130 let fw = font.clone_font_weight().value() as u16;
131 let style_val = font.clone_font_style();
132 let font_style = if style_val == style::values::computed::font::FontStyle::ITALIC {
133 ParleyFontStyle::Italic
134 } else if style_val != style::values::computed::font::FontStyle::NORMAL {
135 ParleyFontStyle::Oblique(None)
136 } else {
137 ParleyFontStyle::Normal
138 };
139 use style_traits::ToCss;
141 let family = font.clone_font_family().to_css_string();
142
143 let text = cv.get_inherited_text();
146 let zero = style::values::computed::CSSPixelLength::new(0.0);
147 let letter_spacing = text
148 .clone_letter_spacing()
149 .0
150 .percentage_relative_to(zero)
151 .px();
152 let word_spacing = text.clone_word_spacing().percentage_relative_to(zero).px();
153
154 Self {
155 font_size: fs,
156 font_weight: fw,
157 font_style,
158 font_family: family,
159 letter_spacing,
160 word_spacing,
161 }
162 }
163}
164
165pub struct FontSystem {
177 font_cx: RefCell<FontContext>,
178 layout_cx: RefCell<LayoutContext>,
179}
180
181impl FontSystem {
182 #[must_use]
187 pub fn new() -> Self {
188 Self {
189 font_cx: RefCell::new(FontContext::new()),
190 layout_cx: RefCell::new(LayoutContext::new()),
191 }
192 }
193
194 pub fn register_font(&self, data: impl Into<FontBlob>) -> Vec<String> {
219 let mut font_cx = self.font_cx.borrow_mut();
220 let blob = data.into().0;
221 let registered = font_cx.collection.register_fonts(blob, None);
222 registered
223 .iter()
224 .filter_map(|(fid, _)| font_cx.collection.family_name(*fid).map(|n| n.to_string()))
225 .collect()
226 }
227
228 pub fn shape_text(&self, text: &str, query: &FontQuery) -> TextMetrics {
233 if text.is_empty() {
234 return TextMetrics { width: 0.0 };
235 }
236
237 let mut font_cx = self.font_cx.borrow_mut();
238 let mut layout_cx = self.layout_cx.borrow_mut();
239
240 let font_stack = family_to_stack(&query.font_family);
241 let mut builder = layout_cx.ranged_builder(&mut font_cx, text, 1.0, true);
242 builder.push_default(StyleProperty::FontSize(query.font_size));
243 builder.push_default(StyleProperty::FontWeight(ParleyFontWeight::new(
244 query.font_weight as f32,
245 )));
246 builder.push_default(StyleProperty::FontStyle(query.font_style));
247 builder.push_default(StyleProperty::FontStack(font_stack));
248
249 let mut layout: Layout<[u8; 4]> = builder.build(text);
250 layout.break_all_lines(None);
251
252 TextMetrics {
253 width: layout.width(),
254 }
255 }
256
257 pub fn query_metrics(&self, query: &FontQuery) -> FontMetrics {
262 let mut font_cx = self.font_cx.borrow_mut();
263 let mut layout_cx = self.layout_cx.borrow_mut();
264
265 let font_stack = family_to_stack(&query.font_family);
266 let reference_text = "x";
267 let mut builder = layout_cx.ranged_builder(&mut font_cx, reference_text, 1.0, true);
268 builder.push_default(StyleProperty::FontSize(query.font_size));
269 builder.push_default(StyleProperty::FontWeight(ParleyFontWeight::new(
270 query.font_weight as f32,
271 )));
272 builder.push_default(StyleProperty::FontStyle(query.font_style));
273 builder.push_default(StyleProperty::FontStack(font_stack));
274
275 let mut layout: Layout<[u8; 4]> = builder.build(reference_text);
276 layout.break_all_lines(None);
277
278 let line = layout
281 .lines()
282 .next()
283 .expect("Parley always produces at least one line for non-empty text");
284 let m = line.metrics();
285 FontMetrics {
286 ascent: m.ascent,
287 descent: m.descent,
288 line_gap: m.leading,
289 }
290 }
291}
292
293impl Default for FontSystem {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299impl TextMeasurer for FontSystem {
304 fn measure(&self, text: &str, font_size: f32) -> TextMetrics {
305 let query = FontQuery::from_size(font_size);
306 FontSystem::shape_text(self, text, &query)
307 }
308
309 fn font_metrics(&self, font_size: f32) -> FontMetrics {
310 let query = FontQuery::from_size(font_size);
311 FontSystem::query_metrics(self, &query)
312 }
313
314 fn shape_text(&self, text: &str, query: &FontQuery) -> TextMetrics {
315 FontSystem::shape_text(self, text, query)
316 }
317
318 fn query_metrics(&self, query: &FontQuery) -> FontMetrics {
319 FontSystem::query_metrics(self, query)
320 }
321
322 fn measure_wrapped(
323 &self,
324 text: &str,
325 font_size: f32,
326 max_width: Option<f32>,
327 ) -> super::measurer::WrappedTextMetrics {
328 if text.is_empty() {
329 let fm = self.query_metrics(&FontQuery::from_size(font_size));
330 return super::measurer::WrappedTextMetrics {
331 width: 0.0,
332 height: fm.ascent + fm.descent,
333 };
334 }
335
336 let mut font_cx = self.font_cx.borrow_mut();
337 let mut layout_cx = self.layout_cx.borrow_mut();
338
339 let query = FontQuery::from_size(font_size);
340 let font_stack = family_to_stack(&query.font_family);
341
342 let mut builder = layout_cx.ranged_builder(&mut font_cx, text, 1.0, true);
343 builder.push_default(StyleProperty::FontSize(font_size));
344 builder.push_default(StyleProperty::FontWeight(ParleyFontWeight::new(400.0)));
345 builder.push_default(StyleProperty::FontStack(font_stack));
346
347 let mut layout: Layout<[u8; 4]> = builder.build(text);
348 layout.break_all_lines(max_width);
351
352 super::measurer::WrappedTextMetrics {
353 width: layout.width(),
354 height: layout.height(),
355 }
356 }
357
358 fn shape_glyphs(&self, text: &str, query: &FontQuery, color: [u8; 4]) -> Vec<ShapedTextRun> {
359 FontSystem::shape_glyphs(self, text, query, color)
361 }
362}
363
364#[derive(Debug, Clone, Copy)]
369pub struct ShapedGlyph {
370 pub id: u32,
371 pub x: f32,
372 pub y: f32,
373}
374
375#[derive(Debug, Clone)]
380pub struct ShapedTextRun {
381 pub font: parley::FontData,
383 pub font_size: f32,
385 pub glyphs: Vec<ShapedGlyph>,
387 pub color: [u8; 4],
389 pub offset: f32,
391 pub baseline: f32,
393 pub normalized_coords: Vec<i16>,
398}
399
400impl FontSystem {
401 pub fn shape_glyphs(
414 &self,
415 text: &str,
416 query: &FontQuery,
417 color: [u8; 4],
418 ) -> Vec<ShapedTextRun> {
419 if text.is_empty() {
420 return Vec::new();
421 }
422
423 let mut font_cx = self.font_cx.borrow_mut();
424 let mut layout_cx = self.layout_cx.borrow_mut();
425
426 let font_stack = family_to_stack(&query.font_family);
427 let mut builder = layout_cx.ranged_builder(&mut font_cx, text, 1.0, true);
428 builder.push_default(StyleProperty::FontSize(query.font_size));
429 builder.push_default(StyleProperty::FontWeight(ParleyFontWeight::new(
430 query.font_weight as f32,
431 )));
432 builder.push_default(StyleProperty::FontStyle(query.font_style));
433 builder.push_default(StyleProperty::FontStack(font_stack));
434 builder.push_default(StyleProperty::Brush(color));
435
436 if query.letter_spacing != 0.0 {
439 builder.push_default(StyleProperty::LetterSpacing(query.letter_spacing));
440 }
441 if query.word_spacing != 0.0 {
442 builder.push_default(StyleProperty::WordSpacing(query.word_spacing));
443 }
444
445 let mut layout: Layout<[u8; 4]> = builder.build(text);
446 layout.break_all_lines(None);
447
448 let mut runs = Vec::new();
449
450 for line in layout.lines() {
451 for item in line.items() {
452 let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else {
453 continue;
454 };
455
456 let run = glyph_run.run();
457 let font_data = run.font().clone();
458 let run_font_size = run.font_size();
459
460 let mut cursor_x = 0.0f32;
465 let glyphs: Vec<ShapedGlyph> = glyph_run
466 .glyphs()
467 .map(|g| {
468 let shaped = ShapedGlyph {
469 id: g.id,
470 x: cursor_x + g.x,
471 y: g.y,
472 };
473 cursor_x += g.advance;
474 shaped
475 })
476 .collect();
477
478 if glyphs.is_empty() {
479 continue;
480 }
481
482 let normalized_coords: Vec<i16> = run.normalized_coords().to_vec();
487
488 runs.push(ShapedTextRun {
489 font: font_data,
490 font_size: run_font_size,
491 glyphs,
492 color,
493 offset: glyph_run.offset(),
494 baseline: glyph_run.baseline(),
495 normalized_coords,
496 });
497 }
498 }
499
500 runs
501 }
502}
503
504fn family_to_stack(family: &str) -> FontStack<'_> {
509 FontStack::Source(family.into())
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn font_system_creates_successfully() {
519 let _fs = FontSystem::new();
520 }
521
522 #[test]
523 fn measure_empty_text() {
524 let fs = FontSystem::new();
525 let query = FontQuery::from_size(16.0);
526 let metrics = fs.shape_text("", &query);
527 assert_eq!(metrics.width, 0.0);
528 }
529
530 #[test]
531 fn measure_text_has_nonzero_width() {
532 let fs = FontSystem::new();
533 let query = FontQuery::from_size(16.0);
534 let metrics = fs.shape_text("Hello", &query);
535 assert!(
536 metrics.width > 0.0,
537 "shaped text should have positive width, got {}",
538 metrics.width
539 );
540 }
541
542 #[test]
543 fn longer_text_is_wider() {
544 let fs = FontSystem::new();
545 let query = FontQuery::from_size(16.0);
546 let short = fs.shape_text("Hi", &query);
547 let long = fs.shape_text("Hello World", &query);
548 assert!(long.width > short.width, "longer text should be wider");
549 }
550
551 #[test]
552 fn larger_font_is_wider() {
553 let fs = FontSystem::new();
554 let small = FontQuery::from_size(12.0);
555 let big = FontQuery::from_size(24.0);
556 let w_small = fs.shape_text("Hello", &small);
557 let w_big = fs.shape_text("Hello", &big);
558 assert!(
559 w_big.width > w_small.width,
560 "larger font should produce wider text"
561 );
562 }
563
564 #[test]
565 fn font_metrics_from_real_font() {
566 let fs = FontSystem::new();
567 let query = FontQuery::from_size(16.0);
568 let metrics = fs.query_metrics(&query);
569
570 assert!(
572 metrics.ascent > 0.0,
573 "ascent should be positive, got {}",
574 metrics.ascent
575 );
576 assert!(
577 metrics.descent > 0.0,
578 "descent should be positive, got {}",
579 metrics.descent
580 );
581 assert!(metrics.line_gap >= 0.0, "line_gap should be non-negative");
582
583 assert!(
585 metrics.ascent > metrics.descent,
586 "ascent ({}) should be > descent ({}) for Latin fonts",
587 metrics.ascent,
588 metrics.descent
589 );
590 }
591
592 #[test]
593 fn font_metrics_scale_with_size() {
594 let fs = FontSystem::new();
595 let small = FontQuery::from_size(12.0);
596 let big = FontQuery::from_size(24.0);
597 let m_small = fs.query_metrics(&small);
598 let m_big = fs.query_metrics(&big);
599
600 assert!(
602 m_big.ascent > m_small.ascent,
603 "bigger font should have larger ascent"
604 );
605 assert!(
606 m_big.descent > m_small.descent,
607 "bigger font should have larger descent"
608 );
609 }
610
611 #[test]
612 fn bold_text_may_differ_in_width() {
613 let fs = FontSystem::new();
614 let family = "sans-serif".to_string();
615 let regular = FontQuery::new(16.0, 400, ParleyFontStyle::Normal, family.clone());
616 let bold = FontQuery::new(16.0, 700, ParleyFontStyle::Normal, family);
617 let w_regular = fs.shape_text("Hello", ®ular);
618 let w_bold = fs.shape_text("Hello", &bold);
619
620 assert!(w_regular.width > 0.0);
622 assert!(w_bold.width > 0.0);
623 }
624
625 #[test]
626 fn monospace_characters_equal_width() {
627 let fs = FontSystem::new();
628 let family = "monospace".to_string();
629 let query = FontQuery::new(16.0, 400, ParleyFontStyle::Normal, family);
630 let w_i = fs.shape_text("iiiii", &query);
631 let w_m = fs.shape_text("mmmmm", &query);
632
633 assert!(
635 (w_i.width - w_m.width).abs() < 1.0,
636 "monospace: 'iiiii' ({:.1}) should equal 'mmmmm' ({:.1})",
637 w_i.width,
638 w_m.width
639 );
640 }
641
642 #[test]
643 fn font_query_from_size_defaults() {
644 let query = FontQuery::from_size(20.0);
645 assert_eq!(query.font_size, 20.0);
646 assert_eq!(query.font_weight, 400);
647 assert_eq!(query.font_family, "sans-serif");
648 }
649
650 #[test]
651 fn named_font_family_query() {
652 let family = "Roboto, Arial, sans-serif".to_string();
653 let query = FontQuery::new(16.0, 400, ParleyFontStyle::Normal, family);
654 assert!(query.font_family.contains("Roboto"));
656 assert!(query.font_family.contains("sans-serif"));
657 }
658}