mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Introduce page / block / inline levels
This commit is contained in:
parent
c627847cb3
commit
5becb32ba4
@ -60,7 +60,7 @@ fn bench_eval(iai: &mut Iai) {
|
|||||||
fn bench_to_tree(iai: &mut Iai) {
|
fn bench_to_tree(iai: &mut Iai) {
|
||||||
let (mut ctx, id) = context();
|
let (mut ctx, id) = context();
|
||||||
let module = ctx.evaluate(id).unwrap();
|
let module = ctx.evaluate(id).unwrap();
|
||||||
iai.run(|| module.template.to_tree(ctx.style()));
|
iai.run(|| module.template.to_pages(ctx.style()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bench_layout(iai: &mut Iai) {
|
fn bench_layout(iai: &mut Iai) {
|
||||||
|
@ -7,6 +7,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
|
use crate::util::RcExt;
|
||||||
|
|
||||||
/// Create a new [`Array`] from values.
|
/// Create a new [`Array`] from values.
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
@ -169,10 +170,7 @@ impl IntoIterator for Array {
|
|||||||
type IntoIter = std::vec::IntoIter<Value>;
|
type IntoIter = std::vec::IntoIter<Value>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
match Rc::try_unwrap(self.0) {
|
Rc::take(self.0).into_iter()
|
||||||
Ok(vec) => vec.into_iter(),
|
|
||||||
Err(rc) => (*rc).clone().into_iter(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use super::{Str, Value};
|
use super::{Str, Value};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
|
use crate::util::RcExt;
|
||||||
|
|
||||||
/// Create a new [`Dict`] from key-value pairs.
|
/// Create a new [`Dict`] from key-value pairs.
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
@ -135,10 +136,7 @@ impl IntoIterator for Dict {
|
|||||||
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
|
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
match Rc::try_unwrap(self.0) {
|
Rc::take(self.0).into_iter()
|
||||||
Ok(map) => map.into_iter(),
|
|
||||||
Err(rc) => (*rc).clone().into_iter(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use super::Str;
|
use super::Str;
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size, SpecAxis};
|
use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild,
|
BlockNode, Decoration, InlineNode, PadNode, PageNode, ParChild, ParNode, StackChild,
|
||||||
StackNode,
|
StackNode,
|
||||||
};
|
};
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
@ -34,9 +34,9 @@ enum TemplateNode {
|
|||||||
/// Spacing.
|
/// Spacing.
|
||||||
Spacing(GenAxis, Linear),
|
Spacing(GenAxis, Linear),
|
||||||
/// An inline node builder.
|
/// An inline node builder.
|
||||||
Inline(Rc<dyn Fn(&Style) -> LayoutNode>, Vec<Decoration>),
|
Inline(Rc<dyn Fn(&Style) -> InlineNode>, Vec<Decoration>),
|
||||||
/// An block node builder.
|
/// An block node builder.
|
||||||
Block(Rc<dyn Fn(&Style) -> LayoutNode>),
|
Block(Rc<dyn Fn(&Style) -> BlockNode>),
|
||||||
/// Save the current style.
|
/// Save the current style.
|
||||||
Save,
|
Save,
|
||||||
/// Restore the last saved style.
|
/// Restore the last saved style.
|
||||||
@ -55,7 +55,7 @@ impl Template {
|
|||||||
pub fn from_inline<F, T>(f: F) -> Self
|
pub fn from_inline<F, T>(f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&Style) -> T + 'static,
|
F: Fn(&Style) -> T + 'static,
|
||||||
T: Into<LayoutNode>,
|
T: Into<InlineNode>,
|
||||||
{
|
{
|
||||||
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
|
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
|
||||||
Self(Rc::new(vec![node]))
|
Self(Rc::new(vec![node]))
|
||||||
@ -65,7 +65,7 @@ impl Template {
|
|||||||
pub fn from_block<F, T>(f: F) -> Self
|
pub fn from_block<F, T>(f: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&Style) -> T + 'static,
|
F: Fn(&Style) -> T + 'static,
|
||||||
T: Into<LayoutNode>,
|
T: Into<BlockNode>,
|
||||||
{
|
{
|
||||||
let node = TemplateNode::Block(Rc::new(move |s| f(s).into()));
|
let node = TemplateNode::Block(Rc::new(move |s| f(s).into()));
|
||||||
Self(Rc::new(vec![node]))
|
Self(Rc::new(vec![node]))
|
||||||
@ -164,10 +164,10 @@ impl Template {
|
|||||||
|
|
||||||
/// Build the layout tree resulting from instantiating the template with the
|
/// Build the layout tree resulting from instantiating the template with the
|
||||||
/// given style.
|
/// given style.
|
||||||
pub fn to_tree(&self, style: &Style) -> LayoutTree {
|
pub fn to_pages(&self, style: &Style) -> Vec<PageNode> {
|
||||||
let mut builder = Builder::new(style, true);
|
let mut builder = Builder::new(style, true);
|
||||||
builder.template(self);
|
builder.template(self);
|
||||||
builder.build_tree()
|
builder.build_pages()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Repeat this template `n` times.
|
/// Repeat this template `n` times.
|
||||||
@ -243,8 +243,8 @@ struct Builder {
|
|||||||
style: Style,
|
style: Style,
|
||||||
/// Snapshots of the style.
|
/// Snapshots of the style.
|
||||||
snapshots: Vec<Style>,
|
snapshots: Vec<Style>,
|
||||||
/// The tree of finished page runs.
|
/// The finished page nodes.
|
||||||
tree: LayoutTree,
|
finished: Vec<PageNode>,
|
||||||
/// When we are building the top-level layout trees, this contains metrics
|
/// When we are building the top-level layout trees, this contains metrics
|
||||||
/// of the page. While building a stack, this is `None`.
|
/// of the page. While building a stack, this is `None`.
|
||||||
page: Option<PageBuilder>,
|
page: Option<PageBuilder>,
|
||||||
@ -258,7 +258,7 @@ impl Builder {
|
|||||||
Self {
|
Self {
|
||||||
style: style.clone(),
|
style: style.clone(),
|
||||||
snapshots: vec![],
|
snapshots: vec![],
|
||||||
tree: LayoutTree { runs: vec![] },
|
finished: vec![],
|
||||||
page: pages.then(|| PageBuilder::new(style, true)),
|
page: pages.then(|| PageBuilder::new(style, true)),
|
||||||
stack: StackBuilder::new(style),
|
stack: StackBuilder::new(style),
|
||||||
}
|
}
|
||||||
@ -309,7 +309,7 @@ impl Builder {
|
|||||||
fn parbreak(&mut self) {
|
fn parbreak(&mut self) {
|
||||||
let amount = self.style.par_spacing();
|
let amount = self.style.par_spacing();
|
||||||
self.stack.finish_par(&self.style);
|
self.stack.finish_par(&self.style);
|
||||||
self.stack.push_soft(StackChild::spacing(amount, SpecAxis::Vertical));
|
self.stack.push_soft(StackChild::Spacing(amount.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a forced page break.
|
/// Apply a forced page break.
|
||||||
@ -317,7 +317,7 @@ impl Builder {
|
|||||||
if let Some(builder) = &mut self.page {
|
if let Some(builder) = &mut self.page {
|
||||||
let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
|
let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
|
||||||
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.style));
|
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.style));
|
||||||
self.tree.runs.extend(page.build(stack.build(), keep));
|
self.finished.extend(page.build(stack.build(), keep));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,15 +327,15 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Push an inline node into the active paragraph.
|
/// Push an inline node into the active paragraph.
|
||||||
fn inline(&mut self, node: impl Into<LayoutNode>, decos: &[Decoration]) {
|
fn inline(&mut self, node: impl Into<InlineNode>, decos: &[Decoration]) {
|
||||||
let align = self.style.aligns.inline;
|
let align = self.style.aligns.inline;
|
||||||
self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
|
self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a block node into the active stack, finishing the active paragraph.
|
/// Push a block node into the active stack, finishing the active paragraph.
|
||||||
fn block(&mut self, node: impl Into<LayoutNode>) {
|
fn block(&mut self, node: impl Into<BlockNode>) {
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
self.stack.push(StackChild::new(node, self.style.aligns.block));
|
self.stack.push(StackChild::Any(node.into(), self.style.aligns.block));
|
||||||
self.parbreak();
|
self.parbreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,7 +344,7 @@ impl Builder {
|
|||||||
match axis {
|
match axis {
|
||||||
GenAxis::Block => {
|
GenAxis::Block => {
|
||||||
self.stack.finish_par(&self.style);
|
self.stack.finish_par(&self.style);
|
||||||
self.stack.push_hard(StackChild::spacing(amount, SpecAxis::Vertical));
|
self.stack.push_hard(StackChild::Spacing(amount));
|
||||||
}
|
}
|
||||||
GenAxis::Inline => {
|
GenAxis::Inline => {
|
||||||
self.stack.par.push_hard(ParChild::Spacing(amount));
|
self.stack.par.push_hard(ParChild::Spacing(amount));
|
||||||
@ -359,10 +359,10 @@ impl Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Finish building and return the created layout tree.
|
/// Finish building and return the created layout tree.
|
||||||
fn build_tree(mut self) -> LayoutTree {
|
fn build_pages(mut self) -> Vec<PageNode> {
|
||||||
assert!(self.page.is_some());
|
assert!(self.page.is_some());
|
||||||
self.pagebreak(true, false);
|
self.pagebreak(true, false);
|
||||||
self.tree
|
self.finished
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a text node with the given text and settings from the current
|
/// Construct a text node with the given text and settings from the current
|
||||||
@ -396,9 +396,9 @@ impl PageBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build(self, child: StackNode, keep: bool) -> Option<PageRun> {
|
fn build(self, child: StackNode, keep: bool) -> Option<PageNode> {
|
||||||
let Self { size, padding, hard } = self;
|
let Self { size, padding, hard } = self;
|
||||||
(!child.children.is_empty() || (keep && hard)).then(|| PageRun {
|
(!child.children.is_empty() || (keep && hard)).then(|| PageNode {
|
||||||
size,
|
size,
|
||||||
child: PadNode { padding, child: child.into() }.into(),
|
child: PadNode { padding, child: child.into() }.into(),
|
||||||
})
|
})
|
||||||
@ -456,7 +456,7 @@ impl StackBuilder {
|
|||||||
struct ParBuilder {
|
struct ParBuilder {
|
||||||
align: Align,
|
align: Align,
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
line_spacing: Length,
|
leading: Length,
|
||||||
children: Vec<ParChild>,
|
children: Vec<ParChild>,
|
||||||
last: Last<ParChild>,
|
last: Last<ParChild>,
|
||||||
}
|
}
|
||||||
@ -466,7 +466,7 @@ impl ParBuilder {
|
|||||||
Self {
|
Self {
|
||||||
align: style.aligns.block,
|
align: style.aligns.block,
|
||||||
dir: style.dir,
|
dir: style.dir,
|
||||||
line_spacing: style.line_spacing(),
|
leading: style.leading(),
|
||||||
children: vec![],
|
children: vec![],
|
||||||
last: Last::None,
|
last: Last::None,
|
||||||
}
|
}
|
||||||
@ -507,9 +507,9 @@ impl ParBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build(self) -> Option<StackChild> {
|
fn build(self) -> Option<StackChild> {
|
||||||
let Self { align, dir, line_spacing, children, .. } = self;
|
let Self { align, dir, leading, children, .. } = self;
|
||||||
(!children.is_empty())
|
(!children.is_empty())
|
||||||
.then(|| StackChild::new(ParNode { dir, line_spacing, children }, align))
|
.then(|| StackChild::Any(ParNode { dir, leading, children }.into(), align))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use super::{Eval, EvalContext, Str, Template, Value};
|
use super::{Eval, EvalContext, Str, Template, Value};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::geom::{Align, SpecAxis};
|
use crate::geom::Align;
|
||||||
use crate::layout::{ParChild, ParNode, StackChild, StackNode};
|
use crate::layout::{ParChild, ParNode, StackChild, StackNode};
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
use crate::util::BoolExt;
|
use crate::util::BoolExt;
|
||||||
@ -108,7 +108,7 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
|
|||||||
ctx.template += Template::from_block(move |style| {
|
ctx.template += Template::from_block(move |style| {
|
||||||
let label = ParNode {
|
let label = ParNode {
|
||||||
dir: style.dir,
|
dir: style.dir,
|
||||||
line_spacing: style.line_spacing(),
|
leading: style.leading(),
|
||||||
children: vec![ParChild::Text(
|
children: vec![ParChild::Text(
|
||||||
(&label).into(),
|
(&label).into(),
|
||||||
style.aligns.inline,
|
style.aligns.inline,
|
||||||
@ -119,9 +119,9 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
|
|||||||
StackNode {
|
StackNode {
|
||||||
dir: style.dir,
|
dir: style.dir,
|
||||||
children: vec![
|
children: vec![
|
||||||
StackChild::new(label, Align::Start),
|
StackChild::Any(label.into(), Align::Start),
|
||||||
StackChild::spacing(style.text.size / 2.0, SpecAxis::Horizontal),
|
StackChild::Spacing((style.text.size / 2.0).into()),
|
||||||
StackChild::new(body.to_stack(&style), Align::Start),
|
StackChild::Any(body.to_stack(&style).into(), Align::Start),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -7,7 +7,7 @@ pub struct Constrained<T> {
|
|||||||
/// The item that is only valid if the constraints are fullfilled.
|
/// The item that is only valid if the constraints are fullfilled.
|
||||||
pub item: T,
|
pub item: T,
|
||||||
/// Constraints on regions in which the item is valid.
|
/// Constraints on regions in which the item is valid.
|
||||||
pub constraints: Constraints,
|
pub cts: Constraints,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Describe regions that match them.
|
/// Describe regions that match them.
|
||||||
|
@ -19,6 +19,70 @@ pub struct Frame {
|
|||||||
pub children: Vec<(Point, FrameChild)>,
|
pub children: Vec<(Point, FrameChild)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
/// Create a new, empty frame.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn new(size: Size, baseline: Length) -> Self {
|
||||||
|
assert!(size.is_finite());
|
||||||
|
Self { size, baseline, children: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an element at a position in the foreground.
|
||||||
|
pub fn push(&mut self, pos: Point, element: Element) {
|
||||||
|
self.children.push((pos, FrameChild::Element(element)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an element at a position in the background.
|
||||||
|
pub fn prepend(&mut self, pos: Point, element: Element) {
|
||||||
|
self.children.insert(0, (pos, FrameChild::Element(element)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a frame element.
|
||||||
|
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
|
||||||
|
self.children.push((pos, FrameChild::Group(subframe)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add all elements of another frame, placing them relative to the given
|
||||||
|
/// position.
|
||||||
|
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
|
||||||
|
if pos == Point::zero() && self.children.is_empty() {
|
||||||
|
self.children = subframe.children;
|
||||||
|
} else {
|
||||||
|
for (subpos, child) in subframe.children {
|
||||||
|
self.children.push((pos + subpos, child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all elements in the frame and its children.
|
||||||
|
pub fn elements(&self) -> Elements {
|
||||||
|
Elements { stack: vec![(0, Point::zero(), self)] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrap the frame with constraints.
|
||||||
|
pub fn constrain(self, cts: Constraints) -> Constrained<Rc<Self>> {
|
||||||
|
Constrained { item: Rc::new(self), cts }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Frame {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
struct Children<'a>(&'a [(Point, FrameChild)]);
|
||||||
|
|
||||||
|
impl Debug for Children<'_> {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.debug_map().entries(self.0.iter().map(|(k, v)| (k, v))).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.debug_struct("Frame")
|
||||||
|
.field("size", &self.size)
|
||||||
|
.field("baseline", &self.baseline)
|
||||||
|
.field("children", &Children(&self.children))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A frame can contain two different kinds of children: a leaf element or a
|
/// A frame can contain two different kinds of children: a leaf element or a
|
||||||
/// nested frame.
|
/// nested frame.
|
||||||
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
@ -29,6 +93,46 @@ pub enum FrameChild {
|
|||||||
Group(Rc<Frame>),
|
Group(Rc<Frame>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for FrameChild {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Element(element) => element.fmt(f),
|
||||||
|
Self::Group(frame) => frame.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator over all elements in a frame, alongside with their positions.
|
||||||
|
pub struct Elements<'a> {
|
||||||
|
stack: Vec<(usize, Point, &'a Frame)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for Elements<'a> {
|
||||||
|
type Item = (Point, &'a Element);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let (cursor, offset, frame) = self.stack.last_mut()?;
|
||||||
|
match frame.children.get(*cursor) {
|
||||||
|
Some((pos, FrameChild::Group(f))) => {
|
||||||
|
let new_offset = *offset + *pos;
|
||||||
|
self.stack.push((0, new_offset, f.as_ref()));
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
Some((pos, FrameChild::Element(e))) => {
|
||||||
|
*cursor += 1;
|
||||||
|
Some((*offset + *pos, e))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.stack.pop();
|
||||||
|
if let Some((cursor, _, _)) = self.stack.last_mut() {
|
||||||
|
*cursor += 1;
|
||||||
|
}
|
||||||
|
self.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The building block frames are composed of.
|
/// The building block frames are composed of.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Element {
|
pub enum Element {
|
||||||
@ -81,107 +185,3 @@ pub enum Geometry {
|
|||||||
/// A filled bezier path.
|
/// A filled bezier path.
|
||||||
Path(Path),
|
Path(Path),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Frame {
|
|
||||||
/// Create a new, empty frame.
|
|
||||||
#[track_caller]
|
|
||||||
pub fn new(size: Size, baseline: Length) -> Self {
|
|
||||||
assert!(size.is_finite());
|
|
||||||
Self { size, baseline, children: vec![] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an element at a position in the foreground.
|
|
||||||
pub fn push(&mut self, pos: Point, element: Element) {
|
|
||||||
self.children.push((pos, FrameChild::Element(element)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an element at a position in the background.
|
|
||||||
pub fn prepend(&mut self, pos: Point, element: Element) {
|
|
||||||
self.children.insert(0, (pos, FrameChild::Element(element)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a frame element.
|
|
||||||
pub fn push_frame(&mut self, pos: Point, subframe: Rc<Self>) {
|
|
||||||
self.children.push((pos, FrameChild::Group(subframe)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add all elements of another frame, placing them relative to the given
|
|
||||||
/// position.
|
|
||||||
pub fn merge_frame(&mut self, pos: Point, subframe: Self) {
|
|
||||||
if pos == Point::zero() && self.children.is_empty() {
|
|
||||||
self.children = subframe.children;
|
|
||||||
} else {
|
|
||||||
for (subpos, child) in subframe.children {
|
|
||||||
self.children.push((pos + subpos, child));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap the frame with constraints.
|
|
||||||
pub fn constrain(self, constraints: Constraints) -> Constrained<Rc<Self>> {
|
|
||||||
Constrained { item: Rc::new(self), constraints }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over all elements in the frame and its children.
|
|
||||||
pub fn elements(&self) -> Elements {
|
|
||||||
Elements { stack: vec![(0, Point::zero(), self)] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An iterator over all elements in a frame, alongside with their positions.
|
|
||||||
pub struct Elements<'a> {
|
|
||||||
stack: Vec<(usize, Point, &'a Frame)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for Elements<'a> {
|
|
||||||
type Item = (Point, &'a Element);
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
let (cursor, offset, frame) = self.stack.last_mut()?;
|
|
||||||
match frame.children.get(*cursor) {
|
|
||||||
Some((pos, FrameChild::Group(f))) => {
|
|
||||||
let new_offset = *offset + *pos;
|
|
||||||
self.stack.push((0, new_offset, f.as_ref()));
|
|
||||||
self.next()
|
|
||||||
}
|
|
||||||
Some((pos, FrameChild::Element(e))) => {
|
|
||||||
*cursor += 1;
|
|
||||||
Some((*offset + *pos, e))
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
self.stack.pop();
|
|
||||||
if let Some((cursor, _, _)) = self.stack.last_mut() {
|
|
||||||
*cursor += 1;
|
|
||||||
}
|
|
||||||
self.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Frame {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
struct Children<'a>(&'a [(Point, FrameChild)]);
|
|
||||||
|
|
||||||
impl Debug for Children<'_> {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.debug_map().entries(self.0.iter().map(|(k, v)| (k, v))).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.debug_struct("Frame")
|
|
||||||
.field("size", &self.size)
|
|
||||||
.field("baseline", &self.baseline)
|
|
||||||
.field("children", &Children(&self.children))
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for FrameChild {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Element(element) => element.fmt(f),
|
|
||||||
Self::Group(frame) => frame.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -9,7 +9,7 @@ pub struct GridNode {
|
|||||||
/// Defines sizing of gutter rows and columns between content.
|
/// Defines sizing of gutter rows and columns between content.
|
||||||
pub gutter: Spec<Vec<TrackSizing>>,
|
pub gutter: Spec<Vec<TrackSizing>>,
|
||||||
/// The nodes to be arranged in a grid.
|
/// The nodes to be arranged in a grid.
|
||||||
pub children: Vec<LayoutNode>,
|
pub children: Vec<BlockNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines how to size a grid cell along an axis.
|
/// Defines how to size a grid cell along an axis.
|
||||||
@ -23,7 +23,7 @@ pub enum TrackSizing {
|
|||||||
Fractional(Fractional),
|
Fractional(Fractional),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for GridNode {
|
impl BlockLevel for GridNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -40,9 +40,9 @@ impl Layout for GridNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<GridNode> for LayoutNode {
|
impl From<GridNode> for BlockNode {
|
||||||
fn from(grid: GridNode) -> Self {
|
fn from(node: GridNode) -> Self {
|
||||||
Self::new(grid)
|
Self::new(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ struct GridLayouter<'a> {
|
|||||||
/// The row tracks including gutter tracks.
|
/// The row tracks including gutter tracks.
|
||||||
rows: Vec<TrackSizing>,
|
rows: Vec<TrackSizing>,
|
||||||
/// The children of the grid.
|
/// The children of the grid.
|
||||||
children: &'a [LayoutNode],
|
children: &'a [BlockNode],
|
||||||
/// The regions to layout into.
|
/// The regions to layout into.
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
/// Resolved column sizes.
|
/// Resolved column sizes.
|
||||||
@ -546,7 +546,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
///
|
///
|
||||||
/// Returns `None` if it's a gutter cell.
|
/// Returns `None` if it's a gutter cell.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn cell(&self, x: usize, y: usize) -> Option<&'a LayoutNode> {
|
fn cell(&self, x: usize, y: usize) -> Option<&'a BlockNode> {
|
||||||
assert!(x < self.cols.len());
|
assert!(x < self.cols.len());
|
||||||
assert!(y < self.rows.len());
|
assert!(y < self.rows.len());
|
||||||
|
|
||||||
|
@ -13,31 +13,23 @@ pub struct ImageNode {
|
|||||||
pub height: Option<Linear>,
|
pub height: Option<Linear>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ImageNode {
|
impl InlineLevel for ImageNode {
|
||||||
fn layout(
|
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
let img = ctx.images.get(self.id);
|
let img = ctx.images.get(self.id);
|
||||||
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
|
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
|
||||||
let pixel_ratio = pixel_size.x / pixel_size.y;
|
let pixel_ratio = pixel_size.x / pixel_size.y;
|
||||||
|
|
||||||
let width = self.width.map(|w| w.resolve(regions.base.w));
|
let width = self.width.map(|w| w.resolve(base.w));
|
||||||
let height = self.height.map(|w| w.resolve(regions.base.h));
|
let height = self.height.map(|w| w.resolve(base.h));
|
||||||
|
|
||||||
let mut cts = Constraints::new(regions.expand);
|
|
||||||
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
|
|
||||||
|
|
||||||
let size = match (width, height) {
|
let size = match (width, height) {
|
||||||
(Some(width), Some(height)) => Size::new(width, height),
|
(Some(width), Some(height)) => Size::new(width, height),
|
||||||
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
||||||
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
cts.exact.x = Some(regions.current.w);
|
if space.is_finite() {
|
||||||
if regions.current.w.is_finite() {
|
|
||||||
// Fit to width.
|
// Fit to width.
|
||||||
Size::new(regions.current.w, regions.current.w / pixel_ratio)
|
Size::new(space, space / pixel_ratio)
|
||||||
} else {
|
} else {
|
||||||
// Unbounded width, we have to make up something,
|
// Unbounded width, we have to make up something,
|
||||||
// so it is 1pt per pixel.
|
// so it is 1pt per pixel.
|
||||||
@ -48,12 +40,12 @@ impl Layout for ImageNode {
|
|||||||
|
|
||||||
let mut frame = Frame::new(size, size.h);
|
let mut frame = Frame::new(size, size.h);
|
||||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
frame.push(Point::zero(), Element::Image(self.id, size));
|
||||||
vec![frame.constrain(cts)]
|
frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ImageNode> for LayoutNode {
|
impl From<ImageNode> for InlineNode {
|
||||||
fn from(image: ImageNode) -> Self {
|
fn from(node: ImageNode) -> Self {
|
||||||
Self::new(image)
|
Self::new(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,7 +230,7 @@ impl FramesEntry {
|
|||||||
let mut iter = regions.iter();
|
let mut iter = regions.iter();
|
||||||
self.frames.iter().all(|frame| {
|
self.frames.iter().all(|frame| {
|
||||||
iter.next().map_or(false, |(current, base)| {
|
iter.next().map_or(false, |(current, base)| {
|
||||||
frame.constraints.check(current, base, regions.expand)
|
frame.cts.check(current, base, regions.expand)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -400,7 +400,7 @@ mod tests {
|
|||||||
fn empty_frames() -> Vec<Constrained<Rc<Frame>>> {
|
fn empty_frames() -> Vec<Constrained<Rc<Frame>>> {
|
||||||
vec![Constrained {
|
vec![Constrained {
|
||||||
item: Rc::new(Frame::default()),
|
item: Rc::new(Frame::default()),
|
||||||
constraints: Constraints::new(Spec::splat(false)),
|
cts: Constraints::new(Spec::splat(false)),
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,8 @@ mod pad;
|
|||||||
mod par;
|
mod par;
|
||||||
mod regions;
|
mod regions;
|
||||||
mod shape;
|
mod shape;
|
||||||
mod shaping;
|
|
||||||
mod spacing;
|
|
||||||
mod stack;
|
mod stack;
|
||||||
mod tree;
|
mod text;
|
||||||
|
|
||||||
pub use self::image::*;
|
pub use self::image::*;
|
||||||
pub use constraints::*;
|
pub use constraints::*;
|
||||||
@ -25,12 +23,10 @@ pub use pad::*;
|
|||||||
pub use par::*;
|
pub use par::*;
|
||||||
pub use regions::*;
|
pub use regions::*;
|
||||||
pub use shape::*;
|
pub use shape::*;
|
||||||
pub use shaping::*;
|
|
||||||
pub use spacing::*;
|
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
pub use tree::*;
|
pub use text::*;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::font::FontStore;
|
use crate::font::FontStore;
|
||||||
@ -39,10 +35,20 @@ use crate::image::ImageStore;
|
|||||||
use crate::util::OptionExt;
|
use crate::util::OptionExt;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Layout a tree into a collection of frames.
|
#[cfg(feature = "layout-cache")]
|
||||||
pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> {
|
use {
|
||||||
|
fxhash::FxHasher64,
|
||||||
|
std::any::Any,
|
||||||
|
std::hash::{Hash, Hasher},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Layout a page-level node into a collection of frames.
|
||||||
|
pub fn layout<T>(ctx: &mut Context, node: &T) -> Vec<Rc<Frame>>
|
||||||
|
where
|
||||||
|
T: PageLevel + ?Sized,
|
||||||
|
{
|
||||||
let mut ctx = LayoutContext::new(ctx);
|
let mut ctx = LayoutContext::new(ctx);
|
||||||
tree.layout(&mut ctx)
|
node.layout(&mut ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
@ -73,12 +79,198 @@ impl<'a> LayoutContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a node.
|
/// Page-level nodes directly produce frames representing pages.
|
||||||
pub trait Layout: Debug {
|
///
|
||||||
/// Layout the node into the given regions.
|
/// Such nodes create their own regions instead of being supplied with them from
|
||||||
|
/// some parent.
|
||||||
|
pub trait PageLevel: Debug {
|
||||||
|
/// Layout the node, producing one frame per page.
|
||||||
|
fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layouts its children onto one or multiple pages.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PageNode {
|
||||||
|
/// The size of the page.
|
||||||
|
pub size: Size,
|
||||||
|
/// The node that produces the actual pages.
|
||||||
|
pub child: BlockNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PageLevel for PageNode {
|
||||||
|
fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||||
|
// When one of the lengths is infinite the page fits its content along
|
||||||
|
// that axis.
|
||||||
|
let expand = self.size.to_spec().map(Length::is_finite);
|
||||||
|
let regions = Regions::repeat(self.size, self.size, expand);
|
||||||
|
self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PageLevel for T
|
||||||
|
where
|
||||||
|
T: AsRef<[PageNode]> + Debug + ?Sized,
|
||||||
|
{
|
||||||
|
fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
||||||
|
self.as_ref().iter().flat_map(|node| node.layout(ctx)).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Block-level nodes can be layouted into a sequence of regions.
|
||||||
|
///
|
||||||
|
/// They return one frame per used region alongside constraints that define
|
||||||
|
/// whether the result is reusable in other regions.
|
||||||
|
pub trait BlockLevel: Debug {
|
||||||
|
/// Layout the node into the given regions, producing constrained frames.
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: &Regions,
|
regions: &Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>>;
|
) -> Vec<Constrained<Rc<Frame>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A dynamic [block-level](BlockLevel) layouting node.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BlockNode {
|
||||||
|
node: Rc<dyn BlockLevel>,
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockNode {
|
||||||
|
/// Create a new dynamic node from any block-level node.
|
||||||
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
|
pub fn new<T>(node: T) -> Self
|
||||||
|
where
|
||||||
|
T: BlockLevel + 'static,
|
||||||
|
{
|
||||||
|
Self { node: Rc::new(node) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new dynamic node from any block-level node.
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
pub fn new<T>(node: T) -> Self
|
||||||
|
where
|
||||||
|
T: BlockLevel + Hash + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
hash: hash_node(&node),
|
||||||
|
node: Rc::new(node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockLevel for BlockNode {
|
||||||
|
fn layout(
|
||||||
|
&self,
|
||||||
|
ctx: &mut LayoutContext,
|
||||||
|
regions: &Regions,
|
||||||
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
|
return self.node.layout(ctx, regions);
|
||||||
|
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
ctx.layouts.get(self.hash, regions).unwrap_or_else(|| {
|
||||||
|
ctx.level += 1;
|
||||||
|
let frames = self.node.layout(ctx, regions);
|
||||||
|
ctx.level -= 1;
|
||||||
|
|
||||||
|
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if !entry.check(regions) {
|
||||||
|
eprintln!("node: {:#?}", self.node);
|
||||||
|
eprintln!("regions: {:#?}", regions);
|
||||||
|
eprintln!(
|
||||||
|
"constraints: {:#?}",
|
||||||
|
frames.iter().map(|c| c.cts).collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
panic!("constraints did not match regions they were created for");
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.layouts.insert(self.hash, entry);
|
||||||
|
frames
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
impl Hash for BlockNode {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write_u64(self.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for BlockNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.node.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inline-level nodes are layouted as part of paragraph layout.
|
||||||
|
///
|
||||||
|
/// They only know the width and not the height of the paragraph's region and
|
||||||
|
/// return only a single frame.
|
||||||
|
pub trait InlineLevel: Debug {
|
||||||
|
/// Layout the node into a frame.
|
||||||
|
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dynamic [inline-level](InlineLevel) layouting node.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct InlineNode {
|
||||||
|
node: Rc<dyn InlineLevel>,
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
hash: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineNode {
|
||||||
|
/// Create a new dynamic node from any inline-level node.
|
||||||
|
#[cfg(not(feature = "layout-cache"))]
|
||||||
|
pub fn new<T>(node: T) -> Self
|
||||||
|
where
|
||||||
|
T: InlineLevel + 'static,
|
||||||
|
{
|
||||||
|
Self { node: Rc::new(node) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new dynamic node from any inline-level node.
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
pub fn new<T>(node: T) -> Self
|
||||||
|
where
|
||||||
|
T: InlineLevel + Hash + 'static,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
hash: hash_node(&node),
|
||||||
|
node: Rc::new(node),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineLevel for InlineNode {
|
||||||
|
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||||
|
self.node.layout(ctx, space, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
impl Hash for InlineNode {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
state.write_u64(self.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for InlineNode {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
self.node.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hash a node alongside its type id.
|
||||||
|
#[cfg(feature = "layout-cache")]
|
||||||
|
fn hash_node(node: &(impl Hash + 'static)) -> u64 {
|
||||||
|
let mut state = FxHasher64::default();
|
||||||
|
node.type_id().hash(&mut state);
|
||||||
|
node.hash(&mut state);
|
||||||
|
state.finish()
|
||||||
|
}
|
||||||
|
@ -7,10 +7,10 @@ pub struct PadNode {
|
|||||||
/// The amount of padding.
|
/// The amount of padding.
|
||||||
pub padding: Sides<Linear>,
|
pub padding: Sides<Linear>,
|
||||||
/// The child node whose sides to pad.
|
/// The child node whose sides to pad.
|
||||||
pub child: LayoutNode,
|
pub child: BlockNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for PadNode {
|
impl BlockLevel for PadNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -22,7 +22,7 @@ impl Layout for PadNode {
|
|||||||
®ions.map(|size| size - self.padding.resolve(size).size()),
|
®ions.map(|size| size - self.padding.resolve(size).size()),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (Constrained { item: frame, constraints }, (current, base)) in
|
for (Constrained { item: frame, cts }, (current, base)) in
|
||||||
frames.iter_mut().zip(regions.iter())
|
frames.iter_mut().zip(regions.iter())
|
||||||
{
|
{
|
||||||
fn solve_axis(length: Length, padding: Linear) -> Length {
|
fn solve_axis(length: Length, padding: Linear) -> Length {
|
||||||
@ -46,7 +46,7 @@ impl Layout for PadNode {
|
|||||||
new.push_frame(origin, prev);
|
new.push_frame(origin, prev);
|
||||||
|
|
||||||
// Inflate min and max contraints by the padding.
|
// Inflate min and max contraints by the padding.
|
||||||
for spec in [&mut constraints.min, &mut constraints.max] {
|
for spec in [&mut cts.min, &mut cts.max] {
|
||||||
if let Some(x) = spec.x.as_mut() {
|
if let Some(x) = spec.x.as_mut() {
|
||||||
*x += padding.size().w;
|
*x += padding.size().w;
|
||||||
}
|
}
|
||||||
@ -56,18 +56,18 @@ impl Layout for PadNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set exact and base constraints if the child had them.
|
// Set exact and base constraints if the child had them.
|
||||||
constraints.exact.x.and_set(Some(current.w));
|
cts.exact.x.and_set(Some(current.w));
|
||||||
constraints.exact.y.and_set(Some(current.h));
|
cts.exact.y.and_set(Some(current.h));
|
||||||
constraints.base.x.and_set(Some(base.w));
|
cts.base.x.and_set(Some(base.w));
|
||||||
constraints.base.y.and_set(Some(base.h));
|
cts.base.y.and_set(Some(base.h));
|
||||||
|
|
||||||
// Also set base constraints if the padding is relative.
|
// Also set base constraints if the padding is relative.
|
||||||
if self.padding.left.is_relative() || self.padding.right.is_relative() {
|
if self.padding.left.is_relative() || self.padding.right.is_relative() {
|
||||||
constraints.base.x = Some(base.w);
|
cts.base.x = Some(base.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.padding.top.is_relative() || self.padding.bottom.is_relative() {
|
if self.padding.top.is_relative() || self.padding.bottom.is_relative() {
|
||||||
constraints.base.y = Some(base.h);
|
cts.base.y = Some(base.h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,8 +75,8 @@ impl Layout for PadNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PadNode> for LayoutNode {
|
impl From<PadNode> for BlockNode {
|
||||||
fn from(pad: PadNode) -> Self {
|
fn from(node: PadNode) -> Self {
|
||||||
Self::new(pad)
|
Self::new(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ pub struct ParNode {
|
|||||||
/// The inline direction of this paragraph.
|
/// The inline direction of this paragraph.
|
||||||
pub dir: Dir,
|
pub dir: Dir,
|
||||||
/// The spacing to insert between each line.
|
/// The spacing to insert between each line.
|
||||||
pub line_spacing: Length,
|
pub leading: Length,
|
||||||
/// The nodes to be arranged in a paragraph.
|
/// The nodes to be arranged in a paragraph.
|
||||||
pub children: Vec<ParChild>,
|
pub children: Vec<ParChild>,
|
||||||
}
|
}
|
||||||
@ -31,10 +31,10 @@ pub enum ParChild {
|
|||||||
/// A run of text and how to align it in its line.
|
/// A run of text and how to align it in its line.
|
||||||
Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>),
|
Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>),
|
||||||
/// Any child node and how to align it in its line.
|
/// Any child node and how to align it in its line.
|
||||||
Any(LayoutNode, Align, Vec<Decoration>),
|
Any(InlineNode, Align, Vec<Decoration>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ParNode {
|
impl BlockLevel for ParNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -88,9 +88,9 @@ impl ParNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParNode> for LayoutNode {
|
impl From<ParNode> for BlockNode {
|
||||||
fn from(par: ParNode) -> Self {
|
fn from(node: ParNode) -> Self {
|
||||||
Self::new(par)
|
Self::new(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +110,7 @@ struct ParLayouter<'a> {
|
|||||||
/// The top-level direction.
|
/// The top-level direction.
|
||||||
dir: Dir,
|
dir: Dir,
|
||||||
/// The line spacing.
|
/// The line spacing.
|
||||||
line_spacing: Length,
|
leading: Length,
|
||||||
/// Bidirectional text embedding levels for the paragraph.
|
/// Bidirectional text embedding levels for the paragraph.
|
||||||
bidi: BidiInfo<'a>,
|
bidi: BidiInfo<'a>,
|
||||||
/// Layouted children and separated text runs.
|
/// Layouted children and separated text runs.
|
||||||
@ -143,14 +143,14 @@ impl<'a> ParLayouter<'a> {
|
|||||||
// TODO: Also split by language and script.
|
// TODO: Also split by language and script.
|
||||||
for (subrange, dir) in split_runs(&bidi, range) {
|
for (subrange, dir) in split_runs(&bidi, range) {
|
||||||
let text = &bidi.text[subrange.clone()];
|
let text = &bidi.text[subrange.clone()];
|
||||||
let shaped = shape(ctx, text, dir, style);
|
let shaped = shape(ctx, text, style, dir);
|
||||||
items.push(ParItem::Text(shaped, *align, decos));
|
items.push(ParItem::Text(shaped, *align, decos));
|
||||||
ranges.push(subrange);
|
ranges.push(subrange);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ParChild::Any(node, align, decos) => {
|
ParChild::Any(node, align, decos) => {
|
||||||
let frame = node.layout(ctx, regions).remove(0);
|
let frame = node.layout(ctx, regions.current.w, regions.base);
|
||||||
items.push(ParItem::Frame(frame.item, *align, decos));
|
items.push(ParItem::Frame(frame, *align, decos));
|
||||||
ranges.push(range);
|
ranges.push(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,7 +158,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
dir: par.dir,
|
dir: par.dir,
|
||||||
line_spacing: par.line_spacing,
|
leading: par.leading,
|
||||||
bidi,
|
bidi,
|
||||||
items,
|
items,
|
||||||
ranges,
|
ranges,
|
||||||
@ -171,7 +171,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
let mut stack = LineStack::new(self.line_spacing, regions);
|
let mut stack = LineStack::new(self.leading, regions);
|
||||||
|
|
||||||
// The current line attempt.
|
// The current line attempt.
|
||||||
// Invariant: Always fits into `stack.regions.current`.
|
// Invariant: Always fits into `stack.regions.current`.
|
||||||
@ -196,18 +196,18 @@ impl<'a> ParLayouter<'a> {
|
|||||||
// the width of the region must not fit the width of the
|
// the width of the region must not fit the width of the
|
||||||
// tried line.
|
// tried line.
|
||||||
if !stack.regions.current.w.fits(line.size.w) {
|
if !stack.regions.current.w.fits(line.size.w) {
|
||||||
stack.constraints.max.x.set_min(line.size.w);
|
stack.cts.max.x.set_min(line.size.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same as above, but for height.
|
// Same as above, but for height.
|
||||||
if !stack.regions.current.h.fits(line.size.h) {
|
if !stack.regions.current.h.fits(line.size.h) {
|
||||||
let too_large = stack.size.h + self.line_spacing + line.size.h;
|
let too_large = stack.size.h + self.leading + line.size.h;
|
||||||
stack.constraints.max.y.set_min(too_large);
|
stack.cts.max.y.set_min(too_large);
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.push(last_line);
|
stack.push(last_line);
|
||||||
|
|
||||||
stack.constraints.min.y = Some(stack.size.h);
|
stack.cts.min.y = Some(stack.size.h);
|
||||||
start = last_end;
|
start = last_end;
|
||||||
line = LineLayout::new(ctx, &self, start .. end);
|
line = LineLayout::new(ctx, &self, start .. end);
|
||||||
}
|
}
|
||||||
@ -223,8 +223,8 @@ impl<'a> ParLayouter<'a> {
|
|||||||
// Again, the line must not fit. It would if the space taken up
|
// Again, the line must not fit. It would if the space taken up
|
||||||
// plus the line height would fit, therefore the constraint
|
// plus the line height would fit, therefore the constraint
|
||||||
// below.
|
// below.
|
||||||
let too_large = stack.size.h + self.line_spacing + line.size.h;
|
let too_large = stack.size.h + self.leading + line.size.h;
|
||||||
stack.constraints.max.y.set_min(too_large);
|
stack.cts.max.y.set_min(too_large);
|
||||||
|
|
||||||
stack.finish_region(ctx);
|
stack.finish_region(ctx);
|
||||||
}
|
}
|
||||||
@ -247,18 +247,18 @@ impl<'a> ParLayouter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.constraints.min.y = Some(stack.size.h);
|
stack.cts.min.y = Some(stack.size.h);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, the line fits both horizontally and vertically
|
// Otherwise, the line fits both horizontally and vertically
|
||||||
// and we remember it.
|
// and we remember it.
|
||||||
stack.constraints.min.x.set_max(line.size.w);
|
stack.cts.min.x.set_max(line.size.w);
|
||||||
last = Some((line, end));
|
last = Some((line, end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((line, _)) = last {
|
if let Some((line, _)) = last {
|
||||||
stack.push(line);
|
stack.push(line);
|
||||||
stack.constraints.min.y = Some(stack.size.h);
|
stack.cts.min.y = Some(stack.size.h);
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.finish(ctx)
|
stack.finish(ctx)
|
||||||
@ -292,7 +292,7 @@ enum ParItem<'a> {
|
|||||||
/// A shaped text run with consistent direction.
|
/// A shaped text run with consistent direction.
|
||||||
Text(ShapedText<'a>, Align, &'a [Decoration]),
|
Text(ShapedText<'a>, Align, &'a [Decoration]),
|
||||||
/// A layouted child node.
|
/// A layouted child node.
|
||||||
Frame(Rc<Frame>, Align, &'a [Decoration]),
|
Frame(Frame, Align, &'a [Decoration]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParItem<'_> {
|
impl ParItem<'_> {
|
||||||
@ -463,10 +463,10 @@ impl<'a> LineLayout<'a> {
|
|||||||
ParItem::Frame(ref frame, align, decos) => {
|
ParItem::Frame(ref frame, align, decos) => {
|
||||||
let mut frame = frame.clone();
|
let mut frame = frame.clone();
|
||||||
for deco in decos {
|
for deco in decos {
|
||||||
deco.apply(ctx, Rc::make_mut(&mut frame));
|
deco.apply(ctx, &mut frame);
|
||||||
}
|
}
|
||||||
let pos = position(&frame, align);
|
let pos = position(&frame, align);
|
||||||
output.push_frame(pos, frame);
|
output.merge_frame(pos, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -522,23 +522,23 @@ impl<'a> LineLayout<'a> {
|
|||||||
|
|
||||||
/// Stacks lines on top of each other.
|
/// Stacks lines on top of each other.
|
||||||
struct LineStack<'a> {
|
struct LineStack<'a> {
|
||||||
line_spacing: Length,
|
leading: Length,
|
||||||
full: Size,
|
full: Size,
|
||||||
regions: Regions,
|
regions: Regions,
|
||||||
size: Size,
|
size: Size,
|
||||||
lines: Vec<LineLayout<'a>>,
|
lines: Vec<LineLayout<'a>>,
|
||||||
finished: Vec<Constrained<Rc<Frame>>>,
|
finished: Vec<Constrained<Rc<Frame>>>,
|
||||||
constraints: Constraints,
|
cts: Constraints,
|
||||||
overflowing: bool,
|
overflowing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LineStack<'a> {
|
impl<'a> LineStack<'a> {
|
||||||
/// Create an empty line stack.
|
/// Create an empty line stack.
|
||||||
fn new(line_spacing: Length, regions: Regions) -> Self {
|
fn new(leading: Length, regions: Regions) -> Self {
|
||||||
Self {
|
Self {
|
||||||
line_spacing,
|
leading,
|
||||||
full: regions.current,
|
full: regions.current,
|
||||||
constraints: Constraints::new(regions.expand),
|
cts: Constraints::new(regions.expand),
|
||||||
regions,
|
regions,
|
||||||
size: Size::zero(),
|
size: Size::zero(),
|
||||||
lines: vec![],
|
lines: vec![],
|
||||||
@ -549,12 +549,12 @@ impl<'a> LineStack<'a> {
|
|||||||
|
|
||||||
/// Push a new line into the stack.
|
/// Push a new line into the stack.
|
||||||
fn push(&mut self, line: LineLayout<'a>) {
|
fn push(&mut self, line: LineLayout<'a>) {
|
||||||
self.regions.current.h -= line.size.h + self.line_spacing;
|
self.regions.current.h -= line.size.h + self.leading;
|
||||||
|
|
||||||
self.size.w.set_max(line.size.w);
|
self.size.w.set_max(line.size.w);
|
||||||
self.size.h += line.size.h;
|
self.size.h += line.size.h;
|
||||||
if !self.lines.is_empty() {
|
if !self.lines.is_empty() {
|
||||||
self.size.h += self.line_spacing;
|
self.size.h += self.leading;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lines.push(line);
|
self.lines.push(line);
|
||||||
@ -564,13 +564,13 @@ impl<'a> LineStack<'a> {
|
|||||||
fn finish_region(&mut self, ctx: &LayoutContext) {
|
fn finish_region(&mut self, ctx: &LayoutContext) {
|
||||||
if self.regions.expand.x {
|
if self.regions.expand.x {
|
||||||
self.size.w = self.regions.current.w;
|
self.size.w = self.regions.current.w;
|
||||||
self.constraints.exact.x = Some(self.regions.current.w);
|
self.cts.exact.x = Some(self.regions.current.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.overflowing {
|
if self.overflowing {
|
||||||
self.constraints.min.y = None;
|
self.cts.min.y = None;
|
||||||
self.constraints.max.y = None;
|
self.cts.max.y = None;
|
||||||
self.constraints.exact = self.full.to_spec().map(Some);
|
self.cts.exact = self.full.to_spec().map(Some);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut output = Frame::new(self.size, self.size.h);
|
let mut output = Frame::new(self.size, self.size.h);
|
||||||
@ -586,14 +586,14 @@ impl<'a> LineStack<'a> {
|
|||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += frame.size.h + self.line_spacing;
|
offset += frame.size.h + self.leading;
|
||||||
output.merge_frame(pos, frame);
|
output.merge_frame(pos, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.finished.push(output.constrain(self.constraints));
|
self.finished.push(output.constrain(self.cts));
|
||||||
self.regions.next();
|
self.regions.next();
|
||||||
self.full = self.regions.current;
|
self.full = self.regions.current;
|
||||||
self.constraints = Constraints::new(self.regions.expand);
|
self.cts = Constraints::new(self.regions.expand);
|
||||||
self.size = Size::zero();
|
self.size = Size::zero();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use std::f64::consts::SQRT_2;
|
use std::f64::consts::SQRT_2;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::util::RcExt;
|
||||||
|
|
||||||
/// Places its child into a sizable and fillable shape.
|
/// Places its child into a sizable and fillable shape.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -15,7 +16,7 @@ pub struct ShapeNode {
|
|||||||
/// How to fill the shape, if at all.
|
/// How to fill the shape, if at all.
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
/// The child node to place into the shape, if any.
|
/// The child node to place into the shape, if any.
|
||||||
pub child: Option<LayoutNode>,
|
pub child: Option<BlockNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of a shape.
|
/// The type of a shape.
|
||||||
@ -31,40 +32,15 @@ pub enum ShapeKind {
|
|||||||
Ellipse,
|
Ellipse,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for ShapeNode {
|
impl InlineLevel for ShapeNode {
|
||||||
fn layout(
|
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
// Resolve width and height relative to the region's base.
|
// Resolve width and height relative to the region's base.
|
||||||
let width = self.width.map(|w| w.resolve(regions.base.w));
|
let width = self.width.map(|w| w.resolve(base.w));
|
||||||
let height = self.height.map(|h| h.resolve(regions.base.h));
|
let height = self.height.map(|h| h.resolve(base.h));
|
||||||
|
|
||||||
// Generate constraints.
|
|
||||||
let constraints = {
|
|
||||||
let mut cts = Constraints::new(regions.expand);
|
|
||||||
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
|
|
||||||
|
|
||||||
// Set tight exact and base constraints if the child is
|
|
||||||
// automatically sized since we don't know what the child might do.
|
|
||||||
if self.width.is_none() {
|
|
||||||
cts.exact.x = Some(regions.current.w);
|
|
||||||
cts.base.x = Some(regions.base.w);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same here.
|
|
||||||
if self.height.is_none() {
|
|
||||||
cts.exact.y = Some(regions.current.h);
|
|
||||||
cts.base.y = Some(regions.base.h);
|
|
||||||
}
|
|
||||||
|
|
||||||
cts
|
|
||||||
};
|
|
||||||
|
|
||||||
// Layout.
|
// Layout.
|
||||||
let mut frames = if let Some(child) = &self.child {
|
let mut frame = if let Some(child) = &self.child {
|
||||||
let mut node: &dyn Layout = child;
|
let mut node: &dyn BlockLevel = child;
|
||||||
|
|
||||||
let padded;
|
let padded;
|
||||||
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
|
if matches!(self.shape, ShapeKind::Circle | ShapeKind::Ellipse) {
|
||||||
@ -79,14 +55,12 @@ impl Layout for ShapeNode {
|
|||||||
|
|
||||||
// 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 mut pod = {
|
let mut pod = {
|
||||||
let size = Size::new(
|
let size =
|
||||||
width.unwrap_or(regions.current.w),
|
Size::new(width.unwrap_or(space), height.unwrap_or(Length::inf()));
|
||||||
height.unwrap_or(regions.current.h),
|
|
||||||
);
|
|
||||||
|
|
||||||
let base = Size::new(
|
let base = Size::new(
|
||||||
if width.is_some() { size.w } else { regions.base.w },
|
if width.is_some() { size.w } else { base.w },
|
||||||
if height.is_some() { size.h } else { regions.base.h },
|
if height.is_some() { size.h } else { base.h },
|
||||||
);
|
);
|
||||||
|
|
||||||
let expand = Spec::new(width.is_some(), height.is_some());
|
let expand = Spec::new(width.is_some(), height.is_some());
|
||||||
@ -108,17 +82,15 @@ impl Layout for ShapeNode {
|
|||||||
|
|
||||||
// Validate and set constraints.
|
// Validate and set constraints.
|
||||||
assert_eq!(frames.len(), 1);
|
assert_eq!(frames.len(), 1);
|
||||||
frames[0].constraints = constraints;
|
Rc::take(frames.into_iter().next().unwrap().item)
|
||||||
frames
|
|
||||||
} else {
|
} else {
|
||||||
// Resolve shape size.
|
// Resolve shape size.
|
||||||
let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default());
|
let size = Size::new(width.unwrap_or_default(), height.unwrap_or_default());
|
||||||
vec![Frame::new(size, size.h).constrain(constraints)]
|
Frame::new(size, size.h)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add background shape if desired.
|
// Add background shape if desired.
|
||||||
if let Some(fill) = self.fill {
|
if let Some(fill) = self.fill {
|
||||||
let frame = Rc::make_mut(&mut frames[0].item);
|
|
||||||
let (pos, geometry) = match self.shape {
|
let (pos, geometry) = match self.shape {
|
||||||
ShapeKind::Square | ShapeKind::Rect => {
|
ShapeKind::Square | ShapeKind::Rect => {
|
||||||
(Point::zero(), Geometry::Rect(frame.size))
|
(Point::zero(), Geometry::Rect(frame.size))
|
||||||
@ -131,12 +103,12 @@ impl Layout for ShapeNode {
|
|||||||
frame.prepend(pos, Element::Geometry(geometry, fill));
|
frame.prepend(pos, Element::Geometry(geometry, fill));
|
||||||
}
|
}
|
||||||
|
|
||||||
frames
|
frame
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ShapeNode> for LayoutNode {
|
impl From<ShapeNode> for InlineNode {
|
||||||
fn from(shape: ShapeNode) -> Self {
|
fn from(node: ShapeNode) -> Self {
|
||||||
Self::new(shape)
|
Self::new(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
/// Spacing between other nodes.
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
|
||||||
pub struct SpacingNode {
|
|
||||||
/// Which axis to space on.
|
|
||||||
pub axis: SpecAxis,
|
|
||||||
/// How much spacing to add.
|
|
||||||
pub amount: Linear,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for SpacingNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
_: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
let base = regions.base.get(self.axis);
|
|
||||||
let resolved = self.amount.resolve(base);
|
|
||||||
let limit = regions.current.get(self.axis);
|
|
||||||
|
|
||||||
// Generate constraints.
|
|
||||||
let mut cts = Constraints::new(regions.expand);
|
|
||||||
if self.amount.is_relative() {
|
|
||||||
cts.base.set(self.axis, Some(base));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the spacing fits into the region, any larger region would also do.
|
|
||||||
// If it was limited though, any change it region size might lead to
|
|
||||||
// different results.
|
|
||||||
if resolved < limit {
|
|
||||||
cts.min.set(self.axis, Some(resolved));
|
|
||||||
} else {
|
|
||||||
cts.exact.set(self.axis, Some(limit));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create frame with limited spacing size along spacing axis and zero
|
|
||||||
// extent along the other axis.
|
|
||||||
let mut size = Size::zero();
|
|
||||||
size.set(self.axis, resolved.min(limit));
|
|
||||||
vec![Frame::new(size, size.h).constrain(cts)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SpacingNode> for LayoutNode {
|
|
||||||
fn from(spacing: SpacingNode) -> Self {
|
|
||||||
Self::new(spacing)
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,7 +12,16 @@ pub struct StackNode {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Layout for StackNode {
|
/// A child of a stack node.
|
||||||
|
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
||||||
|
pub enum StackChild {
|
||||||
|
/// Spacing between other nodes.
|
||||||
|
Spacing(Linear),
|
||||||
|
/// Any block node and how to align it in the stack.
|
||||||
|
Any(BlockNode, Align),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlockLevel for StackNode {
|
||||||
fn layout(
|
fn layout(
|
||||||
&self,
|
&self,
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
@ -22,37 +31,18 @@ impl Layout for StackNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StackNode> for LayoutNode {
|
impl From<StackNode> for BlockNode {
|
||||||
fn from(stack: StackNode) -> Self {
|
fn from(node: StackNode) -> Self {
|
||||||
Self::new(stack)
|
Self::new(node)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A child of a stack node.
|
|
||||||
#[cfg_attr(feature = "layout-cache", derive(Hash))]
|
|
||||||
pub struct StackChild {
|
|
||||||
/// The node itself.
|
|
||||||
pub node: LayoutNode,
|
|
||||||
/// How to align the node along the block axis.
|
|
||||||
pub align: Align,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackChild {
|
|
||||||
/// Create a new stack child.
|
|
||||||
pub fn new(node: impl Into<LayoutNode>, align: Align) -> Self {
|
|
||||||
Self { node: node.into(), align }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a spacing stack child.
|
|
||||||
pub fn spacing(amount: impl Into<Linear>, axis: SpecAxis) -> Self {
|
|
||||||
Self::new(SpacingNode { amount: amount.into(), axis }, Align::Start)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for StackChild {
|
impl Debug for StackChild {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "{:?}: ", self.align)?;
|
match self {
|
||||||
self.node.fmt(f)
|
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
||||||
|
Self::Any(node, _) => node.fmt(f),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,12 +96,17 @@ impl<'a> StackLayouter<'a> {
|
|||||||
/// Layout all children.
|
/// Layout all children.
|
||||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
||||||
for child in &self.stack.children {
|
for child in &self.stack.children {
|
||||||
let frames = child.node.layout(ctx, &self.regions);
|
match *child {
|
||||||
let len = frames.len();
|
StackChild::Spacing(amount) => self.space(amount),
|
||||||
for (i, frame) in frames.into_iter().enumerate() {
|
StackChild::Any(ref node, align) => {
|
||||||
self.push_frame(frame.item, child.align);
|
let frames = node.layout(ctx, &self.regions);
|
||||||
if i + 1 < len {
|
let len = frames.len();
|
||||||
self.finish_region();
|
for (i, frame) in frames.into_iter().enumerate() {
|
||||||
|
self.push_frame(frame.item, align);
|
||||||
|
if i + 1 < len {
|
||||||
|
self.finish_region();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,6 +115,22 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.finished
|
self.finished
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add block-axis spacing into the current region.
|
||||||
|
fn space(&mut self, amount: Linear) {
|
||||||
|
// Resolve the linear.
|
||||||
|
let full = self.full.get(self.axis);
|
||||||
|
let resolved = amount.resolve(full);
|
||||||
|
|
||||||
|
// Cap the spacing to the remaining available space. This action does
|
||||||
|
// not directly affect the constraints because of the cap.
|
||||||
|
let remaining = self.regions.current.get_mut(self.axis);
|
||||||
|
let capped = resolved.min(*remaining);
|
||||||
|
|
||||||
|
// Grow our size and shrink the available space in the region.
|
||||||
|
self.used.block += capped;
|
||||||
|
*remaining -= capped;
|
||||||
|
}
|
||||||
|
|
||||||
/// Push a frame into the current region.
|
/// Push a frame into the current region.
|
||||||
fn push_frame(&mut self, frame: Rc<Frame>, align: Align) {
|
fn push_frame(&mut self, frame: Rc<Frame>, align: Align) {
|
||||||
// Grow our size.
|
// Grow our size.
|
||||||
|
@ -3,7 +3,7 @@ use std::ops::Range;
|
|||||||
|
|
||||||
use rustybuzz::UnicodeBuffer;
|
use rustybuzz::UnicodeBuffer;
|
||||||
|
|
||||||
use super::{Element, Frame, Glyph, LayoutContext, Text};
|
use super::*;
|
||||||
use crate::font::{Face, FaceId, FontVariant};
|
use crate::font::{Face, FaceId, FontVariant};
|
||||||
use crate::geom::{Dir, Em, Length, Point, Size};
|
use crate::geom::{Dir, Em, Length, Point, Size};
|
||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
@ -13,8 +13,8 @@ use crate::util::SliceExt;
|
|||||||
pub fn shape<'a>(
|
pub fn shape<'a>(
|
||||||
ctx: &mut LayoutContext,
|
ctx: &mut LayoutContext,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
dir: Dir,
|
|
||||||
style: &'a TextStyle,
|
style: &'a TextStyle,
|
||||||
|
dir: Dir,
|
||||||
) -> ShapedText<'a> {
|
) -> ShapedText<'a> {
|
||||||
let mut glyphs = vec![];
|
let mut glyphs = vec![];
|
||||||
if !text.is_empty() {
|
if !text.is_empty() {
|
||||||
@ -23,16 +23,15 @@ pub fn shape<'a>(
|
|||||||
&mut glyphs,
|
&mut glyphs,
|
||||||
0,
|
0,
|
||||||
text,
|
text,
|
||||||
dir,
|
|
||||||
style.size,
|
style.size,
|
||||||
style.variant(),
|
style.variant(),
|
||||||
style.families(),
|
style.families(),
|
||||||
None,
|
None,
|
||||||
|
dir,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (size, baseline) = measure(ctx, &glyphs, style);
|
let (size, baseline) = measure(ctx, &glyphs, style);
|
||||||
|
|
||||||
ShapedText {
|
ShapedText {
|
||||||
text,
|
text,
|
||||||
dir,
|
dir,
|
||||||
@ -134,7 +133,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
glyphs: Cow::Borrowed(glyphs),
|
glyphs: Cow::Borrowed(glyphs),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shape(ctx, &self.text[text_range], self.dir, self.style)
|
shape(ctx, &self.text[text_range], self.style, self.dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,11 +208,11 @@ fn shape_segment<'a>(
|
|||||||
glyphs: &mut Vec<ShapedGlyph>,
|
glyphs: &mut Vec<ShapedGlyph>,
|
||||||
base: usize,
|
base: usize,
|
||||||
text: &str,
|
text: &str,
|
||||||
dir: Dir,
|
|
||||||
size: Length,
|
size: Length,
|
||||||
variant: FontVariant,
|
variant: FontVariant,
|
||||||
mut families: impl Iterator<Item = &'a str> + Clone,
|
mut families: impl Iterator<Item = &'a str> + Clone,
|
||||||
mut first_face: Option<FaceId>,
|
mut first_face: Option<FaceId>,
|
||||||
|
dir: Dir,
|
||||||
) {
|
) {
|
||||||
// Select the font family.
|
// Select the font family.
|
||||||
let (face_id, fallback) = loop {
|
let (face_id, fallback) = loop {
|
||||||
@ -316,11 +315,11 @@ fn shape_segment<'a>(
|
|||||||
glyphs,
|
glyphs,
|
||||||
base + range.start,
|
base + range.start,
|
||||||
&text[range],
|
&text[range],
|
||||||
dir,
|
|
||||||
size,
|
size,
|
||||||
variant,
|
variant,
|
||||||
families.clone(),
|
families.clone(),
|
||||||
first_face,
|
first_face,
|
||||||
|
dir,
|
||||||
);
|
);
|
||||||
|
|
||||||
face = ctx.fonts.get(face_id);
|
face = ctx.fonts.get(face_id);
|
@ -1,132 +0,0 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
use {
|
|
||||||
fxhash::FxHasher64,
|
|
||||||
std::any::Any,
|
|
||||||
std::hash::{Hash, Hasher},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A tree of layout nodes.
|
|
||||||
pub struct LayoutTree {
|
|
||||||
/// Runs of pages with the same properties.
|
|
||||||
pub runs: Vec<PageRun>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutTree {
|
|
||||||
/// Layout the tree into a collection of frames.
|
|
||||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
|
||||||
self.runs.iter().flat_map(|run| run.layout(ctx)).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for LayoutTree {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.debug_list().entries(&self.runs).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A run of pages that all have the same properties.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct PageRun {
|
|
||||||
/// The size of each page.
|
|
||||||
pub size: Size,
|
|
||||||
/// The layout node that produces the actual pages (typically a
|
|
||||||
/// [`StackNode`]).
|
|
||||||
pub child: LayoutNode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PageRun {
|
|
||||||
/// Layout the page run.
|
|
||||||
pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<Rc<Frame>> {
|
|
||||||
// When one of the lengths is infinite the page fits its content along
|
|
||||||
// that axis.
|
|
||||||
let expand = self.size.to_spec().map(Length::is_finite);
|
|
||||||
let regions = Regions::repeat(self.size, self.size, expand);
|
|
||||||
self.child.layout(ctx, ®ions).into_iter().map(|c| c.item).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dynamic layouting node.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct LayoutNode {
|
|
||||||
node: Rc<dyn Layout>,
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
hash: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutNode {
|
|
||||||
/// Create a new instance from any node that satisifies the required bounds.
|
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
|
||||||
pub fn new<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + 'static,
|
|
||||||
{
|
|
||||||
Self { node: Rc::new(node) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new instance from any node that satisifies the required bounds.
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
pub fn new<T>(node: T) -> Self
|
|
||||||
where
|
|
||||||
T: Layout + Hash + 'static,
|
|
||||||
{
|
|
||||||
let hash = {
|
|
||||||
let mut state = FxHasher64::default();
|
|
||||||
node.type_id().hash(&mut state);
|
|
||||||
node.hash(&mut state);
|
|
||||||
state.finish()
|
|
||||||
};
|
|
||||||
|
|
||||||
Self { node: Rc::new(node), hash }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Layout for LayoutNode {
|
|
||||||
fn layout(
|
|
||||||
&self,
|
|
||||||
ctx: &mut LayoutContext,
|
|
||||||
regions: &Regions,
|
|
||||||
) -> Vec<Constrained<Rc<Frame>>> {
|
|
||||||
#[cfg(not(feature = "layout-cache"))]
|
|
||||||
return self.node.layout(ctx, regions);
|
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
ctx.layouts.get(self.hash, regions).unwrap_or_else(|| {
|
|
||||||
ctx.level += 1;
|
|
||||||
let frames = self.node.layout(ctx, regions);
|
|
||||||
ctx.level -= 1;
|
|
||||||
|
|
||||||
let entry = FramesEntry::new(frames.clone(), ctx.level);
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
if !entry.check(regions) {
|
|
||||||
eprintln!("node: {:#?}", self.node);
|
|
||||||
eprintln!("regions: {:#?}", regions);
|
|
||||||
eprintln!(
|
|
||||||
"constraints: {:#?}",
|
|
||||||
frames.iter().map(|c| c.constraints).collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
panic!("constraints did not match regions they were created for");
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.layouts.insert(self.hash, entry);
|
|
||||||
frames
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for LayoutNode {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
self.node.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "layout-cache")]
|
|
||||||
impl Hash for LayoutNode {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
state.write_u64(self.hash);
|
|
||||||
}
|
|
||||||
}
|
|
11
src/lib.rs
11
src/lib.rs
@ -8,7 +8,7 @@
|
|||||||
//! - **Evaluation:** The next step is to [evaluate] the markup. This produces a
|
//! - **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
|
//! [module], consisting of a scope of values that were exported by the code
|
||||||
//! and a template with the contents of the module. This template can be
|
//! and a template with the contents of the module. This template can be
|
||||||
//! [instantiated] with a style to produce a layout tree, a high-level, fully
|
//! instantiated with a style to produce a layout tree, a high-level, fully
|
||||||
//! styled representation of the document. The nodes of this tree are
|
//! styled representation of the document. The nodes of this tree are
|
||||||
//! self-contained and order-independent and thus much better suited for
|
//! self-contained and order-independent and thus much better suited for
|
||||||
//! layouting than the raw markup.
|
//! layouting than the raw markup.
|
||||||
@ -23,7 +23,6 @@
|
|||||||
//! [markup]: syntax::Markup
|
//! [markup]: syntax::Markup
|
||||||
//! [evaluate]: eval::eval
|
//! [evaluate]: eval::eval
|
||||||
//! [module]: eval::Module
|
//! [module]: eval::Module
|
||||||
//! [instantiated]: eval::Template::to_tree
|
|
||||||
//! [layout tree]: layout::LayoutTree
|
//! [layout tree]: layout::LayoutTree
|
||||||
//! [layouted]: layout::layout
|
//! [layouted]: layout::layout
|
||||||
//! [PDF]: export::pdf
|
//! [PDF]: export::pdf
|
||||||
@ -53,7 +52,7 @@ use crate::font::FontStore;
|
|||||||
use crate::image::ImageStore;
|
use crate::image::ImageStore;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use crate::layout::{EvictionPolicy, LayoutCache};
|
use crate::layout::{EvictionPolicy, LayoutCache};
|
||||||
use crate::layout::{Frame, LayoutTree};
|
use crate::layout::{Frame, PageNode};
|
||||||
use crate::loading::Loader;
|
use crate::loading::Loader;
|
||||||
use crate::source::{SourceId, SourceStore};
|
use crate::source::{SourceId, SourceStore};
|
||||||
use crate::style::Style;
|
use crate::style::Style;
|
||||||
@ -110,10 +109,10 @@ impl Context {
|
|||||||
eval::eval(self, id, &ast)
|
eval::eval(self, id, &ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a source file and produce the resulting layout tree.
|
/// Execute a source file and produce the resulting page nodes.
|
||||||
pub fn execute(&mut self, id: SourceId) -> TypResult<LayoutTree> {
|
pub fn execute(&mut self, id: SourceId) -> TypResult<Vec<PageNode>> {
|
||||||
let module = self.evaluate(id)?;
|
let module = self.evaluate(id)?;
|
||||||
Ok(module.template.to_tree(&self.style))
|
Ok(module.template.to_pages(&self.style))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Typeset a source file into a collection of layouted frames.
|
/// Typeset a source file into a collection of layouted frames.
|
||||||
|
@ -220,16 +220,16 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
for child in &list {
|
for child in &list {
|
||||||
match child {
|
match child {
|
||||||
Child::Spacing(v) => {
|
Child::Spacing(v) => {
|
||||||
children.push(StackChild::spacing(*v, dir.axis()));
|
children.push(StackChild::Spacing(*v));
|
||||||
delayed = None;
|
delayed = None;
|
||||||
}
|
}
|
||||||
Child::Any(template) => {
|
Child::Any(template) => {
|
||||||
if let Some(v) = delayed {
|
if let Some(v) = delayed {
|
||||||
children.push(StackChild::spacing(v, dir.axis()));
|
children.push(StackChild::Spacing(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = template.to_stack(style);
|
let node = template.to_stack(style).into();
|
||||||
children.push(StackChild::new(node, style.aligns.block));
|
children.push(StackChild::Any(node, style.aligns.block));
|
||||||
delayed = spacing;
|
delayed = spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,18 +110,18 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
|||||||
|
|
||||||
/// `par`: Configure paragraphs.
|
/// `par`: Configure paragraphs.
|
||||||
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||||
let par_spacing = args.named("spacing")?;
|
let spacing = args.named("spacing")?;
|
||||||
let line_spacing = args.named("leading")?;
|
let leading = args.named("leading")?;
|
||||||
|
|
||||||
ctx.template.modify(move |style| {
|
ctx.template.modify(move |style| {
|
||||||
let par = style.par_mut();
|
let par = style.par_mut();
|
||||||
|
|
||||||
if let Some(par_spacing) = par_spacing {
|
if let Some(spacing) = spacing {
|
||||||
par.par_spacing = par_spacing;
|
par.spacing = spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(line_spacing) = line_spacing {
|
if let Some(leading) = leading {
|
||||||
par.line_spacing = line_spacing;
|
par.leading = leading;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -43,13 +43,13 @@ impl Style {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The resolved line spacing.
|
/// The resolved line spacing.
|
||||||
pub fn line_spacing(&self) -> Length {
|
pub fn leading(&self) -> Length {
|
||||||
self.par.line_spacing.resolve(self.text.size)
|
self.par.leading.resolve(self.text.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The resolved paragraph spacing.
|
/// The resolved paragraph spacing.
|
||||||
pub fn par_spacing(&self) -> Length {
|
pub fn par_spacing(&self) -> Length {
|
||||||
self.par.par_spacing.resolve(self.text.size)
|
self.par.spacing.resolve(self.text.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,16 +105,16 @@ impl Default for PageStyle {
|
|||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct ParStyle {
|
pub struct ParStyle {
|
||||||
/// The spacing between paragraphs (dependent on scaled font size).
|
/// The spacing between paragraphs (dependent on scaled font size).
|
||||||
pub par_spacing: Linear,
|
pub spacing: Linear,
|
||||||
/// The spacing between lines (dependent on scaled font size).
|
/// The spacing between lines (dependent on scaled font size).
|
||||||
pub line_spacing: Linear,
|
pub leading: Linear,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ParStyle {
|
impl Default for ParStyle {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
par_spacing: Relative::new(1.2).into(),
|
spacing: Relative::new(1.2).into(),
|
||||||
line_spacing: Relative::new(0.65).into(),
|
leading: Relative::new(0.65).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use super::RcExt;
|
||||||
|
|
||||||
/// An economical string with inline storage and clone-on-write semantics.
|
/// An economical string with inline storage and clone-on-write semantics.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EcoString(Repr);
|
pub struct EcoString(Repr);
|
||||||
@ -293,10 +295,7 @@ impl From<EcoString> for String {
|
|||||||
fn from(s: EcoString) -> Self {
|
fn from(s: EcoString) -> Self {
|
||||||
match s.0 {
|
match s.0 {
|
||||||
Repr::Small { .. } => s.as_str().to_owned(),
|
Repr::Small { .. } => s.as_str().to_owned(),
|
||||||
Repr::Large(rc) => match Rc::try_unwrap(rc) {
|
Repr::Large(rc) => Rc::take(rc),
|
||||||
Ok(string) => string,
|
|
||||||
Err(rc) => (*rc).clone(),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ use std::cell::RefMut;
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Component, Path, PathBuf};
|
use std::path::{Component, Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
/// Additional methods for booleans.
|
/// Additional methods for booleans.
|
||||||
pub trait BoolExt {
|
pub trait BoolExt {
|
||||||
@ -67,6 +68,25 @@ impl<T> OptionExt<T> for Option<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional methods for reference-counted pointers.
|
||||||
|
pub trait RcExt<T> {
|
||||||
|
/// Takes the inner value if there is exactly one strong reference and
|
||||||
|
/// clones it otherwise.
|
||||||
|
fn take(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RcExt<T> for Rc<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
fn take(self) -> T {
|
||||||
|
match Rc::try_unwrap(self) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(rc) => (*rc).clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Additional methods for slices.
|
/// Additional methods for slices.
|
||||||
pub trait SliceExt<T> {
|
pub trait SliceExt<T> {
|
||||||
/// Split a slice into consecutive groups with the same key.
|
/// Split a slice into consecutive groups with the same key.
|
||||||
|
@ -17,7 +17,7 @@ use typst::geom::{
|
|||||||
};
|
};
|
||||||
use typst::image::Image;
|
use typst::image::Image;
|
||||||
#[cfg(feature = "layout-cache")]
|
#[cfg(feature = "layout-cache")]
|
||||||
use typst::layout::LayoutTree;
|
use typst::layout::PageNode;
|
||||||
use typst::layout::{layout, Element, Frame, Geometry, Text};
|
use typst::layout::{layout, Element, Frame, Geometry, Text};
|
||||||
use typst::loading::FsLoader;
|
use typst::loading::FsLoader;
|
||||||
use typst::parse::Scanner;
|
use typst::parse::Scanner;
|
||||||
@ -282,7 +282,7 @@ fn test_part(
|
|||||||
fn test_incremental(
|
fn test_incremental(
|
||||||
ctx: &mut Context,
|
ctx: &mut Context,
|
||||||
i: usize,
|
i: usize,
|
||||||
tree: &LayoutTree,
|
tree: &[PageNode],
|
||||||
frames: &[Rc<Frame>],
|
frames: &[Rc<Frame>],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user