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