Skip to main content

kashida/
lib.rs

1//! Arabic kashida (tatweel) insertion-point finding.
2//!
3//! Given text plus a compiled pattern set, produce the junctions where a
4//! kashida may be inserted, each with a priority (0–9, higher = stronger).
5
6#![warn(missing_docs)]
7#![warn(unreachable_pub)]
8
9mod builtin;
10mod error;
11mod grapheme;
12mod pattern;
13mod rasm;
14mod resolve;
15
16#[cfg(test)]
17mod tests;
18
19pub use builtin::{builtin_pattern_set, is_builtin_pattern_set};
20pub use error::{CompileError, CompileErrorKind};
21pub use pattern::{compile_pattern_text, PatternSet};
22
23use grapheme::{is_bare_tatweel_at, joined_runs, split_graphemes, KASHIDA};
24use resolve::resolve_run;
25
26/// A junction where a kashida may be inserted.
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub struct KashidaPoint {
29    /// Insert a kashida after this grapheme-cluster index.
30    pub index: u32,
31    /// 0–9, higher = stronger, filled first.
32    pub priority: u8,
33}
34
35/// Kashida insertion points for `word` from the pattern set alone.
36pub fn find_kashida_points_patterns(word: &str, set: &PatternSet) -> Vec<KashidaPoint> {
37    let graphemes = split_graphemes(word);
38    let mut out = Vec::new();
39    for run in joined_runs(&graphemes) {
40        out.extend(resolve_run(&graphemes, &run, set));
41    }
42    out
43}
44
45fn strip_bare_tatweel(word: &str) -> String {
46    if !word.contains(KASHIDA) {
47        return word.to_string();
48    }
49    let chars: Vec<char> = word.chars().collect();
50    let mut out = String::with_capacity(word.len());
51    for k in 0..chars.len() {
52        if is_bare_tatweel_at(&chars, k) {
53            continue;
54        }
55        out.push(chars[k]);
56    }
57    out
58}
59
60/// Kashida insertion points for `word` under the given pattern set.
61///
62/// Bare user kashidas are stripped first (unless asked not to); a kept one
63/// is an ordinary run letter that patterns can target, like
64/// the built-in simple set's `@Tatweel 9` rule. Returns the (possibly stripped)
65/// text along with the points, whose indices refer to it.
66///
67/// # Example
68///
69/// ```
70/// use kashida::{builtin_pattern_set, find_kashida_points};
71///
72/// let set = builtin_pattern_set("arabic-simple").unwrap();
73/// let (cleaned, points) = find_kashida_points("بيت", set, true);
74/// for point in points {
75///     // Insert a kashida after grapheme cluster `point.index`.
76///     println!("{} @ {}", point.priority, point.index);
77/// }
78/// ```
79pub fn find_kashida_points(
80    word: &str,
81    set: &PatternSet,
82    remove_existing_kashida: bool,
83) -> (String, Vec<KashidaPoint>) {
84    let cleaned = if remove_existing_kashida {
85        strip_bare_tatweel(word)
86    } else {
87        word.to_string()
88    };
89    let points = find_kashida_points_patterns(&cleaned, set);
90    (cleaned, points)
91}