diff --git a/fonts/Latin-Modern-Math.otf b/fonts/Latin-Modern-Math.otf new file mode 100644 index 000000000..0e4642e91 Binary files /dev/null and b/fonts/Latin-Modern-Math.otf differ diff --git a/fonts/fonts.toml b/fonts/fonts.toml index 844e2d349..b289ff5cf 100644 --- a/fonts/fonts.toml +++ b/fonts/fonts.toml @@ -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"] diff --git a/src/export/pdf.rs b/src/export/pdf.rs index f029a37ff..bb6881183 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -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)) diff --git a/src/layout/flex.rs b/src/layout/flex.rs index a6f2e0912..b97b42392 100644 --- a/src/layout/flex.rs +++ b/src/layout/flex.rs @@ -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 { diff --git a/src/layout/mod.rs b/src/layout/mod.rs index fccbe8c8e..470ac3ba3 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -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. diff --git a/src/layout/stacked.rs b/src/layout/stacked.rs index d87e53943..0097ce3e2 100644 --- a/src/layout/stacked.rs +++ b/src/layout/stacked.rs @@ -26,15 +26,17 @@ pub struct StackContext { pub space: LayoutSpace, pub followup_spaces: Option, 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. diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 2c904369f..3a58115f7 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -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(); diff --git a/src/lib.rs b/src/lib.rs index c01e5d7e9..0b559be99 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 975888f4c..71a184a66 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -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 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 { - let layout = layout_tree(&self.body, ctx)?; + let layout = layout_tree(&self.body, LayoutContext { + flow: self.flow, + .. ctx + })?; + Ok(commands![Command::AddMany(layout)]) } } diff --git a/tests/layouts/boxes.typ b/tests/layouts/boxes.typ deleted file mode 100644 index 055e09fe5..000000000 --- a/tests/layouts/boxes.typ +++ /dev/null @@ -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] diff --git a/tests/layouts/coma.typ b/tests/layouts/coma.typ new file mode 100644 index 000000000..7b3266a92 --- /dev/null +++ b/tests/layouts/coma.typ @@ -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. + +