use std::fmt::{self, Debug, Formatter}; use std::mem; use std::ops::Range; use unicode_bidi::{BidiInfo, Level}; use xi_unicode::LineBreakIterator; use super::*; use crate::exec::FontProps; use crate::parse::is_newline; /// A node that arranges its children into a paragraph. #[derive(Debug, Clone, PartialEq)] pub struct ParNode { /// The inline direction of this paragraph. pub dir: Dir, /// The spacing to insert between each line. pub line_spacing: Length, /// The nodes to be arranged in a paragraph. pub children: Vec, } /// A child of a paragraph node. #[derive(Clone, PartialEq)] pub enum ParChild { /// Spacing between other nodes. Spacing(Length), /// A run of text and how to align it in its line. Text(TextNode, Align), /// Any child node and how to align it in its line. Any(AnyNode, Align), } /// A consecutive, styled run of text. #[derive(Clone, PartialEq)] pub struct TextNode { /// The text. pub text: String, /// Properties used for font selection and layout. pub props: FontProps, } impl Layout for ParNode { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec { 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 { ParChild::Spacing(_) => text.push(' '), ParChild::Text(node, _) => text.push_str(&node.text), ParChild::Any(_, _) => text.push('\u{FFFC}'), } ranges.push(start .. text.len()); } // Find out the BiDi embedding levels. let bidi = BidiInfo::new(&text, Level::from_dir(self.dir)); 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) => { layouter.push_text(ctx, range, &node.props, align); } ParChild::Any(ref node, align) => { for frame in node.layout(ctx, &layouter.areas) { layouter.push_frame(range.clone(), frame, align); } } } } layouter.finish() } } impl From for AnyNode { fn from(par: ParNode) -> Self { Self::new(par) } } struct ParLayouter<'a> { dir: Dir, line_spacing: Length, bidi: &'a BidiInfo<'a>, areas: Areas, finished: Vec, stack: Vec<(Length, Frame, Align)>, stack_size: Size, line: Line, } struct Line { items: Vec, width: Length, top: Length, bottom: Length, ruler: Align, hard: bool, } struct LineItem { range: Range, frame: Frame, align: Align, } impl<'a> ParLayouter<'a> { fn new(dir: Dir, line_spacing: Length, bidi: &'a BidiInfo<'a>, areas: Areas) -> Self { Self { dir, line_spacing, bidi, areas, finished: vec![], stack: vec![], stack_size: Size::ZERO, line: Line::new(true), } } /// Push horizontal spacing. fn push_spacing(&mut self, range: Range, amount: Length) { let amount = amount.min(self.areas.current.width - self.line.width); self.line.width += amount; self.line.items.push(LineItem { range, frame: Frame::new(Size::new(amount, Length::ZERO), Length::ZERO), align: Align::default(), }) } /// Push text with equal font properties, but possibly containing runs of /// different directions. fn push_text( &mut self, ctx: &mut LayoutContext, range: Range, 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, ) { // Position in the text at which the current line starts. let mut start = range.start; // The current line attempt: Text shaped up to the previous line break // 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(&(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 end != range.end { line = line.trim_end(); } // 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) { if mandatory { // 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 = end; last = None; } else { // 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 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, we just have to push it. self.push_frame(start .. end, frame, align); self.finish_line(false); start = end; } iter.next(); } // Leftovers. if let Some((frame, pos)) = last { self.push_frame(start .. pos, frame, align); } } fn push_frame(&mut self, range: Range, frame: Frame, align: Align) { // When the alignment of the last pushed frame (stored in the "ruler") // is further to the end than the new `frame`, we need a line break. // // For example // ``` // #align(right)[First] #align(center)[Second] // ``` // would be laid out as: // +----------------------------+ // | First | // | Second | // +----------------------------+ if self.line.ruler > align { self.finish_line(false); } // Find out whether the area still has enough space for this frame. if !self.usable().fits(frame.size) && self.line.width > Length::ZERO { self.finish_line(false); // Here, we can directly check whether the frame fits into // `areas.current` since we just called `finish_line`. while !self.areas.current.fits(frame.size) { if self.areas.in_full_last() { // The frame fits nowhere. // TODO: Should this be placed into the first area or the last? // TODO: Produce diagnostic once the necessary spans exist. break; } else { self.finish_area(); } } } // A line can contain frames with different alignments. Their exact // positions are calculated later depending on the alignments. let Frame { size, baseline, .. } = frame; self.line.items.push(LineItem { range, frame, align }); self.line.width += size.width; self.line.top = self.line.top.max(baseline); self.line.bottom = self.line.bottom.max(size.height - baseline); self.line.ruler = align; } fn usable(&self) -> Size { // Space occupied by previous lines is already removed from // `areas.current`, but the width of the current line needs to be // subtracted to make sure the frame fits. let mut usable = self.areas.current; usable.width -= self.line.width; usable } fn finish_line(&mut self, hard: bool) { let mut line = mem::replace(&mut self.line, Line::new(hard)); if !line.hard && line.items.is_empty() { return; } // BiDi reordering. line.reorder(&self.bidi); let full_size = { let expand = self.areas.expand.horizontal; let full = self.areas.full.width; Size::new(expand.resolve(line.width, full), line.top + line.bottom) }; let mut output = Frame::new(full_size, line.top + line.bottom); let mut offset = Length::ZERO; for item in line.items { // Align along the x axis. let x = item.align.resolve(if self.dir.is_positive() { offset .. full_size.width - line.width + offset } else { full_size.width - line.width + offset .. offset }); offset += item.frame.size.width; let pos = Point::new(x, line.top - item.frame.baseline); output.push_frame(pos, item.frame); } // Add line spacing, but only between lines, not after the last line. if !self.stack.is_empty() { self.stack_size.height += self.line_spacing; self.areas.current.height -= self.line_spacing; } self.stack.push((self.stack_size.height, output, line.ruler)); self.stack_size.height += full_size.height; self.stack_size.width = self.stack_size.width.max(full_size.width); self.areas.current.height -= full_size.height; } fn finish_area(&mut self) { let mut output = Frame::new(self.stack_size, Length::ZERO); let mut baseline = None; for (before, line, align) in mem::take(&mut self.stack) { // Align along the x axis. let x = align.resolve(if self.dir.is_positive() { Length::ZERO .. self.stack_size.width - line.size.width } else { self.stack_size.width - line.size.width .. Length::ZERO }); let pos = Point::new(x, before); baseline.get_or_insert(pos.y + line.baseline); output.push_frame(pos, line); } if let Some(baseline) = baseline { output.baseline = baseline; } self.finished.push(output); self.areas.next(); self.stack_size = Size::ZERO; } fn finish(mut self) -> Vec { self.finish_line(false); self.finish_area(); self.finished } } impl Line { fn new(hard: bool) -> Self { Self { items: vec![], width: Length::ZERO, top: Length::ZERO, bottom: Length::ZERO, ruler: Align::Start, hard, } } fn reorder(&mut self, bidi: &BidiInfo) { let items = &mut self.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 = 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) = 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) }); } } 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 { Self::Spacing(amount) => write!(f, "Spacing({:?})", amount), Self::Text(node, align) => write!(f, "Text({:?}, {:?})", node.text, align), Self::Any(any, align) => { f.debug_tuple("Any").field(any).field(align).finish() } } } } impl Debug for TextNode { fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "Text({:?})", self.text) } }