mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
More dynamic content representation
This commit is contained in:
parent
f547c97072
commit
37ac5d966e
@ -12,7 +12,7 @@ use syn::{Error, Ident, Result};
|
|||||||
#[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(TokenStream2::from(stream), impl_block)
|
expand(stream.into(), impl_block)
|
||||||
.unwrap_or_else(|err| err.to_compile_error())
|
.unwrap_or_else(|err| err.to_compile_error())
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
@ -51,14 +51,35 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenSt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let construct =
|
let construct = construct.unwrap_or_else(|| {
|
||||||
construct.ok_or_else(|| Error::new(impl_block.span(), "missing constructor"))?;
|
parse_quote! {
|
||||||
|
fn construct(
|
||||||
|
_: &mut model::Vm,
|
||||||
|
_: &mut model::Args,
|
||||||
|
) -> crate::diag::SourceResult<model::Content> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let set = generate_set(&properties, set);
|
let set = generate_set(&properties, set);
|
||||||
let showable = match stream.to_string().as_str() {
|
|
||||||
"" => false,
|
let items: syn::punctuated::Punctuated<Ident, syn::Token![,]> =
|
||||||
"showable" => true,
|
parse_quote! { #stream };
|
||||||
_ => return Err(Error::new(stream.span(), "unrecognized argument")),
|
|
||||||
|
let checks = items.iter().map(|cap| {
|
||||||
|
quote! {
|
||||||
|
if id == TypeId::of::<dyn #cap>() {
|
||||||
|
return Some(unsafe { crate::util::fat::vtable(self as &dyn #cap) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let vtable = quote! {
|
||||||
|
fn vtable(&self, id: TypeId) -> Option<*const ()> {
|
||||||
|
#(#checks)*
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Put everything into a module with a hopefully unique type to isolate
|
// Put everything into a module with a hopefully unique type to isolate
|
||||||
@ -75,9 +96,13 @@ fn expand(stream: TokenStream2, mut impl_block: syn::ItemImpl) -> Result<TokenSt
|
|||||||
#impl_block
|
#impl_block
|
||||||
|
|
||||||
impl<#params> model::Node for #self_ty {
|
impl<#params> model::Node for #self_ty {
|
||||||
const SHOWABLE: bool = #showable;
|
|
||||||
#construct
|
#construct
|
||||||
#set
|
#set
|
||||||
|
#vtable
|
||||||
|
|
||||||
|
fn id(&self) -> model::NodeId {
|
||||||
|
model::NodeId::of::<Self>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#(#key_modules)*
|
#(#key_modules)*
|
||||||
@ -331,7 +356,7 @@ fn generate_set(
|
|||||||
) -> syn::ImplItemMethod {
|
) -> syn::ImplItemMethod {
|
||||||
let user = user.map(|method| {
|
let user = user.map(|method| {
|
||||||
let block = &method.block;
|
let block = &method.block;
|
||||||
quote! { (|| -> SourceResult<()> { #block; Ok(()) } )()?; }
|
quote! { (|| -> crate::diag::SourceResult<()> { #block; Ok(()) } )()?; }
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut shorthands = vec![];
|
let mut shorthands = vec![];
|
||||||
@ -367,8 +392,11 @@ fn generate_set(
|
|||||||
});
|
});
|
||||||
|
|
||||||
parse_quote! {
|
parse_quote! {
|
||||||
fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap> {
|
fn set(
|
||||||
let mut styles = StyleMap::new();
|
args: &mut model::Args,
|
||||||
|
constructor: bool,
|
||||||
|
) -> crate::diag::SourceResult<model::StyleMap> {
|
||||||
|
let mut styles = model::StyleMap::new();
|
||||||
#user
|
#user
|
||||||
#(#bindings)*
|
#(#bindings)*
|
||||||
#(#sets)*
|
#(#sets)*
|
||||||
|
26
src/lib.rs
26
src/lib.rs
@ -54,8 +54,7 @@ use comemo::{Prehashed, Track};
|
|||||||
use crate::diag::{FileResult, SourceResult};
|
use crate::diag::{FileResult, SourceResult};
|
||||||
use crate::font::{Font, FontBook};
|
use crate::font::{Font, FontBook};
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::model::StyleMap;
|
use crate::model::{Content, Route, Scope, StyleMap};
|
||||||
use crate::model::{Content, Route, Scope};
|
|
||||||
use crate::syntax::{Source, SourceId};
|
use crate::syntax::{Source, SourceId};
|
||||||
use crate::util::{Buffer, EcoString};
|
use crate::util::{Buffer, EcoString};
|
||||||
|
|
||||||
@ -130,13 +129,18 @@ impl Default for Config {
|
|||||||
/// Definition of certain standard library items the language is aware of.
|
/// Definition of certain standard library items the language is aware of.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct LangItems {
|
pub struct LangItems {
|
||||||
pub strong: fn(Content) -> Content,
|
pub space: fn() -> Content,
|
||||||
pub emph: fn(Content) -> Content,
|
pub linebreak: fn(justify: bool) -> Content,
|
||||||
pub raw: fn(EcoString, Option<EcoString>, bool) -> Content,
|
pub text: fn(text: EcoString) -> Content,
|
||||||
pub link: fn(EcoString) -> Content,
|
pub smart_quote: fn(double: bool) -> Content,
|
||||||
pub ref_: fn(EcoString) -> Content,
|
pub parbreak: fn() -> Content,
|
||||||
pub heading: fn(NonZeroUsize, Content) -> Content,
|
pub strong: fn(body: Content) -> Content,
|
||||||
pub list_item: fn(Content) -> Content,
|
pub emph: fn(body: Content) -> Content,
|
||||||
pub enum_item: fn(Option<usize>, Content) -> Content,
|
pub raw: fn(text: EcoString, tag: Option<EcoString>, block: bool) -> Content,
|
||||||
pub desc_item: fn(Content, Content) -> Content,
|
pub link: fn(label: EcoString) -> Content,
|
||||||
|
pub ref_: fn(target: EcoString) -> Content,
|
||||||
|
pub heading: fn(level: NonZeroUsize, body: Content) -> Content,
|
||||||
|
pub list_item: fn(body: Content) -> Content,
|
||||||
|
pub enum_item: fn(number: Option<usize>, body: Content) -> Content,
|
||||||
|
pub desc_item: fn(term: Content, body: Content) -> Content,
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,12 @@ use crate::library::prelude::*;
|
|||||||
|
|
||||||
/// Hide a node without affecting layout.
|
/// Hide a node without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct HideNode(pub LayoutNode);
|
pub struct HideNode(pub Content);
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
impl HideNode {
|
impl HideNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::inline(Self(args.expect("body")?)))
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,10 +18,14 @@ impl Layout for HideNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let mut frames = self.0.layout(world, regions, styles)?;
|
let mut frames = self.0.layout_inline(world, regions, styles)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
frame.clear();
|
frame.clear();
|
||||||
}
|
}
|
||||||
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]
|
#[node(Layout)]
|
||||||
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;
|
||||||
@ -32,9 +32,7 @@ impl ImageNode {
|
|||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
|
|
||||||
Ok(Content::inline(
|
Ok(ImageNode(image).pack().boxed(Axes::new(width, height)))
|
||||||
ImageNode(image).pack().sized(Axes::new(width, height)),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +95,10 @@ 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]
|
#[node(Layout)]
|
||||||
impl LineNode {
|
impl LineNode {
|
||||||
/// How to stroke the line.
|
/// How to stroke the line.
|
||||||
#[property(resolve, fold)]
|
#[property(resolve, fold)]
|
||||||
@ -32,7 +32,7 @@ impl LineNode {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Content::inline(Self { origin, delta }))
|
Ok(Self { origin, delta }.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +65,10 @@ impl Layout for LineNode {
|
|||||||
|
|
||||||
Ok(vec![frame])
|
Ok(vec![frame])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Inline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
|
@ -5,7 +5,7 @@ use crate::library::text::TextNode;
|
|||||||
|
|
||||||
/// Place a node into a sizable and fillable shape.
|
/// Place a node into a sizable and fillable shape.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ShapeNode<const S: ShapeKind>(pub Option<LayoutNode>);
|
pub struct ShapeNode<const S: ShapeKind>(pub Option<Content>);
|
||||||
|
|
||||||
/// Place a node into a square.
|
/// Place a node into a square.
|
||||||
pub type SquareNode = ShapeNode<SQUARE>;
|
pub type SquareNode = ShapeNode<SQUARE>;
|
||||||
@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode<CIRCLE>;
|
|||||||
/// Place a node into an ellipse.
|
/// Place a node into an ellipse.
|
||||||
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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;
|
||||||
@ -55,9 +55,7 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
size => size,
|
size => size,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Content::inline(
|
Ok(Self(args.eat()?).pack().boxed(Axes::new(width, height)))
|
||||||
Self(args.eat()?).pack().sized(Axes::new(width, height)),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(...) {
|
fn set(...) {
|
||||||
@ -92,7 +90,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||||
|
|
||||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||||
frames = child.layout(world, &pod, styles)?;
|
frames = child.layout_inline(world, &pod, styles)?;
|
||||||
|
|
||||||
for frame in frames.iter_mut() {
|
for frame in frames.iter_mut() {
|
||||||
frame.apply_role(Role::GenericBlock);
|
frame.apply_role(Role::GenericBlock);
|
||||||
@ -112,7 +110,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
|
|
||||||
pod.first = Size::splat(length);
|
pod.first = Size::splat(length);
|
||||||
pod.expand = Axes::splat(true);
|
pod.expand = Axes::splat(true);
|
||||||
frames = child.layout(world, &pod, styles)?;
|
frames = child.layout_inline(world, &pod, styles)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The default size that a shape takes on if it has no child and
|
// The default size that a shape takes on if it has no child and
|
||||||
@ -175,6 +173,10 @@ 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.
|
||||||
|
@ -7,21 +7,25 @@ pub struct AlignNode {
|
|||||||
/// How to align the node horizontally and vertically.
|
/// How to align the node horizontally and vertically.
|
||||||
pub aligns: Axes<Option<RawAlign>>,
|
pub aligns: Axes<Option<RawAlign>>,
|
||||||
/// The node to be aligned.
|
/// The node to be aligned.
|
||||||
pub child: LayoutNode,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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")?;
|
||||||
Ok(match (body, aligns) {
|
|
||||||
(Content::Block(node), _) => Content::Block(node.aligned(aligns)),
|
if let Axes { x: Some(x), y: None } = aligns {
|
||||||
(other, Axes { x: Some(x), y: None }) => {
|
if body
|
||||||
other.styled(ParNode::ALIGN, HorizontalAlign(x))
|
.to::<dyn Layout>()
|
||||||
|
.map_or(true, |node| node.level() == Level::Inline)
|
||||||
|
{
|
||||||
|
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
|
||||||
}
|
}
|
||||||
(other, _) => Content::Block(other.pack().aligned(aligns)),
|
}
|
||||||
})
|
|
||||||
|
Ok(body.aligned(aligns))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +47,7 @@ impl Layout for AlignNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = self.child.layout(world, &pod, passed.chain(&styles))?;
|
let mut frames = self.child.layout_block(world, &pod, passed.chain(&styles))?;
|
||||||
for (region, frame) in regions.iter().zip(&mut frames) {
|
for (region, frame) in regions.iter().zip(&mut frames) {
|
||||||
// Align in the target size. The target size depends on whether we
|
// Align in the target size. The target size depends on whether we
|
||||||
// should expand.
|
// should expand.
|
||||||
@ -58,4 +62,8 @@ impl Layout for AlignNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Block
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,20 +8,21 @@ pub struct ColumnsNode {
|
|||||||
pub columns: NonZeroUsize,
|
pub columns: NonZeroUsize,
|
||||||
/// The child to be layouted into the columns. Most likely, this should be a
|
/// The child to be layouted into the columns. Most likely, this should be a
|
||||||
/// flow or stack node.
|
/// flow or stack node.
|
||||||
pub child: LayoutNode,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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)]
|
||||||
pub const GUTTER: Rel<Length> = Ratio::new(0.04).into();
|
pub const GUTTER: Rel<Length> = Ratio::new(0.04).into();
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::block(Self {
|
Ok(Self {
|
||||||
columns: args.expect("column count")?,
|
columns: args.expect("column count")?,
|
||||||
child: args.expect("body")?,
|
child: args.expect("body")?,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ impl Layout for ColumnsNode {
|
|||||||
// Separating the infinite space into infinite columns does not make
|
// Separating the infinite space into infinite columns does not make
|
||||||
// much sense.
|
// much sense.
|
||||||
if !regions.first.x.is_finite() {
|
if !regions.first.x.is_finite() {
|
||||||
return self.child.layout(world, regions, styles);
|
return self.child.layout_block(world, regions, styles);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
@ -57,7 +58,7 @@ impl Layout for ColumnsNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout the children.
|
// Layout the children.
|
||||||
let mut frames = self.child.layout(world, &pod, styles)?.into_iter();
|
let mut frames = self.child.layout_block(world, &pod, styles)?.into_iter();
|
||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
|
|
||||||
let dir = styles.get(TextNode::DIR);
|
let dir = styles.get(TextNode::DIR);
|
||||||
@ -99,15 +100,22 @@ impl Layout for ColumnsNode {
|
|||||||
|
|
||||||
Ok(finished)
|
Ok(finished)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Block
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A column break.
|
/// A column break.
|
||||||
pub struct ColbreakNode;
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct ColbreakNode {
|
||||||
|
pub weak: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl ColbreakNode {
|
impl ColbreakNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Colbreak { weak })
|
Ok(Self { weak }.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,88 @@
|
|||||||
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 and places it into a paragraph.
|
||||||
pub struct BoxNode;
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct BoxNode {
|
||||||
|
/// How to size the node horizontally and vertically.
|
||||||
|
pub sizing: Axes<Option<Rel<Length>>>,
|
||||||
|
/// The node to be sized.
|
||||||
|
pub child: Content,
|
||||||
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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")?;
|
||||||
let height = args.named("height")?;
|
let height = args.named("height")?;
|
||||||
let body: LayoutNode = args.eat()?.unwrap_or_default();
|
let body = args.eat::<Content>()?.unwrap_or_default();
|
||||||
Ok(Content::inline(body.sized(Axes::new(width, height))))
|
Ok(body.boxed(Axes::new(width, height)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for BoxNode {
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
// The "pod" is the region into which the child will be layouted.
|
||||||
|
let pod = {
|
||||||
|
// Resolve the sizing to a concrete size.
|
||||||
|
let size = self
|
||||||
|
.sizing
|
||||||
|
.resolve(styles)
|
||||||
|
.zip(regions.base)
|
||||||
|
.map(|(s, b)| s.map(|v| v.relative_to(b)))
|
||||||
|
.unwrap_or(regions.first);
|
||||||
|
|
||||||
|
// Select the appropriate base and expansion for the child depending
|
||||||
|
// on whether it is automatically or relatively sized.
|
||||||
|
let is_auto = self.sizing.as_ref().map(Option::is_none);
|
||||||
|
let base = is_auto.select(regions.base, size);
|
||||||
|
let expand = regions.expand | !is_auto;
|
||||||
|
|
||||||
|
Regions::one(size, base, expand)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Layout the child.
|
||||||
|
let mut frames = self.child.layout_inline(world, &pod, styles)?;
|
||||||
|
|
||||||
|
// Ensure frame size matches regions size if expansion is on.
|
||||||
|
let frame = &mut frames[0];
|
||||||
|
let target = regions.expand.select(regions.first, frame.size());
|
||||||
|
frame.resize(target, Align::LEFT_TOP);
|
||||||
|
|
||||||
|
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.
|
||||||
pub struct BlockNode;
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct BlockNode(pub Content);
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
impl BlockNode {
|
impl BlockNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::Block(args.eat()?.unwrap_or_default()))
|
Ok(Self(args.eat()?.unwrap_or_default()).pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for BlockNode {
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
self.0.layout_block(world, regions, styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,14 @@ pub enum FlowChild {
|
|||||||
/// Vertical spacing between other children.
|
/// Vertical spacing between other children.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary block-level node.
|
/// An arbitrary block-level node.
|
||||||
Node(LayoutNode),
|
Node(Content),
|
||||||
/// A column / region break.
|
/// A column / region break.
|
||||||
Colbreak,
|
Colbreak,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node(Layout)]
|
||||||
|
impl FlowNode {}
|
||||||
|
|
||||||
impl Layout for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
@ -48,6 +51,10 @@ impl Layout for FlowNode {
|
|||||||
|
|
||||||
Ok(layouter.finish())
|
Ok(layouter.finish())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Block
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for FlowNode {
|
impl Debug for FlowNode {
|
||||||
@ -150,7 +157,7 @@ impl FlowLayouter {
|
|||||||
pub fn layout_node(
|
pub fn layout_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
node: &LayoutNode,
|
node: &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.
|
||||||
@ -162,7 +169,7 @@ impl FlowLayouter {
|
|||||||
// aligned later.
|
// aligned later.
|
||||||
if let Some(placed) = node.downcast::<PlaceNode>() {
|
if let Some(placed) = node.downcast::<PlaceNode>() {
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow() {
|
||||||
let frame = node.layout(world, &self.regions, styles)?.remove(0);
|
let frame = node.layout_block(world, &self.regions, styles)?.remove(0);
|
||||||
self.items.push(FlowItem::Placed(frame));
|
self.items.push(FlowItem::Placed(frame));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -180,7 +187,7 @@ impl FlowLayouter {
|
|||||||
.unwrap_or(Align::Top),
|
.unwrap_or(Align::Top),
|
||||||
);
|
);
|
||||||
|
|
||||||
let frames = node.layout(world, &self.regions, styles)?;
|
let frames = node.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.
|
||||||
|
@ -8,10 +8,10 @@ pub struct GridNode {
|
|||||||
/// 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 nodes to be arranged in a grid.
|
||||||
pub cells: Vec<LayoutNode>,
|
pub cells: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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();
|
||||||
@ -19,14 +19,15 @@ impl GridNode {
|
|||||||
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
||||||
let column_gutter = args.named("column-gutter")?;
|
let column_gutter = args.named("column-gutter")?;
|
||||||
let row_gutter = args.named("row-gutter")?;
|
let row_gutter = args.named("row-gutter")?;
|
||||||
Ok(Content::block(Self {
|
Ok(Self {
|
||||||
tracks: Axes::new(columns, rows),
|
tracks: Axes::new(columns, rows),
|
||||||
gutter: Axes::new(
|
gutter: Axes::new(
|
||||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
||||||
row_gutter.unwrap_or(base_gutter),
|
row_gutter.unwrap_or(base_gutter),
|
||||||
),
|
),
|
||||||
cells: args.all()?,
|
cells: args.all()?,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +51,10 @@ 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.
|
||||||
@ -95,7 +100,7 @@ pub struct GridLayouter<'a> {
|
|||||||
/// The core context.
|
/// The core context.
|
||||||
world: Tracked<'a, dyn World>,
|
world: Tracked<'a, dyn World>,
|
||||||
/// The grid cells.
|
/// The grid cells.
|
||||||
cells: &'a [LayoutNode],
|
cells: &'a [Content],
|
||||||
/// The column tracks including gutter tracks.
|
/// The column tracks including gutter tracks.
|
||||||
cols: Vec<TrackSizing>,
|
cols: Vec<TrackSizing>,
|
||||||
/// The row tracks including gutter tracks.
|
/// The row tracks including gutter tracks.
|
||||||
@ -136,7 +141,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
world: Tracked<'a, dyn World>,
|
world: Tracked<'a, dyn World>,
|
||||||
tracks: Axes<&[TrackSizing]>,
|
tracks: Axes<&[TrackSizing]>,
|
||||||
gutter: Axes<&[TrackSizing]>,
|
gutter: Axes<&[TrackSizing]>,
|
||||||
cells: &'a [LayoutNode],
|
cells: &'a [Content],
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -301,7 +306,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
v.resolve(self.styles).relative_to(self.regions.base.y);
|
v.resolve(self.styles).relative_to(self.regions.base.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = node.layout(self.world, &pod, self.styles)?.remove(0);
|
let frame =
|
||||||
|
node.layout_block(self.world, &pod, self.styles)?.remove(0);
|
||||||
resolved.set_max(frame.width());
|
resolved.set_max(frame.width());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -371,7 +377,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut sizes = node
|
let mut sizes = node
|
||||||
.layout(self.world, &pod, self.styles)?
|
.layout_block(self.world, &pod, self.styles)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|frame| frame.height());
|
.map(|frame| frame.height());
|
||||||
|
|
||||||
@ -460,7 +466,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(self.world, &pod, self.styles)?.remove(0);
|
let frame = node.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)
|
||||||
@ -508,7 +514,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(self.world, &pod, self.styles)?;
|
let frames = node.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) => {
|
||||||
@ -576,7 +582,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
///
|
///
|
||||||
/// Returns `None` if it's a gutter cell.
|
/// Returns `None` if it's a gutter cell.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> {
|
fn cell(&self, x: usize, y: usize) -> Option<&'a Content> {
|
||||||
assert!(x < self.cols.len());
|
assert!(x < self.cols.len());
|
||||||
assert!(y < self.rows.len());
|
assert!(y < self.rows.len());
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ 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 child node whose sides to pad.
|
||||||
pub child: LayoutNode,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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()?);
|
||||||
@ -19,9 +19,9 @@ impl PadNode {
|
|||||||
let top = args.named("top")?.or(y).or(all).unwrap_or_default();
|
let top = args.named("top")?.or(y).or(all).unwrap_or_default();
|
||||||
let right = args.named("right")?.or(x).or(all).unwrap_or_default();
|
let right = args.named("right")?.or(x).or(all).unwrap_or_default();
|
||||||
let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
|
let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
|
||||||
let body: LayoutNode = args.expect("body")?;
|
let body = args.expect::<Content>("body")?;
|
||||||
let padding = Sides::new(left, top, right, bottom);
|
let padding = Sides::new(left, top, right, bottom);
|
||||||
Ok(Content::block(body.padded(padding)))
|
Ok(body.padded(padding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ impl Layout for PadNode {
|
|||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let padding = self.padding.resolve(styles);
|
let padding = self.padding.resolve(styles);
|
||||||
let pod = regions.map(|size| shrink(size, padding));
|
let pod = regions.map(|size| shrink(size, padding));
|
||||||
let mut frames = self.child.layout(world, &pod, styles)?;
|
let mut frames = self.child.layout_block(world, &pod, styles)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
@ -51,6 +51,10 @@ 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.
|
||||||
|
@ -5,7 +5,7 @@ use crate::library::prelude::*;
|
|||||||
|
|
||||||
/// Layouts its child onto one or multiple pages.
|
/// Layouts its child onto one or multiple pages.
|
||||||
#[derive(PartialEq, Clone, Hash)]
|
#[derive(PartialEq, Clone, Hash)]
|
||||||
pub struct PageNode(pub LayoutNode);
|
pub struct PageNode(pub Content);
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
@ -41,7 +41,7 @@ impl PageNode {
|
|||||||
pub const FOREGROUND: Marginal = Marginal::None;
|
pub const FOREGROUND: Marginal = Marginal::None;
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::Page(Self(args.expect("body")?)))
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(...) {
|
fn set(...) {
|
||||||
@ -96,7 +96,7 @@ impl PageNode {
|
|||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
|
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
|
||||||
let mut frames = child.layout(world, ®ions, styles)?;
|
let mut frames = child.layout_block(world, ®ions, styles)?;
|
||||||
|
|
||||||
let header = styles.get(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
let footer = styles.get(Self::FOOTER);
|
let footer = styles.get(Self::FOOTER);
|
||||||
@ -127,7 +127,7 @@ impl PageNode {
|
|||||||
] {
|
] {
|
||||||
if let Some(content) = marginal.resolve(world, page)? {
|
if let Some(content) = marginal.resolve(world, page)? {
|
||||||
let pod = Regions::one(area, area, Axes::splat(true));
|
let pod = Regions::one(area, area, Axes::splat(true));
|
||||||
let mut sub = content.layout(world, &pod, styles)?.remove(0);
|
let mut sub = content.layout_block(world, &pod, styles)?.remove(0);
|
||||||
sub.apply_role(role);
|
sub.apply_role(role);
|
||||||
|
|
||||||
if role == Role::Background {
|
if role == Role::Background {
|
||||||
@ -154,13 +154,16 @@ impl Debug for PageNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A page break.
|
/// A page break.
|
||||||
pub struct PagebreakNode;
|
#[derive(Debug, Copy, Clone, Hash)]
|
||||||
|
pub struct PagebreakNode {
|
||||||
|
pub weak: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl PagebreakNode {
|
impl PagebreakNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Pagebreak { weak })
|
Ok(Self { weak }.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +204,7 @@ impl Cast<Spanned<Value>> for Marginal {
|
|||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
match value.v {
|
match value.v {
|
||||||
Value::None => Ok(Self::None),
|
Value::None => Ok(Self::None),
|
||||||
Value::Str(v) => Ok(Self::Content(Content::Text(v.into()))),
|
Value::Str(v) => Ok(Self::Content(TextNode(v.into()).pack())),
|
||||||
Value::Content(v) => Ok(Self::Content(v)),
|
Value::Content(v) => Ok(Self::Content(v)),
|
||||||
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
Value::Func(v) => Ok(Self::Func(v, value.span)),
|
||||||
v => Err(format!(
|
v => Err(format!(
|
||||||
|
@ -3,18 +3,16 @@ use crate::library::prelude::*;
|
|||||||
|
|
||||||
/// Place a node at an absolute position.
|
/// Place a node at an absolute position.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PlaceNode(pub LayoutNode);
|
pub struct PlaceNode(pub Content);
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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)));
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
let dx = args.named("dx")?.unwrap_or_default();
|
||||||
let dy = args.named("dy")?.unwrap_or_default();
|
let dy = args.named("dy")?.unwrap_or_default();
|
||||||
let body: LayoutNode = args.expect("body")?;
|
let body = args.expect::<Content>("body")?;
|
||||||
Ok(Content::block(Self(
|
Ok(Self(body.moved(Axes::new(dx, dy)).aligned(aligns)).pack())
|
||||||
body.moved(Axes::new(dx, dy)).aligned(aligns),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +33,7 @@ impl Layout for PlaceNode {
|
|||||||
Regions::one(regions.base, regions.base, expand)
|
Regions::one(regions.base, regions.base, expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frames = self.0.layout(world, &pod, styles)?;
|
let mut frames = self.0.layout_block(world, &pod, styles)?;
|
||||||
|
|
||||||
// If expansion is off, zero all sizes so that we don't take up any
|
// If expansion is off, zero all sizes so that we don't take up any
|
||||||
// space in our parent. Otherwise, respect the expand settings.
|
// space in our parent. Otherwise, respect the expand settings.
|
||||||
@ -44,6 +42,10 @@ impl Layout for PlaceNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Block
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
|
@ -4,26 +4,35 @@ use crate::library::prelude::*;
|
|||||||
use crate::library::text::ParNode;
|
use crate::library::text::ParNode;
|
||||||
|
|
||||||
/// Horizontal spacing.
|
/// Horizontal spacing.
|
||||||
pub struct HNode;
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct HNode {
|
||||||
|
pub amount: Spacing,
|
||||||
|
pub weak: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl HNode {
|
impl HNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let amount = args.expect("spacing")?;
|
let amount = args.expect("spacing")?;
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Horizontal { amount, weak })
|
Ok(Self { amount, weak }.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vertical spacing.
|
/// Vertical spacing.
|
||||||
pub struct VNode;
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct VNode {
|
||||||
|
pub amount: Spacing,
|
||||||
|
pub weak: bool,
|
||||||
|
pub generated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl VNode {
|
impl VNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let amount = args.expect("spacing")?;
|
let amount = args.expect("spacing")?;
|
||||||
let weak = args.named("weak")?.unwrap_or(false);
|
let weak = args.named("weak")?.unwrap_or(false);
|
||||||
Ok(Content::Vertical { amount, weak, generated: false })
|
Ok(Self { amount, weak, generated: false }.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use super::{AlignNode, Spacing};
|
use super::{AlignNode, Spacing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::ParNode;
|
use crate::library::text::ParNode;
|
||||||
|
use crate::model::StyledNode;
|
||||||
|
|
||||||
/// Arrange nodes and spacing along an axis.
|
/// Arrange nodes and spacing along an axis.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
@ -13,14 +14,15 @@ pub struct StackNode {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
impl StackNode {
|
impl StackNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::block(Self {
|
Ok(Self {
|
||||||
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
dir: args.named("dir")?.unwrap_or(Dir::TTB),
|
||||||
spacing: args.named("spacing")?,
|
spacing: args.named("spacing")?,
|
||||||
children: args.all()?,
|
children: args.all()?,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +57,10 @@ 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.
|
||||||
@ -63,7 +69,7 @@ pub enum StackChild {
|
|||||||
/// Spacing between other nodes.
|
/// Spacing between other nodes.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary node.
|
/// An arbitrary node.
|
||||||
Node(LayoutNode),
|
Node(Content),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for StackChild {
|
impl Debug for StackChild {
|
||||||
@ -82,7 +88,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.pack()),
|
Value::Content(v) => Self::Node(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs stack layout.
|
/// Performs stack layout.
|
||||||
@ -169,7 +175,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
pub fn layout_node(
|
pub fn layout_node(
|
||||||
&mut self,
|
&mut self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
node: &LayoutNode,
|
node: &Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
if self.regions.is_full() {
|
if self.regions.is_full() {
|
||||||
@ -183,17 +189,17 @@ impl<'a> StackLayouter<'a> {
|
|||||||
.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(Content::Styled(styled)) = node.downcast::<Content>() {
|
if let Some(styled) = node.downcast::<StyledNode>() {
|
||||||
let map = &styled.1;
|
let map = &styled.map;
|
||||||
if map.contains(ParNode::ALIGN) {
|
if map.contains(ParNode::ALIGN) {
|
||||||
return StyleChain::with_root(&styled.1).get(ParNode::ALIGN);
|
return StyleChain::with_root(map).get(ParNode::ALIGN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.dir.start().into()
|
self.dir.start().into()
|
||||||
});
|
});
|
||||||
|
|
||||||
let frames = node.layout(world, &self.regions, styles)?;
|
let frames = node.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.
|
||||||
|
@ -7,18 +7,19 @@ pub struct MoveNode {
|
|||||||
/// The offset by which to move the node.
|
/// The offset by which to move the node.
|
||||||
pub delta: Axes<Rel<Length>>,
|
pub delta: Axes<Rel<Length>>,
|
||||||
/// The node whose contents should be moved.
|
/// The node whose contents should be moved.
|
||||||
pub child: LayoutNode,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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();
|
||||||
let dy = args.named("dy")?.unwrap_or_default();
|
let dy = args.named("dy")?.unwrap_or_default();
|
||||||
Ok(Content::inline(Self {
|
Ok(Self {
|
||||||
delta: Axes::new(dx, dy),
|
delta: Axes::new(dx, dy),
|
||||||
child: args.expect("body")?,
|
child: args.expect("body")?,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ impl Layout for MoveNode {
|
|||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let mut frames = self.child.layout(world, regions, styles)?;
|
let mut frames = self.child.layout_inline(world, regions, styles)?;
|
||||||
|
|
||||||
let delta = self.delta.resolve(styles);
|
let delta = self.delta.resolve(styles);
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
@ -39,6 +40,10 @@ impl Layout for MoveNode {
|
|||||||
|
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Inline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transform a node without affecting layout.
|
/// Transform a node without affecting layout.
|
||||||
@ -47,7 +52,7 @@ pub struct TransformNode<const T: TransformKind> {
|
|||||||
/// Transformation to apply to the contents.
|
/// Transformation to apply to the contents.
|
||||||
pub transform: Transform,
|
pub transform: Transform,
|
||||||
/// The node whose contents should be transformed.
|
/// The node whose contents should be transformed.
|
||||||
pub child: LayoutNode,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rotate a node without affecting layout.
|
/// Rotate a node without affecting layout.
|
||||||
@ -56,7 +61,7 @@ pub type RotateNode = TransformNode<ROTATE>;
|
|||||||
/// Scale a node without affecting layout.
|
/// Scale a node without affecting layout.
|
||||||
pub type ScaleNode = TransformNode<SCALE>;
|
pub type ScaleNode = TransformNode<SCALE>;
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
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)]
|
||||||
@ -76,10 +81,7 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Content::inline(Self {
|
Ok(Self { transform, child: args.expect("body")? }.pack())
|
||||||
transform,
|
|
||||||
child: args.expect("body")?,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +93,7 @@ impl<const T: TransformKind> Layout for TransformNode<T> {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||||
let mut frames = self.child.layout(world, regions, styles)?;
|
let mut frames = self.child.layout_inline(world, regions, styles)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||||
@ -104,6 +106,10 @@ 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(showable)]
|
#[node(Show, Layout)]
|
||||||
impl MathNode {
|
impl MathNode {
|
||||||
/// The math font family.
|
/// The math font family.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -54,16 +54,6 @@ impl MathNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MathNode {
|
impl MathNode {
|
||||||
/// Whether this is a display-style node.
|
|
||||||
pub fn display(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Row(row, _) => {
|
|
||||||
matches!(row.as_slice(), [MathNode::Space, .., MathNode::Space])
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Strip parentheses from the node.
|
/// Strip parentheses from the node.
|
||||||
pub fn unparen(self) -> Self {
|
pub fn unparen(self) -> Self {
|
||||||
if let Self::Row(row, span) = &self {
|
if let Self::Row(row, span) = &self {
|
||||||
@ -80,8 +70,8 @@ impl MathNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for MathNode {
|
impl Show for MathNode {
|
||||||
fn unguard(&self, _: Selector) -> ShowNode {
|
fn unguard_parts(&self, _: Selector) -> Content {
|
||||||
ShowNode::new(self.clone())
|
self.clone().pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn field(&self, _: &str) -> Option<Value> {
|
fn field(&self, _: &str) -> Option<Value> {
|
||||||
@ -89,13 +79,11 @@ impl Show for MathNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(if self.display() {
|
Ok(match self.level() {
|
||||||
Content::block(
|
Level::Inline => self.clone().pack(),
|
||||||
LayoutNode::new(self.clone())
|
Level::Block => {
|
||||||
.aligned(Axes::with_x(Some(Align::Center.into()))),
|
self.clone().pack().aligned(Axes::with_x(Some(Align::Center.into())))
|
||||||
)
|
}
|
||||||
} else {
|
|
||||||
Content::inline(self.clone())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,10 +93,11 @@ impl Show for MathNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
realized: Content,
|
realized: Content,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
Ok(if self.display() {
|
Ok(match self.level() {
|
||||||
|
Level::Inline => realized,
|
||||||
|
Level::Block => {
|
||||||
realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
|
realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW))
|
||||||
} else {
|
}
|
||||||
realized
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,13 +109,28 @@ impl Layout for MathNode {
|
|||||||
_: &Regions,
|
_: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Vec<Frame>> {
|
||||||
let style = if self.display() { Style::Display } else { Style::Text };
|
let style = match self.level() {
|
||||||
|
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(),
|
||||||
};
|
};
|
||||||
|
|
||||||
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.
|
||||||
|
@ -18,9 +18,11 @@ pub fn scope() -> Scope {
|
|||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
|
|
||||||
// Text.
|
// Text.
|
||||||
|
std.def_node::<text::SpaceNode>("space");
|
||||||
|
std.def_node::<text::LinebreakNode>("linebreak");
|
||||||
|
std.def_node::<text::SmartQuoteNode>("smartquote");
|
||||||
std.def_node::<text::TextNode>("text");
|
std.def_node::<text::TextNode>("text");
|
||||||
std.def_node::<text::ParNode>("par");
|
std.def_node::<text::ParNode>("par");
|
||||||
std.def_node::<text::LinebreakNode>("linebreak");
|
|
||||||
std.def_node::<text::ParbreakNode>("parbreak");
|
std.def_node::<text::ParbreakNode>("parbreak");
|
||||||
std.def_node::<text::StrongNode>("strong");
|
std.def_node::<text::StrongNode>("strong");
|
||||||
std.def_node::<text::EmphNode>("emph");
|
std.def_node::<text::EmphNode>("emph");
|
||||||
@ -146,27 +148,29 @@ pub fn scope() -> Scope {
|
|||||||
/// Construct the language map.
|
/// Construct the language map.
|
||||||
pub fn items() -> LangItems {
|
pub fn items() -> LangItems {
|
||||||
LangItems {
|
LangItems {
|
||||||
strong: |body| Content::show(text::StrongNode(body)),
|
space: || text::SpaceNode.pack(),
|
||||||
emph: |body| Content::show(text::EmphNode(body)),
|
linebreak: |justify| text::LinebreakNode { justify }.pack(),
|
||||||
|
text: |text| text::TextNode(text).pack(),
|
||||||
|
smart_quote: |double| text::SmartQuoteNode { double }.pack(),
|
||||||
|
parbreak: || text::ParbreakNode.pack(),
|
||||||
|
strong: |body| text::StrongNode(body).pack(),
|
||||||
|
emph: |body| text::EmphNode(body).pack(),
|
||||||
raw: |text, lang, block| {
|
raw: |text, lang, block| {
|
||||||
let node = Content::show(text::RawNode { text, block });
|
let node = text::RawNode { text, block }.pack();
|
||||||
match lang {
|
match lang {
|
||||||
Some(_) => node.styled(text::RawNode::LANG, lang),
|
Some(_) => node.styled(text::RawNode::LANG, lang),
|
||||||
None => node,
|
None => node,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
link: |url| Content::show(text::LinkNode::from_url(url)),
|
link: |url| text::LinkNode::from_url(url).pack(),
|
||||||
ref_: |target| Content::show(structure::RefNode(target)),
|
ref_: |target| structure::RefNode(target).pack(),
|
||||||
heading: |level, body| Content::show(structure::HeadingNode { level, body }),
|
heading: |level, body| structure::HeadingNode { level, body }.pack(),
|
||||||
list_item: |body| Content::Item(structure::ListItem::List(Box::new(body))),
|
list_item: |body| structure::ListItem::List(Box::new(body)).pack(),
|
||||||
enum_item: |number, body| {
|
enum_item: |number, body| {
|
||||||
Content::Item(structure::ListItem::Enum(number, Box::new(body)))
|
structure::ListItem::Enum(number, Box::new(body)).pack()
|
||||||
},
|
},
|
||||||
desc_item: |term, body| {
|
desc_item: |term, body| {
|
||||||
Content::Item(structure::ListItem::Desc(Box::new(structure::DescItem {
|
structure::ListItem::Desc(Box::new(structure::DescItem { term, body })).pack()
|
||||||
term,
|
|
||||||
body,
|
|
||||||
})))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,19 +185,96 @@ pub trait ContentExt {
|
|||||||
|
|
||||||
/// Underline this content.
|
/// Underline this content.
|
||||||
fn underlined(self) -> Self;
|
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 {
|
impl ContentExt for Content {
|
||||||
fn strong(self) -> Self {
|
fn strong(self) -> Self {
|
||||||
Self::show(text::StrongNode(self))
|
text::StrongNode(self).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emph(self) -> Self {
|
fn emph(self) -> Self {
|
||||||
Self::show(text::EmphNode(self))
|
text::EmphNode(self).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn underlined(self) -> Self {
|
fn underlined(self) -> Self {
|
||||||
Self::show(text::DecoNode::<{ text::UNDERLINE }>(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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,44 +296,66 @@ impl StyleMapExt for StyleMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional methods for layout nodes.
|
/// Fill the frames resulting from a node.
|
||||||
pub trait LayoutNodeExt {
|
#[derive(Debug, Hash)]
|
||||||
/// Set alignments for this node.
|
struct FillNode {
|
||||||
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self;
|
/// How to fill the frames resulting from the `child`.
|
||||||
|
fill: Paint,
|
||||||
/// Pad this node at the sides.
|
/// The node whose frames should be filled.
|
||||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self;
|
child: Content,
|
||||||
|
|
||||||
/// Transform this node's contents without affecting layout.
|
|
||||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutNodeExt for LayoutNode {
|
#[node(Layout)]
|
||||||
fn aligned(self, aligns: Axes<Option<RawAlign>>) -> Self {
|
impl FillNode {}
|
||||||
if aligns.any(Option::is_some) {
|
|
||||||
layout::AlignNode { aligns, child: self }.pack()
|
impl Layout for FillNode {
|
||||||
} else {
|
fn layout(
|
||||||
self
|
&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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn padded(self, padding: Sides<Rel<Length>>) -> Self {
|
/// Stroke the frames resulting from a node.
|
||||||
if !padding.left.is_zero()
|
#[derive(Debug, Hash)]
|
||||||
|| !padding.top.is_zero()
|
struct StrokeNode {
|
||||||
|| !padding.right.is_zero()
|
/// How to stroke the frames resulting from the `child`.
|
||||||
|| !padding.bottom.is_zero()
|
stroke: Stroke,
|
||||||
{
|
/// The node whose frames should be stroked.
|
||||||
layout::PadNode { padding, child: self }.pack()
|
child: Content,
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moved(self, delta: Axes<Rel<Length>>) -> Self {
|
#[node(Layout)]
|
||||||
if delta.any(|r| !r.is_zero()) {
|
impl StrokeNode {}
|
||||||
layout::MoveNode { delta, child: self }.pack()
|
|
||||||
} else {
|
impl Layout for StrokeNode {
|
||||||
self
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,17 @@ pub use std::sync::Arc;
|
|||||||
pub use comemo::Tracked;
|
pub use comemo::Tracked;
|
||||||
pub use typst_macros::node;
|
pub use typst_macros::node;
|
||||||
|
|
||||||
pub use super::{ContentExt, LayoutNodeExt, StyleMapExt};
|
pub use super::{ContentExt, StyleMapExt};
|
||||||
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, LayoutNode,
|
Arg, Args, Array, Cast, Content, Dict, Dynamic, Fold, Func, Key, Layout, Level, Node,
|
||||||
Node, RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, ShowNode, Smart,
|
RawAlign, RawStroke, Regions, Resolve, Scope, Selector, Show, Smart, Str, StyleChain,
|
||||||
Str, StyleChain, StyleMap, StyleVec, Value, Vm,
|
StyleMap, 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,4 +1,4 @@
|
|||||||
use crate::library::layout::BlockSpacing;
|
use crate::library::layout::{BlockNode, BlockSpacing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::{FontFamily, TextNode, TextSize};
|
use crate::library::text::{FontFamily, TextNode, TextSize};
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ pub struct HeadingNode {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl HeadingNode {
|
impl HeadingNode {
|
||||||
/// The heading's font family. Just the normal text family if `auto`.
|
/// The heading's font family. Just the normal text family if `auto`.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -61,15 +61,16 @@ impl HeadingNode {
|
|||||||
pub const NUMBERED: bool = true;
|
pub const NUMBERED: bool = true;
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Self {
|
||||||
body: args.expect("body")?,
|
body: args.expect("body")?,
|
||||||
level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()),
|
level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()),
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for HeadingNode {
|
impl Show for HeadingNode {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
Self { body: self.body.unguard(sel), ..*self }.pack()
|
Self { body: self.body.unguard(sel), ..*self }.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ impl Show for HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(Content::block(self.body.clone()))
|
Ok(BlockNode(self.body.clone()).pack())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use unscanny::Scanner;
|
use unscanny::Scanner;
|
||||||
|
|
||||||
use crate::library::layout::{BlockSpacing, GridNode, TrackSizing};
|
use crate::library::layout::{BlockSpacing, GridNode, HNode, TrackSizing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::library::text::ParNode;
|
use crate::library::text::{ParNode, SpaceNode};
|
||||||
use crate::library::utility::Numbering;
|
use crate::library::utility::Numbering;
|
||||||
|
|
||||||
/// An unordered (bulleted) or ordered (numbered) list.
|
/// An unordered (bulleted) or ordered (numbered) list.
|
||||||
@ -22,7 +22,7 @@ pub type EnumNode = ListNode<ENUM>;
|
|||||||
/// A description list.
|
/// A description list.
|
||||||
pub type DescNode = ListNode<DESC>;
|
pub type DescNode = ListNode<DESC>;
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl<const L: ListKind> ListNode<L> {
|
impl<const L: ListKind> ListNode<L> {
|
||||||
/// How the list is labelled.
|
/// How the list is labelled.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -73,16 +73,17 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Content::show(Self {
|
Ok(Self {
|
||||||
tight: args.named("tight")?.unwrap_or(true),
|
tight: args.named("tight")?.unwrap_or(true),
|
||||||
attached: args.named("attached")?.unwrap_or(false),
|
attached: args.named("attached")?.unwrap_or(false),
|
||||||
items,
|
items,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: ListKind> Show for ListNode<L> {
|
impl<const L: ListKind> Show for ListNode<L> {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
Self {
|
Self {
|
||||||
items: self.items.map(|item| item.unguard(sel)),
|
items: self.items.map(|item| item.unguard(sel)),
|
||||||
..*self
|
..*self
|
||||||
@ -123,36 +124,37 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
number = n;
|
number = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
cells.push(LayoutNode::default());
|
cells.push(Content::empty());
|
||||||
|
|
||||||
let label = if L == LIST || L == ENUM {
|
let label = if L == LIST || L == ENUM {
|
||||||
label.resolve(world, L, number)?.styled_with_map(map.clone()).pack()
|
label.resolve(world, L, number)?.styled_with_map(map.clone())
|
||||||
} else {
|
} else {
|
||||||
LayoutNode::default()
|
Content::empty()
|
||||||
};
|
};
|
||||||
|
|
||||||
cells.push(label);
|
cells.push(label);
|
||||||
cells.push(LayoutNode::default());
|
cells.push(Content::empty());
|
||||||
|
|
||||||
let body = match &item {
|
let body = match &item {
|
||||||
ListItem::List(body) => body.as_ref().clone(),
|
ListItem::List(body) => body.as_ref().clone(),
|
||||||
ListItem::Enum(_, body) => body.as_ref().clone(),
|
ListItem::Enum(_, body) => body.as_ref().clone(),
|
||||||
ListItem::Desc(item) => Content::sequence(vec![
|
ListItem::Desc(item) => Content::sequence(vec![
|
||||||
Content::Horizontal {
|
HNode {
|
||||||
amount: (-body_indent).into(),
|
amount: (-body_indent).into(),
|
||||||
weak: false,
|
weak: false,
|
||||||
},
|
}
|
||||||
(item.term.clone() + Content::Text(':'.into())).strong(),
|
.pack(),
|
||||||
Content::Space,
|
(item.term.clone() + TextNode(':'.into()).pack()).strong(),
|
||||||
|
SpaceNode.pack(),
|
||||||
item.body.clone(),
|
item.body.clone(),
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
|
|
||||||
cells.push(body.styled_with_map(map.clone()).pack());
|
cells.push(body.styled_with_map(map.clone()));
|
||||||
number += 1;
|
number += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Content::block(GridNode {
|
Ok(GridNode {
|
||||||
tracks: Axes::with_x(vec![
|
tracks: Axes::with_x(vec![
|
||||||
TrackSizing::Relative(indent.into()),
|
TrackSizing::Relative(indent.into()),
|
||||||
TrackSizing::Auto,
|
TrackSizing::Auto,
|
||||||
@ -161,7 +163,8 @@ impl<const L: ListKind> Show for ListNode<L> {
|
|||||||
]),
|
]),
|
||||||
gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]),
|
gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]),
|
||||||
cells,
|
cells,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
@ -250,6 +253,9 @@ impl Debug for ListItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl ListItem {}
|
||||||
|
|
||||||
/// A description list item.
|
/// A description list item.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct DescItem {
|
pub struct DescItem {
|
||||||
@ -310,14 +316,14 @@ impl Label {
|
|||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Default => match kind {
|
Self::Default => match kind {
|
||||||
LIST => Content::Text('•'.into()),
|
LIST => TextNode('•'.into()).pack(),
|
||||||
ENUM => Content::Text(format_eco!("{}.", number)),
|
ENUM => TextNode(format_eco!("{}.", number)).pack(),
|
||||||
DESC | _ => panic!("description lists don't have a label"),
|
DESC | _ => panic!("description lists don't have a label"),
|
||||||
},
|
},
|
||||||
Self::Pattern(prefix, numbering, upper, suffix) => {
|
Self::Pattern(prefix, numbering, upper, suffix) => {
|
||||||
let fmt = numbering.apply(number);
|
let fmt = numbering.apply(number);
|
||||||
let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() };
|
let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() };
|
||||||
Content::Text(format_eco!("{}{}{}", prefix, mid, suffix))
|
TextNode(format_eco!("{}{}{}", prefix, mid, suffix)).pack()
|
||||||
}
|
}
|
||||||
Self::Content(content) => content.clone(),
|
Self::Content(content) => content.clone(),
|
||||||
Self::Func(func, span) => {
|
Self::Func(func, span) => {
|
||||||
@ -335,7 +341,7 @@ impl Cast<Spanned<Value>> for Label {
|
|||||||
|
|
||||||
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
fn cast(value: Spanned<Value>) -> StrResult<Self> {
|
||||||
match value.v {
|
match value.v {
|
||||||
Value::None => Ok(Self::Content(Content::Empty)),
|
Value::None => Ok(Self::Content(Content::empty())),
|
||||||
Value::Str(pattern) => {
|
Value::Str(pattern) => {
|
||||||
let mut s = Scanner::new(&pattern);
|
let mut s = Scanner::new(&pattern);
|
||||||
let mut prefix;
|
let mut prefix;
|
||||||
|
@ -4,15 +4,15 @@ use crate::library::prelude::*;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct RefNode(pub EcoString);
|
pub struct RefNode(pub EcoString);
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl RefNode {
|
impl RefNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("label")?)))
|
Ok(Self(args.expect("label")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for RefNode {
|
impl Show for RefNode {
|
||||||
fn unguard(&self, _: Selector) -> ShowNode {
|
fn unguard_parts(&self, _: Selector) -> Content {
|
||||||
Self(self.0.clone()).pack()
|
Self(self.0.clone()).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +24,6 @@ impl Show for RefNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(Content::Text(format_eco!("@{}", self.0)))
|
Ok(TextNode(format_eco!("@{}", self.0)).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub struct TableNode {
|
|||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl TableNode {
|
impl TableNode {
|
||||||
/// How to fill the cells.
|
/// How to fill the cells.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -36,19 +36,20 @@ impl TableNode {
|
|||||||
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
let base_gutter: Vec<TrackSizing> = args.named("gutter")?.unwrap_or_default();
|
||||||
let column_gutter = args.named("column-gutter")?;
|
let column_gutter = args.named("column-gutter")?;
|
||||||
let row_gutter = args.named("row-gutter")?;
|
let row_gutter = args.named("row-gutter")?;
|
||||||
Ok(Content::show(Self {
|
Ok(Self {
|
||||||
tracks: Axes::new(columns, rows),
|
tracks: Axes::new(columns, rows),
|
||||||
gutter: Axes::new(
|
gutter: Axes::new(
|
||||||
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
column_gutter.unwrap_or_else(|| base_gutter.clone()),
|
||||||
row_gutter.unwrap_or(base_gutter),
|
row_gutter.unwrap_or(base_gutter),
|
||||||
),
|
),
|
||||||
cells: args.all()?,
|
cells: args.all()?,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for TableNode {
|
impl Show for TableNode {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
Self {
|
Self {
|
||||||
tracks: self.tracks.clone(),
|
tracks: self.tracks.clone(),
|
||||||
gutter: self.gutter.clone(),
|
gutter: self.gutter.clone(),
|
||||||
@ -82,7 +83,7 @@ impl Show for TableNode {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, child)| {
|
.map(|(i, child)| {
|
||||||
let mut child = child.pack().padded(Sides::splat(padding));
|
let mut child = child.padded(Sides::splat(padding));
|
||||||
|
|
||||||
if let Some(stroke) = stroke {
|
if let Some(stroke) = stroke {
|
||||||
child = child.stroked(stroke);
|
child = child.stroked(stroke);
|
||||||
@ -98,11 +99,12 @@ impl Show for TableNode {
|
|||||||
})
|
})
|
||||||
.collect::<SourceResult<_>>()?;
|
.collect::<SourceResult<_>>()?;
|
||||||
|
|
||||||
Ok(Content::block(GridNode {
|
Ok(GridNode {
|
||||||
tracks: self.tracks.clone(),
|
tracks: self.tracks.clone(),
|
||||||
gutter: self.gutter.clone(),
|
gutter: self.gutter.clone(),
|
||||||
cells,
|
cells,
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(
|
fn finalize(
|
||||||
|
@ -17,7 +17,7 @@ pub type StrikethroughNode = DecoNode<STRIKETHROUGH>;
|
|||||||
/// Typeset overlined text.
|
/// Typeset overlined text.
|
||||||
pub type OverlineNode = DecoNode<OVERLINE>;
|
pub type OverlineNode = DecoNode<OVERLINE>;
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl<const L: DecoLine> DecoNode<L> {
|
impl<const L: DecoLine> DecoNode<L> {
|
||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line. The text color and thickness are read from the
|
||||||
/// font tables if `auto`.
|
/// font tables if `auto`.
|
||||||
@ -35,12 +35,12 @@ impl<const L: DecoLine> DecoNode<L> {
|
|||||||
pub const EVADE: bool = true;
|
pub const EVADE: bool = true;
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: DecoLine> Show for DecoNode<L> {
|
impl<const L: DecoLine> Show for DecoNode<L> {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
Self(self.0.unguard(sel)).pack()
|
Self(self.0.unguard(sel)).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ impl LinkNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl LinkNode {
|
impl LinkNode {
|
||||||
/// The fill color of text in the link. Just the surrounding text color
|
/// The fill color of text in the link. Just the surrounding text color
|
||||||
/// if `auto`.
|
/// if `auto`.
|
||||||
@ -26,14 +26,12 @@ impl LinkNode {
|
|||||||
pub const UNDERLINE: Smart<bool> = Smart::Auto;
|
pub const UNDERLINE: Smart<bool> = Smart::Auto;
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::show({
|
|
||||||
let dest = args.expect::<Destination>("destination")?;
|
let dest = args.expect::<Destination>("destination")?;
|
||||||
let body = match dest {
|
let body = match dest {
|
||||||
Destination::Url(_) => args.eat()?,
|
Destination::Url(_) => args.eat()?,
|
||||||
Destination::Internal(_) => Some(args.expect("body")?),
|
Destination::Internal(_) => Some(args.expect("body")?),
|
||||||
};
|
};
|
||||||
Self { dest, body }
|
Ok(Self { dest, body }.pack())
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +48,7 @@ castable! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for LinkNode {
|
impl Show for LinkNode {
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
Self {
|
Self {
|
||||||
dest: self.dest.clone(),
|
dest: self.dest.clone(),
|
||||||
body: self.body.as_ref().map(|body| body.unguard(sel)),
|
body: self.body.as_ref().map(|body| body.unguard(sel)),
|
||||||
@ -83,9 +81,9 @@ impl Show for LinkNode {
|
|||||||
text = text.trim_start_matches(prefix);
|
text = text.trim_start_matches(prefix);
|
||||||
}
|
}
|
||||||
let shorter = text.len() < url.len();
|
let shorter = text.len() < url.len();
|
||||||
Content::Text(if shorter { text.into() } else { url.clone() })
|
TextNode(if shorter { text.into() } else { url.clone() }).pack()
|
||||||
}
|
}
|
||||||
Destination::Internal(_) => Content::Empty,
|
Destination::Internal(_) => Content::empty(),
|
||||||
})
|
})
|
||||||
.styled(TextNode::LINK, Some(self.dest.clone())))
|
.styled(TextNode::LINK, Some(self.dest.clone())))
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ mod link;
|
|||||||
mod par;
|
mod par;
|
||||||
mod quotes;
|
mod quotes;
|
||||||
mod raw;
|
mod raw;
|
||||||
mod repeat;
|
|
||||||
mod shaping;
|
mod shaping;
|
||||||
mod shift;
|
mod shift;
|
||||||
|
|
||||||
@ -14,7 +13,6 @@ pub use link::*;
|
|||||||
pub use par::*;
|
pub use par::*;
|
||||||
pub use quotes::*;
|
pub use quotes::*;
|
||||||
pub use raw::*;
|
pub use raw::*;
|
||||||
pub use repeat::*;
|
|
||||||
pub use shaping::*;
|
pub use shaping::*;
|
||||||
pub use shift::*;
|
pub use shift::*;
|
||||||
|
|
||||||
@ -27,8 +25,8 @@ use crate::library::prelude::*;
|
|||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// A single run of text with the same style.
|
/// A single run of text with the same style.
|
||||||
#[derive(Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct TextNode;
|
pub struct TextNode(pub EcoString);
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl TextNode {
|
impl TextNode {
|
||||||
@ -142,7 +140,7 @@ impl TextNode {
|
|||||||
for item in args.items.iter().filter(|item| item.name.is_none()) {
|
for item in args.items.iter().filter(|item| item.name.is_none()) {
|
||||||
if EcoString::is(&item.value) {
|
if EcoString::is(&item.value) {
|
||||||
count += 1;
|
count += 1;
|
||||||
} else if Content::is(&item.value) {
|
} else if <Content as Cast<Spanned<Value>>>::is(&item.value) {
|
||||||
content = true;
|
content = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,6 +431,45 @@ impl Fold for Vec<(Tag, u32)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A text space.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct SpaceNode;
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl SpaceNode {
|
||||||
|
fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
|
||||||
|
Ok(Self.pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A line break.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct LinebreakNode {
|
||||||
|
pub justify: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl LinebreakNode {
|
||||||
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
let justify = args.named("justify")?.unwrap_or(false);
|
||||||
|
Ok(Self { justify }.pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A smart quote.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
pub struct SmartQuoteNode {
|
||||||
|
pub double: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl SmartQuoteNode {
|
||||||
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
let double = args.named("double")?.unwrap_or(true);
|
||||||
|
Ok(Self { double }.pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert a string or content to lowercase.
|
/// Convert a string or content to lowercase.
|
||||||
pub fn lower(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
|
pub fn lower(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
|
||||||
case(Case::Lower, args)
|
case(Case::Lower, args)
|
||||||
@ -478,6 +515,62 @@ pub fn smallcaps(_: &mut Vm, args: &mut Args) -> SourceResult<Value> {
|
|||||||
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
|
Ok(Value::Content(body.styled(TextNode::SMALLCAPS, true)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Strong text, rendered in boldface by default.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct StrongNode(pub Content);
|
||||||
|
|
||||||
|
#[node(Show)]
|
||||||
|
impl StrongNode {
|
||||||
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
Ok(Self(args.expect("body")?).pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for StrongNode {
|
||||||
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
|
Self(self.0.unguard(sel)).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field(&self, name: &str) -> Option<Value> {
|
||||||
|
match name {
|
||||||
|
"body" => Some(Value::Content(self.0.clone())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emphasized text, rendered with an italic font by default.
|
||||||
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct EmphNode(pub Content);
|
||||||
|
|
||||||
|
#[node(Show)]
|
||||||
|
impl EmphNode {
|
||||||
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
|
Ok(Self(args.expect("body")?).pack())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Show for EmphNode {
|
||||||
|
fn unguard_parts(&self, sel: Selector) -> Content {
|
||||||
|
Self(self.0.unguard(sel)).pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn field(&self, name: &str) -> Option<Value> {
|
||||||
|
match name {
|
||||||
|
"body" => Some(Value::Content(self.0.clone())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||||
|
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A toggle that turns on and off alternatingly if folded.
|
/// A toggle that turns on and off alternatingly if folded.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Toggle;
|
pub struct Toggle;
|
||||||
@ -498,59 +591,3 @@ impl Fold for Decoration {
|
|||||||
outer
|
outer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Strong text, rendered in boldface by default.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct StrongNode(pub Content);
|
|
||||||
|
|
||||||
#[node(showable)]
|
|
||||||
impl StrongNode {
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for StrongNode {
|
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
|
||||||
Self(self.0.unguard(sel)).pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emphasized text, rendered with an italic font by default.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct EmphNode(pub Content);
|
|
||||||
|
|
||||||
#[node(showable)]
|
|
||||||
impl EmphNode {
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for EmphNode {
|
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
|
||||||
Self(self.0.unguard(sel)).pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
match name {
|
|
||||||
"body" => Some(Value::Content(self.0.clone())),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn realize(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use unicode_bidi::{BidiInfo, Level};
|
use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||||
use unicode_script::{Script, UnicodeScript};
|
use unicode_script::{Script, UnicodeScript};
|
||||||
use xi_unicode::LineBreakIterator;
|
use xi_unicode::LineBreakIterator;
|
||||||
|
|
||||||
use super::{shape, Lang, Quoter, Quotes, RepeatNode, ShapedText, TextNode};
|
use super::{shape, Lang, Quoter, Quotes, ShapedText, TextNode};
|
||||||
use crate::library::layout::Spacing;
|
use crate::library::layout::Spacing;
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -23,10 +23,10 @@ pub enum ParChild {
|
|||||||
/// Horizontal spacing between other children.
|
/// Horizontal spacing between other children.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level node.
|
/// An arbitrary inline-level node.
|
||||||
Node(LayoutNode),
|
Node(Content),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
impl ParNode {
|
impl ParNode {
|
||||||
/// The spacing between lines.
|
/// The spacing between lines.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -54,9 +54,9 @@ impl ParNode {
|
|||||||
// node. Instead, it just ensures that the passed content lives is in a
|
// node. Instead, it just ensures that the passed content lives is in a
|
||||||
// separate paragraph and styles it.
|
// separate paragraph and styles it.
|
||||||
Ok(Content::sequence(vec![
|
Ok(Content::sequence(vec![
|
||||||
Content::Parbreak,
|
ParbreakNode.pack(),
|
||||||
args.expect("body")?,
|
args.expect("body")?,
|
||||||
Content::Parbreak,
|
ParbreakNode.pack(),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,6 +82,10 @@ 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 {
|
||||||
@ -166,23 +170,39 @@ impl Resolve for Smart<Linebreaks> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A paragraph break.
|
/// A paragraph break.
|
||||||
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct ParbreakNode;
|
pub struct ParbreakNode;
|
||||||
|
|
||||||
#[node]
|
#[node]
|
||||||
impl ParbreakNode {
|
impl ParbreakNode {
|
||||||
fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, _: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::Parbreak)
|
Ok(Self.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A line break.
|
/// A node that should be repeated to fill up a line.
|
||||||
pub struct LinebreakNode;
|
#[derive(Debug, Hash)]
|
||||||
|
pub struct RepeatNode(pub Content);
|
||||||
|
|
||||||
#[node]
|
#[node(Layout)]
|
||||||
impl LinebreakNode {
|
impl RepeatNode {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let justify = args.named("justify")?.unwrap_or(false);
|
Ok(Self(args.expect("body")?).pack())
|
||||||
Ok(Content::Linebreak { justify })
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout for RepeatNode {
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
world: Tracked<dyn World>,
|
||||||
|
regions: &Regions,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Vec<Frame>> {
|
||||||
|
self.0.layout_inline(world, regions, styles)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn level(&self) -> Level {
|
||||||
|
Level::Inline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +292,7 @@ enum Segment<'a> {
|
|||||||
/// Horizontal spacing between other segments.
|
/// Horizontal spacing between other segments.
|
||||||
Spacing(Spacing),
|
Spacing(Spacing),
|
||||||
/// An arbitrary inline-level layout node.
|
/// An arbitrary inline-level layout node.
|
||||||
Node(&'a LayoutNode),
|
Node(&'a Content),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Segment<'_> {
|
impl Segment<'_> {
|
||||||
@ -504,8 +524,8 @@ fn prepare<'a>(
|
|||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<Preparation<'a>> {
|
) -> SourceResult<Preparation<'a>> {
|
||||||
let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) {
|
let bidi = BidiInfo::new(&text, match styles.get(TextNode::DIR) {
|
||||||
Dir::LTR => Some(Level::ltr()),
|
Dir::LTR => Some(BidiLevel::ltr()),
|
||||||
Dir::RTL => Some(Level::rtl()),
|
Dir::RTL => Some(BidiLevel::rtl()),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -529,12 +549,12 @@ fn prepare<'a>(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Segment::Node(node) => {
|
Segment::Node(node) => {
|
||||||
if let Some(repeat) = node.downcast() {
|
if let Some(repeat) = node.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(world, &pod, styles)?.remove(0);
|
let mut frame = node.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));
|
||||||
@ -566,13 +586,13 @@ fn shape_range<'a>(
|
|||||||
range: Range,
|
range: Range,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) {
|
) {
|
||||||
let mut process = |text, level: Level| {
|
let mut process = |text, level: BidiLevel| {
|
||||||
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
let dir = if level.is_ltr() { Dir::LTR } else { Dir::RTL };
|
||||||
let shaped = shape(world, text, styles, dir);
|
let shaped = shape(world, text, styles, dir);
|
||||||
items.push(Item::Text(shaped));
|
items.push(Item::Text(shaped));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut prev_level = Level::ltr();
|
let mut prev_level = BidiLevel::ltr();
|
||||||
let mut prev_script = Script::Unknown;
|
let mut prev_script = Script::Unknown;
|
||||||
let mut cursor = range.start;
|
let mut cursor = range.start;
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ use syntect::highlighting::{
|
|||||||
};
|
};
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
|
|
||||||
use super::{FontFamily, Hyphenate, TextNode};
|
use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
|
||||||
use crate::library::layout::BlockSpacing;
|
use crate::library::layout::{BlockNode, BlockSpacing};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
|
||||||
/// Monospaced text with optional syntax highlighting.
|
/// Monospaced text with optional syntax highlighting.
|
||||||
@ -18,7 +18,7 @@ pub struct RawNode {
|
|||||||
pub block: bool,
|
pub block: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(showable)]
|
#[node(Show)]
|
||||||
impl RawNode {
|
impl RawNode {
|
||||||
/// The language to syntax-highlight in.
|
/// The language to syntax-highlight in.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -34,15 +34,16 @@ impl RawNode {
|
|||||||
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::show(Self {
|
Ok(Self {
|
||||||
text: args.expect("text")?,
|
text: args.expect("text")?,
|
||||||
block: args.named("block")?.unwrap_or(false),
|
block: args.named("block")?.unwrap_or(false),
|
||||||
}))
|
}
|
||||||
|
.pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for RawNode {
|
impl Show for RawNode {
|
||||||
fn unguard(&self, _: Selector) -> ShowNode {
|
fn unguard_parts(&self, _: Selector) -> Content {
|
||||||
Self { text: self.text.clone(), ..*self }.pack()
|
Self { text: self.text.clone(), ..*self }.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +87,7 @@ impl Show for RawNode {
|
|||||||
let mut highlighter = HighlightLines::new(syntax, &THEME);
|
let mut highlighter = HighlightLines::new(syntax, &THEME);
|
||||||
for (i, line) in self.text.lines().enumerate() {
|
for (i, line) in self.text.lines().enumerate() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
seq.push(Content::Linebreak { justify: false });
|
seq.push(LinebreakNode { justify: false }.pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (style, piece) in
|
for (style, piece) in
|
||||||
@ -98,11 +99,11 @@ impl Show for RawNode {
|
|||||||
|
|
||||||
Content::sequence(seq)
|
Content::sequence(seq)
|
||||||
} else {
|
} else {
|
||||||
Content::Text(self.text.clone())
|
TextNode(self.text.clone()).pack()
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.block {
|
if self.block {
|
||||||
realized = Content::block(realized);
|
realized = BlockNode(realized).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
@ -132,7 +133,7 @@ impl Show for RawNode {
|
|||||||
|
|
||||||
/// Style a piece of text with a syntect style.
|
/// Style a piece of text with a syntect style.
|
||||||
fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
|
fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
|
||||||
let mut body = Content::Text(piece.into());
|
let mut body = TextNode(piece.into()).pack();
|
||||||
|
|
||||||
let paint = style.foreground.into();
|
let paint = style.foreground.into();
|
||||||
if paint != foreground {
|
if paint != foreground {
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
use crate::library::prelude::*;
|
|
||||||
|
|
||||||
/// A node that should be repeated to fill up a line.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
pub struct RepeatNode(pub LayoutNode);
|
|
||||||
|
|
||||||
#[node]
|
|
||||||
impl RepeatNode {
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
Ok(Content::inline(Self(args.expect("body")?)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for RepeatNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
// The actual repeating happens directly in the paragraph.
|
|
||||||
self.0.layout(world, regions, styles)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
use super::{variant, TextNode, TextSize};
|
use super::{variant, SpaceNode, TextNode, TextSize};
|
||||||
use crate::library::prelude::*;
|
use crate::library::prelude::*;
|
||||||
|
use crate::model::SequenceNode;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// Sub or superscript text.
|
/// Sub or superscript text.
|
||||||
@ -17,7 +18,7 @@ pub type SuperNode = ShiftNode<SUPERSCRIPT>;
|
|||||||
/// Shift the text into subscript.
|
/// Shift the text into subscript.
|
||||||
pub type SubNode = ShiftNode<SUBSCRIPT>;
|
pub type SubNode = ShiftNode<SUBSCRIPT>;
|
||||||
|
|
||||||
#[node]
|
#[node(Show)]
|
||||||
impl<const S: ScriptKind> ShiftNode<S> {
|
impl<const S: ScriptKind> ShiftNode<S> {
|
||||||
/// Whether to prefer the dedicated sub- and superscript characters of the
|
/// Whether to prefer the dedicated sub- and superscript characters of the
|
||||||
/// font.
|
/// font.
|
||||||
@ -29,12 +30,12 @@ impl<const S: ScriptKind> ShiftNode<S> {
|
|||||||
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
pub const SIZE: TextSize = TextSize(Em::new(0.6).into());
|
||||||
|
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Content::show(Self(args.expect("body")?)))
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const S: ScriptKind> Show for ShiftNode<S> {
|
impl<const S: ScriptKind> Show for ShiftNode<S> {
|
||||||
fn unguard(&self, _: Selector) -> ShowNode {
|
fn unguard_parts(&self, _: Selector) -> Content {
|
||||||
Self(self.0.clone()).pack()
|
Self(self.0.clone()).pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ impl<const S: ScriptKind> Show for ShiftNode<S> {
|
|||||||
if styles.get(Self::TYPOGRAPHIC) {
|
if styles.get(Self::TYPOGRAPHIC) {
|
||||||
if let Some(text) = search_text(&self.0, S) {
|
if let Some(text) = search_text(&self.0, S) {
|
||||||
if is_shapable(world, &text, styles) {
|
if is_shapable(world, &text, styles) {
|
||||||
transformed = Some(Content::Text(text));
|
transformed = Some(TextNode(text).pack());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -71,28 +72,26 @@ impl<const S: ScriptKind> Show for ShiftNode<S> {
|
|||||||
/// Find and transform the text contained in `content` to the given script kind
|
/// Find and transform the text contained in `content` to the given script kind
|
||||||
/// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes.
|
/// if and only if it only consists of `Text`, `Space`, and `Empty` leaf nodes.
|
||||||
fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
|
fn search_text(content: &Content, mode: ScriptKind) -> Option<EcoString> {
|
||||||
match content {
|
if content.is_empty() {
|
||||||
Content::Text(_) => {
|
Some(EcoString::new())
|
||||||
if let Content::Text(t) = content {
|
} else if content.is::<SpaceNode>() {
|
||||||
if let Some(sup) = convert_script(t, mode) {
|
Some(' '.into())
|
||||||
|
} else if let Some(text) = content.downcast::<TextNode>() {
|
||||||
|
if let Some(sup) = convert_script(&text.0, mode) {
|
||||||
return Some(sup);
|
return Some(sup);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
None
|
None
|
||||||
}
|
} else if let Some(seq) = content.downcast::<SequenceNode>() {
|
||||||
Content::Space => Some(' '.into()),
|
|
||||||
Content::Empty => Some(EcoString::new()),
|
|
||||||
Content::Sequence(seq) => {
|
|
||||||
let mut full = EcoString::new();
|
let mut full = EcoString::new();
|
||||||
for item in seq.iter() {
|
for item in seq.0.iter() {
|
||||||
match search_text(item, mode) {
|
match search_text(item, mode) {
|
||||||
Some(text) => full.push_str(&text),
|
Some(text) => full.push_str(&text),
|
||||||
None => return None,
|
None => return None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(full)
|
Some(full)
|
||||||
}
|
} else {
|
||||||
_ => None,
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use super::{Content, Layout, LayoutNode, Pattern, Regex, Value};
|
use super::{Pattern, Regex, Value};
|
||||||
use crate::diag::{with_alternative, StrResult};
|
use crate::diag::{with_alternative, StrResult};
|
||||||
use crate::geom::{Corners, Dir, Paint, Sides};
|
use crate::geom::{Corners, Dir, Paint, Sides};
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
@ -170,14 +170,6 @@ castable! {
|
|||||||
Value::Str(string) => string.into(),
|
Value::Str(string) => string.into(),
|
||||||
}
|
}
|
||||||
|
|
||||||
castable! {
|
|
||||||
LayoutNode,
|
|
||||||
Expected: "content",
|
|
||||||
Value::None => Self::default(),
|
|
||||||
Value::Str(text) => Content::Text(text.into()).pack(),
|
|
||||||
Value::Content(content) => content.pack(),
|
|
||||||
}
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Pattern,
|
Pattern,
|
||||||
Expected: "function, string or regular expression",
|
Expected: "function, string or regular expression",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use super::{StyleChain, StyleVec, StyleVecBuilder};
|
use super::{StyleChain, StyleVec, StyleVecBuilder};
|
||||||
|
|
||||||
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
|
/// A wrapper around a [`StyleVecBuilder`] that allows to collapse items.
|
||||||
pub struct CollapsingBuilder<'a, T> {
|
pub(super) struct CollapsingBuilder<'a, T> {
|
||||||
/// The internal builder.
|
/// The internal builder.
|
||||||
builder: StyleVecBuilder<'a, T>,
|
builder: StyleVecBuilder<'a, T>,
|
||||||
/// Staged weak and ignorant items that we can't yet commit to the builder.
|
/// Staged weak and ignorant items that we can't yet commit to the builder.
|
||||||
|
@ -1,124 +1,84 @@
|
|||||||
|
use std::any::{Any, TypeId};
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::Sum;
|
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 comemo::Tracked;
|
||||||
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
|
use typst_macros::node;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Builder, Key, Layout, LayoutNode, Property, Regions, Scratch, Selector, Show,
|
Args, Barrier, Builder, Key, Layout, Level, Property, Regions, Scratch, Selector,
|
||||||
ShowNode, StyleChain, StyleEntry, StyleMap,
|
StyleChain, StyleEntry, StyleMap, Vm,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::frame::Frame;
|
use crate::frame::Frame;
|
||||||
use crate::geom::Abs;
|
use crate::util::ReadableTypeId;
|
||||||
use crate::library::layout::{PageNode, Spacing};
|
|
||||||
use crate::library::structure::ListItem;
|
|
||||||
use crate::util::EcoString;
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Composable representation of styled content.
|
/// Composable representation of styled content.
|
||||||
///
|
///
|
||||||
/// This results from:
|
/// This results from:
|
||||||
/// - anything written between square brackets in Typst
|
/// - anything written between square brackets in Typst
|
||||||
/// - any node constructor
|
/// - any constructor function
|
||||||
///
|
#[derive(Clone, Hash)]
|
||||||
/// Content is represented as a tree of nodes. There are two nodes of special
|
pub struct Content(Arc<dyn Bounds>);
|
||||||
/// interest:
|
|
||||||
///
|
|
||||||
/// 1. A `Styled` node attaches a style map to other content. For example, a
|
|
||||||
/// single bold word could be represented as a `Styled(Text("Hello"),
|
|
||||||
/// [TextNode::STRONG: true])` node.
|
|
||||||
///
|
|
||||||
/// 2. A `Sequence` node content combines other arbitrary content and is the
|
|
||||||
/// representation of a "flow" of other nodes. So, when you write `[Hi] +
|
|
||||||
/// [you]` in Typst, this type's [`Add`] implementation is invoked and the
|
|
||||||
/// two [`Text`](Self::Text) nodes are combined into a single
|
|
||||||
/// [`Sequence`](Self::Sequence) node. A sequence may contain nested
|
|
||||||
/// sequences.
|
|
||||||
#[derive(PartialEq, Clone, Hash)]
|
|
||||||
pub enum Content {
|
|
||||||
/// Empty content.
|
|
||||||
Empty,
|
|
||||||
/// A word space.
|
|
||||||
Space,
|
|
||||||
/// A forced line break.
|
|
||||||
Linebreak { justify: bool },
|
|
||||||
/// Horizontal spacing.
|
|
||||||
Horizontal { amount: Spacing, weak: bool },
|
|
||||||
/// Plain text.
|
|
||||||
Text(EcoString),
|
|
||||||
/// A smart quote.
|
|
||||||
Quote { double: bool },
|
|
||||||
/// An inline-level node.
|
|
||||||
Inline(LayoutNode),
|
|
||||||
/// A paragraph break.
|
|
||||||
Parbreak,
|
|
||||||
/// A column break.
|
|
||||||
Colbreak { weak: bool },
|
|
||||||
/// Vertical spacing.
|
|
||||||
Vertical {
|
|
||||||
amount: Spacing,
|
|
||||||
weak: bool,
|
|
||||||
generated: bool,
|
|
||||||
},
|
|
||||||
/// A block-level node.
|
|
||||||
Block(LayoutNode),
|
|
||||||
/// A list / enum item.
|
|
||||||
Item(ListItem),
|
|
||||||
/// A page break.
|
|
||||||
Pagebreak { weak: bool },
|
|
||||||
/// A page node.
|
|
||||||
Page(PageNode),
|
|
||||||
/// A node that can be realized with styles, optionally with attached
|
|
||||||
/// properties.
|
|
||||||
Show(ShowNode),
|
|
||||||
/// Content with attached styles.
|
|
||||||
Styled(Arc<(Self, StyleMap)>),
|
|
||||||
/// A sequence of multiple nodes.
|
|
||||||
Sequence(Arc<Vec<Self>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Content {
|
impl Content {
|
||||||
/// Create empty content.
|
/// Create empty content.
|
||||||
pub fn new() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self::Empty
|
SequenceNode(vec![]).pack()
|
||||||
}
|
|
||||||
|
|
||||||
/// Create content from an inline-level node.
|
|
||||||
pub fn inline<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self::Inline(node.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create content from a block-level node.
|
|
||||||
pub fn block<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self::Block(node.pack())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create content from a showable node.
|
|
||||||
pub fn show<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Show + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self::Show(node.pack())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new sequence node from multiples nodes.
|
/// Create a new sequence node from multiples nodes.
|
||||||
pub fn sequence(seq: Vec<Self>) -> Self {
|
pub fn sequence(seq: Vec<Self>) -> Self {
|
||||||
match seq.as_slice() {
|
match seq.as_slice() {
|
||||||
[] => Self::Empty,
|
|
||||||
[_] => seq.into_iter().next().unwrap(),
|
[_] => seq.into_iter().next().unwrap(),
|
||||||
_ => Self::Sequence(Arc::new(seq)),
|
_ => SequenceNode(seq).pack(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.downcast::<SequenceNode>().map_or(false, |seq| seq.0.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(&self) -> NodeId {
|
||||||
|
(*self.0).id()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is<T: 'static>(&self) -> bool {
|
||||||
|
(*self.0).as_any().is::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||||
|
(*self.0).as_any().downcast_ref::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
|
||||||
|
Arc::get_mut(&mut self.0)?.as_any_mut().downcast_mut::<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this content has the given capability.
|
||||||
|
pub fn has<C>(&self) -> bool
|
||||||
|
where
|
||||||
|
C: Capability + ?Sized,
|
||||||
|
{
|
||||||
|
self.0.vtable(TypeId::of::<C>()).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast to a trait object if this content has the given capability.
|
||||||
|
pub fn to<C>(&self) -> Option<&C>
|
||||||
|
where
|
||||||
|
C: Capability + ?Sized,
|
||||||
|
{
|
||||||
|
let node: &dyn Bounds = &*self.0;
|
||||||
|
let vtable = node.vtable(TypeId::of::<C>())?;
|
||||||
|
let data = node as *const dyn Bounds as *const ();
|
||||||
|
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
||||||
|
}
|
||||||
|
|
||||||
/// Repeat this content `n` times.
|
/// Repeat this content `n` times.
|
||||||
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
pub fn repeat(&self, n: i64) -> StrResult<Self> {
|
||||||
let count = usize::try_from(n)
|
let count = usize::try_from(n)
|
||||||
@ -134,14 +94,12 @@ impl Content {
|
|||||||
|
|
||||||
/// Style this content with a style entry.
|
/// Style this content with a style entry.
|
||||||
pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self {
|
pub fn styled_with_entry(mut self, entry: StyleEntry) -> Self {
|
||||||
if let Self::Styled(styled) = &mut self {
|
if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
|
||||||
if let Some((_, map)) = Arc::get_mut(styled) {
|
styled.map.apply(entry);
|
||||||
map.apply(entry);
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Self::Styled(Arc::new((self, entry.into())))
|
StyledNode { sub: self, map: entry.into() }.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style this content with a full style map.
|
/// Style this content with a full style map.
|
||||||
@ -150,14 +108,12 @@ impl Content {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Self::Styled(styled) = &mut self {
|
if let Some(styled) = self.try_downcast_mut::<StyledNode>() {
|
||||||
if let Some((_, map)) = Arc::get_mut(styled) {
|
styled.map.apply_map(&styles);
|
||||||
map.apply_map(&styles);
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Self::Styled(Arc::new((self, styles)))
|
StyledNode { sub: self, map: styles }.pack()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reenable the show rule identified by the selector.
|
/// Reenable the show rule identified by the selector.
|
||||||
@ -165,41 +121,22 @@ impl Content {
|
|||||||
self.clone().styled_with_entry(StyleEntry::Unguard(sel))
|
self.clone().styled_with_entry(StyleEntry::Unguard(sel))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add weak vertical spacing above and below the node.
|
#[comemo::memoize]
|
||||||
pub fn spaced(self, above: Option<Abs>, below: Option<Abs>) -> Self {
|
pub fn layout_block(
|
||||||
if above.is_none() && below.is_none() {
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut seq = vec![];
|
|
||||||
if let Some(above) = above {
|
|
||||||
seq.push(Content::Vertical {
|
|
||||||
amount: above.into(),
|
|
||||||
weak: true,
|
|
||||||
generated: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
seq.push(self);
|
|
||||||
if let Some(below) = below {
|
|
||||||
seq.push(Content::Vertical {
|
|
||||||
amount: below.into(),
|
|
||||||
weak: true,
|
|
||||||
generated: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Self::sequence(seq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for Content {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> 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 scratch = Scratch::default();
|
||||||
let mut builder = Builder::new(world, &scratch, false);
|
let mut builder = Builder::new(world, &scratch, false);
|
||||||
builder.accept(self, styles)?;
|
builder.accept(self, styles)?;
|
||||||
@ -207,77 +144,74 @@ impl Layout for Content {
|
|||||||
flow.layout(world, regions, shared)
|
flow.layout(world, regions, shared)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
|
||||||
match self {
|
#[comemo::memoize]
|
||||||
Content::Block(node) => node,
|
pub fn layout_inline(
|
||||||
other => LayoutNode::new(other),
|
&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 {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Content {
|
impl Debug for Content {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
self.0.fmt(f)
|
||||||
Self::Empty => f.pad("Empty"),
|
|
||||||
Self::Space => f.pad("Space"),
|
|
||||||
Self::Linebreak { justify } => write!(f, "Linebreak({justify})"),
|
|
||||||
Self::Horizontal { amount, weak } => {
|
|
||||||
write!(f, "Horizontal({amount:?}, {weak})")
|
|
||||||
}
|
}
|
||||||
Self::Text(text) => write!(f, "Text({text:?})"),
|
|
||||||
Self::Quote { double } => write!(f, "Quote({double})"),
|
|
||||||
Self::Inline(node) => node.fmt(f),
|
|
||||||
Self::Parbreak => f.pad("Parbreak"),
|
|
||||||
Self::Colbreak { weak } => write!(f, "Colbreak({weak})"),
|
|
||||||
Self::Vertical { amount, weak, generated } => {
|
|
||||||
write!(f, "Vertical({amount:?}, {weak}, {generated})")
|
|
||||||
}
|
|
||||||
Self::Block(node) => node.fmt(f),
|
|
||||||
Self::Item(item) => item.fmt(f),
|
|
||||||
Self::Pagebreak { weak } => write!(f, "Pagebreak({weak})"),
|
|
||||||
Self::Page(page) => page.fmt(f),
|
|
||||||
Self::Show(node) => node.fmt(f),
|
|
||||||
Self::Styled(styled) => {
|
|
||||||
let (sub, map) = styled.as_ref();
|
|
||||||
map.fmt(f)?;
|
|
||||||
sub.fmt(f)
|
|
||||||
}
|
|
||||||
Self::Sequence(seq) => f.debug_list().entries(seq.iter()).finish(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Content {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
(*self.0).hash128() == (*other.0).hash128()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for Content {
|
impl Add for Content {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn add(self, rhs: Self) -> Self::Output {
|
fn add(self, mut rhs: Self) -> Self::Output {
|
||||||
Self::Sequence(match (self, rhs) {
|
let mut lhs = self;
|
||||||
(Self::Empty, rhs) => return rhs,
|
if let Some(lhs_mut) = lhs.try_downcast_mut::<SequenceNode>() {
|
||||||
(lhs, Self::Empty) => return lhs,
|
if let Some(rhs_mut) = rhs.try_downcast_mut::<SequenceNode>() {
|
||||||
(Self::Sequence(mut lhs), Self::Sequence(rhs)) => {
|
lhs_mut.0.extend(rhs_mut.0.drain(..));
|
||||||
let mutable = Arc::make_mut(&mut lhs);
|
} else if let Some(rhs) = rhs.downcast::<SequenceNode>() {
|
||||||
match Arc::try_unwrap(rhs) {
|
lhs_mut.0.extend(rhs.0.iter().cloned());
|
||||||
Ok(vec) => mutable.extend(vec),
|
} else {
|
||||||
Err(rc) => mutable.extend(rc.iter().cloned()),
|
lhs_mut.0.push(rhs);
|
||||||
}
|
}
|
||||||
lhs
|
return lhs;
|
||||||
}
|
}
|
||||||
(Self::Sequence(mut lhs), rhs) => {
|
|
||||||
Arc::make_mut(&mut lhs).push(rhs);
|
let seq = match (
|
||||||
lhs
|
lhs.downcast::<SequenceNode>(),
|
||||||
}
|
rhs.downcast::<SequenceNode>(),
|
||||||
(lhs, Self::Sequence(mut rhs)) => {
|
) {
|
||||||
Arc::make_mut(&mut rhs).insert(0, lhs);
|
(Some(lhs), Some(rhs)) => lhs.0.iter().chain(&rhs.0).cloned().collect(),
|
||||||
rhs
|
(Some(lhs), None) => lhs.0.iter().cloned().chain(iter::once(rhs)).collect(),
|
||||||
}
|
(None, Some(rhs)) => iter::once(lhs).chain(rhs.0.iter().cloned()).collect(),
|
||||||
(lhs, rhs) => Arc::new(vec![lhs, rhs]),
|
(None, None) => vec![lhs, rhs],
|
||||||
})
|
};
|
||||||
|
|
||||||
|
SequenceNode(seq).pack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,3 +226,124 @@ impl Sum for Content {
|
|||||||
Self::sequence(iter.collect())
|
Self::sequence(iter.collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait Bounds: Node + Debug + Sync + Send + 'static {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
|
fn hash128(&self) -> u128;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Bounds for T
|
||||||
|
where
|
||||||
|
T: Node + Debug + Hash + Sync + Send + 'static,
|
||||||
|
{
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash128(&self) -> u128 {
|
||||||
|
let mut state = SipHasher::new();
|
||||||
|
self.type_id().hash(&mut state);
|
||||||
|
self.hash(&mut state);
|
||||||
|
state.finish128().as_u128()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for dyn Bounds {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write_u128(self.hash128());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A constructable, stylable content node.
|
||||||
|
pub trait Node: 'static {
|
||||||
|
/// Pack into type-erased content.
|
||||||
|
fn pack(self) -> Content
|
||||||
|
where
|
||||||
|
Self: Debug + Hash + Sync + Send + Sized + 'static,
|
||||||
|
{
|
||||||
|
Content(Arc::new(self))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a node from the arguments.
|
||||||
|
///
|
||||||
|
/// This is passed only the arguments that remain after execution of the
|
||||||
|
/// node's set rule.
|
||||||
|
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
/// Parse relevant arguments into style properties for this node.
|
||||||
|
///
|
||||||
|
/// When `constructor` is true, [`construct`](Self::construct) will run
|
||||||
|
/// after this invocation of `set` with the remaining arguments.
|
||||||
|
fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
|
||||||
|
/// A unique identifier of the node type.
|
||||||
|
fn id(&self) -> NodeId;
|
||||||
|
|
||||||
|
/// Extract the pointer of the vtable of the trait object with the
|
||||||
|
/// given type `id` if this node implements that trait.
|
||||||
|
fn vtable(&self, id: TypeId) -> Option<*const ()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A unique identifier for a node.
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct NodeId(ReadableTypeId);
|
||||||
|
|
||||||
|
impl NodeId {
|
||||||
|
/// The id of the given node.
|
||||||
|
pub fn of<T: 'static>() -> Self {
|
||||||
|
Self(ReadableTypeId::of::<T>())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for NodeId {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A capability a node can have.
|
||||||
|
///
|
||||||
|
/// This is implemented by trait objects.
|
||||||
|
pub trait Capability: 'static + Send + Sync {}
|
||||||
|
|
||||||
|
/// A node with applied styles.
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
|
pub struct StyledNode {
|
||||||
|
pub sub: Content,
|
||||||
|
pub map: StyleMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl StyledNode {}
|
||||||
|
|
||||||
|
impl Debug for StyledNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.map.fmt(f)?;
|
||||||
|
self.sub.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sequence of nodes.
|
||||||
|
///
|
||||||
|
/// Combines other arbitrary content. So, when you write `[Hi] + [you]` in
|
||||||
|
/// Typst, the two text nodes are combined into a single sequence node.
|
||||||
|
#[derive(Clone, Hash)]
|
||||||
|
pub struct SequenceNode(pub Vec<Content>);
|
||||||
|
|
||||||
|
#[node]
|
||||||
|
impl SequenceNode {}
|
||||||
|
|
||||||
|
impl Debug for SequenceNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.debug_list().entries(self.0.iter()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,11 +8,12 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
|
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
|
||||||
Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
|
Node, Pattern, Recipe, Scope, Scopes, Show, StyleEntry, StyleMap, Value, Vm,
|
||||||
};
|
};
|
||||||
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
|
use crate::diag::{At, SourceResult, StrResult, Trace, Tracepoint};
|
||||||
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
||||||
use crate::library;
|
use crate::library::math;
|
||||||
|
use crate::library::text::TextNode;
|
||||||
use crate::syntax::ast::TypedNode;
|
use crate::syntax::ast::TypedNode;
|
||||||
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
|
use crate::syntax::{ast, SourceId, Span, Spanned, Unit};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
@ -189,11 +190,11 @@ impl Eval for ast::MarkupNode {
|
|||||||
impl Eval for ast::Space {
|
impl Eval for ast::Space {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(if self.newlines() < 2 {
|
Ok(if self.newlines() < 2 {
|
||||||
Content::Space
|
(vm.items().space)()
|
||||||
} else {
|
} else {
|
||||||
Content::Parbreak
|
(vm.items().parbreak)()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,40 +202,40 @@ impl Eval for ast::Space {
|
|||||||
impl Eval for ast::Linebreak {
|
impl Eval for ast::Linebreak {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(Content::Linebreak { justify: false })
|
Ok((vm.items().linebreak)(false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::Text {
|
impl Eval for ast::Text {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(Content::Text(self.get().clone()))
|
Ok(vm.text(self.get().clone()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::Escape {
|
impl Eval for ast::Escape {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(Content::Text(self.get().into()))
|
Ok(vm.text(self.get()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::Shorthand {
|
impl Eval for ast::Shorthand {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(Content::Text(self.get().into()))
|
Ok(vm.text(self.get()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::SmartQuote {
|
impl Eval for ast::SmartQuote {
|
||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(Content::Quote { double: self.double() })
|
Ok((vm.items().smart_quote)(self.double()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,7 +278,7 @@ impl Eval for ast::Label {
|
|||||||
type Output = Content;
|
type Output = Content;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(Content::Empty)
|
Ok(Content::empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,26 +336,23 @@ impl Eval for ast::Math {
|
|||||||
.children()
|
.children()
|
||||||
.map(|node| node.eval(vm))
|
.map(|node| node.eval(vm))
|
||||||
.collect::<SourceResult<_>>()?;
|
.collect::<SourceResult<_>>()?;
|
||||||
Ok(Content::show(library::math::MathNode::Row(
|
Ok(math::MathNode::Row(Arc::new(nodes), self.span()).pack())
|
||||||
Arc::new(nodes),
|
|
||||||
self.span(),
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::MathNode {
|
impl Eval for ast::MathNode {
|
||||||
type Output = library::math::MathNode;
|
type Output = math::MathNode;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
Self::Space(_) => library::math::MathNode::Space,
|
Self::Space(_) => math::MathNode::Space,
|
||||||
Self::Linebreak(_) => library::math::MathNode::Linebreak,
|
Self::Linebreak(_) => math::MathNode::Linebreak,
|
||||||
Self::Escape(c) => library::math::MathNode::Atom(c.get().into()),
|
Self::Escape(c) => math::MathNode::Atom(c.get().into()),
|
||||||
Self::Atom(atom) => library::math::MathNode::Atom(atom.get().clone()),
|
Self::Atom(atom) => math::MathNode::Atom(atom.get().clone()),
|
||||||
Self::Script(node) => node.eval(vm)?,
|
Self::Script(node) => node.eval(vm)?,
|
||||||
Self::Frac(node) => node.eval(vm)?,
|
Self::Frac(node) => node.eval(vm)?,
|
||||||
Self::Align(node) => node.eval(vm)?,
|
Self::Align(node) => node.eval(vm)?,
|
||||||
Self::Group(node) => library::math::MathNode::Row(
|
Self::Group(node) => math::MathNode::Row(
|
||||||
Arc::new(
|
Arc::new(
|
||||||
node.children()
|
node.children()
|
||||||
.map(|node| node.eval(vm))
|
.map(|node| node.eval(vm))
|
||||||
@ -362,20 +360,23 @@ impl Eval for ast::MathNode {
|
|||||||
),
|
),
|
||||||
node.span(),
|
node.span(),
|
||||||
),
|
),
|
||||||
Self::Expr(expr) => match expr.eval(vm)?.display(vm.world) {
|
Self::Expr(expr) => {
|
||||||
Content::Text(text) => library::math::MathNode::Atom(text),
|
let content = expr.eval(vm)?.display(vm.world);
|
||||||
_ => bail!(expr.span(), "expected text"),
|
if let Some(node) = content.downcast::<TextNode>() {
|
||||||
},
|
math::MathNode::Atom(node.0.clone())
|
||||||
|
} else {
|
||||||
|
bail!(expr.span(), "expected text")
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::Script {
|
impl Eval for ast::Script {
|
||||||
type Output = library::math::MathNode;
|
type Output = math::MathNode;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(library::math::MathNode::Script(Arc::new(
|
Ok(math::MathNode::Script(Arc::new(math::ScriptNode {
|
||||||
library::math::ScriptNode {
|
|
||||||
base: self.base().eval(vm)?,
|
base: self.base().eval(vm)?,
|
||||||
sub: self
|
sub: self
|
||||||
.sub()
|
.sub()
|
||||||
@ -387,29 +388,26 @@ impl Eval for ast::Script {
|
|||||||
.map(|node| node.eval(vm))
|
.map(|node| node.eval(vm))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.map(|node| node.unparen()),
|
.map(|node| node.unparen()),
|
||||||
},
|
})))
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::Frac {
|
impl Eval for ast::Frac {
|
||||||
type Output = library::math::MathNode;
|
type Output = math::MathNode;
|
||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(library::math::MathNode::Frac(Arc::new(
|
Ok(math::MathNode::Frac(Arc::new(math::FracNode {
|
||||||
library::math::FracNode {
|
|
||||||
num: self.num().eval(vm)?.unparen(),
|
num: self.num().eval(vm)?.unparen(),
|
||||||
denom: self.denom().eval(vm)?.unparen(),
|
denom: self.denom().eval(vm)?.unparen(),
|
||||||
},
|
})))
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eval for ast::Align {
|
impl Eval for ast::Align {
|
||||||
type Output = library::math::MathNode;
|
type Output = math::MathNode;
|
||||||
|
|
||||||
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
Ok(library::math::MathNode::Align(self.count()))
|
Ok(math::MathNode::Align(self.count()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,8 +704,9 @@ impl Eval for ast::FieldAccess {
|
|||||||
Ok(match object {
|
Ok(match object {
|
||||||
Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
|
Value::Dict(dict) => dict.get(&field).at(span)?.clone(),
|
||||||
|
|
||||||
Value::Content(Content::Show(node)) => node
|
Value::Content(node) => node
|
||||||
.field(&field)
|
.to::<dyn Show>()
|
||||||
|
.and_then(|node| node.field(&field))
|
||||||
.ok_or_else(|| format!("unknown field {field:?}"))
|
.ok_or_else(|| format!("unknown field {field:?}"))
|
||||||
.at(span)?
|
.at(span)?
|
||||||
.clone(),
|
.clone(),
|
||||||
|
@ -4,9 +4,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use comemo::{Track, Tracked};
|
use comemo::{Track, Tracked};
|
||||||
|
|
||||||
use super::{
|
use super::{Args, Eval, Flow, Node, NodeId, Route, Scope, Scopes, StyleMap, Value, Vm};
|
||||||
Args, Content, Eval, Flow, 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::Expr;
|
||||||
use crate::syntax::SourceId;
|
use crate::syntax::SourceId;
|
||||||
@ -52,7 +50,7 @@ impl Func {
|
|||||||
Ok(Value::Content(content.styled_with_map(styles.scoped())))
|
Ok(Value::Content(content.styled_with_map(styles.scoped())))
|
||||||
},
|
},
|
||||||
set: Some(|args| T::set(args, false)),
|
set: Some(|args| T::set(args, false)),
|
||||||
node: T::SHOWABLE.then(|| NodeId::of::<T>()),
|
node: Some(NodeId::of::<T>()),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,24 +166,6 @@ impl Hash for Native {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructable, stylable content node.
|
|
||||||
pub trait Node: 'static {
|
|
||||||
/// Whether this node can be customized through a show rule.
|
|
||||||
const SHOWABLE: bool;
|
|
||||||
|
|
||||||
/// Construct a node from the arguments.
|
|
||||||
///
|
|
||||||
/// This is passed only the arguments that remain after execution of the
|
|
||||||
/// node's set rule.
|
|
||||||
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>;
|
|
||||||
|
|
||||||
/// Parse relevant arguments into style properties for this node.
|
|
||||||
///
|
|
||||||
/// When `constructor` is true, [`construct`](Self::construct) will run
|
|
||||||
/// after this invocation of `set` with the remaining arguments.
|
|
||||||
fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A user-defined closure.
|
/// A user-defined closure.
|
||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct Closure {
|
pub struct Closure {
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
//! Layouting infrastructure.
|
//! Layouting infrastructure.
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use comemo::{Prehashed, Tracked};
|
use comemo::Tracked;
|
||||||
|
|
||||||
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
use super::{Builder, Capability, Content, Scratch, StyleChain};
|
||||||
use super::{Builder, Content, Scratch};
|
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::frame::{Element, Frame};
|
use crate::frame::Frame;
|
||||||
use crate::geom::{Abs, Align, Axes, Geometry, Length, Paint, Point, Rel, Size, Stroke};
|
use crate::geom::{Abs, Axes, Size};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Layout content into a collection of pages.
|
/// Layout content into a collection of pages.
|
||||||
@ -30,7 +26,7 @@ pub fn layout(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec<
|
|||||||
/// A node that can be layouted into a sequence of regions.
|
/// A node that can be layouted into a sequence of regions.
|
||||||
///
|
///
|
||||||
/// Layouting returns one frame per used region.
|
/// Layouting returns one frame per used region.
|
||||||
pub trait Layout: 'static {
|
pub trait Layout: 'static + Sync + Send {
|
||||||
/// Layout this node into the given regions, producing frames.
|
/// Layout this node into the given regions, producing frames.
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
@ -39,13 +35,17 @@ pub trait Layout: 'static {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>>;
|
) -> SourceResult<Vec<Frame>>;
|
||||||
|
|
||||||
/// Convert to a packed node.
|
/// Whether this is an inline-level or block-level node.
|
||||||
fn pack(self) -> LayoutNode
|
fn level(&self) -> Level;
|
||||||
where
|
|
||||||
Self: Debug + Hash + Sized + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
LayoutNode::new(self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// A sequence of regions to layout into.
|
||||||
@ -140,226 +140,3 @@ impl Regions {
|
|||||||
first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
|
first.chain(backlog.chain(last).map(|&h| Size::new(self.first.x, h)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type-erased layouting node with a precomputed hash.
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct LayoutNode(Arc<Prehashed<dyn Bounds>>);
|
|
||||||
|
|
||||||
impl LayoutNode {
|
|
||||||
/// Pack any layoutable node.
|
|
||||||
pub fn new<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self(Arc::new(Prehashed::new(node)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check whether the contained node is a specific layout node.
|
|
||||||
pub fn is<T: 'static>(&self) -> bool {
|
|
||||||
(**self.0).as_any().is::<T>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The id of this node.
|
|
||||||
pub fn id(&self) -> NodeId {
|
|
||||||
(**self.0).node_id()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to downcast to a specific layout node.
|
|
||||||
pub fn downcast<T>(&self) -> Option<&T>
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + 'static,
|
|
||||||
{
|
|
||||||
(**self.0).as_any().downcast_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Force a size for this node.
|
|
||||||
pub fn sized(self, sizing: Axes<Option<Rel<Length>>>) -> Self {
|
|
||||||
if sizing.any(Option::is_some) {
|
|
||||||
SizedNode { sizing, child: self }.pack()
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill the frames resulting from a node.
|
|
||||||
pub fn filled(self, fill: Paint) -> Self {
|
|
||||||
FillNode { fill, child: self }.pack()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stroke the frames resulting from a node.
|
|
||||||
pub fn stroked(self, stroke: Stroke) -> Self {
|
|
||||||
StrokeNode { stroke, child: self }.pack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for LayoutNode {
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn layout(
|
|
||||||
&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);
|
|
||||||
self.0.layout(world, regions, styles)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pack(self) -> LayoutNode {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for LayoutNode {
|
|
||||||
fn default() -> Self {
|
|
||||||
EmptyNode.pack()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for LayoutNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Layout(")?;
|
|
||||||
self.0.fmt(f)?;
|
|
||||||
f.write_char(')')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for LayoutNode {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.eq(&other.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Bounds: Layout + Debug + Sync + Send + 'static {
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
fn node_id(&self) -> NodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds for T
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn node_id(&self) -> NodeId {
|
|
||||||
NodeId::of::<Self>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A layout node that produces an empty frame.
|
|
||||||
///
|
|
||||||
/// The packed version of this is returned by [`PackedNode::default`].
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
struct EmptyNode;
|
|
||||||
|
|
||||||
impl Layout for EmptyNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
_: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
_: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
Ok(vec![Frame::new(
|
|
||||||
regions.expand.select(regions.first, Size::zero()),
|
|
||||||
)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fix the size of a node.
|
|
||||||
#[derive(Debug, Hash)]
|
|
||||||
struct SizedNode {
|
|
||||||
/// How to size the node horizontally and vertically.
|
|
||||||
sizing: Axes<Option<Rel<Length>>>,
|
|
||||||
/// The node to be sized.
|
|
||||||
child: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for SizedNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
// The "pod" is the region into which the child will be layouted.
|
|
||||||
let pod = {
|
|
||||||
// Resolve the sizing to a concrete size.
|
|
||||||
let size = self
|
|
||||||
.sizing
|
|
||||||
.resolve(styles)
|
|
||||||
.zip(regions.base)
|
|
||||||
.map(|(s, b)| s.map(|v| v.relative_to(b)))
|
|
||||||
.unwrap_or(regions.first);
|
|
||||||
|
|
||||||
// Select the appropriate base and expansion for the child depending
|
|
||||||
// on whether it is automatically or relatively sized.
|
|
||||||
let is_auto = self.sizing.as_ref().map(Option::is_none);
|
|
||||||
let base = is_auto.select(regions.base, size);
|
|
||||||
let expand = regions.expand | !is_auto;
|
|
||||||
|
|
||||||
Regions::one(size, base, expand)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Layout the child.
|
|
||||||
let mut frames = self.child.layout(world, &pod, styles)?;
|
|
||||||
|
|
||||||
// Ensure frame size matches regions size if expansion is on.
|
|
||||||
let frame = &mut frames[0];
|
|
||||||
let target = regions.expand.select(regions.first, frame.size());
|
|
||||||
frame.resize(target, Align::LEFT_TOP);
|
|
||||||
|
|
||||||
Ok(frames)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for FillNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
let mut frames = self.child.layout(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 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: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for StrokeNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
regions: &Regions,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
let mut frames = self.child.layout(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -36,7 +36,6 @@ pub fn call(
|
|||||||
"position" => string
|
"position" => string
|
||||||
.position(args.expect("pattern")?)
|
.position(args.expect("pattern")?)
|
||||||
.map_or(Value::None, Value::Int),
|
.map_or(Value::None, Value::Int),
|
||||||
|
|
||||||
"match" => string
|
"match" => string
|
||||||
.match_(args.expect("pattern")?)
|
.match_(args.expect("pattern")?)
|
||||||
.map_or(Value::None, Value::Dict),
|
.map_or(Value::None, Value::Dict),
|
||||||
|
@ -8,7 +8,6 @@ mod eval;
|
|||||||
mod layout;
|
mod layout;
|
||||||
mod property;
|
mod property;
|
||||||
mod recipe;
|
mod recipe;
|
||||||
mod show;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod cast;
|
mod cast;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -36,7 +35,6 @@ pub use args::*;
|
|||||||
pub use array::*;
|
pub use array::*;
|
||||||
pub use capture::*;
|
pub use capture::*;
|
||||||
pub use cast::*;
|
pub use cast::*;
|
||||||
pub use collapse::*;
|
|
||||||
pub use content::*;
|
pub use content::*;
|
||||||
pub use dict::*;
|
pub use dict::*;
|
||||||
pub use eval::*;
|
pub use eval::*;
|
||||||
@ -48,10 +46,10 @@ pub use raw::*;
|
|||||||
pub use recipe::*;
|
pub use recipe::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
pub use scope::*;
|
pub use scope::*;
|
||||||
pub use show::*;
|
|
||||||
pub use styles::*;
|
pub use styles::*;
|
||||||
pub use typst_macros::node;
|
pub use typst_macros::node;
|
||||||
pub use value::*;
|
pub use value::*;
|
||||||
pub use vm::*;
|
pub use vm::*;
|
||||||
|
|
||||||
|
// use collapse::*;
|
||||||
use realize::*;
|
use realize::*;
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use super::{RawAlign, RawStroke, Regex, Smart, Value};
|
use super::{Node, RawAlign, RawStroke, 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 Value::*;
|
use Value::*;
|
||||||
|
|
||||||
/// Bail with a type mismatch error.
|
/// Bail with a type mismatch error.
|
||||||
@ -20,8 +21,8 @@ pub fn join(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
(a, None) => a,
|
(a, None) => a,
|
||||||
(None, b) => b,
|
(None, b) => b,
|
||||||
(Str(a), Str(b)) => Str(a + b),
|
(Str(a), Str(b)) => Str(a + b),
|
||||||
(Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b),
|
(Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
|
||||||
(Content(a), Str(b)) => Content(a + super::Content::Text(b.into())),
|
(Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
|
||||||
(Content(a), Content(b)) => Content(a + b),
|
(Content(a), Content(b)) => Content(a + b),
|
||||||
(Array(a), Array(b)) => Array(a + b),
|
(Array(a), Array(b)) => Array(a + b),
|
||||||
(Dict(a), Dict(b)) => Dict(a + b),
|
(Dict(a), Dict(b)) => Dict(a + b),
|
||||||
@ -86,8 +87,8 @@ pub fn add(lhs: Value, rhs: Value) -> StrResult<Value> {
|
|||||||
|
|
||||||
(Str(a), Str(b)) => Str(a + b),
|
(Str(a), Str(b)) => Str(a + b),
|
||||||
(Content(a), Content(b)) => Content(a + b),
|
(Content(a), Content(b)) => Content(a + b),
|
||||||
(Content(a), Str(b)) => Content(a + super::Content::Text(b.into())),
|
(Content(a), Str(b)) => Content(a + TextNode(b.into()).pack()),
|
||||||
(Str(a), Content(b)) => Content(super::Content::Text(a.into()) + b),
|
(Str(a), Content(b)) => Content(TextNode(a.into()).pack() + b),
|
||||||
|
|
||||||
(Array(a), Array(b)) => Array(a + b),
|
(Array(a), Array(b)) => Array(a + b),
|
||||||
(Dict(a), Dict(b)) => Dict(a + b),
|
(Dict(a), Dict(b)) => Dict(a + b),
|
||||||
|
@ -17,10 +17,10 @@ pub struct Property {
|
|||||||
/// The id of the property's [key](Key).
|
/// The id of the property's [key](Key).
|
||||||
key: KeyId,
|
key: KeyId,
|
||||||
/// The id of the node the property belongs to.
|
/// The id of the node the property belongs to.
|
||||||
pub node: NodeId,
|
node: NodeId,
|
||||||
/// Whether the property should only affect the first node down the
|
/// Whether the property should only affect the first node down the
|
||||||
/// hierarchy. Used by constructors.
|
/// hierarchy. Used by constructors.
|
||||||
pub scoped: bool,
|
scoped: bool,
|
||||||
/// The property's value.
|
/// The property's value.
|
||||||
value: Arc<Prehashed<dyn Bounds>>,
|
value: Arc<Prehashed<dyn Bounds>>,
|
||||||
/// The name of the property.
|
/// The name of the property.
|
||||||
@ -60,6 +60,21 @@ impl Property {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// What kind of structure the property interrupts.
|
||||||
pub fn interruption(&self) -> Option<Interruption> {
|
pub fn interruption(&self) -> Option<Interruption> {
|
||||||
if self.is_of::<PageNode>() {
|
if self.is_of::<PageNode>() {
|
||||||
@ -110,24 +125,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a property key.
|
/// A style property key.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Style property keys.
|
|
||||||
///
|
///
|
||||||
/// This trait is not intended to be implemented manually, but rather through
|
/// This trait is not intended to be implemented manually, but rather through
|
||||||
/// the `#[node]` proc-macro.
|
/// the `#[node]` proc-macro.
|
||||||
@ -153,6 +151,23 @@ pub trait Key<'a>: Copy + 'static {
|
|||||||
) -> Self::Output;
|
) -> 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.
|
/// A scoped property barrier.
|
||||||
///
|
///
|
||||||
/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
|
/// Barriers interact with [scoped](super::StyleMap::scoped) styles: A scoped
|
||||||
|
@ -3,15 +3,20 @@ use std::mem;
|
|||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
|
use super::collapse::CollapsingBuilder;
|
||||||
use super::{
|
use super::{
|
||||||
Barrier, CollapsingBuilder, Content, Interruption, Layout, ShowNode, StyleChain,
|
Barrier, Content, Interruption, Layout, Level, Node, SequenceNode, Show, StyleChain,
|
||||||
StyleEntry, StyleMap, StyleVecBuilder, Target,
|
StyleEntry, StyleMap, StyleVecBuilder, StyledNode, Target,
|
||||||
};
|
};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::geom::Numeric;
|
use crate::geom::Numeric;
|
||||||
use crate::library::layout::{FlowChild, FlowNode, PageNode, PlaceNode};
|
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::structure::{DocNode, ListItem, ListNode, DESC, ENUM, LIST};
|
||||||
use crate::library::text::{ParChild, ParNode};
|
use crate::library::text::{
|
||||||
|
LinebreakNode, ParChild, ParNode, ParbreakNode, SmartQuoteNode, SpaceNode, TextNode,
|
||||||
|
};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Builds a document or a flow node from content.
|
/// Builds a document or a flow node from content.
|
||||||
@ -78,20 +83,19 @@ impl<'a> Builder<'a> {
|
|||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
match content {
|
if let Some(node) = content.downcast::<TextNode>() {
|
||||||
Content::Empty => return Ok(()),
|
if let Some(realized) = styles.apply(self.world, Target::Text(&node.0))? {
|
||||||
Content::Text(text) => {
|
|
||||||
if let Some(realized) = styles.apply(self.world, Target::Text(text))? {
|
|
||||||
let stored = self.scratch.templates.alloc(realized);
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
return self.accept(stored, styles);
|
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(());
|
||||||
}
|
}
|
||||||
|
|
||||||
Content::Show(node) => return self.show(node, styles),
|
|
||||||
Content::Styled(styled) => return self.styled(styled, styles),
|
|
||||||
Content::Sequence(seq) => return self.sequence(seq, styles),
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.list.accept(content, styles) {
|
if self.list.accept(content, styles) {
|
||||||
@ -100,7 +104,7 @@ impl<'a> Builder<'a> {
|
|||||||
|
|
||||||
self.interrupt(Interruption::List, styles, false)?;
|
self.interrupt(Interruption::List, styles, false)?;
|
||||||
|
|
||||||
if let Content::Item(_) = content {
|
if content.is::<ListItem>() {
|
||||||
self.list.accept(content, styles);
|
self.list.accept(content, styles);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -115,7 +119,7 @@ impl<'a> Builder<'a> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let keep = matches!(content, Content::Pagebreak { weak: false });
|
let keep = content.downcast::<PagebreakNode>().map_or(false, |node| !node.weak);
|
||||||
self.interrupt(Interruption::Page, styles, keep)?;
|
self.interrupt(Interruption::Page, styles, keep)?;
|
||||||
|
|
||||||
if let Some(doc) = &mut self.doc {
|
if let Some(doc) = &mut self.doc {
|
||||||
@ -128,7 +132,7 @@ impl<'a> Builder<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show(&mut self, node: &ShowNode, styles: StyleChain<'a>) -> SourceResult<()> {
|
fn show(&mut self, node: &'a Content, styles: StyleChain<'a>) -> SourceResult<bool> {
|
||||||
if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
|
if let Some(mut realized) = styles.apply(self.world, Target::Node(node))? {
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
let barrier = Barrier::new(node.id());
|
let barrier = Barrier::new(node.id());
|
||||||
@ -137,24 +141,26 @@ impl<'a> Builder<'a> {
|
|||||||
realized = realized.styled_with_map(map);
|
realized = realized.styled_with_map(map);
|
||||||
let stored = self.scratch.templates.alloc(realized);
|
let stored = self.scratch.templates.alloc(realized);
|
||||||
self.accept(stored, styles)?;
|
self.accept(stored, styles)?;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled(
|
fn styled(
|
||||||
&mut self,
|
&mut self,
|
||||||
(content, map): &'a (Content, StyleMap),
|
styled: &'a StyledNode,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
let stored = self.scratch.styles.alloc(styles);
|
let stored = self.scratch.styles.alloc(styles);
|
||||||
let styles = map.chain(stored);
|
let styles = styled.map.chain(stored);
|
||||||
let intr = map.interruption();
|
let intr = styled.map.interruption();
|
||||||
|
|
||||||
if let Some(intr) = intr {
|
if let Some(intr) = intr {
|
||||||
self.interrupt(intr, styles, false)?;
|
self.interrupt(intr, styles, false)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.accept(content, styles)?;
|
self.accept(&styled.sub, styles)?;
|
||||||
|
|
||||||
if let Some(intr) = intr {
|
if let Some(intr) = intr {
|
||||||
self.interrupt(intr, styles, true)?;
|
self.interrupt(intr, styles, true)?;
|
||||||
@ -193,10 +199,10 @@ impl<'a> Builder<'a> {
|
|||||||
|
|
||||||
fn sequence(
|
fn sequence(
|
||||||
&mut self,
|
&mut self,
|
||||||
seq: &'a [Content],
|
seq: &'a SequenceNode,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
for content in seq {
|
for content in &seq.0 {
|
||||||
self.accept(content, styles)?;
|
self.accept(content, styles)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -213,16 +219,14 @@ struct DocBuilder<'a> {
|
|||||||
|
|
||||||
impl<'a> DocBuilder<'a> {
|
impl<'a> DocBuilder<'a> {
|
||||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) {
|
||||||
match content {
|
if let Some(pagebreak) = content.downcast::<PagebreakNode>() {
|
||||||
Content::Pagebreak { weak } => {
|
self.keep_next = !pagebreak.weak;
|
||||||
self.keep_next = !weak;
|
|
||||||
}
|
}
|
||||||
Content::Page(page) => {
|
|
||||||
|
if let Some(page) = content.downcast::<PageNode>() {
|
||||||
self.pages.push(page.clone(), styles);
|
self.pages.push(page.clone(), styles);
|
||||||
self.keep_next = false;
|
self.keep_next = false;
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,36 +254,34 @@ impl<'a> FlowBuilder<'a> {
|
|||||||
// 4 | generated weak fractional spacing
|
// 4 | generated weak fractional spacing
|
||||||
// 5 | par spacing
|
// 5 | par spacing
|
||||||
|
|
||||||
match content {
|
if let Some(_) = content.downcast::<ParbreakNode>() {
|
||||||
Content::Parbreak => {}
|
/* Nothing to do */
|
||||||
Content::Colbreak { weak } => {
|
} else if let Some(colbreak) = content.downcast::<ColbreakNode>() {
|
||||||
if *weak {
|
if colbreak.weak {
|
||||||
self.0.weak(FlowChild::Colbreak, styles, 0);
|
self.0.weak(FlowChild::Colbreak, styles, 0);
|
||||||
} else {
|
} else {
|
||||||
self.0.destructive(FlowChild::Colbreak, styles);
|
self.0.destructive(FlowChild::Colbreak, styles);
|
||||||
}
|
}
|
||||||
}
|
} else if let Some(vertical) = content.downcast::<VNode>() {
|
||||||
&Content::Vertical { amount, weak, generated } => {
|
let child = FlowChild::Spacing(vertical.amount);
|
||||||
let child = FlowChild::Spacing(amount);
|
let frac = vertical.amount.is_fractional();
|
||||||
let frac = amount.is_fractional();
|
if vertical.weak {
|
||||||
if weak {
|
let weakness = 1 + u8::from(frac) + 2 * u8::from(vertical.generated);
|
||||||
let weakness = 1 + u8::from(frac) + 2 * u8::from(generated);
|
|
||||||
self.0.weak(child, styles, weakness);
|
self.0.weak(child, styles, weakness);
|
||||||
} else if frac {
|
} else if frac {
|
||||||
self.0.destructive(child, styles);
|
self.0.destructive(child, styles);
|
||||||
} else {
|
} else {
|
||||||
self.0.ignorant(child, styles);
|
self.0.ignorant(child, styles);
|
||||||
}
|
}
|
||||||
}
|
} else if content.has::<dyn Layout>() {
|
||||||
Content::Block(node) => {
|
let child = FlowChild::Node(content.clone());
|
||||||
let child = FlowChild::Node(node.clone());
|
if content.is::<PlaceNode>() {
|
||||||
if node.is::<PlaceNode>() {
|
|
||||||
self.0.ignorant(child, styles);
|
self.0.ignorant(child, styles);
|
||||||
} else {
|
} else {
|
||||||
self.0.supportive(child, styles);
|
self.0.supportive(child, styles);
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
_ => return false,
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
@ -321,18 +323,15 @@ impl<'a> ParBuilder<'a> {
|
|||||||
// 1 | weak spacing
|
// 1 | weak spacing
|
||||||
// 2 | space
|
// 2 | space
|
||||||
|
|
||||||
match content {
|
if content.is::<SpaceNode>() {
|
||||||
Content::Space => {
|
|
||||||
self.0.weak(ParChild::Text(' '.into()), styles, 2);
|
self.0.weak(ParChild::Text(' '.into()), styles, 2);
|
||||||
}
|
} else if let Some(linebreak) = content.downcast::<LinebreakNode>() {
|
||||||
&Content::Linebreak { justify } => {
|
let c = if linebreak.justify { '\u{2028}' } else { '\n' };
|
||||||
let c = if justify { '\u{2028}' } else { '\n' };
|
|
||||||
self.0.destructive(ParChild::Text(c.into()), styles);
|
self.0.destructive(ParChild::Text(c.into()), styles);
|
||||||
}
|
} else if let Some(horizontal) = content.downcast::<HNode>() {
|
||||||
&Content::Horizontal { amount, weak } => {
|
let child = ParChild::Spacing(horizontal.amount);
|
||||||
let child = ParChild::Spacing(amount);
|
let frac = horizontal.amount.is_fractional();
|
||||||
let frac = amount.is_fractional();
|
if horizontal.weak {
|
||||||
if weak {
|
|
||||||
let weakness = u8::from(!frac);
|
let weakness = u8::from(!frac);
|
||||||
self.0.weak(child, styles, weakness);
|
self.0.weak(child, styles, weakness);
|
||||||
} else if frac {
|
} else if frac {
|
||||||
@ -340,17 +339,18 @@ impl<'a> ParBuilder<'a> {
|
|||||||
} else {
|
} else {
|
||||||
self.0.ignorant(child, styles);
|
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;
|
||||||
}
|
}
|
||||||
&Content::Quote { double } => {
|
} else {
|
||||||
self.0.supportive(ParChild::Quote { double }, styles);
|
return false;
|
||||||
}
|
|
||||||
Content::Text(text) => {
|
|
||||||
self.0.supportive(ParChild::Text(text.clone()), styles);
|
|
||||||
}
|
|
||||||
Content::Inline(node) => {
|
|
||||||
self.0.supportive(ParChild::Node(node.clone()), styles);
|
|
||||||
}
|
|
||||||
_ => return false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
@ -412,29 +412,31 @@ struct ListBuilder<'a> {
|
|||||||
impl<'a> ListBuilder<'a> {
|
impl<'a> ListBuilder<'a> {
|
||||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||||
if self.items.is_empty() {
|
if self.items.is_empty() {
|
||||||
match content {
|
if content.is::<ParbreakNode>() {
|
||||||
Content::Space => {}
|
self.attachable = false;
|
||||||
Content::Item(_) => {}
|
} else if !content.is::<SpaceNode>() && !content.is::<ListItem>() {
|
||||||
Content::Parbreak => self.attachable = false,
|
self.attachable = true;
|
||||||
_ => self.attachable = true,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match content {
|
if let Some(item) = content.downcast::<ListItem>() {
|
||||||
Content::Item(item)
|
|
||||||
if self
|
if self
|
||||||
.items
|
.items
|
||||||
.items()
|
.items()
|
||||||
.next()
|
.next()
|
||||||
.map_or(true, |first| item.kind() == first.kind()) =>
|
.map_or(true, |first| item.kind() == first.kind())
|
||||||
{
|
{
|
||||||
self.items.push(item.clone(), styles);
|
self.items.push(item.clone(), styles);
|
||||||
self.tight &= self.staged.drain(..).all(|(t, _)| *t != Content::Parbreak);
|
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
Content::Space | Content::Parbreak if !self.items.is_empty() => {
|
} else if !self.items.is_empty()
|
||||||
|
&& (content.is::<SpaceNode>() || content.is::<ParbreakNode>())
|
||||||
|
{
|
||||||
self.staged.push((content, styles));
|
self.staged.push((content, styles));
|
||||||
}
|
} else {
|
||||||
_ => return false,
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
true
|
||||||
@ -450,9 +452,9 @@ impl<'a> ListBuilder<'a> {
|
|||||||
let tight = self.tight;
|
let tight = self.tight;
|
||||||
let attached = tight && self.attachable;
|
let attached = tight && self.attachable;
|
||||||
let content = match kind {
|
let content = match kind {
|
||||||
LIST => Content::show(ListNode::<LIST> { tight, attached, items }),
|
LIST => ListNode::<LIST> { tight, attached, items }.pack(),
|
||||||
ENUM => Content::show(ListNode::<ENUM> { tight, attached, items }),
|
ENUM => ListNode::<ENUM> { tight, attached, items }.pack(),
|
||||||
DESC | _ => Content::show(ListNode::<DESC> { tight, attached, items }),
|
DESC | _ => ListNode::<DESC> { tight, attached, items }.pack(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let stored = parent.scratch.templates.alloc(content);
|
let stored = parent.scratch.templates.alloc(content);
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
Args, Content, Func, Interruption, NodeId, Regex, Show, ShowNode, StyleEntry, Value,
|
Args, Capability, Content, Func, Interruption, Node, NodeId, Regex, StyleChain,
|
||||||
|
StyleEntry, Value,
|
||||||
};
|
};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::library::structure::{DescNode, EnumNode, ListNode};
|
use crate::library::structure::{DescNode, EnumNode, ListNode};
|
||||||
|
use crate::library::text::TextNode;
|
||||||
use crate::syntax::Spanned;
|
use crate::syntax::Spanned;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -38,8 +41,8 @@ impl Recipe {
|
|||||||
) -> SourceResult<Option<Content>> {
|
) -> SourceResult<Option<Content>> {
|
||||||
let content = match (target, &self.pattern) {
|
let content = match (target, &self.pattern) {
|
||||||
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
|
(Target::Node(node), &Pattern::Node(id)) if node.id() == id => {
|
||||||
let node = node.unguard(sel);
|
let node = node.to::<dyn Show>().unwrap().unguard_parts(sel);
|
||||||
self.call(world, || Value::Content(Content::Show(node)))?
|
self.call(world, || Value::Content(node))?
|
||||||
}
|
}
|
||||||
|
|
||||||
(Target::Text(text), Pattern::Regex(regex)) => {
|
(Target::Text(text), Pattern::Regex(regex)) => {
|
||||||
@ -49,7 +52,7 @@ impl Recipe {
|
|||||||
for mat in regex.find_iter(text) {
|
for mat in regex.find_iter(text) {
|
||||||
let start = mat.start();
|
let start = mat.start();
|
||||||
if cursor < start {
|
if cursor < start {
|
||||||
result.push(Content::Text(text[cursor .. start].into()));
|
result.push(TextNode(text[cursor .. start].into()).pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
|
result.push(self.call(world, || Value::Str(mat.as_str().into()))?);
|
||||||
@ -61,7 +64,7 @@ impl Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cursor < text.len() {
|
if cursor < text.len() {
|
||||||
result.push(Content::Text(text[cursor ..].into()));
|
result.push(TextNode(text[cursor ..].into()).pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
Content::sequence(result)
|
Content::sequence(result)
|
||||||
@ -132,7 +135,7 @@ impl Pattern {
|
|||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum Target<'a> {
|
pub enum Target<'a> {
|
||||||
/// A showable node.
|
/// A showable node.
|
||||||
Node(&'a ShowNode),
|
Node(&'a Content),
|
||||||
/// A slice of text.
|
/// A slice of text.
|
||||||
Text(&'a str),
|
Text(&'a str),
|
||||||
}
|
}
|
||||||
@ -145,3 +148,38 @@ pub enum Selector {
|
|||||||
/// The base recipe for a kind of node.
|
/// The base recipe for a kind of node.
|
||||||
Base(NodeId),
|
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,127 +0,0 @@
|
|||||||
use std::fmt::{self, Debug, Formatter, Write};
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use comemo::{Prehashed, Tracked};
|
|
||||||
|
|
||||||
use super::{Content, NodeId, Selector, StyleChain, Value};
|
|
||||||
use crate::diag::SourceResult;
|
|
||||||
use crate::World;
|
|
||||||
|
|
||||||
/// A node that can be realized given some styles.
|
|
||||||
pub trait Show: 'static {
|
|
||||||
/// Unguard nested content against recursive show rules.
|
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode;
|
|
||||||
|
|
||||||
/// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert to a packed show node.
|
|
||||||
fn pack(self) -> ShowNode
|
|
||||||
where
|
|
||||||
Self: Debug + Hash + Sized + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
ShowNode::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A type-erased showable node with a precomputed hash.
|
|
||||||
#[derive(Clone, Hash)]
|
|
||||||
pub struct ShowNode(Arc<Prehashed<dyn Bounds>>);
|
|
||||||
|
|
||||||
impl ShowNode {
|
|
||||||
/// Pack any showable node.
|
|
||||||
pub fn new<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Show + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self(Arc::new(Prehashed::new(node)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The id of this node.
|
|
||||||
pub fn id(&self) -> NodeId {
|
|
||||||
(**self.0).node_id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for ShowNode {
|
|
||||||
fn unguard(&self, sel: Selector) -> ShowNode {
|
|
||||||
self.0.unguard(sel)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
|
||||||
self.0.field(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn realize(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
styles: StyleChain,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
self.0.realize(world, styles)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalize(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
styles: StyleChain,
|
|
||||||
realized: Content,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
self.0.finalize(world, styles, realized)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pack(self) -> ShowNode {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for ShowNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.write_str("Show(")?;
|
|
||||||
self.0.fmt(f)?;
|
|
||||||
f.write_char(')')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for ShowNode {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.0.eq(&other.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Bounds: Show + Debug + Sync + Send + 'static {
|
|
||||||
fn node_id(&self) -> NodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds for T
|
|
||||||
where
|
|
||||||
T: Show + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
fn node_id(&self) -> NodeId {
|
|
||||||
NodeId::of::<Self>()
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,6 @@ use comemo::Tracked;
|
|||||||
|
|
||||||
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
use super::{Barrier, Content, Key, Property, Recipe, Selector, Show, Target};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::util::ReadableTypeId;
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// A map of style properties.
|
/// A map of style properties.
|
||||||
@ -98,7 +97,7 @@ impl StyleMap {
|
|||||||
pub fn scoped(mut self) -> Self {
|
pub fn scoped(mut self) -> Self {
|
||||||
for entry in &mut self.0 {
|
for entry in &mut self.0 {
|
||||||
if let StyleEntry::Property(property) = entry {
|
if let StyleEntry::Property(property) = entry {
|
||||||
property.scoped = true;
|
property.make_scoped();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -125,23 +124,6 @@ impl Debug for StyleMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a node.
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct NodeId(ReadableTypeId);
|
|
||||||
|
|
||||||
impl NodeId {
|
|
||||||
/// The id of the given node.
|
|
||||||
pub fn of<T: 'static>() -> Self {
|
|
||||||
Self(ReadableTypeId::of::<T>())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for NodeId {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines whether a style could interrupt some composable structure.
|
/// Determines whether a style could interrupt some composable structure.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum Interruption {
|
pub enum Interruption {
|
||||||
@ -175,7 +157,7 @@ impl StyleEntry {
|
|||||||
if !tail
|
if !tail
|
||||||
.entries()
|
.entries()
|
||||||
.filter_map(StyleEntry::property)
|
.filter_map(StyleEntry::property)
|
||||||
.any(|p| p.scoped && barrier.is_for(p.node))
|
.any(|p| p.scoped() && barrier.is_for(p.node()))
|
||||||
{
|
{
|
||||||
return *tail;
|
return *tail;
|
||||||
}
|
}
|
||||||
@ -302,7 +284,13 @@ impl<'a> StyleChain<'a> {
|
|||||||
if self.guarded(sel) {
|
if self.guarded(sel) {
|
||||||
guarded = true;
|
guarded = true;
|
||||||
} else {
|
} else {
|
||||||
let content = node.unguard(sel).realize(world, self)?;
|
let content = node
|
||||||
|
.to::<dyn Show>()
|
||||||
|
.unwrap()
|
||||||
|
.unguard_parts(sel)
|
||||||
|
.to::<dyn Show>()
|
||||||
|
.unwrap()
|
||||||
|
.realize(world, self)?;
|
||||||
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
|
realized = Some(content.styled_with_entry(StyleEntry::Guard(sel)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,7 +298,9 @@ impl<'a> StyleChain<'a> {
|
|||||||
// Finalize only if guarding didn't stop any recipe.
|
// Finalize only if guarding didn't stop any recipe.
|
||||||
if !guarded {
|
if !guarded {
|
||||||
if let Some(content) = realized {
|
if let Some(content) = realized {
|
||||||
realized = Some(node.finalize(world, self, content)?);
|
realized = Some(
|
||||||
|
node.to::<dyn Show>().unwrap().finalize(world, self, content)?,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -403,7 +393,7 @@ impl<'a, K: Key<'a>> Iterator for Values<'a, K> {
|
|||||||
match entry {
|
match entry {
|
||||||
StyleEntry::Property(property) => {
|
StyleEntry::Property(property) => {
|
||||||
if let Some(value) = property.downcast::<K>() {
|
if let Some(value) = property.downcast::<K>() {
|
||||||
if !property.scoped || self.depth <= 1 {
|
if !property.scoped() || self.depth <= 1 {
|
||||||
return Some(value);
|
return Some(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,10 @@ use std::sync::Arc;
|
|||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
|
|
||||||
use super::{ops, Args, Array, Cast, Content, Dict, Func, Layout, Str};
|
use super::{ops, Args, Array, Cast, Content, Dict, Func, Node, Str};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
|
use crate::geom::{Abs, Angle, Color, Em, Fr, Length, Ratio, Rel, RgbaColor};
|
||||||
|
use crate::library::text::TextNode;
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -55,22 +56,6 @@ pub enum Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
/// Create a content value from an inline-level node.
|
|
||||||
pub fn inline<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self::Content(Content::inline(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a content value from a block-level node.
|
|
||||||
pub fn block<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + Debug + Hash + Sync + Send + 'static,
|
|
||||||
{
|
|
||||||
Self::Content(Content::block(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new dynamic value.
|
/// Create a new dynamic value.
|
||||||
pub fn dynamic<T>(any: T) -> Self
|
pub fn dynamic<T>(any: T) -> Self
|
||||||
where
|
where
|
||||||
@ -115,16 +100,17 @@ impl Value {
|
|||||||
|
|
||||||
/// Return the display representation of the value.
|
/// Return the display representation of the value.
|
||||||
pub fn display(self, world: Tracked<dyn World>) -> Content {
|
pub fn display(self, world: Tracked<dyn World>) -> Content {
|
||||||
|
let items = &world.config().items;
|
||||||
match self {
|
match self {
|
||||||
Value::None => Content::new(),
|
Value::None => Content::empty(),
|
||||||
Value::Int(v) => Content::Text(format_eco!("{}", v)),
|
Value::Int(v) => (items.text)(format_eco!("{}", v)),
|
||||||
Value::Float(v) => Content::Text(format_eco!("{}", v)),
|
Value::Float(v) => (items.text)(format_eco!("{}", v)),
|
||||||
Value::Str(v) => Content::Text(v.into()),
|
Value::Str(v) => (items.text)(v.into()),
|
||||||
Value::Content(v) => v,
|
Value::Content(v) => v,
|
||||||
|
|
||||||
// For values which can't be shown "naturally", we return the raw
|
// For values which can't be shown "naturally", we return the raw
|
||||||
// representation with typst code syntax highlighting.
|
// representation with typst code syntax highlighting.
|
||||||
v => (world.config().items.raw)(v.repr().into(), Some("typc".into()), false),
|
v => (items.raw)(v.repr().into(), Some("typc".into()), false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -398,8 +384,8 @@ primitive! { Color: "color", Color }
|
|||||||
primitive! { Str: "string", Str }
|
primitive! { Str: "string", Str }
|
||||||
primitive! { Content: "content",
|
primitive! { Content: "content",
|
||||||
Content,
|
Content,
|
||||||
None => Content::new(),
|
None => Content::empty(),
|
||||||
Str(text) => Content::Text(text.into())
|
Str(text) => TextNode(text.into()).pack()
|
||||||
}
|
}
|
||||||
primitive! { Array: "array", Array }
|
primitive! { Array: "array", Array }
|
||||||
primitive! { Dict: "dictionary", Dict }
|
primitive! { Dict: "dictionary", Dict }
|
||||||
@ -448,7 +434,6 @@ mod tests {
|
|||||||
test(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
|
test(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
|
||||||
|
|
||||||
// Functions, content and dynamics.
|
// Functions, content and dynamics.
|
||||||
test(Content::Text("a".into()), "[...]");
|
|
||||||
test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil");
|
test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil");
|
||||||
test(Dynamic::new(1), "1");
|
test(Dynamic::new(1), "1");
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,10 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
|
|
||||||
use super::{Route, Scopes, Value};
|
use super::{Content, Route, Scopes, Value};
|
||||||
use crate::diag::{SourceError, StrResult};
|
use crate::diag::{SourceError, StrResult};
|
||||||
use crate::syntax::{SourceId, Span};
|
use crate::syntax::{SourceId, Span};
|
||||||
use crate::util::PathExt;
|
use crate::util::{EcoString, PathExt};
|
||||||
use crate::{LangItems, World};
|
use crate::{LangItems, World};
|
||||||
|
|
||||||
/// A virtual machine.
|
/// A virtual machine.
|
||||||
@ -59,6 +59,13 @@ impl<'a> Vm<'a> {
|
|||||||
pub fn items(&self) -> &LangItems {
|
pub fn items(&self) -> &LangItems {
|
||||||
&self.world.config().items
|
&self.world.config().items
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create text content.
|
||||||
|
///
|
||||||
|
/// This is a shorthand for `(vm.items().text)(..)`.
|
||||||
|
pub fn text(&self, text: impl Into<EcoString>) -> Content {
|
||||||
|
(self.items().text)(text.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A control flow event that occurred during evaluation.
|
/// A control flow event that occurred during evaluation.
|
||||||
|
64
src/util/fat.rs
Normal file
64
src/util/fat.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//! Fat pointer handling.
|
||||||
|
//!
|
||||||
|
//! This assumes the memory representation of fat pointers. Although it is not
|
||||||
|
//! guaranteed by Rust, it's improbable that it will change. Still, when the
|
||||||
|
//! pointer metadata APIs are stable, we should definitely move to them:
|
||||||
|
//! <https://github.com/rust-lang/rust/issues/81513>
|
||||||
|
|
||||||
|
use std::alloc;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// Create a fat pointer from a data address and a vtable address.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Must only be called when `T` is a `dyn Trait`. The data address must point
|
||||||
|
/// to a value whose type implements the trait of `T` and the `vtable` must have
|
||||||
|
/// been extracted with [`vtable`].
|
||||||
|
pub unsafe fn from_raw_parts<T: ?Sized>(data: *const (), vtable: *const ()) -> *const T {
|
||||||
|
debug_assert_eq!(
|
||||||
|
alloc::Layout::new::<*const T>(),
|
||||||
|
alloc::Layout::new::<FatPointer>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let fat = FatPointer { data, vtable };
|
||||||
|
mem::transmute_copy::<FatPointer, *const T>(&fat)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a mutable fat pointer from a data address and a vtable address.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Must only be called when `T` is a `dyn Trait`. The data address must point
|
||||||
|
/// to a value whose type implements the trait of `T` and the `vtable` must have
|
||||||
|
/// been extracted with [`vtable`].
|
||||||
|
pub unsafe fn from_raw_parts_mut<T: ?Sized>(data: *mut (), vtable: *const ()) -> *mut T {
|
||||||
|
debug_assert_eq!(
|
||||||
|
alloc::Layout::new::<*mut T>(),
|
||||||
|
alloc::Layout::new::<FatPointer>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let fat = FatPointer { data, vtable };
|
||||||
|
mem::transmute_copy::<FatPointer, *mut T>(&fat)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the address to a trait object's vtable.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Must only be called when `T` is a `dyn Trait`.
|
||||||
|
pub unsafe fn vtable<T: ?Sized>(ptr: *const T) -> *const () {
|
||||||
|
debug_assert_eq!(
|
||||||
|
alloc::Layout::new::<*const T>(),
|
||||||
|
alloc::Layout::new::<FatPointer>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
mem::transmute_copy::<*const T, FatPointer>(&ptr).vtable
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The memory representation of a trait object pointer.
|
||||||
|
///
|
||||||
|
/// Although this is not guaranteed by Rust, it's improbable that it will
|
||||||
|
/// change.
|
||||||
|
#[repr(C)]
|
||||||
|
struct FatPointer {
|
||||||
|
data: *const (),
|
||||||
|
vtable: *const (),
|
||||||
|
}
|
@ -1,12 +1,14 @@
|
|||||||
//! Utilities.
|
//! Utilities.
|
||||||
|
|
||||||
|
pub mod fat;
|
||||||
|
|
||||||
|
pub use buffer::Buffer;
|
||||||
|
pub use eco::EcoString;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod eco;
|
mod eco;
|
||||||
mod buffer;
|
mod buffer;
|
||||||
|
|
||||||
pub use buffer::Buffer;
|
|
||||||
pub use eco::EcoString;
|
|
||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user