diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-29 13:37:25 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-29 14:18:13 +0100 |
| commit | 0efe669278a5e1c3f2985eba2f3360e91159c54a (patch) | |
| tree | 502712857c48f0decb5e698257c0a96d358a436e /library/src/structure | |
| parent | 836692e73cff0356e409a9ba5b4887b86809d4ca (diff) | |
Reorganize library and tests
Diffstat (limited to 'library/src/structure')
| -rw-r--r-- | library/src/structure/document.rs | 48 | ||||
| -rw-r--r-- | library/src/structure/heading.rs | 62 | ||||
| -rw-r--r-- | library/src/structure/list.rs | 272 | ||||
| -rw-r--r-- | library/src/structure/mod.rs | 13 | ||||
| -rw-r--r-- | library/src/structure/reference.rs | 26 | ||||
| -rw-r--r-- | library/src/structure/table.rs | 136 |
6 files changed, 0 insertions, 557 deletions
diff --git a/library/src/structure/document.rs b/library/src/structure/document.rs deleted file mode 100644 index e52c92ad..00000000 --- a/library/src/structure/document.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::layout::{LayoutRoot, PageNode}; -use crate::prelude::*; - -/// The root node of the model. -#[derive(Hash)] -pub struct DocumentNode(pub StyleVec<PageNode>); - -#[node(LayoutRoot)] -impl DocumentNode { - /// The document's title. - #[property(referenced)] - pub const TITLE: Option<EcoString> = None; - - /// The document's author. - #[property(referenced)] - pub const AUTHOR: Option<EcoString> = None; -} - -impl LayoutRoot for DocumentNode { - /// Layout the document into a sequence of frames, one per page. - fn layout_root( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - ) -> SourceResult<Document> { - let mut pages = vec![]; - for (page, map) in self.0.iter() { - let number = 1 + pages.len(); - let fragment = page.layout(world, number, styles.chain(map))?; - pages.extend(fragment); - } - - Ok(Document { - metadata: Metadata { - title: styles.get(Self::TITLE).clone(), - author: styles.get(Self::AUTHOR).clone(), - }, - pages, - }) - } -} - -impl Debug for DocumentNode { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("Document ")?; - self.0.fmt(f) - } -} diff --git a/library/src/structure/heading.rs b/library/src/structure/heading.rs deleted file mode 100644 index b251f27b..00000000 --- a/library/src/structure/heading.rs +++ /dev/null @@ -1,62 +0,0 @@ -use typst::font::FontWeight; - -use crate::layout::{BlockNode, VNode}; -use crate::prelude::*; -use crate::text::{TextNode, TextSize}; - -/// A section heading. -#[derive(Debug, Hash)] -pub struct HeadingNode { - /// The logical nesting depth of the section, starting from one. In the - /// default style, this controls the text size of the heading. - pub level: NonZeroUsize, - /// The heading's contents. - pub body: Content, -} - -#[node(Show, Finalize)] -impl HeadingNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - body: args.expect("body")?, - level: args.named("level")?.unwrap_or(NonZeroUsize::new(1).unwrap()), - } - .pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "level" => Some(Value::Int(self.level.get() as i64)), - "body" => Some(Value::Content(self.body.clone())), - _ => None, - } - } -} - -impl Show for HeadingNode { - fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content { - BlockNode(self.body.clone()).pack() - } -} - -impl Finalize for HeadingNode { - fn finalize(&self, realized: Content) -> Content { - let scale = match self.level.get() { - 1 => 1.4, - 2 => 1.2, - _ => 1.0, - }; - - let size = Em::new(scale); - let above = Em::new(if self.level.get() == 1 { 1.8 } else { 1.44 }) / scale; - let below = Em::new(0.66) / scale; - - let mut map = StyleMap::new(); - map.set(TextNode::SIZE, TextSize(size.into())); - map.set(TextNode::WEIGHT, FontWeight::BOLD); - map.set(BlockNode::ABOVE, VNode::block_around(above.into())); - map.set(BlockNode::BELOW, VNode::block_around(below.into())); - map.set(BlockNode::STICKY, true); - realized.styled_with_map(map) - } -} diff --git a/library/src/structure/list.rs b/library/src/structure/list.rs deleted file mode 100644 index b51284a8..00000000 --- a/library/src/structure/list.rs +++ /dev/null @@ -1,272 +0,0 @@ -use crate::base::NumberingPattern; -use crate::layout::{BlockNode, GridNode, HNode, Spacing, TrackSizing}; -use crate::prelude::*; -use crate::text::{ParNode, SpaceNode, TextNode}; - -/// An unordered (bulleted) or ordered (numbered) list. -#[derive(Debug, Hash)] -pub struct ListNode<const L: ListKind = LIST> { - /// If true, the items are separated by leading instead of list spacing. - pub tight: bool, - /// The individual bulleted or numbered items. - pub items: StyleVec<ListItem>, -} - -/// An ordered list. -pub type EnumNode = ListNode<ENUM>; - -/// A description list. -pub type DescNode = ListNode<DESC>; - -#[node(Layout)] -impl<const L: ListKind> ListNode<L> { - /// How the list is labelled. - #[property(referenced)] - pub const LABEL: Label = Label::Default; - /// The indentation of each item's label. - #[property(resolve)] - pub const INDENT: Length = Length::zero(); - /// The space between the label and the body of each item. - #[property(resolve)] - pub const BODY_INDENT: Length = Em::new(match L { - LIST | ENUM => 0.5, - DESC | _ => 1.0, - }) - .into(); - /// The spacing between the items of a wide (non-tight) list. - pub const SPACING: Smart<Spacing> = Smart::Auto; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - let items = match L { - LIST => args - .all()? - .into_iter() - .map(|body| ListItem::List(Box::new(body))) - .collect(), - ENUM => { - let mut number: usize = args.named("start")?.unwrap_or(1); - args.all()? - .into_iter() - .map(|body| { - let item = ListItem::Enum(Some(number), Box::new(body)); - number += 1; - item - }) - .collect() - } - DESC | _ => args - .all()? - .into_iter() - .map(|item| ListItem::Desc(Box::new(item))) - .collect(), - }; - - Ok(Self { tight: args.named("tight")?.unwrap_or(true), items }.pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "tight" => Some(Value::Bool(self.tight)), - "items" => { - Some(Value::Array(self.items.items().map(|item| item.encode()).collect())) - } - _ => None, - } - } -} - -impl<const L: ListKind> Layout for ListNode<L> { - fn layout( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - regions: &Regions, - ) -> SourceResult<Fragment> { - let mut cells = vec![]; - let mut number = 1; - - let label = styles.get(Self::LABEL); - let indent = styles.get(Self::INDENT); - let body_indent = styles.get(Self::BODY_INDENT); - let gutter = if self.tight { - styles.get(ParNode::LEADING).into() - } else { - styles - .get(Self::SPACING) - .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount) - }; - - for (item, map) in self.items.iter() { - if let &ListItem::Enum(Some(n), _) = item { - number = n; - } - - cells.push(Content::empty()); - - let label = if L == LIST || L == ENUM { - label.resolve(world, L, number)?.styled_with_map(map.clone()) - } else { - Content::empty() - }; - - cells.push(label); - cells.push(Content::empty()); - - let body = match &item { - ListItem::List(body) => body.as_ref().clone(), - ListItem::Enum(_, body) => body.as_ref().clone(), - ListItem::Desc(item) => Content::sequence(vec![ - HNode { amount: (-body_indent).into(), weak: false }.pack(), - (item.term.clone() + TextNode::packed(':')).strong(), - SpaceNode.pack(), - item.body.clone(), - ]), - }; - - cells.push(body.styled_with_map(map.clone())); - number += 1; - } - - GridNode { - tracks: Axes::with_x(vec![ - TrackSizing::Relative(indent.into()), - TrackSizing::Auto, - TrackSizing::Relative(body_indent.into()), - TrackSizing::Auto, - ]), - gutter: Axes::with_y(vec![gutter.into()]), - cells, - } - .layout(world, styles, regions) - } -} - -/// An item in a list. -#[derive(Debug, Clone, Hash)] -pub enum ListItem { - /// An item of an unordered list. - List(Box<Content>), - /// An item of an ordered list. - Enum(Option<usize>, Box<Content>), - /// An item of a description list. - Desc(Box<DescItem>), -} - -impl ListItem { - /// What kind of item this is. - pub fn kind(&self) -> ListKind { - match self { - Self::List(_) => LIST, - Self::Enum { .. } => ENUM, - Self::Desc { .. } => DESC, - } - } - - /// Encode the item into a value. - fn encode(&self) -> Value { - match self { - Self::List(body) => Value::Content(body.as_ref().clone()), - Self::Enum(number, body) => Value::Dict(dict! { - "number" => match *number { - Some(n) => Value::Int(n as i64), - None => Value::None, - }, - "body" => Value::Content(body.as_ref().clone()), - }), - Self::Desc(item) => Value::Dict(dict! { - "term" => Value::Content(item.term.clone()), - "body" => Value::Content(item.body.clone()), - }), - } - } -} - -#[node] -impl ListItem {} - -/// A description list item. -#[derive(Debug, Clone, Hash)] -pub struct DescItem { - /// The term described by the list item. - pub term: Content, - /// The description of the term. - pub body: Content, -} - -castable! { - DescItem, - Expected: "dictionary with `term` and `body` keys", - Value::Dict(dict) => { - let term: Content = dict.get("term")?.clone().cast()?; - let body: Content = dict.get("body")?.clone().cast()?; - Self { term, body } - }, -} - -/// How to label a list. -pub type ListKind = usize; - -/// An unordered list. -pub const LIST: ListKind = 0; - -/// An ordered list. -pub const ENUM: ListKind = 1; - -/// A description list. -pub const DESC: ListKind = 2; - -/// How to label a list or enumeration. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Label { - /// The default labelling. - Default, - /// A pattern with prefix, numbering, lower / upper case and suffix. - Pattern(NumberingPattern), - /// Bare content. - Content(Content), - /// A closure mapping from an item number to a value. - Func(Func, Span), -} - -impl Label { - /// Resolve the label based on the level. - pub fn resolve( - &self, - world: Tracked<dyn World>, - kind: ListKind, - number: usize, - ) -> SourceResult<Content> { - Ok(match self { - Self::Default => match kind { - LIST => TextNode::packed('•'), - ENUM => TextNode::packed(format_eco!("{}.", number)), - DESC | _ => panic!("description lists don't have a label"), - }, - Self::Pattern(pattern) => TextNode::packed(pattern.apply(number)), - Self::Content(content) => content.clone(), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(number as i64)]); - func.call_detached(world, args)?.display() - } - }) - } -} - -impl Cast<Spanned<Value>> for Label { - fn is(value: &Spanned<Value>) -> bool { - matches!(&value.v, Value::Content(_) | Value::Func(_)) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::None => Ok(Self::Content(Content::empty())), - Value::Str(v) => Ok(Self::Pattern(v.parse()?)), - Value::Content(v) => Ok(Self::Content(v)), - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => Err(format_eco!( - "expected string, content or function, found {}", - v.type_name(), - )), - } - } -} diff --git a/library/src/structure/mod.rs b/library/src/structure/mod.rs deleted file mode 100644 index a1c27eed..00000000 --- a/library/src/structure/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Document structuring. - -mod document; -mod heading; -mod list; -mod reference; -mod table; - -pub use self::document::*; -pub use self::heading::*; -pub use self::list::*; -pub use self::reference::*; -pub use self::table::*; diff --git a/library/src/structure/reference.rs b/library/src/structure/reference.rs deleted file mode 100644 index 948aa6f6..00000000 --- a/library/src/structure/reference.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::prelude::*; -use crate::text::TextNode; - -/// A reference to a label. -#[derive(Debug, Hash)] -pub struct RefNode(pub EcoString); - -#[node(Show)] -impl RefNode { - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self(args.expect("target")?).pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "target" => Some(Value::Str(self.0.clone().into())), - _ => None, - } - } -} - -impl Show for RefNode { - fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content { - TextNode::packed(format_eco!("@{}", self.0)) - } -} diff --git a/library/src/structure/table.rs b/library/src/structure/table.rs deleted file mode 100644 index bb900f3d..00000000 --- a/library/src/structure/table.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::layout::{GridNode, TrackSizing, TrackSizings}; -use crate::prelude::*; - -/// A table of items. -#[derive(Debug, Hash)] -pub struct TableNode { - /// Defines sizing for content rows and columns. - pub tracks: Axes<Vec<TrackSizing>>, - /// Defines sizing of gutter rows and columns between content. - pub gutter: Axes<Vec<TrackSizing>>, - /// The content to be arranged in the table. - pub cells: Vec<Content>, -} - -#[node(Layout)] -impl TableNode { - /// How to fill the cells. - #[property(referenced)] - pub const FILL: Celled<Option<Paint>> = Celled::Value(None); - /// How to stroke the cells. - #[property(resolve, fold)] - pub const STROKE: Option<PartialStroke> = Some(PartialStroke::default()); - /// How much to pad the cells's content. - pub const PADDING: Rel<Length> = Abs::pt(5.0).into(); - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - let TrackSizings(columns) = args.named("columns")?.unwrap_or_default(); - let TrackSizings(rows) = args.named("rows")?.unwrap_or_default(); - let TrackSizings(base_gutter) = args.named("gutter")?.unwrap_or_default(); - let column_gutter = args.named("column-gutter")?.map(|TrackSizings(v)| v); - let row_gutter = args.named("row-gutter")?.map(|TrackSizings(v)| v); - Ok(Self { - tracks: Axes::new(columns, rows), - gutter: Axes::new( - column_gutter.unwrap_or_else(|| base_gutter.clone()), - row_gutter.unwrap_or(base_gutter), - ), - cells: args.all()?, - } - .pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "cells" => Some(Value::Array( - self.cells.iter().cloned().map(Value::Content).collect(), - )), - _ => None, - } - } -} - -impl Layout for TableNode { - fn layout( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - regions: &Regions, - ) -> SourceResult<Fragment> { - let fill = styles.get(Self::FILL); - let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); - let padding = styles.get(Self::PADDING); - - let cols = self.tracks.x.len().max(1); - let cells = self - .cells - .iter() - .cloned() - .enumerate() - .map(|(i, child)| { - let mut child = child.padded(Sides::splat(padding)); - - if let Some(stroke) = stroke { - child = child.stroked(stroke); - } - - let x = i % cols; - let y = i / cols; - if let Some(fill) = fill.resolve(world, x, y)? { - child = child.filled(fill); - } - - Ok(child) - }) - .collect::<SourceResult<_>>()?; - - GridNode { - tracks: self.tracks.clone(), - gutter: self.gutter.clone(), - cells, - } - .layout(world, styles, regions) - } -} - -/// A value that can be configured per cell. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Celled<T> { - /// A bare value, the same for all cells. - Value(T), - /// A closure mapping from cell coordinates to a value. - Func(Func, Span), -} - -impl<T: Cast + Clone> Celled<T> { - /// Resolve the value based on the cell position. - pub fn resolve( - &self, - world: Tracked<dyn World>, - x: usize, - y: usize, - ) -> SourceResult<T> { - Ok(match self { - Self::Value(value) => value.clone(), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(world, args)?.cast().at(*span)? - } - }) - } -} - -impl<T: Cast> Cast<Spanned<Value>> for Celled<T> { - fn is(value: &Spanned<Value>) -> bool { - matches!(&value.v, Value::Func(_)) || T::is(&value.v) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => T::cast(v) - .map(Self::Value) - .map_err(|msg| with_alternative(msg, "function")), - } - } -} |
