Skip to main content

devpage_design/render/
mod.rs

1//! Scene → drawing. 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`]). No pixels are made here:
5//! a backend paints any area of it through [`crate::display::paint`].
6//! This module holds the renderer — its fonts and text shaping.
7
8use parley::{
9    Alignment, AlignmentOptions, FontContext, FontFamily, FontWeight, Layout as Text,
10    LayoutContext, LineHeight, StyleProperty,
11};
12use std::collections::{BTreeSet, HashMap};
13use taffy::prelude::*;
14
15use kurbo::Rect as KRect;
16use peniko::ImageData;
17
18use crate::display::{Draw, Frame, Glyph, Paint};
19use crate::fonts::{self, FontFiles, FontId};
20use crate::scene::{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/// Why a scene can't be drawn: images and shadows that don't fit the GPU's
66/// atlas — `at` the attribute that broke it, when one did.
67#[derive(Debug, Clone, PartialEq)]
68pub struct Undrawable {
69    pub message: String,
70    pub at: Option<Origin>,
71}
72
73impl std::fmt::Display for Undrawable {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.write_str(&self.message)
76    }
77}
78
79impl Renderer {
80    pub fn new() -> Renderer {
81        let fonts = FontContext {
82            collection: parley::fontique::Collection::new(parley::fontique::CollectionOptions {
83                shared: false,
84                system_fonts: false,
85            }),
86            source_cache: parley::fontique::SourceCache::default(),
87        };
88        let mut renderer = Renderer {
89            fonts,
90            inter: FONTS
91                .iter()
92                .map(|font| parley::fontique::Blob::new(std::sync::Arc::new(*font)))
93                .collect(),
94            registered: None,
95            layouts: LayoutContext::new(),
96            blobs: HashMap::new(),
97            painted: Vec::new(),
98        };
99        renderer.register(&FontFiles::new());
100        renderer
101    }
102
103    /// Makes the scene's font files available to layout, each under a
104    /// family name of its own. The collection keeps what it has while a
105    /// scene uses no other font file; otherwise it starts again from Inter
106    /// and this scene's, so it never holds more than one scene's fonts.
107    fn register(&mut self, files: &FontFiles) {
108        if let Some(registered) = &self.registered
109            && files.keys().all(|id| registered.contains(id))
110        {
111            return;
112        }
113        let collection = &mut self.fonts.collection;
114        collection.clear();
115        for blob in &self.inter {
116            collection.register_fonts(blob.clone(), None);
117        }
118        for (id, file) in files {
119            let family = id.family();
120            collection.register_fonts(
121                file.blob.clone(),
122                Some(parley::fontique::FontInfoOverride {
123                    family_name: Some(&family),
124                    ..Default::default()
125                }),
126            );
127        }
128        self.registered = Some(files.keys().copied().collect());
129    }
130
131    fn text_layout(&mut self, text: &scene::TextStyle, width: Option<f32>) -> Text<()> {
132        let mut builder = self
133            .layouts
134            .ranged_builder(&mut self.fonts, &text.content, 1.0, true);
135        let family = match text.font {
136            Some(id) => id.family(),
137            None => fonts::INTER.to_string(),
138        };
139        builder.push_default(StyleProperty::FontFamily(FontFamily::named(&family)));
140        builder.push_default(StyleProperty::FontSize(text.size));
141        builder.push_default(StyleProperty::FontWeight(FontWeight::new(
142            text.weight as f32,
143        )));
144        builder.push_default(StyleProperty::LineHeight(LineHeight::FontSizeRelative(
145            text.line_height,
146        )));
147        let mut layout = builder.build(&text.content);
148        layout.break_all_lines(width);
149        let align = match text.align {
150            TextAlign::Left => Alignment::Left,
151            TextAlign::Center => Alignment::Center,
152            TextAlign::Right => Alignment::Right,
153        };
154        layout.align(align, AlignmentOptions::default());
155        layout
156    }
157}
158
159fn frame_rect(f: Frame) -> KRect {
160    KRect::new(
161        f.x as f64,
162        f.y as f64,
163        (f.x + f.width) as f64,
164        (f.y + f.height) as f64,
165    )
166}