-
Notifications
You must be signed in to change notification settings - Fork 15
Serialization Proposal 1 #52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
35871b0
ab5f0eb
941ceed
e17f5da
dc96dcc
bd23ac5
7eb585f
5c1c7d6
d90124c
386937e
33dc8a8
e7a05b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| use crate::{BdfFont, BdfGlyph, DisplayBdfGlyph}; | ||
| use embedded_graphics::{ | ||
| prelude::*, | ||
| primitives::Rectangle, | ||
| text::{ | ||
| renderer::{CharacterStyle, TextMetrics, TextRenderer}, | ||
| Baseline, | ||
| }, | ||
| }; | ||
|
|
||
| #[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] | ||
| pub struct Metrics { | ||
| pub ascent: u32, | ||
| pub descent: u32, | ||
| pub line_height: u32, | ||
| } | ||
|
|
||
| /// A proportional font | ||
| pub trait ProportionalFont<'a>: Clone { | ||
| /// Returns a struct containing ascent, descent, baseline_offset, and line_height | ||
| fn metrics(&self) -> Metrics; | ||
| /// Finds a BdfGlyph for a character | ||
| fn lookup(&self, c: char) -> Option<DisplayBdfGlyph<'_>>; | ||
| /// Finds the replacement glyph | ||
| fn replacement_glyph(&'a self) -> DisplayBdfGlyph<'a>; | ||
|
|
||
| /// Returns the baseline offset | ||
| fn baseline_offset(&self, baseline: Baseline) -> i32 { | ||
| match baseline { | ||
| Baseline::Top => self.metrics().ascent.saturating_sub(1) as i32, | ||
| Baseline::Bottom => -(self.metrics().descent as i32), | ||
| Baseline::Middle => (self.metrics().ascent as i32 - self.metrics().descent as i32) / 2, | ||
| Baseline::Alphabetic => 0, | ||
| } | ||
| } | ||
|
|
||
| /// Returns a glyph, or a replacement character if no corresponding glyph exists | ||
| fn glyph_or_replacement(&'a self, c: char) -> DisplayBdfGlyph<'a> { | ||
| self.lookup(c).unwrap_or(self.replacement_glyph()) | ||
| } | ||
| } | ||
|
|
||
| impl<'a> ProportionalFont<'a> for BdfFont<'a> { | ||
| fn metrics(&self) -> Metrics { | ||
| Metrics { | ||
| ascent: self.ascent, | ||
| descent: self.descent, | ||
| line_height: self.ascent + self.descent, | ||
| } | ||
| } | ||
|
|
||
| fn replacement_glyph(&'a self) -> DisplayBdfGlyph<'a> { | ||
| self.glyphs[self.replacement_character].into_glyph(&self) | ||
| } | ||
|
|
||
| fn lookup(&self, c: char) -> Option<DisplayBdfGlyph<'_>> { | ||
| if let Some(&g) = self.glyphs.iter().find(|g| g.character == c) { | ||
| Some(g.into_glyph(self)) | ||
| } else { | ||
| None | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// A generalized text style for proportional fonts | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
| pub struct ProportionalTextStyle<'a, F: ProportionalFont<'a>, C: PixelColor> { | ||
| font: &'a F, | ||
| color: C, | ||
| } | ||
|
|
||
| impl<'a, F: ProportionalFont<'a>, C: PixelColor> ProportionalTextStyle<'a, F, C> { | ||
| /// Creates a new text style | ||
| pub fn new(font: &'a F, color: C) -> Self { | ||
| Self { font, color } | ||
| } | ||
| } | ||
|
|
||
| impl<'a, C: PixelColor, F: ProportionalFont<'a>> CharacterStyle | ||
| for ProportionalTextStyle<'a, F, C> | ||
| { | ||
| type Color = C; | ||
|
|
||
| fn set_text_color(&mut self, text_color: Option<C>) { | ||
| // TODO: support transparent text | ||
| if let Some(color) = text_color { | ||
| self.color = color; | ||
| } | ||
| } | ||
|
|
||
| // TODO: implement additional methods | ||
| } | ||
|
|
||
| impl<'a, C: PixelColor, F: ProportionalFont<'a>> TextRenderer for ProportionalTextStyle<'a, F, C> { | ||
| type Color = C; | ||
|
|
||
| fn draw_string<D>( | ||
| &self, | ||
| text: &str, | ||
| position: Point, | ||
| baseline: Baseline, | ||
| target: &mut D, | ||
| ) -> Result<Point, D::Error> | ||
| where | ||
| D: DrawTarget<Color = Self::Color>, | ||
| { | ||
| let mut position = position + Point::new(0, self.font.baseline_offset(baseline)); | ||
|
|
||
| for c in text.chars() { | ||
| let glyph = self.font.glyph_or_replacement(c); | ||
|
|
||
| glyph.draw(position, self.color, target)?; | ||
|
|
||
| position.x += glyph.device_width as i32; | ||
| } | ||
|
|
||
| Ok(position) | ||
| } | ||
|
|
||
| fn draw_whitespace<D>( | ||
| &self, | ||
| width: u32, | ||
| position: Point, | ||
| baseline: Baseline, | ||
| _target: &mut D, | ||
| ) -> Result<Point, D::Error> | ||
| where | ||
| D: DrawTarget<Color = Self::Color>, | ||
| { | ||
| let position = position + Point::new(0, self.font.baseline_offset(baseline)); | ||
|
|
||
| Ok(position + Size::new(width, 0)) | ||
| } | ||
|
|
||
| fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics { | ||
| let position = position + Point::new(0, self.font.baseline_offset(baseline)); | ||
|
|
||
| let dx = text | ||
| .chars() | ||
| .map(|c| self.font.glyph_or_replacement(c).device_width) | ||
| .sum(); | ||
|
|
||
| // TODO: calculate correct bounding box | ||
| let bounding_box = Rectangle::new( | ||
| position - Size::new(0, self.font.metrics().ascent.saturating_sub(1)), | ||
| Size::new(dx, self.font.metrics().line_height), | ||
| ); | ||
|
|
||
| TextMetrics { | ||
| bounding_box, | ||
| next_position: position + Size::new(dx, 0), | ||
| } | ||
| } | ||
|
|
||
| fn line_height(&self) -> u32 { | ||
| self.font.metrics().line_height | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| use crate::{DisplayBdfGlyph, ProportionalFont, ProportionalTextStyle}; | ||
| use embedded_graphics::{prelude::*, primitives::Rectangle}; | ||
|
|
||
| /// * Header (12 Bytes): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In case this format is also used to load a font at runtime it would be a good idea to add some validation that it is a valid font file. A magic value, a version, and the file size should be enough. |
||
| /// - Ascent (pixels, u16 BE) | ||
| /// - Descent (pixels, u16 BE) | ||
| /// - Replacement Character (index into character table, u32 BE) | ||
| /// - Character Table Length (entries, u32 BE) | ||
| /// | ||
| /// * Glyph Table (17 Bytes Per Entry): | ||
| /// - corresponding codepoint (u32 BE) | ||
| /// - top_left.x (i16 BE) | ||
| /// - top_left.y (i16 BE) | ||
| /// - size.width (u16 BE) | ||
| /// - size.height (u16 BE) | ||
| /// - device_width (pixels, u8) | ||
| /// - data index (bytes from start of data, u32 BE) | ||
| /// | ||
| /// Font bitmap data is stored afterwards | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
| pub struct SerializedBdfFont<'a> { | ||
| /// The raw u8 data of the serialized font | ||
| pub data: &'a [u8], | ||
| } | ||
| impl<'a> SerializedBdfFont<'a> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The deserialization code contains too many magical values and it shouldn't panic in case of malformed or truncated input data. As a first step I would add some constants and getters, like: const HEADER_SIZE: usize = 12;
const GLYPH_SIZE: usize = 17;
impl<'a> SerializedBdfFont<'a> {
...
fn bitmap_data(&self) -> &[u8] {
&self.data[HEADER_SIZE + self.character_count() * GLYPH_SIZE..]
}
fn glyph_data(&self, index: usize) -> Option<&[u8; GLYPH_SIZE]> {
let offset = HEADER_SIZE + index * GLYPH_SIZE;
self.data
.get(offset..offset + GLYPH_SIZE)
.map(|slice| slice.try_into().unwrap())
}
fn character_table(&self, index: usize) -> Option<DisplayBdfGlyph<'_>> {
let glyph = self.glyph_data(index)?;
let corresponding_character = char::from_u32(u32::from_be_bytes(glyph[0..4].try_into().unwrap()))?;
let top_left_x = i16::from_be_bytes(glyph[4..6].try_into().unwrap());
...
}
...
} |
||
| /// Returns the length of the glyph table | ||
| pub const fn character_count(self) -> u32 { | ||
| u32::from_be_bytes([self.data[8], self.data[9], self.data[10], self.data[11]]) | ||
| } | ||
|
|
||
| /// Returns the offset of the data block | ||
| fn data_index(self) -> usize { | ||
| 12 + (self.character_count() * 17) as usize | ||
| } | ||
|
|
||
| /// Returns a BdfGlyph in the glyph table | ||
| pub fn character_table(self, idx: u32) -> DisplayBdfGlyph<'a> { | ||
| let offset = 12 + (idx * 17) as usize; | ||
| let corresponding_character = char::from_u32(u32::from_be_bytes([ | ||
| self.data[offset], | ||
| self.data[offset + 1], | ||
| self.data[offset + 2], | ||
| self.data[offset + 3], | ||
| ])); | ||
| let top_left_x = i16::from_be_bytes([self.data[offset + 4], self.data[offset + 5]]); | ||
| let top_left_y = i16::from_be_bytes([self.data[offset + 6], self.data[offset + 7]]); | ||
| let width = u16::from_be_bytes([self.data[offset + 8], self.data[offset + 9]]); | ||
| let height = u16::from_be_bytes([self.data[offset + 10], self.data[offset + 11]]); | ||
| let kerning = self.data[offset + 12]; | ||
| let data_index = u32::from_be_bytes([ | ||
| self.data[offset + 13], | ||
| self.data[offset + 14], | ||
| self.data[offset + 15], | ||
| self.data[offset + 16], | ||
| ]); | ||
|
|
||
| DisplayBdfGlyph { | ||
| character: corresponding_character.unwrap(), | ||
| bounding_box: Rectangle { | ||
| top_left: Point { | ||
| x: top_left_x as i32, | ||
| y: top_left_y as i32, | ||
| }, | ||
| size: Size { | ||
| width: width as u32, | ||
| height: height as u32, | ||
| }, | ||
| }, | ||
| device_width: kerning as u32, | ||
| bitmap_data: &self.data[(self.data_index() + data_index as usize)..], | ||
| } | ||
|
iriswebb marked this conversation as resolved.
Outdated
|
||
| } | ||
| } | ||
| impl<'a> ProportionalFont<'a> for SerializedBdfFont<'a> { | ||
| fn metrics(&self) -> crate::proportional::Metrics { | ||
| crate::proportional::Metrics { | ||
| ascent: u16::from_be_bytes([self.data[0], self.data[1]]) as u32, | ||
| descent: u16::from_be_bytes([self.data[2], self.data[3]]) as u32, | ||
| line_height: (u16::from_be_bytes([self.data[0], self.data[1]]) | ||
| + u16::from_be_bytes([self.data[2], self.data[3]])) as u32, | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See note about |
||
| } | ||
|
|
||
| fn replacement_glyph(&self) -> DisplayBdfGlyph<'_> { | ||
| let rpos = u32::from_be_bytes([self.data[4], self.data[5], self.data[6], self.data[7]]); | ||
| self.character_table(rpos) | ||
| } | ||
|
|
||
| fn lookup(&self, c: char) -> Option<DisplayBdfGlyph<'_>> { | ||
| for i in 0..self.character_count() { | ||
| let tested_character = self.character_table(i); | ||
| if self.character_table(i).character == c { | ||
| return Some(tested_character); | ||
| } | ||
| } | ||
|
|
||
| None | ||
| } | ||
| } | ||
|
|
||
| /// Stylized serialized BDF text | ||
| pub type SerializedBdfTextStyle<'a, C> = ProportionalTextStyle<'a, SerializedBdfFont<'a>, C>; | ||
Uh oh!
There was an error while loading. Please reload this page.