pub const PRESETS: [&str; 2] = ["pixel", "bold"];
const PIXEL: i16 = 128;
const UNITS_PER_EM: u16 = 1024;
#[rustfmt::skip]
const GLYPHS: [[u8; 9]; 95] = [
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00], [0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x0A, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x0A, 0x00, 0x00], [0x04, 0x0F, 0x14, 0x0E, 0x05, 0x1E, 0x04, 0x00, 0x00], [0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03, 0x00, 0x00], [0x0C, 0x12, 0x14, 0x08, 0x15, 0x12, 0x0D, 0x00, 0x00], [0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02, 0x00, 0x00], [0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00, 0x00], [0x00, 0x04, 0x15, 0x0E, 0x15, 0x04, 0x00, 0x00, 0x00], [0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00, 0x00, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x04, 0x08, 0x00], [0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00], [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x00, 0x00, 0x00], [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E, 0x00, 0x00], [0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E, 0x00, 0x00], [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F, 0x00, 0x00], [0x1F, 0x02, 0x04, 0x02, 0x01, 0x11, 0x0E, 0x00, 0x00], [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02, 0x00, 0x00], [0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E, 0x00, 0x00], [0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E, 0x00, 0x00], [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, 0x00, 0x00], [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E, 0x00, 0x00], [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C, 0x00, 0x00], [0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x00], [0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x04, 0x08, 0x00], [0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00], [0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00], [0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08, 0x00, 0x00], [0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04, 0x00, 0x00], [0x0E, 0x11, 0x01, 0x0D, 0x15, 0x15, 0x0E, 0x00, 0x00], [0x0E, 0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x00, 0x00], [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E, 0x00, 0x00], [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E, 0x00, 0x00], [0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C, 0x00, 0x00], [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F, 0x00, 0x00], [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10, 0x00, 0x00], [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F, 0x00, 0x00], [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11, 0x00, 0x00], [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E, 0x00, 0x00], [0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C, 0x00, 0x00], [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, 0x00], [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F, 0x00, 0x00], [0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11, 0x00, 0x00], [0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00], [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E, 0x00, 0x00], [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10, 0x00, 0x00], [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D, 0x00, 0x00], [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11, 0x00, 0x00], [0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E, 0x00, 0x00], [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00], [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E, 0x00, 0x00], [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04, 0x00, 0x00], [0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A, 0x00, 0x00], [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11, 0x00, 0x00], [0x11, 0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x00, 0x00], [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F, 0x00, 0x00], [0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E, 0x00, 0x00], [0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00], [0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E, 0x00, 0x00], [0x04, 0x0A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x00], [0x08, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], [0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F, 0x00, 0x00], [0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x1E, 0x00, 0x00], [0x00, 0x00, 0x0E, 0x10, 0x10, 0x11, 0x0E, 0x00, 0x00], [0x01, 0x01, 0x0D, 0x13, 0x11, 0x11, 0x0F, 0x00, 0x00], [0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E, 0x00, 0x00], [0x06, 0x09, 0x08, 0x1C, 0x08, 0x08, 0x08, 0x00, 0x00], [0x00, 0x00, 0x0F, 0x11, 0x11, 0x11, 0x0F, 0x01, 0x0E], [0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x11, 0x00, 0x00], [0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E, 0x00, 0x00], [0x02, 0x00, 0x06, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C], [0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12, 0x00, 0x00], [0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E, 0x00, 0x00], [0x00, 0x00, 0x1A, 0x15, 0x15, 0x11, 0x11, 0x00, 0x00], [0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11, 0x00, 0x00], [0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E, 0x00, 0x00], [0x00, 0x00, 0x1E, 0x11, 0x11, 0x11, 0x1E, 0x10, 0x10], [0x00, 0x00, 0x0F, 0x11, 0x11, 0x11, 0x0F, 0x01, 0x01], [0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10, 0x00, 0x00], [0x00, 0x00, 0x0F, 0x10, 0x0E, 0x01, 0x1E, 0x00, 0x00], [0x08, 0x08, 0x1C, 0x08, 0x08, 0x09, 0x06, 0x00, 0x00], [0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D, 0x00, 0x00], [0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04, 0x00, 0x00], [0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A, 0x00, 0x00], [0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x00, 0x00], [0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0F, 0x01, 0x0E], [0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F, 0x00, 0x00], [0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02, 0x00, 0x00], [0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00], [0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08, 0x00, 0x00], [0x00, 0x00, 0x08, 0x15, 0x02, 0x00, 0x00, 0x00, 0x00], ];
const NOTDEF: [u8; 9] = [0x1F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1F, 0, 0];
type Rect = [i16; 4];
fn outline(rows: &[u8; 9], bold: bool) -> Vec<Rect> {
let mut rects = Vec::new();
for (row, &bits) in rows.iter().enumerate() {
let bits =
u16::from(bits) << 1 | if bold { u16::from(bits) } else { 0 };
let top = (7 - row as i16) * PIXEL;
let mut column = 0;
while column < 6 {
let set = |column: i16| bits >> (5 - column) & 1 == 1;
if !set(column) {
column += 1;
continue;
}
let start = column;
while column < 6 && set(column) {
column += 1;
}
rects.push([start * PIXEL, top - PIXEL, column * PIXEL, top]);
}
}
rects
}
pub fn glyph_pixels(c: char) -> Vec<[i16; 4]> {
let index = if (' '..='~').contains(&c) {
c as usize - usize::from(b' ')
} else {
usize::from(b'?' - b' ')
};
outline(&GLYPHS[index], false)
.into_iter()
.map(|rect| rect.map(|value| value / PIXEL))
.collect()
}
fn glyph_record(rects: &[Rect]) -> Vec<u8> {
if rects.is_empty() {
return Vec::new();
}
let mut out = Vec::new();
let min = |i: usize| rects.iter().map(|r| r[i]).min().unwrap_or(0);
let max = |i: usize| rects.iter().map(|r| r[i]).max().unwrap_or(0);
put16(&mut out, rects.len() as u16);
for value in [min(0), min(1), max(2), max(3)] {
put16(&mut out, value as u16);
}
for contour in 0..rects.len() {
put16(&mut out, (contour * 4 + 3) as u16);
}
put16(&mut out, 0); out.extend(std::iter::repeat_n(1, rects.len() * 4)); let points: Vec<[i16; 2]> = rects
.iter()
.flat_map(|&[x0, y0, x1, y1]| [[x0, y1], [x1, y1], [x1, y0], [x0, y0]])
.collect();
for axis in 0..2 {
let mut last = 0;
for point in &points {
put16(&mut out, (point[axis] - last) as u16);
last = point[axis];
}
}
while out.len() % 4 != 0 {
out.push(0);
}
out
}
fn put16(out: &mut Vec<u8>, value: u16) {
out.extend(value.to_be_bytes());
}
fn put32(out: &mut Vec<u8>, value: u32) {
out.extend(value.to_be_bytes());
}
fn checksum(bytes: &[u8]) -> u32 {
bytes.chunks(4).fold(0u32, |sum, chunk| {
let mut word = [0; 4];
word[..chunk.len()].copy_from_slice(chunk);
sum.wrapping_add(u32::from_be_bytes(word))
})
}
#[must_use]
pub fn synth(preset: &str) -> Option<Vec<u8>> {
let bold = match preset {
"pixel" => false,
"bold" => true,
_ => return None,
};
let style = if bold { "Bold" } else { "Regular" };
let advance = (if bold { 7 } else { 6 }) * PIXEL;
let outlines: Vec<Vec<Rect>> = std::iter::once(&NOTDEF)
.chain(&GLYPHS)
.map(|rows| outline(rows, bold))
.collect();
let glyphs = outlines.len() as u16;
let mut glyf = Vec::new();
let mut loca = Vec::new();
let mut hmtx = Vec::new();
for rects in &outlines {
put32(&mut loca, glyf.len() as u32);
glyf.extend(glyph_record(rects));
put16(&mut hmtx, advance as u16);
let left = rects.iter().map(|r| r[0]).min().unwrap_or(0);
put16(&mut hmtx, left as u16);
}
put32(&mut loca, glyf.len() as u32);
let all = outlines.iter().flatten();
let bounds = [
all.clone().map(|r| r[0]).min().unwrap_or(0),
all.clone().map(|r| r[1]).min().unwrap_or(0),
all.clone().map(|r| r[2]).max().unwrap_or(0),
all.clone().map(|r| r[3]).max().unwrap_or(0),
];
let max_contours = outlines.iter().map(Vec::len).max().unwrap_or(0) as u16;
let ascent: i16 = 8 * PIXEL;
let descent: i16 = -2 * PIXEL;
let mut head = Vec::new();
put32(&mut head, 0x0001_0000);
put32(&mut head, 0x0001_0000); put32(&mut head, 0); put32(&mut head, 0x5F0F_3CF5);
put16(&mut head, 0x0003); put16(&mut head, UNITS_PER_EM);
head.extend([0; 16]); for value in bounds {
put16(&mut head, value as u16);
}
put16(&mut head, u16::from(bold)); put16(&mut head, 8); put16(&mut head, 2);
put16(&mut head, 1); put16(&mut head, 0);
let mut hhea = Vec::new();
put32(&mut hhea, 0x0001_0000);
for value in [ascent, descent, 0, advance, bounds[0], 0, bounds[2], 1, 0] {
put16(&mut hhea, value as u16);
}
hhea.extend([0; 10]); put16(&mut hhea, 0);
put16(&mut hhea, glyphs);
let mut maxp = Vec::new();
put32(&mut maxp, 0x0001_0000);
for value in [glyphs, max_contours * 4, max_contours, 0, 0, 2] {
put16(&mut maxp, value);
}
maxp.extend([0; 16]);
let mut cmap = Vec::new();
for value in [0, 1, 3, 1, 0, 12, 4, 32, 0, 4, 4, 1, 0] {
put16(&mut cmap, value);
}
for value in [0x7E, 0xFFFF, 0, 0x20, 0xFFFF] {
put16(&mut cmap, value);
}
for value in [1i16 - 0x20, 1, 0, 0] {
put16(&mut cmap, value as u16);
}
let family = "Rusting Pixel";
let names = [
(0, "Generated by Rusting Engine; dedicated to the public domain (CC0 1.0)"),
(1, family),
(2, style),
(3, &format!("{family} {style} 1.0")),
(4, &format!("{family} {style}")),
(5, "Version 1.0"),
(6, &format!("RustingPixel-{style}")),
(13, "CC0 1.0 Universal"),
(14, "https://creativecommons.org/publicdomain/zero/1.0/"),
];
let mut name = Vec::new();
let mut strings = Vec::new();
put16(&mut name, 0);
put16(&mut name, names.len() as u16);
put16(&mut name, 6 + 12 * names.len() as u16);
for (id, text) in names {
let utf16: Vec<u8> =
text.encode_utf16().flat_map(u16::to_be_bytes).collect();
for value in [3, 1, 0x0409, id] {
put16(&mut name, value);
}
put16(&mut name, utf16.len() as u16);
put16(&mut name, strings.len() as u16);
strings.extend(utf16);
}
name.extend(strings);
let mut os2 = Vec::new();
for value in [4, advance as u16, if bold { 700 } else { 400 }, 5, 0] {
put16(&mut os2, value);
}
for value in [
5 * PIXEL,
5 * PIXEL,
0,
PIXEL,
5 * PIXEL,
5 * PIXEL,
0,
4 * PIXEL,
PIXEL,
3 * PIXEL,
0,
] {
put16(&mut os2, value as u16);
}
os2.extend([0; 10]); put32(&mut os2, 1); os2.extend([0; 12]);
os2.extend(*b"NONE");
put16(&mut os2, 0x80 | if bold { 0x20 } else { 0x40 }); for value in [0x20, 0x7E, ascent as u16, descent as u16, 0] {
put16(&mut os2, value);
}
put16(&mut os2, ascent as u16);
put16(&mut os2, -descent as u16);
put32(&mut os2, 1); put32(&mut os2, 0);
for value in [5 * PIXEL, 7 * PIXEL, 0, 0x20, 1] {
put16(&mut os2, value as u16);
}
let mut post = Vec::new();
put32(&mut post, 0x0003_0000); put32(&mut post, 0);
put16(&mut post, (-PIXEL) as u16);
put16(&mut post, PIXEL as u16);
put32(&mut post, 1); post.extend([0; 16]);
let tables: [(&[u8; 4], Vec<u8>); 10] = [
(b"OS/2", os2),
(b"cmap", cmap),
(b"glyf", glyf),
(b"head", head),
(b"hhea", hhea),
(b"hmtx", hmtx),
(b"loca", loca),
(b"maxp", maxp),
(b"name", name),
(b"post", post),
];
let mut font = Vec::new();
put32(&mut font, 0x0001_0000);
for value in [10, 128, 3, 32] {
put16(&mut font, value);
}
let mut offset = 12 + 16 * tables.len();
let mut head_at = 0;
for (tag, table) in &tables {
if *tag == b"head" {
head_at = offset;
}
font.extend(*tag);
put32(&mut font, checksum(table));
put32(&mut font, offset as u32);
put32(&mut font, table.len() as u32);
offset += table.len().next_multiple_of(4);
}
for (_, table) in &tables {
font.extend(table);
font.resize(font.len().next_multiple_of(4), 0);
}
let adjustment = 0xB1B0_AFBAu32.wrapping_sub(checksum(&font));
font[head_at + 8..head_at + 12].copy_from_slice(&adjustment.to_be_bytes());
Some(font)
}
#[cfg(test)]
mod tests {
use super::*;
fn read16(bytes: &[u8], at: usize) -> u16 {
u16::from_be_bytes([bytes[at], bytes[at + 1]])
}
#[test]
fn fonts_are_stable_well_formed_truetype() {
for preset in PRESETS {
let font = synth(preset).unwrap();
assert_eq!(font, synth(preset).unwrap(), "{preset} is stable");
assert_eq!(&font[..4], &[0, 1, 0, 0]);
assert_eq!(checksum(&font), 0xB1B0_AFBA, "{preset} checksum");
let tables = usize::from(read16(&font, 4));
for record in 0..tables {
let at = 12 + 16 * record;
let word = |i: usize| {
u32::from_be_bytes(
font[at + i..at + i + 4].try_into().unwrap(),
) as usize
};
assert_eq!(word(8) % 4, 0);
assert!(word(8) + word(12) <= font.len());
}
}
assert_ne!(synth("pixel"), synth("bold"));
assert_eq!(synth("serif"), None);
}
#[test]
fn outlines_follow_the_bitmaps() {
let h = outline(&GLYPHS[usize::from(b'H' - b' ')], false);
assert_eq!(h.len(), 2 * 6 + 1);
assert!(h.contains(&[0, 3 * PIXEL, 5 * PIXEL, 4 * PIXEL]));
let bold = outline(&GLYPHS[usize::from(b'H' - b' ')], true);
assert!(bold.contains(&[0, 3 * PIXEL, 6 * PIXEL, 4 * PIXEL]));
assert!(outline(&GLYPHS[0], false).is_empty());
let g = outline(&GLYPHS[usize::from(b'g' - b' ')], false);
assert_eq!(g.iter().map(|r| r[1]).min(), Some(-2 * PIXEL));
}
#[cfg(feature = "ui")]
#[test]
fn egui_draws_text_in_the_generated_font() {
use crate::text_texture::{
text_texture, text_texture_in_font, TextStyle,
};
let style = TextStyle {
size: 16.0,
..TextStyle::default()
};
for preset in PRESETS {
let font = synth(preset).unwrap();
let drawn = text_texture_in_font("Score 42", style, &font)
.expect("egui reads the font");
let builtin = text_texture("Score 42", style);
assert_ne!(
drawn.rgba8, builtin.rgba8,
"{preset} is not the built-in font"
);
assert!(drawn.rgba8.chunks(4).any(|texel| texel[0] > 200));
let width = |text: &str| {
let drawn = text_texture_in_font(text, style, &font).unwrap();
drawn.size[0] - 2 * style.padding
};
assert!(width("M") > 4, "{preset} advances");
assert_eq!(width("iiii"), width("MMMM"));
assert!(width("MMMM").abs_diff(4 * width("M")) <= 2);
}
}
}