use crate::macros::is_script_tamil_ext;
use crate::script_data::{List, ScriptData};
use crate::utils::binary_search::binary_search_lower_with_index;
use crate::utils::strings::char_substring;
impl ScriptData {
#[allow(dead_code)]
pub fn krama_text_or_null(&self, idx: i16) -> Option<&str> {
if idx < 0 {
return None;
}
match &self.get_common_attr().krama_text_arr.get(idx as usize) {
Some(item) => Some(&item.0),
None => None,
}
}
pub fn krama_text_or_empty(&self, idx: i16) -> &str {
if idx < 0 {
return &"";
}
match &self.get_common_attr().krama_text_arr.get(idx as usize) {
Some(item) => &item.0,
None => &"",
}
}
pub fn krama_index_of_text(&self, text: &str) -> Option<usize> {
binary_search_lower_with_index(
&self.get_common_attr().krama_text_arr,
&self.get_common_attr().krama_text_arr_index,
&text,
|arr, i| &arr[i].0,
|a, b| a.cmp(b),
)
}
}
pub struct ResultStringBuilder {
result: Vec<String>,
}
impl ResultStringBuilder {
pub fn new() -> ResultStringBuilder {
ResultStringBuilder { result: Vec::new() }
}
pub fn emit(&mut self, text: String) {
if text.is_empty() {
return;
}
self.result.push(text);
}
pub fn emit_pieces(&mut self, pieces: &[String]) {
for p in pieces {
self.emit(p.to_owned());
}
}
pub fn last_piece(&self) -> Option<String> {
let last = self.result.last().cloned();
return last;
}
pub fn last_char(&self) -> Option<char> {
match self.last_piece() {
Some(v) => v.chars().last(),
None => None,
}
}
pub fn pop_last_char(&mut self) -> Option<char> {
let result_len = self.result.len();
let lp = self.last_piece();
match lp {
Some(mut lp) => {
if lp.len() == 0 {
return None;
}
let ch = lp.pop();
if let Some(ch) = ch {
self.result[result_len - 1] = lp;
return Some(ch);
}
return None;
}
None => None,
}
}
pub fn rewrite_tail_pieces(&mut self, count: usize, new_pieces: &[String]) {
let len = self.result.len();
let start = len.saturating_sub(count); self.result.truncate(start);
for p in new_pieces {
if !p.is_empty() {
self.result.push(p.to_owned());
}
}
}
pub fn with_last_char_moved_after(&mut self, before_pieces: &[String], after_pieces: &[String]) {
let ch = self.pop_last_char();
match ch {
None => {
self.emit_pieces(before_pieces);
self.emit_pieces(after_pieces);
}
Some(c) => {
self.emit_pieces(before_pieces);
self.emit(c.to_string());
self.emit_pieces(after_pieces);
}
}
}
pub fn peek_at(&self, index: isize) -> Option<InputCursor> {
let len = self.result.len() as isize;
if len == 0 {
return None;
}
let mut i = index;
if i < 0 {
i = len + i;
} else if i < 0 || i >= len {
return None;
}
let item = self.result.get(i as usize);
match item {
Some(item) => {
return Some(InputCursor {
ch: item.to_owned(),
});
}
None => None,
}
}
pub fn rewrite_at(&mut self, index: isize, new_piece: String) {
let len = self.result.len() as isize;
if len == 0 {
return;
}
let mut i = index;
if i < 0 {
i = len + i;
} else if i < 0 || i >= len {
return;
}
self.result[i as usize] = new_piece;
}
pub fn to_string(&self) -> String {
self.result.concat()
}
}
type PrevContextItem = (Option<String>, Option<List>);
pub struct PrevContextBuilder {
arr: Vec<PrevContextItem>,
max_len: usize,
}
impl PrevContextBuilder {
pub fn new(max_len: usize) -> PrevContextBuilder {
PrevContextBuilder {
arr: Vec::new(),
max_len,
}
}
pub fn clear(&mut self) {
self.arr.clear();
}
pub fn length(&self) -> usize {
self.arr.len()
}
fn resolve_arr_index(&self, i: isize) -> Option<usize> {
if self.arr.is_empty() {
return None;
}
let len = self.arr.len() as isize;
let mut idx = i;
if idx < 0 {
idx = len + idx;
}
if idx < 0 || idx >= len {
None
} else {
Some(idx as usize)
}
}
pub fn at(&self, i: isize) -> Option<&PrevContextItem> {
match self.resolve_arr_index(i) {
None => None,
Some(idx) => self.arr.get(idx),
}
}
#[allow(dead_code)]
pub fn last(&self) -> Option<&PrevContextItem> {
self.arr.last()
}
#[allow(dead_code)]
pub fn last_text(&self) -> Option<&str> {
self.last().and_then(|(text_opt, _)| text_opt.as_deref())
}
#[allow(dead_code)]
pub fn last_type(&self) -> Option<&List> {
self.last().and_then(|(_, list_opt)| list_opt.as_ref())
}
pub fn type_at(&self, i: isize) -> Option<&List> {
self.at(i).and_then(|(_, list_opt)| list_opt.as_ref())
}
pub fn text_at(&self, i: isize) -> Option<&str> {
self.at(i).and_then(|(text_opt, _)| text_opt.as_deref())
}
#[allow(dead_code)]
pub fn is_last_type(&self, t: &List) -> bool {
self.last_type() == Some(t)
}
pub fn push(&mut self, item: PrevContextItem) {
let text_ok = item.0.as_ref().map(|s| !s.is_empty()).unwrap_or(false);
if !text_ok {
return;
}
self.arr.push(item);
if self.arr.len() > self.max_len {
self.arr.remove(0);
}
}
}
pub struct InputTextCursor<'a> {
text: &'a str,
pos: usize,
}
pub struct InputCursor {
pub ch: String,
}
impl<'a> InputTextCursor<'a> {
pub fn new(text: &'a str) -> InputTextCursor<'a> {
InputTextCursor { text, pos: 0 }
}
pub fn pos(&self) -> usize {
self.pos
}
pub fn peek_at(&self, index_units: usize) -> Option<InputCursor> {
self
.text
.chars()
.nth(index_units)
.and_then(|ch| Some(InputCursor { ch: ch.to_string() }))
}
pub fn peek(&self) -> Option<InputCursor> {
self.peek_at(self.pos)
}
#[allow(dead_code)]
pub fn peek_at_offset_units(&self, offset_units: usize) -> Option<InputCursor> {
self.peek_at(self.pos + offset_units)
}
pub fn advance(&mut self, units: usize) {
self.pos += units;
}
pub fn slice(&self, start: usize, end: usize) -> Option<String> {
let char_count = self.text.chars().count();
if start > end || end > char_count {
return None;
}
Some(char_substring(&self.text, start, end).to_owned())
}
}
pub struct MatchPrevKramaSequenceResult {
pub matched: bool,
pub matched_len: usize,
}
impl ScriptData {
pub fn match_prev_krama_sequence<F>(
&self,
peek_at: F,
anchor_index: isize,
prev: &[usize], ) -> MatchPrevKramaSequenceResult
where
F: Fn(isize) -> Option<InputCursor>,
{
for i in 0..prev.len() {
let expected_krama_index = prev[prev.len() - 1 - i];
let info = match peek_at(anchor_index - i as isize) {
Some(v) => v,
None => {
return MatchPrevKramaSequenceResult {
matched: false,
matched_len: 0,
};
}
};
let got_krama_index = self.krama_index_of_text(&info.ch);
match got_krama_index {
Some(got) if got == expected_krama_index => {}
_ => {
return MatchPrevKramaSequenceResult {
matched: false,
matched_len: 0,
};
}
}
}
MatchPrevKramaSequenceResult {
matched: true,
matched_len: prev.len(),
}
}
pub fn replace_with_pieces(&self, replace_with: &[i16]) -> Vec<String> {
replace_with
.iter()
.map(|&k| self.krama_text_or_empty(k))
.filter(|s| !s.is_empty())
.map(|s| s.to_owned())
.collect()
}
}
pub const TAMIL_EXTENDED_SUPERSCRIPT_NUMBERS: [char; 3] = ['²', '³', '⁴'];
pub fn is_ta_ext_superscript_tail(ch: Option<char>) -> bool {
match ch {
Some(c) => TAMIL_EXTENDED_SUPERSCRIPT_NUMBERS.contains(&c),
None => false,
}
}
pub const VEDIC_SVARAS: [char; 4] = ['॒', '॑', '᳚', '᳛'];
pub fn is_vedic_svara_tail(ch: Option<char>) -> bool {
match ch {
Some(c) => VEDIC_SVARAS.contains(&c),
None => false,
}
}
impl ResultStringBuilder {
pub fn emit_pieces_with_reorder(
&mut self,
pieces: &[String],
halant: &str,
should_reorder: bool,
) {
if pieces.is_empty() {
return;
}
if !should_reorder {
self.emit_pieces(pieces);
return;
}
let first_piece = pieces.first().map(|s| s.as_str()).unwrap_or("");
if first_piece.starts_with(halant) {
let rest_first = first_piece.strip_prefix(halant).unwrap_or("");
let mut after_pieces: Vec<String> = Vec::new();
if !rest_first.is_empty() {
after_pieces.push(rest_first.to_owned());
}
for p in pieces.into_iter().skip(1) {
after_pieces.push(p.to_owned());
}
self.with_last_char_moved_after(&[halant.to_owned()], &after_pieces);
} else {
self.with_last_char_moved_after(pieces, &[]);
}
}
}
const VEDIC_SVARAS_TYPING_SYMBOLS: [&str; 4] = ["_", "'''", "''", "'"];
const VEDIC_SVARAS_NORMAL_SYMBOLS: [&str; 4] = ["↓", "↑↑↑", "↑↑", "↑"];
pub fn apply_typing_input_aliases(mut text: String, to_script_name: &str) -> String {
if text.is_empty() {
return text;
}
if text.contains('x') {
text = text.replace("x", "kSh");
}
if is_script_tamil_ext!(to_script_name) {
for i in 0..VEDIC_SVARAS_TYPING_SYMBOLS.len() {
let symbol = VEDIC_SVARAS_TYPING_SYMBOLS[i];
if text.contains(symbol) {
text = text.replace(symbol, VEDIC_SVARAS_NORMAL_SYMBOLS[i]);
}
}
}
text
}