pub const MAX_DIMEN: i64 = (1 << 30) - 1;
pub const SP_PER_PT: f64 = 65536.0;
pub const UNITS: [(&str, f64); 12] = [
("pt", 65536.0),
("pc", 786432.0),
("in", 4736286.72),
("bp", 65781.76),
("cm", 1864679.811),
("mm", 186467.981),
("dd", 70124.086),
("cc", 841489.04),
("sp", 1.0),
("em", 655360.0),
("ex", 282168.0),
("px", 65781.76),
];
pub fn round_decimals(digits: &[u8]) -> i64 {
let mut a: i64 = 0;
for &d in digits.iter().take(17).rev() {
a = (a + d as i64 * 131072) / 10;
}
(a + 1) / 2
}
pub fn xn_over_d(x: i64, n: i64, d: i64) -> (i64, i64) {
let neg = x < 0;
let x = x.abs();
let t = (x % 32768) * n;
let mut u = (x / 32768) * n + t / 32768;
let v = (u % d) * 32768 + t % 32768;
u = 32768 * (u / d) + v / d;
let rem = v % d;
if neg {
(-u, -rem)
} else {
(u, rem)
}
}
pub fn mult(n: i64, x: i64) -> i64 {
n.saturating_mul(x)
}
pub fn ratio(unit: &str) -> Option<(i64, i64)> {
Some(match unit {
"pt" => (1, 1),
"in" => (7227, 100),
"pc" => (12, 1),
"cm" => (7227, 254),
"mm" => (7227, 2540),
"bp" | "px" => (7227, 7200),
"dd" => (1238, 1157),
"cc" => (14856, 1157),
_ => return None,
})
}
pub fn exact(integer: i64, digits: &[u8], unit: &str) -> Option<i64> {
let mut f = round_decimals(digits);
let mut v = integer;
if unit == "sp" {
return Some(v);
}
if let Some(sp) = match unit {
"em" => Some(655360),
"ex" => Some(282168),
_ => None,
} {
let (frac, _) = xn_over_d(sp, f, 65536);
return Some(mult(v, sp) + frac);
}
let (n, d) = ratio(unit)?;
if (n, d) != (1, 1) {
let (q, r) = xn_over_d(v, n, d);
v = q;
f = (n * f + 65536 * r) / d;
v += f / 65536;
f %= 65536;
}
Some(v * 65536 + f)
}
pub fn show(sp: i64) -> String {
let mut s = String::new();
let mut v = sp;
if v < 0 {
s.push('-');
v = -v;
}
s.push_str(&(v / 65536).to_string());
s.push('.');
let mut f = 10 * (v % 65536) + 5;
let mut delta = 10;
loop {
if delta > 65536 {
f += 0x8000 - 50000;
}
s.push(char::from(b'0' + (f / 65536) as u8));
f = 10 * (f % 65536);
delta *= 10;
if f <= delta {
break;
}
}
s.push_str("pt");
s
}
pub fn to_cm(sp: i64) -> f64 {
sp as f64 / SP_PER_PT * 2.54 / 72.27
}
pub fn from_cm(cm: f64) -> i64 {
(cm / 2.54 * 72.27 * SP_PER_PT).round().clamp(-(MAX_DIMEN as f64), MAX_DIMEN as f64) as i64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shown_as_tex_shows_them() {
assert_eq!(show(65536 * 12), "12.0pt");
assert_eq!(show(-26214), "-0.4pt");
assert_eq!(show(1), "0.00002pt");
assert_eq!(show(32768), "0.5pt");
assert_eq!(show(exact(1, &[], "cm").unwrap()), "28.45274pt");
assert_eq!(show(exact(1, &[5], "pt").unwrap()), "1.5pt");
assert_eq!(show(exact(0, &[4], "pt").unwrap()), "0.4pt");
}
}