Support quotes in HTML output (#5673)

This commit is contained in:
Michael Färber 2025-01-23 13:21:34 +01:00 committed by GitHub
parent e61cd6fb9e
commit b3fb6c2326
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 120 additions and 32 deletions

View File

@ -2,13 +2,14 @@ use crate::diag::SourceResult;
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart, 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::introspection::Locatable;
use crate::layout::{ use crate::layout::{
Alignment, BlockBody, BlockElem, Em, HElem, PadElem, Spacing, VElem, 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}; use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
/// Displays a quote alongside an optional attribution. /// 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> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body.clone(); let mut realized = self.body.clone();
let block = self.block(styles); let block = self.block(styles);
let html = TargetElem::target_in(styles).is_html();
if self.quotes(styles) == Smart::Custom(true) || !block { if self.quotes(styles) == Smart::Custom(true) || !block {
let quotes = SmartQuotes::get( let quotes = SmartQuotes::get(
@ -171,50 +173,65 @@ impl Show for Packed<QuoteElem> {
let Depth(depth) = QuoteElem::depth_in(styles); let Depth(depth) = QuoteElem::depth_in(styles);
let double = depth % 2 == 0; let double = depth % 2 == 0;
if !html {
// Add zero-width weak spacing to make the quotes "sticky". // Add zero-width weak spacing to make the quotes "sticky".
let hole = HElem::hole().pack(); let hole = HElem::hole().pack();
realized = Content::sequence([hole.clone(), realized, hole]);
}
realized = Content::sequence([ realized = Content::sequence([
TextElem::packed(quotes.open(double)), TextElem::packed(quotes.open(double)),
hole.clone(),
realized, realized,
hole,
TextElem::packed(quotes.close(double)), TextElem::packed(quotes.close(double)),
]) ])
.styled(QuoteElem::set_depth(Depth(1))); .styled(QuoteElem::set_depth(Depth(1)));
} }
let attribution = self.attribution(styles);
if block { if block {
realized = BlockElem::new() realized = if html {
.with_body(Some(BlockBody::Content(realized))) let mut elem = HtmlElem::new(tag::blockquote).with_body(Some(realized));
.pack() if let Some(Attribution::Content(attribution)) = attribution {
.spanned(self.span()); if let Some(link) = attribution.to_packed::<LinkElem>() {
if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
if let Some(attribution) = self.attribution(styles).as_ref() { elem = elem.with_attr(
let mut seq = vec![TextElem::packed('—'), SpaceElem::shared().clone()]; HtmlAttr::constant("cite"),
url.clone().into_inner(),
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()),
); );
} }
} }
}
elem.pack()
} else {
BlockElem::new().with_body(Some(BlockBody::Content(realized))).pack()
}
.spanned(self.span());
// Use v(0.9em, weak: true) bring the attribution closer to the if let Some(attribution) = attribution.as_ref() {
// quote. 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 gap = Spacing::Rel(Em::new(0.9).into());
let v = VElem::new(gap).with_weak(true).pack(); let v = VElem::new(gap).with_weak(true).pack();
realized += v + Content::sequence(seq).aligned(Alignment::END); realized += v;
}
realized += Content::sequence(attribution).aligned(Alignment::END);
} }
if !html {
realized = PadElem::new(realized).pack(); realized = PadElem::new(realized).pack();
} else if let Some(Attribution::Label(label)) = self.attribution(styles) { }
} else if let Some(Attribution::Label(label)) = attribution {
realized += SpaceElem::shared().clone() realized += SpaceElem::shared().clone()
+ CiteElem::new(*label).pack().spanned(self.span()); + CiteElem::new(*label).pack().spanned(self.span());
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -84,3 +84,26 @@ And I quote: #quote(attribution: [René Descartes])[cogito, ergo sum].
// With custom quotes. // With custom quotes.
#set smartquote(quotes: (single: ("<", ">"), double: ("(", ")"))) #set smartquote(quotes: (single: ("<", ">"), double: ("(", ")")))
#quote[A #quote[nested] quote] #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
]