diff options
Diffstat (limited to 'library/src/basics')
| -rw-r--r-- | library/src/basics/enum.rs | 206 | ||||
| -rw-r--r-- | library/src/basics/heading.rs | 200 | ||||
| -rw-r--r-- | library/src/basics/list.rs | 160 | ||||
| -rw-r--r-- | library/src/basics/mod.rs | 14 | ||||
| -rw-r--r-- | library/src/basics/table.rs | 236 | ||||
| -rw-r--r-- | library/src/basics/terms.rs | 178 |
6 files changed, 0 insertions, 994 deletions
diff --git a/library/src/basics/enum.rs b/library/src/basics/enum.rs deleted file mode 100644 index 2bff7133..00000000 --- a/library/src/basics/enum.rs +++ /dev/null @@ -1,206 +0,0 @@ -use std::str::FromStr; - -use crate::compute::{Numbering, NumberingPattern}; -use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; -use crate::prelude::*; - -/// # Numbered List -/// A numbered list. -/// -/// Displays a sequence of items vertically and numbers them consecutively. -/// -/// ## Example -/// ```example -/// Automatically numbered: -/// + Preparations -/// + Analysis -/// + Conclusions -/// -/// Manually numbered: -/// 2. What is the first step? -/// 5. I am confused. -/// + Moving on ... -/// -/// Function call. -/// #enum[First][Second] -/// ``` -/// -/// ## Syntax -/// This functions also has dedicated syntax: -/// -/// - Starting a line with a plus sign creates an automatically numbered -/// enumeration item. -/// - Start a line with a number followed by a dot creates an explicitly -/// numbered enumeration item. -/// -/// Enumeration items can contain multiple paragraphs and other block-level -/// content. All content that is indented more than an item's plus sign or dot -/// becomes part of that item. -/// -/// ## Parameters -/// - items: `Content` (positional, variadic) -/// The enumeration's children. -/// -/// When using the enum syntax, adjacent items are automatically collected -/// into enumerations, even through constructs like for loops. -/// -/// ```example -/// #for phase in ( -/// "Launch", -/// "Orbit", -/// "Descent", -/// ) [+ #phase] -/// ``` -/// -/// - start: `NonZeroUsize` (named) -/// Which number to start the enumeration with. -/// -/// ```example -/// #enum( -/// start: 3, -/// [Skipping], -/// [Ahead], -/// ) -/// ``` -/// -/// - tight: `bool` (named) -/// If this is `{false}`, the items are spaced apart with -/// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal -/// [leading]($func/par.leading) instead. This makes the enumeration more -/// compact, which can look better if the items are short. -/// -/// ```example -/// + If an enum has a lot of text, and -/// maybe other inline content, it -/// should not be tight anymore. -/// -/// + To make an enum wide, simply -/// insert a blank line between the -/// items. -/// ``` -/// -/// ## Category -/// basics -#[func] -#[capable(Layout)] -#[derive(Debug, Hash)] -pub struct EnumNode { - /// If true, the items are separated by leading instead of list spacing. - pub tight: bool, - /// The individual numbered items. - pub items: StyleVec<(Option<NonZeroUsize>, Content)>, -} - -#[node] -impl EnumNode { - /// How to number the enumeration. Accepts a - /// [numbering pattern or function]($func/numbering). - /// - /// ```example - /// #set enum(numbering: "(a)") - /// - /// + Different - /// + Numbering - /// + Style - /// ``` - #[property(referenced)] - pub const NUMBERING: Numbering = - Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()); - - /// The indentation of each item's label. - #[property(resolve)] - pub const INDENT: Length = Length::zero(); - - /// The space between the numbering and the body of each item. - #[property(resolve)] - pub const BODY_INDENT: Length = Em::new(0.5).into(); - - /// The spacing between the items of a wide (non-tight) enumeration. - /// - /// If set to `{auto}` uses the spacing [below blocks]($func/block.below). - pub const SPACING: Smart<Spacing> = Smart::Auto; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - let mut number: NonZeroUsize = - args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap()); - - Ok(Self { - tight: args.named("tight")?.unwrap_or(true), - items: args - .all()? - .into_iter() - .map(|body| { - let item = (Some(number), body); - number = number.saturating_add(1); - item - }) - .collect(), - } - .pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "tight" => Some(Value::Bool(self.tight)), - "items" => Some(Value::Array( - self.items - .items() - .map(|(number, body)| { - Value::Dict(dict! { - "number" => match *number { - Some(n) => Value::Int(n.get() as i64), - None => Value::None, - }, - "body" => Value::Content(body.clone()), - }) - }) - .collect(), - )), - _ => None, - } - } -} - -impl Layout for EnumNode { - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult<Fragment> { - let numbering = styles.get(Self::NUMBERING); - 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) - }; - - let mut cells = vec![]; - let mut number = NonZeroUsize::new(1).unwrap(); - for ((n, item), map) in self.items.iter() { - number = n.unwrap_or(number); - let resolved = numbering.apply(vt.world(), &[number])?.display(); - cells.push(Content::empty()); - cells.push(resolved.styled_with_map(map.clone())); - cells.push(Content::empty()); - cells.push(item.clone().styled_with_map(map.clone())); - number = number.saturating_add(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(vt, styles, regions) - } -} diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs deleted file mode 100644 index 7ae76920..00000000 --- a/library/src/basics/heading.rs +++ /dev/null @@ -1,200 +0,0 @@ -use typst::font::FontWeight; - -use crate::compute::Numbering; -use crate::layout::{BlockNode, VNode}; -use crate::prelude::*; -use crate::text::{SpaceNode, TextNode, TextSize}; - -/// # Heading -/// A section heading. -/// -/// With headings, you can structure your document into sections. Each heading -/// has a _level,_ which starts at one and is unbounded upwards. This level -/// indicates the logical role of the following content (section, subsection, -/// etc.) A top-level heading indicates a top-level section of the document -/// (not the document's title). -/// -/// Typst can automatically number your headings for you. To enable numbering, -/// specify how you want your headings to be numbered with a -/// [numbering pattern or function]($func/numbering). -/// -/// Independently from the numbering, Typst can also automatically generate an -/// [outline]($func/outline) of all headings for you. To exclude one or more -/// headings from this outline, you can set the `outlined` parameter to -/// `{false}`. -/// -/// ## Example -/// ```example -/// #set heading(numbering: "1.a)") -/// -/// = Introduction -/// In recent years, ... -/// -/// == Preliminaries -/// To start, ... -/// ``` -/// -/// ## Syntax -/// Headings have dedicated syntax: They can be created by starting a line with -/// one or multiple equals signs, followed by a space. The number of equals -/// signs determines the heading's logical nesting depth. -/// -/// ## Parameters -/// - title: `Content` (positional, required) -/// The heading's title. -/// -/// - level: `NonZeroUsize` (named) -/// The logical nesting depth of the heading, starting from one. -/// -/// ## Category -/// basics -#[func] -#[capable(Prepare, Show, Finalize)] -#[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 title: Content, -} - -#[node] -impl HeadingNode { - /// How to number the heading. Accepts a - /// [numbering pattern or function]($func/numbering). - /// - /// ```example - /// #set heading(numbering: "1.a.") - /// - /// = A section - /// == A subsection - /// === A sub-subsection - /// ``` - #[property(referenced)] - pub const NUMBERING: Option<Numbering> = None; - - /// Whether the heading should appear in the outline. - /// - /// ```example - /// #outline() - /// - /// #heading[Normal] - /// This is a normal heading. - /// - /// #heading(outlined: false)[Hidden] - /// This heading does not appear - /// in the outline. - /// ``` - pub const OUTLINED: bool = true; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - title: args.expect("title")?, - 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)), - "title" => Some(Value::Content(self.title.clone())), - _ => None, - } - } -} - -impl Prepare for HeadingNode { - fn prepare( - &self, - vt: &mut Vt, - mut this: Content, - styles: StyleChain, - ) -> SourceResult<Content> { - let my_id = vt.identify(&this); - - let mut counter = HeadingCounter::new(); - for (node_id, node) in vt.locate(Selector::node::<HeadingNode>()) { - if node_id == my_id { - break; - } - - let numbers = node.field("numbers").unwrap(); - if numbers != Value::None { - let heading = node.to::<Self>().unwrap(); - counter.advance(heading); - } - } - - let mut numbers = Value::None; - if let Some(numbering) = styles.get(Self::NUMBERING) { - numbers = numbering.apply(vt.world(), counter.advance(self))?; - } - - this.push_field("outlined", Value::Bool(styles.get(Self::OUTLINED))); - this.push_field("numbers", numbers); - - let meta = Meta::Node(my_id, this.clone()); - Ok(this.styled(Meta::DATA, vec![meta])) - } -} - -impl Show for HeadingNode { - fn show(&self, _: &mut Vt, this: &Content, _: StyleChain) -> SourceResult<Content> { - let mut realized = self.title.clone(); - let numbers = this.field("numbers").unwrap(); - if numbers != Value::None { - realized = numbers.display() + SpaceNode.pack() + realized; - } - Ok(BlockNode(realized).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) - } -} - -/// Counters through headings with different levels. -pub struct HeadingCounter(Vec<NonZeroUsize>); - -impl HeadingCounter { - /// Create a new heading counter. - pub fn new() -> Self { - Self(vec![]) - } - - /// Advance the counter and return the numbers for the given heading. - pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] { - let level = heading.level.get(); - - if self.0.len() >= level { - self.0[level - 1] = self.0[level - 1].saturating_add(1); - self.0.truncate(level); - } - - while self.0.len() < level { - self.0.push(NonZeroUsize::new(1).unwrap()); - } - - &self.0 - } -} diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs deleted file mode 100644 index 4b9fdafb..00000000 --- a/library/src/basics/list.rs +++ /dev/null @@ -1,160 +0,0 @@ -use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; -use crate::prelude::*; -use crate::text::TextNode; - -/// # Bullet List -/// A bullet list. -/// -/// Displays a sequence of items vertically, with each item introduced by a -/// marker. -/// -/// ## Example -/// ```example -/// - *Content* -/// - Basics -/// - Text -/// - Math -/// - Layout -/// - Visualize -/// - Meta -/// -/// - *Compute* -/// #list( -/// [Foundations], -/// [Calculate], -/// [Create], -/// [Data Loading], -/// [Utility], -/// ) -/// ``` -/// -/// ## Syntax -/// This functions also has dedicated syntax: Start a line with a hyphen, -/// followed by a space to create a list item. A list item can contain multiple -/// paragraphs and other block-level content. All content that is indented -/// more than an item's hyphen becomes part of that item. -/// -/// ## Parameters -/// - items: `Content` (positional, variadic) -/// The list's children. -/// -/// When using the list syntax, adjacent items are automatically collected -/// into lists, even through constructs like for loops. -/// -/// ```example -/// #for letter in "ABC" [ -/// - Letter #letter -/// ] -/// ``` -/// -/// - tight: `bool` (named) -/// If this is `{false}`, the items are spaced apart with [list -/// spacing]($func/list.spacing). If it is `{true}`, they use normal -/// [leading]($func/par.leading) instead. This makes the list more compact, -/// which can look better if the items are short. -/// -/// ```example -/// - If a list has a lot of text, and -/// maybe other inline content, it -/// should not be tight anymore. -/// -/// - To make a list wide, simply insert -/// a blank line between the items. -/// ``` -/// -/// ## Category -/// basics -#[func] -#[capable(Layout)] -#[derive(Debug, Hash)] -pub struct ListNode { - /// If true, the items are separated by leading instead of list spacing. - pub tight: bool, - /// The individual bulleted or numbered items. - pub items: StyleVec<Content>, -} - -#[node] -impl ListNode { - /// The marker which introduces each element. - /// - /// ```example - /// #set list(marker: [--]) - /// - /// - A more classic list - /// - With en-dashes - /// ``` - #[property(referenced)] - pub const MARKER: Content = TextNode::packed('•'); - - /// The indent of each item's marker. - #[property(resolve)] - pub const INDENT: Length = Length::zero(); - - /// The spacing between the marker and the body of each item. - #[property(resolve)] - pub const BODY_INDENT: Length = Em::new(0.5).into(); - - /// The spacing between the items of a wide (non-tight) list. - /// - /// If set to `{auto}` uses the spacing [below blocks]($func/block.below). - pub const SPACING: Smart<Spacing> = Smart::Auto; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - tight: args.named("tight")?.unwrap_or(true), - items: args.all()?.into_iter().collect(), - } - .pack()) - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "tight" => Some(Value::Bool(self.tight)), - "items" => Some(Value::Array( - self.items.items().cloned().map(Value::Content).collect(), - )), - _ => None, - } - } -} - -impl Layout for ListNode { - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult<Fragment> { - let marker = styles.get(Self::MARKER); - 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) - }; - - let mut cells = vec![]; - for (item, map) in self.items.iter() { - cells.push(Content::empty()); - cells.push(marker.clone()); - cells.push(Content::empty()); - cells.push(item.clone().styled_with_map(map.clone())); - } - - 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(vt, styles, regions) - } -} diff --git a/library/src/basics/mod.rs b/library/src/basics/mod.rs deleted file mode 100644 index 7520c42d..00000000 --- a/library/src/basics/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Common document elements. - -#[path = "enum.rs"] -mod enum_; -mod heading; -mod list; -mod table; -mod terms; - -pub use self::enum_::*; -pub use self::heading::*; -pub use self::list::*; -pub use self::table::*; -pub use self::terms::*; diff --git a/library/src/basics/table.rs b/library/src/basics/table.rs deleted file mode 100644 index 56df2cea..00000000 --- a/library/src/basics/table.rs +++ /dev/null @@ -1,236 +0,0 @@ -use crate::layout::{AlignNode, GridNode, TrackSizing, TrackSizings}; -use crate::prelude::*; - -/// # Table -/// A table of items. -/// -/// Tables are used to arrange content in cells. Cells can contain arbitrary -/// content, including multiple paragraphs and are specified in row-major order. -/// Because tables are just grids with configurable cell properties, refer to -/// the [grid documentation]($func/grid) for more information on how to size the -/// table tracks. -/// -/// ## Example -/// ```example -/// #table( -/// columns: (1fr, auto, auto), -/// inset: 10pt, -/// align: horizon, -/// [], [*Area*], [*Parameters*], -/// image("cylinder.svg"), -/// $ pi h (D^2 - d^2) / 4 $, -/// [ -/// $h$: height \ -/// $D$: outer radius \ -/// $d$: inner radius -/// ], -/// image("tetrahedron.svg"), -/// $ sqrt(2) / 12 a^3 $, -/// [$a$: edge length] -/// ) -/// ``` -/// -/// ## Parameters -/// - cells: `Content` (positional, variadic) -/// The contents of the table cells. -/// -/// - rows: `TrackSizings` (named) -/// Defines the row sizes. -/// See the [grid documentation]($func/grid) for more information on track -/// sizing. -/// -/// - columns: `TrackSizings` (named) -/// Defines the column sizes. -/// See the [grid documentation]($func/grid) for more information on track -/// sizing. -/// -/// - gutter: `TrackSizings` (named) -/// Defines the gaps between rows & columns. -/// See the [grid documentation]($func/grid) for more information on gutters. -/// -/// - column-gutter: `TrackSizings` (named) -/// Defines the gaps between columns. Takes precedence over `gutter`. -/// See the [grid documentation]($func/grid) for more information on gutters. -/// -/// - row-gutter: `TrackSizings` (named) -/// Defines the gaps between rows. Takes precedence over `gutter`. -/// See the [grid documentation]($func/grid) for more information on gutters. -/// -/// ## Category -/// basics -#[func] -#[capable(Layout)] -#[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] -impl TableNode { - /// How to fill the cells. - /// - /// This can be a color or a function that returns a color. The function is - /// passed the cell's column and row index, starting at zero. This can be - /// used to implement striped tables. - /// - /// ```example - /// #table( - /// fill: (col, _) => if calc.odd(col) { luma(240) } else { white }, - /// align: (col, row) => - /// if row == 0 { center } - /// else if col == 0 { left } - /// else { right }, - /// columns: 4, - /// [], [*Q1*], [*Q2*], [*Q3*], - /// [Revenue:], [1000 €], [2000 €], [3000 €], - /// [Expenses:], [500 €], [1000 €], [1500 €], - /// [Profit:], [500 €], [1000 €], [1500 €], - /// ) - /// ``` - #[property(referenced)] - pub const FILL: Celled<Option<Paint>> = Celled::Value(None); - - /// How to align the cell's content. - /// - /// This can either be a single alignment or a function that returns an - /// alignment. The function is passed the cell's column and row index, - /// starting at zero. If set to `{auto}`, the outer alignment is used. - #[property(referenced)] - pub const ALIGN: Celled<Smart<Axes<Option<GenAlign>>>> = Celled::Value(Smart::Auto); - - /// How to stroke the cells. - /// - /// This can be a color, a stroke width, both, or `{none}` to disable - /// the stroke. - #[property(resolve, fold)] - pub const STROKE: Option<PartialStroke> = Some(PartialStroke::default()); - - /// How much to pad the cells's content. - /// - /// The default value is `{5pt}`. - pub const INSET: 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 { - "columns" => Some(TrackSizing::encode_slice(&self.tracks.x)), - "rows" => Some(TrackSizing::encode_slice(&self.tracks.y)), - "column-gutter" => Some(TrackSizing::encode_slice(&self.gutter.x)), - "row-gutter" => Some(TrackSizing::encode_slice(&self.gutter.y)), - "cells" => Some(Value::Array( - self.cells.iter().cloned().map(Value::Content).collect(), - )), - _ => None, - } - } -} - -impl Layout for TableNode { - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult<Fragment> { - let fill = styles.get(Self::FILL); - let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default); - let inset = styles.get(Self::INSET); - let align = styles.get(Self::ALIGN); - - 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(inset)); - - let x = i % cols; - let y = i / cols; - if let Smart::Custom(alignment) = align.resolve(vt, x, y)? { - child = child.styled(AlignNode::ALIGNS, alignment) - } - - if let Some(stroke) = stroke { - child = child.stroked(stroke); - } - - if let Some(fill) = fill.resolve(vt, x, y)? { - child = child.filled(fill); - } - - Ok(child) - }) - .collect::<SourceResult<_>>()?; - - GridNode { - tracks: self.tracks.clone(), - gutter: self.gutter.clone(), - cells, - } - .layout(vt, 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), -} - -impl<T: Cast + Clone> Celled<T> { - /// Resolve the value based on the cell position. - pub fn resolve(&self, vt: &Vt, x: usize, y: usize) -> SourceResult<T> { - Ok(match self { - Self::Value(value) => value.clone(), - Self::Func(func) => { - let args = - Args::new(func.span(), [Value::Int(x as i64), Value::Int(y as i64)]); - func.call_detached(vt.world(), args)?.cast().at(func.span())? - } - }) - } -} - -impl<T: Cast> Cast for Celled<T> { - fn is(value: &Value) -> bool { - matches!(value, Value::Func(_)) || T::is(value) - } - - fn cast(value: Value) -> StrResult<Self> { - match value { - Value::Func(v) => Ok(Self::Func(v)), - v if T::is(&v) => Ok(Self::Value(T::cast(v)?)), - v => <Self as Cast>::error(v), - } - } - - fn describe() -> CastInfo { - T::describe() + CastInfo::Type("function") - } -} diff --git a/library/src/basics/terms.rs b/library/src/basics/terms.rs deleted file mode 100644 index 2e52c87d..00000000 --- a/library/src/basics/terms.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing}; -use crate::prelude::*; -use crate::text::{SpaceNode, TextNode}; - -/// # Term List -/// A list of terms and their descriptions. -/// -/// Displays a sequence of terms and their descriptions vertically. When the -/// descriptions span over multiple lines, they use hanging indent to -/// communicate the visual hierarchy. -/// -/// ## Syntax -/// This function also has dedicated syntax: Starting a line with a slash, -/// followed by a term, a colon and a description creates a term list item. -/// -/// ## Example -/// ```example -/// / Ligature: A merged glyph. -/// / Kerning: A spacing adjustment -/// between two adjacent letters. -/// ``` -/// -/// ## Parameters -/// - items: `Content` (positional, variadic) -/// The term list's children. -/// -/// When using the term list syntax, adjacent items are automatically -/// collected into term lists, even through constructs like for loops. -/// -/// ```example -/// #for year, product in ( -/// "1978": "TeX", -/// "1984": "LaTeX", -/// "2019": "Typst", -/// ) [/ #product: Born in #year.] -/// ``` -/// -/// - tight: `bool` (named) -/// If this is `{false}`, the items are spaced apart with [term list -/// spacing]($func/terms.spacing). If it is `{true}`, they use normal -/// [leading]($func/par.leading) instead. This makes the term list more -/// compact, which can look better if the items are short. -/// -/// ```example -/// / Fact: If a term list has a lot -/// of text, and maybe other inline -/// content, it should not be tight -/// anymore. -/// -/// / Tip: To make it wide, simply -/// insert a blank line between the -/// items. -/// ``` -/// -/// ## Category -/// basics -#[func] -#[capable(Layout)] -#[derive(Debug, Hash)] -pub struct TermsNode { - /// If true, the items are separated by leading instead of list spacing. - pub tight: bool, - /// The individual bulleted or numbered items. - pub items: StyleVec<TermItem>, -} - -#[node] -impl TermsNode { - /// The indentation of each item's term. - #[property(resolve)] - pub const INDENT: Length = Length::zero(); - - /// The hanging indent of the description. - /// - /// ```example - /// #set terms(hanging-indent: 0pt) - /// / Term: This term list does not - /// make use of hanging indents. - /// ``` - #[property(resolve)] - pub const HANGING_INDENT: Length = Em::new(1.0).into(); - - /// The spacing between the items of a wide (non-tight) term list. - /// - /// If set to `{auto}` uses the spacing [below blocks]($func/block.below). - pub const SPACING: Smart<Spacing> = Smart::Auto; - - fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> { - Ok(Self { - tight: args.named("tight")?.unwrap_or(true), - items: args.all()?.into_iter().collect(), - } - .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 Layout for TermsNode { - fn layout( - &self, - vt: &mut Vt, - styles: StyleChain, - regions: Regions, - ) -> SourceResult<Fragment> { - let indent = styles.get(Self::INDENT); - let body_indent = styles.get(Self::HANGING_INDENT); - let gutter = if self.tight { - styles.get(ParNode::LEADING).into() - } else { - styles - .get(Self::SPACING) - .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount) - }; - - let mut cells = vec![]; - for (item, map) in self.items.iter() { - let body = Content::sequence(vec![ - HNode { amount: (-body_indent).into(), weak: false }.pack(), - (item.term.clone() + TextNode::packed(':')).strong(), - SpaceNode.pack(), - item.description.clone(), - ]); - - cells.push(Content::empty()); - cells.push(body.styled_with_map(map.clone())); - } - - GridNode { - tracks: Axes::with_x(vec![ - TrackSizing::Relative((indent + body_indent).into()), - TrackSizing::Auto, - ]), - gutter: Axes::with_y(vec![gutter.into()]), - cells, - } - .layout(vt, styles, regions) - } -} - -/// A term list item. -#[derive(Debug, Clone, Hash)] -pub struct TermItem { - /// The term described by the list item. - pub term: Content, - /// The description of the term. - pub description: Content, -} - -impl TermItem { - /// Encode the item into a value. - fn encode(&self) -> Value { - Value::Array(array![ - Value::Content(self.term.clone()), - Value::Content(self.description.clone()), - ]) - } -} - -castable! { - TermItem, - array: Array => { - let mut iter = array.into_iter(); - let (term, description) = match (iter.next(), iter.next(), iter.next()) { - (Some(a), Some(b), None) => (a.cast()?, b.cast()?), - _ => Err("array must contain exactly two entries")?, - }; - Self { term, description } - }, -} |
