use super::super::ir::PlacedNode;
use super::super::{approx_width, prim};
use super::anchors::rotated;
use super::geometry::P;
use crate::error::Error;
use crate::resolve::{AttrMap, ResolvedValue};
use crate::span::Span;
#[derive(Clone, Copy, PartialEq)]
pub(super) enum Glyph {
None,
Dia,
R,
Deg,
}
pub(super) struct DimText {
pub main: String,
pub devs: Option<(String, String)>,
}
const TOL_STACK: f64 = 0.7;
const DEV_PAD: f64 = 2.0;
#[allow(clippy::too_many_arguments)]
pub(super) fn compose(
glyph: Glyph,
value: f64,
count: Option<usize>,
replaces: Option<&str>,
follows: Option<&str>,
attrs: &AttrMap,
unit: Option<&str>,
span: Span,
) -> Result<DimText, Error> {
let mut main = String::new();
if let Some(n) = count {
main.push_str(&format!("{n}× "));
}
match replaces {
Some(label) => main.push_str(label),
None => {
match glyph {
Glyph::None => main.push_str(&fmt(value)),
Glyph::Dia => main.push_str(&format!("⌀{}", fmt(value))),
Glyph::R => main.push_str(&format!("R{}", fmt(value))),
Glyph::Deg => main.push_str(&format!("{}°", fmt(value))),
}
if glyph == Glyph::None
&& let Some(u) = unit
{
main.push(' ');
main.push_str(u);
}
}
}
if let Some(label) = follows {
main.push(' ');
main.push_str(label);
}
let devs = tol(&mut main, attrs, span)?;
Ok(DimText { main, devs })
}
fn tol(main: &mut String, attrs: &AttrMap, span: Span) -> Result<Option<(String, String)>, Error> {
let bad = || {
Error::at(
span,
"'tol' takes a number, '+upper -lower', or a fit ident",
)
};
match attrs.get("tol") {
None => Ok(None),
Some(ResolvedValue::Number(t)) => {
main.push_str(&format!("±{}", fmt(t.abs())));
Ok(None)
}
Some(ResolvedValue::Ident(fit)) => {
main.push(' ');
main.push_str(fit);
Ok(None)
}
Some(ResolvedValue::Tuple(pair)) => {
let (Some(u), Some(l)) = (
pair.first().and_then(ResolvedValue::as_number),
pair.get(1).and_then(ResolvedValue::as_number),
) else {
return Err(bad());
};
if pair.len() != 2 {
return Err(bad());
}
Ok(Some((signed(u), signed(l))))
}
Some(_) => Err(bad()),
}
}
fn signed(v: f64) -> String {
if v > 0.0 {
format!("+{}", fmt(v))
} else {
fmt(v)
}
}
pub(super) fn fmt(v: f64) -> String {
let r = (v * 100.0).round() / 100.0;
let r = if r == 0.0 { 0.0 } else { r };
let mut s = format!("{r:.2}");
while s.ends_with('0') {
s.pop();
}
if s.ends_with('.') {
s.pop();
}
s
}
impl DimText {
pub fn width(&self, fs: f64) -> f64 {
let main = approx_width(&self.main, fs, 0.0);
match &self.devs {
None => main,
Some((u, l)) => {
let dfs = fs * TOL_STACK;
main + DEV_PAD + approx_width(u, dfs, 0.0).max(approx_width(l, dfs, 0.0))
}
}
}
pub fn nodes(&self, centre: P, rot: f64, fs: f64) -> Vec<PlacedNode> {
let place = |content: &str, local: P, size: f64| {
let p = rotated(local, rot);
let mut n = prim::text(content, centre.0 + p.0, centre.1 + p.1, size, None, false);
if rot != 0.0 {
n.rotation = rot;
n.attrs.insert("rotate", ResolvedValue::Number(rot));
}
n
};
let Some((u, l)) = &self.devs else {
return vec![place(&self.main, (0.0, 0.0), fs)];
};
let dfs = fs * TOL_STACK;
let wm = approx_width(&self.main, fs, 0.0);
let wd = approx_width(u, dfs, 0.0).max(approx_width(l, dfs, 0.0));
let total = wm + DEV_PAD + wd;
let dev_x = total / 2.0 - wd / 2.0;
vec![
place(&self.main, (wm / 2.0 - total / 2.0, 0.0), fs),
place(u, (dev_x, -dfs * 0.55), dfs),
place(l, (dev_x, dfs * 0.55), dfs),
]
}
}