1use std::ops::Range;
2
3use crate::text::{fontdb, Family, Stretch, Style, Weight};
4use peniko::Color;
5
6#[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#[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#[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 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 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 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 pub fn stretch(mut self, stretch: Stretch) -> Self {
125 self.attrs = self.attrs.stretch(stretch);
126 self
127 }
128
129 pub fn style(mut self, style: Style) -> Self {
131 self.attrs = self.attrs.style(style);
132 self
133 }
134
135 pub fn weight(mut self, weight: Weight) -> Self {
137 self.attrs = self.attrs.weight(weight);
138 self
139 }
140
141 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 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 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 pub fn metadata(mut self, metadata: usize) -> Self {
173 self.attrs = self.attrs.metadata(metadata);
174 self
175 }
176
177 pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
179 self.attrs.matches(face)
180 }
181
182 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 pub fn new(defaults: Attrs) -> Self {
194 Self(cosmic_text::AttrsList::new(&defaults.attrs))
195 }
196
197 pub fn defaults(&self) -> Attrs {
199 self.0.defaults().into()
200 }
201
202 pub fn clear_spans(&mut self) {
204 self.0.clear_spans();
205 }
206
207 pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
209 self.0.add_span(range, &attrs.attrs);
210 }
211
212 pub fn get_span(&self, index: usize) -> Attrs {
216 self.0.get_span(index).into()
217 }
218
219 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}