1#![allow(clippy::collapsible_if)]
5
6mod color;
7mod debug_overlay;
8mod filters;
9mod gradient;
10mod kurbo_css;
11mod layers;
12mod render;
13
14pub use layers::{LayerSite, SceneLayerCounts, latest_scene_layers};
15mod sizing;
16mod text;
17
18use std::collections::HashMap;
19
20#[derive(Clone, Copy, Debug, PartialEq)]
26pub struct PaintRegion {
27 pub scale: f64,
28 pub width: u32,
29 pub height: u32,
30 scene_x: f64,
31 scene_y: f64,
32 document_clip: Rect,
33}
34
35impl PaintRegion {
36 #[must_use]
38 pub fn crop(scale: f64, x: f64, y: f64, width: u32, height: u32) -> Self {
39 let left = x * scale;
40 let top = y * scale;
41 Self {
42 scale,
43 width,
44 height,
45 scene_x: -left,
46 scene_y: -top,
47 document_clip: Rect::new(left, top, left + f64::from(width), top + f64::from(height)),
48 }
49 }
50}
51
52use anyrender::{PaintScene, Scene};
53use blitz_dom::{BaseDocument, NodeId, util::Color};
54use kurbo::Rect;
55use render::BlitzDomPainter;
56
57const FONT_EMBOLDEN_ENABLED: bool = cfg!(any(
58 feature = "font-embolden",
59 all(feature = "apple-font-embolden", target_os = "macos"),
60 all(feature = "apple-font-embolden", target_os = "ios"),
61));
62
63const SELECTION_COLOR: Color = Color::from_rgb8(180, 213, 255);
65
66type CustomWidgetSceneMap = HashMap<(usize, NodeId), Scene>;
68
69pub fn paint_scene(
79 scene: &mut impl PaintScene,
80 doc: &mut BaseDocument,
81 scale: f64,
82 width: u32,
83 height: u32,
84 x_offset: u32,
85 y_offset: u32,
86) {
87 paint_scene_at_time(scene, doc, scale, width, height, x_offset, y_offset, 0.0)
88}
89
90pub fn paint_scene_region(
92 scene: &mut impl PaintScene,
93 doc: &mut BaseDocument,
94 region: PaintRegion,
95) {
96 paint_scene_region_at_time(scene, doc, region, 0.0);
97}
98
99#[allow(clippy::too_many_arguments)]
106pub fn paint_scene_at_time(
107 scene: &mut impl PaintScene,
108 doc: &mut BaseDocument,
109 scale: f64,
110 width: u32,
111 height: u32,
112 x_offset: u32,
113 y_offset: u32,
114 animation_time: f64,
115) {
116 let region = PaintRegion {
117 scale,
118 width,
119 height,
120 scene_x: x_offset as f64,
121 scene_y: y_offset as f64,
122 document_clip: Rect::new(
123 0.0,
124 0.0,
125 f64::from(width) * scale,
126 f64::from(height) * scale,
127 ),
128 };
129 paint_scene_region_at_time(scene, doc, region, animation_time);
130}
131
132fn paint_scene_region_at_time(
133 scene: &mut impl PaintScene,
134 doc: &mut BaseDocument,
135 region: PaintRegion,
136 animation_time: f64,
137) {
138 #[allow(unused_mut)]
143 let mut custom_widget_scenes: CustomWidgetSceneMap = HashMap::new();
144 #[cfg(feature = "custom-widget")]
145 if !doc.stash_paint {
146 build_custom_widget_scenes(&mut custom_widget_scenes, doc, scene, region.scale);
147 }
148
149 let generator = BlitzDomPainter::new_with_clip(
150 doc,
151 region.scale,
152 region.width,
153 region.height,
154 region.scene_x,
155 region.scene_y,
156 region.document_clip,
157 &custom_widget_scenes,
158 animation_time,
159 );
160 generator.paint_scene(scene);
161
162 }
169
170#[cfg(feature = "custom-widget")]
171fn build_custom_widget_scenes(
172 custom_widget_scenes: &mut CustomWidgetSceneMap,
173 doc: &mut BaseDocument,
174 render_ctx: &mut impl anyrender::RenderContext,
175 scale: f64,
176) {
177 let doc_id = doc.id();
178
179 let custom_widget_node_ids = doc.custom_widget_node_ids();
181 for node_id in custom_widget_node_ids.into_iter() {
182 if let Some(scene) = process_custom_widget_node(doc, render_ctx, node_id, scale) {
183 custom_widget_scenes.insert((doc_id, node_id), scene);
184 }
185 }
186
187 let sub_document_node_ids = doc.sub_document_node_ids();
189 for node_id in sub_document_node_ids.into_iter() {
190 if let Some(sub_doc) = doc.get_node_mut(node_id).and_then(|node| node.subdoc_mut()) {
191 let mut inner = sub_doc.inner_mut();
192 build_custom_widget_scenes(custom_widget_scenes, &mut inner, render_ctx, scale);
193 }
194 }
195}
196
197#[cfg(feature = "custom-widget")]
198fn process_custom_widget_node(
199 doc: &mut BaseDocument,
200 render_ctx: &mut impl anyrender::RenderContext,
201 node_id: NodeId,
202 scale: f64,
203) -> Option<Scene> {
204 use blitz_dom::node::{CustomWidgetStatus, ProxyRenderContext};
205
206 let node = doc.get_node_mut(node_id)?;
207 let width = (node.final_layout().size.width as f64 * scale) as u32;
208 let height = (node.final_layout().size.height as f64 * scale) as u32;
209
210 if width == 0 || height == 0 {
211 return None;
212 }
213
214 let style = (*node.stylo_element_data().primary_styles()?).clone();
215 let element = node.data.downcast_element_mut()?;
216 let widget_data = element.custom_widget_data_mut()?;
217
218 let mut render_ctx = ProxyRenderContext {
219 inner: render_ctx,
220 resource_ids: &mut widget_data.active_resource_ids,
221 };
222
223 if widget_data.status == CustomWidgetStatus::Suspended {
224 widget_data.widget.can_create_surfaces(&mut render_ctx);
225 widget_data.status = CustomWidgetStatus::Active;
226 }
227
228 let widget_scene = widget_data
229 .widget
230 .paint(&mut render_ctx, &style, width, height, scale);
231
232 Some(widget_scene)
233}