diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-10-27 19:04:55 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-27 18:04:55 +0000 |
| commit | be7cfc85d08c545abfac08098b7b33b4bd71f37e (patch) | |
| tree | f4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-library/src/text/deco.rs | |
| parent | b8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff) | |
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-library/src/text/deco.rs')
| -rw-r--r-- | crates/typst-library/src/text/deco.rs | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs new file mode 100644 index 00000000..5da7ecec --- /dev/null +++ b/crates/typst-library/src/text/deco.rs @@ -0,0 +1,401 @@ +use smallvec::smallvec; + +use crate::diag::SourceResult; +use crate::engine::Engine; +use crate::foundations::{elem, Content, Packed, Show, Smart, StyleChain}; +use crate::layout::{Abs, Corners, Length, Rel, Sides}; +use crate::text::{BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric}; +use crate::visualize::{Color, FixedStroke, Paint, Stroke}; + +/// Underlines text. +/// +/// # Example +/// ```example +/// This is #underline[important]. +/// ``` +#[elem(Show)] +pub struct UnderlineElem { + /// How to [stroke] the line. + /// + /// If set to `{auto}`, takes on the text's color and a thickness defined in + /// the current font. + /// + /// ```example + /// Take #underline( + /// stroke: 1.5pt + red, + /// offset: 2pt, + /// [care], + /// ) + /// ``` + #[resolve] + #[fold] + pub stroke: Smart<Stroke>, + + /// The 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 + /// ] + /// ``` + #[resolve] + pub offset: Smart<Length>, + + /// The amount by which to extend the line beyond (or within if negative) + /// the content. + /// + /// ```example + /// #align(center, + /// underline(extent: 2pt)[Chapter 1] + /// ) + /// ``` + #[resolve] + pub extent: Length, + + /// 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]. + /// ``` + #[default(true)] + pub evade: bool, + + /// Whether the line is placed behind the content it underlines. + /// + /// ```example + /// #set underline(stroke: (thickness: 1em, paint: maroon, cap: "round")) + /// #underline(background: true)[This is stylized.] \ + /// #underline(background: false)[This is partially hidden.] + /// ``` + #[default(false)] + pub background: bool, + + /// The content to underline. + #[required] + pub body: Content, +} + +impl Show for Packed<UnderlineElem> { + #[typst_macros::time(name = "underline", span = self.span())] + fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { + line: DecoLine::Underline { + stroke: self.stroke(styles).unwrap_or_default(), + offset: self.offset(styles), + evade: self.evade(styles), + background: self.background(styles), + }, + extent: self.extent(styles), + }]))) + } +} + +/// Adds a line over text. +/// +/// # Example +/// ```example +/// #overline[A line over text.] +/// ``` +#[elem(Show)] +pub struct OverlineElem { + /// How to [stroke] the line. + /// + /// If set to `{auto}`, takes on the text's color and a thickness defined in + /// the current font. + /// + /// ```example + /// #set text(fill: olive) + /// #overline( + /// stroke: green.darken(20%), + /// offset: -12pt, + /// [The Forest Theme], + /// ) + /// ``` + #[resolve] + #[fold] + pub stroke: Smart<Stroke>, + + /// The 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 + /// ] + /// ``` + #[resolve] + pub offset: Smart<Length>, + + /// The amount by which to extend the line beyond (or within if negative) + /// the content. + /// + /// ```example + /// #set overline(extent: 4pt) + /// #set underline(extent: 4pt) + /// #overline(underline[Typography Today]) + /// ``` + #[resolve] + pub extent: Length, + + /// 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], + /// ) + /// ``` + #[default(true)] + pub evade: bool, + + /// Whether the line is placed behind the content it overlines. + /// + /// ```example + /// #set overline(stroke: (thickness: 1em, paint: maroon, cap: "round")) + /// #overline(background: true)[This is stylized.] \ + /// #overline(background: false)[This is partially hidden.] + /// ``` + #[default(false)] + pub background: bool, + + /// The content to add a line over. + #[required] + pub body: Content, +} + +impl Show for Packed<OverlineElem> { + #[typst_macros::time(name = "overline", span = self.span())] + fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { + line: DecoLine::Overline { + stroke: self.stroke(styles).unwrap_or_default(), + offset: self.offset(styles), + evade: self.evade(styles), + background: self.background(styles), + }, + extent: self.extent(styles), + }]))) + } +} + +/// Strikes through text. +/// +/// # Example +/// ```example +/// This is #strike[not] relevant. +/// ``` +#[elem(title = "Strikethrough", Show)] +pub struct StrikeElem { + /// How to [stroke] the line. + /// + /// If set to `{auto}`, takes on the text's color and a thickness defined in + /// the current font. + /// + /// _Note:_ Please don't use this for real redaction as you can still copy + /// paste the text. + /// + /// ```example + /// This is #strike(stroke: 1.5pt + red)[very stricken through]. \ + /// This is #strike(stroke: 10pt)[redacted]. + /// ``` + #[resolve] + #[fold] + pub stroke: Smart<Stroke>, + + /// The 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(font: "Inria Serif") + /// This is #strike(offset: auto)[low-ish]. \ + /// This is #strike(offset: -3.5pt)[on-top]. + /// ``` + #[resolve] + pub offset: Smart<Length>, + + /// The amount by which to extend the line beyond (or within if negative) + /// the content. + /// + /// ```example + /// This #strike(extent: -2pt)[skips] parts of the word. + /// This #strike(extent: 2pt)[extends] beyond the word. + /// ``` + #[resolve] + pub extent: Length, + + /// Whether the line is placed behind the content. + /// + /// ```example + /// #set strike(stroke: red) + /// #strike(background: true)[This is behind.] \ + /// #strike(background: false)[This is in front.] + /// ``` + #[default(false)] + pub background: bool, + + /// The content to strike through. + #[required] + pub body: Content, +} + +impl Show for Packed<StrikeElem> { + #[typst_macros::time(name = "strike", span = self.span())] + fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { + // Note that we do not support evade option for strikethrough. + line: DecoLine::Strikethrough { + stroke: self.stroke(styles).unwrap_or_default(), + offset: self.offset(styles), + background: self.background(styles), + }, + extent: self.extent(styles), + }]))) + } +} + +/// Highlights text with a background color. +/// +/// # Example +/// ```example +/// This is #highlight[important]. +/// ``` +#[elem(Show)] +pub struct HighlightElem { + /// The color to highlight the text with. + /// + /// ```example + /// This is #highlight( + /// fill: blue + /// )[highlighted with blue]. + /// ``` + #[default(Some(Color::from_u8(0xFF, 0xFD, 0x11, 0xA1).into()))] + pub fill: Option<Paint>, + + /// The highlight's border color. See the + /// [rectangle's documentation]($rect.stroke) for more details. + /// + /// ```example + /// This is a #highlight( + /// stroke: fuchsia + /// )[stroked highlighting]. + /// ``` + #[resolve] + #[fold] + pub stroke: Sides<Option<Option<Stroke>>>, + + /// The top end of the background rectangle. + /// + /// ```example + /// #set highlight(top-edge: "ascender") + /// #highlight[a] #highlight[aib] + /// + /// #set highlight(top-edge: "x-height") + /// #highlight[a] #highlight[aib] + /// ``` + #[default(TopEdge::Metric(TopEdgeMetric::Ascender))] + pub top_edge: TopEdge, + + /// The bottom end of the background rectangle. + /// + /// ```example + /// #set highlight(bottom-edge: "descender") + /// #highlight[a] #highlight[ap] + /// + /// #set highlight(bottom-edge: "baseline") + /// #highlight[a] #highlight[ap] + /// ``` + #[default(BottomEdge::Metric(BottomEdgeMetric::Descender))] + pub bottom_edge: BottomEdge, + + /// The amount by which to extend the background to the sides beyond + /// (or within if negative) the content. + /// + /// ```example + /// A long #highlight(extent: 4pt)[background]. + /// ``` + #[resolve] + pub extent: Length, + + /// How much to round the highlight's corners. See the + /// [rectangle's documentation]($rect.radius) for more details. + /// + /// ```example + /// Listen #highlight( + /// radius: 5pt, extent: 2pt + /// )[carefully], it will be on the test. + /// ``` + #[resolve] + #[fold] + pub radius: Corners<Option<Rel<Length>>>, + + /// The content that should be highlighted. + #[required] + pub body: Content, +} + +impl Show for Packed<HighlightElem> { + #[typst_macros::time(name = "highlight", span = self.span())] + fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { + Ok(self.body().clone().styled(TextElem::set_deco(smallvec![Decoration { + line: DecoLine::Highlight { + fill: self.fill(styles), + stroke: self + .stroke(styles) + .unwrap_or_default() + .map(|stroke| stroke.map(Stroke::unwrap_or_default)), + top_edge: self.top_edge(styles), + bottom_edge: self.bottom_edge(styles), + radius: self.radius(styles).unwrap_or_default(), + }, + extent: self.extent(styles), + }]))) + } +} + +/// A text decoration. +/// +/// Can be positioned over, under, or on top of text, or highlight the text with +/// a background. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct Decoration { + pub line: DecoLine, + pub extent: Abs, +} + +/// A kind of decorative line. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum DecoLine { + Underline { + stroke: Stroke<Abs>, + offset: Smart<Abs>, + evade: bool, + background: bool, + }, + Strikethrough { + stroke: Stroke<Abs>, + offset: Smart<Abs>, + background: bool, + }, + Overline { + stroke: Stroke<Abs>, + offset: Smart<Abs>, + evade: bool, + background: bool, + }, + Highlight { + fill: Option<Paint>, + stroke: Sides<Option<FixedStroke>>, + top_edge: TopEdge, + bottom_edge: BottomEdge, + radius: Corners<Rel<Abs>>, + }, +} |
