azul_core/
ui_solver.rs

1#[cfg(not(feature = "std"))]
2use alloc::string::{String, ToString};
3use alloc::{boxed::Box, collections::btree_map::BTreeMap, vec::Vec};
4#[cfg(target_arch = "x86_64")]
5use core::arch::x86_64::__m256;
6use core::{
7    fmt,
8    sync::atomic::{AtomicBool, Ordering as AtomicOrdering},
9};
10
11use azul_css::{
12    ColorU as StyleColorU, CssPropertyValue, LayoutBorderBottomWidth, LayoutBorderLeftWidth,
13    LayoutBorderRightWidth, LayoutBorderTopWidth, LayoutBottom, LayoutBoxSizing, LayoutDisplay,
14    LayoutFlexDirection, LayoutJustifyContent, LayoutLeft, LayoutMarginBottom, LayoutMarginLeft,
15    LayoutMarginRight, LayoutMarginTop, LayoutOverflow, LayoutPaddingBottom, LayoutPaddingLeft,
16    LayoutPaddingRight, LayoutPaddingTop, LayoutPoint, LayoutPosition, LayoutRect, LayoutRectVec,
17    LayoutRight, LayoutSize, LayoutTop, OptionF32, PixelValue, StyleBoxShadow, StyleFontSize,
18    StyleTextAlign, StyleTextColor, StyleTransform, StyleTransformOrigin, StyleVerticalAlign,
19};
20use rust_fontconfig::FcFontCache;
21
22use crate::{
23    app_resources::{
24        Epoch, FontInstanceKey, GlTextureCache, IdNamespace, ImageCache, OpacityKey,
25        RendererResources, ShapedWords, TransformKey, UpdateImageResult, WordPositions, Words,
26    },
27    callbacks::{
28        DocumentId, HidpiAdjustedBounds, HitTestItem, IFrameCallbackInfo, IFrameCallbackReturn,
29        PipelineId, ScrollHitTestItem,
30    },
31    display_list::{CachedDisplayList, RenderCallbacks},
32    dom::{DomNodeHash, ScrollTagId, TagId},
33    gl::OptionGlContextPtr,
34    id_tree::{NodeDataContainer, NodeDataContainerRef, NodeId},
35    styled_dom::{DomId, NodeHierarchyItemId, StyledDom},
36    window::{
37        FullWindowState, LogicalPosition, LogicalRect, LogicalRectVec, LogicalSize, ScrollStates,
38        WindowSize, WindowTheme,
39    },
40    window_state::RelayoutFn,
41};
42
43static INITIALIZED: AtomicBool = AtomicBool::new(false);
44static USE_AVX: AtomicBool = AtomicBool::new(false);
45static USE_SSE: AtomicBool = AtomicBool::new(false);
46
47pub const DEFAULT_FONT_SIZE_PX: isize = 16;
48pub const DEFAULT_FONT_SIZE: StyleFontSize = StyleFontSize {
49    inner: PixelValue::const_px(DEFAULT_FONT_SIZE_PX),
50};
51pub const DEFAULT_FONT_ID: &str = "serif";
52pub const DEFAULT_TEXT_COLOR: StyleTextColor = StyleTextColor {
53    inner: StyleColorU {
54        r: 0,
55        b: 0,
56        g: 0,
57        a: 255,
58    },
59};
60pub const DEFAULT_LINE_HEIGHT: f32 = 1.0;
61pub const DEFAULT_WORD_SPACING: f32 = 1.0;
62pub const DEFAULT_LETTER_SPACING: f32 = 0.0;
63pub const DEFAULT_TAB_WIDTH: f32 = 4.0;
64
65#[derive(Debug, Clone, PartialEq, PartialOrd)]
66#[repr(C)]
67pub struct InlineTextLayout {
68    pub lines: InlineTextLineVec,
69    pub content_size: LogicalSize,
70}
71
72impl_vec!(
73    InlineTextLayout,
74    InlineTextLayoutVec,
75    InlineTextLayoutVecDestructor
76);
77impl_vec_clone!(
78    InlineTextLayout,
79    InlineTextLayoutVec,
80    InlineTextLayoutVecDestructor
81);
82impl_vec_debug!(InlineTextLayout, InlineTextLayoutVec);
83impl_vec_partialeq!(InlineTextLayout, InlineTextLayoutVec);
84impl_vec_partialord!(InlineTextLayout, InlineTextLayoutVec);
85
86/// NOTE: The bounds of the text line is the TOP left corner (relative to the text origin),
87/// but the word_position is the BOTTOM left corner (relative to the text line)
88#[derive(Debug, Clone, PartialEq, PartialOrd)]
89#[repr(C)]
90pub struct InlineTextLine {
91    pub bounds: LogicalRect,
92    /// At which word does this line start?
93    pub word_start: usize,
94    /// At which word does this line end
95    pub word_end: usize,
96}
97
98impl_vec!(
99    InlineTextLine,
100    InlineTextLineVec,
101    InlineTextLineVecDestructor
102);
103impl_vec_clone!(
104    InlineTextLine,
105    InlineTextLineVec,
106    InlineTextLineVecDestructor
107);
108impl_vec_mut!(InlineTextLine, InlineTextLineVec);
109impl_vec_debug!(InlineTextLine, InlineTextLineVec);
110impl_vec_partialeq!(InlineTextLine, InlineTextLineVec);
111impl_vec_partialord!(InlineTextLine, InlineTextLineVec);
112
113impl InlineTextLine {
114    pub const fn new(bounds: LogicalRect, word_start: usize, word_end: usize) -> Self {
115        Self {
116            bounds,
117            word_start,
118            word_end,
119        }
120    }
121}
122
123impl InlineTextLayout {
124    #[inline]
125    pub fn get_leading(&self) -> f32 {
126        match self.lines.as_ref().first() {
127            None => 0.0,
128            Some(s) => s.bounds.origin.x as f32,
129        }
130    }
131
132    #[inline]
133    pub fn get_trailing(&self) -> f32 {
134        match self.lines.as_ref().first() {
135            None => 0.0,
136            Some(s) => (s.bounds.origin.x + s.bounds.size.width) as f32,
137        }
138    }
139
140    /// Align the lines horizontal to *their bounding box*
141    pub fn align_children_horizontal(
142        &mut self,
143        parent_size: &LogicalSize,
144        horizontal_alignment: StyleTextAlign,
145    ) {
146        let shift_multiplier = match calculate_horizontal_shift_multiplier(horizontal_alignment) {
147            None => return,
148            Some(s) => s,
149        };
150
151        for line in self.lines.as_mut().iter_mut() {
152            line.bounds.origin.x += shift_multiplier * (parent_size.width - line.bounds.size.width);
153        }
154    }
155
156    /// Align the lines vertical to *their parents container*
157    pub fn align_children_vertical_in_parent_bounds(
158        &mut self,
159        parent_size: &LogicalSize,
160        vertical_alignment: StyleVerticalAlign,
161    ) {
162        let shift_multiplier = match calculate_vertical_shift_multiplier(vertical_alignment) {
163            None => return,
164            Some(s) => s,
165        };
166
167        let glyphs_vertical_bottom = self
168            .lines
169            .as_ref()
170            .last()
171            .map(|l| l.bounds.origin.y)
172            .unwrap_or(0.0);
173        let vertical_shift = (parent_size.height - glyphs_vertical_bottom) * shift_multiplier;
174
175        for line in self.lines.as_mut().iter_mut() {
176            line.bounds.origin.y += vertical_shift;
177        }
178    }
179}
180
181#[inline]
182pub fn calculate_horizontal_shift_multiplier(horizontal_alignment: StyleTextAlign) -> Option<f32> {
183    use azul_css::StyleTextAlign::*;
184    match horizontal_alignment {
185        Left => None,
186        Center => Some(0.5), // move the line by the half width
187        Right => Some(1.0),  // move the line by the full width
188    }
189}
190
191#[inline]
192pub fn calculate_vertical_shift_multiplier(vertical_alignment: StyleVerticalAlign) -> Option<f32> {
193    use azul_css::StyleVerticalAlign::*;
194    match vertical_alignment {
195        Top => None,
196        Center => Some(0.5), // move the line by the half width
197        Bottom => Some(1.0), // move the line by the full width
198    }
199}
200
201#[derive(Clone, Copy, Eq, Hash, PartialEq, Ord, PartialOrd)]
202#[repr(C)]
203pub struct ExternalScrollId(pub u64, pub PipelineId);
204
205impl ::core::fmt::Display for ExternalScrollId {
206    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207        write!(f, "ExternalScrollId({})", self.0)
208    }
209}
210
211impl ::core::fmt::Debug for ExternalScrollId {
212    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
213        write!(f, "{}", self)
214    }
215}
216
217#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
218pub struct ScrolledNodes {
219    pub overflowing_nodes: BTreeMap<NodeHierarchyItemId, OverflowingScrollNode>,
220    /// Nodes that need to clip their direct children (i.e. nodes with overflow-x and overflow-y
221    /// set to "Hidden")
222    pub clip_nodes: BTreeMap<NodeId, LogicalSize>,
223    pub tags_to_node_ids: BTreeMap<ScrollTagId, NodeHierarchyItemId>,
224}
225
226#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
227pub struct OverflowingScrollNode {
228    pub parent_rect: LogicalRect,
229    pub child_rect: LogicalRect,
230    pub virtual_child_rect: LogicalRect,
231    pub parent_external_scroll_id: ExternalScrollId,
232    pub parent_dom_hash: DomNodeHash,
233    pub scroll_tag_id: ScrollTagId,
234}
235
236impl Default for OverflowingScrollNode {
237    fn default() -> Self {
238        use crate::dom::TagId;
239        Self {
240            parent_rect: LogicalRect::zero(),
241            child_rect: LogicalRect::zero(),
242            virtual_child_rect: LogicalRect::zero(),
243            parent_external_scroll_id: ExternalScrollId(0, PipelineId::DUMMY),
244            parent_dom_hash: DomNodeHash(0),
245            scroll_tag_id: ScrollTagId(TagId(0)),
246        }
247    }
248}
249
250#[derive(Debug, Copy, Clone, PartialEq)]
251pub enum WhConstraint {
252    /// between min, max
253    Between(f32, f32),
254    /// Value needs to be exactly X
255    EqualTo(f32),
256    /// Value can be anything
257    Unconstrained,
258}
259
260impl Default for WhConstraint {
261    fn default() -> Self {
262        WhConstraint::Unconstrained
263    }
264}
265
266impl WhConstraint {
267    /// Returns the minimum value or 0 on `Unconstrained`
268    /// (warning: this might not be what you want)
269    pub fn min_needed_space(&self) -> Option<f32> {
270        use self::WhConstraint::*;
271        match self {
272            Between(min, _) => Some(*min),
273            EqualTo(exact) => Some(*exact),
274            Unconstrained => None,
275        }
276    }
277
278    /// Returns the maximum space until the constraint is violated - returns
279    /// `None` if the constraint is unbounded
280    pub fn max_available_space(&self) -> Option<f32> {
281        use self::WhConstraint::*;
282        match self {
283            Between(_, max) => Some(*max),
284            EqualTo(exact) => Some(*exact),
285            Unconstrained => None,
286        }
287    }
288
289    /// Returns if this `WhConstraint` is an `EqualTo` constraint
290    pub fn is_fixed_constraint(&self) -> bool {
291        use self::WhConstraint::*;
292        match self {
293            EqualTo(_) => true,
294            _ => false,
295        }
296    }
297
298    // The absolute positioned node might have a max-width constraint, which has a
299    // higher precedence than `top, bottom, left, right`.
300    pub fn calculate_from_relative_parent(&self, relative_parent_width: f32) -> f32 {
301        match self {
302            WhConstraint::EqualTo(e) => *e,
303            WhConstraint::Between(min, max) => relative_parent_width.max(*min).min(*max),
304            WhConstraint::Unconstrained => relative_parent_width,
305        }
306    }
307}
308
309#[derive(Debug, Default, Copy, Clone, PartialEq)]
310pub struct WidthCalculatedRect {
311    pub preferred_width: WhConstraint,
312
313    pub margin_right: Option<CssPropertyValue<LayoutMarginRight>>,
314    pub margin_left: Option<CssPropertyValue<LayoutMarginLeft>>,
315
316    pub padding_right: Option<CssPropertyValue<LayoutPaddingRight>>,
317    pub padding_left: Option<CssPropertyValue<LayoutPaddingLeft>>,
318
319    pub border_right: Option<CssPropertyValue<LayoutBorderRightWidth>>,
320    pub border_left: Option<CssPropertyValue<LayoutBorderLeftWidth>>,
321
322    pub box_sizing: LayoutBoxSizing,
323
324    pub left: Option<CssPropertyValue<LayoutLeft>>,
325    pub right: Option<CssPropertyValue<LayoutRight>>,
326
327    pub flex_grow_px: f32,
328    pub min_inner_size_px: f32,
329}
330
331impl WidthCalculatedRect {
332    pub fn overflow_width(&self) -> f32 {
333        if !self.flex_grow_px.is_sign_positive() {
334            self.min_inner_size_px
335        } else {
336            self.min_inner_size_px + self.flex_grow_px
337        }
338    }
339
340    pub fn get_border_left(&self, percent_resolve: f32) -> f32 {
341        self.border_left
342            .as_ref()
343            .and_then(|p| {
344                p.get_property()
345                    .map(|px| px.inner.to_pixels(percent_resolve))
346            })
347            .unwrap_or(0.0)
348    }
349
350    pub fn get_border_right(&self, percent_resolve: f32) -> f32 {
351        self.border_right
352            .as_ref()
353            .and_then(|p| {
354                p.get_property()
355                    .map(|px| px.inner.to_pixels(percent_resolve))
356            })
357            .unwrap_or(0.0)
358    }
359
360    pub fn get_raw_padding_left(&self, percent_resolve: f32) -> f32 {
361        self.padding_left
362            .as_ref()
363            .and_then(|p| {
364                p.get_property()
365                    .map(|px| px.inner.to_pixels(percent_resolve))
366            })
367            .unwrap_or(0.0)
368    }
369
370    pub fn get_raw_padding_right(&self, percent_resolve: f32) -> f32 {
371        self.padding_right
372            .as_ref()
373            .and_then(|p| {
374                p.get_property()
375                    .map(|px| px.inner.to_pixels(percent_resolve))
376            })
377            .unwrap_or(0.0)
378    }
379
380    pub fn get_padding_left(&self, percent_resolve: f32) -> f32 {
381        self.get_raw_padding_left(percent_resolve) + self.get_border_left(percent_resolve)
382    }
383
384    pub fn get_padding_right(&self, percent_resolve: f32) -> f32 {
385        self.get_raw_padding_right(percent_resolve) + self.get_border_right(percent_resolve)
386    }
387
388    pub fn get_margin_left(&self, percent_resolve: f32) -> f32 {
389        self.margin_left
390            .as_ref()
391            .and_then(|p| {
392                p.get_property()
393                    .map(|px| px.inner.to_pixels(percent_resolve))
394            })
395            .unwrap_or(0.0)
396    }
397
398    pub fn get_margin_right(&self, percent_resolve: f32) -> f32 {
399        self.margin_right
400            .as_ref()
401            .and_then(|p| {
402                p.get_property()
403                    .map(|px| px.inner.to_pixels(percent_resolve))
404            })
405            .unwrap_or(0.0)
406    }
407
408    /// Get the flex basis in the horizontal direction - vertical axis has to be calculated
409    /// differently
410    pub fn get_flex_basis_horizontal(&self, parent_width: f32) -> f32 {
411        self.min_inner_size_px
412            + self.get_margin_left(parent_width)
413            + self.get_margin_right(parent_width)
414            + self.get_raw_padding_left(parent_width)
415            + self.get_raw_padding_right(parent_width)
416            + self.get_border_left(parent_width)
417            + self.get_border_right(parent_width)
418    }
419
420    pub fn get_horizontal_border(&self, parent_width: f32) -> f32 {
421        self.get_border_left(parent_width) + self.get_border_right(parent_width)
422    }
423
424    /// Get the sum of the horizontal padding amount (`padding.left + padding.right`)
425    pub fn get_horizontal_padding(&self, parent_width: f32) -> f32 {
426        self.get_padding_left(parent_width) + self.get_padding_right(parent_width)
427    }
428
429    /// Get the sum of the horizontal padding amount (`margin.left + margin.right`)
430    pub fn get_horizontal_margin(&self, parent_width: f32) -> f32 {
431        self.get_margin_left(parent_width) + self.get_margin_right(parent_width)
432    }
433
434    /// Called after solver has run: Solved width of rectangle
435    pub fn total(&self) -> f32 {
436        self.min_inner_size_px + self.flex_grow_px
437    }
438
439    pub fn solved_result(&self) -> WidthSolvedResult {
440        WidthSolvedResult {
441            min_width: self.min_inner_size_px,
442            space_added: self.flex_grow_px,
443        }
444    }
445}
446
447#[derive(Debug, Default, Copy, Clone, PartialEq)]
448pub struct HeightCalculatedRect {
449    pub preferred_height: WhConstraint,
450
451    pub margin_top: Option<CssPropertyValue<LayoutMarginTop>>,
452    pub margin_bottom: Option<CssPropertyValue<LayoutMarginBottom>>,
453
454    pub padding_top: Option<CssPropertyValue<LayoutPaddingTop>>,
455    pub padding_bottom: Option<CssPropertyValue<LayoutPaddingBottom>>,
456
457    pub border_top: Option<CssPropertyValue<LayoutBorderTopWidth>>,
458    pub border_bottom: Option<CssPropertyValue<LayoutBorderBottomWidth>>,
459
460    pub top: Option<CssPropertyValue<LayoutTop>>,
461    pub bottom: Option<CssPropertyValue<LayoutBottom>>,
462
463    pub box_sizing: LayoutBoxSizing,
464
465    pub flex_grow_px: f32,
466    pub min_inner_size_px: f32,
467}
468
469impl HeightCalculatedRect {
470    pub fn overflow_height(&self) -> f32 {
471        if !self.flex_grow_px.is_sign_positive() {
472            self.min_inner_size_px
473        } else {
474            self.min_inner_size_px + self.flex_grow_px
475        }
476    }
477
478    pub fn get_border_top(&self, percent_resolve: f32) -> f32 {
479        self.border_top
480            .as_ref()
481            .and_then(|p| {
482                p.get_property()
483                    .map(|px| px.inner.to_pixels(percent_resolve))
484            })
485            .unwrap_or(0.0)
486    }
487
488    pub fn get_border_bottom(&self, percent_resolve: f32) -> f32 {
489        self.border_bottom
490            .as_ref()
491            .and_then(|p| {
492                p.get_property()
493                    .map(|px| px.inner.to_pixels(percent_resolve))
494            })
495            .unwrap_or(0.0)
496    }
497
498    pub fn get_raw_padding_top(&self, percent_resolve: f32) -> f32 {
499        self.padding_top
500            .as_ref()
501            .and_then(|p| {
502                p.get_property()
503                    .map(|px| px.inner.to_pixels(percent_resolve))
504            })
505            .unwrap_or(0.0)
506    }
507
508    pub fn get_raw_padding_bottom(&self, percent_resolve: f32) -> f32 {
509        self.padding_bottom
510            .as_ref()
511            .and_then(|p| {
512                p.get_property()
513                    .map(|px| px.inner.to_pixels(percent_resolve))
514            })
515            .unwrap_or(0.0)
516    }
517
518    pub fn get_padding_bottom(&self, percent_resolve: f32) -> f32 {
519        self.get_raw_padding_bottom(percent_resolve) + self.get_border_bottom(percent_resolve)
520    }
521
522    pub fn get_padding_top(&self, percent_resolve: f32) -> f32 {
523        self.get_raw_padding_top(percent_resolve) + self.get_border_top(percent_resolve)
524    }
525
526    pub fn get_margin_top(&self, percent_resolve: f32) -> f32 {
527        self.margin_top
528            .as_ref()
529            .and_then(|p| {
530                p.get_property()
531                    .map(|px| px.inner.to_pixels(percent_resolve))
532            })
533            .unwrap_or(0.0)
534    }
535
536    pub fn get_margin_bottom(&self, percent_resolve: f32) -> f32 {
537        self.margin_bottom
538            .as_ref()
539            .and_then(|p| {
540                p.get_property()
541                    .map(|px| px.inner.to_pixels(percent_resolve))
542            })
543            .unwrap_or(0.0)
544    }
545
546    /// Get the flex basis in the horizontal direction - vertical axis has to be calculated
547    /// differently
548    pub fn get_flex_basis_vertical(&self, parent_height: f32) -> f32 {
549        self.min_inner_size_px
550            + self.get_margin_top(parent_height)
551            + self.get_margin_bottom(parent_height)
552            + self.get_raw_padding_top(parent_height)
553            + self.get_raw_padding_bottom(parent_height)
554            + self.get_border_top(parent_height)
555            + self.get_border_bottom(parent_height)
556    }
557
558    /// Get the sum of the horizontal padding amount (`padding_top + padding_bottom`)
559    pub fn get_vertical_padding(&self, parent_height: f32) -> f32 {
560        self.get_padding_top(parent_height) + self.get_padding_bottom(parent_height)
561    }
562
563    /// Get the sum of the horizontal padding amount (`padding_top + padding_bottom`)
564    pub fn get_vertical_border(&self, parent_height: f32) -> f32 {
565        self.get_border_top(parent_height) + self.get_border_bottom(parent_height)
566    }
567
568    /// Get the sum of the horizontal margin amount (`margin_top + margin_bottom`)
569    pub fn get_vertical_margin(&self, parent_height: f32) -> f32 {
570        self.get_margin_top(parent_height) + self.get_margin_bottom(parent_height)
571    }
572
573    /// Called after solver has run: Solved height of rectangle
574    pub fn total(&self) -> f32 {
575        self.min_inner_size_px + self.flex_grow_px
576    }
577
578    /// Called after solver has run: Solved width of rectangle
579    pub fn solved_result(&self) -> HeightSolvedResult {
580        HeightSolvedResult {
581            min_height: self.min_inner_size_px,
582            space_added: self.flex_grow_px,
583        }
584    }
585}
586
587#[derive(Debug, Copy, Clone, PartialEq)]
588pub struct WidthSolvedResult {
589    pub min_width: f32,
590    pub space_added: f32,
591}
592
593#[derive(Debug, Copy, Clone, PartialEq)]
594pub struct HeightSolvedResult {
595    pub min_height: f32,
596    pub space_added: f32,
597}
598
599#[repr(transparent)]
600#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
601pub struct HorizontalSolvedPosition(pub f32);
602
603#[repr(transparent)]
604#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
605pub struct VerticalSolvedPosition(pub f32);
606
607pub struct LayoutResult {
608    pub dom_id: DomId,
609    pub parent_dom_id: Option<DomId>,
610    pub styled_dom: StyledDom,
611    pub root_size: LayoutSize,
612    pub root_position: LayoutPoint,
613    pub preferred_widths: NodeDataContainer<Option<f32>>,
614    pub preferred_heights: NodeDataContainer<Option<f32>>,
615    pub width_calculated_rects: NodeDataContainer<WidthCalculatedRect>, /* TODO: warning: large
616                                                                         * struct */
617    pub height_calculated_rects: NodeDataContainer<HeightCalculatedRect>, /* TODO: warning:
618                                                                           * large struct */
619    pub solved_pos_x: NodeDataContainer<HorizontalSolvedPosition>,
620    pub solved_pos_y: NodeDataContainer<VerticalSolvedPosition>,
621    pub layout_flex_grows: NodeDataContainer<f32>,
622    pub layout_displays: NodeDataContainer<CssPropertyValue<LayoutDisplay>>,
623    pub layout_positions: NodeDataContainer<LayoutPosition>,
624    pub layout_flex_directions: NodeDataContainer<LayoutFlexDirection>,
625    pub layout_justify_contents: NodeDataContainer<LayoutJustifyContent>,
626    pub rects: NodeDataContainer<PositionedRectangle>, // TODO: warning: large struct
627    pub words_cache: BTreeMap<NodeId, Words>,
628    pub shaped_words_cache: BTreeMap<NodeId, ShapedWords>,
629    pub positioned_words_cache: BTreeMap<NodeId, (WordPositions, FontInstanceKey)>,
630    pub scrollable_nodes: ScrolledNodes,
631    pub iframe_mapping: BTreeMap<NodeId, DomId>,
632    pub gpu_value_cache: GpuValueCache,
633}
634
635impl fmt::Debug for LayoutResult {
636    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
637        write!(
638            f,
639            "LayoutResult {{
640            dom_id: {},
641            bounds: {:?} @ {:?},
642            styled_dom (len = {}): {:#?},
643            preferred_widths(len = {}),
644            preferred_heights(len = {}),
645            width_calculated_rects(len = {}),
646            height_calculated_rects(len = {}),
647            solved_pos_x(len = {}),
648            solved_pos_y(len = {}),
649            layout_flex_grows(len = {}),
650            layout_displays(len = {}),
651            layout_positions(len = {}),
652            layout_flex_directions(len = {}),
653            layout_justify_contents(len = {}),
654            rects(len = {}),
655            words_cache(len = {}),
656            shaped_words_cache(len = {}),
657            positioned_words_cache(len = {}),
658            scrollable_nodes: {:#?},
659            iframe_mapping(len = {}): {:#?},
660            gpu_value_cache: {:#?},
661        }}",
662            self.dom_id.inner,
663            self.root_size,
664            self.root_position,
665            self.styled_dom.node_hierarchy.len(),
666            self.styled_dom,
667            self.preferred_widths.len(),
668            self.preferred_heights.len(),
669            self.width_calculated_rects.len(),
670            self.height_calculated_rects.len(),
671            self.solved_pos_x.len(),
672            self.solved_pos_y.len(),
673            self.layout_flex_grows.len(),
674            self.layout_displays.len(),
675            self.layout_positions.len(),
676            self.layout_flex_directions.len(),
677            self.layout_justify_contents.len(),
678            self.rects.len(),
679            self.words_cache.len(),
680            self.shaped_words_cache.len(),
681            self.positioned_words_cache.len(),
682            self.scrollable_nodes,
683            self.iframe_mapping.len(),
684            self.iframe_mapping,
685            self.gpu_value_cache,
686        )
687    }
688}
689
690pub struct QuickResizeResult {
691    pub gpu_event_changes: GpuEventChanges,
692    pub updated_images: Vec<UpdateImageResult>,
693    pub resized_nodes: BTreeMap<DomId, Vec<NodeId>>,
694}
695
696impl LayoutResult {
697    pub fn get_bounds(&self) -> LayoutRect {
698        LayoutRect::new(self.root_position, self.root_size)
699    }
700
701    pub fn get_cached_display_list(
702        document_id: &DocumentId,
703        dom_id: DomId,
704        epoch: Epoch,
705        layout_results: &[LayoutResult],
706        full_window_state: &FullWindowState,
707        gl_texture_cache: &GlTextureCache,
708        renderer_resources: &RendererResources,
709        image_cache: &ImageCache,
710    ) -> CachedDisplayList {
711        use crate::display_list::{
712            displaylist_handle_rect, push_rectangles_into_displaylist, DisplayListFrame,
713            DisplayListMsg, DisplayListParametersRef, LayoutRectContent, RectBackground,
714        };
715
716        let layout_result = match layout_results.get(dom_id.inner) {
717            Some(s) => s,
718            None => return CachedDisplayList::empty(),
719        };
720
721        let rects_in_rendering_order = layout_result.styled_dom.get_rects_in_rendering_order();
722        let referenced_content = DisplayListParametersRef {
723            dom_id,
724            document_id,
725            epoch,
726            full_window_state,
727            layout_results,
728            gl_texture_cache,
729            renderer_resources,
730            image_cache,
731        };
732
733        let root_width =
734            layout_result.width_calculated_rects.as_ref()[NodeId::ZERO].overflow_width();
735        let root_height =
736            layout_result.height_calculated_rects.as_ref()[NodeId::ZERO].overflow_height();
737        let root_size = LogicalSize::new(root_width, root_height);
738
739        let mut root_content = displaylist_handle_rect(
740            rects_in_rendering_order.root.into_crate_internal().unwrap(),
741            &referenced_content,
742        )
743        .unwrap_or(DisplayListMsg::Frame(DisplayListFrame::root(
744            LayoutSize::zero(),
745            LayoutPoint::zero(),
746        )));
747
748        let children = rects_in_rendering_order
749            .children
750            .as_ref()
751            .iter()
752            .filter_map(|child_content_group| {
753                push_rectangles_into_displaylist(child_content_group, &referenced_content)
754            })
755            .collect();
756
757        root_content.append_children(children);
758
759        let mut dl = CachedDisplayList {
760            root: root_content,
761            root_size,
762        };
763
764        // push the window background color, if the
765        // root node doesn't have any content
766        if dl.root.is_content_empty() {
767            dl.root.push_content(LayoutRectContent::Background {
768                content: RectBackground::Color(full_window_state.background_color),
769                size: None,
770                offset: None,
771                repeat: None,
772            });
773        }
774
775        dl
776    }
777
778    // Does a "quick" re-layout of the given DomId, calls iframe callbacks that have been resized
779    // and updates their DOM IDs.
780    //
781    // Assumes that an OpenGL context is active
782    #[must_use]
783    pub fn do_quick_resize(
784        id_namespace: IdNamespace,
785        document_id: DocumentId,
786        epoch: Epoch,
787        dom_id: DomId,
788        image_cache: &ImageCache,
789        gl_context: &OptionGlContextPtr,
790        layout_results: &mut [LayoutResult],
791        gl_texture_cache: &mut GlTextureCache,
792        renderer_resources: &mut RendererResources,
793        callbacks: &RenderCallbacks,
794        relayout_fn: RelayoutFn,
795        fc_cache: &FcFontCache,
796        window_size: &WindowSize,
797        window_theme: WindowTheme,
798    ) -> QuickResizeResult {
799        let dom_bounds = LogicalRect::new(LogicalPosition::zero(), window_size.dimensions);
800        let mut dom_ids_to_resize = vec![(dom_id, dom_bounds)];
801        let mut gpu_event_changes = GpuEventChanges::default();
802        let mut rsn = BTreeMap::new(); // resized nodes [DomID => Vec<NodeId>]
803
804        loop {
805            let mut new_dom_ids_to_resize = Vec::new();
806
807            for (dom_id, new_size) in dom_ids_to_resize.iter() {
808                let layout_size = new_size.to_layout_rect();
809
810                // Call the relayout function on the DOM to get the resized DOM
811                let mut resized_nodes = (relayout_fn)(
812                    *dom_id,
813                    layout_size,
814                    &mut layout_results[dom_id.inner],
815                    image_cache,
816                    renderer_resources,
817                    &document_id,
818                    None, // no new nodes to relayout
819                    None, // no text changes
820                );
821
822                rsn.insert(*dom_id, resized_nodes.resized_nodes.clone());
823
824                gpu_event_changes.merge(&mut resized_nodes.gpu_key_changes);
825
826                for node_id in resized_nodes.resized_nodes.into_iter() {
827                    let iframe_dom_id =
828                        match layout_results[dom_id.inner].iframe_mapping.get(&node_id) {
829                            Some(dom_id) => *dom_id,
830                            None => continue,
831                        };
832
833                    let iframe_rect_relative_to_parent = LayoutRect {
834                        origin: layout_results[iframe_dom_id.inner].root_position,
835                        size: layout_results[iframe_dom_id.inner].root_size,
836                    };
837
838                    let iframe_needs_to_be_invoked =
839                        !layout_size.contains_rect(&iframe_rect_relative_to_parent);
840
841                    if !iframe_needs_to_be_invoked {
842                        continue; // old iframe size still covers the new extent
843                    }
844
845                    let iframe_return: IFrameCallbackReturn = {
846                        let layout_result = &mut layout_results[dom_id.inner];
847                        let mut node_data_mut =
848                            layout_result.styled_dom.node_data.as_container_mut();
849                        let mut node = &mut node_data_mut[node_id];
850                        let iframe_node = match node.get_iframe_node() {
851                            Some(iframe_node) => iframe_node,
852                            None => continue, // not an iframe
853                        };
854
855                        // invoke the iframe with the new size and replace the dom with the DOM ID
856                        let hidpi_bounds = HidpiAdjustedBounds::from_bounds(
857                            layout_size.size,
858                            window_size.get_hidpi_factor(),
859                        );
860                        let scroll_node = layout_result
861                            .scrollable_nodes
862                            .overflowing_nodes
863                            .get(&NodeHierarchyItemId::from_crate_internal(Some(node_id)))
864                            .cloned()
865                            .unwrap_or_default();
866
867                        let mut iframe_callback_info = IFrameCallbackInfo::new(
868                            fc_cache,
869                            image_cache,
870                            window_theme,
871                            hidpi_bounds,
872                            // see /examples/assets/images/scrollbounds.png for documentation!
873                            /* scroll_size */
874                            scroll_node.child_rect.size,
875                            /* scroll_offset */
876                            scroll_node.child_rect.origin - scroll_node.parent_rect.origin,
877                            /* virtual_scroll_size */ scroll_node.virtual_child_rect.size,
878                            /* virtual_scroll_offset */
879                            scroll_node.virtual_child_rect.origin - scroll_node.parent_rect.origin,
880                        );
881                        (iframe_node.callback.cb)(&mut iframe_node.data, &mut iframe_callback_info)
882                    };
883
884                    // TODO: what to do if the new iframe has less or more sub-iframes
885                    // than the current one? edge-case, solve later.
886
887                    layout_results[iframe_dom_id.inner].styled_dom = iframe_return.dom;
888
889                    let new_iframe_rect = LogicalRect {
890                        // TODO: correct? or layout_results[dom_id.0].positioned_rects[node_id]?
891                        origin: LogicalPosition::zero(),
892                        size: layout_results[dom_id.inner].rects.as_ref()[node_id].size,
893                    };
894
895                    // Store the new scroll position
896                    // (trust the iframe to return these values correctly)
897                    let osn = layout_results[dom_id.inner]
898                        .scrollable_nodes
899                        .overflowing_nodes
900                        .entry(NodeHierarchyItemId::from_crate_internal(Some(node_id)))
901                        .or_insert_with(|| OverflowingScrollNode::default());
902
903                    osn.child_rect = LogicalRect {
904                        origin: iframe_return.scroll_offset,
905                        size: iframe_return.scroll_size,
906                    };
907                    osn.virtual_child_rect = LogicalRect {
908                        origin: iframe_return.virtual_scroll_offset,
909                        size: iframe_return.virtual_scroll_size,
910                    };
911
912                    new_dom_ids_to_resize.push((iframe_dom_id, new_iframe_rect));
913                }
914            }
915
916            if new_dom_ids_to_resize.is_empty() {
917                break;
918            } else {
919                dom_ids_to_resize = new_dom_ids_to_resize; // recurse
920            }
921        }
922
923        let updated_images = Self::resize_images(
924            id_namespace,
925            document_id,
926            epoch,
927            dom_id,
928            image_cache,
929            gl_context,
930            layout_results,
931            gl_texture_cache,
932            renderer_resources,
933            callbacks,
934            relayout_fn,
935            fc_cache,
936            window_size,
937            window_theme,
938            &rsn,
939        );
940
941        QuickResizeResult {
942            gpu_event_changes,
943            updated_images,
944            resized_nodes: rsn,
945        }
946    }
947
948    pub fn resize_images(
949        id_namespace: IdNamespace,
950        document_id: DocumentId,
951        epoch: Epoch,
952        dom_id: DomId,
953        image_cache: &ImageCache,
954        gl_context: &OptionGlContextPtr,
955        layout_results: &mut [LayoutResult],
956        gl_texture_cache: &mut GlTextureCache,
957        renderer_resources: &mut RendererResources,
958        callbacks: &RenderCallbacks,
959        relayout_fn: RelayoutFn,
960        fc_cache: &FcFontCache,
961        window_size: &WindowSize,
962        window_theme: WindowTheme,
963        rsn: &BTreeMap<DomId, Vec<NodeId>>,
964    ) -> Vec<UpdateImageResult> {
965        let mut updated_images = Vec::new();
966
967        for (dom_id, node_ids) in rsn.iter() {
968            for node_id in node_ids.iter() {
969                if let Some(update) = renderer_resources.rerender_image_callback(
970                    *dom_id,
971                    *node_id,
972                    document_id,
973                    epoch,
974                    id_namespace,
975                    gl_context,
976                    image_cache,
977                    fc_cache,
978                    window_size.get_hidpi_factor(),
979                    callbacks,
980                    layout_results,
981                    gl_texture_cache,
982                ) {
983                    updated_images.push(update);
984                }
985            }
986        }
987
988        updated_images
989    }
990
991    // Calls the IFrame callbacks again if they are currently
992    // scrolled out of bounds
993    pub fn scroll_iframes(
994        document_id: &DocumentId,
995        dom_id: DomId,
996        epoch: Epoch,
997        layout_results: &[LayoutResult],
998        full_window_state: &FullWindowState,
999        gl_texture_cache: &GlTextureCache,
1000        renderer_resources: &RendererResources,
1001        image_cache: &ImageCache,
1002    ) {
1003        // TODO
1004    }
1005}
1006
1007#[derive(Default, Debug, Clone, PartialEq, PartialOrd)]
1008pub struct GpuValueCache {
1009    pub transform_keys: BTreeMap<NodeId, TransformKey>,
1010    pub current_transform_values: BTreeMap<NodeId, ComputedTransform3D>,
1011    pub opacity_keys: BTreeMap<NodeId, OpacityKey>,
1012    pub current_opacity_values: BTreeMap<NodeId, f32>,
1013}
1014
1015#[derive(Debug, Clone, PartialEq, PartialOrd)]
1016pub enum GpuTransformKeyEvent {
1017    Added(NodeId, TransformKey, ComputedTransform3D),
1018    Changed(
1019        NodeId,
1020        TransformKey,
1021        ComputedTransform3D,
1022        ComputedTransform3D,
1023    ),
1024    Removed(NodeId, TransformKey),
1025}
1026
1027#[derive(Debug, Clone, PartialEq, PartialOrd)]
1028pub enum GpuOpacityKeyEvent {
1029    Added(NodeId, OpacityKey, f32),
1030    Changed(NodeId, OpacityKey, f32, f32),
1031    Removed(NodeId, OpacityKey),
1032}
1033
1034#[derive(Default, Debug, Clone, PartialEq, PartialOrd)]
1035pub struct GpuEventChanges {
1036    pub transform_key_changes: Vec<GpuTransformKeyEvent>,
1037    pub opacity_key_changes: Vec<GpuOpacityKeyEvent>,
1038}
1039
1040impl GpuEventChanges {
1041    pub fn empty() -> Self {
1042        Self::default()
1043    }
1044    pub fn is_empty(&self) -> bool {
1045        self.transform_key_changes.is_empty() && self.opacity_key_changes.is_empty()
1046    }
1047    pub fn merge(&mut self, other: &mut Self) {
1048        self.transform_key_changes
1049            .extend(other.transform_key_changes.drain(..));
1050        self.opacity_key_changes
1051            .extend(other.opacity_key_changes.drain(..));
1052    }
1053}
1054
1055#[derive(Default, Debug, Clone, PartialEq, PartialOrd)]
1056pub struct RelayoutChanges {
1057    pub resized_nodes: Vec<NodeId>,
1058    pub gpu_key_changes: GpuEventChanges,
1059}
1060
1061impl RelayoutChanges {
1062    pub const EMPTY: RelayoutChanges = RelayoutChanges {
1063        resized_nodes: Vec::new(),
1064        gpu_key_changes: GpuEventChanges {
1065            transform_key_changes: Vec::new(),
1066            opacity_key_changes: Vec::new(),
1067        },
1068    };
1069
1070    pub fn empty() -> Self {
1071        Self::EMPTY.clone()
1072    }
1073}
1074
1075impl GpuValueCache {
1076    pub fn empty() -> Self {
1077        Self::default()
1078    }
1079
1080    #[must_use]
1081    pub fn synchronize<'a>(
1082        &mut self,
1083        positioned_rects: &NodeDataContainerRef<'a, PositionedRectangle>,
1084        styled_dom: &StyledDom,
1085    ) -> GpuEventChanges {
1086        let css_property_cache = styled_dom.get_css_property_cache();
1087        let node_data = styled_dom.node_data.as_container();
1088        let node_states = styled_dom.styled_nodes.as_container();
1089
1090        let default_transform_origin = StyleTransformOrigin::default();
1091
1092        #[cfg(target_arch = "x86_64")]
1093        unsafe {
1094            if !INITIALIZED.load(AtomicOrdering::SeqCst) {
1095                use core::arch::x86_64::__cpuid;
1096
1097                let mut cpuid = __cpuid(0);
1098                let n_ids = cpuid.eax;
1099
1100                if n_ids > 0 {
1101                    // cpuid instruction is present
1102                    cpuid = __cpuid(1);
1103                    USE_SSE.store((cpuid.edx & (1_u32 << 25)) != 0, AtomicOrdering::SeqCst);
1104                    USE_AVX.store((cpuid.ecx & (1_u32 << 28)) != 0, AtomicOrdering::SeqCst);
1105                }
1106                INITIALIZED.store(true, AtomicOrdering::SeqCst);
1107            }
1108        }
1109
1110        // calculate the transform values of every single node that has a non-default transform
1111        let all_current_transform_events = (0..styled_dom.node_data.len())
1112            .into_iter()
1113            .filter_map(|node_id| {
1114                let node_id = NodeId::new(node_id);
1115                let styled_node_state = &node_states[node_id].state;
1116                let node_data = &node_data[node_id];
1117                let current_transform = css_property_cache
1118                    .get_transform(node_data, &node_id, styled_node_state)?
1119                    .get_property()
1120                    .map(|t| {
1121                        let parent_size = positioned_rects[node_id].size;
1122                        let transform_origin = css_property_cache.get_transform_origin(
1123                            node_data,
1124                            &node_id,
1125                            styled_node_state,
1126                        );
1127                        let transform_origin = transform_origin
1128                            .as_ref()
1129                            .and_then(|o| o.get_property())
1130                            .unwrap_or(&default_transform_origin);
1131
1132                        ComputedTransform3D::from_style_transform_vec(
1133                            t.as_ref(),
1134                            transform_origin,
1135                            parent_size.width,
1136                            parent_size.height,
1137                            RotationMode::ForWebRender,
1138                        )
1139                    });
1140
1141                let existing_transform = self.current_transform_values.get(&node_id);
1142
1143                match (existing_transform, current_transform) {
1144                    (None, None) => None, // no new transform, no old transform
1145                    (None, Some(new)) => Some(GpuTransformKeyEvent::Added(
1146                        node_id,
1147                        TransformKey::unique(),
1148                        new,
1149                    )),
1150                    (Some(old), Some(new)) => Some(GpuTransformKeyEvent::Changed(
1151                        node_id,
1152                        self.transform_keys.get(&node_id).copied()?,
1153                        *old,
1154                        new,
1155                    )),
1156                    (Some(_old), None) => Some(GpuTransformKeyEvent::Removed(
1157                        node_id,
1158                        self.transform_keys.get(&node_id).copied()?,
1159                    )),
1160                }
1161            })
1162            .collect::<Vec<GpuTransformKeyEvent>>();
1163
1164        // remove / add the transform keys accordingly
1165        for event in all_current_transform_events.iter() {
1166            match &event {
1167                GpuTransformKeyEvent::Added(node_id, key, matrix) => {
1168                    self.transform_keys.insert(*node_id, *key);
1169                    self.current_transform_values.insert(*node_id, *matrix);
1170                }
1171                GpuTransformKeyEvent::Changed(node_id, _key, _old_state, new_state) => {
1172                    self.current_transform_values.insert(*node_id, *new_state);
1173                }
1174                GpuTransformKeyEvent::Removed(node_id, _key) => {
1175                    self.transform_keys.remove(node_id);
1176                    self.current_transform_values.remove(node_id);
1177                }
1178            }
1179        }
1180
1181        // calculate the opacity of every single node that has a non-default opacity
1182        let all_current_opacity_events = (0..styled_dom.node_data.len())
1183            .into_iter()
1184            .filter_map(|node_id| {
1185                let node_id = NodeId::new(node_id);
1186                let styled_node_state = &node_states[node_id].state;
1187                let node_data = &node_data[node_id];
1188                let current_opacity =
1189                    css_property_cache.get_opacity(node_data, &node_id, styled_node_state)?;
1190                let current_opacity = current_opacity.get_property();
1191                let existing_opacity = self.current_opacity_values.get(&node_id);
1192
1193                match (existing_opacity, current_opacity) {
1194                    (None, None) => None, // no new opacity, no old transform
1195                    (None, Some(new)) => Some(GpuOpacityKeyEvent::Added(
1196                        node_id,
1197                        OpacityKey::unique(),
1198                        new.inner.normalized(),
1199                    )),
1200                    (Some(old), Some(new)) => Some(GpuOpacityKeyEvent::Changed(
1201                        node_id,
1202                        self.opacity_keys.get(&node_id).copied()?,
1203                        *old,
1204                        new.inner.normalized(),
1205                    )),
1206                    (Some(_old), None) => Some(GpuOpacityKeyEvent::Removed(
1207                        node_id,
1208                        self.opacity_keys.get(&node_id).copied()?,
1209                    )),
1210                }
1211            })
1212            .collect::<Vec<GpuOpacityKeyEvent>>();
1213
1214        // remove / add the opacity keys accordingly
1215        for event in all_current_opacity_events.iter() {
1216            match &event {
1217                GpuOpacityKeyEvent::Added(node_id, key, opacity) => {
1218                    self.opacity_keys.insert(*node_id, *key);
1219                    self.current_opacity_values.insert(*node_id, *opacity);
1220                }
1221                GpuOpacityKeyEvent::Changed(node_id, _key, _old_state, new_state) => {
1222                    self.current_opacity_values.insert(*node_id, *new_state);
1223                }
1224                GpuOpacityKeyEvent::Removed(node_id, _key) => {
1225                    self.opacity_keys.remove(node_id);
1226                    self.current_opacity_values.remove(node_id);
1227                }
1228            }
1229        }
1230
1231        GpuEventChanges {
1232            transform_key_changes: all_current_transform_events,
1233            opacity_key_changes: all_current_opacity_events,
1234        }
1235    }
1236}
1237
1238#[derive(Debug, Clone, PartialEq, PartialOrd)]
1239pub struct HitTest {
1240    pub regular_hit_test_nodes: BTreeMap<NodeId, HitTestItem>,
1241    pub scroll_hit_test_nodes: BTreeMap<NodeId, ScrollHitTestItem>,
1242}
1243
1244impl HitTest {
1245    pub fn empty() -> Self {
1246        Self {
1247            regular_hit_test_nodes: BTreeMap::new(),
1248            scroll_hit_test_nodes: BTreeMap::new(),
1249        }
1250    }
1251    pub fn is_empty(&self) -> bool {
1252        self.regular_hit_test_nodes.is_empty() && self.scroll_hit_test_nodes.is_empty()
1253    }
1254}
1255
1256/// Layout options that can impact the flow of word positions
1257#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
1258pub struct TextLayoutOptions {
1259    /// Font size (in pixels) that this text has been laid out with
1260    pub font_size_px: PixelValue,
1261    /// Multiplier for the line height, default to 1.0
1262    pub line_height: Option<f32>,
1263    /// Additional spacing between glyphs (in pixels)
1264    pub letter_spacing: Option<PixelValue>,
1265    /// Additional spacing between words (in pixels)
1266    pub word_spacing: Option<PixelValue>,
1267    /// How many spaces should a tab character emulate
1268    /// (multiplying value, i.e. `4.0` = one tab = 4 spaces)?
1269    pub tab_width: Option<f32>,
1270    /// Maximum width of the text (in pixels) - if the text is set to `overflow:visible`, set this
1271    /// to None.
1272    pub max_horizontal_width: Option<f32>,
1273    /// How many pixels of leading does the first line have? Note that this added onto to the
1274    /// holes, so for effects like `:first-letter`, use a hole instead of a leading.
1275    pub leading: Option<f32>,
1276    /// This is more important for inline text layout where items can punch "holes"
1277    /// into the text flow, for example an image that floats to the right.
1278    ///
1279    /// TODO: Currently unused!
1280    pub holes: Vec<LayoutRect>,
1281}
1282
1283/// Same as `TextLayoutOptions`, but with the widths / heights of the `PixelValue`s
1284/// resolved to regular f32s (because `letter_spacing`, `word_spacing`, etc. may be %-based value)
1285#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
1286#[repr(C)]
1287pub struct ResolvedTextLayoutOptions {
1288    /// Font size (in pixels) that this text has been laid out with
1289    pub font_size_px: f32,
1290    /// Multiplier for the line height, default to 1.0
1291    pub line_height: OptionF32,
1292    /// Additional spacing between glyphs (in pixels)
1293    pub letter_spacing: OptionF32,
1294    /// Additional spacing between words (in pixels)
1295    pub word_spacing: OptionF32,
1296    /// How many spaces should a tab character emulate
1297    /// (multiplying value, i.e. `4.0` = one tab = 4 spaces)?
1298    pub tab_width: OptionF32,
1299    /// Maximum width of the text (in pixels) - if the text is set to `overflow:visible`, set this
1300    /// to None.
1301    pub max_horizontal_width: OptionF32,
1302    /// How many pixels of leading does the first line have? Note that this added onto to the
1303    /// holes, so for effects like `:first-letter`, use a hole instead of a leading.
1304    pub leading: OptionF32,
1305    /// This is more important for inline text layout where items can punch "holes"
1306    /// into the text flow, for example an image that floats to the right.
1307    ///
1308    /// TODO: Currently unused!
1309    pub holes: LogicalRectVec,
1310}
1311
1312impl_option!(
1313    ResolvedTextLayoutOptions,
1314    OptionResolvedTextLayoutOptions,
1315    copy = false,
1316    [Debug, Clone, PartialEq, PartialOrd]
1317);
1318
1319#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)]
1320#[repr(C)]
1321pub struct ResolvedOffsets {
1322    pub top: f32,
1323    pub left: f32,
1324    pub right: f32,
1325    pub bottom: f32,
1326}
1327
1328impl ResolvedOffsets {
1329    pub const fn zero() -> Self {
1330        Self {
1331            top: 0.0,
1332            left: 0.0,
1333            right: 0.0,
1334            bottom: 0.0,
1335        }
1336    }
1337    pub fn total_vertical(&self) -> f32 {
1338        self.top + self.bottom
1339    }
1340    pub fn total_horizontal(&self) -> f32 {
1341        self.left + self.right
1342    }
1343}
1344
1345#[derive(Debug, Clone, PartialEq, PartialOrd)]
1346pub struct PositionedRectangle {
1347    /// Outer bounds of the rectangle
1348    pub size: LogicalSize,
1349    /// How the rectangle should be positioned
1350    pub position: PositionInfo,
1351    /// Padding of the rectangle
1352    pub padding: ResolvedOffsets,
1353    /// Margin of the rectangle
1354    pub margin: ResolvedOffsets,
1355    /// Border widths of the rectangle
1356    pub border_widths: ResolvedOffsets,
1357    /// Widths of the box shadow(s), necessary to calculate clip rect
1358    pub box_shadow: StyleBoxShadowOffsets,
1359    /// Whether the borders are included in the size or not
1360    pub box_sizing: LayoutBoxSizing,
1361    /// Evaluated result of the overflow-x property
1362    pub overflow_x: LayoutOverflow,
1363    /// Evaluated result of the overflow-y property
1364    pub overflow_y: LayoutOverflow,
1365    // TODO: box_shadow_widths
1366    /// If this is an inline rectangle, resolve the %-based font sizes
1367    /// and store them here.
1368    pub resolved_text_layout_options: Option<(ResolvedTextLayoutOptions, InlineTextLayout)>,
1369}
1370
1371impl Default for PositionedRectangle {
1372    fn default() -> Self {
1373        PositionedRectangle {
1374            size: LogicalSize::zero(),
1375            overflow_x: LayoutOverflow::default(),
1376            overflow_y: LayoutOverflow::default(),
1377            position: PositionInfo::Static(PositionInfoInner {
1378                x_offset: 0.0,
1379                y_offset: 0.0,
1380                static_x_offset: 0.0,
1381                static_y_offset: 0.0,
1382            }),
1383            padding: ResolvedOffsets::zero(),
1384            margin: ResolvedOffsets::zero(),
1385            border_widths: ResolvedOffsets::zero(),
1386            box_shadow: StyleBoxShadowOffsets::default(),
1387            box_sizing: LayoutBoxSizing::default(),
1388            resolved_text_layout_options: None,
1389        }
1390    }
1391}
1392
1393impl PositionedRectangle {
1394    #[inline]
1395    pub fn get_approximate_static_bounds(&self) -> LayoutRect {
1396        LayoutRect::new(self.get_static_offset(), self.get_content_size())
1397    }
1398
1399    // Returns the rect where the content should be placed (for example the text itself)
1400    #[inline]
1401    fn get_content_size(&self) -> LayoutSize {
1402        LayoutSize::new(
1403            libm::roundf(self.size.width) as isize,
1404            libm::roundf(self.size.height) as isize,
1405        )
1406    }
1407
1408    #[inline]
1409    fn get_logical_static_offset(&self) -> LogicalPosition {
1410        match self.position {
1411            PositionInfo::Static(p)
1412            | PositionInfo::Fixed(p)
1413            | PositionInfo::Absolute(p)
1414            | PositionInfo::Relative(p) => {
1415                LogicalPosition::new(p.static_x_offset, p.static_y_offset)
1416            }
1417        }
1418    }
1419
1420    #[inline]
1421    fn get_logical_relative_offset(&self) -> LogicalPosition {
1422        match self.position {
1423            PositionInfo::Static(p)
1424            | PositionInfo::Fixed(p)
1425            | PositionInfo::Absolute(p)
1426            | PositionInfo::Relative(p) => LogicalPosition::new(p.x_offset, p.y_offset),
1427        }
1428    }
1429
1430    #[inline]
1431    fn get_static_offset(&self) -> LayoutPoint {
1432        match self.position {
1433            PositionInfo::Static(p)
1434            | PositionInfo::Fixed(p)
1435            | PositionInfo::Absolute(p)
1436            | PositionInfo::Relative(p) => LayoutPoint::new(
1437                libm::roundf(p.static_x_offset) as isize,
1438                libm::roundf(p.static_y_offset) as isize,
1439            ),
1440        }
1441    }
1442
1443    // Returns the rect that includes bounds, expanded by the padding + the border widths
1444    #[inline]
1445    pub fn get_background_bounds(&self) -> (LogicalSize, PositionInfo) {
1446        use crate::ui_solver::PositionInfo::*;
1447
1448        let b_size = LogicalSize {
1449            width: self.size.width
1450                + self.padding.total_horizontal()
1451                + self.border_widths.total_horizontal(),
1452            height: self.size.height
1453                + self.padding.total_vertical()
1454                + self.border_widths.total_vertical(),
1455        };
1456
1457        let x_offset_add = 0.0 - self.padding.left - self.border_widths.left;
1458        let y_offset_add = 0.0 - self.padding.top - self.border_widths.top;
1459
1460        let b_position = match self.position {
1461            Static(PositionInfoInner {
1462                x_offset,
1463                y_offset,
1464                static_x_offset,
1465                static_y_offset,
1466            }) => Static(PositionInfoInner {
1467                x_offset: x_offset + x_offset_add,
1468                y_offset: y_offset + y_offset_add,
1469                static_x_offset,
1470                static_y_offset,
1471            }),
1472            Fixed(PositionInfoInner {
1473                x_offset,
1474                y_offset,
1475                static_x_offset,
1476                static_y_offset,
1477            }) => Fixed(PositionInfoInner {
1478                x_offset: x_offset + x_offset_add,
1479                y_offset: y_offset + y_offset_add,
1480                static_x_offset,
1481                static_y_offset,
1482            }),
1483            Relative(PositionInfoInner {
1484                x_offset,
1485                y_offset,
1486                static_x_offset,
1487                static_y_offset,
1488            }) => Relative(PositionInfoInner {
1489                x_offset: x_offset + x_offset_add,
1490                y_offset: y_offset + y_offset_add,
1491                static_x_offset,
1492                static_y_offset,
1493            }),
1494            Absolute(PositionInfoInner {
1495                x_offset,
1496                y_offset,
1497                static_x_offset,
1498                static_y_offset,
1499            }) => Absolute(PositionInfoInner {
1500                x_offset: x_offset + x_offset_add,
1501                y_offset: y_offset + y_offset_add,
1502                static_x_offset,
1503                static_y_offset,
1504            }),
1505        };
1506
1507        (b_size, b_position)
1508    }
1509
1510    #[inline]
1511    pub fn get_margin_box_width(&self) -> f32 {
1512        self.size.width
1513            + self.padding.total_horizontal()
1514            + self.border_widths.total_horizontal()
1515            + self.margin.total_horizontal()
1516    }
1517
1518    #[inline]
1519    pub fn get_margin_box_height(&self) -> f32 {
1520        self.size.height
1521            + self.padding.total_vertical()
1522            + self.border_widths.total_vertical()
1523            + self.margin.total_vertical()
1524    }
1525
1526    #[inline]
1527    pub fn get_left_leading(&self) -> f32 {
1528        self.margin.left + self.padding.left + self.border_widths.left
1529    }
1530
1531    #[inline]
1532    pub fn get_top_leading(&self) -> f32 {
1533        self.margin.top + self.padding.top + self.border_widths.top
1534    }
1535}
1536
1537#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
1538pub struct OverflowInfo {
1539    pub overflow_x: DirectionalOverflowInfo,
1540    pub overflow_y: DirectionalOverflowInfo,
1541}
1542
1543// stores how much the children overflow the parent in the given direction
1544// if amount is negative, the children do not overflow the parent
1545// if the amount is set to None, that means there are no children for this node, so no overflow can
1546// be calculated
1547#[derive(Debug, Clone, PartialEq, PartialOrd)]
1548pub enum DirectionalOverflowInfo {
1549    Scroll { amount: Option<isize> },
1550    Auto { amount: Option<isize> },
1551    Hidden { amount: Option<isize> },
1552    Visible { amount: Option<isize> },
1553}
1554
1555impl Default for DirectionalOverflowInfo {
1556    fn default() -> DirectionalOverflowInfo {
1557        DirectionalOverflowInfo::Auto { amount: None }
1558    }
1559}
1560
1561impl DirectionalOverflowInfo {
1562    #[inline]
1563    pub fn get_amount(&self) -> Option<isize> {
1564        match self {
1565            DirectionalOverflowInfo::Scroll { amount: Some(s) }
1566            | DirectionalOverflowInfo::Auto { amount: Some(s) }
1567            | DirectionalOverflowInfo::Hidden { amount: Some(s) }
1568            | DirectionalOverflowInfo::Visible { amount: Some(s) } => Some(*s),
1569            _ => None,
1570        }
1571    }
1572
1573    #[inline]
1574    pub fn is_negative(&self) -> bool {
1575        match self {
1576            DirectionalOverflowInfo::Scroll { amount: Some(s) }
1577            | DirectionalOverflowInfo::Auto { amount: Some(s) }
1578            | DirectionalOverflowInfo::Hidden { amount: Some(s) }
1579            | DirectionalOverflowInfo::Visible { amount: Some(s) } => *s < 0_isize,
1580            _ => true, // no overflow = no scrollbar
1581        }
1582    }
1583
1584    #[inline]
1585    pub fn is_none(&self) -> bool {
1586        match self {
1587            DirectionalOverflowInfo::Scroll { amount: None }
1588            | DirectionalOverflowInfo::Auto { amount: None }
1589            | DirectionalOverflowInfo::Hidden { amount: None }
1590            | DirectionalOverflowInfo::Visible { amount: None } => true,
1591            _ => false,
1592        }
1593    }
1594}
1595
1596#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1597#[repr(C, u8)]
1598pub enum PositionInfo {
1599    Static(PositionInfoInner),
1600    Fixed(PositionInfoInner),
1601    Absolute(PositionInfoInner),
1602    Relative(PositionInfoInner),
1603}
1604
1605impl PositionInfo {
1606    /// Shift this node vertically by `offset_amount`.
1607    /// i.e. add `offset_amount` to both the relative and static y-offsets.
1608    pub fn translate_vertical(&mut self, offset_amount: f32) {
1609        match self {
1610            PositionInfo::Static(ref mut info)
1611            | PositionInfo::Absolute(ref mut info)
1612            | PositionInfo::Fixed(ref mut info)
1613            | PositionInfo::Relative(ref mut info) => {
1614                info.y_offset += offset_amount;
1615                info.static_y_offset += offset_amount;
1616            }
1617        }
1618    }
1619
1620    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
1621        match self {
1622            PositionInfo::Static(p) => p.scale_for_dpi(scale_factor),
1623            PositionInfo::Fixed(p) => p.scale_for_dpi(scale_factor),
1624            PositionInfo::Absolute(p) => p.scale_for_dpi(scale_factor),
1625            PositionInfo::Relative(p) => p.scale_for_dpi(scale_factor),
1626        }
1627    }
1628}
1629
1630impl_option!(
1631    PositionInfo,
1632    OptionPositionInfo,
1633    [Debug, Copy, Clone, PartialEq, PartialOrd]
1634);
1635
1636#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1637#[repr(C)]
1638pub struct PositionInfoInner {
1639    pub x_offset: f32,
1640    pub y_offset: f32,
1641    pub static_x_offset: f32,
1642    pub static_y_offset: f32,
1643}
1644
1645impl PositionInfoInner {
1646    #[inline]
1647    pub const fn zero() -> Self {
1648        Self {
1649            x_offset: 0.0,
1650            y_offset: 0.0,
1651            static_x_offset: 0.0,
1652            static_y_offset: 0.0,
1653        }
1654    }
1655
1656    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
1657        self.x_offset *= scale_factor;
1658        self.y_offset *= scale_factor;
1659        self.static_x_offset *= scale_factor;
1660        self.static_y_offset *= scale_factor;
1661    }
1662}
1663
1664impl PositionInfo {
1665    #[inline]
1666    pub fn is_positioned(&self) -> bool {
1667        match self {
1668            PositionInfo::Static(_) => false,
1669            PositionInfo::Fixed(_) => true,
1670            PositionInfo::Absolute(_) => true,
1671            PositionInfo::Relative(_) => true,
1672        }
1673    }
1674
1675    #[inline]
1676    pub fn get_relative_offset(&self) -> LogicalPosition {
1677        match self {
1678            PositionInfo::Static(p)
1679            | PositionInfo::Fixed(p)
1680            | PositionInfo::Absolute(p)
1681            | PositionInfo::Relative(p) => LogicalPosition {
1682                x: p.x_offset,
1683                y: p.y_offset,
1684            },
1685        }
1686    }
1687
1688    #[inline]
1689    pub fn get_static_offset(&self) -> LogicalPosition {
1690        match self {
1691            PositionInfo::Static(p)
1692            | PositionInfo::Fixed(p)
1693            | PositionInfo::Absolute(p)
1694            | PositionInfo::Relative(p) => LogicalPosition {
1695                x: p.static_x_offset,
1696                y: p.static_y_offset,
1697            },
1698        }
1699    }
1700}
1701
1702#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)]
1703pub struct StyleBoxShadowOffsets {
1704    pub left: Option<CssPropertyValue<StyleBoxShadow>>,
1705    pub right: Option<CssPropertyValue<StyleBoxShadow>>,
1706    pub top: Option<CssPropertyValue<StyleBoxShadow>>,
1707    pub bottom: Option<CssPropertyValue<StyleBoxShadow>>,
1708}
1709
1710/// For some reason the rotation matrix for webrender is inverted:
1711/// When rendering, the matrix turns the rectangle counter-clockwise
1712/// direction instead of clockwise.
1713///
1714/// This is technically a workaround, but it's necessary so that
1715/// rotation works properly
1716#[derive(Debug, Copy, Clone)]
1717pub enum RotationMode {
1718    ForWebRender,
1719    ForHitTesting,
1720}
1721
1722/// Computed transform of pixels in pixel space
1723///
1724/// NOTE: Matrix is row-major, not column-major
1725#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
1726#[repr(C)]
1727pub struct ComputedTransform3D {
1728    pub m: [[f32; 4]; 4],
1729}
1730
1731impl ComputedTransform3D {
1732    pub const IDENTITY: Self = Self {
1733        m: [
1734            [1.0, 0.0, 0.0, 0.0],
1735            [0.0, 1.0, 0.0, 0.0],
1736            [0.0, 0.0, 1.0, 0.0],
1737            [0.0, 0.0, 0.0, 1.0],
1738        ],
1739    };
1740
1741    pub const fn new(
1742        m11: f32,
1743        m12: f32,
1744        m13: f32,
1745        m14: f32,
1746        m21: f32,
1747        m22: f32,
1748        m23: f32,
1749        m24: f32,
1750        m31: f32,
1751        m32: f32,
1752        m33: f32,
1753        m34: f32,
1754        m41: f32,
1755        m42: f32,
1756        m43: f32,
1757        m44: f32,
1758    ) -> Self {
1759        Self {
1760            m: [
1761                [m11, m12, m13, m14],
1762                [m21, m22, m23, m24],
1763                [m31, m32, m33, m34],
1764                [m41, m42, m43, m44],
1765            ],
1766        }
1767    }
1768
1769    pub const fn new_2d(m11: f32, m12: f32, m21: f32, m22: f32, m41: f32, m42: f32) -> Self {
1770        Self::new(
1771            m11, m12, 0.0, 0.0, m21, m22, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, m41, m42, 0.0, 1.0,
1772        )
1773    }
1774
1775    // very slow inverse function
1776    pub fn inverse(&self) -> Self {
1777        let det = self.determinant();
1778
1779        // if det == 0.0 { return None; }
1780
1781        let m = ComputedTransform3D::new(
1782            self.m[1][2] * self.m[2][3] * self.m[3][1] - self.m[1][3] * self.m[2][2] * self.m[3][1]
1783                + self.m[1][3] * self.m[2][1] * self.m[3][2]
1784                - self.m[1][1] * self.m[2][3] * self.m[3][2]
1785                - self.m[1][2] * self.m[2][1] * self.m[3][3]
1786                + self.m[1][1] * self.m[2][2] * self.m[3][3],
1787            self.m[0][3] * self.m[2][2] * self.m[3][1]
1788                - self.m[0][2] * self.m[2][3] * self.m[3][1]
1789                - self.m[0][3] * self.m[2][1] * self.m[3][2]
1790                + self.m[0][1] * self.m[2][3] * self.m[3][2]
1791                + self.m[0][2] * self.m[2][1] * self.m[3][3]
1792                - self.m[0][1] * self.m[2][2] * self.m[3][3],
1793            self.m[0][2] * self.m[1][3] * self.m[3][1] - self.m[0][3] * self.m[1][2] * self.m[3][1]
1794                + self.m[0][3] * self.m[1][1] * self.m[3][2]
1795                - self.m[0][1] * self.m[1][3] * self.m[3][2]
1796                - self.m[0][2] * self.m[1][1] * self.m[3][3]
1797                + self.m[0][1] * self.m[1][2] * self.m[3][3],
1798            self.m[0][3] * self.m[1][2] * self.m[2][1]
1799                - self.m[0][2] * self.m[1][3] * self.m[2][1]
1800                - self.m[0][3] * self.m[1][1] * self.m[2][2]
1801                + self.m[0][1] * self.m[1][3] * self.m[2][2]
1802                + self.m[0][2] * self.m[1][1] * self.m[2][3]
1803                - self.m[0][1] * self.m[1][2] * self.m[2][3],
1804            self.m[1][3] * self.m[2][2] * self.m[3][0]
1805                - self.m[1][2] * self.m[2][3] * self.m[3][0]
1806                - self.m[1][3] * self.m[2][0] * self.m[3][2]
1807                + self.m[1][0] * self.m[2][3] * self.m[3][2]
1808                + self.m[1][2] * self.m[2][0] * self.m[3][3]
1809                - self.m[1][0] * self.m[2][2] * self.m[3][3],
1810            self.m[0][2] * self.m[2][3] * self.m[3][0] - self.m[0][3] * self.m[2][2] * self.m[3][0]
1811                + self.m[0][3] * self.m[2][0] * self.m[3][2]
1812                - self.m[0][0] * self.m[2][3] * self.m[3][2]
1813                - self.m[0][2] * self.m[2][0] * self.m[3][3]
1814                + self.m[0][0] * self.m[2][2] * self.m[3][3],
1815            self.m[0][3] * self.m[1][2] * self.m[3][0]
1816                - self.m[0][2] * self.m[1][3] * self.m[3][0]
1817                - self.m[0][3] * self.m[1][0] * self.m[3][2]
1818                + self.m[0][0] * self.m[1][3] * self.m[3][2]
1819                + self.m[0][2] * self.m[1][0] * self.m[3][3]
1820                - self.m[0][0] * self.m[1][2] * self.m[3][3],
1821            self.m[0][2] * self.m[1][3] * self.m[2][0] - self.m[0][3] * self.m[1][2] * self.m[2][0]
1822                + self.m[0][3] * self.m[1][0] * self.m[2][2]
1823                - self.m[0][0] * self.m[1][3] * self.m[2][2]
1824                - self.m[0][2] * self.m[1][0] * self.m[2][3]
1825                + self.m[0][0] * self.m[1][2] * self.m[2][3],
1826            self.m[1][1] * self.m[2][3] * self.m[3][0] - self.m[1][3] * self.m[2][1] * self.m[3][0]
1827                + self.m[1][3] * self.m[2][0] * self.m[3][1]
1828                - self.m[1][0] * self.m[2][3] * self.m[3][1]
1829                - self.m[1][1] * self.m[2][0] * self.m[3][3]
1830                + self.m[1][0] * self.m[2][1] * self.m[3][3],
1831            self.m[0][3] * self.m[2][1] * self.m[3][0]
1832                - self.m[0][1] * self.m[2][3] * self.m[3][0]
1833                - self.m[0][3] * self.m[2][0] * self.m[3][1]
1834                + self.m[0][0] * self.m[2][3] * self.m[3][1]
1835                + self.m[0][1] * self.m[2][0] * self.m[3][3]
1836                - self.m[0][0] * self.m[2][1] * self.m[3][3],
1837            self.m[0][1] * self.m[1][3] * self.m[3][0] - self.m[0][3] * self.m[1][1] * self.m[3][0]
1838                + self.m[0][3] * self.m[1][0] * self.m[3][1]
1839                - self.m[0][0] * self.m[1][3] * self.m[3][1]
1840                - self.m[0][1] * self.m[1][0] * self.m[3][3]
1841                + self.m[0][0] * self.m[1][1] * self.m[3][3],
1842            self.m[0][3] * self.m[1][1] * self.m[2][0]
1843                - self.m[0][1] * self.m[1][3] * self.m[2][0]
1844                - self.m[0][3] * self.m[1][0] * self.m[2][1]
1845                + self.m[0][0] * self.m[1][3] * self.m[2][1]
1846                + self.m[0][1] * self.m[1][0] * self.m[2][3]
1847                - self.m[0][0] * self.m[1][1] * self.m[2][3],
1848            self.m[1][2] * self.m[2][1] * self.m[3][0]
1849                - self.m[1][1] * self.m[2][2] * self.m[3][0]
1850                - self.m[1][2] * self.m[2][0] * self.m[3][1]
1851                + self.m[1][0] * self.m[2][2] * self.m[3][1]
1852                + self.m[1][1] * self.m[2][0] * self.m[3][2]
1853                - self.m[1][0] * self.m[2][1] * self.m[3][2],
1854            self.m[0][1] * self.m[2][2] * self.m[3][0] - self.m[0][2] * self.m[2][1] * self.m[3][0]
1855                + self.m[0][2] * self.m[2][0] * self.m[3][1]
1856                - self.m[0][0] * self.m[2][2] * self.m[3][1]
1857                - self.m[0][1] * self.m[2][0] * self.m[3][2]
1858                + self.m[0][0] * self.m[2][1] * self.m[3][2],
1859            self.m[0][2] * self.m[1][1] * self.m[3][0]
1860                - self.m[0][1] * self.m[1][2] * self.m[3][0]
1861                - self.m[0][2] * self.m[1][0] * self.m[3][1]
1862                + self.m[0][0] * self.m[1][2] * self.m[3][1]
1863                + self.m[0][1] * self.m[1][0] * self.m[3][2]
1864                - self.m[0][0] * self.m[1][1] * self.m[3][2],
1865            self.m[0][1] * self.m[1][2] * self.m[2][0] - self.m[0][2] * self.m[1][1] * self.m[2][0]
1866                + self.m[0][2] * self.m[1][0] * self.m[2][1]
1867                - self.m[0][0] * self.m[1][2] * self.m[2][1]
1868                - self.m[0][1] * self.m[1][0] * self.m[2][2]
1869                + self.m[0][0] * self.m[1][1] * self.m[2][2],
1870        );
1871
1872        m.multiply_scalar(1.0 / det)
1873    }
1874
1875    fn determinant(&self) -> f32 {
1876        self.m[0][3] * self.m[1][2] * self.m[2][1] * self.m[3][0]
1877            - self.m[0][2] * self.m[1][3] * self.m[2][1] * self.m[3][0]
1878            - self.m[0][3] * self.m[1][1] * self.m[2][2] * self.m[3][0]
1879            + self.m[0][1] * self.m[1][3] * self.m[2][2] * self.m[3][0]
1880            + self.m[0][2] * self.m[1][1] * self.m[2][3] * self.m[3][0]
1881            - self.m[0][1] * self.m[1][2] * self.m[2][3] * self.m[3][0]
1882            - self.m[0][3] * self.m[1][2] * self.m[2][0] * self.m[3][1]
1883            + self.m[0][2] * self.m[1][3] * self.m[2][0] * self.m[3][1]
1884            + self.m[0][3] * self.m[1][0] * self.m[2][2] * self.m[3][1]
1885            - self.m[0][0] * self.m[1][3] * self.m[2][2] * self.m[3][1]
1886            - self.m[0][2] * self.m[1][0] * self.m[2][3] * self.m[3][1]
1887            + self.m[0][0] * self.m[1][2] * self.m[2][3] * self.m[3][1]
1888            + self.m[0][3] * self.m[1][1] * self.m[2][0] * self.m[3][2]
1889            - self.m[0][1] * self.m[1][3] * self.m[2][0] * self.m[3][2]
1890            - self.m[0][3] * self.m[1][0] * self.m[2][1] * self.m[3][2]
1891            + self.m[0][0] * self.m[1][3] * self.m[2][1] * self.m[3][2]
1892            + self.m[0][1] * self.m[1][0] * self.m[2][3] * self.m[3][2]
1893            - self.m[0][0] * self.m[1][1] * self.m[2][3] * self.m[3][2]
1894            - self.m[0][2] * self.m[1][1] * self.m[2][0] * self.m[3][3]
1895            + self.m[0][1] * self.m[1][2] * self.m[2][0] * self.m[3][3]
1896            + self.m[0][2] * self.m[1][0] * self.m[2][1] * self.m[3][3]
1897            - self.m[0][0] * self.m[1][2] * self.m[2][1] * self.m[3][3]
1898            - self.m[0][1] * self.m[1][0] * self.m[2][2] * self.m[3][3]
1899            + self.m[0][0] * self.m[1][1] * self.m[2][2] * self.m[3][3]
1900    }
1901
1902    fn multiply_scalar(&self, x: f32) -> Self {
1903        ComputedTransform3D::new(
1904            self.m[0][0] * x,
1905            self.m[0][1] * x,
1906            self.m[0][2] * x,
1907            self.m[0][3] * x,
1908            self.m[1][0] * x,
1909            self.m[1][1] * x,
1910            self.m[1][2] * x,
1911            self.m[1][3] * x,
1912            self.m[2][0] * x,
1913            self.m[2][1] * x,
1914            self.m[2][2] * x,
1915            self.m[2][3] * x,
1916            self.m[3][0] * x,
1917            self.m[3][1] * x,
1918            self.m[3][2] * x,
1919            self.m[3][3] * x,
1920        )
1921    }
1922
1923    // Computes the matrix of a rect from a Vec<StyleTransform>
1924    pub fn from_style_transform_vec(
1925        t_vec: &[StyleTransform],
1926        transform_origin: &StyleTransformOrigin,
1927        percent_resolve_x: f32,
1928        percent_resolve_y: f32,
1929        rotation_mode: RotationMode,
1930    ) -> Self {
1931        // TODO: use correct SIMD optimization!
1932        let mut matrix = Self::IDENTITY;
1933        let use_avx =
1934            INITIALIZED.load(AtomicOrdering::SeqCst) && USE_AVX.load(AtomicOrdering::SeqCst);
1935        let use_sse = !use_avx
1936            && INITIALIZED.load(AtomicOrdering::SeqCst)
1937            && USE_SSE.load(AtomicOrdering::SeqCst);
1938
1939        if use_avx {
1940            for t in t_vec.iter() {
1941                #[cfg(target_arch = "x86_64")]
1942                unsafe {
1943                    matrix = matrix.then_avx8(&Self::from_style_transform(
1944                        t,
1945                        transform_origin,
1946                        percent_resolve_x,
1947                        percent_resolve_y,
1948                        rotation_mode,
1949                    ));
1950                }
1951            }
1952        } else if use_sse {
1953            for t in t_vec.iter() {
1954                #[cfg(target_arch = "x86_64")]
1955                unsafe {
1956                    matrix = matrix.then_sse(&Self::from_style_transform(
1957                        t,
1958                        transform_origin,
1959                        percent_resolve_x,
1960                        percent_resolve_y,
1961                        rotation_mode,
1962                    ));
1963                }
1964            }
1965        } else {
1966            // fallback for everything else
1967            for t in t_vec.iter() {
1968                matrix = matrix.then(&Self::from_style_transform(
1969                    t,
1970                    transform_origin,
1971                    percent_resolve_x,
1972                    percent_resolve_y,
1973                    rotation_mode,
1974                ));
1975            }
1976        }
1977
1978        matrix
1979    }
1980
1981    /// Creates a new transform from a style transform using the
1982    /// parent width as a way to resolve for percentages
1983    pub fn from_style_transform(
1984        t: &StyleTransform,
1985        transform_origin: &StyleTransformOrigin,
1986        percent_resolve_x: f32,
1987        percent_resolve_y: f32,
1988        rotation_mode: RotationMode,
1989    ) -> Self {
1990        use azul_css::StyleTransform::*;
1991        match t {
1992            Matrix(mat2d) => {
1993                let a = mat2d.a.to_pixels(percent_resolve_x);
1994                let b = mat2d.b.to_pixels(percent_resolve_x);
1995                let c = mat2d.c.to_pixels(percent_resolve_x);
1996                let d = mat2d.d.to_pixels(percent_resolve_x);
1997                let tx = mat2d.tx.to_pixels(percent_resolve_x);
1998                let ty = mat2d.ty.to_pixels(percent_resolve_x);
1999
2000                Self::new_2d(a, b, c, d, tx, ty)
2001            }
2002            Matrix3D(mat3d) => {
2003                let m11 = mat3d.m11.to_pixels(percent_resolve_x);
2004                let m12 = mat3d.m12.to_pixels(percent_resolve_x);
2005                let m13 = mat3d.m13.to_pixels(percent_resolve_x);
2006                let m14 = mat3d.m14.to_pixels(percent_resolve_x);
2007                let m21 = mat3d.m21.to_pixels(percent_resolve_x);
2008                let m22 = mat3d.m22.to_pixels(percent_resolve_x);
2009                let m23 = mat3d.m23.to_pixels(percent_resolve_x);
2010                let m24 = mat3d.m24.to_pixels(percent_resolve_x);
2011                let m31 = mat3d.m31.to_pixels(percent_resolve_x);
2012                let m32 = mat3d.m32.to_pixels(percent_resolve_x);
2013                let m33 = mat3d.m33.to_pixels(percent_resolve_x);
2014                let m34 = mat3d.m34.to_pixels(percent_resolve_x);
2015                let m41 = mat3d.m41.to_pixels(percent_resolve_x);
2016                let m42 = mat3d.m42.to_pixels(percent_resolve_x);
2017                let m43 = mat3d.m43.to_pixels(percent_resolve_x);
2018                let m44 = mat3d.m44.to_pixels(percent_resolve_x);
2019
2020                Self::new(
2021                    m11, m12, m13, m14, m21, m22, m23, m24, m31, m32, m33, m34, m41, m42, m43, m44,
2022                )
2023            }
2024            Translate(trans2d) => Self::new_translation(
2025                trans2d.x.to_pixels(percent_resolve_x),
2026                trans2d.y.to_pixels(percent_resolve_y),
2027                0.0,
2028            ),
2029            Translate3D(trans3d) => Self::new_translation(
2030                trans3d.x.to_pixels(percent_resolve_x),
2031                trans3d.y.to_pixels(percent_resolve_y),
2032                trans3d.z.to_pixels(percent_resolve_x), // ???
2033            ),
2034            TranslateX(trans_x) => {
2035                Self::new_translation(trans_x.to_pixels(percent_resolve_x), 0.0, 0.0)
2036            }
2037            TranslateY(trans_y) => {
2038                Self::new_translation(0.0, trans_y.to_pixels(percent_resolve_y), 0.0)
2039            }
2040            TranslateZ(trans_z) => {
2041                Self::new_translation(0.0, 0.0, trans_z.to_pixels(percent_resolve_x))
2042            } // ???
2043            Rotate3D(rot3d) => {
2044                let rotation_origin = (
2045                    transform_origin.x.to_pixels(percent_resolve_x),
2046                    transform_origin.y.to_pixels(percent_resolve_y),
2047                );
2048                Self::make_rotation(
2049                    rotation_origin,
2050                    rot3d.angle.to_degrees(),
2051                    rot3d.x.normalized(),
2052                    rot3d.y.normalized(),
2053                    rot3d.z.normalized(),
2054                    rotation_mode,
2055                )
2056            }
2057            RotateX(angle_x) => {
2058                let rotation_origin = (
2059                    transform_origin.x.to_pixels(percent_resolve_x),
2060                    transform_origin.y.to_pixels(percent_resolve_y),
2061                );
2062                Self::make_rotation(
2063                    rotation_origin,
2064                    angle_x.to_degrees(),
2065                    1.0,
2066                    0.0,
2067                    0.0,
2068                    rotation_mode,
2069                )
2070            }
2071            RotateY(angle_y) => {
2072                let rotation_origin = (
2073                    transform_origin.x.to_pixels(percent_resolve_x),
2074                    transform_origin.y.to_pixels(percent_resolve_y),
2075                );
2076                Self::make_rotation(
2077                    rotation_origin,
2078                    angle_y.to_degrees(),
2079                    0.0,
2080                    1.0,
2081                    0.0,
2082                    rotation_mode,
2083                )
2084            }
2085            Rotate(angle_z) | RotateZ(angle_z) => {
2086                let rotation_origin = (
2087                    transform_origin.x.to_pixels(percent_resolve_x),
2088                    transform_origin.y.to_pixels(percent_resolve_y),
2089                );
2090                Self::make_rotation(
2091                    rotation_origin,
2092                    angle_z.to_degrees(),
2093                    0.0,
2094                    0.0,
2095                    1.0,
2096                    rotation_mode,
2097                )
2098            }
2099            Scale(scale2d) => Self::new_scale(scale2d.x.normalized(), scale2d.y.normalized(), 1.0),
2100            Scale3D(scale3d) => Self::new_scale(
2101                scale3d.x.normalized(),
2102                scale3d.y.normalized(),
2103                scale3d.z.normalized(),
2104            ),
2105            ScaleX(scale_x) => Self::new_scale(scale_x.normalized(), 1.0, 1.0),
2106            ScaleY(scale_y) => Self::new_scale(1.0, scale_y.normalized(), 1.0),
2107            ScaleZ(scale_z) => Self::new_scale(1.0, 1.0, scale_z.normalized()),
2108            Skew(skew2d) => Self::new_skew(skew2d.x.normalized(), skew2d.y.normalized()),
2109            SkewX(skew_x) => Self::new_skew(skew_x.normalized(), 0.0),
2110            SkewY(skew_y) => Self::new_skew(0.0, skew_y.normalized()),
2111            Perspective(px) => Self::new_perspective(px.to_pixels(percent_resolve_x)),
2112        }
2113    }
2114
2115    #[inline]
2116    pub const fn new_scale(x: f32, y: f32, z: f32) -> Self {
2117        Self::new(
2118            x, 0.0, 0.0, 0.0, 0.0, y, 0.0, 0.0, 0.0, 0.0, z, 0.0, 0.0, 0.0, 0.0, 1.0,
2119        )
2120    }
2121
2122    #[inline]
2123    pub const fn new_translation(x: f32, y: f32, z: f32) -> Self {
2124        Self::new(
2125            1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, x, y, z, 1.0,
2126        )
2127    }
2128
2129    #[inline]
2130    pub fn new_perspective(d: f32) -> Self {
2131        Self::new(
2132            1.0,
2133            0.0,
2134            0.0,
2135            0.0,
2136            0.0,
2137            1.0,
2138            0.0,
2139            0.0,
2140            0.0,
2141            0.0,
2142            1.0,
2143            -1.0 / d,
2144            0.0,
2145            0.0,
2146            0.0,
2147            1.0,
2148        )
2149    }
2150
2151    /// Create a 3d rotation transform from an angle / axis.
2152    /// The supplied axis must be normalized.
2153    #[inline]
2154    pub fn new_rotation(x: f32, y: f32, z: f32, theta_radians: f32) -> Self {
2155        let xx = x * x;
2156        let yy = y * y;
2157        let zz = z * z;
2158
2159        let half_theta = theta_radians / 2.0;
2160        let sc = half_theta.sin() * half_theta.cos();
2161        let sq = half_theta.sin() * half_theta.sin();
2162
2163        Self::new(
2164            1.0 - 2.0 * (yy + zz) * sq,
2165            2.0 * (x * y * sq + z * sc),
2166            2.0 * (x * z * sq - y * sc),
2167            0.0,
2168            2.0 * (x * y * sq - z * sc),
2169            1.0 - 2.0 * (xx + zz) * sq,
2170            2.0 * (y * z * sq + x * sc),
2171            0.0,
2172            2.0 * (x * z * sq + y * sc),
2173            2.0 * (y * z * sq - x * sc),
2174            1.0 - 2.0 * (xx + yy) * sq,
2175            0.0,
2176            0.0,
2177            0.0,
2178            0.0,
2179            1.0,
2180        )
2181    }
2182
2183    #[inline]
2184    pub fn new_skew(alpha: f32, beta: f32) -> Self {
2185        let (sx, sy) = (beta.to_radians().tan(), alpha.to_radians().tan());
2186        Self::new(
2187            1.0, sx, 0.0, 0.0, sy, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
2188        )
2189    }
2190
2191    pub fn get_column_major(&self) -> Self {
2192        ComputedTransform3D::new(
2193            self.m[0][0],
2194            self.m[1][0],
2195            self.m[2][0],
2196            self.m[3][0],
2197            self.m[0][1],
2198            self.m[1][1],
2199            self.m[2][1],
2200            self.m[3][1],
2201            self.m[0][2],
2202            self.m[1][2],
2203            self.m[2][2],
2204            self.m[3][2],
2205            self.m[0][3],
2206            self.m[1][3],
2207            self.m[2][3],
2208            self.m[3][3],
2209        )
2210    }
2211
2212    // Transforms a 2D point into the target coordinate space
2213    #[must_use]
2214    pub fn transform_point2d(&self, p: LogicalPosition) -> Option<LogicalPosition> {
2215        let w =
2216            p.x.mul_add(self.m[0][3], p.y.mul_add(self.m[1][3], self.m[3][3]));
2217
2218        if !w.is_sign_positive() {
2219            return None;
2220        }
2221
2222        let x =
2223            p.x.mul_add(self.m[0][0], p.y.mul_add(self.m[1][0], self.m[3][0]));
2224        let y =
2225            p.x.mul_add(self.m[0][1], p.y.mul_add(self.m[1][1], self.m[3][1]));
2226
2227        Some(LogicalPosition { x: x / w, y: y / w })
2228    }
2229
2230    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
2231        // only scale the translation, don't scale anything else
2232        self.m[3][0] *= scale_factor;
2233        self.m[3][1] *= scale_factor;
2234        self.m[3][2] *= scale_factor;
2235    }
2236
2237    /// Computes the sum of two matrices while applying `other` AFTER the current matrix.
2238    #[must_use]
2239    #[inline]
2240    pub fn then(&self, other: &Self) -> Self {
2241        Self::new(
2242            self.m[0][0].mul_add(
2243                other.m[0][0],
2244                self.m[0][1].mul_add(
2245                    other.m[1][0],
2246                    self.m[0][2].mul_add(other.m[2][0], self.m[0][3] * other.m[3][0]),
2247                ),
2248            ),
2249            self.m[0][0].mul_add(
2250                other.m[0][1],
2251                self.m[0][1].mul_add(
2252                    other.m[1][1],
2253                    self.m[0][2].mul_add(other.m[2][1], self.m[0][3] * other.m[3][1]),
2254                ),
2255            ),
2256            self.m[0][0].mul_add(
2257                other.m[0][2],
2258                self.m[0][1].mul_add(
2259                    other.m[1][2],
2260                    self.m[0][2].mul_add(other.m[2][2], self.m[0][3] * other.m[3][2]),
2261                ),
2262            ),
2263            self.m[0][0].mul_add(
2264                other.m[0][3],
2265                self.m[0][1].mul_add(
2266                    other.m[1][3],
2267                    self.m[0][2].mul_add(other.m[2][3], self.m[0][3] * other.m[3][3]),
2268                ),
2269            ),
2270            self.m[1][0].mul_add(
2271                other.m[0][0],
2272                self.m[1][1].mul_add(
2273                    other.m[1][0],
2274                    self.m[1][2].mul_add(other.m[2][0], self.m[1][3] * other.m[3][0]),
2275                ),
2276            ),
2277            self.m[1][0].mul_add(
2278                other.m[0][1],
2279                self.m[1][1].mul_add(
2280                    other.m[1][1],
2281                    self.m[1][2].mul_add(other.m[2][1], self.m[1][3] * other.m[3][1]),
2282                ),
2283            ),
2284            self.m[1][0].mul_add(
2285                other.m[0][2],
2286                self.m[1][1].mul_add(
2287                    other.m[1][2],
2288                    self.m[1][2].mul_add(other.m[2][2], self.m[1][3] * other.m[3][2]),
2289                ),
2290            ),
2291            self.m[1][0].mul_add(
2292                other.m[0][3],
2293                self.m[1][1].mul_add(
2294                    other.m[1][3],
2295                    self.m[1][2].mul_add(other.m[2][3], self.m[1][3] * other.m[3][3]),
2296                ),
2297            ),
2298            self.m[2][0].mul_add(
2299                other.m[0][0],
2300                self.m[2][1].mul_add(
2301                    other.m[1][0],
2302                    self.m[2][2].mul_add(other.m[2][0], self.m[2][3] * other.m[3][0]),
2303                ),
2304            ),
2305            self.m[2][0].mul_add(
2306                other.m[0][1],
2307                self.m[2][1].mul_add(
2308                    other.m[1][1],
2309                    self.m[2][2].mul_add(other.m[2][1], self.m[2][3] * other.m[3][1]),
2310                ),
2311            ),
2312            self.m[2][0].mul_add(
2313                other.m[0][2],
2314                self.m[2][1].mul_add(
2315                    other.m[1][2],
2316                    self.m[2][2].mul_add(other.m[2][2], self.m[2][3] * other.m[3][2]),
2317                ),
2318            ),
2319            self.m[2][0].mul_add(
2320                other.m[0][3],
2321                self.m[2][1].mul_add(
2322                    other.m[1][3],
2323                    self.m[2][2].mul_add(other.m[2][3], self.m[2][3] * other.m[3][3]),
2324                ),
2325            ),
2326            self.m[3][0].mul_add(
2327                other.m[0][0],
2328                self.m[3][1].mul_add(
2329                    other.m[1][0],
2330                    self.m[3][2].mul_add(other.m[2][0], self.m[3][3] * other.m[3][0]),
2331                ),
2332            ),
2333            self.m[3][0].mul_add(
2334                other.m[0][1],
2335                self.m[3][1].mul_add(
2336                    other.m[1][1],
2337                    self.m[3][2].mul_add(other.m[2][1], self.m[3][3] * other.m[3][1]),
2338                ),
2339            ),
2340            self.m[3][0].mul_add(
2341                other.m[0][2],
2342                self.m[3][1].mul_add(
2343                    other.m[1][2],
2344                    self.m[3][2].mul_add(other.m[2][2], self.m[3][3] * other.m[3][2]),
2345                ),
2346            ),
2347            self.m[3][0].mul_add(
2348                other.m[0][3],
2349                self.m[3][1].mul_add(
2350                    other.m[1][3],
2351                    self.m[3][2].mul_add(other.m[2][3], self.m[3][3] * other.m[3][3]),
2352                ),
2353            ),
2354        )
2355    }
2356
2357    // credit: https://gist.github.com/rygorous/4172889
2358
2359    // linear combination:
2360    // a[0] * B.row[0] + a[1] * B.row[1] + a[2] * B.row[2] + a[3] * B.row[3]
2361    #[cfg(target_arch = "x86_64")]
2362    #[inline]
2363    unsafe fn linear_combine_sse(a: [f32; 4], b: &ComputedTransform3D) -> [f32; 4] {
2364        use core::{
2365            arch::x86_64::{__m128, _mm_add_ps, _mm_mul_ps, _mm_shuffle_ps},
2366            mem,
2367        };
2368
2369        let a: __m128 = mem::transmute(a);
2370        let mut result = _mm_mul_ps(_mm_shuffle_ps(a, a, 0x00), mem::transmute(b.m[0]));
2371        result = _mm_add_ps(
2372            result,
2373            _mm_mul_ps(_mm_shuffle_ps(a, a, 0x55), mem::transmute(b.m[1])),
2374        );
2375        result = _mm_add_ps(
2376            result,
2377            _mm_mul_ps(_mm_shuffle_ps(a, a, 0xaa), mem::transmute(b.m[2])),
2378        );
2379        result = _mm_add_ps(
2380            result,
2381            _mm_mul_ps(_mm_shuffle_ps(a, a, 0xff), mem::transmute(b.m[3])),
2382        );
2383
2384        mem::transmute(result)
2385    }
2386
2387    #[cfg(target_arch = "x86_64")]
2388    #[inline]
2389    pub unsafe fn then_sse(&self, other: &Self) -> Self {
2390        Self {
2391            m: [
2392                Self::linear_combine_sse(self.m[0], other),
2393                Self::linear_combine_sse(self.m[1], other),
2394                Self::linear_combine_sse(self.m[2], other),
2395                Self::linear_combine_sse(self.m[3], other),
2396            ],
2397        }
2398    }
2399
2400    // dual linear combination using AVX instructions on YMM regs
2401    #[cfg(target_arch = "x86_64")]
2402    pub unsafe fn linear_combine_avx8(a01: __m256, b: &ComputedTransform3D) -> __m256 {
2403        use core::{
2404            arch::x86_64::{_mm256_add_ps, _mm256_broadcast_ps, _mm256_mul_ps, _mm256_shuffle_ps},
2405            mem,
2406        };
2407
2408        let mut result = _mm256_mul_ps(
2409            _mm256_shuffle_ps(a01, a01, 0x00),
2410            _mm256_broadcast_ps(mem::transmute(&b.m[0])),
2411        );
2412        result = _mm256_add_ps(
2413            result,
2414            _mm256_mul_ps(
2415                _mm256_shuffle_ps(a01, a01, 0x55),
2416                _mm256_broadcast_ps(mem::transmute(&b.m[1])),
2417            ),
2418        );
2419        result = _mm256_add_ps(
2420            result,
2421            _mm256_mul_ps(
2422                _mm256_shuffle_ps(a01, a01, 0xaa),
2423                _mm256_broadcast_ps(mem::transmute(&b.m[2])),
2424            ),
2425        );
2426        result = _mm256_add_ps(
2427            result,
2428            _mm256_mul_ps(
2429                _mm256_shuffle_ps(a01, a01, 0xff),
2430                _mm256_broadcast_ps(mem::transmute(&b.m[3])),
2431            ),
2432        );
2433        result
2434    }
2435
2436    #[cfg(target_arch = "x86_64")]
2437    #[inline]
2438    pub unsafe fn then_avx8(&self, other: &Self) -> Self {
2439        use core::{
2440            arch::x86_64::{_mm256_loadu_ps, _mm256_storeu_ps, _mm256_zeroupper},
2441            mem,
2442        };
2443
2444        _mm256_zeroupper();
2445
2446        let a01: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[0][0]));
2447        let a23: __m256 = _mm256_loadu_ps(mem::transmute(&self.m[2][0]));
2448
2449        let out01x = Self::linear_combine_avx8(a01, other);
2450        let out23x = Self::linear_combine_avx8(a23, other);
2451
2452        let mut out = Self {
2453            m: [self.m[0], self.m[1], self.m[2], self.m[3]],
2454        };
2455
2456        _mm256_storeu_ps(mem::transmute(&mut out.m[0][0]), out01x);
2457        _mm256_storeu_ps(mem::transmute(&mut out.m[2][0]), out23x);
2458
2459        out
2460    }
2461
2462    // NOTE: webrenders RENDERING has a different
2463    // rotation mode (positive / negative angle)
2464    #[inline]
2465    pub fn make_rotation(
2466        rotation_origin: (f32, f32),
2467        mut degrees: f32,
2468        axis_x: f32,
2469        axis_y: f32,
2470        axis_z: f32,
2471        // see documentation for RotationMode
2472        rotation_mode: RotationMode,
2473    ) -> Self {
2474        degrees = match rotation_mode {
2475            RotationMode::ForWebRender => -degrees, // CSS rotations are clockwise
2476            RotationMode::ForHitTesting => degrees, // hit-testing turns counter-clockwise
2477        };
2478
2479        let (origin_x, origin_y) = rotation_origin;
2480        let pre_transform = Self::new_translation(-origin_x, -origin_y, -0.0);
2481        let post_transform = Self::new_translation(origin_x, origin_y, 0.0);
2482        let theta = 2.0_f32 * core::f32::consts::PI - degrees.to_radians();
2483        let rotate_transform =
2484            Self::new_rotation(axis_x, axis_y, axis_z, theta).then(&Self::IDENTITY);
2485
2486        pre_transform.then(&rotate_transform).then(&post_transform)
2487    }
2488}