ugui.c3l/src/ugui_font.c3

228 lines
5.0 KiB
Plaintext
Raw Normal View History

2024-12-06 22:03:35 +01:00
module ugui;
import schrift;
2024-12-15 22:29:07 +01:00
import grapheme;
2024-12-11 01:14:14 +01:00
import std::collections::map;
import std::core::mem;
2024-12-11 20:39:06 +01:00
import std::io;
2024-12-15 22:29:07 +01:00
import std::ascii;
2024-12-06 22:03:35 +01:00
2024-12-11 01:14:14 +01:00
// unicode code point, different type for a different hash
def Codepoint = uint;
/* width and height of a glyph contain the kering advance
* (u,v)
* +-------------*---+ -
* | ^ | | ^
* | |oy | | |
* | v | | |
* | .ii. | | |
2024-12-11 20:39:06 +01:00
* | @@@@@@. | | |
* | V@Mio@@o | | |
* | :i. V@V | | h
2024-12-11 01:14:14 +01:00
* | :oM@@M | | |
* | :@@@MM@M | | |
* | @@o o@M | | |
* |<->:@@. M@M | | |
* |ox @@@o@@@@ | | |
* | :M@@V:@@.| | v
* +-------------*---+ -
2024-12-11 20:39:06 +01:00
* |<---- w ---->|
* |<------ adv ---->|
2024-12-11 01:14:14 +01:00
*/
struct Glyph {
Codepoint code;
ushort u, v;
ushort w, h;
short adv, ox, oy;
}
const uint FONT_CACHED = 512;
def GlyphTable = map::HashMap(<Codepoint, Glyph>) @private;
fault UgFontError {
TTF_LOAD_FAILED,
MISSING_GLYPH,
BAD_GLYPH_METRICS,
RENDER_ERROR,
}
struct Font {
2024-12-06 22:03:35 +01:00
schrift::Sft sft;
String path;
2024-12-15 21:39:26 +01:00
Id id; // font id, same as atlas id
2024-12-11 01:14:14 +01:00
GlyphTable table;
float size;
float ascender, descender, linegap; // Line Metrics
2024-12-15 21:39:26 +01:00
Atlas atlas;
bool should_update; // should send update_atlas command, resets at frame_end()
2024-12-11 01:14:14 +01:00
}
2024-12-16 13:54:53 +01:00
fn void! Font.load(&font, String name, ZString path, uint height, float scale)
2024-12-06 22:03:35 +01:00
{
2024-12-11 01:14:14 +01:00
font.table.new_init(capacity: FONT_CACHED);
2024-12-15 21:39:26 +01:00
font.id = name.hash();
2024-12-11 01:14:14 +01:00
font.size = height*scale;
font.sft = schrift::Sft{
.xScale = (double)font.size,
.yScale = (double)font.size,
.flags = schrift::SFT_DOWNWARD_Y,
};
font.sft.font = schrift::loadfile(path);
if (font.sft.font == null) {
return UgFontError.TTF_LOAD_FAILED?;
}
2024-12-06 22:03:35 +01:00
2024-12-11 01:14:14 +01:00
schrift::SftLMetrics lmetrics;
schrift::lmetrics(&font.sft, &lmetrics);
font.ascender = (float)lmetrics.ascender;
font.descender = (float)lmetrics.descender;
font.linegap = (float)lmetrics.lineGap;
2024-12-11 20:39:06 +01:00
//io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap);
2024-12-11 01:14:14 +01:00
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
// like the letter 'A'
2024-12-11 20:39:06 +01:00
ushort size = (ushort)font.size*256;
2024-12-15 21:39:26 +01:00
font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!;
2024-12-11 01:14:14 +01:00
// preallocate the ASCII range
2024-12-16 17:06:33 +01:00
for (char c = ' '; c < '~'; c++) {
font.get_glyph((Codepoint)c)!;
}
2024-12-06 22:03:35 +01:00
}
2024-12-15 21:39:26 +01:00
fn Glyph*! Font.get_glyph(&font, Codepoint code)
2024-12-06 22:03:35 +01:00
{
2024-12-11 01:14:14 +01:00
Glyph*! gp;
gp = font.table.get_ref(code);
2024-12-15 21:39:26 +01:00
2024-12-11 01:14:14 +01:00
if (catch excuse = gp) {
if (excuse != SearchResult.MISSING) {
return excuse?;
}
} else {
return gp;
}
// missing glyph, render and place into an atlas
Glyph glyph;
2024-12-06 22:03:35 +01:00
2024-12-11 01:14:14 +01:00
schrift::SftGlyph gid;
schrift::SftGMetrics gmtx;
2024-12-15 21:39:26 +01:00
2024-12-11 01:14:14 +01:00
if (schrift::lookup(&font.sft, code, &gid) < 0) {
return UgFontError.MISSING_GLYPH?;
}
if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) {
return UgFontError.BAD_GLYPH_METRICS?;
}
schrift::SftImage img = {
.width = gmtx.minWidth,
.height = gmtx.minHeight,
};
char[] pixels = mem::new_array(char, (usz)img.width * img.height);
img.pixels = pixels;
if (schrift::render(&font.sft, gid, img) < 0) {
return UgFontError.RENDER_ERROR?;
}
glyph.code = code;
glyph.w = (ushort)img.width;
glyph.h = (ushort)img.height;
2024-12-11 20:39:06 +01:00
glyph.ox = (short)gmtx.leftSideBearing;
glyph.oy = (short)gmtx.yOffset;
2024-12-11 01:14:14 +01:00
glyph.adv = (short)gmtx.advanceWidth;
2024-12-11 20:39:06 +01:00
//io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d",
// glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
2024-12-11 01:14:14 +01:00
2024-12-15 21:39:26 +01:00
Point uv = font.atlas.place(pixels, glyph.w, glyph.h)!;
2024-12-11 01:14:14 +01:00
glyph.u = uv.x;
glyph.v = uv.y;
mem::free(pixels);
font.table.set(code, glyph);
2024-12-15 21:39:26 +01:00
font.should_update = true;
2024-12-11 01:14:14 +01:00
return font.table.get_ref(code);
}
fn void Font.free(&font)
{
2024-12-15 21:39:26 +01:00
font.atlas.free();
2024-12-11 01:14:14 +01:00
schrift::freefont(font.sft.font);
2024-12-06 22:03:35 +01:00
}
2024-12-15 21:39:26 +01:00
2024-12-16 13:54:53 +01:00
fn void! Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0)
2024-12-15 21:39:26 +01:00
{
return ctx.font.load(name, path, height, scale);
2024-12-15 22:29:07 +01:00
}
<*
@require off != null
*>
fn Codepoint str_to_codepoint(char[] str, usz* off)
{
Codepoint cp;
isz b = grapheme::decode_utf8(str, str.len, &cp);
if (b == 0 || b > str.len) {
return 0;
}
*off = b;
return cp;
}
fn Rect! Ctx.get_text_bounds(&ctx, String text)
{
Rect text_bounds;
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
short line_gap = (short)ctx.font.linegap;
text_bounds.h = line_height;
Glyph* gp;
// TODO: account for unicode codepoints
short line_len;
Codepoint cp;
usz off, x;
while ((cp = str_to_codepoint(text[off..], &x)) != 0) {
off += x;
bool n;
if (!ascii::is_cntrl((char)cp)) {
gp = ctx.font.get_glyph(cp)!;
line_len += gp.adv;
} else if (cp == '\n'){
text_bounds.h += line_height + line_gap;
line_len = 0;
} else {
continue;
}
if (line_len > text_bounds.w) {
text_bounds.w = line_len;
}
}
return text_bounds;
}
2024-12-20 19:50:58 +01:00
fn Point Ctx.center_text(&ctx, Rect text_bounds, Rect bounds)
{
short dw = bounds.w - text_bounds.w;
short dh = bounds.h - text_bounds.h;
return Point{.x = dw/2, .y = dh/2};
}
// TODO: check if the font is present in the context
fn Id Ctx.get_font_id(&ctx, String label)
{
return (Id)label.hash();
2024-12-20 19:50:58 +01:00
}