summaryrefslogtreecommitdiff
path: root/src/font.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2019-02-12 21:31:35 +0100
committerLaurenz <laurmaedje@gmail.com>2019-02-12 21:31:35 +0100
commit5a600eb354c65ec008cbf020e45705c2f401d669 (patch)
treeb61d6ae3168716ba198c5631934520053c4a57c4 /src/font.rs
Move crate into workspace subfolder
Diffstat (limited to 'src/font.rs')
-rw-r--r--src/font.rs270
1 files changed, 270 insertions, 0 deletions
diff --git a/src/font.rs b/src/font.rs
new file mode 100644
index 00000000..1280aec3
--- /dev/null
+++ b/src/font.rs
@@ -0,0 +1,270 @@
+//! Reading of metrics and font data from _OpenType_ and _TrueType_ font files.
+
+#![allow(unused_variables)]
+
+use std::fmt;
+use std::io::{self, Read, Seek, SeekFrom};
+use byteorder::{BE, ReadBytesExt};
+
+
+/// A loaded opentype (or truetype) font.
+#[derive(Debug, Clone, PartialEq)]
+pub struct Font {
+ /// The PostScript name of this font.
+ pub name: String,
+}
+
+impl Font {
+ /// Create a new font from a byte source.
+ pub fn new<R>(data: &mut R) -> FontResult<Font> where R: Read + Seek {
+ OpenTypeReader::new(data).read()
+ }
+}
+
+/// Built-in fonts.
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[allow(missing_docs)]
+pub enum BuiltinFont {
+ Courier,
+ CourierBold,
+ CourierOblique,
+ CourierBoldOblique,
+ Helvetica,
+ HelveticaBold,
+ HelveticaOblique,
+ HelveticaBoldOblique,
+ TimesRoman,
+ TimesBold,
+ TimeItalic,
+ TimeBoldItalic,
+ Symbol,
+ ZapfDingbats,
+}
+
+impl BuiltinFont {
+ /// The name of the font.
+ pub fn name(&self) -> &'static str {
+ use BuiltinFont::*;
+ match self {
+ Courier => "Courier",
+ CourierBold => "Courier-Bold",
+ CourierOblique => "Courier-Oblique",
+ CourierBoldOblique => "Courier-BoldOblique",
+ Helvetica => "Helvetica",
+ HelveticaBold => "Helvetica-Bold",
+ HelveticaOblique => "Helvetica-Oblique",
+ HelveticaBoldOblique => "Helvetica-BoldOblique",
+ TimesRoman => "Times-Roman",
+ TimesBold => "Times-Bold",
+ TimeItalic => "Time-Italic",
+ TimeBoldItalic => "Time-BoldItalic",
+ Symbol => "Symbol",
+ ZapfDingbats => "ZapfDingbats",
+ }
+ }
+}
+
+
+/// Result type used for tokenization.
+type FontResult<T> = std::result::Result<T, LoadingError>;
+
+/// A failure when loading a font.
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct LoadingError {
+ /// A message describing the error.
+ pub message: String,
+}
+
+impl From<io::Error> for LoadingError {
+ fn from(err: io::Error) -> LoadingError {
+ LoadingError { message: format!("io error: {}", err) }
+ }
+}
+
+impl fmt::Display for LoadingError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "font loading error: {}", self.message)
+ }
+}
+
+
+/// Reads a font from a _OpenType_ or _TrueType_ font file.
+struct OpenTypeReader<'r, R> where R: Read + Seek {
+ data: &'r mut R,
+ font: Font,
+ table_records: Vec<TableRecord>,
+}
+
+/// Used to identify a table, design-variation axis, script,
+/// language system, feature, or baseline.
+#[derive(Clone, PartialEq)]
+struct Tag(pub [u8; 4]);
+
+impl PartialEq<&str> for Tag {
+ fn eq(&self, other: &&str) -> bool {
+ other.as_bytes() == &self.0
+ }
+}
+
+impl fmt::Debug for Tag {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "\"{}\"", self)
+ }
+}
+
+impl fmt::Display for Tag {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let a = self.0;
+ write!(f, "{}{}{}{}", a[0] as char, a[1] as char, a[2] as char, a[3] as char)
+ }
+}
+
+/// Stores information about one table.
+#[derive(Debug, Clone, PartialEq)]
+struct TableRecord {
+ table: Tag,
+ check_sum: u32,
+ offset: u32,
+ length: u32,
+}
+
+impl<'r, R> OpenTypeReader<'r, R> where R: Read + Seek {
+ /// Create a new reader from a byte source.
+ pub fn new(data: &'r mut R) -> OpenTypeReader<'r, R> {
+ OpenTypeReader {
+ data,
+ font: Font {
+ name: String::new(),
+ },
+ table_records: vec![],
+ }
+ }
+
+ /// Read the font from the byte source.
+ pub fn read(mut self) -> FontResult<Font> {
+ self.read_table_records()?;
+ self.read_name_table()?;
+
+ Ok(self.font)
+ }
+
+ /// Read the offset table.
+ fn read_table_records(&mut self) -> FontResult<()> {
+ let sfnt_version = self.data.read_u32::<BE>()?;
+ let num_tables = self.data.read_u16::<BE>()?;
+ let search_range = self.data.read_u16::<BE>()?;
+ let entry_selector = self.data.read_u16::<BE>()?;
+ let range_shift = self.data.read_u16::<BE>()?;
+
+ let outlines = match sfnt_version {
+ 0x00010000 => "truetype",
+ 0x4F54544F => "cff",
+ _ => return self.err("unsuported font outlines"),
+ };
+
+ for _ in 0 .. num_tables {
+ let table = self.read_tag()?;
+ let check_sum = self.data.read_u32::<BE>()?;
+ let offset = self.data.read_u32::<BE>()?;
+ let length = self.data.read_u32::<BE>()?;
+
+ self.table_records.push(TableRecord {
+ table,
+ check_sum,
+ offset,
+ length,
+ });
+ }
+
+ Ok(())
+ }
+
+ /// Read the name table (gives general information about the font).
+ fn read_name_table(&mut self) -> FontResult<()> {
+ let table = match self.table_records.iter().find(|record| record.table == "name") {
+ Some(table) => table,
+ None => return self.err("missing 'name' table"),
+ };
+
+ self.data.seek(SeekFrom::Start(table.offset as u64))?;
+
+ let format = self.data.read_u16::<BE>()?;
+ let count = self.data.read_u16::<BE>()?;
+ let string_offset = self.data.read_u16::<BE>()?;
+
+ let storage = (table.offset + string_offset as u32) as u64;
+
+ let mut name = None;
+
+ for _ in 0 .. count {
+ let platform_id = self.data.read_u16::<BE>()?;
+ let encoding_id = self.data.read_u16::<BE>()?;
+ let language_id = self.data.read_u16::<BE>()?;
+ let name_id = self.data.read_u16::<BE>()?;
+ let length = self.data.read_u16::<BE>()?;
+ let offset = self.data.read_u16::<BE>()?;
+
+ // Postscript name is what we are interested in
+ if name_id == 6 && platform_id == 3 && encoding_id == 1 {
+ if length % 2 != 0 {
+ return self.err("invalid encoded name");
+ }
+
+ self.data.seek(SeekFrom::Start(storage + offset as u64))?;
+ let mut buffer = Vec::with_capacity(length as usize / 2);
+
+ for _ in 0 .. length / 2 {
+ buffer.push(self.data.read_u16::<BE>()?);
+ }
+
+ name = match String::from_utf16(&buffer) {
+ Ok(string) => Some(string),
+ Err(_) => return self.err("invalid encoded name"),
+ };
+
+ break;
+ }
+ }
+
+ self.font.name = match name {
+ Some(name) => name,
+ None => return self.err("missing postscript font name"),
+ };
+
+ Ok(())
+ }
+
+ /// Read a tag (array of four u8's).
+ fn read_tag(&mut self) -> FontResult<Tag> {
+ let mut tag = [0u8; 4];
+ self.data.read(&mut tag)?;
+ Ok(Tag(tag))
+ }
+
+ /// Gives a font loading error with a message.
+ fn err<T, S: Into<String>>(&self, message: S) -> FontResult<T> {
+ Err(LoadingError { message: message.into() })
+ }
+}
+
+
+#[cfg(test)]
+mod font_tests {
+ use super::*;
+
+ /// Test if the loaded font is the same as the expected font.
+ fn test(path: &str, font: Font) {
+ let mut file = std::fs::File::open(path).unwrap();
+ assert_eq!(Font::new(&mut file), Ok(font));
+ }
+
+ #[test]
+ fn opentype() {
+ test("../fonts/NotoSerif-Regular.ttf", Font {
+ name: "NotoSerif".to_owned(),
+ });
+ test("../fonts/NotoSansMath-Regular.ttf", Font {
+ name: "NotoSansMath-Regular".to_owned(),
+ });
+ }
+}