Simple line layouter 🧾

This commit is contained in:
Laurenz 2019-12-30 22:28:56 +01:00
parent b8620121a6
commit 269f069a4d
14 changed files with 572 additions and 463 deletions

View File

@ -117,6 +117,7 @@ macro_rules! function {
}
/// Parse the body of a function.
///
/// - If the function does not expect a body, use `parse!(forbidden: body)`.
/// - If the function can have a body, use `parse!(optional: body, ctx)`.
/// - If the function must have a body, use `parse!(expected: body, ctx)`.
@ -146,7 +147,7 @@ macro_rules! parse {
}
/// Early-return with a formatted typesetting error or construct an error
/// expression without returning when prefixed with `@`.
/// expression.
#[macro_export]
macro_rules! error {
(@unexpected_argument) => (error!(@"unexpected argument"));

View File

@ -104,7 +104,6 @@ pub enum Command<'a> {
AddSpacing(Size, SpacingKind, GenericAxis),
FinishLine,
FinishRun,
FinishSpace,
BreakParagraph,
BreakPage,

View File

@ -1,198 +0,0 @@
use super::*;
/// The flex layouter first arranges boxes along a primary and if necessary also
/// along a secondary axis.
#[derive(Debug, Clone)]
pub struct FlexLayouter {
axes: LayoutAxes,
flex_spacing: Size,
stack: StackLayouter,
units: Vec<FlexUnit>,
line: FlexLine,
part: PartialLine,
}
#[derive(Debug, Clone)]
enum FlexUnit {
Boxed(Layout),
Space(Size, SpacingKind),
SetAxes(LayoutAxes),
Break,
}
#[derive(Debug, Clone)]
struct FlexLine {
usable: Size,
actions: LayoutActions,
combined_dimensions: Size2D,
}
impl FlexLine {
fn new(usable: Size) -> FlexLine {
FlexLine {
usable,
actions: LayoutActions::new(),
combined_dimensions: Size2D::ZERO,
}
}
}
#[derive(Debug, Clone)]
struct PartialLine {
usable: Size,
content: Vec<(Size, Layout)>,
dimensions: Size2D,
space: LastSpacing,
}
impl PartialLine {
fn new(usable: Size) -> PartialLine {
PartialLine {
usable,
content: vec![],
dimensions: Size2D::ZERO,
space: LastSpacing::Hard,
}
}
}
/// The context for flex layouting.
///
/// See [`LayoutContext`] for details about the fields.
#[derive(Debug, Clone)]
pub struct FlexContext {
pub spaces: LayoutSpaces,
pub axes: LayoutAxes,
pub alignment: LayoutAlignment,
pub flex_spacing: Size,
pub repeat: bool,
pub debug: bool,
}
impl FlexLayouter {
/// Create a new flex layouter.
pub fn new(ctx: FlexContext) -> FlexLayouter {
let stack = StackLayouter::new(StackContext {
spaces: ctx.spaces,
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug,
});
let usable = stack.primary_usable();
FlexLayouter {
axes: ctx.axes,
flex_spacing: ctx.flex_spacing,
stack,
units: vec![],
line: FlexLine::new(usable),
part: PartialLine::new(usable),
}
}
pub fn add(&mut self, layout: Layout) {
self.units.push(FlexUnit::Boxed(layout));
}
pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts {
self.add(layout);
}
}
pub fn add_break(&mut self) {
self.units.push(FlexUnit::Break);
}
pub fn add_primary_space(&mut self, space: Size, kind: SpacingKind) {
self.units.push(FlexUnit::Space(space, kind))
}
pub fn add_secondary_space(&mut self, space: Size, kind: SpacingKind) -> LayoutResult<()> {
if !self.run_is_empty() {
self.finish_run()?;
}
Ok(self.stack.add_spacing(space, kind))
}
pub fn set_axes(&mut self, axes: LayoutAxes) {
self.units.push(FlexUnit::SetAxes(axes));
}
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.run_is_empty() && self.stack.space_is_empty() {
self.stack.set_spaces(spaces, true);
self.start_line();
} else {
self.stack.set_spaces(spaces, false);
}
}
pub fn remaining(&self) -> LayoutSpaces {
self.stack.remaining()
}
pub fn run_is_empty(&self) -> bool {
!self.units.iter().any(|unit| matches!(unit, FlexUnit::Boxed(_)))
}
pub fn run_last_is_space(&self) -> bool {
matches!(self.units.last(), Some(FlexUnit::Space(_, _)))
}
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
self.finish_space(false)?;
self.stack.finish()
}
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
if !self.run_is_empty() {
self.finish_run()?;
}
self.stack.finish_space(hard)?;
Ok(self.start_line())
}
pub fn finish_run(&mut self) -> LayoutResult<Size2D> {
let units = std::mem::replace(&mut self.units, vec![]);
for unit in units {
match unit {
FlexUnit::Boxed(boxed) => self.layout_box(boxed)?,
FlexUnit::Space(space, kind) => self.layout_space(space, kind),
FlexUnit::SetAxes(axes) => self.layout_set_axes(axes),
FlexUnit::Break => { self.finish_line()?; },
}
}
self.finish_line()
}
fn finish_line(&mut self) -> LayoutResult<Size2D> {
unimplemented!()
}
fn start_line(&mut self) {
unimplemented!()
}
fn finish_partial_line(&mut self) {
unimplemented!()
}
fn layout_box(&mut self, _boxed: Layout) -> LayoutResult<()> {
unimplemented!()
}
fn layout_space(&mut self, _space: Size, _kind: SpacingKind) {
unimplemented!()
}
fn layout_set_axes(&mut self, _axes: LayoutAxes) {
unimplemented!()
}
}

