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,
14 },
15 selection::{Selection, SelectionRange, SelectionState, TextSelection},
16 styled_dom::StyledDom,
17 ui_solver::GlyphInstance,
18};
19use azul_css::{
20 css::CssPropertyValue,
21 format_rust_code::GetHash,
22 props::{
23 basic::{ColorU, FontRef, PixelValue},
24 layout::{LayoutDisplay, LayoutOverflow, LayoutPosition},
25 property::{CssProperty, CssPropertyType},
26 style::{
27 background::{ConicGradient, ExtendMode, LinearGradient, RadialGradient},
28 border_radius::StyleBorderRadius,
29 box_shadow::{BoxShadowClipMode, StyleBoxShadow},
30 filter::{StyleFilter, StyleFilterVec},
31 BorderStyle, LayoutBorderBottomWidth, LayoutBorderLeftWidth, LayoutBorderRightWidth,
32 LayoutBorderTopWidth, StyleBorderBottomColor, StyleBorderBottomStyle,
33 StyleBorderLeftColor, StyleBorderLeftStyle, StyleBorderRightColor,
34 StyleBorderRightStyle, StyleBorderTopColor, StyleBorderTopStyle,
35 },
36 },
37 LayoutDebugMessage,
38};
39
40#[cfg(feature = "text_layout")]
41use crate::text3;
42#[cfg(feature = "text_layout")]
43use crate::text3::cache::{InlineShape, PositionedItem};
44use crate::{
45 debug_info,
46 font_traits::{
47 FontHash, FontLoaderTrait, ImageSource, InlineContent, ParsedFontTrait, ShapedItem,
48 UnifiedLayout,
49 },
50 solver3::{
51 getters::{
52 get_background_color, get_background_contents, get_border_info, get_border_radius,
53 get_break_after, get_break_before, get_caret_style, get_overflow_x, get_overflow_y,
54 get_scrollbar_info_from_layout, get_scrollbar_style, get_selection_style,
55 get_style_border_radius, get_z_index, is_forced_page_break, BorderInfo, CaretStyle,
56 ComputedScrollbarStyle, SelectionStyle,
57 },
58 layout_tree::{LayoutNode, LayoutTree},
59 positioning::get_position_type,
60 scrollbar::ScrollbarRequirements,
61 LayoutContext, LayoutError, Result,
62 },
63};
64
65#[derive(Debug, Clone, Copy)]
70pub struct StyleBorderWidths {
71 pub top: Option<CssPropertyValue<LayoutBorderTopWidth>>,
73 pub right: Option<CssPropertyValue<LayoutBorderRightWidth>>,
75 pub bottom: Option<CssPropertyValue<LayoutBorderBottomWidth>>,
77 pub left: Option<CssPropertyValue<LayoutBorderLeftWidth>>,
79}
80
81#[derive(Debug, Clone, Copy)]
86pub struct StyleBorderColors {
87 pub top: Option<CssPropertyValue<StyleBorderTopColor>>,
89 pub right: Option<CssPropertyValue<StyleBorderRightColor>>,
91 pub bottom: Option<CssPropertyValue<StyleBorderBottomColor>>,
93 pub left: Option<CssPropertyValue<StyleBorderLeftColor>>,
95}
96
97#[derive(Debug, Clone, Copy)]
103pub struct StyleBorderStyles {
104 pub top: Option<CssPropertyValue<StyleBorderTopStyle>>,
106 pub right: Option<CssPropertyValue<StyleBorderRightStyle>>,
108 pub bottom: Option<CssPropertyValue<StyleBorderBottomStyle>>,
110 pub left: Option<CssPropertyValue<StyleBorderLeftStyle>>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq)]
117pub struct BorderBoxRect(pub LogicalRect);
118
119#[derive(Debug, Clone, Copy)]
121pub struct PhysicalSizeImport {
122 pub width: f32,
123 pub height: f32,
124}
125
126#[derive(Debug, Clone)]
134pub struct ScrollbarDrawInfo {
135 pub bounds: LogicalRect,
137 pub orientation: ScrollbarOrientation,
139
140 pub track_bounds: LogicalRect,
143 pub track_color: ColorU,
145
146 pub thumb_bounds: LogicalRect,
149 pub thumb_color: ColorU,
151 pub thumb_border_radius: BorderRadius,
153
154 pub button_decrement_bounds: Option<LogicalRect>,
157 pub button_increment_bounds: Option<LogicalRect>,
159 pub button_color: ColorU,
161
162 pub opacity_key: Option<OpacityKey>,
164 pub hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
166 pub clip_to_container_border: bool,
168 pub container_border_radius: BorderRadius,
170}
171
172impl BorderBoxRect {
173 pub fn to_content_box(
176 self,
177 padding: &crate::solver3::geometry::EdgeSizes,
178 border: &crate::solver3::geometry::EdgeSizes,
179 ) -> ContentBoxRect {
180 ContentBoxRect(LogicalRect {
181 origin: LogicalPosition {
182 x: self.0.origin.x + padding.left + border.left,
183 y: self.0.origin.y + padding.top + border.top,
184 },
185 size: LogicalSize {
186 width: self.0.size.width
187 - padding.left
188 - padding.right
189 - border.left
190 - border.right,
191 height: self.0.size.height
192 - padding.top
193 - padding.bottom
194 - border.top
195 - border.bottom,
196 },
197 })
198 }
199
200 pub fn rect(&self) -> LogicalRect {
202 self.0
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq)]
209pub struct ContentBoxRect(pub LogicalRect);
210
211impl ContentBoxRect {
212 pub fn rect(&self) -> LogicalRect {
214 self.0
215 }
216}
217
218#[derive(Debug, Default)]
223pub struct DisplayList {
224 pub items: Vec<DisplayListItem>,
225 pub node_mapping: Vec<Option<NodeId>>,
229 pub forced_page_breaks: Vec<f32>,
233}
234
235impl DisplayList {
236 pub fn to_debug_json(&self) -> String {
239 use std::fmt::Write;
240 let mut json = String::new();
241 writeln!(json, "{{").unwrap();
242 writeln!(json, " \"total_items\": {},", self.items.len()).unwrap();
243 writeln!(json, " \"items\": [").unwrap();
244
245 let mut clip_depth = 0i32;
246 let mut scroll_depth = 0i32;
247 let mut stacking_depth = 0i32;
248
249 for (i, item) in self.items.iter().enumerate() {
250 let comma = if i < self.items.len() - 1 { "," } else { "" };
251 let node_id = self.node_mapping.get(i).and_then(|n| *n);
252
253 match item {
254 DisplayListItem::PushClip {
255 bounds,
256 border_radius,
257 } => {
258 clip_depth += 1;
259 writeln!(json, " {{").unwrap();
260 writeln!(json, " \"index\": {},", i).unwrap();
261 writeln!(json, " \"type\": \"PushClip\",").unwrap();
262 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
263 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
264 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
265 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
266 writeln!(json, " \"border_radius\": {{ \"tl\": {:.1}, \"tr\": {:.1}, \"bl\": {:.1}, \"br\": {:.1} }},",
267 border_radius.top_left, border_radius.top_right,
268 border_radius.bottom_left, border_radius.bottom_right).unwrap();
269 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
270 writeln!(json, " }}{}", comma).unwrap();
271 }
272 DisplayListItem::PopClip => {
273 writeln!(json, " {{").unwrap();
274 writeln!(json, " \"index\": {},", i).unwrap();
275 writeln!(json, " \"type\": \"PopClip\",").unwrap();
276 writeln!(json, " \"clip_depth_before\": {},", clip_depth).unwrap();
277 writeln!(json, " \"clip_depth_after\": {}", clip_depth - 1).unwrap();
278 writeln!(json, " }}{}", comma).unwrap();
279 clip_depth -= 1;
280 }
281 DisplayListItem::PushScrollFrame {
282 clip_bounds,
283 content_size,
284 scroll_id,
285 } => {
286 scroll_depth += 1;
287 writeln!(json, " {{").unwrap();
288 writeln!(json, " \"index\": {},", i).unwrap();
289 writeln!(json, " \"type\": \"PushScrollFrame\",").unwrap();
290 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
291 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
292 writeln!(json, " \"clip_bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
293 clip_bounds.origin.x, clip_bounds.origin.y,
294 clip_bounds.size.width, clip_bounds.size.height).unwrap();
295 writeln!(
296 json,
297 " \"content_size\": {{ \"w\": {:.1}, \"h\": {:.1} }},",
298 content_size.width, content_size.height
299 )
300 .unwrap();
301 writeln!(json, " \"scroll_id\": {},", scroll_id).unwrap();
302 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
303 writeln!(json, " }}{}", comma).unwrap();
304 }
305 DisplayListItem::PopScrollFrame => {
306 writeln!(json, " {{").unwrap();
307 writeln!(json, " \"index\": {},", i).unwrap();
308 writeln!(json, " \"type\": \"PopScrollFrame\",").unwrap();
309 writeln!(json, " \"scroll_depth_before\": {},", scroll_depth).unwrap();
310 writeln!(json, " \"scroll_depth_after\": {}", scroll_depth - 1).unwrap();
311 writeln!(json, " }}{}", comma).unwrap();
312 scroll_depth -= 1;
313 }
314 DisplayListItem::PushStackingContext { z_index, bounds } => {
315 stacking_depth += 1;
316 writeln!(json, " {{").unwrap();
317 writeln!(json, " \"index\": {},", i).unwrap();
318 writeln!(json, " \"type\": \"PushStackingContext\",").unwrap();
319 writeln!(json, " \"stacking_depth\": {},", stacking_depth).unwrap();
320 writeln!(json, " \"z_index\": {},", z_index).unwrap();
321 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
322 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
323 writeln!(json, " }}{}", comma).unwrap();
324 }
325 DisplayListItem::PopStackingContext => {
326 writeln!(json, " {{").unwrap();
327 writeln!(json, " \"index\": {},", i).unwrap();
328 writeln!(json, " \"type\": \"PopStackingContext\",").unwrap();
329 writeln!(json, " \"stacking_depth_before\": {},", stacking_depth).unwrap();
330 writeln!(
331 json,
332 " \"stacking_depth_after\": {}",
333 stacking_depth - 1
334 )
335 .unwrap();
336 writeln!(json, " }}{}", comma).unwrap();
337 stacking_depth -= 1;
338 }
339 DisplayListItem::Rect {
340 bounds,
341 color,
342 border_radius,
343 } => {
344 writeln!(json, " {{").unwrap();
345 writeln!(json, " \"index\": {},", i).unwrap();
346 writeln!(json, " \"type\": \"Rect\",").unwrap();
347 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
348 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
349 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
350 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
351 writeln!(
352 json,
353 " \"color\": \"rgba({},{},{},{})\",",
354 color.r, color.g, color.b, color.a
355 )
356 .unwrap();
357 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
358 writeln!(json, " }}{}", comma).unwrap();
359 }
360 DisplayListItem::Border { bounds, .. } => {
361 writeln!(json, " {{").unwrap();
362 writeln!(json, " \"index\": {},", i).unwrap();
363 writeln!(json, " \"type\": \"Border\",").unwrap();
364 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
365 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
366 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }},",
367 bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height).unwrap();
368 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
369 writeln!(json, " }}{}", comma).unwrap();
370 }
371 DisplayListItem::ScrollBarStyled { info } => {
372 writeln!(json, " {{").unwrap();
373 writeln!(json, " \"index\": {},", i).unwrap();
374 writeln!(json, " \"type\": \"ScrollBarStyled\",").unwrap();
375 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
376 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
377 writeln!(json, " \"orientation\": \"{:?}\",", info.orientation).unwrap();
378 writeln!(json, " \"bounds\": {{ \"x\": {:.1}, \"y\": {:.1}, \"w\": {:.1}, \"h\": {:.1} }}",
379 info.bounds.origin.x, info.bounds.origin.y,
380 info.bounds.size.width, info.bounds.size.height).unwrap();
381 writeln!(json, " }}{}", comma).unwrap();
382 }
383 _ => {
384 writeln!(json, " {{").unwrap();
385 writeln!(json, " \"index\": {},", i).unwrap();
386 writeln!(
387 json,
388 " \"type\": \"{:?}\",",
389 std::mem::discriminant(item)
390 )
391 .unwrap();
392 writeln!(json, " \"clip_depth\": {},", clip_depth).unwrap();
393 writeln!(json, " \"scroll_depth\": {},", scroll_depth).unwrap();
394 writeln!(json, " \"node_id\": {:?}", node_id).unwrap();
395 writeln!(json, " }}{}", comma).unwrap();
396 }
397 }
398 }
399
400 writeln!(json, " ],").unwrap();
401 writeln!(json, " \"final_clip_depth\": {},", clip_depth).unwrap();
402 writeln!(json, " \"final_scroll_depth\": {},", scroll_depth).unwrap();
403 writeln!(json, " \"final_stacking_depth\": {},", stacking_depth).unwrap();
404 writeln!(
405 json,
406 " \"balanced\": {}",
407 clip_depth == 0 && scroll_depth == 0 && stacking_depth == 0
408 )
409 .unwrap();
410 writeln!(json, "}}").unwrap();
411
412 json
413 }
414}
415
416#[derive(Debug, Clone)]
419pub enum DisplayListItem {
420 Rect {
424 bounds: LogicalRect,
426 color: ColorU,
428 border_radius: BorderRadius,
430 },
431 SelectionRect {
434 bounds: LogicalRect,
436 border_radius: BorderRadius,
438 color: ColorU,
440 },
441 CursorRect {
444 bounds: LogicalRect,
446 color: ColorU,
448 },
449 Border {
452 bounds: LogicalRect,
454 widths: StyleBorderWidths,
456 colors: StyleBorderColors,
458 styles: StyleBorderStyles,
460 border_radius: StyleBorderRadius,
462 },
463 TextLayout {
467 layout: Arc<dyn std::any::Any + Send + Sync>, bounds: LogicalRect,
469 font_hash: FontHash,
470 font_size_px: f32,
471 color: ColorU,
472 },
473 Text {
475 glyphs: Vec<GlyphInstance>,
476 font_hash: FontHash, font_size_px: f32,
478 color: ColorU,
479 clip_rect: LogicalRect,
480 },
481 Underline {
483 bounds: LogicalRect,
484 color: ColorU,
485 thickness: f32,
486 },
487 Strikethrough {
489 bounds: LogicalRect,
490 color: ColorU,
491 thickness: f32,
492 },
493 Overline {
495 bounds: LogicalRect,
496 color: ColorU,
497 thickness: f32,
498 },
499 Image {
500 bounds: LogicalRect,
501 image: ImageRef,
502 },
503 ScrollBar {
506 bounds: LogicalRect,
507 color: ColorU,
508 orientation: ScrollbarOrientation,
509 opacity_key: Option<OpacityKey>,
513 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
516 },
517 ScrollBarStyled {
520 info: Box<ScrollbarDrawInfo>,
522 },
523
524 IFrame {
528 child_dom_id: DomId,
530 bounds: LogicalRect,
532 clip_rect: LogicalRect,
534 },
535
536 PushClip {
540 bounds: LogicalRect,
541 border_radius: BorderRadius,
542 },
543 PopClip,
545
546 PushScrollFrame {
549 clip_bounds: LogicalRect,
551 content_size: LogicalSize,
553 scroll_id: LocalScrollId, },
556 PopScrollFrame,
558
559 PushStackingContext {
562 z_index: i32,
564 bounds: LogicalRect,
566 },
567 PopStackingContext,
569
570 HitTestArea {
572 bounds: LogicalRect,
573 tag: DisplayListTagId, },
575
576 LinearGradient {
579 bounds: LogicalRect,
580 gradient: LinearGradient,
581 border_radius: BorderRadius,
582 },
583 RadialGradient {
585 bounds: LogicalRect,
586 gradient: RadialGradient,
587 border_radius: BorderRadius,
588 },
589 ConicGradient {
591 bounds: LogicalRect,
592 gradient: ConicGradient,
593 border_radius: BorderRadius,
594 },
595
596 BoxShadow {
599 bounds: LogicalRect,
600 shadow: StyleBoxShadow,
601 border_radius: BorderRadius,
602 },
603
604 PushFilter {
607 bounds: LogicalRect,
608 filters: Vec<StyleFilter>,
609 },
610 PopFilter,
612
613 PushBackdropFilter {
615 bounds: LogicalRect,
616 filters: Vec<StyleFilter>,
617 },
618 PopBackdropFilter,
620
621 PushOpacity {
623 bounds: LogicalRect,
624 opacity: f32,
625 },
626 PopOpacity,
628}
629
630#[derive(Debug, Copy, Clone, Default)]
632pub struct BorderRadius {
633 pub top_left: f32,
634 pub top_right: f32,
635 pub bottom_left: f32,
636 pub bottom_right: f32,
637}
638
639impl BorderRadius {
640 pub fn is_zero(&self) -> bool {
641 self.top_left == 0.0
642 && self.top_right == 0.0
643 && self.bottom_left == 0.0
644 && self.bottom_right == 0.0
645 }
646}
647
648pub type LocalScrollId = u64;
650pub type DisplayListTagId = (u64, u16);
655
656#[derive(Debug, Default)]
658struct DisplayListBuilder {
659 items: Vec<DisplayListItem>,
660 node_mapping: Vec<Option<NodeId>>,
661 current_node: Option<NodeId>,
663 debug_messages: Vec<LayoutDebugMessage>,
665 debug_enabled: bool,
667 forced_page_breaks: Vec<f32>,
669}
670
671impl DisplayListBuilder {
672 pub fn new() -> Self {
673 Self::default()
674 }
675
676 pub fn with_debug(debug_enabled: bool) -> Self {
677 Self {
678 items: Vec::new(),
679 node_mapping: Vec::new(),
680 current_node: None,
681 debug_messages: Vec::new(),
682 debug_enabled,
683 forced_page_breaks: Vec::new(),
684 }
685 }
686
687 fn debug_log(&mut self, message: String) {
689 if self.debug_enabled {
690 self.debug_messages.push(LayoutDebugMessage::info(message));
691 }
692 }
693
694 pub fn build_with_debug(
696 mut self,
697 debug_messages: &mut Option<Vec<LayoutDebugMessage>>,
698 ) -> DisplayList {
699 if let Some(msgs) = debug_messages.as_mut() {
701 msgs.append(&mut self.debug_messages);
702 }
703 DisplayList {
704 items: self.items,
705 node_mapping: self.node_mapping,
706 forced_page_breaks: self.forced_page_breaks,
707 }
708 }
709
710 pub fn set_current_node(&mut self, node_id: Option<NodeId>) {
712 self.current_node = node_id;
713 }
714
715 pub fn add_forced_page_break(&mut self, y_position: f32) {
718 if !self.forced_page_breaks.contains(&y_position) {
720 self.forced_page_breaks.push(y_position);
721 self.forced_page_breaks.sort_by(|a, b| a.partial_cmp(b).unwrap());
722 }
723 }
724
725 fn push_item(&mut self, item: DisplayListItem) {
727 self.items.push(item);
728 self.node_mapping.push(self.current_node);
729 }
730
731 pub fn build(self) -> DisplayList {
732 DisplayList {
733 items: self.items,
734 node_mapping: self.node_mapping,
735 forced_page_breaks: self.forced_page_breaks,
736 }
737 }
738
739 pub fn push_hit_test_area(&mut self, bounds: LogicalRect, tag: DisplayListTagId) {
740 self.push_item(DisplayListItem::HitTestArea { bounds, tag });
741 }
742
743 pub fn push_scrollbar(
745 &mut self,
746 bounds: LogicalRect,
747 color: ColorU,
748 orientation: ScrollbarOrientation,
749 opacity_key: Option<OpacityKey>,
750 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
751 ) {
752 if color.a > 0 || opacity_key.is_some() {
753 self.push_item(DisplayListItem::ScrollBar {
755 bounds,
756 color,
757 orientation,
758 opacity_key,
759 hit_id,
760 });
761 }
762 }
763
764 pub fn push_scrollbar_styled(&mut self, info: ScrollbarDrawInfo) {
766 if info.thumb_color.a > 0 || info.track_color.a > 0 || info.opacity_key.is_some() {
768 self.push_item(DisplayListItem::ScrollBarStyled {
769 info: Box::new(info),
770 });
771 }
772 }
773
774 pub fn push_rect(&mut self, bounds: LogicalRect, color: ColorU, border_radius: BorderRadius) {
775 if color.a > 0 {
776 self.push_item(DisplayListItem::Rect {
778 bounds,
779 color,
780 border_radius,
781 });
782 }
783 }
784
785 pub fn push_backgrounds_and_border(
795 &mut self,
796 bounds: LogicalRect,
797 background_contents: &[azul_css::props::style::StyleBackgroundContent],
798 border_info: &BorderInfo,
799 simple_border_radius: BorderRadius,
800 style_border_radius: StyleBorderRadius,
801 ) {
802 use azul_css::props::style::StyleBackgroundContent;
803
804 for bg in background_contents {
806 match bg {
807 StyleBackgroundContent::Color(color) => {
808 self.push_rect(bounds, *color, simple_border_radius);
809 }
810 StyleBackgroundContent::LinearGradient(gradient) => {
811 self.push_linear_gradient(bounds, gradient.clone(), simple_border_radius);
812 }
813 StyleBackgroundContent::RadialGradient(gradient) => {
814 self.push_radial_gradient(bounds, gradient.clone(), simple_border_radius);
815 }
816 StyleBackgroundContent::ConicGradient(gradient) => {
817 self.push_conic_gradient(bounds, gradient.clone(), simple_border_radius);
818 }
819 StyleBackgroundContent::Image(_image_id) => {
820 }
822 }
823 }
824
825 self.push_border(
827 bounds,
828 border_info.widths,
829 border_info.colors,
830 border_info.styles,
831 style_border_radius,
832 );
833 }
834
835 pub fn push_inline_backgrounds_and_border(
842 &mut self,
843 bounds: LogicalRect,
844 background_color: Option<ColorU>,
845 background_contents: &[azul_css::props::style::StyleBackgroundContent],
846 border: Option<&crate::text3::cache::InlineBorderInfo>,
847 ) {
848 use azul_css::props::style::StyleBackgroundContent;
849
850 if let Some(bg_color) = background_color {
852 self.push_rect(bounds, bg_color, BorderRadius::default());
853 }
854
855 for bg in background_contents {
857 match bg {
858 StyleBackgroundContent::Color(color) => {
859 self.push_rect(bounds, *color, BorderRadius::default());
860 }
861 StyleBackgroundContent::LinearGradient(gradient) => {
862 self.push_linear_gradient(bounds, gradient.clone(), BorderRadius::default());
863 }
864 StyleBackgroundContent::RadialGradient(gradient) => {
865 self.push_radial_gradient(bounds, gradient.clone(), BorderRadius::default());
866 }
867 StyleBackgroundContent::ConicGradient(gradient) => {
868 self.push_conic_gradient(bounds, gradient.clone(), BorderRadius::default());
869 }
870 StyleBackgroundContent::Image(_image_id) => {
871 }
873 }
874 }
875
876 if let Some(border) = border {
878 if border.top > 0.0 || border.right > 0.0 || border.bottom > 0.0 || border.left > 0.0 {
879 let border_widths = StyleBorderWidths {
880 top: Some(CssPropertyValue::Exact(LayoutBorderTopWidth {
881 inner: PixelValue::px(border.top),
882 })),
883 right: Some(CssPropertyValue::Exact(LayoutBorderRightWidth {
884 inner: PixelValue::px(border.right),
885 })),
886 bottom: Some(CssPropertyValue::Exact(LayoutBorderBottomWidth {
887 inner: PixelValue::px(border.bottom),
888 })),
889 left: Some(CssPropertyValue::Exact(LayoutBorderLeftWidth {
890 inner: PixelValue::px(border.left),
891 })),
892 };
893 let border_colors = StyleBorderColors {
894 top: Some(CssPropertyValue::Exact(StyleBorderTopColor {
895 inner: border.top_color,
896 })),
897 right: Some(CssPropertyValue::Exact(StyleBorderRightColor {
898 inner: border.right_color,
899 })),
900 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomColor {
901 inner: border.bottom_color,
902 })),
903 left: Some(CssPropertyValue::Exact(StyleBorderLeftColor {
904 inner: border.left_color,
905 })),
906 };
907 let border_styles = StyleBorderStyles {
908 top: Some(CssPropertyValue::Exact(StyleBorderTopStyle {
909 inner: BorderStyle::Solid,
910 })),
911 right: Some(CssPropertyValue::Exact(StyleBorderRightStyle {
912 inner: BorderStyle::Solid,
913 })),
914 bottom: Some(CssPropertyValue::Exact(StyleBorderBottomStyle {
915 inner: BorderStyle::Solid,
916 })),
917 left: Some(CssPropertyValue::Exact(StyleBorderLeftStyle {
918 inner: BorderStyle::Solid,
919 })),
920 };
921 let radius_px = PixelValue::px(border.radius.unwrap_or(0.0));
922 let border_radius = StyleBorderRadius {
923 top_left: radius_px,
924 top_right: radius_px,
925 bottom_left: radius_px,
926 bottom_right: radius_px,
927 };
928
929 self.push_border(
930 bounds,
931 border_widths,
932 border_colors,
933 border_styles,
934 border_radius,
935 );
936 }
937 }
938 }
939
940 pub fn push_linear_gradient(
942 &mut self,
943 bounds: LogicalRect,
944 gradient: LinearGradient,
945 border_radius: BorderRadius,
946 ) {
947 self.push_item(DisplayListItem::LinearGradient {
948 bounds,
949 gradient,
950 border_radius,
951 });
952 }
953
954 pub fn push_radial_gradient(
956 &mut self,
957 bounds: LogicalRect,
958 gradient: RadialGradient,
959 border_radius: BorderRadius,
960 ) {
961 self.push_item(DisplayListItem::RadialGradient {
962 bounds,
963 gradient,
964 border_radius,
965 });
966 }
967
968 pub fn push_conic_gradient(
970 &mut self,
971 bounds: LogicalRect,
972 gradient: ConicGradient,
973 border_radius: BorderRadius,
974 ) {
975 self.push_item(DisplayListItem::ConicGradient {
976 bounds,
977 gradient,
978 border_radius,
979 });
980 }
981
982 pub fn push_selection_rect(
983 &mut self,
984 bounds: LogicalRect,
985 color: ColorU,
986 border_radius: BorderRadius,
987 ) {
988 if color.a > 0 {
989 self.push_item(DisplayListItem::SelectionRect {
990 bounds,
991 color,
992 border_radius,
993 });
994 }
995 }
996
997 pub fn push_cursor_rect(&mut self, bounds: LogicalRect, color: ColorU) {
998 if color.a > 0 {
999 self.push_item(DisplayListItem::CursorRect { bounds, color });
1000 }
1001 }
1002 pub fn push_clip(&mut self, bounds: LogicalRect, border_radius: BorderRadius) {
1003 self.push_item(DisplayListItem::PushClip {
1004 bounds,
1005 border_radius,
1006 });
1007 }
1008 pub fn pop_clip(&mut self) {
1009 self.push_item(DisplayListItem::PopClip);
1010 }
1011 pub fn push_scroll_frame(
1012 &mut self,
1013 clip_bounds: LogicalRect,
1014 content_size: LogicalSize,
1015 scroll_id: LocalScrollId,
1016 ) {
1017 self.push_item(DisplayListItem::PushScrollFrame {
1018 clip_bounds,
1019 content_size,
1020 scroll_id,
1021 });
1022 }
1023 pub fn pop_scroll_frame(&mut self) {
1024 self.push_item(DisplayListItem::PopScrollFrame);
1025 }
1026 pub fn push_border(
1027 &mut self,
1028 bounds: LogicalRect,
1029 widths: StyleBorderWidths,
1030 colors: StyleBorderColors,
1031 styles: StyleBorderStyles,
1032 border_radius: StyleBorderRadius,
1033 ) {
1034 let has_visible_border = {
1036 let has_width = widths.top.is_some()
1037 || widths.right.is_some()
1038 || widths.bottom.is_some()
1039 || widths.left.is_some();
1040 let has_style = styles.top.is_some()
1041 || styles.right.is_some()
1042 || styles.bottom.is_some()
1043 || styles.left.is_some();
1044 has_width && has_style
1045 };
1046
1047 if has_visible_border {
1048 self.push_item(DisplayListItem::Border {
1049 bounds,
1050 widths,
1051 colors,
1052 styles,
1053 border_radius,
1054 });
1055 }
1056 }
1057
1058 pub fn push_stacking_context(&mut self, z_index: i32, bounds: LogicalRect) {
1059 self.push_item(DisplayListItem::PushStackingContext { z_index, bounds });
1060 }
1061
1062 pub fn pop_stacking_context(&mut self) {
1063 self.push_item(DisplayListItem::PopStackingContext);
1064 }
1065
1066 pub fn push_text_run(
1067 &mut self,
1068 glyphs: Vec<GlyphInstance>,
1069 font_hash: FontHash, font_size_px: f32,
1071 color: ColorU,
1072 clip_rect: LogicalRect,
1073 ) {
1074 self.debug_log(format!(
1075 "[push_text_run] {} glyphs, font_size={}px, color=({},{},{},{}), clip={:?}",
1076 glyphs.len(),
1077 font_size_px,
1078 color.r,
1079 color.g,
1080 color.b,
1081 color.a,
1082 clip_rect
1083 ));
1084
1085 if !glyphs.is_empty() && color.a > 0 {
1086 self.push_item(DisplayListItem::Text {
1087 glyphs,
1088 font_hash,
1089 font_size_px,
1090 color,
1091 clip_rect,
1092 });
1093 } else {
1094 self.debug_log(format!(
1095 "[push_text_run] SKIPPED: glyphs.is_empty()={}, color.a={}",
1096 glyphs.is_empty(),
1097 color.a
1098 ));
1099 }
1100 }
1101
1102 pub fn push_text_layout(
1103 &mut self,
1104 layout: Arc<dyn std::any::Any + Send + Sync>,
1105 bounds: LogicalRect,
1106 font_hash: FontHash,
1107 font_size_px: f32,
1108 color: ColorU,
1109 ) {
1110 if color.a > 0 {
1111 self.push_item(DisplayListItem::TextLayout {
1112 layout,
1113 bounds,
1114 font_hash,
1115 font_size_px,
1116 color,
1117 });
1118 }
1119 }
1120
1121 pub fn push_underline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1122 if color.a > 0 && thickness > 0.0 {
1123 self.push_item(DisplayListItem::Underline {
1124 bounds,
1125 color,
1126 thickness,
1127 });
1128 }
1129 }
1130
1131 pub fn push_strikethrough(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1132 if color.a > 0 && thickness > 0.0 {
1133 self.push_item(DisplayListItem::Strikethrough {
1134 bounds,
1135 color,
1136 thickness,
1137 });
1138 }
1139 }
1140
1141 pub fn push_overline(&mut self, bounds: LogicalRect, color: ColorU, thickness: f32) {
1142 if color.a > 0 && thickness > 0.0 {
1143 self.push_item(DisplayListItem::Overline {
1144 bounds,
1145 color,
1146 thickness,
1147 });
1148 }
1149 }
1150
1151 pub fn push_image(&mut self, bounds: LogicalRect, image: ImageRef) {
1152 self.push_item(DisplayListItem::Image { bounds, image });
1153 }
1154}
1155
1156pub fn generate_display_list<T: ParsedFontTrait + Sync + 'static>(
1158 ctx: &mut LayoutContext<T>,
1159 tree: &LayoutTree,
1160 calculated_positions: &BTreeMap<usize, LogicalPosition>,
1161 scroll_offsets: &BTreeMap<NodeId, ScrollPosition>,
1162 scroll_ids: &BTreeMap<usize, u64>,
1163 gpu_value_cache: Option<&GpuValueCache>,
1164 renderer_resources: &RendererResources,
1165 id_namespace: IdNamespace,
1166 dom_id: DomId,
1167) -> Result<DisplayList> {
1168 debug_info!(
1169 ctx,
1170 "[DisplayList] generate_display_list: tree has {} nodes, {} positions calculated",
1171 tree.nodes.len(),
1172 calculated_positions.len()
1173 );
1174
1175 debug_info!(ctx, "Starting display list generation");
1176 debug_info!(
1177 ctx,
1178 "Collecting stacking contexts from root node {}",
1179 tree.root
1180 );
1181
1182 let positioned_tree = PositionedTree {
1183 tree,
1184 calculated_positions,
1185 };
1186 let mut generator = DisplayListGenerator::new(
1187 ctx,
1188 scroll_offsets,
1189 &positioned_tree,
1190 scroll_ids,
1191 gpu_value_cache,
1192 renderer_resources,
1193 id_namespace,
1194 dom_id,
1195 );
1196
1197 let debug_enabled = generator.ctx.debug_messages.is_some();
1199 let mut builder = DisplayListBuilder::with_debug(debug_enabled);
1200
1201 let stacking_context_tree = generator.collect_stacking_contexts(tree.root)?;
1203
1204 debug_info!(
1206 generator.ctx,
1207 "Generating display items from stacking context tree"
1208 );
1209 generator.generate_for_stacking_context(&mut builder, &stacking_context_tree)?;
1210
1211 let display_list = builder.build_with_debug(generator.ctx.debug_messages);
1213 debug_info!(
1214 generator.ctx,
1215 "[DisplayList] Generated {} display items",
1216 display_list.items.len()
1217 );
1218 Ok(display_list)
1219}
1220
1221struct DisplayListGenerator<'a, 'b, T: ParsedFontTrait> {
1223 ctx: &'a mut LayoutContext<'b, T>,
1224 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1225 positioned_tree: &'a PositionedTree<'a>,
1226 scroll_ids: &'a BTreeMap<usize, u64>,
1227 gpu_value_cache: Option<&'a GpuValueCache>,
1228 renderer_resources: &'a RendererResources,
1229 id_namespace: IdNamespace,
1230 dom_id: DomId,
1231}
1232
1233#[derive(Debug)]
1235struct StackingContext {
1236 node_index: usize,
1237 z_index: i32,
1238 child_contexts: Vec<StackingContext>,
1239 in_flow_children: Vec<usize>,
1241}
1242
1243impl<'a, 'b, T> DisplayListGenerator<'a, 'b, T>
1244where
1245 T: ParsedFontTrait + Sync + 'static,
1246{
1247 pub fn new(
1248 ctx: &'a mut LayoutContext<'b, T>,
1249 scroll_offsets: &'a BTreeMap<NodeId, ScrollPosition>,
1250 positioned_tree: &'a PositionedTree<'a>,
1251 scroll_ids: &'a BTreeMap<usize, u64>,
1252 gpu_value_cache: Option<&'a GpuValueCache>,
1253 renderer_resources: &'a RendererResources,
1254 id_namespace: IdNamespace,
1255 dom_id: DomId,
1256 ) -> Self {
1257 Self {
1258 ctx,
1259 scroll_offsets,
1260 positioned_tree,
1261 scroll_ids,
1262 gpu_value_cache,
1263 renderer_resources,
1264 id_namespace,
1265 dom_id,
1266 }
1267 }
1268
1269 fn get_styled_node_state(&self, dom_id: NodeId) -> azul_core::styled_dom::StyledNodeState {
1271 self.ctx
1272 .styled_dom
1273 .styled_nodes
1274 .as_container()
1275 .get(dom_id)
1276 .map(|n| n.styled_node_state.clone())
1277 .unwrap_or_default()
1278 }
1279
1280 fn get_cursor_type_for_text_node(&self, node_id: NodeId) -> CursorType {
1283 use azul_css::props::style::effects::StyleCursor;
1284
1285 let styled_node_state = self.get_styled_node_state(node_id);
1286 let node_data_container = self.ctx.styled_dom.node_data.as_container();
1287 let node_data = node_data_container.get(node_id);
1288
1289 if let Some(node_data) = node_data {
1291 if let Some(cursor_value) = self.ctx.styled_dom.get_css_property_cache().get_cursor(
1292 node_data,
1293 &node_id,
1294 &styled_node_state,
1295 ) {
1296 if let CssPropertyValue::Exact(cursor) = cursor_value {
1297 return match cursor {
1298 StyleCursor::Default => CursorType::Default,
1299 StyleCursor::Pointer => CursorType::Pointer,
1300 StyleCursor::Text => CursorType::Text,
1301 StyleCursor::Crosshair => CursorType::Crosshair,
1302 StyleCursor::Move => CursorType::Move,
1303 StyleCursor::Help => CursorType::Help,
1304 StyleCursor::Wait => CursorType::Wait,
1305 StyleCursor::Progress => CursorType::Progress,
1306 StyleCursor::NsResize => CursorType::NsResize,
1307 StyleCursor::EwResize => CursorType::EwResize,
1308 StyleCursor::NeswResize => CursorType::NeswResize,
1309 StyleCursor::NwseResize => CursorType::NwseResize,
1310 StyleCursor::NResize => CursorType::NResize,
1311 StyleCursor::SResize => CursorType::SResize,
1312 StyleCursor::EResize => CursorType::EResize,
1313 StyleCursor::WResize => CursorType::WResize,
1314 StyleCursor::Grab => CursorType::Grab,
1315 StyleCursor::Grabbing => CursorType::Grabbing,
1316 StyleCursor::RowResize => CursorType::RowResize,
1317 StyleCursor::ColResize => CursorType::ColResize,
1318 StyleCursor::SeResize | StyleCursor::NeswResize => CursorType::NeswResize,
1320 StyleCursor::ZoomIn | StyleCursor::ZoomOut => CursorType::Default,
1321 StyleCursor::Copy | StyleCursor::Alias => CursorType::Default,
1322 StyleCursor::Cell => CursorType::Crosshair,
1323 StyleCursor::AllScroll => CursorType::Move,
1324 StyleCursor::ContextMenu => CursorType::Default,
1325 StyleCursor::VerticalText => CursorType::Text,
1326 StyleCursor::Unset => CursorType::Text, };
1328 }
1329 }
1330 }
1331
1332 CursorType::Text
1334 }
1335
1336 fn paint_selections(
1339 &self,
1340 builder: &mut DisplayListBuilder,
1341 node_index: usize,
1342 ) -> Result<()> {
1343 let node = self
1344 .positioned_tree
1345 .tree
1346 .get(node_index)
1347 .ok_or(LayoutError::InvalidTree)?;
1348 let Some(dom_id) = node.dom_node_id else {
1349 return Ok(());
1350 };
1351
1352 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
1356 return Ok(());
1357 };
1358
1359 let node_pos = self
1361 .positioned_tree
1362 .calculated_positions
1363 .get(&node_index)
1364 .copied()
1365 .unwrap_or_default();
1366
1367 let padding = &node.box_props.padding;
1369 let border = &node.box_props.border;
1370 let content_box_offset_x = node_pos.x + padding.left + border.left;
1371 let content_box_offset_y = node_pos.y + padding.top + border.top;
1372
1373 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1375 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
1376
1377 if !is_selectable {
1378 return Ok(());
1379 }
1380
1381 if let Some(text_selection) = self.ctx.text_selections.get(&self.ctx.styled_dom.dom_id) {
1383 if let Some(range) = text_selection.affected_nodes.get(&dom_id) {
1384 let is_collapsed = text_selection.is_collapsed();
1385
1386 if !is_collapsed {
1388 let rects = layout.get_selection_rects(range);
1389 let style = get_selection_style(self.ctx.styled_dom, Some(dom_id));
1390
1391 let border_radius = BorderRadius {
1392 top_left: style.radius,
1393 top_right: style.radius,
1394 bottom_left: style.radius,
1395 bottom_right: style.radius,
1396 };
1397
1398 for mut rect in rects {
1399 rect.origin.x += content_box_offset_x;
1400 rect.origin.y += content_box_offset_y;
1401 builder.push_selection_rect(rect, style.bg_color, border_radius);
1402 }
1403 }
1404
1405 return Ok(());
1406 }
1407 }
1408
1409 let Some(selection_state) = self.ctx.selections.get(&self.ctx.styled_dom.dom_id) else {
1411 return Ok(());
1412 };
1413
1414 if selection_state.node_id.node.into_crate_internal() != Some(dom_id) {
1415 return Ok(());
1416 }
1417
1418 for selection in selection_state.selections.as_slice() {
1419 if let Selection::Range(range) = &selection {
1420 let rects = layout.get_selection_rects(range);
1421 let style = get_selection_style(self.ctx.styled_dom, Some(dom_id));
1422
1423 let border_radius = BorderRadius {
1424 top_left: style.radius,
1425 top_right: style.radius,
1426 bottom_left: style.radius,
1427 bottom_right: style.radius,
1428 };
1429
1430 for mut rect in rects {
1431 rect.origin.x += content_box_offset_x;
1432 rect.origin.y += content_box_offset_y;
1433 builder.push_selection_rect(rect, style.bg_color, border_radius);
1434 }
1435 }
1436 }
1437
1438 Ok(())
1439 }
1440
1441 fn paint_cursor(
1444 &self,
1445 builder: &mut DisplayListBuilder,
1446 node_index: usize,
1447 ) -> Result<()> {
1448 if !self.ctx.cursor_is_visible {
1450 return Ok(());
1451 }
1452
1453 let Some((cursor_dom_id, cursor_node_id, cursor)) = &self.ctx.cursor_location else {
1455 return Ok(());
1456 };
1457
1458 let node = self
1459 .positioned_tree
1460 .tree
1461 .get(node_index)
1462 .ok_or(LayoutError::InvalidTree)?;
1463 let Some(dom_id) = node.dom_node_id else {
1464 return Ok(());
1465 };
1466
1467 if dom_id != *cursor_node_id {
1469 return Ok(());
1470 }
1471
1472 if self.ctx.styled_dom.dom_id != *cursor_dom_id {
1474 return Ok(());
1475 }
1476
1477 let Some(layout) = self.positioned_tree.tree.get_inline_layout_for_node(node_index) else {
1481 return Ok(());
1482 };
1483
1484 let is_contenteditable = super::getters::is_node_contenteditable_inherited(self.ctx.styled_dom, dom_id);
1487 if !is_contenteditable {
1488 return Ok(());
1489 }
1490
1491 let node_state = &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
1493 let is_selectable = super::getters::is_text_selectable(self.ctx.styled_dom, dom_id, node_state);
1494 if !is_selectable {
1495 return Ok(());
1496 }
1497
1498 let Some(mut rect) = layout.get_cursor_rect(cursor) else {
1500 return Ok(());
1501 };
1502
1503 let node_pos = self
1505 .positioned_tree
1506 .calculated_positions
1507 .get(&node_index)
1508 .copied()
1509 .unwrap_or_default();
1510
1511 let padding = &node.box_props.padding;
1513 let border = &node.box_props.border;
1514 let content_box_offset_x = node_pos.x + padding.left + border.left;
1515 let content_box_offset_y = node_pos.y + padding.top + border.top;
1516
1517 rect.origin.x += content_box_offset_x;
1518 rect.origin.y += content_box_offset_y;
1519
1520 let style = get_caret_style(self.ctx.styled_dom, Some(dom_id));
1521
1522 rect.size.width = style.width;
1524
1525 builder.push_cursor_rect(rect, style.color);
1526
1527 Ok(())
1528 }
1529
1530 fn paint_selection_and_cursor(
1533 &self,
1534 builder: &mut DisplayListBuilder,
1535 node_index: usize,
1536 ) -> Result<()> {
1537 self.paint_selections(builder, node_index)?;
1538 self.paint_cursor(builder, node_index)?;
1539 Ok(())
1540 }
1541
1542 fn collect_stacking_contexts(&mut self, node_index: usize) -> Result<StackingContext> {
1544 let node = self
1545 .positioned_tree
1546 .tree
1547 .get(node_index)
1548 .ok_or(LayoutError::InvalidTree)?;
1549 let z_index = get_z_index(self.ctx.styled_dom, node.dom_node_id);
1550
1551 if let Some(dom_id) = node.dom_node_id {
1552 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
1553 debug_info!(
1554 self.ctx,
1555 "Collecting stacking context for node {} ({:?}), z-index={}",
1556 node_index,
1557 node_type.get_node_type(),
1558 z_index
1559 );
1560 }
1561
1562 let mut child_contexts = Vec::new();
1563 let mut in_flow_children = Vec::new();
1564
1565 for &child_index in &node.children {
1566 if self.establishes_stacking_context(child_index) {
1567 child_contexts.push(self.collect_stacking_contexts(child_index)?);
1568 } else {
1569 in_flow_children.push(child_index);
1570 }
1571 }
1572
1573 Ok(StackingContext {
1574 node_index,
1575 z_index,
1576 child_contexts,
1577 in_flow_children,
1578 })
1579 }
1580
1581 fn generate_for_stacking_context(
1584 &mut self,
1585 builder: &mut DisplayListBuilder,
1586 context: &StackingContext,
1587 ) -> Result<()> {
1588 let node = self
1590 .positioned_tree
1591 .tree
1592 .get(context.node_index)
1593 .ok_or(LayoutError::InvalidTree)?;
1594
1595 if let Some(dom_id) = node.dom_node_id {
1596 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
1597 debug_info!(
1598 self.ctx,
1599 "Painting stacking context for node {} ({:?}), z-index={}, {} child contexts, {} \
1600 in-flow children",
1601 context.node_index,
1602 node_type.get_node_type(),
1603 context.z_index,
1604 context.child_contexts.len(),
1605 context.in_flow_children.len()
1606 );
1607 }
1608
1609 let node_pos = self
1612 .positioned_tree
1613 .calculated_positions
1614 .get(&context.node_index)
1615 .copied()
1616 .unwrap_or_default();
1617 let node_size = node.used_size.unwrap_or(LogicalSize {
1618 width: 0.0,
1619 height: 0.0,
1620 });
1621 let node_bounds = LogicalRect {
1622 origin: node_pos,
1623 size: node_size,
1624 };
1625 builder.push_stacking_context(context.z_index, node_bounds);
1626
1627 self.paint_node_background_and_border(builder, context.node_index)?;
1631
1632 if let Some(dom_id) = node.dom_node_id {
1637 let styled_node_state = self.get_styled_node_state(dom_id);
1638 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
1639 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
1640 if overflow_x.is_scroll() || overflow_y.is_scroll() {
1641 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
1642 builder.push_hit_test_area(node_bounds, tag_id);
1643 }
1644 }
1645 }
1646
1647 let did_push_clip_or_scroll = self.push_node_clips(builder, context.node_index, node)?;
1649
1650 let mut negative_z_children: Vec<_> = context
1652 .child_contexts
1653 .iter()
1654 .filter(|c| c.z_index < 0)
1655 .collect();
1656 negative_z_children.sort_by_key(|c| c.z_index);
1657 for child in negative_z_children {
1658 self.generate_for_stacking_context(builder, child)?;
1659 }
1660
1661 self.paint_in_flow_descendants(builder, context.node_index, &context.in_flow_children)?;
1663
1664 for child in context.child_contexts.iter().filter(|c| c.z_index == 0) {
1666 self.generate_for_stacking_context(builder, child)?;
1667 }
1668
1669 let mut positive_z_children: Vec<_> = context
1671 .child_contexts
1672 .iter()
1673 .filter(|c| c.z_index > 0)
1674 .collect();
1675
1676 positive_z_children.sort_by_key(|c| c.z_index);
1677
1678 for child in positive_z_children {
1679 self.generate_for_stacking_context(builder, child)?;
1680 }
1681
1682 builder.pop_stacking_context();
1684
1685 if did_push_clip_or_scroll {
1687 self.pop_node_clips(builder, node)?;
1688 }
1689
1690 self.paint_scrollbars(builder, context.node_index)?;
1693
1694 Ok(())
1695 }
1696
1697 fn paint_in_flow_descendants(
1699 &mut self,
1700 builder: &mut DisplayListBuilder,
1701 node_index: usize,
1702 children_indices: &[usize],
1703 ) -> Result<()> {
1704 self.paint_selection_and_cursor(builder, node_index)?;
1710
1711 self.paint_node_content(builder, node_index)?;
1713
1714 let mut non_float_children = Vec::new();
1722 let mut float_children = Vec::new();
1723
1724 for &child_index in children_indices {
1725 let child_node = self
1726 .positioned_tree
1727 .tree
1728 .get(child_index)
1729 .ok_or(LayoutError::InvalidTree)?;
1730
1731 let is_float = if let Some(dom_id) = child_node.dom_node_id {
1733 use crate::solver3::getters::get_float;
1734 let styled_node_state = self.get_styled_node_state(dom_id);
1735 let float_value = get_float(self.ctx.styled_dom, dom_id, &styled_node_state);
1736 !matches!(
1737 float_value.unwrap_or_default(),
1738 azul_css::props::layout::LayoutFloat::None
1739 )
1740 } else {
1741 false
1742 };
1743
1744 if is_float {
1745 float_children.push(child_index);
1746 } else {
1747 non_float_children.push(child_index);
1748 }
1749 }
1750
1751 for child_index in non_float_children {
1753 let child_node = self
1754 .positioned_tree
1755 .tree
1756 .get(child_index)
1757 .ok_or(LayoutError::InvalidTree)?;
1758
1759 self.paint_node_background_and_border(builder, child_index)?;
1763
1764 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
1766
1767 self.paint_in_flow_descendants(builder, child_index, &child_node.children)?;
1769
1770 if did_push_clip {
1772 self.pop_node_clips(builder, child_node)?;
1773 }
1774
1775 self.paint_scrollbars(builder, child_index)?;
1777 }
1778
1779 for child_index in float_children {
1781 let child_node = self
1782 .positioned_tree
1783 .tree
1784 .get(child_index)
1785 .ok_or(LayoutError::InvalidTree)?;
1786
1787 self.paint_node_background_and_border(builder, child_index)?;
1789 let did_push_clip = self.push_node_clips(builder, child_index, child_node)?;
1790 self.paint_in_flow_descendants(builder, child_index, &child_node.children)?;
1791
1792 if did_push_clip {
1793 self.pop_node_clips(builder, child_node)?;
1794 }
1795
1796 self.paint_scrollbars(builder, child_index)?;
1798 }
1799
1800 Ok(())
1801 }
1802
1803 fn push_node_clips(
1806 &self,
1807 builder: &mut DisplayListBuilder,
1808 node_index: usize,
1809 node: &LayoutNode,
1810 ) -> Result<bool> {
1811 let Some(dom_id) = node.dom_node_id else {
1812 return Ok(false);
1813 };
1814
1815 let styled_node_state = self.get_styled_node_state(dom_id);
1816
1817 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
1818 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
1819
1820 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
1821 let element_size = PhysicalSizeImport {
1822 width: paint_rect.size.width,
1823 height: paint_rect.size.height,
1824 };
1825 let border_radius = get_border_radius(
1826 self.ctx.styled_dom,
1827 dom_id,
1828 &styled_node_state,
1829 element_size,
1830 self.ctx.viewport_size,
1831 );
1832
1833 let needs_clip = overflow_x.is_clipped() || overflow_y.is_clipped();
1834
1835 if !needs_clip {
1836 return Ok(false);
1837 }
1838
1839 let paint_rect = self.get_paint_rect(node_index).unwrap_or_default();
1840
1841 let border = &node.box_props.border;
1842
1843 let scrollbar_info = get_scrollbar_info_from_layout(node);
1845
1846 let clip_rect = LogicalRect {
1849 origin: LogicalPosition {
1850 x: paint_rect.origin.x + border.left,
1851 y: paint_rect.origin.y + border.top,
1852 },
1853 size: LogicalSize {
1854 width: (paint_rect.size.width
1856 - border.left
1857 - border.right
1858 - scrollbar_info.scrollbar_width)
1859 .max(0.0),
1860 height: (paint_rect.size.height
1861 - border.top
1862 - border.bottom
1863 - scrollbar_info.scrollbar_height)
1864 .max(0.0),
1865 },
1866 };
1867
1868 if overflow_x.is_scroll() || overflow_y.is_scroll() {
1869 builder.push_clip(clip_rect, border_radius);
1873 let scroll_id = self.scroll_ids.get(&node_index).copied().unwrap_or(0);
1874 let content_size = get_scroll_content_size(node);
1875 builder.push_scroll_frame(clip_rect, content_size, scroll_id);
1876 } else {
1877 builder.push_clip(clip_rect, border_radius);
1879 }
1880
1881 Ok(true)
1882 }
1883
1884 fn pop_node_clips(&self, builder: &mut DisplayListBuilder, node: &LayoutNode) -> Result<()> {
1886 let Some(dom_id) = node.dom_node_id else {
1887 return Ok(());
1888 };
1889
1890 let styled_node_state = self.get_styled_node_state(dom_id);
1891 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
1892 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
1893
1894 let paint_rect = self
1895 .get_paint_rect(
1896 self.positioned_tree
1897 .tree
1898 .nodes
1899 .iter()
1900 .position(|n| n.dom_node_id == Some(dom_id))
1901 .unwrap_or(0),
1902 )
1903 .unwrap_or_default();
1904
1905 let element_size = PhysicalSizeImport {
1906 width: paint_rect.size.width,
1907 height: paint_rect.size.height,
1908 };
1909 let border_radius = get_border_radius(
1910 self.ctx.styled_dom,
1911 dom_id,
1912 &styled_node_state,
1913 element_size,
1914 self.ctx.viewport_size,
1915 );
1916
1917 let needs_clip =
1918 overflow_x.is_clipped() || overflow_y.is_clipped() || !border_radius.is_zero();
1919
1920 if needs_clip {
1921 if overflow_x.is_scroll() || overflow_y.is_scroll() {
1922 builder.pop_scroll_frame();
1924 builder.pop_clip();
1925 } else {
1926 builder.pop_clip();
1928 }
1929 }
1930 Ok(())
1931 }
1932
1933 fn get_paint_rect(&self, node_index: usize) -> Option<LogicalRect> {
1951 let node = self.positioned_tree.tree.get(node_index)?;
1952 let pos = self
1953 .positioned_tree
1954 .calculated_positions
1955 .get(&node_index)
1956 .copied()
1957 .unwrap_or_default();
1958 let size = node.used_size.unwrap_or_default();
1959
1960 Some(LogicalRect::new(pos, size))
1965 }
1966
1967 fn paint_node_background_and_border(
1969 &mut self,
1970 builder: &mut DisplayListBuilder,
1971 node_index: usize,
1972 ) -> Result<()> {
1973 let Some(paint_rect) = self.get_paint_rect(node_index) else {
1974 return Ok(());
1975 };
1976 let node = self
1977 .positioned_tree
1978 .tree
1979 .get(node_index)
1980 .ok_or(LayoutError::InvalidTree)?;
1981
1982 builder.set_current_node(node.dom_node_id);
1984
1985 if let Some(dom_id) = node.dom_node_id {
1988 let break_before = get_break_before(self.ctx.styled_dom, Some(dom_id));
1989 let break_after = get_break_after(self.ctx.styled_dom, Some(dom_id));
1990
1991 if is_forced_page_break(break_before) {
1993 let y_position = paint_rect.origin.y;
1994 builder.add_forced_page_break(y_position);
1995 debug_info!(
1996 self.ctx,
1997 "Registered forced page break BEFORE node {} at y={}",
1998 node_index,
1999 y_position
2000 );
2001 }
2002
2003 if is_forced_page_break(break_after) {
2005 let y_position = paint_rect.origin.y + paint_rect.size.height;
2006 builder.add_forced_page_break(y_position);
2007 debug_info!(
2008 self.ctx,
2009 "Registered forced page break AFTER node {} at y={}",
2010 node_index,
2011 y_position
2012 );
2013 }
2014 }
2015
2016 if let Some(dom_id) = node.dom_node_id {
2020 let styled_node_state = self.get_styled_node_state(dom_id);
2021 let display = self
2022 .ctx
2023 .styled_dom
2024 .css_property_cache
2025 .ptr
2026 .get_display(
2027 &self.ctx.styled_dom.node_data.as_container()[dom_id],
2028 &dom_id,
2029 &styled_node_state,
2030 )
2031 .and_then(|v| v.get_property().cloned())
2032 .unwrap_or(LayoutDisplay::Inline);
2033
2034 if display == LayoutDisplay::InlineBlock || display == LayoutDisplay::Inline {
2035 return Ok(());
2038 }
2039 }
2040
2041 if matches!(node.formatting_context, FormattingContext::Table) {
2044 debug_info!(
2045 self.ctx,
2046 "Painting table backgrounds/borders for node {} at {:?}",
2047 node_index,
2048 paint_rect
2049 );
2050 return self.paint_table_items(builder, node_index);
2052 }
2053
2054 let border_radius = if let Some(dom_id) = node.dom_node_id {
2055 let styled_node_state = self.get_styled_node_state(dom_id);
2056 let background_contents =
2057 get_background_contents(self.ctx.styled_dom, dom_id, &styled_node_state);
2058 let border_info = get_border_info(self.ctx.styled_dom, dom_id, &styled_node_state);
2059
2060 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2061 debug_info!(
2062 self.ctx,
2063 "Painting background/border for node {} ({:?}) at {:?}, backgrounds={:?}",
2064 node_index,
2065 node_type.get_node_type(),
2066 paint_rect,
2067 background_contents.len()
2068 );
2069
2070 let element_size = PhysicalSizeImport {
2073 width: paint_rect.size.width,
2074 height: paint_rect.size.height,
2075 };
2076 let simple_border_radius = get_border_radius(
2077 self.ctx.styled_dom,
2078 dom_id,
2079 &styled_node_state,
2080 element_size,
2081 self.ctx.viewport_size,
2082 );
2083 let style_border_radius =
2084 get_style_border_radius(self.ctx.styled_dom, dom_id, &styled_node_state);
2085
2086 builder.push_backgrounds_and_border(
2088 paint_rect,
2089 &background_contents,
2090 &border_info,
2091 simple_border_radius,
2092 style_border_radius,
2093 );
2094
2095 simple_border_radius
2096 } else {
2097 BorderRadius::default()
2098 };
2099
2100 Ok(())
2101 }
2102
2103 fn paint_table_items(
2121 &self,
2122 builder: &mut DisplayListBuilder,
2123 table_index: usize,
2124 ) -> Result<()> {
2125 let table_node = self
2126 .positioned_tree
2127 .tree
2128 .get(table_index)
2129 .ok_or(LayoutError::InvalidTree)?;
2130
2131 let Some(table_paint_rect) = self.get_paint_rect(table_index) else {
2132 return Ok(());
2133 };
2134
2135 if let Some(dom_id) = table_node.dom_node_id {
2137 let styled_node_state = self.get_styled_node_state(dom_id);
2138 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
2139 let element_size = PhysicalSizeImport {
2140 width: table_paint_rect.size.width,
2141 height: table_paint_rect.size.height,
2142 };
2143 let border_radius = get_border_radius(
2144 self.ctx.styled_dom,
2145 dom_id,
2146 &styled_node_state,
2147 element_size,
2148 self.ctx.viewport_size,
2149 );
2150
2151 builder.push_rect(table_paint_rect, bg_color, border_radius);
2152 }
2153
2154 for &child_idx in &table_node.children {
2159 let child_node = self.positioned_tree.tree.get(child_idx);
2160 if let Some(node) = child_node {
2161 if matches!(node.formatting_context, FormattingContext::TableColumnGroup) {
2162 self.paint_element_background(builder, child_idx)?;
2164
2165 for &col_idx in &node.children {
2167 self.paint_element_background(builder, col_idx)?;
2168 }
2169 }
2170 }
2171 }
2172
2173 for &child_idx in &table_node.children {
2177 let child_node = self.positioned_tree.tree.get(child_idx);
2178 if let Some(node) = child_node {
2179 match node.formatting_context {
2180 FormattingContext::TableRowGroup => {
2181 self.paint_element_background(builder, child_idx)?;
2183
2184 for &row_idx in &node.children {
2186 self.paint_table_row_and_cells(builder, row_idx)?;
2187 }
2188 }
2189 FormattingContext::TableRow => {
2190 self.paint_table_row_and_cells(builder, child_idx)?;
2192 }
2193 _ => {}
2194 }
2195 }
2196 }
2197
2198 Ok(())
2203 }
2204
2205 fn paint_table_row_and_cells(
2209 &self,
2210 builder: &mut DisplayListBuilder,
2211 row_idx: usize,
2212 ) -> Result<()> {
2213 self.paint_element_background(builder, row_idx)?;
2215
2216 let row_node = self.positioned_tree.tree.get(row_idx);
2218 if let Some(node) = row_node {
2219 for &cell_idx in &node.children {
2220 self.paint_element_background(builder, cell_idx)?;
2221 }
2222 }
2223
2224 Ok(())
2225 }
2226
2227 fn paint_element_background(
2230 &self,
2231 builder: &mut DisplayListBuilder,
2232 node_index: usize,
2233 ) -> Result<()> {
2234 let Some(paint_rect) = self.get_paint_rect(node_index) else {
2235 return Ok(());
2236 };
2237
2238 let Some(node) = self.positioned_tree.tree.get(node_index) else {
2239 return Ok(());
2240 };
2241 let Some(dom_id) = node.dom_node_id else {
2242 return Ok(());
2243 };
2244
2245 let styled_node_state = self.get_styled_node_state(dom_id);
2246 let bg_color = get_background_color(self.ctx.styled_dom, dom_id, &styled_node_state);
2247
2248 if bg_color.a == 0 {
2250 return Ok(());
2251 }
2252
2253 let element_size = PhysicalSizeImport {
2254 width: paint_rect.size.width,
2255 height: paint_rect.size.height,
2256 };
2257 let border_radius = get_border_radius(
2258 self.ctx.styled_dom,
2259 dom_id,
2260 &styled_node_state,
2261 element_size,
2262 self.ctx.viewport_size,
2263 );
2264
2265 builder.push_rect(paint_rect, bg_color, border_radius);
2266
2267 Ok(())
2268 }
2269
2270 fn paint_node_content(
2272 &mut self,
2273 builder: &mut DisplayListBuilder,
2274 node_index: usize,
2275 ) -> Result<()> {
2276 let node = self
2277 .positioned_tree
2278 .tree
2279 .get(node_index)
2280 .ok_or(LayoutError::InvalidTree)?;
2281
2282 builder.set_current_node(node.dom_node_id);
2284
2285 let Some(mut paint_rect) = self.get_paint_rect(node_index) else {
2286 return Ok(());
2287 };
2288
2289 if paint_rect.size.width == 0.0 || paint_rect.size.height == 0.0 {
2292 if let Some(cached_layout) = &node.inline_layout_result {
2293 let content_bounds = cached_layout.layout.bounds();
2294 paint_rect.size.width = content_bounds.width;
2295 paint_rect.size.height = content_bounds.height;
2296 }
2297 }
2298
2299 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, node.dom_node_id) {
2304 let is_scrollable = if let Some(dom_id) = node.dom_node_id {
2305 let styled_node_state = self.get_styled_node_state(dom_id);
2306 let overflow_x = get_overflow_x(self.ctx.styled_dom, dom_id, &styled_node_state);
2307 let overflow_y = get_overflow_y(self.ctx.styled_dom, dom_id, &styled_node_state);
2308 overflow_x.is_scroll() || overflow_y.is_scroll()
2309 } else {
2310 false
2311 };
2312
2313 if !is_scrollable {
2319 builder.push_hit_test_area(paint_rect, tag_id);
2320 }
2321 }
2322
2323 if let Some(cached_layout) = &node.inline_layout_result {
2325 let inline_layout = &cached_layout.layout;
2326 debug_info!(
2327 self.ctx,
2328 "[paint_node] node {} has inline_layout with {} items",
2329 node_index,
2330 inline_layout.items.len()
2331 );
2332
2333 if let Some(dom_id) = node.dom_node_id {
2334 let node_type = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2335 debug_info!(
2336 self.ctx,
2337 "Painting inline content for node {} ({:?}) at {:?}, {} layout items",
2338 node_index,
2339 node_type.get_node_type(),
2340 paint_rect,
2341 inline_layout.items.len()
2342 );
2343 }
2344
2345 let border_box = BorderBoxRect(paint_rect);
2349 let mut content_box_rect =
2350 border_box.to_content_box(&node.box_props.padding, &node.box_props.border).rect();
2351
2352 let content_size = get_scroll_content_size(node);
2356 if content_size.height > content_box_rect.size.height {
2357 content_box_rect.size.height = content_size.height;
2358 }
2359 if content_size.width > content_box_rect.size.width {
2360 content_box_rect.size.width = content_size.width;
2361 }
2362
2363 self.paint_inline_content(builder, content_box_rect, inline_layout)?;
2364 } else if let Some(dom_id) = node.dom_node_id {
2365 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
2367 if let NodeType::Image(image_ref) = node_data.get_node_type() {
2368 debug_info!(
2369 self.ctx,
2370 "Painting image for node {} at {:?}",
2371 node_index,
2372 paint_rect
2373 );
2374 builder.push_image(paint_rect, image_ref.clone());
2376 }
2377 }
2378
2379 Ok(())
2380 }
2381
2382 fn paint_scrollbars(&self, builder: &mut DisplayListBuilder, node_index: usize) -> Result<()> {
2385 let node = self
2386 .positioned_tree
2387 .tree
2388 .get(node_index)
2389 .ok_or(LayoutError::InvalidTree)?;
2390
2391 let Some(paint_rect) = self.get_paint_rect(node_index) else {
2392 return Ok(());
2393 };
2394
2395 let scrollbar_info = get_scrollbar_info_from_layout(node);
2397
2398 let node_id = node.dom_node_id;
2400
2401 let scrollbar_style = node_id
2403 .map(|nid| {
2404 let node_state =
2405 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
2406 get_scrollbar_style(self.ctx.styled_dom, nid, node_state)
2407 })
2408 .unwrap_or_default();
2409
2410 if matches!(
2412 scrollbar_style.width_mode,
2413 azul_css::props::style::scrollbar::LayoutScrollbarWidth::None
2414 ) {
2415 return Ok(());
2416 }
2417
2418 let border = &node.box_props.border;
2420
2421 let container_border_radius = node_id
2423 .map(|nid| {
2424 let node_state =
2425 &self.ctx.styled_dom.styled_nodes.as_container()[nid].styled_node_state;
2426 let element_size = PhysicalSizeImport {
2427 width: paint_rect.size.width,
2428 height: paint_rect.size.height,
2429 };
2430 let viewport_size =
2431 LogicalSize::new(self.ctx.viewport_size.width, self.ctx.viewport_size.height);
2432 get_border_radius(
2433 self.ctx.styled_dom,
2434 nid,
2435 node_state,
2436 element_size,
2437 viewport_size,
2438 )
2439 })
2440 .unwrap_or_default();
2441
2442 let inner_rect = LogicalRect {
2445 origin: LogicalPosition::new(
2446 paint_rect.origin.x + border.left,
2447 paint_rect.origin.y + border.top,
2448 ),
2449 size: LogicalSize::new(
2450 (paint_rect.size.width - border.left - border.right).max(0.0),
2451 (paint_rect.size.height - border.top - border.bottom).max(0.0),
2452 ),
2453 };
2454
2455 let (scroll_offset_x, scroll_offset_y) = node_id
2459 .and_then(|nid| {
2460 self.scroll_offsets.get(&nid).map(|pos| {
2461 (
2462 pos.children_rect.origin.x - pos.parent_rect.origin.x,
2463 pos.children_rect.origin.y - pos.parent_rect.origin.y,
2464 )
2465 })
2466 })
2467 .unwrap_or((0.0, 0.0));
2468
2469 let content_size = node.get_content_size();
2474
2475 let thumb_radius = scrollbar_style.width_px / 2.0;
2477 let thumb_border_radius = BorderRadius {
2478 top_left: thumb_radius,
2479 top_right: thumb_radius,
2480 bottom_left: thumb_radius,
2481 bottom_right: thumb_radius,
2482 };
2483
2484 if scrollbar_info.needs_vertical {
2485 let opacity_key = node_id.and_then(|nid| {
2487 self.gpu_value_cache.and_then(|cache| {
2488 cache
2489 .scrollbar_v_opacity_keys
2490 .get(&(self.dom_id, nid))
2491 .copied()
2492 })
2493 });
2494
2495 let track_height = if scrollbar_info.needs_horizontal {
2497 inner_rect.size.height - scrollbar_style.width_px
2498 } else {
2499 inner_rect.size.height
2500 };
2501
2502 let track_bounds = LogicalRect {
2503 origin: LogicalPosition::new(
2504 inner_rect.origin.x + inner_rect.size.width - scrollbar_style.width_px,
2505 inner_rect.origin.y,
2506 ),
2507 size: LogicalSize::new(scrollbar_style.width_px, track_height),
2508 };
2509
2510 let viewport_height = inner_rect.size.height;
2512 let thumb_ratio = (viewport_height / content_size.height).min(1.0);
2513 let thumb_height = (track_height * thumb_ratio).max(scrollbar_style.width_px * 2.0);
2514
2515 let max_scroll = (content_size.height - viewport_height).max(0.0);
2516 let scroll_ratio = if max_scroll > 0.0 {
2517 scroll_offset_y.abs() / max_scroll
2518 } else {
2519 0.0
2520 };
2521 let thumb_y = track_bounds.origin.y
2522 + (track_height - thumb_height) * scroll_ratio.clamp(0.0, 1.0);
2523
2524 let thumb_bounds = LogicalRect {
2525 origin: LogicalPosition::new(track_bounds.origin.x, thumb_y),
2526 size: LogicalSize::new(scrollbar_style.width_px, thumb_height),
2527 };
2528
2529 let hit_id = node_id
2531 .map(|nid| azul_core::hit_test::ScrollbarHitId::VerticalThumb(self.dom_id, nid));
2532
2533 let button_size = scrollbar_style.width_px;
2535 let button_decrement_bounds = Some(LogicalRect {
2536 origin: LogicalPosition::new(track_bounds.origin.x, track_bounds.origin.y),
2537 size: LogicalSize::new(button_size, button_size),
2538 });
2539 let button_increment_bounds = Some(LogicalRect {
2540 origin: LogicalPosition::new(
2541 track_bounds.origin.x,
2542 track_bounds.origin.y + track_height - button_size,
2543 ),
2544 size: LogicalSize::new(button_size, button_size),
2545 });
2546 let debug_button_color = ColorU { r: 144, g: 238, b: 144, a: 255 };
2548
2549 builder.push_scrollbar_styled(ScrollbarDrawInfo {
2550 bounds: track_bounds,
2551 orientation: ScrollbarOrientation::Vertical,
2552 track_bounds,
2553 track_color: scrollbar_style.track_color,
2554 thumb_bounds,
2555 thumb_color: scrollbar_style.thumb_color,
2556 thumb_border_radius,
2557 button_decrement_bounds,
2558 button_increment_bounds,
2559 button_color: debug_button_color,
2560 opacity_key,
2561 hit_id,
2562 clip_to_container_border: scrollbar_style.clip_to_container_border,
2563 container_border_radius,
2564 });
2565 }
2566
2567 if scrollbar_info.needs_horizontal {
2568 let opacity_key = node_id.and_then(|nid| {
2570 self.gpu_value_cache.and_then(|cache| {
2571 cache
2572 .scrollbar_h_opacity_keys
2573 .get(&(self.dom_id, nid))
2574 .copied()
2575 })
2576 });
2577
2578 let track_width = if scrollbar_info.needs_vertical {
2580 inner_rect.size.width - scrollbar_style.width_px
2581 } else {
2582 inner_rect.size.width
2583 };
2584
2585 let track_bounds = LogicalRect {
2586 origin: LogicalPosition::new(
2587 inner_rect.origin.x,
2588 inner_rect.origin.y + inner_rect.size.height - scrollbar_style.width_px,
2589 ),
2590 size: LogicalSize::new(track_width, scrollbar_style.width_px),
2591 };
2592
2593 let viewport_width = inner_rect.size.width;
2595 let thumb_ratio = (viewport_width / content_size.width).min(1.0);
2596 let thumb_width = (track_width * thumb_ratio).max(scrollbar_style.width_px * 2.0);
2597
2598 let max_scroll = (content_size.width - viewport_width).max(0.0);
2599 let scroll_ratio = if max_scroll > 0.0 {
2600 scroll_offset_x.abs() / max_scroll
2601 } else {
2602 0.0
2603 };
2604 let thumb_x = track_bounds.origin.x
2605 + (track_width - thumb_width) * scroll_ratio.clamp(0.0, 1.0);
2606
2607 let thumb_bounds = LogicalRect {
2608 origin: LogicalPosition::new(thumb_x, track_bounds.origin.y),
2609 size: LogicalSize::new(thumb_width, scrollbar_style.width_px),
2610 };
2611
2612 let hit_id = node_id
2614 .map(|nid| azul_core::hit_test::ScrollbarHitId::HorizontalThumb(self.dom_id, nid));
2615
2616 let button_size = scrollbar_style.width_px;
2618 let button_decrement_bounds = Some(LogicalRect {
2619 origin: LogicalPosition::new(track_bounds.origin.x, track_bounds.origin.y),
2620 size: LogicalSize::new(button_size, button_size),
2621 });
2622 let button_increment_bounds = Some(LogicalRect {
2623 origin: LogicalPosition::new(
2624 track_bounds.origin.x + track_width - button_size,
2625 track_bounds.origin.y,
2626 ),
2627 size: LogicalSize::new(button_size, button_size),
2628 });
2629 let debug_button_color = ColorU { r: 144, g: 238, b: 144, a: 255 };
2631
2632 builder.push_scrollbar_styled(ScrollbarDrawInfo {
2633 bounds: track_bounds,
2634 orientation: ScrollbarOrientation::Horizontal,
2635 track_bounds,
2636 track_color: scrollbar_style.track_color,
2637 thumb_bounds,
2638 thumb_color: scrollbar_style.thumb_color,
2639 thumb_border_radius,
2640 button_decrement_bounds,
2641 button_increment_bounds,
2642 button_color: debug_button_color,
2643 opacity_key,
2644 hit_id,
2645 clip_to_container_border: scrollbar_style.clip_to_container_border,
2646 container_border_radius,
2647 });
2648 }
2649
2650 Ok(())
2651 }
2652
2653 fn paint_inline_content(
2655 &self,
2656 builder: &mut DisplayListBuilder,
2657 container_rect: LogicalRect,
2658 layout: &UnifiedLayout,
2659 ) -> Result<()> {
2660 builder.push_text_layout(
2669 Arc::new(layout.clone()) as Arc<dyn std::any::Any + Send + Sync>,
2670 container_rect,
2671 FontHash::from_hash(0), 12.0, ColorU {
2674 r: 0,
2675 g: 0,
2676 b: 0,
2677 a: 255,
2678 }, );
2680
2681 let glyph_runs = crate::text3::glyphs::get_glyph_runs_simple(layout);
2682
2683 for glyph_run in glyph_runs.iter() {
2686 if let (Some(first_glyph), Some(last_glyph)) =
2688 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
2689 {
2690 let run_start_x = container_rect.origin.x + first_glyph.point.x;
2692 let run_end_x = container_rect.origin.x + last_glyph.point.x;
2693 let run_width = (run_end_x - run_start_x).max(0.0);
2694
2695 if run_width <= 0.0 {
2697 continue;
2698 }
2699
2700 let baseline_y = container_rect.origin.y + first_glyph.point.y;
2702 let font_size = glyph_run.font_size_px;
2703 let ascent = font_size * 0.8; let run_bounds = LogicalRect::new(
2706 LogicalPosition::new(run_start_x, baseline_y - ascent),
2707 LogicalSize::new(run_width, font_size),
2708 );
2709
2710 builder.push_inline_backgrounds_and_border(
2712 run_bounds,
2713 glyph_run.background_color,
2714 &glyph_run.background_content,
2715 glyph_run.border.as_ref(),
2716 );
2717 }
2718 }
2719
2720 for (idx, glyph_run) in glyph_runs.iter().enumerate() {
2722 let clip_rect = container_rect; let offset_glyphs: Vec<GlyphInstance> = glyph_run
2727 .glyphs
2728 .iter()
2729 .map(|g| {
2730 let mut g = g.clone();
2731 g.point.x += container_rect.origin.x;
2732 g.point.y += container_rect.origin.y;
2733 g
2734 })
2735 .collect();
2736
2737 builder.push_text_run(
2739 offset_glyphs,
2740 FontHash::from_hash(glyph_run.font_hash),
2741 glyph_run.font_size_px,
2742 glyph_run.color,
2743 clip_rect,
2744 );
2745
2746 let needs_underline = glyph_run.text_decoration.underline || glyph_run.is_ime_preview;
2748 let needs_strikethrough = glyph_run.text_decoration.strikethrough;
2749 let needs_overline = glyph_run.text_decoration.overline;
2750
2751 if needs_underline || needs_strikethrough || needs_overline {
2752 if let (Some(first_glyph), Some(last_glyph)) =
2754 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
2755 {
2756 let decoration_start_x = container_rect.origin.x + first_glyph.point.x;
2757 let decoration_end_x = container_rect.origin.x + last_glyph.point.x;
2758 let decoration_width = decoration_end_x - decoration_start_x;
2759
2760 let font_size = glyph_run.font_size_px;
2763 let thickness = (font_size * 0.08).max(1.0); let baseline_y = container_rect.origin.y + first_glyph.point.y;
2767
2768 if needs_underline {
2769 let underline_y = baseline_y + (font_size * 0.12);
2772 let underline_bounds = LogicalRect::new(
2773 LogicalPosition::new(decoration_start_x, underline_y),
2774 LogicalSize::new(decoration_width, thickness),
2775 );
2776 builder.push_underline(underline_bounds, glyph_run.color, thickness);
2777 }
2778
2779 if needs_strikethrough {
2780 let strikethrough_y = baseline_y - (font_size * 0.3);
2782 let strikethrough_bounds = LogicalRect::new(
2783 LogicalPosition::new(decoration_start_x, strikethrough_y),
2784 LogicalSize::new(decoration_width, thickness),
2785 );
2786 builder.push_strikethrough(
2787 strikethrough_bounds,
2788 glyph_run.color,
2789 thickness,
2790 );
2791 }
2792
2793 if needs_overline {
2794 let overline_y = baseline_y - (font_size * 0.85);
2796 let overline_bounds = LogicalRect::new(
2797 LogicalPosition::new(decoration_start_x, overline_y),
2798 LogicalSize::new(decoration_width, thickness),
2799 );
2800 builder.push_overline(overline_bounds, glyph_run.color, thickness);
2801 }
2802 }
2803 }
2804 }
2805
2806 for glyph_run in glyph_runs.iter() {
2809 let Some(source_node_id) = glyph_run.source_node_id else {
2811 continue;
2812 };
2813
2814 if let (Some(first_glyph), Some(last_glyph)) =
2816 (glyph_run.glyphs.first(), glyph_run.glyphs.last())
2817 {
2818 let run_start_x = container_rect.origin.x + first_glyph.point.x;
2819 let run_end_x = container_rect.origin.x + last_glyph.point.x;
2820 let run_width = (run_end_x - run_start_x).max(0.0);
2821
2822 if run_width <= 0.0 {
2824 continue;
2825 }
2826
2827 let baseline_y = container_rect.origin.y + first_glyph.point.y;
2829 let font_size = glyph_run.font_size_px;
2830 let ascent = font_size * 0.8; let run_bounds = LogicalRect::new(
2833 LogicalPosition::new(run_start_x, baseline_y - ascent),
2834 LogicalSize::new(run_width, font_size),
2835 );
2836
2837 let cursor_type = self.get_cursor_type_for_text_node(source_node_id);
2840
2841 let tag_value = ((self.dom_id.inner as u64) << 32) | (source_node_id.index() as u64);
2845 let tag_type = TAG_TYPE_CURSOR | (cursor_type as u16);
2846 let tag_id = (tag_value, tag_type);
2847
2848 builder.push_hit_test_area(run_bounds, tag_id);
2849 }
2850 }
2851
2852 for positioned_item in &layout.items {
2856 self.paint_inline_object(builder, container_rect.origin, positioned_item)?;
2857 }
2858 Ok(())
2859 }
2860
2861 fn paint_inline_object(
2863 &self,
2864 builder: &mut DisplayListBuilder,
2865 base_pos: LogicalPosition,
2866 positioned_item: &PositionedItem,
2867 ) -> Result<()> {
2868 let ShapedItem::Object {
2869 content, bounds, ..
2870 } = &positioned_item.item
2871 else {
2872 return Ok(());
2874 };
2875
2876 let object_bounds = LogicalRect::new(
2879 LogicalPosition::new(
2880 base_pos.x + positioned_item.position.x,
2881 base_pos.y + positioned_item.position.y,
2882 ),
2883 LogicalSize::new(bounds.width, bounds.height),
2884 );
2885
2886 match content {
2887 InlineContent::Image(image) => {
2888 if let Some(image_ref) = get_image_ref_for_image_source(&image.source) {
2889 builder.push_image(object_bounds, image_ref);
2890 }
2891 }
2892 InlineContent::Shape(shape) => {
2893 self.paint_inline_shape(builder, object_bounds, shape, bounds)?;
2894 }
2895 _ => {}
2896 }
2897 Ok(())
2898 }
2899
2900 fn paint_inline_shape(
2902 &self,
2903 builder: &mut DisplayListBuilder,
2904 object_bounds: LogicalRect,
2905 shape: &InlineShape,
2906 bounds: &crate::text3::cache::Rect,
2907 ) -> Result<()> {
2908 let Some(node_id) = shape.source_node_id else {
2911 return Ok(());
2912 };
2913
2914 let styled_node_state =
2915 &self.ctx.styled_dom.styled_nodes.as_container()[node_id].styled_node_state;
2916
2917 let background_contents =
2919 get_background_contents(self.ctx.styled_dom, node_id, styled_node_state);
2920
2921 let border_info = get_border_info(self.ctx.styled_dom, node_id, styled_node_state);
2923
2924 let margins = if let Some(indices) = self.positioned_tree.tree.dom_to_layout.get(&node_id) {
2927 if let Some(&idx) = indices.first() {
2928 self.positioned_tree.tree.nodes[idx].box_props.margin
2929 } else {
2930 Default::default()
2931 }
2932 } else {
2933 Default::default()
2934 };
2935
2936 let border_box_bounds = LogicalRect {
2938 origin: LogicalPosition {
2939 x: object_bounds.origin.x + margins.left,
2940 y: object_bounds.origin.y + margins.top,
2941 },
2942 size: LogicalSize {
2943 width: (object_bounds.size.width - margins.left - margins.right).max(0.0),
2944 height: (object_bounds.size.height - margins.top - margins.bottom).max(0.0),
2945 },
2946 };
2947
2948 let element_size = PhysicalSizeImport {
2949 width: border_box_bounds.size.width,
2950 height: border_box_bounds.size.height,
2951 };
2952
2953 let simple_border_radius = get_border_radius(
2955 self.ctx.styled_dom,
2956 node_id,
2957 styled_node_state,
2958 element_size,
2959 self.ctx.viewport_size,
2960 );
2961
2962 let style_border_radius =
2964 get_style_border_radius(self.ctx.styled_dom, node_id, styled_node_state);
2965
2966 builder.push_backgrounds_and_border(
2968 border_box_bounds,
2969 &background_contents,
2970 &border_info,
2971 simple_border_radius,
2972 style_border_radius,
2973 );
2974
2975 if let Some(tag_id) = get_tag_id(self.ctx.styled_dom, Some(node_id)) {
2979 builder.push_hit_test_area(border_box_bounds, tag_id);
2980 }
2981
2982 Ok(())
2983 }
2984
2985 fn establishes_stacking_context(&self, node_index: usize) -> bool {
2987 let Some(node) = self.positioned_tree.tree.get(node_index) else {
2988 return false;
2989 };
2990 let Some(dom_id) = node.dom_node_id else {
2991 return false;
2992 };
2993
2994 let position = get_position_type(self.ctx.styled_dom, Some(dom_id));
2995 if position == LayoutPosition::Absolute || position == LayoutPosition::Fixed {
2996 return true;
2997 }
2998
2999 let z_index = get_z_index(self.ctx.styled_dom, Some(dom_id));
3000 if position == LayoutPosition::Relative && z_index != 0 {
3001 return true;
3002 }
3003
3004 if let Some(styled_node) = self.ctx.styled_dom.styled_nodes.as_container().get(dom_id) {
3005 let node_data = &self.ctx.styled_dom.node_data.as_container()[dom_id];
3006 let node_state =
3007 &self.ctx.styled_dom.styled_nodes.as_container()[dom_id].styled_node_state;
3008
3009 let opacity = self
3011 .ctx
3012 .styled_dom
3013 .css_property_cache
3014 .ptr
3015 .get_opacity(node_data, &dom_id, node_state)
3016 .and_then(|v| v.get_property())
3017 .map(|v| v.inner.normalized())
3018 .unwrap_or(1.0);
3019
3020 if opacity < 1.0 {
3021 return true;
3022 }
3023
3024 let has_transform = self
3026 .ctx
3027 .styled_dom
3028 .css_property_cache
3029 .ptr
3030 .get_transform(node_data, &dom_id, node_state)
3031 .and_then(|v| v.get_property())
3032 .map(|v| !v.is_empty())
3033 .unwrap_or(false);
3034
3035 if has_transform {
3036 return true;
3037 }
3038 }
3039
3040 false
3041 }
3042}
3043
3044pub struct PositionedTree<'a> {
3050 pub tree: &'a LayoutTree,
3052 pub calculated_positions: &'a BTreeMap<usize, LogicalPosition>,
3054}
3055
3056#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3062pub enum OverflowBehavior {
3063 Visible,
3065 Hidden,
3067 Clip,
3069 Scroll,
3071 Auto,
3073}
3074
3075impl OverflowBehavior {
3076 pub fn is_clipped(&self) -> bool {
3081 matches!(self, Self::Hidden | Self::Clip | Self::Scroll | Self::Auto)
3082 }
3083
3084 pub fn is_scroll(&self) -> bool {
3089 matches!(self, Self::Scroll | Self::Auto)
3090 }
3091}
3092
3093fn get_scroll_id(id: Option<NodeId>) -> LocalScrollId {
3094 id.map(|i| i.index() as u64).unwrap_or(0)
3095}
3096
3097fn get_scroll_content_size(node: &LayoutNode) -> LogicalSize {
3100 if let Some(overflow_size) = node.overflow_content_size {
3102 return overflow_size;
3103 }
3104
3105 let mut content_size = node.used_size.unwrap_or_default();
3107
3108 if let Some(ref cached_layout) = node.inline_layout_result {
3110 let text_layout = &cached_layout.layout;
3111 let mut max_x: f32 = 0.0;
3113 let mut max_y: f32 = 0.0;
3114
3115 for positioned_item in &text_layout.items {
3116 let item_bounds = positioned_item.item.bounds();
3117 let item_right = positioned_item.position.x + item_bounds.width;
3118 let item_bottom = positioned_item.position.y + item_bounds.height;
3119
3120 max_x = max_x.max(item_right);
3121 max_y = max_y.max(item_bottom);
3122 }
3123
3124 content_size.width = content_size.width.max(max_x);
3126 content_size.height = content_size.height.max(max_y);
3127 }
3128
3129 content_size
3130}
3131
3132fn get_tag_id(dom: &StyledDom, id: Option<NodeId>) -> Option<DisplayListTagId> {
3133 let node_id = id?;
3134 let styled_nodes = dom.styled_nodes.as_container();
3135 let styled_node = styled_nodes.get(node_id)?;
3136 let tag_id = styled_node.tag_id.into_option()?;
3137 Some((tag_id.inner, 0x0100))
3140}
3141
3142fn get_image_ref_for_image_source(
3143 source: &ImageSource,
3144) -> Option<ImageRef> {
3145 match source {
3146 ImageSource::Ref(image_ref) => Some(image_ref.clone()),
3147 ImageSource::Url(_url) => {
3148 None
3151 }
3152 ImageSource::Data(_) | ImageSource::Svg(_) | ImageSource::Placeholder(_) => {
3153 None
3155 }
3156 }
3157}
3158
3159fn get_display_item_bounds(item: &DisplayListItem) -> Option<LogicalRect> {
3161 match item {
3162 DisplayListItem::Rect { bounds, .. } => Some(*bounds),
3163 DisplayListItem::SelectionRect { bounds, .. } => Some(*bounds),
3164 DisplayListItem::CursorRect { bounds, .. } => Some(*bounds),
3165 DisplayListItem::Border { bounds, .. } => Some(*bounds),
3166 DisplayListItem::TextLayout { bounds, .. } => Some(*bounds),
3167 DisplayListItem::Text { clip_rect, .. } => Some(*clip_rect),
3168 DisplayListItem::Underline { bounds, .. } => Some(*bounds),
3169 DisplayListItem::Strikethrough { bounds, .. } => Some(*bounds),
3170 DisplayListItem::Overline { bounds, .. } => Some(*bounds),
3171 DisplayListItem::Image { bounds, .. } => Some(*bounds),
3172 DisplayListItem::ScrollBar { bounds, .. } => Some(*bounds),
3173 DisplayListItem::ScrollBarStyled { info } => Some(info.bounds),
3174 DisplayListItem::PushClip { bounds, .. } => Some(*bounds),
3175 DisplayListItem::PushScrollFrame { clip_bounds, .. } => Some(*clip_bounds),
3176 DisplayListItem::HitTestArea { bounds, .. } => Some(*bounds),
3177 DisplayListItem::PushStackingContext { bounds, .. } => Some(*bounds),
3178 DisplayListItem::IFrame { bounds, .. } => Some(*bounds),
3179 _ => None,
3180 }
3181}
3182
3183fn clip_and_offset_display_item(
3186 item: &DisplayListItem,
3187 page_top: f32,
3188 page_bottom: f32,
3189) -> Option<DisplayListItem> {
3190 match item {
3191 DisplayListItem::Rect {
3192 bounds,
3193 color,
3194 border_radius,
3195 } => clip_rect_item(*bounds, *color, *border_radius, page_top, page_bottom),
3196
3197 DisplayListItem::Border {
3198 bounds,
3199 widths,
3200 colors,
3201 styles,
3202 border_radius,
3203 } => clip_border_item(
3204 *bounds,
3205 *widths,
3206 *colors,
3207 *styles,
3208 border_radius.clone(),
3209 page_top,
3210 page_bottom,
3211 ),
3212
3213 DisplayListItem::SelectionRect {
3214 bounds,
3215 border_radius,
3216 color,
3217 } => clip_selection_rect_item(*bounds, *border_radius, *color, page_top, page_bottom),
3218
3219 DisplayListItem::CursorRect { bounds, color } => {
3220 clip_cursor_rect_item(*bounds, *color, page_top, page_bottom)
3221 }
3222
3223 DisplayListItem::Image { bounds, image } => {
3224 clip_image_item(*bounds, image.clone(), page_top, page_bottom)
3225 }
3226
3227 DisplayListItem::TextLayout {
3228 layout,
3229 bounds,
3230 font_hash,
3231 font_size_px,
3232 color,
3233 } => clip_text_layout_item(
3234 layout,
3235 *bounds,
3236 *font_hash,
3237 *font_size_px,
3238 *color,
3239 page_top,
3240 page_bottom,
3241 ),
3242
3243 DisplayListItem::Text {
3244 glyphs,
3245 font_hash,
3246 font_size_px,
3247 color,
3248 clip_rect,
3249 } => clip_text_item(
3250 glyphs,
3251 *font_hash,
3252 *font_size_px,
3253 *color,
3254 *clip_rect,
3255 page_top,
3256 page_bottom,
3257 ),
3258
3259 DisplayListItem::Underline {
3260 bounds,
3261 color,
3262 thickness,
3263 } => clip_text_decoration_item(
3264 *bounds,
3265 *color,
3266 *thickness,
3267 TextDecorationType::Underline,
3268 page_top,
3269 page_bottom,
3270 ),
3271
3272 DisplayListItem::Strikethrough {
3273 bounds,
3274 color,
3275 thickness,
3276 } => clip_text_decoration_item(
3277 *bounds,
3278 *color,
3279 *thickness,
3280 TextDecorationType::Strikethrough,
3281 page_top,
3282 page_bottom,
3283 ),
3284
3285 DisplayListItem::Overline {
3286 bounds,
3287 color,
3288 thickness,
3289 } => clip_text_decoration_item(
3290 *bounds,
3291 *color,
3292 *thickness,
3293 TextDecorationType::Overline,
3294 page_top,
3295 page_bottom,
3296 ),
3297
3298 DisplayListItem::ScrollBar {
3299 bounds,
3300 color,
3301 orientation,
3302 opacity_key,
3303 hit_id,
3304 } => clip_scrollbar_item(
3305 *bounds,
3306 *color,
3307 *orientation,
3308 *opacity_key,
3309 *hit_id,
3310 page_top,
3311 page_bottom,
3312 ),
3313
3314 DisplayListItem::HitTestArea { bounds, tag } => {
3315 clip_hit_test_area_item(*bounds, *tag, page_top, page_bottom)
3316 }
3317
3318 DisplayListItem::IFrame {
3319 child_dom_id,
3320 bounds,
3321 clip_rect,
3322 } => clip_iframe_item(*child_dom_id, *bounds, *clip_rect, page_top, page_bottom),
3323
3324 DisplayListItem::ScrollBarStyled { info } => {
3326 let bounds = info.bounds;
3327 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3328 None
3329 } else {
3330 let mut clipped_info = (**info).clone();
3332 let y_offset = -page_top;
3333 clipped_info.bounds = offset_rect_y(clipped_info.bounds, y_offset);
3334 clipped_info.track_bounds = offset_rect_y(clipped_info.track_bounds, y_offset);
3335 clipped_info.thumb_bounds = offset_rect_y(clipped_info.thumb_bounds, y_offset);
3336 if let Some(b) = clipped_info.button_decrement_bounds {
3337 clipped_info.button_decrement_bounds = Some(offset_rect_y(b, y_offset));
3338 }
3339 if let Some(b) = clipped_info.button_increment_bounds {
3340 clipped_info.button_increment_bounds = Some(offset_rect_y(b, y_offset));
3341 }
3342 Some(DisplayListItem::ScrollBarStyled {
3343 info: Box::new(clipped_info),
3344 })
3345 }
3346 }
3347
3348 DisplayListItem::PushClip { .. }
3350 | DisplayListItem::PopClip
3351 | DisplayListItem::PushScrollFrame { .. }
3352 | DisplayListItem::PopScrollFrame
3353 | DisplayListItem::PushStackingContext { .. }
3354 | DisplayListItem::PopStackingContext => None,
3355
3356 DisplayListItem::LinearGradient {
3358 bounds,
3359 gradient,
3360 border_radius,
3361 } => {
3362 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3363 None
3364 } else {
3365 Some(DisplayListItem::LinearGradient {
3366 bounds: offset_rect_y(*bounds, -page_top),
3367 gradient: gradient.clone(),
3368 border_radius: *border_radius,
3369 })
3370 }
3371 }
3372 DisplayListItem::RadialGradient {
3373 bounds,
3374 gradient,
3375 border_radius,
3376 } => {
3377 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3378 None
3379 } else {
3380 Some(DisplayListItem::RadialGradient {
3381 bounds: offset_rect_y(*bounds, -page_top),
3382 gradient: gradient.clone(),
3383 border_radius: *border_radius,
3384 })
3385 }
3386 }
3387 DisplayListItem::ConicGradient {
3388 bounds,
3389 gradient,
3390 border_radius,
3391 } => {
3392 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3393 None
3394 } else {
3395 Some(DisplayListItem::ConicGradient {
3396 bounds: offset_rect_y(*bounds, -page_top),
3397 gradient: gradient.clone(),
3398 border_radius: *border_radius,
3399 })
3400 }
3401 }
3402
3403 DisplayListItem::BoxShadow {
3405 bounds,
3406 shadow,
3407 border_radius,
3408 } => {
3409 if bounds.origin.y + bounds.size.height < page_top || bounds.origin.y > page_bottom {
3410 None
3411 } else {
3412 Some(DisplayListItem::BoxShadow {
3413 bounds: offset_rect_y(*bounds, -page_top),
3414 shadow: *shadow,
3415 border_radius: *border_radius,
3416 })
3417 }
3418 }
3419
3420 DisplayListItem::PushFilter { .. }
3422 | DisplayListItem::PopFilter
3423 | DisplayListItem::PushBackdropFilter { .. }
3424 | DisplayListItem::PopBackdropFilter
3425 | DisplayListItem::PushOpacity { .. }
3426 | DisplayListItem::PopOpacity => None,
3427 }
3428}
3429
3430#[derive(Debug, Clone, Copy)]
3434enum TextDecorationType {
3435 Underline,
3436 Strikethrough,
3437 Overline,
3438}
3439
3440fn clip_rect_item(
3442 bounds: LogicalRect,
3443 color: ColorU,
3444 border_radius: BorderRadius,
3445 page_top: f32,
3446 page_bottom: f32,
3447) -> Option<DisplayListItem> {
3448 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Rect {
3449 bounds: clipped,
3450 color,
3451 border_radius,
3452 })
3453}
3454
3455fn clip_border_item(
3457 bounds: LogicalRect,
3458 widths: StyleBorderWidths,
3459 colors: StyleBorderColors,
3460 styles: StyleBorderStyles,
3461 border_radius: StyleBorderRadius,
3462 page_top: f32,
3463 page_bottom: f32,
3464) -> Option<DisplayListItem> {
3465 let original_bounds = bounds;
3466 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| {
3467 let new_widths = adjust_border_widths_for_clipping(
3468 widths,
3469 original_bounds,
3470 clipped,
3471 page_top,
3472 page_bottom,
3473 );
3474 DisplayListItem::Border {
3475 bounds: clipped,
3476 widths: new_widths,
3477 colors,
3478 styles,
3479 border_radius,
3480 }
3481 })
3482}
3483
3484fn adjust_border_widths_for_clipping(
3487 mut widths: StyleBorderWidths,
3488 original_bounds: LogicalRect,
3489 clipped: LogicalRect,
3490 page_top: f32,
3491 page_bottom: f32,
3492) -> StyleBorderWidths {
3493 if clipped.origin.y > 0.0 && original_bounds.origin.y < page_top {
3495 widths.top = None;
3496 }
3497
3498 let original_bottom = original_bounds.origin.y + original_bounds.size.height;
3500 let clipped_bottom = clipped.origin.y + clipped.size.height;
3501 if original_bottom > page_bottom && clipped_bottom >= page_bottom - page_top - 1.0 {
3502 widths.bottom = None;
3503 }
3504
3505 widths
3506}
3507
3508fn clip_selection_rect_item(
3510 bounds: LogicalRect,
3511 border_radius: BorderRadius,
3512 color: ColorU,
3513 page_top: f32,
3514 page_bottom: f32,
3515) -> Option<DisplayListItem> {
3516 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::SelectionRect {
3517 bounds: clipped,
3518 border_radius,
3519 color,
3520 })
3521}
3522
3523fn clip_cursor_rect_item(
3525 bounds: LogicalRect,
3526 color: ColorU,
3527 page_top: f32,
3528 page_bottom: f32,
3529) -> Option<DisplayListItem> {
3530 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::CursorRect {
3531 bounds: clipped,
3532 color,
3533 })
3534}
3535
3536fn clip_image_item(
3538 bounds: LogicalRect,
3539 image: ImageRef,
3540 page_top: f32,
3541 page_bottom: f32,
3542) -> Option<DisplayListItem> {
3543 if !rect_intersects(&bounds, page_top, page_bottom) {
3544 return None;
3545 }
3546 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::Image {
3547 bounds: clipped,
3548 image,
3549 })
3550}
3551
3552fn clip_text_layout_item(
3554 layout: &Arc<dyn std::any::Any + Send + Sync>,
3555 bounds: LogicalRect,
3556 font_hash: FontHash,
3557 font_size_px: f32,
3558 color: ColorU,
3559 page_top: f32,
3560 page_bottom: f32,
3561) -> Option<DisplayListItem> {
3562 if !rect_intersects(&bounds, page_top, page_bottom) {
3563 return None;
3564 }
3565
3566 #[cfg(feature = "text_layout")]
3568 if let Some(unified_layout) = layout.downcast_ref::<crate::text3::cache::UnifiedLayout>() {
3569 return clip_unified_layout(
3570 unified_layout,
3571 bounds,
3572 font_hash,
3573 font_size_px,
3574 color,
3575 page_top,
3576 page_bottom,
3577 );
3578 }
3579
3580 Some(DisplayListItem::TextLayout {
3582 layout: layout.clone(),
3583 bounds: offset_rect_y(bounds, -page_top),
3584 font_hash,
3585 font_size_px,
3586 color,
3587 })
3588}
3589
3590#[cfg(feature = "text_layout")]
3592fn clip_unified_layout(
3593 unified_layout: &crate::text3::cache::UnifiedLayout,
3594 bounds: LogicalRect,
3595 font_hash: FontHash,
3596 font_size_px: f32,
3597 color: ColorU,
3598 page_top: f32,
3599 page_bottom: f32,
3600) -> Option<DisplayListItem> {
3601 let layout_origin_y = bounds.origin.y;
3602 let layout_origin_x = bounds.origin.x;
3603
3604 let filtered_items: Vec<_> = unified_layout
3606 .items
3607 .iter()
3608 .filter(|item| item_center_on_page(item, layout_origin_y, page_top, page_bottom))
3609 .cloned()
3610 .collect();
3611
3612 if filtered_items.is_empty() {
3613 return None;
3614 }
3615
3616 let new_origin_y = (layout_origin_y - page_top).max(0.0);
3618
3619 let (offset_items, min_y, max_y, max_width) =
3621 transform_items_to_page_coords(filtered_items, layout_origin_y, page_top, new_origin_y);
3622
3623 let new_layout = crate::text3::cache::UnifiedLayout {
3624 items: offset_items,
3625 overflow: unified_layout.overflow.clone(),
3626 };
3627
3628 let new_bounds = LogicalRect {
3629 origin: LogicalPosition {
3630 x: layout_origin_x,
3631 y: new_origin_y,
3632 },
3633 size: LogicalSize {
3634 width: max_width.max(bounds.size.width),
3635 height: (max_y - min_y.min(0.0)).max(0.0),
3636 },
3637 };
3638
3639 Some(DisplayListItem::TextLayout {
3640 layout: Arc::new(new_layout) as Arc<dyn std::any::Any + Send + Sync>,
3641 bounds: new_bounds,
3642 font_hash,
3643 font_size_px,
3644 color,
3645 })
3646}
3647
3648#[cfg(feature = "text_layout")]
3650fn item_center_on_page(
3651 item: &crate::text3::cache::PositionedItem,
3652 layout_origin_y: f32,
3653 page_top: f32,
3654 page_bottom: f32,
3655) -> bool {
3656 let item_y_absolute = layout_origin_y + item.position.y;
3657 let item_height = item.item.bounds().height;
3658 let item_center_y = item_y_absolute + (item_height / 2.0);
3659 item_center_y >= page_top && item_center_y < page_bottom
3660}
3661
3662#[cfg(feature = "text_layout")]
3665fn transform_items_to_page_coords(
3666 items: Vec<crate::text3::cache::PositionedItem>,
3667 layout_origin_y: f32,
3668 page_top: f32,
3669 new_origin_y: f32,
3670) -> (Vec<crate::text3::cache::PositionedItem>, f32, f32, f32) {
3671 let mut min_y = f32::MAX;
3672 let mut max_y = f32::MIN;
3673 let mut max_width = 0.0f32;
3674
3675 let offset_items: Vec<_> = items
3676 .into_iter()
3677 .map(|mut item| {
3678 let abs_y = layout_origin_y + item.position.y;
3679 let page_y = abs_y - page_top;
3680 let new_item_y = page_y - new_origin_y;
3681
3682 let item_bounds = item.item.bounds();
3683 min_y = min_y.min(new_item_y);
3684 max_y = max_y.max(new_item_y + item_bounds.height);
3685 max_width = max_width.max(item.position.x + item_bounds.width);
3686
3687 item.position.y = new_item_y;
3688 item
3689 })
3690 .collect();
3691
3692 (offset_items, min_y, max_y, max_width)
3693}
3694
3695fn clip_text_item(
3697 glyphs: &[GlyphInstance],
3698 font_hash: FontHash,
3699 font_size_px: f32,
3700 color: ColorU,
3701 clip_rect: LogicalRect,
3702 page_top: f32,
3703 page_bottom: f32,
3704) -> Option<DisplayListItem> {
3705 if !rect_intersects(&clip_rect, page_top, page_bottom) {
3706 return None;
3707 }
3708
3709 let page_glyphs: Vec<_> = glyphs
3711 .iter()
3712 .filter(|g| g.point.y >= page_top && g.point.y < page_bottom)
3713 .map(|g| GlyphInstance {
3714 index: g.index,
3715 point: LogicalPosition {
3716 x: g.point.x,
3717 y: g.point.y - page_top,
3718 },
3719 size: g.size,
3720 })
3721 .collect();
3722
3723 if page_glyphs.is_empty() {
3724 return None;
3725 }
3726
3727 Some(DisplayListItem::Text {
3728 glyphs: page_glyphs,
3729 font_hash,
3730 font_size_px,
3731 color,
3732 clip_rect: offset_rect_y(clip_rect, -page_top),
3733 })
3734}
3735
3736fn clip_text_decoration_item(
3738 bounds: LogicalRect,
3739 color: ColorU,
3740 thickness: f32,
3741 decoration_type: TextDecorationType,
3742 page_top: f32,
3743 page_bottom: f32,
3744) -> Option<DisplayListItem> {
3745 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| match decoration_type {
3746 TextDecorationType::Underline => DisplayListItem::Underline {
3747 bounds: clipped,
3748 color,
3749 thickness,
3750 },
3751 TextDecorationType::Strikethrough => DisplayListItem::Strikethrough {
3752 bounds: clipped,
3753 color,
3754 thickness,
3755 },
3756 TextDecorationType::Overline => DisplayListItem::Overline {
3757 bounds: clipped,
3758 color,
3759 thickness,
3760 },
3761 })
3762}
3763
3764fn clip_scrollbar_item(
3766 bounds: LogicalRect,
3767 color: ColorU,
3768 orientation: ScrollbarOrientation,
3769 opacity_key: Option<OpacityKey>,
3770 hit_id: Option<azul_core::hit_test::ScrollbarHitId>,
3771 page_top: f32,
3772 page_bottom: f32,
3773) -> Option<DisplayListItem> {
3774 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::ScrollBar {
3775 bounds: clipped,
3776 color,
3777 orientation,
3778 opacity_key,
3779 hit_id,
3780 })
3781}
3782
3783fn clip_hit_test_area_item(
3785 bounds: LogicalRect,
3786 tag: DisplayListTagId,
3787 page_top: f32,
3788 page_bottom: f32,
3789) -> Option<DisplayListItem> {
3790 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::HitTestArea {
3791 bounds: clipped,
3792 tag,
3793 })
3794}
3795
3796fn clip_iframe_item(
3798 child_dom_id: DomId,
3799 bounds: LogicalRect,
3800 clip_rect: LogicalRect,
3801 page_top: f32,
3802 page_bottom: f32,
3803) -> Option<DisplayListItem> {
3804 clip_rect_bounds(bounds, page_top, page_bottom).map(|clipped| DisplayListItem::IFrame {
3805 child_dom_id,
3806 bounds: clipped,
3807 clip_rect: offset_rect_y(clip_rect, -page_top),
3808 })
3809}
3810
3811fn clip_rect_bounds(bounds: LogicalRect, page_top: f32, page_bottom: f32) -> Option<LogicalRect> {
3814 let item_top = bounds.origin.y;
3815 let item_bottom = bounds.origin.y + bounds.size.height;
3816
3817 if item_bottom <= page_top || item_top >= page_bottom {
3819 return None;
3820 }
3821
3822 let clipped_top = item_top.max(page_top);
3824 let clipped_bottom = item_bottom.min(page_bottom);
3825 let clipped_height = clipped_bottom - clipped_top;
3826
3827 let page_relative_y = clipped_top - page_top;
3829
3830 Some(LogicalRect {
3831 origin: LogicalPosition {
3832 x: bounds.origin.x,
3833 y: page_relative_y,
3834 },
3835 size: LogicalSize {
3836 width: bounds.size.width,
3837 height: clipped_height,
3838 },
3839 })
3840}
3841
3842fn rect_intersects(bounds: &LogicalRect, page_top: f32, page_bottom: f32) -> bool {
3844 let item_top = bounds.origin.y;
3845 let item_bottom = bounds.origin.y + bounds.size.height;
3846 item_bottom > page_top && item_top < page_bottom
3847}
3848
3849fn offset_rect_y(bounds: LogicalRect, offset_y: f32) -> LogicalRect {
3851 LogicalRect {
3852 origin: LogicalPosition {
3853 x: bounds.origin.x,
3854 y: bounds.origin.y + offset_y,
3855 },
3856 size: bounds.size,
3857 }
3858}
3859
3860use azul_css::props::layout::fragmentation::{BreakInside, PageBreak};
3869
3870use crate::solver3::pagination::{
3871 HeaderFooterConfig, MarginBoxContent, PageInfo, TableHeaderInfo, TableHeaderTracker,
3872};
3873
3874#[derive(Debug, Clone, Default)]
3876pub struct SlicerConfig {
3877 pub page_content_height: f32,
3879 pub page_gap: f32,
3882 pub allow_clipping: bool,
3884 pub header_footer: HeaderFooterConfig,
3886 pub page_width: f32,
3888 pub table_headers: TableHeaderTracker,
3890}
3891
3892impl SlicerConfig {
3893 pub fn simple(page_height: f32) -> Self {
3895 Self {
3896 page_content_height: page_height,
3897 page_gap: 0.0,
3898 allow_clipping: true,
3899 header_footer: HeaderFooterConfig::default(),
3900 page_width: 595.0, table_headers: TableHeaderTracker::default(),
3902 }
3903 }
3904
3905 pub fn with_gap(page_height: f32, gap: f32) -> Self {
3907 Self {
3908 page_content_height: page_height,
3909 page_gap: gap,
3910 allow_clipping: true,
3911 header_footer: HeaderFooterConfig::default(),
3912 page_width: 595.0,
3913 table_headers: TableHeaderTracker::default(),
3914 }
3915 }
3916
3917 pub fn with_header_footer(mut self, config: HeaderFooterConfig) -> Self {
3919 self.header_footer = config;
3920 self
3921 }
3922
3923 pub fn with_page_width(mut self, width: f32) -> Self {
3925 self.page_width = width;
3926 self
3927 }
3928
3929 pub fn with_table_headers(mut self, tracker: TableHeaderTracker) -> Self {
3931 self.table_headers = tracker;
3932 self
3933 }
3934
3935 pub fn register_table_header(&mut self, info: TableHeaderInfo) {
3937 self.table_headers.register_table_header(info);
3938 }
3939
3940 pub fn page_slot_height(&self) -> f32 {
3942 self.page_content_height + self.page_gap
3943 }
3944
3945 pub fn page_for_y(&self, y: f32) -> usize {
3947 if self.page_slot_height() <= 0.0 {
3948 return 0;
3949 }
3950 (y / self.page_slot_height()).floor() as usize
3951 }
3952
3953 pub fn page_bounds(&self, page_index: usize) -> (f32, f32) {
3955 let start = page_index as f32 * self.page_slot_height();
3956 let end = start + self.page_content_height;
3957 (start, end)
3958 }
3959}
3960
3961pub fn paginate_display_list_with_slicer_and_breaks(
3969 full_display_list: DisplayList,
3970 config: &SlicerConfig,
3971) -> Result<Vec<DisplayList>> {
3972 if config.page_content_height <= 0.0 || config.page_content_height >= f32::MAX {
3973 return Ok(vec![full_display_list]);
3974 }
3975
3976 let base_header_space = if config.header_footer.show_header {
3978 config.header_footer.header_height
3979 } else {
3980 0.0
3981 };
3982 let base_footer_space = if config.header_footer.show_footer {
3983 config.header_footer.footer_height
3984 } else {
3985 0.0
3986 };
3987
3988 let normal_page_content_height =
3990 config.page_content_height - base_header_space - base_footer_space;
3991 let first_page_content_height = if config.header_footer.skip_first_page {
3992 config.page_content_height
3994 } else {
3995 normal_page_content_height
3996 };
3997
3998 let page_breaks = calculate_page_break_positions(
4008 &full_display_list,
4009 first_page_content_height,
4010 normal_page_content_height,
4011 );
4012
4013 let num_pages = page_breaks.len();
4014
4015 let mut pages: Vec<DisplayList> = Vec::with_capacity(num_pages);
4017
4018 for (page_idx, &(content_start_y, content_end_y)) in page_breaks.iter().enumerate() {
4019 let page_info = PageInfo::new(page_idx + 1, num_pages);
4021
4022 let skip_this_page = config.header_footer.skip_first_page && page_info.is_first;
4024 let header_space = if config.header_footer.show_header && !skip_this_page {
4025 config.header_footer.header_height
4026 } else {
4027 0.0
4028 };
4029 let footer_space = if config.header_footer.show_footer && !skip_this_page {
4030 config.header_footer.footer_height
4031 } else {
4032 0.0
4033 };
4034
4035 let _ = footer_space; let mut page_items = Vec::new();
4038 let mut page_node_mapping = Vec::new();
4039
4040 if config.header_footer.show_header && !skip_this_page {
4042 let header_text = config.header_footer.header_text(page_info);
4043 if !header_text.is_empty() {
4044 let header_items = generate_text_display_items(
4045 &header_text,
4046 LogicalRect {
4047 origin: LogicalPosition { x: 0.0, y: 0.0 },
4048 size: LogicalSize {
4049 width: config.page_width,
4050 height: config.header_footer.header_height,
4051 },
4052 },
4053 config.header_footer.font_size,
4054 config.header_footer.text_color,
4055 TextAlignment::Center,
4056 );
4057 for item in header_items {
4058 page_items.push(item);
4059 page_node_mapping.push(None);
4060 }
4061 }
4062 }
4063
4064 let repeated_headers = config.table_headers.get_repeated_headers_for_page(
4066 page_idx,
4067 content_start_y,
4068 content_end_y,
4069 );
4070
4071 let mut thead_total_height = 0.0f32;
4072 for (y_offset_from_page_top, thead_items, thead_height) in repeated_headers {
4073 let thead_y = header_space + y_offset_from_page_top;
4074 for item in thead_items {
4075 let translated_item = offset_display_item_y(item, thead_y);
4076 page_items.push(translated_item);
4077 page_node_mapping.push(None);
4078 }
4079 thead_total_height = thead_total_height.max(thead_height);
4080 }
4081
4082 let content_y_offset = header_space + thead_total_height;
4084
4085 for (item_idx, item) in full_display_list.items.iter().enumerate() {
4087 if let Some(clipped_item) =
4088 clip_and_offset_display_item(item, content_start_y, content_end_y)
4089 {
4090 let final_item = if content_y_offset > 0.0 {
4091 offset_display_item_y(&clipped_item, content_y_offset)
4092 } else {
4093 clipped_item
4094 };
4095 page_items.push(final_item);
4096 let node_mapping = full_display_list
4097 .node_mapping
4098 .get(item_idx)
4099 .copied()
4100 .flatten();
4101 page_node_mapping.push(node_mapping);
4102 }
4103 }
4104
4105 if config.header_footer.show_footer && !skip_this_page {
4107 let footer_text = config.header_footer.footer_text(page_info);
4108 if !footer_text.is_empty() {
4109 let footer_y = config.page_content_height - config.header_footer.footer_height;
4110 let footer_items = generate_text_display_items(
4111 &footer_text,
4112 LogicalRect {
4113 origin: LogicalPosition {
4114 x: 0.0,
4115 y: footer_y,
4116 },
4117 size: LogicalSize {
4118 width: config.page_width,
4119 height: config.header_footer.footer_height,
4120 },
4121 },
4122 config.header_footer.font_size,
4123 config.header_footer.text_color,
4124 TextAlignment::Center,
4125 );
4126 for item in footer_items {
4127 page_items.push(item);
4128 page_node_mapping.push(None);
4129 }
4130 }
4131 }
4132
4133 pages.push(DisplayList {
4134 items: page_items,
4135 node_mapping: page_node_mapping,
4136 forced_page_breaks: Vec::new(), });
4138 }
4139
4140 if pages.is_empty() {
4142 pages.push(DisplayList::default());
4143 }
4144
4145 Ok(pages)
4146}
4147
4148fn calculate_page_break_positions(
4156 display_list: &DisplayList,
4157 first_page_height: f32,
4158 normal_page_height: f32,
4159) -> Vec<(f32, f32)> {
4160 let total_height = calculate_display_list_height(display_list);
4161
4162 if total_height <= 0.0 || first_page_height <= 0.0 {
4163 return vec![(0.0, total_height.max(first_page_height))];
4164 }
4165
4166 let mut break_points: Vec<f32> = Vec::new();
4168
4169 for &forced_break_y in &display_list.forced_page_breaks {
4171 if forced_break_y > 0.0 && forced_break_y < total_height {
4172 break_points.push(forced_break_y);
4173 }
4174 }
4175
4176 let mut y = first_page_height;
4178 while y < total_height {
4179 break_points.push(y);
4180 y += normal_page_height;
4181 }
4182
4183 break_points.sort_by(|a, b| a.partial_cmp(b).unwrap());
4185 break_points.dedup_by(|a, b| (*a - *b).abs() < 1.0); let mut page_breaks: Vec<(f32, f32)> = Vec::new();
4189 let mut page_start = 0.0f32;
4190
4191 for break_y in break_points {
4192 if break_y > page_start {
4193 page_breaks.push((page_start, break_y));
4194 page_start = break_y;
4195 }
4196 }
4197
4198 if page_start < total_height {
4200 page_breaks.push((page_start, total_height));
4201 }
4202
4203 if page_breaks.is_empty() {
4205 page_breaks.push((0.0, total_height.max(first_page_height)));
4206 }
4207
4208 page_breaks
4209}
4210
4211#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4213pub enum TextAlignment {
4214 Left,
4215 Center,
4216 Right,
4217}
4218
4219fn offset_display_item_y(item: &DisplayListItem, y_offset: f32) -> DisplayListItem {
4221 if y_offset == 0.0 {
4222 return item.clone();
4223 }
4224
4225 match item {
4226 DisplayListItem::Rect {
4227 bounds,
4228 color,
4229 border_radius,
4230 } => DisplayListItem::Rect {
4231 bounds: offset_rect_y(*bounds, y_offset),
4232 color: *color,
4233 border_radius: *border_radius,
4234 },
4235 DisplayListItem::Border {
4236 bounds,
4237 widths,
4238 colors,
4239 styles,
4240 border_radius,
4241 } => DisplayListItem::Border {
4242 bounds: offset_rect_y(*bounds, y_offset),
4243 widths: widths.clone(),
4244 colors: *colors,
4245 styles: *styles,
4246 border_radius: border_radius.clone(),
4247 },
4248 DisplayListItem::Text {
4249 glyphs,
4250 font_hash,
4251 font_size_px,
4252 color,
4253 clip_rect,
4254 } => {
4255 let offset_glyphs: Vec<GlyphInstance> = glyphs
4256 .iter()
4257 .map(|g| GlyphInstance {
4258 index: g.index,
4259 point: LogicalPosition {
4260 x: g.point.x,
4261 y: g.point.y + y_offset,
4262 },
4263 size: g.size,
4264 })
4265 .collect();
4266 DisplayListItem::Text {
4267 glyphs: offset_glyphs,
4268 font_hash: *font_hash,
4269 font_size_px: *font_size_px,
4270 color: *color,
4271 clip_rect: offset_rect_y(*clip_rect, y_offset),
4272 }
4273 }
4274 DisplayListItem::TextLayout {
4275 layout,
4276 bounds,
4277 font_hash,
4278 font_size_px,
4279 color,
4280 } => DisplayListItem::TextLayout {
4281 layout: layout.clone(),
4282 bounds: offset_rect_y(*bounds, y_offset),
4283 font_hash: *font_hash,
4284 font_size_px: *font_size_px,
4285 color: *color,
4286 },
4287 DisplayListItem::Image { bounds, image } => DisplayListItem::Image {
4288 bounds: offset_rect_y(*bounds, y_offset),
4289 image: image.clone(),
4290 },
4291 DisplayListItem::SelectionRect {
4293 bounds,
4294 border_radius,
4295 color,
4296 } => DisplayListItem::SelectionRect {
4297 bounds: offset_rect_y(*bounds, y_offset),
4298 border_radius: *border_radius,
4299 color: *color,
4300 },
4301 DisplayListItem::CursorRect { bounds, color } => DisplayListItem::CursorRect {
4302 bounds: offset_rect_y(*bounds, y_offset),
4303 color: *color,
4304 },
4305 DisplayListItem::Underline {
4306 bounds,
4307 color,
4308 thickness,
4309 } => DisplayListItem::Underline {
4310 bounds: offset_rect_y(*bounds, y_offset),
4311 color: *color,
4312 thickness: *thickness,
4313 },
4314 DisplayListItem::Strikethrough {
4315 bounds,
4316 color,
4317 thickness,
4318 } => DisplayListItem::Strikethrough {
4319 bounds: offset_rect_y(*bounds, y_offset),
4320 color: *color,
4321 thickness: *thickness,
4322 },
4323 DisplayListItem::Overline {
4324 bounds,
4325 color,
4326 thickness,
4327 } => DisplayListItem::Overline {
4328 bounds: offset_rect_y(*bounds, y_offset),
4329 color: *color,
4330 thickness: *thickness,
4331 },
4332 DisplayListItem::ScrollBar {
4333 bounds,
4334 color,
4335 orientation,
4336 opacity_key,
4337 hit_id,
4338 } => DisplayListItem::ScrollBar {
4339 bounds: offset_rect_y(*bounds, y_offset),
4340 color: *color,
4341 orientation: *orientation,
4342 opacity_key: *opacity_key,
4343 hit_id: *hit_id,
4344 },
4345 DisplayListItem::HitTestArea { bounds, tag } => DisplayListItem::HitTestArea {
4346 bounds: offset_rect_y(*bounds, y_offset),
4347 tag: *tag,
4348 },
4349 DisplayListItem::PushClip {
4350 bounds,
4351 border_radius,
4352 } => DisplayListItem::PushClip {
4353 bounds: offset_rect_y(*bounds, y_offset),
4354 border_radius: *border_radius,
4355 },
4356 DisplayListItem::PushScrollFrame {
4357 clip_bounds,
4358 content_size,
4359 scroll_id,
4360 } => DisplayListItem::PushScrollFrame {
4361 clip_bounds: offset_rect_y(*clip_bounds, y_offset),
4362 content_size: *content_size,
4363 scroll_id: *scroll_id,
4364 },
4365 DisplayListItem::PushStackingContext { bounds, z_index } => {
4366 DisplayListItem::PushStackingContext {
4367 bounds: offset_rect_y(*bounds, y_offset),
4368 z_index: *z_index,
4369 }
4370 }
4371 DisplayListItem::IFrame {
4372 child_dom_id,
4373 bounds,
4374 clip_rect,
4375 } => DisplayListItem::IFrame {
4376 child_dom_id: *child_dom_id,
4377 bounds: offset_rect_y(*bounds, y_offset),
4378 clip_rect: offset_rect_y(*clip_rect, y_offset),
4379 },
4380 DisplayListItem::PopClip => DisplayListItem::PopClip,
4382 DisplayListItem::PopScrollFrame => DisplayListItem::PopScrollFrame,
4383 DisplayListItem::PopStackingContext => DisplayListItem::PopStackingContext,
4384
4385 DisplayListItem::LinearGradient {
4387 bounds,
4388 gradient,
4389 border_radius,
4390 } => DisplayListItem::LinearGradient {
4391 bounds: offset_rect_y(*bounds, y_offset),
4392 gradient: gradient.clone(),
4393 border_radius: *border_radius,
4394 },
4395 DisplayListItem::RadialGradient {
4396 bounds,
4397 gradient,
4398 border_radius,
4399 } => DisplayListItem::RadialGradient {
4400 bounds: offset_rect_y(*bounds, y_offset),
4401 gradient: gradient.clone(),
4402 border_radius: *border_radius,
4403 },
4404 DisplayListItem::ConicGradient {
4405 bounds,
4406 gradient,
4407 border_radius,
4408 } => DisplayListItem::ConicGradient {
4409 bounds: offset_rect_y(*bounds, y_offset),
4410 gradient: gradient.clone(),
4411 border_radius: *border_radius,
4412 },
4413
4414 DisplayListItem::BoxShadow {
4416 bounds,
4417 shadow,
4418 border_radius,
4419 } => DisplayListItem::BoxShadow {
4420 bounds: offset_rect_y(*bounds, y_offset),
4421 shadow: *shadow,
4422 border_radius: *border_radius,
4423 },
4424
4425 DisplayListItem::PushFilter { bounds, filters } => DisplayListItem::PushFilter {
4427 bounds: offset_rect_y(*bounds, y_offset),
4428 filters: filters.clone(),
4429 },
4430 DisplayListItem::PopFilter => DisplayListItem::PopFilter,
4431 DisplayListItem::PushBackdropFilter { bounds, filters } => {
4432 DisplayListItem::PushBackdropFilter {
4433 bounds: offset_rect_y(*bounds, y_offset),
4434 filters: filters.clone(),
4435 }
4436 }
4437 DisplayListItem::PopBackdropFilter => DisplayListItem::PopBackdropFilter,
4438 DisplayListItem::PushOpacity { bounds, opacity } => DisplayListItem::PushOpacity {
4439 bounds: offset_rect_y(*bounds, y_offset),
4440 opacity: *opacity,
4441 },
4442 DisplayListItem::PopOpacity => DisplayListItem::PopOpacity,
4443 DisplayListItem::ScrollBarStyled { info } => {
4444 let mut offset_info = (**info).clone();
4445 offset_info.bounds = offset_rect_y(offset_info.bounds, y_offset);
4446 offset_info.track_bounds = offset_rect_y(offset_info.track_bounds, y_offset);
4447 offset_info.thumb_bounds = offset_rect_y(offset_info.thumb_bounds, y_offset);
4448 if let Some(b) = offset_info.button_decrement_bounds {
4449 offset_info.button_decrement_bounds = Some(offset_rect_y(b, y_offset));
4450 }
4451 if let Some(b) = offset_info.button_increment_bounds {
4452 offset_info.button_increment_bounds = Some(offset_rect_y(b, y_offset));
4453 }
4454 DisplayListItem::ScrollBarStyled {
4455 info: Box::new(offset_info),
4456 }
4457 }
4458 }
4459}
4460
4461fn generate_text_display_items(
4466 text: &str,
4467 bounds: LogicalRect,
4468 font_size: f32,
4469 color: ColorU,
4470 alignment: TextAlignment,
4471) -> Vec<DisplayListItem> {
4472 use crate::font_traits::FontHash;
4473
4474 if text.is_empty() {
4475 return Vec::new();
4476 }
4477
4478 let char_width = font_size * 0.5;
4481 let text_width = text.len() as f32 * char_width;
4482
4483 let x_offset = match alignment {
4484 TextAlignment::Left => bounds.origin.x,
4485 TextAlignment::Center => bounds.origin.x + (bounds.size.width - text_width) / 2.0,
4486 TextAlignment::Right => bounds.origin.x + bounds.size.width - text_width,
4487 };
4488
4489 let y_pos = bounds.origin.y + (bounds.size.height + font_size) / 2.0 - font_size * 0.2;
4491
4492 let glyphs: Vec<GlyphInstance> = text
4495 .chars()
4496 .enumerate()
4497 .filter(|(_, c)| !c.is_control())
4498 .map(|(i, c)| GlyphInstance {
4499 index: c as u32, point: LogicalPosition {
4501 x: x_offset + i as f32 * char_width,
4502 y: y_pos,
4503 },
4504 size: LogicalSize::new(char_width, font_size),
4505 })
4506 .collect();
4507
4508 if glyphs.is_empty() {
4509 return Vec::new();
4510 }
4511
4512 vec![DisplayListItem::Text {
4513 glyphs,
4514 font_hash: FontHash::from_hash(0), font_size_px: font_size,
4516 color,
4517 clip_rect: bounds,
4518 }]
4519}
4520
4521fn calculate_display_list_height(display_list: &DisplayList) -> f32 {
4523 let mut max_bottom = 0.0f32;
4524
4525 for item in &display_list.items {
4526 if let Some(bounds) = get_display_item_bounds(item) {
4527 let item_bottom = bounds.origin.y + bounds.size.height;
4528 max_bottom = max_bottom.max(item_bottom);
4529 }
4530 }
4531
4532 max_bottom
4533}
4534
4535#[derive(Debug, Clone, Copy, Default)]
4537pub struct BreakProperties {
4538 pub break_before: PageBreak,
4539 pub break_after: PageBreak,
4540 pub break_inside: BreakInside,
4541}