Skip to content

Commit 667ad27

Browse files
authored
Approximate ascent and descent if property is missing (#47)
1 parent 8d4af3b commit 667ad27

9 files changed

Lines changed: 150 additions & 76 deletions

File tree

bdf-parser/src/glyph.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ impl Glyphs {
293293
}
294294
}
295295

296+
if glyphs.is_empty() {
297+
return Err(ParserError::new("no CHARS in font"));
298+
}
299+
296300
Ok(Self { glyphs })
297301
}
298302

@@ -315,6 +319,32 @@ impl Glyphs {
315319
pub fn iter(&self) -> impl Iterator<Item = &Glyph> {
316320
self.glyphs.iter()
317321
}
322+
323+
/// Approximates the ascent.
324+
///
325+
/// See section 8.2.1 FONT_ASCENT in https://www.x.org/docs/XLFD/xlfd.pdf.
326+
pub(crate) fn approximate_ascent(&self) -> u32 {
327+
self.glyphs
328+
.iter()
329+
.map(|glyph| glyph.bounding_box.size.y - glyph.bounding_box.offset.y)
330+
.max()
331+
.unwrap_or_default()
332+
.try_into()
333+
.unwrap()
334+
}
335+
336+
/// Approximates the descent.
337+
///
338+
/// See section 8.2.2 FONT_DESCENT in https://www.x.org/docs/XLFD/xlfd.pdf.
339+
pub(crate) fn approximate_descent(&self) -> u32 {
340+
self.glyphs
341+
.iter()
342+
.map(|glyph| -glyph.bounding_box.offset.y)
343+
.max()
344+
.unwrap_or_default()
345+
.try_into()
346+
.unwrap()
347+
}
318348
}
319349

320350
#[cfg(test)]

bdf-parser/src/lib.rs

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod properties;
1212
pub use glyph::{Encoding, Glyph, Glyphs};
1313
pub use metadata::{Metadata, MetricsSet};
1414
pub use parser::ParserError;
15-
pub use properties::{Properties, Property, PropertyError, PropertyType};
15+
pub use properties::{Properties, Property, PropertyType};
1616

1717
use crate::parser::{Line, Lines};
1818

@@ -24,6 +24,9 @@ pub struct Font {
2424

2525
/// Glyphs.
2626
pub glyphs: Glyphs,
27+
28+
/// Metrics.
29+
pub metrics: Metrics,
2730
}
2831

2932
impl Font {
@@ -44,8 +47,13 @@ impl Font {
4447

4548
let metadata = Metadata::parse(&mut lines)?;
4649
let glyphs = Glyphs::parse(&mut lines, &metadata)?;
50+
let metrics = Metrics::new(&metadata, &glyphs)?;
4751

48-
Ok(Font { metadata, glyphs })
52+
Ok(Font {
53+
metadata,
54+
glyphs,
55+
metrics,
56+
})
4957
}
5058
}
5159

@@ -133,6 +141,39 @@ impl Coord {
133141
}
134142
}
135143

