Word wrapping with bi-directional text

This commit is contained in:
Jeremy Soller 2022-10-06 16:09:43 -06:00
parent 028894d62c
commit fc6904dc3b
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
6 changed files with 100 additions and 40 deletions

View file

@ -11,6 +11,7 @@ orbclient = "0.3"
rusttype = { version = "0.9", optional = true }
rustybuzz = "0.5"
unicode-bidi = "0.3"
unicode-linebreak = "0.1"
unicode-script = "0.5"
[features]

View file

@ -1 +1,3 @@
العربية
عندما يريد العالم أن ‪يتكلّم ، فهو يتحدّث بلغة يونيكود. تسجّل الآن لحضور المؤتمر الدولي العاشر ليونيكود (Unicode Conference)، الذي سيعقد في 10-12 آذار 1997 بمدينة مَايِنْتْس، ألمانيا. و سيجمع المؤتمر بين خبراء من كافة قطاعات الصناعة على الشبكة العالمية انترنيت ويونيكود، حيث ستتم، على الصعيدين الدولي والمحلي على حد سواء مناقشة سبل استخدام يونكود في النظم القائمة وفيما يخص التطبيقات الحاسوبية، الخطوط، تصميم النصوص والحوسبة متعددة اللغات.

View file

@ -1 +1,3 @@
כאשר העולם רוצה לדבר, הוא מדבר ב־Unicode. הירשמו כעת לכנס Unicode הבינלאומי העשירי, שייערך בין התאריכים 12־10 במרץ 1997, בְּמָיְינְץ שבגרמניה. בכנס ישתתפו מומחים מכל ענפי התעשייה בנושא האינטרנט העולמי וה־Unicode, בהתאמה לשוק הבינלאומי והמקומי, ביישום Unicode במערכות הפעלה וביישומים, בגופנים, בפריסת טקסט ובמחשוב רב־לשוני.
Many computer programs fail to display bidirectional text correctly. For example, this page is mostly LTR English script, and here is the RTL Hebrew name Sarah: שרה, spelled sin (ש) on the right, resh (ר) in the middle, and heh (ה) on the left.

View file

