summaryrefslogtreecommitdiff
path: root/library/src/text/deco.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-07-02 19:59:52 +0200
committerLaurenz <laurmaedje@gmail.com>2023-07-02 20:07:43 +0200
commitebfdb1dafa430786db10dad2ef7d5467c1bdbed1 (patch)
tree2bbc24ddb4124c4bb14dec0e536129d4de37b056 /library/src/text/deco.rs
parent3ab19185093d7709f824b95b979060ce125389d8 (diff)
Move everything into `crates/` directory
Diffstat (limited to 'library/src/text/deco.rs')
-rw-r--r--library/src/text/deco.rs420
1 files changed, 0 insertions, 420 deletions
diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs
deleted file mode 100644
index 9ec4ca32..00000000
--- a/library/src/text/deco.rs
+++ /dev/null
@@ -1,420 +0,0 @@
-use kurbo::{BezPath, Line, ParamCurve};
-use ttf_parser::{GlyphId, OutlineBuilder};
-
-use super::TextElem;
-use crate::prelude::*;
-
-/// Underlines text.
-///
-/// ## Example { #example }
-/// ```example
-/// This is #underline[important].
-/// ```
-///
-/// Display: Underline
-/// Category: text
-#[element(Show)]
-pub struct UnderlineElem {
- /// How to stroke the line.
- ///
- /// See the [line's documentation]($func/line.stroke) for more details. 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<PartialStroke>,
-
- /// 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,
-
- /// The content to underline.
- #[required]
- pub body: Content,
-}
-
-impl Show for UnderlineElem {
- #[tracing::instrument(name = "UnderlineElem::show", skip_all)]
- fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(self.body().styled(TextElem::set_deco(Decoration {
- line: DecoLine::Underline,
- stroke: self.stroke(styles).unwrap_or_default(),
- offset: self.offset(styles),
- extent: self.extent(styles),
- evade: self.evade(styles),
- })))
- }
-}
-
-/// Adds a line over text.
-///
-/// ## Example { #example }
-/// ```example
-/// #overline[A line over text.]
-/// ```
-///
-/// Display: Overline
-/// Category: text
-#[element(Show)]
-pub struct OverlineElem {
- /// How to stroke the line.
- ///
- /// See the [line's documentation]($func/line.stroke) for more details. 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<PartialStroke>,
-
- /// 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,
-
- /// The content to add a line over.
- #[required]
- pub body: Content,
-}
-
-impl Show for OverlineElem {
- #[tracing::instrument(name = "OverlineElem::show", skip_all)]
- fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(self.body().styled(TextElem::set_deco(Decoration {
- line: DecoLine::Overline,
- stroke: self.stroke(styles).unwrap_or_default(),
- offset: self.offset(styles),
- extent: self.extent(styles),
- evade: self.evade(styles),
- })))
- }
-}
-
-/// Strikes through text.
-///
-/// ## Example { #example }
-/// ```example
-/// This is #strike[not] relevant.
-/// ```
-///
-/// Display: Strikethrough
-/// Category: text
-#[element(Show)]
-pub struct StrikeElem {
- /// How to stroke the line.
- ///
- /// See the [line's documentation]($func/line.stroke) for more details. 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<PartialStroke>,
-
- /// 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,
-
- /// The content to strike through.
- #[required]
- pub body: Content,
-}
-
-impl Show for StrikeElem {
- #[tracing::instrument(name = "StrikeElem::show", skip_all)]
- fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
- Ok(self.body().styled(TextElem::set_deco(Decoration {
- line: DecoLine::Strikethrough,
- stroke: self.stroke(styles).unwrap_or_default(),
- offset: self.offset(styles),
- extent: self.extent(styles),
- evade: false,
- })))
- }
-}
-
-/// Defines a line that is positioned over, under or on top of text.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct Decoration {
- pub line: DecoLine,
- pub stroke: PartialStroke<Abs>,
- pub offset: Smart<Abs>,
- pub extent: Abs,
- pub evade: bool,
-}
-
-impl Fold for Decoration {
- type Output = Vec<Self>;
-
- fn fold(self, mut outer: Self::Output) -> Self::Output {
- outer.insert(0, self);
- outer
- }
-}
-
-cast! {
- type Decoration: "decoration",
-}
-
-/// A kind of decorative line.
-#[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(
- frame: &mut Frame,
- deco: &Decoration,
- text: &TextItem,
- shift: Abs,
- pos: Point,
- width: Abs,
-) {
- let font_metrics = text.font.metrics();
- let metrics = match deco.line {
- DecoLine::Strikethrough => font_metrics.strikethrough,
- DecoLine::Overline => font_metrics.overline,
- DecoLine::Underline => font_metrics.underline,
- };
-
- let offset = deco.offset.unwrap_or(-metrics.position.at(text.size)) - shift;
- let stroke = deco.stroke.clone().unwrap_or(Stroke {
- paint: text.fill.clone(),
- thickness: metrics.thickness.at(text.size),
- ..Stroke::default()
- });
-
- let gap_padding = 0.08 * text.size;
- let min_width = 0.162 * text.size;
-
- let start = pos.x - deco.extent;
- let end = pos.x + (width + 2.0 * deco.extent);
-
- let mut push_segment = |from: Abs, to: Abs| {
- let origin = Point::new(from, pos.y + offset);
- let target = Point::new(to - from, Abs::zero());
-
- if target.x >= min_width || !deco.evade {
- let shape = Geometry::Line(target).stroked(stroke.clone());
- frame.push(origin, FrameItem::Shape(shape, Span::detached()));
- }
- };
-
- if !deco.evade {
- push_segment(start, end);
- return;
- }
-
- let line = Line::new(
- kurbo::Point::new(pos.x.to_raw(), offset.to_raw()),
- kurbo::Point::new((pos.x + width).to_raw(), offset.to_raw()),
- );
-
- let mut x = pos.x;
- let mut intersections = vec![];
-
- for glyph in text.glyphs.iter() {
- let dx = glyph.x_offset.at(text.size) + x;
- let mut builder =
- BezPathBuilder::new(font_metrics.units_per_em, text.size, dx.to_raw());
-
- let bbox = text.font.ttf().outline_glyph(GlyphId(glyph.id), &mut builder);
- let path = builder.finish();
-
- x += glyph.x_advance.at(text.size);
-
- // Only do the costly segments intersection test if the line
- // intersects the bounding box.
- let intersect = bbox.map_or(false, |bbox| {
- let y_min = -text.font.to_em(bbox.y_max).at(text.size);
- let y_max = -text.font.to_em(bbox.y_min).at(text.size);
- offset >= y_min && offset <= y_max
- });
-
- if intersect {
- // Find all intersections of segments with the line.
- intersections.extend(
- path.segments()
- .flat_map(|seg| seg.intersect_line(line))
- .map(|is| Abs::raw(line.eval(is.line_t).x)),
- );
- }
- }
-
- // Add start and end points, taking padding into account.
- intersections.push(start - gap_padding);
- intersections.push(end + gap_padding);
- // When emitting the decorative line segments, we move from left to
- // right. The intersections are not necessarily in this order, yet.
- intersections.sort();
-
- for edge in intersections.windows(2) {
- let l = edge[0];
- let r = edge[1];
-
- // If we are too close, don't draw the segment
- if r - l < gap_padding {
- continue;
- } else {
- push_segment(l + gap_padding, r - gap_padding);
- }
- }
-}
-
-/// Builds a kurbo [`BezPath`] for a glyph.
-struct BezPathBuilder {
- path: BezPath,
- units_per_em: f64,
- font_size: Abs,
- x_offset: f64,
-}
-
-impl BezPathBuilder {
- fn new(units_per_em: f64, font_size: Abs, x_offset: f64) -> Self {
- Self {
- path: BezPath::new(),
- units_per_em,
- font_size,
- x_offset,
- }
- }
-
- fn finish(self) -> BezPath {
- self.path
- }
-
- fn p(&self, x: f32, y: f32) -> kurbo::Point {
- kurbo::Point::new(self.s(x) + self.x_offset, -self.s(y))
- }
-
- fn s(&self, v: f32) -> f64 {
- Em::from_units(v, self.units_per_em).at(self.font_size).to_raw()
- }
-}
-
-impl OutlineBuilder for BezPathBuilder {
- fn move_to(&mut self, x: f32, y: f32) {
- self.path.move_to(self.p(x, y));
- }
-
- fn line_to(&mut self, x: f32, y: f32) {
- self.path.line_to(self.p(x, y));
- }
-
- fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
- self.path.quad_to(self.p(x1, y1), self.p(x, y));
- }
-
- fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
- self.path.curve_to(self.p(x1, y1), self.p(x2, y2), self.p(x, y));
- }
-
- fn close(&mut self) {
- self.path.close_path();
- }
-}