diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs index d3976948..0744687e 100644 --- a/crates/typst/src/model/heading.rs +++ b/crates/typst/src/model/heading.rs @@ -3,12 +3,14 @@ use std::num::NonZeroUsize; use crate::diag::SourceResult; use crate::engine::Engine; use crate::foundations::{ - elem, Content, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles, - Synthesize, + elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain, + Styles, Synthesize, }; use crate::introspection::{Count, Counter, CounterUpdate, Locatable}; -use crate::layout::{BlockElem, Em, HElem, VElem}; -use crate::model::{Numbering, Outlinable, Refable, Supplement}; +use crate::layout::{ + Abs, Axes, BlockElem, Em, HElem, LayoutMultiple, Length, Regions, VElem, +}; +use crate::model::{Numbering, Outlinable, ParElem, Refable, Supplement}; use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize}; use crate::util::NonZeroExt; @@ -163,6 +165,18 @@ pub struct HeadingElem { #[default(Smart::Auto)] pub bookmarked: Smart, + /// The indent all but the first line of a heading should have. + /// + /// The default value of `{auto}` indicates that the subsequent heading + /// lines will be indented based on the width of the numbering. + /// + /// ```example + /// #set heading(numbering: "1.") + /// #heading[A very, very, very, very, very, very long heading] + /// ``` + #[default(Smart::Auto)] + pub hanging_indent: Smart, + /// The heading's title. #[required] pub body: Content, @@ -201,15 +215,39 @@ impl Synthesize for Packed { impl Show for Packed { #[typst_macros::time(name = "heading", span = self.span())] fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { + const SPACING_TO_NUMBERING: Em = Em::new(0.3); + let span = self.span(); let mut realized = self.body().clone(); + + let hanging_indent = self.hanging_indent(styles); + + let mut indent = match hanging_indent { + Smart::Custom(length) => length.resolve(styles), + Smart::Auto => Abs::zero(), + }; + if let Some(numbering) = (**self).numbering(styles).as_ref() { - realized = Counter::of(HeadingElem::elem()) + let numbering = Counter::of(HeadingElem::elem()) .display_at_loc(engine, self.location().unwrap(), styles, numbering)? - .spanned(span) - + HElem::new(Em::new(0.3).into()).with_weak(true).pack() + .spanned(span); + + if hanging_indent.is_auto() { + let pod = Regions::one(Axes::splat(Abs::inf()), Axes::splat(false)); + let size = numbering.measure(engine, styles, pod)?.into_frame().size(); + + indent = size.x + SPACING_TO_NUMBERING.resolve(styles); + } + + realized = numbering + + HElem::new(SPACING_TO_NUMBERING.into()).with_weak(true).pack() + realized; } + + if indent != Abs::zero() { + realized = realized.styled(ParElem::set_hanging_indent(indent.into())); + } + Ok(BlockElem::new().with_body(Some(realized)).pack().spanned(span)) } } diff --git a/tests/ref/outline-first-line-indent.png b/tests/ref/outline-first-line-indent.png index dd995f31..91a494f7 100644 Binary files a/tests/ref/outline-first-line-indent.png and b/tests/ref/outline-first-line-indent.png differ