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}