diff --git a/crates/typst/src/text/deco.rs b/crates/typst/src/text/deco.rs index 7f0b9e467..d105a3535 100644 --- a/crates/typst/src/text/deco.rs +++ b/crates/typst/src/text/deco.rs @@ -5,12 +5,14 @@ use ttf_parser::{GlyphId, OutlineBuilder}; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{elem, Content, Packed, Show, Smart, StyleChain}; -use crate::layout::{Abs, Em, Frame, FrameItem, Length, Point, Size}; +use crate::layout::{ + Abs, Corners, Em, Frame, FrameItem, Length, Point, Rel, Sides, Size, +}; use crate::syntax::Span; use crate::text::{ BottomEdge, BottomEdgeMetric, TextElem, TextItem, TopEdge, TopEdgeMetric, }; -use crate::visualize::{Color, FixedStroke, Geometry, Paint, Stroke}; +use crate::visualize::{styled_rect, Color, FixedStroke, Geometry, Paint, Stroke}; /// Underlines text. /// @@ -283,6 +285,12 @@ pub struct HighlightElem { #[default(Color::from_u8(0xFF, 0xFD, 0x11, 0xA1).into())] pub fill: Paint, + /// The highlight's border color. See the + /// [rectangle's documentation]($rect.stroke) for more details. + #[resolve] + #[fold] + pub stroke: Sides>>, + /// The top end of the background rectangle. /// /// ```example @@ -316,6 +324,12 @@ pub struct HighlightElem { #[resolve] pub extent: Length, + /// How much to round the highlight's corners. See the + /// [rectangle's documentation]($rect.radius) for more details. + #[resolve] + #[fold] + pub radius: Corners>>, + /// The content that should be highlighted. #[required] pub body: Content, @@ -327,8 +341,13 @@ impl Show for Packed { 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), }]))) @@ -348,10 +367,30 @@ pub struct Decoration { /// A kind of decorative line. #[derive(Debug, Clone, Eq, PartialEq, Hash)] enum DecoLine { - Underline { stroke: Stroke, offset: Smart, evade: bool, background: bool }, - Strikethrough { stroke: Stroke, offset: Smart, background: bool }, - Overline { stroke: Stroke, offset: Smart, evade: bool, background: bool }, - Highlight { fill: Paint, top_edge: TopEdge, bottom_edge: BottomEdge }, + Underline { + stroke: Stroke, + offset: Smart, + evade: bool, + background: bool, + }, + Strikethrough { + stroke: Stroke, + offset: Smart, + background: bool, + }, + Overline { + stroke: Stroke, + offset: Smart, + evade: bool, + background: bool, + }, + Highlight { + fill: Paint, + stroke: Sides>, + top_edge: TopEdge, + bottom_edge: BottomEdge, + radius: Corners>, + }, } /// Add line decorations to a single run of shaped text. @@ -365,12 +404,18 @@ pub(crate) fn decorate( ) { let font_metrics = text.font.metrics(); - if let DecoLine::Highlight { fill, top_edge, bottom_edge } = &deco.line { + if let DecoLine::Highlight { fill, stroke, top_edge, bottom_edge, radius } = + &deco.line + { let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge); - let rect = Geometry::Rect(Size::new(width + 2.0 * deco.extent, top - bottom)) - .filled(fill.clone()); + let size = Size::new(width + 2.0 * deco.extent, top - bottom); + let rects = styled_rect(size, *radius, Some(fill.clone()), stroke.clone()); let origin = Point::new(pos.x - deco.extent, pos.y - top - shift); - frame.prepend(origin, FrameItem::Shape(rect, Span::detached())); + frame.prepend_multiple( + rects + .into_iter() + .map(|shape| (origin, FrameItem::Shape(shape, Span::detached()))), + ); return; } diff --git a/tests/ref/text/deco.png b/tests/ref/text/deco.png index 782e628c3..3a11e72f2 100644 Binary files a/tests/ref/text/deco.png and b/tests/ref/text/deco.png differ diff --git a/tests/typ/text/deco.typ b/tests/typ/text/deco.typ index dbf86a864..cc9b9b3ae 100644 --- a/tests/typ/text/deco.typ +++ b/tests/typ/text/deco.typ @@ -58,6 +58,17 @@ We can also specify a customized value #highlight[abc] #highlight[abc #sym.integral] +--- +// Test highlight radius +#highlight(radius: 3pt)[abc], +#highlight(radius: 1em)[#lorem(5)] + +--- +// Test highlight stroke +#highlight(stroke: 2pt + blue)[abc] +#highlight(stroke: (top: blue, left: red, bottom: green, right: orange))[abc] +#highlight(stroke: 1pt, radius: 3pt)[#lorem(5)] + --- // Test underline background #set underline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round")) @@ -68,7 +79,6 @@ We can also specify a customized value #set overline(background: true, stroke: (thickness: 0.5em, paint: red, cap: "round")) #overline[This is in the background] - --- // Test strike background #set strike(background: true, stroke: 5pt + red)