azul_layout/solver3/geometry.rs
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
7use azul_core::{
8 geom::{LogicalPosition, LogicalRect, LogicalSize},
9 ui_solver::ResolvedOffsets,
10};
11use 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)]
19pub enum BoxSizing {
20 #[default]
21 ContentBox,
22 BorderBox,
23}
24
25#[derive(Debug, Clone, PartialEq, PartialOrd)]
26pub 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)]
42pub struct EdgeSizes {
43 pub top: f32,
44 pub right: f32,
45 pub bottom: f32,
46 pub left: f32,
47}
48
49impl 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 pub fn main_start(&self, wm: LayoutWritingMode) -> f32 {
84 match wm {
85 LayoutWritingMode::HorizontalTb => self.top,
86 LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.left,
87 }
88 }
89
90 /// Returns the size of the edge at the end of the main/block axis.
91 pub fn main_end(&self, wm: LayoutWritingMode) -> f32 {
92 match wm {
93 LayoutWritingMode::HorizontalTb => self.bottom,
94 LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.right,
95 }
96 }
97
98 /// Returns the sum of the start and end sizes on the main/block axis.
99 pub fn main_sum(&self, wm: LayoutWritingMode) -> f32 {
100 self.main_start(wm) + self.main_end(wm)
101 }
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 pub fn cross_start(&self, wm: LayoutWritingMode) -> f32 {
106 match wm {
107 LayoutWritingMode::HorizontalTb => self.left,
108 LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.top,
109 }
110 }
111
112 /// Returns the size of the edge at the end of the cross/inline axis.
113 pub fn cross_end(&self, wm: LayoutWritingMode) -> f32 {
114 match wm {
115 LayoutWritingMode::HorizontalTb => self.right,
116 LayoutWritingMode::VerticalRl | LayoutWritingMode::VerticalLr => self.bottom,
117 }
118 }
119
120 /// Returns the sum of the start and end sizes on the cross/inline axis.
121 pub fn cross_sum(&self, wm: LayoutWritingMode) -> f32 {
122 self.cross_start(wm) + self.cross_end(wm)
123 }
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)]
136pub 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
146impl UnresolvedMargin {
147 /// Returns true if this is an auto margin
148 pub fn is_auto(&self) -> bool {
149 matches!(self, UnresolvedMargin::Auto)
150 }
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 pub fn resolve(&self, ctx: &ResolutionContext) -> f32 {
158 match self {
159 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 UnresolvedMargin::Auto => 0.0, // Auto is handled separately in layout
163 UnresolvedMargin::Length(pv) => pv.resolve_with_context(ctx, PropertyContext::Margin),
164 }
165 }
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)]
173pub struct UnresolvedEdge<T> {
174 pub top: T,
175 pub right: T,
176 pub bottom: T,
177 pub left: T,
178}
179
180impl<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
186impl UnresolvedEdge<UnresolvedMargin> {
187 /// Resolve all margin edges to pixel values.
188 pub fn resolve(&self, ctx: &ResolutionContext) -> EdgeSizes {
189 EdgeSizes {
190 top: self.top.resolve(ctx),
191 right: self.right.resolve(ctx),
192 bottom: self.bottom.resolve(ctx),
193 left: self.left.resolve(ctx),
194 }
195 }
196
197 /// Extract which margins are set to `auto`.
198 pub fn get_margin_auto(&self) -> MarginAuto {
199 MarginAuto {
200 top: self.top.is_auto(),
201 right: self.right.is_auto(),
202 bottom: self.bottom.is_auto(),
203 left: self.left.is_auto(),
204 }
205 }
206}
207
208impl UnresolvedEdge<PixelValue> {
209 /// Resolve all edges to pixel values.
210 pub fn resolve(&self, ctx: &ResolutionContext, prop_ctx: PropertyContext) -> EdgeSizes {
211 EdgeSizes {
212 top: self.top.resolve_with_context(ctx, prop_ctx),
213 right: self.right.resolve_with_context(ctx, prop_ctx),
214 bottom: self.bottom.resolve_with_context(ctx, prop_ctx),
215 left: self.left.resolve_with_context(ctx, prop_ctx),
216 }
217 }
218}
219
220/// Parameters needed to resolve CSS values to pixels.
221#[derive(Debug, Clone, Copy)]
222pub 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
234impl ResolutionParams {
235 /// Create a ResolutionContext from these parameters.
236 pub fn to_resolution_context(&self) -> ResolutionContext {
237 ResolutionContext {
238 element_font_size: self.element_font_size,
239 // For non-font properties, `em` resolves against the element's own
240 // computed font-size, so parent_font_size == element_font_size here.
241 // Do NOT use this context for font-size resolution itself.
242 parent_font_size: self.element_font_size,
243 root_font_size: self.root_font_size,
244 element_size: None,
245 containing_block_size: PhysicalSize::new(
246 self.containing_block.width,
247 self.containing_block.height,
248 ),
249 viewport_size: PhysicalSize::new(
250 self.viewport_size.width,
251 self.viewport_size.height,
252 ),
253 }
254 }
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)]
266pub struct UnresolvedBoxProps {
267 pub margin: UnresolvedEdge<UnresolvedMargin>,
268 pub padding: UnresolvedEdge<PixelValue>,
269 pub border: UnresolvedEdge<PixelValue>,
270}
271
272impl UnresolvedBoxProps {
273 /// Resolve all box properties to pixel values.
274 pub fn resolve(&self, params: &ResolutionParams) -> ResolvedBoxProps {
275 let ctx = params.to_resolution_context();
276 ResolvedBoxProps {
277 margin: self.margin.resolve(&ctx),
278 padding: self.padding.resolve(&ctx, PropertyContext::Padding),
279 border: self.border.resolve(&ctx, PropertyContext::BorderWidth),
280 margin_auto: self.margin.get_margin_auto(),
281 }
282 }
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)]
291pub 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)]
306pub 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
316impl 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 pub fn inner_size(&self, outer_size: LogicalSize, wm: LayoutWritingMode) -> LogicalSize {
322 let outer_main = outer_size.main(wm);
323 let outer_cross = outer_size.cross(wm);
324
325 // The sum of padding and border along the cross (inline) axis.
326 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 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 let inner_main = (outer_main - main_axis_spacing).max(0.0);
334 let inner_cross = (outer_cross - cross_axis_spacing).max(0.0);
335
336 LogicalSize::from_main_cross(inner_main, inner_cross, wm)
337 }
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.
396pub 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)]
407pub 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
414impl PackedBoxProps {
415 /// Pack a `ResolvedBoxProps` into compact i16×10 encoding.
416 #[inline]
417 pub fn pack(bp: &ResolvedBoxProps) -> Self {
418 Self {
419 margin: Self::pack_edge(&bp.margin),
420 padding: Self::pack_edge(&bp.padding),
421 border: Self::pack_edge(&bp.border),
422 margin_auto: bp.margin_auto,
423 }
424 }
425
426 /// Unpack to full `ResolvedBoxProps` with f32 values.
427 #[inline]
428 pub fn unpack(&self) -> ResolvedBoxProps {
429 ResolvedBoxProps {
430 margin: Self::unpack_edge(&self.margin),
431 padding: Self::unpack_edge(&self.padding),
432 border: Self::unpack_edge(&self.border),
433 margin_auto: self.margin_auto,
434 }
435 }
436
437 /// Convenience: unpack and call `inner_size` on the result.
438 #[inline]
439 pub fn inner_size(&self, outer_size: LogicalSize, wm: LayoutWritingMode) -> LogicalSize {
440 self.unpack().inner_size(outer_size, wm)
441 }
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 fn pack_edge(e: &EdgeSizes) -> [i16; 4] {
487 [
488 (e.top * 10.0).round() as i16,
489 (e.right * 10.0).round() as i16,
490 (e.bottom * 10.0).round() as i16,
491 (e.left * 10.0).round() as i16,
492 ]
493 }
494
495 #[inline(always)]
496 fn unpack_edge(e: &[i16; 4]) -> EdgeSizes {
497 EdgeSizes {
498 top: e[0] as f32 * 0.1,
499 right: e[1] as f32 * 0.1,
500 bottom: e[2] as f32 * 0.1,
501 left: e[3] as f32 * 0.1,
502 }
503 }
504}
505
506// Re-export float and clear types from azul_css
507pub 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)]
519pub 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
552pub fn is_horizontal(writing_mode: LayoutWritingMode) -> bool {
553 matches!(writing_mode, LayoutWritingMode::HorizontalTb)
554}
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)]
573pub 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
580impl Default for WritingModeContext {
581 fn default() -> Self {
582 Self {
583 writing_mode: LayoutWritingMode::HorizontalTb,
584 direction: StyleDirection::Ltr,
585 text_orientation: StyleTextOrientation::Mixed,
586 }
587 }
588}
589
590impl WritingModeContext {
591 /// Constructs a `WritingModeContext`, applying spec-mandated overrides.
592 // +spec:writing-modes:8307e4 - text-orientation: upright forces used direction to ltr
593 pub fn new(
594 writing_mode: LayoutWritingMode,
595 direction: StyleDirection,
596 text_orientation: StyleTextOrientation,
597 ) -> 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 let used_direction = if text_orientation == StyleTextOrientation::Upright {
602 StyleDirection::Ltr
603 } else {
604 direction
605 };
606 Self {
607 writing_mode,
608 direction: used_direction,
609 text_orientation,
610 }
611 }
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 pub fn is_horizontal(&self) -> bool {
635 is_horizontal(self.writing_mode)
636 }
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}