Tidy up
This commit is contained in:
parent
96f72eee6c
commit
bf5edbbbbb
@ -70,7 +70,7 @@ impl ContentExt for Content {
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional methods for the style chain.
|
||||
/// Additional methods for style maps.
|
||||
pub trait StyleMapExt {
|
||||
/// Set a font family composed of a preferred family and existing families
|
||||
/// from a style chain.
|
||||
@ -106,10 +106,10 @@ impl LayoutBlock for FillNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut frames = self.child.layout_block(world, regions, styles)?;
|
||||
let mut frames = self.child.layout_block(world, styles, regions)?;
|
||||
for frame in &mut frames {
|
||||
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||
@ -134,10 +134,10 @@ impl LayoutBlock for StrokeNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut frames = self.child.layout_block(world, regions, styles)?;
|
||||
let mut frames = self.child.layout_block(world, styles, regions)?;
|
||||
for frame in &mut frames {
|
||||
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||
|
@ -15,10 +15,10 @@ impl LayoutInline for HideNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
let mut frame = self.0.layout_inline(world, regions, styles)?;
|
||||
let mut frame = self.0.layout_inline(world, styles, regions)?;
|
||||
frame.clear();
|
||||
Ok(frame)
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ impl LayoutInline for ImageNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
let pxw = self.0.width() as f64;
|
||||
let pxh = self.0.height() as f64;
|
||||
|
@ -40,8 +40,8 @@ impl LayoutInline for LineNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
let stroke = styles.get(Self::STROKE).unwrap_or_default();
|
||||
|
||||
|
@ -76,8 +76,8 @@ impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
let mut frame;
|
||||
if let Some(child) = &self.0 {
|
||||
@ -90,7 +90,7 @@ impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
|
||||
let child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||
|
||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||
frame = child.layout_inline(world, &pod, styles)?;
|
||||
frame = child.layout_inline(world, styles, &pod)?;
|
||||
|
||||
// Relayout with full expansion into square region to make sure
|
||||
// the result is really a square or circle.
|
||||
@ -106,7 +106,7 @@ impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
|
||||
|
||||
pod.first = Size::splat(length);
|
||||
pod.expand = Axes::splat(true);
|
||||
frame = child.layout_inline(world, &pod, styles)?;
|
||||
frame = child.layout_inline(world, styles, &pod)?;
|
||||
}
|
||||
} else {
|
||||
// The default size that a shape takes on if it has no child and
|
||||
|
@ -30,21 +30,21 @@ impl LayoutBlock for AlignNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// The child only needs to expand along an axis if there's no alignment.
|
||||
let mut pod = regions.clone();
|
||||
pod.expand &= self.aligns.as_ref().map(Option::is_none);
|
||||
|
||||
// Align paragraphs inside the child.
|
||||
let mut passed = StyleMap::new();
|
||||
let mut map = StyleMap::new();
|
||||
if let Some(align) = self.aligns.x {
|
||||
passed.set(ParNode::ALIGN, HorizontalAlign(align));
|
||||
map.set(ParNode::ALIGN, HorizontalAlign(align));
|
||||
}
|
||||
|
||||
// Layout the child.
|
||||
let mut frames = self.child.layout_block(world, &pod, passed.chain(&styles))?;
|
||||
let mut frames = self.child.layout_block(world, styles.chain(&map), &pod)?;
|
||||
for (region, frame) in regions.iter().zip(&mut frames) {
|
||||
// Align in the target size. The target size depends on whether we
|
||||
// should expand.
|
||||
|
@ -30,13 +30,13 @@ impl LayoutBlock for ColumnsNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// Separating the infinite space into infinite columns does not make
|
||||
// much sense.
|
||||
if !regions.first.x.is_finite() {
|
||||
return self.child.layout_block(world, regions, styles);
|
||||
return self.child.layout_block(world, styles, regions);
|
||||
}
|
||||
|
||||
// Determine the width of the gutter and each column.
|
||||
@ -58,7 +58,7 @@ impl LayoutBlock for ColumnsNode {
|
||||
};
|
||||
|
||||
// Layout the children.
|
||||
let mut frames = self.child.layout_block(world, &pod, styles)?.into_iter();
|
||||
let mut frames = self.child.layout_block(world, styles, &pod)?.into_iter();
|
||||
let mut finished = vec![];
|
||||
|
||||
let dir = styles.get(TextNode::DIR);
|
||||
|
@ -24,8 +24,8 @@ impl LayoutInline for BoxNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
// The "pod" is the region into which the child will be layouted.
|
||||
let pod = {
|
||||
@ -47,7 +47,7 @@ impl LayoutInline for BoxNode {
|
||||
};
|
||||
|
||||
// Layout the child.
|
||||
let mut frame = self.child.layout_inline(world, &pod, styles)?;
|
||||
let mut frame = self.child.layout_inline(world, styles, &pod)?;
|
||||
|
||||
// Ensure frame size matches regions size if expansion is on.
|
||||
let target = regions.expand.select(regions.first, frame.size());
|
||||
@ -91,9 +91,9 @@ impl LayoutBlock for BlockNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
self.0.layout_block(world, regions, styles)
|
||||
self.0.layout_block(world, styles, regions)
|
||||
}
|
||||
}
|
||||
|
@ -18,13 +18,13 @@ impl LayoutBlock for FlowNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut layouter = FlowLayouter::new(regions);
|
||||
|
||||
for (child, map) in self.0.iter() {
|
||||
let styles = map.chain(&styles);
|
||||
let styles = styles.chain(&map);
|
||||
if let Some(&node) = child.to::<VNode>() {
|
||||
layouter.layout_spacing(node.amount, styles);
|
||||
} else if child.has::<dyn LayoutBlock>() {
|
||||
@ -136,7 +136,7 @@ impl FlowLayouter {
|
||||
// aligned later.
|
||||
if let Some(placed) = block.to::<PlaceNode>() {
|
||||
if placed.out_of_flow() {
|
||||
let frame = block.layout_block(world, &self.regions, styles)?.remove(0);
|
||||
let frame = block.layout_block(world, styles, &self.regions)?.remove(0);
|
||||
self.items.push(FlowItem::Placed(frame));
|
||||
return Ok(());
|
||||
}
|
||||
@ -162,11 +162,11 @@ impl FlowLayouter {
|
||||
if !self.last_block_was_par && is_par && !styles.get(ParNode::INDENT).is_zero() {
|
||||
let property = Property::new(ParNode::INDENT, Length::zero());
|
||||
reset = Style::Property(property);
|
||||
chained = reset.chain(&styles);
|
||||
chained = styles.chain_one(&reset);
|
||||
}
|
||||
|
||||
// Layout the block itself.
|
||||
let frames = block.layout_block(world, &self.regions, chained)?;
|
||||
let frames = block.layout_block(world, chained, &self.regions)?;
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
|
@ -37,8 +37,8 @@ impl LayoutBlock for GridNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// Prepare grid layout by unifying content and gutter tracks.
|
||||
let layouter = GridLayouter::new(
|
||||
@ -321,7 +321,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
let frame =
|
||||
cell.layout_block(self.world, &pod, self.styles)?.remove(0);
|
||||
cell.layout_block(self.world, self.styles, &pod)?.remove(0);
|
||||
resolved.set_max(frame.width());
|
||||
}
|
||||
}
|
||||
@ -391,7 +391,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
let mut sizes = cell
|
||||
.layout_block(self.world, &pod, self.styles)?
|
||||
.layout_block(self.world, self.styles, &pod)?
|
||||
.into_iter()
|
||||
.map(|frame| frame.height());
|
||||
|
||||
@ -480,7 +480,7 @@ impl<'a> GridLayouter<'a> {
|
||||
.select(self.regions.base, size);
|
||||
|
||||
let pod = Regions::one(size, base, Axes::splat(true));
|
||||
let frame = cell.layout_block(self.world, &pod, self.styles)?.remove(0);
|
||||
let frame = cell.layout_block(self.world, self.styles, &pod)?.remove(0);
|
||||
output.push_frame(pos, frame);
|
||||
}
|
||||
|
||||
@ -520,7 +520,7 @@ impl<'a> GridLayouter<'a> {
|
||||
}
|
||||
|
||||
// Push the layouted frames into the individual output frames.
|
||||
let frames = cell.layout_block(self.world, &pod, self.styles)?;
|
||||
let frames = cell.layout_block(self.world, self.styles, &pod)?;
|
||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
||||
output.push_frame(pos, frame);
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ use typst::diag::SourceResult;
|
||||
use typst::frame::Frame;
|
||||
use typst::geom::*;
|
||||
use typst::model::{
|
||||
capability, Content, Node, SequenceNode, Style, StyleChain, StyleVecBuilder,
|
||||
StyledNode,
|
||||
applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain,
|
||||
StyleVecBuilder, StyledNode,
|
||||
};
|
||||
use typst::World;
|
||||
|
||||
@ -77,8 +77,8 @@ pub trait LayoutBlock {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>>;
|
||||
}
|
||||
|
||||
@ -87,17 +87,17 @@ impl LayoutBlock for Content {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let scratch = Scratch::default();
|
||||
let (realized, styles) = realize_block(world, &scratch, self, styles)?;
|
||||
let barrier = Style::Barrier(realized.id());
|
||||
let styles = barrier.chain(&styles);
|
||||
let styles = styles.chain_one(&barrier);
|
||||
realized
|
||||
.with::<dyn LayoutBlock>()
|
||||
.unwrap()
|
||||
.layout_block(world, regions, styles)
|
||||
.layout_block(world, styles, regions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -108,8 +108,8 @@ pub trait LayoutInline {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame>;
|
||||
}
|
||||
|
||||
@ -118,22 +118,22 @@ impl LayoutInline for Content {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
assert!(regions.backlog.is_empty());
|
||||
assert!(regions.last.is_none());
|
||||
|
||||
if self.has::<dyn LayoutInline>() && !styles.applicable(self) {
|
||||
if self.has::<dyn LayoutInline>() && !applicable(self, styles) {
|
||||
let barrier = Style::Barrier(self.id());
|
||||
let styles = barrier.chain(&styles);
|
||||
let styles = styles.chain_one(&barrier);
|
||||
return self
|
||||
.with::<dyn LayoutInline>()
|
||||
.unwrap()
|
||||
.layout_inline(world, regions, styles);
|
||||
.layout_inline(world, styles, regions);
|
||||
}
|
||||
|
||||
Ok(self.layout_block(world, regions, styles)?.remove(0))
|
||||
Ok(self.layout_block(world, styles, regions)?.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +237,7 @@ fn realize_root<'a>(
|
||||
content: &'a Content,
|
||||
styles: StyleChain<'a>,
|
||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||
if content.has::<dyn LayoutRoot>() && !styles.applicable(content) {
|
||||
if content.has::<dyn LayoutRoot>() && !applicable(content, styles) {
|
||||
return Ok((content.clone(), styles));
|
||||
}
|
||||
|
||||
@ -255,7 +255,7 @@ fn realize_block<'a>(
|
||||
content: &'a Content,
|
||||
styles: StyleChain<'a>,
|
||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||
if content.has::<dyn LayoutBlock>() && !styles.applicable(content) {
|
||||
if content.has::<dyn LayoutBlock>() && !applicable(content, styles) {
|
||||
return Ok((content.clone(), styles));
|
||||
}
|
||||
|
||||
@ -319,7 +319,7 @@ impl<'a> Builder<'a> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(realized) = styles.show(self.world, content)? {
|
||||
if let Some(realized) = realize(self.world, content, styles)? {
|
||||
let stored = self.scratch.content.alloc(realized);
|
||||
return self.accept(stored, styles);
|
||||
}
|
||||
@ -366,7 +366,7 @@ impl<'a> Builder<'a> {
|
||||
styles: StyleChain<'a>,
|
||||
) -> SourceResult<()> {
|
||||
let stored = self.scratch.styles.alloc(styles);
|
||||
let styles = styled.map.chain(stored);
|
||||
let styles = stored.chain(&styled.map);
|
||||
self.interrupt_style(&styled.map, None)?;
|
||||
self.accept(&styled.sub, styles)?;
|
||||
self.interrupt_style(&styled.map, Some(styles))?;
|
||||
|
@ -29,13 +29,13 @@ impl LayoutBlock for PadNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// Layout child into padded regions.
|
||||
let padding = self.padding.resolve(styles);
|
||||
let pod = regions.map(|size| shrink(size, padding));
|
||||
let mut frames = self.child.layout_block(world, &pod, styles)?;
|
||||
let mut frames = self.child.layout_block(world, styles, &pod)?;
|
||||
|
||||
for frame in &mut frames {
|
||||
// Apply the padding inversely such that the grown size padded
|
||||
|
@ -97,7 +97,7 @@ impl PageNode {
|
||||
|
||||
// Layout the child.
|
||||
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
|
||||
let mut frames = child.layout_block(world, ®ions, styles)?;
|
||||
let mut frames = child.layout_block(world, styles, ®ions)?;
|
||||
|
||||
let header = styles.get(Self::HEADER);
|
||||
let footer = styles.get(Self::FOOTER);
|
||||
@ -118,7 +118,7 @@ impl PageNode {
|
||||
] {
|
||||
if let Some(content) = marginal.resolve(world, page)? {
|
||||
let pod = Regions::one(area, area, Axes::splat(true));
|
||||
let sub = content.layout_block(world, &pod, styles)?.remove(0);
|
||||
let sub = content.layout_block(world, styles, &pod)?.remove(0);
|
||||
if std::ptr::eq(marginal, background) {
|
||||
frame.prepend_frame(pos, sub);
|
||||
} else {
|
||||
@ -402,9 +402,9 @@ papers! {
|
||||
|
||||
// ---------------------------------------------------------------------- //
|
||||
// Other
|
||||
(NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact")
|
||||
(NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner")
|
||||
(NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet")
|
||||
(NEWSPAPER_COMPACT: 280.0, 430.0, "newspaper-compact")
|
||||
(NEWSPAPER_BERLINER: 315.0, 470.0, "newspaper-berliner")
|
||||
(NEWSPAPER_BROADSHEET: 381.0, 578.0, "newspaper-broadsheet")
|
||||
(PRESENTATION_16_9: 297.0, 167.0625, "presentation-16-9")
|
||||
(PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3")
|
||||
(PRESENTATION_4_3: 280.0, 210.0, "presentation-4-3")
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ impl LayoutBlock for PlaceNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let out_of_flow = self.out_of_flow();
|
||||
|
||||
@ -33,7 +33,7 @@ impl LayoutBlock for PlaceNode {
|
||||
Regions::one(regions.base, regions.base, expand)
|
||||
};
|
||||
|
||||
let mut frames = self.0.layout_block(world, &pod, styles)?;
|
||||
let mut frames = self.0.layout_block(world, styles, &pod)?;
|
||||
|
||||
// If expansion is off, zero all sizes so that we don't take up any
|
||||
// space in our parent. Otherwise, respect the expand settings.
|
||||
|
@ -31,8 +31,8 @@ impl LayoutBlock for StackNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
||||
|
||||
@ -189,14 +189,14 @@ impl<'a> StackLayouter<'a> {
|
||||
if let Some(styled) = block.to::<StyledNode>() {
|
||||
let map = &styled.map;
|
||||
if map.contains(ParNode::ALIGN) {
|
||||
return StyleChain::with_root(map).get(ParNode::ALIGN);
|
||||
return StyleChain::new(map).get(ParNode::ALIGN);
|
||||
}
|
||||
}
|
||||
|
||||
self.dir.start().into()
|
||||
});
|
||||
|
||||
let frames = block.layout_block(world, &self.regions, styles)?;
|
||||
let frames = block.layout_block(world, styles, &self.regions)?;
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
// Grow our size, shrink the region and save the frame for later.
|
||||
|
@ -28,10 +28,10 @@ impl LayoutInline for MoveNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
let mut frame = self.child.layout_inline(world, regions, styles)?;
|
||||
let mut frame = self.child.layout_inline(world, styles, regions)?;
|
||||
let delta = self.delta.resolve(styles);
|
||||
let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s));
|
||||
frame.translate(delta.to_point());
|
||||
@ -82,10 +82,10 @@ impl<const T: TransformKind> LayoutInline for TransformNode<T> {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
let mut frame = self.child.layout_inline(world, regions, styles)?;
|
||||
let mut frame = self.child.layout_inline(world, styles, regions)?;
|
||||
|
||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
||||
let Axes { x, y } = origin.zip(frame.size()).map(|(o, s)| o.position(s));
|
||||
|
@ -159,7 +159,7 @@ fn styles() -> StyleMap {
|
||||
/// Construct the standard lang item mapping.
|
||||
fn items() -> LangItems {
|
||||
LangItems {
|
||||
layout: |content, world, styles| content.layout_root(world, styles),
|
||||
layout: |world, content, styles| content.layout_root(world, styles),
|
||||
em: |styles| styles.get(text::TextNode::SIZE),
|
||||
dir: |styles| styles.get(text::TextNode::DIR),
|
||||
space: || text::SpaceNode.pack(),
|
||||
|
@ -4,6 +4,8 @@ mod tex;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
use typst::model::Guard;
|
||||
|
||||
use self::tex::{layout_tex, Texify};
|
||||
use crate::prelude::*;
|
||||
use crate::text::FontFamily;
|
||||
@ -28,21 +30,21 @@ impl MathNode {
|
||||
}
|
||||
|
||||
impl Show for MathNode {
|
||||
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
|
||||
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content {
|
||||
let mut map = StyleMap::new();
|
||||
map.set_family(FontFamily::new("NewComputerModernMath"), styles);
|
||||
|
||||
let mut realized = self
|
||||
.clone()
|
||||
.pack()
|
||||
.guarded(RecipeId::Base(NodeId::of::<Self>()))
|
||||
.guarded(Guard::Base(NodeId::of::<Self>()))
|
||||
.styled_with_map(map);
|
||||
|
||||
if self.display {
|
||||
realized = realized.aligned(Axes::with_x(Some(Align::Center.into())))
|
||||
}
|
||||
|
||||
Ok(realized)
|
||||
realized
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,10 +52,10 @@ impl LayoutInline for MathNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
_: &Regions,
|
||||
styles: StyleChain,
|
||||
_: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
layout_tex(&self.texify(), self.display, world, styles)
|
||||
layout_tex(world, &self.texify(), self.display, styles)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,9 +35,9 @@ impl Texify for Content {
|
||||
|
||||
/// Layout a TeX formula into a frame.
|
||||
pub fn layout_tex(
|
||||
world: Tracked<dyn World>,
|
||||
tex: &str,
|
||||
display: bool,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Frame> {
|
||||
// Load the font.
|
||||
|
@ -16,8 +16,8 @@ pub use typst::geom::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::model::{
|
||||
array, capability, castable, dict, dynamic, format_str, node, Args, Array, Cast,
|
||||
Content, Dict, Finalize, Fold, Func, Node, NodeId, RecipeId, Resolve, Show, Smart,
|
||||
Str, StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm,
|
||||
Content, Dict, Finalize, Fold, Func, Node, NodeId, Resolve, Show, Smart, Str,
|
||||
StyleChain, StyleMap, StyleVec, Unlabellable, Value, Vm,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::syntax::{Span, Spanned};
|
||||
|
@ -18,7 +18,7 @@ impl LayoutRoot for DocNode {
|
||||
let mut frames = vec![];
|
||||
for (page, map) in self.0.iter() {
|
||||
let number = 1 + frames.len();
|
||||
frames.extend(page.layout(world, number, map.chain(&styles))?);
|
||||
frames.extend(page.layout(world, number, styles.chain(map))?);
|
||||
}
|
||||
Ok(frames)
|
||||
}
|
||||
|
@ -34,18 +34,13 @@ impl HeadingNode {
|
||||
}
|
||||
|
||||
impl Show for HeadingNode {
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(BlockNode(self.body.clone()).pack())
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content {
|
||||
BlockNode(self.body.clone()).pack()
|
||||
}
|
||||
}
|
||||
|
||||
impl Finalize for HeadingNode {
|
||||
fn finalize(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
_: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
fn finalize(&self, realized: Content) -> Content {
|
||||
let scale = match self.level.get() {
|
||||
1 => 1.4,
|
||||
2 => 1.2,
|
||||
@ -61,7 +56,6 @@ impl Finalize for HeadingNode {
|
||||
map.set(TextNode::WEIGHT, FontWeight::BOLD);
|
||||
map.set(BlockNode::ABOVE, VNode::block_around(above.into()));
|
||||
map.set(BlockNode::BELOW, VNode::block_around(below.into()));
|
||||
|
||||
Ok(realized.styled_with_map(map))
|
||||
realized.styled_with_map(map)
|
||||
}
|
||||
}
|
||||
|
@ -81,8 +81,8 @@ impl<const L: ListKind> LayoutBlock for ListNode<L> {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let mut cells = vec![];
|
||||
let mut number = 1;
|
||||
@ -139,7 +139,7 @@ impl<const L: ListKind> LayoutBlock for ListNode<L> {
|
||||
gutter: Axes::with_y(vec![gutter.into()]),
|
||||
cells,
|
||||
}
|
||||
.layout_block(world, regions, styles)
|
||||
.layout_block(world, styles, regions)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ impl RefNode {
|
||||
}
|
||||
|
||||
impl Show for RefNode {
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(TextNode::packed(format_eco!("@{}", self.0)))
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content {
|
||||
TextNode::packed(format_eco!("@{}", self.0))
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +54,8 @@ impl LayoutBlock for TableNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
let fill = styles.get(Self::FILL);
|
||||
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
|
||||
@ -89,7 +89,7 @@ impl LayoutBlock for TableNode {
|
||||
gutter: self.gutter.clone(),
|
||||
cells,
|
||||
}
|
||||
.layout_block(world, regions, styles)
|
||||
.layout_block(world, styles, regions)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +47,8 @@ impl<const L: DecoLine> DecoNode<L> {
|
||||
}
|
||||
|
||||
impl<const L: DecoLine> Show for DecoNode<L> {
|
||||
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.0.clone().styled(
|
||||
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content {
|
||||
self.0.clone().styled(
|
||||
TextNode::DECO,
|
||||
Decoration {
|
||||
line: L,
|
||||
@ -57,7 +57,7 @@ impl<const L: DecoLine> Show for DecoNode<L> {
|
||||
extent: styles.get(Self::EXTENT),
|
||||
evade: styles.get(Self::EVADE),
|
||||
},
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,18 +54,13 @@ impl LinkNode {
|
||||
}
|
||||
|
||||
impl Show for LinkNode {
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body.clone())
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content {
|
||||
self.body.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Finalize for LinkNode {
|
||||
fn finalize(
|
||||
&self,
|
||||
_: Tracked<dyn World>,
|
||||
_: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content> {
|
||||
Ok(realized.styled(Self::DEST, Some(self.dest.clone())))
|
||||
fn finalize(&self, realized: Content) -> Content {
|
||||
realized.styled(Self::DEST, Some(self.dest.clone()))
|
||||
}
|
||||
}
|
||||
|
@ -527,8 +527,8 @@ impl StrongNode {
|
||||
}
|
||||
|
||||
impl Show for StrongNode {
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.0.clone().styled(TextNode::BOLD, Toggle))
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content {
|
||||
self.0.clone().styled(TextNode::BOLD, Toggle)
|
||||
}
|
||||
}
|
||||
|
||||
@ -551,8 +551,8 @@ impl EmphNode {
|
||||
}
|
||||
|
||||
impl Show for EmphNode {
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.0.clone().styled(TextNode::ITALIC, Toggle))
|
||||
fn show(&self, _: Tracked<dyn World>, _: StyleChain) -> Content {
|
||||
self.0.clone().styled(TextNode::ITALIC, Toggle)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,8 +47,8 @@ impl LayoutBlock for ParNode {
|
||||
fn layout_block(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
// Collect all text into one string for BiDi analysis.
|
||||
let (text, segments) = collect(self, &styles);
|
||||
@ -59,10 +59,10 @@ impl LayoutBlock for ParNode {
|
||||
let p = prepare(world, self, &text, segments, regions, styles)?;
|
||||
|
||||
// Break the paragraph into lines.
|
||||
let lines = linebreak(&p, world, regions.first.x);
|
||||
let lines = linebreak(&p, regions.first.x);
|
||||
|
||||
// Stack the lines into one frame per region.
|
||||
stack(&p, world, &lines, regions)
|
||||
stack(&p, &lines, regions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,10 +141,10 @@ impl LayoutInline for RepeatNode {
|
||||
fn layout_inline(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
regions: &Regions,
|
||||
styles: StyleChain,
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Frame> {
|
||||
self.0.layout_inline(world, regions, styles)
|
||||
self.0.layout_inline(world, styles, regions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,6 +163,8 @@ const NODE_REPLACE: char = '\u{FFFC}'; // Object Replacement Character
|
||||
/// Only when a line break falls onto a text index that is not safe-to-break per
|
||||
/// rustybuzz, we have to reshape that portion.
|
||||
struct Preparation<'a> {
|
||||
/// The compilation environment.
|
||||
world: Tracked<'a, dyn World>,
|
||||
/// Bidirectional text embedding levels for the paragraph.
|
||||
bidi: BidiInfo<'a>,
|
||||
/// Text runs, spacing and layouted nodes.
|
||||
@ -416,7 +418,7 @@ fn collect<'a>(
|
||||
}
|
||||
|
||||
while let Some((child, map)) = iter.next() {
|
||||
let styles = map.chain(styles);
|
||||
let styles = styles.chain(map);
|
||||
let segment = if child.is::<SpaceNode>() {
|
||||
full.push(' ');
|
||||
Segment::Text(1)
|
||||
@ -487,7 +489,7 @@ fn collect<'a>(
|
||||
/// Prepare paragraph layout by shaping the whole paragraph and layouting all
|
||||
/// contained inline-level content.
|
||||
fn prepare<'a>(
|
||||
world: Tracked<dyn World>,
|
||||
world: Tracked<'a, dyn World>,
|
||||
par: &'a ParNode,
|
||||
text: &'a str,
|
||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||
@ -528,7 +530,7 @@ fn prepare<'a>(
|
||||
} else {
|
||||
let size = Size::new(regions.first.x, regions.base.y);
|
||||
let pod = Regions::one(size, regions.base, Axes::splat(false));
|
||||
let mut frame = inline.layout_inline(world, &pod, styles)?;
|
||||
let mut frame = inline.layout_inline(world, styles, &pod)?;
|
||||
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
||||
items.push(Item::Frame(frame));
|
||||
}
|
||||
@ -539,6 +541,7 @@ fn prepare<'a>(
|
||||
}
|
||||
|
||||
Ok(Preparation {
|
||||
world,
|
||||
bidi,
|
||||
items,
|
||||
styles,
|
||||
@ -618,11 +621,7 @@ fn shared_get<'a, K: Key>(
|
||||
}
|
||||
|
||||
/// Find suitable linebreaks.
|
||||
fn linebreak<'a>(
|
||||
p: &'a Preparation<'a>,
|
||||
world: Tracked<dyn World>,
|
||||
width: Abs,
|
||||
) -> Vec<Line<'a>> {
|
||||
fn linebreak<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
let linebreaks = p.styles.get(ParNode::LINEBREAKS).unwrap_or_else(|| {
|
||||
if p.styles.get(ParNode::JUSTIFY) {
|
||||
Linebreaks::Optimized
|
||||
@ -632,26 +631,22 @@ fn linebreak<'a>(
|
||||
});
|
||||
|
||||
match linebreaks {
|
||||
Linebreaks::Simple => linebreak_simple(p, world, width),
|
||||
Linebreaks::Optimized => linebreak_optimized(p, world, width),
|
||||
Linebreaks::Simple => linebreak_simple(p, width),
|
||||
Linebreaks::Optimized => linebreak_optimized(p, width),
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform line breaking in simple first-fit style. This means that we build
|
||||
/// lines greedily, always taking the longest possible line. This may lead to
|
||||
/// very unbalanced line, but is fast and simple.
|
||||
fn linebreak_simple<'a>(
|
||||
p: &'a Preparation<'a>,
|
||||
world: Tracked<dyn World>,
|
||||
width: Abs,
|
||||
) -> Vec<Line<'a>> {
|
||||
fn linebreak_simple<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
let mut lines = vec![];
|
||||
let mut start = 0;
|
||||
let mut last = None;
|
||||
|
||||
for (end, mandatory, hyphen) in breakpoints(p) {
|
||||
// Compute the line and its size.
|
||||
let mut attempt = line(p, world, start..end, mandatory, hyphen);
|
||||
let mut attempt = line(p, start..end, mandatory, hyphen);
|
||||
|
||||
// If the line doesn't fit anymore, we push the last fitting attempt
|
||||
// into the stack and rebuild the line from the attempt's end. The
|
||||
@ -660,7 +655,7 @@ fn linebreak_simple<'a>(
|
||||
if let Some((last_attempt, last_end)) = last.take() {
|
||||
lines.push(last_attempt);
|
||||
start = last_end;
|
||||
attempt = line(p, world, start..end, mandatory, hyphen);
|
||||
attempt = line(p, start..end, mandatory, hyphen);
|
||||
}
|
||||
}
|
||||
|
||||
@ -700,11 +695,7 @@ fn linebreak_simple<'a>(
|
||||
/// computed and stored in dynamic programming table) is minimal. The final
|
||||
/// result is simply the layout determined for the last breakpoint at the end of
|
||||
/// text.
|
||||
fn linebreak_optimized<'a>(
|
||||
p: &'a Preparation<'a>,
|
||||
world: Tracked<dyn World>,
|
||||
width: Abs,
|
||||
) -> Vec<Line<'a>> {
|
||||
fn linebreak_optimized<'a>(p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
/// The cost of a line or paragraph layout.
|
||||
type Cost = f64;
|
||||
|
||||
@ -727,7 +718,7 @@ fn linebreak_optimized<'a>(
|
||||
let mut table = vec![Entry {
|
||||
pred: 0,
|
||||
total: 0.0,
|
||||
line: line(p, world, 0..0, false, false),
|
||||
line: line(p, 0..0, false, false),
|
||||
}];
|
||||
|
||||
let em = p.styles.get(TextNode::SIZE);
|
||||
@ -741,7 +732,7 @@ fn linebreak_optimized<'a>(
|
||||
for (i, pred) in table.iter_mut().enumerate().skip(active) {
|
||||
// Layout the line.
|
||||
let start = pred.line.end;
|
||||
let attempt = line(p, world, start..end, mandatory, hyphen);
|
||||
let attempt = line(p, start..end, mandatory, hyphen);
|
||||
|
||||
// Determine how much the line's spaces would need to be stretched
|
||||
// to make it the desired width.
|
||||
@ -915,7 +906,6 @@ impl Breakpoints<'_> {
|
||||
/// Create a line which spans the given range.
|
||||
fn line<'a>(
|
||||
p: &'a Preparation,
|
||||
world: Tracked<dyn World>,
|
||||
mut range: Range,
|
||||
mandatory: bool,
|
||||
hyphen: bool,
|
||||
@ -970,9 +960,9 @@ fn line<'a>(
|
||||
if hyphen || start + shaped.text.len() > range.end {
|
||||
if hyphen || start < range.end || before.is_empty() {
|
||||
let shifted = start - base..range.end - base;
|
||||
let mut reshaped = shaped.reshape(world, shifted);
|
||||
let mut reshaped = shaped.reshape(p.world, shifted);
|
||||
if hyphen || shy {
|
||||
reshaped.push_hyphen(world);
|
||||
reshaped.push_hyphen(p.world);
|
||||
}
|
||||
width += reshaped.width;
|
||||
last = Some(Item::Text(reshaped));
|
||||
@ -993,7 +983,7 @@ fn line<'a>(
|
||||
if range.start + shaped.text.len() > end {
|
||||
if range.start < end {
|
||||
let shifted = range.start - base..end - base;
|
||||
let reshaped = shaped.reshape(world, shifted);
|
||||
let reshaped = shaped.reshape(p.world, shifted);
|
||||
width += reshaped.width;
|
||||
first = Some(Item::Text(reshaped));
|
||||
}
|
||||
@ -1021,12 +1011,7 @@ fn line<'a>(
|
||||
}
|
||||
|
||||
/// Combine layouted lines into one frame per region.
|
||||
fn stack(
|
||||
p: &Preparation,
|
||||
world: Tracked<dyn World>,
|
||||
lines: &[Line],
|
||||
regions: &Regions,
|
||||
) -> SourceResult<Vec<Frame>> {
|
||||
fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult<Vec<Frame>> {
|
||||
// Determine the paragraph's width: Full width of the region if we
|
||||
// should expand or there's fractional spacing, fit-to-width otherwise.
|
||||
let mut width = regions.first.x;
|
||||
@ -1042,7 +1027,7 @@ fn stack(
|
||||
|
||||
// Stack the lines into one frame per region.
|
||||
for line in lines {
|
||||
let frame = commit(p, world, line, ®ions, width)?;
|
||||
let frame = commit(p, line, ®ions, width)?;
|
||||
let height = frame.size().y;
|
||||
|
||||
while !regions.first.y.fits(height) && !regions.in_last() {
|
||||
@ -1071,7 +1056,6 @@ fn stack(
|
||||
/// Commit to a line and build its frame.
|
||||
fn commit(
|
||||
p: &Preparation,
|
||||
world: Tracked<dyn World>,
|
||||
line: &Line,
|
||||
regions: &Regions,
|
||||
width: Abs,
|
||||
@ -1142,7 +1126,7 @@ fn commit(
|
||||
offset += v.share(fr, remaining);
|
||||
}
|
||||
Item::Text(shaped) => {
|
||||
let frame = shaped.build(world, justification);
|
||||
let frame = shaped.build(p.world, justification);
|
||||
push(&mut offset, frame);
|
||||
}
|
||||
Item::Frame(frame) => {
|
||||
@ -1153,7 +1137,7 @@ fn commit(
|
||||
let fill = Fr::one().share(fr, remaining);
|
||||
let size = Size::new(fill, regions.base.y);
|
||||
let pod = Regions::one(size, regions.base, Axes::new(false, false));
|
||||
let frame = repeat.layout_inline(world, &pod, *styles)?;
|
||||
let frame = repeat.layout_inline(p.world, *styles, &pod)?;
|
||||
let width = frame.width();
|
||||
let count = (fill / width).floor();
|
||||
let remaining = fill % width;
|
||||
|
@ -43,7 +43,7 @@ impl RawNode {
|
||||
}
|
||||
|
||||
impl Show for RawNode {
|
||||
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> SourceResult<Content> {
|
||||
fn show(&self, _: Tracked<dyn World>, styles: StyleChain) -> Content {
|
||||
let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
|
||||
let foreground = THEME
|
||||
.settings
|
||||
@ -96,7 +96,7 @@ impl Show for RawNode {
|
||||
map.set(TextNode::SMART_QUOTES, false);
|
||||
map.set_family(FontFamily::new("IBM Plex Mono"), styles);
|
||||
|
||||
Ok(realized.styled_with_map(map))
|
||||
realized.styled_with_map(map)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,11 +43,7 @@ impl<const S: ShiftKind> ShiftNode<S> {
|
||||
}
|
||||
|
||||
impl<const S: ShiftKind> Show for ShiftNode<S> {
|
||||
fn show(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Content> {
|
||||
fn show(&self, world: Tracked<dyn World>, styles: StyleChain) -> Content {
|
||||
let mut transformed = None;
|
||||
if styles.get(Self::TYPOGRAPHIC) {
|
||||
if let Some(text) = search_text(&self.0, S) {
|
||||
@ -57,12 +53,12 @@ impl<const S: ShiftKind> Show for ShiftNode<S> {
|
||||
}
|
||||
};
|
||||
|
||||
Ok(transformed.unwrap_or_else(|| {
|
||||
transformed.unwrap_or_else(|| {
|
||||
let mut map = StyleMap::new();
|
||||
map.set(TextNode::BASELINE, styles.get(Self::BASELINE));
|
||||
map.set(TextNode::SIZE, styles.get(Self::SIZE));
|
||||
self.0.clone().styled_with_map(map)
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,7 @@ use super::*;
|
||||
/// Expand the `#[node]` macro.
|
||||
pub fn expand(attr: TokenStream, body: syn::ItemImpl) -> Result<TokenStream> {
|
||||
let node = prepare(attr, body)?;
|
||||
let scope = create(&node)?;
|
||||
Ok(quote! {
|
||||
const _: () = { #scope };
|
||||
})
|
||||
create(&node)
|
||||
}
|
||||
|
||||
/// Details about a node.
|
||||
@ -236,6 +233,7 @@ fn create(node: &Node) -> Result<TokenStream> {
|
||||
|
||||
let mut modules: Vec<syn::ItemMod> = vec![];
|
||||
let mut items: Vec<syn::ImplItem> = vec![];
|
||||
let scope = quote::format_ident!("__{}_keys", node.self_name);
|
||||
|
||||
for property in &node.properties {
|
||||
let (key, module) = create_property_module(node, &property);
|
||||
@ -246,7 +244,8 @@ fn create(node: &Node) -> Result<TokenStream> {
|
||||
let vis = &property.vis;
|
||||
items.push(parse_quote! {
|
||||
#(#attrs)*
|
||||
#vis const #name: #name::#key = #name::Key(::std::marker::PhantomData);
|
||||
#vis const #name: #scope::#name::#key
|
||||
= #scope::#name::Key(::std::marker::PhantomData);
|
||||
});
|
||||
}
|
||||
|
||||
@ -255,8 +254,11 @@ fn create(node: &Node) -> Result<TokenStream> {
|
||||
|
||||
Ok(quote! {
|
||||
#body
|
||||
#node_impl
|
||||
#(#modules)*
|
||||
mod #scope {
|
||||
use super::*;
|
||||
#node_impl
|
||||
#(#modules)*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -428,7 +430,7 @@ fn create_property_module(node: &Node, property: &Property) -> (syn::Type, syn::
|
||||
// Generate the module code.
|
||||
let module = parse_quote! {
|
||||
#[allow(non_snake_case)]
|
||||
mod #name { #scope }
|
||||
pub mod #name { #scope }
|
||||
};
|
||||
|
||||
(key, module)
|
||||
|
@ -100,7 +100,7 @@ pub enum Tracepoint {
|
||||
/// A function call.
|
||||
Call(Option<EcoString>),
|
||||
/// A show rule application.
|
||||
Apply(EcoString),
|
||||
Show(EcoString),
|
||||
/// A module import.
|
||||
Import,
|
||||
}
|
||||
@ -114,7 +114,7 @@ impl Display for Tracepoint {
|
||||
Tracepoint::Call(None) => {
|
||||
write!(f, "error occured in this function call")
|
||||
}
|
||||
Tracepoint::Apply(name) => {
|
||||
Tracepoint::Show(name) => {
|
||||
write!(f, "error occured while applying show rule to this {name}")
|
||||
}
|
||||
Tracepoint::Import => {
|
||||
|
@ -14,7 +14,7 @@ use crate::geom::{
|
||||
};
|
||||
use crate::image::{DecodedImage, Image};
|
||||
|
||||
/// Export a frame into a rendered image.
|
||||
/// Export a frame into a raster image.
|
||||
///
|
||||
/// This renders the frame at the given number of pixels per printer's point and
|
||||
/// returns the resulting `tiny-skia` pixel buffer.
|
||||
|
26
src/lib.rs
26
src/lib.rs
@ -1,21 +1,21 @@
|
||||
//! The compiler for the _Typst_ typesetting language.
|
||||
//! The compiler for the _Typst_ markup language.
|
||||
//!
|
||||
//! # Steps
|
||||
//! - **Parsing:** The parsing step first transforms a plain string into an
|
||||
//! - **Parsing:** The compiler first transforms a plain string into an
|
||||
//! [iterator of tokens][tokens]. This token stream is [parsed] into a [syntax
|
||||
//! tree]. The tree itself is untyped, but the [AST] module provides a typed
|
||||
//! layer over it.
|
||||
//! - **Evaluation:** The next step is to [evaluate] the markup. This produces a
|
||||
//! [module], consisting of a scope of values that were exported by the code
|
||||
//! and [content], a hierarchical, styled representation of the text,
|
||||
//! structure, layouts, etc. of the module. The nodes of the content tree are
|
||||
//! well structured and order-independent and thus much better suited for
|
||||
//! layouting than the raw markup.
|
||||
//! - **Layouting:** Next, the content is layouted into a portable version of
|
||||
//! the typeset document. The output of this is a collection of [`Frame`]s
|
||||
//! (one per page), ready for exporting.
|
||||
//! - **Exporting:** The finished layout can be exported into a supported
|
||||
//! format. Currently, the only supported output format is [PDF].
|
||||
//! and [content], a hierarchical, styled representation of what was written
|
||||
//! in the source file. The nodes of the content tree are well structured and
|
||||
//! order-independent and thus much better suited for further processing than
|
||||
//! the raw markup.
|
||||
//! - **Typesetting:** Next, the content is [typeset] into a collection of
|
||||
//! [`Frame`]s (one per page) with elements and fixed positions, ready for
|
||||
//! exporting.
|
||||
//! - **Exporting:** These frames can finally be exported into an output format
|
||||
//! (currently supported are [PDF] and [raster images]).
|
||||
//!
|
||||
//! [tokens]: syntax::Tokens
|
||||
//! [parsed]: syntax::parse
|
||||
@ -24,7 +24,9 @@
|
||||
//! [evaluate]: model::eval
|
||||
//! [module]: model::Module
|
||||
//! [content]: model::Content
|
||||
//! [typeset]: model::typeset
|
||||
//! [PDF]: export::pdf
|
||||
//! [raster images]: export::render
|
||||
|
||||
extern crate self as typst;
|
||||
|
||||
@ -73,7 +75,7 @@ pub fn compile(
|
||||
/// The environment in which typesetting occurs.
|
||||
#[comemo::track]
|
||||
pub trait World {
|
||||
/// The compilation root.
|
||||
/// The path relative to which absolute paths are.
|
||||
fn root(&self) -> &Path;
|
||||
|
||||
/// The standard library.
|
||||
|
@ -9,9 +9,7 @@ use comemo::Tracked;
|
||||
use siphasher::sip128::{Hasher128, SipHasher};
|
||||
use typst_macros::node;
|
||||
|
||||
use super::{
|
||||
Args, Key, Property, Recipe, RecipeId, Style, StyleMap, Unlabellable, Value, Vm,
|
||||
};
|
||||
use super::{capability, Args, Guard, Key, Property, Recipe, Style, StyleMap, Value, Vm};
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::syntax::Span;
|
||||
use crate::util::{EcoString, ReadableTypeId};
|
||||
@ -21,7 +19,7 @@ use crate::World;
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Content {
|
||||
obj: Arc<dyn Bounds>,
|
||||
guards: Vec<RecipeId>,
|
||||
guards: Vec<Guard>,
|
||||
span: Option<Span>,
|
||||
label: Option<EcoString>,
|
||||
}
|
||||
@ -69,11 +67,11 @@ impl Content {
|
||||
/// Style this content with a style entry.
|
||||
pub fn styled_with_entry(mut self, style: Style) -> Self {
|
||||
if let Some(styled) = self.to_mut::<StyledNode>() {
|
||||
styled.map.apply(style);
|
||||
styled.map.apply_one(style);
|
||||
self
|
||||
} else if let Some(styled) = self.to::<StyledNode>() {
|
||||
let mut map = styled.map.clone();
|
||||
map.apply(style);
|
||||
map.apply_one(style);
|
||||
StyledNode { sub: styled.sub.clone(), map }.pack()
|
||||
} else {
|
||||
StyledNode { sub: self, map: style.into() }.pack()
|
||||
@ -87,7 +85,7 @@ impl Content {
|
||||
}
|
||||
|
||||
if let Some(styled) = self.to_mut::<StyledNode>() {
|
||||
styled.map.apply_map(&styles);
|
||||
styled.map.apply(styles);
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -101,7 +99,7 @@ impl Content {
|
||||
recipe: Recipe,
|
||||
) -> SourceResult<Self> {
|
||||
if recipe.selector.is_none() {
|
||||
recipe.transform.apply(world, recipe.span, self)
|
||||
recipe.apply(world, self)
|
||||
} else {
|
||||
Ok(self.styled_with_entry(Style::Recipe(recipe)))
|
||||
}
|
||||
@ -185,7 +183,7 @@ impl Content {
|
||||
|
||||
/// Disable a show rule recipe.
|
||||
#[doc(hidden)]
|
||||
pub fn guarded(mut self, id: RecipeId) -> Self {
|
||||
pub fn guarded(mut self, id: Guard) -> Self {
|
||||
self.guards.push(id);
|
||||
self
|
||||
}
|
||||
@ -201,7 +199,7 @@ impl Content {
|
||||
}
|
||||
|
||||
/// Check whether a show rule recipe is disabled.
|
||||
pub(super) fn is_guarded(&self, id: RecipeId) -> bool {
|
||||
pub(super) fn is_guarded(&self, id: Guard) -> bool {
|
||||
self.guards.contains(&id)
|
||||
}
|
||||
|
||||
@ -378,12 +376,12 @@ pub trait Node: 'static + Capable {
|
||||
fn field(&self, name: &str) -> Option<Value>;
|
||||
}
|
||||
|
||||
/// A unique identifier for a node.
|
||||
/// A unique identifier for a node type.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct NodeId(ReadableTypeId);
|
||||
|
||||
impl NodeId {
|
||||
/// The id of the given node.
|
||||
/// The id of the given node type.
|
||||
pub fn of<T: 'static>() -> Self {
|
||||
Self(ReadableTypeId::of::<T>())
|
||||
}
|
||||
@ -397,7 +395,8 @@ impl Debug for NodeId {
|
||||
|
||||
/// A capability a node can have.
|
||||
///
|
||||
/// This is implemented by trait objects.
|
||||
/// Should be implemented by trait objects that are accessible through
|
||||
/// [`Capable`].
|
||||
pub trait Capability: 'static {}
|
||||
|
||||
/// Dynamically access a trait implementation at runtime.
|
||||
@ -406,3 +405,7 @@ pub unsafe trait Capable {
|
||||
/// if `self` implements the trait.
|
||||
fn vtable(&self, of: TypeId) -> Option<*const ()>;
|
||||
}
|
||||
|
||||
/// Indicates that a node cannot be labelled.
|
||||
#[capability]
|
||||
pub trait Unlabellable {}
|
||||
|
@ -104,7 +104,7 @@ impl Dict {
|
||||
self.0.values().cloned().collect()
|
||||
}
|
||||
|
||||
/// Transform each pair in the array with a function.
|
||||
/// Transform each pair in the dictionary with a function.
|
||||
pub fn map(&self, vm: &Vm, f: Spanned<Func>) -> SourceResult<Array> {
|
||||
self.iter()
|
||||
.map(|(key, value)| {
|
||||
|
@ -2,19 +2,22 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use comemo::{Track, Tracked};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::{
|
||||
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Flow, Func,
|
||||
Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value, Vm,
|
||||
methods, ops, Arg, Args, Array, CapturesVisitor, Closure, Content, Dict, Func,
|
||||
LangItems, Recipe, Scope, Scopes, Selector, StyleMap, Transform, Value,
|
||||
};
|
||||
use crate::diag::{
|
||||
bail, error, At, SourceError, SourceResult, StrResult, Trace, Tracepoint,
|
||||
};
|
||||
use crate::diag::{bail, error, At, SourceResult, StrResult, Trace, Tracepoint};
|
||||
use crate::geom::{Abs, Angle, Em, Fr, Ratio};
|
||||
use crate::syntax::ast::AstNode;
|
||||
use crate::syntax::{ast, Source, SourceId, Span, Spanned, Unit};
|
||||
use crate::util::{format_eco, EcoString};
|
||||
use crate::util::{format_eco, EcoString, PathExt};
|
||||
use crate::World;
|
||||
|
||||
/// Evaluate a source file and return the resulting module.
|
||||
@ -54,6 +57,94 @@ pub fn eval(
|
||||
Ok(Module { scope: vm.scopes.top, content: result? })
|
||||
}
|
||||
|
||||
/// A virtual machine.
|
||||
///
|
||||
/// Holds the state needed to [evaluate](super::eval()) Typst sources. A new
|
||||
/// virtual machine is created for each module evaluation and function call.
|
||||
pub struct Vm<'a> {
|
||||
/// The compilation environment.
|
||||
pub(super) world: Tracked<'a, dyn World>,
|
||||
/// The language items.
|
||||
pub(super) items: LangItems,
|
||||
/// The route of source ids the VM took to reach its current location.
|
||||
pub(super) route: Tracked<'a, Route>,
|
||||
/// The current location.
|
||||
pub(super) location: SourceId,
|
||||
/// A control flow event that is currently happening.
|
||||
pub(super) flow: Option<Flow>,
|
||||
/// The stack of scopes.
|
||||
pub(super) scopes: Scopes<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
/// Create a new virtual machine.
|
||||
pub fn new(
|
||||
world: Tracked<'a, dyn World>,
|
||||
route: Tracked<'a, Route>,
|
||||
location: SourceId,
|
||||
scopes: Scopes<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
world,
|
||||
items: world.library().items.clone(),
|
||||
route,
|
||||
location,
|
||||
flow: None,
|
||||
scopes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the underlying world.
|
||||
pub fn world(&self) -> Tracked<dyn World> {
|
||||
self.world
|
||||
}
|
||||
|
||||
/// Resolve a user-entered path to be relative to the compilation
|
||||
/// environment's root.
|
||||
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
||||
if !self.location.is_detached() {
|
||||
if let Some(path) = path.strip_prefix('/') {
|
||||
return Ok(self.world.root().join(path).normalize());
|
||||
}
|
||||
|
||||
if let Some(dir) = self.world.source(self.location).path().parent() {
|
||||
return Ok(dir.join(path).normalize());
|
||||
}
|
||||
}
|
||||
|
||||
Err("cannot access file system from here".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A control flow event that occurred during evaluation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Flow {
|
||||
/// Stop iteration in a loop.
|
||||
Break(Span),
|
||||
/// Skip the remainder of the current iteration in a loop.
|
||||
Continue(Span),
|
||||
/// Stop execution of a function early, optionally returning an explicit
|
||||
/// value.
|
||||
Return(Span, Option<Value>),
|
||||
}
|
||||
|
||||
impl Flow {
|
||||
/// Return an error stating that this control flow is forbidden.
|
||||
pub fn forbidden(&self) -> SourceError {
|
||||
match *self {
|
||||
Self::Break(span) => {
|
||||
error!(span, "cannot break outside of loop")
|
||||
}
|
||||
Self::Continue(span) => {
|
||||
error!(span, "cannot continue outside of loop")
|
||||
}
|
||||
Self::Return(span, _) => {
|
||||
error!(span, "cannot return outside of function")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A route of source ids.
|
||||
#[derive(Default)]
|
||||
pub struct Route {
|
||||
@ -87,7 +178,7 @@ impl Route {
|
||||
}
|
||||
}
|
||||
|
||||
/// An evaluated module, ready for importing or layouting.
|
||||
/// An evaluated module, ready for importing or typesetting.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Module {
|
||||
/// The top-level definitions that were bound in this module.
|
||||
@ -700,18 +791,19 @@ impl Eval for ast::MethodCall {
|
||||
fn eval(&self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let span = self.span();
|
||||
let method = self.method().take();
|
||||
let point = || Tracepoint::Call(Some(method.clone()));
|
||||
|
||||
Ok(if methods::is_mutating(&method) {
|
||||
let result = if methods::is_mutating(&method) {
|
||||
let args = self.args().eval(vm)?;
|
||||
let value = self.target().access(vm)?;
|
||||
methods::call_mut(value, &method, args, span).trace(vm.world, point, span)?;
|
||||
Value::None
|
||||
methods::call_mut(value, &method, args, span).map(|()| Value::None)
|
||||
} else {
|
||||
let value = self.target().eval(vm)?;
|
||||
let args = self.args().eval(vm)?;
|
||||
methods::call(vm, value, &method, args, span).trace(vm.world, point, span)?
|
||||
})
|
||||
methods::call(vm, value, &method, args, span)
|
||||
};
|
||||
|
||||
let point = || Tracepoint::Call(Some(method.clone()));
|
||||
result.trace(vm.world, point, span)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ impl Func {
|
||||
}
|
||||
|
||||
/// Create a new function from a closure.
|
||||
pub fn from_closure(closure: Closure) -> Self {
|
||||
pub(super) fn from_closure(closure: Closure) -> Self {
|
||||
Self(Arc::new(Repr::Closure(closure)))
|
||||
}
|
||||
|
||||
@ -127,7 +127,8 @@ impl Func {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a selector from this node and the given arguments.
|
||||
/// Create a selector for this function's node type, filtering by node's
|
||||
/// whose [fields](super::Content::field) match the given arguments.
|
||||
pub fn where_(self, args: &mut Args) -> StrResult<Selector> {
|
||||
match self.0.as_ref() {
|
||||
Repr::Native(Native { node: Some(id), .. }) => {
|
||||
@ -178,7 +179,7 @@ impl Hash for Native {
|
||||
|
||||
/// A user-defined closure.
|
||||
#[derive(Hash)]
|
||||
pub struct Closure {
|
||||
pub(super) struct Closure {
|
||||
/// The source file where the closure was defined.
|
||||
pub location: SourceId,
|
||||
/// The name of the closure.
|
||||
@ -196,7 +197,7 @@ pub struct Closure {
|
||||
|
||||
impl Closure {
|
||||
/// Call the function in the context with the arguments.
|
||||
pub fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||
fn call(&self, vm: &Vm, args: &mut Args) -> SourceResult<Value> {
|
||||
// Don't leak the scopes from the call site. Instead, we use the scope
|
||||
// of captured variables we collected earlier.
|
||||
let mut scopes = Scopes::new(None);
|
||||
@ -241,7 +242,7 @@ impl Closure {
|
||||
}
|
||||
|
||||
/// The number of positional arguments this function takes, if known.
|
||||
pub fn argc(&self) -> Option<usize> {
|
||||
fn argc(&self) -> Option<usize> {
|
||||
if self.sink.is_some() {
|
||||
return None;
|
||||
}
|
||||
@ -272,20 +273,6 @@ impl<'a> CapturesVisitor<'a> {
|
||||
self.captures
|
||||
}
|
||||
|
||||
/// Bind a new internal variable.
|
||||
pub fn bind(&mut self, ident: ast::Ident) {
|
||||
self.internal.top.define(ident.take(), Value::None);
|
||||
}
|
||||
|
||||
/// Capture a variable if it isn't internal.
|
||||
pub fn capture(&mut self, ident: ast::Ident) {
|
||||
if self.internal.get(&ident).is_err() {
|
||||
if let Ok(value) = self.external.get(&ident) {
|
||||
self.captures.define_captured(ident.take(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit any node and collect all captured variables.
|
||||
pub fn visit(&mut self, node: &SyntaxNode) {
|
||||
match node.cast() {
|
||||
@ -366,6 +353,20 @@ impl<'a> CapturesVisitor<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Bind a new internal variable.
|
||||
fn bind(&mut self, ident: ast::Ident) {
|
||||
self.internal.top.define(ident.take(), Value::None);
|
||||
}
|
||||
|
||||
/// Capture a variable if it isn't internal.
|
||||
fn capture(&mut self, ident: ast::Ident) {
|
||||
if self.internal.get(&ident).is_err() {
|
||||
if let Ok(value) = self.external.get(&ident) {
|
||||
self.captures.define_captured(ident.take(), value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -12,7 +12,7 @@ use crate::geom::{Abs, Dir};
|
||||
use crate::util::{hash128, EcoString};
|
||||
use crate::World;
|
||||
|
||||
/// A Typst standard library.
|
||||
/// Definition of Typst's standard library.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
pub struct Library {
|
||||
/// The scope containing definitions that are available everywhere.
|
||||
@ -23,13 +23,13 @@ pub struct Library {
|
||||
pub items: LangItems,
|
||||
}
|
||||
|
||||
/// Definition of certain standard library items the language is aware of.
|
||||
/// Definition of library items the language is aware of.
|
||||
#[derive(Clone)]
|
||||
pub struct LangItems {
|
||||
/// The root layout function.
|
||||
pub layout: fn(
|
||||
content: &Content,
|
||||
world: Tracked<dyn World>,
|
||||
content: &Content,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Vec<Frame>>,
|
||||
/// Access the em size.
|
||||
|
@ -20,9 +20,9 @@ mod eval;
|
||||
mod func;
|
||||
mod methods;
|
||||
mod ops;
|
||||
mod realize;
|
||||
mod scope;
|
||||
mod typeset;
|
||||
mod vm;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use once_cell;
|
||||
@ -36,9 +36,9 @@ pub use self::dict::*;
|
||||
pub use self::eval::*;
|
||||
pub use self::func::*;
|
||||
pub use self::library::*;
|
||||
pub use self::realize::*;
|
||||
pub use self::scope::*;
|
||||
pub use self::str::*;
|
||||
pub use self::styles::*;
|
||||
pub use self::typeset::*;
|
||||
pub use self::value::*;
|
||||
pub use self::vm::*;
|
||||
|
145
src/model/realize.rs
Normal file
145
src/model/realize.rs
Normal file
@ -0,0 +1,145 @@
|
||||
use comemo::Tracked;
|
||||
|
||||
use super::{capability, Content, NodeId, Recipe, Selector, StyleChain};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::World;
|
||||
|
||||
/// Whether the target is affected by show rules in the given style chain.
|
||||
pub fn applicable(target: &Content, styles: StyleChain) -> bool {
|
||||
// Find out how many recipes there are.
|
||||
let mut n = styles.recipes().count();
|
||||
|
||||
// Find out whether any recipe matches and is unguarded.
|
||||
for recipe in styles.recipes() {
|
||||
if recipe.applicable(target) && !target.is_guarded(Guard::Nth(n)) {
|
||||
return true;
|
||||
}
|
||||
n -= 1;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Apply the show rules in the given style chain to a target.
|
||||
pub fn realize(
|
||||
world: Tracked<dyn World>,
|
||||
target: &Content,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
// Find out how many recipes there are.
|
||||
let mut n = styles.recipes().count();
|
||||
|
||||
// Find an applicable recipe.
|
||||
let mut realized = None;
|
||||
for recipe in styles.recipes() {
|
||||
let guard = Guard::Nth(n);
|
||||
if recipe.applicable(target) && !target.is_guarded(guard) {
|
||||
if let Some(content) = try_apply(world, &target, recipe, guard)? {
|
||||
realized = Some(content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
n -= 1;
|
||||
}
|
||||
|
||||
// Realize if there was no matching recipe.
|
||||
if let Some(showable) = target.with::<dyn Show>() {
|
||||
let guard = Guard::Base(target.id());
|
||||
if realized.is_none() && !target.is_guarded(guard) {
|
||||
realized = Some(showable.show(world, styles));
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize only if this is the first application for this node.
|
||||
if let Some(node) = target.with::<dyn Finalize>() {
|
||||
if target.is_pristine() {
|
||||
if let Some(already) = realized {
|
||||
realized = Some(node.finalize(already));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(realized)
|
||||
}
|
||||
|
||||
/// Try to apply a recipe to the target.
|
||||
fn try_apply(
|
||||
world: Tracked<dyn World>,
|
||||
target: &Content,
|
||||
recipe: &Recipe,
|
||||
guard: Guard,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
match &recipe.selector {
|
||||
Some(Selector::Node(id, _)) => {
|
||||
if target.id() != *id {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
recipe.apply(world, target.clone().guarded(guard)).map(Some)
|
||||
}
|
||||
|
||||
Some(Selector::Regex(regex)) => {
|
||||
let Some(text) = item!(text_str)(&target) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let make = |s| {
|
||||
let mut content = item!(text)(s);
|
||||
content.copy_meta(&target);
|
||||
content
|
||||
};
|
||||
|
||||
let mut result = vec![];
|
||||
let mut cursor = 0;
|
||||
|
||||
for m in regex.find_iter(text) {
|
||||
let start = m.start();
|
||||
if cursor < start {
|
||||
result.push(make(text[cursor..start].into()));
|
||||
}
|
||||
|
||||
let piece = make(m.as_str().into()).guarded(guard);
|
||||
let transformed = recipe.apply(world, piece)?;
|
||||
result.push(transformed);
|
||||
cursor = m.end();
|
||||
}
|
||||
|
||||
if result.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if cursor < text.len() {
|
||||
result.push(make(text[cursor..].into()));
|
||||
}
|
||||
|
||||
Ok(Some(Content::sequence(result)))
|
||||
}
|
||||
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// The base recipe for a node.
|
||||
#[capability]
|
||||
pub trait Show {
|
||||
/// Execute the base recipe for this node.
|
||||
fn show(&self, world: Tracked<dyn World>, styles: StyleChain) -> Content;
|
||||
}
|
||||
|
||||
/// Post-process a node after it was realized.
|
||||
#[capability]
|
||||
pub trait Finalize {
|
||||
/// 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
|
||||
/// the linking behaviour of a link node.
|
||||
fn finalize(&self, realized: Content) -> Content;
|
||||
}
|
||||
|
||||
/// Guards content against being affected by the same show rule multiple times.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
||||
pub enum Guard {
|
||||
/// The nth recipe from the top of the chain.
|
||||
Nth(usize),
|
||||
/// The [base recipe](Show) for a kind of node.
|
||||
Base(NodeId),
|
||||
}
|
@ -77,69 +77,69 @@ impl Str {
|
||||
}
|
||||
|
||||
/// Whether the given pattern exists in this string.
|
||||
pub fn contains(&self, pattern: Pattern) -> bool {
|
||||
pub fn contains(&self, pattern: StrPattern) -> bool {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => self.0.contains(pat.as_str()),
|
||||
Pattern::Regex(re) => re.is_match(self),
|
||||
StrPattern::Str(pat) => self.0.contains(pat.as_str()),
|
||||
StrPattern::Regex(re) => re.is_match(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this string begins with the given pattern.
|
||||
pub fn starts_with(&self, pattern: Pattern) -> bool {
|
||||
pub fn starts_with(&self, pattern: StrPattern) -> bool {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => self.0.starts_with(pat.as_str()),
|
||||
Pattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0),
|
||||
StrPattern::Str(pat) => self.0.starts_with(pat.as_str()),
|
||||
StrPattern::Regex(re) => re.find(self).map_or(false, |m| m.start() == 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether this string ends with the given pattern.
|
||||
pub fn ends_with(&self, pattern: Pattern) -> bool {
|
||||
pub fn ends_with(&self, pattern: StrPattern) -> bool {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => self.0.ends_with(pat.as_str()),
|
||||
Pattern::Regex(re) => {
|
||||
StrPattern::Str(pat) => self.0.ends_with(pat.as_str()),
|
||||
StrPattern::Regex(re) => {
|
||||
re.find_iter(self).last().map_or(false, |m| m.end() == self.0.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The text of the pattern's first match in this string.
|
||||
pub fn find(&self, pattern: Pattern) -> Option<Self> {
|
||||
pub fn find(&self, pattern: StrPattern) -> Option<Self> {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat),
|
||||
Pattern::Regex(re) => re.find(self).map(|m| m.as_str().into()),
|
||||
StrPattern::Str(pat) => self.0.contains(pat.as_str()).then(|| pat),
|
||||
StrPattern::Regex(re) => re.find(self).map(|m| m.as_str().into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of the pattern's first match in this string.
|
||||
pub fn position(&self, pattern: Pattern) -> Option<i64> {
|
||||
pub fn position(&self, pattern: StrPattern) -> Option<i64> {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64),
|
||||
Pattern::Regex(re) => re.find(self).map(|m| m.start() as i64),
|
||||
StrPattern::Str(pat) => self.0.find(pat.as_str()).map(|i| i as i64),
|
||||
StrPattern::Regex(re) => re.find(self).map(|m| m.start() as i64),
|
||||
}
|
||||
}
|
||||
|
||||
/// The start and, text and capture groups (if any) of the first match of
|
||||
/// the pattern in this string.
|
||||
pub fn match_(&self, pattern: Pattern) -> Option<Dict> {
|
||||
pub fn match_(&self, pattern: StrPattern) -> Option<Dict> {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => {
|
||||
StrPattern::Str(pat) => {
|
||||
self.0.match_indices(pat.as_str()).next().map(match_to_dict)
|
||||
}
|
||||
Pattern::Regex(re) => re.captures(self).map(captures_to_dict),
|
||||
StrPattern::Regex(re) => re.captures(self).map(captures_to_dict),
|
||||
}
|
||||
}
|
||||
|
||||
/// The start, end, text and capture groups (if any) of all matches of the
|
||||
/// pattern in this string.
|
||||
pub fn matches(&self, pattern: Pattern) -> Array {
|
||||
pub fn matches(&self, pattern: StrPattern) -> Array {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => self
|
||||
StrPattern::Str(pat) => self
|
||||
.0
|
||||
.match_indices(pat.as_str())
|
||||
.map(match_to_dict)
|
||||
.map(Value::Dict)
|
||||
.collect(),
|
||||
Pattern::Regex(re) => re
|
||||
StrPattern::Regex(re) => re
|
||||
.captures_iter(self)
|
||||
.map(captures_to_dict)
|
||||
.map(Value::Dict)
|
||||
@ -148,14 +148,14 @@ impl Str {
|
||||
}
|
||||
|
||||
/// Split this string at whitespace or a specific pattern.
|
||||
pub fn split(&self, pattern: Option<Pattern>) -> Array {
|
||||
pub fn split(&self, pattern: Option<StrPattern>) -> Array {
|
||||
let s = self.as_str();
|
||||
match pattern {
|
||||
None => s.split_whitespace().map(|v| Value::Str(v.into())).collect(),
|
||||
Some(Pattern::Str(pat)) => {
|
||||
Some(StrPattern::Str(pat)) => {
|
||||
s.split(pat.as_str()).map(|v| Value::Str(v.into())).collect()
|
||||
}
|
||||
Some(Pattern::Regex(re)) => {
|
||||
Some(StrPattern::Regex(re)) => {
|
||||
re.split(s).map(|v| Value::Str(v.into())).collect()
|
||||
}
|
||||
}
|
||||
@ -167,7 +167,7 @@ impl Str {
|
||||
/// pattern.
|
||||
pub fn trim(
|
||||
&self,
|
||||
pattern: Option<Pattern>,
|
||||
pattern: Option<StrPattern>,
|
||||
at: Option<StrSide>,
|
||||
repeat: bool,
|
||||
) -> Self {
|
||||
@ -180,7 +180,7 @@ impl Str {
|
||||
Some(StrSide::Start) => self.0.trim_start(),
|
||||
Some(StrSide::End) => self.0.trim_end(),
|
||||
},
|
||||
Some(Pattern::Str(pat)) => {
|
||||
Some(StrPattern::Str(pat)) => {
|
||||
let pat = pat.as_str();
|
||||
let mut s = self.as_str();
|
||||
if repeat {
|
||||
@ -200,7 +200,7 @@ impl Str {
|
||||
}
|
||||
s
|
||||
}
|
||||
Some(Pattern::Regex(re)) => {
|
||||
Some(StrPattern::Regex(re)) => {
|
||||
let s = self.as_str();
|
||||
let mut last = 0;
|
||||
let mut range = 0..s.len();
|
||||
@ -240,13 +240,13 @@ impl Str {
|
||||
|
||||
/// Replace at most `count` occurances of the given pattern with a
|
||||
/// replacement string (beginning from the start).
|
||||
pub fn replace(&self, pattern: Pattern, with: Self, count: Option<usize>) -> Self {
|
||||
pub fn replace(&self, pattern: StrPattern, with: Self, count: Option<usize>) -> Self {
|
||||
match pattern {
|
||||
Pattern::Str(pat) => match count {
|
||||
StrPattern::Str(pat) => match count {
|
||||
Some(n) => self.0.replacen(pat.as_str(), &with, n).into(),
|
||||
None => self.0.replace(pat.as_str(), &with).into(),
|
||||
},
|
||||
Pattern::Regex(re) => match count {
|
||||
StrPattern::Regex(re) => match count {
|
||||
Some(n) => re.replacen(self, n, with.as_str()).into(),
|
||||
None => re.replace(self, with.as_str()).into(),
|
||||
},
|
||||
@ -433,7 +433,7 @@ impl Hash for Regex {
|
||||
|
||||
/// A pattern which can be searched for in a string.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Pattern {
|
||||
pub enum StrPattern {
|
||||
/// Just a string.
|
||||
Str(Str),
|
||||
/// A regular expression.
|
||||
@ -441,7 +441,7 @@ pub enum Pattern {
|
||||
}
|
||||
|
||||
castable! {
|
||||
Pattern,
|
||||
StrPattern,
|
||||
Expected: "string or regular expression",
|
||||
Value::Str(text) => Self::Str(text),
|
||||
@regex: Regex => Self::Regex(regex.clone()),
|
||||
|
@ -7,7 +7,7 @@ use std::sync::Arc;
|
||||
|
||||
use comemo::{Prehashed, Tracked};
|
||||
|
||||
use super::{capability, Args, Content, Dict, Func, NodeId, Regex, Smart, Value};
|
||||
use super::{Args, Content, Dict, Func, NodeId, Regex, Smart, Value};
|
||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||
use crate::geom::{
|
||||
Abs, Align, Axes, Corners, Em, GenAlign, Length, Numeric, PartialStroke, Rel, Sides,
|
||||
@ -55,34 +55,15 @@ impl StyleMap {
|
||||
.any(|property| property.is::<K>())
|
||||
}
|
||||
|
||||
/// Make `self` the first link of the `tail` chain.
|
||||
///
|
||||
/// The resulting style chain contains styles from `self` as well as
|
||||
/// `tail`. The ones from `self` take precedence over the ones from
|
||||
/// `tail`. For folded properties `self` contributes the inner value.
|
||||
pub fn chain<'a>(&'a self, tail: &'a StyleChain<'a>) -> StyleChain<'a> {
|
||||
if self.is_empty() {
|
||||
*tail
|
||||
} else {
|
||||
StyleChain { head: &self.0, tail: Some(tail) }
|
||||
}
|
||||
/// Apply outer styles. Like [`chain`](StyleChain::chain), but in-place.
|
||||
pub fn apply(&mut self, outer: Self) {
|
||||
self.0.splice(0..0, outer.0.iter().cloned());
|
||||
}
|
||||
|
||||
/// Set an outer style.
|
||||
///
|
||||
/// Like [`chain`](Self::chain) or [`apply_map`](Self::apply_map), but with
|
||||
/// only a entry.
|
||||
pub fn apply(&mut self, style: Style) {
|
||||
self.0.insert(0, style);
|
||||
}
|
||||
|
||||
/// Apply styles from `tail` in-place. The resulting style map is equivalent
|
||||
/// to the style chain created by `self.chain(StyleChain::new(tail))`.
|
||||
///
|
||||
/// This is useful over `chain` when you want to combine two maps, but you
|
||||
/// still need an owned map without a lifetime.
|
||||
pub fn apply_map(&mut self, tail: &Self) {
|
||||
self.0.splice(0..0, tail.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
|
||||
@ -131,21 +112,6 @@ pub enum Style {
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Make this style the first link of the `tail` chain.
|
||||
pub fn chain<'a>(&'a self, tail: &'a StyleChain) -> StyleChain<'a> {
|
||||
if let Style::Barrier(id) = self {
|
||||
if !tail
|
||||
.entries()
|
||||
.filter_map(Style::property)
|
||||
.any(|p| p.scoped() && *id == p.node())
|
||||
{
|
||||
return *tail;
|
||||
}
|
||||
}
|
||||
|
||||
StyleChain { head: std::slice::from_ref(self), tail: Some(tail) }
|
||||
}
|
||||
|
||||
/// If this is a property, return it.
|
||||
pub fn property(&self) -> Option<&Property> {
|
||||
match self {
|
||||
@ -184,32 +150,6 @@ impl Debug for Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// A style property key.
|
||||
///
|
||||
/// This trait is not intended to be implemented manually, but rather through
|
||||
/// the `#[node]` proc-macro.
|
||||
pub trait Key: Copy + 'static {
|
||||
/// The unfolded type which this property is stored as in a style map.
|
||||
type Value: Debug + Clone + Hash + Sync + Send + 'static;
|
||||
|
||||
/// The folded type of value that is returned when reading this property
|
||||
/// from a style chain.
|
||||
type Output<'a>;
|
||||
|
||||
/// The name of the property, used for debug printing.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// The id of the node the key belongs to.
|
||||
fn node() -> NodeId;
|
||||
|
||||
/// Compute an output value from a sequence of values belonging to this key,
|
||||
/// folding if necessary.
|
||||
fn get<'a>(
|
||||
chain: StyleChain<'a>,
|
||||
values: impl Iterator<Item = &'a Self::Value>,
|
||||
) -> Self::Output<'a>;
|
||||
}
|
||||
|
||||
/// A style property originating from a set rule or constructor.
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Property {
|
||||
@ -308,6 +248,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A style property key.
|
||||
///
|
||||
/// This trait is not intended to be implemented manually, but rather through
|
||||
/// the `#[node]` proc-macro.
|
||||
pub trait Key: Copy + 'static {
|
||||
/// The unfolded type which this property is stored as in a style map.
|
||||
type Value: Debug + Clone + Hash + Sync + Send + 'static;
|
||||
|
||||
/// The folded type of value that is returned when reading this property
|
||||
/// from a style chain.
|
||||
type Output<'a>;
|
||||
|
||||
/// The name of the property, used for debug printing.
|
||||
const NAME: &'static str;
|
||||
|
||||
/// The id of the node the key belongs to.
|
||||
fn node() -> NodeId;
|
||||
|
||||
/// Compute an output value from a sequence of values belonging to this key,
|
||||
/// folding if necessary.
|
||||
fn get<'a>(
|
||||
chain: StyleChain<'a>,
|
||||
values: impl Iterator<Item = &'a Self::Value>,
|
||||
) -> Self::Output<'a>;
|
||||
}
|
||||
|
||||
/// A unique identifier for a property key.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
struct KeyId(ReadableTypeId);
|
||||
@ -325,35 +291,6 @@ impl Debug for KeyId {
|
||||
}
|
||||
}
|
||||
|
||||
/// A built-in show rule for a node.
|
||||
#[capability]
|
||||
pub trait Show {
|
||||
/// Execute the base recipe for this node.
|
||||
fn show(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
) -> SourceResult<Content>;
|
||||
}
|
||||
|
||||
/// Post-process a node after it was realized.
|
||||
#[capability]
|
||||
pub trait Finalize {
|
||||
/// 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
|
||||
/// the linking behaviour of a link node.
|
||||
fn finalize(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
styles: StyleChain,
|
||||
realized: Content,
|
||||
) -> SourceResult<Content>;
|
||||
}
|
||||
|
||||
/// Indicates that a node cannot be labelled.
|
||||
#[capability]
|
||||
pub trait Unlabellable {}
|
||||
|
||||
/// A show rule recipe.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub struct Recipe {
|
||||
@ -366,6 +303,14 @@ pub struct Recipe {
|
||||
}
|
||||
|
||||
impl Recipe {
|
||||
/// Whether this recipe is for the given node.
|
||||
pub fn is_of(&self, node: NodeId) -> bool {
|
||||
match self.selector {
|
||||
Some(Selector::Node(id, _)) => id == node,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the recipe is applicable to the target.
|
||||
pub fn applicable(&self, target: &Content) -> bool {
|
||||
self.selector
|
||||
@ -373,74 +318,24 @@ impl Recipe {
|
||||
.map_or(false, |selector| selector.matches(target))
|
||||
}
|
||||
|
||||
/// Try to apply the recipe to the target.
|
||||
/// Apply the recipe to the given content.
|
||||
pub fn apply(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
sel: RecipeId,
|
||||
target: &Content,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
let content = match &self.selector {
|
||||
Some(Selector::Node(id, _)) => {
|
||||
if target.id() != *id {
|
||||
return Ok(None);
|
||||
content: Content,
|
||||
) -> SourceResult<Content> {
|
||||
match &self.transform {
|
||||
Transform::Content(content) => Ok(content.clone()),
|
||||
Transform::Func(func) => {
|
||||
let args = Args::new(self.span, [Value::Content(content.clone())]);
|
||||
let mut result = func.call_detached(world, args);
|
||||
if let Some(span) = content.span() {
|
||||
let point = || Tracepoint::Show(content.name().into());
|
||||
result = result.trace(world, point, span);
|
||||
}
|
||||
|
||||
self.transform.apply(world, self.span, target.clone().guarded(sel))?
|
||||
Ok(result?.display())
|
||||
}
|
||||
|
||||
Some(Selector::Regex(regex)) => {
|
||||
let Some(text) = item!(text_str)(target) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let make = |s| {
|
||||
let mut content = item!(text)(s);
|
||||
content.copy_meta(target);
|
||||
content
|
||||
};
|
||||
|
||||
let mut result = vec![];
|
||||
let mut cursor = 0;
|
||||
|
||||
for m in regex.find_iter(text) {
|
||||
let start = m.start();
|
||||
if cursor < start {
|
||||
result.push(make(text[cursor..start].into()));
|
||||
}
|
||||
|
||||
let transformed = self.transform.apply(
|
||||
world,
|
||||
self.span,
|
||||
make(m.as_str().into()).guarded(sel),
|
||||
)?;
|
||||
|
||||
result.push(transformed);
|
||||
cursor = m.end();
|
||||
}
|
||||
|
||||
if result.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if cursor < text.len() {
|
||||
result.push(make(text[cursor..].into()));
|
||||
}
|
||||
|
||||
Content::sequence(result)
|
||||
}
|
||||
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some(content))
|
||||
}
|
||||
|
||||
/// Whether this recipe is for the given node.
|
||||
pub fn is_of(&self, node: NodeId) -> bool {
|
||||
match self.selector {
|
||||
Some(Selector::Node(id, _)) => id == node,
|
||||
_ => false,
|
||||
Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -495,39 +390,6 @@ pub enum Transform {
|
||||
Style(StyleMap),
|
||||
}
|
||||
|
||||
impl Transform {
|
||||
/// Apply the transform.
|
||||
pub fn apply(
|
||||
&self,
|
||||
world: Tracked<dyn World>,
|
||||
rule_span: Span,
|
||||
content: Content,
|
||||
) -> SourceResult<Content> {
|
||||
match self {
|
||||
Transform::Content(content) => Ok(content.clone()),
|
||||
Transform::Func(func) => {
|
||||
let args = Args::new(rule_span, [Value::Content(content.clone())]);
|
||||
let mut result = func.call_detached(world, args);
|
||||
if let Some(span) = content.span() {
|
||||
let point = || Tracepoint::Apply(content.name().into());
|
||||
result = result.trace(world, point, span);
|
||||
}
|
||||
Ok(result?.display())
|
||||
}
|
||||
Transform::Style(styles) => Ok(content.styled_with_map(styles.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifies a show rule recipe.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Hash)]
|
||||
pub enum RecipeId {
|
||||
/// The nth recipe from the top of the chain.
|
||||
Nth(usize),
|
||||
/// The base recipe for a kind of node.
|
||||
Base(NodeId),
|
||||
}
|
||||
|
||||
/// A chain of style maps, similar to a linked list.
|
||||
///
|
||||
/// A style chain allows to combine properties from multiple style maps in a
|
||||
@ -544,14 +406,40 @@ pub struct StyleChain<'a> {
|
||||
}
|
||||
|
||||
impl<'a> StyleChain<'a> {
|
||||
/// Create a new, empty style chain.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
/// Start a new style chain with a root map.
|
||||
pub fn new(root: &'a StyleMap) -> Self {
|
||||
Self { head: &root.0, tail: None }
|
||||
}
|
||||
|
||||
/// Start a new style chain with a root map.
|
||||
pub fn with_root(root: &'a StyleMap) -> Self {
|
||||
Self { head: &root.0, tail: None }
|
||||
/// Make the given map the first link of this chain.
|
||||
///
|
||||
/// The resulting style chain contains styles from `map` as well as
|
||||
/// `self`. The ones from `map` take precedence over the ones from
|
||||
/// `self`. For folded properties `map` contributes the inner value.
|
||||
pub fn chain<'b>(&'b self, map: &'b StyleMap) -> StyleChain<'b> {
|
||||
if map.is_empty() {
|
||||
*self
|
||||
} else {
|
||||
StyleChain { head: &map.0, tail: Some(self) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Make the given style the first link of the this chain.
|
||||
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 {
|
||||
head: std::slice::from_ref(style),
|
||||
tail: Some(self),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the output value of a style property.
|
||||
@ -563,78 +451,9 @@ impl<'a> StyleChain<'a> {
|
||||
K::get(self, self.values(key))
|
||||
}
|
||||
|
||||
/// Apply show recipes in this style chain to a target.
|
||||
pub fn show(
|
||||
self,
|
||||
world: Tracked<dyn World>,
|
||||
target: &Content,
|
||||
) -> SourceResult<Option<Content>> {
|
||||
// Find out how many recipes there are.
|
||||
let mut n = self.entries().filter_map(Style::recipe).count();
|
||||
|
||||
// Find an applicable recipe.
|
||||
let mut realized = None;
|
||||
for recipe in self.entries().filter_map(Style::recipe) {
|
||||
let sel = RecipeId::Nth(n);
|
||||
if recipe.applicable(target) && !target.is_guarded(sel) {
|
||||
if let Some(content) = recipe.apply(world, sel, target)? {
|
||||
realized = Some(content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
n -= 1;
|
||||
}
|
||||
|
||||
// Realize if there was no matching recipe.
|
||||
let base = RecipeId::Base(target.id());
|
||||
if realized.is_none() && !target.is_guarded(base) {
|
||||
if let Some(showable) = target.with::<dyn Show>() {
|
||||
realized = Some(showable.show(world, self)?);
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize only if this is the first application for this node.
|
||||
if let Some(node) = target.with::<dyn Finalize>() {
|
||||
if target.is_pristine() {
|
||||
if let Some(content) = realized {
|
||||
realized = Some(node.finalize(world, self, content)?);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(realized)
|
||||
}
|
||||
|
||||
/// Whether the style chain has a matching recipe for the content.
|
||||
pub fn applicable(self, target: &Content) -> bool {
|
||||
// Find out how many recipes there are.
|
||||
let mut n = self.entries().filter_map(Style::recipe).count();
|
||||
|
||||
// Find out whether any recipe matches and is unguarded.
|
||||
for recipe in self.entries().filter_map(Style::recipe) {
|
||||
if recipe.applicable(target) && !target.is_guarded(RecipeId::Nth(n)) {
|
||||
return true;
|
||||
}
|
||||
n -= 1;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Remove the last link from the chain.
|
||||
fn pop(&mut self) {
|
||||
*self = self.tail.copied().unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Build a style map from the suffix (all links beyond the `len`) of the
|
||||
/// chain.
|
||||
fn suffix(self, len: usize) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
let take = self.links().count().saturating_sub(len);
|
||||
for link in self.links().take(take) {
|
||||
suffix.0.splice(0..0, link.iter().cloned());
|
||||
}
|
||||
suffix
|
||||
/// Iterate over all style recipes in the chain.
|
||||
pub fn recipes(self) -> impl Iterator<Item = &'a Recipe> {
|
||||
self.entries().filter_map(Style::recipe)
|
||||
}
|
||||
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
@ -655,6 +474,22 @@ impl<'a> StyleChain<'a> {
|
||||
fn links(self) -> Links<'a> {
|
||||
Links(Some(self))
|
||||
}
|
||||
|
||||
/// Build a style map from the suffix (all links beyond the `len`) of the
|
||||
/// chain.
|
||||
fn suffix(self, len: usize) -> StyleMap {
|
||||
let mut suffix = StyleMap::new();
|
||||
let take = self.links().count().saturating_sub(len);
|
||||
for link in self.links().take(take) {
|
||||
suffix.0.splice(0..0, link.iter().cloned());
|
||||
}
|
||||
suffix
|
||||
}
|
||||
|
||||
/// Remove the last link from the chain.
|
||||
fn pop(&mut self) {
|
||||
*self = self.tail.copied().unwrap_or_default();
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for StyleChain<'_> {
|
||||
@ -675,37 +510,6 @@ impl PartialEq for StyleChain<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the values in a style chain.
|
||||
struct Values<'a, K> {
|
||||
entries: Entries<'a>,
|
||||
key: PhantomData<K>,
|
||||
barriers: usize,
|
||||
}
|
||||
|
||||
impl<'a, K: Key> Iterator for Values<'a, K> {
|
||||
type Item = &'a K::Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
for entry in &mut self.entries {
|
||||
match entry {
|
||||
Style::Property(property) => {
|
||||
if let Some(value) = property.downcast::<K>() {
|
||||
if !property.scoped() || self.barriers <= 1 {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Style::Barrier(id) => {
|
||||
self.barriers += (*id == K::node()) as usize;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the entries in a style chain.
|
||||
struct Entries<'a> {
|
||||
inner: std::slice::Iter<'a, Style>,
|
||||
@ -742,6 +546,37 @@ impl<'a> Iterator for Links<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the values in a style chain.
|
||||
struct Values<'a, K> {
|
||||
entries: Entries<'a>,
|
||||
key: PhantomData<K>,
|
||||
barriers: usize,
|
||||
}
|
||||
|
||||
impl<'a, K: Key> Iterator for Values<'a, K> {
|
||||
type Item = &'a K::Value;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
for entry in &mut self.entries {
|
||||
match entry {
|
||||
Style::Property(property) => {
|
||||
if let Some(value) = property.downcast::<K>() {
|
||||
if !property.scoped() || self.barriers <= 1 {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
Style::Barrier(id) => {
|
||||
self.barriers += (*id == K::node()) as usize;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of items with associated styles.
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct StyleVec<T> {
|
||||
|
@ -10,8 +10,9 @@ use crate::World;
|
||||
/// Returns either a vector of frames representing individual pages or
|
||||
/// diagnostics in the form of a vector of error message with file and span
|
||||
/// information.
|
||||
#[comemo::memoize]
|
||||
pub fn typeset(world: Tracked<dyn World>, content: &Content) -> SourceResult<Vec<Frame>> {
|
||||
let library = world.library();
|
||||
let styles = StyleChain::with_root(&library.styles);
|
||||
(library.items.layout)(content, world, styles)
|
||||
let styles = StyleChain::new(&library.styles);
|
||||
(library.items.layout)(world, content, styles)
|
||||
}
|
||||
|
@ -1,97 +0,0 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use comemo::Tracked;
|
||||
|
||||
use super::{LangItems, Route, Scopes, Value};
|
||||
use crate::diag::{error, SourceError, StrResult};
|
||||
use crate::syntax::{SourceId, Span};
|
||||
use crate::util::PathExt;
|
||||
use crate::World;
|
||||
|
||||
/// A virtual machine.
|
||||
///
|
||||
/// Holds the state needed to evaluate Typst sources. A new virtual machine is
|
||||
/// created for each module evaluation and function call.
|
||||
pub struct Vm<'a> {
|
||||
/// The core context.
|
||||
pub(crate) world: Tracked<'a, dyn World>,
|
||||
/// The route of source ids the VM took to reach its current location.
|
||||
pub(crate) route: Tracked<'a, Route>,
|
||||
/// The current location.
|
||||
pub(crate) location: SourceId,
|
||||
/// The stack of scopes.
|
||||
pub(crate) scopes: Scopes<'a>,
|
||||
/// A control flow event that is currently happening.
|
||||
pub(crate) flow: Option<Flow>,
|
||||
/// The language items.
|
||||
pub(crate) items: LangItems,
|
||||
}
|
||||
|
||||
impl<'a> Vm<'a> {
|
||||
/// Create a new virtual machine.
|
||||
pub fn new(
|
||||
world: Tracked<'a, dyn World>,
|
||||
route: Tracked<'a, Route>,
|
||||
location: SourceId,
|
||||
scopes: Scopes<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
world,
|
||||
route,
|
||||
location,
|
||||
scopes,
|
||||
flow: None,
|
||||
items: world.library().items.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Access the underlying world.
|
||||
pub fn world(&self) -> Tracked<dyn World> {
|
||||
self.world
|
||||
}
|
||||
|
||||
/// Resolve a user-entered path to be relative to the compilation
|
||||
/// environment's root.
|
||||
pub fn locate(&self, path: &str) -> StrResult<PathBuf> {
|
||||
if !self.location.is_detached() {
|
||||
if let Some(path) = path.strip_prefix('/') {
|
||||
return Ok(self.world.root().join(path).normalize());
|
||||
}
|
||||
|
||||
if let Some(dir) = self.world.source(self.location).path().parent() {
|
||||
return Ok(dir.join(path).normalize());
|
||||
}
|
||||
}
|
||||
|
||||
Err("cannot access file system from here".into())
|
||||
}
|
||||
}
|
||||
|
||||
/// A control flow event that occurred during evaluation.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Flow {
|
||||
/// Stop iteration in a loop.
|
||||
Break(Span),
|
||||
/// Skip the remainder of the current iteration in a loop.
|
||||
Continue(Span),
|
||||
/// Stop execution of a function early, optionally returning an explicit
|
||||
/// value.
|
||||
Return(Span, Option<Value>),
|
||||
}
|
||||
|
||||
impl Flow {
|
||||
/// Return an error stating that this control flow is forbidden.
|
||||
pub fn forbidden(&self) -> SourceError {
|
||||
match *self {
|
||||
Self::Break(span) => {
|
||||
error!(span, "cannot break outside of loop")
|
||||
}
|
||||
Self::Continue(span) => {
|
||||
error!(span, "cannot continue outside of loop")
|
||||
}
|
||||
Self::Return(span, _) => {
|
||||
error!(span, "cannot return outside of function")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user