use std::collections::HashMap;
use lyon::math::point;
use lyon::path::Path;
use lyon::tessellation::{
BuffersBuilder, FillOptions, FillTessellator, FillVertex, VertexBuffers,
};
use crate::renderer::vertex::Vertex;
#[derive(Debug, thiserror::Error)]
pub enum GlyphError {
#[error("Tessellation failed for character '{0}': {1}")]
TessellationFailed(char, String),
}
const GLYPH_ADVANCE: f32 = 0.7;
pub struct GlyphAtlas {
glyphs: HashMap<char, Vec<Vec<[f32; 2]>>>,
}
impl Default for GlyphAtlas {
fn default() -> Self {
Self::new()
}
}
impl GlyphAtlas {
pub fn new() -> Self {
let mut glyphs: HashMap<char, Vec<Vec<[f32; 2]>>> = HashMap::new();
macro_rules! glyph {
($ch:expr, $( [ $($pt:expr),+ ] ),+ $(,)?) => {
glyphs.insert($ch, vec![ $( vec![ $($pt),+ ] ),+ ]);
};
}
glyph!('A',
[[0.0, 1.0], [0.3, 0.0], [0.6, 1.0], [0.5, 1.0], [0.3, 0.2], [0.1, 1.0]],
[[0.15, 0.65], [0.45, 0.65], [0.45, 0.55], [0.15, 0.55]]
);
glyph!('B',
[[0.0, 0.0], [0.4, 0.0], [0.55, 0.1], [0.55, 0.4], [0.4, 0.5],
[0.55, 0.6], [0.55, 0.9], [0.4, 1.0], [0.0, 1.0], [0.0, 0.9],
[0.1, 0.9], [0.1, 0.55], [0.4, 0.55], [0.45, 0.45], [0.45, 0.1],
[0.1, 0.1], [0.1, 0.9], [0.0, 0.9]]
);
glyph!('C',
[[0.55, 0.1], [0.1, 0.1], [0.1, 0.9], [0.55, 0.9], [0.55, 1.0],
[0.0, 1.0], [0.0, 0.0], [0.55, 0.0]]
);
glyph!('D',
[[0.0, 0.0], [0.35, 0.0], [0.55, 0.2], [0.55, 0.8], [0.35, 1.0],
[0.0, 1.0], [0.0, 0.9], [0.1, 0.9], [0.1, 0.1], [0.35, 0.1],
[0.45, 0.25], [0.45, 0.75], [0.35, 0.9], [0.1, 0.9], [0.0, 0.9]]
);
glyph!('E',
[[0.0, 0.0], [0.5, 0.0], [0.5, 0.1], [0.1, 0.1], [0.1, 0.45],
[0.4, 0.45], [0.4, 0.55], [0.1, 0.55], [0.1, 0.9], [0.5, 0.9],
[0.5, 1.0], [0.0, 1.0]]
);
glyph!('F',
[[0.0, 0.0], [0.5, 0.0], [0.5, 0.1], [0.1, 0.1], [0.1, 0.45],
[0.4, 0.45], [0.4, 0.55], [0.1, 0.55], [0.1, 1.0], [0.0, 1.0]]
);
glyph!('G',
[[0.55, 0.1], [0.1, 0.1], [0.1, 0.9], [0.55, 0.9], [0.55, 0.5],
[0.3, 0.5], [0.3, 0.6], [0.45, 0.6], [0.45, 0.8], [0.55, 0.8],
[0.55, 1.0], [0.0, 1.0], [0.0, 0.0], [0.55, 0.0]]
);
glyph!('H',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.45], [0.5, 0.45], [0.5, 0.0],
[0.6, 0.0], [0.6, 1.0], [0.5, 1.0], [0.5, 0.55], [0.1, 0.55],
[0.1, 1.0], [0.0, 1.0]]
);
glyph!('I',
[[0.1, 0.0], [0.4, 0.0], [0.4, 0.1], [0.3, 0.1], [0.3, 0.9],
[0.4, 0.9], [0.4, 1.0], [0.1, 1.0], [0.1, 0.9], [0.2, 0.9],
[0.2, 0.1], [0.1, 0.1]]
);
glyph!('J',
[[0.15, 0.0], [0.5, 0.0], [0.5, 0.1], [0.4, 0.1], [0.4, 0.85],
[0.3, 0.95], [0.1, 0.95], [0.0, 0.85], [0.0, 0.7], [0.1, 0.7],
[0.1, 0.8], [0.15, 0.85], [0.25, 0.85], [0.3, 0.8], [0.3, 0.1],
[0.15, 0.1]]
);
glyph!('K',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.4], [0.4, 0.0], [0.55, 0.0],
[0.2, 0.45], [0.55, 1.0], [0.4, 1.0], [0.1, 0.55], [0.1, 1.0],
[0.0, 1.0]]
);
glyph!('L',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.9], [0.5, 0.9], [0.5, 1.0],
[0.0, 1.0]]
);
glyph!('M',
[[0.0, 0.0], [0.1, 0.0], [0.3, 0.4], [0.5, 0.0], [0.6, 0.0],
[0.6, 1.0], [0.5, 1.0], [0.5, 0.25], [0.3, 0.6], [0.1, 0.25],
[0.1, 1.0], [0.0, 1.0]]
);
glyph!('N',
[[0.0, 0.0], [0.1, 0.0], [0.5, 0.75], [0.5, 0.0], [0.6, 0.0],
[0.6, 1.0], [0.5, 1.0], [0.1, 0.25], [0.1, 1.0], [0.0, 1.0]]
);
glyph!('O',
[[0.0, 0.1], [0.1, 0.0], [0.5, 0.0], [0.6, 0.1], [0.6, 0.9],
[0.5, 1.0], [0.1, 1.0], [0.0, 0.9], [0.0, 0.1],
[0.1, 0.15], [0.1, 0.85], [0.15, 0.9], [0.45, 0.9], [0.5, 0.85],
[0.5, 0.15], [0.45, 0.1], [0.15, 0.1], [0.1, 0.15]]
);
glyph!('P',
[[0.0, 0.0], [0.4, 0.0], [0.55, 0.1], [0.55, 0.4], [0.4, 0.5],
[0.1, 0.5], [0.1, 1.0], [0.0, 1.0], [0.0, 0.0],
[0.1, 0.1], [0.1, 0.4], [0.4, 0.4], [0.45, 0.35], [0.45, 0.15],
[0.4, 0.1], [0.1, 0.1]]
);
glyph!('Q',
[[0.0, 0.1], [0.1, 0.0], [0.5, 0.0], [0.6, 0.1], [0.6, 0.9],
[0.5, 1.0], [0.1, 1.0], [0.0, 0.9]],
[[0.4, 0.75], [0.55, 1.0], [0.45, 1.0], [0.35, 0.8]]
);
glyph!('R',
[[0.0, 0.0], [0.4, 0.0], [0.55, 0.1], [0.55, 0.4], [0.4, 0.5],
[0.55, 1.0], [0.4, 1.0], [0.28, 0.55], [0.1, 0.55], [0.1, 1.0],
[0.0, 1.0], [0.0, 0.0],
[0.1, 0.1], [0.1, 0.45], [0.4, 0.45], [0.45, 0.35], [0.45, 0.15],
[0.4, 0.1], [0.1, 0.1]]
);
glyph!('S',
[[0.0, 0.85], [0.0, 0.55], [0.1, 0.55], [0.1, 0.8], [0.45, 0.8],
[0.5, 0.75], [0.5, 0.6], [0.45, 0.55], [0.1, 0.45], [0.0, 0.35],
[0.0, 0.1], [0.1, 0.0], [0.55, 0.0], [0.55, 0.3], [0.45, 0.3],
[0.45, 0.15], [0.15, 0.15], [0.1, 0.2], [0.1, 0.35], [0.15, 0.4],
[0.5, 0.5], [0.6, 0.6], [0.6, 0.85], [0.5, 0.95], [0.1, 0.95]]
);
glyph!('T',
[[0.0, 0.0], [0.6, 0.0], [0.6, 0.1], [0.35, 0.1], [0.35, 1.0],
[0.25, 1.0], [0.25, 0.1], [0.0, 0.1]]
);
glyph!('U',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.85], [0.15, 0.9], [0.45, 0.9],
[0.5, 0.85], [0.5, 0.0], [0.6, 0.0], [0.6, 0.9], [0.5, 1.0],
[0.1, 1.0], [0.0, 0.9]]
);
glyph!('V',
[[0.0, 0.0], [0.1, 0.0], [0.3, 0.8], [0.5, 0.0], [0.6, 0.0],
[0.35, 1.0], [0.25, 1.0]]
);
glyph!('W',
[[0.0, 0.0], [0.1, 0.0], [0.2, 0.7], [0.3, 0.3], [0.4, 0.7],
[0.5, 0.0], [0.6, 0.0], [0.45, 1.0], [0.35, 1.0], [0.3, 0.6],
[0.25, 1.0], [0.15, 1.0]]
);
glyph!('X',
[[0.0, 0.0], [0.15, 0.0], [0.3, 0.4], [0.45, 0.0], [0.6, 0.0],
[0.38, 0.5], [0.6, 1.0], [0.45, 1.0], [0.3, 0.6], [0.15, 1.0],
[0.0, 1.0], [0.22, 0.5]]
);
glyph!('Y',
[[0.0, 0.0], [0.15, 0.0], [0.3, 0.4], [0.45, 0.0], [0.6, 0.0],
[0.35, 0.55], [0.35, 1.0], [0.25, 1.0], [0.25, 0.55]]
);
glyph!('Z',
[[0.0, 0.0], [0.6, 0.0], [0.6, 0.1], [0.15, 0.9], [0.6, 0.9],
[0.6, 1.0], [0.0, 1.0], [0.0, 0.9], [0.45, 0.1], [0.0, 0.1]]
);
glyph!('a',
[[0.05, 0.35], [0.4, 0.35], [0.5, 0.45], [0.5, 1.0], [0.4, 1.0],
[0.4, 0.9], [0.15, 0.95], [0.05, 0.85], [0.05, 0.7], [0.15, 0.6],
[0.4, 0.6], [0.4, 0.5], [0.15, 0.5], [0.05, 0.45]],
[[0.15, 0.7], [0.4, 0.7], [0.4, 0.8], [0.15, 0.85]]
);
glyph!('b',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.4], [0.35, 0.35], [0.5, 0.45],
[0.5, 0.9], [0.35, 1.0], [0.1, 1.0], [0.0, 1.0]],
[[0.1, 0.5], [0.35, 0.5], [0.4, 0.55], [0.4, 0.85], [0.35, 0.9],
[0.1, 0.9]]
);
glyph!('c',
[[0.45, 0.45], [0.1, 0.45], [0.05, 0.5], [0.05, 0.9], [0.1, 0.95],
[0.45, 0.95], [0.45, 1.0], [0.05, 1.0], [0.0, 0.9], [0.0, 0.45],
[0.1, 0.35], [0.45, 0.35]]
);
glyph!('d',
[[0.5, 0.0], [0.6, 0.0], [0.6, 1.0], [0.25, 1.0], [0.1, 0.9],
[0.1, 0.45], [0.25, 0.35], [0.5, 0.35]],
[[0.2, 0.5], [0.5, 0.5], [0.5, 0.9], [0.25, 0.9], [0.2, 0.85]]
);
glyph!('e',
[[0.05, 0.65], [0.45, 0.65], [0.45, 0.5], [0.1, 0.5], [0.05, 0.55],
[0.05, 0.45], [0.1, 0.35], [0.45, 0.35], [0.5, 0.45], [0.5, 0.7],
[0.05, 0.7], [0.05, 0.9], [0.1, 0.95], [0.45, 0.95], [0.45, 1.0],
[0.05, 1.0], [0.0, 0.9], [0.0, 0.45]]
);
glyph!('f',
[[0.15, 0.35], [0.5, 0.35], [0.5, 0.45], [0.3, 0.45], [0.3, 1.0],
[0.2, 1.0], [0.2, 0.45], [0.05, 0.45], [0.05, 0.35], [0.2, 0.35],
[0.2, 0.15], [0.25, 0.05], [0.4, 0.0], [0.5, 0.0], [0.5, 0.1],
[0.3, 0.1], [0.3, 0.35]]
);
glyph!('g',
[[0.1, 0.35], [0.45, 0.35], [0.5, 0.45], [0.5, 1.1], [0.45, 1.2],
[0.1, 1.2], [0.1, 1.1], [0.4, 1.1], [0.4, 0.95], [0.15, 1.0],
[0.05, 0.9], [0.05, 0.45], [0.1, 0.35]],
[[0.15, 0.5], [0.4, 0.5], [0.4, 0.85], [0.15, 0.9]]
);
glyph!('h',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.45], [0.35, 0.35], [0.45, 0.45],
[0.45, 1.0], [0.35, 1.0], [0.35, 0.5], [0.1, 0.55], [0.1, 1.0],
[0.0, 1.0]]
);
glyph!('i',
[[0.2, 0.4], [0.3, 0.4], [0.3, 1.0], [0.2, 1.0]],
[[0.2, 0.15], [0.3, 0.15], [0.3, 0.28], [0.2, 0.28]]
);
glyph!('j',
[[0.3, 0.4], [0.4, 0.4], [0.4, 1.05], [0.35, 1.15], [0.15, 1.15],
[0.1, 1.1], [0.2, 1.1], [0.3, 1.05], [0.3, 0.4]],
[[0.3, 0.15], [0.4, 0.15], [0.4, 0.28], [0.3, 0.28]]
);
glyph!('k',
[[0.0, 0.0], [0.1, 0.0], [0.1, 0.55], [0.35, 0.35], [0.5, 0.35],
[0.2, 0.6], [0.5, 1.0], [0.35, 1.0], [0.1, 0.65], [0.1, 1.0],
[0.0, 1.0]]
);
glyph!('l',
[[0.2, 0.0], [0.3, 0.0], [0.3, 0.9], [0.4, 1.0], [0.4, 1.0],
[0.3, 1.0], [0.2, 0.9]]
);
glyph!('m',
[[0.0, 0.35], [0.1, 0.35], [0.1, 0.4], [0.2, 0.35], [0.3, 0.4],
[0.4, 0.35], [0.5, 0.4], [0.5, 1.0], [0.4, 1.0], [0.4, 0.5],
[0.3, 0.5], [0.3, 1.0], [0.2, 1.0], [0.2, 0.5], [0.1, 0.5],
[0.1, 1.0], [0.0, 1.0]]
);
glyph!('n',
[[0.0, 0.35], [0.1, 0.35], [0.1, 0.45], [0.35, 0.35], [0.45, 0.45],
[0.45, 1.0], [0.35, 1.0], [0.35, 0.5], [0.1, 0.55], [0.1, 1.0],
[0.0, 1.0]]
);
glyph!('o',
[[0.05, 0.45], [0.15, 0.35], [0.45, 0.35], [0.55, 0.45], [0.55, 0.9],
[0.45, 1.0], [0.15, 1.0], [0.05, 0.9]],
[[0.15, 0.5], [0.45, 0.5], [0.45, 0.85], [0.15, 0.85]]
);
glyph!('p',
[[0.0, 0.35], [0.1, 0.35], [0.1, 0.4], [0.35, 0.35], [0.5, 0.45],
[0.5, 0.9], [0.35, 1.0], [0.1, 1.0], [0.1, 1.2], [0.0, 1.2]],
[[0.1, 0.5], [0.35, 0.5], [0.4, 0.55], [0.4, 0.85], [0.35, 0.9],
[0.1, 0.9]]
);
glyph!('q',
[[0.5, 0.35], [0.6, 0.35], [0.6, 1.2], [0.5, 1.2], [0.5, 1.0],
[0.25, 1.0], [0.1, 0.9], [0.1, 0.45], [0.25, 0.35]],
[[0.2, 0.5], [0.5, 0.5], [0.5, 0.9], [0.25, 0.9], [0.2, 0.85]]
);
glyph!('r',
[[0.0, 0.35], [0.1, 0.35], [0.1, 0.45], [0.25, 0.35], [0.45, 0.35],
[0.45, 0.45], [0.2, 0.5], [0.1, 0.55], [0.1, 1.0], [0.0, 1.0]]
);
glyph!('s',
[[0.05, 0.85], [0.05, 0.7], [0.15, 0.7], [0.15, 0.8], [0.4, 0.8],
[0.45, 0.75], [0.45, 0.7], [0.1, 0.6], [0.05, 0.5], [0.05, 0.45],
[0.1, 0.35], [0.5, 0.35], [0.5, 0.5], [0.4, 0.5], [0.4, 0.45],
[0.15, 0.45], [0.1, 0.5], [0.1, 0.55], [0.45, 0.65], [0.5, 0.75],
[0.5, 0.9], [0.4, 1.0], [0.1, 1.0]]
);
glyph!('t',
[[0.15, 0.1], [0.25, 0.1], [0.25, 0.35], [0.45, 0.35], [0.45, 0.45],
[0.25, 0.45], [0.25, 0.9], [0.35, 0.95], [0.45, 0.95], [0.45, 1.0],
[0.3, 1.0], [0.15, 0.9]]
);
glyph!('u',
[[0.0, 0.35], [0.1, 0.35], [0.1, 0.85], [0.15, 0.9], [0.35, 0.9],
[0.4, 0.85], [0.4, 0.35], [0.5, 0.35], [0.5, 0.9], [0.4, 1.0],
[0.1, 1.0], [0.0, 0.9]]
);
glyph!('v',
[[0.0, 0.35], [0.1, 0.35], [0.25, 0.85], [0.4, 0.35], [0.5, 0.35],
[0.3, 1.0], [0.2, 1.0]]
);
glyph!('w',
[[0.0, 0.35], [0.1, 0.35], [0.15, 0.75], [0.25, 0.5], [0.35, 0.75],
[0.4, 0.35], [0.5, 0.35], [0.4, 1.0], [0.3, 1.0], [0.25, 0.7],
[0.2, 1.0], [0.1, 1.0]]
);
glyph!('x',
[[0.0, 0.35], [0.15, 0.35], [0.25, 0.55], [0.35, 0.35], [0.5, 0.35],
[0.32, 0.65], [0.5, 1.0], [0.35, 1.0], [0.25, 0.75], [0.15, 1.0],
[0.0, 1.0], [0.18, 0.65]]
);
glyph!('y',
[[0.0, 0.35], [0.1, 0.35], [0.25, 0.75], [0.4, 0.35], [0.5, 0.35],
[0.3, 0.85], [0.2, 1.15], [0.05, 1.2], [0.05, 1.1], [0.15, 1.1]]
);
glyph!('z',
[[0.05, 0.35], [0.5, 0.35], [0.5, 0.45], [0.15, 0.9], [0.5, 0.9],
[0.5, 1.0], [0.05, 1.0], [0.05, 0.9], [0.4, 0.45], [0.05, 0.45]]
);
glyph!('0',
[[0.05, 0.1], [0.15, 0.0], [0.45, 0.0], [0.55, 0.1], [0.55, 0.9],
[0.45, 1.0], [0.15, 1.0], [0.05, 0.9]],
[[0.15, 0.15], [0.45, 0.15], [0.45, 0.85], [0.15, 0.85]]
);
glyph!('1',
[[0.15, 0.15], [0.3, 0.0], [0.4, 0.0], [0.4, 0.9], [0.5, 0.9],
[0.5, 1.0], [0.15, 1.0], [0.15, 0.9], [0.3, 0.9], [0.3, 0.2],
[0.15, 0.3]]
);
glyph!('2',
[[0.05, 0.1], [0.15, 0.0], [0.45, 0.0], [0.55, 0.1], [0.55, 0.4],
[0.45, 0.5], [0.15, 0.5], [0.1, 0.55], [0.55, 0.9], [0.55, 1.0],
[0.05, 1.0], [0.05, 0.9], [0.4, 0.55], [0.05, 0.45], [0.05, 0.1]]
);
glyph!('3',
[[0.05, 0.1], [0.15, 0.0], [0.45, 0.0], [0.55, 0.1], [0.55, 0.4],
[0.45, 0.5], [0.55, 0.6], [0.55, 0.9], [0.45, 1.0], [0.15, 1.0],
[0.05, 0.9], [0.05, 0.8], [0.15, 0.8], [0.15, 0.9], [0.4, 0.9],
[0.45, 0.85], [0.45, 0.6], [0.35, 0.55], [0.35, 0.45], [0.45, 0.4],
[0.45, 0.15], [0.4, 0.1], [0.15, 0.1], [0.15, 0.2], [0.05, 0.2]]
);
glyph!('4',
[[0.35, 0.0], [0.45, 0.0], [0.45, 0.55], [0.55, 0.55], [0.55, 0.65],
[0.45, 0.65], [0.45, 1.0], [0.35, 1.0], [0.35, 0.65], [0.05, 0.65],
[0.05, 0.55], [0.35, 0.0]],
[[0.15, 0.55], [0.35, 0.55], [0.35, 0.2]]
);
glyph!('5',
[[0.05, 0.0], [0.55, 0.0], [0.55, 0.1], [0.15, 0.1], [0.15, 0.4],
[0.45, 0.4], [0.55, 0.5], [0.55, 0.9], [0.45, 1.0], [0.15, 1.0],
[0.05, 0.9], [0.05, 0.8], [0.15, 0.8], [0.15, 0.9], [0.4, 0.9],
[0.45, 0.85], [0.45, 0.55], [0.4, 0.5], [0.05, 0.5]]
);
glyph!('6',
[[0.15, 0.0], [0.45, 0.0], [0.55, 0.1], [0.55, 0.15], [0.45, 0.15],
[0.45, 0.1], [0.15, 0.1], [0.1, 0.15], [0.1, 0.45], [0.45, 0.45],
[0.55, 0.55], [0.55, 0.9], [0.45, 1.0], [0.15, 1.0], [0.05, 0.9],
[0.05, 0.1]],
[[0.15, 0.55], [0.45, 0.55], [0.45, 0.85], [0.15, 0.85]]
);
glyph!('7',
[[0.05, 0.0], [0.55, 0.0], [0.55, 0.15], [0.3, 1.0], [0.2, 1.0],
[0.45, 0.1], [0.05, 0.1]]
);
glyph!('8',
[[0.1, 0.0], [0.5, 0.0], [0.55, 0.1], [0.55, 0.4], [0.5, 0.5],
[0.55, 0.6], [0.55, 0.9], [0.5, 1.0], [0.1, 1.0], [0.05, 0.9],
[0.05, 0.6], [0.1, 0.5], [0.05, 0.4], [0.05, 0.1]],
[[0.15, 0.1], [0.45, 0.1], [0.45, 0.4], [0.15, 0.4]],
[[0.15, 0.6], [0.45, 0.6], [0.45, 0.9], [0.15, 0.9]]
);
glyph!('9',
[[0.1, 0.0], [0.5, 0.0], [0.55, 0.1], [0.55, 0.9], [0.45, 1.0],
[0.15, 1.0], [0.05, 0.9], [0.05, 0.85], [0.15, 0.85], [0.15, 0.9],
[0.45, 0.9], [0.5, 0.85], [0.5, 0.55], [0.15, 0.55], [0.05, 0.45],
[0.05, 0.1]],
[[0.15, 0.15], [0.45, 0.15], [0.45, 0.45], [0.15, 0.45]]
);
glyph!('.',
[[0.1, 0.85], [0.2, 0.85], [0.2, 1.0], [0.1, 1.0]]
);
glyph!(',',
[[0.1, 0.85], [0.2, 0.85], [0.2, 1.0], [0.05, 1.15], [0.0, 1.1],
[0.1, 1.0]]
);
glyph!('!',
[[0.1, 0.0], [0.2, 0.0], [0.2, 0.7], [0.1, 0.7]],
[[0.1, 0.85], [0.2, 0.85], [0.2, 1.0], [0.1, 1.0]]
);
glyph!('?',
[[0.0, 0.1], [0.1, 0.0], [0.4, 0.0], [0.5, 0.1], [0.5, 0.35],
[0.35, 0.5], [0.3, 0.5], [0.3, 0.65], [0.2, 0.65], [0.2, 0.45],
[0.4, 0.3], [0.4, 0.15], [0.35, 0.1], [0.15, 0.1], [0.1, 0.15],
[0.0, 0.15]],
[[0.2, 0.8], [0.3, 0.8], [0.3, 0.95], [0.2, 0.95]]
);
glyph!(':',
[[0.15, 0.3], [0.25, 0.3], [0.25, 0.45], [0.15, 0.45]],
[[0.15, 0.7], [0.25, 0.7], [0.25, 0.85], [0.15, 0.85]]
);
glyph!(';',
[[0.15, 0.3], [0.25, 0.3], [0.25, 0.45], [0.15, 0.45]],
[[0.15, 0.7], [0.25, 0.7], [0.25, 0.85], [0.1, 1.0], [0.05, 0.95],
[0.15, 0.85]]
);
glyph!('-',
[[0.1, 0.45], [0.4, 0.45], [0.4, 0.55], [0.1, 0.55]]
);
glyph!('_',
[[0.0, 0.95], [0.5, 0.95], [0.5, 1.0], [0.0, 1.0]]
);
glyphs.insert(' ', vec![]);
glyph!('+',
[[0.2, 0.25], [0.3, 0.25], [0.3, 0.45], [0.5, 0.45], [0.5, 0.55],
[0.3, 0.55], [0.3, 0.75], [0.2, 0.75], [0.2, 0.55], [0.0, 0.55],
[0.0, 0.45], [0.2, 0.45]]
);
glyph!('=',
[[0.05, 0.35], [0.45, 0.35], [0.45, 0.45], [0.05, 0.45]],
[[0.05, 0.55], [0.45, 0.55], [0.45, 0.65], [0.05, 0.65]]
);
glyph!('(',
[[0.3, 0.0], [0.35, 0.0], [0.2, 0.3], [0.2, 0.7], [0.35, 1.0],
[0.3, 1.0], [0.1, 0.7], [0.1, 0.3]]
);
glyph!(')',
[[0.15, 0.0], [0.2, 0.0], [0.4, 0.3], [0.4, 0.7], [0.2, 1.0],
[0.15, 1.0], [0.3, 0.7], [0.3, 0.3]]
);
glyph!('/',
[[0.4, 0.0], [0.5, 0.0], [0.1, 1.0], [0.0, 1.0]]
);
glyph!('@',
[[0.55, 0.6], [0.55, 0.1], [0.5, 0.0], [0.1, 0.0], [0.0, 0.1],
[0.0, 0.9], [0.1, 1.0], [0.55, 1.0], [0.55, 0.85], [0.6, 0.85],
[0.6, 1.0], [0.5, 1.1], [0.1, 1.1], [0.0, 1.0], [0.0, 0.0],
[0.1, 0.0], [0.55, 0.0], [0.6, 0.1], [0.6, 0.6]],
[[0.2, 0.3], [0.4, 0.3], [0.45, 0.4], [0.45, 0.7], [0.4, 0.8],
[0.2, 0.8], [0.15, 0.7], [0.15, 0.4]]
);
glyph!('#',
[[0.15, 0.0], [0.25, 0.0], [0.22, 0.3], [0.42, 0.3], [0.45, 0.0],
[0.55, 0.0], [0.52, 0.3], [0.6, 0.3], [0.6, 0.4], [0.5, 0.4],
[0.48, 0.6], [0.6, 0.6], [0.6, 0.7], [0.46, 0.7], [0.43, 1.0],
[0.33, 1.0], [0.36, 0.7], [0.16, 0.7], [0.13, 1.0], [0.03, 1.0],
[0.06, 0.7], [0.0, 0.7], [0.0, 0.6], [0.08, 0.6], [0.1, 0.4],
[0.0, 0.4], [0.0, 0.3], [0.12, 0.3]],
[[0.2, 0.4], [0.18, 0.6], [0.38, 0.6], [0.4, 0.4]]
);
glyph!('"',
[[0.1, 0.0], [0.2, 0.0], [0.2, 0.3], [0.1, 0.3]],
[[0.3, 0.0], [0.4, 0.0], [0.4, 0.3], [0.3, 0.3]]
);
glyph!('\'',
[[0.15, 0.0], [0.25, 0.0], [0.25, 0.3], [0.15, 0.3]]
);
Self { glyphs }
}
pub fn tessellate_string(
&self,
text: &str,
x: f32,
y: f32,
font_size: f32,
) -> Vec<Vertex> {
if text.is_empty() {
return Vec::new();
}
let mut vertices = Vec::new();
let mut cursor_x = x;
let advance = GLYPH_ADVANCE * font_size;
let default_color = [1.0_f32, 1.0, 1.0, 1.0];
for ch in text.chars() {
if let Some(polygons) = self.glyphs.get(&ch) {
for polygon in polygons {
let tess_verts = tessellate_glyph_polygon(
polygon, cursor_x, y, font_size, default_color,
);
vertices.extend_from_slice(&tess_verts);
}
}
cursor_x += advance;
}
vertices
}
}
fn tessellate_glyph_polygon(
polygon: &[[f32; 2]],
ox: f32,
oy: f32,
font_size: f32,
color: [f32; 4],
) -> Vec<Vertex> {
if polygon.len() < 3 {
return Vec::new();
}
let mut builder = Path::builder();
builder.begin(point(
polygon[0][0] * font_size + ox,
polygon[0][1] * font_size + oy,
));
for pt in &polygon[1..] {
builder.line_to(point(pt[0] * font_size + ox, pt[1] * font_size + oy));
}
builder.close();
let path = builder.build();
let mut geometry: VertexBuffers<Vertex, u32> = VertexBuffers::new();
let mut fill_tess = FillTessellator::new();
let result = fill_tess.tessellate_path(
&path,
&FillOptions::default(),
&mut BuffersBuilder::new(&mut geometry, |vertex: FillVertex| Vertex {
position: vertex.position().to_array(),
color,
}),
);
match result {
Ok(_) => {
geometry
.indices
.iter()
.map(|&i| geometry.vertices[i as usize])
.collect()
}
Err(e) => {
log::warn!("Glyph tessellation failed: {e}");
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_string_returns_empty_vec() {
let atlas = GlyphAtlas::new();
let verts = atlas.tessellate_string("", 0.0, 0.0, 16.0);
assert!(verts.is_empty());
}
#[test]
fn ascii_letter_produces_vertices() {
let atlas = GlyphAtlas::new();
let verts = atlas.tessellate_string("A", 0.0, 0.0, 16.0);
assert!(!verts.is_empty(), "Expected vertices for 'A'");
}
#[test]
fn unknown_char_skipped_but_cursor_advances() {
let atlas = GlyphAtlas::new();
let with_unknown = atlas.tessellate_string("A★B", 0.0, 0.0, 16.0);
let without_unknown = atlas.tessellate_string("AB", 0.0, 0.0, 16.0);
assert!(!with_unknown.is_empty());
assert!(!without_unknown.is_empty());
}
#[test]
fn space_produces_no_vertices() {
let atlas = GlyphAtlas::new();
let verts = atlas.tessellate_string(" ", 0.0, 0.0, 16.0);
assert!(verts.is_empty(), "Space should produce no vertices");
}
#[test]
fn digits_produce_vertices() {
let atlas = GlyphAtlas::new();
let verts = atlas.tessellate_string("0123456789", 0.0, 0.0, 16.0);
assert!(!verts.is_empty(), "Expected vertices for digits");
}
#[test]
fn punctuation_produces_vertices() {
let atlas = GlyphAtlas::new();
let verts = atlas.tessellate_string(".,!?:-_+=()/", 0.0, 0.0, 16.0);
assert!(!verts.is_empty(), "Expected vertices for punctuation");
}
#[test]
fn all_uppercase_produce_vertices() {
let atlas = GlyphAtlas::new();
for ch in 'A'..='Z' {
let s = String::from(ch);
let verts = atlas.tessellate_string(&s, 0.0, 0.0, 16.0);
assert!(!verts.is_empty(), "Expected vertices for '{ch}'");
}
}
#[test]
fn all_lowercase_produce_vertices() {
let atlas = GlyphAtlas::new();
for ch in 'a'..='z' {
let s = String::from(ch);
let verts = atlas.tessellate_string(&s, 0.0, 0.0, 16.0);
assert!(!verts.is_empty(), "Expected vertices for '{ch}'");
}
}
}