use super::helpers::detect_direction;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum TextDirection {
#[default]
Ltr,
Rtl,
Auto,
}
impl TextDirection {
pub fn is_rtl(&self) -> bool {
matches!(self, TextDirection::Rtl)
}
pub fn is_ltr(&self) -> bool {
matches!(self, TextDirection::Ltr)
}
pub fn is_auto(&self) -> bool {
matches!(self, TextDirection::Auto)
}
pub fn resolve(&self, text: &str) -> ResolvedDirection {
match self {
TextDirection::Ltr => ResolvedDirection::Ltr,
TextDirection::Rtl => ResolvedDirection::Rtl,
TextDirection::Auto => detect_direction(text),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ResolvedDirection {
#[default]
Ltr,
Rtl,
}
impl ResolvedDirection {
pub fn is_rtl(&self) -> bool {
matches!(self, ResolvedDirection::Rtl)
}
pub fn is_ltr(&self) -> bool {
matches!(self, ResolvedDirection::Ltr)
}
pub fn opposite(&self) -> Self {
match self {
ResolvedDirection::Ltr => ResolvedDirection::Rtl,
ResolvedDirection::Rtl => ResolvedDirection::Ltr,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BidiClass {
L,
R,
AL,
EN,
ES,
ET,
AN,
CS,
NSM,
BN,
B,
S,
WS,
ON,
LRE,
LRO,
RLE,
RLO,
PDF,
LRI,
RLI,
FSI,
PDI,
}
impl BidiClass {
pub fn of(c: char) -> Self {
let code = c as u32;
match c {
'\u{202A}' => return BidiClass::LRE,
'\u{202B}' => return BidiClass::RLE,
'\u{202C}' => return BidiClass::PDF,
'\u{202D}' => return BidiClass::LRO,
'\u{202E}' => return BidiClass::RLO,
'\u{2066}' => return BidiClass::LRI,
'\u{2067}' => return BidiClass::RLI,
'\u{2068}' => return BidiClass::FSI,
'\u{2069}' => return BidiClass::PDI,
_ => {}
}
if (0x0600..=0x06FF).contains(&code) || (0x0750..=0x077F).contains(&code) {
if (0x0660..=0x0669).contains(&code) || (0x06F0..=0x06F9).contains(&code) {
return BidiClass::AN;
}
return BidiClass::AL;
}
if (0x08A0..=0x08FF).contains(&code) {
return BidiClass::AL;
}
if (0xFB50..=0xFDFF).contains(&code) || (0xFE70..=0xFEFF).contains(&code) {
return BidiClass::AL;
}
if (0x0590..=0x05FF).contains(&code) {
return BidiClass::R;
}
if (0x07C0..=0x07FF).contains(&code) || (0x0800..=0x089F).contains(&code) || (0x10800..=0x10FFF).contains(&code) || (0x1E800..=0x1EFFF).contains(&code)
{
return BidiClass::R;
}
if c.is_ascii_digit() {
return BidiClass::EN;
}
if matches!(c, '+' | '-') {
return BidiClass::ES;
}
if matches!(c, '#' | '$' | '%' | '°' | '€' | '£' | '¥') {
return BidiClass::ET;
}
if matches!(c, ',' | '.' | ':') {
return BidiClass::CS;
}
if matches!(c, '\n' | '\r' | '\u{0085}' | '\u{2029}') {
return BidiClass::B;
}
if matches!(c, '\t' | '\u{001F}' | '\u{001E}' | '\u{000B}') {
return BidiClass::S;
}
if c.is_whitespace() {
return BidiClass::WS;
}
if c.is_ascii_punctuation() || matches!(c, '(' | ')' | '[' | ']' | '{' | '}' | '"' | '\'') {
return BidiClass::ON;
}
BidiClass::L
}
pub fn is_strong(&self) -> bool {
matches!(self, BidiClass::L | BidiClass::R | BidiClass::AL)
}
pub fn is_strong_rtl(&self) -> bool {
matches!(self, BidiClass::R | BidiClass::AL)
}
pub fn is_weak(&self) -> bool {
matches!(
self,
BidiClass::EN
| BidiClass::ES
| BidiClass::ET
| BidiClass::AN
| BidiClass::CS
| BidiClass::NSM
| BidiClass::BN
)
}
pub fn is_neutral(&self) -> bool {
matches!(
self,
BidiClass::B | BidiClass::S | BidiClass::WS | BidiClass::ON
)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BidiRun {
pub text: String,
pub range: std::ops::Range<usize>,
pub level: u8,
pub direction: ResolvedDirection,
}
impl BidiRun {
pub fn new(text: String, range: std::ops::Range<usize>, level: u8) -> Self {
let direction = if level.is_multiple_of(2) {
ResolvedDirection::Ltr
} else {
ResolvedDirection::Rtl
};
Self {
text,
range,
level,
direction,
}
}
pub fn char_count(&self) -> usize {
self.text.chars().count()
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TextAlign {
#[default]
Start,
End,
Left,
Right,
Center,
}
#[derive(Clone, Debug)]
pub struct BidiInfo {
pub text: String,
pub base_direction: ResolvedDirection,
pub runs: Vec<BidiRun>,
pub visual_order: Vec<usize>,
}
impl BidiInfo {
pub fn new(text: &str, direction: TextDirection) -> Self {
let base_direction = direction.resolve(text);
let runs = Vec::new(); let visual_order = Vec::new();
Self {
text: text.to_string(),
base_direction,
runs,
visual_order,
}
}
pub fn visual_text(&self) -> String {
self.text.clone() }
pub fn has_rtl(&self) -> bool {
self.base_direction.is_rtl()
}
pub fn is_pure_ltr(&self) -> bool {
!self.has_rtl()
}
pub fn is_pure_rtl(&self) -> bool {
self.runs.iter().all(|r| r.direction.is_rtl())
}
}
#[derive(Clone, Debug)]
pub struct BidiConfig {
pub default_direction: TextDirection,
pub enable_overrides: bool,
pub enable_mirroring: bool,
}
impl Default for BidiConfig {
fn default() -> Self {
Self {
default_direction: TextDirection::Auto,
enable_overrides: true,
enable_mirroring: true,
}
}
}
#[derive(Clone, Debug)]
pub struct RtlLayout {
pub width: usize,
pub align: TextAlign,
pub direction: ResolvedDirection,
}
impl RtlLayout {
pub fn new(width: usize, direction: ResolvedDirection) -> Self {
Self {
width,
align: TextAlign::Start,
direction,
}
}
pub fn align(mut self, align: TextAlign) -> Self {
self.align = align;
self
}
pub fn position(&self, text_width: usize) -> usize {
if text_width >= self.width {
return 0;
}
let padding = self.width - text_width;
match self.align {
TextAlign::Start => {
if self.direction.is_rtl() {
padding
} else {
0
}
}
TextAlign::End => {
if self.direction.is_rtl() {
0
} else {
padding
}
}
TextAlign::Left => 0,
TextAlign::Right => padding,
TextAlign::Center => padding / 2,
}
}
pub fn pad(&self, text: &str, text_width: usize) -> String {
if text_width >= self.width {
return text.to_string();
}
let padding = self.width - text_width;
let pos = self.position(text_width);
let left_pad = pos;
let right_pad = padding - left_pad;
format!("{}{}{}", " ".repeat(left_pad), text, " ".repeat(right_pad))
}
}