mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Make all optional fields settable
This commit is contained in:
parent
d7a65fa26d
commit
c38d72383d
@ -311,7 +311,7 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
|
|||||||
Ok(doc) => doc.pages,
|
Ok(doc) => doc.pages,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let msg = &err[0].message;
|
let msg = &err[0].message;
|
||||||
panic!("while trying to compile {text}:\n\nerror: {msg}");
|
panic!("while trying to compile:\n{text}:\n\nerror: {msg}");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,9 +15,6 @@ use crate::prelude::*;
|
|||||||
/// Display: Align
|
/// Display: Align
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
#[set({
|
|
||||||
styles.set(Self::set_alignment(args.find()?.unwrap_or_default()));
|
|
||||||
})]
|
|
||||||
pub struct AlignNode {
|
pub struct AlignNode {
|
||||||
/// The alignment along both axes.
|
/// The alignment along both axes.
|
||||||
///
|
///
|
||||||
@ -50,10 +47,8 @@ pub struct AlignNode {
|
|||||||
/// rect(inset: 12pt)[ركن]
|
/// rect(inset: 12pt)[ركن]
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[positional]
|
#[positional]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[skip]
|
|
||||||
#[default(Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top)))]
|
#[default(Axes::new(GenAlign::Start, GenAlign::Specific(Align::Top)))]
|
||||||
pub alignment: Axes<Option<GenAlign>>,
|
pub alignment: Axes<Option<GenAlign>>,
|
||||||
|
|
||||||
@ -64,7 +59,9 @@ pub struct AlignNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Show for AlignNode {
|
impl Show for AlignNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body())
|
Ok(self
|
||||||
|
.body()
|
||||||
|
.styled(Self::set_alignment(self.alignment(styles).map(Some))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,19 +36,18 @@ use crate::text::TextNode;
|
|||||||
pub struct ColumnsNode {
|
pub struct ColumnsNode {
|
||||||
/// The number of columns.
|
/// The number of columns.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[default(NonZeroUsize::new(2).unwrap())]
|
||||||
pub count: NonZeroUsize,
|
pub count: NonZeroUsize,
|
||||||
|
|
||||||
|
/// The size of the gutter space between each column.
|
||||||
|
#[resolve]
|
||||||
|
#[default(Ratio::new(0.04).into())]
|
||||||
|
pub gutter: Rel<Length>,
|
||||||
|
|
||||||
/// The content that should be layouted into the columns.
|
/// The content that should be layouted into the columns.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// The size of the gutter space between each column.
|
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
|
||||||
#[default(Ratio::new(0.04).into())]
|
|
||||||
pub gutter: Rel<Length>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ColumnsNode {
|
impl Layout for ColumnsNode {
|
||||||
@ -67,8 +66,8 @@ impl Layout for ColumnsNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
let columns = self.count().get();
|
let columns = self.count(styles).get();
|
||||||
let gutter = Self::gutter_in(styles).relative_to(regions.base().x);
|
let gutter = self.gutter(styles).relative_to(regions.base().x);
|
||||||
let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
|
let width = (regions.size.x - gutter * (columns - 1) as f64) / columns as f64;
|
||||||
|
|
||||||
let backlog: Vec<_> = std::iter::once(®ions.size.y)
|
let backlog: Vec<_> = std::iter::once(®ions.size.y)
|
||||||
@ -157,14 +156,13 @@ impl Layout for ColumnsNode {
|
|||||||
pub struct ColbreakNode {
|
pub struct ColbreakNode {
|
||||||
/// If `{true}`, the column break is skipped if the current column is
|
/// If `{true}`, the column break is skipped if the current column is
|
||||||
/// already empty.
|
/// already empty.
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub weak: bool,
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Behave for ColbreakNode {
|
impl Behave for ColbreakNode {
|
||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
if self.weak() {
|
if self.weak(StyleChain::default()) {
|
||||||
Behaviour::Weak(1)
|
Behaviour::Weak(1)
|
||||||
} else {
|
} else {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
|
@ -23,11 +23,6 @@ use crate::prelude::*;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct BoxNode {
|
pub struct BoxNode {
|
||||||
/// The contents of the box.
|
|
||||||
#[positional]
|
|
||||||
#[default]
|
|
||||||
pub body: Option<Content>,
|
|
||||||
|
|
||||||
/// The width of the box.
|
/// The width of the box.
|
||||||
///
|
///
|
||||||
/// Boxes can have [fractional]($type/fraction) widths, as the example
|
/// Boxes can have [fractional]($type/fraction) widths, as the example
|
||||||
@ -40,13 +35,9 @@ pub struct BoxNode {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// Line in #box(width: 1fr, line(length: 100%)) between.
|
/// Line in #box(width: 1fr, line(length: 100%)) between.
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub width: Sizing,
|
pub width: Sizing,
|
||||||
|
|
||||||
/// The height of the box.
|
/// The height of the box.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// An amount to shift the box's baseline by.
|
/// An amount to shift the box's baseline by.
|
||||||
@ -54,39 +45,29 @@ pub struct BoxNode {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
|
/// Image: #box(baseline: 40%, image("tiger.jpg", width: 2cm)).
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub baseline: Rel<Length>,
|
pub baseline: Rel<Length>,
|
||||||
|
|
||||||
/// The box's background color. See the
|
/// The box's background color. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// The box's border color. See the
|
/// The box's border color. See the
|
||||||
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Sides<Option<Option<PartialStroke>>>,
|
pub stroke: Sides<Option<Option<PartialStroke>>>,
|
||||||
|
|
||||||
/// How much to round the box's corners. See the [rectangle's
|
/// How much to round the box's corners. See the [rectangle's
|
||||||
/// documentation]($func/rect.radius) for more details.
|
/// documentation]($func/rect.radius) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub radius: Corners<Option<Rel<Length>>>,
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the box's content. See the [rectangle's
|
/// How much to pad the box's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub inset: Sides<Option<Rel<Length>>>,
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the box's size without affecting the layout.
|
/// How much to expand the box's size without affecting the layout.
|
||||||
@ -103,11 +84,13 @@ pub struct BoxNode {
|
|||||||
/// outset: (y: 3pt),
|
/// outset: (y: 3pt),
|
||||||
/// radius: 2pt,
|
/// radius: 2pt,
|
||||||
/// )[rectangle].
|
/// )[rectangle].
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub outset: Sides<Option<Rel<Length>>>,
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
|
/// The contents of the box.
|
||||||
|
#[positional]
|
||||||
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for BoxNode {
|
impl Layout for BoxNode {
|
||||||
@ -117,14 +100,14 @@ impl Layout for BoxNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let width = match self.width() {
|
let width = match self.width(styles) {
|
||||||
Sizing::Auto => Smart::Auto,
|
Sizing::Auto => Smart::Auto,
|
||||||
Sizing::Rel(rel) => Smart::Custom(rel),
|
Sizing::Rel(rel) => Smart::Custom(rel),
|
||||||
Sizing::Fr(_) => Smart::Custom(Ratio::one().into()),
|
Sizing::Fr(_) => Smart::Custom(Ratio::one().into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
let sizing = Axes::new(width, self.height());
|
let sizing = Axes::new(width, self.height(styles));
|
||||||
let expand = sizing.as_ref().map(Smart::is_custom);
|
let expand = sizing.as_ref().map(Smart::is_custom);
|
||||||
let size = sizing
|
let size = sizing
|
||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
@ -133,8 +116,8 @@ impl Layout for BoxNode {
|
|||||||
.unwrap_or(regions.base());
|
.unwrap_or(regions.base());
|
||||||
|
|
||||||
// Apply inset.
|
// Apply inset.
|
||||||
let mut body = self.body().unwrap_or_default();
|
let mut body = self.body(styles).unwrap_or_default();
|
||||||
let inset = Self::inset_in(styles);
|
let inset = self.inset(styles);
|
||||||
if inset.iter().any(|v| !v.is_zero()) {
|
if inset.iter().any(|v| !v.is_zero()) {
|
||||||
body = body.padded(inset.map(|side| side.map(Length::from)));
|
body = body.padded(inset.map(|side| side.map(Length::from)));
|
||||||
}
|
}
|
||||||
@ -145,20 +128,19 @@ impl Layout for BoxNode {
|
|||||||
let mut frame = body.layout(vt, styles, pod)?.into_frame();
|
let mut frame = body.layout(vt, styles, pod)?.into_frame();
|
||||||
|
|
||||||
// Apply baseline shift.
|
// Apply baseline shift.
|
||||||
let shift = Self::baseline_in(styles).relative_to(frame.height());
|
let shift = self.baseline(styles).relative_to(frame.height());
|
||||||
if !shift.is_zero() {
|
if !shift.is_zero() {
|
||||||
frame.set_baseline(frame.baseline() - shift);
|
frame.set_baseline(frame.baseline() - shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare fill and stroke.
|
// Prepare fill and stroke.
|
||||||
let fill = Self::fill_in(styles);
|
let fill = self.fill(styles);
|
||||||
let stroke =
|
let stroke = self.stroke(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
|
||||||
Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
|
|
||||||
|
|
||||||
// Add fill and/or stroke.
|
// Add fill and/or stroke.
|
||||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||||
let outset = Self::outset_in(styles);
|
let outset = self.outset(styles);
|
||||||
let radius = Self::radius_in(styles);
|
let radius = self.radius(styles);
|
||||||
frame.fill_and_stroke(fill, stroke, outset, radius);
|
frame.fill_and_stroke(fill, stroke, outset, radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,27 +198,7 @@ impl Layout for BoxNode {
|
|||||||
/// Display: Block
|
/// Display: Block
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
#[set({
|
|
||||||
let spacing = args.named("spacing")?;
|
|
||||||
styles.set_opt(
|
|
||||||
args.named("above")?
|
|
||||||
.map(VNode::block_around)
|
|
||||||
.or_else(|| spacing.map(VNode::block_spacing))
|
|
||||||
.map(Self::set_above),
|
|
||||||
);
|
|
||||||
styles.set_opt(
|
|
||||||
args.named("below")?
|
|
||||||
.map(VNode::block_around)
|
|
||||||
.or_else(|| spacing.map(VNode::block_spacing))
|
|
||||||
.map(Self::set_below),
|
|
||||||
);
|
|
||||||
})]
|
|
||||||
pub struct BlockNode {
|
pub struct BlockNode {
|
||||||
/// The contents of the block.
|
|
||||||
#[positional]
|
|
||||||
#[default]
|
|
||||||
pub body: Option<Content>,
|
|
||||||
|
|
||||||
/// The block's width.
|
/// The block's width.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -248,8 +210,6 @@ pub struct BlockNode {
|
|||||||
/// lorem(10),
|
/// lorem(10),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// The block's height. When the height is larger than the remaining space on
|
/// The block's height. When the height is larger than the remaining space on
|
||||||
@ -265,8 +225,6 @@ pub struct BlockNode {
|
|||||||
/// fill: aqua,
|
/// fill: aqua,
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// Whether the block can be broken and continue on the next page.
|
/// Whether the block can be broken and continue on the next page.
|
||||||
@ -281,55 +239,48 @@ pub struct BlockNode {
|
|||||||
/// lorem(15),
|
/// lorem(15),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub breakable: bool,
|
pub breakable: bool,
|
||||||
|
|
||||||
/// The block's background color. See the
|
/// The block's background color. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// The block's border color. See the
|
/// The block's border color. See the
|
||||||
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
/// [rectangle's documentation]($func/rect.stroke) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Sides<Option<Option<PartialStroke>>>,
|
pub stroke: Sides<Option<Option<PartialStroke>>>,
|
||||||
|
|
||||||
/// How much to round the block's corners. See the [rectangle's
|
/// How much to round the block's corners. See the [rectangle's
|
||||||
/// documentation]($func/rect.radius) for more details.
|
/// documentation]($func/rect.radius) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub radius: Corners<Option<Rel<Length>>>,
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the block's content. See the [rectangle's
|
/// How much to pad the block's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub inset: Sides<Option<Rel<Length>>>,
|
pub inset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to expand the block's size without affecting the layout. See
|
/// How much to expand the block's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub outset: Sides<Option<Rel<Length>>>,
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// The spacing between this block and its predecessor. Takes precedence over
|
/// The spacing between this block and its predecessor. Takes precedence
|
||||||
/// `spacing`. Can be used in combination with a show rule to adjust the
|
/// over `spacing`. Can be used in combination with a show rule to adjust
|
||||||
/// spacing around arbitrary block-level elements.
|
/// the spacing around arbitrary block-level elements.
|
||||||
///
|
///
|
||||||
/// The default value is `{1.2em}`.
|
/// The default value is `{1.2em}`.
|
||||||
#[settable]
|
#[parse(
|
||||||
#[skip]
|
let spacing = args.named("spacing")?;
|
||||||
|
args.named("above")?
|
||||||
|
.map(VNode::block_around)
|
||||||
|
.or_else(|| spacing.map(VNode::block_spacing))
|
||||||
|
)]
|
||||||
#[default(VNode::block_spacing(Em::new(1.2).into()))]
|
#[default(VNode::block_spacing(Em::new(1.2).into()))]
|
||||||
pub above: VNode,
|
pub above: VNode,
|
||||||
|
|
||||||
@ -337,16 +288,22 @@ pub struct BlockNode {
|
|||||||
/// over `spacing`.
|
/// over `spacing`.
|
||||||
///
|
///
|
||||||
/// The default value is `{1.2em}`.
|
/// The default value is `{1.2em}`.
|
||||||
#[settable]
|
#[parse(
|
||||||
#[skip]
|
args.named("below")?
|
||||||
|
.map(VNode::block_around)
|
||||||
|
.or_else(|| spacing.map(VNode::block_spacing))
|
||||||
|
)]
|
||||||
#[default(VNode::block_spacing(Em::new(1.2).into()))]
|
#[default(VNode::block_spacing(Em::new(1.2).into()))]
|
||||||
pub below: VNode,
|
pub below: VNode,
|
||||||
|
|
||||||
|
/// The contents of the block.
|
||||||
|
#[positional]
|
||||||
|
pub body: Option<Content>,
|
||||||
|
|
||||||
/// Whether this block must stick to the following one.
|
/// Whether this block must stick to the following one.
|
||||||
///
|
///
|
||||||
/// Use this to prevent page breaks between e.g. a heading and its body.
|
/// Use this to prevent page breaks between e.g. a heading and its body.
|
||||||
#[settable]
|
#[internal]
|
||||||
#[skip]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub sticky: bool,
|
pub sticky: bool,
|
||||||
}
|
}
|
||||||
@ -359,14 +316,14 @@ impl Layout for BlockNode {
|
|||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
// Apply inset.
|
// Apply inset.
|
||||||
let mut body = self.body().unwrap_or_default();
|
let mut body = self.body(styles).unwrap_or_default();
|
||||||
let inset = Self::inset_in(styles);
|
let inset = self.inset(styles);
|
||||||
if inset.iter().any(|v| !v.is_zero()) {
|
if inset.iter().any(|v| !v.is_zero()) {
|
||||||
body = body.clone().padded(inset.map(|side| side.map(Length::from)));
|
body = body.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
let sizing = Axes::new(self.width(), self.height());
|
let sizing = Axes::new(self.width(styles), self.height(styles));
|
||||||
let mut expand = sizing.as_ref().map(Smart::is_custom);
|
let mut expand = sizing.as_ref().map(Smart::is_custom);
|
||||||
let mut size = sizing
|
let mut size = sizing
|
||||||
.resolve(styles)
|
.resolve(styles)
|
||||||
@ -375,7 +332,7 @@ impl Layout for BlockNode {
|
|||||||
.unwrap_or(regions.base());
|
.unwrap_or(regions.base());
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = if Self::breakable_in(styles) {
|
let mut frames = if self.breakable(styles) {
|
||||||
// Measure to ensure frames for all regions have the same width.
|
// Measure to ensure frames for all regions have the same width.
|
||||||
if sizing.x == Smart::Auto {
|
if sizing.x == Smart::Auto {
|
||||||
let pod = Regions::one(size, Axes::splat(false));
|
let pod = Regions::one(size, Axes::splat(false));
|
||||||
@ -413,9 +370,8 @@ impl Layout for BlockNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Prepare fill and stroke.
|
// Prepare fill and stroke.
|
||||||
let fill = Self::fill_in(styles);
|
let fill = self.fill(styles);
|
||||||
let stroke =
|
let stroke = self.stroke(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
|
||||||
Self::stroke_in(styles).map(|s| s.map(PartialStroke::unwrap_or_default));
|
|
||||||
|
|
||||||
// Add fill and/or stroke.
|
// Add fill and/or stroke.
|
||||||
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||||
@ -424,8 +380,8 @@ impl Layout for BlockNode {
|
|||||||
skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty());
|
skip = first.is_empty() && rest.iter().any(|frame| !frame.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
let outset = Self::outset_in(styles);
|
let outset = self.outset(styles);
|
||||||
let radius = Self::radius_in(styles);
|
let radius = self.radius(styles);
|
||||||
for frame in frames.iter_mut().skip(skip as usize) {
|
for frame in frames.iter_mut().skip(skip as usize) {
|
||||||
frame.fill_and_stroke(fill, stroke, outset, radius);
|
frame.fill_and_stroke(fill, stroke, outset, radius);
|
||||||
}
|
}
|
||||||
|
@ -48,37 +48,10 @@ use super::GridLayouter;
|
|||||||
/// content. All content that is indented more than an item's plus sign or dot
|
/// content. All content that is indented more than an item's plus sign or dot
|
||||||
/// becomes part of that item.
|
/// becomes part of that item.
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
|
||||||
/// - start: `NonZeroUsize` (named)
|
|
||||||
/// Which number to start the enumeration with.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #enum(
|
|
||||||
/// start: 3,
|
|
||||||
/// [Skipping],
|
|
||||||
/// [Ahead],
|
|
||||||
/// )
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// Display: Numbered List
|
/// Display: Numbered List
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct EnumNode {
|
pub struct EnumNode {
|
||||||
/// The numbered list's items.
|
|
||||||
///
|
|
||||||
/// When using the enum syntax, adjacent items are automatically collected
|
|
||||||
/// into enumerations, even through constructs like for loops.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #for phase in (
|
|
||||||
/// "Launch",
|
|
||||||
/// "Orbit",
|
|
||||||
/// "Descent",
|
|
||||||
/// ) [+ #phase]
|
|
||||||
/// ```
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<EnumItem>,
|
|
||||||
|
|
||||||
/// If this is `{false}`, the items are spaced apart with
|
/// If this is `{false}`, the items are spaced apart with
|
||||||
/// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal
|
/// [enum spacing]($func/enum.spacing). If it is `{true}`, they use normal
|
||||||
/// [leading]($func/par.leading) instead. This makes the enumeration more
|
/// [leading]($func/par.leading) instead. This makes the enumeration more
|
||||||
@ -93,7 +66,6 @@ pub struct EnumNode {
|
|||||||
/// insert a blank line between the
|
/// insert a blank line between the
|
||||||
/// items.
|
/// items.
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub tight: bool,
|
pub tight: bool,
|
||||||
|
|
||||||
@ -116,10 +88,21 @@ pub struct EnumNode {
|
|||||||
/// + Superscript
|
/// + Superscript
|
||||||
/// + Numbering!
|
/// + Numbering!
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
|
#[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
|
||||||
pub numbering: Numbering,
|
pub numbering: Numbering,
|
||||||
|
|
||||||
|
/// Which number to start the enumeration with.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #enum(
|
||||||
|
/// start: 3,
|
||||||
|
/// [Skipping],
|
||||||
|
/// [Ahead],
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
#[default(NonZeroUsize::new(1).unwrap())]
|
||||||
|
pub start: NonZeroUsize,
|
||||||
|
|
||||||
/// Whether to display the full numbering, including the numbers of
|
/// Whether to display the full numbering, including the numbers of
|
||||||
/// all parent enumerations.
|
/// all parent enumerations.
|
||||||
///
|
///
|
||||||
@ -132,18 +115,14 @@ pub struct EnumNode {
|
|||||||
/// + Add integredients
|
/// + Add integredients
|
||||||
/// + Eat
|
/// + Eat
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub full: bool,
|
pub full: bool,
|
||||||
|
|
||||||
/// The indentation of each item's label.
|
/// The indentation of each item's label.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub indent: Length,
|
pub indent: Length,
|
||||||
|
|
||||||
/// The space between the numbering and the body of each item.
|
/// The space between the numbering and the body of each item.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Em::new(0.5).into())]
|
#[default(Em::new(0.5).into())]
|
||||||
pub body_indent: Length,
|
pub body_indent: Length,
|
||||||
@ -151,35 +130,29 @@ pub struct EnumNode {
|
|||||||
/// The spacing between the items of a wide (non-tight) enumeration.
|
/// The spacing between the items of a wide (non-tight) enumeration.
|
||||||
///
|
///
|
||||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub spacing: Smart<Spacing>,
|
pub spacing: Smart<Spacing>,
|
||||||
|
|
||||||
|
/// The numbered list's items.
|
||||||
|
///
|
||||||
|
/// When using the enum syntax, adjacent items are automatically collected
|
||||||
|
/// into enumerations, even through constructs like for loops.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #for phase in (
|
||||||
|
/// "Launch",
|
||||||
|
/// "Orbit",
|
||||||
|
/// "Descent",
|
||||||
|
/// ) [+ #phase]
|
||||||
|
/// ```
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<EnumItem>,
|
||||||
|
|
||||||
/// The numbers of parent items.
|
/// The numbers of parent items.
|
||||||
#[settable]
|
#[internal]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[skip]
|
|
||||||
#[default]
|
|
||||||
parents: Parent,
|
parents: Parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for EnumNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let mut items = args.all::<EnumItem>()?;
|
|
||||||
if let Some(number) = args.named::<NonZeroUsize>("start")? {
|
|
||||||
if let Some(first) = items.first_mut() {
|
|
||||||
if first.number().is_none() {
|
|
||||||
*first = EnumItem::new(first.body()).with_number(Some(number));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self::new(items)
|
|
||||||
.with_tight(args.named("tight")?.unwrap_or(true))
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for EnumNode {
|
impl Layout for EnumNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
@ -187,23 +160,23 @@ impl Layout for EnumNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let numbering = Self::numbering_in(styles);
|
let numbering = self.numbering(styles);
|
||||||
let indent = Self::indent_in(styles);
|
let indent = self.indent(styles);
|
||||||
let body_indent = Self::body_indent_in(styles);
|
let body_indent = self.body_indent(styles);
|
||||||
let gutter = if self.tight() {
|
let gutter = if self.tight(styles) {
|
||||||
ParNode::leading_in(styles).into()
|
ParNode::leading_in(styles).into()
|
||||||
} else {
|
} else {
|
||||||
Self::spacing_in(styles)
|
self.spacing(styles)
|
||||||
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
let mut number = NonZeroUsize::new(1).unwrap();
|
let mut number = self.start(styles);
|
||||||
let mut parents = Self::parents_in(styles);
|
let mut parents = self.parents(styles);
|
||||||
let full = Self::full_in(styles);
|
let full = self.full(styles);
|
||||||
|
|
||||||
for item in self.children() {
|
for item in self.children() {
|
||||||
number = item.number().unwrap_or(number);
|
number = item.number(styles).unwrap_or(number);
|
||||||
|
|
||||||
let resolved = if full {
|
let resolved = if full {
|
||||||
parents.push(number);
|
parents.push(number);
|
||||||
@ -252,7 +225,6 @@ impl Layout for EnumNode {
|
|||||||
pub struct EnumItem {
|
pub struct EnumItem {
|
||||||
/// The item's number.
|
/// The item's number.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[default]
|
|
||||||
pub number: Option<NonZeroUsize>,
|
pub number: Option<NonZeroUsize>,
|
||||||
|
|
||||||
/// The item's body.
|
/// The item's body.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use typst::model::{Style, StyledNode};
|
use typst::model::StyledNode;
|
||||||
|
|
||||||
use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode};
|
use super::{AlignNode, BlockNode, ColbreakNode, ParNode, PlaceNode, Spacing, VNode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
@ -40,8 +40,6 @@ impl Layout for FlowNode {
|
|||||||
if let Some(node) = child.to::<VNode>() {
|
if let Some(node) = child.to::<VNode>() {
|
||||||
layouter.layout_spacing(node, styles);
|
layouter.layout_spacing(node, styles);
|
||||||
} else if let Some(node) = child.to::<ParNode>() {
|
} else if let Some(node) = child.to::<ParNode>() {
|
||||||
let barrier = Style::Barrier(child.id());
|
|
||||||
let styles = styles.chain_one(&barrier);
|
|
||||||
layouter.layout_par(vt, node, styles)?;
|
layouter.layout_par(vt, node, styles)?;
|
||||||
} else if child.is::<RectNode>()
|
} else if child.is::<RectNode>()
|
||||||
|| child.is::<SquareNode>()
|
|| child.is::<SquareNode>()
|
||||||
@ -49,8 +47,6 @@ impl Layout for FlowNode {
|
|||||||
|| child.is::<CircleNode>()
|
|| child.is::<CircleNode>()
|
||||||
|| child.is::<ImageNode>()
|
|| child.is::<ImageNode>()
|
||||||
{
|
{
|
||||||
let barrier = Style::Barrier(child.id());
|
|
||||||
let styles = styles.chain_one(&barrier);
|
|
||||||
layouter.layout_single(vt, &child, styles)?;
|
layouter.layout_single(vt, &child, styles)?;
|
||||||
} else if child.has::<dyn Layout>() {
|
} else if child.has::<dyn Layout>() {
|
||||||
layouter.layout_multiple(vt, &child, styles)?;
|
layouter.layout_multiple(vt, &child, styles)?;
|
||||||
@ -121,7 +117,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
self.layout_item(match node.amount() {
|
self.layout_item(match node.amount() {
|
||||||
Spacing::Rel(v) => FlowItem::Absolute(
|
Spacing::Rel(v) => FlowItem::Absolute(
|
||||||
v.resolve(styles).relative_to(self.initial.y),
|
v.resolve(styles).relative_to(self.initial.y),
|
||||||
node.weakness() > 0,
|
node.weakness(styles) > 0,
|
||||||
),
|
),
|
||||||
Spacing::Fr(v) => FlowItem::Fractional(v),
|
Spacing::Fr(v) => FlowItem::Fractional(v),
|
||||||
});
|
});
|
||||||
@ -200,7 +196,7 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
// Placed nodes that are out of flow produce placed items which aren't
|
// Placed nodes that are out of flow produce placed items which aren't
|
||||||
// aligned later.
|
// aligned later.
|
||||||
if let Some(placed) = block.to::<PlaceNode>() {
|
if let Some(placed) = block.to::<PlaceNode>() {
|
||||||
if placed.out_of_flow() {
|
if placed.out_of_flow(styles) {
|
||||||
let frame = block.layout(vt, styles, self.regions)?.into_frame();
|
let frame = block.layout(vt, styles, self.regions)?.into_frame();
|
||||||
self.layout_item(FlowItem::Placed(frame));
|
self.layout_item(FlowItem::Placed(frame));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -208,7 +204,14 @@ impl<'a> FlowLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// How to align the block.
|
// How to align the block.
|
||||||
let aligns = AlignNode::alignment_in(styles).resolve(styles);
|
let aligns = if let Some(align) = block.to::<AlignNode>() {
|
||||||
|
align.alignment(styles)
|
||||||
|
} else if let Some(styled) = block.to::<StyledNode>() {
|
||||||
|
AlignNode::alignment_in(styles.chain(&styled.map()))
|
||||||
|
} else {
|
||||||
|
AlignNode::alignment_in(styles)
|
||||||
|
}
|
||||||
|
.resolve(styles);
|
||||||
|
|
||||||
// Layout the block itself.
|
// Layout the block itself.
|
||||||
let sticky = BlockNode::sticky_in(styles);
|
let sticky = BlockNode::sticky_in(styles);
|
||||||
|
@ -60,7 +60,7 @@ use super::Sizing;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - gutter: `TrackSizings` (named)
|
/// - gutter: `TrackSizings` (named, settable)
|
||||||
/// Defines the gaps between rows & columns.
|
/// Defines the gaps between rows & columns.
|
||||||
///
|
///
|
||||||
/// If there are more gutters than defined sizes, the last gutter is repeated.
|
/// If there are more gutters than defined sizes, the last gutter is repeated.
|
||||||
@ -69,41 +69,36 @@ use super::Sizing;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct GridNode {
|
pub struct GridNode {
|
||||||
/// The contents of the table cells.
|
|
||||||
///
|
|
||||||
/// The cells are populated in row-major order.
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
|
|
||||||
/// Defines the column sizes.
|
/// Defines the column sizes.
|
||||||
///
|
///
|
||||||
/// Either specify a track size array or provide an integer to create a grid
|
/// Either specify a track size array or provide an integer to create a grid
|
||||||
/// with that many `{auto}`-sized columns. Note that opposed to rows and
|
/// with that many `{auto}`-sized columns. Note that opposed to rows and
|
||||||
/// gutters, providing a single track size will only ever create a single
|
/// gutters, providing a single track size will only ever create a single
|
||||||
/// column.
|
/// column.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub columns: TrackSizings,
|
pub columns: TrackSizings,
|
||||||
|
|
||||||
/// Defines the row sizes.
|
/// Defines the row sizes.
|
||||||
///
|
///
|
||||||
/// If there are more cells than fit the defined rows, the last row is
|
/// If there are more cells than fit the defined rows, the last row is
|
||||||
/// repeated until there are no more cells.
|
/// repeated until there are no more cells.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub rows: TrackSizings,
|
pub rows: TrackSizings,
|
||||||
|
|
||||||
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
||||||
#[named]
|
#[parse(
|
||||||
#[shorthand(gutter)]
|
let gutter = args.named("gutter")?;
|
||||||
#[default]
|
args.named("column-gutter")?.or_else(|| gutter.clone())
|
||||||
|
)]
|
||||||
pub column_gutter: TrackSizings,
|
pub column_gutter: TrackSizings,
|
||||||
|
|
||||||
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
||||||
#[named]
|
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
|
||||||
#[shorthand(gutter)]
|
|
||||||
#[default]
|
|
||||||
pub row_gutter: TrackSizings,
|
pub row_gutter: TrackSizings,
|
||||||
|
|
||||||
|
/// The contents of the table cells.
|
||||||
|
///
|
||||||
|
/// The cells are populated in row-major order.
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for GridNode {
|
impl Layout for GridNode {
|
||||||
@ -117,8 +112,8 @@ impl Layout for GridNode {
|
|||||||
let cells = self.children();
|
let cells = self.children();
|
||||||
let layouter = GridLayouter::new(
|
let layouter = GridLayouter::new(
|
||||||
vt,
|
vt,
|
||||||
Axes::new(&self.columns().0, &self.rows().0),
|
Axes::new(&self.columns(styles).0, &self.rows(styles).0),
|
||||||
Axes::new(&self.column_gutter().0, &self.row_gutter().0),
|
Axes::new(&self.column_gutter(styles).0, &self.row_gutter(styles).0),
|
||||||
&cells,
|
&cells,
|
||||||
regions,
|
regions,
|
||||||
styles,
|
styles,
|
||||||
|
@ -38,19 +38,6 @@ use super::GridLayouter;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct ListNode {
|
pub struct ListNode {
|
||||||
/// The bullet list's children.
|
|
||||||
///
|
|
||||||
/// When using the list syntax, adjacent items are automatically collected
|
|
||||||
/// into lists, even through constructs like for loops.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #for letter in "ABC" [
|
|
||||||
/// - Letter #letter
|
|
||||||
/// ]
|
|
||||||
/// ```
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<ListItem>,
|
|
||||||
|
|
||||||
/// If this is `{false}`, the items are spaced apart with [list
|
/// If this is `{false}`, the items are spaced apart with [list
|
||||||
/// spacing]($func/list.spacing). If it is `{true}`, they use normal
|
/// spacing]($func/list.spacing). If it is `{true}`, they use normal
|
||||||
/// [leading]($func/par.leading) instead. This makes the list more compact,
|
/// [leading]($func/par.leading) instead. This makes the list more compact,
|
||||||
@ -64,7 +51,6 @@ pub struct ListNode {
|
|||||||
/// - To make a list wide, simply insert
|
/// - To make a list wide, simply insert
|
||||||
/// a blank line between the items.
|
/// a blank line between the items.
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub tight: bool,
|
pub tight: bool,
|
||||||
|
|
||||||
@ -89,18 +75,14 @@ pub struct ListNode {
|
|||||||
/// - Items
|
/// - Items
|
||||||
/// - Items
|
/// - Items
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(ListMarker::Content(vec![]))]
|
#[default(ListMarker::Content(vec![]))]
|
||||||
pub marker: ListMarker,
|
pub marker: ListMarker,
|
||||||
|
|
||||||
/// The indent of each item's marker.
|
/// The indent of each item's marker.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub indent: Length,
|
pub indent: Length,
|
||||||
|
|
||||||
/// The spacing between the marker and the body of each item.
|
/// The spacing between the marker and the body of each item.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Em::new(0.5).into())]
|
#[default(Em::new(0.5).into())]
|
||||||
pub body_indent: Length,
|
pub body_indent: Length,
|
||||||
@ -108,15 +90,24 @@ pub struct ListNode {
|
|||||||
/// 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]($func/block.below).
|
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub spacing: Smart<Spacing>,
|
pub spacing: Smart<Spacing>,
|
||||||
|
|
||||||
|
/// The bullet list's children.
|
||||||
|
///
|
||||||
|
/// When using the list syntax, adjacent items are automatically collected
|
||||||
|
/// into lists, even through constructs like for loops.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #for letter in "ABC" [
|
||||||
|
/// - Letter #letter
|
||||||
|
/// ]
|
||||||
|
/// ```
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<ListItem>,
|
||||||
|
|
||||||
/// The nesting depth.
|
/// The nesting depth.
|
||||||
#[settable]
|
#[internal]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[skip]
|
|
||||||
#[default]
|
|
||||||
depth: Depth,
|
depth: Depth,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,17 +118,17 @@ impl Layout for ListNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let indent = Self::indent_in(styles);
|
let indent = self.indent(styles);
|
||||||
let body_indent = Self::body_indent_in(styles);
|
let body_indent = self.body_indent(styles);
|
||||||
let gutter = if self.tight() {
|
let gutter = if self.tight(styles) {
|
||||||
ParNode::leading_in(styles).into()
|
ParNode::leading_in(styles).into()
|
||||||
} else {
|
} else {
|
||||||
Self::spacing_in(styles)
|
self.spacing(styles)
|
||||||
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
||||||
};
|
};
|
||||||
|
|
||||||
let depth = Self::depth_in(styles);
|
let depth = self.depth(styles);
|
||||||
let marker = Self::marker_in(styles).resolve(vt.world(), depth)?;
|
let marker = self.marker(styles).resolve(vt.world(), depth)?;
|
||||||
|
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
for item in self.children() {
|
for item in self.children() {
|
||||||
|
@ -48,7 +48,7 @@ use std::mem;
|
|||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
use typst::diag::SourceResult;
|
use typst::diag::SourceResult;
|
||||||
use typst::model::{
|
use typst::model::{
|
||||||
applicable, realize, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder,
|
applicable, realize, Content, Node, SequenceNode, StyleChain, StyleVecBuilder,
|
||||||
StyledNode,
|
StyledNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -124,8 +124,6 @@ impl Layout for Content {
|
|||||||
let mut vt = Vt { world, provider, introspector };
|
let mut vt = Vt { world, provider, introspector };
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?;
|
let (realized, styles) = realize_block(&mut vt, &scratch, node, styles)?;
|
||||||
let barrier = Style::Barrier(realized.id());
|
|
||||||
let styles = styles.chain_one(&barrier);
|
|
||||||
realized
|
realized
|
||||||
.with::<dyn Layout>()
|
.with::<dyn Layout>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@ -283,7 +281,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
|||||||
|
|
||||||
let keep = content
|
let keep = content
|
||||||
.to::<PagebreakNode>()
|
.to::<PagebreakNode>()
|
||||||
.map_or(false, |pagebreak| !pagebreak.weak());
|
.map_or(false, |pagebreak| !pagebreak.weak(styles));
|
||||||
|
|
||||||
self.interrupt_page(keep.then(|| styles))?;
|
self.interrupt_page(keep.then(|| styles))?;
|
||||||
|
|
||||||
@ -399,7 +397,7 @@ struct DocBuilder<'a> {
|
|||||||
impl<'a> DocBuilder<'a> {
|
impl<'a> DocBuilder<'a> {
|
||||||
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
||||||
if let Some(pagebreak) = content.to::<PagebreakNode>() {
|
if let Some(pagebreak) = content.to::<PagebreakNode>() {
|
||||||
self.keep_next = !pagebreak.weak();
|
self.keep_next = !pagebreak.weak(styles);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,11 +438,11 @@ impl<'a> FlowBuilder<'a> {
|
|||||||
|
|
||||||
if content.has::<dyn Layout>() || content.is::<ParNode>() {
|
if content.has::<dyn Layout>() || content.is::<ParNode>() {
|
||||||
let is_tight_list = if let Some(node) = content.to::<ListNode>() {
|
let is_tight_list = if let Some(node) = content.to::<ListNode>() {
|
||||||
node.tight()
|
node.tight(styles)
|
||||||
} else if let Some(node) = content.to::<EnumNode>() {
|
} else if let Some(node) = content.to::<EnumNode>() {
|
||||||
node.tight()
|
node.tight(styles)
|
||||||
} else if let Some(node) = content.to::<TermsNode>() {
|
} else if let Some(node) = content.to::<TermsNode>() {
|
||||||
node.tight()
|
node.tight(styles)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
@ -478,7 +476,7 @@ impl<'a> ParBuilder<'a> {
|
|||||||
|| content.is::<HNode>()
|
|| content.is::<HNode>()
|
||||||
|| content.is::<LinebreakNode>()
|
|| content.is::<LinebreakNode>()
|
||||||
|| content.is::<SmartQuoteNode>()
|
|| content.is::<SmartQuoteNode>()
|
||||||
|| content.to::<FormulaNode>().map_or(false, |node| !node.block())
|
|| content.to::<FormulaNode>().map_or(false, |node| !node.block(styles))
|
||||||
|| content.is::<BoxNode>()
|
|| content.is::<BoxNode>()
|
||||||
{
|
{
|
||||||
self.0.push(content.clone(), styles);
|
self.0.push(content.clone(), styles);
|
||||||
@ -539,7 +537,7 @@ impl<'a> ListBuilder<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(item, map)| {
|
.map(|(item, map)| {
|
||||||
let item = item.to::<ListItem>().unwrap();
|
let item = item.to::<ListItem>().unwrap();
|
||||||
ListItem::new(item.body().styled_with_map(map.clone()))
|
item.clone().with_body(item.body().styled_with_map(map.clone()))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
@ -551,8 +549,7 @@ impl<'a> ListBuilder<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(item, map)| {
|
.map(|(item, map)| {
|
||||||
let item = item.to::<EnumItem>().unwrap();
|
let item = item.to::<EnumItem>().unwrap();
|
||||||
EnumItem::new(item.body().styled_with_map(map.clone()))
|
item.clone().with_body(item.body().styled_with_map(map.clone()))
|
||||||
.with_number(item.number())
|
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
@ -564,10 +561,11 @@ impl<'a> ListBuilder<'a> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(item, map)| {
|
.map(|(item, map)| {
|
||||||
let item = item.to::<TermItem>().unwrap();
|
let item = item.to::<TermItem>().unwrap();
|
||||||
TermItem::new(
|
item.clone()
|
||||||
item.term().styled_with_map(map.clone()),
|
.with_term(item.term().styled_with_map(map.clone()))
|
||||||
item.description().styled_with_map(map.clone()),
|
.with_description(
|
||||||
)
|
item.description().styled_with_map(map.clone()),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
|
@ -16,62 +16,44 @@ use crate::prelude::*;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - x: `Rel<Length>` (named)
|
/// - x: `Rel<Length>` (named, settable)
|
||||||
/// The horizontal padding. Both `left` and `right` take precedence over this.
|
/// The horizontal padding. Both `left` and `right` take precedence over this.
|
||||||
///
|
///
|
||||||
/// - y: `Rel<Length>` (named)
|
/// - y: `Rel<Length>` (named, settable)
|
||||||
/// The vertical padding. Both `top` and `bottom` take precedence over this.
|
/// The vertical padding. Both `top` and `bottom` take precedence over this.
|
||||||
///
|
///
|
||||||
/// - rest: `Rel<Length>` (named)
|
/// - rest: `Rel<Length>` (named, settable)
|
||||||
/// The padding for all sides. All other parameters take precedence over this.
|
/// The padding for all sides. All other parameters take precedence over this.
|
||||||
///
|
///
|
||||||
/// Display: Padding
|
/// Display: Padding
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct PadNode {
|
pub struct PadNode {
|
||||||
|
/// The padding at the left side.
|
||||||
|
#[parse(
|
||||||
|
let all = args.named("rest")?.or(args.find()?);
|
||||||
|
let x = args.named("x")?.or(all);
|
||||||
|
let y = args.named("y")?.or(all);
|
||||||
|
args.named("left")?.or(x)
|
||||||
|
)]
|
||||||
|
pub left: Rel<Length>,
|
||||||
|
|
||||||
|
/// The padding at the top side.
|
||||||
|
#[parse(args.named("top")?.or(y))]
|
||||||
|
pub top: Rel<Length>,
|
||||||
|
|
||||||
|
/// The padding at the right side.
|
||||||
|
#[parse(args.named("right")?.or(x))]
|
||||||
|
pub right: Rel<Length>,
|
||||||
|
|
||||||
|
/// The padding at the bottom side.
|
||||||
|
#[parse(args.named("bottom")?.or(y))]
|
||||||
|
pub bottom: Rel<Length>,
|
||||||
|
|
||||||
/// The content to pad at the sides.
|
/// The content to pad at the sides.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// The padding at the left side.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub left: Rel<Length>,
|
|
||||||
|
|
||||||
/// The padding at the right side.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub right: Rel<Length>,
|
|
||||||
|
|
||||||
/// The padding at the top side.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub top: Rel<Length>,
|
|
||||||
|
|
||||||
/// The padding at the bottom side.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub bottom: Rel<Length>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for PadNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let all = args.named("rest")?.or(args.find()?);
|
|
||||||
let x = args.named("x")?;
|
|
||||||
let y = args.named("y")?;
|
|
||||||
let left = args.named("left")?.or(x).or(all).unwrap_or_default();
|
|
||||||
let top = args.named("top")?.or(y).or(all).unwrap_or_default();
|
|
||||||
let right = args.named("right")?.or(x).or(all).unwrap_or_default();
|
|
||||||
let bottom = args.named("bottom")?.or(y).or(all).unwrap_or_default();
|
|
||||||
let body = args.expect::<Content>("body")?;
|
|
||||||
Ok(Self::new(body)
|
|
||||||
.with_left(left)
|
|
||||||
.with_top(top)
|
|
||||||
.with_bottom(bottom)
|
|
||||||
.with_right(right)
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PadNode {
|
impl Layout for PadNode {
|
||||||
@ -81,10 +63,15 @@ impl Layout for PadNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut backlog = vec![];
|
let sides = Sides::new(
|
||||||
|
self.left(styles),
|
||||||
|
self.top(styles),
|
||||||
|
self.right(styles),
|
||||||
|
self.bottom(styles),
|
||||||
|
);
|
||||||
|
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let sides = Sides::new(self.left(), self.top(), self.right(), self.bottom());
|
let mut backlog = vec![];
|
||||||
let padding = sides.resolve(styles);
|
let padding = sides.resolve(styles);
|
||||||
let pod = regions.map(&mut backlog, |size| shrink(size, padding));
|
let pod = regions.map(&mut backlog, |size| shrink(size, padding));
|
||||||
let mut fragment = self.body().layout(vt, styles, pod)?;
|
let mut fragment = self.body().layout(vt, styles, pod)?;
|
||||||
|
@ -21,29 +21,14 @@ use crate::prelude::*;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - paper: `Paper` (positional, settable)
|
/// - paper: `Paper` (positional, named, settable)
|
||||||
/// A standard paper size to set width and height. When this is not specified,
|
/// A standard paper size to set width and height. When this is not specified,
|
||||||
/// Typst defaults to `{"a4"}` paper.
|
/// Typst defaults to `{"a4"}` paper.
|
||||||
///
|
///
|
||||||
/// Display: Page
|
/// Display: Page
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node]
|
#[node]
|
||||||
#[set({
|
|
||||||
if let Some(paper) = args.named_or_find::<Paper>("paper")? {
|
|
||||||
styles.set(Self::set_width(Smart::Custom(paper.width().into())));
|
|
||||||
styles.set(Self::set_height(Smart::Custom(paper.height().into())));
|
|
||||||
}
|
|
||||||
})]
|
|
||||||
pub struct PageNode {
|
pub struct PageNode {
|
||||||
/// The contents of the page(s).
|
|
||||||
///
|
|
||||||
/// Multiple pages will be created if the content does not fit on a single
|
|
||||||
/// page. A new page with the page properties prior to the function invocation
|
|
||||||
/// will be created after the body has been typeset.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The width of the page.
|
/// The width of the page.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -56,8 +41,12 @@ pub struct PageNode {
|
|||||||
/// box(square(width: 1cm))
|
/// box(square(width: 1cm))
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
|
#[parse(
|
||||||
|
let paper = args.named_or_find::<Paper>("paper")?;
|
||||||
|
args.named("width")?
|
||||||
|
.or_else(|| paper.map(|paper| Smart::Custom(paper.width().into())))
|
||||||
|
)]
|
||||||
#[default(Smart::Custom(Paper::A4.width().into()))]
|
#[default(Smart::Custom(Paper::A4.width().into()))]
|
||||||
pub width: Smart<Length>,
|
pub width: Smart<Length>,
|
||||||
|
|
||||||
@ -67,8 +56,11 @@ pub struct PageNode {
|
|||||||
/// by inserting a [page break]($func/pagebreak). Most examples throughout
|
/// by inserting a [page break]($func/pagebreak). Most examples throughout
|
||||||
/// this documentation use `{auto}` for the height of the page to
|
/// this documentation use `{auto}` for the height of the page to
|
||||||
/// dynamically grow and shrink to fit their content.
|
/// dynamically grow and shrink to fit their content.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
|
#[parse(
|
||||||
|
args.named("height")?
|
||||||
|
.or_else(|| paper.map(|paper| Smart::Custom(paper.height().into())))
|
||||||
|
)]
|
||||||
#[default(Smart::Custom(Paper::A4.height().into()))]
|
#[default(Smart::Custom(Paper::A4.height().into()))]
|
||||||
pub height: Smart<Length>,
|
pub height: Smart<Length>,
|
||||||
|
|
||||||
@ -90,7 +82,6 @@ pub struct PageNode {
|
|||||||
/// New York, NY 10001 \
|
/// New York, NY 10001 \
|
||||||
/// +1 555 555 5555
|
/// +1 555 555 5555
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub flipped: bool,
|
pub flipped: bool,
|
||||||
|
|
||||||
@ -122,9 +113,7 @@ pub struct PageNode {
|
|||||||
/// fill: aqua,
|
/// fill: aqua,
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub margin: Sides<Option<Smart<Rel<Length>>>>,
|
pub margin: Sides<Option<Smart<Rel<Length>>>>,
|
||||||
|
|
||||||
/// How many columns the page has.
|
/// How many columns the page has.
|
||||||
@ -141,7 +130,6 @@ pub struct PageNode {
|
|||||||
/// emissions and mitigate the impacts
|
/// emissions and mitigate the impacts
|
||||||
/// of a rapidly changing climate.
|
/// of a rapidly changing climate.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(NonZeroUsize::new(1).unwrap())]
|
#[default(NonZeroUsize::new(1).unwrap())]
|
||||||
pub columns: NonZeroUsize,
|
pub columns: NonZeroUsize,
|
||||||
|
|
||||||
@ -157,8 +145,6 @@ pub struct PageNode {
|
|||||||
/// #set text(fill: rgb("fdfdfd"))
|
/// #set text(fill: rgb("fdfdfd"))
|
||||||
/// *Dark mode enabled.*
|
/// *Dark mode enabled.*
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// The page's header.
|
/// The page's header.
|
||||||
@ -180,8 +166,6 @@ pub struct PageNode {
|
|||||||
///
|
///
|
||||||
/// #lorem(18)
|
/// #lorem(18)
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub header: Option<Marginal>,
|
pub header: Option<Marginal>,
|
||||||
|
|
||||||
/// The page's footer.
|
/// The page's footer.
|
||||||
@ -205,8 +189,6 @@ pub struct PageNode {
|
|||||||
///
|
///
|
||||||
/// #lorem(18)
|
/// #lorem(18)
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub footer: Option<Marginal>,
|
pub footer: Option<Marginal>,
|
||||||
|
|
||||||
/// Content in the page's background.
|
/// Content in the page's background.
|
||||||
@ -227,8 +209,6 @@ pub struct PageNode {
|
|||||||
/// In the year 2023, we plan to take over the world
|
/// In the year 2023, we plan to take over the world
|
||||||
/// (of typesetting).
|
/// (of typesetting).
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub background: Option<Marginal>,
|
pub background: Option<Marginal>,
|
||||||
|
|
||||||
/// Content in the page's foreground.
|
/// Content in the page's foreground.
|
||||||
@ -245,9 +225,16 @@ pub struct PageNode {
|
|||||||
/// "Weak Reject" because they did
|
/// "Weak Reject" because they did
|
||||||
/// not understand our approach...
|
/// not understand our approach...
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub foreground: Option<Marginal>,
|
pub foreground: Option<Marginal>,
|
||||||
|
|
||||||
|
/// The contents of the page(s).
|
||||||
|
///
|
||||||
|
/// Multiple pages will be created if the content does not fit on a single
|
||||||
|
/// page. A new page with the page properties prior to the function invocation
|
||||||
|
/// will be created after the body has been typeset.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageNode {
|
impl PageNode {
|
||||||
@ -260,10 +247,10 @@ impl PageNode {
|
|||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
// When one of the lengths is infinite the page fits its content along
|
// When one of the lengths is infinite the page fits its content along
|
||||||
// that axis.
|
// that axis.
|
||||||
let width = Self::width_in(styles).unwrap_or(Abs::inf());
|
let width = self.width(styles).unwrap_or(Abs::inf());
|
||||||
let height = Self::height_in(styles).unwrap_or(Abs::inf());
|
let height = self.height(styles).unwrap_or(Abs::inf());
|
||||||
let mut size = Size::new(width, height);
|
let mut size = Size::new(width, height);
|
||||||
if Self::flipped_in(styles) {
|
if self.flipped(styles) {
|
||||||
std::mem::swap(&mut size.x, &mut size.y);
|
std::mem::swap(&mut size.x, &mut size.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,14 +261,14 @@ impl PageNode {
|
|||||||
|
|
||||||
// Determine the margins.
|
// Determine the margins.
|
||||||
let default = Rel::from(0.1190 * min);
|
let default = Rel::from(0.1190 * min);
|
||||||
let padding = Self::margin_in(styles).map(|side| side.unwrap_or(default));
|
let padding = self.margin(styles).map(|side| side.unwrap_or(default));
|
||||||
|
|
||||||
let mut child = self.body();
|
let mut child = self.body();
|
||||||
|
|
||||||
// Realize columns.
|
// Realize columns.
|
||||||
let columns = Self::columns_in(styles);
|
let columns = self.columns(styles);
|
||||||
if columns.get() > 1 {
|
if columns.get() > 1 {
|
||||||
child = ColumnsNode::new(columns, child).pack();
|
child = ColumnsNode::new(child).with_count(columns).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Realize margins.
|
// Realize margins.
|
||||||
@ -291,11 +278,11 @@ impl PageNode {
|
|||||||
let regions = Regions::repeat(size, size.map(Abs::is_finite));
|
let regions = Regions::repeat(size, size.map(Abs::is_finite));
|
||||||
let mut fragment = child.layout(vt, styles, regions)?;
|
let mut fragment = child.layout(vt, styles, regions)?;
|
||||||
|
|
||||||
let fill = Self::fill_in(styles);
|
let fill = self.fill(styles);
|
||||||
let header = Self::header_in(styles);
|
let header = self.header(styles);
|
||||||
let footer = Self::footer_in(styles);
|
let footer = self.footer(styles);
|
||||||
let foreground = Self::foreground_in(styles);
|
let foreground = self.foreground(styles);
|
||||||
let background = Self::background_in(styles);
|
let background = self.background(styles);
|
||||||
|
|
||||||
// Realize overlays.
|
// Realize overlays.
|
||||||
for frame in &mut fragment {
|
for frame in &mut fragment {
|
||||||
@ -352,7 +339,6 @@ impl PageNode {
|
|||||||
pub struct PagebreakNode {
|
pub struct PagebreakNode {
|
||||||
/// If `{true}`, the page break is skipped if the current page is already
|
/// If `{true}`, the page break is skipped if the current page is already
|
||||||
/// empty.
|
/// empty.
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub weak: bool,
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
|
@ -43,11 +43,6 @@ use crate::text::{
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Construct)]
|
#[node(Construct)]
|
||||||
pub struct ParNode {
|
pub struct ParNode {
|
||||||
/// The paragraph's children.
|
|
||||||
#[variadic]
|
|
||||||
#[skip]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
|
|
||||||
/// The indent the first line of a consecutive paragraph should have.
|
/// The indent the first line of a consecutive paragraph should have.
|
||||||
///
|
///
|
||||||
/// The first paragraph on a page will never be indented.
|
/// The first paragraph on a page will never be indented.
|
||||||
@ -56,15 +51,12 @@ pub struct ParNode {
|
|||||||
/// space between paragraphs or indented first lines. Consider turning the
|
/// space between paragraphs or indented first lines. Consider turning the
|
||||||
/// [paragraph spacing]($func/block.spacing) off when using this property
|
/// [paragraph spacing]($func/block.spacing) off when using this property
|
||||||
/// (e.g. using `[#show par: set block(spacing: 0pt)]`).
|
/// (e.g. using `[#show par: set block(spacing: 0pt)]`).
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub indent: Length,
|
pub indent: Length,
|
||||||
|
|
||||||
/// The spacing between lines.
|
/// The spacing between lines.
|
||||||
///
|
///
|
||||||
/// The default value is `{0.65em}`.
|
/// The default value is `{0.65em}`.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Em::new(0.65).into())]
|
#[default(Em::new(0.65).into())]
|
||||||
pub leading: Length,
|
pub leading: Length,
|
||||||
@ -78,7 +70,6 @@ pub struct ParNode {
|
|||||||
/// Note that the current [alignment]($func/align) still has an effect on
|
/// Note that the current [alignment]($func/align) still has an effect on
|
||||||
/// the placement of the last line except if it ends with a [justified line
|
/// the placement of the last line except if it ends with a [justified line
|
||||||
/// break]($func/linebreak.justify).
|
/// break]($func/linebreak.justify).
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub justify: bool,
|
pub justify: bool,
|
||||||
|
|
||||||
@ -105,9 +96,13 @@ pub struct ParNode {
|
|||||||
/// very aesthetic example is one
|
/// very aesthetic example is one
|
||||||
/// of them.
|
/// of them.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
#[default]
|
||||||
pub linebreaks: Smart<Linebreaks>,
|
pub linebreaks: Smart<Linebreaks>,
|
||||||
|
|
||||||
|
/// The paragraph's children.
|
||||||
|
#[internal]
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for ParNode {
|
impl Construct for ParNode {
|
||||||
@ -115,9 +110,11 @@ impl Construct for ParNode {
|
|||||||
// The paragraph constructor is special: It doesn't create a paragraph
|
// The paragraph constructor is special: It doesn't create a paragraph
|
||||||
// node. Instead, it just ensures that the passed content lives in a
|
// node. Instead, it just ensures that the passed content lives in a
|
||||||
// separate paragraph and styles it.
|
// separate paragraph and styles it.
|
||||||
|
let styles = Self::set(args)?;
|
||||||
|
let body = args.expect::<Content>("body")?;
|
||||||
Ok(Content::sequence(vec![
|
Ok(Content::sequence(vec![
|
||||||
ParbreakNode::new().pack(),
|
ParbreakNode::new().pack(),
|
||||||
args.expect("body")?,
|
body.styled_with_map(styles),
|
||||||
ParbreakNode::new().pack(),
|
ParbreakNode::new().pack(),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
@ -334,7 +331,7 @@ enum Segment<'a> {
|
|||||||
/// A math formula.
|
/// A math formula.
|
||||||
Formula(&'a FormulaNode),
|
Formula(&'a FormulaNode),
|
||||||
/// A box with arbitrary content.
|
/// A box with arbitrary content.
|
||||||
Box(&'a BoxNode),
|
Box(&'a BoxNode, bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Segment<'_> {
|
impl Segment<'_> {
|
||||||
@ -343,8 +340,8 @@ impl Segment<'_> {
|
|||||||
match *self {
|
match *self {
|
||||||
Self::Text(len) => len,
|
Self::Text(len) => len,
|
||||||
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
Self::Spacing(_) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Box(node) if node.width().is_fractional() => SPACING_REPLACE.len_utf8(),
|
Self::Box(_, true) => SPACING_REPLACE.len_utf8(),
|
||||||
Self::Formula(_) | Self::Box(_) => NODE_REPLACE.len_utf8(),
|
Self::Formula(_) | Self::Box(_, _) => NODE_REPLACE.len_utf8(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -540,7 +537,7 @@ fn collect<'a>(
|
|||||||
full.push(SPACING_REPLACE);
|
full.push(SPACING_REPLACE);
|
||||||
Segment::Spacing(node.amount())
|
Segment::Spacing(node.amount())
|
||||||
} else if let Some(node) = child.to::<LinebreakNode>() {
|
} else if let Some(node) = child.to::<LinebreakNode>() {
|
||||||
let c = if node.justify() { '\u{2028}' } else { '\n' };
|
let c = if node.justify(styles) { '\u{2028}' } else { '\n' };
|
||||||
full.push(c);
|
full.push(c);
|
||||||
Segment::Text(c.len_utf8())
|
Segment::Text(c.len_utf8())
|
||||||
} else if let Some(node) = child.to::<SmartQuoteNode>() {
|
} else if let Some(node) = child.to::<SmartQuoteNode>() {
|
||||||
@ -561,21 +558,18 @@ fn collect<'a>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
full.push_str(quoter.quote("es, node.double(), peeked));
|
full.push_str(quoter.quote("es, node.double(styles), peeked));
|
||||||
} else {
|
} else {
|
||||||
full.push(if node.double() { '"' } else { '\'' });
|
full.push(if node.double(styles) { '"' } else { '\'' });
|
||||||
}
|
}
|
||||||
Segment::Text(full.len() - prev)
|
Segment::Text(full.len() - prev)
|
||||||
} else if let Some(node) = child.to::<FormulaNode>() {
|
} else if let Some(node) = child.to::<FormulaNode>() {
|
||||||
full.push(NODE_REPLACE);
|
full.push(NODE_REPLACE);
|
||||||
Segment::Formula(node)
|
Segment::Formula(node)
|
||||||
} else if let Some(node) = child.to::<BoxNode>() {
|
} else if let Some(node) = child.to::<BoxNode>() {
|
||||||
full.push(if node.width().is_fractional() {
|
let frac = node.width(styles).is_fractional();
|
||||||
SPACING_REPLACE
|
full.push(if frac { SPACING_REPLACE } else { NODE_REPLACE });
|
||||||
} else {
|
Segment::Box(node, frac)
|
||||||
NODE_REPLACE
|
|
||||||
});
|
|
||||||
Segment::Box(node)
|
|
||||||
} else if let Some(span) = child.span() {
|
} else if let Some(span) = child.span() {
|
||||||
bail!(span, "unexpected document child");
|
bail!(span, "unexpected document child");
|
||||||
} else {
|
} else {
|
||||||
@ -645,8 +639,8 @@ fn prepare<'a>(
|
|||||||
frame.translate(Point::with_y(TextNode::baseline_in(styles)));
|
frame.translate(Point::with_y(TextNode::baseline_in(styles)));
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
Segment::Box(node) => {
|
Segment::Box(node, _) => {
|
||||||
if let Sizing::Fr(v) = node.width() {
|
if let Sizing::Fr(v) = node.width(styles) {
|
||||||
items.push(Item::Fractional(v, Some((node, styles))));
|
items.push(Item::Fractional(v, Some((node, styles))));
|
||||||
} else {
|
} else {
|
||||||
let pod = Regions::one(region, Axes::splat(false));
|
let pod = Regions::one(region, Axes::splat(false));
|
||||||
|
@ -34,11 +34,6 @@ pub struct PlaceNode {
|
|||||||
#[default(Axes::with_x(Some(GenAlign::Start)))]
|
#[default(Axes::with_x(Some(GenAlign::Start)))]
|
||||||
pub alignment: Axes<Option<GenAlign>>,
|
pub alignment: Axes<Option<GenAlign>>,
|
||||||
|
|
||||||
/// The content to place.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The horizontal displacement of the placed content.
|
/// The horizontal displacement of the placed content.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -48,14 +43,15 @@ pub struct PlaceNode {
|
|||||||
/// place(center, dx: amount - 32pt, dy: amount)[A]
|
/// place(center, dx: amount - 32pt, dy: amount)[A]
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub dx: Rel<Length>,
|
pub dx: Rel<Length>,
|
||||||
|
|
||||||
/// The vertical displacement of the placed content.
|
/// The vertical displacement of the placed content.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub dy: Rel<Length>,
|
pub dy: Rel<Length>,
|
||||||
|
|
||||||
|
/// The content to place.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PlaceNode {
|
impl Layout for PlaceNode {
|
||||||
@ -65,7 +61,7 @@ impl Layout for PlaceNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let out_of_flow = self.out_of_flow();
|
let out_of_flow = self.out_of_flow(styles);
|
||||||
|
|
||||||
// The pod is the base area of the region because for absolute
|
// The pod is the base area of the region because for absolute
|
||||||
// placement we don't really care about the already used area.
|
// placement we don't really care about the already used area.
|
||||||
@ -77,8 +73,8 @@ impl Layout for PlaceNode {
|
|||||||
|
|
||||||
let child = self
|
let child = self
|
||||||
.body()
|
.body()
|
||||||
.moved(Axes::new(self.dx(), self.dy()))
|
.moved(Axes::new(self.dx(styles), self.dy(styles)))
|
||||||
.aligned(self.alignment());
|
.aligned(self.alignment(styles));
|
||||||
|
|
||||||
let mut frame = child.layout(vt, styles, pod)?.into_frame();
|
let mut frame = child.layout(vt, styles, pod)?.into_frame();
|
||||||
|
|
||||||
@ -95,8 +91,8 @@ impl PlaceNode {
|
|||||||
/// Whether this node wants to be placed relative to its its parent's base
|
/// Whether this node wants to be placed relative to its its parent's base
|
||||||
/// origin. Instead of relative to the parent's current flow/cursor
|
/// origin. Instead of relative to the parent's current flow/cursor
|
||||||
/// position.
|
/// position.
|
||||||
pub fn out_of_flow(&self) -> bool {
|
pub fn out_of_flow(&self, styles: StyleChain) -> bool {
|
||||||
self.alignment().y.is_some()
|
self.alignment(styles).y.is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,6 @@ pub struct HNode {
|
|||||||
/// #h(8pt, weak: true) on both
|
/// #h(8pt, weak: true) on both
|
||||||
/// sides, they do show up.
|
/// sides, they do show up.
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub weak: bool,
|
pub weak: bool,
|
||||||
}
|
}
|
||||||
@ -51,7 +50,7 @@ impl Behave for HNode {
|
|||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
if self.amount().is_fractional() {
|
if self.amount().is_fractional() {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
} else if self.weak() {
|
} else if self.weak(StyleChain::default()) {
|
||||||
Behaviour::Weak(1)
|
Behaviour::Weak(1)
|
||||||
} else {
|
} else {
|
||||||
Behaviour::Ignorant
|
Behaviour::Ignorant
|
||||||
@ -86,7 +85,7 @@ impl Behave for HNode {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - weak: `bool` (named)
|
/// - weak: `bool` (named, settable)
|
||||||
/// If true, the spacing collapses at the start or end of a flow. Moreover,
|
/// 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.
|
/// from multiple adjacent weak spacings all but the largest one collapse.
|
||||||
/// Weak spacings will always collapse adjacent paragraph spacing, even if the
|
/// Weak spacings will always collapse adjacent paragraph spacing, even if the
|
||||||
@ -103,7 +102,7 @@ impl Behave for HNode {
|
|||||||
///
|
///
|
||||||
/// Display: Spacing (V)
|
/// Display: Spacing (V)
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Construct, Behave)]
|
#[node(Behave)]
|
||||||
pub struct VNode {
|
pub struct VNode {
|
||||||
/// How much spacing to insert.
|
/// How much spacing to insert.
|
||||||
#[positional]
|
#[positional]
|
||||||
@ -111,24 +110,11 @@ pub struct VNode {
|
|||||||
pub amount: Spacing,
|
pub amount: Spacing,
|
||||||
|
|
||||||
/// The node's weakness level, see also [`Behaviour`].
|
/// The node's weakness level, see also [`Behaviour`].
|
||||||
#[named]
|
#[internal]
|
||||||
#[skip]
|
#[parse(args.named("weak")?.map(|v: bool| v as usize))]
|
||||||
#[default]
|
|
||||||
pub weakness: usize,
|
pub weakness: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for 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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VNode {
|
impl VNode {
|
||||||
/// Normal strong spacing.
|
/// Normal strong spacing.
|
||||||
pub fn strong(amount: Spacing) -> Self {
|
pub fn strong(amount: Spacing) -> Self {
|
||||||
@ -160,8 +146,8 @@ impl Behave for VNode {
|
|||||||
fn behaviour(&self) -> Behaviour {
|
fn behaviour(&self) -> Behaviour {
|
||||||
if self.amount().is_fractional() {
|
if self.amount().is_fractional() {
|
||||||
Behaviour::Destructive
|
Behaviour::Destructive
|
||||||
} else if self.weakness() > 0 {
|
} else if self.weakness(StyleChain::default()) > 0 {
|
||||||
Behaviour::Weak(self.weakness())
|
Behaviour::Weak(self.weakness(StyleChain::default()))
|
||||||
} else {
|
} else {
|
||||||
Behaviour::Ignorant
|
Behaviour::Ignorant
|
||||||
}
|
}
|
||||||
|
@ -22,24 +22,21 @@ use crate::prelude::*;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct StackNode {
|
pub struct StackNode {
|
||||||
/// The childfren to stack along the axis.
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<StackChild>,
|
|
||||||
|
|
||||||
/// The direction along which the items are stacked. Possible values are:
|
/// The direction along which the items are stacked. Possible values are:
|
||||||
///
|
///
|
||||||
/// - `{ltr}`: Left to right.
|
/// - `{ltr}`: Left to right.
|
||||||
/// - `{rtl}`: Right to left.
|
/// - `{rtl}`: Right to left.
|
||||||
/// - `{ttb}`: Top to bottom.
|
/// - `{ttb}`: Top to bottom.
|
||||||
/// - `{btt}`: Bottom to top.
|
/// - `{btt}`: Bottom to top.
|
||||||
#[named]
|
|
||||||
#[default(Dir::TTB)]
|
#[default(Dir::TTB)]
|
||||||
pub dir: Dir,
|
pub dir: Dir,
|
||||||
|
|
||||||
/// Spacing to insert between items where no explicit spacing was provided.
|
/// Spacing to insert between items where no explicit spacing was provided.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub spacing: Option<Spacing>,
|
pub spacing: Option<Spacing>,
|
||||||
|
|
||||||
|
/// The childfren to stack along the axis.
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for StackNode {
|
impl Layout for StackNode {
|
||||||
@ -49,10 +46,10 @@ impl Layout for StackNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut layouter = StackLayouter::new(self.dir(), regions, styles);
|
let mut layouter = StackLayouter::new(self.dir(styles), regions, styles);
|
||||||
|
|
||||||
// Spacing to insert before the next block.
|
// Spacing to insert before the next block.
|
||||||
let spacing = self.spacing();
|
let spacing = self.spacing(styles);
|
||||||
let mut deferred = None;
|
let mut deferred = None;
|
||||||
|
|
||||||
for child in self.children() {
|
for child in self.children() {
|
||||||
@ -201,12 +198,15 @@ impl<'a> StackLayouter<'a> {
|
|||||||
|
|
||||||
// Block-axis alignment of the `AlignNode` is respected
|
// Block-axis alignment of the `AlignNode` is respected
|
||||||
// by the stack node.
|
// by the stack node.
|
||||||
let aligns = match block.to::<StyledNode>() {
|
let aligns = if let Some(align) = block.to::<AlignNode>() {
|
||||||
Some(styled) => AlignNode::alignment_in(styles.chain(&styled.map())),
|
align.alignment(styles)
|
||||||
None => AlignNode::alignment_in(styles),
|
} else if let Some(styled) = block.to::<StyledNode>() {
|
||||||
};
|
AlignNode::alignment_in(styles.chain(&styled.map()))
|
||||||
|
} else {
|
||||||
|
AlignNode::alignment_in(styles)
|
||||||
|
}
|
||||||
|
.resolve(styles);
|
||||||
|
|
||||||
let aligns = aligns.resolve(styles);
|
|
||||||
let fragment = block.layout(vt, styles, self.regions)?;
|
let fragment = block.layout(vt, styles, self.regions)?;
|
||||||
let len = fragment.len();
|
let len = fragment.len();
|
||||||
for (i, frame) in fragment.into_iter().enumerate() {
|
for (i, frame) in fragment.into_iter().enumerate() {
|
||||||
|
@ -30,7 +30,7 @@ use crate::prelude::*;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - gutter: `TrackSizings` (named)
|
/// - gutter: `TrackSizings` (named, settable)
|
||||||
/// Defines the gaps between rows & columns.
|
/// Defines the gaps between rows & columns.
|
||||||
/// See the [grid documentation]($func/grid) for more information on gutters.
|
/// See the [grid documentation]($func/grid) for more information on gutters.
|
||||||
///
|
///
|
||||||
@ -38,36 +38,27 @@ use crate::prelude::*;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct TableNode {
|
pub struct TableNode {
|
||||||
/// The contents of the table cells.
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
|
|
||||||
/// Defines the column sizes.
|
/// Defines the column sizes.
|
||||||
/// See the [grid documentation]($func/grid) for more information on track
|
/// See the [grid documentation]($func/grid) for more information on track
|
||||||
/// sizing.
|
/// sizing.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub columns: TrackSizings,
|
pub columns: TrackSizings,
|
||||||
|
|
||||||
/// Defines the row sizes.
|
/// Defines the row sizes.
|
||||||
/// See the [grid documentation]($func/grid) for more information on track
|
/// See the [grid documentation]($func/grid) for more information on track
|
||||||
/// sizing.
|
/// sizing.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub rows: TrackSizings,
|
pub rows: TrackSizings,
|
||||||
|
|
||||||
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
/// Defines the gaps between columns. Takes precedence over `gutter`.
|
||||||
/// See the [grid documentation]($func/grid) for more information on gutters.
|
/// See the [grid documentation]($func/grid) for more information on gutters.
|
||||||
#[named]
|
#[parse(
|
||||||
#[shorthand(gutter)]
|
let gutter = args.named("gutter")?;
|
||||||
#[default]
|
args.named("column-gutter")?.or_else(|| gutter.clone())
|
||||||
|
)]
|
||||||
pub column_gutter: TrackSizings,
|
pub column_gutter: TrackSizings,
|
||||||
|
|
||||||
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
/// Defines the gaps between rows. Takes precedence over `gutter`.
|
||||||
/// See the [grid documentation]($func/grid) for more information on gutters.
|
/// See the [grid documentation]($func/grid) for more information on gutters.
|
||||||
#[named]
|
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
|
||||||
#[shorthand(gutter)]
|
|
||||||
#[default]
|
|
||||||
pub row_gutter: TrackSizings,
|
pub row_gutter: TrackSizings,
|
||||||
|
|
||||||
/// How to fill the cells.
|
/// How to fill the cells.
|
||||||
@ -90,8 +81,6 @@ pub struct TableNode {
|
|||||||
/// [Profit:], [500 €], [1000 €], [1500 €],
|
/// [Profit:], [500 €], [1000 €], [1500 €],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Celled<Option<Paint>>,
|
pub fill: Celled<Option<Paint>>,
|
||||||
|
|
||||||
/// How to align the cell's content.
|
/// How to align the cell's content.
|
||||||
@ -99,15 +88,12 @@ pub struct TableNode {
|
|||||||
/// This can either be a single alignment or a function that returns an
|
/// This can either be a single alignment or a function that returns an
|
||||||
/// alignment. The function is passed the cell's column and row index,
|
/// alignment. The function is passed the cell's column and row index,
|
||||||
/// starting at zero. If set to `{auto}`, the outer alignment is used.
|
/// starting at zero. If set to `{auto}`, the outer alignment is used.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub align: Celled<Smart<Axes<Option<GenAlign>>>>,
|
pub align: Celled<Smart<Axes<Option<GenAlign>>>>,
|
||||||
|
|
||||||
/// How to stroke the cells.
|
/// How to stroke the cells.
|
||||||
///
|
///
|
||||||
/// This can be a color, a stroke width, both, or `{none}` to disable
|
/// This can be a color, a stroke width, both, or `{none}` to disable
|
||||||
/// the stroke.
|
/// the stroke.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Some(PartialStroke::default()))]
|
#[default(Some(PartialStroke::default()))]
|
||||||
@ -116,9 +102,12 @@ pub struct TableNode {
|
|||||||
/// How much to pad the cells's content.
|
/// How much to pad the cells's content.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[settable]
|
|
||||||
#[default(Abs::pt(5.0).into())]
|
#[default(Abs::pt(5.0).into())]
|
||||||
pub inset: Rel<Length>,
|
pub inset: Rel<Length>,
|
||||||
|
|
||||||
|
/// The contents of the table cells.
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for TableNode {
|
impl Layout for TableNode {
|
||||||
@ -128,11 +117,11 @@ impl Layout for TableNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let inset = Self::inset_in(styles);
|
let inset = self.inset(styles);
|
||||||
let align = Self::align_in(styles);
|
let align = self.align(styles);
|
||||||
|
|
||||||
let tracks = Axes::new(self.columns().0, self.rows().0);
|
let tracks = Axes::new(self.columns(styles).0, self.rows(styles).0);
|
||||||
let gutter = Axes::new(self.column_gutter().0, self.row_gutter().0);
|
let gutter = Axes::new(self.column_gutter(styles).0, self.row_gutter(styles).0);
|
||||||
let cols = tracks.x.len().max(1);
|
let cols = tracks.x.len().max(1);
|
||||||
let cells: Vec<_> = self
|
let cells: Vec<_> = self
|
||||||
.children()
|
.children()
|
||||||
@ -151,8 +140,8 @@ impl Layout for TableNode {
|
|||||||
})
|
})
|
||||||
.collect::<SourceResult<_>>()?;
|
.collect::<SourceResult<_>>()?;
|
||||||
|
|
||||||
let fill = Self::fill_in(styles);
|
let fill = self.fill(styles);
|
||||||
let stroke = Self::stroke_in(styles).map(PartialStroke::unwrap_or_default);
|
let stroke = self.stroke(styles).map(PartialStroke::unwrap_or_default);
|
||||||
|
|
||||||
// Prepare grid layout by unifying content and gutter tracks.
|
// Prepare grid layout by unifying content and gutter tracks.
|
||||||
let layouter = GridLayouter::new(
|
let layouter = GridLayouter::new(
|
||||||
|
@ -23,21 +23,6 @@ use crate::text::{SpaceNode, TextNode};
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct TermsNode {
|
pub struct TermsNode {
|
||||||
/// The term list's children.
|
|
||||||
///
|
|
||||||
/// When using the term list syntax, adjacent items are automatically
|
|
||||||
/// collected into term lists, even through constructs like for loops.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #for year, product in (
|
|
||||||
/// "1978": "TeX",
|
|
||||||
/// "1984": "LaTeX",
|
|
||||||
/// "2019": "Typst",
|
|
||||||
/// ) [/ #product: Born in #year.]
|
|
||||||
/// ```
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<TermItem>,
|
|
||||||
|
|
||||||
/// If this is `{false}`, the items are spaced apart with [term list
|
/// If this is `{false}`, the items are spaced apart with [term list
|
||||||
/// spacing]($func/terms.spacing). If it is `{true}`, they use normal
|
/// spacing]($func/terms.spacing). If it is `{true}`, they use normal
|
||||||
/// [leading]($func/par.leading) instead. This makes the term list more
|
/// [leading]($func/par.leading) instead. This makes the term list more
|
||||||
@ -53,14 +38,11 @@ pub struct TermsNode {
|
|||||||
/// insert a blank line between the
|
/// insert a blank line between the
|
||||||
/// items.
|
/// items.
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub tight: bool,
|
pub tight: bool,
|
||||||
|
|
||||||
/// The indentation of each item's term.
|
/// The indentation of each item's term.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub indent: Length,
|
pub indent: Length,
|
||||||
|
|
||||||
/// The hanging indent of the description.
|
/// The hanging indent of the description.
|
||||||
@ -70,7 +52,6 @@ pub struct TermsNode {
|
|||||||
/// / Term: This term list does not
|
/// / Term: This term list does not
|
||||||
/// make use of hanging indents.
|
/// make use of hanging indents.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Em::new(1.0).into())]
|
#[default(Em::new(1.0).into())]
|
||||||
pub hanging_indent: Length,
|
pub hanging_indent: Length,
|
||||||
@ -78,9 +59,22 @@ pub struct TermsNode {
|
|||||||
/// The spacing between the items of a wide (non-tight) term list.
|
/// The spacing between the items of a wide (non-tight) term list.
|
||||||
///
|
///
|
||||||
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
/// If set to `{auto}`, uses the spacing [below blocks]($func/block.below).
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub spacing: Smart<Spacing>,
|
pub spacing: Smart<Spacing>,
|
||||||
|
|
||||||
|
/// The term list's children.
|
||||||
|
///
|
||||||
|
/// When using the term list syntax, adjacent items are automatically
|
||||||
|
/// collected into term lists, even through constructs like for loops.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #for year, product in (
|
||||||
|
/// "1978": "TeX",
|
||||||
|
/// "1984": "LaTeX",
|
||||||
|
/// "2019": "Typst",
|
||||||
|
/// ) [/ #product: Born in #year.]
|
||||||
|
/// ```
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<TermItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for TermsNode {
|
impl Layout for TermsNode {
|
||||||
@ -90,12 +84,12 @@ impl Layout for TermsNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let indent = Self::indent_in(styles);
|
let indent = self.indent(styles);
|
||||||
let body_indent = Self::hanging_indent_in(styles);
|
let body_indent = self.hanging_indent(styles);
|
||||||
let gutter = if self.tight() {
|
let gutter = if self.tight(styles) {
|
||||||
ParNode::leading_in(styles).into()
|
ParNode::leading_in(styles).into()
|
||||||
} else {
|
} else {
|
||||||
Self::spacing_in(styles)
|
self.spacing(styles)
|
||||||
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
.unwrap_or_else(|| BlockNode::below_in(styles).amount())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,20 +25,16 @@ use crate::prelude::*;
|
|||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct MoveNode {
|
pub struct MoveNode {
|
||||||
|
/// The horizontal displacement of the content.
|
||||||
|
pub dx: Rel<Length>,
|
||||||
|
|
||||||
|
/// The vertical displacement of the content.
|
||||||
|
pub dy: Rel<Length>,
|
||||||
|
|
||||||
/// The content to move.
|
/// The content to move.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// The horizontal displacement of the content.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub dx: Rel<Length>,
|
|
||||||
|
|
||||||
/// The vertical displacement of the content.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub dy: Rel<Length>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for MoveNode {
|
impl Layout for MoveNode {
|
||||||
@ -50,7 +46,7 @@ impl Layout for MoveNode {
|
|||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let delta = Axes::new(self.dx(), self.dy()).resolve(styles);
|
let delta = Axes::new(self.dx(styles), self.dy(styles)).resolve(styles);
|
||||||
let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s));
|
let delta = delta.zip(regions.base()).map(|(d, s)| d.relative_to(s));
|
||||||
frame.translate(delta.to_point());
|
frame.translate(delta.to_point());
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
@ -83,14 +79,8 @@ pub struct RotateNode {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
|
||||||
pub angle: Angle,
|
pub angle: Angle,
|
||||||
|
|
||||||
/// The content to rotate.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The origin of the rotation.
|
/// The origin of the rotation.
|
||||||
///
|
///
|
||||||
/// By default, the origin is the center of the rotated element. If,
|
/// By default, the origin is the center of the rotated element. If,
|
||||||
@ -107,10 +97,13 @@ pub struct RotateNode {
|
|||||||
/// #box(rotate(30deg, origin: top + left, square()))
|
/// #box(rotate(30deg, origin: top + left, square()))
|
||||||
/// #box(rotate(30deg, origin: bottom + right, square()))
|
/// #box(rotate(30deg, origin: bottom + right, square()))
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub origin: Axes<Option<GenAlign>>,
|
pub origin: Axes<Option<GenAlign>>,
|
||||||
|
|
||||||
|
/// The content to rotate.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for RotateNode {
|
impl Layout for RotateNode {
|
||||||
@ -122,10 +115,10 @@ impl Layout for RotateNode {
|
|||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let origin = Self::origin_in(styles).unwrap_or(Align::CENTER_HORIZON);
|
let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON);
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||||
let ts = Transform::translate(x, y)
|
let ts = Transform::translate(x, y)
|
||||||
.pre_concat(Transform::rotate(self.angle()))
|
.pre_concat(Transform::rotate(self.angle(styles)))
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
frame.transform(ts);
|
frame.transform(ts);
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
@ -146,24 +139,22 @@ impl Layout for RotateNode {
|
|||||||
///
|
///
|
||||||
/// Display: Scale
|
/// Display: Scale
|
||||||
/// Category: layout
|
/// Category: layout
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct ScaleNode {
|
pub struct ScaleNode {
|
||||||
/// The content to scale.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The horizontal scaling factor.
|
/// The horizontal scaling factor.
|
||||||
///
|
///
|
||||||
/// The body will be mirrored horizontally if the parameter is negative.
|
/// The body will be mirrored horizontally if the parameter is negative.
|
||||||
#[named]
|
#[parse(
|
||||||
|
let all = args.find()?;
|
||||||
|
args.named("x")?.or(all)
|
||||||
|
)]
|
||||||
#[default(Ratio::one())]
|
#[default(Ratio::one())]
|
||||||
pub x: Ratio,
|
pub x: Ratio,
|
||||||
|
|
||||||
/// The vertical scaling factor.
|
/// The vertical scaling factor.
|
||||||
///
|
///
|
||||||
/// The body will be mirrored vertically if the parameter is negative.
|
/// The body will be mirrored vertically if the parameter is negative.
|
||||||
#[named]
|
#[parse(args.named("y")?.or(all))]
|
||||||
#[default(Ratio::one())]
|
#[default(Ratio::one())]
|
||||||
pub y: Ratio,
|
pub y: Ratio,
|
||||||
|
|
||||||
@ -175,19 +166,13 @@ pub struct ScaleNode {
|
|||||||
/// A#box(scale(75%)[A])A \
|
/// A#box(scale(75%)[A])A \
|
||||||
/// B#box(scale(75%, origin: bottom + left)[B])B
|
/// B#box(scale(75%, origin: bottom + left)[B])B
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub origin: Axes<Option<GenAlign>>,
|
pub origin: Axes<Option<GenAlign>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for ScaleNode {
|
/// The content to scale.
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[positional]
|
||||||
let all = args.find()?;
|
#[required]
|
||||||
let x = args.named("x")?.or(all).unwrap_or(Ratio::one());
|
pub body: Content,
|
||||||
let y = args.named("y")?.or(all).unwrap_or(Ratio::one());
|
|
||||||
Ok(Self::new(args.expect::<Content>("body")?).with_x(x).with_y(y).pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ScaleNode {
|
impl Layout for ScaleNode {
|
||||||
@ -199,10 +184,10 @@ impl Layout for ScaleNode {
|
|||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let pod = Regions::one(regions.base(), Axes::splat(false));
|
let pod = Regions::one(regions.base(), Axes::splat(false));
|
||||||
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
let mut frame = self.body().layout(vt, styles, pod)?.into_frame();
|
||||||
let origin = Self::origin_in(styles).unwrap_or(Align::CENTER_HORIZON);
|
let origin = self.origin(styles).unwrap_or(Align::CENTER_HORIZON);
|
||||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||||
let transform = Transform::translate(x, y)
|
let transform = Transform::translate(x, y)
|
||||||
.pre_concat(Transform::scale(self.x(), self.y()))
|
.pre_concat(Transform::scale(self.x(styles), self.y(styles)))
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
frame.transform(transform);
|
frame.transform(transform);
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
|
@ -176,11 +176,11 @@ fn items() -> LangItems {
|
|||||||
strong: |body| text::StrongNode::new(body).pack(),
|
strong: |body| text::StrongNode::new(body).pack(),
|
||||||
emph: |body| text::EmphNode::new(body).pack(),
|
emph: |body| text::EmphNode::new(body).pack(),
|
||||||
raw: |text, lang, block| {
|
raw: |text, lang, block| {
|
||||||
let content = text::RawNode::new(text).with_block(block).pack();
|
let mut node = text::RawNode::new(text).with_block(block);
|
||||||
match lang {
|
if let Some(lang) = lang {
|
||||||
Some(_) => content.styled(text::RawNode::set_lang(lang)),
|
node = node.with_lang(Some(lang));
|
||||||
None => content,
|
|
||||||
}
|
}
|
||||||
|
node.pack()
|
||||||
},
|
},
|
||||||
link: |url| meta::LinkNode::from_url(url).pack(),
|
link: |url| meta::LinkNode::from_url(url).pack(),
|
||||||
ref_: |target| meta::RefNode::new(target).pack(),
|
ref_: |target| meta::RefNode::new(target).pack(),
|
||||||
|
@ -21,13 +21,9 @@ pub struct AttachNode {
|
|||||||
pub base: Content,
|
pub base: Content,
|
||||||
|
|
||||||
/// The top attachment.
|
/// The top attachment.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub top: Option<Content>,
|
pub top: Option<Content>,
|
||||||
|
|
||||||
/// The bottom attachment.
|
/// The bottom attachment.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub bottom: Option<Content>,
|
pub bottom: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,11 +36,17 @@ impl LayoutMath for AttachNode {
|
|||||||
let base = ctx.layout_fragment(&base)?;
|
let base = ctx.layout_fragment(&base)?;
|
||||||
|
|
||||||
ctx.style(ctx.style.for_subscript());
|
ctx.style(ctx.style.for_subscript());
|
||||||
let top = self.top().map(|node| ctx.layout_fragment(&node)).transpose()?;
|
let top = self
|
||||||
|
.top(ctx.styles())
|
||||||
|
.map(|node| ctx.layout_fragment(&node))
|
||||||
|
.transpose()?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
ctx.style(ctx.style.for_superscript());
|
ctx.style(ctx.style.for_superscript());
|
||||||
let bottom = self.bottom().map(|node| ctx.layout_fragment(&node)).transpose()?;
|
let bottom = self
|
||||||
|
.bottom(ctx.styles())
|
||||||
|
.map(|node| ctx.layout_fragment(&node))
|
||||||
|
.transpose()?;
|
||||||
ctx.unstyle();
|
ctx.unstyle();
|
||||||
|
|
||||||
let display_limits = display_limits
|
let display_limits = display_limits
|
||||||
|
@ -16,23 +16,17 @@ pub(super) const DELIM_SHORT_FALL: Em = Em::new(0.1);
|
|||||||
///
|
///
|
||||||
/// Display: Left/Right
|
/// Display: Left/Right
|
||||||
/// Category: math
|
/// Category: math
|
||||||
#[node(Construct, LayoutMath)]
|
#[node(LayoutMath)]
|
||||||
pub struct LrNode {
|
pub struct LrNode {
|
||||||
/// The delimited content, including the delimiters.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The size of the brackets, relative to the height of the wrapped content.
|
/// The size of the brackets, relative to the height of the wrapped content.
|
||||||
///
|
///
|
||||||
/// Defaults to `{100%}`.
|
/// Defaults to `{100%}`.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub size: Smart<Rel<Length>>,
|
pub size: Smart<Rel<Length>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for LrNode {
|
/// The delimited content, including the delimiters.
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
#[parse(
|
||||||
let mut body = Content::empty();
|
let mut body = Content::empty();
|
||||||
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
for (i, arg) in args.all::<Content>()?.into_iter().enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
@ -40,16 +34,16 @@ impl Construct for LrNode {
|
|||||||
}
|
}
|
||||||
body += arg;
|
body += arg;
|
||||||
}
|
}
|
||||||
let size = args.named::<Smart<Rel<Length>>>("size")?.unwrap_or_default();
|
body
|
||||||
Ok(Self::new(body).with_size(size).pack())
|
)]
|
||||||
}
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for LrNode {
|
impl LayoutMath for LrNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let mut body = self.body();
|
let mut body = self.body();
|
||||||
if let Some(node) = body.to::<LrNode>() {
|
if let Some(node) = body.to::<LrNode>() {
|
||||||
if node.size().is_auto() {
|
if node.size(ctx.styles()).is_auto() {
|
||||||
body = node.body();
|
body = node.body();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,7 +57,7 @@ impl LayoutMath for LrNode {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let height = self
|
let height = self
|
||||||
.size()
|
.size(ctx.styles())
|
||||||
.unwrap_or(Rel::one())
|
.unwrap_or(Rel::one())
|
||||||
.resolve(ctx.styles())
|
.resolve(ctx.styles())
|
||||||
.relative_to(2.0 * max_extent);
|
.relative_to(2.0 * max_extent);
|
||||||
|
@ -18,24 +18,23 @@ const VERTICAL_PADDING: Ratio = Ratio::new(0.1);
|
|||||||
/// Category: math
|
/// Category: math
|
||||||
#[node(LayoutMath)]
|
#[node(LayoutMath)]
|
||||||
pub struct VecNode {
|
pub struct VecNode {
|
||||||
/// The elements of the vector.
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
|
|
||||||
/// The delimiter to use.
|
/// The delimiter to use.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set math.vec(delim: "[")
|
/// #set math.vec(delim: "[")
|
||||||
/// $ vec(1, 2) $
|
/// $ vec(1, 2) $
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Delimiter::Paren)]
|
#[default(Delimiter::Paren)]
|
||||||
pub delim: Delimiter,
|
pub delim: Delimiter,
|
||||||
|
|
||||||
|
/// The elements of the vector.
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for VecNode {
|
impl LayoutMath for VecNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let delim = Self::delim_in(ctx.styles());
|
let delim = self.delim(ctx.styles());
|
||||||
let frame = layout_vec_body(ctx, &self.children(), Align::Center)?;
|
let frame = layout_vec_body(ctx, &self.children(), Align::Center)?;
|
||||||
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
||||||
}
|
}
|
||||||
@ -63,8 +62,17 @@ impl LayoutMath for VecNode {
|
|||||||
///
|
///
|
||||||
/// Display: Matrix
|
/// Display: Matrix
|
||||||
/// Category: math
|
/// Category: math
|
||||||
#[node(Construct, LayoutMath)]
|
#[node(LayoutMath)]
|
||||||
pub struct MatNode {
|
pub struct MatNode {
|
||||||
|
/// The delimiter to use.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set math.mat(delim: "[")
|
||||||
|
/// $ mat(1, 2; 3, 4) $
|
||||||
|
/// ```
|
||||||
|
#[default(Delimiter::Paren)]
|
||||||
|
pub delim: Delimiter,
|
||||||
|
|
||||||
/// An array of arrays with the rows of the matrix.
|
/// An array of arrays with the rows of the matrix.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -73,21 +81,7 @@ pub struct MatNode {
|
|||||||
/// $ v := matrix $
|
/// $ v := matrix $
|
||||||
/// ```
|
/// ```
|
||||||
#[variadic]
|
#[variadic]
|
||||||
pub rows: Vec<Vec<Content>>,
|
#[parse(
|
||||||
|
|
||||||
/// The delimiter to use.
|
|
||||||
///
|
|
||||||
/// ```example
|
|
||||||
/// #set math.mat(delim: "[")
|
|
||||||
/// $ mat(1, 2; 3, 4) $
|
|
||||||
/// ```
|
|
||||||
#[settable]
|
|
||||||
#[default(Delimiter::Paren)]
|
|
||||||
pub delim: Delimiter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for MatNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
let mut width = 0;
|
let mut width = 0;
|
||||||
|
|
||||||
@ -109,13 +103,14 @@ impl Construct for MatNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self::new(rows).pack())
|
rows
|
||||||
}
|
)]
|
||||||
|
pub rows: Vec<Vec<Content>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for MatNode {
|
impl LayoutMath for MatNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let delim = Self::delim_in(ctx.styles());
|
let delim = self.delim(ctx.styles());
|
||||||
let frame = layout_mat_body(ctx, &self.rows())?;
|
let frame = layout_mat_body(ctx, &self.rows())?;
|
||||||
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
layout_delimiters(ctx, frame, Some(delim.open()), Some(delim.close()))
|
||||||
}
|
}
|
||||||
@ -139,24 +134,23 @@ impl LayoutMath for MatNode {
|
|||||||
/// Category: math
|
/// Category: math
|
||||||
#[node(LayoutMath)]
|
#[node(LayoutMath)]
|
||||||
pub struct CasesNode {
|
pub struct CasesNode {
|
||||||
/// The branches of the case distinction.
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
|
|
||||||
/// The delimiter to use.
|
/// The delimiter to use.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set math.cases(delim: "[")
|
/// #set math.cases(delim: "[")
|
||||||
/// $ x = cases(1, 2) $
|
/// $ x = cases(1, 2) $
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Delimiter::Brace)]
|
#[default(Delimiter::Brace)]
|
||||||
pub delim: Delimiter,
|
pub delim: Delimiter,
|
||||||
|
|
||||||
|
/// The branches of the case distinction.
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for CasesNode {
|
impl LayoutMath for CasesNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
let delim = Self::delim_in(ctx.styles());
|
let delim = self.delim(ctx.styles());
|
||||||
let frame = layout_vec_body(ctx, &self.children(), Align::Left)?;
|
let frame = layout_vec_body(ctx, &self.children(), Align::Left)?;
|
||||||
layout_delimiters(ctx, frame, Some(delim.open()), None)
|
layout_delimiters(ctx, frame, Some(delim.open()), None)
|
||||||
}
|
}
|
||||||
|
@ -134,21 +134,20 @@ pub fn module() -> Module {
|
|||||||
/// Category: math
|
/// Category: math
|
||||||
#[node(Show, Finalize, Layout, LayoutMath)]
|
#[node(Show, Finalize, Layout, LayoutMath)]
|
||||||
pub struct FormulaNode {
|
pub struct FormulaNode {
|
||||||
|
/// Whether the formula is displayed as a separate block.
|
||||||
|
#[default(false)]
|
||||||
|
pub block: bool,
|
||||||
|
|
||||||
/// The content of the formula.
|
/// The content of the formula.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// Whether the formula is displayed as a separate block.
|
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
|
||||||
pub block: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for FormulaNode {
|
impl Show for FormulaNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, _: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
let mut realized = self.clone().pack().guarded(Guard::Base(NodeId::of::<Self>()));
|
||||||
if self.block() {
|
if self.block(styles) {
|
||||||
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
||||||
}
|
}
|
||||||
Ok(realized)
|
Ok(realized)
|
||||||
@ -156,7 +155,7 @@ impl Show for FormulaNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Finalize for FormulaNode {
|
impl Finalize for FormulaNode {
|
||||||
fn finalize(&self, realized: Content) -> Content {
|
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
|
||||||
realized
|
realized
|
||||||
.styled(TextNode::set_weight(FontWeight::from_number(450)))
|
.styled(TextNode::set_weight(FontWeight::from_number(450)))
|
||||||
.styled(TextNode::set_font(FontList(vec![FontFamily::new(
|
.styled(TextNode::set_font(FontList(vec![FontFamily::new(
|
||||||
@ -172,7 +171,7 @@ impl Layout for FormulaNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let block = self.block();
|
let block = self.block(styles);
|
||||||
|
|
||||||
// Find a math font.
|
// Find a math font.
|
||||||
let variant = variant(styles);
|
let variant = variant(styles);
|
||||||
|
@ -30,7 +30,6 @@ pub struct OpNode {
|
|||||||
/// Whether the operator should force attachments to display as limits.
|
/// Whether the operator should force attachments to display as limits.
|
||||||
///
|
///
|
||||||
/// Defaults to `{false}`.
|
/// Defaults to `{false}`.
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub limits: bool,
|
pub limits: bool,
|
||||||
}
|
}
|
||||||
@ -41,7 +40,7 @@ impl LayoutMath for OpNode {
|
|||||||
ctx.push(
|
ctx.push(
|
||||||
FrameFragment::new(ctx, frame)
|
FrameFragment::new(ctx, frame)
|
||||||
.with_class(MathClass::Large)
|
.with_class(MathClass::Large)
|
||||||
.with_limits(self.limits()),
|
.with_limits(self.limits(ctx.styles())),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -68,13 +68,12 @@ pub struct UnderbraceNode {
|
|||||||
|
|
||||||
/// The optional content below the brace.
|
/// The optional content below the brace.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[default]
|
|
||||||
pub annotation: Option<Content>,
|
pub annotation: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for UnderbraceNode {
|
impl LayoutMath for UnderbraceNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body(), &self.annotation(), '⏟', BRACE_GAP, false)
|
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⏟', BRACE_GAP, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,13 +95,12 @@ pub struct OverbraceNode {
|
|||||||
|
|
||||||
/// The optional content above the brace.
|
/// The optional content above the brace.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[default]
|
|
||||||
pub annotation: Option<Content>,
|
pub annotation: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for OverbraceNode {
|
impl LayoutMath for OverbraceNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body(), &self.annotation(), '⏞', BRACE_GAP, true)
|
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⏞', BRACE_GAP, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,13 +122,12 @@ pub struct UnderbracketNode {
|
|||||||
|
|
||||||
/// The optional content below the bracket.
|
/// The optional content below the bracket.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[default]
|
|
||||||
pub annotation: Option<Content>,
|
pub annotation: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for UnderbracketNode {
|
impl LayoutMath for UnderbracketNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body(), &self.annotation(), '⎵', BRACKET_GAP, false)
|
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⎵', BRACKET_GAP, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,13 +149,12 @@ pub struct OverbracketNode {
|
|||||||
|
|
||||||
/// The optional content above the bracket.
|
/// The optional content above the bracket.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[default]
|
|
||||||
pub annotation: Option<Content>,
|
pub annotation: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMath for OverbracketNode {
|
impl LayoutMath for OverbracketNode {
|
||||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||||
layout(ctx, &self.body(), &self.annotation(), '⎴', BRACKET_GAP, true)
|
layout(ctx, &self.body(), &self.annotation(ctx.styles()), '⎴', BRACKET_GAP, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,20 +16,17 @@ use crate::prelude::*;
|
|||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[node(LayoutRoot)]
|
#[node(LayoutRoot)]
|
||||||
pub struct DocumentNode {
|
pub struct DocumentNode {
|
||||||
/// The page runs.
|
|
||||||
#[variadic]
|
|
||||||
pub children: Vec<Content>,
|
|
||||||
|
|
||||||
/// The document's title. This is often rendered as the title of the
|
/// The document's title. This is often rendered as the title of the
|
||||||
/// PDF viewer window.
|
/// PDF viewer window.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub title: Option<EcoString>,
|
pub title: Option<EcoString>,
|
||||||
|
|
||||||
/// The document's authors.
|
/// The document's authors.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub author: Author,
|
pub author: Author,
|
||||||
|
|
||||||
|
/// The page runs.
|
||||||
|
#[internal]
|
||||||
|
#[variadic]
|
||||||
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutRoot for DocumentNode {
|
impl LayoutRoot for DocumentNode {
|
||||||
@ -58,8 +55,8 @@ impl LayoutRoot for DocumentNode {
|
|||||||
|
|
||||||
Ok(Document {
|
Ok(Document {
|
||||||
pages,
|
pages,
|
||||||
title: Self::title_in(styles),
|
title: self.title(styles),
|
||||||
author: Self::author_in(styles).0,
|
author: self.author(styles).0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,13 +42,7 @@ use crate::text::{TextNode, TextSize};
|
|||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[node(Prepare, Show, Finalize)]
|
#[node(Prepare, Show, Finalize)]
|
||||||
pub struct HeadingNode {
|
pub struct HeadingNode {
|
||||||
/// The heading's title.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The logical nesting depth of the heading, starting from one.
|
/// The logical nesting depth of the heading, starting from one.
|
||||||
#[named]
|
|
||||||
#[default(NonZeroUsize::new(1).unwrap())]
|
#[default(NonZeroUsize::new(1).unwrap())]
|
||||||
pub level: NonZeroUsize,
|
pub level: NonZeroUsize,
|
||||||
|
|
||||||
@ -62,8 +56,6 @@ pub struct HeadingNode {
|
|||||||
/// == A subsection
|
/// == A subsection
|
||||||
/// === A sub-subsection
|
/// === A sub-subsection
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub numbering: Option<Numbering>,
|
pub numbering: Option<Numbering>,
|
||||||
|
|
||||||
/// Whether the heading should appear in the outline.
|
/// Whether the heading should appear in the outline.
|
||||||
@ -78,9 +70,13 @@ pub struct HeadingNode {
|
|||||||
/// This heading does not appear
|
/// This heading does not appear
|
||||||
/// in the outline.
|
/// in the outline.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub outlined: bool,
|
pub outlined: bool,
|
||||||
|
|
||||||
|
/// The heading's title.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Prepare for HeadingNode {
|
impl Prepare for HeadingNode {
|
||||||
@ -106,11 +102,11 @@ impl Prepare for HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut numbers = Value::None;
|
let mut numbers = Value::None;
|
||||||
if let Some(numbering) = Self::numbering_in(styles) {
|
if let Some(numbering) = self.numbering(styles) {
|
||||||
numbers = numbering.apply(vt.world(), counter.advance(self))?;
|
numbers = numbering.apply(vt.world(), counter.advance(self))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.push_field("outlined", Value::Bool(Self::outlined_in(styles)));
|
this.push_field("outlined", Value::Bool(self.outlined(styles)));
|
||||||
this.push_field("numbers", numbers);
|
this.push_field("numbers", numbers);
|
||||||
|
|
||||||
let meta = Meta::Node(my_id, this.clone());
|
let meta = Meta::Node(my_id, this.clone());
|
||||||
@ -132,8 +128,8 @@ impl Show for HeadingNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Finalize for HeadingNode {
|
impl Finalize for HeadingNode {
|
||||||
fn finalize(&self, realized: Content) -> Content {
|
fn finalize(&self, realized: Content, styles: StyleChain) -> Content {
|
||||||
let level = self.level().get();
|
let level = self.level(styles).get();
|
||||||
let scale = match level {
|
let scale = match level {
|
||||||
1 => 1.4,
|
1 => 1.4,
|
||||||
2 => 1.2,
|
2 => 1.2,
|
||||||
@ -165,7 +161,7 @@ impl HeadingCounter {
|
|||||||
|
|
||||||
/// Advance the counter and return the numbers for the given heading.
|
/// Advance the counter and return the numbers for the given heading.
|
||||||
pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] {
|
pub fn advance(&mut self, heading: &HeadingNode) -> &[NonZeroUsize] {
|
||||||
let level = heading.level().get();
|
let level = heading.level(StyleChain::default()).get();
|
||||||
|
|
||||||
if self.0.len() >= level {
|
if self.0.len() >= level {
|
||||||
self.0[level - 1] = self.0[level - 1].saturating_add(1);
|
self.0[level - 1] = self.0[level - 1].saturating_add(1);
|
||||||
|
@ -25,7 +25,7 @@ use crate::text::{Hyphenate, TextNode};
|
|||||||
///
|
///
|
||||||
/// Display: Link
|
/// Display: Link
|
||||||
/// Category: meta
|
/// Category: meta
|
||||||
#[node(Construct, Show, Finalize)]
|
#[node(Show, Finalize)]
|
||||||
pub struct LinkNode {
|
pub struct LinkNode {
|
||||||
/// The destination the link points to.
|
/// The destination the link points to.
|
||||||
///
|
///
|
||||||
@ -48,6 +48,10 @@ pub struct LinkNode {
|
|||||||
///
|
///
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
|
#[parse(
|
||||||
|
let dest = args.expect::<Destination>("destination")?;
|
||||||
|
dest.clone()
|
||||||
|
)]
|
||||||
pub dest: Destination,
|
pub dest: Destination,
|
||||||
|
|
||||||
/// How the link is represented.
|
/// How the link is represented.
|
||||||
@ -56,7 +60,14 @@ pub struct LinkNode {
|
|||||||
/// parameter can be omitted. In this case, the URL will be shown as the
|
/// parameter can be omitted. In this case, the URL will be shown as the
|
||||||
/// link.
|
/// link.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[default]
|
#[required]
|
||||||
|
#[parse(match &dest {
|
||||||
|
Destination::Url(url) => match args.eat()? {
|
||||||
|
Some(body) => body,
|
||||||
|
None => body_from_url(url),
|
||||||
|
},
|
||||||
|
Destination::Internal(_) => args.expect("body")?,
|
||||||
|
})]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,21 +75,7 @@ impl LinkNode {
|
|||||||
/// Create a link node from a URL with its bare text.
|
/// Create a link node from a URL with its bare text.
|
||||||
pub fn from_url(url: EcoString) -> Self {
|
pub fn from_url(url: EcoString) -> Self {
|
||||||
let body = body_from_url(&url);
|
let body = body_from_url(&url);
|
||||||
Self::new(Destination::Url(url)).with_body(body)
|
Self::new(Destination::Url(url), body)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for LinkNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let dest = args.expect::<Destination>("destination")?;
|
|
||||||
let body = match &dest {
|
|
||||||
Destination::Url(url) => match args.eat()? {
|
|
||||||
Some(body) => body,
|
|
||||||
None => body_from_url(url),
|
|
||||||
},
|
|
||||||
Destination::Internal(_) => args.expect("body")?,
|
|
||||||
};
|
|
||||||
Ok(Self::new(dest).with_body(body).pack())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +86,7 @@ impl Show for LinkNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Finalize for LinkNode {
|
impl Finalize for LinkNode {
|
||||||
fn finalize(&self, realized: Content) -> Content {
|
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
|
||||||
realized
|
realized
|
||||||
.styled(MetaNode::set_data(vec![Meta::Link(self.dest())]))
|
.styled(MetaNode::set_data(vec![Meta::Link(self.dest())]))
|
||||||
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
|
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
|
||||||
|
@ -30,14 +30,11 @@ pub struct OutlineNode {
|
|||||||
/// language]($func/text.lang) will be used. This is the default.
|
/// language]($func/text.lang) will be used. This is the default.
|
||||||
/// - When set to `{none}`, the outline will not have a title.
|
/// - When set to `{none}`, the outline will not have a title.
|
||||||
/// - A custom title can be set by passing content.
|
/// - A custom title can be set by passing content.
|
||||||
#[settable]
|
|
||||||
#[default(Some(Smart::Auto))]
|
#[default(Some(Smart::Auto))]
|
||||||
pub title: Option<Smart<Content>>,
|
pub title: Option<Smart<Content>>,
|
||||||
|
|
||||||
/// The maximum depth up to which headings are included in the outline. When
|
/// The maximum depth up to which headings are included in the outline. When
|
||||||
/// this argument is `{none}`, all headings are included.
|
/// this argument is `{none}`, all headings are included.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub depth: Option<NonZeroUsize>,
|
pub depth: Option<NonZeroUsize>,
|
||||||
|
|
||||||
/// Whether to indent the subheadings to align the start of their numbering
|
/// Whether to indent the subheadings to align the start of their numbering
|
||||||
@ -57,7 +54,6 @@ pub struct OutlineNode {
|
|||||||
/// == Products
|
/// == Products
|
||||||
/// #lorem(10)
|
/// #lorem(10)
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub indent: bool,
|
pub indent: bool,
|
||||||
|
|
||||||
@ -69,7 +65,6 @@ pub struct OutlineNode {
|
|||||||
///
|
///
|
||||||
/// = A New Beginning
|
/// = A New Beginning
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))]
|
#[default(Some(RepeatNode::new(TextNode::packed(".")).pack()))]
|
||||||
pub fill: Option<Content>,
|
pub fill: Option<Content>,
|
||||||
}
|
}
|
||||||
@ -102,7 +97,7 @@ impl Show for OutlineNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
let mut seq = vec![ParbreakNode::new().pack()];
|
let mut seq = vec![ParbreakNode::new().pack()];
|
||||||
if let Some(title) = Self::title_in(styles) {
|
if let Some(title) = self.title(styles) {
|
||||||
let title = title.clone().unwrap_or_else(|| {
|
let title = title.clone().unwrap_or_else(|| {
|
||||||
TextNode::packed(match TextNode::lang_in(styles) {
|
TextNode::packed(match TextNode::lang_in(styles) {
|
||||||
Lang::GERMAN => "Inhaltsverzeichnis",
|
Lang::GERMAN => "Inhaltsverzeichnis",
|
||||||
@ -112,14 +107,15 @@ impl Show for OutlineNode {
|
|||||||
|
|
||||||
seq.push(
|
seq.push(
|
||||||
HeadingNode::new(title)
|
HeadingNode::new(title)
|
||||||
.pack()
|
.with_level(NonZeroUsize::new(1).unwrap())
|
||||||
.styled(HeadingNode::set_numbering(None))
|
.with_numbering(None)
|
||||||
.styled(HeadingNode::set_outlined(false)),
|
.with_outlined(false)
|
||||||
|
.pack(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let indent = Self::indent_in(styles);
|
let indent = self.indent(styles);
|
||||||
let depth = Self::depth_in(styles);
|
let depth = self.depth(styles);
|
||||||
|
|
||||||
let mut ancestors: Vec<&Content> = vec![];
|
let mut ancestors: Vec<&Content> = vec![];
|
||||||
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
for (_, node) in vt.locate(Selector::node::<HeadingNode>()) {
|
||||||
@ -129,13 +125,14 @@ impl Show for OutlineNode {
|
|||||||
|
|
||||||
let heading = node.to::<HeadingNode>().unwrap();
|
let heading = node.to::<HeadingNode>().unwrap();
|
||||||
if let Some(depth) = depth {
|
if let Some(depth) = depth {
|
||||||
if depth < heading.level() {
|
if depth < heading.level(StyleChain::default()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while ancestors.last().map_or(false, |last| {
|
while ancestors.last().map_or(false, |last| {
|
||||||
last.to::<HeadingNode>().unwrap().level() >= heading.level()
|
last.to::<HeadingNode>().unwrap().level(StyleChain::default())
|
||||||
|
>= heading.level(StyleChain::default())
|
||||||
}) {
|
}) {
|
||||||
ancestors.pop();
|
ancestors.pop();
|
||||||
}
|
}
|
||||||
@ -171,7 +168,7 @@ impl Show for OutlineNode {
|
|||||||
seq.push(start.linked(Destination::Internal(loc)));
|
seq.push(start.linked(Destination::Internal(loc)));
|
||||||
|
|
||||||
// Add filler symbols between the section name and page number.
|
// Add filler symbols between the section name and page number.
|
||||||
if let Some(filler) = Self::fill_in(styles) {
|
if let Some(filler) = self.fill(styles) {
|
||||||
seq.push(SpaceNode::new().pack());
|
seq.push(SpaceNode::new().pack());
|
||||||
seq.push(
|
seq.push(
|
||||||
BoxNode::new()
|
BoxNode::new()
|
||||||
|
@ -15,11 +15,6 @@ use crate::prelude::*;
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
pub struct UnderlineNode {
|
pub struct UnderlineNode {
|
||||||
/// The content to underline.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// 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}`.
|
||||||
///
|
///
|
||||||
@ -30,10 +25,8 @@ pub struct UnderlineNode {
|
|||||||
/// [care],
|
/// [care],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Smart<PartialStroke>,
|
pub stroke: Smart<PartialStroke>,
|
||||||
|
|
||||||
/// Position of the line relative to the baseline, read from the font tables
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
@ -44,9 +37,7 @@ pub struct UnderlineNode {
|
|||||||
/// The Tale Of A Faraway Line I
|
/// The Tale Of A Faraway Line I
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub offset: Smart<Length>,
|
pub offset: Smart<Length>,
|
||||||
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text.
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
@ -56,9 +47,7 @@ pub struct UnderlineNode {
|
|||||||
/// underline(extent: 2pt)[Chapter 1]
|
/// underline(extent: 2pt)[Chapter 1]
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub extent: Length,
|
pub extent: Length,
|
||||||
|
|
||||||
/// Whether the line skips sections in which it would collide with the
|
/// Whether the line skips sections in which it would collide with the
|
||||||
@ -68,19 +57,23 @@ pub struct UnderlineNode {
|
|||||||
/// This #underline(evade: true)[is great].
|
/// This #underline(evade: true)[is great].
|
||||||
/// This #underline(evade: false)[is less great].
|
/// This #underline(evade: false)[is less great].
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub evade: bool,
|
pub evade: bool,
|
||||||
|
|
||||||
|
/// The content to underline.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for UnderlineNode {
|
impl Show for UnderlineNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||||
line: DecoLine::Underline,
|
line: DecoLine::Underline,
|
||||||
stroke: Self::stroke_in(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
offset: Self::offset_in(styles),
|
offset: self.offset(styles),
|
||||||
extent: Self::extent_in(styles),
|
extent: self.extent(styles),
|
||||||
evade: Self::evade_in(styles),
|
evade: self.evade(styles),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,11 +89,6 @@ impl Show for UnderlineNode {
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
pub struct OverlineNode {
|
pub struct OverlineNode {
|
||||||
/// The content to add a line over.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// 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}`.
|
||||||
///
|
///
|
||||||
@ -112,10 +100,8 @@ pub struct OverlineNode {
|
|||||||
/// [The Forest Theme],
|
/// [The Forest Theme],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Smart<PartialStroke>,
|
pub stroke: Smart<PartialStroke>,
|
||||||
|
|
||||||
/// Position of the line relative to the baseline, read from the font tables
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
@ -126,9 +112,7 @@ pub struct OverlineNode {
|
|||||||
/// The Tale Of A Faraway Line II
|
/// The Tale Of A Faraway Line II
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub offset: Smart<Length>,
|
pub offset: Smart<Length>,
|
||||||
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text.
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
@ -138,9 +122,7 @@ pub struct OverlineNode {
|
|||||||
/// #set underline(extent: 4pt)
|
/// #set underline(extent: 4pt)
|
||||||
/// #overline(underline[Typography Today])
|
/// #overline(underline[Typography Today])
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub extent: Length,
|
pub extent: Length,
|
||||||
|
|
||||||
/// Whether the line skips sections in which it would collide with the
|
/// Whether the line skips sections in which it would collide with the
|
||||||
@ -155,19 +137,23 @@ pub struct OverlineNode {
|
|||||||
/// [Temple],
|
/// [Temple],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub evade: bool,
|
pub evade: bool,
|
||||||
|
|
||||||
|
/// The content to add a line over.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for OverlineNode {
|
impl Show for OverlineNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||||
line: DecoLine::Overline,
|
line: DecoLine::Overline,
|
||||||
stroke: Self::stroke_in(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
offset: Self::offset_in(styles),
|
offset: self.offset(styles),
|
||||||
extent: Self::extent_in(styles),
|
extent: self.extent(styles),
|
||||||
evade: Self::evade_in(styles),
|
evade: self.evade(styles),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -183,11 +169,6 @@ impl Show for OverlineNode {
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
pub struct StrikeNode {
|
pub struct StrikeNode {
|
||||||
/// The content to strike through.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// 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}`.
|
||||||
///
|
///
|
||||||
@ -198,10 +179,8 @@ pub struct StrikeNode {
|
|||||||
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
|
/// This is #strike(stroke: 1.5pt + red)[very stricken through]. \
|
||||||
/// This is #strike(stroke: 10pt)[redacted].
|
/// This is #strike(stroke: 10pt)[redacted].
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Smart<PartialStroke>,
|
pub stroke: Smart<PartialStroke>,
|
||||||
|
|
||||||
/// Position of the line relative to the baseline, read from the font tables
|
/// Position of the line relative to the baseline, read from the font tables
|
||||||
@ -214,9 +193,7 @@ pub struct StrikeNode {
|
|||||||
/// This is #strike(offset: auto)[low-ish]. \
|
/// This is #strike(offset: auto)[low-ish]. \
|
||||||
/// This is #strike(offset: -3.5pt)[on-top].
|
/// This is #strike(offset: -3.5pt)[on-top].
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub offset: Smart<Length>,
|
pub offset: Smart<Length>,
|
||||||
|
|
||||||
/// Amount that the line will be longer or shorter than its associated text.
|
/// Amount that the line will be longer or shorter than its associated text.
|
||||||
@ -225,19 +202,22 @@ pub struct StrikeNode {
|
|||||||
/// This #strike(extent: -2pt)[skips] parts of the word.
|
/// This #strike(extent: -2pt)[skips] parts of the word.
|
||||||
/// This #strike(extent: 2pt)[extends] beyond the word.
|
/// This #strike(extent: 2pt)[extends] beyond the word.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub extent: Length,
|
pub extent: Length,
|
||||||
|
|
||||||
|
/// The content to strike through.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrikeNode {
|
impl Show for StrikeNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
Ok(self.body().styled(TextNode::set_deco(Decoration {
|
||||||
line: DecoLine::Strikethrough,
|
line: DecoLine::Strikethrough,
|
||||||
stroke: Self::stroke_in(styles).unwrap_or_default(),
|
stroke: self.stroke(styles).unwrap_or_default(),
|
||||||
offset: Self::offset_in(styles),
|
offset: self.offset(styles),
|
||||||
extent: Self::extent_in(styles),
|
extent: self.extent(styles),
|
||||||
evade: false,
|
evade: false,
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,6 @@ pub struct LinebreakNode {
|
|||||||
/// line breaks in this paragraph #jb
|
/// line breaks in this paragraph #jb
|
||||||
/// for an _interesting_ result. #jb
|
/// for an _interesting_ result. #jb
|
||||||
/// ```
|
/// ```
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub justify: bool,
|
pub justify: bool,
|
||||||
}
|
}
|
||||||
@ -85,25 +84,24 @@ impl Behave for LinebreakNode {
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
pub struct StrongNode {
|
pub struct StrongNode {
|
||||||
/// The content to strongly emphasize.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The delta to apply on the font weight.
|
/// The delta to apply on the font weight.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #set strong(delta: 0)
|
/// #set strong(delta: 0)
|
||||||
/// No *effect!*
|
/// No *effect!*
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(300)]
|
#[default(300)]
|
||||||
pub delta: i64,
|
pub delta: i64,
|
||||||
|
|
||||||
|
/// The content to strongly emphasize.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for StrongNode {
|
impl Show for StrongNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
Ok(self.body().styled(TextNode::set_delta(Delta(Self::delta_in(styles)))))
|
Ok(self.body().styled(TextNode::set_delta(Delta(self.delta(styles)))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,12 +46,6 @@ use crate::prelude::*;
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Construct)]
|
#[node(Construct)]
|
||||||
pub struct TextNode {
|
pub struct TextNode {
|
||||||
/// The text.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
#[skip]
|
|
||||||
pub text: EcoString,
|
|
||||||
|
|
||||||
/// A prioritized sequence 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
|
||||||
@ -69,7 +63,6 @@ pub struct TextNode {
|
|||||||
/// هذا عربي.
|
/// هذا عربي.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(FontList(vec![FontFamily::new("Linux Libertine")]))]
|
#[default(FontList(vec![FontFamily::new("Linux Libertine")]))]
|
||||||
pub font: FontList,
|
pub font: FontList,
|
||||||
|
|
||||||
@ -90,7 +83,6 @@ pub struct TextNode {
|
|||||||
/// #set text(fallback: false)
|
/// #set text(fallback: false)
|
||||||
/// هذا عربي
|
/// هذا عربي
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub fallback: bool,
|
pub fallback: bool,
|
||||||
|
|
||||||
@ -111,8 +103,6 @@ pub struct TextNode {
|
|||||||
/// #text(font: "Linux Libertine", style: "italic")[Italic]
|
/// #text(font: "Linux Libertine", style: "italic")[Italic]
|
||||||
/// #text(font: "DejaVu Sans", style: "oblique")[Oblique]
|
/// #text(font: "DejaVu Sans", style: "oblique")[Oblique]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(FontStyle::Normal)]
|
|
||||||
pub style: FontStyle,
|
pub style: FontStyle,
|
||||||
|
|
||||||
/// The desired thickness of the font's glyphs. Accepts an integer between
|
/// The desired thickness of the font's glyphs. Accepts an integer between
|
||||||
@ -132,8 +122,6 @@ pub struct TextNode {
|
|||||||
/// #text(weight: 500)[Medium] \
|
/// #text(weight: 500)[Medium] \
|
||||||
/// #text(weight: "bold")[Bold]
|
/// #text(weight: "bold")[Bold]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(FontWeight::REGULAR)]
|
|
||||||
pub weight: FontWeight,
|
pub weight: FontWeight,
|
||||||
|
|
||||||
/// The desired width of the glyphs. Accepts a ratio between `{50%}` and
|
/// The desired width of the glyphs. Accepts a ratio between `{50%}` and
|
||||||
@ -144,8 +132,6 @@ pub struct TextNode {
|
|||||||
/// #text(stretch: 75%)[Condensed] \
|
/// #text(stretch: 75%)[Condensed] \
|
||||||
/// #text(stretch: 100%)[Normal]
|
/// #text(stretch: 100%)[Normal]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(FontStretch::NORMAL)]
|
|
||||||
pub stretch: FontStretch,
|
pub stretch: FontStretch,
|
||||||
|
|
||||||
/// The size of the glyphs. This value forms the basis of the `em` unit:
|
/// The size of the glyphs. This value forms the basis of the `em` unit:
|
||||||
@ -158,8 +144,7 @@ pub struct TextNode {
|
|||||||
/// #set text(size: 20pt)
|
/// #set text(size: 20pt)
|
||||||
/// very #text(1.5em)[big] text
|
/// very #text(1.5em)[big] text
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
#[parse(args.named_or_find("size")?)]
|
||||||
#[shorthand]
|
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Abs::pt(11.0))]
|
#[default(Abs::pt(11.0))]
|
||||||
pub size: TextSize,
|
pub size: TextSize,
|
||||||
@ -170,8 +155,7 @@ pub struct TextNode {
|
|||||||
/// #set text(fill: red)
|
/// #set text(fill: red)
|
||||||
/// This text is red.
|
/// This text is red.
|
||||||
/// ```
|
/// ```
|
||||||
#[shorthand]
|
#[parse(args.named_or_find("fill")?)]
|
||||||
#[settable]
|
|
||||||
#[default(Color::BLACK.into())]
|
#[default(Color::BLACK.into())]
|
||||||
pub fill: Paint,
|
pub fill: Paint,
|
||||||
|
|
||||||
@ -181,9 +165,7 @@ pub struct TextNode {
|
|||||||
/// #set text(tracking: 1.5pt)
|
/// #set text(tracking: 1.5pt)
|
||||||
/// Distant text.
|
/// Distant text.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Length::zero())]
|
|
||||||
pub tracking: Length,
|
pub tracking: Length,
|
||||||
|
|
||||||
/// The amount of space between words.
|
/// The amount of space between words.
|
||||||
@ -195,7 +177,6 @@ pub struct TextNode {
|
|||||||
/// #set text(spacing: 200%)
|
/// #set text(spacing: 200%)
|
||||||
/// Text with distant words.
|
/// Text with distant words.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Rel::one())]
|
#[default(Rel::one())]
|
||||||
pub spacing: Rel<Length>,
|
pub spacing: Rel<Length>,
|
||||||
@ -206,9 +187,7 @@ pub struct TextNode {
|
|||||||
/// A #text(baseline: 3pt)[lowered]
|
/// A #text(baseline: 3pt)[lowered]
|
||||||
/// word.
|
/// word.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Length::zero())]
|
|
||||||
pub baseline: Length,
|
pub baseline: Length,
|
||||||
|
|
||||||
/// Whether certain glyphs can hang over into the margin in justified text.
|
/// Whether certain glyphs can hang over into the margin in justified text.
|
||||||
@ -231,7 +210,6 @@ pub struct TextNode {
|
|||||||
/// margin, making the paragraph's
|
/// margin, making the paragraph's
|
||||||
/// edge less clear.
|
/// edge less clear.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub overhang: bool,
|
pub overhang: bool,
|
||||||
|
|
||||||
@ -248,7 +226,6 @@ pub struct TextNode {
|
|||||||
/// #set text(top-edge: "cap-height")
|
/// #set text(top-edge: "cap-height")
|
||||||
/// #rect(fill: aqua)[Typst]
|
/// #rect(fill: aqua)[Typst]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(TextEdge::Metric(VerticalFontMetric::CapHeight))]
|
#[default(TextEdge::Metric(VerticalFontMetric::CapHeight))]
|
||||||
pub top_edge: TextEdge,
|
pub top_edge: TextEdge,
|
||||||
|
|
||||||
@ -265,7 +242,6 @@ pub struct TextNode {
|
|||||||
/// #set text(bottom-edge: "descender")
|
/// #set text(bottom-edge: "descender")
|
||||||
/// #rect(fill: aqua)[Typst]
|
/// #rect(fill: aqua)[Typst]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(TextEdge::Metric(VerticalFontMetric::Baseline))]
|
#[default(TextEdge::Metric(VerticalFontMetric::Baseline))]
|
||||||
pub bottom_edge: TextEdge,
|
pub bottom_edge: TextEdge,
|
||||||
|
|
||||||
@ -286,15 +262,12 @@ pub struct TextNode {
|
|||||||
/// = Einleitung
|
/// = Einleitung
|
||||||
/// In diesem Dokument, ...
|
/// In diesem Dokument, ...
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Lang::ENGLISH)]
|
#[default(Lang::ENGLISH)]
|
||||||
pub lang: Lang,
|
pub lang: Lang,
|
||||||
|
|
||||||
/// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
/// An [ISO 3166-1 alpha-2 region code.](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
|
||||||
///
|
///
|
||||||
/// This lets the text processing pipeline make more informed choices.
|
/// This lets the text processing pipeline make more informed choices.
|
||||||
#[settable]
|
|
||||||
#[default(None)]
|
|
||||||
pub region: Option<Region>,
|
pub region: Option<Region>,
|
||||||
|
|
||||||
/// The dominant direction for text and inline objects. Possible values are:
|
/// The dominant direction for text and inline objects. Possible values are:
|
||||||
@ -321,9 +294,7 @@ pub struct TextNode {
|
|||||||
/// #set text(dir: rtl)
|
/// #set text(dir: rtl)
|
||||||
/// هذا عربي.
|
/// هذا عربي.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(HorizontalDir(Smart::Auto))]
|
|
||||||
pub dir: HorizontalDir,
|
pub dir: HorizontalDir,
|
||||||
|
|
||||||
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
|
/// Whether to hyphenate text to improve line breaking. When `{auto}`, text
|
||||||
@ -343,9 +314,7 @@ pub struct TextNode {
|
|||||||
/// enabling hyphenation can
|
/// enabling hyphenation can
|
||||||
/// improve justification.
|
/// improve justification.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[default(Hyphenate(Smart::Auto))]
|
|
||||||
pub hyphenate: Hyphenate,
|
pub hyphenate: Hyphenate,
|
||||||
|
|
||||||
/// Whether to apply kerning.
|
/// Whether to apply kerning.
|
||||||
@ -363,7 +332,6 @@ pub struct TextNode {
|
|||||||
/// #set text(kerning: false)
|
/// #set text(kerning: false)
|
||||||
/// Totally
|
/// Totally
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub kerning: bool,
|
pub kerning: bool,
|
||||||
|
|
||||||
@ -380,7 +348,6 @@ pub struct TextNode {
|
|||||||
/// #set text(alternates: true)
|
/// #set text(alternates: true)
|
||||||
/// 0, a, g, ß
|
/// 0, a, g, ß
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub alternates: bool,
|
pub alternates: bool,
|
||||||
|
|
||||||
@ -389,8 +356,6 @@ pub struct TextNode {
|
|||||||
/// you need to consult your font to know which sets are available. When set
|
/// you need to consult your font to know which sets are available. When set
|
||||||
/// to an integer between `{1}` and `{20}`, enables the corresponding
|
/// to an integer between `{1}` and `{20}`, enables the corresponding
|
||||||
/// OpenType font feature from `ss01`, ..., `ss20`.
|
/// OpenType font feature from `ss01`, ..., `ss20`.
|
||||||
#[settable]
|
|
||||||
#[default(None)]
|
|
||||||
pub stylistic_set: Option<StylisticSet>,
|
pub stylistic_set: Option<StylisticSet>,
|
||||||
|
|
||||||
/// Whether standard ligatures are active.
|
/// Whether standard ligatures are active.
|
||||||
@ -407,19 +372,16 @@ pub struct TextNode {
|
|||||||
/// #set text(ligatures: false)
|
/// #set text(ligatures: false)
|
||||||
/// A fine ligature.
|
/// A fine ligature.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub ligatures: bool,
|
pub ligatures: bool,
|
||||||
|
|
||||||
/// Whether ligatures that should be used sparingly are active. Setting this
|
/// Whether ligatures that should be used sparingly are active. Setting this
|
||||||
/// to `{true}` enables the OpenType `dlig` font feature.
|
/// to `{true}` enables the OpenType `dlig` font feature.
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub discretionary_ligatures: bool,
|
pub discretionary_ligatures: bool,
|
||||||
|
|
||||||
/// Whether historical ligatures are active. Setting this to `{true}`
|
/// Whether historical ligatures are active. Setting this to `{true}`
|
||||||
/// enables the OpenType `hlig` font feature.
|
/// enables the OpenType `hlig` font feature.
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub historical_ligatures: bool,
|
pub historical_ligatures: bool,
|
||||||
|
|
||||||
@ -434,8 +396,6 @@ pub struct TextNode {
|
|||||||
/// #set text(number-type: "old-style")
|
/// #set text(number-type: "old-style")
|
||||||
/// Number 9.
|
/// Number 9.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Smart::Auto)]
|
|
||||||
pub number_type: Smart<NumberType>,
|
pub number_type: Smart<NumberType>,
|
||||||
|
|
||||||
/// The width of numbers / figures. When set to `{auto}`, the default
|
/// The width of numbers / figures. When set to `{auto}`, the default
|
||||||
@ -451,8 +411,6 @@ pub struct TextNode {
|
|||||||
/// A 12 B 34. \
|
/// A 12 B 34. \
|
||||||
/// A 56 B 78.
|
/// A 56 B 78.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(Smart::Auto)]
|
|
||||||
pub number_width: Smart<NumberWidth>,
|
pub number_width: Smart<NumberWidth>,
|
||||||
|
|
||||||
/// Whether to have a slash through the zero glyph. Setting this to `{true}`
|
/// Whether to have a slash through the zero glyph. Setting this to `{true}`
|
||||||
@ -461,7 +419,6 @@ pub struct TextNode {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// 0, #text(slashed-zero: true)[0]
|
/// 0, #text(slashed-zero: true)[0]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub slashed_zero: bool,
|
pub slashed_zero: bool,
|
||||||
|
|
||||||
@ -472,7 +429,6 @@ pub struct TextNode {
|
|||||||
/// 1/2 \
|
/// 1/2 \
|
||||||
/// #text(fractions: true)[1/2]
|
/// #text(fractions: true)[1/2]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub fractions: bool,
|
pub fractions: bool,
|
||||||
|
|
||||||
@ -488,43 +444,39 @@ pub struct TextNode {
|
|||||||
/// #set text(features: ("frac",))
|
/// #set text(features: ("frac",))
|
||||||
/// 1/2
|
/// 1/2
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(FontFeatures(vec![]))]
|
|
||||||
pub features: FontFeatures,
|
pub features: FontFeatures,
|
||||||
|
|
||||||
|
/// The text.
|
||||||
|
#[internal]
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub text: EcoString,
|
||||||
|
|
||||||
/// A delta to apply on the font weight.
|
/// A delta to apply on the font weight.
|
||||||
#[settable]
|
#[internal]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[skip]
|
|
||||||
#[default(0)]
|
|
||||||
pub delta: Delta,
|
pub delta: Delta,
|
||||||
|
|
||||||
/// Whether the font style should be inverted.
|
/// Whether the font style should be inverted.
|
||||||
#[settable]
|
#[internal]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[skip]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub emph: Toggle,
|
pub emph: Toggle,
|
||||||
|
|
||||||
|
/// Decorative lines.
|
||||||
|
#[internal]
|
||||||
|
#[fold]
|
||||||
|
pub deco: Decoration,
|
||||||
|
|
||||||
/// A case transformation that should be applied to the text.
|
/// A case transformation that should be applied to the text.
|
||||||
#[settable]
|
#[internal]
|
||||||
#[skip]
|
|
||||||
#[default(None)]
|
|
||||||
pub case: Option<Case>,
|
pub case: Option<Case>,
|
||||||
|
|
||||||
/// Whether small capital glyphs should be used. ("smcp")
|
/// Whether small capital glyphs should be used. ("smcp")
|
||||||
#[settable]
|
#[internal]
|
||||||
#[skip]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub smallcaps: bool,
|
pub smallcaps: bool,
|
||||||
|
|
||||||
/// Decorative lines.
|
|
||||||
#[settable]
|
|
||||||
#[fold]
|
|
||||||
#[skip]
|
|
||||||
#[default(vec![])]
|
|
||||||
pub deco: Decoration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextNode {
|
impl TextNode {
|
||||||
@ -539,7 +491,9 @@ impl Construct for TextNode {
|
|||||||
// The text constructor is special: It doesn't create a text node.
|
// The text constructor is special: It doesn't create a text node.
|
||||||
// Instead, it leaves the passed argument structurally unchanged, but
|
// Instead, it leaves the passed argument structurally unchanged, but
|
||||||
// styles all text in it.
|
// styles all text in it.
|
||||||
args.expect("body")
|
let styles = Self::set(args)?;
|
||||||
|
let body = args.expect::<Content>("body")?;
|
||||||
|
Ok(body.styled_with_map(styles))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +605,7 @@ cast_to_value! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The direction of text and inline objects in their line.
|
/// The direction of text and inline objects in their line.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct HorizontalDir(pub Smart<Dir>);
|
pub struct HorizontalDir(pub Smart<Dir>);
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
@ -680,7 +634,7 @@ impl Resolve for HorizontalDir {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to hyphenate text.
|
/// Whether to hyphenate text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Hyphenate(pub Smart<bool>);
|
pub struct Hyphenate(pub Smart<bool>);
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
|
@ -27,7 +27,6 @@ use crate::prelude::*;
|
|||||||
#[node]
|
#[node]
|
||||||
pub struct SmartQuoteNode {
|
pub struct SmartQuoteNode {
|
||||||
/// Whether this should be a double quote.
|
/// Whether this should be a double quote.
|
||||||
#[named]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub double: bool,
|
pub double: bool,
|
||||||
|
|
||||||
@ -41,7 +40,6 @@ pub struct SmartQuoteNode {
|
|||||||
///
|
///
|
||||||
/// These are "dumb" quotes.
|
/// These are "dumb" quotes.
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,6 @@ pub struct RawNode {
|
|||||||
/// rg "Hello World"
|
/// rg "Hello World"
|
||||||
/// ```
|
/// ```
|
||||||
/// ````
|
/// ````
|
||||||
#[named]
|
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
pub block: bool,
|
pub block: bool,
|
||||||
|
|
||||||
@ -102,8 +101,6 @@ pub struct RawNode {
|
|||||||
/// This is *Typst!*
|
/// This is *Typst!*
|
||||||
/// ```
|
/// ```
|
||||||
/// ````
|
/// ````
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub lang: Option<EcoString>,
|
pub lang: Option<EcoString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +111,7 @@ impl Prepare for RawNode {
|
|||||||
mut this: Content,
|
mut this: Content,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
this.push_field("lang", Self::lang_in(styles).clone());
|
this.push_field("lang", self.lang(styles).clone());
|
||||||
Ok(this)
|
Ok(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +119,7 @@ impl Prepare for RawNode {
|
|||||||
impl Show for RawNode {
|
impl Show for RawNode {
|
||||||
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
fn show(&self, _: &mut Vt, _: &Content, styles: StyleChain) -> SourceResult<Content> {
|
||||||
let text = self.text();
|
let text = self.text();
|
||||||
let lang = Self::lang_in(styles).as_ref().map(|s| s.to_lowercase());
|
let lang = self.lang(styles).as_ref().map(|s| s.to_lowercase());
|
||||||
let foreground = THEME
|
let foreground = THEME
|
||||||
.settings
|
.settings
|
||||||
.foreground
|
.foreground
|
||||||
@ -170,7 +167,7 @@ impl Show for RawNode {
|
|||||||
TextNode::packed(text)
|
TextNode::packed(text)
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.block() {
|
if self.block(styles) {
|
||||||
realized = BlockNode::new().with_body(Some(realized)).pack();
|
realized = BlockNode::new().with_body(Some(realized)).pack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +176,7 @@ impl Show for RawNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Finalize for RawNode {
|
impl Finalize for RawNode {
|
||||||
fn finalize(&self, realized: Content) -> Content {
|
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
|
||||||
let mut map = StyleMap::new();
|
let mut map = StyleMap::new();
|
||||||
map.set(TextNode::set_overhang(false));
|
map.set(TextNode::set_overhang(false));
|
||||||
map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))));
|
map.set(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))));
|
||||||
|
@ -16,11 +16,6 @@ use crate::prelude::*;
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
pub struct SubNode {
|
pub struct SubNode {
|
||||||
/// The text to display in subscript.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// Whether to prefer the dedicated subscript characters of the font.
|
/// Whether to prefer the dedicated subscript characters of the font.
|
||||||
///
|
///
|
||||||
/// If this is enabled, Typst first tries to transform the text to subscript
|
/// If this is enabled, Typst first tries to transform the text to subscript
|
||||||
@ -31,23 +26,25 @@ pub struct SubNode {
|
|||||||
/// N#sub(typographic: true)[1]
|
/// N#sub(typographic: true)[1]
|
||||||
/// N#sub(typographic: false)[1]
|
/// N#sub(typographic: false)[1]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub typographic: bool,
|
pub typographic: bool,
|
||||||
|
|
||||||
/// The baseline shift for synthetic subscripts. Does not apply if
|
/// The baseline shift for synthetic subscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has subscript codepoints for the
|
/// `typographic` is true and the font has subscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
#[settable]
|
|
||||||
#[default(Em::new(0.2).into())]
|
#[default(Em::new(0.2).into())]
|
||||||
pub baseline: Length,
|
pub baseline: Length,
|
||||||
|
|
||||||
/// The font size for synthetic subscripts. Does not apply if
|
/// The font size for synthetic subscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has subscript codepoints for the
|
/// `typographic` is true and the font has subscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
#[settable]
|
|
||||||
#[default(TextSize(Em::new(0.6).into()))]
|
#[default(TextSize(Em::new(0.6).into()))]
|
||||||
pub size: TextSize,
|
pub size: TextSize,
|
||||||
|
|
||||||
|
/// The text to display in subscript.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for SubNode {
|
impl Show for SubNode {
|
||||||
@ -59,7 +56,7 @@ impl Show for SubNode {
|
|||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if Self::typographic_in(styles) {
|
if self.typographic(styles) {
|
||||||
if let Some(text) = search_text(&body, true) {
|
if let Some(text) = search_text(&body, true) {
|
||||||
if is_shapable(vt, &text, styles) {
|
if is_shapable(vt, &text, styles) {
|
||||||
transformed = Some(TextNode::packed(text));
|
transformed = Some(TextNode::packed(text));
|
||||||
@ -68,8 +65,8 @@ impl Show for SubNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(transformed.unwrap_or_else(|| {
|
Ok(transformed.unwrap_or_else(|| {
|
||||||
body.styled(TextNode::set_baseline(Self::baseline_in(styles)))
|
body.styled(TextNode::set_baseline(self.baseline(styles)))
|
||||||
.styled(TextNode::set_size(Self::size_in(styles)))
|
.styled(TextNode::set_size(self.size(styles)))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,11 +84,6 @@ impl Show for SubNode {
|
|||||||
/// Category: text
|
/// Category: text
|
||||||
#[node(Show)]
|
#[node(Show)]
|
||||||
pub struct SuperNode {
|
pub struct SuperNode {
|
||||||
/// The text to display in superscript.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// Whether to prefer the dedicated superscript characters of the font.
|
/// Whether to prefer the dedicated superscript characters of the font.
|
||||||
///
|
///
|
||||||
/// If this is enabled, Typst first tries to transform the text to
|
/// If this is enabled, Typst first tries to transform the text to
|
||||||
@ -102,23 +94,25 @@ pub struct SuperNode {
|
|||||||
/// N#super(typographic: true)[1]
|
/// N#super(typographic: true)[1]
|
||||||
/// N#super(typographic: false)[1]
|
/// N#super(typographic: false)[1]
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub typographic: bool,
|
pub typographic: bool,
|
||||||
|
|
||||||
/// The baseline shift for synthetic superscripts. Does not apply if
|
/// The baseline shift for synthetic superscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has superscript codepoints for the
|
/// `typographic` is true and the font has superscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
#[settable]
|
|
||||||
#[default(Em::new(-0.5).into())]
|
#[default(Em::new(-0.5).into())]
|
||||||
pub baseline: Length,
|
pub baseline: Length,
|
||||||
|
|
||||||
/// The font size for synthetic superscripts. Does not apply if
|
/// The font size for synthetic superscripts. Does not apply if
|
||||||
/// `typographic` is true and the font has superscript codepoints for the
|
/// `typographic` is true and the font has superscript codepoints for the
|
||||||
/// given `body`.
|
/// given `body`.
|
||||||
#[settable]
|
|
||||||
#[default(TextSize(Em::new(0.6).into()))]
|
#[default(TextSize(Em::new(0.6).into()))]
|
||||||
pub size: TextSize,
|
pub size: TextSize,
|
||||||
|
|
||||||
|
/// The text to display in superscript.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for SuperNode {
|
impl Show for SuperNode {
|
||||||
@ -130,7 +124,7 @@ impl Show for SuperNode {
|
|||||||
) -> SourceResult<Content> {
|
) -> SourceResult<Content> {
|
||||||
let body = self.body();
|
let body = self.body();
|
||||||
let mut transformed = None;
|
let mut transformed = None;
|
||||||
if Self::typographic_in(styles) {
|
if self.typographic(styles) {
|
||||||
if let Some(text) = search_text(&body, false) {
|
if let Some(text) = search_text(&body, false) {
|
||||||
if is_shapable(vt, &text, styles) {
|
if is_shapable(vt, &text, styles) {
|
||||||
transformed = Some(TextNode::packed(text));
|
transformed = Some(TextNode::packed(text));
|
||||||
@ -139,8 +133,8 @@ impl Show for SuperNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok(transformed.unwrap_or_else(|| {
|
Ok(transformed.unwrap_or_else(|| {
|
||||||
body.styled(TextNode::set_baseline(Self::baseline_in(styles)))
|
body.styled(TextNode::set_baseline(self.baseline(styles)))
|
||||||
.styled(TextNode::set_size(Self::size_in(styles)))
|
.styled(TextNode::set_size(self.size(styles)))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,39 +20,29 @@ use crate::prelude::*;
|
|||||||
///
|
///
|
||||||
/// Display: Image
|
/// Display: Image
|
||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct ImageNode {
|
pub struct ImageNode {
|
||||||
/// Path to an image file.
|
/// Path to an image file.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
pub path: EcoString,
|
#[parse(
|
||||||
|
|
||||||
/// The width of the image.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub width: Smart<Rel<Length>>,
|
|
||||||
|
|
||||||
/// The height of the image.
|
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub height: Smart<Rel<Length>>,
|
|
||||||
|
|
||||||
/// How the image should adjust itself to a given area.
|
|
||||||
#[settable]
|
|
||||||
#[default(ImageFit::Cover)]
|
|
||||||
pub fit: ImageFit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for ImageNode {
|
|
||||||
fn construct(vm: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let Spanned { v: path, span } =
|
let Spanned { v: path, span } =
|
||||||
args.expect::<Spanned<EcoString>>("path to image file")?;
|
args.expect::<Spanned<EcoString>>("path to image file")?;
|
||||||
let path: EcoString = vm.locate(&path).at(span)?.to_string_lossy().into();
|
let path: EcoString = vm.locate(&path).at(span)?.to_string_lossy().into();
|
||||||
let _ = load(vm.world(), &path).at(span)?;
|
let _ = load(vm.world(), &path).at(span)?;
|
||||||
let width = args.named::<Smart<Rel<Length>>>("width")?.unwrap_or_default();
|
path
|
||||||
let height = args.named::<Smart<Rel<Length>>>("height")?.unwrap_or_default();
|
)]
|
||||||
Ok(ImageNode::new(path).with_width(width).with_height(height).pack())
|
pub path: EcoString,
|
||||||
}
|
|
||||||
|
/// The width of the image.
|
||||||
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// The height of the image.
|
||||||
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
|
/// How the image should adjust itself to a given area.
|
||||||
|
#[default(ImageFit::Cover)]
|
||||||
|
pub fit: ImageFit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl Layout for ImageNode {
|
||||||
@ -63,7 +53,7 @@ impl Layout for ImageNode {
|
|||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let image = load(vt.world(), &self.path()).unwrap();
|
let image = load(vt.world(), &self.path()).unwrap();
|
||||||
let sizing = Axes::new(self.width(), self.height());
|
let sizing = Axes::new(self.width(styles), self.height(styles));
|
||||||
let region = sizing
|
let region = sizing
|
||||||
.zip(regions.base())
|
.zip(regions.base())
|
||||||
.map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r)))
|
.map(|(s, r)| s.map(|v| v.resolve(styles).relative_to(r)))
|
||||||
@ -90,7 +80,7 @@ impl Layout for ImageNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Compute the actual size of the fitted image.
|
// Compute the actual size of the fitted image.
|
||||||
let fit = Self::fit_in(styles);
|
let fit = self.fit(styles);
|
||||||
let fitted = match fit {
|
let fitted = match fit {
|
||||||
ImageFit::Cover | ImageFit::Contain => {
|
ImageFit::Cover | ImageFit::Contain => {
|
||||||
if wide == (fit == ImageFit::Contain) {
|
if wide == (fit == ImageFit::Contain) {
|
||||||
|
@ -9,34 +9,28 @@ use crate::prelude::*;
|
|||||||
/// #line(end: (50%, 50%))
|
/// #line(end: (50%, 50%))
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
|
||||||
/// - end: `Axes<Rel<Length>>` (named)
|
|
||||||
/// The end point of the line.
|
|
||||||
/// Must be an array of exactly two relative lengths.
|
|
||||||
///
|
|
||||||
/// - length: `Rel<Length>` (named)
|
|
||||||
/// The line's length. Mutually exclusive with `end`.
|
|
||||||
///
|
|
||||||
/// - angle: `Angle` (named)
|
|
||||||
/// The angle at which the line points away from the origin. Mutually
|
|
||||||
/// exclusive with `end`.
|
|
||||||
///
|
|
||||||
/// Display: Line
|
/// Display: Line
|
||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct LineNode {
|
pub struct LineNode {
|
||||||
/// The start point of the line.
|
/// The start point of the line.
|
||||||
///
|
///
|
||||||
/// Must be an array of exactly two relative lengths.
|
/// Must be an array of exactly two relative lengths.
|
||||||
#[named]
|
#[resolve]
|
||||||
#[default]
|
|
||||||
pub start: Axes<Rel<Length>>,
|
pub start: Axes<Rel<Length>>,
|
||||||
|
|
||||||
/// The offset from `start` where the line ends.
|
/// The offset from `start` where the line ends.
|
||||||
#[named]
|
#[resolve]
|
||||||
#[default]
|
pub end: Smart<Axes<Rel<Length>>>,
|
||||||
#[skip]
|
|
||||||
pub delta: Axes<Rel<Length>>,
|
/// The line's length. Mutually exclusive with `end`.
|
||||||
|
#[resolve]
|
||||||
|
#[default(Abs::pt(30.0).into())]
|
||||||
|
pub length: Rel<Length>,
|
||||||
|
|
||||||
|
/// The angle at which the line points away from the origin. Mutually
|
||||||
|
/// exclusive with `end`.
|
||||||
|
pub angle: Angle,
|
||||||
|
|
||||||
/// How to stroke the line. This can be:
|
/// How to stroke the line. This can be:
|
||||||
///
|
///
|
||||||
@ -50,33 +44,11 @@ pub struct LineNode {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// #line(length: 100%, stroke: 2pt + red)
|
/// #line(length: 100%, stroke: 2pt + red)
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: PartialStroke,
|
pub stroke: PartialStroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for LineNode {
|
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
|
||||||
let start = args.named("start")?.unwrap_or_default();
|
|
||||||
let delta = match args.named::<Axes<Rel<Length>>>("end")? {
|
|
||||||
Some(end) => end.zip(start).map(|(to, from)| to - from),
|
|
||||||
None => {
|
|
||||||
let length =
|
|
||||||
args.named::<Rel<Length>>("length")?.unwrap_or(Abs::pt(30.0).into());
|
|
||||||
|
|
||||||
let angle = args.named::<Angle>("angle")?.unwrap_or_default();
|
|
||||||
let x = angle.cos() * length;
|
|
||||||
let y = angle.sin() * length;
|
|
||||||
|
|
||||||
Axes::new(x, y)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(Self::new().with_start(start).with_delta(delta).pack())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for LineNode {
|
impl Layout for LineNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
@ -84,27 +56,27 @@ impl Layout for LineNode {
|
|||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> SourceResult<Fragment> {
|
) -> SourceResult<Fragment> {
|
||||||
let stroke = Self::stroke_in(styles).unwrap_or_default();
|
let resolve = |axes: Axes<Rel<Abs>>| {
|
||||||
|
axes.zip(regions.base()).map(|(l, b)| l.relative_to(b))
|
||||||
|
};
|
||||||
|
|
||||||
let origin = self
|
let start = resolve(self.start(styles));
|
||||||
.start()
|
let delta =
|
||||||
.resolve(styles)
|
self.end(styles).map(|end| resolve(end) - start).unwrap_or_else(|| {
|
||||||
.zip(regions.base())
|
let length = self.length(styles);
|
||||||
.map(|(l, b)| l.relative_to(b));
|
let angle = self.angle(styles);
|
||||||
|
let x = angle.cos() * length;
|
||||||
|
let y = angle.sin() * length;
|
||||||
|
resolve(Axes::new(x, y))
|
||||||
|
});
|
||||||
|
|
||||||
let delta = self
|
let stroke = self.stroke(styles).unwrap_or_default();
|
||||||
.delta()
|
let size = start.max(start + delta).max(Size::zero());
|
||||||
.resolve(styles)
|
|
||||||
.zip(regions.base())
|
|
||||||
.map(|(l, b)| l.relative_to(b));
|
|
||||||
|
|
||||||
let size = origin.max(origin + delta).max(Size::zero());
|
|
||||||
let target = regions.expand.select(regions.size, size);
|
let target = regions.expand.select(regions.size, size);
|
||||||
|
|
||||||
let mut frame = Frame::new(target);
|
let mut frame = Frame::new(target);
|
||||||
let shape = Geometry::Line(delta.to_point()).stroked(stroke);
|
let shape = Geometry::Line(delta.to_point()).stroked(stroke);
|
||||||
frame.push(origin.to_point(), Element::Shape(shape));
|
frame.push(start.to_point(), Element::Shape(shape));
|
||||||
|
|
||||||
Ok(Fragment::frame(frame))
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,22 +20,10 @@ use crate::prelude::*;
|
|||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct RectNode {
|
pub struct RectNode {
|
||||||
/// The content to place into the rectangle.
|
|
||||||
///
|
|
||||||
/// When this is omitted, the rectangle takes on a default size of at most
|
|
||||||
/// `{45pt}` by `{30pt}`.
|
|
||||||
#[positional]
|
|
||||||
#[default]
|
|
||||||
pub body: Option<Content>,
|
|
||||||
|
|
||||||
/// The rectangle's width, relative to its parent container.
|
/// The rectangle's width, relative to its parent container.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// The rectangle's height, relative to its parent container.
|
/// The rectangle's height, relative to its parent container.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the rectangle.
|
/// How to fill the rectangle.
|
||||||
@ -46,8 +34,6 @@ pub struct RectNode {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// #rect(fill: blue)
|
/// #rect(fill: blue)
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the rectangle. This can be:
|
/// How to stroke the rectangle. This can be:
|
||||||
@ -82,10 +68,8 @@ pub struct RectNode {
|
|||||||
/// rect(stroke: 2pt + red),
|
/// rect(stroke: 2pt + red),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
|
pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
|
||||||
|
|
||||||
/// How much to round the rectangle's corners, relative to the minimum of
|
/// How much to round the rectangle's corners, relative to the minimum of
|
||||||
@ -122,10 +106,8 @@ pub struct RectNode {
|
|||||||
/// ),
|
/// ),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub radius: Corners<Option<Rel<Length>>>,
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the rectangle's content.
|
/// How much to pad the rectangle's content.
|
||||||
@ -138,7 +120,6 @@ pub struct RectNode {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// #rect(inset: 0pt)[Tight])
|
/// #rect(inset: 0pt)[Tight])
|
||||||
/// ```
|
/// ```
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
@ -146,11 +127,16 @@ pub struct RectNode {
|
|||||||
|
|
||||||
/// How much to expand the rectangle's size without affecting the layout.
|
/// How much to expand the rectangle's size without affecting the layout.
|
||||||
/// See the [box's documentation]($func/box.outset) for more details.
|
/// See the [box's documentation]($func/box.outset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub outset: Sides<Option<Rel<Length>>>,
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
|
/// The content to place into the rectangle.
|
||||||
|
///
|
||||||
|
/// When this is omitted, the rectangle takes on a default size of at most
|
||||||
|
/// `{45pt}` by `{30pt}`.
|
||||||
|
#[positional]
|
||||||
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for RectNode {
|
impl Layout for RectNode {
|
||||||
@ -165,13 +151,13 @@ impl Layout for RectNode {
|
|||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Rect,
|
ShapeKind::Rect,
|
||||||
&self.body(),
|
&self.body(styles),
|
||||||
Axes::new(self.width(), self.height()),
|
Axes::new(self.width(styles), self.height(styles)),
|
||||||
Self::fill_in(styles),
|
self.fill(styles),
|
||||||
Self::stroke_in(styles),
|
self.stroke(styles),
|
||||||
Self::inset_in(styles),
|
self.inset(styles),
|
||||||
Self::outset_in(styles),
|
self.outset(styles),
|
||||||
Self::radius_in(styles),
|
self.radius(styles),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,66 +177,57 @@ impl Layout for RectNode {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - size: `Smart<Length>` (named)
|
/// - size: `Smart<Length>` (named, settable)
|
||||||
/// The square's side length. This is mutually exclusive with `width` and
|
/// The square's side length. This is mutually exclusive with `width` and
|
||||||
/// `height`.
|
/// `height`.
|
||||||
///
|
///
|
||||||
/// Display: Square
|
/// Display: Square
|
||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct SquareNode {
|
pub struct SquareNode {
|
||||||
/// The content to place into the square. The square expands to fit this
|
|
||||||
/// content, keeping the 1-1 aspect ratio.
|
|
||||||
///
|
|
||||||
/// When this is omitted, the square takes on a default size of at most
|
|
||||||
/// `{30pt}`.
|
|
||||||
#[positional]
|
|
||||||
#[default]
|
|
||||||
pub body: Option<Content>,
|
|
||||||
|
|
||||||
/// The square's width. This is mutually exclusive with `size` and `height`.
|
/// The square's width. This is mutually exclusive with `size` and `height`.
|
||||||
///
|
///
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
/// width.
|
/// width.
|
||||||
#[named]
|
#[parse(
|
||||||
#[default]
|
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
|
||||||
|
match size {
|
||||||
|
None => args.named("width")?,
|
||||||
|
size => size,
|
||||||
|
}
|
||||||
|
)]
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// The square's height. This is mutually exclusive with `size` and `width`.
|
/// The square's height. This is mutually exclusive with `size` and `width`.
|
||||||
///
|
///
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
/// height.
|
/// height.
|
||||||
#[named]
|
#[parse(match size {
|
||||||
#[default]
|
None => args.named("height")?,
|
||||||
|
size => size,
|
||||||
|
})]
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the square. See the
|
/// How to fill the square. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the square. See the [rectangle's
|
/// How to stroke the square. See the [rectangle's
|
||||||
/// documentation]($func/rect.stroke) for more details.
|
/// documentation]($func/rect.stroke) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
|
pub stroke: Smart<Sides<Option<Option<PartialStroke>>>>,
|
||||||
|
|
||||||
/// How much to round the square's corners. See the [rectangle's
|
/// How much to round the square's corners. See the [rectangle's
|
||||||
/// documentation]($func/rect.radius) for more details.
|
/// documentation]($func/rect.radius) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub radius: Corners<Option<Rel<Length>>>,
|
pub radius: Corners<Option<Rel<Length>>>,
|
||||||
|
|
||||||
/// How much to pad the square's content. See the [rectangle's
|
/// How much to pad the square's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
@ -258,33 +235,17 @@ pub struct SquareNode {
|
|||||||
|
|
||||||
/// How much to expand the square's size without affecting the layout. See
|
/// How much to expand the square's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub outset: Sides<Option<Rel<Length>>>,
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for SquareNode {
|
/// The content to place into the square. The square expands to fit this
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// content, keeping the 1-1 aspect ratio.
|
||||||
let size = args.named::<Smart<Length>>("size")?.map(|s| s.map(Rel::from));
|
///
|
||||||
let width = match size {
|
/// When this is omitted, the square takes on a default size of at most
|
||||||
None => args.named("width")?,
|
/// `{30pt}`.
|
||||||
size => size,
|
#[positional]
|
||||||
}
|
pub body: Option<Content>,
|
||||||
.unwrap_or_default();
|
|
||||||
let height = match size {
|
|
||||||
None => args.named("height")?,
|
|
||||||
size => size,
|
|
||||||
}
|
|
||||||
.unwrap_or_default();
|
|
||||||
let body = args.eat::<Content>()?;
|
|
||||||
Ok(Self::new()
|
|
||||||
.with_body(body)
|
|
||||||
.with_width(width)
|
|
||||||
.with_height(height)
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for SquareNode {
|
impl Layout for SquareNode {
|
||||||
@ -299,13 +260,13 @@ impl Layout for SquareNode {
|
|||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Square,
|
ShapeKind::Square,
|
||||||
&self.body(),
|
&self.body(styles),
|
||||||
Axes::new(self.width(), self.height()),
|
Axes::new(self.width(styles), self.height(styles)),
|
||||||
Self::fill_in(styles),
|
self.fill(styles),
|
||||||
Self::stroke_in(styles),
|
self.stroke(styles),
|
||||||
Self::inset_in(styles),
|
self.inset(styles),
|
||||||
Self::outset_in(styles),
|
self.outset(styles),
|
||||||
Self::radius_in(styles),
|
self.radius(styles),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -329,43 +290,26 @@ impl Layout for SquareNode {
|
|||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[node(Layout)]
|
#[node(Layout)]
|
||||||
pub struct EllipseNode {
|
pub struct EllipseNode {
|
||||||
/// The content to place into the ellipse.
|
|
||||||
///
|
|
||||||
/// When this is omitted, the ellipse takes on a default size of at most
|
|
||||||
/// `{45pt}` by `{30pt}`.
|
|
||||||
#[positional]
|
|
||||||
#[default]
|
|
||||||
pub body: Option<Content>,
|
|
||||||
|
|
||||||
/// The ellipse's width, relative to its parent container.
|
/// The ellipse's width, relative to its parent container.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// The ellipse's height, relative to its parent container.
|
/// The ellipse's height, relative to its parent container.
|
||||||
#[named]
|
|
||||||
#[default]
|
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the ellipse. See the
|
/// How to fill the ellipse. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the ellipse. See the [rectangle's
|
/// How to stroke the ellipse. See the [rectangle's
|
||||||
/// documentation]($func/rect.stroke) for more details.
|
/// documentation]($func/rect.stroke) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub stroke: Smart<Option<PartialStroke>>,
|
pub stroke: Smart<Option<PartialStroke>>,
|
||||||
|
|
||||||
/// How much to pad the ellipse's content. See the [rectangle's
|
/// How much to pad the ellipse's content. See the [rectangle's
|
||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
@ -373,11 +317,16 @@ pub struct EllipseNode {
|
|||||||
|
|
||||||
/// How much to expand the ellipse's size without affecting the layout. See
|
/// How much to expand the ellipse's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub outset: Sides<Option<Rel<Length>>>,
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
|
|
||||||
|
/// The content to place into the ellipse.
|
||||||
|
///
|
||||||
|
/// When this is omitted, the ellipse takes on a default size of at most
|
||||||
|
/// `{45pt}` by `{30pt}`.
|
||||||
|
#[positional]
|
||||||
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for EllipseNode {
|
impl Layout for EllipseNode {
|
||||||
@ -392,12 +341,12 @@ impl Layout for EllipseNode {
|
|||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Ellipse,
|
ShapeKind::Ellipse,
|
||||||
&self.body(),
|
&self.body(styles),
|
||||||
Axes::new(self.width(), self.height()),
|
Axes::new(self.width(styles), self.height(styles)),
|
||||||
Self::fill_in(styles),
|
self.fill(styles),
|
||||||
Self::stroke_in(styles).map(Sides::splat),
|
self.stroke(styles).map(Sides::splat),
|
||||||
Self::inset_in(styles),
|
self.inset(styles),
|
||||||
Self::outset_in(styles),
|
self.outset(styles),
|
||||||
Corners::splat(Rel::zero()),
|
Corners::splat(Rel::zero()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -419,27 +368,28 @@ impl Layout for EllipseNode {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Parameters
|
/// ## Parameters
|
||||||
/// - radius: `Length` (named)
|
/// - radius: `Length` (named, settable)
|
||||||
/// The circle's radius. This is mutually exclusive with `width` and
|
/// The circle's radius. This is mutually exclusive with `width` and
|
||||||
/// `height`.
|
/// `height`.
|
||||||
///
|
///
|
||||||
/// Display: Circle
|
/// Display: Circle
|
||||||
/// Category: visualize
|
/// Category: visualize
|
||||||
#[node(Construct, Layout)]
|
#[node(Layout)]
|
||||||
pub struct CircleNode {
|
pub struct CircleNode {
|
||||||
/// The content to place into the circle. The circle expands to fit this
|
|
||||||
/// content, keeping the 1-1 aspect ratio.
|
|
||||||
#[positional]
|
|
||||||
#[default]
|
|
||||||
pub body: Option<Content>,
|
|
||||||
|
|
||||||
/// The circle's width. This is mutually exclusive with `radius` and
|
/// The circle's width. This is mutually exclusive with `radius` and
|
||||||
/// `height`.
|
/// `height`.
|
||||||
///
|
///
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
/// width.
|
/// width.
|
||||||
#[named]
|
#[parse(
|
||||||
#[default]
|
let size = args
|
||||||
|
.named::<Smart<Length>>("radius")?
|
||||||
|
.map(|s| s.map(|r| 2.0 * Rel::from(r)));
|
||||||
|
match size {
|
||||||
|
None => args.named("width")?,
|
||||||
|
size => size,
|
||||||
|
}
|
||||||
|
)]
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// The circle's height.This is mutually exclusive with `radius` and
|
/// The circle's height.This is mutually exclusive with `radius` and
|
||||||
@ -447,19 +397,18 @@ pub struct CircleNode {
|
|||||||
///
|
///
|
||||||
/// In contrast to `size`, this can be relative to the parent container's
|
/// In contrast to `size`, this can be relative to the parent container's
|
||||||
/// height.
|
/// height.
|
||||||
#[named]
|
#[parse(match size {
|
||||||
#[default]
|
None => args.named("height")?,
|
||||||
|
size => size,
|
||||||
|
})]
|
||||||
pub height: Smart<Rel<Length>>,
|
pub height: Smart<Rel<Length>>,
|
||||||
|
|
||||||
/// How to fill the circle. See the
|
/// How to fill the circle. See the
|
||||||
/// [rectangle's documentation]($func/rect.fill) for more details.
|
/// [rectangle's documentation]($func/rect.fill) for more details.
|
||||||
#[settable]
|
|
||||||
#[default]
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
/// How to stroke the circle. See the [rectangle's
|
/// How to stroke the circle. See the [rectangle's
|
||||||
/// documentation]($func/rect.stroke) for more details.
|
/// documentation]($func/rect.stroke) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Smart::Auto)]
|
#[default(Smart::Auto)]
|
||||||
@ -469,7 +418,6 @@ pub struct CircleNode {
|
|||||||
/// documentation]($func/rect.inset) for more details.
|
/// documentation]($func/rect.inset) for more details.
|
||||||
///
|
///
|
||||||
/// The default value is `{5pt}`.
|
/// The default value is `{5pt}`.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
#[default(Sides::splat(Abs::pt(5.0).into()))]
|
||||||
@ -477,35 +425,14 @@ pub struct CircleNode {
|
|||||||
|
|
||||||
/// How much to expand the circle's size without affecting the layout. See
|
/// How much to expand the circle's size without affecting the layout. See
|
||||||
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
/// the [rectangle's documentation]($func/rect.outset) for more details.
|
||||||
#[settable]
|
|
||||||
#[resolve]
|
#[resolve]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub outset: Sides<Option<Rel<Length>>>,
|
pub outset: Sides<Option<Rel<Length>>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Construct for CircleNode {
|
/// The content to place into the circle. The circle expands to fit this
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
/// content, keeping the 1-1 aspect ratio.
|
||||||
let size = args
|
#[positional]
|
||||||
.named::<Smart<Length>>("radius")?
|
pub body: Option<Content>,
|
||||||
.map(|s| s.map(|r| 2.0 * Rel::from(r)));
|
|
||||||
let width = match size {
|
|
||||||
None => args.named("width")?,
|
|
||||||
size => size,
|
|
||||||
}
|
|
||||||
.unwrap_or_default();
|
|
||||||
let height = match size {
|
|
||||||
None => args.named("height")?,
|
|
||||||
size => size,
|
|
||||||
}
|
|
||||||
.unwrap_or_default();
|
|
||||||
let body = args.eat::<Content>()?;
|
|
||||||
Ok(Self::new()
|
|
||||||
.with_body(body)
|
|
||||||
.with_width(width)
|
|
||||||
.with_height(height)
|
|
||||||
.pack())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for CircleNode {
|
impl Layout for CircleNode {
|
||||||
@ -520,12 +447,12 @@ impl Layout for CircleNode {
|
|||||||
styles,
|
styles,
|
||||||
regions,
|
regions,
|
||||||
ShapeKind::Circle,
|
ShapeKind::Circle,
|
||||||
&self.body(),
|
&self.body(styles),
|
||||||
Axes::new(self.width(), self.height()),
|
Axes::new(self.width(styles), self.height(styles)),
|
||||||
Self::fill_in(styles),
|
self.fill(styles),
|
||||||
Self::stroke_in(styles).map(Sides::splat),
|
self.stroke(styles).map(Sides::splat),
|
||||||
Self::inset_in(styles),
|
self.inset(styles),
|
||||||
Self::outset_in(styles),
|
self.outset(styles),
|
||||||
Corners::splat(Rel::zero()),
|
Corners::splat(Rel::zero()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -143,8 +143,8 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
|
|||||||
|
|
||||||
for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) {
|
for part in s.eat_until(')').split(',').map(str::trim).filter(|s| !s.is_empty()) {
|
||||||
match part {
|
match part {
|
||||||
"named" => named = true,
|
|
||||||
"positional" => positional = true,
|
"positional" => positional = true,
|
||||||
|
"named" => named = true,
|
||||||
"required" => required = true,
|
"required" => required = true,
|
||||||
"variadic" => variadic = true,
|
"variadic" => variadic = true,
|
||||||
"settable" => settable = true,
|
"settable" => settable = true,
|
||||||
@ -152,8 +152,7 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!named && !positional) || (variadic && !positional) || (required && variadic)
|
if (!named && !positional) || (variadic && !positional) {
|
||||||
{
|
|
||||||
bail!(callsite, "invalid combination of parameter flags");
|
bail!(callsite, "invalid combination of parameter flags");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,10 +168,10 @@ fn params(docs: &mut String) -> Result<(Vec<TokenStream>, Vec<String>)> {
|
|||||||
cast: <#ty as ::typst::eval::Cast<
|
cast: <#ty as ::typst::eval::Cast<
|
||||||
::typst::syntax::Spanned<::typst::eval::Value>
|
::typst::syntax::Spanned<::typst::eval::Value>
|
||||||
>>::describe(),
|
>>::describe(),
|
||||||
named: #named,
|
|
||||||
positional: #positional,
|
positional: #positional,
|
||||||
required: #required,
|
named: #named,
|
||||||
variadic: #variadic,
|
variadic: #variadic,
|
||||||
|
required: #required,
|
||||||
settable: #settable,
|
settable: #settable,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -12,48 +12,64 @@ struct Node {
|
|||||||
ident: Ident,
|
ident: Ident,
|
||||||
name: String,
|
name: String,
|
||||||
capable: Vec<Ident>,
|
capable: Vec<Ident>,
|
||||||
set: Option<syn::Block>,
|
|
||||||
fields: Vec<Field>,
|
fields: Vec<Field>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
fn inherent(&self) -> impl Iterator<Item = &Field> {
|
||||||
|
self.fields.iter().filter(|field| field.inherent())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settable(&self) -> impl Iterator<Item = &Field> {
|
||||||
|
self.fields.iter().filter(|field| field.settable())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Field {
|
struct Field {
|
||||||
attrs: Vec<syn::Attribute>,
|
attrs: Vec<syn::Attribute>,
|
||||||
vis: syn::Visibility,
|
vis: syn::Visibility,
|
||||||
|
|
||||||
|
name: String,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
ident_in: Ident,
|
ident_in: Ident,
|
||||||
with_ident: Ident,
|
with_ident: Ident,
|
||||||
set_ident: Ident,
|
set_ident: Ident,
|
||||||
name: String,
|
|
||||||
|
|
||||||
|
internal: bool,
|
||||||
positional: bool,
|
positional: bool,
|
||||||
required: bool,
|
required: bool,
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
|
|
||||||
named: bool,
|
|
||||||
shorthand: Option<Shorthand>,
|
|
||||||
|
|
||||||
settable: bool,
|
|
||||||
fold: bool,
|
fold: bool,
|
||||||
resolve: bool,
|
resolve: bool,
|
||||||
skip: bool,
|
parse: Option<FieldParser>,
|
||||||
|
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
output: syn::Type,
|
output: syn::Type,
|
||||||
default: Option<syn::Expr>,
|
default: syn::Expr,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Shorthand {
|
impl Field {
|
||||||
Positional,
|
fn inherent(&self) -> bool {
|
||||||
Named(Ident),
|
self.required || self.variadic
|
||||||
}
|
|
||||||
|
|
||||||
impl Node {
|
|
||||||
fn inherent(&self) -> impl Iterator<Item = &Field> + Clone {
|
|
||||||
self.fields.iter().filter(|field| !field.settable)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settable(&self) -> impl Iterator<Item = &Field> + Clone {
|
fn settable(&self) -> bool {
|
||||||
self.fields.iter().filter(|field| field.settable)
|
!self.inherent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FieldParser {
|
||||||
|
prefix: Vec<syn::Stmt>,
|
||||||
|
expr: syn::Stmt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for FieldParser {
|
||||||
|
fn parse(input: ParseStream) -> Result<Self> {
|
||||||
|
let mut stmts = syn::Block::parse_within(input)?;
|
||||||
|
let Some(expr) = stmts.pop() else {
|
||||||
|
return Err(input.error("expected at least on expression"));
|
||||||
|
};
|
||||||
|
Ok(Self { prefix: stmts, expr })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,34 +86,30 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut attrs = field.attrs.clone();
|
let mut attrs = field.attrs.clone();
|
||||||
|
let variadic = has_attr(&mut attrs, "variadic");
|
||||||
|
|
||||||
let mut field = Field {
|
let mut field = Field {
|
||||||
vis: field.vis.clone(),
|
vis: field.vis.clone(),
|
||||||
|
|
||||||
|
name: kebab_case(&ident),
|
||||||
ident: ident.clone(),
|
ident: ident.clone(),
|
||||||
ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
|
ident_in: Ident::new(&format!("{}_in", ident), ident.span()),
|
||||||
with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
|
with_ident: Ident::new(&format!("with_{}", ident), ident.span()),
|
||||||
set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
|
set_ident: Ident::new(&format!("set_{}", ident), ident.span()),
|
||||||
name: kebab_case(&ident),
|
|
||||||
|
|
||||||
positional: has_attr(&mut attrs, "positional"),
|
internal: has_attr(&mut attrs, "internal"),
|
||||||
required: has_attr(&mut attrs, "required"),
|
positional: has_attr(&mut attrs, "positional") || variadic,
|
||||||
variadic: has_attr(&mut attrs, "variadic"),
|
required: has_attr(&mut attrs, "required") || variadic,
|
||||||
|
variadic,
|
||||||
named: has_attr(&mut attrs, "named"),
|
|
||||||
shorthand: parse_attr(&mut attrs, "shorthand")?.map(|v| match v {
|
|
||||||
None => Shorthand::Positional,
|
|
||||||
Some(ident) => Shorthand::Named(ident),
|
|
||||||
}),
|
|
||||||
|
|
||||||
settable: has_attr(&mut attrs, "settable"),
|
|
||||||
fold: has_attr(&mut attrs, "fold"),
|
fold: has_attr(&mut attrs, "fold"),
|
||||||
resolve: has_attr(&mut attrs, "resolve"),
|
resolve: has_attr(&mut attrs, "resolve"),
|
||||||
skip: has_attr(&mut attrs, "skip"),
|
parse: parse_attr(&mut attrs, "parse")?.flatten(),
|
||||||
|
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
output: field.ty.clone(),
|
output: field.ty.clone(),
|
||||||
default: parse_attr(&mut attrs, "default")?.map(|opt| {
|
default: parse_attr(&mut attrs, "default")?
|
||||||
opt.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() })
|
.flatten()
|
||||||
}),
|
.unwrap_or_else(|| parse_quote! { ::std::default::Default::default() }),
|
||||||
|
|
||||||
attrs: {
|
attrs: {
|
||||||
validate_attrs(&attrs)?;
|
validate_attrs(&attrs)?;
|
||||||
@ -105,6 +117,14 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if field.required && (field.fold || field.resolve) {
|
||||||
|
bail!(ident, "required fields cannot be folded or resolved");
|
||||||
|
}
|
||||||
|
|
||||||
|
if field.required && !field.positional {
|
||||||
|
bail!(ident, "only positional fields can be required");
|
||||||
|
}
|
||||||
|
|
||||||
if field.resolve {
|
if field.resolve {
|
||||||
let output = &field.output;
|
let output = &field.output;
|
||||||
field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output };
|
field.output = parse_quote! { <#output as ::typst::model::Resolve>::Output };
|
||||||
@ -114,14 +134,6 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
|||||||
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
|
field.output = parse_quote! { <#output as ::typst::model::Fold>::Output };
|
||||||
}
|
}
|
||||||
|
|
||||||
if !field.positional && !field.named && !field.variadic && !field.settable {
|
|
||||||
bail!(ident, "expected positional, named, variadic, or settable");
|
|
||||||
}
|
|
||||||
|
|
||||||
if !field.required && !field.variadic && field.default.is_none() {
|
|
||||||
bail!(ident, "non-required fields must have a default value");
|
|
||||||
}
|
|
||||||
|
|
||||||
fields.push(field);
|
fields.push(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,14 +142,13 @@ fn prepare(stream: TokenStream, body: &syn::ItemStruct) -> Result<Node> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut attrs = body.attrs.clone();
|
let attrs = body.attrs.clone();
|
||||||
Ok(Node {
|
Ok(Node {
|
||||||
vis: body.vis.clone(),
|
vis: body.vis.clone(),
|
||||||
ident: body.ident.clone(),
|
ident: body.ident.clone(),
|
||||||
name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
|
name: body.ident.to_string().trim_end_matches("Node").to_lowercase(),
|
||||||
capable,
|
capable,
|
||||||
fields,
|
fields,
|
||||||
set: parse_attr(&mut attrs, "set")?.flatten(),
|
|
||||||
attrs: {
|
attrs: {
|
||||||
validate_attrs(&attrs)?;
|
validate_attrs(&attrs)?;
|
||||||
attrs
|
attrs
|
||||||
@ -153,10 +164,10 @@ fn create(node: &Node) -> TokenStream {
|
|||||||
|
|
||||||
// Inherent methods and functions.
|
// Inherent methods and functions.
|
||||||
let new = create_new_func(node);
|
let new = create_new_func(node);
|
||||||
let field_methods = node.inherent().map(create_field_method);
|
let field_methods = node.fields.iter().map(create_field_method);
|
||||||
let with_fields_methods = node.inherent().map(create_with_field_method);
|
|
||||||
let field_in_methods = node.settable().map(create_field_in_method);
|
let field_in_methods = node.settable().map(create_field_in_method);
|
||||||
let field_style_methods = node.settable().map(create_field_style_method);
|
let with_fields_methods = node.fields.iter().map(create_with_field_method);
|
||||||
|
let field_style_methods = node.settable().map(create_set_field_method);
|
||||||
|
|
||||||
// Trait implementations.
|
// Trait implementations.
|
||||||
let construct = node
|
let construct = node
|
||||||
@ -177,8 +188,8 @@ fn create(node: &Node) -> TokenStream {
|
|||||||
impl #ident {
|
impl #ident {
|
||||||
#new
|
#new
|
||||||
#(#field_methods)*
|
#(#field_methods)*
|
||||||
#(#with_fields_methods)*
|
|
||||||
#(#field_in_methods)*
|
#(#field_in_methods)*
|
||||||
|
#(#with_fields_methods)*
|
||||||
#(#field_style_methods)*
|
#(#field_style_methods)*
|
||||||
|
|
||||||
/// The node's span.
|
/// The node's span.
|
||||||
@ -201,42 +212,73 @@ fn create(node: &Node) -> TokenStream {
|
|||||||
|
|
||||||
/// Create the `new` function for the node.
|
/// Create the `new` function for the node.
|
||||||
fn create_new_func(node: &Node) -> TokenStream {
|
fn create_new_func(node: &Node) -> TokenStream {
|
||||||
let relevant = node.inherent().filter(|field| field.required || field.variadic);
|
let params = node.inherent().map(|Field { ident, ty, .. }| {
|
||||||
let params = relevant.clone().map(|field| {
|
|
||||||
let ident = &field.ident;
|
|
||||||
let ty = &field.ty;
|
|
||||||
quote! { #ident: #ty }
|
quote! { #ident: #ty }
|
||||||
});
|
});
|
||||||
let pushes = relevant.map(|field| {
|
let builder_calls = node.inherent().map(|Field { ident, with_ident, .. }| {
|
||||||
let ident = &field.ident;
|
|
||||||
let with_ident = &field.with_ident;
|
|
||||||
quote! { .#with_ident(#ident) }
|
quote! { .#with_ident(#ident) }
|
||||||
});
|
});
|
||||||
let defaults = node
|
|
||||||
.inherent()
|
|
||||||
.filter_map(|field| field.default.as_ref().map(|default| (field, default)))
|
|
||||||
.map(|(field, default)| {
|
|
||||||
let with_ident = &field.with_ident;
|
|
||||||
quote! { .#with_ident(#default) }
|
|
||||||
});
|
|
||||||
quote! {
|
quote! {
|
||||||
/// Create a new node.
|
/// Create a new node.
|
||||||
pub fn new(#(#params),*) -> Self {
|
pub fn new(#(#params),*) -> Self {
|
||||||
Self(::typst::model::Content::new::<Self>())
|
Self(::typst::model::Content::new::<Self>())
|
||||||
#(#pushes)*
|
#(#builder_calls)*
|
||||||
#(#defaults)*
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create an accessor methods for a field.
|
/// Create an accessor methods for a field.
|
||||||
fn create_field_method(field: &Field) -> TokenStream {
|
fn create_field_method(field: &Field) -> TokenStream {
|
||||||
let Field { attrs, vis, ident, name, ty, .. } = field;
|
let Field { attrs, vis, ident, name, output, .. } = field;
|
||||||
quote! {
|
if field.inherent() {
|
||||||
#(#attrs)*
|
quote! {
|
||||||
#vis fn #ident(&self) -> #ty {
|
#(#attrs)*
|
||||||
self.0.cast_field(#name)
|
#vis fn #ident(&self) -> #output {
|
||||||
|
self.0.cast_required_field(#name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let access =
|
||||||
|
create_style_chain_access(field, quote! { self.0.field(#name).cloned() });
|
||||||
|
quote! {
|
||||||
|
#(#attrs)*
|
||||||
|
#vis fn #ident(&self, styles: ::typst::model::StyleChain) -> #output {
|
||||||
|
#access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a style chain access method for a field.
|
||||||
|
fn create_field_in_method(field: &Field) -> TokenStream {
|
||||||
|
let Field { vis, ident_in, name, output, .. } = field;
|
||||||
|
let doc = format!("Access the `{}` field in the given style chain.", name);
|
||||||
|
let access = create_style_chain_access(field, quote! { None });
|
||||||
|
quote! {
|
||||||
|
#[doc = #doc]
|
||||||
|
#vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
|
||||||
|
#access
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a style chain access method for a field.
|
||||||
|
fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream {
|
||||||
|
let Field { name, ty, default, .. } = field;
|
||||||
|
let getter = match (field.fold, field.resolve) {
|
||||||
|
(false, false) => quote! { get },
|
||||||
|
(false, true) => quote! { get_resolve },
|
||||||
|
(true, false) => quote! { get_fold },
|
||||||
|
(true, true) => quote! { get_resolve_fold },
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
styles.#getter::<#ty>(
|
||||||
|
::typst::model::NodeId::of::<Self>(),
|
||||||
|
#name,
|
||||||
|
#inherent,
|
||||||
|
|| #default,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -252,70 +294,8 @@ fn create_with_field_method(field: &Field) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a style chain access method for a field.
|
/// Create a setter method for a field.
|
||||||
fn create_field_in_method(field: &Field) -> TokenStream {
|
fn create_set_field_method(field: &Field) -> TokenStream {
|
||||||
let Field { vis, ident_in, name, ty, output, default, .. } = field;
|
|
||||||
|
|
||||||
let doc = format!("Access the `{}` field in the given style chain.", name);
|
|
||||||
let args = quote! { ::typst::model::NodeId::of::<Self>(), #name };
|
|
||||||
|
|
||||||
let body = if field.fold && field.resolve {
|
|
||||||
quote! {
|
|
||||||
fn next(
|
|
||||||
mut values: impl ::std::iter::Iterator<Item = #ty>,
|
|
||||||
styles: ::typst::model::StyleChain,
|
|
||||||
) -> #output {
|
|
||||||
values
|
|
||||||
.next()
|
|
||||||
.map(|value| {
|
|
||||||
::typst::model::Fold::fold(
|
|
||||||
::typst::model::Resolve::resolve(value, styles),
|
|
||||||
next(values, styles),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| #default)
|
|
||||||
}
|
|
||||||
next(styles.properties(#args), styles)
|
|
||||||
}
|
|
||||||
} else if field.fold {
|
|
||||||
quote! {
|
|
||||||
fn next(
|
|
||||||
mut values: impl ::std::iter::Iterator<Item = #ty>,
|
|
||||||
styles: ::typst::model::StyleChain,
|
|
||||||
) -> #output {
|
|
||||||
values
|
|
||||||
.next()
|
|
||||||
.map(|value| {
|
|
||||||
::typst::model::Fold::fold(value, next(values, styles))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| #default)
|
|
||||||
}
|
|
||||||
next(styles.properties(#args), styles)
|
|
||||||
}
|
|
||||||
} else if field.resolve {
|
|
||||||
quote! {
|
|
||||||
::typst::model::Resolve::resolve(
|
|
||||||
styles.property::<#ty>(#args).unwrap_or_else(|| #default),
|
|
||||||
styles
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
styles.property(#args).unwrap_or_else(|| #default)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#[doc = #doc]
|
|
||||||
#[allow(unused)]
|
|
||||||
#vis fn #ident_in(styles: ::typst::model::StyleChain) -> #output {
|
|
||||||
#body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a style creation method for a field.
|
|
||||||
fn create_field_style_method(field: &Field) -> TokenStream {
|
|
||||||
let Field { vis, ident, set_ident, name, ty, .. } = field;
|
let Field { vis, ident, set_ident, name, ty, .. } = field;
|
||||||
let doc = format!("Create a style property for the `{}` field.", name);
|
let doc = format!("Create a style property for the `{}` field.", name);
|
||||||
quote! {
|
quote! {
|
||||||
@ -335,8 +315,17 @@ fn create_node_impl(node: &Node) -> TokenStream {
|
|||||||
let ident = &node.ident;
|
let ident = &node.ident;
|
||||||
let name = &node.name;
|
let name = &node.name;
|
||||||
let vtable_func = create_vtable_func(node);
|
let vtable_func = create_vtable_func(node);
|
||||||
|
let infos = node
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.filter(|field| !field.internal)
|
||||||
|
.map(create_param_info);
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Node for #ident {
|
impl ::typst::model::Node for #ident {
|
||||||
|
fn pack(self) -> ::typst::model::Content {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
fn id() -> ::typst::model::NodeId {
|
fn id() -> ::typst::model::NodeId {
|
||||||
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
|
static META: ::typst::model::NodeMeta = ::typst::model::NodeMeta {
|
||||||
name: #name,
|
name: #name,
|
||||||
@ -345,179 +334,145 @@ fn create_node_impl(node: &Node) -> TokenStream {
|
|||||||
::typst::model::NodeId::from_meta(&META)
|
::typst::model::NodeId::from_meta(&META)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pack(self) -> ::typst::model::Content {
|
fn params() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
|
||||||
self.0
|
::std::vec![#(#infos),*]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the node's metadata vtable.
|
/// Create the node's casting vtable.
|
||||||
fn create_vtable_func(node: &Node) -> TokenStream {
|
fn create_vtable_func(node: &Node) -> TokenStream {
|
||||||
let ident = &node.ident;
|
let ident = &node.ident;
|
||||||
let checks =
|
let relevant = node.capable.iter().filter(|&ident| ident != "Construct");
|
||||||
node.capable
|
let checks = relevant.map(|capability| {
|
||||||
.iter()
|
quote! {
|
||||||
.filter(|&ident| ident != "Construct")
|
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
||||||
.map(|capability| {
|
return Some(unsafe {
|
||||||
quote! {
|
::typst::util::fat::vtable(&null as &dyn #capability)
|
||||||
if id == ::std::any::TypeId::of::<dyn #capability>() {
|
});
|
||||||
return Some(unsafe {
|
}
|
||||||
::typst::util::fat::vtable(&
|
}
|
||||||
Self(::typst::model::Content::new::<#ident>()) as &dyn #capability
|
});
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
|id| {
|
|id| {
|
||||||
|
let null = Self(::typst::model::Content::new::<#ident>());
|
||||||
#(#checks)*
|
#(#checks)*
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a parameter info for a field.
|
||||||
|
fn create_param_info(field: &Field) -> TokenStream {
|
||||||
|
let Field { name, positional, variadic, required, ty, .. } = field;
|
||||||
|
let named = !positional;
|
||||||
|
let settable = field.settable();
|
||||||
|
let docs = documentation(&field.attrs);
|
||||||
|
let docs = docs.trim();
|
||||||
|
quote! {
|
||||||
|
::typst::eval::ParamInfo {
|
||||||
|
name: #name,
|
||||||
|
docs: #docs,
|
||||||
|
cast: <#ty as ::typst::eval::Cast<
|
||||||
|
::typst::syntax::Spanned<::typst::eval::Value>
|
||||||
|
>>::describe(),
|
||||||
|
positional: #positional,
|
||||||
|
named: #named,
|
||||||
|
variadic: #variadic,
|
||||||
|
required: #required,
|
||||||
|
settable: #settable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create the node's `Construct` implementation.
|
/// Create the node's `Construct` implementation.
|
||||||
fn create_construct_impl(node: &Node) -> TokenStream {
|
fn create_construct_impl(node: &Node) -> TokenStream {
|
||||||
let ident = &node.ident;
|
let ident = &node.ident;
|
||||||
let shorthands = create_construct_shorthands(node);
|
let handlers = node
|
||||||
let builders = node.inherent().map(create_construct_builder_call);
|
.fields
|
||||||
|
.iter()
|
||||||
|
.filter(|field| !field.internal || field.parse.is_some())
|
||||||
|
.map(|field| {
|
||||||
|
let with_ident = &field.with_ident;
|
||||||
|
let (prefix, value) = create_field_parser(field);
|
||||||
|
if field.settable() {
|
||||||
|
quote! {
|
||||||
|
#prefix
|
||||||
|
if let Some(value) = #value {
|
||||||
|
node = node.#with_ident(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#prefix
|
||||||
|
node = node.#with_ident(#value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Construct for #ident {
|
impl ::typst::model::Construct for #ident {
|
||||||
fn construct(
|
fn construct(
|
||||||
_: &::typst::eval::Vm,
|
vm: &::typst::eval::Vm,
|
||||||
args: &mut ::typst::eval::Args,
|
args: &mut ::typst::eval::Args,
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
) -> ::typst::diag::SourceResult<::typst::model::Content> {
|
||||||
#(#shorthands)*
|
let mut node = Self(::typst::model::Content::new::<Self>());
|
||||||
Ok(::typst::model::Node::pack(
|
#(#handlers)*
|
||||||
Self(::typst::model::Content::new::<Self>())
|
Ok(node.0)
|
||||||
#(#builders)*))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create let bindings for shorthands in the constructor.
|
|
||||||
fn create_construct_shorthands(node: &Node) -> impl Iterator<Item = TokenStream> + '_ {
|
|
||||||
let mut shorthands = vec![];
|
|
||||||
for field in node.inherent() {
|
|
||||||
if let Some(Shorthand::Named(named)) = &field.shorthand {
|
|
||||||
shorthands.push(named);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shorthands.sort();
|
|
||||||
shorthands.dedup_by_key(|ident| ident.to_string());
|
|
||||||
shorthands.into_iter().map(|ident| {
|
|
||||||
let string = ident.to_string();
|
|
||||||
quote! { let #ident = args.named(#string)?; }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a builder call for the constructor.
|
|
||||||
fn create_construct_builder_call(field: &Field) -> TokenStream {
|
|
||||||
let name = &field.name;
|
|
||||||
let with_ident = &field.with_ident;
|
|
||||||
|
|
||||||
let mut value = if field.variadic {
|
|
||||||
quote! { args.all()? }
|
|
||||||
} else if field.required {
|
|
||||||
quote! { args.expect(#name)? }
|
|
||||||
} else if let Some(shorthand) = &field.shorthand {
|
|
||||||
match shorthand {
|
|
||||||
Shorthand::Positional => quote! { args.named_or_find(#name)? },
|
|
||||||
Shorthand::Named(named) => {
|
|
||||||
quote! { args.named(#name)?.or_else(|| #named.clone()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if field.named {
|
|
||||||
quote! { args.named(#name)? }
|
|
||||||
} else {
|
|
||||||
quote! { args.find()? }
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(default) = &field.default {
|
|
||||||
value = quote! { #value.unwrap_or(#default) };
|
|
||||||
}
|
|
||||||
|
|
||||||
quote! { .#with_ident(#value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create the node's `Set` implementation.
|
/// Create the node's `Set` implementation.
|
||||||
fn create_set_impl(node: &Node) -> TokenStream {
|
fn create_set_impl(node: &Node) -> TokenStream {
|
||||||
let ident = &node.ident;
|
let ident = &node.ident;
|
||||||
let custom = node.set.as_ref().map(|block| {
|
let handlers = node
|
||||||
quote! { (|| -> typst::diag::SourceResult<()> { #block; Ok(()) } )()?; }
|
.fields
|
||||||
});
|
.iter()
|
||||||
|
.filter(|field| field.settable() && (!field.internal || field.parse.is_some()))
|
||||||
let mut shorthands = vec![];
|
|
||||||
let sets: Vec<_> = node
|
|
||||||
.settable()
|
|
||||||
.filter(|field| !field.skip)
|
|
||||||
.map(|field| {
|
.map(|field| {
|
||||||
let name = &field.name;
|
|
||||||
let set_ident = &field.set_ident;
|
let set_ident = &field.set_ident;
|
||||||
let value = match &field.shorthand {
|
let (prefix, value) = create_field_parser(field);
|
||||||
Some(Shorthand::Positional) => quote! { args.named_or_find(#name)? },
|
quote! {
|
||||||
Some(Shorthand::Named(named)) => {
|
#prefix
|
||||||
shorthands.push(named);
|
if let Some(value) = #value {
|
||||||
quote! { args.named(#name)?.or_else(|| #named.clone()) }
|
styles.set(Self::#set_ident(value));
|
||||||
}
|
}
|
||||||
None => quote! { args.named(#name)? },
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! { styles.set_opt(#value.map(Self::#set_ident)); }
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
shorthands.sort();
|
|
||||||
shorthands.dedup_by_key(|ident| ident.to_string());
|
|
||||||
|
|
||||||
let bindings = shorthands.into_iter().map(|ident| {
|
|
||||||
let string = ident.to_string();
|
|
||||||
quote! { let #ident = args.named(#string)?; }
|
|
||||||
});
|
|
||||||
|
|
||||||
let infos = node.fields.iter().filter(|p| !p.skip).map(|field| {
|
|
||||||
let name = &field.name;
|
|
||||||
let value_ty = &field.ty;
|
|
||||||
let shorthand = matches!(field.shorthand, Some(Shorthand::Positional));
|
|
||||||
let docs = documentation(&field.attrs);
|
|
||||||
let docs = docs.trim();
|
|
||||||
quote! {
|
|
||||||
::typst::eval::ParamInfo {
|
|
||||||
name: #name,
|
|
||||||
docs: #docs,
|
|
||||||
cast: <#value_ty as ::typst::eval::Cast<
|
|
||||||
::typst::syntax::Spanned<::typst::eval::Value>
|
|
||||||
>>::describe(),
|
|
||||||
named: true,
|
|
||||||
positional: #shorthand,
|
|
||||||
required: false,
|
|
||||||
variadic: false,
|
|
||||||
settable: true,
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Set for #ident {
|
impl ::typst::model::Set for #ident {
|
||||||
fn set(
|
fn set(
|
||||||
args: &mut ::typst::eval::Args,
|
args: &mut ::typst::eval::Args,
|
||||||
constructor: bool,
|
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::StyleMap> {
|
) -> ::typst::diag::SourceResult<::typst::model::StyleMap> {
|
||||||
let mut styles = ::typst::model::StyleMap::new();
|
let mut styles = ::typst::model::StyleMap::new();
|
||||||
#custom
|
#(#handlers)*
|
||||||
#(#bindings)*
|
|
||||||
#(#sets)*
|
|
||||||
Ok(styles)
|
Ok(styles)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties() -> ::std::vec::Vec<::typst::eval::ParamInfo> {
|
|
||||||
::std::vec![#(#infos),*]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create argument parsing code for a field.
|
||||||
|
fn create_field_parser(field: &Field) -> (TokenStream, TokenStream) {
|
||||||
|
let name = &field.name;
|
||||||
|
if let Some(FieldParser { prefix, expr }) = &field.parse {
|
||||||
|
return (quote! { #(#prefix);* }, quote! { #expr });
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = if field.variadic {
|
||||||
|
quote! { args.all()? }
|
||||||
|
} else if field.required {
|
||||||
|
quote! { args.expect(#name)? }
|
||||||
|
} else if field.positional {
|
||||||
|
quote! { args.find()? }
|
||||||
|
} else {
|
||||||
|
quote! { args.named(#name)? }
|
||||||
|
};
|
||||||
|
|
||||||
|
(quote! {}, value)
|
||||||
|
}
|
||||||
|
@ -605,9 +605,7 @@ pub enum Meta {
|
|||||||
pub struct MetaNode {
|
pub struct MetaNode {
|
||||||
/// Metadata that should be attached to all elements affected by this style
|
/// Metadata that should be attached to all elements affected by this style
|
||||||
/// property.
|
/// property.
|
||||||
#[settable]
|
|
||||||
#[fold]
|
#[fold]
|
||||||
#[default]
|
|
||||||
pub data: Vec<Meta>,
|
pub data: Vec<Meta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,15 +54,11 @@ impl Func {
|
|||||||
|
|
||||||
/// Create a new function from a native rust node.
|
/// Create a new function from a native rust node.
|
||||||
pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
|
pub fn from_node<T: Node>(mut info: FuncInfo) -> Self {
|
||||||
info.params.extend(T::properties());
|
info.params.extend(T::params());
|
||||||
Self(
|
Self(
|
||||||
Arc::new(Prehashed::new(Repr::Native(Native {
|
Arc::new(Prehashed::new(Repr::Native(Native {
|
||||||
func: |ctx, args| {
|
func: |vm, args| T::construct(vm, args).map(Value::Content),
|
||||||
let styles = T::set(args, true)?;
|
set: Some(T::set),
|
||||||
let content = T::construct(ctx, args)?;
|
|
||||||
Ok(Value::Content(content.styled_with_map(styles.scoped())))
|
|
||||||
},
|
|
||||||
set: Some(|args| T::set(args, false)),
|
|
||||||
node: Some(NodeId::of::<T>()),
|
node: Some(NodeId::of::<T>()),
|
||||||
info,
|
info,
|
||||||
}))),
|
}))),
|
||||||
@ -281,10 +277,10 @@ pub struct ParamInfo {
|
|||||||
/// Can be true even if `positional` is true if the parameter can be given
|
/// Can be true even if `positional` is true if the parameter can be given
|
||||||
/// in both variants.
|
/// in both variants.
|
||||||
pub named: bool,
|
pub named: bool,
|
||||||
/// Is the parameter required?
|
|
||||||
pub required: bool,
|
|
||||||
/// Can the parameter be given any number of times?
|
/// Can the parameter be given any number of times?
|
||||||
pub variadic: bool,
|
pub variadic: bool,
|
||||||
|
/// Is the parameter required?
|
||||||
|
pub required: bool,
|
||||||
/// Is the parameter settable with a set rule?
|
/// Is the parameter settable with a set rule?
|
||||||
pub settable: bool,
|
pub settable: bool,
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ impl Content {
|
|||||||
/// Attach a span to the content.
|
/// Attach a span to the content.
|
||||||
pub fn spanned(mut self, span: Span) -> Self {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
if let Some(styled) = self.to::<StyledNode>() {
|
if let Some(styled) = self.to::<StyledNode>() {
|
||||||
self = StyledNode::new(styled.body().spanned(span), styled.map()).pack();
|
self = StyledNode::new(styled.map(), styled.body().spanned(span)).pack();
|
||||||
}
|
}
|
||||||
self.span = Some(span);
|
self.span = Some(span);
|
||||||
self
|
self
|
||||||
@ -78,9 +78,9 @@ impl Content {
|
|||||||
} else if let Some(styled) = self.to::<StyledNode>() {
|
} else if let Some(styled) = self.to::<StyledNode>() {
|
||||||
let mut map = styled.map();
|
let mut map = styled.map();
|
||||||
map.apply(styles);
|
map.apply(styles);
|
||||||
StyledNode::new(styled.body(), map).pack()
|
StyledNode::new(map, styled.body()).pack()
|
||||||
} else {
|
} else {
|
||||||
StyledNode::new(self, styles).pack()
|
StyledNode::new(styles, self).pack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ impl Content {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn cast_field<T: Cast>(&self, name: &str) -> T {
|
pub fn cast_required_field<T: Cast>(&self, name: &str) -> T {
|
||||||
match self.field(name) {
|
match self.field(name) {
|
||||||
Some(value) => value.clone().cast().unwrap(),
|
Some(value) => value.clone().cast().unwrap(),
|
||||||
None => field_is_missing(name),
|
None => field_is_missing(name),
|
||||||
@ -329,15 +329,15 @@ impl Sum for Content {
|
|||||||
/// Category: special
|
/// Category: special
|
||||||
#[node]
|
#[node]
|
||||||
pub struct StyledNode {
|
pub struct StyledNode {
|
||||||
/// The styled content.
|
|
||||||
#[positional]
|
|
||||||
#[required]
|
|
||||||
pub body: Content,
|
|
||||||
|
|
||||||
/// The styles.
|
/// The styles.
|
||||||
#[positional]
|
#[positional]
|
||||||
#[required]
|
#[required]
|
||||||
pub map: StyleMap,
|
pub map: StyleMap,
|
||||||
|
|
||||||
|
/// The styled content.
|
||||||
|
#[positional]
|
||||||
|
#[required]
|
||||||
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
cast_from_value! {
|
cast_from_value! {
|
||||||
@ -353,8 +353,8 @@ cast_from_value! {
|
|||||||
/// Category: special
|
/// Category: special
|
||||||
#[node]
|
#[node]
|
||||||
pub struct SequenceNode {
|
pub struct SequenceNode {
|
||||||
|
#[positional]
|
||||||
#[variadic]
|
#[variadic]
|
||||||
#[required]
|
|
||||||
pub children: Vec<Content>,
|
pub children: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,11 +370,14 @@ impl Debug for Label {
|
|||||||
|
|
||||||
/// A constructable, stylable content node.
|
/// A constructable, stylable content node.
|
||||||
pub trait Node: Construct + Set + Sized + 'static {
|
pub trait Node: Construct + Set + Sized + 'static {
|
||||||
|
/// Pack a node into type-erased content.
|
||||||
|
fn pack(self) -> Content;
|
||||||
|
|
||||||
/// The node's ID.
|
/// The node's ID.
|
||||||
fn id() -> NodeId;
|
fn id() -> NodeId;
|
||||||
|
|
||||||
/// Pack a node into type-erased content.
|
/// List the fields of the node.
|
||||||
fn pack(self) -> Content;
|
fn params() -> Vec<ParamInfo>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique identifier for a node.
|
/// A unique identifier for a node.
|
||||||
@ -432,13 +435,7 @@ pub trait Construct {
|
|||||||
|
|
||||||
pub trait Set {
|
pub trait Set {
|
||||||
/// Parse relevant arguments into style properties for this node.
|
/// Parse relevant arguments into style properties for this node.
|
||||||
///
|
fn set(args: &mut Args) -> SourceResult<StyleMap>;
|
||||||
/// When `constructor` is true, [`construct`](Construct::construct) will run
|
|
||||||
/// after this invocation of `set` with the remaining arguments.
|
|
||||||
fn set(args: &mut Args, constructor: bool) -> SourceResult<StyleMap>;
|
|
||||||
|
|
||||||
/// List the settable properties.
|
|
||||||
fn properties() -> Vec<ParamInfo>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates that a node cannot be labelled.
|
/// Indicates that a node cannot be labelled.
|
||||||
|
@ -59,7 +59,7 @@ pub fn realize(
|
|||||||
if let Some(node) = target.with::<dyn Finalize>() {
|
if let Some(node) = target.with::<dyn Finalize>() {
|
||||||
if target.is_pristine() {
|
if target.is_pristine() {
|
||||||
if let Some(already) = realized {
|
if let Some(already) = realized {
|
||||||
realized = Some(node.finalize(already));
|
realized = Some(node.finalize(already, styles));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +159,7 @@ pub trait Finalize {
|
|||||||
/// Finalize the fully realized form of the node. Use this for effects that
|
/// Finalize the fully realized form of the node. Use this for effects that
|
||||||
/// should work even in the face of a user-defined show rule, for example
|
/// should work even in the face of a user-defined show rule, for example
|
||||||
/// the linking behaviour of a link node.
|
/// the linking behaviour of a link node.
|
||||||
fn finalize(&self, realized: Content) -> Content;
|
fn finalize(&self, realized: Content, styles: StyleChain) -> Content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Guards content against being affected by the same show rule multiple times.
|
/// Guards content against being affected by the same show rule multiple times.
|
||||||
|
@ -34,11 +34,6 @@ impl StyleMap {
|
|||||||
self.0.push(Style::Property(property));
|
self.0.push(Style::Property(property));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set an inner value for a style property if it is `Some(_)`.
|
|
||||||
pub fn set_opt(&mut self, property: Option<Property>) {
|
|
||||||
self.0.extend(property.map(Style::Property));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the style that was last set.
|
/// Remove the style that was last set.
|
||||||
pub fn unset(&mut self) {
|
pub fn unset(&mut self) {
|
||||||
self.0.pop();
|
self.0.pop();
|
||||||
@ -49,30 +44,11 @@ impl StyleMap {
|
|||||||
self.0.splice(0..0, outer.0.iter().cloned());
|
self.0.splice(0..0, outer.0.iter().cloned());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set an outer style. Like [`chain_one`](StyleChain::chain_one), but
|
|
||||||
/// in-place.
|
|
||||||
pub fn apply_one(&mut self, outer: Style) {
|
|
||||||
self.0.insert(0, outer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mark all contained properties as _scoped_. This means that they only
|
|
||||||
/// apply to the first descendant node (of their type) in the hierarchy and
|
|
||||||
/// not its children, too. This is used by
|
|
||||||
/// [constructors](super::Construct::construct).
|
|
||||||
pub fn scoped(mut self) -> Self {
|
|
||||||
for entry in &mut self.0 {
|
|
||||||
if let Style::Property(property) = entry {
|
|
||||||
property.scoped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an origin span to all contained properties.
|
/// Add an origin span to all contained properties.
|
||||||
pub fn spanned(mut self, span: Span) -> Self {
|
pub fn spanned(mut self, span: Span) -> Self {
|
||||||
for entry in &mut self.0 {
|
for entry in &mut self.0 {
|
||||||
if let Style::Property(property) = entry {
|
if let Style::Property(property) = entry {
|
||||||
property.origin = Some(span);
|
property.span = Some(span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -83,9 +59,8 @@ impl StyleMap {
|
|||||||
pub fn interruption<T: Node>(&self) -> Option<Option<Span>> {
|
pub fn interruption<T: Node>(&self) -> Option<Option<Span>> {
|
||||||
let node = NodeId::of::<T>();
|
let node = NodeId::of::<T>();
|
||||||
self.0.iter().find_map(|entry| match entry {
|
self.0.iter().find_map(|entry| match entry {
|
||||||
Style::Property(property) => property.is_of(node).then(|| property.origin),
|
Style::Property(property) => property.is_of(node).then(|| property.span),
|
||||||
Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)),
|
Style::Recipe(recipe) => recipe.is_of(node).then(|| Some(recipe.span)),
|
||||||
_ => None,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,8 +93,6 @@ pub enum Style {
|
|||||||
Property(Property),
|
Property(Property),
|
||||||
/// A show rule recipe.
|
/// A show rule recipe.
|
||||||
Recipe(Recipe),
|
Recipe(Recipe),
|
||||||
/// A barrier for scoped styles.
|
|
||||||
Barrier(NodeId),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Style {
|
impl Style {
|
||||||
@ -145,7 +118,6 @@ impl Debug for Style {
|
|||||||
match self {
|
match self {
|
||||||
Self::Property(property) => property.fmt(f),
|
Self::Property(property) => property.fmt(f),
|
||||||
Self::Recipe(recipe) => recipe.fmt(f),
|
Self::Recipe(recipe) => recipe.fmt(f),
|
||||||
Self::Barrier(id) => write!(f, "#[Barrier for {id:?}]"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,17 +137,14 @@ pub struct Property {
|
|||||||
name: EcoString,
|
name: EcoString,
|
||||||
/// The property's value.
|
/// The property's value.
|
||||||
value: Value,
|
value: Value,
|
||||||
/// Whether the property should only affect the first node down the
|
|
||||||
/// hierarchy. Used by constructors.
|
|
||||||
scoped: bool,
|
|
||||||
/// The span of the set rule the property stems from.
|
/// The span of the set rule the property stems from.
|
||||||
origin: Option<Span>,
|
span: Option<Span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Property {
|
impl Property {
|
||||||
/// Create a new property from a key-value pair.
|
/// Create a new property from a key-value pair.
|
||||||
pub fn new(node: NodeId, name: EcoString, value: Value) -> Self {
|
pub fn new(node: NodeId, name: EcoString, value: Value) -> Self {
|
||||||
Self { node, name, value, scoped: false, origin: None }
|
Self { node, name, value, span: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this property is the given one.
|
/// Whether this property is the given one.
|
||||||
@ -187,28 +156,11 @@ impl Property {
|
|||||||
pub fn is_of(&self, node: NodeId) -> bool {
|
pub fn is_of(&self, node: NodeId) -> bool {
|
||||||
self.node == node
|
self.node == node
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the property's value as the given type.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn cast<T: Cast>(&self) -> T {
|
|
||||||
self.value.clone().cast().unwrap_or_else(|err| {
|
|
||||||
panic!(
|
|
||||||
"{} (for {}.{} with value {:?})",
|
|
||||||
err,
|
|
||||||
self.node.name(),
|
|
||||||
self.name,
|
|
||||||
self.value
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Property {
|
impl Debug for Property {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "#set {}({}: {:?})", self.node.name(), self.name, self.value)?;
|
write!(f, "#set {}({}: {:?})", self.node.name(), self.name, self.value)?;
|
||||||
if self.scoped {
|
|
||||||
write!(f, " [scoped]")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -381,55 +333,111 @@ impl<'a> StyleChain<'a> {
|
|||||||
|
|
||||||
/// Make the given style the first link of the this chain.
|
/// Make the given style the first link of the this chain.
|
||||||
pub fn chain_one<'b>(&'b self, style: &'b Style) -> StyleChain<'b> {
|
pub fn chain_one<'b>(&'b self, style: &'b Style) -> StyleChain<'b> {
|
||||||
if let Style::Barrier(id) = style {
|
|
||||||
if !self
|
|
||||||
.entries()
|
|
||||||
.filter_map(Style::property)
|
|
||||||
.any(|p| p.scoped && *id == p.node)
|
|
||||||
{
|
|
||||||
return *self;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyleChain {
|
StyleChain {
|
||||||
head: std::slice::from_ref(style),
|
head: std::slice::from_ref(style),
|
||||||
tail: Some(self),
|
tail: Some(self),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cast the first value for the given property in the chain.
|
||||||
|
pub fn get<T: Cast>(
|
||||||
|
self,
|
||||||
|
node: NodeId,
|
||||||
|
name: &'a str,
|
||||||
|
inherent: Option<Value>,
|
||||||
|
default: impl Fn() -> T,
|
||||||
|
) -> T {
|
||||||
|
self.properties::<T>(node, name, inherent)
|
||||||
|
.next()
|
||||||
|
.unwrap_or_else(default)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast the first value for the given property in the chain.
|
||||||
|
pub fn get_resolve<T: Cast + Resolve>(
|
||||||
|
self,
|
||||||
|
node: NodeId,
|
||||||
|
name: &'a str,
|
||||||
|
inherent: Option<Value>,
|
||||||
|
default: impl Fn() -> T,
|
||||||
|
) -> T::Output {
|
||||||
|
self.get(node, name, inherent, default).resolve(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast the first value for the given property in the chain.
|
||||||
|
pub fn get_fold<T: Cast + Fold>(
|
||||||
|
self,
|
||||||
|
node: NodeId,
|
||||||
|
name: &'a str,
|
||||||
|
inherent: Option<Value>,
|
||||||
|
default: impl Fn() -> T::Output,
|
||||||
|
) -> T::Output {
|
||||||
|
fn next<T: Fold>(
|
||||||
|
mut values: impl Iterator<Item = T>,
|
||||||
|
styles: StyleChain,
|
||||||
|
default: &impl Fn() -> T::Output,
|
||||||
|
) -> T::Output {
|
||||||
|
values
|
||||||
|
.next()
|
||||||
|
.map(|value| value.fold(next(values, styles, default)))
|
||||||
|
.unwrap_or_else(|| default())
|
||||||
|
}
|
||||||
|
next(self.properties::<T>(node, name, inherent), self, &default)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast the first value for the given property in the chain.
|
||||||
|
pub fn get_resolve_fold<T>(
|
||||||
|
self,
|
||||||
|
node: NodeId,
|
||||||
|
name: &'a str,
|
||||||
|
inherent: Option<Value>,
|
||||||
|
default: impl Fn() -> <T::Output as Fold>::Output,
|
||||||
|
) -> <T::Output as Fold>::Output
|
||||||
|
where
|
||||||
|
T: Cast + Resolve,
|
||||||
|
T::Output: Fold,
|
||||||
|
{
|
||||||
|
fn next<T>(
|
||||||
|
mut values: impl Iterator<Item = T>,
|
||||||
|
styles: StyleChain,
|
||||||
|
default: &impl Fn() -> <T::Output as Fold>::Output,
|
||||||
|
) -> <T::Output as Fold>::Output
|
||||||
|
where
|
||||||
|
T: Resolve,
|
||||||
|
T::Output: Fold,
|
||||||
|
{
|
||||||
|
values
|
||||||
|
.next()
|
||||||
|
.map(|value| value.resolve(styles).fold(next(values, styles, default)))
|
||||||
|
.unwrap_or_else(|| default())
|
||||||
|
}
|
||||||
|
next(self.properties::<T>(node, name, inherent), self, &default)
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterate over all style recipes in the chain.
|
/// Iterate over all style recipes in the chain.
|
||||||
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
|
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
|
||||||
self.entries().filter_map(Style::recipe)
|
self.entries().filter_map(Style::recipe)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cast the first value for the given property in the chain.
|
|
||||||
pub fn property<T: Cast>(self, node: NodeId, name: &'a str) -> Option<T> {
|
|
||||||
self.properties(node, name).next()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all values for the given property in the chain.
|
/// Iterate over all values for the given property in the chain.
|
||||||
pub fn properties<T: Cast>(
|
pub fn properties<T: Cast + 'a>(
|
||||||
self,
|
self,
|
||||||
node: NodeId,
|
node: NodeId,
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
|
inherent: Option<Value>,
|
||||||
) -> impl Iterator<Item = T> + '_ {
|
) -> impl Iterator<Item = T> + '_ {
|
||||||
let mut barriers = 0;
|
inherent
|
||||||
self.entries().filter_map(move |entry| {
|
.into_iter()
|
||||||
match entry {
|
.chain(
|
||||||
Style::Property(property) => {
|
self.entries()
|
||||||
if property.is(node, name) {
|
.filter_map(Style::property)
|
||||||
if !property.scoped || barriers <= 1 {
|
.filter(move |property| property.is(node, name))
|
||||||
return Some(property.cast());
|
.map(|property| property.value.clone()),
|
||||||
}
|
)
|
||||||
}
|
.map(move |value| {
|
||||||
}
|
value.cast().unwrap_or_else(|err| {
|
||||||
Style::Barrier(id) => {
|
panic!("{} (for {}.{})", err, node.name(), name)
|
||||||
barriers += (*id == node) as usize;
|
})
|
||||||
}
|
})
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over the entries of the chain.
|
/// Iterate over the entries of the chain.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user