diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-08-24 00:39:43 +0200 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-08-24 00:41:15 +0200 |
| commit | 148a06c070e6376e6f86b878d08dfd4f0aef8a73 (patch) | |
| tree | 7330ecae5fa3dbb79c3bb3ce6a099205ec92d2c9 /src/layout/par.rs | |
| parent | d546453880721d7a12ea228e5c1ed6c65b653ca2 (diff) | |
Switch from state to decorations for underline/strikethrough/overline
Diffstat (limited to 'src/layout/par.rs')
| -rw-r--r-- | src/layout/par.rs | 113 |
1 files changed, 98 insertions, 15 deletions
diff --git a/src/layout/par.rs b/src/layout/par.rs index e92e5a18..d7fbde16 100644 --- a/src/layout/par.rs +++ b/src/layout/par.rs @@ -4,7 +4,7 @@ use unicode_bidi::{BidiInfo, Level}; use xi_unicode::LineBreakIterator; use super::*; -use crate::eval::{Decoration, FontState}; +use crate::eval::FontState; use crate::util::{EcoString, RangeExt, SliceExt}; type Range = std::ops::Range<usize>; @@ -368,7 +368,7 @@ impl<'a> LineStack<'a> { let mut first = true; for line in self.lines.drain(..) { - let frame = line.build(ctx, self.size.w); + let frame = line.build(self.size.w); let pos = Point::new(Length::zero(), offset); if first { @@ -380,21 +380,14 @@ impl<'a> LineStack<'a> { output.merge_frame(pos, frame); } - // For each frame, we look if any decorations apply. - for i in 0 .. output.children.len() { - let &(point, ref child) = &output.children[i]; - if let &FrameChild::Frame(Some(frame_idx), ref frame) = child { - let size = frame.size; - for deco in match &self.children[frame_idx] { + for (_, child) in &mut output.children { + if let FrameChild::Frame(Some(frame_idx), frame) = child { + for deco in match &self.children[*frame_idx] { ParChild::Spacing(_) => continue, ParChild::Text(.., decos) => decos, ParChild::Any(.., decos) => decos, } { - match deco { - Decoration::Link(href) => { - output.push(point, Element::Link(href.to_string(), size)); - } - } + deco.apply(ctx, Rc::make_mut(frame)); } } } @@ -528,7 +521,7 @@ impl<'a> LineLayout<'a> { } /// Build the line's frame. - fn build(&self, ctx: &LayoutContext, width: Length) -> Frame { + fn build(&self, width: Length) -> Frame { let size = Size::new(self.size.w.max(width), self.size.h); let free = size.w - self.size.w; @@ -544,7 +537,7 @@ impl<'a> LineLayout<'a> { } ParItem::Text(ref shaped, align, _) => { ruler = ruler.max(align); - Rc::new(shaped.build(ctx)) + Rc::new(shaped.build()) } ParItem::Frame(ref frame, align, _) => { ruler = ruler.max(align); @@ -618,6 +611,96 @@ impl<'a> LineLayout<'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>; |
