diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4f0a44528 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +Cargo.lock +things diff --git a/Cargo.toml b/Cargo.toml index 765353027..a82af22ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,28 +1,16 @@ [package] -name = "typeset" +name = "typst" version = "0.1.0" authors = ["Laurenz Mädje "] edition = "2018" [dependencies] -pdf = { path = "../pdf" } -opentype = { path = "../opentype" } +tide = { path = "../tide" } +toddle = { path = "../toddle" } byteorder = "1" smallvec = "0.6.10" unicode-xid = "0.1.0" -toml = "0.5" - -[dev-dependencies] -bencher = "0.1" [[bin]] -name = "typst" +name = "typstc" path = "src/bin/main.rs" - -[[bench]] -name = "font" -harness = false - -[[bench]] -name = "complete" -harness = false diff --git a/benches/complete.rs b/benches/complete.rs deleted file mode 100644 index 5b2010f41..000000000 --- a/benches/complete.rs +++ /dev/null @@ -1,48 +0,0 @@ -use bencher::Bencher; -use typeset::Typesetter; -use typeset::font::FileSystemFontProvider; -use typeset::export::pdf::PdfExporter; - - -fn prepare<'p>() -> (Typesetter<'p>, &'static str) { - let src = include_str!("../test/shakespeare.tps"); - - let mut typesetter = Typesetter::new(); - let provider = FileSystemFontProvider::from_listing("../fonts/fonts.toml").unwrap(); - typesetter.add_font_provider(provider); - - (typesetter, src) -} - -/// Benchmarks only the parsing step. -fn parsing(b: &mut Bencher) { - let (typesetter, src) = prepare(); - b.iter(|| { typesetter.parse(src).unwrap(); }); -} - -/// Benchmarks only the layouting step. -fn layouting(b: &mut Bencher) { - let (typesetter, src) = prepare(); - let tree = typesetter.parse(src).unwrap(); - b.iter(|| { typesetter.layout(&tree).unwrap(); }); -} - -/// Benchmarks the full typesetting step. -fn typesetting(b: &mut Bencher) { - let (typesetter, src) = prepare(); - b.iter(|| { typesetter.typeset(src).unwrap(); }); -} - -/// Benchmarks only the exporting step. -fn exporting(b: &mut Bencher) { - let (typesetter, src) = prepare(); - let doc = typesetter.typeset(src).unwrap(); - let exporter = PdfExporter::new(); - b.iter(|| { - let mut buf = Vec::new(); - exporter.export(&doc, &typesetter.loader(), &mut buf).unwrap(); - }); -} - -bencher::benchmark_group!(benches, parsing, layouting, typesetting, exporting); -bencher::benchmark_main!(benches); diff --git a/benches/font.rs b/benches/font.rs deleted file mode 100644 index cfcb5c1fc..000000000 --- a/benches/font.rs +++ /dev/null @@ -1,46 +0,0 @@ -use bencher::Bencher; -use typeset::font::{*, FontClass::*}; -use typeset::style::TextStyle; - - -/// Benchmarks just the char-by-char font loading. -fn font_loading(b: &mut Bencher) { - let provider = FileSystemFontProvider::from_listing("../fonts/fonts.toml").unwrap(); - let mut font_loader = FontLoader::new(); - font_loader.add_font_provider(provider); - - let text = include_str!("../test/shakespeare.tps"); - - let mut style = TextStyle { - classes: vec![Regular], - fallback: vec![ - Family("Helvetica".to_string()), - Family("Computer Modern".to_string()), - Serif, - Monospace, - ], - font_size: 12.0, - line_spacing: 1.0, - paragraph_spacing: 1.0, - }; - - b.iter(|| { - for character in text.chars() { - match character { - '_' => style.toggle_class(Italic), - '*' => style.toggle_class(Bold), - '\n' | '[' | ']' => {}, - _ => { - let _font = font_loader.get(FontQuery { - character, - classes: style.classes.clone(), - fallback: style.fallback.clone(), - }).unwrap(); - }, - } - } - }); -} - -bencher::benchmark_group!(benches, font_loading); -bencher::benchmark_main!(benches); diff --git a/fonts/CMU-SansSerif-Bold-Italic.ttf b/fonts/CMU-SansSerif-Bold-Italic.ttf new file mode 100644 index 000000000..529277b53 Binary files /dev/null and b/fonts/CMU-SansSerif-Bold-Italic.ttf differ diff --git a/fonts/CMU-SansSerif-Bold.ttf b/fonts/CMU-SansSerif-Bold.ttf new file mode 100644 index 000000000..d13901bec Binary files /dev/null and b/fonts/CMU-SansSerif-Bold.ttf differ diff --git a/fonts/CMU-SansSerif-Italic.ttf b/fonts/CMU-SansSerif-Italic.ttf new file mode 100644 index 000000000..bd30a3f97 Binary files /dev/null and b/fonts/CMU-SansSerif-Italic.ttf differ diff --git a/fonts/CMU-SansSerif-Regular.ttf b/fonts/CMU-SansSerif-Regular.ttf new file mode 100644 index 000000000..d7513c86a Binary files /dev/null and b/fonts/CMU-SansSerif-Regular.ttf differ diff --git a/fonts/CMU-Serif-Bold-Italic.ttf b/fonts/CMU-Serif-Bold-Italic.ttf new file mode 100644 index 000000000..3b529ed8d Binary files /dev/null and b/fonts/CMU-Serif-Bold-Italic.ttf differ diff --git a/fonts/CMU-Serif-Bold.ttf b/fonts/CMU-Serif-Bold.ttf new file mode 100644 index 000000000..2c7198e5d Binary files /dev/null and b/fonts/CMU-Serif-Bold.ttf differ diff --git a/fonts/CMU-Serif-Italic.ttf b/fonts/CMU-Serif-Italic.ttf new file mode 100644 index 000000000..993d5c029 Binary files /dev/null and b/fonts/CMU-Serif-Italic.ttf differ diff --git a/fonts/CMU-Serif-Regular.ttf b/fonts/CMU-Serif-Regular.ttf new file mode 100644 index 000000000..1c3fff0a6 Binary files /dev/null and b/fonts/CMU-Serif-Regular.ttf differ diff --git a/fonts/CMU-Typewriter-Bold-Italic.ttf b/fonts/CMU-Typewriter-Bold-Italic.ttf new file mode 100644 index 000000000..c7db8b714 Binary files /dev/null and b/fonts/CMU-Typewriter-Bold-Italic.ttf differ diff --git a/fonts/CMU-Typewriter-Bold.ttf b/fonts/CMU-Typewriter-Bold.ttf new file mode 100644 index 000000000..f940901f4 Binary files /dev/null and b/fonts/CMU-Typewriter-Bold.ttf differ diff --git a/fonts/CMU-Typewriter-Italic.ttf b/fonts/CMU-Typewriter-Italic.ttf new file mode 100644 index 000000000..82c90a568 Binary files /dev/null and b/fonts/CMU-Typewriter-Italic.ttf differ diff --git a/fonts/CMU-Typewriter-Regular.ttf b/fonts/CMU-Typewriter-Regular.ttf new file mode 100644 index 000000000..1651877db Binary files /dev/null and b/fonts/CMU-Typewriter-Regular.ttf differ diff --git a/fonts/License-CMU.txt b/fonts/License-CMU.txt new file mode 100644 index 000000000..a55bb6b2b --- /dev/null +++ b/fonts/License-CMU.txt @@ -0,0 +1,103 @@ +Copyright (C) Authors of original metafont fonts: +Donald Ervin Knuth (cm, concrete fonts) +1995, 1996, 1997 J"org Knappen, 1990, 1992 Norbert Schwarz (ec fonts) +1992-2006 A.Khodulev, O.Lapko, A.Berdnikov, V.Volovich (lh fonts) +1997-2005 Claudio Beccari (cb greek fonts) +2002 FUKUI Rei (tipa fonts) +2003-2005 Han The Thanh (Vietnamese fonts) +1996-2005 Walter Schmidt (cmbright fonts) + +Copyright (C) 2003-2009, Andrey V. Panov (panov@canopus.iacp.dvo.ru), +with Reserved Font Family Name "Computer Modern Unicode fonts". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/License-Noto.txt b/fonts/License-Noto.txt new file mode 100644 index 000000000..c82d72e42 --- /dev/null +++ b/fonts/License-Noto.txt @@ -0,0 +1,94 @@ +Copyright 2018 The Noto Project Authors (github.com/googlei18n/noto-fonts) + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fonts/NotoEmoji-Regular.ttf b/fonts/NotoEmoji-Regular.ttf new file mode 100644 index 000000000..19b7badf4 Binary files /dev/null and b/fonts/NotoEmoji-Regular.ttf differ diff --git a/fonts/fonts.toml b/fonts/fonts.toml new file mode 100644 index 000000000..844e2d349 --- /dev/null +++ b/fonts/fonts.toml @@ -0,0 +1,13 @@ +"CMU-SansSerif-Regular.ttf" = ["Computer Modern", "Regular", "SansSerif"] +"CMU-SansSerif-Italic.ttf" = ["Computer Modern", "Italic", "SansSerif"] +"CMU-SansSerif-Bold.ttf" = ["Computer Modern", "Bold", "SansSerif"] +"CMU-SansSerif-Bold-Italic.ttf" = ["Computer Modern", "Bold", "Italic", "SansSerif"] +"CMU-Serif-Regular.ttf" = ["Computer Modern", "Regular", "Serif"] +"CMU-Serif-Italic.ttf" = ["Computer Modern", "Italic", "Serif"] +"CMU-Serif-Bold.ttf" = ["Computer Modern", "Bold", "Serif"] +"CMU-Serif-Bold-Italic.ttf" = ["Computer Modern", "Bold", "Italic", "Serif"] +"CMU-Typewriter-Regular.ttf" = ["Computer Modern", "Regular", "Serif", "SansSerif", "Monospace"] +"CMU-Typewriter-Italic.ttf" = ["Computer Modern", "Italic", "Serif", "SansSerif", "Monospace"] +"CMU-Typewriter-Bold.ttf" = ["Computer Modern", "Bold", "Serif", "SansSerif", "Monospace"] +"CMU-Typewriter-Bold-Italic.ttf" = ["Computer Modern", "Bold", "Italic", "Serif", "SansSerif", "Monospace"] +"NotoEmoji-Regular.ttf" = ["Noto", "Noto Emoji", "Regular", "SansSerif", "Serif", "Monospace"] diff --git a/src/bin/main.rs b/src/bin/main.rs index df2cbc79c..b678cda9e 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -5,9 +5,9 @@ use std::io::{Read, BufWriter}; use std::path::{Path, PathBuf}; use std::process; -use typeset::Typesetter; -use typeset::font::FileSystemFontProvider; -use typeset::export::pdf::PdfExporter; +use typst::Typesetter; +use typst::export::pdf::PdfExporter; +use typst::toddle::query::FileSystemFontProvider; fn main() { diff --git a/src/export/pdf.rs b/src/export/pdf.rs index d01a9966d..d601f6b34 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -3,13 +3,18 @@ use std::collections::{HashMap, HashSet}; use std::io::{self, Write}; -use pdf::{PdfWriter, Ref, Rect, Version, Trailer, Content}; -use pdf::doc::{Catalog, PageTree, Page, Resource, Text}; -use pdf::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags}; -use pdf::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream}; +use tide::{PdfWriter, Ref, Rect, Version, Trailer}; +use tide::content::Content; +use tide::doc::{Catalog, PageTree, Page, Resource, Text}; +use tide::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags}; +use tide::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream}; + +use toddle::tables::{Header, Post, OS2, HorizontalMetrics, CharMap, Name, NameEntry, MacStyleFlags}; +use toddle::font::OwnedFont; +use toddle::query::SharedFontLoader; +use toddle::Error as FontError; use crate::doc::{Document, Page as DocPage, LayoutAction}; -use crate::font::{Font, FontLoader, FontError}; use crate::size::{Size, Size2D}; @@ -26,7 +31,7 @@ impl PdfExporter { /// Export a typesetted document into a writer. Returns how many bytes were written. #[inline] - pub fn export(&self, document: &Document, loader: &FontLoader, target: W) + pub fn export(&self, document: &Document, loader: &SharedFontLoader, target: W) -> PdfResult { let mut engine = PdfEngine::new(document, loader, target)?; engine.write() @@ -34,13 +39,12 @@ impl PdfExporter { } /// Writes documents in the _PDF_ format. -#[derive(Debug)] struct PdfEngine<'d, W: Write> { writer: PdfWriter, doc: &'d Document, offsets: Offsets, font_remap: HashMap, - fonts: Vec, + fonts: Vec, } /// Offsets for the various groups of ids. @@ -55,7 +59,7 @@ struct Offsets { impl<'d, W: Write> PdfEngine<'d, W> { /// Create a new _PDF_ engine. - fn new(doc: &'d Document, loader: &FontLoader, target: W) -> PdfResult> { + fn new(doc: &'d Document, loader: &SharedFontLoader, target: W) -> PdfResult> { // Create a subsetted PDF font for each font in the document. let mut font_remap = HashMap::new(); let fonts = { @@ -82,11 +86,22 @@ impl<'d, W: Write> PdfEngine<'d, W> { } // Collect the fonts into a vector in the order of the values in the remapping. + let mut loader = loader.borrow_mut(); let mut order = font_remap.iter().map(|(&old, &new)| (old, new)).collect::>(); order.sort_by_key(|&(_, new)| new); - order.into_iter() - .map(|(old, _)| PdfFont::new(&loader.get_with_index(old), &chars[&old])) - .collect::>>()? + + let mut fonts = vec![]; + for (index, _) in order { + let font = loader.get_with_index(index); + let subsetted = font.subsetted( + chars[&index].iter().cloned(), + &["name", "OS/2", "post", "head", "hhea", "hmtx", "maxp", + "cmap", "cvt ", "fpgm", "prep", "loca", "glyf"][..] + )?; + fonts.push(OwnedFont::from_bytes(subsetted)?); + } + + fonts }; // Calculate a unique id for all objects that will be written. @@ -108,12 +123,12 @@ impl<'d, W: Write> PdfEngine<'d, W> { /// Write the complete document. fn write(&mut self) -> PdfResult { - self.writer.write_header(&Version::new(1, 7))?; + self.writer.write_header(Version::new(1, 7))?; self.write_page_tree()?; self.write_pages()?; self.write_fonts()?; self.writer.write_xref_table()?; - self.writer.write_trailer(&Trailer::new(self.offsets.catalog))?; + self.writer.write_trailer(Trailer::new(self.offsets.catalog))?; Ok(self.writer.written()) } @@ -185,7 +200,7 @@ impl<'d, W: Write> PdfEngine<'d, W> { } // Write the text. - text.tj(self.fonts[active_font.0].encode_text(&string)); + text.tj(self.fonts[active_font.0].encode_text(&string)?); }, } } @@ -199,8 +214,11 @@ impl<'d, W: Write> PdfEngine<'d, W> { fn write_fonts(&mut self) -> PdfResult<()> { let mut id = self.offsets.fonts.0; - for font in &self.fonts { - let base_font = format!("ABCDEF+{}", font.name); + for font in &mut self.fonts { + let name = font.read_table::()? + .get_decoded(NameEntry::PostScriptName) + .unwrap_or_else(|| "unknown".to_string()); + let base_font = format!("ABCDEF+{}", name); // Write the base font object referencing the CID font. self.writer.write_obj(id, @@ -211,39 +229,77 @@ impl<'d, W: Write> PdfEngine<'d, W> { ).to_unicode(id + 3) )?; - let system_info = CIDSystemInfo::new("Adobe", "Identity", 0); + // Extract information from the head table. + let head = font.read_table::
()?; + + let font_unit_ratio = 1.0 / (head.units_per_em as f32); + let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); + let font_unit_to_glyph_unit = |fu| { + let size = font_unit_to_size(fu); + (1000.0 * size.to_pt()).round() as GlyphUnit + }; + + let italic = head.mac_style.contains(MacStyleFlags::ITALIC); + let bounding_box = Rect::new( + font_unit_to_glyph_unit(head.x_min as f32), + font_unit_to_glyph_unit(head.y_min as f32), + font_unit_to_glyph_unit(head.x_max as f32), + font_unit_to_glyph_unit(head.y_max as f32), + ); + + // Transform the width into PDF units. + let widths: Vec<_> = font + .read_table::()? + .metrics.iter() + .map(|m| font_unit_to_glyph_unit(m.advance_width as f32)) + .collect(); // Write the CID font referencing the font descriptor. + let system_info = CIDSystemInfo::new("Adobe", "Identity", 0); self.writer.write_obj(id + 1, CIDFont::new( CIDFontType::Type2, base_font.clone(), system_info.clone(), id + 2, - ).widths(vec![WidthRecord::start(0, font.widths.clone())]) + ).widths(vec![WidthRecord::start(0, widths)]) )?; + // Extract information from the post table. + let post = font.read_table::()?; + let fixed_pitch = post.is_fixed_pitch; + let italic_angle = post.italic_angle.to_f32(); + + // Build the flag set. + let mut flags = FontFlags::empty(); + flags.set(FontFlags::SERIF, name.contains("Serif")); + flags.set(FontFlags::FIXED_PITCH, fixed_pitch); + flags.set(FontFlags::ITALIC, italic); + flags.insert(FontFlags::SYMBOLIC); + flags.insert(FontFlags::SMALL_CAP); + + // Extract information from the OS/2 table. + let os2 = font.read_table::()?; + // Write the font descriptor (contains the global information about the font). self.writer.write_obj(id + 2, - FontDescriptor::new( - base_font, - font.flags, - font.italic_angle, - ) - .font_bbox(font.bounding_box) - .ascent(font.ascender) - .descent(font.descender) - .cap_height(font.cap_height) - .stem_v(font.stem_v) - .font_file_2(id + 4) + FontDescriptor::new(base_font, flags, italic_angle) + .font_bbox(bounding_box) + .ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32)) + .descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32)) + .cap_height(font_unit_to_glyph_unit(os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32)) + .stem_v((10.0 + 0.244 * (os2.us_weight_class as f32 - 50.0)) as GlyphUnit) + .font_file_2(id + 4) )?; // Write the CMap, which maps glyphs to unicode codepoints. - let mapping = font.font.mapping.iter().map(|(&c, &cid)| (cid, c)); + let mapping = font.read_table::()? + .mapping.iter() + .map(|(&c, &cid)| (cid, c)); self.writer.write_obj(id + 3, &CMap::new("Custom", system_info, mapping))?; // Finally write the subsetted font program. - self.writer.write_obj(id + 4, &FontStream::new(&font.program))?; + self.writer.write_obj(id + 4, &FontStream::new(font.data().get_ref()))?; id += 5; } @@ -257,78 +313,6 @@ fn ids((start, end): (Ref, Ref)) -> impl Iterator { start ..= end } -/// The data we need from the font. -#[derive(Debug, Clone)] -struct PdfFont { - font: Font, - widths: Vec, - flags: FontFlags, - italic_angle: f32, - bounding_box: Rect, - ascender: GlyphUnit, - descender: GlyphUnit, - cap_height: GlyphUnit, - stem_v: GlyphUnit, -} - -impl PdfFont { - /// Create a subetted version of the font and calculate some information - /// needed for creating the _PDF_. - fn new(font: &Font, chars: &HashSet) -> PdfResult { - /// Convert a size into a _PDF_ glyph unit. - fn size_to_glyph_unit(size: Size) -> GlyphUnit { - (1000.0 * size.to_pt()).round() as GlyphUnit - } - - let subset_result = font.subsetted( - chars.iter().cloned(), - &["head", "hhea", "hmtx", "maxp", "cmap", "cvt ", "fpgm", "prep", "loca", "glyf"][..] - ); - - // Check if the subsetting was successful and if it could not handle this - // font we just copy it plainly. - let subsetted = match subset_result { - Ok(font) => font, - Err(FontError::UnsupportedFont(_)) => font.clone(), - Err(err) => return Err(err.into()), - }; - - let mut flags = FontFlags::empty(); - flags.set(FontFlags::FIXED_PITCH, font.metrics.monospace); - flags.set(FontFlags::SERIF, font.name.contains("Serif")); - flags.insert(FontFlags::SYMBOLIC); - flags.set(FontFlags::ITALIC, font.metrics.italic); - flags.insert(FontFlags::SMALL_CAP); - - let widths = subsetted.widths.iter().map(|&x| size_to_glyph_unit(x)).collect(); - - Ok(PdfFont { - font: subsetted, - widths, - flags, - italic_angle: font.metrics.italic_angle, - bounding_box: Rect::new( - size_to_glyph_unit(font.metrics.bounding_box[0]), - size_to_glyph_unit(font.metrics.bounding_box[1]), - size_to_glyph_unit(font.metrics.bounding_box[2]), - size_to_glyph_unit(font.metrics.bounding_box[3]), - ), - ascender: size_to_glyph_unit(font.metrics.ascender), - descender: size_to_glyph_unit(font.metrics.descender), - cap_height: size_to_glyph_unit(font.metrics.cap_height), - stem_v: (10.0 + 0.244 * (font.metrics.weight_class as f32 - 50.0)) as GlyphUnit, - }) - } -} - -impl std::ops::Deref for PdfFont { - type Target = Font; - - fn deref(&self) -> &Font { - &self.font - } -} - /// The error type for _PDF_ creation. pub enum PdfExportError { /// An error occured while subsetting the font for the _PDF_. diff --git a/src/font/loader.rs b/src/font/loader.rs deleted file mode 100644 index a0e2dce1a..000000000 --- a/src/font/loader.rs +++ /dev/null @@ -1,165 +0,0 @@ -//! Loading of fonts matching queries. - -use std::cell::{RefCell, Ref}; -use std::collections::HashMap; -use std::fmt::{self, Debug, Formatter}; - -use super::{Font, FontInfo, FontClass, FontProvider}; - - -/// Serves fonts matching queries. -pub struct FontLoader<'p> { - /// The font providers. - providers: Vec>, - /// The internal state. Uses interior mutability because the loader works behind - /// an immutable reference to ease usage. - state: RefCell, -} - -/// Internal state of the font loader (seperated to wrap it in a `RefCell`). -struct FontLoaderState { - /// The loaded fonts alongside their external indices. Some fonts may not - /// have external indices because they were loaded but did not contain the - /// required character. However, these are still stored because they may - /// be needed later. The index is just set to `None` then. - fonts: Vec<(Option, Font)>, - /// Allows to retrieve a font (index) quickly if a query was submitted before. - query_cache: HashMap, - /// Allows to re-retrieve loaded fonts by their info instead of loading them again. - info_cache: HashMap, - /// Indexed by external indices (the ones inside the tuples in the `fonts` vector) - /// and maps to internal indices (the actual indices into the vector). - inner_index: Vec, -} - -impl<'p> FontLoader<'p> { - /// Create a new font loader. - pub fn new() -> FontLoader<'p> { - FontLoader { - providers: vec![], - state: RefCell::new(FontLoaderState { - query_cache: HashMap::new(), - info_cache: HashMap::new(), - inner_index: vec![], - fonts: vec![], - }), - } - } - - /// Add a font provider to this loader. - pub fn add_font_provider(&mut self, provider: P) { - self.providers.push(Box::new(provider)); - } - - /// Returns the font (and its index) best matching the query, if there is any. - pub fn get(&self, query: FontQuery) -> Option<(usize, Ref)> { - // Load results from the cache, if we had the exact same query before. - let state = self.state.borrow(); - if let Some(&index) = state.query_cache.get(&query) { - // The font must have an external index already because it is in the query cache. - // It has been served before. - let extern_index = state.fonts[index].0.unwrap(); - let font = Ref::map(state, |s| &s.fonts[index].1); - - return Some((extern_index, font)); - } - drop(state); - - // 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 fonts from all font providers. - for provider in &self.providers { - for info in provider.available().iter() { - let viable = info.classes.contains(class); - let matches = viable && query.classes.iter() - .all(|class| info.classes.contains(class)); - - if matches { - let mut state = self.state.borrow_mut(); - - // Check if we have already loaded this font before, otherwise, - // we will load it from the provider. - let index = if let Some(&index) = state.info_cache.get(info) { - index - } else if let Some(mut source) = provider.get(info) { - let mut program = Vec::new(); - source.read_to_end(&mut program).ok()?; - let font = Font::new(program).ok()?; - - // Insert it into the storage and cache it by its info. - let index = state.fonts.len(); - state.info_cache.insert(info.clone(), index); - state.fonts.push((None, font)); - - index - } else { - // Strangely, this provider lied and cannot give us the promised font. - continue; - }; - - // Proceed if this font has the character we need. - let has_char = state.fonts[index].1.mapping.contains_key(&query.character); - if has_char { - // This font is suitable, thus we cache the query result. - state.query_cache.insert(query, index); - - // Now we have to find out the external index of it or assign - // a new one if it has none. - let external_index = state.fonts[index].0.unwrap_or_else(|| { - // We have to assign an external index before serving. - let new_index = state.inner_index.len(); - state.inner_index.push(index); - state.fonts[index].0 = Some(new_index); - new_index - }); - - // Release the mutable borrow to be allowed to borrow immutably. - drop(state); - - // Finally, get a reference to the actual font. - let font = Ref::map(self.state.borrow(), |s| &s.fonts[index].1); - return Some((external_index, font)); - } - } - } - } - } - - // Not a single match! - None - } - - /// Return the font previously loaded at this index. - /// Panics if the index is not assigned. - #[inline] - pub fn get_with_index(&self, index: usize) -> Ref { - let state = self.state.borrow(); - let internal = state.inner_index[index]; - Ref::map(state, |s| &s.fonts[internal].1) - } -} - -impl Debug for FontLoader<'_> { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let state = self.state.borrow(); - f.debug_struct("FontLoader") - .field("providers", &self.providers.len()) - .field("fonts", &state.fonts) - .field("query_cache", &state.query_cache) - .field("info_cache", &state.info_cache) - .field("inner_index", &state.inner_index) - .finish() - } -} - -/// A query for a font with specific properties. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct FontQuery { - /// Which character is needed. - pub character: char, - /// Which classes the font has to be part of. - pub classes: Vec, - /// The font matching the leftmost class in this sequence should be returned. - pub fallback: Vec, -} diff --git a/src/font/mod.rs b/src/font/mod.rs deleted file mode 100644 index 976327d93..000000000 --- a/src/font/mod.rs +++ /dev/null @@ -1,448 +0,0 @@ -//! Font loading and subsetting. -//! -//! # Font handling -//! To do the typesetting, the engine needs font data. However, to be highly portable the engine -//! itself assumes nothing about the environment. To still work with fonts, the consumer of this -//! library has to add _font providers_ to their typesetting instance. These can be queried for font -//! data given flexible font filters specifying required font families and styles. A font provider -//! is a type implementing the [`FontProvider`](crate::font::FontProvider) trait. -//! -//! There is one [included font provider](crate::font::FileSystemFontProvider) that serves fonts -//! from a folder on the file system. - -use std::collections::HashMap; -use std::fs::{self, File}; -use std::io::{self, Cursor, Read, Seek, BufReader}; -use std::path::{Path, PathBuf}; - -use opentype::{Error as OpentypeError, OpenTypeReader}; -use opentype::tables::{Header, Name, CharMap, HorizontalMetrics, Post, OS2}; -use opentype::types::{MacStyleFlags, NameEntry}; -use toml::map::Map as TomlMap; -use toml::value::Value as TomlValue; - -use self::subset::Subsetter; -use crate::size::Size; - -mod loader; -mod subset; - -pub use loader::{FontLoader, FontQuery}; - - -/// A parsed _OpenType_ font program. -#[derive(Debug, Clone)] -pub struct Font { - /// The name of the font. - pub name: String, - /// The complete, raw bytes of the font program. - pub program: Vec, - /// The mapping from character codes to glyph ids. - pub mapping: HashMap, - /// The widths of the glyphs indexed by glyph id. - pub widths: Vec, - /// The id of the fallback glyph. - pub default_glyph: u16, - /// The typesetting or exporting-relevant metrics of this font. - pub metrics: FontMetrics, -} - -/// Font metrics relevant to the typesetting or exporting processes. -#[derive(Debug, Copy, Clone)] -pub struct FontMetrics { - /// Whether the font is italic. - pub italic: bool, - /// Whether font is monospace. - pub monospace: bool, - /// The angle of text in italics (in counter-clockwise degrees from vertical). - pub italic_angle: f32, - /// The extremal values [x_min, y_min, x_max, y_max] for all glyph bounding boxes. - pub bounding_box: [Size; 4], - /// The typographic ascender. - pub ascender: Size, - /// The typographic descender. - pub descender: Size, - /// The approximate height of capital letters. - pub cap_height: Size, - /// The weight class of the font (from 100 for thin to 900 for heavy). - pub weight_class: u16, -} - -impl Font { - /// Create a `Font` from a raw font program. - pub fn new(program: Vec) -> FontResult { - let cursor = Cursor::new(&program); - let mut reader = OpenTypeReader::new(cursor); - - // All of these tables are required by the OpenType specification, - // so we do not really have to handle the case that they are missing. - let head = reader.read_table::
()?; - let name = reader.read_table::()?; - let os2 = reader.read_table::()?; - let cmap = reader.read_table::()?; - let hmtx = reader.read_table::()?; - let post = reader.read_table::()?; - - // Create a conversion function between font units and sizes. - let font_unit_ratio = 1.0 / (head.units_per_em as f32); - let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); - - let font_name = name - .get_decoded(NameEntry::PostScriptName) - .unwrap_or_else(|| "unknown".to_owned()); - - let widths = hmtx.metrics.iter() - .map(|m| font_unit_to_size(m.advance_width as f32)).collect(); - - let metrics = FontMetrics { - italic: head.mac_style.contains(MacStyleFlags::ITALIC), - monospace: post.is_fixed_pitch, - italic_angle: post.italic_angle.to_f32(), - bounding_box: [ - font_unit_to_size(head.x_min as f32), - font_unit_to_size(head.y_min as f32), - font_unit_to_size(head.x_max as f32), - font_unit_to_size(head.y_max as f32), - ], - ascender: font_unit_to_size(os2.s_typo_ascender as f32), - descender: font_unit_to_size(os2.s_typo_descender as f32), - cap_height: font_unit_to_size(os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32), - weight_class: os2.us_weight_class, - }; - - Ok(Font { - name: font_name, - program, - mapping: cmap.mapping, - widths, - default_glyph: os2.us_default_char.unwrap_or(0), - metrics, - }) - } - - /// Encode a character into it's glyph id. - #[inline] - pub fn encode(&self, character: char) -> u16 { - self.mapping.get(&character).map(|&g| g).unwrap_or(self.default_glyph) - } - - /// Encode the given text into a vector of glyph ids. - #[inline] - pub fn encode_text(&self, text: &str) -> Vec { - const BYTES_PER_GLYPH: usize = 2; - let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * text.len()); - for c in text.chars() { - let glyph = self.encode(c); - bytes.push((glyph >> 8) as u8); - bytes.push((glyph & 0xff) as u8); - } - bytes - } - - /// Generate a subsetted version of this font. - /// - /// This version includes only the given `chars` and _OpenType_ `tables`. - #[inline] - pub fn subsetted(&self, chars: C, tables: I) -> Result - where - C: IntoIterator, - I: IntoIterator, - S: AsRef - { - Subsetter::subset(self, chars, tables) - } -} - -/// A type that provides fonts. -pub trait FontProvider { - /// Returns a font with the given info if this provider has one. - fn get(&self, info: &FontInfo) -> Option>; - - /// The available fonts this provider can serve. While these should generally - /// be retrievable through the `get` method, this is not guaranteed. - fn available<'p>(&'p self) -> &'p [FontInfo]; -} - -/// A wrapper trait around `Read + Seek`. -/// -/// This type is needed because currently you can't make a trait object with two traits, like -/// `Box`. Automatically implemented for all types that are [`Read`] and [`Seek`]. -pub trait FontData: Read + Seek {} -impl FontData for T {} - -/// Classifies a font by listing the font classes it is part of. -/// -/// All fonts with the same [`FontInfo`] are part of the same intersection -/// of [font classes](FontClass). -/// -/// This structure can be constructed conveniently through the [`font`] macro. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct FontInfo { - /// The font classes this font is part of. - pub classes: Vec, -} - -impl FontInfo { - /// Create a new font info from a collection of classes. - #[inline] - pub fn new(classes: I) -> FontInfo where I: IntoIterator { - FontInfo { - classes: classes.into_iter().collect() - } - } -} - -/// A class of fonts. -/// -/// The set of all fonts can be classified into subsets of font classes like -/// _serif_ or _bold_. This enum lists such subclasses. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum FontClass { - Serif, - SansSerif, - Monospace, - Regular, - Bold, - Italic, - /// A custom family like _Arial_ or _Times_. - Family(String), -} - -/// A macro to create [FontInfos](crate::font::FontInfo) easily. -/// -/// Accepts an ordered list of font classes. Strings expressions are parsed -/// into custom `Family`-variants and others can be named directly. -/// -/// # Examples -/// ``` -/// # use typeset::font; -/// // Noto Sans in regular typeface. -/// font!["NotoSans", "Noto", Regular, SansSerif]; -/// -/// // Noto Serif in italics and boldface. -/// font!["NotoSerif", "Noto", Bold, Italic, Serif]; -/// -/// // Arial in italics. -/// font!["Arial", Italic, SansSerif]; -/// -/// // Noto Emoji, which works in sans-serif and serif contexts. -/// font!["NotoEmoji", "Noto", Regular, SansSerif, Serif, Monospace]; -/// ``` -#[macro_export] -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)*) - }; - (@__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)*) - }; - - // Entry point - ($($tts:tt)*) => {{ - let mut classes = Vec::new(); - font!(@__cls classes, $($tts)*); - $crate::font::FontInfo { classes } - }}; -} - -/// A font provider serving fonts from a folder on the local file system. -#[derive(Debug)] -pub struct FileSystemFontProvider { - /// The base folder all other paths are relative to. - base: PathBuf, - /// Paths of the fonts relative to the `base` path. - paths: Vec, - /// The info for the font with the same index in `paths`. - infos: Vec, -} - -impl FileSystemFontProvider { - /// Create a new provider serving fonts from a base path. The `fonts` iterator - /// should contain paths of fonts relative to the base alongside matching - /// infos for these fonts. - /// - /// # Example - /// Serve the two fonts `NotoSans-Regular` and `NotoSans-Italic` from the local folder - /// `../fonts`. - /// ``` - /// # use typeset::{font::FileSystemFontProvider, font}; - /// FileSystemFontProvider::new("../fonts", vec![ - /// ("NotoSans-Regular.ttf", font!["NotoSans", Regular, SansSerif]), - /// ("NotoSans-Italic.ttf", font!["NotoSans", Italic, SansSerif]), - /// ]); - /// ``` - pub fn new(base: B, fonts: I) -> FileSystemFontProvider - where - B: Into, - I: IntoIterator, - P: Into, - { - let iter = fonts.into_iter(); - - // Find out how long the iterator is at least, to reserve the correct - // capacity for the vectors. - let min = iter.size_hint().0; - let mut paths = Vec::with_capacity(min); - let mut infos = Vec::with_capacity(min); - - for (path, info) in iter { - paths.push(path.into()); - infos.push(info); - } - - FileSystemFontProvider { - base: base.into(), - paths, - infos, - } - } - - /// Create a new provider from a font listing file. - pub fn from_listing>(file: P) -> FontResult { - fn inv(message: S) -> FontError { - FontError::InvalidListing(message.to_string()) - } - - let file = file.as_ref(); - let base = file.parent() - .ok_or_else(|| inv("expected listings file"))?; - - let bytes = fs::read(file)?; - let map: TomlMap = toml::de::from_slice(&bytes) - .map_err(|err| inv(err))?; - - let mut paths = Vec::new(); - let mut infos = Vec::new(); - - for value in map.values() { - if let TomlValue::Table(table) = value { - // Parse the string file key. - paths.push(match table.get("file") { - Some(TomlValue::String(s)) => PathBuf::from(s), - _ => return Err(inv("expected file name")), - }); - - // Parse the array classes key. - infos.push(if let Some(TomlValue::Array(array)) = table.get("classes") { - let mut classes = Vec::with_capacity(array.len()); - for class in array { - classes.push(match class { - TomlValue::String(class) => match class.as_str() { - "Serif" => FontClass::Serif, - "SansSerif" => FontClass::SansSerif, - "Monospace" => FontClass::Monospace, - "Regular" => FontClass::Regular, - "Bold" => FontClass::Bold, - "Italic" => FontClass::Italic, - _ => FontClass::Family(class.to_string()), - }, - _ => return Err(inv("expect font class string")), - }) - } - FontInfo { classes } - } else { - return Err(inv("expected font classes")); - }); - } else { - return Err(inv("expected file/classes table")); - } - } - - Ok(FileSystemFontProvider { - base: base.to_owned(), - paths, - infos, - }) - } -} - -impl FontProvider for FileSystemFontProvider { - #[inline] - fn get(&self, info: &FontInfo) -> Option> { - let index = self.infos.iter().position(|c| c == info)?; - let path = &self.paths[index]; - let full_path = self.base.join(path); - let file = File::open(full_path).ok()?; - Some(Box::new(BufReader::new(file)) as Box) - } - - #[inline] - fn available<'p>(&'p self) -> &'p [FontInfo] { - &self.infos - } -} - - -/// The error type for font operations. -pub enum FontError { - /// The font file is incorrect. - InvalidFont(String), - /// The font listing is incorrect. - InvalidListing(String), - /// A character requested for subsetting was not present in the source font. - MissingCharacter(char), - /// A requested or required table was not present. - MissingTable(String), - /// The table is unknown to the subsetting engine. - UnsupportedTable(String), - /// The font is not supported by the subsetting engine. - UnsupportedFont(String), - /// An I/O Error occured while reading the font program. - Io(io::Error), -} - -error_type! { - err: FontError, - res: FontResult, - show: f => match err { - FontError::InvalidFont(message) => write!(f, "invalid font: {}", message), - FontError::InvalidListing(message) => write!(f, "invalid font listing: {}", message), - FontError::MissingCharacter(c) => write!(f, "missing character: '{}'", c), - FontError::MissingTable(table) => write!(f, "missing table: '{}'", table), - FontError::UnsupportedTable(table) => write!(f, "unsupported table: {}", table), - FontError::UnsupportedFont(message) => write!(f, "unsupported font: {}", message), - FontError::Io(err) => write!(f, "io error: {}", err), - }, - source: match err { - FontError::Io(err) => Some(err), - _ => None, - }, - from: (io::Error, FontError::Io(err)), - from: (OpentypeError, match err { - OpentypeError::InvalidFont(message) => FontError::InvalidFont(message), - OpentypeError::MissingTable(tag) => FontError::MissingTable(tag.to_string()), - OpentypeError::Io(err) => FontError::Io(err), - }), -} - - -#[cfg(test)] -mod tests { - use super::*; - - /// Tests the font info macro. - #[test] - 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!["NotoSerif", Serif, Italic, "Noto"], FontInfo { - classes: vec![ - Family("NotoSerif".to_owned()), Serif, Italic, - Family("Noto".to_owned()) - ], - }); - } -} diff --git a/src/font/subset.rs b/src/font/subset.rs deleted file mode 100644 index 840d9e6b4..000000000 --- a/src/font/subset.rs +++ /dev/null @@ -1,562 +0,0 @@ -//! Subsetting of opentype fonts. - -use std::collections::HashMap; -use std::io::{Cursor, Seek, SeekFrom}; - -use byteorder::{BE, ReadBytesExt, WriteBytesExt}; -use opentype::{OpenTypeReader, Outlines, Table, TableRecord, Tag}; -use opentype::tables::{Header, CharMap, Locations, HorizontalMetrics, Glyphs}; - -use crate::size::Size; -use super::{Font, FontError, FontResult}; - - -/// Subsets a font. -#[derive(Debug)] -pub struct Subsetter<'a> { - // The original font - font: &'a Font, - reader: OpenTypeReader>, - outlines: Outlines, - tables: Vec, - glyphs: Vec, - - // The subsetted font - chars: Vec, - records: Vec, - body: Vec, -} - -impl<'a> Subsetter<'a> { - /// Subset a font. See [`Font::subetted`] for more details. - pub fn subset(font: &Font, chars: C, tables: I) -> Result - where - C: IntoIterator, - I: IntoIterator, - S: AsRef - { - let mut reader = OpenTypeReader::from_slice(&font.program); - - let outlines = reader.outlines()?; - let table_records = reader.tables()?.to_vec(); - let chars: Vec<_> = chars.into_iter().collect(); - - let subsetter = Subsetter { - font, - reader, - outlines, - tables: table_records, - glyphs: Vec::with_capacity(1 + chars.len()), - chars, - records: vec![], - body: vec![], - }; - - subsetter.run(tables) - } - - /// Do the subsetting. - fn run(mut self, tables: I) -> FontResult - where I: IntoIterator, S: AsRef { - if self.outlines == Outlines::CFF { - return Err(FontError::UnsupportedFont("CFF outlines".to_string())); - } - - // Find out which glyphs to include based on which characters we want and - // which glyphs are additionally used by composite glyphs. - self.find_glyphs()?; - - // Copy/subset all the tables the caller wants. - for table in tables.into_iter() { - let tag = table.as_ref().parse() - .map_err(|_| FontError::UnsupportedTable(table.as_ref().to_string()))?; - - if self.contains_table(tag) { - self.subset_table(tag)?; - } - } - - // Preprend the new header to the body. We have to do this last, because - // we only have the necessary information now. - self.write_header()?; - - Ok(Font { - name: self.font.name.clone(), - mapping: self.compute_mapping(), - widths: self.compute_widths()?, - program: self.body, - default_glyph: self.font.default_glyph, - metrics: self.font.metrics, - }) - } - - /// Store all glyphs the subset shall contain into `self.glyphs`. - fn find_glyphs(&mut self) -> FontResult<()> { - if self.outlines == Outlines::TrueType { - let char_map = self.read_table::()?; - let glyf = self.read_table::()?; - - // The default glyph should always be at index 0. - self.glyphs.push(self.font.default_glyph); - - for &c in &self.chars { - let glyph = char_map.get(c).ok_or_else(|| FontError::MissingCharacter(c))?; - self.glyphs.push(glyph); - } - - // Collect the glyphs not used mapping from characters but used in - // composite glyphs, too. - let mut i = 0; - while i < self.glyphs.len() as u16 { - let glyph_id = self.glyphs[i as usize]; - let glyph = glyf.get(glyph_id).take_invalid("missing glyf entry")?; - - for &composite in &glyph.composites { - if self.glyphs.iter().rev().all(|&x| x != composite) { - self.glyphs.push(composite); - } - } - - i += 1; - } - } else { - unimplemented!() - } - - Ok(()) - } - - /// Prepend the new header to the constructed body. - fn write_header(&mut self) -> FontResult<()> { - // Create an output buffer - const BASE_HEADER_LEN: usize = 12; - const TABLE_RECORD_LEN: usize = 16; - let header_len = BASE_HEADER_LEN + self.records.len() * TABLE_RECORD_LEN; - let mut header = Vec::with_capacity(header_len); - - let num_tables = self.records.len() as u16; - - let mut max_power = 1u16; - while max_power * 2 <= num_tables { - max_power *= 2; - } - max_power = std::cmp::min(max_power, num_tables); - - let search_range = max_power * 16; - let entry_selector = (max_power as f32).log2() as u16; - let range_shift = num_tables * 16 - search_range; - - // Write the base OpenType header - header.write_u32::(match self.outlines { - Outlines::TrueType => 0x00010000, - Outlines::CFF => 0x4f54544f, - })?; - header.write_u16::(num_tables)?; - header.write_u16::(search_range)?; - header.write_u16::(entry_selector)?; - header.write_u16::(range_shift)?; - - // Write the table records - for record in &self.records { - header.extend(record.tag.value()); - header.write_u32::(record.check_sum)?; - header.write_u32::(header_len as u32 + record.offset)?; - header.write_u32::(record.length)?; - } - - // Prepend the fresh header to the body. - header.append(&mut self.body); - self.body = header; - - Ok(()) - } - - /// Compute the new subsetted widths vector. - fn compute_widths(&self) -> FontResult> { - let mut widths = Vec::with_capacity(self.glyphs.len()); - for &glyph in &self.glyphs { - let &width = self.font.widths.get(glyph as usize) - .take_invalid("missing glyph width")?; - widths.push(width); - } - Ok(widths) - } - - /// Compute the new character to glyph id mapping. - fn compute_mapping(&self) -> HashMap { - // The mapping is basically just the index into the char vector, but we add one - // to each index here because we added the default glyph to the front. - self.chars.iter().enumerate() - .map(|(i, &c)| (c, 1 + i as u16)) - .collect::>() - } - - /// Subset and write the table with the given tag to the output. - fn subset_table(&mut self, tag: Tag) -> FontResult<()> { - match tag.value() { - // These tables can just be copied. - b"head" | b"name" | b"OS/2" | - b"cvt " | b"fpgm" | b"prep" | b"gasp" => self.copy_table(tag), - - // These tables have more complex subsetting routines. - b"hhea" => self.subset_hhea(), - b"hmtx" => self.subset_hmtx(), - b"maxp" => self.subset_maxp(), - b"post" => self.subset_post(), - b"cmap" => self.subset_cmap(), - b"glyf" => self.subset_glyf(), - b"loca" => self.subset_loca(), - - _ => Err(FontError::UnsupportedTable(tag.to_string())) - } - } - - /// Copy the table body without modification. - fn copy_table(&mut self, tag: Tag) -> FontResult<()> { - self.write_table_body(tag, |this| { - let table = this.read_table_data(tag)?; - Ok(this.body.extend(table)) - }) - } - - /// Subset the `hhea` table by changing the number of horizontal metrics in it. - fn subset_hhea(&mut self) -> FontResult<()> { - let tag = "hhea".parse().unwrap(); - let hhea = self.read_table_data(tag)?; - let glyph_count = self.glyphs.len() as u16; - self.write_table_body(tag, |this| { - this.body.extend(&hhea[..hhea.len() - 2]); - this.body.write_u16::(glyph_count)?; - Ok(()) - }) - } - - /// Subset the `hmtx` table by changing the included metrics. - fn subset_hmtx(&mut self) -> FontResult<()> { - let tag = "hmtx".parse().unwrap(); - let hmtx = self.read_table::()?; - self.write_table_body(tag, |this| { - for &glyph in &this.glyphs { - let metrics = hmtx.get(glyph).take_invalid("missing glyph metrics")?; - this.body.write_u16::(metrics.advance_width)?; - this.body.write_i16::(metrics.left_side_bearing)?; - } - Ok(()) - }) - } - - /// Subset the `maxp` table by changing the glyph count in it. - fn subset_maxp(&mut self) -> FontResult<()> { - let tag = "maxp".parse().unwrap(); - let maxp = self.read_table_data(tag)?; - let glyph_count = self.glyphs.len() as u16; - self.write_table_body(tag, |this| { - this.body.extend(&maxp[..4]); - this.body.write_u16::(glyph_count)?; - Ok(this.body.extend(&maxp[6..])) - }) - } - - /// Subset the `post` table by removing all name information. - fn subset_post(&mut self) -> FontResult<()> { - let tag = "post".parse().unwrap(); - let post = self.read_table_data(tag)?; - self.write_table_body(tag, |this| { - this.body.write_u32::(0x00030000)?; - Ok(this.body.extend(&post[4..32])) - }) - } - - /// Subset the `cmap` table by only including the selected characters. - /// Always uses format 12 for simplicity. - fn subset_cmap(&mut self) -> FontResult<()> { - let tag = "cmap".parse().unwrap(); - - self.write_table_body(tag, |this| { - let mut groups = Vec::new(); - - // Find out which chars are in consecutive groups. - let mut end = 0; - let len = this.chars.len(); - while end < len { - // Compute the end of the consecutive group. - let start = end; - while end + 1 < len && this.chars[end+1] as u32 == this.chars[end] as u32 + 1 { - end += 1; - } - - // Add one to the start because we inserted the default glyph in front. - let glyph_id = 1 + start; - groups.push((this.chars[start], this.chars[end], glyph_id)); - end += 1; - } - - // Write the table header. - this.body.write_u16::(0)?; - this.body.write_u16::(1)?; - this.body.write_u16::(3)?; - this.body.write_u16::(10)?; - this.body.write_u32::(12)?; - - // Write the subtable header. - this.body.write_u16::(12)?; - this.body.write_u16::(0)?; - this.body.write_u32::((16 + 12 * groups.len()) as u32)?; - this.body.write_u32::(0)?; - this.body.write_u32::(groups.len() as u32)?; - - // Write the subtable body. - for group in &groups { - this.body.write_u32::(group.0 as u32)?; - this.body.write_u32::(group.1 as u32)?; - this.body.write_u32::(group.2 as u32)?; - } - - Ok(()) - }) - } - - /// Subset the `glyf` table by changing the indices of composite glyphs. - fn subset_glyf(&mut self) -> FontResult<()> { - let tag = "glyf".parse().unwrap(); - let loca = self.read_table::()?; - let glyf = self.read_table_data(tag)?; - - self.write_table_body(tag, |this| { - for &glyph in &this.glyphs { - // Find out the location of the glyph in the glyf table. - let start = loca.offset(glyph).take_invalid("missing loca entry")?; - let end = loca.offset(glyph + 1).take_invalid("missing loca entry")?; - - // If this glyph has no contours, skip it. - if end == start { - continue; - } - - let mut glyph_data = glyf.get(start as usize .. end as usize) - .take_invalid("missing glyph data")?.to_vec(); - let mut cursor = Cursor::new(&mut glyph_data); - - // This is a composite glyph - let num_contours = cursor.read_i16::()?; - if num_contours < 0 { - cursor.seek(SeekFrom::Current(8))?; - loop { - let flags = cursor.read_u16::()?; - - let old_glyph_index = cursor.read_u16::()?; - - // Compute the new glyph index by searching for it's index - // in the glyph vector. - let new_glyph_index = this.glyphs.iter() - .position(|&g| g == old_glyph_index) - .take_invalid("invalid composite glyph")? as u16; - - // Overwrite the old index with the new one. - cursor.seek(SeekFrom::Current(-2))?; - cursor.write_u16::(new_glyph_index)?; - - // This was the last component - if flags & 0x0020 == 0 { - break; - } - - // Skip additional arguments. - let skip = if flags & 1 != 0 { 4 } else { 2 } - + if flags & 8 != 0 { 2 } - else if flags & 64 != 0 { 4 } - else if flags & 128 != 0 { 8 } - else { 0 }; - - cursor.seek(SeekFrom::Current(skip))?; - } - } - - this.body.extend(glyph_data); - } - Ok(()) - }) - } - - /// Subset the `loca` table by changing to the new offsets. - fn subset_loca(&mut self) -> FontResult<()> { - let format = self.read_table::
()?.index_to_loc_format; - let tag = "loca".parse().unwrap(); - let loca = self.read_table::()?; - - self.write_table_body(tag, |this| { - let mut offset = 0; - for &glyph in &this.glyphs { - if format == 0 { - this.body.write_u16::((offset / 2) as u16)?; - } else { - this.body.write_u32::(offset)?; - } - - let len = loca.length(glyph).take_invalid("missing loca entry")?; - offset += len; - } - - // Write the final offset (so that it is known how long the last glyph is). - if format == 0 { - this.body.write_u16::((offset / 2) as u16)?; - } else { - this.body.write_u32::(offset)?; - } - - Ok(()) - }) - } - - /// Let a writer write the table body and then store the relevant metadata. - fn write_table_body(&mut self, tag: Tag, writer: F) -> FontResult<()> - where F: FnOnce(&mut Self) -> FontResult<()> { - // Run the writer and capture the length. - let start = self.body.len(); - writer(self)?; - let end = self.body.len(); - - // Pad with zeros. - while (self.body.len() - start) % 4 != 0 { - self.body.push(0); - } - - Ok(self.records.push(TableRecord { - tag, - check_sum: calculate_check_sum(&self.body[start..]), - offset: start as u32, - length: (end - start) as u32, - })) - } - - /// Whether this font contains a given table. - fn contains_table(&self, tag: Tag) -> bool { - self.tables.binary_search_by_key(&tag, |r| r.tag).is_ok() - } - - /// Read a table with the opentype reader. - fn read_table(&mut self) -> FontResult { - self.reader.read_table::().map_err(Into::into) - } - - /// Read the raw table data of a table. - fn read_table_data(&self, tag: Tag) -> FontResult<&'a [u8]> { - let record = match self.tables.binary_search_by_key(&tag, |r| r.tag) { - Ok(index) => &self.tables[index], - Err(_) => return Err(FontError::MissingTable(tag.to_string())), - }; - - self.font.program - .get(record.offset as usize .. (record.offset + record.length) as usize) - .take_invalid("missing table data") - } -} - -/// Calculate a checksum over the sliced data as sum of u32's. The data -/// length has to be a multiple of four. -fn calculate_check_sum(data: &[u8]) -> u32 { - let mut sum = 0u32; - data.chunks_exact(4).for_each(|c| { - sum = sum.wrapping_add( - ((c[0] as u32) << 24) - + ((c[1] as u32) << 16) - + ((c[2] as u32) << 8) - + (c[3] as u32) - ); - }); - sum -} - -/// Helper trait to create subsetting errors more easily. -trait TakeInvalid: Sized { - /// Pull the type out of self, returning an invalid font - /// error if self was not valid. - fn take_invalid>(self, message: S) -> FontResult; -} - -impl TakeInvalid for Option { - fn take_invalid>(self, message: S) -> FontResult { - self.ok_or(FontError::InvalidFont(message.into())) - } -} - - -#[cfg(test)] -mod tests { - use std::fs; - use crate::font::Font; - use opentype::{OpenTypeReader, TableRecord}; - use opentype::tables::{CharMap, Locations}; - - const ALPHABET: &str = "abcdefghijklmnopqrstuvwxyz"; - - /// Stores some tables for inspections. - struct Tables<'a> { - cmap: CharMap, - loca: Locations, - glyf_data: &'a [u8], - } - - impl<'a> Tables<'a> { - /// Load the tables from the font. - fn new(font: &'a Font) -> Tables<'a> { - let mut reader = OpenTypeReader::from_slice(&font.program); - - let cmap = reader.read_table::().unwrap(); - let loca = reader.read_table::().unwrap(); - - let &TableRecord { offset, length, .. } = reader.get_table_record("glyf").unwrap(); - let glyf_data = &font.program[offset as usize .. (offset + length) as usize]; - - Tables { cmap, loca, glyf_data } - } - - /// Return the glyph data for the given character. - fn glyph_data(&self, character: char) -> Option<&'a [u8]> { - let glyph = self.cmap.get(character)?; - let start = self.loca.offset(glyph)?; - let end = self.loca.offset(glyph + 1)?; - Some(&self.glyf_data[start as usize .. end as usize]) - } - } - - /// Return the original and subsetted version of a font with the characters - /// included that are given as the chars of the string. - fn subset(font: &str, chars: &str) -> (Font, Font) { - let program = fs::read(format!("../fonts/{}", font)).unwrap(); - let font = Font::new(program).unwrap(); - - let subsetted = font.subsetted( - chars.chars(), - &["name", "OS/2", "post", "head", "hhea", "hmtx", "maxp", "cmap", - "cvt ", "fpgm", "prep", "gasp", "loca", "glyf"][..] - ).unwrap(); - - (font, subsetted) - } - - /// A test that creates a subsetted fonts in the `target` directory - /// for manual inspection. - #[test] - fn manual_files() { - let subsetted = subset("SourceSansPro-Regular.ttf", ALPHABET).1; - fs::write("../target/SourceSansPro-Subsetted.ttf", &subsetted.program).unwrap(); - - let subsetted = subset("NotoSans-Regular.ttf", ALPHABET).1; - fs::write("../target/NotoSans-Subsetted.ttf", &subsetted.program).unwrap(); - } - - /// Tests whether the glyph data for specific glyphs match in the original - /// and subsetted version. - #[test] - fn glyph_data() { - let (font, subsetted) = subset("SourceSansPro-Regular.ttf", ALPHABET); - let font_tables = Tables::new(&font); - let subset_tables = Tables::new(&subsetted); - - // Go through all characters but skip the composite glyphs. - for c in ALPHABET.chars().filter(|&x| x != 'i' && x != 'j') { - assert_eq!(font_tables.glyph_data(c), subset_tables.glyph_data(c)); - } - } -} diff --git a/src/func.rs b/src/func.rs index e7f3dbfac..c8e464f8f 100644 --- a/src/func.rs +++ b/src/func.rs @@ -3,8 +3,8 @@ use std::any::Any; use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; +use toddle::query::FontClass; -use crate::font::FontClass; use crate::layout::{layout, Layout, LayoutContext, LayoutResult}; use crate::layout::flex::FlexLayout; use crate::parsing::{parse, ParseContext, ParseError, ParseResult}; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 676024e8b..6b0400896 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -3,8 +3,10 @@ use std::borrow::Cow; use std::mem; +use toddle::query::{SharedFontLoader, FontClass}; +use toddle::Error as FontError; + use crate::doc::LayoutAction; -use crate::font::{FontLoader, FontClass, FontError}; use crate::size::{Size, Size2D, SizeBox}; use crate::syntax::{SyntaxTree, Node, FuncCall}; use crate::style::TextStyle; @@ -33,10 +35,10 @@ pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult } /// The context for layouting. -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] pub struct LayoutContext<'a, 'p> { /// Loads fonts matching queries. - pub loader: &'a FontLoader<'p>, + pub loader: &'a SharedFontLoader<'p>, /// Base style to set text with. pub style: &'a TextStyle, /// The space to layout in. @@ -66,12 +68,11 @@ impl LayoutSpace { } /// Transforms a syntax tree into a box layout. -#[derive(Debug)] struct Layouter<'a, 'p> { tree: &'a SyntaxTree, box_layouter: BoxLayouter, flex_layout: FlexLayout, - loader: &'a FontLoader<'p>, + loader: &'a SharedFontLoader<'p>, style: Cow<'a, TextStyle>, } diff --git a/src/layout/text.rs b/src/layout/text.rs index 6aa5ef719..c1f4e464b 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,22 +1,26 @@ //! Layouting of text into boxes. +use toddle::query::{FontQuery, SharedFontLoader}; +use toddle::tables::{Header, CharMap, HorizontalMetrics}; + use crate::doc::LayoutAction; -use crate::font::FontQuery; use crate::size::{Size, Size2D}; use super::*; /// The context for text layouting. -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] pub struct TextContext<'a, 'p> { /// Loads fonts matching queries. - pub loader: &'a FontLoader<'p>, + pub loader: &'a SharedFontLoader<'p>, /// Base style to set text with. pub style: &'a TextStyle, } /// Layout one piece of text without any breaks as one continous box. pub fn layout(text: &str, ctx: TextContext) -> LayoutResult { + let mut loader = ctx.loader.borrow_mut(); + let mut actions = Vec::new(); let mut active_font = std::usize::MAX; let mut buffer = String::new(); @@ -25,14 +29,45 @@ pub fn layout(text: &str, ctx: TextContext) -> LayoutResult { // Walk the characters. for character in text.chars() { // Retrieve the best font for this character. - let (index, font) = ctx.loader.get(FontQuery { - classes: ctx.style.classes.clone(), - fallback: ctx.style.fallback.clone(), - character, - }).ok_or_else(|| LayoutError::NoSuitableFont(character))?; + let mut font = None; + let mut classes = ctx.style.classes.clone(); + for class in &ctx.style.fallback { + classes.push(class.clone()); + + font = loader.get(FontQuery { + chars: &[character], + classes: &classes, + }); + + if font.is_some() { + break; + } + + classes.pop(); + } + + let (font, index) = match font { + Some(f) => f, + None => return Err(LayoutError::NoSuitableFont(character)), + }; + + // Create a conversion function between font units and sizes. + let font_unit_ratio = 1.0 / (font.read_table::
()?.units_per_em as f32); + let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); // Add the char width to the total box width. - let char_width = font.widths[font.encode(character) as usize] * ctx.style.font_size; + let glyph = font.read_table::()? + .get(character) + .expect("layout text: font should have char"); + + let glyph_width = font_unit_to_size( + font.read_table::()? + .get(glyph) + .expect("layout text: font should have glyph") + .advance_width as f32 + ); + + let char_width = glyph_width * ctx.style.font_size; width += char_width; // Change the font if necessary. diff --git a/src/lib.rs b/src/lib.rs index 3dc669110..1a97c5604 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,6 @@ -//! The compiler for the _Typeset_ typesetting language 📜. +//! The compiler for the _Typst_ typesetting language. //! -//! # Compilation +//! # Steps //! - **Parsing:** The parsing step first transforms a plain string into an [iterator of //! tokens](crate::parsing::Tokens). Then the [parser](crate::parsing::Parser) operates on that to //! construct a syntax tree. The structures describing the tree can be found in the [syntax] @@ -11,38 +11,13 @@ //! - **Exporting:** The finished document can then be exported into supported formats. Submodules //! for the supported formats are located in the [export] module. Currently the only supported //! format is _PDF_. -//! -//! # Example -//! ``` -//! use std::fs::File; -//! use typeset::Typesetter; -//! use typeset::{font::FileSystemFontProvider, font}; -//! use typeset::export::pdf::PdfExporter; -//! -//! // Simple example source code. -//! let src = "Hello World from _Typeset_! 🌍"; -//! -//! // Create a typesetter with a font provider that provides three fonts -//! // (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!["Computer Modern", Regular, Serif]), -//! ("CMU-Serif-Italic.ttf", font!["Computer Modern", Italic, Serif]), -//! ("NotoEmoji-Regular.ttf", font!["Noto", Regular, Serif, SansSerif, Monospace]), -//! ])); -//! -//! // Typeset the document and export it into a PDF file. -//! let document = typesetter.typeset(src).unwrap(); -//! # /* -//! let file = File::create("hello-typeset.pdf").unwrap(); -//! # */ -//! # let file = File::create("../target/typeset-doc-hello.pdf").unwrap(); -//! let exporter = PdfExporter::new(); -//! exporter.export(&document, typesetter.loader(), file).unwrap(); -//! ``` + +pub extern crate toddle; + +use std::cell::RefCell; +use toddle::query::{FontLoader, SharedFontLoader, FontProvider}; use crate::doc::Document; -use crate::font::{FontLoader, FontProvider}; use crate::func::Scope; use crate::parsing::{parse, ParseContext, ParseResult, ParseError}; use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult}; @@ -54,8 +29,6 @@ use crate::syntax::SyntaxTree; mod error; pub mod doc; pub mod export; -#[macro_use] -pub mod font; pub mod func; pub mod layout; pub mod parsing; @@ -67,10 +40,9 @@ pub mod syntax; /// Transforms source code into typesetted documents. /// /// Can be configured through various methods. -#[derive(Debug)] pub struct Typesetter<'p> { /// The font loader shared by all typesetting processes. - loader: FontLoader<'p>, + loader: SharedFontLoader<'p>, /// The default text style. text_style: TextStyle, /// The default page style. @@ -82,7 +54,7 @@ impl<'p> Typesetter<'p> { #[inline] pub fn new() -> Typesetter<'p> { Typesetter { - loader: FontLoader::new(), + loader: RefCell::new(FontLoader::new()), text_style: TextStyle::default(), page_style: PageStyle::default(), } @@ -103,7 +75,7 @@ impl<'p> Typesetter<'p> { /// Add a font provider to the context of this typesetter. #[inline] pub fn add_font_provider(&mut self, provider: P) where P: FontProvider { - self.loader.add_font_provider(provider); + self.loader.get_mut().add_provider(provider); } /// Parse source code into a syntax tree. @@ -135,7 +107,7 @@ impl<'p> Typesetter<'p> { } /// A reference to the backing font loader. - pub fn loader(&self) -> &FontLoader<'p> { + pub fn loader(&self) -> &SharedFontLoader<'p> { &self.loader } } @@ -170,12 +142,12 @@ mod test { use std::io::BufWriter; use crate::Typesetter; use crate::export::pdf::PdfExporter; - use crate::font::{FileSystemFontProvider}; + use toddle::query::FileSystemFontProvider; /// Create a _PDF_ with a name from the source code. fn test(name: &str, src: &str) { let mut typesetter = Typesetter::new(); - let provider = FileSystemFontProvider::from_listing("../fonts/fonts.toml").unwrap(); + let provider = FileSystemFontProvider::from_listing("fonts/fonts.toml").unwrap(); typesetter.add_font_provider(provider); // Typeset into document. diff --git a/src/style.rs b/src/style.rs index 042042bff..cd404dd29 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,6 +1,6 @@ //! Styles for layouting. -use crate::font::FontClass; +use toddle::query::FontClass; use crate::size::{Size, Size2D, SizeBox};