Expand description
A library for finding kashida (tatweel) insertion points and priorities, driven by a small pattern language.
Given a text and a compiled pattern set, the crate returns the possible kashida insertion points and their priorities.
§Example
Here is an example that breaks a text into lines and justifies them by inserting kashida. For simplicity, the example assumes a monospaced font where every character has the same width.
- Break text into lines, greedily
- For each line, insert the highest priority kashidas first across the
line:
- Only the highest kashida point in any given word
- Up to
MAX_KASHIDAkashidas at each point
- Repeat with the next priority, until the line is filled, or there are no more kashida points to fill.
The example is also runnable with
cargo run --example justify:
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 {
// Split line into words, and find the highest-priority kashida point in
// each word.
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),
));
}
// Insert kashidas until the line is filled, starting with highest-priority
// points across the line.
let mut room = WIDTH - line.chars().count();
for priority in (0..=9).rev() {
for (word, point) in &mut words {
// No more space left to fill.
if room == 0 {
break;
}
if let Some(point) = point.filter(|point| point.priority == priority) {
// The kashida goes after the grapheme cluster at point.index.
let mut boundaries = GraphemeClusterSegmenter::new().segment_str(word);
let index = boundaries.nth(point.index as usize + 1).unwrap();
// Do not insert more than MAX_KASHIDA at each point.
let count = MAX_KASHIDA.min(room);
word.insert_str(index, &KASHIDA.repeat(count));
room -= count;
}
}
}
words.into_iter().map(|(word, _)| word).collect()
}
fn main() {
// Break the text into lines.
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());
// Print the unjustified lines.
println!("unjustified");
for line in &lines {
println!("{line}");
}
// Print justified lines, the last line is unjustified.
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}");
}Which prints:
unjustified قال أفلاطون: «الخط عقال العقل». وقال إقليدس الإغريقي: «الخط هندسة روحانية وإن ظهرت بآلة جسمانية». وقال أبو دلف رحالة القرن العاشر الميلادي: «الخط رياض العلوم». وقال النظام المعتزلي: «الخط أصيل في الروح وإن ظهر بحواس البدن». justified قال أفلاطون: «الخـط عقال العقل». وقــال إقليـدس الإغريقي: «الخــط هندســة روحانيــة وإن ظهرت بـآلة جسمانيــة». وقــال أبو دلف رحالة القرن العاشــر الميلادي: «الخــط ريــاض العـلوم». وقــال النــظام المعتزلي: «الخــط أصـيل في الروح وإن ظهر بحواس البدن».
Structs§
- Compile
Error - An error while compiling pattern text.
- Kashida
Point - A point where a kashida may be inserted.
- Pattern
Set - A compiled set of kashida insertion patterns.
Enums§
- Compile
Error Kind - What went wrong on a pattern line.
Functions§
- builtin_
pattern_ set - The built-in pattern set of that name, compiled on first use, or
Noneif there is none.builtin_pattern_set_nameslists and describes them. - builtin_
pattern_ set_ names - The names of the built-in pattern sets:
- compile_
pattern_ text - Compiles pattern text into a
PatternSet. - find_
kashida_ points - Kashida insertion points for
textunder the given pattern set. - find_
kashida_ points_ patterns - Kashida insertion points for
textfrom the pattern set alone. - is_
builtin_ pattern_ set - Whether
namerefers to a built-in pattern set, without compiling it, unlikebuiltin_pattern_set.