floem_renderer/text/
attrs.rs

1use std::ops::Range;
2
3use crate::text::{fontdb, Family, Stretch, Style, Weight};
4use peniko::Color;
5
6/// An owned version of [`Family`]
7#[derive(Clone, Debug, Eq, Hash, PartialEq)]
8pub enum FamilyOwned {
9    Name(String),
10    Serif,
11    SansSerif,
12    Cursive,
13    Fantasy,
14    Monospace,
15}
16
17impl FamilyOwned {
18    pub fn new(family: Family) -> Self {
19        match family {
20            Family::Name(name) => FamilyOwned::Name(name.to_string()),
21            Family::Serif => FamilyOwned::Serif,
22            Family::SansSerif => FamilyOwned::SansSerif,
23            Family::Cursive => FamilyOwned::Cursive,
24            Family::Fantasy => FamilyOwned::Fantasy,
25            Family::Monospace => FamilyOwned::Monospace,
26        }
27    }
28
29    pub fn as_family(&self) -> Family {
30        match self {
31            FamilyOwned::Name(name) => Family::Name(name),
32            FamilyOwned::Serif => Family::Serif,
33            FamilyOwned::SansSerif => Family::SansSerif,
34            FamilyOwned::Cursive => Family::Cursive,
35            FamilyOwned::Fantasy => Family::Fantasy,
36            FamilyOwned::Monospace => Family::Monospace,
37        }
38    }
39
40    pub fn parse_list(s: &str) -> impl Iterator<Item = FamilyOwned> + '_ + Clone {
41        ParseList {
42            source: s.as_bytes(),
43            len: s.len(),
44            pos: 0,
45        }
46    }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq)]
50pub enum LineHeightValue {
51    Normal(f32),
52    Px(f32),
53}
54
55/// Text attributes
56#[derive(Clone, Debug)]
57pub struct AttrsOwned {
58    attrs: cosmic_text::AttrsOwned,
59    pub font_size: f32,
60    line_height: LineHeightValue,
61}
62impl AttrsOwned {
63    pub fn new(attrs: Attrs) -> Self {
64        Self {
65            attrs: cosmic_text::AttrsOwned::new(&attrs.attrs),
66            font_size: attrs.font_size,
67            line_height: attrs.line_height,
68        }
69    }
70
71    pub fn as_attrs(&self) -> Attrs {
72        Attrs {
73            attrs: self.attrs.as_attrs(),
74            font_size: self.font_size,
75            line_height: self.line_height,
76        }
77    }
78}
79
80/// Text attributes
81#[derive(Clone, Debug)]
82pub struct Attrs<'a> {
83    attrs: cosmic_text::Attrs<'a>,
84    pub font_size: f32,
85    line_height: LineHeightValue,
86}
87
88impl Default for Attrs<'_> {
89    fn default() -> Self {
90        Self::new()
91    }
92}
93
94impl<'a> Attrs<'a> {
95    /// Create a new set of attributes with sane defaults
96    ///
97    /// This defaults to a regular Sans-Serif font.
98    pub fn new() -> Self {
99        Self {
100            attrs: cosmic_text::Attrs::new(),
101            font_size: 16.0,
102            line_height: LineHeightValue::Normal(1.0),
103        }
104    }
105
106    /// Set [Color]
107    pub fn color(mut self, color: Color) -> Self {
108        let c = color.to_rgba8();
109        self.attrs = self
110            .attrs
111            .color(cosmic_text::Color::rgba(c.r, c.g, c.b, c.a));
112        self
113    }
114
115    /// Set [Family]
116    pub fn family(mut self, family: &'a [FamilyOwned]) -> Self {
117        if let Some(family) = family.first() {
118            self.attrs = self.attrs.family(family.as_family());
119        }
120        self
121    }
122
123    /// Set [Stretch]
124    pub fn stretch(mut self, stretch: Stretch) -> Self {
125        self.attrs = self.attrs.stretch(stretch);
126        self
127    }
128
129    /// Set [Style]
130    pub fn style(mut self, style: Style) -> Self {
131        self.attrs = self.attrs.style(style);
132        self
133    }
134
135    /// Set [Weight]
136    pub fn weight(mut self, weight: Weight) -> Self {
137        self.attrs = self.attrs.weight(weight);
138        self
139    }
140
141    /// Set Weight from u16 value
142    pub fn raw_weight(mut self, weight: u16) -> Self {
143        self.attrs = self.attrs.weight(Weight(weight));
144        self
145    }
146
147    fn get_metrics(&self) -> cosmic_text::Metrics {
148        let line_height = match self.line_height {
149            LineHeightValue::Normal(n) => self.font_size * n,
150            LineHeightValue::Px(n) => n,
151        };
152        cosmic_text::Metrics::new(self.font_size, line_height)
153    }
154
155    /// Set font size
156    pub fn font_size(mut self, font_size: f32) -> Self {
157        self.font_size = font_size;
158        let metrics = self.get_metrics();
159        self.attrs = self.attrs.metrics(metrics);
160        self
161    }
162
163    /// Set line height
164    pub fn line_height(mut self, line_height: LineHeightValue) -> Self {
165        self.line_height = line_height;
166        let metrics = self.get_metrics();
167        self.attrs = self.attrs.metrics(metrics);
168        self
169    }
170
171    /// Set metadata
172    pub fn metadata(mut self, metadata: usize) -> Self {
173        self.attrs = self.attrs.metadata(metadata);
174        self
175    }
176
177    /// Check if font matches
178    pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
179        self.attrs.matches(face)
180    }
181
182    /// Check if this set of attributes can be shaped with another
183    pub fn compatible(&self, other: &Self) -> bool {
184        self.attrs.compatible(&other.attrs)
185    }
186}
187
188#[derive(PartialEq, Clone)]
189pub struct AttrsList(pub cosmic_text::AttrsList);
190
191impl AttrsList {
192    /// Create a new attributes list with a set of default [Attrs]
193    pub fn new(defaults: Attrs) -> Self {
194        Self(cosmic_text::AttrsList::new(&defaults.attrs))
195    }
196
197    /// Get the default [Attrs]
198    pub fn defaults(&self) -> Attrs {
199        self.0.defaults().into()
200    }
201
202    /// Clear the current attribute spans
203    pub fn clear_spans(&mut self) {
204        self.0.clear_spans();
205    }
206
207    /// Add an attribute span, removes any previous matching parts of spans
208    pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
209        self.0.add_span(range, &attrs.attrs);
210    }
211
212    /// Get the attribute span for an index
213    ///
214    /// This returns a span that contains the index
215    pub fn get_span(&self, index: usize) -> Attrs {
216        self.0.get_span(index).into()
217    }
218
219    /// Split attributes list at an offset
220    pub fn split_off(&mut self, index: usize) -> Self {
221        let new = self.0.split_off(index);
222        Self(new)
223    }
224}
225
226impl<'a> From<cosmic_text::Attrs<'a>> for Attrs<'a> {
227    fn from(attrs: cosmic_text::Attrs<'a>) -> Self {
228        Self {
229            attrs,
230            font_size: 1.0,
231            line_height: LineHeightValue::Normal(1.0),
232        }
233    }
234}
235
236#[derive(Clone)]
237struct ParseList<'a> {
238    source: &'a [u8],
239    len: usize,
240    pos: usize,
241}
242
243impl Iterator for ParseList<'_> {
244    type Item = FamilyOwned;
245
246    fn next(&mut self) -> Option<Self::Item> {
247        let mut quote = None;
248        let mut pos = self.pos;
249        while pos < self.len && {
250            let ch = self.source[pos];
251            ch.is_ascii_whitespace() || ch == b','
252        } {
253            pos += 1;
254        }
255        self.pos = pos;
256        if pos >= self.len {
257            return None;
258        }
259        let first = self.source[pos];
260        let mut start = pos;
261        match first {
262            b'"' | b'\'' => {
263                quote = Some(first);
264                pos += 1;
265                start += 1;
266            }
267            _ => {}
268        }
269        if let Some(quote) = quote {
270            while pos < self.len {
271                if self.source[pos] == quote {
272                    self.pos = pos + 1;
273                    return Some(FamilyOwned::Name(
274                        core::str::from_utf8(self.source.get(start..pos)?)
275                            .ok()?
276                            .trim()
277                            .to_string(),
278                    ));
279                }
280                pos += 1;
281            }
282            self.pos = pos;
283            return Some(FamilyOwned::Name(
284                core::str::from_utf8(self.source.get(start..pos)?)
285                    .ok()?
286                    .trim()
287                    .to_string(),
288            ));
289        }
290        let mut end = start;
291        while pos < self.len {
292            if self.source[pos] == b',' {
293                pos += 1;
294                break;
295            }
296            pos += 1;
297            end += 1;
298        }
299        self.pos = pos;
300        let name = core::str::from_utf8(self.source.get(start..end)?)
301            .ok()?
302            .trim();
303        Some(match name.to_lowercase().as_str() {
304            "serif" => FamilyOwned::Serif,
305            "sans-serif" => FamilyOwned::SansSerif,
306            "monospace" => FamilyOwned::Monospace,
307            "cursive" => FamilyOwned::Cursive,
308            "fantasy" => FamilyOwned::Fantasy,
309            _ => FamilyOwned::Name(name.to_string()),
310        })
311    }
312}