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) {
|
||||
let (mut ctx, id) = context();
|
||||
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) {
|
||||
|
@ -7,6 +7,7 @@ use std::rc::Rc;
|
||||
|
||||
use super::Value;
|
||||
use crate::diag::StrResult;
|
||||
use crate::util::RcExt;
|
||||
|
||||
/// Create a new [`Array`] from values.
|
||||
#[allow(unused_macros)]
|
||||
@ -169,10 +170,7 @@ impl IntoIterator for Array {
|
||||
type IntoIter = std::vec::IntoIter<Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match Rc::try_unwrap(self.0) {
|
||||
Ok(vec) => vec.into_iter(),
|
||||
Err(rc) => (*rc).clone().into_iter(),
|
||||
}
|
||||
Rc::take(self.0).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ use std::rc::Rc;
|
||||
|
||||
use super::{Str, Value};
|
||||
use crate::diag::StrResult;
|
||||
use crate::util::RcExt;
|
||||
|
||||
/// Create a new [`Dict`] from key-value pairs.
|
||||
#[allow(unused_macros)]
|
||||
@ -135,10 +136,7 @@ impl IntoIterator for Dict {
|
||||
type IntoIter = std::collections::btree_map::IntoIter<Str, Value>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
match Rc::try_unwrap(self.0) {
|
||||
Ok(map) => map.into_iter(),
|
||||
Err(rc) => (*rc).clone().into_iter(),
|
||||
}
|
||||
Rc::take(self.0).into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,9 @@ use std::rc::Rc;
|
||||
|
||||
use super::Str;
|
||||
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::{
|
||||
Decoration, LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild,
|
||||
BlockNode, Decoration, InlineNode, PadNode, PageNode, ParChild, ParNode, StackChild,
|
||||
StackNode,
|
||||
};
|
||||
use crate::style::Style;
|
||||
@ -34,9 +34,9 @@ enum TemplateNode {
|
||||
/// Spacing.
|
||||
Spacing(GenAxis, Linear),
|
||||
/// An inline node builder.
|
||||
Inline(Rc<dyn Fn(&Style) -> LayoutNode>, Vec<Decoration>),
|
||||
Inline(Rc<dyn Fn(&Style) -> InlineNode>, Vec<Decoration>),
|
||||
/// An block node builder.
|
||||
Block(Rc<dyn Fn(&Style) -> LayoutNode>),
|
||||
Block(Rc<dyn Fn(&Style) -> BlockNode>),
|
||||
/// Save the current style.
|
||||
Save,
|
||||
/// Restore the last saved style.
|
||||
@ -55,7 +55,7 @@ impl Template {
|
||||
pub fn from_inline<F, T>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Style) -> T + 'static,
|
||||
T: Into<LayoutNode>,
|
||||
T: Into<InlineNode>,
|
||||
{
|
||||
let node = TemplateNode::Inline(Rc::new(move |s| f(s).into()), vec![]);
|
||||
Self(Rc::new(vec![node]))
|
||||
@ -65,7 +65,7 @@ impl Template {
|
||||
pub fn from_block<F, T>(f: F) -> Self
|
||||
where
|
||||
F: Fn(&Style) -> T + 'static,
|
||||
T: Into<LayoutNode>,
|
||||
T: Into<BlockNode>,
|
||||
{
|
||||
let node = TemplateNode::Block(Rc::new(move |s| f(s).into()));
|
||||
Self(Rc::new(vec![node]))
|
||||
@ -164,10 +164,10 @@ impl Template {
|
||||
|
||||
/// Build the layout tree resulting from instantiating the template with the
|
||||
/// 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);
|
||||
builder.template(self);
|
||||
builder.build_tree()
|
||||
builder.build_pages()
|
||||
}
|
||||
|
||||
/// Repeat this template `n` times.
|
||||
@ -243,8 +243,8 @@ struct Builder {
|
||||
style: Style,
|
||||
/// Snapshots of the style.
|
||||
snapshots: Vec<Style>,
|
||||
/// The tree of finished page runs.
|
||||
tree: LayoutTree,
|
||||
/// The finished page nodes.
|
||||
finished: Vec<PageNode>,
|
||||
/// When we are building the top-level layout trees, this contains metrics
|
||||
/// of the page. While building a stack, this is `None`.
|
||||
page: Option<PageBuilder>,
|
||||
@ -258,7 +258,7 @@ impl Builder {
|
||||
Self {
|
||||
style: style.clone(),
|
||||
snapshots: vec![],
|
||||
tree: LayoutTree { runs: vec![] },
|
||||
finished: vec![],
|
||||
page: pages.then(|| PageBuilder::new(style, true)),
|
||||
stack: StackBuilder::new(style),
|
||||
}
|
||||
@ -309,7 +309,7 @@ impl Builder {
|
||||
fn parbreak(&mut self) {
|
||||
let amount = self.style.par_spacing();
|
||||
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.
|
||||
@ -317,7 +317,7 @@ impl Builder {
|
||||
if let Some(builder) = &mut self.page {
|
||||
let page = mem::replace(builder, PageBuilder::new(&self.style, hard));
|
||||
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.
|
||||
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;
|
||||
self.stack.par.push(ParChild::Any(node.into(), align, decos.to_vec()));
|
||||
}
|
||||
|
||||
/// 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.stack.push(StackChild::new(node, self.style.aligns.block));
|
||||
self.stack.push(StackChild::Any(node.into(), self.style.aligns.block));
|
||||
self.parbreak();
|
||||
}
|
||||
|
||||
@ -344,7 +344,7 @@ impl Builder {
|
||||
match axis {
|
||||
GenAxis::Block => {
|
||||
self.stack.finish_par(&self.style);
|
||||
self.stack.push_hard(StackChild::spacing(amount, SpecAxis::Vertical));
|
||||
self.stack.push_hard(StackChild::Spacing(amount));
|
||||
}
|
||||
GenAxis::Inline => {
|
||||
self.stack.par.push_hard(ParChild::Spacing(amount));
|
||||
@ -359,10 +359,10 @@ impl Builder {
|
||||
}
|
||||
|
||||
/// 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());
|
||||
self.pagebreak(true, false);
|
||||
self.tree
|
||||
self.finished
|
||||
}
|
||||
|
||||
/// 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;
|
||||
(!child.children.is_empty() || (keep && hard)).then(|| PageRun {
|
||||
(!child.children.is_empty() || (keep && hard)).then(|| PageNode {
|
||||
size,
|
||||
child: PadNode { padding, child: child.into() }.into(),
|
||||
})
|
||||
@ -456,7 +456,7 @@ impl StackBuilder {
|
||||
struct ParBuilder {
|
||||
align: Align,
|
||||
dir: Dir,
|
||||
line_spacing: Length,
|
||||
leading: Length,
|
||||
children: Vec<ParChild>,
|
||||
last: Last<ParChild>,
|
||||
}
|
||||
@ -466,7 +466,7 @@ impl ParBuilder {
|
||||
Self {
|
||||
align: style.aligns.block,
|
||||
dir: style.dir,
|
||||
line_spacing: style.line_spacing(),
|
||||
leading: style.leading(),
|
||||
children: vec![],
|
||||
last: Last::None,
|
||||
}
|
||||
@ -507,9 +507,9 @@ impl ParBuilder {
|
||||
}
|
||||
|
||||
fn build(self) -> Option<StackChild> {
|
||||
let Self { align, dir, line_spacing, children, .. } = self;
|
||||
let Self { align, dir, leading, children, .. } = self;
|
||||
(!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 crate::diag::TypResult;
|
||||
use crate::geom::{Align, SpecAxis};
|
||||
use crate::geom::Align;
|
||||
use crate::layout::{ParChild, ParNode, StackChild, StackNode};
|
||||
use crate::syntax::*;
|
||||
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| {
|
||||
let label = ParNode {
|
||||
dir: style.dir,
|
||||
line_spacing: style.line_spacing(),
|
||||
leading: style.leading(),
|
||||
children: vec![ParChild::Text(
|
||||
(&label).into(),
|
||||
style.aligns.inline,
|
||||
@ -119,9 +119,9 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) {
|
||||
StackNode {
|
||||
dir: style.dir,
|
||||
children: vec![
|
||||
StackChild::new(label, Align::Start),
|
||||
StackChild::spacing(style.text.size / 2.0, SpecAxis::Horizontal),
|
||||
StackChild::new(body.to_stack(&style), Align::Start),
|
||||
StackChild::Any(label.into(), Align::Start),
|
||||
StackChild::Spacing((style.text.size / 2.0).into()),
|
||||
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.
|
||||
pub item: T,
|
||||
/// Constraints on regions in which the item is valid.
|
||||
pub constraints: Constraints,
|
||||
pub cts: Constraints,
|
||||
}
|
||||
|
||||
/// Describe regions that match them.
|
||||
|
@ -19,6 +19,70 @@ pub struct Frame {
|
||||
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
|
||||
/// nested frame.
|
||||
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
@ -29,6 +93,46 @@ pub enum FrameChild {
|
||||
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.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Element {
|
||||
@ -81,107 +185,3 @@ pub enum Geometry {
|
||||
/// A filled bezier 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.
|
||||
pub gutter: Spec<Vec<TrackSizing>>,
|
||||
/// 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.
|
||||
@ -23,7 +23,7 @@ pub enum TrackSizing {
|
||||
Fractional(Fractional),
|
||||
}
|
||||
|
||||
impl Layout for GridNode {
|
||||
impl BlockLevel for GridNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -40,9 +40,9 @@ impl Layout for GridNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GridNode> for LayoutNode {
|
||||
fn from(grid: GridNode) -> Self {
|
||||
Self::new(grid)
|
||||
impl From<GridNode> for BlockNode {
|
||||
fn from(node: GridNode) -> Self {
|
||||
Self::new(node)
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ struct GridLayouter<'a> {
|
||||
/// The row tracks including gutter tracks.
|
||||
rows: Vec<TrackSizing>,
|
||||
/// The children of the grid.
|
||||
children: &'a [LayoutNode],
|
||||
children: &'a [BlockNode],
|
||||
/// The regions to layout into.
|
||||
regions: Regions,
|
||||
/// Resolved column sizes.
|
||||
@ -546,7 +546,7 @@ impl<'a> GridLayouter<'a> {
|
||||
///
|
||||
/// Returns `None` if it's a gutter cell.
|
||||
#[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!(y < self.rows.len());
|
||||
|
||||
|
@ -13,31 +13,23 @@ pub struct ImageNode {
|
||||
pub height: Option<Linear>,
|
||||
}
|
||||
|
||||
impl Layout for ImageNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
impl InlineLevel for ImageNode {
|
||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||
let img = ctx.images.get(self.id);
|
||||
let pixel_size = Spec::new(img.width() as f64, img.height() as f64);
|
||||
let pixel_ratio = pixel_size.x / pixel_size.y;
|
||||
|
||||
let width = self.width.map(|w| w.resolve(regions.base.w));
|
||||
let height = self.height.map(|w| w.resolve(regions.base.h));
|
||||
|
||||
let mut cts = Constraints::new(regions.expand);
|
||||
cts.set_base_if_linear(regions.base, Spec::new(self.width, self.height));
|
||||
let width = self.width.map(|w| w.resolve(base.w));
|
||||
let height = self.height.map(|w| w.resolve(base.h));
|
||||
|
||||
let size = match (width, height) {
|
||||
(Some(width), Some(height)) => Size::new(width, height),
|
||||
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
||||
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
||||
(None, None) => {
|
||||
cts.exact.x = Some(regions.current.w);
|
||||
if regions.current.w.is_finite() {
|
||||
if space.is_finite() {
|
||||
// Fit to width.
|
||||
Size::new(regions.current.w, regions.current.w / pixel_ratio)
|
||||
Size::new(space, space / pixel_ratio)
|
||||
} else {
|
||||
// Unbounded width, we have to make up something,
|
||||
// so it is 1pt per pixel.
|
||||
@ -48,12 +40,12 @@ impl Layout for ImageNode {
|
||||
|
||||
let mut frame = Frame::new(size, size.h);
|
||||
frame.push(Point::zero(), Element::Image(self.id, size));
|
||||
vec![frame.constrain(cts)]
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ImageNode> for LayoutNode {
|
||||
fn from(image: ImageNode) -> Self {
|
||||
Self::new(image)
|
||||
impl From<ImageNode> for InlineNode {
|
||||
fn from(node: ImageNode) -> Self {
|
||||
Self::new(node)
|
||||
}
|
||||
}
|
||||
|
@ -230,7 +230,7 @@ impl FramesEntry {
|
||||
let mut iter = regions.iter();
|
||||
self.frames.iter().all(|frame| {
|
||||
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>>> {
|
||||
vec![Constrained {
|
||||
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 regions;
|
||||
mod shape;
|
||||
mod shaping;
|
||||
mod spacing;
|
||||
mod stack;
|
||||
mod tree;
|
||||
mod text;
|
||||
|
||||
pub use self::image::*;
|
||||
pub use constraints::*;
|
||||
@ -25,12 +23,10 @@ pub use pad::*;
|
||||
pub use par::*;
|
||||
pub use regions::*;
|
||||
pub use shape::*;
|
||||
pub use shaping::*;
|
||||
pub use spacing::*;
|
||||
pub use stack::*;
|
||||
pub use tree::*;
|
||||
pub use text::*;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::font::FontStore;
|
||||
@ -39,10 +35,20 @@ use crate::image::ImageStore;
|
||||
use crate::util::OptionExt;
|
||||
use crate::Context;
|
||||
|
||||
/// Layout a tree into a collection of frames.
|
||||
pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
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);
|
||||
tree.layout(&mut ctx)
|
||||
node.layout(&mut ctx)
|
||||
}
|
||||
|
||||
/// The context for layouting.
|
||||
@ -73,12 +79,198 @@ impl<'a> LayoutContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Layout a node.
|
||||
pub trait Layout: Debug {
|
||||
/// Layout the node into the given regions.
|
||||
/// Page-level nodes directly produce frames representing pages.
|
||||
///
|
||||
/// 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(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> 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.
|
||||
pub padding: Sides<Linear>,
|
||||
/// The child node whose sides to pad.
|
||||
pub child: LayoutNode,
|
||||
pub child: BlockNode,
|
||||
}
|
||||
|
||||
impl Layout for PadNode {
|
||||
impl BlockLevel for PadNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -22,7 +22,7 @@ impl Layout for PadNode {
|
||||
®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())
|
||||
{
|
||||
fn solve_axis(length: Length, padding: Linear) -> Length {
|
||||
@ -46,7 +46,7 @@ impl Layout for PadNode {
|
||||
new.push_frame(origin, prev);
|
||||
|
||||
// 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() {
|
||||
*x += padding.size().w;
|
||||
}
|
||||
@ -56,18 +56,18 @@ impl Layout for PadNode {
|
||||
}
|
||||
|
||||
// Set exact and base constraints if the child had them.
|
||||
constraints.exact.x.and_set(Some(current.w));
|
||||
constraints.exact.y.and_set(Some(current.h));
|
||||
constraints.base.x.and_set(Some(base.w));
|
||||
constraints.base.y.and_set(Some(base.h));
|
||||
cts.exact.x.and_set(Some(current.w));
|
||||
cts.exact.y.and_set(Some(current.h));
|
||||
cts.base.x.and_set(Some(base.w));
|
||||
cts.base.y.and_set(Some(base.h));
|
||||
|
||||
// Also set base constraints if the padding 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() {
|
||||
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 {
|
||||
fn from(pad: PadNode) -> Self {
|
||||
Self::new(pad)
|
||||
impl From<PadNode> for BlockNode {
|
||||
fn from(node: PadNode) -> Self {
|
||||
Self::new(node)
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ pub struct ParNode {
|
||||
/// The inline direction of this paragraph.
|
||||
pub dir: Dir,
|
||||
/// The spacing to insert between each line.
|
||||
pub line_spacing: Length,
|
||||
pub leading: Length,
|
||||
/// The nodes to be arranged in a paragraph.
|
||||
pub children: Vec<ParChild>,
|
||||
}
|
||||
@ -31,10 +31,10 @@ pub enum ParChild {
|
||||
/// A run of text and how to align it in its line.
|
||||
Text(EcoString, Align, Rc<TextStyle>, Vec<Decoration>),
|
||||
/// 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(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -88,9 +88,9 @@ impl ParNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParNode> for LayoutNode {
|
||||
fn from(par: ParNode) -> Self {
|
||||
Self::new(par)
|
||||
impl From<ParNode> for BlockNode {
|
||||
fn from(node: ParNode) -> Self {
|
||||
Self::new(node)
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,7 +110,7 @@ struct ParLayouter<'a> {
|
||||
/// The top-level direction.
|
||||
dir: Dir,
|
||||
/// The line spacing.
|
||||
line_spacing: Length,
|
||||
leading: Length,
|
||||
/// Bidirectional text embedding levels for the paragraph.
|
||||
bidi: BidiInfo<'a>,
|
||||
/// Layouted children and separated text runs.
|
||||
@ -143,14 +143,14 @@ impl<'a> ParLayouter<'a> {
|
||||
// TODO: Also split by language and script.
|
||||
for (subrange, dir) in split_runs(&bidi, range) {
|
||||
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));
|
||||
ranges.push(subrange);
|
||||
}
|
||||
}
|
||||
ParChild::Any(node, align, decos) => {
|
||||
let frame = node.layout(ctx, regions).remove(0);
|
||||
items.push(ParItem::Frame(frame.item, *align, decos));
|
||||
let frame = node.layout(ctx, regions.current.w, regions.base);
|
||||
items.push(ParItem::Frame(frame, *align, decos));
|
||||
ranges.push(range);
|
||||
}
|
||||
}
|
||||
@ -158,7 +158,7 @@ impl<'a> ParLayouter<'a> {
|
||||
|
||||
Self {
|
||||
dir: par.dir,
|
||||
line_spacing: par.line_spacing,
|
||||
leading: par.leading,
|
||||
bidi,
|
||||
items,
|
||||
ranges,
|
||||
@ -171,7 +171,7 @@ impl<'a> ParLayouter<'a> {
|
||||
ctx: &mut LayoutContext,
|
||||
regions: Regions,
|
||||
) -> 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.
|
||||
// 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
|
||||
// tried line.
|
||||
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.
|
||||
if !stack.regions.current.h.fits(line.size.h) {
|
||||
let too_large = stack.size.h + self.line_spacing + line.size.h;
|
||||
stack.constraints.max.y.set_min(too_large);
|
||||
let too_large = stack.size.h + self.leading + line.size.h;
|
||||
stack.cts.max.y.set_min(too_large);
|
||||
}
|
||||
|
||||
stack.push(last_line);
|
||||
|
||||
stack.constraints.min.y = Some(stack.size.h);
|
||||
stack.cts.min.y = Some(stack.size.h);
|
||||
start = last_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
|
||||
// plus the line height would fit, therefore the constraint
|
||||
// below.
|
||||
let too_large = stack.size.h + self.line_spacing + line.size.h;
|
||||
stack.constraints.max.y.set_min(too_large);
|
||||
let too_large = stack.size.h + self.leading + line.size.h;
|
||||
stack.cts.max.y.set_min(too_large);
|
||||
|
||||
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 {
|
||||
// Otherwise, the line fits both horizontally and vertically
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((line, _)) = last {
|
||||
stack.push(line);
|
||||
stack.constraints.min.y = Some(stack.size.h);
|
||||
stack.cts.min.y = Some(stack.size.h);
|
||||
}
|
||||
|
||||
stack.finish(ctx)
|
||||
@ -292,7 +292,7 @@ enum ParItem<'a> {
|
||||
/// A shaped text run with consistent direction.
|
||||
Text(ShapedText<'a>, Align, &'a [Decoration]),
|
||||
/// A layouted child node.
|
||||
Frame(Rc<Frame>, Align, &'a [Decoration]),
|
||||
Frame(Frame, Align, &'a [Decoration]),
|
||||
}
|
||||
|
||||
impl ParItem<'_> {
|
||||
@ -463,10 +463,10 @@ impl<'a> LineLayout<'a> {
|
||||
ParItem::Frame(ref frame, align, decos) => {
|
||||
let mut frame = frame.clone();
|
||||
for deco in decos {
|
||||
deco.apply(ctx, Rc::make_mut(&mut frame));
|
||||
deco.apply(ctx, &mut frame);
|
||||
}
|
||||
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.
|
||||
struct LineStack<'a> {
|
||||
line_spacing: Length,
|
||||
leading: Length,
|
||||
full: Size,
|
||||
regions: Regions,
|
||||
size: Size,
|
||||
lines: Vec<LineLayout<'a>>,
|
||||
finished: Vec<Constrained<Rc<Frame>>>,
|
||||
constraints: Constraints,
|
||||
cts: Constraints,
|
||||
overflowing: bool,
|
||||
}
|
||||
|
||||
impl<'a> LineStack<'a> {
|
||||
/// Create an empty line stack.
|
||||
fn new(line_spacing: Length, regions: Regions) -> Self {
|
||||
fn new(leading: Length, regions: Regions) -> Self {
|
||||
Self {
|
||||
line_spacing,
|
||||
leading,
|
||||
full: regions.current,
|
||||
constraints: Constraints::new(regions.expand),
|
||||
cts: Constraints::new(regions.expand),
|
||||
regions,
|
||||
size: Size::zero(),
|
||||
lines: vec![],
|
||||
@ -549,12 +549,12 @@ impl<'a> LineStack<'a> {
|
||||
|
||||
/// Push a new line into the stack.
|
||||
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.h += line.size.h;
|
||||
if !self.lines.is_empty() {
|
||||
self.size.h += self.line_spacing;
|
||||
self.size.h += self.leading;
|
||||
}
|
||||
|
||||
self.lines.push(line);
|
||||
@ -564,13 +564,13 @@ impl<'a> LineStack<'a> {
|
||||
fn finish_region(&mut self, ctx: &LayoutContext) {
|
||||
if self.regions.expand.x {
|
||||
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 {
|
||||
self.constraints.min.y = None;
|
||||
self.constraints.max.y = None;
|
||||
self.constraints.exact = self.full.to_spec().map(Some);
|
||||
self.cts.min.y = None;
|
||||
self.cts.max.y = None;
|
||||
self.cts.exact = self.full.to_spec().map(Some);
|
||||
}
|
||||
|
||||
let mut output = Frame::new(self.size, self.size.h);
|
||||
@ -586,14 +586,14 @@ impl<'a> LineStack<'a> {
|
||||
first = false;
|
||||
}
|
||||
|
||||
offset += frame.size.h + self.line_spacing;
|
||||
offset += frame.size.h + self.leading;
|
||||
output.merge_frame(pos, frame);
|
||||
}
|
||||
|
||||
self.finished.push(output.constrain(self.constraints));
|
||||
self.finished.push(output.constrain(self.cts));
|
||||
self.regions.next();
|
||||
self.full = self.regions.current;
|
||||
self.constraints = Constraints::new(self.regions.expand);
|
||||
self.cts = Constraints::new(self.regions.expand);
|
||||
self.size = Size::zero();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
use std::f64::consts::SQRT_2;
|
||||
|
||||
use super::*;
|
||||
use crate::util::RcExt;
|
||||
|
||||
/// Places its child into a sizable and fillable shape.
|
||||
#[derive(Debug)]
|
||||
@ -15,7 +16,7 @@ pub struct ShapeNode {
|
||||
/// How to fill the shape, if at all.
|
||||
pub fill: Option<Paint>,
|
||||
/// The child node to place into the shape, if any.
|
||||
pub child: Option<LayoutNode>,
|
||||
pub child: Option<BlockNode>,
|
||||
}
|
||||
|
||||
/// The type of a shape.
|
||||
@ -31,40 +32,15 @@ pub enum ShapeKind {
|
||||
Ellipse,
|
||||
}
|
||||
|
||||
impl Layout for ShapeNode {
|
||||
fn layout(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
regions: &Regions,
|
||||
) -> Vec<Constrained<Rc<Frame>>> {
|
||||
impl InlineLevel for ShapeNode {
|
||||
fn layout(&self, ctx: &mut LayoutContext, space: Length, base: Size) -> Frame {
|
||||
// Resolve width and height relative to the region's base.
|
||||
let width = self.width.map(|w| w.resolve(regions.base.w));
|
||||
let height = self.height.map(|h| h.resolve(regions.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
|
||||
};
|
||||
let width = self.width.map(|w| w.resolve(base.w));
|
||||
let height = self.height.map(|h| h.resolve(base.h));
|
||||
|
||||
// Layout.
|
||||
let mut frames = if let Some(child) = &self.child {
|
||||
let mut node: &dyn Layout = child;
|
||||
let mut frame = if let Some(child) = &self.child {
|
||||
let mut node: &dyn BlockLevel = child;
|
||||
|
||||
let padded;
|
||||
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.
|
||||
let mut pod = {
|
||||
let size = Size::new(
|
||||
width.unwrap_or(regions.current.w),
|
||||
height.unwrap_or(regions.current.h),
|
||||
);
|
||||
let size =
|
||||
Size::new(width.unwrap_or(space), height.unwrap_or(Length::inf()));
|
||||
|
||||
let base = Size::new(
|
||||
if width.is_some() { size.w } else { regions.base.w },
|
||||
if height.is_some() { size.h } else { regions.base.h },
|
||||
if width.is_some() { size.w } else { base.w },
|
||||
if height.is_some() { size.h } else { base.h },
|
||||
);
|
||||
|
||||
let expand = Spec::new(width.is_some(), height.is_some());
|
||||
@ -108,17 +82,15 @@ impl Layout for ShapeNode {
|
||||
|
||||
// Validate and set constraints.
|
||||
assert_eq!(frames.len(), 1);
|
||||
frames[0].constraints = constraints;
|
||||
frames
|
||||
Rc::take(frames.into_iter().next().unwrap().item)
|
||||
} else {
|
||||
// Resolve shape size.
|
||||
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.
|
||||
if let Some(fill) = self.fill {
|
||||
let frame = Rc::make_mut(&mut frames[0].item);
|
||||
let (pos, geometry) = match self.shape {
|
||||
ShapeKind::Square | ShapeKind::Rect => {
|
||||
(Point::zero(), Geometry::Rect(frame.size))
|
||||
@ -131,12 +103,12 @@ impl Layout for ShapeNode {
|
||||
frame.prepend(pos, Element::Geometry(geometry, fill));
|
||||
}
|
||||
|
||||
frames
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ShapeNode> for LayoutNode {
|
||||
fn from(shape: ShapeNode) -> Self {
|
||||
Self::new(shape)
|
||||
impl From<ShapeNode> for InlineNode {
|
||||
fn from(node: ShapeNode) -> Self {
|
||||
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>,
|
||||
}
|
||||
|
||||
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(
|
||||
&self,
|
||||
ctx: &mut LayoutContext,
|
||||
@ -22,37 +31,18 @@ impl Layout for StackNode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StackNode> for LayoutNode {
|
||||
fn from(stack: StackNode) -> Self {
|
||||
Self::new(stack)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 From<StackNode> for BlockNode {
|
||||
fn from(node: StackNode) -> Self {
|
||||
Self::new(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for StackChild {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}: ", self.align)?;
|
||||
self.node.fmt(f)
|
||||
match self {
|
||||
Self::Spacing(v) => write!(f, "Spacing({:?})", v),
|
||||
Self::Any(node, _) => node.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,12 +96,17 @@ impl<'a> StackLayouter<'a> {
|
||||
/// Layout all children.
|
||||
fn layout(mut self, ctx: &mut LayoutContext) -> Vec<Constrained<Rc<Frame>>> {
|
||||
for child in &self.stack.children {
|
||||
let frames = child.node.layout(ctx, &self.regions);
|
||||
let len = frames.len();
|
||||
for (i, frame) in frames.into_iter().enumerate() {
|
||||
self.push_frame(frame.item, child.align);
|
||||
if i + 1 < len {
|
||||
self.finish_region();
|
||||
match *child {
|
||||
StackChild::Spacing(amount) => self.space(amount),
|
||||
StackChild::Any(ref node, align) => {
|
||||
let frames = node.layout(ctx, &self.regions);
|
||||
let len = frames.len();
|
||||
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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
fn push_frame(&mut self, frame: Rc<Frame>, align: Align) {
|
||||
// Grow our size.
|
||||
|
@ -3,7 +3,7 @@ use std::ops::Range;
|
||||
|
||||
use rustybuzz::UnicodeBuffer;
|
||||
|
||||
use super::{Element, Frame, Glyph, LayoutContext, Text};
|
||||
use super::*;
|
||||
use crate::font::{Face, FaceId, FontVariant};
|
||||
use crate::geom::{Dir, Em, Length, Point, Size};
|
||||
use crate::style::TextStyle;
|
||||
@ -13,8 +13,8 @@ use crate::util::SliceExt;
|
||||
pub fn shape<'a>(
|
||||
ctx: &mut LayoutContext,
|
||||
text: &'a str,
|
||||
dir: Dir,
|
||||
style: &'a TextStyle,
|
||||
dir: Dir,
|
||||
) -> ShapedText<'a> {
|
||||
let mut glyphs = vec![];
|
||||
if !text.is_empty() {
|
||||
@ -23,16 +23,15 @@ pub fn shape<'a>(
|
||||
&mut glyphs,
|
||||
0,
|
||||
text,
|
||||
dir,
|
||||
style.size,
|
||||
style.variant(),
|
||||
style.families(),
|
||||
None,
|
||||
dir,
|
||||
);
|
||||
}
|
||||
|
||||
let (size, baseline) = measure(ctx, &glyphs, style);
|
||||
|
||||
ShapedText {
|
||||
text,
|
||||
dir,
|
||||
@ -134,7 +133,7 @@ impl<'a> ShapedText<'a> {
|
||||
glyphs: Cow::Borrowed(glyphs),
|
||||
}
|
||||
} 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>,
|
||||
base: usize,
|
||||
text: &str,
|
||||
dir: Dir,
|
||||
size: Length,
|
||||
variant: FontVariant,
|
||||
mut families: impl Iterator<Item = &'a str> + Clone,
|
||||
mut first_face: Option<FaceId>,
|
||||
dir: Dir,
|
||||
) {
|
||||
// Select the font family.
|
||||
let (face_id, fallback) = loop {
|
||||
@ -316,11 +315,11 @@ fn shape_segment<'a>(
|
||||
glyphs,
|
||||
base + range.start,
|
||||
&text[range],
|
||||
dir,
|
||||
size,
|
||||
variant,
|
||||
families.clone(),
|
||||
first_face,
|
||||
dir,
|
||||
);
|
||||
|
||||
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
|
||||
//! [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
|
||||
//! [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
|
||||
//! self-contained and order-independent and thus much better suited for
|
||||
//! layouting than the raw markup.
|
||||
@ -23,7 +23,6 @@
|
||||
//! [markup]: syntax::Markup
|
||||
//! [evaluate]: eval::eval
|
||||
//! [module]: eval::Module
|
||||
//! [instantiated]: eval::Template::to_tree
|
||||
//! [layout tree]: layout::LayoutTree
|
||||
//! [layouted]: layout::layout
|
||||
//! [PDF]: export::pdf
|
||||
@ -53,7 +52,7 @@ use crate::font::FontStore;
|
||||
use crate::image::ImageStore;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use crate::layout::{EvictionPolicy, LayoutCache};
|
||||
use crate::layout::{Frame, LayoutTree};
|
||||
use crate::layout::{Frame, PageNode};
|
||||
use crate::loading::Loader;
|
||||
use crate::source::{SourceId, SourceStore};
|
||||
use crate::style::Style;
|
||||
@ -110,10 +109,10 @@ impl Context {
|
||||
eval::eval(self, id, &ast)
|
||||
}
|
||||
|
||||
/// Execute a source file and produce the resulting layout tree.
|
||||
pub fn execute(&mut self, id: SourceId) -> TypResult<LayoutTree> {
|
||||
/// Execute a source file and produce the resulting page nodes.
|
||||
pub fn execute(&mut self, id: SourceId) -> TypResult<Vec<PageNode>> {
|
||||
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.
|
||||
|
@ -220,16 +220,16 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
for child in &list {
|
||||
match child {
|
||||
Child::Spacing(v) => {
|
||||
children.push(StackChild::spacing(*v, dir.axis()));
|
||||
children.push(StackChild::Spacing(*v));
|
||||
delayed = None;
|
||||
}
|
||||
Child::Any(template) => {
|
||||
if let Some(v) = delayed {
|
||||
children.push(StackChild::spacing(v, dir.axis()));
|
||||
children.push(StackChild::Spacing(v));
|
||||
}
|
||||
|
||||
let node = template.to_stack(style);
|
||||
children.push(StackChild::new(node, style.aligns.block));
|
||||
let node = template.to_stack(style).into();
|
||||
children.push(StackChild::Any(node, style.aligns.block));
|
||||
delayed = spacing;
|
||||
}
|
||||
}
|
||||
|
@ -110,18 +110,18 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
|
||||
/// `par`: Configure paragraphs.
|
||||
pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
|
||||
let par_spacing = args.named("spacing")?;
|
||||
let line_spacing = args.named("leading")?;
|
||||
let spacing = args.named("spacing")?;
|
||||
let leading = args.named("leading")?;
|
||||
|
||||
ctx.template.modify(move |style| {
|
||||
let par = style.par_mut();
|
||||
|
||||
if let Some(par_spacing) = par_spacing {
|
||||
par.par_spacing = par_spacing;
|
||||
if let Some(spacing) = spacing {
|
||||
par.spacing = spacing;
|
||||
}
|
||||
|
||||
if let Some(line_spacing) = line_spacing {
|
||||
par.line_spacing = line_spacing;
|
||||
if let Some(leading) = leading {
|
||||
par.leading = leading;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -43,13 +43,13 @@ impl Style {
|
||||
}
|
||||
|
||||
/// The resolved line spacing.
|
||||
pub fn line_spacing(&self) -> Length {
|
||||
self.par.line_spacing.resolve(self.text.size)
|
||||
pub fn leading(&self) -> Length {
|
||||
self.par.leading.resolve(self.text.size)
|
||||
}
|
||||
|
||||
/// The resolved paragraph spacing.
|
||||
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)]
|
||||
pub struct ParStyle {
|
||||
/// 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).
|
||||
pub line_spacing: Linear,
|
||||
pub leading: Linear,
|
||||
}
|
||||
|
||||
impl Default for ParStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
par_spacing: Relative::new(1.2).into(),
|
||||
line_spacing: Relative::new(0.65).into(),
|
||||
spacing: Relative::new(1.2).into(),
|
||||
leading: Relative::new(0.65).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::RcExt;
|
||||
|
||||
/// An economical string with inline storage and clone-on-write semantics.
|
||||
#[derive(Clone)]
|
||||
pub struct EcoString(Repr);
|
||||
@ -293,10 +295,7 @@ impl From<EcoString> for String {
|
||||
fn from(s: EcoString) -> Self {
|
||||
match s.0 {
|
||||
Repr::Small { .. } => s.as_str().to_owned(),
|
||||
Repr::Large(rc) => match Rc::try_unwrap(rc) {
|
||||
Ok(string) => string,
|
||||
Err(rc) => (*rc).clone(),
|
||||
},
|
||||
Repr::Large(rc) => Rc::take(rc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ use std::cell::RefMut;
|
||||
use std::cmp::Ordering;
|
||||
use std::ops::Range;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Additional methods for booleans.
|
||||
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.
|
||||
pub trait SliceExt<T> {
|
||||
/// Split a slice into consecutive groups with the same key.
|
||||
|
@ -17,7 +17,7 @@ use typst::geom::{
|
||||
};
|
||||
use typst::image::Image;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use typst::layout::LayoutTree;
|
||||
use typst::layout::PageNode;
|
||||
use typst::layout::{layout, Element, Frame, Geometry, Text};
|
||||
use typst::loading::FsLoader;
|
||||
use typst::parse::Scanner;
|
||||
@ -282,7 +282,7 @@ fn test_part(
|
||||
fn test_incremental(
|
||||
ctx: &mut Context,
|
||||
i: usize,
|
||||
tree: &LayoutTree,
|
||||
tree: &[PageNode],
|
||||
frames: &[Rc<Frame>],
|
||||
) -> bool {
|
||||
let mut ok = true;
|
||||
|
Loading…
x
Reference in New Issue
Block a user