1use text_document::{
7 BlockSnapshot, CellSnapshot, FlowElementSnapshot, FlowSnapshot, FragmentContent, FrameSnapshot,
8 TableSnapshot,
9};
10
11use crate::layout::block::{BlockLayoutParams, FragmentParams};
12use crate::layout::frame::{FrameLayoutParams, FramePosition};
13use crate::layout::paragraph::Alignment;
14use crate::layout::table::{CellLayoutParams, TableLayoutParams};
15
16const DEFAULT_LIST_INDENT: f32 = 24.0;
17const INDENT_PER_LEVEL: f32 = 24.0;
18
19pub fn convert_flow(flow: &FlowSnapshot) -> FlowElements {
21 let mut blocks = Vec::new();
22 let mut tables = Vec::new();
23 let mut frames = Vec::new();
24
25 for (i, element) in flow.elements.iter().enumerate() {
26 match element {
27 FlowElementSnapshot::Block(block) => {
28 blocks.push((i, convert_block(block)));
29 }
30 FlowElementSnapshot::Table(table) => {
31 tables.push((i, convert_table(table)));
32 }
33 FlowElementSnapshot::Frame(frame) => {
34 frames.push((i, convert_frame(frame)));
35 }
36 }
37 }
38
39 FlowElements {
40 blocks,
41 tables,
42 frames,
43 }
44}
45
46pub struct FlowElements {
48 pub blocks: Vec<(usize, BlockLayoutParams)>,
50 pub tables: Vec<(usize, TableLayoutParams)>,
51 pub frames: Vec<(usize, FrameLayoutParams)>,
52}
53
54pub fn convert_block(block: &BlockSnapshot) -> BlockLayoutParams {
55 let alignment = block
56 .block_format
57 .alignment
58 .as_ref()
59 .map(convert_alignment)
60 .unwrap_or_default();
61
62 let heading_scale = match block.block_format.heading_level {
63 Some(1) => 2.0,
64 Some(2) => 1.5,
65 Some(3) => 1.25,
66 Some(4) => 1.1,
67 _ => 1.0,
68 };
69
70 let fragments: Vec<FragmentParams> = block
71 .fragments
72 .iter()
73 .map(|f| convert_fragment(f, heading_scale))
74 .collect();
75
76 let indent_level = block.block_format.indent.unwrap_or(0) as f32;
77
78 let (list_marker, list_indent) = if let Some(ref info) = block.list_info {
79 (
80 info.marker.clone(),
81 DEFAULT_LIST_INDENT + indent_level * INDENT_PER_LEVEL,
82 )
83 } else {
84 (String::new(), indent_level * INDENT_PER_LEVEL)
85 };
86
87 let checkbox = match block.block_format.marker {
88 Some(text_document::MarkerType::Checked) => Some(true),
89 Some(text_document::MarkerType::Unchecked) => Some(false),
90 _ => None,
91 };
92
93 BlockLayoutParams {
94 block_id: block.block_id,
95 position: block.position,
96 text: block.text.clone(),
97 fragments,
98 alignment,
99 top_margin: block.block_format.top_margin.unwrap_or(0) as f32,
100 bottom_margin: block.block_format.bottom_margin.unwrap_or(0) as f32,
101 left_margin: block.block_format.left_margin.unwrap_or(0) as f32,
102 right_margin: block.block_format.right_margin.unwrap_or(0) as f32,
103 text_indent: block.block_format.text_indent.unwrap_or(0) as f32,
104 list_marker,
105 list_indent,
106 tab_positions: block
107 .block_format
108 .tab_positions
109 .iter()
110 .map(|&t| t as f32)
111 .collect(),
112 line_height_multiplier: block.block_format.line_height,
113 non_breakable_lines: block.block_format.non_breakable_lines.unwrap_or(false),
114 checkbox,
115 background_color: None, }
117}
118
119fn convert_fragment(frag: &FragmentContent, heading_scale: f32) -> FragmentParams {
120 match frag {
121 FragmentContent::Text {
122 text,
123 format,
124 offset,
125 length,
126 } => FragmentParams {
127 text: text.clone(),
128 offset: *offset,
129 length: *length,
130 font_family: format.font_family.clone(),
131 font_weight: format.font_weight,
132 font_bold: format.font_bold,
133 font_italic: format.font_italic,
134 font_point_size: if heading_scale != 1.0 {
135 Some((format.font_point_size.unwrap_or(16) as f32 * heading_scale) as u32)
137 } else {
138 format.font_point_size
139 },
140 underline: format.font_underline.unwrap_or(false),
141 overline: format.font_overline.unwrap_or(false),
142 strikeout: format.font_strikeout.unwrap_or(false),
143 is_link: format.is_anchor.unwrap_or(false),
144 letter_spacing: format.letter_spacing.unwrap_or(0) as f32,
145 word_spacing: format.word_spacing.unwrap_or(0) as f32,
146 },
147 FragmentContent::Image {
148 name: _,
149 width: _,
150 height: _,
151 quality: _,
152 format,
153 offset,
154 } => {
155 FragmentParams {
158 text: String::new(),
159 offset: *offset,
160 length: 1,
161 font_family: None,
162 font_weight: None,
163 font_bold: None,
164 font_italic: None,
165 font_point_size: None,
166 underline: false,
167 overline: false,
168 strikeout: false,
169 is_link: format.is_anchor.unwrap_or(false),
170 letter_spacing: 0.0,
171 word_spacing: 0.0,
172 }
173 }
174 }
175}
176
177fn convert_alignment(a: &text_document::Alignment) -> Alignment {
178 match a {
179 text_document::Alignment::Left => Alignment::Left,
180 text_document::Alignment::Right => Alignment::Right,
181 text_document::Alignment::Center => Alignment::Center,
182 text_document::Alignment::Justify => Alignment::Justify,
183 }
184}
185
186pub fn convert_table(table: &TableSnapshot) -> TableLayoutParams {
187 let column_widths: Vec<f32> = table.column_widths.iter().map(|&w| w as f32).collect();
188
189 let cells: Vec<CellLayoutParams> = table.cells.iter().map(convert_cell).collect();
190
191 TableLayoutParams {
192 table_id: table.table_id,
193 rows: table.rows,
194 columns: table.columns,
195 column_widths,
196 border_width: table.format.border.unwrap_or(1) as f32,
197 cell_spacing: table.format.cell_spacing.unwrap_or(0) as f32,
198 cell_padding: table.format.cell_padding.unwrap_or(4) as f32,
199 cells,
200 }
201}
202
203fn convert_cell(cell: &CellSnapshot) -> CellLayoutParams {
204 let blocks: Vec<BlockLayoutParams> = cell.blocks.iter().map(convert_block).collect();
205
206 let background_color = cell
208 .format
209 .background_color
210 .as_ref()
211 .map(|_| [0.9, 0.9, 0.9, 1.0]); CellLayoutParams {
214 row: cell.row,
215 column: cell.column,
216 blocks,
217 background_color,
218 }
219}
220
221pub fn convert_frame(frame: &FrameSnapshot) -> FrameLayoutParams {
222 let mut blocks = Vec::new();
223 let mut tables = Vec::new();
224
225 for (i, element) in frame.elements.iter().enumerate() {
226 match element {
227 FlowElementSnapshot::Block(block) => {
228 blocks.push(convert_block(block));
229 }
230 FlowElementSnapshot::Table(table) => {
231 tables.push((i, convert_table(table)));
232 }
233 FlowElementSnapshot::Frame(_) => {
234 }
236 }
237 }
238
239 let position = match &frame.format.position {
240 Some(text_document::FramePosition::InFlow) | None => FramePosition::Inline,
241 Some(text_document::FramePosition::FloatLeft) => FramePosition::FloatLeft,
242 Some(text_document::FramePosition::FloatRight) => FramePosition::FloatRight,
243 };
244
245 FrameLayoutParams {
246 frame_id: frame.frame_id,
247 position,
248 width: frame.format.width.map(|w| w as f32),
249 height: frame.format.height.map(|h| h as f32),
250 margin_top: frame.format.top_margin.unwrap_or(0) as f32,
251 margin_bottom: frame.format.bottom_margin.unwrap_or(0) as f32,
252 margin_left: frame.format.left_margin.unwrap_or(0) as f32,
253 margin_right: frame.format.right_margin.unwrap_or(0) as f32,
254 padding: frame.format.padding.unwrap_or(0) as f32,
255 border_width: frame.format.border.unwrap_or(0) as f32,
256 blocks,
257 tables,
258 }
259}