@ -9,16 +9,13 @@ impl<'a> FontMatches<'a> {
let word = &line[start_word..end_word];
let mut words_by_font = Vec::with_capacity(self.fonts.len());
for (font_i, font) in self.fonts.iter().enumerate() {
for font in self.fonts.iter() {
let font_scale = font.rustybuzz.units_per_em() as f32;
let mut buffer = rustybuzz::UnicodeBuffer::new();
buffer.push_str(word);
buffer.guess_segment_properties();
let direction = buffer.direction();
if font_i == 0 {
println!("{:?}, {:?}: '{}'", buffer.script(), direction, word);
}
let glyph_buffer = rustybuzz::shape(&font.rustybuzz, &[], buffer);
let glyph_infos = glyph_buffer.glyph_infos();
@ -86,6 +83,12 @@ impl<'a> FontMatches<'a> {
_ => (),
}
/*
for glyph in glyphs.iter() {
println!("'{}': {}, {}, {}, {}", &line[glyph.start..glyph.end], glyph.x_advance, glyph.y_advance, glyph.x_offset, glyph.y_offset);
}
*/
let word = FontShapeWord { glyphs };
if misses == 0 {
return word;
@ -110,14 +113,56 @@ impl<'a> FontMatches<'a> {
words_by_font.remove(least_misses_i).1
}
fn shape_span(&self, line: &str, start_span: usize, end_span: usize, rtl: bool) -> FontShapeSpan {
fn shape_span(&self, line: &str, start_span: usize, end_span: usize, para_rtl: bool, span_rtl: bool) -> FontShapeSpan {
use unicode_script::{Script, UnicodeScript};
let span = &line[start_span..end_span];
let mut words = Vec::new();
println!("Span {}: '{}'", if span_rtl { "RTL" } else { "LTR" }, span);
let mut words = vec![
self.shape_word(line, start_span, end_span),
];
if span_rtl {
for word in words.iter_mut() {
word.glyphs.reverse();
}
}
//TODO: improve performance
for (linebreak, _) in unicode_linebreak::linebreaks(span) {
println!("linebreak {}", linebreak);
let mut glyphs_opt = None;
'words: for word_i in 0..words.len() {
for glyph_i in 0..words[word_i].glyphs.len() {
if words[word_i].glyphs[glyph_i].start == start_span + linebreak {
println!("glyph {}", words[word_i].glyphs[glyph_i].start);
println!("word '{}'", &line[
words[word_i].glyphs[0].start
..
words[word_i].glyphs[glyph_i].start
]);
glyphs_opt = Some(words[word_i].glyphs.split_off(glyph_i));
break 'words;
}
}
}
if let Some(glyphs) = glyphs_opt {
words.push(FontShapeWord { glyphs });
}
}
if span_rtl {
for word in words.iter_mut() {
word.glyphs.reverse();
}
}
if para_rtl != span_rtl {
words.reverse();
}
let mut start = 0;
/*
let mut word_script = Script::Unknown;
for (i, c) in span.char_indices() {
@ -131,11 +176,11 @@ impl<'a> FontMatches<'a> {
word_script = next_script;
}
}
*/
words.push(self.shape_word(line, start_span + start, end_span));
*/
FontShapeSpan {
rtl,
rtl: span_rtl,
words,
}
}
@ -158,12 +203,12 @@ impl<'a> FontMatches<'a> {
for i in paragraph.para.range.clone() {
let next_rtl = paragraph.info.levels[i].is_rtl();
if span_rtl != next_rtl {
spans.push(self.shape_span(line, start, i, para_rtl, span_rtl));
span_rtl = next_rtl;
spans.push(self.shape_span(line, start, i, span_rtl));
start = i;
}
}
spans.push(self.shape_span(line, start, line.len(), span_rtl));
spans.push(self.shape_span(line, start, line.len(), para_rtl, span_rtl));
para_rtl
};

View file

@ -44,26 +44,31 @@ impl<'a> FontShapeLine<'a> {
let mut y = 0.0;
for span in self.spans.iter() {
for word in span.words.iter() {
let mut word_size = 0.0;
for glyph in word.glyphs.iter() {
word_size += font_size as f32 * glyph.x_advance;
}
//TODO: make wrapping optional
if x + word_size > line_width as f32 && ! glyphs.is_empty() {
let mut glyphs_swap = Vec::new();
std::mem::swap(&mut glyphs, &mut glyphs_swap);
layout_lines.insert(layout_i, FontLayoutLine {
line_i: self.line_i,
glyphs: glyphs_swap
});
layout_i += 1;
x = 0.0;
y = 0.0;
}
for glyph in word.glyphs.iter() {
let x_advance = font_size as f32 * glyph.x_advance;
let y_advance = font_size as f32 * glyph.y_advance;
let x_offset = font_size as f32 * glyph.x_offset;
let y_offset = font_size as f32 * glyph.y_offset;
//TODO: make wrapping optional
if x + x_advance > line_width as f32 {
let mut glyphs_swap = Vec::new();
std::mem::swap(&mut glyphs, &mut glyphs_swap);
layout_lines.insert(layout_i, FontLayoutLine {
line_i: self.line_i,
glyphs: glyphs_swap
});
layout_i += 1;
x = 0.0;
y = 0.0;
}
#[cfg(feature = "ab_glyph")]
let inner = glyph.font.outline_glyph(
glyph.inner.with_scale_and_position(
@ -115,26 +120,31 @@ impl<'a> FontShapeLine<'a> {
let mut y = 0.0;
for span in self.spans.iter() {
for word in span.words.iter() {
let mut word_size = 0.0;
for glyph in word.glyphs.iter() {
word_size += font_size as f32 * glyph.x_advance;
}
//TODO: make wrapping optional
if x - word_size < 0.0 && ! glyphs.is_empty() {
let mut glyphs_swap = Vec::new();
std::mem::swap(&mut glyphs, &mut glyphs_swap);
layout_lines.insert(layout_i, FontLayoutLine {
line_i: self.line_i,
glyphs: glyphs_swap
});
layout_i += 1;
x = line_width as f32;
y = 0.0;
}
for glyph in word.glyphs.iter().rev() {
let x_advance = font_size as f32 * glyph.x_advance;
let y_advance = font_size as f32 * glyph.y_advance;
let x_offset = font_size as f32 * glyph.x_offset;
let y_offset = font_size as f32 * glyph.y_offset;
//TODO: make wrapping optional
if x - x_advance < 0.0 {
let mut glyphs_swap = Vec::new();
std::mem::swap(&mut glyphs, &mut glyphs_swap);
layout_lines.insert(layout_i, FontLayoutLine {
line_i: self.line_i,
glyphs: glyphs_swap
});
layout_i += 1;
x = line_width as f32;
y = 0.0;
}
x -= x_advance;
#[cfg(feature = "ab_glyph")]

View file

@ -181,7 +181,7 @@ fn main() {
(28, 36), // Title 2
(32, 44), // Title 1
];
let font_size_default = 2; // Title 4
let font_size_default = 5; // Title 1
let mut font_size_i = font_size_default;
let text = if let Some(arg) = env::args().nth(1) {