devpage_design/render/
mod.rs1use 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
43pub struct Renderer {
45 fonts: FontContext,
46 inter: Vec<parley::fontique::Blob<u8>>,
48 registered: Option<BTreeSet<FontId>>,
51 layouts: LayoutContext<()>,
52 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#[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 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}