From 148a06c070e6376e6f86b878d08dfd4f0aef8a73 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 24 Aug 2021 00:39:43 +0200 Subject: [PATCH] Switch from state to decorations for underline/strikethrough/overline --- src/eval/state.rs | 25 -------- src/eval/template.rs | 12 +--- src/export/pdf.rs | 30 ++++----- src/font.rs | 1 + src/layout/frame.rs | 2 + src/layout/par.rs | 113 ++++++++++++++++++++++++++++----- src/layout/shaping.rs | 60 ++--------------- src/library/text.rs | 37 ++++------- tests/ref/text/decorations.png | Bin 10821 -> 9340 bytes tests/typ/text/decorations.typ | 3 - 10 files changed, 139 insertions(+), 144 deletions(-) diff --git a/src/eval/state.rs b/src/eval/state.rs index 05558915..d7ca014d 100644 --- a/src/eval/state.rs +++ b/src/eval/state.rs @@ -140,12 +140,6 @@ pub struct FontState { /// A list of font families with generic class definitions (the final /// family list also depends on `monospace`). pub families: Rc, - /// The specifications for a strikethrough line, if any. - pub strikethrough: Option>, - /// The specifications for a underline, if any. - pub underline: Option>, - /// The specifications for a overline line, if any. - pub overline: Option>, } impl FontState { @@ -212,9 +206,6 @@ impl Default for FontState { top_edge: VerticalFontMetric::CapHeight, bottom_edge: VerticalFontMetric::Baseline, fill: Paint::Color(Color::Rgba(RgbaColor::BLACK)), - strikethrough: None, - underline: None, - overline: None, } } } @@ -248,19 +239,3 @@ impl Default for FamilyState { } } } - -/// Defines a line that is positioned over, under or on top of text. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct LineState { - /// 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, -} diff --git a/src/eval/template.rs b/src/eval/template.rs index 0ab49d04..4fc6985a 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -8,7 +8,8 @@ use super::{State, Str}; use crate::diag::StrResult; use crate::geom::{Align, Dir, Gen, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{ - LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode, + Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, + StackNode, }; use crate::util::EcoString; @@ -43,13 +44,6 @@ enum TemplateNode { Modify(Rc), } -/// A template node decoration. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Decoration { - /// A link. - Link(EcoString), -} - impl Template { /// Create a new, empty template. pub fn new() -> Self { @@ -114,7 +108,7 @@ impl Template { self.make_mut().push(TemplateNode::Spacing(axis, spacing)); } - /// Add a decoration to the last template node. + /// Add a decoration to all contained nodes. pub fn decorate(&mut self, deco: Decoration) { for node in self.make_mut() { let decos = match node { diff --git a/src/export/pdf.rs b/src/export/pdf.rs index d613efc3..ee2b026c 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -50,7 +50,7 @@ impl<'a> PdfExporter<'a> { for frame in frames { for (_, element) in frame.elements() { match *element { - Element::Text(ref shaped) => font_map.insert(shaped.face_id), + Element::Text(ref text) => font_map.insert(text.face_id), Element::Geometry(_, _) => {} Element::Image(id, _) => { let img = ctx.images.get(id); @@ -168,35 +168,35 @@ impl<'a> PdfExporter<'a> { let y = (page.size.h - pos.y).to_pt() as f32; match *element { - Element::Text(ref shaped) => { - if fill != Some(shaped.fill) { - write_fill(&mut content, shaped.fill); - fill = Some(shaped.fill); + Element::Text(ref text) => { + if fill != Some(text.fill) { + write_fill(&mut content, text.fill); + fill = Some(text.fill); } - let mut text = content.text(); + let mut text_writer = content.text(); // Then, also check if we need to issue a font switching // action. - if face_id != Some(shaped.face_id) || shaped.size != size { - face_id = Some(shaped.face_id); - size = shaped.size; + if face_id != Some(text.face_id) || text.size != size { + face_id = Some(text.face_id); + size = text.size; - let name = format!("F{}", self.font_map.map(shaped.face_id)); - text.font(Name(name.as_bytes()), size.to_pt() as f32); + let name = format!("F{}", self.font_map.map(text.face_id)); + text_writer.font(Name(name.as_bytes()), size.to_pt() as f32); } - let face = self.fonts.get(shaped.face_id); + let face = self.fonts.get(text.face_id); // Position the text. - text.matrix(1.0, 0.0, 0.0, 1.0, x, y); + text_writer.matrix(1.0, 0.0, 0.0, 1.0, x, y); - let mut positioned = text.show_positioned(); + let mut positioned = text_writer.show_positioned(); let mut adjustment = Em::zero(); let mut encoded = vec![]; // Write the glyphs with kerning adjustments. - for glyph in &shaped.glyphs { + for glyph in &text.glyphs { adjustment += glyph.x_offset; if !adjustment.is_zero() { diff --git a/src/font.rs b/src/font.rs index 690884f4..633a1a0e 100644 --- a/src/font.rs +++ b/src/font.rs @@ -163,6 +163,7 @@ pub struct Face { } /// Metrics for a decorative line. +#[derive(Debug, Copy, Clone)] pub struct LineMetrics { pub strength: Em, pub position: Em, 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); - } -} diff --git a/src/library/text.rs b/src/library/text.rs index cfd2de99..f3e086c8 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -1,5 +1,4 @@ -use crate::eval::{Decoration, FontState, LineState}; -use crate::layout::Paint; +use crate::layout::{Decoration, LineDecoration, LineKind, Paint}; use super::*; @@ -155,47 +154,39 @@ fn lang_dir(iso: &str) -> Dir { /// `strike`: Set striken-through text. pub fn strike(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult { - line_impl(ctx, args, |font| &mut font.strikethrough) + line_impl(ctx, args, LineKind::Strikethrough) } /// `underline`: Set underlined text. pub fn underline(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult { - line_impl(ctx, args, |font| &mut font.underline) + line_impl(ctx, args, LineKind::Underline) } /// `overline`: Set text with an overline. pub fn overline(ctx: &mut EvalContext, args: &mut Arguments) -> TypResult { - line_impl(ctx, args, |font| &mut font.overline) + line_impl(ctx, args, LineKind::Overline) } fn line_impl( _: &mut EvalContext, args: &mut Arguments, - substate: fn(&mut FontState) -> &mut Option>, + kind: LineKind, ) -> TypResult { let stroke = args.named("stroke")?.or_else(|| args.eat()); let thickness = args.named::("thickness")?.or_else(|| args.eat()); let offset = args.named("offset")?; let extent = args.named("extent")?.unwrap_or_default(); - let body = args.expect("body")?; - // Suppress any existing strikethrough if strength is explicitly zero. - let line = thickness.map_or(true, |s| !s.is_zero()).then(|| { - Rc::new(LineState { - stroke: stroke.map(Paint::Color), - thickness, - offset, - extent, - }) - }); + let mut body: Template = args.expect("body")?; + body.decorate(Decoration::Line(LineDecoration { + kind, + stroke: stroke.map(Paint::Color), + thickness, + offset, + extent, + })); - let mut template = Template::new(); - template.save(); - template.modify(move |state| *substate(state.font_mut()) = line.clone()); - template += body; - template.restore(); - - Ok(Value::Template(template)) + Ok(Value::Template(body)) } /// `link`: Set a link. diff --git a/tests/ref/text/decorations.png b/tests/ref/text/decorations.png index 5ae569f39ee732111e8557b342d324ff92d78772..b1e3171b937d3e5dce82ca21559ed1a01efbdd36 100644 GIT binary patch delta 9133 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 delta 10527 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 diff --git a/tests/typ/text/decorations.typ b/tests/typ/text/decorations.typ index bbda3de6..3f953b07 100644 --- a/tests/typ/text/decorations.typ +++ b/tests/typ/text/decorations.typ @@ -18,9 +18,6 @@ // Both over- and underline. #overline(underline[Running amongst the wolves.]) -// Disable underline by setting it back to 0pt. -#underline[Still important, but not #underline(0pt)[mission ]critical.] - --- #let redact = strike with (10pt, extent: 5%) #let highlight = strike with (