mirror of
https://github.com/typst/typst
synced 2025-06-28 00:03:17 +08:00
Move layout traits into library
This commit is contained in:
parent
37ac5d966e
commit
56342bd972
@ -85,7 +85,7 @@ fn bench_layout(iai: &mut Iai) {
|
|||||||
let id = world.source.id();
|
let id = world.source.id();
|
||||||
let route = typst::model::Route::default();
|
let route = typst::model::Route::default();
|
||||||
let module = typst::model::eval(world.track(), route.track(), id).unwrap();
|
let module = typst::model::eval(world.track(), route.track(), id).unwrap();
|
||||||
iai.run(|| typst::model::layout(world.track(), &module.content));
|
iai.run(|| typst::library::layout::Layout::layout(&module.content, world.track()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_render(iai: &mut Iai) {
|
fn bench_render(iai: &mut Iai) {
|
||||||
|
@ -8,17 +8,31 @@ use syn::punctuated::Punctuated;
|
|||||||
use syn::spanned::Spanned;
|
use syn::spanned::Spanned;
|
||||||
use syn::{Error, Ident, Result};
|
use syn::{Error, Ident, Result};
|
||||||
|
|
||||||
/// Turn a struct into a node / a function with settable properties.
|
/// Implement `Capability` for a trait.
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn capability(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let item_trait = syn::parse_macro_input!(item as syn::ItemTrait);
|
||||||
|
let name = &item_trait.ident;
|
||||||
|
quote! {
|
||||||
|
#item_trait
|
||||||
|
impl crate::model::Capability for dyn #name {}
|
||||||
|
}.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement `Node` for a struct.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn node(stream: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn node(stream: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
let impl_block = syn::parse_macro_input!(item as syn::ItemImpl);
|
let impl_block = syn::parse_macro_input!(item as syn::ItemImpl);
|
||||||
expand(stream.into(), impl_block)
|
expand_node(stream.into(), impl_block)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand an impl block for a node.
|
/// Expand an impl block for a node.
|
||||||
fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenStream2> {
|
fn expand_node(
|
||||||
|
stream: TokenStream2,
|
||||||
|
mut impl_block: syn::ItemImpl,
|
||||||
|
) -> Result<TokenStream2> {
|
||||||
// Split the node type into name and generic type arguments.
|
// Split the node type into name and generic type arguments.
|
||||||
let params = &impl_block.generics.params;
|
let params = &impl_block.generics.params;
|
||||||
let self_ty = &*impl_block.self_ty;
|
let self_ty = &*impl_block.self_ty;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
//! structure, layouts, etc. of the module. The nodes of the content tree are
|
//! structure, layouts, etc. of the module. The nodes of the content tree are
|
||||||
//! well structured and order-independent and thus much better suited for
|
//! well structured and order-independent and thus much better suited for
|
||||||
//! layouting than the raw markup.
|
//! layouting than the raw markup.
|
||||||
//! - **Layouting:** Next, the content is [layouted] into a portable version of
|
//! - **Layouting:** Next, the content is layouted into a portable version of
|
||||||
//! the typeset document. The output of this is a collection of [`Frame`]s
|
//! the typeset document. The output of this is a collection of [`Frame`]s
|
||||||
//! (one per page), ready for exporting.
|
//! (one per page), ready for exporting.
|
||||||
//! - **Exporting:** The finished layout can be exported into a supported
|
//! - **Exporting:** The finished layout can be exported into a supported
|
||||||
@ -24,7 +24,6 @@
|
|||||||
//! [evaluate]: model::eval
|
//! [evaluate]: model::eval
|
||||||
//! [module]: model::Module
|
//! [module]: model::Module
|
||||||
//! [content]: model::Content
|
//! [content]: model::Content
|
||||||
//! [layouted]: model::layout
|
|
||||||
//! [PDF]: export::pdf
|
//! [PDF]: export::pdf
|
||||||
|
|
||||||
#![allow(clippy::len_without_is_empty)]
|
#![allow(clippy::len_without_is_empty)]
|
||||||
@ -69,7 +68,7 @@ pub fn typeset(
|
|||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let route = Route::default();
|
let route = Route::default();
|
||||||
let module = model::eval(world.track(), route.track(), main)?;
|
let module = model::eval(world.track(), route.track(), main)?;
|
||||||
model::layout(world.track(), &module.content)
|
library::layout::Layout::layout(&module.content, world.track())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The environment in which typesetting occurs.
|
/// The environment in which typesetting occurs.
|
||||||
|
179
src/library/ext.rs
Normal file
179
src/library/ext.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
|
/// Additional methods on content.
|
||||||
|
pub trait ContentExt {
|
||||||
|
/// Make this content strong.
|
||||||
|
fn strong(self) -> Self;
|
||||||
|
|
||||||
|
/// Make this content emphasized.
|
||||||
|
fn emph(self) -> Self;
|
||||||
|
|
||||||
|
/// Underline this content.
|
||||||
|
fn underlined(self) -> Self;
|
||||||
|
|
||||||
|
/// Add weak vertical spacing above and below the content.
|
||||||
|
fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self;
|
||||||
|
|
||||||
|
/// Force a size for this content.
|
||||||
|
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
|
||||||
|
|
||||||
|
/// Set alignments for this content.
|
||||||
|
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
|
||||||
|
|
||||||
|
/// Pad this content at the sides.
|
||||||
|
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
|
||||||
|
|
||||||
|
/// Transform this content's contents without affecting layout.
|
||||||
|
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
||||||
|
|
||||||
|
/// Fill the frames resulting from a content.
|
||||||
|
fn filled(self, fill: Paint) -> Self;
|
||||||
|
|
||||||
|
/// Stroke the frames resulting from a content.
|
||||||
|
fn stroked(self, stroke: Stroke) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContentExt for Content {
|
||||||
|
fn strong(self) -> Self {
|
||||||
|
text::StrongNode(self).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emph(self) -> Self {
|
||||||
|
text::EmphNode(self).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn underlined(self) -> Self {
|
||||||
|
text::DecoNode::<{ text::UNDERLINE }>(self).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self {
|
||||||
|
if above.is_none() && below.is_none() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut seq = vec![];
|
||||||
|
if let Some(above) = above {
|
||||||
|
seq.push(
|
||||||
|
layout::VNode {
|
||||||
|
amount: above.into(),
|
||||||
|
weak: true,
|
||||||
|
generated: true,
|
||||||
|
}
|
||||||
|
.pack(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
seq.push(self);
|
||||||
|
if let Some(below) = below {
|
||||||
|
seq.push(
|
||||||
|
layout::VNode {
|
||||||
|
amount: below.into(),
|
||||||
|
weak: true,
|
||||||
|
generated: true,
|
||||||
|
}
|
||||||
|
.pack(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Content::sequence(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
|
||||||
|
layout::BoxNode { sizing, child: self }.pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
|
||||||
|
layout::AlignNode { aligns, child: self }.pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
||||||
|
layout::PadNode { padding, child: self }.pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
||||||
|
layout::MoveNode { delta, child: self }.pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filled(self, fill: Paint) -> Self {
|
||||||
|
FillNode { fill, child: self }.pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stroked(self, stroke: Stroke) -> Self {
|
||||||
|
StrokeNode { stroke, child: self }.pack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional methods for the style chain.
|
||||||
|
pub trait StyleMapExt {
|
||||||
|
/// Set a font family composed of a preferred family and existing families
|
||||||
|
/// from a style chain.
|
||||||
|
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StyleMapExt for StyleMap {
|
||||||
|
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) {
|
||||||
|
self.set(
|
||||||
|
text::TextNode::FAMILY,
|
||||||
|
std::iter::once(preferred)
|
||||||
|
.chain(existing.get(text::TextNode::FAMILY).iter().cloned())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fill the frames resulting from content.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
struct FillNode {
|
||||||
|
/// How to fill the frames resulting from the `child`.
|
||||||
|
fill: Paint,
|
||||||
|
/// The content whose frames should be filled.
|
||||||
|
child: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node(LayoutBlock)]
|
||||||
|
impl FillNode {}
|
||||||
|
|
||||||
|
impl LayoutBlock for FillNode {
|
||||||
|
fn layout_block(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
let mut frames = self.child.layout_block(world, regions, styles)?;
|
||||||
|
for frame in &mut frames {
|
||||||
|
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
||||||
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
|
}
|
||||||
|
Ok(frames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stroke the frames resulting from content.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
struct StrokeNode {
|
||||||
|
/// How to stroke the frames resulting from the `child`.
|
||||||
|
stroke: Stroke,
|
||||||
|
/// The content whose frames should be stroked.
|
||||||
|
child: Content,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node(LayoutBlock)]
|
||||||
|
impl StrokeNode {}
|
||||||
|
|
||||||
|
impl LayoutBlock for StrokeNode {
|
||||||
|
fn layout_block(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
let mut frames = self.child.layout_block(world, regions, styles)?;
|
||||||
|
for frame in &mut frames {
|
||||||
|
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
||||||
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
|
}
|
||||||
|
Ok(frames)
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,18 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Hide a node without affecting layout.
|
/// Hide content without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct HideNode(pub Content);
|
pub struct HideNode(pub Content);
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl HideNode {
|
impl HideNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self(args.expect("body")?).pack())
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for HideNode {
|
impl LayoutInline for HideNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -24,8 +24,4 @@ impl Layout for HideNode {
|
|||||||
}
|
}
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ use crate::library::text::TextNode;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ImageNode(pub Image);
|
pub struct ImageNode(pub Image);
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl ImageNode {
|
impl ImageNode {
|
||||||
/// How the image should adjust itself to a given area.
|
/// How the image should adjust itself to a given area.
|
||||||
pub const FIT: ImageFit = ImageFit::Cover;
|
pub const FIT: ImageFit = ImageFit::Cover;
|
||||||
@ -36,8 +36,8 @@ impl ImageNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl LayoutInline for ImageNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
_: Tracked<dyn World>,
|
_: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -95,10 +95,6 @@ impl Layout for ImageNode {
|
|||||||
|
|
||||||
Ok(vec![frame])
|
Ok(vec![frame])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// How an image should adjust itself to a given area.
|
/// How an image should adjust itself to a given area.
|
||||||
|
@ -9,7 +9,7 @@ pub struct LineNode {
|
|||||||
delta: Axes<Rel<Length>>,
|
delta: Axes<Rel<Length>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl LineNode {
|
impl LineNode {
|
||||||
/// How to stroke the line.
|
/// How to stroke the line.
|
||||||
#[property(resolve, fold)]
|
#[property(resolve, fold)]
|
||||||
@ -36,8 +36,8 @@ impl LineNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for LineNode {
|
impl LayoutInline for LineNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
_: Tracked<dyn World>,
|
_: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -65,10 +65,6 @@ impl Layout for LineNode {
|
|||||||
|
|
||||||
Ok(vec![frame])
|
Ok(vec![frame])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
|
@ -3,23 +3,23 @@ use std::f64::consts::SQRT_2;
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::TextNode;
|
use crate::library::text::TextNode;
|
||||||
|
|
||||||
/// Place a node into a sizable and fillable shape.
|
/// A sizable and fillable shape with optional content.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ShapeNode<const S: ShapeKind>(pub Option<Content>);
|
pub struct ShapeNode<const S: ShapeKind>(pub Option<Content>);
|
||||||
|
|
||||||
/// Place a node into a square.
|
/// A square with optional content.
|
||||||
pub type SquareNode = ShapeNode<SQUARE>;
|
pub type SquareNode = ShapeNode<SQUARE>;
|
||||||
|
|
||||||
/// Place a node into a rectangle.
|
/// A rectangle with optional content.
|
||||||
pub type RectNode = ShapeNode<RECT>;
|
pub type RectNode = ShapeNode<RECT>;
|
||||||
|
|
||||||
/// Place a node into a circle.
|
/// A circle with optional content.
|
||||||
pub type CircleNode = ShapeNode<CIRCLE>;
|
pub type CircleNode = ShapeNode<CIRCLE>;
|
||||||
|
|
||||||
/// Place a node into an ellipse.
|
/// A ellipse with optional content.
|
||||||
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl<const S: ShapeKind> ShapeNode<S> {
|
impl<const S: ShapeKind> ShapeNode<S> {
|
||||||
/// How to fill the shape.
|
/// How to fill the shape.
|
||||||
pub const FILL: Option<Paint> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
@ -72,8 +72,8 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -173,10 +173,6 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A category of shape.
|
/// A category of shape.
|
||||||
|
@ -1,26 +1,23 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{HorizontalAlign, ParNode};
|
use crate::library::text::{HorizontalAlign, ParNode};
|
||||||
|
|
||||||
/// Align a node along the layouting axes.
|
/// Align content along the layouting axes.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct AlignNode {
|
pub struct AlignNode {
|
||||||
/// How to align the node horizontally and vertically.
|
/// How to align the content horizontally and vertically.
|
||||||
pub aligns: Axes<Option<RawAlign>>,
|
pub aligns: Axes<Option<RawAlign>>,
|
||||||
/// The node to be aligned.
|
/// The content to be aligned.
|
||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl AlignNode {
|
impl AlignNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
|
let aligns: Axes<Option<RawAlign>> = args.find()?.unwrap_or_default();
|
||||||
let body: Content = args.expect("body")?;
|
let body: Content = args.expect("body")?;
|
||||||
|
|
||||||
if let Axes { x: Some(x), y: None } = aligns {
|
if let Axes { x: Some(x), y: None } = aligns {
|
||||||
if body
|
if !body.has::<dyn LayoutBlock>() {
|
||||||
.to::<dyn Layout>()
|
|
||||||
.map_or(true, |node| node.level() == Level::Inline)
|
|
||||||
{
|
|
||||||
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
|
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,8 +26,8 @@ impl AlignNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for AlignNode {
|
impl LayoutBlock for AlignNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -62,8 +59,4 @@ impl Layout for AlignNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ pub struct ColumnsNode {
|
|||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl ColumnsNode {
|
impl ColumnsNode {
|
||||||
/// The size of the gutter space between each column.
|
/// The size of the gutter space between each column.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -26,8 +26,8 @@ impl ColumnsNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ColumnsNode {
|
impl LayoutBlock for ColumnsNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -66,7 +66,7 @@ impl Layout for ColumnsNode {
|
|||||||
|
|
||||||
// Stitch together the columns for each region.
|
// Stitch together the columns for each region.
|
||||||
for region in regions.iter().take(total_regions) {
|
for region in regions.iter().take(total_regions) {
|
||||||
// The height should be the parent height if the node shall expand.
|
// The height should be the parent height if we should expand.
|
||||||
// Otherwise its the maximum column height for the frame. In that
|
// Otherwise its the maximum column height for the frame. In that
|
||||||
// case, the frame is first created with zero height and then
|
// case, the frame is first created with zero height and then
|
||||||
// resized.
|
// resized.
|
||||||
@ -100,10 +100,6 @@ impl Layout for ColumnsNode {
|
|||||||
|
|
||||||
Ok(finished)
|
Ok(finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A column break.
|
/// A column break.
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// An inline-level container that sizes content and places it into a paragraph.
|
/// An inline-level container that sizes content.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct BoxNode {
|
pub struct BoxNode {
|
||||||
/// How to size the node horizontally and vertically.
|
/// How to size the content horizontally and vertically.
|
||||||
pub sizing: Axes<Option<Rel<Length>>>,
|
pub sizing: Axes<Option<Rel<Length>>>,
|
||||||
/// The node to be sized.
|
/// The content to be sized.
|
||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl BoxNode {
|
impl BoxNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
@ -19,8 +19,8 @@ impl BoxNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for BoxNode {
|
impl LayoutInline for BoxNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -55,25 +55,21 @@ impl Layout for BoxNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A block-level container that places content into a separate flow.
|
/// A block-level container that places content into a separate flow.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct BlockNode(pub Content);
|
pub struct BlockNode(pub Content);
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl BlockNode {
|
impl BlockNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self(args.eat()?.unwrap_or_default()).pack())
|
Ok(Self(args.eat()?.unwrap_or_default()).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for BlockNode {
|
impl LayoutBlock for BlockNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -81,8 +77,4 @@ impl Layout for BlockNode {
|
|||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
self.0.layout_block(world, regions, styles)
|
self.0.layout_block(world, regions, styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use super::{AlignNode, PlaceNode, Spacing};
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::ParNode;
|
use crate::library::text::ParNode;
|
||||||
|
|
||||||
/// Arrange spacing, paragraphs and other block-level nodes into a flow.
|
/// Arrange spacing, paragraphs and block-level nodes into a flow.
|
||||||
///
|
///
|
||||||
/// This node is reponsible for layouting both the top-level content flow and
|
/// This node is reponsible for layouting both the top-level content flow and
|
||||||
/// the contents of boxes.
|
/// the contents of boxes.
|
||||||
@ -16,17 +16,17 @@ pub struct FlowNode(pub StyleVec<FlowChild>);
|
|||||||
pub enum FlowChild {
|
pub enum FlowChild {
|
||||||
/// Vertical spacing between other children.
|
/// Vertical spacing between other children.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary block-level node.
|
/// Arbitrary block-level content.
|
||||||
Node(Content),
|
Block(Content),
|
||||||
/// A column / region break.
|
/// A column / region break.
|
||||||
Colbreak,
|
Colbreak,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl FlowNode {}
|
impl FlowNode {}
|
||||||
|
|
||||||
impl Layout for FlowNode {
|
impl LayoutBlock for FlowNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -40,8 +40,8 @@ impl Layout for FlowNode {
|
|||||||
FlowChild::Spacing(kind) => {
|
FlowChild::Spacing(kind) => {
|
||||||
layouter.layout_spacing(*kind, styles);
|
layouter.layout_spacing(*kind, styles);
|
||||||
}
|
}
|
||||||
FlowChild::Node(ref node) => {
|
FlowChild::Block(block) => {
|
||||||
layouter.layout_node(world, node, styles)?;
|
layouter.layout_block(world, block, styles)?;
|
||||||
}
|
}
|
||||||
FlowChild::Colbreak => {
|
FlowChild::Colbreak => {
|
||||||
layouter.finish_region();
|
layouter.finish_region();
|
||||||
@ -51,10 +51,6 @@ impl Layout for FlowNode {
|
|||||||
|
|
||||||
Ok(layouter.finish())
|
Ok(layouter.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for FlowNode {
|
impl Debug for FlowNode {
|
||||||
@ -68,7 +64,7 @@ impl Debug for FlowChild {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Block(block) => block.fmt(f),
|
||||||
Self::Colbreak => f.pad("Colbreak"),
|
Self::Colbreak => f.pad("Colbreak"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,7 +92,7 @@ pub struct FlowLayouter {
|
|||||||
used: Size,
|
used: Size,
|
||||||
/// The sum of fractions in the current region.
|
/// The sum of fractions in the current region.
|
||||||
fr: Fr,
|
fr: Fr,
|
||||||
/// Spacing and layouted nodes.
|
/// Spacing and layouted blocks.
|
||||||
items: Vec<FlowItem>,
|
items: Vec<FlowItem>,
|
||||||
/// Finished frames for previous regions.
|
/// Finished frames for previous regions.
|
||||||
finished: Vec<Frame>,
|
finished: Vec<Frame>,
|
||||||
@ -108,7 +104,7 @@ enum FlowItem {
|
|||||||
Absolute(Abs),
|
Absolute(Abs),
|
||||||
/// Fractional spacing between other items.
|
/// Fractional spacing between other items.
|
||||||
Fractional(Fr),
|
Fractional(Fr),
|
||||||
/// A frame for a layouted child node and how to align it.
|
/// A frame for a layouted block and how to align it.
|
||||||
Frame(Frame, Axes<Align>),
|
Frame(Frame, Axes<Align>),
|
||||||
/// An absolutely placed frame.
|
/// An absolutely placed frame.
|
||||||
Placed(Frame),
|
Placed(Frame),
|
||||||
@ -153,11 +149,11 @@ impl FlowLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a node.
|
/// Layout a block.
|
||||||
pub fn layout_node(
|
pub fn layout_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
node: &Content,
|
block: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
// Don't even try layouting into a full region.
|
// Don't even try layouting into a full region.
|
||||||
@ -167,27 +163,28 @@ impl FlowLayouter {
|
|||||||
|
|
||||||
// Placed nodes that are out of flow produce placed items which aren't
|
// Placed nodes that are out of flow produce placed items which aren't
|
||||||
// aligned later.
|
// aligned later.
|
||||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
if let Some(placed) = block.downcast::<PlaceNode>() {
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow() {
|
||||||
let frame = node.layout_block(world, &self.regions, styles)?.remove(0);
|
let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
|
||||||
self.items.push(FlowItem::Placed(frame));
|
self.items.push(FlowItem::Placed(frame));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// How to align the node.
|
// How to align the block.
|
||||||
let aligns = Axes::new(
|
let aligns = Axes::new(
|
||||||
// For non-expanding paragraphs it is crucial that we align the
|
// For non-expanding paragraphs it is crucial that we align the
|
||||||
// whole paragraph as it is itself aligned.
|
// whole paragraph as it is itself aligned.
|
||||||
styles.get(ParNode::ALIGN),
|
styles.get(ParNode::ALIGN),
|
||||||
// Vertical align node alignment is respected by the flow node.
|
// Vertical align node alignment is respected by the flow.
|
||||||
node.downcast::<AlignNode>()
|
block
|
||||||
|
.downcast::<AlignNode>()
|
||||||
.and_then(|aligned| aligned.aligns.y)
|
.and_then(|aligned| aligned.aligns.y)
|
||||||
.map(|align| align.resolve(styles))
|
.map(|align| align.resolve(styles))
|
||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
let frames = node.layout_block(world, &self.regions, styles)?;
|
let frames = block.layout_block(world, &self.regions, styles)?;
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, mut frame) in frames.into_iter().enumerate() {
|
for (i, mut frame) in frames.into_iter().enumerate() {
|
||||||
// Set the generic block role.
|
// Set the generic block role.
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Arrange nodes in a grid.
|
/// Arrange content in a grid.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct GridNode {
|
pub struct GridNode {
|
||||||
/// Defines sizing for content rows and columns.
|
/// Defines sizing for content rows and columns.
|
||||||
pub tracks: Axes<Vec<TrackSizing>>,
|
pub tracks: Axes<Vec<TrackSizing>>,
|
||||||
/// Defines sizing of gutter rows and columns between content.
|
/// Defines sizing of gutter rows and columns between content.
|
||||||
pub gutter: Axes<Vec<TrackSizing>>,
|
pub gutter: Axes<Vec<TrackSizing>>,
|
||||||
/// The nodes to be arranged in a grid.
|
/// The content to be arranged in a grid.
|
||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl GridNode {
|
impl GridNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let columns = args.named("columns")?.unwrap_or_default();
|
let columns = args.named("columns")?.unwrap_or_default();
|
||||||
@ -31,8 +31,8 @@ impl GridNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for GridNode {
|
impl LayoutBlock for GridNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -51,10 +51,6 @@ impl Layout for GridNode {
|
|||||||
// Measure the columns and layout the grid row-by-row.
|
// Measure the columns and layout the grid row-by-row.
|
||||||
layouter.layout()
|
layouter.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how to size a grid cell along an axis.
|
/// Defines how to size a grid cell along an axis.
|
||||||
@ -293,7 +289,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
let mut resolved = Abs::zero();
|
let mut resolved = Abs::zero();
|
||||||
for y in 0 .. self.rows.len() {
|
for y in 0 .. self.rows.len() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(cell) = self.cell(x, y) {
|
||||||
let size = Size::new(available, self.regions.base.y);
|
let size = Size::new(available, self.regions.base.y);
|
||||||
let mut pod =
|
let mut pod =
|
||||||
Regions::one(size, self.regions.base, Axes::splat(false));
|
Regions::one(size, self.regions.base, Axes::splat(false));
|
||||||
@ -307,7 +303,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let frame =
|
let frame =
|
||||||
node.layout_block(self.world, &pod, self.styles)?.remove(0);
|
cell.layout_block(self.world, &pod, self.styles)?.remove(0);
|
||||||
resolved.set_max(frame.width());
|
resolved.set_max(frame.width());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -366,7 +362,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
|
|
||||||
// Determine the size for each region of the row.
|
// Determine the size for each region of the row.
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(cell) = self.cell(x, y) {
|
||||||
let mut pod = self.regions.clone();
|
let mut pod = self.regions.clone();
|
||||||
pod.first.x = rcol;
|
pod.first.x = rcol;
|
||||||
pod.base.x = rcol;
|
pod.base.x = rcol;
|
||||||
@ -376,7 +372,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pod.base.x = self.regions.base.x;
|
pod.base.x = self.regions.base.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sizes = node
|
let mut sizes = cell
|
||||||
.layout_block(self.world, &pod, self.styles)?
|
.layout_block(self.world, &pod, self.styles)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|frame| frame.height());
|
.map(|frame| frame.height());
|
||||||
@ -456,7 +452,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
|
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(cell) = self.cell(x, y) {
|
||||||
let size = Size::new(rcol, height);
|
let size = Size::new(rcol, height);
|
||||||
|
|
||||||
// Set the base to the region's base for auto rows and to the
|
// Set the base to the region's base for auto rows and to the
|
||||||
@ -466,7 +462,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.select(self.regions.base, size);
|
.select(self.regions.base, size);
|
||||||
|
|
||||||
let pod = Regions::one(size, base, Axes::splat(true));
|
let pod = Regions::one(size, base, Axes::splat(true));
|
||||||
let frame = node.layout_block(self.world, &pod, self.styles)?.remove(0);
|
let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0);
|
||||||
match frame.role() {
|
match frame.role() {
|
||||||
Some(Role::ListLabel | Role::ListItemBody) => {
|
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||||
output.apply_role(Role::ListItem)
|
output.apply_role(Role::ListItem)
|
||||||
@ -504,7 +500,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// Layout the row.
|
// Layout the row.
|
||||||
let mut pos = Point::zero();
|
let mut pos = Point::zero();
|
||||||
for (x, &rcol) in self.rcols.iter().enumerate() {
|
for (x, &rcol) in self.rcols.iter().enumerate() {
|
||||||
if let Some(node) = self.cell(x, y) {
|
if let Some(cell) = self.cell(x, y) {
|
||||||
pod.first.x = rcol;
|
pod.first.x = rcol;
|
||||||
pod.base.x = rcol;
|
pod.base.x = rcol;
|
||||||
|
|
||||||
@ -514,7 +510,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push the layouted frames into the individual output frames.
|
// Push the layouted frames into the individual output frames.
|
||||||
let frames = node.layout_block(self.world, &pod, self.styles)?;
|
let frames = cell.layout_block(self.world, &pod, self.styles)?;
|
||||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||||
match frame.role() {
|
match frame.role() {
|
||||||
Some(Role::ListLabel | Role::ListItemBody) => {
|
Some(Role::ListLabel | Role::ListItemBody) => {
|
||||||
@ -578,7 +574,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the node in the cell in column `x` and row `y`.
|
/// Get the content of the cell in column `x` and row `y`.
|
||||||
///
|
///
|
||||||
/// Returns `None` if it's a gutter cell.
|
/// Returns `None` if it's a gutter cell.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -23,3 +23,792 @@ pub use place::*;
|
|||||||
pub use spacing::*;
|
pub use spacing::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
pub use transform::*;
|
pub use transform::*;
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use comemo::Tracked;
|
||||||
|
use typed_arena::Arena;
|
||||||
|
|
||||||
|
use crate::diag::SourceResult;
|
||||||
|
use crate::frame::Frame;
|
||||||
|
use crate::geom::*;
|
||||||
|
use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
|
||||||
|
use crate::library::text::{
|
||||||
|
LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
|
||||||
|
};
|
||||||
|
use crate::model::{
|
||||||
|
capability, Barrier, Content, Interruption, Node, SequenceNode, Show, StyleChain,
|
||||||
|
StyleEntry, StyleMap, StyleVec, StyleVecBuilder, StyledNode, Target,
|
||||||
|
};
|
||||||
|
use crate::World;
|
||||||
|
|
||||||
|
/// The root-level layout.
|
||||||
|
#[capability]
|
||||||
|
pub trait Layout: 'static + Sync + Send {
|
||||||
|
/// Layout into one frame per page.
|
||||||
|
fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for Content {
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn layout(&self, world: Tracked<dyn World>) -> SourceResult<Vec<Frame>> {
|
||||||
|
let styles = StyleChain::with_root(&world.config().styles);
|
||||||
|
let scratch = Scratch::default();
|
||||||
|
|
||||||
|
let mut builder = Builder::new(world, &scratch, true);
|
||||||
|
builder.accept(self, styles)?;
|
||||||
|
|
||||||
|
let (doc, shared) = builder.into_doc(styles)?;
|
||||||
|
doc.layout(world, shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block-level layout.
|
||||||
|
#[capability]
|
||||||
|
pub trait LayoutBlock: 'static + Sync + Send {
|
||||||
|
/// Layout into one frame per region.
|
||||||
|
fn layout_block(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutBlock for Content {
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn layout_block(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
if let Some(node) = self.to::<dyn LayoutBlock>() {
|
||||||
|
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
||||||
|
let styles = barrier.chain(&styles);
|
||||||
|
return node.layout_block(world, regions, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scratch = Scratch::default();
|
||||||
|
let mut builder = Builder::new(world, &scratch, false);
|
||||||
|
builder.accept(self, styles)?;
|
||||||
|
let (flow, shared) = builder.into_flow(styles)?;
|
||||||
|
flow.layout_block(world, regions, shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inline-level layout.
|
||||||
|
#[capability]
|
||||||
|
pub trait LayoutInline: 'static + Sync + Send {
|
||||||
|
/// Layout into a single frame.
|
||||||
|
fn layout_inline(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutInline for Content {
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn layout_inline(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
if let Some(node) = self.to::<dyn LayoutInline>() {
|
||||||
|
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
||||||
|
let styles = barrier.chain(&styles);
|
||||||
|
return node.layout_inline(world, regions, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(node) = self.to::<dyn LayoutBlock>() {
|
||||||
|
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
||||||
|
let styles = barrier.chain(&styles);
|
||||||
|
return node.layout_block(world, regions, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scratch = Scratch::default();
|
||||||
|
let mut builder = Builder::new(world, &scratch, false);
|
||||||
|
builder.accept(self, styles)?;
|
||||||
|
let (flow, shared) = builder.into_flow(styles)?;
|
||||||
|
flow.layout_block(world, regions, shared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of regions to layout into.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct Regions {
|
||||||
|
/// The (remaining) size of the first region.
|
||||||
|
pub first: Size,
|
||||||
|
/// The base size for relative sizing.
|
||||||
|
pub base: Size,
|
||||||
|
/// The height of followup regions. The width is the same for all regions.
|
||||||
|
pub backlog: Vec<Abs>,
|
||||||
|
/// The height of the final region that is repeated once the backlog is
|
||||||
|
/// drained. The width is the same for all regions.
|
||||||
|
pub last: Option<Abs>,
|
||||||
|
/// Whether nodes should expand to fill the regions instead of shrinking to
|
||||||
|
/// fit the content.
|
||||||
|
pub expand: Axes<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Regions {
|
||||||
|
/// Create a new region sequence with exactly one region.
|
||||||
|
pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
|
||||||
|
Self {
|
||||||
|
first: size,
|
||||||
|
base,
|
||||||
|
backlog: vec![],
|
||||||
|
last: None,
|
||||||
|
expand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new sequence of same-size regions that repeats indefinitely.
|
||||||
|
pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
|
||||||
|
Self {
|
||||||
|
first: size,
|
||||||
|
base,
|
||||||
|
backlog: vec![],
|
||||||
|
last: Some(size.y),
|
||||||
|
expand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new regions where all sizes are mapped with `f`.
|
||||||
|
///
|
||||||
|
/// Note that since all regions must have the same width, the width returned
|
||||||
|
/// by `f` is ignored for the backlog and the final region.
|
||||||
|
pub fn map<F>(&self, mut f: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(Size) -> Size,
|
||||||
|
{
|
||||||
|
let x = self.first.x;
|
||||||
|
Self {
|
||||||
|
first: f(self.first),
|
||||||
|
base: f(self.base),
|
||||||
|
backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(),
|
||||||
|
last: self.last.map(|y| f(Size::new(x, y)).y),
|
||||||
|
expand: self.expand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the first region is full and a region break is called for.
|
||||||
|
pub fn is_full(&self) -> bool {
|
||||||
|
Abs::zero().fits(self.first.y) && !self.in_last()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the first region is the last usable region.
|
||||||
|
///
|
||||||
|
/// If this is true, calling `next()` will have no effect.
|
||||||
|
pub fn in_last(&self) -> bool {
|
||||||
|
self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance to the next region if there is any.
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
if let Some(height) = (!self.backlog.is_empty())
|
||||||
|
.then(|| self.backlog.remove(0))
|
||||||
|
.or(self.last)
|
||||||
|
{
|
||||||
|
self.first.y = height;
|
||||||
|
self.base.y = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator that returns the sizes of the first and all following
|
||||||
|
/// regions, equivalently to what would be produced by calling
|
||||||
|
/// [`next()`](Self::next) repeatedly until all regions are exhausted.
|
||||||
|
/// This iterater may be infinite.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
|
||||||
|
let first = std::iter::once(self.first);
|
||||||
|
let backlog = self.backlog.iter();
|
||||||
|
let last = self.last.iter().cycle();
|
||||||
|
first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a document or a flow node from content.
|
||||||
|
struct Builder<'a> {
|
||||||
|
/// The core context.
|
||||||
|
world: Tracked<'a, dyn World>,
|
||||||
|
/// 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.
|
||||||
|
styles: Arena<StyleChain<'a>>,
|
||||||
|
/// An arena where intermediate content resulting from show rules is stored.
|
||||||
|
templates: Arena<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Builder<'a> {
|
||||||
|
pub fn new(
|
||||||
|
world: Tracked<'a, dyn World>,
|
||||||
|
scratch: &'a Scratch<'a>,
|
||||||
|
top: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
world,
|
||||||
|
scratch,
|
||||||
|
doc: top.then(|| DocBuilder::default()),
|
||||||
|
flow: FlowBuilder::default(),
|
||||||
|
par: ParBuilder::default(),
|
||||||
|
list: ListBuilder::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_doc(
|
||||||
|
mut self,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> SourceResult<(DocNode, StyleChain<'a>)> {
|
||||||
|
self.interrupt(Interruption::Page, styles, true)?;
|
||||||
|
let (pages, shared) = self.doc.unwrap().pages.finish();
|
||||||
|
Ok((DocNode(pages), shared))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_flow(
|
||||||
|
mut self,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> SourceResult<(FlowNode, StyleChain<'a>)> {
|
||||||
|
self.interrupt(Interruption::Par, styles, false)?;
|
||||||
|
let (children, shared) = self.flow.0.finish();
|
||||||
|
Ok((FlowNode(children), shared))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accept(
|
||||||
|
&mut self,
|
||||||
|
content: &'a Content,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
if let Some(text) = content.downcast::<TextNode>() {
|
||||||
|
if let Some(realized) = styles.apply(self.world, Target::Text(&text.0))? {
|
||||||
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
|
return self.accept(stored, styles);
|
||||||
|
}
|
||||||
|
} else if let Some(styled) = content.downcast::<StyledNode>() {
|
||||||
|
return self.styled(styled, styles);
|
||||||
|
} else if let Some(seq) = content.downcast::<SequenceNode>() {
|
||||||
|
return self.sequence(seq, styles);
|
||||||
|
} else if content.has::<dyn Show>() {
|
||||||
|
if self.show(&content, styles)? {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.list.accept(content, styles) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interrupt(Interruption::List, styles, false)?;
|
||||||
|
|
||||||
|
if content.is::<ListItem>() {
|
||||||
|
self.list.accept(content, styles);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.par.accept(content, styles) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.interrupt(Interruption::Par, styles, false)?;
|
||||||
|
|
||||||
|
if self.flow.accept(content, styles) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let keep = content
|
||||||
|
.downcast::<PagebreakNode>()
|
||||||
|
.map_or(false, |pagebreak| !pagebreak.weak);
|
||||||
|
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,
|
||||||
|
content: &'a Content,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> SourceResult<bool> {
|
||||||
|
if let Some(mut realized) = styles.apply(self.world, Target::Node(content))? {
|
||||||
|
let mut map = StyleMap::new();
|
||||||
|
let barrier = Barrier::new(content.id());
|
||||||
|
map.push(StyleEntry::Barrier(barrier));
|
||||||
|
map.push(StyleEntry::Barrier(barrier));
|
||||||
|
realized = realized.styled_with_map(map);
|
||||||
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
|
self.accept(stored, styles)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn styled(
|
||||||
|
&mut self,
|
||||||
|
styled: &'a StyledNode,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
let stored = self.scratch.styles.alloc(styles);
|
||||||
|
let styles = styled.map.chain(stored);
|
||||||
|
let intr = styled.map.interruption();
|
||||||
|
|
||||||
|
if let Some(intr) = intr {
|
||||||
|
self.interrupt(intr, styles, false)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.accept(&styled.sub, styles)?;
|
||||||
|
|
||||||
|
if let Some(intr) = intr {
|
||||||
|
self.interrupt(intr, styles, true)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interrupt(
|
||||||
|
&mut self,
|
||||||
|
intr: Interruption,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
keep: bool,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
if intr >= Interruption::List && !self.list.is_empty() {
|
||||||
|
mem::take(&mut self.list).finish(self)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if intr >= Interruption::Par {
|
||||||
|
if !self.par.is_empty() {
|
||||||
|
mem::take(&mut self.par).finish(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sequence(
|
||||||
|
&mut self,
|
||||||
|
seq: &'a SequenceNode,
|
||||||
|
styles: StyleChain<'a>,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
for content in &seq.0 {
|
||||||
|
self.accept(content, styles)?;
|
||||||
|
}
|
||||||
|
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>) {
|
||||||
|
if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
|
||||||
|
self.keep_next = !pagebreak.weak;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(page) = content.downcast::<PageNode>() {
|
||||||
|
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 {
|
||||||
|
// Weak flow elements:
|
||||||
|
// Weakness | Element
|
||||||
|
// 0 | weak colbreak
|
||||||
|
// 1 | weak fractional spacing
|
||||||
|
// 2 | weak spacing
|
||||||
|
// 3 | generated weak spacing
|
||||||
|
// 4 | generated weak fractional spacing
|
||||||
|
// 5 | par spacing
|
||||||
|
|
||||||
|
if let Some(_) = content.downcast::<ParbreakNode>() {
|
||||||
|
/* Nothing to do */
|
||||||
|
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
|
||||||
|
if colbreak.weak {
|
||||||
|
self.0.weak(FlowChild::Colbreak, styles, 0);
|
||||||
|
} else {
|
||||||
|
self.0.destructive(FlowChild::Colbreak, styles);
|
||||||
|
}
|
||||||
|
} else if let Some(vertical) = content.downcast::<VNode>() {
|
||||||
|
let child = FlowChild::Spacing(vertical.amount);
|
||||||
|
let frac = vertical.amount.is_fractional();
|
||||||
|
if vertical.weak {
|
||||||
|
let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
|
||||||
|
self.0.weak(child, styles, weakness);
|
||||||
|
} else if frac {
|
||||||
|
self.0.destructive(child, styles);
|
||||||
|
} else {
|
||||||
|
self.0.ignorant(child, styles);
|
||||||
|
}
|
||||||
|
} else if content.has::<dyn LayoutBlock>() {
|
||||||
|
let child = FlowChild::Block(content.clone());
|
||||||
|
if content.is::<PlaceNode>() {
|
||||||
|
self.0.ignorant(child, styles);
|
||||||
|
} else {
|
||||||
|
self.0.supportive(child, styles);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
|
||||||
|
let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
|
||||||
|
styles.get(ParNode::LEADING).into()
|
||||||
|
} else {
|
||||||
|
styles.get(ParNode::SPACING).into()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.0.weak(FlowChild::Spacing(amount), styles, 5);
|
||||||
|
self.0.supportive(FlowChild::Block(par.pack()), styles);
|
||||||
|
self.0.weak(FlowChild::Spacing(amount), styles, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// Weak par elements:
|
||||||
|
// Weakness | Element
|
||||||
|
// 0 | weak fractional spacing
|
||||||
|
// 1 | weak spacing
|
||||||
|
// 2 | space
|
||||||
|
|
||||||
|
if content.is::<SpaceNode>() {
|
||||||
|
self.0.weak(ParChild::Text(' '.into()), styles, 2);
|
||||||
|
} else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
|
||||||
|
let c = if linebreak.justify { '\u{2028}' } else { '\n' };
|
||||||
|
self.0.destructive(ParChild::Text(c.into()), styles);
|
||||||
|
} else if let Some(horizontal) = content.downcast::<HNode>() {
|
||||||
|
let child = ParChild::Spacing(horizontal.amount);
|
||||||
|
let frac = horizontal.amount.is_fractional();
|
||||||
|
if horizontal.weak {
|
||||||
|
let weakness = u8::from(!frac);
|
||||||
|
self.0.weak(child, styles, weakness);
|
||||||
|
} else if frac {
|
||||||
|
self.0.destructive(child, styles);
|
||||||
|
} else {
|
||||||
|
self.0.ignorant(child, styles);
|
||||||
|
}
|
||||||
|
} else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
|
||||||
|
self.0.supportive(ParChild::Quote { double: quote.double }, styles);
|
||||||
|
} else if let Some(text) = content.downcast::<TextNode>() {
|
||||||
|
self.0.supportive(ParChild::Text(text.0.clone()), styles);
|
||||||
|
} else if content.has::<dyn LayoutInline>() {
|
||||||
|
self.0.supportive(ParChild::Inline(content.clone()), styles);
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
// text and follows directly after another paragraph.
|
||||||
|
let indent = shared.get(ParNode::INDENT);
|
||||||
|
if !indent.is_zero()
|
||||||
|
&& children
|
||||||
|
.items()
|
||||||
|
.find_map(|child| match child {
|
||||||
|
ParChild::Spacing(_) => None,
|
||||||
|
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
||||||
|
ParChild::Inline(_) => Some(false),
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
&& parent
|
||||||
|
.flow
|
||||||
|
.0
|
||||||
|
.items()
|
||||||
|
.rev()
|
||||||
|
.find_map(|child| match child {
|
||||||
|
FlowChild::Spacing(_) => None,
|
||||||
|
FlowChild::Block(content) => Some(content.is::<ParNode>()),
|
||||||
|
FlowChild::Colbreak => Some(false),
|
||||||
|
})
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
children.push_front(ParChild::Spacing(indent.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.flow.par(ParNode(children), shared, !indent.is_zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
/// Whether the list can be attached.
|
||||||
|
attachable: 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 {
|
||||||
|
if self.items.is_empty() {
|
||||||
|
if content.is::<ParbreakNode>() {
|
||||||
|
self.attachable = false;
|
||||||
|
} else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
|
||||||
|
self.attachable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(item) = content.downcast::<ListItem>() {
|
||||||
|
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.is::<ParbreakNode>());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if !self.items.is_empty()
|
||||||
|
&& (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
|
||||||
|
{
|
||||||
|
self.staged.push((content, styles));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
|
||||||
|
let (items, shared) = self.items.finish();
|
||||||
|
let kind = match items.items().next() {
|
||||||
|
Some(item) => item.kind(),
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tight = self.tight;
|
||||||
|
let attached = tight && self.attachable;
|
||||||
|
let content = match kind {
|
||||||
|
LIST => ListNode::<LIST> { tight, attached, items }.pack(),
|
||||||
|
ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
|
||||||
|
DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let stored = parent.scratch.templates.alloc(content);
|
||||||
|
parent.accept(stored, shared)?;
|
||||||
|
|
||||||
|
for (content, styles) in self.staged {
|
||||||
|
parent.accept(content, styles)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.list.attachable = true;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_empty(&self) -> bool {
|
||||||
|
self.items.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ListBuilder<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
items: StyleVecBuilder::default(),
|
||||||
|
tight: true,
|
||||||
|
attachable: true,
|
||||||
|
staged: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
|
||||||
|
struct CollapsingBuilder<'a, T> {
|
||||||
|
/// The internal builder.
|
||||||
|
builder: StyleVecBuilder<'a, T>,
|
||||||
|
/// Staged weak and ignorant items that we can't yet commit to the builder.
|
||||||
|
/// The option is `Some(_)` for weak items and `None` for ignorant items.
|
||||||
|
staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
|
||||||
|
/// What the last non-ignorant item was.
|
||||||
|
last: Last,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the last non-ignorant item was.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
enum Last {
|
||||||
|
Weak,
|
||||||
|
Destructive,
|
||||||
|
Supportive,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> CollapsingBuilder<'a, T> {
|
||||||
|
/// Create a new style-vec builder.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
builder: StyleVecBuilder::new(),
|
||||||
|
staged: vec![],
|
||||||
|
last: Last::Destructive,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
/// and to its right, with no destructive items in between. There may be
|
||||||
|
/// ignorant items in between in both directions.
|
||||||
|
///
|
||||||
|
/// Between weak items, there may be at least one per layer and among the
|
||||||
|
/// candidates the strongest one (smallest `weakness`) wins. When tied,
|
||||||
|
/// the one that compares larger through `PartialOrd` wins.
|
||||||
|
pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
if self.last == Last::Destructive {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.last == Last::Weak {
|
||||||
|
if let Some(i) =
|
||||||
|
self.staged.iter().position(|(prev_item, _, prev_weakness)| {
|
||||||
|
prev_weakness.map_or(false, |prev_weakness| {
|
||||||
|
weakness < prev_weakness
|
||||||
|
|| (weakness == prev_weakness && item > *prev_item)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
{
|
||||||
|
self.staged.remove(i);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.staged.push((item, styles, Some(weakness)));
|
||||||
|
self.last = Last::Weak;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Forces nearby weak items to collapse.
|
||||||
|
pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
|
||||||
|
self.flush(false);
|
||||||
|
self.builder.push(item, styles);
|
||||||
|
self.last = Last::Destructive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows nearby weak items to exist.
|
||||||
|
pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
|
||||||
|
self.flush(true);
|
||||||
|
self.builder.push(item, styles);
|
||||||
|
self.last = Last::Supportive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Has no influence on other items.
|
||||||
|
pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
|
||||||
|
self.staged.push((item, styles, None));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the contained items.
|
||||||
|
pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
|
||||||
|
self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the finish style vec and the common prefix chain.
|
||||||
|
pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
|
||||||
|
self.flush(false);
|
||||||
|
self.builder.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Push the staged items, filtering out weak items if `supportive` is
|
||||||
|
/// false.
|
||||||
|
fn flush(&mut self, supportive: bool) {
|
||||||
|
for (item, styles, meta) in self.staged.drain(..) {
|
||||||
|
if supportive || meta.is_none() {
|
||||||
|
self.builder.push(item, styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Default for CollapsingBuilder<'a, T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Pad a node at the sides.
|
/// Pad content at the sides.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PadNode {
|
pub struct PadNode {
|
||||||
/// The amount of padding.
|
/// The amount of padding.
|
||||||
pub padding: Sides<Rel<Length>>,
|
pub padding: Sides<Rel<Length>>,
|
||||||
/// The child node whose sides to pad.
|
/// The content whose sides to pad.
|
||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl PadNode {
|
impl PadNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let all = args.named("rest")?.or(args.find()?);
|
let all = args.named("rest")?.or(args.find()?);
|
||||||
@ -25,8 +25,8 @@ impl PadNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PadNode {
|
impl LayoutBlock for PadNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -51,10 +51,6 @@ impl Layout for PadNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shrink a size by padding relative to the size itself.
|
/// Shrink a size by padding relative to the size itself.
|
||||||
|
@ -80,7 +80,7 @@ impl PageNode {
|
|||||||
|
|
||||||
let mut child = self.0.clone();
|
let mut child = self.0.clone();
|
||||||
|
|
||||||
// Realize columns with columns node.
|
// Realize columns.
|
||||||
let columns = styles.get(Self::COLUMNS);
|
let columns = styles.get(Self::COLUMNS);
|
||||||
if columns.get() > 1 {
|
if columns.get() > 1 {
|
||||||
child = ColumnsNode { columns, child: self.0.clone() }.pack();
|
child = ColumnsNode { columns, child: self.0.clone() }.pack();
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use super::AlignNode;
|
use super::AlignNode;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Place a node at an absolute position.
|
/// Place content at an absolute position.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PlaceNode(pub Content);
|
pub struct PlaceNode(pub Content);
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
|
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(RawAlign::Start)));
|
||||||
@ -16,8 +16,8 @@ impl PlaceNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PlaceNode {
|
impl LayoutBlock for PlaceNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -42,10 +42,6 @@ impl Layout for PlaceNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
|
@ -78,7 +78,7 @@ castable! {
|
|||||||
Value::Fraction(v) => Self::Fractional(v),
|
Value::Fraction(v) => Self::Fractional(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spacing around and between block-level nodes, relative to paragraph spacing.
|
/// Spacing around and between blocks, relative to paragraph spacing.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct BlockSpacing(Rel<Length>);
|
pub struct BlockSpacing(Rel<Length>);
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ use crate::library::prelude::*;
|
|||||||
use crate::library::text::ParNode;
|
use crate::library::text::ParNode;
|
||||||
use crate::model::StyledNode;
|
use crate::model::StyledNode;
|
||||||
|
|
||||||
/// Arrange nodes and spacing along an axis.
|
/// Arrange content and spacing along an axis.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct StackNode {
|
pub struct StackNode {
|
||||||
/// The stacking direction.
|
/// The stacking direction.
|
||||||
@ -14,7 +14,7 @@ pub struct StackNode {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl StackNode {
|
impl StackNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -26,8 +26,8 @@ impl StackNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for StackNode {
|
impl LayoutBlock for StackNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -35,7 +35,7 @@ impl Layout for StackNode {
|
|||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
||||||
|
|
||||||
// Spacing to insert before the next node.
|
// Spacing to insert before the next block.
|
||||||
let mut deferred = None;
|
let mut deferred = None;
|
||||||
|
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
@ -44,12 +44,12 @@ impl Layout for StackNode {
|
|||||||
layouter.layout_spacing(*kind);
|
layouter.layout_spacing(*kind);
|
||||||
deferred = None;
|
deferred = None;
|
||||||
}
|
}
|
||||||
StackChild::Node(node) => {
|
StackChild::Block(block) => {
|
||||||
if let Some(kind) = deferred {
|
if let Some(kind) = deferred {
|
||||||
layouter.layout_spacing(kind);
|
layouter.layout_spacing(kind);
|
||||||
}
|
}
|
||||||
|
|
||||||
layouter.layout_node(world, node, styles)?;
|
layouter.layout_block(world, block, styles)?;
|
||||||
deferred = self.spacing;
|
deferred = self.spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,26 +57,22 @@ impl Layout for StackNode {
|
|||||||
|
|
||||||
Ok(layouter.finish())
|
Ok(layouter.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A child of a stack node.
|
/// A child of a stack node.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub enum StackChild {
|
pub enum StackChild {
|
||||||
/// Spacing between other nodes.
|
/// Spacing between other children.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary node.
|
/// Arbitrary block-level content.
|
||||||
Node(Content),
|
Block(Content),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for StackChild {
|
impl Debug for StackChild {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Spacing(kind) => kind.fmt(f),
|
Self::Spacing(kind) => kind.fmt(f),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Block(block) => block.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -88,7 +84,7 @@ castable! {
|
|||||||
Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())),
|
Value::Ratio(v) => Self::Spacing(Spacing::Relative(v.into())),
|
||||||
Value::Relative(v) => Self::Spacing(Spacing::Relative(v)),
|
Value::Relative(v) => Self::Spacing(Spacing::Relative(v)),
|
||||||
Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)),
|
Value::Fraction(v) => Self::Spacing(Spacing::Fractional(v)),
|
||||||
Value::Content(v) => Self::Node(v),
|
Value::Content(v) => Self::Block(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs stack layout.
|
/// Performs stack layout.
|
||||||
@ -122,7 +118,7 @@ enum StackItem {
|
|||||||
Absolute(Abs),
|
Absolute(Abs),
|
||||||
/// Fractional spacing between other items.
|
/// Fractional spacing between other items.
|
||||||
Fractional(Fr),
|
Fractional(Fr),
|
||||||
/// A frame for a layouted child node.
|
/// A frame for a layouted block.
|
||||||
Frame(Frame, Align),
|
Frame(Frame, Align),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,11 +167,11 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout an arbitrary node.
|
/// Layout an arbitrary block.
|
||||||
pub fn layout_node(
|
pub fn layout_block(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
node: &Content,
|
block: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
if self.regions.is_full() {
|
if self.regions.is_full() {
|
||||||
@ -184,12 +180,12 @@ impl<'a> StackLayouter<'a> {
|
|||||||
|
|
||||||
// Block-axis alignment of the `AlignNode` is respected
|
// Block-axis alignment of the `AlignNode` is respected
|
||||||
// by the stack node.
|
// by the stack node.
|
||||||
let align = node
|
let align = block
|
||||||
.downcast::<AlignNode>()
|
.downcast::<AlignNode>()
|
||||||
.and_then(|node| node.aligns.get(self.axis))
|
.and_then(|node| node.aligns.get(self.axis))
|
||||||
.map(|align| align.resolve(styles))
|
.map(|align| align.resolve(styles))
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
if let Some(styled) = node.downcast::<StyledNode>() {
|
if let Some(styled) = block.downcast::<StyledNode>() {
|
||||||
let map = &styled.map;
|
let map = &styled.map;
|
||||||
if map.contains(ParNode::ALIGN) {
|
if map.contains(ParNode::ALIGN) {
|
||||||
return StyleChain::with_root(map).get(ParNode::ALIGN);
|
return StyleChain::with_root(map).get(ParNode::ALIGN);
|
||||||
@ -199,7 +195,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.dir.start().into()
|
self.dir.start().into()
|
||||||
});
|
});
|
||||||
|
|
||||||
let frames = node.layout_block(world, &self.regions, styles)?;
|
let frames = block.layout_block(world, &self.regions, styles)?;
|
||||||
let len = frames.len();
|
let len = frames.len();
|
||||||
for (i, mut frame) in frames.into_iter().enumerate() {
|
for (i, mut frame) in frames.into_iter().enumerate() {
|
||||||
// Set the generic block role.
|
// Set the generic block role.
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
use crate::geom::Transform;
|
use crate::geom::Transform;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Move a node without affecting layout.
|
/// Move content without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct MoveNode {
|
pub struct MoveNode {
|
||||||
/// The offset by which to move the node.
|
/// The offset by which to move the content.
|
||||||
pub delta: Axes<Rel<Length>>,
|
pub delta: Axes<Rel<Length>>,
|
||||||
/// The node whose contents should be moved.
|
/// The content that should be moved.
|
||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl MoveNode {
|
impl MoveNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
let dx = args.named("dx")?.unwrap_or_default();
|
||||||
@ -23,8 +23,8 @@ impl MoveNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for MoveNode {
|
impl LayoutInline for MoveNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -40,28 +40,24 @@ impl Layout for MoveNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform a node without affecting layout.
|
/// Transform content without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct TransformNode<const T: TransformKind> {
|
pub struct TransformNode<const T: TransformKind> {
|
||||||
/// Transformation to apply to the contents.
|
/// Transformation to apply to the content.
|
||||||
pub transform: Transform,
|
pub transform: Transform,
|
||||||
/// The node whose contents should be transformed.
|
/// The content that should be transformed.
|
||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rotate a node without affecting layout.
|
/// Rotate content without affecting layout.
|
||||||
pub type RotateNode = TransformNode<ROTATE>;
|
pub type RotateNode = TransformNode<ROTATE>;
|
||||||
|
|
||||||
/// Scale a node without affecting layout.
|
/// Scale content without affecting layout.
|
||||||
pub type ScaleNode = TransformNode<SCALE>;
|
pub type ScaleNode = TransformNode<SCALE>;
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl<const T: TransformKind> TransformNode<T> {
|
impl<const T: TransformKind> TransformNode<T> {
|
||||||
/// The origin of the transformation.
|
/// The origin of the transformation.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -85,8 +81,8 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const T: TransformKind> Layout for TransformNode<T> {
|
impl<const T: TransformKind> LayoutInline for TransformNode<T> {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -106,10 +102,6 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kinds of transformations.
|
/// Kinds of transformations.
|
||||||
|
@ -36,7 +36,7 @@ pub enum MathNode {
|
|||||||
Row(Arc<Vec<MathNode>>, Span),
|
Row(Arc<Vec<MathNode>>, Span),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Show, Layout)]
|
#[node(Show, LayoutInline)]
|
||||||
impl MathNode {
|
impl MathNode {
|
||||||
/// The math font family.
|
/// The math font family.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -67,6 +67,15 @@ impl MathNode {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the formula is display level.
|
||||||
|
pub fn display(&self) -> bool {
|
||||||
|
if let Self::Row(row, _) = self {
|
||||||
|
matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space])
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for MathNode {
|
impl Show for MathNode {
|
||||||
@ -79,11 +88,10 @@ impl Show for MathNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(match self.level() {
|
Ok(if self.display() {
|
||||||
Level::Inline => self.clone().pack(),
|
self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
|
||||||
Level::Block => {
|
} else {
|
||||||
self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
|
self.clone().pack()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,27 +101,22 @@ impl Show for MathNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
Ok(match self.level() {
|
Ok(if self.display() {
|
||||||
Level::Inline => realized,
|
realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
|
||||||
Level::Block => {
|
} else {
|
||||||
realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
|
realized
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for MathNode {
|
impl LayoutInline for MathNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
_: &Regions,
|
_: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let style = match self.level() {
|
let style = if self.display() { Style::Display } else { Style::Text };
|
||||||
Level::Inline => Style::Text,
|
|
||||||
Level::Block => Style::Display,
|
|
||||||
};
|
|
||||||
|
|
||||||
let span = match self {
|
let span = match self {
|
||||||
&Self::Row(_, span) => span,
|
&Self::Row(_, span) => span,
|
||||||
_ => Span::detached(),
|
_ => Span::detached(),
|
||||||
@ -121,16 +124,6 @@ impl Layout for MathNode {
|
|||||||
|
|
||||||
Ok(vec![layout_tex(world, self, span, style, styles)?])
|
Ok(vec![layout_tex(world, self, span, style, styles)?])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
if let Self::Row(row, _) = self {
|
|
||||||
if matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space]) {
|
|
||||||
return Level::Block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a TeX formula into a frame.
|
/// Layout a TeX formula into a frame.
|
||||||
|
@ -11,7 +11,14 @@ pub mod structure;
|
|||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod utility;
|
pub mod utility;
|
||||||
|
|
||||||
use prelude::*;
|
mod ext;
|
||||||
|
mod raw;
|
||||||
|
|
||||||
|
pub use raw::*;
|
||||||
|
|
||||||
|
use crate::geom::{Align, Color, Dir};
|
||||||
|
use crate::model::{Node, Scope};
|
||||||
|
use crate::LangItems;
|
||||||
|
|
||||||
/// Construct a scope containing all standard library definitions.
|
/// Construct a scope containing all standard library definitions.
|
||||||
pub fn scope() -> Scope {
|
pub fn scope() -> Scope {
|
||||||
@ -156,10 +163,10 @@ pub fn items() -> LangItems {
|
|||||||
strong: |body| text::StrongNode(body).pack(),
|
strong: |body| text::StrongNode(body).pack(),
|
||||||
emph: |body| text::EmphNode(body).pack(),
|
emph: |body| text::EmphNode(body).pack(),
|
||||||
raw: |text, lang, block| {
|
raw: |text, lang, block| {
|
||||||
let node = text::RawNode { text, block }.pack();
|
let content = text::RawNode { text, block }.pack();
|
||||||
match lang {
|
match lang {
|
||||||
Some(_) => node.styled(text::RawNode::LANG, lang),
|
Some(_) => content.styled(text::RawNode::LANG, lang),
|
||||||
None => node,
|
None => content,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
link: |url| text::LinkNode::from_url(url).pack(),
|
link: |url| text::LinkNode::from_url(url).pack(),
|
||||||
@ -174,188 +181,3 @@ pub fn items() -> LangItems {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods on content.
|
|
||||||
pub trait ContentExt {
|
|
||||||
/// Make this content strong.
|
|
||||||
fn strong(self) -> Self;
|
|
||||||
|
|
||||||
/// Make this content emphasized.
|
|
||||||
fn emph(self) -> Self;
|
|
||||||
|
|
||||||
/// Underline this content.
|
|
||||||
fn underlined(self) -> Self;
|
|
||||||
|
|
||||||
/// Add weak vertical spacing above and below the content.
|
|
||||||
fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self;
|
|
||||||
|
|
||||||
/// Force a size for this node.
|
|
||||||
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self;
|
|
||||||
|
|
||||||
/// Set alignments for this node.
|
|
||||||
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
|
|
||||||
|
|
||||||
/// Pad this node at the sides.
|
|
||||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
|
|
||||||
|
|
||||||
/// Transform this node's contents without affecting layout.
|
|
||||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
|
||||||
|
|
||||||
/// Fill the frames resulting from a node.
|
|
||||||
fn filled(self, fill: Paint) -> Self;
|
|
||||||
|
|
||||||
/// Stroke the frames resulting from a node.
|
|
||||||
fn stroked(self, stroke: Stroke) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentExt for Content {
|
|
||||||
fn strong(self) -> Self {
|
|
||||||
text::StrongNode(self).pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emph(self) -> Self {
|
|
||||||
text::EmphNode(self).pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn underlined(self) -> Self {
|
|
||||||
text::DecoNode::<{ text::UNDERLINE }>(self).pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self {
|
|
||||||
if above.is_none() && below.is_none() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut seq = vec![];
|
|
||||||
if let Some(above) = above {
|
|
||||||
seq.push(
|
|
||||||
layout::VNode {
|
|
||||||
amount: above.into(),
|
|
||||||
weak: true,
|
|
||||||
generated: true,
|
|
||||||
}
|
|
||||||
.pack(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
seq.push(self);
|
|
||||||
if let Some(below) = below {
|
|
||||||
seq.push(
|
|
||||||
layout::VNode {
|
|
||||||
amount: below.into(),
|
|
||||||
weak: true,
|
|
||||||
generated: true,
|
|
||||||
}
|
|
||||||
.pack(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::sequence(seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn boxed(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
|
|
||||||
layout::BoxNode { sizing, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
|
|
||||||
layout::AlignNode { aligns, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
|
||||||
layout::PadNode { padding, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
|
||||||
layout::MoveNode { delta, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filled(self, fill: Paint) -> Self {
|
|
||||||
FillNode { fill, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stroked(self, stroke: Stroke) -> Self {
|
|
||||||
StrokeNode { stroke, child: self }.pack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods for the style chain.
|
|
||||||
pub trait StyleMapExt {
|
|
||||||
/// Set a font family composed of a preferred family and existing families
|
|
||||||
/// from a style chain.
|
|
||||||
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StyleMapExt for StyleMap {
|
|
||||||
fn set_family(&mut self, preferred: text::FontFamily, existing: StyleChain) {
|
|
||||||
self.set(
|
|
||||||
text::TextNode::FAMILY,
|
|
||||||
std::iter::once(preferred)
|
|
||||||
.chain(existing.get(text::TextNode::FAMILY).iter().cloned())
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill the frames resulting from a node.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
struct FillNode {
|
|
||||||
/// How to fill the frames resulting from the `child`.
|
|
||||||
fill: Paint,
|
|
||||||
/// The node whose frames should be filled.
|
|
||||||
child: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node(Layout)]
|
|
||||||
impl FillNode {}
|
|
||||||
|
|
||||||
impl Layout for FillNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
let mut frames = self.child.layout_block(world, regions, styles)?;
|
|
||||||
for frame in &mut frames {
|
|
||||||
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
|
||||||
}
|
|
||||||
Ok(frames)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stroke the frames resulting from a node.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
struct StrokeNode {
|
|
||||||
/// How to stroke the frames resulting from the `child`.
|
|
||||||
stroke: Stroke,
|
|
||||||
/// The node whose frames should be stroked.
|
|
||||||
child: Content,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node(Layout)]
|
|
||||||
impl StrokeNode {}
|
|
||||||
|
|
||||||
impl Layout for StrokeNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
let mut frames = self.child.layout_block(world, regions, styles)?;
|
|
||||||
for frame in &mut frames {
|
|
||||||
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
|
||||||
}
|
|
||||||
Ok(frames)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -7,19 +7,20 @@ pub use std::num::NonZeroUsize;
|
|||||||
pub use std::sync::Arc;
|
pub use std::sync::Arc;
|
||||||
|
|
||||||
pub use comemo::Tracked;
|
pub use comemo::Tracked;
|
||||||
pub use typst_macros::node;
|
|
||||||
|
|
||||||
pub use super::{ContentExt, StyleMapExt};
|
pub use super::ext::{ContentExt, StyleMapExt};
|
||||||
|
pub use super::layout::{Layout, LayoutBlock, LayoutInline, Regions};
|
||||||
|
pub use super::text::TextNode;
|
||||||
|
pub use super::{RawAlign, RawStroke};
|
||||||
pub use crate::diag::{
|
pub use crate::diag::{
|
||||||
with_alternative, At, FileError, FileResult, SourceError, SourceResult, StrResult,
|
with_alternative, At, FileError, FileResult, SourceError, SourceResult, StrResult,
|
||||||
};
|
};
|
||||||
pub use crate::frame::*;
|
pub use crate::frame::*;
|
||||||
pub use crate::geom::*;
|
pub use crate::geom::*;
|
||||||
pub use crate::library::text::TextNode;
|
|
||||||
pub use crate::model::{
|
pub use crate::model::{
|
||||||
Arg, Args, Array, Cast, Content, Dict, Dynamic, Fold, Func, Key, Layout, Level, Node,
|
capability, node, Arg, Args, Array, Capability, Cast, Content, Dict, Dynamic, Fold,
|
||||||
RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, Smart, Str, StyleChain,
|
Func, Key, Node, Resolve, Scope, Selector, Show, Smart, Str, StyleChain, StyleMap,
|
||||||
StyleMap, StyleVec, Value, Vm,
|
StyleVec, Value, Vm,
|
||||||
};
|
};
|
||||||
pub use crate::syntax::{Span, Spanned};
|
pub use crate::syntax::{Span, Spanned};
|
||||||
pub use crate::util::EcoString;
|
pub use crate::util::EcoString;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use super::{Fold, Resolve, Smart, StyleChain, Value};
|
|
||||||
use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
|
use crate::geom::{Abs, Align, Axes, Axis, Get, Length, Paint, Stroke};
|
||||||
use crate::library::text::TextNode;
|
use crate::library::text::TextNode;
|
||||||
|
use crate::model::{Fold, Resolve, Smart, StyleChain, Value};
|
||||||
|
|
||||||
/// The unresolved alignment representation.
|
/// The unresolved alignment representation.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
@ -8,7 +8,7 @@ pub struct TableNode {
|
|||||||
pub tracks: Axes<Vec<TrackSizing>>,
|
pub tracks: Axes<Vec<TrackSizing>>,
|
||||||
/// Defines sizing of gutter rows and columns between content.
|
/// Defines sizing of gutter rows and columns between content.
|
||||||
pub gutter: Axes<Vec<TrackSizing>>,
|
pub gutter: Axes<Vec<TrackSizing>>,
|
||||||
/// The nodes to be arranged in the table.
|
/// The content to be arranged in the table.
|
||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ pub enum ParChild {
|
|||||||
Quote { double: bool },
|
Quote { double: bool },
|
||||||
/// Horizontal spacing between other children.
|
/// Horizontal spacing between other children.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level node.
|
/// Arbitrary inline-level content.
|
||||||
Node(Content),
|
Inline(Content),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutBlock)]
|
||||||
impl ParNode {
|
impl ParNode {
|
||||||
/// The spacing between lines.
|
/// The spacing between lines.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -61,8 +61,8 @@ impl ParNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ParNode {
|
impl LayoutBlock for ParNode {
|
||||||
fn layout(
|
fn layout_block(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -82,10 +82,6 @@ impl Layout for ParNode {
|
|||||||
// Stack the lines into one frame per region.
|
// Stack the lines into one frame per region.
|
||||||
stack(&p, world, &lines, regions)
|
stack(&p, world, &lines, regions)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Block
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for ParNode {
|
impl Debug for ParNode {
|
||||||
@ -101,7 +97,7 @@ impl Debug for ParChild {
|
|||||||
Self::Text(text) => write!(f, "Text({:?})", text),
|
Self::Text(text) => write!(f, "Text({:?})", text),
|
||||||
Self::Quote { double } => write!(f, "Quote({double})"),
|
Self::Quote { double } => write!(f, "Quote({double})"),
|
||||||
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
Self::Spacing(kind) => write!(f, "{:?}", kind),
|
||||||
Self::Node(node) => node.fmt(f),
|
Self::Inline(inline) => inline.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -180,19 +176,19 @@ impl ParbreakNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A node that should be repeated to fill up a line.
|
/// Repeats content to fill a line.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct RepeatNode(pub Content);
|
pub struct RepeatNode(pub Content);
|
||||||
|
|
||||||
#[node(Layout)]
|
#[node(LayoutInline)]
|
||||||
impl RepeatNode {
|
impl RepeatNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self(args.expect("body")?).pack())
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for RepeatNode {
|
impl LayoutInline for RepeatNode {
|
||||||
fn layout(
|
fn layout_inline(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
@ -200,16 +196,12 @@ impl Layout for RepeatNode {
|
|||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
self.0.layout_inline(world, regions, styles)
|
self.0.layout_inline(world, regions, styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn level(&self) -> Level {
|
|
||||||
Level::Inline
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Range of a substring of text.
|
/// Range of a substring of text.
|
||||||
type Range = std::ops::Range<usize>;
|
type Range = std::ops::Range<usize>;
|
||||||
|
|
||||||
// The characters by which spacing, nodes and pins are replaced in the
|
// The characters by which spacing, inline content and pins are replaced in the
|
||||||
// paragraph's full text.
|
// paragraph's full text.
|
||||||
const SPACING_REPLACE: char = ' '; // Space
|
const SPACING_REPLACE: char = ' '; // Space
|
||||||
const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
||||||
@ -291,8 +283,8 @@ enum Segment<'a> {
|
|||||||
Text(usize),
|
Text(usize),
|
||||||
/// Horizontal spacing between other segments.
|
/// Horizontal spacing between other segments.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level layout node.
|
/// Arbitrary inline-level content.
|
||||||
Node(&'a Content),
|
Inline(&'a Content),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Segment<'_> {
|
impl Segment<'_> {
|
||||||
@ -301,7 +293,7 @@ impl Segment<'_> {
|
|||||||
match *self {
|
match *self {
|
||||||
Self::Text(len) => len,
|
Self::Text(len) => len,
|
||||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Node(_) => NODE_REPLACE.len_utf8(),
|
Self::Inline(_) => NODE_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -315,9 +307,9 @@ enum Item<'a> {
|
|||||||
Absolute(Abs),
|
Absolute(Abs),
|
||||||
/// Fractional spacing between other items.
|
/// Fractional spacing between other items.
|
||||||
Fractional(Fr),
|
Fractional(Fr),
|
||||||
/// A layouted child node.
|
/// Layouted inline-level content.
|
||||||
Frame(Frame),
|
Frame(Frame),
|
||||||
/// A repeating node that fills the remaining space.
|
/// A repeating node that fills the remaining space in a line.
|
||||||
Repeat(&'a RepeatNode, StyleChain<'a>),
|
Repeat(&'a RepeatNode, StyleChain<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -475,7 +467,7 @@ fn collect<'a>(
|
|||||||
ParChild::Text(text) => text.chars().next(),
|
ParChild::Text(text) => text.chars().next(),
|
||||||
ParChild::Quote { .. } => Some('"'),
|
ParChild::Quote { .. } => Some('"'),
|
||||||
ParChild::Spacing(_) => Some(SPACING_REPLACE),
|
ParChild::Spacing(_) => Some(SPACING_REPLACE),
|
||||||
ParChild::Node(_) => Some(NODE_REPLACE),
|
ParChild::Inline(_) => Some(NODE_REPLACE),
|
||||||
});
|
});
|
||||||
|
|
||||||
full.push_str(quoter.quote("es, double, peeked));
|
full.push_str(quoter.quote("es, double, peeked));
|
||||||
@ -488,9 +480,9 @@ fn collect<'a>(
|
|||||||
full.push(SPACING_REPLACE);
|
full.push(SPACING_REPLACE);
|
||||||
Segment::Spacing(spacing)
|
Segment::Spacing(spacing)
|
||||||
}
|
}
|
||||||
ParChild::Node(node) => {
|
ParChild::Inline(inline) => {
|
||||||
full.push(NODE_REPLACE);
|
full.push(NODE_REPLACE);
|
||||||
Segment::Node(node)
|
Segment::Inline(inline)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -514,7 +506,7 @@ fn collect<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||||
/// contained inline-level nodes.
|
/// contained inline-level content.
|
||||||
fn prepare<'a>(
|
fn prepare<'a>(
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
par: &'a ParNode,
|
par: &'a ParNode,
|
||||||
@ -548,13 +540,13 @@ fn prepare<'a>(
|
|||||||
items.push(Item::Fractional(v));
|
items.push(Item::Fractional(v));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Segment::Node(node) => {
|
Segment::Inline(inline) => {
|
||||||
if let Some(repeat) = node.downcast::<RepeatNode>() {
|
if let Some(repeat) = inline.downcast::<RepeatNode>() {
|
||||||
items.push(Item::Repeat(repeat, styles));
|
items.push(Item::Repeat(repeat, styles));
|
||||||
} else {
|
} else {
|
||||||
let size = Size::new(regions.first.x, regions.base.y);
|
let size = Size::new(regions.first.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Axes::splat(false));
|
let pod = Regions::one(size, regions.base, Axes::splat(false));
|
||||||
let mut frame = node.layout_inline(world, &pod, styles)?.remove(0);
|
let mut frame = inline.layout_inline(world, &pod, styles)?.remove(0);
|
||||||
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
||||||
frame.apply_role(Role::GenericInline);
|
frame.apply_role(Role::GenericInline);
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
@ -1169,12 +1161,12 @@ fn commit(
|
|||||||
Item::Frame(frame) => {
|
Item::Frame(frame) => {
|
||||||
push(&mut offset, frame.clone());
|
push(&mut offset, frame.clone());
|
||||||
}
|
}
|
||||||
Item::Repeat(node, styles) => {
|
Item::Repeat(repeat, styles) => {
|
||||||
let before = offset;
|
let before = offset;
|
||||||
let fill = Fr::one().share(fr, remaining);
|
let fill = Fr::one().share(fr, remaining);
|
||||||
let size = Size::new(fill, regions.base.y);
|
let size = Size::new(fill, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Axes::new(false, false));
|
let pod = Regions::one(size, regions.base, Axes::new(false, false));
|
||||||
let frame = node.layout(world, &pod, *styles)?.remove(0);
|
let frame = repeat.layout_inline(world, &pod, *styles)?.remove(0);
|
||||||
let width = frame.width();
|
let width = frame.width();
|
||||||
let count = (fill / width).floor();
|
let count = (fill / width).floor();
|
||||||
let remaining = fill % width;
|
let remaining = fill % width;
|
||||||
|
@ -1,186 +0,0 @@
|
|||||||
use super::{Scope, Scopes, Value};
|
|
||||||
use crate::syntax::ast::TypedNode;
|
|
||||||
use crate::syntax::{ast, SyntaxNode};
|
|
||||||
|
|
||||||
/// A visitor that captures variable slots.
|
|
||||||
pub struct CapturesVisitor<'a> {
|
|
||||||
external: &'a Scopes<'a>,
|
|
||||||
internal: Scopes<'a>,
|
|
||||||
captures: Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CapturesVisitor<'a> {
|
|
||||||
/// Create a new visitor for the given external scopes.
|
|
||||||
pub fn new(external: &'a Scopes) -> Self {
|
|
||||||
Self {
|
|
||||||
external,
|
|
||||||
internal: Scopes::new(None),
|
|
||||||
captures: Scope::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the scope of captured variables.
|
|
||||||
pub fn finish(self) -> Scope {
|
|
||||||
self.captures
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Bind a new internal variable.
|
|
||||||
pub fn bind(&mut self, ident: ast::Ident) {
|
|
||||||
self.internal.top.define(ident.take(), Value::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Capture a variable if it isn't internal.
|
|
||||||
pub fn capture(&mut self, ident: ast::Ident) {
|
|
||||||
if self.internal.get(&ident).is_err() {
|
|
||||||
if let Ok(value) = self.external.get(&ident) {
|
|
||||||
self.captures.define_captured(ident.take(), value.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Visit any node and collect all captured variables.
|
|
||||||
pub fn visit(&mut self, node: &SyntaxNode) {
|
|
||||||
match node.cast() {
|
|
||||||
// Every identifier is a potential variable that we need to capture.
|
|
||||||
// Identifiers that shouldn't count as captures because they
|
|
||||||
// actually bind a new name are handled below (individually through
|
|
||||||
// the expressions that contain them).
|
|
||||||
Some(ast::Expr::Ident(ident)) => self.capture(ident),
|
|
||||||
|
|
||||||
// Code and content blocks create a scope.
|
|
||||||
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
|
||||||
self.internal.enter();
|
|
||||||
for child in node.children() {
|
|
||||||
self.visit(child);
|
|
||||||
}
|
|
||||||
self.internal.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// A closure contains parameter bindings, which are bound before the
|
|
||||||
// body is evaluated. Care must be taken so that the default values
|
|
||||||
// of named parameters cannot access previous parameter bindings.
|
|
||||||
Some(ast::Expr::Closure(expr)) => {
|
|
||||||
for param in expr.params() {
|
|
||||||
if let ast::Param::Named(named) = param {
|
|
||||||
self.visit(named.expr().as_untyped());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for param in expr.params() {
|
|
||||||
match param {
|
|
||||||
ast::Param::Pos(ident) => self.bind(ident),
|
|
||||||
ast::Param::Named(named) => self.bind(named.name()),
|
|
||||||
ast::Param::Sink(ident) => self.bind(ident),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.visit(expr.body().as_untyped());
|
|
||||||
}
|
|
||||||
|
|
||||||
// A let expression contains a binding, but that binding is only
|
|
||||||
// active after the body is evaluated.
|
|
||||||
Some(ast::Expr::Let(expr)) => {
|
|
||||||
if let Some(init) = expr.init() {
|
|
||||||
self.visit(init.as_untyped());
|
|
||||||
}
|
|
||||||
self.bind(expr.binding());
|
|
||||||
}
|
|
||||||
|
|
||||||
// A show rule contains a binding, but that binding is only active
|
|
||||||
// after the target has been evaluated.
|
|
||||||
Some(ast::Expr::Show(show)) => {
|
|
||||||
self.visit(show.pattern().as_untyped());
|
|
||||||
if let Some(binding) = show.binding() {
|
|
||||||
self.bind(binding);
|
|
||||||
}
|
|
||||||
self.visit(show.body().as_untyped());
|
|
||||||
}
|
|
||||||
|
|
||||||
// A for loop contains one or two bindings in its pattern. These are
|
|
||||||
// active after the iterable is evaluated but before the body is
|
|
||||||
// evaluated.
|
|
||||||
Some(ast::Expr::For(expr)) => {
|
|
||||||
self.visit(expr.iter().as_untyped());
|
|
||||||
let pattern = expr.pattern();
|
|
||||||
if let Some(key) = pattern.key() {
|
|
||||||
self.bind(key);
|
|
||||||
}
|
|
||||||
self.bind(pattern.value());
|
|
||||||
self.visit(expr.body().as_untyped());
|
|
||||||
}
|
|
||||||
|
|
||||||
// An import contains items, but these are active only after the
|
|
||||||
// path is evaluated.
|
|
||||||
Some(ast::Expr::Import(expr)) => {
|
|
||||||
self.visit(expr.path().as_untyped());
|
|
||||||
if let ast::Imports::Items(items) = expr.imports() {
|
|
||||||
for item in items {
|
|
||||||
self.bind(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything else is traversed from left to right.
|
|
||||||
_ => {
|
|
||||||
for child in node.children() {
|
|
||||||
self.visit(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::syntax::parse;
|
|
||||||
|
|
||||||
#[track_caller]
|
|
||||||
fn test(text: &str, result: &[&str]) {
|
|
||||||
let mut scopes = Scopes::new(None);
|
|
||||||
scopes.top.define("x", 0);
|
|
||||||
scopes.top.define("y", 0);
|
|
||||||
scopes.top.define("z", 0);
|
|
||||||
|
|
||||||
let mut visitor = CapturesVisitor::new(&scopes);
|
|
||||||
let root = parse(text);
|
|
||||||
visitor.visit(&root);
|
|
||||||
|
|
||||||
let captures = visitor.finish();
|
|
||||||
let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
|
|
||||||
names.sort();
|
|
||||||
|
|
||||||
assert_eq!(names, result);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_captures() {
|
|
||||||
// Let binding and function definition.
|
|
||||||
test("#let x = x", &["x"]);
|
|
||||||
test("#let x; {x + y}", &["y"]);
|
|
||||||
test("#let f(x, y) = x + y", &[]);
|
|
||||||
|
|
||||||
// Closure with different kinds of params.
|
|
||||||
test("{(x, y) => x + z}", &["z"]);
|
|
||||||
test("{(x: y, z) => x + z}", &["y"]);
|
|
||||||
test("{(..x) => x + y}", &["y"]);
|
|
||||||
test("{(x, y: x + z) => x + y}", &["x", "z"]);
|
|
||||||
|
|
||||||
// Show rule.
|
|
||||||
test("#show x: y as x", &["y"]);
|
|
||||||
test("#show x: y as x + z", &["y", "z"]);
|
|
||||||
test("#show x: x as x", &["x"]);
|
|
||||||
|
|
||||||
// For loop.
|
|
||||||
test("#for x in y { x + z }", &["y", "z"]);
|
|
||||||
test("#for x, y in y { x + y }", &["y"]);
|
|
||||||
|
|
||||||
// Import.
|
|
||||||
test("#import x, y from z", &["z"]);
|
|
||||||
test("#import x, y, z from x + y", &["x", "y"]);
|
|
||||||
|
|
||||||
// Blocks.
|
|
||||||
test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
|
||||||
test("[#let x = 1]#x", &["x"]);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
use super::{StyleChain, StyleVec, StyleVecBuilder};
|
|
||||||
|
|
||||||
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
|
|
||||||
pub(super) struct CollapsingBuilder<'a, T> {
|
|
||||||
/// The internal builder.
|
|
||||||
builder: StyleVecBuilder<'a, T>,
|
|
||||||
/// Staged weak and ignorant items that we can't yet commit to the builder.
|
|
||||||
/// The option is `Some(_)` for weak items and `None` for ignorant items.
|
|
||||||
staged: Vec<(T, StyleChain<'a>, Option<u8>)>,
|
|
||||||
/// What the last non-ignorant item was.
|
|
||||||
last: Last,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What the last non-ignorant item was.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
enum Last {
|
|
||||||
Weak,
|
|
||||||
Destructive,
|
|
||||||
Supportive,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> CollapsingBuilder<'a, T> {
|
|
||||||
/// Create a new style-vec builder.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
builder: StyleVecBuilder::new(),
|
|
||||||
staged: vec![],
|
|
||||||
last: Last::Destructive,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
/// and to its right, with no destructive items in between. There may be
|
|
||||||
/// ignorant items in between in both directions.
|
|
||||||
///
|
|
||||||
/// Between weak items, there may be at least one per layer and among the
|
|
||||||
/// candidates the strongest one (smallest `weakness`) wins. When tied,
|
|
||||||
/// the one that compares larger through `PartialOrd` wins.
|
|
||||||
pub fn weak(&mut self, item: T, styles: StyleChain<'a>, weakness: u8)
|
|
||||||
where
|
|
||||||
T: PartialOrd,
|
|
||||||
{
|
|
||||||
if self.last == Last::Destructive {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.last == Last::Weak {
|
|
||||||
if let Some(i) =
|
|
||||||
self.staged.iter().position(|(prev_item, _, prev_weakness)| {
|
|
||||||
prev_weakness.map_or(false, |prev_weakness| {
|
|
||||||
weakness < prev_weakness
|
|
||||||
|| (weakness == prev_weakness && item > *prev_item)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
{
|
|
||||||
self.staged.remove(i);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.staged.push((item, styles, Some(weakness)));
|
|
||||||
self.last = Last::Weak;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Forces nearby weak items to collapse.
|
|
||||||
pub fn destructive(&mut self, item: T, styles: StyleChain<'a>) {
|
|
||||||
self.flush(false);
|
|
||||||
self.builder.push(item, styles);
|
|
||||||
self.last = Last::Destructive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allows nearby weak items to exist.
|
|
||||||
pub fn supportive(&mut self, item: T, styles: StyleChain<'a>) {
|
|
||||||
self.flush(true);
|
|
||||||
self.builder.push(item, styles);
|
|
||||||
self.last = Last::Supportive;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Has no influence on other items.
|
|
||||||
pub fn ignorant(&mut self, item: T, styles: StyleChain<'a>) {
|
|
||||||
self.staged.push((item, styles, None));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over the contained items.
|
|
||||||
pub fn items(&self) -> impl DoubleEndedIterator<Item = &T> {
|
|
||||||
self.builder.items().chain(self.staged.iter().map(|(item, ..)| item))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the finish style vec and the common prefix chain.
|
|
||||||
pub fn finish(mut self) -> (StyleVec<T>, StyleChain<'a>) {
|
|
||||||
self.flush(false);
|
|
||||||
self.builder.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push the staged items, filtering out weak items if `supportive` is
|
|
||||||
/// false.
|
|
||||||
fn flush(&mut self, supportive: bool) {
|
|
||||||
for (item, styles, meta) in self.staged.drain(..) {
|
|
||||||
if supportive || meta.is_none() {
|
|
||||||
self.builder.push(item, styles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Default for CollapsingBuilder<'a, T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,18 +5,12 @@ use std::iter::{self, Sum};
|
|||||||
use std::ops::{Add, AddAssign};
|
use std::ops::{Add, AddAssign};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use comemo::Tracked;
|
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
use typst_macros::node;
|
use typst_macros::node;
|
||||||
|
|
||||||
use super::{
|
use super::{Args, Key, Property, Selector, StyleEntry, StyleMap, Vm};
|
||||||
Args, Barrier, Builder, Key, Layout, Level, Property, Regions, Scratch, Selector,
|
|
||||||
StyleChain, StyleEntry, StyleMap, Vm,
|
|
||||||
};
|
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::frame::Frame;
|
|
||||||
use crate::util::ReadableTypeId;
|
use crate::util::ReadableTypeId;
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
/// Composable representation of styled content.
|
/// Composable representation of styled content.
|
||||||
///
|
///
|
||||||
@ -40,22 +34,27 @@ impl Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the content is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
|
self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The id of the contained node.
|
||||||
pub fn id(&self) -> NodeId {
|
pub fn id(&self) -> NodeId {
|
||||||
(*self.0).id()
|
(*self.0).id()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the contained node is of type `T`.
|
||||||
pub fn is<T: 'static>(&self) -> bool {
|
pub fn is<T: 'static>(&self) -> bool {
|
||||||
(*self.0).as_any().is::<T>()
|
(*self.0).as_any().is::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cast to `T` if the contained node is of type `T`.
|
||||||
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||||
(*self.0).as_any().downcast_ref::<T>()
|
(*self.0).as_any().downcast_ref::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Try to cast to a mutable instance of `T`.
|
||||||
fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||||
Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
|
Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
|
||||||
}
|
}
|
||||||
@ -120,51 +119,6 @@ impl Content {
|
|||||||
pub fn unguard(&self, sel: Selector) -> Self {
|
pub fn unguard(&self, sel: Selector) -> Self {
|
||||||
self.clone().styled_with_entry(StyleEntry::Unguard(sel))
|
self.clone().styled_with_entry(StyleEntry::Unguard(sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[comemo::memoize]
|
|
||||||
pub fn layout_block(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
|
||||||
let styles = barrier.chain(&styles);
|
|
||||||
|
|
||||||
if let Some(node) = self.to::<dyn Layout>() {
|
|
||||||
if node.level() == Level::Block {
|
|
||||||
return node.layout(world, regions, styles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let scratch = Scratch::default();
|
|
||||||
let mut builder = Builder::new(world, &scratch, false);
|
|
||||||
builder.accept(self, styles)?;
|
|
||||||
let (flow, shared) = builder.into_flow(styles)?;
|
|
||||||
flow.layout(world, regions, shared)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[comemo::memoize]
|
|
||||||
pub fn layout_inline(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
let barrier = StyleEntry::Barrier(Barrier::new(self.id()));
|
|
||||||
let styles = barrier.chain(&styles);
|
|
||||||
|
|
||||||
if let Some(node) = self.to::<dyn Layout>() {
|
|
||||||
return node.layout(world, regions, styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
let scratch = Scratch::default();
|
|
||||||
let mut builder = Builder::new(world, &scratch, false);
|
|
||||||
builder.accept(self, styles)?;
|
|
||||||
let (flow, shared) = builder.into_flow(styles)?;
|
|
||||||
flow.layout(world, regions, shared)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Content {
|
impl Default for Content {
|
||||||
@ -293,6 +247,11 @@ pub trait Node: 'static {
|
|||||||
fn vtable(&self, id: TypeId) -> Option<*const ()>;
|
fn vtable(&self, id: TypeId) -> Option<*const ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A capability a node can have.
|
||||||
|
///
|
||||||
|
/// This is implemented by trait objects.
|
||||||
|
pub trait Capability: 'static + Send + Sync {}
|
||||||
|
|
||||||
/// A unique identifier for a node.
|
/// A unique identifier for a node.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct NodeId(ReadableTypeId);
|
pub struct NodeId(ReadableTypeId);
|
||||||
@ -310,15 +269,12 @@ impl Debug for NodeId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A capability a node can have.
|
|
||||||
///
|
|
||||||
/// This is implemented by trait objects.
|
|
||||||
pub trait Capability: 'static + Send + Sync {}
|
|
||||||
|
|
||||||
/// A node with applied styles.
|
/// A node with applied styles.
|
||||||
#[derive(Clone, Hash)]
|
#[derive(Clone, Hash)]
|
||||||
pub struct StyledNode {
|
pub struct StyledNode {
|
||||||
|
/// The styled content.
|
||||||
pub sub: Content,
|
pub sub: Content,
|
||||||
|
/// The styles.
|
||||||
pub map: StyleMap,
|
pub map: StyleMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
use super::Smart;
|
|
||||||
use crate::geom::{Abs, Corners, Length, Rel, Sides};
|
|
||||||
|
|
||||||
/// A property that is folded to determine its final value.
|
|
||||||
pub trait Fold {
|
|
||||||
/// The type of the folded output.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// Fold this inner value with an outer folded value.
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Option<T>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
T::Output: Default,
|
|
||||||
{
|
|
||||||
type Output = Option<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Smart<T>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
T::Output: Default,
|
|
||||||
{
|
|
||||||
type Output = Smart<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Sides<T>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
{
|
|
||||||
type Output = Sides<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer, |inner, outer| inner.fold(outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for Sides<Option<Rel<Abs>>> {
|
|
||||||
type Output = Sides<Rel<Abs>>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for Sides<Option<Smart<Rel<Length>>>> {
|
|
||||||
type Output = Sides<Smart<Rel<Length>>>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Fold for Corners<T>
|
|
||||||
where
|
|
||||||
T: Fold,
|
|
||||||
{
|
|
||||||
type Output = Corners<T::Output>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer, |inner, outer| inner.fold(outer))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Fold for Corners<Option<Rel<Abs>>> {
|
|
||||||
type Output = Corners<Rel<Abs>>;
|
|
||||||
|
|
||||||
fn fold(self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,8 +6,8 @@ use comemo::{Track, Tracked};
|
|||||||
|
|
||||||
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
|
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::syntax::ast::Expr;
|
use crate::syntax::ast::{self, Expr, TypedNode};
|
||||||
use crate::syntax::SourceId;
|
use crate::syntax::{SourceId, SyntaxNode};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -227,3 +227,186 @@ impl Closure {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A visitor that determines which variables to capture for a closure.
|
||||||
|
pub struct CapturesVisitor<'a> {
|
||||||
|
external: &'a Scopes<'a>,
|
||||||
|
internal: Scopes<'a>,
|
||||||
|
captures: Scope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CapturesVisitor<'a> {
|
||||||
|
/// Create a new visitor for the given external scopes.
|
||||||
|
pub fn new(external: &'a Scopes) -> Self {
|
||||||
|
Self {
|
||||||
|
external,
|
||||||
|
internal: Scopes::new(None),
|
||||||
|
captures: Scope::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the scope of captured variables.
|
||||||
|
pub fn finish(self) -> Scope {
|
||||||
|
self.captures
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind a new internal variable.
|
||||||
|
pub fn bind(&mut self, ident: ast::Ident) {
|
||||||
|
self.internal.top.define(ident.take(), Value::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Capture a variable if it isn't internal.
|
||||||
|
pub fn capture(&mut self, ident: ast::Ident) {
|
||||||
|
if self.internal.get(&ident).is_err() {
|
||||||
|
if let Ok(value) = self.external.get(&ident) {
|
||||||
|
self.captures.define_captured(ident.take(), value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit any node and collect all captured variables.
|
||||||
|
pub fn visit(&mut self, node: &SyntaxNode) {
|
||||||
|
match node.cast() {
|
||||||
|
// Every identifier is a potential variable that we need to capture.
|
||||||
|
// Identifiers that shouldn't count as captures because they
|
||||||
|
// actually bind a new name are handled below (individually through
|
||||||
|
// the expressions that contain them).
|
||||||
|
Some(ast::Expr::Ident(ident)) => self.capture(ident),
|
||||||
|
|
||||||
|
// Code and content blocks create a scope.
|
||||||
|
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
||||||
|
self.internal.enter();
|
||||||
|
for child in node.children() {
|
||||||
|
self.visit(child);
|
||||||
|
}
|
||||||
|
self.internal.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A closure contains parameter bindings, which are bound before the
|
||||||
|
// body is evaluated. Care must be taken so that the default values
|
||||||
|
// of named parameters cannot access previous parameter bindings.
|
||||||
|
Some(ast::Expr::Closure(expr)) => {
|
||||||
|
for param in expr.params() {
|
||||||
|
if let ast::Param::Named(named) = param {
|
||||||
|
self.visit(named.expr().as_untyped());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for param in expr.params() {
|
||||||
|
match param {
|
||||||
|
ast::Param::Pos(ident) => self.bind(ident),
|
||||||
|
ast::Param::Named(named) => self.bind(named.name()),
|
||||||
|
ast::Param::Sink(ident) => self.bind(ident),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.visit(expr.body().as_untyped());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A let expression contains a binding, but that binding is only
|
||||||
|
// active after the body is evaluated.
|
||||||
|
Some(ast::Expr::Let(expr)) => {
|
||||||
|
if let Some(init) = expr.init() {
|
||||||
|
self.visit(init.as_untyped());
|
||||||
|
}
|
||||||
|
self.bind(expr.binding());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A show rule contains a binding, but that binding is only active
|
||||||
|
// after the target has been evaluated.
|
||||||
|
Some(ast::Expr::Show(show)) => {
|
||||||
|
self.visit(show.pattern().as_untyped());
|
||||||
|
if let Some(binding) = show.binding() {
|
||||||
|
self.bind(binding);
|
||||||
|
}
|
||||||
|
self.visit(show.body().as_untyped());
|
||||||
|
}
|
||||||
|
|
||||||
|
// A for loop contains one or two bindings in its pattern. These are
|
||||||
|
// active after the iterable is evaluated but before the body is
|
||||||
|
// evaluated.
|
||||||
|
Some(ast::Expr::For(expr)) => {
|
||||||
|
self.visit(expr.iter().as_untyped());
|
||||||
|
let pattern = expr.pattern();
|
||||||
|
if let Some(key) = pattern.key() {
|
||||||
|
self.bind(key);
|
||||||
|
}
|
||||||
|
self.bind(pattern.value());
|
||||||
|
self.visit(expr.body().as_untyped());
|
||||||
|
}
|
||||||
|
|
||||||
|
// An import contains items, but these are active only after the
|
||||||
|
// path is evaluated.
|
||||||
|
Some(ast::Expr::Import(expr)) => {
|
||||||
|
self.visit(expr.path().as_untyped());
|
||||||
|
if let ast::Imports::Items(items) = expr.imports() {
|
||||||
|
for item in items {
|
||||||
|
self.bind(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else is traversed from left to right.
|
||||||
|
_ => {
|
||||||
|
for child in node.children() {
|
||||||
|
self.visit(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::syntax::parse;
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn test(text: &str, result: &[&str]) {
|
||||||
|
let mut scopes = Scopes::new(None);
|
||||||
|
scopes.top.define("x", 0);
|
||||||
|
scopes.top.define("y", 0);
|
||||||
|
scopes.top.define("z", 0);
|
||||||
|
|
||||||
|
let mut visitor = CapturesVisitor::new(&scopes);
|
||||||
|
let root = parse(text);
|
||||||
|
visitor.visit(&root);
|
||||||
|
|
||||||
|
let captures = visitor.finish();
|
||||||
|
let mut names: Vec<_> = captures.iter().map(|(k, _)| k).collect();
|
||||||
|
names.sort();
|
||||||
|
|
||||||
|
assert_eq!(names, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_captures() {
|
||||||
|
// Let binding and function definition.
|
||||||
|
test("#let x = x", &["x"]);
|
||||||
|
test("#let x; {x + y}", &["y"]);
|
||||||
|
test("#let f(x, y) = x + y", &[]);
|
||||||
|
|
||||||
|
// Closure with different kinds of params.
|
||||||
|
test("{(x, y) => x + z}", &["z"]);
|
||||||
|
test("{(x: y, z) => x + z}", &["y"]);
|
||||||
|
test("{(..x) => x + y}", &["y"]);
|
||||||
|
test("{(x, y: x + z) => x + y}", &["x", "z"]);
|
||||||
|
|
||||||
|
// Show rule.
|
||||||
|
test("#show x: y as x", &["y"]);
|
||||||
|
test("#show x: y as x + z", &["y", "z"]);
|
||||||
|
test("#show x: x as x", &["x"]);
|
||||||
|
|
||||||
|
// For loop.
|
||||||
|
test("#for x in y { x + z }", &["y", "z"]);
|
||||||
|
test("#for x, y in y { x + y }", &["y"]);
|
||||||
|
|
||||||
|
// Import.
|
||||||
|
test("#import x, y from z", &["z"]);
|
||||||
|
test("#import x, y, z from x + y", &["x", "y"]);
|
||||||
|
|
||||||
|
// Blocks.
|
||||||
|
test("{ let x = 1; { let y = 2; y }; x + y }", &["y"]);
|
||||||
|
test("[#let x = 1]#x", &["x"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
//! Layouting infrastructure.
|
|
||||||
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use comemo::Tracked;
|
|
||||||
|
|
||||||
use super::{Builder, Capability, Content, Scratch, StyleChain};
|
|
||||||
use crate::diag::SourceResult;
|
|
||||||
use crate::frame::Frame;
|
|
||||||
use crate::geom::{Abs, Axes, Size};
|
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
/// Layout content into a collection of pages.
|
|
||||||
#[comemo::memoize]
|
|
||||||
pub fn layout(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec<Frame>> {
|
|
||||||
let styles = StyleChain::with_root(&world.config().styles);
|
|
||||||
let scratch = Scratch::default();
|
|
||||||
|
|
||||||
let mut builder = Builder::new(world, &scratch, true);
|
|
||||||
builder.accept(content, styles)?;
|
|
||||||
|
|
||||||
let (doc, shared) = builder.into_doc(styles)?;
|
|
||||||
doc.layout(world, shared)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that can be layouted into a sequence of regions.
|
|
||||||
///
|
|
||||||
/// Layouting returns one frame per used region.
|
|
||||||
pub trait Layout: 'static + Sync + Send {
|
|
||||||
/// Layout this node into the given regions, producing frames.
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>>;
|
|
||||||
|
|
||||||
/// Whether this is an inline-level or block-level node.
|
|
||||||
fn level(&self) -> Level;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Capability for dyn Layout {}
|
|
||||||
|
|
||||||
/// At which level a node operates.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Level {
|
|
||||||
Inline,
|
|
||||||
Block,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sequence of regions to layout into.
|
|
||||||
#[derive(Debug, Clone, Hash)]
|
|
||||||
pub struct Regions {
|
|
||||||
/// The (remaining) size of the first region.
|
|
||||||
pub first: Size,
|
|
||||||
/// The base size for relative sizing.
|
|
||||||
pub base: Size,
|
|
||||||
/// The height of followup regions. The width is the same for all regions.
|
|
||||||
pub backlog: Vec<Abs>,
|
|
||||||
/// The height of the final region that is repeated once the backlog is
|
|
||||||
/// drained. The width is the same for all regions.
|
|
||||||
pub last: Option<Abs>,
|
|
||||||
/// Whether nodes should expand to fill the regions instead of shrinking to
|
|
||||||
/// fit the content.
|
|
||||||
pub expand: Axes<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Regions {
|
|
||||||
/// Create a new region sequence with exactly one region.
|
|
||||||
pub fn one(size: Size, base: Size, expand: Axes<bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
first: size,
|
|
||||||
base,
|
|
||||||
backlog: vec![],
|
|
||||||
last: None,
|
|
||||||
expand,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new sequence of same-size regions that repeats indefinitely.
|
|
||||||
pub fn repeat(size: Size, base: Size, expand: Axes<bool>) -> Self {
|
|
||||||
Self {
|
|
||||||
first: size,
|
|
||||||
base,
|
|
||||||
backlog: vec![],
|
|
||||||
last: Some(size.y),
|
|
||||||
expand,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create new regions where all sizes are mapped with `f`.
|
|
||||||
///
|
|
||||||
/// Note that since all regions must have the same width, the width returned
|
|
||||||
/// by `f` is ignored for the backlog and the final region.
|
|
||||||
pub fn map<F>(&self, mut f: F) -> Self
|
|
||||||
where
|
|
||||||
F: FnMut(Size) -> Size,
|
|
||||||
{
|
|
||||||
let x = self.first.x;
|
|
||||||
Self {
|
|
||||||
first: f(self.first),
|
|
||||||
base: f(self.base),
|
|
||||||
backlog: self.backlog.iter().map(|&y| f(Size::new(x, y)).y).collect(),
|
|
||||||
last: self.last.map(|y| f(Size::new(x, y)).y),
|
|
||||||
expand: self.expand,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the first region is full and a region break is called for.
|
|
||||||
pub fn is_full(&self) -> bool {
|
|
||||||
Abs::zero().fits(self.first.y) && !self.in_last()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the first region is the last usable region.
|
|
||||||
///
|
|
||||||
/// If this is true, calling `next()` will have no effect.
|
|
||||||
pub fn in_last(&self) -> bool {
|
|
||||||
self.backlog.len() == 0 && self.last.map_or(true, |height| self.first.y == height)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance to the next region if there is any.
|
|
||||||
pub fn next(&mut self) {
|
|
||||||
if let Some(height) = (!self.backlog.is_empty())
|
|
||||||
.then(|| self.backlog.remove(0))
|
|
||||||
.or(self.last)
|
|
||||||
{
|
|
||||||
self.first.y = height;
|
|
||||||
self.base.y = height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator that returns the sizes of the first and all following
|
|
||||||
/// regions, equivalently to what would be produced by calling
|
|
||||||
/// [`next()`](Self::next) repeatedly until all regions are exhausted.
|
|
||||||
/// This iterater may be infinite.
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = Size> + '_ {
|
|
||||||
let first = std::iter::once(self.first);
|
|
||||||
let backlog = self.backlog.iter();
|
|
||||||
let last = self.last.iter().cycle();
|
|
||||||
first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,5 @@
|
|||||||
//! Layout and computation model.
|
//! Layout and computation model.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
mod styles;
|
|
||||||
mod collapse;
|
|
||||||
mod content;
|
|
||||||
mod eval;
|
|
||||||
mod layout;
|
|
||||||
mod property;
|
|
||||||
mod recipe;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod cast;
|
mod cast;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -18,38 +10,29 @@ mod dict;
|
|||||||
mod str;
|
mod str;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod value;
|
mod value;
|
||||||
|
#[macro_use]
|
||||||
|
mod styles;
|
||||||
mod args;
|
mod args;
|
||||||
mod capture;
|
mod content;
|
||||||
mod fold;
|
mod eval;
|
||||||
mod func;
|
mod func;
|
||||||
pub mod methods;
|
|
||||||
pub mod ops;
|
|
||||||
mod raw;
|
|
||||||
mod realize;
|
|
||||||
mod resolve;
|
|
||||||
mod scope;
|
mod scope;
|
||||||
mod vm;
|
mod vm;
|
||||||
|
|
||||||
|
pub mod methods;
|
||||||
|
pub mod ops;
|
||||||
|
|
||||||
pub use self::str::*;
|
pub use self::str::*;
|
||||||
pub use args::*;
|
pub use args::*;
|
||||||
pub use array::*;
|
pub use array::*;
|
||||||
pub use capture::*;
|
|
||||||
pub use cast::*;
|
pub use cast::*;
|
||||||
pub use content::*;
|
pub use content::*;
|
||||||
pub use dict::*;
|
pub use dict::*;
|
||||||
pub use eval::*;
|
pub use eval::*;
|
||||||
pub use fold::*;
|
|
||||||
pub use func::*;
|
pub use func::*;
|
||||||
pub use layout::*;
|
|
||||||
pub use property::*;
|
|
||||||
pub use raw::*;
|
|
||||||
pub use recipe::*;
|
|
||||||
pub use resolve::*;
|
|
||||||
pub use scope::*;
|
pub use scope::*;
|
||||||
pub use styles::*;
|
pub use styles::*;
|
||||||
pub use typst_macros::node;
|
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
pub use vm::*;
|
pub use vm::*;
|
||||||
|
|
||||||
// use collapse::*;
|
pub use typst_macros::{capability, node};
|
||||||
use realize::*;
|
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::{Node, RawAlign, RawStroke, Regex, Smart, Value};
|
use super::{Node, Regex, Smart, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Axes, Axis, Length, Numeric, Rel};
|
use crate::geom::{Axes, Axis, Length, Numeric, Rel};
|
||||||
use crate::library::text::TextNode;
|
use crate::library::text::TextNode;
|
||||||
|
use crate::library::{RawAlign, RawStroke};
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
|
||||||
/// Bail with a type mismatch error.
|
/// Bail with a type mismatch error.
|
||||||
|
@ -1,195 +0,0 @@
|
|||||||
use std::any::Any;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use comemo::Prehashed;
|
|
||||||
|
|
||||||
use super::{Interruption, NodeId, StyleChain};
|
|
||||||
use crate::library::layout::PageNode;
|
|
||||||
use crate::library::structure::{DescNode, EnumNode, ListNode};
|
|
||||||
use crate::library::text::ParNode;
|
|
||||||
use crate::util::ReadableTypeId;
|
|
||||||
|
|
||||||
/// A style property originating from a set rule or constructor.
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct Property {
|
|
||||||
/// The id of the property's [key](Key).
|
|
||||||
key: KeyId,
|
|
||||||
/// The id of the node the property belongs to.
|
|
||||||
node: NodeId,
|
|
||||||
/// Whether the property should only affect the first node down the
|
|
||||||
/// hierarchy. Used by constructors.
|
|
||||||
scoped: bool,
|
|
||||||
/// The property's value.
|
|
||||||
value: Arc<Prehashed<dyn Bounds>>,
|
|
||||||
/// The name of the property.
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Property {
|
|
||||||
/// Create a new property from a key-value pair.
|
|
||||||
pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self {
|
|
||||||
Self {
|
|
||||||
key: KeyId::of::<K>(),
|
|
||||||
node: K::node(),
|
|
||||||
value: Arc::new(Prehashed::new(value)),
|
|
||||||
scoped: false,
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
name: K::NAME,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this property has the given key.
|
|
||||||
pub fn is<'a, K: Key<'a>>(&self) -> bool {
|
|
||||||
self.key == KeyId::of::<K>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this property belongs to the node `T`.
|
|
||||||
pub fn is_of<T: 'static>(&self) -> bool {
|
|
||||||
self.node == NodeId::of::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access the property's value if it is of the given key.
|
|
||||||
pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> {
|
|
||||||
if self.key == KeyId::of::<K>() {
|
|
||||||
(**self.value).as_any().downcast_ref()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The node this property is for.
|
|
||||||
pub fn node(&self) -> NodeId {
|
|
||||||
self.node
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the property is scoped.
|
|
||||||
pub fn scoped(&self) -> bool {
|
|
||||||
self.scoped
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Make the property scoped.
|
|
||||||
pub fn make_scoped(&mut self) {
|
|
||||||
self.scoped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What kind of structure the property interrupts.
|
|
||||||
pub fn interruption(&self) -> Option<Interruption> {
|
|
||||||
if self.is_of::<PageNode>() {
|
|
||||||
Some(Interruption::Page)
|
|
||||||
} else if self.is_of::<ParNode>() {
|
|
||||||
Some(Interruption::Par)
|
|
||||||
} else if self.is_of::<ListNode>()
|
|
||||||
|| self.is_of::<EnumNode>()
|
|
||||||
|| self.is_of::<DescNode>()
|
|
||||||
{
|
|
||||||
Some(Interruption::List)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Property {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
write!(f, "{} = ", self.name)?;
|
|
||||||
write!(f, "{:?}", self.value)?;
|
|
||||||
if self.scoped {
|
|
||||||
write!(f, " [scoped]")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Property {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.key == other.key
|
|
||||||
&& self.value.eq(&other.value)
|
|
||||||
&& self.scoped == other.scoped
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Bounds: Debug + Sync + Send + 'static {
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds for T
|
|
||||||
where
|
|
||||||
T: Debug + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A style property key.
|
|
||||||
///
|
|
||||||
/// This trait is not intended to be implemented manually, but rather through
|
|
||||||
/// the `#[node]` proc-macro.
|
|
||||||
pub trait Key<'a>: Copy + 'static {
|
|
||||||
/// The unfolded type which this property is stored as in a style map.
|
|
||||||
type Value: Debug + Clone + Hash + Sync + Send + 'static;
|
|
||||||
|
|
||||||
/// The folded type of value that is returned when reading this property
|
|
||||||
/// from a style chain.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// The name of the property, used for debug printing.
|
|
||||||
const NAME: &'static str;
|
|
||||||
|
|
||||||
/// The id of the node the key belongs to.
|
|
||||||
fn node() -> NodeId;
|
|
||||||
|
|
||||||
/// Compute an output value from a sequence of values belonging to this key,
|
|
||||||
/// folding if necessary.
|
|
||||||
fn get(
|
|
||||||
chain: StyleChain<'a>,
|
|
||||||
values: impl Iterator<Item = &'a Self::Value>,
|
|
||||||
) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A unique identifier for a property key.
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
struct KeyId(ReadableTypeId);
|
|
||||||
|
|
||||||
impl KeyId {
|
|
||||||
/// The id of the given key.
|
|
||||||
pub fn of<'a, T: Key<'a>>() -> Self {
|
|
||||||
Self(ReadableTypeId::of::<T>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for KeyId {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scoped property barrier.
|
|
||||||
///
|
|
||||||
/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
|
|
||||||
/// style can still be read through a single barrier (the one of the node it
|
|
||||||
/// _should_ apply to), but a second barrier will make it invisible.
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct Barrier(NodeId);
|
|
||||||
|
|
||||||
impl Barrier {
|
|
||||||
/// Create a new barrier for the given node.
|
|
||||||
pub fn new(node: NodeId) -> Self {
|
|
||||||
Self(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this barrier is for the node `T`.
|
|
||||||
pub fn is_for(&self, node: NodeId) -> bool {
|
|
||||||
self.0 == node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Barrier {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(f, "Barrier for {:?}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,486 +0,0 @@
|
|||||||
use std::mem;
|
|
||||||
|
|
||||||
use comemo::Tracked;
|
|
||||||
use typed_arena::Arena;
|
|
||||||
|
|
||||||
use super::collapse::CollapsingBuilder;
|
|
||||||
use super::{
|
|
||||||
Barrier, Content, Interruption, Layout, Level, Node, SequenceNode, Show, StyleChain,
|
|
||||||
StyleEntry, StyleMap, StyleVecBuilder, StyledNode, Target,
|
|
||||||
};
|
|
||||||
use crate::diag::SourceResult;
|
|
||||||
use crate::geom::Numeric;
|
|
||||||
use crate::library::layout::{
|
|
||||||
ColbreakNode, FlowChild, FlowNode, HNode, PageNode, PagebreakNode, PlaceNode, VNode,
|
|
||||||
};
|
|
||||||
use crate::library::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
|
|
||||||
use crate::library::text::{
|
|
||||||
LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
|
|
||||||
};
|
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
/// Builds a document or a flow node from content.
|
|
||||||
pub(super) struct Builder<'a> {
|
|
||||||
/// The core context.
|
|
||||||
world: Tracked<'a, dyn World>,
|
|
||||||
/// 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)]
|
|
||||||
pub(super) struct Scratch<'a> {
|
|
||||||
/// An arena where intermediate style chains are stored.
|
|
||||||
styles: Arena<StyleChain<'a>>,
|
|
||||||
/// An arena where intermediate content resulting from show rules is stored.
|
|
||||||
templates: Arena<Content>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Builder<'a> {
|
|
||||||
pub fn new(
|
|
||||||
world: Tracked<'a, dyn World>,
|
|
||||||
scratch: &'a Scratch<'a>,
|
|
||||||
top: bool,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
world,
|
|
||||||
scratch,
|
|
||||||
doc: top.then(|| DocBuilder::default()),
|
|
||||||
flow: FlowBuilder::default(),
|
|
||||||
par: ParBuilder::default(),
|
|
||||||
list: ListBuilder::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_doc(
|
|
||||||
mut self,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> SourceResult<(DocNode, StyleChain<'a>)> {
|
|
||||||
self.interrupt(Interruption::Page, styles, true)?;
|
|
||||||
let (pages, shared) = self.doc.unwrap().pages.finish();
|
|
||||||
Ok((DocNode(pages), shared))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_flow(
|
|
||||||
mut self,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> SourceResult<(FlowNode, StyleChain<'a>)> {
|
|
||||||
self.interrupt(Interruption::Par, styles, false)?;
|
|
||||||
let (children, shared) = self.flow.0.finish();
|
|
||||||
Ok((FlowNode(children), shared))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn accept(
|
|
||||||
&mut self,
|
|
||||||
content: &'a Content,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
if let Some(node) = content.downcast::<TextNode>() {
|
|
||||||
if let Some(realized) = styles.apply(self.world, Target::Text(&node.0))? {
|
|
||||||
let stored = self.scratch.templates.alloc(realized);
|
|
||||||
return self.accept(stored, styles);
|
|
||||||
}
|
|
||||||
} else if let Some(styled) = content.downcast::<StyledNode>() {
|
|
||||||
return self.styled(styled, styles);
|
|
||||||
} else if let Some(seq) = content.downcast::<SequenceNode>() {
|
|
||||||
return self.sequence(seq, styles);
|
|
||||||
} else if content.has::<dyn Show>() {
|
|
||||||
if self.show(&content, styles)? {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.list.accept(content, styles) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.interrupt(Interruption::List, styles, false)?;
|
|
||||||
|
|
||||||
if content.is::<ListItem>() {
|
|
||||||
self.list.accept(content, styles);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.par.accept(content, styles) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.interrupt(Interruption::Par, styles, false)?;
|
|
||||||
|
|
||||||
if self.flow.accept(content, styles) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let keep = content.downcast::<PagebreakNode>().map_or(false, |node| !node.weak);
|
|
||||||
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: &'a Content, styles: StyleChain<'a>) -> SourceResult<bool> {
|
|
||||||
if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
|
|
||||||
let mut map = StyleMap::new();
|
|
||||||
let barrier = Barrier::new(node.id());
|
|
||||||
map.push(StyleEntry::Barrier(barrier));
|
|
||||||
map.push(StyleEntry::Barrier(barrier));
|
|
||||||
realized = realized.styled_with_map(map);
|
|
||||||
let stored = self.scratch.templates.alloc(realized);
|
|
||||||
self.accept(stored, styles)?;
|
|
||||||
Ok(true)
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn styled(
|
|
||||||
&mut self,
|
|
||||||
styled: &'a StyledNode,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
let stored = self.scratch.styles.alloc(styles);
|
|
||||||
let styles = styled.map.chain(stored);
|
|
||||||
let intr = styled.map.interruption();
|
|
||||||
|
|
||||||
if let Some(intr) = intr {
|
|
||||||
self.interrupt(intr, styles, false)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.accept(&styled.sub, styles)?;
|
|
||||||
|
|
||||||
if let Some(intr) = intr {
|
|
||||||
self.interrupt(intr, styles, true)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interrupt(
|
|
||||||
&mut self,
|
|
||||||
intr: Interruption,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
keep: bool,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
if intr >= Interruption::List && !self.list.is_empty() {
|
|
||||||
mem::take(&mut self.list).finish(self)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if intr >= Interruption::Par {
|
|
||||||
if !self.par.is_empty() {
|
|
||||||
mem::take(&mut self.par).finish(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sequence(
|
|
||||||
&mut self,
|
|
||||||
seq: &'a SequenceNode,
|
|
||||||
styles: StyleChain<'a>,
|
|
||||||
) -> SourceResult<()> {
|
|
||||||
for content in &seq.0 {
|
|
||||||
self.accept(content, styles)?;
|
|
||||||
}
|
|
||||||
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>) {
|
|
||||||
if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
|
|
||||||
self.keep_next = !pagebreak.weak;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(page) = content.downcast::<PageNode>() {
|
|
||||||
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 {
|
|
||||||
// Weak flow elements:
|
|
||||||
// Weakness | Element
|
|
||||||
// 0 | weak colbreak
|
|
||||||
// 1 | weak fractional spacing
|
|
||||||
// 2 | weak spacing
|
|
||||||
// 3 | generated weak spacing
|
|
||||||
// 4 | generated weak fractional spacing
|
|
||||||
// 5 | par spacing
|
|
||||||
|
|
||||||
if let Some(_) = content.downcast::<ParbreakNode>() {
|
|
||||||
/* Nothing to do */
|
|
||||||
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
|
|
||||||
if colbreak.weak {
|
|
||||||
self.0.weak(FlowChild::Colbreak, styles, 0);
|
|
||||||
} else {
|
|
||||||
self.0.destructive(FlowChild::Colbreak, styles);
|
|
||||||
}
|
|
||||||
} else if let Some(vertical) = content.downcast::<VNode>() {
|
|
||||||
let child = FlowChild::Spacing(vertical.amount);
|
|
||||||
let frac = vertical.amount.is_fractional();
|
|
||||||
if vertical.weak {
|
|
||||||
let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
|
|
||||||
self.0.weak(child, styles, weakness);
|
|
||||||
} else if frac {
|
|
||||||
self.0.destructive(child, styles);
|
|
||||||
} else {
|
|
||||||
self.0.ignorant(child, styles);
|
|
||||||
}
|
|
||||||
} else if content.has::<dyn Layout>() {
|
|
||||||
let child = FlowChild::Node(content.clone());
|
|
||||||
if content.is::<PlaceNode>() {
|
|
||||||
self.0.ignorant(child, styles);
|
|
||||||
} else {
|
|
||||||
self.0.supportive(child, styles);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn par(&mut self, par: ParNode, styles: StyleChain<'a>, indent: bool) {
|
|
||||||
let amount = if indent && !styles.get(ParNode::SPACING_AND_INDENT) {
|
|
||||||
styles.get(ParNode::LEADING).into()
|
|
||||||
} else {
|
|
||||||
styles.get(ParNode::SPACING).into()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.0.weak(FlowChild::Spacing(amount), styles, 5);
|
|
||||||
self.0.supportive(FlowChild::Node(par.pack()), styles);
|
|
||||||
self.0.weak(FlowChild::Spacing(amount), styles, 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// Weak par elements:
|
|
||||||
// Weakness | Element
|
|
||||||
// 0 | weak fractional spacing
|
|
||||||
// 1 | weak spacing
|
|
||||||
// 2 | space
|
|
||||||
|
|
||||||
if content.is::<SpaceNode>() {
|
|
||||||
self.0.weak(ParChild::Text(' '.into()), styles, 2);
|
|
||||||
} else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
|
|
||||||
let c = if linebreak.justify { '\u{2028}' } else { '\n' };
|
|
||||||
self.0.destructive(ParChild::Text(c.into()), styles);
|
|
||||||
} else if let Some(horizontal) = content.downcast::<HNode>() {
|
|
||||||
let child = ParChild::Spacing(horizontal.amount);
|
|
||||||
let frac = horizontal.amount.is_fractional();
|
|
||||||
if horizontal.weak {
|
|
||||||
let weakness = u8::from(!frac);
|
|
||||||
self.0.weak(child, styles, weakness);
|
|
||||||
} else if frac {
|
|
||||||
self.0.destructive(child, styles);
|
|
||||||
} else {
|
|
||||||
self.0.ignorant(child, styles);
|
|
||||||
}
|
|
||||||
} else if let Some(quote) = content.downcast::<SmartQuoteNode>() {
|
|
||||||
self.0.supportive(ParChild::Quote { double: quote.double }, styles);
|
|
||||||
} else if let Some(node) = content.downcast::<TextNode>() {
|
|
||||||
self.0.supportive(ParChild::Text(node.0.clone()), styles);
|
|
||||||
} else if let Some(node) = content.to::<dyn Layout>() {
|
|
||||||
if node.level() == Level::Inline {
|
|
||||||
self.0.supportive(ParChild::Node(content.clone()), styles);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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
|
|
||||||
// text and follows directly after another paragraph.
|
|
||||||
let indent = shared.get(ParNode::INDENT);
|
|
||||||
if !indent.is_zero()
|
|
||||||
&& children
|
|
||||||
.items()
|
|
||||||
.find_map(|child| match child {
|
|
||||||
ParChild::Spacing(_) => None,
|
|
||||||
ParChild::Text(_) | ParChild::Quote { .. } => Some(true),
|
|
||||||
ParChild::Node(_) => Some(false),
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
&& parent
|
|
||||||
.flow
|
|
||||||
.0
|
|
||||||
.items()
|
|
||||||
.rev()
|
|
||||||
.find_map(|child| match child {
|
|
||||||
FlowChild::Spacing(_) => None,
|
|
||||||
FlowChild::Node(node) => Some(node.is::<ParNode>()),
|
|
||||||
FlowChild::Colbreak => Some(false),
|
|
||||||
})
|
|
||||||
.unwrap_or_default()
|
|
||||||
{
|
|
||||||
children.push_front(ParChild::Spacing(indent.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.flow.par(ParNode(children), shared, !indent.is_zero());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.0.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
/// Whether the list can be attached.
|
|
||||||
attachable: 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 {
|
|
||||||
if self.items.is_empty() {
|
|
||||||
if content.is::<ParbreakNode>() {
|
|
||||||
self.attachable = false;
|
|
||||||
} else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
|
|
||||||
self.attachable = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(item) = content.downcast::<ListItem>() {
|
|
||||||
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.is::<ParbreakNode>());
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if !self.items.is_empty()
|
|
||||||
&& (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
|
|
||||||
{
|
|
||||||
self.staged.push((content, styles));
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish(self, parent: &mut Builder<'a>) -> SourceResult<()> {
|
|
||||||
let (items, shared) = self.items.finish();
|
|
||||||
let kind = match items.items().next() {
|
|
||||||
Some(item) => item.kind(),
|
|
||||||
None => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let tight = self.tight;
|
|
||||||
let attached = tight && self.attachable;
|
|
||||||
let content = match kind {
|
|
||||||
LIST => ListNode::<LIST> { tight, attached, items }.pack(),
|
|
||||||
ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
|
|
||||||
DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let stored = parent.scratch.templates.alloc(content);
|
|
||||||
parent.accept(stored, shared)?;
|
|
||||||
|
|
||||||
for (content, styles) in self.staged {
|
|
||||||
parent.accept(content, styles)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.list.attachable = true;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
|
||||||
self.items.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ListBuilder<'_> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
items: StyleVecBuilder::default(),
|
|
||||||
tight: true,
|
|
||||||
attachable: true,
|
|
||||||
staged: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
use std::hash::Hash;
|
|
||||||
|
|
||||||
use comemo::Tracked;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
Args, Capability, Content, Func, Interruption, Node, NodeId, Regex, StyleChain,
|
|
||||||
StyleEntry, Value,
|
|
||||||
};
|
|
||||||
use crate::diag::SourceResult;
|
|
||||||
use crate::library::structure::{DescNode, EnumNode, ListNode};
|
|
||||||
use crate::library::text::TextNode;
|
|
||||||
use crate::syntax::Spanned;
|
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
/// A show rule recipe.
|
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
|
||||||
pub struct Recipe {
|
|
||||||
/// The patterns to customize.
|
|
||||||
pub pattern: Pattern,
|
|
||||||
/// The function that defines the recipe.
|
|
||||||
pub func: Spanned<Func>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Recipe {
|
|
||||||
/// Whether the recipe is applicable to the target.
|
|
||||||
pub fn applicable(&self, target: Target) -> bool {
|
|
||||||
match (&self.pattern, target) {
|
|
||||||
(Pattern::Node(id), Target::Node(node)) => *id == node.id(),
|
|
||||||
(Pattern::Regex(_), Target::Text(_)) => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to apply the recipe to the target.
|
|
||||||
pub fn apply(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
sel: Selector,
|
|
||||||
target: Target,
|
|
||||||
) -> SourceResult<Option<Content>> {
|
|
||||||
let content = match (target, &self.pattern) {
|
|
||||||
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
|
|
||||||
let node = node.to::<dyn Show>().unwrap().unguard_parts(sel);
|
|
||||||
self.call(world, || Value::Content(node))?
|
|
||||||
}
|
|
||||||
|
|
||||||
(Target::Text(text), Pattern::Regex(regex)) => {
|
|
||||||
let mut result = vec![];
|
|
||||||
let mut cursor = 0;
|
|
||||||
|
|
||||||
for mat in regex.find_iter(text) {
|
|
||||||
let start = mat.start();
|
|
||||||
if cursor < start {
|
|
||||||
result.push(TextNode(text[cursor .. start].into()).pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
|
|
||||||
cursor = mat.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursor < text.len() {
|
|
||||||
result.push(TextNode(text[cursor ..].into()).pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
Content::sequence(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call the recipe function, with the argument if desired.
|
|
||||||
fn call<F>(&self, world: Tracked<dyn World>, arg: F) -> SourceResult<Content>
|
|
||||||
where
|
|
||||||
F: FnOnce() -> Value,
|
|
||||||
{
|
|
||||||
let args = if self.func.v.argc() == Some(0) {
|
|
||||||
Args::new(self.func.span, [])
|
|
||||||
} else {
|
|
||||||
Args::new(self.func.span, [arg()])
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(self.func.v.call_detached(world, args)?.display(world))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What kind of structure the property interrupts.
|
|
||||||
pub fn interruption(&self) -> Option<Interruption> {
|
|
||||||
if let Pattern::Node(id) = self.pattern {
|
|
||||||
if id == NodeId::of::<ListNode>()
|
|
||||||
|| id == NodeId::of::<EnumNode>()
|
|
||||||
|| id == NodeId::of::<DescNode>()
|
|
||||||
{
|
|
||||||
return Some(Interruption::List);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Recipe {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"Recipe matching {:?} from {:?}",
|
|
||||||
self.pattern, self.func.span
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A show rule pattern that may match a target.
|
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
|
||||||
pub enum Pattern {
|
|
||||||
/// Defines the appearence of some node.
|
|
||||||
Node(NodeId),
|
|
||||||
/// Defines text to be replaced.
|
|
||||||
Regex(Regex),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pattern {
|
|
||||||
/// Define a simple text replacement pattern.
|
|
||||||
pub fn text(text: &str) -> Self {
|
|
||||||
Self::Regex(Regex::new(®ex::escape(text)).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A target for a show rule recipe.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub enum Target<'a> {
|
|
||||||
/// A showable node.
|
|
||||||
Node(&'a Content),
|
|
||||||
/// A slice of text.
|
|
||||||
Text(&'a str),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Identifies a show rule recipe.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
|
||||||
pub enum Selector {
|
|
||||||
/// The nth recipe from the top of the chain.
|
|
||||||
Nth(usize),
|
|
||||||
/// The base recipe for a kind of node.
|
|
||||||
Base(NodeId),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node that can be realized given some styles.
|
|
||||||
pub trait Show: 'static + Sync + Send {
|
|
||||||
/// Unguard nested content against recursive show rules.
|
|
||||||
fn unguard_parts(&self, sel: Selector) -> Content;
|
|
||||||
|
|
||||||
/// Access a field on this node.
|
|
||||||
fn field(&self, name: &str) -> Option<Value>;
|
|
||||||
|
|
||||||
/// The base recipe for this node that is executed if there is no
|
|
||||||
/// user-defined show rule.
|
|
||||||
fn realize(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content>;
|
|
||||||
|
|
||||||
/// Finalize this node given the realization of a base or user recipe. Use
|
|
||||||
/// this for effects that should work even in the face of a user-defined
|
|
||||||
/// show rule, for example:
|
|
||||||
/// - Application of general settable properties
|
|
||||||
///
|
|
||||||
/// Defaults to just the realized content.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn finalize(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
styles: StyleChain,
|
|
||||||
realized: Content,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Capability for dyn Show {}
|
|
@ -1,84 +0,0 @@
|
|||||||
use super::{Smart, StyleChain};
|
|
||||||
use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
|
|
||||||
use crate::library::text::TextNode;
|
|
||||||
|
|
||||||
/// A property that is resolved with other properties from the style chain.
|
|
||||||
pub trait Resolve {
|
|
||||||
/// The type of the resolved output.
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
/// Resolve the value using the style chain.
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolve for Em {
|
|
||||||
type Output = Abs;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
if self.is_zero() {
|
|
||||||
Abs::zero()
|
|
||||||
} else {
|
|
||||||
self.at(styles.get(TextNode::SIZE))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resolve for Length {
|
|
||||||
type Output = Abs;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.abs + self.em.resolve(styles)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Option<T> {
|
|
||||||
type Output = Option<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Smart<T> {
|
|
||||||
type Output = Smart<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Axes<T> {
|
|
||||||
type Output = Axes<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Sides<T> {
|
|
||||||
type Output = Sides<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Resolve> Resolve for Corners<T> {
|
|
||||||
type Output = Corners<T::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|v| v.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Resolve for Rel<T>
|
|
||||||
where
|
|
||||||
T: Resolve + Numeric,
|
|
||||||
<T as Resolve>::Output: Numeric,
|
|
||||||
{
|
|
||||||
type Output = Rel<<T as Resolve>::Output>;
|
|
||||||
|
|
||||||
fn resolve(self, styles: StyleChain) -> Self::Output {
|
|
||||||
self.map(|abs| abs.resolve(styles))
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,8 +5,9 @@ use std::ops::{Add, AddAssign, Deref};
|
|||||||
|
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{Array, Dict, RawAlign, Value};
|
use super::{Array, Dict, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
|
use crate::library::RawAlign;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Create a new [`Str`] from a format string.
|
/// Create a new [`Str`] from a format string.
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
|
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::iter;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::{Prehashed, Tracked};
|
||||||
|
|
||||||
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
use super::{capability, Args, Content, Func, Node, NodeId, Regex, Smart, Value};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
|
use crate::geom::{Abs, Axes, Corners, Em, Length, Numeric, Rel, Sides};
|
||||||
|
use crate::library::layout::PageNode;
|
||||||
|
use crate::library::structure::{DescNode, EnumNode, ListNode};
|
||||||
|
use crate::library::text::{ParNode, TextNode};
|
||||||
|
use crate::syntax::Spanned;
|
||||||
|
use crate::util::ReadableTypeId;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
@ -618,3 +626,516 @@ impl<'a, T> Default for StyleVecBuilder<'a, T> {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A style property originating from a set rule or constructor.
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
|
pub struct Property {
|
||||||
|
/// The id of the property's [key](Key).
|
||||||
|
key: KeyId,
|
||||||
|
/// The id of the node the property belongs to.
|
||||||
|
node: NodeId,
|
||||||
|
/// Whether the property should only affect the first node down the
|
||||||
|
/// hierarchy. Used by constructors.
|
||||||
|
scoped: bool,
|
||||||
|
/// The property's value.
|
||||||
|
value: Arc<Prehashed<dyn Bounds>>,
|
||||||
|
/// The name of the property.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
/// Create a new property from a key-value pair.
|
||||||
|
pub fn new<'a, K: Key<'a>>(_: K, value: K::Value) -> Self {
|
||||||
|
Self {
|
||||||
|
key: KeyId::of::<K>(),
|
||||||
|
node: K::node(),
|
||||||
|
value: Arc::new(Prehashed::new(value)),
|
||||||
|
scoped: false,
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
name: K::NAME,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this property has the given key.
|
||||||
|
pub fn is<'a, K: Key<'a>>(&self) -> bool {
|
||||||
|
self.key == KeyId::of::<K>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this property belongs to the node `T`.
|
||||||
|
pub fn is_of<T: 'static>(&self) -> bool {
|
||||||
|
self.node == NodeId::of::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the property's value if it is of the given key.
|
||||||
|
pub fn downcast<'a, K: Key<'a>>(&'a self) -> Option<&'a K::Value> {
|
||||||
|
if self.key == KeyId::of::<K>() {
|
||||||
|
(**self.value).as_any().downcast_ref()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The node this property is for.
|
||||||
|
pub fn node(&self) -> NodeId {
|
||||||
|
self.node
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the property is scoped.
|
||||||
|
pub fn scoped(&self) -> bool {
|
||||||
|
self.scoped
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make the property scoped.
|
||||||
|
pub fn make_scoped(&mut self) {
|
||||||
|
self.scoped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What kind of structure the property interrupts.
|
||||||
|
pub fn interruption(&self) -> Option<Interruption> {
|
||||||
|
if self.is_of::<PageNode>() {
|
||||||
|
Some(Interruption::Page)
|
||||||
|
} else if self.is_of::<ParNode>() {
|
||||||
|
Some(Interruption::Par)
|
||||||
|
} else if self.is_of::<ListNode>()
|
||||||
|
|| self.is_of::<EnumNode>()
|
||||||
|
|| self.is_of::<DescNode>()
|
||||||
|
{
|
||||||
|
Some(Interruption::List)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Property {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
write!(f, "{} = ", self.name)?;
|
||||||
|
write!(f, "{:?}", self.value)?;
|
||||||
|
if self.scoped {
|
||||||
|
write!(f, " [scoped]")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Property {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.key == other.key
|
||||||
|
&& self.value.eq(&other.value)
|
||||||
|
&& self.scoped == other.scoped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Bounds: Debug + Sync + Send + 'static {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Bounds for T
|
||||||
|
where
|
||||||
|
T: Debug + Sync + Send + 'static,
|
||||||
|
{
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A style property key.
|
||||||
|
///
|
||||||
|
/// This trait is not intended to be implemented manually, but rather through
|
||||||
|
/// the `#[node]` proc-macro.
|
||||||
|
pub trait Key<'a>: Copy + 'static {
|
||||||
|
/// The unfolded type which this property is stored as in a style map.
|
||||||
|
type Value: Debug + Clone + Hash + Sync + Send + 'static;
|
||||||
|
|
||||||
|
/// The folded type of value that is returned when reading this property
|
||||||
|
/// from a style chain.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// The name of the property, used for debug printing.
|
||||||
|
const NAME: &'static str;
|
||||||
|
|
||||||
|
/// The id of the node the key belongs to.
|
||||||
|
fn node() -> NodeId;
|
||||||
|
|
||||||
|
/// Compute an output value from a sequence of values belonging to this key,
|
||||||
|
/// folding if necessary.
|
||||||
|
fn get(
|
||||||
|
chain: StyleChain<'a>,
|
||||||
|
values: impl Iterator<Item = &'a Self::Value>,
|
||||||
|
) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A unique identifier for a property key.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
struct KeyId(ReadableTypeId);
|
||||||
|
|
||||||
|
impl KeyId {
|
||||||
|
/// The id of the given key.
|
||||||
|
pub fn of<'a, T: Key<'a>>() -> Self {
|
||||||
|
Self(ReadableTypeId::of::<T>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for KeyId {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A scoped property barrier.
|
||||||
|
///
|
||||||
|
/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
|
||||||
|
/// style can still be read through a single barrier (the one of the node it
|
||||||
|
/// _should_ apply to), but a second barrier will make it invisible.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Barrier(NodeId);
|
||||||
|
|
||||||
|
impl Barrier {
|
||||||
|
/// Create a new barrier for the given node.
|
||||||
|
pub fn new(node: NodeId) -> Self {
|
||||||
|
Self(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this barrier is for the node `T`.
|
||||||
|
pub fn is_for(&self, node: NodeId) -> bool {
|
||||||
|
self.0 == node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Barrier {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Barrier for {:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A property that is resolved with other properties from the style chain.
|
||||||
|
pub trait Resolve {
|
||||||
|
/// The type of the resolved output.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// Resolve the value using the style chain.
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for Em {
|
||||||
|
type Output = Abs;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
if self.is_zero() {
|
||||||
|
Abs::zero()
|
||||||
|
} else {
|
||||||
|
self.at(styles.get(TextNode::SIZE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolve for Length {
|
||||||
|
type Output = Abs;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.abs + self.em.resolve(styles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Option<T> {
|
||||||
|
type Output = Option<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Smart<T> {
|
||||||
|
type Output = Smart<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Axes<T> {
|
||||||
|
type Output = Axes<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Sides<T> {
|
||||||
|
type Output = Sides<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Resolve> Resolve for Corners<T> {
|
||||||
|
type Output = Corners<T::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|v| v.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Resolve for Rel<T>
|
||||||
|
where
|
||||||
|
T: Resolve + Numeric,
|
||||||
|
<T as Resolve>::Output: Numeric,
|
||||||
|
{
|
||||||
|
type Output = Rel<<T as Resolve>::Output>;
|
||||||
|
|
||||||
|
fn resolve(self, styles: StyleChain) -> Self::Output {
|
||||||
|
self.map(|abs| abs.resolve(styles))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A property that is folded to determine its final value.
|
||||||
|
pub trait Fold {
|
||||||
|
/// The type of the folded output.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// Fold this inner value with an outer folded value.
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Fold for Option<T>
|
||||||
|
where
|
||||||
|
T: Fold,
|
||||||
|
T::Output: Default,
|
||||||
|
{
|
||||||
|
type Output = Option<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Fold for Smart<T>
|
||||||
|
where
|
||||||
|
T: Fold,
|
||||||
|
T::Output: Default,
|
||||||
|
{
|
||||||
|
type Output = Smart<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Fold for Sides<T>
|
||||||
|
where
|
||||||
|
T: Fold,
|
||||||
|
{
|
||||||
|
type Output = Sides<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer, |inner, outer| inner.fold(outer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for Sides<Option<Rel<Abs>>> {
|
||||||
|
type Output = Sides<Rel<Abs>>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for Sides<Option<Smart<Rel<Length>>>> {
|
||||||
|
type Output = Sides<Smart<Rel<Length>>>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Fold for Corners<T>
|
||||||
|
where
|
||||||
|
T: Fold,
|
||||||
|
{
|
||||||
|
type Output = Corners<T::Output>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer, |inner, outer| inner.fold(outer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for Corners<Option<Rel<Abs>>> {
|
||||||
|
type Output = Corners<Rel<Abs>>;
|
||||||
|
|
||||||
|
fn fold(self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.zip(outer, |inner, outer| inner.unwrap_or(outer))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A show rule recipe.
|
||||||
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
|
pub struct Recipe {
|
||||||
|
/// The patterns to customize.
|
||||||
|
pub pattern: Pattern,
|
||||||
|
/// The function that defines the recipe.
|
||||||
|
pub func: Spanned<Func>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Recipe {
|
||||||
|
/// Whether the recipe is applicable to the target.
|
||||||
|
pub fn applicable(&self, target: Target) -> bool {
|
||||||
|
match (&self.pattern, target) {
|
||||||
|
(Pattern::Node(id), Target::Node(node)) => *id == node.id(),
|
||||||
|
(Pattern::Regex(_), Target::Text(_)) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to apply the recipe to the target.
|
||||||
|
pub fn apply(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
sel: Selector,
|
||||||
|
target: Target,
|
||||||
|
) -> SourceResult<Option<Content>> {
|
||||||
|
let content = match (target, &self.pattern) {
|
||||||
|
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
|
||||||
|
let node = node.to::<dyn Show>().unwrap().unguard_parts(sel);
|
||||||
|
self.call(world, || Value::Content(node))?
|
||||||
|
}
|
||||||
|
|
||||||
|
(Target::Text(text), Pattern::Regex(regex)) => {
|
||||||
|
let mut result = vec![];
|
||||||
|
let mut cursor = 0;
|
||||||
|
|
||||||
|
for mat in regex.find_iter(text) {
|
||||||
|
let start = mat.start();
|
||||||
|
if cursor < start {
|
||||||
|
result.push(TextNode(text[cursor .. start].into()).pack());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
|
||||||
|
cursor = mat.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if cursor < text.len() {
|
||||||
|
result.push(TextNode(text[cursor ..].into()).pack());
|
||||||
|
}
|
||||||
|
|
||||||
|
Content::sequence(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(content.styled_with_entry(StyleEntry::Guard(sel))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call the recipe function, with the argument if desired.
|
||||||
|
fn call<F>(&self, world: Tracked<dyn World>, arg: F) -> SourceResult<Content>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> Value,
|
||||||
|
{
|
||||||
|
let args = if self.func.v.argc() == Some(0) {
|
||||||
|
Args::new(self.func.span, [])
|
||||||
|
} else {
|
||||||
|
Args::new(self.func.span, [arg()])
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.func.v.call_detached(world, args)?.display(world))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What kind of structure the property interrupts.
|
||||||
|
pub fn interruption(&self) -> Option<Interruption> {
|
||||||
|
if let Pattern::Node(id) = self.pattern {
|
||||||
|
if id == NodeId::of::<ListNode>()
|
||||||
|
|| id == NodeId::of::<EnumNode>()
|
||||||
|
|| id == NodeId::of::<DescNode>()
|
||||||
|
{
|
||||||
|
return Some(Interruption::List);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Recipe {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Recipe matching {:?} from {:?}",
|
||||||
|
self.pattern, self.func.span
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A show rule pattern that may match a target.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
|
pub enum Pattern {
|
||||||
|
/// Defines the appearence of some node.
|
||||||
|
Node(NodeId),
|
||||||
|
/// Defines text to be replaced.
|
||||||
|
Regex(Regex),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern {
|
||||||
|
/// Define a simple text replacement pattern.
|
||||||
|
pub fn text(text: &str) -> Self {
|
||||||
|
Self::Regex(Regex::new(®ex::escape(text)).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A target for a show rule recipe.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub enum Target<'a> {
|
||||||
|
/// A showable node.
|
||||||
|
Node(&'a Content),
|
||||||
|
/// A slice of text.
|
||||||
|
Text(&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Identifies a show rule recipe.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
||||||
|
pub enum Selector {
|
||||||
|
/// The nth recipe from the top of the chain.
|
||||||
|
Nth(usize),
|
||||||
|
/// The base recipe for a kind of node.
|
||||||
|
Base(NodeId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node that can be realized given some styles.
|
||||||
|
#[capability]
|
||||||
|
pub trait Show: 'static + Sync + Send {
|
||||||
|
/// Unguard nested content against recursive show rules.
|
||||||
|
fn unguard_parts(&self, sel: Selector) -> Content;
|
||||||
|
|
||||||
|
/// Access a field on this node.
|
||||||
|
fn field(&self, name: &str) -> Option<Value>;
|
||||||
|
|
||||||
|
/// The base recipe for this node that is executed if there is no
|
||||||
|
/// user-defined show rule.
|
||||||
|
fn realize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content>;
|
||||||
|
|
||||||
|
/// Finalize this node given the realization of a base or user recipe. Use
|
||||||
|
/// this for effects that should work even in the face of a user-defined
|
||||||
|
/// show rule, for example:
|
||||||
|
/// - Application of general settable properties
|
||||||
|
///
|
||||||
|
/// Defaults to just the realized content.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn finalize(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
styles: StyleChain,
|
||||||
|
realized: Content,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
Ok(realized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -307,7 +307,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hash128(&self) -> u128 {
|
fn hash128(&self) -> u128 {
|
||||||
// Also hash the TypeId since nodes with different types but
|
// Also hash the TypeId since values with different types but
|
||||||
// equal data should be different.
|
// equal data should be different.
|
||||||
let mut state = SipHasher::new();
|
let mut state = SipHasher::new();
|
||||||
self.type_id().hash(&mut state);
|
self.type_id().hash(&mut state);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user