Initialize Minecraft text generator project with basic structure and CLI functionality

This commit is contained in:
2026-03-23 00:56:19 +01:00
commit 55d730d542
20 changed files with 900 additions and 0 deletions

1
.gitignore vendored Normal file
View File

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

440
Cargo.lock generated Normal file
View File

@@ -0,0 +1,440 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "ab_glyph"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
]
[[package]]
name = "ab_glyph_rasterizer"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys",
]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "exporters"
version = "0.1.0"
dependencies = [
"anyhow",
"lib",
"thiserror",
"tracing",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "lib"
version = "0.1.0"
dependencies = [
"ab_glyph",
"anyhow",
"thiserror",
"tracing",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "minecraft-text-generator"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"exporters",
"lib",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "owned_ttf_parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
dependencies = [
"ttf-parser",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[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 = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[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 = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

9
Cargo.toml Normal file
View File

@@ -0,0 +1,9 @@
[workspace]
members = ["crates/bin", "crates/exporters", "crates/lib"]
resolver = "2"
[workspace.dependencies]
anyhow = "1.0.102"
thiserror = "2.0.18"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }

4
README.md Normal file
View File

@@ -0,0 +1,4 @@
# Minecraft text generator
This little project is a tool that takes a string of text and generates minecraft structure that represents given text in a font of your choice. User can choose blocks to use (or define their own palette) or just use preset.
Exported structure can be in various formats, such as .schematic, .litematic or .nbt, or just .mcfunction file with setblock commands.

22
crates/bin/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "minecraft-text-generator"
version = "0.1.0"
edition = "2024"
default-run = "minecraft-text-generator"
[profile.release]
strip = true
lto = true
codegen-units = 1
opt-level = 3
[dependencies]
lib = { path = "../lib" }
exporters = { path = "../exporters" }
clap = { version = "4.6.0", features = ["derive"] }
anyhow = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }

64
crates/bin/src/cli.rs Normal file
View File

@@ -0,0 +1,64 @@
use std::{fs, path::PathBuf};
use clap::Parser;
use exporters::McFunctionExporter;
use lib::{StructureExporter, TextBuilder, TtfFont};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// The text you want to generate
#[arg(short, long)]
text: String,
/// Path to the .ttf font file
#[arg(short, long)]
font: PathBuf,
/// How many blocks deep the text should be
#[arg(short, long, default_value_t = 1)]
depth: u32,
/// The Minecraft block ID to use for the text body
#[arg(short, long, default_value = "minecraft:quartz_block")]
block: String,
/// Output file path (without extension)
#[arg(short, long, default_value = "output")]
out: PathBuf,
/// Height of the text in blocks
#[arg(long, default_value_t = 16.0)]
size: f32,
}
pub fn run() -> anyhow::Result<()> {
let cli = Cli::parse();
tracing::info!("loading font from: {:?}", cli.font);
let font_bytes = fs::read(&cli.font)?;
let font = TtfFont::from_bytes(&font_bytes, cli.size)
.map_err(|e| anyhow::anyhow!(e))?;
tracing::info!("generating voxel grid for text: '{}'", cli.text);
let builder = TextBuilder::new(&font).with_depth(cli.depth);
let grid = builder.generate(&cli.text);
tracing::info!(
"grid generated: {}x{}x{}",
grid.width, grid.height, grid.depth
);
let exporter = McFunctionExporter::new(&cli.block, "minecraft:obsidian");
let output_bytes = exporter.export(&grid)?;
let mut out_path = cli.out.clone();
out_path.set_extension(exporter.file_extension());
fs::write(&out_path, output_bytes)?;
tracing::info!("saved to: {:?}", out_path);
Ok(())
}

1
crates/bin/src/lib.rs Normal file
View File

@@ -0,0 +1 @@
pub mod cli;

10
crates/bin/src/main.rs Normal file
View File

@@ -0,0 +1,10 @@
fn main() {
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
minecraft_text_generator::cli::run().unwrap_or_else(|e| {
tracing::error!("{e}");
std::process::exit(1);
});
}

View File

@@ -0,0 +1,10 @@
[package]
name = "exporters"
version = "0.1.0"
edition = "2024"
[dependencies]
lib = { path = "../lib" }
anyhow = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }

View File

@@ -0,0 +1,3 @@
mod mcfunction;
pub use mcfunction::McFunctionExporter;

View File

@@ -0,0 +1,50 @@
use std::collections::HashMap;
use lib::{StructureExporter, VoxelType};
pub struct McFunctionExporter {
palette: HashMap<VoxelType, String>,
}
impl McFunctionExporter {
pub fn new(body_block: &str, outline_block: &str) -> Self {
let mut palette = HashMap::new();
palette.insert(VoxelType::Body, body_block.to_string());
palette.insert(VoxelType::Outline, outline_block.to_string());
Self { palette }
}
}
impl StructureExporter for McFunctionExporter {
fn export(&self, grid: &lib::VoxelGrid) -> anyhow::Result<Vec<u8>> {
let mut output = String::new();
output.push_str(&format!("# Generated by Minecraft Text Builder\n"));
output.push_str(&format!(
"# Dimensions: {}x{}x{}\n\n",
grid.width, grid.height, grid.depth
));
for z in 0..grid.depth {
for y in 0..grid.height {
for x in 0..grid.width {
if let Some(voxel_type) = grid.get(x, y, z) {
if let Some(block_id) = self.palette.get(&voxel_type) {
// ~x ~y ~z generates blocks relative to where the command is executed
let command = format!("setblock ~{} ~{} ~{} {}\n", x, y, z, block_id);
output.push_str(&command);
}
}
}
}
}
Ok(output.into_bytes())
}
fn file_extension(&self) -> &'static str {
"mcfunction"
}
}

