summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-06-22 15:32:19 +0200
committerLaurenz <laurmaedje@gmail.com>2019-06-22 15:32:19 +0200
commit099ce71aba54a40455b7ce35768c8fe003f7b16a (patch)
treed0a475e91967882d4608dea59ceb41c9a6232e07 /src
parentc7ee2b393a369325b3578557e045f2ff94ceab8f (diff)
Unify font classes + By-value-contexts ⚖
Diffstat (limited to 'src')
-rw-r--r--src/bin/main.rs30
-rw-r--r--src/font.rs176
-rw-r--r--src/func.rs26
-rw-r--r--src/layout/mod.rs185
-rw-r--r--src/layout/text.rs11
-rw-r--r--src/lib.rs47
-rw-r--r--src/parsing.rs26
-rw-r--r--src/style.rs44
8 files changed, 265 insertions, 280 deletions
diff --git a/src/bin/main.rs b/src/bin/main.rs
index b2f3d405..edf639f0 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use std::process;
use typeset::Typesetter;
-use typeset::{font::FileSystemFontProvider, font_info};
+use typeset::{font::FileSystemFontProvider, font};
use typeset::export::pdf::PdfExporter;
@@ -50,20 +50,20 @@ fn run() -> Result<(), Box<Error>> {
// Create a typesetter with a font provider that provides the default fonts.
let mut typesetter = Typesetter::new();
- typesetter.add_font_provider(FileSystemFontProvider::new("fonts", vec![
- ("CMU-SansSerif-Regular.ttf", font_info!(["Computer Modern", SansSerif])),
- ("CMU-SansSerif-Italic.ttf", font_info!(["Computer Modern", SansSerif], italic)),
- ("CMU-SansSerif-Bold.ttf", font_info!(["Computer Modern", SansSerif], bold)),
- ("CMU-SansSerif-Bold-Italic.ttf", font_info!(["Computer Modern", SansSerif], bold, italic)),
- ("CMU-Serif-Regular.ttf", font_info!(["Computer Modern", Serif])),
- ("CMU-Serif-Italic.ttf", font_info!(["Computer Modern", Serif], italic)),
- ("CMU-Serif-Bold.ttf", font_info!(["Computer Modern", Serif], bold)),
- ("CMU-Serif-Bold-Italic.ttf", font_info!(["Computer Modern", Serif], bold, italic)),
- ("CMU-Typewriter-Regular.ttf", font_info!(["Computer Modern", Monospace])),
- ("CMU-Typewriter-Italic.ttf", font_info!(["Computer Modern", Monospace], italic)),
- ("CMU-Typewriter-Bold.ttf", font_info!(["Computer Modern", Monospace], bold)),
- ("CMU-Typewriter-Bold-Italic.ttf", font_info!(["Computer Modern", Monospace], bold, italic)),
- ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
+ typesetter.add_font_provider(FileSystemFontProvider::new("../fonts", vec![
+ ("CMU-SansSerif-Regular.ttf", font!["Computer Modern", Regular, SansSerif]),
+ ("CMU-SansSerif-Italic.ttf", font!["Computer Modern", Italic, SansSerif]),
+ ("CMU-SansSerif-Bold.ttf", font!["Computer Modern", Bold, SansSerif]),
+ ("CMU-SansSerif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, SansSerif]),
+ ("CMU-Serif-Regular.ttf", font!["Computer Modern", Regular, Serif]),
+ ("CMU-Serif-Italic.ttf", font!["Computer Modern", Italic, Serif]),
+ ("CMU-Serif-Bold.ttf", font!["Computer Modern", Bold, Serif]),
+ ("CMU-Serif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Serif]),
+ ("CMU-Typewriter-Regular.ttf", font!["Computer Modern", Regular, Monospace]),
+ ("CMU-Typewriter-Italic.ttf", font!["Computer Modern", Italic, Monospace]),
+ ("CMU-Typewriter-Bold.ttf", font!["Computer Modern", Bold, Monospace]),
+ ("CMU-Typewriter-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Monospace]),
+ ("NotoEmoji-Regular.ttf", font!["Noto", Regular, SansSerif, Serif, Monospace]),
]));
// Typeset the source code.
diff --git a/src/font.rs b/src/font.rs
index d11fe75c..ce3c3784 100644
--- a/src/font.rs
+++ b/src/font.rs
@@ -154,94 +154,83 @@ pub struct FontMetrics {
/// Categorizes a font.
///
-/// Can be constructed conveniently with the [`font_info`] macro.
+/// Can be constructed conveniently with the [`font`] macro.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FontInfo {
/// The font families this font is part of.
- pub families: Vec<FontFamily>,
- /// Whether the font is italic.
- pub italic: bool,
- /// Whether the font bold.
- pub bold: bool,
+ pub classes: Vec<FontClass>,
+}
+
+impl FontInfo {
+ /// Create a new font info from an iterator of classes.
+ pub fn new<I>(classes: I) -> FontInfo where I: IntoIterator<Item=FontClass> {
+ FontInfo { classes: classes.into_iter().collect() }
+ }
}
-/// A family of fonts.
+/// A class of fonts.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub enum FontFamily {
+pub enum FontClass {
Serif,
SansSerif,
Monospace,
- /// A custom class like _Arial_ or _Times_.
- Named(String),
+ Regular,
+ Bold,
+ Italic,
+ /// A custom family like _Arial_ or _Times_.
+ Family(String),
}
/// A macro to create [FontInfos](crate::font::FontInfo) easily.
///
-/// Accepts first a bracketed, ordered list of font families. Allowed are string expressions as well
-/// as the three base families `SansSerif`, `Serif` and `Monospace`. Then there may follow
-/// (separated by commas) the keywords `italic` and/or `bold`.
+/// Accepts an ordered list of font classes. Strings expressions are parsed
+/// into custom `Family`-variants and others can be named directly.
///
/// # Examples
/// The font _Noto Sans_ in regular typeface.
/// ```
-/// # use typeset::font_info;
-/// font_info!(["NotoSans", "Noto", SansSerif]);
+/// # use typeset::font;
+/// font!["NotoSans", "Noto", Regular, SansSerif];
/// ```
///
/// The font _Noto Serif_ in italics and boldface.
/// ```
-/// # use typeset::font_info;
-/// font_info!(["NotoSerif", "Noto", Serif], italic, bold);
+/// # use typeset::font;
+/// font!["NotoSerif", "Noto", Bold, Italic, Serif];
/// ```
///
/// The font _Arial_ in italics.
/// ```
-/// # use typeset::font_info;
-/// font_info!(["Arial", SansSerif], italic);
+/// # use typeset::font;
+/// font!["Arial", Italic, SansSerif];
/// ```
///
/// The font _Noto Emoji_, which works with all base families. 🙂
/// ```
-/// # use typeset::font_info;
-/// font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace]);
+/// # use typeset::font;
+/// font!["NotoEmoji", "Noto", Regular, SansSerif, Serif, Monospace];
/// ```
#[macro_export]
-macro_rules! font_info {
- // Entry point
- ([$($tts:tt)*] $(,$style:tt)*) => {{
- let mut families = Vec::new();
- font_info!(@__fam families, $($tts)*);
-
- #[allow(unused)] let mut italic = false;
- #[allow(unused)] let mut bold = false;
- $( font_info!(@__sty (italic, bold) $style); )*
-
- $crate::font::FontInfo { families, italic, bold }
- }};
-
- // Parse family list
- (@__fam $v:expr) => {};
- (@__fam $v:expr, $f:ident) => { $v.push(font_info!(@__gen $f)); };
- (@__fam $v:expr, $f:ident, $($tts:tt)*) => {
- font_info!(@__fam $v, $f);
- font_info!(@__fam $v, $($tts)*)
- };
- (@__fam $v:expr, $f:expr) => {
- $v.push( $crate::font::FontFamily::Named($f.to_string()));
+macro_rules! font {
+ // Parse class list one by one.
+ (@__cls $v:expr) => {};
+ (@__cls $v:expr, $c:ident) => { $v.push($crate::font::FontClass::$c); };
+ (@__cls $v:expr, $c:ident, $($tts:tt)*) => {
+ font!(@__cls $v, $c);
+ font!(@__cls $v, $($tts)*)
};
- (@__fam $v:expr, $f:expr, $($tts:tt)*) => {
- font_info!(@__fam $v, $f);
- font_info!(@__fam $v, $($tts)*)
+ (@__cls $v:expr, $f:expr) => { $v.push( $crate::font::FontClass::Family($f.to_string())); };
+ (@__cls $v:expr, $f:expr, $($tts:tt)*) => {
+ font!(@__cls $v, $f);
+ font!(@__cls $v, $($tts)*)
};
- // Parse styles (italic/bold)
- (@__sty ($i:ident, $b:ident) italic) => { $i = true; };
- (@__sty ($i:ident, $b:ident) bold) => { $b = true; };
-
- // Parse enum variants
- (@__gen SansSerif) => { $crate::font::FontFamily::SansSerif };
- (@__gen Serif) => { $crate::font::FontFamily::Serif };
- (@__gen Monospace) => { $crate::font::FontFamily::Monospace };
+ // Entry point
+ ($($tts:tt)*) => {{
+ let mut classes = Vec::new();
+ font!(@__cls classes, $($tts)*);
+ $crate::font::FontInfo { classes }
+ }};
}
//------------------------------------------------------------------------------------------------//
@@ -282,10 +271,10 @@ impl FileSystemFontProvider {
/// Serve the two fonts `NotoSans-Regular` and `NotoSans-Italic` from the local folder
/// `../fonts`.
/// ```
- /// # use typeset::{font::FileSystemFontProvider, font_info};
+ /// # use typeset::{font::FileSystemFontProvider, font};
/// FileSystemFontProvider::new("../fonts", vec![
- /// ("NotoSans-Regular.ttf", font_info!(["NotoSans", SansSerif])),
- /// ("NotoSans-Italic.ttf", font_info!(["NotoSans", SansSerif], italic)),
+ /// ("NotoSans-Regular.ttf", font!["NotoSans", Regular, SansSerif]),
+ /// ("NotoSans-Italic.ttf", font!["NotoSans", Italic, SansSerif]),
/// ]);
/// ```
#[inline]
@@ -395,14 +384,17 @@ impl<'p> FontLoader<'p> {
}
drop(state);
- // The outermost loop goes over the families because we want to serve the font that matches
- // the first possible family.
- for family in &query.families {
- // For each family now go over all font infos from all font providers.
+ // The outermost loop goes over the fallbacks because we want to serve the font that matches
+ // the first possible class.
+ for class in &query.fallback {
+ // For each class now go over all font infos from all font providers.
for (provider, infos) in self.providers.iter().zip(&self.provider_fonts) {
for info in infos.iter() {
- // Proceed only if this font matches the query.
- if Self::matches(&query, family, info) {
+ let matches = info.classes.contains(class)
+ && query.classes.iter().all(|class| info.classes.contains(class));
+
+ // Proceed only if this font matches the query up to now.
+ if matches {
let mut state = self.state.borrow_mut();
// Check if we have already loaded this font before, otherwise, we will load
@@ -483,12 +475,6 @@ impl<'p> FontLoader<'p> {
if maybe_index.is_some() { Some(font) } else { None }
}).collect()
}
-
- /// Checks whether the query and the family match the info.
- fn matches(query: &FontQuery, family: &FontFamily, info: &FontInfo) -> bool {
- info.italic == query.italic && info.bold == query.bold
- && info.families.contains(family)
- }
}
impl Debug for FontLoader<'_> {
@@ -510,13 +496,11 @@ impl Debug for FontLoader<'_> {
pub struct FontQuery {
/// Which character is needed.
pub character: char,
- /// Whether the font should be in italics.
- pub italic: bool,
- /// Whether the font should be in boldface.
- pub bold: bool,
- /// A fallback list of font families to accept. The font matching the first possible family in
- /// this list satisfying all other constraints should be returned.
- pub families: Vec<FontFamily>,
+ /// Which classes the font has to be part of.
+ pub classes: Vec<FontClass>,
+ /// A sequence of classes. The font matching the leftmost class in this sequence
+ /// should be returned.
+ pub fallback: Vec<FontClass>,
}
//------------------------------------------------------------------------------------------------//
@@ -626,7 +610,7 @@ impl<'a> Subsetter<'a> {
mapping,
widths,
default_glyph: self.font.default_glyph,
- metrics: self.font.metrics.clone(),
+ metrics: self.font.metrics,
})
}
@@ -1031,33 +1015,21 @@ mod tests {
/// Tests the font info macro.
#[test]
- fn font_info_macro() {
- use FontFamily::{SansSerif as S, Serif as F, Monospace as M};
- #[allow(non_snake_case)]
- fn N(family: &str) -> FontFamily { FontFamily::Named(family.to_string()) }
-
- assert_eq!(font_info!(["NotoSans", "Noto", SansSerif]), FontInfo {
- families: vec![N("NotoSans"), N("Noto"), S],
- italic: false,
- bold: false,
+ fn font_macro() {
+ use FontClass::*;
+
+ assert_eq!(font!["NotoSans", "Noto", Regular, SansSerif], FontInfo {
+ classes: vec![
+ Family("NotoSans".to_owned()), Family("Noto".to_owned()),
+ Regular, SansSerif
+ ]
});
- assert_eq!(font_info!(["NotoSerif", Serif, "Noto"], italic), FontInfo {
- families: vec![N("NotoSerif"), F, N("Noto")],
- italic: true,
- bold: false,
- });
-
- assert_eq!(font_info!(["NotoSans", "Noto", SansSerif], italic, bold), FontInfo {
- families: vec![N("NotoSans"), N("Noto"), S],
- italic: true,
- bold: true,
- });
-
- assert_eq!(font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace]), FontInfo {
- families: vec![N("NotoEmoji"), N("Noto"), S, F, M],
- italic: false,
- bold: false,
+ assert_eq!(font!["NotoSerif", Serif, Italic, "Noto"], FontInfo {
+ classes: vec![
+ Family("NotoSerif".to_owned()), Serif, Italic,
+ Family("Noto".to_owned())
+ ],
});
}
}
diff --git a/src/func.rs b/src/func.rs
index 0d2a6274..91f3cf74 100644
--- a/src/func.rs
+++ b/src/func.rs
@@ -4,6 +4,7 @@ use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
+use crate::font::FontClass;
use crate::layout::{layout, Layout, LayoutContext, LayoutResult};
use crate::layout::flex::FlexLayout;
use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
@@ -19,14 +20,14 @@ use crate::syntax::{SyntaxTree, FuncHeader};
/// functions, that is they fulfill the bounds `Debug + PartialEq + 'static`.
pub trait Function: FunctionBounds {
/// Parse the header and body into this function given a context.
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
-> ParseResult<Self> where Self: Sized;
/// Layout this function given a context.
///
/// Returns optionally the resulting layout and a new context if changes to the context should
/// be made.
- fn layout(&self, ctx: &LayoutContext) -> LayoutResult<Option<Layout>>;
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>>;
}
impl PartialEq for dyn Function {
@@ -67,7 +68,7 @@ pub struct Scope {
}
/// A function which parses a function invocation into a function type.
-type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, &ParseContext)
+type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext)
-> ParseResult<Box<dyn Function>>;
impl Scope {
@@ -112,12 +113,12 @@ impl Debug for Scope {
/// Creates style functions like bold and italic.
macro_rules! style_func {
($(#[$outer:meta])* pub struct $struct:ident { $name:expr },
- $new_ctx:ident => $ctx_change:block) => {
+ $style:ident => $style_change:block) => {
$(#[$outer])*
#[derive(Debug, PartialEq)]
pub struct $struct { body: SyntaxTree }
impl Function for $struct {
- fn parse(header: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
+ fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
-> ParseResult<Self> where Self: Sized {
// Accept only invocations without arguments and with body.
if header.args.is_empty() && header.kwargs.is_empty() {
@@ -131,13 +132,16 @@ macro_rules! style_func {
}
}
- fn layout(&self, ctx: &LayoutContext) -> LayoutResult<Option<Layout>> {
+ fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
// Change the context.
- let mut $new_ctx = ctx.clone();
- $ctx_change
+ let mut $style = ctx.style.clone();
+ $style_change
// Create a box and put it into a flex layout.
- let boxed = layout(&self.body, &$new_ctx)?;
+ let boxed = layout(&self.body, LayoutContext {
+ style: &$style,
+ .. ctx
+ })?;
let flex = FlexLayout::from_box(boxed);
Ok(Some(Layout::Flex(flex)))
@@ -149,11 +153,11 @@ macro_rules! style_func {
style_func! {
/// Typesets text in bold.
pub struct BoldFunc { "bold" },
- ctx => { ctx.style.bold = !ctx.style.bold }
+ style => { style.toggle_class(FontClass::Bold) }
}
style_func! {
/// Typesets text in italics.
pub struct ItalicFunc { "italic" },
- ctx => { ctx.style.italic = !ctx.style.italic }
+ style => { style.toggle_class(FontClass::Italic) }
}
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index b678ab2b..3ac23005 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -1,9 +1,12 @@
//! The layouting engine.
+use std::borrow::Cow;
+use std::mem;
+
use crate::doc::LayoutAction;
-use crate::font::{FontLoader, FontError};
+use crate::font::{FontLoader, FontClass, FontError};
use crate::size::{Size, Size2D, SizeBox};
-use crate::syntax::{SyntaxTree, Node};
+use crate::syntax::{SyntaxTree, Node, FuncCall};
use crate::style::TextStyle;
use self::flex::{FlexLayout, FlexContext};
@@ -24,13 +27,18 @@ pub enum Layout {
Flex(FlexLayout),
}
+/// Layout a syntax tree in a given context.
+pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<BoxLayout> {
+ Layouter::new(tree, ctx).layout()
+}
+
/// The context for layouting.
-#[derive(Debug, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct LayoutContext<'a, 'p> {
/// Loads fonts matching queries.
pub loader: &'a FontLoader<'p>,
/// Base style to set text with.
- pub style: TextStyle,
+ pub style: &'a TextStyle,
/// The space to layout in.
pub space: LayoutSpace,
}
@@ -57,62 +65,25 @@ impl LayoutSpace {
}
}
-/// Layout a syntax tree in a given context.
-pub fn layout(tree: &SyntaxTree, ctx: &LayoutContext) -> LayoutResult<BoxLayout> {
- Layouter::new(tree, ctx).layout()
-}
-
/// Transforms a syntax tree into a box layout.
#[derive(Debug)]
struct Layouter<'a, 'p> {
tree: &'a SyntaxTree,
box_layouter: BoxLayouter,
flex_layout: FlexLayout,
- flex_ctx: FlexContext,
- text_ctx: TextContext<'a, 'p>,
- func_ctx: LayoutContext<'a, 'p>,
+ loader: &'a FontLoader<'p>,
+ style: Cow<'a, TextStyle>,
}
impl<'a, 'p> Layouter<'a, 'p> {
/// Create a new layouter.
- fn new(tree: &'a SyntaxTree, ctx: &LayoutContext<'a, 'p>) -> Layouter<'a, 'p> {
- // The top-level context for arranging paragraphs.
- let box_ctx = BoxContext { space: ctx.space };
-
- // The sub-level context for arranging pieces of text.
- let flex_ctx = FlexContext {
- space: LayoutSpace {
- dimensions: ctx.space.usable(),
- padding: SizeBox::zero(),
- shrink_to_fit: true,
- },
- flex_spacing: (ctx.style.line_spacing - 1.0) * Size::pt(ctx.style.font_size),
- };
-
- // The mutable context for layouting single pieces of text.
- let text_ctx = TextContext {
- loader: &ctx.loader,
- style: ctx.style.clone(),
- };
-
- // The mutable context for layouting single functions.
- let func_ctx = LayoutContext {
- loader: &ctx.loader,
- style: ctx.style.clone(),
- space: LayoutSpace {
- dimensions: ctx.space.usable(),
- padding: SizeBox::zero(),
- shrink_to_fit: true,
- },
- };
-
+ fn new(tree: &'a SyntaxTree, ctx: LayoutContext<'a, 'p>) -> Layouter<'a, 'p> {
Layouter {
tree,
- box_layouter: BoxLayouter::new(box_ctx),
+ box_layouter: BoxLayouter::new(BoxContext { space: ctx.space }),
flex_layout: FlexLayout::new(),
- flex_ctx,
- text_ctx,
- func_ctx,
+ loader: ctx.loader,
+ style: Cow::Borrowed(ctx.style)
}
}
@@ -122,79 +93,101 @@ impl<'a, 'p> Layouter<'a, 'p> {
for node in &self.tree.nodes {
match node {
// Layout a single piece of text.
- Node::Text(text) => {
- let boxed = self::text::layout(text, &self.text_ctx)?;
- self.flex_layout.add_box(boxed);
- },
+ Node::Text(text) => self.layout_text(text, false)?,
+
+ // Add a space.
Node::Space => {
if !self.flex_layout.is_empty() {
- let boxed = self::text::layout(" ", &self.text_ctx)?;
- self.flex_layout.add_glue(boxed);
+ self.layout_text(" ", true)?;
}
},
// Finish the current flex layout and add it to the box layouter.
- // Then start a new flex layouting process.
Node::Newline => {
// Finish the current paragraph into a box and add it.
- let boxed = self.flex_layout.finish(self.flex_ctx)?;
- self.box_layouter.add_box(boxed)?;
+ self.layout_flex()?;
- // Create a fresh flex layout for the next paragraph.
- self.flex_ctx.space.dimensions = self.box_layouter.remaining();
- self.flex_layout = FlexLayout::new();
- self.add_paragraph_spacing()?;
+ // Add some paragraph spacing.
+ let size = Size::pt(self.style.font_size)
+ * (self.style.line_spacing * self.style.paragraph_spacing - 1.0);
+ self.box_layouter.add_space(size)?;
},
// Toggle the text styles.
- Node::ToggleItalics => {
- self.text_ctx.style.italic = !self.text_ctx.style.italic;
- self.func_ctx.style.italic = !self.func_ctx.style.italic;
- },
- Node::ToggleBold => {
- self.text_ctx.style.bold = !self.text_ctx.style.bold;
- self.func_ctx.style.bold = !self.func_ctx.style.bold;
- },
+ Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
+ Node::ToggleBold => self.style.to_mut().toggle_class(FontClass::Bold),
// Execute a function.
- Node::Func(func) => {
- self.func_ctx.space.dimensions = self.box_layouter.remaining();
- let layout = func.body.layout(&self.func_ctx)?;
-
- // Add the potential layout.
- if let Some(layout) = layout {
- match layout {
- Layout::Boxed(boxed) => {
- // Finish the previous flex run before adding the box.
- let previous = self.flex_layout.finish(self.flex_ctx)?;
- self.box_layouter.add_box(previous)?;
- self.box_layouter.add_box(boxed)?;
-
- // Create a fresh flex layout for the following content.
- self.flex_ctx.space.dimensions = self.box_layouter.remaining();
- self.flex_layout = FlexLayout::new();
- },
- Layout::Flex(flex) => self.flex_layout.add_flexible(flex),
- }
- }
- },
+ Node::Func(func) => self.layout_func(func)?,
}
}
// If there are remainings, add them to the layout.
if !self.flex_layout.is_empty() {
- let boxed = self.flex_layout.finish(self.flex_ctx)?;
- self.box_layouter.add_box(boxed)?;
+ self.layout_flex()?;
}
Ok(self.box_layouter.finish())
}
- /// Add the spacing between two paragraphs.
- fn add_paragraph_spacing(&mut self) -> LayoutResult<()> {
- let size = Size::pt(self.text_ctx.style.font_size)
- * (self.text_ctx.style.line_spacing * self.text_ctx.style.paragraph_spacing - 1.0);
- self.box_layouter.add_space(size)
+ /// Layout a piece of text into a box.
+ fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
+ let boxed = self::text::layout(text, TextContext {
+ loader: &self.loader,
+ style: &self.style,
+ })?;
+
+ if glue {
+ self.flex_layout.add_glue(boxed);
+ } else {
+ self.flex_layout.add_box(boxed);
+ }
+
+ Ok(())
+ }
+
+ /// Finish the current flex run and return the resulting box.
+ fn layout_flex(&mut self) -> LayoutResult<()> {
+ let mut layout = FlexLayout::new();
+ mem::swap(&mut layout, &mut self.flex_layout);
+
+ let boxed = layout.finish(FlexContext {
+ space: LayoutSpace {
+ dimensions: self.box_layouter.remaining(),
+ padding: SizeBox::zero(),
+ shrink_to_fit: true,
+ },
+ flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
+ })?;
+
+ self.box_layouter.add_box(boxed)
+ }
+
+ /// Layout a function.
+ fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
+ let layout = func.body.layout(LayoutContext {
+ loader: &self.loader,
+ style: &self.style,
+ space: LayoutSpace {
+ dimensions: self.box_layouter.remaining(),
+ padding: SizeBox::zero(),
+ shrink_to_fit: true,
+ },
+ })?;
+
+ // Add the potential layout.
+ if let Some(layout) = layout {
+ match layout {
+ Layout::Boxed(boxed) => {
+ // Finish the previous flex run before adding the box.
+ self.layout_flex()?;
+ self.box_layouter.add_box(boxed)?;
+ },
+ Layout::Flex(flex) => self.flex_layout.add_flexible(flex),
+ }
+ }
+
+ Ok(())
}
}
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 0a0241a0..75a59254 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -7,16 +7,16 @@ use super::*;
/// The context for text layouting.
-#[derive(Debug, Clone)]
+#[derive(Debug, Copy, Clone)]
pub struct TextContext<'a, 'p> {
/// Loads fonts matching queries.
pub loader: &'a FontLoader<'p>,
/// Base style to set text with.
- pub style: TextStyle,
+ pub style: &'a TextStyle,
}
/// Layout one piece of text without any breaks as one continous box.
-pub fn layout(text: &str, ctx: &TextContext) -> LayoutResult<BoxLayout> {
+pub fn layout(text: &str, ctx: TextContext) -> LayoutResult<BoxLayout> {
let mut actions = Vec::new();
let mut active_font = std::usize::MAX;
let mut buffer = String::new();
@@ -26,9 +26,8 @@ pub fn layout(text: &str, ctx: &TextContext) -> LayoutResult<BoxLayout> {
for character in text.chars() {
// Retrieve the best font for this character.
let (index, font) = ctx.loader.get(FontQuery {
- families: ctx.style.font_families.clone(),
- italic: ctx.style.italic,
- bold: ctx.style.bold,
+ classes: ctx.style.classes.clone(),
+ fallback: ctx.style.fallback.clone(),
character,
}).ok_or_else(|| LayoutError::NoSuitableFont(character))?;
diff --git a/src/lib.rs b/src/lib.rs
index 4799188b..1c5cf325 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,7 +16,7 @@
//! ```
//! use std::fs::File;
//! use typeset::Typesetter;
-//! use typeset::{font::FileSystemFontProvider, font_info};
+//! use typeset::{font::FileSystemFontProvider, font};
//! use typeset::export::pdf::PdfExporter;
//!
//! // Simple example source code.
@@ -26,9 +26,9 @@
//! // (two sans-serif fonts and a fallback for the emoji).
//! let mut typesetter = Typesetter::new();
//! typesetter.add_font_provider(FileSystemFontProvider::new("../fonts", vec![
-//! ("CMU-Serif-Regular.ttf", font_info!(["Computer Modern", Serif])),
-//! ("CMU-Serif-Italic.ttf", font_info!(["Computer Modern", Serif], italic)),
-//! ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
+//! ("CMU-Serif-Regular.ttf", font!["Computer Modern", Regular, Serif]),
+//! ("CMU-Serif-Italic.ttf", font!["Computer Modern", Italic, Serif]),
+//! ("NotoEmoji-Regular.ttf", font!["Noto", Regular, Serif, SansSerif, Monospace]),
//! ]));
//! // Typeset the source code into a document.
//! let document = typesetter.typeset(src).unwrap();
@@ -112,24 +112,21 @@ impl<'p> Typesetter<'p> {
#[inline]
pub fn parse(&self, src: &str) -> ParseResult<SyntaxTree> {
let scope = Scope::with_std();
- let ctx = ParseContext { scope: &scope };
- parse(src, &ctx)
+ parse(src, ParseContext { scope: &scope })
}
/// Layout a syntax tree and return the layout and the referenced font list.
pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<(BoxLayout, Vec<Font>)> {
let loader = FontLoader::new(&self.font_providers);
- let ctx = LayoutContext {
+ let pages = layout(&tree, LayoutContext {
loader: &loader,
- style: self.text_style.clone(),
+ style: &self.text_style,
space: LayoutSpace {
dimensions: self.page_style.dimensions,
padding: self.page_style.margins,
shrink_to_fit: false,
},
- };
-
- let pages = layout(&tree, &ctx)?;
+ })?;
Ok((pages, loader.into_fonts()))
}
@@ -182,25 +179,25 @@ mod test {
use std::io::BufWriter;
use crate::Typesetter;
use crate::export::pdf::PdfExporter;
- use crate::font::FileSystemFontProvider;
+ use crate::font::{FileSystemFontProvider};
/// Create a _PDF_ with a name from the source code.
fn test(name: &str, src: &str) {
let mut typesetter = Typesetter::new();
typesetter.add_font_provider(FileSystemFontProvider::new("../fonts", vec![
- ("CMU-SansSerif-Regular.ttf", font_info!(["Computer Modern", SansSerif])),
- ("CMU-SansSerif-Italic.ttf", font_info!(["Computer Modern", SansSerif], italic)),
- ("CMU-SansSerif-Bold.ttf", font_info!(["Computer Modern", SansSerif], bold)),
- ("CMU-SansSerif-Bold-Italic.ttf", font_info!(["Computer Modern", SansSerif], bold, italic)),
- ("CMU-Serif-Regular.ttf", font_info!(["Computer Modern", Serif])),
- ("CMU-Serif-Italic.ttf", font_info!(["Computer Modern", Serif], italic)),
- ("CMU-Serif-Bold.ttf", font_info!(["Computer Modern", Serif], bold)),
- ("CMU-Serif-Bold-Italic.ttf", font_info!(["Computer Modern", Serif], bold, italic)),
- ("CMU-Typewriter-Regular.ttf", font_info!(["Computer Modern", Monospace])),
- ("CMU-Typewriter-Italic.ttf", font_info!(["Computer Modern", Monospace], italic)),
- ("CMU-Typewriter-Bold.ttf", font_info!(["Computer Modern", Monospace], bold)),
- ("CMU-Typewriter-Bold-Italic.ttf", font_info!(["Computer Modern", Monospace], bold, italic)),
- ("NotoEmoji-Regular.ttf", font_info!(["NotoEmoji", "Noto", SansSerif, Serif, Monospace])),
+ ("CMU-SansSerif-Regular.ttf", font!["Computer Modern", Regular, SansSerif]),
+ ("CMU-SansSerif-Italic.ttf", font!["Computer Modern", Italic, SansSerif]),
+ ("CMU-SansSerif-Bold.ttf", font!["Computer Modern", Bold, SansSerif]),
+ ("CMU-SansSerif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, SansSerif]),
+ ("CMU-Serif-Regular.ttf", font!["Computer Modern", Regular, Serif]),
+ ("CMU-Serif-Italic.ttf", font!["Computer Modern", Italic, Serif]),
+ ("CMU-Serif-Bold.ttf", font!["Computer Modern", Bold, Serif]),
+ ("CMU-Serif-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Serif]),
+ ("CMU-Typewriter-Regular.ttf", font!["Computer Modern", Regular, Monospace]),
+ ("CMU-Typewriter-Italic.ttf", font!["Computer Modern", Italic, Monospace]),
+ ("CMU-Typewriter-Bold.ttf", font!["Computer Modern", Bold, Monospace]),
+ ("CMU-Typewriter-Bold-Italic.ttf", font!["Computer Modern", Bold, Italic, Monospace]),
+ ("NotoEmoji-Regular.ttf", font!["Noto", Regular, SansSerif, Serif, Monospace]),
]));
// Typeset into document.
diff --git a/src/parsing.rs b/src/parsing.rs
index 81a729ab..79066e47 100644
--- a/src/parsing.rs
+++ b/src/parsing.rs
@@ -326,12 +326,12 @@ impl Iterator for PeekableChars<'_> {
/// Parses source code into a syntax tree given a context.
#[inline]
-pub fn parse(src: &str, ctx: &ParseContext) -> ParseResult<SyntaxTree> {
+pub fn parse(src: &str, ctx: ParseContext) -> ParseResult<SyntaxTree> {
Parser::new(src, ctx).parse()
}
/// The context for parsing.
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
pub struct ParseContext<'a> {
/// The scope containing function definitions.
pub scope: &'a Scope,
@@ -343,7 +343,7 @@ struct Parser<'s> {
src: &'s str,
tokens: PeekableTokens<'s>,
state: ParserState,
- ctx: &'s ParseContext<'s>,
+ ctx: ParseContext<'s>,
tree: SyntaxTree,
}
@@ -360,12 +360,12 @@ enum ParserState {
impl<'s> Parser<'s> {
/// Create a new parser from the source and the context.
- fn new(src: &'s str, ctx: &'s ParseContext) -> Parser<'s> {
+ fn new(src: &'s str, ctx: ParseContext<'s>) -> Parser<'s> {
Parser {
src,
tokens: PeekableTokens::new(tokenize(src)),
- ctx,
state: ParserState::Body,
+ ctx,
tree: SyntaxTree::new(),
}
}
@@ -813,7 +813,7 @@ mod parse_tests {
pub struct TreeFn(pub SyntaxTree);
impl Function for TreeFn {
- fn parse(_: &FuncHeader, body: Option<&str>, ctx: &ParseContext)
+ fn parse(_: &FuncHeader, body: Option<&str>, ctx: ParseContext)
-> ParseResult<Self> where Self: Sized {
if let Some(src) = body {
parse(src, ctx).map(|tree| TreeFn(tree))
@@ -822,7 +822,7 @@ mod parse_tests {
}
}
- fn layout(&self, _: &LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
+ fn layout(&self, _: LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
}
/// A testing function without a body.
@@ -830,7 +830,7 @@ mod parse_tests {
pub struct BodylessFn;
impl Function for BodylessFn {
- fn parse(_: &FuncHeader, body: Option<&str>, _: &ParseContext)
+ fn parse(_: &FuncHeader, body: Option<&str>, _: ParseContext)
-> ParseResult<Self> where Self: Sized {
if body.is_none() {
Ok(BodylessFn)
@@ -839,32 +839,32 @@ mod parse_tests {
}
}
- fn layout(&self, _: &LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
+ fn layout(&self, _: LayoutContext) -> LayoutResult<Option<Layout>> { Ok(None) }
}
}
/// Test if the source code parses into the syntax tree.
fn test(src: &str, tree: SyntaxTree) {
let ctx = ParseContext { scope: &Scope::new() };
- assert_eq!(parse(src, &ctx).unwrap(), tree);
+ assert_eq!(parse(src, ctx).unwrap(), tree);
}
/// Test with a scope containing function definitions.
fn test_scoped(scope: &Scope, src: &str, tree: SyntaxTree) {
let ctx = ParseContext { scope };
- assert_eq!(parse(src, &ctx).unwrap(), tree);
+ assert_eq!(parse(src, ctx).unwrap(), tree);
}
/// Test if the source parses into the error.
fn test_err(src: &str, err: &str) {
let ctx = ParseContext { scope: &Scope::new() };
- assert_eq!(parse(src, &ctx).unwrap_err().to_string(), err);
+ assert_eq!(parse(src, ctx).unwrap_err().to_string(), err);
}
/// Test with a scope if the source parses into the error.
fn test_err_scoped(scope: &Scope, src: &str, err: &str) {
let ctx = ParseContext { scope };
- assert_eq!(parse(src, &ctx).unwrap_err().to_string(), err);
+ assert_eq!(parse(src, ctx).unwrap_err().to_string(), err);
}
/// Create a text node.
diff --git a/src/style.rs b/src/style.rs
index cd0a9579..042042bf 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -1,18 +1,17 @@
//! Styles for layouting.
-use crate::font::FontFamily;
+use crate::font::FontClass;
use crate::size::{Size, Size2D, SizeBox};
/// Default styles for text.
#[derive(Debug, Clone)]
pub struct TextStyle {
- /// A fallback list of font families to use.
- pub font_families: Vec<FontFamily>,
- /// Whether the font is in italics.
- pub italic: bool,
- /// Whether the font is bold.
- pub bold: bool,
+ /// The classes the font we want has to be part of.
+ pub classes: Vec<FontClass>,
+ /// A sequence of classes. We need the font to be part of at least one of these
+ /// and preferably the leftmost possible.
+ pub fallback: Vec<FontClass>,
/// The font size.
pub font_size: f32,
/// The line spacing (as a multiple of the font size).
@@ -21,14 +20,35 @@ pub struct TextStyle {
pub paragraph_spacing: f32,
}
+impl TextStyle {
+ /// Toggle a class.
+ ///
+ /// If the class was one of _italic_ or _bold_, then:
+ /// - If it was not present, the _regular_ class will be removed.
+ /// - If it was present, the _regular_ class will be added in case the
+ /// other style class is not present.
+ pub fn toggle_class(&mut self, class: FontClass) {
+ if self.classes.contains(&class) {
+ self.classes.retain(|x| x != &class);
+ if (class == FontClass::Italic && !self.classes.contains(&FontClass::Bold))
+ || (class == FontClass::Bold && !self.classes.contains(&FontClass::Italic)) {
+ self.classes.push(FontClass::Regular);
+ }
+ } else {
+ if class == FontClass::Italic || class == FontClass::Bold {
+ self.classes.retain(|x| x != &FontClass::Regular);
+ }
+ self.classes.push(class);
+ }
+ }
+}
+
impl Default for TextStyle {
fn default() -> TextStyle {
- use FontFamily::*;
+ use FontClass::*;
TextStyle {
- // Default font family, font size and line spacing.
- font_families: vec![Serif, SansSerif, Monospace],
- italic: false,
- bold: false,
+ classes: vec![Regular],
+ fallback: vec![Serif],
font_size: 11.0,
line_spacing: 1.2,
paragraph_spacing: 1.5,