Skip to main content

text_document/
convert.rs

1//! Conversion helpers between public API types and backend DTOs.
2//!
3//! The backend uses `i64` for all positions/sizes. The public API uses `usize`.
4//! All Option mapping between public format structs and backend DTOs lives here.
5
6use crate::{
7    BlockFormat, BlockInfo, DocumentStats, FindMatch, FindOptions, FrameFormat, TextFormat,
8};
9
10// ── Position conversion ─────────────────────────────────────────
11
12pub fn to_i64(v: usize) -> i64 {
13    debug_assert!(v <= i64::MAX as usize, "position overflow: {v}");
14    v as i64
15}
16
17pub fn to_usize(v: i64) -> usize {
18    assert!(v >= 0, "negative position: {v}");
19    v as usize
20}
21
22// ── DocumentStats ───────────────────────────────────────────────
23
24impl From<&frontend::document_inspection::DocumentStatsDto> for DocumentStats {
25    fn from(dto: &frontend::document_inspection::DocumentStatsDto) -> Self {
26        Self {
27            character_count: to_usize(dto.character_count),
28            word_count: to_usize(dto.word_count),
29            block_count: to_usize(dto.block_count),
30            frame_count: to_usize(dto.frame_count),
31            image_count: to_usize(dto.image_count),
32            list_count: to_usize(dto.list_count),
33            table_count: to_usize(dto.table_count),
34        }
35    }
36}
37
38// ── BlockInfo ───────────────────────────────────────────────────
39
40impl From<&frontend::document_inspection::BlockInfoDto> for BlockInfo {
41    fn from(dto: &frontend::document_inspection::BlockInfoDto) -> Self {
42        Self {
43            block_id: to_usize(dto.block_id),
44            block_number: to_usize(dto.block_number),
45            start: to_usize(dto.block_start),
46            length: to_usize(dto.block_length),
47        }
48    }
49}
50
51// ── FindMatch / FindOptions ─────────────────────────────────────
52
53impl FindOptions {
54    pub(crate) fn to_find_text_dto(
55        &self,
56        query: &str,
57        start_position: usize,
58    ) -> frontend::document_search::FindTextDto {
59        frontend::document_search::FindTextDto {
60            query: query.into(),
61            case_sensitive: self.case_sensitive,
62            whole_word: self.whole_word,
63            use_regex: self.use_regex,
64            search_backward: self.search_backward,
65            start_position: to_i64(start_position),
66        }
67    }
68
69    pub(crate) fn to_find_all_dto(&self, query: &str) -> frontend::document_search::FindAllDto {
70        frontend::document_search::FindAllDto {
71            query: query.into(),
72            case_sensitive: self.case_sensitive,
73            whole_word: self.whole_word,
74            use_regex: self.use_regex,
75        }
76    }
77
78    pub(crate) fn to_replace_dto(
79        &self,
80        query: &str,
81        replacement: &str,
82        replace_all: bool,
83    ) -> frontend::document_search::ReplaceTextDto {
84        frontend::document_search::ReplaceTextDto {
85            query: query.into(),
86            replacement: replacement.into(),
87            case_sensitive: self.case_sensitive,
88            whole_word: self.whole_word,
89            use_regex: self.use_regex,
90            replace_all,
91        }
92    }
93}
94
95pub fn find_result_to_match(dto: &frontend::document_search::FindResultDto) -> Option<FindMatch> {
96    if dto.found {
97        Some(FindMatch {
98            position: to_usize(dto.position),
99            length: to_usize(dto.length),
100        })
101    } else {
102        None
103    }
104}
105
106pub fn find_all_to_matches(dto: &frontend::document_search::FindAllResultDto) -> Vec<FindMatch> {
107    dto.positions
108        .iter()
109        .zip(dto.lengths.iter())
110        .map(|(&pos, &len)| FindMatch {
111            position: to_usize(pos),
112            length: to_usize(len),
113        })
114        .collect()
115}
116
117// ── Domain ↔ DTO enum conversions ───────────────────────────────
118//
119// The DTO layer has its own enum types, separate from domain enums
120// in `common::entities`. This keeps the API boundary stable even
121// when domain internals change.
122
123// Formatting DTOs have their own enum types (separate from entity DTO enums).
124// These conversion functions bridge the two at the public API boundary.
125use frontend::document_formatting::dtos as fmt_dto;
126
127fn underline_style_to_dto(v: &crate::UnderlineStyle) -> fmt_dto::UnderlineStyle {
128    match v {
129        crate::UnderlineStyle::NoUnderline => fmt_dto::UnderlineStyle::NoUnderline,
130        crate::UnderlineStyle::SingleUnderline => fmt_dto::UnderlineStyle::SingleUnderline,
131        crate::UnderlineStyle::DashUnderline => fmt_dto::UnderlineStyle::DashUnderline,
132        crate::UnderlineStyle::DotLine => fmt_dto::UnderlineStyle::DotLine,
133        crate::UnderlineStyle::DashDotLine => fmt_dto::UnderlineStyle::DashDotLine,
134        crate::UnderlineStyle::DashDotDotLine => fmt_dto::UnderlineStyle::DashDotDotLine,
135        crate::UnderlineStyle::WaveUnderline => fmt_dto::UnderlineStyle::WaveUnderline,
136        crate::UnderlineStyle::SpellCheckUnderline => fmt_dto::UnderlineStyle::SpellCheckUnderline,
137    }
138}
139
140fn vertical_alignment_to_dto(v: &crate::CharVerticalAlignment) -> fmt_dto::CharVerticalAlignment {
141    match v {
142        crate::CharVerticalAlignment::Normal => fmt_dto::CharVerticalAlignment::Normal,
143        crate::CharVerticalAlignment::SuperScript => fmt_dto::CharVerticalAlignment::SuperScript,
144        crate::CharVerticalAlignment::SubScript => fmt_dto::CharVerticalAlignment::SubScript,
145        crate::CharVerticalAlignment::Middle => fmt_dto::CharVerticalAlignment::Middle,
146        crate::CharVerticalAlignment::Bottom => fmt_dto::CharVerticalAlignment::Bottom,
147        crate::CharVerticalAlignment::Top => fmt_dto::CharVerticalAlignment::Top,
148        crate::CharVerticalAlignment::Baseline => fmt_dto::CharVerticalAlignment::Baseline,
149    }
150}
151
152fn alignment_to_dto(v: &crate::Alignment) -> fmt_dto::Alignment {
153    match v {
154        crate::Alignment::Left => fmt_dto::Alignment::Left,
155        crate::Alignment::Right => fmt_dto::Alignment::Right,
156        crate::Alignment::Center => fmt_dto::Alignment::Center,
157        crate::Alignment::Justify => fmt_dto::Alignment::Justify,
158    }
159}
160
161fn marker_to_dto(v: &crate::MarkerType) -> fmt_dto::MarkerType {
162    match v {
163        crate::MarkerType::NoMarker => fmt_dto::MarkerType::NoMarker,
164        crate::MarkerType::Unchecked => fmt_dto::MarkerType::Unchecked,
165        crate::MarkerType::Checked => fmt_dto::MarkerType::Checked,
166    }
167}
168
169// ── TextFormat → SetTextFormatDto ───────────────────────────────
170//
171// Backend DTOs now use `Option` fields: `None` means "don't change
172// this property" and `Some(value)` means "set to value".
173
174impl TextFormat {
175    pub(crate) fn to_set_dto(
176        &self,
177        position: usize,
178        anchor: usize,
179    ) -> frontend::document_formatting::SetTextFormatDto {
180        frontend::document_formatting::SetTextFormatDto {
181            position: to_i64(position),
182            anchor: to_i64(anchor),
183            font_family: self.font_family.clone(),
184            font_point_size: self.font_point_size.map(|v| v as i64),
185            font_weight: self.font_weight.map(|v| v as i64),
186            font_bold: self.font_bold,
187            font_italic: self.font_italic,
188            font_underline: self.font_underline,
189            font_overline: self.font_overline,
190            font_strikeout: self.font_strikeout,
191            letter_spacing: self.letter_spacing.map(|v| v as i64),
192            word_spacing: self.word_spacing.map(|v| v as i64),
193            underline_style: self.underline_style.as_ref().map(underline_style_to_dto),
194            vertical_alignment: self
195                .vertical_alignment
196                .as_ref()
197                .map(vertical_alignment_to_dto),
198        }
199    }
200
201    pub(crate) fn to_merge_dto(
202        &self,
203        position: usize,
204        anchor: usize,
205    ) -> frontend::document_formatting::MergeTextFormatDto {
206        frontend::document_formatting::MergeTextFormatDto {
207            position: to_i64(position),
208            anchor: to_i64(anchor),
209            font_family: self.font_family.clone(),
210            font_bold: self.font_bold,
211            font_italic: self.font_italic,
212            font_underline: self.font_underline,
213        }
214    }
215}
216
217// ── InlineElement entity → TextFormat ───────────────────────────
218
219impl From<&frontend::inline_element::dtos::InlineElementDto> for TextFormat {
220    fn from(el: &frontend::inline_element::dtos::InlineElementDto) -> Self {
221        Self {
222            font_family: el.fmt_font_family.clone(),
223            font_point_size: el.fmt_font_point_size.map(|v| v as u32),
224            font_weight: el.fmt_font_weight.map(|v| v as u32),
225            font_bold: el.fmt_font_bold,
226            font_italic: el.fmt_font_italic,
227            font_underline: el.fmt_font_underline,
228            font_overline: el.fmt_font_overline,
229            font_strikeout: el.fmt_font_strikeout,
230            letter_spacing: el.fmt_letter_spacing.map(|v| v as i32),
231            word_spacing: el.fmt_word_spacing.map(|v| v as i32),
232            underline_style: el.fmt_underline_style.clone(),
233            vertical_alignment: el.fmt_vertical_alignment.clone(),
234            anchor_href: el.fmt_anchor_href.clone(),
235            anchor_names: el.fmt_anchor_names.clone(),
236            is_anchor: el.fmt_is_anchor,
237            tooltip: el.fmt_tooltip.clone(),
238            foreground_color: None,
239            background_color: None,
240            underline_color: None,
241        }
242    }
243}
244
245// ── BlockFormat ─────────────────────────────────────────────────
246
247impl BlockFormat {
248    pub(crate) fn to_set_dto(
249        &self,
250        position: usize,
251        anchor: usize,
252    ) -> frontend::document_formatting::SetBlockFormatDto {
253        frontend::document_formatting::SetBlockFormatDto {
254            position: to_i64(position),
255            anchor: to_i64(anchor),
256            alignment: self.alignment.as_ref().map(alignment_to_dto),
257            heading_level: self.heading_level.map(|v| v as i64),
258            indent: self.indent.map(|v| v as i64),
259            marker: self.marker.as_ref().map(marker_to_dto),
260            line_height: self.line_height.map(|v| (v * 1000.0) as i64),
261            non_breakable_lines: self.non_breakable_lines,
262            direction: self.direction.clone(),
263            background_color: self.background_color.clone(),
264        }
265    }
266}
267
268impl From<&frontend::block::dtos::BlockDto> for BlockFormat {
269    fn from(b: &frontend::block::dtos::BlockDto) -> Self {
270        Self {
271            alignment: b.fmt_alignment.clone(),
272            top_margin: b.fmt_top_margin.map(|v| v as i32),
273            bottom_margin: b.fmt_bottom_margin.map(|v| v as i32),
274            left_margin: b.fmt_left_margin.map(|v| v as i32),
275            right_margin: b.fmt_right_margin.map(|v| v as i32),
276            heading_level: b.fmt_heading_level.map(|v| v as u8),
277            indent: b.fmt_indent.map(|v| v as u8),
278            text_indent: b.fmt_text_indent.map(|v| v as i32),
279            marker: b.fmt_marker.clone(),
280            tab_positions: b.fmt_tab_positions.iter().map(|&v| v as i32).collect(),
281            line_height: b.fmt_line_height.map(|v| v as f32 / 1000.0),
282            non_breakable_lines: b.fmt_non_breakable_lines,
283            direction: b.fmt_direction.clone(),
284            background_color: b.fmt_background_color.clone(),
285        }
286    }
287}
288
289// ── FrameFormat ─────────────────────────────────────────────────
290
291impl FrameFormat {
292    pub(crate) fn to_set_dto(
293        &self,
294        position: usize,
295        anchor: usize,
296        frame_id: usize,
297    ) -> frontend::document_formatting::SetFrameFormatDto {
298        frontend::document_formatting::SetFrameFormatDto {
299            position: to_i64(position),
300            anchor: to_i64(anchor),
301            frame_id: to_i64(frame_id),
302            height: self.height.map(|v| v as i64),
303            width: self.width.map(|v| v as i64),
304            top_margin: self.top_margin.map(|v| v as i64),
305            bottom_margin: self.bottom_margin.map(|v| v as i64),
306            left_margin: self.left_margin.map(|v| v as i64),
307            right_margin: self.right_margin.map(|v| v as i64),
308            padding: self.padding.map(|v| v as i64),
309            border: self.border.map(|v| v as i64),
310        }
311    }
312}
313
314// ── TableFormat ────────────────────────────────────────────────
315
316impl crate::flow::TableFormat {
317    pub(crate) fn to_set_dto(
318        &self,
319        table_id: usize,
320    ) -> frontend::document_formatting::SetTableFormatDto {
321        frontend::document_formatting::SetTableFormatDto {
322            table_id: to_i64(table_id),
323            border: self.border.map(|v| v as i64),
324            cell_spacing: self.cell_spacing.map(|v| v as i64),
325            cell_padding: self.cell_padding.map(|v| v as i64),
326            width: self.width.map(|v| v as i64),
327            alignment: self.alignment.as_ref().map(alignment_to_dto),
328        }
329    }
330}
331
332// ── CellFormat ─────────────────────────────────────────────────
333
334fn cell_vertical_alignment_to_dto(
335    v: &crate::flow::CellVerticalAlignment,
336) -> fmt_dto::CellVerticalAlignment {
337    match v {
338        crate::flow::CellVerticalAlignment::Top => fmt_dto::CellVerticalAlignment::Top,
339        crate::flow::CellVerticalAlignment::Middle => fmt_dto::CellVerticalAlignment::Middle,
340        crate::flow::CellVerticalAlignment::Bottom => fmt_dto::CellVerticalAlignment::Bottom,
341    }
342}
343
344impl crate::flow::CellFormat {
345    pub(crate) fn to_set_dto(
346        &self,
347        cell_id: usize,
348    ) -> frontend::document_formatting::SetTableCellFormatDto {
349        frontend::document_formatting::SetTableCellFormatDto {
350            cell_id: to_i64(cell_id),
351            padding: self.padding.map(|v| v as i64),
352            border: self.border.map(|v| v as i64),
353            vertical_alignment: self
354                .vertical_alignment
355                .as_ref()
356                .map(cell_vertical_alignment_to_dto),
357            background_color: self.background_color.clone(),
358        }
359    }
360}