1use std::fmt::Write;
4
5use super::spatial::Bounds;
6use super::{Node, NodeKind};
7use crate::display_list::{Builder, mapped_bounds};
8use crate::drawing::BlendMode;
9use crate::validation::ValidationState;
10use crate::{
11 Canvas, DrawCommand, DrawContext, PixelSize, Size, Transform, ValidatedDisplayList,
12 ValidationError, ValidationLimits,
13};
14
15mod inspection;
16#[cfg(test)]
17mod tests;
18
19pub use inspection::NodeInspection;
20
21const CHILD_PATH_SLACK: usize = 24;
23
24impl Node {
25 pub fn compile(
82 &self,
83 design_size: Size,
84 output_size: PixelSize,
85 limits: ValidationLimits,
86 ) -> Result<ValidatedDisplayList, ValidationError> {
87 self.compile_in_fonts(design_size, output_size, limits, None)
88 }
89
90 pub fn compile_with_fonts(
92 &self,
93 design_size: Size,
94 output_size: PixelSize,
95 limits: ValidationLimits,
96 fonts: impl Into<crate::asset::environment::FontEnvironment>,
97 ) -> Result<ValidatedDisplayList, ValidationError> {
98 let fonts = fonts.into();
99 self.compile_in_fonts(design_size, output_size, limits, Some(&fonts))
100 }
101
102 fn compile_in_fonts(
103 &self,
104 design_size: Size,
105 output_size: PixelSize,
106 limits: ValidationLimits,
107 fonts: Option<&crate::asset::environment::FontEnvironment>,
108 ) -> Result<ValidatedDisplayList, ValidationError> {
109 let mut context = DrawContext::default();
110 if let Some(fonts) = fonts {
111 context = context.with_fonts(fonts.clone());
112 }
113 self.compile_with_context(design_size, output_size, limits, &context)
114 }
115
116 pub fn compile_with_context(
118 &self,
119 design_size: Size,
120 output_size: PixelSize,
121 limits: ValidationLimits,
122 drawing: &DrawContext,
123 ) -> Result<ValidatedDisplayList, ValidationError> {
124 let (root, scale) = output_size.mapping(design_size)?;
125 let mut context = drawing.clone().with_validation_limits(limits);
126 context.device_scale = scale;
127 let mut source = ValidationState::new(limits)?;
128 let plan = plan(self, "node", 1, &context, &mut source)?;
129 let mut output = Builder::new(output_size, limits)?;
130 plan.emit(root, &mut output)?;
131 output.finish_validated()
132 }
133}
134
135struct Plan {
136 transform: Transform,
137 bounds: Bounds,
138 opacity: f64,
139 blend_mode: BlendMode,
140 filters: Vec<crate::Filter>,
141 clip: Option<crate::PathClip>,
142 rect_clip: Option<crate::RectClip>,
143 shape_clip: Option<crate::ShapeClip>,
144 mask: Option<(Box<Plan>, crate::drawing::MaskMode)>,
145 path: String,
146 content: Content,
147 has_commands: bool,
148 accumulate: bool,
149}
150
151enum Content {
152 Leaf(Vec<DrawCommand>),
153 Children(Vec<Plan>),
154}
155
156fn plan(
157 node: &Node,
158 path: &str,
159 depth: usize,
160 context: &DrawContext,
161 state: &mut ValidationState,
162) -> Result<Plan, ValidationError> {
163 let context = &context
164 .clone()
165 .with_time(node.data.time.unwrap_or(context.time()));
166 let path = node
167 .data
168 .name
169 .as_ref()
170 .map_or_else(|| path.to_owned(), |name| format!("{path}(name={name})"));
171 state.enter(&path, depth)?;
172 let opacity = node.data.opacity;
173 if !opacity.is_finite() || !(0.0..=1.0).contains(&opacity) {
174 return Err(ValidationError::invalid(
175 &path,
176 "opacity",
177 "Require finite opacity in 0..=1",
178 ));
179 }
180 node.validate_clip(&path, depth, state)?;
181 let mask = node
182 .data
183 .mask
184 .as_ref()
185 .map(|mask| {
186 plan(
187 &mask.node,
188 &format!("{path}.mask"),
189 depth + 1,
190 context,
191 state,
192 )
193 .map(|planned| (Box::new(planned), mask.mode))
194 })
195 .transpose()?;
196 let (content, bounds, has_commands) = match &node.data.kind {
197 NodeKind::Empty => (Content::Children(Vec::new()), Bounds::Empty, false),
198 NodeKind::Relative(relative) => {
199 let mut subject = plan(
200 &relative.node,
201 &format!("{path}.subject"),
202 depth + 1,
203 context,
204 state,
205 )?;
206 let reference = relative.reference.measure_bounds(
207 &format!("{path}.reference"),
208 depth + 1,
209 state,
210 context,
211 )?;
212 let translation =
213 relative.translation(subject.bounds.rect(&path)?, reference.rect(&path)?)?;
214 subject.transform = subject.transform.then(translation);
215 subject.bounds = subject.bounds.transformed(translation, &path)?;
216 let bounds = subject.bounds;
217 let has_commands = subject.has_commands;
218 (Content::Children(vec![subject]), bounds, has_commands)
219 }
220 NodeKind::Leaf(drawable) => {
221 let leaf = record_leaf(drawable.as_ref(), &path, depth, context, state);
222 let (bounds, commands) = match leaf {
223 Err(error)
224 if context.marks_invalid_geometry()
225 && error.kind == crate::ValidationErrorKind::InvalidValue =>
226 {
227 invalid_geometry_marker(&path, depth, state)?
228 }
229 leaf => leaf?,
230 };
231 let has_commands = !commands.is_empty();
232 (Content::Leaf(commands), Bounds::Known(bounds), has_commands)
233 }
234 NodeKind::Deferred(content) => {
235 let resolved = content.resolve(context).map_err(|mut error| {
236 error.path = format!("{path}.{}", error.path);
237 error
238 })?;
239 let expanded = plan(
240 &resolved,
241 &format!("{path}.resolved"),
242 depth + 1,
243 context,
244 state,
245 )?;
246 let bounds = expanded.bounds;
247 let has_commands = expanded.has_commands;
248 (Content::Children(vec![expanded]), bounds, has_commands)
249 }
250 NodeKind::Group(children)
251 | NodeKind::Layout(_, children)
252 | NodeKind::Accumulate(children) => {
253 let mut planned = Vec::with_capacity(children.len());
254 let mut child_path = String::with_capacity(path.len() + CHILD_PATH_SLACK);
255 for (index, child) in children.iter().enumerate() {
256 child_path.clear();
257 let _ = write!(child_path, "{path}.children[{index}]");
258 planned.push((index, plan(child, &child_path, depth + 1, context, state)?));
259 }
260 if let NodeKind::Layout(layout, _) = node.data.kind {
261 let geometry: Vec<_> = planned
262 .iter()
263 .map(|(index, child)| (*index, child.bounds))
264 .collect();
265 let baselines = layout.baselines(children, context, &path)?;
266 for ((_, child), placement) in planned
267 .iter_mut()
268 .zip(layout.placements(&geometry, &baselines, &path)?)
269 {
270 if let Some((placement, bounds)) = placement {
271 child.transform = child.transform.then(placement.transform);
272 child.bounds = bounds;
273 }
274 }
275 }
276 planned.sort_by_key(|(index, _)| children[*index].data.z_index);
277 let mut bounds = Bounds::Empty;
278 let mut has_commands = false;
279 let children = planned
280 .into_iter()
281 .map(|(_, child)| {
282 bounds = bounds.union(child.bounds);
283 has_commands |= child.has_commands;
284 child
285 })
286 .collect();
287 (Content::Children(children), bounds, has_commands)
288 }
289 };
290 let placement = node.data.placement.resolve(bounds, &path)?;
291 let bounds = bounds.transformed(placement.transform, &path)?;
292 Ok(Plan {
293 transform: placement.transform,
294 bounds,
295 opacity,
296 blend_mode: node.data.blend_mode,
297 filters: node.data.filters.clone(),
298 clip: node.data.clip.clone(),
299 rect_clip: node.data.rect_clip,
300 shape_clip: node.data.shape_clip.clone(),
301 mask,
302 path,
303 content,
304 has_commands,
305 accumulate: matches!(node.data.kind, NodeKind::Accumulate(_)),
306 })
307}
308
309fn record_leaf(
310 drawable: &dyn crate::Drawable,
311 path: &str,
312 depth: usize,
313 context: &DrawContext,
314 state: &mut ValidationState,
315) -> Result<(crate::Rect, Vec<DrawCommand>), ValidationError> {
316 let bounds = drawable.bounds(context)?;
317 crate::drawing::validate_rect(bounds, path, "bounds")?;
318 let commands = Canvas::new(state, path, depth + 1).record(drawable, context)?;
319 Ok((bounds, commands))
320}
321
322fn invalid_geometry_marker(
324 path: &str,
325 depth: usize,
326 state: &mut ValidationState,
327) -> Result<(crate::Rect, Vec<DrawCommand>), ValidationError> {
328 let bounds = crate::Rect::new(0.0, 0.0, 24.0, 24.0);
329 let mut canvas = Canvas::new(state, path, depth + 1);
330 canvas.fill_rect(bounds, crate::Color::from_rgb_float(1.0, 0.0, 1.0))?;
331 Ok((bounds, canvas.finish()?))
332}
333
334impl Plan {
335 fn input_bounds(&self, parent: Transform) -> Result<Bounds, ValidationError> {
336 let transform = self.transform.then(parent);
337 match &self.content {
338 Content::Leaf(_) => self.bounds.transformed(parent, &self.path),
339 Content::Children(children) => {
340 let mut bounds = Bounds::Empty;
341 for child in children {
342 bounds = bounds.union(child.visual_bounds(transform)?);
343 }
344 Ok(bounds)
345 }
346 }
347 }
348
349 fn visual_bounds(&self, parent: Transform) -> Result<Bounds, ValidationError> {
350 let bounds = self.input_bounds(parent)?;
351 if let Bounds::Known(mut bounds) = bounds {
352 for filter in &self.filters {
353 bounds = filter
354 .device(self.transform.then(parent), &self.path)?
355 .extent(bounds, &self.path)?
356 .0;
357 }
358 Ok(Bounds::Known(bounds))
359 } else {
360 Ok(bounds)
361 }
362 }
363
364 fn emit(self, parent: Transform, output: &mut Builder) -> Result<(), ValidationError> {
365 let transform = self.transform.then(parent);
366 if !transform.is_finite() {
367 return Err(ValidationError::invalid(
368 &self.path,
369 "transform",
370 "Composed device transform must remain finite",
371 ));
372 }
373 let input_bounds = self.input_bounds(parent)?;
374 let visual_bounds = self.visual_bounds(parent)?;
375 let filters = self
376 .filters
377 .iter()
378 .map(|filter| filter.device(transform, &self.path))
379 .collect::<Result<Vec<_>, _>>()?;
380 let rect_clip = self
381 .rect_clip
382 .map(|mut clip| {
383 clip.transform = clip.transform.then(transform);
384 mapped_bounds(
385 clip.bounds,
386 clip.transform,
387 &format!("{}.rect_clip", self.path),
388 )?;
389 Ok::<_, ValidationError>(clip)
390 })
391 .transpose()?;
392 let shape_clip = self
393 .shape_clip
394 .map(|mut clip| {
395 clip.transform = clip.transform.then(transform);
396 clip.validate(&format!("{}.shape_clip", self.path))?;
397 Ok::<_, ValidationError>(clip)
398 })
399 .transpose()?;
400 let isolate = (self.accumulate
401 || self.opacity != 1.0
402 || self.blend_mode != BlendMode::SourceOver
403 || self.clip.is_some()
404 || self.rect_clip.is_some()
405 || shape_clip.is_some()
406 || self.mask.is_some()
407 || !filters.is_empty())
408 && self.has_commands;
409 if isolate {
410 let bounds = visual_bounds.rect(&self.path)?;
411 let clip = self.clip.map(|mut clip| {
412 clip.transform = clip.transform.then(transform);
413 clip
414 });
415 let command = if let Some(shape_clip) = shape_clip {
416 DrawCommand::PushShapeLayer {
417 opacity: self.opacity,
418 bounds,
419 blend_mode: self.blend_mode,
420 clip,
421 rect_clip,
422 shape_clip,
423 }
424 } else if let Some(rect_clip) = rect_clip {
425 DrawCommand::PushRectLayer {
426 opacity: self.opacity,
427 bounds,
428 blend_mode: self.blend_mode,
429 clip,
430 rect_clip,
431 }
432 } else {
433 DrawCommand::PushLayer {
434 opacity: self.opacity,
435 bounds,
436 blend_mode: self.blend_mode,
437 clip,
438 }
439 };
440 output.push(command, &format!("{}.push_layer", self.path))?;
441 }
442 if self.has_commands && !filters.is_empty() {
443 output.push(
444 DrawCommand::PushFilterLayer {
445 bounds: input_bounds.rect(&self.path)?,
446 filters,
447 },
448 &format!("{}.filters", self.path),
449 )?;
450 }
451 match self.content {
452 Content::Leaf(commands) => {
453 let mut command_path = String::with_capacity(self.path.len() + CHILD_PATH_SLACK);
454 for (index, mut command) in commands.into_iter().enumerate() {
455 match &mut command {
456 DrawCommand::FillRect {
457 transform: local, ..
458 }
459 | DrawCommand::Fill {
460 transform: local, ..
461 }
462 | DrawCommand::ParticleInstances {
463 transform: local, ..
464 } => *local = local.then(transform),
465 DrawCommand::PushFilterLayer { .. }
466 | DrawCommand::PushLayer { .. }
467 | DrawCommand::PushRectLayer { .. }
468 | DrawCommand::PushShapeLayer { .. }
469 | DrawCommand::PopLayer
470 | DrawCommand::BeginMask { .. }
471 | DrawCommand::EndMask => {
472 return Err(ValidationError::invalid(
473 &self.path,
474 "commands",
475 "Local drawables cannot supply compiled layers",
476 ));
477 }
478 }
479 command_path.clear();
480 let _ = write!(command_path, "{}.commands[{index}]", self.path);
481 output.push(command, &command_path)?;
482 }
483 }
484 Content::Children(children) => {
485 for child in children {
486 child.emit(transform, output)?;
487 }
488 }
489 }
490 if self.has_commands && !self.filters.is_empty() {
491 output.push(DrawCommand::PopLayer, &format!("{}.filters.pop", self.path))?;
492 }
493 if isolate {
494 if let Some((mask, mode)) = self.mask {
495 output.push(
496 DrawCommand::BeginMask { mode },
497 &format!("{}.begin_mask", self.path),
498 )?;
499 mask.emit(transform, output)?;
500 output.push(DrawCommand::EndMask, &format!("{}.end_mask", self.path))?;
501 }
502 output.push(DrawCommand::PopLayer, &format!("{}.pop_layer", self.path))?;
503 }
504 Ok(())
505 }
506}
507
508#[cfg(test)]
509mod deferred_content_tests {
510 use super::*;
511 use crate::Shape;
512 use crate::{Color, DeferredContent, NodeModifiers, Rect, TimeLiteral};
513
514 struct Deferred;
515
516 impl DeferredContent for Deferred {
517 fn resolve(&self, context: &DrawContext) -> Result<Node, ValidationError> {
518 Ok(Rect::new(0.0, 0.0, 2.0 + context.time().as_seconds(), 1.0)
519 .fill(Color::WHITE)
520 .into())
521 }
522 }
523
524 #[test]
525 fn deferred_content_preserves_outer_placement_and_inherited_local_time() {
526 let node = Node::deferred(Deferred)
527 .translate(10.0, 0.0)
528 .with_scene_time(1.seconds().after_origin());
529 assert_eq!(
530 node.measure(ValidationLimits::default()).unwrap(),
531 Rect::new(10.0, 0.0, 13.0, 1.0)
532 );
533 let list = node
534 .compile(
535 Size::new(20.0, 4.0),
536 PixelSize::new(20, 4),
537 ValidationLimits::default(),
538 )
539 .unwrap();
540 assert_eq!(list.commands.len(), 1);
541 }
542}