Bring back StyleVec (#4323)

This commit is contained in:
Laurenz 2024-06-03 11:39:34 +02:00 committed by GitHub
parent 755dd4112d
commit 3257efd03a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 162 additions and 112 deletions

View File

@ -72,8 +72,8 @@ pub struct Styles(EcoVec<LazyHash<Style>>);
impl Styles { impl Styles {
/// Create a new, empty style list. /// Create a new, empty style list.
pub fn new() -> Self { pub const fn new() -> Self {
Self::default() Self(EcoVec::new())
} }
/// Whether this contains no styles. /// Whether this contains no styles.

View File

@ -9,7 +9,7 @@ use std::fmt::{self, Debug, Formatter};
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
elem, Content, NativeElement, Packed, Resolve, Smart, StyleChain, StyledElem, elem, Args, Construct, Content, NativeElement, Packed, Resolve, Smart, StyleChain,
}; };
use crate::introspection::TagElem; use crate::introspection::TagElem;
use crate::layout::{ use crate::layout::{
@ -17,17 +17,25 @@ use crate::layout::{
Fragment, Frame, FrameItem, PlaceElem, Point, Regions, Rel, Size, Spacing, VElem, Fragment, Frame, FrameItem, PlaceElem, Point, Regions, Rel, Size, Spacing, VElem,
}; };
use crate::model::{FootnoteElem, FootnoteEntry, ParElem}; use crate::model::{FootnoteElem, FootnoteEntry, ParElem};
use crate::realize::StyleVec;
use crate::utils::Numeric; use crate::utils::Numeric;
/// Arranges spacing, paragraphs and block-level elements into a flow. /// Arranges spacing, paragraphs and block-level elements into a flow.
/// ///
/// This element is responsible for layouting both the top-level content flow /// This element is responsible for layouting both the top-level content flow
/// and the contents of boxes. /// and the contents of boxes.
#[elem(Debug)] #[elem(Debug, Construct)]
pub struct FlowElem { pub struct FlowElem {
/// The children that will be arranged into a flow. /// The children that will be arranged into a flow.
#[internal]
#[variadic] #[variadic]
pub children: Vec<Content>, pub children: StyleVec,
}
impl Construct for FlowElem {
fn construct(_: &mut Engine, args: &mut Args) -> SourceResult<Content> {
bail!(args.span, "cannot be constructed manually");
}
} }
impl Packed<FlowElem> { impl Packed<FlowElem> {
@ -54,23 +62,12 @@ impl Packed<FlowElem> {
// through the block & pad and reach the innermost flow, so that things // through the block & pad and reach the innermost flow, so that things
// are properly bottom-aligned. // are properly bottom-aligned.
let mut alone = false; let mut alone = false;
if let [child] = self.children().as_slice() { if let [child] = self.children().elements() {
alone = child alone = child.is::<BlockElem>();
.to_packed::<StyledElem>()
.map_or(child, |styled| &styled.child)
.is::<BlockElem>();
} }
let outer = styles;
let mut layouter = FlowLayouter::new(regions, styles, alone); let mut layouter = FlowLayouter::new(regions, styles, alone);
for mut child in self.children().iter() { for (child, styles) in self.children().chain(&styles) {
let mut styles = styles;
if let Some(styled) = child.to_packed::<StyledElem>() {
child = &styled.child;
styles = outer.chain(&styled.styles);
}
if let Some(elem) = child.to_packed::<TagElem>() { if let Some(elem) = child.to_packed::<TagElem>() {
layouter.layout_tag(elem); layouter.layout_tag(elem);
} else if child.is::<FlushElem>() { } else if child.is::<FlushElem>() {
@ -100,7 +97,7 @@ impl Packed<FlowElem> {
impl Debug for FlowElem { impl Debug for FlowElem {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Flow ")?; write!(f, "Flow ")?;
f.debug_list().entries(&self.children).finish() self.children.fmt(f)
} }
} }

View File

@ -13,13 +13,14 @@ use self::shaping::{
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::{Engine, Route}; use crate::engine::{Engine, Route};
use crate::eval::Tracer; use crate::eval::Tracer;
use crate::foundations::{Content, Packed, Resolve, Smart, StyleChain, StyledElem}; use crate::foundations::{Packed, Resolve, Smart, StyleChain};
use crate::introspection::{Introspector, Locator, TagElem}; use crate::introspection::{Introspector, Locator, TagElem};
use crate::layout::{ use crate::layout::{
Abs, AlignElem, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, FrameItem, Abs, AlignElem, BoxElem, Dir, Em, FixedAlignment, Fr, Fragment, Frame, FrameItem,
HElem, InlineElem, InlineItem, Point, Size, Sizing, Spacing, HElem, InlineElem, InlineItem, Point, Size, Sizing, Spacing,
}; };
use crate::model::{Linebreaks, ParElem}; use crate::model::{Linebreaks, ParElem};
use crate::realize::StyleVec;
use crate::syntax::Span; use crate::syntax::Span;
use crate::text::{ use crate::text::{
Costs, Lang, LinebreakElem, SmartQuoteElem, SmartQuoter, SmartQuotes, SpaceElem, Costs, Lang, LinebreakElem, SmartQuoteElem, SmartQuoter, SmartQuotes, SpaceElem,
@ -30,7 +31,7 @@ use crate::World;
/// Layouts content inline. /// Layouts content inline.
pub(crate) fn layout_inline( pub(crate) fn layout_inline(
children: &[Content], children: &StyleVec,
engine: &mut Engine, engine: &mut Engine,
styles: StyleChain, styles: StyleChain,
consecutive: bool, consecutive: bool,
@ -40,7 +41,7 @@ pub(crate) fn layout_inline(
#[comemo::memoize] #[comemo::memoize]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn cached( fn cached(
children: &[Content], children: &StyleVec,
world: Tracked<dyn World + '_>, world: Tracked<dyn World + '_>,
introspector: Tracked<Introspector>, introspector: Tracked<Introspector>,
route: Tracked<Route>, route: Tracked<Route>,
@ -428,14 +429,14 @@ impl<'a> Line<'a> {
/// Collect all text of the paragraph into one string and layout equations. This /// Collect all text of the paragraph into one string and layout equations. This
/// also performs string-level preprocessing like case transformations. /// also performs string-level preprocessing like case transformations.
fn collect<'a>( fn collect<'a>(
children: &'a [Content], children: &'a StyleVec,
engine: &mut Engine<'_>, engine: &mut Engine<'_>,
styles: &'a StyleChain<'a>, styles: &'a StyleChain<'a>,
region: Size, region: Size,
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.iter().peekable(); let mut iter = children.chain(styles).peekable();
let first_line_indent = ParElem::first_line_indent_in(*styles); let first_line_indent = ParElem::first_line_indent_in(*styles);
if !first_line_indent.is_zero() if !first_line_indent.is_zero()
@ -455,14 +456,7 @@ fn collect<'a>(
let outer_dir = TextElem::dir_in(*styles); let outer_dir = TextElem::dir_in(*styles);
while let Some(mut child) = iter.next() { while let Some((child, styles)) = iter.next() {
let outer = styles;
let mut styles = *styles;
if let Some(styled) = child.to_packed::<StyledElem>() {
child = &styled.child;
styles = outer.chain(&styled.styles);
}
let prev_len = collector.full.len(); let prev_len = collector.full.len();
if child.is::<SpaceElem>() { if child.is::<SpaceElem>() {
@ -515,12 +509,7 @@ fn collect<'a>(
TextElem::region_in(styles), TextElem::region_in(styles),
elem.alternative(styles), elem.alternative(styles),
); );
let peeked = iter.peek().and_then(|&child| { let peeked = iter.peek().and_then(|(child, _)| {
let child = if let Some(styled) = child.to_packed::<StyledElem>() {
&styled.child
} else {
child
};
if let Some(elem) = child.to_packed::<TextElem>() { if let Some(elem) = child.to_packed::<TextElem>() {
elem.text().chars().next() elem.text().chars().next()
} else if child.is::<SmartQuoteElem>() { } else if child.is::<SmartQuoteElem>() {
@ -642,7 +631,7 @@ impl<'a> Collector<'a> {
/// Prepare paragraph layout by shaping the whole paragraph. /// Prepare paragraph layout by shaping the whole paragraph.
fn prepare<'a>( fn prepare<'a>(
engine: &mut Engine, engine: &mut Engine,
children: &'a [Content], children: &'a StyleVec,
text: &'a str, text: &'a str,
segments: Vec<Segment<'a>>, segments: Vec<Segment<'a>>,
spans: SpanMapper, spans: SpanMapper,
@ -682,9 +671,9 @@ fn prepare<'a>(
bidi, bidi,
items, items,
spans, spans,
hyphenate: shared_get(styles, children, TextElem::hyphenate_in), hyphenate: children.shared_get(styles, TextElem::hyphenate_in),
costs: TextElem::costs_in(styles), costs: TextElem::costs_in(styles),
lang: shared_get(styles, children, TextElem::lang_in), lang: children.shared_get(styles, TextElem::lang_in),
align: AlignElem::alignment_in(styles).resolve(styles).x, align: AlignElem::alignment_in(styles).resolve(styles).x,
justify: ParElem::justify_in(styles), justify: ParElem::justify_in(styles),
hang: ParElem::hanging_indent_in(styles), hang: ParElem::hanging_indent_in(styles),
@ -815,21 +804,6 @@ fn is_compatible(a: Script, b: Script) -> bool {
is_generic_script(a) || is_generic_script(b) || a == b is_generic_script(a) || is_generic_script(b) || a == b
} }
/// Get a style property, but only if it is the same for all children of the
/// paragraph.
fn shared_get<T: PartialEq>(
styles: StyleChain<'_>,
children: &[Content],
getter: fn(StyleChain) -> T,
) -> Option<T> {
let value = getter(styles);
children
.iter()
.filter_map(|child| child.to_packed::<StyledElem>())
.all(|styled| getter(styles.chain(&styled.styles)) == value)
.then_some(value)
}
/// Find suitable linebreaks. /// Find suitable linebreaks.
fn linebreak<'a>(engine: &Engine, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> { fn linebreak<'a>(engine: &Engine, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
let linebreaks = p.linebreaks.unwrap_or_else(|| { let linebreaks = p.linebreaks.unwrap_or_else(|| {

View File

@ -1,6 +1,6 @@
use std::f64::consts::SQRT_2; use std::f64::consts::SQRT_2;
use ecow::EcoString; use ecow::{eco_vec, EcoString};
use rustybuzz::Feature; use rustybuzz::Feature;
use ttf_parser::gsub::{AlternateSubstitution, SingleSubstitution, SubstitutionSubtable}; use ttf_parser::gsub::{AlternateSubstitution, SingleSubstitution, SubstitutionSubtable};
use ttf_parser::math::MathValue; use ttf_parser::math::MathValue;
@ -18,6 +18,7 @@ use crate::math::{
LayoutMath, MathFragment, MathRun, MathSize, THICK, LayoutMath, MathFragment, MathRun, MathSize, THICK,
}; };
use crate::model::ParElem; use crate::model::ParElem;
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,
@ -286,7 +287,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
// to extend as far as needed. // to extend as far as needed.
let spaced = text.graphemes(true).nth(1).is_some(); let spaced = text.graphemes(true).nth(1).is_some();
let text = TextElem::packed(text).spanned(span); let text = TextElem::packed(text).spanned(span);
let par = ParElem::new(vec![text]); let par = ParElem::new(StyleVec::wrap(eco_vec![text]));
let frame = Packed::new(par) let frame = Packed::new(par)
.spanned(span) .spanned(span)
.layout(self.engine, styles, false, Size::splat(Abs::inf()), false)? .layout(self.engine, styles, false, Size::splat(Abs::inf()), false)?

View File

@ -241,7 +241,7 @@ impl LayoutMath for Content {
self.sequence_recursive_for_each(&mut |child: &Content| { self.sequence_recursive_for_each(&mut |child: &Content| {
bb.push(child, StyleChain::default()); bb.push(child, StyleChain::default());
}); });
for child in bb.finish::<Content>().0 { for (child, _) in bb.finish().0.chain(&styles) {
child.layout_math(ctx, styles)?; child.layout_math(ctx, styles)?;
} }
return Ok(()); return Ok(());

View File

@ -4,10 +4,11 @@ use crate::diag::{bail, SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain, cast, elem, Args, Array, Construct, Content, Datetime, Packed, Smart, StyleChain,
StyledElem, Value, Value,
}; };
use crate::introspection::{Introspector, ManualPageCounter}; use crate::introspection::{Introspector, ManualPageCounter};
use crate::layout::{Page, PageElem}; use crate::layout::{Page, PageElem};
use crate::realize::StyleVec;
/// The root element of a document and its metadata. /// The root element of a document and its metadata.
/// ///
@ -60,7 +61,7 @@ pub struct DocumentElem {
/// The page runs. /// The page runs.
#[internal] #[internal]
#[variadic] #[variadic]
pub children: Vec<Content>, pub children: StyleVec,
} }
impl Construct for DocumentElem { impl Construct for DocumentElem {
@ -81,24 +82,13 @@ impl Packed<DocumentElem> {
let mut page_counter = ManualPageCounter::new(); let mut page_counter = ManualPageCounter::new();
let children = self.children(); let children = self.children();
let mut iter = children.iter().peekable(); let mut iter = children.chain(&styles).peekable();
while let Some(mut child) = iter.next() {
let outer = styles;
let mut styles = styles;
if let Some(styled) = child.to_packed::<StyledElem>() {
child = &styled.child;
styles = outer.chain(&styled.styles);
}
while let Some((child, styles)) = iter.next() {
if let Some(page) = child.to_packed::<PageElem>() { if let Some(page) = child.to_packed::<PageElem>() {
let extend_to = iter.peek().and_then(|&next| { let extend_to = iter
*next .peek()
.to_packed::<StyledElem>() .and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
.map_or(next, |styled| &styled.child)
.to_packed::<PageElem>()?
.clear_to()?
});
let run = page.layout(engine, styles, &mut page_counter, extend_to)?; let run = page.layout(engine, styles, &mut page_counter, extend_to)?;
pages.extend(run); pages.extend(run);
} else { } else {

View File

@ -7,6 +7,7 @@ use crate::foundations::{
Unlabellable, Unlabellable,
}; };
use crate::layout::{Em, Fragment, Length, Size}; use crate::layout::{Em, Fragment, Length, Size};
use crate::realize::StyleVec;
/// Arranges text, spacing and inline-level elements into a paragraph. /// Arranges text, spacing and inline-level elements into a paragraph.
/// ///
@ -113,7 +114,7 @@ pub struct ParElem {
/// The paragraph's children. /// The paragraph's children.
#[internal] #[internal]
#[variadic] #[variadic]
pub children: Vec<Content>, pub children: StyleVec,
} }
impl Construct for ParElem { impl Construct for ParElem {
@ -143,7 +144,7 @@ impl Packed<ParElem> {
expand: bool, expand: bool,
) -> SourceResult<Fragment> { ) -> SourceResult<Fragment> {
crate::layout::layout_inline( crate::layout::layout_inline(
self.children(), &self.children,
engine, engine,
styles, styles,
consecutive, consecutive,
@ -156,7 +157,7 @@ impl Packed<ParElem> {
impl Debug for ParElem { impl Debug for ParElem {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Par ")?; write!(f, "Par ")?;
f.debug_list().entries(&self.children).finish() self.children.fmt(f)
} }
} }

View File

@ -1,5 +1,9 @@
//! Element interaction. //! Element interaction.
use std::fmt::{Debug, Formatter};
use ecow::EcoVec;
use crate::foundations::{Content, StyleChain, Styles}; use crate::foundations::{Content, StyleChain, Styles};
use crate::syntax::Span; use crate::syntax::Span;
@ -125,44 +129,39 @@ impl<'a> BehavedBuilder<'a> {
/// Return the built content (possibly styled with local styles) plus a /// Return the built content (possibly styled with local styles) plus a
/// trunk style chain and a span for the collection. /// trunk style chain and a span for the collection.
pub fn finish<F: From<Content>>(self) -> (Vec<F>, StyleChain<'a>, Span) { pub fn finish(mut self) -> (StyleVec, StyleChain<'a>, Span) {
let (output, trunk, span) = self.finish_iter();
let output = output.map(|(c, s)| c.clone().styled_with_map(s).into()).collect();
(output, trunk, span)
}
/// Return an iterator over the built content and its local styles plus a
/// trunk style chain and a span for the collection.
pub fn finish_iter(
mut self,
) -> (impl Iterator<Item = (&'a Content, Styles)>, StyleChain<'a>, Span) {
self.trim_weak(); self.trim_weak();
let span = self.determine_span(); let span = self.determine_span();
let (trunk, depth) = self.determine_style_trunk(); let (trunk, depth) = self.determine_style_trunk();
let mut iter = self.buf.into_iter().peekable(); let mut elements = EcoVec::with_capacity(self.buf.len());
let mut reuse = None; let mut styles = EcoVec::<(Styles, usize)>::new();
let mut last: Option<(StyleChain<'a>, usize)> = None;
// Map the content + style chains to content + suffix maps, reusing for (element, chain) in self.buf.into_iter() {
// equivalent adjacent suffix maps, if possible. elements.push(element.clone());
let output = std::iter::from_fn(move || {
let (c, s) = iter.next()?;
// Try to reuse a suffix map that the previous element has if let Some((prev, run)) = &mut last {
// stored for us. if chain == *prev {
let suffix = reuse.take().unwrap_or_else(|| s.suffix(depth)); *run += 1;
} else {
// Store a suffix map for the next element if it has the same style styles.push((prev.suffix(depth), *run));
// chain. last = Some((chain, 1));
if iter.peek().is_some_and(|&(_, s2)| s == s2) { }
reuse = Some(suffix.clone()); } else {
last = Some((chain, 1));
}
} }
Some((c, suffix)) if let Some((last, run)) = last {
}); let skippable = styles.is_empty() && last == trunk;
if !skippable {
styles.push((last.suffix(depth), run));
}
}
(output, trunk, span) (StyleVec { elements, styles }, trunk, span)
} }
/// Trim a possibly remaining weak item. /// Trim a possibly remaining weak item.
@ -228,3 +227,91 @@ impl<'a> Default for BehavedBuilder<'a> {
Self::new() Self::new()
} }
} }
/// A sequence of elements with associated styles.
#[derive(Clone, PartialEq, Hash)]
pub struct StyleVec {
elements: EcoVec<Content>,
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() }
}
/// 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()
}
}

View File

@ -11,7 +11,7 @@ mod behaviour;
mod process; mod process;
pub use self::arenas::Arenas; pub use self::arenas::Arenas;
pub use self::behaviour::{Behave, BehavedBuilder, Behaviour}; pub use self::behaviour::{Behave, BehavedBuilder, Behaviour, StyleVec};
pub use self::process::process; pub use self::process::process;
use std::mem; use std::mem;
@ -504,8 +504,8 @@ impl<'a> ListBuilder<'a> {
/// 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) -> (Content, StyleChain<'a>) {
let (items, trunk, span) = self.items.finish_iter(); let (items, trunk, span) = self.items.finish();
let mut items = items.peekable(); let mut items = items.into_iter().peekable();
let (first, _) = items.peek().unwrap(); let (first, _) = items.peek().unwrap();
let output = if first.is::<ListItem>() { let output = if first.is::<ListItem>() {
ListElem::new( ListElem::new(