From 4baae18dd6624d4c5116e5b3cc95dfd3fece5a65 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 1 Aug 2026 18:23:06 +0000 Subject: [PATCH] completed basic functions --- .gitignore | 1 + Cargo.lock | 72 ++++++++++++++ Cargo.toml | 7 ++ links.txt | 1 + src/glyph.rs | 42 ++++++++ src/lib.rs | 239 ++++++++++++++++++++++++++++++++++++++++++++++ src/parser.rs | 222 ++++++++++++++++++++++++++++++++++++++++++ src/properties.rs | 27 ++++++ 8 files changed, 611 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 links.txt create mode 100644 src/glyph.rs create mode 100644 src/lib.rs create mode 100644 src/parser.rs create mode 100644 src/properties.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..607dd42 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bdf-tools" +version = "0.1.0" +dependencies = [ + "strum", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..02760ea --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "bdf-tools" +version = "0.1.0" +edition = "2024" + +[dependencies] +strum = { version = "0.28.0", features = ["derive", "strum_macros"] } diff --git a/links.txt b/links.txt new file mode 100644 index 0000000..fba1525 --- /dev/null +++ b/links.txt @@ -0,0 +1 @@ +https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/XLFD/xlfd.html \ No newline at end of file diff --git a/src/glyph.rs b/src/glyph.rs new file mode 100644 index 0000000..5d6dd8b --- /dev/null +++ b/src/glyph.rs @@ -0,0 +1,42 @@ +use crate::Point; + +pub struct Glyph { + pub(crate) startchar: String, + pub(crate) encoding: GlyphEncoding, + pub(crate) swidth: Point, + pub(crate) dwidth: Point, + pub(crate) swidth1: Option, + pub(crate) dwidth1: Option, + pub(crate) vvector: Option, + pub(crate) bbx: GlyphBBX, + pub(crate) attributes: Option<[char;4]>, + pub(crate) bitmap: Vec, +} + +pub enum GlyphEncoding { + Standard(i32), + NonStandard(Option), +} + +impl std::fmt::Display for GlyphEncoding { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Standard(i) => write!(f, "{}", i), + Self::NonStandard(i) => match i { + Some(v) => write!(f, "-1 {}", v), + None => write!(f, "-1"), + } + } + } +} + +pub struct GlyphBBX { + pub(crate) x_width: i32, + pub(crate) y_width: i32, + pub(crate) x_offset: i32, + pub(crate) y_offset: i32, +} + +pub struct GlyphBitmap { + raw_bitmap: Vec, +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e5be874 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,239 @@ +use crate::properties::Properties; + +mod glyph; +mod properties; +mod parser; + +pub struct Font { + start_font: f32, + content_version: Option, + font: String, + size: Size, + font_bounding_box: BoundBox, + metrics_set: Option, + scalable_width: Option, + device_width: Option, + scalable_width_1: Option, + device_width_1: Option, + v_vector: Option, + properties: properties::Properties, + chars: u32, + glyphs: Vec +} + +impl Font { + pub fn to_string(&self) -> String { + let mut output = String::new(); + output.push_str(&format!("STARTFONT {}\n", self.start_font)); + + match self.content_version { + Some(cv) => output.push_str(&format!("CONTENTVERSION {}\n", cv)), + None => (), + } + + output.push_str(&format!("FONT {}\n", self.font)); + output.push_str(&format!("SIZE {} {} {}\n", self.size.point_size, self.size.resolution.x, self.size.resolution.y)); + output.push_str(&format!("FONTBOUNDINGBOX {} {} {} {}\n", self.font_bounding_box.size.x, self.font_bounding_box.size.y, self.font_bounding_box.offset.x, self.font_bounding_box.offset.y)); + + match self.metrics_set { + Some(ms) => output.push_str(&format!("METRICSSET {}\n", ms)), + None => (), + } + match &self.scalable_width { + Some(sw) => output.push_str(&format!("SWIDTH {} {}", sw.x, sw.y)), + None => (), + } + match &self.device_width { + Some(dw) => output.push_str(&format!("DWIDTH {} {}", dw.x, dw.y)), + None => (), + } + match &self.scalable_width_1 { + Some(sw) => output.push_str(&format!("SWIDTH1 {} {}", sw.x, sw.y)), + None => (), + } + match &self.device_width_1 { + Some(dw) => output.push_str(&format!("SWIDTH1 {} {}", dw.x, dw.y)), + None => (), + } + match &self.v_vector { + Some(vv) => output.push_str(&format!("SWIDTH1 {} {}", vv.x, vv.y)), + None => (), + } + + output.push_str(&format!("STARTPROPERTIES {}\n", self.properties.properties.len())); + for i in self.properties.properties.iter() { + output.push_str(&format!("{} {}\n", i.0, i.1)); + } + output.push_str("ENDROPERTIES\n"); + output.push_str(&format!("CHARS {}\n", self.chars)); + + for i in self.glyphs.iter() { + output.push_str(&format!("STARTCHAR {}\n", i.startchar)); + output.push_str(&format!("ENCODING {}\n", i.encoding)); + output.push_str(&format!("SWIDTH {} {}\n", i.swidth.x, i.swidth.y)); + output.push_str(&format!("DWIDTH {} {}\n", i.dwidth.x, i.dwidth.y)); + + match &i.swidth1 { + Some(sw) => output.push_str(&format!("SWIDTH1 {} {}\n", sw.x, sw.y)), + None => (), + } + match &i.dwidth1 { + Some(dw) => output.push_str(&format!("DWIDTH1 {} {}\n", dw.x, dw.y)), + None => (), + } + match &i.vvector { + Some(vv) => output.push_str(&format!("VVECTOR {} {}\n", vv.x, vv.y)), + None => (), + } + output.push_str(&format!("BBX {} {} {} {}\n", i.bbx.x_width, i.bbx.y_width, i.bbx.x_offset, i.bbx.y_offset)); + + match &i.attributes { + Some(at) => { + output.push_str("ATTRIBUTES"); + + for j in at { + output.push_str(&format!(" {}", j)) + } + output.push('\n'); + }, + None => (), + } + + output.push_str("BITMAP\n"); + + for row in i.bitmap.chunks(i.bbx.x_width as usize) { + let mut formatted = String::new(); + for item in row { + formatted.push_str(&format!("{:02x}", item)); + } + output.push_str(&format!("{}\n", formatted)); + } + output.push_str("ENDCHAR"); + } + + output.push_str("ENDFONT"); + output + } +} + +pub struct FontBuilder { + start_font: Option, + content_version: Option, + font: Option, + size: Option, + font_bounding_box: Option, + metrics_set: Option, + scalable_width: Option, + device_width: Option, + scalable_width_1: Option, + device_width_1: Option, + v_vector: Option, + properties: properties::Properties, + chars: Option, + glyphs: Vec +} + +impl FontBuilder { + pub fn new() -> Self { + Self { + start_font: None, + content_version: None, + font: None, + size: None, + font_bounding_box: None, + metrics_set: None, + scalable_width: None, + device_width: None, + scalable_width_1: None, + device_width_1: None, + v_vector: None, + properties: Properties::new(), + chars: None, + glyphs: Vec::new(), + } + } + + pub fn build(self) -> Result { + Ok(Font { + start_font: self.start_font.ok_or(FontError{})?, + content_version: self.content_version, + font: self.font.ok_or(FontError{})?, + size: self.size.ok_or(FontError{})?, + font_bounding_box: self.font_bounding_box.ok_or(FontError{})?, + metrics_set: self.metrics_set, + scalable_width: self.scalable_width, + device_width: self.device_width, + scalable_width_1: self.scalable_width_1, + device_width_1: self.device_width_1, + v_vector: self.v_vector, + properties: self.properties, + chars: self.chars.ok_or(FontError{})?, + glyphs: self.glyphs, + }) + } + + pub fn start_font(mut self, font_version: f32) -> Self { + self.start_font = Some(font_version); + self + } + + pub fn content_version(mut self, content_version: i32) -> Self { + self.content_version = Some(content_version); + self + } + + pub fn font(mut self, font: String) -> Self { + self.font = Some(font); + self + } + + pub fn size(mut self, size: Size) -> Self { + self.size = Some(size); + self + } + + pub fn bounding_box(mut self, bounding_box: BoundBox) -> Self { + self.font_bounding_box = Some(bounding_box); + self + } + + pub fn metrics_set(mut self, metrics_set: i32) -> Self { + self.metrics_set = Some(metrics_set); + self + } + +} + +pub struct FontError { + +} + +pub struct Size { + point_size: i32, + resolution: Point, +} + +pub struct BoundBox { + size: Point, + offset: Point, +} + +pub struct Point { + x: i32, + y: i32, +} + +pub fn parse_font(input: &str ) -> Font { + let mut parser = parser::Parser::new(); + + parser.parse(input).unwrap() +} + +#[cfg(test)] +mod tests { + //use super::*; + + #[test] + fn it_works() { + } +} \ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..13e5f6b --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,222 @@ +use crate::properties::Property; + +pub(crate) struct Parser { + state: ParserState, + parsed_font: crate::FontBuilder, +} + +impl Parser { + pub(crate) fn new() -> Self { + Self { + state: ParserState::Font, + parsed_font: crate::FontBuilder::new() + } + } + + pub(crate) fn parse(&mut self, input: &str) -> Result { + for line in input.lines() { + let keyword = line.split_once(' ').unwrap(); + match self.state { + ParserState::Font => { + match keyword.0.to_ascii_uppercase().as_str() { + "STARTFONT" => { + if self.parsed_font.start_font.is_some() { + return Err(ParseError::MultipleStartFont) + } + self.parsed_font.start_font = match keyword.1.parse::() { + Ok(val) => Some(val), + Err(_) => return Err(ParseError::BDFVersionInvalid) + } + }, + "COMMENT" => { + + }, + "CONTENTVERSION" => { + if self.parsed_font.content_version.is_some() { + return Err(ParseError::MultipleContentVersion) + } + self.parsed_font.content_version = match keyword.1.parse::() { + Ok(val) => Some(val), + Err(_) => return Err(ParseError::ContentVersionInvalid) + } + }, + "FONT" => { + if self.parsed_font.font.is_some() { + return Err(ParseError::MultipleFont) + } + self.parsed_font.font = Some(keyword.1.to_string()); + }, + "SIZE" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let point_size = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + let x_res = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + let y_res = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + + let size = crate::Size{ + point_size, + resolution: crate::Point { + x: x_res, + y: y_res + } + }; + + self.parsed_font.size = Some(size); + }, + "FONTBOUNDINGBOX" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let x_size = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + let y_size = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + let x_offset = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + let y_offset = elements.next().ok_or(ParseError::SizeInvalid)?.parse::().map_err(|_| ParseError::SizeInvalid)?; + + let bounding_box = crate::BoundBox{ + size: crate::Point { + x: x_size, + y: y_size, + }, + offset: crate::Point { + x: x_offset, + y: y_offset, + } + }; + + self.parsed_font.font_bounding_box = Some(bounding_box); + }, + "METRICSSET" => { + self.parsed_font.metrics_set = Some(keyword.1.parse::().map_err(|_| ParseError::MetricsSetInvalid)?); + }, + "SWIDTH" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let x_res = elements.next().ok_or(ParseError::SWidthInvalid)?.parse::().map_err(|_| ParseError::SWidthInvalid)?; + let y_res = elements.next().ok_or(ParseError::SWidthInvalid)?.parse::().map_err(|_| ParseError::SWidthInvalid)?; + + let swidth = crate::Point { + x: x_res, + y: y_res + }; + + self.parsed_font.scalable_width = Some(swidth); + }, + "DWIDTH" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let x_res = elements.next().ok_or(ParseError::DWidthInvalid)?.parse::().map_err(|_| ParseError::DWidthInvalid)?; + let y_res = elements.next().ok_or(ParseError::DWidthInvalid)?.parse::().map_err(|_| ParseError::DWidthInvalid)?; + + let dwidth = crate::Point { + x: x_res, + y: y_res + }; + + self.parsed_font.device_width = Some(dwidth); + }, + "SWIDTH1" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let x_res = elements.next().ok_or(ParseError::SWidth1Invalid)?.parse::().map_err(|_| ParseError::SWidth1Invalid)?; + let y_res = elements.next().ok_or(ParseError::SWidth1Invalid)?.parse::().map_err(|_| ParseError::SWidth1Invalid)?; + + let swidth = crate::Point { + x: x_res, + y: y_res + }; + + self.parsed_font.scalable_width = Some(swidth); + }, + "DWIDTH1" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let x_res = elements.next().ok_or(ParseError::DWidth1Invalid)?.parse::().map_err(|_| ParseError::DWidth1Invalid)?; + let y_res = elements.next().ok_or(ParseError::DWidth1Invalid)?.parse::().map_err(|_| ParseError::DWidth1Invalid)?; + + let dwidth = crate::Point { + x: x_res, + y: y_res + }; + + self.parsed_font.device_width = Some(dwidth); + }, + "VVECTOR" => { + let mut elements = keyword.1.split_ascii_whitespace(); + + let x_res = elements.next().ok_or(ParseError::VVEctorInvalid)?.parse::().map_err(|_| ParseError::VVEctorInvalid)?; + let y_res = elements.next().ok_or(ParseError::VVEctorInvalid)?.parse::().map_err(|_| ParseError::VVEctorInvalid)?; + + let vv = crate::Point { + x: x_res, + y: y_res + }; + + self.parsed_font.device_width = Some(vv); + }, + "STARTPROPERTIES" => { + self.state = ParserState::Properties + }, + "CHARS" => { + self.state = ParserState::Glyph; + let chars = keyword.1.parse::().map_err(|_| ParseError::CharsInvalid)?; + self.parsed_font.chars = Some(chars); + }, + _ => { + return Err(ParseError::InvalidKeyword) + } + } + }, + + ParserState::Properties => { + if keyword.0.to_ascii_uppercase().as_str() == "ENDPROPERTIES" { + self.state = ParserState::Font; + } else { + let property = match keyword.1.parse::() { + Ok(val) => Property::Integer(val), + Err(_) => Property::Text(keyword.1.to_owned()), + }; + + self.parsed_font.properties.properties.insert(keyword.0.to_string(), property); + } + }, + + ParserState::Glyph => { + if keyword.0.to_ascii_uppercase().as_str() == "ENDCHAR" { + self.state = ParserState::Font; + + } else { + todo!("Parse Glyph Contents") + } + } + } + } + + todo!("Parse font to output") + } +} + +enum ParserState { + Font, + Properties, + Glyph, +} + +#[derive(Debug)] +pub enum ParseError { + InvalidKeyword, + BDFVersionInvalid, + ContentVersionInvalid, + SizeInvalid, + MetricsSetInvalid, + SWidthInvalid, + DWidthInvalid, + SWidth1Invalid, + DWidth1Invalid, + VVEctorInvalid, + CharsInvalid, + + MultipleStartFont, + MultipleContentVersion, + MultipleFont, + +} + diff --git a/src/properties.rs b/src/properties.rs new file mode 100644 index 0000000..f6c6adf --- /dev/null +++ b/src/properties.rs @@ -0,0 +1,27 @@ +use std::collections::HashMap; + +pub struct Properties { + pub(crate) properties: HashMap +} + +impl Properties { + pub(crate) fn new() -> Self { + Self { + properties: HashMap::new() + } + } +} + +pub enum Property { + Integer(i32), + Text(String), +} + +impl std::fmt::Display for Property { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Integer(i) => write!(f, "{}", i), + Self::Text(t) => write!(f, "{}", t), + } + } +} \ No newline at end of file