use crate::Color;
use std::{fmt, sync::Arc};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NumberFormatError;
impl fmt::Display for NumberFormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("invalid fixed-point number format")
}
}
impl std::error::Error for NumberFormatError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FixedNumber {
coefficient: i64,
scale: u8,
}
impl FixedNumber {
pub fn new(coefficient: i64, scale: u8) -> Result<Self, NumberFormatError> {
if scale > 18 {
return Err(NumberFormatError);
}
Ok(Self { coefficient, scale })
}
pub fn coefficient(self) -> i64 {
self.coefficient
}
pub fn scale(self) -> u8 {
self.scale
}
}
impl From<i64> for FixedNumber {
fn from(coefficient: i64) -> Self {
Self {
coefficient,
scale: 0,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Format {
prefix: Arc<str>,
suffix: Arc<str>,
decimal: Arc<str>,
separator: Arc<str>,
primary: usize,
secondary: usize,
digits: [char; 10],
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NumberFormat(Arc<Format>);
impl Default for NumberFormat {
fn default() -> Self {
Self(Arc::new(Format {
prefix: "".into(),
suffix: "".into(),
decimal: ".".into(),
separator: "".into(),
primary: 3,
secondary: 3,
digits: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
}))
}
}
impl NumberFormat {
pub fn prefix(mut self, prefix: impl Into<Arc<str>>) -> Self {
Arc::make_mut(&mut self.0).prefix = prefix.into();
self
}
pub fn suffix(mut self, suffix: impl Into<Arc<str>>) -> Self {
Arc::make_mut(&mut self.0).suffix = suffix.into();
self
}
pub fn decimal(mut self, separator: impl Into<Arc<str>>) -> Self {
let separator = separator.into();
assert!(!separator.is_empty(), "decimal separator must not be empty");
Arc::make_mut(&mut self.0).decimal = separator;
self
}
pub fn grouping(
mut self,
separator: impl Into<Arc<str>>,
primary: usize,
secondary: usize,
) -> Self {
assert!(
(1..=18).contains(&primary) && (1..=18).contains(&secondary),
"invalid digit groups"
);
let format = Arc::make_mut(&mut self.0);
format.separator = separator.into();
format.primary = primary;
format.secondary = secondary;
self
}
pub fn digits(mut self, digits: [char; 10]) -> Self {
assert!(
digits
.iter()
.enumerate()
.all(|(i, c)| c.is_numeric() && !digits[..i].contains(c)),
"invalid digit alphabet"
);
Arc::make_mut(&mut self.0).digits = digits;
self
}
pub fn format(&self, value: FixedNumber, min_integer_digits: usize) -> FormattedNumber {
assert!(min_integer_digits <= 256, "minimum digit count exceeds 256");
let scale = usize::from(value.scale);
let magnitude = value.coefficient.unsigned_abs().to_string();
let width = magnitude
.len()
.max(scale + 1)
.max(scale + min_integer_digits);
let padded = format!("{magnitude:0>width$}");
let integer = padded.len() - scale;
let mut text = String::new();
let mut slots = Vec::new();
let push =
|text: &mut String, slots: &mut Vec<(usize, usize)>, string: &str, slot: usize| {
for (index, c) in string.char_indices() {
slots.push((text.len(), slot + index * 8));
text.push(c);
}
};
if value.coefficient < 0 {
push(&mut text, &mut slots, "-", 4);
}
push(&mut text, &mut slots, &self.0.prefix, 2);
for (index, digit) in padded.bytes().enumerate() {
if index == integer && scale > 0 {
push(&mut text, &mut slots, &self.0.decimal, 5);
}
if index > 0 && index < integer && !self.0.separator.is_empty() {
let remaining = integer - index;
if remaining == self.0.primary
|| (remaining > self.0.primary
&& (remaining - self.0.primary).is_multiple_of(self.0.secondary))
{
push(
&mut text,
&mut slots,
&self.0.separator,
6 + remaining * self.0.separator.len() * 8,
);
}
}
let slot = if index < integer {
(integer - index - 1) * 8
} else {
(index - integer) * 8 + 1
};
slots.push((text.len(), slot));
text.push(self.0.digits[usize::from(digit - b'0')]);
}
push(&mut text, &mut slots, &self.0.suffix, 3);
FormattedNumber {
text: text.into(),
slots: slots.into(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FormattedNumber {
text: Arc<str>,
slots: Arc<[(usize, usize)]>,
}
impl FormattedNumber {
pub fn text(&self) -> &str {
&self.text
}
pub fn slot_at(&self, byte: usize) -> Option<usize> {
if byte >= self.text.len() {
return None;
}
let index = self.slots.partition_point(|(start, _)| *start <= byte);
index.checked_sub(1).map(|index| self.slots[index].1)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextSpan {
pub(crate) text: Arc<str>,
pub(crate) color: Option<Color>,
pub(crate) family: Option<Arc<str>>,
pub(crate) weight: Option<f32>,
}
impl TextSpan {
pub fn new(text: impl Into<Arc<str>>) -> Self {
Self {
text: text.into(),
color: None,
family: None,
weight: None,
}
}
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
pub fn font_family(mut self, family: impl Into<Arc<str>>) -> Self {
self.family = Some(family.into());
self
}
pub fn font_weight(mut self, weight: f32) -> Self {
assert!(
weight.is_finite() && (1.0..=1000.0).contains(&weight),
"invalid font weight"
);
self.weight = Some(weight);
self
}
pub fn text_color(&self) -> Option<Color> {
self.color
}
pub fn family(&self) -> Option<&str> {
self.family.as_deref()
}
pub fn weight(&self) -> Option<f32> {
self.weight
}
pub fn text(&self) -> &str {
&self.text
}
}
#[derive(Clone, Debug, PartialEq)]
struct RichData {
text: Arc<str>,
spans: Vec<TextSpan>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RichText(Arc<RichData>);
impl RichText {
pub fn new(spans: impl IntoIterator<Item = TextSpan>) -> Self {
let spans: Vec<_> = spans
.into_iter()
.filter(|span| !span.text.is_empty())
.collect();
let text = spans
.iter()
.map(|span| span.text.as_ref())
.collect::<String>();
Self(Arc::new(RichData {
text: text.into(),
spans,
}))
}
pub fn text(&self) -> &str {
&self.0.text
}
pub fn spans(&self) -> &[TextSpan] {
&self.0.spans
}
pub fn span_at(&self, byte: usize) -> Option<&TextSpan> {
let mut end = 0;
self.0.spans.iter().find(|span| {
end += span.text.len();
byte < end
})
}
}