use icu_segmenter::{
options::{LineBreakOptions, WordBreakInvariantOptions},
GraphemeClusterSegmenter, LineSegmenter, WordSegmenter,
};
use kashida::{builtin_pattern_set, find_kashida_points, PatternSet};
const KASHIDA: &str = "\u{0640}";
const WIDTH: usize = 32;
const MAX_KASHIDA: usize = 2;
const TEXT: &str = concat!(
"قال أفلاطون: «الخط عقال العقل». وقال إقليدس ",
"الإغريقي: «الخط هندسة روحانية وإن ظهرت بآلة ",
"جسمانية». وقال أبو دلف رحالة القرن العاشر ",
"الميلادي: «الخط رياض العلوم». وقال النظام المعتزلي: ",
"«الخط أصيل في الروح وإن ظهر بحواس البدن»."
);
fn justify(line: &str, set: &PatternSet) -> String {
let mut words = Vec::new();
let mut prev = 0;
for end in WordSegmenter::new_auto(WordBreakInvariantOptions::default()).segment_str(line) {
let (cleaned, points) = find_kashida_points(&line[prev..end], set, true);
prev = end;
words.push((
cleaned,
points.into_iter().max_by_key(|point| point.priority),
));
}
let mut room = WIDTH - line.chars().count();
for priority in (0..=9).rev() {
for (word, point) in &mut words {
if room == 0 {
break;
}
if let Some(point) = point.filter(|point| point.priority == priority) {
let mut boundaries = GraphemeClusterSegmenter::new().segment_str(word);
let index = boundaries.nth(point.index as usize + 1).unwrap();
let count = MAX_KASHIDA.min(room);
word.insert_str(index, &KASHIDA.repeat(count));
room -= count;
}
}
}
words.into_iter().map(|(word, _)| word).collect()
}
fn main() {
let (mut lines, mut start, mut prev) = (Vec::new(), 0, 0);
for brk in LineSegmenter::new_auto(LineBreakOptions::default()).segment_str(TEXT) {
if TEXT[start..brk].trim_end().chars().count() > WIDTH {
lines.push(TEXT[start..prev].trim_end());
start = prev;
}
prev = brk;
}
lines.push(TEXT[start..].trim_end());
println!("unjustified");
for line in &lines {
println!("{line}");
}
let pattern_set = builtin_pattern_set("arabic-naskh").unwrap();
println!("\njustified");
let last = lines.pop().unwrap();
for line in lines {
println!("{}", justify(line, pattern_set));
}
println!("{last}");
}