mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Refactor model into tree 🛒
This commit is contained in:
parent
cbbc46215f
commit
266d457292
@ -15,7 +15,7 @@ use fontdock::FaceId;
|
|||||||
use ttf_parser::{name_id, GlyphId};
|
use ttf_parser::{name_id, GlyphId};
|
||||||
|
|
||||||
use crate::SharedFontLoader;
|
use crate::SharedFontLoader;
|
||||||
use crate::layout::{MultiLayout, Layout};
|
use crate::layout::{MultiLayout, BoxLayout};
|
||||||
use crate::layout::elements::LayoutElement;
|
use crate::layout::elements::LayoutElement;
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
|
|
||||||
@ -117,8 +117,8 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
let rect = Rect::new(
|
let rect = Rect::new(
|
||||||
0.0,
|
0.0,
|
||||||
0.0,
|
0.0,
|
||||||
Length::raw(page.dimensions.x).as_pt() as f32,
|
Length::raw(page.size.x).as_pt() as f32,
|
||||||
Length::raw(page.dimensions.y).as_pt() as f32,
|
Length::raw(page.size.y).as_pt() as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.writer.write_obj(
|
self.writer.write_obj(
|
||||||
@ -141,7 +141,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write the content of a page.
|
/// Write the content of a page.
|
||||||
fn write_page(&mut self, id: u32, page: &Layout) -> io::Result<()> {
|
fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> {
|
||||||
// Moves and face switches are always cached and only flushed once
|
// Moves and face switches are always cached and only flushed once
|
||||||
// needed.
|
// needed.
|
||||||
let mut text = Text::new();
|
let mut text = Text::new();
|
||||||
@ -161,7 +161,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let x = Length::raw(pos.x).as_pt();
|
let x = Length::raw(pos.x).as_pt();
|
||||||
let y = Length::raw(page.dimensions.y - pos.y - size).as_pt();
|
let y = Length::raw(page.size.y - pos.y - size).as_pt();
|
||||||
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
|
text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32);
|
||||||
text.tj(shaped.encode_glyphs());
|
text.tj(shaped.encode_glyphs());
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ pub mod prelude {
|
|||||||
pub use crate::layout::Command::{self, *};
|
pub use crate::layout::Command::{self, *};
|
||||||
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
pub use crate::syntax::expr::*;
|
pub use crate::syntax::expr::*;
|
||||||
pub use crate::syntax::model::SyntaxModel;
|
pub use crate::syntax::tree::SyntaxTree;
|
||||||
pub use crate::syntax::span::{Span, Spanned};
|
pub use crate::syntax::span::{Span, Spanned};
|
||||||
pub use crate::syntax::value::*;
|
pub use crate::syntax::value::*;
|
||||||
pub use super::OptionExt;
|
pub use super::OptionExt;
|
||||||
@ -132,11 +132,11 @@ macro_rules! function {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => {
|
(@layout($name:ident) layout($this:ident, $ctx:ident, $feedback:ident) $code:block) => {
|
||||||
impl $crate::syntax::model::Model for $name {
|
impl $crate::layout::Layout for $name {
|
||||||
fn layout<'a, 'b, 't>(
|
fn layout<'a, 'b, 't>(
|
||||||
#[allow(unused)] &'a $this,
|
#[allow(unused)] &'a $this,
|
||||||
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>,
|
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>,
|
||||||
) -> $crate::layout::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>>
|
) -> $crate::DynFuture<'t, $crate::Pass<$crate::layout::Commands<'a>>>
|
||||||
where
|
where
|
||||||
'a: 't,
|
'a: 't,
|
||||||
'b: 't,
|
'b: 't,
|
||||||
|
@ -35,8 +35,7 @@ impl Default for LayoutElements {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layouting action, which is the basic building block layouts are composed
|
/// A layout element, which is the basic building block layouts are composed of.
|
||||||
/// of.
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum LayoutElement {
|
pub enum LayoutElement {
|
||||||
/// Shaped text.
|
/// Shaped text.
|
||||||
|
@ -45,7 +45,7 @@ pub struct LineContext {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct LineRun {
|
struct LineRun {
|
||||||
/// The so-far accumulated layouts in the line.
|
/// The so-far accumulated layouts in the line.
|
||||||
layouts: Vec<(f64, Layout)>,
|
layouts: Vec<(f64, BoxLayout)>,
|
||||||
/// The width and maximal height of the line.
|
/// The width and maximal height of the line.
|
||||||
size: Size,
|
size: Size,
|
||||||
/// The alignment of all layouts in the line.
|
/// The alignment of all layouts in the line.
|
||||||
@ -77,7 +77,7 @@ impl LineLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a layout to the run.
|
/// Add a layout to the run.
|
||||||
pub fn add(&mut self, layout: Layout) {
|
pub fn add(&mut self, layout: BoxLayout) {
|
||||||
let axes = self.ctx.axes;
|
let axes = self.ctx.axes;
|
||||||
|
|
||||||
if let Some(align) = self.run.align {
|
if let Some(align) = self.run.align {
|
||||||
@ -116,7 +116,7 @@ impl LineLayouter {
|
|||||||
self.add_primary_spacing(spacing, SpacingKind::Hard);
|
self.add_primary_spacing(spacing, SpacingKind::Hard);
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = layout.dimensions.generalized(axes);
|
let size = layout.size.generalized(axes);
|
||||||
|
|
||||||
if !self.usable().fits(size) {
|
if !self.usable().fits(size) {
|
||||||
if !self.line_is_empty() {
|
if !self.line_is_empty() {
|
||||||
@ -125,7 +125,7 @@ impl LineLayouter {
|
|||||||
|
|
||||||
// TODO: Issue warning about overflow if there is overflow.
|
// TODO: Issue warning about overflow if there is overflow.
|
||||||
if !self.usable().fits(size) {
|
if !self.usable().fits(size) {
|
||||||
self.stack.skip_to_fitting_space(layout.dimensions);
|
self.stack.skip_to_fitting_space(layout.size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +222,7 @@ impl LineLayouter {
|
|||||||
/// a function how much space it has to layout itself.
|
/// a function how much space it has to layout itself.
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
let mut spaces = self.stack.remaining();
|
let mut spaces = self.stack.remaining();
|
||||||
*spaces[0].dimensions.secondary_mut(self.ctx.axes)
|
*spaces[0].size.secondary_mut(self.ctx.axes)
|
||||||
-= self.run.size.y;
|
-= self.run.size.y;
|
||||||
spaces
|
spaces
|
||||||
}
|
}
|
||||||
@ -256,15 +256,15 @@ impl LineLayouter {
|
|||||||
true => offset,
|
true => offset,
|
||||||
false => self.run.size.x
|
false => self.run.size.x
|
||||||
- offset
|
- offset
|
||||||
- layout.dimensions.primary(self.ctx.axes),
|
- layout.size.primary(self.ctx.axes),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pos = Size::with_x(x);
|
let pos = Size::with_x(x);
|
||||||
elements.extend_offset(pos, layout.elements);
|
elements.extend_offset(pos, layout.elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stack.add(Layout {
|
self.stack.add(BoxLayout {
|
||||||
dimensions: self.run.size.specialized(self.ctx.axes),
|
size: self.run.size.specialized(self.ctx.axes),
|
||||||
align: self.run.align
|
align: self.run.align
|
||||||
.unwrap_or(LayoutAlign::new(Start, Start)),
|
.unwrap_or(LayoutAlign::new(Start, Start)),
|
||||||
elements
|
elements
|
||||||
|
@ -1,57 +1,100 @@
|
|||||||
//! Layouting types and engines.
|
//! Layouting types and engines.
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::Pass;
|
||||||
|
use crate::font::SharedFontLoader;
|
||||||
use crate::geom::{Size, Margins};
|
use crate::geom::{Size, Margins};
|
||||||
|
use crate::style::{LayoutStyle, TextStyle, PageStyle};
|
||||||
|
use crate::syntax::tree::SyntaxTree;
|
||||||
|
|
||||||
use elements::LayoutElements;
|
use elements::LayoutElements;
|
||||||
|
use tree::TreeLayouter;
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
|
pub mod elements;
|
||||||
pub mod line;
|
pub mod line;
|
||||||
|
pub mod primitive;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod elements;
|
pub mod tree;
|
||||||
pub_use_mod!(model);
|
|
||||||
|
pub use primitive::*;
|
||||||
|
|
||||||
/// Basic types used across the layouting engine.
|
/// Basic types used across the layouting engine.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use super::{
|
pub use super::layout;
|
||||||
layout, LayoutContext, LayoutSpace, Command, Commands,
|
pub use super::primitive::*;
|
||||||
LayoutAxes, LayoutAlign, LayoutExpansion,
|
pub use Dir::*;
|
||||||
};
|
pub use GenAxis::*;
|
||||||
pub use super::Dir::{self, *};
|
pub use SpecAxis::*;
|
||||||
pub use super::GenAxis::{self, *};
|
pub use GenAlign::*;
|
||||||
pub use super::SpecAxis::{self, *};
|
pub use SpecAlign::*;
|
||||||
pub use super::GenAlign::{self, *};
|
|
||||||
pub use super::SpecAlign::{self, *};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of layouts.
|
/// A collection of layouts.
|
||||||
pub type MultiLayout = Vec<Layout>;
|
pub type MultiLayout = Vec<BoxLayout>;
|
||||||
|
|
||||||
/// A finished box with content at fixed positions.
|
/// A finished box with content at fixed positions.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Layout {
|
pub struct BoxLayout {
|
||||||
/// The size of the box.
|
/// The size of the box.
|
||||||
pub dimensions: Size,
|
pub size: Size,
|
||||||
/// How to align this layout in a parent container.
|
/// How to align this layout in a parent container.
|
||||||
pub align: LayoutAlign,
|
pub align: LayoutAlign,
|
||||||
/// The actions composing this layout.
|
/// The elements composing this layout.
|
||||||
pub elements: LayoutElements,
|
pub elements: LayoutElements,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A vector of layout spaces, that is stack allocated as long as it only
|
/// Layouting of elements.
|
||||||
/// contains at most 2 spaces.
|
#[async_trait(?Send)]
|
||||||
|
pub trait Layout {
|
||||||
|
/// Layout self into a sequence of layouting commands.
|
||||||
|
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout a syntax tree into a list of boxes.
|
||||||
|
pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_>) -> Pass<MultiLayout> {
|
||||||
|
let mut layouter = TreeLayouter::new(ctx);
|
||||||
|
layouter.layout_tree(tree).await;
|
||||||
|
layouter.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The context for layouting.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LayoutContext<'a> {
|
||||||
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
|
/// using [`layout_text`].
|
||||||
|
pub loader: &'a SharedFontLoader,
|
||||||
|
/// The style for pages and text.
|
||||||
|
pub style: &'a LayoutStyle,
|
||||||
|
/// The base unpadded size of this container (for relative sizing).
|
||||||
|
pub base: Size,
|
||||||
|
/// The spaces to layout in.
|
||||||
|
pub spaces: LayoutSpaces,
|
||||||
|
/// Whether to have repeated spaces or to use only the first and only once.
|
||||||
|
pub repeat: bool,
|
||||||
|
/// The initial axes along which content is laid out.
|
||||||
|
pub axes: LayoutAxes,
|
||||||
|
/// The alignment of the finished layout.
|
||||||
|
pub align: LayoutAlign,
|
||||||
|
/// Whether the layout that is to be created will be nested in a parent
|
||||||
|
/// container.
|
||||||
|
pub nested: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A collection of layout spaces.
|
||||||
pub type LayoutSpaces = Vec<LayoutSpace>;
|
pub type LayoutSpaces = Vec<LayoutSpace>;
|
||||||
|
|
||||||
/// The space into which content is laid out.
|
/// The space into which content is laid out.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct LayoutSpace {
|
pub struct LayoutSpace {
|
||||||
/// The maximum size of the box to layout in.
|
/// The maximum size of the box to layout in.
|
||||||
pub dimensions: Size,
|
pub size: Size,
|
||||||
/// Padding that should be respected on each side.
|
/// Padding that should be respected on each side.
|
||||||
pub padding: Margins,
|
pub padding: Margins,
|
||||||
/// Whether to expand the dimensions of the resulting layout to the full
|
/// Whether to expand the size of the resulting layout to the full size of
|
||||||
/// dimensions of this space or to shrink them to fit the content.
|
/// this space or to shrink them to fit the content.
|
||||||
pub expansion: LayoutExpansion,
|
pub expansion: LayoutExpansion,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,363 +105,63 @@ impl LayoutSpace {
|
|||||||
Size::new(self.padding.left, self.padding.top)
|
Size::new(self.padding.left, self.padding.top)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The actually usable area (dimensions minus padding).
|
/// The actually usable area (size minus padding).
|
||||||
pub fn usable(&self) -> Size {
|
pub fn usable(&self) -> Size {
|
||||||
self.dimensions.unpadded(self.padding)
|
self.size.unpadded(self.padding)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A layout space without padding and dimensions reduced by the padding.
|
/// A layout space without padding and size reduced by the padding.
|
||||||
pub fn usable_space(&self) -> LayoutSpace {
|
pub fn usable_space(&self) -> LayoutSpace {
|
||||||
LayoutSpace {
|
LayoutSpace {
|
||||||
dimensions: self.usable(),
|
size: self.usable(),
|
||||||
padding: Margins::ZERO,
|
padding: Margins::ZERO,
|
||||||
expansion: LayoutExpansion::new(false, false),
|
expansion: LayoutExpansion::new(false, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies along which axes content is laid out.
|
/// A sequence of layouting commands.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
pub type Commands<'a> = Vec<Command<'a>>;
|
||||||
pub struct LayoutAxes {
|
|
||||||
/// The primary layouting direction.
|
|
||||||
pub primary: Dir,
|
|
||||||
/// The secondary layouting direction.
|
|
||||||
pub secondary: Dir,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutAxes {
|
/// Commands issued to the layouting engine by trees.
|
||||||
/// Create a new instance from the two values.
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Command<'a> {
|
||||||
|
/// Layout the given tree in the current context (i.e. not nested). The
|
||||||
|
/// content of the tree is not laid out into a separate box and then added,
|
||||||
|
/// but simply laid out flat in the active layouting process.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// This has the effect that the content fits nicely into the active line
|
||||||
/// This function panics if the axes are aligned, that is, they are
|
/// layouting, enabling functions to e.g. change the style of some piece of
|
||||||
/// on the same axis.
|
/// text while keeping it integrated in the current paragraph.
|
||||||
pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
|
LayoutSyntaxTree(&'a SyntaxTree),
|
||||||
if primary.axis() == secondary.axis() {
|
|
||||||
panic!("invalid aligned axes {} and {}", primary, secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
LayoutAxes { primary, secondary }
|
/// Add a already computed layout.
|
||||||
}
|
Add(BoxLayout),
|
||||||
|
/// Add multiple layouts, one after another. This is equivalent to multiple
|
||||||
|
/// [Add](Command::Add) commands.
|
||||||
|
AddMultiple(MultiLayout),
|
||||||
|
|
||||||
/// Return the direction of the specified generic axis.
|
/// Add spacing of given [kind](super::SpacingKind) along the primary or
|
||||||
pub fn get(self, axis: GenAxis) -> Dir {
|
/// secondary axis. The spacing kind defines how the spacing interacts with
|
||||||
match axis {
|
/// surrounding spacing.
|
||||||
Primary => self.primary,
|
AddSpacing(f64, SpacingKind, GenAxis),
|
||||||
Secondary => self.secondary,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrow the direction of the specified generic axis mutably.
|
/// Start a new line.
|
||||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir {
|
BreakLine,
|
||||||
match axis {
|
/// Start a new paragraph.
|
||||||
Primary => &mut self.primary,
|
BreakParagraph,
|
||||||
Secondary => &mut self.secondary,
|
/// Start a new page, which will exist in the finished layout even if it
|
||||||
}
|
/// stays empty (since the page break is a _hard_ space break).
|
||||||
}
|
BreakPage,
|
||||||
}
|
|
||||||
|
/// Update the text style.
|
||||||
/// Directions along which content is laid out.
|
SetTextStyle(TextStyle),
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
/// Update the page style.
|
||||||
pub enum Dir {
|
SetPageStyle(PageStyle),
|
||||||
LTT,
|
|
||||||
RTL,
|
/// Update the alignment for future boxes added to this layouting process.
|
||||||
TTB,
|
SetAlignment(LayoutAlign),
|
||||||
BTT,
|
/// Update the layouting axes along which future boxes will be laid
|
||||||
}
|
/// out. This finishes the current line.
|
||||||
|
SetAxes(LayoutAxes),
|
||||||
impl Dir {
|
|
||||||
/// The specific axis this direction belongs to.
|
|
||||||
pub fn axis(self) -> SpecAxis {
|
|
||||||
match self {
|
|
||||||
LTT | RTL => Horizontal,
|
|
||||||
TTB | BTT => Vertical,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether this axis points into the positive coordinate direction.
|
|
||||||
///
|
|
||||||
/// The positive axes are left-to-right and top-to-bottom.
|
|
||||||
pub fn is_positive(self) -> bool {
|
|
||||||
match self {
|
|
||||||
LTT | TTB => true,
|
|
||||||
RTL | BTT => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The factor for this direction.
|
|
||||||
///
|
|
||||||
/// - `1` if the direction is positive.
|
|
||||||
/// - `-1` if the direction is negative.
|
|
||||||
pub fn factor(self) -> f64 {
|
|
||||||
if self.is_positive() { 1.0 } else { -1.0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The inverse axis.
|
|
||||||
pub fn inv(self) -> Dir {
|
|
||||||
match self {
|
|
||||||
LTT => RTL,
|
|
||||||
RTL => LTT,
|
|
||||||
TTB => BTT,
|
|
||||||
BTT => TTB,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Dir {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad(match self {
|
|
||||||
LTT => "ltr",
|
|
||||||
RTL => "rtl",
|
|
||||||
TTB => "ttb",
|
|
||||||
BTT => "btt",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The two generic layouting axes.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum GenAxis {
|
|
||||||
/// The primary axis along which words are laid out.
|
|
||||||
Primary,
|
|
||||||
/// The secondary axis along which lines and paragraphs are laid out.
|
|
||||||
Secondary,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GenAxis {
|
|
||||||
/// The specific version of this axis in the given system of axes.
|
|
||||||
pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis {
|
|
||||||
axes.get(self).axis()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for GenAxis {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad(match self {
|
|
||||||
Primary => "primary",
|
|
||||||
Secondary => "secondary",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The two specific layouting axes.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum SpecAxis {
|
|
||||||
/// The horizontal layouting axis.
|
|
||||||
Horizontal,
|
|
||||||
/// The vertical layouting axis.
|
|
||||||
Vertical,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecAxis {
|
|
||||||
/// The generic version of this axis in the given system of axes.
|
|
||||||
pub fn to_generic(self, axes: LayoutAxes) -> GenAxis {
|
|
||||||
if self == axes.primary.axis() { Primary } else { Secondary }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SpecAxis {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad(match self {
|
|
||||||
Horizontal => "horizontal",
|
|
||||||
Vertical => "vertical",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specifies where to align a layout in a parent container.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct LayoutAlign {
|
|
||||||
/// The alignment along the primary axis.
|
|
||||||
pub primary: GenAlign,
|
|
||||||
/// The alignment along the secondary axis.
|
|
||||||
pub secondary: GenAlign,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutAlign {
|
|
||||||
/// Create a new instance from the two values.
|
|
||||||
pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign {
|
|
||||||
LayoutAlign { primary, secondary }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the alignment of the specified generic axis.
|
|
||||||
pub fn get(self, axis: GenAxis) -> GenAlign {
|
|
||||||
match axis {
|
|
||||||
Primary => self.primary,
|
|
||||||
Secondary => self.secondary,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrow the alignment of the specified generic axis mutably.
|
|
||||||
pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
|
|
||||||
match axis {
|
|
||||||
Primary => &mut self.primary,
|
|
||||||
Secondary => &mut self.secondary,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where to align content along a generic context.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
||||||
pub enum GenAlign {
|
|
||||||
Start,
|
|
||||||
Center,
|
|
||||||
End,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GenAlign {
|
|
||||||
/// The inverse alignment.
|
|
||||||
pub fn inv(self) -> GenAlign {
|
|
||||||
match self {
|
|
||||||
Start => End,
|
|
||||||
Center => Center,
|
|
||||||
End => Start,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for GenAlign {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad(match self {
|
|
||||||
Start => "start",
|
|
||||||
Center => "center",
|
|
||||||
End => "end",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where to align content in a specific context.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
||||||
pub enum SpecAlign {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Top,
|
|
||||||
Bottom,
|
|
||||||
Center,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpecAlign {
|
|
||||||
/// The specific axis this alignment refers to.
|
|
||||||
///
|
|
||||||
/// Returns `None` if this is center.
|
|
||||||
pub fn axis(self) -> Option<SpecAxis> {
|
|
||||||
match self {
|
|
||||||
Self::Left => Some(Horizontal),
|
|
||||||
Self::Right => Some(Horizontal),
|
|
||||||
Self::Top => Some(Vertical),
|
|
||||||
Self::Bottom => Some(Vertical),
|
|
||||||
Self::Center => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert this to a generic alignment.
|
|
||||||
pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
|
|
||||||
let get = |spec: SpecAxis, align: GenAlign| {
|
|
||||||
let axis = spec.to_generic(axes);
|
|
||||||
if axes.get(axis).is_positive() { align } else { align.inv() }
|
|
||||||
};
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::Left => get(Horizontal, Start),
|
|
||||||
Self::Right => get(Horizontal, End),
|
|
||||||
Self::Top => get(Vertical, Start),
|
|
||||||
Self::Bottom => get(Vertical, End),
|
|
||||||
Self::Center => GenAlign::Center,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for SpecAlign {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.pad(match self {
|
|
||||||
Self::Left => "left",
|
|
||||||
Self::Right => "right",
|
|
||||||
Self::Top => "top",
|
|
||||||
Self::Bottom => "bottom",
|
|
||||||
Self::Center => "center",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Specifies whether to expand a layout to the full size of the space it is
|
|
||||||
/// laid out in or to shrink it to fit the content.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub struct LayoutExpansion {
|
|
||||||
/// Whether to expand on the horizontal axis.
|
|
||||||
pub horizontal: bool,
|
|
||||||
/// Whether to expand on the vertical axis.
|
|
||||||
pub vertical: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutExpansion {
|
|
||||||
/// Create a new instance from the two values.
|
|
||||||
pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
|
|
||||||
LayoutExpansion { horizontal, vertical }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the expansion value for the given specific axis.
|
|
||||||
pub fn get(self, axis: SpecAxis) -> bool {
|
|
||||||
match axis {
|
|
||||||
Horizontal => self.horizontal,
|
|
||||||
Vertical => self.vertical,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrow the expansion value for the given specific axis mutably.
|
|
||||||
pub fn get_mut(&mut self, axis: SpecAxis) -> &mut bool {
|
|
||||||
match axis {
|
|
||||||
Horizontal => &mut self.horizontal,
|
|
||||||
Vertical => &mut self.vertical,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines how a given spacing interacts with (possibly existing) surrounding
|
|
||||||
/// spacing.
|
|
||||||
///
|
|
||||||
/// There are two options for interaction: Hard and soft spacing. Typically,
|
|
||||||
/// hard spacing is used when a fixed amount of space needs to be inserted no
|
|
||||||
/// matter what. In contrast, soft spacing can be used to insert a default
|
|
||||||
/// spacing between e.g. two words or paragraphs that can still be overridden by
|
|
||||||
/// a hard space.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum SpacingKind {
|
|
||||||
/// Hard spaces are always laid out and consume surrounding soft space.
|
|
||||||
Hard,
|
|
||||||
/// Soft spaces are not laid out if they are touching a hard space and
|
|
||||||
/// consume neighbouring soft spaces with higher levels.
|
|
||||||
Soft(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpacingKind {
|
|
||||||
/// The standard spacing kind used for paragraph spacing.
|
|
||||||
pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
|
|
||||||
|
|
||||||
/// The standard spacing kind used for line spacing.
|
|
||||||
pub const LINE: SpacingKind = SpacingKind::Soft(2);
|
|
||||||
|
|
||||||
/// The standard spacing kind used for word spacing.
|
|
||||||
pub const WORD: SpacingKind = SpacingKind::Soft(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The spacing kind of the most recently inserted item in a layouting process.
|
|
||||||
/// This is not about the last _spacing item_, but the last _item_, which is why
|
|
||||||
/// this can be `None`.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
enum LastSpacing {
|
|
||||||
/// The last item was hard spacing.
|
|
||||||
Hard,
|
|
||||||
/// The last item was soft spacing with the given width and level.
|
|
||||||
Soft(f64, u32),
|
|
||||||
/// The last item was not spacing.
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LastSpacing {
|
|
||||||
/// The width of the soft space if this is a soft space or zero otherwise.
|
|
||||||
fn soft_or_zero(self) -> f64 {
|
|
||||||
match self {
|
|
||||||
LastSpacing::Soft(space, _) => space,
|
|
||||||
_ => 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,313 +0,0 @@
|
|||||||
//! The model layouter layouts models (i.e.
|
|
||||||
//! [syntax models](crate::syntax::SyntaxModel) and [functions](crate::func))
|
|
||||||
//! by executing commands issued by the models.
|
|
||||||
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use crate::{Pass, Feedback};
|
|
||||||
use crate::SharedFontLoader;
|
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
|
||||||
use crate::geom::Size;
|
|
||||||
use crate::syntax::decoration::Decoration;
|
|
||||||
use crate::syntax::model::{Model, SyntaxModel, Node};
|
|
||||||
use crate::syntax::span::{Span, Spanned};
|
|
||||||
use super::line::{LineLayouter, LineContext};
|
|
||||||
use super::text::{layout_text, TextContext};
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
/// Performs the model layouting.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ModelLayouter<'a> {
|
|
||||||
ctx: LayoutContext<'a>,
|
|
||||||
layouter: LineLayouter,
|
|
||||||
style: LayoutStyle,
|
|
||||||
feedback: Feedback,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The context for layouting.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct LayoutContext<'a> {
|
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
|
||||||
/// using [`layout_text`].
|
|
||||||
pub loader: &'a SharedFontLoader,
|
|
||||||
/// The style for pages and text.
|
|
||||||
pub style: &'a LayoutStyle,
|
|
||||||
/// The base unpadded dimensions of this container (for relative sizing).
|
|
||||||
pub base: Size,
|
|
||||||
/// The spaces to layout in.
|
|
||||||
pub spaces: LayoutSpaces,
|
|
||||||
/// Whether to have repeated spaces or to use only the first and only once.
|
|
||||||
pub repeat: bool,
|
|
||||||
/// The initial axes along which content is laid out.
|
|
||||||
pub axes: LayoutAxes,
|
|
||||||
/// The alignment of the finished layout.
|
|
||||||
pub align: LayoutAlign,
|
|
||||||
/// Whether the layout that is to be created will be nested in a parent
|
|
||||||
/// container.
|
|
||||||
pub nested: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A sequence of layouting commands.
|
|
||||||
pub type Commands<'a> = Vec<Command<'a>>;
|
|
||||||
|
|
||||||
/// Commands issued to the layouting engine by models.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Command<'a> {
|
|
||||||
/// Layout the given model in the current context (i.e. not nested). The
|
|
||||||
/// content of the model is not laid out into a separate box and then added,
|
|
||||||
/// but simply laid out flat in the active layouting process.
|
|
||||||
///
|
|
||||||
/// This has the effect that the content fits nicely into the active line
|
|
||||||
/// layouting, enabling functions to e.g. change the style of some piece of
|
|
||||||
/// text while keeping it integrated in the current paragraph.
|
|
||||||
LayoutSyntaxModel(&'a SyntaxModel),
|
|
||||||
|
|
||||||
/// Add a already computed layout.
|
|
||||||
Add(Layout),
|
|
||||||
/// Add multiple layouts, one after another. This is equivalent to multiple
|
|
||||||
/// [Add](Command::Add) commands.
|
|
||||||
AddMultiple(MultiLayout),
|
|
||||||
|
|
||||||
/// Add spacing of given [kind](super::SpacingKind) along the primary or
|
|
||||||
/// secondary axis. The spacing kind defines how the spacing interacts with
|
|
||||||
/// surrounding spacing.
|
|
||||||
AddSpacing(f64, SpacingKind, GenAxis),
|
|
||||||
|
|
||||||
/// Start a new line.
|
|
||||||
BreakLine,
|
|
||||||
/// Start a new paragraph.
|
|
||||||
BreakParagraph,
|
|
||||||
/// Start a new page, which will exist in the finished layout even if it
|
|
||||||
/// stays empty (since the page break is a _hard_ space break).
|
|
||||||
BreakPage,
|
|
||||||
|
|
||||||
/// Update the text style.
|
|
||||||
SetTextStyle(TextStyle),
|
|
||||||
/// Update the page style.
|
|
||||||
SetPageStyle(PageStyle),
|
|
||||||
|
|
||||||
/// Update the alignment for future boxes added to this layouting process.
|
|
||||||
SetAlignment(LayoutAlign),
|
|
||||||
/// Update the layouting axes along which future boxes will be laid
|
|
||||||
/// out. This finishes the current line.
|
|
||||||
SetAxes(LayoutAxes),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout a syntax model into a list of boxes.
|
|
||||||
pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_>) -> Pass<MultiLayout> {
|
|
||||||
let mut layouter = ModelLayouter::new(ctx);
|
|
||||||
layouter.layout_syntax_model(model).await;
|
|
||||||
layouter.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dynamic future type which allows recursive invocation of async functions
|
|
||||||
/// when used as the return type. This is also how the async trait functions
|
|
||||||
/// work internally.
|
|
||||||
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
|
|
||||||
|
|
||||||
impl<'a> ModelLayouter<'a> {
|
|
||||||
/// Create a new model layouter.
|
|
||||||
pub fn new(ctx: LayoutContext<'a>) -> ModelLayouter<'a> {
|
|
||||||
ModelLayouter {
|
|
||||||
layouter: LineLayouter::new(LineContext {
|
|
||||||
spaces: ctx.spaces.clone(),
|
|
||||||
axes: ctx.axes,
|
|
||||||
align: ctx.align,
|
|
||||||
repeat: ctx.repeat,
|
|
||||||
line_spacing: ctx.style.text.line_spacing(),
|
|
||||||
}),
|
|
||||||
style: ctx.style.clone(),
|
|
||||||
ctx,
|
|
||||||
feedback: Feedback::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Flatly layout a model into this layouting process.
|
|
||||||
pub async fn layout<'r>(
|
|
||||||
&'r mut self,
|
|
||||||
model: Spanned<&'r dyn Model>
|
|
||||||
) {
|
|
||||||
// Execute the model's layout function which generates the commands.
|
|
||||||
let layouted = model.v.layout(LayoutContext {
|
|
||||||
style: &self.style,
|
|
||||||
spaces: self.layouter.remaining(),
|
|
||||||
nested: true,
|
|
||||||
.. self.ctx
|
|
||||||
}).await;
|
|
||||||
|
|
||||||
// Add the errors generated by the model to the error list.
|
|
||||||
self.feedback.extend_offset(layouted.feedback, model.span.start);
|
|
||||||
|
|
||||||
for command in layouted.output {
|
|
||||||
self.execute_command(command, model.span).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Layout a syntax model by directly processing the nodes instead of using
|
|
||||||
/// the command based architecture.
|
|
||||||
pub async fn layout_syntax_model<'r>(
|
|
||||||
&'r mut self,
|
|
||||||
model: &'r SyntaxModel
|
|
||||||
) {
|
|
||||||
use Node::*;
|
|
||||||
|
|
||||||
for Spanned { v: node, span } in &model.nodes {
|
|
||||||
let decorate = |this: &mut ModelLayouter, deco| {
|
|
||||||
this.feedback.decorations.push(Spanned::new(deco, *span));
|
|
||||||
};
|
|
||||||
|
|
||||||
match node {
|
|
||||||
Space => self.layout_space(),
|
|
||||||
Parbreak => self.layout_paragraph(),
|
|
||||||
Linebreak => self.layouter.finish_line(),
|
|
||||||
|
|
||||||
Text(text) => {
|
|
||||||
if self.style.text.italic {
|
|
||||||
decorate(self, Decoration::Italic);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.style.text.bolder {
|
|
||||||
decorate(self, Decoration::Bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.layout_text(text).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleItalic => {
|
|
||||||
self.style.text.italic = !self.style.text.italic;
|
|
||||||
decorate(self, Decoration::Italic);
|
|
||||||
}
|
|
||||||
|
|
||||||
ToggleBolder => {
|
|
||||||
self.style.text.bolder = !self.style.text.bolder;
|
|
||||||
decorate(self, Decoration::Bold);
|
|
||||||
}
|
|
||||||
|
|
||||||
Raw(lines) => {
|
|
||||||
// TODO: Make this more efficient.
|
|
||||||
let fallback = self.style.text.fallback.clone();
|
|
||||||
self.style.text.fallback.list_mut().insert(0, "monospace".to_string());
|
|
||||||
self.style.text.fallback.flatten();
|
|
||||||
|
|
||||||
// Layout the first line.
|
|
||||||
let mut iter = lines.iter();
|
|
||||||
if let Some(line) = iter.next() {
|
|
||||||
self.layout_text(line).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put a newline before each following line.
|
|
||||||
for line in iter {
|
|
||||||
self.layouter.finish_line();
|
|
||||||
self.layout_text(line).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.style.text.fallback = fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
Model(model) => {
|
|
||||||
self.layout(Spanned::new(model.as_ref(), *span)).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the finished list of boxes.
|
|
||||||
pub fn finish(self) -> Pass<MultiLayout> {
|
|
||||||
Pass::new(self.layouter.finish(), self.feedback)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute a command issued by a model. When the command is errorful, the
|
|
||||||
/// given span is stored with the error.
|
|
||||||
fn execute_command<'r>(
|
|
||||||
&'r mut self,
|
|
||||||
command: Command<'r>,
|
|
||||||
model_span: Span,
|
|
||||||
) -> DynFuture<'r, ()> { Box::pin(async move {
|
|
||||||
use Command::*;
|
|
||||||
|
|
||||||
match command {
|
|
||||||
LayoutSyntaxModel(model) => self.layout_syntax_model(model).await,
|
|
||||||
|
|
||||||
Add(layout) => self.layouter.add(layout),
|
|
||||||
AddMultiple(layouts) => self.layouter.add_multiple(layouts),
|
|
||||||
AddSpacing(space, kind, axis) => match axis {
|
|
||||||
Primary => self.layouter.add_primary_spacing(space, kind),
|
|
||||||
Secondary => self.layouter.add_secondary_spacing(space, kind),
|
|
||||||
}
|
|
||||||
|
|
||||||
BreakLine => self.layouter.finish_line(),
|
|
||||||
BreakParagraph => self.layout_paragraph(),
|
|
||||||
BreakPage => {
|
|
||||||
if self.ctx.nested {
|
|
||||||
error!(
|
|
||||||
@self.feedback, model_span,
|
|
||||||
"page break cannot be issued from nested context",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.layouter.finish_space(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetTextStyle(style) => {
|
|
||||||
self.layouter.set_line_spacing(style.line_spacing());
|
|
||||||
self.style.text = style;
|
|
||||||
}
|
|
||||||
SetPageStyle(style) => {
|
|
||||||
if self.ctx.nested {
|
|
||||||
error!(
|
|
||||||
@self.feedback, model_span,
|
|
||||||
"page style cannot be changed from nested context",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.style.page = style;
|
|
||||||
|
|
||||||
// The line layouter has no idea of page styles and thus we
|
|
||||||
// need to recompute the layouting space resulting of the
|
|
||||||
// new page style and update it within the layouter.
|
|
||||||
let margins = style.margins();
|
|
||||||
self.ctx.base = style.dimensions.unpadded(margins);
|
|
||||||
self.layouter.set_spaces(vec![
|
|
||||||
LayoutSpace {
|
|
||||||
dimensions: style.dimensions,
|
|
||||||
padding: margins,
|
|
||||||
expansion: LayoutExpansion::new(true, true),
|
|
||||||
}
|
|
||||||
], true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SetAlignment(align) => self.ctx.align = align,
|
|
||||||
SetAxes(axes) => {
|
|
||||||
self.layouter.set_axes(axes);
|
|
||||||
self.ctx.axes = axes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) }
|
|
||||||
|
|
||||||
/// Layout a continous piece of text and add it to the line layouter.
|
|
||||||
async fn layout_text(&mut self, text: &str) {
|
|
||||||
self.layouter.add(layout_text(text, TextContext {
|
|
||||||
loader: &self.ctx.loader,
|
|
||||||
style: &self.style.text,
|
|
||||||
axes: self.ctx.axes,
|
|
||||||
align: self.ctx.align,
|
|
||||||
}).await)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add the spacing for a syntactic space node.
|
|
||||||
fn layout_space(&mut self) {
|
|
||||||
self.layouter.add_primary_spacing(
|
|
||||||
self.style.text.word_spacing(),
|
|
||||||
SpacingKind::WORD,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Finish the paragraph and add paragraph spacing.
|
|
||||||
fn layout_paragraph(&mut self) {
|
|
||||||
self.layouter.add_secondary_spacing(
|
|
||||||
self.style.text.paragraph_spacing(),
|
|
||||||
SpacingKind::PARAGRAPH,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
350
src/layout/primitive.rs
Normal file
350
src/layout/primitive.rs
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
//! Layouting primitives.
|
||||||
|
|
||||||
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
use super::prelude::*;
|
||||||
|
|
||||||
|
/// Specifies along which axes content is laid out.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct LayoutAxes {
|
||||||
|
/// The primary layouting direction.
|
||||||
|
pub primary: Dir,
|
||||||
|
/// The secondary layouting direction.
|
||||||
|
pub secondary: Dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutAxes {
|
||||||
|
/// Create a new instance from the two values.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function panics if the axes are aligned, that is, they are
|
||||||
|
/// on the same axis.
|
||||||
|
pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes {
|
||||||
|
if primary.axis() == secondary.axis() {
|
||||||
|
panic!("invalid aligned axes {} and {}", primary, secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutAxes { primary, secondary }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the direction of the specified generic axis.
|
||||||
|
pub fn get(self, axis: GenAxis) -> Dir {
|
||||||
|
match axis {
|
||||||
|
Primary => self.primary,
|
||||||
|
Secondary => self.secondary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow the direction of the specified generic axis mutably.
|
||||||
|
pub fn get_mut(&mut self, axis: GenAxis) -> &mut Dir {
|
||||||
|
match axis {
|
||||||
|
Primary => &mut self.primary,
|
||||||
|
Secondary => &mut self.secondary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Directions along which content is laid out.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum Dir {
|
||||||
|
LTT,
|
||||||
|
RTL,
|
||||||
|
TTB,
|
||||||
|
BTT,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dir {
|
||||||
|
/// The specific axis this direction belongs to.
|
||||||
|
pub fn axis(self) -> SpecAxis {
|
||||||
|
match self {
|
||||||
|
LTT | RTL => Horizontal,
|
||||||
|
TTB | BTT => Vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether this axis points into the positive coordinate direction.
|
||||||
|
///
|
||||||
|
/// The positive axes are left-to-right and top-to-bottom.
|
||||||
|
pub fn is_positive(self) -> bool {
|
||||||
|
match self {
|
||||||
|
LTT | TTB => true,
|
||||||
|
RTL | BTT => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The factor for this direction.
|
||||||
|
///
|
||||||
|
/// - `1` if the direction is positive.
|
||||||
|
/// - `-1` if the direction is negative.
|
||||||
|
pub fn factor(self) -> f64 {
|
||||||
|
if self.is_positive() { 1.0 } else { -1.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The inverse axis.
|
||||||
|
pub fn inv(self) -> Dir {
|
||||||
|
match self {
|
||||||
|
LTT => RTL,
|
||||||
|
RTL => LTT,
|
||||||
|
TTB => BTT,
|
||||||
|
BTT => TTB,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Dir {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
|
LTT => "ltr",
|
||||||
|
RTL => "rtl",
|
||||||
|
TTB => "ttb",
|
||||||
|
BTT => "btt",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The two generic layouting axes.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum GenAxis {
|
||||||
|
/// The primary axis along which words are laid out.
|
||||||
|
Primary,
|
||||||
|
/// The secondary axis along which lines and paragraphs are laid out.
|
||||||
|
Secondary,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenAxis {
|
||||||
|
/// The specific version of this axis in the given system of axes.
|
||||||
|
pub fn to_specific(self, axes: LayoutAxes) -> SpecAxis {
|
||||||
|
axes.get(self).axis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for GenAxis {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
|
Primary => "primary",
|
||||||
|
Secondary => "secondary",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The two specific layouting axes.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum SpecAxis {
|
||||||
|
/// The horizontal layouting axis.
|
||||||
|
Horizontal,
|
||||||
|
/// The vertical layouting axis.
|
||||||
|
Vertical,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecAxis {
|
||||||
|
/// The generic version of this axis in the given system of axes.
|
||||||
|
pub fn to_generic(self, axes: LayoutAxes) -> GenAxis {
|
||||||
|
if self == axes.primary.axis() { Primary } else { Secondary }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SpecAxis {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
|
Horizontal => "horizontal",
|
||||||
|
Vertical => "vertical",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies where to align a layout in a parent container.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct LayoutAlign {
|
||||||
|
/// The alignment along the primary axis.
|
||||||
|
pub primary: GenAlign,
|
||||||
|
/// The alignment along the secondary axis.
|
||||||
|
pub secondary: GenAlign,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutAlign {
|
||||||
|
/// Create a new instance from the two values.
|
||||||
|
pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign {
|
||||||
|
LayoutAlign { primary, secondary }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the alignment of the specified generic axis.
|
||||||
|
pub fn get(self, axis: GenAxis) -> GenAlign {
|
||||||
|
match axis {
|
||||||
|
Primary => self.primary,
|
||||||
|
Secondary => self.secondary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow the alignment of the specified generic axis mutably.
|
||||||
|
pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
|
||||||
|
match axis {
|
||||||
|
Primary => &mut self.primary,
|
||||||
|
Secondary => &mut self.secondary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where to align content along a generic context.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum GenAlign {
|
||||||
|
Start,
|
||||||
|
Center,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GenAlign {
|
||||||
|
/// The inverse alignment.
|
||||||
|
pub fn inv(self) -> GenAlign {
|
||||||
|
match self {
|
||||||
|
Start => End,
|
||||||
|
Center => Center,
|
||||||
|
End => Start,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for GenAlign {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
|
Start => "start",
|
||||||
|
Center => "center",
|
||||||
|
End => "end",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where to align content in a specific context.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum SpecAlign {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Top,
|
||||||
|
Bottom,
|
||||||
|
Center,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpecAlign {
|
||||||
|
/// The specific axis this alignment refers to.
|
||||||
|
///
|
||||||
|
/// Returns `None` if this is center.
|
||||||
|
pub fn axis(self) -> Option<SpecAxis> {
|
||||||
|
match self {
|
||||||
|
Self::Left => Some(Horizontal),
|
||||||
|
Self::Right => Some(Horizontal),
|
||||||
|
Self::Top => Some(Vertical),
|
||||||
|
Self::Bottom => Some(Vertical),
|
||||||
|
Self::Center => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert this to a generic alignment.
|
||||||
|
pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
|
||||||
|
let get = |spec: SpecAxis, align: GenAlign| {
|
||||||
|
let axis = spec.to_generic(axes);
|
||||||
|
if axes.get(axis).is_positive() { align } else { align.inv() }
|
||||||
|
};
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Self::Left => get(Horizontal, Start),
|
||||||
|
Self::Right => get(Horizontal, End),
|
||||||
|
Self::Top => get(Vertical, Start),
|
||||||
|
Self::Bottom => get(Vertical, End),
|
||||||
|
Self::Center => GenAlign::Center,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for SpecAlign {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad(match self {
|
||||||
|
Self::Left => "left",
|
||||||
|
Self::Right => "right",
|
||||||
|
Self::Top => "top",
|
||||||
|
Self::Bottom => "bottom",
|
||||||
|
Self::Center => "center",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies whether to expand a layout to the full size of the space it is
|
||||||
|
/// laid out in or to shrink it to fit the content.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct LayoutExpansion {
|
||||||
|
/// Whether to expand on the horizontal axis.
|
||||||
|
pub horizontal: bool,
|
||||||
|
/// Whether to expand on the vertical axis.
|
||||||
|
pub vertical: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutExpansion {
|
||||||
|
/// Create a new instance from the two values.
|
||||||
|
pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
|
||||||
|
LayoutExpansion { horizontal, vertical }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the expansion value for the given specific axis.
|
||||||
|
pub fn get(self, axis: SpecAxis) -> bool {
|
||||||
|
match axis {
|
||||||
|
Horizontal => self.horizontal,
|
||||||
|
Vertical => self.vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow the expansion value for the given specific axis mutably.
|
||||||
|
pub fn get_mut(&mut self, axis: SpecAxis) -> &mut bool {
|
||||||
|
match axis {
|
||||||
|
Horizontal => &mut self.horizontal,
|
||||||
|
Vertical => &mut self.vertical,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines how a given spacing interacts with (possibly existing) surrounding
|
||||||
|
/// spacing.
|
||||||
|
///
|
||||||
|
/// There are two options for interaction: Hard and soft spacing. Typically,
|
||||||
|
/// hard spacing is used when a fixed amount of space needs to be inserted no
|
||||||
|
/// matter what. In contrast, soft spacing can be used to insert a default
|
||||||
|
/// spacing between e.g. two words or paragraphs that can still be overridden by
|
||||||
|
/// a hard space.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum SpacingKind {
|
||||||
|
/// Hard spaces are always laid out and consume surrounding soft space.
|
||||||
|
Hard,
|
||||||
|
/// Soft spaces are not laid out if they are touching a hard space and
|
||||||
|
/// consume neighbouring soft spaces with higher levels.
|
||||||
|
Soft(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpacingKind {
|
||||||
|
/// The standard spacing kind used for paragraph spacing.
|
||||||
|
pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1);
|
||||||
|
|
||||||
|
/// The standard spacing kind used for line spacing.
|
||||||
|
pub const LINE: SpacingKind = SpacingKind::Soft(2);
|
||||||
|
|
||||||
|
/// The standard spacing kind used for word spacing.
|
||||||
|
pub const WORD: SpacingKind = SpacingKind::Soft(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The spacing kind of the most recently inserted item in a layouting process.
|
||||||
|
/// This is not about the last _spacing item_, but the last _item_, which is why
|
||||||
|
/// this can be `None`.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
|
pub(crate) enum LastSpacing {
|
||||||
|
/// The last item was hard spacing.
|
||||||
|
Hard,
|
||||||
|
/// The last item was soft spacing with the given width and level.
|
||||||
|
Soft(f64, u32),
|
||||||
|
/// The last item was not spacing.
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LastSpacing {
|
||||||
|
/// The width of the soft space if this is a soft space or zero otherwise.
|
||||||
|
pub(crate) fn soft_or_zero(self) -> f64 {
|
||||||
|
match self {
|
||||||
|
LastSpacing::Soft(space, _) => space,
|
||||||
|
_ => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -59,12 +59,12 @@ struct Space {
|
|||||||
/// Whether to add the layout for this space even if it would be empty.
|
/// Whether to add the layout for this space even if it would be empty.
|
||||||
hard: bool,
|
hard: bool,
|
||||||
/// The so-far accumulated layouts.
|
/// The so-far accumulated layouts.
|
||||||
layouts: Vec<(LayoutAxes, Layout)>,
|
layouts: Vec<(LayoutAxes, BoxLayout)>,
|
||||||
/// The specialized size of this space.
|
/// The specialized size of this space.
|
||||||
size: Size,
|
size: Size,
|
||||||
/// The specialized remaining space.
|
/// The specialized remaining space.
|
||||||
usable: Size,
|
usable: Size,
|
||||||
/// The specialized extra-needed dimensions to affect the size at all.
|
/// The specialized extra-needed size to affect the size at all.
|
||||||
extra: Size,
|
extra: Size,
|
||||||
/// The rulers of a space dictate which alignments for new boxes are still
|
/// The rulers of a space dictate which alignments for new boxes are still
|
||||||
/// allowed and which require a new space to be started.
|
/// allowed and which require a new space to be started.
|
||||||
@ -85,7 +85,7 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a layout to the stack.
|
/// Add a layout to the stack.
|
||||||
pub fn add(&mut self, layout: Layout) {
|
pub fn add(&mut self, layout: BoxLayout) {
|
||||||
// If the alignment cannot be fitted in this space, finish it.
|
// If the alignment cannot be fitted in this space, finish it.
|
||||||
// TODO: Issue warning for non-fitting alignment in
|
// TODO: Issue warning for non-fitting alignment in
|
||||||
// non-repeating context.
|
// non-repeating context.
|
||||||
@ -101,12 +101,12 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Issue warning about overflow if there is overflow.
|
// TODO: Issue warning about overflow if there is overflow.
|
||||||
if !self.space.usable.fits(layout.dimensions) && self.ctx.repeat {
|
if !self.space.usable.fits(layout.size) && self.ctx.repeat {
|
||||||
self.skip_to_fitting_space(layout.dimensions);
|
self.skip_to_fitting_space(layout.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the usable space and size of the space.
|
// Change the usable space and size of the space.
|
||||||
self.update_metrics(layout.dimensions.generalized(self.ctx.axes));
|
self.update_metrics(layout.size.generalized(self.ctx.axes));
|
||||||
|
|
||||||
// Add the box to the vector and remember that spacings are allowed
|
// Add the box to the vector and remember that spacings are allowed
|
||||||
// again.
|
// again.
|
||||||
@ -130,11 +130,11 @@ impl StackLayouter {
|
|||||||
SpacingKind::Hard => {
|
SpacingKind::Hard => {
|
||||||
// Reduce the spacing such that it definitely fits.
|
// Reduce the spacing such that it definitely fits.
|
||||||
spacing = spacing.min(self.space.usable.secondary(self.ctx.axes));
|
spacing = spacing.min(self.space.usable.secondary(self.ctx.axes));
|
||||||
let dimensions = Size::with_y(spacing);
|
let size = Size::with_y(spacing);
|
||||||
|
|
||||||
self.update_metrics(dimensions);
|
self.update_metrics(size);
|
||||||
self.space.layouts.push((self.ctx.axes, Layout {
|
self.space.layouts.push((self.ctx.axes, BoxLayout {
|
||||||
dimensions: dimensions.specialized(self.ctx.axes),
|
size: size.specialized(self.ctx.axes),
|
||||||
align: LayoutAlign::new(Start, Start),
|
align: LayoutAlign::new(Start, Start),
|
||||||
elements: LayoutElements::new(),
|
elements: LayoutElements::new(),
|
||||||
}));
|
}));
|
||||||
@ -159,22 +159,22 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update the size metrics to reflect that a layout or spacing with the
|
/// Update the size metrics to reflect that a layout or spacing with the
|
||||||
/// given generalized dimensions has been added.
|
/// given generalized size has been added.
|
||||||
fn update_metrics(&mut self, dimensions: Size) {
|
fn update_metrics(&mut self, added: Size) {
|
||||||
let axes = self.ctx.axes;
|
let axes = self.ctx.axes;
|
||||||
|
|
||||||
let mut size = self.space.size.generalized(axes);
|
let mut size = self.space.size.generalized(axes);
|
||||||
let mut extra = self.space.extra.generalized(axes);
|
let mut extra = self.space.extra.generalized(axes);
|
||||||
|
|
||||||
size.x += (dimensions.x - extra.x).max(0.0);
|
size.x += (added.x - extra.x).max(0.0);
|
||||||
size.y += (dimensions.y - extra.y).max(0.0);
|
size.y += (added.y - extra.y).max(0.0);
|
||||||
|
|
||||||
extra.x = extra.x.max(dimensions.x);
|
extra.x = extra.x.max(added.x);
|
||||||
extra.y = (extra.y - dimensions.y).max(0.0);
|
extra.y = (extra.y - added.y).max(0.0);
|
||||||
|
|
||||||
self.space.size = size.specialized(axes);
|
self.space.size = size.specialized(axes);
|
||||||
self.space.extra = extra.specialized(axes);
|
self.space.extra = extra.specialized(axes);
|
||||||
*self.space.usable.secondary_mut(axes) -= dimensions.y;
|
*self.space.usable.secondary_mut(axes) -= added.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the rulers to account for the new layout. Returns true if a
|
/// Update the rulers to account for the new layout. Returns true if a
|
||||||
@ -226,12 +226,12 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move to the first space that can fit the given dimensions or do nothing
|
/// Move to the first space that can fit the given size or do nothing
|
||||||
/// if no space is capable of that.
|
/// if no space is capable of that.
|
||||||
pub fn skip_to_fitting_space(&mut self, dimensions: Size) {
|
pub fn skip_to_fitting_space(&mut self, size: Size) {
|
||||||
let start = self.next_space();
|
let start = self.next_space();
|
||||||
for (index, space) in self.ctx.spaces[start..].iter().enumerate() {
|
for (index, space) in self.ctx.spaces[start..].iter().enumerate() {
|
||||||
if space.usable().fits(dimensions) {
|
if space.usable().fits(size) {
|
||||||
self.finish_space(true);
|
self.finish_space(true);
|
||||||
self.start_space(start + index, true);
|
self.start_space(start + index, true);
|
||||||
return;
|
return;
|
||||||
@ -242,10 +242,10 @@ impl StackLayouter {
|
|||||||
/// The remaining unpadded, unexpanding spaces. If a function is laid out
|
/// The remaining unpadded, unexpanding spaces. If a function is laid out
|
||||||
/// into these spaces, it will fit into this stack.
|
/// into these spaces, it will fit into this stack.
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
let dimensions = self.usable();
|
let size = self.usable();
|
||||||
|
|
||||||
let mut spaces = vec![LayoutSpace {
|
let mut spaces = vec![LayoutSpace {
|
||||||
dimensions,
|
size,
|
||||||
padding: Margins::ZERO,
|
padding: Margins::ZERO,
|
||||||
expansion: LayoutExpansion::new(false, false),
|
expansion: LayoutExpansion::new(false, false),
|
||||||
}];
|
}];
|
||||||
@ -287,7 +287,7 @@ impl StackLayouter {
|
|||||||
let space = self.ctx.spaces[self.space.index];
|
let space = self.ctx.spaces[self.space.index];
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Step 1: Determine the full dimensions of the space.
|
// Step 1: Determine the full size of the space.
|
||||||
// (Mostly done already while collecting the boxes, but here we
|
// (Mostly done already while collecting the boxes, but here we
|
||||||
// expand if necessary.)
|
// expand if necessary.)
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ impl StackLayouter {
|
|||||||
if space.expansion.horizontal { self.space.size.x = usable.x; }
|
if space.expansion.horizontal { self.space.size.x = usable.x; }
|
||||||
if space.expansion.vertical { self.space.size.y = usable.y; }
|
if space.expansion.vertical { self.space.size.y = usable.y; }
|
||||||
|
|
||||||
let dimensions = self.space.size.padded(space.padding);
|
let size = self.space.size.padded(space.padding);
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
// Step 2: Forward pass. Create a bounding box for each layout in which
|
// Step 2: Forward pass. Create a bounding box for each layout in which
|
||||||
@ -323,7 +323,7 @@ impl StackLayouter {
|
|||||||
// the usable space for following layouts at it's origin by its
|
// the usable space for following layouts at it's origin by its
|
||||||
// extent along the secondary axis.
|
// extent along the secondary axis.
|
||||||
*bound.get_mut(axes.secondary, Start)
|
*bound.get_mut(axes.secondary, Start)
|
||||||
+= axes.secondary.factor() * layout.dimensions.secondary(*axes);
|
+= axes.secondary.factor() * layout.size.secondary(*axes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
@ -355,7 +355,7 @@ impl StackLayouter {
|
|||||||
-= axes.secondary.factor() * extent.y;
|
-= axes.secondary.factor() * extent.y;
|
||||||
|
|
||||||
// Then, we add this layout's secondary extent to the accumulator.
|
// Then, we add this layout's secondary extent to the accumulator.
|
||||||
let size = layout.dimensions.generalized(*axes);
|
let size = layout.size.generalized(*axes);
|
||||||
extent.x = extent.x.max(size.x);
|
extent.x = extent.x.max(size.x);
|
||||||
extent.y += size.y;
|
extent.y += size.y;
|
||||||
}
|
}
|
||||||
@ -368,7 +368,7 @@ impl StackLayouter {
|
|||||||
|
|
||||||
let layouts = std::mem::take(&mut self.space.layouts);
|
let layouts = std::mem::take(&mut self.space.layouts);
|
||||||
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
|
for ((axes, layout), bound) in layouts.into_iter().zip(bounds) {
|
||||||
let size = layout.dimensions.specialized(axes);
|
let size = layout.size.specialized(axes);
|
||||||
let align = layout.align;
|
let align = layout.align;
|
||||||
|
|
||||||
// The space in which this layout is aligned is given by the
|
// The space in which this layout is aligned is given by the
|
||||||
@ -383,8 +383,8 @@ impl StackLayouter {
|
|||||||
elements.extend_offset(pos, layout.elements);
|
elements.extend_offset(pos, layout.elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.layouts.push(Layout {
|
self.layouts.push(BoxLayout {
|
||||||
dimensions,
|
size,
|
||||||
align: self.ctx.align,
|
align: self.ctx.align,
|
||||||
elements,
|
elements,
|
||||||
});
|
});
|
||||||
|
@ -40,7 +40,7 @@ pub struct TextContext<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layouts text into a box.
|
/// Layouts text into a box.
|
||||||
pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> Layout {
|
pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout {
|
||||||
TextLayouter::new(text, ctx).layout().await
|
TextLayouter::new(text, ctx).layout().await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ impl<'a> TextLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Do the layouting.
|
/// Do the layouting.
|
||||||
async fn layout(mut self) -> Layout {
|
async fn layout(mut self) -> BoxLayout {
|
||||||
// If the primary axis is negative, we layout the characters reversed.
|
// If the primary axis is negative, we layout the characters reversed.
|
||||||
if self.ctx.axes.primary.is_positive() {
|
if self.ctx.axes.primary.is_positive() {
|
||||||
for c in self.text.chars() {
|
for c in self.text.chars() {
|
||||||
@ -76,8 +76,8 @@ impl<'a> TextLayouter<'a> {
|
|||||||
self.elements.push(pos, LayoutElement::Text(self.shaped));
|
self.elements.push(pos, LayoutElement::Text(self.shaped));
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout {
|
BoxLayout {
|
||||||
dimensions: Size::new(self.width, self.ctx.style.font_size()),
|
size: Size::new(self.width, self.ctx.style.font_size()),
|
||||||
align: self.ctx.align,
|
align: self.ctx.align,
|
||||||
elements: self.elements,
|
elements: self.elements,
|
||||||
}
|
}
|
||||||
|
223
src/layout/tree.rs
Normal file
223
src/layout/tree.rs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
//! The tree layouter layouts trees (i.e.
|
||||||
|
//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func))
|
||||||
|
//! by executing commands issued by the trees.
|
||||||
|
|
||||||
|
use crate::{Pass, Feedback, DynFuture};
|
||||||
|
use crate::style::LayoutStyle;
|
||||||
|
use crate::syntax::decoration::Decoration;
|
||||||
|
use crate::syntax::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
||||||
|
use crate::syntax::span::{Span, Spanned};
|
||||||
|
use super::line::{LineLayouter, LineContext};
|
||||||
|
use super::text::{layout_text, TextContext};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Performs the tree layouting.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TreeLayouter<'a> {
|
||||||
|
ctx: LayoutContext<'a>,
|
||||||
|
layouter: LineLayouter,
|
||||||
|
style: LayoutStyle,
|
||||||
|
feedback: Feedback,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TreeLayouter<'a> {
|
||||||
|
/// Create a new tree layouter.
|
||||||
|
pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> {
|
||||||
|
TreeLayouter {
|
||||||
|
layouter: LineLayouter::new(LineContext {
|
||||||
|
spaces: ctx.spaces.clone(),
|
||||||
|
axes: ctx.axes,
|
||||||
|
align: ctx.align,
|
||||||
|
repeat: ctx.repeat,
|
||||||
|
line_spacing: ctx.style.text.line_spacing(),
|
||||||
|
}),
|
||||||
|
style: ctx.style.clone(),
|
||||||
|
ctx,
|
||||||
|
feedback: Feedback::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout a syntax tree by directly processing the nodes instead of using
|
||||||
|
/// the command based architecture.
|
||||||
|
pub async fn layout_tree(&mut self, tree: &SyntaxTree) {
|
||||||
|
for node in tree {
|
||||||
|
self.layout_node(node).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) {
|
||||||
|
let decorate = |this: &mut TreeLayouter, deco| {
|
||||||
|
this.feedback.decorations.push(Spanned::new(deco, node.span));
|
||||||
|
};
|
||||||
|
|
||||||
|
match &node.v {
|
||||||
|
SyntaxNode::Space => self.layout_space(),
|
||||||
|
SyntaxNode::Parbreak => self.layout_paragraph(),
|
||||||
|
SyntaxNode::Linebreak => self.layouter.finish_line(),
|
||||||
|
|
||||||
|
SyntaxNode::Text(text) => {
|
||||||
|
if self.style.text.italic {
|
||||||
|
decorate(self, Decoration::Italic);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.style.text.bolder {
|
||||||
|
decorate(self, Decoration::Bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layout_text(text).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxNode::ToggleItalic => {
|
||||||
|
self.style.text.italic = !self.style.text.italic;
|
||||||
|
decorate(self, Decoration::Italic);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxNode::ToggleBolder => {
|
||||||
|
self.style.text.bolder = !self.style.text.bolder;
|
||||||
|
decorate(self, Decoration::Bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxNode::Raw(lines) => {
|
||||||
|
// TODO: Make this more efficient.
|
||||||
|
let fallback = self.style.text.fallback.clone();
|
||||||
|
self.style.text.fallback.list_mut().insert(0, "monospace".to_string());
|
||||||
|
self.style.text.fallback.flatten();
|
||||||
|
|
||||||
|
// Layout the first line.
|
||||||
|
let mut iter = lines.iter();
|
||||||
|
if let Some(line) = iter.next() {
|
||||||
|
self.layout_text(line).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a newline before each following line.
|
||||||
|
for line in iter {
|
||||||
|
self.layouter.finish_line();
|
||||||
|
self.layout_text(line).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.style.text.fallback = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyntaxNode::Dyn(dynamic) => {
|
||||||
|
self.layout_dyn(Spanned::new(dynamic.as_ref(), node.span)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Layout a node into this layouting process.
|
||||||
|
pub async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) {
|
||||||
|
// Execute the tree's layout function which generates the commands.
|
||||||
|
let layouted = dynamic.v.layout(LayoutContext {
|
||||||
|
style: &self.style,
|
||||||
|
spaces: self.layouter.remaining(),
|
||||||
|
nested: true,
|
||||||
|
.. self.ctx
|
||||||
|
}).await;
|
||||||
|
|
||||||
|
// Add the errors generated by the tree to the error list.
|
||||||
|
self.feedback.extend_offset(layouted.feedback, dynamic.span.start);
|
||||||
|
|
||||||
|
for command in layouted.output {
|
||||||
|
self.execute_command(command, dynamic.span).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the finished list of boxes.
|
||||||
|
pub fn finish(self) -> Pass<MultiLayout> {
|
||||||
|
Pass::new(self.layouter.finish(), self.feedback)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a command issued by a tree. When the command is errorful, the
|
||||||
|
/// given span is stored with the error.
|
||||||
|
fn execute_command<'r>(
|
||||||
|
&'r mut self,
|
||||||
|
command: Command<'r>,
|
||||||
|
tree_span: Span,
|
||||||
|
) -> DynFuture<'r, ()> { Box::pin(async move {
|
||||||
|
use Command::*;
|
||||||
|
|
||||||
|
match command {
|
||||||
|
LayoutSyntaxTree(tree) => self.layout_tree(tree).await,
|
||||||
|
|
||||||
|
Add(layout) => self.layouter.add(layout),
|
||||||
|
AddMultiple(layouts) => self.layouter.add_multiple(layouts),
|
||||||
|
AddSpacing(space, kind, axis) => match axis {
|
||||||
|
Primary => self.layouter.add_primary_spacing(space, kind),
|
||||||
|
Secondary => self.layouter.add_secondary_spacing(space, kind),
|
||||||
|
}
|
||||||
|
|
||||||
|
BreakLine => self.layouter.finish_line(),
|
||||||
|
BreakParagraph => self.layout_paragraph(),
|
||||||
|
BreakPage => {
|
||||||
|
if self.ctx.nested {
|
||||||
|
error!(
|
||||||
|
@self.feedback, tree_span,
|
||||||
|
"page break cannot be issued from nested context",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.layouter.finish_space(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTextStyle(style) => {
|
||||||
|
self.layouter.set_line_spacing(style.line_spacing());
|
||||||
|
self.style.text = style;
|
||||||
|
}
|
||||||
|
SetPageStyle(style) => {
|
||||||
|
if self.ctx.nested {
|
||||||
|
error!(
|
||||||
|
@self.feedback, tree_span,
|
||||||
|
"page style cannot be changed from nested context",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.style.page = style;
|
||||||
|
|
||||||
|
// The line layouter has no idea of page styles and thus we
|
||||||
|
// need to recompute the layouting space resulting of the
|
||||||
|
// new page style and update it within the layouter.
|
||||||
|
let margins = style.margins();
|
||||||
|
self.ctx.base = style.size.unpadded(margins);
|
||||||
|
self.layouter.set_spaces(vec![
|
||||||
|
LayoutSpace {
|
||||||
|
size: style.size,
|
||||||
|
padding: margins,
|
||||||
|
expansion: LayoutExpansion::new(true, true),
|
||||||
|
}
|
||||||
|
], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAlignment(align) => self.ctx.align = align,
|
||||||
|
SetAxes(axes) => {
|
||||||
|
self.layouter.set_axes(axes);
|
||||||
|
self.ctx.axes = axes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) }
|
||||||
|
|
||||||
|
/// Layout a continous piece of text and add it to the line layouter.
|
||||||
|
async fn layout_text(&mut self, text: &str) {
|
||||||
|
self.layouter.add(layout_text(text, TextContext {
|
||||||
|
loader: &self.ctx.loader,
|
||||||
|
style: &self.style.text,
|
||||||
|
axes: self.ctx.axes,
|
||||||
|
align: self.ctx.align,
|
||||||
|
}).await)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the spacing for a syntactic space node.
|
||||||
|
fn layout_space(&mut self) {
|
||||||
|
self.layouter.add_primary_spacing(
|
||||||
|
self.style.text.word_spacing(),
|
||||||
|
SpacingKind::WORD,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish the paragraph and add paragraph spacing.
|
||||||
|
fn layout_paragraph(&mut self) {
|
||||||
|
self.layouter.add_secondary_spacing(
|
||||||
|
self.style.text.paragraph_spacing(),
|
||||||
|
SpacingKind::PARAGRAPH,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
30
src/lib.rs
30
src/lib.rs
@ -16,25 +16,19 @@
|
|||||||
//! format is [_PDF_](crate::export::pdf).
|
//! format is [_PDF_](crate::export::pdf).
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
use crate::diagnostic::Diagnostics;
|
use crate::diagnostic::Diagnostics;
|
||||||
use crate::font::SharedFontLoader;
|
use crate::font::SharedFontLoader;
|
||||||
use crate::layout::MultiLayout;
|
use crate::layout::MultiLayout;
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
use crate::syntax::decoration::Decorations;
|
use crate::syntax::decoration::Decorations;
|
||||||
use crate::syntax::model::SyntaxModel;
|
use crate::syntax::tree::SyntaxTree;
|
||||||
use crate::syntax::parsing::{parse, ParseState};
|
use crate::syntax::parsing::{parse, ParseState};
|
||||||
use crate::syntax::scope::Scope;
|
use crate::syntax::scope::Scope;
|
||||||
use crate::syntax::span::{Offset, Pos};
|
use crate::syntax::span::{Offset, Pos};
|
||||||
|
|
||||||
/// Declare a module and reexport all its contents.
|
|
||||||
macro_rules! pub_use_mod {
|
|
||||||
($name:ident) => {
|
|
||||||
mod $name;
|
|
||||||
pub use $name::*;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -84,23 +78,24 @@ impl Typesetter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse source code into a syntax tree.
|
/// Parse source code into a syntax tree.
|
||||||
pub fn parse(&self, src: &str) -> Pass<SyntaxModel> {
|
pub fn parse(&self, src: &str) -> Pass<SyntaxTree> {
|
||||||
parse(src, Pos::ZERO, &self.parse_state)
|
parse(src, Pos::ZERO, &self.parse_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a syntax tree and return the produced layout.
|
/// Layout a syntax tree and return the produced layout.
|
||||||
pub async fn layout(&self, model: &SyntaxModel) -> Pass<MultiLayout> {
|
pub async fn layout(&self, tree: &SyntaxTree) -> Pass<MultiLayout> {
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::prelude::*;
|
||||||
|
use crate::layout::{LayoutContext, LayoutSpace};
|
||||||
|
|
||||||
let margins = self.style.page.margins();
|
let margins = self.style.page.margins();
|
||||||
crate::layout::layout(
|
layout(
|
||||||
&model,
|
&tree,
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
base: self.style.page.dimensions.unpadded(margins),
|
base: self.style.page.size.unpadded(margins),
|
||||||
spaces: vec![LayoutSpace {
|
spaces: vec![LayoutSpace {
|
||||||
dimensions: self.style.page.dimensions,
|
size: self.style.page.size,
|
||||||
padding: margins,
|
padding: margins,
|
||||||
expansion: LayoutExpansion::new(true, true),
|
expansion: LayoutExpansion::new(true, true),
|
||||||
}],
|
}],
|
||||||
@ -121,6 +116,11 @@ impl Typesetter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A dynamic future type which allows recursive invocation of async functions
|
||||||
|
/// when used as the return type. This is also how the async trait functions
|
||||||
|
/// work internally.
|
||||||
|
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
|
||||||
|
|
||||||
/// The result of some pass: Some output `T` and feedback data.
|
/// The result of some pass: Some output `T` and feedback data.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct Pass<T> {
|
pub struct Pass<T> {
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Font configuration.
|
||||||
|
|
||||||
use fontdock::{FontStyle, FontWeight, FontWidth};
|
use fontdock::{FontStyle, FontWeight, FontWidth};
|
||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -6,7 +8,7 @@ function! {
|
|||||||
/// `font`: Configure the font.
|
/// `font`: Configure the font.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FontFunc {
|
pub struct FontFunc {
|
||||||
body: Option<SyntaxModel>,
|
body: Option<SyntaxTree>,
|
||||||
size: Option<ScaleLength>,
|
size: Option<ScaleLength>,
|
||||||
style: Option<FontStyle>,
|
style: Option<FontStyle>,
|
||||||
weight: Option<FontWeight>,
|
weight: Option<FontWeight>,
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Layout building blocks.
|
||||||
|
|
||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
@ -5,14 +7,14 @@ function! {
|
|||||||
/// `box`: Layouts content into a box.
|
/// `box`: Layouts content into a box.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct BoxFunc {
|
pub struct BoxFunc {
|
||||||
body: SyntaxModel,
|
body: SyntaxTree,
|
||||||
width: Option<ScaleLength>,
|
width: Option<ScaleLength>,
|
||||||
height: Option<ScaleLength>,
|
height: Option<ScaleLength>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, ctx, f) {
|
parse(header, body, ctx, f) {
|
||||||
BoxFunc {
|
BoxFunc {
|
||||||
body: body!(opt: body, ctx, f).unwrap_or(SyntaxModel::new()),
|
body: body!(opt: body, ctx, f).unwrap_or(SyntaxTree::new()),
|
||||||
width: header.args.key.get::<ScaleLength>("width", f),
|
width: header.args.key.get::<ScaleLength>("width", f),
|
||||||
height: header.args.key.get::<ScaleLength>("height", f),
|
height: header.args.key.get::<ScaleLength>("height", f),
|
||||||
}
|
}
|
||||||
@ -25,14 +27,14 @@ function! {
|
|||||||
self.width.with(|v| {
|
self.width.with(|v| {
|
||||||
let length = v.raw_scaled(ctx.base.x);
|
let length = v.raw_scaled(ctx.base.x);
|
||||||
ctx.base.x = length;
|
ctx.base.x = length;
|
||||||
ctx.spaces[0].dimensions.x = length;
|
ctx.spaces[0].size.x = length;
|
||||||
ctx.spaces[0].expansion.horizontal = true;
|
ctx.spaces[0].expansion.horizontal = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
self.height.with(|v| {
|
self.height.with(|v| {
|
||||||
let length = v.raw_scaled(ctx.base.y);
|
let length = v.raw_scaled(ctx.base.y);
|
||||||
ctx.base.y = length;
|
ctx.base.y = length;
|
||||||
ctx.spaces[0].dimensions.y = length;
|
ctx.spaces[0].size.y = length;
|
||||||
ctx.spaces[0].expansion.vertical = true;
|
ctx.spaces[0].expansion.vertical = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,7 +50,7 @@ function! {
|
|||||||
/// `align`: Aligns content along the layouting axes.
|
/// `align`: Aligns content along the layouting axes.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AlignFunc {
|
pub struct AlignFunc {
|
||||||
body: Option<SyntaxModel>,
|
body: Option<SyntaxTree>,
|
||||||
aligns: Vec<Spanned<SpecAlign>>,
|
aligns: Vec<Spanned<SpecAlign>>,
|
||||||
h: Option<Spanned<SpecAlign>>,
|
h: Option<Spanned<SpecAlign>>,
|
||||||
v: Option<Spanned<SpecAlign>>,
|
v: Option<Spanned<SpecAlign>>,
|
||||||
@ -64,7 +66,7 @@ function! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
ctx.base = ctx.spaces[0].dimensions;
|
ctx.base = ctx.spaces[0].size;
|
||||||
|
|
||||||
let axes = ctx.axes;
|
let axes = ctx.axes;
|
||||||
let all = self.aligns.iter()
|
let all = self.aligns.iter()
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
//! The _Typst_ standard library.
|
//! The _Typst_ standard library.
|
||||||
|
|
||||||
use crate::syntax::scope::Scope;
|
|
||||||
use crate::func::prelude::*;
|
use crate::func::prelude::*;
|
||||||
|
use crate::layout::{LayoutContext, Commands};
|
||||||
|
use crate::syntax::scope::Scope;
|
||||||
|
|
||||||
pub_use_mod!(font);
|
macro_rules! lib { ($name:ident) => { mod $name; pub use $name::*; }}
|
||||||
pub_use_mod!(layout);
|
lib!(font);
|
||||||
pub_use_mod!(page);
|
lib!(layout);
|
||||||
pub_use_mod!(spacing);
|
lib!(page);
|
||||||
|
lib!(spacing);
|
||||||
|
|
||||||
/// Create a scope with all standard functions.
|
/// Create a scope with all standard functions.
|
||||||
pub fn std() -> Scope {
|
pub fn std() -> Scope {
|
||||||
@ -17,10 +19,10 @@ pub fn std() -> Scope {
|
|||||||
std.add::<PageFunc>("page");
|
std.add::<PageFunc>("page");
|
||||||
std.add::<AlignFunc>("align");
|
std.add::<AlignFunc>("align");
|
||||||
std.add::<BoxFunc>("box");
|
std.add::<BoxFunc>("box");
|
||||||
std.add_with_meta::<SpacingFunc>("h", Horizontal);
|
|
||||||
std.add_with_meta::<SpacingFunc>("v", Vertical);
|
|
||||||
std.add::<ParBreakFunc>("parbreak");
|
std.add::<ParBreakFunc>("parbreak");
|
||||||
std.add::<PageBreakFunc>("pagebreak");
|
std.add::<PageBreakFunc>("pagebreak");
|
||||||
|
std.add_with_meta::<SpacingFunc>("h", Horizontal);
|
||||||
|
std.add_with_meta::<SpacingFunc>("v", Vertical);
|
||||||
|
|
||||||
std
|
std
|
||||||
}
|
}
|
||||||
@ -29,7 +31,7 @@ function! {
|
|||||||
/// `val`: Layouts the body with no special effect.
|
/// `val`: Layouts the body with no special effect.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ValFunc {
|
pub struct ValFunc {
|
||||||
body: Option<SyntaxModel>,
|
body: Option<SyntaxTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
@ -40,7 +42,7 @@ function! {
|
|||||||
|
|
||||||
layout(self, ctx, f) {
|
layout(self, ctx, f) {
|
||||||
match &self.body {
|
match &self.body {
|
||||||
Some(model) => vec![LayoutSyntaxModel(model)],
|
Some(tree) => vec![LayoutSyntaxTree(tree)],
|
||||||
None => vec![],
|
None => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +50,7 @@ function! {
|
|||||||
|
|
||||||
/// Layout an optional body with a change of the text style.
|
/// Layout an optional body with a change of the text style.
|
||||||
fn styled<'a, T, F>(
|
fn styled<'a, T, F>(
|
||||||
body: &'a Option<SyntaxModel>,
|
body: &'a Option<SyntaxTree>,
|
||||||
ctx: LayoutContext<'_>,
|
ctx: LayoutContext<'_>,
|
||||||
data: Option<T>,
|
data: Option<T>,
|
||||||
f: F,
|
f: F,
|
||||||
@ -58,9 +60,9 @@ fn styled<'a, T, F>(
|
|||||||
f(&mut style, data);
|
f(&mut style, data);
|
||||||
|
|
||||||
match body {
|
match body {
|
||||||
Some(model) => vec![
|
Some(tree) => vec![
|
||||||
SetTextStyle(style),
|
SetTextStyle(style),
|
||||||
LayoutSyntaxModel(model),
|
LayoutSyntaxTree(tree),
|
||||||
SetTextStyle(ctx.style.text.clone()),
|
SetTextStyle(ctx.style.text.clone()),
|
||||||
],
|
],
|
||||||
None => vec![SetTextStyle(style)],
|
None => vec![SetTextStyle(style)],
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Page setup.
|
||||||
|
|
||||||
use crate::length::{Length, ScaleLength};
|
use crate::length::{Length, ScaleLength};
|
||||||
use crate::paper::{Paper, PaperClass};
|
use crate::paper::{Paper, PaperClass};
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -37,13 +39,13 @@ function! {
|
|||||||
|
|
||||||
if let Some(paper) = self.paper {
|
if let Some(paper) = self.paper {
|
||||||
style.class = paper.class;
|
style.class = paper.class;
|
||||||
style.dimensions = paper.size();
|
style.size = paper.size();
|
||||||
} else if self.width.is_some() || self.height.is_some() {
|
} else if self.width.is_some() || self.height.is_some() {
|
||||||
style.class = PaperClass::Custom;
|
style.class = PaperClass::Custom;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.width.with(|v| style.dimensions.x = v.as_raw());
|
self.width.with(|v| style.size.x = v.as_raw());
|
||||||
self.height.with(|v| style.dimensions.y = v.as_raw());
|
self.height.with(|v| style.size.y = v.as_raw());
|
||||||
self.margins.with(|v| style.margins.set_all(Some(v)));
|
self.margins.with(|v| style.margins.set_all(Some(v)));
|
||||||
self.left.with(|v| style.margins.left = Some(v));
|
self.left.with(|v| style.margins.left = Some(v));
|
||||||
self.right.with(|v| style.margins.right = Some(v));
|
self.right.with(|v| style.margins.right = Some(v));
|
||||||
@ -51,7 +53,7 @@ function! {
|
|||||||
self.bottom.with(|v| style.margins.bottom = Some(v));
|
self.bottom.with(|v| style.margins.bottom = Some(v));
|
||||||
|
|
||||||
if self.flip {
|
if self.flip {
|
||||||
style.dimensions.swap();
|
style.size.swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![SetPageStyle(style)]
|
vec![SetPageStyle(style)]
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Spacing.
|
||||||
|
|
||||||
use crate::length::ScaleLength;
|
use crate::length::ScaleLength;
|
||||||
use crate::layout::SpacingKind;
|
use crate::layout::SpacingKind;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
15
src/style.rs
15
src/style.rs
@ -97,7 +97,7 @@ pub struct PageStyle {
|
|||||||
/// The class of this page.
|
/// The class of this page.
|
||||||
pub class: PaperClass,
|
pub class: PaperClass,
|
||||||
/// The width and height of the page.
|
/// The width and height of the page.
|
||||||
pub dimensions: Size,
|
pub size: Size,
|
||||||
/// The amount of white space on each side. If a side is set to `None`, the
|
/// The amount of white space on each side. If a side is set to `None`, the
|
||||||
/// default for the paper class is used.
|
/// default for the paper class is used.
|
||||||
pub margins: Value4<Option<ScaleLength>>,
|
pub margins: Value4<Option<ScaleLength>>,
|
||||||
@ -108,21 +108,20 @@ impl PageStyle {
|
|||||||
pub fn new(paper: Paper) -> PageStyle {
|
pub fn new(paper: Paper) -> PageStyle {
|
||||||
PageStyle {
|
PageStyle {
|
||||||
class: paper.class,
|
class: paper.class,
|
||||||
dimensions: paper.size(),
|
size: paper.size(),
|
||||||
margins: Value4::with_all(None),
|
margins: Value4::with_all(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute margins.
|
/// The absolute margins.
|
||||||
pub fn margins(&self) -> Margins {
|
pub fn margins(&self) -> Margins {
|
||||||
let dims = self.dimensions;
|
let size = self.size;
|
||||||
let default = self.class.default_margins();
|
let default = self.class.default_margins();
|
||||||
|
|
||||||
Margins {
|
Margins {
|
||||||
left: self.margins.left.unwrap_or(default.left).raw_scaled(dims.x),
|
left: self.margins.left.unwrap_or(default.left).raw_scaled(size.x),
|
||||||
top: self.margins.top.unwrap_or(default.top).raw_scaled(dims.y),
|
top: self.margins.top.unwrap_or(default.top).raw_scaled(size.y),
|
||||||
right: self.margins.right.unwrap_or(default.right).raw_scaled(dims.x),
|
right: self.margins.right.unwrap_or(default.right).raw_scaled(size.x),
|
||||||
bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(dims.y),
|
bottom: self.margins.bottom.unwrap_or(default.bottom).raw_scaled(size.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use std::u8;
|
|||||||
|
|
||||||
use crate::Feedback;
|
use crate::Feedback;
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
use super::span::Spanned;
|
use super::span::{Spanned, SpanVec};
|
||||||
use super::tokens::is_identifier;
|
use super::tokens::is_identifier;
|
||||||
use super::value::Value;
|
use super::value::Value;
|
||||||
|
|
||||||
@ -237,7 +237,7 @@ impl fmt::Display for ParseColorError {
|
|||||||
/// (false, 12cm, "hi")
|
/// (false, 12cm, "hi")
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Tuple(pub Vec<Spanned<Expr>>);
|
pub struct Tuple(pub SpanVec<Expr>);
|
||||||
|
|
||||||
impl Tuple {
|
impl Tuple {
|
||||||
/// Create an empty tuple.
|
/// Create an empty tuple.
|
||||||
@ -333,7 +333,7 @@ impl Deref for NamedTuple {
|
|||||||
/// { fit: false, width: 12cm, items: (1, 2, 3) }
|
/// { fit: false, width: 12cm, items: (1, 2, 3) }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Object(pub Vec<Spanned<Pair>>);
|
pub struct Object(pub SpanVec<Pair>);
|
||||||
|
|
||||||
/// A key-value pair in an object.
|
/// A key-value pair in an object.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Syntax models, parsing and tokenization.
|
//! Syntax trees, parsing and tokenization.
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -6,7 +6,7 @@ mod test;
|
|||||||
|
|
||||||
pub mod decoration;
|
pub mod decoration;
|
||||||
pub mod expr;
|
pub mod expr;
|
||||||
pub mod model;
|
pub mod tree;
|
||||||
pub mod parsing;
|
pub mod parsing;
|
||||||
pub mod span;
|
pub mod span;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
|
@ -1,134 +0,0 @@
|
|||||||
//! The syntax model.
|
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
use crate::{Pass, Feedback};
|
|
||||||
use crate::layout::{LayoutContext, Commands, Command};
|
|
||||||
use super::span::{Spanned, SpanVec};
|
|
||||||
|
|
||||||
/// Represents a parsed piece of source that can be layouted and in the future
|
|
||||||
/// also be queried for information used for refactorings, autocomplete, etc.
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
pub trait Model: Debug + ModelBounds {
|
|
||||||
/// Layout the model into a sequence of commands processed by a
|
|
||||||
/// [`ModelLayouter`](crate::layout::ModelLayouter).
|
|
||||||
async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A tree representation of source code.
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
|
||||||
pub struct SyntaxModel {
|
|
||||||
/// The syntactical elements making up this model.
|
|
||||||
pub nodes: SpanVec<Node>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SyntaxModel {
|
|
||||||
/// Create an empty syntax model.
|
|
||||||
pub fn new() -> SyntaxModel {
|
|
||||||
SyntaxModel { nodes: vec![] }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a node to the model.
|
|
||||||
pub fn add(&mut self, node: Spanned<Node>) {
|
|
||||||
self.nodes.push(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
|
||||||
impl Model for SyntaxModel {
|
|
||||||
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>> {
|
|
||||||
Pass::new(vec![Command::LayoutSyntaxModel(self)], Feedback::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node in the [syntax model](SyntaxModel).
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Node {
|
|
||||||
/// Whitespace containing less than two newlines.
|
|
||||||
Space,
|
|
||||||
/// Whitespace with more than two newlines.
|
|
||||||
Parbreak,
|
|
||||||
/// A forced line break.
|
|
||||||
Linebreak,
|
|
||||||
/// Plain text.
|
|
||||||
Text(String),
|
|
||||||
/// Lines of raw text.
|
|
||||||
Raw(Vec<String>),
|
|
||||||
/// Italics were enabled / disabled.
|
|
||||||
ToggleItalic,
|
|
||||||
/// Bolder was enabled / disabled.
|
|
||||||
ToggleBolder,
|
|
||||||
/// A submodel, typically a function invocation.
|
|
||||||
Model(Box<dyn Model>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Node {
|
|
||||||
fn eq(&self, other: &Node) -> bool {
|
|
||||||
use Node::*;
|
|
||||||
match (self, other) {
|
|
||||||
(Space, Space) => true,
|
|
||||||
(Parbreak, Parbreak) => true,
|
|
||||||
(Linebreak, Linebreak) => true,
|
|
||||||
(Text(a), Text(b)) => a == b,
|
|
||||||
(Raw(a), Raw(b)) => a == b,
|
|
||||||
(ToggleItalic, ToggleItalic) => true,
|
|
||||||
(ToggleBolder, ToggleBolder) => true,
|
|
||||||
(Model(a), Model(b)) => a == b,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl dyn Model {
|
|
||||||
/// Downcast this model to a concrete type implementing [`Model`].
|
|
||||||
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
|
|
||||||
self.as_any().downcast_ref::<T>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for dyn Model {
|
|
||||||
fn eq(&self, other: &dyn Model) -> bool {
|
|
||||||
self.bound_eq(other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Box<dyn Model> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.bound_clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This trait describes bounds necessary for types implementing [`Model`]. It is
|
|
||||||
/// automatically implemented for all types that are [`Model`], [`PartialEq`],
|
|
||||||
/// [`Clone`] and `'static`.
|
|
||||||
///
|
|
||||||
/// It is necessary to make models comparable and clonable.
|
|
||||||
pub trait ModelBounds {
|
|
||||||
/// Convert into a `dyn Any`.
|
|
||||||
fn as_any(&self) -> &dyn Any;
|
|
||||||
|
|
||||||
/// Check for equality with another model.
|
|
||||||
fn bound_eq(&self, other: &dyn Model) -> bool;
|
|
||||||
|
|
||||||
/// Clone into a boxed model trait object.
|
|
||||||
fn bound_clone(&self) -> Box<dyn Model>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ModelBounds for T where T: Model + PartialEq + Clone + 'static {
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bound_eq(&self, other: &dyn Model) -> bool {
|
|
||||||
match other.as_any().downcast_ref::<Self>() {
|
|
||||||
Some(other) => self == other,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bound_clone(&self) -> Box<dyn Model> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
//! Parsing of source code into syntax models.
|
//! Parsing of source code into syntax trees.
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -8,10 +8,10 @@ use super::expr::*;
|
|||||||
use super::scope::Scope;
|
use super::scope::Scope;
|
||||||
use super::span::{Pos, Span, Spanned};
|
use super::span::{Pos, Span, Spanned};
|
||||||
use super::tokens::{is_newline_char, Token, Tokens, TokenMode};
|
use super::tokens::{is_newline_char, Token, Tokens, TokenMode};
|
||||||
use super::model::{SyntaxModel, Node, Model};
|
use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
||||||
|
|
||||||
/// A function which parses a function call into a model.
|
/// A function which parses a function call into a tree.
|
||||||
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn Model>>;
|
pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
|
||||||
|
|
||||||
/// An invocation of a function.
|
/// An invocation of a function.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
@ -73,12 +73,12 @@ pub struct ParseState {
|
|||||||
|
|
||||||
/// Parse a string of source code.
|
/// Parse a string of source code.
|
||||||
///
|
///
|
||||||
/// All spans in the resulting model and feedback are offset by the given
|
/// All spans in the resulting tree and feedback are offset by the given
|
||||||
/// `offset` position. This is used to make spans of a function body relative to
|
/// `offset` position. This is used to make spans of a function body relative to
|
||||||
/// the start of the function as a whole as opposed to the start of the
|
/// the start of the function as a whole as opposed to the start of the
|
||||||
/// function's body.
|
/// function's body.
|
||||||
pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
|
pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
|
||||||
let mut model = SyntaxModel::new();
|
let mut tree = SyntaxTree::new();
|
||||||
let mut feedback = Feedback::new();
|
let mut feedback = Feedback::new();
|
||||||
|
|
||||||
for token in Tokens::new(src, offset, TokenMode::Body) {
|
for token in Tokens::new(src, offset, TokenMode::Body) {
|
||||||
@ -87,9 +87,9 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
|
|||||||
// Starting from two newlines counts as a paragraph break, a single
|
// Starting from two newlines counts as a paragraph break, a single
|
||||||
// newline does not.
|
// newline does not.
|
||||||
Token::Space(newlines) => if newlines >= 2 {
|
Token::Space(newlines) => if newlines >= 2 {
|
||||||
Node::Parbreak
|
SyntaxNode::Parbreak
|
||||||
} else {
|
} else {
|
||||||
Node::Space
|
SyntaxNode::Space
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Function { header, body, terminated } => {
|
Token::Function { header, body, terminated } => {
|
||||||
@ -103,19 +103,19 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
|
|||||||
parsed.output
|
parsed.output
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Star => Node::ToggleBolder,
|
Token::Star => SyntaxNode::ToggleBolder,
|
||||||
Token::Underscore => Node::ToggleItalic,
|
Token::Underscore => SyntaxNode::ToggleItalic,
|
||||||
Token::Backslash => Node::Linebreak,
|
Token::Backslash => SyntaxNode::Linebreak,
|
||||||
|
|
||||||
Token::Raw { raw, terminated } => {
|
Token::Raw { raw, terminated } => {
|
||||||
if !terminated {
|
if !terminated {
|
||||||
error!(@feedback, Span::at(span.end), "expected backtick");
|
error!(@feedback, Span::at(span.end), "expected backtick");
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::Raw(unescape_raw(raw))
|
SyntaxNode::Raw(unescape_raw(raw))
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Text(text) => Node::Text(text.to_string()),
|
Token::Text(text) => SyntaxNode::Text(text.to_string()),
|
||||||
|
|
||||||
Token::LineComment(_) | Token::BlockComment(_) => continue,
|
Token::LineComment(_) | Token::BlockComment(_) => continue,
|
||||||
unexpected => {
|
unexpected => {
|
||||||
@ -124,10 +124,10 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxModel> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
model.add(Spanned::new(node, span));
|
tree.push(Spanned::new(node, span));
|
||||||
}
|
}
|
||||||
|
|
||||||
Pass::new(model, feedback)
|
Pass::new(tree, feedback)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FuncParser<'s> {
|
struct FuncParser<'s> {
|
||||||
@ -164,7 +164,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(mut self) -> Pass<Node> {
|
fn parse(mut self) -> Pass<SyntaxNode> {
|
||||||
let (parser, header) = if let Some(header) = self.parse_func_header() {
|
let (parser, header) = if let Some(header) = self.parse_func_header() {
|
||||||
let name = header.name.v.as_str();
|
let name = header.name.v.as_str();
|
||||||
let (parser, deco) = match self.state.scope.get_parser(name) {
|
let (parser, deco) = match self.state.scope.get_parser(name) {
|
||||||
@ -197,9 +197,8 @@ impl<'s> FuncParser<'s> {
|
|||||||
|
|
||||||
let call = FuncCall { header, body: self.body };
|
let call = FuncCall { header, body: self.body };
|
||||||
let parsed = parser(call, self.state);
|
let parsed = parser(call, self.state);
|
||||||
|
|
||||||
self.feedback.extend(parsed.feedback);
|
self.feedback.extend(parsed.feedback);
|
||||||
Pass::new(Node::Model(parsed.output), self.feedback)
|
Pass::new(SyntaxNode::Dyn(parsed.output), self.feedback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_func_header(&mut self) -> Option<FuncHeader> {
|
fn parse_func_header(&mut self) -> Option<FuncHeader> {
|
||||||
@ -662,26 +661,27 @@ fn unescape_raw(raw: &str) -> Vec<String> {
|
|||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::length::Length;
|
use crate::length::Length;
|
||||||
use super::super::test::{check, DebugFn};
|
use crate::syntax::span::SpanVec;
|
||||||
|
use crate::syntax::test::{check, DebugFn};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use Decoration::*;
|
use Decoration::*;
|
||||||
use Expr::{Number as Num, Length as Len, Bool};
|
use Expr::{Number as Num, Length as Len, Bool};
|
||||||
use Node::{
|
use SyntaxNode::{
|
||||||
Space as S, ToggleItalic as Italic, ToggleBolder as Bold,
|
Space as S, ToggleItalic as Italic, ToggleBolder as Bold,
|
||||||
Parbreak, Linebreak,
|
Parbreak, Linebreak,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Test whether the given string parses into
|
/// Test whether the given string parses into
|
||||||
/// - the given node list (required).
|
/// - the given SyntaxNode list (required).
|
||||||
/// - the given error list (optional, if omitted checks against empty list).
|
/// - the given error list (optional, if omitted checks against empty list).
|
||||||
/// - the given decoration list (optional, if omitted it is not tested).
|
/// - the given decoration list (optional, if omitted it is not tested).
|
||||||
macro_rules! p {
|
macro_rules! p {
|
||||||
($source:expr => [$($model:tt)*]) => {
|
($source:expr => [$($tree:tt)*]) => {
|
||||||
p!($source => [$($model)*], []);
|
p!($source => [$($tree)*], []);
|
||||||
};
|
};
|
||||||
|
|
||||||
($source:expr => [$($model:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
|
($source:expr => [$($tree:tt)*], [$($diagnostics:tt)*] $(, [$($decos:tt)*])? $(,)?) => {
|
||||||
let mut scope = Scope::new::<DebugFn>();
|
let mut scope = Scope::new::<DebugFn>();
|
||||||
scope.add::<DebugFn>("f");
|
scope.add::<DebugFn>("f");
|
||||||
scope.add::<DebugFn>("n");
|
scope.add::<DebugFn>("n");
|
||||||
@ -691,9 +691,9 @@ mod tests {
|
|||||||
let state = ParseState { scope };
|
let state = ParseState { scope };
|
||||||
let pass = parse($source, Pos::ZERO, &state);
|
let pass = parse($source, Pos::ZERO, &state);
|
||||||
|
|
||||||
// Test model.
|
// Test tree.
|
||||||
let (exp, cmp) = span_vec![$($model)*];
|
let (exp, cmp) = span_vec![$($tree)*];
|
||||||
check($source, exp, pass.output.nodes, cmp);
|
check($source, exp, pass.output, cmp);
|
||||||
|
|
||||||
// Test diagnostics.
|
// Test diagnostics.
|
||||||
let (exp, cmp) = span_vec![$($diagnostics)*];
|
let (exp, cmp) = span_vec![$($diagnostics)*];
|
||||||
@ -728,7 +728,7 @@ mod tests {
|
|||||||
fn Sub(e1: Expr, e2: Expr) -> Expr { Expr::Sub(Box::new(Z(e1)), Box::new(Z(e2))) }
|
fn Sub(e1: Expr, e2: Expr) -> Expr { Expr::Sub(Box::new(Z(e1)), Box::new(Z(e2))) }
|
||||||
fn Mul(e1: Expr, e2: Expr) -> Expr { Expr::Mul(Box::new(Z(e1)), Box::new(Z(e2))) }
|
fn Mul(e1: Expr, e2: Expr) -> Expr { Expr::Mul(Box::new(Z(e1)), Box::new(Z(e2))) }
|
||||||
fn Div(e1: Expr, e2: Expr) -> Expr { Expr::Div(Box::new(Z(e1)), Box::new(Z(e2))) }
|
fn Div(e1: Expr, e2: Expr) -> Expr { Expr::Div(Box::new(Z(e1)), Box::new(Z(e2))) }
|
||||||
fn T(text: &str) -> Node { Node::Text(text.to_string()) }
|
fn T(text: &str) -> SyntaxNode { SyntaxNode::Text(text.to_string()) }
|
||||||
fn Z<T>(v: T) -> Spanned<T> { Spanned::zero(v) }
|
fn Z<T>(v: T) -> Spanned<T> { Spanned::zero(v) }
|
||||||
|
|
||||||
macro_rules! tuple {
|
macro_rules! tuple {
|
||||||
@ -757,7 +757,7 @@ mod tests {
|
|||||||
|
|
||||||
macro_rules! raw {
|
macro_rules! raw {
|
||||||
($($line:expr),* $(,)?) => {
|
($($line:expr),* $(,)?) => {
|
||||||
Node::Raw(vec![$($line.to_string()),*])
|
SyntaxNode::Raw(vec![$($line.to_string()),*])
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -769,7 +769,7 @@ mod tests {
|
|||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut args = FuncArgs::new();
|
let mut args = FuncArgs::new();
|
||||||
$(
|
$(
|
||||||
let items: Vec<Spanned<Expr>> = span_vec![$($pos)*].0;
|
let items: SpanVec<Expr> = span_vec![$($pos)*].0;
|
||||||
for item in items {
|
for item in items {
|
||||||
args.push(item.map(|v| FuncArg::Pos(v)));
|
args.push(item.map(|v| FuncArg::Pos(v)));
|
||||||
}
|
}
|
||||||
@ -778,7 +778,7 @@ mod tests {
|
|||||||
value: Z($value),
|
value: Z($value),
|
||||||
})));)*)?
|
})));)*)?
|
||||||
)?
|
)?
|
||||||
Node::Model(Box::new(DebugFn {
|
SyntaxNode::Dyn(Box::new(DebugFn {
|
||||||
header: FuncHeader {
|
header: FuncHeader {
|
||||||
name: span_item!($name).map(|s| Ident(s.to_string())),
|
name: span_item!($name).map(|s| Ident(s.to_string())),
|
||||||
args,
|
args,
|
||||||
@ -786,7 +786,7 @@ mod tests {
|
|||||||
body: func!(@body $($($body)*)?),
|
body: func!(@body $($($body)*)?),
|
||||||
}))
|
}))
|
||||||
}};
|
}};
|
||||||
(@body [$($body:tt)*]) => { Some(SyntaxModel { nodes: span_vec![$($body)*].0 }) };
|
(@body [$($body:tt)*]) => { Some(span_vec![$($body)*].0) };
|
||||||
(@body) => { None };
|
(@body) => { None };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -818,8 +818,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn unescape_raws() {
|
fn unescape_raws() {
|
||||||
fn test(raw: &str, expected: Node) {
|
fn test(raw: &str, expected: SyntaxNode) {
|
||||||
let vec = if let Node::Raw(v) = expected { v } else { panic!() };
|
let vec = if let SyntaxNode::Raw(v) = expected { v } else { panic!() };
|
||||||
assert_eq!(unescape_raw(raw), vec);
|
assert_eq!(unescape_raw(raw), vec);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -834,8 +834,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_basic_nodes() {
|
fn parse_basic_SyntaxNodes() {
|
||||||
// Basic nodes.
|
// Basic SyntaxNodes.
|
||||||
p!("" => []);
|
p!("" => []);
|
||||||
p!("hi" => [T("hi")]);
|
p!("hi" => [T("hi")]);
|
||||||
p!("*hi" => [Bold, T("hi")]);
|
p!("*hi" => [Bold, T("hi")]);
|
||||||
@ -855,7 +855,7 @@ mod tests {
|
|||||||
p!("`hi\nyou" => [raw!["hi", "you"]], [(1:3, 1:3, "expected backtick")]);
|
p!("`hi\nyou" => [raw!["hi", "you"]], [(1:3, 1:3, "expected backtick")]);
|
||||||
p!("`hi\\`du`" => [raw!["hi`du"]]);
|
p!("`hi\\`du`" => [raw!["hi`du"]]);
|
||||||
|
|
||||||
// Spanned nodes.
|
// Spanned SyntaxNodes.
|
||||||
p!("Hi" => [(0:0, 0:2, T("Hi"))]);
|
p!("Hi" => [(0:0, 0:2, T("Hi"))]);
|
||||||
p!("*Hi*" => [(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)]);
|
p!("*Hi*" => [(0:0, 0:1, Bold), (0:1, 0:3, T("Hi")), (0:3, 0:4, Bold)]);
|
||||||
p!("🌎\n*/[n]" =>
|
p!("🌎\n*/[n]" =>
|
||||||
|
@ -5,7 +5,7 @@ use std::fmt::{self, Debug, Formatter};
|
|||||||
|
|
||||||
use crate::func::ParseFunc;
|
use crate::func::ParseFunc;
|
||||||
use super::parsing::CallParser;
|
use super::parsing::CallParser;
|
||||||
use super::model::Model;
|
use super::tree::DynamicNode;
|
||||||
|
|
||||||
/// A map from identifiers to function parsers.
|
/// A map from identifiers to function parsers.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
@ -17,7 +17,7 @@ impl Scope {
|
|||||||
/// Create a new empty scope with a fallback parser that is invoked when no
|
/// Create a new empty scope with a fallback parser that is invoked when no
|
||||||
/// match is found.
|
/// match is found.
|
||||||
pub fn new<F>() -> Scope
|
pub fn new<F>() -> Scope
|
||||||
where F: ParseFunc<Meta=()> + Model + 'static {
|
where F: ParseFunc<Meta=()> + DynamicNode + 'static {
|
||||||
Scope {
|
Scope {
|
||||||
parsers: HashMap::new(),
|
parsers: HashMap::new(),
|
||||||
fallback: make_parser::<F>(()),
|
fallback: make_parser::<F>(()),
|
||||||
@ -31,14 +31,14 @@ impl Scope {
|
|||||||
|
|
||||||
/// Associate the given name with a type that is parseable into a function.
|
/// Associate the given name with a type that is parseable into a function.
|
||||||
pub fn add<F>(&mut self, name: &str)
|
pub fn add<F>(&mut self, name: &str)
|
||||||
where F: ParseFunc<Meta=()> + Model + 'static {
|
where F: ParseFunc<Meta=()> + DynamicNode + 'static {
|
||||||
self.add_with_meta::<F>(name, ());
|
self.add_with_meta::<F>(name, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a parseable type with additional metadata that is given to the
|
/// Add a parseable type with additional metadata that is given to the
|
||||||
/// parser (other than the default of `()`).
|
/// parser (other than the default of `()`).
|
||||||
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
|
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
|
||||||
where F: ParseFunc + Model + 'static {
|
where F: ParseFunc + DynamicNode + 'static {
|
||||||
self.parsers.insert(
|
self.parsers.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
make_parser::<F>(metadata),
|
make_parser::<F>(metadata),
|
||||||
@ -65,9 +65,9 @@ impl Debug for Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser>
|
fn make_parser<F>(metadata: <F as ParseFunc>::Meta) -> Box<CallParser>
|
||||||
where F: ParseFunc + Model + 'static {
|
where F: ParseFunc + DynamicNode + 'static {
|
||||||
Box::new(move |f, s| {
|
Box::new(move |f, s| {
|
||||||
F::parse(f, s, metadata.clone())
|
F::parse(f, s, metadata.clone())
|
||||||
.map(|model| Box::new(model) as Box<dyn Model>)
|
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair};
|
|||||||
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
|
use super::parsing::{FuncHeader, FuncArgs, FuncArg};
|
||||||
use super::span::Spanned;
|
use super::span::Spanned;
|
||||||
use super::tokens::Token;
|
use super::tokens::Token;
|
||||||
use super::model::{SyntaxModel, Model, Node};
|
use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
|
||||||
|
|
||||||
/// Check whether the expected and found results are the same.
|
/// Check whether the expected and found results are the same.
|
||||||
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
|
pub fn check<T>(src: &str, exp: T, found: T, cmp_spans: bool)
|
||||||
@ -62,7 +62,7 @@ function! {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct DebugFn {
|
pub struct DebugFn {
|
||||||
pub header: FuncHeader,
|
pub header: FuncHeader,
|
||||||
pub body: Option<SyntaxModel>,
|
pub body: Option<SyntaxTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(header, body, state, f) {
|
parse(header, body, state, f) {
|
||||||
@ -83,20 +83,14 @@ pub trait SpanlessEq<Rhs=Self> {
|
|||||||
fn spanless_eq(&self, other: &Rhs) -> bool;
|
fn spanless_eq(&self, other: &Rhs) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpanlessEq for SyntaxModel {
|
impl SpanlessEq for SyntaxNode {
|
||||||
fn spanless_eq(&self, other: &SyntaxModel) -> bool {
|
fn spanless_eq(&self, other: &SyntaxNode) -> bool {
|
||||||
self.nodes.spanless_eq(&other.nodes)
|
fn downcast<'a>(func: &'a (dyn DynamicNode + 'static)) -> &'a DebugFn {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpanlessEq for Node {
|
|
||||||
fn spanless_eq(&self, other: &Node) -> bool {
|
|
||||||
fn downcast<'a>(func: &'a (dyn Model + 'static)) -> &'a DebugFn {
|
|
||||||
func.downcast::<DebugFn>().expect("not a debug fn")
|
func.downcast::<DebugFn>().expect("not a debug fn")
|
||||||
}
|
}
|
||||||
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(Node::Model(a), Node::Model(b)) => {
|
(SyntaxNode::Dyn(a), SyntaxNode::Dyn(b)) => {
|
||||||
downcast(a.as_ref()).spanless_eq(downcast(b.as_ref()))
|
downcast(a.as_ref()).spanless_eq(downcast(b.as_ref()))
|
||||||
}
|
}
|
||||||
(a, b) => a == b,
|
(a, b) => a == b,
|
||||||
|
@ -83,8 +83,8 @@ pub enum Token<'s> {
|
|||||||
ExprHex(&'s str),
|
ExprHex(&'s str),
|
||||||
/// A plus in a function header, signifying the addition of expressions.
|
/// A plus in a function header, signifying the addition of expressions.
|
||||||
Plus,
|
Plus,
|
||||||
/// A hyphen in a function header,
|
/// A hyphen in a function header, signifying the subtraction of
|
||||||
/// signifying the subtraction of expressions.
|
/// expressions.
|
||||||
Hyphen,
|
Hyphen,
|
||||||
/// A slash in a function header, signifying the division of expressions.
|
/// A slash in a function header, signifying the division of expressions.
|
||||||
Slash,
|
Slash,
|
||||||
|
100
src/syntax/tree.rs
Normal file
100
src/syntax/tree.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//! The syntax tree.
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
use crate::layout::Layout;
|
||||||
|
use super::span::SpanVec;
|
||||||
|
|
||||||
|
/// A list of nodes which forms a tree together with the nodes' children.
|
||||||
|
pub type SyntaxTree = SpanVec<SyntaxNode>;
|
||||||
|
|
||||||
|
/// A syntax node, which encompasses a single logical entity of parsed source
|
||||||
|
/// code.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum SyntaxNode {
|
||||||
|
/// Whitespace containing less than two newlines.
|
||||||
|
Space,
|
||||||
|
/// Whitespace with more than two newlines.
|
||||||
|
Parbreak,
|
||||||
|
/// A forced line break.
|
||||||
|
Linebreak,
|
||||||
|
/// Plain text.
|
||||||
|
Text(String),
|
||||||
|
/// Lines of raw text.
|
||||||
|
Raw(Vec<String>),
|
||||||
|
/// Italics were enabled / disabled.
|
||||||
|
ToggleItalic,
|
||||||
|
/// Bolder was enabled / disabled.
|
||||||
|
ToggleBolder,
|
||||||
|
/// A subtree, typically a function invocation.
|
||||||
|
Dyn(Box<dyn DynamicNode>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SyntaxNode {
|
||||||
|
fn eq(&self, other: &SyntaxNode) -> bool {
|
||||||
|
use SyntaxNode::*;
|
||||||
|
match (self, other) {
|
||||||
|
(Space, Space) => true,
|
||||||
|
(Parbreak, Parbreak) => true,
|
||||||
|
(Linebreak, Linebreak) => true,
|
||||||
|
(Text(a), Text(b)) => a == b,
|
||||||
|
(Raw(a), Raw(b)) => a == b,
|
||||||
|
(ToggleItalic, ToggleItalic) => true,
|
||||||
|
(ToggleBolder, ToggleBolder) => true,
|
||||||
|
(Dyn(a), Dyn(b)) => a == b,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dynamic syntax nodes.
|
||||||
|
///
|
||||||
|
/// *Note*: This is automatically implemented for all types which are
|
||||||
|
/// `Debug + Clone + PartialEq`, `Layout` and `'static`.
|
||||||
|
pub trait DynamicNode: Debug + Layout {
|
||||||
|
/// Convert into a `dyn Any`.
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
|
/// Check for equality with another dynamic node.
|
||||||
|
fn dyn_eq(&self, other: &dyn DynamicNode) -> bool;
|
||||||
|
|
||||||
|
/// Clone into a boxed node trait object.
|
||||||
|
fn box_clone(&self) -> Box<dyn DynamicNode>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl dyn DynamicNode {
|
||||||
|
/// Downcast this dynamic node to a concrete node.
|
||||||
|
pub fn downcast<N>(&self) -> Option<&N> where N: DynamicNode + 'static {
|
||||||
|
self.as_any().downcast_ref::<N>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for dyn DynamicNode {
|
||||||
|
fn eq(&self, other: &dyn DynamicNode) -> bool {
|
||||||
|
self.dyn_eq(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Box<dyn DynamicNode> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
self.box_clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> DynamicNode for T where T: Debug + PartialEq + Clone + Layout + 'static {
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_eq(&self, other: &dyn DynamicNode) -> bool {
|
||||||
|
match other.as_any().downcast_ref::<Self>() {
|
||||||
|
Some(other) => self == other,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn box_clone(&self) -> Box<dyn DynamicNode> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
}
|
@ -73,7 +73,7 @@ fn main() {
|
|||||||
|
|
||||||
typesetter.set_page_style(PageStyle {
|
typesetter.set_page_style(PageStyle {
|
||||||
class: PaperClass::Custom,
|
class: PaperClass::Custom,
|
||||||
dimensions: Size::with_all(Length::pt(250.0).as_raw()),
|
size: Size::with_all(Length::pt(250.0).as_raw()),
|
||||||
margins: Value4::with_all(None),
|
margins: Value4::with_all(None),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -148,13 +148,13 @@ fn render(
|
|||||||
) -> DrawTarget {
|
) -> DrawTarget {
|
||||||
let pad = scale * 10.0;
|
let pad = scale * 10.0;
|
||||||
let width = 2.0 * pad + layouts.iter()
|
let width = 2.0 * pad + layouts.iter()
|
||||||
.map(|l| scale * l.dimensions.x)
|
.map(|l| scale * l.size.x)
|
||||||
.max_by(|a, b| a.partial_cmp(&b).unwrap())
|
.max_by(|a, b| a.partial_cmp(&b).unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.round();
|
.round();
|
||||||
|
|
||||||
let height = pad + layouts.iter()
|
let height = pad + layouts.iter()
|
||||||
.map(|l| scale * l.dimensions.y + pad)
|
.map(|l| scale * l.size.y + pad)
|
||||||
.sum::<f64>()
|
.sum::<f64>()
|
||||||
.round();
|
.round();
|
||||||
|
|
||||||
@ -166,8 +166,8 @@ fn render(
|
|||||||
surface.fill_rect(
|
surface.fill_rect(
|
||||||
offset.x as f32,
|
offset.x as f32,
|
||||||
offset.y as f32,
|
offset.y as f32,
|
||||||
(scale * layout.dimensions.x) as f32,
|
(scale * layout.size.x) as f32,
|
||||||
(scale * layout.dimensions.y) as f32,
|
(scale * layout.size.y) as f32,
|
||||||
&Source::Solid(WHITE),
|
&Source::Solid(WHITE),
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
);
|
);
|
||||||
@ -186,7 +186,7 @@ fn render(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset.y += scale * layout.dimensions.y + pad;
|
offset.y += scale * layout.size.y + pad;
|
||||||
}
|
}
|
||||||
|
|
||||||
surface
|
surface
|
||||||
|
Loading…
x
Reference in New Issue
Block a user