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/// How the frame border is drawn.
31#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
32pub enum FrameBorderStyle {
33    /// Draw border on all four sides (default).
34    #[default]
35    Full,
36    /// Draw only the left border (blockquote style).
37    LeftOnly,
38    /// No border.
39    None,
40}
41
42/// Parameters for a frame, extracted from text-document's FrameSnapshot.
43pub struct FrameLayoutParams {
44    pub frame_id: usize,
45    pub position: FramePosition,
46    /// Frame width constraint (None = use available width).
47    pub width: Option<f32>,
48    /// Frame height constraint (None = auto from content).
49    pub height: Option<f32>,
50    pub margin_top: f32,
51    pub margin_bottom: f32,
52    pub margin_left: f32,
53    pub margin_right: f32,
54    pub padding: f32,
55    pub border_width: f32,
56    pub border_style: FrameBorderStyle,
57    /// Nested flow elements: blocks, tables, and frames within the frame.
58    pub blocks: Vec<BlockLayoutParams>,
59    pub tables: Vec<(usize, TableLayoutParams)>, // (flow_index, params) for ordering
60    pub frames: Vec<(usize, FrameLayoutParams)>, // (flow_index, params) for ordering
61}
62
63/// Computed layout for a frame.
64pub struct FrameLayout {
65    pub frame_id: usize,
66    pub y: f32,
67    pub x: f32,
68    pub total_width: f32,
69    pub total_height: f32,
70    pub content_x: f32,
71    pub content_y: f32,
72    pub content_width: f32,
73    pub content_height: f32,
74    pub blocks: Vec<BlockLayout>,
75    pub tables: Vec<TableLayout>,
76    pub frames: Vec<FrameLayout>,
77    pub border_width: f32,
78    pub border_style: FrameBorderStyle,
79}
80
81/// Lay out a frame: compute dimensions, lay out nested content.
82pub fn layout_frame(
83    registry: &FontRegistry,
84    params: &FrameLayoutParams,
85    available_width: f32,
86) -> FrameLayout {
87    let border = params.border_width;
88    let pad = params.padding;
89    let frame_width = params.width.unwrap_or(available_width);
90    let content_width =
91        (frame_width - border * 2.0 - pad * 2.0 - params.margin_left - params.margin_right)
92            .max(0.0);
93
94    // Lay out nested blocks
95    let mut blocks = Vec::new();
96    let mut content_y = 0.0f32;
97
98    for block_params in &params.blocks {
99        let mut block = layout_block(registry, block_params, content_width);
100        block.y = content_y + block.top_margin;
101        let block_content = block.height - block.top_margin - block.bottom_margin;
102        content_y = block.y + block_content + block.bottom_margin;
103        blocks.push(block);
104    }
105
106    // Lay out nested tables
107    let mut tables = Vec::new();
108    for (_flow_idx, table_params) in &params.tables {
109        let mut table = layout_table(registry, table_params, content_width);
110        table.y = content_y;
111        content_y += table.total_height;
112        tables.push(table);
113    }
114
115    // Lay out nested frames (recursive)
116    let mut nested_frames = Vec::new();
117    for (_flow_idx, frame_params) in &params.frames {
118        let mut nested = layout_frame(registry, frame_params, content_width);
119        nested.y = content_y;
120        nested.x = 0.0;
121        content_y += nested.total_height;
122        nested_frames.push(nested);
123    }
124
125    let auto_content_height = content_y;
126    let content_height = params
127        .height
128        .map(|h| (h - border * 2.0 - pad * 2.0).max(0.0))
129        .unwrap_or(auto_content_height);
130
131    let total_height =
132        params.margin_top + border + pad + content_height + pad + border + params.margin_bottom;
133    let total_width =
134        params.margin_left + border + pad + content_width + pad + border + params.margin_right;
135
136    let content_x = params.margin_left + border + pad;
137    let content_y_offset = params.margin_top + border + pad;
138
139    FrameLayout {
140        frame_id: params.frame_id,
141        y: 0.0, // set by flow
142        x: 0.0,
143        total_width,
144        total_height,
145        content_x,
146        content_y: content_y_offset,
147        content_width,
148        content_height,
149        blocks,
150        tables,
151        frames: nested_frames,
152        border_width: border,
153        border_style: params.border_style,
154    }
155}