From 148a06c070e6376e6f86b878d08dfd4f0aef8a73 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 24 Aug 2021 00:39:43 +0200 Subject: Switch from state to decorations for underline/strikethrough/overline --- src/layout/frame.rs | 2 + src/layout/par.rs | 113 +++++++++++++++++++++++++++++++++++++++++++------- src/layout/shaping.rs | 60 +++------------------------ 3 files changed, 106 insertions(+), 69 deletions(-) (limited to 'src/layout') diff --git a/src/layout/frame.rs b/src/layout/frame.rs index 15ef541b..2c8ba3d8 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -131,6 +131,8 @@ pub struct Text { pub face_id: FaceId, /// The font size. pub size: Length, + /// The width of the text run. + pub width: Length, /// Glyph color. pub fill: Paint, /// The glyphs. 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; @@ -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, + /// Thickness of the line's strokes (dependent on scaled font size), read + /// from the font tables if `None`. + pub thickness: Option, + /// Position of the line relative to the baseline (dependent on scaled font + /// size), read from the font tables if `None`. + pub offset: Option, + /// 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; diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index efee1591..eebeacbf 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -4,10 +4,9 @@ use std::ops::Range; use rustybuzz::UnicodeBuffer; use super::{Element, Frame, Glyph, LayoutContext, Text}; -use crate::eval::{FontState, LineState}; -use crate::font::{Face, FaceId, FontVariant, LineMetrics}; +use crate::eval::FontState; +use crate::font::{Face, FaceId, FontVariant}; use crate::geom::{Dir, Em, Length, Point, Size}; -use crate::layout::Geometry; use crate::util::SliceExt; /// Shape text into [`ShapedText`]. @@ -85,7 +84,7 @@ pub struct ShapedGlyph { impl<'a> ShapedText<'a> { /// Build the shaped text's frame. - pub fn build(&self, ctx: &LayoutContext) -> Frame { + pub fn build(&self) -> Frame { let mut frame = Frame::new(self.size, self.baseline); let mut offset = Length::zero(); @@ -95,24 +94,22 @@ impl<'a> ShapedText<'a> { let mut text = Text { face_id, size: self.state.size, + width: Length::zero(), fill: self.state.fill, glyphs: vec![], }; - let mut width = Length::zero(); for glyph in group { text.glyphs.push(Glyph { id: glyph.glyph_id, x_advance: glyph.x_advance, x_offset: glyph.x_offset, }); - width += glyph.x_advance.to_length(text.size); + text.width += glyph.x_advance.to_length(text.size); } + offset += text.width; frame.push(pos, Element::Text(text)); - decorate(ctx, &mut frame, pos, width, face_id, &self.state); - - offset += width; } frame @@ -371,48 +368,3 @@ fn measure( (Size::new(width, top + bottom), top) } - -/// Add underline, strikthrough and overline decorations. -fn decorate( - ctx: &LayoutContext, - frame: &mut Frame, - pos: Point, - width: Length, - face_id: FaceId, - state: &FontState, -) { - let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| { - let metrics = metrics(ctx.fonts.get(face_id)); - - let stroke = substate.stroke.unwrap_or(state.fill); - - let thickness = substate - .thickness - .map(|s| s.resolve(state.size)) - .unwrap_or(metrics.strength.to_length(state.size)); - - let offset = substate - .offset - .map(|s| s.resolve(state.size)) - .unwrap_or(-metrics.position.to_length(state.size)); - - let extent = substate.extent.resolve(state.size); - - let pos = Point::new(pos.x - extent, pos.y + offset); - let target = Point::new(width + 2.0 * extent, Length::zero()); - let element = Element::Geometry(Geometry::Line(target, thickness), stroke); - frame.push(pos, element); - }; - - if let Some(strikethrough) = &state.strikethrough { - apply(strikethrough, |face| &face.strikethrough); - } - - if let Some(underline) = &state.underline { - apply(underline, |face| &face.underline); - } - - if let Some(overline) = &state.overline { - apply(overline, |face| &face.overline); - } -} -- cgit v1.2.3