10
crates/lib/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "lib"
version = "0.1.0"
edition = "2024"
[dependencies]
ab_glyph = "0.2.32"
anyhow = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }

91
crates/lib/src/engine.rs Normal file
View File

@@ -0,0 +1,91 @@
use crate::{font::FontProvider, grid::VoxelGrid, models::VoxelType};
pub struct GenerationOptions {
pub extrusion_depth: u32,
pub generate_outline: bool,
// Future options can be added here, such as:
// shadow, scale, italics, etc.
}
impl Default for GenerationOptions {
fn default() -> Self {
Self {
extrusion_depth: 1,
generate_outline: true,
}
}
}
pub struct TextBuilder<'a> {
font: &'a dyn FontProvider,
options: GenerationOptions,
}
impl<'a> TextBuilder<'a> {
pub fn new(font: &'a dyn FontProvider) -> Self {
Self {
font,
options: GenerationOptions::default(),
}
}
pub fn with_depth(mut self, depth: u32) -> Self {
self.options.extrusion_depth = depth.max(1);
self
}
pub fn with_outline(mut self, generate: bool) -> Self {
self.options.generate_outline = generate;
self
}
pub fn generate(&self, text: &str) -> VoxelGrid {
let mut total_width = 0;
let mut max_height = 0;
let mut glyphs_to_render = Vec::new();
for c in text.chars() {
if let Some(glyph) = self.font.get_glyph(c) {
total_width += glyph.width + self.font.letter_spacing();
max_height = max_height.max(glyph.height);
glyphs_to_render.push(glyph);
}
}
let padding = if self.options.generate_outline { 2 } else { 0 };
let mut grid = VoxelGrid::new(
total_width + padding,
max_height + padding,
self.options.extrusion_depth,
);
let mut current_x = if self.options.generate_outline { 1 } else { 0 };
let base_y = if self.options.generate_outline { 1 } else { 0 };
for glyph in glyphs_to_render {
for gy in 0..glyph.height {
for gx in 0..glyph.width {
let glyph_index = (gy * glyph.width + gx) as usize;
if glyph.data[glyph_index] {
let world_x = current_x + gx;
let world_y = base_y + (glyph.height - 1 - gy); // Flip vertically
for z in 0..self.options.extrusion_depth {
if let Err(e) = grid.set(world_x, world_y, z, VoxelType::Body) {
tracing::warn!("failed to set voxel: {}", e);
}
}
}
}
}
current_x += glyph.width + self.font.letter_spacing();
}
// TODO: Add outline generation logic here if self.options.generate_outline is true.
grid
}
}

13
crates/lib/src/error.rs Normal file
View File

@@ -0,0 +1,13 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum VoxelError {
#[error("coordinates ({x}, {y}, {z}) out of bounds")]
OutOfBounds { x: u32, y: u32, z: u32 },
}
#[derive(Debug, Error)]
pub enum FontError {
#[error("failed to parse TTF font data: invalid format")]
InvalidTtf,
}

14
crates/lib/src/font.rs Normal file
View File

@@ -0,0 +1,14 @@
/// Represents a single 2D character mapped to a boolean grid.
/// `true` means a block exists, `false` means empty space.
pub struct Glyph {
pub width: u32,
pub height: u32,
pub data: Vec<bool>,
}
pub trait FontProvider {
fn get_glyph(&self, character: char) -> Option<Glyph>;
fn letter_spacing(&self) -> u32 {
1
}
}

