typst_library/foundations/
repr.rs1use ecow::{eco_format, EcoString};
4use typst_utils::round_with_precision;
5
6use crate::foundations::{func, Str, Value};
7
8pub const MINUS_SIGN: &str = "\u{2212}";
10
11#[func(title = "Representation")]
28pub fn repr(
29 value: Value,
31) -> Str {
32 value.repr().into()
33}
34
35pub trait Repr {
37 fn repr(&self) -> EcoString;
39}
40
41pub fn format_int_with_base(mut n: i64, base: i64) -> EcoString {
43 if n == 0 {
44 return "0".into();
45 }
46
47 const SIZE: usize = 64 + MINUS_SIGN.len();
50 let mut digits = [b'\0'; SIZE];
51 let mut i = SIZE;
52
53 let negative = n < 0;
56 if n > 0 {
57 n = -n;
58 }
59
60 while n != 0 {
61 let digit = char::from_digit(-(n % base) as u32, base as u32);
62 i -= 1;
63 digits[i] = digit.unwrap_or('?') as u8;
64 n /= base;
65 }
66
67 if negative {
68 let prev = i;
69 i -= MINUS_SIGN.len();
70 digits[i..prev].copy_from_slice(MINUS_SIGN.as_bytes());
71 }
72
73 std::str::from_utf8(&digits[i..]).unwrap_or_default().into()
74}
75
76pub fn format_float(
82 mut value: f64,
83 precision: Option<u8>,
84 force_separator: bool,
85 unit: &str,
86) -> EcoString {
87 if let Some(p) = precision {
88 value = round_with_precision(value, p as i16);
89 }
90 let unit_multiplication = if unit.is_empty() { "" } else { " * 1" };
93 if value.is_nan() {
94 eco_format!("float.nan{unit_multiplication}{unit}")
95 } else if value.is_infinite() {
96 let sign = if value < 0.0 { "-" } else { "" };
97 eco_format!("{sign}float.inf{unit_multiplication}{unit}")
98 } else if force_separator {
99 eco_format!("{value:?}{unit}")
100 } else {
101 eco_format!("{value}{unit}")
102 }
103}
104
105pub fn format_float_component(value: f64) -> EcoString {
109 format_float(value, Some(3), false, "")
110}
111
112pub fn format_float_with_unit(value: f64, unit: &str) -> EcoString {
115 format_float(value, Some(2), false, unit)
116}
117
118pub fn display_float(value: f64) -> EcoString {
120 if value.is_nan() {
121 "NaN".into()
122 } else if value.is_infinite() {
123 let sign = if value < 0.0 { MINUS_SIGN } else { "" };
124 eco_format!("{sign}∞")
125 } else if value < 0.0 {
126 eco_format!("{}{}", MINUS_SIGN, value.abs())
127 } else {
128 eco_format!("{}", value.abs())
129 }
130}
131
132pub fn separated_list(pieces: &[impl AsRef<str>], last: &str) -> String {
134 let mut buf = String::new();
135 for (i, part) in pieces.iter().enumerate() {
136 match i {
137 0 => {}
138 1 if pieces.len() == 2 => {
139 buf.push(' ');
140 buf.push_str(last);
141 buf.push(' ');
142 }
143 i if i + 1 == pieces.len() => {
144 buf.push_str(", ");
145 buf.push_str(last);
146 buf.push(' ');
147 }
148 _ => buf.push_str(", "),
149 }
150 buf.push_str(part.as_ref());
151 }
152 buf
153}
154
155pub fn pretty_comma_list(pieces: &[impl AsRef<str>], trailing_comma: bool) -> String {
160 const MAX_WIDTH: usize = 50;
161
162 let mut buf = String::new();
163 let len = pieces.iter().map(|s| s.as_ref().len()).sum::<usize>()
164 + 2 * pieces.len().saturating_sub(1);
165
166 if len <= MAX_WIDTH {
167 for (i, piece) in pieces.iter().enumerate() {
168 if i > 0 {
169 buf.push_str(", ");
170 }
171 buf.push_str(piece.as_ref());
172 }
173 if trailing_comma {
174 buf.push(',');
175 }
176 } else {
177 for piece in pieces {
178 buf.push_str(piece.as_ref().trim());
179 buf.push_str(",\n");
180 }
181 }
182
183 buf
184}
185
186pub fn pretty_array_like(parts: &[impl AsRef<str>], trailing_comma: bool) -> String {
191 let list = pretty_comma_list(parts, trailing_comma);
192 let mut buf = String::new();
193 buf.push('(');
194 if list.contains('\n') {
195 buf.push('\n');
196 for (i, line) in list.lines().enumerate() {
197 if i > 0 {
198 buf.push('\n');
199 }
200 buf.push_str(" ");
201 buf.push_str(line);
202 }
203 buf.push('\n');
204 } else {
205 buf.push_str(&list);
206 }
207 buf.push(')');
208 buf
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_to_base() {
217 assert_eq!(&format_int_with_base(0, 10), "0");
218 assert_eq!(&format_int_with_base(0, 16), "0");
219 assert_eq!(&format_int_with_base(0, 36), "0");
220 assert_eq!(
221 &format_int_with_base(i64::MAX, 2),
222 "111111111111111111111111111111111111111111111111111111111111111"
223 );
224 assert_eq!(
225 &format_int_with_base(i64::MIN, 2),
226 "\u{2212}1000000000000000000000000000000000000000000000000000000000000000"
227 );
228 assert_eq!(&format_int_with_base(i64::MAX, 10), "9223372036854775807");
229 assert_eq!(&format_int_with_base(i64::MIN, 10), "\u{2212}9223372036854775808");
230 assert_eq!(&format_int_with_base(i64::MAX, 16), "7fffffffffffffff");
231 assert_eq!(&format_int_with_base(i64::MIN, 16), "\u{2212}8000000000000000");
232 assert_eq!(&format_int_with_base(i64::MAX, 36), "1y2p0ij32e8e7");
233 assert_eq!(&format_int_with_base(i64::MIN, 36), "\u{2212}1y2p0ij32e8e8");
234 }
235}