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