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