Upgrade to new toddle interface 🐳

This commit is contained in:
Laurenz 2020-02-03 10:37:50 +01:00
parent 20fb4e7c37
commit 40ea35cbe7
15 changed files with 157 additions and 138 deletions

View File

@ -6,7 +6,7 @@ edition = "2018"
build = "build.rs"
[dependencies]
toddle = { path = "../toddle", default-features = false }
toddle = { path = "../toddle", features = ["query"], default-features = false }
tide = { path = "../tide" }
byteorder = "1"
smallvec = "1"

View File

@ -3,9 +3,9 @@ use std::io::BufWriter;
use std::path::{Path, PathBuf};
use futures_executor::block_on;
use typstc::Typesetter;
use typstc::toddle::query::FileSystemFontProvider;
use typstc::export::pdf::PdfExporter;
use typstc::{Typesetter, DynErrorProvider};
use typstc::toddle::query::fs::EagerFsProvider;
use typstc::export::pdf;
fn main() {
@ -37,15 +37,14 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let src = read_to_string(source)
.map_err(|_| "failed to read from source file")?;
let mut typesetter = Typesetter::new();
let provider = FileSystemFontProvider::from_index("../fonts/index.json").unwrap();
typesetter.add_font_provider(provider);
let (fs, entries) = EagerFsProvider::from_index("../fonts", "index.json")?;
let provider = DynErrorProvider::new(fs);
let typesetter = Typesetter::new((Box::new(provider), entries));
let layouts = block_on(typesetter.typeset(&src));
let exporter = PdfExporter::new();
let writer = BufWriter::new(File::create(&dest)?);
exporter.export(&layouts, typesetter.loader(), writer)?;
pdf::export(&layouts, typesetter.loader(), writer)?;
Ok(())
}

View File

