Skip to main content

text_typeset/layout/
frame.rs

1use crate::font::registry::FontRegistry;
2use crate::layout::block::layout_block;
3use crate::layout::block::{BlockLayout, BlockLayoutParams};
4use crate::layout::table::{TableLayout, TableLayoutParams, layout_table};
5
6/// Frame position type (from text-document's FramePosition).
7///
8/// **Note on floats**: `FloatLeft` and `FloatRight` currently place the frame
9/// at the correct x position but do not implement content wrapping (text does
10/// not flow beside the float). They advance `content_height` like `Inline`,
11/// so subsequent content appears below rather than beside the float.
12/// True float exclusion zones would require tracking available width per
13/// y-range during paragraph layout.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub enum FramePosition {
16    /// Inline: rendered in normal flow order, advances content_height.
17    #[default]
18    Inline,
19    /// Float left: placed at x=0, advances content_height.
20    /// Content wrapping is not yet implemented.
21    FloatLeft,
22    /// Float right: placed at x=available_width - frame_width, advances content_height.
23    /// Content wrapping is not yet implemented.
24    FloatRight,
25    /// Absolute: placed at (margin_left, margin_top) from document origin.
26    /// Does not affect flow or content_height.
27    Absolute,
28}
29
30/// Parameters for a frame, extracted from text-document's FrameSnapshot.
31pub struct FrameLayoutParams {
32    pub frame_id: usize,
33    pub position: FramePosition,
34    /// Frame width constraint (None = use available width).
35    pub width: Option<f32>,
36    /// Frame height constraint (None = auto from content).
37    pub height: Option<f32>,
38    pub margin_top: f32,
39    pub margin_bottom: f32,
40    pub margin_left: f32,
41    pub margin_right: f32,
42    pub padding: f32,
43    pub border_width: f32,
44    /// Nested flow elements: blocks and tables within the frame.
45    pub blocks: Vec<BlockLayoutParams>,
46    pub tables: Vec<(usize, TableLayoutParams)>, // (flow_index, params) for ordering
47}
48
49/// Computed layout for a frame.
50pub struct FrameLayout {
51    pub frame_id: usize,
52    pub y: f32,
53    pub x: f32,
54    pub total_width: f32,
55    pub total_height: f32,
56    pub content_x: f32,
57    pub content_y: f32,
58    pub content_width: f32,
59    pub content_height: f32,
60    pub blocks: Vec<BlockLayout>,
61    pub tables: Vec<TableLayout>,
62    pub border_width: f32,
63}
64
65/// Lay out a frame: compute dimensions, lay out nested content.
66pub fn layout_frame(
67    registry: &FontRegistry,
68    params: &FrameLayoutParams,
69    available_width: f32,
70) -> FrameLayout {
71    let border = params.border_width;
72    let pad = params.padding;
73    let frame_width = params.width.unwrap_or(available_width);
74    let content_width =
75        (frame_width - border * 2.0 - pad * 2.0 - params.margin_left - params.margin_right)
76            .max(0.0);
77
78    // Lay out nested blocks
79    let mut blocks = Vec::new();
80    let mut content_y = 0.0f32;
81
82    for block_params in &params.blocks {
83        let mut block = layout_block(registry, block_params, content_width);
84        block.y = content_y + block.top_margin;
85        let block_content = block.height - block.top_margin - block.bottom_margin;
86        content_y = block.y + block_content + block.bottom_margin;
87        blocks.push(block);
88    }
89
90    // Lay out nested tables
91    let mut tables = Vec::new();
92    for (_flow_idx, table_params) in &params.tables {
93        let mut table = layout_table(registry, table_params, content_width);
94        table.y = content_y;
95        content_y += table.total_height;
96        tables.push(table);
97    }
98
99    let auto_content_height = content_y;
100    let content_height = params
101        .height
102        .map(|h| (h - border * 2.0 - pad * 2.0).max(0.0))
103        .unwrap_or(auto_content_height);
104
105    let total_height =
106        params.margin_top + border + pad + content_height + pad + border + params.margin_bottom;
107    let total_width =
108        params.margin_left + border + pad + content_width + pad + border + params.margin_right;
109
110    let content_x = params.margin_left + border + pad;
111    let content_y_offset = params.margin_top + border + pad;
112
113    FrameLayout {
114        frame_id: params.frame_id,
115        y: 0.0, // set by flow
116        x: 0.0,
117        total_width,
118        total_height,
119        content_x,
120        content_y: content_y_offset,
121        content_width,
122        content_height,
123        blocks,
124        tables,
125        border_width: border,
126    }
127}