Redesign document representation 🧱

This commit is contained in:
Laurenz 2019-03-11 17:24:00 +01:00
parent 5942c3ba2a
commit 67281c4f46
5 changed files with 345 additions and 275 deletions

View File

@ -1,34 +1,60 @@
//! Generation of abstract documents from syntax trees. //! Abstract representation of a typesetted document.
#![allow(dead_code)] use std::ops;
use crate::font::Font;
use std::fmt;
use crate::parsing::{SyntaxTree, Node};
/// Abstract representation of a complete typesetted document.
///
/// This abstract thing can then be serialized into a specific format like _PDF_.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Document { pub struct Document {
/// The pages of the document.
pub pages: Vec<Page>, pub pages: Vec<Page>,
/// The font the document is written in. pub fonts: Vec<Font>,
pub font: String, }
#[derive(Debug, Clone, PartialEq)]
pub struct Style {
// Paper
pub paper_size: [Size; 2],
pub margins: [Size; 4],
// Font handling
pub font_families: Vec<String>,
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)] #[derive(Debug, Clone, PartialEq)]
pub struct Page { pub struct Page {
/// The width and height of the page. pub width: Size,
pub size: [Size; 2], pub height: Size,
/// The contents of the page. pub text: Vec<Text>,
pub contents: Vec<Text>,
} }
/// Plain text. #[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, Eq, PartialEq)] pub struct Text {
pub struct Text(pub String); pub commands: Vec<TextCommand>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TextCommand {
Text(String),
Move(Size, Size),
SetFont(usize, f32),
}
/// A general distance type that can convert between units. /// A general distance type that can convert between units.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
@ -40,170 +66,49 @@ pub struct Size {
impl Size { impl Size {
/// Create a size from a number of points. /// Create a size from a number of points.
#[inline] #[inline]
pub fn from_points(points: f32) -> Size { pub fn from_points(points: f32) -> Size { Size { points } }
Size { points }
}
/// Create a size from a number of inches. /// Create a size from a number of inches.
#[inline] #[inline]
pub fn from_inches(inches: f32) -> Size { pub fn from_inches(inches: f32) -> Size { Size { points: 72.0 * inches } }
Size { points: 72.0 * inches }
}
/// Create a size from a number of millimeters. /// Create a size from a number of millimeters.
#[inline] #[inline]
pub fn from_mm(mm: f32) -> Size { pub fn from_mm(mm: f32) -> Size { Size { points: 2.83465 * mm } }
Size { points: 2.83465 * mm }
}
/// Create a size from a number of centimeters. /// Create a size from a number of centimeters.
#[inline] #[inline]
pub fn from_cm(cm: f32) -> Size { pub fn from_cm(cm: f32) -> Size { Size { points: 28.3465 * cm } }
Size { points: 28.3465 * cm }
}
/// Create a size from a number of points. /// Create a size from a number of points.
#[inline] #[inline]
pub fn to_points(&self) -> f32 { pub fn to_points(&self) -> f32 { self.points }
self.points
}
/// Create a size from a number of inches. /// Create a size from a number of inches.
#[inline] #[inline]
pub fn to_inches(&self) -> f32 { pub fn to_inches(&self) -> f32 { self.points * 0.0138889 }
self.points * 0.0138889
}
/// Create a size from a number of millimeters. /// Create a size from a number of millimeters.
#[inline] #[inline]
pub fn to_mm(&self) -> f32 { pub fn to_mm(&self) -> f32 { self.points * 0.352778 }
self.points * 0.352778
}
/// Create a size from a number of centimeters. /// Create a size from a number of centimeters.
#[inline] #[inline]
pub fn to_cm(&self) -> f32 { pub fn to_cm(&self) -> f32 { self.points * 0.0352778 }
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. fn sub(self, other: Size) -> Size {
pub trait Generate { Size { points: self.points - other.points }
/// Generate a document from self.
fn generate(self) -> GenResult<Document>;
}
impl Generate for SyntaxTree<'_> {
fn generate(self) -> GenResult<Document> {
Generator::new(self).generate()
}
}
/// Result type used for parsing.
type GenResult<T> = std::result::Result<T, GenerationError>;
/// 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<Document> {
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<R, S: Into<String>>(&self, message: S) -> GenResult<R> {
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();
} }
} }

87
src/engine.rs Normal file
View File

@ -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<Document>;
}
impl Typeset for SyntaxTree<'_> {
fn typeset(self) -> TypeResult<Document> {
Engine::new(self).typeset()
}
}
/// Result type used for parsing.
type TypeResult<T> = std::result::Result<T, TypesetError>;
/// 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<Document> {
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],
})
}
}

