mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Strongly typed groups 👔
This commit is contained in:
parent
dd4a4545a6
commit
f29207d999
176
src/eval/mod.rs
176
src/eval/mod.rs
@ -22,7 +22,7 @@ use fontdock::FontStyle;
|
|||||||
|
|
||||||
use crate::diag::Diag;
|
use crate::diag::Diag;
|
||||||
use crate::diag::{Deco, Feedback, Pass};
|
use crate::diag::{Deco, Feedback, Pass};
|
||||||
use crate::geom::{Gen, Length, Relative};
|
use crate::geom::{Align, Dir, Gen, Length, Linear, Relative, Sides, Size};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
|
Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
|
||||||
};
|
};
|
||||||
@ -113,6 +113,87 @@ impl EvalContext {
|
|||||||
self.inner.push(node);
|
self.inner.push(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start a page group based on the active page state.
|
||||||
|
///
|
||||||
|
/// If `hard` is false, empty page runs will be omitted from the output.
|
||||||
|
///
|
||||||
|
/// This also starts an inner paragraph.
|
||||||
|
pub fn start_page_group(&mut self, hard: bool) {
|
||||||
|
self.start_group(PageGroup {
|
||||||
|
size: self.state.page.size,
|
||||||
|
padding: self.state.page.margins(),
|
||||||
|
dirs: self.state.dirs,
|
||||||
|
aligns: self.state.aligns,
|
||||||
|
hard,
|
||||||
|
});
|
||||||
|
self.start_par_group();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End a page group and push it to the finished page runs.
|
||||||
|
///
|
||||||
|
/// This also ends an inner paragraph.
|
||||||
|
pub fn end_page_group(&mut self) {
|
||||||
|
self.end_par_group();
|
||||||
|
let (group, children) = self.end_group::<PageGroup>();
|
||||||
|
if group.hard || !children.is_empty() {
|
||||||
|
self.runs.push(Pages {
|
||||||
|
size: group.size,
|
||||||
|
child: LayoutNode::dynamic(Pad {
|
||||||
|
padding: group.padding,
|
||||||
|
child: LayoutNode::dynamic(Stack {
|
||||||
|
dirs: group.dirs,
|
||||||
|
aligns: group.aligns,
|
||||||
|
expansion: Gen::new(Expansion::Fill, Expansion::Fill),
|
||||||
|
children,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a content group.
|
||||||
|
///
|
||||||
|
/// This also starts an inner paragraph.
|
||||||
|
pub fn start_content_group(&mut self) {
|
||||||
|
self.start_group(ContentGroup);
|
||||||
|
self.start_par_group();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End a content group and return the resulting nodes.
|
||||||
|
///
|
||||||
|
/// This also ends an inner paragraph.
|
||||||
|
pub fn end_content_group(&mut self) -> Vec<LayoutNode> {
|
||||||
|
self.end_par_group();
|
||||||
|
self.end_group::<ContentGroup>().1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a paragraph group based on the active text state.
|
||||||
|
pub fn start_par_group(&mut self) {
|
||||||
|
let em = self.state.font.font_size();
|
||||||
|
self.start_group(ParGroup {
|
||||||
|
dirs: self.state.dirs,
|
||||||
|
aligns: self.state.aligns,
|
||||||
|
line_spacing: self.state.par.line_spacing.eval(em),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// End a paragraph group and push it to its parent group if it's not empty.
|
||||||
|
pub fn end_par_group(&mut self) {
|
||||||
|
let (group, children) = self.end_group::<ParGroup>();
|
||||||
|
if !children.is_empty() {
|
||||||
|
// FIXME: This is a hack and should be superseded by something
|
||||||
|
// better.
|
||||||
|
let cross_expansion = Expansion::fill_if(self.groups.len() <= 1);
|
||||||
|
self.push(Par {
|
||||||
|
dirs: group.dirs,
|
||||||
|
aligns: group.aligns,
|
||||||
|
cross_expansion,
|
||||||
|
line_spacing: group.line_spacing,
|
||||||
|
children,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Start a layouting group.
|
/// Start a layouting group.
|
||||||
///
|
///
|
||||||
/// All further calls to [`push`] will collect nodes for this group.
|
/// All further calls to [`push`] will collect nodes for this group.
|
||||||
@ -121,7 +202,7 @@ impl EvalContext {
|
|||||||
///
|
///
|
||||||
/// [`push`]: #method.push
|
/// [`push`]: #method.push
|
||||||
/// [`end_group`]: #method.end_group
|
/// [`end_group`]: #method.end_group
|
||||||
pub fn start_group<T: 'static>(&mut self, meta: T) {
|
fn start_group<T: 'static>(&mut self, meta: T) {
|
||||||
self.groups.push((Box::new(meta), std::mem::take(&mut self.inner)));
|
self.groups.push((Box::new(meta), std::mem::take(&mut self.inner)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +211,7 @@ impl EvalContext {
|
|||||||
/// This returns the stored metadata and the collected nodes.
|
/// This returns the stored metadata and the collected nodes.
|
||||||
///
|
///
|
||||||
/// [`start_group`]: #method.start_group
|
/// [`start_group`]: #method.start_group
|
||||||
pub fn end_group<T: 'static>(&mut self) -> (T, Vec<LayoutNode>) {
|
fn end_group<T: 'static>(&mut self) -> (T, Vec<LayoutNode>) {
|
||||||
if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() {
|
if let Some(&LayoutNode::Spacing(spacing)) = self.inner.last() {
|
||||||
if spacing.softness == Softness::Soft {
|
if spacing.softness == Softness::Soft {
|
||||||
self.inner.pop();
|
self.inner.pop();
|
||||||
@ -142,69 +223,6 @@ impl EvalContext {
|
|||||||
(group, std::mem::replace(&mut self.inner, outer))
|
(group, std::mem::replace(&mut self.inner, outer))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a page run group based on the active page state.
|
|
||||||
///
|
|
||||||
/// If `hard` is false, empty page runs will be omitted from the output.
|
|
||||||
///
|
|
||||||
/// This also starts an inner paragraph.
|
|
||||||
pub fn start_page_group(&mut self, hard: bool) {
|
|
||||||
let size = self.state.page.size;
|
|
||||||
let margins = self.state.page.margins();
|
|
||||||
let dirs = self.state.dirs;
|
|
||||||
let aligns = self.state.aligns;
|
|
||||||
self.start_group((size, margins, dirs, aligns, hard));
|
|
||||||
self.start_par_group();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End a page run group and push it to its parent group.
|
|
||||||
///
|
|
||||||
/// This also ends an inner paragraph.
|
|
||||||
pub fn end_page_group(&mut self) {
|
|
||||||
self.end_par_group();
|
|
||||||
let ((size, padding, dirs, aligns, hard), children) = self.end_group();
|
|
||||||
let hard: bool = hard;
|
|
||||||
if hard || !children.is_empty() {
|
|
||||||
self.runs.push(Pages {
|
|
||||||
size,
|
|
||||||
child: LayoutNode::dynamic(Pad {
|
|
||||||
padding,
|
|
||||||
child: LayoutNode::dynamic(Stack {
|
|
||||||
dirs,
|
|
||||||
aligns,
|
|
||||||
expansion: Gen::new(Expansion::Fill, Expansion::Fill),
|
|
||||||
children,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a paragraph group based on the active text state.
|
|
||||||
pub fn start_par_group(&mut self) {
|
|
||||||
let dirs = self.state.dirs;
|
|
||||||
let em = self.state.font.font_size();
|
|
||||||
let line_spacing = self.state.par.line_spacing.eval(em);
|
|
||||||
let aligns = self.state.aligns;
|
|
||||||
self.start_group((dirs, line_spacing, aligns));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// End a paragraph group and push it to its parent group if its not empty.
|
|
||||||
pub fn end_par_group(&mut self) {
|
|
||||||
let ((dirs, line_spacing, aligns), children) = self.end_group();
|
|
||||||
if !children.is_empty() {
|
|
||||||
// FIXME: This is a hack and should be superseded by constraints
|
|
||||||
// having min and max size.
|
|
||||||
let cross_expansion = Expansion::fill_if(self.groups.len() <= 1);
|
|
||||||
self.push(Par {
|
|
||||||
dirs,
|
|
||||||
aligns,
|
|
||||||
cross_expansion,
|
|
||||||
line_spacing,
|
|
||||||
children,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct a text node from the given string based on the active text
|
/// Construct a text node from the given string based on the active text
|
||||||
/// state.
|
/// state.
|
||||||
pub fn make_text_node(&self, text: String) -> Text {
|
pub fn make_text_node(&self, text: String) -> Text {
|
||||||
@ -233,6 +251,25 @@ impl EvalContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A group for page runs.
|
||||||
|
struct PageGroup {
|
||||||
|
size: Size,
|
||||||
|
padding: Sides<Linear>,
|
||||||
|
dirs: Gen<Dir>,
|
||||||
|
aligns: Gen<Align>,
|
||||||
|
hard: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A group for generic content.
|
||||||
|
struct ContentGroup;
|
||||||
|
|
||||||
|
/// A group for paragraphs.
|
||||||
|
struct ParGroup {
|
||||||
|
dirs: Gen<Dir>,
|
||||||
|
aligns: Gen<Align>,
|
||||||
|
line_spacing: Length,
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate an item.
|
/// Evaluate an item.
|
||||||
///
|
///
|
||||||
/// _Note_: Evaluation is not necessarily pure, it may change the active state.
|
/// _Note_: Evaluation is not necessarily pure, it may change the active state.
|
||||||
@ -320,9 +357,16 @@ impl Eval for NodeRaw {
|
|||||||
families.list.insert(0, "monospace".to_string());
|
families.list.insert(0, "monospace".to_string());
|
||||||
families.flatten();
|
families.flatten();
|
||||||
|
|
||||||
|
let em = ctx.state.font.font_size();
|
||||||
|
let line_spacing = ctx.state.par.line_spacing.eval(em);
|
||||||
|
|
||||||
let mut children = vec![];
|
let mut children = vec![];
|
||||||
for line in &self.lines {
|
for line in &self.lines {
|
||||||
children.push(LayoutNode::Text(ctx.make_text_node(line.clone())));
|
children.push(LayoutNode::Text(ctx.make_text_node(line.clone())));
|
||||||
|
children.push(LayoutNode::Spacing(Spacing {
|
||||||
|
amount: line_spacing,
|
||||||
|
softness: Softness::Hard,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.push(Stack {
|
ctx.push(Stack {
|
||||||
|
@ -32,14 +32,14 @@ pub fn align(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
.chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
|
.chain(hor.into_iter().map(|align| (Some(SpecAxis::Horizontal), align)))
|
||||||
.chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));
|
.chain(ver.into_iter().map(|align| (Some(SpecAxis::Vertical), align)));
|
||||||
|
|
||||||
let prev_main = ctx.state.aligns.main;
|
let aligns = dedup_aligns(ctx, iter);
|
||||||
ctx.state.aligns = dedup_aligns(ctx, iter);
|
if aligns.main != ctx.state.aligns.main {
|
||||||
|
|
||||||
if prev_main != ctx.state.aligns.main {
|
|
||||||
ctx.end_par_group();
|
ctx.end_par_group();
|
||||||
ctx.start_par_group();
|
ctx.start_par_group();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.state.aligns = aligns;
|
||||||
|
|
||||||
if let Some(body) = body {
|
if let Some(body) = body {
|
||||||
body.eval(ctx);
|
body.eval(ctx);
|
||||||
ctx.state = snapshot;
|
ctx.state = snapshot;
|
||||||
|
@ -8,6 +8,8 @@ use crate::prelude::*;
|
|||||||
/// - `width`: The width of the box (length or relative to parent's width).
|
/// - `width`: The width of the box (length or relative to parent's width).
|
||||||
/// - `height`: The height of the box (length or relative to parent's height).
|
/// - `height`: The height of the box (length or relative to parent's height).
|
||||||
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
||||||
|
let snapshot = ctx.state.clone();
|
||||||
|
|
||||||
let body = args.find::<SynTree>().unwrap_or_default();
|
let body = args.find::<SynTree>().unwrap_or_default();
|
||||||
let width = args.get::<_, Linear>(ctx, "width");
|
let width = args.get::<_, Linear>(ctx, "width");
|
||||||
let height = args.get::<_, Linear>(ctx, "height");
|
let height = args.get::<_, Linear>(ctx, "height");
|
||||||
@ -16,13 +18,9 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
let dirs = ctx.state.dirs;
|
let dirs = ctx.state.dirs;
|
||||||
let aligns = ctx.state.aligns;
|
let aligns = ctx.state.aligns;
|
||||||
|
|
||||||
let snapshot = ctx.state.clone();
|
ctx.start_content_group();
|
||||||
|
|
||||||
ctx.start_group(());
|
|
||||||
ctx.start_par_group();
|
|
||||||
body.eval(ctx);
|
body.eval(ctx);
|
||||||
ctx.end_par_group();
|
let children = ctx.end_content_group();
|
||||||
let ((), children) = ctx.end_group();
|
|
||||||
|
|
||||||
ctx.push(Fixed {
|
ctx.push(Fixed {
|
||||||
width,
|
width,
|
||||||
@ -40,6 +38,5 @@ pub fn boxed(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ctx.state = snapshot;
|
ctx.state = snapshot;
|
||||||
|
|
||||||
Value::None
|
Value::None
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user