View File

@@ -0,0 +1 @@
pub mod ttf_font;

View File

@@ -0,0 +1,82 @@
use ab_glyph::{Font, FontVec, PxScale, ScaleFont};
use crate::{error::FontError, font::FontProvider};
pub struct TtfFont {
font: FontVec,
/// The height of the text in Minecraft blocks (pixels)
scale: PxScale,
/// How "thick" a pixel needs to be to become a block (0.0 to 1.0)
/// Lower threshold = thicker text, Higher threshold = thinner text
threshold: f32,
}
impl TtfFont {
pub fn from_bytes(font_data: &[u8], block_height: f32) -> Result<Self, FontError> {
let font = FontVec::try_from_vec(font_data.to_vec())
.map_err(|_| FontError::InvalidTtf)?;
Ok(Self {
font,
scale: PxScale::from(block_height),
threshold: 0.5, // Default threshold, can be adjusted
})
}
pub fn with_threshold(mut self, threshold: f32) -> Self {
self.threshold = threshold.clamp(0.0, 0.99);
self
}
}
impl FontProvider for TtfFont {
fn get_glyph(&self, character: char) -> Option<crate::font::Glyph> {
let scaled_font = self.font.as_scaled(self.scale);
let glyph_id = scaled_font.glyph_id(character);
if glyph_id.0 == 0 {
return None; // Character not found in font
}
let q_glyph = glyph_id.with_scale_and_position(self.scale, ab_glyph::point(0.0, 0.0));
if let Some(outlined) = scaled_font.outline_glyph(q_glyph) {
let bounds = outlined.px_bounds();
let width = bounds.width().ceil() as u32;
let height = bounds.height().ceil() as u32;
let mut data = vec![false; (width * height) as usize];
outlined.draw(|x, y, coverage| {
if coverage >= self.threshold {
if x < width && y < height {
let idx = (y * width + x) as usize;
data[idx] = true;
}
}
});
Some(crate::font::Glyph {
width,
height,
data,
})
} else {
if character.is_whitespace() {
let width = scaled_font.h_advance(glyph_id).ceil() as u32;
Some(crate::font::Glyph {
width,
height: self.scale.y as u32,
data: vec![false; (width * self.scale.y as u32) as usize],
})
} else {
None
}
}
}
fn letter_spacing(&self) -> u32 {
1
}
}

44
crates/lib/src/grid.rs Normal file
View File

@@ -0,0 +1,44 @@
use crate::{error::VoxelError, models::VoxelType};
pub struct VoxelGrid {
pub width: u32,
pub height: u32,
pub depth: u32,
data: Vec<Option<VoxelType>>,
}
impl VoxelGrid {
pub fn new(width: u32, height: u32, depth: u32) -> Self {
let capacity = (width * height * depth) as usize;
Self {
width,
height,
depth,
data: vec![None; capacity],
}
}
#[inline(always)]
fn get_index(&self, x: u32, y: u32, z: u32) -> Option<usize> {
if x >= self.width || y >= self.height || z >= self.depth {
return None;
}
Some((x + self.width * (y + self.height * z)) as usize)
}
pub fn set(&mut self, x: u32, y: u32, z: u32, voxel: VoxelType) -> Result<(), VoxelError> {
match self.get_index(x, y, z) {
Some(index) => {
self.data[index] = Some(voxel);
Ok(())
}
None => Err(VoxelError::OutOfBounds { x, y, z }),
}
}
pub fn get(&self, x: u32, y: u32, z: u32) -> Option<VoxelType> {
self.get_index(x, y, z).and_then(|index| self.data[index])
}
}

18
crates/lib/src/lib.rs Normal file
View File

@@ -0,0 +1,18 @@
mod engine;
mod error;
mod font;
mod fonts;
mod grid;
mod models;
pub use engine::{GenerationOptions, TextBuilder};
pub use error::{FontError, VoxelError};
pub use font::FontProvider;
pub use fonts::ttf_font::TtfFont;
pub use grid::VoxelGrid;
pub use models::VoxelType;
pub trait StructureExporter {
fn export(&self, grid: &VoxelGrid) -> anyhow::Result<Vec<u8>>;
fn file_extension(&self) -> &'static str;
}

13
crates/lib/src/models.rs Normal file
View File

@@ -0,0 +1,13 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VoxelType {
Body,
Outline,
Shadow,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Point3D {
pub x: u32,
pub y: u32,
pub z: u32,
}