283
src/layout/line.rs Normal file
View File

@ -0,0 +1,283 @@
use super::*;
/// The line layouter arranges boxes next to each other along a primary axis
/// and arranges the resulting lines using an underlying stack layouter.
#[derive(Debug, Clone)]
pub struct LineLayouter {
/// The context for layouting.
ctx: LineContext,
/// The underlying stack layouter.
stack: StackLayouter,
/// The currently written line.
run: LineRun,
}
/// The context for line layouting.
#[derive(Debug, Clone)]
pub struct LineContext {
/// The spaces to layout in.
pub spaces: LayoutSpaces,
/// The initial layouting axes, which can be updated by the
/// [`LineLayouter::set_axes`] method.
pub axes: LayoutAxes,
/// Which alignment to set on the resulting layout. This affects how it will
/// be positioned in a parent box.
pub alignment: LayoutAlignment,
/// Whether to have repeated spaces or to use only the first and only once.
pub repeat: bool,
/// Whether to output a command which renders a debugging box showing the
/// extent of the layout.
pub debug: bool,
/// The line spacing.
pub line_spacing: Size,
}
/// A simple line of boxes.
#[derive(Debug, Clone)]
struct LineRun {
/// The so-far accumulated layouts in the line.
layouts: Vec<(Size, Layout)>,
/// The width (primary size) and maximal height (secondary size) of the
/// line.
size: Size2D,
/// The alignment of all layouts in the line.
alignment: Option<LayoutAlignment>,
/// The remaining usable space if another differently aligned line run
/// already took up some space.
usable: Option<Size>,
/// A possibly cached soft spacing or spacing state.
last_spacing: LastSpacing,
}
impl LineLayouter {
/// Create a new line layouter.
pub fn new(ctx: LineContext) -> LineLayouter {
LineLayouter {
stack: StackLayouter::new(StackContext {
spaces: ctx.spaces.clone(),
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug,
}),
ctx,
run: LineRun::new(),
}
}
/// Add a layout to the run.
pub fn add(&mut self, layout: Layout) -> LayoutResult<()> {
let axes = self.ctx.axes;
if let Some(alignment) = self.run.alignment {
if layout.alignment.secondary != alignment.secondary {
if self.stack.is_fitting_alignment(layout.alignment) {
self.finish_line()?;
} else {
self.finish_space(true)?;
}
} else if layout.alignment.primary < alignment.primary {
self.finish_line()?;
} else if layout.alignment.primary > alignment.primary {
let mut rest_run = LineRun::new();
let usable = self.stack.usable().get_primary(axes);
rest_run.usable = Some(match layout.alignment.primary {
Alignment::Origin => unreachable!("origin > x"),
Alignment::Center => usable - 2 * self.run.size.x,
Alignment::End => usable - self.run.size.x,
});
rest_run.size.y = self.run.size.y;
self.finish_line()?;
self.stack.add_spacing(-rest_run.size.y, SpacingKind::Hard);
self.run = rest_run;
}
}
if let LastSpacing::Soft(spacing, _) = self.run.last_spacing {
self.add_primary_spacing(spacing, SpacingKind::Hard);
}
let size = layout.dimensions.generalized(axes);
while !self.usable().fits(size) {
if !self.line_is_empty() {
self.finish_line()?;
} else {
self.finish_space(true)?;
}
}
self.run.alignment = Some(layout.alignment);
self.run.layouts.push((self.run.size.x, layout));
self.run.size.x += size.x;
self.run.size.y.max_eq(size.y);
self.run.last_spacing = LastSpacing::None;
Ok(())
}
/// Add multiple layouts to the run.
///
/// This function simply calls `add` repeatedly for each layout.
pub fn add_multiple(&mut self, layouts: MultiLayout) -> LayoutResult<()> {
for layout in layouts {
self.add(layout)?;
}
Ok(())
}
/// The remaining usable size in the run.
fn usable(&self) -> Size2D {
// The base is the usable space per stack layouter.
let mut usable = self.stack.usable().generalized(self.ctx.axes);
// If this is a alignment-continuing line, we override the primary
// usable size.
if let Some(primary) = self.run.usable {
usable.x = primary;
}
usable.x -= self.run.size.x;
usable
}
/// Add primary spacing to the line.
pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) {
match kind {
// A hard space is simply an empty box.
SpacingKind::Hard => {
spacing.min_eq(self.usable().x);
self.run.size.x += spacing;
self.run.last_spacing = LastSpacing::Hard;
}
// A soft space is cached if it is not consumed by a hard space or
// previous soft space with higher level.
SpacingKind::Soft(level) => {
let consumes = match self.run.last_spacing {
LastSpacing::None => true,
LastSpacing::Soft(_, prev) if level < prev => true,
_ => false,
};
if consumes {
self.run.last_spacing = LastSpacing::Soft(spacing, level);
}
}
}
}
/// Finish the run and add secondary spacing to the underlying stack.
pub fn add_secondary_spacing(
&mut self,
mut spacing: Size,
kind: SpacingKind
) -> LayoutResult<()> {
self.finish_line_if_not_empty()?;
Ok(self.stack.add_spacing(spacing, kind))
}
/// Change the layouting axes used by this layouter.
pub fn set_axes(&mut self, axes: LayoutAxes) -> LayoutResult<()> {
self.finish_line_if_not_empty()?;
self.ctx.axes = axes;
Ok(self.stack.set_axes(axes))
}
/// Change the layouting spaces to use.
///
/// If `replace_empty` is true, the current space is replaced if there are
/// no boxes laid into it yet. Otherwise, only the followup spaces are
/// replaced.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
self.stack.set_spaces(spaces, replace_empty && self.line_is_empty());
}
/// Change the line spacing.
pub fn set_line_spacing(&mut self, line_spacing: Size) {
self.ctx.line_spacing = line_spacing;
}
/// The remaining unpadded, unexpanding spaces.
pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = self.stack.remaining();
*spaces[0].dimensions.get_secondary_mut(self.ctx.axes)
-= self.run.size.y;
spaces
}
/// Whether the currently set line is empty.
pub fn line_is_empty(&self) -> bool {
self.run.size == Size2D::ZERO && self.run.layouts.is_empty()
}
/// Finish the last line and compute the final multi-layout.
pub fn finish(mut self) -> LayoutResult<MultiLayout> {
self.finish_line_if_not_empty()?;
self.stack.finish()
}
/// Finish the currently active space and start a new one.
pub fn finish_space(&mut self, hard: bool) -> LayoutResult<()> {
self.finish_line_if_not_empty()?;
self.stack.finish_space(hard)
}
/// Add the current line to the stack and start a new line.
pub fn finish_line(&mut self) -> LayoutResult<()> {
let mut actions = LayoutActions::new();
let layouts = std::mem::replace(&mut self.run.layouts, vec![]);
for (offset, layout) in layouts {
let x = match self.ctx.axes.primary.is_positive() {
true => offset,
false => self.run.size.x
- offset
- layout.dimensions.get_primary(self.ctx.axes),
};
let pos = Size2D::with_x(x);
actions.add_layout(pos, layout);
}
self.stack.add(Layout {
dimensions: self.run.size.specialized(self.ctx.axes),
alignment: self.run.alignment
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
actions: actions.to_vec(),
})?;
self.run = LineRun::new();
self.stack.add_spacing(self.ctx.line_spacing, LINE_KIND);
Ok(())
}
/// Finish the current line if it is not empty.
fn finish_line_if_not_empty(&mut self) -> LayoutResult<()> {
if !self.line_is_empty() {
self.finish_line()
} else {
Ok(())
}
}
}
impl LineRun {
fn new() -> LineRun {
LineRun {
layouts: vec![],
size: Size2D::ZERO,
alignment: None,
usable: None,
last_spacing: LastSpacing::Hard,
}
}
}

