Skip to main content

floem_renderer/text/
mod.rs

1//! Text layout, shaping, and font management for Floem.
2//!
3//! This module provides the renderer-facing text vocabulary built on
4//! [Parley](https://docs.rs/parley).
5//!
6//! # Re-exports
7//!
8//! [`FontStyle`] and [`FontWidth`] come from [`fontique`](https://docs.rs/fontique).
9
10mod attrs;
11
12use peniko::{
13    BrushRef, Fill, FontData, StyleRef,
14    kurbo::{Affine, Point},
15};
16
17pub use attrs::{Attrs, AttrsList, AttrsOwned, FamilyOwned, LineHeightValue};
18pub use fontique::{FontStyle, FontWeight, FontWidth};
19pub use parley::layout::Glyph;
20
21// --- Brush type for Parley ---
22
23/// A brush wrapper that satisfies Parley's `Brush` trait bound.
24///
25/// Parley requires its brush type to implement `Default + Clone`. This newtype
26/// wraps [`peniko::Color`] and provides a `Default` of opaque black.
27/// It is used internally to parameterise [`parley::layout::Layout<TextBrush>`]
28/// and is not typically constructed by application code.
29#[derive(Clone, Copy, Debug, PartialEq)]
30pub struct TextBrush(pub peniko::Color);
31
32impl Default for TextBrush {
33    fn default() -> Self {
34        TextBrush(peniko::Color::from_rgba8(0, 0, 0, 255))
35    }
36}
37
38impl From<peniko::Color> for TextBrush {
39    fn from(c: peniko::Color) -> Self {
40        TextBrush(c)
41    }
42}
43
44impl From<TextBrush> for peniko::Color {
45    fn from(b: TextBrush) -> Self {
46        b.0
47    }
48}
49
50/// Variable font design-space coordinate.
51pub type NormalizedCoord = i16;
52
53/// Rendering properties shared by a glyph run.
54#[derive(Clone, Debug)]
55pub struct GlyphRunProps<'a> {
56    pub font: FontData,
57    pub font_size: f32,
58    pub hint: bool,
59    pub normalized_coords: &'a [NormalizedCoord],
60    pub style: StyleRef<'a>,
61    pub brush: BrushRef<'a>,
62    pub brush_alpha: f32,
63    pub transform: Affine,
64    pub glyph_transform: Option<Affine>,
65}
66
67impl<'a> GlyphRunProps<'a> {
68    pub fn new(font: &FontData) -> Self {
69        Self {
70            font: font.clone(),
71            font_size: 16.0,
72            hint: false,
73            normalized_coords: &[],
74            style: Fill::NonZero.into(),
75            brush: peniko::color::palette::css::BLACK.into(),
76            brush_alpha: 1.0,
77            transform: Affine::IDENTITY,
78            glyph_transform: None,
79        }
80    }
81
82    pub fn font(mut self, font: &FontData) -> Self {
83        self.font = font.clone();
84        self
85    }
86
87    pub fn font_size(mut self, font_size: f32) -> Self {
88        self.font_size = font_size;
89        self
90    }
91
92    pub fn hint(mut self, hint: bool) -> Self {
93        self.hint = hint;
94        self
95    }
96
97    pub fn normalized_coords(mut self, normalized_coords: &'a [NormalizedCoord]) -> Self {
98        self.normalized_coords = normalized_coords;
99        self
100    }
101
102    pub fn style(mut self, style: impl Into<StyleRef<'a>>) -> Self {
103        self.style = style.into();
104        self
105    }
106
107    pub fn brush(mut self, brush: impl Into<BrushRef<'a>>) -> Self {
108        self.brush = brush.into();
109        self
110    }
111
112    pub fn brush_alpha(mut self, brush_alpha: f32) -> Self {
113        self.brush_alpha = brush_alpha;
114        self
115    }
116
117    pub fn transform(mut self, transform: Affine) -> Self {
118        self.transform = transform;
119        self
120    }
121
122    pub fn glyph_transform(mut self, glyph_transform: Option<Affine>) -> Self {
123        self.glyph_transform = glyph_transform;
124        self
125    }
126}
127
128pub trait GlyphDrawer {
129    fn draw_glyphs<'a>(
130        &mut self,
131        origin: Point,
132        props: &GlyphRunProps<'a>,
133        glyphs: impl Iterator<Item = Glyph> + 'a,
134    );
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    // -- TextBrush --
142
143    #[test]
144    fn text_brush_default_is_opaque_black() {
145        let b = TextBrush::default();
146        let c: peniko::Color = b.into();
147        assert_eq!(c, peniko::Color::from_rgba8(0, 0, 0, 255));
148    }
149
150    #[test]
151    fn text_glyphs_props_default_is_usable() {
152        let font = FontData::new(peniko::Blob::new(std::sync::Arc::new([])), 0);
153        let props = GlyphRunProps::new(&font);
154        assert_eq!(props.font, font);
155        assert_eq!(props.font_size, 16.0);
156        assert_eq!(props.brush_alpha, 1.0);
157        assert_eq!(props.transform, Affine::IDENTITY);
158        assert!(props.normalized_coords.is_empty());
159    }
160}