diff --git a/bdf-parser/src/glyph.rs b/bdf-parser/src/glyph.rs index bf87d9e..e788797 100644 --- a/bdf-parser/src/glyph.rs +++ b/bdf-parser/src/glyph.rs @@ -2,7 +2,7 @@ use std::convert::TryFrom; use crate::{ parser::{Line, Lines}, - BoundingBox, Coord, ParserError, + BoundingBox, Coord, Metadata, ParserError, }; /// Glyph encoding @@ -17,25 +17,53 @@ pub enum Encoding { Unspecified, } +/// Glyph width. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct GlyphWidth { + /// Scalable width in 1/1000th of the size. + pub scalable: Coord, + /// Device width in device pixels. + pub device: Coord, +} + /// Glyph. #[derive(Debug, Clone, PartialEq, Default)] pub struct Glyph { /// Name. + /// + /// Specified by `STARTCHAR`. pub name: String, /// Encoding. + /// + /// Specified by `ENCODING`. pub encoding: Encoding, - /// Scalable width. - pub scalable_width: Option, + /// Width for writing mode 0. + /// + /// Specified by `DWIDTH` and `SWIDTH` and used for `METRICSSET 0` and + /// `METRICSSET 2`. + pub width_horizontal: Option, - /// Device width. - pub device_width: Coord, + /// Width for writing mode 1. + /// + /// Specified by `DWIDTH1` and `SWIDTH1` and used for `METRICSSET 1` and + /// `METRICSSET 2`. + pub width_vertical: Option, /// Bounding box. + /// + /// Specified by `BBX`. pub bounding_box: BoundingBox, + /// Origin offset between writing mode 0 and 1. + /// + /// Specified by `VVECTOR`. + pub origin_offset: Option, + /// Bitmap data. + /// + /// Specified by the hex values between `BITMAP` and `ENDCHAR`. pub bitmap: Vec, } @@ -57,15 +85,29 @@ fn parse_bitmap_row(line: &Line<'_>, bitmap: &mut Vec) -> Result<(), ()> { Ok(()) } +/// Approximate SWIDTH based on DWIDTH and the font metadata. +fn calculate_swidth(device_width: Coord, metadata: &Metadata) -> Coord { + Coord { + x: device_width.x * 1000 * 72 / metadata.point_size / metadata.resolution.x, + y: device_width.y * 1000 * 72 / metadata.point_size / metadata.resolution.y, + } +} + impl Glyph { - pub(crate) fn parse(mut lines: &mut Lines<'_>) -> Result { + pub(crate) fn parse( + mut lines: &mut Lines<'_>, + metadata: &Metadata, + ) -> Result { let mut encoding = Encoding::Unspecified; - let mut scalable_width = None; - let mut device_width = Coord::new(0, 0); - let mut bounding_box = BoundingBox { + let mut swidth = None; + let mut dwidth = None; + let mut swidth1 = None; + let mut dwidth1 = None; + let mut bbx = BoundingBox { size: Coord::new(0, 0), offset: Coord::new(0, 0), }; + let mut vvector = None; let start = lines.next().unwrap(); assert_eq!(start.keyword, "STARTCHAR"); @@ -91,19 +133,39 @@ impl Glyph { }; } "SWIDTH" => { - scalable_width = Some( + swidth = Some( Coord::parse(&line) .ok_or_else(|| ParserError::with_line("invalid \"SWIDTH\"", &line))?, ); } "DWIDTH" => { - device_width = Coord::parse(&line) - .ok_or_else(|| ParserError::with_line("invalid \"DWIDTH\"", &line))?; + dwidth = Some( + Coord::parse(&line) + .ok_or_else(|| ParserError::with_line("invalid \"DWIDTH\"", &line))?, + ); + } + "SWIDTH1" => { + swidth1 = Some( + Coord::parse(&line) + .ok_or_else(|| ParserError::with_line("invalid \"SWIDTH1\"", &line))?, + ); + } + "DWIDTH1" => { + dwidth1 = Some( + Coord::parse(&line) + .ok_or_else(|| ParserError::with_line("invalid \"DWIDTH1\"", &line))?, + ); } "BBX" => { - bounding_box = BoundingBox::parse(&line) + bbx = BoundingBox::parse(&line) .ok_or_else(|| ParserError::with_line("invalid \"BBX\"", &line))?; } + "VVECTOR" => { + vvector = Some( + Coord::parse(&line) + .ok_or_else(|| ParserError::with_line("invalid \"VVECTOR\"", &line))?, + ); + } "BITMAP" => { break; } @@ -126,13 +188,42 @@ impl Glyph { .map_err(|_| ParserError::with_line("invalid hex data in BITMAP", &line))?; } + let width_horizontal = if swidth.is_some() || dwidth.is_some() { + let device = + dwidth.ok_or_else(|| ParserError::with_line("missing \"DWIDTH\"", &start))?; + + // According to the specs SWIDTH is required, but there are BDF + // files which are missing this value. The parser will try to + // approximate the value in this case. + let scalable = swidth.unwrap_or_else(|| calculate_swidth(device, metadata)); + + Some(GlyphWidth { scalable, device }) + } else { + None + }; + + let width_vertical = if swidth1.is_some() || dwidth1.is_some() { + let device = + dwidth1.ok_or_else(|| ParserError::with_line("missing \"DWIDTH1\"", &start))?; + + // According to the specs SWIDTH is required, but there are BDF + // files which are missing this value. The parser will try to + // approximate the value in this case. + let scalable = swidth1.unwrap_or_else(|| calculate_swidth(device, metadata)); + + Some(GlyphWidth { scalable, device }) + } else { + None + }; + Ok(Self { name: name.to_string(), encoding, - scalable_width, - device_width, - bounding_box, + width_horizontal, + width_vertical, + bounding_box: bbx, bitmap, + origin_offset: vvector, }) } @@ -178,7 +269,7 @@ pub struct Glyphs { } impl Glyphs { - pub(crate) fn parse(lines: &mut Lines<'_>) -> Result { + pub(crate) fn parse(lines: &mut Lines<'_>, metadata: &Metadata) -> Result { let mut glyphs = Vec::new(); while let Some(line) = lines.next() { @@ -188,7 +279,7 @@ impl Glyphs { } "STARTCHAR" => { lines.backtrack(line); - glyphs.push(Glyph::parse(lines)?); + glyphs.push(Glyph::parse(lines, metadata)?); } "ENDFONT" => { break; @@ -228,18 +319,31 @@ impl Glyphs { #[cfg(test)] mod tests { + use crate::Properties; + use super::*; use indoc::indoc; + fn mock_metadata() -> Metadata { + Metadata { + name: "test".to_string(), + point_size: 16, + resolution: Coord::new(100, 100), + bounding_box: BoundingBox::default(), + metrics_set: crate::MetricsSet::Horizontal, + properties: Properties::default(), + } + } + #[track_caller] fn parse_glyph(input: &str) -> Glyph { let mut lines = Lines::new(input); - Glyph::parse(&mut lines).unwrap() + Glyph::parse(&mut lines, &mock_metadata()).unwrap() } #[test] fn test_parse_bitmap() { - let prefix = "STARTCHAR 0\nBITMAP\n"; + let prefix = "STARTCHAR 0\nSWIDTH 0 0\nDWIDTH 0 0\nBITMAP\n"; let suffix = "\nENDCHAR"; for (input, expected) in [ @@ -307,8 +411,12 @@ mod tests { size: Coord::new(8, 16), offset: Coord::new(0, -2), }, - scalable_width: Some(Coord::new(500, 0)), - device_width: Coord::new(8, 0), + width_horizontal: Some(GlyphWidth { + scalable: Coord::new(500, 0), + device: Coord::new(8, 0), + }), + width_vertical: None, + origin_offset: None, }, ) } @@ -325,7 +433,7 @@ mod tests { let mut lines = Lines::new(chardata); - let glyphs = Glyphs::parse(&mut lines).unwrap(); + let glyphs = Glyphs::parse(&mut lines, &mock_metadata()).unwrap(); assert_eq!(glyphs.get('A'), Some(&expected_glyph)); } @@ -433,8 +541,12 @@ mod tests { }, encoding: Encoding::Unspecified, name: "000".to_string(), - scalable_width: Some(Coord::new(432, 0)), - device_width: Coord::new(6, 0), + width_horizontal: Some(GlyphWidth { + scalable: Coord::new(432, 0), + device: Coord::new(6, 0), + }), + width_vertical: None, + origin_offset: None, } ); } @@ -461,8 +573,45 @@ mod tests { }, encoding: Encoding::NonStandard(123), name: "000".to_string(), - scalable_width: Some(Coord::new(432, 0)), - device_width: Coord::new(6, 0), + width_horizontal: Some(GlyphWidth { + scalable: Coord::new(432, 0), + device: Coord::new(6, 0), + }), + width_vertical: None, + origin_offset: None, + } + ); + } + + #[test] + fn parse_glyph_with_writing_mode1_metrics() { + let chardata = indoc! {r#" + STARTCHAR 000 + ENCODING -1 + SWIDTH1 0 432 + DWIDTH1 0 6 + VVECTOR 1 2 + BBX 0 0 0 0 + BITMAP + ENDCHAR + "#}; + + assert_eq!( + parse_glyph(chardata), + Glyph { + bitmap: vec![], + bounding_box: BoundingBox { + size: Coord::new(0, 0), + offset: Coord::new(0, 0), + }, + encoding: Encoding::Unspecified, + name: "000".to_string(), + width_horizontal: None, + width_vertical: Some(GlyphWidth { + scalable: Coord::new(0, 432), + device: Coord::new(0, 6), + }), + origin_offset: Some(Coord::new(1, 2)), } ); } @@ -489,8 +638,12 @@ mod tests { }, encoding: Encoding::Standard(0), name: "000".to_string(), - scalable_width: Some(Coord::new(432, 0)), - device_width: Coord::new(6, 0), + width_horizontal: Some(GlyphWidth { + scalable: Coord::new(432, 0), + device: Coord::new(6, 0), + }), + width_vertical: None, + origin_offset: None, } ); } diff --git a/bdf-parser/src/lib.rs b/bdf-parser/src/lib.rs index 1044e91..4d87c6b 100644 --- a/bdf-parser/src/lib.rs +++ b/bdf-parser/src/lib.rs @@ -43,7 +43,7 @@ impl BdfFont { } let metadata = Metadata::parse(&mut lines)?; - let glyphs = Glyphs::parse(&mut lines)?; + let glyphs = Glyphs::parse(&mut lines, &metadata)?; Ok(BdfFont { metadata, glyphs }) } @@ -135,7 +135,7 @@ impl Coord { #[cfg(test)] mod tests { - use crate::properties::PropertyValue; + use crate::{glyph::GlyphWidth, properties::PropertyValue}; use super::*; use indoc::indoc; @@ -164,6 +164,7 @@ mod tests { CHARS 2 STARTCHAR Char 0 ENCODING 64 + SWIDTH 480 0 DWIDTH 8 0 BBX 8 8 0 0 BITMAP @@ -172,6 +173,7 @@ mod tests { ENDCHAR STARTCHAR Char 1 ENCODING 65 + SWIDTH 480 0 DWIDTH 8 0 BBX 8 8 0 0 BITMAP @@ -219,8 +221,12 @@ mod tests { }, encoding: Encoding::Standard(64), // '@' name: "Char 0".to_string(), - device_width: Coord::new(8, 0), - scalable_width: None, + width_horizontal: Some(GlyphWidth { + device: Coord::new(8, 0), + scalable: Coord::new(480, 0), + }), + width_vertical: None, + origin_offset: None, }, Glyph { bitmap: vec![0x2f, 0x02], @@ -230,8 +236,12 @@ mod tests { }, encoding: Encoding::Standard(65), // 'A' name: "Char 1".to_string(), - device_width: Coord::new(8, 0), - scalable_width: None, + width_horizontal: Some(GlyphWidth { + device: Coord::new(8, 0), + scalable: Coord::new(480, 0), + }), + width_vertical: None, + origin_offset: None, }, ], ); @@ -264,6 +274,17 @@ mod tests { test_font(&BdfFont::parse(&input).unwrap()); } + #[test] + fn parse_font_missing_swidth() { + let lines: Vec<_> = FONT + .lines() + .filter(|line| !line.contains("SWIDTH")) + .collect(); + let input = lines.join("\n"); + + test_font(&BdfFont::parse(&input).unwrap()); + } + #[test] fn parse_font_with_windows_line_endings() { let lines: Vec<_> = FONT.lines().collect(); diff --git a/eg-font-converter/src/eg_bdf_font.rs b/eg-font-converter/src/eg_bdf_font.rs index 247e32a..679f996 100644 --- a/eg-font-converter/src/eg_bdf_font.rs +++ b/eg-font-converter/src/eg_bdf_font.rs @@ -53,10 +53,14 @@ impl EgBdfOutput { } }; + // TODO: error handling + // TODO: use y coordinate or ensure y is zero + let device_width = u32::try_from(glyph.width_horizontal.unwrap().device.x).unwrap(); + glyphs.push(BdfGlyph { character, bounding_box, - device_width: glyph.device_width.x as u32, // TODO: check cast and handle y? + device_width, start_index: data.len(), });