Extend stack layouts from vertical to horizontal flows ➡

This commit is contained in:
Laurenz 2019-10-17 19:21:47 +02:00
parent 1987e5861c
commit 991e879e1d
11 changed files with 116 additions and 37 deletions

BIN
fonts/Latin-Modern-Math.otf Normal file

Binary file not shown.

View File

@ -10,4 +10,5 @@
"CMU-Typewriter-Italic.ttf" = ["Computer Modern", "Italic", "Serif", "SansSerif", "Monospace"]
"CMU-Typewriter-Bold.ttf" = ["Computer Modern", "Bold", "Serif", "SansSerif", "Monospace"]
"CMU-Typewriter-Bold-Italic.ttf" = ["Computer Modern", "Bold", "Italic", "Serif", "SansSerif", "Monospace"]
"Latin-Modern-Math.otf" = ["Latin Modern", "Latin Modern Math", "Math", "Regular", "Serif"]
"NotoEmoji-Regular.ttf" = ["Noto", "Noto Emoji", "Regular", "SansSerif", "Serif", "Monospace"]

View File

@ -146,8 +146,10 @@ impl<'d, W: Write> ExportProcess<'d, W> {
for index in 0 .. num_fonts {
let old_index = new_to_old[&index];
let font = font_loader.get_with_index(old_index);
let subsetted = font.subsetted(font_chars[&old_index].iter().cloned(), &SUBSET_TABLES)?;
fonts.push(OwnedFont::from_bytes(subsetted)?);
let subsetted = font.subsetted(font_chars[&old_index].iter().cloned(), &SUBSET_TABLES)
.map(|bytes| OwnedFont::from_bytes(bytes))
.unwrap_or_else(|_| font.to_owned())?;
fonts.push(subsetted);
}
Ok((fonts, old_to_new))

View File

@ -90,7 +90,7 @@ impl FlexLayouter {
ctx,
units: vec![],
stack: StackLayouter::new(StackContext::from_flex_ctx(ctx)),
stack: StackLayouter::new(StackContext::from_flex_ctx(ctx, Flow::Vertical)),
usable_width: ctx.space.usable().x,
run: FlexRun {

View File

@ -134,6 +134,8 @@ pub struct LayoutContext<'a, 'p> {
pub style: &'a TextStyle,
/// The alignment to use for the content.
pub alignment: Alignment,
/// How to stack the context.
pub flow: Flow,
/// The primary space to layout in.
pub space: LayoutSpace,
/// The additional spaces which are used when the primary space
@ -176,6 +178,13 @@ pub enum Alignment {
Center,
}
/// The flow of content.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum Flow {
Vertical,
Horizontal,
}
/// The error type for layouting.
pub enum LayoutError {
/// There is not enough space to add an item.

View File

@ -26,15 +26,17 @@ pub struct StackContext {
pub space: LayoutSpace,
pub followup_spaces: Option<LayoutSpace>,
pub shrink_to_fit: bool,
pub flow: Flow,
}
macro_rules! reuse {
($ctx:expr) => {
($ctx:expr, $flow:expr) => {
StackContext {
alignment: $ctx.alignment,
space: $ctx.space,
followup_spaces: $ctx.followup_spaces,
shrink_to_fit: $ctx.shrink_to_fit
shrink_to_fit: $ctx.shrink_to_fit,
flow: $flow
}
};
}
@ -42,12 +44,12 @@ macro_rules! reuse {
impl StackContext {
/// Create a stack context from a generic layout context.
pub fn from_layout_ctx(ctx: LayoutContext) -> StackContext {
reuse!(ctx)
reuse!(ctx, ctx.flow)
}
/// Create a stack context from a flex context.
pub fn from_flex_ctx(ctx: FlexContext) -> StackContext {
reuse!(ctx)
pub fn from_flex_ctx(ctx: FlexContext, flow: Flow) -> StackContext {
reuse!(ctx, flow)
}
}
@ -79,9 +81,15 @@ impl StackLayouter {
self.start_new_space()?;
}
let new_dimensions = Size2D {
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
y: self.dimensions.y + layout.dimensions.y,
let new_dimensions = match self.ctx.flow {
Flow::Vertical => Size2D {
x: crate::size::max(self.dimensions.x, layout.dimensions.x),
y: self.dimensions.y + layout.dimensions.y,
},
Flow::Horizontal => Size2D {
x: self.dimensions.x + layout.dimensions.x,
y: crate::size::max(self.dimensions.y, layout.dimensions.y),
}
};
if self.overflows(new_dimensions) {
@ -104,9 +112,13 @@ impl StackLayouter {
Alignment::Center => self.cursor - Size2D::with_x(layout.dimensions.x / 2),
};
self.cursor.y += layout.dimensions.y;
self.dimensions = new_dimensions;
match self.ctx.flow {
Flow::Vertical => self.cursor.y += layout.dimensions.y,
Flow::Horizontal => self.cursor.x += layout.dimensions.x,
}
self.actions.add_layout(position, layout);
Ok(())
@ -120,23 +132,26 @@ impl StackLayouter {
Ok(())
}
/// Add vertical space after the last layout.
/// Add space after the last layout.
pub fn add_space(&mut self, space: Size) -> LayoutResult<()> {
if !self.started {
self.start_new_space()?;
}
let new_dimensions = self.dimensions + Size2D::with_y(space);
let new_space = match self.ctx.flow {
Flow::Vertical => Size2D::with_y(space),
Flow::Horizontal => Size2D::with_x(space),
};
if self.overflows(new_dimensions) {
if self.overflows(self.dimensions + new_space) {
if self.ctx.followup_spaces.is_some() {
self.finish_layout(false)?;
} else {
return Err(LayoutError::NotEnoughSpace("cannot fit space into stack"));
}
} else {
self.cursor.y += space;
self.dimensions.y += space;
self.cursor += new_space;
self.dimensions += new_space;
}
Ok(())
@ -195,10 +210,17 @@ impl StackLayouter {
/// The remaining space for new layouts.
pub fn remaining(&self) -> Size2D {
Size2D {
x: self.usable.x,
y: self.usable.y - self.dimensions.y,
match self.ctx.flow {
Flow::Vertical => Size2D {
x: self.usable.x,
y: self.usable.y - self.dimensions.y,
},
Flow::Horizontal => Size2D {
x: self.usable.x - self.dimensions.x,
y: self.usable.y,
},
}
}
/// Whether the active space of this layouter contains no content.

View File

@ -96,6 +96,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
let mut ctx = self.ctx;
ctx.style = &self.style;
ctx.flow = Flow::Vertical;
ctx.shrink_to_fit = true;
ctx.space.dimensions = remaining;
ctx.space.padding = SizeBox::zero();

View File

@ -21,7 +21,7 @@ use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
use crate::func::Scope;
use crate::layout::{layout_tree, LayoutContext, MultiLayout};
use crate::layout::{Alignment, LayoutError, LayoutResult, LayoutSpace};
use crate::layout::{Alignment, Flow, LayoutError, LayoutResult, LayoutSpace};
use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
use crate::style::{PageStyle, TextStyle};
use crate::syntax::SyntaxTree;
@ -105,6 +105,7 @@ impl<'p> Typesetter<'p> {
loader: &self.loader,
style: &self.text_style,
alignment: Alignment::Left,
flow: Flow::Vertical,
space,
followup_spaces: Some(space),
shrink_to_fit: false,

View File

@ -1,21 +1,39 @@
use super::prelude::*;
use crate::layout::Flow;
/// Wraps content into a box.
#[derive(Debug, PartialEq)]
pub struct BoxFunc {
body: SyntaxTree
body: SyntaxTree,
flow: Flow,
}
impl Function for BoxFunc {
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
where Self: Sized {
if has_arguments(header) {
return err("pagebreak: expected no arguments");
}
let flow = if header.args.is_empty() {
Flow::Vertical
} else if header.args.len() == 1 {
if let Expression::Ident(ident) = &header.args[0] {
match ident.as_str() {
"vertical" => Flow::Vertical,
"horizontal" => Flow::Horizontal,
f => return err(format!("invalid flow specifier: '{}'", f)),
}
} else {
return err(format!(
"expected alignment specifier, found: '{}'",
header.args[0]
));
}
} else {
return err("box: expected flow specifier or no arguments");
};
if let Some(body) = body {
Ok(BoxFunc {
body: parse(body, ctx)?
body: parse(body, ctx)?,
flow,
})
} else {
err("box: expected body")
@ -23,7 +41,11 @@ impl Function for BoxFunc {
}
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
let layout = layout_tree(&self.body, ctx)?;
let layout = layout_tree(&self.body, LayoutContext {
flow: self.flow,
.. ctx
})?;
Ok(commands![Command::AddMany(layout)])
}
}

View File

@ -1,10 +0,0 @@
{size:400pt*250pt}
[box][
*Technical University Berlin* [n]
*Faculty II, Institute for Mathematics* [n]
Secretary Example [n]
Prof. Dr. Example [n]
Assistant #1, Assistant #2, Assistant #3
]
[align: right][*WiSe 2019/2020* [n] Week 1]

31
tests/layouts/coma.typ Normal file
View File

@ -0,0 +1,31 @@
{size:420pt*300pt}
[box: horizontal][
*Technical University Berlin* [n]
*Faculty II, Institute for Mathematics* [n]
Secretary Example [n]
Prof. Dr. Example [n]
Assistant #1, Assistant #2, Assistant #3
[align: right][*WiSe 2019/2020* [n] Week 1]
]
// hack for more whitespace
[box][]
[align: center][
*3. Ubungsblatt Computerorientierte Mathematik II* [n]
*Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) [n]
*Alle Antworten sind zu beweisen.*
]
[box: horizontal][
*1. Aufgabe* [align: right][(1 + 1 + 2 Punkte)]
]
Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten 2 Kinder hat.
Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel
zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges
von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel.