1use crate::Color;
3use std::{fmt, sync::Arc};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub struct NumberFormatError;
8
9impl fmt::Display for NumberFormatError {
10 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
11 f.write_str("invalid fixed-point number format")
12 }
13}
14
15impl std::error::Error for NumberFormatError {}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub struct FixedNumber {
21 coefficient: i64,
22 scale: u8,
23}
24
25impl FixedNumber {
26 pub fn new(coefficient: i64, scale: u8) -> Result<Self, NumberFormatError> {
28 if scale > 18 {
29 return Err(NumberFormatError);
30 }
31
32 Ok(Self { coefficient, scale })
33 }
34
35 pub fn coefficient(self) -> i64 {
37 self.coefficient
38 }
39
40 pub fn scale(self) -> u8 {
42 self.scale
43 }
44}
45
46impl From<i64> for FixedNumber {
47 fn from(coefficient: i64) -> Self {
48 Self {
49 coefficient,
50 scale: 0,
51 }
52 }
53}
54
55#[derive(Clone, Debug, PartialEq, Eq)]
56struct Format {
57 prefix: Arc<str>,
58 suffix: Arc<str>,
59 decimal: Arc<str>,
60 separator: Arc<str>,
61 primary: usize,
62 secondary: usize,
63 digits: [char; 10],
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
71pub struct NumberFormat(Arc<Format>);
72
73impl Default for NumberFormat {
74 fn default() -> Self {
75 Self(Arc::new(Format {
76 prefix: "".into(),
77 suffix: "".into(),
78 decimal: ".".into(),
79 separator: "".into(),
80 primary: 3,
81 secondary: 3,
82 digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
83 }))
84 }
85}
86
87impl NumberFormat {
88 pub fn prefix(mut self, prefix: impl Into<Arc<str>>) -> Self {
90 Arc::make_mut(&mut self.0).prefix = prefix.into();
91
92 self
93 }
94
95 pub fn suffix(mut self, suffix: impl Into<Arc<str>>) -> Self {
97 Arc::make_mut(&mut self.0).suffix = suffix.into();
98
99 self
100 }
101
102 pub fn decimal(mut self, separator: impl Into<Arc<str>>) -> Self {
104 let separator = separator.into();
105
106 assert!(!separator.is_empty(), "decimal separator must not be empty");
107 Arc::make_mut(&mut self.0).decimal = separator;
108
109 self
110 }
111
112 pub fn grouping(
115 mut self,
116 separator: impl Into<Arc<str>>,
117 primary: usize,
118 secondary: usize,
119 ) -> Self {
120 assert!(
121 (1..=18).contains(&primary) && (1..=18).contains(&secondary),
122 "invalid digit groups"
123 );
124 let format = Arc::make_mut(&mut self.0);
125
126 format.separator = separator.into();
127 format.primary = primary;
128 format.secondary = secondary;
129
130 self
131 }
132
133 pub fn digits(mut self, digits: [char; 10]) -> Self {
136 assert!(
137 digits
138 .iter()
139 .enumerate()
140 .all(|(i, c)| c.is_numeric() && !digits[..i].contains(c)),
141 "invalid digit alphabet"
142 );
143 Arc::make_mut(&mut self.0).digits = digits;
144
145 self
146 }
147
148 pub fn format(&self, value: FixedNumber, min_integer_digits: usize) -> FormattedNumber {
152 assert!(min_integer_digits <= 256, "minimum digit count exceeds 256");
153 let scale = usize::from(value.scale);
154 let magnitude = value.coefficient.unsigned_abs().to_string();
155
156 let width = magnitude
157 .len()
158 .max(scale + 1)
159 .max(scale + min_integer_digits);
160
161 let padded = format!("{magnitude:0>width$}");
162 let integer = padded.len() - scale;
163 let mut text = String::new();
164 let mut slots = Vec::new();
165
166 let push =
167 |text: &mut String, slots: &mut Vec<(usize, usize)>, string: &str, slot: usize| {
168 for (index, c) in string.char_indices() {
169 slots.push((text.len(), slot + index * 8));
170 text.push(c);
171 }
172 };
173
174 if value.coefficient < 0 {
175 push(&mut text, &mut slots, "-", 4);
176 }
177
178 push(&mut text, &mut slots, &self.0.prefix, 2);
179
180 for (index, digit) in padded.bytes().enumerate() {
181 if index == integer && scale > 0 {
182 push(&mut text, &mut slots, &self.0.decimal, 5);
183 }
184
185 if index > 0 && index < integer && !self.0.separator.is_empty() {
186 let remaining = integer - index;
187
188 if remaining == self.0.primary
189 || (remaining > self.0.primary
190 && (remaining - self.0.primary).is_multiple_of(self.0.secondary))
191 {
192 push(
193 &mut text,
194 &mut slots,
195 &self.0.separator,
196 6 + remaining * self.0.separator.len() * 8,
197 );
198 }
199 }
200
201 let slot = if index < integer {
202 (integer - index - 1) * 8
203 } else {
204 (index - integer) * 8 + 1
205 };
206
207 slots.push((text.len(), slot));
208 text.push(self.0.digits[usize::from(digit - b'0')]);
209 }
210
211 push(&mut text, &mut slots, &self.0.suffix, 3);
212
213 FormattedNumber {
214 text: text.into(),
215 slots: slots.into(),
216 }
217 }
218}
219
220#[derive(Clone, Debug, PartialEq, Eq)]
223pub struct FormattedNumber {
224 text: Arc<str>,
225 slots: Arc<[(usize, usize)]>,
226}
227
228impl FormattedNumber {
229 pub fn text(&self) -> &str {
231 &self.text
232 }
233
234 pub fn slot_at(&self, byte: usize) -> Option<usize> {
236 if byte >= self.text.len() {
237 return None;
238 }
239
240 let index = self.slots.partition_point(|(start, _)| *start <= byte);
241
242 index.checked_sub(1).map(|index| self.slots[index].1)
243 }
244}
245
246#[derive(Clone, Debug, PartialEq)]
249pub struct TextSpan {
250 pub(crate) text: Arc<str>,
251 pub(crate) color: Option<Color>,
252 pub(crate) family: Option<Arc<str>>,
253 pub(crate) weight: Option<f32>,
254}
255
256impl TextSpan {
257 pub fn new(text: impl Into<Arc<str>>) -> Self {
259 Self {
260 text: text.into(),
261 color: None,
262 family: None,
263 weight: None,
264 }
265 }
266
267 pub fn color(mut self, color: Color) -> Self {
269 self.color = Some(color);
270
271 self
272 }
273
274 pub fn font_family(mut self, family: impl Into<Arc<str>>) -> Self {
276 self.family = Some(family.into());
277
278 self
279 }
280
281 pub fn font_weight(mut self, weight: f32) -> Self {
284 assert!(
285 weight.is_finite() && (1.0..=1000.0).contains(&weight),
286 "invalid font weight"
287 );
288 self.weight = Some(weight);
289
290 self
291 }
292
293 pub fn text_color(&self) -> Option<Color> {
295 self.color
296 }
297
298 pub fn family(&self) -> Option<&str> {
300 self.family.as_deref()
301 }
302
303 pub fn weight(&self) -> Option<f32> {
305 self.weight
306 }
307
308 pub fn text(&self) -> &str {
310 &self.text
311 }
312}
313
314#[derive(Clone, Debug, PartialEq)]
315struct RichData {
316 text: Arc<str>,
317 spans: Vec<TextSpan>,
318}
319
320#[derive(Clone, Debug, PartialEq)]
323pub struct RichText(Arc<RichData>);
324
325impl RichText {
326 pub fn new(spans: impl IntoIterator<Item = TextSpan>) -> Self {
328 let spans: Vec<_> = spans
329 .into_iter()
330 .filter(|span| !span.text.is_empty())
331 .collect();
332
333 let text = spans
334 .iter()
335 .map(|span| span.text.as_ref())
336 .collect::<String>();
337
338 Self(Arc::new(RichData {
339 text: text.into(),
340 spans,
341 }))
342 }
343
344 pub fn text(&self) -> &str {
346 &self.0.text
347 }
348
349 pub fn spans(&self) -> &[TextSpan] {
351 &self.0.spans
352 }
353
354 pub fn span_at(&self, byte: usize) -> Option<&TextSpan> {
356 let mut end = 0;
357
358 self.0.spans.iter().find(|span| {
359 end += span.text.len();
360
361 byte < end
362 })
363 }
364}