1use std::{collections::{BTreeMap, HashMap}, sync::Arc};
17
18use azul_core::{
19 dom::{DomId, FormattingContext, NodeId, NodeType, ScrollbarOrientation},
20 geom::{LogicalPosition, LogicalRect, LogicalSize},
21 gpu::GpuValueCache,
22 hit_test::ScrollPosition,
23 hit_test_tag::{CursorType, TAG_TYPE_CURSOR, TAG_TYPE_DOM_NODE},
24 resources::{
25 IdNamespace, ImageRef, OpacityKey, RendererResources, TransformKey,
26 },
27 transform::ComputedTransform3D,
28 selection::{Selection, SelectionRange, TextSelection},
29 styled_dom::StyledDom,
30 ui_solver::GlyphInstance,
31};
32use azul_css::{
33 css::CssPropertyValue,
34 format_rust_code::GetHash,
35 props::{
36 basic::{ColorU, FontRef, PixelValue},
37 layout::{LayoutDisplay, LayoutOverflow, LayoutPosition},
38 property::{CssProperty, CssPropertyType},
39 style::{
40 background::{ConicGradient, ExtendMode, LinearGradient, RadialGradient},
41 border_radius::StyleBorderRadius,
42 box_shadow::{BoxShadowClipMode, StyleBoxShadow},
43 filter::{StyleFilter, StyleFilterVec},
44 BorderStyle, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth,
45 LayoutBorderTopWidth, StyleBorderBottomColor, StyleBorderBottomStyle,
46 StyleBorderLeftColor, StyleBorderLeftStyle, StyleBorderRightColor,
47 StyleBorderRightStyle, StyleBorderTopColor, StyleBorderTopStyle,
48 },
49 },
50 LayoutDebugMessage,
51};
52
53#[cfg(feature = "text_layout")]
54use crate::text3;
55#[cfg(feature = "text_layout")]
56use crate::text3::cache::{InlineShape, PositionedItem};
57use crate::{
58 debug_info,
59 font_traits::{
60 FontHash, FontLoaderTrait, ImageSource, InlineContent, ParsedFontTrait, ShapedItem,
61 UnifiedLayout,
62 },
63 solver3::{
64 getters::{
65 get_background_color, get_background_contents, get_border_info, get_border_radius,
66 get_break_after, get_break_before, get_caret_style,
67 get_overflow_clip_margin_property, get_overflow_x, get_overflow_y,
68 get_scrollbar_gutter_property, get_scrollbar_info_from_layout, get_scrollbar_style, get_selection_style,
69 get_style_border_radius, get_visibility, get_z_index, is_forced_page_break, BorderInfo, CaretStyle,
70 ComputedScrollbarStyle, SelectionStyle,
71 },
72 layout_tree::{LayoutNode, LayoutNodeHot, LayoutNodeWarm, LayoutTree},
73 positioning::get_position_type,
74 scrollbar::{ScrollbarRequirements, compute_scrollbar_geometry_with_button_size},
75 LayoutContext, LayoutError, Result,
76 },
77};
78
79#[derive(Debug, Clone, Copy)]
84pub struct StyleBorderWidths {
85 pub top: Option<CssPropertyValue<LayoutBorderTopWidth>>,
87 pub right: Option<CssPropertyValue<LayoutBorderRightWidth>>,
89 pub bottom: Option<CssPropertyValue<LayoutBorderBottomWidth>>,
91 pub left: Option<CssPropertyValue<LayoutBorderLeftWidth>>,
93}
94
95#[derive(Debug, Clone, Copy)]
100pub struct StyleBorderColors {
101 pub top: Option<CssPropertyValue<StyleBorderTopColor>>,
103 pub right: Option<CssPropertyValue<StyleBorderRightColor>>,
105 pub bottom: Option<CssPropertyValue<StyleBorderBottomColor>>,
107 pub left: Option<CssPropertyValue<StyleBorderLeftColor>>,
109}
110
111#[derive(Debug, Clone, Copy)]
117pub struct StyleBorderStyles {
118 pub top: Option<CssPropertyValue<StyleBorderTopStyle>>,
120 pub right: Option<CssPropertyValue<StyleBorderRightStyle>>,
122 pub bottom: Option<CssPropertyValue<StyleBorderBottomStyle>>,
124 pub left: Option<CssPropertyValue<StyleBorderLeftStyle>>,
126}
127
128#[derive(Debug, Clone, Copy, PartialEq)]
131pub struct BorderBoxRect(pub LogicalRect);
132
133#[derive(Debug, Copy, Clone, Default, PartialEq, PartialOrd, Eq, Ord, Hash)]
147pub struct WindowLogicalRect(pub LogicalRect);
148
149impl WindowLogicalRect {
150 #[inline]
151 pub const fn new(origin: LogicalPosition, size: LogicalSize) -> Self {
152 Self(LogicalRect::new(origin, size))
153 }
154
155 #[inline]
156 pub const fn zero() -> Self {
157 Self(LogicalRect::zero())
158 }
159
160 #[inline]
163 pub const fn inner(&self) -> &LogicalRect {
164 &self.0
165 }
166
167 #[inline]
168 pub const fn into_inner(self) -> LogicalRect {
169 self.0
170 }
171
172 #[inline] pub fn origin(&self) -> LogicalPosition { self.0.origin }
174 #[inline] pub fn size(&self) -> LogicalSize { self.0.size }
175}
176
177impl From<LogicalRect> for WindowLogicalRect {
178 #[inline]
179 fn from(r: LogicalRect) -> Self { Self(r) }
180}
181
182impl From<WindowLogicalRect> for LogicalRect {
183 #[inline]
184 fn from(w: WindowLogicalRect) -> Self { w.0 }
185}
186
187#[derive(Debug, Clone, Copy)]
189pub struct PhysicalSizeImport {
190 pub width: f32,
191 pub height: f32,
192}
193
194#[derive(Debug, Clone)]
202pub struct ScrollbarDrawInfo {
203 pub bounds: WindowLogicalRect,
205 pub orientation: ScrollbarOrientation,
207
208 pub track_bounds: WindowLogicalRect,
211 pub track_color: ColorU,
213
214 pub thumb_bounds: WindowLogicalRect,
217 pub thumb_color: ColorU,
219 pub thumb_border_radius: BorderRadius,
221
222 pub button_decrement_bounds: Option<WindowLogicalRect>,
225 pub button_increment_bounds: Option<WindowLogicalRect>,
227 pub button_color: ColorU,
229
230 pub opacity_key: Option<OpacityKey>,
232 pub thumb_transform_key: Option<TransformKey>,
237 pub thumb_initial_transform: ComputedTransform3D,
241 pub hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
243 pub clip_to_container_border: bool,
245 pub container_border_radius: BorderRadius,
247 pub visibility: azul_css::props::style::scrollbar::ScrollbarVisibilityMode,
250}
251
252impl BorderBoxRect {
253 pub fn to_content_box(
256 self,
257 padding: &crate::solver3::geometry::EdgeSizes,
258 border: &crate::solver3::geometry::EdgeSizes,
259 ) -> ContentBoxRect {
260 ContentBoxRect(LogicalRect {
261 origin: LogicalPosition {
262 x: self.0.origin.x + padding.left + border.left,
263 y: self.0.origin.y + padding.top + border.top,
264 },
265 size: LogicalSize {
266 width: self.0.size.width
267 - padding.left
268 - padding.right
269 - border.left
270 - border.right,
271 height: self.0.size.height
272 - padding.top
273 - padding.bottom
274 - border.top
275 - border.bottom,
276 },
277 })
278 }
279
280 pub fn rect(&self) -> LogicalRect {
282 self.0
283 }
284}
285
286#[derive(Debug, Clone, Copy, PartialEq)]
289pub struct ContentBoxRect(pub LogicalRect);
290
291impl ContentBoxRect {
292 pub fn rect(&self) -> LogicalRect {
294 self.0
295 }
296}
297
298#[derive(Debug, Default, Clone)]
303pub struct DisplayList {
304 pub items: Vec<DisplayListItem>,
305 pub node_mapping: Vec<Option<NodeId>>,
309 pub forced_page_breaks: Vec<f32>,
313 pub fixed_position_item_ranges: Vec<(usize, usize)>,
316}
317
318impl DisplayList {
319 pub fn patch_text_glyphs(
326 &mut self,
327 node_index: usize,
328 new_glyphs_by_run: &[Vec<GlyphInstance>],
329 ) -> Option<LogicalRect> {
330 let mut run_idx = 0;
331 let mut damage: Option<LogicalRect> = None;
332
333 for item in &mut self.items {
334 if let DisplayListItem::Text {
335 ref mut glyphs,
336 ref clip_rect,
337 source_node_index: Some(src_idx),
338 ..
339 } = item {
340 if *src_idx == node_index {
341 if run_idx < new_glyphs_by_run.len() {
342 *glyphs = new_glyphs_by_run[run_idx].clone();
343 let bounds = *clip_rect.inner();
344 damage = Some(match damage {
345 Some(d) => {
346 let x = d.origin.x.min(bounds.origin.x);
350 let y = d.origin.y.min(bounds.origin.y);
351 let right = (d.origin.x + d.size.width)
352 .max(bounds.origin.x + bounds.size.width);
353 let bottom = (d.origin.y + d.size.height)
354 .max(bounds.origin.y + bounds.size.height);
355 LogicalRect {
356 origin: LogicalPosition { x, y },
357 size: LogicalSize { width: right - x, height: bottom - y },
358 }
359 }
360 None => bounds,
361 });
362 run_idx += 1;
363 }
364 }
365 }
366 }
367
368 damage
369 }
370
371 pub fn compute_text_damage_rect(
374 old_items: &[super::super::text3::cache::PositionedItem],
375 new_items: &[super::super::text3::cache::PositionedItem],
376 container_origin: LogicalPosition,
377 affected_line: usize,
378 ) -> LogicalRect {
379 let expand = |items: &[super::super::text3::cache::PositionedItem]| -> (f32, f32, f32, f32) {
380 let mut lx = f32::MAX;
381 let mut ly = f32::MAX;
382 let mut rx = f32::MIN;
383 let mut ry = f32::MIN;
384 for item in items {
385 if item.line_index >= affected_line {
386 let bounds = item.item.bounds();
387 let x = container_origin.x + item.position.x;
388 let y = container_origin.y + item.position.y;
389 lx = lx.min(x);
390 ly = ly.min(y);
391 rx = rx.max(x + bounds.width);
392 ry = ry.max(y + bounds.height);
393 }
394 }
395 (lx, ly, rx, ry)
396 };
397
398 let (olx, oly, orx, ory) = expand(old_items);
399 let (nlx, nly, nrx, nry) = expand(new_items);
400 let min_x = olx.min(nlx);
401 let min_y = oly.min(nly);
402 let max_x = orx.max(nrx);
403 let max_y = ory.max(nry);
404
405 if min_x > max_x || min_y > max_y {
406 return LogicalRect::default();
407 }
408
409 LogicalRect {
410 origin: LogicalPosition { x: min_x, y: min_y },
411 size: LogicalSize { width: max_x - min_x, height: max_y - min_y },
412 }
413 }
414
415 pub fn to_debug_json(&self) -> String {
418 use std::fmt::Write;
419 let mut json = String::new();
420 writeln!(json, "{{").unwrap();
421 writeln!(json, " \"total_items\": {},", self.items.len()).unwrap();
422 writeln!(json, " \"items\": [").unwrap();
423
424 let mut clip_depth = 0i32;
425 let mut scroll_depth = 0i32;
426 let mut stacking_depth = 0i32;
427
428 for (i, item) in self.items.iter().enumerate() {
429 let comma = if i < self.items.len() - 1 { "," } else { "" };
430 let node_id = self.node_mapping.get(i).and_then(|n| *n);
431
432 match item {
433 DisplayListItem::PushClip {
434 bounds,
435 border_radius,
436 } => {
437 clip_depth += 1;
438 writeln!(json, " {{").unwrap();
439 writeln!(json, " \"index\": {},", i).unwrap();
440 writeln!(json, " \"type\": \"PushClip\",").unwrap();
441 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
442 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
443 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
444 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
445 writeln!(json, " \"border_radius\": {{ \"tl\": {:.1}, \"tr\": {:.1}, \"bl\": {:.1}, \"br\": {:.1} }},",
446 border_radius.top_left, border_radius.top_right,
447 border_radius.bottom_left, border_radius.bottom_right).unwrap();
448 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
449 writeln!(json, " }}{}", comma).unwrap();
450 }
451 DisplayListItem::PopClip => {
452 writeln!(json, " {{").unwrap();
453 writeln!(json, " \"index\": {},", i).unwrap();
454 writeln!(json, " \"type\": \"PopClip\",").unwrap();
455 writeln!(json, " \"clip_depth_before\": {},", clip_depth).unwrap();
456 writeln!(json, " \"clip_depth_after\": {}", clip_depth - 1).unwrap();
457 writeln!(json, " }}{}", comma).unwrap();
458 clip_depth -= 1;
459 }
460 DisplayListItem::PushScrollFrame {
461 clip_bounds,
462 content_size,
463 scroll_id,
464 } => {
465 scroll_depth += 1;
466 writeln!(json, " {{").unwrap();
467 writeln!(json, " \"index\": {},", i).unwrap();
468 writeln!(json, " \"type\": \"PushScrollFrame\",").unwrap();
469 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
470 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
471 writeln!(json, " \"clip_bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
472 clip_bounds.0.origin.x, clip_bounds.0.origin.y,
473 clip_bounds.0.size.width, clip_bounds.0.size.height).unwrap();
474 writeln!(
475 json,
476 " \"content_size\": {{ \"w\": {:.1}, \"h\": {:.1} }},",
477 content_size.width, content_size.height
478 )
479 .unwrap();
480 writeln!(json, " \"scroll_id\": {},", scroll_id).unwrap();
481 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
482 writeln!(json, " }}{}", comma).unwrap();
483 }
484 DisplayListItem::PopScrollFrame => {
485 writeln!(json, " {{").unwrap();
486 writeln!(json, " \"index\": {},", i).unwrap();
487 writeln!(json, " \"type\": \"PopScrollFrame\",").unwrap();
488 writeln!(json, " \"scroll_depth_before\": {},", scroll_depth).unwrap();
489 writeln!(json, " \"scroll_depth_after\": {}", scroll_depth - 1).unwrap();
490 writeln!(json, " }}{}", comma).unwrap();
491 scroll_depth -= 1;
492 }
493 DisplayListItem::PushStackingContext { z_index, bounds } => {
494 stacking_depth += 1;
495 writeln!(json, " {{").unwrap();
496 writeln!(json, " \"index\": {},", i).unwrap();
497 writeln!(json, " \"type\": \"PushStackingContext\",").unwrap();
498 writeln!(json, " \"stacking_depth\": {},", stacking_depth).unwrap();
499 writeln!(json, " \"z_index\": {},", z_index).unwrap();
500 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
501 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
502 writeln!(json, " }}{}", comma).unwrap();
503 }
504 DisplayListItem::PopStackingContext => {
505 writeln!(json, " {{").unwrap();
506 writeln!(json, " \"index\": {},", i).unwrap();
507 writeln!(json, " \"type\": \"PopStackingContext\",").unwrap();
508 writeln!(json, " \"stacking_depth_before\": {},", stacking_depth).unwrap();
509 writeln!(
510 json,
511 " \"stacking_depth_after\": {}",
512 stacking_depth - 1
513 )
514 .unwrap();
515 writeln!(json, " }}{}", comma).unwrap();
516 stacking_depth -= 1;
517 }
518 DisplayListItem::Rect {
519 bounds,
520 color,
521 border_radius,
522 } => {
523 writeln!(json, " {{").unwrap();
524 writeln!(json, " \"index\": {},", i).unwrap();
525 writeln!(json, " \"type\": \"Rect\",").unwrap();
526 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
527 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
528 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
529 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
530 writeln!(
531 json,
532 " \"color\": \"rgba({},{},{},{})\",",
533 color.r, color.g, color.b, color.a
534 )
535 .unwrap();
536 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
537 writeln!(json, " }}{}", comma).unwrap();
538 }
539 DisplayListItem::Border { bounds, .. } => {
540 writeln!(json, " {{").unwrap();
541 writeln!(json, " \"index\": {},", i).unwrap();
542 writeln!(json, " \"type\": \"Border\",").unwrap();
543 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
544 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
545 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
546 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
547 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
548 writeln!(json, " }}{}", comma).unwrap();
549 }
550 DisplayListItem::ScrollBarStyled { info } => {
551 writeln!(json, " {{").unwrap();
552 writeln!(json, " \"index\": {},", i).unwrap();
553 writeln!(json, " \"type\": \"ScrollBarStyled\",").unwrap();
554 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
555 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
556 writeln!(json, " \"orientation\": \"{:?}\",", info.orientation).unwrap();
557 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
558 info.bounds.0.origin.x, info.bounds.0.origin.y,
559 info.bounds.0.size.width, info.bounds.0.size.height).unwrap();
560 writeln!(json, " }}{}", comma).unwrap();
561 }
562 _ => {
563 writeln!(json, " {{").unwrap();
564 writeln!(json, " \"index\": {},", i).unwrap();
565 writeln!(
566 json,
567 " \"type\": \"{:?}\",",
568 std::mem::discriminant(item)
569 )
570 .unwrap();
571 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
572 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
573 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
574 writeln!(json, " }}{}", comma).unwrap();
575 }
576 }
577 }
578
579 writeln!(json, " ],").unwrap();
580 writeln!(json, " \"final_clip_depth\": {},", clip_depth).unwrap();
581 writeln!(json, " \"final_scroll_depth\": {},", scroll_depth).unwrap();
582 writeln!(json, " \"final_stacking_depth\": {},", stacking_depth).unwrap();
583 writeln!(
584 json,
585 " \"balanced\": {}",
586 clip_depth == 0 && scroll_depth == 0 && stacking_depth == 0
587 )
588 .unwrap();
589 writeln!(json, "}}").unwrap();
590
591 json
592 }
593}
594
595#[derive(Debug, Clone)]
598pub enum DisplayListItem {
599 Rect {
603 bounds: WindowLogicalRect,
605 color: ColorU,
607 border_radius: BorderRadius,
609 },
610 SelectionRect {
613 bounds: WindowLogicalRect,
615 border_radius: BorderRadius,
617 color: ColorU,
619 },
620 CursorRect {
623 bounds: WindowLogicalRect,
625 color: ColorU,
627 },
628 Border {
631 bounds: WindowLogicalRect,
633 widths: StyleBorderWidths,
635 colors: StyleBorderColors,
637 styles: StyleBorderStyles,
639 border_radius: StyleBorderRadius,
641 },
642 TextLayout {
646 layout: Arc<dyn std::any::Any + Send + Sync>, bounds: WindowLogicalRect,
648 font_hash: FontHash,
649 font_size_px: f32,
650 color: ColorU,
651 },
652 Text {
654 glyphs: Vec<GlyphInstance>,
655 font_hash: FontHash,
656 font_size_px: f32,
657 color: ColorU,
658 clip_rect: WindowLogicalRect,
659 source_node_index: Option<usize>,
662 },
663 Underline {
665 bounds: WindowLogicalRect,
666 color: ColorU,
667 thickness: f32,
668 },
669 Strikethrough {
671 bounds: WindowLogicalRect,
672 color: ColorU,
673 thickness: f32,
674 },
675 Overline {
677 bounds: WindowLogicalRect,
678 color: ColorU,
679 thickness: f32,
680 },
681 Image {
682 bounds: WindowLogicalRect,
683 image: ImageRef,
684 border_radius: BorderRadius,
685 },
686 ScrollBar {
689 bounds: WindowLogicalRect,
690 color: ColorU,
691 orientation: ScrollbarOrientation,
692 opacity_key: Option<OpacityKey>,
696 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
699 },
700 ScrollBarStyled {
703 info: Box<ScrollbarDrawInfo>,
705 },
706
707 VirtualView {
713 child_dom_id: DomId,
715 bounds: WindowLogicalRect,
717 clip_rect: WindowLogicalRect,
719 },
720
721 VirtualViewPlaceholder {
730 node_id: NodeId,
732 bounds: WindowLogicalRect,
734 clip_rect: WindowLogicalRect,
736 },
737
738 PushClip {
742 bounds: WindowLogicalRect,
743 border_radius: BorderRadius,
744 },
745 PopClip,
747
748 PushImageMaskClip {
752 bounds: WindowLogicalRect,
754 mask_image: ImageRef,
756 mask_rect: WindowLogicalRect,
758 },
759 PopImageMaskClip,
761
762 PushScrollFrame {
765 clip_bounds: WindowLogicalRect,
767 content_size: LogicalSize,
769 scroll_id: LocalScrollId,
771 },
772 PopScrollFrame,
774
775 PushStackingContext {
778 z_index: i32,
780 bounds: WindowLogicalRect,
782 },
783 PopStackingContext,
785
786 PushReferenceFrame {
790 transform_key: TransformKey,
792 initial_transform: ComputedTransform3D,
794 bounds: WindowLogicalRect,
796 },
797 PopReferenceFrame,
799
800 HitTestArea {
802 bounds: WindowLogicalRect,
803 tag: DisplayListTagId, },
805
806 LinearGradient {
809 bounds: WindowLogicalRect,
810 gradient: LinearGradient,
811 border_radius: BorderRadius,
812 },
813 RadialGradient {
815 bounds: WindowLogicalRect,
816 gradient: RadialGradient,
817 border_radius: BorderRadius,
818 },
819 ConicGradient {
821 bounds: WindowLogicalRect,
822 gradient: ConicGradient,
823 border_radius: BorderRadius,
824 },
825
826 BoxShadow {
829 bounds: WindowLogicalRect,
830 shadow: StyleBoxShadow,
831 border_radius: BorderRadius,
832 },
833
834 PushFilter {
837 bounds: WindowLogicalRect,
838 filters: Vec<StyleFilter>,
839 },
840 PopFilter,
842
843 PushBackdropFilter {
845 bounds: WindowLogicalRect,
846 filters: Vec<StyleFilter>,
847 },
848 PopBackdropFilter,
850
851 PushOpacity {
853 bounds: WindowLogicalRect,
854 opacity: f32,
855 },
856 PopOpacity,
858
859 PushTextShadow {
861 shadow: azul_css::props::style::box_shadow::StyleBoxShadow,
862 },
863 PopTextShadow,
865}
866
867impl DisplayListItem {
868 pub fn is_visually_equal(&self, other: &Self) -> bool {
872 if std::mem::discriminant(self) != std::mem::discriminant(other) {
873 return false;
874 }
875 match (self, other) {
876 (Self::Rect { bounds: b1, color: c1, border_radius: br1 },
877 Self::Rect { bounds: b2, color: c2, border_radius: br2 }) => {
878 b1 == b2 && c1 == c2 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
879 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
880 }
881 (Self::SelectionRect { bounds: b1, border_radius: br1, color: c1 },
882 Self::SelectionRect { bounds: b2, border_radius: br2, color: c2 }) => {
883 b1 == b2 && c1 == c2 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
884 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
885 }
886 (Self::CursorRect { bounds: b1, color: c1 },
887 Self::CursorRect { bounds: b2, color: c2 }) => b1 == b2 && c1 == c2,
888 (Self::Text { glyphs: g1, font_hash: fh1, font_size_px: fs1, color: c1, clip_rect: cr1, .. },
889 Self::Text { glyphs: g2, font_hash: fh2, font_size_px: fs2, color: c2, clip_rect: cr2, .. }) => {
890 cr1 == cr2 && c1 == c2 && fh1 == fh2 && fs1 == fs2 && g1.len() == g2.len()
891 && g1.iter().zip(g2.iter()).all(|(a, b)| {
892 a.index == b.index
893 && a.point.x == b.point.x
894 && a.point.y == b.point.y
895 })
896 }
897 (Self::Underline { bounds: b1, color: c1, thickness: t1 },
898 Self::Underline { bounds: b2, color: c2, thickness: t2 }) => b1 == b2 && c1 == c2 && t1 == t2,
899 (Self::Strikethrough { bounds: b1, color: c1, thickness: t1 },
900 Self::Strikethrough { bounds: b2, color: c2, thickness: t2 }) => b1 == b2 && c1 == c2 && t1 == t2,
901 (Self::Overline { bounds: b1, color: c1, thickness: t1 },
902 Self::Overline { bounds: b2, color: c2, thickness: t2 }) => b1 == b2 && c1 == c2 && t1 == t2,
903 (Self::Border { bounds: b1, widths: w1, colors: c1, styles: s1, .. },
904 Self::Border { bounds: b2, widths: w2, colors: c2, styles: s2, .. }) => {
905 b1 == b2
906 && w1.top == w2.top && w1.right == w2.right && w1.bottom == w2.bottom && w1.left == w2.left
907 && c1.top == c2.top && c1.right == c2.right && c1.bottom == c2.bottom && c1.left == c2.left
908 && s1.top == s2.top && s1.right == s2.right && s1.bottom == s2.bottom && s1.left == s2.left
909 }
910 (Self::Image { bounds: b1, image: i1, border_radius: br1 },
911 Self::Image { bounds: b2, image: i2, border_radius: br2 }) => {
912 b1 == b2
913 && i1.data as usize == i2.data as usize && br1.top_left == br2.top_left && br1.top_right == br2.top_right
915 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
916 }
917 (Self::BoxShadow { bounds: b1, shadow: s1, border_radius: br1 },
918 Self::BoxShadow { bounds: b2, shadow: s2, border_radius: br2 }) => {
919 b1 == b2 && s1 == s2
920 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
921 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
922 }
923 (Self::LinearGradient { bounds: b1, gradient: g1, border_radius: br1 },
924 Self::LinearGradient { bounds: b2, gradient: g2, border_radius: br2 }) => {
925 b1 == b2 && g1 == g2
926 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
927 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
928 }
929 (Self::RadialGradient { bounds: b1, gradient: g1, border_radius: br1 },
930 Self::RadialGradient { bounds: b2, gradient: g2, border_radius: br2 }) => {
931 b1 == b2 && g1 == g2
932 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
933 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
934 }
935 (Self::ConicGradient { bounds: b1, gradient: g1, border_radius: br1 },
936 Self::ConicGradient { bounds: b2, gradient: g2, border_radius: br2 }) => {
937 b1 == b2 && g1 == g2
938 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
939 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
940 }
941 (Self::ScrollBar { bounds: b1, color: c1, .. },
942 Self::ScrollBar { bounds: b2, color: c2, .. }) => b1 == b2 && c1 == c2,
943 (Self::PushClip { bounds: b1, .. }, Self::PushClip { bounds: b2, .. }) => b1 == b2,
944 (Self::PushScrollFrame { clip_bounds: b1, scroll_id: s1, .. },
945 Self::PushScrollFrame { clip_bounds: b2, scroll_id: s2, .. }) => b1 == b2 && s1 == s2,
946 (Self::PushStackingContext { z_index: z1, bounds: b1 },
947 Self::PushStackingContext { z_index: z2, bounds: b2 }) => z1 == z2 && b1 == b2,
948 (Self::PushOpacity { bounds: b1, opacity: o1 },
949 Self::PushOpacity { bounds: b2, opacity: o2 }) => b1 == b2 && o1 == o2,
950 (Self::PopClip, Self::PopClip)
952 | (Self::PopImageMaskClip, Self::PopImageMaskClip)
953 | (Self::PopScrollFrame, Self::PopScrollFrame)
954 | (Self::PopStackingContext, Self::PopStackingContext)
955 | (Self::PopReferenceFrame, Self::PopReferenceFrame)
956 | (Self::PopFilter, Self::PopFilter)
957 | (Self::PopBackdropFilter, Self::PopBackdropFilter)
958 | (Self::PopOpacity, Self::PopOpacity)
959 | (Self::PopTextShadow, Self::PopTextShadow) => true,
960 _ => false,
963 }
964 }
965
966 pub fn is_state_management(&self) -> bool {
969 matches!(self,
970 Self::PushClip { .. }
971 | Self::PopClip
972 | Self::PushImageMaskClip { .. }
973 | Self::PopImageMaskClip
974 | Self::PushScrollFrame { .. }
975 | Self::PopScrollFrame
976 | Self::PushStackingContext { .. }
977 | Self::PopStackingContext
978 | Self::PushReferenceFrame { .. }
979 | Self::PopReferenceFrame
980 | Self::PushFilter { .. }
981 | Self::PopFilter
982 | Self::PushBackdropFilter { .. }
983 | Self::PopBackdropFilter
984 | Self::PushOpacity { .. }
985 | Self::PopOpacity
986 | Self::PushTextShadow { .. }
987 | Self::PopTextShadow
988 )
989 }
990
991 pub fn visual_bounds(&self) -> Option<LogicalRect> {
995 match self {
996 Self::BoxShadow { bounds, shadow, .. } => {
997 let b = *bounds.inner();
998 let ox = shadow.offset_x.to_pixels_internal(16.0, 16.0).abs();
1000 let oy = shadow.offset_y.to_pixels_internal(16.0, 16.0).abs();
1001 let blur = shadow.blur_radius.to_pixels_internal(16.0, 16.0).abs();
1002 let spread = shadow.spread_radius.to_pixels_internal(16.0, 16.0).abs();
1003 let expand = ox + oy + blur + spread;
1004 Some(LogicalRect {
1005 origin: LogicalPosition {
1006 x: b.origin.x - expand,
1007 y: b.origin.y - expand,
1008 },
1009 size: LogicalSize {
1010 width: b.size.width + expand * 2.0,
1011 height: b.size.height + expand * 2.0,
1012 },
1013 })
1014 }
1015 _ => self.bounds(),
1016 }
1017 }
1018
1019 pub fn bounds(&self) -> Option<LogicalRect> {
1022 match self {
1023 Self::Rect { bounds, .. }
1024 | Self::SelectionRect { bounds, .. }
1025 | Self::CursorRect { bounds, .. }
1026 | Self::Border { bounds, .. }
1027 | Self::Text { clip_rect: bounds, .. }
1028 | Self::TextLayout { bounds, .. }
1029 | Self::Underline { bounds, .. }
1030 | Self::Strikethrough { bounds, .. }
1031 | Self::Overline { bounds, .. }
1032 | Self::Image { bounds, .. }
1033 | Self::ScrollBar { bounds, .. }
1034 | Self::LinearGradient { bounds, .. }
1035 | Self::RadialGradient { bounds, .. }
1036 | Self::ConicGradient { bounds, .. }
1037 | Self::BoxShadow { bounds, .. }
1038 | Self::VirtualView { bounds, .. }
1039 | Self::VirtualViewPlaceholder { bounds, .. }
1040 | Self::HitTestArea { bounds, .. }
1041 | Self::PushClip { bounds, .. }
1042 | Self::PushImageMaskClip { bounds, .. }
1043 | Self::PushScrollFrame { clip_bounds: bounds, .. }
1044 | Self::PushStackingContext { bounds, .. }
1045 | Self::PushReferenceFrame { bounds, .. }
1046 | Self::PushFilter { bounds, .. }
1047 | Self::PushBackdropFilter { bounds, .. }
1048 | Self::PushOpacity { bounds, .. } => Some(*bounds.inner()),
1049 Self::ScrollBarStyled { info, .. } => Some(*info.bounds.inner()),
1050 Self::PushTextShadow { .. } => None, Self::PopClip
1052 | Self::PopImageMaskClip
1053 | Self::PopScrollFrame
1054 | Self::PopStackingContext
1055 | Self::PopReferenceFrame
1056 | Self::PopFilter
1057 | Self::PopBackdropFilter
1058 | Self::PopOpacity
1059 | Self::PopTextShadow => None,
1060 }
1061 }
1062}
1063
1064#[derive(Debug, Copy, Clone, Default)]
1066pub struct BorderRadius {
1067 pub top_left: f32,
1068 pub top_right: f32,
1069 pub bottom_left: f32,
1070 pub bottom_right: f32,
1071}
1072
1073impl BorderRadius {
1074 pub fn is_zero(&self) -> bool {
1075 self.top_left == 0.0
1076 && self.top_right == 0.0
1077 && self.bottom_left == 0.0
1078 && self.bottom_right == 0.0
1079 }
1080}
1081
1082pub type LocalScrollId = u64;
1084pub type DisplayListTagId = (u64, u16);
1089
1090#[derive(Debug, Default)]
1092struct DisplayListBuilder {
1093 items: Vec<DisplayListItem>,
1094 node_mapping: Vec<Option<NodeId>>,
1095 current_node: Option<NodeId>,
1097 debug_messages: Vec<LayoutDebugMessage>,
1099 debug_enabled: bool,
1101 forced_page_breaks: Vec<f32>,
1103 fixed_position_item_ranges: Vec<(usize, usize)>,
1105 fixed_position_start: Option<usize>,
1107}
1108
1109impl DisplayListBuilder {
1110 pub fn new() -> Self {
1111 Self::default()
1112 }
1113
1114 pub fn with_debug(debug_enabled: bool) -> Self {
1115 Self {
1116 items: Vec::new(),
1117 node_mapping: Vec::new(),
1118 current_node: None,
1119 debug_messages: Vec::new(),
1120 debug_enabled,
1121 forced_page_breaks: Vec::new(),
1122 fixed_position_item_ranges: Vec::new(),
1123 fixed_position_start: None,
1124 }
1125 }
1126
1127 fn debug_log(&mut self, message: String) {
1129 if self.debug_enabled {
1130 self.debug_messages.push(LayoutDebugMessage::info(message));
1131 }
1132 }
1133
1134 pub fn build_with_debug(
1136 mut self,
1137 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1138 ) -> DisplayList {
1139 if let Some(msgs) = debug_messages.as_mut() {
1141 msgs.append(&mut self.debug_messages);
1142 }
1143 DisplayList {
1144 items: self.items,
1145 node_mapping: self.node_mapping,
1146 forced_page_breaks: self.forced_page_breaks,
1147 fixed_position_item_ranges: self.fixed_position_item_ranges,
1148 }
1149 }
1150
1151 pub fn set_current_node(&mut self, node_id: Option<NodeId>) {
1153 self.current_node = node_id;
1154 }
1155
1156 pub fn begin_fixed_position_element(&mut self) {
1158 self.fixed_position_start = Some(self.items.len());
1159 }
1160
1161 pub fn end_fixed_position_element(&mut self) {
1164 if let Some(start) = self.fixed_position_start.take() {
1165 let end = self.items.len();
1166 if end > start {
1167 self.fixed_position_item_ranges.push((start, end));
1168 }
1169 }
1170 }
1171
1172 pub fn add_forced_page_break(&mut self, y_position: f32) {
1175 if !self.forced_page_breaks.contains(&y_position) {
1177 self.forced_page_breaks.push(y_position);
1178 self.forced_page_breaks.sort_by(|a, b| a.partial_cmp(b).unwrap());
1179 }
1180 }
1181
1182 fn push_item(&mut self, item: DisplayListItem) {
1184 self.items.push(item);
1185 self.node_mapping.push(self.current_node);
1186 }
1187
1188 pub fn build(self) -> DisplayList {
1189 DisplayList {
1190 items: self.items,
1191 node_mapping: self.node_mapping,
1192 forced_page_breaks: self.forced_page_breaks,
1193 fixed_position_item_ranges: self.fixed_position_item_ranges,
1194 }
1195 }
1196
1197 pub fn push_hit_test_area(&mut self, bounds: LogicalRect, tag: DisplayListTagId) {
1198 self.push_item(DisplayListItem::HitTestArea { bounds: bounds.into(), tag });
1199 }
1200
1201 pub fn push_scrollbar(
1203 &mut self,
1204 bounds: LogicalRect,
1205 color: ColorU,
1206 orientation: ScrollbarOrientation,
1207 opacity_key: Option<OpacityKey>,
1208 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
1209 ) {
1210 if color.a > 0 || opacity_key.is_some() {
1211 self.push_item(DisplayListItem::ScrollBar {
1213 bounds: bounds.into(),
1214 color,
1215 orientation,
1216 opacity_key,
1217 hit_id,
1218 });
1219 }
1220 }
1221
1222 pub fn push_scrollbar_styled(&mut self, info: ScrollbarDrawInfo) {
1224 if info.thumb_color.a > 0 || info.track_color.a > 0 || info.opacity_key.is_some() {
1226 self.push_item(DisplayListItem::ScrollBarStyled {
1227 info: Box::new(info),
1228 });
1229 }
1230 }
1231
1232 pub fn push_rect(&mut self, bounds: LogicalRect, color: ColorU, border_radius: BorderRadius) {
1233 if color.a > 0 {
1234 self.push_item(DisplayListItem::Rect {
1236 bounds: bounds.into(),
1237 color,
1238 border_radius,
1239 });
1240 }
1241 }
1242
1243 pub fn push_backgrounds_and_border(
1253 &mut self,
1254 bounds: LogicalRect,
1255 background_contents: &[azul_css::props::style::StyleBackgroundContent],
1256 border_info: &BorderInfo,
1257 simple_border_radius: BorderRadius,
1258 style_border_radius: StyleBorderRadius,
1259 image_cache: &azul_core::resources::ImageCache,
1260 ) {
1261 use azul_css::props::style::StyleBackgroundContent;
1262
1263 for bg in background_contents {
1265 match bg {
1266 StyleBackgroundContent::Color(color) => {
1267 self.push_rect(bounds, *color, simple_border_radius);
1268 }
1269 StyleBackgroundContent::LinearGradient(gradient) => {
1270 self.push_linear_gradient(bounds, gradient.clone(), simple_border_radius);
1271 }
1272 StyleBackgroundContent::RadialGradient(gradient) => {
1273 self.push_radial_gradient(bounds, gradient.clone(), simple_border_radius);
1274 }
1275 StyleBackgroundContent::ConicGradient(gradient) => {
1276 self.push_conic_gradient(bounds, gradient.clone(), simple_border_radius);
1277 }
1278 StyleBackgroundContent::Image(image_id) => {
1279 if let Some(image_ref) = image_cache.get_css_image_id(image_id) {
1280 self.push_image(bounds, image_ref.clone(), simple_border_radius);
1281 }
1282 }
1283 }
1284 }
1285
1286 self.push_border(
1288 bounds,
1289 border_info.widths,
1290 border_info.colors,
1291 border_info.styles,
1292 style_border_radius,
1293 );
1294 }
1295
1296 pub fn push_inline_backgrounds_and_border(
1303 &mut self,
1304 bounds: LogicalRect,
1305 background_color: Option<ColorU>,
1306 background_contents: &[azul_css::props::style::StyleBackgroundContent],
1307 border: Option<&crate::text3::cache::InlineBorderInfo>,
1308 image_cache: &azul_core::resources::ImageCache,
1309 ) {
1310 use azul_css::props::style::StyleBackgroundContent;
1311
1312 if let Some(bg_color) = background_color {
1314 self.push_rect(bounds, bg_color, BorderRadius::default());
1315 }
1316
1317 for bg in background_contents {
1319 match bg {
1320 StyleBackgroundContent::Color(color) => {
1321 self.push_rect(bounds, *color, BorderRadius::default());
1322 }
1323 StyleBackgroundContent::LinearGradient(gradient) => {
1324 self.push_linear_gradient(bounds, gradient.clone(), BorderRadius::default());
1325 }
1326 StyleBackgroundContent::RadialGradient(gradient) => {
1327 self.push_radial_gradient(bounds, gradient.clone(), BorderRadius::default());
1328 }
1329 StyleBackgroundContent::ConicGradient(gradient) => {
1330 self.push_conic_gradient(bounds, gradient.clone(), BorderRadius::default());
1331 }
1332 StyleBackgroundContent::Image(image_id) => {
1333 if let Some(image_ref) = image_cache.get_css_image_id(image_id) {
1334 self.push_image(bounds, image_ref.clone(), BorderRadius::default());
1335 }
1336 }
1337 }
1338 }
1339
1340 if let Some(border) = border {
1343 let effective_left = if border.left_inset() > 0.0 { border.left } else { 0.0 };
1344 let effective_right = if border.right_inset() > 0.0 { border.right } else { 0.0 };
1345 if border.top > 0.0 || effective_right > 0.0 || border.bottom > 0.0 || effective_left > 0.0 {
1346 let border_widths = StyleBorderWidths {
1347 top: Some(CssPropertyValue::Exact(LayoutBorderTopWidth {
1348 inner: PixelValue::px(border.top),
1349 })),
1350 right: Some(CssPropertyValue::Exact(LayoutBorderRightWidth {
1351 inner: PixelValue::px(effective_right),
1352 })),
1353 bottom: Some(CssPropertyValue::Exact(LayoutBorderBottomWidth {
1354 inner: PixelValue::px(border.bottom),
1355 })),
1356 left: Some(CssPropertyValue::Exact(LayoutBorderLeftWidth {
1357 inner: PixelValue::px(effective_left),
1358 })),
1359 };
1360 let border_colors = StyleBorderColors {
1361 top: Some(CssPropertyValue::Exact(StyleBorderTopColor {
1362 inner: border.top_color,
1363 })),
1364 right: Some(CssPropertyValue::Exact(StyleBorderRightColor {
1365 inner: border.right_color,
1366 })),
1367 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomColor {
1368 inner: border.bottom_color,
1369 })),
1370 left: Some(CssPropertyValue::Exact(StyleBorderLeftColor {
1371 inner: border.left_color,
1372 })),
1373 };
1374 let border_styles = StyleBorderStyles {
1375 top: Some(CssPropertyValue::Exact(StyleBorderTopStyle {
1376 inner: BorderStyle::Solid,
1377 })),
1378 right: Some(CssPropertyValue::Exact(StyleBorderRightStyle {
1379 inner: BorderStyle::Solid,
1380 })),
1381 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomStyle {
1382 inner: BorderStyle::Solid,
1383 })),
1384 left: Some(CssPropertyValue::Exact(StyleBorderLeftStyle {
1385 inner: BorderStyle::Solid,
1386 })),
1387 };
1388 let radius_px = PixelValue::px(border.radius.unwrap_or(0.0));
1389 let border_radius = StyleBorderRadius {
1390 top_left: radius_px,
1391 top_right: radius_px,
1392 bottom_left: radius_px,
1393 bottom_right: radius_px,
1394 };
1395
1396 self.push_border(
1397 bounds,
1398 border_widths,
1399 border_colors,
1400 border_styles,
1401 border_radius,
1402 );
1403 }
1404 }
1405 }
1406
1407 pub fn push_linear_gradient(
1409 &mut self,
1410 bounds: LogicalRect,
1411 gradient: LinearGradient,
1412 border_radius: BorderRadius,
1413 ) {
1414 self.push_item(DisplayListItem::LinearGradient {
1415 bounds: bounds.into(),
1416 gradient,
1417 border_radius,
1418 });
1419 }
1420
1421 pub fn push_radial_gradient(
1423 &mut self,
1424 bounds: LogicalRect,
1425 gradient: RadialGradient,
1426 border_radius: BorderRadius,
1427 ) {
1428 self.push_item(DisplayListItem::RadialGradient {
1429 bounds: bounds.into(),
1430 gradient,
1431 border_radius,
1432 });
1433 }
1434
1435 pub fn push_conic_gradient(
1437 &mut self,
1438 bounds: LogicalRect,
1439 gradient: ConicGradient,
1440 border_radius: BorderRadius,
1441 ) {
1442 self.push_item(DisplayListItem::ConicGradient {
1443 bounds: bounds.into(),
1444 gradient,
1445 border_radius,
1446 });
1447 }
1448
1449 pub fn push_selection_rect(
1450 &mut self,
1451 bounds: LogicalRect,
1452 color: ColorU,
1453 border_radius: BorderRadius,
1454 ) {
1455 if color.a > 0 {
1456 self.push_item(DisplayListItem::SelectionRect {
1457 bounds: bounds.into(),
1458 color,
1459 border_radius,
1460 });
1461 }
1462 }
1463
1464 pub fn push_cursor_rect(&mut self, bounds: LogicalRect, color: ColorU) {
1465 if color.a > 0 {
1466 self.push_item(DisplayListItem::CursorRect { bounds: bounds.into(), color });
1467 }
1468 }
1469 pub fn push_clip(&mut self, bounds: LogicalRect, border_radius: BorderRadius) {
1470 self.push_item(DisplayListItem::PushClip {
1471 bounds: bounds.into(),
1472 border_radius,
1473 });
1474 }
1475 pub fn pop_clip(&mut self) {
1476 self.push_item(DisplayListItem::PopClip);
1477 }
1478 pub fn push_image_mask_clip(&mut self, bounds: LogicalRect, mask_image: ImageRef, mask_rect: LogicalRect) {
1479 self.push_item(DisplayListItem::PushImageMaskClip {
1480 bounds: bounds.into(),
1481 mask_image,
1482 mask_rect: mask_rect.into(),
1483 });
1484 }
1485 pub fn pop_image_mask_clip(&mut self) {
1486 self.push_item(DisplayListItem::PopImageMaskClip);
1487 }
1488 pub fn push_scroll_frame(
1489 &mut self,
1490 clip_bounds: LogicalRect,
1491 content_size: LogicalSize,
1492 scroll_id: LocalScrollId,
1493 ) {
1494 self.push_item(DisplayListItem::PushScrollFrame {
1495 clip_bounds: clip_bounds.into(),
1496 content_size,
1497 scroll_id,
1498 });
1499 }
1500 pub fn pop_scroll_frame(&mut self) {
1501 self.push_item(DisplayListItem::PopScrollFrame);
1502 }
1503 pub fn push_virtual_view_placeholder(
1504 &mut self,
1505 node_id: NodeId,
1506 bounds: LogicalRect,
1507 clip_rect: LogicalRect,
1508 ) {
1509 self.push_item(DisplayListItem::VirtualViewPlaceholder {
1510 node_id,
1511 bounds: bounds.into(),
1512 clip_rect: clip_rect.into(),
1513 });
1514 }
1515 pub fn push_border(
1516 &mut self,
1517 bounds: LogicalRect,
1518 widths: StyleBorderWidths,
1519 colors: StyleBorderColors,
1520 styles: StyleBorderStyles,
1521 border_radius: StyleBorderRadius,
1522 ) {
1523 let has_visible_border = {
1525 let has_width = widths.top.is_some()
1526 || widths.right.is_some()
1527 || widths.bottom.is_some()
1528 || widths.left.is_some();
1529 let has_style = styles.top.is_some()
1530 || styles.right.is_some()
1531 || styles.bottom.is_some()
1532 || styles.left.is_some();
1533 has_width && has_style
1534 };
1535
1536 if has_visible_border {
1537 self.push_item(DisplayListItem::Border {
1538 bounds: bounds.into(),
1539 widths,
1540 colors,
1541 styles,
1542 border_radius,
1543 });
1544 }
1545 }
1546
1547 pub fn push_stacking_context(&mut self, z_index: i32, bounds: LogicalRect) {
1548 self.push_item(DisplayListItem::PushStackingContext { z_index, bounds: bounds.into() });
1549 }
1550
1551 pub fn pop_stacking_context(&mut self) {
1552 self.push_item(DisplayListItem::PopStackingContext);
1553 }
1554
1555 pub fn push_reference_frame(
1556 &mut self,
1557 transform_key: TransformKey,
1558 initial_transform: ComputedTransform3D,
1559 bounds: LogicalRect,
1560 ) {
1561 self.push_item(DisplayListItem::PushReferenceFrame {
1562 transform_key,
1563 initial_transform,
1564 bounds: bounds.into(),
1565 });
1566 }
1567
1568 pub fn pop_reference_frame(&mut self) {
1569 self.push_item(DisplayListItem::PopReferenceFrame);
1570 }
1571
1572 pub fn push_text_run(
1573 &mut self,
1574 glyphs: Vec<GlyphInstance>,
1575 font_hash: FontHash, font_size_px: f32,
1577 color: ColorU,
1578 clip_rect: LogicalRect,
1579 source_node_index: Option<usize>,
1580 ) {
1581 self.debug_log(format!(
1582 "[push_text_run] {} glyphs, font_size={}px, color=({},{},{},{}), clip={:?}",
1583 glyphs.len(),
1584 font_size_px,
1585 color.r,
1586 color.g,
1587 color.b,
1588 color.a,
1589 clip_rect
1590 ));
1591
1592 if !glyphs.is_empty() && color.a > 0 {
1593 self.push_item(DisplayListItem::Text {
1594 glyphs,
1595 font_hash,
1596 font_size_px,
1597 color,
1598 clip_rect: clip_rect.into(),
1599 source_node_index,
1600 });
1601 } else {
1602 self.debug_log(format!(
1603 "[push_text_run] SKIPPED: glyphs.is_empty()={}, color.a={}",
1604 glyphs.is_empty(),
1605 color.a
1606 ));
1607 }
1608 }
1609
1610 pub fn push_text_layout(
1611 &mut self,
1612 layout: Arc<dyn std::any::Any + Send + Sync>,
1613 bounds: LogicalRect,
1614 font_hash: FontHash,
1615 font_size_px: f32,
1616 color: ColorU,
1617 ) {
1618 if color.a > 0 {
1619 self.push_item(DisplayListItem::TextLayout {
1620 layout,
1621 bounds: bounds.into(),
1622 font_hash,
1623 font_size_px,
1624 color,
1625 });
1626 }
1627 }
1628
1629 pub fn push_underline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1630 if color.a > 0 && thickness > 0.0 {
1631 self.push_item(DisplayListItem::Underline {
1632 bounds: bounds.into(),
1633 color,
1634 thickness,
1635 });
1636 }
1637 }
1638
1639 pub fn push_strikethrough(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1640 if color.a > 0 && thickness > 0.0 {
1641 self.push_item(DisplayListItem::Strikethrough {
1642 bounds: bounds.into(),
1643 color,
1644 thickness,
1645 });
1646 }
1647 }
1648
1649 pub fn push_overline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1650 if color.a > 0 && thickness > 0.0 {
1651 self.push_item(DisplayListItem::Overline {
1652 bounds: bounds.into(),
1653 color,
1654 thickness,
1655 });
1656 }
1657 }
1658
1659 pub fn push_image(&mut self, bounds: LogicalRect, image: ImageRef, border_radius: BorderRadius) {
1660 self.push_item(DisplayListItem::Image { bounds: bounds.into(), image, border_radius });
1661 }
1662}
1663
1664pub fn generate_display_list<T: ParsedFontTrait + Sync + 'static>(
1666 ctx: &mut LayoutContext<T>,
1667 tree: &LayoutTree,
1668 calculated_positions: &super::PositionVec,
1669 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
1670 scroll_ids: &HashMap<usize, u64>,
1671 gpu_value_cache: Option<&GpuValueCache>,
1672 renderer_resources: &RendererResources,
1673 id_namespace: IdNamespace,
1674 dom_id: DomId,
1675) -> Result<DisplayList> {
1676 debug_info!(
1677 ctx,
1678 "[DisplayList] generate_display_list: tree has {} nodes, {} positions calculated",
1679 tree.nodes.len(),
1680 calculated_positions.len()
1681 );
1682
1683 debug_info!(ctx, "Starting display list generation");
1684 debug_info!(
1685 ctx,
1686 "Collecting stacking contexts from root node {}",
1687 tree.root
1688 );
1689
1690 let positioned_tree = PositionedTree {
1691 tree,
1692 calculated_positions,
1693 };
1694 let mut generator = DisplayListGenerator::new(
1695 ctx,
1696 scroll_offsets,
1697 &positioned_tree,
1698 scroll_ids,
1699 gpu_value_cache,
1700 renderer_resources,
1701 id_namespace,
1702 dom_id,
1703 );
1704
1705 let debug_enabled = generator.ctx.debug_messages.is_some();
1707 let mut builder = DisplayListBuilder::with_debug(debug_enabled);
1708
1709 {
1716 let root_node = tree.get(tree.root);
1717 if let Some(root) = root_node {
1718 if let Some(root_dom_id) = root.dom_node_id {
1719 let root_state = generator.get_styled_node_state(root_dom_id);
1720 let canvas_bg = get_background_color(
1721 generator.ctx.styled_dom,
1722 root_dom_id,
1723 &root_state,
1724 );
1725 if canvas_bg.a > 0 {
1726 let viewport_rect = LogicalRect {
1727 origin: LogicalPosition::zero(),
1728 size: generator.ctx.viewport_size,
1729 };
1730 builder.push_rect(viewport_rect, canvas_bg, BorderRadius::default());
1731 debug_info!(
1732 generator.ctx,
1733 "[DisplayList] Canvas background: color=({},{},{},{}), size={:?}",
1734 canvas_bg.r, canvas_bg.g, canvas_bg.b, canvas_bg.a,
1735 generator.ctx.viewport_size
1736 );
1737 }
1738 }
1739 }
1740 }
1741
1742 let stacking_context_tree = generator.collect_stacking_contexts(tree.root)?;
1747
1748 debug_info!(
1750 generator.ctx,
1751 "Generating display items from stacking context tree"
1752 );
1753 generator.generate_for_stacking_context(&mut builder, &stacking_context_tree)?;
1754
1755 let display_list = builder.build_with_debug(generator.ctx.debug_messages);
1757 debug_info!(
1758 generator.ctx,
1759 "[DisplayList] Generated {} display items",
1760 display_list.items.len()
1761 );
1762 Ok(display_list)
1763}
1764
1765struct DisplayListGenerator<'a, 'b, T: ParsedFontTrait> {
1767 ctx: &'a mut LayoutContext<'b, T>,
1768 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1769 positioned_tree: &'a PositionedTree<'a>,
1770 scroll_ids: &'a HashMap<usize, u64>,
1771 gpu_value_cache: Option<&'a GpuValueCache>,
1772 renderer_resources: &'a RendererResources,
1773 id_namespace: IdNamespace,
1774 dom_id: DomId,
1775}
1776
1777#[derive(Debug)]
1780struct StackingContext {
1781 node_index: usize,
1782 z_index: i32,
1783 child_contexts: Vec<StackingContext>,
1784 in_flow_children: Vec<usize>,
1786}
1787
1788impl<'a, 'b, T> DisplayListGenerator<'a, 'b, T>
1789where
1790 T: ParsedFontTrait + Sync + 'static,
1791{
1792 pub fn new(
1793 ctx: &'a mut LayoutContext<'b, T>,
1794 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1795 positioned_tree: &'a PositionedTree<'a>,
1796 scroll_ids: &'a HashMap<usize, u64>,
1797 gpu_value_cache: Option<&'a GpuValueCache>,
1798 renderer_resources: &'a RendererResources,
1799 id_namespace: IdNamespace,
1800 dom_id: DomId,
1801 ) -> Self {
1802 Self {
1803 ctx,
1804 scroll_offsets,
1805 positioned_tree,
1806 scroll_ids,
1807 gpu_value_cache,
1808 renderer_resources,
1809 id_namespace,
1810 dom_id,
1811 }
1812 }
1813
1814 fn get_styled_node_state(&self, dom_id: NodeId) -> azul_core::styled_dom::StyledNodeState {
1816 self.ctx
1817 .styled_dom
1818 .styled_nodes
1819 .as_container()
1820 .get(dom_id)
1821 .map(|n| n.styled_node_state.clone())
1822 .unwrap_or_default()
1823 }
1824
1825 fn is_node_hidden(&self, node_index: usize) -> bool {
1829 use azul_css::props::style::effects::StyleVisibility;
1830 let node = match self.positioned_tree.tree.get(node_index) {
1831 Some(n) => n,
1832 None => return false,
1833 };
1834 let dom_id = match node.dom_node_id {
1835 Some(id) => id,
1836 None => return false,
1837 };
1838 let node_state = self.get_styled_node_state(dom_id);
1839 match get_visibility(self.ctx.styled_dom, dom_id, &node_state) {
1840 crate::solver3::getters::MultiValue::Exact(StyleVisibility::Hidden)
1841 | crate::solver3::getters::MultiValue::Exact(StyleVisibility::Collapse) => true,
1842 _ => false,
1843 }
1844 }
1845
1846 fn get_cursor_type_for_text_node(&self, node_id: NodeId) -> CursorType {
1849 use azul_css::props::style::effects::StyleCursor;
1850
1851 let styled_node_state = self.get_styled_node_state(node_id);
1852 let node_data_container = self.ctx.styled_dom.node_data.as_container();
1853 let node_data = node_data_container.get(node_id);
1854
1855 if let Some(node_data) = node_data {
1857 if let Some(cursor_value) = self.ctx.styled_dom.get_css_property_cache().get_cursor(
1858 node_data,
1859 &node_id,
1860 &styled_node_state,
1861 ) {
1862 if let CssPropertyValue::Exact(cursor) = cursor_value {
1863 return match cursor {
1864 StyleCursor::Default => CursorType::Default,
1865 StyleCursor::Pointer => CursorType::Pointer,
1866 StyleCursor::Text => CursorType::Text,
1867 StyleCursor::Crosshair => CursorType::Crosshair,
1868 StyleCursor::Move => CursorType::Move,
1869 StyleCursor::Help => CursorType::Help,
1870 StyleCursor::Wait => CursorType::Wait,
1871 StyleCursor::Progress => CursorType::Progress,
1872 StyleCursor::NsResize => CursorType::NsResize,
1873 StyleCursor::EwResize => CursorType::EwResize,
1874 StyleCursor::NeswResize => CursorType::NeswResize,
1875 StyleCursor::NwseResize => CursorType::NwseResize,
1876 StyleCursor::NResize => CursorType::NResize,
1877 StyleCursor::SResize => CursorType::SResize,
1878 StyleCursor::EResize => CursorType::EResize,
1879 StyleCursor::WResize => CursorType::WResize,
1880 StyleCursor::Grab => CursorType::Grab,
1881 StyleCursor::Grabbing => CursorType::Grabbing,
1882 StyleCursor::RowResize => CursorType::RowResize,
1883 StyleCursor::ColResize => CursorType::ColResize,
1884 StyleCursor::SeResize | StyleCursor::NeswResize => CursorType::NeswResize,
1886 StyleCursor::ZoomIn | StyleCursor::ZoomOut => CursorType::Default,
1887 StyleCursor::Copy | StyleCursor::Alias => CursorType::Default,
1888 StyleCursor::Cell => CursorType::Crosshair,
1889 StyleCursor::AllScroll => CursorType::Move,
1890 StyleCursor::ContextMenu => CursorType::Default,
1891 StyleCursor::VerticalText => CursorType::Text,
1892 StyleCursor::Unset => CursorType::Text, };
1894 }
1895 }
1896 }
1897
1898 CursorType::Text
1900 }
1901
1902 fn paint_selections(
1905 &self,
1906 builder: &mut DisplayListBuilder,
1907 node_index: usize,
1908 ) -> Result<()> {
1909 let node = self
1910 .positioned_tree
1911 .tree
1912 .get(node_index)
1913 .ok_or(LayoutError::InvalidTree)?;
1914 let Some(dom_id) = node.dom_node_id else {
1915 return Ok(());
1916 };
1917
1918 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
1922 return Ok(());
1923 };
1924
1925 let node_pos = self
1927 .positioned_tree
1928 .calculated_positions
1929 .get(node_index)
1930 .copied()
1931 .unwrap_or_default();
1932
1933 let bp = node.box_props.unpack();
1935 let padding = &bp.padding;
1936 let border = &bp.border;
1937 let content_box_offset_x = node_pos.x + padding.left + border.left;
1938 let content_box_offset_y = node_pos.y + padding.top + border.top;
1939
1940 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1942 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
1943
1944 if !is_selectable {
1945 return Ok(());
1946 }
1947
1948 if let Some(text_selection) = self.ctx.text_selections.get(&self.ctx.styled_dom.dom_id) {
1950 if let Some(range) = text_selection.affected_nodes.get(&dom_id) {
1951 let is_collapsed = text_selection.is_collapsed();
1952
1953 if !is_collapsed {
1955 let rects = layout.get_selection_rects(range);
1956 let style = get_selection_style(self.ctx.styled_dom, Some(dom_id), self.ctx.system_style.as_ref());
1957
1958 let border_radius = BorderRadius {
1959 top_left: style.radius,
1960 top_right: style.radius,
1961 bottom_left: style.radius,
1962 bottom_right: style.radius,
1963 };
1964
1965 for mut rect in rects {
1966 rect.origin.x += content_box_offset_x;
1967 rect.origin.y += content_box_offset_y;
1968 builder.push_selection_rect(rect, style.bg_color, border_radius);
1969 }
1970 }
1971
1972 return Ok(());
1973 }
1974 }
1975
1976 Ok(())
1977 }
1978
1979 fn paint_cursor(
1983 &self,
1984 builder: &mut DisplayListBuilder,
1985 node_index: usize,
1986 ) -> Result<()> {
1987 if !self.ctx.cursor_is_visible {
1989 return Ok(());
1990 }
1991
1992 if self.ctx.cursor_locations.is_empty() {
1994 return Ok(());
1995 }
1996
1997 let node = self
1998 .positioned_tree
1999 .tree
2000 .get(node_index)
2001 .ok_or(LayoutError::InvalidTree)?;
2002 let Some(dom_id) = node.dom_node_id else {
2003 return Ok(());
2004 };
2005
2006 let is_contenteditable = super::getters::is_node_contenteditable_inherited(self.ctx.styled_dom, dom_id);
2008 if !is_contenteditable {
2009 return Ok(());
2010 }
2011
2012 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2014 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
2015 if !is_selectable {
2016 return Ok(());
2017 }
2018
2019 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
2021 return Ok(());
2022 };
2023
2024 let node_pos = self
2026 .positioned_tree
2027 .calculated_positions
2028 .get(node_index)
2029 .copied()
2030 .unwrap_or_default();
2031 let bp = node.box_props.unpack();
2032 let padding = &bp.padding;
2033 let border = &bp.border;
2034 let content_box_offset_x = node_pos.x + padding.left + border.left;
2035 let content_box_offset_y = node_pos.y + padding.top + border.top;
2036
2037 let style = get_caret_style(self.ctx.styled_dom, Some(dom_id));
2038
2039 let primary_idx_for_this_node = self.ctx.cursor_locations.iter().enumerate()
2042 .rev()
2043 .find(|(_, (cd, cn, _))| {
2044 *cd == self.ctx.styled_dom.dom_id && (*cn == dom_id || self.positioned_tree.tree.children(node_index).iter().any(|&child_idx| {
2045 self.positioned_tree.tree.get(child_idx)
2046 .and_then(|c| c.dom_node_id)
2047 .map(|id| id == *cn)
2048 .unwrap_or(false)
2049 }))
2050 })
2051 .map(|(i, _)| i);
2052
2053 for (i, (cursor_dom_id, cursor_node_id, cursor)) in self.ctx.cursor_locations.iter().enumerate() {
2054 if self.ctx.styled_dom.dom_id != *cursor_dom_id {
2056 continue;
2057 }
2058
2059 if dom_id != *cursor_node_id {
2061 let is_ifc_root_of_cursor = self.positioned_tree.tree.children(node_index)
2062 .iter()
2063 .any(|&child_idx| {
2064 self.positioned_tree.tree.get(child_idx)
2065 .and_then(|c| c.dom_node_id)
2066 .map(|id| id == *cursor_node_id)
2067 .unwrap_or(false)
2068 });
2069 if !is_ifc_root_of_cursor {
2070 continue;
2071 }
2072 }
2073
2074 let Some(mut rect) = layout.get_cursor_rect(cursor) else {
2076 continue;
2077 };
2078
2079 rect.origin.x += content_box_offset_x;
2080 rect.origin.y += content_box_offset_y;
2081 rect.size.width = style.width;
2082
2083 builder.push_cursor_rect(rect, style.color);
2084
2085 let is_primary = primary_idx_for_this_node == Some(i);
2087 if is_primary {
2088 if let Some(ref preedit) = self.ctx.preedit_text {
2089 if !preedit.is_empty() {
2090 let char_count = preedit.chars().count() as f32;
2091 let approx_char_width = style.width.max(8.0);
2092 let preedit_width = char_count * approx_char_width;
2093 let underline_bounds = azul_core::geom::LogicalRect {
2094 origin: azul_core::geom::LogicalPosition {
2095 x: rect.origin.x + rect.size.width,
2096 y: rect.origin.y + rect.size.height - 2.0,
2097 },
2098 size: azul_core::geom::LogicalSize {
2099 width: preedit_width,
2100 height: 2.0,
2101 },
2102 };
2103 builder.push_underline(underline_bounds, style.color, 2.0);
2104 }
2105 }
2106 }
2107 }
2108
2109 Ok(())
2110 }
2111
2112 fn paint_selection_and_cursor(
2115 &self,
2116 builder: &mut DisplayListBuilder,
2117 node_index: usize,
2118 ) -> Result<()> {
2119 self.paint_selections(builder, node_index)?;
2120 self.paint_cursor(builder, node_index)?;
2121 Ok(())
2122 }
2123
2124 fn collect_stacking_contexts(&mut self, node_index: usize) -> Result<StackingContext> {
2127 let node = self
2128 .positioned_tree
2129 .tree
2130 .get(node_index)
2131 .ok_or(LayoutError::InvalidTree)?;
2132 let z_index = get_z_index(self.ctx.styled_dom, node.dom_node_id);
2133
2134 if let Some(dom_id) = node.dom_node_id {
2135 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2136 debug_info!(
2137 self.ctx,
2138 "Collecting stacking context for node {} ({:?}), z-index={}",
2139 node_index,
2140 node_type.get_node_type(),
2141 z_index
2142 );
2143 }
2144
2145 let mut child_contexts = Vec::new();
2146 let mut in_flow_children = Vec::new();
2147
2148 for &child_index in self.positioned_tree.tree.children(node_index) {
2149 if self.establishes_stacking_context(child_index) {
2150 child_contexts.push(self.collect_stacking_contexts(child_index)?);
2151 } else {
2152 in_flow_children.push(child_index);
2153 self.find_nested_stacking_contexts(child_index, &mut child_contexts)?;
2157 }
2158 }
2159
2160 Ok(StackingContext {
2161 node_index,
2162 z_index,
2163 child_contexts,
2164 in_flow_children,
2165 })
2166 }
2167
2168 fn find_nested_stacking_contexts(
2171 &mut self,
2172 parent_index: usize,
2173 child_contexts: &mut Vec<StackingContext>,
2174 ) -> Result<()> {
2175 for &child_index in self.positioned_tree.tree.children(parent_index) {
2176 if self.establishes_stacking_context(child_index) {
2177 child_contexts.push(self.collect_stacking_contexts(child_index)?);
2178 } else {
2179 self.find_nested_stacking_contexts(child_index, child_contexts)?;
2180 }
2181 }
2182 Ok(())
2183 }
2184
2185 fn generate_for_stacking_context(
2199 &mut self,
2200 builder: &mut DisplayListBuilder,
2201 context: &StackingContext,
2202 ) -> Result<()> {
2203 let node = self
2205 .positioned_tree
2206 .tree
2207 .get(context.node_index)
2208 .ok_or(LayoutError::InvalidTree)?;
2209
2210 if let Some(dom_id) = node.dom_node_id {
2211 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2212 debug_info!(
2213 self.ctx,
2214 "Painting stacking context for node {} ({:?}), z-index={}, {} child contexts, {} \
2215 in-flow children",
2216 context.node_index,
2217 node_type.get_node_type(),
2218 context.z_index,
2219 context.child_contexts.len(),
2220 context.in_flow_children.len()
2221 );
2222 }
2223
2224 builder.set_current_node(node.dom_node_id);
2228
2229 let is_fixed_position = node.dom_node_id
2231 .map(|dom_id| get_position_type(self.ctx.styled_dom, Some(dom_id)) == LayoutPosition::Fixed)
2232 .unwrap_or(false);
2233 if is_fixed_position {
2234 builder.begin_fixed_position_element();
2235 }
2236
2237 let has_reference_frame = node.dom_node_id.and_then(|dom_id| {
2240 self.gpu_value_cache.and_then(|cache| {
2241 let key = cache.css_transform_keys.get(&dom_id)?;
2242 let transform = cache.css_current_transform_values.get(&dom_id)?;
2243 Some((*key, *transform))
2244 })
2245 });
2246
2247 let node_pos = self
2250 .positioned_tree
2251 .calculated_positions
2252 .get(context.node_index)
2253 .copied()
2254 .unwrap_or_default();
2255 let node_size = node.used_size.unwrap_or(LogicalSize {
2256 width: 0.0,
2257 height: 0.0,
2258 });
2259 let node_bounds = LogicalRect {
2260 origin: node_pos,
2261 size: node_size,
2262 };
2263
2264 if let Some((transform_key, initial_transform)) = has_reference_frame {
2266 builder.push_reference_frame(transform_key, initial_transform, node_bounds);
2267 }
2268
2269 builder.push_stacking_context(context.z_index, node_bounds);
2270
2271 let mut pushed_opacity = false;
2273 let mut pushed_filter = false;
2274 let mut pushed_backdrop_filter = false;
2275
2276 if let Some(dom_id) = node.dom_node_id {
2277 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2278 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2279
2280 let opacity = crate::solver3::getters::get_opacity(
2282 self.ctx.styled_dom, dom_id, node_state,
2283 );
2284
2285 if opacity < 1.0 {
2286 builder.push_item(DisplayListItem::PushOpacity {
2287 bounds: node_bounds.into(),
2288 opacity,
2289 });
2290 pushed_opacity = true;
2291 }
2292
2293 if let Some(filter_vec_value) = self.ctx.styled_dom.css_property_cache.ptr
2295 .get_filter(node_data, &dom_id, node_state)
2296 {
2297 if let Some(filter_vec) = filter_vec_value.get_property() {
2298 let filters: Vec<_> = filter_vec.as_ref().to_vec();
2299 if !filters.is_empty() {
2300 builder.push_item(DisplayListItem::PushFilter {
2301 bounds: node_bounds.into(),
2302 filters,
2303 });
2304 pushed_filter = true;
2305 }
2306 }
2307 }
2308
2309 if let Some(backdrop_filter_value) = self.ctx.styled_dom.css_property_cache.ptr
2311 .get_backdrop_filter(node_data, &dom_id, node_state)
2312 {
2313 if let Some(filter_vec) = backdrop_filter_value.get_property() {
2314 let filters: Vec<_> = filter_vec.as_ref().to_vec();
2315 if !filters.is_empty() {
2316 builder.push_item(DisplayListItem::PushBackdropFilter {
2317 bounds: node_bounds.into(),
2318 filters,
2319 });
2320 pushed_backdrop_filter = true;
2321 }
2322 }
2323 }
2324 }
2325
2326 let did_push_image_mask = self.push_image_mask_clip(builder, context.node_index);
2329
2330 self.paint_node_background_and_border(builder, context.node_index)?;
2336
2337 if !self.is_node_hidden(context.node_index) {
2344 if let Some(dom_id) = node.dom_node_id {
2345 let styled_node_state = self.get_styled_node_state(dom_id);
2346 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2347 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2348 if overflow_x.is_scroll() || overflow_y.is_scroll() {
2349 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
2350 builder.push_hit_test_area(node_bounds, tag_id);
2351 }
2352 }
2353 }
2354 }
2355
2356 let did_push_clip_or_scroll = self.push_node_clips(builder, context.node_index, node)?;
2363
2364 let mut negative_z_children: Vec<_> = context
2367 .child_contexts
2368 .iter()
2369 .filter(|c| c.z_index < 0)
2370 .collect();
2371 negative_z_children.sort_by_key(|c| c.z_index);
2372 for child in negative_z_children {
2373 self.generate_for_stacking_context(builder, child)?;
2374 }
2375
2376 self.paint_in_flow_descendants(builder, context.node_index, &context.in_flow_children)?;
2378
2379 for child in context.child_contexts.iter().filter(|c| c.z_index == 0) {
2382 self.generate_for_stacking_context(builder, child)?;
2383 }
2384
2385 let mut positive_z_children: Vec<_> = context
2388 .child_contexts
2389 .iter()
2390 .filter(|c| c.z_index > 0)
2391 .collect();
2392
2393 positive_z_children.sort_by_key(|c| c.z_index);
2394
2395 for child in positive_z_children {
2396 self.generate_for_stacking_context(builder, child)?;
2397 }
2398
2399 if did_push_image_mask {
2401 builder.pop_image_mask_clip();
2402 }
2403
2404 if pushed_backdrop_filter {
2406 builder.push_item(DisplayListItem::PopBackdropFilter);
2407 }
2408 if pushed_filter {
2409 builder.push_item(DisplayListItem::PopFilter);
2410 }
2411 if pushed_opacity {
2412 builder.push_item(DisplayListItem::PopOpacity);
2413 }
2414
2415 builder.pop_stacking_context();
2417
2418 if has_reference_frame.is_some() {
2420 builder.pop_reference_frame();
2421 }
2422
2423 if is_fixed_position {
2425 builder.end_fixed_position_element();
2426 }
2427
2428 if did_push_clip_or_scroll {
2432 if let Some(dom_id) = node.dom_node_id {
2434 if self.is_virtual_view_node(dom_id) {
2435 builder.push_virtual_view_placeholder(dom_id, node_bounds, node_bounds);
2436 }
2437 }
2438 self.pop_node_clips(builder, node)?;
2439 } else {
2440 if let Some(dom_id) = node.dom_node_id {
2442 if self.is_virtual_view_node(dom_id) {
2443 builder.push_virtual_view_placeholder(dom_id, node_bounds, node_bounds);
2444 }
2445 }
2446 }
2447
2448 self.paint_scrollbars(builder, context.node_index)?;
2451
2452 Ok(())
2453 }
2454
2455 fn paint_in_flow_descendants(
2457 &mut self,
2458 builder: &mut DisplayListBuilder,
2459 node_index: usize,
2460 children_indices: &[usize],
2461 ) -> Result<()> {
2462 self.paint_selection_and_cursor(builder, node_index)?;
2468
2469 self.paint_node_content(builder, node_index)?;
2471
2472 let mut non_float_children = Vec::new();
2483 let mut float_children = Vec::new();
2484 let mut dragging_children = Vec::new();
2485
2486 for &child_index in children_indices {
2487 if self.establishes_stacking_context(child_index) {
2490 continue;
2491 }
2492 let child_node = self
2493 .positioned_tree
2494 .tree
2495 .get(child_index)
2496 .ok_or(LayoutError::InvalidTree)?;
2497
2498 let is_dragging = if let Some(dom_id) = child_node.dom_node_id {
2500 let styled_node_state = self.get_styled_node_state(dom_id);
2501 styled_node_state.dragging
2502 } else {
2503 false
2504 };
2505
2506 if is_dragging {
2507 dragging_children.push(child_index);
2508 continue;
2509 }
2510
2511 let is_float = if let Some(dom_id) = child_node.dom_node_id {
2513 use crate::solver3::getters::get_float;
2514 let styled_node_state = self.get_styled_node_state(dom_id);
2515 let float_value = get_float(self.ctx.styled_dom, dom_id, &styled_node_state);
2516 !matches!(
2517 float_value.unwrap_or_default(),
2518 azul_css::props::layout::LayoutFloat::None
2519 )
2520 } else {
2521 false
2522 };
2523
2524 if is_float {
2525 float_children.push(child_index);
2526 } else {
2527 non_float_children.push(child_index);
2528 }
2529 }
2530
2531 for child_index in non_float_children {
2533 let child_node = self
2534 .positioned_tree
2535 .tree
2536 .get(child_index)
2537 .ok_or(LayoutError::InvalidTree)?;
2538
2539 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2541 self.gpu_value_cache.and_then(|cache| {
2542 let key = cache.css_transform_keys.get(&dom_id)?;
2543 let transform = cache.css_current_transform_values.get(&dom_id)?;
2544 Some((*key, *transform))
2545 })
2546 });
2547
2548 if let Some((transform_key, initial_transform)) = child_ref_frame {
2550 let child_pos = self
2551 .positioned_tree
2552 .calculated_positions
2553 .get(child_index)
2554 .copied()
2555 .unwrap_or_default();
2556 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2557 width: 0.0,
2558 height: 0.0,
2559 });
2560 let child_bounds = LogicalRect {
2561 origin: child_pos,
2562 size: child_size,
2563 };
2564 builder.set_current_node(child_node.dom_node_id);
2565 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2566 }
2567
2568 let did_push_child_image_mask = self.push_image_mask_clip(builder, child_index);
2570
2571 self.paint_node_background_and_border(builder, child_index)?;
2575
2576 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2578
2579 self.paint_in_flow_descendants(builder, child_index, self.positioned_tree.tree.children(child_index))?;
2581
2582 if let Some(dom_id) = child_node.dom_node_id {
2584 if self.is_virtual_view_node(dom_id) {
2585 let child_bounds = self.get_paint_rect(child_index).unwrap_or_default();
2586 builder.push_virtual_view_placeholder(dom_id, child_bounds, child_bounds);
2587 }
2588 }
2589
2590 if did_push_clip {
2592 self.pop_node_clips(builder, child_node)?;
2593 }
2594
2595 if did_push_child_image_mask {
2597 builder.pop_image_mask_clip();
2598 }
2599
2600 self.paint_scrollbars(builder, child_index)?;
2602
2603 if child_ref_frame.is_some() {
2605 builder.pop_reference_frame();
2606 }
2607 }
2608
2609 for child_index in float_children {
2612 let child_node = self
2613 .positioned_tree
2614 .tree
2615 .get(child_index)
2616 .ok_or(LayoutError::InvalidTree)?;
2617
2618 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2620 self.gpu_value_cache.and_then(|cache| {
2621 let key = cache.css_transform_keys.get(&dom_id)?;
2622 let transform = cache.css_current_transform_values.get(&dom_id)?;
2623 Some((*key, *transform))
2624 })
2625 });
2626
2627 if let Some((transform_key, initial_transform)) = child_ref_frame {
2629 let child_pos = self
2630 .positioned_tree
2631 .calculated_positions
2632 .get(child_index)
2633 .copied()
2634 .unwrap_or_default();
2635 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2636 width: 0.0,
2637 height: 0.0,
2638 });
2639 let child_bounds = LogicalRect {
2640 origin: child_pos,
2641 size: child_size,
2642 };
2643 builder.set_current_node(child_node.dom_node_id);
2644 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2645 }
2646
2647 let did_push_child_image_mask = self.push_image_mask_clip(builder, child_index);
2649 self.paint_node_background_and_border(builder, child_index)?;
2650 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2651 self.paint_in_flow_descendants(builder, child_index, self.positioned_tree.tree.children(child_index))?;
2652
2653 if let Some(dom_id) = child_node.dom_node_id {
2655 if self.is_virtual_view_node(dom_id) {
2656 let child_bounds = self.get_paint_rect(child_index).unwrap_or_default();
2657 builder.push_virtual_view_placeholder(dom_id, child_bounds, child_bounds);
2658 }
2659 }
2660
2661 if did_push_clip {
2662 self.pop_node_clips(builder, child_node)?;
2663 }
2664 if did_push_child_image_mask {
2665 builder.pop_image_mask_clip();
2666 }
2667
2668 self.paint_scrollbars(builder, child_index)?;
2670
2671 if child_ref_frame.is_some() {
2673 builder.pop_reference_frame();
2674 }
2675 }
2676
2677 for child_index in dragging_children {
2679 let child_node = self
2680 .positioned_tree
2681 .tree
2682 .get(child_index)
2683 .ok_or(LayoutError::InvalidTree)?;
2684
2685 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2687 self.gpu_value_cache.and_then(|cache| {
2688 let key = cache.css_transform_keys.get(&dom_id)?;
2689 let transform = cache.css_current_transform_values.get(&dom_id)?;
2690 Some((*key, *transform))
2691 })
2692 });
2693
2694 if let Some((transform_key, initial_transform)) = child_ref_frame {
2696 let child_pos = self
2697 .positioned_tree
2698 .calculated_positions
2699 .get(child_index)
2700 .copied()
2701 .unwrap_or_default();
2702 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2703 width: 0.0,
2704 height: 0.0,
2705 });
2706 let child_bounds = LogicalRect {
2707 origin: child_pos,
2708 size: child_size,
2709 };
2710 builder.set_current_node(child_node.dom_node_id);
2711 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2712 }
2713
2714 let did_push_child_image_mask = self.push_image_mask_clip(builder, child_index);
2716 self.paint_node_background_and_border(builder, child_index)?;
2717 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2718 self.paint_in_flow_descendants(builder, child_index, self.positioned_tree.tree.children(child_index))?;
2719
2720 if let Some(dom_id) = child_node.dom_node_id {
2722 if self.is_virtual_view_node(dom_id) {
2723 let child_bounds = self.get_paint_rect(child_index).unwrap_or_default();
2724 builder.push_virtual_view_placeholder(dom_id, child_bounds, child_bounds);
2725 }
2726 }
2727
2728 if did_push_clip {
2729 self.pop_node_clips(builder, child_node)?;
2730 }
2731 if did_push_child_image_mask {
2732 builder.pop_image_mask_clip();
2733 }
2734
2735 self.paint_scrollbars(builder, child_index)?;
2737
2738 if child_ref_frame.is_some() {
2740 builder.pop_reference_frame();
2741 }
2742 }
2743
2744 Ok(())
2745 }
2746
2747 fn is_virtual_view_node(&self, dom_id: NodeId) -> bool {
2749 let node_data_container = self.ctx.styled_dom.node_data.as_container();
2750 node_data_container
2751 .get(dom_id)
2752 .map(|nd| matches!(nd.get_node_type(), NodeType::VirtualView))
2753 .unwrap_or(false)
2754 }
2755
2756 fn push_image_mask_clip(
2759 &self,
2760 builder: &mut DisplayListBuilder,
2761 node_index: usize,
2762 ) -> bool {
2763 let node = match self.positioned_tree.tree.get(node_index) {
2764 Some(n) => n,
2765 None => return false,
2766 };
2767 let dom_id = match node.dom_node_id {
2768 Some(id) => id,
2769 None => return false,
2770 };
2771 let node_data_container = self.ctx.styled_dom.node_data.as_container();
2772 let node_data = match node_data_container.get(dom_id) {
2773 Some(nd) => nd,
2774 None => return false,
2775 };
2776 match node_data.get_svg_data() {
2777 Some(azul_core::dom::SvgNodeData::ImageClipMask(clip_mask)) => {
2778 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2779 let mask_rect = LogicalRect {
2781 origin: LogicalPosition {
2782 x: paint_rect.origin.x + clip_mask.rect.origin.x,
2783 y: paint_rect.origin.y + clip_mask.rect.origin.y,
2784 },
2785 size: clip_mask.rect.size,
2786 };
2787 builder.push_image_mask_clip(
2788 paint_rect,
2789 clip_mask.image.clone(),
2790 mask_rect,
2791 );
2792 true
2793 }
2794 #[cfg(feature = "cpurender")]
2795 Some(azul_core::dom::SvgNodeData::Path(svg_clip)) => {
2796 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2797 if let Some(mask_image) = rasterize_svg_clip_to_r8(svg_clip, &paint_rect) {
2798 builder.push_image_mask_clip(paint_rect, mask_image, paint_rect);
2799 true
2800 } else {
2801 false
2802 }
2803 }
2804 #[cfg(not(feature = "cpurender"))]
2805 Some(azul_core::dom::SvgNodeData::Path(_)) => false,
2806 Some(_) => false,
2808 None => false,
2809 }
2810 }
2811
2812 fn push_node_clips(
2827 &self,
2828 builder: &mut DisplayListBuilder,
2829 node_index: usize,
2830 node: &LayoutNodeHot,
2831 ) -> Result<bool> {
2832 let Some(dom_id) = node.dom_node_id else {
2833 return Ok(false);
2834 };
2835
2836 let styled_node_state = self.get_styled_node_state(dom_id);
2837
2838 let raw_overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2839 let raw_overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2840 let overflow_x = raw_overflow_x.resolve_computed(&raw_overflow_y);
2842 let overflow_y = raw_overflow_y.resolve_computed(&raw_overflow_x);
2843
2844 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2845 let element_size = PhysicalSizeImport {
2846 width: paint_rect.size.width,
2847 height: paint_rect.size.height,
2848 };
2849 let border_radius = get_border_radius(
2850 self.ctx.styled_dom,
2851 dom_id,
2852 &styled_node_state,
2853 element_size,
2854 self.ctx.viewport_size,
2855 );
2856
2857 let has_clip_path = if let Some(clip_path) = super::getters::get_clip_path(
2863 self.ctx.styled_dom, dom_id, &styled_node_state,
2864 ) {
2865 if let Some((clip_rect, radius)) = resolve_clip_path(&clip_path, paint_rect) {
2866 let br = if radius > 0.0 {
2867 BorderRadius {
2868 top_left: radius,
2869 top_right: radius,
2870 bottom_left: radius,
2871 bottom_right: radius,
2872 }
2873 } else {
2874 BorderRadius::default()
2875 };
2876 builder.push_clip(clip_rect, br);
2877 true
2878 } else {
2879 false
2880 }
2881 } else {
2882 false
2883 };
2884
2885 let needs_clip = overflow_x.is_clipped() || overflow_y.is_clipped();
2888
2889 if !needs_clip {
2890 return Ok(has_clip_path);
2891 }
2892
2893 let ox_clip = overflow_x.is_clipped() && !overflow_x.is_scroll() && !overflow_x.is_auto_overflow();
2898 let oy_clip = overflow_y.is_clipped() && !overflow_y.is_scroll() && !overflow_y.is_auto_overflow();
2899 let ox_visible = !overflow_x.is_clipped();
2900 let oy_visible = !overflow_y.is_clipped();
2901 let border_radius = if (ox_clip && oy_visible) || (oy_clip && ox_visible)
2902 {
2903 BorderRadius::default()
2904 } else {
2905 border_radius
2906 };
2907
2908 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2909
2910 let bp = node.box_props.unpack();
2911 let border = &bp.border;
2912
2913 let scrollbar_info = self.positioned_tree.tree.warm(node_index)
2915 .and_then(|w| w.scrollbar_info.clone())
2916 .unwrap_or_default();
2917
2918 let mut clip_rect = LogicalRect {
2926 origin: LogicalPosition {
2927 x: paint_rect.origin.x + border.left,
2928 y: paint_rect.origin.y + border.top,
2929 },
2930 size: LogicalSize {
2931 width: (paint_rect.size.width
2933 - border.left
2934 - border.right
2935 - scrollbar_info.scrollbar_width)
2936 .max(0.0),
2937 height: (paint_rect.size.height
2938 - border.top
2939 - border.bottom
2940 - scrollbar_info.scrollbar_height)
2941 .max(0.0),
2942 },
2943 };
2944
2945 apply_overflow_clip_margin(
2949 &mut clip_rect,
2950 &overflow_x,
2951 &overflow_y,
2952 self.ctx.styled_dom,
2953 dom_id,
2954 &styled_node_state,
2955 );
2956
2957 let is_virtual_view = self.is_virtual_view_node(dom_id);
2958
2959 if overflow_x.is_scroll() || overflow_y.is_scroll() {
2962 if is_virtual_view {
2963 builder.push_clip(clip_rect, border_radius);
2968 } else {
2969 builder.push_clip(clip_rect, border_radius);
2973 let scroll_id = self.scroll_ids.get(&node_index).copied().unwrap_or(0);
2974 let content_size = get_scroll_content_size(node, self.positioned_tree.tree.warm(node_index));
2975 builder.push_scroll_frame(clip_rect, content_size, scroll_id);
2976 }
2977 } else {
2978 builder.push_clip(clip_rect, border_radius);
2980 }
2981
2982 Ok(true)
2983 }
2984
2985 fn pop_node_clips(&self, builder: &mut DisplayListBuilder, node: &LayoutNodeHot) -> Result<()> {
2987 let Some(dom_id) = node.dom_node_id else {
2988 return Ok(());
2989 };
2990
2991 let styled_node_state = self.get_styled_node_state(dom_id);
2992 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2993 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2994
2995 let paint_rect = self
2996 .get_paint_rect(
2997 self.positioned_tree
2998 .tree
2999 .nodes
3000 .iter()
3001 .position(|n| n.dom_node_id == Some(dom_id))
3002 .unwrap_or(0),
3003 )
3004 .unwrap_or_default();
3005
3006 let element_size = PhysicalSizeImport {
3007 width: paint_rect.size.width,
3008 height: paint_rect.size.height,
3009 };
3010 let border_radius = get_border_radius(
3011 self.ctx.styled_dom,
3012 dom_id,
3013 &styled_node_state,
3014 element_size,
3015 self.ctx.viewport_size,
3016 );
3017
3018 let needs_clip =
3019 overflow_x.is_clipped() || overflow_y.is_clipped();
3020
3021 let is_virtual_view = self.is_virtual_view_node(dom_id);
3022
3023 if needs_clip {
3024 if (overflow_x.is_scroll() || overflow_y.is_scroll()) && !is_virtual_view {
3025 builder.pop_scroll_frame();
3027 builder.pop_clip();
3028 } else {
3029 builder.pop_clip();
3031 }
3032 }
3033
3034 if let Some(clip_path) = super::getters::get_clip_path(
3039 self.ctx.styled_dom, dom_id, &styled_node_state,
3040 ) {
3041 if resolve_clip_path(&clip_path, paint_rect).is_some() {
3042 builder.pop_clip();
3043 }
3044 }
3045
3046 Ok(())
3047 }
3048
3049 fn get_paint_rect(&self, node_index: usize) -> Option<LogicalRect> {
3067 let node = self.positioned_tree.tree.get(node_index)?;
3068 let pos = self
3069 .positioned_tree
3070 .calculated_positions
3071 .get(node_index)
3072 .copied()
3073 .unwrap_or_default();
3074 let size = node.used_size.unwrap_or_default();
3075
3076 Some(LogicalRect::new(pos, size))
3081 }
3082
3083 fn paint_node_background_and_border(
3085 &mut self,
3086 builder: &mut DisplayListBuilder,
3087 node_index: usize,
3088 ) -> Result<()> {
3089 let Some(paint_rect) = self.get_paint_rect(node_index) else {
3090 return Ok(());
3091 };
3092 let node = self
3093 .positioned_tree
3094 .tree
3095 .get(node_index)
3096 .ok_or(LayoutError::InvalidTree)?;
3097
3098 builder.set_current_node(node.dom_node_id);
3100
3101 if let Some(dom_id) = node.dom_node_id {
3104 let break_before = get_break_before(self.ctx.styled_dom, Some(dom_id));
3105 let break_after = get_break_after(self.ctx.styled_dom, Some(dom_id));
3106
3107 if is_forced_page_break(break_before) {
3109 let y_position = paint_rect.origin.y;
3110 builder.add_forced_page_break(y_position);
3111 debug_info!(
3112 self.ctx,
3113 "Registered forced page break BEFORE node {} at y={}",
3114 node_index,
3115 y_position
3116 );
3117 }
3118
3119 if is_forced_page_break(break_after) {
3121 let y_position = paint_rect.origin.y + paint_rect.size.height;
3122 builder.add_forced_page_break(y_position);
3123 debug_info!(
3124 self.ctx,
3125 "Registered forced page break AFTER node {} at y={}",
3126 node_index,
3127 y_position
3128 );
3129 }
3130 }
3131
3132 if self.is_node_hidden(node_index) {
3136 return Ok(());
3137 }
3138
3139 let warm = self.positioned_tree.tree.warm(node_index);
3148 let parent_is_flex_or_grid = warm
3149 .and_then(|w| w.parent_formatting_context.as_ref().map(|fc| matches!(fc, FormattingContext::Flex | FormattingContext::Grid)))
3150 .unwrap_or(false);
3151
3152 if let Some(dom_id) = node.dom_node_id {
3153 let display = {
3154 use crate::solver3::getters::get_display_property;
3155 get_display_property(self.ctx.styled_dom, Some(dom_id))
3156 .unwrap_or(LayoutDisplay::Inline)
3157 };
3158
3159 if display == LayoutDisplay::InlineBlock || display == LayoutDisplay::Inline {
3160 debug_info!(
3161 self.ctx,
3162 "[paint_node] node {} has display={:?}, parent_formatting_context={:?}, parent_is_flex_or_grid={}",
3163 node_index,
3164 display,
3165 warm.and_then(|w| w.parent_formatting_context.as_ref()),
3166 parent_is_flex_or_grid
3167 );
3168
3169 if !parent_is_flex_or_grid {
3170 if display == LayoutDisplay::InlineBlock
3179 && self.establishes_stacking_context(node_index)
3180 {
3181 } else {
3183 return Ok(());
3184 }
3185 }
3186 }
3188 }
3189
3190 if matches!(node.formatting_context,
3199 FormattingContext::TableRowGroup | FormattingContext::TableRow |
3200 FormattingContext::TableColumnGroup
3201 ) {
3202 return Ok(());
3203 }
3204
3205 if matches!(node.formatting_context, FormattingContext::Table) {
3207 debug_info!(
3208 self.ctx,
3209 "Painting table backgrounds/borders for node {} at {:?}",
3210 node_index,
3211 paint_rect
3212 );
3213 return self.paint_table_items(builder, node_index);
3215 }
3216
3217 if let Some(dom_id) = node.dom_node_id {
3218 let styled_node_state = self.get_styled_node_state(dom_id);
3219 let background_contents =
3220 get_background_contents(self.ctx.styled_dom, dom_id, &styled_node_state);
3221 let border_info = get_border_info(self.ctx.styled_dom, dom_id, &styled_node_state);
3222
3223 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3224 debug_info!(
3225 self.ctx,
3226 "Painting background/border for node {} ({:?}) at {:?}, backgrounds={:?}",
3227 node_index,
3228 node_type.get_node_type(),
3229 paint_rect,
3230 background_contents.len()
3231 );
3232
3233 let element_size = PhysicalSizeImport {
3236 width: paint_rect.size.width,
3237 height: paint_rect.size.height,
3238 };
3239 let simple_border_radius = get_border_radius(
3240 self.ctx.styled_dom,
3241 dom_id,
3242 &styled_node_state,
3243 element_size,
3244 self.ctx.viewport_size,
3245 );
3246 let style_border_radius =
3247 get_style_border_radius(self.ctx.styled_dom, dom_id, &styled_node_state);
3248
3249 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
3251
3252 for shadow in [
3257 super::getters::get_box_shadow_left(self.ctx.styled_dom, dom_id, node_state),
3258 super::getters::get_box_shadow_right(self.ctx.styled_dom, dom_id, node_state),
3259 super::getters::get_box_shadow_top(self.ctx.styled_dom, dom_id, node_state),
3260 super::getters::get_box_shadow_bottom(self.ctx.styled_dom, dom_id, node_state),
3261 ] {
3262 if let Some(shadow) = shadow {
3263 builder.push_item(DisplayListItem::BoxShadow {
3264 bounds: paint_rect.into(),
3265 shadow,
3266 border_radius: simple_border_radius,
3267 });
3268 }
3269 }
3270
3271 builder.push_backgrounds_and_border(
3273 paint_rect,
3274 &background_contents,
3275 &border_info,
3276 simple_border_radius,
3277 style_border_radius,
3278 self.ctx.image_cache,
3279 );
3280
3281 }
3282
3283 Ok(())
3284 }
3285
3286 fn paint_table_items(
3309 &self,
3310 builder: &mut DisplayListBuilder,
3311 table_index: usize,
3312 ) -> Result<()> {
3313 let table_node = self
3314 .positioned_tree
3315 .tree
3316 .get(table_index)
3317 .ok_or(LayoutError::InvalidTree)?;
3318
3319 let Some(table_paint_rect) = self.get_paint_rect(table_index) else {
3320 return Ok(());
3321 };
3322
3323 if let Some(dom_id) = table_node.dom_node_id {
3325 let styled_node_state = self.get_styled_node_state(dom_id);
3326 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
3327 let element_size = PhysicalSizeImport {
3328 width: table_paint_rect.size.width,
3329 height: table_paint_rect.size.height,
3330 };
3331 let border_radius = get_border_radius(
3332 self.ctx.styled_dom,
3333 dom_id,
3334 &styled_node_state,
3335 element_size,
3336 self.ctx.viewport_size,
3337 );
3338
3339 builder.push_rect(table_paint_rect, bg_color, border_radius);
3340 }
3341
3342 for &child_idx in self.positioned_tree.tree.children(table_index) {
3347 let child_node = self.positioned_tree.tree.get(child_idx);
3348 if let Some(node) = child_node {
3349 if matches!(node.formatting_context, FormattingContext::TableColumnGroup) {
3350 self.paint_element_background(builder, child_idx)?;
3352
3353 for &col_idx in self.positioned_tree.tree.children(child_idx) {
3355 self.paint_element_background(builder, col_idx)?;
3356 }
3357 }
3358 }
3359 }
3360
3361 for &child_idx in self.positioned_tree.tree.children(table_index) {
3365 let child_node = self.positioned_tree.tree.get(child_idx);
3366 if let Some(node) = child_node {
3367 match node.formatting_context {
3368 FormattingContext::TableRowGroup => {
3369 self.paint_element_background(builder, child_idx)?;
3371
3372 for &row_idx in self.positioned_tree.tree.children(child_idx) {
3374 self.paint_table_row_and_cells(builder, row_idx)?;
3375 }
3376 }
3377 FormattingContext::TableRow => {
3378 self.paint_table_row_and_cells(builder, child_idx)?;
3380 }
3381 _ => {}
3382 }
3383 }
3384 }
3385
3386 Ok(())
3393 }
3394
3395 fn paint_table_row_and_cells(
3399 &self,
3400 builder: &mut DisplayListBuilder,
3401 row_idx: usize,
3402 ) -> Result<()> {
3403 if let Some(row_node) = self.positioned_tree.tree.get(row_idx) {
3408 if let Some(dom_id) = row_node.dom_node_id {
3409 let styled_node_state = self.get_styled_node_state(dom_id);
3410 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
3411 if bg_color.a > 0 {
3412 let mut min_x = f32::MAX;
3414 let mut min_y = f32::MAX;
3415 let mut max_x = f32::MIN;
3416 let mut max_y = f32::MIN;
3417 for &cell_idx in self.positioned_tree.tree.children(row_idx) {
3418 if let Some(cell_rect) = self.get_paint_rect(cell_idx) {
3419 min_x = min_x.min(cell_rect.origin.x);
3420 min_y = min_y.min(cell_rect.origin.y);
3421 max_x = max_x.max(cell_rect.origin.x + cell_rect.size.width);
3422 max_y = max_y.max(cell_rect.origin.y + cell_rect.size.height);
3423 }
3424 }
3425 if min_x < max_x && min_y < max_y {
3426 let row_rect = LogicalRect::new(
3427 LogicalPosition::new(min_x, min_y),
3428 LogicalSize::new(max_x - min_x, max_y - min_y),
3429 );
3430 builder.push_rect(row_rect, bg_color, BorderRadius::default());
3431 }
3432 }
3433 }
3434 }
3435
3436 if let Some(_node) = self.positioned_tree.tree.get(row_idx) {
3438 for &cell_idx in self.positioned_tree.tree.children(row_idx) {
3439 self.paint_element_background(builder, cell_idx)?;
3440 }
3441 }
3442
3443 Ok(())
3444 }
3445
3446 fn paint_element_background(
3449 &self,
3450 builder: &mut DisplayListBuilder,
3451 node_index: usize,
3452 ) -> Result<()> {
3453 let Some(paint_rect) = self.get_paint_rect(node_index) else {
3454 return Ok(());
3455 };
3456
3457 let Some(node) = self.positioned_tree.tree.get(node_index) else {
3458 return Ok(());
3459 };
3460 let Some(dom_id) = node.dom_node_id else {
3461 return Ok(());
3462 };
3463
3464 let styled_node_state = self.get_styled_node_state(dom_id);
3465 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
3466
3467 if bg_color.a == 0 {
3469 return Ok(());
3470 }
3471
3472 let element_size = PhysicalSizeImport {
3473 width: paint_rect.size.width,
3474 height: paint_rect.size.height,
3475 };
3476 let border_radius = get_border_radius(
3477 self.ctx.styled_dom,
3478 dom_id,
3479 &styled_node_state,
3480 element_size,
3481 self.ctx.viewport_size,
3482 );
3483
3484 builder.push_rect(paint_rect, bg_color, border_radius);
3485
3486 Ok(())
3487 }
3488
3489 fn paint_node_content(
3491 &mut self,
3492 builder: &mut DisplayListBuilder,
3493 node_index: usize,
3494 ) -> Result<()> {
3495 if self.is_node_hidden(node_index) {
3497 return Ok(());
3498 }
3499
3500 let node = self
3501 .positioned_tree
3502 .tree
3503 .get(node_index)
3504 .ok_or(LayoutError::InvalidTree)?;
3505 let node_warm = self.positioned_tree.tree.warm(node_index);
3506
3507 builder.set_current_node(node.dom_node_id);
3509
3510 let Some(mut paint_rect) = self.get_paint_rect(node_index) else {
3511 return Ok(());
3512 };
3513
3514 if paint_rect.size.width == 0.0 || paint_rect.size.height == 0.0 {
3517 if let Some(cached_layout) = node_warm.and_then(|w| w.inline_layout_result.as_ref()) {
3518 let content_bounds = cached_layout.layout.bounds();
3519 paint_rect.size.width = content_bounds.width;
3520 paint_rect.size.height = content_bounds.height;
3521 }
3522 }
3523
3524 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
3529 let is_scrollable = if let Some(dom_id) = node.dom_node_id {
3530 let styled_node_state = self.get_styled_node_state(dom_id);
3531 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
3532 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
3533 overflow_x.is_scroll() || overflow_y.is_scroll()
3534 } else {
3535 false
3536 };
3537
3538 if !is_scrollable {
3544 builder.push_hit_test_area(paint_rect, tag_id);
3545 }
3546 }
3547
3548 if let Some(cached_layout) = node_warm.and_then(|w| w.inline_layout_result.as_ref()) {
3550 let inline_layout = &cached_layout.layout;
3551 debug_info!(
3552 self.ctx,
3553 "[paint_node] node {} has inline_layout with {} items",
3554 node_index,
3555 inline_layout.items.len()
3556 );
3557
3558 if let Some(dom_id) = node.dom_node_id {
3559 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3560 debug_info!(
3561 self.ctx,
3562 "Painting inline content for node {} ({:?}) at {:?}, {} layout items",
3563 node_index,
3564 node_type.get_node_type(),
3565 paint_rect,
3566 inline_layout.items.len()
3567 );
3568 }
3569
3570 let border_box = BorderBoxRect(paint_rect);
3574 let nbp = node.box_props.unpack();
3575 let mut content_box_rect =
3576 border_box.to_content_box(&nbp.padding, &nbp.border).rect();
3577
3578 let viewport_clip_rect = content_box_rect;
3582
3583 let content_size = get_scroll_content_size(node, node_warm);
3587 if content_size.height > content_box_rect.size.height {
3588 content_box_rect.size.height = content_size.height;
3589 }
3590 if content_size.width > content_box_rect.size.width {
3591 content_box_rect.size.width = content_size.width;
3592 }
3593
3594 let mut pushed_text_shadow = false;
3596 if let Some(dom_id) = node.dom_node_id {
3597 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3598 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
3599 if let Some(shadow_val) = self.ctx.styled_dom.css_property_cache.ptr
3600 .get_text_shadow(node_data, &dom_id, node_state)
3601 {
3602 if let Some(shadow) = shadow_val.get_property() {
3603 builder.push_item(DisplayListItem::PushTextShadow {
3604 shadow: (**shadow).clone(),
3605 });
3606 pushed_text_shadow = true;
3607 }
3608 }
3609 }
3610
3611 self.paint_inline_content(builder, content_box_rect, viewport_clip_rect, inline_layout, node_index)?;
3612
3613 if pushed_text_shadow {
3614 builder.push_item(DisplayListItem::PopTextShadow);
3615 }
3616 } else if let Some(dom_id) = node.dom_node_id {
3617 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3621 if let NodeType::Image(image_ref) = node_data.get_node_type() {
3622 debug_info!(
3623 self.ctx,
3624 "Painting image for node {} at {:?}",
3625 node_index,
3626 paint_rect
3627 );
3628 let styled_node_state = self.get_styled_node_state(dom_id);
3630 let element_size = PhysicalSizeImport {
3631 width: paint_rect.size.width,
3632 height: paint_rect.size.height,
3633 };
3634 let border_radius = get_border_radius(
3635 self.ctx.styled_dom,
3636 dom_id,
3637 &styled_node_state,
3638 element_size,
3639 self.ctx.viewport_size,
3640 );
3641 builder.push_image(paint_rect, image_ref.as_ref().clone(), border_radius);
3643 }
3644 }
3645
3646 Ok(())
3647 }
3648
3649 fn paint_scrollbars(&self, builder: &mut DisplayListBuilder, node_index: usize) -> Result<()> {
3652 if self.is_node_hidden(node_index) {
3655 return Ok(());
3656 }
3657
3658 let node = self
3659 .positioned_tree
3660 .tree
3661 .get(node_index)
3662 .ok_or(LayoutError::InvalidTree)?;
3663
3664 let Some(paint_rect) = self.get_paint_rect(node_index) else {
3665 return Ok(());
3666 };
3667
3668 let scrollbar_info = self.positioned_tree.tree.warm(node_index)
3670 .and_then(|w| w.scrollbar_info.clone())
3671 .unwrap_or_default();
3672
3673 let node_id = node.dom_node_id;
3675
3676 let scrollbar_style = node_id
3678 .map(|nid| {
3679 let node_state =
3680 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3681 crate::solver3::getters::get_scrollbar_style_cached(self.ctx, nid, node_state)
3682 })
3683 .unwrap_or_default();
3684
3685 if matches!(
3687 scrollbar_style.width_mode,
3688 azul_css::props::style::scrollbar::LayoutScrollbarWidth::None
3689 ) {
3690 return Ok(());
3691 }
3692
3693 let scrollbar_gutter = node_id
3696 .and_then(|nid| {
3697 let node_state =
3698 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3699 get_scrollbar_gutter_property(self.ctx.styled_dom, nid, node_state).exact()
3700 })
3701 .unwrap_or_default();
3702 let gutter_is_stable = matches!(
3703 scrollbar_gutter,
3704 azul_css::props::layout::overflow::StyleScrollbarGutter::Stable
3705 | azul_css::props::layout::overflow::StyleScrollbarGutter::StableBothEdges
3706 );
3707 let gutter_both_edges = matches!(
3708 scrollbar_gutter,
3709 azul_css::props::layout::overflow::StyleScrollbarGutter::StableBothEdges
3710 );
3711
3712 if gutter_is_stable {
3713 let gbp = node.box_props.unpack();
3714 let border = &gbp.border;
3715 let gutter_width = scrollbar_style.visual_width_px;
3716 let bg_color = node_id
3718 .map(|nid| {
3719 let node_state =
3720 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3721 get_background_color(self.ctx.styled_dom, nid, node_state)
3722 })
3723 .unwrap_or(ColorU::TRANSPARENT);
3724
3725 if !scrollbar_info.needs_vertical && gutter_width > 0.0 {
3726 let gutter_rect = LogicalRect {
3728 origin: LogicalPosition::new(
3729 paint_rect.origin.x + paint_rect.size.width - border.right - gutter_width,
3730 paint_rect.origin.y + border.top,
3731 ),
3732 size: LogicalSize::new(
3733 gutter_width,
3734 (paint_rect.size.height - border.top - border.bottom).max(0.0),
3735 ),
3736 };
3737 builder.push_rect(gutter_rect, bg_color, BorderRadius::default());
3738
3739 if gutter_both_edges {
3741 let left_gutter_rect = LogicalRect {
3742 origin: LogicalPosition::new(
3743 paint_rect.origin.x + border.left,
3744 paint_rect.origin.y + border.top,
3745 ),
3746 size: LogicalSize::new(
3747 gutter_width,
3748 (paint_rect.size.height - border.top - border.bottom).max(0.0),
3749 ),
3750 };
3751 builder.push_rect(left_gutter_rect, bg_color, BorderRadius::default());
3752 }
3753 }
3754 }
3755
3756 let sbp = node.box_props.unpack();
3758 let border = &sbp.border;
3759
3760 let container_border_radius = node_id
3762 .map(|nid| {
3763 let node_state =
3764 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3765 let element_size = PhysicalSizeImport {
3766 width: paint_rect.size.width,
3767 height: paint_rect.size.height,
3768 };
3769 let viewport_size =
3770 LogicalSize::new(self.ctx.viewport_size.width, self.ctx.viewport_size.height);
3771 get_border_radius(
3772 self.ctx.styled_dom,
3773 nid,
3774 node_state,
3775 element_size,
3776 viewport_size,
3777 )
3778 })
3779 .unwrap_or_default();
3780
3781 let inner_rect = LogicalRect {
3784 origin: LogicalPosition::new(
3785 paint_rect.origin.x + border.left,
3786 paint_rect.origin.y + border.top,
3787 ),
3788 size: LogicalSize::new(
3789 (paint_rect.size.width - border.left - border.right).max(0.0),
3790 (paint_rect.size.height - border.top - border.bottom).max(0.0),
3791 ),
3792 };
3793
3794 let (scroll_offset_x, scroll_offset_y) = node_id
3798 .and_then(|nid| {
3799 self.scroll_offsets.get(&nid).map(|pos| {
3800 (
3801 pos.children_rect.origin.x - pos.parent_rect.origin.x,
3802 pos.children_rect.origin.y - pos.parent_rect.origin.y,
3803 )
3804 })
3805 })
3806 .unwrap_or((0.0, 0.0));
3807
3808 let content_size = node_id
3814 .and_then(|nid| self.scroll_offsets.get(&nid))
3815 .map(|pos| pos.children_rect.size)
3816 .unwrap_or_else(|| self.positioned_tree.tree.get_content_size(node_index));
3817
3818 let thumb_radius = scrollbar_style.visual_width_px / 2.0;
3820 let thumb_border_radius = BorderRadius {
3821 top_left: thumb_radius,
3822 top_right: thumb_radius,
3823 bottom_left: thumb_radius,
3824 bottom_right: thumb_radius,
3825 };
3826
3827 if scrollbar_info.needs_vertical {
3828 let opacity_key = node_id.map(|nid| {
3835 self.gpu_value_cache
3836 .and_then(|cache| {
3837 cache
3838 .scrollbar_v_opacity_keys
3839 .get(&(self.dom_id, nid))
3840 .copied()
3841 })
3842 .unwrap_or_else(|| OpacityKey::unique())
3843 });
3844
3845 let button_size = if scrollbar_style.show_scroll_buttons {
3847 scrollbar_style.scroll_button_size_px
3848 } else {
3849 0.0
3850 };
3851 let v_geom = compute_scrollbar_geometry_with_button_size(
3852 ScrollbarOrientation::Vertical,
3853 inner_rect,
3854 content_size,
3855 scroll_offset_y,
3856 scrollbar_style.visual_width_px,
3857 scrollbar_info.needs_horizontal,
3858 button_size,
3859 );
3860
3861 let thumb_bounds = LogicalRect {
3863 origin: LogicalPosition::new(
3864 v_geom.track_rect.origin.x,
3865 v_geom.track_rect.origin.y + v_geom.button_size,
3866 ),
3867 size: LogicalSize::new(v_geom.width_px, v_geom.thumb_length),
3868 };
3869
3870 let thumb_transform_key = node_id.map(|nid| {
3875 self.gpu_value_cache
3876 .and_then(|cache| cache.transform_keys.get(&nid).copied())
3877 .unwrap_or_else(|| TransformKey::unique())
3878 });
3879
3880 let thumb_initial_transform =
3882 ComputedTransform3D::new_translation(0.0, v_geom.thumb_offset, 0.0);
3883
3884 let hit_id = node_id
3886 .map(|nid| azul_core::hit_test::ScrollbarHitId::VerticalThumb(self.dom_id, nid));
3887
3888 let (button_decrement_bounds, button_increment_bounds) = if scrollbar_style.show_scroll_buttons && v_geom.button_size > 0.0 {
3890 (
3891 Some(LogicalRect {
3892 origin: v_geom.track_rect.origin,
3893 size: LogicalSize::new(v_geom.button_size, v_geom.button_size),
3894 }),
3895 Some(LogicalRect {
3896 origin: LogicalPosition::new(
3897 v_geom.track_rect.origin.x,
3898 v_geom.track_rect.origin.y + v_geom.track_rect.size.height - v_geom.button_size,
3899 ),
3900 size: LogicalSize::new(v_geom.button_size, v_geom.button_size),
3901 }),
3902 )
3903 } else {
3904 (None, None)
3905 };
3906 builder.push_scrollbar_styled(ScrollbarDrawInfo {
3907 bounds: v_geom.track_rect.into(),
3908 orientation: ScrollbarOrientation::Vertical,
3909 track_bounds: v_geom.track_rect.into(),
3910 track_color: scrollbar_style.track_color,
3911 thumb_bounds: thumb_bounds.into(),
3912 thumb_color: scrollbar_style.thumb_color,
3913 thumb_border_radius,
3914 button_decrement_bounds: button_decrement_bounds.map(|b| b.into()),
3915 button_increment_bounds: button_increment_bounds.map(|b| b.into()),
3916 button_color: scrollbar_style.button_color,
3917 opacity_key,
3918 thumb_transform_key,
3919 thumb_initial_transform,
3920 hit_id,
3921 clip_to_container_border: scrollbar_style.clip_to_container_border,
3922 container_border_radius,
3923 visibility: scrollbar_style.visibility,
3924 });
3925 }
3926
3927 if scrollbar_info.needs_horizontal {
3928 let opacity_key = node_id.map(|nid| {
3930 self.gpu_value_cache
3931 .and_then(|cache| {
3932 cache
3933 .scrollbar_h_opacity_keys
3934 .get(&(self.dom_id, nid))
3935 .copied()
3936 })
3937 .unwrap_or_else(|| OpacityKey::unique())
3938 });
3939
3940 let h_button_size = if scrollbar_style.show_scroll_buttons {
3942 scrollbar_style.scroll_button_size_px
3943 } else {
3944 0.0
3945 };
3946 let h_geom = compute_scrollbar_geometry_with_button_size(
3947 ScrollbarOrientation::Horizontal,
3948 inner_rect,
3949 content_size,
3950 scroll_offset_x,
3951 scrollbar_style.visual_width_px,
3952 scrollbar_info.needs_vertical,
3953 h_button_size,
3954 );
3955
3956 let thumb_bounds = LogicalRect {
3958 origin: LogicalPosition::new(
3959 h_geom.track_rect.origin.x + h_geom.button_size,
3960 h_geom.track_rect.origin.y,
3961 ),
3962 size: LogicalSize::new(h_geom.thumb_length, h_geom.width_px),
3963 };
3964
3965 let thumb_transform_key = node_id.map(|nid| {
3967 self.gpu_value_cache
3968 .and_then(|cache| cache.h_transform_keys.get(&nid).copied())
3969 .unwrap_or_else(|| TransformKey::unique())
3970 });
3971 let thumb_initial_transform =
3972 ComputedTransform3D::new_translation(h_geom.thumb_offset, 0.0, 0.0);
3973
3974 let hit_id = node_id
3976 .map(|nid| azul_core::hit_test::ScrollbarHitId::HorizontalThumb(self.dom_id, nid));
3977
3978 let (button_decrement_bounds, button_increment_bounds) = if scrollbar_style.show_scroll_buttons && h_geom.button_size > 0.0 {
3980 (
3981 Some(LogicalRect {
3982 origin: h_geom.track_rect.origin,
3983 size: LogicalSize::new(h_geom.button_size, h_geom.button_size),
3984 }),
3985 Some(LogicalRect {
3986 origin: LogicalPosition::new(
3987 h_geom.track_rect.origin.x + h_geom.track_rect.size.width - h_geom.button_size,
3988 h_geom.track_rect.origin.y,
3989 ),
3990 size: LogicalSize::new(h_geom.button_size, h_geom.button_size),
3991 }),
3992 )
3993 } else {
3994 (None, None)
3995 };
3996 builder.push_scrollbar_styled(ScrollbarDrawInfo {
3997 bounds: h_geom.track_rect.into(),
3998 orientation: ScrollbarOrientation::Horizontal,
3999 track_bounds: h_geom.track_rect.into(),
4000 track_color: scrollbar_style.track_color,
4001 thumb_bounds: thumb_bounds.into(),
4002 thumb_color: scrollbar_style.thumb_color,
4003 thumb_border_radius,
4004 button_decrement_bounds: button_decrement_bounds.map(|b| b.into()),
4005 button_increment_bounds: button_increment_bounds.map(|b| b.into()),
4006 button_color: scrollbar_style.button_color,
4007 opacity_key,
4008 thumb_transform_key,
4009 thumb_initial_transform,
4010 hit_id,
4011 clip_to_container_border: scrollbar_style.clip_to_container_border,
4012 container_border_radius,
4013 visibility: scrollbar_style.visibility,
4014 });
4015 }
4016
4017 Ok(())
4018 }
4019
4020 fn paint_inline_content(
4022 &self,
4023 builder: &mut DisplayListBuilder,
4024 container_rect: LogicalRect,
4025 viewport_clip_rect: LogicalRect,
4026 layout: &UnifiedLayout,
4027 source_node_index: usize,
4028 ) -> Result<()> {
4029 let layout_bounds = layout.bounds();
4045 let actual_bounds = if layout_bounds.width > 0.0 && layout_bounds.height > 0.0 {
4046 LogicalRect {
4047 origin: container_rect.origin,
4048 size: LogicalSize {
4049 width: layout_bounds.width,
4050 height: layout_bounds.height,
4051 },
4052 }
4053 } else {
4054 LogicalRect {
4057 origin: container_rect.origin,
4058 size: LogicalSize::default(),
4059 }
4060 };
4061
4062 if layout_bounds.width > 0.0 || layout_bounds.height > 0.0 {
4066 builder.push_text_layout(
4067 Arc::new(layout.clone()) as Arc<dyn std::any::Any + Send + Sync>,
4068 actual_bounds,
4069 FontHash::from_hash(0), 12.0, ColorU {
4072 r: 0,
4073 g: 0,
4074 b: 0,
4075 a: 255,
4076 }, );
4078 }
4079
4080 let glyph_runs = crate::text3::glyphs::get_glyph_runs_simple(layout);
4081
4082 for glyph_run in glyph_runs.iter() {
4085 if let (Some(first_glyph), Some(last_glyph)) =
4087 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
4088 {
4089 let run_start_x = container_rect.origin.x + first_glyph.point.x;
4091 let run_end_x = container_rect.origin.x + last_glyph.point.x;
4092 let run_width = (run_end_x - run_start_x).max(0.0);
4093
4094 if run_width <= 0.0 {
4096 continue;
4097 }
4098
4099 let baseline_y = container_rect.origin.y + first_glyph.point.y;
4101 let font_size = glyph_run.font_size_px;
4102 let ascent = font_size * 0.8; let mut run_bounds = LogicalRect::new(
4105 LogicalPosition::new(run_start_x, baseline_y - ascent),
4106 LogicalSize::new(run_width, font_size),
4107 );
4108
4109 if let Some(border) = &glyph_run.border {
4112 let left_inset = border.left_inset();
4113 let right_inset = border.right_inset();
4114 let top_inset = border.top_inset();
4115 let bottom_inset = border.bottom_inset();
4116
4117 run_bounds.origin.x -= left_inset;
4118 run_bounds.origin.y -= top_inset;
4119 run_bounds.size.width += left_inset + right_inset;
4120 run_bounds.size.height += top_inset + bottom_inset;
4121 }
4122
4123 builder.push_inline_backgrounds_and_border(
4124 run_bounds,
4125 glyph_run.background_color,
4126 &glyph_run.background_content,
4127 glyph_run.border.as_ref(),
4128 self.ctx.image_cache,
4129 );
4130 }
4131 }
4132
4133 for (_idx, glyph_run) in glyph_runs.iter().enumerate() {
4135 let clip_rect = viewport_clip_rect;
4139
4140 let offset_glyphs: Vec<GlyphInstance> = glyph_run
4143 .glyphs
4144 .iter()
4145 .map(|g| {
4146 let mut g = g.clone();
4147 g.point.x += container_rect.origin.x;
4148 g.point.y += container_rect.origin.y;
4149 g
4150 })
4151 .collect();
4152
4153 builder.push_text_run(
4155 offset_glyphs,
4156 FontHash::from_hash(glyph_run.font_hash),
4157 glyph_run.font_size_px,
4158 glyph_run.color,
4159 clip_rect,
4160 Some(source_node_index),
4161 );
4162
4163 let needs_underline = glyph_run.text_decoration.underline || glyph_run.is_ime_preview;
4165 let needs_strikethrough = glyph_run.text_decoration.strikethrough;
4166 let needs_overline = glyph_run.text_decoration.overline;
4167
4168 if needs_underline || needs_strikethrough || needs_overline {
4169 if let (Some(first_glyph), Some(last_glyph)) =
4171 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
4172 {
4173 let decoration_start_x = container_rect.origin.x + first_glyph.point.x;
4174 let decoration_end_x = container_rect.origin.x + last_glyph.point.x;
4175 let decoration_width = decoration_end_x - decoration_start_x;
4176
4177 let font_size = glyph_run.font_size_px;
4180 let thickness = (font_size * 0.08).max(1.0); let baseline_y = container_rect.origin.y + first_glyph.point.y;
4184
4185 if needs_underline {
4186 let underline_y = baseline_y + (font_size * 0.12);
4189 let underline_bounds = LogicalRect::new(
4190 LogicalPosition::new(decoration_start_x, underline_y),
4191 LogicalSize::new(decoration_width, thickness),
4192 );
4193 builder.push_underline(underline_bounds, glyph_run.color, thickness);
4194 }
4195
4196 if needs_strikethrough {
4197 let strikethrough_y = baseline_y - (font_size * 0.3);
4199 let strikethrough_bounds = LogicalRect::new(
4200 LogicalPosition::new(decoration_start_x, strikethrough_y),
4201 LogicalSize::new(decoration_width, thickness),
4202 );
4203 builder.push_strikethrough(
4204 strikethrough_bounds,
4205 glyph_run.color,
4206 thickness,
4207 );
4208 }
4209
4210 if needs_overline {
4211 let overline_y = baseline_y - (font_size * 0.85);
4213 let overline_bounds = LogicalRect::new(
4214 LogicalPosition::new(decoration_start_x, overline_y),
4215 LogicalSize::new(decoration_width, thickness),
4216 );
4217 builder.push_overline(overline_bounds, glyph_run.color, thickness);
4218 }
4219 }
4220 }
4221 }
4222
4223 for glyph_run in glyph_runs.iter() {
4226 let Some(source_node_id) = glyph_run.source_node_id else {
4228 continue;
4229 };
4230
4231 if let (Some(first_glyph), Some(last_glyph)) =
4233 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
4234 {
4235 let run_start_x = container_rect.origin.x + first_glyph.point.x;
4236 let run_end_x = container_rect.origin.x + last_glyph.point.x;
4237 let run_width = (run_end_x - run_start_x).max(0.0);
4238
4239 if run_width <= 0.0 {
4241 continue;
4242 }
4243
4244 let baseline_y = container_rect.origin.y + first_glyph.point.y;
4246 let font_size = glyph_run.font_size_px;
4247 let ascent = font_size * 0.8; let run_bounds = LogicalRect::new(
4250 LogicalPosition::new(run_start_x, baseline_y - ascent),
4251 LogicalSize::new(run_width, font_size),
4252 );
4253
4254 let cursor_type = self.get_cursor_type_for_text_node(source_node_id);
4257
4258 let tag_value = ((self.dom_id.inner as u64) << 32) | (source_node_id.index() as u64);
4262 let tag_type = TAG_TYPE_CURSOR | (cursor_type as u16);
4263 let tag_id = (tag_value, tag_type);
4264
4265 builder.push_hit_test_area(run_bounds, tag_id);
4266 }
4267 }
4268
4269 for positioned_item in &layout.items {
4273 self.paint_inline_object(builder, container_rect.origin, positioned_item)?;
4274 }
4275 Ok(())
4276 }
4277
4278 fn paint_inline_object(
4280 &self,
4281 builder: &mut DisplayListBuilder,
4282 base_pos: LogicalPosition,
4283 positioned_item: &PositionedItem,
4284 ) -> Result<()> {
4285 let ShapedItem::Object {
4286 content, bounds, ..
4287 } = &positioned_item.item
4288 else {
4289 return Ok(());
4291 };
4292
4293 let object_bounds = LogicalRect::new(
4296 LogicalPosition::new(
4297 base_pos.x + positioned_item.position.x,
4298 base_pos.y + positioned_item.position.y,
4299 ),
4300 LogicalSize::new(bounds.width, bounds.height),
4301 );
4302
4303 match content {
4304 InlineContent::Image(image) => {
4305 if let Some(image_ref) = get_image_ref_for_image_source(&image.source) {
4306 builder.push_image(object_bounds, image_ref, BorderRadius::default());
4307 }
4308 }
4309 InlineContent::Shape(shape) => {
4310 self.paint_inline_shape(builder, object_bounds, shape, bounds)?;
4311 }
4312 _ => {}
4313 }
4314 Ok(())
4315 }
4316
4317 fn paint_inline_shape(
4320 &self,
4321 builder: &mut DisplayListBuilder,
4322 object_bounds: LogicalRect,
4323 shape: &InlineShape,
4324 bounds: &crate::text3::cache::Rect,
4325 ) -> Result<()> {
4326 let Some(node_id) = shape.source_node_id else {
4329 return Ok(());
4330 };
4331
4332 if let Some(indices) = self.positioned_tree.tree.dom_to_layout.get(&node_id) {
4337 if let Some(&idx) = indices.first() {
4338 if self.establishes_stacking_context(idx) {
4339 return Ok(());
4340 }
4341 }
4342 }
4343
4344 let styled_node_state =
4345 &self.ctx.styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
4346
4347 let background_contents =
4349 get_background_contents(self.ctx.styled_dom, node_id, styled_node_state);
4350
4351 let border_info = get_border_info(self.ctx.styled_dom, node_id, styled_node_state);
4353
4354 let margins = if let Some(indices) = self.positioned_tree.tree.dom_to_layout.get(&node_id) {
4357 if let Some(&idx) = indices.first() {
4358 self.positioned_tree.tree.nodes[idx].box_props.unpack().margin
4359 } else {
4360 Default::default()
4361 }
4362 } else {
4363 Default::default()
4364 };
4365
4366 let border_box_bounds = LogicalRect {
4368 origin: LogicalPosition {
4369 x: object_bounds.origin.x + margins.left,
4370 y: object_bounds.origin.y + margins.top,
4371 },
4372 size: LogicalSize {
4373 width: (object_bounds.size.width - margins.left - margins.right).max(0.0),
4374 height: (object_bounds.size.height - margins.top - margins.bottom).max(0.0),
4375 },
4376 };
4377
4378 let element_size = PhysicalSizeImport {
4379 width: border_box_bounds.size.width,
4380 height: border_box_bounds.size.height,
4381 };
4382
4383 let simple_border_radius = get_border_radius(
4385 self.ctx.styled_dom,
4386 node_id,
4387 styled_node_state,
4388 element_size,
4389 self.ctx.viewport_size,
4390 );
4391
4392 let style_border_radius =
4394 get_style_border_radius(self.ctx.styled_dom, node_id, styled_node_state);
4395
4396 builder.push_backgrounds_and_border(
4398 border_box_bounds,
4399 &background_contents,
4400 &border_info,
4401 simple_border_radius,
4402 style_border_radius,
4403 self.ctx.image_cache,
4404 );
4405
4406 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, Some(node_id)) {
4410 builder.push_hit_test_area(border_box_bounds, tag_id);
4411 }
4412
4413 Ok(())
4414 }
4415
4416 fn establishes_stacking_context(&self, node_index: usize) -> bool {
4423 let Some(node) = self.positioned_tree.tree.get(node_index) else {
4424 return false;
4425 };
4426 let Some(dom_id) = node.dom_node_id else {
4427 return false;
4428 };
4429
4430 let position = get_position_type(self.ctx.styled_dom, Some(dom_id));
4431 let z_auto = crate::solver3::getters::is_z_index_auto(self.ctx.styled_dom, Some(dom_id));
4432
4433 if position == LayoutPosition::Fixed || position == LayoutPosition::Sticky {
4435 return true;
4436 }
4437
4438 if position == LayoutPosition::Absolute {
4441 return !z_auto;
4442 }
4443
4444 if position == LayoutPosition::Relative && !z_auto {
4446 return true;
4447 }
4448
4449 if let Some(styled_node) = self.ctx.styled_dom.styled_nodes.as_container().get(dom_id) {
4450 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
4451 let node_state =
4452 &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
4453
4454 if crate::solver3::getters::get_opacity(
4456 self.ctx.styled_dom, dom_id, node_state,
4457 ) < 1.0 {
4458 return true;
4459 }
4460
4461 if let Some(t) = crate::solver3::getters::get_transform(
4463 self.ctx.styled_dom, dom_id, node_state,
4464 ) {
4465 if !t.is_empty() {
4466 return true;
4467 }
4468 }
4469 }
4470
4471 false
4472 }
4473}
4474
4475pub fn node_establishes_stacking_context(
4483 styled_dom: &StyledDom,
4484 dom_id: NodeId,
4485) -> bool {
4486 let position = crate::solver3::positioning::get_position_type(styled_dom, Some(dom_id));
4487 let z_auto = crate::solver3::getters::is_z_index_auto(styled_dom, Some(dom_id));
4488
4489 if position == LayoutPosition::Fixed || position == LayoutPosition::Sticky {
4491 return true;
4492 }
4493 if position == LayoutPosition::Absolute && !z_auto {
4495 return true;
4496 }
4497 if position == LayoutPosition::Relative && !z_auto {
4499 return true;
4500 }
4501
4502 let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
4503
4504 if crate::solver3::getters::get_opacity(styled_dom, dom_id, node_state) < 1.0 {
4506 return true;
4507 }
4508
4509 if let Some(t) = crate::solver3::getters::get_transform(styled_dom, dom_id, node_state) {
4511 if !t.is_empty() {
4512 return true;
4513 }
4514 }
4515
4516 false
4517}
4518
4519pub struct PositionedTree<'a> {
4525 pub tree: &'a LayoutTree,
4527 pub calculated_positions: &'a super::PositionVec,
4529}
4530
4531#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4537pub enum OverflowBehavior {
4538 Visible,
4540 Hidden,
4542 Clip,
4544 Scroll,
4546 Auto,
4548}
4549
4550impl OverflowBehavior {
4551 pub fn is_clipped(&self) -> bool {
4556 matches!(self, Self::Hidden | Self::Clip | Self::Scroll | Self::Auto)
4557 }
4558
4559 pub fn is_scroll(&self) -> bool {
4564 matches!(self, Self::Scroll | Self::Auto)
4565 }
4566}
4567
4568fn apply_overflow_clip_margin(
4573 clip_rect: &mut LogicalRect,
4574 overflow_x: &super::getters::MultiValue<LayoutOverflow>,
4575 overflow_y: &super::getters::MultiValue<LayoutOverflow>,
4576 styled_dom: &StyledDom,
4577 dom_id: NodeId,
4578 styled_node_state: &azul_core::styled_dom::StyledNodeState,
4579) {
4580 if !overflow_x.is_clip() && !overflow_y.is_clip() {
4581 return;
4582 }
4583 let clip_margin = get_overflow_clip_margin_property(styled_dom, dom_id, styled_node_state);
4584 let Some(margin_val) = clip_margin.exact() else {
4585 return;
4586 };
4587 let m = margin_val.inner.to_pixels_internal(0.0, 0.0, 0.0).max(0.0);
4588 if m <= 0.0 {
4589 return;
4590 }
4591 if overflow_x.is_clip() {
4592 clip_rect.origin.x -= m;
4593 clip_rect.size.width += m * 2.0;
4594 }
4595 if overflow_y.is_clip() {
4596 clip_rect.origin.y -= m;
4597 clip_rect.size.height += m * 2.0;
4598 }
4599}
4600
4601fn get_scroll_id(id: Option<NodeId>) -> LocalScrollId {
4602 id.map(|i| i.index() as u64).unwrap_or(0)
4603}
4604
4605fn get_scroll_content_size(node: &LayoutNodeHot, warm: Option<&LayoutNodeWarm>) -> LogicalSize {
4610 if let Some(overflow_size) = warm.and_then(|w| w.overflow_content_size) {
4612 return overflow_size;
4613 }
4614
4615 let mut content_size = node.used_size.unwrap_or_default();
4617
4618 if let Some(cached_layout) = warm.and_then(|w| w.inline_layout_result.as_ref()) {
4620 let text_layout = &cached_layout.layout;
4621 let mut max_x: f32 = 0.0;
4623 let mut max_y: f32 = 0.0;
4624
4625 for positioned_item in &text_layout.items {
4626 let item_bounds = positioned_item.item.bounds();
4627 let item_right = positioned_item.position.x + item_bounds.width;
4628 let item_bottom = positioned_item.position.y + item_bounds.height;
4629
4630 max_x = max_x.max(item_right);
4631 max_y = max_y.max(item_bottom);
4632 }
4633
4634 content_size.width = content_size.width.max(max_x);
4636 content_size.height = content_size.height.max(max_y);
4637 }
4638
4639 content_size
4640}
4641
4642fn get_tag_id(dom: &StyledDom, id: Option<NodeId>) -> Option<DisplayListTagId> {
4643 let node_id = id?;
4644 let tag_mapping = dom.tag_ids_to_node_ids.as_ref().iter().find(|m| {
4645 m.node_id.into_crate_internal() == Some(node_id)
4646 })?;
4647 Some((tag_mapping.tag_id.inner, TAG_TYPE_DOM_NODE))
4648}
4649
4650fn get_image_ref_for_image_source(
4651 source: &ImageSource,
4652) -> Option<ImageRef> {
4653 match source {
4654 ImageSource::Ref(image_ref) => Some(image_ref.clone()),
4655 ImageSource::Url(_url) => {
4656 None
4659 }
4660 ImageSource::Data(_) | ImageSource::Svg(_) | ImageSource::Placeholder(_) => {
4661 None
4663 }
4664 }
4665}
4666
4667fn get_display_item_bounds(item: &DisplayListItem) -> Option<WindowLogicalRect> {
4669 match item {
4670 DisplayListItem::Rect { bounds, .. } => Some(*bounds),
4671 DisplayListItem::SelectionRect { bounds, .. } => Some(*bounds),
4672 DisplayListItem::CursorRect { bounds, .. } => Some(*bounds),
4673 DisplayListItem::Border { bounds, .. } => Some(*bounds),
4674 DisplayListItem::TextLayout { bounds, .. } => Some(*bounds),
4675 DisplayListItem::Text { clip_rect, .. } => Some(*clip_rect),
4676 DisplayListItem::Underline { bounds, .. } => Some(*bounds),
4677 DisplayListItem::Strikethrough { bounds, .. } => Some(*bounds),
4678 DisplayListItem::Overline { bounds, .. } => Some(*bounds),
4679 DisplayListItem::Image { bounds, .. } => Some(*bounds),
4680 DisplayListItem::ScrollBar { bounds, .. } => Some(*bounds),
4681 DisplayListItem::ScrollBarStyled { info } => Some(info.bounds),
4682 DisplayListItem::PushClip { bounds, .. } => Some(*bounds),
4683 DisplayListItem::PushScrollFrame { clip_bounds, .. } => Some(*clip_bounds),
4684 DisplayListItem::HitTestArea { bounds, .. } => Some(*bounds),
4685 DisplayListItem::PushStackingContext { bounds, .. } => Some(*bounds),
4686 DisplayListItem::VirtualView { bounds, .. } => Some(*bounds),
4687 _ => None,
4688 }
4689}
4690
4691fn clip_and_offset_display_item(
4694 item: &DisplayListItem,
4695 page_top: f32,
4696 page_bottom: f32,
4697) -> Option<DisplayListItem> {
4698 match item {
4699 DisplayListItem::Rect {
4700 bounds,
4701 color,
4702 border_radius,
4703 } => clip_rect_item(bounds.into_inner(), *color, *border_radius, page_top, page_bottom),
4704
4705 DisplayListItem::Border {
4706 bounds,
4707 widths,
4708 colors,
4709 styles,
4710 border_radius,
4711 } => clip_border_item(
4712 bounds.into_inner(),
4713 *widths,
4714 *colors,
4715 *styles,
4716 border_radius.clone(),
4717 page_top,
4718 page_bottom,
4719 ),
4720
4721 DisplayListItem::SelectionRect {
4722 bounds,
4723 border_radius,
4724 color,
4725 } => clip_selection_rect_item(bounds.into_inner(), *border_radius, *color, page_top, page_bottom),
4726
4727 DisplayListItem::CursorRect { bounds, color } => {
4728 clip_cursor_rect_item(bounds.into_inner(), *color, page_top, page_bottom)
4729 }
4730
4731 DisplayListItem::Image { bounds, image, border_radius } => {
4732 clip_image_item(bounds.into_inner(), image.clone(), *border_radius, page_top, page_bottom)
4733 }
4734
4735 DisplayListItem::TextLayout {
4736 layout,
4737 bounds,
4738 font_hash,
4739 font_size_px,
4740 color,
4741 } => clip_text_layout_item(
4742 layout,
4743 bounds.into_inner(),
4744 *font_hash,
4745 *font_size_px,
4746 *color,
4747 page_top,
4748 page_bottom,
4749 ),
4750
4751 DisplayListItem::Text {
4752 glyphs,
4753 font_hash,
4754 font_size_px,
4755 color,
4756 clip_rect,
4757 ..
4758 } => clip_text_item(
4759 glyphs,
4760 *font_hash,
4761 *font_size_px,
4762 *color,
4763 clip_rect.into_inner(),
4764 page_top,
4765 page_bottom,
4766 ),
4767
4768 DisplayListItem::Underline {
4769 bounds,
4770 color,
4771 thickness,
4772 } => clip_text_decoration_item(
4773 bounds.into_inner(),
4774 *color,
4775 *thickness,
4776 TextDecorationType::Underline,
4777 page_top,
4778 page_bottom,
4779 ),
4780
4781 DisplayListItem::Strikethrough {
4782 bounds,
4783 color,
4784 thickness,
4785 } => clip_text_decoration_item(
4786 bounds.into_inner(),
4787 *color,
4788 *thickness,
4789 TextDecorationType::Strikethrough,
4790 page_top,
4791 page_bottom,
4792 ),
4793
4794 DisplayListItem::Overline {
4795 bounds,
4796 color,
4797 thickness,
4798 } => clip_text_decoration_item(
4799 bounds.into_inner(),
4800 *color,
4801 *thickness,
4802 TextDecorationType::Overline,
4803 page_top,
4804 page_bottom,
4805 ),
4806
4807 DisplayListItem::ScrollBar {
4808 bounds,
4809 color,
4810 orientation,
4811 opacity_key,
4812 hit_id,
4813 } => clip_scrollbar_item(
4814 bounds.into_inner(),
4815 *color,
4816 *orientation,
4817 *opacity_key,
4818 *hit_id,
4819 page_top,
4820 page_bottom,
4821 ),
4822
4823 DisplayListItem::HitTestArea { bounds, tag } => {
4824 clip_hit_test_area_item(bounds.into_inner(), *tag, page_top, page_bottom)
4825 }
4826
4827 DisplayListItem::VirtualView {
4828 child_dom_id,
4829 bounds,
4830 clip_rect,
4831 } => clip_virtual_view_item(*child_dom_id, bounds.into_inner(), clip_rect.into_inner(), page_top, page_bottom),
4832
4833 DisplayListItem::ScrollBarStyled { info } => {
4835 let bounds = info.bounds;
4836 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4837 None
4838 } else {
4839 let mut clipped_info = (**info).clone();
4841 let y_offset = -page_top;
4842 clipped_info.bounds = offset_rect_y(clipped_info.bounds.into_inner(), y_offset).into();
4843 clipped_info.track_bounds = offset_rect_y(clipped_info.track_bounds.into_inner(), y_offset).into();
4844 clipped_info.thumb_bounds = offset_rect_y(clipped_info.thumb_bounds.into_inner(), y_offset).into();
4845 if let Some(b) = clipped_info.button_decrement_bounds {
4846 clipped_info.button_decrement_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
4847 }
4848 if let Some(b) = clipped_info.button_increment_bounds {
4849 clipped_info.button_increment_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
4850 }
4851 Some(DisplayListItem::ScrollBarStyled {
4852 info: Box::new(clipped_info),
4853 })
4854 }
4855 }
4856
4857 DisplayListItem::PushClip { .. }
4859 | DisplayListItem::PopClip
4860 | DisplayListItem::PushScrollFrame { .. }
4861 | DisplayListItem::PopScrollFrame
4862 | DisplayListItem::PushStackingContext { .. }
4863 | DisplayListItem::PopStackingContext
4864 | DisplayListItem::VirtualViewPlaceholder { .. } => None,
4865
4866 DisplayListItem::LinearGradient {
4868 bounds,
4869 gradient,
4870 border_radius,
4871 } => {
4872 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4873 None
4874 } else {
4875 Some(DisplayListItem::LinearGradient {
4876 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4877 gradient: gradient.clone(),
4878 border_radius: *border_radius,
4879 })
4880 }
4881 }
4882 DisplayListItem::RadialGradient {
4883 bounds,
4884 gradient,
4885 border_radius,
4886 } => {
4887 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4888 None
4889 } else {
4890 Some(DisplayListItem::RadialGradient {
4891 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4892 gradient: gradient.clone(),
4893 border_radius: *border_radius,
4894 })
4895 }
4896 }
4897 DisplayListItem::ConicGradient {
4898 bounds,
4899 gradient,
4900 border_radius,
4901 } => {
4902 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4903 None
4904 } else {
4905 Some(DisplayListItem::ConicGradient {
4906 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4907 gradient: gradient.clone(),
4908 border_radius: *border_radius,
4909 })
4910 }
4911 }
4912
4913 DisplayListItem::BoxShadow {
4915 bounds,
4916 shadow,
4917 border_radius,
4918 } => {
4919 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4920 None
4921 } else {
4922 Some(DisplayListItem::BoxShadow {
4923 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4924 shadow: *shadow,
4925 border_radius: *border_radius,
4926 })
4927 }
4928 }
4929
4930 DisplayListItem::PushFilter { .. }
4932 | DisplayListItem::PopFilter
4933 | DisplayListItem::PushBackdropFilter { .. }
4934 | DisplayListItem::PopBackdropFilter
4935 | DisplayListItem::PushOpacity { .. }
4936 | DisplayListItem::PopOpacity
4937 | DisplayListItem::PushReferenceFrame { .. }
4938 | DisplayListItem::PopReferenceFrame
4939 | DisplayListItem::PushTextShadow { .. }
4940 | DisplayListItem::PopTextShadow
4941 | DisplayListItem::PushImageMaskClip { .. }
4942 | DisplayListItem::PopImageMaskClip => None,
4943 }
4944}
4945
4946#[derive(Debug, Clone, Copy)]
4950enum TextDecorationType {
4951 Underline,
4952 Strikethrough,
4953 Overline,
4954}
4955
4956fn clip_rect_item(
4958 bounds: LogicalRect,
4959 color: ColorU,
4960 border_radius: BorderRadius,
4961 page_top: f32,
4962 page_bottom: f32,
4963) -> Option<DisplayListItem> {
4964 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Rect {
4965 bounds: clipped.into(),
4966 color,
4967 border_radius,
4968 })
4969}
4970
4971fn clip_border_item(
4973 bounds: LogicalRect,
4974 widths: StyleBorderWidths,
4975 colors: StyleBorderColors,
4976 styles: StyleBorderStyles,
4977 border_radius: StyleBorderRadius,
4978 page_top: f32,
4979 page_bottom: f32,
4980) -> Option<DisplayListItem> {
4981 let original_bounds = bounds;
4982 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| {
4983 let new_widths = adjust_border_widths_for_clipping(
4984 widths,
4985 original_bounds,
4986 clipped,
4987 page_top,
4988 page_bottom,
4989 );
4990 DisplayListItem::Border {
4991 bounds: clipped.into(),
4992 widths: new_widths,
4993 colors,
4994 styles,
4995 border_radius,
4996 }
4997 })
4998}
4999
5000fn adjust_border_widths_for_clipping(
5003 mut widths: StyleBorderWidths,
5004 original_bounds: LogicalRect,
5005 clipped: LogicalRect,
5006 page_top: f32,
5007 page_bottom: f32,
5008) -> StyleBorderWidths {
5009 if clipped.origin.y > 0.0 && original_bounds.origin.y < page_top {
5011 widths.top = None;
5012 }
5013
5014 let original_bottom = original_bounds.origin.y + original_bounds.size.height;
5016 let clipped_bottom = clipped.origin.y + clipped.size.height;
5017 if original_bottom > page_bottom && clipped_bottom >= page_bottom - page_top - 1.0 {
5018 widths.bottom = None;
5019 }
5020
5021 widths
5022}
5023
5024fn clip_selection_rect_item(
5026 bounds: LogicalRect,
5027 border_radius: BorderRadius,
5028 color: ColorU,
5029 page_top: f32,
5030 page_bottom: f32,
5031) -> Option<DisplayListItem> {
5032 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::SelectionRect {
5033 bounds: clipped.into(),
5034 border_radius,
5035 color,
5036 })
5037}
5038
5039fn clip_cursor_rect_item(
5041 bounds: LogicalRect,
5042 color: ColorU,
5043 page_top: f32,
5044 page_bottom: f32,
5045) -> Option<DisplayListItem> {
5046 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::CursorRect {
5047 bounds: clipped.into(),
5048 color,
5049 })
5050}
5051
5052fn clip_image_item(
5054 bounds: LogicalRect,
5055 image: ImageRef,
5056 border_radius: BorderRadius,
5057 page_top: f32,
5058 page_bottom: f32,
5059) -> Option<DisplayListItem> {
5060 if !rect_intersects(&bounds, page_top, page_bottom) {
5061 return None;
5062 }
5063 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Image {
5064 bounds: clipped.into(),
5065 image,
5066 border_radius,
5067 })
5068}
5069
5070fn clip_text_layout_item(
5072 layout: &Arc<dyn std::any::Any + Send + Sync>,
5073 bounds: LogicalRect,
5074 font_hash: FontHash,
5075 font_size_px: f32,
5076 color: ColorU,
5077 page_top: f32,
5078 page_bottom: f32,
5079) -> Option<DisplayListItem> {
5080 if !rect_intersects(&bounds, page_top, page_bottom) {
5081 return None;
5082 }
5083
5084 #[cfg(feature = "text_layout")]
5086 if let Some(unified_layout) = layout.downcast_ref::<crate::text3::cache::UnifiedLayout>() {
5087 return clip_unified_layout(
5088 unified_layout,
5089 bounds,
5090 font_hash,
5091 font_size_px,
5092 color,
5093 page_top,
5094 page_bottom,
5095 );
5096 }
5097
5098 Some(DisplayListItem::TextLayout {
5100 layout: layout.clone(),
5101 bounds: offset_rect_y(bounds, -page_top).into(),
5102 font_hash,
5103 font_size_px,
5104 color,
5105 })
5106}
5107
5108#[cfg(feature = "text_layout")]
5110fn clip_unified_layout(
5111 unified_layout: &crate::text3::cache::UnifiedLayout,
5112 bounds: LogicalRect,
5113 font_hash: FontHash,
5114 font_size_px: f32,
5115 color: ColorU,
5116 page_top: f32,
5117 page_bottom: f32,
5118) -> Option<DisplayListItem> {
5119 let layout_origin_y = bounds.origin.y;
5120 let layout_origin_x = bounds.origin.x;
5121
5122 let filtered_items: Vec<_> = unified_layout
5124 .items
5125 .iter()
5126 .filter(|item| item_center_on_page(item, layout_origin_y, page_top, page_bottom))
5127 .cloned()
5128 .collect();
5129
5130 if filtered_items.is_empty() {
5131 return None;
5132 }
5133
5134 let new_origin_y = (layout_origin_y - page_top).max(0.0);
5136
5137 let (offset_items, min_y, max_y, max_width) =
5139 transform_items_to_page_coords(filtered_items, layout_origin_y, page_top, new_origin_y);
5140
5141 let new_layout = crate::text3::cache::UnifiedLayout {
5142 items: offset_items,
5143 overflow: unified_layout.overflow.clone(),
5144 };
5145
5146 let new_bounds = LogicalRect {
5147 origin: LogicalPosition {
5148 x: layout_origin_x,
5149 y: new_origin_y,
5150 },
5151 size: LogicalSize {
5152 width: max_width.max(bounds.size.width),
5153 height: (max_y - min_y.min(0.0)).max(0.0),
5154 },
5155 };
5156
5157 Some(DisplayListItem::TextLayout {
5158 layout: Arc::new(new_layout) as Arc<dyn std::any::Any + Send + Sync>,
5159 bounds: new_bounds.into(),
5160 font_hash,
5161 font_size_px,
5162 color,
5163 })
5164}
5165
5166#[cfg(feature = "text_layout")]
5168fn item_center_on_page(
5169 item: &crate::text3::cache::PositionedItem,
5170 layout_origin_y: f32,
5171 page_top: f32,
5172 page_bottom: f32,
5173) -> bool {
5174 let item_y_absolute = layout_origin_y + item.position.y;
5175 let item_height = item.item.bounds().height;
5176 let item_center_y = item_y_absolute + (item_height / 2.0);
5177 item_center_y >= page_top && item_center_y < page_bottom
5178}
5179
5180#[cfg(feature = "text_layout")]
5183fn transform_items_to_page_coords(
5184 items: Vec<crate::text3::cache::PositionedItem>,
5185 layout_origin_y: f32,
5186 page_top: f32,
5187 new_origin_y: f32,
5188) -> (Vec<crate::text3::cache::PositionedItem>, f32, f32, f32) {
5189 let mut min_y = f32::MAX;
5190 let mut max_y = f32::MIN;
5191 let mut max_width = 0.0f32;
5192
5193 let offset_items: Vec<_> = items
5194 .into_iter()
5195 .map(|mut item| {
5196 let abs_y = layout_origin_y + item.position.y;
5197 let page_y = abs_y - page_top;
5198 let new_item_y = page_y - new_origin_y;
5199
5200 let item_bounds = item.item.bounds();
5201 min_y = min_y.min(new_item_y);
5202 max_y = max_y.max(new_item_y + item_bounds.height);
5203 max_width = max_width.max(item.position.x + item_bounds.width);
5204
5205 item.position.y = new_item_y;
5206 item
5207 })
5208 .collect();
5209
5210 (offset_items, min_y, max_y, max_width)
5211}
5212
5213fn clip_text_item(
5215 glyphs: &[GlyphInstance],
5216 font_hash: FontHash,
5217 font_size_px: f32,
5218 color: ColorU,
5219 clip_rect: LogicalRect,
5220 page_top: f32,
5221 page_bottom: f32,
5222) -> Option<DisplayListItem> {
5223 if !rect_intersects(&clip_rect, page_top, page_bottom) {
5224 return None;
5225 }
5226
5227 let page_glyphs: Vec<_> = glyphs
5229 .iter()
5230 .filter(|g| g.point.y >= page_top && g.point.y < page_bottom)
5231 .map(|g| GlyphInstance {
5232 index: g.index,
5233 point: LogicalPosition {
5234 x: g.point.x,
5235 y: g.point.y - page_top,
5236 },
5237 size: g.size,
5238 })
5239 .collect();
5240
5241 if page_glyphs.is_empty() {
5242 return None;
5243 }
5244
5245 Some(DisplayListItem::Text {
5246 glyphs: page_glyphs,
5247 font_hash,
5248 font_size_px,
5249 color,
5250 clip_rect: offset_rect_y(clip_rect, -page_top).into(),
5251 source_node_index: None,
5252 })
5253}
5254
5255fn clip_text_decoration_item(
5257 bounds: LogicalRect,
5258 color: ColorU,
5259 thickness: f32,
5260 decoration_type: TextDecorationType,
5261 page_top: f32,
5262 page_bottom: f32,
5263) -> Option<DisplayListItem> {
5264 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| match decoration_type {
5265 TextDecorationType::Underline => DisplayListItem::Underline {
5266 bounds: clipped.into(),
5267 color,
5268 thickness,
5269 },
5270 TextDecorationType::Strikethrough => DisplayListItem::Strikethrough {
5271 bounds: clipped.into(),
5272 color,
5273 thickness,
5274 },
5275 TextDecorationType::Overline => DisplayListItem::Overline {
5276 bounds: clipped.into(),
5277 color,
5278 thickness,
5279 },
5280 })
5281}
5282
5283fn clip_scrollbar_item(
5285 bounds: LogicalRect,
5286 color: ColorU,
5287 orientation: ScrollbarOrientation,
5288 opacity_key: Option<OpacityKey>,
5289 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
5290 page_top: f32,
5291 page_bottom: f32,
5292) -> Option<DisplayListItem> {
5293 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::ScrollBar {
5294 bounds: clipped.into(),
5295 color,
5296 orientation,
5297 opacity_key,
5298 hit_id,
5299 })
5300}
5301
5302fn clip_hit_test_area_item(
5304 bounds: LogicalRect,
5305 tag: DisplayListTagId,
5306 page_top: f32,
5307 page_bottom: f32,
5308) -> Option<DisplayListItem> {
5309 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::HitTestArea {
5310 bounds: clipped.into(),
5311 tag,
5312 })
5313}
5314
5315fn clip_virtual_view_item(
5317 child_dom_id: DomId,
5318 bounds: LogicalRect,
5319 clip_rect: LogicalRect,
5320 page_top: f32,
5321 page_bottom: f32,
5322) -> Option<DisplayListItem> {
5323 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::VirtualView {
5324 child_dom_id,
5325 bounds: clipped.into(),
5326 clip_rect: offset_rect_y(clip_rect, -page_top).into(),
5327 })
5328}
5329
5330fn clip_rect_bounds(bounds: LogicalRect, page_top: f32, page_bottom: f32) -> Option<LogicalRect> {
5333 let item_top = bounds.origin.y;
5334 let item_bottom = bounds.origin.y + bounds.size.height;
5335
5336 if item_bottom <= page_top || item_top >= page_bottom {
5338 return None;
5339 }
5340
5341 let clipped_top = item_top.max(page_top);
5343 let clipped_bottom = item_bottom.min(page_bottom);
5344 let clipped_height = clipped_bottom - clipped_top;
5345
5346 let page_relative_y = clipped_top - page_top;
5348
5349 Some(LogicalRect {
5350 origin: LogicalPosition {
5351 x: bounds.origin.x,
5352 y: page_relative_y,
5353 },
5354 size: LogicalSize {
5355 width: bounds.size.width,
5356 height: clipped_height,
5357 },
5358 })
5359}
5360
5361fn rect_intersects(bounds: &LogicalRect, page_top: f32, page_bottom: f32) -> bool {
5363 let item_top = bounds.origin.y;
5364 let item_bottom = bounds.origin.y + bounds.size.height;
5365 item_bottom > page_top && item_top < page_bottom
5366}
5367
5368fn offset_rect_y(bounds: LogicalRect, offset_y: f32) -> LogicalRect {
5370 LogicalRect {
5371 origin: LogicalPosition {
5372 x: bounds.origin.x,
5373 y: bounds.origin.y + offset_y,
5374 },
5375 size: bounds.size,
5376 }
5377}
5378
5379use azul_css::props::layout::fragmentation::{BreakInside, PageBreak};
5388
5389use crate::solver3::pagination::{
5390 HeaderFooterConfig, MarginBoxContent, PageInfo, TableHeaderInfo, TableHeaderTracker,
5391};
5392
5393#[derive(Debug, Clone, Default)]
5395pub struct SlicerConfig {
5396 pub page_content_height: f32,
5398 pub page_gap: f32,
5401 pub allow_clipping: bool,
5403 pub header_footer: HeaderFooterConfig,
5405 pub page_width: f32,
5407 pub table_headers: TableHeaderTracker,
5409}
5410
5411impl SlicerConfig {
5412 pub fn simple(page_height: f32) -> Self {
5414 Self {
5415 page_content_height: page_height,
5416 page_gap: 0.0,
5417 allow_clipping: true,
5418 header_footer: HeaderFooterConfig::default(),
5419 page_width: 595.0, table_headers: TableHeaderTracker::default(),
5421 }
5422 }
5423
5424 pub fn with_gap(page_height: f32, gap: f32) -> Self {
5426 Self {
5427 page_content_height: page_height,
5428 page_gap: gap,
5429 allow_clipping: true,
5430 header_footer: HeaderFooterConfig::default(),
5431 page_width: 595.0,
5432 table_headers: TableHeaderTracker::default(),
5433 }
5434 }
5435
5436 pub fn with_header_footer(mut self, config: HeaderFooterConfig) -> Self {
5438 self.header_footer = config;
5439 self
5440 }
5441
5442 pub fn with_page_width(mut self, width: f32) -> Self {
5444 self.page_width = width;
5445 self
5446 }
5447
5448 pub fn with_table_headers(mut self, tracker: TableHeaderTracker) -> Self {
5450 self.table_headers = tracker;
5451 self
5452 }
5453
5454 pub fn register_table_header(&mut self, info: TableHeaderInfo) {
5456 self.table_headers.register_table_header(info);
5457 }
5458
5459 pub fn page_slot_height(&self) -> f32 {
5461 self.page_content_height + self.page_gap
5462 }
5463
5464 pub fn page_for_y(&self, y: f32) -> usize {
5466 if self.page_slot_height() <= 0.0 {
5467 return 0;
5468 }
5469 (y / self.page_slot_height()).floor() as usize
5470 }
5471
5472 pub fn page_bounds(&self, page_index: usize) -> (f32, f32) {
5474 let start = page_index as f32 * self.page_slot_height();
5475 let end = start + self.page_content_height;
5476 (start, end)
5477 }
5478}
5479
5480pub fn paginate_display_list_with_slicer_and_breaks(
5488 full_display_list: DisplayList,
5489 config: &SlicerConfig,
5490) -> Result<Vec<DisplayList>> {
5491 if config.page_content_height <= 0.0 || config.page_content_height >= f32::MAX {
5492 return Ok(vec![full_display_list]);
5493 }
5494
5495 let base_header_space = if config.header_footer.show_header {
5497 config.header_footer.header_height
5498 } else {
5499 0.0
5500 };
5501 let base_footer_space = if config.header_footer.show_footer {
5502 config.header_footer.footer_height
5503 } else {
5504 0.0
5505 };
5506
5507 let normal_page_content_height =
5509 config.page_content_height - base_header_space - base_footer_space;
5510 let first_page_content_height = if config.header_footer.skip_first_page {
5511 config.page_content_height
5513 } else {
5514 normal_page_content_height
5515 };
5516
5517 let page_breaks = calculate_page_break_positions(
5527 &full_display_list,
5528 first_page_content_height,
5529 normal_page_content_height,
5530 );
5531
5532 let num_pages = page_breaks.len();
5533
5534 let mut pages: Vec<DisplayList> = Vec::with_capacity(num_pages);
5536
5537 for (page_idx, &(content_start_y, content_end_y)) in page_breaks.iter().enumerate() {
5538 let page_info = PageInfo::new(page_idx + 1, num_pages);
5540
5541 let skip_this_page = config.header_footer.skip_first_page && page_info.is_first;
5543 let header_space = if config.header_footer.show_header && !skip_this_page {
5544 config.header_footer.header_height
5545 } else {
5546 0.0
5547 };
5548 let footer_space = if config.header_footer.show_footer && !skip_this_page {
5549 config.header_footer.footer_height
5550 } else {
5551 0.0
5552 };
5553
5554 let _ = footer_space; let mut page_items = Vec::new();
5557 let mut page_node_mapping = Vec::new();
5558
5559 if config.header_footer.show_header && !skip_this_page {
5561 let header_text = config.header_footer.header_text(page_info);
5562 if !header_text.is_empty() {
5563 let header_items = generate_text_display_items(
5564 &header_text,
5565 LogicalRect {
5566 origin: LogicalPosition { x: 0.0, y: 0.0 },
5567 size: LogicalSize {
5568 width: config.page_width,
5569 height: config.header_footer.header_height,
5570 },
5571 },
5572 config.header_footer.font_size,
5573 config.header_footer.text_color,
5574 TextAlignment::Center,
5575 );
5576 for item in header_items {
5577 page_items.push(item);
5578 page_node_mapping.push(None);
5579 }
5580 }
5581 }
5582
5583 let repeated_headers = config.table_headers.get_repeated_headers_for_page(
5585 page_idx,
5586 content_start_y,
5587 content_end_y,
5588 );
5589
5590 let mut thead_total_height = 0.0f32;
5591 for (y_offset_from_page_top, thead_items, thead_height) in repeated_headers {
5592 let thead_y = header_space + y_offset_from_page_top;
5593 for item in thead_items {
5594 let translated_item = offset_display_item_y(item, thead_y);
5595 page_items.push(translated_item);
5596 page_node_mapping.push(None);
5597 }
5598 thead_total_height = thead_total_height.max(thead_height);
5599 }
5600
5601 let content_y_offset = header_space + thead_total_height;
5603
5604 for (item_idx, item) in full_display_list.items.iter().enumerate() {
5606 let is_fixed = full_display_list.fixed_position_item_ranges.iter()
5608 .any(|&(start, end)| item_idx >= start && item_idx < end);
5609 if is_fixed {
5610 continue;
5611 }
5612 if let Some(clipped_item) =
5613 clip_and_offset_display_item(item, content_start_y, content_end_y)
5614 {
5615 let final_item = if content_y_offset > 0.0 {
5616 offset_display_item_y(&clipped_item, content_y_offset)
5617 } else {
5618 clipped_item
5619 };
5620 page_items.push(final_item);
5621 let node_mapping = full_display_list
5622 .node_mapping
5623 .get(item_idx)
5624 .copied()
5625 .flatten();
5626 page_node_mapping.push(node_mapping);
5627 }
5628 }
5629
5630 for &(start, end) in &full_display_list.fixed_position_item_ranges {
5634 for item_idx in start..end {
5635 if let Some(item) = full_display_list.items.get(item_idx) {
5636 let final_item = if content_y_offset > 0.0 {
5637 offset_display_item_y(item, content_y_offset)
5638 } else {
5639 item.clone()
5640 };
5641 page_items.push(final_item);
5642 let node_mapping = full_display_list
5643 .node_mapping
5644 .get(item_idx)
5645 .copied()
5646 .flatten();
5647 page_node_mapping.push(node_mapping);
5648 }
5649 }
5650 }
5651
5652 if config.header_footer.show_footer && !skip_this_page {
5654 let footer_text = config.header_footer.footer_text(page_info);
5655 if !footer_text.is_empty() {
5656 let footer_y = config.page_content_height - config.header_footer.footer_height;
5657 let footer_items = generate_text_display_items(
5658 &footer_text,
5659 LogicalRect {
5660 origin: LogicalPosition {
5661 x: 0.0,
5662 y: footer_y,
5663 },
5664 size: LogicalSize {
5665 width: config.page_width,
5666 height: config.header_footer.footer_height,
5667 },
5668 },
5669 config.header_footer.font_size,
5670 config.header_footer.text_color,
5671 TextAlignment::Center,
5672 );
5673 for item in footer_items {
5674 page_items.push(item);
5675 page_node_mapping.push(None);
5676 }
5677 }
5678 }
5679
5680 pages.push(DisplayList {
5681 items: page_items,
5682 node_mapping: page_node_mapping,
5683 forced_page_breaks: Vec::new(),
5684 fixed_position_item_ranges: Vec::new(), });
5686 }
5687
5688 if pages.is_empty() {
5690 pages.push(DisplayList::default());
5691 }
5692
5693 Ok(pages)
5694}
5695
5696fn calculate_page_break_positions(
5704 display_list: &DisplayList,
5705 first_page_height: f32,
5706 normal_page_height: f32,
5707) -> Vec<(f32, f32)> {
5708 let total_height = calculate_display_list_height(display_list);
5709
5710 if total_height <= 0.0 || first_page_height <= 0.0 {
5711 return vec![(0.0, total_height.max(first_page_height))];
5712 }
5713
5714 let mut break_points: Vec<f32> = Vec::new();
5716
5717 for &forced_break_y in &display_list.forced_page_breaks {
5719 if forced_break_y > 0.0 && forced_break_y < total_height {
5720 break_points.push(forced_break_y);
5721 }
5722 }
5723
5724 let mut y = first_page_height;
5726 while y < total_height {
5727 break_points.push(y);
5728 y += normal_page_height;
5729 }
5730
5731 break_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
5733 break_points.dedup_by(|a, b| (*a - *b).abs() < 1.0); let mut page_breaks: Vec<(f32, f32)> = Vec::new();
5737 let mut page_start = 0.0f32;
5738
5739 for break_y in break_points {
5740 if break_y > page_start {
5741 page_breaks.push((page_start, break_y));
5742 page_start = break_y;
5743 }
5744 }
5745
5746 if page_start < total_height {
5748 page_breaks.push((page_start, total_height));
5749 }
5750
5751 if page_breaks.is_empty() {
5753 page_breaks.push((0.0, total_height.max(first_page_height)));
5754 }
5755
5756 page_breaks
5757}
5758
5759#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5761pub enum TextAlignment {
5762 Left,
5763 Center,
5764 Right,
5765}
5766
5767fn offset_display_item_y(item: &DisplayListItem, y_offset: f32) -> DisplayListItem {
5769 if y_offset == 0.0 {
5770 return item.clone();
5771 }
5772
5773 match item {
5774 DisplayListItem::Rect {
5775 bounds,
5776 color,
5777 border_radius,
5778 } => DisplayListItem::Rect {
5779 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5780 color: *color,
5781 border_radius: *border_radius,
5782 },
5783 DisplayListItem::Border {
5784 bounds,
5785 widths,
5786 colors,
5787 styles,
5788 border_radius,
5789 } => DisplayListItem::Border {
5790 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5791 widths: widths.clone(),
5792 colors: *colors,
5793 styles: *styles,
5794 border_radius: border_radius.clone(),
5795 },
5796 DisplayListItem::Text {
5797 glyphs,
5798 font_hash,
5799 font_size_px,
5800 color,
5801 clip_rect,
5802 ..
5803 } => {
5804 let offset_glyphs: Vec<GlyphInstance> = glyphs
5805 .iter()
5806 .map(|g| GlyphInstance {
5807 index: g.index,
5808 point: LogicalPosition {
5809 x: g.point.x,
5810 y: g.point.y + y_offset,
5811 },
5812 size: g.size,
5813 })
5814 .collect();
5815 DisplayListItem::Text {
5816 glyphs: offset_glyphs,
5817 font_hash: *font_hash,
5818 font_size_px: *font_size_px,
5819 color: *color,
5820 clip_rect: offset_rect_y(clip_rect.into_inner(), y_offset).into(),
5821 source_node_index: None,
5822 }
5823 }
5824 DisplayListItem::TextLayout {
5825 layout,
5826 bounds,
5827 font_hash,
5828 font_size_px,
5829 color,
5830 } => DisplayListItem::TextLayout {
5831 layout: layout.clone(),
5832 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5833 font_hash: *font_hash,
5834 font_size_px: *font_size_px,
5835 color: *color,
5836 },
5837 DisplayListItem::Image { bounds, image, border_radius } => DisplayListItem::Image {
5838 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5839 image: image.clone(),
5840 border_radius: *border_radius,
5841 },
5842 DisplayListItem::SelectionRect {
5844 bounds,
5845 border_radius,
5846 color,
5847 } => DisplayListItem::SelectionRect {
5848 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5849 border_radius: *border_radius,
5850 color: *color,
5851 },
5852 DisplayListItem::CursorRect { bounds, color } => DisplayListItem::CursorRect {
5853 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5854 color: *color,
5855 },
5856 DisplayListItem::Underline {
5857 bounds,
5858 color,
5859 thickness,
5860 } => DisplayListItem::Underline {
5861 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5862 color: *color,
5863 thickness: *thickness,
5864 },
5865 DisplayListItem::Strikethrough {
5866 bounds,
5867 color,
5868 thickness,
5869 } => DisplayListItem::Strikethrough {
5870 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5871 color: *color,
5872 thickness: *thickness,
5873 },
5874 DisplayListItem::Overline {
5875 bounds,
5876 color,
5877 thickness,
5878 } => DisplayListItem::Overline {
5879 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5880 color: *color,
5881 thickness: *thickness,
5882 },
5883 DisplayListItem::ScrollBar {
5884 bounds,
5885 color,
5886 orientation,
5887 opacity_key,
5888 hit_id,
5889 } => DisplayListItem::ScrollBar {
5890 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5891 color: *color,
5892 orientation: *orientation,
5893 opacity_key: *opacity_key,
5894 hit_id: *hit_id,
5895 },
5896 DisplayListItem::HitTestArea { bounds, tag } => DisplayListItem::HitTestArea {
5897 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5898 tag: *tag,
5899 },
5900 DisplayListItem::PushClip {
5901 bounds,
5902 border_radius,
5903 } => DisplayListItem::PushClip {
5904 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5905 border_radius: *border_radius,
5906 },
5907 DisplayListItem::PushScrollFrame {
5908 clip_bounds,
5909 content_size,
5910 scroll_id,
5911 } => DisplayListItem::PushScrollFrame {
5912 clip_bounds: offset_rect_y(clip_bounds.into_inner(), y_offset).into(),
5913 content_size: *content_size,
5914 scroll_id: *scroll_id,
5915 },
5916 DisplayListItem::PushStackingContext { bounds, z_index } => {
5917 DisplayListItem::PushStackingContext {
5918 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5919 z_index: *z_index,
5920 }
5921 }
5922 DisplayListItem::VirtualView {
5923 child_dom_id,
5924 bounds,
5925 clip_rect,
5926 } => DisplayListItem::VirtualView {
5927 child_dom_id: *child_dom_id,
5928 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5929 clip_rect: offset_rect_y(clip_rect.into_inner(), y_offset).into(),
5930 },
5931 DisplayListItem::VirtualViewPlaceholder {
5932 node_id,
5933 bounds,
5934 clip_rect,
5935 } => DisplayListItem::VirtualViewPlaceholder {
5936 node_id: *node_id,
5937 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5938 clip_rect: offset_rect_y(clip_rect.into_inner(), y_offset).into(),
5939 },
5940 DisplayListItem::PopClip => DisplayListItem::PopClip,
5942 DisplayListItem::PopScrollFrame => DisplayListItem::PopScrollFrame,
5943 DisplayListItem::PopStackingContext => DisplayListItem::PopStackingContext,
5944
5945 DisplayListItem::LinearGradient {
5947 bounds,
5948 gradient,
5949 border_radius,
5950 } => DisplayListItem::LinearGradient {
5951 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5952 gradient: gradient.clone(),
5953 border_radius: *border_radius,
5954 },
5955 DisplayListItem::RadialGradient {
5956 bounds,
5957 gradient,
5958 border_radius,
5959 } => DisplayListItem::RadialGradient {
5960 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5961 gradient: gradient.clone(),
5962 border_radius: *border_radius,
5963 },
5964 DisplayListItem::ConicGradient {
5965 bounds,
5966 gradient,
5967 border_radius,
5968 } => DisplayListItem::ConicGradient {
5969 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5970 gradient: gradient.clone(),
5971 border_radius: *border_radius,
5972 },
5973
5974 DisplayListItem::BoxShadow {
5976 bounds,
5977 shadow,
5978 border_radius,
5979 } => DisplayListItem::BoxShadow {
5980 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5981 shadow: *shadow,
5982 border_radius: *border_radius,
5983 },
5984
5985 DisplayListItem::PushFilter { bounds, filters } => DisplayListItem::PushFilter {
5987 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5988 filters: filters.clone(),
5989 },
5990 DisplayListItem::PopFilter => DisplayListItem::PopFilter,
5991 DisplayListItem::PushBackdropFilter { bounds, filters } => {
5992 DisplayListItem::PushBackdropFilter {
5993 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5994 filters: filters.clone(),
5995 }
5996 }
5997 DisplayListItem::PopBackdropFilter => DisplayListItem::PopBackdropFilter,
5998 DisplayListItem::PushOpacity { bounds, opacity } => DisplayListItem::PushOpacity {
5999 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
6000 opacity: *opacity,
6001 },
6002 DisplayListItem::PopOpacity => DisplayListItem::PopOpacity,
6003 DisplayListItem::ScrollBarStyled { info } => {
6004 let mut offset_info = (**info).clone();
6005 offset_info.bounds = offset_rect_y(offset_info.bounds.into_inner(), y_offset).into();
6006 offset_info.track_bounds = offset_rect_y(offset_info.track_bounds.into_inner(), y_offset).into();
6007 offset_info.thumb_bounds = offset_rect_y(offset_info.thumb_bounds.into_inner(), y_offset).into();
6008 if let Some(b) = offset_info.button_decrement_bounds {
6009 offset_info.button_decrement_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
6010 }
6011 if let Some(b) = offset_info.button_increment_bounds {
6012 offset_info.button_increment_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
6013 }
6014 DisplayListItem::ScrollBarStyled {
6015 info: Box::new(offset_info),
6016 }
6017 }
6018
6019 DisplayListItem::PushReferenceFrame {
6021 transform_key,
6022 initial_transform,
6023 bounds,
6024 } => DisplayListItem::PushReferenceFrame {
6025 transform_key: *transform_key,
6026 initial_transform: *initial_transform,
6027 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
6028 },
6029 DisplayListItem::PopReferenceFrame => DisplayListItem::PopReferenceFrame,
6030 DisplayListItem::PushTextShadow { shadow } => DisplayListItem::PushTextShadow {
6031 shadow: shadow.clone(),
6032 },
6033 DisplayListItem::PopTextShadow => DisplayListItem::PopTextShadow,
6034 DisplayListItem::PushImageMaskClip {
6035 bounds,
6036 mask_image,
6037 mask_rect,
6038 } => DisplayListItem::PushImageMaskClip {
6039 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
6040 mask_image: mask_image.clone(),
6041 mask_rect: offset_rect_y(mask_rect.into_inner(), y_offset).into(),
6042 },
6043 DisplayListItem::PopImageMaskClip => DisplayListItem::PopImageMaskClip,
6044 }
6045}
6046
6047fn generate_text_display_items(
6052 text: &str,
6053 bounds: LogicalRect,
6054 font_size: f32,
6055 color: ColorU,
6056 alignment: TextAlignment,
6057) -> Vec<DisplayListItem> {
6058 use crate::font_traits::FontHash;
6059
6060 if text.is_empty() {
6061 return Vec::new();
6062 }
6063
6064 let char_width = font_size * 0.5;
6067 let text_width = text.len() as f32 * char_width;
6068
6069 let x_offset = match alignment {
6070 TextAlignment::Left => bounds.origin.x,
6071 TextAlignment::Center => bounds.origin.x + (bounds.size.width - text_width) / 2.0,
6072 TextAlignment::Right => bounds.origin.x + bounds.size.width - text_width,
6073 };
6074
6075 let y_pos = bounds.origin.y + (bounds.size.height + font_size) / 2.0 - font_size * 0.2;
6077
6078 let glyphs: Vec<GlyphInstance> = text
6081 .chars()
6082 .enumerate()
6083 .filter(|(_, c)| !c.is_control())
6084 .map(|(i, c)| GlyphInstance {
6085 index: c as u32, point: LogicalPosition {
6087 x: x_offset + i as f32 * char_width,
6088 y: y_pos,
6089 },
6090 size: LogicalSize::new(char_width, font_size),
6091 })
6092 .collect();
6093
6094 if glyphs.is_empty() {
6095 return Vec::new();
6096 }
6097
6098 vec![DisplayListItem::Text {
6099 glyphs,
6100 font_hash: FontHash::from_hash(0), font_size_px: font_size,
6102 color,
6103 clip_rect: bounds.into(),
6104 source_node_index: None,
6105 }]
6106}
6107
6108fn calculate_display_list_height(display_list: &DisplayList) -> f32 {
6110 let mut max_bottom = 0.0f32;
6111
6112 for item in &display_list.items {
6113 if let Some(bounds) = get_display_item_bounds(item) {
6114 if bounds.0.size.height < 0.1 {
6116 continue;
6117 }
6118
6119 let item_bottom = bounds.0.origin.y + bounds.0.size.height;
6120 if item_bottom > max_bottom {
6121 max_bottom = item_bottom;
6122 }
6123 }
6124 }
6125
6126 max_bottom
6127}
6128
6129#[derive(Debug, Clone, Copy, Default)]
6131pub struct BreakProperties {
6132 pub break_before: PageBreak,
6133 pub break_after: PageBreak,
6134 pub break_inside: BreakInside,
6135}
6136
6137pub fn apply_text_overflow_ellipsis(
6173 display_list: &mut DisplayList,
6174 container_bounds: LogicalRect,
6175 _ellipsis: &str,
6176) {
6177 let container_right = container_bounds.origin.x + container_bounds.size.width;
6178
6179 for item in display_list.items.iter_mut() {
6182 match item {
6183 DisplayListItem::Text {
6184 glyphs,
6185 font_size_px,
6186 clip_rect,
6187 ..
6188 } => {
6189 if glyphs.is_empty() {
6190 continue;
6191 }
6192
6193 let last_glyph = &glyphs[glyphs.len() - 1];
6195 let last_glyph_right = last_glyph.point.x + last_glyph.size.width;
6196
6197 if last_glyph_right <= container_right {
6198 continue; }
6200
6201 let ellipsis_width = *font_size_px * 0.6;
6203 let truncation_edge = container_right - ellipsis_width;
6204
6205 let mut keep_count = 0;
6207 for (i, glyph) in glyphs.iter().enumerate() {
6208 let glyph_right = glyph.point.x + glyph.size.width;
6209 if glyph_right > truncation_edge {
6210 break;
6211 }
6212 keep_count = i + 1;
6213 }
6214
6215 glyphs.truncate(keep_count);
6217
6218 let ellipsis_x = if let Some(last) = glyphs.last() {
6223 last.point.x + last.size.width
6224 } else {
6225 container_bounds.origin.x
6226 };
6227
6228 let ellipsis_glyph = GlyphInstance {
6229 index: 0x2026, point: LogicalPosition::new(ellipsis_x, glyphs.first().map_or(
6231 container_bounds.origin.y,
6232 |g| g.point.y,
6233 )),
6234 size: LogicalSize::new(ellipsis_width, *font_size_px),
6235 };
6236
6237 glyphs.push(ellipsis_glyph);
6238
6239 *clip_rect = container_bounds.into();
6242 }
6243 _ => {} }
6245 }
6246}
6247
6248pub fn resolve_clip_path(
6277 clip_path: &azul_css::props::layout::shape::ClipPath,
6278 node_bounds: LogicalRect,
6279) -> Option<(LogicalRect, f32)> {
6280 use azul_css::props::layout::shape::ClipPath;
6281 use azul_css::shape::CssShape;
6282
6283 match clip_path {
6284 ClipPath::None => None,
6285 ClipPath::Shape(shape) => {
6286 match shape {
6287 CssShape::Inset(inset) => {
6288 let x = node_bounds.origin.x + inset.inset_left;
6291 let y = node_bounds.origin.y + inset.inset_top;
6292 let w = (node_bounds.size.width - inset.inset_left - inset.inset_right).max(0.0);
6293 let h = (node_bounds.size.height - inset.inset_top - inset.inset_bottom).max(0.0);
6294 let radius = match inset.border_radius {
6295 azul_css::corety::OptionF32::Some(r) => r,
6296 azul_css::corety::OptionF32::None => 0.0,
6297 };
6298 Some((LogicalRect {
6299 origin: LogicalPosition::new(x, y),
6300 size: LogicalSize::new(w, h),
6301 }, radius))
6302 }
6303 CssShape::Circle(circle) => {
6304 let cx = node_bounds.origin.x + circle.center.x;
6308 let cy = node_bounds.origin.y + circle.center.y;
6309 let r = circle.radius;
6310 Some((LogicalRect {
6311 origin: LogicalPosition::new(cx - r, cy - r),
6312 size: LogicalSize::new(r * 2.0, r * 2.0),
6313 }, r))
6314 }
6315 CssShape::Ellipse(ellipse) => {
6316 let cx = node_bounds.origin.x + ellipse.center.x;
6318 let cy = node_bounds.origin.y + ellipse.center.y;
6319 let rx = ellipse.radius_x;
6320 let ry = ellipse.radius_y;
6321 let radius = rx.min(ry);
6322 Some((LogicalRect {
6323 origin: LogicalPosition::new(cx - rx, cy - ry),
6324 size: LogicalSize::new(rx * 2.0, ry * 2.0),
6325 }, radius))
6326 }
6327 CssShape::Polygon(polygon) => {
6328 if polygon.points.is_empty() {
6330 return None;
6331 }
6332 let mut min_x = f32::INFINITY;
6333 let mut min_y = f32::INFINITY;
6334 let mut max_x = f32::NEG_INFINITY;
6335 let mut max_y = f32::NEG_INFINITY;
6336 for point in polygon.points.iter() {
6337 let px = node_bounds.origin.x + point.x;
6339 let py = node_bounds.origin.y + point.y;
6340 min_x = min_x.min(px);
6341 min_y = min_y.min(py);
6342 max_x = max_x.max(px);
6343 max_y = max_y.max(py);
6344 }
6345 Some((LogicalRect {
6346 origin: LogicalPosition::new(min_x, min_y),
6347 size: LogicalSize::new((max_x - min_x).max(0.0), (max_y - min_y).max(0.0)),
6348 }, 0.0))
6349 }
6350 CssShape::Path(_) => {
6351 None
6354 }
6355 }
6356 }
6357 }
6358}
6359
6360pub fn apply_clip_path(
6372 display_list: &mut DisplayList,
6373 start_index: usize,
6374 clip_rect: LogicalRect,
6375 border_radius: f32,
6376) {
6377 let br = if border_radius > 0.0 {
6378 BorderRadius {
6379 top_left: border_radius,
6380 top_right: border_radius,
6381 bottom_left: border_radius,
6382 bottom_right: border_radius,
6383 }
6384 } else {
6385 BorderRadius::default()
6386 };
6387
6388 display_list.items.insert(start_index, DisplayListItem::PushClip {
6390 bounds: clip_rect.into(),
6391 border_radius: br,
6392 });
6393 if display_list.node_mapping.len() >= start_index {
6395 display_list.node_mapping.insert(start_index, None);
6396 }
6397
6398 display_list.items.push(DisplayListItem::PopClip);
6400 display_list.node_mapping.push(None);
6401}
6402
6403#[cfg(feature = "cpurender")]
6407fn rasterize_svg_clip_to_r8(
6408 svg_clip: &azul_core::svg::SvgMultiPolygon,
6409 paint_rect: &LogicalRect,
6410) -> Option<azul_core::resources::ImageRef> {
6411 use agg_rust::{
6412 basics::FillingRule,
6413 color::Rgba8,
6414 path_storage::PathStorage,
6415 pixfmt_rgba::PixfmtRgba32,
6416 rasterizer_scanline_aa::RasterizerScanlineAa,
6417 renderer_base::RendererBase,
6418 renderer_scanline::render_scanlines_aa_solid,
6419 rendering_buffer::RowAccessor,
6420 scanline_u::ScanlineU8,
6421 };
6422 use azul_core::resources::{ImageRef, RawImage, RawImageFormat, RawImageData};
6423
6424 let w = paint_rect.size.width.ceil() as u32;
6425 let h = paint_rect.size.height.ceil() as u32;
6426 if w == 0 || h == 0 {
6427 return None;
6428 }
6429
6430 let mut path = PathStorage::new();
6432 for ring in svg_clip.rings.as_ref().iter() {
6433 let mut first = true;
6434 for item in ring.items.as_ref().iter() {
6435 match item {
6436 azul_core::svg::SvgPathElement::Line(l) => {
6437 if first {
6438 path.move_to(
6439 (l.start.x - paint_rect.origin.x) as f64,
6440 (l.start.y - paint_rect.origin.y) as f64,
6441 );
6442 first = false;
6443 }
6444 path.line_to(
6445 (l.end.x - paint_rect.origin.x) as f64,
6446 (l.end.y - paint_rect.origin.y) as f64,
6447 );
6448 }
6449 azul_core::svg::SvgPathElement::QuadraticCurve(q) => {
6450 if first {
6451 path.move_to(
6452 (q.start.x - paint_rect.origin.x) as f64,
6453 (q.start.y - paint_rect.origin.y) as f64,
6454 );
6455 first = false;
6456 }
6457 path.curve3(
6458 (q.ctrl.x - paint_rect.origin.x) as f64,
6459 (q.ctrl.y - paint_rect.origin.y) as f64,
6460 (q.end.x - paint_rect.origin.x) as f64,
6461 (q.end.y - paint_rect.origin.y) as f64,
6462 );
6463 }
6464 azul_core::svg::SvgPathElement::CubicCurve(c) => {
6465 if first {
6466 path.move_to(
6467 (c.start.x - paint_rect.origin.x) as f64,
6468 (c.start.y - paint_rect.origin.y) as f64,
6469 );
6470 first = false;
6471 }
6472 path.curve4(
6473 (c.ctrl_1.x - paint_rect.origin.x) as f64,
6474 (c.ctrl_1.y - paint_rect.origin.y) as f64,
6475 (c.ctrl_2.x - paint_rect.origin.x) as f64,
6476 (c.ctrl_2.y - paint_rect.origin.y) as f64,
6477 (c.end.x - paint_rect.origin.x) as f64,
6478 (c.end.y - paint_rect.origin.y) as f64,
6479 );
6480 }
6481 }
6482 }
6483 }
6484
6485 let mut rgba_buf = vec![0u8; (w * h * 4) as usize];
6487 {
6488 let stride = (w * 4) as i32;
6489 let mut ra = unsafe {
6490 RowAccessor::new_with_buf(rgba_buf.as_mut_ptr(), w, h, stride)
6491 };
6492 let pf = PixfmtRgba32::new(&mut ra);
6493 let mut rb = RendererBase::new(pf);
6494
6495 let mut ras = RasterizerScanlineAa::new();
6496 ras.filling_rule(FillingRule::NonZero);
6497 ras.add_path(&mut path, 0);
6498
6499 let mut sl = ScanlineU8::new();
6500 let white = Rgba8 { r: 255, g: 255, b: 255, a: 255 };
6501 render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
6502 }
6503
6504 let r8_data: Vec<u8> = rgba_buf.chunks_exact(4).map(|px| px[3]).collect();
6506
6507 ImageRef::new_rawimage(RawImage {
6508 pixels: RawImageData::U8(r8_data.into()),
6509 width: w as usize,
6510 height: h as usize,
6511 premultiplied_alpha: false,
6512 data_format: RawImageFormat::R8,
6513 tag: Vec::new().into(),
6514 })
6515}