summaryrefslogtreecommitdiff
path: root/library/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-21 12:50:33 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-21 12:50:33 +0100
commitba294e2670814243122c9e4f560e0f21a4bec13d (patch)
tree36bdf987dc53729bb4a176713b40e2ca7c08a874 /library/src
parent959df6da3b6e3c5876e9d5627cb2970e32a0a37f (diff)
Split up list type into three separate types and document them
Diffstat (limited to 'library/src')
-rw-r--r--library/src/basics/desc.rs183
-rw-r--r--library/src/basics/enum.rs251
-rw-r--r--library/src/basics/heading.rs4
-rw-r--r--library/src/basics/list.rs316
-rw-r--r--library/src/basics/mod.rs5
-rw-r--r--library/src/compute/data.rs8
-rw-r--r--library/src/compute/foundations.rs3
-rw-r--r--library/src/layout/mod.rs55
-rw-r--r--library/src/lib.rs8
-rw-r--r--library/src/text/deco.rs3
-rw-r--r--library/src/text/mod.rs20
-rw-r--r--library/src/text/raw.rs4
12 files changed, 596 insertions, 264 deletions
diff --git a/library/src/basics/desc.rs b/library/src/basics/desc.rs
new file mode 100644
index 00000000..2c764c59
--- /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<DescItem>,
+}
+
+#[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<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 DescNode {
+ 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 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 00000000..f0ca217a
--- /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<NonZeroUsize>, 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<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.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<Content> {
+ 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<Spanned<Value>> for EnumNumbering {
+ fn is(value: &Spanned<Value>) -> bool {
+ matches!(&value.v, Value::Content(_) | Value::Func(_))
+ }
+
+ fn cast(value: Spanned<Value>) -> StrResult<Self> {
+ 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 12a6ac66..925d23a2 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 ca60576c..eaba6594 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.
+/// When using the list syntax, adjacents items are automatically collected
+/// into lists, even through constructs like for loops.
+///
+/// ### Example
+/// ```
+/// #for letter in "ABC" [
+/// - Letter #letter
+/// ]
+/// ```
///
/// - tight: bool (named)
-/// Makes the list more compact, if enabled. This looks better if the items
-/// fit into a single line each.
+/// 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
/// ```
-/// #show columns.with(2)
-/// #list(tight: true)[Tight][List]
-/// #colbreak()
-/// #list(tight: false)[Wide][List]
+/// - 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<const L: ListKind = LIST> {
+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<ListItem>,
+ pub items: StyleVec<Content>,
}
-/// An ordered list.
-pub type EnumNode = ListNode<ENUM>;
-
-/// A description list.
-pub type DescNode = ListNode<DESC>;
-
#[node]
-impl<const L: ListKind> ListNode<L> {
- /// 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<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: 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<Value> {
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<const L: ListKind> Layout for ListNode<L> {
+impl Layout for ListNode {
fn layout(
&self,
vt: &mut Vt,
styles: StyleChain,
regions: Regions,
) -> SourceResult<Fragment> {
- 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<const L: ListKind> Layout for ListNode<L> {
.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<const L: ListKind> Layout for ListNode<L> {
.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<Content>),
- /// An item of an ordered list.
- Enum(Option<NonZeroUsize>, 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.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<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.get() as i64)]);
- func.call_detached(vt.world(), args)?.display()
- }
- })
- }
-}
-
-impl Cast<Spanned<Value>> for ListLabel {
- fn is(value: &Spanned<Value>) -> bool {
- matches!(
- &value.v,
- Value::None | Value::Str(_) | 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 => 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 5916df6b..431cb004 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 3751292c..43d06753 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 91c97ecc..06795887 100644
--- a/library/src/compute/foundations.rs
+++ b/library/src/compute/foundations.rs
@@ -41,7 +41,8 @@ pub fn type_(args: &mut Args) -> SourceResult<Value> {
/// ```
/// { 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 afa1344f..bd4b0430 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::<ListItem>() {
- 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::<ParbreakNode>());
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::<LIST> { tight: self.tight, items }.pack(),
- ENUM => ListNode::<ENUM> { tight: self.tight, items }.pack(),
- DESC | _ => ListNode::<DESC> { 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<NonZeroUsize>, 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 6b6cae17..26727711 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 f74a45a2..6f454573 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 191b1fb8..4a25197d 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 938224d0..3cf7e8e7 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("+") {