From 38a0404050b1f6725db1bfd357a41539ef4d9973 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Tue, 20 Dec 2022 18:19:01 +0100 Subject: [PATCH] Document underline, strikethrough, and overline --- library/src/shared/ext.rs | 2 +- library/src/text/deco.rs | 277 +++++++++++++++++++++++++++---- tests/typ/basics/heading.typ | 4 +- tests/typ/compiler/show-node.typ | 6 +- tests/typ/compute/calc.typ | 2 +- 5 files changed, 249 insertions(+), 42 deletions(-) diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs index ba3579e7e..bda4b6713 100644 --- a/library/src/shared/ext.rs +++ b/library/src/shared/ext.rs @@ -45,7 +45,7 @@ impl ContentExt for Content { } fn underlined(self) -> Self { - crate::text::DecoNode::<{ crate::text::UNDERLINE }>(self).pack() + crate::text::UnderlineNode(self).pack() } fn linked(self, dest: Destination) -> Self { diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index 81157bfb7..f74a45a21 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -5,43 +5,71 @@ use super::TextNode; use crate::prelude::*; /// # Underline -/// Typeset underline, stricken-through or overlined text. +/// Underline text. +/// +/// ## Example +/// ``` +/// This is #underline[important]. +/// ``` /// /// ## Parameters /// - body: Content (positional, required) -/// The content to decorate. +/// The content to underline. /// /// ## Category /// text #[func] #[capable(Show)] #[derive(Debug, Hash)] -pub struct DecoNode(pub Content); - -/// Typeset underlined text. -pub type UnderlineNode = DecoNode; - -/// Typeset stricken-through text. -pub type StrikeNode = DecoNode; - -/// Typeset overlined text. -pub type OverlineNode = DecoNode; +pub struct UnderlineNode(pub Content); #[node] -impl DecoNode { +impl UnderlineNode { /// How to stroke the line. The text color and thickness are read from the - /// font tables if `auto`. + /// font tables if `{auto}`. + /// + /// # Example + /// ``` + /// Take #underline( + /// stroke: 1.5pt + red, + /// offset: 2pt, + /// [care], + /// ) + /// ``` #[property(shorthand, resolve, fold)] pub const STROKE: Smart = Smart::Auto; + /// Position of the line relative to the baseline, read from the font tables - /// if `auto`. + /// if `{auto}`. + /// + /// # Example + /// ``` + /// #underline(offset: 5pt)[ + /// The Tale Of A Faraway Line I + /// ] + /// ``` #[property(resolve)] pub const OFFSET: Smart = Smart::Auto; + /// Amount that the line will be longer or shorter than its associated text. + /// + /// # Example + /// ``` + /// #align(center, + /// underline(extent: 2pt)[Chapter 1] + /// ) + /// ``` #[property(resolve)] pub const EXTENT: Length = Length::zero(); - /// Whether the line skips sections in which it would collide - /// with the glyphs. Does not apply to strikethrough. + + /// Whether the line skips sections in which it would collide with the + /// glyphs. + /// + /// # Example + /// ``` + /// This #underline(evade: true)[is great]. + /// This #underline(evade: false)[is less great]. + /// ``` pub const EVADE: bool = true; fn construct(_: &Vm, args: &mut Args) -> SourceResult { @@ -56,12 +84,12 @@ impl DecoNode { } } -impl Show for DecoNode { +impl Show for UnderlineNode { fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { Ok(self.0.clone().styled( TextNode::DECO, Decoration { - line: L, + line: DecoLine::Underline, stroke: styles.get(Self::STROKE).unwrap_or_default(), offset: styles.get(Self::OFFSET), extent: styles.get(Self::EXTENT), @@ -71,6 +99,190 @@ impl Show for DecoNode { } } +/// # Overline +/// Add a line over text. +/// +/// ## Example +/// ``` +/// #overline[A line over text.] +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content to add a line over. +/// +/// ## Category +/// text +#[func] +#[capable(Show)] +#[derive(Debug, Hash)] +pub struct OverlineNode(pub Content); + +#[node] +impl OverlineNode { + /// How to stroke the line. The text color and thickness are read from the + /// font tables if `{auto}`. + /// + /// # Example + /// ``` + /// #set text(fill: olive) + /// #overline( + /// stroke: green.darken(20%), + /// offset: -12pt, + /// [The Forest Theme], + /// ) + /// ``` + #[property(shorthand, resolve, fold)] + pub const STROKE: Smart = Smart::Auto; + + /// Position of the line relative to the baseline, read from the font tables + /// if `{auto}`. + /// + /// # Example + /// ``` + /// #overline(offset: -1.2em)[ + /// The Tale Of A Faraway Line II + /// ] + /// ``` + #[property(resolve)] + pub const OFFSET: Smart = Smart::Auto; + + /// Amount that the line will be longer or shorter than its associated text. + /// + /// # Example + /// ``` + /// #set overline(extent: 4pt) + /// #set underline(extent: 4pt) + /// #overline(underline[Typography Today]) + /// ``` + #[property(resolve)] + pub const EXTENT: Length = Length::zero(); + + /// Whether the line skips sections in which it would collide with the + /// glyphs. + /// + /// # Example + /// ``` + /// #overline( + /// evade: false, + /// offset: -7.5pt, + /// stroke: 1pt, + /// extent: 3pt, + /// [Temple], + /// ) + /// ``` + pub const EVADE: bool = true; + + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } +} + +impl Show for OverlineNode { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { + Ok(self.0.clone().styled( + TextNode::DECO, + Decoration { + line: DecoLine::Overline, + stroke: styles.get(Self::STROKE).unwrap_or_default(), + offset: styles.get(Self::OFFSET), + extent: styles.get(Self::EXTENT), + evade: styles.get(Self::EVADE), + }, + )) + } +} + +/// # Strikethrough +/// Strike through text. +/// +/// ## Example +/// ``` +/// This is #strike[not] relevant. +/// ``` +/// +/// ## Parameters +/// - body: Content (positional, required) +/// The content to strike through. +/// +/// ## Category +/// text +#[func] +#[capable(Show)] +#[derive(Debug, Hash)] +pub struct StrikeNode(pub Content); + +#[node] +impl StrikeNode { + /// How to stroke the line. The text color and thickness are read from the + /// font tables if `{auto}`. + /// + /// # Example + /// ``` + /// This is #strike(stroke: 1.5pt + red)[very stricken through]. \ + /// This is #strike(stroke: 10pt)[redacted]. + /// ``` + #[property(shorthand, resolve, fold)] + pub const STROKE: Smart = Smart::Auto; + + /// Position of the line relative to the baseline, read from the font tables + /// if `{auto}`. + /// + /// This is useful if you are unhappy with the offset your font provides. + /// + /// # Example + /// ``` + /// #set text(family: "Inria Serif") + /// This is #strike(offset: auto)[low-ish]. \ + /// This is #strike(offset: -3.5pt)[on-top]. + /// ``` + #[property(resolve)] + pub const OFFSET: Smart = Smart::Auto; + + /// Amount that the line will be longer or shorter than its associated text. + /// + /// # Example + /// ``` + /// This #strike(extent: -2pt)[skips] parts of the word. + /// This #strike(extent: 2pt)[extends] beyond the word. + /// ``` + #[property(resolve)] + pub const EXTENT: Length = Length::zero(); + + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self(args.expect("body")?).pack()) + } + + fn field(&self, name: &str) -> Option { + match name { + "body" => Some(Value::Content(self.0.clone())), + _ => None, + } + } +} + +impl Show for StrikeNode { + fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult { + Ok(self.0.clone().styled( + TextNode::DECO, + Decoration { + line: DecoLine::Strikethrough, + stroke: styles.get(Self::STROKE).unwrap_or_default(), + offset: styles.get(Self::OFFSET), + extent: styles.get(Self::EXTENT), + evade: false, + }, + )) + } +} + /// Defines a line that is positioned over, under or on top of text. /// /// For more details, see [`DecoNode`]. @@ -93,16 +305,12 @@ impl Fold for Decoration { } /// A kind of decorative line. -pub type DecoLine = usize; - -/// A line under text. -pub const UNDERLINE: DecoLine = 0; - -/// A line through text. -pub const STRIKETHROUGH: DecoLine = 1; - -/// A line over text. -pub const OVERLINE: DecoLine = 2; +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum DecoLine { + Underline, + Strikethrough, + Overline, +} /// Add line decorations to a single run of shaped text. pub(super) fn decorate( @@ -115,12 +323,11 @@ pub(super) fn decorate( ) { let font_metrics = text.font.metrics(); let metrics = match deco.line { - STRIKETHROUGH => font_metrics.strikethrough, - OVERLINE => font_metrics.overline, - UNDERLINE | _ => font_metrics.underline, + DecoLine::Strikethrough => font_metrics.strikethrough, + DecoLine::Overline => font_metrics.overline, + DecoLine::Underline => font_metrics.underline, }; - let evade = deco.evade && deco.line != STRIKETHROUGH; let offset = deco.offset.unwrap_or(-metrics.position.at(text.size)) - shift; let stroke = deco.stroke.unwrap_or(Stroke { paint: text.fill, @@ -137,13 +344,13 @@ pub(super) fn decorate( let origin = Point::new(from, pos.y + offset); let target = Point::new(to - from, Abs::zero()); - if target.x >= min_width || !evade { + if target.x >= min_width || !deco.evade { let shape = Geometry::Line(target).stroked(stroke); frame.push(origin, Element::Shape(shape)); } }; - if !evade { + if !deco.evade { push_segment(start, end); return; } diff --git a/tests/typ/basics/heading.typ b/tests/typ/basics/heading.typ index 9fd4e6482..4e9550b97 100644 --- a/tests/typ/basics/heading.typ +++ b/tests/typ/basics/heading.typ @@ -1,7 +1,7 @@ // Test headings. --- -#show heading: it => text(blue, it.body) +#show heading: it => text(blue, it.title) = No heading @@ -44,7 +44,7 @@ multiline. --- // Test styling. #show heading.where(level: 5): it => block( - text(family: "Roboto", fill: eastern, it.body + [!]) + text(family: "Roboto", fill: eastern, it.title + [!]) ) = Heading diff --git a/tests/typ/compiler/show-node.typ b/tests/typ/compiler/show-node.typ index f14fb002f..5dd7ad984 100644 --- a/tests/typ/compiler/show-node.typ +++ b/tests/typ/compiler/show-node.typ @@ -31,9 +31,9 @@ my heading? move(dy: -1pt)[📖] h(5pt) if it.level == 1 { - underline(text(1.25em, blue, it.body)) + underline(text(1.25em, blue, it.title)) } else { - text(red, it.body) + text(red, it.title) } }) @@ -51,7 +51,7 @@ Another text. #show heading: it => { set text(red) show "ding": [🛎] - it.body + it.title } = Heading diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ index bc6ef7f64..be207a05d 100644 --- a/tests/typ/compute/calc.typ +++ b/tests/typ/compute/calc.typ @@ -39,7 +39,7 @@ #test(abs(-25%), 25%) --- -// Error: 6-17 expected integer, float, angle, ratio, or fraction, found string +// Error: 6-17 expected integer, float, length, angle, ratio, or fraction, found string #abs("no number") ---