Improvements to bidi rendering

This commit is contained in:
Jeremy Soller 2022-10-06 13:47:25 -06:00
parent 0b0ed1df68
commit 956331e152
No known key found for this signature in database
GPG key ID: 87F211AF2BE4C2FE
4 changed files with 187 additions and 110 deletions

View file

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

View file

@ -1 +1 @@
כאשר העולם רוצה לדבר, הוא מדבר ב־Unicode. הירשמו כעת לכנס Unicode הבינלאומי העשירי, שייערך בין התאריכים 12־10 במרץ 1997, בְּמָיְינְץ שבגרמניה. בכנס ישתתפו מומחים מכל ענפי התעשייה בנושא האינטרנט העולמי וה־Unicode, בהתאמה לשוק הבינלאומי והמקומי, ביישום Unicode במערכות הפעלה וביישומים, בגופנים, בפריסת טקסט ובמחשוב רב־לשוני.
כאשר העולם רוצה לדבר, הוא מדבר ב־Unicode. הירשמו כעת לכנס Unicode הבינלאומי העשירי, שייערך בין התאריכים 12־10 במרץ 1997, בְּמָיְינְץ שבגרמניה. בכנס ישתתפו מומחים מכל ענפי התעשייה בנושא האינטרנט העולמי וה־Unicode, בהתאמה לשוק הבינלאומי והמקומי, ביישום Unicode במערכות הפעלה וביישומים, בגופנים, בפריסת טקסט ובמחשוב רב־לשוני.

View file

