icu_collator/options.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// The bit layout of `CollatorOptions` is adapted from ICU4C and, therefore,
6// is subject to the ICU license as described in LICENSE.
7
8//! This module contains the types that are part of the API for setting
9//! the options for the collator.
10
11use crate::{
12 elements::{CASE_MASK, TERTIARY_MASK},
13 preferences::CollationCaseFirst,
14 preferences::CollationNumericOrdering,
15 CollatorPreferences,
16};
17
18/// The collation strength that indicates how many levels to compare. The primary
19/// level considers base letters, i.e. 'a' and 'b' are unequal but 'E' and 'é'
20/// are equal, with further levels dealing with distinctions such as accents
21/// and case.
22///
23/// Note that what constitutes a base letter depends on the language and
24/// not on Unicode character properties. For example, 'ö' is analyzed as a base letter
25/// for various languages (e.g. Estonian, Finnish, Icelandic, Swedish, and Turkish),
26/// so it is distinct from 'o' even on the primary level in such languages.
27/// Inputs that are equal in fold case (as tailored for e.g. Turkish) are expected
28/// to be equal on the primary level. For example, "ß" is primary-equal with "ss".
29/// Characters that are graphically ligature-like can be primary-equal with what
30/// they appear to be ligatures of. For example, in the root collation (but not
31/// in e.g. Danish and Norwegian) "æ" is primary-equal with "ae".
32///
33/// If an earlier level isn't equal, the earlier level is decisive.
34/// If the result is equal on a level, but the strength is higher,
35/// the comparison proceeds to the next level.
36///
37/// Note that lowering the strength means that more user-perceptible differences
38/// compare as equal. This may make sense when sorting more complex structures
39/// where the string to be compared is just one field, and ties between strings
40/// that differ only in case, accent, or similar are resolved by comparing some
41/// secondary field in the larger structure to be sorted.
42///
43/// Therefore, if the sort is just a string sort without some other field for
44/// resolving ties, lowering the strength means that factors that don't make
45/// sense to the user (such as the order of items prior to sorting with a stable
46/// sort algorithm or the internal details of a sorting algorithm that doesn't
47/// provide the stability property) affect the relative order of strings that
48/// do have user-perceptible differences particularly in accents or case.
49///
50/// Lowering the strength is less of a perfomance optimization that it may seem
51/// directly from the above description. As described above, in the case
52/// of identical strings to be compared, the algorithm has to work though all
53/// the levels included in the strength without an early exit. However, this
54/// collator implements an identical prefix optimization, which examines the
55/// code units of the strings to be compared to skip the identical prefix before
56/// starting the actual collation algorithm. When the strings to be compared
57/// are identical on the byte level, they are found to be equal without the
58/// actual collation algorithm running at all! Therefore, the strength setting
59/// only has an effect (whether order effect or performance effect) for
60/// comparisons where the strings to be compared are not equal on the byte level
61/// but are equal on the primary level/strength. The common cases are that
62/// a comparison is decided on the primary level or the strings are byte
63/// equal, which narrows the performance effect of lowering the strength
64/// setting.
65///
66/// Note: The bit layout of `CollatorOptionsBitField` requires `Strength`
67/// to fit in 3 bits.
68#[derive(Eq, PartialEq, Debug, Copy, Clone, PartialOrd, Ord)]
69#[repr(u8)]
70#[non_exhaustive]
71pub enum Strength {
72 /// Compare only on the level of base letters. This level
73 /// corresponds to the ECMA-402 sensitivity "base" with
74 /// [`CaseLevel::Off`] (the default for [`CaseLevel`]) and
75 /// to ECMA-402 sensitivity "case" with [`CaseLevel::On`].
76 ///
77 /// ```
78 /// use icu::collator::{options::*, *};
79 ///
80 /// let mut options = CollatorOptions::default();
81 /// options.strength = Some(Strength::Primary);
82 /// let collator = Collator::try_new(Default::default(), options).unwrap();
83 /// assert_eq!(collator.compare("E", "é"), core::cmp::Ordering::Equal);
84 /// ```
85 Primary = 0,
86
87 /// Compare also on the secondary level, which corresponds
88 /// to diacritics in scripts that use them. This level corresponds
89 /// to the ECMA-402 sensitivity "accent".
90 ///
91 /// ```
92 /// use icu::collator::{options::*, *};
93 ///
94 /// let mut options = CollatorOptions::default();
95 /// options.strength = Some(Strength::Secondary);
96 /// let collator = Collator::try_new(Default::default(), options).unwrap();
97 /// assert_eq!(collator.compare("E", "e"), core::cmp::Ordering::Equal);
98 /// assert_eq!(collator.compare("e", "é"), core::cmp::Ordering::Less);
99 /// assert_eq!(collator.compare("あ", "ア"), core::cmp::Ordering::Equal);
100 /// assert_eq!(collator.compare("ァ", "ア"), core::cmp::Ordering::Equal);
101 /// assert_eq!(collator.compare("ア", "ア"), core::cmp::Ordering::Equal);
102 /// ```
103 Secondary = 1,
104
105 /// Compare also on the tertiary level. By default, if the separate
106 /// case level is disabled, this corresponds to case for bicameral
107 /// scripts. This level distinguishes Hiragana and Katakana. This
108 /// also captures other minor differences, such as half-width vs.
109 /// full-width when the Japanese tailoring isn't in use.
110 ///
111 /// This is the default comparison level and appropriate for
112 /// most scripts. This level corresponds to the ECMA-402
113 /// sensitivity "variant".
114 ///
115 /// ```
116 /// use icu::collator::{*, options::*};
117 /// use icu::locale::locale;
118 ///
119 /// let mut options = CollatorOptions::default();
120 /// options.strength = Some(Strength::Tertiary);
121 /// let collator =
122 /// Collator::try_new(Default::default(),
123 /// options).unwrap();
124 /// assert_eq!(collator.compare("E", "e"),
125 /// core::cmp::Ordering::Greater);
126 /// assert_eq!(collator.compare("e", "é"),
127 /// core::cmp::Ordering::Less);
128 /// assert_eq!(collator.compare("あ", "ア"),
129 /// core::cmp::Ordering::Less);
130 /// assert_eq!(collator.compare("ァ", "ア"),
131 /// core::cmp::Ordering::Less);
132 /// assert_eq!(collator.compare("ア", "ア"),
133 /// core::cmp::Ordering::Less);
134 /// assert_eq!(collator.compare("e", "e"), // Full-width e
135 /// core::cmp::Ordering::Less);
136 ///
137 /// let ja_collator =
138 /// Collator::try_new(locale!("ja").into(), options).unwrap();
139 /// assert_eq!(ja_collator.compare("E", "e"),
140 /// core::cmp::Ordering::Greater);
141 /// assert_eq!(ja_collator.compare("e", "é"),
142 /// core::cmp::Ordering::Less);
143 /// assert_eq!(ja_collator.compare("あ", "ア"),
144 /// core::cmp::Ordering::Equal); // Unlike root!
145 /// assert_eq!(ja_collator.compare("ァ", "ア"),
146 /// core::cmp::Ordering::Less);
147 /// assert_eq!(ja_collator.compare("ア", "ア"),
148 /// core::cmp::Ordering::Equal); // Unlike root!
149 /// assert_eq!(ja_collator.compare("e", "e"), // Full-width e
150 /// core::cmp::Ordering::Equal); // Unlike root!
151 /// ```
152 Tertiary = 2,
153
154 /// Compare also on the quaternary level. For Japanese, Hiragana
155 /// and Katakana are distinguished at the quaternary level. Also,
156 /// if `AlternateHandling::Shifted` is used, the collation
157 /// elements whose level gets shifted are shifted to this
158 /// level.
159 ///
160 /// ```
161 /// use icu::collator::{*, options::*};
162 /// use icu::locale::locale;
163 ///
164 /// let mut options = CollatorOptions::default();
165 /// options.strength = Some(Strength::Quaternary);
166 ///
167 /// let ja_collator =
168 /// Collator::try_new(locale!("ja").into(), options).unwrap();
169 /// assert_eq!(ja_collator.compare("あ", "ア"),
170 /// core::cmp::Ordering::Less);
171 /// assert_eq!(ja_collator.compare("ア", "ア"),
172 /// core::cmp::Ordering::Equal);
173 /// assert_eq!(ja_collator.compare("e", "e"), // Full-width e
174 /// core::cmp::Ordering::Equal);
175 ///
176 /// // Even this level doesn't distinguish everything,
177 /// // e.g. Hebrew cantillation marks are still ignored.
178 /// let collator =
179 /// Collator::try_new(Default::default(),
180 /// options).unwrap();
181 /// assert_eq!(collator.compare("דחי", "דחי֭"),
182 /// core::cmp::Ordering::Equal);
183 /// ```
184 // TODO: Thai example.
185 Quaternary = 3,
186
187 /// Compare the NFD form by code point order as the quinary
188 /// level. This level makes the comparison slower and should
189 /// not be used in the general case. However, it can be used
190 /// to distinguish full-width and half-width forms when the
191 /// Japanese tailoring is in use and to distinguish e.g.
192 /// Hebrew cantillation markse. Use this level if you need
193 /// JIS X 4061-1996 compliance for Japanese on the level of
194 /// distinguishing full-width and half-width forms.
195 ///
196 /// ```
197 /// use icu::collator::{*, options::*};
198 /// use icu::locale::locale;
199 ///
200 /// let mut options = CollatorOptions::default();
201 /// options.strength = Some(Strength::Identical);
202 ///
203 /// let ja_collator =
204 /// Collator::try_new(locale!("ja").into(), options).unwrap();
205 /// assert_eq!(ja_collator.compare("ア", "ア"),
206 /// core::cmp::Ordering::Less);
207 /// assert_eq!(ja_collator.compare("e", "e"), // Full-width e
208 /// core::cmp::Ordering::Less);
209 ///
210 /// let collator =
211 /// Collator::try_new(Default::default(),
212 /// options).unwrap();
213 /// assert_eq!(collator.compare("דחי", "דחי֭"),
214 /// core::cmp::Ordering::Less);
215 /// ```
216 Identical = 7,
217}
218
219/// What to do about characters whose comparison level can be
220/// varied dynamically.
221#[derive(Eq, PartialEq, Debug, Copy, Clone, PartialOrd, Ord)]
222#[repr(u8)]
223#[non_exhaustive]
224pub enum AlternateHandling {
225 /// Keep the characters whose level can be varied on the
226 /// primary level.
227 NonIgnorable = 0,
228 /// Shift the characters at or below `MaxVariable` to the
229 /// quaternary level.
230 Shifted = 1,
231 // Possible future values: ShiftTrimmed, Blanked
232}
233
234/// What characters get shifted to the quaternary level
235/// with `AlternateHandling::Shifted`.
236#[derive(Eq, PartialEq, Debug, Copy, Clone)]
237#[repr(u8)] // This repr is necessary for transmute safety
238#[non_exhaustive]
239pub enum MaxVariable {
240 /// Characters classified as spaces are shifted.
241 Space = 0,
242 /// Characters classified as spaces or punctuation
243 /// are shifted.
244 Punctuation = 1,
245 /// Characters classified as spaces, punctuation,
246 /// or symbols are shifted.
247 Symbol = 2,
248 /// Characters classified as spaces, punctuation,
249 /// symbols, or currency symbols are shifted.
250 Currency = 3,
251}
252
253/// Whether to distinguish case in sorting, even for sorting levels higher
254/// than tertiary, without having to use tertiary level just to enable case level differences.
255#[derive(Eq, PartialEq, Debug, Copy, Clone)]
256#[repr(u8)]
257#[non_exhaustive]
258pub enum CaseLevel {
259 /// Leave off the case level option. Case differences will be handled by default
260 /// in tertiary strength.
261 Off = 0,
262 /// Turn on the case level option, thereby making a separate level for case
263 /// differences, positioned between secondary and tertiary.
264 ///
265 /// When used together with [`Strength::Primary`], this corresponds to the
266 /// ECMA-402 sensitivity "case".
267 On = 1,
268}
269
270/// Options settable by the user of the API.
271///
272/// With the exception of reordering (BCP47 `kr`), options that can by implied by locale are
273/// set via [`CollatorPreferences`].
274///
275/// See the [spec](https://www.unicode.org/reports/tr35/tr35-collation.html#Setting_Options).
276///
277/// The setters take an `Option` so that `None` can be used to go back to default.
278///
279/// # Options
280///
281/// Examples for using the different options below can be found in the [crate-level docs](crate).
282///
283/// ## ECMA-402 Sensitivity
284///
285/// ECMA-402 `sensitivity` maps to a combination of [`Strength`] and [`CaseLevel`] as follows:
286///
287/// <dl>
288/// <dt><code>sensitivity: "base"</code></dt>
289/// <dd><a href="enum.Strength.html#variant.Primary"><code>Strength::Primary</code></a></dd>
290/// <dt><code>sensitivity: "accent"</code></dt>
291/// <dd><a href="enum.Strength.html#variant.Secondary"><code>Strength::Secondary</code></a></dd>
292/// <dt><code>sensitivity: "case"</code></dt>
293/// <dd><a href="enum.Strength.html#variant.Primary"><code>Strength::Primary</code></a> and <a href="enum.CaseLevel.html#variant.On"><code>CaseLevel::On</code></a></dd>
294/// <dt><code>sensitivity: "variant"</code></dt>
295/// <dd><a href="enum.Strength.html#variant.Tertiary"><code>Strength::Tertiary</code></a></dd>
296/// </dl>
297///
298/// ## Strength
299///
300/// This is the BCP47 key `ks`. The default is [`Strength::Tertiary`].
301///
302/// ## Alternate Handling
303///
304/// This is the BCP47 key `ka`. Note that `AlternateHandling::ShiftTrimmed` and
305/// `AlternateHandling::Blanked` are unimplemented. The default is
306/// [`AlternateHandling::NonIgnorable`], except
307/// for Thai, whose default is [`AlternateHandling::Shifted`].
308///
309/// ## Case Level
310///
311/// See the [spec](https://www.unicode.org/reports/tr35/tr35-collation.html#Case_Parameters).
312/// This is the BCP47 key `kc`. The default is [`CaseLevel::Off`].
313///
314/// # Unsupported BCP47 options
315///
316/// Reordering (BCP47 `kr`) currently cannot be set via the API and is implied
317/// by the locale of the collation. `kr` is prohibited by ECMA-402.
318///
319/// Backward second level (BCP47 `kb`) cannot be set via the API and is implied
320/// by the locale of the collation (in practice only `fr-CA` turns it on and it's
321/// off otherwise). `kb` is prohibited by ECMA-402.
322///
323/// Normalization is always enabled and cannot be turned off. Therefore, there
324/// is no option corresponding to BCP47 `kk`. `kk` is prohibited by ECMA-402.
325///
326/// Hiragana quaternary handling is part of the strength for the Japanese
327/// tailoring. The BCP47 key `kh` is unsupported. `kh` is deprecated and
328/// prohibited by ECMA-402.
329///
330/// Variable top (BCP47 `vt`) is unsupported (use Max Variable instead). `vt`
331/// is deprecated and prohibited by ECMA-402.
332///
333/// ## ECMA-402 Usage
334///
335/// ECMA-402 `usage: "search"` is represented as `-u-co-search` as part of the
336/// locale in ICU4X. However, neither ECMA-402 nor ICU4X provides prefix matching
337/// or substring matching API surface. This makes the utility of search collations
338/// very narrow: With `-u-co-search`, [`Strength::Primary`], and observing whether
339/// comparison output is [`core::cmp::Ordering::Equal`] (making no distinction between
340/// [`core::cmp::Ordering::Less`] and [`core::cmp::Ordering::Greater`]), it is
341/// possible to check if a set of human-readable strings contains a full-string
342/// fuzzy match of a user-entered string, where "fuzzy" means case-insensitive and
343/// accent-insensitive for scripts that have such concepts and something roughly
344/// similar for other scripts.
345///
346/// Due to the very limited utility, ICU4X data does not include search collations
347/// by default.
348#[non_exhaustive]
349#[derive(Debug, Copy, Clone, Default)]
350pub struct CollatorOptions {
351 /// User-specified strength collation option.
352 pub strength: Option<Strength>,
353 /// User-specified alternate handling collation option.
354 pub alternate_handling: Option<AlternateHandling>,
355 /// User-specified max variable collation option.
356 pub max_variable: Option<MaxVariable>,
357 /// User-specified case level collation option.
358 pub case_level: Option<CaseLevel>,
359}
360
361impl CollatorOptions {
362 /// Create a new `CollatorOptions` with the defaults.
363 pub const fn default() -> Self {
364 Self {
365 strength: None,
366 alternate_handling: None,
367 max_variable: None,
368 case_level: None,
369 }
370 }
371}
372
373// Make it possible to easily copy the resolved options of
374// one collator into another collator.
375impl From<ResolvedCollatorOptions> for CollatorOptions {
376 /// Convenience conversion for copying the options from an
377 /// existing collator into a new one (overriding any locale-provided
378 /// defaults of the new one!).
379 fn from(options: ResolvedCollatorOptions) -> CollatorOptions {
380 Self {
381 strength: Some(options.strength),
382 alternate_handling: Some(options.alternate_handling),
383 max_variable: Some(options.max_variable),
384 case_level: Some(options.case_level),
385 }
386 }
387}
388
389// Make it possible to easily copy the resolved preferences of
390// one collator into another collator.
391impl From<ResolvedCollatorOptions> for CollatorPreferences {
392 /// Convenience conversion for copying the preferences from an
393 /// existing collator into a new one.
394 ///
395 /// Note that some preferences may not be fully preserved when recovering them
396 /// from an already initialized collator e.g [`LocalePreferences`] and [`CollationType`], because
397 /// those are only relevant when loading the collation data.
398 ///
399 /// [`LocalePreferences`]: icu_locale_core::preferences::LocalePreferences
400 /// [`CollationType`]: crate::preferences::CollationType
401 fn from(options: ResolvedCollatorOptions) -> CollatorPreferences {
402 CollatorPreferences {
403 case_first: Some(options.case_first),
404 numeric_ordering: Some(options.numeric),
405 ..Default::default()
406 }
407 }
408}
409
410/// The resolved (actually used) options used by the collator.
411///
412/// See the documentation of `CollatorOptions`.
413#[non_exhaustive]
414#[derive(Debug, Copy, Clone)]
415pub struct ResolvedCollatorOptions {
416 /// Resolved strength collation option.
417 pub strength: Strength,
418 /// Resolved alternate handling collation option.
419 pub alternate_handling: AlternateHandling,
420 /// Resolved case first collation option.
421 pub case_first: CollationCaseFirst,
422 /// Resolved max variable collation option.
423 pub max_variable: MaxVariable,
424 /// Resolved case level collation option.
425 pub case_level: CaseLevel,
426 /// Resolved numeric collation option.
427 pub numeric: CollationNumericOrdering,
428}
429
430impl From<CollatorOptionsBitField> for ResolvedCollatorOptions {
431 fn from(options: CollatorOptionsBitField) -> ResolvedCollatorOptions {
432 Self {
433 strength: options.strength(),
434 alternate_handling: options.alternate_handling(),
435 case_first: options.case_first(),
436 max_variable: options.max_variable(),
437 case_level: if options.case_level() {
438 CaseLevel::On
439 } else {
440 CaseLevel::Off
441 },
442 numeric: if options.numeric() {
443 CollationNumericOrdering::True
444 } else {
445 CollationNumericOrdering::False
446 },
447 // `options.backward_second_level()` not exposed.
448 }
449 }
450}
451
452#[derive(Copy, Clone, Debug)]
453pub(crate) struct CollatorOptionsBitField(u32);
454
455impl Default for CollatorOptionsBitField {
456 fn default() -> Self {
457 Self::default()
458 }
459}
460
461impl CollatorOptionsBitField {
462 /// Bits 0..2 : Strength
463 const STRENGTH_MASK: u32 = 0b111;
464 /// Bits 3..4 : Alternate handling: 00 non-ignorable, 01 shifted,
465 /// 10 reserved for shift-trimmed, 11 reserved for blanked.
466 /// In other words, bit 4 is currently always 0.
467 const ALTERNATE_HANDLING_MASK: u32 = 1 << 3;
468 /// Bits 5..6 : 2-bit max variable value to be shifted by `MAX_VARIABLE_SHIFT`.
469 const MAX_VARIABLE_MASK: u32 = 0b01100000;
470 const MAX_VARIABLE_SHIFT: u32 = 5;
471 /// Bit 7 : Reserved for extending max variable.
472 /// Bit 8 : Sort uppercase first if case level or case first is on.
473 const UPPER_FIRST_MASK: u32 = 1 << 8;
474 /// Bit 9 : Keep the case bits in the tertiary weight (they trump
475 /// other tertiary values)
476 /// unless case level is on (when they are *moved* into the separate case level).
477 /// By default, the case bits are removed from the tertiary weight (ignored).
478 /// When `CASE_FIRST` is off, `UPPER_FIRST` must be off too, corresponding to
479 /// the tri-value `UCOL_CASE_FIRST` attribute: `UCOL_OFF` vs. `UCOL_LOWER_FIRST` vs.
480 /// `UCOL_UPPER_FIRST`.
481 const CASE_FIRST_MASK: u32 = 1 << 9;
482 /// Bit 10 : Insert the case level between the secondary and tertiary levels.
483 const CASE_LEVEL_MASK: u32 = 1 << 10;
484 /// Bit 11 : Backward secondary level
485 const BACKWARD_SECOND_LEVEL_MASK: u32 = 1 << 11;
486 /// Bit 12 : Numeric
487 const NUMERIC_MASK: u32 = 1 << 12;
488
489 /// Whether strength is explicitly set.
490 const EXPLICIT_STRENGTH_MASK: u32 = 1 << 31;
491 /// Whether max variable is explicitly set.
492 const EXPLICIT_MAX_VARIABLE_MASK: u32 = 1 << 30;
493 /// Whether alternate handling is explicitly set.
494 const EXPLICIT_ALTERNATE_HANDLING_MASK: u32 = 1 << 29;
495 /// Whether case level is explicitly set.
496 const EXPLICIT_CASE_LEVEL_MASK: u32 = 1 << 28;
497 /// Whether case first is explicitly set.
498 const EXPLICIT_CASE_FIRST_MASK: u32 = 1 << 27;
499 /// Whether backward secondary is explicitly set.
500 const EXPLICIT_BACKWARD_SECOND_LEVEL_MASK: u32 = 1 << 26;
501 /// Whether numeric is explicitly set.
502 const EXPLICIT_NUMERIC_MASK: u32 = 1 << 25;
503
504 /// Create a new [`CollatorOptionsBitField`] with the defaults.
505 pub const fn default() -> Self {
506 Self(Strength::Tertiary as u32)
507 }
508
509 /// This is the BCP47 key `ks`.
510 pub fn strength(self) -> Strength {
511 let mut bits = self.0 & CollatorOptionsBitField::STRENGTH_MASK;
512 if !(bits <= 3 || bits == 7) {
513 debug_assert!(false, "Bad value for strength.");
514 // If the bits say higher than `Quaternary` but
515 // lower than `Identical`, clamp to `Quaternary`.
516 bits = 3;
517 }
518 // Safety: Strength is repr(u8) and has discriminants between 0 and 7. The
519 // above code ensures that, since the mask puts us `≤ 8`
520 unsafe { core::mem::transmute(bits as u8) }
521 }
522
523 /// This is the BCP47 key `ks`. See the enum for examples.
524 pub fn set_strength(&mut self, strength: Option<Strength>) {
525 self.0 &= !CollatorOptionsBitField::STRENGTH_MASK;
526 if let Some(strength) = strength {
527 self.0 |= CollatorOptionsBitField::EXPLICIT_STRENGTH_MASK;
528 self.0 |= strength as u32;
529 } else {
530 self.0 &= !CollatorOptionsBitField::EXPLICIT_STRENGTH_MASK;
531 }
532 }
533
534 /// The maximum character class that `AlternateHandling::Shifted`
535 /// applies to.
536 pub fn max_variable(self) -> MaxVariable {
537 // Safe, because we mask two bits and shift them to the low
538 // two bits and the enum has values for 0 to 3, inclusive.
539 unsafe {
540 core::mem::transmute(
541 ((self.0 & CollatorOptionsBitField::MAX_VARIABLE_MASK)
542 >> CollatorOptionsBitField::MAX_VARIABLE_SHIFT) as u8,
543 )
544 }
545 }
546
547 /// The maximum character class that `AlternateHandling::Shifted`
548 /// applies to. See the enum for examples.
549 pub fn set_max_variable(&mut self, max_variable: Option<MaxVariable>) {
550 self.0 &= !CollatorOptionsBitField::MAX_VARIABLE_MASK;
551 if let Some(max_variable) = max_variable {
552 self.0 |= CollatorOptionsBitField::EXPLICIT_MAX_VARIABLE_MASK;
553 self.0 |= (max_variable as u32) << CollatorOptionsBitField::MAX_VARIABLE_SHIFT;
554 } else {
555 self.0 &= !CollatorOptionsBitField::EXPLICIT_MAX_VARIABLE_MASK;
556 }
557 }
558
559 /// Whether certain characters are moved from the primary level to
560 /// the quaternary level.
561 pub fn alternate_handling(self) -> AlternateHandling {
562 if (self.0 & CollatorOptionsBitField::ALTERNATE_HANDLING_MASK) != 0 {
563 AlternateHandling::Shifted
564 } else {
565 AlternateHandling::NonIgnorable
566 }
567 }
568
569 /// Whether certain characters are moved from the primary level to
570 /// the quaternary level. See the enum for examples.
571 pub fn set_alternate_handling(&mut self, alternate_handling: Option<AlternateHandling>) {
572 self.0 &= !CollatorOptionsBitField::ALTERNATE_HANDLING_MASK;
573 if let Some(alternate_handling) = alternate_handling {
574 self.0 |= CollatorOptionsBitField::EXPLICIT_ALTERNATE_HANDLING_MASK;
575 if alternate_handling == AlternateHandling::Shifted {
576 self.0 |= CollatorOptionsBitField::ALTERNATE_HANDLING_MASK;
577 }
578 } else {
579 self.0 &= !CollatorOptionsBitField::EXPLICIT_ALTERNATE_HANDLING_MASK;
580 }
581 }
582
583 /// Whether there's a dedicated case level.
584 pub fn case_level(self) -> bool {
585 (self.0 & CollatorOptionsBitField::CASE_LEVEL_MASK) != 0
586 }
587
588 /// Whether there's a dedicated case level. If `true`, detaches
589 /// the case aspect of the tertiary level and inserts it between
590 /// the secondary and tertiary levels. Can be combined with the
591 /// primary-only strength. Setting this to `true` with
592 /// `Strength::Primary` corresponds to the ECMA-402 sensitivity
593 /// "case".
594 ///
595 /// See [the ICU guide](https://unicode-org.github.io/icu/userguide/collation/concepts.html#caselevel).
596 pub fn set_case_level(&mut self, case_level: Option<bool>) {
597 self.0 &= !CollatorOptionsBitField::CASE_LEVEL_MASK;
598 if let Some(case_level) = case_level {
599 self.0 |= CollatorOptionsBitField::EXPLICIT_CASE_LEVEL_MASK;
600 if case_level {
601 self.0 |= CollatorOptionsBitField::CASE_LEVEL_MASK;
602 }
603 } else {
604 self.0 &= !CollatorOptionsBitField::EXPLICIT_CASE_LEVEL_MASK;
605 }
606 }
607
608 pub fn set_case_level_from_enum(&mut self, case_level: Option<CaseLevel>) {
609 match case_level {
610 Some(CaseLevel::On) => {
611 self.set_case_level(Some(true));
612 }
613 Some(CaseLevel::Off) => {
614 self.set_case_level(Some(false));
615 }
616 _ => self.set_case_level(None),
617 }
618 }
619
620 fn case_first(self) -> CollationCaseFirst {
621 if (self.0 & CollatorOptionsBitField::CASE_FIRST_MASK) != 0 {
622 if (self.0 & CollatorOptionsBitField::UPPER_FIRST_MASK) != 0 {
623 CollationCaseFirst::Upper
624 } else {
625 CollationCaseFirst::Lower
626 }
627 } else {
628 CollationCaseFirst::False
629 }
630 }
631
632 /// Whether case is the most significant part of the tertiary
633 /// level.
634 ///
635 /// See [the ICU guide](https://unicode-org.github.io/icu/userguide/collation/concepts.html#caselevel).
636 pub fn set_case_first(&mut self, case_first: Option<CollationCaseFirst>) {
637 self.0 &=
638 !(CollatorOptionsBitField::CASE_FIRST_MASK | CollatorOptionsBitField::UPPER_FIRST_MASK);
639 if let Some(case_first) = case_first {
640 self.0 |= CollatorOptionsBitField::EXPLICIT_CASE_FIRST_MASK;
641 match case_first {
642 CollationCaseFirst::False => {}
643 CollationCaseFirst::Lower => {
644 self.0 |= CollatorOptionsBitField::CASE_FIRST_MASK;
645 }
646 CollationCaseFirst::Upper => {
647 self.0 |= CollatorOptionsBitField::CASE_FIRST_MASK;
648 self.0 |= CollatorOptionsBitField::UPPER_FIRST_MASK;
649 }
650 _ => {
651 debug_assert!(false, "unknown variant `{case_first:?}`");
652 }
653 }
654 } else {
655 self.0 &= !CollatorOptionsBitField::EXPLICIT_CASE_FIRST_MASK;
656 }
657 }
658
659 /// Whether second level compares the last accent difference
660 /// instead of the first accent difference.
661 pub fn backward_second_level(self) -> bool {
662 (self.0 & CollatorOptionsBitField::BACKWARD_SECOND_LEVEL_MASK) != 0
663 }
664
665 /// Whether second level compares the last accent difference
666 /// instead of the first accent difference.
667 pub fn set_backward_second_level(&mut self, backward_second_level: Option<bool>) {
668 self.0 &= !CollatorOptionsBitField::BACKWARD_SECOND_LEVEL_MASK;
669 if let Some(backward_second_level) = backward_second_level {
670 self.0 |= CollatorOptionsBitField::EXPLICIT_BACKWARD_SECOND_LEVEL_MASK;
671 if backward_second_level {
672 self.0 |= CollatorOptionsBitField::BACKWARD_SECOND_LEVEL_MASK;
673 }
674 } else {
675 self.0 &= !CollatorOptionsBitField::EXPLICIT_BACKWARD_SECOND_LEVEL_MASK;
676 }
677 }
678
679 /// Whether sequences of decimal digits are compared according
680 /// to their numeric value.
681 pub fn numeric(self) -> bool {
682 (self.0 & CollatorOptionsBitField::NUMERIC_MASK) != 0
683 }
684
685 /// Whether sequences of decimal digits are compared according
686 /// to their numeric value.
687 pub fn set_numeric(&mut self, numeric: Option<bool>) {
688 self.0 &= !CollatorOptionsBitField::NUMERIC_MASK;
689 if let Some(numeric) = numeric {
690 self.0 |= CollatorOptionsBitField::EXPLICIT_NUMERIC_MASK;
691 if numeric {
692 self.0 |= CollatorOptionsBitField::NUMERIC_MASK;
693 }
694 } else {
695 self.0 &= !CollatorOptionsBitField::EXPLICIT_NUMERIC_MASK;
696 }
697 }
698
699 pub fn set_numeric_from_enum(&mut self, numeric: Option<CollationNumericOrdering>) {
700 match numeric {
701 Some(CollationNumericOrdering::True) => {
702 self.set_numeric(Some(true));
703 }
704 Some(CollationNumericOrdering::False) => {
705 self.set_numeric(Some(false));
706 }
707 Some(_) => {
708 debug_assert!(false, "unknown variant `{numeric:?}`");
709 self.set_numeric(Some(false));
710 }
711 None => self.set_numeric(None),
712 }
713 }
714
715 /// If strength is <= secondary, returns `None`.
716 /// Otherwise, returns the appropriate mask.
717 pub(crate) fn tertiary_mask(self) -> Option<u16> {
718 if self.strength() <= Strength::Secondary {
719 None
720 } else if (self.0
721 & (CollatorOptionsBitField::CASE_FIRST_MASK | CollatorOptionsBitField::CASE_LEVEL_MASK))
722 == CollatorOptionsBitField::CASE_FIRST_MASK
723 {
724 Some(CASE_MASK | TERTIARY_MASK)
725 } else {
726 Some(TERTIARY_MASK)
727 }
728 }
729
730 /// Internal upper first getter
731 pub(crate) fn upper_first(self) -> bool {
732 (self.0 & CollatorOptionsBitField::UPPER_FIRST_MASK) != 0
733 }
734
735 /// For options left as defaults in this `CollatorOptions`,
736 /// set the value from `other`. Values taken from `other`
737 /// are marked as explicitly set if they were explicitly
738 /// set in `other`.
739 pub fn set_defaults(&mut self, other: CollatorOptionsBitField) {
740 if self.0 & CollatorOptionsBitField::EXPLICIT_STRENGTH_MASK == 0 {
741 self.0 &= !CollatorOptionsBitField::STRENGTH_MASK;
742 self.0 |= other.0 & CollatorOptionsBitField::STRENGTH_MASK;
743 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_STRENGTH_MASK;
744 }
745 if self.0 & CollatorOptionsBitField::EXPLICIT_MAX_VARIABLE_MASK == 0 {
746 self.0 &= !CollatorOptionsBitField::MAX_VARIABLE_MASK;
747 self.0 |= other.0 & CollatorOptionsBitField::MAX_VARIABLE_MASK;
748 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_MAX_VARIABLE_MASK;
749 }
750 if self.0 & CollatorOptionsBitField::EXPLICIT_ALTERNATE_HANDLING_MASK == 0 {
751 self.0 &= !CollatorOptionsBitField::ALTERNATE_HANDLING_MASK;
752 self.0 |= other.0 & CollatorOptionsBitField::ALTERNATE_HANDLING_MASK;
753 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_ALTERNATE_HANDLING_MASK;
754 }
755 if self.0 & CollatorOptionsBitField::EXPLICIT_CASE_LEVEL_MASK == 0 {
756 self.0 &= !CollatorOptionsBitField::CASE_LEVEL_MASK;
757 self.0 |= other.0 & CollatorOptionsBitField::CASE_LEVEL_MASK;
758 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_CASE_LEVEL_MASK;
759 }
760 if self.0 & CollatorOptionsBitField::EXPLICIT_CASE_FIRST_MASK == 0 {
761 self.0 &= !(CollatorOptionsBitField::CASE_FIRST_MASK
762 | CollatorOptionsBitField::UPPER_FIRST_MASK);
763 self.0 |= other.0
764 & (CollatorOptionsBitField::CASE_FIRST_MASK
765 | CollatorOptionsBitField::UPPER_FIRST_MASK);
766 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_CASE_FIRST_MASK;
767 }
768 if self.0 & CollatorOptionsBitField::EXPLICIT_BACKWARD_SECOND_LEVEL_MASK == 0 {
769 self.0 &= !CollatorOptionsBitField::BACKWARD_SECOND_LEVEL_MASK;
770 self.0 |= other.0 & CollatorOptionsBitField::BACKWARD_SECOND_LEVEL_MASK;
771 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_BACKWARD_SECOND_LEVEL_MASK;
772 }
773 if self.0 & CollatorOptionsBitField::EXPLICIT_NUMERIC_MASK == 0 {
774 self.0 &= !CollatorOptionsBitField::NUMERIC_MASK;
775 self.0 |= other.0 & CollatorOptionsBitField::NUMERIC_MASK;
776 self.0 |= other.0 & CollatorOptionsBitField::EXPLICIT_NUMERIC_MASK;
777 }
778 }
779}
780
781impl From<CollatorOptions> for CollatorOptionsBitField {
782 fn from(options: CollatorOptions) -> CollatorOptionsBitField {
783 let mut result = Self::default();
784 result.set_strength(options.strength);
785 result.set_max_variable(options.max_variable);
786 result.set_alternate_handling(options.alternate_handling);
787 result.set_case_level_from_enum(options.case_level);
788 result
789 }
790}