bdf_reader/
reader.rs

1use crate::{
2	tokens::{Token, WritingDirection},
3	BoundingBox, Error, Font, Glyph, Size, Value
4};
5use bit_vec::BitVec;
6use log::debug;
7use std::{
8	collections::{BTreeSet, HashMap},
9	io::BufRead,
10	mem
11};
12
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum State {
15	/// Initial state.
16	Initial,
17
18	/// Between start and end font.
19	Font,
20
21	/// Inside the font's properties, with `len` properties remaining.
22	Properties { len: usize },
23
24	/// Inside the font's chars, with `len` glyphs remaining.
25	Chars { len: usize },
26
27	/// Inside one of the font's chars, with `chars` glyphs remaining including the
28	/// current one.
29	Glyph { chars: usize },
30
31	/// Inside the glyph bitmap, with `len` lines remaining and `chars` glyphs remaining
32	/// after the current one.
33	Bitmap { chars: usize, len: usize },
34
35	/// Final state
36	Final
37}
38
39impl State {
40	fn assert_initial(self, token: &Token) -> Result<(), Error> {
41		match self {
42			Self::Initial => Ok(()),
43			_ => Err(Error::InvalidContext(token.clone(), self, "Initial"))
44		}
45	}
46
47	fn assert_font(self, token: &Token) -> Result<(), Error> {
48		match self {
49			Self::Font => Ok(()),
50			_ => Err(Error::InvalidContext(token.clone(), self, "Font"))
51		}
52	}
53
54	fn assert_properties(self, token: &Token) -> Result<usize, Error> {
55		match self {
56			Self::Properties { len } => Ok(len),
57			_ => Err(Error::InvalidContext(token.clone(), self, "Properties"))
58		}
59	}
60
61	fn assert_chars(self, token: &Token) -> Result<usize, Error> {
62		match self {
63			Self::Chars { len } => Ok(len),
64			_ => Err(Error::InvalidContext(token.clone(), self, "Chars"))
65		}
66	}
67
68	fn assert_glyph(self, token: &Token) -> Result<usize, Error> {
69		match self {
70			Self::Glyph { chars } => Ok(chars),
71			_ => Err(Error::InvalidContext(token.clone(), self, "Glyph"))
72		}
73	}
74
75	fn assert_bitmap(self, token: &Token) -> Result<(usize, usize), Error> {
76		match self {
77			Self::Bitmap { chars, len } => Ok((chars, len)),
78			_ => Err(Error::InvalidContext(token.clone(), self, "Bitmap"))
79		}
80	}
81}
82
83impl Font {
84	pub fn read<R: BufRead>(reader: R) -> Result<Self, Error> {
85		let mut font_version = None;
86		let mut font_name = None;
87		let mut font_size = None;
88		let mut font_bbox = None;
89		let mut font_swidth = None;
90		let mut font_dwidth = None;
91		let mut font_properties = HashMap::new();
92		let mut font_glyphs = BTreeSet::new();
93
94		let mut glyph_name = None;
95		let mut glyph_encoding = None;
96		let mut glyph_swidth = None;
97		let mut glyph_dwidth = None;
98		let mut glyph_bbox = None;
99		let mut glyph_bitmap = Vec::new();
100
101		let mut state = State::Initial;
102		for (ll, line) in reader.lines().enumerate().map(|(ll, line)| (ll + 1, line)) {
103			let line = line?;
104			if line.trim().is_empty() {
105				debug!("Skipping blank line {ll}");
106				continue;
107			}
108			debug!("Parsing line {ll} {line:?}, state={state:?}");
109
110			match &mut state {
111				State::Properties { len } if *len > 0 => {
112					let idx: usize = line
113						.chars()
114						.take_while(|ch| !ch.is_ascii_whitespace())
115						.map(|ch| ch.len_utf8())
116						.sum();
117					let key = &line[0 .. idx];
118					let value_str = &line[idx + 1 ..];
119					let v = if value_str.starts_with('"') && value_str.ends_with('"') {
120						Value::String(value_str.trim_matches('"').replace("''", "\""))
121					} else {
122						Value::Integer(
123							value_str.parse().map_err(Error::InvalidPropertyValue)?
124						)
125					};
126					font_properties.insert(key.to_owned(), v);
127
128					*len -= 1;
129					continue;
130				},
131
132				State::Bitmap { len, .. } if *len > 0 => {
133					let mut iter = line.chars().filter(|ch| ch.is_ascii_hexdigit());
134					let mut raw = Vec::new();
135					while let Some(first) = iter.next() {
136						let second = iter.next().ok_or_else(|| {
137							Error::InvalidBitmapValue(format!("{first}"))
138						})?;
139						let hex = format!("{first}{second}");
140						let byte = u8::from_str_radix(&hex, 16)
141							.map_err(|_| Error::InvalidBitmapValue(hex))?;
142						raw.push(byte);
143					}
144					glyph_bitmap.push(BitVec::from_bytes(&raw));
145
146					*len -= 1;
147					continue;
148				},
149
150				_ => {}
151			}
152
153			let token = Token::parse_line(&line)?;
154			match token {
155				Token::StartFont { .. } => {
156					state.assert_initial(&token)?;
157					state = State::Font;
158				},
159
160				Token::ContentVersion { ver } => {
161					state.assert_font(&token)?;
162					font_version = Some(ver);
163				},
164
165				Token::Font { ref name } => {
166					state.assert_font(&token)?;
167					font_name = Some(name.into());
168				},
169
170				Token::Size { pt, xres, yres } => {
171					state.assert_font(&token)?;
172					font_size = Some(Size { pt, xres, yres });
173				},
174
175				Token::FontBoundingBox {
176					fbbx,
177					fbby,
178					xoff,
179					yoff
180				} => {
181					state.assert_font(&token)?;
182					font_bbox = Some(BoundingBox {
183						width: fbbx,
184						height: fbby,
185						offset_x: xoff,
186						offset_y: yoff
187					});
188				},
189
190				Token::MetricsSet {
191					dir: WritingDirection::Horizontal
192				} => {},
193				Token::MetricsSet { dir } => {
194					unimplemented!("METRICSSET {dir:?} is currently not supported");
195				},
196
197				Token::SWidth { swx0, swy0 } if matches!(state, State::Font) => {
198					font_swidth = Some((swx0, swy0));
199				},
200				Token::SWidth { swx0, swy0 } => {
201					state.assert_glyph(&token)?;
202					glyph_swidth = Some((swx0, swy0));
203				},
204
205				Token::DWidth { dwx0, dwy0 } if matches!(state, State::Font) => {
206					font_dwidth = Some((dwx0, dwy0));
207				},
208				Token::DWidth { dwx0, dwy0 } => {
209					state.assert_glyph(&token)?;
210					glyph_dwidth = Some((dwx0, dwy0));
211				},
212
213				Token::SWidthVertical { swx1, swy1 } => {
214					unimplemented!("SWIDTH1 {swx1} {swy1} is currently not supported");
215				},
216				Token::DWidthVertical { dwx1, dwy1 } => {
217					unimplemented!("DWIDTH1 {dwx1} {dwy1} is currently not supported");
218				},
219				Token::VVector { xoff, yoff } => {
220					unimplemented!("VVECTOR {xoff} {yoff} is currently not supported");
221				},
222
223				Token::StartProperties { n } => {
224					state.assert_font(&token)?;
225					state = State::Properties { len: n };
226				},
227
228				Token::EndProperties => {
229					let len = state.assert_properties(&token)?;
230					if len != 0 {
231						return Err(Error::UnexpectedEnd("Properties"));
232					}
233					state = State::Font;
234				},
235
236				Token::Chars { nglyphs } => {
237					state.assert_font(&token)?;
238					state = State::Chars { len: nglyphs };
239				},
240
241				Token::StartChar { ref name } => {
242					let chars = state.assert_chars(&token)?;
243					state = State::Glyph { chars };
244
245					glyph_name = Some(name.to_owned());
246					glyph_encoding = None;
247					glyph_swidth = font_swidth;
248					glyph_dwidth = font_dwidth;
249					glyph_bbox = font_bbox;
250					glyph_bitmap.clear();
251				},
252
253				Token::Encoding { enc } => {
254					state.assert_glyph(&token)?;
255					glyph_encoding = Some(enc);
256				},
257
258				Token::BoundingBox {
259					bbw,
260					bbh,
261					bbxoff,
262					bbyoff
263				} => {
264					state.assert_glyph(&token)?;
265					glyph_bbox = Some(BoundingBox {
266						width: bbw,
267						height: bbh,
268						offset_x: bbxoff,
269						offset_y: bbyoff
270					});
271				},
272
273				Token::Bitmap => {
274					let chars = state.assert_glyph(&token)?;
275					state = State::Bitmap {
276						chars,
277						len: glyph_bbox.ok_or(Error::MissingGlyphBoundingBox)?.height
278							as usize
279					}
280				},
281
282				Token::EndChar => {
283					let (chars, len) = state.assert_bitmap(&token)?;
284					if len != 0 {
285						return Err(Error::UnexpectedEnd("Char"));
286					}
287					state = State::Chars { len: chars - 1 };
288
289					font_glyphs.insert(
290						Glyph {
291							name: glyph_name.take().unwrap(),
292							encoding: glyph_encoding
293								.ok_or(Error::MissingGlyphEncoding)?,
294							swidth: glyph_swidth,
295							dwidth: glyph_dwidth,
296							bbox: glyph_bbox.ok_or(Error::MissingGlyphBoundingBox)?,
297							bitmap: mem::take(&mut glyph_bitmap)
298						}
299						.into()
300					);
301				},
302
303				Token::EndFont => {
304					let chars = state.assert_chars(&token)?;
305					if chars != 0 {
306						return Err(Error::UnexpectedEnd("Font"));
307					}
308					state = State::Final;
309				},
310
311				// ignored
312				Token::Comment { .. } => {}
313			};
314		}
315
316		// TODO check that state = final
317		Ok(Self {
318			version: font_version,
319			name: font_name.ok_or(Error::MissingFontName)?,
320			bbox: font_bbox.ok_or(Error::MissingFontBoundingBox)?,
321			size: font_size.ok_or(Error::MissingFontSize)?,
322			properties: font_properties,
323			glyphs: font_glyphs
324		})
325	}
326}