completed basic functions

This commit is contained in:
2026-08-01 18:23:06 +00:00
commit 4baae18dd6
8 changed files with 611 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

72
Cargo.lock generated Normal file
View File

@@ -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"

7
Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "bdf-tools"
version = "0.1.0"
edition = "2024"
[dependencies]
strum = { version = "0.28.0", features = ["derive", "strum_macros"] }

1
links.txt Normal file
View File

@@ -0,0 +1 @@
https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/XLFD/xlfd.html

42
src/glyph.rs Normal file
View File

@@ -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<Point>,
pub(crate) dwidth1: Option<Point>,
pub(crate) vvector: Option<Point>,
pub(crate) bbx: GlyphBBX,
pub(crate) attributes: Option<[char;4]>,
pub(crate) bitmap: Vec<u8>,
}
pub enum GlyphEncoding {
Standard(i32),
NonStandard(Option<i32>),
}
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<u8>,
}

239
src/lib.rs Normal file
View File

@@ -0,0 +1,239 @@
use crate::properties::Properties;
mod glyph;
mod properties;
mod parser;
pub struct Font {
start_font: f32,
content_version: Option<i32>,
font: String,
size: Size,
font_bounding_box: BoundBox,
metrics_set: Option<i32>,
scalable_width: Option<Point>,
device_width: Option<Point>,
scalable_width_1: Option<Point>,
device_width_1: Option<Point>,
v_vector: Option<Point>,
properties: properties::Properties,
chars: u32,
glyphs: Vec<glyph::Glyph>
}
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<f32>,
content_version: Option<i32>,
font: Option<String>,
size: Option<Size>,
font_bounding_box: Option<BoundBox>,
metrics_set: Option<i32>,
scalable_width: Option<Point>,
device_width: Option<Point>,
scalable_width_1: Option<Point>,
device_width_1: Option<Point>,
v_vector: Option<Point>,
properties: properties::Properties,
chars: Option<u32>,
glyphs: Vec<glyph::Glyph>
}
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<Font, FontError> {
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() {
}
}

222
src/parser.rs Normal file
View File

@@ -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<crate::Font, ParseError> {
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::<f32>() {
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::<i32>() {
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::<i32>().map_err(|_| ParseError::SizeInvalid)?;
let x_res = elements.next().ok_or(ParseError::SizeInvalid)?.parse::<i32>().map_err(|_| ParseError::SizeInvalid)?;
let y_res = elements.next().ok_or(ParseError::SizeInvalid)?.parse::<i32>().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::<i32>().map_err(|_| ParseError::SizeInvalid)?;
let y_size = elements.next().ok_or(ParseError::SizeInvalid)?.parse::<i32>().map_err(|_| ParseError::SizeInvalid)?;
let x_offset = elements.next().ok_or(ParseError::SizeInvalid)?.parse::<i32>().map_err(|_| ParseError::SizeInvalid)?;
let y_offset = elements.next().ok_or(ParseError::SizeInvalid)?.parse::<i32>().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::<i32>().map_err(|_| ParseError::MetricsSetInvalid)?);
},
"SWIDTH" => {
let mut elements = keyword.1.split_ascii_whitespace();
let x_res = elements.next().ok_or(ParseError::SWidthInvalid)?.parse::<i32>().map_err(|_| ParseError::SWidthInvalid)?;
let y_res = elements.next().ok_or(ParseError::SWidthInvalid)?.parse::<i32>().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::<i32>().map_err(|_| ParseError::DWidthInvalid)?;
let y_res = elements.next().ok_or(ParseError::DWidthInvalid)?.parse::<i32>().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::<i32>().map_err(|_| ParseError::SWidth1Invalid)?;
let y_res = elements.next().ok_or(ParseError::SWidth1Invalid)?.parse::<i32>().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::<i32>().map_err(|_| ParseError::DWidth1Invalid)?;
let y_res = elements.next().ok_or(ParseError::DWidth1Invalid)?.parse::<i32>().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::<i32>().map_err(|_| ParseError::VVEctorInvalid)?;
let y_res = elements.next().ok_or(ParseError::VVEctorInvalid)?.parse::<i32>().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::<u32>().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::<i32>() {
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,
}

27
src/properties.rs Normal file
View File

@@ -0,0 +1,27 @@
use std::collections::HashMap;
pub struct Properties {
pub(crate) properties: HashMap<String, Property>
}
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),
}
}
}