From 67281c4f469716c7f2341676f2ad656d8c544ea3 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Mon, 11 Mar 2019 17:24:00 +0100 Subject: [PATCH] =?UTF-8?q?Redesign=20document=20representation=20?= =?UTF-8?q?=F0=9F=A7=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/doc.rs | 223 ++++++++++++++----------------------------------- src/engine.rs | 87 +++++++++++++++++++ src/font.rs | 77 ++++++++++++++--- src/lib.rs | 7 +- src/pdf.rs | 226 +++++++++++++++++++++++++++----------------------- 5 files changed, 345 insertions(+), 275 deletions(-) create mode 100644 src/engine.rs diff --git a/src/doc.rs b/src/doc.rs index d0733dfa6..890da26a2 100644 --- a/src/doc.rs +++ b/src/doc.rs @@ -1,34 +1,60 @@ -//! Generation of abstract documents from syntax trees. +//! Abstract representation of a typesetted document. -#![allow(dead_code)] - -use std::fmt; -use crate::parsing::{SyntaxTree, Node}; +use std::ops; +use crate::font::Font; -/// Abstract representation of a complete typesetted document. -/// -/// This abstract thing can then be serialized into a specific format like _PDF_. #[derive(Debug, Clone, PartialEq)] pub struct Document { - /// The pages of the document. pub pages: Vec, - /// The font the document is written in. - pub font: String, + pub fonts: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Style { + // Paper + pub paper_size: [Size; 2], + pub margins: [Size; 4], + + // Font handling + pub font_families: Vec, + pub font_size: f32, +} + +impl Default for Style { + fn default() -> Style { + Style { + // A4 paper with 1.5 cm margins in all directions + paper_size: [Size::from_mm(210.0), Size::from_mm(297.0)], + margins: [Size::from_cm(2.5); 4], + + // Default font family + font_families: (&[ + "NotoSans", "NotoSansMath" + ]).iter().map(ToString::to_string).collect(), + font_size: 12.0, + } + } } -/// A page of a document. #[derive(Debug, Clone, PartialEq)] pub struct Page { - /// The width and height of the page. - pub size: [Size; 2], - /// The contents of the page. - pub contents: Vec, + pub width: Size, + pub height: Size, + pub text: Vec, } -/// Plain text. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct Text(pub String); +#[derive(Debug, Clone, PartialEq)] +pub struct Text { + pub commands: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TextCommand { + Text(String), + Move(Size, Size), + SetFont(usize, f32), +} /// A general distance type that can convert between units. #[derive(Debug, Copy, Clone, PartialEq)] @@ -40,170 +66,49 @@ pub struct Size { impl Size { /// Create a size from a number of points. #[inline] - pub fn from_points(points: f32) -> Size { - Size { points } - } + pub fn from_points(points: f32) -> Size { Size { points } } /// Create a size from a number of inches. #[inline] - pub fn from_inches(inches: f32) -> Size { - Size { points: 72.0 * inches } - } + pub fn from_inches(inches: f32) -> Size { Size { points: 72.0 * inches } } /// Create a size from a number of millimeters. #[inline] - pub fn from_mm(mm: f32) -> Size { - Size { points: 2.83465 * mm } - } + pub fn from_mm(mm: f32) -> Size { Size { points: 2.83465 * mm } } /// Create a size from a number of centimeters. #[inline] - pub fn from_cm(cm: f32) -> Size { - Size { points: 28.3465 * cm } - } + pub fn from_cm(cm: f32) -> Size { Size { points: 28.3465 * cm } } /// Create a size from a number of points. #[inline] - pub fn to_points(&self) -> f32 { - self.points - } + pub fn to_points(&self) -> f32 { self.points } /// Create a size from a number of inches. #[inline] - pub fn to_inches(&self) -> f32 { - self.points * 0.0138889 - } + pub fn to_inches(&self) -> f32 { self.points * 0.0138889 } /// Create a size from a number of millimeters. #[inline] - pub fn to_mm(&self) -> f32 { - self.points * 0.352778 - } + pub fn to_mm(&self) -> f32 { self.points * 0.352778 } /// Create a size from a number of centimeters. #[inline] - pub fn to_cm(&self) -> f32 { - self.points * 0.0352778 + pub fn to_cm(&self) -> f32 { self.points * 0.0352778 } +} + +impl ops::Add for Size { + type Output = Size; + + fn add(self, other: Size) -> Size { + Size { points: self.points + other.points } } } +impl ops::Sub for Size { + type Output = Size; -/// A type that can be generated into a document. -pub trait Generate { - /// Generate a document from self. - fn generate(self) -> GenResult; -} - -impl Generate for SyntaxTree<'_> { - fn generate(self) -> GenResult { - Generator::new(self).generate() - } -} - -/// Result type used for parsing. -type GenResult = std::result::Result; - -/// A failure when generating. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct GenerationError { - /// A message describing the error. - message: String, -} - -impl fmt::Display for GenerationError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "generation error: {}", self.message) - } -} - - -/// Transforms an abstract syntax tree into a document. -#[derive(Debug, Clone)] -struct Generator<'s> { - tree: SyntaxTree<'s>, -} - -impl<'s> Generator<'s> { - /// Create a new generator from a syntax tree. - fn new(tree: SyntaxTree<'s>) -> Generator<'s> { - Generator { tree } - } - - /// Generate the abstract document. - fn generate(&mut self) -> GenResult { - let mut text = String::new(); - for node in &self.tree.nodes { - match node { - Node::Space if !text.is_empty() => text.push(' '), - Node::Space | Node::Newline => (), - Node::Word(word) => text.push_str(word), - - Node::ToggleItalics | Node::ToggleBold | Node::ToggleMath => unimplemented!(), - Node::Func(_) => unimplemented!(), - - } - } - - let page = Page { - size: [Size::from_mm(210.0), Size::from_mm(297.0)], - contents: vec![ Text(text) ], - }; - - Ok(Document { - pages: vec![page], - font: "NotoSans-Regular".to_owned(), - }) - } - - /// Gives a generation error with a message. - #[inline] - fn err>(&self, message: S) -> GenResult { - Err(GenerationError { message: message.into() }) - } -} - - -#[cfg(test)] -mod generator_tests { - use super::*; - use crate::parsing::ParseTree; - - /// Test if the source gets generated into the document. - fn test(src: &str, doc: Document) { - assert_eq!(src.parse_tree().unwrap().generate(), Ok(doc)); - } - - /// Test if generation gives this error for the source code. - fn test_err(src: &str, err: GenerationError) { - assert_eq!(src.parse_tree().unwrap().generate(), Err(err)); - } - - #[test] - fn generator_simple() { - test("This is an example of a sentence.", Document { - pages: vec![ - Page { - size: [Size::from_mm(210.0), Size::from_mm(297.0)], - contents: vec![ - Text("This is an example of a sentence.".to_owned()), - ] - } - ], - font: "NotoSans-Regular".to_owned(), - }); - } - - #[test] - fn generate_emoji() { - use crate::write::WritePdf; - let doc = Document { - pages: vec![Page { - size: [Size::from_mm(210.0), Size::from_mm(297.0)], - contents: vec![Text("🌍".to_owned())] - }], - font: "NotoEmoji-Regular".to_owned(), - }; - let mut file = std::fs::File::create("../target/typeset-doc-emoji.pdf").unwrap(); - file.write_pdf(&doc).unwrap(); + fn sub(self, other: Size) -> Size { + Size { points: self.points - other.points } } } diff --git a/src/engine.rs b/src/engine.rs new file mode 100644 index 000000000..f7d820e88 --- /dev/null +++ b/src/engine.rs @@ -0,0 +1,87 @@ +//! Core typesetting engine. + +use std::fmt; +use crate::parsing::{SyntaxTree, Node}; +use crate::doc::{Document, Style, Page, Text, TextCommand}; +use crate::font::Font; + + +/// A type that can be typesetted into a document. +pub trait Typeset { + /// Generate a document from self. + fn typeset(self) -> TypeResult; +} + +impl Typeset for SyntaxTree<'_> { + fn typeset(self) -> TypeResult { + Engine::new(self).typeset() + } +} + +/// Result type used for parsing. +type TypeResult = std::result::Result; + +/// Errors occuring in the process of typesetting. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct TypesetError { + message: String, +} + +impl fmt::Display for TypesetError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.message) + } +} + + +/// Transforms an abstract syntax tree into a document. +#[derive(Debug, Clone)] +struct Engine<'s> { + tree: SyntaxTree<'s>, +} + +impl<'s> Engine<'s> { + /// Create a new generator from a syntax tree. + fn new(tree: SyntaxTree<'s>) -> Engine<'s> { + Engine { tree } + } + + /// Generate the abstract document. + fn typeset(&mut self) -> TypeResult { + let style = Style::default(); + + // Load font defined by style + let font_family = style.font_families.first().unwrap(); + let program = std::fs::read(format!("../fonts/{}-Regular.ttf", font_family)).unwrap(); + let font = Font::new(program).unwrap(); + + let mut text = String::new(); + for node in &self.tree.nodes { + match node { + Node::Space if !text.is_empty() => text.push(' '), + Node::Space | Node::Newline => (), + Node::Word(word) => text.push_str(word), + + Node::ToggleItalics | Node::ToggleBold | Node::ToggleMath => unimplemented!(), + Node::Func(_) => unimplemented!(), + } + } + + let page = Page { + width: style.paper_size[0], + height: style.paper_size[1], + text: vec![Text { + commands: vec![ + TextCommand::Move(style.margins[0], style.paper_size[1] - style.margins[1]), + TextCommand::SetFont(0, style.font_size), + TextCommand::Text(text) + ] + }], + }; + + Ok(Document { + pages: vec![page], + fonts: vec![font], + }) + } +} diff --git a/src/font.rs b/src/font.rs index 460822864..1e22e47db 100644 --- a/src/font.rs +++ b/src/font.rs @@ -6,18 +6,62 @@ use std::io::{self, Cursor, Seek, SeekFrom}; use std::collections::HashMap; use byteorder::{BE, ReadBytesExt, WriteBytesExt}; use opentype::{OpenTypeReader, Outlines, TableRecord, Tag}; -use opentype::tables::{Header, CharMap, MaximumProfile, HorizontalMetrics}; +use opentype::tables::{Header, Name, NameEntry, CharMap, MaximumProfile, HorizontalMetrics, OS2}; +use crate::doc::Size; /// An font wrapper which allows to subset a font. +#[derive(Debug, Clone, PartialEq)] pub struct Font { - program: Vec, + pub name: String, + pub program: Vec, + pub mapping: HashMap, + pub widths: Vec, + pub default_glyph: u16, } impl Font { /// Create a new font from a font program. - pub fn new(program: Vec) -> Font { - Font { program } + pub fn new(program: Vec) -> Result { + let mut readable = Cursor::new(&program); + let mut reader = OpenTypeReader::new(&mut readable); + + let head = reader.read_table::
()?; + let name = reader.read_table::()?; + let os2 = reader.read_table::()?; + let charmap = reader.read_table::()?; + let hmtx = reader.read_table::()?; + + let unit_ratio = 1.0 / (head.units_per_em as f32); + let convert = |x| Size::from_points(unit_ratio * x as f32); + + let base_font = name.get_decoded(NameEntry::PostScriptName); + let font_name = base_font.unwrap_or_else(|| "unknown".to_owned()); + let widths = hmtx.metrics.iter().map(|m| convert(m.advance_width)).collect(); + + Ok(Font { + name: font_name, + program, + mapping: charmap.mapping, + widths, + default_glyph: os2.us_default_char.unwrap_or(0), + }) + } + + /// Map a character to it's glyph index. + pub fn map(&self, c: char) -> u16 { + self.mapping.get(&c).map(|&g| g).unwrap_or(self.default_glyph) + } + + /// Encode the given text for our font (into glyph ids). + pub fn encode(&self, text: &str) -> Vec { + println!("encoding {} with {:?}", text, self.mapping); + let mut bytes = Vec::with_capacity(2 * text.len()); + for glyph in text.chars().map(|c| self.map(c)) { + bytes.push((glyph >> 8) as u8); + bytes.push((glyph & 0xff) as u8); + } + bytes } /// Generate a subsetted version of this font including only the chars listed in @@ -33,7 +77,7 @@ impl Font { chars: C, needed_tables: I1, optional_tables: I2 - ) -> Result<(Vec, HashMap), SubsettingError> + ) -> Result where C: IntoIterator, I1: IntoIterator, S1: AsRef, @@ -48,7 +92,7 @@ impl Font { tables.sort_by_key(|r| r.tag); Subsetter { - program: &self.program, + font: &self, reader, outlines, tables, @@ -65,7 +109,7 @@ impl Font { struct Subsetter<'p> { // Original font - program: &'p [u8], + font: &'p Font, reader: OpenTypeReader<'p, Cursor<&'p Vec>>, outlines: Outlines, tables: Vec, @@ -82,7 +126,7 @@ struct Subsetter<'p> { impl<'p> Subsetter<'p> { fn subset(mut self, needed_tables: I1, optional_tables: I2) - -> SubsettingResult<(Vec, HashMap)> + -> SubsettingResult where I1: IntoIterator, S1: AsRef, I2: IntoIterator, S2: AsRef @@ -117,10 +161,21 @@ impl<'p> Subsetter<'p> { self.write_header()?; + let widths = self.glyphs.iter() + .map(|&glyph| self.font.widths.get(glyph as usize).map(|&w| w) + .take_invalid("missing glyph metrics")) + .collect::>>()?; + let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as u16)) .collect::>(); - Ok((self.body, mapping)) + Ok(Font { + name: self.font.name.clone(), + program: self.body, + mapping, + widths, + default_glyph: self.font.default_glyph, + }) } fn build_glyphs(&mut self) -> SubsettingResult<()> { @@ -131,6 +186,8 @@ impl<'p> Subsetter<'p> { self.glyphs.push(cmap.get(c).ok_or_else(|| SubsettingError::MissingCharacter(c))?) } + self.glyphs.push(self.font.default_glyph); + // Composite glyphs may need additional glyphs we have not yet in our list. // So now we have a look at the glyf table to check that and add glyphs // we need additionally. @@ -400,7 +457,7 @@ impl<'p> Subsetter<'p> { Err(_) => return Err(SubsettingError::MissingTable(tag.to_string())), }; - self.program.get(record.offset as usize .. (record.offset + record.length) as usize) + self.font.program.get(record.offset as usize .. (record.offset + record.length) as usize) .take_bytes() } diff --git a/src/lib.rs b/src/lib.rs index 09740fb11..f2c9b5ebe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! # Example //! This is an example of compiling a really simple document into _PDF_. //! ``` -//! use typeset::{parsing::ParseTree, doc::Generate, write::WritePdf}; +//! use typeset::{parsing::ParseTree, engine::Typeset, write::WritePdf}; //! //! // Create an output file. //! # /* @@ -15,7 +15,7 @@ //! //! // Parse the source and then generate the document. //! let src = "Hello World from Typeset‼"; -//! let doc = src.parse_tree().unwrap().generate().unwrap(); +//! let doc = src.parse_tree().unwrap().typeset().unwrap(); //! //! // Write the document into file as PDF. //! file.write_pdf(&doc).unwrap(); @@ -23,9 +23,10 @@ mod pdf; mod utility; -pub mod font; pub mod parsing; pub mod doc; +pub mod engine; +pub mod font; /// Writing of documents into supported formats. pub mod write { diff --git a/src/pdf.rs b/src/pdf.rs index 0d7ad298f..87d324226 100644 --- a/src/pdf.rs +++ b/src/pdf.rs @@ -2,7 +2,7 @@ use std::fmt; use std::io::{self, Write, Cursor}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use pdf::{PdfWriter, Id, Rect, Version, Trailer}; use pdf::doc::{Catalog, PageTree, Page, Resource, Content}; use pdf::text::Text; @@ -10,8 +10,8 @@ use pdf::font::{ Type0Font, CMapEncoding, CIDFont, CIDFontType, CIDSystemInfo, WidthRecord, FontDescriptor, FontFlags, EmbeddedFont, GlyphUnit }; -use opentype::{OpenTypeReader, tables::{self, NameEntry, MacStyleFlags}}; -use crate::doc::Document; +use opentype::{OpenTypeReader, tables::{self, MacStyleFlags}}; +use crate::doc::{self, Document, TextCommand}; use crate::font::Font; @@ -68,7 +68,7 @@ struct PdfCreator<'a, W: Write> { writer: PdfWriter<'a, W>, doc: &'a Document, offsets: Offsets, - font: PdfFont, + fonts: Vec, } /// Offsets for the various groups of ids. @@ -87,33 +87,50 @@ impl<'a, W: Write> PdfCreator<'a, W> { let catalog = 1; let page_tree = catalog + 1; let pages = (page_tree + 1, page_tree + doc.pages.len() as Id); - let content_count = doc.pages.iter().flat_map(|p| p.contents.iter()).count() as Id; + let content_count = doc.pages.iter().flat_map(|p| p.text.iter()).count() as Id; let contents = (pages.1 + 1, pages.1 + content_count); - let fonts = (contents.1 + 1, contents.1 + 4); + let fonts = (contents.1 + 1, contents.1 + 4 * doc.fonts.len() as Id); + + let offsets = Offsets { + catalog, + page_tree, + pages, + contents, + fonts, + }; + + assert!(doc.fonts.len() > 0); // Find out which chars are used in this document. - let mut chars = HashSet::new(); + let mut char_sets = vec![HashSet::new(); doc.fonts.len()]; + let mut current_font: usize = 0; for page in &doc.pages { - for content in &page.contents { - chars.extend(content.0.chars()); + for text in &page.text { + for command in &text.commands { + match command { + TextCommand::Text(string) => { + char_sets[current_font].extend(string.chars()); + }, + TextCommand::SetFont(id, _) => { + assert!(*id < doc.fonts.len()); + current_font = *id; + }, + _ => {}, + } + } } } // Create a subsetted pdf font. - let data = std::fs::read(format!("../fonts/{}.ttf", doc.font))?; - let font = PdfFont::new(&doc.font, data, chars)?; + let fonts = doc.fonts.iter().enumerate().map(|(i, font)| { + PdfFont::new(font, &char_sets[i]) + }).collect::>>()?; Ok(PdfCreator { writer: PdfWriter::new(target), doc, - offsets: Offsets { - catalog, - page_tree, - pages, - contents, - fonts, - }, - font, + offsets, + fonts, }) } @@ -154,8 +171,8 @@ impl<'a, W: Write> PdfCreator<'a, W> { // The page objects let mut id = self.offsets.pages.0; for page in &self.doc.pages { - let width = page.size[0].to_points(); - let height = page.size[1].to_points(); + let width = page.width.to_points(); + let height = page.height.to_points(); self.writer.write_obj(id, Page::new(self.offsets.page_tree) .media_box(Rect::new(0.0, 0.0, width, height)) @@ -172,77 +189,92 @@ impl<'a, W: Write> PdfCreator<'a, W> { fn write_contents(&mut self) -> PdfResult<()> { let mut id = self.offsets.contents.0; for page in &self.doc.pages { - for content in &page.contents { - self.writer.write_obj(id, &Text::new() - .set_font(1, 13.0) - .move_line(108.0, 734.0) - .write_text(&self.encode(&content.0)) - .to_stream() - )?; + for text in &page.text { + self.write_text(id, text)?; id += 1; } } + Ok(()) + } + + fn write_text(&mut self, id: u32, text: &doc::Text) -> PdfResult<()> { + let mut current_font = 0; + let encoded = text.commands.iter().filter_map(|cmd| match cmd { + TextCommand::Text(string) => Some(self.fonts[current_font].encode(&string)), + TextCommand::SetFont(id, _) => { current_font = *id; None }, + _ => None, + }).collect::>(); + + let mut object = Text::new(); + let mut nr = 0; + + for command in &text.commands { + match command { + TextCommand::Text(_) => { + object.write_text(&encoded[nr]); + nr += 1; + }, + TextCommand::SetFont(id, size) => { + object.set_font(*id as u32 + 1, *size); + }, + TextCommand::Move(x, y) => { + object.move_line(x.to_points(), y.to_points()); + } + } + } + + self.writer.write_obj(id, &object.to_stream())?; Ok(()) } /// Write the fonts. fn write_fonts(&mut self) -> PdfResult<()> { - let id = self.offsets.fonts.0; + let mut id = self.offsets.fonts.0; - self.writer.write_obj(id, &Type0Font::new( - self.font.name.clone(), - CMapEncoding::Predefined("Identity-H".to_owned()), - id + 1 - )).unwrap(); + for font in &self.fonts { + self.writer.write_obj(id, &Type0Font::new( + font.name.clone(), + CMapEncoding::Predefined("Identity-H".to_owned()), + id + 1 + ))?; - self.writer.write_obj(id + 1, - CIDFont::new( - CIDFontType::Type2, - self.font.name.clone(), - CIDSystemInfo::new("(Adobe)", "(Identity)", 0), - id + 2, - ).widths(vec![WidthRecord::start(0, self.font.widths.clone())]) - ).unwrap(); + self.writer.write_obj(id + 1, + CIDFont::new( + CIDFontType::Type2, + font.name.clone(), + CIDSystemInfo::new("(Adobe)", "(Identity)", 0), + id + 2, + ).widths(vec![WidthRecord::start(0, font.widths.clone())]) + )?; - self.writer.write_obj(id + 2, - FontDescriptor::new( - self.font.name.clone(), - self.font.flags, - self.font.italic_angle, - ) - .font_bbox(self.font.bounding_box) - .ascent(self.font.ascender) - .descent(self.font.descender) - .cap_height(self.font.cap_height) - .stem_v(self.font.stem_v) - .font_file_3(id + 3) - ).unwrap(); + self.writer.write_obj(id + 2, + FontDescriptor::new( + font.name.clone(), + 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_3(id + 3) + )?; + self.writer.write_obj(id + 3, &EmbeddedFont::OpenType(&font.program))?; - self.writer.write_obj(id + 3, &EmbeddedFont::OpenType(&self.font.data)).unwrap(); + id += 4; + } Ok(()) } - - /// Encode the given text for our font. - fn encode(&self, text: &str) -> Vec { - let mut bytes = Vec::with_capacity(2 * text.len()); - for glyph in text.chars().map(|c| self.font.map(c)) { - bytes.push((glyph >> 8) as u8); - bytes.push((glyph & 0xff) as u8); - } - bytes - } } /// The data we need from the font. struct PdfFont { - data: Vec, - mapping: HashMap, - default_glyph: u16, - name: String, + font: Font, widths: Vec, flags: FontFlags, italic_angle: f32, @@ -256,47 +288,36 @@ struct PdfFont { impl PdfFont { /// Create a subetted version of the font and calculate some information /// needed for creating the _PDF_. - pub fn new(font_name: &str, data: Vec, chars: HashSet) -> PdfResult { - let mut readable = Cursor::new(&data); + pub fn new(font: &Font, chars: &HashSet) -> PdfResult { + let mut readable = Cursor::new(&font.program); let mut reader = OpenTypeReader::new(&mut readable); let head = reader.read_table::()?; - let name = reader.read_table::()?; let post = reader.read_table::()?; let os2 = reader.read_table::()?; - let font = Font::new(data); - let (subsetted, mapping) = font.subsetted( - chars, + let subsetted = font.subsetted( + chars.iter().cloned(), &["head", "hhea", "maxp", "hmtx", "loca", "glyf"], &["cvt ", "prep", "fpgm", "OS/2", "cmap", "name", "post"], )?; - let unit_ratio = 1000.0 / (head.units_per_em as f32); - let convert = |x| (unit_ratio * x as f32).round() as GlyphUnit; - - let base_font = name.get_decoded(NameEntry::PostScriptName); - let font_name = base_font.unwrap_or_else(|| font_name.to_owned()); - - let mut flags = FontFlags::empty(); flags.set(FontFlags::FIXED_PITCH, post.is_fixed_pitch); - flags.set(FontFlags::SERIF, font_name.contains("Serif")); + flags.set(FontFlags::SERIF, font.name.contains("Serif")); flags.insert(FontFlags::SYMBOLIC); flags.set(FontFlags::ITALIC, head.mac_style.contains(MacStyleFlags::ITALIC)); flags.insert(FontFlags::SMALL_CAP); - let mut readable = Cursor::new(&subsetted); - let mut reader = OpenTypeReader::new(&mut readable); - let hmtx = reader.read_table::()?; - let widths = hmtx.metrics.iter().map(|m| convert(m.advance_width)).collect(); + let widths = subsetted.widths.iter() + .map(|w| (1000.0 * w.to_points()).round() as GlyphUnit) + .collect(); + let unit_ratio = 1.0 / (head.units_per_em as f32); + let convert = |x| (unit_ratio * x as f32).round() as GlyphUnit; Ok(PdfFont { - data: subsetted, - mapping, - default_glyph: os2.us_default_char.unwrap_or(0), - name: font_name, + font: subsetted, widths, flags, italic_angle: post.italic_angle.to_f32(), @@ -312,10 +333,13 @@ impl PdfFont { stem_v: (10.0 + 220.0 * (os2.us_weight_class as f32 - 50.0) / 900.0) as GlyphUnit, }) } +} - /// Map a character to it's glyph index. - fn map(&self, c: char) -> u16 { - self.mapping.get(&c).map(|&g| g).unwrap_or(self.default_glyph) +impl std::ops::Deref for PdfFont { + type Target = Font; + + fn deref(&self) -> &Font { + &self.font } } @@ -324,20 +348,21 @@ impl PdfFont { mod pdf_tests { use super::*; use crate::parsing::ParseTree; - use crate::doc::Generate; + use crate::engine::Typeset; /// Create a pdf with a name from the source code. fn test(name: &str, src: &str) { - let doc = src.parse_tree().unwrap().generate().unwrap(); + let doc = src.parse_tree().unwrap().typeset().unwrap(); let path = format!("../target/typeset-pdf-{}.pdf", name); let mut file = std::fs::File::create(path).unwrap(); file.write_pdf(&doc).unwrap(); } #[test] - fn pdf_simple() { + fn pdf() { test("unicode", "∑mbe∂∂ed font with Unicode!"); test("parentheses", "Text with ) and ( or (enclosed) works."); + test("composite-glyph", "Composite character‼"); test("multiline"," Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed @@ -345,9 +370,4 @@ mod pdf_tests { Stet clita kasd gubergren, no sea takimata sanctus est. "); } - - #[test] - fn pdf_composite_glyph() { - test("composite-glyph", "Composite character‼"); - } }