summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Färber <01mf02@gmail.com>2025-01-23 13:21:34 +0100
committerGitHub <noreply@github.com>2025-01-23 12:21:34 +0000
commitb3fb6c2326ac6d585cc17d1f643bc06e076be042 (patch)
tree2edeab197eb7549db5e20bb816cbbd314aed3ca4
parente61cd6fb9e9a90de8d78f05a43246f08feddcf8f (diff)
Support quotes in HTML output (#5673)
-rw-r--r--crates/typst-library/src/model/quote.rs83
-rw-r--r--tests/ref/html/quote-attribution-link.html15
-rw-r--r--tests/ref/html/quote-nesting-html.html12
-rw-r--r--tests/ref/html/quote-plato.html21
-rw-r--r--tests/suite/model/quote.typ23
5 files changed, 121 insertions, 33 deletions
diff --git a/crates/typst-library/src/model/quote.rs b/crates/typst-library/src/model/quote.rs
index 2eaa32d4..774384ac 100644
--- a/crates/typst-library/src/model/quote.rs
+++ b/crates/typst-library/src/model/quote.rs
@@ -2,13 +2,14 @@ use crate::diag::SourceResult;
use crate::engine::Engine;
use crate::foundations::{
cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart,
- StyleChain, Styles,
+ StyleChain, Styles, TargetElem,
};
+use crate::html::{tag, HtmlAttr, HtmlElem};
use crate::introspection::Locatable;
use crate::layout::{
Alignment, BlockBody, BlockElem, Em, HElem, PadElem, Spacing, VElem,
};
-use crate::model::{CitationForm, CiteElem};
+use crate::model::{CitationForm, CiteElem, Destination, LinkElem, LinkTarget};
use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
/// Displays a quote alongside an optional attribution.
@@ -158,6 +159,7 @@ impl Show for Packed<QuoteElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body.clone();
let block = self.block(styles);
+ let html = TargetElem::target_in(styles).is_html();
if self.quotes(styles) == Smart::Custom(true) || !block {
let quotes = SmartQuotes::get(
@@ -171,50 +173,65 @@ impl Show for Packed<QuoteElem> {
let Depth(depth) = QuoteElem::depth_in(styles);
let double = depth % 2 == 0;
- // Add zero-width weak spacing to make the quotes "sticky".
- let hole = HElem::hole().pack();
+ if !html {
+ // Add zero-width weak spacing to make the quotes "sticky".
+ let hole = HElem::hole().pack();
+ realized = Content::sequence([hole.clone(), realized, hole]);
+ }
realized = Content::sequence([
TextElem::packed(quotes.open(double)),
- hole.clone(),
realized,
- hole,
TextElem::packed(quotes.close(double)),
])
.styled(QuoteElem::set_depth(Depth(1)));
}
- if block {
- realized = BlockElem::new()
- .with_body(Some(BlockBody::Content(realized)))
- .pack()
- .spanned(self.span());
-
- if let Some(attribution) = self.attribution(styles).as_ref() {
- let mut seq = vec![TextElem::packed('—'), SpaceElem::shared().clone()];
+ let attribution = self.attribution(styles);
- match attribution {
- Attribution::Content(content) => {
- seq.push(content.clone());
- }
- Attribution::Label(label) => {
- seq.push(
- CiteElem::new(*label)
- .with_form(Some(CitationForm::Prose))
- .pack()
- .spanned(self.span()),
- );
+ if block {
+ realized = if html {
+ let mut elem = HtmlElem::new(tag::blockquote).with_body(Some(realized));
+ if let Some(Attribution::Content(attribution)) = attribution {
+ if let Some(link) = attribution.to_packed::<LinkElem>() {
+ if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
+ elem = elem.with_attr(
+ HtmlAttr::constant("cite"),
+ url.clone().into_inner(),
+ );
+ }
}
}
-
- // Use v(0.9em, weak: true) bring the attribution closer to the
- // quote.
- let gap = Spacing::Rel(Em::new(0.9).into());
- let v = VElem::new(gap).with_weak(true).pack();
- realized += v + Content::sequence(seq).aligned(Alignment::END);
+ elem.pack()
+ } else {
+ BlockElem::new().with_body(Some(BlockBody::Content(realized))).pack()
+ }
+ .spanned(self.span());
+
+ if let Some(attribution) = attribution.as_ref() {
+ let attribution = match attribution {
+ Attribution::Content(content) => content.clone(),
+ Attribution::Label(label) => CiteElem::new(*label)
+ .with_form(Some(CitationForm::Prose))
+ .pack()
+ .spanned(self.span()),
+ };
+ let attribution =
+ [TextElem::packed('—'), SpaceElem::shared().clone(), attribution];
+
+ if !html {
+ // Use v(0.9em, weak: true) to bring the attribution closer
+ // to the quote.
+ let gap = Spacing::Rel(Em::new(0.9).into());
+ let v = VElem::new(gap).with_weak(true).pack();
+ realized += v;
+ }
+ realized += Content::sequence(attribution).aligned(Alignment::END);
}
- realized = PadElem::new(realized).pack();
- } else if let Some(Attribution::Label(label)) = self.attribution(styles) {
+ if !html {
+ realized = PadElem::new(realized).pack();
+ }
+ } else if let Some(Attribution::Label(label)) = attribution {
realized += SpaceElem::shared().clone()
+ CiteElem::new(*label).pack().spanned(self.span());
}
diff --git a/tests/ref/html/quote-attribution-link.html b/tests/ref/html/quote-attribution-link.html
new file mode 100644
index 00000000..4da8b47f
--- /dev/null
+++ b/tests/ref/html/quote-attribution-link.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ </head>
+ <body>
+ <blockquote cite="https://typst.app/home">
+ Compose papers faster
+ </blockquote>
+ <p>
+ — <a href="https://typst.app/home">typst.com</a>
+ </p>
+ </body>
+</html>
diff --git a/tests/ref/html/quote-nesting-html.html b/tests/ref/html/quote-nesting-html.html
new file mode 100644
index 00000000..c652bd97
--- /dev/null
+++ b/tests/ref/html/quote-nesting-html.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ </head>
+ <body>
+ <p>
+ When you said that “he surely meant that ‘she intended to say “I'm sorry”’”, I was quite confused.
+ </p>
+ </body>
+</html>
diff --git a/tests/ref/html/quote-plato.html b/tests/ref/html/quote-plato.html
new file mode 100644
index 00000000..fc052d10
--- /dev/null
+++ b/tests/ref/html/quote-plato.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ </head>
+ <body>
+ <blockquote>
+ … ἔοικα γοῦν τούτου γε σμικρῷ τινι αὐτῷ τούτῳ σοφώτερος εἶναι, ὅτι ἃ μὴ οἶδα οὐδὲ οἴομαι εἰδέναι.
+ </blockquote>
+ <p>
+ — Plato
+ </p>
+ <blockquote>
+ … I seem, then, in just this little thing to be wiser than this man at any rate, that what I do not know I do not think I know either.
+ </blockquote>
+ <p>
+ — from the Henry Cary literal translation of 1897
+ </p>
+ </body>
+</html>
diff --git a/tests/suite/model/quote.typ b/tests/suite/model/quote.typ
index 2c93f92c..d0dcc55d 100644
--- a/tests/suite/model/quote.typ
+++ b/tests/suite/model/quote.typ
@@ -84,3 +84,26 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
// With custom quotes.
#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
#quote[A #quote[nested] quote]
+
+--- quote-plato html ---
+#set quote(block: true)
+
+#quote(attribution: [Plato])[
+ ... ἔοικα γοῦν τούτου γε σμικρῷ τινι αὐτῷ τούτῳ σοφώτερος εἶναι, ὅτι
+ ἃ μὴ οἶδα οὐδὲ οἴομαι εἰδέναι.
+]
+#quote(attribution: [from the Henry Cary literal translation of 1897])[
+ ... I seem, then, in just this little thing to be wiser than this man at
+ any rate, that what I do not know I do not think I know either.
+]
+
+--- quote-nesting-html html ---
+When you said that #quote[he surely meant that #quote[she intended to say #quote[I'm sorry]]], I was quite confused.
+
+--- quote-attribution-link html ---
+#quote(
+ block: true,
+ attribution: link("https://typst.app/home")[typst.com]
+)[
+ Compose papers faster
+]