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