1use super::ids::{PopupId, TabId};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ChromeImeTextSpanType {
7 Composition,
8 Suggestion,
9 MisspellingSuggestion,
10 Autocorrect,
11 GrammarSuggestion,
12}
13
14impl From<ChromeImeTextSpanType> for cbf::data::ime::ImeTextSpanType {
15 fn from(value: ChromeImeTextSpanType) -> Self {
16 match value {
17 ChromeImeTextSpanType::Composition => Self::Composition,
18 ChromeImeTextSpanType::Suggestion => Self::Suggestion,
19 ChromeImeTextSpanType::MisspellingSuggestion => Self::MisspellingSuggestion,
20 ChromeImeTextSpanType::Autocorrect => Self::Autocorrect,
21 ChromeImeTextSpanType::GrammarSuggestion => Self::GrammarSuggestion,
22 }
23 }
24}
25
26impl From<cbf::data::ime::ImeTextSpanType> for ChromeImeTextSpanType {
27 fn from(value: cbf::data::ime::ImeTextSpanType) -> Self {
28 match value {
29 cbf::data::ime::ImeTextSpanType::Composition => Self::Composition,
30 cbf::data::ime::ImeTextSpanType::Suggestion => Self::Suggestion,
31 cbf::data::ime::ImeTextSpanType::MisspellingSuggestion => Self::MisspellingSuggestion,
32 cbf::data::ime::ImeTextSpanType::Autocorrect => Self::Autocorrect,
33 cbf::data::ime::ImeTextSpanType::GrammarSuggestion => Self::GrammarSuggestion,
34 }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
39pub enum ChromeImeTextSpanThickness {
40 #[default]
41 None,
42 Thin,
43 Thick,
44}
45
46impl From<ChromeImeTextSpanThickness> for cbf::data::ime::ImeTextSpanThickness {
47 fn from(value: ChromeImeTextSpanThickness) -> Self {
48 match value {
49 ChromeImeTextSpanThickness::None => Self::None,
50 ChromeImeTextSpanThickness::Thin => Self::Thin,
51 ChromeImeTextSpanThickness::Thick => Self::Thick,
52 }
53 }
54}
55
56impl From<cbf::data::ime::ImeTextSpanThickness> for ChromeImeTextSpanThickness {
57 fn from(value: cbf::data::ime::ImeTextSpanThickness) -> Self {
58 match value {
59 cbf::data::ime::ImeTextSpanThickness::None => Self::None,
60 cbf::data::ime::ImeTextSpanThickness::Thin => Self::Thin,
61 cbf::data::ime::ImeTextSpanThickness::Thick => Self::Thick,
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
67pub enum ChromeImeTextSpanUnderlineStyle {
68 #[default]
69 None,
70 Solid,
71 Dot,
72 Dash,
73 Squiggle,
74}
75
76impl From<ChromeImeTextSpanUnderlineStyle> for cbf::data::ime::ImeTextSpanUnderlineStyle {
77 fn from(value: ChromeImeTextSpanUnderlineStyle) -> Self {
78 match value {
79 ChromeImeTextSpanUnderlineStyle::None => Self::None,
80 ChromeImeTextSpanUnderlineStyle::Solid => Self::Solid,
81 ChromeImeTextSpanUnderlineStyle::Dot => Self::Dot,
82 ChromeImeTextSpanUnderlineStyle::Dash => Self::Dash,
83 ChromeImeTextSpanUnderlineStyle::Squiggle => Self::Squiggle,
84 }
85 }
86}
87
88impl From<cbf::data::ime::ImeTextSpanUnderlineStyle> for ChromeImeTextSpanUnderlineStyle {
89 fn from(value: cbf::data::ime::ImeTextSpanUnderlineStyle) -> Self {
90 match value {
91 cbf::data::ime::ImeTextSpanUnderlineStyle::None => Self::None,
92 cbf::data::ime::ImeTextSpanUnderlineStyle::Solid => Self::Solid,
93 cbf::data::ime::ImeTextSpanUnderlineStyle::Dot => Self::Dot,
94 cbf::data::ime::ImeTextSpanUnderlineStyle::Dash => Self::Dash,
95 cbf::data::ime::ImeTextSpanUnderlineStyle::Squiggle => Self::Squiggle,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub struct ChromeImeTextRange {
102 pub start: i32,
103 pub end: i32,
104}
105
106impl From<ChromeImeTextRange> for cbf::data::ime::ImeTextRange {
107 fn from(value: ChromeImeTextRange) -> Self {
108 Self {
109 start: value.start,
110 end: value.end,
111 }
112 }
113}
114
115impl From<cbf::data::ime::ImeTextRange> for ChromeImeTextRange {
116 fn from(value: cbf::data::ime::ImeTextRange) -> Self {
117 Self {
118 start: value.start,
119 end: value.end,
120 }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Default)]
125pub struct ChromeImeTextSpanStyle {
126 pub underline_color: u32,
127 pub thickness: ChromeImeTextSpanThickness,
128 pub underline_style: ChromeImeTextSpanUnderlineStyle,
129 pub text_color: u32,
130 pub background_color: u32,
131 pub suggestion_highlight_color: u32,
132 pub remove_on_finish_composing: bool,
133 pub interim_char_selection: bool,
134 pub should_hide_suggestion_menu: bool,
135}
136
137impl From<ChromeImeTextSpanStyle> for cbf::data::ime::ImeTextSpanStyle {
138 fn from(value: ChromeImeTextSpanStyle) -> Self {
139 Self {
140 underline_color: value.underline_color,
141 thickness: value.thickness.into(),
142 underline_style: value.underline_style.into(),
143 text_color: value.text_color,
144 background_color: value.background_color,
145 suggestion_highlight_color: value.suggestion_highlight_color,
146 remove_on_finish_composing: value.remove_on_finish_composing,
147 interim_char_selection: value.interim_char_selection,
148 should_hide_suggestion_menu: value.should_hide_suggestion_menu,
149 }
150 }
151}
152
153impl From<cbf::data::ime::ImeTextSpanStyle> for ChromeImeTextSpanStyle {
154 fn from(value: cbf::data::ime::ImeTextSpanStyle) -> Self {
155 Self {
156 underline_color: value.underline_color,
157 thickness: value.thickness.into(),
158 underline_style: value.underline_style.into(),
159 text_color: value.text_color,
160 background_color: value.background_color,
161 suggestion_highlight_color: value.suggestion_highlight_color,
162 remove_on_finish_composing: value.remove_on_finish_composing,
163 interim_char_selection: value.interim_char_selection,
164 should_hide_suggestion_menu: value.should_hide_suggestion_menu,
165 }
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct ChromeImeTextSpan {
171 pub r#type: ChromeImeTextSpanType,
172 pub start_offset: u32,
173 pub end_offset: u32,
174 pub chrome_style: Option<ChromeImeTextSpanStyle>,
175}
176
177impl ChromeImeTextSpan {
178 pub fn new(r#type: ChromeImeTextSpanType, start_offset: u32, end_offset: u32) -> Self {
179 Self {
180 r#type,
181 start_offset,
182 end_offset,
183 chrome_style: None,
184 }
185 }
186
187 pub fn with_chrome_style(mut self, chrome_style: ChromeImeTextSpanStyle) -> Self {
188 self.chrome_style = Some(chrome_style);
189 self
190 }
191
192 pub fn no_decoration(
193 r#type: ChromeImeTextSpanType,
194 start_offset: u32,
195 end_offset: u32,
196 ) -> Self {
197 Self {
198 r#type,
199 start_offset,
200 end_offset,
201 chrome_style: Some(ChromeImeTextSpanStyle::default()),
202 }
203 }
204}
205
206impl From<ChromeImeTextSpan> for cbf::data::ime::ImeTextSpan {
207 fn from(value: ChromeImeTextSpan) -> Self {
208 Self {
209 r#type: value.r#type.into(),
210 start_offset: value.start_offset,
211 end_offset: value.end_offset,
212 style: value.chrome_style.map(Into::into),
213 }
214 }
215}
216
217impl From<cbf::data::ime::ImeTextSpan> for ChromeImeTextSpan {
218 fn from(value: cbf::data::ime::ImeTextSpan) -> Self {
219 Self {
220 r#type: value.r#type.into(),
221 start_offset: value.start_offset,
222 end_offset: value.end_offset,
223 chrome_style: value.style.map(Into::into),
224 }
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
229pub struct ChromeImeComposition {
230 pub browsing_context_id: TabId,
231 pub text: String,
232 pub selection_start: i32,
233 pub selection_end: i32,
234 pub replacement_range: Option<ChromeImeTextRange>,
235 pub spans: Vec<ChromeImeTextSpan>,
236}
237
238impl From<ChromeImeComposition> for cbf::data::ime::ImeComposition {
239 fn from(value: ChromeImeComposition) -> Self {
240 Self {
241 browsing_context_id: value.browsing_context_id.into(),
242 text: value.text,
243 selection_start: value.selection_start,
244 selection_end: value.selection_end,
245 replacement_range: value.replacement_range.map(Into::into),
246 spans: value.spans.into_iter().map(Into::into).collect(),
247 }
248 }
249}
250
251impl From<cbf::data::ime::ImeComposition> for ChromeImeComposition {
252 fn from(value: cbf::data::ime::ImeComposition) -> Self {
253 Self {
254 browsing_context_id: value.browsing_context_id.into(),
255 text: value.text,
256 selection_start: value.selection_start,
257 selection_end: value.selection_end,
258 replacement_range: value.replacement_range.map(Into::into),
259 spans: value.spans.into_iter().map(Into::into).collect(),
260 }
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, Eq)]
265pub struct ChromeImeCommitText {
266 pub browsing_context_id: TabId,
267 pub text: String,
268 pub relative_caret_position: i32,
269 pub replacement_range: Option<ChromeImeTextRange>,
270 pub spans: Vec<ChromeImeTextSpan>,
271}
272
273impl From<ChromeImeCommitText> for cbf::data::ime::ImeCommitText {
274 fn from(value: ChromeImeCommitText) -> Self {
275 Self {
276 browsing_context_id: value.browsing_context_id.into(),
277 text: value.text,
278 relative_caret_position: value.relative_caret_position,
279 replacement_range: value.replacement_range.map(Into::into),
280 spans: value.spans.into_iter().map(Into::into).collect(),
281 }
282 }
283}
284
285impl From<cbf::data::ime::ImeCommitText> for ChromeImeCommitText {
286 fn from(value: cbf::data::ime::ImeCommitText) -> Self {
287 Self {
288 browsing_context_id: value.browsing_context_id.into(),
289 text: value.text,
290 relative_caret_position: value.relative_caret_position,
291 replacement_range: value.replacement_range.map(Into::into),
292 spans: value.spans.into_iter().map(Into::into).collect(),
293 }
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298pub struct ChromeTransientImeComposition {
299 pub popup_id: PopupId,
300 pub text: String,
301 pub selection_start: i32,
302 pub selection_end: i32,
303 pub replacement_range: Option<ChromeImeTextRange>,
304 pub spans: Vec<ChromeImeTextSpan>,
305}
306
307impl From<cbf::data::transient_browsing_context::TransientImeComposition>
308 for ChromeTransientImeComposition
309{
310 fn from(value: cbf::data::transient_browsing_context::TransientImeComposition) -> Self {
311 Self {
312 popup_id: value.transient_browsing_context_id.into(),
313 text: value.text,
314 selection_start: value.selection_start,
315 selection_end: value.selection_end,
316 replacement_range: value.replacement_range.map(Into::into),
317 spans: value.spans.into_iter().map(Into::into).collect(),
318 }
319 }
320}
321
322#[derive(Debug, Clone, PartialEq, Eq)]
323pub struct ChromeTransientImeCommitText {
324 pub popup_id: PopupId,
325 pub text: String,
326 pub relative_caret_position: i32,
327 pub replacement_range: Option<ChromeImeTextRange>,
328 pub spans: Vec<ChromeImeTextSpan>,
329}
330
331impl From<cbf::data::transient_browsing_context::TransientImeCommitText>
332 for ChromeTransientImeCommitText
333{
334 fn from(value: cbf::data::transient_browsing_context::TransientImeCommitText) -> Self {
335 Self {
336 popup_id: value.transient_browsing_context_id.into(),
337 text: value.text,
338 relative_caret_position: value.relative_caret_position,
339 replacement_range: value.replacement_range.map(Into::into),
340 spans: value.spans.into_iter().map(Into::into).collect(),
341 }
342 }
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub enum ChromeConfirmCompositionBehavior {
347 DoNotKeepSelection,
348 KeepSelection,
349}
350
351impl From<ChromeConfirmCompositionBehavior> for cbf::data::ime::ConfirmCompositionBehavior {
352 fn from(value: ChromeConfirmCompositionBehavior) -> Self {
353 match value {
354 ChromeConfirmCompositionBehavior::DoNotKeepSelection => Self::DoNotKeepSelection,
355 ChromeConfirmCompositionBehavior::KeepSelection => Self::KeepSelection,
356 }
357 }
358}
359
360impl From<cbf::data::ime::ConfirmCompositionBehavior> for ChromeConfirmCompositionBehavior {
361 fn from(value: cbf::data::ime::ConfirmCompositionBehavior) -> Self {
362 match value {
363 cbf::data::ime::ConfirmCompositionBehavior::DoNotKeepSelection => {
364 Self::DoNotKeepSelection
365 }
366 cbf::data::ime::ConfirmCompositionBehavior::KeepSelection => Self::KeepSelection,
367 }
368 }
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq)]
372pub struct ChromeImeRect {
373 pub x: i32,
374 pub y: i32,
375 pub width: i32,
376 pub height: i32,
377}
378
379impl From<ChromeImeRect> for cbf::data::ime::ImeRect {
380 fn from(value: ChromeImeRect) -> Self {
381 Self {
382 x: value.x,
383 y: value.y,
384 width: value.width,
385 height: value.height,
386 }
387 }
388}
389
390impl From<cbf::data::ime::ImeRect> for ChromeImeRect {
391 fn from(value: cbf::data::ime::ImeRect) -> Self {
392 Self {
393 x: value.x,
394 y: value.y,
395 width: value.width,
396 height: value.height,
397 }
398 }
399}
400
401#[derive(Debug, Clone, PartialEq, Eq)]
402pub struct ChromeImeCompositionBounds {
403 pub range_start: i32,
404 pub range_end: i32,
405 pub character_bounds: Vec<ChromeImeRect>,
406}
407
408impl From<ChromeImeCompositionBounds> for cbf::data::ime::ImeCompositionBounds {
409 fn from(value: ChromeImeCompositionBounds) -> Self {
410 Self {
411 range_start: value.range_start,
412 range_end: value.range_end,
413 character_bounds: value.character_bounds.into_iter().map(Into::into).collect(),
414 }
415 }
416}
417
418impl From<cbf::data::ime::ImeCompositionBounds> for ChromeImeCompositionBounds {
419 fn from(value: cbf::data::ime::ImeCompositionBounds) -> Self {
420 Self {
421 range_start: value.range_start,
422 range_end: value.range_end,
423 character_bounds: value.character_bounds.into_iter().map(Into::into).collect(),
424 }
425 }
426}
427
428#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct ChromeTextSelectionBounds {
430 pub range_start: i32,
431 pub range_end: i32,
432 pub caret_rect: ChromeImeRect,
433 pub first_selection_rect: ChromeImeRect,
434}
435
436impl From<ChromeTextSelectionBounds> for cbf::data::ime::TextSelectionBounds {
437 fn from(value: ChromeTextSelectionBounds) -> Self {
438 Self {
439 range_start: value.range_start,
440 range_end: value.range_end,
441 caret_rect: value.caret_rect.into(),
442 first_selection_rect: value.first_selection_rect.into(),
443 }
444 }
445}
446
447impl From<cbf::data::ime::TextSelectionBounds> for ChromeTextSelectionBounds {
448 fn from(value: cbf::data::ime::TextSelectionBounds) -> Self {
449 Self {
450 range_start: value.range_start,
451 range_end: value.range_end,
452 caret_rect: value.caret_rect.into(),
453 first_selection_rect: value.first_selection_rect.into(),
454 }
455 }
456}
457
458#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct ChromeImeBoundsUpdate {
460 pub composition: Option<ChromeImeCompositionBounds>,
461 pub selection: Option<ChromeTextSelectionBounds>,
462}
463
464impl From<ChromeImeBoundsUpdate> for cbf::data::ime::ImeBoundsUpdate {
465 fn from(value: ChromeImeBoundsUpdate) -> Self {
466 Self {
467 composition: value.composition.map(Into::into),
468 selection: value.selection.map(Into::into),
469 }
470 }
471}
472
473impl From<cbf::data::ime::ImeBoundsUpdate> for ChromeImeBoundsUpdate {
474 fn from(value: cbf::data::ime::ImeBoundsUpdate) -> Self {
475 Self {
476 composition: value.composition.map(Into::into),
477 selection: value.selection.map(Into::into),
478 }
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::{
485 ChromeImeBoundsUpdate, ChromeImeCommitText, ChromeImeComposition,
486 ChromeImeCompositionBounds, ChromeImeRect, ChromeImeTextRange, ChromeImeTextSpan,
487 ChromeImeTextSpanStyle, ChromeImeTextSpanThickness, ChromeImeTextSpanType,
488 ChromeImeTextSpanUnderlineStyle, ChromeTextSelectionBounds,
489 };
490 use crate::data::ids::TabId;
491
492 #[test]
493 fn ime_text_span_round_trip_preserves_chrome_style() {
494 let raw = ChromeImeTextSpan {
495 r#type: ChromeImeTextSpanType::GrammarSuggestion,
496 start_offset: 2,
497 end_offset: 7,
498 chrome_style: Some(ChromeImeTextSpanStyle {
499 underline_color: 0x00112233,
500 thickness: ChromeImeTextSpanThickness::Thick,
501 underline_style: ChromeImeTextSpanUnderlineStyle::Squiggle,
502 text_color: 0x00445566,
503 background_color: 0x00778899,
504 suggestion_highlight_color: 0x00AABBCC,
505 remove_on_finish_composing: true,
506 interim_char_selection: false,
507 should_hide_suggestion_menu: true,
508 }),
509 };
510
511 let generic: cbf::data::ime::ImeTextSpan = raw.clone().into();
512 let round_trip = ChromeImeTextSpan::from(generic);
513
514 assert_eq!(round_trip, raw);
515 }
516
517 #[test]
518 fn ime_composition_and_commit_round_trip() {
519 let composition = ChromeImeComposition {
520 browsing_context_id: TabId::new(42),
521 text: "あいう".to_string(),
522 selection_start: 1,
523 selection_end: 3,
524 replacement_range: Some(ChromeImeTextRange { start: 0, end: 2 }),
525 spans: vec![ChromeImeTextSpan::new(
526 ChromeImeTextSpanType::Composition,
527 0,
528 3,
529 )],
530 };
531 let commit = ChromeImeCommitText {
532 browsing_context_id: TabId::new(42),
533 text: "確定".to_string(),
534 relative_caret_position: -1,
535 replacement_range: Some(ChromeImeTextRange { start: 0, end: 3 }),
536 spans: vec![ChromeImeTextSpan::no_decoration(
537 ChromeImeTextSpanType::Suggestion,
538 0,
539 2,
540 )],
541 };
542
543 let composition_generic: cbf::data::ime::ImeComposition = composition.clone().into();
544 let commit_generic: cbf::data::ime::ImeCommitText = commit.clone().into();
545
546 assert_eq!(ChromeImeComposition::from(composition_generic), composition);
547 assert_eq!(ChromeImeCommitText::from(commit_generic), commit);
548 }
549
550 #[test]
551 fn ime_bounds_update_round_trip() {
552 let raw = ChromeImeBoundsUpdate {
553 composition: Some(ChromeImeCompositionBounds {
554 range_start: 0,
555 range_end: 2,
556 character_bounds: vec![
557 ChromeImeRect {
558 x: 10,
559 y: 20,
560 width: 30,
561 height: 40,
562 },
563 ChromeImeRect {
564 x: 50,
565 y: 60,
566 width: 70,
567 height: 80,
568 },
569 ],
570 }),
571 selection: Some(ChromeTextSelectionBounds {
572 range_start: 1,
573 range_end: 2,
574 caret_rect: ChromeImeRect {
575 x: 100,
576 y: 200,
577 width: 10,
578 height: 20,
579 },
580 first_selection_rect: ChromeImeRect {
581 x: 110,
582 y: 210,
583 width: 15,
584 height: 25,
585 },
586 }),
587 };
588
589 let generic: cbf::data::ime::ImeBoundsUpdate = raw.clone().into();
590 let round_trip = ChromeImeBoundsUpdate::from(generic);
591
592 assert_eq!(round_trip, raw);
593 }
594}