Skip to main content

devpage_design/
lib.rs

1//! The design engine: parses `.design` files, resolves them into scenes, lays
2//! them out and paints them. It does no I/O, so the same code runs natively
3//! in devpage and compiled to WebAssembly in the browser.
4//!
5//! **The core** links no rasterizer: text → syntax tree ([`parse`]) → scene
6//! ([`resolve`], [`fonts`]; what a scene is, and the edits from one to the
7//! next, are [`scene`]'s) → layout, the painter's commands and a kept,
8//! updatable drawing ([`render`]) → chunks under property trees with their
9//! index, the one paint entry point and hit testing ([`display`]) → tiles,
10//! their cache and the damage that makes them stale ([`tiles`]) — and a
11//! design as it is held while shown, its scene and its drawings brought
12//! along by each edit, with one rule for a scene that can't be drawn
13//! ([`design`]); with the
14//! shadows' sampling ([`shadow`]), the GPU atlas's model ([`atlas`]), the
15//! wire form of scenes and their edits ([`wire`], [`proto`]) and the rule two
16//! drawings are compared by ([`compare`]). Its geometry and colours are
17//! kurbo's and peniko's, depended on directly.
18//!
19//! **Adapters** plug into its one port, [`display::Target`] — paint this area
20//! at this scale: [`cpu`], `vello_cpu`; the module's `vello` on WebGPU. And
21//! [`png`] writes the PNG band by band from whatever gives it tiles. The
22//! core names no adapter; a test holds it to that.
23
24// The core.
25pub mod atlas;
26pub mod compare;
27pub mod design;
28pub mod display;
29pub mod fonts;
30pub mod ids;
31pub mod parse;
32pub mod prefix_sums;
33pub mod proto;
34pub mod render;
35pub mod resolve;
36pub mod scene;
37pub mod shadow;
38pub mod tiles;
39pub mod tree;
40pub mod wire;
41
42// Adapters.
43pub mod cpu;
44pub mod png;
45
46use std::fmt;
47
48pub use design::Design;
49pub use kurbo::Rect as KRect;
50pub use render::{Damage, Drawing, Renderer};
51pub use resolve::resolve;
52pub use scene::{Files, Scene, Theme};
53
54/// Something wrong with a design, at a place in one of its files.
55#[derive(Debug, Clone, PartialEq)]
56pub struct Problem {
57    pub file: String,
58    pub pos: parse::Pos,
59    pub message: String,
60}
61
62impl fmt::Display for Problem {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(
65            f,
66            "{}:{}:{}: {}",
67            self.file, self.pos.line, self.pos.col, self.message
68        )
69    }
70}
71
72// SAFETY: a drawing isn't `Send` only because taffy's `CompactLength`, in
73// the layout tree it keeps, packs a length into a `*const ()`. That value is
74// a pointer only for a `calc()` length, behind taffy's `calc` feature, which
75// the engine neither enables nor makes — every length it gives taffy is a
76// number or a keyword, bits in the word — so nothing in a drawing points
77// anywhere, and a server can keep one behind a lock and paint it from any
78// thread.
79unsafe impl Send for render::Drawing {}
80
81/// The core names no adapter and links no rasterizer: no module of it
82/// mentions `vello_cpu`, `vello`, `wgpu`, the PNG encoder, or [`cpu`] and
83/// [`png`] — outside its tests. Dependencies point inward.
84#[cfg(test)]
85#[test]
86fn the_core_names_no_adapter() {
87    let src = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
88    let adapters = ["lib.rs", "cpu", "png"];
89    let forbidden = [
90        "vello_cpu",
91        "vello::",
92        "wgpu",
93        "png::Encoder",
94        "png::Stream",
95        "crate::cpu",
96        "crate::png",
97    ];
98    let mut stack = vec![src.clone()];
99    while let Some(dir) = stack.pop() {
100        for entry in std::fs::read_dir(&dir).unwrap() {
101            let path = entry.unwrap().path();
102            let rel = path.strip_prefix(&src).unwrap();
103            let top = rel
104                .components()
105                .next()
106                .unwrap()
107                .as_os_str()
108                .to_string_lossy();
109            let name = path.file_name().unwrap().to_string_lossy().into_owned();
110            if adapters.contains(&top.trim_end_matches(".rs"))
111                || adapters.contains(&top.as_ref())
112                || name == "tests.rs"
113                || name == "testing.rs"
114                || name == "examples.rs"
115            {
116                continue;
117            }
118            if path.is_dir() {
119                stack.push(path);
120                continue;
121            }
122            let text = std::fs::read_to_string(&path).unwrap();
123            // A module's own unit tests sit at its end.
124            let text = text.split("#[cfg(test)]\nmod tests {").next().unwrap();
125            for word in forbidden {
126                assert!(!text.contains(word), "{} names {word}", rel.display());
127            }
128        }
129    }
130}