icu_capi/
bidi.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5#[diplomat::bridge]
6pub mod ffi {
7    use alloc::boxed::Box;
8    use alloc::vec::Vec;
9
10    use core::fmt::Write;
11    use icu_properties::bidi::BidiClassAdapter;
12    use icu_properties::maps;
13    use icu_properties::BidiClass;
14    use unicode_bidi::BidiInfo;
15    use unicode_bidi::Level;
16    use unicode_bidi::Paragraph;
17
18    use crate::errors::ffi::ICU4XError;
19    use crate::provider::ffi::ICU4XDataProvider;
20
21    pub enum ICU4XBidiDirection {
22        Ltr,
23        Rtl,
24        Mixed,
25    }
26
27    #[diplomat::opaque]
28    /// An ICU4X Bidi object, containing loaded bidi data
29    #[diplomat::rust_link(icu::properties::bidi::BidiClassAdapter, Struct)]
30    // #[diplomat::rust_link(icu::properties::maps::load_bidi_class, Struct)]
31    pub struct ICU4XBidi(pub maps::CodePointMapData<BidiClass>);
32
33    impl ICU4XBidi {
34        /// Creates a new [`ICU4XBidi`] from locale data.
35        #[diplomat::rust_link(icu::properties::bidi::BidiClassAdapter::new, FnInStruct)]
36        #[diplomat::attr(all(supports = constructors, supports = fallible_constructors), constructor)]
37        pub fn create(provider: &ICU4XDataProvider) -> Result<Box<ICU4XBidi>, ICU4XError> {
38            Ok(Box::new(ICU4XBidi(call_constructor_unstable!(
39                maps::bidi_class [m => Ok(m.static_to_owned())],
40                maps::load_bidi_class,
41                provider,
42            )?)))
43        }
44
45        /// Use the data loaded in this object to process a string and calculate bidi information
46        ///
47        /// Takes in a Level for the default level, if it is an invalid value it will default to LTR
48        ///
49        /// Returns nothing if `text` is invalid UTF-8.
50        #[diplomat::rust_link(unicode_bidi::BidiInfo::new_with_data_source, FnInStruct)]
51        #[diplomat::rust_link(
52            icu::properties::bidi::BidiClassAdapter::bidi_class,
53            FnInStruct,
54            hidden
55        )]
56        #[diplomat::attr(dart, disable)]
57        pub fn for_text<'text>(
58            &self,
59            text: &'text DiplomatStr,
60            default_level: u8,
61        ) -> Option<Box<ICU4XBidiInfo<'text>>> {
62            let text = core::str::from_utf8(text).ok()?;
63
64            let data = self.0.as_borrowed();
65            let adapter = BidiClassAdapter::new(data);
66
67            Some(Box::new(ICU4XBidiInfo(BidiInfo::new_with_data_source(
68                &adapter,
69                text,
70                Level::new(default_level).ok(),
71            ))))
72        }
73
74        /// Use the data loaded in this object to process a string and calculate bidi information
75        ///
76        /// Takes in a Level for the default level, if it is an invalid value it will default to LTR
77        #[diplomat::rust_link(unicode_bidi::BidiInfo::new_with_data_source, FnInStruct)]
78        #[diplomat::rust_link(
79            icu::properties::bidi::BidiClassAdapter::bidi_class,
80            FnInStruct,
81            hidden
82        )]
83        #[diplomat::attr(not(dart), disable)]
84        #[diplomat::attr(*, rename = "for_text")]
85        #[diplomat::skip_if_ast]
86        pub fn for_text_valid_utf8<'text>(
87            &self,
88            text: &'text str,
89            default_level: u8,
90        ) -> Box<ICU4XBidiInfo<'text>> {
91            let data = self.0.as_borrowed();
92            let adapter = BidiClassAdapter::new(data);
93
94            Box::new(ICU4XBidiInfo(BidiInfo::new_with_data_source(
95                &adapter,
96                text,
97                Level::new(default_level).ok(),
98            )))
99        }
100
101        /// Utility function for producing reorderings given a list of levels
102        ///
103        /// Produces a map saying which visual index maps to which source index.
104        ///
105        /// The levels array must not have values greater than 126 (this is the
106        /// Bidi maximum explicit depth plus one).
107        /// Failure to follow this invariant may lead to incorrect results,
108        /// but is still safe.
109        #[diplomat::rust_link(unicode_bidi::BidiInfo::reorder_visual, FnInStruct)]
110        pub fn reorder_visual(&self, levels: &[u8]) -> Box<ICU4XReorderedIndexMap> {
111            let levels = Level::from_slice_unchecked(levels);
112            Box::new(ICU4XReorderedIndexMap(BidiInfo::reorder_visual(levels)))
113        }
114
115        /// Check if a Level returned by level_at is an RTL level.
116        ///
117        /// Invalid levels (numbers greater than 125) will be assumed LTR
118        #[diplomat::rust_link(unicode_bidi::Level::is_rtl, FnInStruct)]
119        pub fn level_is_rtl(level: u8) -> bool {
120            Level::new(level).unwrap_or_else(|_| Level::ltr()).is_rtl()
121        }
122
123        /// Check if a Level returned by level_at is an LTR level.
124        ///
125        /// Invalid levels (numbers greater than 125) will be assumed LTR
126        #[diplomat::rust_link(unicode_bidi::Level::is_ltr, FnInStruct)]
127        pub fn level_is_ltr(level: u8) -> bool {
128            Level::new(level).unwrap_or_else(|_| Level::ltr()).is_ltr()
129        }
130
131        /// Get a basic RTL Level value
132        #[diplomat::rust_link(unicode_bidi::Level::rtl, FnInStruct)]
133        pub fn level_rtl() -> u8 {
134            Level::rtl().number()
135        }
136
137        /// Get a simple LTR Level value
138        #[diplomat::rust_link(unicode_bidi::Level::ltr, FnInStruct)]
139        pub fn level_ltr() -> u8 {
140            Level::ltr().number()
141        }
142    }
143
144    /// Thin wrapper around a vector that maps visual indices to source indices
145    ///
146    /// `map[visualIndex] = sourceIndex`
147    ///
148    /// Produced by `reorder_visual()` on [`ICU4XBidi`].
149    #[diplomat::opaque]
150    pub struct ICU4XReorderedIndexMap(pub Vec<usize>);
151
152    impl ICU4XReorderedIndexMap {
153        /// Get this as a slice/array of indices
154        #[diplomat::attr(supports = accessors, getter)]
155        pub fn as_slice<'a>(&'a self) -> &'a [usize] {
156            &self.0
157        }
158
159        /// The length of this map
160        #[diplomat::attr(supports = accessors, getter = "length")]
161        pub fn len(&self) -> usize {
162            self.0.len()
163        }
164
165        /// Whether this map is empty
166        #[diplomat::attr(supports = accessors, getter)]
167        pub fn is_empty(&self) -> bool {
168            self.0.is_empty()
169        }
170
171        /// Get element at `index`. Returns 0 when out of bounds
172        /// (note that 0 is also a valid in-bounds value, please use `len()`
173        /// to avoid out-of-bounds)
174        #[diplomat::attr(supports = indexing, indexer)]
175        pub fn get(&self, index: usize) -> usize {
176            self.0.get(index).copied().unwrap_or(0)
177        }
178    }
179
180    /// An object containing bidi information for a given string, produced by `for_text()` on `ICU4XBidi`
181    #[diplomat::rust_link(unicode_bidi::BidiInfo, Struct)]
182    #[diplomat::opaque]
183    pub struct ICU4XBidiInfo<'text>(pub BidiInfo<'text>);
184
185    impl<'text> ICU4XBidiInfo<'text> {
186        /// The number of paragraphs contained here
187        #[diplomat::attr(supports = accessors, getter)]
188        pub fn paragraph_count(&self) -> usize {
189            self.0.paragraphs.len()
190        }
191
192        /// Get the nth paragraph, returning `None` if out of bounds
193        pub fn paragraph_at(&'text self, n: usize) -> Option<Box<ICU4XBidiParagraph<'text>>> {
194            self.0
195                .paragraphs
196                .get(n)
197                .map(|p| Box::new(ICU4XBidiParagraph(Paragraph::new(&self.0, p))))
198        }
199
200        /// The number of bytes in this full text
201        #[diplomat::attr(supports = accessors, getter)]
202        pub fn size(&self) -> usize {
203            self.0.levels.len()
204        }
205
206        /// Get the BIDI level at a particular byte index in the full text.
207        /// This integer is conceptually a `unicode_bidi::Level`,
208        /// and can be further inspected using the static methods on ICU4XBidi.
209        ///
210        /// Returns 0 (equivalent to `Level::ltr()`) on error
211        pub fn level_at(&self, pos: usize) -> u8 {
212            if let Some(l) = self.0.levels.get(pos) {
213                l.number()
214            } else {
215                0
216            }
217        }
218    }
219
220    /// Bidi information for a single processed paragraph
221    #[diplomat::opaque]
222    pub struct ICU4XBidiParagraph<'info>(pub Paragraph<'info, 'info>);
223
224    impl<'info> ICU4XBidiParagraph<'info> {
225        /// Given a paragraph index `n` within the surrounding text, this sets this
226        /// object to the paragraph at that index. Returns `ICU4XError::OutOfBoundsError` when out of bounds.
227        ///
228        /// This is equivalent to calling `paragraph_at()` on `ICU4XBidiInfo` but doesn't
229        /// create a new object
230        pub fn set_paragraph_in_text(&mut self, n: usize) -> Result<(), ICU4XError> {
231            let para = self
232                .0
233                .info
234                .paragraphs
235                .get(n)
236                .ok_or(ICU4XError::OutOfBoundsError)?;
237            self.0 = Paragraph::new(self.0.info, para);
238            Ok(())
239        }
240        #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)]
241        #[diplomat::attr(supports = accessors, getter)]
242        /// The primary direction of this paragraph
243        pub fn direction(&self) -> ICU4XBidiDirection {
244            self.0.direction().into()
245        }
246
247        /// The number of bytes in this paragraph
248        #[diplomat::rust_link(unicode_bidi::ParagraphInfo::len, FnInStruct)]
249        #[diplomat::attr(supports = accessors, getter)]
250        pub fn size(&self) -> usize {
251            self.0.para.len()
252        }
253
254        /// The start index of this paragraph within the source text
255        #[diplomat::attr(supports = accessors, getter)]
256        pub fn range_start(&self) -> usize {
257            self.0.para.range.start
258        }
259
260        /// The end index of this paragraph within the source text
261        #[diplomat::attr(supports = accessors, getter)]
262        pub fn range_end(&self) -> usize {
263            self.0.para.range.end
264        }
265
266        /// Reorder a line based on display order. The ranges are specified relative to the source text and must be contained
267        /// within this paragraph's range.
268        #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)]
269        pub fn reorder_line(
270            &self,
271            range_start: usize,
272            range_end: usize,
273            out: &mut DiplomatWriteable,
274        ) -> Result<(), ICU4XError> {
275            if range_start < self.range_start() || range_end > self.range_end() {
276                return Err(ICU4XError::OutOfBoundsError);
277            }
278
279            let info = self.0.info;
280            let para = self.0.para;
281
282            let reordered = info.reorder_line(para, range_start..range_end);
283
284            Ok(out.write_str(&reordered)?)
285        }
286
287        /// Get the BIDI level at a particular byte index in this paragraph.
288        /// This integer is conceptually a `unicode_bidi::Level`,
289        /// and can be further inspected using the static methods on ICU4XBidi.
290        ///
291        /// Returns 0 (equivalent to `Level::ltr()`) on error
292        #[diplomat::rust_link(unicode_bidi::Paragraph::level_at, FnInStruct)]
293        pub fn level_at(&self, pos: usize) -> u8 {
294            if pos >= self.size() {
295                return 0;
296            }
297
298            self.0.level_at(pos).number()
299        }
300    }
301}
302
303use unicode_bidi::Direction;
304
305impl From<Direction> for ffi::ICU4XBidiDirection {
306    fn from(other: Direction) -> Self {
307        match other {
308            Direction::Ltr => Self::Ltr,
309            Direction::Rtl => Self::Rtl,
310            Direction::Mixed => Self::Mixed,
311        }
312    }
313}