mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
279 lines
7.0 KiB
Rust
279 lines
7.0 KiB
Rust
use std::cmp::Ordering;
|
|
|
|
use crate::prelude::*;
|
|
|
|
/// # Spacing (H)
|
|
/// Insert horizontal spacing into a paragraph.
|
|
///
|
|
/// The spacing can be a length or a fractional. In the latter case, the
|
|
/// remaining space on the line is distributed among all fractional spacings
|
|
/// according to their relative fractions.
|
|
///
|
|
/// ## Example
|
|
/// ```
|
|
/// #circle(fill: red)
|
|
/// #h(1fr)
|
|
/// #circle(fill: yellow)
|
|
/// #h(2fr)
|
|
/// #circle(fill: green)
|
|
/// ```
|
|
///
|
|
/// ## Parameters
|
|
/// - amount: Spacing (positional, required)
|
|
/// How much spacing to insert.
|
|
///
|
|
/// - weak: bool (named)
|
|
/// If true, the spacing collapses at the start or end of a paragraph.
|
|
/// Moreover, from multiple adjacent weak spacings all but the largest one
|
|
/// collapse.
|
|
///
|
|
/// ### Example
|
|
/// ```
|
|
/// #h(1cm, weak: true)
|
|
/// We identified a group of
|
|
/// _weak_ specimens that fail to
|
|
/// manifest in most cases. However,
|
|
/// when #h(8pt, weak: true)
|
|
/// supported
|
|
/// #h(8pt, weak: true) on both
|
|
/// sides, they do show up.
|
|
/// ```
|
|
///
|
|
/// ## Category
|
|
/// layout
|
|
#[func]
|
|
#[capable(Behave)]
|
|
#[derive(Debug, Copy, Clone, Hash)]
|
|
pub struct HNode {
|
|
/// The amount of horizontal spacing.
|
|
pub amount: Spacing,
|
|
/// Whether the node is weak, see also [`Behaviour`].
|
|
pub weak: bool,
|
|
}
|
|
|
|
#[node]
|
|
impl HNode {
|
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
let amount = args.expect("amount")?;
|
|
let weak = args.named("weak")?.unwrap_or(false);
|
|
Ok(Self { amount, weak }.pack())
|
|
}
|
|
|
|
fn field(&self, name: &str) -> Option<Value> {
|
|
match name {
|
|
"amount" => Some(self.amount.encode()),
|
|
"weak" => Some(Value::Bool(self.weak)),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl HNode {
|
|
/// Normal strong spacing.
|
|
pub fn strong(amount: Spacing) -> Self {
|
|
Self { amount, weak: false }
|
|
}
|
|
|
|
/// User-created weak spacing.
|
|
pub fn weak(amount: Spacing) -> Self {
|
|
Self { amount, weak: true }
|
|
}
|
|
}
|
|
|
|
impl Behave for HNode {
|
|
fn behaviour(&self) -> Behaviour {
|
|
if self.amount.is_fractional() {
|
|
Behaviour::Destructive
|
|
} else if self.weak {
|
|
Behaviour::Weak(1)
|
|
} else {
|
|
Behaviour::Ignorant
|
|
}
|
|
}
|
|
|
|
fn larger(&self, prev: &Content) -> bool {
|
|
let Some(prev) = prev.to::<Self>() else { return false };
|
|
self.amount > prev.amount
|
|
}
|
|
}
|
|
|
|
/// # Spacing (V)
|
|
/// Insert vertical spacing.
|
|
///
|
|
/// The spacing can be a length or a fractional. In the latter case, the
|
|
/// remaining space on the page is distributed among all fractional spacings
|
|
/// according to their relative fractions.
|
|
///
|
|
/// ## Example
|
|
/// ```
|
|
/// In this report, we will explore
|
|
/// the various ethical
|
|
/// considerations that must be
|
|
/// taken into account when
|
|
/// conducting psychological
|
|
/// research:
|
|
/// #v(5mm)
|
|
///
|
|
/// - Informed consent
|
|
/// - Participant confidentiality
|
|
/// - The use of
|
|
/// vulnerable populations.
|
|
/// ```
|
|
///
|
|
/// ## Parameters
|
|
/// - amount: Spacing (positional, required)
|
|
/// How much spacing to insert.
|
|
///
|
|
/// - weak: bool (named)
|
|
/// If true, the spacing collapses at the start or end of a flow. Moreover,
|
|
/// from multiple adjacent weak spacings all but the largest one collapse.
|
|
/// Weak spacings will always collapse adjacent paragraph spacing, even if the
|
|
/// paragraph spacing is larger.
|
|
///
|
|
/// ### Example
|
|
/// ```
|
|
/// The following theorem is
|
|
/// foundational to the field:
|
|
/// #v(4pt, weak: true)
|
|
/// $ x^2 + y^2 = r^2 $
|
|
/// #v(4pt, weak: true)
|
|
/// The proof is simple:
|
|
/// ```
|
|
/// ## Category
|
|
/// layout
|
|
#[func]
|
|
#[capable(Behave)]
|
|
#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd)]
|
|
pub struct VNode {
|
|
/// The amount of vertical spacing.
|
|
pub amount: Spacing,
|
|
/// The node's weakness level, see also [`Behaviour`].
|
|
pub weakness: u8,
|
|
}
|
|
|
|
#[node]
|
|
impl VNode {
|
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
let amount = args.expect("spacing")?;
|
|
let node = if args.named("weak")?.unwrap_or(false) {
|
|
Self::weak(amount)
|
|
} else {
|
|
Self::strong(amount)
|
|
};
|
|
Ok(node.pack())
|
|
}
|
|
|
|
fn field(&self, name: &str) -> Option<Value> {
|
|
match name {
|
|
"amount" => Some(self.amount.encode()),
|
|
"weak" => Some(Value::Bool(self.weakness != 0)),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VNode {
|
|
/// Normal strong spacing.
|
|
pub fn strong(amount: Spacing) -> Self {
|
|
Self { amount, weakness: 0 }
|
|
}
|
|
|
|
/// User-created weak spacing.
|
|
pub fn weak(amount: Spacing) -> Self {
|
|
Self { amount, weakness: 1 }
|
|
}
|
|
|
|
/// Weak spacing with list attach weakness.
|
|
pub fn list_attach(amount: Spacing) -> Self {
|
|
Self { amount, weakness: 2 }
|
|
}
|
|
|
|
/// Weak spacing with BlockNode::ABOVE/BELOW weakness.
|
|
pub fn block_around(amount: Spacing) -> Self {
|
|
Self { amount, weakness: 3 }
|
|
}
|
|
|
|
/// Weak spacing with BlockNode::SPACING weakness.
|
|
pub fn block_spacing(amount: Spacing) -> Self {
|
|
Self { amount, weakness: 4 }
|
|
}
|
|
}
|
|
|
|
impl Behave for VNode {
|
|
fn behaviour(&self) -> Behaviour {
|
|
if self.amount.is_fractional() {
|
|
Behaviour::Destructive
|
|
} else if self.weakness > 0 {
|
|
Behaviour::Weak(self.weakness)
|
|
} else {
|
|
Behaviour::Ignorant
|
|
}
|
|
}
|
|
|
|
fn larger(&self, prev: &Content) -> bool {
|
|
let Some(prev) = prev.to::<Self>() else { return false };
|
|
self.amount > prev.amount
|
|
}
|
|
}
|
|
|
|
/// Kinds of spacing.
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
pub enum Spacing {
|
|
/// Spacing specified in absolute terms and relative to the parent's size.
|
|
Relative(Rel<Length>),
|
|
/// Spacing specified as a fraction of the remaining free space in the
|
|
/// parent.
|
|
Fractional(Fr),
|
|
}
|
|
|
|
impl Spacing {
|
|
/// Whether this is fractional spacing.
|
|
pub fn is_fractional(self) -> bool {
|
|
matches!(self, Self::Fractional(_))
|
|
}
|
|
|
|
/// Encode into a value.
|
|
pub fn encode(self) -> Value {
|
|
match self {
|
|
Self::Relative(rel) => {
|
|
if rel.rel.is_zero() {
|
|
Value::Length(rel.abs)
|
|
} else if rel.abs.is_zero() {
|
|
Value::Ratio(rel.rel)
|
|
} else {
|
|
Value::Relative(rel)
|
|
}
|
|
}
|
|
Self::Fractional(fr) => Value::Fraction(fr),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Abs> for Spacing {
|
|
fn from(abs: Abs) -> Self {
|
|
Self::Relative(abs.into())
|
|
}
|
|
}
|
|
|
|
impl From<Em> for Spacing {
|
|
fn from(em: Em) -> Self {
|
|
Self::Relative(Rel::new(Ratio::zero(), em.into()))
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for Spacing {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
match (self, other) {
|
|
(Self::Relative(a), Self::Relative(b)) => a.partial_cmp(b),
|
|
(Self::Fractional(a), Self::Fractional(b)) => a.partial_cmp(b),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
castable! {
|
|
Spacing,
|
|
v: Rel<Length> => Self::Relative(v),
|
|
v: Fr => Self::Fractional(v),
|
|
}
|