label.rs

  1use serde_json::json;
  2
  3use crate::{
  4    color::ColorU,
  5    font_cache::FamilyId,
  6    fonts::{FontId, Properties},
  7    geometry::{
  8        rect::RectF,
  9        vector::{vec2f, Vector2F},
 10    },
 11    json::{ToJson, Value},
 12    text_layout::Line,
 13    AfterLayoutContext, DebugContext, Element, Event, EventContext, FontCache, LayoutContext,
 14    PaintContext, SizeConstraint,
 15};
 16use std::{ops::Range, sync::Arc};
 17
 18pub struct Label {
 19    text: String,
 20    family_id: FamilyId,
 21    font_properties: Properties,
 22    font_size: f32,
 23    highlights: Option<Highlights>,
 24}
 25
 26pub struct LayoutState {
 27    line: Arc<Line>,
 28    colors: Vec<(Range<usize>, ColorU)>,
 29}
 30
 31pub struct Highlights {
 32    color: ColorU,
 33    indices: Vec<usize>,
 34    font_properties: Properties,
 35}
 36
 37impl Label {
 38    pub fn new(text: String, family_id: FamilyId, font_size: f32) -> Self {
 39        Self {
 40            text,
 41            family_id,
 42            font_properties: Properties::new(),
 43            font_size,
 44            highlights: None,
 45        }
 46    }
 47
 48    pub fn with_highlights(
 49        mut self,
 50        color: ColorU,
 51        font_properties: Properties,
 52        indices: Vec<usize>,
 53    ) -> Self {
 54        self.highlights = Some(Highlights {
 55            color,
 56            font_properties,
 57            indices,
 58        });
 59        self
 60    }
 61
 62    fn layout_text(
 63        &self,
 64        font_cache: &FontCache,
 65        font_id: FontId,
 66    ) -> (Vec<(Range<usize>, FontId)>, Vec<(Range<usize>, ColorU)>) {
 67        let text_len = self.text.len();
 68        let mut styles;
 69        let mut colors;
 70        if let Some(highlights) = self.highlights.as_ref() {
 71            styles = Vec::new();
 72            colors = Vec::new();
 73            let highlight_font_id = font_cache
 74                .select_font(self.family_id, &highlights.font_properties)
 75                .unwrap_or(font_id);
 76            let mut pending_highlight: Option<Range<usize>> = None;
 77            for ix in &highlights.indices {
 78                if let Some(pending_highlight) = pending_highlight.as_mut() {
 79                    if *ix == pending_highlight.end {
 80                        pending_highlight.end += 1;
 81                    } else {
 82                        styles.push((pending_highlight.clone(), highlight_font_id));
 83                        colors.push((pending_highlight.clone(), highlights.color));
 84                        styles.push((pending_highlight.end..*ix, font_id));
 85                        colors.push((pending_highlight.end..*ix, ColorU::black()));
 86                        *pending_highlight = *ix..*ix + 1;
 87                    }
 88                } else {
 89                    styles.push((0..*ix, font_id));
 90                    colors.push((0..*ix, ColorU::black()));
 91                    pending_highlight = Some(*ix..*ix + 1);
 92                }
 93            }
 94            if let Some(pending_highlight) = pending_highlight.as_mut() {
 95                styles.push((pending_highlight.clone(), highlight_font_id));
 96                colors.push((pending_highlight.clone(), highlights.color));
 97                if text_len > pending_highlight.end {
 98                    styles.push((pending_highlight.end..text_len, font_id));
 99                    colors.push((pending_highlight.end..text_len, ColorU::black()));
100                }
101            } else {
102                styles.push((0..text_len, font_id));
103                colors.push((0..text_len, ColorU::black()));
104            }
105        } else {
106            styles = vec![(0..text_len, font_id)];
107            colors = vec![(0..text_len, ColorU::black())];
108        }
109
110        (styles, colors)
111    }
112}
113
114impl Element for Label {
115    type LayoutState = LayoutState;
116    type PaintState = ();
117
118    fn layout(
119        &mut self,
120        constraint: SizeConstraint,
121        ctx: &mut LayoutContext,
122    ) -> (Vector2F, Self::LayoutState) {
123        let font_id = ctx
124            .font_cache
125            .select_font(self.family_id, &self.font_properties)
126            .unwrap();
127        let (styles, colors) = self.layout_text(&ctx.font_cache, font_id);
128        let line =
129            ctx.text_layout_cache
130                .layout_str(self.text.as_str(), self.font_size, styles.as_slice());
131
132        let size = vec2f(
133            line.width.max(constraint.min.x()).min(constraint.max.x()),
134            ctx.font_cache.line_height(font_id, self.font_size).ceil(),
135        );
136
137        (size, LayoutState { line, colors })
138    }
139
140    fn after_layout(&mut self, _: Vector2F, _: &mut Self::LayoutState, _: &mut AfterLayoutContext) {
141    }
142
143    fn paint(
144        &mut self,
145        bounds: RectF,
146        layout: &mut Self::LayoutState,
147        ctx: &mut PaintContext,
148    ) -> Self::PaintState {
149        layout.line.paint(
150            bounds.origin(),
151            RectF::new(vec2f(0., 0.), bounds.size()),
152            layout.colors.as_slice(),
153            ctx,
154        )
155    }
156
157    fn dispatch_event(
158        &mut self,
159        _: &Event,
160        _: RectF,
161        _: &mut Self::LayoutState,
162        _: &mut Self::PaintState,
163        _: &mut EventContext,
164    ) -> bool {
165        false
166    }
167
168    fn debug(
169        &self,
170        bounds: RectF,
171        _: &Self::LayoutState,
172        _: &Self::PaintState,
173        ctx: &DebugContext,
174    ) -> Value {
175        json!({
176            "type": "Label",
177            "bounds": bounds.to_json(),
178            "font_family": ctx.font_cache.family_name(self.family_id).unwrap(),
179            "font_size": self.font_size,
180            "font_properties": self.font_properties.to_json(),
181            "text": &self.text,
182            "highlights": self.highlights.to_json(),
183        })
184    }
185}
186
187impl ToJson for Highlights {
188    fn to_json(&self) -> Value {
189        json!({
190            "color": self.color.to_json(),
191            "indices": self.indices,
192            "font_properties": self.font_properties.to_json(),
193        })
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use font_kit::properties::Weight;
200
201    use super::*;
202
203    #[crate::test(self)]
204    fn test_layout_label_with_highlights(app: &mut crate::MutableAppContext) {
205        let menlo = app.font_cache().load_family(&["Menlo"]).unwrap();
206        let menlo_regular = app
207            .font_cache()
208            .select_font(menlo, &Properties::new())
209            .unwrap();
210        let menlo_bold = app
211            .font_cache()
212            .select_font(menlo, Properties::new().weight(Weight::BOLD))
213            .unwrap();
214        let black = ColorU::black();
215        let red = ColorU::new(255, 0, 0, 255);
216
217        let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0).with_highlights(
218            red,
219            *Properties::new().weight(Weight::BOLD),
220            vec![
221                "".len(),
222                ".αβ".len(),
223                ".αβγδ".len(),
224                ".αβγδε.ⓐ".len(),
225                ".αβγδε.ⓐⓑ".len(),
226            ],
227        );
228
229        let (styles, colors) = label.layout_text(app.font_cache().as_ref(), menlo_regular);
230        assert_eq!(styles.len(), colors.len());
231
232        let mut spans = Vec::new();
233        for ((style_range, font_id), (color_range, color)) in styles.into_iter().zip(colors) {
234            assert_eq!(style_range, color_range);
235            spans.push((style_range, font_id, color));
236        }
237        assert_eq!(
238            spans,
239            &[
240                (0..3, menlo_regular, black),
241                (3..4, menlo_bold, red),
242                (4..5, menlo_regular, black),
243                (5..6, menlo_bold, red),
244                (6..9, menlo_regular, black),
245                (9..10, menlo_bold, red),
246                (10..15, menlo_regular, black),
247                (15..16, menlo_bold, red),
248                (16..18, menlo_regular, black),
249                (18..19, menlo_bold, red),
250                (19..34, menlo_regular, black)
251            ]
252        );
253    }
254}