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) => crate::cpurender::union_rect(&d, &bounds),
346 None => bounds,
347 });
348 run_idx += 1;
349 }
350 }
351 }
352 }
353
354 damage
355 }
356
357 pub fn compute_text_damage_rect(
360 old_items: &[super::super::text3::cache::PositionedItem],
361 new_items: &[super::super::text3::cache::PositionedItem],
362 container_origin: LogicalPosition,
363 affected_line: usize,
364 ) -> LogicalRect {
365 let expand = |items: &[super::super::text3::cache::PositionedItem]| -> (f32, f32, f32, f32) {
366 let mut lx = f32::MAX;
367 let mut ly = f32::MAX;
368 let mut rx = f32::MIN;
369 let mut ry = f32::MIN;
370 for item in items {
371 if item.line_index >= affected_line {
372 let bounds = item.item.bounds();
373 let x = container_origin.x + item.position.x;
374 let y = container_origin.y + item.position.y;
375 lx = lx.min(x);
376 ly = ly.min(y);
377 rx = rx.max(x + bounds.width);
378 ry = ry.max(y + bounds.height);
379 }
380 }
381 (lx, ly, rx, ry)
382 };
383
384 let (olx, oly, orx, ory) = expand(old_items);
385 let (nlx, nly, nrx, nry) = expand(new_items);
386 let min_x = olx.min(nlx);
387 let min_y = oly.min(nly);
388 let max_x = orx.max(nrx);
389 let max_y = ory.max(nry);
390
391 if min_x > max_x || min_y > max_y {
392 return LogicalRect::default();
393 }
394
395 LogicalRect {
396 origin: LogicalPosition { x: min_x, y: min_y },
397 size: LogicalSize { width: max_x - min_x, height: max_y - min_y },
398 }
399 }
400
401 pub fn to_debug_json(&self) -> String {
404 use std::fmt::Write;
405 let mut json = String::new();
406 writeln!(json, "{{").unwrap();
407 writeln!(json, " \"total_items\": {},", self.items.len()).unwrap();
408 writeln!(json, " \"items\": [").unwrap();
409
410 let mut clip_depth = 0i32;
411 let mut scroll_depth = 0i32;
412 let mut stacking_depth = 0i32;
413
414 for (i, item) in self.items.iter().enumerate() {
415 let comma = if i < self.items.len() - 1 { "," } else { "" };
416 let node_id = self.node_mapping.get(i).and_then(|n| *n);
417
418 match item {
419 DisplayListItem::PushClip {
420 bounds,
421 border_radius,
422 } => {
423 clip_depth += 1;
424 writeln!(json, " {{").unwrap();
425 writeln!(json, " \"index\": {},", i).unwrap();
426 writeln!(json, " \"type\": \"PushClip\",").unwrap();
427 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
428 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
429 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
430 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
431 writeln!(json, " \"border_radius\": {{ \"tl\": {:.1}, \"tr\": {:.1}, \"bl\": {:.1}, \"br\": {:.1} }},",
432 border_radius.top_left, border_radius.top_right,
433 border_radius.bottom_left, border_radius.bottom_right).unwrap();
434 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
435 writeln!(json, " }}{}", comma).unwrap();
436 }
437 DisplayListItem::PopClip => {
438 writeln!(json, " {{").unwrap();
439 writeln!(json, " \"index\": {},", i).unwrap();
440 writeln!(json, " \"type\": \"PopClip\",").unwrap();
441 writeln!(json, " \"clip_depth_before\": {},", clip_depth).unwrap();
442 writeln!(json, " \"clip_depth_after\": {}", clip_depth - 1).unwrap();
443 writeln!(json, " }}{}", comma).unwrap();
444 clip_depth -= 1;
445 }
446 DisplayListItem::PushScrollFrame {
447 clip_bounds,
448 content_size,
449 scroll_id,
450 } => {
451 scroll_depth += 1;
452 writeln!(json, " {{").unwrap();
453 writeln!(json, " \"index\": {},", i).unwrap();
454 writeln!(json, " \"type\": \"PushScrollFrame\",").unwrap();
455 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
456 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
457 writeln!(json, " \"clip_bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
458 clip_bounds.0.origin.x, clip_bounds.0.origin.y,
459 clip_bounds.0.size.width, clip_bounds.0.size.height).unwrap();
460 writeln!(
461 json,
462 " \"content_size\": {{ \"w\": {:.1}, \"h\": {:.1} }},",
463 content_size.width, content_size.height
464 )
465 .unwrap();
466 writeln!(json, " \"scroll_id\": {},", scroll_id).unwrap();
467 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
468 writeln!(json, " }}{}", comma).unwrap();
469 }
470 DisplayListItem::PopScrollFrame => {
471 writeln!(json, " {{").unwrap();
472 writeln!(json, " \"index\": {},", i).unwrap();
473 writeln!(json, " \"type\": \"PopScrollFrame\",").unwrap();
474 writeln!(json, " \"scroll_depth_before\": {},", scroll_depth).unwrap();
475 writeln!(json, " \"scroll_depth_after\": {}", scroll_depth - 1).unwrap();
476 writeln!(json, " }}{}", comma).unwrap();
477 scroll_depth -= 1;
478 }
479 DisplayListItem::PushStackingContext { z_index, bounds } => {
480 stacking_depth += 1;
481 writeln!(json, " {{").unwrap();
482 writeln!(json, " \"index\": {},", i).unwrap();
483 writeln!(json, " \"type\": \"PushStackingContext\",").unwrap();
484 writeln!(json, " \"stacking_depth\": {},", stacking_depth).unwrap();
485 writeln!(json, " \"z_index\": {},", z_index).unwrap();
486 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
487 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
488 writeln!(json, " }}{}", comma).unwrap();
489 }
490 DisplayListItem::PopStackingContext => {
491 writeln!(json, " {{").unwrap();
492 writeln!(json, " \"index\": {},", i).unwrap();
493 writeln!(json, " \"type\": \"PopStackingContext\",").unwrap();
494 writeln!(json, " \"stacking_depth_before\": {},", stacking_depth).unwrap();
495 writeln!(
496 json,
497 " \"stacking_depth_after\": {}",
498 stacking_depth - 1
499 )
500 .unwrap();
501 writeln!(json, " }}{}", comma).unwrap();
502 stacking_depth -= 1;
503 }
504 DisplayListItem::Rect {
505 bounds,
506 color,
507 border_radius,
508 } => {
509 writeln!(json, " {{").unwrap();
510 writeln!(json, " \"index\": {},", i).unwrap();
511 writeln!(json, " \"type\": \"Rect\",").unwrap();
512 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
513 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
514 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
515 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
516 writeln!(
517 json,
518 " \"color\": \"rgba({},{},{},{})\",",
519 color.r, color.g, color.b, color.a
520 )
521 .unwrap();
522 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
523 writeln!(json, " }}{}", comma).unwrap();
524 }
525 DisplayListItem::Border { bounds, .. } => {
526 writeln!(json, " {{").unwrap();
527 writeln!(json, " \"index\": {},", i).unwrap();
528 writeln!(json, " \"type\": \"Border\",").unwrap();
529 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
530 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
531 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
532 bounds.0.origin.x, bounds.0.origin.y, bounds.0.size.width, bounds.0.size.height).unwrap();
533 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
534 writeln!(json, " }}{}", comma).unwrap();
535 }
536 DisplayListItem::ScrollBarStyled { info } => {
537 writeln!(json, " {{").unwrap();
538 writeln!(json, " \"index\": {},", i).unwrap();
539 writeln!(json, " \"type\": \"ScrollBarStyled\",").unwrap();
540 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
541 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
542 writeln!(json, " \"orientation\": \"{:?}\",", info.orientation).unwrap();
543 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
544 info.bounds.0.origin.x, info.bounds.0.origin.y,
545 info.bounds.0.size.width, info.bounds.0.size.height).unwrap();
546 writeln!(json, " }}{}", comma).unwrap();
547 }
548 _ => {
549 writeln!(json, " {{").unwrap();
550 writeln!(json, " \"index\": {},", i).unwrap();
551 writeln!(
552 json,
553 " \"type\": \"{:?}\",",
554 std::mem::discriminant(item)
555 )
556 .unwrap();
557 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
558 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
559 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
560 writeln!(json, " }}{}", comma).unwrap();
561 }
562 }
563 }
564
565 writeln!(json, " ],").unwrap();
566 writeln!(json, " \"final_clip_depth\": {},", clip_depth).unwrap();
567 writeln!(json, " \"final_scroll_depth\": {},", scroll_depth).unwrap();
568 writeln!(json, " \"final_stacking_depth\": {},", stacking_depth).unwrap();
569 writeln!(
570 json,
571 " \"balanced\": {}",
572 clip_depth == 0 && scroll_depth == 0 && stacking_depth == 0
573 )
574 .unwrap();
575 writeln!(json, "}}").unwrap();
576
577 json
578 }
579}
580
581#[derive(Debug, Clone)]
584pub enum DisplayListItem {
585 Rect {
589 bounds: WindowLogicalRect,
591 color: ColorU,
593 border_radius: BorderRadius,
595 },
596 SelectionRect {
599 bounds: WindowLogicalRect,
601 border_radius: BorderRadius,
603 color: ColorU,
605 },
606 CursorRect {
609 bounds: WindowLogicalRect,
611 color: ColorU,
613 },
614 Border {
617 bounds: WindowLogicalRect,
619 widths: StyleBorderWidths,
621 colors: StyleBorderColors,
623 styles: StyleBorderStyles,
625 border_radius: StyleBorderRadius,
627 },
628 TextLayout {
632 layout: Arc<dyn std::any::Any + Send + Sync>, bounds: WindowLogicalRect,
634 font_hash: FontHash,
635 font_size_px: f32,
636 color: ColorU,
637 },
638 Text {
640 glyphs: Vec<GlyphInstance>,
641 font_hash: FontHash,
642 font_size_px: f32,
643 color: ColorU,
644 clip_rect: WindowLogicalRect,
645 source_node_index: Option<usize>,
648 },
649 Underline {
651 bounds: WindowLogicalRect,
652 color: ColorU,
653 thickness: f32,
654 },
655 Strikethrough {
657 bounds: WindowLogicalRect,
658 color: ColorU,
659 thickness: f32,
660 },
661 Overline {
663 bounds: WindowLogicalRect,
664 color: ColorU,
665 thickness: f32,
666 },
667 Image {
668 bounds: WindowLogicalRect,
669 image: ImageRef,
670 border_radius: BorderRadius,
671 },
672 ScrollBar {
675 bounds: WindowLogicalRect,
676 color: ColorU,
677 orientation: ScrollbarOrientation,
678 opacity_key: Option<OpacityKey>,
682 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
685 },
686 ScrollBarStyled {
689 info: Box<ScrollbarDrawInfo>,
691 },
692
693 VirtualView {
699 child_dom_id: DomId,
701 bounds: WindowLogicalRect,
703 clip_rect: WindowLogicalRect,
705 },
706
707 VirtualViewPlaceholder {
716 node_id: NodeId,
718 bounds: WindowLogicalRect,
720 clip_rect: WindowLogicalRect,
722 },
723
724 PushClip {
728 bounds: WindowLogicalRect,
729 border_radius: BorderRadius,
730 },
731 PopClip,
733
734 PushImageMaskClip {
738 bounds: WindowLogicalRect,
740 mask_image: ImageRef,
742 mask_rect: WindowLogicalRect,
744 },
745 PopImageMaskClip,
747
748 PushScrollFrame {
751 clip_bounds: WindowLogicalRect,
753 content_size: LogicalSize,
755 scroll_id: LocalScrollId,
757 },
758 PopScrollFrame,
760
761 PushStackingContext {
764 z_index: i32,
766 bounds: WindowLogicalRect,
768 },
769 PopStackingContext,
771
772 PushReferenceFrame {
776 transform_key: TransformKey,
778 initial_transform: ComputedTransform3D,
780 bounds: WindowLogicalRect,
782 },
783 PopReferenceFrame,
785
786 HitTestArea {
788 bounds: WindowLogicalRect,
789 tag: DisplayListTagId, },
791
792 LinearGradient {
795 bounds: WindowLogicalRect,
796 gradient: LinearGradient,
797 border_radius: BorderRadius,
798 },
799 RadialGradient {
801 bounds: WindowLogicalRect,
802 gradient: RadialGradient,
803 border_radius: BorderRadius,
804 },
805 ConicGradient {
807 bounds: WindowLogicalRect,
808 gradient: ConicGradient,
809 border_radius: BorderRadius,
810 },
811
812 BoxShadow {
815 bounds: WindowLogicalRect,
816 shadow: StyleBoxShadow,
817 border_radius: BorderRadius,
818 },
819
820 PushFilter {
823 bounds: WindowLogicalRect,
824 filters: Vec<StyleFilter>,
825 },
826 PopFilter,
828
829 PushBackdropFilter {
831 bounds: WindowLogicalRect,
832 filters: Vec<StyleFilter>,
833 },
834 PopBackdropFilter,
836
837 PushOpacity {
839 bounds: WindowLogicalRect,
840 opacity: f32,
841 },
842 PopOpacity,
844
845 PushTextShadow {
847 shadow: azul_css::props::style::box_shadow::StyleBoxShadow,
848 },
849 PopTextShadow,
851}
852
853impl DisplayListItem {
854 pub fn is_visually_equal(&self, other: &Self) -> bool {
858 if std::mem::discriminant(self) != std::mem::discriminant(other) {
859 return false;
860 }
861 match (self, other) {
862 (Self::Rect { bounds: b1, color: c1, border_radius: br1 },
863 Self::Rect { bounds: b2, color: c2, border_radius: br2 }) => {
864 b1 == b2 && c1 == c2 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
865 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
866 }
867 (Self::SelectionRect { bounds: b1, border_radius: br1, color: c1 },
868 Self::SelectionRect { bounds: b2, border_radius: br2, color: c2 }) => {
869 b1 == b2 && c1 == c2 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
870 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
871 }
872 (Self::CursorRect { bounds: b1, color: c1 },
873 Self::CursorRect { bounds: b2, color: c2 }) => b1 == b2 && c1 == c2,
874 (Self::Text { glyphs: g1, font_hash: fh1, font_size_px: fs1, color: c1, clip_rect: cr1, .. },
875 Self::Text { glyphs: g2, font_hash: fh2, font_size_px: fs2, color: c2, clip_rect: cr2, .. }) => {
876 cr1 == cr2 && c1 == c2 && fh1 == fh2 && fs1 == fs2 && g1.len() == g2.len()
877 && g1.iter().zip(g2.iter()).all(|(a, b)| {
878 a.index == b.index
879 && a.point.x == b.point.x
880 && a.point.y == b.point.y
881 })
882 }
883 (Self::Underline { bounds: b1, color: c1, thickness: t1 },
884 Self::Underline { bounds: b2, color: c2, thickness: t2 }) => b1 == b2 && c1 == c2 && t1 == t2,
885 (Self::Strikethrough { bounds: b1, color: c1, thickness: t1 },
886 Self::Strikethrough { bounds: b2, color: c2, thickness: t2 }) => b1 == b2 && c1 == c2 && t1 == t2,
887 (Self::Overline { bounds: b1, color: c1, thickness: t1 },
888 Self::Overline { bounds: b2, color: c2, thickness: t2 }) => b1 == b2 && c1 == c2 && t1 == t2,
889 (Self::Border { bounds: b1, widths: w1, colors: c1, styles: s1, .. },
890 Self::Border { bounds: b2, widths: w2, colors: c2, styles: s2, .. }) => {
891 b1 == b2
892 && w1.top == w2.top && w1.right == w2.right && w1.bottom == w2.bottom && w1.left == w2.left
893 && c1.top == c2.top && c1.right == c2.right && c1.bottom == c2.bottom && c1.left == c2.left
894 && s1.top == s2.top && s1.right == s2.right && s1.bottom == s2.bottom && s1.left == s2.left
895 }
896 (Self::Image { bounds: b1, image: i1, border_radius: br1 },
897 Self::Image { bounds: b2, image: i2, border_radius: br2 }) => {
898 b1 == b2
899 && i1.data as usize == i2.data as usize && br1.top_left == br2.top_left && br1.top_right == br2.top_right
901 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
902 }
903 (Self::BoxShadow { bounds: b1, shadow: s1, border_radius: br1 },
904 Self::BoxShadow { bounds: b2, shadow: s2, border_radius: br2 }) => {
905 b1 == b2 && s1 == s2
906 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
907 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
908 }
909 (Self::LinearGradient { bounds: b1, gradient: g1, border_radius: br1 },
910 Self::LinearGradient { bounds: b2, gradient: g2, border_radius: br2 }) => {
911 b1 == b2 && g1 == g2
912 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
913 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
914 }
915 (Self::RadialGradient { bounds: b1, gradient: g1, border_radius: br1 },
916 Self::RadialGradient { bounds: b2, gradient: g2, border_radius: br2 }) => {
917 b1 == b2 && g1 == g2
918 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
919 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
920 }
921 (Self::ConicGradient { bounds: b1, gradient: g1, border_radius: br1 },
922 Self::ConicGradient { bounds: b2, gradient: g2, border_radius: br2 }) => {
923 b1 == b2 && g1 == g2
924 && br1.top_left == br2.top_left && br1.top_right == br2.top_right
925 && br1.bottom_left == br2.bottom_left && br1.bottom_right == br2.bottom_right
926 }
927 (Self::ScrollBar { bounds: b1, color: c1, .. },
928 Self::ScrollBar { bounds: b2, color: c2, .. }) => b1 == b2 && c1 == c2,
929 (Self::PushClip { bounds: b1, .. }, Self::PushClip { bounds: b2, .. }) => b1 == b2,
930 (Self::PushScrollFrame { clip_bounds: b1, scroll_id: s1, .. },
931 Self::PushScrollFrame { clip_bounds: b2, scroll_id: s2, .. }) => b1 == b2 && s1 == s2,
932 (Self::PushStackingContext { z_index: z1, bounds: b1 },
933 Self::PushStackingContext { z_index: z2, bounds: b2 }) => z1 == z2 && b1 == b2,
934 (Self::PushOpacity { bounds: b1, opacity: o1 },
935 Self::PushOpacity { bounds: b2, opacity: o2 }) => b1 == b2 && o1 == o2,
936 (Self::PopClip, Self::PopClip)
938 | (Self::PopImageMaskClip, Self::PopImageMaskClip)
939 | (Self::PopScrollFrame, Self::PopScrollFrame)
940 | (Self::PopStackingContext, Self::PopStackingContext)
941 | (Self::PopReferenceFrame, Self::PopReferenceFrame)
942 | (Self::PopFilter, Self::PopFilter)
943 | (Self::PopBackdropFilter, Self::PopBackdropFilter)
944 | (Self::PopOpacity, Self::PopOpacity)
945 | (Self::PopTextShadow, Self::PopTextShadow) => true,
946 _ => false,
949 }
950 }
951
952 pub fn is_state_management(&self) -> bool {
955 matches!(self,
956 Self::PushClip { .. }
957 | Self::PopClip
958 | Self::PushImageMaskClip { .. }
959 | Self::PopImageMaskClip
960 | Self::PushScrollFrame { .. }
961 | Self::PopScrollFrame
962 | Self::PushStackingContext { .. }
963 | Self::PopStackingContext
964 | Self::PushReferenceFrame { .. }
965 | Self::PopReferenceFrame
966 | Self::PushFilter { .. }
967 | Self::PopFilter
968 | Self::PushBackdropFilter { .. }
969 | Self::PopBackdropFilter
970 | Self::PushOpacity { .. }
971 | Self::PopOpacity
972 | Self::PushTextShadow { .. }
973 | Self::PopTextShadow
974 )
975 }
976
977 pub fn visual_bounds(&self) -> Option<LogicalRect> {
981 match self {
982 Self::BoxShadow { bounds, shadow, .. } => {
983 let b = *bounds.inner();
984 let ox = shadow.offset_x.to_pixels_internal(16.0, 16.0).abs();
986 let oy = shadow.offset_y.to_pixels_internal(16.0, 16.0).abs();
987 let blur = shadow.blur_radius.to_pixels_internal(16.0, 16.0).abs();
988 let spread = shadow.spread_radius.to_pixels_internal(16.0, 16.0).abs();
989 let expand = ox + oy + blur + spread;
990 Some(LogicalRect {
991 origin: LogicalPosition {
992 x: b.origin.x - expand,
993 y: b.origin.y - expand,
994 },
995 size: LogicalSize {
996 width: b.size.width + expand * 2.0,
997 height: b.size.height + expand * 2.0,
998 },
999 })
1000 }
1001 _ => self.bounds(),
1002 }
1003 }
1004
1005 pub fn bounds(&self) -> Option<LogicalRect> {
1008 match self {
1009 Self::Rect { bounds, .. }
1010 | Self::SelectionRect { bounds, .. }
1011 | Self::CursorRect { bounds, .. }
1012 | Self::Border { bounds, .. }
1013 | Self::Text { clip_rect: bounds, .. }
1014 | Self::TextLayout { bounds, .. }
1015 | Self::Underline { bounds, .. }
1016 | Self::Strikethrough { bounds, .. }
1017 | Self::Overline { bounds, .. }
1018 | Self::Image { bounds, .. }
1019 | Self::ScrollBar { bounds, .. }
1020 | Self::LinearGradient { bounds, .. }
1021 | Self::RadialGradient { bounds, .. }
1022 | Self::ConicGradient { bounds, .. }
1023 | Self::BoxShadow { bounds, .. }
1024 | Self::VirtualView { bounds, .. }
1025 | Self::VirtualViewPlaceholder { bounds, .. }
1026 | Self::HitTestArea { bounds, .. }
1027 | Self::PushClip { bounds, .. }
1028 | Self::PushImageMaskClip { bounds, .. }
1029 | Self::PushScrollFrame { clip_bounds: bounds, .. }
1030 | Self::PushStackingContext { bounds, .. }
1031 | Self::PushReferenceFrame { bounds, .. }
1032 | Self::PushFilter { bounds, .. }
1033 | Self::PushBackdropFilter { bounds, .. }
1034 | Self::PushOpacity { bounds, .. } => Some(*bounds.inner()),
1035 Self::ScrollBarStyled { info, .. } => Some(*info.bounds.inner()),
1036 Self::PushTextShadow { .. } => None, Self::PopClip
1038 | Self::PopImageMaskClip
1039 | Self::PopScrollFrame
1040 | Self::PopStackingContext
1041 | Self::PopReferenceFrame
1042 | Self::PopFilter
1043 | Self::PopBackdropFilter
1044 | Self::PopOpacity
1045 | Self::PopTextShadow => None,
1046 }
1047 }
1048}
1049
1050#[derive(Debug, Copy, Clone, Default)]
1052pub struct BorderRadius {
1053 pub top_left: f32,
1054 pub top_right: f32,
1055 pub bottom_left: f32,
1056 pub bottom_right: f32,
1057}
1058
1059impl BorderRadius {
1060 pub fn is_zero(&self) -> bool {
1061 self.top_left == 0.0
1062 && self.top_right == 0.0
1063 && self.bottom_left == 0.0
1064 && self.bottom_right == 0.0
1065 }
1066}
1067
1068pub type LocalScrollId = u64;
1070pub type DisplayListTagId = (u64, u16);
1075
1076#[derive(Debug, Default)]
1078struct DisplayListBuilder {
1079 items: Vec<DisplayListItem>,
1080 node_mapping: Vec<Option<NodeId>>,
1081 current_node: Option<NodeId>,
1083 debug_messages: Vec<LayoutDebugMessage>,
1085 debug_enabled: bool,
1087 forced_page_breaks: Vec<f32>,
1089 fixed_position_item_ranges: Vec<(usize, usize)>,
1091 fixed_position_start: Option<usize>,
1093}
1094
1095impl DisplayListBuilder {
1096 pub fn new() -> Self {
1097 Self::default()
1098 }
1099
1100 pub fn with_debug(debug_enabled: bool) -> Self {
1101 Self {
1102 items: Vec::new(),
1103 node_mapping: Vec::new(),
1104 current_node: None,
1105 debug_messages: Vec::new(),
1106 debug_enabled,
1107 forced_page_breaks: Vec::new(),
1108 fixed_position_item_ranges: Vec::new(),
1109 fixed_position_start: None,
1110 }
1111 }
1112
1113 fn debug_log(&mut self, message: String) {
1115 if self.debug_enabled {
1116 self.debug_messages.push(LayoutDebugMessage::info(message));
1117 }
1118 }
1119
1120 pub fn build_with_debug(
1122 mut self,
1123 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
1124 ) -> DisplayList {
1125 if let Some(msgs) = debug_messages.as_mut() {
1127 msgs.append(&mut self.debug_messages);
1128 }
1129 DisplayList {
1130 items: self.items,
1131 node_mapping: self.node_mapping,
1132 forced_page_breaks: self.forced_page_breaks,
1133 fixed_position_item_ranges: self.fixed_position_item_ranges,
1134 }
1135 }
1136
1137 pub fn set_current_node(&mut self, node_id: Option<NodeId>) {
1139 self.current_node = node_id;
1140 }
1141
1142 pub fn begin_fixed_position_element(&mut self) {
1144 self.fixed_position_start = Some(self.items.len());
1145 }
1146
1147 pub fn end_fixed_position_element(&mut self) {
1150 if let Some(start) = self.fixed_position_start.take() {
1151 let end = self.items.len();
1152 if end > start {
1153 self.fixed_position_item_ranges.push((start, end));
1154 }
1155 }
1156 }
1157
1158 pub fn add_forced_page_break(&mut self, y_position: f32) {
1161 if !self.forced_page_breaks.contains(&y_position) {
1163 self.forced_page_breaks.push(y_position);
1164 self.forced_page_breaks.sort_by(|a, b| a.partial_cmp(b).unwrap());
1165 }
1166 }
1167
1168 fn push_item(&mut self, item: DisplayListItem) {
1170 self.items.push(item);
1171 self.node_mapping.push(self.current_node);
1172 }
1173
1174 pub fn build(self) -> DisplayList {
1175 DisplayList {
1176 items: self.items,
1177 node_mapping: self.node_mapping,
1178 forced_page_breaks: self.forced_page_breaks,
1179 fixed_position_item_ranges: self.fixed_position_item_ranges,
1180 }
1181 }
1182
1183 pub fn push_hit_test_area(&mut self, bounds: LogicalRect, tag: DisplayListTagId) {
1184 self.push_item(DisplayListItem::HitTestArea { bounds: bounds.into(), tag });
1185 }
1186
1187 pub fn push_scrollbar(
1189 &mut self,
1190 bounds: LogicalRect,
1191 color: ColorU,
1192 orientation: ScrollbarOrientation,
1193 opacity_key: Option<OpacityKey>,
1194 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
1195 ) {
1196 if color.a > 0 || opacity_key.is_some() {
1197 self.push_item(DisplayListItem::ScrollBar {
1199 bounds: bounds.into(),
1200 color,
1201 orientation,
1202 opacity_key,
1203 hit_id,
1204 });
1205 }
1206 }
1207
1208 pub fn push_scrollbar_styled(&mut self, info: ScrollbarDrawInfo) {
1210 if info.thumb_color.a > 0 || info.track_color.a > 0 || info.opacity_key.is_some() {
1212 self.push_item(DisplayListItem::ScrollBarStyled {
1213 info: Box::new(info),
1214 });
1215 }
1216 }
1217
1218 pub fn push_rect(&mut self, bounds: LogicalRect, color: ColorU, border_radius: BorderRadius) {
1219 if color.a > 0 {
1220 self.push_item(DisplayListItem::Rect {
1222 bounds: bounds.into(),
1223 color,
1224 border_radius,
1225 });
1226 }
1227 }
1228
1229 pub fn push_backgrounds_and_border(
1239 &mut self,
1240 bounds: LogicalRect,
1241 background_contents: &[azul_css::props::style::StyleBackgroundContent],
1242 border_info: &BorderInfo,
1243 simple_border_radius: BorderRadius,
1244 style_border_radius: StyleBorderRadius,
1245 image_cache: &azul_core::resources::ImageCache,
1246 ) {
1247 use azul_css::props::style::StyleBackgroundContent;
1248
1249 for bg in background_contents {
1251 match bg {
1252 StyleBackgroundContent::Color(color) => {
1253 self.push_rect(bounds, *color, simple_border_radius);
1254 }
1255 StyleBackgroundContent::LinearGradient(gradient) => {
1256 self.push_linear_gradient(bounds, gradient.clone(), simple_border_radius);
1257 }
1258 StyleBackgroundContent::RadialGradient(gradient) => {
1259 self.push_radial_gradient(bounds, gradient.clone(), simple_border_radius);
1260 }
1261 StyleBackgroundContent::ConicGradient(gradient) => {
1262 self.push_conic_gradient(bounds, gradient.clone(), simple_border_radius);
1263 }
1264 StyleBackgroundContent::Image(image_id) => {
1265 if let Some(image_ref) = image_cache.get_css_image_id(image_id) {
1266 self.push_image(bounds, image_ref.clone(), simple_border_radius);
1267 }
1268 }
1269 }
1270 }
1271
1272 self.push_border(
1274 bounds,
1275 border_info.widths,
1276 border_info.colors,
1277 border_info.styles,
1278 style_border_radius,
1279 );
1280 }
1281
1282 pub fn push_inline_backgrounds_and_border(
1289 &mut self,
1290 bounds: LogicalRect,
1291 background_color: Option<ColorU>,
1292 background_contents: &[azul_css::props::style::StyleBackgroundContent],
1293 border: Option<&crate::text3::cache::InlineBorderInfo>,
1294 image_cache: &azul_core::resources::ImageCache,
1295 ) {
1296 use azul_css::props::style::StyleBackgroundContent;
1297
1298 if let Some(bg_color) = background_color {
1300 self.push_rect(bounds, bg_color, BorderRadius::default());
1301 }
1302
1303 for bg in background_contents {
1305 match bg {
1306 StyleBackgroundContent::Color(color) => {
1307 self.push_rect(bounds, *color, BorderRadius::default());
1308 }
1309 StyleBackgroundContent::LinearGradient(gradient) => {
1310 self.push_linear_gradient(bounds, gradient.clone(), BorderRadius::default());
1311 }
1312 StyleBackgroundContent::RadialGradient(gradient) => {
1313 self.push_radial_gradient(bounds, gradient.clone(), BorderRadius::default());
1314 }
1315 StyleBackgroundContent::ConicGradient(gradient) => {
1316 self.push_conic_gradient(bounds, gradient.clone(), BorderRadius::default());
1317 }
1318 StyleBackgroundContent::Image(image_id) => {
1319 if let Some(image_ref) = image_cache.get_css_image_id(image_id) {
1320 self.push_image(bounds, image_ref.clone(), BorderRadius::default());
1321 }
1322 }
1323 }
1324 }
1325
1326 if let Some(border) = border {
1329 let effective_left = if border.left_inset() > 0.0 { border.left } else { 0.0 };
1330 let effective_right = if border.right_inset() > 0.0 { border.right } else { 0.0 };
1331 if border.top > 0.0 || effective_right > 0.0 || border.bottom > 0.0 || effective_left > 0.0 {
1332 let border_widths = StyleBorderWidths {
1333 top: Some(CssPropertyValue::Exact(LayoutBorderTopWidth {
1334 inner: PixelValue::px(border.top),
1335 })),
1336 right: Some(CssPropertyValue::Exact(LayoutBorderRightWidth {
1337 inner: PixelValue::px(effective_right),
1338 })),
1339 bottom: Some(CssPropertyValue::Exact(LayoutBorderBottomWidth {
1340 inner: PixelValue::px(border.bottom),
1341 })),
1342 left: Some(CssPropertyValue::Exact(LayoutBorderLeftWidth {
1343 inner: PixelValue::px(effective_left),
1344 })),
1345 };
1346 let border_colors = StyleBorderColors {
1347 top: Some(CssPropertyValue::Exact(StyleBorderTopColor {
1348 inner: border.top_color,
1349 })),
1350 right: Some(CssPropertyValue::Exact(StyleBorderRightColor {
1351 inner: border.right_color,
1352 })),
1353 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomColor {
1354 inner: border.bottom_color,
1355 })),
1356 left: Some(CssPropertyValue::Exact(StyleBorderLeftColor {
1357 inner: border.left_color,
1358 })),
1359 };
1360 let border_styles = StyleBorderStyles {
1361 top: Some(CssPropertyValue::Exact(StyleBorderTopStyle {
1362 inner: BorderStyle::Solid,
1363 })),
1364 right: Some(CssPropertyValue::Exact(StyleBorderRightStyle {
1365 inner: BorderStyle::Solid,
1366 })),
1367 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomStyle {
1368 inner: BorderStyle::Solid,
1369 })),
1370 left: Some(CssPropertyValue::Exact(StyleBorderLeftStyle {
1371 inner: BorderStyle::Solid,
1372 })),
1373 };
1374 let radius_px = PixelValue::px(border.radius.unwrap_or(0.0));
1375 let border_radius = StyleBorderRadius {
1376 top_left: radius_px,
1377 top_right: radius_px,
1378 bottom_left: radius_px,
1379 bottom_right: radius_px,
1380 };
1381
1382 self.push_border(
1383 bounds,
1384 border_widths,
1385 border_colors,
1386 border_styles,
1387 border_radius,
1388 );
1389 }
1390 }
1391 }
1392
1393 pub fn push_linear_gradient(
1395 &mut self,
1396 bounds: LogicalRect,
1397 gradient: LinearGradient,
1398 border_radius: BorderRadius,
1399 ) {
1400 self.push_item(DisplayListItem::LinearGradient {
1401 bounds: bounds.into(),
1402 gradient,
1403 border_radius,
1404 });
1405 }
1406
1407 pub fn push_radial_gradient(
1409 &mut self,
1410 bounds: LogicalRect,
1411 gradient: RadialGradient,
1412 border_radius: BorderRadius,
1413 ) {
1414 self.push_item(DisplayListItem::RadialGradient {
1415 bounds: bounds.into(),
1416 gradient,
1417 border_radius,
1418 });
1419 }
1420
1421 pub fn push_conic_gradient(
1423 &mut self,
1424 bounds: LogicalRect,
1425 gradient: ConicGradient,
1426 border_radius: BorderRadius,
1427 ) {
1428 self.push_item(DisplayListItem::ConicGradient {
1429 bounds: bounds.into(),
1430 gradient,
1431 border_radius,
1432 });
1433 }
1434
1435 pub fn push_selection_rect(
1436 &mut self,
1437 bounds: LogicalRect,
1438 color: ColorU,
1439 border_radius: BorderRadius,
1440 ) {
1441 if color.a > 0 {
1442 self.push_item(DisplayListItem::SelectionRect {
1443 bounds: bounds.into(),
1444 color,
1445 border_radius,
1446 });
1447 }
1448 }
1449
1450 pub fn push_cursor_rect(&mut self, bounds: LogicalRect, color: ColorU) {
1451 if color.a > 0 {
1452 self.push_item(DisplayListItem::CursorRect { bounds: bounds.into(), color });
1453 }
1454 }
1455 pub fn push_clip(&mut self, bounds: LogicalRect, border_radius: BorderRadius) {
1456 self.push_item(DisplayListItem::PushClip {
1457 bounds: bounds.into(),
1458 border_radius,
1459 });
1460 }
1461 pub fn pop_clip(&mut self) {
1462 self.push_item(DisplayListItem::PopClip);
1463 }
1464 pub fn push_image_mask_clip(&mut self, bounds: LogicalRect, mask_image: ImageRef, mask_rect: LogicalRect) {
1465 self.push_item(DisplayListItem::PushImageMaskClip {
1466 bounds: bounds.into(),
1467 mask_image,
1468 mask_rect: mask_rect.into(),
1469 });
1470 }
1471 pub fn pop_image_mask_clip(&mut self) {
1472 self.push_item(DisplayListItem::PopImageMaskClip);
1473 }
1474 pub fn push_scroll_frame(
1475 &mut self,
1476 clip_bounds: LogicalRect,
1477 content_size: LogicalSize,
1478 scroll_id: LocalScrollId,
1479 ) {
1480 self.push_item(DisplayListItem::PushScrollFrame {
1481 clip_bounds: clip_bounds.into(),
1482 content_size,
1483 scroll_id,
1484 });
1485 }
1486 pub fn pop_scroll_frame(&mut self) {
1487 self.push_item(DisplayListItem::PopScrollFrame);
1488 }
1489 pub fn push_virtual_view_placeholder(
1490 &mut self,
1491 node_id: NodeId,
1492 bounds: LogicalRect,
1493 clip_rect: LogicalRect,
1494 ) {
1495 self.push_item(DisplayListItem::VirtualViewPlaceholder {
1496 node_id,
1497 bounds: bounds.into(),
1498 clip_rect: clip_rect.into(),
1499 });
1500 }
1501 pub fn push_border(
1502 &mut self,
1503 bounds: LogicalRect,
1504 widths: StyleBorderWidths,
1505 colors: StyleBorderColors,
1506 styles: StyleBorderStyles,
1507 border_radius: StyleBorderRadius,
1508 ) {
1509 let has_visible_border = {
1511 let has_width = widths.top.is_some()
1512 || widths.right.is_some()
1513 || widths.bottom.is_some()
1514 || widths.left.is_some();
1515 let has_style = styles.top.is_some()
1516 || styles.right.is_some()
1517 || styles.bottom.is_some()
1518 || styles.left.is_some();
1519 has_width && has_style
1520 };
1521
1522 if has_visible_border {
1523 self.push_item(DisplayListItem::Border {
1524 bounds: bounds.into(),
1525 widths,
1526 colors,
1527 styles,
1528 border_radius,
1529 });
1530 }
1531 }
1532
1533 pub fn push_stacking_context(&mut self, z_index: i32, bounds: LogicalRect) {
1534 self.push_item(DisplayListItem::PushStackingContext { z_index, bounds: bounds.into() });
1535 }
1536
1537 pub fn pop_stacking_context(&mut self) {
1538 self.push_item(DisplayListItem::PopStackingContext);
1539 }
1540
1541 pub fn push_reference_frame(
1542 &mut self,
1543 transform_key: TransformKey,
1544 initial_transform: ComputedTransform3D,
1545 bounds: LogicalRect,
1546 ) {
1547 self.push_item(DisplayListItem::PushReferenceFrame {
1548 transform_key,
1549 initial_transform,
1550 bounds: bounds.into(),
1551 });
1552 }
1553
1554 pub fn pop_reference_frame(&mut self) {
1555 self.push_item(DisplayListItem::PopReferenceFrame);
1556 }
1557
1558 pub fn push_text_run(
1559 &mut self,
1560 glyphs: Vec<GlyphInstance>,
1561 font_hash: FontHash, font_size_px: f32,
1563 color: ColorU,
1564 clip_rect: LogicalRect,
1565 source_node_index: Option<usize>,
1566 ) {
1567 self.debug_log(format!(
1568 "[push_text_run] {} glyphs, font_size={}px, color=({},{},{},{}), clip={:?}",
1569 glyphs.len(),
1570 font_size_px,
1571 color.r,
1572 color.g,
1573 color.b,
1574 color.a,
1575 clip_rect
1576 ));
1577
1578 if !glyphs.is_empty() && color.a > 0 {
1579 self.push_item(DisplayListItem::Text {
1580 glyphs,
1581 font_hash,
1582 font_size_px,
1583 color,
1584 clip_rect: clip_rect.into(),
1585 source_node_index,
1586 });
1587 } else {
1588 self.debug_log(format!(
1589 "[push_text_run] SKIPPED: glyphs.is_empty()={}, color.a={}",
1590 glyphs.is_empty(),
1591 color.a
1592 ));
1593 }
1594 }
1595
1596 pub fn push_text_layout(
1597 &mut self,
1598 layout: Arc<dyn std::any::Any + Send + Sync>,
1599 bounds: LogicalRect,
1600 font_hash: FontHash,
1601 font_size_px: f32,
1602 color: ColorU,
1603 ) {
1604 if color.a > 0 {
1605 self.push_item(DisplayListItem::TextLayout {
1606 layout,
1607 bounds: bounds.into(),
1608 font_hash,
1609 font_size_px,
1610 color,
1611 });
1612 }
1613 }
1614
1615 pub fn push_underline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1616 if color.a > 0 && thickness > 0.0 {
1617 self.push_item(DisplayListItem::Underline {
1618 bounds: bounds.into(),
1619 color,
1620 thickness,
1621 });
1622 }
1623 }
1624
1625 pub fn push_strikethrough(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1626 if color.a > 0 && thickness > 0.0 {
1627 self.push_item(DisplayListItem::Strikethrough {
1628 bounds: bounds.into(),
1629 color,
1630 thickness,
1631 });
1632 }
1633 }
1634
1635 pub fn push_overline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1636 if color.a > 0 && thickness > 0.0 {
1637 self.push_item(DisplayListItem::Overline {
1638 bounds: bounds.into(),
1639 color,
1640 thickness,
1641 });
1642 }
1643 }
1644
1645 pub fn push_image(&mut self, bounds: LogicalRect, image: ImageRef, border_radius: BorderRadius) {
1646 self.push_item(DisplayListItem::Image { bounds: bounds.into(), image, border_radius });
1647 }
1648}
1649
1650pub fn generate_display_list<T: ParsedFontTrait + Sync + 'static>(
1652 ctx: &mut LayoutContext<T>,
1653 tree: &LayoutTree,
1654 calculated_positions: &super::PositionVec,
1655 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
1656 scroll_ids: &HashMap<usize, u64>,
1657 gpu_value_cache: Option<&GpuValueCache>,
1658 renderer_resources: &RendererResources,
1659 id_namespace: IdNamespace,
1660 dom_id: DomId,
1661) -> Result<DisplayList> {
1662 debug_info!(
1663 ctx,
1664 "[DisplayList] generate_display_list: tree has {} nodes, {} positions calculated",
1665 tree.nodes.len(),
1666 calculated_positions.len()
1667 );
1668
1669 debug_info!(ctx, "Starting display list generation");
1670 debug_info!(
1671 ctx,
1672 "Collecting stacking contexts from root node {}",
1673 tree.root
1674 );
1675
1676 let positioned_tree = PositionedTree {
1677 tree,
1678 calculated_positions,
1679 };
1680 let mut generator = DisplayListGenerator::new(
1681 ctx,
1682 scroll_offsets,
1683 &positioned_tree,
1684 scroll_ids,
1685 gpu_value_cache,
1686 renderer_resources,
1687 id_namespace,
1688 dom_id,
1689 );
1690
1691 let debug_enabled = generator.ctx.debug_messages.is_some();
1693 let mut builder = DisplayListBuilder::with_debug(debug_enabled);
1694
1695 {
1702 let root_node = tree.get(tree.root);
1703 if let Some(root) = root_node {
1704 if let Some(root_dom_id) = root.dom_node_id {
1705 let root_state = generator.get_styled_node_state(root_dom_id);
1706 let canvas_bg = get_background_color(
1707 generator.ctx.styled_dom,
1708 root_dom_id,
1709 &root_state,
1710 );
1711 if canvas_bg.a > 0 {
1712 let viewport_rect = LogicalRect {
1713 origin: LogicalPosition::zero(),
1714 size: generator.ctx.viewport_size,
1715 };
1716 builder.push_rect(viewport_rect, canvas_bg, BorderRadius::default());
1717 debug_info!(
1718 generator.ctx,
1719 "[DisplayList] Canvas background: color=({},{},{},{}), size={:?}",
1720 canvas_bg.r, canvas_bg.g, canvas_bg.b, canvas_bg.a,
1721 generator.ctx.viewport_size
1722 );
1723 }
1724 }
1725 }
1726 }
1727
1728 let stacking_context_tree = generator.collect_stacking_contexts(tree.root)?;
1733
1734 debug_info!(
1736 generator.ctx,
1737 "Generating display items from stacking context tree"
1738 );
1739 generator.generate_for_stacking_context(&mut builder, &stacking_context_tree)?;
1740
1741 let display_list = builder.build_with_debug(generator.ctx.debug_messages);
1743 debug_info!(
1744 generator.ctx,
1745 "[DisplayList] Generated {} display items",
1746 display_list.items.len()
1747 );
1748 Ok(display_list)
1749}
1750
1751struct DisplayListGenerator<'a, 'b, T: ParsedFontTrait> {
1753 ctx: &'a mut LayoutContext<'b, T>,
1754 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1755 positioned_tree: &'a PositionedTree<'a>,
1756 scroll_ids: &'a HashMap<usize, u64>,
1757 gpu_value_cache: Option<&'a GpuValueCache>,
1758 renderer_resources: &'a RendererResources,
1759 id_namespace: IdNamespace,
1760 dom_id: DomId,
1761}
1762
1763#[derive(Debug)]
1766struct StackingContext {
1767 node_index: usize,
1768 z_index: i32,
1769 child_contexts: Vec<StackingContext>,
1770 in_flow_children: Vec<usize>,
1772}
1773
1774impl<'a, 'b, T> DisplayListGenerator<'a, 'b, T>
1775where
1776 T: ParsedFontTrait + Sync + 'static,
1777{
1778 pub fn new(
1779 ctx: &'a mut LayoutContext<'b, T>,
1780 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1781 positioned_tree: &'a PositionedTree<'a>,
1782 scroll_ids: &'a HashMap<usize, u64>,
1783 gpu_value_cache: Option<&'a GpuValueCache>,
1784 renderer_resources: &'a RendererResources,
1785 id_namespace: IdNamespace,
1786 dom_id: DomId,
1787 ) -> Self {
1788 Self {
1789 ctx,
1790 scroll_offsets,
1791 positioned_tree,
1792 scroll_ids,
1793 gpu_value_cache,
1794 renderer_resources,
1795 id_namespace,
1796 dom_id,
1797 }
1798 }
1799
1800 fn get_styled_node_state(&self, dom_id: NodeId) -> azul_core::styled_dom::StyledNodeState {
1802 self.ctx
1803 .styled_dom
1804 .styled_nodes
1805 .as_container()
1806 .get(dom_id)
1807 .map(|n| n.styled_node_state.clone())
1808 .unwrap_or_default()
1809 }
1810
1811 fn is_node_hidden(&self, node_index: usize) -> bool {
1815 use azul_css::props::style::effects::StyleVisibility;
1816 let node = match self.positioned_tree.tree.get(node_index) {
1817 Some(n) => n,
1818 None => return false,
1819 };
1820 let dom_id = match node.dom_node_id {
1821 Some(id) => id,
1822 None => return false,
1823 };
1824 let node_state = self.get_styled_node_state(dom_id);
1825 match get_visibility(self.ctx.styled_dom, dom_id, &node_state) {
1826 crate::solver3::getters::MultiValue::Exact(StyleVisibility::Hidden)
1827 | crate::solver3::getters::MultiValue::Exact(StyleVisibility::Collapse) => true,
1828 _ => false,
1829 }
1830 }
1831
1832 fn get_cursor_type_for_text_node(&self, node_id: NodeId) -> CursorType {
1835 use azul_css::props::style::effects::StyleCursor;
1836
1837 let styled_node_state = self.get_styled_node_state(node_id);
1838 let node_data_container = self.ctx.styled_dom.node_data.as_container();
1839 let node_data = node_data_container.get(node_id);
1840
1841 if let Some(node_data) = node_data {
1843 if let Some(cursor_value) = self.ctx.styled_dom.get_css_property_cache().get_cursor(
1844 node_data,
1845 &node_id,
1846 &styled_node_state,
1847 ) {
1848 if let CssPropertyValue::Exact(cursor) = cursor_value {
1849 return match cursor {
1850 StyleCursor::Default => CursorType::Default,
1851 StyleCursor::Pointer => CursorType::Pointer,
1852 StyleCursor::Text => CursorType::Text,
1853 StyleCursor::Crosshair => CursorType::Crosshair,
1854 StyleCursor::Move => CursorType::Move,
1855 StyleCursor::Help => CursorType::Help,
1856 StyleCursor::Wait => CursorType::Wait,
1857 StyleCursor::Progress => CursorType::Progress,
1858 StyleCursor::NsResize => CursorType::NsResize,
1859 StyleCursor::EwResize => CursorType::EwResize,
1860 StyleCursor::NeswResize => CursorType::NeswResize,
1861 StyleCursor::NwseResize => CursorType::NwseResize,
1862 StyleCursor::NResize => CursorType::NResize,
1863 StyleCursor::SResize => CursorType::SResize,
1864 StyleCursor::EResize => CursorType::EResize,
1865 StyleCursor::WResize => CursorType::WResize,
1866 StyleCursor::Grab => CursorType::Grab,
1867 StyleCursor::Grabbing => CursorType::Grabbing,
1868 StyleCursor::RowResize => CursorType::RowResize,
1869 StyleCursor::ColResize => CursorType::ColResize,
1870 StyleCursor::SeResize | StyleCursor::NeswResize => CursorType::NeswResize,
1872 StyleCursor::ZoomIn | StyleCursor::ZoomOut => CursorType::Default,
1873 StyleCursor::Copy | StyleCursor::Alias => CursorType::Default,
1874 StyleCursor::Cell => CursorType::Crosshair,
1875 StyleCursor::AllScroll => CursorType::Move,
1876 StyleCursor::ContextMenu => CursorType::Default,
1877 StyleCursor::VerticalText => CursorType::Text,
1878 StyleCursor::Unset => CursorType::Text, };
1880 }
1881 }
1882 }
1883
1884 CursorType::Text
1886 }
1887
1888 fn paint_selections(
1891 &self,
1892 builder: &mut DisplayListBuilder,
1893 node_index: usize,
1894 ) -> Result<()> {
1895 let node = self
1896 .positioned_tree
1897 .tree
1898 .get(node_index)
1899 .ok_or(LayoutError::InvalidTree)?;
1900 let Some(dom_id) = node.dom_node_id else {
1901 return Ok(());
1902 };
1903
1904 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
1908 return Ok(());
1909 };
1910
1911 let node_pos = self
1913 .positioned_tree
1914 .calculated_positions
1915 .get(node_index)
1916 .copied()
1917 .unwrap_or_default();
1918
1919 let bp = node.box_props.unpack();
1921 let padding = &bp.padding;
1922 let border = &bp.border;
1923 let content_box_offset_x = node_pos.x + padding.left + border.left;
1924 let content_box_offset_y = node_pos.y + padding.top + border.top;
1925
1926 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1928 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
1929
1930 if !is_selectable {
1931 return Ok(());
1932 }
1933
1934 if let Some(text_selection) = self.ctx.text_selections.get(&self.ctx.styled_dom.dom_id) {
1936 if let Some(range) = text_selection.affected_nodes.get(&dom_id) {
1937 let is_collapsed = text_selection.is_collapsed();
1938
1939 if !is_collapsed {
1941 let rects = layout.get_selection_rects(range);
1942 let style = get_selection_style(self.ctx.styled_dom, Some(dom_id), self.ctx.system_style.as_ref());
1943
1944 let border_radius = BorderRadius {
1945 top_left: style.radius,
1946 top_right: style.radius,
1947 bottom_left: style.radius,
1948 bottom_right: style.radius,
1949 };
1950
1951 for mut rect in rects {
1952 rect.origin.x += content_box_offset_x;
1953 rect.origin.y += content_box_offset_y;
1954 builder.push_selection_rect(rect, style.bg_color, border_radius);
1955 }
1956 }
1957
1958 return Ok(());
1959 }
1960 }
1961
1962 Ok(())
1963 }
1964
1965 fn paint_cursor(
1969 &self,
1970 builder: &mut DisplayListBuilder,
1971 node_index: usize,
1972 ) -> Result<()> {
1973 if !self.ctx.cursor_is_visible {
1975 return Ok(());
1976 }
1977
1978 if self.ctx.cursor_locations.is_empty() {
1980 return Ok(());
1981 }
1982
1983 let node = self
1984 .positioned_tree
1985 .tree
1986 .get(node_index)
1987 .ok_or(LayoutError::InvalidTree)?;
1988 let Some(dom_id) = node.dom_node_id else {
1989 return Ok(());
1990 };
1991
1992 let is_contenteditable = super::getters::is_node_contenteditable_inherited(self.ctx.styled_dom, dom_id);
1994 if !is_contenteditable {
1995 return Ok(());
1996 }
1997
1998 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2000 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
2001 if !is_selectable {
2002 return Ok(());
2003 }
2004
2005 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
2007 return Ok(());
2008 };
2009
2010 let node_pos = self
2012 .positioned_tree
2013 .calculated_positions
2014 .get(node_index)
2015 .copied()
2016 .unwrap_or_default();
2017 let bp = node.box_props.unpack();
2018 let padding = &bp.padding;
2019 let border = &bp.border;
2020 let content_box_offset_x = node_pos.x + padding.left + border.left;
2021 let content_box_offset_y = node_pos.y + padding.top + border.top;
2022
2023 let style = get_caret_style(self.ctx.styled_dom, Some(dom_id));
2024
2025 let primary_idx_for_this_node = self.ctx.cursor_locations.iter().enumerate()
2028 .rev()
2029 .find(|(_, (cd, cn, _))| {
2030 *cd == self.ctx.styled_dom.dom_id && (*cn == dom_id || self.positioned_tree.tree.children(node_index).iter().any(|&child_idx| {
2031 self.positioned_tree.tree.get(child_idx)
2032 .and_then(|c| c.dom_node_id)
2033 .map(|id| id == *cn)
2034 .unwrap_or(false)
2035 }))
2036 })
2037 .map(|(i, _)| i);
2038
2039 for (i, (cursor_dom_id, cursor_node_id, cursor)) in self.ctx.cursor_locations.iter().enumerate() {
2040 if self.ctx.styled_dom.dom_id != *cursor_dom_id {
2042 continue;
2043 }
2044
2045 if dom_id != *cursor_node_id {
2047 let is_ifc_root_of_cursor = self.positioned_tree.tree.children(node_index)
2048 .iter()
2049 .any(|&child_idx| {
2050 self.positioned_tree.tree.get(child_idx)
2051 .and_then(|c| c.dom_node_id)
2052 .map(|id| id == *cursor_node_id)
2053 .unwrap_or(false)
2054 });
2055 if !is_ifc_root_of_cursor {
2056 continue;
2057 }
2058 }
2059
2060 let Some(mut rect) = layout.get_cursor_rect(cursor) else {
2062 continue;
2063 };
2064
2065 rect.origin.x += content_box_offset_x;
2066 rect.origin.y += content_box_offset_y;
2067 rect.size.width = style.width;
2068
2069 builder.push_cursor_rect(rect, style.color);
2070
2071 let is_primary = primary_idx_for_this_node == Some(i);
2073 if is_primary {
2074 if let Some(ref preedit) = self.ctx.preedit_text {
2075 if !preedit.is_empty() {
2076 let char_count = preedit.chars().count() as f32;
2077 let approx_char_width = style.width.max(8.0);
2078 let preedit_width = char_count * approx_char_width;
2079 let underline_bounds = azul_core::geom::LogicalRect {
2080 origin: azul_core::geom::LogicalPosition {
2081 x: rect.origin.x + rect.size.width,
2082 y: rect.origin.y + rect.size.height - 2.0,
2083 },
2084 size: azul_core::geom::LogicalSize {
2085 width: preedit_width,
2086 height: 2.0,
2087 },
2088 };
2089 builder.push_underline(underline_bounds, style.color, 2.0);
2090 }
2091 }
2092 }
2093 }
2094
2095 Ok(())
2096 }
2097
2098 fn paint_selection_and_cursor(
2101 &self,
2102 builder: &mut DisplayListBuilder,
2103 node_index: usize,
2104 ) -> Result<()> {
2105 self.paint_selections(builder, node_index)?;
2106 self.paint_cursor(builder, node_index)?;
2107 Ok(())
2108 }
2109
2110 fn collect_stacking_contexts(&mut self, node_index: usize) -> Result<StackingContext> {
2113 let node = self
2114 .positioned_tree
2115 .tree
2116 .get(node_index)
2117 .ok_or(LayoutError::InvalidTree)?;
2118 let z_index = get_z_index(self.ctx.styled_dom, node.dom_node_id);
2119
2120 if let Some(dom_id) = node.dom_node_id {
2121 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2122 debug_info!(
2123 self.ctx,
2124 "Collecting stacking context for node {} ({:?}), z-index={}",
2125 node_index,
2126 node_type.get_node_type(),
2127 z_index
2128 );
2129 }
2130
2131 let mut child_contexts = Vec::new();
2132 let mut in_flow_children = Vec::new();
2133
2134 for &child_index in self.positioned_tree.tree.children(node_index) {
2135 if self.establishes_stacking_context(child_index) {
2136 child_contexts.push(self.collect_stacking_contexts(child_index)?);
2137 } else {
2138 in_flow_children.push(child_index);
2139 self.find_nested_stacking_contexts(child_index, &mut child_contexts)?;
2143 }
2144 }
2145
2146 Ok(StackingContext {
2147 node_index,
2148 z_index,
2149 child_contexts,
2150 in_flow_children,
2151 })
2152 }
2153
2154 fn find_nested_stacking_contexts(
2157 &mut self,
2158 parent_index: usize,
2159 child_contexts: &mut Vec<StackingContext>,
2160 ) -> Result<()> {
2161 for &child_index in self.positioned_tree.tree.children(parent_index) {
2162 if self.establishes_stacking_context(child_index) {
2163 child_contexts.push(self.collect_stacking_contexts(child_index)?);
2164 } else {
2165 self.find_nested_stacking_contexts(child_index, child_contexts)?;
2166 }
2167 }
2168 Ok(())
2169 }
2170
2171 fn generate_for_stacking_context(
2185 &mut self,
2186 builder: &mut DisplayListBuilder,
2187 context: &StackingContext,
2188 ) -> Result<()> {
2189 let node = self
2191 .positioned_tree
2192 .tree
2193 .get(context.node_index)
2194 .ok_or(LayoutError::InvalidTree)?;
2195
2196 if let Some(dom_id) = node.dom_node_id {
2197 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2198 debug_info!(
2199 self.ctx,
2200 "Painting stacking context for node {} ({:?}), z-index={}, {} child contexts, {} \
2201 in-flow children",
2202 context.node_index,
2203 node_type.get_node_type(),
2204 context.z_index,
2205 context.child_contexts.len(),
2206 context.in_flow_children.len()
2207 );
2208 }
2209
2210 builder.set_current_node(node.dom_node_id);
2214
2215 let is_fixed_position = node.dom_node_id
2217 .map(|dom_id| get_position_type(self.ctx.styled_dom, Some(dom_id)) == LayoutPosition::Fixed)
2218 .unwrap_or(false);
2219 if is_fixed_position {
2220 builder.begin_fixed_position_element();
2221 }
2222
2223 let has_reference_frame = node.dom_node_id.and_then(|dom_id| {
2226 self.gpu_value_cache.and_then(|cache| {
2227 let key = cache.css_transform_keys.get(&dom_id)?;
2228 let transform = cache.css_current_transform_values.get(&dom_id)?;
2229 Some((*key, *transform))
2230 })
2231 });
2232
2233 let node_pos = self
2236 .positioned_tree
2237 .calculated_positions
2238 .get(context.node_index)
2239 .copied()
2240 .unwrap_or_default();
2241 let node_size = node.used_size.unwrap_or(LogicalSize {
2242 width: 0.0,
2243 height: 0.0,
2244 });
2245 let node_bounds = LogicalRect {
2246 origin: node_pos,
2247 size: node_size,
2248 };
2249
2250 if let Some((transform_key, initial_transform)) = has_reference_frame {
2252 builder.push_reference_frame(transform_key, initial_transform, node_bounds);
2253 }
2254
2255 builder.push_stacking_context(context.z_index, node_bounds);
2256
2257 let mut pushed_opacity = false;
2259 let mut pushed_filter = false;
2260 let mut pushed_backdrop_filter = false;
2261
2262 if let Some(dom_id) = node.dom_node_id {
2263 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2264 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2265
2266 let opacity = crate::solver3::getters::get_opacity(
2268 self.ctx.styled_dom, dom_id, node_state,
2269 );
2270
2271 if opacity < 1.0 {
2272 builder.push_item(DisplayListItem::PushOpacity {
2273 bounds: node_bounds.into(),
2274 opacity,
2275 });
2276 pushed_opacity = true;
2277 }
2278
2279 if let Some(filter_vec_value) = self.ctx.styled_dom.css_property_cache.ptr
2281 .get_filter(node_data, &dom_id, node_state)
2282 {
2283 if let Some(filter_vec) = filter_vec_value.get_property() {
2284 let filters: Vec<_> = filter_vec.as_ref().to_vec();
2285 if !filters.is_empty() {
2286 builder.push_item(DisplayListItem::PushFilter {
2287 bounds: node_bounds.into(),
2288 filters,
2289 });
2290 pushed_filter = true;
2291 }
2292 }
2293 }
2294
2295 if let Some(backdrop_filter_value) = self.ctx.styled_dom.css_property_cache.ptr
2297 .get_backdrop_filter(node_data, &dom_id, node_state)
2298 {
2299 if let Some(filter_vec) = backdrop_filter_value.get_property() {
2300 let filters: Vec<_> = filter_vec.as_ref().to_vec();
2301 if !filters.is_empty() {
2302 builder.push_item(DisplayListItem::PushBackdropFilter {
2303 bounds: node_bounds.into(),
2304 filters,
2305 });
2306 pushed_backdrop_filter = true;
2307 }
2308 }
2309 }
2310 }
2311
2312 let did_push_image_mask = self.push_image_mask_clip(builder, context.node_index);
2315
2316 self.paint_node_background_and_border(builder, context.node_index)?;
2322
2323 if !self.is_node_hidden(context.node_index) {
2330 if let Some(dom_id) = node.dom_node_id {
2331 let styled_node_state = self.get_styled_node_state(dom_id);
2332 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2333 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2334 if overflow_x.is_scroll() || overflow_y.is_scroll() {
2335 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
2336 builder.push_hit_test_area(node_bounds, tag_id);
2337 }
2338 }
2339 }
2340 }
2341
2342 let did_push_clip_or_scroll = self.push_node_clips(builder, context.node_index, node)?;
2349
2350 let mut negative_z_children: Vec<_> = context
2353 .child_contexts
2354 .iter()
2355 .filter(|c| c.z_index < 0)
2356 .collect();
2357 negative_z_children.sort_by_key(|c| c.z_index);
2358 for child in negative_z_children {
2359 self.generate_for_stacking_context(builder, child)?;
2360 }
2361
2362 self.paint_in_flow_descendants(builder, context.node_index, &context.in_flow_children)?;
2364
2365 for child in context.child_contexts.iter().filter(|c| c.z_index == 0) {
2368 self.generate_for_stacking_context(builder, child)?;
2369 }
2370
2371 let mut positive_z_children: Vec<_> = context
2374 .child_contexts
2375 .iter()
2376 .filter(|c| c.z_index > 0)
2377 .collect();
2378
2379 positive_z_children.sort_by_key(|c| c.z_index);
2380
2381 for child in positive_z_children {
2382 self.generate_for_stacking_context(builder, child)?;
2383 }
2384
2385 if did_push_image_mask {
2387 builder.pop_image_mask_clip();
2388 }
2389
2390 if pushed_backdrop_filter {
2392 builder.push_item(DisplayListItem::PopBackdropFilter);
2393 }
2394 if pushed_filter {
2395 builder.push_item(DisplayListItem::PopFilter);
2396 }
2397 if pushed_opacity {
2398 builder.push_item(DisplayListItem::PopOpacity);
2399 }
2400
2401 builder.pop_stacking_context();
2403
2404 if has_reference_frame.is_some() {
2406 builder.pop_reference_frame();
2407 }
2408
2409 if is_fixed_position {
2411 builder.end_fixed_position_element();
2412 }
2413
2414 if did_push_clip_or_scroll {
2418 if let Some(dom_id) = node.dom_node_id {
2420 if self.is_virtual_view_node(dom_id) {
2421 builder.push_virtual_view_placeholder(dom_id, node_bounds, node_bounds);
2422 }
2423 }
2424 self.pop_node_clips(builder, node)?;
2425 } else {
2426 if let Some(dom_id) = node.dom_node_id {
2428 if self.is_virtual_view_node(dom_id) {
2429 builder.push_virtual_view_placeholder(dom_id, node_bounds, node_bounds);
2430 }
2431 }
2432 }
2433
2434 self.paint_scrollbars(builder, context.node_index)?;
2437
2438 Ok(())
2439 }
2440
2441 fn paint_in_flow_descendants(
2443 &mut self,
2444 builder: &mut DisplayListBuilder,
2445 node_index: usize,
2446 children_indices: &[usize],
2447 ) -> Result<()> {
2448 self.paint_selection_and_cursor(builder, node_index)?;
2454
2455 self.paint_node_content(builder, node_index)?;
2457
2458 let mut non_float_children = Vec::new();
2469 let mut float_children = Vec::new();
2470 let mut dragging_children = Vec::new();
2471
2472 for &child_index in children_indices {
2473 if self.establishes_stacking_context(child_index) {
2476 continue;
2477 }
2478 let child_node = self
2479 .positioned_tree
2480 .tree
2481 .get(child_index)
2482 .ok_or(LayoutError::InvalidTree)?;
2483
2484 let is_dragging = if let Some(dom_id) = child_node.dom_node_id {
2486 let styled_node_state = self.get_styled_node_state(dom_id);
2487 styled_node_state.dragging
2488 } else {
2489 false
2490 };
2491
2492 if is_dragging {
2493 dragging_children.push(child_index);
2494 continue;
2495 }
2496
2497 let is_float = if let Some(dom_id) = child_node.dom_node_id {
2499 use crate::solver3::getters::get_float;
2500 let styled_node_state = self.get_styled_node_state(dom_id);
2501 let float_value = get_float(self.ctx.styled_dom, dom_id, &styled_node_state);
2502 !matches!(
2503 float_value.unwrap_or_default(),
2504 azul_css::props::layout::LayoutFloat::None
2505 )
2506 } else {
2507 false
2508 };
2509
2510 if is_float {
2511 float_children.push(child_index);
2512 } else {
2513 non_float_children.push(child_index);
2514 }
2515 }
2516
2517 for child_index in non_float_children {
2519 let child_node = self
2520 .positioned_tree
2521 .tree
2522 .get(child_index)
2523 .ok_or(LayoutError::InvalidTree)?;
2524
2525 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2527 self.gpu_value_cache.and_then(|cache| {
2528 let key = cache.css_transform_keys.get(&dom_id)?;
2529 let transform = cache.css_current_transform_values.get(&dom_id)?;
2530 Some((*key, *transform))
2531 })
2532 });
2533
2534 if let Some((transform_key, initial_transform)) = child_ref_frame {
2536 let child_pos = self
2537 .positioned_tree
2538 .calculated_positions
2539 .get(child_index)
2540 .copied()
2541 .unwrap_or_default();
2542 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2543 width: 0.0,
2544 height: 0.0,
2545 });
2546 let child_bounds = LogicalRect {
2547 origin: child_pos,
2548 size: child_size,
2549 };
2550 builder.set_current_node(child_node.dom_node_id);
2551 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2552 }
2553
2554 let did_push_child_image_mask = self.push_image_mask_clip(builder, child_index);
2556
2557 self.paint_node_background_and_border(builder, child_index)?;
2561
2562 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2564
2565 self.paint_in_flow_descendants(builder, child_index, self.positioned_tree.tree.children(child_index))?;
2567
2568 if let Some(dom_id) = child_node.dom_node_id {
2570 if self.is_virtual_view_node(dom_id) {
2571 let child_bounds = self.get_paint_rect(child_index).unwrap_or_default();
2572 builder.push_virtual_view_placeholder(dom_id, child_bounds, child_bounds);
2573 }
2574 }
2575
2576 if did_push_clip {
2578 self.pop_node_clips(builder, child_node)?;
2579 }
2580
2581 if did_push_child_image_mask {
2583 builder.pop_image_mask_clip();
2584 }
2585
2586 self.paint_scrollbars(builder, child_index)?;
2588
2589 if child_ref_frame.is_some() {
2591 builder.pop_reference_frame();
2592 }
2593 }
2594
2595 for child_index in float_children {
2598 let child_node = self
2599 .positioned_tree
2600 .tree
2601 .get(child_index)
2602 .ok_or(LayoutError::InvalidTree)?;
2603
2604 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2606 self.gpu_value_cache.and_then(|cache| {
2607 let key = cache.css_transform_keys.get(&dom_id)?;
2608 let transform = cache.css_current_transform_values.get(&dom_id)?;
2609 Some((*key, *transform))
2610 })
2611 });
2612
2613 if let Some((transform_key, initial_transform)) = child_ref_frame {
2615 let child_pos = self
2616 .positioned_tree
2617 .calculated_positions
2618 .get(child_index)
2619 .copied()
2620 .unwrap_or_default();
2621 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2622 width: 0.0,
2623 height: 0.0,
2624 });
2625 let child_bounds = LogicalRect {
2626 origin: child_pos,
2627 size: child_size,
2628 };
2629 builder.set_current_node(child_node.dom_node_id);
2630 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2631 }
2632
2633 let did_push_child_image_mask = self.push_image_mask_clip(builder, child_index);
2635 self.paint_node_background_and_border(builder, child_index)?;
2636 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2637 self.paint_in_flow_descendants(builder, child_index, self.positioned_tree.tree.children(child_index))?;
2638
2639 if let Some(dom_id) = child_node.dom_node_id {
2641 if self.is_virtual_view_node(dom_id) {
2642 let child_bounds = self.get_paint_rect(child_index).unwrap_or_default();
2643 builder.push_virtual_view_placeholder(dom_id, child_bounds, child_bounds);
2644 }
2645 }
2646
2647 if did_push_clip {
2648 self.pop_node_clips(builder, child_node)?;
2649 }
2650 if did_push_child_image_mask {
2651 builder.pop_image_mask_clip();
2652 }
2653
2654 self.paint_scrollbars(builder, child_index)?;
2656
2657 if child_ref_frame.is_some() {
2659 builder.pop_reference_frame();
2660 }
2661 }
2662
2663 for child_index in dragging_children {
2665 let child_node = self
2666 .positioned_tree
2667 .tree
2668 .get(child_index)
2669 .ok_or(LayoutError::InvalidTree)?;
2670
2671 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2673 self.gpu_value_cache.and_then(|cache| {
2674 let key = cache.css_transform_keys.get(&dom_id)?;
2675 let transform = cache.css_current_transform_values.get(&dom_id)?;
2676 Some((*key, *transform))
2677 })
2678 });
2679
2680 if let Some((transform_key, initial_transform)) = child_ref_frame {
2682 let child_pos = self
2683 .positioned_tree
2684 .calculated_positions
2685 .get(child_index)
2686 .copied()
2687 .unwrap_or_default();
2688 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2689 width: 0.0,
2690 height: 0.0,
2691 });
2692 let child_bounds = LogicalRect {
2693 origin: child_pos,
2694 size: child_size,
2695 };
2696 builder.set_current_node(child_node.dom_node_id);
2697 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2698 }
2699
2700 let did_push_child_image_mask = self.push_image_mask_clip(builder, child_index);
2702 self.paint_node_background_and_border(builder, child_index)?;
2703 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2704 self.paint_in_flow_descendants(builder, child_index, self.positioned_tree.tree.children(child_index))?;
2705
2706 if let Some(dom_id) = child_node.dom_node_id {
2708 if self.is_virtual_view_node(dom_id) {
2709 let child_bounds = self.get_paint_rect(child_index).unwrap_or_default();
2710 builder.push_virtual_view_placeholder(dom_id, child_bounds, child_bounds);
2711 }
2712 }
2713
2714 if did_push_clip {
2715 self.pop_node_clips(builder, child_node)?;
2716 }
2717 if did_push_child_image_mask {
2718 builder.pop_image_mask_clip();
2719 }
2720
2721 self.paint_scrollbars(builder, child_index)?;
2723
2724 if child_ref_frame.is_some() {
2726 builder.pop_reference_frame();
2727 }
2728 }
2729
2730 Ok(())
2731 }
2732
2733 fn is_virtual_view_node(&self, dom_id: NodeId) -> bool {
2735 let node_data_container = self.ctx.styled_dom.node_data.as_container();
2736 node_data_container
2737 .get(dom_id)
2738 .map(|nd| matches!(nd.get_node_type(), NodeType::VirtualView))
2739 .unwrap_or(false)
2740 }
2741
2742 fn push_image_mask_clip(
2745 &self,
2746 builder: &mut DisplayListBuilder,
2747 node_index: usize,
2748 ) -> bool {
2749 let node = match self.positioned_tree.tree.get(node_index) {
2750 Some(n) => n,
2751 None => return false,
2752 };
2753 let dom_id = match node.dom_node_id {
2754 Some(id) => id,
2755 None => return false,
2756 };
2757 let node_data_container = self.ctx.styled_dom.node_data.as_container();
2758 let node_data = match node_data_container.get(dom_id) {
2759 Some(nd) => nd,
2760 None => return false,
2761 };
2762 match node_data.get_svg_data() {
2763 Some(azul_core::dom::SvgNodeData::ImageClipMask(clip_mask)) => {
2764 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2765 let mask_rect = LogicalRect {
2767 origin: LogicalPosition {
2768 x: paint_rect.origin.x + clip_mask.rect.origin.x,
2769 y: paint_rect.origin.y + clip_mask.rect.origin.y,
2770 },
2771 size: clip_mask.rect.size,
2772 };
2773 builder.push_image_mask_clip(
2774 paint_rect,
2775 clip_mask.image.clone(),
2776 mask_rect,
2777 );
2778 true
2779 }
2780 #[cfg(feature = "cpurender")]
2781 Some(azul_core::dom::SvgNodeData::Path(svg_clip)) => {
2782 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2783 if let Some(mask_image) = rasterize_svg_clip_to_r8(svg_clip, &paint_rect) {
2784 builder.push_image_mask_clip(paint_rect, mask_image, paint_rect);
2785 true
2786 } else {
2787 false
2788 }
2789 }
2790 #[cfg(not(feature = "cpurender"))]
2791 Some(azul_core::dom::SvgNodeData::Path(_)) => false,
2792 Some(_) => false,
2794 None => false,
2795 }
2796 }
2797
2798 fn push_node_clips(
2813 &self,
2814 builder: &mut DisplayListBuilder,
2815 node_index: usize,
2816 node: &LayoutNodeHot,
2817 ) -> Result<bool> {
2818 let Some(dom_id) = node.dom_node_id else {
2819 return Ok(false);
2820 };
2821
2822 let styled_node_state = self.get_styled_node_state(dom_id);
2823
2824 let raw_overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2825 let raw_overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2826 let overflow_x = raw_overflow_x.resolve_computed(&raw_overflow_y);
2828 let overflow_y = raw_overflow_y.resolve_computed(&raw_overflow_x);
2829
2830 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2831 let element_size = PhysicalSizeImport {
2832 width: paint_rect.size.width,
2833 height: paint_rect.size.height,
2834 };
2835 let border_radius = get_border_radius(
2836 self.ctx.styled_dom,
2837 dom_id,
2838 &styled_node_state,
2839 element_size,
2840 self.ctx.viewport_size,
2841 );
2842
2843 let has_clip_path = if let Some(clip_path) = super::getters::get_clip_path(
2849 self.ctx.styled_dom, dom_id, &styled_node_state,
2850 ) {
2851 if let Some((clip_rect, radius)) = resolve_clip_path(&clip_path, paint_rect) {
2852 let br = if radius > 0.0 {
2853 BorderRadius {
2854 top_left: radius,
2855 top_right: radius,
2856 bottom_left: radius,
2857 bottom_right: radius,
2858 }
2859 } else {
2860 BorderRadius::default()
2861 };
2862 builder.push_clip(clip_rect, br);
2863 true
2864 } else {
2865 false
2866 }
2867 } else {
2868 false
2869 };
2870
2871 let needs_clip = overflow_x.is_clipped() || overflow_y.is_clipped();
2874
2875 if !needs_clip {
2876 return Ok(has_clip_path);
2877 }
2878
2879 let ox_clip = overflow_x.is_clipped() && !overflow_x.is_scroll() && !overflow_x.is_auto_overflow();
2884 let oy_clip = overflow_y.is_clipped() && !overflow_y.is_scroll() && !overflow_y.is_auto_overflow();
2885 let ox_visible = !overflow_x.is_clipped();
2886 let oy_visible = !overflow_y.is_clipped();
2887 let border_radius = if (ox_clip && oy_visible) || (oy_clip && ox_visible)
2888 {
2889 BorderRadius::default()
2890 } else {
2891 border_radius
2892 };
2893
2894 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2895
2896 let bp = node.box_props.unpack();
2897 let border = &bp.border;
2898
2899 let scrollbar_info = self.positioned_tree.tree.warm(node_index)
2901 .and_then(|w| w.scrollbar_info.clone())
2902 .unwrap_or_default();
2903
2904 let mut clip_rect = LogicalRect {
2912 origin: LogicalPosition {
2913 x: paint_rect.origin.x + border.left,
2914 y: paint_rect.origin.y + border.top,
2915 },
2916 size: LogicalSize {
2917 width: (paint_rect.size.width
2919 - border.left
2920 - border.right
2921 - scrollbar_info.scrollbar_width)
2922 .max(0.0),
2923 height: (paint_rect.size.height
2924 - border.top
2925 - border.bottom
2926 - scrollbar_info.scrollbar_height)
2927 .max(0.0),
2928 },
2929 };
2930
2931 apply_overflow_clip_margin(
2935 &mut clip_rect,
2936 &overflow_x,
2937 &overflow_y,
2938 self.ctx.styled_dom,
2939 dom_id,
2940 &styled_node_state,
2941 );
2942
2943 let is_virtual_view = self.is_virtual_view_node(dom_id);
2944
2945 if overflow_x.is_scroll() || overflow_y.is_scroll() {
2948 if is_virtual_view {
2949 builder.push_clip(clip_rect, border_radius);
2954 } else {
2955 builder.push_clip(clip_rect, border_radius);
2959 let scroll_id = self.scroll_ids.get(&node_index).copied().unwrap_or(0);
2960 let content_size = get_scroll_content_size(node, self.positioned_tree.tree.warm(node_index));
2961 builder.push_scroll_frame(clip_rect, content_size, scroll_id);
2962 }
2963 } else {
2964 builder.push_clip(clip_rect, border_radius);
2966 }
2967
2968 Ok(true)
2969 }
2970
2971 fn pop_node_clips(&self, builder: &mut DisplayListBuilder, node: &LayoutNodeHot) -> Result<()> {
2973 let Some(dom_id) = node.dom_node_id else {
2974 return Ok(());
2975 };
2976
2977 let styled_node_state = self.get_styled_node_state(dom_id);
2978 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2979 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2980
2981 let paint_rect = self
2982 .get_paint_rect(
2983 self.positioned_tree
2984 .tree
2985 .nodes
2986 .iter()
2987 .position(|n| n.dom_node_id == Some(dom_id))
2988 .unwrap_or(0),
2989 )
2990 .unwrap_or_default();
2991
2992 let element_size = PhysicalSizeImport {
2993 width: paint_rect.size.width,
2994 height: paint_rect.size.height,
2995 };
2996 let border_radius = get_border_radius(
2997 self.ctx.styled_dom,
2998 dom_id,
2999 &styled_node_state,
3000 element_size,
3001 self.ctx.viewport_size,
3002 );
3003
3004 let needs_clip =
3005 overflow_x.is_clipped() || overflow_y.is_clipped();
3006
3007 let is_virtual_view = self.is_virtual_view_node(dom_id);
3008
3009 if needs_clip {
3010 if (overflow_x.is_scroll() || overflow_y.is_scroll()) && !is_virtual_view {
3011 builder.pop_scroll_frame();
3013 builder.pop_clip();
3014 } else {
3015 builder.pop_clip();
3017 }
3018 }
3019
3020 if let Some(clip_path) = super::getters::get_clip_path(
3025 self.ctx.styled_dom, dom_id, &styled_node_state,
3026 ) {
3027 if resolve_clip_path(&clip_path, paint_rect).is_some() {
3028 builder.pop_clip();
3029 }
3030 }
3031
3032 Ok(())
3033 }
3034
3035 fn get_paint_rect(&self, node_index: usize) -> Option<LogicalRect> {
3053 let node = self.positioned_tree.tree.get(node_index)?;
3054 let pos = self
3055 .positioned_tree
3056 .calculated_positions
3057 .get(node_index)
3058 .copied()
3059 .unwrap_or_default();
3060 let size = node.used_size.unwrap_or_default();
3061
3062 Some(LogicalRect::new(pos, size))
3067 }
3068
3069 fn paint_node_background_and_border(
3071 &mut self,
3072 builder: &mut DisplayListBuilder,
3073 node_index: usize,
3074 ) -> Result<()> {
3075 let Some(paint_rect) = self.get_paint_rect(node_index) else {
3076 return Ok(());
3077 };
3078 let node = self
3079 .positioned_tree
3080 .tree
3081 .get(node_index)
3082 .ok_or(LayoutError::InvalidTree)?;
3083
3084 builder.set_current_node(node.dom_node_id);
3086
3087 if let Some(dom_id) = node.dom_node_id {
3090 let break_before = get_break_before(self.ctx.styled_dom, Some(dom_id));
3091 let break_after = get_break_after(self.ctx.styled_dom, Some(dom_id));
3092
3093 if is_forced_page_break(break_before) {
3095 let y_position = paint_rect.origin.y;
3096 builder.add_forced_page_break(y_position);
3097 debug_info!(
3098 self.ctx,
3099 "Registered forced page break BEFORE node {} at y={}",
3100 node_index,
3101 y_position
3102 );
3103 }
3104
3105 if is_forced_page_break(break_after) {
3107 let y_position = paint_rect.origin.y + paint_rect.size.height;
3108 builder.add_forced_page_break(y_position);
3109 debug_info!(
3110 self.ctx,
3111 "Registered forced page break AFTER node {} at y={}",
3112 node_index,
3113 y_position
3114 );
3115 }
3116 }
3117
3118 if self.is_node_hidden(node_index) {
3122 return Ok(());
3123 }
3124
3125 let warm = self.positioned_tree.tree.warm(node_index);
3134 let parent_is_flex_or_grid = warm
3135 .and_then(|w| w.parent_formatting_context.as_ref().map(|fc| matches!(fc, FormattingContext::Flex | FormattingContext::Grid)))
3136 .unwrap_or(false);
3137
3138 if let Some(dom_id) = node.dom_node_id {
3139 let display = {
3140 use crate::solver3::getters::get_display_property;
3141 get_display_property(self.ctx.styled_dom, Some(dom_id))
3142 .unwrap_or(LayoutDisplay::Inline)
3143 };
3144
3145 if display == LayoutDisplay::InlineBlock || display == LayoutDisplay::Inline {
3146 debug_info!(
3147 self.ctx,
3148 "[paint_node] node {} has display={:?}, parent_formatting_context={:?}, parent_is_flex_or_grid={}",
3149 node_index,
3150 display,
3151 warm.and_then(|w| w.parent_formatting_context.as_ref()),
3152 parent_is_flex_or_grid
3153 );
3154
3155 if !parent_is_flex_or_grid {
3156 if display == LayoutDisplay::InlineBlock
3165 && self.establishes_stacking_context(node_index)
3166 {
3167 } else {
3169 return Ok(());
3170 }
3171 }
3172 }
3174 }
3175
3176 if matches!(node.formatting_context,
3185 FormattingContext::TableRowGroup | FormattingContext::TableRow |
3186 FormattingContext::TableColumnGroup
3187 ) {
3188 return Ok(());
3189 }
3190
3191 if matches!(node.formatting_context, FormattingContext::Table) {
3193 debug_info!(
3194 self.ctx,
3195 "Painting table backgrounds/borders for node {} at {:?}",
3196 node_index,
3197 paint_rect
3198 );
3199 return self.paint_table_items(builder, node_index);
3201 }
3202
3203 if let Some(dom_id) = node.dom_node_id {
3204 let styled_node_state = self.get_styled_node_state(dom_id);
3205 let background_contents =
3206 get_background_contents(self.ctx.styled_dom, dom_id, &styled_node_state);
3207 let border_info = get_border_info(self.ctx.styled_dom, dom_id, &styled_node_state);
3208
3209 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3210 debug_info!(
3211 self.ctx,
3212 "Painting background/border for node {} ({:?}) at {:?}, backgrounds={:?}",
3213 node_index,
3214 node_type.get_node_type(),
3215 paint_rect,
3216 background_contents.len()
3217 );
3218
3219 let element_size = PhysicalSizeImport {
3222 width: paint_rect.size.width,
3223 height: paint_rect.size.height,
3224 };
3225 let simple_border_radius = get_border_radius(
3226 self.ctx.styled_dom,
3227 dom_id,
3228 &styled_node_state,
3229 element_size,
3230 self.ctx.viewport_size,
3231 );
3232 let style_border_radius =
3233 get_style_border_radius(self.ctx.styled_dom, dom_id, &styled_node_state);
3234
3235 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
3237
3238 for shadow in [
3243 super::getters::get_box_shadow_left(self.ctx.styled_dom, dom_id, node_state),
3244 super::getters::get_box_shadow_right(self.ctx.styled_dom, dom_id, node_state),
3245 super::getters::get_box_shadow_top(self.ctx.styled_dom, dom_id, node_state),
3246 super::getters::get_box_shadow_bottom(self.ctx.styled_dom, dom_id, node_state),
3247 ] {
3248 if let Some(shadow) = shadow {
3249 builder.push_item(DisplayListItem::BoxShadow {
3250 bounds: paint_rect.into(),
3251 shadow,
3252 border_radius: simple_border_radius,
3253 });
3254 }
3255 }
3256
3257 builder.push_backgrounds_and_border(
3259 paint_rect,
3260 &background_contents,
3261 &border_info,
3262 simple_border_radius,
3263 style_border_radius,
3264 self.ctx.image_cache,
3265 );
3266
3267 }
3268
3269 Ok(())
3270 }
3271
3272 fn paint_table_items(
3295 &self,
3296 builder: &mut DisplayListBuilder,
3297 table_index: usize,
3298 ) -> Result<()> {
3299 let table_node = self
3300 .positioned_tree
3301 .tree
3302 .get(table_index)
3303 .ok_or(LayoutError::InvalidTree)?;
3304
3305 let Some(table_paint_rect) = self.get_paint_rect(table_index) else {
3306 return Ok(());
3307 };
3308
3309 if let Some(dom_id) = table_node.dom_node_id {
3311 let styled_node_state = self.get_styled_node_state(dom_id);
3312 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
3313 let element_size = PhysicalSizeImport {
3314 width: table_paint_rect.size.width,
3315 height: table_paint_rect.size.height,
3316 };
3317 let border_radius = get_border_radius(
3318 self.ctx.styled_dom,
3319 dom_id,
3320 &styled_node_state,
3321 element_size,
3322 self.ctx.viewport_size,
3323 );
3324
3325 builder.push_rect(table_paint_rect, bg_color, border_radius);
3326 }
3327
3328 for &child_idx in self.positioned_tree.tree.children(table_index) {
3333 let child_node = self.positioned_tree.tree.get(child_idx);
3334 if let Some(node) = child_node {
3335 if matches!(node.formatting_context, FormattingContext::TableColumnGroup) {
3336 self.paint_element_background(builder, child_idx)?;
3338
3339 for &col_idx in self.positioned_tree.tree.children(child_idx) {
3341 self.paint_element_background(builder, col_idx)?;
3342 }
3343 }
3344 }
3345 }
3346
3347 for &child_idx in self.positioned_tree.tree.children(table_index) {
3351 let child_node = self.positioned_tree.tree.get(child_idx);
3352 if let Some(node) = child_node {
3353 match node.formatting_context {
3354 FormattingContext::TableRowGroup => {
3355 self.paint_element_background(builder, child_idx)?;
3357
3358 for &row_idx in self.positioned_tree.tree.children(child_idx) {
3360 self.paint_table_row_and_cells(builder, row_idx)?;
3361 }
3362 }
3363 FormattingContext::TableRow => {
3364 self.paint_table_row_and_cells(builder, child_idx)?;
3366 }
3367 _ => {}
3368 }
3369 }
3370 }
3371
3372 Ok(())
3379 }
3380
3381 fn paint_table_row_and_cells(
3385 &self,
3386 builder: &mut DisplayListBuilder,
3387 row_idx: usize,
3388 ) -> Result<()> {
3389 if let Some(row_node) = self.positioned_tree.tree.get(row_idx) {
3394 if let Some(dom_id) = row_node.dom_node_id {
3395 let styled_node_state = self.get_styled_node_state(dom_id);
3396 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
3397 if bg_color.a > 0 {
3398 let mut min_x = f32::MAX;
3400 let mut min_y = f32::MAX;
3401 let mut max_x = f32::MIN;
3402 let mut max_y = f32::MIN;
3403 for &cell_idx in self.positioned_tree.tree.children(row_idx) {
3404 if let Some(cell_rect) = self.get_paint_rect(cell_idx) {
3405 min_x = min_x.min(cell_rect.origin.x);
3406 min_y = min_y.min(cell_rect.origin.y);
3407 max_x = max_x.max(cell_rect.origin.x + cell_rect.size.width);
3408 max_y = max_y.max(cell_rect.origin.y + cell_rect.size.height);
3409 }
3410 }
3411 if min_x < max_x && min_y < max_y {
3412 let row_rect = LogicalRect::new(
3413 LogicalPosition::new(min_x, min_y),
3414 LogicalSize::new(max_x - min_x, max_y - min_y),
3415 );
3416 builder.push_rect(row_rect, bg_color, BorderRadius::default());
3417 }
3418 }
3419 }
3420 }
3421
3422 if let Some(_node) = self.positioned_tree.tree.get(row_idx) {
3424 for &cell_idx in self.positioned_tree.tree.children(row_idx) {
3425 self.paint_element_background(builder, cell_idx)?;
3426 }
3427 }
3428
3429 Ok(())
3430 }
3431
3432 fn paint_element_background(
3435 &self,
3436 builder: &mut DisplayListBuilder,
3437 node_index: usize,
3438 ) -> Result<()> {
3439 let Some(paint_rect) = self.get_paint_rect(node_index) else {
3440 return Ok(());
3441 };
3442
3443 let Some(node) = self.positioned_tree.tree.get(node_index) else {
3444 return Ok(());
3445 };
3446 let Some(dom_id) = node.dom_node_id else {
3447 return Ok(());
3448 };
3449
3450 let styled_node_state = self.get_styled_node_state(dom_id);
3451 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
3452
3453 if bg_color.a == 0 {
3455 return Ok(());
3456 }
3457
3458 let element_size = PhysicalSizeImport {
3459 width: paint_rect.size.width,
3460 height: paint_rect.size.height,
3461 };
3462 let border_radius = get_border_radius(
3463 self.ctx.styled_dom,
3464 dom_id,
3465 &styled_node_state,
3466 element_size,
3467 self.ctx.viewport_size,
3468 );
3469
3470 builder.push_rect(paint_rect, bg_color, border_radius);
3471
3472 Ok(())
3473 }
3474
3475 fn paint_node_content(
3477 &mut self,
3478 builder: &mut DisplayListBuilder,
3479 node_index: usize,
3480 ) -> Result<()> {
3481 if self.is_node_hidden(node_index) {
3483 return Ok(());
3484 }
3485
3486 let node = self
3487 .positioned_tree
3488 .tree
3489 .get(node_index)
3490 .ok_or(LayoutError::InvalidTree)?;
3491 let node_warm = self.positioned_tree.tree.warm(node_index);
3492
3493 builder.set_current_node(node.dom_node_id);
3495
3496 let Some(mut paint_rect) = self.get_paint_rect(node_index) else {
3497 return Ok(());
3498 };
3499
3500 if paint_rect.size.width == 0.0 || paint_rect.size.height == 0.0 {
3503 if let Some(cached_layout) = node_warm.and_then(|w| w.inline_layout_result.as_ref()) {
3504 let content_bounds = cached_layout.layout.bounds();
3505 paint_rect.size.width = content_bounds.width;
3506 paint_rect.size.height = content_bounds.height;
3507 }
3508 }
3509
3510 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
3515 let is_scrollable = if let Some(dom_id) = node.dom_node_id {
3516 let styled_node_state = self.get_styled_node_state(dom_id);
3517 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
3518 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
3519 overflow_x.is_scroll() || overflow_y.is_scroll()
3520 } else {
3521 false
3522 };
3523
3524 if !is_scrollable {
3530 builder.push_hit_test_area(paint_rect, tag_id);
3531 }
3532 }
3533
3534 if let Some(cached_layout) = node_warm.and_then(|w| w.inline_layout_result.as_ref()) {
3536 let inline_layout = &cached_layout.layout;
3537 debug_info!(
3538 self.ctx,
3539 "[paint_node] node {} has inline_layout with {} items",
3540 node_index,
3541 inline_layout.items.len()
3542 );
3543
3544 if let Some(dom_id) = node.dom_node_id {
3545 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3546 debug_info!(
3547 self.ctx,
3548 "Painting inline content for node {} ({:?}) at {:?}, {} layout items",
3549 node_index,
3550 node_type.get_node_type(),
3551 paint_rect,
3552 inline_layout.items.len()
3553 );
3554 }
3555
3556 let border_box = BorderBoxRect(paint_rect);
3560 let nbp = node.box_props.unpack();
3561 let mut content_box_rect =
3562 border_box.to_content_box(&nbp.padding, &nbp.border).rect();
3563
3564 let viewport_clip_rect = content_box_rect;
3568
3569 let content_size = get_scroll_content_size(node, node_warm);
3573 if content_size.height > content_box_rect.size.height {
3574 content_box_rect.size.height = content_size.height;
3575 }
3576 if content_size.width > content_box_rect.size.width {
3577 content_box_rect.size.width = content_size.width;
3578 }
3579
3580 let mut pushed_text_shadow = false;
3582 if let Some(dom_id) = node.dom_node_id {
3583 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3584 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
3585 if let Some(shadow_val) = self.ctx.styled_dom.css_property_cache.ptr
3586 .get_text_shadow(node_data, &dom_id, node_state)
3587 {
3588 if let Some(shadow) = shadow_val.get_property() {
3589 builder.push_item(DisplayListItem::PushTextShadow {
3590 shadow: (**shadow).clone(),
3591 });
3592 pushed_text_shadow = true;
3593 }
3594 }
3595 }
3596
3597 self.paint_inline_content(builder, content_box_rect, viewport_clip_rect, inline_layout, node_index)?;
3598
3599 if pushed_text_shadow {
3600 builder.push_item(DisplayListItem::PopTextShadow);
3601 }
3602 } else if let Some(dom_id) = node.dom_node_id {
3603 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3607 if let NodeType::Image(image_ref) = node_data.get_node_type() {
3608 debug_info!(
3609 self.ctx,
3610 "Painting image for node {} at {:?}",
3611 node_index,
3612 paint_rect
3613 );
3614 let styled_node_state = self.get_styled_node_state(dom_id);
3616 let element_size = PhysicalSizeImport {
3617 width: paint_rect.size.width,
3618 height: paint_rect.size.height,
3619 };
3620 let border_radius = get_border_radius(
3621 self.ctx.styled_dom,
3622 dom_id,
3623 &styled_node_state,
3624 element_size,
3625 self.ctx.viewport_size,
3626 );
3627 builder.push_image(paint_rect, image_ref.as_ref().clone(), border_radius);
3629 }
3630 }
3631
3632 Ok(())
3633 }
3634
3635 fn paint_scrollbars(&self, builder: &mut DisplayListBuilder, node_index: usize) -> Result<()> {
3638 if self.is_node_hidden(node_index) {
3641 return Ok(());
3642 }
3643
3644 let node = self
3645 .positioned_tree
3646 .tree
3647 .get(node_index)
3648 .ok_or(LayoutError::InvalidTree)?;
3649
3650 let Some(paint_rect) = self.get_paint_rect(node_index) else {
3651 return Ok(());
3652 };
3653
3654 let scrollbar_info = self.positioned_tree.tree.warm(node_index)
3656 .and_then(|w| w.scrollbar_info.clone())
3657 .unwrap_or_default();
3658
3659 let node_id = node.dom_node_id;
3661
3662 let scrollbar_style = node_id
3664 .map(|nid| {
3665 let node_state =
3666 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3667 crate::solver3::getters::get_scrollbar_style_cached(self.ctx, nid, node_state)
3668 })
3669 .unwrap_or_default();
3670
3671 if matches!(
3673 scrollbar_style.width_mode,
3674 azul_css::props::style::scrollbar::LayoutScrollbarWidth::None
3675 ) {
3676 return Ok(());
3677 }
3678
3679 let scrollbar_gutter = node_id
3682 .and_then(|nid| {
3683 let node_state =
3684 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3685 get_scrollbar_gutter_property(self.ctx.styled_dom, nid, node_state).exact()
3686 })
3687 .unwrap_or_default();
3688 let gutter_is_stable = matches!(
3689 scrollbar_gutter,
3690 azul_css::props::layout::overflow::StyleScrollbarGutter::Stable
3691 | azul_css::props::layout::overflow::StyleScrollbarGutter::StableBothEdges
3692 );
3693 let gutter_both_edges = matches!(
3694 scrollbar_gutter,
3695 azul_css::props::layout::overflow::StyleScrollbarGutter::StableBothEdges
3696 );
3697
3698 if gutter_is_stable {
3699 let gbp = node.box_props.unpack();
3700 let border = &gbp.border;
3701 let gutter_width = scrollbar_style.visual_width_px;
3702 let bg_color = node_id
3704 .map(|nid| {
3705 let node_state =
3706 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3707 get_background_color(self.ctx.styled_dom, nid, node_state)
3708 })
3709 .unwrap_or(ColorU::TRANSPARENT);
3710
3711 if !scrollbar_info.needs_vertical && gutter_width > 0.0 {
3712 let gutter_rect = LogicalRect {
3714 origin: LogicalPosition::new(
3715 paint_rect.origin.x + paint_rect.size.width - border.right - gutter_width,
3716 paint_rect.origin.y + border.top,
3717 ),
3718 size: LogicalSize::new(
3719 gutter_width,
3720 (paint_rect.size.height - border.top - border.bottom).max(0.0),
3721 ),
3722 };
3723 builder.push_rect(gutter_rect, bg_color, BorderRadius::default());
3724
3725 if gutter_both_edges {
3727 let left_gutter_rect = LogicalRect {
3728 origin: LogicalPosition::new(
3729 paint_rect.origin.x + border.left,
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(left_gutter_rect, bg_color, BorderRadius::default());
3738 }
3739 }
3740 }
3741
3742 let sbp = node.box_props.unpack();
3744 let border = &sbp.border;
3745
3746 let container_border_radius = node_id
3748 .map(|nid| {
3749 let node_state =
3750 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
3751 let element_size = PhysicalSizeImport {
3752 width: paint_rect.size.width,
3753 height: paint_rect.size.height,
3754 };
3755 let viewport_size =
3756 LogicalSize::new(self.ctx.viewport_size.width, self.ctx.viewport_size.height);
3757 get_border_radius(
3758 self.ctx.styled_dom,
3759 nid,
3760 node_state,
3761 element_size,
3762 viewport_size,
3763 )
3764 })
3765 .unwrap_or_default();
3766
3767 let inner_rect = LogicalRect {
3770 origin: LogicalPosition::new(
3771 paint_rect.origin.x + border.left,
3772 paint_rect.origin.y + border.top,
3773 ),
3774 size: LogicalSize::new(
3775 (paint_rect.size.width - border.left - border.right).max(0.0),
3776 (paint_rect.size.height - border.top - border.bottom).max(0.0),
3777 ),
3778 };
3779
3780 let (scroll_offset_x, scroll_offset_y) = node_id
3784 .and_then(|nid| {
3785 self.scroll_offsets.get(&nid).map(|pos| {
3786 (
3787 pos.children_rect.origin.x - pos.parent_rect.origin.x,
3788 pos.children_rect.origin.y - pos.parent_rect.origin.y,
3789 )
3790 })
3791 })
3792 .unwrap_or((0.0, 0.0));
3793
3794 let content_size = node_id
3800 .and_then(|nid| self.scroll_offsets.get(&nid))
3801 .map(|pos| pos.children_rect.size)
3802 .unwrap_or_else(|| self.positioned_tree.tree.get_content_size(node_index));
3803
3804 let thumb_radius = scrollbar_style.visual_width_px / 2.0;
3806 let thumb_border_radius = BorderRadius {
3807 top_left: thumb_radius,
3808 top_right: thumb_radius,
3809 bottom_left: thumb_radius,
3810 bottom_right: thumb_radius,
3811 };
3812
3813 if scrollbar_info.needs_vertical {
3814 let opacity_key = node_id.map(|nid| {
3821 self.gpu_value_cache
3822 .and_then(|cache| {
3823 cache
3824 .scrollbar_v_opacity_keys
3825 .get(&(self.dom_id, nid))
3826 .copied()
3827 })
3828 .unwrap_or_else(|| OpacityKey::unique())
3829 });
3830
3831 let button_size = if scrollbar_style.show_scroll_buttons {
3833 scrollbar_style.scroll_button_size_px
3834 } else {
3835 0.0
3836 };
3837 let v_geom = compute_scrollbar_geometry_with_button_size(
3838 ScrollbarOrientation::Vertical,
3839 inner_rect,
3840 content_size,
3841 scroll_offset_y,
3842 scrollbar_style.visual_width_px,
3843 scrollbar_info.needs_horizontal,
3844 button_size,
3845 );
3846
3847 let thumb_bounds = LogicalRect {
3849 origin: LogicalPosition::new(
3850 v_geom.track_rect.origin.x,
3851 v_geom.track_rect.origin.y + v_geom.button_size,
3852 ),
3853 size: LogicalSize::new(v_geom.width_px, v_geom.thumb_length),
3854 };
3855
3856 let thumb_transform_key = node_id.map(|nid| {
3861 self.gpu_value_cache
3862 .and_then(|cache| cache.transform_keys.get(&nid).copied())
3863 .unwrap_or_else(|| TransformKey::unique())
3864 });
3865
3866 let thumb_initial_transform =
3868 ComputedTransform3D::new_translation(0.0, v_geom.thumb_offset, 0.0);
3869
3870 let hit_id = node_id
3872 .map(|nid| azul_core::hit_test::ScrollbarHitId::VerticalThumb(self.dom_id, nid));
3873
3874 let (button_decrement_bounds, button_increment_bounds) = if scrollbar_style.show_scroll_buttons && v_geom.button_size > 0.0 {
3876 (
3877 Some(LogicalRect {
3878 origin: v_geom.track_rect.origin,
3879 size: LogicalSize::new(v_geom.button_size, v_geom.button_size),
3880 }),
3881 Some(LogicalRect {
3882 origin: LogicalPosition::new(
3883 v_geom.track_rect.origin.x,
3884 v_geom.track_rect.origin.y + v_geom.track_rect.size.height - v_geom.button_size,
3885 ),
3886 size: LogicalSize::new(v_geom.button_size, v_geom.button_size),
3887 }),
3888 )
3889 } else {
3890 (None, None)
3891 };
3892 builder.push_scrollbar_styled(ScrollbarDrawInfo {
3893 bounds: v_geom.track_rect.into(),
3894 orientation: ScrollbarOrientation::Vertical,
3895 track_bounds: v_geom.track_rect.into(),
3896 track_color: scrollbar_style.track_color,
3897 thumb_bounds: thumb_bounds.into(),
3898 thumb_color: scrollbar_style.thumb_color,
3899 thumb_border_radius,
3900 button_decrement_bounds: button_decrement_bounds.map(|b| b.into()),
3901 button_increment_bounds: button_increment_bounds.map(|b| b.into()),
3902 button_color: scrollbar_style.button_color,
3903 opacity_key,
3904 thumb_transform_key,
3905 thumb_initial_transform,
3906 hit_id,
3907 clip_to_container_border: scrollbar_style.clip_to_container_border,
3908 container_border_radius,
3909 visibility: scrollbar_style.visibility,
3910 });
3911 }
3912
3913 if scrollbar_info.needs_horizontal {
3914 let opacity_key = node_id.map(|nid| {
3916 self.gpu_value_cache
3917 .and_then(|cache| {
3918 cache
3919 .scrollbar_h_opacity_keys
3920 .get(&(self.dom_id, nid))
3921 .copied()
3922 })
3923 .unwrap_or_else(|| OpacityKey::unique())
3924 });
3925
3926 let h_button_size = if scrollbar_style.show_scroll_buttons {
3928 scrollbar_style.scroll_button_size_px
3929 } else {
3930 0.0
3931 };
3932 let h_geom = compute_scrollbar_geometry_with_button_size(
3933 ScrollbarOrientation::Horizontal,
3934 inner_rect,
3935 content_size,
3936 scroll_offset_x,
3937 scrollbar_style.visual_width_px,
3938 scrollbar_info.needs_vertical,
3939 h_button_size,
3940 );
3941
3942 let thumb_bounds = LogicalRect {
3944 origin: LogicalPosition::new(
3945 h_geom.track_rect.origin.x + h_geom.button_size,
3946 h_geom.track_rect.origin.y,
3947 ),
3948 size: LogicalSize::new(h_geom.thumb_length, h_geom.width_px),
3949 };
3950
3951 let thumb_transform_key = node_id.map(|nid| {
3953 self.gpu_value_cache
3954 .and_then(|cache| cache.h_transform_keys.get(&nid).copied())
3955 .unwrap_or_else(|| TransformKey::unique())
3956 });
3957 let thumb_initial_transform =
3958 ComputedTransform3D::new_translation(h_geom.thumb_offset, 0.0, 0.0);
3959
3960 let hit_id = node_id
3962 .map(|nid| azul_core::hit_test::ScrollbarHitId::HorizontalThumb(self.dom_id, nid));
3963
3964 let (button_decrement_bounds, button_increment_bounds) = if scrollbar_style.show_scroll_buttons && h_geom.button_size > 0.0 {
3966 (
3967 Some(LogicalRect {
3968 origin: h_geom.track_rect.origin,
3969 size: LogicalSize::new(h_geom.button_size, h_geom.button_size),
3970 }),
3971 Some(LogicalRect {
3972 origin: LogicalPosition::new(
3973 h_geom.track_rect.origin.x + h_geom.track_rect.size.width - h_geom.button_size,
3974 h_geom.track_rect.origin.y,
3975 ),
3976 size: LogicalSize::new(h_geom.button_size, h_geom.button_size),
3977 }),
3978 )
3979 } else {
3980 (None, None)
3981 };
3982 builder.push_scrollbar_styled(ScrollbarDrawInfo {
3983 bounds: h_geom.track_rect.into(),
3984 orientation: ScrollbarOrientation::Horizontal,
3985 track_bounds: h_geom.track_rect.into(),
3986 track_color: scrollbar_style.track_color,
3987 thumb_bounds: thumb_bounds.into(),
3988 thumb_color: scrollbar_style.thumb_color,
3989 thumb_border_radius,
3990 button_decrement_bounds: button_decrement_bounds.map(|b| b.into()),
3991 button_increment_bounds: button_increment_bounds.map(|b| b.into()),
3992 button_color: scrollbar_style.button_color,
3993 opacity_key,
3994 thumb_transform_key,
3995 thumb_initial_transform,
3996 hit_id,
3997 clip_to_container_border: scrollbar_style.clip_to_container_border,
3998 container_border_radius,
3999 visibility: scrollbar_style.visibility,
4000 });
4001 }
4002
4003 Ok(())
4004 }
4005
4006 fn paint_inline_content(
4008 &self,
4009 builder: &mut DisplayListBuilder,
4010 container_rect: LogicalRect,
4011 viewport_clip_rect: LogicalRect,
4012 layout: &UnifiedLayout,
4013 source_node_index: usize,
4014 ) -> Result<()> {
4015 let layout_bounds = layout.bounds();
4031 let actual_bounds = if layout_bounds.width > 0.0 && layout_bounds.height > 0.0 {
4032 LogicalRect {
4033 origin: container_rect.origin,
4034 size: LogicalSize {
4035 width: layout_bounds.width,
4036 height: layout_bounds.height,
4037 },
4038 }
4039 } else {
4040 LogicalRect {
4043 origin: container_rect.origin,
4044 size: LogicalSize::default(),
4045 }
4046 };
4047
4048 if layout_bounds.width > 0.0 || layout_bounds.height > 0.0 {
4052 builder.push_text_layout(
4053 Arc::new(layout.clone()) as Arc<dyn std::any::Any + Send + Sync>,
4054 actual_bounds,
4055 FontHash::from_hash(0), 12.0, ColorU {
4058 r: 0,
4059 g: 0,
4060 b: 0,
4061 a: 255,
4062 }, );
4064 }
4065
4066 let glyph_runs = crate::text3::glyphs::get_glyph_runs_simple(layout);
4067
4068 for glyph_run in glyph_runs.iter() {
4071 if let (Some(first_glyph), Some(last_glyph)) =
4073 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
4074 {
4075 let run_start_x = container_rect.origin.x + first_glyph.point.x;
4077 let run_end_x = container_rect.origin.x + last_glyph.point.x;
4078 let run_width = (run_end_x - run_start_x).max(0.0);
4079
4080 if run_width <= 0.0 {
4082 continue;
4083 }
4084
4085 let baseline_y = container_rect.origin.y + first_glyph.point.y;
4087 let font_size = glyph_run.font_size_px;
4088 let ascent = font_size * 0.8; let mut run_bounds = LogicalRect::new(
4091 LogicalPosition::new(run_start_x, baseline_y - ascent),
4092 LogicalSize::new(run_width, font_size),
4093 );
4094
4095 if let Some(border) = &glyph_run.border {
4098 let left_inset = border.left_inset();
4099 let right_inset = border.right_inset();
4100 let top_inset = border.top_inset();
4101 let bottom_inset = border.bottom_inset();
4102
4103 run_bounds.origin.x -= left_inset;
4104 run_bounds.origin.y -= top_inset;
4105 run_bounds.size.width += left_inset + right_inset;
4106 run_bounds.size.height += top_inset + bottom_inset;
4107 }
4108
4109 builder.push_inline_backgrounds_and_border(
4110 run_bounds,
4111 glyph_run.background_color,
4112 &glyph_run.background_content,
4113 glyph_run.border.as_ref(),
4114 self.ctx.image_cache,
4115 );
4116 }
4117 }
4118
4119 for (_idx, glyph_run) in glyph_runs.iter().enumerate() {
4121 let clip_rect = viewport_clip_rect;
4125
4126 let offset_glyphs: Vec<GlyphInstance> = glyph_run
4129 .glyphs
4130 .iter()
4131 .map(|g| {
4132 let mut g = g.clone();
4133 g.point.x += container_rect.origin.x;
4134 g.point.y += container_rect.origin.y;
4135 g
4136 })
4137 .collect();
4138
4139 builder.push_text_run(
4141 offset_glyphs,
4142 FontHash::from_hash(glyph_run.font_hash),
4143 glyph_run.font_size_px,
4144 glyph_run.color,
4145 clip_rect,
4146 Some(source_node_index),
4147 );
4148
4149 let needs_underline = glyph_run.text_decoration.underline || glyph_run.is_ime_preview;
4151 let needs_strikethrough = glyph_run.text_decoration.strikethrough;
4152 let needs_overline = glyph_run.text_decoration.overline;
4153
4154 if needs_underline || needs_strikethrough || needs_overline {
4155 if let (Some(first_glyph), Some(last_glyph)) =
4157 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
4158 {
4159 let decoration_start_x = container_rect.origin.x + first_glyph.point.x;
4160 let decoration_end_x = container_rect.origin.x + last_glyph.point.x;
4161 let decoration_width = decoration_end_x - decoration_start_x;
4162
4163 let font_size = glyph_run.font_size_px;
4166 let thickness = (font_size * 0.08).max(1.0); let baseline_y = container_rect.origin.y + first_glyph.point.y;
4170
4171 if needs_underline {
4172 let underline_y = baseline_y + (font_size * 0.12);
4175 let underline_bounds = LogicalRect::new(
4176 LogicalPosition::new(decoration_start_x, underline_y),
4177 LogicalSize::new(decoration_width, thickness),
4178 );
4179 builder.push_underline(underline_bounds, glyph_run.color, thickness);
4180 }
4181
4182 if needs_strikethrough {
4183 let strikethrough_y = baseline_y - (font_size * 0.3);
4185 let strikethrough_bounds = LogicalRect::new(
4186 LogicalPosition::new(decoration_start_x, strikethrough_y),
4187 LogicalSize::new(decoration_width, thickness),
4188 );
4189 builder.push_strikethrough(
4190 strikethrough_bounds,
4191 glyph_run.color,
4192 thickness,
4193 );
4194 }
4195
4196 if needs_overline {
4197 let overline_y = baseline_y - (font_size * 0.85);
4199 let overline_bounds = LogicalRect::new(
4200 LogicalPosition::new(decoration_start_x, overline_y),
4201 LogicalSize::new(decoration_width, thickness),
4202 );
4203 builder.push_overline(overline_bounds, glyph_run.color, thickness);
4204 }
4205 }
4206 }
4207 }
4208
4209 for glyph_run in glyph_runs.iter() {
4212 let Some(source_node_id) = glyph_run.source_node_id else {
4214 continue;
4215 };
4216
4217 if let (Some(first_glyph), Some(last_glyph)) =
4219 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
4220 {
4221 let run_start_x = container_rect.origin.x + first_glyph.point.x;
4222 let run_end_x = container_rect.origin.x + last_glyph.point.x;
4223 let run_width = (run_end_x - run_start_x).max(0.0);
4224
4225 if run_width <= 0.0 {
4227 continue;
4228 }
4229
4230 let baseline_y = container_rect.origin.y + first_glyph.point.y;
4232 let font_size = glyph_run.font_size_px;
4233 let ascent = font_size * 0.8; let run_bounds = LogicalRect::new(
4236 LogicalPosition::new(run_start_x, baseline_y - ascent),
4237 LogicalSize::new(run_width, font_size),
4238 );
4239
4240 let cursor_type = self.get_cursor_type_for_text_node(source_node_id);
4243
4244 let tag_value = ((self.dom_id.inner as u64) << 32) | (source_node_id.index() as u64);
4248 let tag_type = TAG_TYPE_CURSOR | (cursor_type as u16);
4249 let tag_id = (tag_value, tag_type);
4250
4251 builder.push_hit_test_area(run_bounds, tag_id);
4252 }
4253 }
4254
4255 for positioned_item in &layout.items {
4259 self.paint_inline_object(builder, container_rect.origin, positioned_item)?;
4260 }
4261 Ok(())
4262 }
4263
4264 fn paint_inline_object(
4266 &self,
4267 builder: &mut DisplayListBuilder,
4268 base_pos: LogicalPosition,
4269 positioned_item: &PositionedItem,
4270 ) -> Result<()> {
4271 let ShapedItem::Object {
4272 content, bounds, ..
4273 } = &positioned_item.item
4274 else {
4275 return Ok(());
4277 };
4278
4279 let object_bounds = LogicalRect::new(
4282 LogicalPosition::new(
4283 base_pos.x + positioned_item.position.x,
4284 base_pos.y + positioned_item.position.y,
4285 ),
4286 LogicalSize::new(bounds.width, bounds.height),
4287 );
4288
4289 match content {
4290 InlineContent::Image(image) => {
4291 if let Some(image_ref) = get_image_ref_for_image_source(&image.source) {
4292 builder.push_image(object_bounds, image_ref, BorderRadius::default());
4293 }
4294 }
4295 InlineContent::Shape(shape) => {
4296 self.paint_inline_shape(builder, object_bounds, shape, bounds)?;
4297 }
4298 _ => {}
4299 }
4300 Ok(())
4301 }
4302
4303 fn paint_inline_shape(
4306 &self,
4307 builder: &mut DisplayListBuilder,
4308 object_bounds: LogicalRect,
4309 shape: &InlineShape,
4310 bounds: &crate::text3::cache::Rect,
4311 ) -> Result<()> {
4312 let Some(node_id) = shape.source_node_id else {
4315 return Ok(());
4316 };
4317
4318 if let Some(indices) = self.positioned_tree.tree.dom_to_layout.get(&node_id) {
4323 if let Some(&idx) = indices.first() {
4324 if self.establishes_stacking_context(idx) {
4325 return Ok(());
4326 }
4327 }
4328 }
4329
4330 let styled_node_state =
4331 &self.ctx.styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
4332
4333 let background_contents =
4335 get_background_contents(self.ctx.styled_dom, node_id, styled_node_state);
4336
4337 let border_info = get_border_info(self.ctx.styled_dom, node_id, styled_node_state);
4339
4340 let margins = if let Some(indices) = self.positioned_tree.tree.dom_to_layout.get(&node_id) {
4343 if let Some(&idx) = indices.first() {
4344 self.positioned_tree.tree.nodes[idx].box_props.unpack().margin
4345 } else {
4346 Default::default()
4347 }
4348 } else {
4349 Default::default()
4350 };
4351
4352 let border_box_bounds = LogicalRect {
4354 origin: LogicalPosition {
4355 x: object_bounds.origin.x + margins.left,
4356 y: object_bounds.origin.y + margins.top,
4357 },
4358 size: LogicalSize {
4359 width: (object_bounds.size.width - margins.left - margins.right).max(0.0),
4360 height: (object_bounds.size.height - margins.top - margins.bottom).max(0.0),
4361 },
4362 };
4363
4364 let element_size = PhysicalSizeImport {
4365 width: border_box_bounds.size.width,
4366 height: border_box_bounds.size.height,
4367 };
4368
4369 let simple_border_radius = get_border_radius(
4371 self.ctx.styled_dom,
4372 node_id,
4373 styled_node_state,
4374 element_size,
4375 self.ctx.viewport_size,
4376 );
4377
4378 let style_border_radius =
4380 get_style_border_radius(self.ctx.styled_dom, node_id, styled_node_state);
4381
4382 builder.push_backgrounds_and_border(
4384 border_box_bounds,
4385 &background_contents,
4386 &border_info,
4387 simple_border_radius,
4388 style_border_radius,
4389 self.ctx.image_cache,
4390 );
4391
4392 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, Some(node_id)) {
4396 builder.push_hit_test_area(border_box_bounds, tag_id);
4397 }
4398
4399 Ok(())
4400 }
4401
4402 fn establishes_stacking_context(&self, node_index: usize) -> bool {
4409 let Some(node) = self.positioned_tree.tree.get(node_index) else {
4410 return false;
4411 };
4412 let Some(dom_id) = node.dom_node_id else {
4413 return false;
4414 };
4415
4416 let position = get_position_type(self.ctx.styled_dom, Some(dom_id));
4417 let z_auto = crate::solver3::getters::is_z_index_auto(self.ctx.styled_dom, Some(dom_id));
4418
4419 if position == LayoutPosition::Fixed || position == LayoutPosition::Sticky {
4421 return true;
4422 }
4423
4424 if position == LayoutPosition::Absolute {
4427 return !z_auto;
4428 }
4429
4430 if position == LayoutPosition::Relative && !z_auto {
4432 return true;
4433 }
4434
4435 if let Some(styled_node) = self.ctx.styled_dom.styled_nodes.as_container().get(dom_id) {
4436 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
4437 let node_state =
4438 &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
4439
4440 if crate::solver3::getters::get_opacity(
4442 self.ctx.styled_dom, dom_id, node_state,
4443 ) < 1.0 {
4444 return true;
4445 }
4446
4447 if let Some(t) = crate::solver3::getters::get_transform(
4449 self.ctx.styled_dom, dom_id, node_state,
4450 ) {
4451 if !t.is_empty() {
4452 return true;
4453 }
4454 }
4455 }
4456
4457 false
4458 }
4459}
4460
4461pub fn node_establishes_stacking_context(
4469 styled_dom: &StyledDom,
4470 dom_id: NodeId,
4471) -> bool {
4472 let position = crate::solver3::positioning::get_position_type(styled_dom, Some(dom_id));
4473 let z_auto = crate::solver3::getters::is_z_index_auto(styled_dom, Some(dom_id));
4474
4475 if position == LayoutPosition::Fixed || position == LayoutPosition::Sticky {
4477 return true;
4478 }
4479 if position == LayoutPosition::Absolute && !z_auto {
4481 return true;
4482 }
4483 if position == LayoutPosition::Relative && !z_auto {
4485 return true;
4486 }
4487
4488 let node_state = &styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
4489
4490 if crate::solver3::getters::get_opacity(styled_dom, dom_id, node_state) < 1.0 {
4492 return true;
4493 }
4494
4495 if let Some(t) = crate::solver3::getters::get_transform(styled_dom, dom_id, node_state) {
4497 if !t.is_empty() {
4498 return true;
4499 }
4500 }
4501
4502 false
4503}
4504
4505pub struct PositionedTree<'a> {
4511 pub tree: &'a LayoutTree,
4513 pub calculated_positions: &'a super::PositionVec,
4515}
4516
4517#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4523pub enum OverflowBehavior {
4524 Visible,
4526 Hidden,
4528 Clip,
4530 Scroll,
4532 Auto,
4534}
4535
4536impl OverflowBehavior {
4537 pub fn is_clipped(&self) -> bool {
4542 matches!(self, Self::Hidden | Self::Clip | Self::Scroll | Self::Auto)
4543 }
4544
4545 pub fn is_scroll(&self) -> bool {
4550 matches!(self, Self::Scroll | Self::Auto)
4551 }
4552}
4553
4554fn apply_overflow_clip_margin(
4559 clip_rect: &mut LogicalRect,
4560 overflow_x: &super::getters::MultiValue<LayoutOverflow>,
4561 overflow_y: &super::getters::MultiValue<LayoutOverflow>,
4562 styled_dom: &StyledDom,
4563 dom_id: NodeId,
4564 styled_node_state: &azul_core::styled_dom::StyledNodeState,
4565) {
4566 if !overflow_x.is_clip() && !overflow_y.is_clip() {
4567 return;
4568 }
4569 let clip_margin = get_overflow_clip_margin_property(styled_dom, dom_id, styled_node_state);
4570 let Some(margin_val) = clip_margin.exact() else {
4571 return;
4572 };
4573 let m = margin_val.inner.to_pixels_internal(0.0, 0.0, 0.0).max(0.0);
4574 if m <= 0.0 {
4575 return;
4576 }
4577 if overflow_x.is_clip() {
4578 clip_rect.origin.x -= m;
4579 clip_rect.size.width += m * 2.0;
4580 }
4581 if overflow_y.is_clip() {
4582 clip_rect.origin.y -= m;
4583 clip_rect.size.height += m * 2.0;
4584 }
4585}
4586
4587fn get_scroll_id(id: Option<NodeId>) -> LocalScrollId {
4588 id.map(|i| i.index() as u64).unwrap_or(0)
4589}
4590
4591fn get_scroll_content_size(node: &LayoutNodeHot, warm: Option<&LayoutNodeWarm>) -> LogicalSize {
4596 if let Some(overflow_size) = warm.and_then(|w| w.overflow_content_size) {
4598 return overflow_size;
4599 }
4600
4601 let mut content_size = node.used_size.unwrap_or_default();
4603
4604 if let Some(cached_layout) = warm.and_then(|w| w.inline_layout_result.as_ref()) {
4606 let text_layout = &cached_layout.layout;
4607 let mut max_x: f32 = 0.0;
4609 let mut max_y: f32 = 0.0;
4610
4611 for positioned_item in &text_layout.items {
4612 let item_bounds = positioned_item.item.bounds();
4613 let item_right = positioned_item.position.x + item_bounds.width;
4614 let item_bottom = positioned_item.position.y + item_bounds.height;
4615
4616 max_x = max_x.max(item_right);
4617 max_y = max_y.max(item_bottom);
4618 }
4619
4620 content_size.width = content_size.width.max(max_x);
4622 content_size.height = content_size.height.max(max_y);
4623 }
4624
4625 content_size
4626}
4627
4628fn get_tag_id(dom: &StyledDom, id: Option<NodeId>) -> Option<DisplayListTagId> {
4629 let node_id = id?;
4630 let tag_mapping = dom.tag_ids_to_node_ids.as_ref().iter().find(|m| {
4631 m.node_id.into_crate_internal() == Some(node_id)
4632 })?;
4633 Some((tag_mapping.tag_id.inner, TAG_TYPE_DOM_NODE))
4634}
4635
4636fn get_image_ref_for_image_source(
4637 source: &ImageSource,
4638) -> Option<ImageRef> {
4639 match source {
4640 ImageSource::Ref(image_ref) => Some(image_ref.clone()),
4641 ImageSource::Url(_url) => {
4642 None
4645 }
4646 ImageSource::Data(_) | ImageSource::Svg(_) | ImageSource::Placeholder(_) => {
4647 None
4649 }
4650 }
4651}
4652
4653fn get_display_item_bounds(item: &DisplayListItem) -> Option<WindowLogicalRect> {
4655 match item {
4656 DisplayListItem::Rect { bounds, .. } => Some(*bounds),
4657 DisplayListItem::SelectionRect { bounds, .. } => Some(*bounds),
4658 DisplayListItem::CursorRect { bounds, .. } => Some(*bounds),
4659 DisplayListItem::Border { bounds, .. } => Some(*bounds),
4660 DisplayListItem::TextLayout { bounds, .. } => Some(*bounds),
4661 DisplayListItem::Text { clip_rect, .. } => Some(*clip_rect),
4662 DisplayListItem::Underline { bounds, .. } => Some(*bounds),
4663 DisplayListItem::Strikethrough { bounds, .. } => Some(*bounds),
4664 DisplayListItem::Overline { bounds, .. } => Some(*bounds),
4665 DisplayListItem::Image { bounds, .. } => Some(*bounds),
4666 DisplayListItem::ScrollBar { bounds, .. } => Some(*bounds),
4667 DisplayListItem::ScrollBarStyled { info } => Some(info.bounds),
4668 DisplayListItem::PushClip { bounds, .. } => Some(*bounds),
4669 DisplayListItem::PushScrollFrame { clip_bounds, .. } => Some(*clip_bounds),
4670 DisplayListItem::HitTestArea { bounds, .. } => Some(*bounds),
4671 DisplayListItem::PushStackingContext { bounds, .. } => Some(*bounds),
4672 DisplayListItem::VirtualView { bounds, .. } => Some(*bounds),
4673 _ => None,
4674 }
4675}
4676
4677fn clip_and_offset_display_item(
4680 item: &DisplayListItem,
4681 page_top: f32,
4682 page_bottom: f32,
4683) -> Option<DisplayListItem> {
4684 match item {
4685 DisplayListItem::Rect {
4686 bounds,
4687 color,
4688 border_radius,
4689 } => clip_rect_item(bounds.into_inner(), *color, *border_radius, page_top, page_bottom),
4690
4691 DisplayListItem::Border {
4692 bounds,
4693 widths,
4694 colors,
4695 styles,
4696 border_radius,
4697 } => clip_border_item(
4698 bounds.into_inner(),
4699 *widths,
4700 *colors,
4701 *styles,
4702 border_radius.clone(),
4703 page_top,
4704 page_bottom,
4705 ),
4706
4707 DisplayListItem::SelectionRect {
4708 bounds,
4709 border_radius,
4710 color,
4711 } => clip_selection_rect_item(bounds.into_inner(), *border_radius, *color, page_top, page_bottom),
4712
4713 DisplayListItem::CursorRect { bounds, color } => {
4714 clip_cursor_rect_item(bounds.into_inner(), *color, page_top, page_bottom)
4715 }
4716
4717 DisplayListItem::Image { bounds, image, border_radius } => {
4718 clip_image_item(bounds.into_inner(), image.clone(), *border_radius, page_top, page_bottom)
4719 }
4720
4721 DisplayListItem::TextLayout {
4722 layout,
4723 bounds,
4724 font_hash,
4725 font_size_px,
4726 color,
4727 } => clip_text_layout_item(
4728 layout,
4729 bounds.into_inner(),
4730 *font_hash,
4731 *font_size_px,
4732 *color,
4733 page_top,
4734 page_bottom,
4735 ),
4736
4737 DisplayListItem::Text {
4738 glyphs,
4739 font_hash,
4740 font_size_px,
4741 color,
4742 clip_rect,
4743 ..
4744 } => clip_text_item(
4745 glyphs,
4746 *font_hash,
4747 *font_size_px,
4748 *color,
4749 clip_rect.into_inner(),
4750 page_top,
4751 page_bottom,
4752 ),
4753
4754 DisplayListItem::Underline {
4755 bounds,
4756 color,
4757 thickness,
4758 } => clip_text_decoration_item(
4759 bounds.into_inner(),
4760 *color,
4761 *thickness,
4762 TextDecorationType::Underline,
4763 page_top,
4764 page_bottom,
4765 ),
4766
4767 DisplayListItem::Strikethrough {
4768 bounds,
4769 color,
4770 thickness,
4771 } => clip_text_decoration_item(
4772 bounds.into_inner(),
4773 *color,
4774 *thickness,
4775 TextDecorationType::Strikethrough,
4776 page_top,
4777 page_bottom,
4778 ),
4779
4780 DisplayListItem::Overline {
4781 bounds,
4782 color,
4783 thickness,
4784 } => clip_text_decoration_item(
4785 bounds.into_inner(),
4786 *color,
4787 *thickness,
4788 TextDecorationType::Overline,
4789 page_top,
4790 page_bottom,
4791 ),
4792
4793 DisplayListItem::ScrollBar {
4794 bounds,
4795 color,
4796 orientation,
4797 opacity_key,
4798 hit_id,
4799 } => clip_scrollbar_item(
4800 bounds.into_inner(),
4801 *color,
4802 *orientation,
4803 *opacity_key,
4804 *hit_id,
4805 page_top,
4806 page_bottom,
4807 ),
4808
4809 DisplayListItem::HitTestArea { bounds, tag } => {
4810 clip_hit_test_area_item(bounds.into_inner(), *tag, page_top, page_bottom)
4811 }
4812
4813 DisplayListItem::VirtualView {
4814 child_dom_id,
4815 bounds,
4816 clip_rect,
4817 } => clip_virtual_view_item(*child_dom_id, bounds.into_inner(), clip_rect.into_inner(), page_top, page_bottom),
4818
4819 DisplayListItem::ScrollBarStyled { info } => {
4821 let bounds = info.bounds;
4822 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4823 None
4824 } else {
4825 let mut clipped_info = (**info).clone();
4827 let y_offset = -page_top;
4828 clipped_info.bounds = offset_rect_y(clipped_info.bounds.into_inner(), y_offset).into();
4829 clipped_info.track_bounds = offset_rect_y(clipped_info.track_bounds.into_inner(), y_offset).into();
4830 clipped_info.thumb_bounds = offset_rect_y(clipped_info.thumb_bounds.into_inner(), y_offset).into();
4831 if let Some(b) = clipped_info.button_decrement_bounds {
4832 clipped_info.button_decrement_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
4833 }
4834 if let Some(b) = clipped_info.button_increment_bounds {
4835 clipped_info.button_increment_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
4836 }
4837 Some(DisplayListItem::ScrollBarStyled {
4838 info: Box::new(clipped_info),
4839 })
4840 }
4841 }
4842
4843 DisplayListItem::PushClip { .. }
4845 | DisplayListItem::PopClip
4846 | DisplayListItem::PushScrollFrame { .. }
4847 | DisplayListItem::PopScrollFrame
4848 | DisplayListItem::PushStackingContext { .. }
4849 | DisplayListItem::PopStackingContext
4850 | DisplayListItem::VirtualViewPlaceholder { .. } => None,
4851
4852 DisplayListItem::LinearGradient {
4854 bounds,
4855 gradient,
4856 border_radius,
4857 } => {
4858 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4859 None
4860 } else {
4861 Some(DisplayListItem::LinearGradient {
4862 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4863 gradient: gradient.clone(),
4864 border_radius: *border_radius,
4865 })
4866 }
4867 }
4868 DisplayListItem::RadialGradient {
4869 bounds,
4870 gradient,
4871 border_radius,
4872 } => {
4873 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4874 None
4875 } else {
4876 Some(DisplayListItem::RadialGradient {
4877 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4878 gradient: gradient.clone(),
4879 border_radius: *border_radius,
4880 })
4881 }
4882 }
4883 DisplayListItem::ConicGradient {
4884 bounds,
4885 gradient,
4886 border_radius,
4887 } => {
4888 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4889 None
4890 } else {
4891 Some(DisplayListItem::ConicGradient {
4892 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4893 gradient: gradient.clone(),
4894 border_radius: *border_radius,
4895 })
4896 }
4897 }
4898
4899 DisplayListItem::BoxShadow {
4901 bounds,
4902 shadow,
4903 border_radius,
4904 } => {
4905 if bounds.0.origin.y + bounds.0.size.height < page_top || bounds.0.origin.y > page_bottom {
4906 None
4907 } else {
4908 Some(DisplayListItem::BoxShadow {
4909 bounds: offset_rect_y(bounds.into_inner(), -page_top).into(),
4910 shadow: *shadow,
4911 border_radius: *border_radius,
4912 })
4913 }
4914 }
4915
4916 DisplayListItem::PushFilter { .. }
4918 | DisplayListItem::PopFilter
4919 | DisplayListItem::PushBackdropFilter { .. }
4920 | DisplayListItem::PopBackdropFilter
4921 | DisplayListItem::PushOpacity { .. }
4922 | DisplayListItem::PopOpacity
4923 | DisplayListItem::PushReferenceFrame { .. }
4924 | DisplayListItem::PopReferenceFrame
4925 | DisplayListItem::PushTextShadow { .. }
4926 | DisplayListItem::PopTextShadow
4927 | DisplayListItem::PushImageMaskClip { .. }
4928 | DisplayListItem::PopImageMaskClip => None,
4929 }
4930}
4931
4932#[derive(Debug, Clone, Copy)]
4936enum TextDecorationType {
4937 Underline,
4938 Strikethrough,
4939 Overline,
4940}
4941
4942fn clip_rect_item(
4944 bounds: LogicalRect,
4945 color: ColorU,
4946 border_radius: BorderRadius,
4947 page_top: f32,
4948 page_bottom: f32,
4949) -> Option<DisplayListItem> {
4950 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Rect {
4951 bounds: clipped.into(),
4952 color,
4953 border_radius,
4954 })
4955}
4956
4957fn clip_border_item(
4959 bounds: LogicalRect,
4960 widths: StyleBorderWidths,
4961 colors: StyleBorderColors,
4962 styles: StyleBorderStyles,
4963 border_radius: StyleBorderRadius,
4964 page_top: f32,
4965 page_bottom: f32,
4966) -> Option<DisplayListItem> {
4967 let original_bounds = bounds;
4968 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| {
4969 let new_widths = adjust_border_widths_for_clipping(
4970 widths,
4971 original_bounds,
4972 clipped,
4973 page_top,
4974 page_bottom,
4975 );
4976 DisplayListItem::Border {
4977 bounds: clipped.into(),
4978 widths: new_widths,
4979 colors,
4980 styles,
4981 border_radius,
4982 }
4983 })
4984}
4985
4986fn adjust_border_widths_for_clipping(
4989 mut widths: StyleBorderWidths,
4990 original_bounds: LogicalRect,
4991 clipped: LogicalRect,
4992 page_top: f32,
4993 page_bottom: f32,
4994) -> StyleBorderWidths {
4995 if clipped.origin.y > 0.0 && original_bounds.origin.y < page_top {
4997 widths.top = None;
4998 }
4999
5000 let original_bottom = original_bounds.origin.y + original_bounds.size.height;
5002 let clipped_bottom = clipped.origin.y + clipped.size.height;
5003 if original_bottom > page_bottom && clipped_bottom >= page_bottom - page_top - 1.0 {
5004 widths.bottom = None;
5005 }
5006
5007 widths
5008}
5009
5010fn clip_selection_rect_item(
5012 bounds: LogicalRect,
5013 border_radius: BorderRadius,
5014 color: ColorU,
5015 page_top: f32,
5016 page_bottom: f32,
5017) -> Option<DisplayListItem> {
5018 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::SelectionRect {
5019 bounds: clipped.into(),
5020 border_radius,
5021 color,
5022 })
5023}
5024
5025fn clip_cursor_rect_item(
5027 bounds: LogicalRect,
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::CursorRect {
5033 bounds: clipped.into(),
5034 color,
5035 })
5036}
5037
5038fn clip_image_item(
5040 bounds: LogicalRect,
5041 image: ImageRef,
5042 border_radius: BorderRadius,
5043 page_top: f32,
5044 page_bottom: f32,
5045) -> Option<DisplayListItem> {
5046 if !rect_intersects(&bounds, page_top, page_bottom) {
5047 return None;
5048 }
5049 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Image {
5050 bounds: clipped.into(),
5051 image,
5052 border_radius,
5053 })
5054}
5055
5056fn clip_text_layout_item(
5058 layout: &Arc<dyn std::any::Any + Send + Sync>,
5059 bounds: LogicalRect,
5060 font_hash: FontHash,
5061 font_size_px: f32,
5062 color: ColorU,
5063 page_top: f32,
5064 page_bottom: f32,
5065) -> Option<DisplayListItem> {
5066 if !rect_intersects(&bounds, page_top, page_bottom) {
5067 return None;
5068 }
5069
5070 #[cfg(feature = "text_layout")]
5072 if let Some(unified_layout) = layout.downcast_ref::<crate::text3::cache::UnifiedLayout>() {
5073 return clip_unified_layout(
5074 unified_layout,
5075 bounds,
5076 font_hash,
5077 font_size_px,
5078 color,
5079 page_top,
5080 page_bottom,
5081 );
5082 }
5083
5084 Some(DisplayListItem::TextLayout {
5086 layout: layout.clone(),
5087 bounds: offset_rect_y(bounds, -page_top).into(),
5088 font_hash,
5089 font_size_px,
5090 color,
5091 })
5092}
5093
5094#[cfg(feature = "text_layout")]
5096fn clip_unified_layout(
5097 unified_layout: &crate::text3::cache::UnifiedLayout,
5098 bounds: LogicalRect,
5099 font_hash: FontHash,
5100 font_size_px: f32,
5101 color: ColorU,
5102 page_top: f32,
5103 page_bottom: f32,
5104) -> Option<DisplayListItem> {
5105 let layout_origin_y = bounds.origin.y;
5106 let layout_origin_x = bounds.origin.x;
5107
5108 let filtered_items: Vec<_> = unified_layout
5110 .items
5111 .iter()
5112 .filter(|item| item_center_on_page(item, layout_origin_y, page_top, page_bottom))
5113 .cloned()
5114 .collect();
5115
5116 if filtered_items.is_empty() {
5117 return None;
5118 }
5119
5120 let new_origin_y = (layout_origin_y - page_top).max(0.0);
5122
5123 let (offset_items, min_y, max_y, max_width) =
5125 transform_items_to_page_coords(filtered_items, layout_origin_y, page_top, new_origin_y);
5126
5127 let new_layout = crate::text3::cache::UnifiedLayout {
5128 items: offset_items,
5129 overflow: unified_layout.overflow.clone(),
5130 };
5131
5132 let new_bounds = LogicalRect {
5133 origin: LogicalPosition {
5134 x: layout_origin_x,
5135 y: new_origin_y,
5136 },
5137 size: LogicalSize {
5138 width: max_width.max(bounds.size.width),
5139 height: (max_y - min_y.min(0.0)).max(0.0),
5140 },
5141 };
5142
5143 Some(DisplayListItem::TextLayout {
5144 layout: Arc::new(new_layout) as Arc<dyn std::any::Any + Send + Sync>,
5145 bounds: new_bounds.into(),
5146 font_hash,
5147 font_size_px,
5148 color,
5149 })
5150}
5151
5152#[cfg(feature = "text_layout")]
5154fn item_center_on_page(
5155 item: &crate::text3::cache::PositionedItem,
5156 layout_origin_y: f32,
5157 page_top: f32,
5158 page_bottom: f32,
5159) -> bool {
5160 let item_y_absolute = layout_origin_y + item.position.y;
5161 let item_height = item.item.bounds().height;
5162 let item_center_y = item_y_absolute + (item_height / 2.0);
5163 item_center_y >= page_top && item_center_y < page_bottom
5164}
5165
5166#[cfg(feature = "text_layout")]
5169fn transform_items_to_page_coords(
5170 items: Vec<crate::text3::cache::PositionedItem>,
5171 layout_origin_y: f32,
5172 page_top: f32,
5173 new_origin_y: f32,
5174) -> (Vec<crate::text3::cache::PositionedItem>, f32, f32, f32) {
5175 let mut min_y = f32::MAX;
5176 let mut max_y = f32::MIN;
5177 let mut max_width = 0.0f32;
5178
5179 let offset_items: Vec<_> = items
5180 .into_iter()
5181 .map(|mut item| {
5182 let abs_y = layout_origin_y + item.position.y;
5183 let page_y = abs_y - page_top;
5184 let new_item_y = page_y - new_origin_y;
5185
5186 let item_bounds = item.item.bounds();
5187 min_y = min_y.min(new_item_y);
5188 max_y = max_y.max(new_item_y + item_bounds.height);
5189 max_width = max_width.max(item.position.x + item_bounds.width);
5190
5191 item.position.y = new_item_y;
5192 item
5193 })
5194 .collect();
5195
5196 (offset_items, min_y, max_y, max_width)
5197}
5198
5199fn clip_text_item(
5201 glyphs: &[GlyphInstance],
5202 font_hash: FontHash,
5203 font_size_px: f32,
5204 color: ColorU,
5205 clip_rect: LogicalRect,
5206 page_top: f32,
5207 page_bottom: f32,
5208) -> Option<DisplayListItem> {
5209 if !rect_intersects(&clip_rect, page_top, page_bottom) {
5210 return None;
5211 }
5212
5213 let page_glyphs: Vec<_> = glyphs
5215 .iter()
5216 .filter(|g| g.point.y >= page_top && g.point.y < page_bottom)
5217 .map(|g| GlyphInstance {
5218 index: g.index,
5219 point: LogicalPosition {
5220 x: g.point.x,
5221 y: g.point.y - page_top,
5222 },
5223 size: g.size,
5224 })
5225 .collect();
5226
5227 if page_glyphs.is_empty() {
5228 return None;
5229 }
5230
5231 Some(DisplayListItem::Text {
5232 glyphs: page_glyphs,
5233 font_hash,
5234 font_size_px,
5235 color,
5236 clip_rect: offset_rect_y(clip_rect, -page_top).into(),
5237 source_node_index: None,
5238 })
5239}
5240
5241fn clip_text_decoration_item(
5243 bounds: LogicalRect,
5244 color: ColorU,
5245 thickness: f32,
5246 decoration_type: TextDecorationType,
5247 page_top: f32,
5248 page_bottom: f32,
5249) -> Option<DisplayListItem> {
5250 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| match decoration_type {
5251 TextDecorationType::Underline => DisplayListItem::Underline {
5252 bounds: clipped.into(),
5253 color,
5254 thickness,
5255 },
5256 TextDecorationType::Strikethrough => DisplayListItem::Strikethrough {
5257 bounds: clipped.into(),
5258 color,
5259 thickness,
5260 },
5261 TextDecorationType::Overline => DisplayListItem::Overline {
5262 bounds: clipped.into(),
5263 color,
5264 thickness,
5265 },
5266 })
5267}
5268
5269fn clip_scrollbar_item(
5271 bounds: LogicalRect,
5272 color: ColorU,
5273 orientation: ScrollbarOrientation,
5274 opacity_key: Option<OpacityKey>,
5275 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
5276 page_top: f32,
5277 page_bottom: f32,
5278) -> Option<DisplayListItem> {
5279 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::ScrollBar {
5280 bounds: clipped.into(),
5281 color,
5282 orientation,
5283 opacity_key,
5284 hit_id,
5285 })
5286}
5287
5288fn clip_hit_test_area_item(
5290 bounds: LogicalRect,
5291 tag: DisplayListTagId,
5292 page_top: f32,
5293 page_bottom: f32,
5294) -> Option<DisplayListItem> {
5295 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::HitTestArea {
5296 bounds: clipped.into(),
5297 tag,
5298 })
5299}
5300
5301fn clip_virtual_view_item(
5303 child_dom_id: DomId,
5304 bounds: LogicalRect,
5305 clip_rect: LogicalRect,
5306 page_top: f32,
5307 page_bottom: f32,
5308) -> Option<DisplayListItem> {
5309 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::VirtualView {
5310 child_dom_id,
5311 bounds: clipped.into(),
5312 clip_rect: offset_rect_y(clip_rect, -page_top).into(),
5313 })
5314}
5315
5316fn clip_rect_bounds(bounds: LogicalRect, page_top: f32, page_bottom: f32) -> Option<LogicalRect> {
5319 let item_top = bounds.origin.y;
5320 let item_bottom = bounds.origin.y + bounds.size.height;
5321
5322 if item_bottom <= page_top || item_top >= page_bottom {
5324 return None;
5325 }
5326
5327 let clipped_top = item_top.max(page_top);
5329 let clipped_bottom = item_bottom.min(page_bottom);
5330 let clipped_height = clipped_bottom - clipped_top;
5331
5332 let page_relative_y = clipped_top - page_top;
5334
5335 Some(LogicalRect {
5336 origin: LogicalPosition {
5337 x: bounds.origin.x,
5338 y: page_relative_y,
5339 },
5340 size: LogicalSize {
5341 width: bounds.size.width,
5342 height: clipped_height,
5343 },
5344 })
5345}
5346
5347fn rect_intersects(bounds: &LogicalRect, page_top: f32, page_bottom: f32) -> bool {
5349 let item_top = bounds.origin.y;
5350 let item_bottom = bounds.origin.y + bounds.size.height;
5351 item_bottom > page_top && item_top < page_bottom
5352}
5353
5354fn offset_rect_y(bounds: LogicalRect, offset_y: f32) -> LogicalRect {
5356 LogicalRect {
5357 origin: LogicalPosition {
5358 x: bounds.origin.x,
5359 y: bounds.origin.y + offset_y,
5360 },
5361 size: bounds.size,
5362 }
5363}
5364
5365use azul_css::props::layout::fragmentation::{BreakInside, PageBreak};
5374
5375use crate::solver3::pagination::{
5376 HeaderFooterConfig, MarginBoxContent, PageInfo, TableHeaderInfo, TableHeaderTracker,
5377};
5378
5379#[derive(Debug, Clone, Default)]
5381pub struct SlicerConfig {
5382 pub page_content_height: f32,
5384 pub page_gap: f32,
5387 pub allow_clipping: bool,
5389 pub header_footer: HeaderFooterConfig,
5391 pub page_width: f32,
5393 pub table_headers: TableHeaderTracker,
5395}
5396
5397impl SlicerConfig {
5398 pub fn simple(page_height: f32) -> Self {
5400 Self {
5401 page_content_height: page_height,
5402 page_gap: 0.0,
5403 allow_clipping: true,
5404 header_footer: HeaderFooterConfig::default(),
5405 page_width: 595.0, table_headers: TableHeaderTracker::default(),
5407 }
5408 }
5409
5410 pub fn with_gap(page_height: f32, gap: f32) -> Self {
5412 Self {
5413 page_content_height: page_height,
5414 page_gap: gap,
5415 allow_clipping: true,
5416 header_footer: HeaderFooterConfig::default(),
5417 page_width: 595.0,
5418 table_headers: TableHeaderTracker::default(),
5419 }
5420 }
5421
5422 pub fn with_header_footer(mut self, config: HeaderFooterConfig) -> Self {
5424 self.header_footer = config;
5425 self
5426 }
5427
5428 pub fn with_page_width(mut self, width: f32) -> Self {
5430 self.page_width = width;
5431 self
5432 }
5433
5434 pub fn with_table_headers(mut self, tracker: TableHeaderTracker) -> Self {
5436 self.table_headers = tracker;
5437 self
5438 }
5439
5440 pub fn register_table_header(&mut self, info: TableHeaderInfo) {
5442 self.table_headers.register_table_header(info);
5443 }
5444
5445 pub fn page_slot_height(&self) -> f32 {
5447 self.page_content_height + self.page_gap
5448 }
5449
5450 pub fn page_for_y(&self, y: f32) -> usize {
5452 if self.page_slot_height() <= 0.0 {
5453 return 0;
5454 }
5455 (y / self.page_slot_height()).floor() as usize
5456 }
5457
5458 pub fn page_bounds(&self, page_index: usize) -> (f32, f32) {
5460 let start = page_index as f32 * self.page_slot_height();
5461 let end = start + self.page_content_height;
5462 (start, end)
5463 }
5464}
5465
5466pub fn paginate_display_list_with_slicer_and_breaks(
5474 full_display_list: DisplayList,
5475 config: &SlicerConfig,
5476) -> Result<Vec<DisplayList>> {
5477 if config.page_content_height <= 0.0 || config.page_content_height >= f32::MAX {
5478 return Ok(vec![full_display_list]);
5479 }
5480
5481 let base_header_space = if config.header_footer.show_header {
5483 config.header_footer.header_height
5484 } else {
5485 0.0
5486 };
5487 let base_footer_space = if config.header_footer.show_footer {
5488 config.header_footer.footer_height
5489 } else {
5490 0.0
5491 };
5492
5493 let normal_page_content_height =
5495 config.page_content_height - base_header_space - base_footer_space;
5496 let first_page_content_height = if config.header_footer.skip_first_page {
5497 config.page_content_height
5499 } else {
5500 normal_page_content_height
5501 };
5502
5503 let page_breaks = calculate_page_break_positions(
5513 &full_display_list,
5514 first_page_content_height,
5515 normal_page_content_height,
5516 );
5517
5518 let num_pages = page_breaks.len();
5519
5520 let mut pages: Vec<DisplayList> = Vec::with_capacity(num_pages);
5522
5523 for (page_idx, &(content_start_y, content_end_y)) in page_breaks.iter().enumerate() {
5524 let page_info = PageInfo::new(page_idx + 1, num_pages);
5526
5527 let skip_this_page = config.header_footer.skip_first_page && page_info.is_first;
5529 let header_space = if config.header_footer.show_header && !skip_this_page {
5530 config.header_footer.header_height
5531 } else {
5532 0.0
5533 };
5534 let footer_space = if config.header_footer.show_footer && !skip_this_page {
5535 config.header_footer.footer_height
5536 } else {
5537 0.0
5538 };
5539
5540 let _ = footer_space; let mut page_items = Vec::new();
5543 let mut page_node_mapping = Vec::new();
5544
5545 if config.header_footer.show_header && !skip_this_page {
5547 let header_text = config.header_footer.header_text(page_info);
5548 if !header_text.is_empty() {
5549 let header_items = generate_text_display_items(
5550 &header_text,
5551 LogicalRect {
5552 origin: LogicalPosition { x: 0.0, y: 0.0 },
5553 size: LogicalSize {
5554 width: config.page_width,
5555 height: config.header_footer.header_height,
5556 },
5557 },
5558 config.header_footer.font_size,
5559 config.header_footer.text_color,
5560 TextAlignment::Center,
5561 );
5562 for item in header_items {
5563 page_items.push(item);
5564 page_node_mapping.push(None);
5565 }
5566 }
5567 }
5568
5569 let repeated_headers = config.table_headers.get_repeated_headers_for_page(
5571 page_idx,
5572 content_start_y,
5573 content_end_y,
5574 );
5575
5576 let mut thead_total_height = 0.0f32;
5577 for (y_offset_from_page_top, thead_items, thead_height) in repeated_headers {
5578 let thead_y = header_space + y_offset_from_page_top;
5579 for item in thead_items {
5580 let translated_item = offset_display_item_y(item, thead_y);
5581 page_items.push(translated_item);
5582 page_node_mapping.push(None);
5583 }
5584 thead_total_height = thead_total_height.max(thead_height);
5585 }
5586
5587 let content_y_offset = header_space + thead_total_height;
5589
5590 for (item_idx, item) in full_display_list.items.iter().enumerate() {
5592 let is_fixed = full_display_list.fixed_position_item_ranges.iter()
5594 .any(|&(start, end)| item_idx >= start && item_idx < end);
5595 if is_fixed {
5596 continue;
5597 }
5598 if let Some(clipped_item) =
5599 clip_and_offset_display_item(item, content_start_y, content_end_y)
5600 {
5601 let final_item = if content_y_offset > 0.0 {
5602 offset_display_item_y(&clipped_item, content_y_offset)
5603 } else {
5604 clipped_item
5605 };
5606 page_items.push(final_item);
5607 let node_mapping = full_display_list
5608 .node_mapping
5609 .get(item_idx)
5610 .copied()
5611 .flatten();
5612 page_node_mapping.push(node_mapping);
5613 }
5614 }
5615
5616 for &(start, end) in &full_display_list.fixed_position_item_ranges {
5620 for item_idx in start..end {
5621 if let Some(item) = full_display_list.items.get(item_idx) {
5622 let final_item = if content_y_offset > 0.0 {
5623 offset_display_item_y(item, content_y_offset)
5624 } else {
5625 item.clone()
5626 };
5627 page_items.push(final_item);
5628 let node_mapping = full_display_list
5629 .node_mapping
5630 .get(item_idx)
5631 .copied()
5632 .flatten();
5633 page_node_mapping.push(node_mapping);
5634 }
5635 }
5636 }
5637
5638 if config.header_footer.show_footer && !skip_this_page {
5640 let footer_text = config.header_footer.footer_text(page_info);
5641 if !footer_text.is_empty() {
5642 let footer_y = config.page_content_height - config.header_footer.footer_height;
5643 let footer_items = generate_text_display_items(
5644 &footer_text,
5645 LogicalRect {
5646 origin: LogicalPosition {
5647 x: 0.0,
5648 y: footer_y,
5649 },
5650 size: LogicalSize {
5651 width: config.page_width,
5652 height: config.header_footer.footer_height,
5653 },
5654 },
5655 config.header_footer.font_size,
5656 config.header_footer.text_color,
5657 TextAlignment::Center,
5658 );
5659 for item in footer_items {
5660 page_items.push(item);
5661 page_node_mapping.push(None);
5662 }
5663 }
5664 }
5665
5666 pages.push(DisplayList {
5667 items: page_items,
5668 node_mapping: page_node_mapping,
5669 forced_page_breaks: Vec::new(),
5670 fixed_position_item_ranges: Vec::new(), });
5672 }
5673
5674 if pages.is_empty() {
5676 pages.push(DisplayList::default());
5677 }
5678
5679 Ok(pages)
5680}
5681
5682fn calculate_page_break_positions(
5690 display_list: &DisplayList,
5691 first_page_height: f32,
5692 normal_page_height: f32,
5693) -> Vec<(f32, f32)> {
5694 let total_height = calculate_display_list_height(display_list);
5695
5696 if total_height <= 0.0 || first_page_height <= 0.0 {
5697 return vec![(0.0, total_height.max(first_page_height))];
5698 }
5699
5700 let mut break_points: Vec<f32> = Vec::new();
5702
5703 for &forced_break_y in &display_list.forced_page_breaks {
5705 if forced_break_y > 0.0 && forced_break_y < total_height {
5706 break_points.push(forced_break_y);
5707 }
5708 }
5709
5710 let mut y = first_page_height;
5712 while y < total_height {
5713 break_points.push(y);
5714 y += normal_page_height;
5715 }
5716
5717 break_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
5719 break_points.dedup_by(|a, b| (*a - *b).abs() < 1.0); let mut page_breaks: Vec<(f32, f32)> = Vec::new();
5723 let mut page_start = 0.0f32;
5724
5725 for break_y in break_points {
5726 if break_y > page_start {
5727 page_breaks.push((page_start, break_y));
5728 page_start = break_y;
5729 }
5730 }
5731
5732 if page_start < total_height {
5734 page_breaks.push((page_start, total_height));
5735 }
5736
5737 if page_breaks.is_empty() {
5739 page_breaks.push((0.0, total_height.max(first_page_height)));
5740 }
5741
5742 page_breaks
5743}
5744
5745#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5747pub enum TextAlignment {
5748 Left,
5749 Center,
5750 Right,
5751}
5752
5753fn offset_display_item_y(item: &DisplayListItem, y_offset: f32) -> DisplayListItem {
5755 if y_offset == 0.0 {
5756 return item.clone();
5757 }
5758
5759 match item {
5760 DisplayListItem::Rect {
5761 bounds,
5762 color,
5763 border_radius,
5764 } => DisplayListItem::Rect {
5765 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5766 color: *color,
5767 border_radius: *border_radius,
5768 },
5769 DisplayListItem::Border {
5770 bounds,
5771 widths,
5772 colors,
5773 styles,
5774 border_radius,
5775 } => DisplayListItem::Border {
5776 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5777 widths: widths.clone(),
5778 colors: *colors,
5779 styles: *styles,
5780 border_radius: border_radius.clone(),
5781 },
5782 DisplayListItem::Text {
5783 glyphs,
5784 font_hash,
5785 font_size_px,
5786 color,
5787 clip_rect,
5788 ..
5789 } => {
5790 let offset_glyphs: Vec<GlyphInstance> = glyphs
5791 .iter()
5792 .map(|g| GlyphInstance {
5793 index: g.index,
5794 point: LogicalPosition {
5795 x: g.point.x,
5796 y: g.point.y + y_offset,
5797 },
5798 size: g.size,
5799 })
5800 .collect();
5801 DisplayListItem::Text {
5802 glyphs: offset_glyphs,
5803 font_hash: *font_hash,
5804 font_size_px: *font_size_px,
5805 color: *color,
5806 clip_rect: offset_rect_y(clip_rect.into_inner(), y_offset).into(),
5807 source_node_index: None,
5808 }
5809 }
5810 DisplayListItem::TextLayout {
5811 layout,
5812 bounds,
5813 font_hash,
5814 font_size_px,
5815 color,
5816 } => DisplayListItem::TextLayout {
5817 layout: layout.clone(),
5818 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5819 font_hash: *font_hash,
5820 font_size_px: *font_size_px,
5821 color: *color,
5822 },
5823 DisplayListItem::Image { bounds, image, border_radius } => DisplayListItem::Image {
5824 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5825 image: image.clone(),
5826 border_radius: *border_radius,
5827 },
5828 DisplayListItem::SelectionRect {
5830 bounds,
5831 border_radius,
5832 color,
5833 } => DisplayListItem::SelectionRect {
5834 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5835 border_radius: *border_radius,
5836 color: *color,
5837 },
5838 DisplayListItem::CursorRect { bounds, color } => DisplayListItem::CursorRect {
5839 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5840 color: *color,
5841 },
5842 DisplayListItem::Underline {
5843 bounds,
5844 color,
5845 thickness,
5846 } => DisplayListItem::Underline {
5847 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5848 color: *color,
5849 thickness: *thickness,
5850 },
5851 DisplayListItem::Strikethrough {
5852 bounds,
5853 color,
5854 thickness,
5855 } => DisplayListItem::Strikethrough {
5856 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5857 color: *color,
5858 thickness: *thickness,
5859 },
5860 DisplayListItem::Overline {
5861 bounds,
5862 color,
5863 thickness,
5864 } => DisplayListItem::Overline {
5865 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5866 color: *color,
5867 thickness: *thickness,
5868 },
5869 DisplayListItem::ScrollBar {
5870 bounds,
5871 color,
5872 orientation,
5873 opacity_key,
5874 hit_id,
5875 } => DisplayListItem::ScrollBar {
5876 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5877 color: *color,
5878 orientation: *orientation,
5879 opacity_key: *opacity_key,
5880 hit_id: *hit_id,
5881 },
5882 DisplayListItem::HitTestArea { bounds, tag } => DisplayListItem::HitTestArea {
5883 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5884 tag: *tag,
5885 },
5886 DisplayListItem::PushClip {
5887 bounds,
5888 border_radius,
5889 } => DisplayListItem::PushClip {
5890 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5891 border_radius: *border_radius,
5892 },
5893 DisplayListItem::PushScrollFrame {
5894 clip_bounds,
5895 content_size,
5896 scroll_id,
5897 } => DisplayListItem::PushScrollFrame {
5898 clip_bounds: offset_rect_y(clip_bounds.into_inner(), y_offset).into(),
5899 content_size: *content_size,
5900 scroll_id: *scroll_id,
5901 },
5902 DisplayListItem::PushStackingContext { bounds, z_index } => {
5903 DisplayListItem::PushStackingContext {
5904 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5905 z_index: *z_index,
5906 }
5907 }
5908 DisplayListItem::VirtualView {
5909 child_dom_id,
5910 bounds,
5911 clip_rect,
5912 } => DisplayListItem::VirtualView {
5913 child_dom_id: *child_dom_id,
5914 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5915 clip_rect: offset_rect_y(clip_rect.into_inner(), y_offset).into(),
5916 },
5917 DisplayListItem::VirtualViewPlaceholder {
5918 node_id,
5919 bounds,
5920 clip_rect,
5921 } => DisplayListItem::VirtualViewPlaceholder {
5922 node_id: *node_id,
5923 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5924 clip_rect: offset_rect_y(clip_rect.into_inner(), y_offset).into(),
5925 },
5926 DisplayListItem::PopClip => DisplayListItem::PopClip,
5928 DisplayListItem::PopScrollFrame => DisplayListItem::PopScrollFrame,
5929 DisplayListItem::PopStackingContext => DisplayListItem::PopStackingContext,
5930
5931 DisplayListItem::LinearGradient {
5933 bounds,
5934 gradient,
5935 border_radius,
5936 } => DisplayListItem::LinearGradient {
5937 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5938 gradient: gradient.clone(),
5939 border_radius: *border_radius,
5940 },
5941 DisplayListItem::RadialGradient {
5942 bounds,
5943 gradient,
5944 border_radius,
5945 } => DisplayListItem::RadialGradient {
5946 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5947 gradient: gradient.clone(),
5948 border_radius: *border_radius,
5949 },
5950 DisplayListItem::ConicGradient {
5951 bounds,
5952 gradient,
5953 border_radius,
5954 } => DisplayListItem::ConicGradient {
5955 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5956 gradient: gradient.clone(),
5957 border_radius: *border_radius,
5958 },
5959
5960 DisplayListItem::BoxShadow {
5962 bounds,
5963 shadow,
5964 border_radius,
5965 } => DisplayListItem::BoxShadow {
5966 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5967 shadow: *shadow,
5968 border_radius: *border_radius,
5969 },
5970
5971 DisplayListItem::PushFilter { bounds, filters } => DisplayListItem::PushFilter {
5973 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5974 filters: filters.clone(),
5975 },
5976 DisplayListItem::PopFilter => DisplayListItem::PopFilter,
5977 DisplayListItem::PushBackdropFilter { bounds, filters } => {
5978 DisplayListItem::PushBackdropFilter {
5979 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5980 filters: filters.clone(),
5981 }
5982 }
5983 DisplayListItem::PopBackdropFilter => DisplayListItem::PopBackdropFilter,
5984 DisplayListItem::PushOpacity { bounds, opacity } => DisplayListItem::PushOpacity {
5985 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
5986 opacity: *opacity,
5987 },
5988 DisplayListItem::PopOpacity => DisplayListItem::PopOpacity,
5989 DisplayListItem::ScrollBarStyled { info } => {
5990 let mut offset_info = (**info).clone();
5991 offset_info.bounds = offset_rect_y(offset_info.bounds.into_inner(), y_offset).into();
5992 offset_info.track_bounds = offset_rect_y(offset_info.track_bounds.into_inner(), y_offset).into();
5993 offset_info.thumb_bounds = offset_rect_y(offset_info.thumb_bounds.into_inner(), y_offset).into();
5994 if let Some(b) = offset_info.button_decrement_bounds {
5995 offset_info.button_decrement_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
5996 }
5997 if let Some(b) = offset_info.button_increment_bounds {
5998 offset_info.button_increment_bounds = Some(offset_rect_y(b.into_inner(), y_offset).into());
5999 }
6000 DisplayListItem::ScrollBarStyled {
6001 info: Box::new(offset_info),
6002 }
6003 }
6004
6005 DisplayListItem::PushReferenceFrame {
6007 transform_key,
6008 initial_transform,
6009 bounds,
6010 } => DisplayListItem::PushReferenceFrame {
6011 transform_key: *transform_key,
6012 initial_transform: *initial_transform,
6013 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
6014 },
6015 DisplayListItem::PopReferenceFrame => DisplayListItem::PopReferenceFrame,
6016 DisplayListItem::PushTextShadow { shadow } => DisplayListItem::PushTextShadow {
6017 shadow: shadow.clone(),
6018 },
6019 DisplayListItem::PopTextShadow => DisplayListItem::PopTextShadow,
6020 DisplayListItem::PushImageMaskClip {
6021 bounds,
6022 mask_image,
6023 mask_rect,
6024 } => DisplayListItem::PushImageMaskClip {
6025 bounds: offset_rect_y(bounds.into_inner(), y_offset).into(),
6026 mask_image: mask_image.clone(),
6027 mask_rect: offset_rect_y(mask_rect.into_inner(), y_offset).into(),
6028 },
6029 DisplayListItem::PopImageMaskClip => DisplayListItem::PopImageMaskClip,
6030 }
6031}
6032
6033fn generate_text_display_items(
6038 text: &str,
6039 bounds: LogicalRect,
6040 font_size: f32,
6041 color: ColorU,
6042 alignment: TextAlignment,
6043) -> Vec<DisplayListItem> {
6044 use crate::font_traits::FontHash;
6045
6046 if text.is_empty() {
6047 return Vec::new();
6048 }
6049
6050 let char_width = font_size * 0.5;
6053 let text_width = text.len() as f32 * char_width;
6054
6055 let x_offset = match alignment {
6056 TextAlignment::Left => bounds.origin.x,
6057 TextAlignment::Center => bounds.origin.x + (bounds.size.width - text_width) / 2.0,
6058 TextAlignment::Right => bounds.origin.x + bounds.size.width - text_width,
6059 };
6060
6061 let y_pos = bounds.origin.y + (bounds.size.height + font_size) / 2.0 - font_size * 0.2;
6063
6064 let glyphs: Vec<GlyphInstance> = text
6067 .chars()
6068 .enumerate()
6069 .filter(|(_, c)| !c.is_control())
6070 .map(|(i, c)| GlyphInstance {
6071 index: c as u32, point: LogicalPosition {
6073 x: x_offset + i as f32 * char_width,
6074 y: y_pos,
6075 },
6076 size: LogicalSize::new(char_width, font_size),
6077 })
6078 .collect();
6079
6080 if glyphs.is_empty() {
6081 return Vec::new();
6082 }
6083
6084 vec![DisplayListItem::Text {
6085 glyphs,
6086 font_hash: FontHash::from_hash(0), font_size_px: font_size,
6088 color,
6089 clip_rect: bounds.into(),
6090 source_node_index: None,
6091 }]
6092}
6093
6094fn calculate_display_list_height(display_list: &DisplayList) -> f32 {
6096 let mut max_bottom = 0.0f32;
6097
6098 for item in &display_list.items {
6099 if let Some(bounds) = get_display_item_bounds(item) {
6100 if bounds.0.size.height < 0.1 {
6102 continue;
6103 }
6104
6105 let item_bottom = bounds.0.origin.y + bounds.0.size.height;
6106 if item_bottom > max_bottom {
6107 max_bottom = item_bottom;
6108 }
6109 }
6110 }
6111
6112 max_bottom
6113}
6114
6115#[derive(Debug, Clone, Copy, Default)]
6117pub struct BreakProperties {
6118 pub break_before: PageBreak,
6119 pub break_after: PageBreak,
6120 pub break_inside: BreakInside,
6121}
6122
6123pub fn apply_text_overflow_ellipsis(
6159 display_list: &mut DisplayList,
6160 container_bounds: LogicalRect,
6161 _ellipsis: &str,
6162) {
6163 let container_right = container_bounds.origin.x + container_bounds.size.width;
6164
6165 for item in display_list.items.iter_mut() {
6168 match item {
6169 DisplayListItem::Text {
6170 glyphs,
6171 font_size_px,
6172 clip_rect,
6173 ..
6174 } => {
6175 if glyphs.is_empty() {
6176 continue;
6177 }
6178
6179 let last_glyph = &glyphs[glyphs.len() - 1];
6181 let last_glyph_right = last_glyph.point.x + last_glyph.size.width;
6182
6183 if last_glyph_right <= container_right {
6184 continue; }
6186
6187 let ellipsis_width = *font_size_px * 0.6;
6189 let truncation_edge = container_right - ellipsis_width;
6190
6191 let mut keep_count = 0;
6193 for (i, glyph) in glyphs.iter().enumerate() {
6194 let glyph_right = glyph.point.x + glyph.size.width;
6195 if glyph_right > truncation_edge {
6196 break;
6197 }
6198 keep_count = i + 1;
6199 }
6200
6201 glyphs.truncate(keep_count);
6203
6204 let ellipsis_x = if let Some(last) = glyphs.last() {
6209 last.point.x + last.size.width
6210 } else {
6211 container_bounds.origin.x
6212 };
6213
6214 let ellipsis_glyph = GlyphInstance {
6215 index: 0x2026, point: LogicalPosition::new(ellipsis_x, glyphs.first().map_or(
6217 container_bounds.origin.y,
6218 |g| g.point.y,
6219 )),
6220 size: LogicalSize::new(ellipsis_width, *font_size_px),
6221 };
6222
6223 glyphs.push(ellipsis_glyph);
6224
6225 *clip_rect = container_bounds.into();
6228 }
6229 _ => {} }
6231 }
6232}
6233
6234pub fn resolve_clip_path(
6263 clip_path: &azul_css::props::layout::shape::ClipPath,
6264 node_bounds: LogicalRect,
6265) -> Option<(LogicalRect, f32)> {
6266 use azul_css::props::layout::shape::ClipPath;
6267 use azul_css::shape::CssShape;
6268
6269 match clip_path {
6270 ClipPath::None => None,
6271 ClipPath::Shape(shape) => {
6272 match shape {
6273 CssShape::Inset(inset) => {
6274 let x = node_bounds.origin.x + inset.inset_left;
6277 let y = node_bounds.origin.y + inset.inset_top;
6278 let w = (node_bounds.size.width - inset.inset_left - inset.inset_right).max(0.0);
6279 let h = (node_bounds.size.height - inset.inset_top - inset.inset_bottom).max(0.0);
6280 let radius = match inset.border_radius {
6281 azul_css::corety::OptionF32::Some(r) => r,
6282 azul_css::corety::OptionF32::None => 0.0,
6283 };
6284 Some((LogicalRect {
6285 origin: LogicalPosition::new(x, y),
6286 size: LogicalSize::new(w, h),
6287 }, radius))
6288 }
6289 CssShape::Circle(circle) => {
6290 let cx = node_bounds.origin.x + circle.center.x;
6294 let cy = node_bounds.origin.y + circle.center.y;
6295 let r = circle.radius;
6296 Some((LogicalRect {
6297 origin: LogicalPosition::new(cx - r, cy - r),
6298 size: LogicalSize::new(r * 2.0, r * 2.0),
6299 }, r))
6300 }
6301 CssShape::Ellipse(ellipse) => {
6302 let cx = node_bounds.origin.x + ellipse.center.x;
6304 let cy = node_bounds.origin.y + ellipse.center.y;
6305 let rx = ellipse.radius_x;
6306 let ry = ellipse.radius_y;
6307 let radius = rx.min(ry);
6308 Some((LogicalRect {
6309 origin: LogicalPosition::new(cx - rx, cy - ry),
6310 size: LogicalSize::new(rx * 2.0, ry * 2.0),
6311 }, radius))
6312 }
6313 CssShape::Polygon(polygon) => {
6314 if polygon.points.is_empty() {
6316 return None;
6317 }
6318 let mut min_x = f32::INFINITY;
6319 let mut min_y = f32::INFINITY;
6320 let mut max_x = f32::NEG_INFINITY;
6321 let mut max_y = f32::NEG_INFINITY;
6322 for point in polygon.points.iter() {
6323 let px = node_bounds.origin.x + point.x;
6325 let py = node_bounds.origin.y + point.y;
6326 min_x = min_x.min(px);
6327 min_y = min_y.min(py);
6328 max_x = max_x.max(px);
6329 max_y = max_y.max(py);
6330 }
6331 Some((LogicalRect {
6332 origin: LogicalPosition::new(min_x, min_y),
6333 size: LogicalSize::new((max_x - min_x).max(0.0), (max_y - min_y).max(0.0)),
6334 }, 0.0))
6335 }
6336 CssShape::Path(_) => {
6337 None
6340 }
6341 }
6342 }
6343 }
6344}
6345
6346pub fn apply_clip_path(
6358 display_list: &mut DisplayList,
6359 start_index: usize,
6360 clip_rect: LogicalRect,
6361 border_radius: f32,
6362) {
6363 let br = if border_radius > 0.0 {
6364 BorderRadius {
6365 top_left: border_radius,
6366 top_right: border_radius,
6367 bottom_left: border_radius,
6368 bottom_right: border_radius,
6369 }
6370 } else {
6371 BorderRadius::default()
6372 };
6373
6374 display_list.items.insert(start_index, DisplayListItem::PushClip {
6376 bounds: clip_rect.into(),
6377 border_radius: br,
6378 });
6379 if display_list.node_mapping.len() >= start_index {
6381 display_list.node_mapping.insert(start_index, None);
6382 }
6383
6384 display_list.items.push(DisplayListItem::PopClip);
6386 display_list.node_mapping.push(None);
6387}
6388
6389#[cfg(feature = "cpurender")]
6393fn rasterize_svg_clip_to_r8(
6394 svg_clip: &azul_core::svg::SvgMultiPolygon,
6395 paint_rect: &LogicalRect,
6396) -> Option<azul_core::resources::ImageRef> {
6397 use agg_rust::{
6398 basics::FillingRule,
6399 color::Rgba8,
6400 path_storage::PathStorage,
6401 pixfmt_rgba::PixfmtRgba32,
6402 rasterizer_scanline_aa::RasterizerScanlineAa,
6403 renderer_base::RendererBase,
6404 renderer_scanline::render_scanlines_aa_solid,
6405 rendering_buffer::RowAccessor,
6406 scanline_u::ScanlineU8,
6407 };
6408 use azul_core::resources::{ImageRef, RawImage, RawImageFormat, RawImageData};
6409
6410 let w = paint_rect.size.width.ceil() as u32;
6411 let h = paint_rect.size.height.ceil() as u32;
6412 if w == 0 || h == 0 {
6413 return None;
6414 }
6415
6416 let mut path = PathStorage::new();
6418 for ring in svg_clip.rings.as_ref().iter() {
6419 let mut first = true;
6420 for item in ring.items.as_ref().iter() {
6421 match item {
6422 azul_core::svg::SvgPathElement::Line(l) => {
6423 if first {
6424 path.move_to(
6425 (l.start.x - paint_rect.origin.x) as f64,
6426 (l.start.y - paint_rect.origin.y) as f64,
6427 );
6428 first = false;
6429 }
6430 path.line_to(
6431 (l.end.x - paint_rect.origin.x) as f64,
6432 (l.end.y - paint_rect.origin.y) as f64,
6433 );
6434 }
6435 azul_core::svg::SvgPathElement::QuadraticCurve(q) => {
6436 if first {
6437 path.move_to(
6438 (q.start.x - paint_rect.origin.x) as f64,
6439 (q.start.y - paint_rect.origin.y) as f64,
6440 );
6441 first = false;
6442 }
6443 path.curve3(
6444 (q.ctrl.x - paint_rect.origin.x) as f64,
6445 (q.ctrl.y - paint_rect.origin.y) as f64,
6446 (q.end.x - paint_rect.origin.x) as f64,
6447 (q.end.y - paint_rect.origin.y) as f64,
6448 );
6449 }
6450 azul_core::svg::SvgPathElement::CubicCurve(c) => {
6451 if first {
6452 path.move_to(
6453 (c.start.x - paint_rect.origin.x) as f64,
6454 (c.start.y - paint_rect.origin.y) as f64,
6455 );
6456 first = false;
6457 }
6458 path.curve4(
6459 (c.ctrl_1.x - paint_rect.origin.x) as f64,
6460 (c.ctrl_1.y - paint_rect.origin.y) as f64,
6461 (c.ctrl_2.x - paint_rect.origin.x) as f64,
6462 (c.ctrl_2.y - paint_rect.origin.y) as f64,
6463 (c.end.x - paint_rect.origin.x) as f64,
6464 (c.end.y - paint_rect.origin.y) as f64,
6465 );
6466 }
6467 }
6468 }
6469 }
6470
6471 let mut rgba_buf = vec![0u8; (w * h * 4) as usize];
6473 {
6474 let stride = (w * 4) as i32;
6475 let mut ra = unsafe {
6476 RowAccessor::new_with_buf(rgba_buf.as_mut_ptr(), w, h, stride)
6477 };
6478 let pf = PixfmtRgba32::new(&mut ra);
6479 let mut rb = RendererBase::new(pf);
6480
6481 let mut ras = RasterizerScanlineAa::new();
6482 ras.filling_rule(FillingRule::NonZero);
6483 ras.add_path(&mut path, 0);
6484
6485 let mut sl = ScanlineU8::new();
6486 let white = Rgba8 { r: 255, g: 255, b: 255, a: 255 };
6487 render_scanlines_aa_solid(&mut ras, &mut sl, &mut rb, &white);
6488 }
6489
6490 let r8_data: Vec<u8> = rgba_buf.chunks_exact(4).map(|px| px[3]).collect();
6492
6493 ImageRef::new_rawimage(RawImage {
6494 pixels: RawImageData::U8(r8_data.into()),
6495 width: w as usize,
6496 height: h as usize,
6497 premultiplied_alpha: false,
6498 data_format: RawImageFormat::R8,
6499 tag: Vec::new().into(),
6500 })
6501}