mirror of
https://github.com/typst/typst
synced 2025-08-14 07:07:54 +08:00
Provide highlight
function for setting the background color of text (#2076)
This commit is contained in:
parent
09442d93ee
commit
71dccde031
@ -289,10 +289,11 @@ impl Layout for EquationElem {
|
|||||||
frame.push_frame(Point::new(x, y), counter)
|
frame.push_frame(Point::new(x, y), counter)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
let font_size = TextElem::size_in(styles);
|
||||||
let slack = ParElem::leading_in(styles) * 0.7;
|
let slack = ParElem::leading_in(styles) * 0.7;
|
||||||
let top_edge = TextElem::top_edge_in(styles).resolve(styles, &font, None);
|
let top_edge = TextElem::top_edge_in(styles).resolve(font_size, &font, None);
|
||||||
let bottom_edge =
|
let bottom_edge =
|
||||||
-TextElem::bottom_edge_in(styles).resolve(styles, &font, None);
|
-TextElem::bottom_edge_in(styles).resolve(font_size, &font, None);
|
||||||
|
|
||||||
let ascent = top_edge.max(frame.ascent() - slack);
|
let ascent = top_edge.max(frame.ascent() - slack);
|
||||||
let descent = bottom_edge.max(frame.descent() - slack);
|
let descent = bottom_edge.max(frame.descent() - slack);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use kurbo::{BezPath, Line, ParamCurve};
|
use kurbo::{BezPath, Line, ParamCurve};
|
||||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
|
|
||||||
use super::TextElem;
|
use super::{BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// Underlines text.
|
/// Underlines text.
|
||||||
@ -73,11 +73,12 @@ impl Show for UnderlineElem {
|
|||||||
#[tracing::instrument(name = "UnderlineElem::show", skip_all)]
|
#[tracing::instrument(name = "UnderlineElem::show", skip_all)]
|
||||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||||
line: DecoLine::Underline,
|
line: DecoLine::Underline {
|
||||||
stroke: self.stroke(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
offset: self.offset(styles),
|
offset: self.offset(styles),
|
||||||
extent: self.extent(styles),
|
|
||||||
evade: self.evade(styles),
|
evade: self.evade(styles),
|
||||||
|
},
|
||||||
|
extent: self.extent(styles),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,11 +158,12 @@ impl Show for OverlineElem {
|
|||||||
#[tracing::instrument(name = "OverlineElem::show", skip_all)]
|
#[tracing::instrument(name = "OverlineElem::show", skip_all)]
|
||||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||||
line: DecoLine::Overline,
|
line: DecoLine::Overline {
|
||||||
stroke: self.stroke(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
offset: self.offset(styles),
|
offset: self.offset(styles),
|
||||||
extent: self.extent(styles),
|
|
||||||
evade: self.evade(styles),
|
evade: self.evade(styles),
|
||||||
|
},
|
||||||
|
extent: self.extent(styles),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -226,23 +228,98 @@ impl Show for StrikeElem {
|
|||||||
#[tracing::instrument(name = "StrikeElem::show", skip_all)]
|
#[tracing::instrument(name = "StrikeElem::show", skip_all)]
|
||||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||||
line: DecoLine::Strikethrough,
|
// Note that we do not support evade option for strikethrough.
|
||||||
|
line: DecoLine::Strikethrough {
|
||||||
stroke: self.stroke(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
offset: self.offset(styles),
|
offset: self.offset(styles),
|
||||||
|
},
|
||||||
extent: self.extent(styles),
|
extent: self.extent(styles),
|
||||||
evade: false,
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines a line that is positioned over, under or on top of text.
|
/// Highlights text with a background color.
|
||||||
|
///
|
||||||
|
/// ## Example { #example }
|
||||||
|
/// ```example
|
||||||
|
/// This is #highlight[important].
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Display: Highlight
|
||||||
|
/// Category: text
|
||||||
|
#[element(Show)]
|
||||||
|
pub struct HighlightElem {
|
||||||
|
/// The color to highlight the text with.
|
||||||
|
/// (Default: 0xffff5f)
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// This is #highlight(fill: blue)[with blue].
|
||||||
|
/// ```
|
||||||
|
#[default(Color::Rgba(RgbaColor::new(0xFF, 0xFF, 0x5F, 0xFF)).into())]
|
||||||
|
pub fill: Paint,
|
||||||
|
|
||||||
|
/// The top end of the background rectangle. Note that top edge will update
|
||||||
|
/// to be always higher than the glyph's bounding box.
|
||||||
|
/// (default: "ascender")
|
||||||
|
///
|
||||||
|
/// ```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. Note that top edge will update
|
||||||
|
/// to be always lower than the glyph's bounding box.
|
||||||
|
/// (default: "descender")
|
||||||
|
///
|
||||||
|
/// ```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,
|
||||||
|
|
||||||
|
/// The content that should be highlighted.
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for HighlightElem {
|
||||||
|
#[tracing::instrument(name = "HighlightElem::show", skip_all)]
|
||||||
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||||
|
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||||
|
line: DecoLine::Highlight {
|
||||||
|
fill: self.fill(styles),
|
||||||
|
top_edge: self.top_edge(styles),
|
||||||
|
bottom_edge: self.bottom_edge(styles),
|
||||||
|
},
|
||||||
|
extent: self.extent(styles),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a line-based decoration that is positioned over, under or on top of text,
|
||||||
|
/// or highlights the text with a background.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Decoration {
|
pub struct Decoration {
|
||||||
pub line: DecoLine,
|
line: DecoLine,
|
||||||
pub stroke: PartialStroke<Abs>,
|
extent: Abs,
|
||||||
pub offset: Smart<Abs>,
|
|
||||||
pub extent: Abs,
|
|
||||||
pub evade: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fold for Decoration {
|
impl Fold for Decoration {
|
||||||
@ -259,11 +336,12 @@ cast! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A kind of decorative line.
|
/// A kind of decorative line.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum DecoLine {
|
enum DecoLine {
|
||||||
Underline,
|
Underline { stroke: PartialStroke<Abs>, offset: Smart<Abs>, evade: bool },
|
||||||
Strikethrough,
|
Strikethrough { stroke: PartialStroke<Abs>, offset: Smart<Abs> },
|
||||||
Overline,
|
Overline { stroke: PartialStroke<Abs>, offset: Smart<Abs>, evade: bool },
|
||||||
|
Highlight { fill: Paint, top_edge: TopEdge, bottom_edge: BottomEdge },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add line decorations to a single run of shaped text.
|
/// Add line decorations to a single run of shaped text.
|
||||||
@ -271,19 +349,36 @@ pub(super) fn decorate(
|
|||||||
frame: &mut Frame,
|
frame: &mut Frame,
|
||||||
deco: &Decoration,
|
deco: &Decoration,
|
||||||
text: &TextItem,
|
text: &TextItem,
|
||||||
|
width: Abs,
|
||||||
shift: Abs,
|
shift: Abs,
|
||||||
pos: Point,
|
pos: Point,
|
||||||
width: Abs,
|
|
||||||
) {
|
) {
|
||||||
let font_metrics = text.font.metrics();
|
let font_metrics = text.font.metrics();
|
||||||
let metrics = match deco.line {
|
|
||||||
DecoLine::Strikethrough => font_metrics.strikethrough,
|
if let DecoLine::Highlight { fill, top_edge, bottom_edge } = &deco.line {
|
||||||
DecoLine::Overline => font_metrics.overline,
|
let (top, bottom) = determine_edges(text, *top_edge, *bottom_edge);
|
||||||
DecoLine::Underline => font_metrics.underline,
|
let rect = Geometry::Rect(Size::new(width + 2.0 * deco.extent, top - bottom))
|
||||||
|
.filled(fill.clone());
|
||||||
|
let origin = Point::new(pos.x - deco.extent, pos.y - top - shift);
|
||||||
|
frame.prepend(origin, FrameItem::Shape(rect, Span::detached()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (stroke, metrics, offset, evade) = match &deco.line {
|
||||||
|
DecoLine::Strikethrough { stroke, offset } => {
|
||||||
|
(stroke, font_metrics.strikethrough, offset, false)
|
||||||
|
}
|
||||||
|
DecoLine::Overline { stroke, offset, evade } => {
|
||||||
|
(stroke, font_metrics.overline, offset, *evade)
|
||||||
|
}
|
||||||
|
DecoLine::Underline { stroke, offset, evade } => {
|
||||||
|
(stroke, font_metrics.underline, offset, *evade)
|
||||||
|
}
|
||||||
|
_ => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
let offset = deco.offset.unwrap_or(-metrics.position.at(text.size)) - shift;
|
let offset = offset.unwrap_or(-metrics.position.at(text.size)) - shift;
|
||||||
let stroke = deco.stroke.clone().unwrap_or(Stroke {
|
let stroke = stroke.clone().unwrap_or(Stroke {
|
||||||
paint: text.fill.clone(),
|
paint: text.fill.clone(),
|
||||||
thickness: metrics.thickness.at(text.size),
|
thickness: metrics.thickness.at(text.size),
|
||||||
..Stroke::default()
|
..Stroke::default()
|
||||||
@ -299,13 +394,13 @@ pub(super) fn decorate(
|
|||||||
let origin = Point::new(from, pos.y + offset);
|
let origin = Point::new(from, pos.y + offset);
|
||||||
let target = Point::new(to - from, Abs::zero());
|
let target = Point::new(to - from, Abs::zero());
|
||||||
|
|
||||||
if target.x >= min_width || !deco.evade {
|
if target.x >= min_width || !evade {
|
||||||
let shape = Geometry::Line(target).stroked(stroke.clone());
|
let shape = Geometry::Line(target).stroked(stroke.clone());
|
||||||
frame.push(origin, FrameItem::Shape(shape, Span::detached()));
|
frame.push(origin, FrameItem::Shape(shape, Span::detached()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !deco.evade {
|
if !evade {
|
||||||
push_segment(start, end);
|
push_segment(start, end);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -366,6 +461,31 @@ pub(super) fn decorate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the top/bottom edge of the text given the metric of the font.
|
||||||
|
fn determine_edges(
|
||||||
|
text: &TextItem,
|
||||||
|
top_edge: TopEdge,
|
||||||
|
bottom_edge: BottomEdge,
|
||||||
|
) -> (Abs, Abs) {
|
||||||
|
let mut bbox = None;
|
||||||
|
if top_edge.is_bounds() || bottom_edge.is_bounds() {
|
||||||
|
let ttf = text.font.ttf();
|
||||||
|
bbox = text
|
||||||
|
.glyphs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|g| ttf.glyph_bounding_box(ttf_parser::GlyphId(g.id)))
|
||||||
|
.reduce(|a, b| ttf_parser::Rect {
|
||||||
|
y_max: a.y_max.max(b.y_max),
|
||||||
|
y_min: a.y_min.min(b.y_min),
|
||||||
|
..a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let top = top_edge.resolve(text.size, &text.font, bbox);
|
||||||
|
let bottom = bottom_edge.resolve(text.size, &text.font, bbox);
|
||||||
|
(top, bottom)
|
||||||
|
}
|
||||||
|
|
||||||
/// Builds a kurbo [`BezPath`] for a glyph.
|
/// Builds a kurbo [`BezPath`] for a glyph.
|
||||||
struct BezPathBuilder {
|
struct BezPathBuilder {
|
||||||
path: BezPath,
|
path: BezPath,
|
||||||
|
@ -35,6 +35,7 @@ pub(super) fn define(global: &mut Scope) {
|
|||||||
global.define("super", SuperElem::func());
|
global.define("super", SuperElem::func());
|
||||||
global.define("underline", UnderlineElem::func());
|
global.define("underline", UnderlineElem::func());
|
||||||
global.define("strike", StrikeElem::func());
|
global.define("strike", StrikeElem::func());
|
||||||
|
global.define("highlight", HighlightElem::func());
|
||||||
global.define("overline", OverlineElem::func());
|
global.define("overline", OverlineElem::func());
|
||||||
global.define("raw", RawElem::func());
|
global.define("raw", RawElem::func());
|
||||||
global.define("lorem", lorem_func());
|
global.define("lorem", lorem_func());
|
||||||
@ -652,17 +653,17 @@ impl TopEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the value of the text edge given a font's metrics.
|
/// Resolve the value of the text edge given a font's metrics.
|
||||||
pub fn resolve(self, styles: StyleChain, font: &Font, bbox: Option<Rect>) -> Abs {
|
pub fn resolve(self, font_size: Abs, font: &Font, bbox: Option<Rect>) -> Abs {
|
||||||
match self {
|
match self {
|
||||||
TopEdge::Metric(metric) => {
|
TopEdge::Metric(metric) => {
|
||||||
if let Ok(metric) = metric.try_into() {
|
if let Ok(metric) = metric.try_into() {
|
||||||
font.metrics().vertical(metric).resolve(styles)
|
font.metrics().vertical(metric).at(font_size)
|
||||||
} else {
|
} else {
|
||||||
bbox.map(|bbox| (font.to_em(bbox.y_max)).resolve(styles))
|
bbox.map(|bbox| (font.to_em(bbox.y_max)).at(font_size))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TopEdge::Length(length) => length.resolve(styles),
|
TopEdge::Length(length) => length.at(font_size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -722,17 +723,17 @@ impl BottomEdge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve the value of the text edge given a font's metrics.
|
/// Resolve the value of the text edge given a font's metrics.
|
||||||
pub fn resolve(self, styles: StyleChain, font: &Font, bbox: Option<Rect>) -> Abs {
|
pub fn resolve(self, font_size: Abs, font: &Font, bbox: Option<Rect>) -> Abs {
|
||||||
match self {
|
match self {
|
||||||
BottomEdge::Metric(metric) => {
|
BottomEdge::Metric(metric) => {
|
||||||
if let Ok(metric) = metric.try_into() {
|
if let Ok(metric) = metric.try_into() {
|
||||||
font.metrics().vertical(metric).resolve(styles)
|
font.metrics().vertical(metric).at(font_size)
|
||||||
} else {
|
} else {
|
||||||
bbox.map(|bbox| (font.to_em(bbox.y_min)).resolve(styles))
|
bbox.map(|bbox| (font.to_em(bbox.y_min)).at(font_size))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BottomEdge::Length(length) => length.resolve(styles),
|
BottomEdge::Length(length) => length.at(font_size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -293,15 +293,17 @@ impl<'a> ShapedText<'a> {
|
|||||||
glyphs,
|
glyphs,
|
||||||
};
|
};
|
||||||
|
|
||||||
let layer = frame.layer();
|
|
||||||
let width = item.width();
|
let width = item.width();
|
||||||
|
if decos.is_empty() {
|
||||||
|
frame.push(pos, FrameItem::Text(item));
|
||||||
|
} else {
|
||||||
// Apply line decorations.
|
// Apply line decorations.
|
||||||
|
frame.push(pos, FrameItem::Text(item.clone()));
|
||||||
for deco in &decos {
|
for deco in &decos {
|
||||||
decorate(&mut frame, deco, &item, shift, pos, width);
|
decorate(&mut frame, deco, &item, width, shift, pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.insert(layer, pos, FrameItem::Text(item));
|
|
||||||
offset += width;
|
offset += width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,8 +323,8 @@ impl<'a> ShapedText<'a> {
|
|||||||
|
|
||||||
// Expand top and bottom by reading the font's vertical metrics.
|
// Expand top and bottom by reading the font's vertical metrics.
|
||||||
let mut expand = |font: &Font, bbox: Option<ttf_parser::Rect>| {
|
let mut expand = |font: &Font, bbox: Option<ttf_parser::Rect>| {
|
||||||
top.set_max(top_edge.resolve(self.styles, font, bbox));
|
top.set_max(top_edge.resolve(self.size, font, bbox));
|
||||||
bottom.set_max(-bottom_edge.resolve(self.styles, font, bbox));
|
bottom.set_max(-bottom_edge.resolve(self.size, font, bbox));
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.glyphs.is_empty() {
|
if self.glyphs.is_empty() {
|
||||||
|
@ -34,6 +34,11 @@ impl Length {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert to an absolute length at the given font size.
|
||||||
|
pub fn at(self, font_size: Abs) -> Abs {
|
||||||
|
self.abs + self.em.at(font_size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Length {
|
impl Debug for Length {
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 47 KiB |
@ -20,14 +20,40 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
#let redact = strike.with(stroke: 10pt, extent: 0.05em)
|
#let redact = strike.with(stroke: 10pt, extent: 0.05em)
|
||||||
#let highlight = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em)
|
#let highlight-custom = strike.with(stroke: 10pt + rgb("abcdef88"), extent: 0.05em)
|
||||||
|
|
||||||
// Abuse thickness and transparency for redacting and highlighting stuff.
|
// Abuse thickness and transparency for redacting and highlighting stuff.
|
||||||
Sometimes, we work #redact[in secret].
|
Sometimes, we work #redact[in secret].
|
||||||
There might be #highlight[redacted] things.
|
There might be #highlight-custom[redacted] things.
|
||||||
underline()
|
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test stroke folding.
|
// Test stroke folding.
|
||||||
#set underline(stroke: 2pt, offset: 2pt)
|
#set underline(stroke: 2pt, offset: 2pt)
|
||||||
#underline(text(red, [DANGER!]))
|
#underline(text(red, [DANGER!]))
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test highlight.
|
||||||
|
This is the built-in #highlight[highlight with default color].
|
||||||
|
We can also specify a customized value
|
||||||
|
#highlight(fill: green.lighten(80%))[to highlight].
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test default highlight bounds.
|
||||||
|
#highlight[ace],
|
||||||
|
#highlight[base],
|
||||||
|
#highlight[super],
|
||||||
|
#highlight[phone #sym.integral]
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test a tighter highlight.
|
||||||
|
#set highlight(top-edge: "x-height", bottom-edge: "baseline")
|
||||||
|
#highlight[ace],
|
||||||
|
#highlight[base],
|
||||||
|
#highlight[super],
|
||||||
|
#highlight[phone #sym.integral]
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test a bounds highlight.
|
||||||
|
#set highlight(top-edge: "bounds", bottom-edge: "bounds")
|
||||||
|
#highlight[abc]
|
||||||
|
#highlight[abc #sym.integral]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user