From c00cca3677b9b2f010099b969452969d6b47cb3c Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 30 Mar 2021 16:19:16 +0200 Subject: [PATCH] =?UTF-8?q?Refactor=20=E2=99=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layout/par.rs | 205 +++++++++++++++++++++++-------------- src/layout/shaping.rs | 23 +++-- tests/ref/text/shaping.png | Bin 12536 -> 12565 bytes 3 files changed, 142 insertions(+), 86 deletions(-) diff --git a/src/layout/par.rs b/src/layout/par.rs index de3b1bab7..5646d41b4 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -45,6 +45,7 @@ impl Layout for ParNode { let mut text = String::new(); let mut ranges = vec![]; + // Collect all text into one string used for BiDi analysis. for child in &self.children { let start = text.len(); match child { @@ -55,49 +56,20 @@ impl Layout for ParNode { ranges.push(start .. text.len()); } - let level = match self.dir { - Dir::LTR => Level::ltr(), - Dir::RTL => Level::rtl(), - _ => panic!("invalid paragraph direction"), - }; + // Find out the BiDi embedding levels. + let bidi = BidiInfo::new(&text, Level::from_dir(self.dir)); - let bidi = BidiInfo::new(&text, Some(level)); let mut layouter = ParLayouter::new(self.dir, self.line_spacing, &bidi, areas.clone()); + // Layout the children. for (range, child) in ranges.into_iter().zip(&self.children) { match *child { ParChild::Spacing(amount) => { layouter.push_spacing(range, amount); } ParChild::Text(ref node, align) => { - let mut start = range.start; - let mut last = None; - for (idx, level) in bidi.levels[range.clone()].iter().enumerate() { - let idx = range.start + idx; - - if last.map_or(false, |last| last != level) { - // Push the text up until `idx` (exclusively). - layouter.push_text( - ctx, - start .. idx, - &text[start .. idx], - &node.props, - align, - ); - start = idx; - } - - last = Some(level); - } - - layouter.push_text( - ctx, - start .. range.end, - &text[start .. range.end], - &node.props, - align, - ); + layouter.push_text(ctx, range, &node.props, align); } ParChild::Any(ref node, align) => { for frame in node.layout(ctx, &layouter.areas) { @@ -126,13 +98,13 @@ struct ParLayouter<'a> { stack: Vec<(Length, Frame, Align)>, stack_size: Size, line: Line, - hard: bool, } struct Line { items: Vec, size: Size, ruler: Align, + hard: bool, } struct LineItem { @@ -155,11 +127,12 @@ impl<'a> ParLayouter<'a> { items: vec![], size: Size::ZERO, ruler: Align::Start, + hard: true, }, - hard: true, } } + /// Push horizontal spacing. fn push_spacing(&mut self, range: Range, amount: Length) { let amount = amount.min(self.areas.current.width - self.line.size.width); self.line.size.width += amount; @@ -170,11 +143,42 @@ impl<'a> ParLayouter<'a> { }) } + /// Push text with equal font properties, but possibly containing runs of + /// different directions. fn push_text( &mut self, ctx: &mut LayoutContext, range: Range, - text: &str, + props: &FontProps, + align: Align, + ) { + let levels = &self.bidi.levels[range.clone()]; + + let mut start = range.start; + let mut last = match levels.first() { + Some(&level) => level, + None => return, + }; + + // Split into runs with the same embedding level. + for (idx, &level) in levels.iter().enumerate() { + let end = range.start + idx; + if last != level { + self.push_run(ctx, start .. end, last.dir(), props, align); + start = end; + } + last = level; + } + + self.push_run(ctx, start .. range.end, last.dir(), props, align); + } + + /// Push a text run with fixed direction. + fn push_run( + &mut self, + ctx: &mut LayoutContext, + range: Range, + dir: Dir, props: &FontProps, align: Align, ) { @@ -185,45 +189,54 @@ impl<'a> ParLayouter<'a> { // opportunity. let mut last = None; + // Create an iterator over the line break opportunities. + let text = &self.bidi.text[range.clone()]; let mut iter = LineBreakIterator::new(text).peekable(); - while let Some(&(pos, mandatory)) = iter.peek() { - let line = &text[start - range.start .. pos]; + + while let Some(&(end, mandatory)) = iter.peek() { + // Slice the line of text. + let end = range.start + end; + let line = &self.bidi.text[start .. end]; // Remove trailing newline and spacing at the end of lines. let mut line = line.trim_end_matches(is_newline); - if pos != text.len() { + if end != range.end { line = line.trim_end(); } - let pos = range.start + pos; - let frame = shape(line, &mut ctx.env.fonts, props); + // Shape the line. + let frame = shape(line, dir, &mut ctx.env.fonts, props); + // Find out whether the runs still fits into the line. if self.usable().fits(frame.size) { - // Still fits into the line. if mandatory { - // We have to break here. - self.push_frame(start .. pos, frame, align); + // We have to break here because the text contained a hard + // line break like "\n". + self.push_frame(start .. end, frame, align); self.finish_line(true); - start = pos; + start = end; last = None; } else { - last = Some((frame, pos)); + // Still fits, so we remember it and try making the line + // even longer. + last = Some((frame, end)); } } else if let Some((frame, pos)) = last.take() { - // The line start..pos doesn't fit. So we write the line up to - // the last position and retry writing just the single piece - // behind it. + // The line we just tried doesn't fit. So we write the line up + // to the last position. self.push_frame(start .. pos, frame, align); self.finish_line(false); start = pos; + + // Retry writing just the single piece. continue; } else { - // Since last is `None`, we are at the first piece behind a line - // break and it still doesn't fit. Since we can't break it up - // further, so we just have to push it. - self.push_frame(start .. pos, frame, align); + // Since `last` is `None`, we are at the first piece behind a + // line break and it still doesn't fit. Since we can't break it + // up further, we just have to push it. + self.push_frame(start .. end, frame, align); self.finish_line(false); - start = pos; + start = end; } iter.next(); @@ -289,30 +302,12 @@ impl<'a> ParLayouter<'a> { } fn finish_line(&mut self, hard: bool) { - if !mem::replace(&mut self.hard, hard) && self.line.items.is_empty() { + if !mem::replace(&mut self.line.hard, hard) && self.line.items.is_empty() { return; } - let mut items = mem::take(&mut self.line.items); - if let (Some(first), Some(last)) = (items.first(), items.last()) { - let range = first.range.start .. last.range.end; - let para = self - .bidi - .paragraphs - .iter() - .find(|para| para.range.contains(&range.start)) - .unwrap(); - - let (levels, ranges) = self.bidi.visual_runs(¶, range); - - items.sort_by_key(|item| { - let start = item.range.start; - let idx = ranges.iter().position(|r| r.contains(&start)).unwrap(); - let ltr = levels[start].is_ltr(); - let sec = start as isize * if ltr { 1 } else { -1 }; - (idx, sec) - }); - } + // BiDi reordering. + self.reorder_line(); let full_size = { let expand = self.areas.expand.horizontal; @@ -326,7 +321,7 @@ impl<'a> ParLayouter<'a> { let mut output = Frame::new(full_size); let mut offset = Length::ZERO; - for item in items { + for item in mem::take(&mut self.line.items) { // Align along the x axis. let x = item.align.resolve(if self.dir.is_positive() { offset .. full_size.width - self.line.size.width + offset @@ -354,6 +349,45 @@ impl<'a> ParLayouter<'a> { self.line.ruler = Align::Start; } + fn reorder_line(&mut self) { + let items = &mut self.line.items; + let line_range = match (items.first(), items.last()) { + (Some(first), Some(last)) => first.range.start .. last.range.end, + _ => return, + }; + + // Find the paragraph that contains the frame. + let para = self + .bidi + .paragraphs + .iter() + .find(|para| para.range.contains(&line_range.start)) + .unwrap(); + + // Compute the reordered ranges in visual order (left to right). + let (levels, ranges) = self.bidi.visual_runs(para, line_range); + + // Reorder the items. + items.sort_by_key(|item| { + let Range { start, end } = item.range; + + // Determine the index in visual order. + let idx = ranges.iter().position(|r| r.contains(&start)).unwrap(); + + // A run might span more than one frame. To sort frames inside a run + // based on the run's direction, we compute the distance from + // the "start" of the run. + let run = &ranges[idx]; + let dist = if levels[start].is_ltr() { + start - run.start + } else { + run.end - end + }; + + (idx, dist) + }); + } + fn finish_area(&mut self) { let mut output = Frame::new(self.stack_size); for (before, line, align) in mem::take(&mut self.stack) { @@ -380,6 +414,25 @@ impl<'a> ParLayouter<'a> { } } +trait LevelExt: Sized { + fn from_dir(dir: Dir) -> Option; + fn dir(self) -> Dir; +} + +impl LevelExt for Level { + fn from_dir(dir: Dir) -> Option { + match dir { + Dir::LTR => Some(Level::ltr()), + Dir::RTL => Some(Level::rtl()), + _ => None, + } + } + + fn dir(self) -> Dir { + if self.is_ltr() { Dir::LTR } else { Dir::RTL } + } +} + impl Debug for ParChild { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index 8d035516d..6e4b2b410 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -5,12 +5,13 @@ use ttf_parser::GlyphId; use super::{Element, Frame, ShapedText}; use crate::env::FontLoader; use crate::exec::FontProps; -use crate::geom::{Point, Size}; +use crate::geom::{Dir, Point, Size}; /// Shape text into a frame containing [`ShapedText`] runs. -pub fn shape(text: &str, loader: &mut FontLoader, props: &FontProps) -> Frame { +pub fn shape(text: &str, dir: Dir, loader: &mut FontLoader, props: &FontProps) -> Frame { let mut frame = Frame::new(Size::ZERO); - shape_segment(&mut frame, text, loader, props, props.families.iter(), None); + let iter = props.families.iter(); + shape_segment(&mut frame, text, dir, loader, props, iter, None); frame } @@ -18,6 +19,7 @@ pub fn shape(text: &str, loader: &mut FontLoader, props: &FontProps) -> Frame { fn shape_segment<'a>( frame: &mut Frame, text: &str, + dir: Dir, loader: &mut FontLoader, props: &FontProps, mut families: impl Iterator + Clone, @@ -57,11 +59,11 @@ fn shape_segment<'a>( // Fill the buffer with our text. let mut buffer = UnicodeBuffer::new(); buffer.push_str(text); - buffer.guess_segment_properties(); - - // Find out the text direction. - // TODO: Replace this once we do BiDi. - let rtl = matches!(buffer.direction(), rustybuzz::Direction::RightToLeft); + buffer.set_direction(match dir { + Dir::LTR => rustybuzz::Direction::LeftToRight, + Dir::RTL => rustybuzz::Direction::RightToLeft, + _ => unimplemented!(), + }); // Shape! let glyphs = rustybuzz::shape(face.buzz(), &[], buffer); @@ -92,7 +94,7 @@ fn shape_segment<'a>( // Because Harfbuzz outputs glyphs in visual order, the start // cluster actually corresponds to the last codepoint in // right-to-left text. - if rtl { + if !dir.is_positive() { assert!(end <= start); std::mem::swap(&mut start, &mut end); } @@ -102,9 +104,10 @@ fn shape_segment<'a>( // char. let offset = text[end ..].chars().next().unwrap().len_utf8(); let range = start .. end + offset; + let part = &text[range]; // Recursively shape the tofu sequence with the next family. - shape_segment(frame, &text[range], loader, props, families.clone(), first); + shape_segment(frame, part, dir, loader, props, families.clone(), first); } else { // Add the glyph to the shaped output. // TODO: Don't ignore y_advance and y_offset. diff --git a/tests/ref/text/shaping.png b/tests/ref/text/shaping.png index e6da3e4d2f7d38286784e3903ec35f1118aeb448..676a67ff79a1afd0c732e1cc2de066eb6d00b430 100644 GIT binary patch literal 12565 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