144+
/// Metrics.
145+
#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
146+
pub struct Metrics {
147+
/// Ascent above the baseline in pixels.
148+
pub ascent: u32,
149+
150+
/// Descent above the baseline in pixels.
151+
pub descent: u32,
152+
}
153+
154+
impl Metrics {
155+
fn new(metadata: &Metadata, glyphs: &Glyphs) -> Result<Self, ParserError> {
156+
let ascent = metadata
157+
.properties
158+
.try_get::<u32>(Property::FontAscent)
159+
.map_err(|_| ParserError::new("invalid value for FONT_ASCENT property"))?
160+
.unwrap_or_else(|| glyphs.approximate_ascent());
161+
162+
let descent = metadata
163+
.properties
164+
.try_get::<u32>(Property::FontDescent)
165+
.map_err(|_| ParserError::new("invalid value for FONT_DESCENT property"))?
166+
.unwrap_or_else(|| glyphs.approximate_descent());
167+
168+
Ok(Self { ascent, descent })
169+
}
170+
171+
/// Gets the line height in pixels.
172+
pub const fn line_height(&self) -> u32 {
173+
self.ascent + self.descent
174+
}
175+
}
176+
136177
#[cfg(test)]
137178
mod tests {
138179
use crate::{glyph::GlyphWidth, properties::PropertyValue};

bdf-parser/src/metadata.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,10 @@ mod tests {
138138
FONTBOUNDINGBOX 0 1 2 3
139139
SIZE 1 2 3
140140
COMMENT "comment"
141-
CHARS 0
141+
CHARS 1
142+
STARTCHAR 0
143+
BITMAP
144+
ENDCHAR
142145
ENDFONT
143146
"#};
144147

@@ -193,7 +196,10 @@ mod tests {
193196
SIZE 1 2 3
194197
COMMENT "comment"
195198
METRICSSET 2
196-
CHARS 0
199+
CHARS 1
200+
STARTCHAR 0
201+
BITMAP
202+
ENDCHAR
197203
ENDFONT
198204
"#};
199205

bdf-parser/src/properties.rs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -176,19 +176,25 @@ impl Properties {
176176

177177
/// Tries to get a property.
178178
///
179-
/// Returns an error if the property doesn't exist or the value has the wrong type.
180-
pub fn try_get<T: PropertyType>(&self, property: Property) -> Result<T, PropertyError> {
179+
/// Returns `None` if the property doesn't exits and an error if the value has the wrong type.
180+
pub fn try_get<T: PropertyType>(
181+
&self,
182+
property: Property,
183+
) -> Result<Option<T>, PropertyTypeError> {
181184
self.try_get_by_name(&property.to_string())
182185
}
183186

184187
/// Tries to get a property by name.
185188
///
186-
/// Returns an error if the property doesn't exist or the value has the wrong type.
187-
pub fn try_get_by_name<T: PropertyType>(&self, name: &str) -> Result<T, PropertyError> {
189+
/// Returns `None` if the property doesn't exits and an error if the value has the wrong type.
190+
pub fn try_get_by_name<T: PropertyType>(
191+
&self,
192+
name: &str,
193+
) -> Result<Option<T>, PropertyTypeError> {
188194
self.properties
189195
.get(name)
190-
.ok_or_else(|| PropertyError::Undefined(name.to_string()))
191-
.and_then(TryFrom::try_from)
196+
.map(|value| value.try_into())
197+
.transpose()
192198
}
193199

194200
/// Returns `true` if no properties exist.
@@ -200,12 +206,13 @@ impl Properties {
200206
/// Marker trait for property value types.
201207
pub trait PropertyType
202208
where
203-
Self: for<'a> TryFrom<&'a PropertyValue, Error = PropertyError>,
209+
Self: for<'a> TryFrom<&'a PropertyValue, Error = PropertyTypeError>,
204210
{
205211
}
206212

207213
impl PropertyType for String {}
208214
impl PropertyType for i32 {}
215+
impl PropertyType for u32 {}
209216

210217
#[derive(Debug, Clone, PartialEq, Eq)]
211218
pub enum PropertyValue {
@@ -214,38 +221,43 @@ pub enum PropertyValue {
214221
}
215222

216223
impl TryFrom<&PropertyValue> for String {
217-
type Error = PropertyError;
224+
type Error = PropertyTypeError;
218225

219226
fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
220227
match value {
221228
PropertyValue::Text(text) => Ok(text.clone()),
222-
_ => Err(PropertyError::WrongType),
229+
_ => Err(PropertyTypeError),
223230
}
224231
}
225232
}
226233

227234
impl TryFrom<&PropertyValue> for i32 {
228-
type Error = PropertyError;
235+
type Error = PropertyTypeError;
229236

230237
fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
231238
match value {
232239
PropertyValue::Int(int) => Ok(*int),
233-
_ => Err(PropertyError::WrongType),
240+
_ => Err(PropertyTypeError),
234241
}
235242
}
236243
}
237244

238-
/// Error returned by property getters.
239-
#[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord)]
240-
pub enum PropertyError {
241-
/// Undefined property.
242-
#[error("property \"{0}\" is undefined")]
243-
Undefined(String),
244-
/// Wrong property type.
245-
#[error("wrong property type")]
246-
WrongType,
245+
impl TryFrom<&PropertyValue> for u32 {
246+
type Error = PropertyTypeError;
247+
248+
fn try_from(value: &PropertyValue) -> Result<Self, Self::Error> {
249+
match value {
250+
PropertyValue::Int(int) if *int >= 0 => Ok(*int as u32),
251+
_ => Err(PropertyTypeError),
252+
}
253+
}
247254
}
248255

256+
/// Invalid property type error.
257+
#[derive(Debug, Error, PartialEq, Eq, PartialOrd, Ord)]
258+
#[error("invalid property type")]
259+
pub struct PropertyTypeError;
260+
249261
#[cfg(test)]
250262
mod tests {
251263
use super::*;
@@ -271,7 +283,7 @@ mod tests {
271283
] {
272284
assert_eq!(
273285
properties.try_get_by_name::<String>(key).unwrap(),
274-
expected.to_string(),
286+
Some(expected.to_string()),
275287
"key=\"{key}\""
276288
);
277289
}
@@ -289,16 +301,19 @@ mod tests {
289301
let mut lines = Lines::new(INPUT);
290302
let properties = Properties::parse(&mut lines).unwrap();
291303

292-
for (key, expected) in [
293-
("POS_INT", 10), //
294-
("NEG_INT", -20),
295-
] {
296-
assert_eq!(
297-
properties.try_get_by_name::<i32>(key).unwrap(),
298-
expected,
299-
"key=\"{key}\""
300-
);
301-
}
304+
assert_eq!(properties.try_get_by_name::<i32>("POS_INT"), Ok(Some(10)));
305+
assert_eq!(properties.try_get_by_name::<i32>("NEG_INT"), Ok(Some(-20)));
306+
307+
assert_eq!(properties.try_get_by_name::<u32>("POS_INT"), Ok(Some(10)));
308+
assert_eq!(
309+
properties.try_get_by_name::<u32>("NEG_INT"),
310+
Err(PropertyTypeError)
311+
);
312+
313+
assert_eq!(
314+
properties.try_get_by_name::<String>("POS_INT"),
315+
Err(PropertyTypeError)
316+
);
302317
}
303318

304319
#[test]

eg-bdf-examples/examples/font_viewer.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,27 +105,26 @@ fn try_main() -> Result<()> {
105105
.nth(1)
106106
.ok_or_else(|| anyhow!("missing filename"))?;
107107

108-
let converted_font = FontConverter::with_file(&file, "BDF_FILE")
108+
let converter = FontConverter::with_file(&file, "BDF_FILE")
109109
.glyphs(Mapping::Ascii)
110110
.missing_glyph_substitute('?');
111111

112-
let output = converted_font
112+
let bdf_output = converter
113113
.convert_eg_bdf()
114114
.with_context(|| "couldn't convert font")?;
115-
let bdf_font = output.as_font();
115+
let bdf_font = bdf_output.as_font();
116116

117-
let output = converted_font
117+
let mono_output = converter
118118
.convert_mono_font()
119119
.with_context(|| "couldn't convert font")?;
120-
let mono_font = output.as_font();
120+
let mono_font = mono_output.as_font();
121121

122122
let hints_style = MonoTextStyle::new(&FONT_6X10, Rgb888::CSS_DIM_GRAY);
123123
let bottom_right = TextStyleBuilder::new()
124124
.baseline(Baseline::Bottom)
125125
.alignment(Alignment::Right)
126126
.build();
127127

128-
// TODO: add metrics getter
129128
let line_height = bdf_font.ascent + bdf_font.descent;
130129
let display_height = line_height * 8;
131130
let display_width = (line_height * 25).max(display_height);

eg-font-converter/src/eg_bdf_font.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{fs, io, path::Path};
22

33
use anyhow::Result;
4-
use bdf_parser::{BoundingBox, Encoding};
4+
use bdf_parser::{BoundingBox, Encoding, Metrics};
55
use bitvec::{prelude::*, vec::BitVec};
66
use eg_bdf::{BdfFont, BdfGlyph};
77
use embedded_graphics::{
@@ -84,11 +84,14 @@ impl EgBdfOutput {
8484
let constant_name = format_ident!("{}", self.font.name);
8585
let data_file = self.font.data_file().to_string_lossy().to_string();
8686
let ConvertedFont {
87+
bdf,
8788
replacement_character,
88-
ascent,
89-
descent,
9089
..
91-
} = self.font;
90+
} = &self.font;
91+
92+
let Metrics {
93+
ascent, descent, ..
94+
} = bdf.metrics;
9295

9396
let glyphs = self.glyphs.iter().map(|glyph| {
9497
let BdfGlyph {
@@ -150,10 +153,12 @@ impl EgBdfOutput {
150153

151154
/// Returns the converted font as a [`BdfFont`].
152155
pub fn as_font(&self) -> BdfFont<'_> {
156+
let metrics = &self.font.bdf.metrics;
157+
153158
BdfFont {
154159
replacement_character: self.font.replacement_character,
155-
ascent: self.font.ascent,
156-
descent: self.font.descent,
160+
ascent: metrics.ascent,
161+
descent: metrics.descent,
157162
glyphs: &self.glyphs,
158163
data: self.data(),
159164
}

0 commit comments

Comments
 (0)