Skip to main content

deckcraft_geom/
preset.rs

1//! Preset shape geometry.
2//!
3//! Each preset is identified by its Office Open XML name (`rect`, `roundRect`, `rightArrow`…) so
4//! files round-trip, but every outline here is our own construction in Rust: we re-derive the shapes
5//! from what they look like and from the meaning of their adjust values, not from any reference
6//! geometry table. Adjust values use the file convention: ratios in 1/100 000 of the shorter side (or
7//! of the width/height), angles in 1/60 000 degree.
8
9use kurbo::{Arc, BezPath, Point, Rect, Shape as _, Vec2};
10use serde::{Deserialize, Serialize};
11
12mod extra;
13
14/// How a sub-path is filled. `Lighten`/`Darken` variants shade the shape's fill (3-D looking faces).
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub enum FillMode {
18    Norm,
19    None,
20    Lighten,
21    LightenLess,
22    Darken,
23    DarkenLess,
24}
25
26#[derive(Clone, Debug)]
27pub struct SubPath {
28    pub path: BezPath,
29    pub fill: FillMode,
30    pub stroke: bool,
31    /// Even-odd fill (holes in donuts and frames).
32    pub even_odd: bool,
33}
34
35/// A draggable adjust handle (the yellow dot). `pos = origin + dir * value`; dragging projects
36/// the pointer onto `dir`.
37#[derive(Clone, Debug, PartialEq)]
38pub struct Handle {
39    pub index: usize,
40    pub pos: Point,
41    pub origin: Point,
42    pub dir: Vec2,
43    pub min: f64,
44    pub max: f64,
45}
46
47impl Handle {
48    fn new(index: usize, origin: Point, dir: Vec2, value: f64, min: f64, max: f64) -> Self {
49        Handle {
50            index,
51            pos: origin + dir * value,
52            origin,
53            dir,
54            min,
55            max,
56        }
57    }
58    /// The adjust value for a pointer at `p` (shape-local space).
59    pub fn value_at(&self, p: Point) -> f64 {
60        let l2 = self.dir.hypot2();
61        if l2 < 1e-18 {
62            return self.min;
63        }
64        let v = (p - self.origin).dot(self.dir) / l2;
65        if !v.is_finite() {
66            return self.min;
67        }
68        v.clamp(self.min.min(self.max), self.max.max(self.min))
69            .round()
70    }
71}
72
73#[derive(Clone, Debug)]
74pub struct Geometry {
75    pub paths: Vec<SubPath>,
76    /// Where text goes, in shape-local space.
77    pub text_rect: Rect,
78    pub handles: Vec<Handle>,
79    /// Connector glue points, shape-local.
80    pub sites: Vec<Point>,
81}
82
83impl Geometry {
84    /// The union outline used for hit testing and selection (all filled or stroked sub-paths).
85    pub fn outline(&self) -> BezPath {
86        let mut p = BezPath::new();
87        for s in &self.paths {
88            p.extend(s.path.iter());
89        }
90        p
91    }
92    /// Whether nothing is filled (lines, connectors, arcs, brackets).
93    pub fn is_open(&self) -> bool {
94        self.paths.iter().all(|s| s.fill == FillMode::None)
95    }
96}
97
98/// Gallery category, as in the Shapes menu.
99#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub enum Category {
102    Lines,
103    Rectangles,
104    BasicShapes,
105    BlockArrows,
106    EquationShapes,
107    Flowchart,
108    StarsAndBanners,
109    Callouts,
110    ActionButtons,
111}
112
113impl Category {
114    pub const ALL: [Category; 9] = [
115        Category::Lines,
116        Category::Rectangles,
117        Category::BasicShapes,
118        Category::BlockArrows,
119        Category::EquationShapes,
120        Category::Flowchart,
121        Category::StarsAndBanners,
122        Category::Callouts,
123        Category::ActionButtons,
124    ];
125    pub fn label(self) -> &'static str {
126        match self {
127            Category::Lines => "Lines",
128            Category::Rectangles => "Rectangles",
129            Category::BasicShapes => "Basic Shapes",
130            Category::BlockArrows => "Block Arrows",
131            Category::EquationShapes => "Equation Shapes",
132            Category::Flowchart => "Flowchart",
133            Category::StarsAndBanners => "Stars and Banners",
134            Category::Callouts => "Callouts",
135            Category::ActionButtons => "Action Buttons",
136        }
137    }
138}
139
140pub struct PresetInfo {
141    pub name: &'static str,
142    pub label: &'static str,
143    pub category: Category,
144    /// Default adjust values.
145    pub defaults: &'static [f64],
146}
147
148macro_rules! p {
149    ($n:literal, $l:literal, $c:ident, [$($d:expr),*]) => {
150        PresetInfo { name: $n, label: $l, category: Category::$c, defaults: &[$($d as f64),*] }
151    };
152}
153
154/// Every preset we generate, in gallery order.
155pub static CATALOG: &[PresetInfo] = &[
156    p!("line", "Line", Lines, []),
157    p!("straightConnector1", "Arrow", Lines, []),
158    p!("bentConnector3", "Connector: Elbow", Lines, [50000]),
159    p!("curvedConnector3", "Connector: Curved", Lines, [50000]),
160    p!("bentConnector2", "Connector: Elbow (one bend)", Lines, []),
161    p!("lineInv", "Line: Rising", Lines, []),
162    p!("rect", "Rectangle", Rectangles, []),
163    p!(
164        "roundRect",
165        "Rectangle: Rounded Corners",
166        Rectangles,
167        [16667]
168    ),
169    p!(
170        "snip1Rect",
171        "Rectangle: Single Corner Snipped",
172        Rectangles,
173        [16667]
174    ),
175    p!(
176        "snip2SameRect",
177        "Rectangle: Top Corners Snipped",
178        Rectangles,
179        [16667, 0]
180    ),
181    p!(
182        "snip2DiagRect",
183        "Rectangle: Diagonal Corners Snipped",
184        Rectangles,
185        [0, 16667]
186    ),
187    p!(
188        "snipRoundRect",
189        "Rectangle: Top Corners One Rounded and One Snipped",
190        Rectangles,
191        [16667, 16667]
192    ),
193    p!(
194        "round1Rect",
195        "Rectangle: Single Corner Rounded",
196        Rectangles,
197        [16667]
198    ),
199    p!(
200        "round2SameRect",
201        "Rectangle: Top Corners Rounded",
202        Rectangles,
203        [16667, 0]
204    ),
205    p!(
206        "round2DiagRect",
207        "Rectangle: Diagonal Corners Rounded",
208        Rectangles,
209        [16667, 0]
210    ),
211    p!("textBox", "Text Box", BasicShapes, []),
212    p!("ellipse", "Oval", BasicShapes, []),
213    p!("triangle", "Isosceles Triangle", BasicShapes, [50000]),
214    p!("rtTriangle", "Right Triangle", BasicShapes, []),
215    p!("parallelogram", "Parallelogram", BasicShapes, [25000]),
216    p!("trapezoid", "Trapezoid", BasicShapes, [25000]),
217    p!(
218        "nonIsoscelesTrapezoid",
219        "Trapezoid: Uneven Sides",
220        BasicShapes,
221        [25000, 25000]
222    ),
223    p!("diamond", "Diamond", BasicShapes, []),
224    p!("pentagon", "Regular Pentagon", BasicShapes, []),
225    p!("hexagon", "Hexagon", BasicShapes, [25000]),
226    p!("heptagon", "Heptagon", BasicShapes, []),
227    p!("octagon", "Octagon", BasicShapes, [29289]),
228    p!("decagon", "Decagon", BasicShapes, []),
229    p!("dodecagon", "Dodecagon", BasicShapes, []),
230    p!("pie", "Partial Circle", BasicShapes, [0, 16200000]),
231    p!("pieWedge", "Quarter Wedge", BasicShapes, []),
232    p!("chord", "Chord", BasicShapes, [2700000, 16200000]),
233    p!("teardrop", "Teardrop", BasicShapes, [100000]),
234    p!("frame", "Frame", BasicShapes, [12500]),
235    p!("cornerTabs", "Corner Tabs: Triangles", BasicShapes, []),
236    p!("squareTabs", "Corner Tabs: Squares", BasicShapes, []),
237    p!(
238        "plaqueTabs",
239        "Corner Tabs: Quarter Circles",
240        BasicShapes,
241        []
242    ),
243    p!("halfFrame", "Half Frame", BasicShapes, [33333, 33333]),
244    p!("corner", "L-Shape", BasicShapes, [50000, 50000]),
245    p!("diagStripe", "Diagonal Stripe", BasicShapes, [50000]),
246    p!("plus", "Cross", BasicShapes, [25000]),
247    p!("plaque", "Plaque", BasicShapes, [16667]),
248    p!("can", "Cylinder", BasicShapes, [25000]),
249    p!("cube", "Cube", BasicShapes, [25000]),
250    p!("bevel", "Bevel", BasicShapes, [12500]),
251    p!("donut", "Donut", BasicShapes, [25000]),
252    p!("noSmoking", "\"Not Allowed\" Symbol", BasicShapes, [18750]),
253    p!("blockArc", "Block Arc", BasicShapes, [10800000, 0, 25000]),
254    p!("foldedCorner", "Folded Corner", BasicShapes, [16667]),
255    p!("smileyFace", "Smiley Face", BasicShapes, [4653]),
256    p!("heart", "Heart", BasicShapes, []),
257    p!("lightningBolt", "Lightning Bolt", BasicShapes, []),
258    p!("sun", "Sun", BasicShapes, [25000]),
259    p!("moon", "Moon", BasicShapes, [50000]),
260    p!("cloud", "Cloud", BasicShapes, []),
261    p!("gear6", "Gear: 6 Teeth", BasicShapes, [15000, 3526]),
262    p!("gear9", "Gear: 9 Teeth", BasicShapes, [10000, 1763]),
263    p!("funnel", "Funnel", BasicShapes, []),
264    p!("arc", "Arc", BasicShapes, [16200000, 0]),
265    p!("bracketPair", "Double Bracket", BasicShapes, [16667]),
266    p!("bracePair", "Double Brace", BasicShapes, [8333]),
267    p!("leftBracket", "Left Bracket", BasicShapes, [8333]),
268    p!("rightBracket", "Right Bracket", BasicShapes, [8333]),
269    p!("leftBrace", "Left Brace", BasicShapes, [8333, 50000]),
270    p!("rightBrace", "Right Brace", BasicShapes, [8333, 50000]),
271    p!("chartX", "Chart Marker: Cross Lines", BasicShapes, []),
272    p!("chartStar", "Chart Marker: Star Lines", BasicShapes, []),
273    p!("chartPlus", "Chart Marker: Plus Lines", BasicShapes, []),
274    p!("rightArrow", "Arrow: Right", BlockArrows, [50000, 50000]),
275    p!("leftArrow", "Arrow: Left", BlockArrows, [50000, 50000]),
276    p!("upArrow", "Arrow: Up", BlockArrows, [50000, 50000]),
277    p!("downArrow", "Arrow: Down", BlockArrows, [50000, 50000]),
278    p!(
279        "leftRightArrow",
280        "Arrow: Left-Right",
281        BlockArrows,
282        [50000, 50000]
283    ),
284    p!("upDownArrow", "Arrow: Up-Down", BlockArrows, [50000, 50000]),
285    p!(
286        "quadArrow",
287        "Arrow: Quad",
288        BlockArrows,
289        [22500, 22500, 22500]
290    ),
291    p!(
292        "leftRightUpArrow",
293        "Arrow: Left-Right-Up",
294        BlockArrows,
295        [25000, 25000, 25000]
296    ),
297    p!(
298        "bentArrow",
299        "Arrow: Bent",
300        BlockArrows,
301        [25000, 25000, 25000, 43750]
302    ),
303    p!(
304        "uturnArrow",
305        "Arrow: U-Turn",
306        BlockArrows,
307        [25000, 25000, 25000, 43750, 75000]
308    ),
309    p!(
310        "curvedRightArrow",
311        "Arrow: Curved Right",
312        BlockArrows,
313        [25000, 50000, 25000]
314    ),
315    p!(
316        "curvedLeftArrow",
317        "Arrow: Curved Left",
318        BlockArrows,
319        [25000, 50000, 25000]
320    ),
321    p!(
322        "curvedUpArrow",
323        "Arrow: Curved Up",
324        BlockArrows,
325        [25000, 50000, 25000]
326    ),
327    p!(
328        "curvedDownArrow",
329        "Arrow: Curved Down",
330        BlockArrows,
331        [25000, 50000, 25000]
332    ),
333    p!(
334        "leftUpArrow",
335        "Arrow: Left-Up",
336        BlockArrows,
337        [25000, 25000, 25000]
338    ),
339    p!(
340        "bentUpArrow",
341        "Arrow: Bent-Up",
342        BlockArrows,
343        [25000, 25000, 25000]
344    ),
345    p!(
346        "stripedRightArrow",
347        "Arrow: Striped Right",
348        BlockArrows,
349        [50000, 50000]
350    ),
351    p!(
352        "notchedRightArrow",
353        "Arrow: Notched Right",
354        BlockArrows,
355        [50000, 50000]
356    ),
357    p!("homePlate", "Arrow: Pentagon", BlockArrows, [50000]),
358    p!("chevron", "Arrow: Chevron", BlockArrows, [50000]),
359    p!(
360        "circularArrow",
361        "Arrow: Circular",
362        BlockArrows,
363        [12500, 1142319, 20457681, 10800000, 12500]
364    ),
365    p!(
366        "leftCircularArrow",
367        "Arrow: Circular Counter-clockwise",
368        BlockArrows,
369        [12500, -1142319, 1142319, 10800000, 12500]
370    ),
371    p!(
372        "leftRightCircularArrow",
373        "Arrow: Circular Two-headed",
374        BlockArrows,
375        [12500, 1142319, 20457681, 11942319, 12500]
376    ),
377    p!("swooshArrow", "Arrow: Swoosh", BlockArrows, [25000, 16667]),
378    p!(
379        "rightArrowCallout",
380        "Callout: Right Arrow",
381        BlockArrows,
382        [25000, 25000, 25000, 64977]
383    ),
384    p!(
385        "downArrowCallout",
386        "Callout: Down Arrow",
387        BlockArrows,
388        [25000, 25000, 25000, 64977]
389    ),
390    p!(
391        "leftArrowCallout",
392        "Callout: Left Arrow",
393        BlockArrows,
394        [25000, 25000, 25000, 64977]
395    ),
396    p!(
397        "upArrowCallout",
398        "Callout: Up Arrow",
399        BlockArrows,
400        [25000, 25000, 25000, 64977]
401    ),
402    p!(
403        "leftRightArrowCallout",
404        "Callout: Left-Right Arrow",
405        BlockArrows,
406        [25000, 25000, 25000, 48123]
407    ),
408    p!(
409        "upDownArrowCallout",
410        "Callout: Up-Down Arrow",
411        BlockArrows,
412        [25000, 25000, 25000, 48123]
413    ),
414    p!(
415        "quadArrowCallout",
416        "Callout: Quad Arrow",
417        BlockArrows,
418        [18515, 18515, 18515, 48123]
419    ),
420    p!("mathPlus", "Plus Sign", EquationShapes, [23520]),
421    p!("mathMinus", "Minus Sign", EquationShapes, [23520]),
422    p!(
423        "mathMultiply",
424        "Multiplication Sign",
425        EquationShapes,
426        [23520]
427    ),
428    p!(
429        "mathDivide",
430        "Division Sign",
431        EquationShapes,
432        [23520, 5880, 11760]
433    ),
434    p!("mathEqual", "Equals", EquationShapes, [23520, 11760]),
435    p!(
436        "mathNotEqual",
437        "Not Equal",
438        EquationShapes,
439        [23520, 6600000, 11760]
440    ),
441    p!("flowChartProcess", "Flowchart: Process", Flowchart, []),
442    p!(
443        "flowChartAlternateProcess",
444        "Flowchart: Alternate Process",
445        Flowchart,
446        []
447    ),
448    p!("flowChartDecision", "Flowchart: Decision", Flowchart, []),
449    p!("flowChartInputOutput", "Flowchart: Data", Flowchart, []),
450    p!(
451        "flowChartPredefinedProcess",
452        "Flowchart: Predefined Process",
453        Flowchart,
454        []
455    ),
456    p!(
457        "flowChartInternalStorage",
458        "Flowchart: Internal Storage",
459        Flowchart,
460        []
461    ),
462    p!("flowChartDocument", "Flowchart: Document", Flowchart, []),
463    p!(
464        "flowChartMultidocument",
465        "Flowchart: Multidocument",
466        Flowchart,
467        []
468    ),
469    p!(
470        "flowChartTerminator",
471        "Flowchart: Terminator",
472        Flowchart,
473        []
474    ),
475    p!(
476        "flowChartPreparation",
477        "Flowchart: Preparation",
478        Flowchart,
479        []
480    ),
481    p!(
482        "flowChartManualInput",
483        "Flowchart: Manual Input",
484        Flowchart,
485        []
486    ),
487    p!(
488        "flowChartManualOperation",
489        "Flowchart: Manual Operation",
490        Flowchart,
491        []
492    ),
493    p!("flowChartConnector", "Flowchart: Connector", Flowchart, []),
494    p!(
495        "flowChartOffpageConnector",
496        "Flowchart: Off-page Connector",
497        Flowchart,
498        []
499    ),
500    p!("flowChartPunchedCard", "Flowchart: Card", Flowchart, []),
501    p!(
502        "flowChartPunchedTape",
503        "Flowchart: Punched Tape",
504        Flowchart,
505        []
506    ),
507    p!(
508        "flowChartSummingJunction",
509        "Flowchart: Summing Junction",
510        Flowchart,
511        []
512    ),
513    p!("flowChartOr", "Flowchart: Or", Flowchart, []),
514    p!("flowChartCollate", "Flowchart: Collate", Flowchart, []),
515    p!("flowChartSort", "Flowchart: Sort", Flowchart, []),
516    p!("flowChartExtract", "Flowchart: Extract", Flowchart, []),
517    p!("flowChartMerge", "Flowchart: Merge", Flowchart, []),
518    p!(
519        "flowChartOnlineStorage",
520        "Flowchart: Stored Data",
521        Flowchart,
522        []
523    ),
524    p!("flowChartDelay", "Flowchart: Delay", Flowchart, []),
525    p!(
526        "flowChartMagneticTape",
527        "Flowchart: Sequential Access Storage",
528        Flowchart,
529        []
530    ),
531    p!(
532        "flowChartMagneticDisk",
533        "Flowchart: Magnetic Disk",
534        Flowchart,
535        []
536    ),
537    p!(
538        "flowChartMagneticDrum",
539        "Flowchart: Direct Access Storage",
540        Flowchart,
541        []
542    ),
543    p!("flowChartDisplay", "Flowchart: Display", Flowchart, []),
544    p!(
545        "flowChartOfflineStorage",
546        "Flowchart: Offline Storage",
547        Flowchart,
548        []
549    ),
550    p!("irregularSeal1", "Explosion: 8 Points", StarsAndBanners, []),
551    p!(
552        "irregularSeal2",
553        "Explosion: 14 Points",
554        StarsAndBanners,
555        []
556    ),
557    p!("star4", "Star: 4 Points", StarsAndBanners, [12500]),
558    p!("star5", "Star: 5 Points", StarsAndBanners, [19098]),
559    p!("star6", "Star: 6 Points", StarsAndBanners, [28868]),
560    p!("star7", "Star: 7 Points", StarsAndBanners, [34601]),
561    p!("star8", "Star: 8 Points", StarsAndBanners, [37500]),
562    p!("star10", "Star: 10 Points", StarsAndBanners, [42533]),
563    p!("star12", "Star: 12 Points", StarsAndBanners, [37500]),
564    p!("star16", "Star: 16 Points", StarsAndBanners, [37500]),
565    p!("star24", "Star: 24 Points", StarsAndBanners, [37500]),
566    p!("star32", "Star: 32 Points", StarsAndBanners, [37500]),
567    p!(
568        "ribbon2",
569        "Ribbon: Tilted Up",
570        StarsAndBanners,
571        [16667, 50000]
572    ),
573    p!(
574        "ribbon",
575        "Ribbon: Tilted Down",
576        StarsAndBanners,
577        [16667, 50000]
578    ),
579    p!(
580        "ellipseRibbon2",
581        "Ribbon: Curved and Tilted Up",
582        StarsAndBanners,
583        [25000, 50000, 12500]
584    ),
585    p!(
586        "ellipseRibbon",
587        "Ribbon: Curved and Tilted Down",
588        StarsAndBanners,
589        [25000, 50000, 12500]
590    ),
591    p!(
592        "leftRightRibbon",
593        "Ribbon: Left-Right Fold",
594        StarsAndBanners,
595        [50000, 50000, 16667]
596    ),
597    p!(
598        "verticalScroll",
599        "Scroll: Vertical",
600        StarsAndBanners,
601        [12500]
602    ),
603    p!(
604        "horizontalScroll",
605        "Scroll: Horizontal",
606        StarsAndBanners,
607        [12500]
608    ),
609    p!("wave", "Wave", StarsAndBanners, [12500, 0]),
610    p!("doubleWave", "Double Wave", StarsAndBanners, [6250, 0]),
611    p!(
612        "wedgeRectCallout",
613        "Speech Bubble: Rectangle",
614        Callouts,
615        [-20833, 62500]
616    ),
617    p!(
618        "wedgeRoundRectCallout",
619        "Speech Bubble: Rectangle with Corners Rounded",
620        Callouts,
621        [-20833, 62500, 16667]
622    ),
623    p!(
624        "wedgeEllipseCallout",
625        "Speech Bubble: Oval",
626        Callouts,
627        [-20833, 62500]
628    ),
629    p!(
630        "cloudCallout",
631        "Thought Bubble: Cloud",
632        Callouts,
633        [-20833, 62500]
634    ),
635    p!(
636        "borderCallout1",
637        "Callout: Line",
638        Callouts,
639        [18750, -8333, 112500, -38333]
640    ),
641    p!(
642        "borderCallout2",
643        "Callout: Bent Line",
644        Callouts,
645        [18750, -8333, 18750, -16667, 112500, -46667]
646    ),
647    p!(
648        "borderCallout3",
649        "Callout: Double Bent Line",
650        Callouts,
651        [18750, -8333, 18750, -16667, 100000, -16667, 112963, -8333]
652    ),
653    p!(
654        "accentCallout1",
655        "Callout: Line with Accent Bar",
656        Callouts,
657        [18750, -8333, 112500, -38333]
658    ),
659    p!(
660        "accentCallout2",
661        "Callout: Bent Line with Accent Bar",
662        Callouts,
663        [18750, -8333, 18750, -16667, 112500, -46667]
664    ),
665    p!(
666        "accentCallout3",
667        "Callout: Double Bent Line with Accent Bar",
668        Callouts,
669        [18750, -8333, 18750, -16667, 100000, -16667, 112963, -8333]
670    ),
671    p!(
672        "callout1",
673        "Callout: Line with No Border",
674        Callouts,
675        [18750, -8333, 112500, -38333]
676    ),
677    p!(
678        "callout2",
679        "Callout: Bent Line with No Border",
680        Callouts,
681        [18750, -8333, 18750, -16667, 112500, -46667]
682    ),
683    p!(
684        "callout3",
685        "Callout: Double Bent Line with No Border",
686        Callouts,
687        [18750, -8333, 18750, -16667, 100000, -16667, 112963, -8333]
688    ),
689    p!(
690        "accentBorderCallout1",
691        "Callout: Line with Border and Accent Bar",
692        Callouts,
693        [18750, -8333, 112500, -38333]
694    ),
695    p!(
696        "accentBorderCallout2",
697        "Callout: Bent Line with Border and Accent Bar",
698        Callouts,
699        [18750, -8333, 18750, -16667, 112500, -46667]
700    ),
701    p!(
702        "accentBorderCallout3",
703        "Callout: Double Bent Line with Border and Accent Bar",
704        Callouts,
705        [18750, -8333, 18750, -16667, 100000, -16667, 112963, -8333]
706    ),
707    p!(
708        "actionButtonBackPrevious",
709        "Action Button: Go Back or Previous",
710        ActionButtons,
711        []
712    ),
713    p!(
714        "actionButtonForwardNext",
715        "Action Button: Go Forward or Next",
716        ActionButtons,
717        []
718    ),
719    p!(
720        "actionButtonBeginning",
721        "Action Button: Go to Beginning",
722        ActionButtons,
723        []
724    ),
725    p!(
726        "actionButtonEnd",
727        "Action Button: Go to End",
728        ActionButtons,
729        []
730    ),
731    p!(
732        "actionButtonHome",
733        "Action Button: Go Home",
734        ActionButtons,
735        []
736    ),
737    p!(
738        "actionButtonInformation",
739        "Action Button: Get Information",
740        ActionButtons,
741        []
742    ),
743    p!(
744        "actionButtonReturn",
745        "Action Button: Go Back",
746        ActionButtons,
747        []
748    ),
749    p!(
750        "actionButtonMovie",
751        "Action Button: Video",
752        ActionButtons,
753        []
754    ),
755    p!(
756        "actionButtonDocument",
757        "Action Button: Document",
758        ActionButtons,
759        []
760    ),
761    p!(
762        "actionButtonSound",
763        "Action Button: Sound",
764        ActionButtons,
765        []
766    ),
767    p!("actionButtonHelp", "Action Button: Help", ActionButtons, []),
768    p!(
769        "actionButtonBlank",
770        "Action Button: Custom",
771        ActionButtons,
772        []
773    ),
774];
775
776pub fn info(name: &str) -> Option<&'static PresetInfo> {
777    CATALOG.iter().find(|p| p.name == name)
778}
779
780pub fn is_line_like(name: &str) -> bool {
781    matches!(
782        name,
783        "line"
784            | "straightConnector1"
785            | "bentConnector2"
786            | "bentConnector3"
787            | "curvedConnector3"
788            | "arc"
789    ) || name.starts_with("bentConnector")
790        || name.starts_with("curvedConnector")
791}
792
793// ---------- path builder ----------
794
795struct Pb {
796    p: BezPath,
797    cur: Point,
798}
799
800impl Pb {
801    fn new() -> Self {
802        Pb {
803            p: BezPath::new(),
804            cur: Point::ZERO,
805        }
806    }
807    fn m(&mut self, x: f64, y: f64) -> &mut Self {
808        self.cur = Point::new(x, y);
809        self.p.move_to(self.cur);
810        self
811    }
812    fn l(&mut self, x: f64, y: f64) -> &mut Self {
813        self.cur = Point::new(x, y);
814        self.p.line_to(self.cur);
815        self
816    }
817    fn c(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x: f64, y: f64) -> &mut Self {
818        self.cur = Point::new(x, y);
819        self.p
820            .curve_to(Point::new(x1, y1), Point::new(x2, y2), self.cur);
821        self
822    }
823    fn q(&mut self, x1: f64, y1: f64, x: f64, y: f64) -> &mut Self {
824        self.cur = Point::new(x, y);
825        self.p.quad_to(Point::new(x1, y1), self.cur);
826        self
827    }
828    /// Elliptic arc continuing from the current point, which lies on the ellipse at parametric
829    /// angle `st` (degrees); sweeps `sw` degrees (positive = clockwise on screen).
830    fn arc(&mut self, wr: f64, hr: f64, st: f64, sw: f64) -> &mut Self {
831        let (s, e) = (st.to_radians(), (st + sw).to_radians());
832        let c = Point::new(self.cur.x - wr * s.cos(), self.cur.y - hr * s.sin());
833        let a = Arc::new(c, Vec2::new(wr.abs(), hr.abs()), s, e - s, 0.0);
834        a.to_cubic_beziers(0.05, |p1, p2, p| {
835            self.p.curve_to(p1, p2, p);
836        });
837        self.cur = Point::new(c.x + wr * e.cos(), c.y + hr * e.sin());
838        self
839    }
840    fn z(&mut self) -> &mut Self {
841        self.p.close_path();
842        self
843    }
844    fn poly(&mut self, pts: &[(f64, f64)]) -> &mut Self {
845        for (i, (x, y)) in pts.iter().enumerate() {
846            if i == 0 {
847                self.m(*x, *y);
848            } else {
849                self.l(*x, *y);
850            }
851        }
852        self.z()
853    }
854    fn ellipse(&mut self, cx: f64, cy: f64, rx: f64, ry: f64) -> &mut Self {
855        self.m(cx + rx, cy);
856        self.arc(rx, ry, 0.0, 360.0);
857        self.z()
858    }
859    fn take(&mut self) -> BezPath {
860        std::mem::take(&mut self.p)
861    }
862}
863
864fn fill(p: BezPath) -> SubPath {
865    SubPath {
866        path: p,
867        fill: FillMode::Norm,
868        stroke: true,
869        even_odd: false,
870    }
871}
872fn fill_eo(p: BezPath) -> SubPath {
873    SubPath {
874        path: p,
875        fill: FillMode::Norm,
876        stroke: true,
877        even_odd: true,
878    }
879}
880fn shade(p: BezPath, m: FillMode) -> SubPath {
881    SubPath {
882        path: p,
883        fill: m,
884        stroke: true,
885        even_odd: false,
886    }
887}
888fn stroke_only(p: BezPath) -> SubPath {
889    SubPath {
890        path: p,
891        fill: FillMode::None,
892        stroke: true,
893        even_odd: false,
894    }
895}
896fn fill_only(p: BezPath) -> SubPath {
897    SubPath {
898        path: p,
899        fill: FillMode::Norm,
900        stroke: false,
901        even_odd: false,
902    }
903}
904
905/// Parametric angle on an rx×ry ellipse for a visual angle `deg` (file angles are visual).
906fn vis(deg: f64, rx: f64, ry: f64) -> f64 {
907    let t = deg.to_radians();
908    (rx * t.sin()).atan2(ry * t.cos()).to_degrees()
909}
910
911fn polygon_regular(n: usize, w: f64, h: f64, phase: f64) -> Vec<(f64, f64)> {
912    let pts: Vec<(f64, f64)> = (0..n)
913        .map(|i| {
914            let a = (phase + 360.0 * i as f64 / n as f64).to_radians();
915            (a.cos(), a.sin())
916        })
917        .collect();
918    normalize(&pts, w, h)
919}
920
921/// Scale `pts` so their bounding box fills 0..w × 0..h.
922fn normalize(pts: &[(f64, f64)], w: f64, h: f64) -> Vec<(f64, f64)> {
923    let (mut x0, mut y0, mut x1, mut y1) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
924    for (x, y) in pts {
925        x0 = x0.min(*x);
926        y0 = y0.min(*y);
927        x1 = x1.max(*x);
928        y1 = y1.max(*y);
929    }
930    let sx = if x1 - x0 > 1e-12 { w / (x1 - x0) } else { 0.0 };
931    let sy = if y1 - y0 > 1e-12 { h / (y1 - y0) } else { 0.0 };
932    pts.iter()
933        .map(|(x, y)| ((x - x0) * sx, (y - y0) * sy))
934        .collect()
935}
936
937fn star(n: usize, w: f64, h: f64, inner: f64, normalized: bool) -> Vec<(f64, f64)> {
938    let mut pts = Vec::with_capacity(n * 2);
939    for i in 0..n * 2 {
940        let a = (-90.0 + 180.0 * i as f64 / n as f64).to_radians();
941        let r = if i % 2 == 0 { 1.0 } else { inner };
942        pts.push((r * a.cos(), r * a.sin()));
943    }
944    if normalized {
945        normalize(&pts, w, h)
946    } else {
947        pts.iter()
948            .map(|(x, y)| (w / 2.0 + x * w / 2.0, h / 2.0 + y * h / 2.0))
949            .collect()
950    }
951}
952
953#[derive(Clone, Copy)]
954enum Corner {
955    Square,
956    Round(f64),
957    Snip(f64),
958}
959
960/// A rectangle with each corner (TL, TR, BR, BL) square, rounded or snipped.
961fn corner_rect(w: f64, h: f64, c: [Corner; 4]) -> BezPath {
962    let mut b = Pb::new();
963    let size = |k: Corner| match k {
964        Corner::Square => 0.0,
965        Corner::Round(r) | Corner::Snip(r) => r.max(0.0),
966    };
967    let s0 = size(c[0]);
968    b.m(s0, 0.0);
969    // top edge → TR
970    let s1 = size(c[1]);
971    b.l(w - s1, 0.0);
972    match c[1] {
973        Corner::Round(r) if r > 0.0 => {
974            b.arc(r, r, 270.0, 90.0);
975        }
976        Corner::Snip(_) => {
977            b.l(w, s1);
978        }
979        _ => {}
980    }
981    let s2 = size(c[2]);
982    b.l(w, h - s2);
983    match c[2] {
984        Corner::Round(r) if r > 0.0 => {
985            b.arc(r, r, 0.0, 90.0);
986        }
987        Corner::Snip(_) => {
988            b.l(w - s2, h);
989        }
990        _ => {}
991    }
992    let s3 = size(c[3]);
993    b.l(s3, h);
994    match c[3] {
995        Corner::Round(r) if r > 0.0 => {
996            b.arc(r, r, 90.0, 90.0);
997        }
998        Corner::Snip(_) => {
999            b.l(0.0, h - s3);
1000        }
1001        _ => {}
1002    }
1003    b.l(0.0, s0);
1004    match c[0] {
1005        Corner::Round(r) if r > 0.0 => {
1006            b.arc(r, r, 180.0, 90.0);
1007        }
1008        Corner::Snip(_) => {
1009            b.l(s0, 0.0);
1010        }
1011        _ => {}
1012    }
1013    b.z();
1014    b.take()
1015}
1016
1017fn mid_sites(w: f64, h: f64) -> Vec<Point> {
1018    vec![
1019        Point::new(w / 2.0, 0.0),
1020        Point::new(0.0, h / 2.0),
1021        Point::new(w / 2.0, h),
1022        Point::new(w, h / 2.0),
1023    ]
1024}
1025
1026/// Orientation for single-axis arrows built along +x.
1027#[derive(Clone, Copy)]
1028enum Dir {
1029    Right,
1030    Left,
1031    Up,
1032    Down,
1033}
1034
1035/// Map canonical (u along the arrow's length L, v across thickness T) to shape space.
1036fn orient(d: Dir, w: f64, h: f64, pts: &[(f64, f64)]) -> Vec<(f64, f64)> {
1037    pts.iter()
1038        .map(|&(u, v)| match d {
1039            Dir::Right => (u, v),
1040            Dir::Left => (w - u, v),
1041            Dir::Up => (v, h - u),
1042            Dir::Down => (v, u),
1043        })
1044        .collect()
1045}
1046
1047fn len_thick(d: Dir, w: f64, h: f64) -> (f64, f64) {
1048    match d {
1049        Dir::Right | Dir::Left => (w, h),
1050        Dir::Up | Dir::Down => (h, w),
1051    }
1052}
1053
1054fn arrow(d: Dir, w: f64, h: f64, a1: f64, a2: f64) -> Geometry {
1055    let (l, t) = len_thick(d, w, h);
1056    let ss = l.min(t);
1057    let a1 = a1.clamp(0.0, 100000.0) / 100000.0;
1058    let dx = (ss * a2.clamp(0.0, 100000.0 * l / ss.max(1e-9)) / 100000.0).min(l);
1059    let y1 = t / 2.0 - t * a1 / 2.0;
1060    let y2 = t / 2.0 + t * a1 / 2.0;
1061    let pts = [
1062        (0.0, y1),
1063        (l - dx, y1),
1064        (l - dx, 0.0),
1065        (l, t / 2.0),
1066        (l - dx, t),
1067        (l - dx, y2),
1068        (0.0, y2),
1069    ];
1070    let pts = orient(d, w, h, &pts);
1071    let tr = orient(
1072        d,
1073        w,
1074        h,
1075        &[(0.0, y1), (l - dx + dx * (y1 / (t / 2.0).max(1e-9)), y2)],
1076    );
1077    let mut text = Rect::from_points(tr[0], tr[1]);
1078    text = text.abs();
1079    let mut g = Geometry {
1080        paths: vec![fill(Pb::new().poly(&pts).take())],
1081        text_rect: text,
1082        handles: vec![],
1083        sites: mid_sites(w, h),
1084    };
1085    // handle 0: shaft thickness on the head base; handle 1: head length.
1086    let o = orient(d, w, h, &[(l - dx, t / 2.0), (l, t / 2.0)]);
1087    let (o0, o1) = (Point::new(o[0].0, o[0].1), Point::new(o[1].0, o[1].1));
1088    let across = match d {
1089        Dir::Right | Dir::Left => Vec2::new(0.0, -t / 2.0 / 100000.0),
1090        Dir::Up | Dir::Down => Vec2::new(-t / 2.0 / 100000.0, 0.0),
1091    };
1092    let along = (o0 - o1) * (ss / 100000.0 / dx.max(1e-9));
1093    g.handles
1094        .push(Handle::new(0, o0, across, a1 * 100000.0, 0.0, 100000.0));
1095    if dx > 1e-9 {
1096        g.handles.push(Handle::new(
1097            1,
1098            o1,
1099            along,
1100            a2,
1101            0.0,
1102            100000.0 * l / ss.max(1e-9),
1103        ));
1104    }
1105    g
1106}
1107
1108fn double_arrow(vertical: bool, w: f64, h: f64, a1: f64, a2: f64) -> Geometry {
1109    let (l, t) = if vertical { (h, w) } else { (w, h) };
1110    let ss = l.min(t);
1111    let a1 = a1.clamp(0.0, 100000.0) / 100000.0;
1112    let dx = (ss * a2.max(0.0) / 100000.0).min(l / 2.0);
1113    let y1 = t / 2.0 - t * a1 / 2.0;
1114    let y2 = t / 2.0 + t * a1 / 2.0;
1115    let pts = [
1116        (0.0, t / 2.0),
1117        (dx, 0.0),
1118        (dx, y1),
1119        (l - dx, y1),
1120        (l - dx, 0.0),
1121        (l, t / 2.0),
1122        (l - dx, t),
1123        (l - dx, y2),
1124        (dx, y2),
1125        (dx, t),
1126    ];
1127    let pts = orient(if vertical { Dir::Down } else { Dir::Right }, w, h, &pts);
1128    let r = if vertical {
1129        Rect::new(y1, dx, y2, h - dx)
1130    } else {
1131        Rect::new(dx, y1, w - dx, y2)
1132    };
1133    Geometry {
1134        paths: vec![fill(Pb::new().poly(&pts).take())],
1135        text_rect: r,
1136        handles: vec![],
1137        sites: mid_sites(w, h),
1138    }
1139}
1140
1141fn simple(path: BezPath, text: Rect, w: f64, h: f64) -> Geometry {
1142    Geometry {
1143        paths: vec![fill(path)],
1144        text_rect: text,
1145        handles: vec![],
1146        sites: mid_sites(w, h),
1147    }
1148}
1149
1150/// Builds the geometry for preset `name` at size `w`×`h` with adjust values `adj` (missing
1151/// values use the preset's defaults). Unknown names return `None`; the caller falls back to `rect`.
1152pub fn build(name: &str, w: f64, h: f64, adj: &[f64]) -> Option<Geometry> {
1153    if !(w.is_finite() && h.is_finite()) {
1154        return None;
1155    }
1156    let w = w.max(0.0);
1157    let h = h.max(0.0);
1158    let defaults = info(name).map(|i| i.defaults).unwrap_or(&[]);
1159    let a = |i: usize| -> f64 {
1160        let v = adj
1161            .get(i)
1162            .copied()
1163            .or_else(|| defaults.get(i).copied())
1164            .unwrap_or(0.0);
1165        if v.is_finite() { v } else { 0.0 }
1166    };
1167    let ss = w.min(h);
1168    let full = Rect::new(0.0, 0.0, w, h);
1169    let mut b = Pb::new();
1170    let g = match name {
1171        "rect" | "textBox" | "flowChartProcess" => {
1172            simple(corner_rect(w, h, [Corner::Square; 4]), full, w, h)
1173        }
1174        "actionButtonBlank"
1175        | "actionButtonBackPrevious"
1176        | "actionButtonForwardNext"
1177        | "actionButtonBeginning"
1178        | "actionButtonEnd"
1179        | "actionButtonHome"
1180        | "actionButtonInformation"
1181        | "actionButtonReturn"
1182        | "actionButtonMovie"
1183        | "actionButtonDocument"
1184        | "actionButtonSound"
1185        | "actionButtonHelp" => action_button(name, w, h),
1186        "roundRect" => {
1187            let v = a(0).clamp(0.0, 50000.0);
1188            let r = ss * v / 100000.0;
1189            let mut g = simple(
1190                corner_rect(w, h, [Corner::Round(r); 4]),
1191                full.inset(-r * 0.29289),
1192                w,
1193                h,
1194            );
1195            g.handles.push(Handle::new(
1196                0,
1197                Point::ZERO,
1198                Vec2::new(ss / 100000.0, 0.0),
1199                v,
1200                0.0,
1201                50000.0,
1202            ));
1203            g
1204        }
1205        "flowChartAlternateProcess" => simple(
1206            corner_rect(w, h, [Corner::Round(ss / 6.0); 4]),
1207            full.inset(-ss / 6.0 * 0.29289),
1208            w,
1209            h,
1210        ),
1211        "snip1Rect" => {
1212            let v = a(0).clamp(0.0, 50000.0);
1213            let d = ss * v / 100000.0;
1214            let mut g = simple(
1215                corner_rect(
1216                    w,
1217                    h,
1218                    [
1219                        Corner::Square,
1220                        Corner::Snip(d),
1221                        Corner::Square,
1222                        Corner::Square,
1223                    ],
1224                ),
1225                Rect::new(0.0, d / 2.0, w - d / 2.0, h),
1226                w,
1227                h,
1228            );
1229            g.handles.push(Handle::new(
1230                0,
1231                Point::new(w, 0.0),
1232                Vec2::new(-ss / 100000.0, 0.0),
1233                v,
1234                0.0,
1235                50000.0,
1236            ));
1237            g
1238        }
1239        "snip2SameRect" => {
1240            let (d1, d2) = (
1241                ss * a(0).clamp(0.0, 50000.0) / 1e5,
1242                ss * a(1).clamp(0.0, 50000.0) / 1e5,
1243            );
1244            simple(
1245                corner_rect(
1246                    w,
1247                    h,
1248                    [
1249                        Corner::Snip(d1),
1250                        Corner::Snip(d1),
1251                        Corner::Snip(d2),
1252                        Corner::Snip(d2),
1253                    ],
1254                ),
1255                full.inset(-d1.max(d2) / 2.0),
1256                w,
1257                h,
1258            )
1259        }
1260        "snip2DiagRect" => {
1261            let (d1, d2) = (
1262                ss * a(0).clamp(0.0, 50000.0) / 1e5,
1263                ss * a(1).clamp(0.0, 50000.0) / 1e5,
1264            );
1265            simple(
1266                corner_rect(
1267                    w,
1268                    h,
1269                    [
1270                        Corner::Snip(d1),
1271                        Corner::Snip(d2),
1272                        Corner::Snip(d1),
1273                        Corner::Snip(d2),
1274                    ],
1275                ),
1276                full.inset(-d1.max(d2) / 2.0),
1277                w,
1278                h,
1279            )
1280        }
1281        "snipRoundRect" => {
1282            let (r, d) = (
1283                ss * a(0).clamp(0.0, 50000.0) / 1e5,
1284                ss * a(1).clamp(0.0, 50000.0) / 1e5,
1285            );
1286            simple(
1287                corner_rect(
1288                    w,
1289                    h,
1290                    [
1291                        Corner::Round(r),
1292                        Corner::Snip(d),
1293                        Corner::Square,
1294                        Corner::Square,
1295                    ],
1296                ),
1297                full.inset(-r.max(d) / 2.0),
1298                w,
1299                h,
1300            )
1301        }
1302        "round1Rect" => {
1303            let r = ss * a(0).clamp(0.0, 50000.0) / 1e5;
1304            simple(
1305                corner_rect(
1306                    w,
1307                    h,
1308                    [
1309                        Corner::Square,
1310                        Corner::Round(r),
1311                        Corner::Square,
1312                        Corner::Square,
1313                    ],
1314                ),
1315                full.inset(-r * 0.29289),
1316                w,
1317                h,
1318            )
1319        }
1320        "round2SameRect" => {
1321            let (r1, r2) = (
1322                ss * a(0).clamp(0.0, 50000.0) / 1e5,
1323                ss * a(1).clamp(0.0, 50000.0) / 1e5,
1324            );
1325            simple(
1326                corner_rect(
1327                    w,
1328                    h,
1329                    [
1330                        Corner::Round(r1),
1331                        Corner::Round(r1),
1332                        Corner::Round(r2),
1333                        Corner::Round(r2),
1334                    ],
1335                ),
1336                full.inset(-r1.max(r2) * 0.29289),
1337                w,
1338                h,
1339            )
1340        }
1341        "round2DiagRect" => {
1342            let (r1, r2) = (
1343                ss * a(0).clamp(0.0, 50000.0) / 1e5,
1344                ss * a(1).clamp(0.0, 50000.0) / 1e5,
1345            );
1346            simple(
1347                corner_rect(
1348                    w,
1349                    h,
1350                    [
1351                        Corner::Round(r1),
1352                        Corner::Round(r2),
1353                        Corner::Round(r1),
1354                        Corner::Round(r2),
1355                    ],
1356                ),
1357                full.inset(-r1.max(r2) * 0.29289),
1358                w,
1359                h,
1360            )
1361        }
1362        "ellipse" | "flowChartConnector" => {
1363            let mut g = simple(
1364                b.ellipse(w / 2.0, h / 2.0, w / 2.0, h / 2.0).take(),
1365                Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
1366                w,
1367                h,
1368            );
1369            g.sites = ellipse_sites(w, h);
1370            g
1371        }
1372        "triangle" | "flowChartExtract" => {
1373            let v = if name == "triangle" {
1374                a(0).clamp(0.0, 100000.0)
1375            } else {
1376                50000.0
1377            };
1378            let ax = w * v / 100000.0;
1379            let mut g = simple(
1380                b.poly(&[(0.0, h), (ax, 0.0), (w, h)]).take(),
1381                Rect::new(ax / 2.0, h / 2.0, (ax + w) / 2.0, h),
1382                w,
1383                h,
1384            );
1385            g.sites = vec![
1386                Point::new(ax, 0.0),
1387                Point::new(ax / 2.0, h / 2.0),
1388                Point::new(0.0, h),
1389                Point::new(w / 2.0, h),
1390                Point::new(w, h),
1391                Point::new((ax + w) / 2.0, h / 2.0),
1392            ];
1393            if name == "triangle" {
1394                g.handles.push(Handle::new(
1395                    0,
1396                    Point::ZERO,
1397                    Vec2::new(w / 100000.0, 0.0),
1398                    v,
1399                    0.0,
1400                    100000.0,
1401                ));
1402            }
1403            g
1404        }
1405        "flowChartMerge" => simple(
1406            b.poly(&[(0.0, 0.0), (w, 0.0), (w / 2.0, h)]).take(),
1407            Rect::new(w / 4.0, 0.0, w * 0.75, h / 2.0),
1408            w,
1409            h,
1410        ),
1411        "rtTriangle" => simple(
1412            b.poly(&[(0.0, 0.0), (0.0, h), (w, h)]).take(),
1413            Rect::new(w / 12.0, h * 7.0 / 12.0, w * 7.0 / 12.0, h * 11.0 / 12.0),
1414            w,
1415            h,
1416        ),
1417        "parallelogram" | "flowChartInputOutput" => {
1418            let v = if name == "parallelogram" {
1419                a(0).clamp(0.0, 100000.0 * w / ss.max(1e-9))
1420            } else {
1421                20000.0 * w / ss.max(1e-9)
1422            };
1423            let off = ss * v / 100000.0;
1424            let mut g = simple(
1425                b.poly(&[(0.0, h), (off, 0.0), (w, 0.0), (w - off, h)])
1426                    .take(),
1427                Rect::new(
1428                    off / 2.0 + off * 0.2,
1429                    h * 0.08,
1430                    w - off / 2.0 - off * 0.2,
1431                    h * 0.92,
1432                ),
1433                w,
1434                h,
1435            );
1436            if name == "parallelogram" {
1437                g.handles.push(Handle::new(
1438                    0,
1439                    Point::ZERO,
1440                    Vec2::new(ss / 100000.0, 0.0),
1441                    v,
1442                    0.0,
1443                    100000.0 * w / ss.max(1e-9),
1444                ));
1445            }
1446            g
1447        }
1448        "trapezoid" => {
1449            let v = a(0).clamp(0.0, 50000.0 * w / ss.max(1e-9));
1450            let off = ss * v / 100000.0;
1451            let mut g = simple(
1452                b.poly(&[(0.0, h), (off, 0.0), (w - off, 0.0), (w, h)])
1453                    .take(),
1454                Rect::new(off * 0.66, h * 0.2, w - off * 0.66, h),
1455                w,
1456                h,
1457            );
1458            g.handles.push(Handle::new(
1459                0,
1460                Point::ZERO,
1461                Vec2::new(ss / 100000.0, 0.0),
1462                v,
1463                0.0,
1464                50000.0 * w / ss.max(1e-9),
1465            ));
1466            g
1467        }
1468        "flowChartManualOperation" => simple(
1469            b.poly(&[(0.0, 0.0), (w, 0.0), (w * 0.8, h), (w * 0.2, h)])
1470                .take(),
1471            Rect::new(w * 0.2, 0.0, w * 0.8, h),
1472            w,
1473            h,
1474        ),
1475        "diamond" | "flowChartDecision" => simple(
1476            b.poly(&[(w / 2.0, 0.0), (w, h / 2.0), (w / 2.0, h), (0.0, h / 2.0)])
1477                .take(),
1478            Rect::new(w / 4.0, h / 4.0, w * 0.75, h * 0.75),
1479            w,
1480            h,
1481        ),
1482        "flowChartSort" => {
1483            let d = b
1484                .poly(&[(w / 2.0, 0.0), (w, h / 2.0), (w / 2.0, h), (0.0, h / 2.0)])
1485                .take();
1486            let line = Pb::new().m(0.0, h / 2.0).l(w, h / 2.0).take();
1487            Geometry {
1488                paths: vec![fill(d), stroke_only(line)],
1489                text_rect: Rect::new(w / 4.0, h / 4.0, w * 0.75, h * 0.75),
1490                handles: vec![],
1491                sites: mid_sites(w, h),
1492            }
1493        }
1494        "pentagon" => simple(
1495            b.poly(&polygon_regular(5, w, h, -90.0)).take(),
1496            Rect::new(w * 0.2, h * 0.27, w * 0.8, h * 0.9),
1497            w,
1498            h,
1499        ),
1500        "heptagon" => simple(
1501            b.poly(&polygon_regular(7, w, h, -90.0)).take(),
1502            Rect::new(w * 0.16, h * 0.2, w * 0.84, h * 0.86),
1503            w,
1504            h,
1505        ),
1506        "decagon" => simple(
1507            b.poly(&polygon_regular(10, w, h, 0.0)).take(),
1508            Rect::new(w * 0.1, h * 0.17, w * 0.9, h * 0.83),
1509            w,
1510            h,
1511        ),
1512        "dodecagon" => simple(
1513            b.poly(&polygon_regular(12, w, h, 15.0)).take(),
1514            Rect::new(w * 0.13, h * 0.13, w * 0.87, h * 0.87),
1515            w,
1516            h,
1517        ),
1518        "hexagon" | "flowChartPreparation" => {
1519            let v = if name == "hexagon" {
1520                a(0).clamp(0.0, 50000.0 * w / ss.max(1e-9))
1521            } else {
1522                20000.0 * w / ss.max(1e-9)
1523            };
1524            let off = ss * v / 100000.0;
1525            let mut g = simple(
1526                b.poly(&[
1527                    (0.0, h / 2.0),
1528                    (off, 0.0),
1529                    (w - off, 0.0),
1530                    (w, h / 2.0),
1531                    (w - off, h),
1532                    (off, h),
1533                ])
1534                .take(),
1535                Rect::new(off * 0.6, h * 0.14, w - off * 0.6, h * 0.86),
1536                w,
1537                h,
1538            );
1539            if name == "hexagon" {
1540                g.handles.push(Handle::new(
1541                    0,
1542                    Point::ZERO,
1543                    Vec2::new(ss / 100000.0, 0.0),
1544                    v,
1545                    0.0,
1546                    50000.0 * w / ss.max(1e-9),
1547                ));
1548            }
1549            g
1550        }
1551        "octagon" => {
1552            let v = a(0).clamp(0.0, 50000.0);
1553            let d = ss * v / 100000.0;
1554            let mut g = simple(
1555                b.poly(&[
1556                    (d, 0.0),
1557                    (w - d, 0.0),
1558                    (w, d),
1559                    (w, h - d),
1560                    (w - d, h),
1561                    (d, h),
1562                    (0.0, h - d),
1563                    (0.0, d),
1564                ])
1565                .take(),
1566                full.inset(-d / 2.0),
1567                w,
1568                h,
1569            );
1570            g.handles.push(Handle::new(
1571                0,
1572                Point::ZERO,
1573                Vec2::new(ss / 100000.0, 0.0),
1574                v,
1575                0.0,
1576                50000.0,
1577            ));
1578            g
1579        }
1580        "plus" => {
1581            let v = a(0).clamp(0.0, 50000.0);
1582            let d = ss * v / 100000.0;
1583            let mut g = simple(
1584                b.poly(&[
1585                    (d, 0.0),
1586                    (w - d, 0.0),
1587                    (w - d, d),
1588                    (w, d),
1589                    (w, h - d),
1590                    (w - d, h - d),
1591                    (w - d, h),
1592                    (d, h),
1593                    (d, h - d),
1594                    (0.0, h - d),
1595                    (0.0, d),
1596                    (d, d),
1597                ])
1598                .take(),
1599                Rect::new(0.0, d, w, h - d),
1600                w,
1601                h,
1602            );
1603            g.handles.push(Handle::new(
1604                0,
1605                Point::ZERO,
1606                Vec2::new(ss / 100000.0, 0.0),
1607                v,
1608                0.0,
1609                50000.0,
1610            ));
1611            g
1612        }
1613        "frame" => {
1614            let v = a(0).clamp(0.0, 50000.0);
1615            let t = ss * v / 100000.0;
1616            b.poly(&[(0.0, 0.0), (w, 0.0), (w, h), (0.0, h)]);
1617            b.poly(&[(t, t), (w - t, t), (w - t, h - t), (t, h - t)]);
1618            let mut g = Geometry {
1619                paths: vec![fill_eo(b.take())],
1620                text_rect: full.inset(-t),
1621                handles: vec![],
1622                sites: mid_sites(w, h),
1623            };
1624            g.handles.push(Handle::new(
1625                0,
1626                Point::ZERO,
1627                Vec2::new(ss / 100000.0, 0.0),
1628                v,
1629                0.0,
1630                50000.0,
1631            ));
1632            g
1633        }
1634        "halfFrame" => {
1635            let (t1, t2) = (
1636                ss * a(0).clamp(0.0, 50000.0) / 1e5,
1637                ss * a(1).clamp(0.0, 50000.0) / 1e5,
1638            );
1639            simple(
1640                b.poly(&[
1641                    (0.0, 0.0),
1642                    (w, 0.0),
1643                    (w - t1, t1),
1644                    (t2, t1),
1645                    (t2, h - t2),
1646                    (0.0, h),
1647                ])
1648                .take(),
1649                full,
1650                w,
1651                h,
1652            )
1653        }
1654        "corner" => {
1655            let (t1, t2) = (
1656                ss * a(0).clamp(0.0, 100000.0) / 1e5,
1657                ss * a(1).clamp(0.0, 100000.0) / 1e5,
1658            );
1659            simple(
1660                b.poly(&[
1661                    (0.0, 0.0),
1662                    (t2, 0.0),
1663                    (t2, h - t1),
1664                    (w, h - t1),
1665                    (w, h),
1666                    (0.0, h),
1667                ])
1668                .take(),
1669                Rect::new(0.0, h - t1, w, h),
1670                w,
1671                h,
1672            )
1673        }
1674        "diagStripe" => {
1675            let k = a(0).clamp(0.0, 100000.0) / 1e5;
1676            simple(
1677                b.poly(&[(0.0, h * k), (w * k, 0.0), (w, 0.0), (0.0, h)])
1678                    .take(),
1679                Rect::new(0.0, 0.0, w / 2.0, h / 2.0),
1680                w,
1681                h,
1682            )
1683        }
1684        "plaque" => {
1685            let v = a(0).clamp(0.0, 50000.0);
1686            let r = ss * v / 100000.0;
1687            b.m(0.0, r)
1688                .arc(r, r, 90.0, -90.0)
1689                .l(w - r, 0.0)
1690                .arc(r, r, 180.0, -90.0)
1691                .l(w, h - r)
1692                .arc(r, r, 270.0, -90.0)
1693                .l(r, h)
1694                .arc(r, r, 0.0, -90.0)
1695                .z();
1696            let mut g = simple(b.take(), full.inset(-r * 0.7), w, h);
1697            g.handles.push(Handle::new(
1698                0,
1699                Point::ZERO,
1700                Vec2::new(ss / 100000.0, 0.0),
1701                v,
1702                0.0,
1703                50000.0,
1704            ));
1705            g
1706        }
1707        "can" | "flowChartMagneticDisk" => {
1708            let v = if name == "can" {
1709                a(0).clamp(0.0, 50000.0 * h / ss.max(1e-9))
1710            } else {
1711                33333.0 * h / ss.max(1e-9) / 2.0
1712            };
1713            let eh = ss * v / 100000.0;
1714            let r = eh / 2.0;
1715            let body = Pb::new()
1716                .m(0.0, r)
1717                .l(0.0, h - r)
1718                .arc(w / 2.0, r, 180.0, -180.0)
1719                .l(w, r)
1720                .arc(w / 2.0, r, 0.0, -180.0)
1721                .z()
1722                .take();
1723            let top = Pb::new().ellipse(w / 2.0, r, w / 2.0, r).take();
1724            let mut g = Geometry {
1725                paths: vec![
1726                    fill(body),
1727                    shade(
1728                        top,
1729                        if name == "can" {
1730                            FillMode::Lighten
1731                        } else {
1732                            FillMode::Norm
1733                        },
1734                    ),
1735                ],
1736                text_rect: Rect::new(0.0, eh, w, h - r),
1737                handles: vec![],
1738                sites: mid_sites(w, h),
1739            };
1740            if name == "can" {
1741                g.handles.push(Handle::new(
1742                    0,
1743                    Point::new(w / 2.0, 0.0),
1744                    Vec2::new(0.0, ss / 100000.0),
1745                    v,
1746                    0.0,
1747                    50000.0 * h / ss.max(1e-9),
1748                ));
1749            }
1750            g
1751        }
1752        "flowChartMagneticDrum" => {
1753            let r = w / 6.0 / 2.0;
1754            let body = Pb::new()
1755                .m(r, 0.0)
1756                .l(w - r, 0.0)
1757                .arc(r, h / 2.0, 270.0, 180.0)
1758                .l(r, h)
1759                .arc(r, h / 2.0, 90.0, 180.0)
1760                .z()
1761                .take();
1762            let side = Pb::new()
1763                .m(w - r, 0.0)
1764                .arc(r, h / 2.0, 270.0, -180.0)
1765                .take();
1766            Geometry {
1767                paths: vec![fill(body), stroke_only(side)],
1768                text_rect: Rect::new(r, 0.0, w - 2.0 * r, h),
1769                handles: vec![],
1770                sites: mid_sites(w, h),
1771            }
1772        }
1773        "cube" => {
1774            let v = a(0).clamp(0.0, 100000.0);
1775            let d = ss * v / 100000.0;
1776            let front = Pb::new()
1777                .poly(&[(0.0, d), (w - d, d), (w - d, h), (0.0, h)])
1778                .take();
1779            let top = Pb::new()
1780                .poly(&[(0.0, d), (d, 0.0), (w, 0.0), (w - d, d)])
1781                .take();
1782            let side = Pb::new()
1783                .poly(&[(w - d, d), (w, 0.0), (w, h - d), (w - d, h)])
1784                .take();
1785            let mut g = Geometry {
1786                paths: vec![
1787                    fill(front),
1788                    shade(top, FillMode::LightenLess),
1789                    shade(side, FillMode::DarkenLess),
1790                ],
1791                text_rect: Rect::new(0.0, d, w - d, h),
1792                handles: vec![],
1793                sites: mid_sites(w, h),
1794            };
1795            g.handles.push(Handle::new(
1796                0,
1797                Point::new(0.0, 0.0),
1798                Vec2::new(0.0, ss / 100000.0),
1799                v,
1800                0.0,
1801                100000.0,
1802            ));
1803            g
1804        }
1805        "bevel" => {
1806            let v = a(0).clamp(0.0, 50000.0);
1807            let d = ss * v / 100000.0;
1808            let inner = Pb::new()
1809                .poly(&[(d, d), (w - d, d), (w - d, h - d), (d, h - d)])
1810                .take();
1811            let top = Pb::new()
1812                .poly(&[(0.0, 0.0), (w, 0.0), (w - d, d), (d, d)])
1813                .take();
1814            let left = Pb::new()
1815                .poly(&[(0.0, 0.0), (d, d), (d, h - d), (0.0, h)])
1816                .take();
1817            let right = Pb::new()
1818                .poly(&[(w, 0.0), (w, h), (w - d, h - d), (w - d, d)])
1819                .take();
1820            let bottom = Pb::new()
1821                .poly(&[(0.0, h), (d, h - d), (w - d, h - d), (w, h)])
1822                .take();
1823            let mut g = Geometry {
1824                paths: vec![
1825                    fill(inner),
1826                    shade(top, FillMode::LightenLess),
1827                    shade(left, FillMode::Lighten),
1828                    shade(right, FillMode::DarkenLess),
1829                    shade(bottom, FillMode::Darken),
1830                ],
1831                text_rect: full.inset(-d),
1832                handles: vec![],
1833                sites: mid_sites(w, h),
1834            };
1835            g.handles.push(Handle::new(
1836                0,
1837                Point::ZERO,
1838                Vec2::new(ss / 100000.0, 0.0),
1839                v,
1840                0.0,
1841                50000.0,
1842            ));
1843            g
1844        }
1845        "donut" => {
1846            let v = a(0).clamp(0.0, 50000.0);
1847            let t = ss * v / 100000.0;
1848            b.ellipse(w / 2.0, h / 2.0, w / 2.0, h / 2.0);
1849            b.ellipse(
1850                w / 2.0,
1851                h / 2.0,
1852                (w / 2.0 - t).max(0.0),
1853                (h / 2.0 - t).max(0.0),
1854            );
1855            let mut g = Geometry {
1856                paths: vec![fill_eo(b.take())],
1857                text_rect: Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
1858                handles: vec![],
1859                sites: ellipse_sites(w, h),
1860            };
1861            g.handles.push(Handle::new(
1862                0,
1863                Point::new(0.0, h / 2.0),
1864                Vec2::new(ss / 100000.0, 0.0),
1865                v,
1866                0.0,
1867                50000.0,
1868            ));
1869            g
1870        }
1871        "noSmoking" => {
1872            let v = a(0).clamp(0.0, 50000.0);
1873            let t = ss * v / 100000.0;
1874            let (rx, ry) = ((w / 2.0 - t).max(0.0), (h / 2.0 - t).max(0.0));
1875            b.ellipse(w / 2.0, h / 2.0, w / 2.0, h / 2.0);
1876            b.ellipse(w / 2.0, h / 2.0, rx, ry);
1877            let ring = b.take();
1878            let th = (h).atan2(w);
1879            let dir = Vec2::new(th.cos(), th.sin());
1880            let r = if rx > 0.0 && ry > 0.0 {
1881                rx * ry / ((ry * th.cos()).powi(2) + (rx * th.sin()).powi(2)).sqrt()
1882            } else {
1883                0.0
1884            };
1885            let perp = Vec2::new(-dir.y, dir.x) * (t / 2.0);
1886            let c = Point::new(w / 2.0, h / 2.0);
1887            let p = [
1888                c - dir * r - perp,
1889                c + dir * r - perp,
1890                c + dir * r + perp,
1891                c - dir * r + perp,
1892            ];
1893            let bar = Pb::new().poly(&p.map(|q| (q.x, q.y))).take();
1894            Geometry {
1895                paths: vec![fill_eo(ring), fill(bar)],
1896                text_rect: Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
1897                handles: vec![Handle::new(
1898                    0,
1899                    Point::new(0.0, h / 2.0),
1900                    Vec2::new(ss / 100000.0, 0.0),
1901                    v,
1902                    0.0,
1903                    50000.0,
1904                )],
1905                sites: ellipse_sites(w, h),
1906            }
1907        }
1908        "blockArc" => {
1909            let st = a(0) / 60000.0;
1910            let en = a(1) / 60000.0;
1911            let t = ss * a(2).clamp(0.0, 50000.0) / 100000.0;
1912            let mut sw = en - st;
1913            if sw <= 0.0 {
1914                sw += 360.0;
1915            }
1916            let (rx, ry) = (w / 2.0, h / 2.0);
1917            let (irx, iry) = ((rx - t).max(0.0), (ry - t).max(0.0));
1918            let s0 = vis(st, rx, ry);
1919            let e0 = vis(st + sw, rx, ry);
1920            let mut sweep = e0 - s0;
1921            if sweep <= 0.0 {
1922                sweep += 360.0;
1923            }
1924            let si = vis(st + sw, irx, iry);
1925            let ei = vis(st, irx, iry);
1926            let mut isweep = ei - si;
1927            if isweep >= 0.0 {
1928                isweep -= 360.0;
1929            }
1930            let p0 = Point::new(
1931                w / 2.0 + rx * s0.to_radians().cos(),
1932                h / 2.0 + ry * s0.to_radians().sin(),
1933            );
1934            b.m(p0.x, p0.y).arc(rx, ry, s0, sweep);
1935            let pi = Point::new(
1936                w / 2.0 + irx * si.to_radians().cos(),
1937                h / 2.0 + iry * si.to_radians().sin(),
1938            );
1939            b.l(pi.x, pi.y).arc(irx, iry, si, isweep).z();
1940            simple(b.take(), full, w, h)
1941        }
1942        "pie" | "chord" | "arc" => {
1943            let (rx, ry) = (w / 2.0, h / 2.0);
1944            let s = vis(a(0) / 60000.0, rx, ry);
1945            let e = vis(a(1) / 60000.0, rx, ry);
1946            let mut sw = e - s;
1947            if sw <= 0.0 {
1948                sw += 360.0;
1949            }
1950            let p0 = Point::new(
1951                rx + rx * s.to_radians().cos(),
1952                ry + ry * s.to_radians().sin(),
1953            );
1954            b.m(p0.x, p0.y).arc(rx, ry, s, sw);
1955            let text = Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355);
1956            match name {
1957                "pie" => {
1958                    b.l(rx, ry).z();
1959                    simple(b.take(), text, w, h)
1960                }
1961                "chord" => {
1962                    b.z();
1963                    simple(b.take(), text, w, h)
1964                }
1965                _ => {
1966                    let open = b.take();
1967                    let mut f = BezPath::from_vec(open.elements().to_vec());
1968                    f.line_to(Point::new(rx, ry));
1969                    f.close_path();
1970                    Geometry {
1971                        paths: vec![fill_only(f), stroke_only(open)],
1972                        text_rect: text,
1973                        handles: vec![],
1974                        sites: mid_sites(w, h),
1975                    }
1976                }
1977            }
1978        }
1979        "teardrop" => {
1980            let k = a(0).clamp(0.0, 200000.0) / 100000.0;
1981            let tip = (w / 2.0 + w / 2.0 * k, h / 2.0 - h / 2.0 * k);
1982            b.m(0.0, h / 2.0).arc(w / 2.0, h / 2.0, 180.0, 90.0);
1983            b.q(w / 2.0 + w / 4.0 * k, 0.0, tip.0, tip.1)
1984                .q(w, h / 2.0 - h / 4.0 * k, w, h / 2.0);
1985            b.arc(w / 2.0, h / 2.0, 0.0, 180.0).z();
1986            simple(
1987                b.take(),
1988                Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
1989                w,
1990                h,
1991            )
1992        }
1993        "foldedCorner" => {
1994            let v = a(0).clamp(0.0, 50000.0);
1995            let d = ss * v / 100000.0;
1996            let body = Pb::new()
1997                .poly(&[(0.0, 0.0), (w, 0.0), (w, h - d), (w - d, h), (0.0, h)])
1998                .take();
1999            let fold = Pb::new()
2000                .m(w - d, h)
2001                .l(w - d + d * 0.2, h - d + d * 0.2)
2002                .l(w, h - d)
2003                .z()
2004                .take();
2005            Geometry {
2006                paths: vec![fill(body), shade(fold, FillMode::DarkenLess)],
2007                text_rect: Rect::new(0.0, 0.0, w, h - d),
2008                handles: vec![],
2009                sites: mid_sites(w, h),
2010            }
2011        }
2012        "smileyFace" => {
2013            let k = a(0).clamp(-4653.0, 4653.0) / 100000.0;
2014            let face = Pb::new().ellipse(w / 2.0, h / 2.0, w / 2.0, h / 2.0).take();
2015            let mut eyes = Pb::new();
2016            eyes.ellipse(w * 0.35, h * 0.37, w * 0.05, h * 0.05);
2017            eyes.ellipse(w * 0.65, h * 0.37, w * 0.05, h * 0.05);
2018            let ys = h * (0.72 - k);
2019            let smile = Pb::new()
2020                .m(w * 0.22, ys)
2021                .q(w / 2.0, ys + h * k * 4.0 + h * 0.0, w * 0.78, ys)
2022                .take();
2023            Geometry {
2024                paths: vec![
2025                    fill(face),
2026                    shade(eyes.take(), FillMode::DarkenLess),
2027                    stroke_only(smile),
2028                ],
2029                text_rect: Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
2030                handles: vec![],
2031                sites: ellipse_sites(w, h),
2032            }
2033        }
2034        "heart" => {
2035            b.m(w / 2.0, h * 0.25);
2036            b.c(w * 0.62, h * -0.02, w, h * 0.0, w, h * 0.32);
2037            b.c(w, h * 0.6, w * 0.62, h * 0.78, w / 2.0, h);
2038            b.c(w * 0.38, h * 0.78, 0.0, h * 0.6, 0.0, h * 0.32);
2039            b.c(0.0, h * 0.0, w * 0.38, h * -0.02, w / 2.0, h * 0.25);
2040            b.z();
2041            simple(
2042                b.take(),
2043                Rect::new(w * 0.2, h * 0.25, w * 0.8, h * 0.7),
2044                w,
2045                h,
2046            )
2047        }
2048        "lightningBolt" => {
2049            let pts = normalize(
2050                &[
2051                    (0.36, 0.0),
2052                    (0.68, 0.0),
2053                    (0.53, 0.34),
2054                    (0.79, 0.34),
2055                    (0.24, 1.0),
2056                    (0.39, 0.53),
2057                    (0.15, 0.53),
2058                ],
2059                w,
2060                h,
2061            );
2062            simple(
2063                b.poly(&pts).take(),
2064                Rect::new(w * 0.3, h * 0.35, w * 0.65, h * 0.6),
2065                w,
2066                h,
2067            )
2068        }
2069        "sun" => {
2070            let k = a(0).clamp(12500.0, 46875.0) / 100000.0;
2071            let (cx, cy) = (w / 2.0, h / 2.0);
2072            let cr = 0.5 - k;
2073            let core = Pb::new()
2074                .ellipse(cx, cy, w * cr * 0.72, h * cr * 0.72)
2075                .take();
2076            let mut rays = Pb::new();
2077            for i in 0..8 {
2078                let ang = (i as f64 * 45.0).to_radians();
2079                let half = 9f64.to_radians();
2080                let rb = cr * 0.72 + 0.06;
2081                let tip = (cx + w * 0.5 * ang.cos(), cy + h * 0.5 * ang.sin());
2082                let b1 = (
2083                    cx + w * rb * (ang - half).cos(),
2084                    cy + h * rb * (ang - half).sin(),
2085                );
2086                let b2 = (
2087                    cx + w * rb * (ang + half).cos(),
2088                    cy + h * rb * (ang + half).sin(),
2089                );
2090                rays.poly(&[tip, b1, b2]);
2091            }
2092            Geometry {
2093                paths: vec![fill(rays.take()), fill(core)],
2094                text_rect: Rect::new(w * 0.3, h * 0.3, w * 0.7, h * 0.7),
2095                handles: vec![],
2096                sites: mid_sites(w, h),
2097            }
2098        }
2099        "moon" => {
2100            let k = a(0).clamp(0.0, 87500.0) / 100000.0;
2101            let g0 = w * k;
2102            b.m(w, 0.0)
2103                .arc(w, h / 2.0, 270.0, -180.0)
2104                .arc((w - g0).max(0.0), h / 2.0, 90.0, 180.0)
2105                .z();
2106            simple(
2107                b.take(),
2108                Rect::new(w * 0.1, h * 0.25, w * (1.0 - k).max(0.2) * 0.8, h * 0.75),
2109                w,
2110                h,
2111            )
2112        }
2113        "cloud" | "cloudCallout" => cloud(name, w, h, a(0), a(1)),
2114        "bracketPair" => {
2115            let r = ss * a(0).clamp(0.0, 50000.0) / 1e5;
2116            let mut l = Pb::new();
2117            l.m(r, h)
2118                .arc(r, r, 90.0, 90.0)
2119                .l(0.0, r)
2120                .arc(r, r, 180.0, 90.0);
2121            l.m(w - r, 0.0)
2122                .arc(r, r, 270.0, 90.0)
2123                .l(w, h - r)
2124                .arc(r, r, 0.0, 90.0);
2125            let f = corner_rect(w, h, [Corner::Round(r); 4]);
2126            Geometry {
2127                paths: vec![fill_only(f), stroke_only(l.take())],
2128                text_rect: full.inset(-r * 0.29289),
2129                handles: vec![],
2130                sites: mid_sites(w, h),
2131            }
2132        }
2133        "bracePair" => {
2134            let r = ss * a(0).clamp(0.0, 25000.0) / 1e5;
2135            let mut l = Pb::new();
2136            l.m(2.0 * r, h)
2137                .arc(r, r, 90.0, 90.0)
2138                .l(r, h / 2.0 + r)
2139                .arc(r, r, 0.0, -90.0)
2140                .arc(r, r, 90.0, -90.0)
2141                .l(r, r)
2142                .arc(r, r, 180.0, 90.0);
2143            l.m(w - 2.0 * r, 0.0)
2144                .arc(r, r, 270.0, 90.0)
2145                .l(w - r, h / 2.0 - r)
2146                .arc(r, r, 180.0, -90.0)
2147                .arc(r, r, 270.0, -90.0)
2148                .l(w - r, h - r)
2149                .arc(r, r, 0.0, 90.0);
2150            let f = corner_rect(w, h, [Corner::Round(r); 4]);
2151            Geometry {
2152                paths: vec![fill_only(f), stroke_only(l.take())],
2153                text_rect: full.inset(-r * 1.2),
2154                handles: vec![],
2155                sites: mid_sites(w, h),
2156            }
2157        }
2158        "leftBracket" | "rightBracket" => {
2159            let r = (ss * a(0).clamp(0.0, 50000.0 * h / ss.max(1e-9)) / 1e5).min(h / 2.0);
2160            let mut l = Pb::new();
2161            l.m(w, h)
2162                .arc(w, r, 90.0, 90.0)
2163                .l(0.0, r)
2164                .arc(w, r, 180.0, 90.0);
2165            let mut path = l.take();
2166            if name == "rightBracket" {
2167                path.apply_affine(kurbo::Affine::new([-1.0, 0.0, 0.0, 1.0, w, 0.0]));
2168            }
2169            Geometry {
2170                paths: vec![stroke_only(path)],
2171                text_rect: full,
2172                handles: vec![],
2173                sites: mid_sites(w, h),
2174            }
2175        }
2176        "leftBrace" | "rightBrace" => {
2177            let r = (ss * a(0).clamp(0.0, 25000.0 * h / ss.max(1e-9)) / 1e5).min(h / 4.0);
2178            let my = h * a(1).clamp(0.0, 100000.0) / 1e5;
2179            let hw = w / 2.0;
2180            let mut l = Pb::new();
2181            l.m(w, h)
2182                .arc(hw, r, 90.0, 90.0)
2183                .l(hw, my + r)
2184                .arc(hw, r, 0.0, -90.0)
2185                .arc(hw, r, 90.0, -90.0)
2186                .l(hw, r)
2187                .arc(hw, r, 180.0, 90.0);
2188            let mut path = l.take();
2189            if name == "rightBrace" {
2190                path.apply_affine(kurbo::Affine::new([-1.0, 0.0, 0.0, 1.0, w, 0.0]));
2191            }
2192            Geometry {
2193                paths: vec![stroke_only(path)],
2194                text_rect: full,
2195                handles: vec![],
2196                sites: mid_sites(w, h),
2197            }
2198        }
2199        "rightArrow" => arrow(Dir::Right, w, h, a(0), a(1)),
2200        "leftArrow" => arrow(Dir::Left, w, h, a(0), a(1)),
2201        "upArrow" => arrow(Dir::Up, w, h, a(0), a(1)),
2202        "downArrow" => arrow(Dir::Down, w, h, a(0), a(1)),
2203        "leftRightArrow" => double_arrow(false, w, h, a(0), a(1)),
2204        "upDownArrow" => double_arrow(true, w, h, a(0), a(1)),
2205        "notchedRightArrow" | "stripedRightArrow" => {
2206            let mut g = arrow(Dir::Right, w, h, a(0), a(1));
2207            let k = a(0).clamp(0.0, 100000.0) / 1e5;
2208            let (y1, y2) = (h / 2.0 - h * k / 2.0, h / 2.0 + h * k / 2.0);
2209            let dx = (ss * a(1).max(0.0) / 1e5).min(w);
2210            if name == "notchedRightArrow" {
2211                let n = dx * (k / 2.0) * 0.9;
2212                let pts = [
2213                    (0.0, y1),
2214                    (w - dx, y1),
2215                    (w - dx, 0.0),
2216                    (w, h / 2.0),
2217                    (w - dx, h),
2218                    (w - dx, y2),
2219                    (0.0, y2),
2220                    (n, h / 2.0),
2221                ];
2222                g.paths = vec![fill(Pb::new().poly(&pts).take())];
2223            } else {
2224                let s = ss / 32.0;
2225                let start = 5.0 * s;
2226                let pts = [
2227                    (start, y1),
2228                    (w - dx, y1),
2229                    (w - dx, 0.0),
2230                    (w, h / 2.0),
2231                    (w - dx, h),
2232                    (w - dx, y2),
2233                    (start, y2),
2234                ];
2235                let mut stripes = Pb::new();
2236                stripes.poly(&[(0.0, y1), (s, y1), (s, y2), (0.0, y2)]);
2237                stripes.poly(&[(2.0 * s, y1), (4.0 * s, y1), (4.0 * s, y2), (2.0 * s, y2)]);
2238                g.paths = vec![fill(stripes.take()), fill(Pb::new().poly(&pts).take())];
2239            }
2240            g
2241        }
2242        "homePlate" | "chevron" => {
2243            let v = a(0).clamp(0.0, 100000.0 * w / ss.max(1e-9));
2244            let d = (ss * v / 1e5).min(w);
2245            let pts: Vec<(f64, f64)> = if name == "homePlate" {
2246                vec![(0.0, 0.0), (w - d, 0.0), (w, h / 2.0), (w - d, h), (0.0, h)]
2247            } else {
2248                vec![
2249                    (0.0, 0.0),
2250                    (w - d, 0.0),
2251                    (w, h / 2.0),
2252                    (w - d, h),
2253                    (0.0, h),
2254                    (d, h / 2.0),
2255                ]
2256            };
2257            let tx = if name == "homePlate" { 0.0 } else { d };
2258            let mut g = simple(
2259                b.poly(&pts).take(),
2260                Rect::new(tx, 0.0, w - d / 2.0, h),
2261                w,
2262                h,
2263            );
2264            g.handles.push(Handle::new(
2265                0,
2266                Point::new(w, 0.0),
2267                Vec2::new(-ss / 1e5, 0.0),
2268                v,
2269                0.0,
2270                100000.0 * w / ss.max(1e-9),
2271            ));
2272            g
2273        }
2274        "quadArrow" => {
2275            let (t, hw, hl) = (
2276                ss * a(0).clamp(0.0, 50000.0) / 1e5,
2277                ss * a(1).clamp(0.0, 50000.0) / 1e5,
2278                ss * a(2).clamp(0.0, 50000.0) / 1e5,
2279            );
2280            let (cx, cy) = (w / 2.0, h / 2.0);
2281            let (t2, hw2) = (t / 2.0, hw);
2282            let pts = [
2283                (0.0, cy),
2284                (hl, cy - hw2),
2285                (hl, cy - t2),
2286                (cx - t2, cy - t2),
2287                (cx - t2, hl),
2288                (cx - hw2, hl),
2289                (cx, 0.0),
2290                (cx + hw2, hl),
2291                (cx + t2, hl),
2292                (cx + t2, cy - t2),
2293                (w - hl, cy - t2),
2294                (w - hl, cy - hw2),
2295                (w, cy),
2296                (w - hl, cy + hw2),
2297                (w - hl, cy + t2),
2298                (cx + t2, cy + t2),
2299                (cx + t2, h - hl),
2300                (cx + hw2, h - hl),
2301                (cx, h),
2302                (cx - hw2, h - hl),
2303                (cx - t2, h - hl),
2304                (cx - t2, cy + t2),
2305                (hl, cy + t2),
2306                (hl, cy + hw2),
2307            ];
2308            simple(
2309                b.poly(&pts).take(),
2310                Rect::new(cx - t2, cy - t2, cx + t2, cy + t2),
2311                w,
2312                h,
2313            )
2314        }
2315        "leftRightUpArrow" => {
2316            let (t, hw, hl) = (
2317                ss * a(0).clamp(0.0, 50000.0) / 1e5,
2318                ss * a(1).clamp(0.0, 50000.0) / 1e5,
2319                ss * a(2).clamp(0.0, 50000.0) / 1e5,
2320            );
2321            let cx = w / 2.0;
2322            let by = h - hw; // horizontal shaft centre
2323            let pts = [
2324                (0.0, by),
2325                (hl, by - hw),
2326                (hl, by - t / 2.0),
2327                (cx - t / 2.0, by - t / 2.0),
2328                (cx - t / 2.0, hl),
2329                (cx - hw, hl),
2330                (cx, 0.0),
2331                (cx + hw, hl),
2332                (cx + t / 2.0, hl),
2333                (cx + t / 2.0, by - t / 2.0),
2334                (w - hl, by - t / 2.0),
2335                (w - hl, by - hw),
2336                (w, by),
2337                (w - hl, h),
2338                (w - hl, by + t / 2.0),
2339                (hl, by + t / 2.0),
2340                (hl, h),
2341            ];
2342            simple(
2343                b.poly(&pts).take(),
2344                Rect::new(hl, by - t / 2.0, w - hl, by + t / 2.0),
2345                w,
2346                h,
2347            )
2348        }
2349        "leftUpArrow" | "bentUpArrow" => {
2350            let (t, hw, hl) = (
2351                ss * a(0).clamp(0.0, 50000.0) / 1e5,
2352                ss * a(1).clamp(0.0, 50000.0) / 1e5,
2353                ss * a(2).clamp(0.0, 50000.0) / 1e5,
2354            );
2355            let pts: Vec<(f64, f64)> = if name == "leftUpArrow" {
2356                let cx = w - hw;
2357                let cy = h - hw;
2358                vec![
2359                    (0.0, cy),
2360                    (hl, cy - hw),
2361                    (hl, cy - t / 2.0),
2362                    (cx - t / 2.0, cy - t / 2.0),
2363                    (cx - t / 2.0, hl),
2364                    (cx - hw, hl),
2365                    (cx, 0.0),
2366                    (w, hl),
2367                    (cx + t / 2.0, hl),
2368                    (cx + t / 2.0, cy + t / 2.0),
2369                    (hl, cy + t / 2.0),
2370                    (hl, h),
2371                ]
2372            } else {
2373                let cx = w - hw;
2374                vec![
2375                    (0.0, h - t),
2376                    (cx - t / 2.0, h - t),
2377                    (cx - t / 2.0, hl),
2378                    (cx - hw, hl),
2379                    (cx, 0.0),
2380                    (w, hl),
2381                    (cx + t / 2.0, hl),
2382                    (cx + t / 2.0, h),
2383                    (0.0, h),
2384                ]
2385            };
2386            simple(b.poly(&pts).take(), full, w, h)
2387        }
2388        "bentArrow" => {
2389            let (t, hw, hl, r) = (
2390                ss * a(0).clamp(0.0, 50000.0) / 1e5,
2391                ss * a(1).clamp(0.0, 50000.0) / 1e5,
2392                ss * a(2).clamp(0.0, 50000.0) / 1e5,
2393                ss * a(3).clamp(0.0, 100000.0) / 1e5,
2394            );
2395            let cy = hw; // head centre line
2396            let ro = r.max(t);
2397            let ri = (ro - t).max(0.0);
2398            b.m(0.0, h)
2399                .l(0.0, cy - t / 2.0 + ro)
2400                .arc(ro, ro, 180.0, 90.0)
2401                .l(w - hl, cy - t / 2.0)
2402                .l(w - hl, 0.0)
2403                .l(w, cy)
2404                .l(w - hl, 2.0 * hw)
2405                .l(w - hl, cy + t / 2.0);
2406            b.l(t + ri, cy + t / 2.0);
2407            if ri > 0.0 {
2408                b.arc(ri, ri, 270.0, -90.0);
2409            }
2410            b.l(t, h).z();
2411            simple(b.take(), full, w, h)
2412        }
2413        "uturnArrow" => {
2414            let (t, hw, hl, r) = (
2415                ss * a(0).clamp(0.0, 25000.0) / 1e5,
2416                ss * a(1).clamp(0.0, 25000.0) / 1e5,
2417                ss * a(2).clamp(0.0, 25000.0) / 1e5,
2418                ss * a(3).clamp(0.0, 100000.0) / 1e5,
2419            );
2420            let hy = (h * a(4).clamp(0.0, 100000.0) / 1e5).max(hl + t);
2421            let ax = w - hw; // arrow shaft centre x
2422            let ro = r.max(t).min((ax + t / 2.0) / 2.0);
2423            let ri = (ro - t).max(0.0);
2424            b.m(0.0, h)
2425                .l(0.0, ro)
2426                .arc(ro, ro, 180.0, 90.0)
2427                .l(ax + t / 2.0 - ro, 0.0)
2428                .arc(ro, ro, 270.0, 90.0);
2429            b.l(ax + t / 2.0, hy - hl)
2430                .l(w, hy - hl)
2431                .l(ax, hy)
2432                .l(ax - hw, hy - hl)
2433                .l(ax - t / 2.0, hy - hl)
2434                .l(ax - t / 2.0, ro);
2435            if ri > 0.0 {
2436                b.arc(ri, ri, 0.0, -90.0);
2437            }
2438            b.l(t + ri, t);
2439            if ri > 0.0 {
2440                b.arc(ri, ri, 270.0, -90.0);
2441            }
2442            b.l(t, h).z();
2443            simple(b.take(), full, w, h)
2444        }
2445        "rightArrowCallout" | "leftArrowCallout" | "upArrowCallout" | "downArrowCallout" => {
2446            let d = match name {
2447                "rightArrowCallout" => Dir::Right,
2448                "leftArrowCallout" => Dir::Left,
2449                "upArrowCallout" => Dir::Up,
2450                _ => Dir::Down,
2451            };
2452            let (l, t) = len_thick(d, w, h);
2453            let ss2 = l.min(t);
2454            let shaft = ss2 * a(0).clamp(0.0, 100000.0) / 1e5;
2455            let head = ss2 * a(1).clamp(0.0, 100000.0) / 1e5;
2456            let hl = ss2 * a(2).clamp(0.0, 100000.0) / 1e5;
2457            let boxl = (l * a(3).clamp(0.0, 100000.0) / 1e5).min(l - hl);
2458            let c = t / 2.0;
2459            let pts = [
2460                (0.0, 0.0),
2461                (boxl, 0.0),
2462                (boxl, c - shaft / 2.0),
2463                (l - hl, c - shaft / 2.0),
2464                (l - hl, c - head),
2465                (l, c),
2466                (l - hl, c + head),
2467                (l - hl, c + shaft / 2.0),
2468                (boxl, c + shaft / 2.0),
2469                (boxl, t),
2470                (0.0, t),
2471            ];
2472            let pts = orient(d, w, h, &pts);
2473            let tr = orient(d, w, h, &[(0.0, 0.0), (boxl, t)]);
2474            simple(
2475                b.poly(&pts).take(),
2476                Rect::from_points(Point::new(tr[0].0, tr[0].1), Point::new(tr[1].0, tr[1].1)).abs(),
2477                w,
2478                h,
2479            )
2480        }
2481        "mathPlus" | "mathMinus" | "mathEqual" | "mathDivide" | "mathMultiply" | "mathNotEqual" => {
2482            math(name, w, h, a(0), a(1), a(2))
2483        }
2484        "flowChartPredefinedProcess" => {
2485            let r = Pb::new()
2486                .poly(&[(0.0, 0.0), (w, 0.0), (w, h), (0.0, h)])
2487                .take();
2488            let mut l = Pb::new();
2489            l.m(w / 8.0, 0.0)
2490                .l(w / 8.0, h)
2491                .m(w * 7.0 / 8.0, 0.0)
2492                .l(w * 7.0 / 8.0, h);
2493            Geometry {
2494                paths: vec![fill(r), stroke_only(l.take())],
2495                text_rect: Rect::new(w / 8.0, 0.0, w * 7.0 / 8.0, h),
2496                handles: vec![],
2497                sites: mid_sites(w, h),
2498            }
2499        }
2500        "flowChartInternalStorage" => {
2501            let r = Pb::new()
2502                .poly(&[(0.0, 0.0), (w, 0.0), (w, h), (0.0, h)])
2503                .take();
2504            let mut l = Pb::new();
2505            l.m(w / 8.0, 0.0)
2506                .l(w / 8.0, h)
2507                .m(0.0, h / 8.0)
2508                .l(w, h / 8.0);
2509            Geometry {
2510                paths: vec![fill(r), stroke_only(l.take())],
2511                text_rect: Rect::new(w / 8.0, h / 8.0, w, h),
2512                handles: vec![],
2513                sites: mid_sites(w, h),
2514            }
2515        }
2516        "flowChartDocument" => {
2517            b.m(0.0, 0.0)
2518                .l(w, 0.0)
2519                .l(w, h * 0.8)
2520                .c(w * 0.7, h * 0.62, w * 0.35, h * 1.08, 0.0, h * 0.88)
2521                .z();
2522            simple(b.take(), Rect::new(0.0, 0.0, w, h * 0.78), w, h)
2523        }
2524        "flowChartMultidocument" => {
2525            let (dx, dy) = (w * 0.1, h * 0.1);
2526            let mut back = Pb::new();
2527            back.poly(&[
2528                (2.0 * dx, 0.0),
2529                (w, 0.0),
2530                (w, h * 0.7),
2531                (w - dx * 0.5, h * 0.7),
2532                (w - dx * 0.5, dy * 0.5),
2533                (2.0 * dx, dy * 0.5),
2534            ]);
2535            back.poly(&[
2536                (dx, dy * 0.5),
2537                (w - dx * 0.5, dy * 0.5),
2538                (w - dx * 0.5, h * 0.78),
2539                (w - dx, h * 0.78),
2540                (w - dx, dy),
2541                (dx, dy),
2542            ]);
2543            let front = Pb::new()
2544                .m(0.0, dy)
2545                .l(w - dx, dy)
2546                .l(w - dx, h * 0.84)
2547                .c(w * 0.62, h * 0.7, w * 0.32, h * 1.08, 0.0, h * 0.9)
2548                .z()
2549                .take();
2550            Geometry {
2551                paths: vec![fill(back.take()), fill(front)],
2552                text_rect: Rect::new(0.0, dy, w - dx, h * 0.8),
2553                handles: vec![],
2554                sites: mid_sites(w, h),
2555            }
2556        }
2557        "flowChartTerminator" => {
2558            let rx = (w * 0.16).min(w / 2.0);
2559            b.m(rx, 0.0)
2560                .l(w - rx, 0.0)
2561                .arc(rx, h / 2.0, 270.0, 180.0)
2562                .l(rx, h)
2563                .arc(rx, h / 2.0, 90.0, 180.0)
2564                .z();
2565            simple(
2566                b.take(),
2567                Rect::new(rx * 0.3, h * 0.15, w - rx * 0.3, h * 0.85),
2568                w,
2569                h,
2570            )
2571        }
2572        "flowChartManualInput" => simple(
2573            b.poly(&[(0.0, h / 5.0), (w, 0.0), (w, h), (0.0, h)]).take(),
2574            Rect::new(0.0, h / 5.0, w, h),
2575            w,
2576            h,
2577        ),
2578        "flowChartOffpageConnector" => simple(
2579            b.poly(&[
2580                (0.0, 0.0),
2581                (w, 0.0),
2582                (w, h * 0.8),
2583                (w / 2.0, h),
2584                (0.0, h * 0.8),
2585            ])
2586            .take(),
2587            Rect::new(0.0, 0.0, w, h * 0.8),
2588            w,
2589            h,
2590        ),
2591        "flowChartPunchedCard" => simple(
2592            b.poly(&[(w * 0.2, 0.0), (w, 0.0), (w, h), (0.0, h), (0.0, h * 0.2)])
2593                .take(),
2594            Rect::new(0.0, h * 0.2, w, h),
2595            w,
2596            h,
2597        ),
2598        "flowChartPunchedTape" => {
2599            let a1 = h * 0.1;
2600            b.m(0.0, a1)
2601                .c(w * 0.25, a1 * 3.0, w * 0.25, -a1, w / 2.0, a1)
2602                .c(w * 0.75, a1 * 3.0, w * 0.75, -a1, w, a1)
2603                .l(w, h - a1);
2604            b.c(w * 0.75, h - 3.0 * a1, w * 0.75, h + a1, w / 2.0, h - a1)
2605                .c(w * 0.25, h - 3.0 * a1, w * 0.25, h + a1, 0.0, h - a1)
2606                .z();
2607            simple(b.take(), Rect::new(0.0, h * 0.2, w, h * 0.8), w, h)
2608        }
2609        "flowChartSummingJunction" | "flowChartOr" => {
2610            let e = Pb::new().ellipse(w / 2.0, h / 2.0, w / 2.0, h / 2.0).take();
2611            let mut l = Pb::new();
2612            if name == "flowChartOr" {
2613                l.m(w / 2.0, 0.0)
2614                    .l(w / 2.0, h)
2615                    .m(0.0, h / 2.0)
2616                    .l(w, h / 2.0);
2617            } else {
2618                let (dx, dy) = (
2619                    w / 2.0 * std::f64::consts::FRAC_1_SQRT_2,
2620                    h / 2.0 * std::f64::consts::FRAC_1_SQRT_2,
2621                );
2622                l.m(w / 2.0 - dx, h / 2.0 - dy)
2623                    .l(w / 2.0 + dx, h / 2.0 + dy)
2624                    .m(w / 2.0 + dx, h / 2.0 - dy)
2625                    .l(w / 2.0 - dx, h / 2.0 + dy);
2626            }
2627            Geometry {
2628                paths: vec![fill(e), stroke_only(l.take())],
2629                text_rect: Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
2630                handles: vec![],
2631                sites: ellipse_sites(w, h),
2632            }
2633        }
2634        "flowChartCollate" => simple(
2635            b.poly(&[(0.0, 0.0), (w, 0.0), (0.0, h), (w, h)]).take(),
2636            Rect::new(w / 4.0, h / 4.0, w * 0.75, h * 0.75),
2637            w,
2638            h,
2639        ),
2640        "flowChartOnlineStorage" => {
2641            let rx = w / 6.0;
2642            b.m(rx, 0.0)
2643                .l(w, 0.0)
2644                .arc(rx, h / 2.0, 270.0, -180.0)
2645                .l(rx, h)
2646                .arc(rx, h / 2.0, 90.0, 180.0)
2647                .z();
2648            simple(b.take(), Rect::new(rx, 0.0, w - rx, h), w, h)
2649        }
2650        "flowChartDelay" => {
2651            b.m(0.0, 0.0)
2652                .l(w / 2.0, 0.0)
2653                .arc(w / 2.0, h / 2.0, 270.0, 180.0)
2654                .l(0.0, h)
2655                .z();
2656            simple(b.take(), Rect::new(0.0, h * 0.15, w * 0.85, h * 0.85), w, h)
2657        }
2658        "flowChartMagneticTape" => {
2659            b.m(w / 2.0, h)
2660                .arc(w / 2.0, h / 2.0, 90.0, 90.0)
2661                .arc(w / 2.0, h / 2.0, 180.0, 90.0)
2662                .arc(w / 2.0, h / 2.0, 270.0, 90.0)
2663                .arc(w / 2.0, h / 2.0, 0.0, 45.0);
2664            let p = b.cur;
2665            b.l(w, p.y).l(w, h).z();
2666            simple(
2667                b.take(),
2668                Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
2669                w,
2670                h,
2671            )
2672        }
2673        "flowChartDisplay" => {
2674            let rx = w / 6.0;
2675            b.m(0.0, h / 2.0)
2676                .l(rx, 0.0)
2677                .l(w - rx, 0.0)
2678                .arc(rx, h / 2.0, 270.0, 180.0)
2679                .l(rx, h)
2680                .z();
2681            simple(b.take(), Rect::new(rx, 0.0, w - rx, h), w, h)
2682        }
2683        "star4" | "star5" | "star6" | "star7" | "star8" | "star10" | "star12" | "star16"
2684        | "star24" | "star32" => {
2685            let n: usize = name.trim_start_matches("star").parse().unwrap_or(5);
2686            let v = a(0).clamp(0.0, 50000.0);
2687            let inner = v / 50000.0;
2688            let normalized = matches!(n, 5 | 7);
2689            let mut g = simple(
2690                b.poly(&star(n, w, h, inner, normalized)).take(),
2691                full.inset(-ss * (0.5 - inner * 0.5) * 0.5),
2692                w,
2693                h,
2694            );
2695            g.handles.push(Handle::new(
2696                0,
2697                Point::new(w / 2.0, h / 2.0),
2698                Vec2::new(0.0, -h / 2.0 / 50000.0),
2699                v,
2700                0.0,
2701                50000.0,
2702            ));
2703            g
2704        }
2705        "irregularSeal1" | "irregularSeal2" => {
2706            let pts: &[(f64, f64)] = if name == "irregularSeal1" {
2707                &[
2708                    (0.48, 0.18),
2709                    (0.62, 0.0),
2710                    (0.64, 0.2),
2711                    (0.86, 0.1),
2712                    (0.78, 0.3),
2713                    (1.0, 0.36),
2714                    (0.82, 0.5),
2715                    (0.94, 0.72),
2716                    (0.7, 0.66),
2717                    (0.68, 0.9),
2718                    (0.54, 0.72),
2719                    (0.42, 1.0),
2720                    (0.36, 0.74),
2721                    (0.14, 0.86),
2722                    (0.22, 0.62),
2723                    (0.0, 0.6),
2724                    (0.18, 0.44),
2725                    (0.04, 0.24),
2726                    (0.3, 0.3),
2727                    (0.26, 0.06),
2728                ]
2729            } else {
2730                &[
2731                    (0.5, 0.16),
2732                    (0.58, 0.0),
2733                    (0.63, 0.18),
2734                    (0.78, 0.06),
2735                    (0.76, 0.24),
2736                    (0.96, 0.2),
2737                    (0.86, 0.38),
2738                    (1.0, 0.5),
2739                    (0.84, 0.58),
2740                    (0.92, 0.76),
2741                    (0.74, 0.72),
2742                    (0.76, 0.94),
2743                    (0.6, 0.8),
2744                    (0.5, 1.0),
2745                    (0.44, 0.8),
2746                    (0.28, 0.94),
2747                    (0.28, 0.74),
2748                    (0.08, 0.82),
2749                    (0.16, 0.62),
2750                    (0.0, 0.54),
2751                    (0.14, 0.44),
2752                    (0.02, 0.28),
2753                    (0.22, 0.3),
2754                    (0.2, 0.1),
2755                    (0.36, 0.22),
2756                    (0.4, 0.04),
2757                ]
2758            };
2759            let pts: Vec<(f64, f64)> = pts.iter().map(|(x, y)| (x * w, y * h)).collect();
2760            simple(
2761                b.poly(&pts).take(),
2762                Rect::new(w * 0.25, h * 0.3, w * 0.75, h * 0.7),
2763                w,
2764                h,
2765            )
2766        }
2767        "ribbon" | "ribbon2" => ribbon(name == "ribbon2", w, h, a(0), a(1)),
2768        "verticalScroll" | "horizontalScroll" => scroll(name == "verticalScroll", w, h, a(0)),
2769        "wave" | "doubleWave" => {
2770            let amp = h * a(0).clamp(0.0, 20000.0) / 1e5;
2771            let shift = w * a(1).clamp(-10000.0, 10000.0) / 1e5;
2772            if name == "wave" {
2773                b.m(shift.max(0.0), amp).c(
2774                    w / 3.0,
2775                    -amp,
2776                    2.0 * w / 3.0,
2777                    3.0 * amp,
2778                    w + shift.min(0.0),
2779                    amp,
2780                );
2781                b.l(w - shift.max(0.0), h - amp)
2782                    .c(
2783                        2.0 * w / 3.0,
2784                        h - 3.0 * amp,
2785                        w / 3.0,
2786                        h + amp,
2787                        -shift.min(0.0),
2788                        h - amp,
2789                    )
2790                    .z();
2791            } else {
2792                b.m(0.0, amp)
2793                    .c(w / 6.0, -amp, w / 3.0, 3.0 * amp, w / 2.0, amp)
2794                    .c(2.0 * w / 3.0, -amp, 5.0 * w / 6.0, 3.0 * amp, w, amp);
2795                b.l(w, h - amp)
2796                    .c(
2797                        5.0 * w / 6.0,
2798                        h - 3.0 * amp,
2799                        2.0 * w / 3.0,
2800                        h + amp,
2801                        w / 2.0,
2802                        h - amp,
2803                    )
2804                    .c(w / 3.0, h - 3.0 * amp, w / 6.0, h + amp, 0.0, h - amp)
2805                    .z();
2806            }
2807            simple(b.take(), Rect::new(0.0, amp * 2.0, w, h - amp * 2.0), w, h)
2808        }
2809        "wedgeRectCallout" | "wedgeRoundRectCallout" => {
2810            let r = if name == "wedgeRoundRectCallout" {
2811                ss * a(2).clamp(0.0, 50000.0) / 1e5
2812            } else {
2813                0.0
2814            };
2815            wedge_rect(w, h, a(0), a(1), r)
2816        }
2817        "wedgeEllipseCallout" => {
2818            let tip = Point::new(w / 2.0 + w * a(0) / 1e5, h / 2.0 + h * a(1) / 1e5);
2819            let ang = (tip.y - h / 2.0).atan2(tip.x - w / 2.0).to_degrees();
2820            let s = vis(ang + 10.0, w / 2.0, h / 2.0);
2821            let p0 = Point::new(
2822                w / 2.0 + w / 2.0 * s.to_radians().cos(),
2823                h / 2.0 + h / 2.0 * s.to_radians().sin(),
2824            );
2825            let e = vis(ang - 10.0, w / 2.0, h / 2.0);
2826            let mut sw = e - s;
2827            if sw <= 0.0 {
2828                sw += 360.0;
2829            }
2830            b.m(p0.x, p0.y)
2831                .arc(w / 2.0, h / 2.0, s, sw)
2832                .l(tip.x, tip.y)
2833                .z();
2834            let mut g = simple(
2835                b.take(),
2836                Rect::new(w * 0.14645, h * 0.14645, w * 0.85355, h * 0.85355),
2837                w,
2838                h,
2839            );
2840            g.handles.push(Handle::new(
2841                0,
2842                Point::new(w / 2.0, h / 2.0),
2843                Vec2::new(w / 1e5, 0.0),
2844                a(0),
2845                -1e6,
2846                1e6,
2847            ));
2848            g.handles.push(Handle::new(
2849                1,
2850                Point::new(w / 2.0, h / 2.0),
2851                Vec2::new(0.0, h / 1e5),
2852                a(1),
2853                -1e6,
2854                1e6,
2855            ));
2856            g.handles.retain(|_| false);
2857            g.handles.push(tip_handle(w, h, tip));
2858            g
2859        }
2860        "borderCallout1" => {
2861            let r = Pb::new()
2862                .poly(&[(0.0, 0.0), (w, 0.0), (w, h), (0.0, h)])
2863                .take();
2864            let p1 = (w * a(1) / 1e5, h * a(0) / 1e5);
2865            let p2 = (w * a(3) / 1e5, h * a(2) / 1e5);
2866            let l = Pb::new().m(p1.0, p1.1).l(p2.0, p2.1).take();
2867            Geometry {
2868                paths: vec![fill(r), stroke_only(l)],
2869                text_rect: full,
2870                handles: vec![],
2871                sites: mid_sites(w, h),
2872            }
2873        }
2874        "line" | "straightConnector1" => {
2875            let l = Pb::new().m(0.0, 0.0).l(w, h).take();
2876            Geometry {
2877                paths: vec![stroke_only(l)],
2878                text_rect: full,
2879                handles: vec![],
2880                sites: vec![Point::ZERO, Point::new(w, h)],
2881            }
2882        }
2883        "bentConnector2" => {
2884            let l = Pb::new().m(0.0, 0.0).l(w, 0.0).l(w, h).take();
2885            Geometry {
2886                paths: vec![stroke_only(l)],
2887                text_rect: full,
2888                handles: vec![],
2889                sites: vec![Point::ZERO, Point::new(w, h)],
2890            }
2891        }
2892        "bentConnector3" | "bentConnector4" | "bentConnector5" => {
2893            let x = w * a(0) / 1e5;
2894            let l = Pb::new().m(0.0, 0.0).l(x, 0.0).l(x, h).l(w, h).take();
2895            let mut g = Geometry {
2896                paths: vec![stroke_only(l)],
2897                text_rect: full,
2898                handles: vec![],
2899                sites: vec![Point::ZERO, Point::new(w, h)],
2900            };
2901            g.handles.push(Handle::new(
2902                0,
2903                Point::new(0.0, h / 2.0),
2904                Vec2::new(w / 1e5, 0.0),
2905                a(0),
2906                -1e6,
2907                1e6,
2908            ));
2909            g
2910        }
2911        "curvedConnector2" | "curvedConnector3" | "curvedConnector4" | "curvedConnector5" => {
2912            let x = w * if name == "curvedConnector2" {
2913                100000.0
2914            } else {
2915                a(0)
2916            } / 1e5;
2917            let l = Pb::new()
2918                .m(0.0, 0.0)
2919                .c(x / 2.0, 0.0, x, h / 4.0, x, h / 2.0)
2920                .c(x, h * 0.75, (x + w) / 2.0, h, w, h)
2921                .take();
2922            Geometry {
2923                paths: vec![stroke_only(l)],
2924                text_rect: full,
2925                handles: vec![],
2926                sites: vec![Point::ZERO, Point::new(w, h)],
2927            }
2928        }
2929        _ => return extra::build(name, w, h, &a),
2930    };
2931    Some(g)
2932}
2933
2934fn ellipse_sites(w: f64, h: f64) -> Vec<Point> {
2935    (0..8)
2936        .map(|i| {
2937            let a = (-90.0 - 45.0 * i as f64).to_radians();
2938            Point::new(w / 2.0 + w / 2.0 * a.cos(), h / 2.0 + h / 2.0 * a.sin())
2939        })
2940        .collect()
2941}
2942
2943fn tip_handle(w: f64, h: f64, tip: Point) -> Handle {
2944    // A free 2-D handle is represented as index 0 with zero direction; callers move both values.
2945    Handle {
2946        index: 0,
2947        pos: tip,
2948        origin: Point::new(w / 2.0, h / 2.0),
2949        dir: Vec2::new(w / 1e5, h / 1e5),
2950        min: -1e6,
2951        max: 1e6,
2952    }
2953}
2954
2955fn wedge_rect(w: f64, h: f64, a0: f64, a1: f64, r: f64) -> Geometry {
2956    let tip = Point::new(w / 2.0 + w * a0 / 1e5, h / 2.0 + h * a1 / 1e5);
2957    let inside = tip.x > 0.0 && tip.x < w && tip.y > 0.0 && tip.y < h;
2958    let (dx, dy) = (
2959        (tip.x - w / 2.0) / w.max(1e-9),
2960        (tip.y - h / 2.0) / h.max(1e-9),
2961    );
2962    let mut b = Pb::new();
2963    let r = r.min(w / 2.0).min(h / 2.0);
2964    // Walk clockwise from the top-left; insert the wedge on the edge facing the tip.
2965    let horiz = dx.abs() > dy.abs();
2966    let (bx0, bx1) = (w * 5.0 / 12.0, w * 7.0 / 12.0);
2967    let (by0, by1) = (h * 5.0 / 12.0, h * 7.0 / 12.0);
2968    let (bx0, bx1) = if tip.x < w / 2.0 {
2969        (w * 2.0 / 12.0, w * 5.0 / 12.0)
2970    } else {
2971        (bx0 + w / 12.0 * 2.0 - w / 12.0 * 2.0, bx1 + w * 2.0 / 12.0)
2972    };
2973    let (by0, by1) = if tip.y < h / 2.0 {
2974        (h * 2.0 / 12.0, h * 5.0 / 12.0)
2975    } else {
2976        (by0, by1 + h * 2.0 / 12.0)
2977    };
2978    b.m(r, 0.0);
2979    if !inside && !horiz && dy < 0.0 {
2980        b.l(bx0, 0.0).l(tip.x, tip.y).l(bx1, 0.0);
2981    }
2982    b.l(w - r, 0.0);
2983    if r > 0.0 {
2984        b.arc(r, r, 270.0, 90.0);
2985    }
2986    if !inside && horiz && dx > 0.0 {
2987        b.l(w, by0).l(tip.x, tip.y).l(w, by1);
2988    }
2989    b.l(w, h - r);
2990    if r > 0.0 {
2991        b.arc(r, r, 0.0, 90.0);
2992    }
2993    if !inside && !horiz && dy > 0.0 {
2994        b.l(bx1, h).l(tip.x, tip.y).l(bx0, h);
2995    }
2996    b.l(r, h);
2997    if r > 0.0 {
2998        b.arc(r, r, 90.0, 90.0);
2999    }
3000    if !inside && horiz && dx < 0.0 {
3001        b.l(0.0, by1).l(tip.x, tip.y).l(0.0, by0);
3002    }
3003    b.l(0.0, r);
3004    if r > 0.0 {
3005        b.arc(r, r, 180.0, 90.0);
3006    }
3007    b.z();
3008    let mut g = simple(
3009        b.take(),
3010        Rect::new(0.0, 0.0, w, h).inset(-r * 0.29289),
3011        w,
3012        h,
3013    );
3014    g.handles.push(tip_handle(w, h, tip));
3015    g
3016}
3017
3018fn cloud(name: &str, w: f64, h: f64, a0: f64, a1: f64) -> Geometry {
3019    let n = 10;
3020    let pts: Vec<Point> = (0..n)
3021        .map(|i| {
3022            let t = (i as f64 / n as f64 * 360.0 - 90.0).to_radians();
3023            let wob = if i % 2 == 0 { 1.0 } else { 0.92 };
3024            Point::new(0.5 + 0.40 * wob * t.cos(), 0.5 + 0.36 * wob * t.sin())
3025        })
3026        .collect();
3027    let mut b = Pb::new();
3028    let first = pts.first().copied().unwrap_or(Point::ZERO);
3029    b.m(first.x, first.y);
3030    for i in 0..n {
3031        let p = pts.get(i).copied().unwrap_or(Point::ZERO);
3032        let q = pts.get((i + 1) % n).copied().unwrap_or(Point::ZERO);
3033        let mid = p.midpoint(q);
3034        let out = (mid - Point::new(0.5, 0.5)).normalize();
3035        let bulge = (q - p).hypot() * 0.75;
3036        let c1 = p + (mid - p) * 0.2 + out * bulge;
3037        let c2 = q + (mid - q) * 0.2 + out * bulge;
3038        b.c(c1.x, c1.y, c2.x, c2.y, q.x, q.y);
3039    }
3040    b.z();
3041    let mut path = b.take();
3042    let bb = path.bounding_box();
3043    let sx = if bb.width() > 0.0 {
3044        w / bb.width()
3045    } else {
3046        1.0
3047    };
3048    let sy = if bb.height() > 0.0 {
3049        h / bb.height()
3050    } else {
3051        1.0
3052    };
3053    path.apply_affine(
3054        kurbo::Affine::scale_non_uniform(sx, sy) * kurbo::Affine::translate(-bb.origin().to_vec2()),
3055    );
3056    let mut paths = vec![fill(path)];
3057    let text_rect = Rect::new(w * 0.2, h * 0.22, w * 0.8, h * 0.78);
3058    let mut handles = vec![];
3059    if name == "cloudCallout" {
3060        let tip = Point::new(w / 2.0 + w * a0 / 1e5, h / 2.0 + h * a1 / 1e5);
3061        let c = Point::new(w / 2.0, h / 2.0);
3062        let mut bubbles = Pb::new();
3063        for (k, r) in [(0.55, 0.06), (0.75, 0.045), (0.95, 0.03)] {
3064            let p = c + (tip - c) * k;
3065            if !(p.x > w * 0.1 && p.x < w * 0.9 && p.y > h * 0.1 && p.y < h * 0.9) || k > 0.9 {
3066                bubbles.ellipse(p.x, p.y, w * r, h * r);
3067            }
3068        }
3069        paths.push(fill(bubbles.take()));
3070        handles.push(tip_handle(w, h, tip));
3071    }
3072    Geometry {
3073        paths,
3074        text_rect,
3075        handles,
3076        sites: mid_sites(w, h),
3077    }
3078}
3079
3080fn math(name: &str, w: f64, h: f64, a0: f64, a1: f64, a2: f64) -> Geometry {
3081    let ss = w.min(h);
3082    let t = ss * a0.clamp(0.0, 50000.0) / 1e5;
3083    let (cx, cy) = (w / 2.0, h / 2.0);
3084    let mx = w * 0.0735;
3085    let my = h * 0.0735;
3086    let mut b = Pb::new();
3087    match name {
3088        "mathPlus" => {
3089            let (hx, hy) = (cx - mx, cy - my);
3090            b.poly(&[
3091                (cx - t / 2.0, cy - hy),
3092                (cx + t / 2.0, cy - hy),
3093                (cx + t / 2.0, cy - t / 2.0),
3094                (cx + hx, cy - t / 2.0),
3095                (cx + hx, cy + t / 2.0),
3096                (cx + t / 2.0, cy + t / 2.0),
3097                (cx + t / 2.0, cy + hy),
3098                (cx - t / 2.0, cy + hy),
3099                (cx - t / 2.0, cy + t / 2.0),
3100                (cx - hx, cy + t / 2.0),
3101                (cx - hx, cy - t / 2.0),
3102                (cx - t / 2.0, cy - t / 2.0),
3103            ]);
3104        }
3105        "mathMinus" => {
3106            b.poly(&[
3107                (mx, cy - t / 2.0),
3108                (w - mx, cy - t / 2.0),
3109                (w - mx, cy + t / 2.0),
3110                (mx, cy + t / 2.0),
3111            ]);
3112        }
3113        "mathEqual" => {
3114            let gap = h * a1.clamp(0.0, 100000.0) / 1e5;
3115            b.poly(&[
3116                (mx, cy - gap / 2.0 - t),
3117                (w - mx, cy - gap / 2.0 - t),
3118                (w - mx, cy - gap / 2.0),
3119                (mx, cy - gap / 2.0),
3120            ]);
3121            b.poly(&[
3122                (mx, cy + gap / 2.0),
3123                (w - mx, cy + gap / 2.0),
3124                (w - mx, cy + gap / 2.0 + t),
3125                (mx, cy + gap / 2.0 + t),
3126            ]);
3127        }
3128        "mathNotEqual" => {
3129            let gap = h * a2.clamp(0.0, 100000.0) / 1e5;
3130            b.poly(&[
3131                (mx, cy - gap / 2.0 - t),
3132                (w - mx, cy - gap / 2.0 - t),
3133                (w - mx, cy - gap / 2.0),
3134                (mx, cy - gap / 2.0),
3135            ]);
3136            b.poly(&[
3137                (mx, cy + gap / 2.0),
3138                (w - mx, cy + gap / 2.0),
3139                (w - mx, cy + gap / 2.0 + t),
3140                (mx, cy + gap / 2.0 + t),
3141            ]);
3142            let ang = (a1 / 60000.0).to_radians();
3143            let dir = Vec2::new(ang.cos(), ang.sin());
3144            let len = (h / 2.0 - my) / ang.sin().abs().max(0.2);
3145            let perp = Vec2::new(-dir.y, dir.x) * (t / 2.0);
3146            let c = Point::new(cx, cy);
3147            let p = [
3148                c - dir * len - perp,
3149                c + dir * len - perp,
3150                c + dir * len + perp,
3151                c - dir * len + perp,
3152            ];
3153            b.poly(&p.map(|q| (q.x, q.y)));
3154        }
3155        "mathDivide" => {
3156            let gap = h * a1.clamp(0.0, 100000.0) / 1e5;
3157            let r = ss * a2.clamp(0.0, 50000.0) / 1e5 / 2.0;
3158            b.poly(&[
3159                (mx, cy - t / 2.0),
3160                (w - mx, cy - t / 2.0),
3161                (w - mx, cy + t / 2.0),
3162                (mx, cy + t / 2.0),
3163            ]);
3164            b.ellipse(cx, cy - t / 2.0 - gap - r, r, r);
3165            b.ellipse(cx, cy + t / 2.0 + gap + r, r, r);
3166        }
3167        _ => {
3168            // mathMultiply: two bars crossing at the centre along the diagonals.
3169            let len = ((cx - mx).powi(2) + (cy - my).powi(2)).sqrt();
3170            let c = Point::new(cx, cy);
3171            for d in [Vec2::new(cx - mx, cy - my), Vec2::new(cx - mx, -(cy - my))] {
3172                let dir = d / d.hypot().max(1e-9);
3173                let perp = Vec2::new(-dir.y, dir.x) * (t / 2.0);
3174                let p = [
3175                    c - dir * len - perp,
3176                    c + dir * len - perp,
3177                    c + dir * len + perp,
3178                    c - dir * len + perp,
3179                ];
3180                b.poly(&p.map(|q| (q.x, q.y)));
3181            }
3182        }
3183    }
3184    Geometry {
3185        paths: vec![fill(b.take())],
3186        text_rect: Rect::new(mx, cy - t / 2.0, w - mx, cy + t / 2.0),
3187        handles: vec![],
3188        sites: mid_sites(w, h),
3189    }
3190}
3191
3192fn ribbon(up: bool, w: f64, h: f64, a0: f64, a1: f64) -> Geometry {
3193    let fold = h * a0.clamp(0.0, 33333.0) / 1e5;
3194    let cw = w * a1.clamp(25000.0, 75000.0) / 1e5; // centre band width
3195    let x0 = (w - cw) / 2.0;
3196    let x1 = w - x0;
3197    let ear = w / 8.0;
3198    let (top, bot) = (0.0, h);
3199    let mut b = Pb::new();
3200    let mut shade_p = Pb::new();
3201    if up {
3202        // centre band sits high; ends hang low.
3203        b.poly(&[(x0, top), (x1, top), (x1, bot - fold), (x0, bot - fold)]);
3204        let mut ends = Pb::new();
3205        ends.poly(&[
3206            (0.0, fold),
3207            (x0 + ear * 0.5, fold),
3208            (x0 + ear * 0.5, bot),
3209            (0.0, bot),
3210            (ear, (fold + bot) / 2.0),
3211        ]);
3212        ends.poly(&[
3213            (w, fold),
3214            (x1 - ear * 0.5, fold),
3215            (x1 - ear * 0.5, bot),
3216            (w, bot),
3217            (w - ear, (fold + bot) / 2.0),
3218        ]);
3219        shade_p.poly(&[
3220            (x0, bot - fold),
3221            (x0 + ear * 0.5, bot - fold),
3222            (x0 + ear * 0.5, bot),
3223        ]);
3224        shade_p.poly(&[
3225            (x1, bot - fold),
3226            (x1 - ear * 0.5, bot - fold),
3227            (x1 - ear * 0.5, bot),
3228        ]);
3229        let text = Rect::new(x0, top, x1, bot - fold);
3230        return Geometry {
3231            paths: vec![
3232                fill(ends.take()),
3233                shade(shade_p.take(), FillMode::DarkenLess),
3234                fill(b.take()),
3235            ],
3236            text_rect: text,
3237            handles: vec![],
3238            sites: mid_sites(w, h),
3239        };
3240    }
3241    b.poly(&[(x0, top + fold), (x1, top + fold), (x1, bot), (x0, bot)]);
3242    let mut ends = Pb::new();
3243    ends.poly(&[
3244        (0.0, top),
3245        (x0 + ear * 0.5, top),
3246        (x0 + ear * 0.5, bot - fold),
3247        (0.0, bot - fold),
3248        (ear, (bot - fold) / 2.0),
3249    ]);
3250    ends.poly(&[
3251        (w, top),
3252        (x1 - ear * 0.5, top),
3253        (x1 - ear * 0.5, bot - fold),
3254        (w, bot - fold),
3255        (w - ear, (bot - fold) / 2.0),
3256    ]);
3257    shade_p.poly(&[
3258        (x0, top + fold),
3259        (x0 + ear * 0.5, top + fold),
3260        (x0 + ear * 0.5, top),
3261    ]);
3262    shade_p.poly(&[
3263        (x1, top + fold),
3264        (x1 - ear * 0.5, top + fold),
3265        (x1 - ear * 0.5, top),
3266    ]);
3267    let text = Rect::new(x0, top + fold, x1, bot);
3268    Geometry {
3269        paths: vec![
3270            fill(ends.take()),
3271            shade(shade_p.take(), FillMode::DarkenLess),
3272            fill(b.take()),
3273        ],
3274        text_rect: text,
3275        handles: vec![],
3276        sites: mid_sites(w, h),
3277    }
3278}
3279
3280fn scroll(vertical: bool, w: f64, h: f64, a0: f64) -> Geometry {
3281    let ss = w.min(h);
3282    let r = ss * a0.clamp(0.0, 25000.0) / 1e5 / 2.0;
3283    let mut b = Pb::new();
3284    let mut curl = Pb::new();
3285    if vertical {
3286        b.poly(&[(r, r * 2.0), (w - r, r * 2.0), (w - r, h - r), (r, h - r)]);
3287        b.ellipse(w / 2.0, r, w / 2.0 - r * 0.0, r);
3288        curl.ellipse(r, h - r, r, r);
3289        curl.ellipse(w - r * 2.0, r, r * 0.5, r * 0.5);
3290        let text = Rect::new(r, r * 2.0, w - r, h - r);
3291        return Geometry {
3292            paths: vec![fill(b.take()), shade(curl.take(), FillMode::DarkenLess)],
3293            text_rect: text,
3294            handles: vec![],
3295            sites: mid_sites(w, h),
3296        };
3297    }
3298    b.poly(&[(r * 2.0, r), (w - r, r), (w - r, h - r), (r * 2.0, h - r)]);
3299    b.ellipse(r, h / 2.0, r, h / 2.0);
3300    curl.ellipse(w - r, r, r, r);
3301    curl.ellipse(r, h - r * 2.0, r * 0.5, r * 0.5);
3302    let text = Rect::new(r * 2.0, r, w - r, h - r);
3303    Geometry {
3304        paths: vec![fill(b.take()), shade(curl.take(), FillMode::DarkenLess)],
3305        text_rect: text,
3306        handles: vec![],
3307        sites: mid_sites(w, h),
3308    }
3309}
3310
3311fn action_button(name: &str, w: f64, h: f64) -> Geometry {
3312    let body = corner_rect(w, h, [Corner::Square; 4]);
3313    let ss = w.min(h);
3314    let (cx, cy) = (w / 2.0, h / 2.0);
3315    let s = ss * 0.3;
3316    let mut g = Pb::new();
3317    match name {
3318        "actionButtonBackPrevious" => {
3319            g.poly(&[(cx + s, cy - s), (cx + s, cy + s), (cx - s, cy)]);
3320        }
3321        "actionButtonForwardNext" => {
3322            g.poly(&[(cx - s, cy - s), (cx - s, cy + s), (cx + s, cy)]);
3323        }
3324        "actionButtonBeginning" => {
3325            g.poly(&[(cx + s, cy - s), (cx + s, cy + s), (cx - s * 0.6, cy)]);
3326            g.poly(&[
3327                (cx - s, cy - s),
3328                (cx - s * 0.7, cy - s),
3329                (cx - s * 0.7, cy + s),
3330                (cx - s, cy + s),
3331            ]);
3332        }
3333        "actionButtonEnd" => {
3334            g.poly(&[(cx - s, cy - s), (cx - s, cy + s), (cx + s * 0.6, cy)]);
3335            g.poly(&[
3336                (cx + s * 0.7, cy - s),
3337                (cx + s, cy - s),
3338                (cx + s, cy + s),
3339                (cx + s * 0.7, cy + s),
3340            ]);
3341        }
3342        "actionButtonHome" => {
3343            g.poly(&[
3344                (cx, cy - s),
3345                (cx + s, cy),
3346                (cx + s * 0.7, cy),
3347                (cx + s * 0.7, cy + s),
3348                (cx - s * 0.7, cy + s),
3349                (cx - s * 0.7, cy),
3350                (cx - s, cy),
3351            ]);
3352        }
3353        "actionButtonInformation" | "actionButtonHelp" => {
3354            g.ellipse(cx, cy, s, s);
3355        }
3356        "actionButtonReturn" => {
3357            g.m(cx + s, cy - s * 0.6)
3358                .l(cx + s, cy + s * 0.2)
3359                .q(cx + s, cy + s * 0.8, cx + s * 0.4, cy + s * 0.8)
3360                .l(cx - s * 0.5, cy + s * 0.8);
3361            g.l(cx - s * 0.5, cy + s * 0.4)
3362                .l(cx - s, cy + s * 0.9)
3363                .l(cx - s * 0.5, cy + s * 1.4)
3364                .l(cx - s * 0.5, cy + s)
3365                .l(cx + s * 0.6, cy + s)
3366                .l(cx + s * 0.6, cy - s * 0.6)
3367                .z();
3368        }
3369        "actionButtonMovie" => {
3370            g.poly(&[
3371                (cx - s, cy - s * 0.6),
3372                (cx + s * 0.4, cy - s * 0.6),
3373                (cx + s * 0.4, cy + s * 0.6),
3374                (cx - s, cy + s * 0.6),
3375            ]);
3376            g.poly(&[
3377                (cx + s * 0.4, cy),
3378                (cx + s, cy - s * 0.5),
3379                (cx + s, cy + s * 0.5),
3380            ]);
3381        }
3382        "actionButtonDocument" => {
3383            g.poly(&[
3384                (cx - s * 0.7, cy - s),
3385                (cx + s * 0.3, cy - s),
3386                (cx + s * 0.7, cy - s * 0.6),
3387                (cx + s * 0.7, cy + s),
3388                (cx - s * 0.7, cy + s),
3389            ]);
3390        }
3391        "actionButtonSound" => {
3392            g.poly(&[
3393                (cx - s, cy - s * 0.35),
3394                (cx - s * 0.5, cy - s * 0.35),
3395                (cx, cy - s),
3396                (cx, cy + s),
3397                (cx - s * 0.5, cy + s * 0.35),
3398                (cx - s, cy + s * 0.35),
3399            ]);
3400        }
3401        _ => {}
3402    }
3403    Geometry {
3404        paths: vec![fill(body), shade(g.take(), FillMode::DarkenLess)],
3405        text_rect: Rect::new(0.0, 0.0, w, h),
3406        handles: vec![],
3407        sites: mid_sites(w, h),
3408    }
3409}
3410
3411#[cfg(test)]
3412mod tests {
3413    use super::*;
3414    use kurbo::Shape;
3415
3416    #[test]
3417    fn every_catalog_preset_builds_within_bounds() {
3418        for p in CATALOG {
3419            for (w, h) in [
3420                (200.0, 100.0),
3421                (100.0, 200.0),
3422                (50.0, 50.0),
3423                (0.0, 0.0),
3424                (1.0, 300.0),
3425            ] {
3426                let g =
3427                    build(p.name, w, h, &[]).unwrap_or_else(|| panic!("{} did not build", p.name));
3428                assert!(!g.paths.is_empty(), "{} has no paths", p.name);
3429                for s in &g.paths {
3430                    let bb = s.path.bounding_box();
3431                    if w > 0.0
3432                        && h > 0.0
3433                        && !p.name.contains("Connector")
3434                        && !p.name.contains("allout")
3435                    {
3436                        let tol = w.max(h) * 0.35 + 1.0;
3437                        assert!(
3438                            bb.x0 >= -tol && bb.y0 >= -tol && bb.x1 <= w + tol && bb.y1 <= h + tol,
3439                            "{} {w}x{h} out of bounds: {bb:?}",
3440                            p.name
3441                        );
3442                    }
3443                    assert!(
3444                        bb.x0.is_finite() && bb.y1.is_finite(),
3445                        "{} produced non-finite geometry",
3446                        p.name
3447                    );
3448                }
3449                assert!(
3450                    g.text_rect.x0.is_finite() && g.text_rect.y1.is_finite(),
3451                    "{}",
3452                    p.name
3453                );
3454            }
3455        }
3456    }
3457
3458    #[test]
3459    fn hostile_adjust_values_never_panic() {
3460        for p in CATALOG {
3461            for v in [f64::NAN, f64::INFINITY, -1e12, 1e12, -1.0, 0.0] {
3462                let adj = vec![v; 6];
3463                let g = build(p.name, 120.0, 80.0, &adj);
3464                assert!(g.is_some(), "{}", p.name);
3465            }
3466        }
3467        assert!(build("rect", f64::NAN, 10.0, &[]).is_none());
3468        assert!(build("noSuchShape", 10.0, 10.0, &[]).is_none());
3469    }
3470
3471    #[test]
3472    fn catalog_names_are_unique() {
3473        let mut names: Vec<&str> = CATALOG.iter().map(|p| p.name).collect();
3474        names.sort_unstable();
3475        let n = names.len();
3476        names.dedup();
3477        assert_eq!(n, names.len());
3478        assert!(n >= 140, "catalog has {n} presets");
3479    }
3480
3481    #[test]
3482    fn round_rect_handle_maps_back() {
3483        let g = build("roundRect", 200.0, 100.0, &[16667.0]).unwrap();
3484        let h = g.handles.first().unwrap();
3485        assert!((h.value_at(h.pos) - 16667.0).abs() <= 1.0);
3486        assert_eq!(h.value_at(Point::new(-50.0, 0.0)), 0.0);
3487        assert_eq!(h.value_at(Point::new(500.0, 0.0)), 50000.0);
3488    }
3489
3490    #[test]
3491    fn rect_area_and_ellipse_area() {
3492        let g = build("rect", 100.0, 50.0, &[]).unwrap();
3493        assert!((g.paths[0].path.area().abs() - 5000.0).abs() < 1e-6);
3494        let e = build("ellipse", 100.0, 50.0, &[]).unwrap();
3495        let expect = std::f64::consts::PI * 50.0 * 25.0;
3496        assert!((e.paths[0].path.area().abs() - expect).abs() / expect < 0.01);
3497    }
3498
3499    #[test]
3500    fn lines_are_open() {
3501        assert!(build("line", 10.0, 10.0, &[]).unwrap().is_open());
3502        assert!(!build("rect", 10.0, 10.0, &[]).unwrap().is_open());
3503    }
3504}