Files
cell/source/font.c

364 lines
9.9 KiB
C

#include "font.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "render.h"
#include "stb_image_write.h"
#include "stb_rect_pack.h"
#include "stb_truetype.h"
#include "stb_ds.h"
#include "HandmadeMath.h"
struct sFont *use_font;
void font_free(JSRuntime *rt, font *f)
{
free(f);
}
struct sFont *MakeFont(void *ttf_buffer, size_t len, int height) {
if (!ttf_buffer)
return NULL;
int packsize = 1024;
struct sFont *newfont = calloc(1, sizeof(struct sFont));
newfont->height = height;
unsigned char *bitmap = malloc(packsize * packsize);
stbtt_packedchar glyphs[95];
stbtt_pack_context pc;
int pad = 2;
stbtt_PackBegin(&pc, bitmap, packsize, packsize, 0, pad, NULL);
stbtt_PackFontRange(&pc, ttf_buffer, 0, height, 32, 95, glyphs);
stbtt_PackEnd(&pc);
stbtt_fontinfo fontinfo;
if (!stbtt_InitFont(&fontinfo, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer, 0))) {
// YughError("Failed to make font %s", fontfile);
}
int ascent, descent, linegap;
stbtt_GetFontVMetrics(&fontinfo, &ascent, &descent, &linegap);
float emscale = stbtt_ScaleForMappingEmToPixels(&fontinfo, height);
newfont->ascent = ascent*emscale;
newfont->descent = descent*emscale;
newfont->linegap = linegap*emscale;
newfont->surface = SDL_CreateSurface(packsize,packsize, SDL_PIXELFORMAT_RGBA32);
if (!newfont->surface) printf("SDL ERROR: %s\n", SDL_GetError());
for (int i = 0; i < packsize; i++)
for (int j = 0; j < packsize; j++)
if (!SDL_WriteSurfacePixel(newfont->surface, j, i, 255,255,255,bitmap[i*packsize+j]))
printf("SDLERROR: %s\n", SDL_GetError());
for (unsigned char c = 32; c < 127; c++) {
stbtt_packedchar glyph = glyphs[c - 32];
rect uv;
uv.x = (glyph.x0) / (float)packsize;
uv.w = (glyph.x1-glyph.x0) / (float)packsize;
uv.y = (glyph.y1) / (float)packsize;
uv.h = (glyph.y0-glyph.y1) / (float)packsize;
newfont->Characters[c].uv = uv;
rect quad;
quad.x = glyph.xoff;
quad.w = glyph.xoff2-glyph.xoff;
quad.y = -glyph.yoff2;
quad.h = glyph.yoff2-glyph.yoff;
newfont->Characters[c].quad = quad;
newfont->Characters[c].advance = glyph.xadvance;
}
free(bitmap);
return newfont;
}
void sdrawCharacter(struct text_vert **buffer, stbtt_packedchar c, HMM_Vec2 cursor, float scale, struct rgba color) {
struct text_vert vert;
// vert.pos.x = cursor.X + c.leftbearing;
// vert.pos.y = cursor.Y + c.topbearing;
// vert.wh = c.size;
// if (vert.pos.x > frame.l || vert.pos.y > frame.t || (vert.pos.y + vert.wh.y) < frame.b || (vert.pos.x + vert.wh.x) < frame.l) return;
// vert.uv.x = c.rect.x;
// vert.uv.y = c.rect.y;
// vert.st.x = c.rect.w;
// vert.st.y = c.rect.h;
rgba2floats(vert.color.e, color);
arrput(*buffer, vert);
}
void draw_char_verts(struct text_vert **buffer, struct character c, HMM_Vec2 cursor, colorf color)
{
// packedchar has
// Adds four verts: bottom left, bottom right, top left, top right
text_vert bl;
bl.pos.x = cursor.X + c.quad.x;
bl.pos.y = cursor.Y + c.quad.y;
bl.uv.x = c.uv.x;
bl.uv.y = c.uv.y;
bl.color = color;
arrput(*buffer, bl);
text_vert br = bl;
br.pos.x += c.quad.w;
br.uv.x += c.uv.w;
arrput(*buffer, br);
text_vert ul = bl;
ul.pos.y += c.quad.h;
ul.uv.y += c.uv.h;
arrput(*buffer, ul);
text_vert ur = ul;
ur.pos.x = br.pos.x;
ur.uv.x = br.uv.x;
arrput(*buffer, ur);
}
const char *esc_color(const char *c, struct rgba *color, struct rgba defc)
{
struct rgba d;
if (!color) color = &d;
if (*c != '\033') return c;
c++;
if (*c != '[') return c;
c++;
if (*c == '0') {
*color = defc;
c++;
return c;
}
else if (!strncmp(c, "38;2;", 5)) {
c += 5;
*color = (struct rgba){0,0,0,255};
color->r = atoi(c);
c = strchr(c, ';')+1;
color->g = atoi(c);
c = strchr(c,';')+1;
color->b = atoi(c);
c = strchr(c,';')+1;
return c;
}
return c;
}
// text is a string, font f, wrap is how long a line is before wrapping. -1 to not wrap
HMM_Vec2 measure_text(const char *text, font *f, float letterSpacing, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align)
{
int breakAtWord = (breakAt == WORD);
HMM_Vec2 dim = {0};
float maxWidth = 0;
float lineWidth = 0;
float lineHeight = f->ascent - f->descent;
float height = lineHeight;
const char *wordStart = text;
float wordWidth = 0;
float spaceWidth = f->Characters[' '].advance + letterSpacing;
for (const char *c = text; *c != '\0'; ) {
if (*c == '\n') {
maxWidth = fmaxf(maxWidth, lineWidth);
lineWidth = 0;
height += lineHeight + f->linegap;
wordStart = c + 1;
wordWidth = 0;
c++;
continue;
}
float charWidth = f->Characters[(unsigned char)*c].advance + letterSpacing;
if (wrap > 0 && lineWidth + charWidth > wrap) {
if (breakAtWord && *c != ' ' && wordWidth > 0 && wordWidth <= wrap) {
// Backtrack to word start only if the word would fit on a new line
lineWidth -= wordWidth;
c = wordStart;
} else {
// Character-based wrapping or word too long to fit
maxWidth = fmaxf(maxWidth, lineWidth);
lineWidth = 0;
height += lineHeight + f->linegap;
wordStart = c;
wordWidth = 0;
if (!breakAtWord || *c == ' ') {
c++;
continue;
}
// For word-based wrapping with long words, don't advance c here - let it be processed in this line
}
} else {
c++;
}
lineWidth += charWidth;
if (breakAtWord) {
if (*c == ' ') {
wordWidth = 0;
wordStart = c + 1;
} else {
wordWidth += charWidth;
}
}
}
maxWidth = fmaxf(maxWidth, lineWidth);
dim.x = maxWidth;
dim.y = height;
return dim;
}
/* pos given in screen coordinates */
struct text_vert *renderText(const char *text, HMM_Vec2 pos, font *f, colorf color, float wrap, TEXT_BREAK breakAt, TEXT_ALIGN align) {
text_vert *buffer = NULL;
int breakAtWord = (breakAt == WORD);
float lineHeight = f->ascent - f->descent;
float spaceWidth = f->Characters[' '].advance;
// First pass: calculate line breaks and widths
typedef struct {
const char *start;
const char *end;
float width;
} Line;
Line *lines = NULL;
const char *lineStart = text;
const char *wordStart = text;
float lineWidth = 0;
float wordWidth = 0;
for (const char *c = text; *c != '\0'; c++) {
if (*c == '\n') {
Line line = {lineStart, c, lineWidth};
arrput(lines, line);
lineStart = c + 1;
wordStart = c + 1;
lineWidth = 0;
wordWidth = 0;
continue;
}
float charWidth = f->Characters[(unsigned char)*c].advance;
if (wrap > 0 && lineWidth + charWidth > wrap) {
const char *breakPoint = c;
float breakWidth = lineWidth;
if (breakAtWord && *c != ' ' && wordWidth > 0 && wordStart > lineStart) {
breakPoint = wordStart;
breakWidth = lineWidth - wordWidth;
}
Line line = {lineStart, breakPoint, breakWidth};
arrput(lines, line);
lineStart = breakPoint;
if (breakAtWord && *lineStart == ' ') {
lineStart++;
}
c = lineStart - 1;
wordStart = lineStart;
lineWidth = 0;
wordWidth = 0;
continue;
}
lineWidth += charWidth;
if (breakAtWord) {
if (*c == ' ') {
wordWidth = 0;
wordStart = c + 1;
} else {
wordWidth += charWidth;
}
}
}
// Add final line
if (lineStart < text + strlen(text)) {
Line line = {lineStart, text + strlen(text), lineWidth};
arrput(lines, line);
}
// Second pass: render with alignment
HMM_Vec2 cursor = pos;
for (int i = 0; i < arrlen(lines); i++) {
Line *line = &lines[i];
float startX = pos.x;
float extraSpace = 0;
int spaceCount = 0;
// Calculate alignment offset
if (wrap > 0) {
switch (align) {
case RIGHT:
startX = pos.x + wrap - line->width;
break;
case CENTER:
startX = pos.x + (wrap - line->width) / 2.0f;
break;
case JUSTIFY:
if (i < arrlen(lines) - 1 && *(line->end) != '\n') {
for (const char *c = line->start; c < line->end; c++) {
if (*c == ' ') spaceCount++;
}
if (spaceCount > 0) {
extraSpace = (wrap - line->width) / spaceCount;
}
}
break;
case LEFT:
default:
break;
}
}
cursor.x = startX;
for (const char *c = line->start; c < line->end; c++) {
struct character chara = f->Characters[(unsigned char)*c];
if (!isspace(*c)) {
draw_char_verts(&buffer, chara, cursor, color);
}
cursor.x += chara.advance;
if (align == JUSTIFY && *c == ' ') {
cursor.x += extraSpace;
}
}
cursor.x = pos.x;
cursor.y -= lineHeight + f->linegap;
}
arrfree(lines);
return buffer;
}