1use 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
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, Copy, PartialEq)]
67pub struct Frame {
68 pub x: f32,
69 pub y: f32,
70 pub width: f32,
71 pub height: f32,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq)]
77pub struct Glyph {
78 pub id: u32,
79 pub x: f32,
80 pub y: f32,
81}
82
83pub enum Paint {
85 Solid(PColor),
86 Gradient(Gradient),
88 Image(ImageData, Affine),
90}
91
92pub enum Draw {
97 Fill(BezPath, Paint),
98 Stroke(BezPath, f32, PColor),
99 Glyphs(FontData, f32, PColor, Vec<i16>, Vec<Glyph>),
102}
103
104#[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 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}