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, 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 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 self.attrs = self.attrs.metrics(self.get_metrics());
159 self
160 }
161
162 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 pub fn metadata(mut self, metadata: usize) -> Self {
171 self.attrs = self.attrs.metadata(metadata);
172 self
173 }
174
175 pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
177 self.attrs.matches(face)
178 }
179
180 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 pub fn new(defaults: Attrs) -> Self {
192 Self(cosmic_text::AttrsList::new(defaults.attrs))
193 }
194
195 pub fn defaults(&self) -> Attrs {
197 self.0.defaults().into()
198 }
199
200 pub fn clear_spans(&mut self) {
202 self.0.clear_spans();
203 }
204
205 pub fn add_span(&mut self, range: Range<usize>, attrs: Attrs) {
207 self.0.add_span(range, attrs.attrs);
208 }
209
210 pub fn get_span(&self, index: usize) -> Attrs {
214 self.0.get_span(index).into()
215 }
216
217 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}