Document underline, strikethrough, and overline
This commit is contained in:
parent
08daade59f
commit
38a0404050
@ -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 {
|
||||
|
@ -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<const L: DecoLine>(pub Content);
|
||||
|
||||
/// Typeset underlined text.
|
||||
pub type UnderlineNode = DecoNode<UNDERLINE>;
|
||||
|
||||
/// Typeset stricken-through text.
|
||||
pub type StrikeNode = DecoNode<STRIKETHROUGH>;
|
||||
|
||||
/// Typeset overlined text.
|
||||
pub type OverlineNode = DecoNode<OVERLINE>;
|
||||
pub struct UnderlineNode(pub Content);
|
||||
|
||||
#[node]
|
||||
impl<const L: DecoLine> DecoNode<L> {
|
||||
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<PartialStroke> = 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<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. 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<Content> {
|
||||
@ -56,12 +84,12 @@ impl<const L: DecoLine> DecoNode<L> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<const L: DecoLine> Show for DecoNode<L> {
|
||||
impl Show for UnderlineNode {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||
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<const L: DecoLine> Show for DecoNode<L> {
|
||||
}
|
||||
}
|
||||
|
||||
/// # 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<PartialStroke> = 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<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.
|
||||
///
|
||||
/// # 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> {
|
||||
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 OverlineNode {
|
||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||
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<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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
||||
---
|
||||
|
Loading…
x
Reference in New Issue
Block a user