mod tables;
#[cfg(test)]
mod tests;
use crate::GraphemeProps;
use crate::line::LineProps;
use crate::word::WordProps;
const _: () = assert!(
tables::GRAPHEME_CLUSTER_BREAK_MASK == 0xF
&& tables::INDIC_CONJUNCT_BREAK_SHIFT == tables::GRAPHEME_CLUSTER_BREAK_SHIFT + 4
&& tables::INDIC_CONJUNCT_BREAK_MASK == 0x3
&& tables::GRAPHEME_PICTOGRAPHIC == 1 << (tables::GRAPHEME_CLUSTER_BREAK_SHIFT + 6)
);
const _: () = assert!(
tables::LINE_CLASS_SHIFT == 0
&& tables::LINE_CLASS_MASK == 0x3F
&& tables::LINE_EAST_ASIAN == 0x40
&& tables::LINE_UNASSIGNED_PICTOGRAPHIC == 0x80
);
const _: () = assert!(
tables::WORD_CLASS_SHIFT == 0
&& tables::WORD_CLASS_MASK == 0x1F
&& tables::WORD_PICTOGRAPHIC == 0x20
&& tables::WORD_RUN_SHIFT == 6
&& tables::WORD_RUN_MASK == 0x3
&& tables::WORD_RUN_SOUTHEAST_ASIAN == 1
&& tables::WORD_RUN_CHINESE_JAPANESE == 2
);
pub const UNICODE_VERSION: (u8, u8, u8) = tables::UNICODE_VERSION;
#[inline]
pub fn core_props(ch: char) -> CoreProps {
CoreProps(tables::core_word(ch))
}
#[inline]
pub fn rare_props(ch: char) -> RareProps {
RareProps(tables::rare_word(ch))
}
#[inline]
pub fn line_break(ch: char) -> LineBreak {
line_props(ch).line_break()
}
#[inline]
pub(crate) fn line_props(ch: char) -> LineProps {
let bits = match tables::LINE_LATIN_1.get(ch as usize) {
Some(&bits) => bits,
None => tables::line_word(ch),
};
LineProps::from_bits(bits)
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct CoreProps(u32);
impl CoreProps {
#[inline]
pub const fn bidi_class(self) -> BidiClass {
BidiClass(((self.0 >> tables::BIDI_CLASS_SHIFT) & tables::BIDI_CLASS_MASK) as u8)
}
#[inline]
pub const fn scripts(self) -> ScriptWithExt {
ScriptWithExt(((self.0 >> tables::SCRIPT_SHIFT) & tables::SCRIPT_MASK) as u16)
}
#[inline]
pub fn script(self) -> ScriptId {
let scripts = self.scripts();
match scripts.single() {
Some(script) => script,
None => match scripts.set_run().first() {
Some(script) => *script,
None => ScriptId::UNKNOWN,
},
}
}
#[inline]
pub fn script_extensions(self) -> &'static [ScriptId] {
let scripts = self.scripts();
match scripts.single() {
Some(script) => {
let at = usize::from(script.0);
tables::SCRIPT_RUNS.get(at..at + 1).unwrap_or(&[])
}
None => scripts.set_run().get(1..).unwrap_or(&[]),
}
}
#[inline]
pub const fn bracket_pair(self) -> Option<BracketPairId> {
match (self.0 >> tables::BRACKET_PAIR_SHIFT) & tables::BRACKET_PAIR_MASK {
0 => None,
stored => Some(BracketPairId((stored - 1) as u8)),
}
}
#[inline]
pub const fn opens_bracket(self) -> bool {
self.0 & tables::OPENS_BRACKET != 0
}
#[inline]
pub fn closing_bracket(self) -> Option<char> {
let pair = self.bracket_pair()?;
tables::BRACKET_CLOSINGS.get(usize::from(pair.0)).copied()
}
#[inline]
pub const fn is_regional_indicator(self) -> bool {
self.0 & tables::REGIONAL_INDICATOR != 0
}
#[inline]
pub const fn is_extended_pictographic(self) -> bool {
self.0 & tables::EXTENDED_PICTOGRAPHIC != 0
}
#[inline]
pub const fn is_emoji_presentation(self) -> bool {
self.0 & tables::EMOJI_PRESENTATION != 0
}
#[inline]
pub const fn is_paragraph_separator(self) -> bool {
self.0 & tables::PARAGRAPH_SEPARATOR != 0
}
#[inline]
pub const fn is_white_space(self) -> bool {
self.0 & tables::WHITE_SPACE != 0
}
#[inline]
pub const fn is_complex_context(self) -> bool {
self.0 & tables::COMPLEX_CONTEXT != 0
}
#[inline]
pub const fn is_mark(self) -> bool {
self.0 & tables::MARK != 0
}
#[inline]
pub const fn is_emoji(self) -> bool {
self.0 & tables::EMOJI != 0
}
}
#[inline]
pub(crate) fn word_props(ch: char) -> WordProps {
let bits = match tables::WORD_LATIN_1.get(ch as usize) {
Some(&bits) => bits,
None => tables::word_break_word(ch),
};
WordProps::from_bits(bits)
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct RareProps(u32);
impl RareProps {
#[inline]
pub const fn is_join_causing(self) -> bool {
self.0 & tables::JOIN_CAUSING != 0
}
#[inline]
pub const fn is_wide(self) -> bool {
self.0 & tables::WIDE != 0
}
#[inline]
pub const fn is_mixed_upright(self) -> bool {
self.0 & tables::UPRIGHT_IN_MIXED != 0
}
#[inline]
pub const fn is_conditional_japanese_starter(self) -> bool {
self.0 & tables::CONDITIONAL_JAPANESE_STARTER != 0
}
#[inline]
pub const fn is_glue(self) -> bool {
self.0 & tables::GLUE != 0
}
#[inline]
#[cfg(test)]
const fn is_emoji_modifier_base(self) -> bool {
self.0 & tables::EMOJI_MODIFIER_BASE != 0
}
#[inline]
#[cfg(test)]
const fn is_emoji_component(self) -> bool {
self.0 & tables::EMOJI_COMPONENT != 0
}
#[inline]
pub const fn is_default_ignorable(self) -> bool {
self.0 & tables::DEFAULT_IGNORABLE != 0
}
#[inline]
pub const fn grapheme_cluster_break(self) -> GraphemeClusterBreak {
GraphemeClusterBreak::from_bits(
(self.0 >> tables::GRAPHEME_CLUSTER_BREAK_SHIFT) & tables::GRAPHEME_CLUSTER_BREAK_MASK,
)
}
#[inline]
pub const fn grapheme_props(self) -> GraphemeProps {
GraphemeProps::from_bits(((self.0 >> tables::GRAPHEME_CLUSTER_BREAK_SHIFT) & 0x7F) as u8)
}
#[inline]
#[cfg(test)]
const fn indic_conjunct_break(self) -> IndicConjunctBreak {
IndicConjunctBreak::from_bits(
(self.0 >> tables::INDIC_CONJUNCT_BREAK_SHIFT) & tables::INDIC_CONJUNCT_BREAK_MASK,
)
}
#[cfg(feature = "css")]
#[inline]
pub const fn east_asian_spacing(self) -> EastAsianSpacing {
EastAsianSpacing::from_bits(
(self.0 >> tables::EAST_ASIAN_SPACING_SHIFT) & tables::EAST_ASIAN_SPACING_MASK,
)
}
#[cfg(feature = "css")]
#[inline]
pub const fn text_spacing(self) -> TextSpacingClass {
TextSpacingClass::from_bits(
(self.0 >> tables::TEXT_SPACING_SHIFT) & tables::TEXT_SPACING_MASK,
)
}
#[cfg(feature = "css")]
#[inline]
pub const fn emphasis(self) -> EmphasisClass {
EmphasisClass::from_bits((self.0 >> tables::EMPHASIS_SHIFT) & tables::EMPHASIS_MASK)
}
#[cfg(feature = "css")]
#[inline]
pub const fn first_letter(self) -> FirstLetterClass {
FirstLetterClass::from_bits(
(self.0 >> tables::FIRST_LETTER_SHIFT) & tables::FIRST_LETTER_MASK,
)
}
#[inline]
pub const fn is_cased(self) -> bool {
self.0 & tables::CASED != 0
}
#[inline]
pub const fn is_case_ignorable(self) -> bool {
self.0 & tables::CASE_IGNORABLE != 0
}
#[cfg(feature = "css")]
#[inline]
pub const fn is_letter_or_number(self) -> bool {
self.0 & tables::LETTER_OR_NUMBER != 0
}
}
#[derive(Copy, Clone, Default, Eq, PartialEq, Debug)]
#[repr(transparent)]
pub struct BidiClass(u8);
impl BidiClass {
pub const LEFT_TO_RIGHT: Self = Self(0);
pub const RIGHT_TO_LEFT: Self = Self(1);
pub const EUROPEAN_NUMBER: Self = Self(2);
pub const EUROPEAN_SEPARATOR: Self = Self(3);
pub const EUROPEAN_TERMINATOR: Self = Self(4);
pub const ARABIC_NUMBER: Self = Self(5);
pub const COMMON_SEPARATOR: Self = Self(6);
pub const PARAGRAPH_SEPARATOR: Self = Self(7);
pub const SEGMENT_SEPARATOR: Self = Self(8);
pub const WHITE_SPACE: Self = Self(9);
pub const OTHER_NEUTRAL: Self = Self(10);
pub const LEFT_TO_RIGHT_EMBEDDING: Self = Self(11);
pub const LEFT_TO_RIGHT_OVERRIDE: Self = Self(12);
pub const ARABIC_LETTER: Self = Self(13);
pub const RIGHT_TO_LEFT_EMBEDDING: Self = Self(14);
pub const RIGHT_TO_LEFT_OVERRIDE: Self = Self(15);
pub const POP_DIRECTIONAL_FORMAT: Self = Self(16);
pub const NONSPACING_MARK: Self = Self(17);
pub const BOUNDARY_NEUTRAL: Self = Self(18);
pub const FIRST_STRONG_ISOLATE: Self = Self(19);
pub const LEFT_TO_RIGHT_ISOLATE: Self = Self(20);
pub const RIGHT_TO_LEFT_ISOLATE: Self = Self(21);
pub const POP_DIRECTIONAL_ISOLATE: Self = Self(22);
#[inline]
pub const fn new(value: u8) -> Self {
Self(value)
}
#[inline]
pub const fn get(self) -> u8 {
self.0
}
#[inline]
pub const fn is_removed_by_x9(self) -> bool {
matches!(self.0, 11 | 12 | 14 | 15 | 16 | 18)
}
#[inline]
pub const fn mask(self) -> u32 {
1u32 << if self.0 < 31 { self.0 as u32 } else { 31 }
}
}
#[derive(Copy, Clone, Default, Eq, PartialEq, Debug)]
#[repr(transparent)]
pub struct LineBreak(u8);
impl LineBreak {
pub const UNKNOWN: Self = Self(0);
pub const AMBIGUOUS: Self = Self(1);
pub const ALPHABETIC: Self = Self(2);
pub const BREAK_BOTH: Self = Self(3);
pub const BREAK_AFTER: Self = Self(4);
pub const BREAK_BEFORE: Self = Self(5);
pub const MANDATORY_BREAK: Self = Self(6);
pub const CONTINGENT_BREAK: Self = Self(7);
pub const CLOSE_PUNCTUATION: Self = Self(8);
pub const COMBINING_MARK: Self = Self(9);
pub const CARRIAGE_RETURN: Self = Self(10);
pub const EXCLAMATION: Self = Self(11);
pub const GLUE: Self = Self(12);
pub const HYPHEN: Self = Self(13);
pub const IDEOGRAPHIC: Self = Self(14);
pub const INSEPARABLE: Self = Self(15);
pub const INFIX_NUMERIC: Self = Self(16);
pub const LINE_FEED: Self = Self(17);
pub const NONSTARTER: Self = Self(18);
pub const NUMERIC: Self = Self(19);
pub const OPEN_PUNCTUATION: Self = Self(20);
pub const POSTFIX_NUMERIC: Self = Self(21);
pub const PREFIX_NUMERIC: Self = Self(22);
pub const QUOTATION: Self = Self(23);
pub const COMPLEX_CONTEXT: Self = Self(24);
pub const SPACE: Self = Self(26);
pub const BREAK_SYMBOLS: Self = Self(27);
pub const ZERO_WIDTH_SPACE: Self = Self(28);
pub const NEXT_LINE: Self = Self(29);
pub const WORD_JOINER: Self = Self(30);
pub const H2: Self = Self(31);
pub const H3: Self = Self(32);
pub const JL: Self = Self(33);
pub const JT: Self = Self(34);
pub const JV: Self = Self(35);
pub const CLOSE_PARENTHESIS: Self = Self(36);
pub const CONDITIONAL_JAPANESE_STARTER: Self = Self(37);
pub const HEBREW_LETTER: Self = Self(38);
pub const REGIONAL_INDICATOR: Self = Self(39);
pub const E_BASE: Self = Self(40);
pub const E_MODIFIER: Self = Self(41);
pub const ZWJ: Self = Self(42);
pub const AKSARA: Self = Self(43);
pub const AKSARA_PREBASE: Self = Self(44);
pub const AKSARA_START: Self = Self(45);
pub const VIRAMA_FINAL: Self = Self(46);
pub const VIRAMA: Self = Self(47);
pub const UNAMBIGUOUS_HYPHEN: Self = Self(48);
#[inline]
pub const fn new(value: u8) -> Self {
Self(value)
}
#[inline]
pub const fn get(self) -> u8 {
self.0
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
#[repr(transparent)]
pub struct ScriptId(u16);
impl ScriptId {
pub const COMMON: Self = Self(tables::SCRIPT_COMMON);
pub const INHERITED: Self = Self(tables::SCRIPT_INHERITED);
pub const UNKNOWN: Self = Self(tables::SCRIPT_UNKNOWN);
pub const HAN: Self = Self(tables::SCRIPT_HAN);
pub const HIRAGANA: Self = Self(tables::SCRIPT_HIRAGANA);
pub const KATAKANA: Self = Self(tables::SCRIPT_KATAKANA);
pub const KHMER: Self = Self(tables::SCRIPT_KHMER);
pub const LAO: Self = Self(tables::SCRIPT_LAO);
pub const MYANMAR: Self = Self(tables::SCRIPT_MYANMAR);
pub const THAI: Self = Self(tables::SCRIPT_THAI);
#[inline]
pub fn tag(self) -> [u8; 4] {
match tables::SCRIPT_TAGS.get(usize::from(self.0)) {
Some(tag) => *tag,
None => *b"Zzzz",
}
}
#[inline]
pub const fn get(self) -> u16 {
self.0
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ScriptWithExt(u16);
impl ScriptWithExt {
#[inline]
pub const fn single(self) -> Option<ScriptId> {
if self.0 < tables::SCRIPT_COUNT {
Some(ScriptId(self.0))
} else {
None
}
}
#[inline]
fn set_run(self) -> &'static [ScriptId] {
let Some(set) = self.0.checked_sub(tables::SCRIPT_COUNT) else {
return &[];
};
let set = usize::from(set);
let starts = &tables::SCRIPT_SET_STARTS;
let (Some(start), Some(end)) = (starts.get(set), starts.get(set + 1)) else {
return &[];
};
tables::SCRIPT_RUNS
.get(usize::from(*start)..usize::from(*end))
.unwrap_or(&[])
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
#[repr(transparent)]
pub struct BracketPairId(u8);
#[cfg(feature = "css")]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[repr(u8)]
pub enum EastAsianSpacing {
#[default]
Other = 0,
Narrow = 1,
NarrowDigit = 2,
Conditional = 3,
Wide = 4,
Mark = 5,
}
#[cfg(feature = "css")]
impl EastAsianSpacing {
#[inline]
const fn from_bits(bits: u32) -> Self {
match bits {
1 => Self::Narrow,
2 => Self::NarrowDigit,
3 => Self::Conditional,
4 => Self::Wide,
5 => Self::Mark,
_ => Self::Other,
}
}
}
#[cfg(feature = "css")]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[repr(u8)]
pub enum TextSpacingClass {
#[default]
Other = 0,
Open = 1,
Close = 2,
Middle = 3,
OpenNarrow = 4,
CloseNarrow = 5,
Dot = 6,
Colon = 7,
Semicolon = 8,
OpenQuote = 9,
CloseQuote = 10,
}
#[cfg(feature = "css")]
impl TextSpacingClass {
#[inline]
const fn from_bits(bits: u32) -> Self {
match bits {
1 => Self::Open,
2 => Self::Close,
3 => Self::Middle,
4 => Self::OpenNarrow,
5 => Self::CloseNarrow,
6 => Self::Dot,
7 => Self::Colon,
8 => Self::Semicolon,
9 => Self::OpenQuote,
10 => Self::CloseQuote,
_ => Self::Other,
}
}
}
#[cfg(feature = "css")]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[repr(u8)]
pub enum EmphasisClass {
#[default]
Marked = 0,
Space = 1,
Punctuation = 2,
Symbol = 3,
Never = 4,
}
#[cfg(feature = "css")]
impl EmphasisClass {
#[inline]
const fn from_bits(bits: u32) -> Self {
match bits {
1 => Self::Space,
2 => Self::Punctuation,
3 => Self::Symbol,
4 => Self::Never,
_ => Self::Marked,
}
}
}
#[cfg(feature = "css")]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[repr(u8)]
pub enum FirstLetterClass {
#[default]
Other = 0,
Space = 1,
Opening = 2,
Punctuation = 3,
}
#[cfg(feature = "css")]
impl FirstLetterClass {
#[inline]
const fn from_bits(bits: u32) -> Self {
match bits {
1 => Self::Space,
2 => Self::Opening,
3 => Self::Punctuation,
_ => Self::Other,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[repr(u8)]
pub enum GraphemeClusterBreak {
#[default]
Other = 0,
Control = 1,
Cr = 2,
Extend = 3,
L = 4,
Lf = 5,
Lv = 6,
Lvt = 7,
T = 8,
V = 9,
SpacingMark = 10,
Prepend = 11,
RegionalIndicator = 12,
Zwj = 13,
}
impl GraphemeClusterBreak {
#[inline]
pub const fn stands_alone(self) -> bool {
matches!(self, Self::Other | Self::Control | Self::Cr | Self::Lf)
}
#[inline]
const fn from_bits(bits: u32) -> Self {
match bits {
1 => Self::Control,
2 => Self::Cr,
3 => Self::Extend,
4 => Self::L,
5 => Self::Lf,
6 => Self::Lv,
7 => Self::Lvt,
8 => Self::T,
9 => Self::V,
10 => Self::SpacingMark,
11 => Self::Prepend,
12 => Self::RegionalIndicator,
13 => Self::Zwj,
_ => Self::Other,
}
}
}
#[cfg(test)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
#[repr(u8)]
enum IndicConjunctBreak {
#[default]
None = 0,
Consonant = 1,
Extend = 2,
Linker = 3,
}
#[cfg(test)]
impl IndicConjunctBreak {
const fn from_bits(bits: u32) -> Self {
match bits {
1 => Self::Consonant,
2 => Self::Extend,
3 => Self::Linker,
_ => Self::None,
}
}
}