simple_psf/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4
5/**
6A PSF font
7
8Use [parse](Psf::parse) to create one, [get_glyph_pixels](Psf::get_glyph_pixels) to get a glyph by index,
9and [iter_unicode_entries](Psf::iter_unicode_entries) to get a glyph index by unicode character.
10PSF stores ASCII glyphs at the corresponding index, so you can retrieve ASCII characters without calling
11[iter_unicode_entries](Psf::iter_unicode_entries) by giving the ASCII number to [get_glyph_pixels](Psf::get_glyph_pixels) directly.
12*/
13pub struct Psf<'a> {
14	pub glyph_byte_length: usize,
15	pub glyph_width: usize,
16	pub glyph_height: usize,
17	pub glyphs: &'a [u8],
18	pub unicode_table: Option<&'a [u8]>,
19}
20
21
22impl<'a> Psf<'a> {
23	/**
24	Parse a PSF font from some bytes
25
26	See [ParseError] for possible errors.
27
28	PSF fonts may optionally have a unicode table indicating which glyphs correspond to which
29	unicode characters. This function does not validate the structure of the unicode table.
30	Errors in the encoding of the unicode table, if they can be detected, are reported by the
31	iterator returned from the [iter_unicode_entries](Psf::iter_unicode_entries) function.
32
33	This function is const making it possible to parse a Psf font at compile time
34	directly into a constant.
35	*/
36	pub const fn parse(data: &[u8]) -> Result<Psf, ParseError> {
37		if data.len() < 32 {
38			return Err(ParseError::HeaderMissing);
39		}
40
41		if u32_at(data, 0) == 0x864ab572 {
42			Self::parse_psf2(data)
43		} else if u32_at(data, 0) & 0xffff == 0x0436 {
44			Self::parse_psf1(data)
45		} else {
46			Err(ParseError::InvalidMagicBytes)
47		}
48	}
49
50	const fn parse_psf1(data: &[u8]) -> Result<Psf, ParseError> {
51		let mode = data[2];
52		let has_512_glyphs = 0 != (mode & 0x1);
53		let has_unicode_table = 0 != (mode & 0x2);
54
55		let glyph_count =
56			if has_512_glyphs {
57				256
58			} else {
59				512
60			};
61
62		let glyph_height = data[3] as usize;
63
64		let glyphs = slice_len(data, 4, glyph_count * glyph_height);
65
66		let unicode_entries =
67			if has_unicode_table {
68				let (unicode_entries, _) = data.split_at(4 + glyph_count * glyph_height);
69				Some(unicode_entries)
70			} else {
71				None
72			};
73
74		let psf =
75			Psf {
76				glyph_byte_length: glyph_height,
77				glyph_width: 8,
78				glyph_height: glyph_height,
79				glyphs: glyphs,
80				unicode_table: unicode_entries,
81			};
82
83		Ok(psf)
84	}
85
86	const fn parse_psf2(data: &[u8]) -> Result<Psf, ParseError> {
87		let version = u32_at(data, 4);
88		if 0 != version {
89			return Err(ParseError::UnknownVersion(version));
90		}
91
92		let header_size = u32_at(data, 8) as usize;
93		let flags = u32_at(data, 12);
94		let has_unicode_table = 1 == (flags & 0x00000001);
95		let glyph_count = u32_at(data, 16) as usize;
96		let glyph_byte_length = u32_at(data, 20) as usize;
97		let glyph_height = u32_at(data, 24) as usize;
98		let glyph_width = u32_at(data, 28) as usize;
99
100		let expected_byte_count = header_size + (glyph_count * glyph_byte_length);
101		if data.len() < expected_byte_count {
102			return Err(ParseError::GlyphTableTruncated { expected_byte_count: expected_byte_count });
103		}
104
105		let glyphs = slice_len(data, header_size, glyph_byte_length * glyph_count);
106
107		let unicode_table =
108			if has_unicode_table {
109				Some(data.split_at(expected_byte_count).1)
110			} else {
111				None
112			};
113
114		let psf = Psf {
115			glyph_byte_length: glyph_byte_length,
116			glyph_width: glyph_width,
117			glyph_height: glyph_height,
118			glyphs,
119			unicode_table,
120		};
121
122		Ok(psf)
123	}
124
125	/// Get the bits corresponding to the glyph's bitmap
126	///
127	/// PSF stores the bitmap as packed bits. Each byte in the slice contains
128	/// eight pixels, with the last byte of each row containing padding bits
129	/// so that the next row starts on a byte boundary.
130	///
131	/// You might be looking for [get_glyph_pixels](Psf::get_glyph_pixels) instead, which unpacks
132	/// the bits for you.
133	pub fn get_glyph_bits(&self, glyph_index: usize) -> Option<&[u8]> {
134		let start = self.glyph_byte_length * glyph_index;
135		let end = start + self.glyph_byte_length;
136		self.glyphs.get(start..end)
137	}
138
139	/**
140	Get the pixels that correspond to the given glyph's bitmap
141
142	PSF stores bitmaps in the packed bits of each byte.
143	This iterator unpacks the bits, returning a boolean for each pixel in the bitmap indicating
144	whether that pixel is lit or not.
145
146	PSF stores ASCII glyphs in the corresponding index, so you can retrieve ASCII glyphs directly
147	by calling `get_glyph_pixels(b'a' as usize)`. Unicode characters must be looked up with
148	(iter_unicode_entries)[Psf::iter_unicode_entries].
149	*/
150	pub fn get_glyph_pixels<'b>(&'b self, glyph_index: usize) -> Option<impl Iterator<Item=bool> + 'b> {
151		let glyph_bits = self.get_glyph_bits(glyph_index)?;
152		let bytes_per_row = (self.glyph_width + 7) / 8;
153
154		let iterator =
155			(0..self.glyph_height).flat_map(move |y| {
156				(0..self.glyph_width).map(move |x| {
157					let byte_index = (x / 8) + (y * bytes_per_row);
158					let byte = glyph_bits[byte_index];
159					let bit_offset = 7 - (x % 8);
160					let bit = (byte >> bit_offset) & 1;
161					bit == 1
162				})
163			});
164
165		Some(iterator)
166	}
167
168	/**
169	Iterate over the entries in the unicode table
170
171	PSF fonts may optionally include a unicode table indicating which unicode character each glyph corresponds to.
172	If such a table exists, this function returns an iterator that will yield each unicode entry along with the
173	index of the glyph that represents to it.
174
175	In the case of a malformed unicode table, the behavior of this iterator is unpredictable
176	because errors can not always be unambiguously detected. The iterator may return a unicode entry with
177	a string containing multiple graphemes that ought to each have their own glyph, it may return None,
178	or it may return a [core::str::Utf8Error].
179
180	If you are using the standard library, this iterator can be used to construct a HashMap lookup table of unicode
181	character to glyph index pairs.
182
183	```rust(ignore)
184	let glyph_lookup_table = psf.iter_unicode_entries().unwrap()
185		.filter_map(|(index, result)| result.ok().map(|character| (character, index)))
186		.collect::<std::collections::HashMap<&str, usize>>();
187	```
188	*/
189	pub fn iter_unicode_entries<'b>(&'b self) -> Option<impl Iterator<Item=(usize, Result<&'b str, core::str::Utf8Error>)> + 'b> {
190		let table = self.unicode_table?;
191
192		let iterator = table.split(|&x| x == 0xff).enumerate().flat_map(move |(glyph_index, unicode_entries)| {
193			unicode_entries.split(|&x| x == 0xfe).map(move |unicode_string| {
194				(glyph_index, core::str::from_utf8(unicode_string))
195			})
196		});
197
198		Some(iterator)
199	}
200}
201
202
203/// Possible errors returned by the [Psf::parse] function.
204#[derive(Copy, Clone, Debug, Eq, PartialEq)]
205pub enum ParseError {
206	/// The provided buffer is not large enough to contain the PSF header.
207	HeaderMissing,
208
209	/// The PSF header does not contain 72 b5 4a 86 as its first four bytes.
210	InvalidMagicBytes,
211
212	/// The PSF header contains a version other than 0.
213	/// (0 is the only version that exists as of writing)
214	UnknownVersion(u32),
215
216	/// The provided buffer is not large enough to contain all of the glyphs
217	/// that the PSF header indicated that it should.
218	GlyphTableTruncated { expected_byte_count: usize, },
219}
220
221
222// Same as &data[start..][..len], but const
223const fn slice_len(data: &[u8], start: usize, len: usize) -> &[u8] {
224	let (_, rest) = data.split_at(start);
225	let (segment, _) = rest.split_at(len);
226	segment
227}
228
229// Same as u32::from_le_bytes(data[at..][..4].try_into().unwrap()), but const
230const fn u32_at(data: &[u8], at: usize) -> u32 {
231	let bytes = slice_len(data, at, 4);
232	u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
233}