summaryrefslogtreecommitdiff
path: root/crates/typst-library
diff options
context:
space:
mode:
Diffstat (limited to 'crates/typst-library')
-rw-r--r--crates/typst-library/src/foundations/styles.rs101
-rw-r--r--crates/typst-library/src/layout/container.rs10
-rw-r--r--crates/typst-library/src/math/equation.rs4
-rw-r--r--crates/typst-library/src/model/bibliography.rs44
-rw-r--r--crates/typst-library/src/model/enum.rs15
-rw-r--r--crates/typst-library/src/model/figure.rs33
-rw-r--r--crates/typst-library/src/model/footnote.rs6
-rw-r--r--crates/typst-library/src/model/list.rs13
-rw-r--r--crates/typst-library/src/model/outline.rs1
-rw-r--r--crates/typst-library/src/model/par.rs110
-rw-r--r--crates/typst-library/src/model/quote.rs19
-rw-r--r--crates/typst-library/src/model/terms.rs22
-rw-r--r--crates/typst-library/src/routines.rs76
13 files changed, 226 insertions, 228 deletions
diff --git a/crates/typst-library/src/foundations/styles.rs b/crates/typst-library/src/foundations/styles.rs
index 37094dcd..98380330 100644
--- a/crates/typst-library/src/foundations/styles.rs
+++ b/crates/typst-library/src/foundations/styles.rs
@@ -776,107 +776,6 @@ impl<'a> Iterator for Links<'a> {
}
}
-/// A sequence of elements with associated styles.
-#[derive(Clone, PartialEq, Hash)]
-pub struct StyleVec {
- /// The elements themselves.
- elements: EcoVec<Content>,
- /// A run-length encoded list of style lists.
- ///
- /// Each element is a (styles, count) pair. Any elements whose
- /// style falls after the end of this list is considered to
- /// have an empty style list.
- styles: EcoVec<(Styles, usize)>,
-}
-
-impl StyleVec {
- /// Create a style vector from an unstyled vector content.
- pub fn wrap(elements: EcoVec<Content>) -> Self {
- Self { elements, styles: EcoVec::new() }
- }
-
- /// Create a `StyleVec` from a list of content with style chains.
- pub fn create<'a>(buf: &[(&'a Content, StyleChain<'a>)]) -> (Self, StyleChain<'a>) {
- let trunk = StyleChain::trunk(buf.iter().map(|&(_, s)| s)).unwrap_or_default();
- let depth = trunk.links().count();
-
- let mut elements = EcoVec::with_capacity(buf.len());
- let mut styles = EcoVec::<(Styles, usize)>::new();
- let mut last: Option<(StyleChain<'a>, usize)> = None;
-
- for &(element, chain) in buf {
- elements.push(element.clone());
-
- if let Some((prev, run)) = &mut last {
- if chain == *prev {
- *run += 1;
- } else {
- styles.push((prev.suffix(depth), *run));
- last = Some((chain, 1));
- }
- } else {
- last = Some((chain, 1));
- }
- }
-
- if let Some((last, run)) = last {
- let skippable = styles.is_empty() && last == trunk;
- if !skippable {
- styles.push((last.suffix(depth), run));
- }
- }
-
- (StyleVec { elements, styles }, trunk)
- }
-
- /// Whether there are no elements.
- pub fn is_empty(&self) -> bool {
- self.elements.is_empty()
- }
-
- /// The number of elements.
- pub fn len(&self) -> usize {
- self.elements.len()
- }
-
- /// Iterate over the contained content and style chains.
- pub fn iter<'a>(
- &'a self,
- outer: &'a StyleChain<'_>,
- ) -> impl Iterator<Item = (&'a Content, StyleChain<'a>)> {
- static EMPTY: Styles = Styles::new();
- self.elements
- .iter()
- .zip(
- self.styles
- .iter()
- .flat_map(|(local, count)| std::iter::repeat(local).take(*count))
- .chain(std::iter::repeat(&EMPTY)),
- )
- .map(|(element, local)| (element, outer.chain(local)))
- }
-
- /// Get a style property, but only if it is the same for all children of the
- /// style vector.
- pub fn shared_get<T: PartialEq>(
- &self,
- styles: StyleChain<'_>,
- getter: fn(StyleChain) -> T,
- ) -> Option<T> {
- let value = getter(styles);
- self.styles
- .iter()
- .all(|(local, _)| getter(styles.chain(local)) == value)
- .then_some(value)
- }
-}
-
-impl Debug for StyleVec {
- fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
- f.debug_list().entries(&self.elements).finish()
- }
-}
-
/// A property that is resolved with other properties from the style chain.
pub trait Resolve {
/// The type of the resolved output.
diff --git a/crates/typst-library/src/layout/container.rs b/crates/typst-library/src/layout/container.rs
index c8c74269..725f177b 100644
--- a/crates/typst-library/src/layout/container.rs
+++ b/crates/typst-library/src/layout/container.rs
@@ -14,9 +14,9 @@ use crate::visualize::{Paint, Stroke};
/// An inline-level container that sizes content.
///
/// All elements except inline math, text, and boxes are block-level and cannot
-/// occur inside of a paragraph. The box function can be used to integrate such
-/// elements into a paragraph. Boxes take the size of their contents by default
-/// but can also be sized explicitly.
+/// occur inside of a [paragraph]($par). The box function can be used to
+/// integrate such elements into a paragraph. Boxes take the size of their
+/// contents by default but can also be sized explicitly.
///
/// # Example
/// ```example
@@ -184,6 +184,10 @@ pub enum InlineItem {
/// Such a container can be used to separate content, size it, and give it a
/// background or border.
///
+/// Blocks are also the primary way to control whether text becomes part of a
+/// paragraph or not. See [the paragraph documentation]($par/#what-becomes-a-paragraph)
+/// for more details.
+///
/// # Examples
/// With a block, you can give a background to content while still allowing it
/// to break across multiple pages.
diff --git a/crates/typst-library/src/math/equation.rs b/crates/typst-library/src/math/equation.rs
index 1e346280..32be216a 100644
--- a/crates/typst-library/src/math/equation.rs
+++ b/crates/typst-library/src/math/equation.rs
@@ -20,7 +20,9 @@ use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
/// A mathematical equation.
///
-/// Can be displayed inline with text or as a separate block.
+/// Can be displayed inline with text or as a separate block. An equation
+/// becomes block-level through the presence of at least one space after the
+/// opening dollar sign and one space before the closing dollar sign.
///
/// # Example
/// ```example
diff --git a/crates/typst-library/src/model/bibliography.rs b/crates/typst-library/src/model/bibliography.rs
index 762a97fd..a391e580 100644
--- a/crates/typst-library/src/model/bibliography.rs
+++ b/crates/typst-library/src/model/bibliography.rs
@@ -17,7 +17,7 @@ use hayagriva::{
use indexmap::IndexMap;
use smallvec::{smallvec, SmallVec};
use typst_syntax::{Span, Spanned};
-use typst_utils::{ManuallyHash, NonZeroExt, PicoStr};
+use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
use crate::diag::{bail, error, At, FileError, HintedStrResult, SourceResult, StrResult};
use crate::engine::Engine;
@@ -29,7 +29,7 @@ use crate::foundations::{
use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
- Sizing, TrackSizings, VElem,
+ Sides, Sizing, TrackSizings,
};
use crate::loading::{DataSource, Load};
use crate::model::{
@@ -206,19 +206,20 @@ impl Show for Packed<BibliographyElem> {
const COLUMN_GUTTER: Em = Em::new(0.65);
const INDENT: Em = Em::new(1.5);
+ let span = self.span();
+
let mut seq = vec![];
if let Some(title) = self.title(styles).unwrap_or_else(|| {
- Some(TextElem::packed(Self::local_name_in(styles)).spanned(self.span()))
+ Some(TextElem::packed(Self::local_name_in(styles)).spanned(span))
}) {
seq.push(
HeadingElem::new(title)
.with_depth(NonZeroUsize::ONE)
.pack()
- .spanned(self.span()),
+ .spanned(span),
);
}
- let span = self.span();
let works = Works::generate(engine).at(span)?;
let references = works
.references
@@ -226,10 +227,9 @@ impl Show for Packed<BibliographyElem> {
.ok_or("CSL style is not suitable for bibliographies")
.at(span)?;
- let row_gutter = ParElem::spacing_in(styles);
- let row_gutter_elem = VElem::new(row_gutter.into()).with_weak(true).pack();
-
if references.iter().any(|(prefix, _)| prefix.is_some()) {
+ let row_gutter = ParElem::spacing_in(styles);
+
let mut cells = vec![];
for (prefix, reference) in references {
cells.push(GridChild::Item(GridItem::Cell(
@@ -246,23 +246,27 @@ impl Show for Packed<BibliographyElem> {
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
.with_row_gutter(TrackSizings(smallvec![row_gutter.into()]))
.pack()
- .spanned(self.span()),
+ .spanned(span),
);
} else {
- for (i, (_, reference)) in references.iter().enumerate() {
- if i > 0 {
- seq.push(row_gutter_elem.clone());
- }
- seq.push(reference.clone());
- }
- }
+ for (_, reference) in references {
+ let realized = reference.clone();
+ let block = if works.hanging_indent {
+ let body = HElem::new((-INDENT).into()).pack() + realized;
+ let inset = Sides::default()
+ .with(TextElem::dir_in(styles).start(), Some(INDENT.into()));
+ BlockElem::new()
+ .with_body(Some(BlockBody::Content(body)))
+ .with_inset(inset)
+ } else {
+ BlockElem::new().with_body(Some(BlockBody::Content(realized)))
+ };
- let mut content = Content::sequence(seq);
- if works.hanging_indent {
- content = content.styled(ParElem::set_hanging_indent(INDENT.into()));
+ seq.push(block.pack().spanned(span));
+ }
}
- Ok(content)
+ Ok(Content::sequence(seq))
}
}
diff --git a/crates/typst-library/src/model/enum.rs b/crates/typst-library/src/model/enum.rs
index 4dc834ab..a4126e72 100644
--- a/crates/typst-library/src/model/enum.rs
+++ b/crates/typst-library/src/model/enum.rs
@@ -11,7 +11,9 @@ use crate::foundations::{
};
use crate::html::{attr, tag, HtmlElem};
use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
-use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern, ParElem};
+use crate::model::{
+ ListItemLike, ListLike, Numbering, NumberingPattern, ParElem, ParbreakElem,
+};
/// A numbered list.
///
@@ -226,6 +228,8 @@ impl EnumElem {
impl Show for Packed<EnumElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let tight = self.tight(styles);
+
if TargetElem::target_in(styles).is_html() {
let mut elem = HtmlElem::new(tag::ol);
if self.reversed(styles) {
@@ -239,7 +243,12 @@ impl Show for Packed<EnumElem> {
if let Some(nr) = item.number(styles) {
li = li.with_attr(attr::value, eco_format!("{nr}"));
}
- li.with_body(Some(item.body.clone())).pack().spanned(item.span())
+ // Text in wide enums shall always turn into paragraphs.
+ let mut body = item.body.clone();
+ if !tight {
+ body += ParbreakElem::shared();
+ }
+ li.with_body(Some(body)).pack().spanned(item.span())
}));
return Ok(elem.with_body(Some(body)).pack().spanned(self.span()));
}
@@ -249,7 +258,7 @@ impl Show for Packed<EnumElem> {
.pack()
.spanned(self.span());
- if self.tight(styles) {
+ if tight {
let leading = ParElem::leading_in(styles);
let spacing =
VElem::new(leading.into()).with_weak(true).with_attach(true).pack();
diff --git a/crates/typst-library/src/model/figure.rs b/crates/typst-library/src/model/figure.rs
index ce7460c9..78a79a8e 100644
--- a/crates/typst-library/src/model/figure.rs
+++ b/crates/typst-library/src/model/figure.rs
@@ -19,7 +19,9 @@ use crate::layout::{
AlignElem, Alignment, BlockBody, BlockElem, Em, HAlignment, Length, OuterVAlignment,
PlaceElem, PlacementScope, VAlignment, VElem,
};
-use crate::model::{Numbering, NumberingPattern, Outlinable, Refable, Supplement};
+use crate::model::{
+ Numbering, NumberingPattern, Outlinable, ParbreakElem, Refable, Supplement,
+};
use crate::text::{Lang, Region, TextElem};
use crate::visualize::ImageElem;
@@ -328,6 +330,7 @@ impl Synthesize for Packed<FigureElem> {
impl Show for Packed<FigureElem> {
#[typst_macros::time(name = "figure", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let span = self.span();
let target = TargetElem::target_in(styles);
let mut realized = self.body.clone();
@@ -341,24 +344,27 @@ impl Show for Packed<FigureElem> {
seq.push(first);
if !target.is_html() {
let v = VElem::new(self.gap(styles).into()).with_weak(true);
- seq.push(v.pack().spanned(self.span()))
+ seq.push(v.pack().spanned(span))
}
seq.push(second);
realized = Content::sequence(seq)
}
+ // Ensure that the body is considered a paragraph.
+ realized += ParbreakElem::shared().clone().spanned(span);
+
if target.is_html() {
return Ok(HtmlElem::new(tag::figure)
.with_body(Some(realized))
.pack()
- .spanned(self.span()));
+ .spanned(span));
}
// Wrap the contents in a block.
realized = BlockElem::new()
.with_body(Some(BlockBody::Content(realized)))
.pack()
- .spanned(self.span());
+ .spanned(span);
// Wrap in a float.
if let Some(align) = self.placement(styles) {
@@ -367,10 +373,10 @@ impl Show for Packed<FigureElem> {
.with_scope(self.scope(styles))
.with_float(true)
.pack()
- .spanned(self.span());
+ .spanned(span);
} else if self.scope(styles) == PlacementScope::Parent {
bail!(
- self.span(),
+ span,
"parent-scoped placement is only available for floating figures";
hint: "you can enable floating placement with `figure(placement: auto, ..)`"
);
@@ -604,14 +610,17 @@ impl Show for Packed<FigureCaption> {
realized = supplement + numbers + self.get_separator(styles) + realized;
}
- if TargetElem::target_in(styles).is_html() {
- return Ok(HtmlElem::new(tag::figcaption)
+ Ok(if TargetElem::target_in(styles).is_html() {
+ HtmlElem::new(tag::figcaption)
.with_body(Some(realized))
.pack()
- .spanned(self.span()));
- }
-
- Ok(realized)
+ .spanned(self.span())
+ } else {
+ BlockElem::new()
+ .with_body(Some(BlockBody::Content(realized)))
+ .pack()
+ .spanned(self.span())
+ })
}
}
diff --git a/crates/typst-library/src/model/footnote.rs b/crates/typst-library/src/model/footnote.rs
index f3b2a19e..dfa3933b 100644
--- a/crates/typst-library/src/model/footnote.rs
+++ b/crates/typst-library/src/model/footnote.rs
@@ -310,11 +310,9 @@ impl Show for Packed<FootnoteEntry> {
impl ShowSet for Packed<FootnoteEntry> {
fn show_set(&self, _: StyleChain) -> Styles {
- let text_size = Em::new(0.85);
- let leading = Em::new(0.5);
let mut out = Styles::new();
- out.set(ParElem::set_leading(leading.into()));
- out.set(TextElem::set_size(TextSize(text_size.into())));
+ out.set(ParElem::set_leading(Em::new(0.5).into()));
+ out.set(TextElem::set_size(TextSize(Em::new(0.85).into())));
out
}
}
diff --git a/crates/typst-library/src/model/list.rs b/crates/typst-library/src/model/list.rs
index 1e369d54..d93ec917 100644
--- a/crates/typst-library/src/model/list.rs
+++ b/crates/typst-library/src/model/list.rs
@@ -8,7 +8,7 @@ use crate::foundations::{
};
use crate::html::{tag, HtmlElem};
use crate::layout::{BlockElem, Em, Length, VElem};
-use crate::model::ParElem;
+use crate::model::{ParElem, ParbreakElem};
use crate::text::TextElem;
/// A bullet list.
@@ -141,11 +141,18 @@ impl ListElem {
impl Show for Packed<ListElem> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
+ let tight = self.tight(styles);
+
if TargetElem::target_in(styles).is_html() {
return Ok(HtmlElem::new(tag::ul)
.with_body(Some(Content::sequence(self.children.iter().map(|item| {
+ // Text in wide lists shall always turn into paragraphs.
+ let mut body = item.body.clone();
+ if !tight {
+ body += ParbreakElem::shared();
+ }
HtmlElem::new(tag::li)
- .with_body(Some(item.body.clone()))
+ .with_body(Some(body))
.pack()
.spanned(item.span())
}))))
@@ -158,7 +165,7 @@ impl Show for Packed<ListElem> {
.pack()
.spanned(self.span());
- if self.tight(styles) {
+ if tight {
let leading = ParElem::leading_in(styles);
let spacing =
VElem::new(leading.into()).with_weak(true).with_attach(true).pack();
diff --git a/crates/typst-library/src/model/outline.rs b/crates/typst-library/src/model/outline.rs
index 0db056e4..1214f2b0 100644
--- a/crates/typst-library/src/model/outline.rs
+++ b/crates/typst-library/src/model/outline.rs
@@ -297,7 +297,6 @@ impl ShowSet for Packed<OutlineElem> {
let mut out = Styles::new();
out.set(HeadingElem::set_outlined(false));
out.set(HeadingElem::set_numbering(None));
- out.set(ParElem::set_first_line_indent(Em::new(0.0).into()));
out.set(ParElem::set_justify(false));
out.set(BlockElem::set_above(Smart::Custom(ParElem::leading_in(styles).into())));
// Makes the outline itself available to its entries. Should be
diff --git a/crates/typst-library/src/model/par.rs b/crates/typst-library/src/model/par.rs
index 8b82abdf..0bdbe4ea 100644
--- a/crates/typst-library/src/model/par.rs
+++ b/crates/typst-library/src/model/par.rs
@@ -1,22 +1,78 @@
-use std::fmt::{self, Debug, Formatter};
-
use typst_utils::singleton;
use crate::diag::{bail, SourceResult};
use crate::engine::Engine;
use crate::foundations::{
- elem, scope, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart,
- StyleVec, Unlabellable,
+ elem, scope, Args, Cast, Construct, Content, NativeElement, Packed, Smart,
+ Unlabellable,
};
use crate::introspection::{Count, CounterUpdate, Locatable};
use crate::layout::{Em, HAlignment, Length, OuterHAlignment};
use crate::model::Numbering;
-/// Arranges text, spacing and inline-level elements into a paragraph.
+/// A logical subdivison of textual content.
+///
+/// Typst automatically collects _inline-level_ elements into paragraphs.
+/// Inline-level elements include [text], [horizontal spacing]($h),
+/// [boxes]($box), and [inline equations]($math.equation).
+///
+/// To separate paragraphs, use a blank line (or an explicit [`parbreak`]).
+/// Paragraphs are also automatically interrupted by any block-level element
+/// (like [`block`], [`place`], or anything that shows itself as one of these).
+///
+/// The `par` element is primarily used in set rules to affect paragraph
+/// properties, but it can also be used to explicitly display its argument as a
+/// paragraph of its own. Then, the paragraph's body may not contain any
+/// block-level content.
+///
+/// # Boxes and blocks
+/// As explained above, usually paragraphs only contain inline-level content.
+/// However, you can integrate any kind of block-level content into a paragraph
+/// by wrapping it in a [`box`].
+///
+/// Conversely, you can separate inline-level content from a paragraph by
+/// wrapping it in a [`block`]. In this case, it will not become part of any
+/// paragraph at all. Read the following section for an explanation of why that
+/// matters and how it differs from just adding paragraph breaks around the
+/// content.
+///
+/// # What becomes a paragraph?
+/// When you add inline-level content to your document, Typst will automatically
+/// wrap it in paragraphs. However, a typical document also contains some text
+/// that is not semantically part of a paragraph, for example in a heading or
+/// caption.
+///
+/// The rules for when Typst wraps inline-level content in a paragraph are as
+/// follows:
+///
+/// - All text at the root of a document is wrapped in paragraphs.
+///
+/// - Text in a container (like a `block`) is only wrapped in a paragraph if the
+/// container holds any block-level content. If all of the contents are
+/// inline-level, no paragraph is created.
+///
+/// In the laid-out document, it's not immediately visible whether text became
+/// part of a paragraph. However, it is still important for various reasons:
+///
+/// - Certain paragraph styling like `first-line-indent` will only apply to
+/// proper paragraphs, not any text. Similarly, `par` show rules of course
+/// only trigger on paragraphs.
+///
+/// - A proper distinction between paragraphs and other text helps people who
+/// rely on assistive technologies (such as screen readers) navigate and
+/// understand the document properly. Currently, this only applies to HTML
+/// export since Typst does not yet output accessible PDFs, but support for
+/// this is planned for the near future.
+///
+/// - HTML export will generate a `<p>` tag only for paragraphs.
///
-/// Although this function is primarily used in set rules to affect paragraph
-/// properties, it can also be used to explicitly render its argument onto a
-/// paragraph of its own.
+/// When creating custom reusable components, you can and should take charge
+/// over whether Typst creates paragraphs. By wrapping text in a [`block`]
+/// instead of just adding paragraph breaks around it, you can force the absence
+/// of a paragraph. Conversely, by adding a [`parbreak`] after some content in a
+/// container, you can force it to become a paragraph even if it's just one
+/// word. This is, for example, what [non-`tight`]($list.tight) lists do to
+/// force their items to become paragraphs.
///
/// # Example
/// ```example
@@ -37,7 +93,7 @@ use crate::model::Numbering;
/// let $a$ be the smallest of the
/// three integers. Then, we ...
/// ```
-#[elem(scope, title = "Paragraph", Debug, Construct)]
+#[elem(scope, title = "Paragraph")]
pub struct ParElem {
/// The spacing between lines.
///
@@ -53,7 +109,6 @@ pub struct ParElem {
/// distribution of the top- and bottom-edge values affects the bounds of
/// the first and last line.
#[resolve]
- #[ghost]
#[default(Em::new(0.65).into())]
pub leading: Length,
@@ -68,7 +123,6 @@ pub struct ParElem {
/// takes precedence over the paragraph spacing. Headings, for instance,
/// reduce the spacing below them by default for a better look.
#[resolve]
- #[ghost]
#[default(Em::new(1.2).into())]
pub spacing: Length,
@@ -81,7 +135,6 @@ pub struct ParElem {
/// Note that the current [alignment]($align.alignment) still has an effect
/// on the placement of the last line except if it ends with a
/// [justified line break]($linebreak.justify).
- #[ghost]
#[default(false)]
pub justify: bool,
@@ -106,7 +159,6 @@ pub struct ParElem {
/// challenging to break in a visually
/// pleasing way.
/// ```
- #[ghost]
pub linebreaks: Smart<Linebreaks>,
/// The indent the first line of a paragraph should have.
@@ -118,23 +170,15 @@ pub struct ParElem {
/// space between paragraphs or by indented first lines. Consider reducing
/// the [paragraph spacing]($block.spacing) to the [`leading`]($par.leading)
/// when using this property (e.g. using `[#set par(spacing: 0.65em)]`).
- #[ghost]
pub first_line_indent: Length,
- /// The indent all but the first line of a paragraph should have.
- #[ghost]
+ /// The indent that all but the first line of a paragraph should have.
#[resolve]
pub hanging_indent: Length,
/// The contents of the paragraph.
- #[external]
#[required]
pub body: Content,
-
- /// The paragraph's children.
- #[internal]
- #[variadic]
- pub children: StyleVec,
}
#[scope]
@@ -143,28 +187,6 @@ impl ParElem {
type ParLine;
}
-impl Construct for ParElem {
- fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult<Content> {
- // The paragraph constructor is special: It doesn't create a paragraph
- // element. Instead, it just ensures that the passed content lives in a
- // separate paragraph and styles it.
- let styles = Self::set(engine, args)?;
- let body = args.expect::<Content>("body")?;
- Ok(Content::sequence([
- ParbreakElem::shared().clone(),
- body.styled_with_map(styles),
- ParbreakElem::shared().clone(),
- ]))
- }
-}
-
-impl Debug for ParElem {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- write!(f, "Par ")?;
- self.children.fmt(f)
- }
-}
-
/// How to determine line breaks in a paragraph.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
pub enum Linebreaks {
diff --git a/crates/typst-library/src/model/quote.rs b/crates/typst-library/src/model/quote.rs
index 79e9b4e3..919ab12c 100644
--- a/crates/typst-library/src/model/quote.rs
+++ b/crates/typst-library/src/model/quote.rs
@@ -212,17 +212,24 @@ impl Show for Packed<QuoteElem> {
.pack()
.spanned(self.span()),
};
- let attribution =
- [TextElem::packed('—'), SpaceElem::shared().clone(), attribution];
+ let attribution = Content::sequence([
+ TextElem::packed('—'),
+ SpaceElem::shared().clone(),
+ attribution,
+ ]);
- if !html {
- // Use v(0.9em, weak: true) to bring the attribution closer
- // to the quote.
+ if html {
+ realized += attribution;
+ } else {
+ // Bring the attribution a bit 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 += BlockElem::new()
+ .with_body(Some(BlockBody::Content(attribution)))
+ .pack()
+ .aligned(Alignment::END);
}
- realized += Content::sequence(attribution).aligned(Alignment::END);
}
if !html {
diff --git a/crates/typst-library/src/model/terms.rs b/crates/typst-library/src/model/terms.rs
index c91eeb17..9a2ed6aa 100644
--- a/crates/typst-library/src/model/terms.rs
+++ b/crates/typst-library/src/model/terms.rs
@@ -8,7 +8,7 @@ use crate::foundations::{
};
use crate::html::{tag, HtmlElem};
use crate::layout::{Em, HElem, Length, Sides, StackChild, StackElem, VElem};
-use crate::model::{ListItemLike, ListLike, ParElem};
+use crate::model::{ListItemLike, ListLike, ParElem, ParbreakElem};
use crate::text::TextElem;
/// A list of terms and their descriptions.
@@ -116,17 +116,25 @@ impl TermsElem {
impl Show for Packed<TermsElem> {
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let span = self.span();
+ let tight = self.tight(styles);
+
if TargetElem::target_in(styles).is_html() {
return Ok(HtmlElem::new(tag::dl)
.with_body(Some(Content::sequence(self.children.iter().flat_map(
|item| {
+ // Text in wide term lists shall always turn into paragraphs.
+ let mut description = item.description.clone();
+ if !tight {
+ description += ParbreakElem::shared();
+ }
+
[
HtmlElem::new(tag::dt)
.with_body(Some(item.term.clone()))
.pack()
.spanned(item.term.span()),
HtmlElem::new(tag::dd)
- .with_body(Some(item.description.clone()))
+ .with_body(Some(description))
.pack()
.spanned(item.description.span()),
]
@@ -139,7 +147,7 @@ impl Show for Packed<TermsElem> {
let indent = self.indent(styles);
let hanging_indent = self.hanging_indent(styles);
let gutter = self.spacing(styles).unwrap_or_else(|| {
- if self.tight(styles) {
+ if tight {
ParElem::leading_in(styles).into()
} else {
ParElem::spacing_in(styles).into()
@@ -157,6 +165,12 @@ impl Show for Packed<TermsElem> {
seq.push(child.term.clone().strong());
seq.push((*separator).clone());
seq.push(child.description.clone());
+
+ // Text in wide term lists shall always turn into paragraphs.
+ if !tight {
+ seq.push(ParbreakElem::shared().clone());
+ }
+
children.push(StackChild::Block(Content::sequence(seq)));
}
@@ -168,7 +182,7 @@ impl Show for Packed<TermsElem> {
.spanned(span)
.padded(padding);
- if self.tight(styles) {
+ if tight {
let leading = ParElem::leading_in(styles);
let spacing = VElem::new(leading.into())
.with_weak(true)
diff --git a/crates/typst-library/src/routines.rs b/crates/typst-library/src/routines.rs
index a1126860..b283052a 100644
--- a/crates/typst-library/src/routines.rs
+++ b/crates/typst-library/src/routines.rs
@@ -10,8 +10,7 @@ use typst_utils::LazyHash;
use crate::diag::SourceResult;
use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{
- Args, Cast, Closure, Content, Context, Func, Packed, Scope, StyleChain, StyleVec,
- Styles, Value,
+ Args, Cast, Closure, Content, Context, Func, Packed, Scope, StyleChain, Styles, Value,
};
use crate::introspection::{Introspector, Locator, SplitLocator};
use crate::layout::{
@@ -104,26 +103,6 @@ routines! {
region: Region,
) -> SourceResult<Frame>
- /// Lays out inline content.
- fn layout_inline(
- engine: &mut Engine,
- children: &StyleVec,
- locator: Locator,
- styles: StyleChain,
- consecutive: bool,
- region: Size,
- expand: bool,
- ) -> SourceResult<Fragment>
-
- /// Lays out a [`BoxElem`].
- fn layout_box(
- elem: &Packed<BoxElem>,
- engine: &mut Engine,
- locator: Locator,
- styles: StyleChain,
- region: Size,
- ) -> SourceResult<Frame>
-
/// Lays out a [`ListElem`].
fn layout_list(
elem: &Packed<ListElem>,
@@ -348,17 +327,62 @@ pub enum RealizationKind<'a> {
/// This the root realization for layout. Requires a mutable reference
/// to document metadata that will be filled from `set document` rules.
LayoutDocument(&'a mut DocumentInfo),
- /// A nested realization in a container (e.g. a `block`).
- LayoutFragment,
+ /// A nested realization in a container (e.g. a `block`). Requires a mutable
+ /// reference to an enum that will be set to `FragmentKind::Inline` if the
+ /// fragment's content was fully inline.
+ LayoutFragment(&'a mut FragmentKind),
+ /// A nested realization in a paragraph (i.e. a `par`)
+ LayoutPar,
/// This the root realization for HTML. Requires a mutable reference
/// to document metadata that will be filled from `set document` rules.
HtmlDocument(&'a mut DocumentInfo),
- /// A nested realization in a container (e.g. a `block`).
- HtmlFragment,
+ /// A nested realization in a container (e.g. a `block`). Requires a mutable
+ /// reference to an enum that will be set to `FragmentKind::Inline` if the
+ /// fragment's content was fully inline.
+ HtmlFragment(&'a mut FragmentKind),
/// A realization within math.
Math,
}
+impl RealizationKind<'_> {
+ /// It this a realization for HTML export?
+ pub fn is_html(&self) -> bool {
+ matches!(self, Self::HtmlDocument(_) | Self::HtmlFragment(_))
+ }
+
+ /// It this a realization for a container?
+ pub fn is_fragment(&self) -> bool {
+ matches!(self, Self::LayoutFragment(_) | Self::HtmlFragment(_))
+ }
+
+ /// If this is a document-level realization, accesses the document info.
+ pub fn as_document_mut(&mut self) -> Option<&mut DocumentInfo> {
+ match self {
+ Self::LayoutDocument(info) | Self::HtmlDocument(info) => Some(*info),
+ _ => None,
+ }
+ }
+
+ /// If this is a container-level realization, accesses the fragment kind.
+ pub fn as_fragment_mut(&mut self) -> Option<&mut FragmentKind> {
+ match self {
+ Self::LayoutFragment(kind) | Self::HtmlFragment(kind) => Some(*kind),
+ _ => None,
+ }
+ }
+}
+
+/// The kind of fragment output that realization produced.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+pub enum FragmentKind {
+ /// The fragment's contents were fully inline, and as a result, the output
+ /// elements are too.
+ Inline,
+ /// The fragment contained non-inline content, so inline content was forced
+ /// into paragraphs, and as a result, the output elements are not inline.
+ Block,
+}
+
/// Temporary storage arenas for lifetime extension during realization.
///
/// Must be kept live while the content returned from realization is processed.