View File

@ -9,7 +9,7 @@ use crate::style::LayoutStyle;
mod actions;
mod tree;
mod flex;
mod line;
mod stack;
mod text;
@ -30,7 +30,7 @@ pub mod prelude {
/// Different kinds of layouters (fully re-exported).
pub mod layouters {
pub use super::tree::layout;
pub use super::flex::{FlexLayouter, FlexContext};
pub use super::line::{LineLayouter, LineContext};
pub use super::stack::{StackLayouter, StackContext};
pub use super::text::{layout_text, TextContext};
}
@ -174,7 +174,7 @@ impl LayoutExpansion {
LayoutExpansion { horizontal, vertical }
}
/// Borrow the spcified component mutably.
/// Borrow the specified component mutably.
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool {
match axis {
Horizontal => &mut self.horizontal,
@ -366,6 +366,9 @@ const PARAGRAPH_KIND: SpacingKind = SpacingKind::Soft(1);
/// The standard spacing kind used for line spacing.
const LINE_KIND: SpacingKind = SpacingKind::Soft(2);
/// The standard spacing kind used for word spacing.
const WORD_KIND: SpacingKind = SpacingKind::Soft(1);
/// The last appeared spacing.
#[derive(Debug, Copy, Clone, PartialEq)]
enum LastSpacing {

View File

@ -1,11 +1,9 @@
use smallvec::smallvec;
use crate::size::ValueBox;
use super::*;
/// The stack layouter stack boxes onto each other along the secondary layouting
/// axis.
///
/// The boxes are aligned along both axes according to their requested
/// alignment.
#[derive(Debug, Clone)]
pub struct StackLayouter {
/// The context for layouting.
@ -42,30 +40,21 @@ struct Space {
index: usize,
/// Whether to add the layout for this space even if it would be empty.
hard: bool,
/// The so-far accumulated subspaces.
/// The so-far accumulated layouts.
layouts: Vec<(LayoutAxes, Layout)>,
/// The specialized size of this subspace.
/// The specialized size of this space.
size: Size2D,
/// The specialized remaining space.
usable: Size2D,
/// The specialized extra-needed dimensions to affect the size at all.
extra: Size2D,
/// Dictates the valid alignments for new boxes in this space.
rulers: Rulers,
/// The rulers of a space dictate which alignments for new boxes are still
/// allowed and which require a new space to be started.
rulers: ValueBox<Alignment>,
/// The last added spacing if the last added thing was spacing.
last_spacing: LastSpacing,
}
/// The rulers of a space dictate which alignments for new boxes are still
/// allowed and which require a new space to be started.
#[derive(Debug, Clone)]
struct Rulers {
top: Alignment,
bottom: Alignment,
left: Alignment,
right: Alignment,
}
impl StackLayouter {
/// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter {
@ -157,26 +146,6 @@ impl StackLayouter {
}
}
/// Update the rulers to account for the new layout. Returns true if a
/// space break is necessary.
fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
let axes = self.ctx.axes;
let allowed = self.alignment_allowed(axes.primary, alignment.primary)
&& self.alignment_allowed(axes.secondary, alignment.secondary);
if allowed {
*self.space.rulers.get(axes.secondary) = alignment.secondary;
}
allowed
}
/// Whether the given alignment is still allowed according to the rulers.
fn alignment_allowed(&mut self, direction: Direction, alignment: Alignment) -> bool {
alignment >= *self.space.rulers.get(direction)
&& alignment <= self.space.rulers.get(direction.inv()).inv()
}
/// Update the size metrics to reflect that a layout or spacing with the
/// given generalized dimensions has been added.
fn update_metrics(&mut self, dimensions: Size2D) {
@ -196,10 +165,31 @@ impl StackLayouter {
*self.space.usable.get_secondary_mut(axes) -= dimensions.y;
}
/// Update the rulers to account for the new layout. Returns true if a
/// space break is necessary.
fn update_rulers(&mut self, alignment: LayoutAlignment) -> bool {
let allowed = self.is_fitting_alignment(alignment);
if allowed {
*self.space.rulers.get_mut(self.ctx.axes.secondary, Origin)
= alignment.secondary;
}
allowed
}
/// Whether a layout with the given alignment can still be layouted in the
/// active space.
pub fn is_fitting_alignment(&mut self, alignment: LayoutAlignment) -> bool {
self.is_fitting_axis(self.ctx.axes.primary, alignment.primary)
&& self.is_fitting_axis(self.ctx.axes.secondary, alignment.secondary)
}
/// Whether the given alignment is still allowed according to the rulers.
fn is_fitting_axis(&mut self, direction: Direction, alignment: Alignment) -> bool {
alignment >= *self.space.rulers.get_mut(direction, Origin)
&& alignment <= self.space.rulers.get_mut(direction, End).inv()
}
/// Change the layouting axes used by this layouter.
///
/// This starts a new subspace (if the axes are actually different from the
/// current ones).
pub fn set_axes(&mut self, axes: LayoutAxes) {
// Forget the spacing because it is not relevant anymore.
if axes.secondary != self.ctx.axes.secondary {
@ -227,9 +217,7 @@ impl StackLayouter {
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
/// out into these spaces, it will fit into this stack.
pub fn remaining(&self) -> LayoutSpaces {
let dimensions = self.space.usable
- Size2D::with_y(self.space.last_spacing.soft_or_zero())
.specialized(self.ctx.axes);
let dimensions = self.usable();
let mut spaces = smallvec![LayoutSpace {
dimensions,
@ -244,9 +232,11 @@ impl StackLayouter {
spaces
}
/// The usable size along the primary axis.
pub fn primary_usable(&self) -> Size {
self.space.usable.get_primary(self.ctx.axes)
/// The remaining usable size.
pub fn usable(&self) -> Size2D {
self.space.usable
- Size2D::with_y(self.space.last_spacing.soft_or_zero())
.specialized(self.ctx.axes)
}
/// Whether the current layout space (not subspace) is empty.
@ -409,24 +399,8 @@ impl Space {
size: Size2D::ZERO,
usable,
extra: Size2D::ZERO,
rulers: Rulers {
top: Origin,
bottom: Origin,
left: Origin,
right: Origin,
},
rulers: ValueBox::with_all(Origin),
last_spacing: LastSpacing::Hard,
}
}
}
impl Rulers {
fn get(&mut self, direction: Direction) -> &mut Alignment {
match direction {
TopToBottom => &mut self.top,
BottomToTop => &mut self.bottom,
LeftToRight => &mut self.left,
RightToLeft => &mut self.right,
}
}
}

View File

@ -12,6 +12,7 @@ use super::*;
pub struct TextContext<'a, 'p> {
pub loader: &'a SharedFontLoader<'p>,
pub style: &'a TextStyle,
pub axes: LayoutAxes,
pub alignment: LayoutAlignment,
}
@ -50,22 +51,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
/// Layout the text
fn layout(mut self) -> LayoutResult<Layout> {
for c in self.text.chars() {
let (index, char_width) = self.select_font(c)?;
self.width += char_width;
if self.active_font != index {
if !self.buffer.is_empty() {
self.actions.add(LayoutAction::WriteText(self.buffer));
self.buffer = String::new();
}
self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
self.active_font = index;
if self.ctx.axes.primary.is_positive() {
for c in self.text.chars() {
self.layout_char(c)?;
}
} else {
for c in self.text.chars().rev() {
self.layout_char(c)?;
}
self.buffer.push(c);
}
if !self.buffer.is_empty() {
@ -79,6 +72,27 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
})
}
/// Layout an individual character.
fn layout_char(&mut self, c: char) -> LayoutResult<()> {
let (index, char_width) = self.select_font(c)?;
self.width += char_width;
if self.active_font != index {
if !self.buffer.is_empty() {
let text = std::mem::replace(&mut self.buffer, String::new());
self.actions.add(LayoutAction::WriteText(text));
}
self.actions.add(LayoutAction::SetFont(index, self.ctx.style.font_size()));
self.active_font = index;
}
self.buffer.push(c);
Ok(())
}
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
fn select_font(&mut self, c: char) -> LayoutResult<(usize, Size)> {

View File

@ -16,7 +16,7 @@ pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout
#[derive(Debug, Clone)]
struct TreeLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
stack: StackLayouter,
layouter: LineLayouter,
style: LayoutStyle,
}
@ -24,12 +24,13 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
/// Create a new syntax tree layouter.
fn new(ctx: LayoutContext<'a, 'p>) -> TreeLayouter<'a, 'p> {
TreeLayouter {
stack: StackLayouter::new(StackContext {
layouter: LineLayouter::new(LineContext {
spaces: ctx.spaces.clone(),
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug,
line_spacing: ctx.style.text.line_spacing(),
}),
style: ctx.style.clone(),
ctx,
@ -59,29 +60,27 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
let layout = layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style.text,
axes: self.ctx.axes,
alignment: self.ctx.alignment,
})?;
self.stack.add(layout)
self.layouter.add(layout)
}
fn layout_space(&mut self) {
self.layouter.add_primary_spacing(self.style.text.word_spacing(), WORD_KIND);
}
fn layout_paragraph(&mut self) -> LayoutResult<()> {
Ok(self.stack.add_spacing(
paragraph_spacing(&self.style.text),
PARAGRAPH_KIND,
))
self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND)
}
fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
let commands = func.0.layout(LayoutContext {
style: &self.style,
spaces: self.stack.remaining(),
spaces: self.layouter.remaining(),
nested: true,
debug: true,
debug: false,
.. self.ctx
})?;
@ -98,26 +97,28 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
match command {
LayoutTree(tree) => self.layout(tree)?,
Add(layout) => self.stack.add(layout)?,
AddMultiple(layouts) => self.stack.add_multiple(layouts)?,
Add(layout) => self.layouter.add(layout)?,
AddMultiple(layouts) => self.layouter.add_multiple(layouts)?,
AddSpacing(space, kind, axis) => match axis {
Primary => {},
Secondary => self.stack.add_spacing(space, kind),
Primary => self.layouter.add_primary_spacing(space, kind),
Secondary => self.layouter.add_secondary_spacing(space, kind)?,
}
FinishLine => {},
FinishRun => {},
FinishSpace => self.stack.finish_space(true)?,
FinishLine => self.layouter.finish_line()?,
FinishSpace => self.layouter.finish_space(true)?,
BreakParagraph => self.layout_paragraph()?,
BreakPage => {
if self.ctx.nested {
error!("page break cannot be issued from nested context");
}
self.stack.finish_space(true)?
self.layouter.finish_space(true)?
}
SetTextStyle(style) => self.style.text = style,
SetTextStyle(style) => {
self.layouter.set_line_spacing(style.line_spacing());
self.style.text = style;
}
SetPageStyle(style) => {
if self.ctx.nested {
error!("page style cannot be altered in nested context");
@ -127,7 +128,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
let margins = style.margins();
self.ctx.base = style.dimensions.unpadded(margins);
self.stack.set_spaces(smallvec![
self.layouter.set_spaces(smallvec![
LayoutSpace {
dimensions: style.dimensions,
padding: margins,
@ -137,7 +138,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
}
SetAlignment(alignment) => self.ctx.alignment = alignment,
SetAxes(axes) => {
self.stack.set_axes(axes);
self.layouter.set_axes(axes);
self.ctx.axes = axes;
}
}
@ -146,18 +147,6 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
}
fn finish(self) -> LayoutResult<MultiLayout> {
self.stack.finish()
self.layouter.finish()
}
}
fn word_spacing(style: &TextStyle) -> Size {
style.word_spacing * style.font_size()
}
fn flex_spacing(style: &TextStyle) -> Size {
(style.line_spacing - 1.0) * style.font_size()
}
fn paragraph_spacing(style: &TextStyle) -> Size {
(style.paragraph_spacing - 1.0) * style.font_size()
}

View File

@ -9,28 +9,30 @@ function! {
pub struct Boxed {
body: SyntaxTree,
map: ExtentMap<PSize>,
debug: bool,
debug: Option<bool>,
}
parse(args, body, ctx) {
Boxed {
body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()),
map: ExtentMap::new(&mut args, false)?,
debug: args.get_key_opt::<bool>("debug")?.unwrap_or(true),
debug: args.get_key_opt::<bool>("debug")?,
}
}
layout(self, mut ctx) {
ctx.repeat = false;
ctx.debug = self.debug;
if let Some(debug) = self.debug {
ctx.debug = debug;
}
let map = self.map.dedup(ctx.axes)?;
// Try to layout this box in all spaces.
// Try to layout this box in all spaces until it fits into some space.
let mut error = None;
for &space in &ctx.spaces {
for &(mut space) in &ctx.spaces {
let mut ctx = ctx.clone();
let mut space = space;
for &axis in &[Horizontal, Vertical] {
if let Some(psize) = map.get(axis) {

View File

@ -1,6 +1,6 @@
//! Different-dimensional spacing types.
//! Different-dimensional value and spacing types.
use std::fmt::{self, Display, Formatter};
use std::fmt::{self, Debug, Display, Formatter};
use std::iter::Sum;
use std::ops::*;
use std::str::FromStr;
@ -8,7 +8,7 @@ use std::str::FromStr;
use crate::layout::prelude::*;
/// A general spacing type.
#[derive(Copy, Clone, PartialEq, PartialOrd)]
#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
pub struct Size {
/// The size in typographic points (1/72 inches).
pub points: f32,
@ -92,64 +92,111 @@ impl Sum for Size {
}
}
/// A position or extent in 2-dimensional space.
/// Either an absolute size or a factor of some entity.
#[derive(Copy, Clone, PartialEq)]
pub struct Size2D {
/// The horizontal coordinate.
pub x: Size,
/// The vertical coordinate.
pub y: Size,
pub enum ScaleSize {
Absolute(Size),
Scaled(f32),
}
impl Size2D {
/// The zeroed 2D-size.
pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO };
impl ScaleSize {
/// Use the absolute value or scale the entity.
pub fn scaled(&self, entity: Size) -> Size {
match self {
ScaleSize::Absolute(s) => *s,
ScaleSize::Scaled(s) => *s * entity,
}
}
}
/// Create a new 2D-size from two sizes.
pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } }
impl Display for ScaleSize {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ScaleSize::Absolute(size) => write!(f, "{}", size),
ScaleSize::Scaled(scale) => write!(f, "x{}", scale),
}
}
}
/// Create a new 2D-size with `x` set to a value and `y` zero.
pub fn with_x(x: Size) -> Size2D { Size2D { x, y: Size::ZERO } }
debug_display!(ScaleSize);
/// Create a new 2D-size with `y` set to a value and `x` zero.
pub fn with_y(y: Size) -> Size2D { Size2D { x: Size::ZERO, y } }
/// A scale size that is scaled by the font size.
pub type FSize = ScaleSize;
/// Create a 2D-size with `x` and `y` set to the same value `s`.
pub fn with_all(s: Size) -> Size2D { Size2D { x: s, y: s } }
/// A scale size that is scaled by the size of the padded parent container.
pub type PSize = ScaleSize;
/// A value in two dimensions.
#[derive(Default, Copy, Clone, PartialEq)]
pub struct Value2D<T: Copy> {
/// The horizontal component.
pub x: T,
/// The vertical component.
pub y: T,
}
impl<T: Copy> Value2D<T> {
/// Create a new 2D-value from two values.
pub fn new(x: T, y: T) -> Value2D<T> { Value2D { x, y } }
/// Create a new 2D-value with `x` set to a value and `y` to default.
pub fn with_x(x: T) -> Value2D<T> where T: Default {
Value2D { x, y: T::default() }
}
/// Create a new 2D-value with `y` set to a value and `x` to default.
pub fn with_y(y: T) -> Value2D<T> where T: Default {
Value2D { x: T::default(), y }
}
/// Create a new 2D-value with the primary axis set to a value and the other
/// one to default.
pub fn with_primary(v: T, axes: LayoutAxes) -> Value2D<T> where T: Default {
Value2D::with_x(v).generalized(axes)
}
/// Create a new 2D-value with the secondary axis set to a value and the
/// other one to default.
pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2D<T> where T: Default {
Value2D::with_y(v).generalized(axes)
}
/// Create a 2D-value with `x` and `y` set to the same value `s`.
pub fn with_all(s: T) -> Value2D<T> { Value2D { x: s, y: s } }
/// Get the specificed component.
pub fn get(self, axis: SpecificAxis) -> Size {
pub fn get(self, axis: SpecificAxis) -> T {
match axis {
Horizontal => self.x,
Vertical => self.y,
}
}
/// Get the specificed component mutably.
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut Size {
/// Borrow the specificed component mutably.
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut T {
match axis {
Horizontal => &mut self.x,
Vertical => &mut self.y,
}
}
/// Access the primary size of this specialized 2D-size.
pub fn get_primary(self, axes: LayoutAxes) -> Size {
/// Return the primary value of this specialized 2D-value.
pub fn get_primary(self, axes: LayoutAxes) -> T {
if axes.primary.axis() == Horizontal { self.x } else { self.y }
}
/// Access the primary size of this specialized 2D-size mutably.
pub fn get_primary_mut(&mut self, axes: LayoutAxes) -> &mut Size {
/// Borrow the primary value of this specialized 2D-value mutably.
pub fn get_primary_mut(&mut self, axes: LayoutAxes) -> &mut T {
if axes.primary.axis() == Horizontal { &mut self.x } else { &mut self.y }
}
/// Access the secondary size of this specialized 2D-size.
pub fn get_secondary(self, axes: LayoutAxes) -> Size {
/// Return the secondary value of this specialized 2D-value.
pub fn get_secondary(self, axes: LayoutAxes) -> T {
if axes.primary.axis() == Horizontal { self.y } else { self.x }
}
/// Access the secondary size of this specialized 2D-size mutably.
pub fn get_secondary_mut(&mut self, axes: LayoutAxes) -> &mut Size {
/// Borrow the secondary value of this specialized 2D-value mutably.
pub fn get_secondary_mut(&mut self, axes: LayoutAxes) -> &mut T {
if axes.primary.axis() == Horizontal { &mut self.y } else { &mut self.x }
}
@ -157,20 +204,40 @@ impl Size2D {
/// axes, that is:
/// - `x` describes the primary axis instead of the horizontal one.
/// - `y` describes the secondary axis instead of the vertical one.
pub fn generalized(self, axes: LayoutAxes) -> Size2D {
pub fn generalized(self, axes: LayoutAxes) -> Value2D<T> {
match axes.primary.axis() {
Horizontal => self,
Vertical => Size2D { x: self.y, y: self.x },
Vertical => Value2D { x: self.y, y: self.x },
}
}
/// Returns the specialized version of this generalized Size2D (inverse to
/// `generalized`).
pub fn specialized(self, axes: LayoutAxes) -> Size2D {
pub fn specialized(self, axes: LayoutAxes) -> Value2D<T> {
// In fact, generalized is its own inverse. For reasons of clarity
// at the call site, we still have this second function.
self.generalized(axes)
}
}
impl<T: Copy> Display for Value2D<T> where T: Display {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[{}, {}]", self.x, self.y)
}
}
impl<T: Copy> Debug for Value2D<T> where T: Debug {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[{:?}, {:?}]", self.x, self.y)
}
}
/// A position or extent in 2-dimensional space.
pub type Size2D = Value2D<Size>;
impl Size2D {
/// The zeroed 2D-size.
pub const ZERO: Size2D = Size2D { x: Size::ZERO, y: Size::ZERO };
/// Whether the given 2D-size fits into this one, that is, both coordinate
/// values are smaller or equal.
@ -207,14 +274,6 @@ impl Size2D {
}
}
impl Display for Size2D {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[{}, {}]", self.x, self.y)
}
}
debug_display!(Size2D);
impl Neg for Size2D {
type Output = Size2D;
@ -226,11 +285,41 @@ impl Neg for Size2D {
}
}
/// A size in four dimensions.
pub type SizeBox = ValueBox<Size>;
/// A value that is stretchable in an interval from a minimal through an optimal
/// to a maximal value.
pub struct StretchValue<T: Copy> {
/// The minimum this value can be stretched to.
pub min: T,
/// The optimum for this value.
pub opt: T,
/// The maximum this value can be stretched to.
pub max: T,
}
impl<T: Copy> StretchValue<T> {
/// Create a new stretch size from minimum, optimal and maximum values.
pub fn new(min: T, opt: T, max: T) -> StretchValue<T> {
StretchValue { min, opt, max }
}
}
impl<T: Copy> Display for StretchValue<T> where T: Display {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "({}, {}, {})", self.min, self.opt, self.max)
}
}
impl<T: Copy> Debug for StretchValue<T> where T: Debug {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "({:?}, {:?}, {:?})", self.min, self.opt, self.max)
}
}
/// A size that is stretchable.
pub type StretchSize = StretchValue<Size>;
/// A value in four dimensions.
#[derive(Copy, Clone, PartialEq)]
#[derive(Default, Copy, Clone, PartialEq)]
pub struct ValueBox<T: Copy> {
/// The left extent.
pub left: T,
@ -242,16 +331,6 @@ pub struct ValueBox<T: Copy> {
pub bottom: T,
}
impl SizeBox {
/// The zeroed size box.
pub const ZERO: SizeBox = SizeBox {
left: Size::ZERO,
top: Size::ZERO,
right: Size::ZERO,
bottom: Size::ZERO,
};
}
impl<T: Copy> ValueBox<T> {
/// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> ValueBox<T> {
@ -263,9 +342,10 @@ impl<T: Copy> ValueBox<T> {
ValueBox { left: value, top: value, right: value, bottom: value }
}
/// Get a mutable reference to the value for the specified direction and
/// alignment. Center alignment will be treated the same as origin
/// Get a mutable reference to the value for the specified direction at the
/// alignment.
///
/// Center alignment is treated the same as origin alignment.
pub fn get_mut(&mut self, mut direction: Direction, alignment: Alignment) -> &mut T {
if alignment == End {
direction = direction.inv();
@ -297,49 +377,33 @@ impl<T: Copy> ValueBox<T> {
}
}
impl<T: Copy> Display for ValueBox<T> where T: std::fmt::Debug {
impl<T: Copy> Display for ValueBox<T> where T: Display {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[left: {}, top: {}, right: {}, bottom: {}]",
self.left, self.top, self.right, self.bottom)
}
}
impl<T: Copy> Debug for ValueBox<T> where T: Debug {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "[left: {:?}, top: {:?}, right: {:?}, bottom: {:?}]",
self.left, self.top, self.right, self.bottom)
}
}
debug_display!(ValueBox; T where T: std::fmt::Debug + Copy);
/// A size in four dimensions.
pub type SizeBox = ValueBox<Size>;
/// Either an absolute size or a factor of some metric.
#[derive(Copy, Clone, PartialEq)]
pub enum ScaleSize {
Absolute(Size),
Scaled(f32),
impl SizeBox {
/// The zeroed size box.
pub const ZERO: SizeBox = SizeBox {
left: Size::ZERO,
top: Size::ZERO,
right: Size::ZERO,
bottom: Size::ZERO,
};
}
/// A scale size that is scaled by the font size.
pub type FSize = ScaleSize;
/// A scale size that is scaled by the size of the padded parent container.
pub type PSize = ScaleSize;
impl ScaleSize {
/// Use the absolute value or scale the entity.
pub fn scaled(&self, entity: Size) -> Size {
match self {
ScaleSize::Absolute(s) => *s,
ScaleSize::Scaled(s) => *s * entity,
}
}
}
impl Display for ScaleSize {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
ScaleSize::Absolute(size) => write!(f, "{}", size),
ScaleSize::Scaled(scale) => write!(f, "x{}", scale),
}
}
}
debug_display!(ScaleSize);
/// An error which can be returned when parsing a size.
pub struct ParseSizeError;

View File

@ -26,11 +26,11 @@ pub struct TextStyle {
/// The font scale to apply on the base font size.
pub font_scale: f32,
/// The word spacing (as a multiple of the font size).
pub word_spacing: f32,
pub word_spacing_scale: f32,
/// The line spacing (as a multiple of the font size).
pub line_spacing: f32,
pub line_spacing_scale: f32,
/// The paragraphs spacing (as a multiple of the font size).
pub paragraph_spacing: f32,
pub paragraph_spacing_scale: f32,
}
impl TextStyle {
@ -39,6 +39,21 @@ impl TextStyle {
self.base_font_size * self.font_scale
}
/// The absolute word spacing.
pub fn word_spacing(&self) -> Size {
self.word_spacing_scale * self.font_size()
}
/// The absolute line spacing.
pub fn line_spacing(&self) -> Size {
(self.line_spacing_scale - 1.0) * self.font_size()
}
/// The absolute paragraph spacing.
pub fn paragraph_spacing(&self) -> Size {
(self.paragraph_spacing_scale - 1.0) * self.font_size()
}
/// Toggle a class.
///
/// If the class was one of _italic_ or _bold_, then:
@ -82,9 +97,9 @@ impl Default for TextStyle {
fallback: vec![Serif],
base_font_size: Size::pt(11.0),
font_scale: 1.0,
word_spacing: 0.25,
line_spacing: 1.2,
paragraph_spacing: 1.5,
word_spacing_scale: 0.25,
line_spacing_scale: 1.2,
paragraph_spacing_scale: 1.5,
}
}
}

View File

@ -1,23 +0,0 @@
[box][
A short sentence. [align: right][words.]
A short sentence. [n] [align: right][words.]
A short sentence. [par.break] [align: right][words.]
[align: bottom]
A longer sentence with a few more words.
]
[page.break]
[box][
[align: vertical=top]
Top
[align: vertical=center]
Center
[align: vertical=bottom]
Bottom [n] Bottom [n] Bottom
]

View File

@ -2,19 +2,19 @@
[page.margins: 1cm]
[box][
*Technical University Berlin* [n]
*Faculty II, Institute for Mathematics* [n]
Secretary Example [n]
Prof. Dr. Example [n]
Assistant #1, Assistant #2, Assistant #3
*Technische Universität Berlin* [n]
*Fakultät II, Institut for Mathematik* [n]
Sekretariat MA [n]
Dr. Max Mustermann [n]
Ola Nordmann, John Doe
]
[align: right][*WiSe 2019/2020* [n] Week 1]
[align: right][*WiSe 2019/2020* [n] Woche 3]
[v: 6mm]
[align: center][
*3. Ubungsblatt Computerorientierte Mathematik II* [v: 0.3mm]
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 0.3mm]
*3. Ubungsblatt Computerorientierte Mathematik II* [v: 2mm]
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [v: 2mm]
*Alle Antworten sind zu beweisen.*
]

View File

@ -1,14 +0,0 @@
[page.size: height=5cm]
Line.
[box][
Lines with [box][two] [box][boxes].
Lines with two boxes.
]
[box][
Lines without two boxes.
Lines without two boxes.
]