Skip to main content

devpage_design/render/
mod.rs

1//! Scene → pixels. The engine lays a scene out ([`layout`]: taffy, parley),
2//! paints each node's own commands ([`painter`]) into a [`Drawing`]
3//! ([`drawing`]): its display list in chunks under property trees, kept and
4//! brought up to date by what changes ([`update`]). A backend paints any area
5//! of it through [`crate::display::paint`].
6//! This module holds the renderer — its fonts and text shaping — and the
7//! commands' types.
8
9use parley::{
10    Alignment, AlignmentOptions, FontContext, FontFamily, FontWeight, Layout as Text,
11    LayoutContext, LineHeight, StyleProperty,
12};
13use std::collections::{BTreeSet, HashMap};
14use taffy::prelude::*;
15
16use kurbo::{Affine, BezPath, Rect as KRect};
17use peniko::{Color as PColor, FontData, Gradient, ImageData};
18
19use crate::fonts::{self, FontFiles, FontId};
20use crate::resolve::{self, ImageId, Origin, TextAlign};
21
22mod drawing;
23#[cfg(test)]
24mod examples;
25mod layout;
26mod painter;
27#[cfg(test)]
28pub(crate) mod testing;
29mod update;
30
31use drawing::*;
32pub use drawing::{Damage, Drawing};
33use layout::*;
34use painter::*;
35
36const FONTS: [&[u8]; 4] = [
37    include_bytes!("../../fonts/Inter-Regular.ttf"),
38    include_bytes!("../../fonts/Inter-Medium.ttf"),
39    include_bytes!("../../fonts/Inter-SemiBold.ttf"),
40    include_bytes!("../../fonts/Inter-Bold.ttf"),
41];
42
43/// The fonts and text layout state a renderer keeps between paints.
44pub struct Renderer {
45    fonts: FontContext,
46    /// The embedded Inter faces, each under one blob for the renderer's life.
47    inter: Vec<parley::fontique::Blob<u8>>,
48    /// The font files registered besides Inter — those of a scene laid out —
49    /// or `None` before anything is.
50    registered: Option<BTreeSet<FontId>>,
51    layouts: LayoutContext<()>,
52    /// While a display list is made: the image each image file is painted
53    /// from in it, and every image it paints, in the order it first does,
54    /// with the attribute that asked for it.
55    blobs: HashMap<ImageId, ImageData>,
56    painted: Vec<(Painted, Origin)>,
57}
58
59impl Default for Renderer {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65/// A node's box after layout, in the scene's pixels.
66#[derive(Debug, Clone, Copy, PartialEq)]
67pub struct Frame {
68    pub x: f32,
69    pub y: f32,
70    pub width: f32,
71    pub height: f32,
72}
73
74/// One glyph of a run: its id in its font and where its origin sits, in the
75/// run's coordinates.
76#[derive(Clone, Copy, Debug, PartialEq)]
77pub struct Glyph {
78    pub id: u32,
79    pub x: f32,
80    pub y: f32,
81}
82
83/// What a `Draw::Fill` covers its path with.
84pub enum Paint {
85    Solid(PColor),
86    /// A gradient whose points are in scene pixels.
87    Gradient(Gradient),
88    /// An image, placed by the transform from its pixels to scene pixels.
89    Image(ImageData, Affine),
90}
91
92/// One drawing command, in its chunk's coordinates: what both vello backends
93/// replay. Every decision about the drawing is made before a command is
94/// made; a backend only translates it into its library's call. The layers it
95/// is drawn in are nodes of the property trees, not commands.
96pub enum Draw {
97    Fill(BezPath, Paint),
98    Stroke(BezPath, f32, PColor),
99    /// A glyph run: its font, size and color, its point on the font's
100    /// variation axes — empty for a static font — and its glyphs.
101    Glyphs(FontData, f32, PColor, Vec<i16>, Vec<Glyph>),
102}
103
104/// Why a scene can't be drawn: images and shadows that don't fit the GPU's
105/// atlas — `at` the attribute that broke it, when one did.
106#[derive(Debug, Clone, PartialEq)]
107pub struct Undrawable {
108    pub message: String,
109    pub at: Option<Origin>,
110}
111
112impl std::fmt::Display for Undrawable {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        f.write_str(&self.message)
115    }
116}
117
118impl Renderer {
119    pub fn new() -> Renderer {
120        let fonts = FontContext {
121            collection: parley::fontique::Collection::new(parley::fontique::CollectionOptions {
122                shared: false,
123                system_fonts: false,
124            }),
125            source_cache: parley::fontique::SourceCache::default(),
126        };
127        let mut renderer = Renderer {
128            fonts,
129            inter: FONTS
130                .iter()
131                .map(|font| parley::fontique::Blob::new(std::sync::Arc::new(*font)))
132                .collect(),
133            registered: None,
134            layouts: LayoutContext::new(),
135            blobs: HashMap::new(),
136            painted: Vec::new(),
137        };
138        renderer.register(&FontFiles::new());
139        renderer
140    }
141
142    /// Makes the scene's font files available to layout, each under a
143    /// family name of its own. The collection keeps what it has while a
144    /// scene uses no other font file; otherwise it starts again from Inter
145    /// and this scene's, so it never holds more than one scene's fonts.
146    fn register(&mut self, files: &FontFiles) {
147        if let Some(registered) = &self.registered
148            && files.keys().all(|id| registered.contains(id))
149        {
150            return;
151        }
152        let collection = &mut self.fonts.collection;
153        collection.clear();
154        for blob in &self.inter {
155            collection.register_fonts(blob.clone(), None);
156        }
157        for (id, file) in files {
158            let family = id.family();
159            collection.register_fonts(
160                file.blob.clone(),
161                Some(parley::fontique::FontInfoOverride {
162                    family_name: Some(&family),
163                    ..Default::default()
164                }),
165            );
166        }
167        self.registered = Some(files.keys().copied().collect());
168    }
169
170    fn text_layout(&mut self, text: &resolve::TextStyle, width: Option<f32>) -> Text<()> {
171        let mut builder = self
172            .layouts
173            .ranged_builder(&mut self.fonts, &text.content, 1.0, true);
174        let family = match text.font {
175            Some(id) => id.family(),
176            None => fonts::INTER.to_string(),
177        };
178        builder.push_default(StyleProperty::FontFamily(FontFamily::named(&family)));
179        builder.push_default(StyleProperty::FontSize(text.size));
180        builder.push_default(StyleProperty::FontWeight(FontWeight::new(
181            text.weight as f32,
182        )));
183        builder.push_default(StyleProperty::LineHeight(LineHeight::FontSizeRelative(
184            text.line_height,
185        )));
186        let mut layout = builder.build(&text.content);
187        layout.break_all_lines(width);
188        let align = match text.align {
189            TextAlign::Left => Alignment::Left,
190            TextAlign::Center => Alignment::Center,
191            TextAlign::Right => Alignment::Right,
192        };
193        layout.align(align, AlignmentOptions::default());
194        layout
195    }
196}
197
198fn frame_rect(f: Frame) -> KRect {
199    KRect::new(
200        f.x as f64,
201        f.y as f64,
202        (f.x + f.width) as f64,
203        (f.y + f.height) as f64,
204    )
205}