Skip to main content

batuta/oracle/svg/
mod.rs

1//! SVG Generation Module
2//!
3//! SVG diagram generation with Material Design 3 and Grid Protocol modes:
4//! - Color palettes (light/dark themes, VideoPalette for 1080p)
5//! - Typography scales (Material + VideoTypography for presentations)
6//! - Shape primitives
7//! - Layout engine with collision detection
8//! - Grid Protocol: cell-based 16x9 layout with provable non-overlap
9//! - Specialized renderers for different diagram types
10//! - Linting and validation (including video-mode rules)
11//!
12//! # Toyota Production System Principles
13//!
14//! - **Poka-Yoke**: Grid protocol prevents cell overlap via occupied-set tracking
15//! - **Jidoka**: Lint validation stops on errors
16//! - **Kaizen**: Continuous improvement via validation feedback
17
18pub mod builder;
19pub mod grid_protocol;
20pub mod layout;
21pub mod lint;
22pub mod palette;
23pub mod renderers;
24pub mod shapes;
25pub mod typography;
26
27// Re-exports for convenience
28#[allow(unused_imports)]
29pub use builder::{ComponentDiagramBuilder, SvgBuilder, SvgElement};
30#[allow(unused_imports)]
31pub use grid_protocol::{
32    GridError, GridProtocol, GridSpan, LayoutTemplate, PixelBounds, CANVAS_HEIGHT, CANVAS_WIDTH,
33    CELL_PADDING, CELL_SIZE, GRID_COLS, GRID_ROWS, INTERNAL_PADDING, MIN_BLOCK_GAP, TOTAL_CELLS,
34};
35#[allow(unused_imports)]
36pub use layout::{auto_layout, LayoutEngine, LayoutError, Viewport, GRID_SIZE};
37#[allow(unused_imports)]
38pub use lint::{LintConfig, LintResult, LintRule, LintSeverity, LintViolation, SvgLinter};
39#[allow(unused_imports)]
40pub use palette::{Color, MaterialPalette, SovereignPalette, VideoPalette};
41#[allow(unused_imports)]
42pub use renderers::{RenderMode, ShapeHeavyRenderer, TextHeavyRenderer};
43#[allow(unused_imports)]
44pub use shapes::{ArrowMarker, Circle, Line, Path, PathCommand, Point, Rect, Size, Text};
45#[allow(unused_imports)]
46pub use typography::{
47    FontFamily, FontWeight, MaterialTypography, TextAlign, TextStyle, VideoTypography,
48};
49
50/// Generate a simple component diagram for the Sovereign AI Stack
51///
52/// Uses the grid protocol with Template E (Diagram) for provable
53/// non-overlap layout at 1920x1080.
54pub fn sovereign_stack_diagram() -> String {
55    ShapeHeavyRenderer::new()
56        .template(LayoutTemplate::Diagram)
57        .title("Sovereign AI Stack Architecture")
58        .layer("compute", 50.0, 100.0, 1820.0, 200.0, "Compute Layer")
59        .horizontal_stack(
60            &[("trueno", "Trueno"), ("repartir", "Repartir"), ("trueno_db", "Trueno DB")],
61            Point::new(100.0, 150.0),
62        )
63        .layer("ml", 50.0, 350.0, 1820.0, 200.0, "ML Layer")
64        .horizontal_stack(
65            &[("aprender", "Aprender"), ("entrenar", "Entrenar"), ("realizar", "Realizar")],
66            Point::new(100.0, 400.0),
67        )
68        .layer("orch", 50.0, 600.0, 1820.0, 150.0, "Orchestration")
69        .component("batuta", 100.0, 650.0, "Batuta", "batuta")
70        .build()
71}
72
73/// Generate a documentation diagram
74pub fn documentation_diagram(title: &str, content: &[(&str, &str)]) -> String {
75    let mut renderer = TextHeavyRenderer::new().title(title);
76
77    for (heading, text) in content {
78        renderer = renderer.heading(heading).paragraph(text);
79    }
80
81    renderer.build()
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_sovereign_stack_diagram() {
90        let svg = sovereign_stack_diagram();
91
92        assert!(svg.contains("<svg"));
93        assert!(svg.contains("Trueno"));
94        assert!(svg.contains("Aprender"));
95        assert!(svg.contains("Batuta"));
96        // Grid protocol is now active
97        assert!(svg.contains("GRID PROTOCOL MANIFEST"));
98        assert!(svg.contains("viewBox=\"0 0 1920 1080\""));
99    }
100
101    #[test]
102    fn test_documentation_diagram() {
103        let svg = documentation_diagram(
104            "Test Doc",
105            &[("Section 1", "Content for section 1."), ("Section 2", "Content for section 2.")],
106        );
107
108        assert!(svg.contains("Test Doc"));
109        assert!(svg.contains("Section 1"));
110        assert!(svg.contains("Section 2"));
111    }
112
113    #[test]
114    fn test_svg_builder_quick() {
115        let svg = SvgBuilder::new()
116            .size(400.0, 300.0)
117            .title("Quick Test")
118            .rect("box", 50.0, 50.0, 100.0, 80.0)
119            .text(60.0, 90.0, "Hello")
120            .build();
121
122        assert!(svg.contains("viewBox=\"0 0 400 300\""));
123        assert!(svg.contains("<title>Quick Test</title>"));
124    }
125
126    #[test]
127    fn test_render_mode_selection() {
128        let mode: RenderMode = "shape-heavy".parse().expect("parse failed");
129        assert_eq!(mode, RenderMode::ShapeHeavy);
130
131        let mode: RenderMode = "text-heavy".parse().expect("parse failed");
132        assert_eq!(mode, RenderMode::TextHeavy);
133    }
134
135    #[test]
136    fn test_color_palette() {
137        let palette = MaterialPalette::light();
138        assert_eq!(palette.primary.to_css_hex(), "#6750A4");
139
140        let palette = MaterialPalette::dark();
141        assert_eq!(palette.primary.to_css_hex(), "#D0BCFF");
142    }
143
144    #[test]
145    fn test_typography_scale() {
146        let typo = MaterialTypography::default();
147        assert_eq!(typo.display_large.size, 57.0);
148        assert_eq!(typo.body_medium.size, 14.0);
149        assert_eq!(typo.label_small.size, 11.0);
150    }
151
152    #[test]
153    fn test_layout_grid_snap() {
154        let engine = LayoutEngine::new(Viewport::default());
155
156        // 8px grid
157        assert_eq!(engine.snap_to_grid(10.0), 8.0);
158        assert_eq!(engine.snap_to_grid(15.0), 16.0);
159        assert_eq!(engine.snap_to_grid(8.0), 8.0);
160    }
161
162    #[test]
163    fn test_lint_basic() {
164        let linter = SvgLinter::new();
165
166        // Valid color
167        let palette = MaterialPalette::light();
168        assert!(linter.lint_color(&palette.primary, None).is_none());
169
170        // Invalid color
171        assert!(linter.lint_color(&Color::rgb(1, 2, 3), None).is_some());
172    }
173
174    #[test]
175    fn test_shapes() {
176        let rect = Rect::new(10.0, 20.0, 100.0, 50.0);
177        assert_eq!(rect.center().x, 60.0);
178        assert_eq!(rect.center().y, 45.0);
179
180        let circle = Circle::new(50.0, 50.0, 25.0);
181        assert!(circle.contains(&Point::new(50.0, 50.0)));
182    }
183
184    #[test]
185    fn test_output_size_limit() {
186        // Generate a complex diagram
187        let svg = ShapeHeavyRenderer::new()
188            .title("Large Diagram")
189            .horizontal_stack(
190                &[
191                    ("c1", "Component 1"),
192                    ("c2", "Component 2"),
193                    ("c3", "Component 3"),
194                    ("c4", "Component 4"),
195                    ("c5", "Component 5"),
196                ],
197                Point::new(50.0, 200.0),
198            )
199            .build();
200
201        // Must be under 100KB
202        assert!(svg.len() < 100_000, "SVG too large: {} bytes", svg.len());
203    }
204}