mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Fragments
This commit is contained in:
parent
7caf98fe42
commit
989d170dc7
@ -4,22 +4,26 @@ use crate::prelude::*;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct HideNode(pub Content);
|
pub struct HideNode(pub Content);
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl HideNode {
|
impl HideNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self(args.expect("body")?).pack())
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for HideNode {
|
impl Layout for HideNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut frame = self.0.layout_inline(world, styles, regions)?;
|
let mut fragment = self.0.layout(world, styles, regions)?;
|
||||||
frame.clear();
|
for frame in &mut fragment {
|
||||||
Ok(frame)
|
frame.clear();
|
||||||
|
}
|
||||||
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for HideNode {}
|
||||||
|
@ -9,7 +9,7 @@ use crate::text::LinkNode;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct ImageNode(pub Image);
|
pub struct ImageNode(pub Image);
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl ImageNode {
|
impl ImageNode {
|
||||||
/// How the image should adjust itself to a given area.
|
/// How the image should adjust itself to a given area.
|
||||||
pub const FIT: ImageFit = ImageFit::Cover;
|
pub const FIT: ImageFit = ImageFit::Cover;
|
||||||
@ -37,13 +37,13 @@ impl ImageNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for ImageNode {
|
impl Layout for ImageNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: Tracked<dyn World>,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
let pxw = self.0.width() as f64;
|
let pxw = self.0.width() as f64;
|
||||||
let pxh = self.0.height() as f64;
|
let pxh = self.0.height() as f64;
|
||||||
let px_ratio = pxw / pxh;
|
let px_ratio = pxw / pxh;
|
||||||
@ -94,10 +94,12 @@ impl LayoutInline for ImageNode {
|
|||||||
frame.link(url.clone());
|
frame.link(url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frame)
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for ImageNode {}
|
||||||
|
|
||||||
/// How an image should adjust itself to a given area.
|
/// How an image should adjust itself to a given area.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum ImageFit {
|
pub enum ImageFit {
|
||||||
|
@ -9,7 +9,7 @@ pub struct LineNode {
|
|||||||
delta: Axes<Rel<Length>>,
|
delta: Axes<Rel<Length>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl LineNode {
|
impl LineNode {
|
||||||
/// How to stroke the line.
|
/// How to stroke the line.
|
||||||
#[property(resolve, fold)]
|
#[property(resolve, fold)]
|
||||||
@ -36,13 +36,13 @@ impl LineNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for LineNode {
|
impl Layout for LineNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
_: Tracked<dyn World>,
|
_: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
let stroke = styles.get(Self::STROKE).unwrap_or_default();
|
let stroke = styles.get(Self::STROKE).unwrap_or_default();
|
||||||
|
|
||||||
let origin = self
|
let origin = self
|
||||||
@ -58,11 +58,13 @@ impl LayoutInline for LineNode {
|
|||||||
.map(|(l, b)| l.relative_to(b));
|
.map(|(l, b)| l.relative_to(b));
|
||||||
|
|
||||||
let target = regions.expand.select(regions.first, Size::zero());
|
let target = regions.expand.select(regions.first, Size::zero());
|
||||||
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(origin.to_point(), Element::Shape(shape));
|
||||||
|
|
||||||
Ok(frame)
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for LineNode {}
|
||||||
|
@ -19,7 +19,7 @@ pub type CircleNode = ShapeNode<CIRCLE>;
|
|||||||
/// A ellipse with optional content.
|
/// A ellipse with optional content.
|
||||||
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
pub type EllipseNode = ShapeNode<ELLIPSE>;
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl<const S: ShapeKind> ShapeNode<S> {
|
impl<const S: ShapeKind> ShapeNode<S> {
|
||||||
/// How to fill the shape.
|
/// How to fill the shape.
|
||||||
pub const FILL: Option<Paint> = None;
|
pub const FILL: Option<Paint> = None;
|
||||||
@ -72,13 +72,13 @@ impl<const S: ShapeKind> ShapeNode<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
|
impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut frame;
|
let mut frame;
|
||||||
if let Some(child) = &self.0 {
|
if let Some(child) = &self.0 {
|
||||||
let mut inset = styles.get(Self::INSET);
|
let mut inset = styles.get(Self::INSET);
|
||||||
@ -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 child = child.clone().padded(inset.map(|side| side.map(Length::from)));
|
||||||
|
|
||||||
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
let mut pod = Regions::one(regions.first, regions.base, regions.expand);
|
||||||
frame = child.layout_inline(world, styles, &pod)?;
|
frame = child.layout(world, styles, &pod)?.into_frame();
|
||||||
|
|
||||||
// Relayout with full expansion into square region to make sure
|
// Relayout with full expansion into square region to make sure
|
||||||
// the result is really a square or circle.
|
// 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.first = Size::splat(length);
|
||||||
pod.expand = Axes::splat(true);
|
pod.expand = Axes::splat(true);
|
||||||
frame = child.layout_inline(world, styles, &pod)?;
|
frame = child.layout(world, styles, &pod)?.into_frame();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The default size that a shape takes on if it has no child and
|
// The default size that a shape takes on if it has no child and
|
||||||
@ -165,10 +165,12 @@ impl<const S: ShapeKind> LayoutInline for ShapeNode<S> {
|
|||||||
frame.link(url.clone());
|
frame.link(url.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frame)
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const S: ShapeKind> Inline for ShapeNode<S> {}
|
||||||
|
|
||||||
/// A category of shape.
|
/// A category of shape.
|
||||||
pub type ShapeKind = usize;
|
pub type ShapeKind = usize;
|
||||||
|
|
||||||
|
@ -10,14 +10,14 @@ pub struct AlignNode {
|
|||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl AlignNode {
|
impl AlignNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
|
let aligns: Axes<Option<GenAlign>> = args.find()?.unwrap_or_default();
|
||||||
let body: Content = args.expect("body")?;
|
let body: Content = args.expect("body")?;
|
||||||
|
|
||||||
if let Axes { x: Some(x), y: None } = aligns {
|
if let Axes { x: Some(x), y: None } = aligns {
|
||||||
if !body.has::<dyn LayoutBlock>() {
|
if !body.has::<dyn Layout>() || body.has::<dyn Inline>() {
|
||||||
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
|
return Ok(body.styled(ParNode::ALIGN, HorizontalAlign(x)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -26,13 +26,13 @@ impl AlignNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for AlignNode {
|
impl Layout for AlignNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
// The child only needs to expand along an axis if there's no alignment.
|
// The child only needs to expand along an axis if there's no alignment.
|
||||||
let mut pod = regions.clone();
|
let mut pod = regions.clone();
|
||||||
pod.expand &= self.aligns.as_ref().map(Option::is_none);
|
pod.expand &= self.aligns.as_ref().map(Option::is_none);
|
||||||
@ -44,8 +44,8 @@ impl LayoutBlock for AlignNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frames = self.child.layout_block(world, styles.chain(&map), &pod)?;
|
let mut fragment = self.child.layout(world, styles.chain(&map), &pod)?;
|
||||||
for (region, frame) in regions.iter().zip(&mut frames) {
|
for (region, frame) in regions.iter().zip(&mut fragment) {
|
||||||
// Align in the target size. The target size depends on whether we
|
// Align in the target size. The target size depends on whether we
|
||||||
// should expand.
|
// should expand.
|
||||||
let target = regions.expand.select(region, frame.size());
|
let target = regions.expand.select(region, frame.size());
|
||||||
@ -57,6 +57,6 @@ impl LayoutBlock for AlignNode {
|
|||||||
frame.resize(target, aligns);
|
frame.resize(target, aligns);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frames)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ pub struct ColumnsNode {
|
|||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl ColumnsNode {
|
impl ColumnsNode {
|
||||||
/// The size of the gutter space between each column.
|
/// The size of the gutter space between each column.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -26,17 +26,17 @@ impl ColumnsNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for ColumnsNode {
|
impl Layout for ColumnsNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
// Separating the infinite space into infinite columns does not make
|
// Separating the infinite space into infinite columns does not make
|
||||||
// much sense.
|
// much sense.
|
||||||
if !regions.first.x.is_finite() {
|
if !regions.first.x.is_finite() {
|
||||||
return self.child.layout_block(world, styles, regions);
|
return self.child.layout(world, styles, regions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the width of the gutter and each column.
|
// Determine the width of the gutter and each column.
|
||||||
@ -58,7 +58,7 @@ impl LayoutBlock for ColumnsNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout the children.
|
// Layout the children.
|
||||||
let mut frames = self.child.layout_block(world, styles, &pod)?.into_iter();
|
let mut frames = self.child.layout(world, styles, &pod)?.into_iter();
|
||||||
let mut finished = vec![];
|
let mut finished = vec![];
|
||||||
|
|
||||||
let dir = styles.get(TextNode::DIR);
|
let dir = styles.get(TextNode::DIR);
|
||||||
@ -94,7 +94,7 @@ impl LayoutBlock for ColumnsNode {
|
|||||||
finished.push(output);
|
finished.push(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(finished)
|
Ok(Fragment::frames(finished))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ pub struct BoxNode {
|
|||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl BoxNode {
|
impl BoxNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let width = args.named("width")?;
|
let width = args.named("width")?;
|
||||||
@ -20,13 +20,13 @@ impl BoxNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for BoxNode {
|
impl Layout for BoxNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
// The "pod" is the region into which the child will be layouted.
|
// The "pod" is the region into which the child will be layouted.
|
||||||
let pod = {
|
let pod = {
|
||||||
// Resolve the sizing to a concrete size.
|
// Resolve the sizing to a concrete size.
|
||||||
@ -47,21 +47,23 @@ impl LayoutInline for BoxNode {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let mut frame = self.child.layout_inline(world, styles, &pod)?;
|
let mut frame = self.child.layout(world, styles, &pod)?.into_frame();
|
||||||
|
|
||||||
// Ensure frame size matches regions size if expansion is on.
|
// Ensure frame size matches regions size if expansion is on.
|
||||||
let target = regions.expand.select(regions.first, frame.size());
|
let target = regions.expand.select(regions.first, frame.size());
|
||||||
frame.resize(target, Align::LEFT_TOP);
|
frame.resize(target, Align::LEFT_TOP);
|
||||||
|
|
||||||
Ok(frame)
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for BoxNode {}
|
||||||
|
|
||||||
/// A block-level container that places content into a separate flow.
|
/// A block-level container that places content into a separate flow.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct BlockNode(pub Content);
|
pub struct BlockNode(pub Content);
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl BlockNode {
|
impl BlockNode {
|
||||||
/// The spacing between the previous and this block.
|
/// The spacing between the previous and this block.
|
||||||
#[property(skip)]
|
#[property(skip)]
|
||||||
@ -87,13 +89,13 @@ impl BlockNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for BlockNode {
|
impl Layout for BlockNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
self.0.layout_block(world, styles, regions)
|
self.0.layout(world, styles, regions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,23 +11,23 @@ use crate::text::ParNode;
|
|||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct FlowNode(pub StyleVec<Content>);
|
pub struct FlowNode(pub StyleVec<Content>);
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl FlowNode {}
|
impl FlowNode {}
|
||||||
|
|
||||||
impl LayoutBlock for FlowNode {
|
impl Layout for FlowNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut layouter = FlowLayouter::new(regions);
|
let mut layouter = FlowLayouter::new(regions);
|
||||||
|
|
||||||
for (child, map) in self.0.iter() {
|
for (child, map) in self.0.iter() {
|
||||||
let styles = styles.chain(&map);
|
let styles = styles.chain(&map);
|
||||||
if let Some(&node) = child.to::<VNode>() {
|
if let Some(&node) = child.to::<VNode>() {
|
||||||
layouter.layout_spacing(node.amount, styles);
|
layouter.layout_spacing(node.amount, styles);
|
||||||
} else if child.has::<dyn LayoutBlock>() {
|
} else if child.has::<dyn Layout>() {
|
||||||
layouter.layout_block(world, child, styles)?;
|
layouter.layout_block(world, child, styles)?;
|
||||||
} else if child.is::<ColbreakNode>() {
|
} else if child.is::<ColbreakNode>() {
|
||||||
layouter.finish_region();
|
layouter.finish_region();
|
||||||
@ -136,7 +136,7 @@ impl FlowLayouter {
|
|||||||
// 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() {
|
||||||
let frame = block.layout_block(world, styles, &self.regions)?.remove(0);
|
let frame = block.layout(world, styles, &self.regions)?.into_frame();
|
||||||
self.items.push(FlowItem::Placed(frame));
|
self.items.push(FlowItem::Placed(frame));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -166,9 +166,9 @@ impl FlowLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout the block itself.
|
// Layout the block itself.
|
||||||
let frames = block.layout_block(world, chained, &self.regions)?;
|
let fragment = block.layout(world, chained, &self.regions)?;
|
||||||
let len = frames.len();
|
let len = fragment.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in fragment.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
let size = frame.size();
|
let size = frame.size();
|
||||||
self.used.y += size.y;
|
self.used.y += size.y;
|
||||||
@ -234,8 +234,8 @@ impl FlowLayouter {
|
|||||||
self.finished.push(output);
|
self.finished.push(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish layouting and return the resulting frames.
|
/// Finish layouting and return the resulting fragment.
|
||||||
fn finish(mut self) -> Vec<Frame> {
|
fn finish(mut self) -> Fragment {
|
||||||
if self.expand.y {
|
if self.expand.y {
|
||||||
while !self.regions.backlog.is_empty() {
|
while !self.regions.backlog.is_empty() {
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
@ -243,6 +243,6 @@ impl FlowLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
self.finished
|
Fragment::frames(self.finished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ pub struct GridNode {
|
|||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl GridNode {
|
impl GridNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
|
let TrackSizings(columns) = args.named("columns")?.unwrap_or_default();
|
||||||
@ -33,13 +33,13 @@ impl GridNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for GridNode {
|
impl Layout for GridNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
// 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(
|
||||||
world,
|
world,
|
||||||
@ -222,7 +222,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the columns sizes and then layouts the grid row-by-row.
|
/// Determines the columns sizes and then layouts the grid row-by-row.
|
||||||
fn layout(mut self) -> SourceResult<Vec<Frame>> {
|
fn layout(mut self) -> SourceResult<Fragment> {
|
||||||
self.measure_columns()?;
|
self.measure_columns()?;
|
||||||
|
|
||||||
for y in 0..self.rows.len() {
|
for y in 0..self.rows.len() {
|
||||||
@ -243,7 +243,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.finish_region()?;
|
self.finish_region()?;
|
||||||
Ok(self.finished)
|
Ok(Fragment::frames(self.finished))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine all column sizes.
|
/// Determine all column sizes.
|
||||||
@ -320,8 +320,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
v.resolve(self.styles).relative_to(self.regions.base.y);
|
v.resolve(self.styles).relative_to(self.regions.base.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame =
|
let frame = cell.layout(self.world, self.styles, &pod)?.into_frame();
|
||||||
cell.layout_block(self.world, self.styles, &pod)?.remove(0);
|
|
||||||
resolved.set_max(frame.width());
|
resolved.set_max(frame.width());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -391,7 +390,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut sizes = cell
|
let mut sizes = cell
|
||||||
.layout_block(self.world, self.styles, &pod)?
|
.layout(self.world, self.styles, &pod)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|frame| frame.height());
|
.map(|frame| frame.height());
|
||||||
|
|
||||||
@ -429,9 +428,9 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Layout into multiple regions.
|
// Layout into multiple regions.
|
||||||
let frames = self.layout_multi_row(&resolved, y)?;
|
let fragment = self.layout_multi_row(&resolved, y)?;
|
||||||
let len = frames.len();
|
let len = fragment.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in fragment.into_iter().enumerate() {
|
||||||
self.push_row(frame);
|
self.push_row(frame);
|
||||||
if i + 1 < len {
|
if i + 1 < len {
|
||||||
self.finish_region()?;
|
self.finish_region()?;
|
||||||
@ -480,7 +479,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.select(self.regions.base, size);
|
.select(self.regions.base, size);
|
||||||
|
|
||||||
let pod = Regions::one(size, base, Axes::splat(true));
|
let pod = Regions::one(size, base, Axes::splat(true));
|
||||||
let frame = cell.layout_block(self.world, self.styles, &pod)?.remove(0);
|
let frame = cell.layout(self.world, self.styles, &pod)?.into_frame();
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -491,11 +490,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a row spanning multiple regions.
|
/// Layout a row spanning multiple regions.
|
||||||
fn layout_multi_row(
|
fn layout_multi_row(&mut self, heights: &[Abs], y: usize) -> SourceResult<Fragment> {
|
||||||
&mut self,
|
|
||||||
heights: &[Abs],
|
|
||||||
y: usize,
|
|
||||||
) -> SourceResult<Vec<Frame>> {
|
|
||||||
// Prepare frames.
|
// Prepare frames.
|
||||||
let mut outputs: Vec<_> = heights
|
let mut outputs: Vec<_> = heights
|
||||||
.iter()
|
.iter()
|
||||||
@ -520,8 +515,8 @@ impl<'a> GridLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Push the layouted frames into the individual output frames.
|
// Push the layouted frames into the individual output frames.
|
||||||
let frames = cell.layout_block(self.world, self.styles, &pod)?;
|
let fragment = cell.layout(self.world, self.styles, &pod)?;
|
||||||
for (output, frame) in outputs.iter_mut().zip(frames) {
|
for (output, frame) in outputs.iter_mut().zip(fragment) {
|
||||||
output.push_frame(pos, frame);
|
output.push_frame(pos, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -529,7 +524,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
pos.x += rcol;
|
pos.x += rcol;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(outputs)
|
Ok(Fragment::frames(outputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a row frame into the current region.
|
/// Push a row frame into the current region.
|
||||||
|
@ -29,7 +29,6 @@ use std::mem;
|
|||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
use typst::diag::SourceResult;
|
use typst::diag::SourceResult;
|
||||||
use typst::doc::Frame;
|
|
||||||
use typst::geom::*;
|
use typst::geom::*;
|
||||||
use typst::model::{
|
use typst::model::{
|
||||||
applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain,
|
applicable, capability, realize, Content, Node, SequenceNode, Style, StyleChain,
|
||||||
@ -70,72 +69,37 @@ impl LayoutRoot for Content {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Block-level layout.
|
/// Layout into regions.
|
||||||
#[capability]
|
#[capability]
|
||||||
pub trait LayoutBlock {
|
pub trait Layout {
|
||||||
/// Layout into one frame per region.
|
/// Layout into one frame per region.
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>>;
|
) -> SourceResult<Fragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for Content {
|
impl Layout for Content {
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let scratch = Scratch::default();
|
let scratch = Scratch::default();
|
||||||
let (realized, styles) = realize_block(world, &scratch, self, styles)?;
|
let (realized, styles) = realize_block(world, &scratch, self, styles)?;
|
||||||
let barrier = Style::Barrier(realized.id());
|
let barrier = Style::Barrier(realized.id());
|
||||||
let styles = styles.chain_one(&barrier);
|
let styles = styles.chain_one(&barrier);
|
||||||
realized
|
realized.with::<dyn Layout>().unwrap().layout(world, styles, regions)
|
||||||
.with::<dyn LayoutBlock>()
|
|
||||||
.unwrap()
|
|
||||||
.layout_block(world, styles, regions)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inline-level layout.
|
/// Inline-level layout.
|
||||||
#[capability]
|
#[capability]
|
||||||
pub trait LayoutInline {
|
pub trait Inline: Layout {}
|
||||||
/// Layout into a single frame.
|
|
||||||
fn layout_inline(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> SourceResult<Frame>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutInline for Content {
|
|
||||||
#[comemo::memoize]
|
|
||||||
fn layout_inline(
|
|
||||||
&self,
|
|
||||||
world: Tracked<dyn World>,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> SourceResult<Frame> {
|
|
||||||
assert!(regions.backlog.is_empty());
|
|
||||||
assert!(regions.last.is_none());
|
|
||||||
|
|
||||||
if self.has::<dyn LayoutInline>() && !applicable(self, styles) {
|
|
||||||
let barrier = Style::Barrier(self.id());
|
|
||||||
let styles = styles.chain_one(&barrier);
|
|
||||||
return self
|
|
||||||
.with::<dyn LayoutInline>()
|
|
||||||
.unwrap()
|
|
||||||
.layout_inline(world, styles, regions);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.layout_block(world, styles, regions)?.remove(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sequence of regions to layout into.
|
/// A sequence of regions to layout into.
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
@ -255,7 +219,7 @@ fn realize_block<'a>(
|
|||||||
content: &'a Content,
|
content: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||||
if content.has::<dyn LayoutBlock>() && !applicable(content, styles) {
|
if content.has::<dyn Layout>() && !applicable(content, styles) {
|
||||||
return Ok((content.clone(), styles));
|
return Ok((content.clone(), styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,7 +461,7 @@ impl<'a> FlowBuilder<'a> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.has::<dyn LayoutBlock>() {
|
if content.has::<dyn Layout>() {
|
||||||
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
|
||||||
} else if let Some(node) = content.to::<EnumNode>() {
|
} else if let Some(node) = content.to::<EnumNode>() {
|
||||||
@ -542,7 +506,7 @@ impl<'a> ParBuilder<'a> {
|
|||||||
|| content.is::<HNode>()
|
|| content.is::<HNode>()
|
||||||
|| content.is::<SmartQuoteNode>()
|
|| content.is::<SmartQuoteNode>()
|
||||||
|| content.is::<TextNode>()
|
|| content.is::<TextNode>()
|
||||||
|| content.has::<dyn LayoutInline>()
|
|| content.has::<dyn Inline>()
|
||||||
{
|
{
|
||||||
self.0.push(content.clone(), styles);
|
self.0.push(content.clone(), styles);
|
||||||
return true;
|
return true;
|
||||||
|
@ -9,7 +9,7 @@ pub struct PadNode {
|
|||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl PadNode {
|
impl PadNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let all = args.named("rest")?.or(args.find()?);
|
let all = args.named("rest")?.or(args.find()?);
|
||||||
@ -25,19 +25,19 @@ impl PadNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for PadNode {
|
impl Layout for PadNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
// Layout child into padded regions.
|
// Layout child into padded regions.
|
||||||
let padding = self.padding.resolve(styles);
|
let padding = self.padding.resolve(styles);
|
||||||
let pod = regions.map(|size| shrink(size, padding));
|
let pod = regions.map(|size| shrink(size, padding));
|
||||||
let mut frames = self.child.layout_block(world, styles, &pod)?;
|
let mut fragment = self.child.layout(world, styles, &pod)?;
|
||||||
|
|
||||||
for frame in &mut frames {
|
for frame in &mut fragment {
|
||||||
// Apply the padding inversely such that the grown size padded
|
// Apply the padding inversely such that the grown size padded
|
||||||
// yields the frame's size.
|
// yields the frame's size.
|
||||||
let padded = grow(frame.size(), padding);
|
let padded = grow(frame.size(), padding);
|
||||||
@ -49,7 +49,7 @@ impl LayoutBlock for PadNode {
|
|||||||
frame.translate(offset);
|
frame.translate(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frames)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ impl PageNode {
|
|||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
mut page: usize,
|
mut page: usize,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> 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 = styles.get(Self::WIDTH).unwrap_or(Abs::inf());
|
let width = styles.get(Self::WIDTH).unwrap_or(Abs::inf());
|
||||||
@ -97,7 +97,7 @@ impl PageNode {
|
|||||||
|
|
||||||
// Layout the child.
|
// Layout the child.
|
||||||
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
|
let regions = Regions::repeat(size, size, size.map(Abs::is_finite));
|
||||||
let mut frames = child.layout_block(world, styles, ®ions)?;
|
let mut fragment = child.layout(world, styles, ®ions)?;
|
||||||
|
|
||||||
let header = styles.get(Self::HEADER);
|
let header = styles.get(Self::HEADER);
|
||||||
let footer = styles.get(Self::FOOTER);
|
let footer = styles.get(Self::FOOTER);
|
||||||
@ -105,7 +105,7 @@ impl PageNode {
|
|||||||
let background = styles.get(Self::BACKGROUND);
|
let background = styles.get(Self::BACKGROUND);
|
||||||
|
|
||||||
// Realize overlays.
|
// Realize overlays.
|
||||||
for frame in &mut frames {
|
for frame in &mut fragment {
|
||||||
let size = frame.size();
|
let size = frame.size();
|
||||||
let pad = padding.resolve(styles).relative_to(size);
|
let pad = padding.resolve(styles).relative_to(size);
|
||||||
let pw = size.x - pad.left - pad.right;
|
let pw = size.x - pad.left - pad.right;
|
||||||
@ -118,7 +118,7 @@ impl PageNode {
|
|||||||
] {
|
] {
|
||||||
if let Some(content) = marginal.resolve(world, page)? {
|
if let Some(content) = marginal.resolve(world, page)? {
|
||||||
let pod = Regions::one(area, area, Axes::splat(true));
|
let pod = Regions::one(area, area, Axes::splat(true));
|
||||||
let sub = content.layout_block(world, styles, &pod)?.remove(0);
|
let sub = content.layout(world, styles, &pod)?.into_frame();
|
||||||
if std::ptr::eq(marginal, background) {
|
if std::ptr::eq(marginal, background) {
|
||||||
frame.prepend_frame(pos, sub);
|
frame.prepend_frame(pos, sub);
|
||||||
} else {
|
} else {
|
||||||
@ -130,7 +130,7 @@ impl PageNode {
|
|||||||
page += 1;
|
page += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(frames)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use crate::prelude::*;
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct PlaceNode(pub Content);
|
pub struct PlaceNode(pub Content);
|
||||||
|
|
||||||
#[node(LayoutBlock, Behave)]
|
#[node(Layout, Behave)]
|
||||||
impl PlaceNode {
|
impl PlaceNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
|
let aligns = args.find()?.unwrap_or(Axes::with_x(Some(GenAlign::Start)));
|
||||||
@ -16,13 +16,13 @@ impl PlaceNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for PlaceNode {
|
impl Layout for PlaceNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let out_of_flow = self.out_of_flow();
|
let out_of_flow = self.out_of_flow();
|
||||||
|
|
||||||
// The pod is the base area of the region because for absolute
|
// The pod is the base area of the region because for absolute
|
||||||
@ -33,14 +33,14 @@ impl LayoutBlock for PlaceNode {
|
|||||||
Regions::one(regions.base, regions.base, expand)
|
Regions::one(regions.base, regions.base, expand)
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frames = self.0.layout_block(world, styles, &pod)?;
|
let mut frame = self.0.layout(world, styles, &pod)?.into_frame();
|
||||||
|
|
||||||
// If expansion is off, zero all sizes so that we don't take up any
|
// If expansion is off, zero all sizes so that we don't take up any
|
||||||
// space in our parent. Otherwise, respect the expand settings.
|
// space in our parent. Otherwise, respect the expand settings.
|
||||||
let target = regions.expand.select(regions.first, Size::zero());
|
let target = regions.expand.select(regions.first, Size::zero());
|
||||||
frames[0].resize(target, Align::LEFT_TOP);
|
frame.resize(target, Align::LEFT_TOP);
|
||||||
|
|
||||||
Ok(frames)
|
Ok(Fragment::frame(frame))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ pub struct StackNode {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl StackNode {
|
impl StackNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@ -27,13 +27,13 @@ impl StackNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for StackNode {
|
impl Layout for StackNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
let mut layouter = StackLayouter::new(self.dir, regions, styles);
|
||||||
|
|
||||||
// Spacing to insert before the next block.
|
// Spacing to insert before the next block.
|
||||||
@ -196,9 +196,9 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.dir.start().into()
|
self.dir.start().into()
|
||||||
});
|
});
|
||||||
|
|
||||||
let frames = block.layout_block(world, styles, &self.regions)?;
|
let fragment = block.layout(world, styles, &self.regions)?;
|
||||||
let len = frames.len();
|
let len = fragment.len();
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
for (i, frame) in fragment.into_iter().enumerate() {
|
||||||
// Grow our size, shrink the region and save the frame for later.
|
// Grow our size, shrink the region and save the frame for later.
|
||||||
let size = frame.size();
|
let size = frame.size();
|
||||||
let size = match self.axis {
|
let size = match self.axis {
|
||||||
@ -276,9 +276,9 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish layouting and return the resulting frames.
|
/// Finish layouting and return the resulting frames.
|
||||||
fn finish(mut self) -> Vec<Frame> {
|
fn finish(mut self) -> Fragment {
|
||||||
self.finish_region();
|
self.finish_region();
|
||||||
self.finished
|
Fragment::frames(self.finished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ pub struct MoveNode {
|
|||||||
pub child: Content,
|
pub child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl MoveNode {
|
impl MoveNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
let dx = args.named("dx")?.unwrap_or_default();
|
let dx = args.named("dx")?.unwrap_or_default();
|
||||||
@ -24,21 +24,25 @@ impl MoveNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for MoveNode {
|
impl Layout for MoveNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut frame = self.child.layout_inline(world, styles, regions)?;
|
let mut fragment = self.child.layout(world, styles, regions)?;
|
||||||
let delta = self.delta.resolve(styles);
|
for frame in &mut fragment {
|
||||||
let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s));
|
let delta = self.delta.resolve(styles);
|
||||||
frame.translate(delta.to_point());
|
let delta = delta.zip(frame.size()).map(|(d, s)| d.relative_to(s));
|
||||||
Ok(frame)
|
frame.translate(delta.to_point());
|
||||||
|
}
|
||||||
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for MoveNode {}
|
||||||
|
|
||||||
/// Transform content without affecting layout.
|
/// Transform content without affecting layout.
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct TransformNode<const T: TransformKind> {
|
pub struct TransformNode<const T: TransformKind> {
|
||||||
@ -54,7 +58,7 @@ pub type RotateNode = TransformNode<ROTATE>;
|
|||||||
/// Scale content without affecting layout.
|
/// Scale content without affecting layout.
|
||||||
pub type ScaleNode = TransformNode<SCALE>;
|
pub type ScaleNode = TransformNode<SCALE>;
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl<const T: TransformKind> TransformNode<T> {
|
impl<const T: TransformKind> TransformNode<T> {
|
||||||
/// The origin of the transformation.
|
/// The origin of the transformation.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -78,26 +82,28 @@ impl<const T: TransformKind> TransformNode<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const T: TransformKind> LayoutInline for TransformNode<T> {
|
impl<const T: TransformKind> Layout for TransformNode<T> {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut frame = self.child.layout_inline(world, styles, regions)?;
|
let mut fragment = self.child.layout(world, styles, regions)?;
|
||||||
|
for frame in &mut fragment {
|
||||||
let origin = styles.get(Self::ORIGIN).unwrap_or(Align::CENTER_HORIZON);
|
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));
|
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(self.transform)
|
.pre_concat(self.transform)
|
||||||
.pre_concat(Transform::translate(-x, -y));
|
.pre_concat(Transform::translate(-x, -y));
|
||||||
frame.transform(transform);
|
frame.transform(transform);
|
||||||
|
}
|
||||||
Ok(frame)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const T: TransformKind> Inline for TransformNode<T> {}
|
||||||
|
|
||||||
/// Kinds of transformations.
|
/// Kinds of transformations.
|
||||||
///
|
///
|
||||||
/// The move transformation is handled separately.
|
/// The move transformation is handled separately.
|
||||||
|
@ -19,7 +19,7 @@ pub struct MathNode {
|
|||||||
pub display: bool,
|
pub display: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(Show, LayoutInline, Texify)]
|
#[node(Show, Layout, Inline, Texify)]
|
||||||
impl MathNode {
|
impl MathNode {
|
||||||
fn field(&self, name: &str) -> Option<Value> {
|
fn field(&self, name: &str) -> Option<Value> {
|
||||||
match name {
|
match name {
|
||||||
@ -48,17 +48,19 @@ impl Show for MathNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for MathNode {
|
impl Layout for MathNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
_: &Regions,
|
_: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
layout_tex(world, &self.texify(), self.display, styles)
|
layout_tex(world, &self.texify(), self.display, styles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for MathNode {}
|
||||||
|
|
||||||
impl Texify for MathNode {
|
impl Texify for MathNode {
|
||||||
fn texify(&self) -> EcoString {
|
fn texify(&self) -> EcoString {
|
||||||
self.children.iter().map(Texify::texify).collect()
|
self.children.iter().map(Texify::texify).collect()
|
||||||
|
@ -39,7 +39,7 @@ pub fn layout_tex(
|
|||||||
tex: &str,
|
tex: &str,
|
||||||
display: bool,
|
display: bool,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
// Load the font.
|
// Load the font.
|
||||||
let variant = variant(styles);
|
let variant = variant(styles);
|
||||||
let mut font = None;
|
let mut font = None;
|
||||||
@ -98,7 +98,8 @@ pub fn layout_tex(
|
|||||||
|
|
||||||
// Render into the frame.
|
// Render into the frame.
|
||||||
renderer.render(&layout, &mut backend);
|
renderer.render(&layout, &mut backend);
|
||||||
Ok(backend.frame)
|
|
||||||
|
Ok(Fragment::frame(backend.frame))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A ReX rendering backend that renders into a frame.
|
/// A ReX rendering backend that renders into a frame.
|
||||||
|
@ -27,6 +27,6 @@ pub use typst::util::{format_eco, EcoString};
|
|||||||
pub use typst::World;
|
pub use typst::World;
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::layout::{LayoutBlock, LayoutInline, Regions};
|
pub use crate::layout::{Inline, Layout, Regions};
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::shared::{Behave, Behaviour, ContentExt, StyleMapExt};
|
pub use crate::shared::{Behave, Behaviour, ContentExt, StyleMapExt};
|
||||||
|
@ -99,22 +99,22 @@ struct FillNode {
|
|||||||
child: Content,
|
child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl FillNode {}
|
impl FillNode {}
|
||||||
|
|
||||||
impl LayoutBlock for FillNode {
|
impl Layout for FillNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut frames = self.child.layout_block(world, styles, regions)?;
|
let mut fragment = self.child.layout(world, styles, regions)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut fragment {
|
||||||
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
let shape = Geometry::Rect(frame.size()).filled(self.fill);
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
Ok(frames)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,21 +127,21 @@ struct StrokeNode {
|
|||||||
child: Content,
|
child: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl StrokeNode {}
|
impl StrokeNode {}
|
||||||
|
|
||||||
impl LayoutBlock for StrokeNode {
|
impl Layout for StrokeNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut frames = self.child.layout_block(world, styles, regions)?;
|
let mut fragment = self.child.layout(world, styles, regions)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut fragment {
|
||||||
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
let shape = Geometry::Rect(frame.size()).stroked(self.stroke);
|
||||||
frame.prepend(Point::zero(), Element::Shape(shape));
|
frame.prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
Ok(frames)
|
Ok(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,8 @@ impl LayoutRoot for DocumentNode {
|
|||||||
let mut pages = vec![];
|
let mut pages = vec![];
|
||||||
for (page, map) in self.0.iter() {
|
for (page, map) in self.0.iter() {
|
||||||
let number = 1 + pages.len();
|
let number = 1 + pages.len();
|
||||||
pages.extend(page.layout(world, number, styles.chain(map))?);
|
let fragment = page.layout(world, number, styles.chain(map))?;
|
||||||
|
pages.extend(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Document {
|
Ok(Document {
|
||||||
|
@ -18,7 +18,7 @@ pub type EnumNode = ListNode<ENUM>;
|
|||||||
/// A description list.
|
/// A description list.
|
||||||
pub type DescNode = ListNode<DESC>;
|
pub type DescNode = ListNode<DESC>;
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl<const L: ListKind> ListNode<L> {
|
impl<const L: ListKind> ListNode<L> {
|
||||||
/// How the list is labelled.
|
/// How the list is labelled.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -75,13 +75,13 @@ impl<const L: ListKind> ListNode<L> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const L: ListKind> LayoutBlock for ListNode<L> {
|
impl<const L: ListKind> Layout for ListNode<L> {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let mut cells = vec![];
|
let mut cells = vec![];
|
||||||
let mut number = 1;
|
let mut number = 1;
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ impl<const L: ListKind> LayoutBlock for ListNode<L> {
|
|||||||
gutter: Axes::with_y(vec![gutter.into()]),
|
gutter: Axes::with_y(vec![gutter.into()]),
|
||||||
cells,
|
cells,
|
||||||
}
|
}
|
||||||
.layout_block(world, styles, regions)
|
.layout(world, styles, regions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ pub struct TableNode {
|
|||||||
pub cells: Vec<Content>,
|
pub cells: Vec<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl TableNode {
|
impl TableNode {
|
||||||
/// How to fill the cells.
|
/// How to fill the cells.
|
||||||
#[property(referenced)]
|
#[property(referenced)]
|
||||||
@ -50,13 +50,13 @@ impl TableNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for TableNode {
|
impl Layout for TableNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
let fill = styles.get(Self::FILL);
|
let fill = styles.get(Self::FILL);
|
||||||
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
|
let stroke = styles.get(Self::STROKE).map(PartialStroke::unwrap_or_default);
|
||||||
let padding = styles.get(Self::PADDING);
|
let padding = styles.get(Self::PADDING);
|
||||||
@ -89,7 +89,7 @@ impl LayoutBlock for TableNode {
|
|||||||
gutter: self.gutter.clone(),
|
gutter: self.gutter.clone(),
|
||||||
cells,
|
cells,
|
||||||
}
|
}
|
||||||
.layout_block(world, styles, regions)
|
.layout(world, styles, regions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ use crate::prelude::*;
|
|||||||
#[derive(Hash)]
|
#[derive(Hash)]
|
||||||
pub struct ParNode(pub StyleVec<Content>);
|
pub struct ParNode(pub StyleVec<Content>);
|
||||||
|
|
||||||
#[node(LayoutBlock)]
|
#[node(Layout)]
|
||||||
impl ParNode {
|
impl ParNode {
|
||||||
/// The indent the first line of a consecutive paragraph should have.
|
/// The indent the first line of a consecutive paragraph should have.
|
||||||
#[property(resolve)]
|
#[property(resolve)]
|
||||||
@ -43,13 +43,13 @@ impl ParNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutBlock for ParNode {
|
impl Layout for ParNode {
|
||||||
fn layout_block(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Vec<Frame>> {
|
) -> SourceResult<Fragment> {
|
||||||
// Collect all text into one string for BiDi analysis.
|
// Collect all text into one string for BiDi analysis.
|
||||||
let (text, segments) = collect(self, &styles);
|
let (text, segments) = collect(self, &styles);
|
||||||
|
|
||||||
@ -130,24 +130,26 @@ impl Unlabellable for ParbreakNode {}
|
|||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct RepeatNode(pub Content);
|
pub struct RepeatNode(pub Content);
|
||||||
|
|
||||||
#[node(LayoutInline)]
|
#[node(Layout, Inline)]
|
||||||
impl RepeatNode {
|
impl RepeatNode {
|
||||||
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(_: &Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
Ok(Self(args.expect("body")?).pack())
|
Ok(Self(args.expect("body")?).pack())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutInline for RepeatNode {
|
impl Layout for RepeatNode {
|
||||||
fn layout_inline(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
world: Tracked<dyn World>,
|
world: Tracked<dyn World>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> SourceResult<Frame> {
|
) -> SourceResult<Fragment> {
|
||||||
self.0.layout_inline(world, styles, regions)
|
self.0.layout(world, styles, regions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Inline for RepeatNode {}
|
||||||
|
|
||||||
/// Range of a substring of text.
|
/// Range of a substring of text.
|
||||||
type Range = std::ops::Range<usize>;
|
type Range = std::ops::Range<usize>;
|
||||||
|
|
||||||
@ -405,7 +407,7 @@ fn collect<'a>(
|
|||||||
.find_map(|child| {
|
.find_map(|child| {
|
||||||
if child.is::<TextNode>() || child.is::<SmartQuoteNode>() {
|
if child.is::<TextNode>() || child.is::<SmartQuoteNode>() {
|
||||||
Some(true)
|
Some(true)
|
||||||
} else if child.has::<dyn LayoutInline>() {
|
} else if child.has::<dyn Inline>() {
|
||||||
Some(false)
|
Some(false)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@ -460,7 +462,7 @@ fn collect<'a>(
|
|||||||
} else if let Some(&node) = child.to::<HNode>() {
|
} else if let Some(&node) = child.to::<HNode>() {
|
||||||
full.push(SPACING_REPLACE);
|
full.push(SPACING_REPLACE);
|
||||||
Segment::Spacing(node.amount)
|
Segment::Spacing(node.amount)
|
||||||
} else if child.has::<dyn LayoutInline>() {
|
} else if child.has::<dyn Inline>() {
|
||||||
full.push(NODE_REPLACE);
|
full.push(NODE_REPLACE);
|
||||||
Segment::Inline(child)
|
Segment::Inline(child)
|
||||||
} else {
|
} else {
|
||||||
@ -530,7 +532,7 @@ fn prepare<'a>(
|
|||||||
} else {
|
} else {
|
||||||
let size = Size::new(regions.first.x, regions.base.y);
|
let size = Size::new(regions.first.x, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Axes::splat(false));
|
let pod = Regions::one(size, regions.base, Axes::splat(false));
|
||||||
let mut frame = inline.layout_inline(world, styles, &pod)?;
|
let mut frame = inline.layout(world, styles, &pod)?.into_frame();
|
||||||
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
frame.translate(Point::with_y(styles.get(TextNode::BASELINE)));
|
||||||
items.push(Item::Frame(frame));
|
items.push(Item::Frame(frame));
|
||||||
}
|
}
|
||||||
@ -1011,7 +1013,7 @@ fn line<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Combine layouted lines into one frame per region.
|
/// Combine layouted lines into one frame per region.
|
||||||
fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult<Vec<Frame>> {
|
fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult<Fragment> {
|
||||||
// Determine the paragraph's width: Full width of the region if we
|
// Determine the paragraph's width: Full width of the region if we
|
||||||
// should expand or there's fractional spacing, fit-to-width otherwise.
|
// should expand or there's fractional spacing, fit-to-width otherwise.
|
||||||
let mut width = regions.first.x;
|
let mut width = regions.first.x;
|
||||||
@ -1050,7 +1052,7 @@ fn stack(p: &Preparation, lines: &[Line], regions: &Regions) -> SourceResult<Vec
|
|||||||
}
|
}
|
||||||
|
|
||||||
finished.push(output);
|
finished.push(output);
|
||||||
Ok(finished)
|
Ok(Fragment::frames(finished))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit to a line and build its frame.
|
/// Commit to a line and build its frame.
|
||||||
@ -1137,7 +1139,7 @@ fn commit(
|
|||||||
let fill = Fr::one().share(fr, remaining);
|
let fill = Fr::one().share(fr, remaining);
|
||||||
let size = Size::new(fill, regions.base.y);
|
let size = Size::new(fill, regions.base.y);
|
||||||
let pod = Regions::one(size, regions.base, Axes::new(false, false));
|
let pod = Regions::one(size, regions.base, Axes::new(false, false));
|
||||||
let frame = repeat.layout_inline(p.world, *styles, &pod)?;
|
let frame = repeat.layout(p.world, *styles, &pod)?.into_frame();
|
||||||
let width = frame.width();
|
let width = frame.width();
|
||||||
let count = (fill / width).floor();
|
let count = (fill / width).floor();
|
||||||
let remaining = fill % width;
|
let remaining = fill % width;
|
||||||
|
108
src/doc.rs
108
src/doc.rs
@ -14,7 +14,7 @@ use crate::model::{dict, Dict, Value};
|
|||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// A finished document with metadata and page frames.
|
/// A finished document with metadata and page frames.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
/// The document's metadata.
|
/// The document's metadata.
|
||||||
pub metadata: Metadata,
|
pub metadata: Metadata,
|
||||||
@ -31,6 +31,73 @@ pub struct Metadata {
|
|||||||
pub author: Option<EcoString>,
|
pub author: Option<EcoString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A partial layout result.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Fragment(Vec<Frame>);
|
||||||
|
|
||||||
|
impl Fragment {
|
||||||
|
/// Create a fragment from a single frame.
|
||||||
|
pub fn frame(frame: Frame) -> Self {
|
||||||
|
Self(vec![frame])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a fragment from multiple frames.
|
||||||
|
pub fn frames(frames: Vec<Frame>) -> Self {
|
||||||
|
Self(frames)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of frames in the fragment.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract the first and only frame.
|
||||||
|
///
|
||||||
|
/// Panics if there are multiple frames.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn into_frame(self) -> Frame {
|
||||||
|
assert_eq!(self.0.len(), 1, "expected exactly one frame");
|
||||||
|
self.0.into_iter().next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the contained frames.
|
||||||
|
pub fn iter(&self) -> std::slice::Iter<Frame> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the contained frames.
|
||||||
|
pub fn iter_mut(&mut self) -> std::slice::IterMut<Frame> {
|
||||||
|
self.0.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Fragment {
|
||||||
|
type Item = Frame;
|
||||||
|
type IntoIter = std::vec::IntoIter<Frame>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a Fragment {
|
||||||
|
type Item = &'a Frame;
|
||||||
|
type IntoIter = std::slice::Iter<'a, Frame>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a mut Fragment {
|
||||||
|
type Item = &'a mut Frame;
|
||||||
|
type IntoIter = std::slice::IterMut<'a, Frame>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.iter_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A finished layout with elements at fixed positions.
|
/// A finished layout with elements at fixed positions.
|
||||||
#[derive(Default, Clone, Eq, PartialEq)]
|
#[derive(Default, Clone, Eq, PartialEq)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
@ -39,8 +106,6 @@ pub struct Frame {
|
|||||||
/// The baseline of the frame measured from the top. If this is `None`, the
|
/// The baseline of the frame measured from the top. If this is `None`, the
|
||||||
/// frame's implicit baseline is at the bottom.
|
/// frame's implicit baseline is at the bottom.
|
||||||
baseline: Option<Abs>,
|
baseline: Option<Abs>,
|
||||||
/// The semantic role of the frame.
|
|
||||||
role: Option<Role>,
|
|
||||||
/// The elements composing this layout.
|
/// The elements composing this layout.
|
||||||
elements: Arc<Vec<(Point, Element)>>,
|
elements: Arc<Vec<(Point, Element)>>,
|
||||||
}
|
}
|
||||||
@ -53,12 +118,7 @@ impl Frame {
|
|||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(size: Size) -> Self {
|
pub fn new(size: Size) -> Self {
|
||||||
assert!(size.is_finite());
|
assert!(size.is_finite());
|
||||||
Self {
|
Self { size, baseline: None, elements: Arc::new(vec![]) }
|
||||||
size,
|
|
||||||
baseline: None,
|
|
||||||
role: None,
|
|
||||||
elements: Arc::new(vec![]),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The size of the frame.
|
/// The size of the frame.
|
||||||
@ -96,11 +156,6 @@ impl Frame {
|
|||||||
self.baseline = Some(baseline);
|
self.baseline = Some(baseline);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The role of the frame.
|
|
||||||
pub fn role(&self) -> Option<Role> {
|
|
||||||
self.role
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over the elements inside this frame alongside their
|
/// An iterator over the elements inside this frame alongside their
|
||||||
/// positions relative to the top-left of the frame.
|
/// positions relative to the top-left of the frame.
|
||||||
pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> {
|
pub fn elements(&self) -> std::slice::Iter<'_, (Point, Element)> {
|
||||||
@ -125,7 +180,7 @@ impl Frame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserting elements and subframes.
|
/// Insert elements and subframes.
|
||||||
impl Frame {
|
impl Frame {
|
||||||
/// The layer the next item will be added on. This corresponds to the number
|
/// The layer the next item will be added on. This corresponds to the number
|
||||||
/// of elements in the frame.
|
/// of elements in the frame.
|
||||||
@ -141,7 +196,7 @@ impl Frame {
|
|||||||
/// Add a frame at a position in the foreground.
|
/// Add a frame at a position in the foreground.
|
||||||
///
|
///
|
||||||
/// Automatically decides whether to inline the frame or to include it as a
|
/// Automatically decides whether to inline the frame or to include it as a
|
||||||
/// group based on the number of elements in and the role of the frame.
|
/// group based on the number of elements in it.
|
||||||
pub fn push_frame(&mut self, pos: Point, frame: Frame) {
|
pub fn push_frame(&mut self, pos: Point, frame: Frame) {
|
||||||
if self.should_inline(&frame) {
|
if self.should_inline(&frame) {
|
||||||
self.inline(self.layer(), pos, frame);
|
self.inline(self.layer(), pos, frame);
|
||||||
@ -185,8 +240,7 @@ impl Frame {
|
|||||||
|
|
||||||
/// Whether the given frame should be inlined.
|
/// Whether the given frame should be inlined.
|
||||||
fn should_inline(&self, frame: &Frame) -> bool {
|
fn should_inline(&self, frame: &Frame) -> bool {
|
||||||
(self.elements.is_empty() || frame.elements.len() <= 5)
|
self.elements.is_empty() || frame.elements.len() <= 5
|
||||||
&& frame.role().map_or(true, |role| role.is_weak())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inline a frame at the given layer.
|
/// Inline a frame at the given layer.
|
||||||
@ -294,10 +348,6 @@ impl Frame {
|
|||||||
|
|
||||||
impl Debug for Frame {
|
impl Debug for Frame {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if let Some(role) = self.role {
|
|
||||||
write!(f, "{role:?} ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.debug_list()
|
f.debug_list()
|
||||||
.entries(self.elements.iter().map(|(_, element)| element))
|
.entries(self.elements.iter().map(|(_, element)| element))
|
||||||
.finish()
|
.finish()
|
||||||
@ -503,8 +553,8 @@ impl Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A semantic role of a frame.
|
/// Standard semantic roles.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum Role {
|
pub enum Role {
|
||||||
/// A paragraph.
|
/// A paragraph.
|
||||||
Paragraph,
|
Paragraph,
|
||||||
@ -542,13 +592,3 @@ pub enum Role {
|
|||||||
/// A page foreground.
|
/// A page foreground.
|
||||||
Foreground,
|
Foreground,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Role {
|
|
||||||
/// Whether the role describes a generic element and is not very
|
|
||||||
/// descriptive.
|
|
||||||
pub fn is_weak(self) -> bool {
|
|
||||||
// In Typst, all text is in a paragraph, so paragraph isn't very
|
|
||||||
// descriptive.
|
|
||||||
matches!(self, Self::Paragraph | Self::GenericBlock | Self::GenericInline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -12,7 +12,7 @@ use std::hash::Hash;
|
|||||||
use pdf_writer::types::Direction;
|
use pdf_writer::types::Direction;
|
||||||
use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
use pdf_writer::{Finish, Name, PdfWriter, Ref, TextStr};
|
||||||
|
|
||||||
use self::outline::{Heading, HeadingNode};
|
use self::outline::HeadingNode;
|
||||||
use self::page::Page;
|
use self::page::Page;
|
||||||
use crate::doc::{Document, Lang, Metadata};
|
use crate::doc::{Document, Lang, Metadata};
|
||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
|
@ -4,43 +4,34 @@ use super::{AbsExt, PdfContext, RefExt};
|
|||||||
use crate::geom::{Abs, Point};
|
use crate::geom::{Abs, Point};
|
||||||
use crate::util::EcoString;
|
use crate::util::EcoString;
|
||||||
|
|
||||||
/// A heading that can later be linked in the outline panel.
|
/// A heading in the outline panel.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Heading {
|
pub struct HeadingNode {
|
||||||
pub content: EcoString,
|
pub content: EcoString,
|
||||||
pub level: usize,
|
pub level: usize,
|
||||||
pub position: Point,
|
pub position: Point,
|
||||||
pub page: Ref,
|
pub page: Ref,
|
||||||
}
|
|
||||||
|
|
||||||
/// A node in the outline tree.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct HeadingNode {
|
|
||||||
pub heading: Heading,
|
|
||||||
pub children: Vec<HeadingNode>,
|
pub children: Vec<HeadingNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HeadingNode {
|
impl HeadingNode {
|
||||||
pub fn leaf(heading: Heading) -> Self {
|
|
||||||
HeadingNode { heading, children: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
1 + self.children.iter().map(Self::len).sum::<usize>()
|
1 + self.children.iter().map(Self::len).sum::<usize>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, other: Heading, level: usize) -> bool {
|
#[allow(unused)]
|
||||||
if level >= other.level {
|
pub fn try_insert(&mut self, child: Self, level: usize) -> bool {
|
||||||
|
if level >= child.level {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(child) = self.children.last_mut() {
|
if let Some(last) = self.children.last_mut() {
|
||||||
if child.insert(other.clone(), level + 1) {
|
if last.try_insert(child.clone(), level + 1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.children.push(Self::leaf(other));
|
self.children.push(child);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,10 +65,10 @@ pub fn write_outline_item(
|
|||||||
outline.count(-(node.children.len() as i32));
|
outline.count(-(node.children.len() as i32));
|
||||||
}
|
}
|
||||||
|
|
||||||
outline.title(TextStr(&node.heading.content));
|
outline.title(TextStr(&node.content));
|
||||||
outline.dest_direct().page(node.heading.page).xyz(
|
outline.dest_direct().page(node.page).xyz(
|
||||||
node.heading.position.x.to_f32(),
|
node.position.x.to_f32(),
|
||||||
(node.heading.position.y + Abs::pt(3.0)).to_f32(),
|
(node.position.y + Abs::pt(3.0)).to_f32(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -2,10 +2,8 @@ use pdf_writer::types::{ActionType, AnnotationType, ColorSpaceOperand};
|
|||||||
use pdf_writer::writers::ColorSpace;
|
use pdf_writer::writers::ColorSpace;
|
||||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
||||||
|
|
||||||
use super::{
|
use super::{deflate, AbsExt, EmExt, PdfContext, RefExt, D65_GRAY, SRGB};
|
||||||
deflate, AbsExt, EmExt, Heading, HeadingNode, PdfContext, RefExt, D65_GRAY, SRGB,
|
use crate::doc::{Destination, Element, Frame, Group, Text};
|
||||||
};
|
|
||||||
use crate::doc::{Destination, Element, Frame, Group, Role, Text};
|
|
||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
self, Abs, Color, Em, Geometry, Numeric, Paint, Point, Ratio, Shape, Size, Stroke,
|
||||||
@ -281,23 +279,6 @@ impl PageContext<'_, '_> {
|
|||||||
|
|
||||||
/// Encode a frame into the content stream.
|
/// Encode a frame into the content stream.
|
||||||
fn write_frame(ctx: &mut PageContext, frame: &Frame) {
|
fn write_frame(ctx: &mut PageContext, frame: &Frame) {
|
||||||
if let Some(Role::Heading { level, outlined: true }) = frame.role() {
|
|
||||||
let heading = Heading {
|
|
||||||
position: Point::new(ctx.state.transform.tx, ctx.state.transform.ty),
|
|
||||||
content: frame.text(),
|
|
||||||
page: ctx.page_ref,
|
|
||||||
level: level.get(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(last) = ctx.parent.heading_tree.last_mut() {
|
|
||||||
if !last.insert(heading.clone(), 1) {
|
|
||||||
ctx.parent.heading_tree.push(HeadingNode::leaf(heading))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx.parent.heading_tree.push(HeadingNode::leaf(heading))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for &(pos, ref element) in frame.elements() {
|
for &(pos, ref element) in frame.elements() {
|
||||||
let x = pos.x.to_f32();
|
let x = pos.x.to_f32();
|
||||||
let y = pos.y.to_f32();
|
let y = pos.y.to_f32();
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.5 KiB |
@ -22,22 +22,24 @@
|
|||||||
#let star(width, ..args) = box(width: width, height: width)[
|
#let star(width, ..args) = box(width: width, height: width)[
|
||||||
#set text(spacing: 0%)
|
#set text(spacing: 0%)
|
||||||
#set line(..args)
|
#set line(..args)
|
||||||
|
#set par(align: left)
|
||||||
#align(left)[
|
#line(length: +30%, origin: (09.0%, 02%))
|
||||||
#line(length: +30%, origin: (09.0%, 02%))
|
#line(length: +30%, origin: (38.7%, 02%), angle: -72deg)
|
||||||
#line(length: +30%, origin: (38.7%, 02%), angle: -72deg)
|
#line(length: +30%, origin: (57.5%, 02%), angle: 252deg)
|
||||||
#line(length: +30%, origin: (57.5%, 02%), angle: 252deg)
|
#line(length: +30%, origin: (57.3%, 02%))
|
||||||
#line(length: +30%, origin: (57.3%, 02%))
|
#line(length: -30%, origin: (88.0%, 02%), angle: -36deg)
|
||||||
#line(length: -30%, origin: (88.0%, 02%), angle: -36deg)
|
#line(length: +30%, origin: (73.3%, 48%), angle: 252deg)
|
||||||
#line(length: +30%, origin: (73.3%, 48%), angle: 252deg)
|
#line(length: -30%, origin: (73.5%, 48%), angle: 36deg)
|
||||||
#line(length: -30%, origin: (73.5%, 48%), angle: 36deg)
|
#line(length: +30%, origin: (25.4%, 48%), angle: -36deg)
|
||||||
#line(length: +30%, origin: (25.4%, 48%), angle: -36deg)
|
#line(length: +30%, origin: (25.6%, 48%), angle: -72deg)
|
||||||
#line(length: +30%, origin: (25.6%, 48%), angle: -72deg)
|
#line(length: +32%, origin: (8.50%, 02%), angle: 34deg)
|
||||||
#line(length: +32%, origin: (8.50%, 02%), angle: 34deg)
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
#align(center, grid(columns: (1fr,) * 3, ..((star(20pt, stroke: 0.5pt),) * 9)))
|
#align(center, grid(
|
||||||
|
columns: 3,
|
||||||
|
column-gutter: 10pt,
|
||||||
|
..((star(20pt, stroke: 0.5pt),) * 9)
|
||||||
|
))
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test errors.
|
// Test errors.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user