1use icu_segmenter::{
2 options::{LineBreakOptions, WordBreakInvariantOptions},
3 GraphemeClusterSegmenter, LineSegmenter, WordSegmenter,
4};
5use kashida::{builtin_pattern_set, find_kashida_points, PatternSet};
6
7const KASHIDA: &str = "\u{0640}";
8const WIDTH: usize = 32;
9
10const MAX_KASHIDA: usize = 2;
11
12const TEXT: &str = concat!(
13 "قال أفلاطون: «الخط عقال العقل». وقال إقليدس ",
14 "الإغريقي: «الخط هندسة روحانية وإن ظهرت بآلة ",
15 "جسمانية». وقال أبو دلف رحالة القرن العاشر ",
16 "الميلادي: «الخط رياض العلوم». وقال النظام المعتزلي: ",
17 "«الخط أصيل في الروح وإن ظهر بحواس البدن»."
18);
19
20fn justify(line: &str, set: &PatternSet) -> String {
21 let mut words = Vec::new();
24 let mut prev = 0;
25 for end in WordSegmenter::new_auto(WordBreakInvariantOptions::default()).segment_str(line) {
26 let (cleaned, points) = find_kashida_points(&line[prev..end], set, true);
27 prev = end;
28 words.push((
29 cleaned,
30 points.into_iter().max_by_key(|point| point.priority),
31 ));
32 }
33
34 let mut room = WIDTH - line.chars().count();
37 for priority in (0..=9).rev() {
38 for (word, point) in &mut words {
39 if room == 0 {
41 break;
42 }
43 if let Some(point) = point.filter(|point| point.priority == priority) {
44 let mut boundaries = GraphemeClusterSegmenter::new().segment_str(word);
46 let index = boundaries.nth(point.index as usize + 1).unwrap();
47 let count = MAX_KASHIDA.min(room);
49 word.insert_str(index, &KASHIDA.repeat(count));
50 room -= count;
51 }
52 }
53 }
54 words.into_iter().map(|(word, _)| word).collect()
55}
56
57fn main() {
58 let (mut lines, mut start, mut prev) = (Vec::new(), 0, 0);
60 for brk in LineSegmenter::new_auto(LineBreakOptions::default()).segment_str(TEXT) {
61 if TEXT[start..brk].trim_end().chars().count() > WIDTH {
62 lines.push(TEXT[start..prev].trim_end());
63 start = prev;
64 }
65 prev = brk;
66 }
67 lines.push(TEXT[start..].trim_end());
68
69 println!("unjustified");
71 for line in &lines {
72 println!("{line}");
73 }
74
75 let pattern_set = builtin_pattern_set("arabic-naskh").unwrap();
77 println!("\njustified");
78 let last = lines.pop().unwrap();
79 for line in lines {
80 println!("{}", justify(line, pattern_set));
81 }
82 println!("{last}");
83}