mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Upgrade to new toddle interface 🐳
This commit is contained in:
parent
20fb4e7c37
commit
40ea35cbe7
@ -6,7 +6,7 @@ edition = "2018"
|
|||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
toddle = { path = "../toddle", default-features = false }
|
toddle = { path = "../toddle", features = ["query"], default-features = false }
|
||||||
tide = { path = "../tide" }
|
tide = { path = "../tide" }
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
|
@ -3,9 +3,9 @@ use std::io::BufWriter;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
|
|
||||||
use typstc::Typesetter;
|
use typstc::{Typesetter, DynErrorProvider};
|
||||||
use typstc::toddle::query::FileSystemFontProvider;
|
use typstc::toddle::query::fs::EagerFsProvider;
|
||||||
use typstc::export::pdf::PdfExporter;
|
use typstc::export::pdf;
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -37,15 +37,14 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let src = read_to_string(source)
|
let src = read_to_string(source)
|
||||||
.map_err(|_| "failed to read from source file")?;
|
.map_err(|_| "failed to read from source file")?;
|
||||||
|
|
||||||
let mut typesetter = Typesetter::new();
|
let (fs, entries) = EagerFsProvider::from_index("../fonts", "index.json")?;
|
||||||
let provider = FileSystemFontProvider::from_index("../fonts/index.json").unwrap();
|
let provider = DynErrorProvider::new(fs);
|
||||||
typesetter.add_font_provider(provider);
|
let typesetter = Typesetter::new((Box::new(provider), entries));
|
||||||
|
|
||||||
let layouts = block_on(typesetter.typeset(&src));
|
let layouts = block_on(typesetter.typeset(&src));
|
||||||
|
|
||||||
let exporter = PdfExporter::new();
|
|
||||||
let writer = BufWriter::new(File::create(&dest)?);
|
let writer = BufWriter::new(File::create(&dest)?);
|
||||||
exporter.export(&layouts, typesetter.loader(), writer)?;
|
pdf::export(&layouts, typesetter.loader(), writer)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -11,45 +11,35 @@ use tide::font::{
|
|||||||
CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord,
|
CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord,
|
||||||
};
|
};
|
||||||
|
|
||||||
use toddle::Error as FontError;
|
use toddle::{Font, OwnedFont, LoadError};
|
||||||
use toddle::font::OwnedFont;
|
use toddle::types::Tag;
|
||||||
use toddle::query::{SharedFontLoader, FontIndex};
|
use toddle::query::FontIndex;
|
||||||
use toddle::tables::{
|
use toddle::tables::{
|
||||||
CharMap, Header, HorizontalMetrics, MacStyleFlags,
|
CharMap, Header, HorizontalMetrics, MacStyleFlags,
|
||||||
Name, NameEntry, Post, OS2,
|
Name, NameEntry, Post, OS2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::GlobalFontLoader;
|
||||||
use crate::layout::{MultiLayout, Layout, LayoutAction};
|
use crate::layout::{MultiLayout, Layout, LayoutAction};
|
||||||
use crate::size::Size;
|
use crate::size::Size;
|
||||||
|
|
||||||
|
|
||||||
/// Exports layouts into _PDFs_.
|
/// Export a layouted list of boxes. The same font loader as used for
|
||||||
pub struct PdfExporter {}
|
/// layouting needs to be passed in here since the layout only contains
|
||||||
|
/// indices referencing the loaded fonts. The raw PDF ist written into the
|
||||||
impl PdfExporter {
|
/// target writable, returning the number of bytes written.
|
||||||
/// Create a new exporter.
|
pub fn export<W: Write>(
|
||||||
pub fn new() -> PdfExporter {
|
layout: &MultiLayout,
|
||||||
PdfExporter {}
|
loader: &GlobalFontLoader,
|
||||||
}
|
target: W,
|
||||||
|
) -> PdfResult<usize> {
|
||||||
/// Export a layouted list of boxes. The same font loader as used for
|
PdfExporter::new(layout, loader, target)?.write()
|
||||||
/// 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data relevant to the export of one document.
|
/// The data relevant to the export of one document.
|
||||||
struct ExportProcess<'d, W: Write> {
|
struct PdfExporter<'a, W: Write> {
|
||||||
writer: PdfWriter<W>,
|
writer: PdfWriter<W>,
|
||||||
layouts: &'d MultiLayout,
|
layouts: &'a MultiLayout,
|
||||||
|
|
||||||
/// Since we cross-reference pages and fonts with their IDs already in the document
|
/// 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.
|
/// catalog, we need to know exactly which ID is used for what from the beginning.
|
||||||
@ -76,18 +66,18 @@ struct Offsets {
|
|||||||
fonts: (Ref, Ref),
|
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
|
/// Prepare the export. Only once [`ExportProcess::write`] is called the
|
||||||
/// writing really happens.
|
/// writing really happens.
|
||||||
fn new(
|
fn new(
|
||||||
layouts: &'d MultiLayout,
|
layouts: &'a MultiLayout,
|
||||||
font_loader: &SharedFontLoader,
|
font_loader: &GlobalFontLoader,
|
||||||
target: W,
|
target: W,
|
||||||
) -> PdfResult<ExportProcess<'d, W>> {
|
) -> PdfResult<PdfExporter<'a, W>> {
|
||||||
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
|
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
|
||||||
let offsets = Self::calculate_offsets(layouts.len(), fonts.len());
|
let offsets = Self::calculate_offsets(layouts.len(), fonts.len());
|
||||||
|
|
||||||
Ok(ExportProcess {
|
Ok(PdfExporter {
|
||||||
writer: PdfWriter::new(target),
|
writer: PdfWriter::new(target),
|
||||||
layouts,
|
layouts,
|
||||||
offsets,
|
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
|
/// one used in the PDF. The new ones index into the returned vector of
|
||||||
/// owned fonts.
|
/// owned fonts.
|
||||||
fn subset_fonts(
|
fn subset_fonts(
|
||||||
layouts: &'d MultiLayout,
|
layouts: &'a MultiLayout,
|
||||||
font_loader: &SharedFontLoader
|
font_loader: &GlobalFontLoader,
|
||||||
) -> PdfResult<(Vec<OwnedFont>, HashMap<FontIndex, usize>)> {
|
) -> PdfResult<(Vec<OwnedFont>, HashMap<FontIndex, usize>)> {
|
||||||
let mut fonts = Vec::new();
|
let mut fonts = Vec::new();
|
||||||
let mut font_chars: HashMap<FontIndex, HashSet<char>> = HashMap::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();
|
let mut font_loader = font_loader.borrow_mut();
|
||||||
|
|
||||||
// All tables not listed here are dropped.
|
// All tables not listed here are dropped.
|
||||||
const SUBSET_TABLES: [&str; 13] = [
|
let tables: Vec<_> = [
|
||||||
"name", "OS/2", "post", "head", "hhea", "hmtx", "maxp",
|
b"name", b"OS/2", b"post", b"head", b"hhea", b"hmtx", b"maxp",
|
||||||
"cmap", "cvt ", "fpgm", "prep", "loca", "glyf",
|
b"cmap", b"cvt ", b"fpgm", b"prep", b"loca", b"glyf",
|
||||||
];
|
].iter().map(|&s| Tag(*s)).collect();
|
||||||
|
|
||||||
// Do the subsetting.
|
// Do the subsetting.
|
||||||
for index in 0 .. num_fonts {
|
for index in 0 .. num_fonts {
|
||||||
let old_index = new_to_old[&index];
|
let old_index = new_to_old[&index];
|
||||||
let font = font_loader.get_with_index(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))
|
let chars = font_chars[&old_index].iter().cloned();
|
||||||
.unwrap_or_else(|_| font.to_owned())?;
|
let subsetted = match font.subsetted(chars, tables.iter().copied()) {
|
||||||
|
Ok(data) => Font::from_bytes(data)?,
|
||||||
|
Err(_) => font.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
fonts.push(subsetted);
|
fonts.push(subsetted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +296,7 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
id,
|
id,
|
||||||
Type0Font::new(
|
Type0Font::new(
|
||||||
base_font.clone(),
|
base_font.clone(),
|
||||||
CMapEncoding::Predefined("Identity-H".to_owned()),
|
CMapEncoding::Predefined("Identity-H".to_string()),
|
||||||
id + 1,
|
id + 1,
|
||||||
)
|
)
|
||||||
.to_unicode(id + 3),
|
.to_unicode(id + 3),
|
||||||
@ -409,7 +403,7 @@ fn ids((start, end): (Ref, Ref)) -> impl Iterator<Item = Ref> {
|
|||||||
/// The error type for _PDF_ exporting.
|
/// The error type for _PDF_ exporting.
|
||||||
pub enum PdfExportError {
|
pub enum PdfExportError {
|
||||||
/// An error occured while subsetting the font for the _PDF_.
|
/// An error occured while subsetting the font for the _PDF_.
|
||||||
Font(FontError),
|
Font(LoadError),
|
||||||
/// An I/O Error on the underlying writable.
|
/// An I/O Error on the underlying writable.
|
||||||
Io(io::Error),
|
Io(io::Error),
|
||||||
}
|
}
|
||||||
@ -418,13 +412,13 @@ error_type! {
|
|||||||
self: PdfExportError,
|
self: PdfExportError,
|
||||||
res: PdfResult,
|
res: PdfResult,
|
||||||
show: f => match self {
|
show: f => match self {
|
||||||
PdfExportError::Font(err) => write!(f, "font error: {}", err),
|
PdfExportError::Font(err) => err.fmt(f),
|
||||||
PdfExportError::Io(err) => write!(f, "io error: {}", err),
|
PdfExportError::Io(err) => err.fmt(f),
|
||||||
},
|
},
|
||||||
source: match self {
|
source: match self {
|
||||||
PdfExportError::Font(err) => Some(err),
|
PdfExportError::Font(err) => Some(err),
|
||||||
PdfExportError::Io(err) => Some(err),
|
PdfExportError::Io(err) => Some(err),
|
||||||
},
|
},
|
||||||
from: (err: io::Error, PdfExportError::Io(err)),
|
from: (err: io::Error, PdfExportError::Io(err)),
|
||||||
from: (err: FontError, PdfExportError::Font(err)),
|
from: (err: LoadError, PdfExportError::Font(err)),
|
||||||
}
|
}
|
||||||
|
@ -145,15 +145,14 @@ macro_rules! function {
|
|||||||
|
|
||||||
(@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => {
|
(@layout($name:ident) layout($this:ident, $ctx:ident, $errors:ident) $code:block) => {
|
||||||
impl $crate::syntax::Model for $name {
|
impl $crate::syntax::Model for $name {
|
||||||
fn layout<'a, 'b, 'c, 't>(
|
fn layout<'a, 'b, 't>(
|
||||||
#[allow(unused)] &'a $this,
|
#[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::DynFuture<'t, $crate::layout::Layouted<
|
||||||
$crate::layout::Commands<'a>>
|
$crate::layout::Commands<'a>>
|
||||||
> where
|
> where
|
||||||
'a: 't,
|
'a: 't,
|
||||||
'b: 't,
|
'b: 't,
|
||||||
'c: 't,
|
|
||||||
Self: 't,
|
Self: 't,
|
||||||
{
|
{
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
|
@ -5,8 +5,9 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
use toddle::query::SharedFontLoader;
|
use toddle::query::{SharedFontLoader, FontProvider};
|
||||||
|
|
||||||
|
use crate::GlobalFontLoader;
|
||||||
use crate::error::Errors;
|
use crate::error::Errors;
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
@ -18,8 +19,8 @@ use super::*;
|
|||||||
|
|
||||||
|
|
||||||
/// Performs the model layouting.
|
/// Performs the model layouting.
|
||||||
pub struct ModelLayouter<'a, 'p> {
|
pub struct ModelLayouter<'a> {
|
||||||
ctx: LayoutContext<'a, 'p>,
|
ctx: LayoutContext<'a>,
|
||||||
layouter: LineLayouter,
|
layouter: LineLayouter,
|
||||||
style: LayoutStyle,
|
style: LayoutStyle,
|
||||||
errors: Errors,
|
errors: Errors,
|
||||||
@ -27,10 +28,10 @@ pub struct ModelLayouter<'a, 'p> {
|
|||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutContext<'a, 'p> {
|
pub struct LayoutContext<'a> {
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
/// using [`layout_text`].
|
/// using [`layout_text`].
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
pub loader: &'a GlobalFontLoader,
|
||||||
/// The style for pages and text.
|
/// The style for pages and text.
|
||||||
pub style: &'a LayoutStyle,
|
pub style: &'a LayoutStyle,
|
||||||
/// The base unpadded dimensions of this container (for relative sizing).
|
/// 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.
|
/// 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);
|
let mut layouter = ModelLayouter::new(ctx);
|
||||||
layouter.layout_syntax_model(model).await;
|
layouter.layout_syntax_model(model).await;
|
||||||
layouter.finish()
|
layouter.finish()
|
||||||
@ -116,9 +117,9 @@ pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted
|
|||||||
/// work internally.
|
/// work internally.
|
||||||
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
|
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.
|
/// Create a new model layouter.
|
||||||
pub fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> {
|
pub fn new(ctx: LayoutContext<'a>) -> ModelLayouter<'a> {
|
||||||
ModelLayouter {
|
ModelLayouter {
|
||||||
layouter: LineLayouter::new(LineContext {
|
layouter: LineLayouter::new(LineContext {
|
||||||
spaces: ctx.spaces.clone(),
|
spaces: ctx.spaces.clone(),
|
||||||
@ -182,6 +183,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
|
|||||||
Some("monospace") => { list.remove(0); },
|
Some("monospace") => { list.remove(0); },
|
||||||
_ => list.insert(0, "monospace".to_string()),
|
_ => list.insert(0, "monospace".to_string()),
|
||||||
}
|
}
|
||||||
|
self.style.text.fallback.flatten();
|
||||||
}
|
}
|
||||||
|
|
||||||
Node::Model(model) => {
|
Node::Model(model) => {
|
||||||
|
@ -4,17 +4,18 @@
|
|||||||
//! When the primary layouting axis horizontally inversed, the word is spelled
|
//! When the primary layouting axis horizontally inversed, the word is spelled
|
||||||
//! backwards. Vertical word layout is not yet supported.
|
//! 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 toddle::tables::{CharMap, Header, HorizontalMetrics};
|
||||||
|
|
||||||
|
use crate::GlobalFontLoader;
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
use crate::style::TextStyle;
|
use crate::style::TextStyle;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
/// Performs the text layouting.
|
/// Performs the text layouting.
|
||||||
struct TextLayouter<'a, 'p> {
|
struct TextLayouter<'a> {
|
||||||
ctx: TextContext<'a, 'p>,
|
ctx: TextContext<'a>,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
actions: LayoutActions,
|
actions: LayoutActions,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
@ -24,10 +25,10 @@ struct TextLayouter<'a, 'p> {
|
|||||||
|
|
||||||
/// The context for text layouting.
|
/// The context for text layouting.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TextContext<'a, 'p> {
|
pub struct TextContext<'a> {
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
/// using [`layout_text`].
|
/// using [`layout_text`].
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
pub loader: &'a GlobalFontLoader,
|
||||||
/// The style for text: Font selection with classes, weights and variants,
|
/// The style for text: Font selection with classes, weights and variants,
|
||||||
/// font sizes, spacing and so on.
|
/// font sizes, spacing and so on.
|
||||||
pub style: &'a TextStyle,
|
pub style: &'a TextStyle,
|
||||||
@ -39,13 +40,13 @@ pub struct TextContext<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Layouts text into a box.
|
/// 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
|
TextLayouter::new(text, ctx).layout().await
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'p> TextLayouter<'a, 'p> {
|
impl<'a> TextLayouter<'a> {
|
||||||
/// Create a new text layouter.
|
/// 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 {
|
TextLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
text,
|
text,
|
||||||
|
61
src/lib.rs
61
src/lib.rs
@ -21,8 +21,14 @@
|
|||||||
pub use toddle;
|
pub use toddle;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
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 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::layout::{Layouted, MultiLayout};
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
@ -45,20 +51,29 @@ pub mod syntax;
|
|||||||
/// Transforms source code into typesetted layouts.
|
/// Transforms source code into typesetted layouts.
|
||||||
///
|
///
|
||||||
/// A typesetter can be configured through various methods.
|
/// A typesetter can be configured through various methods.
|
||||||
pub struct Typesetter<'p> {
|
pub struct Typesetter {
|
||||||
/// The font loader shared by all typesetting processes.
|
/// The font loader shared by all typesetting processes.
|
||||||
loader: SharedFontLoader<'p>,
|
loader: GlobalFontLoader,
|
||||||
/// The base layouting style.
|
/// The base layouting style.
|
||||||
style: LayoutStyle,
|
style: LayoutStyle,
|
||||||
/// The standard library scope.
|
/// The standard library scope.
|
||||||
scope: 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.
|
/// Create a new typesetter.
|
||||||
pub fn new() -> Typesetter<'p> {
|
pub fn new(provider: (GlobalProvider, Vec<FontDescriptor>)) -> Typesetter {
|
||||||
Typesetter {
|
Typesetter {
|
||||||
loader: RefCell::new(FontLoader::new()),
|
loader: RefCell::new(FontLoader::new(provider)),
|
||||||
style: LayoutStyle::default(),
|
style: LayoutStyle::default(),
|
||||||
scope: Scope::with_std(),
|
scope: Scope::with_std(),
|
||||||
}
|
}
|
||||||
@ -74,14 +89,8 @@ impl<'p> Typesetter<'p> {
|
|||||||
self.style.text = style;
|
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.
|
/// A reference to the backing font loader.
|
||||||
pub fn loader(&self) -> &SharedFontLoader<'p> {
|
pub fn loader(&self) -> &GlobalFontLoader {
|
||||||
&self.loader
|
&self.loader
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,3 +130,29 @@ impl<'p> Typesetter<'p> {
|
|||||||
self.layout(&tree).await.output
|
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?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,7 +20,10 @@ function! {
|
|||||||
|
|
||||||
layout(self, ctx, errors) {
|
layout(self, ctx, errors) {
|
||||||
styled(&self.body, ctx, Some(&self.list),
|
styled(&self.body, ctx, Some(&self.list),
|
||||||
|s, l| s.fallback.list = l.clone())
|
|s, list| {
|
||||||
|
s.fallback.list = list.clone();
|
||||||
|
s.fallback.flatten();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! The _Typst_ standard library.
|
//! The _Typst_ standard library.
|
||||||
|
|
||||||
|
use toddle::query::FontProvider;
|
||||||
use crate::syntax::Scope;
|
use crate::syntax::Scope;
|
||||||
use crate::func::prelude::*;
|
use crate::func::prelude::*;
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ function! {
|
|||||||
/// Layout an optional body with a change of the text style.
|
/// Layout an optional body with a change of the text style.
|
||||||
fn styled<'a, T, F>(
|
fn styled<'a, T, F>(
|
||||||
body: &'a Option<SyntaxModel>,
|
body: &'a Option<SyntaxModel>,
|
||||||
ctx: LayoutContext,
|
ctx: LayoutContext<'_>,
|
||||||
data: Option<T>,
|
data: Option<T>,
|
||||||
f: F,
|
f: F,
|
||||||
) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) {
|
) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) {
|
||||||
|
40
src/style.rs
40
src/style.rs
@ -1,6 +1,7 @@
|
|||||||
//! Styles for text and pages.
|
//! 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};
|
use crate::size::{Size, Size2D, SizeBox, ValueBox, PSize};
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ pub struct LayoutStyle {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TextStyle {
|
pub struct TextStyle {
|
||||||
/// A tree of font names and generic family names.
|
/// A tree of font names and generic family names.
|
||||||
pub fallback: FontFallbackTree,
|
pub fallback: FallbackTree,
|
||||||
/// The selected font variant.
|
/// The selected font variant.
|
||||||
pub variant: FontVariant,
|
pub variant: FontVariant,
|
||||||
/// Whether the bolder toggle is active or inactive. This determines
|
/// 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 {
|
impl Default for TextStyle {
|
||||||
fn default() -> TextStyle {
|
fn default() -> TextStyle {
|
||||||
TextStyle {
|
TextStyle {
|
||||||
fallback: fallback! {
|
fallback: fallback! {
|
||||||
list: ("sans-serif"),
|
list: ["sans-serif"],
|
||||||
classes: {
|
classes: {
|
||||||
"serif" => ("source serif pro", "noto serif"),
|
"serif" => ["source serif pro", "noto serif"],
|
||||||
"sans-serif" => ("source sans pro", "noto sans"),
|
"sans-serif" => ["source sans pro", "noto sans"],
|
||||||
"monospace" => ("source code pro", "noto sans mono"),
|
"monospace" => ["source code pro", "noto sans mono"],
|
||||||
"math" => ("latin modern math", "serif"),
|
"math" => ["latin modern math", "serif"],
|
||||||
},
|
},
|
||||||
base: ("source sans pro", "noto sans",
|
base: ["source sans pro", "noto sans",
|
||||||
"noto emoji", "latin modern math"),
|
"noto emoji", "latin modern math"],
|
||||||
},
|
},
|
||||||
variant: FontVariant {
|
variant: FontVariant {
|
||||||
style: FontStyle::Normal,
|
style: FontStyle::Normal,
|
||||||
@ -157,7 +139,7 @@ pub struct Paper {
|
|||||||
|
|
||||||
impl Paper {
|
impl Paper {
|
||||||
/// The paper with the given name.
|
/// 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)
|
parse_paper(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ impl Value for FontStyle {
|
|||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
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"))
|
.ok_or_else(|| err!("invalid font style"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +166,7 @@ impl Value for FontWeight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Ident(id) => {
|
Expr::Ident(id) => {
|
||||||
FontWeight::from_str(id.as_str())
|
FontWeight::from_name(id.as_str())
|
||||||
.ok_or_else(|| err!("invalid font weight"))
|
.ok_or_else(|| err!("invalid font weight"))
|
||||||
.map(|weight| (weight, false))
|
.map(|weight| (weight, false))
|
||||||
}
|
}
|
||||||
@ -180,7 +180,7 @@ impl Value for Paper {
|
|||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
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"))
|
.ok_or_else(|| err!("invalid paper type"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,10 @@ pub_use_mod!(tokens);
|
|||||||
pub trait Model: Debug + ModelBounds {
|
pub trait Model: Debug + ModelBounds {
|
||||||
/// Layout the model into a sequence of commands processed by a
|
/// Layout the model into a sequence of commands processed by a
|
||||||
/// [`ModelLayouter`](crate::layout::ModelLayouter).
|
/// [`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.
|
/// A tree representation of source code.
|
||||||
@ -47,7 +50,10 @@ impl SyntaxModel {
|
|||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl Model for SyntaxModel {
|
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 {
|
Layouted {
|
||||||
output: vec![Command::LayoutSyntaxModel(self)],
|
output: vec![Command::LayoutSyntaxModel(self)],
|
||||||
errors: vec![],
|
errors: vec![],
|
||||||
|
@ -81,7 +81,7 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxMode
|
|||||||
Token::Star => Node::ToggleBolder,
|
Token::Star => Node::ToggleBolder,
|
||||||
Token::Underscore => Node::ToggleItalic,
|
Token::Underscore => Node::ToggleItalic,
|
||||||
Token::Backtick => Node::ToggleMonospace,
|
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,
|
Token::LineComment(_) | Token::BlockComment(_) => continue,
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ impl Scope {
|
|||||||
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
|
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseFunc>::Meta)
|
||||||
where F: ParseFunc + Model + 'static {
|
where F: ParseFunc + Model + 'static {
|
||||||
self.parsers.insert(
|
self.parsers.insert(
|
||||||
name.to_owned(),
|
name.to_string(),
|
||||||
parser::<F>(metadata),
|
parser::<F>(metadata),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,12 @@ use std::process::Command;
|
|||||||
|
|
||||||
use futures_executor::block_on;
|
use futures_executor::block_on;
|
||||||
|
|
||||||
use typstc::Typesetter;
|
use typstc::{Typesetter, DynErrorProvider};
|
||||||
use typstc::layout::{MultiLayout, Serialize};
|
use typstc::layout::{MultiLayout, Serialize};
|
||||||
use typstc::size::{Size, Size2D, ValueBox};
|
use typstc::size::{Size, Size2D, ValueBox};
|
||||||
use typstc::style::{PageStyle, PaperClass};
|
use typstc::style::{PageStyle, PaperClass};
|
||||||
use typstc::toddle::query::FileSystemFontProvider;
|
use typstc::export::pdf;
|
||||||
use typstc::export::pdf::PdfExporter;
|
use typstc::toddle::query::fs::EagerFsProvider;
|
||||||
|
|
||||||
|
|
||||||
type DynResult<T> = Result<T, Box<dyn Error>>;
|
type DynResult<T> = Result<T, Box<dyn Error>>;
|
||||||
@ -66,17 +66,17 @@ fn main() -> DynResult<()> {
|
|||||||
fn test(name: &str, src: &str) -> DynResult<()> {
|
fn test(name: &str, src: &str) -> DynResult<()> {
|
||||||
println!("Testing: {}.", name);
|
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 {
|
typesetter.set_page_style(PageStyle {
|
||||||
class: PaperClass::Custom,
|
class: PaperClass::Custom,
|
||||||
dimensions: Size2D::with_all(Size::pt(250.0)),
|
dimensions: Size2D::with_all(Size::pt(250.0)),
|
||||||
margins: ValueBox::with_all(None),
|
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);
|
let layouts = compile(&typesetter, src);
|
||||||
|
|
||||||
// Compute the font's paths.
|
// Compute the font's paths.
|
||||||
@ -84,10 +84,8 @@ fn test(name: &str, src: &str) -> DynResult<()> {
|
|||||||
let loader = typesetter.loader().borrow();
|
let loader = typesetter.loader().borrow();
|
||||||
for layout in &layouts {
|
for layout in &layouts {
|
||||||
for index in layout.find_used_fonts() {
|
for index in layout.find_used_fonts() {
|
||||||
fonts.entry(index).or_insert_with(|| {
|
fonts.entry(index)
|
||||||
let p = loader.get_provider_and_index(index.id).1;
|
.or_insert_with(|| &paths[index.id][index.variant]);
|
||||||
&font_paths[p][index.variant]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
drop(loader);
|
drop(loader);
|
||||||
@ -113,8 +111,7 @@ fn test(name: &str, src: &str) -> DynResult<()> {
|
|||||||
// Write the PDF file.
|
// Write the PDF file.
|
||||||
let path = format!("tests/cache/pdf/{}.pdf", name);
|
let path = format!("tests/cache/pdf/{}.pdf", name);
|
||||||
let file = BufWriter::new(File::create(path)?);
|
let file = BufWriter::new(File::create(path)?);
|
||||||
let exporter = PdfExporter::new();
|
pdf::export(&layouts, typesetter.loader(), file)?;
|
||||||
exporter.export(&layouts, typesetter.loader(), file)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user