1use std::collections::{HashMap, HashSet};
15use std::f64::consts::{FRAC_1_SQRT_2, PI};
16
17use crate::ast::*;
18use crate::geom::{Bbox, Point};
19use crate::ir::*;
20use crate::token::{self, Corner, Dir, EnvVar, Func1, Func2, LineType, Prim};
21
22#[derive(Debug, Clone, PartialEq)]
24pub struct EvalError {
25 pub msg: String,
26}
27
28impl std::fmt::Display for EvalError {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 write!(f, "{}", self.msg)
31 }
32}
33
34type ER<T> = Result<T, EvalError>;
35
36const DP_TEXT_RATIO: f64 = 0.66;
37
38fn err<T>(msg: impl Into<String>) -> ER<T> {
39 Err(EvalError { msg: msg.into() })
40}
41
42pub fn eval(pic: &Picture) -> ER<Drawing> {
44 let mut st = State::new();
45 st.macros = pic.macros.clone();
46 st.base_dir = pic.base_dir.clone();
47 st.eval_stmts(&pic.stmts)?;
48 let want_w = match &pic.width {
49 Some(e) => Some(st.eval_expr(e)?),
50 None => None,
51 };
52 let want_h = match &pic.height {
53 Some(e) => Some(st.eval_expr(e)?),
54 None => None,
55 };
56 let (maxw, maxh) = (st.env.get(EnvVar::Maxpswid), st.env.get(EnvVar::Maxpsht));
57 let canvas_margin = st.canvas_margin()?;
58 let mut d = Drawing {
59 shapes: st.shapes,
60 bbox: st.bbox,
61 prelude_thick: st.env.get(EnvVar::Linethick),
62 canvas_margin,
63 anims: st.anims,
64 diagnostics: st.diagnostics,
65 };
66 apply_ps_size(&mut d, want_w, want_h);
67 clamp_to_maxps(&mut d, maxw, maxh);
68 Ok(d)
69}
70
71fn clamp_to_maxps(d: &mut Drawing, maxw: f64, maxh: f64) {
75 for _ in 0..4 {
76 if d.bbox.is_empty() {
77 return;
78 }
79 let (w, h) = (canvas_width(d), canvas_height(d));
80 let mut factor = 1.0_f64;
81 if maxw > 0.0 && w > maxw {
82 factor = factor.min(maxw / w);
83 }
84 if maxh > 0.0 && h > maxh {
85 factor = factor.min(maxh / h);
86 }
87 if factor >= 1.0 - 1e-9 {
88 return;
89 }
90 for sh in &mut d.shapes {
91 scale_shape(sh, factor);
92 }
93 d.prelude_thick *= factor;
94 d.canvas_margin.scale_by(factor);
95 d.bbox = drawing_painted_bbox(&d.shapes);
96 }
97}
98
99fn apply_ps_size(d: &mut Drawing, want_w: Option<f64>, want_h: Option<f64>) {
103 if d.bbox.is_empty() {
104 return;
105 }
106 let factor = match (want_w, want_h) {
107 (Some(w), _) if w > 0.0 && d.bbox.width() > 0.0 => w / d.bbox.width(),
108 (None, Some(h)) if h > 0.0 && d.bbox.height() > 0.0 => h / d.bbox.height(),
109 _ => return,
110 };
111 if (factor - 1.0).abs() < 1e-9 {
112 return;
113 }
114 for sh in &mut d.shapes {
115 scale_shape(sh, factor);
116 }
117 d.prelude_thick *= factor;
118 d.canvas_margin.scale_by(factor);
119 d.bbox = drawing_painted_bbox(&d.shapes);
120}
121
122fn canvas_width(d: &Drawing) -> f64 {
123 d.bbox.width() + d.canvas_margin.horizontal()
124}
125
126fn canvas_height(d: &Drawing) -> f64 {
127 d.bbox.height() + d.canvas_margin.vertical()
128}
129
130const SCALED_VARS: [EnvVar; 22] = [
132 EnvVar::Arcrad,
133 EnvVar::Arrowht,
134 EnvVar::Arrowwid,
135 EnvVar::Boxht,
136 EnvVar::Boxrad,
137 EnvVar::Boxwid,
138 EnvVar::Circlerad,
139 EnvVar::Dashwid,
140 EnvVar::Ellipseht,
141 EnvVar::Ellipsewid,
142 EnvVar::Lineht,
143 EnvVar::Linewid,
144 EnvVar::Moveht,
145 EnvVar::Movewid,
146 EnvVar::Textht,
147 EnvVar::Textwid,
148 EnvVar::Textoffset,
149 EnvVar::Margin,
150 EnvVar::Topmargin,
151 EnvVar::Rightmargin,
152 EnvVar::Bottommargin,
153 EnvVar::Leftmargin,
154];
155
156#[derive(Clone)]
159struct EnvVars {
160 v: HashMap<u8, f64>,
161}
162
163fn ev_key(e: EnvVar) -> u8 {
164 e as u8
165}
166
167impl EnvVars {
168 fn new() -> Self {
169 use EnvVar::*;
170 let defaults = [
171 (Arcrad, 0.25),
172 (Arrowht, 0.1),
173 (Arrowwid, 0.05),
174 (Boxht, 0.5),
175 (Boxrad, 0.0),
176 (Boxwid, 0.75),
177 (Circlerad, 0.25),
178 (Dashwid, 0.05),
179 (Ellipseht, 0.5),
180 (Ellipsewid, 0.75),
181 (Lineht, 0.5),
182 (Linewid, 0.5),
183 (Moveht, 0.5),
184 (Movewid, 0.5),
185 (Textht, (11.0 / 72.0) * DP_TEXT_RATIO),
186 (Textoffset, 2.0 / 72.0),
187 (Textwid, 0.0),
188 (Arrowhead, 1.0),
189 (Fillval, 0.5),
190 (Linethick, 0.8),
191 (Maxpsht, 11.0),
192 (Maxpswid, 8.5),
193 (Scale, 1.0),
194 (Margin, 0.0),
195 (Topmargin, 0.0),
196 (Rightmargin, 0.0),
197 (Bottommargin, 0.0),
198 (Leftmargin, 0.0),
199 ];
200 let mut v = HashMap::new();
201 for (e, d) in defaults {
202 v.insert(ev_key(e), d);
203 }
204 EnvVars { v }
205 }
206 fn get(&self, e: EnvVar) -> f64 {
207 *self.v.get(&ev_key(e)).unwrap_or(&0.0)
208 }
209 fn set(&mut self, e: EnvVar, val: f64) {
210 self.v.insert(ev_key(e), val);
211 }
212}
213
214#[derive(Clone, Copy, PartialEq, Eq)]
217enum PKind {
218 Box,
219 Circle,
220 Ellipse,
221 Line,
222 Move,
223 Spline,
224 Arc,
225 Block,
226 Text,
227}
228
229#[derive(Clone)]
230struct Placed {
231 kind: PKind,
232 center: Point,
233 bbox: Bbox,
234 start: Point,
235 end: Point,
236 thick: f64,
237 points: Vec<Point>,
238 radius: f64,
239 box_rad: f64,
240 line_wid: f64,
241 line_ht: f64,
242 shape: Option<usize>,
244 members: HashMap<String, Placed>,
247}
248
249impl Placed {
250 fn corner(&self, c: Corner) -> Point {
251 match self.kind {
252 PKind::Circle | PKind::Ellipse => self.ellipse_corner(c),
253 PKind::Line | PKind::Move | PKind::Spline => self.linear_corner(c),
254 PKind::Arc => self.arc_corner(c),
255 PKind::Box => self.box_corner(c),
256 PKind::Block | PKind::Text => self.bbox_corner(c),
257 }
258 }
259
260 fn bbox_corner(&self, c: Corner) -> Point {
261 let (lo, hi) = (self.bbox.min, self.bbox.max);
262 let mid = self.center;
263 match c {
264 Corner::N => Point::new(mid.x, hi.y),
265 Corner::S => Point::new(mid.x, lo.y),
266 Corner::E => Point::new(hi.x, mid.y),
267 Corner::W => Point::new(lo.x, mid.y),
268 Corner::Ne => Point::new(hi.x, hi.y),
269 Corner::Se => Point::new(hi.x, lo.y),
270 Corner::Nw => Point::new(lo.x, hi.y),
271 Corner::Sw => Point::new(lo.x, lo.y),
272 Corner::Center => mid,
273 Corner::Start => self.start,
274 Corner::End => self.end,
275 }
276 }
277
278 fn ellipse_corner(&self, c: Corner) -> Point {
279 let (rx, ry) = (self.bbox.width() / 2.0, self.bbox.height() / 2.0);
280 let diag = |sx: f64, sy: f64| Point::new(sx * rx * FRAC_1_SQRT_2, sy * ry * FRAC_1_SQRT_2);
281 let off = match c {
282 Corner::N => Point::new(0.0, ry),
283 Corner::S => Point::new(0.0, -ry),
284 Corner::E => Point::new(rx, 0.0),
285 Corner::W => Point::new(-rx, 0.0),
286 Corner::Ne => diag(1.0, 1.0),
287 Corner::Se => diag(1.0, -1.0),
288 Corner::Nw => diag(-1.0, 1.0),
289 Corner::Sw => diag(-1.0, -1.0),
290 Corner::Center => Point::ZERO,
291 Corner::Start => return self.start,
292 Corner::End => return self.end,
293 };
294 self.center + off
295 }
296
297 fn linear_corner(&self, c: Corner) -> Point {
298 if self.points.is_empty() {
299 return self.bbox_corner(c);
300 }
301 match c {
302 Corner::Center => (self.points[0] + *self.points.last().unwrap()) * 0.5,
303 Corner::Start => self.points[0],
304 Corner::End => *self.points.last().unwrap(),
305 _ => {
306 let mut best = self.points[0];
307 for p in self.points.iter().skip(1) {
308 let better = match c {
309 Corner::N => p.y > best.y,
310 Corner::S => p.y < best.y,
311 Corner::E => p.x > best.x,
312 Corner::W => p.x < best.x,
313 Corner::Ne => {
314 (p.y > best.y && p.x >= best.x) || (p.y >= best.y && p.x > best.x)
315 }
316 Corner::Se => {
317 (p.y < best.y && p.x >= best.x) || (p.y <= best.y && p.x > best.x)
318 }
319 Corner::Sw => {
320 (p.y < best.y && p.x <= best.x) || (p.y <= best.y && p.x < best.x)
321 }
322 Corner::Nw => {
323 (p.y > best.y && p.x <= best.x) || (p.y >= best.y && p.x < best.x)
324 }
325 Corner::Center | Corner::Start | Corner::End => false,
326 };
327 if better {
328 best = *p;
329 }
330 }
331 best
332 }
333 }
334 }
335
336 fn arc_corner(&self, c: Corner) -> Point {
337 let diag = self.radius * FRAC_1_SQRT_2;
338 let off = match c {
339 Corner::N => Point::new(0.0, self.radius),
340 Corner::S => Point::new(0.0, -self.radius),
341 Corner::E => Point::new(self.radius, 0.0),
342 Corner::W => Point::new(-self.radius, 0.0),
343 Corner::Ne => Point::new(diag, diag),
344 Corner::Se => Point::new(diag, -diag),
345 Corner::Nw => Point::new(-diag, diag),
346 Corner::Sw => Point::new(-diag, -diag),
347 Corner::Center => Point::ZERO,
348 Corner::Start => return self.start,
349 Corner::End => return self.end,
350 };
351 self.center + off
352 }
353
354 fn box_corner(&self, c: Corner) -> Point {
355 match c {
356 Corner::Ne | Corner::Se | Corner::Nw | Corner::Sw if self.box_rad > 0.0 => {
357 let inset = self
358 .box_rad
359 .min(self.bbox.width().abs().min(self.bbox.height().abs()) / 2.0)
360 * (1.0 - FRAC_1_SQRT_2);
361 let x = self.bbox.width() / 2.0 - inset;
362 let y = self.bbox.height() / 2.0 - inset;
363 let off = match c {
364 Corner::Ne => Point::new(x, y),
365 Corner::Se => Point::new(x, -y),
366 Corner::Nw => Point::new(-x, y),
367 Corner::Sw => Point::new(-x, -y),
368 _ => Point::ZERO,
369 };
370 self.center + off
371 }
372 _ => self.bbox_corner(c),
373 }
374 }
375
376 fn attr_width(&self) -> f64 {
377 match self.kind {
378 PKind::Line | PKind::Move | PKind::Spline => self.line_wid,
379 _ => self.bbox.width(),
380 }
381 }
382
383 fn attr_height(&self) -> f64 {
384 match self.kind {
385 PKind::Line | PKind::Move | PKind::Spline => self.line_ht,
386 _ => self.bbox.height(),
387 }
388 }
389
390 fn attr_radius(&self) -> f64 {
391 match self.kind {
392 PKind::Box => self.box_rad,
393 PKind::Circle => self.bbox.width() / 2.0,
394 PKind::Arc => self.radius,
395 _ => 0.0,
396 }
397 }
398
399 fn attr_diameter(&self) -> f64 {
400 match self.kind {
401 PKind::Circle => self.bbox.width(),
402 PKind::Arc => self.radius * 2.0,
403 _ => 0.0,
404 }
405 }
406
407 fn attr_length(&self) -> f64 {
408 match self.kind {
409 PKind::Line | PKind::Move | PKind::Spline => self.start.dist(self.end),
410 _ => 0.0,
411 }
412 }
413}
414
415struct State {
418 pos: Point,
419 dir: Dir,
420 vars: HashMap<String, f64>,
421 inherited_vars: HashSet<String>,
422 export_vars: HashSet<String>,
423 env: EnvVars,
424 macros: Macros,
425 base_dir: Option<std::path::PathBuf>,
426 outer_labels: HashMap<String, Placed>,
430 shapes: Vec<Shape>,
431 placed: Vec<Placed>,
432 labels: HashMap<String, usize>,
433 bbox: Bbox,
435 layout_bbox: Bbox,
438 anims: Vec<Anim>,
440 diagnostics: Vec<String>,
441 anim_cursor: f64,
442 anim_end: HashMap<usize, f64>,
443 rng: GlibcRand,
444}
445
446const DEFAULT_ANIM_DUR: f64 = 0.6;
447
448fn dir_unit(d: Dir) -> Point {
449 match d {
450 Dir::Right => Point::new(1.0, 0.0),
451 Dir::Left => Point::new(-1.0, 0.0),
452 Dir::Up => Point::new(0.0, 1.0),
453 Dir::Down => Point::new(0.0, -1.0),
454 }
455}
456fn horizontal(d: Dir) -> bool {
457 matches!(d, Dir::Right | Dir::Left)
458}
459
460impl State {
461 fn new() -> Self {
462 let mut vars = HashMap::new();
463 install_dpic_compat_vars(&mut vars);
464 State {
465 pos: Point::ZERO,
466 dir: Dir::Right,
467 vars,
468 inherited_vars: HashSet::new(),
469 export_vars: HashSet::new(),
470 env: EnvVars::new(),
471 macros: HashMap::new(),
472 base_dir: None,
473 outer_labels: HashMap::new(),
474 shapes: Vec::new(),
475 placed: Vec::new(),
476 labels: HashMap::new(),
477 bbox: Bbox::new(),
478 layout_bbox: Bbox::new(),
479 anims: Vec::new(),
480 diagnostics: Vec::new(),
481 anim_cursor: 0.0,
482 anim_end: HashMap::new(),
483 rng: GlibcRand::new(1),
484 }
485 }
486
487 fn eval_stmts(&mut self, stmts: &[Stmt]) -> ER<()> {
488 for s in stmts {
489 self.eval_stmt(s)?;
490 }
491 Ok(())
492 }
493
494 fn parse_body(&mut self, body: &Body) -> ER<Vec<Stmt>> {
496 crate::parser::parse_body_tokens(body, &mut self.macros, self.base_dir.as_deref())
497 .map_err(|e| EvalError { msg: e.to_string() })
498 }
499
500 fn eval_stmt(&mut self, s: &Stmt) -> ER<()> {
501 match s {
502 Stmt::Direction(d) => {
503 self.dir = *d;
504 }
505 Stmt::Assign(list) => {
506 for a in list {
507 self.eval_assignment(a)?;
508 }
509 }
510 Stmt::Place { label, pos } => {
511 let p = self.eval_pos(pos)?;
512 let key = self.label_key(label)?;
513 let mut bb = Bbox::new();
514 bb.add(p);
515 let idx = self.placed.len();
516 self.placed.push(Placed {
517 kind: PKind::Text,
518 center: p,
519 bbox: bb,
520 start: p,
521 end: p,
522 thick: 0.0,
523 points: Vec::new(),
524 radius: 0.0,
525 box_rad: 0.0,
526 line_wid: 0.0,
527 line_ht: 0.0,
528 shape: None,
529 members: HashMap::new(),
530 });
531 self.labels.insert(key, idx);
532 }
533 Stmt::Group(stmts) => {
534 let (pos, dir) = (self.pos, self.dir);
535 self.eval_stmts(stmts)?;
536 self.pos = pos;
537 self.dir = dir;
538 }
539 Stmt::Object { label, object } => {
540 let idx = self.eval_object(object)?;
541 if let Some(l) = label {
542 let key = self.label_key(l)?;
543 self.labels.insert(key, idx);
544 }
545 }
546 Stmt::Animate(a) => self.eval_animate(a)?,
547 Stmt::If {
548 cond,
549 then_body,
550 else_body,
551 } => {
552 if self.eval_expr(cond)? != 0.0 {
555 let stmts = self.parse_body(then_body)?;
556 self.eval_stmts(&stmts)?;
557 } else if let Some(e) = else_body {
558 let stmts = self.parse_body(e)?;
559 self.eval_stmts(&stmts)?;
560 }
561 }
562 Stmt::For {
563 var,
564 subscript,
565 from,
566 to,
567 by,
568 mult,
569 body,
570 } => {
571 let body = self.parse_body(body)?;
572 let from = self.eval_expr(from)?;
573 let to = self.eval_expr(to)?;
574 let by = self.eval_expr(by)?;
575 let mut v = from;
576 let mut iters = 0u64;
577 const EPS: f64 = 1e-9;
578 loop {
579 let cont = if *mult {
580 if by >= 1.0 {
581 v <= to + EPS
582 } else {
583 v >= to - EPS
584 }
585 } else if by >= 0.0 {
586 v <= to + EPS
587 } else {
588 v >= to - EPS
589 };
590 if !cont {
591 break;
592 }
593 let key = self.indexed_name(var, subscript.as_ref())?;
594 self.vars.insert(key, v);
595 self.eval_stmts(&body)?;
596 let prev = v;
597 v = if *mult { v * by } else { v + by };
598 if (v - prev).abs() < f64::EPSILON {
599 break; }
601 iters += 1;
602 if iters > 1_000_000 {
603 return err("for loop exceeded 1,000,000 iterations");
604 }
605 }
606 }
607 Stmt::Print(item) => match item {
608 PrintItem::Expr(e) => {
609 let v = self.eval_expr(e)?;
610 self.diagnostics.push(fmt_num(v));
611 }
612 PrintItem::Str(se) => {
613 let s = self.eval_stringexpr(se)?;
614 self.diagnostics.push(s);
615 }
616 },
617 Stmt::Exec { command, arg_frame } => {
618 let src = unescape_exec_source(&self.eval_stringexpr(command)?);
619 let stmts = crate::parser::parse_exec_source(
620 &src,
621 &self.macros,
622 self.base_dir.as_deref(),
623 arg_frame.as_deref(),
624 )
625 .map_err(|e| EvalError { msg: e.to_string() })?;
626 self.eval_stmts(&stmts)?;
627 }
628 Stmt::Reset(list) => {
629 if list.is_empty() {
630 self.env = EnvVars::new();
631 } else {
632 let d = EnvVars::new();
633 for e in list {
634 self.env.set(*e, d.get(*e));
635 }
636 }
637 }
638 }
639 Ok(())
640 }
641
642 fn eval_animate(&mut self, a: &Animate) -> ER<()> {
643 let idx = self.place_index(&a.target)?;
644 let shape = self.placed[idx].shape.ok_or_else(|| EvalError {
645 msg: "cannot animate a point (no drawn shape)".into(),
646 })?;
647 let dur = match &a.duration {
648 Some(e) => self.eval_expr(e)?,
649 None => DEFAULT_ANIM_DUR,
650 };
651 let mut start = match &a.timing {
652 Timing::Sequential => self.anim_cursor,
653 Timing::At(e) => self.eval_expr(e)?,
654 Timing::After(p) => {
655 let i = self.place_index(p)?;
656 let sh = self.placed[i].shape.ok_or_else(|| EvalError {
657 msg: "`after` target has no animation".into(),
658 })?;
659 *self.anim_end.get(&sh).unwrap_or(&0.0)
660 }
661 };
662 if let Some(d) = &a.delay {
663 start += self.eval_expr(d)?;
664 }
665 let end = start + dur;
666 self.anim_cursor = end;
667 self.anim_end.insert(shape, end);
668 self.anims.push(Anim {
669 shape,
670 effect: stringexpr_lit(&a.effect),
671 start,
672 duration: dur,
673 });
674 Ok(())
675 }
676
677 fn scale_value(&self) -> ER<f64> {
678 let scale = self.env.get(EnvVar::Scale);
679 if scale.abs() < 1e-12 {
680 return err("scale must be non-zero");
681 }
682 Ok(scale)
683 }
684
685 fn to_internal_dim(&self, v: f64) -> ER<f64> {
687 Ok(v / self.scale_value()?)
688 }
689
690 fn env_dim(&self, e: EnvVar) -> ER<f64> {
691 self.to_internal_dim(self.env.get(e))
692 }
693
694 fn canvas_margin(&self) -> ER<CanvasMargin> {
695 let all = self.env_dim(EnvVar::Margin)?;
696 Ok(CanvasMargin {
697 top: all + self.env_dim(EnvVar::Topmargin)?,
698 right: all + self.env_dim(EnvVar::Rightmargin)?,
699 bottom: all + self.env_dim(EnvVar::Bottommargin)?,
700 left: all + self.env_dim(EnvVar::Leftmargin)?,
701 })
702 }
703
704 fn expr_dim(&mut self, e: &Expr) -> ER<f64> {
705 let v = self.eval_expr(e)?;
706 self.to_internal_dim(v)
707 }
708
709 fn to_user_dim(&self, v: f64) -> f64 {
711 v * self.env.get(EnvVar::Scale)
712 }
713
714 fn eval_assignment(&mut self, a: &Assignment) -> ER<()> {
715 let rhs = self.eval_expr(&a.value)?;
716 match &a.target {
717 AssignTarget::Var(name, subscript) => {
718 let key = self.indexed_name(name, subscript.as_ref())?;
719 let cur = match self.vars.get(&key).copied() {
720 Some(v) => v,
721 None if matches!(a.op, AssignOp::Set) => 0.0,
722 None => return err(format!("variable not found `{key}`")),
723 };
724 let val = apply_op(a.op, cur, rhs)?;
725 if !matches!(a.op, AssignOp::Set) && self.inherited_vars.contains(&key) {
726 self.export_vars.insert(key.clone());
727 }
728 self.vars.insert(key, val);
729 }
730 AssignTarget::Env(e) => {
731 let cur = self.env.get(*e);
732 let val = apply_op(a.op, cur, rhs)?;
733 if matches!(e, EnvVar::Scale) {
734 if val.abs() < 1e-12 {
735 return err("scale must be non-zero");
736 }
737 if cur.abs() >= 1e-12 {
738 self.scale_existing_geometry(cur / val);
739 }
740 let ratio = if cur != 0.0 { val / cur } else { val };
745 for sv in SCALED_VARS {
746 let v = self.env.get(sv);
747 self.env.set(sv, v * ratio);
748 }
749 }
750 self.env.set(*e, val);
751 }
752 }
753 Ok(())
754 }
755
756 fn scale_existing_geometry(&mut self, factor: f64) {
757 if (factor - 1.0).abs() < 1e-12 {
758 return;
759 }
760 self.pos = self.pos * factor;
761 for sh in &mut self.shapes {
762 scale_shape(sh, factor);
763 }
764 for pl in &mut self.placed {
765 scale_placed(pl, factor);
766 }
767 scale_bbox_in_place(&mut self.bbox, factor);
768 scale_bbox_in_place(&mut self.layout_bbox, factor);
769 }
770
771 fn eval_object(&mut self, obj: &Object) -> ER<usize> {
774 match &obj.kind {
775 ObjectKind::Primitive(p) => match p {
776 Prim::Box | Prim::Circle | Prim::Ellipse => self.closed(*p, obj),
777 Prim::Line | Prim::Arrow | Prim::Move | Prim::Spline => self.open(*p, obj),
778 Prim::Arc => self.arc(obj),
779 },
780 ObjectKind::Text => self.text_obj(obj),
781 ObjectKind::Block(stmts) => self.block(stmts, obj),
782 ObjectKind::Empty => self.block(&[], obj),
783 ObjectKind::Continue => self.continue_obj(obj),
784 }
785 }
786
787 fn continue_obj(&mut self, obj: &Object) -> ER<usize> {
790 let idx = self
791 .shapes
792 .iter()
793 .rposition(|s| matches!(s, Shape::Path { .. } | Shape::Spline { .. }))
794 .ok_or_else(|| EvalError {
795 msg: "`continue` has no previous line to extend".into(),
796 })?;
797 let start = match &self.shapes[idx] {
798 Shape::Path { pts, .. } | Shape::Spline { pts, .. } => *pts.last().unwrap(),
799 _ => unreachable!(),
800 };
801 let deflen_h = self.env_dim(EnvVar::Linewid)?;
802 let deflen_v = self.env_dim(EnvVar::Lineht)?;
803
804 let mut pts = vec![start];
805 let mut pend = Point::ZERO;
806 let mut any = false;
807 let mut last_dir = self.dir;
808 for a in &obj.attrs {
809 match a {
810 Attr::Direction(d, opt) => {
811 let dist = match opt {
812 Some(e) => self.expr_dim(e)?,
813 None => {
814 if horizontal(*d) {
815 deflen_h
816 } else {
817 deflen_v
818 }
819 }
820 };
821 pend = pend + dir_unit(*d) * dist;
822 last_dir = *d;
823 any = true;
824 }
825 Attr::Then => {
826 let np = *pts.last().unwrap() + pend;
827 pts.push(np);
828 pend = Point::ZERO;
829 }
830 Attr::To(pos) => {
831 if pend != Point::ZERO {
832 let np = *pts.last().unwrap() + pend;
833 pts.push(np);
834 pend = Point::ZERO;
835 }
836 pts.push(self.eval_pos(pos)?);
837 any = true;
838 }
839 Attr::By(pos) => {
840 let dp = self.eval_pos(pos)?;
841 pend = pend + (dp - Point::ZERO);
842 any = true;
843 }
844 Attr::Dist(e) => {
845 let dist = self.expr_dim(e)?;
846 pend = pend + dir_unit(last_dir) * dist;
847 any = true;
848 }
849 _ => {}
850 }
851 }
852 if pend != Point::ZERO {
853 let np = *pts.last().unwrap() + pend;
854 pts.push(np);
855 }
856 if pts.len() == 1 && !any {
857 let dist = if horizontal(self.dir) {
858 deflen_h
859 } else {
860 deflen_v
861 };
862 pts.push(start + dir_unit(self.dir) * dist);
863 last_dir = self.dir;
864 }
865
866 let new: Vec<Point> = pts[1..].to_vec();
867 let visible = match &mut self.shapes[idx] {
868 Shape::Path { pts: p, style, .. } | Shape::Spline { pts: p, style, .. } => {
869 p.extend_from_slice(&new);
870 (!style.invis, stroke_half_width(style))
871 }
872 _ => unreachable!(),
873 };
874 let end = *pts.last().unwrap();
875 let mut bb = Bbox::new();
876 for q in &new {
877 self.layout_bbox.add(*q);
878 bb.add(*q);
879 }
880 if visible.0 {
881 self.bbox.union(&painted_bbox(&bb, visible.1));
882 }
883 self.pos = end;
884 self.dir = last_dir;
885
886 let pidx = self.placed.iter().position(|pl| pl.shape == Some(idx));
887 if let Some(pi) = pidx {
888 self.placed[pi].end = end;
889 self.placed[pi].bbox.add(end);
890 self.placed[pi].center = (self.placed[pi].start + end) * 0.5;
891 self.placed[pi].points.extend_from_slice(&new);
892 }
893 Ok(pidx.unwrap_or(idx))
894 }
895
896 fn closed(&mut self, p: Prim, obj: &Object) -> ER<usize> {
897 let style = self.style_of(obj)?;
898 let text = self.text_of(obj)?;
899 let dir = self.dir_of(obj);
900 let scale = self.scale_of(obj)?;
901
902 let prev = if obj.attrs.iter().any(|a| matches!(a, Attr::Same)) {
905 self.last_dims_of(p)
906 } else {
907 None
908 };
909 let (mut w, mut h, mut rad);
910 match p {
911 Prim::Circle => {
912 let def_r = prev
913 .map(|(pw, _)| pw / 2.0)
914 .unwrap_or(self.env_dim(EnvVar::Circlerad)?);
915 let r = self.dim(obj, DimKind::Rad)?.unwrap_or(def_r);
916 let r = self.dim(obj, DimKind::Diam)?.map(|d| d / 2.0).unwrap_or(r);
917 w = 2.0 * r;
918 h = 2.0 * r;
919 rad = r;
920 }
921 Prim::Ellipse => {
922 let (dw, dh) = prev.unwrap_or((
923 self.env_dim(EnvVar::Ellipsewid)?,
924 self.env_dim(EnvVar::Ellipseht)?,
925 ));
926 w = self.dim(obj, DimKind::Wid)?.unwrap_or(dw);
927 h = self.dim(obj, DimKind::Ht)?.unwrap_or(dh);
928 rad = 0.0;
929 }
930 _ => {
931 let (dw, dh) =
932 prev.unwrap_or((self.env_dim(EnvVar::Boxwid)?, self.env_dim(EnvVar::Boxht)?));
933 w = self.dim(obj, DimKind::Wid)?.unwrap_or(dw);
934 h = self.dim(obj, DimKind::Ht)?.unwrap_or(dh);
935 rad = self
936 .dim(obj, DimKind::Rad)?
937 .unwrap_or(self.env_dim(EnvVar::Boxrad)?);
938 }
939 }
940 w *= scale;
941 h *= scale;
942 rad *= scale;
943
944 let extent = if horizontal(dir) { w } else { h };
945 let center = self.place_closed_center(p, obj, dir, extent, (w, h), rad)?;
946
947 let mut bb = Bbox::new();
948 bb.add(center - Point::new(w / 2.0, h / 2.0));
949 bb.add(center + Point::new(w / 2.0, h / 2.0));
950 let layout_bb = if matches!(p, Prim::Box) {
951 dpic_box_layout_bbox(center, w, h)
952 } else {
953 bb
954 };
955 self.layout_bbox.union(&layout_bb);
956 if closed_shape_is_visible(&style) {
957 self.bbox
958 .union(&painted_bbox(&bb, stroke_half_width(&style)));
959 }
960 self.union_text(center, &text);
961
962 let shape = match p {
963 Prim::Circle => Shape::Circle {
964 c: center,
965 r: rad,
966 style,
967 text,
968 },
969 Prim::Ellipse => Shape::Ellipse {
970 c: center,
971 w,
972 h,
973 style,
974 text,
975 },
976 _ => Shape::Box {
977 c: center,
978 w,
979 h,
980 rad,
981 style,
982 text,
983 },
984 };
985 self.shapes.push(shape);
986
987 let half = dir_unit(dir) * (extent / 2.0);
988 let start = center - half;
989 let end = center + half;
990 self.pos = end;
991 self.dir = dir;
992 let kind = match p {
993 Prim::Circle => PKind::Circle,
994 Prim::Ellipse => PKind::Ellipse,
995 _ => PKind::Box,
996 };
997 let sh = self.shapes.len() - 1;
998 let idx = self.record(kind, center, bb, start, end, 0.0, Some(sh));
999 if matches!(kind, PKind::Box) {
1000 self.placed[idx].box_rad = rad;
1001 }
1002 Ok(idx)
1003 }
1004
1005 fn open(&mut self, p: Prim, obj: &Object) -> ER<usize> {
1006 let mut style = self.style_of(obj)?;
1007 let text = self.text_of(obj)?;
1008 let line_wid = style.arrow_wid;
1009 let line_ht = style.arrow_ht;
1010 let is_move = matches!(p, Prim::Move);
1011 if is_move {
1012 style.invis = true;
1013 style.invis_bounds = true;
1014 }
1015 let (deflen_h, deflen_v) = if is_move {
1016 (
1017 self.env_dim(EnvVar::Movewid)?,
1018 self.env_dim(EnvVar::Moveht)?,
1019 )
1020 } else {
1021 (
1022 self.env_dim(EnvVar::Linewid)?,
1023 self.env_dim(EnvVar::Lineht)?,
1024 )
1025 };
1026 let same_vec = if obj.attrs.iter().any(|a| matches!(a, Attr::Same)) {
1027 self.last_open_vector(p)
1028 } else {
1029 None
1030 };
1031
1032 let start = self.find_from(obj)?.unwrap_or(self.pos);
1034 let mut pts = vec![start];
1035 let mut pend = Point::ZERO;
1036 let mut any = false;
1037 let mut last_dir = self.dir;
1038
1039 for a in &obj.attrs {
1040 match a {
1041 Attr::Direction(d, opt) => {
1042 let dist = match opt {
1043 Some(e) => self.expr_dim(e)?,
1044 None => {
1045 if horizontal(*d) {
1046 deflen_h
1047 } else {
1048 deflen_v
1049 }
1050 }
1051 };
1052 pend = pend + dir_unit(*d) * dist;
1053 last_dir = *d;
1054 any = true;
1055 }
1056 Attr::Then => {
1057 let np = *pts.last().unwrap() + pend;
1058 pts.push(np);
1059 pend = Point::ZERO;
1060 }
1061 Attr::To(pos) => {
1062 if pend != Point::ZERO {
1063 let np = *pts.last().unwrap() + pend;
1064 pts.push(np);
1065 pend = Point::ZERO;
1066 }
1067 pts.push(self.eval_pos(pos)?);
1068 any = true;
1069 }
1070 Attr::By(pos) => {
1071 let d = self.eval_pos(pos)?;
1072 pend = pend + (d - Point::ZERO);
1073 any = true;
1074 }
1075 Attr::Dist(e) => {
1076 let dist = self.expr_dim(e)?;
1077 pend = pend + dir_unit(last_dir) * dist;
1078 any = true;
1079 }
1080 _ => {}
1081 }
1082 }
1083 if pend != Point::ZERO {
1084 let np = *pts.last().unwrap() + pend;
1085 pts.push(np);
1086 }
1087 if pts.len() == 1 && !any {
1088 if let Some(v) = same_vec.filter(|v| v.len() > 1e-12) {
1089 pts.push(start + v);
1090 last_dir = nearest_dir(v);
1091 } else {
1092 let dist = if horizontal(self.dir) {
1094 deflen_h
1095 } else {
1096 deflen_v
1097 };
1098 pts.push(start + dir_unit(self.dir) * dist);
1099 last_dir = self.dir;
1100 }
1101 }
1102
1103 if let Some((start_chop, end_chop)) = self.chop_of(obj)?
1106 && pts.len() >= 2
1107 {
1108 let n = pts.len();
1109 if start_chop != 0.0 {
1110 let d0 = pts[1] - pts[0];
1111 let l0 = d0.len();
1112 if start_chop < 0.0 || l0 > start_chop {
1113 pts[0] = pts[0] + d0 / l0 * start_chop;
1114 }
1115 }
1116 if end_chop != 0.0 {
1117 let d1 = pts[n - 2] - pts[n - 1];
1118 let l1 = d1.len();
1119 if end_chop < 0.0 || l1 > end_chop {
1120 pts[n - 1] = pts[n - 1] + d1 / l1 * end_chop;
1121 }
1122 }
1123 }
1124
1125 let arrows = self.arrows_of(obj, matches!(p, Prim::Arrow));
1127
1128 let mut bb = Bbox::new();
1129 for pt in &pts {
1130 bb.add(*pt);
1131 }
1132 self.layout_bbox.union(&bb);
1133 if !style.invis {
1134 self.bbox
1135 .union(&painted_bbox(&bb, stroke_half_width(&style)));
1136 } else if style.invis_bounds {
1137 self.bbox.union(&bb);
1138 }
1139
1140 let end = *pts.last().unwrap();
1141 let center = (pts[0] + end) * 0.5;
1142 self.union_text(center, &text);
1143 let kind = match p {
1144 Prim::Spline => PKind::Spline,
1145 Prim::Move => PKind::Move,
1146 _ => PKind::Line,
1147 };
1148 let tension = if matches!(p, Prim::Spline) {
1149 let mut t = None;
1150 for a in &obj.attrs {
1151 if let Attr::SplineTension(e) = a {
1152 t = Some(self.eval_expr(e)?);
1153 }
1154 }
1155 t
1156 } else {
1157 None
1158 };
1159 let shape = if matches!(p, Prim::Spline) {
1160 Shape::Spline {
1161 pts: pts.clone(),
1162 tension,
1163 arrows,
1164 style,
1165 text,
1166 }
1167 } else {
1168 Shape::Path {
1169 pts: pts.clone(),
1170 arrows,
1171 style,
1172 text,
1173 }
1174 };
1175 self.shapes.push(shape);
1176
1177 self.pos = end;
1178 self.dir = last_dir;
1179 let sh = self.shapes.len() - 1;
1180 let idx = self.record(kind, center, bb, pts[0], end, 0.0, Some(sh));
1181 self.placed[idx].points = pts;
1182 self.placed[idx].line_wid = line_wid;
1183 self.placed[idx].line_ht = line_ht;
1184 Ok(idx)
1185 }
1186
1187 fn arc(&mut self, obj: &Object) -> ER<usize> {
1188 let mut style = self.style_of(obj)?;
1189 if let Some(wid) = self.dim(obj, DimKind::Wid)? {
1190 style.arrow_wid = wid;
1191 }
1192 if let Some(ht) = self.dim(obj, DimKind::Ht)? {
1193 style.arrow_ht = ht;
1194 }
1195 let text = self.text_of(obj)?;
1196 let start = self.find_from(obj)?.unwrap_or(self.pos);
1197 let cw = self.arc_cw_of(obj);
1198 let arc_dir = self.dir_of(obj);
1199 let rad_attr = self.dim(obj, DimKind::Rad)?;
1200 let to = self.dest_of(obj)?;
1201 let explicit_center = if to.is_some() {
1202 self.arc_explicit_center(obj)?
1203 } else {
1204 None
1205 };
1206
1207 let (center, r, a0, a1) = if let Some(end) = to {
1209 let chord = end - start;
1211 let clen = chord.len();
1212 if clen < 1e-9 {
1213 return err("degenerate arc: `from` and `to` coincide");
1214 }
1215 let r = rad_attr
1216 .unwrap_or(self.env_dim(EnvVar::Arcrad)?)
1217 .max(clen / 2.0);
1218 let dx = chord.x;
1219 let dy = chord.y;
1220 let ts = dx * dx + dy * dy;
1221 let mut t = ((4.0 * r * r - ts).max(0.0) / ts).sqrt();
1222 let arc_sign = if cw { -1.0 } else { 1.0 };
1223 match arc_dir {
1226 Dir::Up => {
1227 if arc_sign * ((-dx) - (t * dy)) < 0.0 {
1228 t = -t;
1229 }
1230 }
1231 Dir::Down => {
1232 if arc_sign * ((-dx) - (t * dy)) > 0.0 {
1233 t = -t;
1234 }
1235 }
1236 Dir::Right => {
1237 if arc_sign * (dy - (t * dx)) < 0.0 {
1238 t = -t;
1239 }
1240 }
1241 Dir::Left => {
1242 if arc_sign * (dy - (t * dx)) > 0.0 {
1243 t = -t;
1244 }
1245 }
1246 }
1247 let center = start + Point::new(0.5 * (dx + t * dy), 0.5 * (dy - t * dx));
1248 let (center, r) = if let Some(center) = explicit_center {
1249 (center, end.dist(center))
1250 } else {
1251 (center, r)
1252 };
1253 let (a0, a1) = arc_angles(center, start, end, cw);
1254 (center, r, a0, a1)
1255 } else {
1256 let r = rad_attr.unwrap_or(self.env_dim(EnvVar::Arcrad)?);
1258 let din = dir_unit(self.dir);
1259 let normal = if cw {
1260 Point::new(din.y, -din.x)
1261 } else {
1262 Point::new(-din.y, din.x)
1263 };
1264 let center = start + normal * r;
1265 let a0 = (start - center).y.atan2((start - center).x);
1266 let sweep = if cw { -PI / 2.0 } else { PI / 2.0 };
1267 (center, r, a0, a0 + sweep)
1268 };
1269
1270 let at = |t: f64| center + Point::new(t.cos(), t.sin()) * r;
1271 let end = at(a1);
1272 let arrows = self.arrows_of(obj, false);
1273
1274 let mut bb = Bbox::new();
1275 bb.add(start);
1276 bb.add(end);
1277 for k in 0..=12 {
1278 bb.add(at(a0 + (a1 - a0) * (k as f64 / 12.0)));
1279 }
1280 self.layout_bbox.union(&bb);
1281 if !style.invis {
1282 self.bbox
1283 .union(&painted_bbox(&bb, stroke_half_width(&style)));
1284 }
1285 self.union_text(center, &text);
1286
1287 self.shapes.push(Shape::Arc {
1288 c: center,
1289 r,
1290 a0,
1291 a1,
1292 cw,
1293 arrows,
1294 style,
1295 text,
1296 });
1297
1298 let tang = if a1 >= a0 {
1300 Point::new(-a1.sin(), a1.cos())
1301 } else {
1302 Point::new(a1.sin(), -a1.cos())
1303 };
1304 self.dir = nearest_dir(tang);
1305 self.pos = end;
1306 let sh = self.shapes.len() - 1;
1307 let idx = self.record(PKind::Arc, center, bb, start, end, 0.0, Some(sh));
1308 self.placed[idx].radius = r;
1309 Ok(idx)
1310 }
1311
1312 fn arc_cw_of(&self, obj: &Object) -> bool {
1313 obj.attrs
1314 .iter()
1315 .rev()
1316 .find_map(|a| match a {
1317 Attr::Cw => Some(true),
1318 Attr::Ccw => Some(false),
1319 _ => None,
1320 })
1321 .unwrap_or(false)
1322 }
1323
1324 fn arc_explicit_center(&mut self, obj: &Object) -> ER<Option<Point>> {
1325 for a in &obj.attrs {
1326 match a {
1327 Attr::At(pos) => return Ok(Some(self.eval_pos(pos)?)),
1328 Attr::With {
1329 anchor: WithAnchor::Plain | WithAnchor::Corner(Corner::Center),
1330 at,
1331 } => {
1332 return Ok(Some(self.eval_pos(at)?));
1333 }
1334 _ => {}
1335 }
1336 }
1337 Ok(None)
1338 }
1339
1340 fn text_obj(&mut self, obj: &Object) -> ER<usize> {
1341 let text = self.text_of(obj)?;
1342 let dir = self.dir_of(obj);
1343 let w = self
1344 .dim(obj, DimKind::Wid)?
1345 .unwrap_or(self.env_dim(EnvVar::Textwid)?);
1346 let h = match self.dim(obj, DimKind::Ht)? {
1347 Some(h) => h,
1348 None => self.env_dim(EnvVar::Textht)? * text.len().max(1) as f64,
1349 };
1350 let extent = if horizontal(dir) { w } else { h };
1351 let at = self.place_center(obj, dir, extent, w, h)?;
1352 let mut bb = Bbox::new();
1353 bb.add(at - Point::new(w / 2.0, h / 2.0));
1354 bb.add(at + Point::new(w / 2.0, h / 2.0));
1355 self.layout_bbox.union(&bb);
1356 let text_bb = text_object_bbox(at, &text, w, h);
1357 self.bbox.union(&text_bb);
1358 self.shapes.push(Shape::Text {
1359 at,
1360 text,
1361 bbox: text_bb,
1362 w,
1363 h,
1364 standalone: true,
1365 });
1366 let half = dir_unit(dir) * (extent / 2.0);
1367 let start = at - half;
1368 let end = at + half;
1369 self.pos = end;
1370 self.dir = dir;
1371 let sh = self.shapes.len() - 1;
1372 Ok(self.record(PKind::Text, at, bb, start, end, 0.0, Some(sh)))
1373 }
1374
1375 fn union_text(&mut self, center: Point, lines: &[TextLine]) {
1378 let bb = text_bbox(center, lines);
1379 self.bbox.union(&bb);
1380 }
1381
1382 fn block(&mut self, stmts: &[Stmt], obj: &Object) -> ER<usize> {
1383 let block_text = self.text_of(obj)?;
1384 let mut sub = State::new();
1388 sub.env = self.env.clone();
1389 sub.vars = self.vars.clone();
1390 sub.inherited_vars = self.vars.keys().cloned().collect();
1391 sub.export_vars.clear();
1392 sub.macros = self.macros.clone();
1393 sub.base_dir = self.base_dir.clone();
1394 sub.rng = self.rng.clone();
1395 sub.outer_labels = self.outer_labels.clone();
1397 for (name, &i) in &self.labels {
1398 sub.outer_labels
1399 .insert(name.clone(), self.placed[i].clone());
1400 }
1401 sub.eval_stmts(stmts)?;
1402 self.rng = sub.rng.clone();
1405 self.diagnostics.append(&mut sub.diagnostics);
1406 for key in &sub.export_vars {
1407 if let Some(val) = sub.vars.get(key).copied() {
1408 self.vars.insert(key.clone(), val);
1409 if self.inherited_vars.contains(key) {
1410 self.export_vars.insert(key.clone());
1411 }
1412 }
1413 }
1414
1415 let layout_sub_bb = if sub.layout_bbox.is_empty() {
1416 let mut b = Bbox::new();
1417 b.add(Point::ZERO);
1418 b
1419 } else {
1420 sub.layout_bbox
1421 };
1422 let local_center = (layout_sub_bb.min + layout_sub_bb.max) * 0.5;
1423 let w = layout_sub_bb.width();
1424 let h = layout_sub_bb.height();
1425
1426 let dir = self.dir_of(obj);
1427 let extent = if horizontal(dir) { w } else { h };
1428 let target = self.block_center(obj, dir, extent, w, h, local_center, &mut sub)?;
1429 let shift = target - local_center;
1430
1431 let first_shape = self.shapes.len();
1432 let mut members: HashMap<String, Placed> = HashMap::new();
1435 for (name, &i) in &sub.labels {
1436 let mut pl = sub.placed[i].clone();
1437 rebase_placed(&mut pl, shift, first_shape);
1438 members.insert(name.clone(), pl);
1439 }
1440 for mut sh in sub.shapes.into_iter() {
1441 translate_shape(&mut sh, shift);
1442 self.shapes.push(sh);
1443 }
1444 let shape = if self.shapes.len() > first_shape {
1445 Some(first_shape)
1446 } else {
1447 None
1448 };
1449 let mut bb = Bbox::new();
1450 bb.add(layout_sub_bb.min + shift);
1451 bb.add(layout_sub_bb.max + shift);
1452 self.layout_bbox.union(&bb);
1453 if !sub.bbox.is_empty() {
1454 let mut visible_bb = Bbox::new();
1455 visible_bb.add(sub.bbox.min + shift);
1456 visible_bb.add(sub.bbox.max + shift);
1457 self.bbox.union(&visible_bb);
1458 }
1459 let block_text_bb = text_bbox(target, &block_text);
1460 self.bbox.union(&block_text_bb);
1461 if has_visible_text(&block_text) {
1462 self.shapes.push(Shape::Text {
1463 at: target,
1464 text: block_text,
1465 bbox: block_text_bb,
1466 w: 0.0,
1467 h: 0.0,
1468 standalone: false,
1469 });
1470 }
1471
1472 let half = dir_unit(dir) * (extent / 2.0);
1473 let start = target - half;
1474 let end = target + half;
1475 self.pos = end;
1476 self.dir = dir;
1477 let idx = self.record(PKind::Block, target, bb, start, end, 0.0, shape);
1478 self.placed[idx].members = members;
1479 Ok(idx)
1480 }
1481
1482 #[allow(clippy::too_many_arguments)]
1483 fn record(
1484 &mut self,
1485 kind: PKind,
1486 center: Point,
1487 bbox: Bbox,
1488 start: Point,
1489 end: Point,
1490 thick: f64,
1491 shape: Option<usize>,
1492 ) -> usize {
1493 let idx = self.placed.len();
1494 self.placed.push(Placed {
1495 kind,
1496 center,
1497 bbox,
1498 start,
1499 end,
1500 thick,
1501 points: Vec::new(),
1502 radius: 0.0,
1503 box_rad: 0.0,
1504 line_wid: 0.0,
1505 line_ht: 0.0,
1506 shape,
1507 members: HashMap::new(),
1508 });
1509 idx
1510 }
1511
1512 fn dim(&mut self, obj: &Object, kind: DimKind) -> ER<Option<f64>> {
1515 for a in &obj.attrs {
1516 if let Attr::Dim(k, e) = a
1517 && *k == kind
1518 {
1519 return Ok(Some(match kind {
1520 DimKind::Thick | DimKind::Scaled => self.eval_expr(e)?,
1521 DimKind::Ht | DimKind::Wid | DimKind::Rad | DimKind::Diam => {
1522 self.expr_dim(e)?
1523 }
1524 }));
1525 }
1526 }
1527 Ok(None)
1528 }
1529
1530 fn scale_of(&mut self, obj: &Object) -> ER<f64> {
1531 Ok(self.dim(obj, DimKind::Scaled)?.unwrap_or(1.0))
1532 }
1533
1534 fn dir_of(&self, obj: &Object) -> Dir {
1535 obj.attrs
1536 .iter()
1537 .rev()
1538 .find_map(|a| match a {
1539 Attr::Direction(d, _) => Some(*d),
1540 _ => None,
1541 })
1542 .unwrap_or(self.dir)
1543 }
1544
1545 fn find_from(&mut self, obj: &Object) -> ER<Option<Point>> {
1546 for a in &obj.attrs {
1547 if let Attr::From(pos) = a {
1548 return Ok(Some(self.eval_pos(pos)?));
1549 }
1550 }
1551 Ok(None)
1552 }
1553
1554 fn at_of(&mut self, obj: &Object) -> ER<Option<Point>> {
1555 for a in &obj.attrs {
1556 if let Attr::At(pos) = a {
1557 return Ok(Some(self.eval_pos(pos)?));
1558 }
1559 }
1560 Ok(None)
1561 }
1562
1563 fn dest_of(&mut self, obj: &Object) -> ER<Option<Point>> {
1564 for a in &obj.attrs {
1565 if let Attr::To(pos) = a {
1566 return Ok(Some(self.eval_pos(pos)?));
1567 }
1568 }
1569 Ok(None)
1570 }
1571
1572 fn chop_of(&mut self, obj: &Object) -> ER<Option<(f64, f64)>> {
1575 let mut vals = Vec::new();
1576 for a in &obj.attrs {
1577 if let Attr::Chop(opt) = a {
1578 let amt = match opt {
1579 Some(e) => self.expr_dim(e)?,
1580 None => self.env_dim(EnvVar::Circlerad)?,
1581 };
1582 vals.push(amt);
1583 }
1584 }
1585 Ok(match vals.as_slice() {
1586 [] => None,
1587 [one] => Some((*one, *one)),
1588 [start, end, ..] => Some((*start, *end)),
1589 })
1590 }
1591
1592 fn last_dims_of(&self, p: Prim) -> Option<(f64, f64)> {
1594 let want = match p {
1595 Prim::Box => PKind::Box,
1596 Prim::Circle => PKind::Circle,
1597 Prim::Ellipse => PKind::Ellipse,
1598 _ => return None,
1599 };
1600 self.placed
1601 .iter()
1602 .rev()
1603 .find(|pl| pl.kind == want)
1604 .map(|pl| (pl.bbox.width(), pl.bbox.height()))
1605 }
1606
1607 fn last_open_vector(&self, p: Prim) -> Option<Point> {
1609 let want = match p {
1610 Prim::Move => PKind::Move,
1611 Prim::Spline => PKind::Spline,
1612 Prim::Line | Prim::Arrow => PKind::Line,
1613 _ => return None,
1614 };
1615 self.placed
1616 .iter()
1617 .rev()
1618 .find(|pl| pl.kind == want)
1619 .map(|pl| pl.end - pl.start)
1620 }
1621
1622 fn place_center(&mut self, obj: &Object, dir: Dir, extent: f64, w: f64, h: f64) -> ER<Point> {
1624 self.place_with_corner_offset(obj, dir, extent, w, h, corner_offset)
1625 }
1626
1627 fn place_closed_center(
1628 &mut self,
1629 p: Prim,
1630 obj: &Object,
1631 dir: Dir,
1632 extent: f64,
1633 dims: (f64, f64),
1634 rad: f64,
1635 ) -> ER<Point> {
1636 let (w, h) = dims;
1637 self.place_with_corner_offset(obj, dir, extent, w, h, |c, w, h| {
1638 closed_corner_offset(p, c, w, h, rad)
1639 })
1640 }
1641
1642 fn place_with_corner_offset(
1643 &mut self,
1644 obj: &Object,
1645 dir: Dir,
1646 extent: f64,
1647 w: f64,
1648 h: f64,
1649 corner: impl Fn(Corner, f64, f64) -> Point,
1650 ) -> ER<Point> {
1651 if let Some(at) = self.at_of(obj)? {
1652 return Ok(at);
1653 }
1654 for a in &obj.attrs {
1655 if let Attr::With { anchor, at } = a {
1656 let ap = self.eval_pos(at)?;
1657 let off = match anchor {
1658 WithAnchor::Corner(c) => corner(*c, w, h),
1659 WithAnchor::Pair(x, y) => Point::new(self.expr_dim(x)?, self.expr_dim(y)?),
1660 WithAnchor::Place(_) => {
1661 return err("`with .label` anchors are only valid on blocks");
1662 }
1663 WithAnchor::Plain => Point::ZERO,
1664 };
1665 return Ok(ap - off);
1666 }
1667 }
1668 Ok(self.pos + dir_unit(dir) * (extent / 2.0))
1669 }
1670
1671 #[allow(clippy::too_many_arguments)]
1672 fn block_center(
1673 &mut self,
1674 obj: &Object,
1675 dir: Dir,
1676 extent: f64,
1677 w: f64,
1678 h: f64,
1679 local_center: Point,
1680 sub: &mut State,
1681 ) -> ER<Point> {
1682 if let Some(at) = self.at_of(obj)? {
1683 return Ok(at);
1684 }
1685 for a in &obj.attrs {
1686 if let Attr::With { anchor, at } = a {
1687 let ap = self.eval_pos(at)?;
1688 let off = match anchor {
1689 WithAnchor::Corner(c) => corner_offset(*c, w, h),
1690 WithAnchor::Pair(x, y) => {
1691 Point::new(self.expr_dim(x)?, self.expr_dim(y)?) - local_center
1692 }
1693 WithAnchor::Place(place) => sub.place_point(place)? - local_center,
1694 WithAnchor::Plain => Point::ZERO,
1695 };
1696 return Ok(ap - off);
1697 }
1698 }
1699 Ok(self.pos + dir_unit(dir) * (extent / 2.0))
1700 }
1701
1702 fn arrows_of(&self, obj: &Object, default_end: bool) -> Arrowheads {
1703 let mut found = None;
1704 for a in &obj.attrs {
1705 if let Attr::Arrowhead(h, _) = a {
1706 found = Some(match h {
1707 token::Arrow::Left => Arrowheads::Start,
1708 token::Arrow::Right => Arrowheads::End,
1709 token::Arrow::Double => Arrowheads::Both,
1710 });
1711 }
1712 }
1713 found.unwrap_or(if default_end {
1714 Arrowheads::End
1715 } else {
1716 Arrowheads::None
1717 })
1718 }
1719
1720 fn style_of(&mut self, obj: &Object) -> ER<Style> {
1721 let mut s = Style {
1723 arrow_ht: self.env_dim(EnvVar::Arrowht)?,
1724 arrow_wid: self.env_dim(EnvVar::Arrowwid)?,
1725 arrow_filled: self.env.get(EnvVar::Arrowhead).round() as i64 != 0,
1728 ..Default::default()
1729 };
1730 let lt = self.env.get(EnvVar::Linethick);
1731 if lt > 0.0 {
1732 s.thick = Some(lt);
1733 }
1734 for a in &obj.attrs {
1735 match a {
1736 Attr::LineStyle(lt, opt) => match lt {
1737 LineType::Solid => s.dash = Dash::Solid,
1738 LineType::Dashed => {
1739 let w = match opt {
1740 Some(e) => self.expr_dim(e)?,
1741 None => self.env_dim(EnvVar::Dashwid)?,
1742 };
1743 s.dash = Dash::Dashed(w);
1744 }
1745 LineType::Dotted => {
1746 s.dash = Dash::Dotted(match opt {
1747 Some(e) => Some(self.expr_dim(e)?),
1748 None => None,
1749 });
1750 }
1751 LineType::Invis => s.invis = true,
1752 },
1753 Attr::Fill(opt) => {
1754 let g = match opt {
1755 Some(e) => self.eval_expr(e)?,
1756 None => self.env.get(EnvVar::Fillval),
1757 };
1758 s.fill = Some(Fill::Gray(g));
1759 s.fill_open = true;
1760 }
1761 Attr::Color(kind, se) => {
1762 let name = self.eval_color_expr(se)?;
1763 match kind {
1764 token::Color::Outlined => s.stroke = Some(name),
1765 token::Color::Colored => {
1766 s.stroke = Some(name.clone());
1767 s.fill = Some(Fill::Color(name));
1768 }
1769 token::Color::Shaded => {
1770 s.fill = Some(Fill::Color(name));
1771 s.fill_open = true;
1772 }
1773 }
1774 }
1775 Attr::Dim(DimKind::Thick, e) => s.thick = Some(self.eval_expr(e)?),
1776 Attr::Arrowhead(_, Some(e)) => {
1777 s.arrow_filled = self.eval_expr(e)?.round() as i64 != 0;
1778 }
1779 _ => {}
1780 }
1781 }
1782 Ok(s)
1783 }
1784
1785 fn eval_color_expr(&mut self, se: &StringExpr) -> ER<String> {
1786 if let StringExpr::Lit(name) = se
1787 && let Some(body) = self.macros.get(name).cloned()
1788 {
1789 if let Some(lit) = single_token_macro_string(&body) {
1790 return Ok(lit);
1791 }
1792 let parsed = crate::parser::parse_stringexpr_tokens(
1793 &body,
1794 &mut self.macros,
1795 self.base_dir.as_deref(),
1796 )
1797 .map_err(|e| EvalError { msg: e.to_string() })?;
1798 return self.eval_stringexpr(&parsed);
1799 }
1800 self.eval_stringexpr(se)
1801 }
1802
1803 fn text_of(&mut self, obj: &Object) -> ER<Vec<TextLine>> {
1804 let mut lines: Vec<TextLine> = Vec::new();
1805 let mut pending_halign = 0i8;
1806 let mut pending_valign = 0i8;
1807 for a in &obj.attrs {
1808 match a {
1809 Attr::TextPos(tp) => {
1810 if let Some(line) = lines.last_mut() {
1811 apply_text_pos(&mut line.halign, &mut line.valign, *tp);
1812 } else {
1813 apply_text_pos(&mut pending_halign, &mut pending_valign, *tp);
1814 }
1815 }
1816 Attr::Text(se) => {
1817 let s = self.eval_stringexpr(se)?;
1818 lines.push(TextLine {
1819 s,
1820 halign: pending_halign,
1821 valign: pending_valign,
1822 text_offset: self.env_dim(EnvVar::Textoffset)?,
1823 });
1824 pending_halign = 0;
1825 pending_valign = 0;
1826 }
1827 _ => {}
1828 }
1829 }
1830 Ok(lines)
1831 }
1832
1833 fn eval_pos(&mut self, pos: &Position) -> ER<Point> {
1836 match pos {
1837 Position::Pair(x, y) => Ok(Point::new(self.expr_dim(x)?, self.expr_dim(y)?)),
1838 Position::Place(loc) => self.eval_loc(loc),
1839 Position::Between { frac, a, b, .. } => {
1840 let f = self.eval_expr(frac)?;
1841 let pa = self.eval_pos(a)?;
1842 let pb = self.eval_pos(b)?;
1843 Ok(pa.lerp(pb, f))
1844 }
1845 Position::Sum(sign, a, b) => {
1846 let pa = self.eval_pos(a)?;
1847 let pb = self.eval_pos(b)?;
1848 Ok(match sign {
1849 Sign::Plus => pa + pb,
1850 Sign::Minus => pa - pb,
1851 })
1852 }
1853 Position::Scale(p, s, div) => {
1854 let pp = self.eval_pos(p)?;
1855 let sv = self.eval_expr(s)?;
1856 if *div {
1857 if sv == 0.0 {
1858 return err("division by zero in position");
1859 }
1860 Ok(pp / sv)
1861 } else {
1862 Ok(pp * sv)
1863 }
1864 }
1865 }
1866 }
1867
1868 fn eval_loc(&mut self, loc: &Location) -> ER<Point> {
1869 match loc {
1870 Location::Place(p) => self.place_point(p),
1871 Location::Paren(pos) => self.eval_pos(pos),
1872 Location::ParenPair(p1, p2) => {
1873 Ok(Point::new(self.eval_pos(p1)?.x, self.eval_pos(p2)?.y))
1874 }
1875 }
1876 }
1877
1878 fn place_point(&mut self, p: &Place) -> ER<Point> {
1879 match p {
1880 Place::Here => Ok(self.pos),
1881 Place::Name { name, subscript } => {
1882 let key = self.indexed_name(name, subscript.as_deref())?;
1883 Ok(self.resolve_label(&key)?.center)
1884 }
1885 Place::Nth { count, obj } => {
1886 let idx = self.nth_index(count, obj)?;
1887 Ok(self.placed[idx].center)
1888 }
1889 Place::Corner(inner, c) => {
1890 let pl = self.resolve_obj(inner)?;
1891 Ok(pl.corner(*c))
1892 }
1893 Place::CornerOf(c, inner) => {
1894 let pl = self.resolve_obj(inner)?;
1895 Ok(pl.corner(*c))
1896 }
1897 Place::Member(..) => Ok(self.resolve_obj(p)?.center),
1898 }
1899 }
1900
1901 fn resolve_obj(&mut self, p: &Place) -> ER<Placed> {
1904 match p {
1905 Place::Name { name, subscript } => {
1906 let key = self.indexed_name(name, subscript.as_deref())?;
1907 self.resolve_label(&key)
1908 }
1909 Place::Nth { count, obj } => {
1910 let idx = self.nth_index(count, obj)?;
1911 Ok(self.placed[idx].clone())
1912 }
1913 Place::Corner(inner, _) | Place::CornerOf(_, inner) => self.resolve_obj(inner),
1914 Place::Member(base, sub) => {
1915 let b = self.resolve_obj(base)?;
1916 let key = match sub.as_ref() {
1917 Place::Name { name, subscript } => {
1918 self.indexed_name(name, subscript.as_deref())?
1919 }
1920 _ => return err("a block sub-label must be a name"),
1921 };
1922 b.members.get(&key).cloned().ok_or_else(|| EvalError {
1923 msg: format!("no sub-label `{key}` in that block"),
1924 })
1925 }
1926 Place::Here => err("`Here` is a point, not an object"),
1927 }
1928 }
1929
1930 fn place_index(&mut self, p: &Place) -> ER<usize> {
1931 match p {
1932 Place::Name { name, subscript } => {
1933 let key = self.indexed_name(name, subscript.as_deref())?;
1934 self.label_index(&key)
1935 }
1936 Place::Nth { count, obj } => self.nth_index(count, obj),
1937 Place::Corner(inner, _) | Place::CornerOf(_, inner) => self.place_index(inner),
1938 Place::Here => err("`Here` is a point, not an object"),
1939 Place::Member(_, _) => err("block sub-labels (B.A) are not supported yet"),
1940 }
1941 }
1942
1943 fn label_index(&self, name: &str) -> ER<usize> {
1944 self.labels.get(name).copied().ok_or_else(|| EvalError {
1945 msg: format!("unknown label `{name}`"),
1946 })
1947 }
1948
1949 fn resolve_label(&self, key: &str) -> ER<Placed> {
1952 if let Some(&idx) = self.labels.get(key) {
1953 Ok(self.placed[idx].clone())
1954 } else if let Some(pl) = self.outer_labels.get(key) {
1955 Ok(pl.clone())
1956 } else {
1957 err(format!("unknown label `{key}`"))
1958 }
1959 }
1960
1961 fn label_key(&mut self, label: &Label) -> ER<String> {
1962 self.indexed_name(&label.name, label.subscript.as_ref())
1963 }
1964
1965 fn indexed_name(&mut self, name: &str, subscript: Option<&Expr>) -> ER<String> {
1966 match subscript {
1967 Some(Expr::Index(items)) => {
1968 let mut parts = Vec::with_capacity(items.len());
1969 for e in items {
1970 parts.push(fmt_num(self.eval_expr(e)?));
1971 }
1972 Ok(format!("{name}[{}]", parts.join(",")))
1973 }
1974 Some(e) => Ok(format!("{name}[{}]", fmt_num(self.eval_expr(e)?))),
1975 None => Ok(name.to_string()),
1976 }
1977 }
1978
1979 fn nth_index(&mut self, count: &Nth, obj: &PrimObj) -> ER<usize> {
1980 let want = primobj_kind(obj);
1983 let matches: Vec<usize> = self
1984 .placed
1985 .iter()
1986 .enumerate()
1987 .filter(|(_, pl)| want.is_none_or(|w| pl.kind == w))
1988 .map(|(i, _)| i)
1989 .collect();
1990 if matches.is_empty() {
1991 return match want {
1992 Some(w) => err(format!("no {:?} object to reference", want_name(w))),
1993 None => err("no object to reference".to_string()),
1994 };
1995 }
1996 let idx = match count {
1997 Nth::Last => *matches.last().unwrap(),
1998 Nth::Count(e, from_last) => {
1999 let n = self.eval_expr(e)?.round() as i64;
2000 if n < 1 {
2001 return err("ordinal must be >= 1");
2002 }
2003 let k = (n - 1) as usize;
2004 if *from_last {
2005 if k >= matches.len() {
2006 return err("ordinal out of range");
2007 }
2008 matches[matches.len() - 1 - k]
2009 } else {
2010 if k >= matches.len() {
2011 return err("ordinal out of range");
2012 }
2013 matches[k]
2014 }
2015 }
2016 };
2017 Ok(idx)
2018 }
2019
2020 fn eval_stringexpr(&mut self, se: &StringExpr) -> ER<String> {
2023 Ok(match se {
2024 StringExpr::Lit(s) => s.clone(),
2025 StringExpr::Concat(a, b) => {
2026 format!("{}{}", self.eval_stringexpr(a)?, self.eval_stringexpr(b)?)
2027 }
2028 StringExpr::Arg(n) => format!("${n}"), StringExpr::Sprintf(fmt, args) => {
2030 let f = self.eval_stringexpr(fmt)?;
2031 let mut vals = Vec::with_capacity(args.len());
2032 for e in args {
2033 vals.push(self.eval_printf_arg(e)?);
2034 }
2035 sprintf_fmt(&f, &vals)
2036 }
2037 StringExpr::SvgFont(_) => String::new(),
2038 })
2039 }
2040
2041 fn eval_printf_arg(&mut self, e: &Expr) -> ER<PrintfArg> {
2042 match e {
2043 Expr::Str(se) => Ok(PrintfArg::Str(self.eval_stringexpr(se)?)),
2044 _ => Ok(PrintfArg::Num(self.eval_expr(e)?)),
2045 }
2046 }
2047
2048 fn expr_str(&mut self, e: &Expr) -> ER<String> {
2053 match e {
2054 Expr::Str(se) => self.eval_stringexpr(se),
2055 _ => Ok(fmt_num(self.eval_expr(e)?)),
2056 }
2057 }
2058
2059 fn eval_expr(&mut self, e: &Expr) -> ER<f64> {
2060 Ok(match e {
2061 Expr::Num(v) => *v,
2062 Expr::Str(_) => return err("a string is only valid as an `==`/`!=` operand"),
2063 Expr::Index(_) => return err("a comma subscript is only valid inside `name[...]`"),
2064 Expr::Var(name, subscript) => {
2065 let key = self.indexed_name(name, subscript.as_deref())?;
2066 self.vars.get(&key).copied().ok_or_else(|| EvalError {
2067 msg: format!("variable not found `{key}`"),
2068 })?
2069 }
2070 Expr::Env(v) => self.env.get(*v),
2071 Expr::Unary(op, a) => {
2072 let x = self.eval_expr(a)?;
2073 match op {
2074 UnOp::Neg => -x,
2075 UnOp::Pos => x,
2076 UnOp::Not => bool_f(x == 0.0),
2077 }
2078 }
2079 Expr::Bin(op, a, b) => {
2080 if matches!(op, BinOp::Eq | BinOp::Ne)
2082 && (matches!(a.as_ref(), Expr::Str(_)) || matches!(b.as_ref(), Expr::Str(_)))
2083 {
2084 let sa = self.expr_str(a)?;
2085 let sb = self.expr_str(b)?;
2086 return Ok(bool_f(if matches!(op, BinOp::Eq) {
2087 sa == sb
2088 } else {
2089 sa != sb
2090 }));
2091 }
2092 let x = self.eval_expr(a)?;
2093 let y = self.eval_expr(b)?;
2094 match op {
2095 BinOp::Add => x + y,
2096 BinOp::Sub => x - y,
2097 BinOp::Mul => x * y,
2098 BinOp::Div => {
2099 if y == 0.0 {
2100 return err("division by zero");
2101 }
2102 x / y
2103 }
2104 BinOp::Mod => dpic_mod(x, y, "modulo by zero")?,
2105 BinOp::Pow => dpic_pow(x, y)?,
2106 BinOp::Eq => bool_f(x == y),
2107 BinOp::Ne => bool_f(x != y),
2108 BinOp::Lt => bool_f(x < y),
2109 BinOp::Le => bool_f(x <= y),
2110 BinOp::Gt => bool_f(x > y),
2111 BinOp::Ge => bool_f(x >= y),
2112 BinOp::And => bool_f(x != 0.0 && y != 0.0),
2113 BinOp::Or => bool_f(x != 0.0 || y != 0.0),
2114 }
2115 }
2116 Expr::Func1(f, a) => {
2117 let x = self.eval_expr(a)?;
2118 match f {
2119 Func1::Abs => x.abs(),
2120 Func1::Acos => x.acos(),
2121 Func1::Asin => x.asin(),
2122 Func1::Cos => x.cos(),
2123 Func1::Exp => 10f64.powf(x),
2124 Func1::Expe => x.exp(),
2125 Func1::Int => x.trunc(),
2126 Func1::Log => x.log10(),
2127 Func1::Loge => x.ln(),
2128 Func1::Sign => {
2129 if x >= 0.0 {
2130 1.0
2131 } else {
2132 -1.0
2133 }
2134 }
2135 Func1::Sin => x.sin(),
2136 Func1::Sqrt => x.sqrt(),
2137 Func1::Tan => x.tan(),
2138 Func1::Floor => x.floor(),
2139 }
2140 }
2141 Expr::Func2(f, a, b) => {
2142 let x = self.eval_expr(a)?;
2143 let y = self.eval_expr(b)?;
2144 match f {
2145 Func2::Atan2 => x.atan2(y),
2146 Func2::Max => x.max(y),
2147 Func2::Min => x.min(y),
2148 Func2::Pmod => x.rem_euclid(y),
2149 }
2150 }
2151 Expr::Rand(seed) => {
2152 let seed = match seed {
2153 Some(e) => Some(self.eval_expr(e)?),
2154 None => None,
2155 };
2156 self.rand(seed)
2157 }
2158 Expr::Assign(name, subscript, v) => {
2159 let val = self.eval_expr(v)?;
2160 let key = self.indexed_name(name, subscript.as_deref())?;
2161 self.vars.insert(key, val);
2162 val
2163 }
2164 Expr::DotX(loc) => {
2165 let x = self.eval_loc(loc)?.x;
2166 self.to_user_dim(x)
2167 }
2168 Expr::DotY(loc) => {
2169 let y = self.eval_loc(loc)?.y;
2170 self.to_user_dim(y)
2171 }
2172 Expr::PlaceAttr(place, param) => {
2173 let pl = self.resolve_obj(place)?;
2174 match param {
2175 token::Param::Width => self.to_user_dim(pl.attr_width()),
2176 token::Param::Height => self.to_user_dim(pl.attr_height()),
2177 token::Param::Radius => self.to_user_dim(pl.attr_radius()),
2178 token::Param::Diameter => self.to_user_dim(pl.attr_diameter()),
2179 token::Param::Length => self.to_user_dim(pl.attr_length()),
2180 token::Param::Thickness => pl.thick,
2181 }
2182 }
2183 })
2184 }
2185
2186 fn rand(&mut self, seed: Option<f64>) -> f64 {
2187 if let Some(seed) = seed {
2188 self.rng.seed(seed.trunc() as i64);
2189 }
2190 self.rng.next_f64()
2191 }
2192}
2193
2194fn apply_op(op: AssignOp, cur: f64, rhs: f64) -> ER<f64> {
2197 Ok(match op {
2198 AssignOp::Set | AssignOp::ColonSet => rhs,
2199 AssignOp::Add => cur + rhs,
2200 AssignOp::Sub => cur - rhs,
2201 AssignOp::Mul => cur * rhs,
2202 AssignOp::Div => {
2203 if rhs == 0.0 {
2204 return err("division by zero in assignment");
2205 }
2206 cur / rhs
2207 }
2208 AssignOp::Rem => dpic_mod(cur, rhs, "modulo by zero in assignment")?,
2209 })
2210}
2211
2212#[derive(Clone)]
2213struct GlibcRand {
2214 state: [u32; 31],
2215 f: usize,
2216 r: usize,
2217}
2218
2219impl GlibcRand {
2220 fn new(seed: i64) -> Self {
2221 let mut rng = GlibcRand {
2222 state: [0; 31],
2223 f: 3,
2224 r: 0,
2225 };
2226 rng.seed(seed);
2227 rng
2228 }
2229
2230 fn seed(&mut self, seed: i64) {
2231 let seed = if seed == 0 { 1 } else { seed };
2232 let mut x = seed.rem_euclid(2_147_483_647) as u32;
2233 if x == 0 {
2234 x = 1;
2235 }
2236 self.state[0] = x;
2237 for i in 1..31 {
2238 let prev = self.state[i - 1] as i64;
2239 let hi = prev / 127_773;
2240 let lo = prev % 127_773;
2241 let mut next = 16_807 * lo - 2_836 * hi;
2242 if next < 0 {
2243 next += 2_147_483_647;
2244 }
2245 self.state[i] = next as u32;
2246 }
2247 self.f = 3;
2248 self.r = 0;
2249 for _ in 0..310 {
2250 self.next_i31();
2251 }
2252 }
2253
2254 fn next_i31(&mut self) -> u32 {
2255 let x = self.state[self.f].wrapping_add(self.state[self.r]);
2256 self.state[self.f] = x;
2257 let out = (x >> 1) & 0x7fff_ffff;
2258 self.f = (self.f + 1) % 31;
2259 self.r = (self.r + 1) % 31;
2260 out
2261 }
2262
2263 fn next_f64(&mut self) -> f64 {
2264 self.next_i31() as f64 / 2_147_483_647.0
2265 }
2266}
2267
2268fn bool_f(b: bool) -> f64 {
2269 if b { 1.0 } else { 0.0 }
2270}
2271
2272fn dpic_round(x: f64) -> i64 {
2273 if x < 0.0 {
2274 -((-x + 0.5).floor() as i64)
2275 } else {
2276 (x + 0.5).floor() as i64
2277 }
2278}
2279
2280fn dpic_mod(x: f64, y: f64, zero_msg: &'static str) -> ER<f64> {
2281 let i = dpic_round(x);
2282 let j = dpic_round(y);
2283 if j == 0 {
2284 return err(zero_msg);
2285 }
2286 Ok((i - (i / j) * j) as f64)
2287}
2288
2289fn dpic_pow(x: f64, y: f64) -> ER<f64> {
2290 if x == 0.0 && y < 0.0 {
2291 return err("zero cannot be raised to a negative power");
2292 }
2293 let iy = dpic_round(y);
2294 if iy as f64 == y {
2295 return Ok(dpic_int_pow(x, iy));
2296 }
2297 if x < 0.0 {
2298 return err("negative base with non-integer exponent");
2299 }
2300 Ok(x.powf(y))
2301}
2302
2303fn dpic_int_pow(x: f64, y: i64) -> f64 {
2304 if y == 0 {
2305 return 1.0;
2306 }
2307 if x == 0.0 || y == 1 {
2308 return x;
2309 }
2310 if y < 0 {
2311 return dpic_int_pow(1.0 / x, -y);
2312 }
2313 if y == 2 {
2314 return x * x;
2315 }
2316 if y & 1 == 1 {
2317 x * dpic_int_pow(x, y - 1)
2318 } else {
2319 let half = dpic_int_pow(x, y >> 1);
2320 half * half
2321 }
2322}
2323
2324fn apply_text_pos(halign: &mut i8, valign: &mut i8, pos: token::TextPos) {
2325 match pos {
2326 token::TextPos::Ljust => *halign = -1,
2327 token::TextPos::Rjust => *halign = 1,
2328 token::TextPos::Center => {
2329 *halign = 0;
2330 *valign = 0;
2331 }
2332 token::TextPos::Above => *valign = 1,
2333 token::TextPos::Below => *valign = -1,
2334 }
2335}
2336
2337fn has_visible_text(lines: &[TextLine]) -> bool {
2338 lines.iter().any(|line| !line.s.is_empty())
2339}
2340
2341fn closed_shape_is_visible(style: &Style) -> bool {
2342 !style.invis || style.fill.is_some()
2343}
2344
2345fn stroke_half_width(style: &Style) -> f64 {
2346 if style.invis {
2347 0.0
2348 } else {
2349 style.thick.unwrap_or(0.8) / 144.0
2350 }
2351}
2352
2353fn painted_bbox(bb: &Bbox, pad: f64) -> Bbox {
2354 if bb.is_empty() || pad <= 0.0 {
2355 return *bb;
2356 }
2357 let mut out = Bbox::new();
2358 out.add(bb.min - Point::new(pad, pad));
2359 out.add(bb.max + Point::new(pad, pad));
2360 out
2361}
2362
2363fn dpic_box_layout_bbox(center: Point, w: f64, h: f64) -> Bbox {
2364 fn axis(center: f64, extent: f64) -> (f64, f64) {
2365 let lo = center - extent / 2.0;
2366 let hi = center + extent / 2.0;
2367 if lo <= hi { (lo, hi) } else { (0.0, 0.0) }
2368 }
2369
2370 let (x0, x1) = axis(center.x, w);
2371 let (y0, y1) = axis(center.y, h);
2372 let mut bb = Bbox::new();
2373 bb.add(Point::new(x0, y0));
2374 bb.add(Point::new(x1, y1));
2375 bb
2376}
2377
2378fn drawing_painted_bbox(shapes: &[Shape]) -> Bbox {
2379 let mut out = Bbox::new();
2380 for sh in shapes {
2381 out.union(&shape_painted_bbox(sh));
2382 }
2383 out
2384}
2385
2386fn shape_painted_bbox(sh: &Shape) -> Bbox {
2387 let mut out = Bbox::new();
2388 match sh {
2389 Shape::Box {
2390 c,
2391 w,
2392 h,
2393 style,
2394 text,
2395 ..
2396 } => {
2397 let mut bb = Bbox::new();
2398 bb.add(*c - Point::new(*w / 2.0, *h / 2.0));
2399 bb.add(*c + Point::new(*w / 2.0, *h / 2.0));
2400 if closed_shape_is_visible(style) {
2401 out.union(&painted_bbox(&bb, stroke_half_width(style)));
2402 }
2403 out.union(&text_bbox(*c, text));
2404 }
2405 Shape::Circle { c, r, style, text } => {
2406 let mut bb = Bbox::new();
2407 bb.add(*c - Point::new(*r, *r));
2408 bb.add(*c + Point::new(*r, *r));
2409 if closed_shape_is_visible(style) {
2410 out.union(&painted_bbox(&bb, stroke_half_width(style)));
2411 }
2412 out.union(&text_bbox(*c, text));
2413 }
2414 Shape::Ellipse {
2415 c,
2416 w,
2417 h,
2418 style,
2419 text,
2420 } => {
2421 let mut bb = Bbox::new();
2422 bb.add(*c - Point::new(*w / 2.0, *h / 2.0));
2423 bb.add(*c + Point::new(*w / 2.0, *h / 2.0));
2424 if closed_shape_is_visible(style) {
2425 out.union(&painted_bbox(&bb, stroke_half_width(style)));
2426 }
2427 out.union(&text_bbox(*c, text));
2428 }
2429 Shape::Path {
2430 pts, style, text, ..
2431 }
2432 | Shape::Spline {
2433 pts, style, text, ..
2434 } => {
2435 let mut bb = Bbox::new();
2436 for p in pts {
2437 bb.add(*p);
2438 }
2439 if !style.invis {
2440 out.union(&painted_bbox(&bb, stroke_half_width(style)));
2441 } else if style.invis_bounds {
2442 out.union(&bb);
2443 }
2444 if let (Some(first), Some(last)) = (pts.first(), pts.last()) {
2445 out.union(&text_bbox((*first + *last) * 0.5, text));
2446 }
2447 }
2448 Shape::Arc {
2449 c,
2450 r,
2451 a0,
2452 a1,
2453 style,
2454 text,
2455 ..
2456 } => {
2457 let at = |t: f64| *c + Point::new(t.cos(), t.sin()) * *r;
2458 let mut bb = Bbox::new();
2459 for k in 0..=12 {
2460 bb.add(at(*a0 + (*a1 - *a0) * (k as f64 / 12.0)));
2461 }
2462 if !style.invis {
2463 out.union(&painted_bbox(&bb, stroke_half_width(style)));
2464 }
2465 out.union(&text_bbox(*c, text));
2466 }
2467 Shape::Text { bbox, .. } => out.union(bbox),
2468 }
2469 out
2470}
2471
2472fn text_bbox(center: Point, lines: &[TextLine]) -> Bbox {
2473 let mut bb = Bbox::new();
2474 if !has_visible_text(lines) {
2475 return bb;
2476 }
2477 const EM: f64 = 11.0 / 72.0;
2478 let char_w = 0.6 * EM;
2479 let line_h = 1.2 * EM;
2480 let xheight = 0.66 * EM;
2481 let n = lines.len() as f64;
2482 for (i, line) in lines.iter().enumerate() {
2483 if line.s.is_empty() {
2484 continue;
2485 }
2486 let w = line.s.chars().count() as f64 * char_w;
2487 let base_y = center.y - (i as f64 - (n - 1.0) / 2.0) * line_h;
2488 let y = base_y + line.valign as f64 * (xheight / 2.0 + line.text_offset);
2489 let x = center.x
2490 + match line.halign {
2491 -1 => line.text_offset,
2492 1 => -line.text_offset,
2493 _ => 0.0,
2494 };
2495 let (min_x, max_x) = match line.halign {
2496 -1 => (x, x + w),
2497 1 => (x - w, x),
2498 _ => (x - w / 2.0, x + w / 2.0),
2499 };
2500 bb.add(Point::new(min_x, y - line_h / 2.0));
2501 bb.add(Point::new(max_x, y + line_h / 2.0));
2502 }
2503 bb
2504}
2505
2506fn text_object_bbox(center: Point, lines: &[TextLine], w: f64, h: f64) -> Bbox {
2507 let text_bb = text_bbox(center, lines);
2508 if text_bb.is_empty() {
2509 return text_bb;
2510 }
2511 let min_x = if w > 0.0 {
2512 center.x - w / 2.0
2513 } else {
2514 text_bb.min.x
2515 };
2516 let max_x = if w > 0.0 {
2517 center.x + w / 2.0
2518 } else {
2519 text_bb.max.x
2520 };
2521 let y_bb = if h > 0.0 {
2522 text_object_vertical_bbox(center, lines, h)
2523 } else {
2524 text_bb
2525 };
2526 let min_y = y_bb.min.y;
2527 let max_y = y_bb.max.y;
2528 let mut bb = Bbox::new();
2529 bb.add(Point::new(min_x, min_y));
2530 bb.add(Point::new(max_x, max_y));
2531 bb
2532}
2533
2534fn text_object_vertical_bbox(center: Point, lines: &[TextLine], h: f64) -> Bbox {
2535 let mut bb = Bbox::new();
2536 if !has_visible_text(lines) {
2537 return bb;
2538 }
2539 let n = lines.len() as f64;
2540 let v = n - 1.0 + DP_TEXT_RATIO;
2541 let lineskip = if v.abs() > 1e-12 { h / v } else { 11.0 / 72.0 };
2542 let xheight = lineskip * DP_TEXT_RATIO;
2543 let mut baseline_y = center.y + (v * lineskip / 2.0) - xheight;
2544 for line in lines {
2545 if line.s.is_empty() {
2546 baseline_y -= lineskip;
2547 continue;
2548 }
2549 let just_offset = xheight / 2.0 + line.text_offset;
2550 let y = baseline_y + (line.valign as f64) * just_offset;
2551 bb.add(Point::new(center.x, y));
2552 bb.add(Point::new(center.x, y + xheight));
2553 baseline_y -= lineskip;
2554 }
2555 bb
2556}
2557
2558fn fmt_num(v: f64) -> String {
2560 format!("{v}")
2561}
2562
2563fn install_dpic_compat_vars(vars: &mut HashMap<String, f64>) {
2564 let opts = [
2567 ("optMFpic", 0.0),
2568 ("optMpost", 1.0),
2569 ("optPDF", 2.0),
2570 ("optPGF", 3.0),
2571 ("optPict2e", 4.0),
2572 ("optPS", 5.0),
2573 ("optPSfrag", 6.0),
2574 ("optPSTricks", 7.0),
2575 ("optSVG", 8.0),
2576 ("optTeX", 9.0),
2577 ("opttTeX", 10.0),
2578 ("optxfig", 11.0),
2579 ];
2580 for (name, val) in opts {
2581 vars.insert(name.to_string(), val);
2582 }
2583 vars.insert("dpicopt".to_string(), 8.0);
2584}
2585
2586fn corner_offset(c: Corner, w: f64, h: f64) -> Point {
2587 let (hw, hh) = (w / 2.0, h / 2.0);
2588 match c {
2589 Corner::N => Point::new(0.0, hh),
2590 Corner::S => Point::new(0.0, -hh),
2591 Corner::E => Point::new(hw, 0.0),
2592 Corner::W => Point::new(-hw, 0.0),
2593 Corner::Ne => Point::new(hw, hh),
2594 Corner::Se => Point::new(hw, -hh),
2595 Corner::Nw => Point::new(-hw, hh),
2596 Corner::Sw => Point::new(-hw, -hh),
2597 Corner::Center | Corner::Start | Corner::End => Point::ZERO,
2598 }
2599}
2600
2601fn closed_corner_offset(p: Prim, c: Corner, w: f64, h: f64, rad: f64) -> Point {
2602 match p {
2603 Prim::Circle | Prim::Ellipse => ellipse_corner_offset(c, w, h),
2604 Prim::Box => box_corner_offset(c, w, h, rad),
2605 _ => corner_offset(c, w, h),
2606 }
2607}
2608
2609fn arc_angles(center: Point, start: Point, end: Point, cw: bool) -> (f64, f64) {
2610 let a0 = (start - center).y.atan2((start - center).x);
2611 let mut a1 = (end - center).y.atan2((end - center).x);
2612 if cw {
2613 while a1 > a0 {
2614 a1 -= 2.0 * PI;
2615 }
2616 } else {
2617 while a1 < a0 {
2618 a1 += 2.0 * PI;
2619 }
2620 }
2621 (a0, a1)
2622}
2623
2624fn ellipse_corner_offset(c: Corner, w: f64, h: f64) -> Point {
2625 let (rx, ry) = (w / 2.0, h / 2.0);
2626 let diag = |sx: f64, sy: f64| Point::new(sx * rx * FRAC_1_SQRT_2, sy * ry * FRAC_1_SQRT_2);
2627 match c {
2628 Corner::N => Point::new(0.0, ry),
2629 Corner::S => Point::new(0.0, -ry),
2630 Corner::E => Point::new(rx, 0.0),
2631 Corner::W => Point::new(-rx, 0.0),
2632 Corner::Ne => diag(1.0, 1.0),
2633 Corner::Se => diag(1.0, -1.0),
2634 Corner::Nw => diag(-1.0, 1.0),
2635 Corner::Sw => diag(-1.0, -1.0),
2636 Corner::Center | Corner::Start | Corner::End => Point::ZERO,
2637 }
2638}
2639
2640fn box_corner_offset(c: Corner, w: f64, h: f64, rad: f64) -> Point {
2641 if rad > 0.0 && matches!(c, Corner::Ne | Corner::Se | Corner::Nw | Corner::Sw) {
2642 let inset = rad.min(w.abs().min(h.abs()) / 2.0) * (1.0 - FRAC_1_SQRT_2);
2643 let x = w / 2.0 - inset;
2644 let y = h / 2.0 - inset;
2645 return match c {
2646 Corner::Ne => Point::new(x, y),
2647 Corner::Se => Point::new(x, -y),
2648 Corner::Nw => Point::new(-x, y),
2649 Corner::Sw => Point::new(-x, -y),
2650 _ => Point::ZERO,
2651 };
2652 }
2653 corner_offset(c, w, h)
2654}
2655
2656fn primobj_kind(o: &PrimObj) -> Option<PKind> {
2659 Some(match o {
2660 PrimObj::Prim(p) => match p {
2661 Prim::Box => PKind::Box,
2662 Prim::Circle => PKind::Circle,
2663 Prim::Ellipse => PKind::Ellipse,
2664 Prim::Line | Prim::Arrow => PKind::Line,
2665 Prim::Move => PKind::Move,
2666 Prim::Spline => PKind::Spline,
2667 Prim::Arc => PKind::Arc,
2668 },
2669 PrimObj::Block | PrimObj::EmptyBrack => PKind::Block,
2670 PrimObj::Str(_) => PKind::Text,
2671 PrimObj::Any => return None,
2672 })
2673}
2674
2675fn want_name(k: PKind) -> &'static str {
2676 match k {
2677 PKind::Box => "box",
2678 PKind::Circle => "circle",
2679 PKind::Ellipse => "ellipse",
2680 PKind::Line => "line",
2681 PKind::Move => "move",
2682 PKind::Spline => "spline",
2683 PKind::Arc => "arc",
2684 PKind::Block => "block",
2685 PKind::Text => "text",
2686 }
2687}
2688
2689fn nearest_dir(v: Point) -> Dir {
2690 if v.x.abs() >= v.y.abs() {
2691 if v.x >= 0.0 { Dir::Right } else { Dir::Left }
2692 } else if v.y >= 0.0 {
2693 Dir::Up
2694 } else {
2695 Dir::Down
2696 }
2697}
2698
2699enum PrintfArg {
2700 Num(f64),
2701 Str(String),
2702}
2703
2704impl PrintfArg {
2705 fn num(&self) -> f64 {
2706 match self {
2707 PrintfArg::Num(v) => *v,
2708 PrintfArg::Str(s) => s.parse::<f64>().unwrap_or(0.0),
2709 }
2710 }
2711
2712 fn string(&self) -> String {
2713 match self {
2714 PrintfArg::Num(v) => fmt_num(*v),
2715 PrintfArg::Str(s) => s.clone(),
2716 }
2717 }
2718}
2719
2720fn sprintf_fmt(fmt: &str, vals: &[PrintfArg]) -> String {
2723 let mut out = String::new();
2724 let mut chars = fmt.chars().peekable();
2725 let mut ai = 0usize;
2726 while let Some(c) = chars.next() {
2727 if c != '%' {
2728 out.push(c);
2729 continue;
2730 }
2731 let mut spec = String::new();
2732 while let Some(&n) = chars.peek() {
2733 if "+-0123456789. #".contains(n) {
2734 spec.push(n);
2735 chars.next();
2736 } else {
2737 break;
2738 }
2739 }
2740 let conv = match chars.next() {
2741 Some(c) => c,
2742 None => break,
2743 };
2744 if conv == '%' {
2745 out.push('%');
2746 continue;
2747 }
2748 let arg = vals.get(ai);
2749 ai += 1;
2750 let prec = spec.split('.').nth(1).and_then(|p| {
2751 p.chars()
2752 .take_while(|c| c.is_ascii_digit())
2753 .collect::<String>()
2754 .parse::<usize>()
2755 .ok()
2756 });
2757 match conv {
2758 'd' | 'i' => out.push_str(&format!(
2759 "{}",
2760 arg.map(PrintfArg::num).unwrap_or(0.0).round() as i64
2761 )),
2762 'f' | 'F' => out.push_str(&format!(
2763 "{:.*}",
2764 prec.unwrap_or(6),
2765 arg.map(PrintfArg::num).unwrap_or(0.0)
2766 )),
2767 'e' | 'E' => out.push_str(&format!(
2768 "{:.*e}",
2769 prec.unwrap_or(6),
2770 arg.map(PrintfArg::num).unwrap_or(0.0)
2771 )),
2772 'g' | 'G' => out.push_str(&format!("{}", arg.map(PrintfArg::num).unwrap_or(0.0))),
2773 's' => out.push_str(&arg.map(PrintfArg::string).unwrap_or_default()),
2774 other => {
2775 out.push('%');
2776 out.push(other);
2777 }
2778 }
2779 }
2780 out
2781}
2782
2783fn stringexpr_lit(se: &StringExpr) -> String {
2784 match se {
2785 StringExpr::Lit(s) => s.clone(),
2786 StringExpr::Concat(a, b) => format!("{}{}", stringexpr_lit(a), stringexpr_lit(b)),
2787 StringExpr::Arg(n) => format!("${n}"),
2788 StringExpr::Sprintf(fmt, _) => stringexpr_lit(fmt),
2789 StringExpr::SvgFont(_) => String::new(),
2790 }
2791}
2792
2793fn single_token_macro_string(toks: &[crate::lexer::Spanned]) -> Option<String> {
2794 let mut toks = toks
2795 .iter()
2796 .filter(|s| !matches!(s.tok, token::Token::Newline | token::Token::Eof));
2797 let tok = &toks.next()?.tok;
2798 if toks.next().is_some() {
2799 return None;
2800 }
2801 match tok {
2802 token::Token::Str(s) | token::Token::Name(s) | token::Token::Label(s) => Some(s.clone()),
2803 _ => None,
2804 }
2805}
2806
2807fn unescape_exec_source(src: &str) -> String {
2808 let mut out = String::with_capacity(src.len());
2809 let mut chars = src.chars();
2810 while let Some(c) = chars.next() {
2811 if c == '\\'
2812 && let Some('"') = chars.clone().next()
2813 {
2814 chars.next();
2815 out.push('"');
2816 } else {
2817 out.push(c);
2818 }
2819 }
2820 out
2821}
2822
2823fn rebase_placed(pl: &mut Placed, d: Point, shape_off: usize) {
2826 pl.center = pl.center + d;
2827 pl.start = pl.start + d;
2828 pl.end = pl.end + d;
2829 for p in &mut pl.points {
2830 *p = *p + d;
2831 }
2832 let mut bb = Bbox::new();
2833 bb.add(pl.bbox.min + d);
2834 bb.add(pl.bbox.max + d);
2835 pl.bbox = bb;
2836 if let Some(s) = pl.shape {
2837 pl.shape = Some(s + shape_off);
2838 }
2839 for m in pl.members.values_mut() {
2840 rebase_placed(m, d, shape_off);
2841 }
2842}
2843
2844fn scale_placed(pl: &mut Placed, f: f64) {
2845 pl.center = pl.center * f;
2846 pl.start = pl.start * f;
2847 pl.end = pl.end * f;
2848 for p in &mut pl.points {
2849 *p = *p * f;
2850 }
2851 pl.radius *= f;
2852 pl.box_rad *= f;
2853 pl.line_wid *= f;
2854 pl.line_ht *= f;
2855 scale_bbox_in_place(&mut pl.bbox, f);
2856 for m in pl.members.values_mut() {
2857 scale_placed(m, f);
2858 }
2859}
2860
2861fn translate_shape(sh: &mut Shape, d: Point) {
2862 let mv = |p: &mut Point| *p = *p + d;
2863 match sh {
2864 Shape::Box { c, .. } | Shape::Circle { c, .. } | Shape::Ellipse { c, .. } => mv(c),
2865 Shape::Path { pts, .. } | Shape::Spline { pts, .. } => {
2866 for p in pts {
2867 mv(p);
2868 }
2869 }
2870 Shape::Arc { c, .. } => mv(c),
2871 Shape::Text { at, bbox, .. } => {
2872 mv(at);
2873 bbox.min = bbox.min + d;
2874 bbox.max = bbox.max + d;
2875 }
2876 }
2877}
2878
2879fn scale_bbox_in_place(bb: &mut Bbox, f: f64) {
2880 if bb.is_empty() {
2881 return;
2882 }
2883 let mut b = Bbox::new();
2884 b.add(bb.min * f);
2885 b.add(bb.max * f);
2886 *bb = b;
2887}
2888
2889fn scale_style(style: &mut Style, f: f64) {
2890 let f = f.abs();
2891 style.arrow_ht *= f;
2892 style.arrow_wid *= f;
2893 match &mut style.dash {
2894 Dash::Dashed(w) => *w *= f,
2895 Dash::Dotted(Some(w)) => *w *= f,
2896 Dash::Solid | Dash::Dotted(None) => {}
2897 }
2898}
2899
2900fn scale_shape(sh: &mut Shape, f: f64) {
2902 match sh {
2903 Shape::Box {
2904 c,
2905 w,
2906 h,
2907 rad,
2908 style,
2909 ..
2910 } => {
2911 *c = *c * f;
2912 *w *= f;
2913 *h *= f;
2914 *rad *= f;
2915 scale_style(style, f);
2916 }
2917 Shape::Circle { c, r, style, .. } => {
2918 *c = *c * f;
2919 *r *= f;
2920 scale_style(style, f);
2921 }
2922 Shape::Ellipse { c, w, h, style, .. } => {
2923 *c = *c * f;
2924 *w *= f;
2925 *h *= f;
2926 scale_style(style, f);
2927 }
2928 Shape::Path { pts, style, .. } | Shape::Spline { pts, style, .. } => {
2929 for p in pts {
2930 *p = *p * f;
2931 }
2932 scale_style(style, f);
2933 }
2934 Shape::Arc { c, r, style, .. } => {
2935 *c = *c * f;
2936 *r *= f;
2937 scale_style(style, f);
2938 }
2939 Shape::Text { at, bbox, w, h, .. } => {
2940 *at = *at * f;
2941 scale_bbox_in_place(bbox, f);
2942 *w *= f;
2943 *h *= f;
2944 }
2945 }
2946}
2947
2948#[cfg(test)]
2949mod tests {
2950 use super::*;
2951 use crate::parser::{parse, parse_in_dir};
2952
2953 fn draw(src: &str) -> Drawing {
2954 eval(&parse(src).unwrap()).unwrap()
2955 }
2956
2957 fn scalar(src: &str) -> ER<f64> {
2958 let prog = parse(&format!("x = {src}")).unwrap();
2959 let mut st = State::new();
2960 st.eval_stmts(&prog.stmts)?;
2961 Ok(st.vars["x"])
2962 }
2963
2964 fn assert_box_size(shape: &Shape, want_w: f64, want_h: f64) {
2965 let Shape::Box { w, h, .. } = shape else {
2966 panic!()
2967 };
2968 assert!((*w - want_w).abs() < 1e-9, "w = {w}, want {want_w}");
2969 assert!((*h - want_h).abs() < 1e-9, "h = {h}, want {want_h}");
2970 }
2971
2972 const DEFAULT_STROKE_IN: f64 = 0.8 / 72.0;
2973
2974 #[test]
2975 fn pipeline_chains_left_to_right() {
2976 let d = draw(".PS\nellipse \"document\"\narrow\nbox \"PIC\"\narrow\nbox \"TROFF\"\n.PE");
2977 assert_eq!(d.shapes.len(), 5);
2979 if let Shape::Ellipse { c, .. } = &d.shapes[0] {
2981 assert!((c.x - 0.375).abs() < 1e-9, "ellipse x = {}", c.x);
2982 assert!(c.y.abs() < 1e-9);
2983 } else {
2984 panic!("expected ellipse");
2985 }
2986 assert!(d.bbox.width() > 2.0);
2988 assert!((d.bbox.height() - (0.5 + DEFAULT_STROKE_IN)).abs() < 1e-9);
2989 }
2990
2991 #[test]
2992 fn box_at_absolute() {
2993 let d = draw("box ht 0.3 wid 0.5 at 1,2");
2994 let Shape::Box { c, w, h, .. } = &d.shapes[0] else {
2995 panic!()
2996 };
2997 assert_eq!(*c, Point::new(1.0, 2.0));
2998 assert!((*w - 0.5).abs() < 1e-9 && (*h - 0.3).abs() < 1e-9);
2999 }
3000
3001 #[test]
3002 fn diamond_is_closed() {
3003 let d = draw("line up right then down right then down left then up left");
3004 let Shape::Path { pts, .. } = &d.shapes[0] else {
3005 panic!()
3006 };
3007 assert_eq!(pts.len(), 5);
3008 assert!(pts[0].dist(*pts.last().unwrap()) < 1e-9);
3010 }
3011
3012 #[test]
3013 fn corners_and_labels() {
3014 let d = draw("A: box wid 1 ht 1 at 0,0\nbox wid 0.5 ht 0.5 with .sw at A.ne");
3015 let Shape::Box { c, .. } = &d.shapes[1] else {
3017 panic!()
3018 };
3019 assert!(
3020 (c.x - 0.75).abs() < 1e-9 && (c.y - 0.75).abs() < 1e-9,
3021 "c = {c:?}"
3022 );
3023 }
3024
3025 #[test]
3026 fn circle_corner_anchors_are_on_the_circumference() {
3027 let d = draw("C: circle rad 1 at 0,0\nline from C.sw to C.ne");
3028 let Shape::Path { pts, .. } = &d.shapes[1] else {
3029 panic!()
3030 };
3031 let a = 1.0 / 2.0_f64.sqrt();
3032 assert_eq!(pts.len(), 2);
3033 assert!((pts[0].x + a).abs() < 1e-9 && (pts[0].y + a).abs() < 1e-9);
3034 assert!((pts[1].x - a).abs() < 1e-9 && (pts[1].y - a).abs() < 1e-9);
3035 }
3036
3037 #[test]
3038 fn type_specific_corner_anchors_match_dpic() {
3039 let d = draw("L: line from (0,0) to (1,0) then to (1,1)\ncircle rad .01 at L.nw");
3040 let Shape::Circle { c, .. } = &d.shapes[1] else {
3041 panic!()
3042 };
3043 assert!(c.dist(Point::new(0.0, 0.0)) < 1e-9, "line nw = {c:?}");
3044
3045 let d = draw("A: arc cw rad 0.5 from (0,0) to (0.5,0.5)\ncircle rad .01 at A.s");
3046 let Shape::Circle { c, .. } = &d.shapes[1] else {
3047 panic!()
3048 };
3049 assert!(c.dist(Point::new(0.5, -0.5)) < 1e-9, "arc s = {c:?}");
3050
3051 let d = draw("B: box wid 1 ht 1 rad 0.3 at (0,0)\ncircle rad .01 at B.ne");
3052 let Shape::Circle { c, .. } = &d.shapes[1] else {
3053 panic!()
3054 };
3055 let inset = 0.3 * (1.0 - FRAC_1_SQRT_2);
3056 let expected = Point::new(0.5 - inset, 0.5 - inset);
3057 assert!(c.dist(expected) < 1e-9, "rounded box ne = {c:?}");
3058 }
3059
3060 #[test]
3061 fn with_corner_uses_ellipse_geometry_for_placed_object() {
3062 let d = draw("ellipse; ellipse with .nw at last ellipse.se");
3063 let Shape::Ellipse { c: first, .. } = &d.shapes[0] else {
3064 panic!()
3065 };
3066 let Shape::Ellipse { c: second, .. } = &d.shapes[1] else {
3067 panic!()
3068 };
3069 let expected = *first + Point::new(0.75 * FRAC_1_SQRT_2, -0.5 * FRAC_1_SQRT_2);
3070 assert!(
3071 second.dist(expected) < 1e-9,
3072 "second center = {second:?}, expected = {expected:?}"
3073 );
3074 }
3075
3076 #[test]
3077 fn for_loop_repeats() {
3078 let d = draw("for i = 1 to 3 do { box }");
3079 assert_eq!(d.shapes.len(), 3);
3080 }
3081
3082 #[test]
3083 fn for_loop_can_assign_subscripted_counter() {
3084 let d = draw("i = 1\nfor A[i] = 1 to 3 do { i += 1 }\nbox wid A[1] + A[2] + A[3] ht 0.3");
3085 let Shape::Box { w, .. } = &d.shapes[0] else {
3086 panic!()
3087 };
3088 assert!((*w - 6.0).abs() < 1e-9, "w = {w}");
3089 }
3090
3091 #[test]
3092 fn if_else_branches() {
3093 let d1 = draw("x = 1\nif x > 0 then { box } else { circle }");
3094 assert!(matches!(d1.shapes[0], Shape::Box { .. }));
3095 let d2 = draw("x = 0\nif x > 0 then { box } else { circle }");
3096 assert!(matches!(d2.shapes[0], Shape::Circle { .. }));
3097 }
3098
3099 #[test]
3100 fn define_macro_with_args() {
3101 let d = draw("define elem { box wid $1 }\nelem(0.5)\nelem(1.25)");
3102 assert_eq!(d.shapes.len(), 2);
3103 let Shape::Box { w: w0, .. } = &d.shapes[0] else {
3104 panic!()
3105 };
3106 let Shape::Box { w: w1, .. } = &d.shapes[1] else {
3107 panic!()
3108 };
3109 assert!((*w0 - 0.5).abs() < 1e-9 && (*w1 - 1.25).abs() < 1e-9);
3110 }
3111
3112 #[test]
3113 fn define_accepts_arbitrary_delimiter() {
3114 let d = draw("define elem / box /\nelem");
3115 assert_eq!(d.shapes.len(), 1);
3116 assert!(matches!(d.shapes[0], Shape::Box { .. }));
3117 }
3118
3119 #[test]
3120 fn labelled_call_to_multiline_body_macro() {
3121 let d = draw(
3125 "define elem {\n [\n box wid 0.4 ht 0.2\n L: last box.w\n R: last box.e\n ]\n}\nQ: elem() with .L at (1,1)\n\"x\" at Q.R",
3126 );
3127 assert!(d.shapes.iter().any(|s| matches!(s, Shape::Box { .. })));
3129 let Shape::Text { at, .. } = d.shapes.last().unwrap() else {
3130 panic!()
3131 };
3132 assert!(
3134 (at.x - 1.4).abs() < 1e-6 && (at.y - 1.0).abs() < 1e-6,
3135 "at = {at:?}"
3136 );
3137 }
3138
3139 #[test]
3140 fn copied_forward_macro_expands_in_deferred_multiline_call() {
3141 let dir = std::env::temp_dir().join(format!("rpic_forward_macro_{}", std::process::id()));
3142 std::fs::create_dir_all(&dir).unwrap();
3143 std::fs::write(
3144 dir.join("lib.pic"),
3145 "define outer { if gate then { inner(0.2,\n 0.3) } }\ndefine inner { box wid $1 ht $2 }\n",
3146 )
3147 .unwrap();
3148
3149 let pic = parse_in_dir(
3150 "if 1 then { copy \"lib.pic\" }\ngate = 1\nouter()",
3151 Some(dir.as_path()),
3152 )
3153 .unwrap();
3154 let d = eval(&pic).unwrap();
3155 let _ = std::fs::remove_dir_all(&dir);
3156
3157 let Shape::Box { w, h, .. } = &d.shapes[0] else {
3158 panic!()
3159 };
3160 assert!((*w - 0.2).abs() < 1e-9 && (*h - 0.3).abs() < 1e-9);
3161 }
3162
3163 #[test]
3164 fn deferred_body_uses_macro_frame_from_own_expansion() {
3165 let d = draw(
3166 "define draw_one { [\n scalev = 2\n define project { $1/scalev }\n if use_it then { box wid project(4) ht 0.1 }\n] }\ndefine draw_two { define project { $1/future_scale } }\nuse_it = 1\ndraw_one()\ndraw_two()",
3167 );
3168 let Shape::Box { w, .. } = &d.shapes[0] else {
3169 panic!()
3170 };
3171 assert!((*w - 2.0).abs() < 1e-9, "w = {w}");
3172 }
3173
3174 #[test]
3175 fn subscripted_variables_store_by_index() {
3176 let d = draw("P[1] = 0.4\nP[2] = 0.9\nP[2] += 0.1\nbox wid P[2] ht P[1]");
3177 let Shape::Box { w, h, .. } = &d.shapes[0] else {
3178 panic!()
3179 };
3180 assert!((*w - 1.0).abs() < 1e-9 && (*h - 0.4).abs() < 1e-9);
3181 }
3182
3183 #[test]
3184 fn multidimensional_variables_store_by_index_tuple() {
3185 let d = draw("M[1,2] = 0.7\nj = 2\nM[1,j] += 0.2\nbox wid M[1,2] ht 0.3");
3186 let Shape::Box { w, .. } = &d.shapes[0] else {
3187 panic!()
3188 };
3189 assert!((*w - 0.9).abs() < 1e-9, "w = {w}");
3190 }
3191
3192 #[test]
3193 fn subscripted_label_places_resolve_by_index() {
3194 let d =
3195 draw("for i = 1 to 2 do { A[i]: circle rad 0.01 at (i,0) }\nline from A[1] to A[2]");
3196 let Shape::Path { pts, .. } = &d.shapes[2] else {
3197 panic!()
3198 };
3199 assert!(
3200 pts[0].dist(Point::new(1.0, 0.0)) < 1e-9,
3201 "start {:?}",
3202 pts[0]
3203 );
3204 assert!(pts[1].dist(Point::new(2.0, 0.0)) < 1e-9, "end {:?}", pts[1]);
3205 }
3206
3207 #[test]
3208 fn for_loop_places_in_a_row() {
3209 let d = draw("for i = 1 to 3 do { box; move }");
3211 assert!(d.bbox.width() > 2.0);
3212 }
3213
3214 #[test]
3215 fn animate_timing() {
3216 let d = draw(
3217 "A: box\narrow\nbox\nanimate A with \"fade\" for 0.5\nanimate last arrow with \"draw\"\nanimate 2nd box with \"pop\" after A",
3218 );
3219 assert_eq!(d.anims.len(), 3);
3220 assert_eq!(d.anims[0].effect, "fade");
3222 assert_eq!(d.anims[0].shape, 0);
3223 assert!((d.anims[0].start).abs() < 1e-9 && (d.anims[0].duration - 0.5).abs() < 1e-9);
3224 assert_eq!(d.anims[1].shape, 1);
3226 assert!((d.anims[1].start - 0.5).abs() < 1e-9);
3227 assert_eq!(d.anims[2].shape, 2);
3229 assert!((d.anims[2].start - 0.5).abs() < 1e-9);
3230 }
3231
3232 #[test]
3233 fn continue_extends_previous_line() {
3234 let d = draw("line right 1\ncontinue down 0.5");
3236 assert_eq!(d.shapes.len(), 1, "should extend, not add a shape");
3237 let Shape::Path { pts, .. } = &d.shapes[0] else {
3238 panic!()
3239 };
3240 assert_eq!(pts.len(), 3);
3241 assert!(pts[2].dist(Point::new(1.0, -0.5)) < 1e-9, "{:?}", pts[2]);
3242 let d2 = draw("line right 1\ncontinue");
3244 let Shape::Path { pts, .. } = &d2.shapes[0] else {
3245 panic!()
3246 };
3247 assert!((pts.last().unwrap().x - 1.5).abs() < 1e-9);
3248 }
3249
3250 #[test]
3251 fn arc_from_to_endpoints_and_radius() {
3252 let d = draw("A:(0,0)\nB:(1,1)\narc from A to B");
3254 let Shape::Arc { c, r, a0, a1, .. } = &d.shapes[0] else {
3255 panic!()
3256 };
3257 assert!((*r - 2.0_f64.sqrt() / 2.0).abs() < 1e-9, "r = {r}");
3258 let s = *c + Point::new(a0.cos(), a0.sin()) * *r;
3259 let e = *c + Point::new(a1.cos(), a1.sin()) * *r;
3260 assert!(s.dist(Point::new(0.0, 0.0)) < 1e-9, "start {s:?}");
3261 assert!(e.dist(Point::new(1.0, 1.0)) < 1e-9, "end {e:?}");
3262
3263 let d_short = draw("arc from (0,0) to (0.3,0)");
3264 let Shape::Arc { r, .. } = &d_short.shapes[0] else {
3265 panic!()
3266 };
3267 assert!((*r - 0.25).abs() < 1e-9, "r = {r}");
3268
3269 let d_custom = draw("arcrad = 0.5\narc from (0,0) to (0.3,0)");
3270 let Shape::Arc { r, .. } = &d_custom.shapes[0] else {
3271 panic!()
3272 };
3273 assert!((*r - 0.5).abs() < 1e-9, "r = {r}");
3274
3275 let d2 = draw("A:(0,0)\nB:(1,0)\narc from A to B rad 2");
3277 let Shape::Arc { r, .. } = &d2.shapes[0] else {
3278 panic!()
3279 };
3280 assert!((*r - 2.0).abs() < 1e-9, "r = {r}");
3281 }
3282
3283 #[test]
3284 fn arc_direction_disambiguates_from_to_radius() {
3285 let d = draw(
3286 "arc left from (0.5,0) to (0,0.5) rad 0.5\n\
3287 arc right from (0.5,0) to (0,0.5) rad 0.5 dashed",
3288 );
3289 let Shape::Arc {
3290 c: left,
3291 a0: left_a0,
3292 a1: left_a1,
3293 ..
3294 } = &d.shapes[0]
3295 else {
3296 panic!()
3297 };
3298 let Shape::Arc {
3299 c: right,
3300 a0: right_a0,
3301 a1: right_a1,
3302 ..
3303 } = &d.shapes[1]
3304 else {
3305 panic!()
3306 };
3307
3308 assert!(left.dist(Point::new(0.0, 0.0)) < 1e-9, "left = {left:?}");
3309 assert!(right.dist(Point::new(0.5, 0.5)) < 1e-9, "right = {right:?}");
3310 assert!((left_a1 - left_a0).abs() < PI);
3311 assert!((right_a1 - right_a0).abs() > PI);
3312 }
3313
3314 #[test]
3315 fn arc_with_center_at_disambiguates_large_clockwise_sweep() {
3316 let d = draw("arc cw rad 1 from (0,-1) to (1,0) with .c at (0,0)");
3317 let Shape::Arc { c, r, a0, a1, .. } = &d.shapes[0] else {
3318 panic!()
3319 };
3320 let start = *c + Point::new(a0.cos(), a0.sin()) * *r;
3321 let end = *c + Point::new(a1.cos(), a1.sin()) * *r;
3322
3323 assert!(c.dist(Point::ZERO) < 1e-9, "center = {c:?}");
3324 assert!((*r - 1.0).abs() < 1e-9, "r = {r}");
3325 assert!(
3326 start.dist(Point::new(0.0, -1.0)) < 1e-9,
3327 "start = {start:?}"
3328 );
3329 assert!(end.dist(Point::new(1.0, 0.0)) < 1e-9, "end = {end:?}");
3330 assert!(*a1 - *a0 < -PI, "sweep = {}", *a1 - *a0);
3331 }
3332
3333 #[test]
3334 fn arc_width_height_attrs_size_arrowheads() {
3335 let d = draw("arc <-> wid .5 ht .75");
3336 let Shape::Arc { style, .. } = &d.shapes[0] else {
3337 panic!()
3338 };
3339
3340 assert!(
3341 (style.arrow_wid - 0.5).abs() < 1e-9,
3342 "wid = {}",
3343 style.arrow_wid
3344 );
3345 assert!(
3346 (style.arrow_ht - 0.75).abs() < 1e-9,
3347 "ht = {}",
3348 style.arrow_ht
3349 );
3350 }
3351
3352 #[test]
3353 fn scale_converts_user_units_to_inches() {
3354 let d = draw("scale = 2\nbox");
3357 let Shape::Box { w, h, .. } = &d.shapes[0] else {
3358 panic!()
3359 };
3360 assert!(
3361 (*w - 0.75).abs() < 1e-9 && (*h - 0.5).abs() < 1e-9,
3362 "{w} x {h}"
3363 );
3364
3365 let d = draw("scale = 2\nbox wid 2 ht 1");
3366 let Shape::Box { w, h, .. } = &d.shapes[0] else {
3367 panic!()
3368 };
3369 assert!((*w - 1.0).abs() < 1e-9 && (*h - 0.5).abs() < 1e-9);
3370
3371 let d = draw("scale = 2\nline from (0,0) to (2,0)");
3372 let Shape::Path { pts, .. } = &d.shapes[0] else {
3373 panic!()
3374 };
3375 assert!((pts.last().unwrap().x - 1.0).abs() < 1e-9, "{pts:?}");
3376
3377 let d = draw("scale = 2\nA: (2,0)\nbox wid A.x ht .2");
3378 let Shape::Box { w, .. } = &d.shapes[0] else {
3379 panic!()
3380 };
3381 assert!((*w - 1.0).abs() < 1e-9, "w = {w}");
3382
3383 let d = draw("scale = 2\nbox wid 2 ht 1\nscale = 1\nbox wid 1 ht .5");
3384 let Shape::Box { w, .. } = &d.shapes[0] else {
3385 panic!()
3386 };
3387 assert!((*w - 2.0).abs() < 1e-9, "w = {w}");
3388 let Shape::Box { c, .. } = &d.shapes[1] else {
3389 panic!()
3390 };
3391 assert!((c.x - 2.5).abs() < 1e-9, "center = {c:?}");
3392 }
3393
3394 #[test]
3395 fn same_reuses_previous_dims() {
3396 let d = draw("box wid 1 ht 0.4 at 0,0\nbox same at 2,0");
3398 let Shape::Box { w, h, .. } = &d.shapes[1] else {
3399 panic!()
3400 };
3401 assert!(
3402 (*w - 1.0).abs() < 1e-9 && (*h - 0.4).abs() < 1e-9,
3403 "{w} x {h}"
3404 );
3405 }
3406
3407 #[test]
3408 fn same_reuses_previous_open_vector() {
3409 let d = draw("line up 1\nright\nline same");
3410 let Shape::Path { pts, .. } = &d.shapes[1] else {
3411 panic!()
3412 };
3413 assert!(pts[0].dist(Point::new(0.0, 1.0)) < 1e-9, "{pts:?}");
3414 assert!(pts[1].dist(Point::new(0.0, 2.0)) < 1e-9, "{pts:?}");
3415 }
3416
3417 #[test]
3418 fn spline_expr_is_tension_not_distance() {
3419 let d = draw("spline 0.5 from 0,0 to 1,1 to 2,0");
3423 let Shape::Spline { pts, tension, .. } = &d.shapes[0] else {
3424 panic!("expected a spline")
3425 };
3426 assert_eq!(*tension, Some(0.5));
3427 assert_eq!(pts.len(), 3, "tension must not add a segment: {pts:?}");
3428 assert!(pts[0].dist(Point::new(0.0, 0.0)) < 1e-9, "{pts:?}");
3429 assert!(pts[2].dist(Point::new(2.0, 0.0)) < 1e-9, "{pts:?}");
3430 }
3431
3432 #[test]
3433 fn spline_variable_tension_does_not_drift() {
3434 let plain = draw("spline from 0,0 up 1.5 then right 2 then down 1.5");
3438 let Shape::Spline {
3439 pts: p0,
3440 tension: t0,
3441 ..
3442 } = &plain.shapes[0]
3443 else {
3444 panic!()
3445 };
3446 assert_eq!(*t0, None);
3447
3448 let tensioned = draw("x = 0.6\nspline x from 0,0 up 1.5 then right 2 then down 1.5");
3449 let Shape::Spline {
3450 pts: p1,
3451 tension: t1,
3452 ..
3453 } = &tensioned.shapes[0]
3454 else {
3455 panic!()
3456 };
3457 assert_eq!(*t1, Some(0.6));
3458 assert_eq!(p0.len(), p1.len());
3459 assert!(p0.first().unwrap().dist(*p1.first().unwrap()) < 1e-9);
3460 assert!(p0.last().unwrap().dist(*p1.last().unwrap()) < 1e-9);
3461 }
3462
3463 #[test]
3464 fn chop_trims_line_endpoints() {
3465 let d = draw("circle at 0,0\ncircle at 2,0\nline from 1st circle to 2nd circle chop");
3467 let Shape::Path { pts, .. } = &d.shapes[2] else {
3468 panic!()
3469 };
3470 assert!((pts[0].x - 0.25).abs() < 1e-9, "start {:?}", pts[0]);
3471 assert!(
3472 (pts.last().unwrap().x - 1.75).abs() < 1e-9,
3473 "end {:?}",
3474 pts.last()
3475 );
3476
3477 let d = draw("line from (0,0) to (2,0) chop 0 chop .5");
3478 let Shape::Path { pts, .. } = &d.shapes[0] else {
3479 panic!()
3480 };
3481 assert!((pts[0].x - 0.0).abs() < 1e-9, "{pts:?}");
3482 assert!((pts[1].x - 1.5).abs() < 1e-9, "{pts:?}");
3483
3484 let d = draw("line from (0,0) to (2,0) chop .5 chop 0");
3485 let Shape::Path { pts, .. } = &d.shapes[0] else {
3486 panic!()
3487 };
3488 assert!((pts[0].x - 0.5).abs() < 1e-9, "{pts:?}");
3489 assert!((pts[1].x - 2.0).abs() < 1e-9, "{pts:?}");
3490
3491 let d = draw("line from (0,0) to (2,0) chop -.25 chop -.5");
3492 let Shape::Path { pts, .. } = &d.shapes[0] else {
3493 panic!()
3494 };
3495 assert!((pts[0].x + 0.25).abs() < 1e-9, "{pts:?}");
3496 assert!((pts[1].x - 2.5).abs() < 1e-9, "{pts:?}");
3497 }
3498
3499 #[test]
3500 fn unknown_variables_are_errors() {
3501 assert!(eval(&parse("box wid typo ht 0.2").unwrap()).is_err());
3502 assert!(eval(&parse("typo += 1").unwrap()).is_err());
3503 }
3504
3505 #[test]
3506 fn rand_advances_and_seed_repeats() {
3507 let mut st = State::new();
3508 let a = st.eval_expr(&Expr::Rand(None)).unwrap();
3509 let b = st.eval_expr(&Expr::Rand(None)).unwrap();
3510 assert!((0.0..1.0).contains(&a));
3511 assert!((0.0..1.0).contains(&b));
3512 assert_ne!(a, b);
3513
3514 let seeded_a = st
3515 .eval_expr(&Expr::Rand(Some(Box::new(Expr::Num(1.0)))))
3516 .unwrap();
3517 let seeded_b = st
3518 .eval_expr(&Expr::Rand(Some(Box::new(Expr::Num(1.0)))))
3519 .unwrap();
3520 assert_eq!(seeded_a, seeded_b);
3521 assert!((seeded_a - 0.840_187_717).abs() < 1e-9, "{seeded_a}");
3522 let next = st.eval_expr(&Expr::Rand(None)).unwrap();
3523 assert!((next - 0.394_382_927).abs() < 1e-9, "{next}");
3524 }
3525
3526 #[test]
3527 fn arithmetic_matches_dpic_edge_cases() {
3528 assert_eq!(scalar("5.5 % 2").unwrap(), 0.0);
3529 assert_eq!(scalar("2.5 % 2").unwrap(), 1.0);
3530 assert_eq!(scalar("-2.5 % 2").unwrap(), -1.0);
3531 assert!(scalar("5 % 0.4").is_err());
3532
3533 let mut st = State::new();
3534 st.eval_stmts(&parse("x = 5.5; x %= 2").unwrap().stmts)
3535 .unwrap();
3536 assert_eq!(st.vars["x"], 0.0);
3537
3538 assert_eq!(scalar("sign(0)").unwrap(), 1.0);
3539 assert_eq!(scalar("sign(-0.1)").unwrap(), -1.0);
3540
3541 assert_eq!(scalar("(-2)^3").unwrap(), -8.0);
3542 assert_eq!(scalar("(-2)^2").unwrap(), 4.0);
3543 assert_eq!(scalar("0^0").unwrap(), 1.0);
3544 assert!(scalar("(-2)^0.5").is_err());
3545 assert!(scalar("0^-1").is_err());
3546 }
3547
3548 #[test]
3549 fn standalone_text_occupies_invisible_box() {
3550 let d = draw("textwid = 1; textht = .2\n\"x\"\nbox wid .2 ht .2");
3551 let Shape::Text { at, .. } = &d.shapes[0] else {
3552 panic!()
3553 };
3554 assert!((at.x - 0.5).abs() < 1e-9, "text at {at:?}");
3555 let Shape::Box { c, .. } = &d.shapes[1] else {
3556 panic!()
3557 };
3558 assert!((c.x - 1.1).abs() < 1e-9, "box center {c:?}");
3559 }
3560
3561 #[test]
3562 fn text_position_modifies_the_preceding_string_only() {
3563 let d = draw("\"LLLL\" ljust");
3564 let Shape::Text { text, .. } = &d.shapes[0] else {
3565 panic!()
3566 };
3567 assert_eq!(text[0].halign, -1);
3568
3569 let d = draw("\"RRRR\" rjust");
3570 let Shape::Text { text, .. } = &d.shapes[0] else {
3571 panic!()
3572 };
3573 assert_eq!(text[0].halign, 1);
3574
3575 let d = draw("box wid 1 ht .6 \"AAAA\" above \"BBBB\" below");
3576 let Shape::Box { text, .. } = &d.shapes[0] else {
3577 panic!()
3578 };
3579 assert_eq!(text[0].valign, 1);
3580 assert_eq!(text[1].valign, -1);
3581
3582 let d = draw("box \"AAAA\" above \"BBBB\"");
3583 let Shape::Box { text, .. } = &d.shapes[0] else {
3584 panic!()
3585 };
3586 assert_eq!(text[0].valign, 1);
3587 assert_eq!(text[1].valign, 0);
3588 }
3589
3590 #[test]
3591 fn style_globals_and_dash_lengths_apply() {
3592 let d = draw("linethick = 3\nline dashed .2\nline dotted .05");
3593 let Shape::Path { style, .. } = &d.shapes[0] else {
3594 panic!()
3595 };
3596 assert_eq!(style.thick, Some(3.0));
3597 assert_eq!(style.dash, Dash::Dashed(0.2));
3598 let Shape::Path { style, .. } = &d.shapes[1] else {
3599 panic!()
3600 };
3601 assert_eq!(style.dash, Dash::Dotted(Some(0.05)));
3602 }
3603
3604 #[test]
3605 fn color_attribute_expands_runtime_macro_string() {
3606 let d = draw(
3607 "r = 0; g = 0; b = 0.6\n\
3608 if dpicopt == optSVG then {\n\
3609 define customcolor { sprintf(\"rgb(%g,%g,%g)\", int(r*255), int(g*255), int(b*255)) }\n\
3610 }\n\
3611 arc color customcolor",
3612 );
3613 let Shape::Arc { style, .. } = &d.shapes[0] else {
3614 panic!()
3615 };
3616 assert_eq!(style.stroke.as_deref(), Some("rgb(0,0,153)"));
3617 assert_eq!(style.fill, Some(Fill::Color("rgb(0,0,153)".into())));
3618 }
3619
3620 #[test]
3621 fn ps_width_scales_drawing() {
3622 let d = draw(".PS 6\nbox\n.PE");
3625 let Shape::Box { w, .. } = &d.shapes[0] else {
3626 panic!()
3627 };
3628 assert!((*w - 5.912_408_759).abs() < 1e-9, "w = {w}");
3629 assert!(
3630 (d.bbox.width() - 5.923_519_870).abs() < 1e-9,
3631 "w = {}",
3632 d.bbox.width()
3633 );
3634 }
3635
3636 #[test]
3637 fn text_extent_in_bbox() {
3638 let d = draw("\"a long label here\"");
3640 assert!(d.bbox.width() > 0.5, "w = {}", d.bbox.width());
3641 assert!(d.bbox.height() > 0.1, "h = {}", d.bbox.height());
3642 let d2 = draw("box wid 0.2 ht 0.2 \"a very wide label\"");
3644 assert!(d2.bbox.width() > 0.3, "w = {}", d2.bbox.width());
3645 }
3646
3647 #[test]
3648 fn text_object_width_bounds_rendered_bbox() {
3649 let d = draw("\"abcdefghij\" wid 0.1");
3652 assert!(
3653 (d.bbox.width() - 0.1).abs() < 1e-9,
3654 "w = {}",
3655 d.bbox.width()
3656 );
3657
3658 let d = draw(".PS 1\n\"abcdefghij\" wid 0.1\n.PE");
3659 assert!(
3660 (d.bbox.width() - 1.0).abs() < 1e-9,
3661 "w = {}",
3662 d.bbox.width()
3663 );
3664 }
3665
3666 #[test]
3667 fn text_position_and_offset_expand_bbox_in_the_rendered_direction() {
3668 let d = draw("textoffset = 0.1\n\"abc\" ljust at (0,0)");
3669 assert!(d.bbox.min.x >= 0.1 - 1e-9, "{:?}", d.bbox);
3670
3671 let d = draw("textoffset = 0.1\n\"abc\" rjust at (0,0)");
3672 assert!(d.bbox.max.x <= -0.1 + 1e-9, "{:?}", d.bbox);
3673
3674 let d = draw("textoffset = 0.1\n\"abc\" above at (0,0)");
3675 assert!(d.bbox.min.y > 0.0, "{:?}", d.bbox);
3676
3677 let d = draw("textoffset = 0.1\n\"abc\" below at (0,0)");
3678 assert!(d.bbox.max.y < 0.0, "{:?}", d.bbox);
3679 }
3680
3681 #[test]
3682 fn invisible_geometry_does_not_expand_drawing_bbox() {
3683 let d = draw("box invis wid 1000 ht 1000 at (0,0)\nbox wid 1 ht 1 at (0,0)");
3684 assert!(
3685 (d.bbox.width() - (1.0 + DEFAULT_STROKE_IN)).abs() < 1e-9,
3686 "w = {}",
3687 d.bbox.width()
3688 );
3689 assert!(
3690 (d.bbox.height() - (1.0 + DEFAULT_STROKE_IN)).abs() < 1e-9,
3691 "h = {}",
3692 d.bbox.height()
3693 );
3694
3695 let d2 = draw("line invis from (0,0) to (1000,1000)\nbox wid 1 ht 1 at (0,0)");
3696 assert!(
3697 (d2.bbox.width() - (1.0 + DEFAULT_STROKE_IN)).abs() < 1e-9,
3698 "w = {}",
3699 d2.bbox.width()
3700 );
3701 assert!(
3702 (d2.bbox.height() - (1.0 + DEFAULT_STROKE_IN)).abs() < 1e-9,
3703 "h = {}",
3704 d2.bbox.height()
3705 );
3706
3707 let d3 = draw("I: box invis wid 1000 ht 1000 at (0,0)\nbox wid 1 ht 1 with .sw at I.ne");
3708 let Shape::Box { c, .. } = &d3.shapes[1] else {
3709 panic!()
3710 };
3711 assert!(c.dist(Point::new(500.5, 500.5)) < 1e-9, "c = {c:?}");
3712 assert!(
3713 (d3.bbox.width() - (1.0 + DEFAULT_STROKE_IN)).abs() < 1e-9,
3714 "w = {}",
3715 d3.bbox.width()
3716 );
3717 assert!(
3718 (d3.bbox.height() - (1.0 + DEFAULT_STROKE_IN)).abs() < 1e-9,
3719 "h = {}",
3720 d3.bbox.height()
3721 );
3722 }
3723
3724 #[test]
3725 fn move_expands_drawing_bbox_like_dpic() {
3726 let d = draw("line from (0,0) to (1,0)\nmove left 0.4 from (0,0)");
3727 assert!(
3728 (d.bbox.width() - (1.4 + DEFAULT_STROKE_IN / 2.0)).abs() < 1e-9,
3729 "w = {}",
3730 d.bbox.width()
3731 );
3732 }
3733
3734 #[test]
3735 fn division_by_zero_errors() {
3736 assert!(eval(&parse("box wid 1/0").unwrap()).is_err());
3738 assert!(eval(&parse("A:(0,0)\nB:(0,0)\nx = (B.x-A.x)/(B.y-A.y)").unwrap()).is_err());
3739 }
3740
3741 #[test]
3742 fn place_dot_in_coordinate_pair() {
3743 let d = draw("A: box wid 1 ht 1 at 2,3\nbox wid 0.2 ht 0.2 at (A.x, A.y - 1)");
3745 let Shape::Box { c, .. } = &d.shapes[1] else {
3746 panic!()
3747 };
3748 assert!(
3749 (c.x - 2.0).abs() < 1e-9 && (c.y - 2.0).abs() < 1e-9,
3750 "c = {c:?}"
3751 );
3752 }
3753
3754 #[test]
3755 fn bare_coordinate_pair_places_label() {
3756 let d = draw("P: 1,2\nbox wid 0.2 ht 0.2 at P");
3757 let Shape::Box { c, .. } = &d.shapes[0] else {
3758 panic!()
3759 };
3760 assert!(c.dist(Point::new(1.0, 2.0)) < 1e-9, "c = {c:?}");
3761 }
3762
3763 #[test]
3764 fn block_sub_labels_resolve() {
3765 let d = draw("B: [ A: box wid 1 ht 1 at 0,0 ]\nbox wid 0.2 ht 0.2 with .sw at B.A.ne");
3767 let Shape::Box { c, .. } = &d.shapes.last().unwrap() else {
3768 panic!()
3769 };
3770 assert!(
3773 (c.x - 1.1).abs() < 1e-9 && (c.y - 0.6).abs() < 1e-9,
3774 "c = {c:?}"
3775 );
3776 }
3777
3778 #[test]
3779 fn block_can_anchor_on_own_member() {
3780 let d = draw("P:(2,3)\n[ A: box wid 1 ht 1 at 0,0; circle rad 0.1 at 2,0 ] with .A.c at P");
3783 let Shape::Box { c, .. } = &d.shapes[0] else {
3784 panic!()
3785 };
3786 assert!(c.dist(Point::new(2.0, 3.0)) < 1e-9, "A center = {c:?}");
3787 let Shape::Circle { c, .. } = &d.shapes[1] else {
3788 panic!()
3789 };
3790 assert!(c.dist(Point::new(4.0, 3.0)) < 1e-9, "circle center = {c:?}");
3791 }
3792
3793 #[test]
3794 fn block_pair_anchor_uses_local_coordinates() {
3795 let d = draw("[ box wid 2 ht 1 at (1,0) ] with (0,0) at (10,20)");
3798 let Shape::Box { c, .. } = &d.shapes[0] else {
3799 panic!()
3800 };
3801 assert!(c.dist(Point::new(11.0, 20.0)) < 1e-9, "c = {c:?}");
3802 }
3803
3804 #[test]
3805 fn block_layout_matches_dpic_for_negative_box_width() {
3806 let d = draw("move 1\n[ box wid -0.5 ht 0.5 ]; box wid 0.75 ht 0.75");
3807 let boxes: Vec<Point> = d
3808 .shapes
3809 .iter()
3810 .filter_map(|shape| match shape {
3811 Shape::Box { c, .. } => Some(*c),
3812 _ => None,
3813 })
3814 .collect();
3815 assert_eq!(boxes.len(), 2);
3816 assert!(
3817 boxes[0].dist(Point::new(0.75, 0.0)) < 1e-9,
3818 "negative box center = {:?}",
3819 boxes[0]
3820 );
3821 assert!(
3822 boxes[1].dist(Point::new(1.375, 0.0)) < 1e-9,
3823 "following box center = {:?}",
3824 boxes[1]
3825 );
3826 }
3827
3828 #[test]
3829 fn block_object_renders_attached_text() {
3830 let d = draw("[ box ] \"block label\"");
3831 assert!(d.shapes.iter().any(|s| {
3832 matches!(
3833 s,
3834 Shape::Text { text, .. } if text.iter().any(|line| line.s == "block label")
3835 )
3836 }));
3837 }
3838
3839 #[test]
3840 fn block_anchors_ignore_attached_text_extents() {
3841 let d = draw(
3844 r#"B: [ right; box "{\bf veryveryverywide}"; move; box ]
3845box wid 0.1 ht 0.1 at B.s"#,
3846 );
3847 let Shape::Box { c, .. } = d.shapes.last().unwrap() else {
3848 panic!()
3849 };
3850 assert!(c.dist(Point::new(1.0, -0.25)) < 1e-9, "c = {c:?}");
3851 }
3852
3853 #[test]
3854 fn nested_macro_block_can_reference_parent_label() {
3855 let d = draw(
3856 "define marker { [ P: circle rad 0.01 at $1.start ] with .P at $1.start }\n[ A: arrow from (0,0) to (1,0); marker(A) ]",
3857 );
3858 let Shape::Path { pts, .. } = &d.shapes[0] else {
3859 panic!()
3860 };
3861 let Shape::Circle { c, .. } = &d.shapes[1] else {
3862 panic!()
3863 };
3864 assert!(
3865 c.dist(pts[0]) < 1e-9,
3866 "circle = {c:?}, arrow start = {:?}",
3867 pts[0]
3868 );
3869 }
3870
3871 #[test]
3872 fn position_vector_arithmetic() {
3873 let d = draw("box wid 0.2 ht 0.2 at (2,4)/2 + (1,0)");
3875 let Shape::Box { c, .. } = &d.shapes[0] else {
3876 panic!()
3877 };
3878 assert!(
3879 (c.x - 2.0).abs() < 1e-9 && (c.y - 2.0).abs() < 1e-9,
3880 "c = {c:?}"
3881 );
3882 }
3883
3884 #[test]
3885 fn interpolation_angle_brackets() {
3886 let d = draw("A:(0,0)\nB:(2,0)\nbox wid 0.1 ht 0.1 at 0.5 <A,B>");
3887 let Shape::Box { c, .. } = &d.shapes.last().unwrap() else {
3888 panic!()
3889 };
3890 assert!((c.x - 1.0).abs() < 1e-9, "c = {c:?}");
3891 }
3892
3893 #[test]
3894 fn string_equality_in_if() {
3895 let d1 = draw("if \"a\" == \"\" then { box } else { circle }");
3897 assert!(matches!(d1.shapes[0], Shape::Circle { .. }));
3898 let d2 = draw("if \"\" == \"\" then { box } else { circle }");
3899 assert!(matches!(d2.shapes[0], Shape::Box { .. }));
3900 }
3901
3902 #[test]
3903 fn dpicopt_defaults_to_svg_backend() {
3904 let d = draw("if dpicopt == optSVG then { box } else { circle }");
3905 assert!(matches!(d.shapes[0], Shape::Box { .. }));
3906 }
3907
3908 #[test]
3909 fn svg_font_stub_and_string_sprintf_are_harmless() {
3910 let d = draw("box sprintf(\"x%s\", svg_font(\"Times\", 12))");
3911 let Shape::Box { text, .. } = &d.shapes[0] else {
3912 panic!()
3913 };
3914 assert_eq!(text[0].s, "x");
3915 }
3916
3917 #[test]
3918 fn inch_suffix_and_bare_distance() {
3919 let d = draw("box wid .5i ht .5i");
3921 let Shape::Box { w, .. } = &d.shapes[0] else {
3922 panic!()
3923 };
3924 assert!((*w - 0.5).abs() < 1e-9, "w = {w}");
3925 let d2 = draw("right\nmove 1\nbox at Here");
3926 let Shape::Box { c, .. } = &d2.shapes.last().unwrap() else {
3927 panic!()
3928 };
3929 assert!(c.x > 0.9, "moved to {c:?}");
3930 }
3931
3932 #[test]
3933 fn embedded_assignment_returns_value() {
3934 let d = draw("if (s = 3) > 1 then { box wid s ht 0.1 }");
3935 let Shape::Box { w, .. } = &d.shapes[0] else {
3936 panic!()
3937 };
3938 assert!((*w - 3.0).abs() < 1e-9, "w = {w}");
3939 }
3940
3941 #[test]
3942 fn copy_includes_a_file() {
3943 let dir = std::env::temp_dir().join(format!("rpic_copy_{}", std::process::id()));
3945 std::fs::create_dir_all(&dir).unwrap();
3946 std::fs::write(dir.join("inc.pic"), "box wid 0.5 ht 0.5\n").unwrap();
3947 let pic = parse_in_dir("copy \"inc.pic\"\ncircle", Some(dir.as_path())).unwrap();
3948 let d = eval(&pic).unwrap();
3949 assert!(d.shapes.iter().any(|s| matches!(s, Shape::Box { .. })));
3950 assert!(d.shapes.iter().any(|s| matches!(s, Shape::Circle { .. })));
3951 let _ = std::fs::remove_dir_all(&dir);
3952 }
3953
3954 #[test]
3955 fn paren_position_coordinate() {
3956 let d = draw("A:(0,0)\nB:(3,1)\nbox wid (B-A).x ht (B-A).y at 0,-2");
3958 let Shape::Box { w, h, .. } = &d.shapes[0] else {
3959 panic!()
3960 };
3961 assert!(
3962 (*w - 3.0).abs() < 1e-9 && (*h - 1.0).abs() < 1e-9,
3963 "{w} x {h}"
3964 );
3965 }
3966
3967 #[test]
3968 fn arrowhead_size_follows_globals() {
3969 let d = draw("arrowht = 0.3; arrowwid = 0.2\narrow right 1");
3971 let Shape::Path { style, .. } = &d.shapes[0] else {
3972 panic!()
3973 };
3974 assert!(
3975 (style.arrow_ht - 0.3).abs() < 1e-9,
3976 "ht = {}",
3977 style.arrow_ht
3978 );
3979 assert!(
3980 (style.arrow_wid - 0.2).abs() < 1e-9,
3981 "wid = {}",
3982 style.arrow_wid
3983 );
3984 }
3985
3986 #[test]
3987 fn scaling_existing_geometry_scales_arrowhead_metadata() {
3988 let factor = 6.6 / 8.2;
3992 let d = draw("scale = 6.6/8.2\nline <-\nscale = 1");
3993 let Shape::Path { style, .. } = &d.shapes[0] else {
3994 panic!()
3995 };
3996 assert!(
3997 (style.arrow_ht - 0.1 * factor).abs() < 1e-9,
3998 "ht = {}",
3999 style.arrow_ht
4000 );
4001 assert!(
4002 (style.arrow_wid - 0.05 * factor).abs() < 1e-9,
4003 "wid = {}",
4004 style.arrow_wid
4005 );
4006 }
4007
4008 #[test]
4009 fn dpic_default_env_values_are_readable() {
4010 assert!((scalar("textoffset").unwrap() - 2.0 / 72.0).abs() < 1e-9);
4011 assert!((scalar("textht").unwrap() - (11.0 / 72.0) * 0.66).abs() < 1e-9);
4012 assert!((scalar("arrowhead").unwrap() - 1.0).abs() < 1e-9);
4013 assert!((scalar("linethick").unwrap() - 0.8).abs() < 1e-9);
4014 assert_eq!(scalar("margin").unwrap(), 0.0);
4015 assert_eq!(scalar("topmargin").unwrap(), 0.0);
4016 assert_eq!(scalar("rightmargin").unwrap(), 0.0);
4017 assert_eq!(scalar("bottommargin").unwrap(), 0.0);
4018 assert_eq!(scalar("leftmargin").unwrap(), 0.0);
4019 }
4020
4021 #[test]
4022 fn canvas_margin_vars_are_scaled_dimensions() {
4023 let d = draw("margin = 1; topmargin = 0.5; rightmargin = 0.25; line right");
4024 assert_eq!(
4025 d.canvas_margin,
4026 CanvasMargin {
4027 top: 1.5,
4028 right: 1.25,
4029 bottom: 1.0,
4030 left: 1.0,
4031 }
4032 );
4033
4034 let d = draw("scale = 2; margin = 1; topmargin = 1; line right");
4035 assert_eq!(
4036 d.canvas_margin,
4037 CanvasMargin {
4038 top: 1.0,
4039 right: 0.5,
4040 bottom: 0.5,
4041 left: 0.5,
4042 }
4043 );
4044
4045 let d = draw("margin = 1; scale = 2; line right");
4046 assert_eq!(
4047 d.canvas_margin,
4048 CanvasMargin {
4049 top: 1.0,
4050 right: 1.0,
4051 bottom: 1.0,
4052 left: 1.0,
4053 }
4054 );
4055 }
4056
4057 #[test]
4058 fn print_statements_collect_diagnostics() {
4059 let d = draw("print 5.5\nprint 5.5%2\nprint \"hello\"\nprint sprintf(\"x=%g\", 1.25)");
4060 assert_eq!(d.diagnostics, ["5.5", "0", "hello", "x=1.25"]);
4061
4062 let d = draw("[ print \"inside\"; box ]\nprint 7");
4063 assert_eq!(d.diagnostics, ["inside", "7"]);
4064 }
4065
4066 #[test]
4067 fn open_object_width_height_attrs_are_arrowhead_dimensions() {
4068 let d = draw("arrowwid = 0.2; arrowht = 0.3\nA: line right 2\nbox wid (A.wid) ht (A.ht)");
4069 assert_box_size(&d.shapes[1], 0.2, 0.3);
4070
4071 let d = draw("arrowwid = 0.12; arrowht = 0.34\nA: move right 2\nbox wid (A.wid) ht (A.ht)");
4072 assert_box_size(&d.shapes[1], 0.12, 0.34);
4073
4074 let d = draw(
4075 "arrowwid = 0.23; arrowht = 0.31\nA: spline from (0,0) to (2,1)\nbox wid (A.wid) ht (A.ht)",
4076 );
4077 assert_box_size(&d.shapes[1], 0.23, 0.31);
4078 }
4079
4080 #[test]
4081 fn radius_and_diameter_attrs_are_type_specific() {
4082 let d = draw("B: box rad 0.1 wid 1 ht 1\nbox wid (B.rad) ht 0.3");
4083 assert_box_size(&d.shapes[1], 0.1, 0.3);
4084
4085 let d = draw("C: arc rad 0.7 from (0,0) to (0,1.4)\nbox wid (C.rad) ht (C.diam)");
4086 assert_box_size(&d.shapes[1], 0.7, 1.4);
4087 }
4088
4089 #[test]
4090 fn invalid_type_scalar_attrs_match_dpic_zero() {
4091 let prog = parse(
4092 "E: ellipse wid 2 ht 1\nB: box wid 1 ht 1\nA: arc rad .5\n\
4093 e_rad = E.rad\ne_diam = E.diam\nb_diam = B.diam\na_len = A.len",
4094 )
4095 .unwrap();
4096 let mut st = State::new();
4097 st.eval_stmts(&prog.stmts).unwrap();
4098 assert_eq!(st.vars["e_rad"], 0.0);
4099 assert_eq!(st.vars["e_diam"], 0.0);
4100 assert_eq!(st.vars["b_diam"], 0.0);
4101 assert_eq!(st.vars["a_len"], 0.0);
4102 }
4103
4104 #[test]
4105 fn arrowhead_type_open_vs_filled() {
4106 let d = draw("arrow right 1");
4108 let Shape::Path { style, .. } = &d.shapes[0] else {
4109 panic!()
4110 };
4111 assert!(style.arrow_filled, "default should be filled");
4112 let d2 = draw("arrowhead = 0\narrow right 1");
4113 let Shape::Path { style, .. } = &d2.shapes[0] else {
4114 panic!()
4115 };
4116 assert!(!style.arrow_filled, "arrowhead=0 should be open");
4117
4118 let d3 = draw("arrowhead = 0\nline <- 1 up");
4119 let Shape::Path { style, .. } = &d3.shapes[0] else {
4120 panic!()
4121 };
4122 assert!(style.arrow_filled, "`<- 1` should override the global");
4123
4124 let d4 = draw("line <- 0 up");
4125 let Shape::Path { style, .. } = &d4.shapes[0] else {
4126 panic!()
4127 };
4128 assert!(!style.arrow_filled, "`<- 0` should override the global");
4129 }
4130
4131 #[test]
4132 fn maxps_clamps_oversized_drawing() {
4133 let d = draw("box wid 20 ht 30");
4135 assert!(
4136 d.bbox.width() <= 8.5 + 1e-6 && d.bbox.height() <= 11.0 + 1e-6,
4137 "{}x{}",
4138 d.bbox.width(),
4139 d.bbox.height()
4140 );
4141 let d2 = draw("maxpsht = 200; maxpswid = 50\nbox wid 20 ht 30");
4143 assert!(
4144 (d2.bbox.height() - (30.0 + DEFAULT_STROKE_IN)).abs() < 1e-6,
4145 "h = {}",
4146 d2.bbox.height()
4147 );
4148 let d3 = draw("box wid 2 ht 1");
4150 assert!((d3.bbox.width() - (2.0 + DEFAULT_STROKE_IN)).abs() < 1e-6);
4151
4152 let d4 = draw("maxpswid = 2; maxpsht = 100\nmargin = 1\nbox wid 1 ht 0.5");
4153 assert!(
4154 d4.bbox.width() + d4.canvas_margin.horizontal() <= 2.0 + 1e-6,
4155 "canvas width = {}",
4156 d4.bbox.width() + d4.canvas_margin.horizontal()
4157 );
4158 assert!(
4159 d4.canvas_margin.left < 1.0 && d4.canvas_margin.right < 1.0,
4160 "{:?}",
4161 d4.canvas_margin
4162 );
4163 }
4164
4165 #[test]
4166 fn block_variable_assignments_are_local() {
4167 let d = draw("x = 1\n[ x = 5 ]\nbox wid x ht 0.3");
4168 let Shape::Box { w, .. } = d.shapes.last().unwrap() else {
4169 panic!()
4170 };
4171 assert!((*w - 1.0).abs() < 1e-9, "w = {w}");
4172
4173 assert!(eval(&parse("[ x = 5 ]\nbox wid x ht 0.3").unwrap()).is_err());
4174 }
4175
4176 #[test]
4177 fn block_env_assignments_are_local() {
4178 let d = draw("[ boxwid = 2; box ]\nbox");
4179 let Shape::Box { w, .. } = &d.shapes[0] else {
4180 panic!()
4181 };
4182 assert!((*w - 2.0).abs() < 1e-9, "inner w = {w}");
4183 let Shape::Box { w, .. } = &d.shapes[1] else {
4184 panic!()
4185 };
4186 assert!((*w - 0.75).abs() < 1e-9, "outer w = {w}");
4187 }
4188
4189 #[test]
4190 fn block_mutating_var_assignments_update_inherited_vars() {
4191 let d = draw("x = 1\n[ x := 5 ]\nbox wid x ht 0.3");
4192 let Shape::Box { w, .. } = d.shapes.last().unwrap() else {
4193 panic!()
4194 };
4195 assert!((*w - 5.0).abs() < 1e-9, "w = {w}");
4196
4197 let d = draw("x = 1\n[ x += 2 ]\nbox wid x ht 0.3");
4198 let Shape::Box { w, .. } = d.shapes.last().unwrap() else {
4199 panic!()
4200 };
4201 assert!((*w - 3.0).abs() < 1e-9, "w = {w}");
4202
4203 assert!(eval(&parse("[ x = 1; x += 2 ]\nbox wid x ht 0.3").unwrap()).is_err());
4204
4205 let d = draw("boxwid = 0.75\n[ boxwid := 2; box ]\nbox");
4206 let Shape::Box { w, .. } = &d.shapes[1] else {
4207 panic!()
4208 };
4209 assert!((*w - 0.75).abs() < 1e-9, "outer w = {w}");
4210 }
4211
4212 #[test]
4213 fn figuras_examples_compile() {
4214 for src in [
4217 include_str!("../../../examples/figuras/fig01.pic"),
4218 include_str!("../../../examples/figuras/fig36.pic"),
4219 include_str!("../../../examples/figuras/fig40.pic"),
4220 ] {
4221 let d = eval(&parse(src).unwrap()).unwrap();
4222 assert!(!d.shapes.is_empty());
4223 }
4224 }
4225
4226 #[test]
4227 fn figuras_element_examples_compile() {
4228 let shim = include_str!("../../../examples/figuras/circuit_macros.pic");
4234 for body in [
4235 include_str!("../../../examples/figuras/fig21.pic"),
4236 include_str!("../../../examples/figuras/fig23.pic"),
4237 include_str!("../../../examples/figuras/fig26.pic"),
4238 include_str!("../../../examples/figuras/fig27.pic"),
4239 include_str!("../../../examples/figuras/fig28.pic"),
4240 include_str!("../../../examples/figuras/fig30.pic"),
4241 include_str!("../../../examples/figuras/fig33.pic"),
4242 include_str!("../../../examples/figuras/fig45.pic"),
4243 include_str!("../../../examples/figuras/fig46.pic"),
4244 include_str!("../../../examples/figuras/fig09.pic"),
4245 include_str!("../../../examples/figuras/fig11.pic"),
4246 ] {
4247 let body = body.replace("copy \"circuit_macros.pic\"", shim);
4248 let src = format!("{}\n{}", crate::CIRCUITS, body);
4249 let d = eval(&parse(&src).unwrap()).unwrap();
4250 assert!(!d.shapes.is_empty());
4251 }
4252 }
4253
4254 #[test]
4255 fn lib3d_examples_compile() {
4256 let shim = include_str!("../../../examples/lib3d/lib3d.pic");
4259 for body in [
4260 include_str!("../../../examples/lib3d/frame.pic"),
4261 include_str!("../../../examples/lib3d/views.pic"),
4262 ] {
4263 let src = body.replace("copy \"lib3d.pic\"", shim);
4264 let d = eval(&parse(&src).unwrap()).unwrap();
4265 assert!(!d.shapes.is_empty());
4266 }
4267 }
4268
4269 #[test]
4270 fn brace_ncount_as_place() {
4271 let d = draw(
4273 "box at 0,0\nbox at 2,0\nbox at 4,0\narrow from {2}th last box.e to {1}th last box.w",
4274 );
4275 let Shape::Path { pts, .. } = d.shapes.last().unwrap() else {
4276 panic!()
4277 };
4278 assert!(pts[0].x > 2.0 && pts.last().unwrap().x < 4.0, "{pts:?}");
4279 }
4280
4281 #[test]
4282 fn dpic_unit_suffix() {
4283 let d = draw("box wid 72bp__ ht 0.3");
4285 let Shape::Box { w, .. } = &d.shapes[0] else {
4286 panic!()
4287 };
4288 assert!((*w - 1.0).abs() < 1e-9, "w = {w}");
4289 }
4290
4291 #[test]
4292 fn block_sees_outer_labels() {
4293 let d = draw("A: (0,0)\n[ line from A to (2,0) ]");
4295 assert!(
4296 d.shapes.iter().any(|s| matches!(s, Shape::Path { .. })),
4297 "block should draw a line referencing the outer label A"
4298 );
4299 assert!(eval(&parse("box\n[ circle; \"x\" at last box ]").unwrap()).is_err());
4302 }
4303
4304 #[test]
4305 fn arg_count_macro() {
4306 let d = draw("define cnt { $+ }\nx = cnt(a, b, c)\nbox wid x ht 0.3");
4308 let Shape::Box { w, .. } = &d.shapes[0] else {
4309 panic!()
4310 };
4311 assert!((*w - 3.0).abs() < 1e-9, "w = {w}");
4312 }
4313
4314 #[test]
4315 fn exec_evaluates_generated_pic_in_macro_arg_scope() {
4316 let d = draw(
4317 "define array { for i_array=2 to $+ do { exec sprintf(\"$1[%g] = $%g\", i_array-1, i_array) } }\narray(a, 0, 1, 3)\nbox wid a[2] ht a[3]",
4318 );
4319 let Shape::Box { w, h, .. } = &d.shapes[0] else {
4320 panic!()
4321 };
4322 assert!(
4323 (*w - 1.0).abs() < 1e-9 && (*h - 3.0).abs() < 1e-9,
4324 "{w} x {h}"
4325 );
4326 }
4327
4328 #[test]
4329 fn exec_unescapes_generated_quoted_text() {
4330 let d = draw("exec sprintf(\"\\\"x\\\" at Here\")");
4331 let Shape::Text { text, .. } = &d.shapes[0] else {
4332 panic!()
4333 };
4334 assert_eq!(text[0].s, "x");
4335 }
4336
4337 #[test]
4338 fn macro_token_pasting_concatenates_adjacent_args() {
4339 let d = draw("define mark { $1$2: (1,0) }\nmark(A,B)\nbox wid 0.2 ht 0.2 at AB");
4340 let Shape::Box { c, .. } = &d.shapes[0] else {
4341 panic!()
4342 };
4343 assert!(c.dist(Point::new(1.0, 0.0)) < 1e-9, "c = {c:?}");
4344 }
4345
4346 #[test]
4347 fn macro_string_substitution_preserves_dot_prefixed_arguments() {
4348 let d = draw("define label { \"$1\"; \"$2\" }\nlabel(.ne,above)");
4349 let labels: Vec<&str> = d
4350 .shapes
4351 .iter()
4352 .filter_map(|shape| match shape {
4353 Shape::Text { text, .. } => Some(text[0].s.as_str()),
4354 _ => None,
4355 })
4356 .collect();
4357 assert_eq!(labels, [".ne", "above"]);
4358 }
4359
4360 #[test]
4361 fn recursive_macro_terminates() {
4362 let d = draw("define rec { if $1 <= 0 then { circle } else { box; rec($1-1) } }\nrec(3)");
4365 let boxes = d
4366 .shapes
4367 .iter()
4368 .filter(|s| matches!(s, Shape::Box { .. }))
4369 .count();
4370 let circles = d
4371 .shapes
4372 .iter()
4373 .filter(|s| matches!(s, Shape::Circle { .. }))
4374 .count();
4375 assert_eq!((boxes, circles), (3, 1), "shapes = {:?}", d.shapes.len());
4376 }
4377
4378 #[test]
4379 fn default_argument_idiom() {
4380 let d = draw(
4383 "define b { if \"$1\"==\"\" then { w = 1 } else { w = $1 }\n box wid w ht 0.2 }\nb()",
4384 );
4385 let Shape::Box { w, .. } = &d.shapes[0] else {
4386 panic!()
4387 };
4388 assert!((*w - 1.0).abs() < 1e-9, "w = {w}");
4389 let d2 = draw(
4391 "define b { if \"$1\"==\"\" then { w = 1 } else { w = $1 }\n box wid w ht 0.2 }\nb(2.5)",
4392 );
4393 let Shape::Box { w, .. } = &d2.shapes[0] else {
4394 panic!()
4395 };
4396 assert!((*w - 2.5).abs() < 1e-9, "w = {w}");
4397 }
4398
4399 #[test]
4400 fn last_ordinal() {
4401 let d = draw("box at 0,0\nbox at 2,0\narrow from 1st box.e to 2nd box.w");
4402 let Shape::Path { pts, .. } = &d.shapes[2] else {
4403 panic!()
4404 };
4405 assert!(pts[0].x > 0.0 && pts.last().unwrap().x < 2.0);
4407 }
4408
4409 #[test]
4410 fn untyped_last_references_any_kind() {
4411 let d = draw("circle rad 0.5 at (3,1)\n\"x\" at last.c");
4413 let Shape::Text { at, .. } = d.shapes.last().unwrap() else {
4414 panic!()
4415 };
4416 assert!((at.x - 3.0).abs() < 1e-9 && (at.y - 1.0).abs() < 1e-9);
4417 }
4418
4419 #[test]
4420 fn untyped_last_corner_after_box() {
4421 let d = draw("box wid 2 ht 1 at (0,0)\n\"y\" at last.n");
4423 let Shape::Text { at, .. } = d.shapes.last().unwrap() else {
4424 panic!()
4425 };
4426 assert!((at.x - 0.0).abs() < 1e-9 && (at.y - 0.5).abs() < 1e-9);
4427 }
4428
4429 #[test]
4430 fn untyped_nth_last_spans_kinds() {
4431 let d = draw("box at (0,0)\ncircle at (2,0)\n\"z\" at 2nd last.c");
4433 let Shape::Text { at, .. } = d.shapes.last().unwrap() else {
4434 panic!()
4435 };
4436 assert!((at.x - 0.0).abs() < 1e-9, "x = {}", at.x);
4437 }
4438
4439 #[test]
4440 fn typed_last_still_filters_by_kind() {
4441 let d = draw("box at (0,0)\ncircle at (2,0)\n\"w\" at last box.c");
4443 let Shape::Text { at, .. } = d.shapes.last().unwrap() else {
4444 panic!()
4445 };
4446 assert!((at.x - 0.0).abs() < 1e-9, "x = {}", at.x);
4447 }
4448
4449 #[test]
4450 fn untyped_last_with_no_object_errors() {
4451 assert!(eval(&parse("\"q\" at last.c").unwrap()).is_err());
4452 }
4453}