#include "font.h" #include #include #include #include #include #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; // printf("glyph for %c is x0,y0,x1,y1: %d,%d,%d,%d\n xoff %g, yoff %g, xadvance %g, xoff2 %g, yoff2 %g\n", c, glyph.x0, glyph.y1, glyph.x1, glyph.y1, glyph.xoff, glyph.yoff, glyph.xadvance, glyph.xoff2, glyph.yoff2); } 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, float scale, 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, size is height in pixels, wrap is how long a line is before wrapping. -1to not wrap HMM_Vec2 measure_text(const char *text, font *f, float size, float letterSpacing, float wrap) { int breakAtWord = 0; HMM_Vec2 dim = {0}; float maxWidth = 0; // Maximum width of any line float lineWidth = 0; // Current line width float scale = size / f->height; float lineHeight = (f->ascent - f->descent) * scale; letterSpacing *= scale; float height = lineHeight; // Total height const char *wordStart = text; // Start of the current word for word wrapping float wordWidth = 0; // Width of the current word float spaceWidth = f->Characters[' '].advance + letterSpacing; // Space character width for (const char *c = text; *c != '\0'; c++) { if (*c == '\n') { // Handle explicit line breaks maxWidth = fmaxf(maxWidth, lineWidth); lineWidth = 0; height += lineHeight + f->linegap; wordStart = c + 1; wordWidth = 0; continue; } float charWidth = f->Characters[(unsigned char)*c].advance + letterSpacing; // Handle wrapping if (wrap > 0 && lineWidth + charWidth > wrap) { if (breakAtWord && *c != ' ') { // Roll back to the last word if breaking at word boundaries if (wordWidth > 0) { lineWidth -= wordWidth + spaceWidth; c = wordStart - 1; // Reset to start of the word } } // Finish the current line and reset maxWidth = fmaxf(maxWidth, lineWidth); lineWidth = 0; height += lineHeight + f->linegap; wordStart = c + 1; // Start a new word wordWidth = 0; // Skip to next character if wrapping on letters if (!breakAtWord) { lineWidth += charWidth; continue; } } lineWidth += charWidth; // Update word width if breaking at word boundaries if (breakAtWord) { if (*c == ' ') { wordWidth = 0; wordStart = c + 1; } else { wordWidth += charWidth; } } } // Finalize dimensions 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, float scale, colorf color, float wrap) { text_vert *buffer = NULL; HMM_Vec2 cursor = pos; float lineHeight = f->ascent - f->descent; float lineWidth = 0; for (const char *c = text; *c != 0; c++) { if (*c == '\n') { cursor.x = pos.x; cursor.y -= lineHeight + f->linegap; lineWidth = 0; continue; } struct character chara = f->Characters[(unsigned char)*c]; if (wrap > 0 && lineWidth + chara.advance > wrap) { cursor.x = pos.x; cursor.y -= lineHeight + f->linegap; lineWidth = 0; } if (!isspace(*c)) draw_char_verts(&buffer, chara, cursor, scale, color); lineWidth += chara.advance; cursor.x += chara.advance; } return buffer; }