diff --git a/library/src/basics/desc.rs b/library/src/basics/desc.rs new file mode 100644 index 000000000..2c764c595 --- /dev/null +++ b/library/src/basics/desc.rs @@ -0,0 +1,183 @@ +use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing}; +use crate::prelude::*; +use crate::text::{SpaceNode, TextNode}; + +/// # Description 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 description list +/// item. +/// +/// ## Example +/// ``` +/// / Ligature: A merged glyph. +/// / Kerning: A spacing adjustment +/// between two adjacent letters. +/// ``` +/// +/// ## Parameters +/// - items: Content (positional, variadic) +/// The descrition list's children. +/// +/// When using the description list syntax, adjacents items are automatically +/// collected into description 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 [description list +/// spacing](@desc/spacing). If it is `{true}`, they use normal +/// [leading](@par/leading) instead. This makes the description list more +/// compact, which can look better if the items are short. +/// +/// ### Example +/// ``` +/// / Fact: If a description 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 DescNode { + /// If true, the items are separated by leading instead of list spacing. + pub tight: bool, + /// The individual bulleted or numbered items. + pub items: StyleVec, +} + +#[node] +impl DescNode { + /// The indentation of each item's term. + #[property(resolve)] + pub const INDENT: Length = Length::zero(); + + /// The hanging indent of the description. + /// + /// # Example + /// ``` + /// #set desc(hanging-indent: 0pt) + /// / Term: This description 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) description list. + /// + /// If set to `{auto}` uses the spacing [below blocks](@block/below). + pub const SPACING: Smart = Smart::Auto; + + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + Ok(Self { + tight: args.named("tight")?.unwrap_or(true), + items: args.all()?.into_iter().collect(), + } + .pack()) + } + + fn field(&self, name: &str) -> Option { + match name { + "tight" => Some(Value::Bool(self.tight)), + "items" => { + Some(Value::Array(self.items.items().map(|item| item.encode()).collect())) + } + _ => None, + } + } +} + +impl Layout for DescNode { + fn layout( + &self, + vt: &mut Vt, + styles: StyleChain, + regions: Regions, + ) -> SourceResult { + 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 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 description: Content, +} + +impl DescItem { + /// Encode the item into a value. + fn encode(&self) -> Value { + Value::Array(array![ + Value::Content(self.term.clone()), + Value::Content(self.description.clone()), + ]) + } +} + +castable! { + DescItem, + 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 } + }, +} diff --git a/library/src/basics/enum.rs b/library/src/basics/enum.rs new file mode 100644 index 000000000..f0ca217ac --- /dev/null +++ b/library/src/basics/enum.rs @@ -0,0 +1,251 @@ +use std::str::FromStr; + +use crate::compute::NumberingPattern; +use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; +use crate::prelude::*; +use crate::text::TextNode; + +/// # Enumeration +/// An ordered list. +/// +/// Displays a sequence of items vertically and numbers them consecutively. +/// +/// ## 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. +/// +/// ## 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] +/// ``` +/// +/// ## Parameters +/// - items: Content (positional, variadic) +/// The enumeration's children. +/// +/// When using the enum syntax, adjacents 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](@enum/spacing). If it is `{true}`, they use normal +/// [leading](@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, Content)>, +} + +#[node] +impl EnumNode { + /// How to number the enumeration. Accepts a + /// [numbering pattern](@numbering). + /// + /// # Example + /// ``` + /// #set enum(numbering: "(a)") + /// + /// + Different + /// + Numbering + /// + Style + /// ``` + #[property(referenced)] + pub const NUMBERING: EnumNumbering = + EnumNumbering::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](@block/below). + pub const SPACING: Smart = Smart::Auto; + + fn construct(_: &Vm, args: &mut Args) -> SourceResult { + 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 { + 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 { + 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.resolve(vt, number)?; + 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) + } +} + +/// How to number an enumeration. +#[derive(Debug, Clone, Hash)] +pub enum EnumNumbering { + /// A pattern with prefix, numbering, lower / upper case and suffix. + Pattern(NumberingPattern), + /// A closure mapping from an item's number to content. + Func(Func, Span), +} + +impl EnumNumbering { + /// Resolve the marker based on the number. + pub fn resolve(&self, vt: &Vt, number: NonZeroUsize) -> SourceResult { + Ok(match self { + Self::Pattern(pattern) => TextNode::packed(pattern.apply(&[number])), + Self::Func(func, span) => { + let args = Args::new(*span, [Value::Int(number.get() as i64)]); + func.call_detached(vt.world(), args)?.display() + } + }) + } +} + +impl Cast> for EnumNumbering { + fn is(value: &Spanned) -> bool { + matches!(&value.v, Value::Content(_) | Value::Func(_)) + } + + fn cast(value: Spanned) -> StrResult { + match value.v { + Value::Str(v) => Ok(Self::Pattern(v.parse()?)), + Value::Func(v) => Ok(Self::Func(v, value.span)), + v => Self::error(v), + } + } + + fn describe() -> CastInfo { + CastInfo::Union(vec![CastInfo::Type("string"), CastInfo::Type("function")]) + } +} diff --git a/library/src/basics/heading.rs b/library/src/basics/heading.rs index 12a6ac665..925d23a20 100644 --- a/library/src/basics/heading.rs +++ b/library/src/basics/heading.rs @@ -11,8 +11,8 @@ use crate::text::{SpaceNode, TextNode, TextSize}; /// 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.) Top-level heading indicates a top-level section of the document (not -/// the document's title). +/// 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 diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs index ca60576c7..eaba65943 100644 --- a/library/src/basics/list.rs +++ b/library/src/basics/list.rs @@ -1,28 +1,67 @@ -use crate::compute::NumberingPattern; -use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing}; +use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing}; use crate::prelude::*; -use crate::text::{SpaceNode, TextNode}; +use crate::text::TextNode; /// # List -/// An unordered (bulleted) or ordered (numbered) list. +/// An unordered list. +/// +/// Displays a sequence of items vertically, with each item introduced by a +/// marker. +/// +/// ## 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. +/// +/// ## Example +/// ``` +/// - *Content* +/// - Basics +/// - Text +/// - Math +/// - Layout +/// - Visualize +/// - Meta +/// +/// - *Compute* +/// #list( +/// [Foundations], +/// [Calculate], +/// [Create], +/// [Data Loading], +/// [Utility], +/// ) +/// ``` /// /// ## Parameters /// - items: Content (positional, variadic) -/// The contents of the list items. +/// The list's children. /// -/// - start: NonZeroUsize (named) -/// Which number to start the enumeration with. -/// -/// - tight: bool (named) -/// Makes the list more compact, if enabled. This looks better if the items -/// fit into a single line each. +/// When using the list syntax, adjacents items are automatically collected +/// into lists, even through constructs like for loops. /// /// ### Example /// ``` -/// #show columns.with(2) -/// #list(tight: true)[Tight][List] -/// #colbreak() -/// #list(tight: false)[Wide][List] +/// #for letter in "ABC" [ +/// - Letter #letter +/// ] +/// ``` +/// +/// - tight: bool (named) +/// If this is `{false}`, the items are spaced apart with +/// [list spacing](@list/spacing). If it is `{true}`, they use normal +/// [leading](@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 @@ -30,88 +69,67 @@ use crate::text::{SpaceNode, TextNode}; #[func] #[capable(Layout)] #[derive(Debug, Hash)] -pub struct ListNode { +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, + pub items: StyleVec, } -/// An ordered list. -pub type EnumNode = ListNode; - -/// A description list. -pub type DescNode = ListNode; - #[node] -impl ListNode { - /// How the list is labelled. +impl ListNode { + /// The marker which introduces each element. + /// + /// # Example + /// ``` + /// #set list(marker: [--]) + /// + /// - A more classic list + /// - With en-dashes + /// ``` #[property(referenced)] - pub const LABEL: ListLabel = ListLabel::Default; - /// The indentation of each item's label. + pub const MARKER: Content = TextNode::packed('•'); + + /// The indent of each item's marker. #[property(resolve)] pub const INDENT: Length = Length::zero(); - /// The space between the label and the body of each item. + + /// The spacing between the marker 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(); + 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](@block/below). pub const SPACING: Smart = Smart::Auto; fn construct(_: &Vm, args: &mut Args) -> SourceResult { - let items = match L { - LIST => args - .all()? - .into_iter() - .map(|body| ListItem::List(Box::new(body))) - .collect(), - ENUM => { - let mut number: NonZeroUsize = - args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap()); - args.all()? - .into_iter() - .map(|body| { - let item = ListItem::Enum(Some(number), Box::new(body)); - number = number.saturating_add(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()) + Ok(Self { + tight: args.named("tight")?.unwrap_or(true), + items: args.all()?.into_iter().collect(), + } + .pack()) } fn field(&self, name: &str) -> Option { match name { "tight" => Some(Value::Bool(self.tight)), - "items" => { - Some(Value::Array(self.items.items().map(|item| item.encode()).collect())) - } + "items" => Some(Value::Array( + self.items.items().cloned().map(Value::Content).collect(), + )), _ => None, } } } -impl Layout for ListNode { +impl Layout for ListNode { fn layout( &self, vt: &mut Vt, styles: StyleChain, regions: Regions, ) -> SourceResult { - let mut cells = vec![]; - let mut number = NonZeroUsize::new(1).unwrap(); - - let label = styles.get(Self::LABEL); + 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 { @@ -122,35 +140,12 @@ impl Layout for ListNode { .unwrap_or_else(|| styles.get(BlockNode::BELOW).amount) }; + let mut cells = vec![]; 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(vt, L, number)?.styled_with_map(map.clone()) - } else { - Content::empty() - }; - - cells.push(label); + cells.push(marker.clone()); 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 = number.saturating_add(1); + cells.push(item.clone().styled_with_map(map.clone())); } GridNode { @@ -166,142 +161,3 @@ impl Layout for ListNode { .layout(vt, styles, regions) } } - -/// An item in a list. -#[capable] -#[derive(Debug, Clone, Hash)] -pub enum ListItem { - /// An item of an unordered list. - List(Box), - /// An item of an ordered list. - Enum(Option, Box), - /// An item of a description list. - Desc(Box), -} - -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.get() 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, - mut dict: Dict => { - let term: Content = dict.take("term")?.cast()?; - let body: Content = dict.take("body")?.cast()?; - dict.finish(&["term", "body"])?; - 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, Hash)] -pub enum ListLabel { - /// 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 ListLabel { - /// Resolve the label based on the level. - pub fn resolve( - &self, - vt: &Vt, - kind: ListKind, - number: NonZeroUsize, - ) -> SourceResult { - 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.get() as i64)]); - func.call_detached(vt.world(), args)?.display() - } - }) - } -} - -impl Cast> for ListLabel { - fn is(value: &Spanned) -> bool { - matches!( - &value.v, - Value::None | Value::Str(_) | Value::Content(_) | Value::Func(_) - ) - } - - fn cast(value: Spanned) -> StrResult { - 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 => Self::error(v), - } - } - - fn describe() -> CastInfo { - CastInfo::Union(vec![ - CastInfo::Type("string"), - CastInfo::Type("content"), - CastInfo::Type("function"), - ]) - } -} diff --git a/library/src/basics/mod.rs b/library/src/basics/mod.rs index 5916df6bc..431cb004f 100644 --- a/library/src/basics/mod.rs +++ b/library/src/basics/mod.rs @@ -1,9 +1,14 @@ //! Common document elements. +mod desc; +#[path = "enum.rs"] +mod enum_; mod heading; mod list; mod table; +pub use self::desc::*; +pub use self::enum_::*; pub use self::heading::*; pub use self::list::*; pub use self::table::*; diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs index 3751292c4..43d067530 100644 --- a/library/src/compute/data.rs +++ b/library/src/compute/data.rs @@ -129,8 +129,7 @@ fn format_csv_error(error: csv::Error) -> String { /// ) /// #h(6pt) /// #set text(22pt, baseline: -8pt) -/// {day.temperature} -/// °{day.unit} +/// {day.temperature} °{day.unit} /// ] /// /// #forecast(json("monday.json")) @@ -180,10 +179,7 @@ fn convert_json(value: serde_json::Value) -> Value { /// Format the user-facing JSON error message. fn format_json_error(error: serde_json::Error) -> String { assert!(error.is_syntax() || error.is_eof()); - format!( - "failed to parse json file: syntax error in line {}", - error.line() - ) + format!("failed to parse json file: syntax error in line {}", error.line()) } /// # XML diff --git a/library/src/compute/foundations.rs b/library/src/compute/foundations.rs index 91c97ecc1..06795887a 100644 --- a/library/src/compute/foundations.rs +++ b/library/src/compute/foundations.rs @@ -41,7 +41,8 @@ pub fn type_(args: &mut Args) -> SourceResult { /// ``` /// { none } vs #repr(none) \ /// { "hello" } vs #repr("hello") \ -/// { (1, 2) } vs #repr((1, 2)) +/// { (1, 2) } vs #repr((1, 2)) \ +/// { [*Hi*] } vs #repr([*Hi*]) /// ``` /// /// ## Parameters diff --git a/library/src/layout/mod.rs b/library/src/layout/mod.rs index afa1344fe..bd4b04304 100644 --- a/library/src/layout/mod.rs +++ b/library/src/layout/mod.rs @@ -40,7 +40,7 @@ use typst::model::{ StyleVecBuilder, StyledNode, }; -use crate::basics::{DescNode, EnumNode, ListItem, ListNode, DESC, ENUM, LIST}; +use crate::basics::{DescItem, DescNode, EnumNode, ListNode}; use crate::meta::DocumentNode; use crate::prelude::*; use crate::shared::BehavedBuilder; @@ -589,12 +589,9 @@ impl<'a> ListBuilder<'a> { } if let Some(item) = content.to::() { - if self - .items - .items() - .next() - .map_or(true, |first| item.kind() == first.kind()) - { + if self.items.items().next().map_or(true, |first| { + std::mem::discriminant(item) == std::mem::discriminant(first) + }) { self.items.push(item.clone(), styles); self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::()); return true; @@ -607,10 +604,31 @@ impl<'a> ListBuilder<'a> { fn finish(self) -> (Content, StyleChain<'a>) { let (items, shared) = self.items.finish(); let item = items.items().next().unwrap(); - let output = match item.kind() { - LIST => ListNode:: { tight: self.tight, items }.pack(), - ENUM => ListNode:: { tight: self.tight, items }.pack(), - DESC | _ => ListNode:: { tight: self.tight, items }.pack(), + let output = match item { + ListItem::List(_) => ListNode { + tight: self.tight, + items: items.map(|item| match item { + ListItem::List(item) => item.clone(), + _ => panic!("wrong list item"), + }), + } + .pack(), + ListItem::Enum(..) => EnumNode { + tight: self.tight, + items: items.map(|item| match item { + ListItem::Enum(number, body) => (*number, body.clone()), + _ => panic!("wrong list item"), + }), + } + .pack(), + ListItem::Desc(_) => DescNode { + tight: self.tight, + items: items.map(|item| match item { + ListItem::Desc(item) => item.clone(), + _ => panic!("wrong list item"), + }), + } + .pack(), }; (output, shared) } @@ -625,3 +643,18 @@ impl Default for ListBuilder<'_> { } } } + +/// An item in a list. +#[capable] +#[derive(Debug, Clone, Hash)] +pub enum ListItem { + /// An item of an unordered list. + List(Content), + /// An item of an ordered list. + Enum(Option, Content), + /// An item of a description list. + Desc(DescItem), +} + +#[node] +impl ListItem {} diff --git a/library/src/lib.rs b/library/src/lib.rs index 6b6cae17d..267277118 100644 --- a/library/src/lib.rs +++ b/library/src/lib.rs @@ -198,10 +198,10 @@ fn items() -> LangItems { link: |url| meta::LinkNode::from_url(url).pack(), ref_: |target| meta::RefNode(target).pack(), heading: |level, body| basics::HeadingNode { level, title: body }.pack(), - list_item: |body| basics::ListItem::List(Box::new(body)).pack(), - enum_item: |number, body| basics::ListItem::Enum(number, Box::new(body)).pack(), - desc_item: |term, body| { - basics::ListItem::Desc(Box::new(basics::DescItem { term, body })).pack() + list_item: |body| layout::ListItem::List(body).pack(), + enum_item: |number, body| layout::ListItem::Enum(number, body).pack(), + desc_item: |term, description| { + layout::ListItem::Desc(basics::DescItem { term, description }).pack() }, math: |children, block| math::MathNode { children, block }.pack(), math_atom: |atom| math::AtomNode(atom).pack(), diff --git a/library/src/text/deco.rs b/library/src/text/deco.rs index f74a45a21..6f454573f 100644 --- a/library/src/text/deco.rs +++ b/library/src/text/deco.rs @@ -224,6 +224,9 @@ impl StrikeNode { /// How to stroke the line. The text color and thickness are read from the /// font tables if `{auto}`. /// + /// _Note:_ Please don't use this for real redaction as you can still + /// copy paste the text. + /// /// # Example /// ``` /// This is #strike(stroke: 1.5pt + red)[very stricken through]. \ diff --git a/library/src/text/mod.rs b/library/src/text/mod.rs index 191b1fb83..4a25197db 100644 --- a/library/src/text/mod.rs +++ b/library/src/text/mod.rs @@ -43,8 +43,8 @@ use crate::prelude::*; /// ``` /// /// ## Parameters -/// - family: EcoString (positional, variadic, settable) A prioritized sequence -/// of font families. +/// - family: EcoString (positional, variadic, settable) +/// A prioritized sequence of font families. /// /// When processing text, Typst tries all specified font families in order /// until it finds a font that has the necessary glyphs. In the example below, @@ -63,8 +63,8 @@ use crate::prelude::*; /// /// ``` /// -/// - body: Content (positional, required) Content in which all text is styled -/// according to the other arguments. +/// - body: Content (positional, required) +/// Content in which all text is styled according to the other arguments. /// /// ## Category /// text @@ -141,6 +141,7 @@ impl TextNode { /// #text(weight: "light")[Light] \ /// #text(weight: "regular")[Regular] \ /// #text(weight: "medium")[Medium] \ + /// #text(weight: 500)[Medium] \ /// #text(weight: "bold")[Bold] /// ``` pub const WEIGHT: FontWeight = FontWeight::REGULAR; @@ -296,10 +297,10 @@ impl TextNode { /// - `{rtl}`: Layout text from right to left. /// /// When writing in right-to-left scripts like Arabic or Hebrew, you should - /// set the language or direction. While individual runs of text are - /// automatically layouted in the correct direction, setting the dominant - /// direction gives the bidirectional reordering algorithm the necessary - /// information to correctly place punctuation and inline objects. + /// set the [text language](@text/lang) or direction. While individual runs + /// of text are automatically layouted in the correct direction, setting the + /// dominant direction gives the bidirectional reordering algorithm the + /// necessary information to correctly place punctuation and inline objects. /// Furthermore, setting the direction affects the alignment values `start` /// and `end`, which are equivalent to `left` and `right` in `ltr` text and /// the other way around in `rtl` text. @@ -319,6 +320,9 @@ impl TextNode { /// Whether to hyphenate text to improve line breaking. When `{auto}`, text /// will be hyphenated if and only if justification is enabled. /// + /// Setting the [text language](@text/lang) ensures that the correct + /// hyphenation patterns are used. + /// /// # Example /// ``` /// #set par(justify: true) diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs index 938224d04..3cf7e8e7e 100644 --- a/library/src/text/raw.rs +++ b/library/src/text/raw.rs @@ -41,8 +41,8 @@ use crate::prelude::*; /// /// ### Example /// ```` -/// // Parse numbers in raw blocks with the `mydsl` tag and -/// // sum them up. +/// // Parse numbers in raw blocks with the +/// // `mydsl` tag and sum them up. /// #show raw.where(lang: "mydsl"): it => { /// let sum = 0 /// for part in it.text.split("+") { diff --git a/src/model/eval.rs b/src/model/eval.rs index a9fa2e145..3223ef8f9 100644 --- a/src/model/eval.rs +++ b/src/model/eval.rs @@ -398,8 +398,8 @@ impl Eval for ast::DescItem { fn eval(&self, vm: &mut Vm) -> SourceResult { let term = self.term().eval(vm)?; - let body = self.body().eval(vm)?; - Ok((vm.items.desc_item)(term, body)) + let description = self.description().eval(vm)?; + Ok((vm.items.desc_item)(term, description)) } } diff --git a/src/model/library.rs b/src/model/library.rs index 8b767b0e3..41a5e8d41 100644 --- a/src/model/library.rs +++ b/src/model/library.rs @@ -64,7 +64,7 @@ pub struct LangItems { /// An item in an enumeration (ordered list): `+ ...` or `1. ...`. pub enum_item: fn(number: Option, body: Content) -> Content, /// An item in a description list: `/ Term: Details`. - pub desc_item: fn(term: Content, body: Content) -> Content, + pub desc_item: fn(term: Content, description: Content) -> Content, /// A mathematical formula: `$x$`, `$ x^2 $`. pub math: fn(children: Vec, block: bool) -> Content, /// An atom in a formula: `x`, `+`, `12`. diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index abbca5ec1..5847e8168 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -407,10 +407,10 @@ impl DescItem { } /// The description of the term. - pub fn body(&self) -> Markup { + pub fn description(&self) -> Markup { self.0 .cast_last_child() - .expect("description list item is missing body") + .expect("description list item is missing description") } } diff --git a/tests/ref/compiler/construct.png b/tests/ref/compiler/construct.png index 829b072b5..aac79268f 100644 Binary files a/tests/ref/compiler/construct.png and b/tests/ref/compiler/construct.png differ diff --git a/tests/typ/basics/desc.typ b/tests/typ/basics/desc.typ index 1bc926255..8147b4004 100644 --- a/tests/typ/basics/desc.typ +++ b/tests/typ/basics/desc.typ @@ -8,8 +8,8 @@ No: list \ --- // Test with constructor. #desc( - (term: [One], body: [First]), - (term: [Two], body: [Second]), + ([One], [First]), + ([Two], [Second]), ) --- @@ -32,7 +32,7 @@ No: list \ #set text(8pt) / First list: #lorem(4) -#set desc(body-indent: 30pt) +#set desc(hanging-indent: 30pt) / Second list: #lorem(4) --- @@ -40,7 +40,7 @@ No: list \ #show desc: it => table( columns: 2, padding: 3pt, - ..it.items.map(item => (emph(item.term), item.body)).flatten(), + ..it.items.map(item => (emph(item(0)), item(1))).flatten(), ) / A: One letter diff --git a/tests/typ/basics/enum.typ b/tests/typ/basics/enum.typ index 0c62a2de9..2bec3e9e9 100644 --- a/tests/typ/basics/enum.typ +++ b/tests/typ/basics/enum.typ @@ -17,16 +17,16 @@ --- // Test label pattern. -#set enum(label: "~ A:") +#set enum(numbering: "~ A:") 1. First + Second -#set enum(label: "(*)") +#set enum(numbering: "(*)") + A + B + C -#set enum(label: "i)") +#set enum(numbering: "i)") + A + B @@ -37,17 +37,17 @@ / Desc: List --- -// Test label closure. +// Test numbering with closure. #enum( start: 4, spacing: 0.65em - 3pt, tight: false, - label: n => text(fill: (red, green, blue)(mod(n, 3)), numbering("A", n)), + numbering: n => text(fill: (red, green, blue)(mod(n, 3)), numbering("A", n)), [Red], [Green], [Blue], ) --- -#set enum(label: n => n > 1) +#set enum(numbering: n => n > 1) + A + B @@ -57,9 +57,9 @@ No enum --- -// Error: 18-20 invalid numbering pattern -#set enum(label: "") +// Error: 22-24 invalid numbering pattern +#set enum(numbering: "") --- -// Error: 18-24 invalid numbering pattern -#set enum(label: "(())") +// Error: 22-28 invalid numbering pattern +#set enum(numbering: "(())") diff --git a/tests/typ/basics/list.typ b/tests/typ/basics/list.typ index 9ed5993a2..4a9481315 100644 --- a/tests/typ/basics/list.typ +++ b/tests/typ/basics/list.typ @@ -48,6 +48,6 @@ _Shopping list_ - B with 2 tabs --- -#set list(label: [-]) +#set list(marker: [-]) - Bare hyphen - is not a list diff --git a/tests/typ/compiler/construct.typ b/tests/typ/compiler/construct.typ index df5368b7d..3ed8fed31 100644 --- a/tests/typ/compiler/construct.typ +++ b/tests/typ/compiler/construct.typ @@ -10,8 +10,8 @@ // Ensure that constructor styles win, but not over outer styles. // The outer paragraph should be right-aligned, // but the B should be center-aligned. -#set list(label: [>]) -#list(label: [--])[ +#set list(marker: [>]) +#list(marker: [--])[ #rect(width: 2cm, fill: conifer, inset: 4pt, list[A]) ] @@ -27,5 +27,5 @@ A #rect(fill: yellow, inset: 5pt, rect()) B --- // The constructor property should still work // when there are recursive show rules. -#show list: set text(blue) -#list(label: "(a)", [A], list[B]) +#show enum: set text(blue) +#enum(numbering: "(a)", [A], enum[B]) diff --git a/tests/typ/compiler/set.typ b/tests/typ/compiler/set.typ index fc5053b1d..7414ad5e1 100644 --- a/tests/typ/compiler/set.typ +++ b/tests/typ/compiler/set.typ @@ -39,7 +39,7 @@ Hello *{x}* --- // Test relative path resolving in layout phase. #let choice = ("monkey.svg", "rhino.png", "tiger.jpg") -#set enum(label: n => { +#set enum(numbering: n => { let path = "../../res/" + choice(n - 1) move(dy: -0.15em, image(path, width: 1em, height: 1em)) })