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        }
34    }
35}
36
37// ── BlockInfo ───────────────────────────────────────────────────
38
39impl From<&frontend::document_inspection::BlockInfoDto> for BlockInfo {
40    fn from(dto: &frontend::document_inspection::BlockInfoDto) -> Self {
41        Self {
42            block_id: to_usize(dto.block_id),
43            block_number: to_usize(dto.block_number),
44            start: to_usize(dto.block_start),
45            length: to_usize(dto.block_length),
46        }
47    }
48}
49
50// ── FindMatch / FindOptions ─────────────────────────────────────
51
52impl FindOptions {
53    pub(crate) fn to_find_text_dto(
54        &self,
55        query: &str,
56        start_position: usize,
57    ) -> frontend::document_search::FindTextDto {
58        frontend::document_search::FindTextDto {
59            query: query.into(),
60            case_sensitive: self.case_sensitive,
61            whole_word: self.whole_word,
62            use_regex: self.use_regex,
63            search_backward: self.search_backward,
64            start_position: to_i64(start_position),
65        }
66    }
67
68    pub(crate) fn to_find_all_dto(&self, query: &str) -> frontend::document_search::FindAllDto {
69        frontend::document_search::FindAllDto {
70            query: query.into(),
71            case_sensitive: self.case_sensitive,
72            whole_word: self.whole_word,
73            use_regex: self.use_regex,
74        }
75    }
76
77    pub(crate) fn to_replace_dto(
78        &self,
79        query: &str,
80        replacement: &str,
81        replace_all: bool,
82    ) -> frontend::document_search::ReplaceTextDto {
83        frontend::document_search::ReplaceTextDto {
84            query: query.into(),
85            replacement: replacement.into(),
86            case_sensitive: self.case_sensitive,
87            whole_word: self.whole_word,
88            use_regex: self.use_regex,
89            replace_all,
90        }
91    }
92}
93
94pub fn find_result_to_match(dto: &frontend::document_search::FindResultDto) -> Option<FindMatch> {
95    if dto.found {
96        Some(FindMatch {
97            position: to_usize(dto.position),
98            length: to_usize(dto.length),
99        })
100    } else {
101        None
102    }
103}
104
105pub fn find_all_to_matches(dto: &frontend::document_search::FindAllResultDto) -> Vec<FindMatch> {
106    dto.positions
107        .iter()
108        .zip(dto.lengths.iter())
109        .map(|(&pos, &len)| FindMatch {
110            position: to_usize(pos),
111            length: to_usize(len),
112        })
113        .collect()
114}
115
116// ── Domain ↔ DTO enum conversions ───────────────────────────────
117//
118// The DTO layer has its own enum types, separate from domain enums
119// in `common::entities`. This keeps the API boundary stable even
120// when domain internals change.
121
122// Formatting DTOs have their own enum types (separate from entity DTO enums).
123// These conversion functions bridge the two at the public API boundary.
124use frontend::document_formatting::dtos as fmt_dto;
125
126fn underline_style_to_dto(v: &crate::UnderlineStyle) -> fmt_dto::UnderlineStyle {
127    match v {
128        crate::UnderlineStyle::NoUnderline => fmt_dto::UnderlineStyle::NoUnderline,
129        crate::UnderlineStyle::SingleUnderline => fmt_dto::UnderlineStyle::SingleUnderline,
130        crate::UnderlineStyle::DashUnderline => fmt_dto::UnderlineStyle::DashUnderline,
131        crate::UnderlineStyle::DotLine => fmt_dto::UnderlineStyle::DotLine,
132        crate::UnderlineStyle::DashDotLine => fmt_dto::UnderlineStyle::DashDotLine,
133        crate::UnderlineStyle::DashDotDotLine => fmt_dto::UnderlineStyle::DashDotDotLine,
134        crate::UnderlineStyle::WaveUnderline => fmt_dto::UnderlineStyle::WaveUnderline,
135        crate::UnderlineStyle::SpellCheckUnderline => fmt_dto::UnderlineStyle::SpellCheckUnderline,
136    }
137}
138
139fn vertical_alignment_to_dto(v: &crate::CharVerticalAlignment) -> fmt_dto::CharVerticalAlignment {
140    match v {
141        crate::CharVerticalAlignment::Normal => fmt_dto::CharVerticalAlignment::Normal,
142        crate::CharVerticalAlignment::SuperScript => fmt_dto::CharVerticalAlignment::SuperScript,
143        crate::CharVerticalAlignment::SubScript => fmt_dto::CharVerticalAlignment::SubScript,
144        crate::CharVerticalAlignment::Middle => fmt_dto::CharVerticalAlignment::Middle,
145        crate::CharVerticalAlignment::Bottom => fmt_dto::CharVerticalAlignment::Bottom,
146        crate::CharVerticalAlignment::Top => fmt_dto::CharVerticalAlignment::Top,
147        crate::CharVerticalAlignment::Baseline => fmt_dto::CharVerticalAlignment::Baseline,
148    }
149}
150
151fn alignment_to_dto(v: &crate::Alignment) -> fmt_dto::Alignment {
152    match v {
153        crate::Alignment::Left => fmt_dto::Alignment::Left,
154        crate::Alignment::Right => fmt_dto::Alignment::Right,
155        crate::Alignment::Center => fmt_dto::Alignment::Center,
156        crate::Alignment::Justify => fmt_dto::Alignment::Justify,
157    }
158}
159
160fn marker_to_dto(v: &crate::MarkerType) -> fmt_dto::MarkerType {
161    match v {
162        crate::MarkerType::NoMarker => fmt_dto::MarkerType::NoMarker,
163        crate::MarkerType::Unchecked => fmt_dto::MarkerType::Unchecked,
164        crate::MarkerType::Checked => fmt_dto::MarkerType::Checked,
165    }
166}
167
168// ── TextFormat → SetTextFormatDto ───────────────────────────────
169//
170// Backend DTOs now use `Option` fields: `None` means "don't change
171// this property" and `Some(value)` means "set to value".
172
173impl TextFormat {
174    pub(crate) fn to_set_dto(
175        &self,
176        position: usize,
177        anchor: usize,
178    ) -> frontend::document_formatting::SetTextFormatDto {
179        frontend::document_formatting::SetTextFormatDto {
180            position: to_i64(position),
181            anchor: to_i64(anchor),
182            font_family: self.font_family.clone(),
183            font_point_size: self.font_point_size.map(|v| v as i64),
184            font_weight: self.font_weight.map(|v| v as i64),
185            font_bold: self.font_bold,
186            font_italic: self.font_italic,
187            font_underline: self.font_underline,
188            font_overline: self.font_overline,
189            font_strikeout: self.font_strikeout,
190            letter_spacing: self.letter_spacing.map(|v| v as i64),
191            word_spacing: self.word_spacing.map(|v| v as i64),
192            underline_style: self.underline_style.as_ref().map(underline_style_to_dto),
193            vertical_alignment: self
194                .vertical_alignment
195                .as_ref()
196                .map(vertical_alignment_to_dto),
197        }
198    }
199
200    pub(crate) fn to_merge_dto(
201        &self,
202        position: usize,
203        anchor: usize,
204    ) -> frontend::document_formatting::MergeTextFormatDto {
205        frontend::document_formatting::MergeTextFormatDto {
206            position: to_i64(position),
207            anchor: to_i64(anchor),
208            font_family: self.font_family.clone(),
209            font_bold: self.font_bold,
210            font_italic: self.font_italic,
211            font_underline: self.font_underline,
212        }
213    }
214}
215
216// ── InlineElement entity → TextFormat ───────────────────────────
217
218impl From<&frontend::inline_element::dtos::InlineElementDto> for TextFormat {
219    fn from(el: &frontend::inline_element::dtos::InlineElementDto) -> Self {
220        Self {
221            font_family: el.fmt_font_family.clone(),
222            font_point_size: el.fmt_font_point_size.map(|v| v as u32),
223            font_weight: el.fmt_font_weight.map(|v| v as u32),
224            font_bold: el.fmt_font_bold,
225            font_italic: el.fmt_font_italic,
226            font_underline: el.fmt_font_underline,
227            font_overline: el.fmt_font_overline,
228            font_strikeout: el.fmt_font_strikeout,
229            letter_spacing: el.fmt_letter_spacing.map(|v| v as i32),
230            word_spacing: el.fmt_word_spacing.map(|v| v as i32),
231            underline_style: el.fmt_underline_style.clone(),
232            vertical_alignment: el.fmt_vertical_alignment.clone(),
233            anchor_href: el.fmt_anchor_href.clone(),
234            anchor_names: el.fmt_anchor_names.clone(),
235            is_anchor: el.fmt_is_anchor,
236            tooltip: el.fmt_tooltip.clone(),
237        }
238    }
239}
240
241// ── BlockFormat ─────────────────────────────────────────────────
242
243impl BlockFormat {
244    pub(crate) fn to_set_dto(
245        &self,
246        position: usize,
247        anchor: usize,
248    ) -> frontend::document_formatting::SetBlockFormatDto {
249        frontend::document_formatting::SetBlockFormatDto {
250            position: to_i64(position),
251            anchor: to_i64(anchor),
252            alignment: self.alignment.as_ref().map(alignment_to_dto),
253            heading_level: self.heading_level.map(|v| v as i64),
254            indent: self.indent.map(|v| v as i64),
255            marker: self.marker.as_ref().map(marker_to_dto),
256        }
257    }
258}
259
260impl From<&frontend::block::dtos::BlockDto> for BlockFormat {
261    fn from(b: &frontend::block::dtos::BlockDto) -> Self {
262        Self {
263            alignment: b.fmt_alignment.clone(),
264            top_margin: b.fmt_top_margin.map(|v| v as i32),
265            bottom_margin: b.fmt_bottom_margin.map(|v| v as i32),
266            left_margin: b.fmt_left_margin.map(|v| v as i32),
267            right_margin: b.fmt_right_margin.map(|v| v as i32),
268            heading_level: b.fmt_heading_level.map(|v| v as u8),
269            indent: b.fmt_indent.map(|v| v as u8),
270            text_indent: b.fmt_text_indent.map(|v| v as i32),
271            marker: b.fmt_marker.clone(),
272            tab_positions: b.fmt_tab_positions.iter().map(|&v| v as i32).collect(),
273        }
274    }
275}
276
277// ── FrameFormat ─────────────────────────────────────────────────
278
279impl FrameFormat {
280    pub(crate) fn to_set_dto(
281        &self,
282        position: usize,
283        anchor: usize,
284        frame_id: usize,
285    ) -> frontend::document_formatting::SetFrameFormatDto {
286        frontend::document_formatting::SetFrameFormatDto {
287            position: to_i64(position),
288            anchor: to_i64(anchor),
289            frame_id: to_i64(frame_id),
290            height: self.height.map(|v| v as i64),
291            width: self.width.map(|v| v as i64),
292            top_margin: self.top_margin.map(|v| v as i64),
293            bottom_margin: self.bottom_margin.map(|v| v as i64),
294            left_margin: self.left_margin.map(|v| v as i64),
295            right_margin: self.right_margin.map(|v| v as i64),
296            padding: self.padding.map(|v| v as i64),
297            border: self.border.map(|v| v as i64),
298        }
299    }
300}