1use std::{collections::BTreeMap, sync::Arc};
4
5use allsorts::glyph_position;
6use azul_core::{
7 dom::{DomId, FormattingContext, NodeId, NodeType, ScrollbarOrientation},
8 geom::{LogicalPosition, LogicalRect, LogicalSize},
9 gpu::GpuValueCache,
10 hit_test::ScrollPosition,
11 hit_test_tag::{CursorType, TAG_TYPE_CURSOR},
12 resources::{
13 IdNamespace, ImageRef, OpacityKey, RendererResources, TransformKey,
14 },
15 transform::ComputedTransform3D,
16 selection::{Selection, SelectionRange, SelectionState, TextSelection},
17 styled_dom::StyledDom,
18 ui_solver::GlyphInstance,
19};
20use azul_css::{
21 css::CssPropertyValue,
22 format_rust_code::GetHash,
23 props::{
24 basic::{ColorU, FontRef, PixelValue},
25 layout::{LayoutDisplay, LayoutOverflow, LayoutPosition},
26 property::{CssProperty, CssPropertyType},
27 style::{
28 background::{ConicGradient, ExtendMode, LinearGradient, RadialGradient},
29 border_radius::StyleBorderRadius,
30 box_shadow::{BoxShadowClipMode, StyleBoxShadow},
31 filter::{StyleFilter, StyleFilterVec},
32 BorderStyle, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth,
33 LayoutBorderTopWidth, StyleBorderBottomColor, StyleBorderBottomStyle,
34 StyleBorderLeftColor, StyleBorderLeftStyle, StyleBorderRightColor,
35 StyleBorderRightStyle, StyleBorderTopColor, StyleBorderTopStyle,
36 },
37 },
38 LayoutDebugMessage,
39};
40
41#[cfg(feature = "text_layout")]
42use crate::text3;
43#[cfg(feature = "text_layout")]
44use crate::text3::cache::{InlineShape, PositionedItem};
45use crate::{
46 debug_info,
47 font_traits::{
48 FontHash, FontLoaderTrait, ImageSource, InlineContent, ParsedFontTrait, ShapedItem,
49 UnifiedLayout,
50 },
51 solver3::{
52 getters::{
53 get_background_color, get_background_contents, get_border_info, get_border_radius,
54 get_break_after, get_break_before, get_caret_style, get_overflow_x, get_overflow_y,
55 get_scrollbar_info_from_layout, get_scrollbar_style, get_selection_style,
56 get_style_border_radius, get_z_index, is_forced_page_break, BorderInfo, CaretStyle,
57 ComputedScrollbarStyle, SelectionStyle,
58 },
59 layout_tree::{LayoutNode, LayoutTree},
60 positioning::get_position_type,
61 scrollbar::ScrollbarRequirements,
62 LayoutContext, LayoutError, Result,
63 },
64};
65
66#[derive(Debug, Clone, Copy)]
71pub struct StyleBorderWidths {
72 pub top: Option<CssPropertyValue<LayoutBorderTopWidth>>,
74 pub right: Option<CssPropertyValue<LayoutBorderRightWidth>>,
76 pub bottom: Option<CssPropertyValue<LayoutBorderBottomWidth>>,
78 pub left: Option<CssPropertyValue<LayoutBorderLeftWidth>>,
80}
81
82#[derive(Debug, Clone, Copy)]
87pub struct StyleBorderColors {
88 pub top: Option<CssPropertyValue<StyleBorderTopColor>>,
90 pub right: Option<CssPropertyValue<StyleBorderRightColor>>,
92 pub bottom: Option<CssPropertyValue<StyleBorderBottomColor>>,
94 pub left: Option<CssPropertyValue<StyleBorderLeftColor>>,
96}
97
98#[derive(Debug, Clone, Copy)]
104pub struct StyleBorderStyles {
105 pub top: Option<CssPropertyValue<StyleBorderTopStyle>>,
107 pub right: Option<CssPropertyValue<StyleBorderRightStyle>>,
109 pub bottom: Option<CssPropertyValue<StyleBorderBottomStyle>>,
111 pub left: Option<CssPropertyValue<StyleBorderLeftStyle>>,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq)]
118pub struct BorderBoxRect(pub LogicalRect);
119
120#[derive(Debug, Clone, Copy)]
122pub struct PhysicalSizeImport {
123 pub width: f32,
124 pub height: f32,
125}
126
127#[derive(Debug, Clone)]
135pub struct ScrollbarDrawInfo {
136 pub bounds: LogicalRect,
138 pub orientation: ScrollbarOrientation,
140
141 pub track_bounds: LogicalRect,
144 pub track_color: ColorU,
146
147 pub thumb_bounds: LogicalRect,
150 pub thumb_color: ColorU,
152 pub thumb_border_radius: BorderRadius,
154
155 pub button_decrement_bounds: Option<LogicalRect>,
158 pub button_increment_bounds: Option<LogicalRect>,
160 pub button_color: ColorU,
162
163 pub opacity_key: Option<OpacityKey>,
165 pub hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
167 pub clip_to_container_border: bool,
169 pub container_border_radius: BorderRadius,
171}
172
173impl BorderBoxRect {
174 pub fn to_content_box(
177 self,
178 padding: &crate::solver3::geometry::EdgeSizes,
179 border: &crate::solver3::geometry::EdgeSizes,
180 ) -> ContentBoxRect {
181 ContentBoxRect(LogicalRect {
182 origin: LogicalPosition {
183 x: self.0.origin.x + padding.left + border.left,
184 y: self.0.origin.y + padding.top + border.top,
185 },
186 size: LogicalSize {
187 width: self.0.size.width
188 - padding.left
189 - padding.right
190 - border.left
191 - border.right,
192 height: self.0.size.height
193 - padding.top
194 - padding.bottom
195 - border.top
196 - border.bottom,
197 },
198 })
199 }
200
201 pub fn rect(&self) -> LogicalRect {
203 self.0
204 }
205}
206
207#[derive(Debug, Clone, Copy, PartialEq)]
210pub struct ContentBoxRect(pub LogicalRect);
211
212impl ContentBoxRect {
213 pub fn rect(&self) -> LogicalRect {
215 self.0
216 }
217}
218
219#[derive(Debug, Default)]
224pub struct DisplayList {
225 pub items: Vec<DisplayListItem>,
226 pub node_mapping: Vec<Option<NodeId>>,
230 pub forced_page_breaks: Vec<f32>,
234}
235
236impl DisplayList {
237 pub fn to_debug_json(&self) -> String {
240 use std::fmt::Write;
241 let mut json = String::new();
242 writeln!(json, "{{").unwrap();
243 writeln!(json, " \"total_items\": {},", self.items.len()).unwrap();
244 writeln!(json, " \"items\": [").unwrap();
245
246 let mut clip_depth = 0i32;
247 let mut scroll_depth = 0i32;
248 let mut stacking_depth = 0i32;
249
250 for (i, item) in self.items.iter().enumerate() {
251 let comma = if i < self.items.len() - 1 { "," } else { "" };
252 let node_id = self.node_mapping.get(i).and_then(|n| *n);
253
254 match item {
255 DisplayListItem::PushClip {
256 bounds,
257 border_radius,
258 } => {
259 clip_depth += 1;
260 writeln!(json, " {{").unwrap();
261 writeln!(json, " \"index\": {},", i).unwrap();
262 writeln!(json, " \"type\": \"PushClip\",").unwrap();
263 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
264 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
265 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
266 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
267 writeln!(json, " \"border_radius\": {{ \"tl\": {:.1}, \"tr\": {:.1}, \"bl\": {:.1}, \"br\": {:.1} }},",
268 border_radius.top_left, border_radius.top_right,
269 border_radius.bottom_left, border_radius.bottom_right).unwrap();
270 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
271 writeln!(json, " }}{}", comma).unwrap();
272 }
273 DisplayListItem::PopClip => {
274 writeln!(json, " {{").unwrap();
275 writeln!(json, " \"index\": {},", i).unwrap();
276 writeln!(json, " \"type\": \"PopClip\",").unwrap();
277 writeln!(json, " \"clip_depth_before\": {},", clip_depth).unwrap();
278 writeln!(json, " \"clip_depth_after\": {}", clip_depth - 1).unwrap();
279 writeln!(json, " }}{}", comma).unwrap();
280 clip_depth -= 1;
281 }
282 DisplayListItem::PushScrollFrame {
283 clip_bounds,
284 content_size,
285 scroll_id,
286 } => {
287 scroll_depth += 1;
288 writeln!(json, " {{").unwrap();
289 writeln!(json, " \"index\": {},", i).unwrap();
290 writeln!(json, " \"type\": \"PushScrollFrame\",").unwrap();
291 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
292 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
293 writeln!(json, " \"clip_bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
294 clip_bounds.origin.x, clip_bounds.origin.y,
295 clip_bounds.size.width, clip_bounds.size.height).unwrap();
296 writeln!(
297 json,
298 " \"content_size\": {{ \"w\": {:.1}, \"h\": {:.1} }},",
299 content_size.width, content_size.height
300 )
301 .unwrap();
302 writeln!(json, " \"scroll_id\": {},", scroll_id).unwrap();
303 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
304 writeln!(json, " }}{}", comma).unwrap();
305 }
306 DisplayListItem::PopScrollFrame => {
307 writeln!(json, " {{").unwrap();
308 writeln!(json, " \"index\": {},", i).unwrap();
309 writeln!(json, " \"type\": \"PopScrollFrame\",").unwrap();
310 writeln!(json, " \"scroll_depth_before\": {},", scroll_depth).unwrap();
311 writeln!(json, " \"scroll_depth_after\": {}", scroll_depth - 1).unwrap();
312 writeln!(json, " }}{}", comma).unwrap();
313 scroll_depth -= 1;
314 }
315 DisplayListItem::PushStackingContext { z_index, bounds } => {
316 stacking_depth += 1;
317 writeln!(json, " {{").unwrap();
318 writeln!(json, " \"index\": {},", i).unwrap();
319 writeln!(json, " \"type\": \"PushStackingContext\",").unwrap();
320 writeln!(json, " \"stacking_depth\": {},", stacking_depth).unwrap();
321 writeln!(json, " \"z_index\": {},", z_index).unwrap();
322 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
323 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
324 writeln!(json, " }}{}", comma).unwrap();
325 }
326 DisplayListItem::PopStackingContext => {
327 writeln!(json, " {{").unwrap();
328 writeln!(json, " \"index\": {},", i).unwrap();
329 writeln!(json, " \"type\": \"PopStackingContext\",").unwrap();
330 writeln!(json, " \"stacking_depth_before\": {},", stacking_depth).unwrap();
331 writeln!(
332 json,
333 " \"stacking_depth_after\": {}",
334 stacking_depth - 1
335 )
336 .unwrap();
337 writeln!(json, " }}{}", comma).unwrap();
338 stacking_depth -= 1;
339 }
340 DisplayListItem::Rect {
341 bounds,
342 color,
343 border_radius,
344 } => {
345 writeln!(json, " {{").unwrap();
346 writeln!(json, " \"index\": {},", i).unwrap();
347 writeln!(json, " \"type\": \"Rect\",").unwrap();
348 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
349 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
350 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
351 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
352 writeln!(
353 json,
354 " \"color\": \"rgba({},{},{},{})\",",
355 color.r, color.g, color.b, color.a
356 )
357 .unwrap();
358 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
359 writeln!(json, " }}{}", comma).unwrap();
360 }
361 DisplayListItem::Border { bounds, .. } => {
362 writeln!(json, " {{").unwrap();
363 writeln!(json, " \"index\": {},", i).unwrap();
364 writeln!(json, " \"type\": \"Border\",").unwrap();
365 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
366 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
367 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
368 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
369 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
370 writeln!(json, " }}{}", comma).unwrap();
371 }
372 DisplayListItem::ScrollBarStyled { info } => {
373 writeln!(json, " {{").unwrap();
374 writeln!(json, " \"index\": {},", i).unwrap();
375 writeln!(json, " \"type\": \"ScrollBarStyled\",").unwrap();
376 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
377 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
378 writeln!(json, " \"orientation\": \"{:?}\",", info.orientation).unwrap();
379 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
380 info.bounds.origin.x, info.bounds.origin.y,
381 info.bounds.size.width, info.bounds.size.height).unwrap();
382 writeln!(json, " }}{}", comma).unwrap();
383 }
384 _ => {
385 writeln!(json, " {{").unwrap();
386 writeln!(json, " \"index\": {},", i).unwrap();
387 writeln!(
388 json,
389 " \"type\": \"{:?}\",",
390 std::mem::discriminant(item)
391 )
392 .unwrap();
393 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
394 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
395 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
396 writeln!(json, " }}{}", comma).unwrap();
397 }
398 }
399 }
400
401 writeln!(json, " ],").unwrap();
402 writeln!(json, " \"final_clip_depth\": {},", clip_depth).unwrap();
403 writeln!(json, " \"final_scroll_depth\": {},", scroll_depth).unwrap();
404 writeln!(json, " \"final_stacking_depth\": {},", stacking_depth).unwrap();
405 writeln!(
406 json,
407 " \"balanced\": {}",
408 clip_depth == 0 && scroll_depth == 0 && stacking_depth == 0
409 )
410 .unwrap();
411 writeln!(json, "}}").unwrap();
412
413 json
414 }
415}
416
417#[derive(Debug, Clone)]
420pub enum DisplayListItem {
421 Rect {
425 bounds: LogicalRect,
427 color: ColorU,
429 border_radius: BorderRadius,
431 },
432 SelectionRect {
435 bounds: LogicalRect,
437 border_radius: BorderRadius,
439 color: ColorU,
441 },
442 CursorRect {
445 bounds: LogicalRect,
447 color: ColorU,
449 },
450 Border {
453 bounds: LogicalRect,
455 widths: StyleBorderWidths,
457 colors: StyleBorderColors,
459 styles: StyleBorderStyles,
461 border_radius: StyleBorderRadius,
463 },
464 TextLayout {
468 layout: Arc<dyn std::any::Any + Send + Sync>, bounds: LogicalRect,
470 font_hash: FontHash,
471 font_size_px: f32,
472 color: ColorU,
473 },
474 Text {
476 glyphs: Vec<GlyphInstance>,
477 font_hash: FontHash, font_size_px: f32,
479 color: ColorU,
480 clip_rect: LogicalRect,
481 },
482 Underline {
484 bounds: LogicalRect,
485 color: ColorU,
486 thickness: f32,
487 },
488 Strikethrough {
490 bounds: LogicalRect,
491 color: ColorU,
492 thickness: f32,
493 },
494 Overline {
496 bounds: LogicalRect,
497 color: ColorU,
498 thickness: f32,
499 },
500 Image {
501 bounds: LogicalRect,
502 image: ImageRef,
503 },
504 ScrollBar {
507 bounds: LogicalRect,
508 color: ColorU,
509 orientation: ScrollbarOrientation,
510 opacity_key: Option<OpacityKey>,
514 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
517 },
518 ScrollBarStyled {
521 info: Box<ScrollbarDrawInfo>,
523 },
524
525 IFrame {
529 child_dom_id: DomId,
531 bounds: LogicalRect,
533 clip_rect: LogicalRect,
535 },
536
537 PushClip {
541 bounds: LogicalRect,
542 border_radius: BorderRadius,
543 },
544 PopClip,
546
547 PushScrollFrame {
550 clip_bounds: LogicalRect,
552 content_size: LogicalSize,
554 scroll_id: LocalScrollId, },
557 PopScrollFrame,
559
560 PushStackingContext {
563 z_index: i32,
565 bounds: LogicalRect,
567 },
568 PopStackingContext,
570
571 PushReferenceFrame {
575 transform_key: TransformKey,
577 initial_transform: ComputedTransform3D,
579 bounds: LogicalRect,
581 },
582 PopReferenceFrame,
584
585 HitTestArea {
587 bounds: LogicalRect,
588 tag: DisplayListTagId, },
590
591 LinearGradient {
594 bounds: LogicalRect,
595 gradient: LinearGradient,
596 border_radius: BorderRadius,
597 },
598 RadialGradient {
600 bounds: LogicalRect,
601 gradient: RadialGradient,
602 border_radius: BorderRadius,
603 },
604 ConicGradient {
606 bounds: LogicalRect,
607 gradient: ConicGradient,
608 border_radius: BorderRadius,
609 },
610
611 BoxShadow {
614 bounds: LogicalRect,
615 shadow: StyleBoxShadow,
616 border_radius: BorderRadius,
617 },
618
619 PushFilter {
622 bounds: LogicalRect,
623 filters: Vec<StyleFilter>,
624 },
625 PopFilter,
627
628 PushBackdropFilter {
630 bounds: LogicalRect,
631 filters: Vec<StyleFilter>,
632 },
633 PopBackdropFilter,
635
636 PushOpacity {
638 bounds: LogicalRect,
639 opacity: f32,
640 },
641 PopOpacity,
643
644 PushTextShadow {
646 shadow: azul_css::props::style::box_shadow::StyleBoxShadow,
647 },
648 PopTextShadow,
650}
651
652#[derive(Debug, Copy, Clone, Default)]
654pub struct BorderRadius {
655 pub top_left: f32,
656 pub top_right: f32,
657 pub bottom_left: f32,
658 pub bottom_right: f32,
659}
660
661impl BorderRadius {
662 pub fn is_zero(&self) -> bool {
663 self.top_left == 0.0
664 && self.top_right == 0.0
665 && self.bottom_left == 0.0
666 && self.bottom_right == 0.0
667 }
668}
669
670pub type LocalScrollId = u64;
672pub type DisplayListTagId = (u64, u16);
677
678#[derive(Debug, Default)]
680struct DisplayListBuilder {
681 items: Vec<DisplayListItem>,
682 node_mapping: Vec<Option<NodeId>>,
683 current_node: Option<NodeId>,
685 debug_messages: Vec<LayoutDebugMessage>,
687 debug_enabled: bool,
689 forced_page_breaks: Vec<f32>,
691}
692
693impl DisplayListBuilder {
694 pub fn new() -> Self {
695 Self::default()
696 }
697
698 pub fn with_debug(debug_enabled: bool) -> Self {
699 Self {
700 items: Vec::new(),
701 node_mapping: Vec::new(),
702 current_node: None,
703 debug_messages: Vec::new(),
704 debug_enabled,
705 forced_page_breaks: Vec::new(),
706 }
707 }
708
709 fn debug_log(&mut self, message: String) {
711 if self.debug_enabled {
712 self.debug_messages.push(LayoutDebugMessage::info(message));
713 }
714 }
715
716 pub fn build_with_debug(
718 mut self,
719 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
720 ) -> DisplayList {
721 if let Some(msgs) = debug_messages.as_mut() {
723 msgs.append(&mut self.debug_messages);
724 }
725 DisplayList {
726 items: self.items,
727 node_mapping: self.node_mapping,
728 forced_page_breaks: self.forced_page_breaks,
729 }
730 }
731
732 pub fn set_current_node(&mut self, node_id: Option<NodeId>) {
734 self.current_node = node_id;
735 }
736
737 pub fn add_forced_page_break(&mut self, y_position: f32) {
740 if !self.forced_page_breaks.contains(&y_position) {
742 self.forced_page_breaks.push(y_position);
743 self.forced_page_breaks.sort_by(|a, b| a.partial_cmp(b).unwrap());
744 }
745 }
746
747 fn push_item(&mut self, item: DisplayListItem) {
749 self.items.push(item);
750 self.node_mapping.push(self.current_node);
751 }
752
753 pub fn build(self) -> DisplayList {
754 DisplayList {
755 items: self.items,
756 node_mapping: self.node_mapping,
757 forced_page_breaks: self.forced_page_breaks,
758 }
759 }
760
761 pub fn push_hit_test_area(&mut self, bounds: LogicalRect, tag: DisplayListTagId) {
762 self.push_item(DisplayListItem::HitTestArea { bounds, tag });
763 }
764
765 pub fn push_scrollbar(
767 &mut self,
768 bounds: LogicalRect,
769 color: ColorU,
770 orientation: ScrollbarOrientation,
771 opacity_key: Option<OpacityKey>,
772 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
773 ) {
774 if color.a > 0 || opacity_key.is_some() {
775 self.push_item(DisplayListItem::ScrollBar {
777 bounds,
778 color,
779 orientation,
780 opacity_key,
781 hit_id,
782 });
783 }
784 }
785
786 pub fn push_scrollbar_styled(&mut self, info: ScrollbarDrawInfo) {
788 if info.thumb_color.a > 0 || info.track_color.a > 0 || info.opacity_key.is_some() {
790 self.push_item(DisplayListItem::ScrollBarStyled {
791 info: Box::new(info),
792 });
793 }
794 }
795
796 pub fn push_rect(&mut self, bounds: LogicalRect, color: ColorU, border_radius: BorderRadius) {
797 if color.a > 0 {
798 self.push_item(DisplayListItem::Rect {
800 bounds,
801 color,
802 border_radius,
803 });
804 }
805 }
806
807 pub fn push_backgrounds_and_border(
817 &mut self,
818 bounds: LogicalRect,
819 background_contents: &[azul_css::props::style::StyleBackgroundContent],
820 border_info: &BorderInfo,
821 simple_border_radius: BorderRadius,
822 style_border_radius: StyleBorderRadius,
823 ) {
824 use azul_css::props::style::StyleBackgroundContent;
825
826 for bg in background_contents {
828 match bg {
829 StyleBackgroundContent::Color(color) => {
830 self.push_rect(bounds, *color, simple_border_radius);
831 }
832 StyleBackgroundContent::LinearGradient(gradient) => {
833 self.push_linear_gradient(bounds, gradient.clone(), simple_border_radius);
834 }
835 StyleBackgroundContent::RadialGradient(gradient) => {
836 self.push_radial_gradient(bounds, gradient.clone(), simple_border_radius);
837 }
838 StyleBackgroundContent::ConicGradient(gradient) => {
839 self.push_conic_gradient(bounds, gradient.clone(), simple_border_radius);
840 }
841 StyleBackgroundContent::Image(_image_id) => {
842 }
844 }
845 }
846
847 self.push_border(
849 bounds,
850 border_info.widths,
851 border_info.colors,
852 border_info.styles,
853 style_border_radius,
854 );
855 }
856
857 pub fn push_inline_backgrounds_and_border(
864 &mut self,
865 bounds: LogicalRect,
866 background_color: Option<ColorU>,
867 background_contents: &[azul_css::props::style::StyleBackgroundContent],
868 border: Option<&crate::text3::cache::InlineBorderInfo>,
869 ) {
870 use azul_css::props::style::StyleBackgroundContent;
871
872 if let Some(bg_color) = background_color {
874 self.push_rect(bounds, bg_color, BorderRadius::default());
875 }
876
877 for bg in background_contents {
879 match bg {
880 StyleBackgroundContent::Color(color) => {
881 self.push_rect(bounds, *color, BorderRadius::default());
882 }
883 StyleBackgroundContent::LinearGradient(gradient) => {
884 self.push_linear_gradient(bounds, gradient.clone(), BorderRadius::default());
885 }
886 StyleBackgroundContent::RadialGradient(gradient) => {
887 self.push_radial_gradient(bounds, gradient.clone(), BorderRadius::default());
888 }
889 StyleBackgroundContent::ConicGradient(gradient) => {
890 self.push_conic_gradient(bounds, gradient.clone(), BorderRadius::default());
891 }
892 StyleBackgroundContent::Image(_image_id) => {
893 }
895 }
896 }
897
898 if let Some(border) = border {
900 if border.top > 0.0 || border.right > 0.0 || border.bottom > 0.0 || border.left > 0.0 {
901 let border_widths = StyleBorderWidths {
902 top: Some(CssPropertyValue::Exact(LayoutBorderTopWidth {
903 inner: PixelValue::px(border.top),
904 })),
905 right: Some(CssPropertyValue::Exact(LayoutBorderRightWidth {
906 inner: PixelValue::px(border.right),
907 })),
908 bottom: Some(CssPropertyValue::Exact(LayoutBorderBottomWidth {
909 inner: PixelValue::px(border.bottom),
910 })),
911 left: Some(CssPropertyValue::Exact(LayoutBorderLeftWidth {
912 inner: PixelValue::px(border.left),
913 })),
914 };
915 let border_colors = StyleBorderColors {
916 top: Some(CssPropertyValue::Exact(StyleBorderTopColor {
917 inner: border.top_color,
918 })),
919 right: Some(CssPropertyValue::Exact(StyleBorderRightColor {
920 inner: border.right_color,
921 })),
922 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomColor {
923 inner: border.bottom_color,
924 })),
925 left: Some(CssPropertyValue::Exact(StyleBorderLeftColor {
926 inner: border.left_color,
927 })),
928 };
929 let border_styles = StyleBorderStyles {
930 top: Some(CssPropertyValue::Exact(StyleBorderTopStyle {
931 inner: BorderStyle::Solid,
932 })),
933 right: Some(CssPropertyValue::Exact(StyleBorderRightStyle {
934 inner: BorderStyle::Solid,
935 })),
936 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomStyle {
937 inner: BorderStyle::Solid,
938 })),
939 left: Some(CssPropertyValue::Exact(StyleBorderLeftStyle {
940 inner: BorderStyle::Solid,
941 })),
942 };
943 let radius_px = PixelValue::px(border.radius.unwrap_or(0.0));
944 let border_radius = StyleBorderRadius {
945 top_left: radius_px,
946 top_right: radius_px,
947 bottom_left: radius_px,
948 bottom_right: radius_px,
949 };
950
951 self.push_border(
952 bounds,
953 border_widths,
954 border_colors,
955 border_styles,
956 border_radius,
957 );
958 }
959 }
960 }
961
962 pub fn push_linear_gradient(
964 &mut self,
965 bounds: LogicalRect,
966 gradient: LinearGradient,
967 border_radius: BorderRadius,
968 ) {
969 self.push_item(DisplayListItem::LinearGradient {
970 bounds,
971 gradient,
972 border_radius,
973 });
974 }
975
976 pub fn push_radial_gradient(
978 &mut self,
979 bounds: LogicalRect,
980 gradient: RadialGradient,
981 border_radius: BorderRadius,
982 ) {
983 self.push_item(DisplayListItem::RadialGradient {
984 bounds,
985 gradient,
986 border_radius,
987 });
988 }
989
990 pub fn push_conic_gradient(
992 &mut self,
993 bounds: LogicalRect,
994 gradient: ConicGradient,
995 border_radius: BorderRadius,
996 ) {
997 self.push_item(DisplayListItem::ConicGradient {
998 bounds,
999 gradient,
1000 border_radius,
1001 });
1002 }
1003
1004 pub fn push_selection_rect(
1005 &mut self,
1006 bounds: LogicalRect,
1007 color: ColorU,
1008 border_radius: BorderRadius,
1009 ) {
1010 if color.a > 0 {
1011 self.push_item(DisplayListItem::SelectionRect {
1012 bounds,
1013 color,
1014 border_radius,
1015 });
1016 }
1017 }
1018
1019 pub fn push_cursor_rect(&mut self, bounds: LogicalRect, color: ColorU) {
1020 if color.a > 0 {
1021 self.push_item(DisplayListItem::CursorRect { bounds, color });
1022 }
1023 }
1024 pub fn push_clip(&mut self, bounds: LogicalRect, border_radius: BorderRadius) {
1025 self.push_item(DisplayListItem::PushClip {
1026 bounds,
1027 border_radius,
1028 });
1029 }
1030 pub fn pop_clip(&mut self) {
1031 self.push_item(DisplayListItem::PopClip);
1032 }
1033 pub fn push_scroll_frame(
1034 &mut self,
1035 clip_bounds: LogicalRect,
1036 content_size: LogicalSize,
1037 scroll_id: LocalScrollId,
1038 ) {
1039 self.push_item(DisplayListItem::PushScrollFrame {
1040 clip_bounds,
1041 content_size,
1042 scroll_id,
1043 });
1044 }
1045 pub fn pop_scroll_frame(&mut self) {
1046 self.push_item(DisplayListItem::PopScrollFrame);
1047 }
1048 pub fn push_border(
1049 &mut self,
1050 bounds: LogicalRect,
1051 widths: StyleBorderWidths,
1052 colors: StyleBorderColors,
1053 styles: StyleBorderStyles,
1054 border_radius: StyleBorderRadius,
1055 ) {
1056 let has_visible_border = {
1058 let has_width = widths.top.is_some()
1059 || widths.right.is_some()
1060 || widths.bottom.is_some()
1061 || widths.left.is_some();
1062 let has_style = styles.top.is_some()
1063 || styles.right.is_some()
1064 || styles.bottom.is_some()
1065 || styles.left.is_some();
1066 has_width && has_style
1067 };
1068
1069 if has_visible_border {
1070 self.push_item(DisplayListItem::Border {
1071 bounds,
1072 widths,
1073 colors,
1074 styles,
1075 border_radius,
1076 });
1077 }
1078 }
1079
1080 pub fn push_stacking_context(&mut self, z_index: i32, bounds: LogicalRect) {
1081 self.push_item(DisplayListItem::PushStackingContext { z_index, bounds });
1082 }
1083
1084 pub fn pop_stacking_context(&mut self) {
1085 self.push_item(DisplayListItem::PopStackingContext);
1086 }
1087
1088 pub fn push_reference_frame(
1089 &mut self,
1090 transform_key: TransformKey,
1091 initial_transform: ComputedTransform3D,
1092 bounds: LogicalRect,
1093 ) {
1094 self.push_item(DisplayListItem::PushReferenceFrame {
1095 transform_key,
1096 initial_transform,
1097 bounds,
1098 });
1099 }
1100
1101 pub fn pop_reference_frame(&mut self) {
1102 self.push_item(DisplayListItem::PopReferenceFrame);
1103 }
1104
1105 pub fn push_text_run(
1106 &mut self,
1107 glyphs: Vec<GlyphInstance>,
1108 font_hash: FontHash, font_size_px: f32,
1110 color: ColorU,
1111 clip_rect: LogicalRect,
1112 ) {
1113 self.debug_log(format!(
1114 "[push_text_run] {} glyphs, font_size={}px, color=({},{},{},{}), clip={:?}",
1115 glyphs.len(),
1116 font_size_px,
1117 color.r,
1118 color.g,
1119 color.b,
1120 color.a,
1121 clip_rect
1122 ));
1123
1124 if !glyphs.is_empty() && color.a > 0 {
1125 self.push_item(DisplayListItem::Text {
1126 glyphs,
1127 font_hash,
1128 font_size_px,
1129 color,
1130 clip_rect,
1131 });
1132 } else {
1133 self.debug_log(format!(
1134 "[push_text_run] SKIPPED: glyphs.is_empty()={}, color.a={}",
1135 glyphs.is_empty(),
1136 color.a
1137 ));
1138 }
1139 }
1140
1141 pub fn push_text_layout(
1142 &mut self,
1143 layout: Arc<dyn std::any::Any + Send + Sync>,
1144 bounds: LogicalRect,
1145 font_hash: FontHash,
1146 font_size_px: f32,
1147 color: ColorU,
1148 ) {
1149 if color.a > 0 {
1150 self.push_item(DisplayListItem::TextLayout {
1151 layout,
1152 bounds,
1153 font_hash,
1154 font_size_px,
1155 color,
1156 });
1157 }
1158 }
1159
1160 pub fn push_underline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1161 if color.a > 0 && thickness > 0.0 {
1162 self.push_item(DisplayListItem::Underline {
1163 bounds,
1164 color,
1165 thickness,
1166 });
1167 }
1168 }
1169
1170 pub fn push_strikethrough(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1171 if color.a > 0 && thickness > 0.0 {
1172 self.push_item(DisplayListItem::Strikethrough {
1173 bounds,
1174 color,
1175 thickness,
1176 });
1177 }
1178 }
1179
1180 pub fn push_overline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1181 if color.a > 0 && thickness > 0.0 {
1182 self.push_item(DisplayListItem::Overline {
1183 bounds,
1184 color,
1185 thickness,
1186 });
1187 }
1188 }
1189
1190 pub fn push_image(&mut self, bounds: LogicalRect, image: ImageRef) {
1191 self.push_item(DisplayListItem::Image { bounds, image });
1192 }
1193}
1194
1195pub fn generate_display_list<T: ParsedFontTrait + Sync + 'static>(
1197 ctx: &mut LayoutContext<T>,
1198 tree: &LayoutTree,
1199 calculated_positions: &super::PositionVec,
1200 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
1201 scroll_ids: &BTreeMap<usize, u64>,
1202 gpu_value_cache: Option<&GpuValueCache>,
1203 renderer_resources: &RendererResources,
1204 id_namespace: IdNamespace,
1205 dom_id: DomId,
1206) -> Result<DisplayList> {
1207 debug_info!(
1208 ctx,
1209 "[DisplayList] generate_display_list: tree has {} nodes, {} positions calculated",
1210 tree.nodes.len(),
1211 calculated_positions.len()
1212 );
1213
1214 debug_info!(ctx, "Starting display list generation");
1215 debug_info!(
1216 ctx,
1217 "Collecting stacking contexts from root node {}",
1218 tree.root
1219 );
1220
1221 let positioned_tree = PositionedTree {
1222 tree,
1223 calculated_positions,
1224 };
1225 let mut generator = DisplayListGenerator::new(
1226 ctx,
1227 scroll_offsets,
1228 &positioned_tree,
1229 scroll_ids,
1230 gpu_value_cache,
1231 renderer_resources,
1232 id_namespace,
1233 dom_id,
1234 );
1235
1236 let debug_enabled = generator.ctx.debug_messages.is_some();
1238 let mut builder = DisplayListBuilder::with_debug(debug_enabled);
1239
1240 {
1247 let root_node = tree.get(tree.root);
1248 if let Some(root) = root_node {
1249 if let Some(root_dom_id) = root.dom_node_id {
1250 let root_state = generator.get_styled_node_state(root_dom_id);
1251 let canvas_bg = get_background_color(
1252 generator.ctx.styled_dom,
1253 root_dom_id,
1254 &root_state,
1255 );
1256 if canvas_bg.a > 0 {
1257 let viewport_rect = LogicalRect {
1258 origin: LogicalPosition::zero(),
1259 size: generator.ctx.viewport_size,
1260 };
1261 builder.push_rect(viewport_rect, canvas_bg, BorderRadius::default());
1262 debug_info!(
1263 generator.ctx,
1264 "[DisplayList] Canvas background: color=({},{},{},{}), size={:?}",
1265 canvas_bg.r, canvas_bg.g, canvas_bg.b, canvas_bg.a,
1266 generator.ctx.viewport_size
1267 );
1268 }
1269 }
1270 }
1271 }
1272
1273 let stacking_context_tree = generator.collect_stacking_contexts(tree.root)?;
1275
1276 debug_info!(
1278 generator.ctx,
1279 "Generating display items from stacking context tree"
1280 );
1281 generator.generate_for_stacking_context(&mut builder, &stacking_context_tree)?;
1282
1283 let display_list = builder.build_with_debug(generator.ctx.debug_messages);
1285 debug_info!(
1286 generator.ctx,
1287 "[DisplayList] Generated {} display items",
1288 display_list.items.len()
1289 );
1290 Ok(display_list)
1291}
1292
1293struct DisplayListGenerator<'a, 'b, T: ParsedFontTrait> {
1295 ctx: &'a mut LayoutContext<'b, T>,
1296 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1297 positioned_tree: &'a PositionedTree<'a>,
1298 scroll_ids: &'a BTreeMap<usize, u64>,
1299 gpu_value_cache: Option<&'a GpuValueCache>,
1300 renderer_resources: &'a RendererResources,
1301 id_namespace: IdNamespace,
1302 dom_id: DomId,
1303}
1304
1305#[derive(Debug)]
1307struct StackingContext {
1308 node_index: usize,
1309 z_index: i32,
1310 child_contexts: Vec<StackingContext>,
1311 in_flow_children: Vec<usize>,
1313}
1314
1315impl<'a, 'b, T> DisplayListGenerator<'a, 'b, T>
1316where
1317 T: ParsedFontTrait + Sync + 'static,
1318{
1319 pub fn new(
1320 ctx: &'a mut LayoutContext<'b, T>,
1321 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1322 positioned_tree: &'a PositionedTree<'a>,
1323 scroll_ids: &'a BTreeMap<usize, u64>,
1324 gpu_value_cache: Option<&'a GpuValueCache>,
1325 renderer_resources: &'a RendererResources,
1326 id_namespace: IdNamespace,
1327 dom_id: DomId,
1328 ) -> Self {
1329 Self {
1330 ctx,
1331 scroll_offsets,
1332 positioned_tree,
1333 scroll_ids,
1334 gpu_value_cache,
1335 renderer_resources,
1336 id_namespace,
1337 dom_id,
1338 }
1339 }
1340
1341 fn get_styled_node_state(&self, dom_id: NodeId) -> azul_core::styled_dom::StyledNodeState {
1343 self.ctx
1344 .styled_dom
1345 .styled_nodes
1346 .as_container()
1347 .get(dom_id)
1348 .map(|n| n.styled_node_state.clone())
1349 .unwrap_or_default()
1350 }
1351
1352 fn get_cursor_type_for_text_node(&self, node_id: NodeId) -> CursorType {
1355 use azul_css::props::style::effects::StyleCursor;
1356
1357 let styled_node_state = self.get_styled_node_state(node_id);
1358 let node_data_container = self.ctx.styled_dom.node_data.as_container();
1359 let node_data = node_data_container.get(node_id);
1360
1361 if let Some(node_data) = node_data {
1363 if let Some(cursor_value) = self.ctx.styled_dom.get_css_property_cache().get_cursor(
1364 node_data,
1365 &node_id,
1366 &styled_node_state,
1367 ) {
1368 if let CssPropertyValue::Exact(cursor) = cursor_value {
1369 return match cursor {
1370 StyleCursor::Default => CursorType::Default,
1371 StyleCursor::Pointer => CursorType::Pointer,
1372 StyleCursor::Text => CursorType::Text,
1373 StyleCursor::Crosshair => CursorType::Crosshair,
1374 StyleCursor::Move => CursorType::Move,
1375 StyleCursor::Help => CursorType::Help,
1376 StyleCursor::Wait => CursorType::Wait,
1377 StyleCursor::Progress => CursorType::Progress,
1378 StyleCursor::NsResize => CursorType::NsResize,
1379 StyleCursor::EwResize => CursorType::EwResize,
1380 StyleCursor::NeswResize => CursorType::NeswResize,
1381 StyleCursor::NwseResize => CursorType::NwseResize,
1382 StyleCursor::NResize => CursorType::NResize,
1383 StyleCursor::SResize => CursorType::SResize,
1384 StyleCursor::EResize => CursorType::EResize,
1385 StyleCursor::WResize => CursorType::WResize,
1386 StyleCursor::Grab => CursorType::Grab,
1387 StyleCursor::Grabbing => CursorType::Grabbing,
1388 StyleCursor::RowResize => CursorType::RowResize,
1389 StyleCursor::ColResize => CursorType::ColResize,
1390 StyleCursor::SeResize | StyleCursor::NeswResize => CursorType::NeswResize,
1392 StyleCursor::ZoomIn | StyleCursor::ZoomOut => CursorType::Default,
1393 StyleCursor::Copy | StyleCursor::Alias => CursorType::Default,
1394 StyleCursor::Cell => CursorType::Crosshair,
1395 StyleCursor::AllScroll => CursorType::Move,
1396 StyleCursor::ContextMenu => CursorType::Default,
1397 StyleCursor::VerticalText => CursorType::Text,
1398 StyleCursor::Unset => CursorType::Text, };
1400 }
1401 }
1402 }
1403
1404 CursorType::Text
1406 }
1407
1408 fn paint_selections(
1411 &self,
1412 builder: &mut DisplayListBuilder,
1413 node_index: usize,
1414 ) -> Result<()> {
1415 let node = self
1416 .positioned_tree
1417 .tree
1418 .get(node_index)
1419 .ok_or(LayoutError::InvalidTree)?;
1420 let Some(dom_id) = node.dom_node_id else {
1421 return Ok(());
1422 };
1423
1424 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
1428 return Ok(());
1429 };
1430
1431 let node_pos = self
1433 .positioned_tree
1434 .calculated_positions
1435 .get(node_index)
1436 .copied()
1437 .unwrap_or_default();
1438
1439 let padding = &node.box_props.padding;
1441 let border = &node.box_props.border;
1442 let content_box_offset_x = node_pos.x + padding.left + border.left;
1443 let content_box_offset_y = node_pos.y + padding.top + border.top;
1444
1445 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1447 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
1448
1449 if !is_selectable {
1450 return Ok(());
1451 }
1452
1453 if let Some(text_selection) = self.ctx.text_selections.get(&self.ctx.styled_dom.dom_id) {
1455 if let Some(range) = text_selection.affected_nodes.get(&dom_id) {
1456 let is_collapsed = text_selection.is_collapsed();
1457
1458 if !is_collapsed {
1460 let rects = layout.get_selection_rects(range);
1461 let style = get_selection_style(self.ctx.styled_dom, Some(dom_id), self.ctx.system_style.as_ref());
1462
1463 let border_radius = BorderRadius {
1464 top_left: style.radius,
1465 top_right: style.radius,
1466 bottom_left: style.radius,
1467 bottom_right: style.radius,
1468 };
1469
1470 for mut rect in rects {
1471 rect.origin.x += content_box_offset_x;
1472 rect.origin.y += content_box_offset_y;
1473 builder.push_selection_rect(rect, style.bg_color, border_radius);
1474 }
1475 }
1476
1477 return Ok(());
1478 }
1479 }
1480
1481 let Some(selection_state) = self.ctx.selections.get(&self.ctx.styled_dom.dom_id) else {
1483 return Ok(());
1484 };
1485
1486 if selection_state.node_id.node.into_crate_internal() != Some(dom_id) {
1487 return Ok(());
1488 }
1489
1490 for selection in selection_state.selections.as_slice() {
1491 if let Selection::Range(range) = &selection {
1492 let rects = layout.get_selection_rects(range);
1493 let style = get_selection_style(self.ctx.styled_dom, Some(dom_id), self.ctx.system_style.as_ref());
1494
1495 let border_radius = BorderRadius {
1496 top_left: style.radius,
1497 top_right: style.radius,
1498 bottom_left: style.radius,
1499 bottom_right: style.radius,
1500 };
1501
1502 for mut rect in rects {
1503 rect.origin.x += content_box_offset_x;
1504 rect.origin.y += content_box_offset_y;
1505 builder.push_selection_rect(rect, style.bg_color, border_radius);
1506 }
1507 }
1508 }
1509
1510 Ok(())
1511 }
1512
1513 fn paint_cursor(
1516 &self,
1517 builder: &mut DisplayListBuilder,
1518 node_index: usize,
1519 ) -> Result<()> {
1520 if !self.ctx.cursor_is_visible {
1522 return Ok(());
1523 }
1524
1525 let Some((cursor_dom_id, cursor_node_id, cursor)) = &self.ctx.cursor_location else {
1527 return Ok(());
1528 };
1529
1530 let node = self
1531 .positioned_tree
1532 .tree
1533 .get(node_index)
1534 .ok_or(LayoutError::InvalidTree)?;
1535 let Some(dom_id) = node.dom_node_id else {
1536 return Ok(());
1537 };
1538
1539 if dom_id != *cursor_node_id {
1541 return Ok(());
1542 }
1543
1544 if self.ctx.styled_dom.dom_id != *cursor_dom_id {
1546 return Ok(());
1547 }
1548
1549 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
1553 return Ok(());
1554 };
1555
1556 let is_contenteditable = super::getters::is_node_contenteditable_inherited(self.ctx.styled_dom, dom_id);
1559 if !is_contenteditable {
1560 return Ok(());
1561 }
1562
1563 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1565 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
1566 if !is_selectable {
1567 return Ok(());
1568 }
1569
1570 let Some(mut rect) = layout.get_cursor_rect(cursor) else {
1572 return Ok(());
1573 };
1574
1575 let node_pos = self
1577 .positioned_tree
1578 .calculated_positions
1579 .get(node_index)
1580 .copied()
1581 .unwrap_or_default();
1582
1583 let padding = &node.box_props.padding;
1585 let border = &node.box_props.border;
1586 let content_box_offset_x = node_pos.x + padding.left + border.left;
1587 let content_box_offset_y = node_pos.y + padding.top + border.top;
1588
1589 rect.origin.x += content_box_offset_x;
1590 rect.origin.y += content_box_offset_y;
1591
1592 let style = get_caret_style(self.ctx.styled_dom, Some(dom_id));
1593
1594 rect.size.width = style.width;
1596
1597 builder.push_cursor_rect(rect, style.color);
1598
1599 Ok(())
1600 }
1601
1602 fn paint_selection_and_cursor(
1605 &self,
1606 builder: &mut DisplayListBuilder,
1607 node_index: usize,
1608 ) -> Result<()> {
1609 self.paint_selections(builder, node_index)?;
1610 self.paint_cursor(builder, node_index)?;
1611 Ok(())
1612 }
1613
1614 fn collect_stacking_contexts(&mut self, node_index: usize) -> Result<StackingContext> {
1616 let node = self
1617 .positioned_tree
1618 .tree
1619 .get(node_index)
1620 .ok_or(LayoutError::InvalidTree)?;
1621 let z_index = get_z_index(self.ctx.styled_dom, node.dom_node_id);
1622
1623 if let Some(dom_id) = node.dom_node_id {
1624 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
1625 debug_info!(
1626 self.ctx,
1627 "Collecting stacking context for node {} ({:?}), z-index={}",
1628 node_index,
1629 node_type.get_node_type(),
1630 z_index
1631 );
1632 }
1633
1634 let mut child_contexts = Vec::new();
1635 let mut in_flow_children = Vec::new();
1636
1637 for &child_index in &node.children {
1638 if self.establishes_stacking_context(child_index) {
1639 child_contexts.push(self.collect_stacking_contexts(child_index)?);
1640 } else {
1641 in_flow_children.push(child_index);
1642 }
1643 }
1644
1645 Ok(StackingContext {
1646 node_index,
1647 z_index,
1648 child_contexts,
1649 in_flow_children,
1650 })
1651 }
1652
1653 fn generate_for_stacking_context(
1656 &mut self,
1657 builder: &mut DisplayListBuilder,
1658 context: &StackingContext,
1659 ) -> Result<()> {
1660 let node = self
1662 .positioned_tree
1663 .tree
1664 .get(context.node_index)
1665 .ok_or(LayoutError::InvalidTree)?;
1666
1667 if let Some(dom_id) = node.dom_node_id {
1668 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
1669 debug_info!(
1670 self.ctx,
1671 "Painting stacking context for node {} ({:?}), z-index={}, {} child contexts, {} \
1672 in-flow children",
1673 context.node_index,
1674 node_type.get_node_type(),
1675 context.z_index,
1676 context.child_contexts.len(),
1677 context.in_flow_children.len()
1678 );
1679 }
1680
1681 builder.set_current_node(node.dom_node_id);
1685
1686 let has_reference_frame = node.dom_node_id.and_then(|dom_id| {
1689 self.gpu_value_cache.and_then(|cache| {
1690 let key = cache.transform_keys.get(&dom_id)?;
1691 let transform = cache.current_transform_values.get(&dom_id)?;
1692 Some((*key, *transform))
1693 })
1694 });
1695
1696 let node_pos = self
1699 .positioned_tree
1700 .calculated_positions
1701 .get(context.node_index)
1702 .copied()
1703 .unwrap_or_default();
1704 let node_size = node.used_size.unwrap_or(LogicalSize {
1705 width: 0.0,
1706 height: 0.0,
1707 });
1708 let node_bounds = LogicalRect {
1709 origin: node_pos,
1710 size: node_size,
1711 };
1712
1713 if let Some((transform_key, initial_transform)) = has_reference_frame {
1715 builder.push_reference_frame(transform_key, initial_transform, node_bounds);
1716 }
1717
1718 builder.push_stacking_context(context.z_index, node_bounds);
1719
1720 let mut pushed_opacity = false;
1722 let mut pushed_filter = false;
1723 let mut pushed_backdrop_filter = false;
1724
1725 if let Some(dom_id) = node.dom_node_id {
1726 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
1727 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1728
1729 let opacity = self.ctx.styled_dom.css_property_cache.ptr
1731 .get_opacity(node_data, &dom_id, node_state)
1732 .and_then(|v| v.get_property())
1733 .map(|v| v.inner.normalized())
1734 .unwrap_or(1.0);
1735
1736 if opacity < 1.0 {
1737 builder.push_item(DisplayListItem::PushOpacity {
1738 bounds: node_bounds,
1739 opacity,
1740 });
1741 pushed_opacity = true;
1742 }
1743
1744 if let Some(filter_vec_value) = self.ctx.styled_dom.css_property_cache.ptr
1746 .get_filter(node_data, &dom_id, node_state)
1747 {
1748 if let Some(filter_vec) = filter_vec_value.get_property() {
1749 let filters: Vec<_> = filter_vec.as_ref().to_vec();
1750 if !filters.is_empty() {
1751 builder.push_item(DisplayListItem::PushFilter {
1752 bounds: node_bounds,
1753 filters,
1754 });
1755 pushed_filter = true;
1756 }
1757 }
1758 }
1759
1760 if let Some(backdrop_filter_value) = self.ctx.styled_dom.css_property_cache.ptr
1762 .get_backdrop_filter(node_data, &dom_id, node_state)
1763 {
1764 if let Some(filter_vec) = backdrop_filter_value.get_property() {
1765 let filters: Vec<_> = filter_vec.as_ref().to_vec();
1766 if !filters.is_empty() {
1767 builder.push_item(DisplayListItem::PushBackdropFilter {
1768 bounds: node_bounds,
1769 filters,
1770 });
1771 pushed_backdrop_filter = true;
1772 }
1773 }
1774 }
1775 }
1776
1777 self.paint_node_background_and_border(builder, context.node_index)?;
1781
1782 if let Some(dom_id) = node.dom_node_id {
1787 let styled_node_state = self.get_styled_node_state(dom_id);
1788 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
1789 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
1790 if overflow_x.is_scroll() || overflow_y.is_scroll() {
1791 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
1792 builder.push_hit_test_area(node_bounds, tag_id);
1793 }
1794 }
1795 }
1796
1797 let did_push_clip_or_scroll = self.push_node_clips(builder, context.node_index, node)?;
1799
1800 let mut negative_z_children: Vec<_> = context
1802 .child_contexts
1803 .iter()
1804 .filter(|c| c.z_index < 0)
1805 .collect();
1806 negative_z_children.sort_by_key(|c| c.z_index);
1807 for child in negative_z_children {
1808 self.generate_for_stacking_context(builder, child)?;
1809 }
1810
1811 self.paint_in_flow_descendants(builder, context.node_index, &context.in_flow_children)?;
1813
1814 for child in context.child_contexts.iter().filter(|c| c.z_index == 0) {
1816 self.generate_for_stacking_context(builder, child)?;
1817 }
1818
1819 let mut positive_z_children: Vec<_> = context
1821 .child_contexts
1822 .iter()
1823 .filter(|c| c.z_index > 0)
1824 .collect();
1825
1826 positive_z_children.sort_by_key(|c| c.z_index);
1827
1828 for child in positive_z_children {
1829 self.generate_for_stacking_context(builder, child)?;
1830 }
1831
1832 if pushed_backdrop_filter {
1834 builder.push_item(DisplayListItem::PopBackdropFilter);
1835 }
1836 if pushed_filter {
1837 builder.push_item(DisplayListItem::PopFilter);
1838 }
1839 if pushed_opacity {
1840 builder.push_item(DisplayListItem::PopOpacity);
1841 }
1842
1843 builder.pop_stacking_context();
1845
1846 if has_reference_frame.is_some() {
1848 builder.pop_reference_frame();
1849 }
1850
1851 if did_push_clip_or_scroll {
1853 self.pop_node_clips(builder, node)?;
1854 }
1855
1856 self.paint_scrollbars(builder, context.node_index)?;
1859
1860 Ok(())
1861 }
1862
1863 fn paint_in_flow_descendants(
1865 &mut self,
1866 builder: &mut DisplayListBuilder,
1867 node_index: usize,
1868 children_indices: &[usize],
1869 ) -> Result<()> {
1870 self.paint_selection_and_cursor(builder, node_index)?;
1876
1877 self.paint_node_content(builder, node_index)?;
1879
1880 let mut non_float_children = Vec::new();
1887 let mut float_children = Vec::new();
1888 let mut dragging_children = Vec::new();
1889
1890 for &child_index in children_indices {
1891 let child_node = self
1892 .positioned_tree
1893 .tree
1894 .get(child_index)
1895 .ok_or(LayoutError::InvalidTree)?;
1896
1897 let is_dragging = if let Some(dom_id) = child_node.dom_node_id {
1899 let styled_node_state = self.get_styled_node_state(dom_id);
1900 styled_node_state.dragging
1901 } else {
1902 false
1903 };
1904
1905 if is_dragging {
1906 dragging_children.push(child_index);
1907 continue;
1908 }
1909
1910 let is_float = if let Some(dom_id) = child_node.dom_node_id {
1912 use crate::solver3::getters::get_float;
1913 let styled_node_state = self.get_styled_node_state(dom_id);
1914 let float_value = get_float(self.ctx.styled_dom, dom_id, &styled_node_state);
1915 !matches!(
1916 float_value.unwrap_or_default(),
1917 azul_css::props::layout::LayoutFloat::None
1918 )
1919 } else {
1920 false
1921 };
1922
1923 if is_float {
1924 float_children.push(child_index);
1925 } else {
1926 non_float_children.push(child_index);
1927 }
1928 }
1929
1930 for child_index in non_float_children {
1932 let child_node = self
1933 .positioned_tree
1934 .tree
1935 .get(child_index)
1936 .ok_or(LayoutError::InvalidTree)?;
1937
1938 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
1940 self.gpu_value_cache.and_then(|cache| {
1941 let key = cache.transform_keys.get(&dom_id)?;
1942 let transform = cache.current_transform_values.get(&dom_id)?;
1943 Some((*key, *transform))
1944 })
1945 });
1946
1947 if let Some((transform_key, initial_transform)) = child_ref_frame {
1949 let child_pos = self
1950 .positioned_tree
1951 .calculated_positions
1952 .get(child_index)
1953 .copied()
1954 .unwrap_or_default();
1955 let child_size = child_node.used_size.unwrap_or(LogicalSize {
1956 width: 0.0,
1957 height: 0.0,
1958 });
1959 let child_bounds = LogicalRect {
1960 origin: child_pos,
1961 size: child_size,
1962 };
1963 builder.set_current_node(child_node.dom_node_id);
1964 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
1965 }
1966
1967 self.paint_node_background_and_border(builder, child_index)?;
1971
1972 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
1974
1975 self.paint_in_flow_descendants(builder, child_index, &child_node.children)?;
1977
1978 if did_push_clip {
1980 self.pop_node_clips(builder, child_node)?;
1981 }
1982
1983 self.paint_scrollbars(builder, child_index)?;
1985
1986 if child_ref_frame.is_some() {
1988 builder.pop_reference_frame();
1989 }
1990 }
1991
1992 for child_index in float_children {
1994 let child_node = self
1995 .positioned_tree
1996 .tree
1997 .get(child_index)
1998 .ok_or(LayoutError::InvalidTree)?;
1999
2000 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2002 self.gpu_value_cache.and_then(|cache| {
2003 let key = cache.transform_keys.get(&dom_id)?;
2004 let transform = cache.current_transform_values.get(&dom_id)?;
2005 Some((*key, *transform))
2006 })
2007 });
2008
2009 if let Some((transform_key, initial_transform)) = child_ref_frame {
2011 let child_pos = self
2012 .positioned_tree
2013 .calculated_positions
2014 .get(child_index)
2015 .copied()
2016 .unwrap_or_default();
2017 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2018 width: 0.0,
2019 height: 0.0,
2020 });
2021 let child_bounds = LogicalRect {
2022 origin: child_pos,
2023 size: child_size,
2024 };
2025 builder.set_current_node(child_node.dom_node_id);
2026 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2027 }
2028
2029 self.paint_node_background_and_border(builder, child_index)?;
2031 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2032 self.paint_in_flow_descendants(builder, child_index, &child_node.children)?;
2033
2034 if did_push_clip {
2035 self.pop_node_clips(builder, child_node)?;
2036 }
2037
2038 self.paint_scrollbars(builder, child_index)?;
2040
2041 if child_ref_frame.is_some() {
2043 builder.pop_reference_frame();
2044 }
2045 }
2046
2047 for child_index in dragging_children {
2049 let child_node = self
2050 .positioned_tree
2051 .tree
2052 .get(child_index)
2053 .ok_or(LayoutError::InvalidTree)?;
2054
2055 let child_ref_frame = child_node.dom_node_id.and_then(|dom_id| {
2057 self.gpu_value_cache.and_then(|cache| {
2058 let key = cache.transform_keys.get(&dom_id)?;
2059 let transform = cache.current_transform_values.get(&dom_id)?;
2060 Some((*key, *transform))
2061 })
2062 });
2063
2064 if let Some((transform_key, initial_transform)) = child_ref_frame {
2066 let child_pos = self
2067 .positioned_tree
2068 .calculated_positions
2069 .get(child_index)
2070 .copied()
2071 .unwrap_or_default();
2072 let child_size = child_node.used_size.unwrap_or(LogicalSize {
2073 width: 0.0,
2074 height: 0.0,
2075 });
2076 let child_bounds = LogicalRect {
2077 origin: child_pos,
2078 size: child_size,
2079 };
2080 builder.set_current_node(child_node.dom_node_id);
2081 builder.push_reference_frame(transform_key, initial_transform, child_bounds);
2082 }
2083
2084 self.paint_node_background_and_border(builder, child_index)?;
2086 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
2087 self.paint_in_flow_descendants(builder, child_index, &child_node.children)?;
2088
2089 if did_push_clip {
2090 self.pop_node_clips(builder, child_node)?;
2091 }
2092
2093 self.paint_scrollbars(builder, child_index)?;
2095
2096 if child_ref_frame.is_some() {
2098 builder.pop_reference_frame();
2099 }
2100 }
2101
2102 Ok(())
2103 }
2104
2105 fn push_node_clips(
2108 &self,
2109 builder: &mut DisplayListBuilder,
2110 node_index: usize,
2111 node: &LayoutNode,
2112 ) -> Result<bool> {
2113 let Some(dom_id) = node.dom_node_id else {
2114 return Ok(false);
2115 };
2116
2117 let styled_node_state = self.get_styled_node_state(dom_id);
2118
2119 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2120 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2121
2122 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2123 let element_size = PhysicalSizeImport {
2124 width: paint_rect.size.width,
2125 height: paint_rect.size.height,
2126 };
2127 let border_radius = get_border_radius(
2128 self.ctx.styled_dom,
2129 dom_id,
2130 &styled_node_state,
2131 element_size,
2132 self.ctx.viewport_size,
2133 );
2134
2135 let needs_clip = overflow_x.is_clipped() || overflow_y.is_clipped();
2136
2137 if !needs_clip {
2138 return Ok(false);
2139 }
2140
2141 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
2142
2143 let border = &node.box_props.border;
2144
2145 let scrollbar_info = get_scrollbar_info_from_layout(node);
2147
2148 let clip_rect = LogicalRect {
2151 origin: LogicalPosition {
2152 x: paint_rect.origin.x + border.left,
2153 y: paint_rect.origin.y + border.top,
2154 },
2155 size: LogicalSize {
2156 width: (paint_rect.size.width
2158 - border.left
2159 - border.right
2160 - scrollbar_info.scrollbar_width)
2161 .max(0.0),
2162 height: (paint_rect.size.height
2163 - border.top
2164 - border.bottom
2165 - scrollbar_info.scrollbar_height)
2166 .max(0.0),
2167 },
2168 };
2169
2170 if overflow_x.is_scroll() || overflow_y.is_scroll() {
2171 builder.push_clip(clip_rect, border_radius);
2175 let scroll_id = self.scroll_ids.get(&node_index).copied().unwrap_or(0);
2176 let content_size = get_scroll_content_size(node);
2177 builder.push_scroll_frame(clip_rect, content_size, scroll_id);
2178 } else {
2179 builder.push_clip(clip_rect, border_radius);
2181 }
2182
2183 Ok(true)
2184 }
2185
2186 fn pop_node_clips(&self, builder: &mut DisplayListBuilder, node: &LayoutNode) -> Result<()> {
2188 let Some(dom_id) = node.dom_node_id else {
2189 return Ok(());
2190 };
2191
2192 let styled_node_state = self.get_styled_node_state(dom_id);
2193 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2194 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2195
2196 let paint_rect = self
2197 .get_paint_rect(
2198 self.positioned_tree
2199 .tree
2200 .nodes
2201 .iter()
2202 .position(|n| n.dom_node_id == Some(dom_id))
2203 .unwrap_or(0),
2204 )
2205 .unwrap_or_default();
2206
2207 let element_size = PhysicalSizeImport {
2208 width: paint_rect.size.width,
2209 height: paint_rect.size.height,
2210 };
2211 let border_radius = get_border_radius(
2212 self.ctx.styled_dom,
2213 dom_id,
2214 &styled_node_state,
2215 element_size,
2216 self.ctx.viewport_size,
2217 );
2218
2219 let needs_clip =
2220 overflow_x.is_clipped() || overflow_y.is_clipped() || !border_radius.is_zero();
2221
2222 if needs_clip {
2223 if overflow_x.is_scroll() || overflow_y.is_scroll() {
2224 builder.pop_scroll_frame();
2226 builder.pop_clip();
2227 } else {
2228 builder.pop_clip();
2230 }
2231 }
2232 Ok(())
2233 }
2234
2235 fn get_paint_rect(&self, node_index: usize) -> Option<LogicalRect> {
2253 let node = self.positioned_tree.tree.get(node_index)?;
2254 let pos = self
2255 .positioned_tree
2256 .calculated_positions
2257 .get(node_index)
2258 .copied()
2259 .unwrap_or_default();
2260 let size = node.used_size.unwrap_or_default();
2261
2262 Some(LogicalRect::new(pos, size))
2267 }
2268
2269 fn paint_node_background_and_border(
2271 &mut self,
2272 builder: &mut DisplayListBuilder,
2273 node_index: usize,
2274 ) -> Result<()> {
2275 let Some(paint_rect) = self.get_paint_rect(node_index) else {
2276 return Ok(());
2277 };
2278 let node = self
2279 .positioned_tree
2280 .tree
2281 .get(node_index)
2282 .ok_or(LayoutError::InvalidTree)?;
2283
2284 builder.set_current_node(node.dom_node_id);
2286
2287 if let Some(dom_id) = node.dom_node_id {
2290 let break_before = get_break_before(self.ctx.styled_dom, Some(dom_id));
2291 let break_after = get_break_after(self.ctx.styled_dom, Some(dom_id));
2292
2293 if is_forced_page_break(break_before) {
2295 let y_position = paint_rect.origin.y;
2296 builder.add_forced_page_break(y_position);
2297 debug_info!(
2298 self.ctx,
2299 "Registered forced page break BEFORE node {} at y={}",
2300 node_index,
2301 y_position
2302 );
2303 }
2304
2305 if is_forced_page_break(break_after) {
2307 let y_position = paint_rect.origin.y + paint_rect.size.height;
2308 builder.add_forced_page_break(y_position);
2309 debug_info!(
2310 self.ctx,
2311 "Registered forced page break AFTER node {} at y={}",
2312 node_index,
2313 y_position
2314 );
2315 }
2316 }
2317
2318 let parent_is_flex_or_grid = node.parent_formatting_context
2327 .as_ref()
2328 .map(|fc| matches!(fc, FormattingContext::Flex | FormattingContext::Grid))
2329 .unwrap_or(false);
2330
2331 if let Some(dom_id) = node.dom_node_id {
2332 let display = {
2333 use crate::solver3::getters::get_display_property;
2334 get_display_property(self.ctx.styled_dom, Some(dom_id))
2335 .unwrap_or(LayoutDisplay::Inline)
2336 };
2337
2338 if display == LayoutDisplay::InlineBlock || display == LayoutDisplay::Inline {
2339 debug_info!(
2340 self.ctx,
2341 "[paint_node] node {} has display={:?}, parent_formatting_context={:?}, parent_is_flex_or_grid={}",
2342 node_index,
2343 display,
2344 node.parent_formatting_context,
2345 parent_is_flex_or_grid
2346 );
2347
2348 if !parent_is_flex_or_grid {
2349 return Ok(());
2352 }
2353 }
2355 }
2356
2357 if matches!(node.formatting_context, FormattingContext::Table) {
2360 debug_info!(
2361 self.ctx,
2362 "Painting table backgrounds/borders for node {} at {:?}",
2363 node_index,
2364 paint_rect
2365 );
2366 return self.paint_table_items(builder, node_index);
2368 }
2369
2370 let border_radius = if let Some(dom_id) = node.dom_node_id {
2371 let styled_node_state = self.get_styled_node_state(dom_id);
2372 let background_contents =
2373 get_background_contents(self.ctx.styled_dom, dom_id, &styled_node_state);
2374 let border_info = get_border_info(self.ctx.styled_dom, dom_id, &styled_node_state);
2375
2376 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2377 debug_info!(
2378 self.ctx,
2379 "Painting background/border for node {} ({:?}) at {:?}, backgrounds={:?}",
2380 node_index,
2381 node_type.get_node_type(),
2382 paint_rect,
2383 background_contents.len()
2384 );
2385
2386 let element_size = PhysicalSizeImport {
2389 width: paint_rect.size.width,
2390 height: paint_rect.size.height,
2391 };
2392 let simple_border_radius = get_border_radius(
2393 self.ctx.styled_dom,
2394 dom_id,
2395 &styled_node_state,
2396 element_size,
2397 self.ctx.viewport_size,
2398 );
2399 let style_border_radius =
2400 get_style_border_radius(self.ctx.styled_dom, dom_id, &styled_node_state);
2401
2402 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2404 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2405
2406 for get_shadow_fn in [
2408 azul_core::prop_cache::CssPropertyCache::get_box_shadow_left,
2409 azul_core::prop_cache::CssPropertyCache::get_box_shadow_right,
2410 azul_core::prop_cache::CssPropertyCache::get_box_shadow_top,
2411 azul_core::prop_cache::CssPropertyCache::get_box_shadow_bottom,
2412 ] {
2413 if let Some(shadow_value) = get_shadow_fn(
2414 &self.ctx.styled_dom.css_property_cache.ptr,
2415 node_data,
2416 &dom_id,
2417 &node_state,
2418 ) {
2419 if let Some(shadow) = shadow_value.get_property() {
2420 builder.push_item(DisplayListItem::BoxShadow {
2421 bounds: paint_rect,
2422 shadow: shadow.clone(),
2423 border_radius: simple_border_radius,
2424 });
2425 }
2426 }
2427 }
2428
2429 builder.push_backgrounds_and_border(
2431 paint_rect,
2432 &background_contents,
2433 &border_info,
2434 simple_border_radius,
2435 style_border_radius,
2436 );
2437
2438 simple_border_radius
2439 } else {
2440 BorderRadius::default()
2441 };
2442
2443 Ok(())
2444 }
2445
2446 fn paint_table_items(
2464 &self,
2465 builder: &mut DisplayListBuilder,
2466 table_index: usize,
2467 ) -> Result<()> {
2468 let table_node = self
2469 .positioned_tree
2470 .tree
2471 .get(table_index)
2472 .ok_or(LayoutError::InvalidTree)?;
2473
2474 let Some(table_paint_rect) = self.get_paint_rect(table_index) else {
2475 return Ok(());
2476 };
2477
2478 if let Some(dom_id) = table_node.dom_node_id {
2480 let styled_node_state = self.get_styled_node_state(dom_id);
2481 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
2482 let element_size = PhysicalSizeImport {
2483 width: table_paint_rect.size.width,
2484 height: table_paint_rect.size.height,
2485 };
2486 let border_radius = get_border_radius(
2487 self.ctx.styled_dom,
2488 dom_id,
2489 &styled_node_state,
2490 element_size,
2491 self.ctx.viewport_size,
2492 );
2493
2494 builder.push_rect(table_paint_rect, bg_color, border_radius);
2495 }
2496
2497 for &child_idx in &table_node.children {
2502 let child_node = self.positioned_tree.tree.get(child_idx);
2503 if let Some(node) = child_node {
2504 if matches!(node.formatting_context, FormattingContext::TableColumnGroup) {
2505 self.paint_element_background(builder, child_idx)?;
2507
2508 for &col_idx in &node.children {
2510 self.paint_element_background(builder, col_idx)?;
2511 }
2512 }
2513 }
2514 }
2515
2516 for &child_idx in &table_node.children {
2520 let child_node = self.positioned_tree.tree.get(child_idx);
2521 if let Some(node) = child_node {
2522 match node.formatting_context {
2523 FormattingContext::TableRowGroup => {
2524 self.paint_element_background(builder, child_idx)?;
2526
2527 for &row_idx in &node.children {
2529 self.paint_table_row_and_cells(builder, row_idx)?;
2530 }
2531 }
2532 FormattingContext::TableRow => {
2533 self.paint_table_row_and_cells(builder, child_idx)?;
2535 }
2536 _ => {}
2537 }
2538 }
2539 }
2540
2541 Ok(())
2546 }
2547
2548 fn paint_table_row_and_cells(
2552 &self,
2553 builder: &mut DisplayListBuilder,
2554 row_idx: usize,
2555 ) -> Result<()> {
2556 self.paint_element_background(builder, row_idx)?;
2558
2559 let row_node = self.positioned_tree.tree.get(row_idx);
2561 if let Some(node) = row_node {
2562 for &cell_idx in &node.children {
2563 self.paint_element_background(builder, cell_idx)?;
2564 }
2565 }
2566
2567 Ok(())
2568 }
2569
2570 fn paint_element_background(
2573 &self,
2574 builder: &mut DisplayListBuilder,
2575 node_index: usize,
2576 ) -> Result<()> {
2577 let Some(paint_rect) = self.get_paint_rect(node_index) else {
2578 return Ok(());
2579 };
2580
2581 let Some(node) = self.positioned_tree.tree.get(node_index) else {
2582 return Ok(());
2583 };
2584 let Some(dom_id) = node.dom_node_id else {
2585 return Ok(());
2586 };
2587
2588 let styled_node_state = self.get_styled_node_state(dom_id);
2589 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
2590
2591 if bg_color.a == 0 {
2593 return Ok(());
2594 }
2595
2596 let element_size = PhysicalSizeImport {
2597 width: paint_rect.size.width,
2598 height: paint_rect.size.height,
2599 };
2600 let border_radius = get_border_radius(
2601 self.ctx.styled_dom,
2602 dom_id,
2603 &styled_node_state,
2604 element_size,
2605 self.ctx.viewport_size,
2606 );
2607
2608 builder.push_rect(paint_rect, bg_color, border_radius);
2609
2610 Ok(())
2611 }
2612
2613 fn paint_node_content(
2615 &mut self,
2616 builder: &mut DisplayListBuilder,
2617 node_index: usize,
2618 ) -> Result<()> {
2619 let node = self
2620 .positioned_tree
2621 .tree
2622 .get(node_index)
2623 .ok_or(LayoutError::InvalidTree)?;
2624
2625 builder.set_current_node(node.dom_node_id);
2627
2628 let Some(mut paint_rect) = self.get_paint_rect(node_index) else {
2629 return Ok(());
2630 };
2631
2632 if paint_rect.size.width == 0.0 || paint_rect.size.height == 0.0 {
2635 if let Some(cached_layout) = &node.inline_layout_result {
2636 let content_bounds = cached_layout.layout.bounds();
2637 paint_rect.size.width = content_bounds.width;
2638 paint_rect.size.height = content_bounds.height;
2639 }
2640 }
2641
2642 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
2647 let is_scrollable = if let Some(dom_id) = node.dom_node_id {
2648 let styled_node_state = self.get_styled_node_state(dom_id);
2649 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2650 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2651 overflow_x.is_scroll() || overflow_y.is_scroll()
2652 } else {
2653 false
2654 };
2655
2656 if !is_scrollable {
2662 builder.push_hit_test_area(paint_rect, tag_id);
2663 }
2664 }
2665
2666 if let Some(cached_layout) = &node.inline_layout_result {
2668 let inline_layout = &cached_layout.layout;
2669 debug_info!(
2670 self.ctx,
2671 "[paint_node] node {} has inline_layout with {} items",
2672 node_index,
2673 inline_layout.items.len()
2674 );
2675
2676 if let Some(dom_id) = node.dom_node_id {
2677 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2678 debug_info!(
2679 self.ctx,
2680 "Painting inline content for node {} ({:?}) at {:?}, {} layout items",
2681 node_index,
2682 node_type.get_node_type(),
2683 paint_rect,
2684 inline_layout.items.len()
2685 );
2686 }
2687
2688 let border_box = BorderBoxRect(paint_rect);
2692 let mut content_box_rect =
2693 border_box.to_content_box(&node.box_props.padding, &node.box_props.border).rect();
2694
2695 let content_size = get_scroll_content_size(node);
2699 if content_size.height > content_box_rect.size.height {
2700 content_box_rect.size.height = content_size.height;
2701 }
2702 if content_size.width > content_box_rect.size.width {
2703 content_box_rect.size.width = content_size.width;
2704 }
2705
2706 let mut pushed_text_shadow = false;
2708 if let Some(dom_id) = node.dom_node_id {
2709 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2710 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
2711 if let Some(shadow_val) = self.ctx.styled_dom.css_property_cache.ptr
2712 .get_text_shadow(node_data, &dom_id, node_state)
2713 {
2714 if let Some(shadow) = shadow_val.get_property() {
2715 builder.push_item(DisplayListItem::PushTextShadow {
2716 shadow: shadow.clone(),
2717 });
2718 pushed_text_shadow = true;
2719 }
2720 }
2721 }
2722
2723 self.paint_inline_content(builder, content_box_rect, inline_layout)?;
2724
2725 if pushed_text_shadow {
2726 builder.push_item(DisplayListItem::PopTextShadow);
2727 }
2728 } else if let Some(dom_id) = node.dom_node_id {
2729 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2731 if let NodeType::Image(image_ref) = node_data.get_node_type() {
2732 debug_info!(
2733 self.ctx,
2734 "Painting image for node {} at {:?}",
2735 node_index,
2736 paint_rect
2737 );
2738 builder.push_image(paint_rect, image_ref.clone());
2740 }
2741 }
2742
2743 Ok(())
2744 }
2745
2746 fn paint_scrollbars(&self, builder: &mut DisplayListBuilder, node_index: usize) -> Result<()> {
2749 let node = self
2750 .positioned_tree
2751 .tree
2752 .get(node_index)
2753 .ok_or(LayoutError::InvalidTree)?;
2754
2755 let Some(paint_rect) = self.get_paint_rect(node_index) else {
2756 return Ok(());
2757 };
2758
2759 let scrollbar_info = get_scrollbar_info_from_layout(node);
2761
2762 let node_id = node.dom_node_id;
2764
2765 let scrollbar_style = node_id
2767 .map(|nid| {
2768 let node_state =
2769 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
2770 get_scrollbar_style(self.ctx.styled_dom, nid, node_state)
2771 })
2772 .unwrap_or_default();
2773
2774 if matches!(
2776 scrollbar_style.width_mode,
2777 azul_css::props::style::scrollbar::LayoutScrollbarWidth::None
2778 ) {
2779 return Ok(());
2780 }
2781
2782 let border = &node.box_props.border;
2784
2785 let container_border_radius = node_id
2787 .map(|nid| {
2788 let node_state =
2789 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
2790 let element_size = PhysicalSizeImport {
2791 width: paint_rect.size.width,
2792 height: paint_rect.size.height,
2793 };
2794 let viewport_size =
2795 LogicalSize::new(self.ctx.viewport_size.width, self.ctx.viewport_size.height);
2796 get_border_radius(
2797 self.ctx.styled_dom,
2798 nid,
2799 node_state,
2800 element_size,
2801 viewport_size,
2802 )
2803 })
2804 .unwrap_or_default();
2805
2806 let inner_rect = LogicalRect {
2809 origin: LogicalPosition::new(
2810 paint_rect.origin.x + border.left,
2811 paint_rect.origin.y + border.top,
2812 ),
2813 size: LogicalSize::new(
2814 (paint_rect.size.width - border.left - border.right).max(0.0),
2815 (paint_rect.size.height - border.top - border.bottom).max(0.0),
2816 ),
2817 };
2818
2819 let (scroll_offset_x, scroll_offset_y) = node_id
2823 .and_then(|nid| {
2824 self.scroll_offsets.get(&nid).map(|pos| {
2825 (
2826 pos.children_rect.origin.x - pos.parent_rect.origin.x,
2827 pos.children_rect.origin.y - pos.parent_rect.origin.y,
2828 )
2829 })
2830 })
2831 .unwrap_or((0.0, 0.0));
2832
2833 let content_size = node.get_content_size();
2838
2839 let thumb_radius = scrollbar_style.width_px / 2.0;
2841 let thumb_border_radius = BorderRadius {
2842 top_left: thumb_radius,
2843 top_right: thumb_radius,
2844 bottom_left: thumb_radius,
2845 bottom_right: thumb_radius,
2846 };
2847
2848 if scrollbar_info.needs_vertical {
2849 let opacity_key = node_id.and_then(|nid| {
2851 self.gpu_value_cache.and_then(|cache| {
2852 cache
2853 .scrollbar_v_opacity_keys
2854 .get(&(self.dom_id, nid))
2855 .copied()
2856 })
2857 });
2858
2859 let track_height = if scrollbar_info.needs_horizontal {
2861 inner_rect.size.height - scrollbar_style.width_px
2862 } else {
2863 inner_rect.size.height
2864 };
2865
2866 let track_bounds = LogicalRect {
2867 origin: LogicalPosition::new(
2868 inner_rect.origin.x + inner_rect.size.width - scrollbar_style.width_px,
2869 inner_rect.origin.y,
2870 ),
2871 size: LogicalSize::new(scrollbar_style.width_px, track_height),
2872 };
2873
2874 let viewport_height = inner_rect.size.height;
2876 let thumb_ratio = (viewport_height / content_size.height).min(1.0);
2877 let thumb_height = (track_height * thumb_ratio).max(scrollbar_style.width_px * 2.0);
2878
2879 let max_scroll = (content_size.height - viewport_height).max(0.0);
2880 let scroll_ratio = if max_scroll > 0.0 {
2881 scroll_offset_y.abs() / max_scroll
2882 } else {
2883 0.0
2884 };
2885 let thumb_y = track_bounds.origin.y
2886 + (track_height - thumb_height) * scroll_ratio.clamp(0.0, 1.0);
2887
2888 let thumb_bounds = LogicalRect {
2889 origin: LogicalPosition::new(track_bounds.origin.x, thumb_y),
2890 size: LogicalSize::new(scrollbar_style.width_px, thumb_height),
2891 };
2892
2893 let hit_id = node_id
2895 .map(|nid| azul_core::hit_test::ScrollbarHitId::VerticalThumb(self.dom_id, nid));
2896
2897 let button_size = scrollbar_style.width_px;
2899 let button_decrement_bounds = Some(LogicalRect {
2900 origin: LogicalPosition::new(track_bounds.origin.x, track_bounds.origin.y),
2901 size: LogicalSize::new(button_size, button_size),
2902 });
2903 let button_increment_bounds = Some(LogicalRect {
2904 origin: LogicalPosition::new(
2905 track_bounds.origin.x,
2906 track_bounds.origin.y + track_height - button_size,
2907 ),
2908 size: LogicalSize::new(button_size, button_size),
2909 });
2910 let debug_button_color = ColorU { r: 144, g: 238, b: 144, a: 255 };
2912
2913 builder.push_scrollbar_styled(ScrollbarDrawInfo {
2914 bounds: track_bounds,
2915 orientation: ScrollbarOrientation::Vertical,
2916 track_bounds,
2917 track_color: scrollbar_style.track_color,
2918 thumb_bounds,
2919 thumb_color: scrollbar_style.thumb_color,
2920 thumb_border_radius,
2921 button_decrement_bounds,
2922 button_increment_bounds,
2923 button_color: debug_button_color,
2924 opacity_key,
2925 hit_id,
2926 clip_to_container_border: scrollbar_style.clip_to_container_border,
2927 container_border_radius,
2928 });
2929 }
2930
2931 if scrollbar_info.needs_horizontal {
2932 let opacity_key = node_id.and_then(|nid| {
2934 self.gpu_value_cache.and_then(|cache| {
2935 cache
2936 .scrollbar_h_opacity_keys
2937 .get(&(self.dom_id, nid))
2938 .copied()
2939 })
2940 });
2941
2942 let track_width = if scrollbar_info.needs_vertical {
2944 inner_rect.size.width - scrollbar_style.width_px
2945 } else {
2946 inner_rect.size.width
2947 };
2948
2949 let track_bounds = LogicalRect {
2950 origin: LogicalPosition::new(
2951 inner_rect.origin.x,
2952 inner_rect.origin.y + inner_rect.size.height - scrollbar_style.width_px,
2953 ),
2954 size: LogicalSize::new(track_width, scrollbar_style.width_px),
2955 };
2956
2957 let viewport_width = inner_rect.size.width;
2959 let thumb_ratio = (viewport_width / content_size.width).min(1.0);
2960 let thumb_width = (track_width * thumb_ratio).max(scrollbar_style.width_px * 2.0);
2961
2962 let max_scroll = (content_size.width - viewport_width).max(0.0);
2963 let scroll_ratio = if max_scroll > 0.0 {
2964 scroll_offset_x.abs() / max_scroll
2965 } else {
2966 0.0
2967 };
2968 let thumb_x = track_bounds.origin.x
2969 + (track_width - thumb_width) * scroll_ratio.clamp(0.0, 1.0);
2970
2971 let thumb_bounds = LogicalRect {
2972 origin: LogicalPosition::new(thumb_x, track_bounds.origin.y),
2973 size: LogicalSize::new(thumb_width, scrollbar_style.width_px),
2974 };
2975
2976 let hit_id = node_id
2978 .map(|nid| azul_core::hit_test::ScrollbarHitId::HorizontalThumb(self.dom_id, nid));
2979
2980 let button_size = scrollbar_style.width_px;
2982 let button_decrement_bounds = Some(LogicalRect {
2983 origin: LogicalPosition::new(track_bounds.origin.x, track_bounds.origin.y),
2984 size: LogicalSize::new(button_size, button_size),
2985 });
2986 let button_increment_bounds = Some(LogicalRect {
2987 origin: LogicalPosition::new(
2988 track_bounds.origin.x + track_width - button_size,
2989 track_bounds.origin.y,
2990 ),
2991 size: LogicalSize::new(button_size, button_size),
2992 });
2993 let debug_button_color = ColorU { r: 144, g: 238, b: 144, a: 255 };
2995
2996 builder.push_scrollbar_styled(ScrollbarDrawInfo {
2997 bounds: track_bounds,
2998 orientation: ScrollbarOrientation::Horizontal,
2999 track_bounds,
3000 track_color: scrollbar_style.track_color,
3001 thumb_bounds,
3002 thumb_color: scrollbar_style.thumb_color,
3003 thumb_border_radius,
3004 button_decrement_bounds,
3005 button_increment_bounds,
3006 button_color: debug_button_color,
3007 opacity_key,
3008 hit_id,
3009 clip_to_container_border: scrollbar_style.clip_to_container_border,
3010 container_border_radius,
3011 });
3012 }
3013
3014 Ok(())
3015 }
3016
3017 fn paint_inline_content(
3019 &self,
3020 builder: &mut DisplayListBuilder,
3021 container_rect: LogicalRect,
3022 layout: &UnifiedLayout,
3023 ) -> Result<()> {
3024 let layout_bounds = layout.bounds();
3034 let actual_bounds = if layout_bounds.width > 0.0 && layout_bounds.height > 0.0 {
3035 LogicalRect {
3036 origin: container_rect.origin,
3037 size: LogicalSize {
3038 width: layout_bounds.width,
3039 height: layout_bounds.height,
3040 },
3041 }
3042 } else {
3043 LogicalRect {
3046 origin: container_rect.origin,
3047 size: LogicalSize::default(),
3048 }
3049 };
3050
3051 if layout_bounds.width > 0.0 || layout_bounds.height > 0.0 {
3055 builder.push_text_layout(
3056 Arc::new(layout.clone()) as Arc<dyn std::any::Any + Send + Sync>,
3057 actual_bounds,
3058 FontHash::from_hash(0), 12.0, ColorU {
3061 r: 0,
3062 g: 0,
3063 b: 0,
3064 a: 255,
3065 }, );
3067 }
3068
3069 let glyph_runs = crate::text3::glyphs::get_glyph_runs_simple(layout);
3070
3071 for glyph_run in glyph_runs.iter() {
3074 if let (Some(first_glyph), Some(last_glyph)) =
3076 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
3077 {
3078 let run_start_x = container_rect.origin.x + first_glyph.point.x;
3080 let run_end_x = container_rect.origin.x + last_glyph.point.x;
3081 let run_width = (run_end_x - run_start_x).max(0.0);
3082
3083 if run_width <= 0.0 {
3085 continue;
3086 }
3087
3088 let baseline_y = container_rect.origin.y + first_glyph.point.y;
3090 let font_size = glyph_run.font_size_px;
3091 let ascent = font_size * 0.8; let mut run_bounds = LogicalRect::new(
3094 LogicalPosition::new(run_start_x, baseline_y - ascent),
3095 LogicalSize::new(run_width, font_size),
3096 );
3097
3098 if let Some(border) = &glyph_run.border {
3101 let left_inset = border.left_inset();
3102 let right_inset = border.right_inset();
3103 let top_inset = border.top_inset();
3104 let bottom_inset = border.bottom_inset();
3105
3106 run_bounds.origin.x -= left_inset;
3107 run_bounds.origin.y -= top_inset;
3108 run_bounds.size.width += left_inset + right_inset;
3109 run_bounds.size.height += top_inset + bottom_inset;
3110 }
3111
3112 builder.push_inline_backgrounds_and_border(
3114 run_bounds,
3115 glyph_run.background_color,
3116 &glyph_run.background_content,
3117 glyph_run.border.as_ref(),
3118 );
3119 }
3120 }
3121
3122 for (idx, glyph_run) in glyph_runs.iter().enumerate() {
3124 let clip_rect = container_rect; let offset_glyphs: Vec<GlyphInstance> = glyph_run
3129 .glyphs
3130 .iter()
3131 .map(|g| {
3132 let mut g = g.clone();
3133 g.point.x += container_rect.origin.x;
3134 g.point.y += container_rect.origin.y;
3135 g
3136 })
3137 .collect();
3138
3139 builder.push_text_run(
3141 offset_glyphs,
3142 FontHash::from_hash(glyph_run.font_hash),
3143 glyph_run.font_size_px,
3144 glyph_run.color,
3145 clip_rect,
3146 );
3147
3148 let needs_underline = glyph_run.text_decoration.underline || glyph_run.is_ime_preview;
3150 let needs_strikethrough = glyph_run.text_decoration.strikethrough;
3151 let needs_overline = glyph_run.text_decoration.overline;
3152
3153 if needs_underline || needs_strikethrough || needs_overline {
3154 if let (Some(first_glyph), Some(last_glyph)) =
3156 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
3157 {
3158 let decoration_start_x = container_rect.origin.x + first_glyph.point.x;
3159 let decoration_end_x = container_rect.origin.x + last_glyph.point.x;
3160 let decoration_width = decoration_end_x - decoration_start_x;
3161
3162 let font_size = glyph_run.font_size_px;
3165 let thickness = (font_size * 0.08).max(1.0); let baseline_y = container_rect.origin.y + first_glyph.point.y;
3169
3170 if needs_underline {
3171 let underline_y = baseline_y + (font_size * 0.12);
3174 let underline_bounds = LogicalRect::new(
3175 LogicalPosition::new(decoration_start_x, underline_y),
3176 LogicalSize::new(decoration_width, thickness),
3177 );
3178 builder.push_underline(underline_bounds, glyph_run.color, thickness);
3179 }
3180
3181 if needs_strikethrough {
3182 let strikethrough_y = baseline_y - (font_size * 0.3);
3184 let strikethrough_bounds = LogicalRect::new(
3185 LogicalPosition::new(decoration_start_x, strikethrough_y),
3186 LogicalSize::new(decoration_width, thickness),
3187 );
3188 builder.push_strikethrough(
3189 strikethrough_bounds,
3190 glyph_run.color,
3191 thickness,
3192 );
3193 }
3194
3195 if needs_overline {
3196 let overline_y = baseline_y - (font_size * 0.85);
3198 let overline_bounds = LogicalRect::new(
3199 LogicalPosition::new(decoration_start_x, overline_y),
3200 LogicalSize::new(decoration_width, thickness),
3201 );
3202 builder.push_overline(overline_bounds, glyph_run.color, thickness);
3203 }
3204 }
3205 }
3206 }
3207
3208 for glyph_run in glyph_runs.iter() {
3211 let Some(source_node_id) = glyph_run.source_node_id else {
3213 continue;
3214 };
3215
3216 if let (Some(first_glyph), Some(last_glyph)) =
3218 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
3219 {
3220 let run_start_x = container_rect.origin.x + first_glyph.point.x;
3221 let run_end_x = container_rect.origin.x + last_glyph.point.x;
3222 let run_width = (run_end_x - run_start_x).max(0.0);
3223
3224 if run_width <= 0.0 {
3226 continue;
3227 }
3228
3229 let baseline_y = container_rect.origin.y + first_glyph.point.y;
3231 let font_size = glyph_run.font_size_px;
3232 let ascent = font_size * 0.8; let run_bounds = LogicalRect::new(
3235 LogicalPosition::new(run_start_x, baseline_y - ascent),
3236 LogicalSize::new(run_width, font_size),
3237 );
3238
3239 let cursor_type = self.get_cursor_type_for_text_node(source_node_id);
3242
3243 let tag_value = ((self.dom_id.inner as u64) << 32) | (source_node_id.index() as u64);
3247 let tag_type = TAG_TYPE_CURSOR | (cursor_type as u16);
3248 let tag_id = (tag_value, tag_type);
3249
3250 builder.push_hit_test_area(run_bounds, tag_id);
3251 }
3252 }
3253
3254 for positioned_item in &layout.items {
3258 self.paint_inline_object(builder, container_rect.origin, positioned_item)?;
3259 }
3260 Ok(())
3261 }
3262
3263 fn paint_inline_object(
3265 &self,
3266 builder: &mut DisplayListBuilder,
3267 base_pos: LogicalPosition,
3268 positioned_item: &PositionedItem,
3269 ) -> Result<()> {
3270 let ShapedItem::Object {
3271 content, bounds, ..
3272 } = &positioned_item.item
3273 else {
3274 return Ok(());
3276 };
3277
3278 let object_bounds = LogicalRect::new(
3281 LogicalPosition::new(
3282 base_pos.x + positioned_item.position.x,
3283 base_pos.y + positioned_item.position.y,
3284 ),
3285 LogicalSize::new(bounds.width, bounds.height),
3286 );
3287
3288 match content {
3289 InlineContent::Image(image) => {
3290 if let Some(image_ref) = get_image_ref_for_image_source(&image.source) {
3291 builder.push_image(object_bounds, image_ref);
3292 }
3293 }
3294 InlineContent::Shape(shape) => {
3295 self.paint_inline_shape(builder, object_bounds, shape, bounds)?;
3296 }
3297 _ => {}
3298 }
3299 Ok(())
3300 }
3301
3302 fn paint_inline_shape(
3304 &self,
3305 builder: &mut DisplayListBuilder,
3306 object_bounds: LogicalRect,
3307 shape: &InlineShape,
3308 bounds: &crate::text3::cache::Rect,
3309 ) -> Result<()> {
3310 let Some(node_id) = shape.source_node_id else {
3313 return Ok(());
3314 };
3315
3316 let styled_node_state =
3317 &self.ctx.styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
3318
3319 let background_contents =
3321 get_background_contents(self.ctx.styled_dom, node_id, styled_node_state);
3322
3323 let border_info = get_border_info(self.ctx.styled_dom, node_id, styled_node_state);
3325
3326 let margins = if let Some(indices) = self.positioned_tree.tree.dom_to_layout.get(&node_id) {
3329 if let Some(&idx) = indices.first() {
3330 self.positioned_tree.tree.nodes[idx].box_props.margin
3331 } else {
3332 Default::default()
3333 }
3334 } else {
3335 Default::default()
3336 };
3337
3338 let border_box_bounds = LogicalRect {
3340 origin: LogicalPosition {
3341 x: object_bounds.origin.x + margins.left,
3342 y: object_bounds.origin.y + margins.top,
3343 },
3344 size: LogicalSize {
3345 width: (object_bounds.size.width - margins.left - margins.right).max(0.0),
3346 height: (object_bounds.size.height - margins.top - margins.bottom).max(0.0),
3347 },
3348 };
3349
3350 let element_size = PhysicalSizeImport {
3351 width: border_box_bounds.size.width,
3352 height: border_box_bounds.size.height,
3353 };
3354
3355 let simple_border_radius = get_border_radius(
3357 self.ctx.styled_dom,
3358 node_id,
3359 styled_node_state,
3360 element_size,
3361 self.ctx.viewport_size,
3362 );
3363
3364 let style_border_radius =
3366 get_style_border_radius(self.ctx.styled_dom, node_id, styled_node_state);
3367
3368 builder.push_backgrounds_and_border(
3370 border_box_bounds,
3371 &background_contents,
3372 &border_info,
3373 simple_border_radius,
3374 style_border_radius,
3375 );
3376
3377 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, Some(node_id)) {
3381 builder.push_hit_test_area(border_box_bounds, tag_id);
3382 }
3383
3384 Ok(())
3385 }
3386
3387 fn establishes_stacking_context(&self, node_index: usize) -> bool {
3389 let Some(node) = self.positioned_tree.tree.get(node_index) else {
3390 return false;
3391 };
3392 let Some(dom_id) = node.dom_node_id else {
3393 return false;
3394 };
3395
3396 let position = get_position_type(self.ctx.styled_dom, Some(dom_id));
3397 if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
3398 return true;
3399 }
3400
3401 let z_index = get_z_index(self.ctx.styled_dom, Some(dom_id));
3402 if position == LayoutPosition::Relative && z_index != 0 {
3403 return true;
3404 }
3405
3406 if let Some(styled_node) = self.ctx.styled_dom.styled_nodes.as_container().get(dom_id) {
3407 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3408 let node_state =
3409 &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
3410
3411 let opacity = self
3413 .ctx
3414 .styled_dom
3415 .css_property_cache
3416 .ptr
3417 .get_opacity(node_data, &dom_id, node_state)
3418 .and_then(|v| v.get_property())
3419 .map(|v| v.inner.normalized())
3420 .unwrap_or(1.0);
3421
3422 if opacity < 1.0 {
3423 return true;
3424 }
3425
3426 let has_transform = self
3428 .ctx
3429 .styled_dom
3430 .css_property_cache
3431 .ptr
3432 .get_transform(node_data, &dom_id, node_state)
3433 .and_then(|v| v.get_property())
3434 .map(|v| !v.is_empty())
3435 .unwrap_or(false);
3436
3437 if has_transform {
3438 return true;
3439 }
3440 }
3441
3442 false
3443 }
3444}
3445
3446pub struct PositionedTree<'a> {
3452 pub tree: &'a LayoutTree,
3454 pub calculated_positions: &'a super::PositionVec,
3456}
3457
3458#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3464pub enum OverflowBehavior {
3465 Visible,
3467 Hidden,
3469 Clip,
3471 Scroll,
3473 Auto,
3475}
3476
3477impl OverflowBehavior {
3478 pub fn is_clipped(&self) -> bool {
3483 matches!(self, Self::Hidden | Self::Clip | Self::Scroll | Self::Auto)
3484 }
3485
3486 pub fn is_scroll(&self) -> bool {
3491 matches!(self, Self::Scroll | Self::Auto)
3492 }
3493}
3494
3495fn get_scroll_id(id: Option<NodeId>) -> LocalScrollId {
3496 id.map(|i| i.index() as u64).unwrap_or(0)
3497}
3498
3499fn get_scroll_content_size(node: &LayoutNode) -> LogicalSize {
3502 if let Some(overflow_size) = node.overflow_content_size {
3504 return overflow_size;
3505 }
3506
3507 let mut content_size = node.used_size.unwrap_or_default();
3509
3510 if let Some(ref cached_layout) = node.inline_layout_result {
3512 let text_layout = &cached_layout.layout;
3513 let mut max_x: f32 = 0.0;
3515 let mut max_y: f32 = 0.0;
3516
3517 for positioned_item in &text_layout.items {
3518 let item_bounds = positioned_item.item.bounds();
3519 let item_right = positioned_item.position.x + item_bounds.width;
3520 let item_bottom = positioned_item.position.y + item_bounds.height;
3521
3522 max_x = max_x.max(item_right);
3523 max_y = max_y.max(item_bottom);
3524 }
3525
3526 content_size.width = content_size.width.max(max_x);
3528 content_size.height = content_size.height.max(max_y);
3529 }
3530
3531 content_size
3532}
3533
3534fn get_tag_id(dom: &StyledDom, id: Option<NodeId>) -> Option<DisplayListTagId> {
3535 let node_id = id?;
3536 let tag_mapping = dom.tag_ids_to_node_ids.as_ref().iter().find(|m| {
3537 m.node_id.into_crate_internal() == Some(node_id)
3538 })?;
3539 Some((tag_mapping.tag_id.inner, 0x0100))
3542}
3543
3544fn get_image_ref_for_image_source(
3545 source: &ImageSource,
3546) -> Option<ImageRef> {
3547 match source {
3548 ImageSource::Ref(image_ref) => Some(image_ref.clone()),
3549 ImageSource::Url(_url) => {
3550 None
3553 }
3554 ImageSource::Data(_) | ImageSource::Svg(_) | ImageSource::Placeholder(_) => {
3555 None
3557 }
3558 }
3559}
3560
3561fn get_display_item_bounds(item: &DisplayListItem) -> Option<LogicalRect> {
3563 match item {
3564 DisplayListItem::Rect { bounds, .. } => Some(*bounds),
3565 DisplayListItem::SelectionRect { bounds, .. } => Some(*bounds),
3566 DisplayListItem::CursorRect { bounds, .. } => Some(*bounds),
3567 DisplayListItem::Border { bounds, .. } => Some(*bounds),
3568 DisplayListItem::TextLayout { bounds, .. } => Some(*bounds),
3569 DisplayListItem::Text { clip_rect, .. } => Some(*clip_rect),
3570 DisplayListItem::Underline { bounds, .. } => Some(*bounds),
3571 DisplayListItem::Strikethrough { bounds, .. } => Some(*bounds),
3572 DisplayListItem::Overline { bounds, .. } => Some(*bounds),
3573 DisplayListItem::Image { bounds, .. } => Some(*bounds),
3574 DisplayListItem::ScrollBar { bounds, .. } => Some(*bounds),
3575 DisplayListItem::ScrollBarStyled { info } => Some(info.bounds),
3576 DisplayListItem::PushClip { bounds, .. } => Some(*bounds),
3577 DisplayListItem::PushScrollFrame { clip_bounds, .. } => Some(*clip_bounds),
3578 DisplayListItem::HitTestArea { bounds, .. } => Some(*bounds),
3579 DisplayListItem::PushStackingContext { bounds, .. } => Some(*bounds),
3580 DisplayListItem::IFrame { bounds, .. } => Some(*bounds),
3581 _ => None,
3582 }
3583}
3584
3585fn clip_and_offset_display_item(
3588 item: &DisplayListItem,
3589 page_top: f32,
3590 page_bottom: f32,
3591) -> Option<DisplayListItem> {
3592 match item {
3593 DisplayListItem::Rect {
3594 bounds,
3595 color,
3596 border_radius,
3597 } => clip_rect_item(*bounds, *color, *border_radius, page_top, page_bottom),
3598
3599 DisplayListItem::Border {
3600 bounds,
3601 widths,
3602 colors,
3603 styles,
3604 border_radius,
3605 } => clip_border_item(
3606 *bounds,
3607 *widths,
3608 *colors,
3609 *styles,
3610 border_radius.clone(),
3611 page_top,
3612 page_bottom,
3613 ),
3614
3615 DisplayListItem::SelectionRect {
3616 bounds,
3617 border_radius,
3618 color,
3619 } => clip_selection_rect_item(*bounds, *border_radius, *color, page_top, page_bottom),
3620
3621 DisplayListItem::CursorRect { bounds, color } => {
3622 clip_cursor_rect_item(*bounds, *color, page_top, page_bottom)
3623 }
3624
3625 DisplayListItem::Image { bounds, image } => {
3626 clip_image_item(*bounds, image.clone(), page_top, page_bottom)
3627 }
3628
3629 DisplayListItem::TextLayout {
3630 layout,
3631 bounds,
3632 font_hash,
3633 font_size_px,
3634 color,
3635 } => clip_text_layout_item(
3636 layout,
3637 *bounds,
3638 *font_hash,
3639 *font_size_px,
3640 *color,
3641 page_top,
3642 page_bottom,
3643 ),
3644
3645 DisplayListItem::Text {
3646 glyphs,
3647 font_hash,
3648 font_size_px,
3649 color,
3650 clip_rect,
3651 } => clip_text_item(
3652 glyphs,
3653 *font_hash,
3654 *font_size_px,
3655 *color,
3656 *clip_rect,
3657 page_top,
3658 page_bottom,
3659 ),
3660
3661 DisplayListItem::Underline {
3662 bounds,
3663 color,
3664 thickness,
3665 } => clip_text_decoration_item(
3666 *bounds,
3667 *color,
3668 *thickness,
3669 TextDecorationType::Underline,
3670 page_top,
3671 page_bottom,
3672 ),
3673
3674 DisplayListItem::Strikethrough {
3675 bounds,
3676 color,
3677 thickness,
3678 } => clip_text_decoration_item(
3679 *bounds,
3680 *color,
3681 *thickness,
3682 TextDecorationType::Strikethrough,
3683 page_top,
3684 page_bottom,
3685 ),
3686
3687 DisplayListItem::Overline {
3688 bounds,
3689 color,
3690 thickness,
3691 } => clip_text_decoration_item(
3692 *bounds,
3693 *color,
3694 *thickness,
3695 TextDecorationType::Overline,
3696 page_top,
3697 page_bottom,
3698 ),
3699
3700 DisplayListItem::ScrollBar {
3701 bounds,
3702 color,
3703 orientation,
3704 opacity_key,
3705 hit_id,
3706 } => clip_scrollbar_item(
3707 *bounds,
3708 *color,
3709 *orientation,
3710 *opacity_key,
3711 *hit_id,
3712 page_top,
3713 page_bottom,
3714 ),
3715
3716 DisplayListItem::HitTestArea { bounds, tag } => {
3717 clip_hit_test_area_item(*bounds, *tag, page_top, page_bottom)
3718 }
3719
3720 DisplayListItem::IFrame {
3721 child_dom_id,
3722 bounds,
3723 clip_rect,
3724 } => clip_iframe_item(*child_dom_id, *bounds, *clip_rect, page_top, page_bottom),
3725
3726 DisplayListItem::ScrollBarStyled { info } => {
3728 let bounds = info.bounds;
3729 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3730 None
3731 } else {
3732 let mut clipped_info = (**info).clone();
3734 let y_offset = -page_top;
3735 clipped_info.bounds = offset_rect_y(clipped_info.bounds, y_offset);
3736 clipped_info.track_bounds = offset_rect_y(clipped_info.track_bounds, y_offset);
3737 clipped_info.thumb_bounds = offset_rect_y(clipped_info.thumb_bounds, y_offset);
3738 if let Some(b) = clipped_info.button_decrement_bounds {
3739 clipped_info.button_decrement_bounds = Some(offset_rect_y(b, y_offset));
3740 }
3741 if let Some(b) = clipped_info.button_increment_bounds {
3742 clipped_info.button_increment_bounds = Some(offset_rect_y(b, y_offset));
3743 }
3744 Some(DisplayListItem::ScrollBarStyled {
3745 info: Box::new(clipped_info),
3746 })
3747 }
3748 }
3749
3750 DisplayListItem::PushClip { .. }
3752 | DisplayListItem::PopClip
3753 | DisplayListItem::PushScrollFrame { .. }
3754 | DisplayListItem::PopScrollFrame
3755 | DisplayListItem::PushStackingContext { .. }
3756 | DisplayListItem::PopStackingContext => None,
3757
3758 DisplayListItem::LinearGradient {
3760 bounds,
3761 gradient,
3762 border_radius,
3763 } => {
3764 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3765 None
3766 } else {
3767 Some(DisplayListItem::LinearGradient {
3768 bounds: offset_rect_y(*bounds, -page_top),
3769 gradient: gradient.clone(),
3770 border_radius: *border_radius,
3771 })
3772 }
3773 }
3774 DisplayListItem::RadialGradient {
3775 bounds,
3776 gradient,
3777 border_radius,
3778 } => {
3779 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3780 None
3781 } else {
3782 Some(DisplayListItem::RadialGradient {
3783 bounds: offset_rect_y(*bounds, -page_top),
3784 gradient: gradient.clone(),
3785 border_radius: *border_radius,
3786 })
3787 }
3788 }
3789 DisplayListItem::ConicGradient {
3790 bounds,
3791 gradient,
3792 border_radius,
3793 } => {
3794 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3795 None
3796 } else {
3797 Some(DisplayListItem::ConicGradient {
3798 bounds: offset_rect_y(*bounds, -page_top),
3799 gradient: gradient.clone(),
3800 border_radius: *border_radius,
3801 })
3802 }
3803 }
3804
3805 DisplayListItem::BoxShadow {
3807 bounds,
3808 shadow,
3809 border_radius,
3810 } => {
3811 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3812 None
3813 } else {
3814 Some(DisplayListItem::BoxShadow {
3815 bounds: offset_rect_y(*bounds, -page_top),
3816 shadow: *shadow,
3817 border_radius: *border_radius,
3818 })
3819 }
3820 }
3821
3822 DisplayListItem::PushFilter { .. }
3824 | DisplayListItem::PopFilter
3825 | DisplayListItem::PushBackdropFilter { .. }
3826 | DisplayListItem::PopBackdropFilter
3827 | DisplayListItem::PushOpacity { .. }
3828 | DisplayListItem::PopOpacity
3829 | DisplayListItem::PushReferenceFrame { .. }
3830 | DisplayListItem::PopReferenceFrame
3831 | DisplayListItem::PushTextShadow { .. }
3832 | DisplayListItem::PopTextShadow => None,
3833 }
3834}
3835
3836#[derive(Debug, Clone, Copy)]
3840enum TextDecorationType {
3841 Underline,
3842 Strikethrough,
3843 Overline,
3844}
3845
3846fn clip_rect_item(
3848 bounds: LogicalRect,
3849 color: ColorU,
3850 border_radius: BorderRadius,
3851 page_top: f32,
3852 page_bottom: f32,
3853) -> Option<DisplayListItem> {
3854 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Rect {
3855 bounds: clipped,
3856 color,
3857 border_radius,
3858 })
3859}
3860
3861fn clip_border_item(
3863 bounds: LogicalRect,
3864 widths: StyleBorderWidths,
3865 colors: StyleBorderColors,
3866 styles: StyleBorderStyles,
3867 border_radius: StyleBorderRadius,
3868 page_top: f32,
3869 page_bottom: f32,
3870) -> Option<DisplayListItem> {
3871 let original_bounds = bounds;
3872 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| {
3873 let new_widths = adjust_border_widths_for_clipping(
3874 widths,
3875 original_bounds,
3876 clipped,
3877 page_top,
3878 page_bottom,
3879 );
3880 DisplayListItem::Border {
3881 bounds: clipped,
3882 widths: new_widths,
3883 colors,
3884 styles,
3885 border_radius,
3886 }
3887 })
3888}
3889
3890fn adjust_border_widths_for_clipping(
3893 mut widths: StyleBorderWidths,
3894 original_bounds: LogicalRect,
3895 clipped: LogicalRect,
3896 page_top: f32,
3897 page_bottom: f32,
3898) -> StyleBorderWidths {
3899 if clipped.origin.y > 0.0 && original_bounds.origin.y < page_top {
3901 widths.top = None;
3902 }
3903
3904 let original_bottom = original_bounds.origin.y + original_bounds.size.height;
3906 let clipped_bottom = clipped.origin.y + clipped.size.height;
3907 if original_bottom > page_bottom && clipped_bottom >= page_bottom - page_top - 1.0 {
3908 widths.bottom = None;
3909 }
3910
3911 widths
3912}
3913
3914fn clip_selection_rect_item(
3916 bounds: LogicalRect,
3917 border_radius: BorderRadius,
3918 color: ColorU,
3919 page_top: f32,
3920 page_bottom: f32,
3921) -> Option<DisplayListItem> {
3922 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::SelectionRect {
3923 bounds: clipped,
3924 border_radius,
3925 color,
3926 })
3927}
3928
3929fn clip_cursor_rect_item(
3931 bounds: LogicalRect,
3932 color: ColorU,
3933 page_top: f32,
3934 page_bottom: f32,
3935) -> Option<DisplayListItem> {
3936 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::CursorRect {
3937 bounds: clipped,
3938 color,
3939 })
3940}
3941
3942fn clip_image_item(
3944 bounds: LogicalRect,
3945 image: ImageRef,
3946 page_top: f32,
3947 page_bottom: f32,
3948) -> Option<DisplayListItem> {
3949 if !rect_intersects(&bounds, page_top, page_bottom) {
3950 return None;
3951 }
3952 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Image {
3953 bounds: clipped,
3954 image,
3955 })
3956}
3957
3958fn clip_text_layout_item(
3960 layout: &Arc<dyn std::any::Any + Send + Sync>,
3961 bounds: LogicalRect,
3962 font_hash: FontHash,
3963 font_size_px: f32,
3964 color: ColorU,
3965 page_top: f32,
3966 page_bottom: f32,
3967) -> Option<DisplayListItem> {
3968 if !rect_intersects(&bounds, page_top, page_bottom) {
3969 return None;
3970 }
3971
3972 #[cfg(feature = "text_layout")]
3974 if let Some(unified_layout) = layout.downcast_ref::<crate::text3::cache::UnifiedLayout>() {
3975 return clip_unified_layout(
3976 unified_layout,
3977 bounds,
3978 font_hash,
3979 font_size_px,
3980 color,
3981 page_top,
3982 page_bottom,
3983 );
3984 }
3985
3986 Some(DisplayListItem::TextLayout {
3988 layout: layout.clone(),
3989 bounds: offset_rect_y(bounds, -page_top),
3990 font_hash,
3991 font_size_px,
3992 color,
3993 })
3994}
3995
3996#[cfg(feature = "text_layout")]
3998fn clip_unified_layout(
3999 unified_layout: &crate::text3::cache::UnifiedLayout,
4000 bounds: LogicalRect,
4001 font_hash: FontHash,
4002 font_size_px: f32,
4003 color: ColorU,
4004 page_top: f32,
4005 page_bottom: f32,
4006) -> Option<DisplayListItem> {
4007 let layout_origin_y = bounds.origin.y;
4008 let layout_origin_x = bounds.origin.x;
4009
4010 let filtered_items: Vec<_> = unified_layout
4012 .items
4013 .iter()
4014 .filter(|item| item_center_on_page(item, layout_origin_y, page_top, page_bottom))
4015 .cloned()
4016 .collect();
4017
4018 if filtered_items.is_empty() {
4019 return None;
4020 }
4021
4022 let new_origin_y = (layout_origin_y - page_top).max(0.0);
4024
4025 let (offset_items, min_y, max_y, max_width) =
4027 transform_items_to_page_coords(filtered_items, layout_origin_y, page_top, new_origin_y);
4028
4029 let new_layout = crate::text3::cache::UnifiedLayout {
4030 items: offset_items,
4031 overflow: unified_layout.overflow.clone(),
4032 };
4033
4034 let new_bounds = LogicalRect {
4035 origin: LogicalPosition {
4036 x: layout_origin_x,
4037 y: new_origin_y,
4038 },
4039 size: LogicalSize {
4040 width: max_width.max(bounds.size.width),
4041 height: (max_y - min_y.min(0.0)).max(0.0),
4042 },
4043 };
4044
4045 Some(DisplayListItem::TextLayout {
4046 layout: Arc::new(new_layout) as Arc<dyn std::any::Any + Send + Sync>,
4047 bounds: new_bounds,
4048 font_hash,
4049 font_size_px,
4050 color,
4051 })
4052}
4053
4054#[cfg(feature = "text_layout")]
4056fn item_center_on_page(
4057 item: &crate::text3::cache::PositionedItem,
4058 layout_origin_y: f32,
4059 page_top: f32,
4060 page_bottom: f32,
4061) -> bool {
4062 let item_y_absolute = layout_origin_y + item.position.y;
4063 let item_height = item.item.bounds().height;
4064 let item_center_y = item_y_absolute + (item_height / 2.0);
4065 item_center_y >= page_top && item_center_y < page_bottom
4066}
4067
4068#[cfg(feature = "text_layout")]
4071fn transform_items_to_page_coords(
4072 items: Vec<crate::text3::cache::PositionedItem>,
4073 layout_origin_y: f32,
4074 page_top: f32,
4075 new_origin_y: f32,
4076) -> (Vec<crate::text3::cache::PositionedItem>, f32, f32, f32) {
4077 let mut min_y = f32::MAX;
4078 let mut max_y = f32::MIN;
4079 let mut max_width = 0.0f32;
4080
4081 let offset_items: Vec<_> = items
4082 .into_iter()
4083 .map(|mut item| {
4084 let abs_y = layout_origin_y + item.position.y;
4085 let page_y = abs_y - page_top;
4086 let new_item_y = page_y - new_origin_y;
4087
4088 let item_bounds = item.item.bounds();
4089 min_y = min_y.min(new_item_y);
4090 max_y = max_y.max(new_item_y + item_bounds.height);
4091 max_width = max_width.max(item.position.x + item_bounds.width);
4092
4093 item.position.y = new_item_y;
4094 item
4095 })
4096 .collect();
4097
4098 (offset_items, min_y, max_y, max_width)
4099}
4100
4101fn clip_text_item(
4103 glyphs: &[GlyphInstance],
4104 font_hash: FontHash,
4105 font_size_px: f32,
4106 color: ColorU,
4107 clip_rect: LogicalRect,
4108 page_top: f32,
4109 page_bottom: f32,
4110) -> Option<DisplayListItem> {
4111 if !rect_intersects(&clip_rect, page_top, page_bottom) {
4112 return None;
4113 }
4114
4115 let page_glyphs: Vec<_> = glyphs
4117 .iter()
4118 .filter(|g| g.point.y >= page_top && g.point.y < page_bottom)
4119 .map(|g| GlyphInstance {
4120 index: g.index,
4121 point: LogicalPosition {
4122 x: g.point.x,
4123 y: g.point.y - page_top,
4124 },
4125 size: g.size,
4126 })
4127 .collect();
4128
4129 if page_glyphs.is_empty() {
4130 return None;
4131 }
4132
4133 Some(DisplayListItem::Text {
4134 glyphs: page_glyphs,
4135 font_hash,
4136 font_size_px,
4137 color,
4138 clip_rect: offset_rect_y(clip_rect, -page_top),
4139 })
4140}
4141
4142fn clip_text_decoration_item(
4144 bounds: LogicalRect,
4145 color: ColorU,
4146 thickness: f32,
4147 decoration_type: TextDecorationType,
4148 page_top: f32,
4149 page_bottom: f32,
4150) -> Option<DisplayListItem> {
4151 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| match decoration_type {
4152 TextDecorationType::Underline => DisplayListItem::Underline {
4153 bounds: clipped,
4154 color,
4155 thickness,
4156 },
4157 TextDecorationType::Strikethrough => DisplayListItem::Strikethrough {
4158 bounds: clipped,
4159 color,
4160 thickness,
4161 },
4162 TextDecorationType::Overline => DisplayListItem::Overline {
4163 bounds: clipped,
4164 color,
4165 thickness,
4166 },
4167 })
4168}
4169
4170fn clip_scrollbar_item(
4172 bounds: LogicalRect,
4173 color: ColorU,
4174 orientation: ScrollbarOrientation,
4175 opacity_key: Option<OpacityKey>,
4176 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
4177 page_top: f32,
4178 page_bottom: f32,
4179) -> Option<DisplayListItem> {
4180 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::ScrollBar {
4181 bounds: clipped,
4182 color,
4183 orientation,
4184 opacity_key,
4185 hit_id,
4186 })
4187}
4188
4189fn clip_hit_test_area_item(
4191 bounds: LogicalRect,
4192 tag: DisplayListTagId,
4193 page_top: f32,
4194 page_bottom: f32,
4195) -> Option<DisplayListItem> {
4196 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::HitTestArea {
4197 bounds: clipped,
4198 tag,
4199 })
4200}
4201
4202fn clip_iframe_item(
4204 child_dom_id: DomId,
4205 bounds: LogicalRect,
4206 clip_rect: LogicalRect,
4207 page_top: f32,
4208 page_bottom: f32,
4209) -> Option<DisplayListItem> {
4210 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::IFrame {
4211 child_dom_id,
4212 bounds: clipped,
4213 clip_rect: offset_rect_y(clip_rect, -page_top),
4214 })
4215}
4216
4217fn clip_rect_bounds(bounds: LogicalRect, page_top: f32, page_bottom: f32) -> Option<LogicalRect> {
4220 let item_top = bounds.origin.y;
4221 let item_bottom = bounds.origin.y + bounds.size.height;
4222
4223 if item_bottom <= page_top || item_top >= page_bottom {
4225 return None;
4226 }
4227
4228 let clipped_top = item_top.max(page_top);
4230 let clipped_bottom = item_bottom.min(page_bottom);
4231 let clipped_height = clipped_bottom - clipped_top;
4232
4233 let page_relative_y = clipped_top - page_top;
4235
4236 Some(LogicalRect {
4237 origin: LogicalPosition {
4238 x: bounds.origin.x,
4239 y: page_relative_y,
4240 },
4241 size: LogicalSize {
4242 width: bounds.size.width,
4243 height: clipped_height,
4244 },
4245 })
4246}
4247
4248fn rect_intersects(bounds: &LogicalRect, page_top: f32, page_bottom: f32) -> bool {
4250 let item_top = bounds.origin.y;
4251 let item_bottom = bounds.origin.y + bounds.size.height;
4252 item_bottom > page_top && item_top < page_bottom
4253}
4254
4255fn offset_rect_y(bounds: LogicalRect, offset_y: f32) -> LogicalRect {
4257 LogicalRect {
4258 origin: LogicalPosition {
4259 x: bounds.origin.x,
4260 y: bounds.origin.y + offset_y,
4261 },
4262 size: bounds.size,
4263 }
4264}
4265
4266use azul_css::props::layout::fragmentation::{BreakInside, PageBreak};
4275
4276use crate::solver3::pagination::{
4277 HeaderFooterConfig, MarginBoxContent, PageInfo, TableHeaderInfo, TableHeaderTracker,
4278};
4279
4280#[derive(Debug, Clone, Default)]
4282pub struct SlicerConfig {
4283 pub page_content_height: f32,
4285 pub page_gap: f32,
4288 pub allow_clipping: bool,
4290 pub header_footer: HeaderFooterConfig,
4292 pub page_width: f32,
4294 pub table_headers: TableHeaderTracker,
4296}
4297
4298impl SlicerConfig {
4299 pub fn simple(page_height: f32) -> Self {
4301 Self {
4302 page_content_height: page_height,
4303 page_gap: 0.0,
4304 allow_clipping: true,
4305 header_footer: HeaderFooterConfig::default(),
4306 page_width: 595.0, table_headers: TableHeaderTracker::default(),
4308 }
4309 }
4310
4311 pub fn with_gap(page_height: f32, gap: f32) -> Self {
4313 Self {
4314 page_content_height: page_height,
4315 page_gap: gap,
4316 allow_clipping: true,
4317 header_footer: HeaderFooterConfig::default(),
4318 page_width: 595.0,
4319 table_headers: TableHeaderTracker::default(),
4320 }
4321 }
4322
4323 pub fn with_header_footer(mut self, config: HeaderFooterConfig) -> Self {
4325 self.header_footer = config;
4326 self
4327 }
4328
4329 pub fn with_page_width(mut self, width: f32) -> Self {
4331 self.page_width = width;
4332 self
4333 }
4334
4335 pub fn with_table_headers(mut self, tracker: TableHeaderTracker) -> Self {
4337 self.table_headers = tracker;
4338 self
4339 }
4340
4341 pub fn register_table_header(&mut self, info: TableHeaderInfo) {
4343 self.table_headers.register_table_header(info);
4344 }
4345
4346 pub fn page_slot_height(&self) -> f32 {
4348 self.page_content_height + self.page_gap
4349 }
4350
4351 pub fn page_for_y(&self, y: f32) -> usize {
4353 if self.page_slot_height() <= 0.0 {
4354 return 0;
4355 }
4356 (y / self.page_slot_height()).floor() as usize
4357 }
4358
4359 pub fn page_bounds(&self, page_index: usize) -> (f32, f32) {
4361 let start = page_index as f32 * self.page_slot_height();
4362 let end = start + self.page_content_height;
4363 (start, end)
4364 }
4365}
4366
4367pub fn paginate_display_list_with_slicer_and_breaks(
4375 full_display_list: DisplayList,
4376 config: &SlicerConfig,
4377) -> Result<Vec<DisplayList>> {
4378 if config.page_content_height <= 0.0 || config.page_content_height >= f32::MAX {
4379 return Ok(vec![full_display_list]);
4380 }
4381
4382 let base_header_space = if config.header_footer.show_header {
4384 config.header_footer.header_height
4385 } else {
4386 0.0
4387 };
4388 let base_footer_space = if config.header_footer.show_footer {
4389 config.header_footer.footer_height
4390 } else {
4391 0.0
4392 };
4393
4394 let normal_page_content_height =
4396 config.page_content_height - base_header_space - base_footer_space;
4397 let first_page_content_height = if config.header_footer.skip_first_page {
4398 config.page_content_height
4400 } else {
4401 normal_page_content_height
4402 };
4403
4404 let page_breaks = calculate_page_break_positions(
4414 &full_display_list,
4415 first_page_content_height,
4416 normal_page_content_height,
4417 );
4418
4419 let num_pages = page_breaks.len();
4420
4421 let mut pages: Vec<DisplayList> = Vec::with_capacity(num_pages);
4423
4424 for (page_idx, &(content_start_y, content_end_y)) in page_breaks.iter().enumerate() {
4425 let page_info = PageInfo::new(page_idx + 1, num_pages);
4427
4428 let skip_this_page = config.header_footer.skip_first_page && page_info.is_first;
4430 let header_space = if config.header_footer.show_header && !skip_this_page {
4431 config.header_footer.header_height
4432 } else {
4433 0.0
4434 };
4435 let footer_space = if config.header_footer.show_footer && !skip_this_page {
4436 config.header_footer.footer_height
4437 } else {
4438 0.0
4439 };
4440
4441 let _ = footer_space; let mut page_items = Vec::new();
4444 let mut page_node_mapping = Vec::new();
4445
4446 if config.header_footer.show_header && !skip_this_page {
4448 let header_text = config.header_footer.header_text(page_info);
4449 if !header_text.is_empty() {
4450 let header_items = generate_text_display_items(
4451 &header_text,
4452 LogicalRect {
4453 origin: LogicalPosition { x: 0.0, y: 0.0 },
4454 size: LogicalSize {
4455 width: config.page_width,
4456 height: config.header_footer.header_height,
4457 },
4458 },
4459 config.header_footer.font_size,
4460 config.header_footer.text_color,
4461 TextAlignment::Center,
4462 );
4463 for item in header_items {
4464 page_items.push(item);
4465 page_node_mapping.push(None);
4466 }
4467 }
4468 }
4469
4470 let repeated_headers = config.table_headers.get_repeated_headers_for_page(
4472 page_idx,
4473 content_start_y,
4474 content_end_y,
4475 );
4476
4477 let mut thead_total_height = 0.0f32;
4478 for (y_offset_from_page_top, thead_items, thead_height) in repeated_headers {
4479 let thead_y = header_space + y_offset_from_page_top;
4480 for item in thead_items {
4481 let translated_item = offset_display_item_y(item, thead_y);
4482 page_items.push(translated_item);
4483 page_node_mapping.push(None);
4484 }
4485 thead_total_height = thead_total_height.max(thead_height);
4486 }
4487
4488 let content_y_offset = header_space + thead_total_height;
4490
4491 for (item_idx, item) in full_display_list.items.iter().enumerate() {
4493 if let Some(clipped_item) =
4494 clip_and_offset_display_item(item, content_start_y, content_end_y)
4495 {
4496 let final_item = if content_y_offset > 0.0 {
4497 offset_display_item_y(&clipped_item, content_y_offset)
4498 } else {
4499 clipped_item
4500 };
4501 page_items.push(final_item);
4502 let node_mapping = full_display_list
4503 .node_mapping
4504 .get(item_idx)
4505 .copied()
4506 .flatten();
4507 page_node_mapping.push(node_mapping);
4508 }
4509 }
4510
4511 if config.header_footer.show_footer && !skip_this_page {
4513 let footer_text = config.header_footer.footer_text(page_info);
4514 if !footer_text.is_empty() {
4515 let footer_y = config.page_content_height - config.header_footer.footer_height;
4516 let footer_items = generate_text_display_items(
4517 &footer_text,
4518 LogicalRect {
4519 origin: LogicalPosition {
4520 x: 0.0,
4521 y: footer_y,
4522 },
4523 size: LogicalSize {
4524 width: config.page_width,
4525 height: config.header_footer.footer_height,
4526 },
4527 },
4528 config.header_footer.font_size,
4529 config.header_footer.text_color,
4530 TextAlignment::Center,
4531 );
4532 for item in footer_items {
4533 page_items.push(item);
4534 page_node_mapping.push(None);
4535 }
4536 }
4537 }
4538
4539 pages.push(DisplayList {
4540 items: page_items,
4541 node_mapping: page_node_mapping,
4542 forced_page_breaks: Vec::new(), });
4544 }
4545
4546 if pages.is_empty() {
4548 pages.push(DisplayList::default());
4549 }
4550
4551 Ok(pages)
4552}
4553
4554fn calculate_page_break_positions(
4562 display_list: &DisplayList,
4563 first_page_height: f32,
4564 normal_page_height: f32,
4565) -> Vec<(f32, f32)> {
4566 let total_height = calculate_display_list_height(display_list);
4567
4568 if total_height <= 0.0 || first_page_height <= 0.0 {
4569 return vec![(0.0, total_height.max(first_page_height))];
4570 }
4571
4572 let mut break_points: Vec<f32> = Vec::new();
4574
4575 for &forced_break_y in &display_list.forced_page_breaks {
4577 if forced_break_y > 0.0 && forced_break_y < total_height {
4578 break_points.push(forced_break_y);
4579 }
4580 }
4581
4582 let mut y = first_page_height;
4584 while y < total_height {
4585 break_points.push(y);
4586 y += normal_page_height;
4587 }
4588
4589 break_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
4591 break_points.dedup_by(|a, b| (*a - *b).abs() < 1.0); let mut page_breaks: Vec<(f32, f32)> = Vec::new();
4595 let mut page_start = 0.0f32;
4596
4597 for break_y in break_points {
4598 if break_y > page_start {
4599 page_breaks.push((page_start, break_y));
4600 page_start = break_y;
4601 }
4602 }
4603
4604 if page_start < total_height {
4606 page_breaks.push((page_start, total_height));
4607 }
4608
4609 if page_breaks.is_empty() {
4611 page_breaks.push((0.0, total_height.max(first_page_height)));
4612 }
4613
4614 page_breaks
4615}
4616
4617#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4619pub enum TextAlignment {
4620 Left,
4621 Center,
4622 Right,
4623}
4624
4625fn offset_display_item_y(item: &DisplayListItem, y_offset: f32) -> DisplayListItem {
4627 if y_offset == 0.0 {
4628 return item.clone();
4629 }
4630
4631 match item {
4632 DisplayListItem::Rect {
4633 bounds,
4634 color,
4635 border_radius,
4636 } => DisplayListItem::Rect {
4637 bounds: offset_rect_y(*bounds, y_offset),
4638 color: *color,
4639 border_radius: *border_radius,
4640 },
4641 DisplayListItem::Border {
4642 bounds,
4643 widths,
4644 colors,
4645 styles,
4646 border_radius,
4647 } => DisplayListItem::Border {
4648 bounds: offset_rect_y(*bounds, y_offset),
4649 widths: widths.clone(),
4650 colors: *colors,
4651 styles: *styles,
4652 border_radius: border_radius.clone(),
4653 },
4654 DisplayListItem::Text {
4655 glyphs,
4656 font_hash,
4657 font_size_px,
4658 color,
4659 clip_rect,
4660 } => {
4661 let offset_glyphs: Vec<GlyphInstance> = glyphs
4662 .iter()
4663 .map(|g| GlyphInstance {
4664 index: g.index,
4665 point: LogicalPosition {
4666 x: g.point.x,
4667 y: g.point.y + y_offset,
4668 },
4669 size: g.size,
4670 })
4671 .collect();
4672 DisplayListItem::Text {
4673 glyphs: offset_glyphs,
4674 font_hash: *font_hash,
4675 font_size_px: *font_size_px,
4676 color: *color,
4677 clip_rect: offset_rect_y(*clip_rect, y_offset),
4678 }
4679 }
4680 DisplayListItem::TextLayout {
4681 layout,
4682 bounds,
4683 font_hash,
4684 font_size_px,
4685 color,
4686 } => DisplayListItem::TextLayout {
4687 layout: layout.clone(),
4688 bounds: offset_rect_y(*bounds, y_offset),
4689 font_hash: *font_hash,
4690 font_size_px: *font_size_px,
4691 color: *color,
4692 },
4693 DisplayListItem::Image { bounds, image } => DisplayListItem::Image {
4694 bounds: offset_rect_y(*bounds, y_offset),
4695 image: image.clone(),
4696 },
4697 DisplayListItem::SelectionRect {
4699 bounds,
4700 border_radius,
4701 color,
4702 } => DisplayListItem::SelectionRect {
4703 bounds: offset_rect_y(*bounds, y_offset),
4704 border_radius: *border_radius,
4705 color: *color,
4706 },
4707 DisplayListItem::CursorRect { bounds, color } => DisplayListItem::CursorRect {
4708 bounds: offset_rect_y(*bounds, y_offset),
4709 color: *color,
4710 },
4711 DisplayListItem::Underline {
4712 bounds,
4713 color,
4714 thickness,
4715 } => DisplayListItem::Underline {
4716 bounds: offset_rect_y(*bounds, y_offset),
4717 color: *color,
4718 thickness: *thickness,
4719 },
4720 DisplayListItem::Strikethrough {
4721 bounds,
4722 color,
4723 thickness,
4724 } => DisplayListItem::Strikethrough {
4725 bounds: offset_rect_y(*bounds, y_offset),
4726 color: *color,
4727 thickness: *thickness,
4728 },
4729 DisplayListItem::Overline {
4730 bounds,
4731 color,
4732 thickness,
4733 } => DisplayListItem::Overline {
4734 bounds: offset_rect_y(*bounds, y_offset),
4735 color: *color,
4736 thickness: *thickness,
4737 },
4738 DisplayListItem::ScrollBar {
4739 bounds,
4740 color,
4741 orientation,
4742 opacity_key,
4743 hit_id,
4744 } => DisplayListItem::ScrollBar {
4745 bounds: offset_rect_y(*bounds, y_offset),
4746 color: *color,
4747 orientation: *orientation,
4748 opacity_key: *opacity_key,
4749 hit_id: *hit_id,
4750 },
4751 DisplayListItem::HitTestArea { bounds, tag } => DisplayListItem::HitTestArea {
4752 bounds: offset_rect_y(*bounds, y_offset),
4753 tag: *tag,
4754 },
4755 DisplayListItem::PushClip {
4756 bounds,
4757 border_radius,
4758 } => DisplayListItem::PushClip {
4759 bounds: offset_rect_y(*bounds, y_offset),
4760 border_radius: *border_radius,
4761 },
4762 DisplayListItem::PushScrollFrame {
4763 clip_bounds,
4764 content_size,
4765 scroll_id,
4766 } => DisplayListItem::PushScrollFrame {
4767 clip_bounds: offset_rect_y(*clip_bounds, y_offset),
4768 content_size: *content_size,
4769 scroll_id: *scroll_id,
4770 },
4771 DisplayListItem::PushStackingContext { bounds, z_index } => {
4772 DisplayListItem::PushStackingContext {
4773 bounds: offset_rect_y(*bounds, y_offset),
4774 z_index: *z_index,
4775 }
4776 }
4777 DisplayListItem::IFrame {
4778 child_dom_id,
4779 bounds,
4780 clip_rect,
4781 } => DisplayListItem::IFrame {
4782 child_dom_id: *child_dom_id,
4783 bounds: offset_rect_y(*bounds, y_offset),
4784 clip_rect: offset_rect_y(*clip_rect, y_offset),
4785 },
4786 DisplayListItem::PopClip => DisplayListItem::PopClip,
4788 DisplayListItem::PopScrollFrame => DisplayListItem::PopScrollFrame,
4789 DisplayListItem::PopStackingContext => DisplayListItem::PopStackingContext,
4790
4791 DisplayListItem::LinearGradient {
4793 bounds,
4794 gradient,
4795 border_radius,
4796 } => DisplayListItem::LinearGradient {
4797 bounds: offset_rect_y(*bounds, y_offset),
4798 gradient: gradient.clone(),
4799 border_radius: *border_radius,
4800 },
4801 DisplayListItem::RadialGradient {
4802 bounds,
4803 gradient,
4804 border_radius,
4805 } => DisplayListItem::RadialGradient {
4806 bounds: offset_rect_y(*bounds, y_offset),
4807 gradient: gradient.clone(),
4808 border_radius: *border_radius,
4809 },
4810 DisplayListItem::ConicGradient {
4811 bounds,
4812 gradient,
4813 border_radius,
4814 } => DisplayListItem::ConicGradient {
4815 bounds: offset_rect_y(*bounds, y_offset),
4816 gradient: gradient.clone(),
4817 border_radius: *border_radius,
4818 },
4819
4820 DisplayListItem::BoxShadow {
4822 bounds,
4823 shadow,
4824 border_radius,
4825 } => DisplayListItem::BoxShadow {
4826 bounds: offset_rect_y(*bounds, y_offset),
4827 shadow: *shadow,
4828 border_radius: *border_radius,
4829 },
4830
4831 DisplayListItem::PushFilter { bounds, filters } => DisplayListItem::PushFilter {
4833 bounds: offset_rect_y(*bounds, y_offset),
4834 filters: filters.clone(),
4835 },
4836 DisplayListItem::PopFilter => DisplayListItem::PopFilter,
4837 DisplayListItem::PushBackdropFilter { bounds, filters } => {
4838 DisplayListItem::PushBackdropFilter {
4839 bounds: offset_rect_y(*bounds, y_offset),
4840 filters: filters.clone(),
4841 }
4842 }
4843 DisplayListItem::PopBackdropFilter => DisplayListItem::PopBackdropFilter,
4844 DisplayListItem::PushOpacity { bounds, opacity } => DisplayListItem::PushOpacity {
4845 bounds: offset_rect_y(*bounds, y_offset),
4846 opacity: *opacity,
4847 },
4848 DisplayListItem::PopOpacity => DisplayListItem::PopOpacity,
4849 DisplayListItem::ScrollBarStyled { info } => {
4850 let mut offset_info = (**info).clone();
4851 offset_info.bounds = offset_rect_y(offset_info.bounds, y_offset);
4852 offset_info.track_bounds = offset_rect_y(offset_info.track_bounds, y_offset);
4853 offset_info.thumb_bounds = offset_rect_y(offset_info.thumb_bounds, y_offset);
4854 if let Some(b) = offset_info.button_decrement_bounds {
4855 offset_info.button_decrement_bounds = Some(offset_rect_y(b, y_offset));
4856 }
4857 if let Some(b) = offset_info.button_increment_bounds {
4858 offset_info.button_increment_bounds = Some(offset_rect_y(b, y_offset));
4859 }
4860 DisplayListItem::ScrollBarStyled {
4861 info: Box::new(offset_info),
4862 }
4863 }
4864
4865 DisplayListItem::PushReferenceFrame {
4867 transform_key,
4868 initial_transform,
4869 bounds,
4870 } => DisplayListItem::PushReferenceFrame {
4871 transform_key: *transform_key,
4872 initial_transform: *initial_transform,
4873 bounds: offset_rect_y(*bounds, y_offset),
4874 },
4875 DisplayListItem::PopReferenceFrame => DisplayListItem::PopReferenceFrame,
4876 DisplayListItem::PushTextShadow { shadow } => DisplayListItem::PushTextShadow {
4877 shadow: shadow.clone(),
4878 },
4879 DisplayListItem::PopTextShadow => DisplayListItem::PopTextShadow,
4880 }
4881}
4882
4883fn generate_text_display_items(
4888 text: &str,
4889 bounds: LogicalRect,
4890 font_size: f32,
4891 color: ColorU,
4892 alignment: TextAlignment,
4893) -> Vec<DisplayListItem> {
4894 use crate::font_traits::FontHash;
4895
4896 if text.is_empty() {
4897 return Vec::new();
4898 }
4899
4900 let char_width = font_size * 0.5;
4903 let text_width = text.len() as f32 * char_width;
4904
4905 let x_offset = match alignment {
4906 TextAlignment::Left => bounds.origin.x,
4907 TextAlignment::Center => bounds.origin.x + (bounds.size.width - text_width) / 2.0,
4908 TextAlignment::Right => bounds.origin.x + bounds.size.width - text_width,
4909 };
4910
4911 let y_pos = bounds.origin.y + (bounds.size.height + font_size) / 2.0 - font_size * 0.2;
4913
4914 let glyphs: Vec<GlyphInstance> = text
4917 .chars()
4918 .enumerate()
4919 .filter(|(_, c)| !c.is_control())
4920 .map(|(i, c)| GlyphInstance {
4921 index: c as u32, point: LogicalPosition {
4923 x: x_offset + i as f32 * char_width,
4924 y: y_pos,
4925 },
4926 size: LogicalSize::new(char_width, font_size),
4927 })
4928 .collect();
4929
4930 if glyphs.is_empty() {
4931 return Vec::new();
4932 }
4933
4934 vec![DisplayListItem::Text {
4935 glyphs,
4936 font_hash: FontHash::from_hash(0), font_size_px: font_size,
4938 color,
4939 clip_rect: bounds,
4940 }]
4941}
4942
4943fn calculate_display_list_height(display_list: &DisplayList) -> f32 {
4945 let mut max_bottom = 0.0f32;
4946
4947 for item in &display_list.items {
4948 if let Some(bounds) = get_display_item_bounds(item) {
4949 if bounds.size.height < 0.1 {
4951 continue;
4952 }
4953
4954 let item_bottom = bounds.origin.y + bounds.size.height;
4955 if item_bottom > max_bottom {
4956 max_bottom = item_bottom;
4957 }
4958 }
4959 }
4960
4961 max_bottom
4962}
4963
4964#[derive(Debug, Clone, Copy, Default)]
4966pub struct BreakProperties {
4967 pub break_before: PageBreak,
4968 pub break_after: PageBreak,
4969 pub break_inside: BreakInside,
4970}