cosmic_text/
attrs.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5use core::ops::Range;
6use rangemap::RangeMap;
7use smol_str::SmolStr;
8
9use crate::{CacheKeyFlags, Metrics};
10
11pub use fontdb::{Family, Stretch, Style, Weight};
12
13/// Text color
14#[derive(Clone, Copy, Debug, PartialOrd, Ord, Eq, Hash, PartialEq)]
15pub struct Color(pub u32);
16
17impl Color {
18    /// Create new color with red, green, and blue components
19    #[inline]
20    pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
21        Self::rgba(r, g, b, 0xFF)
22    }
23
24    /// Create new color with red, green, blue, and alpha components
25    #[inline]
26    pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
27        Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
28    }
29
30    /// Get a tuple over all of the attributes, in `(r, g, b, a)` order.
31    #[inline]
32    pub fn as_rgba_tuple(self) -> (u8, u8, u8, u8) {
33        (self.r(), self.g(), self.b(), self.a())
34    }
35
36    /// Get an array over all of the components, in `[r, g, b, a]` order.
37    #[inline]
38    pub fn as_rgba(self) -> [u8; 4] {
39        [self.r(), self.g(), self.b(), self.a()]
40    }
41
42    /// Get the red component
43    #[inline]
44    pub fn r(&self) -> u8 {
45        ((self.0 & 0x00_FF_00_00) >> 16) as u8
46    }
47
48    /// Get the green component
49    #[inline]
50    pub fn g(&self) -> u8 {
51        ((self.0 & 0x00_00_FF_00) >> 8) as u8
52    }
53
54    /// Get the blue component
55    #[inline]
56    pub fn b(&self) -> u8 {
57        (self.0 & 0x00_00_00_FF) as u8
58    }
59
60    /// Get the alpha component
61    #[inline]
62    pub fn a(&self) -> u8 {
63        ((self.0 & 0xFF_00_00_00) >> 24) as u8
64    }
65}
66
67/// An owned version of [`Family`]
68#[derive(Clone, Debug, Eq, Hash, PartialEq)]
69pub enum FamilyOwned {
70    Name(SmolStr),
71    Serif,
72    SansSerif,
73    Cursive,
74    Fantasy,
75    Monospace,
76}
77
78impl FamilyOwned {
79    pub fn new(family: Family) -> Self {
80        match family {
81            Family::Name(name) => FamilyOwned::Name(SmolStr::from(name)),
82            Family::Serif => FamilyOwned::Serif,
83            Family::SansSerif => FamilyOwned::SansSerif,
84            Family::Cursive => FamilyOwned::Cursive,
85            Family::Fantasy => FamilyOwned::Fantasy,
86            Family::Monospace => FamilyOwned::Monospace,
87        }
88    }
89
90    pub fn as_family(&self) -> Family {
91        match self {
92            FamilyOwned::Name(name) => Family::Name(name),
93            FamilyOwned::Serif => Family::Serif,
94            FamilyOwned::SansSerif => Family::SansSerif,
95            FamilyOwned::Cursive => Family::Cursive,
96            FamilyOwned::Fantasy => Family::Fantasy,
97            FamilyOwned::Monospace => Family::Monospace,
98        }
99    }
100}
101
102/// Metrics, but implementing Eq and Hash using u32 representation of f32
103//TODO: what are the edge cases of this?
104#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
105pub struct CacheMetrics {
106    font_size_bits: u32,
107    line_height_bits: u32,
108}
109
110impl From<Metrics> for CacheMetrics {
111    fn from(metrics: Metrics) -> Self {
112        Self {
113            font_size_bits: metrics.font_size.to_bits(),
114            line_height_bits: metrics.line_height.to_bits(),
115        }
116    }
117}
118
119impl From<CacheMetrics> for Metrics {
120    fn from(metrics: CacheMetrics) -> Self {
121        Self {
122            font_size: f32::from_bits(metrics.font_size_bits),
123            line_height: f32::from_bits(metrics.line_height_bits),
124        }
125    }
126}
127
128/// Text attributes
129#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
130pub struct Attrs<'a> {
131    //TODO: should this be an option?
132    pub color_opt: Option<Color>,
133    pub family: Family<'a>,
134    pub stretch: Stretch,
135    pub style: Style,
136    pub weight: Weight,
137    pub metadata: usize,
138    pub cache_key_flags: CacheKeyFlags,
139    pub metrics_opt: Option<CacheMetrics>,
140}
141
142impl<'a> Attrs<'a> {
143    /// Create a new set of attributes with sane defaults
144    ///
145    /// This defaults to a regular Sans-Serif font.
146    pub fn new() -> Self {
147        Self {
148            color_opt: None,
149            family: Family::SansSerif,
150            stretch: Stretch::Normal,
151            style: Style::Normal,
152            weight: Weight::NORMAL,
153            metadata: 0,
154            cache_key_flags: CacheKeyFlags::empty(),
155            metrics_opt: None,
156        }
157    }
158
159    /// Set [Color]
160    pub fn color(mut self, color: Color) -> Self {
161        self.color_opt = Some(color);
162        self
163    }
164
165    /// Set [Family]
166    pub fn family(mut self, family: Family<'a>) -> Self {
167        self.family = family;
168        self
169    }
170
171    /// Set [Stretch]
172    pub fn stretch(mut self, stretch: Stretch) -> Self {
173        self.stretch = stretch;
174        self
175    }
176
177    /// Set [Style]
178    pub fn style(mut self, style: Style) -> Self {
179        self.style = style;
180        self
181    }
182
183    /// Set [Weight]
184    pub fn weight(mut self, weight: Weight) -> Self {
185        self.weight = weight;
186        self
187    }
188
189    /// Set metadata
190    pub fn metadata(mut self, metadata: usize) -> Self {
191        self.metadata = metadata;
192        self
193    }
194
195    /// Set [`CacheKeyFlags`]
196    pub fn cache_key_flags(mut self, cache_key_flags: CacheKeyFlags) -> Self {
197        self.cache_key_flags = cache_key_flags;
198        self
199    }
200
201    /// Set [`Metrics`], overriding values in buffer
202    pub fn metrics(mut self, metrics: Metrics) -> Self {
203        self.metrics_opt = Some(metrics.into());
204        self
205    }
206
207    /// Check if font matches
208    pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
209        //TODO: smarter way of including emoji
210        face.post_script_name.contains("Emoji")
211            || (face.style == self.style && face.stretch == self.stretch)
212    }
213
214    /// Check if this set of attributes can be shaped with another
215    pub fn compatible(&self, other: &Self) -> bool {
216        self.family == other.family
217            && self.stretch == other.stretch
218            && self.style == other.style
219            && self.weight == other.weight
220    }
221}
222
223/// Font-specific part of [`Attrs`] to be used for matching
224#[derive(Clone, Debug, Eq, Hash, PartialEq)]
225pub struct FontMatchAttrs {
226    family: FamilyOwned,
227    stretch: Stretch,
228    style: Style,
229    weight: Weight,
230}
231
232impl<'a> From<Attrs<'a>> for FontMatchAttrs {
233    fn from(attrs: Attrs<'a>) -> Self {
234        Self {
235            family: FamilyOwned::new(attrs.family),
236            stretch: attrs.stretch,
237            style: attrs.style,
238            weight: attrs.weight,
239        }
240    }
241}
242
243/// An owned version of [`Attrs`]
244#[derive(Clone, Debug, Eq, Hash, PartialEq)]
245pub struct AttrsOwned {
246    //TODO: should this be an option?
247    pub color_opt: Option<Color>,
248    pub family_owned: FamilyOwned,
249    pub stretch: Stretch,
250    pub style: Style,
251    pub weight: Weight,
252    pub metadata: usize,
253    pub cache_key_flags: CacheKeyFlags,
254    pub metrics_opt: Option<CacheMetrics>,
255}
256
257impl AttrsOwned {
258    pub fn new(attrs: Attrs) -> Self {
259        Self {
260            color_opt: attrs.color_opt,
261            family_owned: FamilyOwned::new(attrs.family),
262            stretch: attrs.stretch,
263            style: attrs.style,
264            weight: attrs.weight,
265            metadata: attrs.metadata,
266            cache_key_flags: attrs.cache_key_flags,
267            metrics_opt: attrs.metrics_opt,
268        }
269    }
270
271    pub fn as_attrs(&self) -> Attrs {
272        Attrs {
273            color_opt: self.color_opt,
274            family: self.family_owned.as_family(),
275            stretch: self.stretch,
276            style: self.style,
277            weight: self.weight,
278            metadata: self.metadata,
279            cache_key_flags: self.cache_key_flags,
280            metrics_opt: self.metrics_opt,
281        }
282    }
283}
284
285/// List of text attributes to apply to a line
286//TODO: have this clean up the spans when changes are made
287#[derive(Debug, Clone, Eq, PartialEq)]
288pub struct AttrsList {
289    defaults: AttrsOwned,
290    pub(crate) spans: RangeMap<usize, AttrsOwned>,
291}
292
293impl AttrsList {
294    /// Create a new attributes list with a set of default [Attrs]
295    pub fn new(defaults: Attrs) -> Self {
296        Self {
297            defaults: AttrsOwned::new(defaults),
298            spans: RangeMap::new(),
299        }
300    }
301
302    /// Get the default [Attrs]
303    pub fn defaults(&self) -> Attrs {
304        self.defaults.as_attrs()
305    }
306
307    /// Get the current attribute spans
308    pub fn spans(&self) -> Vec<(&Range<usize>, &AttrsOwned)> {
309        self.spans_iter().collect()
310    }
311
312    /// Get an iterator over the current attribute spans
313    pub fn spans_iter(&self) -> impl Iterator<Item = (&Range<usize>, &AttrsOwned)> + '_ {
314        self.spans.iter()
315    }
316
317    /// Clear the current attribute spans
318    pub fn clear_spans(&mut self) {
319        self.spans.clear();
320    }
321
322    /// Add an attribute span, removes any previous matching parts of spans
323    pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
324        //do not support 1..1 or 2..1 even if by accident.
325        if range.is_empty() {
326            return;
327        }
328
329        self.spans.insert(range, AttrsOwned::new(attrs));
330    }
331
332    /// Get the attribute span for an index
333    ///
334    /// This returns a span that contains the index
335    pub fn get_span(&self, index: usize) -> Attrs {
336        self.spans
337            .get(&index)
338            .map(|v| v.as_attrs())
339            .unwrap_or(self.defaults.as_attrs())
340    }
341
342    /// Split attributes list at an offset
343    #[allow(clippy::missing_panics_doc)]
344    pub fn split_off(&mut self, index: usize) -> Self {
345        let mut new = Self::new(self.defaults.as_attrs());
346        let mut removes = Vec::new();
347
348        //get the keys we need to remove or fix.
349        for span in self.spans.iter() {
350            if span.0.end <= index {
351                continue;
352            } else if span.0.start >= index {
353                removes.push((span.0.clone(), false));
354            } else {
355                removes.push((span.0.clone(), true));
356            }
357        }
358
359        for (key, resize) in removes {
360            let (range, attrs) = self
361                .spans
362                .get_key_value(&key.start)
363                .map(|v| (v.0.clone(), v.1.clone()))
364                .expect("attrs span not found");
365            self.spans.remove(key);
366
367            if resize {
368                new.spans.insert(0..range.end - index, attrs.clone());
369                self.spans.insert(range.start..index, attrs);
370            } else {
371                new.spans
372                    .insert(range.start - index..range.end - index, attrs);
373            }
374        }
375        new
376    }
377
378    /// Resets the attributes with new defaults.
379    pub(crate) fn reset(mut self, default: Attrs) -> Self {
380        self.defaults = AttrsOwned::new(default);
381        self.spans.clear();
382        self
383    }
384}