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, Copy, 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        self.attrs = self.attrs.metrics(self.get_metrics());
159        self
160    }
161
162    /// Set line height
163    pub fn line_height(mut self, line_height: LineHeightValue) -> Self {
164        self.line_height = line_height;
165        self.attrs = self.attrs.metrics(self.get_metrics());
166        self
167    }
168
169    /// Set metadata
170    pub fn metadata(mut self, metadata: usize) -> Self {
171        self.attrs = self.attrs.metadata(metadata);
172        self
173    }
174
175    /// Check if font matches
176    pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
177        self.attrs.matches(face)
178    }
179
180    /// Check if this set of attributes can be shaped with another
181    pub fn compatible(&self, other: &Self) -> bool {
182        self.attrs.compatible(&other.attrs)
183    }
184}
185
186#[derive(PartialEq, Clone)]
187pub struct AttrsList(pub cosmic_text::AttrsList);
188
189impl AttrsList {
190    /// Create a new attributes list with a set of default [Attrs]
191    pub fn new(defaults: Attrs) -> Self {
192        Self(cosmic_text::AttrsList::new(defaults.attrs))
193    }
194
195    /// Get the default [Attrs]
196    pub fn defaults(&self) -> Attrs {
197        self.0.defaults().into()
198    }
199
200    /// Clear the current attribute spans
201    pub fn clear_spans(&mut self) {
202        self.0.clear_spans();
203    }
204
205    /// Add an attribute span, removes any previous matching parts of spans
206    pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
207        self.0.add_span(range, attrs.attrs);
208    }
209
210    /// Get the attribute span for an index
211    ///
212    /// This returns a span that contains the index
213    pub fn get_span(&self, index: usize) -> Attrs {
214        self.0.get_span(index).into()
215    }
216
217    /// Split attributes list at an offset
218    pub fn split_off(&mut self, index: usize) -> Self {
219        let new = self.0.split_off(index);
220        Self(new)
221    }
222}
223
224impl<'a> From<cosmic_text::Attrs<'a>> for Attrs<'a> {
225    fn from(attrs: cosmic_text::Attrs<'a>) -> Self {
226        Self {
227            attrs,
228            font_size: 1.0,
229            line_height: LineHeightValue::Normal(1.0),
230        }
231    }
232}
233
234#[derive(Clone)]
235struct ParseList<'a> {
236    source: &'a [u8],
237    len: usize,
238    pos: usize,
239}
240
241impl Iterator for ParseList<'_> {
242    type Item = FamilyOwned;
243
244    fn next(&mut self) -> Option<Self::Item> {
245        let mut quote = None;
246        let mut pos = self.pos;
247        while pos < self.len && {
248            let ch = self.source[pos];
249            ch.is_ascii_whitespace() || ch == b','
250        } {
251            pos += 1;
252        }
253        self.pos = pos;
254        if pos >= self.len {
255            return None;
256        }
257        let first = self.source[pos];
258        let mut start = pos;
259        match first {
260            b'"' | b'\'' => {
261                quote = Some(first);
262                pos += 1;
263                start += 1;
264            }
265            _ => {}
266        }
267        if let Some(quote) = quote {
268            while pos < self.len {
269                if self.source[pos] == quote {
270                    self.pos = pos + 1;
271                    return Some(FamilyOwned::Name(
272                        core::str::from_utf8(self.source.get(start..pos)?)
273                            .ok()?
274                            .trim()
275                            .to_string(),
276                    ));
277                }
278                pos += 1;
279            }
280            self.pos = pos;
281            return Some(FamilyOwned::Name(
282                core::str::from_utf8(self.source.get(start..pos)?)
283                    .ok()?
284                    .trim()
285                    .to_string(),
286            ));
287        }
288        let mut end = start;
289        while pos < self.len {
290            if self.source[pos] == b',' {
291                pos += 1;
292                break;
293            }
294            pos += 1;
295            end += 1;
296        }
297        self.pos = pos;
298        let name = core::str::from_utf8(self.source.get(start..end)?)
299            .ok()?
300            .trim();
301        Some(match name.to_lowercase().as_str() {
302            "serif" => FamilyOwned::Serif,
303            "sans-serif" => FamilyOwned::SansSerif,
304            "monospace" => FamilyOwned::Monospace,
305            "cursive" => FamilyOwned::Cursive,
306            "fantasy" => FamilyOwned::Fantasy,
307            _ => FamilyOwned::Name(name.to_string()),
308        })
309    }
310}