1use std::collections::HashMap;
2
3use crate::font::registry::FontRegistry;
4use crate::layout::block::{BlockLayout, BlockLayoutParams, layout_block};
5use crate::layout::frame::{FrameLayout, FrameLayoutParams, layout_frame};
6use crate::layout::table::{TableLayout, TableLayoutParams, layout_table};
7
8pub enum FlowItem {
9 Block {
10 block_id: usize,
11 y: f32,
12 height: f32,
13 },
14 Table {
15 table_id: usize,
16 y: f32,
17 height: f32,
18 },
19 Frame {
20 frame_id: usize,
21 y: f32,
22 height: f32,
23 },
24}
25
26pub struct FlowLayout {
27 pub blocks: HashMap<usize, BlockLayout>,
28 pub tables: HashMap<usize, TableLayout>,
29 pub frames: HashMap<usize, FrameLayout>,
30 pub flow_order: Vec<FlowItem>,
31 pub content_height: f32,
32 pub viewport_width: f32,
33 pub viewport_height: f32,
34 pub cached_max_content_width: f32,
35}
36
37impl Default for FlowLayout {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl FlowLayout {
44 pub fn new() -> Self {
45 Self {
46 blocks: HashMap::new(),
47 tables: HashMap::new(),
48 frames: HashMap::new(),
49 flow_order: Vec::new(),
50 content_height: 0.0,
51 viewport_width: 0.0,
52 viewport_height: 0.0,
53 cached_max_content_width: 0.0,
54 }
55 }
56
57 pub fn add_table(
59 &mut self,
60 registry: &FontRegistry,
61 params: &TableLayoutParams,
62 available_width: f32,
63 ) {
64 let mut table = layout_table(registry, params, available_width);
65
66 let mut y = self.content_height;
67 table.y = y;
68 y += table.total_height;
69
70 self.flow_order.push(FlowItem::Table {
71 table_id: table.table_id,
72 y: table.y,
73 height: table.total_height,
74 });
75 if table.total_width > self.cached_max_content_width {
76 self.cached_max_content_width = table.total_width;
77 }
78 self.tables.insert(table.table_id, table);
79 self.content_height = y;
80 }
81
82 pub fn add_frame(
91 &mut self,
92 registry: &FontRegistry,
93 params: &FrameLayoutParams,
94 available_width: f32,
95 ) {
96 use crate::layout::frame::FramePosition;
97
98 let mut frame = layout_frame(registry, params, available_width);
99
100 match params.position {
101 FramePosition::Inline => {
102 frame.y = self.content_height;
103 frame.x = 0.0;
104 self.content_height += frame.total_height;
105 }
106 FramePosition::FloatLeft => {
107 frame.y = self.content_height;
108 frame.x = 0.0;
109 self.content_height += frame.total_height;
114 }
115 FramePosition::FloatRight => {
116 frame.y = self.content_height;
117 frame.x = (available_width - frame.total_width).max(0.0);
118 self.content_height += frame.total_height;
119 }
120 FramePosition::Absolute => {
121 frame.y = params.margin_top;
124 frame.x = params.margin_left;
125 }
127 }
128
129 self.flow_order.push(FlowItem::Frame {
130 frame_id: frame.frame_id,
131 y: frame.y,
132 height: frame.total_height,
133 });
134 if frame.total_width > self.cached_max_content_width {
135 self.cached_max_content_width = frame.total_width;
136 }
137 self.frames.insert(frame.frame_id, frame);
138 }
139
140 pub fn clear(&mut self) {
142 self.blocks.clear();
143 self.tables.clear();
144 self.frames.clear();
145 self.flow_order.clear();
146 self.content_height = 0.0;
147 self.cached_max_content_width = 0.0;
148 }
149
150 pub fn add_block(
152 &mut self,
153 registry: &FontRegistry,
154 params: &BlockLayoutParams,
155 available_width: f32,
156 ) {
157 let mut block = layout_block(registry, params, available_width);
158
159 let mut y = self.content_height;
161 if let Some(FlowItem::Block {
162 block_id: prev_id, ..
163 }) = self.flow_order.last()
164 {
165 if let Some(prev_block) = self.blocks.get(prev_id) {
166 let collapsed = prev_block.bottom_margin.max(block.top_margin);
167 y -= prev_block.bottom_margin;
168 y += collapsed;
169 } else {
170 y += block.top_margin;
171 }
172 } else {
173 y += block.top_margin;
174 }
175
176 block.y = y;
177 let block_content = block.height - block.top_margin - block.bottom_margin;
178 y += block_content + block.bottom_margin;
179
180 self.flow_order.push(FlowItem::Block {
181 block_id: block.block_id,
182 y: block.y,
183 height: block.height,
184 });
185 self.update_max_width_for_block(&block);
186 self.blocks.insert(block.block_id, block);
187 self.content_height = y;
188 }
189
190 pub fn layout_blocks(
192 &mut self,
193 registry: &FontRegistry,
194 block_params: Vec<BlockLayoutParams>,
195 available_width: f32,
196 ) {
197 self.clear();
198 for params in &block_params {
203 self.add_block(registry, params, available_width);
204 }
205 }
206
207 pub fn relayout_block(
214 &mut self,
215 registry: &FontRegistry,
216 params: &BlockLayoutParams,
217 available_width: f32,
218 ) {
219 let block_id = params.block_id;
220 let old_y = self.blocks.get(&block_id).map(|b| b.y).unwrap_or(0.0);
221 let old_height = self.blocks.get(&block_id).map(|b| b.height).unwrap_or(0.0);
222 let old_top_margin = self
223 .blocks
224 .get(&block_id)
225 .map(|b| b.top_margin)
226 .unwrap_or(0.0);
227 let old_bottom_margin = self
228 .blocks
229 .get(&block_id)
230 .map(|b| b.bottom_margin)
231 .unwrap_or(0.0);
232 let old_content = old_height - old_top_margin - old_bottom_margin;
233 let old_end = old_y + old_content + old_bottom_margin;
234
235 let mut block = layout_block(registry, params, available_width);
236 block.y = old_y;
237
238 if (block.top_margin - old_top_margin).abs() > 0.001 {
240 let prev_bm = self.prev_block_bottom_margin(block_id).unwrap_or(0.0);
241 let old_collapsed = prev_bm.max(old_top_margin);
242 let new_collapsed = prev_bm.max(block.top_margin);
243 block.y = old_y + (new_collapsed - old_collapsed);
244 }
245
246 let new_content = block.height - block.top_margin - block.bottom_margin;
247 let new_end = block.y + new_content + block.bottom_margin;
248 let delta = new_end - old_end;
249
250 let new_y = block.y;
251 let new_height = block.height;
252 self.update_max_width_for_block(&block);
253 self.blocks.insert(block_id, block);
254
255 for item in &mut self.flow_order {
257 if let FlowItem::Block {
258 block_id: id,
259 y,
260 height,
261 } = item
262 && *id == block_id
263 {
264 *y = new_y;
265 *height = new_height;
266 break;
267 }
268 }
269
270 if delta.abs() > 0.001 {
272 let mut found = false;
273 for item in &mut self.flow_order {
274 match item {
275 FlowItem::Block {
276 block_id: id,
277 y,
278 height: _,
279 } => {
280 if found {
281 *y += delta;
282 if let Some(b) = self.blocks.get_mut(id) {
283 b.y += delta;
284 }
285 }
286 if *id == block_id {
287 found = true;
288 }
289 }
290 FlowItem::Table {
291 table_id: id, y, ..
292 } => {
293 if found {
294 *y += delta;
295 if let Some(t) = self.tables.get_mut(id) {
296 t.y += delta;
297 }
298 }
299 }
300 FlowItem::Frame {
301 frame_id: id, y, ..
302 } => {
303 if found {
304 *y += delta;
305 if let Some(f) = self.frames.get_mut(id) {
306 f.y += delta;
307 }
308 }
309 }
310 }
311 }
312 self.content_height += delta;
313 }
314 }
315
316 fn update_max_width_for_block(&mut self, block: &BlockLayout) {
318 for line in &block.lines {
319 let w = line.width + block.left_margin + block.right_margin;
320 if w > self.cached_max_content_width {
321 self.cached_max_content_width = w;
322 }
323 }
324 }
325
326 fn prev_block_bottom_margin(&self, block_id: usize) -> Option<f32> {
328 let mut prev_bm = None;
329 for item in &self.flow_order {
330 match item {
331 FlowItem::Block { block_id: id, .. } => {
332 if *id == block_id {
333 return prev_bm;
334 }
335 if let Some(b) = self.blocks.get(id) {
336 prev_bm = Some(b.bottom_margin);
337 }
338 }
339 _ => {
340 prev_bm = None;
342 }
343 }
344 }
345 None
346 }
347}