mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Split up list type into three separate types and document them
This commit is contained in:
parent
959df6da3b
commit
ba294e2670
183
library/src/basics/desc.rs
Normal file
183
library/src/basics/desc.rs
Normal file
@ -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 }
|
||||||
|
},
|
||||||
|
}
|
251
library/src/basics/enum.rs
Normal file
251
library/src/basics/enum.rs
Normal file
@ -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")])
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,8 @@ use crate::text::{SpaceNode, TextNode, TextSize};
|
|||||||
/// With headings, you can structure your document into sections. Each 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
|
/// has a _level,_ which starts at one and is unbounded upwards. This level
|
||||||
/// indicates the logical role of the following content (section, subsection,
|
/// indicates the logical role of the following content (section, subsection,
|
||||||
/// etc.) Top-level heading indicates a top-level section of the document (not
|
/// etc.) A top-level heading indicates a top-level section of the document
|
||||||
/// the document's title).
|
/// (not the document's title).
|
||||||
///
|
///
|
||||||
/// Typst can automatically number your headings for you. To enable numbering,
|
/// Typst can automatically number your headings for you. To enable numbering,
|
||||||
/// specify how you want your headings to be numbered with a [numbering
|
/// specify how you want your headings to be numbered with a [numbering
|
||||||
|
@ -1,28 +1,67 @@
|
|||||||
use crate::compute::NumberingPattern;
|
use crate::layout::{BlockNode, GridNode, ParNode, Spacing, TrackSizing};
|
||||||
use crate::layout::{BlockNode, GridNode, HNode, ParNode, Spacing, TrackSizing};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::{SpaceNode, TextNode};
|
use crate::text::TextNode;
|
||||||
|
|
||||||
/// # List
|
/// # 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
|
/// ## Parameters
|
||||||
/// - items: Content (positional, variadic)
|
/// - items: Content (positional, variadic)
|
||||||
/// The contents of the list items.
|
/// The list's children.
|
||||||
///
|
///
|
||||||
/// - start: NonZeroUsize (named)
|
/// When using the list syntax, adjacents items are automatically collected
|
||||||
/// Which number to start the enumeration with.
|
/// into lists, even through constructs like for loops.
|
||||||
///
|
|
||||||
/// - tight: bool (named)
|
|
||||||
/// Makes the list more compact, if enabled. This looks better if the items
|
|
||||||
/// fit into a single line each.
|
|
||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #show columns.with(2)
|
/// #for letter in "ABC" [
|
||||||
/// #list(tight: true)[Tight][List]
|
/// - Letter #letter
|
||||||
/// #colbreak()
|
/// ]
|
||||||
/// #list(tight: false)[Wide][List]
|
/// ```
|
||||||
|
///
|
||||||
|
/// - 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
|
/// ## Category
|
||||||
@ -30,88 +69,67 @@ use crate::text::{SpaceNode, TextNode};
|
|||||||
#[func]
|
#[func]
|
||||||
#[capable(Layout)]
|
#[capable(Layout)]
|
||||||
#[derive(Debug, Hash)]
|
#[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.
|
/// If true, the items are separated by leading instead of list spacing.
|
||||||
pub tight: bool,
|
pub tight: bool,
|
||||||
/// The individual bulleted or numbered items.
|
/// 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]
|
#[node]
|
||||||
impl<const L: ListKind> ListNode<L> {
|
impl ListNode {
|
||||||
/// How the list is labelled.
|
/// The marker which introduces each element.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```
|
||||||
|
/// #set list(marker: [--])
|
||||||
|
///
|
||||||
|
/// - A more classic list
|
||||||
|
/// - With en-dashes
|
||||||
|
/// ```
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
pub const LABEL: ListLabel = ListLabel::Default;
|
pub const MARKER: Content = TextNode::packed('•');
|
||||||
/// The indentation of each item's label.
|
|
||||||
|
/// The indent of each item's marker.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
pub const INDENT: Length = Length::zero();
|
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)]
|
#[property(resolve)]
|
||||||
pub const BODY_INDENT: Length = Em::new(match L {
|
pub const BODY_INDENT: Length = Em::new(0.5).into();
|
||||||
LIST | ENUM => 0.5,
|
|
||||||
DESC | _ => 1.0,
|
|
||||||
})
|
|
||||||
.into();
|
|
||||||
/// The spacing between the items of a wide (non-tight) list.
|
/// 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;
|
pub const SPACING: Smart<Spacing> = Smart::Auto;
|
||||||
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let items = match L {
|
Ok(Self {
|
||||||
LIST => args
|
tight: args.named("tight")?.unwrap_or(true),
|
||||||
.all()?
|
items: args.all()?.into_iter().collect(),
|
||||||
.into_iter()
|
}
|
||||||
.map(|body| ListItem::List(Box::new(body)))
|
.pack())
|
||||||
.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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
fn field(&self, name: &str) -> Option<Value> {
|
||||||
match name {
|
match name {
|
||||||
"tight" => Some(Value::Bool(self.tight)),
|
"tight" => Some(Value::Bool(self.tight)),
|
||||||
"items" => {
|
"items" => Some(Value::Array(
|
||||||
Some(Value::Array(self.items.items().map(|item| item.encode()).collect()))
|
self.items.items().cloned().map(Value::Content).collect(),
|
||||||
}
|
)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: ListKind> Layout for ListNode<L> {
|
impl Layout for ListNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
vt: &mut Vt,
|
vt: &mut Vt,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut cells = vec![];
|
let marker = styles.get(Self::MARKER);
|
||||||
let mut number = NonZeroUsize::new(1).unwrap();
|
|
||||||
|
|
||||||
let label = styles.get(Self::LABEL);
|
|
||||||
let indent = styles.get(Self::INDENT);
|
let indent = styles.get(Self::INDENT);
|
||||||
let body_indent = styles.get(Self::BODY_INDENT);
|
let body_indent = styles.get(Self::BODY_INDENT);
|
||||||
let gutter = if self.tight {
|
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)
|
.unwrap_or_else(|| styles.get(BlockNode::BELOW).amount)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut cells = vec![];
|
||||||
for (item, map) in self.items.iter() {
|
for (item, map) in self.items.iter() {
|
||||||
if let &ListItem::Enum(Some(n), _) = item {
|
|
||||||
number = n;
|
|
||||||
}
|
|
||||||
|
|
||||||
cells.push(Content::empty());
|
cells.push(Content::empty());
|
||||||
|
cells.push(marker.clone());
|
||||||
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(Content::empty());
|
cells.push(Content::empty());
|
||||||
|
cells.push(item.clone().styled_with_map(map.clone()));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GridNode {
|
GridNode {
|
||||||
@ -166,142 +161,3 @@ impl<const L: ListKind> Layout for ListNode<L> {
|
|||||||
.layout(vt, styles, regions)
|
.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"),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
//! Common document elements.
|
//! Common document elements.
|
||||||
|
|
||||||
|
mod desc;
|
||||||
|
#[path = "enum.rs"]
|
||||||
|
mod enum_;
|
||||||
mod heading;
|
mod heading;
|
||||||
mod list;
|
mod list;
|
||||||
mod table;
|
mod table;
|
||||||
|
|
||||||
|
pub use self::desc::*;
|
||||||
|
pub use self::enum_::*;
|
||||||
pub use self::heading::*;
|
pub use self::heading::*;
|
||||||
pub use self::list::*;
|
pub use self::list::*;
|
||||||
pub use self::table::*;
|
pub use self::table::*;
|
||||||
|
@ -129,8 +129,7 @@ fn format_csv_error(error: csv::Error) -> String {
|
|||||||
/// )
|
/// )
|
||||||
/// #h(6pt)
|
/// #h(6pt)
|
||||||
/// #set text(22pt, baseline: -8pt)
|
/// #set text(22pt, baseline: -8pt)
|
||||||
/// {day.temperature}
|
/// {day.temperature} °{day.unit}
|
||||||
/// °{day.unit}
|
|
||||||
/// ]
|
/// ]
|
||||||
///
|
///
|
||||||
/// #forecast(json("monday.json"))
|
/// #forecast(json("monday.json"))
|
||||||
@ -180,10 +179,7 @@ fn convert_json(value: serde_json::Value) -> Value {
|
|||||||
/// Format the user-facing JSON error message.
|
/// Format the user-facing JSON error message.
|
||||||
fn format_json_error(error: serde_json::Error) -> String {
|
fn format_json_error(error: serde_json::Error) -> String {
|
||||||
assert!(error.is_syntax() || error.is_eof());
|
assert!(error.is_syntax() || error.is_eof());
|
||||||
format!(
|
format!("failed to parse json file: syntax error in line {}", error.line())
|
||||||
"failed to parse json file: syntax error in line {}",
|
|
||||||
error.line()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # XML
|
/// # XML
|
||||||
|
@ -41,7 +41,8 @@ pub fn type_(args: &mut Args) -> SourceResult<Value> {
|
|||||||
/// ```
|
/// ```
|
||||||
/// { none } vs #repr(none) \
|
/// { none } vs #repr(none) \
|
||||||
/// { "hello" } vs #repr("hello") \
|
/// { "hello" } vs #repr("hello") \
|
||||||
/// { (1, 2) } vs #repr((1, 2))
|
/// { (1, 2) } vs #repr((1, 2)) \
|
||||||
|
/// { [*Hi*] } vs #repr([*Hi*])
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
|
@ -40,7 +40,7 @@ use typst::model::{
|
|||||||
StyleVecBuilder, StyledNode,
|
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::meta::DocumentNode;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::shared::BehavedBuilder;
|
use crate::shared::BehavedBuilder;
|
||||||
@ -589,12 +589,9 @@ impl<'a> ListBuilder<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(item) = content.to::<ListItem>() {
|
if let Some(item) = content.to::<ListItem>() {
|
||||||
if self
|
if self.items.items().next().map_or(true, |first| {
|
||||||
.items
|
std::mem::discriminant(item) == std::mem::discriminant(first)
|
||||||
.items()
|
}) {
|
||||||
.next()
|
|
||||||
.map_or(true, |first| item.kind() == first.kind())
|
|
||||||
{
|
|
||||||
self.items.push(item.clone(), styles);
|
self.items.push(item.clone(), styles);
|
||||||
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
|
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakNode>());
|
||||||
return true;
|
return true;
|
||||||
@ -607,10 +604,31 @@ impl<'a> ListBuilder<'a> {
|
|||||||
fn finish(self) -> (Content, StyleChain<'a>) {
|
fn finish(self) -> (Content, StyleChain<'a>) {
|
||||||
let (items, shared) = self.items.finish();
|
let (items, shared) = self.items.finish();
|
||||||
let item = items.items().next().unwrap();
|
let item = items.items().next().unwrap();
|
||||||
let output = match item.kind() {
|
let output = match item {
|
||||||
LIST => ListNode::<LIST> { tight: self.tight, items }.pack(),
|
ListItem::List(_) => ListNode {
|
||||||
ENUM => ListNode::<ENUM> { tight: self.tight, items }.pack(),
|
tight: self.tight,
|
||||||
DESC | _ => ListNode::<DESC> { tight: self.tight, items }.pack(),
|
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)
|
(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 {}
|
||||||
|
@ -198,10 +198,10 @@ fn items() -> LangItems {
|
|||||||
link: |url| meta::LinkNode::from_url(url).pack(),
|
link: |url| meta::LinkNode::from_url(url).pack(),
|
||||||
ref_: |target| meta::RefNode(target).pack(),
|
ref_: |target| meta::RefNode(target).pack(),
|
||||||
heading: |level, body| basics::HeadingNode { level, title: body }.pack(),
|
heading: |level, body| basics::HeadingNode { level, title: body }.pack(),
|
||||||
list_item: |body| basics::ListItem::List(Box::new(body)).pack(),
|
list_item: |body| layout::ListItem::List(body).pack(),
|
||||||
enum_item: |number, body| basics::ListItem::Enum(number, Box::new(body)).pack(),
|
enum_item: |number, body| layout::ListItem::Enum(number, body).pack(),
|
||||||
desc_item: |term, body| {
|
desc_item: |term, description| {
|
||||||
basics::ListItem::Desc(Box::new(basics::DescItem { term, body })).pack()
|
layout::ListItem::Desc(basics::DescItem { term, description }).pack()
|
||||||
},
|
},
|
||||||
math: |children, block| math::MathNode { children, block }.pack(),
|
math: |children, block| math::MathNode { children, block }.pack(),
|
||||||
math_atom: |atom| math::AtomNode(atom).pack(),
|
math_atom: |atom| math::AtomNode(atom).pack(),
|
||||||
|
@ -224,6 +224,9 @@ impl StrikeNode {
|
|||||||
/// How to stroke the line. The text color and thickness are read from the
|
/// How to stroke the line. The text color and thickness are read from the
|
||||||
/// font tables if `{auto}`.
|
/// font tables if `{auto}`.
|
||||||
///
|
///
|
||||||
|
/// _Note:_ Please don't use this for real redaction as you can still
|
||||||
|
/// copy paste the text.
|
||||||
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
|
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
|
||||||
|
@ -43,8 +43,8 @@ use crate::prelude::*;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - family: EcoString (positional, variadic, settable) A prioritized sequence
|
/// - family: EcoString (positional, variadic, settable)
|
||||||
/// of font families.
|
/// A prioritized sequence of font families.
|
||||||
///
|
///
|
||||||
/// When processing text, Typst tries all specified font families in order
|
/// 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,
|
/// 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
|
/// - body: Content (positional, required)
|
||||||
/// according to the other arguments.
|
/// Content in which all text is styled according to the other arguments.
|
||||||
///
|
///
|
||||||
/// ## Category
|
/// ## Category
|
||||||
/// text
|
/// text
|
||||||
@ -141,6 +141,7 @@ impl TextNode {
|
|||||||
/// #text(weight: "light")[Light] \
|
/// #text(weight: "light")[Light] \
|
||||||
/// #text(weight: "regular")[Regular] \
|
/// #text(weight: "regular")[Regular] \
|
||||||
/// #text(weight: "medium")[Medium] \
|
/// #text(weight: "medium")[Medium] \
|
||||||
|
/// #text(weight: 500)[Medium] \
|
||||||
/// #text(weight: "bold")[Bold]
|
/// #text(weight: "bold")[Bold]
|
||||||
/// ```
|
/// ```
|
||||||
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
pub const WEIGHT: FontWeight = FontWeight::REGULAR;
|
||||||
@ -296,10 +297,10 @@ impl TextNode {
|
|||||||
/// - `{rtl}`: Layout text from right to left.
|
/// - `{rtl}`: Layout text from right to left.
|
||||||
///
|
///
|
||||||
/// When writing in right-to-left scripts like Arabic or Hebrew, you should
|
/// When writing in right-to-left scripts like Arabic or Hebrew, you should
|
||||||
/// set the language or direction. While individual runs of text are
|
/// set the [text language](@text/lang) or direction. While individual runs
|
||||||
/// automatically layouted in the correct direction, setting the dominant
|
/// of text are automatically layouted in the correct direction, setting the
|
||||||
/// direction gives the bidirectional reordering algorithm the necessary
|
/// dominant direction gives the bidirectional reordering algorithm the
|
||||||
/// information to correctly place punctuation and inline objects.
|
/// necessary information to correctly place punctuation and inline objects.
|
||||||
/// Furthermore, setting the direction affects the alignment values `start`
|
/// Furthermore, setting the direction affects the alignment values `start`
|
||||||
/// and `end`, which are equivalent to `left` and `right` in `ltr` text and
|
/// and `end`, which are equivalent to `left` and `right` in `ltr` text and
|
||||||
/// the other way around in `rtl` text.
|
/// the other way around in `rtl` text.
|
||||||
@ -319,6 +320,9 @@ impl TextNode {
|
|||||||
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
|
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
|
||||||
/// will be hyphenated if and only if justification is enabled.
|
/// 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
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// #set par(justify: true)
|
/// #set par(justify: true)
|
||||||
|
@ -41,8 +41,8 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// ### Example
|
/// ### Example
|
||||||
/// ````
|
/// ````
|
||||||
/// // Parse numbers in raw blocks with the `mydsl` tag and
|
/// // Parse numbers in raw blocks with the
|
||||||
/// // sum them up.
|
/// // `mydsl` tag and sum them up.
|
||||||
/// #show raw.where(lang: "mydsl"): it => {
|
/// #show raw.where(lang: "mydsl"): it => {
|
||||||
/// let sum = 0
|
/// let sum = 0
|
||||||
/// for part in it.text.split("+") {
|
/// for part in it.text.split("+") {
|
||||||
|
@ -398,8 +398,8 @@ impl Eval for ast::DescItem {
|
|||||||
|
|
||||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let term = self.term().eval(vm)?;
|
let term = self.term().eval(vm)?;
|
||||||
let body = self.body().eval(vm)?;
|
let description = self.description().eval(vm)?;
|
||||||
Ok((vm.items.desc_item)(term, body))
|
Ok((vm.items.desc_item)(term, description))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ pub struct LangItems {
|
|||||||
/// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
|
/// An item in an enumeration (ordered list): `+ ...` or `1. ...`.
|
||||||
pub enum_item: fn(number: Option<NonZeroUsize>, body: Content) -> Content,
|
pub enum_item: fn(number: Option<NonZeroUsize>, body: Content) -> Content,
|
||||||
/// An item in a description list: `/ Term: Details`.
|
/// 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 $`.
|
/// A mathematical formula: `$x$`, `$ x^2 $`.
|
||||||
pub math: fn(children: Vec<Content>, block: bool) -> Content,
|
pub math: fn(children: Vec<Content>, block: bool) -> Content,
|
||||||
/// An atom in a formula: `x`, `+`, `12`.
|
/// An atom in a formula: `x`, `+`, `12`.
|
||||||
|
@ -407,10 +407,10 @@ impl DescItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The description of the term.
|
/// The description of the term.
|
||||||
pub fn body(&self) -> Markup {
|
pub fn description(&self) -> Markup {
|
||||||
self.0
|
self.0
|
||||||
.cast_last_child()
|
.cast_last_child()
|
||||||
.expect("description list item is missing body")
|
.expect("description list item is missing description")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 6.0 KiB |
@ -8,8 +8,8 @@ No: list \
|
|||||||
---
|
---
|
||||||
// Test with constructor.
|
// Test with constructor.
|
||||||
#desc(
|
#desc(
|
||||||
(term: [One], body: [First]),
|
([One], [First]),
|
||||||
(term: [Two], body: [Second]),
|
([Two], [Second]),
|
||||||
)
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -32,7 +32,7 @@ No: list \
|
|||||||
#set text(8pt)
|
#set text(8pt)
|
||||||
/ First list: #lorem(4)
|
/ First list: #lorem(4)
|
||||||
|
|
||||||
#set desc(body-indent: 30pt)
|
#set desc(hanging-indent: 30pt)
|
||||||
/ Second list: #lorem(4)
|
/ Second list: #lorem(4)
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -40,7 +40,7 @@ No: list \
|
|||||||
#show desc: it => table(
|
#show desc: it => table(
|
||||||
columns: 2,
|
columns: 2,
|
||||||
padding: 3pt,
|
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
|
/ A: One letter
|
||||||
|
@ -17,16 +17,16 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test label pattern.
|
// Test label pattern.
|
||||||
#set enum(label: "~ A:")
|
#set enum(numbering: "~ A:")
|
||||||
1. First
|
1. First
|
||||||
+ Second
|
+ Second
|
||||||
|
|
||||||
#set enum(label: "(*)")
|
#set enum(numbering: "(*)")
|
||||||
+ A
|
+ A
|
||||||
+ B
|
+ B
|
||||||
+ C
|
+ C
|
||||||
|
|
||||||
#set enum(label: "i)")
|
#set enum(numbering: "i)")
|
||||||
+ A
|
+ A
|
||||||
+ B
|
+ B
|
||||||
|
|
||||||
@ -37,17 +37,17 @@
|
|||||||
/ Desc: List
|
/ Desc: List
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test label closure.
|
// Test numbering with closure.
|
||||||
#enum(
|
#enum(
|
||||||
start: 4,
|
start: 4,
|
||||||
spacing: 0.65em - 3pt,
|
spacing: 0.65em - 3pt,
|
||||||
tight: false,
|
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],
|
[Red], [Green], [Blue],
|
||||||
)
|
)
|
||||||
|
|
||||||
---
|
---
|
||||||
#set enum(label: n => n > 1)
|
#set enum(numbering: n => n > 1)
|
||||||
+ A
|
+ A
|
||||||
+ B
|
+ B
|
||||||
|
|
||||||
@ -57,9 +57,9 @@
|
|||||||
No enum
|
No enum
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 18-20 invalid numbering pattern
|
// Error: 22-24 invalid numbering pattern
|
||||||
#set enum(label: "")
|
#set enum(numbering: "")
|
||||||
|
|
||||||
---
|
---
|
||||||
// Error: 18-24 invalid numbering pattern
|
// Error: 22-28 invalid numbering pattern
|
||||||
#set enum(label: "(())")
|
#set enum(numbering: "(())")
|
||||||
|
@ -48,6 +48,6 @@ _Shopping list_
|
|||||||
- B with 2 tabs
|
- B with 2 tabs
|
||||||
|
|
||||||
---
|
---
|
||||||
#set list(label: [-])
|
#set list(marker: [-])
|
||||||
- Bare hyphen
|
- Bare hyphen
|
||||||
- is not a list
|
- is not a list
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
// Ensure that constructor styles win, but not over outer styles.
|
// Ensure that constructor styles win, but not over outer styles.
|
||||||
// The outer paragraph should be right-aligned,
|
// The outer paragraph should be right-aligned,
|
||||||
// but the B should be center-aligned.
|
// but the B should be center-aligned.
|
||||||
#set list(label: [>])
|
#set list(marker: [>])
|
||||||
#list(label: [--])[
|
#list(marker: [--])[
|
||||||
#rect(width: 2cm, fill: conifer, inset: 4pt, list[A])
|
#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
|
// The constructor property should still work
|
||||||
// when there are recursive show rules.
|
// when there are recursive show rules.
|
||||||
#show list: set text(blue)
|
#show enum: set text(blue)
|
||||||
#list(label: "(a)", [A], list[B])
|
#enum(numbering: "(a)", [A], enum[B])
|
||||||
|
@ -39,7 +39,7 @@ Hello *{x}*
|
|||||||
---
|
---
|
||||||
// Test relative path resolving in layout phase.
|
// Test relative path resolving in layout phase.
|
||||||
#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
|
#let choice = ("monkey.svg", "rhino.png", "tiger.jpg")
|
||||||
#set enum(label: n => {
|
#set enum(numbering: n => {
|
||||||
let path = "../../res/" + choice(n - 1)
|
let path = "../../res/" + choice(n - 1)
|
||||||
move(dy: -0.15em, image(path, width: 1em, height: 1em))
|
move(dy: -0.15em, image(path, width: 1em, height: 1em))
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user