Skip to main content

ravenrs_report/
paint.rs

1//! Draws document pages into a RavenRS [`Scene`] for on-screen previews.
2
3use ravenrs_core::{Color, ImageQuad, Point, Quad, Rect, Scene};
4use ravenrs_text::TextSystem;
5
6use crate::document::{Item, Page};
7use crate::engine::text_attrs;
8
9/// Paints `page` with its top-left corner at `origin`, `scale` logical pixels
10/// per point. Draws the white paper with a soft shadow, then the content.
11pub fn paint_page(page: &Page, text: &mut TextSystem, scene: &mut Scene, origin: Point, scale: f32) {
12    let at = |x: f32, y: f32| Point::new(origin.x + x * scale, origin.y + y * scale);
13    let rect = |r: &Rect| {
14        Rect::from_origin_size(
15            at(r.x(), r.y()),
16            ravenrs_core::Size::new(r.width() * scale, r.height() * scale),
17        )
18    };
19    let paper = Rect::new(origin.x, origin.y, page.width * scale, page.height * scale);
20    scene.push_quad(Quad::new(paper, Color::WHITE).shadow(ravenrs_core::Shadow {
21        offset: Point::new(0.0, 2.0),
22        blur: 12.0,
23        color: Color::rgba(0.0, 0.0, 0.0, 0.35),
24    }));
25    scene.push_clip(paper);
26    for item in &page.items {
27        match item {
28            Item::Rect { rect: r, fill, stroke, radius } => {
29                let mut q = Quad::new(rect(r), fill.unwrap_or(Color::TRANSPARENT)).radius(radius * scale);
30                if let Some((w, c)) = stroke {
31                    q = q.border((w * scale).max(0.5), *c);
32                }
33                scene.push_quad(q);
34            }
35            Item::Ellipse { rect: r, fill, stroke } => {
36                let r = rect(r);
37                let mut q =
38                    Quad::new(r, fill.unwrap_or(Color::TRANSPARENT)).radius(r.width().min(r.height()) / 2.0);
39                if let Some((w, c)) = stroke {
40                    q = q.border((w * scale).max(0.5), *c);
41                }
42                scene.push_quad(q);
43            }
44            Item::Line { from, to, width, color } => {
45                // Lines are axis-aligned in practice (borders, rules); a slanted
46                // one is drawn as a thin box along its bounding rectangle's diagonal span.
47                let (a, b) = (at(from.x, from.y), at(to.x, to.y));
48                let w = (width * scale).max(0.75);
49                let r = if (a.y - b.y).abs() < 0.01 {
50                    Rect::new(a.x.min(b.x), a.y - w / 2.0, (a.x - b.x).abs(), w)
51                } else if (a.x - b.x).abs() < 0.01 {
52                    Rect::new(a.x - w / 2.0, a.y.min(b.y), w, (a.y - b.y).abs())
53                } else {
54                    // Approximate with dots along the segment.
55                    let steps = ((b.x - a.x).hypot(b.y - a.y) / w).ceil().max(1.0) as usize;
56                    for i in 0..=steps {
57                        let t = i as f32 / steps as f32;
58                        let p = Point::new(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t);
59                        scene.push_quad(
60                            Quad::new(Rect::new(p.x - w / 2.0, p.y - w / 2.0, w, w), *color).radius(w / 2.0),
61                        );
62                    }
63                    continue;
64                };
65                scene.push_quad(Quad::new(r, *color));
66            }
67            Item::Text(t) => {
68                scene.push_clip(rect(&t.clip));
69                let attrs = text_attrs(t.size * scale, t.bold);
70                let attrs = ravenrs_text::TextAttrs { line_height: t.line_height * scale, ..attrs };
71                for line in &t.lines {
72                    let mut layout = text.layout(&line.text, &attrs);
73                    layout.measure(None);
74                    layout.paint(at(line.x, line.top), t.color, scene);
75                }
76                scene.pop_clip();
77            }
78            Item::Image { rect: r, clip, image } => {
79                scene.push_clip(rect(clip));
80                scene.push_image(ImageQuad::new(rect(r), image.pixels().clone()));
81                scene.pop_clip();
82            }
83            Item::Vector { rect: r, shapes } => {
84                // Twice as sharp as needed, for high-density screens.
85                if let Some(image) = crate::vector::rasterize(shapes, *r, scale * 2.0) {
86                    scene.push_image(ImageQuad::new(rect(r), image));
87                }
88            }
89            // Nothing to print: they matter in the exported file.
90            Item::Link { .. } | Item::Bookmark { .. } => {}
91        }
92    }
93    scene.pop_clip();
94}