View File

@ -6,18 +6,62 @@ use std::io::{self, Cursor, Seek, SeekFrom};
use std::collections::HashMap; use std::collections::HashMap;
use byteorder::{BE, ReadBytesExt, WriteBytesExt}; use byteorder::{BE, ReadBytesExt, WriteBytesExt};
use opentype::{OpenTypeReader, Outlines, TableRecord, Tag}; 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. /// An font wrapper which allows to subset a font.
#[derive(Debug, Clone, PartialEq)]
pub struct Font { pub struct Font {
program: Vec<u8>, pub name: String,
pub program: Vec<u8>,
pub mapping: HashMap<char, u16>,
pub widths: Vec<Size>,
pub default_glyph: u16,
} }
impl Font { impl Font {
/// Create a new font from a font program. /// Create a new font from a font program.
pub fn new(program: Vec<u8>) -> Font { pub fn new(program: Vec<u8>) -> Result<Font, opentype::Error> {
Font { program } let mut readable = Cursor::new(&program);
let mut reader = OpenTypeReader::new(&mut readable);
let head = reader.read_table::<Header>()?;
let name = reader.read_table::<Name>()?;
let os2 = reader.read_table::<OS2>()?;
let charmap = reader.read_table::<CharMap>()?;
let hmtx = reader.read_table::<HorizontalMetrics>()?;
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<u8> {
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 /// Generate a subsetted version of this font including only the chars listed in
@ -33,7 +77,7 @@ impl Font {
chars: C, chars: C,
needed_tables: I1, needed_tables: I1,
optional_tables: I2 optional_tables: I2
) -> Result<(Vec<u8>, HashMap<char, u16>), SubsettingError> ) -> Result<Font, SubsettingError>
where where
C: IntoIterator<Item=char>, C: IntoIterator<Item=char>,
I1: IntoIterator<Item=S1>, S1: AsRef<str>, I1: IntoIterator<Item=S1>, S1: AsRef<str>,
@ -48,7 +92,7 @@ impl Font {
tables.sort_by_key(|r| r.tag); tables.sort_by_key(|r| r.tag);
Subsetter { Subsetter {
program: &self.program, font: &self,
reader, reader,
outlines, outlines,
tables, tables,
@ -65,7 +109,7 @@ impl Font {
struct Subsetter<'p> { struct Subsetter<'p> {
// Original font // Original font
program: &'p [u8], font: &'p Font,
reader: OpenTypeReader<'p, Cursor<&'p Vec<u8>>>, reader: OpenTypeReader<'p, Cursor<&'p Vec<u8>>>,
outlines: Outlines, outlines: Outlines,
tables: Vec<TableRecord>, tables: Vec<TableRecord>,
@ -82,7 +126,7 @@ struct Subsetter<'p> {
impl<'p> Subsetter<'p> { impl<'p> Subsetter<'p> {
fn subset<I1, S1, I2, S2>(mut self, needed_tables: I1, optional_tables: I2) fn subset<I1, S1, I2, S2>(mut self, needed_tables: I1, optional_tables: I2)
-> SubsettingResult<(Vec<u8>, HashMap<char, u16>)> -> SubsettingResult<Font>
where where
I1: IntoIterator<Item=S1>, S1: AsRef<str>, I1: IntoIterator<Item=S1>, S1: AsRef<str>,
I2: IntoIterator<Item=S2>, S2: AsRef<str> I2: IntoIterator<Item=S2>, S2: AsRef<str>
@ -117,10 +161,21 @@ impl<'p> Subsetter<'p> {
self.write_header()?; 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::<SubsettingResult<Vec<_>>>()?;
let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as u16)) let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as u16))
.collect::<HashMap<char, u16>>(); .collect::<HashMap<char, u16>>();
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<()> { 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(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. // 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 // So now we have a look at the glyf table to check that and add glyphs
// we need additionally. // we need additionally.
@ -400,7 +457,7 @@ impl<'p> Subsetter<'p> {
Err(_) => return Err(SubsettingError::MissingTable(tag.to_string())), 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() .take_bytes()
} }

View File

@ -5,7 +5,7 @@
//! # Example //! # Example
//! This is an example of compiling a really simple document into _PDF_. //! 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. //! // Create an output file.
//! # /* //! # /*
@ -15,7 +15,7 @@
//! //!
//! // Parse the source and then generate the document. //! // Parse the source and then generate the document.
//! let src = "Hello World from Typeset‼"; //! 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. //! // Write the document into file as PDF.
//! file.write_pdf(&doc).unwrap(); //! file.write_pdf(&doc).unwrap();
@ -23,9 +23,10 @@
mod pdf; mod pdf;
mod utility; mod utility;
pub mod font;
pub mod parsing; pub mod parsing;
pub mod doc; pub mod doc;
pub mod engine;
pub mod font;
/// Writing of documents into supported formats. /// Writing of documents into supported formats.
pub mod write { pub mod write {

View File

@ -2,7 +2,7 @@
use std::fmt; use std::fmt;
use std::io::{self, Write, Cursor}; use std::io::{self, Write, Cursor};
use std::collections::{HashMap, HashSet}; use std::collections::HashSet;
use pdf::{PdfWriter, Id, Rect, Version, Trailer}; use pdf::{PdfWriter, Id, Rect, Version, Trailer};
use pdf::doc::{Catalog, PageTree, Page, Resource, Content}; use pdf::doc::{Catalog, PageTree, Page, Resource, Content};
use pdf::text::Text; use pdf::text::Text;
@ -10,8 +10,8 @@ use pdf::font::{
Type0Font, CMapEncoding, CIDFont, CIDFontType, CIDSystemInfo, Type0Font, CMapEncoding, CIDFont, CIDFontType, CIDSystemInfo,
WidthRecord, FontDescriptor, FontFlags, EmbeddedFont, GlyphUnit WidthRecord, FontDescriptor, FontFlags, EmbeddedFont, GlyphUnit
}; };
use opentype::{OpenTypeReader, tables::{self, NameEntry, MacStyleFlags}}; use opentype::{OpenTypeReader, tables::{self, MacStyleFlags}};
use crate::doc::Document; use crate::doc::{self, Document, TextCommand};
use crate::font::Font; use crate::font::Font;
@ -68,7 +68,7 @@ struct PdfCreator<'a, W: Write> {
writer: PdfWriter<'a, W>, writer: PdfWriter<'a, W>,
doc: &'a Document, doc: &'a Document,
offsets: Offsets, offsets: Offsets,
font: PdfFont, fonts: Vec<PdfFont>,
} }
/// Offsets for the various groups of ids. /// Offsets for the various groups of ids.
@ -87,33 +87,50 @@ impl<'a, W: Write> PdfCreator<'a, W> {
let catalog = 1; let catalog = 1;
let page_tree = catalog + 1; let page_tree = catalog + 1;
let pages = (page_tree + 1, page_tree + doc.pages.len() as Id); 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 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. // 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 page in &doc.pages {
for content in &page.contents { for text in &page.text {
chars.extend(content.0.chars()); 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. // Create a subsetted pdf font.
let data = std::fs::read(format!("../fonts/{}.ttf", doc.font))?; let fonts = doc.fonts.iter().enumerate().map(|(i, font)| {
let font = PdfFont::new(&doc.font, data, chars)?; PdfFont::new(font, &char_sets[i])
}).collect::<PdfResult<Vec<_>>>()?;
Ok(PdfCreator { Ok(PdfCreator {
writer: PdfWriter::new(target), writer: PdfWriter::new(target),
doc, doc,
offsets: Offsets { offsets,
catalog, fonts,
page_tree,
pages,
contents,
fonts,
},
font,
}) })
} }
@ -154,8 +171,8 @@ impl<'a, W: Write> PdfCreator<'a, W> {
// The page objects // The page objects
let mut id = self.offsets.pages.0; let mut id = self.offsets.pages.0;
for page in &self.doc.pages { for page in &self.doc.pages {
let width = page.size[0].to_points(); let width = page.width.to_points();
let height = page.size[1].to_points(); let height = page.height.to_points();
self.writer.write_obj(id, Page::new(self.offsets.page_tree) self.writer.write_obj(id, Page::new(self.offsets.page_tree)
.media_box(Rect::new(0.0, 0.0, width, height)) .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<()> { fn write_contents(&mut self) -> PdfResult<()> {
let mut id = self.offsets.contents.0; let mut id = self.offsets.contents.0;
for page in &self.doc.pages { for page in &self.doc.pages {
for content in &page.contents { for text in &page.text {
self.writer.write_obj(id, &Text::new() self.write_text(id, text)?;
.set_font(1, 13.0)
.move_line(108.0, 734.0)
.write_text(&self.encode(&content.0))
.to_stream()
)?;
id += 1; 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::<Vec<_>>();
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(()) Ok(())
} }
/// Write the fonts. /// Write the fonts.
fn write_fonts(&mut self) -> PdfResult<()> { 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( for font in &self.fonts {
self.font.name.clone(), self.writer.write_obj(id, &Type0Font::new(
CMapEncoding::Predefined("Identity-H".to_owned()), font.name.clone(),
id + 1 CMapEncoding::Predefined("Identity-H".to_owned()),
)).unwrap(); id + 1
))?;
self.writer.write_obj(id + 1, self.writer.write_obj(id + 1,
CIDFont::new( CIDFont::new(
CIDFontType::Type2, CIDFontType::Type2,
self.font.name.clone(), font.name.clone(),
CIDSystemInfo::new("(Adobe)", "(Identity)", 0), CIDSystemInfo::new("(Adobe)", "(Identity)", 0),
id + 2, id + 2,
).widths(vec![WidthRecord::start(0, self.font.widths.clone())]) ).widths(vec![WidthRecord::start(0, font.widths.clone())])
).unwrap(); )?;
self.writer.write_obj(id + 2, self.writer.write_obj(id + 2,
FontDescriptor::new( FontDescriptor::new(
self.font.name.clone(), font.name.clone(),
self.font.flags, font.flags,
self.font.italic_angle, font.italic_angle,
) )
.font_bbox(self.font.bounding_box) .font_bbox(font.bounding_box)
.ascent(self.font.ascender) .ascent(font.ascender)
.descent(self.font.descender) .descent(font.descender)
.cap_height(self.font.cap_height) .cap_height(font.cap_height)
.stem_v(self.font.stem_v) .stem_v(font.stem_v)
.font_file_3(id + 3) .font_file_3(id + 3)
).unwrap(); )?;
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(()) Ok(())
} }
/// Encode the given text for our font.
fn encode(&self, text: &str) -> Vec<u8> {
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. /// The data we need from the font.
struct PdfFont { struct PdfFont {
data: Vec<u8>, font: Font,
mapping: HashMap<char, u16>,
default_glyph: u16,
name: String,
widths: Vec<GlyphUnit>, widths: Vec<GlyphUnit>,
flags: FontFlags, flags: FontFlags,
italic_angle: f32, italic_angle: f32,
@ -256,47 +288,36 @@ struct PdfFont {
impl PdfFont { impl PdfFont {
/// Create a subetted version of the font and calculate some information /// Create a subetted version of the font and calculate some information
/// needed for creating the _PDF_. /// needed for creating the _PDF_.
pub fn new(font_name: &str, data: Vec<u8>, chars: HashSet<char>) -> PdfResult<PdfFont> { pub fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> {
let mut readable = Cursor::new(&data); let mut readable = Cursor::new(&font.program);
let mut reader = OpenTypeReader::new(&mut readable); let mut reader = OpenTypeReader::new(&mut readable);
let head = reader.read_table::<tables::Header>()?; let head = reader.read_table::<tables::Header>()?;
let name = reader.read_table::<tables::Name>()?;
let post = reader.read_table::<tables::Post>()?; let post = reader.read_table::<tables::Post>()?;
let os2 = reader.read_table::<tables::OS2>()?; let os2 = reader.read_table::<tables::OS2>()?;
let font = Font::new(data); let subsetted = font.subsetted(
let (subsetted, mapping) = font.subsetted( chars.iter().cloned(),
chars,
&["head", "hhea", "maxp", "hmtx", "loca", "glyf"], &["head", "hhea", "maxp", "hmtx", "loca", "glyf"],
&["cvt ", "prep", "fpgm", "OS/2", "cmap", "name", "post"], &["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(); let mut flags = FontFlags::empty();
flags.set(FontFlags::FIXED_PITCH, post.is_fixed_pitch); 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.insert(FontFlags::SYMBOLIC);
flags.set(FontFlags::ITALIC, head.mac_style.contains(MacStyleFlags::ITALIC)); flags.set(FontFlags::ITALIC, head.mac_style.contains(MacStyleFlags::ITALIC));
flags.insert(FontFlags::SMALL_CAP); flags.insert(FontFlags::SMALL_CAP);
let mut readable = Cursor::new(&subsetted); let widths = subsetted.widths.iter()
let mut reader = OpenTypeReader::new(&mut readable); .map(|w| (1000.0 * w.to_points()).round() as GlyphUnit)
let hmtx = reader.read_table::<tables::HorizontalMetrics>()?; .collect();
let widths = hmtx.metrics.iter().map(|m| convert(m.advance_width)).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 { Ok(PdfFont {
data: subsetted, font: subsetted,
mapping,
default_glyph: os2.us_default_char.unwrap_or(0),
name: font_name,
widths, widths,
flags, flags,
italic_angle: post.italic_angle.to_f32(), 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, 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. impl std::ops::Deref for PdfFont {
fn map(&self, c: char) -> u16 { type Target = Font;
self.mapping.get(&c).map(|&g| g).unwrap_or(self.default_glyph)
fn deref(&self) -> &Font {
&self.font
} }
} }
@ -324,20 +348,21 @@ impl PdfFont {
mod pdf_tests { mod pdf_tests {
use super::*; use super::*;
use crate::parsing::ParseTree; use crate::parsing::ParseTree;
use crate::doc::Generate; use crate::engine::Typeset;
/// Create a pdf with a name from the source code. /// Create a pdf with a name from the source code.
fn test(name: &str, src: &str) { 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 path = format!("../target/typeset-pdf-{}.pdf", name);
let mut file = std::fs::File::create(path).unwrap(); let mut file = std::fs::File::create(path).unwrap();
file.write_pdf(&doc).unwrap(); file.write_pdf(&doc).unwrap();
} }
#[test] #[test]
fn pdf_simple() { fn pdf() {
test("unicode", "∑mbe∂∂ed font with Unicode!"); test("unicode", "∑mbe∂∂ed font with Unicode!");
test("parentheses", "Text with ) and ( or (enclosed) works."); test("parentheses", "Text with ) and ( or (enclosed) works.");
test("composite-glyph", "Composite character‼");
test("multiline"," test("multiline","
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed
diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, 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. Stet clita kasd gubergren, no sea takimata sanctus est.
"); ");
} }
#[test]
fn pdf_composite_glyph() {
test("composite-glyph", "Composite character‼");
}
} }