summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2025-06-26 15:44:45 +0200
committerGitHub <noreply@github.com>2025-06-26 13:44:45 +0000
commit9311f6f08eca583b6214834148c94685f0161c21 (patch)
tree3d217e63cbd2fd162abe98ba0f9818058c39dc27
parent7420ec972ffd6e4893fa24d12992b375da6b11fa (diff)
Basic support for text decoration functions in HTML (#6510)
-rw-r--r--crates/typst-library/src/text/deco.rs32
-rw-r--r--tests/ref/html/html-deco.html11
-rw-r--r--tests/suite/text/deco.typ8
3 files changed, 50 insertions, 1 deletions
diff --git a/crates/typst-library/src/text/deco.rs b/crates/typst-library/src/text/deco.rs
index 7aa06e81..d745a48f 100644
--- a/crates/typst-library/src/text/deco.rs
+++ b/crates/typst-library/src/text/deco.rs
@@ -2,7 +2,10 @@ use smallvec::smallvec;
use crate::diag::SourceResult;
use crate::engine::Engine;
-use crate::foundations::{elem, Content, Packed, Show, Smart, StyleChain};
+use crate::foundations::{
+ elem, Content, NativeElement, Packed, Show, Smart, StyleChain, TargetElem,
+};
+use crate::html::{attr, tag, HtmlElem};
use crate::layout::{Abs, Corners, Length, Rel, Sides};
use crate::text::{BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric};
use crate::visualize::{Color, FixedStroke, Paint, Stroke};
@@ -81,6 +84,16 @@ pub struct UnderlineElem {
impl Show for Packed<UnderlineElem> {
#[typst_macros::time(name = "underline", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ // Note: In modern HTML, `<u>` is not the underline element, but
+ // rather an "Unarticulated Annotation" element (see HTML spec
+ // 4.5.22). Using `text-decoration` instead is recommended by MDN.
+ return Ok(HtmlElem::new(tag::span)
+ .with_attr(attr::style, "text-decoration: underline")
+ .with_body(Some(self.body.clone()))
+ .pack());
+ }
+
Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration {
line: DecoLine::Underline {
stroke: self.stroke(styles).unwrap_or_default(),
@@ -173,6 +186,13 @@ pub struct OverlineElem {
impl Show for Packed<OverlineElem> {
#[typst_macros::time(name = "overline", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::span)
+ .with_attr(attr::style, "text-decoration: overline")
+ .with_body(Some(self.body.clone()))
+ .pack());
+ }
+
Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration {
line: DecoLine::Overline {
stroke: self.stroke(styles).unwrap_or_default(),
@@ -250,6 +270,10 @@ pub struct StrikeElem {
impl Show for Packed<StrikeElem> {
#[typst_macros::time(name = "strike", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::s).with_body(Some(self.body.clone())).pack());
+ }
+
Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration {
// Note that we do not support evade option for strikethrough.
line: DecoLine::Strikethrough {
@@ -345,6 +369,12 @@ pub struct HighlightElem {
impl Show for Packed<HighlightElem> {
#[typst_macros::time(name = "highlight", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ if TargetElem::target_in(styles).is_html() {
+ return Ok(HtmlElem::new(tag::mark)
+ .with_body(Some(self.body.clone()))
+ .pack());
+ }
+
Ok(self.body.clone().styled(TextElem::set_deco(smallvec![Decoration {
line: DecoLine::Highlight {
fill: self.fill(styles),
diff --git a/tests/ref/html/html-deco.html b/tests/ref/html/html-deco.html
new file mode 100644
index 00000000..87f2ab4c
--- /dev/null
+++ b/tests/ref/html/html-deco.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ </head>
+ <body>
+ <p><s>Struck</s> <mark>Highlighted</mark> <span style="text-decoration: underline">Underlined</span> <span style="text-decoration: overline">Overlined</span></p>
+ <p><span style="text-decoration: overline"><span style="text-decoration: underline"><mark><s>Mixed</s></mark></span></span></p>
+ </body>
+</html>
diff --git a/tests/suite/text/deco.typ b/tests/suite/text/deco.typ
index 07fdb6c1..a1d287d9 100644
--- a/tests/suite/text/deco.typ
+++ b/tests/suite/text/deco.typ
@@ -83,3 +83,11 @@ We can also specify a customized value
#highlight(stroke: 2pt + blue)[abc]
#highlight(stroke: (top: blue, left: red, bottom: green, right: orange))[abc]
#highlight(stroke: 1pt, radius: 3pt)[#lorem(5)]
+
+--- html-deco html ---
+#strike[Struck]
+#highlight[Highlighted]
+#underline[Underlined]
+#overline[Overlined]
+
+#(strike, highlight, underline, overline).fold([Mixed], (it, f) => f(it))