@ -1,23 +1,23 @@
use super::{Font, FontLineIndex, FontShapeGlyph, FontShapeLine, FontShapeSpan};
use super::{Font, FontLineIndex, FontShapeGlyph, FontShapeWord, FontShapeLine, FontShapeSpan};
pub struct FontMatches<'a> {
pub fonts: Vec<&'a Font<'a>>,
}
impl<'a> FontMatches<'a> {
fn shape_span(&self, line: &str, start_span: usize, end_span: usize) -> FontShapeSpan {
let span = &line[start_span..end_span];
fn shape_word(&self, line: &str, start_word: usize, end_word: usize) -> FontShapeWord {
let word = &line[start_word..end_word];
let mut spans_by_font = Vec::with_capacity(self.fonts.len());
let mut words_by_font = Vec::with_capacity(self.fonts.len());
for (font_i, font) in self.fonts.iter().enumerate() {
let font_scale = font.rustybuzz.units_per_em() as f32;
let mut buffer = rustybuzz::UnicodeBuffer::new();
buffer.push_str(span);
buffer.push_str(word);
buffer.guess_segment_properties();
let direction = buffer.direction();
if font_i == 0 {
//println!("{:?}, {:?}: '{}'", script, direction, span);
println!("{:?}, {:?}: '{}'", buffer.script(), direction, word);
}
let glyph_buffer = rustybuzz::shape(&font.rustybuzz, &[], buffer);
@ -44,8 +44,8 @@ impl<'a> FontMatches<'a> {
let inner = font.rusttype.glyph(rusttype::GlyphId(info.glyph_id as u16));
glyphs.push(FontShapeGlyph {
start: start_span + info.cluster as usize,
end: end_span, // Set later
start: start_word + info.cluster as usize,
end: end_word, // Set later
x_advance,
y_advance,
x_offset,
@ -86,20 +86,17 @@ impl<'a> FontMatches<'a> {
_ => (),
}
let span = FontShapeSpan {
direction,
glyphs
};
let word = FontShapeWord { glyphs };
if misses == 0 {
return span;
return word;
} else {
spans_by_font.push((misses, span));
words_by_font.push((misses, word));
}
}
let mut least_misses_i = 0;
let mut least_misses = usize::MAX;
for (i, (misses, _)) in spans_by_font.iter().enumerate() {
for (i, (misses, _)) in words_by_font.iter().enumerate() {
if *misses < least_misses {
least_misses_i = i;
least_misses = *misses;
@ -110,41 +107,65 @@ impl<'a> FontMatches<'a> {
//println!("MISSES {}, {}", least_misses_i, least_misses);
}
spans_by_font.remove(least_misses_i).1
words_by_font.remove(least_misses_i).1
}
fn shape_span(&self, line: &str, start_span: usize, end_span: usize, rtl: bool) -> FontShapeSpan {
use unicode_script::{Script, UnicodeScript};
let span = &line[start_span..end_span];
let mut words = Vec::new();
let mut start = 0;
/*
let mut word_script = Script::Unknown;
for (i, c) in span.char_indices() {
let next_script = c.script();
if word_script != next_script && word_script != Script::Unknown {
// No combination, start new span
words.push(self.shape_word(line, start_span + start, start_span + i));
start = i;
word_script = Script::Unknown;
} else {
word_script = next_script;
}
}
*/
words.push(self.shape_word(line, start_span + start, end_span));
FontShapeSpan {
rtl,
words,
}
}
pub fn shape_line(&self, line_i: FontLineIndex, line: &str) -> FontShapeLine {
use unicode_script::{Script, UnicodeScript};
//TODO: more special handling of characters
let mut spans = Vec::new();
let mut start = 0;
let mut prev = Script::Unknown;
for (i, c) in line.char_indices() {
if ! line.is_char_boundary(i) {
continue;
}
let cur = c.script();
if prev != cur && prev != Script::Unknown {
// No combination, start new span
spans.push(self.shape_span(line, start, i));
start = i;
prev = Script::Unknown;
} else {
prev = cur;
}
}
spans.push(self.shape_span(line, start, line.len()));
let bidi = unicode_bidi::BidiInfo::new(line, None);
let rtl = if bidi.paragraphs.is_empty() {
false
} else {
assert_eq!(bidi.paragraphs.len(), 1);
bidi.paragraphs[0].level.is_rtl()
let para_info = &bidi.paragraphs[0];
let para_rtl = para_info.level.is_rtl();
let paragraph = unicode_bidi::Paragraph::new(&bidi, &para_info);
let mut start = 0;
let mut span_rtl = para_rtl;
for i in paragraph.para.range.clone() {
let next_rtl = paragraph.info.levels[i].is_rtl();
if span_rtl != next_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));
para_rtl
};
FontShapeLine {

View file

@ -19,11 +19,15 @@ pub struct FontShapeGlyph<'a> {
pub inner: rusttype::Glyph<'a>,
}
pub struct FontShapeSpan<'a> {
pub direction: rustybuzz::Direction,
pub struct FontShapeWord<'a> {
pub glyphs: Vec<FontShapeGlyph<'a>>,
}
pub struct FontShapeSpan<'a> {
pub rtl: bool,
pub words: Vec<FontShapeWord<'a>>,
}
pub struct FontShapeLine<'a> {
pub line_i: FontLineIndex,
pub rtl: bool,
@ -31,47 +35,22 @@ pub struct FontShapeLine<'a> {
}
impl<'a> FontShapeLine<'a> {
pub fn layout(&self, font_size: i32, line_width: i32, layout_lines: &mut Vec<FontLayoutLine<'a>>, mut layout_i: usize) {
fn layout_ltr(&self, font_size: i32, line_width: i32, layout_lines: &mut Vec<FontLayoutLine<'a>>, mut layout_i: usize) {
let mut push_line = true;
let mut glyphs = Vec::new();
let mut x = 0.0;
let mut y = 0.0;
for span in self.spans.iter() {
let mut span_width = 0.0;
for glyph in span.glyphs.iter() {
let x_advance = font_size as f32 * glyph.x_advance;
span_width += x_advance;
}
for word in span.words.iter() {
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;
if self.rtl {
if glyphs.is_empty() {
x = line_width as f32;
}
x -= span_width;
}
for glyph in span.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 self.rtl {
if x < 0.0 {
let mut glyphs_swap = Vec::new();
std::mem::swap(&mut glyphs, &mut glyphs_swap);
layout_lines.push(FontLayoutLine {
line_i: self.line_i,
glyphs: glyphs_swap
});
layout_i += 1;
x = line_width as f32 - x_advance;
y = 0.0;
}
} else {
//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);
@ -84,43 +63,39 @@ impl<'a> FontShapeLine<'a> {
x = 0.0;
y = 0.0;
}
}
#[cfg(feature = "ab_glyph")]
let inner = glyph.font.outline_glyph(
glyph.inner.with_scale_and_position(
font_size as f32,
ab_glyph::point(
#[cfg(feature = "ab_glyph")]
let inner = glyph.font.outline_glyph(
glyph.inner.with_scale_and_position(
font_size as f32,
ab_glyph::point(
x + x_offset,
y + y_offset,
)
)
);
#[cfg(feature = "rusttype")]
let inner = glyph.inner.clone()
.scaled(rusttype::Scale::uniform(font_size as f32))
.positioned(rusttype::point(
x + x_offset,
y + y_offset,
)
)
);
));
#[cfg(feature = "rusttype")]
let inner = glyph.inner.clone()
.scaled(rusttype::Scale::uniform(font_size as f32))
.positioned(rusttype::point(
x + x_offset,
y + y_offset,
));
glyphs.push(FontLayoutGlyph {
start: glyph.start,
end: glyph.end,
x,
w: x_advance,
inner,
phantom: PhantomData,
});
push_line = true;
glyphs.push(FontLayoutGlyph {
start: glyph.start,
end: glyph.end,
x,
w: x_advance,
inner,
phantom: PhantomData,
});
push_line = true;
x += x_advance;
y += y_advance;
}
if self.rtl {
x -= span_width;
x += x_advance;
y += y_advance;
}
}
}
@ -131,4 +106,85 @@ impl<'a> FontShapeLine<'a> {
});
}
}
fn layout_rtl(&self, font_size: i32, line_width: i32, layout_lines: &mut Vec<FontLayoutLine<'a>>, mut layout_i: usize) {
let mut push_line = true;
let mut glyphs = Vec::new();
let mut x = line_width as f32;
let mut y = 0.0;
for span in self.spans.iter() {
for word in span.words.iter() {
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")]
let inner = glyph.font.outline_glyph(
glyph.inner.with_scale_and_position(
font_size as f32,
ab_glyph::point(
x + x_offset,
y + y_offset,
)
)
);
#[cfg(feature = "rusttype")]
let inner = glyph.inner.clone()
.scaled(rusttype::Scale::uniform(font_size as f32))
.positioned(rusttype::point(
x + x_offset,
y + y_offset,
));
glyphs.push(FontLayoutGlyph {
start: glyph.start,
end: glyph.end,
x,
w: x_advance,
inner,
phantom: PhantomData,
});
push_line = true;
y += y_advance;
}
}
}
if push_line {
layout_lines.insert(layout_i, FontLayoutLine {
line_i: self.line_i,
glyphs
});
}
}
pub fn layout(&self, font_size: i32, line_width: i32, layout_lines: &mut Vec<FontLayoutLine<'a>>, layout_i: usize) {
if self.rtl {
self.layout_rtl(font_size, line_width, layout_lines, layout_i);
} else {
self.layout_ltr(font_size, line_width, layout_lines, layout_i);
}
}
}