diff options
| -rw-r--r-- | src/eval/template.rs | 77 | ||||
| -rw-r--r-- | src/eval/walk.rs | 1 | ||||
| -rw-r--r-- | src/layout/deco.rs | 92 | ||||
| -rw-r--r-- | src/layout/mod.rs | 2 | ||||
| -rw-r--r-- | src/layout/par.rs | 203 | ||||
| -rw-r--r-- | src/library/text.rs | 29 |
6 files changed, 202 insertions, 202 deletions
diff --git a/src/eval/template.rs b/src/eval/template.rs index 11ea3f56..fe3d0cca 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -22,7 +22,7 @@ pub struct Template(Rc<Vec<TemplateNode>>); #[derive(Clone)] enum TemplateNode { /// A word space. - Space(Vec<Decoration>), + Space, /// A line break. Linebreak, /// A paragraph break. @@ -30,11 +30,13 @@ enum TemplateNode { /// A page break. Pagebreak(bool), /// Plain text. - Text(EcoString, Vec<Decoration>), + Text(EcoString), /// Spacing. Spacing(GenAxis, Linear), + /// A decorated template. + Decorated(Decoration, Template), /// An inline node builder. - Inline(Rc<dyn Fn(&Style) -> InlineNode>, Vec<Decoration>), + Inline(Rc<dyn Fn(&Style) -> InlineNode>), /// An block node builder. Block(Rc<dyn Fn(&Style) -> BlockNode>), /// Save the current style. @@ -57,7 +59,7 @@ impl Template { F: Fn(&Style) -> T + 'static, T: Into<InlineNode>, { - let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]); + let node = TemplateNode::Inline(Rc::new(move |s| f(s).into())); Self(Rc::new(vec![node])) } @@ -73,7 +75,7 @@ impl Template { /// Add a word space to the template. pub fn space(&mut self) { - self.make_mut().push(TemplateNode::Space(vec![])); + self.make_mut().push(TemplateNode::Space); } /// Add a line break to the template. @@ -93,7 +95,7 @@ impl Template { /// Add text to the template. pub fn text(&mut self, text: impl Into<EcoString>) { - self.make_mut().push(TemplateNode::Text(text.into(), vec![])); + self.make_mut().push(TemplateNode::Text(text.into())); } /// Add text, but in monospace. @@ -109,19 +111,6 @@ impl Template { self.make_mut().push(TemplateNode::Spacing(axis, spacing)); } - /// Add a decoration to all contained nodes. - pub fn decorate(&mut self, deco: Decoration) { - for node in self.make_mut() { - let decos = match node { - TemplateNode::Space(decos) => decos, - TemplateNode::Text(_, decos) => decos, - TemplateNode::Inline(_, decos) => decos, - _ => continue, - }; - decos.push(deco.clone()); - } - } - /// Register a restorable snapshot. pub fn save(&mut self) { self.make_mut().push(TemplateNode::Save); @@ -154,6 +143,11 @@ impl Template { wrapper } + /// Add a decoration to all contained nodes. + pub fn decorate(self, deco: Decoration) -> Self { + Self(Rc::new(vec![TemplateNode::Decorated(deco, self)])) + } + /// Build the stack node resulting from instantiating the template with the /// given style. pub fn to_stack(&self, style: &Style) -> StackNode { @@ -223,7 +217,7 @@ impl Add<Str> for Template { type Output = Self; fn add(mut self, rhs: Str) -> Self::Output { - Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into(), vec![])); + Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into())); self } } @@ -232,7 +226,7 @@ impl Add<Template> for Str { type Output = Template; fn add(self, mut rhs: Template) -> Self::Output { - Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into(), vec![])); + Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into())); rhs } } @@ -283,26 +277,31 @@ impl Builder { self.pagebreak(true, false); } } - TemplateNode::Space(decos) => self.space(decos), + TemplateNode::Space => self.space(), TemplateNode::Linebreak => self.linebreak(), TemplateNode::Parbreak => self.parbreak(), TemplateNode::Pagebreak(keep) => self.pagebreak(*keep, true), - TemplateNode::Text(text, decos) => self.text(text, decos), + TemplateNode::Text(text) => self.text(text), TemplateNode::Spacing(axis, amount) => self.spacing(*axis, *amount), - TemplateNode::Inline(f, decos) => self.inline(f(&self.style), decos), + TemplateNode::Decorated(deco, template) => { + self.stack.par.push(ParChild::Decorate(deco.clone())); + self.template(template); + self.stack.par.push(ParChild::Undecorate); + } + TemplateNode::Inline(f) => self.inline(f(&self.style)), TemplateNode::Block(f) => self.block(f(&self.style)), TemplateNode::Modify(f) => f(&mut self.style), } } /// Push a word space into the active paragraph. - fn space(&mut self, decos: &[Decoration]) { - self.stack.par.push_soft(self.make_text_node(' ', decos.to_vec())); + fn space(&mut self) { + self.stack.par.push_soft(self.make_text_node(' ')); } /// Apply a forced line break. fn linebreak(&mut self) { - self.stack.par.push_hard(self.make_text_node('\n', vec![])); + self.stack.par.push_hard(self.make_text_node('\n')); } /// Apply a forced paragraph break. @@ -322,14 +321,14 @@ impl Builder { } /// Push text into the active paragraph. - fn text(&mut self, text: impl Into<EcoString>, decos: &[Decoration]) { - self.stack.par.push(self.make_text_node(text, decos.to_vec())); + fn text(&mut self, text: impl Into<EcoString>) { + self.stack.par.push(self.make_text_node(text)); } /// Push an inline node into the active paragraph. - fn inline(&mut self, node: impl Into<InlineNode>, decos: &[Decoration]) { + fn inline(&mut self, node: impl Into<InlineNode>) { let align = self.style.aligns.inline; - self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec())); + self.stack.par.push(ParChild::Any(node.into(), align)); } /// Push a block node into the active stack, finishing the active paragraph. @@ -367,16 +366,11 @@ impl Builder { /// Construct a text node with the given text and settings from the current /// style. - fn make_text_node( - &self, - text: impl Into<EcoString>, - decos: Vec<Decoration>, - ) -> ParChild { + fn make_text_node(&self, text: impl Into<EcoString>) -> ParChild { ParChild::Text( text.into(), self.style.aligns.inline, Rc::clone(&self.style.text), - decos, ) } } @@ -489,14 +483,11 @@ impl ParBuilder { } fn push_inner(&mut self, child: ParChild) { - if let ParChild::Text(curr_text, curr_align, curr_props, curr_decos) = &child { - if let Some(ParChild::Text(prev_text, prev_align, prev_props, prev_decos)) = + if let ParChild::Text(curr_text, curr_align, curr_props) = &child { + if let Some(ParChild::Text(prev_text, prev_align, prev_props)) = self.children.last_mut() { - if prev_align == curr_align - && Rc::ptr_eq(prev_props, curr_props) - && curr_decos == prev_decos - { + if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) { prev_text.push_str(&curr_text); return; } diff --git a/src/eval/walk.rs b/src/eval/walk.rs index 2f44f5df..6ac30150 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -113,7 +113,6 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) { (&label).into(), style.aligns.inline, Rc::clone(&style.text), - vec![], )], }; StackNode { diff --git a/src/layout/deco.rs b/src/layout/deco.rs new file mode 100644 index 00000000..669bf404 --- /dev/null +++ b/src/layout/deco.rs @@ -0,0 +1,92 @@ +use super::*; +use crate::util::EcoString; + +/// A decoration for a frame. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum Decoration { + /// A link to an external resource. + Link(EcoString), + /// An underline/strikethrough/overline decoration. + Line(LineDecoration), +} + +impl Decoration { + /// Apply a decoration to a child's frame. + pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { + match self { + Decoration::Link(href) => { + let link = Element::Link(href.to_string(), frame.size); + frame.push(Point::zero(), link); + } + Decoration::Line(line) => { + line.apply(ctx, frame); + } + } + } +} + +/// Defines a line that is positioned over, under or on top of text. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct LineDecoration { + /// The kind of line. + pub kind: LineKind, + /// Stroke color of the line, defaults to the text color if `None`. + pub stroke: Option<Paint>, + /// Thickness of the line's strokes (dependent on scaled font size), read + /// from the font tables if `None`. + pub thickness: Option<Linear>, + /// Position of the line relative to the baseline (dependent on scaled font + /// size), read from the font tables if `None`. + pub offset: Option<Linear>, + /// Amount that the line will be longer or shorter than its associated text + /// (dependent on scaled font size). + pub extent: Linear, +} + +/// The kind of line decoration. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum LineKind { + /// A line under text. + Underline, + /// A line through text. + Strikethrough, + /// A line over text. + Overline, +} + +impl LineDecoration { + /// Apply a line decoration to a all text elements in a frame. + pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { + for i in 0 .. frame.children.len() { + let (pos, child) = &frame.children[i]; + if let FrameChild::Element(Element::Text(text)) = child { + let face = ctx.fonts.get(text.face_id); + let metrics = match self.kind { + LineKind::Underline => face.underline, + LineKind::Strikethrough => face.strikethrough, + LineKind::Overline => face.overline, + }; + + let stroke = self.stroke.unwrap_or(text.fill); + + let thickness = self + .thickness + .map(|s| s.resolve(text.size)) + .unwrap_or(metrics.strength.to_length(text.size)); + + let offset = self + .offset + .map(|s| s.resolve(text.size)) + .unwrap_or(-metrics.position.to_length(text.size)); + + let extent = self.extent.resolve(text.size); + + let subpos = Point::new(pos.x - extent, pos.y + offset); + let vector = Point::new(text.width + 2.0 * extent, Length::zero()); + let line = Geometry::Line(vector, thickness); + + frame.push(subpos, Element::Geometry(line, stroke)); + } + } + } +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 5c5e2ffd..d0313778 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,6 +1,7 @@ //! Layouting. mod constraints; +mod deco; mod frame; mod grid; mod image; @@ -15,6 +16,7 @@ mod text; pub use self::image::*; pub use constraints::*; +pub use deco::*; pub use frame::*; pub use grid::*; #[cfg(feature = "layout-cache")] diff --git a/src/layout/par.rs b/src/layout/par.rs index 1c618fff..4a1e3743 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -29,9 +29,13 @@ pub enum ParChild { /// Spacing between other nodes. Spacing(Linear), /// A run of text and how to align it in its line. - Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>), + Text(EcoString, Align, Rc<TextStyle>), /// Any child node and how to align it in its line. - Any(InlineNode, Align, Vec<Decoration>), + Any(InlineNode, Align), + /// A decoration that applies until a matching `Undecorate`. + Decorate(Decoration), + /// The end of a decoration. + Undecorate, } impl BlockLevel for ParNode { @@ -84,6 +88,7 @@ impl ParNode { ParChild::Spacing(_) => " ", ParChild::Text(ref piece, ..) => piece, ParChild::Any(..) => "\u{FFFC}", + ParChild::Decorate(_) | ParChild::Undecorate => "", }) } } @@ -100,6 +105,8 @@ impl Debug for ParChild { Self::Spacing(v) => write!(f, "Spacing({:?})", v), Self::Text(text, ..) => write!(f, "Text({:?})", text), Self::Any(node, ..) => node.fmt(f), + Self::Decorate(deco) => write!(f, "Decorate({:?})", deco), + Self::Undecorate => write!(f, "Undecorate"), } } } @@ -117,6 +124,18 @@ struct ParLayouter<'a> { items: Vec<ParItem<'a>>, /// The ranges of the items in `bidi.text`. ranges: Vec<Range>, + /// The decorations and the ranges they span. + decos: Vec<(Range, &'a Decoration)>, +} + +/// A prepared item in a paragraph layout. +enum ParItem<'a> { + /// Spacing between other items. + Spacing(Length), + /// A shaped text run with consistent direction. + Text(ShapedText<'a>, Align), + /// A layouted child node. + Frame(Frame, Align), } impl<'a> ParLayouter<'a> { @@ -127,9 +146,10 @@ impl<'a> ParLayouter<'a> { regions: &Regions, bidi: BidiInfo<'a>, ) -> Self { - // Prepare an iterator over each child an the range it spans. let mut items = vec![]; let mut ranges = vec![]; + let mut starts = vec![]; + let mut decos = vec![]; // Layout the children and collect them into items. for (range, child) in par.ranges().zip(&par.children) { @@ -139,20 +159,27 @@ impl<'a> ParLayouter<'a> { items.push(ParItem::Spacing(resolved)); ranges.push(range); } - ParChild::Text(_, align, style, decos) => { + ParChild::Text(_, align, style) => { // TODO: Also split by language and script. for (subrange, dir) in split_runs(&bidi, range) { let text = &bidi.text[subrange.clone()]; let shaped = shape(ctx, text, style, dir); - items.push(ParItem::Text(shaped, *align, decos)); + items.push(ParItem::Text(shaped, *align)); ranges.push(subrange); } } - ParChild::Any(node, align, decos) => { + ParChild::Any(node, align) => { let frame = node.layout(ctx, regions.current.w, regions.base); - items.push(ParItem::Frame(frame, *align, decos)); + items.push(ParItem::Frame(frame, *align)); ranges.push(range); } + ParChild::Decorate(deco) => { + starts.push((range.start, deco)); + } + ParChild::Undecorate => { + let (start, deco) = starts.pop().unwrap(); + decos.push((start .. range.end, deco)); + } } } @@ -162,6 +189,7 @@ impl<'a> ParLayouter<'a> { bidi, items, ranges, + decos, } } @@ -285,16 +313,6 @@ fn split_runs<'a>( }) } -/// A prepared item in a paragraph layout. -enum ParItem<'a> { - /// Spacing between other items. - Spacing(Length), - /// A shaped text run with consistent direction. - Text(ShapedText<'a>, Align, &'a [Decoration]), - /// A layouted child node. - Frame(Frame, Align, &'a [Decoration]), -} - impl ParItem<'_> { /// The size of the item. pub fn size(&self) -> Size { @@ -319,10 +337,8 @@ impl ParItem<'_> { /// paragraph's text. This type enables you to cheaply measure the size of a /// line in a range before comitting to building the line's frame. struct LineLayout<'a> { - /// The direction of the line. - dir: Dir, /// Bidi information about the paragraph. - bidi: &'a BidiInfo<'a>, + par: &'a ParLayouter<'a>, /// The range the line spans in the paragraph. line: Range, /// A reshaped text item if the line sliced up a text item at the start. @@ -359,7 +375,7 @@ impl<'a> LineLayout<'a> { // Reshape the last item if it's split in half. let mut last = None; - if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_last() { + if let Some((ParItem::Text(shaped, align), rest)) = items.split_last() { // Compute the range we want to shape, trimming whitespace at the // end of the line. let base = par.ranges[last_idx].start; @@ -375,7 +391,7 @@ impl<'a> LineLayout<'a> { if !range.is_empty() || rest.is_empty() { // Reshape that part. let reshaped = shaped.reshape(ctx, range); - last = Some(ParItem::Text(reshaped, *align, *i)); + last = Some(ParItem::Text(reshaped, *align)); } items = rest; @@ -385,7 +401,7 @@ impl<'a> LineLayout<'a> { // Reshape the start item if it's split in half. let mut first = None; - if let Some((ParItem::Text(shaped, align, i), rest)) = items.split_first() { + if let Some((ParItem::Text(shaped, align), rest)) = items.split_first() { // Compute the range we want to shape. let Range { start: base, end: first_end } = par.ranges[first_idx]; let start = line.start; @@ -396,7 +412,7 @@ impl<'a> LineLayout<'a> { if range.len() < shaped.text.len() { if !range.is_empty() { let reshaped = shaped.reshape(ctx, range); - first = Some(ParItem::Text(reshaped, *align, *i)); + first = Some(ParItem::Text(reshaped, *align)); } items = rest; @@ -417,8 +433,7 @@ impl<'a> LineLayout<'a> { } Self { - dir: par.dir, - bidi: &par.bidi, + par, line, first, items, @@ -438,36 +453,29 @@ impl<'a> LineLayout<'a> { let mut offset = Length::zero(); let mut ruler = Align::Start; - for item in self.reordered() { - let mut position = |frame: &Frame, align| { + for (range, item) in self.reordered() { + let mut position = |mut frame: Frame, align: Align| { + // Decorate. + for (deco_range, deco) in &self.par.decos { + if deco_range.contains(&range.start) { + deco.apply(ctx, &mut frame); + } + } + // FIXME: Ruler alignment for RTL. ruler = ruler.max(align); - let x = ruler.resolve(self.dir, offset .. free + offset); + let x = ruler.resolve(self.par.dir, offset .. free + offset); let y = self.baseline - frame.baseline; offset += frame.size.w; - Point::new(x, y) + + // Add to the line's frame. + output.merge_frame(Point::new(x, y), frame); }; match *item { - ParItem::Spacing(amount) => { - offset += amount; - } - ParItem::Text(ref shaped, align, decos) => { - let mut frame = shaped.build(); - for deco in decos { - deco.apply(ctx, &mut frame); - } - let pos = position(&frame, align); - output.merge_frame(pos, frame); - } - ParItem::Frame(ref frame, align, decos) => { - let mut frame = frame.clone(); - for deco in decos { - deco.apply(ctx, &mut frame); - } - let pos = position(&frame, align); - output.merge_frame(pos, frame); - } + ParItem::Spacing(amount) => offset += amount, + ParItem::Text(ref shaped, align) => position(shaped.build(), align), + ParItem::Frame(ref frame, align) => position(frame.clone(), align), } } @@ -475,11 +483,12 @@ impl<'a> LineLayout<'a> { } /// Iterate through the line's items in visual order. - fn reordered(&self) -> impl Iterator<Item = &ParItem<'a>> { + fn reordered(&self) -> impl Iterator<Item = (Range, &ParItem<'a>)> { // The bidi crate doesn't like empty lines. let (levels, runs) = if !self.line.is_empty() { // Find the paragraph that contains the line. let para = self + .par .bidi .paragraphs .iter() @@ -487,7 +496,7 @@ impl<'a> LineLayout<'a> { .unwrap(); // Compute the reordered ranges in visual order (left to right). - self.bidi.visual_runs(para, self.line.clone()) + self.par.bidi.visual_runs(para, self.line.clone()) } else { <_>::default() }; @@ -506,7 +515,7 @@ impl<'a> LineLayout<'a> { Either::Right(range.rev()) } }) - .map(move |idx| self.get(idx).unwrap()) + .map(move |idx| (self.ranges[idx].clone(), self.get(idx).unwrap())) } /// Find the index of the item whose range contains the `text_offset`. @@ -604,96 +613,6 @@ impl<'a> LineStack<'a> { } } -/// A decoration for a paragraph child. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Decoration { - /// A link. - Link(EcoString), - /// An underline/strikethrough/overline decoration. - Line(LineDecoration), -} - -/// Defines a line that is positioned over, under or on top of text. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct LineDecoration { - /// The kind of line. - pub kind: LineKind, - /// Stroke color of the line, defaults to the text color if `None`. - pub stroke: Option<Paint>, - /// Thickness of the line's strokes (dependent on scaled font size), read - /// from the font tables if `None`. - pub thickness: Option<Linear>, - /// Position of the line relative to the baseline (dependent on scaled font - /// size), read from the font tables if `None`. - pub offset: Option<Linear>, - /// Amount that the line will be longer or shorter than its associated text - /// (dependent on scaled font size). - pub extent: Linear, -} - -/// The kind of line decoration. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum LineKind { - /// A line under text. - Underline, - /// A line through text. - Strikethrough, - /// A line over text. - Overline, -} - -impl Decoration { - /// Apply a decoration to a child's frame. - pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { - match self { - Decoration::Link(href) => { - let link = Element::Link(href.to_string(), frame.size); - frame.push(Point::zero(), link); - } - Decoration::Line(line) => { - line.apply(ctx, frame); - } - } - } -} - -impl LineDecoration { - /// Apply a line decoration to a all text elements in a frame. - pub fn apply(&self, ctx: &LayoutContext, frame: &mut Frame) { - for i in 0 .. frame.children.len() { - let (pos, child) = &frame.children[i]; - if let FrameChild::Element(Element::Text(text)) = child { - let face = ctx.fonts.get(text.face_id); - let metrics = match self.kind { - LineKind::Underline => face.underline, - LineKind::Strikethrough => face.strikethrough, - LineKind::Overline => face.overline, - }; - - let stroke = self.stroke.unwrap_or(text.fill); - - let thickness = self - .thickness - .map(|s| s.resolve(text.size)) - .unwrap_or(metrics.strength.to_length(text.size)); - - let offset = self - .offset - .map(|s| s.resolve(text.size)) - .unwrap_or(-metrics.position.to_length(text.size)); - - let extent = self.extent.resolve(text.size); - - let subpos = Point::new(pos.x - extent, pos.y + offset); - let vector = Point::new(text.width + 2.0 * extent, Length::zero()); - let line = Geometry::Line(vector, thickness); - - frame.push(subpos, Element::Geometry(line, stroke)); - } - } - } -} - /// Additional methods for BiDi levels. trait LevelExt: Sized { fn from_dir(dir: Dir) -> Option<Self>; diff --git a/src/library/text.rs b/src/library/text.rs index 3c7055d3..8c5b2547 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -181,30 +181,27 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> { let thickness = args.named::<Linear>("thickness")?.or_else(|| args.eat()); let offset = args.named("offset")?; let extent = args.named("extent")?.unwrap_or_default(); - - let mut body: Template = args.expect("body")?; - body.decorate(Decoration::Line(LineDecoration { - kind, - stroke: stroke.map(Paint::Color), - thickness, - offset, - extent, - })); - - Ok(Value::Template(body)) + let body: Template = args.expect("body")?; + + Ok(Value::Template(body.decorate(Decoration::Line( + LineDecoration { + kind, + stroke: stroke.map(Paint::Color), + thickness, + offset, + extent, + }, + )))) } /// `link`: Typeset text as a link. pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let url = args.expect::<Str>("url")?; - - let mut body = args.eat().unwrap_or_else(|| { + let body = args.eat().unwrap_or_else(|| { let mut template = Template::new(); template.text(&url); template }); - body.decorate(Decoration::Link(url.into())); - - Ok(Value::Template(body)) + Ok(Value::Template(body.decorate(Decoration::Link(url.into())))) } |
