* use alignment for clipped/wrapped text
[open-ps2-loader.git] / src / fntsys.c
blob4a2dbcbe3feccbacaf8ab8041e43710c617e81f9
1 /*
2 Copyright 2010, Volca
3 Licenced under Academic Free License version 3.0
4 Review OpenUsbLd README & LICENSE files for further details.
5 */
7 #include "include/integers.h"
8 #include "include/usbld.h"
9 #include "include/fntsys.h"
10 #include "include/renderman.h"
11 #include "include/ioman.h"
12 #include "include/utf8.h"
13 #include "include/util.h"
14 #include "include/atlas.h"
16 #include <ft2build.h>
18 #include FT_FREETYPE_H
20 extern void* freesansfont_raw;
21 extern int size_freesansfont_raw;
23 /// Maximal count of atlases per font
24 #define ATLAS_MAX 4
25 /// Atlas width in pixels
26 #define ATLAS_WIDTH 128
27 /// Atlas height in pixels
28 #define ATLAS_HEIGHT 128
30 // freetype vars
31 static FT_Library font_library;
33 static rm_quad_t quad;
35 static int gCharHeight;
37 static s32 gFontSemaId;
38 static ee_sema_t gFontSema;
40 static GSCLUT fontClut;
42 /** Single entry in the glyph cache */
43 typedef struct {
44 int isValid;
45 // size in pixels of the glyph
46 int width, height;
47 // offsetting of the glyph
48 int ox, oy;
49 // advancements in pixels after rendering this glyph
50 int shx, shy;
52 // atlas for which the allocation was done
53 atlas_t* atlas;
55 // atlas allocation position
56 struct atlas_allocation_t *allocation;
58 } fnt_glyph_cache_entry_t;
60 /** A whole font definition */
61 typedef struct {
62 /** GLYPH CACHE. Every glyph in the ASCII range is cached when first used
63 * this means no additional memory aside from the one needed to render the
64 * character set is used.
66 fnt_glyph_cache_entry_t **glyphCache;
68 /// Maximal font cache page index
69 int cacheMaxPageID;
71 /// Font face
72 FT_Face face;
74 /// Nonzero if font is used
75 int isValid;
77 /// Nonzero for custom fonts
78 int isDefault;
80 /// Texture atlases (default to NULL)
81 atlas_t *atlases[ATLAS_MAX];
83 /// Pointer to data, if allocation takeover was selected (will be freed)
84 void *dataPtr;
85 } font_t;
87 #define FNT_MAX_COUNT (16)
89 /// Array of font definitions
90 static font_t fonts[FNT_MAX_COUNT];
92 #define GLYPH_CACHE_PAGE_SIZE 256
94 #define GLYPH_PAGE_OK(font,page) ((pageid <= font->cacheMaxPageID) && (font->glyphCache[page]))
96 static int fntPrepareGlyphCachePage(font_t *font, int pageid) {
97 if (pageid > font->cacheMaxPageID) {
98 fnt_glyph_cache_entry_t **np = realloc(font->glyphCache, (pageid + 1) * sizeof(fnt_glyph_cache_entry_t *));
100 if (!np)
101 return 0;
103 font->glyphCache = np;
105 unsigned int page;
106 for (page = font->cacheMaxPageID + 1; page <= pageid; ++page)
107 font->glyphCache[page] = NULL;
109 font->cacheMaxPageID = pageid;
112 // if it already was allocated, skip this
113 if (font->glyphCache[pageid])
114 return 1;
116 // allocate the page
117 font->glyphCache[pageid] = malloc(sizeof(fnt_glyph_cache_entry_t) * GLYPH_CACHE_PAGE_SIZE);
119 int i;
120 for (i = 0; i < GLYPH_CACHE_PAGE_SIZE; ++i) {
121 font->glyphCache[pageid][i].isValid = 0;
122 font->glyphCache[pageid][i].atlas = NULL;
123 font->glyphCache[pageid][i].allocation = NULL;
126 return 1;
129 static void fntResetFontDef(font_t *fnt) {
130 LOG("fntResetFontDef\n");
131 fnt->glyphCache = NULL;
132 fnt->cacheMaxPageID = -1;
133 fnt->isValid = 0;
134 fnt->isDefault = 0;
136 int aid;
137 for(aid = 0; aid < ATLAS_MAX; ++aid)
138 fnt->atlases[aid] = NULL;
140 fnt->dataPtr = NULL;
143 static void fntPrepareCLUT() {
144 fontClut.PSM = GS_PSM_T8;
145 fontClut.ClutPSM = GS_PSM_CT32;
146 fontClut.Clut = memalign(128, 256 * 4);
147 fontClut.VramClut = 0;
149 // generate the clut table
150 size_t i;
151 u32 *clut = fontClut.Clut;
152 for (i = 0; i < 256; ++i) {
153 u8 alpha = i > 0x080 ? 0x080 : i;
155 *clut = alpha << 24 | i << 16 | i << 8 | i;
156 clut++;
160 static void fntDestroyCLUT() {
161 free(fontClut.Clut);
162 fontClut.Clut = NULL;
165 void fntInit(void) {
166 LOG("fntInit\n");
167 int error = FT_Init_FreeType(&font_library);
169 if (error) {
170 // just report over the ps2link
171 LOG("Freetype init failed with %x!\n", error);
172 // SleepThread();
175 fntPrepareCLUT();
177 gFontSema.init_count = 1;
178 gFontSema.max_count = 1;
179 gFontSema.option = 0;
180 gFontSemaId = CreateSema(&gFontSema);
182 int i;
184 for (i = 0; i < FNT_MAX_COUNT; ++i)
185 fntResetFontDef(&fonts[i]);
187 // load the default font (will be id=0)
188 fntLoad(NULL, -1, 0);
191 static void fntCacheFlushPage(fnt_glyph_cache_entry_t *page) {
192 LOG("fntCacheFlushPage\n");
193 int i;
195 for (i = 0; i < GLYPH_CACHE_PAGE_SIZE; ++i, ++page) {
196 page->isValid = 0;
197 // we're not doing any atlasFree or such - atlas has to be rebuild
198 page->allocation = NULL;
199 page->atlas = NULL;
203 static void fntCacheFlush(font_t *fnt) {
204 LOG("fntCacheFlush\n");
206 int i;
208 // Release all the glyphs from the cache
209 for (i = 0; i <= fnt->cacheMaxPageID; ++i) {
210 if (fnt->glyphCache[i]) {
211 fntCacheFlushPage(fnt->glyphCache[i]);
212 free(fnt->glyphCache[i]);
213 fnt->glyphCache[i] = NULL;
217 free(fnt->glyphCache);
218 fnt->glyphCache = NULL;
219 fnt->cacheMaxPageID = -1;
221 // free all atlasses too, they're invalid now anyway
222 int aid;
223 for(aid = 0; aid < ATLAS_MAX; ++aid) {
224 atlasFree(fnt->atlases[aid]);
225 fnt->atlases[aid] = NULL;
229 static int fntNewFont() {
230 LOG("fntNewFont\n");
231 int i;
232 for (i = 0; i < FNT_MAX_COUNT; ++i) {
233 if (fonts[i].isValid == 0) {
234 fntResetFontDef(&fonts[i]);
235 return i;
239 return FNT_ERROR;
242 void fntDeleteFont(font_t *font) {
243 LOG("fntDeleteFont\n");
245 // skip already deleted fonts
246 if (!font->isValid)
247 return;
249 // free the glyph cache, atlases, unload the font
250 fntCacheFlush(font);
252 FT_Done_Face(font->face);
254 if (font->dataPtr) {
255 free(font->dataPtr);
256 font->dataPtr = NULL;
259 font->isValid = 0;
262 int fntLoadSlot(font_t *fnt, void* buffer, int bufferSize) {
263 LOG("fntLoadSlot\n");
265 if (!buffer) {
266 buffer = &freesansfont_raw;
267 bufferSize = size_freesansfont_raw;
268 fnt->isDefault = 1;
271 // load the font via memory handle
272 int error = FT_New_Memory_Face(font_library, (FT_Byte*) buffer, bufferSize, 0, &fnt->face);
274 if (error) {
275 // just report over the ps2link
276 LOG("Freetype: Font loading failed with %x!\n", error);
277 // SleepThread();
278 return -1;
281 gCharHeight = 16;
283 error = FT_Set_Char_Size(fnt->face, 0, gCharHeight * 16, 300, 300);
284 /*error = FT_Set_Pixel_Sizes( face,
285 0, // pixel_width
286 gCharHeight ); // pixel_height*/
288 if (error) {
289 // just report over the ps2link
290 LOG("Freetype: Error setting font pixel size with %x!\n", error);
291 // SleepThread();
292 return -1;
295 fnt->isValid = 1;
296 return 0;
299 int fntLoad(void* buffer, int bufferSize, int takeover) {
300 LOG("fntLoad\n");
302 // we need a new slot in the font array
303 int fontID = fntNewFont();
305 if (fontID == FNT_ERROR)
306 return FNT_ERROR;
308 font_t *fnt = &fonts[fontID];
310 if (fntLoadSlot(fnt, buffer, bufferSize) == FNT_ERROR)
311 return FNT_ERROR;
313 if (takeover)
314 fnt->dataPtr = buffer;
316 return fontID;
319 int fntLoadFile(char* path) {
320 LOG("fntLoadFile\n");
321 // load the buffer with font
322 int size = -1;
323 void* customFont = readFile(path, -1, &size);
325 if (!customFont)
326 return FNT_ERROR;
328 int fontID = fntLoad(customFont, size, 1);
330 return fontID;
333 void fntRelease(int id) {
334 LOG("fntRelease\n");
335 if (id < FNT_MAX_COUNT)
336 fntDeleteFont(&fonts[id]);
339 /** Terminates the font rendering system */
340 void fntEnd(void) {
341 LOG("fntEnd\n");
342 // release all the fonts
343 int id;
344 for (id = 0; id < FNT_MAX_COUNT; ++id)
345 fntDeleteFont(&fonts[id]);
347 // deinit freetype system
348 FT_Done_FreeType(font_library);
350 DeleteSema(gFontSemaId);
352 fntDestroyCLUT();
355 static atlas_t *fntNewAtlas() {
356 atlas_t *atl = atlasNew(ATLAS_WIDTH, ATLAS_HEIGHT, GS_PSM_T8);
358 atl->surface.ClutPSM = GS_PSM_CT32;
359 atl->surface.Clut = (u32*)(&fontClut);
361 return atl;
364 static int fntGlyphAtlasPlace(font_t *fnt, fnt_glyph_cache_entry_t* glyph) {
365 FT_GlyphSlot slot = fnt->face->glyph;
367 LOG("fntGlyphAtlasPlace: Placing the glyph... %d x %d\n", slot->bitmap.width, slot->bitmap.rows);
369 if (slot->bitmap.width == 0 || slot->bitmap.rows == 0) {
370 // no bitmap glyph, just skip
371 return 1;
374 int aid;
376 for (aid = 0; aid < ATLAS_MAX; ++aid) {
377 LOG(" * Placing aid %d...\n", aid);
378 atlas_t **atl = &fnt->atlases[aid];
379 if (!*atl) { // atlas slot not yet used
380 LOG(" * aid %d is new...\n", aid);
381 *atl = fntNewAtlas();
384 glyph->allocation =
385 atlasPlace(*atl, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.buffer);
387 if (glyph->allocation) {
388 LOG(" * found placement\n", aid);
389 glyph->atlas = *atl;
391 return 1;
395 LOG(" * ! no atlas free\n", aid);
396 return 0;
399 /** Internal method. Makes sure the bitmap data for particular character are pre-rendered to the glyph cache */
400 static fnt_glyph_cache_entry_t* fntCacheGlyph(font_t *fnt, uint32_t gid) {
401 // calc page id and in-page index from glyph id
402 int pageid = gid / GLYPH_CACHE_PAGE_SIZE;
403 int idx = gid % GLYPH_CACHE_PAGE_SIZE;
405 // do not call on every char of every font rendering call
406 if (!GLYPH_PAGE_OK(fnt,pageid))
407 if (!fntPrepareGlyphCachePage(fnt, pageid)) // failed to prepare the page...
408 return NULL;
410 fnt_glyph_cache_entry_t *page = fnt->glyphCache[pageid];
412 /* Should never happen.
413 if (!page) // safeguard
414 return NULL;
417 fnt_glyph_cache_entry_t* glyph = &page[idx];
419 if (glyph->isValid)
420 return glyph;
422 // not cached but valid. Cache
423 if (!fnt->face) {
424 LOG("FNT: Face is NULL!\n");
427 int error = FT_Load_Char(fnt->face, gid, FT_LOAD_RENDER);
429 if (error) {
430 LOG("FNT: Error loading glyph - %d\n", error);
431 return NULL;
434 // find atlas placement for the glyph
435 if (!fntGlyphAtlasPlace(fnt, glyph))
436 return NULL;
438 FT_GlyphSlot slot = fnt->face->glyph;
439 glyph->width = slot->bitmap.width;
440 glyph->height = slot->bitmap.rows;
441 glyph->shx = slot->advance.x;
442 glyph->shy = slot->advance.y;
443 glyph->ox = slot->bitmap_left;
444 glyph->oy = gCharHeight - slot->bitmap_top;
446 glyph->isValid = 1;
448 return glyph;
451 void fntSetAspectRatio(float aw, float ah) {
452 LOG("fntSetAspectRatio\n");
453 // flush cache - it will be invalid after the setting
454 int i;
456 for (i = 0; i < FNT_MAX_COUNT; ++i) {
457 if (fonts[i].isValid)
458 fntCacheFlush(&fonts[i]);
461 // set new aspect ratio (Is this correct, I wonder?)
462 // TODO: error = FT_Set_Char_Size(face, 0, gCharHeight*64, ah*300, aw*300);
465 int fntRenderString(int font, int x, int y, short aligned, const unsigned char* string, u64 colour) {
466 // wait for font lock to unlock
467 WaitSema(gFontSemaId);
468 font_t *fnt = &fonts[font];
469 SignalSema(gFontSemaId);
471 if (aligned) {
472 x -= fntCalcDimensions(font, string) >> 1;
473 y -= MENU_ITEM_HEIGHT >> 1;
476 rmApplyShiftRatio(&y);
478 uint32_t codepoint;
479 uint32_t state = 0;
480 FT_Bool use_kerning = FT_HAS_KERNING(fnt->face);
481 FT_UInt glyph_index, previous = 0;
482 FT_Vector delta;
484 // cache glyphs and render as we go
485 for (; *string; ++string) {
486 if (utf8Decode(&state, &codepoint, *string)) // accumulate the codepoint value
487 continue;
489 fnt_glyph_cache_entry_t* glyph = fntCacheGlyph(fnt, codepoint);
490 if (!glyph) {
491 continue;
494 // kerning
495 if (use_kerning && previous) {
496 glyph_index = FT_Get_Char_Index(fnt->face, codepoint);
497 if (glyph_index) {
498 FT_Get_Kerning(fnt->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
499 x += delta.x >> 6;
501 previous = glyph_index;
504 // only if glyph has atlas placement
505 if (glyph->allocation) {
506 /* TODO: Ineffective on many parts:
507 * 1. Usage of floats for UV - fixed point should suffice (and is used internally by GS for UV)
509 * 2. GS_SETREG_TEX0 for every quad - why? gsKit should only set texture if demanded
510 * We should prepare a special fnt render method that would step over most of the
511 * performance problems under - begining with rmSetupQuad and continuing into gsKit
512 * - this method would handle the preparetion of the quads and GS upload itself,
513 * without the use of prim_quad_texture and rmSetupQuad...
515 * (3. rmSetupQuad is cool for a few quads a frame, but glyphs are too many in numbers
516 * - seriously, all that branching for every letter is a bit much)
518 * 4. We should use clut to keep the memory allocations sane - we're linear in the 32bit buffers
519 * anyway, no reason to waste like we do!
521 quad.color = colour;
522 quad.ul.x = x + glyph->ox;
523 quad.br.x = quad.ul.x + glyph->width;
524 quad.ul.y = y + glyph->oy;
525 quad.br.y = quad.ul.y + glyph->height;
527 // UV is our own, custom thing here
528 quad.txt = &glyph->atlas->surface;
529 quad.ul.u = glyph->allocation->x;
530 quad.br.u = quad.ul.u + glyph->width;
531 quad.ul.v = glyph->allocation->y;
532 quad.br.v = quad.ul.v + glyph->height;
534 rmDrawQuad(&quad);
537 x += glyph->shx >> 6;
538 y += glyph->shy >> 6;
541 return x;
544 void fntRenderText(int font, int sx, int sy, short aligned, size_t width, size_t height, const unsigned char* string, u64 colour) {
545 // wait for font lock to unlock
546 WaitSema(gFontSemaId);
547 font_t *fnt = &fonts[font];
548 SignalSema(gFontSemaId);
550 if (aligned) {
551 sx -= min(fntCalcDimensions(font, string), width) >> 1;
552 sy -= MENU_ITEM_HEIGHT >> 1;
555 rmApplyShiftRatio(&sy);
557 int x = sx;
558 int y = sy;
559 int xm = sx + width;
560 int ym = sy + height;
562 uint32_t codepoint;
563 uint32_t state = 0;
564 FT_Bool use_kerning = FT_HAS_KERNING(fnt->face);
565 FT_UInt glyph_index, previous = 0;
566 FT_Vector delta;
568 // Note: We need to change this so that we'll accumulate whole word before doing a layout with it
569 // for now this method breaks on any character - which is a bit ugly
571 // I don't want to do anything complicated though so I'd say
572 // we should instead have a dynamic layout routine that'll replace spaces with newlines as appropriate
573 // because that'll make the code run only once per N frames, not every frame
575 // cache glyphs and render as we go
576 for (; *string; ++string) {
577 if (utf8Decode(&state, &codepoint, *string)) // accumulate the codepoint value
578 continue;
580 fnt_glyph_cache_entry_t* glyph = fntCacheGlyph(fnt, codepoint);
581 if (!glyph)
582 continue;
584 // kerning
585 if (use_kerning && previous) {
586 glyph_index = FT_Get_Char_Index(fnt->face, codepoint);
587 if (glyph_index) {
588 FT_Get_Kerning(fnt->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
589 x += delta.x >> 6;
591 previous = glyph_index;
594 if (codepoint == '\n') {
595 x = sx;
596 y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
597 continue;
600 if ((x + glyph->width > xm) || (y > ym)) // stepped over the max
601 break;
603 // only if glyph has atlas placement
604 if (glyph->allocation) {
605 quad.color = colour;
606 quad.ul.x = x + glyph->ox;
607 quad.br.x = quad.ul.x + glyph->width;
608 quad.ul.y = y + glyph->oy;
609 quad.br.y = quad.ul.y + glyph->height;
611 // UV is our own, custom thing here
612 quad.txt = &glyph->atlas->surface;
613 quad.ul.u = glyph->allocation->x;
614 quad.br.u = quad.ul.u + glyph->width;
615 quad.ul.v = glyph->allocation->y;
616 quad.br.v = quad.ul.v + glyph->height;
618 rmDrawQuad(&quad);
621 x += glyph->shx >> 6;
622 y += glyph->shy >> 6;
626 void fntFitString(int font, unsigned char *string, size_t width) {
627 size_t cw = 0;
628 unsigned char *str = string;
629 size_t spacewidth = fntCalcDimensions(font, " ");
630 unsigned char *psp = NULL;
632 while (*str) {
633 // scan forward to the next whitespace
634 unsigned char *sp = str;
635 for (; *sp && *sp != ' ' && *sp != '\n'; ++sp);
637 // store what was there before
638 unsigned char osp = *sp;
640 // newline resets the situation
641 if (osp == '\n') {
642 cw = 0;
643 str = ++sp;
644 psp = NULL;
645 continue;
648 // terminate after the word
649 *sp = '\0';
651 // Calc the font's width...
652 // NOTE: The word was terminated, so we're seeing a single word
653 // on that position
654 size_t ww = fntCalcDimensions(font, str);
656 if (cw + ww > width) {
657 if (psp) {
658 // we have a prev space to utilise (wrap on it)
659 *psp = '\n';
660 *sp = osp;
661 cw = ww;
662 psp = sp;
663 } else {
664 // no prev. space to hijack, must break after the word
665 // this will mean overflowed text...
666 *sp = '\n';
667 cw = 0;
669 } else {
670 cw += ww;
671 *sp = osp;
672 psp = sp;
675 cw += spacewidth;
676 str = ++sp;
680 int fntCalcDimensions(int font, const unsigned char* str) {
681 int w = 0;
683 WaitSema(gFontSemaId);
684 font_t *fnt = &fonts[font];
685 SignalSema(gFontSemaId);
687 uint32_t codepoint;
688 uint32_t state = 0;
689 FT_Bool use_kerning = FT_HAS_KERNING(fnt->face);
690 FT_UInt glyph_index, previous = 0;
691 FT_Vector delta;
693 // cache glyphs and render as we go
694 for (; *str; ++str) {
695 unsigned char c = *str;
697 if (utf8Decode(&state, &codepoint, c)) // accumulate the codepoint value
698 continue;
700 // Could just as well only get the glyph dimensions
701 // but it is probable the glyphs will be needed anyway
702 fnt_glyph_cache_entry_t* glyph = fntCacheGlyph(fnt, codepoint);
703 if (!glyph) {
704 continue;
707 // kerning
708 if (use_kerning && previous) {
709 glyph_index = FT_Get_Char_Index(fnt->face, codepoint);
710 if (glyph_index) {
711 FT_Get_Kerning(fnt->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
712 w += delta.x >> 6;
714 previous = glyph_index;
717 w += glyph->shx >> 6;
720 return w;
723 void fntReplace(int id, void* buffer, int bufferSize, int takeover, int asDefault) {
724 font_t *fnt = &fonts[id];
726 font_t ndefault, old;
727 fntResetFontDef(&ndefault);
728 fntLoadSlot(&ndefault, buffer, bufferSize);
729 ndefault.isDefault = asDefault;
731 // copy over the new font definition
732 // we have to lock this phase, as the old font may still be used
733 WaitSema(gFontSemaId);
734 memcpy(&old, fnt, sizeof(font_t));
735 memcpy(fnt, &ndefault, sizeof(font_t));
737 if (takeover)
738 fnt->dataPtr = buffer;
740 SignalSema(gFontSemaId);
742 // delete the old font
743 fntDeleteFont(&old);
746 void fntSetDefault(int id) {
747 font_t *fnt = &fonts[id];
749 // already default
750 if (fnt->isDefault)
751 return;
753 font_t ndefault, old;
754 fntResetFontDef(&ndefault);
755 fntLoadSlot(&ndefault, NULL, -1);
757 // copy over the new font definition
758 // we have to lock this phase, as the old font may still be used
759 // Note: No check for concurrency is done here, which is kinda funky!
760 WaitSema(gFontSemaId);
761 memcpy(&old, fnt, sizeof(font_t));
762 memcpy(fnt, &ndefault, sizeof(font_t));
763 SignalSema(gFontSemaId);
765 // delete the old font
766 fntDeleteFont(&old);