Initial commit
This commit is contained in:
commit
63c36ebe1c
2 changed files with 1651 additions and 0 deletions
11
skrivan-cli/Cargo.toml
Normal file
11
skrivan-cli/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "skrivan-cli"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
argp = "0.4.1"
|
||||
pixglyph = "0.6"
|
||||
png = "0.18"
|
||||
rustybuzz = "0.20"
|
||||
skrivan = { path = "../skrivan" }
|
||||
123
skrivan-cli/src/main.rs
Normal file
123
skrivan-cli/src/main.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use skrivan::{Atlas, Glyph};
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
io::Write,
|
||||
};
|
||||
|
||||
/// Render fonts to PNG altas and parsable char-to-coordinate map.
|
||||
///
|
||||
/// The input text file may contain multiple times the same characters,
|
||||
/// they will be rendered only once.
|
||||
/// Newlines and space characters are ignored.
|
||||
///
|
||||
/// Skrivañ, CopyLeft 2026 tuxmain (GNU AGPL v3)
|
||||
#[derive(argp::FromArgs)]
|
||||
struct Cli {
|
||||
/// Path to the font file (TTF, OTF, AAT)
|
||||
#[argp(positional)]
|
||||
font: String,
|
||||
/// Path to a text file containing the characters that will be rendered
|
||||
#[argp(positional)]
|
||||
text: String,
|
||||
/// Path to the output image file
|
||||
#[argp(positional)]
|
||||
image: String,
|
||||
/// Path to the output atlas file
|
||||
#[argp(positional)]
|
||||
atlas: String,
|
||||
/// Font size
|
||||
#[argp(positional)]
|
||||
size: f32,
|
||||
/// Maximum width of the image
|
||||
#[argp(option, short = 'w', default = "256")]
|
||||
width: u32,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli: Cli = argp::parse_args_or_exit(argp::DEFAULT);
|
||||
|
||||
let font_data = std::fs::read(cli.font).expect("Cannot read font file");
|
||||
let face = rustybuzz::ttf_parser::Face::parse(&font_data, 0).expect("Cannot parse font");
|
||||
|
||||
let mut charset = HashSet::new();
|
||||
let mut chars = HashMap::new();
|
||||
let mut buffer = rustybuzz::UnicodeBuffer::new();
|
||||
let text = std::fs::read_to_string(cli.text).expect("Cannot read text file");
|
||||
for (i, c) in text.char_indices() {
|
||||
if c.is_whitespace() {
|
||||
continue;
|
||||
}
|
||||
if charset.insert(c) {
|
||||
buffer.add(c, i as u32);
|
||||
chars.insert(i, c);
|
||||
}
|
||||
}
|
||||
|
||||
let shape = rustybuzz::shape(&rustybuzz::Face::from_face(face.clone()), &[], buffer);
|
||||
|
||||
let mut bitmaps = HashMap::new();
|
||||
let mut max_w = 0;
|
||||
let mut max_h = 0;
|
||||
for c in shape.glyph_infos() {
|
||||
if let Some(glyph) =
|
||||
pixglyph::Glyph::load(&face, rustybuzz::ttf_parser::GlyphId(c.glyph_id as u16))
|
||||
{
|
||||
let bitmap = glyph.rasterize(0., 0., cli.size);
|
||||
max_w = max_w.max(bitmap.width);
|
||||
max_h = max_h.max(bitmap.height);
|
||||
bitmaps.insert(chars[&(c.cluster as usize)], bitmap);
|
||||
dbg!(chars[&(c.cluster as usize)]);
|
||||
}
|
||||
}
|
||||
|
||||
let mut atlas = Atlas {
|
||||
charmap: HashMap::new(),
|
||||
};
|
||||
let image_width = cli.width as usize;
|
||||
let mut pixels = Vec::new();
|
||||
let mut x: u32 = 0;
|
||||
let mut y: u32 = 0;
|
||||
for (c, bitmap) in bitmaps {
|
||||
if x + bitmap.width >= image_width as _ {
|
||||
x = 0;
|
||||
y += max_h;
|
||||
}
|
||||
if pixels.len() <= image_width * y as usize {
|
||||
pixels.extend_from_slice(&vec![0; image_width * max_h as usize]);
|
||||
}
|
||||
atlas.charmap.insert(
|
||||
c,
|
||||
Glyph {
|
||||
x,
|
||||
y,
|
||||
w: bitmap.width,
|
||||
h: bitmap.height,
|
||||
offset_x: bitmap.left,
|
||||
offset_y: bitmap.top,
|
||||
},
|
||||
);
|
||||
for by in 0..bitmap.height {
|
||||
for bx in 0..bitmap.width {
|
||||
pixels[((y + by) * image_width as u32 + x + bx) as usize] =
|
||||
bitmap.coverage[(by * bitmap.width + bx) as usize];
|
||||
}
|
||||
}
|
||||
x += bitmap.width;
|
||||
}
|
||||
|
||||
let output = std::fs::File::create(cli.image).expect("Cannot open output image file");
|
||||
let writer = &mut std::io::BufWriter::new(output);
|
||||
let mut encoder = png::Encoder::new(
|
||||
writer,
|
||||
image_width as u32,
|
||||
(pixels.len() / image_width) as u32,
|
||||
);
|
||||
encoder.set_color(png::ColorType::Grayscale);
|
||||
let mut writer = encoder.write_header().unwrap();
|
||||
writer.write_image_data(&pixels).unwrap();
|
||||
writer.finish().unwrap();
|
||||
|
||||
let mut output = std::fs::File::create(cli.atlas).expect("Cannot open output atlas file");
|
||||
output.write_all(&atlas.serialize()).unwrap();
|
||||
}
|
||||
Loading…
Reference in a new issue