summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/doc.rs217
-rw-r--r--src/engine.rs87
-rw-r--r--src/font.rs77
-rw-r--r--src/lib.rs7
-rw-r--r--src/pdf.rs234
5 files changed, 346 insertions, 276 deletions
diff --git a/src/doc.rs b/src/doc.rs
index d0733dfa..890da26a 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::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)]
pub struct Document {
- /// The pages of the document.
pub pages: Vec<Page>,
- /// The font the document is written in.
- pub font: String,
+ pub fonts: Vec<Font>,
+}
+
+#[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)]
pub struct Page {
- /// The width and height of the page.
- pub size: [Size; 2],
- /// The contents of the page.
- pub contents: Vec<Text>,
+ pub width: Size,
+ pub height: Size,
+ pub text: Vec<Text>,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Text {
+ pub commands: Vec<TextCommand>,
}
-/// Plain text.
-#[derive(Debug, Clone, Eq, PartialEq)]
-pub struct Text(pub String);
+#[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
- }
-}
-
-
-/// A type that can be generated into a document.
-pub trait Generate {
- /// 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)
- }
+ pub fn to_cm(&self) -> f32 { self.points * 0.0352778 }
}
+impl ops::Add for Size {
+ type Output = Size;
-/// 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() })
+ fn add(self, other: Size) -> Size {
+ Size { points: self.points + other.points }
}
}
+impl ops::Sub for Size {
+ type Output = Size;
-#[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 00000000..f7d820e8
--- /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<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],
+ })
+ }
+}
diff --git a/src/font.rs b/src/font.rs
index 46082286..1e22e47d 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<u8>,
+ pub name: String,
+ pub program: Vec<u8>,
+ pub mapping: HashMap<char, u16>,
+ pub widths: Vec<Size>,
+ pub default_glyph: u16,
}
impl Font {
/// Create a new font from a font program.
- pub fn new(program: Vec<u8>) -> Font {
- Font { program }
+ pub fn new(program: Vec<u8>) -> Result<Font, opentype::Error> {
+ 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
@@ -33,7 +77,7 @@ impl Font {
chars: C,
needed_tables: I1,
optional_tables: I2
- ) -> Result<(Vec<u8>, HashMap<char, u16>), SubsettingError>
+ ) -> Result<Font, SubsettingError>
where
C: IntoIterator<Item=char>,
I1: IntoIterator<Item=S1>, S1: AsRef<str>,
@@ -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<u8>>>,
outlines: Outlines,
tables: Vec<TableRecord>,
@@ -82,7 +126,7 @@ struct Subsetter<'p> {
impl<'p> Subsetter<'p> {
fn subset<I1, S1, I2, S2>(mut self, needed_tables: I1, optional_tables: I2)
- -> SubsettingResult<(Vec<u8>, HashMap<char, u16>)>
+ -> SubsettingResult<Font>
where
I1: IntoIterator<Item=S1>, S1: AsRef<str>,
I2: IntoIterator<Item=S2>, S2: AsRef<str>
@@ -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::<SubsettingResult<Vec<_>>>()?;
+
let mapping = self.chars.into_iter().enumerate().map(|(i, c)| (c, i as 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<()> {
@@ -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 09740fb1..f2c9b5eb 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 0d7ad298..87d32422 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<PdfFont>,
}
/// 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::<PdfResult<Vec<_>>>()?;
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::<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(())
}
/// Write the fonts.
fn write_fonts(&mut self) -> PdfResult<()> {
- let 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();
-
- 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 + 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 + 3, &EmbeddedFont::OpenType(&self.font.data)).unwrap();
+ let mut id = self.offsets.fonts.0;
+
+ 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,
+ font.name.clone(),
+ CIDSystemInfo::new("(Adobe)", "(Identity)", 0),
+ id + 2,
+ ).widths(vec![WidthRecord::start(0, font.widths.clone())])
+ )?;
- Ok(())
- }
+ 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))?;
- /// 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);
+ id += 4;
}
- bytes
+
+ Ok(())
}
}
/// The data we need from the font.
struct PdfFont {
- data: Vec<u8>,
- mapping: HashMap<char, u16>,
- default_glyph: u16,
- name: String,
+ font: Font,
widths: Vec<GlyphUnit>,
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<u8>, chars: HashSet<char>) -> PdfResult<PdfFont> {
- let mut readable = Cursor::new(&data);
+ pub fn new(font: &Font, chars: &HashSet<char>) -> PdfResult<PdfFont> {
+ let mut readable = Cursor::new(&font.program);
let mut reader = OpenTypeReader::new(&mut readable);
let head = reader.read_table::<tables::Header>()?;
- let name = reader.read_table::<tables::Name>()?;
let post = reader.read_table::<tables::Post>()?;
let os2 = reader.read_table::<tables::OS2>()?;
- 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::<tables::HorizontalMetrics>()?;
- 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,
})
}
+}
+
+impl std::ops::Deref for PdfFont {
+ type Target = Font;
- /// 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)
+ 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‼");
- }
}