mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
New document & flow building
This commit is contained in:
parent
09aabc3a21
commit
f7c67cde72
@ -177,7 +177,8 @@ impl Eval for ListNode {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
|
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
|
||||||
Ok(Content::List(library::structure::ListItem {
|
Ok(Content::Item(library::structure::ListItem {
|
||||||
|
kind: library::structure::UNORDERED,
|
||||||
number: None,
|
number: None,
|
||||||
body: Box::new(self.body().eval(ctx, scp)?),
|
body: Box::new(self.body().eval(ctx, scp)?),
|
||||||
}))
|
}))
|
||||||
@ -188,7 +189,8 @@ impl Eval for EnumNode {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
|
fn eval(&self, ctx: &mut Context, scp: &mut Scopes) -> EvalResult<Self::Output> {
|
||||||
Ok(Content::Enum(library::structure::ListItem {
|
Ok(Content::Item(library::structure::ListItem {
|
||||||
|
kind: library::structure::ORDERED,
|
||||||
number: self.number(),
|
number: self.number(),
|
||||||
body: Box::new(self.body().eval(ctx, scp)?),
|
body: Box::new(self.body().eval(ctx, scp)?),
|
||||||
}))
|
}))
|
||||||
|
@ -4,7 +4,7 @@ use super::ColumnsNode;
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(PartialEq, Clone, Hash)]
|
||||||
pub struct PageNode(pub LayoutNode);
|
pub struct PageNode(pub LayoutNode);
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
|
29
src/library/structure/doc.rs
Normal file
29
src/library/structure/doc.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use crate::library::layout::PageNode;
|
||||||
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
|
/// A sequence of page runs.
|
||||||
|
#[derive(Hash)]
|
||||||
|
pub struct DocNode(pub StyleVec<PageNode>);
|
||||||
|
|
||||||
|
impl DocNode {
|
||||||
|
/// Layout the document into a sequence of frames, one per page.
|
||||||
|
pub fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut Context,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
|
let mut frames = vec![];
|
||||||
|
for (page, map) in self.0.iter() {
|
||||||
|
let number = 1 + frames.len();
|
||||||
|
frames.extend(page.layout(ctx, number, map.chain(&styles))?);
|
||||||
|
}
|
||||||
|
Ok(frames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for DocNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.write_str("Doc ")?;
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::library::layout::{GridNode, TrackSizing};
|
use crate::library::layout::{GridNode, TrackSizing};
|
||||||
@ -14,12 +16,14 @@ pub struct ListNode<const L: ListKind = UNORDERED> {
|
|||||||
/// there is list spacing between the items.
|
/// there is list spacing between the items.
|
||||||
pub tight: bool,
|
pub tight: bool,
|
||||||
/// The individual bulleted or numbered items.
|
/// The individual bulleted or numbered items.
|
||||||
pub items: Vec<ListItem>,
|
pub items: StyleVec<ListItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An item in a list.
|
/// An item in a list.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
|
/// The kind of item.
|
||||||
|
pub kind: ListKind,
|
||||||
/// The number of the item.
|
/// The number of the item.
|
||||||
pub number: Option<usize>,
|
pub number: Option<usize>,
|
||||||
/// The node that produces the item's body.
|
/// The node that produces the item's body.
|
||||||
@ -59,7 +63,11 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
items: args
|
items: args
|
||||||
.all()?
|
.all()?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|body| ListItem { number: None, body: Box::new(body) })
|
.map(|body| ListItem {
|
||||||
|
kind: L,
|
||||||
|
number: None,
|
||||||
|
body: Box::new(body),
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -72,7 +80,7 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
"tight" => Value::Bool(self.tight),
|
"tight" => Value::Bool(self.tight),
|
||||||
"items" => Value::Array(
|
"items" => Value::Array(
|
||||||
self.items
|
self.items
|
||||||
.iter()
|
.items()
|
||||||
.map(|item| Value::Content((*item.body).clone()))
|
.map(|item| Value::Content((*item.body).clone()))
|
||||||
.collect()
|
.collect()
|
||||||
),
|
),
|
||||||
@ -85,12 +93,13 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
|
|
||||||
let label = styles.get(Self::LABEL);
|
let label = styles.get(Self::LABEL);
|
||||||
|
|
||||||
for item in &self.items {
|
for (item, map) in self.items.iter() {
|
||||||
number = item.number.unwrap_or(number);
|
number = item.number.unwrap_or(number);
|
||||||
cells.push(LayoutNode::default());
|
cells.push(LayoutNode::default());
|
||||||
cells.push(label.resolve(ctx, L, number)?.pack());
|
cells
|
||||||
|
.push(label.resolve(ctx, L, number)?.styled_with_map(map.clone()).pack());
|
||||||
cells.push(LayoutNode::default());
|
cells.push(LayoutNode::default());
|
||||||
cells.push((*item.body).clone().pack());
|
cells.push((*item.body).clone().styled_with_map(map.clone()).pack());
|
||||||
number += 1;
|
number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,9 +136,18 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: ListKind> From<ListItem> for ListNode<L> {
|
impl Debug for ListItem {
|
||||||
fn from(item: ListItem) -> Self {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Self { items: vec![item], tight: true, start: 1 }
|
if self.kind == UNORDERED {
|
||||||
|
f.write_char('-')?;
|
||||||
|
} else {
|
||||||
|
if let Some(number) = self.number {
|
||||||
|
write!(f, "{}", number)?;
|
||||||
|
}
|
||||||
|
f.write_char('.')?;
|
||||||
|
}
|
||||||
|
f.write_char(' ')?;
|
||||||
|
self.body.fmt(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
//! Document structuring.
|
//! Document structuring.
|
||||||
|
|
||||||
|
mod doc;
|
||||||
mod heading;
|
mod heading;
|
||||||
mod list;
|
mod list;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
|
pub use doc::*;
|
||||||
pub use heading::*;
|
pub use heading::*;
|
||||||
pub use list::*;
|
pub use list::*;
|
||||||
pub use table::*;
|
pub use table::*;
|
||||||
|
@ -29,6 +29,11 @@ impl<'a, T> CollapsingBuilder<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the builder is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.builder.is_empty() && self.staged.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// Can only exist when there is at least one supportive item to its left
|
/// Can only exist when there is at least one supportive item to its left
|
||||||
/// and to its right, with no destructive items or weak items in between to
|
/// and to its right, with no destructive items or weak items in between to
|
||||||
/// its left and no destructive items in between to its right. There may be
|
/// its left and no destructive items in between to its right. There may be
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
|
use std::mem;
|
||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
|
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
@ -12,7 +13,7 @@ use super::{
|
|||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode, Spacing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::structure::{ListItem, ListKind, ListNode, ORDERED, UNORDERED};
|
use crate::library::structure::{DocNode, ListItem, ListNode, ORDERED, UNORDERED};
|
||||||
use crate::library::text::{DecoNode, ParChild, ParNode, UNDERLINE};
|
use crate::library::text::{DecoNode, ParChild, ParNode, UNDERLINE};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
@ -58,10 +59,8 @@ pub enum Content {
|
|||||||
Vertical(Spacing),
|
Vertical(Spacing),
|
||||||
/// A block-level node.
|
/// A block-level node.
|
||||||
Block(LayoutNode),
|
Block(LayoutNode),
|
||||||
/// An item in an unordered list.
|
/// A list / enum item.
|
||||||
List(ListItem),
|
Item(ListItem),
|
||||||
/// An item in an ordered list.
|
|
||||||
Enum(ListItem),
|
|
||||||
/// A page break.
|
/// A page break.
|
||||||
Pagebreak(bool),
|
Pagebreak(bool),
|
||||||
/// A page node.
|
/// A page node.
|
||||||
@ -176,25 +175,15 @@ impl Content {
|
|||||||
|
|
||||||
/// Layout this content into a collection of pages.
|
/// Layout this content into a collection of pages.
|
||||||
pub fn layout(&self, ctx: &mut Context) -> TypResult<Vec<Arc<Frame>>> {
|
pub fn layout(&self, ctx: &mut Context) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let sya = Arena::new();
|
let copy = ctx.styles.clone();
|
||||||
let tpa = Arena::new();
|
let styles = StyleChain::with_root(©);
|
||||||
|
let scratch = Scratch::default();
|
||||||
|
|
||||||
let styles = ctx.styles.clone();
|
let mut builder = Builder::new(ctx, &scratch, true);
|
||||||
let styles = StyleChain::with_root(&styles);
|
builder.accept(self, styles)?;
|
||||||
|
|
||||||
let mut builder = Builder::new(&sya, &tpa, true);
|
let (doc, shared) = builder.into_doc(styles)?;
|
||||||
builder.process(ctx, self, styles)?;
|
doc.layout(ctx, shared)
|
||||||
builder.finish(ctx, styles)?;
|
|
||||||
|
|
||||||
let mut frames = vec![];
|
|
||||||
let (pages, shared) = builder.pages.unwrap().finish();
|
|
||||||
|
|
||||||
for (page, map) in pages.iter() {
|
|
||||||
let number = 1 + frames.len();
|
|
||||||
frames.extend(page.layout(ctx, number, map.chain(&shared))?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(frames)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,15 +194,11 @@ impl Layout for Content {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let sya = Arena::new();
|
let scratch = Scratch::default();
|
||||||
let tpa = Arena::new();
|
let mut builder = Builder::new(ctx, &scratch, false);
|
||||||
|
builder.accept(self, styles)?;
|
||||||
let mut builder = Builder::new(&sya, &tpa, false);
|
let (flow, shared) = builder.into_flow(styles)?;
|
||||||
builder.process(ctx, self, styles)?;
|
flow.layout(ctx, regions, shared)
|
||||||
builder.finish(ctx, styles)?;
|
|
||||||
|
|
||||||
let (flow, shared) = builder.flow.finish();
|
|
||||||
FlowNode(flow).layout(ctx, regions, shared)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
fn pack(self) -> LayoutNode {
|
||||||
@ -243,17 +228,7 @@ impl Debug for Content {
|
|||||||
Self::Colbreak => f.pad("Colbreak"),
|
Self::Colbreak => f.pad("Colbreak"),
|
||||||
Self::Vertical(kind) => write!(f, "Vertical({kind:?})"),
|
Self::Vertical(kind) => write!(f, "Vertical({kind:?})"),
|
||||||
Self::Block(node) => node.fmt(f),
|
Self::Block(node) => node.fmt(f),
|
||||||
Self::List(item) => {
|
Self::Item(item) => item.fmt(f),
|
||||||
f.write_str("- ")?;
|
|
||||||
item.body.fmt(f)
|
|
||||||
}
|
|
||||||
Self::Enum(item) => {
|
|
||||||
if let Some(number) = item.number {
|
|
||||||
write!(f, "{}", number)?;
|
|
||||||
}
|
|
||||||
f.write_str(". ")?;
|
|
||||||
item.body.fmt(f)
|
|
||||||
}
|
|
||||||
Self::Pagebreak(soft) => write!(f, "Pagebreak({soft})"),
|
Self::Pagebreak(soft) => write!(f, "Pagebreak({soft})"),
|
||||||
Self::Page(page) => page.fmt(f),
|
Self::Page(page) => page.fmt(f),
|
||||||
Self::Show(node) => node.fmt(f),
|
Self::Show(node) => node.fmt(f),
|
||||||
@ -305,214 +280,298 @@ impl Sum for Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a flow or page nodes from content.
|
/// Builds a document or a flow node from content.
|
||||||
struct Builder<'a> {
|
struct Builder<'a, 'ctx> {
|
||||||
|
/// The core context.
|
||||||
|
ctx: &'ctx mut Context,
|
||||||
|
/// Scratch arenas for building.
|
||||||
|
scratch: &'a Scratch<'a>,
|
||||||
|
/// The current document building state.
|
||||||
|
doc: Option<DocBuilder<'a>>,
|
||||||
|
/// The current flow building state.
|
||||||
|
flow: FlowBuilder<'a>,
|
||||||
|
/// The current paragraph building state.
|
||||||
|
par: ParBuilder<'a>,
|
||||||
|
/// The current list building state.
|
||||||
|
list: ListBuilder<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporary storage arenas for building.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Scratch<'a> {
|
||||||
/// An arena where intermediate style chains are stored.
|
/// An arena where intermediate style chains are stored.
|
||||||
sya: &'a Arena<StyleChain<'a>>,
|
styles: Arena<StyleChain<'a>>,
|
||||||
/// An arena where intermediate content resulting from show rules is stored.
|
/// An arena where intermediate content resulting from show rules is stored.
|
||||||
tpa: &'a Arena<Content>,
|
templates: Arena<Content>,
|
||||||
/// The already built page runs.
|
|
||||||
pages: Option<StyleVecBuilder<'a, PageNode>>,
|
|
||||||
/// The currently built list.
|
|
||||||
list: Option<ListBuilder<'a>>,
|
|
||||||
/// The currently built flow.
|
|
||||||
flow: CollapsingBuilder<'a, FlowChild>,
|
|
||||||
/// The currently built paragraph.
|
|
||||||
par: CollapsingBuilder<'a, ParChild>,
|
|
||||||
/// Whether to keep the next page even if it is empty.
|
|
||||||
keep_next: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds an unordered or ordered list from items.
|
impl<'a, 'ctx> Builder<'a, 'ctx> {
|
||||||
struct ListBuilder<'a> {
|
fn new(ctx: &'ctx mut Context, scratch: &'a Scratch<'a>, top: bool) -> Self {
|
||||||
styles: StyleChain<'a>,
|
|
||||||
kind: ListKind,
|
|
||||||
items: Vec<ListItem>,
|
|
||||||
tight: bool,
|
|
||||||
staged: Vec<(&'a Content, StyleChain<'a>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Builder<'a> {
|
|
||||||
/// Prepare the builder.
|
|
||||||
fn new(sya: &'a Arena<StyleChain<'a>>, tpa: &'a Arena<Content>, top: bool) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
sya,
|
ctx,
|
||||||
tpa,
|
scratch,
|
||||||
pages: top.then(|| StyleVecBuilder::new()),
|
doc: top.then(|| DocBuilder::default()),
|
||||||
flow: CollapsingBuilder::new(),
|
flow: FlowBuilder::default(),
|
||||||
list: None,
|
par: ParBuilder::default(),
|
||||||
par: CollapsingBuilder::new(),
|
list: ListBuilder::default(),
|
||||||
keep_next: true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process content.
|
fn into_doc(
|
||||||
fn process(
|
mut self,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> TypResult<(DocNode, StyleChain<'a>)> {
|
||||||
|
self.interrupt(Interruption::Page, styles, true)?;
|
||||||
|
let (pages, shared) = self.doc.unwrap().pages.finish();
|
||||||
|
Ok((DocNode(pages), shared))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_flow(
|
||||||
|
mut self,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> TypResult<(FlowNode, StyleChain<'a>)> {
|
||||||
|
self.interrupt(Interruption::Par, styles, false)?;
|
||||||
|
let (children, shared) = self.flow.0.finish();
|
||||||
|
Ok((FlowNode(children), shared))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> TypResult<()> {
|
||||||
|
// Handle special content kinds.
|
||||||
|
match content {
|
||||||
|
Content::Show(node) => return self.show(node, styles),
|
||||||
|
Content::Styled(styled) => return self.styled(styled, styles),
|
||||||
|
Content::Sequence(seq) => return self.sequence(seq, styles),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.list.accept(content, styles) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interrupt(Interruption::List, styles, false)?;
|
||||||
|
|
||||||
|
if self.par.accept(content, styles) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interrupt(Interruption::Par, styles, false)?;
|
||||||
|
|
||||||
|
if self.flow.accept(content, styles) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let keep = matches!(content, Content::Pagebreak(false));
|
||||||
|
self.interrupt(Interruption::Page, styles, keep)?;
|
||||||
|
|
||||||
|
if let Some(doc) = &mut self.doc {
|
||||||
|
doc.accept(content, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We might want to issue a warning or error for content that wasn't
|
||||||
|
// handled (e.g. a pagebreak in a flow building process). However, we
|
||||||
|
// don't have the spans here at the moment.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> TypResult<()> {
|
||||||
|
let id = node.id();
|
||||||
|
let realized = match styles.realize(self.ctx, node)? {
|
||||||
|
Some(content) => content,
|
||||||
|
None => node.realize(self.ctx, styles)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = node.finalize(self.ctx, styles, realized)?;
|
||||||
|
let stored = self.scratch.templates.alloc(content);
|
||||||
|
self.accept(stored, styles.unscoped(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn styled(
|
||||||
&mut self,
|
&mut self,
|
||||||
ctx: &mut Context,
|
(content, map): &'a (Content, StyleMap),
|
||||||
content: &'a Content,
|
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> TypResult<()> {
|
) -> TypResult<()> {
|
||||||
if let Some(builder) = &mut self.list {
|
let stored = self.scratch.styles.alloc(styles);
|
||||||
match content {
|
|
||||||
Content::Space => {
|
|
||||||
builder.staged.push((content, styles));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Content::Parbreak => {
|
|
||||||
builder.staged.push((content, styles));
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Content::List(item) if builder.kind == UNORDERED => {
|
|
||||||
builder.tight &=
|
|
||||||
builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak);
|
|
||||||
builder.staged.clear();
|
|
||||||
builder.items.push(item.clone());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Content::Enum(item) if builder.kind == ORDERED => {
|
|
||||||
builder.tight &=
|
|
||||||
builder.staged.iter().all(|&(t, _)| *t != Content::Parbreak);
|
|
||||||
builder.staged.clear();
|
|
||||||
builder.items.push(item.clone());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
_ => self.finish_list(ctx)?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match content {
|
|
||||||
Content::Space => {
|
|
||||||
self.par.weak(ParChild::Text(' '.into()), 0, styles);
|
|
||||||
}
|
|
||||||
Content::Linebreak(justified) => {
|
|
||||||
let c = if *justified { '\u{2028}' } else { '\n' };
|
|
||||||
self.par.destructive(ParChild::Text(c.into()), styles);
|
|
||||||
}
|
|
||||||
Content::Horizontal(kind) => {
|
|
||||||
let child = ParChild::Spacing(*kind);
|
|
||||||
if kind.is_fractional() {
|
|
||||||
self.par.destructive(child, styles);
|
|
||||||
} else {
|
|
||||||
self.par.ignorant(child, styles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Content::Quote(double) => {
|
|
||||||
self.par.supportive(ParChild::Quote(*double), styles);
|
|
||||||
}
|
|
||||||
Content::Text(text) => {
|
|
||||||
self.par.supportive(ParChild::Text(text.clone()), styles);
|
|
||||||
}
|
|
||||||
Content::Inline(node) => {
|
|
||||||
self.par.supportive(ParChild::Node(node.clone()), styles);
|
|
||||||
}
|
|
||||||
Content::Parbreak => {
|
|
||||||
self.finish_par(styles);
|
|
||||||
self.flow.weak(FlowChild::Parbreak, 1, styles);
|
|
||||||
}
|
|
||||||
Content::Colbreak => {
|
|
||||||
self.finish_par(styles);
|
|
||||||
self.flow.destructive(FlowChild::Colbreak, styles);
|
|
||||||
}
|
|
||||||
Content::Vertical(kind) => {
|
|
||||||
self.finish_par(styles);
|
|
||||||
let child = FlowChild::Spacing(*kind);
|
|
||||||
if kind.is_fractional() {
|
|
||||||
self.flow.destructive(child, styles);
|
|
||||||
} else {
|
|
||||||
self.flow.ignorant(child, styles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Content::Block(node) => {
|
|
||||||
self.finish_par(styles);
|
|
||||||
let child = FlowChild::Node(node.clone());
|
|
||||||
if node.is::<PlaceNode>() {
|
|
||||||
self.flow.ignorant(child, styles);
|
|
||||||
} else {
|
|
||||||
self.flow.supportive(child, styles);
|
|
||||||
}
|
|
||||||
self.finish_par(styles);
|
|
||||||
}
|
|
||||||
Content::List(item) => {
|
|
||||||
self.list = Some(ListBuilder {
|
|
||||||
styles,
|
|
||||||
kind: UNORDERED,
|
|
||||||
items: vec![item.clone()],
|
|
||||||
tight: true,
|
|
||||||
staged: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Content::Enum(item) => {
|
|
||||||
self.list = Some(ListBuilder {
|
|
||||||
styles,
|
|
||||||
kind: ORDERED,
|
|
||||||
items: vec![item.clone()],
|
|
||||||
tight: true,
|
|
||||||
staged: vec![],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Content::Pagebreak(soft) => {
|
|
||||||
self.finish_page(ctx, !soft, !soft, styles)?;
|
|
||||||
}
|
|
||||||
Content::Page(page) => {
|
|
||||||
self.finish_page(ctx, false, false, styles)?;
|
|
||||||
if let Some(pages) = &mut self.pages {
|
|
||||||
pages.push(page.clone(), styles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Content::Show(node) => {
|
|
||||||
let id = node.id();
|
|
||||||
let realized = match styles.realize(ctx, node)? {
|
|
||||||
Some(content) => content,
|
|
||||||
None => node.realize(ctx, styles)?,
|
|
||||||
};
|
|
||||||
let content = node.finalize(ctx, styles, realized)?;
|
|
||||||
let stored = self.tpa.alloc(content);
|
|
||||||
self.process(ctx, stored, styles.unscoped(id))?;
|
|
||||||
}
|
|
||||||
Content::Styled(styled) => {
|
|
||||||
let (sub, map) = styled.as_ref();
|
|
||||||
let stored = self.sya.alloc(styles);
|
|
||||||
let styles = map.chain(stored);
|
let styles = map.chain(stored);
|
||||||
|
let intr = map.interruption();
|
||||||
|
|
||||||
let interruption = map.interruption();
|
if let Some(intr) = intr {
|
||||||
match interruption {
|
self.interrupt(intr, styles, false)?;
|
||||||
Some(Interruption::Page) => {
|
|
||||||
self.finish_page(ctx, false, true, styles)?
|
|
||||||
}
|
|
||||||
Some(Interruption::Par) => self.finish_par(styles),
|
|
||||||
None => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.process(ctx, sub, styles)?;
|
self.accept(content, styles)?;
|
||||||
|
|
||||||
match interruption {
|
if let Some(intr) = intr {
|
||||||
Some(Interruption::Page) => {
|
self.interrupt(intr, styles, true)?;
|
||||||
self.finish_page(ctx, true, false, styles)?
|
|
||||||
}
|
}
|
||||||
Some(Interruption::Par) => self.finish_par(styles),
|
|
||||||
None => {}
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interrupt(
|
||||||
|
&mut self,
|
||||||
|
intr: Interruption,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
keep: bool,
|
||||||
|
) -> TypResult<()> {
|
||||||
|
if intr >= Interruption::List && !self.list.is_empty() {
|
||||||
|
mem::take(&mut self.list).finish(self)?;
|
||||||
}
|
}
|
||||||
Content::Sequence(seq) => {
|
|
||||||
for sub in seq.iter() {
|
if intr >= Interruption::Par {
|
||||||
self.process(ctx, sub, styles)?;
|
if !self.par.is_empty() {
|
||||||
|
self.flow.0.weak(FlowChild::Leading, 0, styles);
|
||||||
|
mem::take(&mut self.par).finish(self);
|
||||||
}
|
}
|
||||||
|
self.flow.0.weak(FlowChild::Leading, 0, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if intr >= Interruption::Page {
|
||||||
|
if let Some(doc) = &mut self.doc {
|
||||||
|
if !self.flow.is_empty() || (doc.keep_next && keep) {
|
||||||
|
mem::take(&mut self.flow).finish(doc, styles);
|
||||||
|
}
|
||||||
|
doc.keep_next = !keep;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the currently built paragraph.
|
fn sequence(&mut self, seq: &'a [Content], styles: StyleChain<'a>) -> TypResult<()> {
|
||||||
fn finish_par(&mut self, styles: StyleChain<'a>) {
|
for content in seq {
|
||||||
let (mut par, shared) = std::mem::take(&mut self.par).finish();
|
self.accept(content, styles)?;
|
||||||
if !par.is_empty() {
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accepts pagebreaks and pages.
|
||||||
|
struct DocBuilder<'a> {
|
||||||
|
/// The page runs built so far.
|
||||||
|
pages: StyleVecBuilder<'a, PageNode>,
|
||||||
|
/// Whether to keep a following page even if it is empty.
|
||||||
|
keep_next: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DocBuilder<'a> {
|
||||||
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
|
||||||
|
match content {
|
||||||
|
Content::Pagebreak(soft) => {
|
||||||
|
self.keep_next = !soft;
|
||||||
|
}
|
||||||
|
Content::Page(page) => {
|
||||||
|
self.pages.push(page.clone(), styles);
|
||||||
|
self.keep_next = false;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DocBuilder<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pages: StyleVecBuilder::new(),
|
||||||
|
keep_next: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accepts flow content.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct FlowBuilder<'a>(CollapsingBuilder<'a, FlowChild>);
|
||||||
|
|
||||||
|
impl<'a> FlowBuilder<'a> {
|
||||||
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
|
match content {
|
||||||
|
Content::Parbreak => {
|
||||||
|
self.0.weak(FlowChild::Parbreak, 1, styles);
|
||||||
|
}
|
||||||
|
Content::Colbreak => {
|
||||||
|
self.0.destructive(FlowChild::Colbreak, styles);
|
||||||
|
}
|
||||||
|
Content::Vertical(kind) => {
|
||||||
|
let child = FlowChild::Spacing(*kind);
|
||||||
|
if kind.is_fractional() {
|
||||||
|
self.0.destructive(child, styles);
|
||||||
|
} else {
|
||||||
|
self.0.ignorant(child, styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Content::Block(node) => {
|
||||||
|
let child = FlowChild::Node(node.clone());
|
||||||
|
if node.is::<PlaceNode>() {
|
||||||
|
self.0.ignorant(child, styles);
|
||||||
|
} else {
|
||||||
|
self.0.supportive(child, styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self, doc: &mut DocBuilder<'a>, styles: StyleChain<'a>) {
|
||||||
|
let (flow, shared) = self.0.finish();
|
||||||
|
let styles = if flow.is_empty() { styles } else { shared };
|
||||||
|
let node = PageNode(FlowNode(flow).pack());
|
||||||
|
doc.pages.push(node, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accepts paragraph content.
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ParBuilder<'a>(CollapsingBuilder<'a, ParChild>);
|
||||||
|
|
||||||
|
impl<'a> ParBuilder<'a> {
|
||||||
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
|
match content {
|
||||||
|
Content::Space => {
|
||||||
|
self.0.weak(ParChild::Text(' '.into()), 0, styles);
|
||||||
|
}
|
||||||
|
Content::Linebreak(justified) => {
|
||||||
|
let c = if *justified { '\u{2028}' } else { '\n' };
|
||||||
|
self.0.destructive(ParChild::Text(c.into()), styles);
|
||||||
|
}
|
||||||
|
Content::Horizontal(kind) => {
|
||||||
|
let child = ParChild::Spacing(*kind);
|
||||||
|
if kind.is_fractional() {
|
||||||
|
self.0.destructive(child, styles);
|
||||||
|
} else {
|
||||||
|
self.0.ignorant(child, styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Content::Quote(double) => {
|
||||||
|
self.0.supportive(ParChild::Quote(*double), styles);
|
||||||
|
}
|
||||||
|
Content::Text(text) => {
|
||||||
|
self.0.supportive(ParChild::Text(text.clone()), styles);
|
||||||
|
}
|
||||||
|
Content::Inline(node) => {
|
||||||
|
self.0.supportive(ParChild::Node(node.clone()), styles);
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self, parent: &mut Builder<'a, '_>) {
|
||||||
|
let (mut children, shared) = self.0.finish();
|
||||||
|
if children.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Paragraph indent should only apply if the paragraph starts with
|
// Paragraph indent should only apply if the paragraph starts with
|
||||||
// text and follows directly after another paragraph.
|
// text and follows directly after another paragraph.
|
||||||
let indent = shared.get(ParNode::INDENT);
|
let indent = shared.get(ParNode::INDENT);
|
||||||
if !indent.is_zero()
|
if !indent.is_zero()
|
||||||
&& par
|
&& children
|
||||||
.items()
|
.items()
|
||||||
.find_map(|child| match child {
|
.find_map(|child| match child {
|
||||||
ParChild::Spacing(_) => None,
|
ParChild::Spacing(_) => None,
|
||||||
@ -520,8 +579,9 @@ impl<'a> Builder<'a> {
|
|||||||
ParChild::Node(_) => Some(false),
|
ParChild::Node(_) => Some(false),
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
&& self
|
&& parent
|
||||||
.flow
|
.flow
|
||||||
|
.0
|
||||||
.items()
|
.items()
|
||||||
.rev()
|
.rev()
|
||||||
.find_map(|child| match child {
|
.find_map(|child| match child {
|
||||||
@ -533,60 +593,87 @@ impl<'a> Builder<'a> {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
{
|
{
|
||||||
par.push_front(ParChild::Spacing(indent.into()));
|
children.push_front(ParChild::Spacing(indent.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = ParNode(par).pack();
|
let node = ParNode(children).pack();
|
||||||
self.flow.supportive(FlowChild::Node(node), shared);
|
parent.flow.0.supportive(FlowChild::Node(node), shared);
|
||||||
}
|
|
||||||
self.flow.weak(FlowChild::Leading, 0, styles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the currently built list.
|
fn is_empty(&self) -> bool {
|
||||||
fn finish_list(&mut self, ctx: &mut Context) -> TypResult<()> {
|
self.0.is_empty()
|
||||||
let ListBuilder { styles, kind, items, tight, staged } = match self.list.take() {
|
}
|
||||||
Some(list) => list,
|
}
|
||||||
|
|
||||||
|
/// Accepts list / enum items, spaces, paragraph breaks.
|
||||||
|
struct ListBuilder<'a> {
|
||||||
|
/// The list items collected so far.
|
||||||
|
items: StyleVecBuilder<'a, ListItem>,
|
||||||
|
/// Whether the list contains no paragraph breaks.
|
||||||
|
tight: bool,
|
||||||
|
/// Trailing content for which it is unclear whether it is part of the list.
|
||||||
|
staged: Vec<(&'a Content, StyleChain<'a>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ListBuilder<'a> {
|
||||||
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
|
match content {
|
||||||
|
Content::Space if !self.items.is_empty() => {
|
||||||
|
self.staged.push((content, styles));
|
||||||
|
}
|
||||||
|
Content::Parbreak if !self.items.is_empty() => {
|
||||||
|
self.staged.push((content, styles));
|
||||||
|
}
|
||||||
|
Content::Item(item)
|
||||||
|
if self
|
||||||
|
.items
|
||||||
|
.items()
|
||||||
|
.next()
|
||||||
|
.map_or(true, |first| item.kind == first.kind) =>
|
||||||
|
{
|
||||||
|
self.items.push(item.clone(), styles);
|
||||||
|
self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak);
|
||||||
|
}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self, parent: &mut Builder<'a, '_>) -> TypResult<()> {
|
||||||
|
let (items, shared) = self.items.finish();
|
||||||
|
let kind = match items.items().next() {
|
||||||
|
Some(item) => item.kind,
|
||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let tight = self.tight;
|
||||||
let content = match kind {
|
let content = match kind {
|
||||||
UNORDERED => Content::show(ListNode::<UNORDERED> { start: 1, tight, items }),
|
UNORDERED => Content::show(ListNode::<UNORDERED> { start: 1, tight, items }),
|
||||||
ORDERED | _ => Content::show(ListNode::<ORDERED> { start: 1, tight, items }),
|
ORDERED | _ => Content::show(ListNode::<ORDERED> { start: 1, tight, items }),
|
||||||
};
|
};
|
||||||
|
|
||||||
let stored = self.tpa.alloc(content);
|
let stored = parent.scratch.templates.alloc(content);
|
||||||
self.process(ctx, stored, styles)?;
|
parent.accept(stored, shared)?;
|
||||||
for (content, styles) in staged {
|
|
||||||
self.process(ctx, content, styles)?;
|
for (content, styles) in self.staged {
|
||||||
|
parent.accept(content, styles)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the currently built page run.
|
fn is_empty(&self) -> bool {
|
||||||
fn finish_page(
|
self.items.is_empty()
|
||||||
&mut self,
|
|
||||||
ctx: &mut Context,
|
|
||||||
keep_last: bool,
|
|
||||||
keep_next: bool,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> TypResult<()> {
|
|
||||||
self.finish_list(ctx)?;
|
|
||||||
self.finish_par(styles);
|
|
||||||
if let Some(pages) = &mut self.pages {
|
|
||||||
let (flow, shared) = std::mem::take(&mut self.flow).finish();
|
|
||||||
if !flow.is_empty() || (keep_last && self.keep_next) {
|
|
||||||
let styles = if flow.is_empty() { styles } else { shared };
|
|
||||||
let node = PageNode(FlowNode(flow).pack());
|
|
||||||
pages.push(node, styles);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.keep_next = keep_next;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish everything.
|
impl Default for ListBuilder<'_> {
|
||||||
fn finish(&mut self, ctx: &mut Context, styles: StyleChain<'a>) -> TypResult<()> {
|
fn default() -> Self {
|
||||||
self.finish_page(ctx, true, false, styles)
|
Self {
|
||||||
|
items: StyleVecBuilder::default(),
|
||||||
|
tight: true,
|
||||||
|
staged: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
use std::iter;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ use crate::diag::{At, TypResult};
|
|||||||
use crate::eval::{Args, Func, Node, Smart, Value};
|
use crate::eval::{Args, Func, Node, Smart, Value};
|
||||||
use crate::geom::{Numeric, Relative, Sides, Spec};
|
use crate::geom::{Numeric, Relative, Sides, Spec};
|
||||||
use crate::library::layout::PageNode;
|
use crate::library::layout::PageNode;
|
||||||
|
use crate::library::structure::{EnumNode, ListNode};
|
||||||
use crate::library::text::{FontFamily, ParNode, TextNode};
|
use crate::library::text::{FontFamily, ParNode, TextNode};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::util::{Prehashed, ReadableTypeId};
|
use crate::util::{Prehashed, ReadableTypeId};
|
||||||
@ -62,7 +64,7 @@ impl StyleMap {
|
|||||||
pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
pub fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
||||||
self.set(
|
self.set(
|
||||||
TextNode::FAMILY,
|
TextNode::FAMILY,
|
||||||
std::iter::once(preferred)
|
iter::once(preferred)
|
||||||
.chain(existing.get(TextNode::FAMILY).iter().cloned())
|
.chain(existing.get(TextNode::FAMILY).iter().cloned())
|
||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
@ -281,6 +283,8 @@ impl Property {
|
|||||||
Some(Interruption::Page)
|
Some(Interruption::Page)
|
||||||
} else if self.is_of::<ParNode>() {
|
} else if self.is_of::<ParNode>() {
|
||||||
Some(Interruption::Par)
|
Some(Interruption::Par)
|
||||||
|
} else if self.is_of::<ListNode>() || self.is_of::<EnumNode>() {
|
||||||
|
Some(Interruption::List)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@ -503,6 +507,8 @@ impl Debug for Recipe {
|
|||||||
/// Determines whether a style could interrupt some composable structure.
|
/// Determines whether a style could interrupt some composable structure.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum Interruption {
|
pub enum Interruption {
|
||||||
|
/// The style forces a list break.
|
||||||
|
List,
|
||||||
/// The style forces a paragraph break.
|
/// The style forces a paragraph break.
|
||||||
Par,
|
Par,
|
||||||
/// The style forces a page break.
|
/// The style forces a page break.
|
||||||
@ -721,6 +727,17 @@ impl<T> StyleVec<T> {
|
|||||||
self.items.len()
|
self.items.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Insert an element in the front. The element will share the style of the
|
||||||
|
/// current first element.
|
||||||
|
///
|
||||||
|
/// This method has no effect if the vector is empty.
|
||||||
|
pub fn push_front(&mut self, item: T) {
|
||||||
|
if !self.maps.is_empty() {
|
||||||
|
self.items.insert(0, item);
|
||||||
|
self.maps[0].1 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over the contained maps. Note that zipping this with `items()`
|
/// Iterate over the contained maps. Note that zipping this with `items()`
|
||||||
/// does not yield the same result as calling `iter()` because this method
|
/// does not yield the same result as calling `iter()` because this method
|
||||||
/// only returns maps once that are shared by consecutive items. This method
|
/// only returns maps once that are shared by consecutive items. This method
|
||||||
@ -735,24 +752,13 @@ impl<T> StyleVec<T> {
|
|||||||
self.items.iter()
|
self.items.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the contained items and associated style maps.
|
/// Iterate over references to the contained items and associated style maps.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
|
pub fn iter(&self) -> impl Iterator<Item = (&T, &StyleMap)> + '_ {
|
||||||
let styles = self
|
self.items().zip(
|
||||||
.maps
|
self.maps
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(map, count)| std::iter::repeat(map).take(*count));
|
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
|
||||||
self.items().zip(styles)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert an element in the front. The element will share the style of the
|
|
||||||
/// current first element.
|
|
||||||
///
|
|
||||||
/// This method has no effect if the vector is empty.
|
|
||||||
pub fn push_front(&mut self, item: T) {
|
|
||||||
if !self.maps.is_empty() {
|
|
||||||
self.items.insert(0, item);
|
|
||||||
self.maps[0].1 += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -762,6 +768,14 @@ impl<T> Default for StyleVec<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> FromIterator<T> for StyleVec<T> {
|
||||||
|
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
|
||||||
|
let items: Vec<_> = iter.into_iter().collect();
|
||||||
|
let maps = vec![(StyleMap::new(), items.len())];
|
||||||
|
Self { items, maps }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Debug> Debug for StyleVec<T> {
|
impl<T: Debug> Debug for StyleVec<T> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_list()
|
f.debug_list()
|
||||||
@ -787,6 +801,11 @@ impl<'a, T> StyleVecBuilder<'a, T> {
|
|||||||
Self { items: vec![], chains: vec![] }
|
Self { items: vec![], chains: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the builder is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.items.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
/// Push a new item into the style vector.
|
/// Push a new item into the style vector.
|
||||||
pub fn push(&mut self, item: T, styles: StyleChain<'a>) {
|
pub fn push(&mut self, item: T, styles: StyleChain<'a>) {
|
||||||
self.items.push(item);
|
self.items.push(item);
|
||||||
@ -801,13 +820,6 @@ impl<'a, T> StyleVecBuilder<'a, T> {
|
|||||||
self.chains.push((styles, 1));
|
self.chains.push((styles, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the last item mutably and its chain by value.
|
|
||||||
pub fn last_mut(&mut self) -> Option<(&mut T, StyleChain<'a>)> {
|
|
||||||
let item = self.items.last_mut()?;
|
|
||||||
let chain = self.chains.last()?.0;
|
|
||||||
Some((item, chain))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over the contained items.
|
/// Iterate over the contained items.
|
||||||
pub fn items(&self) -> std::slice::Iter<'_, T> {
|
pub fn items(&self) -> std::slice::Iter<'_, T> {
|
||||||
self.items.iter()
|
self.items.iter()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user