@ -11,45 +11,35 @@ use tide::font::{
CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord,
};
use toddle::Error as FontError;
use toddle::font::OwnedFont;
use toddle::query::{SharedFontLoader, FontIndex};
use toddle::{Font, OwnedFont, LoadError};
use toddle::types::Tag;
use toddle::query::FontIndex;
use toddle::tables::{
CharMap, Header, HorizontalMetrics, MacStyleFlags,
Name, NameEntry, Post, OS2,
};
use crate::GlobalFontLoader;
use crate::layout::{MultiLayout, Layout, LayoutAction};
use crate::size::Size;
/// Exports layouts into _PDFs_.
pub struct PdfExporter {}
impl PdfExporter {
/// Create a new exporter.
pub fn new() -> PdfExporter {
PdfExporter {}
}
/// Export a layouted list of boxes. The same font loader as used for
/// layouting needs to be passed in here since the layout only contains
/// indices referencing the loaded fonts. The raw PDF ist written into the
/// target writable, returning the number of bytes written.
pub fn export<W: Write>(
&self,
layout: &MultiLayout,
loader: &SharedFontLoader,
target: W,
) -> PdfResult<usize> {
ExportProcess::new(layout, loader, target)?.write()
}
/// Export a layouted list of boxes. The same font loader as used for
/// layouting needs to be passed in here since the layout only contains
/// indices referencing the loaded fonts. The raw PDF ist written into the
/// target writable, returning the number of bytes written.
pub fn export<W: Write>(
layout: &MultiLayout,
loader: &GlobalFontLoader,
target: W,
) -> PdfResult<usize> {
PdfExporter::new(layout, loader, target)?.write()
}
/// The data relevant to the export of one document.
struct ExportProcess<'d, W: Write> {
struct PdfExporter<'a, W: Write> {
writer: PdfWriter<W>,
layouts: &'d MultiLayout,
layouts: &'a MultiLayout,
/// Since we cross-reference pages and fonts with their IDs already in the document
/// catalog, we need to know exactly which ID is used for what from the beginning.
@ -76,18 +66,18 @@ struct Offsets {
fonts: (Ref, Ref),
}
impl<'d, W: Write> ExportProcess<'d, W> {
impl<'a, W: Write> PdfExporter<'a, W> {
/// Prepare the export. Only once [`ExportProcess::write`] is called the
/// writing really happens.
fn new(
layouts: &'d MultiLayout,
font_loader: &SharedFontLoader,
layouts: &'a MultiLayout,
font_loader: &GlobalFontLoader,
target: W,
) -> PdfResult<ExportProcess<'d, W>> {
) -> PdfResult<PdfExporter<'a, W>> {
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
let offsets = Self::calculate_offsets(layouts.len(), fonts.len());
Ok(ExportProcess {
Ok(PdfExporter {
writer: PdfWriter::new(target),
layouts,
offsets,
@ -101,8 +91,8 @@ impl<'d, W: Write> ExportProcess<'d, W> {
/// one used in the PDF. The new ones index into the returned vector of
/// owned fonts.
fn subset_fonts(
layouts: &'d MultiLayout,
font_loader: &SharedFontLoader
layouts: &'a MultiLayout,
font_loader: &GlobalFontLoader,
) -> PdfResult<(Vec<OwnedFont>, HashMap<FontIndex, usize>)> {
let mut fonts = Vec::new();
let mut font_chars: HashMap<FontIndex, HashSet<char>> = HashMap::new();
@ -144,18 +134,22 @@ impl<'d, W: Write> ExportProcess<'d, W> {
let mut font_loader = font_loader.borrow_mut();
// All tables not listed here are dropped.
const SUBSET_TABLES: [&str; 13] = [
"name", "OS/2", "post", "head", "hhea", "hmtx", "maxp",
"cmap", "cvt ", "fpgm", "prep", "loca", "glyf",
];
let tables: Vec<_> = [
b"name", b"OS/2", b"post", b"head", b"hhea", b"hmtx", b"maxp",
b"cmap", b"cvt ", b"fpgm", b"prep", b"loca", b"glyf",
].iter().map(|&s| Tag(*s)).collect();
// Do the subsetting.
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)
.map(|bytes| OwnedFont::from_bytes(bytes))
.unwrap_or_else(|_| font.to_owned())?;
let chars = font_chars[&old_index].iter().cloned();
let subsetted = match font.subsetted(chars, tables.iter().copied()) {
Ok(data) => Font::from_bytes(data)?,
Err(_) => font.clone(),
};
fonts.push(subsetted);
}
@ -302,7 +296,7 @@ impl<'d, W: Write> ExportProcess<'d, W> {
id,
Type0Font::new(
base_font.clone(),
CMapEncoding::Predefined("Identity-H".to_owned()),
CMapEncoding::Predefined("Identity-H".to_string()),
id + 1,
)
.to_unicode(id + 3),
@ -409,7 +403,7 @@ fn ids((start, end): (Ref, Ref)) -> impl Iterator<Item = Ref> {
/// The error type for _PDF_ exporting.
pub enum PdfExportError {
/// An error occured while subsetting the font for the _PDF_.
Font(FontError),
Font(LoadError),
/// An I/O Error on the underlying writable.
Io(io::Error),
}
@ -418,13 +412,13 @@ error_type! {
self: PdfExportError,
res: PdfResult,
show: f => match self {
PdfExportError::Font(err) => write!(f, "font error: {}", err),
PdfExportError::Io(err) => write!(f, "io error: {}", err),
PdfExportError::Font(err) => err.fmt(f),
PdfExportError::Io(err) => err.fmt(f),
},
source: match self {
PdfExportError::Font(err) => Some(err),
PdfExportError::Io(err) => Some(err),
},
from: (err: io::Error, PdfExportError::Io(err)),
from: (err: FontError, PdfExportError::Font(err)),
from: (err: LoadError, PdfExportError::Font(err)),
}

View File

@ -145,15 +145,14 @@ macro_rules! function {
(@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => {
impl $crate::syntax::Model for $name {
fn layout<'a, 'b, 'c, 't>(
fn layout<'a, 'b, 't>(
#[allow(unused)] &'a $this,
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b, 'c>,
#[allow(unused)] mut $ctx: $crate::layout::LayoutContext<'b>,
) -> $crate::layout::DynFuture<'t, $crate::layout::Layouted<
$crate::layout::Commands<'a>>
> where
'a: 't,
'b: 't,
'c: 't,
Self: 't,
{
Box::pin(async move {

View File

@ -5,8 +5,9 @@
use std::future::Future;
use std::pin::Pin;
use smallvec::smallvec;
use toddle::query::SharedFontLoader;
use toddle::query::{SharedFontLoader, FontProvider};
use crate::GlobalFontLoader;
use crate::error::Errors;
use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::size::{Size, Size2D};
@ -18,8 +19,8 @@ use super::*;
/// Performs the model layouting.
pub struct ModelLayouter<'a, 'p> {
ctx: LayoutContext<'a, 'p>,
pub struct ModelLayouter<'a> {
ctx: LayoutContext<'a>,
layouter: LineLayouter,
style: LayoutStyle,
errors: Errors,
@ -27,10 +28,10 @@ pub struct ModelLayouter<'a, 'p> {
/// The context for layouting.
#[derive(Debug, Clone)]
pub struct LayoutContext<'a, 'p> {
pub struct LayoutContext<'a> {
/// The font loader to retrieve fonts from when typesetting text
/// using [`layout_text`].
pub loader: &'a SharedFontLoader<'p>,
pub loader: &'a GlobalFontLoader,
/// The style for pages and text.
pub style: &'a LayoutStyle,
/// The base unpadded dimensions of this container (for relative sizing).
@ -105,7 +106,7 @@ pub enum Command<'a> {
}
/// Layout a syntax model into a list of boxes.
pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted<MultiLayout> {
pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_>) -> Layouted<MultiLayout> {
let mut layouter = ModelLayouter::new(ctx);
layouter.layout_syntax_model(model).await;
layouter.finish()
@ -116,9 +117,9 @@ pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted
/// work internally.
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
impl<'a, 'p> ModelLayouter<'a, 'p> {
impl<'a> ModelLayouter<'a> {
/// Create a new model layouter.
pub fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> {
pub fn new(ctx: LayoutContext<'a>) -> ModelLayouter<'a> {
ModelLayouter {
layouter: LineLayouter::new(LineContext {
spaces: ctx.spaces.clone(),
@ -182,6 +183,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
Some("monospace") => { list.remove(0); },
_ => list.insert(0, "monospace".to_string()),
}
self.style.text.fallback.flatten();
}
Node::Model(model) => {

View File

@ -4,17 +4,18 @@
//! When the primary layouting axis horizontally inversed, the word is spelled
//! backwards. Vertical word layout is not yet supported.
use toddle::query::{SharedFontLoader, FontQuery, FontIndex};
use toddle::query::{FontQuery, FontIndex};
use toddle::tables::{CharMap, Header, HorizontalMetrics};
use crate::GlobalFontLoader;
use crate::size::{Size, Size2D};
use crate::style::TextStyle;
use super::*;
/// Performs the text layouting.
struct TextLayouter<'a, 'p> {
ctx: TextContext<'a, 'p>,
struct TextLayouter<'a> {
ctx: TextContext<'a>,
text: &'a str,
actions: LayoutActions,
buffer: String,
@ -24,10 +25,10 @@ struct TextLayouter<'a, 'p> {
/// The context for text layouting.
#[derive(Copy, Clone)]
pub struct TextContext<'a, 'p> {
pub struct TextContext<'a> {
/// The font loader to retrieve fonts from when typesetting text
/// using [`layout_text`].
pub loader: &'a SharedFontLoader<'p>,
pub loader: &'a GlobalFontLoader,
/// The style for text: Font selection with classes, weights and variants,
/// font sizes, spacing and so on.
pub style: &'a TextStyle,
@ -39,13 +40,13 @@ pub struct TextContext<'a, 'p> {
}
/// Layouts text into a box.
pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> Layout {
pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> Layout {
TextLayouter::new(text, ctx).layout().await
}
impl<'a, 'p> TextLayouter<'a, 'p> {
impl<'a> TextLayouter<'a> {
/// Create a new text layouter.
fn new(text: &'a str, ctx: TextContext<'a, 'p>) -> TextLayouter<'a, 'p> {
fn new(text: &'a str, ctx: TextContext<'a>) -> TextLayouter<'a> {
TextLayouter {
ctx,
text,

View File

@ -21,8 +21,14 @@
pub use toddle;
use std::cell::RefCell;
use std::error::Error;
use std::fmt::{self, Debug, Formatter};
use std::io::Cursor;
use async_trait::async_trait;
use smallvec::smallvec;
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
use toddle::{Font, OwnedData};
use toddle::query::{FontLoader, FontProvider, SharedFontLoader, FontDescriptor};
use crate::layout::{Layouted, MultiLayout};
use crate::style::{LayoutStyle, PageStyle, TextStyle};
@ -45,20 +51,29 @@ pub mod syntax;
/// Transforms source code into typesetted layouts.
///
/// A typesetter can be configured through various methods.
pub struct Typesetter<'p> {
pub struct Typesetter {
/// The font loader shared by all typesetting processes.
loader: SharedFontLoader<'p>,
loader: GlobalFontLoader,
/// The base layouting style.
style: LayoutStyle,
/// The standard library scope.
scope: Scope,
}
impl<'p> Typesetter<'p> {
/// The font loader type used in the [`Typesetter`].
///
/// This font loader is ref-cell protected and backed by a dynamic font
/// provider.
pub type GlobalFontLoader = SharedFontLoader<GlobalProvider>;
/// The provider type of font loaders used in the [`Typesetter`].
pub type GlobalProvider = Box<dyn FontProvider<Data=OwnedData, Error=Box<dyn Error>>>;
impl Typesetter {
/// Create a new typesetter.
pub fn new() -> Typesetter<'p> {
pub fn new(provider: (GlobalProvider, Vec<FontDescriptor>)) -> Typesetter {
Typesetter {
loader: RefCell::new(FontLoader::new()),
loader: RefCell::new(FontLoader::new(provider)),
style: LayoutStyle::default(),
scope: Scope::with_std(),
}
@ -74,14 +89,8 @@ impl<'p> Typesetter<'p> {
self.style.text = style;
}
/// Add a font provider to the context of this typesetter.
pub fn add_font_provider<P: 'p>(&mut self, provider: P)
where P: FontProvider {
self.loader.get_mut().add_provider(provider);
}
/// A reference to the backing font loader.
pub fn loader(&self) -> &SharedFontLoader<'p> {
pub fn loader(&self) -> &GlobalFontLoader {
&self.loader
}
@ -121,3 +130,29 @@ impl<'p> Typesetter<'p> {
self.layout(&tree).await.output
}
}
/// Wraps a font provider and transforms its errors into boxed trait objects.
/// This enables font providers that do not return boxed errors to be used with
/// the typesetter.
pub struct DynErrorProvider<P> {
provider: P,
}
impl<P> DynErrorProvider<P>
where P: FontProvider, P::Error: Error + 'static {
/// Create a new dynamic error provider from any provider.
pub fn new(provider: P) -> DynErrorProvider<P> {
DynErrorProvider { provider }
}
}
#[async_trait(?Send)]
impl<P> FontProvider for DynErrorProvider<P>
where P: FontProvider, P::Error: Error + 'static {
type Data = P::Data;
type Error = Box<dyn Error>;
async fn load(&self, index: usize, variant: usize) -> Result<Font<P::Data>, Self::Error> {
Ok(self.provider.load(index, variant).await?)
}
}

View File

@ -20,7 +20,10 @@ function! {
layout(self, ctx, errors) {
styled(&self.body, ctx, Some(&self.list),
|s, l| s.fallback.list = l.clone())
|s, list| {
s.fallback.list = list.clone();
s.fallback.flatten();
})
}
}

View File

@ -1,5 +1,6 @@
//! The _Typst_ standard library.
use toddle::query::FontProvider;
use crate::syntax::Scope;
use crate::func::prelude::*;
@ -68,7 +69,7 @@ function! {
/// Layout an optional body with a change of the text style.
fn styled<'a, T, F>(
body: &'a Option<SyntaxModel>,
ctx: LayoutContext,
ctx: LayoutContext<'_>,
data: Option<T>,
f: F,
) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) {

View File

@ -1,6 +1,7 @@
//! Styles for text and pages.
use toddle::query::{FontFallbackTree, FontVariant, FontStyle, FontWeight};
use toddle::fallback;
use toddle::query::{FallbackTree, FontVariant, FontStyle, FontWeight};
use crate::size::{Size, Size2D, SizeBox, ValueBox, PSize};
@ -17,7 +18,7 @@ pub struct LayoutStyle {
#[derive(Debug, Clone)]
pub struct TextStyle {
/// A tree of font names and generic family names.
pub fallback: FontFallbackTree,
pub fallback: FallbackTree,
/// The selected font variant.
pub variant: FontVariant,
/// Whether the bolder toggle is active or inactive. This determines
@ -57,38 +58,19 @@ impl TextStyle {
}
}
macro_rules! fallback {
(
list: ($($f:expr),*),
classes: { $($c:expr => ($($cf:expr),*),)* },
base: ($($b:expr),*),
) => ({
let mut fallback = FontFallbackTree::new(
vec![$($f.to_string()),*],
vec![$($b.to_string()),*],
);
$(
fallback.set_class_list($c.to_string(), vec![$($cf.to_string()),*])
.expect("TextStyle::default: unexpected error \
when setting class list");
)*
fallback
});
}
impl Default for TextStyle {
fn default() -> TextStyle {
TextStyle {
fallback: fallback! {
list: ("sans-serif"),
list: ["sans-serif"],
classes: {
"serif" => ("source serif pro", "noto serif"),
"sans-serif" => ("source sans pro", "noto sans"),
"monospace" => ("source code pro", "noto sans mono"),
"math" => ("latin modern math", "serif"),
"serif" => ["source serif pro", "noto serif"],
"sans-serif" => ["source sans pro", "noto sans"],
"monospace" => ["source code pro", "noto sans mono"],
"math" => ["latin modern math", "serif"],
},
base: ("source sans pro", "noto sans",
"noto emoji", "latin modern math"),
base: ["source sans pro", "noto sans",
"noto emoji", "latin modern math"],
},
variant: FontVariant {
style: FontStyle::Normal,
@ -157,7 +139,7 @@ pub struct Paper {
impl Paper {
/// The paper with the given name.
pub fn from_str(name: &str) -> Option<Paper> {
pub fn from_name(name: &str) -> Option<Paper> {
parse_paper(name)
}
}

View File

@ -145,7 +145,7 @@ impl Value for FontStyle {
type Output = Self;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
FontStyle::from_str(Ident::parse(expr)?.as_str())
FontStyle::from_name(Ident::parse(expr)?.as_str())
.ok_or_else(|| err!("invalid font style"))
}
}
@ -166,7 +166,7 @@ impl Value for FontWeight {
}
}
Expr::Ident(id) => {
FontWeight::from_str(id.as_str())
FontWeight::from_name(id.as_str())
.ok_or_else(|| err!("invalid font weight"))
.map(|weight| (weight, false))
}
@ -180,7 +180,7 @@ impl Value for Paper {
type Output = Self;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
Paper::from_str(Ident::parse(expr)?.as_str())
Paper::from_name(Ident::parse(expr)?.as_str())
.ok_or_else(|| err!("invalid paper type"))
}
}

View File

@ -23,7 +23,10 @@ pub_use_mod!(tokens);
pub trait Model: Debug + ModelBounds {
/// Layout the model into a sequence of commands processed by a
/// [`ModelLayouter`](crate::layout::ModelLayouter).
async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> Layouted<Commands<'a>>;
async fn layout<'a>(
&'a self,
ctx: LayoutContext<'_>,
) -> Layouted<Commands<'a>>;
}
/// A tree representation of source code.
@ -47,7 +50,10 @@ impl SyntaxModel {
#[async_trait(?Send)]
impl Model for SyntaxModel {
async fn layout<'a>(&'a self, _: LayoutContext<'_, '_>) -> Layouted<Commands<'a>> {
async fn layout<'a>(
&'a self,
_: LayoutContext<'_>,
) -> Layouted<Commands<'a>> {
Layouted {
output: vec![Command::LayoutSyntaxModel(self)],
errors: vec![],

View File

@ -81,7 +81,7 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxMode
Token::Star => Node::ToggleBolder,
Token::Underscore => Node::ToggleItalic,
Token::Backtick => Node::ToggleMonospace,
Token::Text(text) => Node::Text(text.to_owned()),
Token::Text(text) => Node::Text(text.to_string()),
Token::LineComment(_) | Token::BlockComment(_) => continue,

View File

@ -43,7 +43,7 @@ impl Scope {
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
where F: ParseFunc + Model + 'static {
self.parsers.insert(
name.to_owned(),
name.to_string(),
parser::<F>(metadata),
);
}

View File

@ -8,12 +8,12 @@ use std::process::Command;
use futures_executor::block_on;
use typstc::Typesetter;
use typstc::{Typesetter, DynErrorProvider};
use typstc::layout::{MultiLayout, Serialize};
use typstc::size::{Size, Size2D, ValueBox};
use typstc::style::{PageStyle, PaperClass};
use typstc::toddle::query::FileSystemFontProvider;
use typstc::export::pdf::PdfExporter;
use typstc::export::pdf;
use typstc::toddle::query::fs::EagerFsProvider;
type DynResult<T> = Result<T, Box<dyn Error>>;
@ -66,17 +66,17 @@ fn main() -> DynResult<()> {
fn test(name: &str, src: &str) -> DynResult<()> {
println!("Testing: {}.", name);
let mut typesetter = Typesetter::new();
let (fs, entries) = EagerFsProvider::from_index("../fonts", "index.json")?;
let paths = fs.paths();
let provider = DynErrorProvider::new(fs);
let mut typesetter = Typesetter::new((Box::new(provider), entries));
typesetter.set_page_style(PageStyle {
class: PaperClass::Custom,
dimensions: Size2D::with_all(Size::pt(250.0)),
margins: ValueBox::with_all(None),
});
let provider = FileSystemFontProvider::from_index("../fonts/index.json")?;
let font_paths = provider.paths();
typesetter.add_font_provider(provider);
let layouts = compile(&typesetter, src);
// Compute the font's paths.
@ -84,10 +84,8 @@ fn test(name: &str, src: &str) -> DynResult<()> {
let loader = typesetter.loader().borrow();
for layout in &layouts {
for index in layout.find_used_fonts() {
fonts.entry(index).or_insert_with(|| {
let p = loader.get_provider_and_index(index.id).1;
&font_paths[p][index.variant]
});
fonts.entry(index)
.or_insert_with(|| &paths[index.id][index.variant]);
}
}
drop(loader);
@ -113,8 +111,7 @@ fn test(name: &str, src: &str) -> DynResult<()> {
// Write the PDF file.
let path = format!("tests/cache/pdf/{}.pdf", name);
let file = BufWriter::new(File::create(path)?);
let exporter = PdfExporter::new();
exporter.export(&layouts, typesetter.loader(), file)?;
pdf::export(&layouts, typesetter.loader(), file)?;
Ok(())
}