Improve realization and page layout (#4840)
@ -106,6 +106,18 @@ impl<T> OptionExt<T> for Option<T> {
|
|||||||
|
|
||||||
/// Extra methods for [`[T]`](slice).
|
/// Extra methods for [`[T]`](slice).
|
||||||
pub trait SliceExt<T> {
|
pub trait SliceExt<T> {
|
||||||
|
/// Returns a slice with all matching elements from the start of the slice
|
||||||
|
/// removed.
|
||||||
|
fn trim_start_matches<F>(&self, f: F) -> &[T]
|
||||||
|
where
|
||||||
|
F: FnMut(&T) -> bool;
|
||||||
|
|
||||||
|
/// Returns a slice with all matching elements from the end of the slice
|
||||||
|
/// removed.
|
||||||
|
fn trim_end_matches<F>(&self, f: F) -> &[T]
|
||||||
|
where
|
||||||
|
F: FnMut(&T) -> bool;
|
||||||
|
|
||||||
/// Split a slice into consecutive runs with the same key and yield for
|
/// Split a slice into consecutive runs with the same key and yield for
|
||||||
/// each such run the key and the slice of elements with that key.
|
/// each such run the key and the slice of elements with that key.
|
||||||
fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F>
|
fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F>
|
||||||
@ -115,6 +127,29 @@ pub trait SliceExt<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T> SliceExt<T> for [T] {
|
impl<T> SliceExt<T> for [T] {
|
||||||
|
fn trim_start_matches<F>(&self, mut f: F) -> &[T]
|
||||||
|
where
|
||||||
|
F: FnMut(&T) -> bool,
|
||||||
|
{
|
||||||
|
let len = self.len();
|
||||||
|
let mut i = 0;
|
||||||
|
while i < len && f(&self[i]) {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
&self[i..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trim_end_matches<F>(&self, mut f: F) -> &[T]
|
||||||
|
where
|
||||||
|
F: FnMut(&T) -> bool,
|
||||||
|
{
|
||||||
|
let mut i = self.len();
|
||||||
|
while i > 0 && f(&self[i - 1]) {
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
&self[..i]
|
||||||
|
}
|
||||||
|
|
||||||
fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F> {
|
fn group_by_key<K, F>(&self, f: F) -> GroupByKey<'_, T, F> {
|
||||||
GroupByKey { slice: self, f }
|
GroupByKey { slice: self, f }
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ impl Eval for ast::SetRule<'_> {
|
|||||||
})
|
})
|
||||||
.at(target.span())?;
|
.at(target.span())?;
|
||||||
let args = self.args().eval(vm)?.spanned(self.span());
|
let args = self.args().eval(vm)?.spanned(self.span());
|
||||||
Ok(target.set(&mut vm.engine, args)?.spanned(self.span()))
|
Ok(target.set(&mut vm.engine, args)?.spanned(self.span()).liftable())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +46,6 @@ impl Eval for ast::ShowRule<'_> {
|
|||||||
expr => expr.eval(vm)?.cast::<Transformation>().at(span)?,
|
expr => expr.eval(vm)?.cast::<Transformation>().at(span)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Recipe { span, selector, transform })
|
Ok(Recipe::new(selector, transform, span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -369,7 +369,7 @@ impl Content {
|
|||||||
context: Tracked<Context>,
|
context: Tracked<Context>,
|
||||||
recipe: Recipe,
|
recipe: Recipe,
|
||||||
) -> SourceResult<Self> {
|
) -> SourceResult<Self> {
|
||||||
if recipe.selector.is_none() {
|
if recipe.selector().is_none() {
|
||||||
recipe.apply(engine, context, self)
|
recipe.apply(engine, context, self)
|
||||||
} else {
|
} else {
|
||||||
Ok(self.styled(recipe))
|
Ok(self.styled(recipe))
|
||||||
|
@ -93,6 +93,11 @@ impl Styles {
|
|||||||
self.0.iter().map(|style| &**style)
|
self.0.iter().map(|style| &**style)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over the contained styles.
|
||||||
|
pub fn as_slice(&self) -> &[LazyHash<Style>] {
|
||||||
|
self.0.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
/// Set an inner value for a style property.
|
/// Set an inner value for a style property.
|
||||||
///
|
///
|
||||||
/// If the property needs folding and the value is already contained in the
|
/// If the property needs folding and the value is already contained in the
|
||||||
@ -118,16 +123,33 @@ impl Styles {
|
|||||||
self.0.insert(0, LazyHash::new(outer));
|
self.0.insert(0, LazyHash::new(outer));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a slice of outer styles.
|
|
||||||
pub fn apply_slice(&mut self, outer: &[LazyHash<Style>]) {
|
|
||||||
self.0 = outer.iter().cloned().chain(mem::take(self).0).collect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an origin span to all contained properties.
|
/// Add an origin span to all contained properties.
|
||||||
pub fn spanned(mut self, span: Span) -> Self {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
for entry in self.0.make_mut() {
|
for entry in self.0.make_mut() {
|
||||||
if let Style::Property(property) = &mut **entry {
|
if let Style::Property(property) = &mut **entry {
|
||||||
property.span = Some(span);
|
property.span = span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks the styles as having been applied outside of any show rule.
|
||||||
|
pub fn outside(mut self) -> Self {
|
||||||
|
for entry in self.0.make_mut() {
|
||||||
|
match &mut **entry {
|
||||||
|
Style::Property(property) => property.outside = true,
|
||||||
|
Style::Recipe(recipe) => recipe.outside = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks the styles as being allowed to be lifted up to the page level.
|
||||||
|
pub fn liftable(mut self) -> Self {
|
||||||
|
for entry in self.0.make_mut() {
|
||||||
|
if let Style::Property(property) = &mut **entry {
|
||||||
|
property.liftable = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -144,13 +166,8 @@ impl Styles {
|
|||||||
|
|
||||||
/// Returns `Some(_)` with an optional span if this list contains
|
/// Returns `Some(_)` with an optional span if this list contains
|
||||||
/// styles for the given element.
|
/// styles for the given element.
|
||||||
pub fn interruption<T: NativeElement>(&self) -> Option<Option<Span>> {
|
pub fn interruption<T: NativeElement>(&self) -> Option<Span> {
|
||||||
let elem = T::elem();
|
self.0.iter().find_map(|entry| entry.interruption::<T>())
|
||||||
self.0.iter().find_map(|entry| match &**entry {
|
|
||||||
Style::Property(property) => property.is_of(elem).then_some(property.span),
|
|
||||||
Style::Recipe(recipe) => recipe.is_of(elem).then_some(Some(recipe.span)),
|
|
||||||
Style::Revocation(_) => None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a font family composed of a preferred family and existing families
|
/// Set a font family composed of a preferred family and existing families
|
||||||
@ -176,6 +193,21 @@ impl From<Style> for Styles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Styles {
|
||||||
|
type Item = LazyHash<Style>;
|
||||||
|
type IntoIter = ecow::vec::IntoIter<Self::Item>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromIterator<LazyHash<Style>> for Styles {
|
||||||
|
fn from_iter<T: IntoIterator<Item = LazyHash<Style>>>(iter: T) -> Self {
|
||||||
|
Self(iter.into_iter().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for Styles {
|
impl Debug for Styles {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
f.write_str("Styles ")?;
|
f.write_str("Styles ")?;
|
||||||
@ -216,6 +248,37 @@ impl Style {
|
|||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `Some(_)` with an optional span if this style is of
|
||||||
|
/// the given element.
|
||||||
|
pub fn interruption<T: NativeElement>(&self) -> Option<Span> {
|
||||||
|
let elem = T::elem();
|
||||||
|
match self {
|
||||||
|
Style::Property(property) => property.is_of(elem).then_some(property.span),
|
||||||
|
Style::Recipe(recipe) => recipe.is_of(elem).then_some(recipe.span),
|
||||||
|
Style::Revocation(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the style is allowed to be lifted up to the page level. Only
|
||||||
|
/// true for styles originating from set rules.
|
||||||
|
pub fn liftable(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Property(property) => property.liftable,
|
||||||
|
Self::Recipe(_) => true,
|
||||||
|
Self::Revocation(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the style was applied outside of any show rule. This is set
|
||||||
|
/// during realization.
|
||||||
|
pub fn outside(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Property(property) => property.outside,
|
||||||
|
Self::Recipe(recipe) => recipe.outside,
|
||||||
|
Self::Revocation(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Style {
|
impl Debug for Style {
|
||||||
@ -250,7 +313,11 @@ pub struct Property {
|
|||||||
/// The property's value.
|
/// The property's value.
|
||||||
value: Block,
|
value: Block,
|
||||||
/// The span of the set rule the property stems from.
|
/// The span of the set rule the property stems from.
|
||||||
span: Option<Span>,
|
span: Span,
|
||||||
|
/// Whether the property is allowed to be lifted up to the page level.
|
||||||
|
liftable: bool,
|
||||||
|
/// Whether the property was applied outside of any show rule.
|
||||||
|
outside: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Property {
|
impl Property {
|
||||||
@ -264,7 +331,9 @@ impl Property {
|
|||||||
elem: E::elem(),
|
elem: E::elem(),
|
||||||
id,
|
id,
|
||||||
value: Block::new(value),
|
value: Block::new(value),
|
||||||
span: None,
|
span: Span::detached(),
|
||||||
|
liftable: false,
|
||||||
|
outside: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,19 +439,41 @@ impl Hash for dyn Blockable {
|
|||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct Recipe {
|
pub struct Recipe {
|
||||||
/// The span that errors are reported with.
|
|
||||||
pub span: Span,
|
|
||||||
/// Determines whether the recipe applies to an element.
|
/// Determines whether the recipe applies to an element.
|
||||||
///
|
///
|
||||||
/// If this is `None`, then this recipe is from a show rule with
|
/// If this is `None`, then this recipe is from a show rule with
|
||||||
/// no selector (`show: rest => ...`), which is [eagerly applied][Content::styled_with_recipe]
|
/// no selector (`show: rest => ...`), which is [eagerly applied][Content::styled_with_recipe]
|
||||||
/// to the rest of the content in the scope.
|
/// to the rest of the content in the scope.
|
||||||
pub selector: Option<Selector>,
|
selector: Option<Selector>,
|
||||||
/// The transformation to perform on the match.
|
/// The transformation to perform on the match.
|
||||||
pub transform: Transformation,
|
transform: Transformation,
|
||||||
|
/// The span that errors are reported with.
|
||||||
|
span: Span,
|
||||||
|
/// Relevant properties of the kind of construct the style originated from
|
||||||
|
/// and where it was applied.
|
||||||
|
outside: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Recipe {
|
impl Recipe {
|
||||||
|
/// Create a new recipe from a key-value pair.
|
||||||
|
pub fn new(
|
||||||
|
selector: Option<Selector>,
|
||||||
|
transform: Transformation,
|
||||||
|
span: Span,
|
||||||
|
) -> Self {
|
||||||
|
Self { selector, transform, span, outside: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The recipe's selector.
|
||||||
|
pub fn selector(&self) -> Option<&Selector> {
|
||||||
|
self.selector.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The recipe's transformation.
|
||||||
|
pub fn transform(&self) -> &Transformation {
|
||||||
|
&self.transform
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether this recipe is for the given type of element.
|
/// Whether this recipe is for the given type of element.
|
||||||
pub fn is_of(&self, element: Element) -> bool {
|
pub fn is_of(&self, element: Element) -> bool {
|
||||||
match self.selector {
|
match self.selector {
|
||||||
@ -494,7 +585,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
/// `self`. For folded properties `local` contributes the inner value.
|
/// `self`. For folded properties `local` contributes the inner value.
|
||||||
pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b>
|
pub fn chain<'b, C>(&'b self, local: &'b C) -> StyleChain<'b>
|
||||||
where
|
where
|
||||||
C: Chainable,
|
C: Chainable + ?Sized,
|
||||||
{
|
{
|
||||||
Chainable::chain(local, self)
|
Chainable::chain(local, self)
|
||||||
}
|
}
|
||||||
@ -557,7 +648,7 @@ impl<'a> StyleChain<'a> {
|
|||||||
) -> impl Iterator<Item = &'a T> {
|
) -> impl Iterator<Item = &'a T> {
|
||||||
inherent.into_iter().chain(
|
inherent.into_iter().chain(
|
||||||
self.entries()
|
self.entries()
|
||||||
.filter_map(Style::property)
|
.filter_map(|style| style.property())
|
||||||
.filter(move |property| property.is(func, id))
|
.filter(move |property| property.is(func, id))
|
||||||
.map(|property| &property.value)
|
.map(|property| &property.value)
|
||||||
.map(move |value| {
|
.map(move |value| {
|
||||||
@ -573,15 +664,6 @@ impl<'a> StyleChain<'a> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert to a style map.
|
|
||||||
pub fn to_map(self) -> Styles {
|
|
||||||
let mut suffix = Styles::new();
|
|
||||||
for link in self.links() {
|
|
||||||
suffix.apply_slice(link);
|
|
||||||
}
|
|
||||||
suffix
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over the entries of the chain.
|
/// Iterate over the entries of the chain.
|
||||||
pub fn entries(self) -> Entries<'a> {
|
pub fn entries(self) -> Entries<'a> {
|
||||||
Entries { inner: [].as_slice().iter(), links: self.links() }
|
Entries { inner: [].as_slice().iter(), links: self.links() }
|
||||||
@ -592,21 +674,59 @@ impl<'a> StyleChain<'a> {
|
|||||||
Links(Some(self))
|
Links(Some(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert to a style map.
|
||||||
|
pub fn to_map(self) -> Styles {
|
||||||
|
let mut styles: EcoVec<_> = self.entries().cloned().collect();
|
||||||
|
styles.make_mut().reverse();
|
||||||
|
Styles(styles)
|
||||||
|
}
|
||||||
|
|
||||||
/// Build owned styles from the suffix (all links beyond the `len`) of the
|
/// Build owned styles from the suffix (all links beyond the `len`) of the
|
||||||
/// chain.
|
/// chain.
|
||||||
pub fn suffix(self, len: usize) -> Styles {
|
pub fn suffix(self, len: usize) -> Styles {
|
||||||
let mut suffix = Styles::new();
|
let mut styles = EcoVec::new();
|
||||||
let take = self.links().count().saturating_sub(len);
|
let take = self.links().count().saturating_sub(len);
|
||||||
for link in self.links().take(take) {
|
for link in self.links().take(take) {
|
||||||
suffix.apply_slice(link);
|
styles.extend(link.iter().cloned().rev());
|
||||||
}
|
}
|
||||||
suffix
|
styles.make_mut().reverse();
|
||||||
|
Styles(styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove the last link from the chain.
|
/// Remove the last link from the chain.
|
||||||
pub fn pop(&mut self) {
|
pub fn pop(&mut self) {
|
||||||
*self = self.tail.copied().unwrap_or_default();
|
*self = self.tail.copied().unwrap_or_default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determine the shared trunk of a collection of style chains.
|
||||||
|
pub fn trunk(iter: impl IntoIterator<Item = Self>) -> Option<Self> {
|
||||||
|
// Determine shared style depth and first span.
|
||||||
|
let mut iter = iter.into_iter();
|
||||||
|
let mut trunk = iter.next()?;
|
||||||
|
let mut depth = trunk.links().count();
|
||||||
|
|
||||||
|
for mut chain in iter {
|
||||||
|
let len = chain.links().count();
|
||||||
|
if len < depth {
|
||||||
|
for _ in 0..depth - len {
|
||||||
|
trunk.pop();
|
||||||
|
}
|
||||||
|
depth = len;
|
||||||
|
} else if len > depth {
|
||||||
|
for _ in 0..len - depth {
|
||||||
|
chain.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while depth > 0 && chain != trunk {
|
||||||
|
trunk.pop();
|
||||||
|
chain.pop();
|
||||||
|
depth -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(trunk)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for StyleChain<'_> {
|
impl Debug for StyleChain<'_> {
|
||||||
@ -673,7 +793,7 @@ pub struct Entries<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for Entries<'a> {
|
impl<'a> Iterator for Entries<'a> {
|
||||||
type Item = &'a Style;
|
type Item = &'a LazyHash<Style>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
@ -702,6 +822,107 @@ 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.
|
/// A property that is resolved with other properties from the style chain.
|
||||||
pub trait Resolve {
|
pub trait Resolve {
|
||||||
/// The type of the resolved output.
|
/// The type of the resolved output.
|
||||||
|
@ -825,7 +825,7 @@ impl ManualPageCounter {
|
|||||||
match item {
|
match item {
|
||||||
FrameItem::Group(group) => self.visit(engine, &group.frame)?,
|
FrameItem::Group(group) => self.visit(engine, &group.frame)?,
|
||||||
FrameItem::Tag(tag) => {
|
FrameItem::Tag(tag) => {
|
||||||
let Some(elem) = tag.elem.to_packed::<CounterUpdateElem>() else {
|
let Some(elem) = tag.elem().to_packed::<CounterUpdateElem>() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if *elem.key() == CounterKey::Page {
|
if *elem.key() == CounterKey::Page {
|
||||||
|
@ -10,7 +10,7 @@ use smallvec::SmallVec;
|
|||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{Content, Label, Repr, Selector};
|
use crate::foundations::{Content, Label, Repr, Selector};
|
||||||
use crate::introspection::Location;
|
use crate::introspection::{Location, TagKind};
|
||||||
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
|
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
|
||||||
use crate::model::Numbering;
|
use crate::model::Numbering;
|
||||||
use crate::utils::NonZeroExt;
|
use crate::utils::NonZeroExt;
|
||||||
@ -66,20 +66,21 @@ impl Introspector {
|
|||||||
self.extract(&group.frame, page, ts);
|
self.extract(&group.frame, page, ts);
|
||||||
}
|
}
|
||||||
FrameItem::Tag(tag)
|
FrameItem::Tag(tag)
|
||||||
if !self.elems.contains_key(&tag.elem.location().unwrap()) =>
|
if tag.kind() == TagKind::Start
|
||||||
|
&& !self.elems.contains_key(&tag.location()) =>
|
||||||
{
|
{
|
||||||
let pos = pos.transform(ts);
|
let pos = pos.transform(ts);
|
||||||
let loc = tag.elem.location().unwrap();
|
let loc = tag.location();
|
||||||
let ret = self
|
let ret = self
|
||||||
.elems
|
.elems
|
||||||
.insert(loc, (tag.elem.clone(), Position { page, point: pos }));
|
.insert(loc, (tag.elem().clone(), Position { page, point: pos }));
|
||||||
assert!(ret.is_none(), "duplicate locations");
|
assert!(ret.is_none(), "duplicate locations");
|
||||||
|
|
||||||
// Build the key map.
|
// Build the key map.
|
||||||
self.keys.entry(tag.key).or_default().push(loc);
|
self.keys.entry(tag.key()).or_default().push(loc);
|
||||||
|
|
||||||
// Build the label cache.
|
// Build the label cache.
|
||||||
if let Some(label) = tag.elem.label() {
|
if let Some(label) = tag.elem().label() {
|
||||||
self.labels.entry(label).or_default().push(self.elems.len() - 1);
|
self.labels.entry(label).or_default().push(self.elems.len() - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ mod metadata;
|
|||||||
#[path = "query.rs"]
|
#[path = "query.rs"]
|
||||||
mod query_;
|
mod query_;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod tag;
|
||||||
|
|
||||||
pub use self::counter::*;
|
pub use self::counter::*;
|
||||||
pub use self::here_::*;
|
pub use self::here_::*;
|
||||||
@ -22,16 +23,9 @@ pub use self::locator::*;
|
|||||||
pub use self::metadata::*;
|
pub use self::metadata::*;
|
||||||
pub use self::query_::*;
|
pub use self::query_::*;
|
||||||
pub use self::state::*;
|
pub use self::state::*;
|
||||||
|
pub use self::tag::*;
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use crate::foundations::{category, Category, Scope};
|
||||||
|
|
||||||
use crate::diag::{bail, SourceResult};
|
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
category, elem, Args, Category, Construct, Content, NativeElement, Packed, Scope,
|
|
||||||
Unlabellable,
|
|
||||||
};
|
|
||||||
use crate::realize::{Behave, Behaviour};
|
|
||||||
|
|
||||||
/// Interactions between document parts.
|
/// Interactions between document parts.
|
||||||
///
|
///
|
||||||
@ -57,65 +51,3 @@ pub fn define(global: &mut Scope) {
|
|||||||
global.define_func::<query>();
|
global.define_func::<query>();
|
||||||
global.define_func::<locate>();
|
global.define_func::<locate>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Holds a tag for a locatable element that was realized.
|
|
||||||
///
|
|
||||||
/// The `TagElem` is handled by all layouters. The held element becomes
|
|
||||||
/// available for introspection in the next compiler iteration.
|
|
||||||
#[elem(Behave, Unlabellable, Construct)]
|
|
||||||
pub struct TagElem {
|
|
||||||
/// The introspectible element.
|
|
||||||
#[required]
|
|
||||||
#[internal]
|
|
||||||
pub tag: Tag,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TagElem {
|
|
||||||
/// Create a packed tag element.
|
|
||||||
pub fn packed(tag: Tag) -> Content {
|
|
||||||
let mut content = Self::new(tag).pack();
|
|
||||||
// We can skip preparation for the `TagElem`.
|
|
||||||
content.mark_prepared();
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for TagElem {
|
|
||||||
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
bail!(args.span, "cannot be constructed manually")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Unlabellable for Packed<TagElem> {}
|
|
||||||
|
|
||||||
impl Behave for Packed<TagElem> {
|
|
||||||
fn behaviour(&self) -> Behaviour {
|
|
||||||
Behaviour::Invisible
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Holds a locatable element that was realized.
|
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
|
||||||
pub struct Tag {
|
|
||||||
/// The introspectible element.
|
|
||||||
pub elem: Content,
|
|
||||||
/// The element's key hash, which forms the base of its location (but is
|
|
||||||
/// locally disambiguated and combined with outer hashes).
|
|
||||||
///
|
|
||||||
/// We need to retain this for introspector-assisted location assignment
|
|
||||||
/// during measurement.
|
|
||||||
pub(crate) key: u128,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tag {
|
|
||||||
/// Create a tag from an element and its key hash.
|
|
||||||
pub fn new(elem: Content, key: u128) -> Self {
|
|
||||||
Self { elem, key }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Tag {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "Tag({:?})", self.elem.elem().name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
111
crates/typst/src/introspection/tag.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
use crate::diag::{bail, SourceResult};
|
||||||
|
use crate::engine::Engine;
|
||||||
|
use crate::foundations::{
|
||||||
|
elem, Args, Construct, Content, NativeElement, Packed, Unlabellable,
|
||||||
|
};
|
||||||
|
use crate::introspection::Location;
|
||||||
|
use crate::realize::{Behave, Behaviour};
|
||||||
|
|
||||||
|
/// Holds a locatable element that was realized.
|
||||||
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
|
pub struct Tag {
|
||||||
|
/// Whether this is a start or end tag.
|
||||||
|
kind: TagKind,
|
||||||
|
/// The introspectible element.
|
||||||
|
elem: Content,
|
||||||
|
/// The element's key hash.
|
||||||
|
key: u128,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tag {
|
||||||
|
/// Create a start tag from an element and its key hash.
|
||||||
|
///
|
||||||
|
/// Panics if the element does not have a [`Location`].
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(elem: Content, key: u128) -> Self {
|
||||||
|
assert!(elem.location().is_some());
|
||||||
|
Self { elem, key, kind: TagKind::Start }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the same tag with the given kind.
|
||||||
|
pub fn with_kind(self, kind: TagKind) -> Self {
|
||||||
|
Self { kind, ..self }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this is a start or end tag.
|
||||||
|
pub fn kind(&self) -> TagKind {
|
||||||
|
self.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The locatable element that the tag holds.
|
||||||
|
pub fn elem(&self) -> &Content {
|
||||||
|
&self.elem
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the location of the element.
|
||||||
|
pub fn location(&self) -> Location {
|
||||||
|
self.elem.location().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The element's key hash, which forms the base of its location (but is
|
||||||
|
/// locally disambiguated and combined with outer hashes).
|
||||||
|
///
|
||||||
|
/// We need to retain this for introspector-assisted location assignment
|
||||||
|
/// during measurement.
|
||||||
|
pub fn key(&self) -> u128 {
|
||||||
|
self.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Tag {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Tag({:?}, {:?})", self.kind, self.elem.elem().name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines whether a tag marks the start or end of an element.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum TagKind {
|
||||||
|
/// The tag indicates that the element starts here.
|
||||||
|
Start,
|
||||||
|
/// The tag indicates that the element end here.
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds a tag for a locatable element that was realized.
|
||||||
|
///
|
||||||
|
/// The `TagElem` is handled by all layouters. The held element becomes
|
||||||
|
/// available for introspection in the next compiler iteration.
|
||||||
|
#[elem(Behave, Unlabellable, Construct)]
|
||||||
|
pub struct TagElem {
|
||||||
|
/// The introspectible element.
|
||||||
|
#[required]
|
||||||
|
#[internal]
|
||||||
|
pub tag: Tag,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TagElem {
|
||||||
|
/// Create a packed tag element.
|
||||||
|
pub fn packed(tag: Tag) -> Content {
|
||||||
|
let mut content = Self::new(tag).pack();
|
||||||
|
// We can skip preparation for the `TagElem`.
|
||||||
|
content.mark_prepared();
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Construct for TagElem {
|
||||||
|
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
bail!(args.span, "cannot be constructed manually")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unlabellable for Packed<TagElem> {}
|
||||||
|
|
||||||
|
impl Behave for Packed<TagElem> {
|
||||||
|
fn behaviour(&self) -> Behaviour {
|
||||||
|
Behaviour::Ignorant
|
||||||
|
}
|
||||||
|
}
|
@ -125,7 +125,7 @@ pub fn collect<'a>(
|
|||||||
consecutive: bool,
|
consecutive: bool,
|
||||||
) -> SourceResult<(String, Vec<Segment<'a>>, SpanMapper)> {
|
) -> SourceResult<(String, Vec<Segment<'a>>, SpanMapper)> {
|
||||||
let mut collector = Collector::new(2 + children.len());
|
let mut collector = Collector::new(2 + children.len());
|
||||||
let mut iter = children.chain(styles).peekable();
|
let mut iter = children.iter(styles).peekable();
|
||||||
let mut locator = locator.split();
|
let mut locator = locator.split();
|
||||||
|
|
||||||
let outer_dir = TextElem::dir_in(*styles);
|
let outer_dir = TextElem::dir_in(*styles);
|
||||||
|
@ -18,11 +18,10 @@ use self::shaping::{
|
|||||||
};
|
};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::StyleChain;
|
use crate::foundations::{StyleChain, StyleVec};
|
||||||
use crate::introspection::{Introspector, Locator, LocatorLink};
|
use crate::introspection::{Introspector, Locator, LocatorLink};
|
||||||
use crate::layout::{Fragment, Size};
|
use crate::layout::{Fragment, Size};
|
||||||
use crate::model::ParElem;
|
use crate::model::ParElem;
|
||||||
use crate::realize::StyleVec;
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Range of a substring of text.
|
/// Range of a substring of text.
|
||||||
|
@ -8,15 +8,16 @@ use comemo::Track;
|
|||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::{bail, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, AutoValue, Cast, Content, Context, Dict, Fold, Func, Smart, StyleChain,
|
cast, elem, Args, AutoValue, Cast, Construct, Content, Context, Dict, Fold, Func,
|
||||||
Value,
|
NativeElement, Packed, Set, Smart, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Alignment, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel, Sides,
|
Abs, Alignment, FlushElem, Frame, HAlignment, Length, OuterVAlignment, Ratio, Rel,
|
||||||
SpecificAlignment,
|
Sides, SpecificAlignment,
|
||||||
};
|
};
|
||||||
use crate::model::Numbering;
|
use crate::model::Numbering;
|
||||||
use crate::utils::{NonZeroExt, Scalar};
|
use crate::realize::{Behave, Behaviour};
|
||||||
|
use crate::utils::{singleton, NonZeroExt, Scalar};
|
||||||
use crate::visualize::{Color, Paint};
|
use crate::visualize::{Color, Paint};
|
||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
@ -38,11 +39,12 @@ use crate::visualize::{Color, Paint};
|
|||||||
///
|
///
|
||||||
/// There you go, US friends!
|
/// There you go, US friends!
|
||||||
/// ```
|
/// ```
|
||||||
#[elem]
|
#[elem(Construct)]
|
||||||
pub struct PageElem {
|
pub struct PageElem {
|
||||||
/// A standard paper size to set width and height.
|
/// A standard paper size to set width and height.
|
||||||
#[external]
|
#[external]
|
||||||
#[default(Paper::A4)]
|
#[default(Paper::A4)]
|
||||||
|
#[ghost]
|
||||||
pub paper: Paper,
|
pub paper: Paper,
|
||||||
|
|
||||||
/// The width of the page.
|
/// The width of the page.
|
||||||
@ -64,6 +66,7 @@ pub struct PageElem {
|
|||||||
.or_else(|| paper.map(|paper| Smart::Custom(paper.width().into())))
|
.or_else(|| paper.map(|paper| Smart::Custom(paper.width().into())))
|
||||||
)]
|
)]
|
||||||
#[default(Smart::Custom(Paper::A4.width().into()))]
|
#[default(Smart::Custom(Paper::A4.width().into()))]
|
||||||
|
#[ghost]
|
||||||
pub width: Smart<Length>,
|
pub width: Smart<Length>,
|
||||||
|
|
||||||
/// The height of the page.
|
/// The height of the page.
|
||||||
@ -78,6 +81,7 @@ pub struct PageElem {
|
|||||||
.or_else(|| paper.map(|paper| Smart::Custom(paper.height().into())))
|
.or_else(|| paper.map(|paper| Smart::Custom(paper.height().into())))
|
||||||
)]
|
)]
|
||||||
#[default(Smart::Custom(Paper::A4.height().into()))]
|
#[default(Smart::Custom(Paper::A4.height().into()))]
|
||||||
|
#[ghost]
|
||||||
pub height: Smart<Length>,
|
pub height: Smart<Length>,
|
||||||
|
|
||||||
/// Whether the page is flipped into landscape orientation.
|
/// Whether the page is flipped into landscape orientation.
|
||||||
@ -99,6 +103,7 @@ pub struct PageElem {
|
|||||||
/// +1 555 555 5555
|
/// +1 555 555 5555
|
||||||
/// ```
|
/// ```
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
|
#[ghost]
|
||||||
pub flipped: bool,
|
pub flipped: bool,
|
||||||
|
|
||||||
/// The page's margins.
|
/// The page's margins.
|
||||||
@ -138,6 +143,7 @@ pub struct PageElem {
|
|||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[fold]
|
#[fold]
|
||||||
|
#[ghost]
|
||||||
pub margin: Margin,
|
pub margin: Margin,
|
||||||
|
|
||||||
/// On which side the pages will be bound.
|
/// On which side the pages will be bound.
|
||||||
@ -149,6 +155,7 @@ pub struct PageElem {
|
|||||||
///
|
///
|
||||||
/// This affects the meaning of the `inside` and `outside` options for
|
/// This affects the meaning of the `inside` and `outside` options for
|
||||||
/// margins.
|
/// margins.
|
||||||
|
#[ghost]
|
||||||
pub binding: Smart<Binding>,
|
pub binding: Smart<Binding>,
|
||||||
|
|
||||||
/// How many columns the page has.
|
/// How many columns the page has.
|
||||||
@ -169,6 +176,7 @@ pub struct PageElem {
|
|||||||
/// of a rapidly changing climate.
|
/// of a rapidly changing climate.
|
||||||
/// ```
|
/// ```
|
||||||
#[default(NonZeroUsize::ONE)]
|
#[default(NonZeroUsize::ONE)]
|
||||||
|
#[ghost]
|
||||||
pub columns: NonZeroUsize,
|
pub columns: NonZeroUsize,
|
||||||
|
|
||||||
/// The page's background fill.
|
/// The page's background fill.
|
||||||
@ -192,6 +200,7 @@ pub struct PageElem {
|
|||||||
/// *Dark mode enabled.*
|
/// *Dark mode enabled.*
|
||||||
/// ```
|
/// ```
|
||||||
#[borrowed]
|
#[borrowed]
|
||||||
|
#[ghost]
|
||||||
pub fill: Smart<Option<Paint>>,
|
pub fill: Smart<Option<Paint>>,
|
||||||
|
|
||||||
/// How to [number]($numbering) the pages.
|
/// How to [number]($numbering) the pages.
|
||||||
@ -209,6 +218,7 @@ pub struct PageElem {
|
|||||||
/// #lorem(48)
|
/// #lorem(48)
|
||||||
/// ```
|
/// ```
|
||||||
#[borrowed]
|
#[borrowed]
|
||||||
|
#[ghost]
|
||||||
pub numbering: Option<Numbering>,
|
pub numbering: Option<Numbering>,
|
||||||
|
|
||||||
/// The alignment of the page numbering.
|
/// The alignment of the page numbering.
|
||||||
@ -228,6 +238,7 @@ pub struct PageElem {
|
|||||||
/// #lorem(30)
|
/// #lorem(30)
|
||||||
/// ```
|
/// ```
|
||||||
#[default(SpecificAlignment::Both(HAlignment::Center, OuterVAlignment::Bottom))]
|
#[default(SpecificAlignment::Both(HAlignment::Center, OuterVAlignment::Bottom))]
|
||||||
|
#[ghost]
|
||||||
pub number_align: SpecificAlignment<HAlignment, OuterVAlignment>,
|
pub number_align: SpecificAlignment<HAlignment, OuterVAlignment>,
|
||||||
|
|
||||||
/// The page's header. Fills the top margin of each page.
|
/// The page's header. Fills the top margin of each page.
|
||||||
@ -251,11 +262,13 @@ pub struct PageElem {
|
|||||||
/// #lorem(19)
|
/// #lorem(19)
|
||||||
/// ```
|
/// ```
|
||||||
#[borrowed]
|
#[borrowed]
|
||||||
|
#[ghost]
|
||||||
pub header: Smart<Option<Content>>,
|
pub header: Smart<Option<Content>>,
|
||||||
|
|
||||||
/// The amount the header is raised into the top margin.
|
/// The amount the header is raised into the top margin.
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Ratio::new(0.3).into())]
|
#[default(Ratio::new(0.3).into())]
|
||||||
|
#[ghost]
|
||||||
pub header_ascent: Rel<Length>,
|
pub header_ascent: Rel<Length>,
|
||||||
|
|
||||||
/// The page's footer. Fills the bottom margin of each page.
|
/// The page's footer. Fills the bottom margin of each page.
|
||||||
@ -287,11 +300,13 @@ pub struct PageElem {
|
|||||||
/// #lorem(48)
|
/// #lorem(48)
|
||||||
/// ```
|
/// ```
|
||||||
#[borrowed]
|
#[borrowed]
|
||||||
|
#[ghost]
|
||||||
pub footer: Smart<Option<Content>>,
|
pub footer: Smart<Option<Content>>,
|
||||||
|
|
||||||
/// The amount the footer is lowered into the bottom margin.
|
/// The amount the footer is lowered into the bottom margin.
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Ratio::new(0.3).into())]
|
#[default(Ratio::new(0.3).into())]
|
||||||
|
#[ghost]
|
||||||
pub footer_descent: Rel<Length>,
|
pub footer_descent: Rel<Length>,
|
||||||
|
|
||||||
/// Content in the page's background.
|
/// Content in the page's background.
|
||||||
@ -311,6 +326,7 @@ pub struct PageElem {
|
|||||||
/// over the world (of typesetting).
|
/// over the world (of typesetting).
|
||||||
/// ```
|
/// ```
|
||||||
#[borrowed]
|
#[borrowed]
|
||||||
|
#[ghost]
|
||||||
pub background: Option<Content>,
|
pub background: Option<Content>,
|
||||||
|
|
||||||
/// Content in the page's foreground.
|
/// Content in the page's foreground.
|
||||||
@ -325,6 +341,7 @@ pub struct PageElem {
|
|||||||
/// not understand our approach...
|
/// not understand our approach...
|
||||||
/// ```
|
/// ```
|
||||||
#[borrowed]
|
#[borrowed]
|
||||||
|
#[ghost]
|
||||||
pub foreground: Option<Content>,
|
pub foreground: Option<Content>,
|
||||||
|
|
||||||
/// The contents of the page(s).
|
/// The contents of the page(s).
|
||||||
@ -332,13 +349,93 @@ pub struct PageElem {
|
|||||||
/// Multiple pages will be created if the content does not fit on a single
|
/// Multiple pages will be created if the content does not fit on a single
|
||||||
/// page. A new page with the page properties prior to the function invocation
|
/// page. A new page with the page properties prior to the function invocation
|
||||||
/// will be created after the body has been typeset.
|
/// will be created after the body has been typeset.
|
||||||
|
#[external]
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the page should be aligned to an even or odd page.
|
impl Construct for PageElem {
|
||||||
|
fn construct(engine: &mut Engine, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
// The page constructor is special: It doesn't create a page element.
|
||||||
|
// Instead, it just ensures that the passed content lives in a separate
|
||||||
|
// page and styles it.
|
||||||
|
let styles = Self::set(engine, args)?;
|
||||||
|
let body = args.expect::<Content>("body")?;
|
||||||
|
Ok(Content::sequence([
|
||||||
|
PagebreakElem::shared_weak().clone(),
|
||||||
|
// We put an effectless, invisible non-tag element on the page.
|
||||||
|
// This has two desirable consequences:
|
||||||
|
// - The page is kept even if the body is empty
|
||||||
|
// - The page doesn't inherit shared styles from the body
|
||||||
|
FlushElem::new().pack(),
|
||||||
|
body,
|
||||||
|
PagebreakElem::shared_boundary().clone(),
|
||||||
|
])
|
||||||
|
.styled_with_map(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A manual page break.
|
||||||
|
///
|
||||||
|
/// Must not be used inside any containers.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```example
|
||||||
|
/// The next page contains
|
||||||
|
/// more details on compound theory.
|
||||||
|
/// #pagebreak()
|
||||||
|
///
|
||||||
|
/// == Compound Theory
|
||||||
|
/// In 1984, the first ...
|
||||||
|
/// ```
|
||||||
|
#[elem(title = "Page Break", Behave)]
|
||||||
|
pub struct PagebreakElem {
|
||||||
|
/// If `{true}`, the page break is skipped if the current page is already
|
||||||
|
/// empty.
|
||||||
|
#[default(false)]
|
||||||
|
pub weak: bool,
|
||||||
|
|
||||||
|
/// If given, ensures that the next page will be an even/odd page, with an
|
||||||
|
/// empty page in between if necessary.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set page(height: 30pt)
|
||||||
|
///
|
||||||
|
/// First.
|
||||||
|
/// #pagebreak(to: "odd")
|
||||||
|
/// Third.
|
||||||
|
/// ```
|
||||||
|
pub to: Option<Parity>,
|
||||||
|
|
||||||
|
/// Whether this pagebreak designates an end boundary of a page run. This is
|
||||||
|
/// an even weaker version of pagebreak `weak` because it not only doesn't
|
||||||
|
/// force an empty page, but also doesn't force its initial styles onto a
|
||||||
|
/// staged empty page.
|
||||||
#[internal]
|
#[internal]
|
||||||
#[synthesized]
|
#[parse(None)]
|
||||||
pub clear_to: Option<Parity>,
|
#[default(false)]
|
||||||
|
pub boundary: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Behave for Packed<PagebreakElem> {
|
||||||
|
fn behaviour(&self) -> Behaviour {
|
||||||
|
Behaviour::Destructive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PagebreakElem {
|
||||||
|
/// Get the globally shared weak pagebreak element.
|
||||||
|
pub fn shared_weak() -> &'static Content {
|
||||||
|
singleton!(Content, PagebreakElem::new().with_weak(true).pack())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the globally shared boundary pagebreak element.
|
||||||
|
pub fn shared_boundary() -> &'static Content {
|
||||||
|
singleton!(
|
||||||
|
Content,
|
||||||
|
PagebreakElem::new().with_weak(true).with_boundary(true).pack()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A finished page.
|
/// A finished page.
|
||||||
@ -598,39 +695,6 @@ impl PageRanges {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A manual page break.
|
|
||||||
///
|
|
||||||
/// Must not be used inside any containers.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
/// ```example
|
|
||||||
/// The next page contains
|
|
||||||
/// more details on compound theory.
|
|
||||||
/// #pagebreak()
|
|
||||||
///
|
|
||||||
/// == Compound Theory
|
|
||||||
/// In 1984, the first ...
|
|
||||||
/// ```
|
|
||||||
#[elem(title = "Page Break")]
|
|
||||||
pub struct PagebreakElem {
|
|
||||||
/// If `{true}`, the page break is skipped if the current page is already
|
|
||||||
/// empty.
|
|
||||||
#[default(false)]
|
|
||||||
pub weak: bool,
|
|
||||||
|
|
||||||
/// If given, ensures that the next page will be an even/odd page, with an
|
|
||||||
/// empty page in between if necessary.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set page(height: 30pt)
|
|
||||||
///
|
|
||||||
/// First.
|
|
||||||
/// #pagebreak(to: "odd")
|
|
||||||
/// Third.
|
|
||||||
/// ```
|
|
||||||
pub to: Option<Parity>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether something should be even or odd.
|
/// Whether something should be even or odd.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
pub enum Parity {
|
pub enum Parity {
|
||||||
|
@ -177,7 +177,7 @@ pub struct FlushElem {}
|
|||||||
|
|
||||||
impl Behave for Packed<FlushElem> {
|
impl Behave for Packed<FlushElem> {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
Behaviour::Invisible
|
Behaviour::Ignorant
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,14 +11,13 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{Content, Packed, StyleChain};
|
use crate::foundations::{Content, Packed, StyleChain, StyleVec};
|
||||||
use crate::introspection::{Locator, SplitLocator};
|
use crate::introspection::{Locator, SplitLocator};
|
||||||
use crate::layout::{layout_frame, Abs, Axes, BoxElem, Em, Frame, Region, Size};
|
use crate::layout::{layout_frame, Abs, Axes, BoxElem, Em, Frame, Region, Size};
|
||||||
use crate::math::{
|
use crate::math::{
|
||||||
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
|
scaled_font_size, styled_char, EquationElem, FrameFragment, GlyphFragment,
|
||||||
LayoutMath, MathFragment, MathRun, MathSize, THICK,
|
LayoutMath, MathFragment, MathRun, MathSize, THICK,
|
||||||
};
|
};
|
||||||
use crate::realize::StyleVec;
|
|
||||||
use crate::syntax::{is_newline, Span};
|
use crate::syntax::{is_newline, Span};
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
features, BottomEdge, BottomEdgeMetric, Font, TextElem, TextSize, TopEdge,
|
features, BottomEdge, BottomEdgeMetric, Font, TextElem, TextSize, TopEdge,
|
||||||
|
@ -6,6 +6,7 @@ use ttf_parser::{GlyphId, Rect};
|
|||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
use crate::foundations::StyleChain;
|
use crate::foundations::StyleChain;
|
||||||
|
use crate::introspection::Tag;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Corner, Em, Frame, FrameItem, HideElem, Point, Size, VAlignment,
|
Abs, Corner, Em, Frame, FrameItem, HideElem, Point, Size, VAlignment,
|
||||||
};
|
};
|
||||||
@ -26,6 +27,7 @@ pub enum MathFragment {
|
|||||||
Space(Abs),
|
Space(Abs),
|
||||||
Linebreak,
|
Linebreak,
|
||||||
Align,
|
Align,
|
||||||
|
Tag(Tag),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathFragment {
|
impl MathFragment {
|
||||||
@ -74,6 +76,7 @@ impl MathFragment {
|
|||||||
pub fn is_ignorant(&self) -> bool {
|
pub fn is_ignorant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Frame(fragment) => fragment.ignorant,
|
Self::Frame(fragment) => fragment.ignorant,
|
||||||
|
Self::Tag(_) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,6 +90,7 @@ impl MathFragment {
|
|||||||
Self::Space(_) => MathClass::Space,
|
Self::Space(_) => MathClass::Space,
|
||||||
Self::Linebreak => MathClass::Space,
|
Self::Linebreak => MathClass::Space,
|
||||||
Self::Align => MathClass::Special,
|
Self::Align => MathClass::Special,
|
||||||
|
Self::Tag(_) => MathClass::Special,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +176,11 @@ impl MathFragment {
|
|||||||
Self::Glyph(glyph) => glyph.into_frame(),
|
Self::Glyph(glyph) => glyph.into_frame(),
|
||||||
Self::Variant(variant) => variant.frame,
|
Self::Variant(variant) => variant.frame,
|
||||||
Self::Frame(fragment) => fragment.frame,
|
Self::Frame(fragment) => fragment.frame,
|
||||||
|
Self::Tag(tag) => {
|
||||||
|
let mut frame = Frame::soft(Size::zero());
|
||||||
|
frame.push(Point::zero(), FrameItem::Tag(tag));
|
||||||
|
frame
|
||||||
|
}
|
||||||
_ => Frame::soft(self.size()),
|
_ => Frame::soft(self.size()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,10 +47,9 @@ use crate::foundations::{
|
|||||||
category, Category, Content, Module, Resolve, Scope, SequenceElem, StyleChain,
|
category, Category, Content, Module, Resolve, Scope, SequenceElem, StyleChain,
|
||||||
StyledElem,
|
StyledElem,
|
||||||
};
|
};
|
||||||
use crate::introspection::TagElem;
|
use crate::introspection::{TagElem, TagKind};
|
||||||
use crate::layout::{BoxElem, Frame, FrameItem, HElem, Point, Size, Spacing, VAlignment};
|
use crate::layout::{BoxElem, HElem, Spacing, VAlignment};
|
||||||
use crate::realize::Behaviour;
|
use crate::realize::{process, BehavedBuilder, Behaviour};
|
||||||
use crate::realize::{process, BehavedBuilder};
|
|
||||||
use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
||||||
|
|
||||||
/// Typst has special [syntax]($syntax/#math) and library functions to typeset
|
/// Typst has special [syntax]($syntax/#math) and library functions to typeset
|
||||||
@ -237,8 +236,17 @@ impl LayoutMath for Content {
|
|||||||
return elem.layout_math(ctx, styles);
|
return elem.layout_math(ctx, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(realized) = process(ctx.engine, &mut ctx.locator, self, styles)? {
|
if let Some((tag, realized)) =
|
||||||
return realized.layout_math(ctx, styles);
|
process(ctx.engine, &mut ctx.locator, self, styles)?
|
||||||
|
{
|
||||||
|
if let Some(tag) = &tag {
|
||||||
|
ctx.push(MathFragment::Tag(tag.clone()));
|
||||||
|
}
|
||||||
|
realized.layout_math(ctx, styles)?;
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
ctx.push(MathFragment::Tag(tag.with_kind(TagKind::End)));
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.is::<SequenceElem>() {
|
if self.is::<SequenceElem>() {
|
||||||
@ -302,9 +310,7 @@ impl LayoutMath for Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(elem) = self.to_packed::<TagElem>() {
|
if let Some(elem) = self.to_packed::<TagElem>() {
|
||||||
let mut frame = Frame::soft(Size::zero());
|
ctx.push(MathFragment::Tag(elem.tag.clone()));
|
||||||
frame.push(Point::zero(), FrameItem::Tag(elem.tag.clone()));
|
|
||||||
ctx.push(FrameFragment::new(ctx, styles, frame).with_ignorant(true));
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,10 +327,7 @@ impl LayoutMath for Content {
|
|||||||
ctx.push(
|
ctx.push(
|
||||||
FrameFragment::new(ctx, styles, frame)
|
FrameFragment::new(ctx, styles, frame)
|
||||||
.with_spaced(true)
|
.with_spaced(true)
|
||||||
.with_ignorant(matches!(
|
.with_ignorant(self.behaviour() == Behaviour::Ignorant),
|
||||||
self.behaviour(),
|
|
||||||
Behaviour::Invisible | Behaviour::Ignorant
|
|
||||||
)),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -3,10 +3,10 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, Unlabellable,
|
elem, Args, Cast, Construct, Content, NativeElement, Packed, Set, Smart, StyleVec,
|
||||||
|
Unlabellable,
|
||||||
};
|
};
|
||||||
use crate::layout::{Em, Length};
|
use crate::layout::{Em, Length};
|
||||||
use crate::realize::StyleVec;
|
|
||||||
use crate::utils::singleton;
|
use crate::utils::singleton;
|
||||||
|
|
||||||
/// Arranges text, spacing and inline-level elements into a paragraph.
|
/// Arranges text, spacing and inline-level elements into a paragraph.
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
use crate::foundations::{Content, StyleChain};
|
use crate::foundations::{Content, StyleChain, Styles};
|
||||||
|
|
||||||
/// Temporary storage arenas for building.
|
/// Temporary storage arenas for building.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Arenas<'a> {
|
pub struct Arenas<'a> {
|
||||||
chains: Arena<StyleChain<'a>>,
|
chains: Arena<StyleChain<'a>>,
|
||||||
content: Arena<Content>,
|
content: Arena<Content>,
|
||||||
|
styles: Arena<Styles>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Arenas<'a> {
|
impl<'a> Arenas<'a> {
|
||||||
@ -32,3 +33,9 @@ impl<'a> Store<'a> for StyleChain<'a> {
|
|||||||
arenas.chains.alloc(self)
|
arenas.chains.alloc(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Store<'a> for Styles {
|
||||||
|
fn store(self, arenas: &'a Arenas<'a>) -> &'a Self {
|
||||||
|
arenas.styles.alloc(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
//! Element interaction.
|
//! Element interaction.
|
||||||
|
|
||||||
use std::fmt::{Debug, Formatter};
|
use crate::foundations::{Content, StyleChain};
|
||||||
|
|
||||||
use ecow::EcoVec;
|
|
||||||
|
|
||||||
use crate::foundations::{Content, StyleChain, Styles};
|
|
||||||
|
|
||||||
/// How an element interacts with other elements in a stream.
|
/// How an element interacts with other elements in a stream.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
@ -19,12 +15,8 @@ pub enum Behaviour {
|
|||||||
/// An element that destroys adjacent weak elements.
|
/// An element that destroys adjacent weak elements.
|
||||||
Destructive,
|
Destructive,
|
||||||
/// An element that does not interact at all with other elements, having the
|
/// An element that does not interact at all with other elements, having the
|
||||||
/// same effect as if it didn't exist, but has layout extent and/or a visual
|
/// same effect on them as if it didn't exist.
|
||||||
/// representation.
|
|
||||||
Ignorant,
|
Ignorant,
|
||||||
/// An element that does not have any layout extent or visual
|
|
||||||
/// representation.
|
|
||||||
Invisible,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Behaviour {
|
impl Behaviour {
|
||||||
@ -69,15 +61,6 @@ impl<'a> BehavedBuilder<'a> {
|
|||||||
self.buf.is_empty()
|
self.buf.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the builder has any proper (non-weak & visible) elements.
|
|
||||||
pub fn has_strong_elements(&self, last: bool) -> bool {
|
|
||||||
self.buf.iter().any(|(content, _)| {
|
|
||||||
let behaviour = content.behaviour();
|
|
||||||
!matches!(behaviour, Behaviour::Weak(_) | Behaviour::Invisible)
|
|
||||||
|| (last && behaviour == Behaviour::Invisible)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push an item into the builder.
|
/// Push an item into the builder.
|
||||||
pub fn push(&mut self, content: &'a Content, styles: StyleChain<'a>) {
|
pub fn push(&mut self, content: &'a Content, styles: StyleChain<'a>) {
|
||||||
let mut behaviour = content.behaviour();
|
let mut behaviour = content.behaviour();
|
||||||
@ -112,7 +95,7 @@ impl<'a> BehavedBuilder<'a> {
|
|||||||
self.buf.remove(i);
|
self.buf.remove(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behaviour::Ignorant | Behaviour::Invisible => {
|
Behaviour::Ignorant => {
|
||||||
behaviour = self.last;
|
behaviour = self.last;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,162 +135,3 @@ impl<'a> Default for BehavedBuilder<'a> {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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: &[(&Content, StyleChain<'a>)]) -> (Self, StyleChain<'a>) {
|
|
||||||
let (trunk, depth) = determine_style_trunk(buf);
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The raw, unstyled elements.
|
|
||||||
pub fn elements(&self) -> &[Content] {
|
|
||||||
&self.elements
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over the contained content and style chains.
|
|
||||||
pub fn chain<'a>(
|
|
||||||
&'a self,
|
|
||||||
outer: &'a StyleChain<'_>,
|
|
||||||
) -> impl Iterator<Item = (&'a Content, StyleChain<'a>)> {
|
|
||||||
self.iter().map(|(element, local)| (element, outer.chain(local)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over pairs of content and styles.
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&Content, &Styles)> {
|
|
||||||
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)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over pairs of content and styles.
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn into_iter(self) -> impl Iterator<Item = (Content, Styles)> {
|
|
||||||
self.elements.into_iter().zip(
|
|
||||||
self.styles
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|(local, count)| std::iter::repeat(local).take(count))
|
|
||||||
.chain(std::iter::repeat(Styles::new())),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for StyleVec {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
|
||||||
f.debug_list()
|
|
||||||
.entries(self.iter().map(|(element, local)| {
|
|
||||||
typst_utils::debug(|f| {
|
|
||||||
for style in local.iter() {
|
|
||||||
writeln!(f, "#{style:?}")?;
|
|
||||||
}
|
|
||||||
element.fmt(f)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determine the shared trunk style chain.
|
|
||||||
fn determine_style_trunk<'a, T>(buf: &[(T, StyleChain<'a>)]) -> (StyleChain<'a>, usize) {
|
|
||||||
// Determine shared style depth and first span.
|
|
||||||
let mut trunk = match buf.first() {
|
|
||||||
Some(&(_, chain)) => chain,
|
|
||||||
None => Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut depth = trunk.links().count();
|
|
||||||
for (_, mut chain) in buf {
|
|
||||||
let len = chain.links().count();
|
|
||||||
if len < depth {
|
|
||||||
for _ in 0..depth - len {
|
|
||||||
trunk.pop();
|
|
||||||
}
|
|
||||||
depth = len;
|
|
||||||
} else if len > depth {
|
|
||||||
for _ in 0..len - depth {
|
|
||||||
chain.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while depth > 0 && chain != trunk {
|
|
||||||
trunk.pop();
|
|
||||||
chain.pop();
|
|
||||||
depth -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(trunk, depth)
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
//! Realization of content.
|
//! Realization of content.
|
||||||
//!
|
//!
|
||||||
//! *Realization* is the process of applying show rules to produce
|
//! *Realization* is the process of recursively applying styling and, in
|
||||||
//! something that can be laid out directly.
|
//! particular, show rules to produce well-known elements that can be laid out.
|
||||||
//!
|
|
||||||
//! Currently, there are issues with the realization process, and
|
|
||||||
//! it is subject to changes in the future.
|
|
||||||
|
|
||||||
mod arenas;
|
mod arenas;
|
||||||
mod behaviour;
|
mod behaviour;
|
||||||
@ -13,7 +10,7 @@ mod process;
|
|||||||
use once_cell::unsync::Lazy;
|
use once_cell::unsync::Lazy;
|
||||||
|
|
||||||
pub use self::arenas::Arenas;
|
pub use self::arenas::Arenas;
|
||||||
pub use self::behaviour::{Behave, BehavedBuilder, Behaviour, StyleVec};
|
pub use self::behaviour::{Behave, BehavedBuilder, Behaviour};
|
||||||
pub use self::process::process;
|
pub use self::process::process;
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
@ -21,12 +18,13 @@ use std::mem;
|
|||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::{bail, SourceResult};
|
||||||
use crate::engine::{Engine, Route};
|
use crate::engine::{Engine, Route};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Content, NativeElement, Packed, SequenceElem, Smart, StyleChain, StyledElem, Styles,
|
Content, ContextElem, NativeElement, Packed, SequenceElem, Smart, StyleChain,
|
||||||
|
StyleVec, StyledElem, Styles,
|
||||||
};
|
};
|
||||||
use crate::introspection::{SplitLocator, TagElem};
|
use crate::introspection::{SplitLocator, TagElem, TagKind};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
AlignElem, BlockElem, BoxElem, ColbreakElem, FlowElem, FlushElem, HElem, InlineElem,
|
AlignElem, BlockElem, BoxElem, ColbreakElem, FlushElem, HElem, InlineElem, PageElem,
|
||||||
PageElem, PagebreakElem, Parity, PlaceElem, VElem,
|
PagebreakElem, PlaceElem, VElem,
|
||||||
};
|
};
|
||||||
use crate::math::{EquationElem, LayoutMath};
|
use crate::math::{EquationElem, LayoutMath};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
@ -35,41 +33,42 @@ use crate::model::{
|
|||||||
};
|
};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
|
use crate::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
|
||||||
|
use crate::utils::SliceExt;
|
||||||
|
|
||||||
/// A pair of content and a style chain that applies to it.
|
/// A pair of content and a style chain that applies to it.
|
||||||
type Pair<'a> = (&'a Content, StyleChain<'a>);
|
pub type Pair<'a> = (&'a Content, StyleChain<'a>);
|
||||||
|
|
||||||
/// Realize at the root-level.
|
/// Realize at the root level.
|
||||||
#[typst_macros::time(name = "realize root")]
|
#[typst_macros::time(name = "realize")]
|
||||||
pub fn realize_root<'a>(
|
pub fn realize_root<'a>(
|
||||||
engine: &mut Engine<'a>,
|
engine: &mut Engine<'a>,
|
||||||
locator: &mut SplitLocator<'a>,
|
locator: &mut SplitLocator<'a>,
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(StyleVec, StyleChain<'a>, DocumentInfo)> {
|
) -> SourceResult<(Vec<Pair<'a>>, DocumentInfo)> {
|
||||||
let mut builder = Builder::new(engine, locator, arenas, true);
|
let mut builder = Builder::new(engine, locator, arenas, true);
|
||||||
builder.accept(content, styles)?;
|
builder.accept(content, styles)?;
|
||||||
builder.interrupt_page(Some(styles), true)?;
|
builder.interrupt_par()?;
|
||||||
Ok(builder.doc.unwrap().finish())
|
Ok((builder.sink.finish(), builder.doc_info.unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Realize into a `FlowElem`, an element that is capable of block-level layout.
|
/// Realize at the container level.
|
||||||
#[typst_macros::time(name = "realize flow")]
|
#[typst_macros::time(name = "realize")]
|
||||||
pub fn realize_flow<'a>(
|
pub fn realizer_container<'a>(
|
||||||
engine: &mut Engine<'a>,
|
engine: &mut Engine<'a>,
|
||||||
locator: &mut SplitLocator<'a>,
|
locator: &mut SplitLocator<'a>,
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(Packed<FlowElem>, StyleChain<'a>)> {
|
) -> SourceResult<Vec<Pair<'a>>> {
|
||||||
let mut builder = Builder::new(engine, locator, arenas, false);
|
let mut builder = Builder::new(engine, locator, arenas, false);
|
||||||
builder.accept(content, styles)?;
|
builder.accept(content, styles)?;
|
||||||
builder.interrupt_par()?;
|
builder.interrupt_par()?;
|
||||||
Ok(builder.flow.finish())
|
Ok(builder.sink.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a document or a flow element from content.
|
/// Realizes content into a flat list of well-known, styled elements.
|
||||||
struct Builder<'a, 'v> {
|
struct Builder<'a, 'v> {
|
||||||
/// The engine.
|
/// The engine.
|
||||||
engine: &'v mut Engine<'a>,
|
engine: &'v mut Engine<'a>,
|
||||||
@ -77,34 +76,47 @@ struct Builder<'a, 'v> {
|
|||||||
locator: &'v mut SplitLocator<'a>,
|
locator: &'v mut SplitLocator<'a>,
|
||||||
/// Scratch arenas for building.
|
/// Scratch arenas for building.
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
/// The current document building state.
|
|
||||||
doc: Option<DocBuilder<'a>>,
|
/// The output elements of well-known types collected by the builder.
|
||||||
/// The current flow building state.
|
sink: BehavedBuilder<'a>,
|
||||||
flow: FlowBuilder<'a>,
|
/// Document metadata we have collected from `set document` rules. If this
|
||||||
/// The current paragraph building state.
|
/// is `None`, we are in a container.
|
||||||
|
doc_info: Option<DocumentInfo>,
|
||||||
|
|
||||||
|
/// A builder for a paragraph that might be under construction.
|
||||||
par: ParBuilder<'a>,
|
par: ParBuilder<'a>,
|
||||||
/// The current list building state.
|
/// A builder for a list that might be under construction.
|
||||||
list: ListBuilder<'a>,
|
list: ListBuilder<'a>,
|
||||||
/// The current citation grouping state.
|
/// A builder for a citation group that might be under construction.
|
||||||
cites: CiteGroupBuilder<'a>,
|
cites: CiteGroupBuilder<'a>,
|
||||||
|
|
||||||
|
/// Whether we are currently not within any container or show rule output.
|
||||||
|
/// This is used to determine page styles during layout.
|
||||||
|
outside: bool,
|
||||||
|
/// Whether the last item that we visited was a paragraph (with no parbreak
|
||||||
|
/// in between). This is used for attach spacing.
|
||||||
|
last_was_par: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'v> Builder<'a, 'v> {
|
impl<'a, 'v> Builder<'a, 'v> {
|
||||||
|
/// Creates a new builder.
|
||||||
fn new(
|
fn new(
|
||||||
engine: &'v mut Engine<'a>,
|
engine: &'v mut Engine<'a>,
|
||||||
locator: &'v mut SplitLocator<'a>,
|
locator: &'v mut SplitLocator<'a>,
|
||||||
arenas: &'a Arenas<'a>,
|
arenas: &'a Arenas<'a>,
|
||||||
top: bool,
|
root: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
engine,
|
engine,
|
||||||
locator,
|
locator,
|
||||||
arenas,
|
arenas,
|
||||||
doc: top.then(DocBuilder::default),
|
sink: BehavedBuilder::default(),
|
||||||
flow: FlowBuilder::default(),
|
doc_info: root.then(DocumentInfo::default),
|
||||||
par: ParBuilder::default(),
|
par: ParBuilder::default(),
|
||||||
list: ListBuilder::default(),
|
list: ListBuilder::default(),
|
||||||
cites: CiteGroupBuilder::default(),
|
cites: CiteGroupBuilder::default(),
|
||||||
|
outside: root,
|
||||||
|
last_was_par: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,7 +135,9 @@ impl<'a, 'v> Builder<'a, 'v> {
|
|||||||
|
|
||||||
// Styled elements and sequences can (at least currently) also have
|
// Styled elements and sequences can (at least currently) also have
|
||||||
// labels, so this needs to happen before they are handled.
|
// labels, so this needs to happen before they are handled.
|
||||||
if let Some(realized) = process(self.engine, self.locator, content, styles)? {
|
if let Some((tag, realized)) =
|
||||||
|
process(self.engine, self.locator, content, styles)?
|
||||||
|
{
|
||||||
self.engine.route.increase();
|
self.engine.route.increase();
|
||||||
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
|
if !self.engine.route.within(Route::MAX_SHOW_RULE_DEPTH) {
|
||||||
bail!(
|
bail!(
|
||||||
@ -131,9 +145,23 @@ impl<'a, 'v> Builder<'a, 'v> {
|
|||||||
hint: "check whether the show rule matches its own output"
|
hint: "check whether the show rule matches its own output"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let result = self.accept(self.arenas.store(realized), styles);
|
|
||||||
|
if let Some(tag) = &tag {
|
||||||
|
self.accept(self.arenas.store(TagElem::packed(tag.clone())), styles)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev_outside = self.outside;
|
||||||
|
self.outside &= content.is::<ContextElem>();
|
||||||
|
self.accept(self.arenas.store(realized), styles)?;
|
||||||
|
self.outside = prev_outside;
|
||||||
|
|
||||||
|
if let Some(tag) = tag {
|
||||||
|
let end = tag.with_kind(TagKind::End);
|
||||||
|
self.accept(self.arenas.store(TagElem::packed(end)), styles)?;
|
||||||
|
}
|
||||||
|
|
||||||
self.engine.route.decrease();
|
self.engine.route.decrease();
|
||||||
return result;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(styled) = content.to_packed::<StyledElem>() {
|
if let Some(styled) = content.to_packed::<StyledElem>() {
|
||||||
@ -148,6 +176,7 @@ impl<'a, 'v> Builder<'a, 'v> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to merge `content` with an element under construction
|
// Try to merge `content` with an element under construction
|
||||||
|
// (cite group, list, or par).
|
||||||
|
|
||||||
if self.cites.accept(content, styles) {
|
if self.cites.accept(content, styles) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -161,6 +190,7 @@ impl<'a, 'v> Builder<'a, 'v> {
|
|||||||
|
|
||||||
self.interrupt_list()?;
|
self.interrupt_list()?;
|
||||||
|
|
||||||
|
// Try again because it could be another kind of list.
|
||||||
if self.list.accept(content, styles) {
|
if self.list.accept(content, styles) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -171,285 +201,165 @@ impl<'a, 'v> Builder<'a, 'v> {
|
|||||||
|
|
||||||
self.interrupt_par()?;
|
self.interrupt_par()?;
|
||||||
|
|
||||||
if self.flow.accept(self.arenas, content, styles) {
|
self.save(content, styles)
|
||||||
return Ok(());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let keep = content
|
/// Tries to save a piece of content into the sink.
|
||||||
.to_packed::<PagebreakElem>()
|
fn save(&mut self, content: &'a Content, styles: StyleChain<'a>) -> SourceResult<()> {
|
||||||
.is_some_and(|pagebreak| !pagebreak.weak(styles));
|
let last_was_par = std::mem::replace(&mut self.last_was_par, false);
|
||||||
|
let par_spacing = Lazy::new(|| {
|
||||||
|
self.arenas
|
||||||
|
.store(VElem::par_spacing(ParElem::spacing_in(styles).into()).pack())
|
||||||
|
});
|
||||||
|
|
||||||
self.interrupt_page(keep.then_some(styles), false)?;
|
if content.is::<TagElem>()
|
||||||
|
|| content.is::<PlaceElem>()
|
||||||
if let Some(doc) = &mut self.doc {
|
|| content.is::<FlushElem>()
|
||||||
if doc.accept(self.arenas, content, styles) {
|
|| content.is::<ColbreakElem>()
|
||||||
return Ok(());
|
{
|
||||||
|
self.sink.push(content, styles);
|
||||||
|
} else if content.is::<PagebreakElem>() {
|
||||||
|
if self.doc_info.is_none() {
|
||||||
|
bail!(content.span(), "pagebreaks are not allowed inside of containers");
|
||||||
}
|
}
|
||||||
}
|
self.sink.push(content, styles);
|
||||||
|
} else if let Some(elem) = content.to_packed::<VElem>() {
|
||||||
|
if !elem.attach(styles) || last_was_par {
|
||||||
|
self.sink.push(content, styles);
|
||||||
|
}
|
||||||
|
} else if content.is::<ParbreakElem>() {
|
||||||
|
// It's only a boundary, so we can ignore it.
|
||||||
|
} else if content.is::<ParElem>() {
|
||||||
|
self.sink.push(*par_spacing, styles);
|
||||||
|
self.sink.push(content, styles);
|
||||||
|
self.sink.push(*par_spacing, styles);
|
||||||
|
self.last_was_par = true;
|
||||||
|
} else if let Some(elem) = content.to_packed::<BlockElem>() {
|
||||||
|
let above = match elem.above(styles) {
|
||||||
|
Smart::Auto => *par_spacing,
|
||||||
|
Smart::Custom(above) => {
|
||||||
|
self.arenas.store(VElem::block_spacing(above).pack())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if content.is::<PagebreakElem>() {
|
let below = match elem.below(styles) {
|
||||||
bail!(content.span(), "pagebreaks are not allowed inside of containers");
|
Smart::Auto => *par_spacing,
|
||||||
|
Smart::Custom(below) => {
|
||||||
|
self.arenas.store(VElem::block_spacing(below).pack())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.sink.push(above, styles);
|
||||||
|
self.sink.push(content, styles);
|
||||||
|
self.sink.push(below, styles);
|
||||||
} else {
|
} else {
|
||||||
bail!(content.span(), "{} is not allowed here", content.func().name());
|
bail!(content.span(), "{} is not allowed here", content.func().name());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles a styled element.
|
||||||
fn styled(
|
fn styled(
|
||||||
&mut self,
|
&mut self,
|
||||||
styled: &'a StyledElem,
|
styled: &'a StyledElem,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let local = &styled.styles;
|
if let Some(span) = styled.styles.interruption::<DocumentElem>() {
|
||||||
let stored = self.arenas.store(styles);
|
let Some(info) = &mut self.doc_info else {
|
||||||
let styles = stored.chain(local);
|
|
||||||
|
|
||||||
if let Some(Some(span)) = local.interruption::<DocumentElem>() {
|
|
||||||
let Some(doc) = &mut self.doc else {
|
|
||||||
bail!(span, "document set rules are not allowed inside of containers");
|
bail!(span, "document set rules are not allowed inside of containers");
|
||||||
};
|
};
|
||||||
doc.info.populate(local);
|
info.populate(&styled.styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interrupt_style(local, None)?;
|
let page_interruption = styled.styles.interruption::<PageElem>();
|
||||||
self.accept(&styled.child, styles)?;
|
if let Some(span) = page_interruption {
|
||||||
self.interrupt_style(local, Some(styles))?;
|
if self.doc_info.is_none() {
|
||||||
|
bail!(span, "page configuration is not allowed inside of containers");
|
||||||
|
}
|
||||||
|
|
||||||
|
// When there are page styles, we "break free" from our show rule
|
||||||
|
// cage.
|
||||||
|
self.outside = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are not within a container or show rule, mark the styles as
|
||||||
|
// "outside". This will allow them to be lifted to the page level.
|
||||||
|
let outer = self.arenas.store(styles);
|
||||||
|
let local = if self.outside {
|
||||||
|
self.arenas.store(styled.styles.clone().outside())
|
||||||
|
} else {
|
||||||
|
&styled.styles
|
||||||
|
};
|
||||||
|
|
||||||
|
if page_interruption.is_some() {
|
||||||
|
// For the starting pagebreak we only want the styles before and
|
||||||
|
// including the interruptions, not trailing styles that happen to
|
||||||
|
// be in the same `Styles` list.
|
||||||
|
let relevant = local
|
||||||
|
.as_slice()
|
||||||
|
.trim_end_matches(|style| style.interruption::<PageElem>().is_none());
|
||||||
|
self.accept(PagebreakElem::shared_weak(), outer.chain(relevant))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interrupt_styles(local)?;
|
||||||
|
self.accept(&styled.child, outer.chain(local))?;
|
||||||
|
self.interrupt_styles(local)?;
|
||||||
|
|
||||||
|
if page_interruption.is_some() {
|
||||||
|
// For the ending pagebreak, the styles don't really matter because
|
||||||
|
// the styles of a "boundary" pagebreak are ignored during layout.
|
||||||
|
self.accept(PagebreakElem::shared_boundary(), *outer)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interrupt_style(
|
/// Inspects the styles and dispatches to the different interruption
|
||||||
&mut self,
|
/// handlers.
|
||||||
local: &Styles,
|
fn interrupt_styles(&mut self, local: &Styles) -> SourceResult<()> {
|
||||||
outer: Option<StyleChain<'a>>,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
if let Some(Some(span)) = local.interruption::<PageElem>() {
|
|
||||||
if self.doc.is_none() {
|
|
||||||
bail!(span, "page configuration is not allowed inside of containers");
|
|
||||||
}
|
|
||||||
self.interrupt_page(outer, false)?;
|
|
||||||
}
|
|
||||||
if local.interruption::<ParElem>().is_some()
|
if local.interruption::<ParElem>().is_some()
|
||||||
|| local.interruption::<AlignElem>().is_some()
|
|| local.interruption::<AlignElem>().is_some()
|
||||||
{
|
{
|
||||||
self.interrupt_par()?;
|
self.interrupt_par()?;
|
||||||
}
|
} else if local.interruption::<ListElem>().is_some()
|
||||||
if local.interruption::<ListElem>().is_some()
|
|
||||||
|| local.interruption::<EnumElem>().is_some()
|
|| local.interruption::<EnumElem>().is_some()
|
||||||
|| local.interruption::<TermsElem>().is_some()
|
|| local.interruption::<TermsElem>().is_some()
|
||||||
{
|
{
|
||||||
self.interrupt_list()?;
|
self.interrupt_list()?;
|
||||||
|
} else if local.interruption::<CiteElem>().is_some() {
|
||||||
|
self.interrupt_cites()?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interrupts citation grouping and adds the resulting citation group to the builder.
|
/// Interrupts paragraph building and adds the resulting paragraph element
|
||||||
fn interrupt_cites(&mut self) -> SourceResult<()> {
|
/// to the builder.
|
||||||
if !self.cites.items.is_empty() {
|
|
||||||
let staged = mem::take(&mut self.cites.staged);
|
|
||||||
let (group, styles) = mem::take(&mut self.cites).finish();
|
|
||||||
self.accept(self.arenas.store(group.pack()), styles)?;
|
|
||||||
for (content, styles) in staged {
|
|
||||||
self.accept(content, styles)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interrupts list building and adds the resulting list element to the builder.
|
|
||||||
fn interrupt_list(&mut self) -> SourceResult<()> {
|
|
||||||
self.interrupt_cites()?;
|
|
||||||
if !self.list.items.is_empty() {
|
|
||||||
let staged = mem::take(&mut self.list.staged);
|
|
||||||
let (list, styles) = mem::take(&mut self.list).finish();
|
|
||||||
self.accept(self.arenas.store(list), styles)?;
|
|
||||||
for (content, styles) in staged {
|
|
||||||
self.accept(content, styles)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interrupts paragraph building and adds the resulting paragraph element to the builder.
|
|
||||||
fn interrupt_par(&mut self) -> SourceResult<()> {
|
fn interrupt_par(&mut self) -> SourceResult<()> {
|
||||||
self.interrupt_list()?;
|
self.interrupt_list()?;
|
||||||
if !self.par.0.is_empty() {
|
if !self.par.0.is_empty() {
|
||||||
let (par, styles) = mem::take(&mut self.par).finish();
|
mem::take(&mut self.par).finish(self)?;
|
||||||
self.accept(self.arenas.store(par.pack()), styles)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Interrupts page building and adds the resulting page element to the builder.
|
|
||||||
fn interrupt_page(
|
|
||||||
&mut self,
|
|
||||||
styles: Option<StyleChain<'a>>,
|
|
||||||
last: bool,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
self.interrupt_par()?;
|
|
||||||
let Some(doc) = &mut self.doc else { return Ok(()) };
|
|
||||||
if (doc.keep_next && styles.is_some()) || self.flow.0.has_strong_elements(last) {
|
|
||||||
let (flow, trunk) = mem::take(&mut self.flow).finish();
|
|
||||||
let span = flow.span();
|
|
||||||
let styles = if trunk == StyleChain::default() {
|
|
||||||
styles.unwrap_or_default()
|
|
||||||
} else {
|
|
||||||
trunk
|
|
||||||
};
|
|
||||||
let page = PageElem::new(flow.pack()).pack().spanned(span);
|
|
||||||
self.accept(self.arenas.store(page), styles)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a [document][DocumentElem] from pagebreaks and pages.
|
/// Interrupts list building and adds the resulting list element to the
|
||||||
struct DocBuilder<'a> {
|
/// builder.
|
||||||
/// The page runs built so far.
|
fn interrupt_list(&mut self) -> SourceResult<()> {
|
||||||
pages: BehavedBuilder<'a>,
|
self.interrupt_cites()?;
|
||||||
/// Whether to keep a following page even if it is empty.
|
if !self.list.0.is_empty() {
|
||||||
keep_next: bool,
|
mem::take(&mut self.list).finish(self)?;
|
||||||
/// Whether the next page should be cleared to an even or odd number.
|
|
||||||
clear_next: Option<Parity>,
|
|
||||||
/// Details about the document.
|
|
||||||
info: DocumentInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DocBuilder<'a> {
|
|
||||||
/// Tries to accept a piece of content.
|
|
||||||
///
|
|
||||||
/// Returns true if this content could be merged into the document.
|
|
||||||
/// If this function returns false, then the
|
|
||||||
/// content could not be merged, and document building should be
|
|
||||||
/// interrupted so that the content can be added elsewhere.
|
|
||||||
fn accept(
|
|
||||||
&mut self,
|
|
||||||
arenas: &'a Arenas<'a>,
|
|
||||||
content: &'a Content,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> bool {
|
|
||||||
if let Some(pagebreak) = content.to_packed::<PagebreakElem>() {
|
|
||||||
self.keep_next = !pagebreak.weak(styles);
|
|
||||||
self.clear_next = pagebreak.to(styles);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
if let Some(page) = content.to_packed::<PageElem>() {
|
|
||||||
let elem = if let Some(clear_to) = self.clear_next.take() {
|
|
||||||
let mut page = page.clone();
|
|
||||||
page.push_clear_to(Some(clear_to));
|
|
||||||
arenas.store(page.pack())
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
};
|
|
||||||
|
|
||||||
self.pages.push(elem, styles);
|
|
||||||
self.keep_next = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this builder into the resulting page runs, along with
|
/// Interrupts citation grouping and adds the resulting citation group to
|
||||||
/// its [style chain][StyleChain].
|
/// the builder.
|
||||||
fn finish(self) -> (StyleVec, StyleChain<'a>, DocumentInfo) {
|
fn interrupt_cites(&mut self) -> SourceResult<()> {
|
||||||
let buf = self.pages.finish();
|
if !self.cites.0.is_empty() {
|
||||||
let (children, trunk) = StyleVec::create(&buf);
|
mem::take(&mut self.cites).finish(self)?;
|
||||||
(children, trunk, self.info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DocBuilder<'_> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
pages: BehavedBuilder::new(),
|
|
||||||
keep_next: true,
|
|
||||||
clear_next: None,
|
|
||||||
info: DocumentInfo::default(),
|
|
||||||
}
|
}
|
||||||
}
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a [flow][FlowElem] from flow content.
|
|
||||||
#[derive(Default)]
|
|
||||||
struct FlowBuilder<'a>(BehavedBuilder<'a>, bool);
|
|
||||||
|
|
||||||
impl<'a> FlowBuilder<'a> {
|
|
||||||
/// Tries to accept a piece of content.
|
|
||||||
///
|
|
||||||
/// Returns true if this content could be merged into the flow.
|
|
||||||
/// If this function returns false, then the
|
|
||||||
/// content could not be merged, and flow building should be
|
|
||||||
/// interrupted so that the content can be added elsewhere.
|
|
||||||
fn accept(
|
|
||||||
&mut self,
|
|
||||||
arenas: &'a Arenas<'a>,
|
|
||||||
content: &'a Content,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> bool {
|
|
||||||
let last_was_par = self.1;
|
|
||||||
self.1 = false;
|
|
||||||
|
|
||||||
if content.is::<ParbreakElem>() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(elem) = content.to_packed::<VElem>() {
|
|
||||||
if !elem.attach(styles) || last_was_par {
|
|
||||||
self.0.push(content, styles);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if content.is::<ColbreakElem>()
|
|
||||||
|| content.is::<TagElem>()
|
|
||||||
|| content.is::<PlaceElem>()
|
|
||||||
|| content.is::<FlushElem>()
|
|
||||||
{
|
|
||||||
self.0.push(content, styles);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let par_spacing = Lazy::new(|| {
|
|
||||||
arenas.store(VElem::par_spacing(ParElem::spacing_in(styles).into()).pack())
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(elem) = content.to_packed::<BlockElem>() {
|
|
||||||
let above = match elem.above(styles) {
|
|
||||||
Smart::Auto => *par_spacing,
|
|
||||||
Smart::Custom(above) => arenas.store(VElem::block_spacing(above).pack()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let below = match elem.below(styles) {
|
|
||||||
Smart::Auto => *par_spacing,
|
|
||||||
Smart::Custom(below) => arenas.store(VElem::block_spacing(below).pack()),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.0.push(above, styles);
|
|
||||||
self.0.push(content, styles);
|
|
||||||
self.0.push(below, styles);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if content.is::<ParElem>() {
|
|
||||||
self.0.push(*par_spacing, styles);
|
|
||||||
self.0.push(content, styles);
|
|
||||||
self.0.push(*par_spacing, styles);
|
|
||||||
self.1 = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turns this builder into the resulting flow, along with
|
|
||||||
/// its [style chain][StyleChain].
|
|
||||||
fn finish(self) -> (Packed<FlowElem>, StyleChain<'a>) {
|
|
||||||
let buf = self.0.finish();
|
|
||||||
let span = determine_span(&buf);
|
|
||||||
let (children, trunk) = StyleVec::create(&buf);
|
|
||||||
(Packed::new(FlowElem::new(children)).spanned(span), trunk)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,24 +370,12 @@ struct ParBuilder<'a>(BehavedBuilder<'a>);
|
|||||||
impl<'a> ParBuilder<'a> {
|
impl<'a> ParBuilder<'a> {
|
||||||
/// Tries to accept a piece of content.
|
/// Tries to accept a piece of content.
|
||||||
///
|
///
|
||||||
/// Returns true if this content could be merged into the paragraph.
|
/// Returns true if this content could be merged into the paragraph. If this
|
||||||
/// If this function returns false, then the
|
/// function returns false, then the content could not be merged, and
|
||||||
/// content could not be merged, and paragraph building should be
|
/// paragraph building should be interrupted so that the content can be
|
||||||
/// interrupted so that the content can be added elsewhere.
|
/// added elsewhere.
|
||||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
if content.is::<TagElem>() {
|
if Self::is_primary(content) || (!self.0.is_empty() && Self::is_inner(content)) {
|
||||||
if !self.0.is_empty() {
|
|
||||||
self.0.push(content, styles);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if content.is::<SpaceElem>()
|
|
||||||
|| content.is::<TextElem>()
|
|
||||||
|| content.is::<HElem>()
|
|
||||||
|| content.is::<LinebreakElem>()
|
|
||||||
|| content.is::<SmartQuoteElem>()
|
|
||||||
|| content.is::<InlineElem>()
|
|
||||||
|| content.is::<BoxElem>()
|
|
||||||
{
|
|
||||||
self.0.push(content, styles);
|
self.0.push(content, styles);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -485,155 +383,203 @@ impl<'a> ParBuilder<'a> {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns this builder into the resulting paragraph, along with
|
/// Whether this content is of interest to the builder.
|
||||||
|
fn is_primary(content: &'a Content) -> bool {
|
||||||
|
content.is::<SpaceElem>()
|
||||||
|
|| content.is::<TextElem>()
|
||||||
|
|| content.is::<HElem>()
|
||||||
|
|| content.is::<LinebreakElem>()
|
||||||
|
|| content.is::<SmartQuoteElem>()
|
||||||
|
|| content.is::<InlineElem>()
|
||||||
|
|| content.is::<BoxElem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this content can merely exist in between interesting items.
|
||||||
|
fn is_inner(content: &'a Content) -> bool {
|
||||||
|
content.is::<TagElem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Turns this builder into the resulting list, along with
|
||||||
/// its [style chain][StyleChain].
|
/// its [style chain][StyleChain].
|
||||||
fn finish(self) -> (Packed<ParElem>, StyleChain<'a>) {
|
fn finish(self, builder: &mut Builder<'a, '_>) -> SourceResult<()> {
|
||||||
let buf = self.0.finish();
|
let buf = self.0.finish();
|
||||||
let span = determine_span(&buf);
|
let trimmed = buf.trim_end_matches(|(c, _)| c.is::<TagElem>());
|
||||||
let (children, trunk) = StyleVec::create(&buf);
|
let staged = &buf[trimmed.len()..];
|
||||||
(Packed::new(ParElem::new(children)).spanned(span), trunk)
|
|
||||||
|
let span = first_span(trimmed);
|
||||||
|
let (children, trunk) = StyleVec::create(trimmed);
|
||||||
|
let elem = Packed::new(ParElem::new(children)).spanned(span);
|
||||||
|
builder.accept(builder.arenas.store(elem.pack()), trunk)?;
|
||||||
|
|
||||||
|
for &(tag, styles) in staged {
|
||||||
|
builder.accept(tag, styles)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a list (either [`ListElem`], [`EnumElem`], or [`TermsElem`])
|
/// Builds a list (either [`ListElem`], [`EnumElem`], or [`TermsElem`]) from
|
||||||
/// from list or enum items, spaces, and paragraph breaks.
|
/// list or enum items, spaces, and paragraph breaks.
|
||||||
struct ListBuilder<'a> {
|
#[derive(Default)]
|
||||||
/// The list items collected so far.
|
struct ListBuilder<'a>(Vec<Pair<'a>>);
|
||||||
items: Vec<Pair<'a>>,
|
|
||||||
/// Trailing content for which it is unclear whether it is part of the list.
|
|
||||||
staged: Vec<Pair<'a>>,
|
|
||||||
/// Whether the list contains no paragraph breaks.
|
|
||||||
tight: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ListBuilder<'a> {
|
impl<'a> ListBuilder<'a> {
|
||||||
/// Tries to accept a piece of content.
|
/// Tries to accept a piece of content.
|
||||||
///
|
///
|
||||||
/// Returns true if this content could be merged into the list.
|
/// Returns true if this content could be merged into the list. If this
|
||||||
/// If this function returns false, then the
|
/// function returns false, then the content could not be merged, and list
|
||||||
/// content could not be merged, and list building should be
|
/// building should be interrupted so that the content can be added
|
||||||
/// interrupted so that the content can be added elsewhere.
|
/// elsewhere.
|
||||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
if !self.items.is_empty()
|
if (Self::is_primary(content) && self.is_compatible(content))
|
||||||
&& (content.is::<SpaceElem>() || content.is::<ParbreakElem>())
|
|| (!self.0.is_empty() && Self::is_inner(content))
|
||||||
{
|
{
|
||||||
self.staged.push((content, styles));
|
self.0.push((content, styles));
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content.is::<ListItem>()
|
|
||||||
|| content.is::<EnumItem>()
|
|
||||||
|| content.is::<TermItem>())
|
|
||||||
&& self
|
|
||||||
.items
|
|
||||||
.first()
|
|
||||||
.map_or(true, |(first, _)| first.func() == content.func())
|
|
||||||
{
|
|
||||||
self.items.push((content, styles));
|
|
||||||
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakElem>());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this content is of interest to the builder.
|
||||||
|
fn is_primary(content: &'a Content) -> bool {
|
||||||
|
content.is::<ListItem>() || content.is::<EnumItem>() || content.is::<TermItem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this content can merely exist in between interesting items.
|
||||||
|
fn is_inner(content: &'a Content) -> bool {
|
||||||
|
content.is::<TagElem>()
|
||||||
|
|| content.is::<SpaceElem>()
|
||||||
|
|| content.is::<ParbreakElem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this kind of list items is compatible with the builder's type.
|
||||||
|
fn is_compatible(&self, content: &'a Content) -> bool {
|
||||||
|
self.0
|
||||||
|
.first()
|
||||||
|
.map_or(true, |(first, _)| first.func() == content.func())
|
||||||
|
}
|
||||||
|
|
||||||
/// Turns this builder into the resulting list, along with
|
/// Turns this builder into the resulting list, along with
|
||||||
/// its [style chain][StyleChain].
|
/// its [style chain][StyleChain].
|
||||||
fn finish(self) -> (Content, StyleChain<'a>) {
|
fn finish(self, builder: &mut Builder<'a, '_>) -> SourceResult<()> {
|
||||||
let span = determine_span(&self.items);
|
let trimmed = self.0.trim_end_matches(|(c, _)| Self::is_inner(c));
|
||||||
let (children, trunk) = StyleVec::create(&self.items);
|
let tags = trimmed.iter().filter(|(c, _)| c.is::<TagElem>());
|
||||||
|
let staged = &self.0[trimmed.len()..];
|
||||||
|
let items = trimmed.iter().copied().filter(|(c, _)| Self::is_primary(c));
|
||||||
|
let first = items.clone().next().unwrap().0;
|
||||||
|
let tight = !trimmed.iter().any(|(c, _)| c.is::<ParbreakElem>());
|
||||||
|
|
||||||
let mut iter = children.into_iter().peekable();
|
// Determine the styles that are shared by all items. These will be
|
||||||
let (first, _) = iter.peek().unwrap();
|
// used for the list itself.
|
||||||
let output = if first.is::<ListItem>() {
|
let trunk = StyleChain::trunk(items.clone().map(|(_, s)| s)).unwrap();
|
||||||
|
let depth = trunk.links().count();
|
||||||
|
|
||||||
|
// Builder the correct element.
|
||||||
|
let iter = items.map(|(c, s)| (c, s.suffix(depth)));
|
||||||
|
let elem = if first.is::<ListItem>() {
|
||||||
let children = iter
|
let children = iter
|
||||||
.map(|(item, local)| {
|
.map(|(item, local)| {
|
||||||
item.into_packed::<ListItem>().unwrap().styled(local)
|
item.to_packed::<ListItem>().unwrap().clone().styled(local)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
ListElem::new(children).with_tight(self.tight).pack().spanned(span)
|
ListElem::new(children).with_tight(tight).pack()
|
||||||
} else if first.is::<EnumItem>() {
|
} else if first.is::<EnumItem>() {
|
||||||
let children = iter
|
let children = iter
|
||||||
.map(|(item, local)| {
|
.map(|(item, local)| {
|
||||||
item.into_packed::<EnumItem>().unwrap().styled(local)
|
item.to_packed::<EnumItem>().unwrap().clone().styled(local)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
EnumElem::new(children).with_tight(self.tight).pack().spanned(span)
|
EnumElem::new(children).with_tight(tight).pack()
|
||||||
} else if first.is::<TermItem>() {
|
} else if first.is::<TermItem>() {
|
||||||
let children = iter
|
let children = iter
|
||||||
.map(|(item, local)| {
|
.map(|(item, local)| {
|
||||||
item.into_packed::<TermItem>().unwrap().styled(local)
|
item.to_packed::<TermItem>().unwrap().clone().styled(local)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
TermsElem::new(children).with_tight(self.tight).pack().spanned(span)
|
TermsElem::new(children).with_tight(tight).pack()
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
(output, trunk)
|
// Add the list to the builder.
|
||||||
}
|
let span = first_span(&self.0);
|
||||||
}
|
let stored = builder.arenas.store(elem.spanned(span));
|
||||||
|
builder.accept(stored, trunk)?;
|
||||||
|
|
||||||
impl Default for ListBuilder<'_> {
|
// Add the tags and staged elements to the builder.
|
||||||
fn default() -> Self {
|
for &(content, styles) in tags.chain(staged) {
|
||||||
Self { items: vec![], staged: vec![], tight: true }
|
builder.accept(content, styles)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a [citation group][CiteGroup] from citations.
|
/// Builds a [citation group][CiteGroup] from citations.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct CiteGroupBuilder<'a> {
|
struct CiteGroupBuilder<'a>(Vec<Pair<'a>>);
|
||||||
/// The citations.
|
|
||||||
items: Vec<Packed<CiteElem>>,
|
|
||||||
/// Trailing content for which it is unclear whether it is part of the list.
|
|
||||||
staged: Vec<Pair<'a>>,
|
|
||||||
/// The styles.
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CiteGroupBuilder<'a> {
|
impl<'a> CiteGroupBuilder<'a> {
|
||||||
/// Tries to accept a piece of content.
|
/// Tries to accept a piece of content.
|
||||||
///
|
///
|
||||||
/// Returns true if this content could be merged into the citation
|
/// Returns true if this content could be merged into the citation group. If
|
||||||
/// group. If this function returns false, then the
|
/// this function returns false, then the content could not be merged, and
|
||||||
/// content could not be merged, and citation grouping should be
|
/// citation grouping should be interrupted so that the content can be added
|
||||||
/// interrupted so that the content can be added elsewhere.
|
/// elsewhere.
|
||||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
if !self.items.is_empty()
|
if Self::is_primary(content) || (!self.0.is_empty() && Self::is_inner(content)) {
|
||||||
&& (content.is::<SpaceElem>() || content.is::<TagElem>())
|
self.0.push((content, styles));
|
||||||
{
|
|
||||||
self.staged.push((content, styles));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(citation) = content.to_packed::<CiteElem>() {
|
|
||||||
if self.items.is_empty() {
|
|
||||||
self.styles = styles;
|
|
||||||
}
|
|
||||||
self.staged.retain(|(elem, _)| !elem.is::<SpaceElem>());
|
|
||||||
self.items.push(citation.clone());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this content is of interest to the builder.
|
||||||
|
fn is_primary(content: &'a Content) -> bool {
|
||||||
|
content.is::<CiteElem>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this content can merely exist in between interesting items.
|
||||||
|
fn is_inner(content: &'a Content) -> bool {
|
||||||
|
content.is::<TagElem>() || content.is::<SpaceElem>()
|
||||||
|
}
|
||||||
|
|
||||||
/// Turns this builder into the resulting citation group, along with
|
/// Turns this builder into the resulting citation group, along with
|
||||||
/// its [style chain][StyleChain].
|
/// its [style chain][StyleChain].
|
||||||
fn finish(self) -> (Packed<CiteGroup>, StyleChain<'a>) {
|
fn finish(self, builder: &mut Builder<'a, '_>) -> SourceResult<()> {
|
||||||
let span = self.items.first().map(|cite| cite.span()).unwrap_or(Span::detached());
|
let trimmed = self.0.trim_end_matches(|(c, _)| Self::is_inner(c));
|
||||||
(Packed::new(CiteGroup::new(self.items)).spanned(span), self.styles)
|
let tags = trimmed.iter().filter(|(c, _)| c.is::<TagElem>());
|
||||||
|
let staged = &self.0[trimmed.len()..];
|
||||||
|
let trunk = trimmed[0].1;
|
||||||
|
let children = trimmed
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(c, _)| c.to_packed::<CiteElem>())
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Add the citation group to the builder.
|
||||||
|
let span = first_span(&self.0);
|
||||||
|
let elem = CiteGroup::new(children).pack();
|
||||||
|
let stored = builder.arenas.store(elem.spanned(span));
|
||||||
|
builder.accept(stored, trunk)?;
|
||||||
|
|
||||||
|
// Add the tags and staged elements to the builder.
|
||||||
|
for &(content, styles) in tags.chain(staged) {
|
||||||
|
builder.accept(content, styles)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine a span for the built collection.
|
/// Determine a span for the built collection.
|
||||||
fn determine_span(buf: &[(&Content, StyleChain)]) -> Span {
|
pub fn first_span(children: &[(&Content, StyleChain)]) -> Span {
|
||||||
let mut span = Span::detached();
|
children
|
||||||
for &(content, _) in buf {
|
.iter()
|
||||||
span = content.span();
|
.map(|(c, _)| c.span())
|
||||||
if !span.is_detached() {
|
.find(|span| !span.is_detached())
|
||||||
break;
|
.unwrap_or(Span::detached())
|
||||||
}
|
|
||||||
}
|
|
||||||
span
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::foundations::{
|
|||||||
Content, Context, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet, Style,
|
Content, Context, Packed, Recipe, RecipeIndex, Regex, Selector, Show, ShowSet, Style,
|
||||||
StyleChain, Styles, Synthesize, Transformation,
|
StyleChain, Styles, Synthesize, Transformation,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Locatable, SplitLocator, Tag, TagElem};
|
use crate::introspection::{Locatable, SplitLocator, Tag};
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
use crate::utils::SmallBitSet;
|
use crate::utils::SmallBitSet;
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ pub fn process(
|
|||||||
locator: &mut SplitLocator,
|
locator: &mut SplitLocator,
|
||||||
target: &Content,
|
target: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Option<Content>> {
|
) -> SourceResult<Option<(Option<Tag>, Content)>> {
|
||||||
let Some(Verdict { prepared, mut map, step }) = verdict(engine, target, styles)
|
let Some(Verdict { prepared, mut map, step }) = verdict(engine, target, styles)
|
||||||
else {
|
else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@ -54,7 +54,7 @@ pub fn process(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply a step, if there is one.
|
// Apply a step, if there is one.
|
||||||
let mut output = match step {
|
let output = match step {
|
||||||
Some(step) => {
|
Some(step) => {
|
||||||
// Errors in show rules don't terminate compilation immediately. We
|
// Errors in show rules don't terminate compilation immediately. We
|
||||||
// just continue with empty content for them and show all errors
|
// just continue with empty content for them and show all errors
|
||||||
@ -67,12 +67,7 @@ pub fn process(
|
|||||||
None => target,
|
None => target,
|
||||||
};
|
};
|
||||||
|
|
||||||
// If necessary, add the tag generated in the preparation.
|
Ok(Some((tag, output.styled_with_map(map))))
|
||||||
if let Some(tag) = tag {
|
|
||||||
output = tag + output;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(output.styled_with_map(map)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspects a target element and the current styles and determines how to
|
/// Inspects a target element and the current styles and determines how to
|
||||||
@ -106,7 +101,7 @@ fn verdict<'a>(
|
|||||||
|
|
||||||
let mut r = 0;
|
let mut r = 0;
|
||||||
for entry in styles.entries() {
|
for entry in styles.entries() {
|
||||||
let recipe = match entry {
|
let recipe = match &**entry {
|
||||||
Style::Recipe(recipe) => recipe,
|
Style::Recipe(recipe) => recipe,
|
||||||
Style::Property(_) => continue,
|
Style::Property(_) => continue,
|
||||||
Style::Revocation(index) => {
|
Style::Revocation(index) => {
|
||||||
@ -124,7 +119,7 @@ fn verdict<'a>(
|
|||||||
// Special handling for show-set rules. Exception: Regex show rules,
|
// Special handling for show-set rules. Exception: Regex show rules,
|
||||||
// those need to be handled like normal transformations.
|
// those need to be handled like normal transformations.
|
||||||
if let (Transformation::Style(transform), false) =
|
if let (Transformation::Style(transform), false) =
|
||||||
(&recipe.transform, matches!(&recipe.selector, Some(Selector::Regex(_))))
|
(recipe.transform(), matches!(recipe.selector(), Some(Selector::Regex(_))))
|
||||||
{
|
{
|
||||||
// If this is a show-set for an unprepared element, we need to apply
|
// If this is a show-set for an unprepared element, we need to apply
|
||||||
// it.
|
// it.
|
||||||
@ -137,8 +132,9 @@ fn verdict<'a>(
|
|||||||
// applied to the `target` previously. For this purpose, show rules
|
// applied to the `target` previously. For this purpose, show rules
|
||||||
// are indexed from the top of the chain as the chain might grow to
|
// are indexed from the top of the chain as the chain might grow to
|
||||||
// the bottom.
|
// the bottom.
|
||||||
let depth =
|
let depth = *depth.get_or_init(|| {
|
||||||
*depth.get_or_init(|| styles.entries().filter_map(Style::recipe).count());
|
styles.entries().filter_map(|style| style.recipe()).count()
|
||||||
|
});
|
||||||
let index = RecipeIndex(depth - r);
|
let index = RecipeIndex(depth - r);
|
||||||
|
|
||||||
if !target.is_guarded(index) && !revoked.contains(index.0) {
|
if !target.is_guarded(index) && !revoked.contains(index.0) {
|
||||||
@ -187,7 +183,7 @@ fn prepare(
|
|||||||
target: &mut Content,
|
target: &mut Content,
|
||||||
map: &mut Styles,
|
map: &mut Styles,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Option<Content>> {
|
) -> SourceResult<Option<Tag>> {
|
||||||
// Generate a location for the element, which uniquely identifies it in
|
// Generate a location for the element, which uniquely identifies it in
|
||||||
// the document. This has some overhead, so we only do it for elements
|
// the document. This has some overhead, so we only do it for elements
|
||||||
// that are explicitly marked as locatable and labelled elements.
|
// that are explicitly marked as locatable and labelled elements.
|
||||||
@ -226,7 +222,7 @@ fn prepare(
|
|||||||
// materialization, so that it includes the synthesized fields. Do it before
|
// materialization, so that it includes the synthesized fields. Do it before
|
||||||
// marking as prepared so that show-set rules will apply to this element
|
// marking as prepared so that show-set rules will apply to this element
|
||||||
// when queried.
|
// when queried.
|
||||||
let tag = key.map(|key| TagElem::packed(Tag::new(target.clone(), key)));
|
let tag = key.map(|key| Tag::new(target.clone(), key));
|
||||||
|
|
||||||
// Ensure that this preparation only runs once by marking the element as
|
// Ensure that this preparation only runs once by marking the element as
|
||||||
// prepared.
|
// prepared.
|
||||||
@ -246,7 +242,7 @@ fn show(
|
|||||||
// Apply a user-defined show rule.
|
// Apply a user-defined show rule.
|
||||||
ShowStep::Recipe(recipe, guard) => {
|
ShowStep::Recipe(recipe, guard) => {
|
||||||
let context = Context::new(target.location(), Some(styles));
|
let context = Context::new(target.location(), Some(styles));
|
||||||
match &recipe.selector {
|
match recipe.selector() {
|
||||||
// If the selector is a regex, the `target` is guaranteed to be a
|
// If the selector is a regex, the `target` is guaranteed to be a
|
||||||
// text element. This invokes special regex handling.
|
// text element. This invokes special regex handling.
|
||||||
Some(Selector::Regex(regex)) => {
|
Some(Selector::Regex(regex)) => {
|
||||||
|
BIN
tests/ref/counter-page-between-pages.png
Normal file
After Width: | Height: | Size: 299 B |
BIN
tests/ref/counter-page-footer-before-set-page.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
tests/ref/counter-page-footer-only-update.png
Normal file
After Width: | Height: | Size: 205 B |
BIN
tests/ref/counter-page-header-before-set-page.png
Normal file
After Width: | Height: | Size: 551 B |
BIN
tests/ref/counter-page-header-only-update.png
Normal file
After Width: | Height: | Size: 131 B |
BIN
tests/ref/issue-1833-locate-place.png
Normal file
After Width: | Height: | Size: 136 B |
BIN
tests/ref/issue-1886-locate-after-metadata.png
Normal file
After Width: | Height: | Size: 830 B |
BIN
tests/ref/issue-2326-context-set-page.png
Normal file
After Width: | Height: | Size: 676 B |
BIN
tests/ref/issue-2841-pagebreak-to-weak.png
Normal file
After Width: | Height: | Size: 391 B |
BIN
tests/ref/issue-4029-locate-after-pagebreak.png
Normal file
After Width: | Height: | Size: 698 B |
BIN
tests/ref/issue-4029-locate-after-par-and-pagebreak.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
tests/ref/issue-4029-locate-after-spacing.png
Normal file
After Width: | Height: | Size: 706 B |
BIN
tests/ref/issue-4363-set-page-after-tag.png
Normal file
After Width: | Height: | Size: 278 B |
BIN
tests/ref/list-item-styling.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
tests/ref/list-items-context.png
Normal file
After Width: | Height: | Size: 382 B |
BIN
tests/ref/locate-between-pages.png
Normal file
After Width: | Height: | Size: 231 B |
BIN
tests/ref/page-marginal-style-context.png
Normal file
After Width: | Height: | Size: 348 B |
BIN
tests/ref/page-marginal-style-empty.png
Normal file
After Width: | Height: | Size: 152 B |
BIN
tests/ref/page-marginal-style-page-call.png
Normal file
After Width: | Height: | Size: 300 B |
BIN
tests/ref/page-marginal-style-shared-initial-interaction.png
Normal file
After Width: | Height: | Size: 504 B |
BIN
tests/ref/page-marginal-style-show-rule-with-page-call.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
tests/ref/page-marginal-style-show-rule-with-pagebreak.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
tests/ref/page-marginal-style-show-rule-with-set-page.png
Normal file
After Width: | Height: | Size: 727 B |
BIN
tests/ref/page-marginal-style-show-rule.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
tests/ref/page-marginal-style-text-call-around-page-call.png
Normal file
After Width: | Height: | Size: 529 B |
BIN
tests/ref/page-marginal-style-text-call-around-pagebreak.png
Normal file
After Width: | Height: | Size: 510 B |
BIN
tests/ref/page-marginal-style-text-call-around-set-page.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
tests/ref/page-marginal-style-text-call-code.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
tests/ref/page-marginal-style-text-call.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
tests/ref/page-marginal-style-text-set-first.png
Normal file
After Width: | Height: | Size: 457 B |
BIN
tests/ref/page-marginal-style-text-set.png
Normal file
After Width: | Height: | Size: 457 B |
@ -62,6 +62,46 @@ At Beta, it was #context {
|
|||||||
#counter(page).update(1)
|
#counter(page).update(1)
|
||||||
#lorem(20)
|
#lorem(20)
|
||||||
|
|
||||||
|
--- counter-page-footer-before-set-page ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
A
|
||||||
|
#pagebreak()
|
||||||
|
#counter(page).update(5)
|
||||||
|
#set page(fill: aqua)
|
||||||
|
B
|
||||||
|
|
||||||
|
--- counter-page-header-before-set-page ---
|
||||||
|
#set page(numbering: "1", number-align: top + center, margin: (top: 20pt))
|
||||||
|
A
|
||||||
|
#counter(page).update(4)
|
||||||
|
#set page(fill: aqua)
|
||||||
|
B
|
||||||
|
|
||||||
|
--- counter-page-between-pages ---
|
||||||
|
// The update happens conceptually between the pages.
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
A
|
||||||
|
#pagebreak()
|
||||||
|
#counter(page).update(5)
|
||||||
|
#set page(number-align: top + center, margin: (top: 20pt, bottom: 10pt))
|
||||||
|
B
|
||||||
|
|
||||||
|
--- counter-page-header-only-update ---
|
||||||
|
// Header should not be affected by default.
|
||||||
|
// To affect it, put the counter update before the `set page`.
|
||||||
|
#set page(
|
||||||
|
numbering: "1",
|
||||||
|
number-align: top + center,
|
||||||
|
margin: (top: 20pt),
|
||||||
|
)
|
||||||
|
|
||||||
|
#counter(page).update(5)
|
||||||
|
|
||||||
|
--- counter-page-footer-only-update ---
|
||||||
|
// Footer should be affected by default.
|
||||||
|
#set page(numbering: "1 / 1", margin: (bottom: 20pt))
|
||||||
|
#counter(page).update(5)
|
||||||
|
|
||||||
--- counter-figure ---
|
--- counter-figure ---
|
||||||
// Count figures.
|
// Count figures.
|
||||||
#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]
|
#figure(numbering: "A", caption: [Four 'A's], kind: image, supplement: "Figure")[_AAAA!_]
|
||||||
|
@ -37,3 +37,93 @@
|
|||||||
|
|
||||||
// Error: 10-25 selector matches multiple elements
|
// Error: 10-25 selector matches multiple elements
|
||||||
#context locate(heading)
|
#context locate(heading)
|
||||||
|
|
||||||
|
--- locate-between-pages ---
|
||||||
|
// Test locating tags that are before or between pages.
|
||||||
|
#set page(height: 30pt)
|
||||||
|
#context [
|
||||||
|
// Before the first page.
|
||||||
|
// (= at the very start of the first page, before the header)
|
||||||
|
#test(locate(<a>).position(), (page: 1, x: 0pt, y: 0pt))
|
||||||
|
|
||||||
|
// On the first page.
|
||||||
|
#test(locate(<b>).position(), (page: 1, x: 10pt, y: 10pt))
|
||||||
|
|
||||||
|
// Between the two pages.
|
||||||
|
// (= at the very start of the first page, before the header)
|
||||||
|
#test(locate(<c>).position(), (page: 2, x: 0pt, y: 0pt))
|
||||||
|
|
||||||
|
// After the last page.
|
||||||
|
// (= at the very end of the last page, after the footer)
|
||||||
|
#test(locate(<d>).position(), (page: 2, x: 0pt, y: 30pt))
|
||||||
|
#test(locate(<e>).position(), (page: 2, x: 0pt, y: 30pt))
|
||||||
|
]
|
||||||
|
|
||||||
|
#metadata(none) <a>
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
#metadata(none) <b>
|
||||||
|
A
|
||||||
|
#pagebreak()
|
||||||
|
#metadata(none) <c>
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
B
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
#metadata(none) <d>
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
#metadata(none) <e>
|
||||||
|
|
||||||
|
--- issue-4029-locate-after-spacing ---
|
||||||
|
#set page(margin: 10pt)
|
||||||
|
#show heading: it => v(40pt) + it
|
||||||
|
|
||||||
|
= Introduction
|
||||||
|
#context test(
|
||||||
|
locate(heading).position(),
|
||||||
|
(page: 1, x: 10pt, y: 50pt),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
--- issue-4029-locate-after-pagebreak ---
|
||||||
|
#set page(margin: 10pt)
|
||||||
|
#show heading: it => pagebreak() + it
|
||||||
|
|
||||||
|
= Introduction
|
||||||
|
#context test(
|
||||||
|
locate(heading).position(),
|
||||||
|
(page: 2, x: 10pt, y: 10pt),
|
||||||
|
)
|
||||||
|
|
||||||
|
--- issue-4029-locate-after-par-and-pagebreak ---
|
||||||
|
// Ensure that the heading's tag isn't stuck at the end of the paragraph.
|
||||||
|
#set page(margin: 10pt)
|
||||||
|
Par
|
||||||
|
#show heading: it => pagebreak() + it
|
||||||
|
= Introduction
|
||||||
|
#context test(locate(heading).page(), 2)
|
||||||
|
|
||||||
|
--- issue-1886-locate-after-metadata ---
|
||||||
|
#show heading: it => {
|
||||||
|
metadata(it.label)
|
||||||
|
pagebreak(weak: true, to: "odd")
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
Hi
|
||||||
|
= Hello <hello>
|
||||||
|
= World <world>
|
||||||
|
|
||||||
|
// The metadata's position does not migrate to the next page, but the heading's
|
||||||
|
// does.
|
||||||
|
#context {
|
||||||
|
test(locate(metadata.where(value: <hello>)).page(), 1)
|
||||||
|
test(locate(<hello>).page(), 3)
|
||||||
|
test(locate(metadata.where(value: <world>)).page(), 3)
|
||||||
|
test(locate(<world>).page(), 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
--- issue-1833-locate-place ---
|
||||||
|
#set page(height: 60pt)
|
||||||
|
#context {
|
||||||
|
place(right + bottom, rect())
|
||||||
|
test(here().position(), (page: 1, x: 10pt, y: 10pt))
|
||||||
|
}
|
||||||
|
@ -245,6 +245,98 @@ Look, ma, no page numbers!
|
|||||||
#set page(header: auto, footer: auto)
|
#set page(header: auto, footer: auto)
|
||||||
Default page numbers now.
|
Default page numbers now.
|
||||||
|
|
||||||
|
--- page-marginal-style-text-set ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
#set text(red)
|
||||||
|
Red
|
||||||
|
|
||||||
|
--- page-marginal-style-text-set-first ---
|
||||||
|
#set text(red)
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
Red
|
||||||
|
|
||||||
|
--- page-marginal-style-text-call ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
#text(red)[Red]
|
||||||
|
|
||||||
|
--- page-marginal-style-text-call-code ---
|
||||||
|
#{
|
||||||
|
set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
text(red)[Red]
|
||||||
|
}
|
||||||
|
|
||||||
|
--- page-marginal-style-text-call-around-page-call ---
|
||||||
|
#text(red, page(numbering: "1", margin: (bottom: 20pt))[Hello])
|
||||||
|
|
||||||
|
--- page-marginal-style-text-call-around-set-page ---
|
||||||
|
#text(red, {
|
||||||
|
set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
text(style: "italic")[Hello]
|
||||||
|
})
|
||||||
|
|
||||||
|
--- page-marginal-style-text-call-around-pagebreak ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
A
|
||||||
|
#text(red)[
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
B
|
||||||
|
]
|
||||||
|
|
||||||
|
--- page-marginal-style-show-rule ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
= Introduction
|
||||||
|
|
||||||
|
--- page-marginal-style-show-rule-with-set-page ---
|
||||||
|
#show heading: it => {
|
||||||
|
set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
= Introduction
|
||||||
|
|
||||||
|
--- page-marginal-style-show-rule-with-page-call ---
|
||||||
|
#show heading: page.with(fill: aqua)
|
||||||
|
|
||||||
|
A
|
||||||
|
= Introduction
|
||||||
|
B
|
||||||
|
|
||||||
|
--- page-marginal-style-show-rule-with-pagebreak ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
#show heading: it => {
|
||||||
|
pagebreak(weak: true)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
|
||||||
|
= Introduction
|
||||||
|
|
||||||
|
--- page-marginal-style-context ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
#show: it => context {
|
||||||
|
set text(red)
|
||||||
|
it
|
||||||
|
}
|
||||||
|
Hi
|
||||||
|
|
||||||
|
--- page-marginal-style-shared-initial-interaction ---
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
A
|
||||||
|
#{
|
||||||
|
set text(fill: red)
|
||||||
|
pagebreak()
|
||||||
|
}
|
||||||
|
#text(fill: blue)[B]
|
||||||
|
|
||||||
|
--- page-marginal-style-empty ---
|
||||||
|
#set text(red)
|
||||||
|
#set page(numbering: "1", margin: (bottom: 20pt))
|
||||||
|
|
||||||
|
--- page-marginal-style-page-call ---
|
||||||
|
#page(numbering: "1", margin: (bottom: 20pt))[
|
||||||
|
#set text(red)
|
||||||
|
A
|
||||||
|
]
|
||||||
|
|
||||||
--- issue-2631-page-header-ordering ---
|
--- issue-2631-page-header-ordering ---
|
||||||
#set text(6pt)
|
#set text(6pt)
|
||||||
#show heading: set text(6pt, weight: "regular")
|
#show heading: set text(6pt, weight: "regular")
|
||||||
@ -272,3 +364,22 @@ Hi
|
|||||||
#set page(fill: gray)
|
#set page(fill: gray)
|
||||||
text
|
text
|
||||||
#pagebreak()
|
#pagebreak()
|
||||||
|
|
||||||
|
--- issue-2326-context-set-page ---
|
||||||
|
#context [
|
||||||
|
#set page(fill: aqua)
|
||||||
|
On page #here().page()
|
||||||
|
]
|
||||||
|
|
||||||
|
--- issue-3671-get-from-page-call ---
|
||||||
|
#set page(margin: 5pt)
|
||||||
|
#context test(page.margin, 5pt)
|
||||||
|
#page(margin: 10pt, context test(page.margin, 10pt))
|
||||||
|
|
||||||
|
--- issue-4363-set-page-after-tag ---
|
||||||
|
#set page(fill: aqua)
|
||||||
|
1
|
||||||
|
#pagebreak()
|
||||||
|
#metadata(none)
|
||||||
|
#set page(fill: red)
|
||||||
|
2
|
||||||
|
@ -141,3 +141,12 @@ Some text on page 2
|
|||||||
|
|
||||||
#set page(fill: orange) // This sets the color of the page starting from page 4
|
#set page(fill: orange) // This sets the color of the page starting from page 4
|
||||||
Some text on page 4
|
Some text on page 4
|
||||||
|
|
||||||
|
--- issue-2591-single-weak-pagebreak ---
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
|
||||||
|
--- issue-2841-pagebreak-to-weak ---
|
||||||
|
First
|
||||||
|
#pagebreak(to: "odd")
|
||||||
|
#pagebreak(weak: true)
|
||||||
|
Odd
|
||||||
|
@ -142,6 +142,16 @@ Hello
|
|||||||
#list(tight: false)[A][B]
|
#list(tight: false)[A][B]
|
||||||
World
|
World
|
||||||
|
|
||||||
|
--- list-items-context ---
|
||||||
|
#context [+ A]
|
||||||
|
#context [+ B]
|
||||||
|
#context [+ C]
|
||||||
|
|
||||||
|
--- list-item-styling ---
|
||||||
|
- Hello
|
||||||
|
#text(red)[- World]
|
||||||
|
#text(green)[- What up?]
|
||||||
|
|
||||||
--- issue-2530-list-item-panic ---
|
--- issue-2530-list-item-panic ---
|
||||||
// List item (pre-emptive)
|
// List item (pre-emptive)
|
||||||
#list.item[Hello]
|
#list.item[Hello]
|
||||||
|