typst/src/layout/model.rs
Laurenz 06dbac6efd Port font handling to fontdock and ttf-parser 🛳
- Use fontdock for indexing fonts and querying
- Typst binary now automatically indexes and uses system fonts in addition to a fixed font folder!
- Removes subsetting support for now (was half-finished anyways, plan is to use harfbuzz for subsetting in the future)
- Adds font width configuration support
2020-08-01 00:10:54 +02:00

317 lines
11 KiB
Rust

//! The model layouter layouts models (i.e.
//! [syntax models](crate::syntax::SyntaxModel) and [functions](crate::func))
//! by executing commands issued by the models.
use std::future::Future;
use std::pin::Pin;
use smallvec::smallvec;
use crate::{Pass, Feedback};
use crate::SharedFontLoader;
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::length::{Length, Size};
use crate::syntax::{Model, SyntaxModel, Node, Decoration};
use crate::syntax::span::{Span, Spanned};
use super::line::{LineLayouter, LineContext};
use super::text::{layout_text, TextContext};
use super::*;
/// Performs the model layouting.
#[derive(Debug)]
pub struct ModelLayouter<'a> {
ctx: LayoutContext<'a>,
layouter: LineLayouter,
style: LayoutStyle,
feedback: Feedback,
}
/// The context for layouting.
#[derive(Debug, Clone)]
pub struct LayoutContext<'a> {
/// The font loader to retrieve fonts from when typesetting text
/// using [`layout_text`].
pub loader: &'a SharedFontLoader,
/// The style for pages and text.
pub style: &'a LayoutStyle,
/// The base unpadded dimensions of this container (for relative sizing).
pub base: Size,
/// The spaces to layout in.
pub spaces: LayoutSpaces,
/// Whether to have repeated spaces or to use only the first and only once.
pub repeat: bool,
/// The initial axes along which content is laid out.
pub axes: LayoutAxes,
/// The alignment of the finished layout.
pub alignment: LayoutAlignment,
/// Whether the layout that is to be created will be nested in a parent
/// container.
pub nested: bool,
/// Whether to render debug boxs around layouts if `nested` is true.
pub debug: bool,
}
/// A sequence of layouting commands.
pub type Commands<'a> = Vec<Command<'a>>;
/// Commands issued to the layouting engine by models.
#[derive(Debug, Clone)]
pub enum Command<'a> {
/// Layout the given model in the current context (i.e. not nested). The
/// content of the model is not laid out into a separate box and then added,
/// but simply laid out flat in the active layouting process.
///
/// This has the effect that the content fits nicely into the active line
/// layouting, enabling functions to e.g. change the style of some piece of
/// text while keeping it integrated in the current paragraph.
LayoutSyntaxModel(&'a SyntaxModel),
/// Add a already computed layout.
Add(Layout),
/// Add multiple layouts, one after another. This is equivalent to multiple
/// [Add](Command::Add) commands.
AddMultiple(MultiLayout),
/// Add spacing of given [kind](super::SpacingKind) along the primary or
/// secondary axis. The spacing kind defines how the spacing interacts with
/// surrounding spacing.
AddSpacing(Length, SpacingKind, GenericAxis),
/// Start a new line.
BreakLine,
/// Start a new paragraph.
BreakParagraph,
/// Start a new page, which will exist in the finished layout even if it
/// stays empty (since the page break is a _hard_ space break).
BreakPage,
/// Update the text style.
SetTextStyle(TextStyle),
/// Update the page style.
SetPageStyle(PageStyle),
/// Update the alignment for future boxes added to this layouting process.
SetAlignment(LayoutAlignment),
/// Update the layouting axes along which future boxes will be laid out.
/// This finishes the current line.
SetAxes(LayoutAxes),
}
/// Layout a syntax model into a list of boxes.
pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_>) -> Pass<MultiLayout> {
let mut layouter = ModelLayouter::new(ctx);
layouter.layout_syntax_model(model).await;
layouter.finish()
}
/// A dynamic future type which allows recursive invocation of async functions
/// when used as the return type. This is also how the async trait functions
/// work internally.
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
impl<'a> ModelLayouter<'a> {
/// Create a new model layouter.
pub fn new(ctx: LayoutContext<'a>) -> ModelLayouter<'a> {
ModelLayouter {
layouter: LineLayouter::new(LineContext {
spaces: ctx.spaces.clone(),
axes: ctx.axes,
alignment: ctx.alignment,
repeat: ctx.repeat,
debug: ctx.debug && ctx.nested,
line_spacing: ctx.style.text.line_spacing(),
}),
style: ctx.style.clone(),
ctx,
feedback: Feedback::new(),
}
}
/// Flatly layout a model into this layouting process.
pub fn layout<'r>(
&'r mut self,
model: Spanned<&'r dyn Model>
) -> DynFuture<'r, ()> { Box::pin(async move {
// Execute the model's layout function which generates the commands.
let layouted = model.v.layout(LayoutContext {
style: &self.style,
spaces: self.layouter.remaining(),
nested: true,
.. self.ctx
}).await;
// Add the errors generated by the model to the error list.
self.feedback.extend_offset(layouted.feedback, model.span.start);
for command in layouted.output {
self.execute_command(command, model.span).await;
}
}) }
/// Layout a syntax model by directly processing the nodes instead of using
/// the command based architecture.
pub fn layout_syntax_model<'r>(
&'r mut self,
model: &'r SyntaxModel
) -> DynFuture<'r, ()> { Box::pin(async move {
use Node::*;
for Spanned { v: node, span } in &model.nodes {
let decorate = |this: &mut ModelLayouter, deco| {
this.feedback.decorations.push(Spanned::new(deco, *span));
};
match node {
Space => self.layout_space(),
Parbreak => self.layout_paragraph(),
Linebreak => self.layouter.finish_line(),
Text(text) => {
if self.style.text.italic {
decorate(self, Decoration::Italic);
}
if self.style.text.bolder {
decorate(self, Decoration::Bold);
}
self.layout_text(text).await;
}
ToggleItalic => {
self.style.text.italic = !self.style.text.italic;
decorate(self, Decoration::Italic);
}
ToggleBolder => {
self.style.text.bolder = !self.style.text.bolder;
decorate(self, Decoration::Bold);
}
Raw(lines) => {
// TODO: Make this more efficient.
let fallback = self.style.text.fallback.clone();
self.style.text.fallback.list_mut().insert(0, "monospace".to_string());
self.style.text.fallback.flatten();
// Layout the first line.
let mut iter = lines.iter();
if let Some(line) = iter.next() {
self.layout_text(line).await;
}
// Put a newline before each following line.
for line in iter {
self.layouter.finish_line();
self.layout_text(line).await;
}
self.style.text.fallback = fallback;
}
Model(model) => {
self.layout(Spanned::new(model.as_ref(), *span)).await;
}
}
}
}) }
/// Compute the finished list of boxes.
pub fn finish(self) -> Pass<MultiLayout> {
Pass::new(self.layouter.finish(), self.feedback)
}
/// Execute a command issued by a model. When the command is errorful, the
/// given span is stored with the error.
fn execute_command<'r>(
&'r mut self,
command: Command<'r>,
model_span: Span,
) -> DynFuture<'r, ()> { Box::pin(async move {
use Command::*;
match command {
LayoutSyntaxModel(model) => self.layout_syntax_model(model).await,
Add(layout) => self.layouter.add(layout),
AddMultiple(layouts) => self.layouter.add_multiple(layouts),
AddSpacing(space, kind, axis) => match axis {
Primary => self.layouter.add_primary_spacing(space, kind),
Secondary => self.layouter.add_secondary_spacing(space, kind),
}
BreakLine => self.layouter.finish_line(),
BreakParagraph => self.layout_paragraph(),
BreakPage => {
if self.ctx.nested {
error!(
@self.feedback, model_span,
"page break cannot be issued from nested context",
);
} else {
self.layouter.finish_space(true)
}
}
SetTextStyle(style) => {
self.layouter.set_line_spacing(style.line_spacing());
self.style.text = style;
}
SetPageStyle(style) => {
if self.ctx.nested {
error!(
@self.feedback, model_span,
"page style cannot be changed from nested context",
);
} else {
self.style.page = style;
// The line layouter has no idea of page styles and thus we
// need to recompute the layouting space resulting of the
// new page style and update it within the layouter.
let margins = style.margins();
self.ctx.base = style.dimensions.unpadded(margins);
self.layouter.set_spaces(smallvec![
LayoutSpace {
dimensions: style.dimensions,
padding: margins,
expansion: LayoutExpansion::new(true, true),
}
], true);
}
}
SetAlignment(alignment) => self.ctx.alignment = alignment,
SetAxes(axes) => {
self.layouter.set_axes(axes);
self.ctx.axes = axes;
}
}
}) }
/// Layout a continous piece of text and add it to the line layouter.
async fn layout_text(&mut self, text: &str) {
self.layouter.add(layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style.text,
axes: self.ctx.axes,
alignment: self.ctx.alignment,
}).await)
}
/// Add the spacing for a syntactic space node.
fn layout_space(&mut self) {
self.layouter.add_primary_spacing(
self.style.text.word_spacing(),
SpacingKind::WORD,
);
}
/// Finish the paragraph and add paragraph spacing.
fn layout_paragraph(&mut self) {
self.layouter.add_secondary_spacing(
self.style.text.paragraph_spacing(),
SpacingKind::PARAGRAPH,
);
}
}