187 lines
6.6 KiB
Rust
187 lines
6.6 KiB
Rust
use super::{SmartquoteElem, SpaceElem, TextElem};
|
|
use crate::layout::{BlockElem, HElem, PadElem, Spacing, VElem};
|
|
use crate::meta::{CitationForm, CiteElem};
|
|
use crate::prelude::*;
|
|
|
|
/// Displays a quote alongside it's author.
|
|
///
|
|
/// # Example
|
|
/// ```example
|
|
/// Plato is often misquoted as the author of #quote[I know that I know
|
|
/// nothing], however, this is a derivation form his original quote:
|
|
/// #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.
|
|
/// ]
|
|
/// ```
|
|
///
|
|
/// By default block quotes are padded left and right by `{1em}`, alignment and
|
|
/// padding can be controlled with show rules:
|
|
/// ```example
|
|
/// #set quote(block: true)
|
|
/// #show quote: set align(center)
|
|
/// #show quote: set pad(x: 5em)
|
|
///
|
|
/// #quote[
|
|
/// You cannot pass... I am a servant of the Secret Fire, wielder of the
|
|
/// flame of Anor. You cannot pass. The dark fire will not avail you,
|
|
/// flame of Udûn. Go back to the Shadow! You cannot pass.
|
|
/// ]
|
|
/// ```
|
|
#[elem(Finalize, Show)]
|
|
pub struct QuoteElem {
|
|
/// Whether this is a block quote.
|
|
///
|
|
/// ```example
|
|
/// #quote(attribution: [René Descartes])[cogito, ergo sum]
|
|
///
|
|
/// #set quote(block: true)
|
|
/// #quote(attribution: [JFK])[Ich bin ein Berliner.]
|
|
/// ```
|
|
block: bool,
|
|
|
|
/// Whether double quotes should be added around this quote.
|
|
///
|
|
/// The double quotes used are inferred from the `quotes` property on
|
|
/// [smartquote]($smartquote), which is affected by the `lang` property on
|
|
/// [text]($text).
|
|
///
|
|
/// - `{true}`: Wrap this quote in double quotes.
|
|
/// - `{false}`: Do not wrap this quote in double quotes.
|
|
/// - `{auto}`: Infer whether to wrap this quote in double quotes based on
|
|
/// the `block` property. If `block` is `{false}`, double quotes are
|
|
/// automatically added.
|
|
///
|
|
/// ```example
|
|
/// #set text(lang: "de")
|
|
/// #quote[Ich bin ein Berliner.]
|
|
///
|
|
/// #set text(lang: "en")
|
|
/// #set quote(quotes: true)
|
|
/// #quote(block: true)[I am a Berliner.]
|
|
/// ```
|
|
quotes: Smart<bool>,
|
|
|
|
/// The attribution of this quote, usually the author or source. Can be a
|
|
/// label pointing to a bibliography entry or any content. By default only
|
|
/// displayed for block quotes, but can be changed using a `{show}` rule.
|
|
///
|
|
/// ```example
|
|
/// #quote(attribution: [René Descartes])[cogito, ergo sum] \
|
|
///
|
|
/// #show quote.where(block: false): it => [
|
|
/// "#it.body"
|
|
/// #if it.attribution != none [(#it.attribution)]
|
|
/// ]
|
|
/// #quote(attribution: link("https://typst.app/home")[typst.com])[
|
|
/// Compose papers faster
|
|
/// ]
|
|
///
|
|
/// #set quote(block: true)
|
|
/// #quote(attribution: <tolkien54>)[
|
|
/// You cannot pass... I am a servant of the Secret Fire, wielder of the
|
|
/// flame of Anor. You cannot pass. The dark fire will not avail you,
|
|
/// flame of Udûn. Go back to the Shadow! You cannot pass.
|
|
/// ]
|
|
/// #bibliography("works.bib", style: "apa")
|
|
/// ```
|
|
///
|
|
/// Note that bilbiography styles which do not include the author in the
|
|
/// citation (label, numeric and notes) currently produce attributions such
|
|
/// as `[---#super[1]]` or `[--- [1]]`, this will be fixed soon with CSL
|
|
/// support. In the mean time you can simply cite yourself:
|
|
/// ```example
|
|
/// #set quote(block: true)
|
|
/// #quote(attribution: [J. R. R. Tolkien, @tolkien54])[In a hole there lived a hobbit.]
|
|
///
|
|
/// #bibliography("works.bib")
|
|
/// ```
|
|
#[borrowed]
|
|
attribution: Option<Attribution>,
|
|
|
|
/// The quote.
|
|
#[required]
|
|
body: Content,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
pub enum Attribution {
|
|
Content(Content),
|
|
Label(Label),
|
|
}
|
|
|
|
cast! {
|
|
Attribution,
|
|
self => match self {
|
|
Self::Content(content) => content.into_value(),
|
|
Self::Label(label) => label.into_value(),
|
|
},
|
|
content: Content => Self::Content(content),
|
|
label: Label => Self::Label(label),
|
|
}
|
|
|
|
impl Show for QuoteElem {
|
|
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
|
let mut realized = self.body().clone();
|
|
let block = self.block(styles);
|
|
|
|
if self.quotes(styles) == Smart::Custom(true) || !block {
|
|
// Add zero-width weak spacing to make the quotes "sticky".
|
|
let hole = HElem::hole().pack();
|
|
let quote = SmartquoteElem::new().with_double(true).pack();
|
|
realized =
|
|
Content::sequence([quote.clone(), hole.clone(), realized, hole, quote]);
|
|
}
|
|
|
|
if block {
|
|
realized = BlockElem::new().with_body(Some(realized)).pack();
|
|
|
|
if let Some(attribution) = self.attribution(styles).as_ref() {
|
|
let mut seq = vec![TextElem::packed('—'), SpaceElem::new().pack()];
|
|
|
|
match attribution {
|
|
Attribution::Content(content) => {
|
|
seq.push(content.clone());
|
|
}
|
|
Attribution::Label(label) => {
|
|
seq.push(
|
|
CiteElem::new(*label)
|
|
.with_form(Some(CitationForm::Prose))
|
|
.pack(),
|
|
);
|
|
}
|
|
}
|
|
|
|
// Use v(0.9em, weak: true) bring the attribution closer to the
|
|
// quote.
|
|
let weak_v = VElem::weak(Spacing::Rel(Em::new(0.9).into())).pack();
|
|
realized += weak_v + Content::sequence(seq).aligned(Align::END);
|
|
}
|
|
|
|
realized = PadElem::new(realized).pack();
|
|
} else if let Some(Attribution::Label(label)) = self.attribution(styles) {
|
|
realized += SpaceElem::new().pack() + CiteElem::new(*label).pack();
|
|
}
|
|
|
|
Ok(realized)
|
|
}
|
|
}
|
|
|
|
impl Finalize for QuoteElem {
|
|
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
|
|
let x = Em::new(1.0).into();
|
|
let above = Em::new(2.4).into();
|
|
let below = Em::new(1.8).into();
|
|
realized
|
|
.styled(PadElem::set_left(x))
|
|
.styled(PadElem::set_right(x))
|
|
.styled(BlockElem::set_above(VElem::block_around(above)))
|
|
.styled(BlockElem::set_below(VElem::block_around(below)))
|
|
}
|
|
}
|