Unbounded pages 🌌
@ -95,6 +95,7 @@ impl EvalContext {
|
|||||||
pub fn start_page_group(&mut self, softness: Softness) {
|
pub fn start_page_group(&mut self, softness: Softness) {
|
||||||
self.start_group(PageGroup {
|
self.start_group(PageGroup {
|
||||||
size: self.state.page.size,
|
size: self.state.page.size,
|
||||||
|
expand: self.state.page.expand,
|
||||||
padding: self.state.page.margins(),
|
padding: self.state.page.margins(),
|
||||||
dirs: self.state.dirs,
|
dirs: self.state.dirs,
|
||||||
align: self.state.align,
|
align: self.state.align,
|
||||||
@ -124,7 +125,7 @@ impl EvalContext {
|
|||||||
child: NodeStack {
|
child: NodeStack {
|
||||||
dirs: group.dirs,
|
dirs: group.dirs,
|
||||||
align: group.align,
|
align: group.align,
|
||||||
expansion: Gen::uniform(Expansion::Fill),
|
expand: group.expand,
|
||||||
children,
|
children,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
@ -281,6 +282,7 @@ pub enum Softness {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct PageGroup {
|
struct PageGroup {
|
||||||
size: Size,
|
size: Size,
|
||||||
|
expand: Spec<Expansion>,
|
||||||
padding: Sides<Linear>,
|
padding: Sides<Linear>,
|
||||||
dirs: LayoutDirs,
|
dirs: LayoutDirs,
|
||||||
align: ChildAlign,
|
align: ChildAlign,
|
||||||
|
@ -19,7 +19,7 @@ use std::rc::Rc;
|
|||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::diag::Pass;
|
use crate::diag::Pass;
|
||||||
use crate::env::SharedEnv;
|
use crate::env::SharedEnv;
|
||||||
use crate::geom::{Angle, Gen, Length, Relative};
|
use crate::geom::{Angle, Length, Relative, Spec};
|
||||||
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
|
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ impl Eval for Spanned<&NodeRaw> {
|
|||||||
ctx.push(NodeStack {
|
ctx.push(NodeStack {
|
||||||
dirs: ctx.state.dirs,
|
dirs: ctx.state.dirs,
|
||||||
align: ctx.state.align,
|
align: ctx.state.align,
|
||||||
expansion: Gen::uniform(Expansion::Fit),
|
expand: Spec::uniform(Expansion::Fit),
|
||||||
children,
|
children,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -4,8 +4,9 @@ use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, Font
|
|||||||
|
|
||||||
use super::Scope;
|
use super::Scope;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size,
|
Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec,
|
||||||
};
|
};
|
||||||
|
use crate::layout::Expansion;
|
||||||
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||||
|
|
||||||
/// The evaluation state.
|
/// The evaluation state.
|
||||||
@ -45,6 +46,8 @@ pub struct StatePage {
|
|||||||
pub class: PaperClass,
|
pub class: PaperClass,
|
||||||
/// The width and height of the page.
|
/// The width and height of the page.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
|
/// Whether the expand the pages to the `size` or to fit the content.
|
||||||
|
pub expand: Spec<Expansion>,
|
||||||
/// The amount of white space in the order [left, top, right, bottom]. If a
|
/// The amount of white space in the order [left, top, right, bottom]. If a
|
||||||
/// side is set to `None`, the default for the paper class is used.
|
/// side is set to `None`, the default for the paper class is used.
|
||||||
pub margins: Sides<Option<Linear>>,
|
pub margins: Sides<Option<Linear>>,
|
||||||
@ -56,6 +59,7 @@ impl StatePage {
|
|||||||
Self {
|
Self {
|
||||||
class: paper.class,
|
class: paper.class,
|
||||||
size: paper.size(),
|
size: paper.size(),
|
||||||
|
expand: Spec::uniform(Expansion::Fill),
|
||||||
margins: Sides::uniform(None),
|
margins: Sides::uniform(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,16 @@ impl Length {
|
|||||||
Self { raw: self.raw.max(other.raw) }
|
Self { raw: self.raw.max(other.raw) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether the length is finite.
|
||||||
|
pub fn is_finite(self) -> bool {
|
||||||
|
self.raw.is_finite()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the length is infinite.
|
||||||
|
pub fn is_infinite(self) -> bool {
|
||||||
|
self.raw.is_infinite()
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the length is `NaN`.
|
/// Whether the length is `NaN`.
|
||||||
pub fn is_nan(self) -> bool {
|
pub fn is_nan(self) -> bool {
|
||||||
self.raw.is_nan()
|
self.raw.is_nan()
|
||||||
|
@ -26,8 +26,13 @@ impl Relative {
|
|||||||
|
|
||||||
/// Resolve this relative to the given `length`.
|
/// Resolve this relative to the given `length`.
|
||||||
pub fn resolve(self, length: Length) -> Length {
|
pub fn resolve(self, length: Length) -> Length {
|
||||||
|
// Zero wins over infinity.
|
||||||
|
if self.0 == 0.0 {
|
||||||
|
Length::ZERO
|
||||||
|
} else {
|
||||||
self.get() * length
|
self.get() * length
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Relative {
|
impl Display for Relative {
|
||||||
|
@ -31,6 +31,16 @@ impl Size {
|
|||||||
self.width >= other.width && self.height >= other.height
|
self.width >= other.width && self.height >= other.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether both components are finite.
|
||||||
|
pub fn is_finite(self) -> bool {
|
||||||
|
self.width.is_finite() && self.height.is_finite()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether any of the two components is infinite.
|
||||||
|
pub fn is_infinite(self) -> bool {
|
||||||
|
self.width.is_infinite() || self.height.is_infinite()
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether any of the two components is `NaN`.
|
/// Whether any of the two components is `NaN`.
|
||||||
pub fn is_nan(self) -> bool {
|
pub fn is_nan(self) -> bool {
|
||||||
self.width.is_nan() || self.height.is_nan()
|
self.width.is_nan() || self.height.is_nan()
|
||||||
|
@ -14,10 +14,10 @@ pub struct NodeFixed {
|
|||||||
|
|
||||||
impl Layout for NodeFixed {
|
impl Layout for NodeFixed {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||||
let Area { rem, full } = areas.current;
|
let Areas { current, full, .. } = areas;
|
||||||
let size = Size::new(
|
let size = Size::new(
|
||||||
self.width.map(|w| w.resolve(full.width)).unwrap_or(rem.width),
|
self.width.map(|w| w.resolve(full.width)).unwrap_or(current.width),
|
||||||
self.height.map(|h| h.resolve(full.height)).unwrap_or(rem.height),
|
self.height.map(|h| h.resolve(full.height)).unwrap_or(current.height),
|
||||||
);
|
);
|
||||||
|
|
||||||
let areas = Areas::once(size);
|
let areas = Areas::once(size);
|
||||||
|
@ -71,27 +71,13 @@ pub struct LayoutContext {
|
|||||||
pub env: SharedEnv,
|
pub env: SharedEnv,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An area into which content can be laid out.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct Area {
|
|
||||||
/// The remaining size of this area.
|
|
||||||
pub rem: Size,
|
|
||||||
/// The full size this area once had (used for relative sizing).
|
|
||||||
pub full: Size,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Area {
|
|
||||||
/// Create a new area.
|
|
||||||
pub fn new(size: Size) -> Self {
|
|
||||||
Self { rem: size, full: size }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A collection of areas to layout into.
|
/// A collection of areas to layout into.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Areas {
|
pub struct Areas {
|
||||||
/// The current area.
|
/// The remaining size of the current area.
|
||||||
pub current: Area,
|
pub current: Size,
|
||||||
|
/// The full size the current area once had (used for relative sizing).
|
||||||
|
pub full: Size,
|
||||||
/// A stack of followup areas (the next area is the last element).
|
/// A stack of followup areas (the next area is the last element).
|
||||||
pub backlog: Vec<Size>,
|
pub backlog: Vec<Size>,
|
||||||
/// The final area that is repeated when the backlog is empty.
|
/// The final area that is repeated when the backlog is empty.
|
||||||
@ -102,7 +88,8 @@ impl Areas {
|
|||||||
/// Create a new length-1 sequence of areas with just one `area`.
|
/// Create a new length-1 sequence of areas with just one `area`.
|
||||||
pub fn once(size: Size) -> Self {
|
pub fn once(size: Size) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: Area::new(size),
|
current: size,
|
||||||
|
full: size,
|
||||||
backlog: vec![],
|
backlog: vec![],
|
||||||
last: None,
|
last: None,
|
||||||
}
|
}
|
||||||
@ -111,7 +98,8 @@ impl Areas {
|
|||||||
/// Create a new sequence of areas that repeats `area` indefinitely.
|
/// Create a new sequence of areas that repeats `area` indefinitely.
|
||||||
pub fn repeat(size: Size) -> Self {
|
pub fn repeat(size: Size) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: Area::new(size),
|
current: size,
|
||||||
|
full: size,
|
||||||
backlog: vec![],
|
backlog: vec![],
|
||||||
last: Some(size),
|
last: Some(size),
|
||||||
}
|
}
|
||||||
@ -120,7 +108,8 @@ impl Areas {
|
|||||||
/// Advance to the next area if there is any.
|
/// Advance to the next area if there is any.
|
||||||
pub fn next(&mut self) {
|
pub fn next(&mut self) {
|
||||||
if let Some(size) = self.backlog.pop().or(self.last) {
|
if let Some(size) = self.backlog.pop().or(self.last) {
|
||||||
self.current = Area::new(size);
|
self.current = size;
|
||||||
|
self.full = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,11 +119,32 @@ impl Areas {
|
|||||||
pub fn in_full_last(&self) -> bool {
|
pub fn in_full_last(&self) -> bool {
|
||||||
self.backlog.is_empty()
|
self.backlog.is_empty()
|
||||||
&& self.last.map_or(true, |size| {
|
&& self.last.map_or(true, |size| {
|
||||||
self.current.rem.is_nan() || size.is_nan() || self.current.rem == size
|
self.current.is_nan() || size.is_nan() || self.current == size
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to expand or shrink a node along an axis.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Expansion {
|
||||||
|
/// Fit the content.
|
||||||
|
Fit,
|
||||||
|
/// Fill the available space.
|
||||||
|
Fill,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Expansion {
|
||||||
|
/// Resolve the expansion to either the `fit` or `fill` length.
|
||||||
|
///
|
||||||
|
/// Prefers `fit` if `fill` is infinite.
|
||||||
|
pub fn resolve(self, fit: Length, fill: Length) -> Length {
|
||||||
|
match self {
|
||||||
|
Self::Fill if fill.is_finite() => fill,
|
||||||
|
_ => fit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The result of layouting a node.
|
/// The result of layouting a node.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Layouted {
|
pub enum Layouted {
|
||||||
@ -158,15 +168,6 @@ impl Layouted {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to expand or shrink a node along an axis.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Expansion {
|
|
||||||
/// Fit the content.
|
|
||||||
Fit,
|
|
||||||
/// Fill the available space.
|
|
||||||
Fill,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A finished layout with elements at fixed positions.
|
/// A finished layout with elements at fixed positions.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Frame {
|
pub struct Frame {
|
||||||
|
@ -39,10 +39,8 @@ impl From<NodePad> for NodeAny {
|
|||||||
fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
fn shrink(areas: &Areas, padding: Sides<Linear>) -> Areas {
|
||||||
let shrink = |size| size - padding.resolve(size).size();
|
let shrink = |size| size - padding.resolve(size).size();
|
||||||
Areas {
|
Areas {
|
||||||
current: Area {
|
current: shrink(areas.current),
|
||||||
rem: shrink(areas.current.rem),
|
full: shrink(areas.full),
|
||||||
full: shrink(areas.current.full),
|
|
||||||
},
|
|
||||||
backlog: areas.backlog.iter().copied().map(shrink).collect(),
|
backlog: areas.backlog.iter().copied().map(shrink).collect(),
|
||||||
last: areas.last.map(shrink),
|
last: areas.last.map(shrink),
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn push_spacing(&mut self, amount: Length) {
|
fn push_spacing(&mut self, amount: Length) {
|
||||||
let cross_max = self.areas.current.rem.get(self.cross);
|
let cross_max = self.areas.current.get(self.cross);
|
||||||
self.run_size.cross = (self.run_size.cross + amount).min(cross_max);
|
self.run_size.cross = (self.run_size.cross + amount).min(cross_max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fits = {
|
let fits = {
|
||||||
let mut usable = self.areas.current.rem;
|
let mut usable = self.areas.current;
|
||||||
*usable.get_mut(self.cross) -= self.run_size.cross;
|
*usable.get_mut(self.cross) -= self.run_size.cross;
|
||||||
usable.fits(frame.size)
|
usable.fits(frame.size)
|
||||||
};
|
};
|
||||||
@ -92,7 +92,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
if !fits {
|
if !fits {
|
||||||
self.finish_run();
|
self.finish_run();
|
||||||
|
|
||||||
while !self.areas.current.rem.fits(frame.size) {
|
while !self.areas.current.fits(frame.size) {
|
||||||
if self.areas.in_full_last() {
|
if self.areas.in_full_last() {
|
||||||
// TODO: Diagnose once the necessary spans exist.
|
// TODO: Diagnose once the necessary spans exist.
|
||||||
let _ = warning!("cannot fit frame into any area");
|
let _ = warning!("cannot fit frame into any area");
|
||||||
@ -112,10 +112,15 @@ impl<'a> ParLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn finish_run(&mut self) {
|
fn finish_run(&mut self) {
|
||||||
let full_size = Gen::new(self.run_size.main, match self.par.cross_expansion {
|
let full_size = {
|
||||||
Expansion::Fill => self.areas.current.full.get(self.cross),
|
let full = self.areas.full.switch(self.dirs);
|
||||||
Expansion::Fit => self.run_size.cross,
|
Gen::new(
|
||||||
});
|
self.run_size.main,
|
||||||
|
self.par
|
||||||
|
.cross_expansion
|
||||||
|
.resolve(self.run_size.cross.min(full.cross), full.cross),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let mut output = Frame::new(full_size.switch(self.dirs).to_size());
|
let mut output = Frame::new(full_size.switch(self.dirs).to_size());
|
||||||
|
|
||||||
@ -139,7 +144,7 @@ impl<'a> ParLayouter<'a> {
|
|||||||
self.lines.push((self.lines_size.main, output, self.run_ruler));
|
self.lines.push((self.lines_size.main, output, self.run_ruler));
|
||||||
|
|
||||||
let main_offset = full_size.main + self.par.line_spacing;
|
let main_offset = full_size.main + self.par.line_spacing;
|
||||||
*self.areas.current.rem.get_mut(self.main) -= main_offset;
|
*self.areas.current.get_mut(self.main) -= main_offset;
|
||||||
self.lines_size.main += main_offset;
|
self.lines_size.main += main_offset;
|
||||||
self.lines_size.cross = self.lines_size.cross.max(full_size.cross);
|
self.lines_size.cross = self.lines_size.cross.max(full_size.cross);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ pub struct NodeStack {
|
|||||||
/// How to align this stack in _its_ parent.
|
/// How to align this stack in _its_ parent.
|
||||||
pub align: ChildAlign,
|
pub align: ChildAlign,
|
||||||
/// Whether to expand the axes to fill the area or to fit the content.
|
/// Whether to expand the axes to fill the area or to fit the content.
|
||||||
pub expansion: Gen<Expansion>,
|
pub expand: Spec<Expansion>,
|
||||||
/// The nodes to be stacked.
|
/// The nodes to be stacked.
|
||||||
pub children: Vec<Node>,
|
pub children: Vec<Node>,
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn push_spacing(&mut self, amount: Length) {
|
fn push_spacing(&mut self, amount: Length) {
|
||||||
let main_rest = self.areas.current.rem.get_mut(self.main);
|
let main_rest = self.areas.current.get_mut(self.main);
|
||||||
let capped = amount.min(*main_rest);
|
let capped = amount.min(*main_rest);
|
||||||
*main_rest -= capped;
|
*main_rest -= capped;
|
||||||
self.used.main += capped;
|
self.used.main += capped;
|
||||||
@ -77,7 +77,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
self.finish_area();
|
self.finish_area();
|
||||||
}
|
}
|
||||||
|
|
||||||
while !self.areas.current.rem.fits(frame.size) {
|
while !self.areas.current.fits(frame.size) {
|
||||||
if self.areas.in_full_last() {
|
if self.areas.in_full_last() {
|
||||||
// TODO: Diagnose once the necessary spans exist.
|
// TODO: Diagnose once the necessary spans exist.
|
||||||
let _ = warning!("cannot fit frame into any area");
|
let _ = warning!("cannot fit frame into any area");
|
||||||
@ -90,7 +90,7 @@ impl<'a> StackLayouter<'a> {
|
|||||||
let size = frame.size.switch(self.dirs);
|
let size = frame.size.switch(self.dirs);
|
||||||
self.frames.push((self.used.main, frame, align));
|
self.frames.push((self.used.main, frame, align));
|
||||||
|
|
||||||
*self.areas.current.rem.get_mut(self.main) -= size.main;
|
*self.areas.current.get_mut(self.main) -= size.main;
|
||||||
self.used.main += size.main;
|
self.used.main += size.main;
|
||||||
self.used.cross = self.used.cross.max(size.cross);
|
self.used.cross = self.used.cross.max(size.cross);
|
||||||
self.ruler = align.main;
|
self.ruler = align.main;
|
||||||
@ -98,16 +98,11 @@ impl<'a> StackLayouter<'a> {
|
|||||||
|
|
||||||
fn finish_area(&mut self) {
|
fn finish_area(&mut self) {
|
||||||
let full_size = {
|
let full_size = {
|
||||||
let full = self.areas.current.full.switch(self.dirs);
|
let expand = self.stack.expand.switch(self.dirs);
|
||||||
|
let full = self.areas.full.switch(self.dirs);
|
||||||
Gen::new(
|
Gen::new(
|
||||||
match self.stack.expansion.main {
|
expand.main.resolve(self.used.main.min(full.main), full.main),
|
||||||
Expansion::Fill => full.main,
|
expand.cross.resolve(self.used.cross.min(full.cross), full.cross),
|
||||||
Expansion::Fit => self.used.main.min(full.main),
|
|
||||||
},
|
|
||||||
match self.stack.expansion.cross {
|
|
||||||
Expansion::Fill => full.cross,
|
|
||||||
Expansion::Fit => self.used.cross.min(full.cross),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -55,8 +55,11 @@ struct NodeImage {
|
|||||||
|
|
||||||
impl Layout for NodeImage {
|
impl Layout for NodeImage {
|
||||||
fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
|
fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||||
let Area { rem, full } = areas.current;
|
let Areas { current, full, .. } = areas;
|
||||||
let pixel_ratio = (self.dimensions.0 as f64) / (self.dimensions.1 as f64);
|
|
||||||
|
let pixel_width = self.dimensions.0 as f64;
|
||||||
|
let pixel_height = self.dimensions.1 as f64;
|
||||||
|
let pixel_ratio = pixel_width / pixel_height;
|
||||||
|
|
||||||
let width = self.width.map(|w| w.resolve(full.width));
|
let width = self.width.map(|w| w.resolve(full.width));
|
||||||
let height = self.height.map(|w| w.resolve(full.height));
|
let height = self.height.map(|w| w.resolve(full.height));
|
||||||
@ -66,12 +69,15 @@ impl Layout for NodeImage {
|
|||||||
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
(Some(width), None) => Size::new(width, width / pixel_ratio),
|
||||||
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
(None, Some(height)) => Size::new(height * pixel_ratio, height),
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
let ratio = rem.width / rem.height;
|
let ratio = current.width / current.height;
|
||||||
if ratio < pixel_ratio {
|
if ratio < pixel_ratio && current.width.is_finite() {
|
||||||
Size::new(rem.width, rem.width / pixel_ratio)
|
Size::new(current.width, current.width / pixel_ratio)
|
||||||
} else {
|
} else if current.height.is_finite() {
|
||||||
// TODO: Fix issue with line spacing.
|
// TODO: Fix issue with line spacing.
|
||||||
Size::new(rem.height * pixel_ratio, rem.height)
|
Size::new(current.height * pixel_ratio, current.height)
|
||||||
|
} else {
|
||||||
|
// Totally unbounded area, we have to make up something.
|
||||||
|
Size::new(Length::pt(pixel_width), Length::pt(pixel_height))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -197,13 +197,12 @@ pub fn box_(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
let children = ctx.end_content_group();
|
let children = ctx.end_content_group();
|
||||||
|
|
||||||
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
|
let fill_if = |c| if c { Expansion::Fill } else { Expansion::Fit };
|
||||||
let expansion =
|
let expand = Spec::new(fill_if(width.is_some()), fill_if(height.is_some()));
|
||||||
Spec::new(fill_if(width.is_some()), fill_if(height.is_some())).switch(dirs);
|
|
||||||
|
|
||||||
ctx.push(NodeFixed {
|
ctx.push(NodeFixed {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
child: NodeStack { dirs, align, expansion, children }.into(),
|
child: NodeStack { dirs, align, expand, children }.into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.state = snapshot;
|
ctx.state = snapshot;
|
||||||
@ -271,6 +270,7 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
if let Some(paper) = Paper::from_name(&name.v) {
|
if let Some(paper) = Paper::from_name(&name.v) {
|
||||||
ctx.state.page.class = paper.class;
|
ctx.state.page.class = paper.class;
|
||||||
ctx.state.page.size = paper.size();
|
ctx.state.page.size = paper.size();
|
||||||
|
ctx.state.page.expand = Spec::uniform(Expansion::Fill);
|
||||||
} else {
|
} else {
|
||||||
ctx.diag(error!(name.span, "invalid paper name"));
|
ctx.diag(error!(name.span, "invalid paper name"));
|
||||||
}
|
}
|
||||||
@ -279,11 +279,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
if let Some(width) = args.get(ctx, "width") {
|
if let Some(width) = args.get(ctx, "width") {
|
||||||
ctx.state.page.class = PaperClass::Custom;
|
ctx.state.page.class = PaperClass::Custom;
|
||||||
ctx.state.page.size.width = width;
|
ctx.state.page.size.width = width;
|
||||||
|
ctx.state.page.expand.horizontal = Expansion::Fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(height) = args.get(ctx, "height") {
|
if let Some(height) = args.get(ctx, "height") {
|
||||||
ctx.state.page.class = PaperClass::Custom;
|
ctx.state.page.class = PaperClass::Custom;
|
||||||
ctx.state.page.size.height = height;
|
ctx.state.page.size.height = height;
|
||||||
|
ctx.state.page.expand.vertical = Expansion::Fill;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(margins) = args.get(ctx, "margins") {
|
if let Some(margins) = args.get(ctx, "margins") {
|
||||||
@ -307,8 +309,9 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if args.get(ctx, "flip").unwrap_or(false) {
|
if args.get(ctx, "flip").unwrap_or(false) {
|
||||||
let size = &mut ctx.state.page.size;
|
let page = &mut ctx.state.page;
|
||||||
std::mem::swap(&mut size.width, &mut size.height);
|
std::mem::swap(&mut page.size.width, &mut page.size.height);
|
||||||
|
std::mem::swap(&mut page.expand.horizontal, &mut page.expand.vertical);
|
||||||
}
|
}
|
||||||
|
|
||||||
let main = args.get(ctx, "main-dir");
|
let main = args.get(ctx, "main-dir");
|
||||||
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 120 B After Width: | Height: | Size: 94 B |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 295 KiB After Width: | Height: | Size: 215 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 821 B |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 7.7 KiB |
@ -12,13 +12,13 @@ Add [h 10pt] [h 10pt] up
|
|||||||
// Relative to font size.
|
// Relative to font size.
|
||||||
Relative [h 100%] spacing
|
Relative [h 100%] spacing
|
||||||
|
|
||||||
|
// Missing spacing.
|
||||||
|
// Error: 1:11-1:11 missing argument: spacing
|
||||||
|
Totally [h] ignored
|
||||||
|
|
||||||
// Swapped axes.
|
// Swapped axes.
|
||||||
[page main-dir: rtl, cross-dir: ttb][
|
[page main-dir: rtl, cross-dir: ttb, height: 80pt][
|
||||||
1 [h 1cm] 2
|
1 [h 1cm] 2
|
||||||
|
|
||||||
3 [v 1cm] 4 [v -1cm] 5
|
3 [v 1cm] 4 [v -1cm] 5
|
||||||
]
|
]
|
||||||
|
|
||||||
// Missing spacing.
|
|
||||||
// Error: 1:11-1:11 missing argument: spacing
|
|
||||||
Totally [h] ignored
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
[image "res/rhino.png"]
|
[image "res/rhino.png"]
|
||||||
|
|
||||||
// Fit to height of page.
|
// Fit to height of page.
|
||||||
[page width: 270pt][
|
[page height: 40pt][
|
||||||
[image "res/rhino.png"]
|
[image "res/rhino.png"]
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
// Make sure the bounding-box of the image is correct.
|
// Make sure the bounding-box of the image is correct.
|
||||||
[align bottom, right][
|
[align bottom, right][
|
||||||
[image "res/tiger.jpg"]
|
[image "res/tiger.jpg", width: 60pt]
|
||||||
]
|
]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -1,28 +1,36 @@
|
|||||||
// Test configuring page sizes and margins.
|
// Test configuring page sizes and margins.
|
||||||
|
|
||||||
// Set width.
|
// Set width and height.
|
||||||
[page width: 50pt][High]
|
[page width: 120pt, height: 120pt]
|
||||||
|
[page width: 40pt][High]
|
||||||
// Set height.
|
[page height: 40pt][Wide]
|
||||||
[page height: 50pt][Wide]
|
|
||||||
|
|
||||||
// Set all margins at once.
|
// Set all margins at once.
|
||||||
[page margins: 40pt][
|
[page margins: 30pt][
|
||||||
[align top, left][TL]
|
[align top, left][TL]
|
||||||
[align bottom, right][BR]
|
[align bottom, right][BR]
|
||||||
]
|
]
|
||||||
|
|
||||||
// Set individual margins.
|
// Set individual margins.
|
||||||
|
[page height: 40pt]
|
||||||
[page left: 0pt | align left][Left]
|
[page left: 0pt | align left][Left]
|
||||||
[page right: 0pt | align right][Right]
|
[page right: 0pt | align right][Right]
|
||||||
[page top: 0pt | align top][Top]
|
[page top: 0pt | align top][Top]
|
||||||
[page bottom: 0pt | align bottom][Bottom]
|
[page bottom: 0pt | align bottom][Bottom]
|
||||||
|
|
||||||
// Ensure that specific margins override general margins.
|
// Ensure that specific margins override general margins.
|
||||||
[page margins: 0pt, left: 40pt][Overriden]
|
[page margins: 0pt, left: 20pt][Overriden]
|
||||||
|
|
||||||
// Flip the page.
|
---
|
||||||
[page "a10", flip: true][Flipped]
|
// Test flipping.
|
||||||
|
|
||||||
|
// Flipped predefined paper.
|
||||||
|
[page "a11", flip: true][Flipped A11]
|
||||||
|
|
||||||
|
// Flipped custom page size.
|
||||||
|
[page width: 40pt, height: 120pt]
|
||||||
|
[page flip: true]
|
||||||
|
Wide
|
||||||
|
|
||||||
---
|
---
|
||||||
// Test a combination of pages with bodies and normal content.
|
// Test a combination of pages with bodies and normal content.
|
||||||
@ -40,7 +48,7 @@ Sixth
|
|||||||
---
|
---
|
||||||
// Test changing the layouting directions of pages.
|
// Test changing the layouting directions of pages.
|
||||||
|
|
||||||
[page main-dir: btt, cross-dir: rtl]
|
[page height: 50pt, main-dir: btt, cross-dir: rtl]
|
||||||
|
|
||||||
Right to left!
|
Right to left!
|
||||||
|
|
||||||
|
@ -8,15 +8,12 @@
|
|||||||
####### Seven
|
####### Seven
|
||||||
|
|
||||||
---
|
---
|
||||||
// Is a heading.
|
// Heading vs. no heading.
|
||||||
|
|
||||||
/**/ # Heading
|
/**/ # Heading
|
||||||
{[## Heading]}
|
{[## Heading]}
|
||||||
[box][### Heading]
|
[box][### Heading]
|
||||||
|
|
||||||
---
|
|
||||||
// Is no heading.
|
|
||||||
|
|
||||||
\# No heading
|
\# No heading
|
||||||
|
|
||||||
Text with # hashtag
|
Text with # hashtag
|
||||||
|
@ -18,8 +18,8 @@ use typst::env::{Env, ImageResource, ResourceLoader, SharedEnv};
|
|||||||
use typst::eval::{Args, EvalContext, State, Value, ValueFunc};
|
use typst::eval::{Args, EvalContext, State, Value, ValueFunc};
|
||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::FontLoader;
|
use typst::font::FontLoader;
|
||||||
use typst::geom::{Length, Point, Sides, Size};
|
use typst::geom::{Length, Point, Sides, Size, Spec};
|
||||||
use typst::layout::{Element, Frame, Image};
|
use typst::layout::{Element, Expansion, Frame, Image};
|
||||||
use typst::parse::{LineMap, Scanner};
|
use typst::parse::{LineMap, Scanner};
|
||||||
use typst::shaping::Shaped;
|
use typst::shaping::Shaped;
|
||||||
use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan};
|
use typst::syntax::{Location, Pos, SpanVec, Spanned, WithSpan};
|
||||||
@ -153,12 +153,12 @@ fn test(
|
|||||||
|
|
||||||
let env = env.borrow();
|
let env = env.borrow();
|
||||||
if !frames.is_empty() {
|
if !frames.is_empty() {
|
||||||
let canvas = draw(&frames, &env, 2.0);
|
|
||||||
canvas.pixmap.save_png(png_path).unwrap();
|
|
||||||
|
|
||||||
let pdf_data = pdf::export(&frames, &env);
|
let pdf_data = pdf::export(&frames, &env);
|
||||||
fs::write(pdf_path, pdf_data).unwrap();
|
fs::write(pdf_path, pdf_data).unwrap();
|
||||||
|
|
||||||
|
let canvas = draw(&frames, &env, 2.0);
|
||||||
|
canvas.pixmap.save_png(png_path).unwrap();
|
||||||
|
|
||||||
if let Some(ref_path) = ref_path {
|
if let Some(ref_path) = ref_path {
|
||||||
if let Ok(ref_pixmap) = Pixmap::load_png(ref_path) {
|
if let Ok(ref_pixmap) = Pixmap::load_png(ref_path) {
|
||||||
if canvas.pixmap != ref_pixmap {
|
if canvas.pixmap != ref_pixmap {
|
||||||
@ -184,7 +184,11 @@ fn test_part(i: usize, src: &str, env: &SharedEnv) -> (bool, Vec<Frame>) {
|
|||||||
let (compare_ref, ref_diags) = parse_metadata(src, &map);
|
let (compare_ref, ref_diags) = parse_metadata(src, &map);
|
||||||
|
|
||||||
let mut state = State::default();
|
let mut state = State::default();
|
||||||
state.page.size = Size::uniform(Length::pt(120.0));
|
|
||||||
|
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||||
|
// large and fit them to match their content.
|
||||||
|
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
|
||||||
|
state.page.expand = Spec::new(Expansion::Fill, Expansion::Fit);
|
||||||
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
|
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
|
||||||
|
|
||||||
pub fn dump(_: &mut EvalContext, args: &mut Args) -> Value {
|
pub fn dump(_: &mut EvalContext, args: &mut Args) -> Value {
|
||||||
@ -283,6 +287,10 @@ fn draw(frames: &[Frame], env: &Env, pixel_per_pt: f32) -> Canvas {
|
|||||||
|
|
||||||
let pixel_width = (pixel_per_pt * width.to_pt() as f32) as u32;
|
let pixel_width = (pixel_per_pt * width.to_pt() as f32) as u32;
|
||||||
let pixel_height = (pixel_per_pt * height.to_pt() as f32) as u32;
|
let pixel_height = (pixel_per_pt * height.to_pt() as f32) as u32;
|
||||||
|
if pixel_width > 4000 || pixel_height > 4000 {
|
||||||
|
panic!("overlarge image: {} by {}", pixel_width, pixel_height);
|
||||||
|
}
|
||||||
|
|
||||||
let mut canvas = Canvas::new(pixel_width, pixel_height).unwrap();
|
let mut canvas = Canvas::new(pixel_width, pixel_height).unwrap();
|
||||||
canvas.scale(pixel_per_pt, pixel_per_pt);
|
canvas.scale(pixel_per_pt, pixel_per_pt);
|
||||||
canvas.pixmap.fill(Color::BLACK);
|
canvas.pixmap.fill(Color::BLACK);
|
||||||
|