1
//! Box model geometry types and writing-mode support for the layout solver.
2
//!
3
//! Provides edge-size types (`EdgeSizes`, `ResolvedBoxProps`, `PackedBoxProps`),
4
//! CSS value resolution (`UnresolvedMargin`, `UnresolvedEdge`, `ResolutionParams`),
5
//! intrinsic sizing (`IntrinsicSizes`), and writing-mode context (`WritingModeContext`).
6

            
7
use azul_core::{
8
    geom::{LogicalPosition, LogicalRect, LogicalSize},
9
    ui_solver::ResolvedOffsets,
10
};
11
use azul_css::props::{
12
    basic::{pixel::PixelValue, PhysicalSize, PropertyContext, ResolutionContext, SizeMetric},
13
    layout::LayoutWritingMode,
14
    style::{StyleDirection, StyleTextOrientation},
15
};
16

            
17
/// Represents the CSS `box-sizing` property.
18
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19
pub enum BoxSizing {
20
    #[default]
21
    ContentBox,
22
    BorderBox,
23
}
24

            
25
#[derive(Debug, Clone, PartialEq, PartialOrd)]
26
pub struct PositionedRectangle {
27
    /// The outer bounds of the rectangle
28
    pub bounds: LogicalRect,
29
    /// Margin of the rectangle.
30
    pub margin: ResolvedOffsets,
31
    /// Border widths of the rectangle.
32
    pub border: ResolvedOffsets,
33
    /// Padding of the rectangle.
34
    pub padding: ResolvedOffsets,
35
}
36

            
37
// +spec:box-model:83b3b8 - Box dimensions: content area with optional padding, border, margin areas
38
/// Represents the four edges of a box for properties like margin, padding, border.
39
// +spec:box-model:3b155c - "4 values assigned to sides" pattern (top, right, bottom, left) matching margin/inset shorthands
40
// +spec:width-calculation:37f9e7 - CSS 2.2 §8.1 box dimensions: content, padding, border, margin areas with top/right/bottom/left segments
41
#[derive(Debug, Clone, Copy, Default)]
42
pub struct EdgeSizes {
43
    pub top: f32,
44
    pub right: f32,
45
    pub bottom: f32,
46
    pub left: f32,
47
}
48

            
49
impl EdgeSizes {
50
    /// Sum of horizontal edges (left + right).
51
    pub fn horizontal_sum(&self) -> f32 {
52
        self.left + self.right
53
    }
54

            
55
    /// Sum of vertical edges (top + bottom).
56
    pub fn vertical_sum(&self) -> f32 {
57
        self.top + self.bottom
58
    }
59

            
60
    // +spec:block-formatting-context:440282 - vertical writing modes use analogous layout via main/cross axis abstraction
61
    // +spec:block-formatting-context:a49f9e - line-relative directions mapped via writing mode
62
    // +spec:block-formatting-context:387117 - writing-mode property maps block flow to vertical/horizontal axes
63
    // +spec:box-model:4c01a3 - dimensional mapping: main=block axis, cross=inline axis per writing mode
64
    // +spec:box-model:4c1a9f - physical-to-logical mapping of margin/padding/border for vertical writing modes
65
    // +spec:box-model:9414ab - flow-relative mapping of box edges (margin/padding/border) per writing mode
66
    // +spec:inline-formatting-context:2de457 - block/inline dimension mapping via writing mode
67
    // +spec:inline-formatting-context:c6b91e - line-relative "over"/"under" mapped to physical top/bottom via writing mode
68
    // +spec:writing-modes:00a918 - Abstract-to-physical mappings for block/inline to top/right/bottom/left
69
    // +spec:writing-modes:14e6f0 - block-start/end depend only on writing-mode; inline-start/end also depend on direction (handled in positioning.rs)
70
    // +spec:writing-modes:1c2101 - Abstract directional terms (top/right/bottom/left) to logical axes (main/cross) based on writing-mode
71
    // +spec:writing-modes:1c5155 - line-relative mappings: over/under/line-left/line-right → top/bottom/left/right in horizontal-tb
72
    // +spec:writing-modes:70daf1 - block/inline axis mapping per writing-mode for edge sizes
73
    // +spec:writing-modes:f9af71 - flow-relative directions: block-start/end and inline-start/end mapped to physical edges
74
    // +spec:writing-modes:60b023 - abstract-to-physical mapping: block axis = main, inline axis = cross
75
    // +spec:writing-modes:829cd7 - flow-relative directions: block-start/end from writing-mode, inline-start/end from writing-mode+direction
76
    // +spec:writing-modes:a2113d - block/inline axis mapping for writing modes (block-axis, inline-axis, block-start/end, inline-start/end)
77
    // +spec:writing-modes:c0ae9c - abstract directional mappings from writing-mode/direction
78
    // +spec:writing-modes:c91130 - Abstract box terminology: block/inline axis mapping per writing-mode
79
    // +spec:writing-modes:cd31ce - flow-relative directions mapped to physical via writing mode
80
    // +spec:writing-modes:fd8c18 - block/inline axis mapping based on writing mode
81
    // +spec:writing-modes:0e549a - writing-mode computed value influences physical/logical axis mapping
82
    /// Returns the size of the edge at the start of the main/block axis.
83
435190
    pub fn main_start(&self, wm: LayoutWritingMode) -> f32 {
84
435190
        match wm {
85
435190
            LayoutWritingMode::HorizontalTb => self.top,
86
            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.left,
87
        }
88
435190
    }
89

            
90
    /// Returns the size of the edge at the end of the main/block axis.
91
442470
    pub fn main_end(&self, wm: LayoutWritingMode) -> f32 {
92
442470
        match wm {
93
442470
            LayoutWritingMode::HorizontalTb => self.bottom,
94
            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.right,
95
        }
96
442470
    }
97

            
98
    /// Returns the sum of the start and end sizes on the main/block axis.
99
286860
    pub fn main_sum(&self, wm: LayoutWritingMode) -> f32 {
100
286860
        self.main_start(wm) + self.main_end(wm)
101
286860
    }
102

            
103
    // +spec:block-formatting-context:6225cb - line-relative directions: vertical modes map line-over/under to top/bottom
104
    /// Returns the size of the edge at the start of the cross/inline axis.
105
298235
    pub fn cross_start(&self, wm: LayoutWritingMode) -> f32 {
106
298235
        match wm {
107
298235
            LayoutWritingMode::HorizontalTb => self.left,
108
            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.top,
109
        }
110
298235
    }
111

            
112
    /// Returns the size of the edge at the end of the cross/inline axis.
113
277375
    pub fn cross_end(&self, wm: LayoutWritingMode) -> f32 {
114
277375
        match wm {
115
277375
            LayoutWritingMode::HorizontalTb => self.right,
116
            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.bottom,
117
        }
118
277375
    }
119

            
120
    /// Returns the sum of the start and end sizes on the cross/inline axis.
121
242900
    pub fn cross_sum(&self, wm: LayoutWritingMode) -> f32 {
122
242900
        self.cross_start(wm) + self.cross_end(wm)
123
242900
    }
124
}
125

            
126
// ============================================================================
127
// UNRESOLVED VALUE TYPES (for lazy resolution during layout)
128
// ============================================================================
129

            
130
/// An unresolved CSS margin value.
131
// +spec:box-model:ff1730 - margin properties apply to both continuous and paged media
132
///
133
/// Margins can be `auto` (for centering) or a length value that needs
134
/// resolution against the containing block.
135
#[derive(Debug, Clone, Copy, Default, PartialEq)]
136
pub enum UnresolvedMargin {
137
    /// margin: 0 (default)
138
    #[default]
139
    Zero,
140
    /// margin: auto (for centering, CSS 2.2 § 10.3.3)
141
    Auto,
142
    /// A length value (px, %, em, vh, etc.)
143
    Length(PixelValue),
144
}
145

            
146
impl UnresolvedMargin {
147
    /// Returns true if this is an auto margin
148
101080
    pub fn is_auto(&self) -> bool {
149
101080
        matches!(self, UnresolvedMargin::Auto)
150
101080
    }
151

            
152
    /// Resolve this margin value to pixels.
153
    ///
154
    /// - `Auto` returns 0.0 (actual auto margin calculation happens in layout)
155
    /// - `Zero` returns 0.0
156
    /// - `Length` is resolved using the resolution context
157
101080
    pub fn resolve(&self, ctx: &ResolutionContext) -> f32 {
158
101080
        match self {
159
33530
            UnresolvedMargin::Zero => 0.0,
160
            // +spec:box-model:c921aa - auto margin-top/bottom used value is 0 for block-level non-replaced elements in normal flow
161
            // +spec:box-model:e25fdc - auto margins treated as zero for abspos size computation
162
70
            UnresolvedMargin::Auto => 0.0, // Auto is handled separately in layout
163
67480
            UnresolvedMargin::Length(pv) => pv.resolve_with_context(ctx, PropertyContext::Margin),
164
        }
165
101080
    }
166
}
167

            
168
/// Unresolved edge sizes for margin/padding/border.
169
///
170
/// This stores the raw CSS values before resolution, allowing us to
171
/// defer resolution until the containing block size is known.
172
#[derive(Debug, Clone, Copy, Default)]
173
pub struct UnresolvedEdge<T> {
174
    pub top: T,
175
    pub right: T,
176
    pub bottom: T,
177
    pub left: T,
178
}
179

            
180
impl<T> UnresolvedEdge<T> {
181
    pub fn new(top: T, right: T, bottom: T, left: T) -> Self {
182
        Self { top, right, bottom, left }
183
    }
184
}
185

            
186
impl UnresolvedEdge<UnresolvedMargin> {
187
    /// Resolve all margin edges to pixel values.
188
25270
    pub fn resolve(&self, ctx: &ResolutionContext) -> EdgeSizes {
189
25270
        EdgeSizes {
190
25270
            top: self.top.resolve(ctx),
191
25270
            right: self.right.resolve(ctx),
192
25270
            bottom: self.bottom.resolve(ctx),
193
25270
            left: self.left.resolve(ctx),
194
25270
        }
195
25270
    }
196

            
197
    /// Extract which margins are set to `auto`.
198
25270
    pub fn get_margin_auto(&self) -> MarginAuto {
199
25270
        MarginAuto {
200
25270
            top: self.top.is_auto(),
201
25270
            right: self.right.is_auto(),
202
25270
            bottom: self.bottom.is_auto(),
203
25270
            left: self.left.is_auto(),
204
25270
        }
205
25270
    }
206
}
207

            
208
impl UnresolvedEdge<PixelValue> {
209
    /// Resolve all edges to pixel values.
210
50540
    pub fn resolve(&self, ctx: &ResolutionContext, prop_ctx: PropertyContext) -> EdgeSizes {
211
50540
        EdgeSizes {
212
50540
            top: self.top.resolve_with_context(ctx, prop_ctx),
213
50540
            right: self.right.resolve_with_context(ctx, prop_ctx),
214
50540
            bottom: self.bottom.resolve_with_context(ctx, prop_ctx),
215
50540
            left: self.left.resolve_with_context(ctx, prop_ctx),
216
50540
        }
217
50540
    }
218
}
219

            
220
/// Parameters needed to resolve CSS values to pixels.
221
#[derive(Debug, Clone, Copy)]
222
pub struct ResolutionParams {
223
    // +spec:inline-formatting-context:26c933 - LogicalSize maps inline/block dimensions to physical width/height per writing mode
224
    /// The containing block size (for % resolution)
225
    pub containing_block: LogicalSize,
226
    /// The viewport size (for vh/vw resolution)
227
    pub viewport_size: LogicalSize,
228
    /// The element's computed font-size (for em resolution)
229
    pub element_font_size: f32,
230
    /// The root element's font-size (for rem resolution)
231
    pub root_font_size: f32,
232
}
233

            
234
impl ResolutionParams {
235
    /// Create a ResolutionContext from these parameters.
236
25270
    pub fn to_resolution_context(&self) -> ResolutionContext {
237
25270
        ResolutionContext {
238
25270
            element_font_size: self.element_font_size,
239
25270
            // For non-font properties, `em` resolves against the element's own
240
25270
            // computed font-size, so parent_font_size == element_font_size here.
241
25270
            // Do NOT use this context for font-size resolution itself.
242
25270
            parent_font_size: self.element_font_size,
243
25270
            root_font_size: self.root_font_size,
244
25270
            element_size: None,
245
25270
            containing_block_size: PhysicalSize::new(
246
25270
                self.containing_block.width,
247
25270
                self.containing_block.height,
248
25270
            ),
249
25270
            viewport_size: PhysicalSize::new(
250
25270
                self.viewport_size.width,
251
25270
                self.viewport_size.height,
252
25270
            ),
253
25270
        }
254
25270
    }
255
}
256

            
257
// ============================================================================
258
// UNRESOLVED BOX PROPS (new design)
259
// ============================================================================
260

            
261
/// Box properties with unresolved CSS values.
262
///
263
/// This stores the raw CSS values as parsed, deferring resolution until
264
/// layout time when the containing block size is known.
265
#[derive(Debug, Clone, Copy, Default)]
266
pub struct UnresolvedBoxProps {
267
    pub margin: UnresolvedEdge<UnresolvedMargin>,
268
    pub padding: UnresolvedEdge<PixelValue>,
269
    pub border: UnresolvedEdge<PixelValue>,
270
}
271

            
272
impl UnresolvedBoxProps {
273
    /// Resolve all box properties to pixel values.
274
25270
    pub fn resolve(&self, params: &ResolutionParams) -> ResolvedBoxProps {
275
25270
        let ctx = params.to_resolution_context();
276
25270
        ResolvedBoxProps {
277
25270
            margin: self.margin.resolve(&ctx),
278
25270
            padding: self.padding.resolve(&ctx, PropertyContext::Padding),
279
25270
            border: self.border.resolve(&ctx, PropertyContext::BorderWidth),
280
25270
            margin_auto: self.margin.get_margin_auto(),
281
25270
        }
282
25270
    }
283
}
284

            
285
// ============================================================================
286
// RESOLVED BOX PROPS (legacy name: BoxProps)
287
// ============================================================================
288

            
289
/// Tracks which margins are set to `auto` (for centering calculations).
290
#[derive(Debug, Clone, Copy, Default)]
291
pub struct MarginAuto {
292
    pub left: bool,
293
    pub right: bool,
294
    pub top: bool,
295
    pub bottom: bool,
296
}
297

            
298
/// A fully resolved representation of a node's box model properties.
299
// +spec:box-model:3e083b - content/padding/border/margin box model layers
300
// +spec:box-model:a227ff - content/padding/border/margin edges defining box extents for overflow
301
// +spec:containing-block:bca691 - box model edges: padding/border/margin boxes with content-box, padding-box, margin-box methods
302
///
303
/// All values are in pixels. This is the result of resolving `UnresolvedBoxProps`
304
/// against a containing block.
305
#[derive(Debug, Clone, Copy, Default)]
306
pub struct ResolvedBoxProps {
307
    pub margin: EdgeSizes,
308
    pub padding: EdgeSizes,
309
    pub border: EdgeSizes,
310
    /// Tracks which margins are set to `auto`.
311
    /// CSS 2.2 § 10.3.3: If both margin-left and margin-right are auto,
312
    /// their used values are equal, centering the element within its container.
313
    pub margin_auto: MarginAuto,
314
}
315

            
316
impl ResolvedBoxProps {
317
    // +spec:box-model:be08c6 - inner size (content-box) from outer size minus border+padding, floored at zero
318
    // +spec:writing-modes:a58616 - abstract dimensions: inline size maps to physical width/height per writing-mode
319
    /// Calculates the inner content-box size from an outer border-box size,
320
    /// correctly accounting for the specified writing mode.
321
121450
    pub fn inner_size(&self, outer_size: LogicalSize, wm: LayoutWritingMode) -> LogicalSize {
322
121450
        let outer_main = outer_size.main(wm);
323
121450
        let outer_cross = outer_size.cross(wm);
324

            
325
        // The sum of padding and border along the cross (inline) axis.
326
121450
        let cross_axis_spacing = self.padding.cross_sum(wm) + self.border.cross_sum(wm);
327

            
328
        // The sum of padding and border along the main (block) axis.
329
121450
        let main_axis_spacing = self.padding.main_sum(wm) + self.border.main_sum(wm);
330

            
331
        // +spec:box-model:2589b1 - content size = border-box - border - padding, floored at zero
332
        // +spec:box-model:3ab53d - if padding+border > border-box, content floors at 0px
333
121450
        let inner_main = (outer_main - main_axis_spacing).max(0.0);
334
121450
        let inner_cross = (outer_cross - cross_axis_spacing).max(0.0);
335

            
336
121450
        LogicalSize::from_main_cross(inner_main, inner_cross, wm)
337
121450
    }
338

            
339
    // +spec:box-model:aa585e - Content/padding/border/margin edge relationships
340
    // +spec:height-calculation:6c9abb - box model edges: margin > border > padding > content
341
    /// Returns the content-box rect from a border-box rect.
342
    /// Shrinks inward by border + padding on each side.
343
    // +spec:box-model:1720a5 - content of a block box is confined to its content edges
344
    pub fn content_box(&self, border_box: LogicalRect) -> LogicalRect {
345
        let x = border_box.origin.x + self.border.left + self.padding.left;
346
        let y = border_box.origin.y + self.border.top + self.padding.top;
347
        let w = (border_box.size.width - self.border.horizontal_sum() - self.padding.horizontal_sum()).max(0.0);
348
        let h = (border_box.size.height - self.border.vertical_sum() - self.padding.vertical_sum()).max(0.0);
349
        LogicalRect { origin: LogicalPosition { x, y }, size: LogicalSize { width: w, height: h } }
350
    }
351

            
352
    /// Returns the padding-box rect from a border-box rect.
353
    /// Shrinks inward by border on each side.
354
    pub fn padding_box(&self, border_box: LogicalRect) -> LogicalRect {
355
        let x = border_box.origin.x + self.border.left;
356
        let y = border_box.origin.y + self.border.top;
357
        let w = (border_box.size.width - self.border.horizontal_sum()).max(0.0);
358
        let h = (border_box.size.height - self.border.vertical_sum()).max(0.0);
359
        LogicalRect { origin: LogicalPosition { x, y }, size: LogicalSize { width: w, height: h } }
360
    }
361

            
362
    /// Returns the margin-box rect from a border-box rect.
363
    /// Expands outward by margin on each side.
364
    pub fn margin_box(&self, border_box: LogicalRect) -> LogicalRect {
365
        let x = border_box.origin.x - self.margin.left;
366
        let y = border_box.origin.y - self.margin.top;
367
        let w = border_box.size.width + self.margin.horizontal_sum();
368
        let h = border_box.size.height + self.margin.vertical_sum();
369
        LogicalRect { origin: LogicalPosition { x, y }, size: LogicalSize { width: w, height: h } }
370
    }
371

            
372
    // +spec:box-model:0e75c1 - margin, padding, border contribute to layout bounds (default line-fit-edge: leading uses line-height model)
373
    /// Total horizontal space consumed by margin + border + padding.
374
    pub fn horizontal_mbp(&self) -> f32 {
375
        self.margin.horizontal_sum() + self.border.horizontal_sum() + self.padding.horizontal_sum()
376
    }
377

            
378
    /// Total vertical space consumed by margin + border + padding.
379
    pub fn vertical_mbp(&self) -> f32 {
380
        self.margin.vertical_sum() + self.border.vertical_sum() + self.padding.vertical_sum()
381
    }
382

            
383
    /// Total horizontal space consumed by border + padding only (no margin).
384
    pub fn horizontal_bp(&self) -> f32 {
385
        self.border.horizontal_sum() + self.padding.horizontal_sum()
386
    }
387

            
388
    /// Total vertical space consumed by border + padding only (no margin).
389
    pub fn vertical_bp(&self) -> f32 {
390
        self.border.vertical_sum() + self.padding.vertical_sum()
391
    }
392
}
393

            
394
/// Type alias for backwards compatibility.
395
/// TODO: Remove this once all code uses ResolvedBoxProps directly.
396
pub type BoxProps = ResolvedBoxProps;
397

            
398
/// Packed representation of box model properties using i16×10 encoding.
399
///
400
/// Stores margin/padding/border as i16 values scaled by 10 (0.1px precision),
401
/// reducing the hot struct from 52B to 26B. Range: ±3276.7px per edge.
402
///
403
/// Only used for storage in `LayoutNodeHot`. The layout solver unpacks to
404
/// `ResolvedBoxProps` (f32) for computation.
405
#[derive(Debug, Clone, Copy, Default)]
406
#[repr(C)]
407
pub struct PackedBoxProps {
408
    pub margin: [i16; 4],     // top, right, bottom, left — ×10
409
    pub padding: [i16; 4],    // ×10
410
    pub border: [i16; 4],     // ×10
411
    pub margin_auto: MarginAuto,
412
}
413

            
414
impl PackedBoxProps {
415
    /// Pack a `ResolvedBoxProps` into compact i16×10 encoding.
416
    #[inline]
417
25515
    pub fn pack(bp: &ResolvedBoxProps) -> Self {
418
25515
        Self {
419
25515
            margin: Self::pack_edge(&bp.margin),
420
25515
            padding: Self::pack_edge(&bp.padding),
421
25515
            border: Self::pack_edge(&bp.border),
422
25515
            margin_auto: bp.margin_auto,
423
25515
        }
424
25515
    }
425

            
426
    /// Unpack to full `ResolvedBoxProps` with f32 values.
427
    #[inline]
428
452725
    pub fn unpack(&self) -> ResolvedBoxProps {
429
452725
        ResolvedBoxProps {
430
452725
            margin: Self::unpack_edge(&self.margin),
431
452725
            padding: Self::unpack_edge(&self.padding),
432
452725
            border: Self::unpack_edge(&self.border),
433
452725
            margin_auto: self.margin_auto,
434
452725
        }
435
452725
    }
436

            
437
    /// Convenience: unpack and call `inner_size` on the result.
438
    #[inline]
439
45325
    pub fn inner_size(&self, outer_size: LogicalSize, wm: LayoutWritingMode) -> LogicalSize {
440
45325
        self.unpack().inner_size(outer_size, wm)
441
45325
    }
442

            
443
    /// Convenience: unpack and call `content_box` on the result.
444
    #[inline]
445
    pub fn content_box(&self, border_box: LogicalRect) -> LogicalRect {
446
        self.unpack().content_box(border_box)
447
    }
448

            
449
    /// Convenience: unpack and call `padding_box` on the result.
450
    #[inline]
451
    pub fn padding_box(&self, border_box: LogicalRect) -> LogicalRect {
452
        self.unpack().padding_box(border_box)
453
    }
454

            
455
    /// Convenience: unpack and call `margin_box` on the result.
456
    #[inline]
457
    pub fn margin_box(&self, border_box: LogicalRect) -> LogicalRect {
458
        self.unpack().margin_box(border_box)
459
    }
460

            
461
    /// Convenience: unpack and return horizontal MBP.
462
    #[inline]
463
    pub fn horizontal_mbp(&self) -> f32 {
464
        self.unpack().horizontal_mbp()
465
    }
466

            
467
    /// Convenience: unpack and return vertical MBP.
468
    #[inline]
469
    pub fn vertical_mbp(&self) -> f32 {
470
        self.unpack().vertical_mbp()
471
    }
472

            
473
    /// Convenience: unpack and return horizontal BP.
474
    #[inline]
475
    pub fn horizontal_bp(&self) -> f32 {
476
        self.unpack().horizontal_bp()
477
    }
478

            
479
    /// Convenience: unpack and return vertical BP.
480
    #[inline]
481
    pub fn vertical_bp(&self) -> f32 {
482
        self.unpack().vertical_bp()
483
    }
484

            
485
    #[inline(always)]
486
76545
    fn pack_edge(e: &EdgeSizes) -> [i16; 4] {
487
76545
        [
488
76545
            (e.top * 10.0).round() as i16,
489
76545
            (e.right * 10.0).round() as i16,
490
76545
            (e.bottom * 10.0).round() as i16,
491
76545
            (e.left * 10.0).round() as i16,
492
76545
        ]
493
76545
    }
494

            
495
    #[inline(always)]
496
1358175
    fn unpack_edge(e: &[i16; 4]) -> EdgeSizes {
497
1358175
        EdgeSizes {
498
1358175
            top: e[0] as f32 * 0.1,
499
1358175
            right: e[1] as f32 * 0.1,
500
1358175
            bottom: e[2] as f32 * 0.1,
501
1358175
            left: e[3] as f32 * 0.1,
502
1358175
        }
503
1358175
    }
504
}
505

            
506
// Re-export float and clear types from azul_css
507
pub use azul_css::props::layout::{LayoutClear, LayoutFloat};
508

            
509
// +spec:intrinsic-sizing:af39b6 - min-content, max-content, and stretch fit size definitions
510
// min-content constraint, max-content constraint definitions
511
// and fit-content sizes for both inline and block axes
512
// +spec:height-calculation:e9ec84 - replaced elements have natural dimensions (width, height, ratio)
513
/// Represents the intrinsic sizing information for an element, calculated
514
/// without knowledge of the final containing block size.
515
// +spec:intrinsic-sizing:127a10 - min-content, max-content, fit-content size definitions (css-sizing-3 §2.1)
516
// +spec:intrinsic-sizing:21f2cb - defines min-content, max-content, and stretch-fit size terminology
517
// +spec:width-calculation:1583c4 - min-content, max-content, fit-content intrinsic size definitions (§2.1)
518
#[derive(Debug, Clone, Copy, Default)]
519
pub struct IntrinsicSizes {
520
    // +spec:width-calculation:b83d0a - min-content width ("preferred minimum width" in CSS2.1§10.3.5)
521
    // +spec:writing-modes:1583c4 - min-content size in inline axis = size fitting contents with all soft wraps taken
522
    /// §2.1 min-content inline size: inline size fitting contents if all soft wraps taken.
523
    pub min_content_width: f32,
524
    // +spec:width-calculation:0c74d3 - max-content width ("preferred width" in CSS2.1§10.3.5)
525
    // +spec:writing-modes:6e85d3 - max-content inline size is the "ideal" size in the inline axis (writing-mode-dependent)
526
    /// §2.1 max-content inline size: narrowest inline size if no soft wraps taken.
527
    pub max_content_width: f32,
528
    /// The width specified by CSS properties, if any.
529
    pub preferred_width: Option<f32>,
530
    /// §2.1 min-content block size: for block containers, tables, and inline boxes,
531
    /// equivalent to max-content block size.
532
    pub min_content_height: f32,
533
    // +spec:writing-modes:8c94e2 - max-content block size is the "ideal" block size after layout
534
    /// §2.1 max-content block size: "ideal" block size, usually content height after layout.
535
    pub max_content_height: f32,
536
    /// The height specified by CSS properties, if any.
537
    pub preferred_height: Option<f32>,
538
}
539

            
540
// ============================================================================
541
// WRITING MODE SUPPORT
542
// ============================================================================
543

            
544
/// Returns true if the writing mode is horizontal (HorizontalTb).
545
///
546
/// This is the main entry point for code that needs to check whether layout
547
/// should proceed in horizontal or vertical mode. In horizontal mode, the
548
/// inline axis is horizontal (left-to-right or right-to-left) and the block
549
/// axis is vertical (top-to-bottom). In vertical modes, these are swapped.
550
// +spec:block-formatting-context:6225cb - vertical writing modes: line-over is right, line-under is left
551
// +spec:block-formatting-context:9a4269 - vertical vs horizontal script classification
552
29645
pub fn is_horizontal(writing_mode: LayoutWritingMode) -> bool {
553
29645
    matches!(writing_mode, LayoutWritingMode::HorizontalTb)
554
29645
}
555

            
556
/// Captures the resolved writing mode context for a node.
557
///
558
/// This struct bundles together all the CSS properties that affect how
559
/// logical directions (inline/block) map to physical directions (x/y).
560
/// Spec agents should use this struct to implement writing-mode-aware layout.
561
///
562
/// # CSS Writing Modes Level 4
563
///
564
/// - `writing-mode` determines the block flow direction and inline base direction
565
/// - `direction` determines the inline base direction (ltr or rtl)
566
/// - `text-orientation` determines glyph orientation in vertical writing modes
567
// +spec:block-formatting-context:333dcb - typographic mode captured by text_orientation field
568
// +spec:block-formatting-context:66eb6d - text-orientation property (mixed|upright|sideways) integrated into WritingModeContext
569
// +spec:block-formatting-context:8be1b0 - writing modes and vertical text orientation context (UTN#22)
570
// +spec:display-property:0a39dc - text-orientation affects inline-level alignment via WritingModeContext
571
// +spec:display-property:591355 - bidirectionality support via direction property in WritingModeContext
572
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
573
pub struct WritingModeContext {
574
    pub writing_mode: LayoutWritingMode,
575
    pub direction: StyleDirection,
576
    // +spec:block-formatting-context:925cfe - text-orientation mixed/upright for horizontal scripts in vertical mode
577
    pub text_orientation: StyleTextOrientation,
578
}
579

            
580
impl Default for WritingModeContext {
581
1260
    fn default() -> Self {
582
1260
        Self {
583
1260
            writing_mode: LayoutWritingMode::HorizontalTb,
584
1260
            direction: StyleDirection::Ltr,
585
1260
            text_orientation: StyleTextOrientation::Mixed,
586
1260
        }
587
1260
    }
588
}
589

            
590
impl WritingModeContext {
591
    /// Constructs a `WritingModeContext`, applying spec-mandated overrides.
592
    // +spec:writing-modes:8307e4 - text-orientation: upright forces used direction to ltr
593
58310
    pub fn new(
594
58310
        writing_mode: LayoutWritingMode,
595
58310
        direction: StyleDirection,
596
58310
        text_orientation: StyleTextOrientation,
597
58310
    ) -> Self {
598
        // CSS Writing Modes Level 4 §5.1: text-orientation: upright causes
599
        // the used value of direction to be ltr, and all characters to be
600
        // treated as strong LTR for bidi reordering purposes.
601
58310
        let used_direction = if text_orientation == StyleTextOrientation::Upright {
602
            StyleDirection::Ltr
603
        } else {
604
58310
            direction
605
        };
606
58310
        Self {
607
58310
            writing_mode,
608
58310
            direction: used_direction,
609
58310
            text_orientation,
610
58310
        }
611
58310
    }
612

            
613
    // +spec:writing-modes:458d31 - text-orientation:upright forces used direction to ltr
614
    /// Returns the used value of `direction`, accounting for `text-orientation: upright`
615
    /// which forces direction to `ltr` in vertical writing modes per CSS Writing Modes 4 §5.1.
616
    pub fn used_direction(&self) -> StyleDirection {
617
        match self.writing_mode {
618
            LayoutWritingMode::HorizontalTb => self.direction,
619
            LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => {
620
                if self.text_orientation == StyleTextOrientation::Upright {
621
                    StyleDirection::Ltr
622
                } else {
623
                    self.direction
624
                }
625
            }
626
        }
627
    }
628

            
629
    // +spec:containing-block:c205e5 - orthogonal flow: child writing mode perpendicular to containing block's
630

            
631
    /// Returns true if the writing mode is horizontal (HorizontalTb).
632
    ///
633
    /// When true, the inline axis is horizontal and the block axis is vertical.
634
29645
    pub fn is_horizontal(&self) -> bool {
635
29645
        is_horizontal(self.writing_mode)
636
29645
    }
637

            
638
    /// Returns true if the inline size corresponds to the physical width.
639
    ///
640
    /// In horizontal writing modes, inline size = width.
641
    /// In vertical writing modes, inline size = height.
642
    // +spec:block-formatting-context:bb9845 - orthogonal flows: inline/block axis mapping
643
    pub fn inline_size_is_width(&self) -> bool {
644
        self.is_horizontal()
645
    }
646

            
647
    /// Returns true if the block size corresponds to the physical height.
648
    ///
649
    /// In horizontal writing modes, block size = height.
650
    /// In vertical writing modes, block size = width.
651
    pub fn block_size_is_height(&self) -> bool {
652
        self.is_horizontal()
653
    }
654

            
655
    // +spec:writing-modes:32541a - direction property controls inline text direction via stylesheet
656
    /// Returns true if the inline direction is reversed (RTL in horizontal,
657
    /// or bottom-to-top in certain vertical modes).
658
    pub fn is_inline_reversed(&self) -> bool {
659
        self.used_direction() == StyleDirection::Rtl
660
    }
661
}