summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-20 18:19:01 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-20 18:20:19 +0100
commit38a0404050b1f6725db1bfd357a41539ef4d9973 (patch)
tree068c57372d255900ecb727ced593143d49cf2b8f /library
parent08daade59f3e0005916881fddd1a774a86278fbb (diff)
Document underline, strikethrough, and overline
Diffstat (limited to 'library')
-rw-r--r--library/src/shared/ext.rs2
-rw-r--r--library/src/text/deco.rs271
2 files changed, 240 insertions, 33 deletions
diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs
index ba3579e7..bda4b671 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 81157bfb..f74a45a2 100644
--- a/library/src/text/deco.rs
+++ b/library/src/text/deco.rs
@@ -5,43 +5,172 @@ 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<const L: DecoLine>(pub Content);
+pub struct UnderlineNode(pub Content);
+
+#[node]
+impl UnderlineNode {
+ /// How to stroke the line. The text color and thickness are read from the
+ /// font tables if `{auto}`.
+ ///
+ /// # Example
+ /// ```
+ /// Take #underline(
+ /// stroke: 1.5pt + red,
+ /// offset: 2pt,
+ /// [care],
+ /// )
+ /// ```
+ #[property(shorthand, resolve, fold)]
+ pub const STROKE: Smart<PartialStroke> = Smart::Auto;
+
+ /// Position of the line relative to the baseline, read from the font tables
+ /// if `{auto}`.
+ ///
+ /// # Example
+ /// ```
+ /// #underline(offset: 5pt)[
+ /// The Tale Of A Faraway Line I
+ /// ]
+ /// ```
+ #[property(resolve)]
+ pub const OFFSET: Smart<Length> = 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.
+ ///
+ /// # 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<Content> {
+ Ok(Self(args.expect("body")?).pack())
+ }
-/// Typeset underlined text.
-pub type UnderlineNode = DecoNode<UNDERLINE>;
+ fn field(&self, name: &str) -> Option<Value> {
+ match name {
+ "body" => Some(Value::Content(self.0.clone())),
+ _ => None,
+ }
+ }
+}
-/// Typeset stricken-through text.
-pub type StrikeNode = DecoNode<STRIKETHROUGH>;
+impl Show for UnderlineNode {
+ fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
+ Ok(self.0.clone().styled(
+ TextNode::DECO,
+ Decoration {
+ line: DecoLine::Underline,
+ stroke: styles.get(Self::STROKE).unwrap_or_default(),
+ offset: styles.get(Self::OFFSET),
+ extent: styles.get(Self::EXTENT),
+ evade: styles.get(Self::EVADE),
+ },
+ ))
+ }
+}
-/// Typeset overlined text.
-pub type OverlineNode = DecoNode<OVERLINE>;
+/// # 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<const L: DecoLine> DecoNode<L> {
+impl OverlineNode {
/// How to stroke the line. The text color and thickness are read from the
- /// font tables if `auto`.
+ /// 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<PartialStroke> = Smart::Auto;
+
/// Position of the line relative to the baseline, read from the font tables
- /// if `auto`.
+ /// if `{auto}`.
+ ///
+ /// # Example
+ /// ```
+ /// #overline(offset: -1.2em)[
+ /// The Tale Of A Faraway Line II
+ /// ]
+ /// ```
#[property(resolve)]
pub const OFFSET: Smart<Length> = 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. Does not apply to strikethrough.
+
+ /// 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<Content> {
@@ -56,12 +185,12 @@ impl<const L: DecoLine> DecoNode<L> {
}
}
-impl<const L: DecoLine> Show for DecoNode<L> {
+impl Show for OverlineNode {
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
Ok(self.0.clone().styled(
TextNode::DECO,
Decoration {
- line: L,
+ line: DecoLine::Overline,
stroke: styles.get(Self::STROKE).unwrap_or_default(),
offset: styles.get(Self::OFFSET),
extent: styles.get(Self::EXTENT),
@@ -71,6 +200,89 @@ impl<const L: DecoLine> Show for DecoNode<L> {
}
}
+/// # 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<PartialStroke> = 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<Length> = 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<Content> {
+ Ok(Self(args.expect("body")?).pack())
+ }
+
+ fn field(&self, name: &str) -> Option<Value> {
+ match name {
+ "body" => Some(Value::Content(self.0.clone())),
+ _ => None,
+ }
+ }
+}
+
+impl Show for StrikeNode {
+ fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
+ 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;
}