3 Licenced under Academic Free License version 3.0
4 Review OpenUsbLd README & LICENSE files for further details.
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"
18 #include FT_FREETYPE_H
20 extern void* freesansfont_raw
;
21 extern int size_freesansfont_raw
;
23 /// Maximal count of atlases per font
25 /// Atlas width in pixels
26 #define ATLAS_WIDTH 128
27 /// Atlas height in pixels
28 #define ATLAS_HEIGHT 128
31 static FT_Library font_library
;
33 static int gCharHeight
= 16;
35 static s32 gFontSemaId
;
36 static ee_sema_t gFontSema
;
38 static GSCLUT fontClut
;
40 /** Single entry in the glyph cache */
43 // size in pixels of the glyph
45 // offsetting of the glyph
47 // advancements in pixels after rendering this glyph
50 // atlas for which the allocation was done
53 // atlas allocation position
54 struct atlas_allocation_t
*allocation
;
56 } fnt_glyph_cache_entry_t
;
58 /** A whole font definition */
60 /** GLYPH CACHE. Every glyph in the ASCII range is cached when first used
61 * this means no additional memory aside from the one needed to render the
62 * character set is used.
64 fnt_glyph_cache_entry_t
**glyphCache
;
66 /// Maximal font cache page index
72 /// Nonzero if font is used
75 /// Texture atlases (default to NULL)
76 atlas_t
*atlases
[ATLAS_MAX
];
78 /// Pointer to data, if allocation takeover was selected (will be freed)
82 #define FNT_MAX_COUNT (16)
84 /// Array of font definitions
85 static font_t fonts
[FNT_MAX_COUNT
];
87 static rm_quad_t quad
;
88 static uint32_t codepoint
, state
;
89 static fnt_glyph_cache_entry_t
* glyph
;
90 static FT_Bool use_kerning
;
91 static FT_UInt glyph_index
, previous
;
92 static FT_Vector delta
;
94 #define GLYPH_CACHE_PAGE_SIZE 256
96 #define GLYPH_PAGE_OK(font,page) ((pageid <= font->cacheMaxPageID) && (font->glyphCache[page]))
98 static void fntCacheFlushPage(fnt_glyph_cache_entry_t
*page
) {
101 for (i
= 0; i
< GLYPH_CACHE_PAGE_SIZE
; ++i
, ++page
) {
103 // we're not doing any atlasFree or such - atlas has to be rebuild
104 page
->allocation
= NULL
;
109 static void fntCacheFlush(font_t
*font
) {
110 // Release all the glyphs from the cache
112 for (i
= 0; i
<= font
->cacheMaxPageID
; ++i
) {
113 if (font
->glyphCache
[i
]) {
114 fntCacheFlushPage(font
->glyphCache
[i
]);
115 free(font
->glyphCache
[i
]);
116 font
->glyphCache
[i
] = NULL
;
120 free(font
->glyphCache
);
121 font
->glyphCache
= NULL
;
122 font
->cacheMaxPageID
= -1;
124 // free all atlasses too, they're invalid now anyway
126 for(aid
= 0; aid
< ATLAS_MAX
; ++aid
) {
127 atlasFree(font
->atlases
[aid
]);
128 font
->atlases
[aid
] = NULL
;
132 static int fntPrepareGlyphCachePage(font_t
*font
, int pageid
) {
133 if (pageid
> font
->cacheMaxPageID
) {
134 fnt_glyph_cache_entry_t
**np
= realloc(font
->glyphCache
, (pageid
+ 1) * sizeof(fnt_glyph_cache_entry_t
*));
139 font
->glyphCache
= np
;
142 for (page
= font
->cacheMaxPageID
+ 1; page
<= pageid
; ++page
)
143 font
->glyphCache
[page
] = NULL
;
145 font
->cacheMaxPageID
= pageid
;
148 // if it already was allocated, skip this
149 if (font
->glyphCache
[pageid
])
153 font
->glyphCache
[pageid
] = malloc(sizeof(fnt_glyph_cache_entry_t
) * GLYPH_CACHE_PAGE_SIZE
);
156 for (i
= 0; i
< GLYPH_CACHE_PAGE_SIZE
; ++i
) {
157 font
->glyphCache
[pageid
][i
].isValid
= 0;
158 font
->glyphCache
[pageid
][i
].atlas
= NULL
;
159 font
->glyphCache
[pageid
][i
].allocation
= NULL
;
165 static void fntPrepareCLUT() {
166 fontClut
.PSM
= GS_PSM_T8
;
167 fontClut
.ClutPSM
= GS_PSM_CT32
;
168 fontClut
.Clut
= memalign(128, 256 * 4);
169 fontClut
.VramClut
= 0;
171 // generate the clut table
173 u32
*clut
= fontClut
.Clut
;
174 for (i
= 0; i
< 256; ++i
) {
175 u8 alpha
= i
> 0x080 ? 0x080 : i
;
177 *clut
= alpha
<< 24 | i
<< 16 | i
<< 8 | i
;
182 static void fntDestroyCLUT() {
184 fontClut
.Clut
= NULL
;
187 static void fntInitSlot(font_t
*font
) {
189 font
->glyphCache
= NULL
;
190 font
->cacheMaxPageID
= -1;
191 font
->dataPtr
= NULL
;
195 for(; aid
< ATLAS_MAX
; ++aid
)
196 font
->atlases
[aid
] = NULL
;
199 static void fntDeleteSlot(font_t
*font
) {
200 // free the glyph cache, atlases, unload the font
203 FT_Done_Face(font
->face
);
208 font
->dataPtr
= NULL
;
214 void fntRelease(int id
) {
215 if (id
> FNT_DEFAULT
&& id
< FNT_MAX_COUNT
)
216 fntDeleteSlot(&fonts
[id
]);
219 static int fntLoadSlot(font_t
*font
, char* path
) {
226 buffer
= readFile(path
, -1, &bufferSize
);
228 LOG("FNTSYS Font file loading failed: %s\n", path
);
231 font
->dataPtr
= buffer
;
233 buffer
= &freesansfont_raw
;
234 bufferSize
= size_freesansfont_raw
;
237 // load the font via memory handle
238 int error
= FT_New_Memory_Face(font_library
, (FT_Byte
*) buffer
, bufferSize
, 0, &font
->face
);
240 LOG("FNTSYS Freetype font loading failed with %x!\n", error
);
245 error
= FT_Set_Char_Size(font
->face
, 0, gCharHeight
* 16, 300, 300);
246 /*error = FT_Set_Pixel_Sizes( face, 0, // pixel_width gCharHeight ); // pixel_height */
248 LOG("FNTSYS Freetype error setting font pixel size with %x!\n", error
);
258 LOG("FNTSYS Init\n");
259 int error
= FT_Init_FreeType(&font_library
);
261 // just report over the ps2link
262 LOG("FNTSYS Freetype init failed with %x!\n", error
);
268 gFontSema
.init_count
= 1;
269 gFontSema
.max_count
= 1;
270 gFontSema
.option
= 0;
271 gFontSemaId
= CreateSema(&gFontSema
);
274 for (; i
< FNT_MAX_COUNT
; ++i
)
275 fntInitSlot(&fonts
[i
]);
277 fntLoadDefault(NULL
);
280 int fntLoadFile(char* path
) {
283 for (; i
< FNT_MAX_COUNT
; i
++) {
285 if (!font
->isValid
) {
286 if (fntLoadSlot(font
, path
) != FNT_ERROR
)
295 void fntLoadDefault(char* path
) {
296 font_t newFont
, oldFont
;
298 if (fntLoadSlot(&newFont
, path
) != FNT_ERROR
) {
299 // copy over the new font definition
300 // we have to lock this phase, as the old font may still be used
301 // Note: No check for concurrency is done here, which is kinda funky!
302 WaitSema(gFontSemaId
);
303 memcpy(&oldFont
, &fonts
[FNT_DEFAULT
], sizeof(font_t
));
304 memcpy(&fonts
[FNT_DEFAULT
], &newFont
, sizeof(font_t
));
305 SignalSema(gFontSemaId
);
307 // delete the old font
308 fntDeleteSlot(&oldFont
);
314 // release all the fonts
316 for (id
= 0; id
< FNT_MAX_COUNT
; ++id
)
317 fntDeleteSlot(&fonts
[id
]);
319 // deinit freetype system
320 FT_Done_FreeType(font_library
);
322 DeleteSema(gFontSemaId
);
327 static atlas_t
*fntNewAtlas() {
328 atlas_t
*atl
= atlasNew(ATLAS_WIDTH
, ATLAS_HEIGHT
, GS_PSM_T8
);
330 atl
->surface
.ClutPSM
= GS_PSM_CT32
;
331 atl
->surface
.Clut
= (u32
*)(&fontClut
);
336 static int fntGlyphAtlasPlace(font_t
*font
, fnt_glyph_cache_entry_t
* glyph
) {
337 FT_GlyphSlot slot
= font
->face
->glyph
;
339 //LOG("FNTSYS GlyphAtlasPlace: Placing the glyph... %d x %d\n", slot->bitmap.width, slot->bitmap.rows);
341 if (slot
->bitmap
.width
== 0 || slot
->bitmap
.rows
== 0) {
342 // no bitmap glyph, just skip
347 for (; aid
< ATLAS_MAX
; aid
++) {
348 //LOG("FNTSYS Placing aid %d...\n", aid);
349 atlas_t
**atl
= &font
->atlases
[aid
];
350 if (!*atl
) { // atlas slot not yet used
351 //LOG("FNTSYS aid %d is new...\n", aid);
352 *atl
= fntNewAtlas();
355 glyph
->allocation
= atlasPlace(*atl
, slot
->bitmap
.width
, slot
->bitmap
.rows
, slot
->bitmap
.buffer
);
356 if (glyph
->allocation
) {
357 //LOG("FNTSYS Found placement\n", aid);
364 LOG("FNTSYS No atlas free\n", aid
);
368 /** Internal method. Makes sure the bitmap data for particular character are pre-rendered to the glyph cache */
369 static fnt_glyph_cache_entry_t
* fntCacheGlyph(font_t
*font
, uint32_t gid
) {
370 // calc page id and in-page index from glyph id
371 int pageid
= gid
/ GLYPH_CACHE_PAGE_SIZE
;
372 int idx
= gid
% GLYPH_CACHE_PAGE_SIZE
;
374 // do not call on every char of every font rendering call
375 if (!GLYPH_PAGE_OK(font
, pageid
))
376 if (!fntPrepareGlyphCachePage(font
, pageid
)) // failed to prepare the page...
379 fnt_glyph_cache_entry_t
*page
= font
->glyphCache
[pageid
];
380 /* Should never happen.
381 if (!page) // safeguard
385 fnt_glyph_cache_entry_t
* glyph
= &page
[idx
];
389 // not cached but valid. Cache
391 LOG("FNTSYS Face is NULL!\n");
394 int error
= FT_Load_Char(font
->face
, gid
, FT_LOAD_RENDER
);
396 LOG("FNTSYS Error loading glyph - %d\n", error
);
400 // find atlas placement for the glyph
401 if (!fntGlyphAtlasPlace(font
, glyph
))
404 FT_GlyphSlot slot
= font
->face
->glyph
;
405 glyph
->width
= slot
->bitmap
.width
;
406 glyph
->height
= slot
->bitmap
.rows
;
407 glyph
->shx
= slot
->advance
.x
;
408 glyph
->shy
= slot
->advance
.y
;
409 glyph
->ox
= slot
->bitmap_left
;
410 glyph
->oy
= gCharHeight
- slot
->bitmap_top
;
417 void fntSetAspectRatio(float aw
, float ah
) {
418 // flush cache - it will be invalid after the setting
420 for (; i
< FNT_MAX_COUNT
; i
++) {
421 if (fonts
[i
].isValid
)
422 fntCacheFlush(&fonts
[i
]);
425 // TODO: set new aspect ratio (Is this correct, I wonder?)
426 // error = FT_Set_Char_Size(face, 0, gCharHeight*64, ah*300, aw*300);
429 static void fntRenderGlyph(fnt_glyph_cache_entry_t
* glyph
, int pen_x
, int pen_y
) {
430 // only if glyph has atlas placement
431 if (glyph
->allocation
) {
432 /* TODO: Ineffective on many parts:
433 * 1. Usage of floats for UV - fixed point should suffice (and is used internally by GS for UV)
435 * 2. GS_SETREG_TEX0 for every quad - why? gsKit should only set texture if demanded
436 * We should prepare a special fnt render method that would step over most of the
437 * performance problems under - beginning with rmSetupQuad and continuing into gsKit
438 * - this method would handle the preparation of the quads and GS upload itself,
439 * without the use of prim_quad_texture and rmSetupQuad...
441 * 3. We should use clut to keep the memory allocations sane - we're linear in the 32bit buffers
442 * anyway, no reason to waste like we do!
444 quad
.ul
.x
= pen_x
+ glyph
->ox
;
445 quad
.br
.x
= quad
.ul
.x
+ glyph
->width
;
446 quad
.ul
.y
= pen_y
+ glyph
->oy
;
447 quad
.br
.y
= quad
.ul
.y
+ glyph
->height
;
449 // UV is our own, custom thing here
450 quad
.txt
= &glyph
->atlas
->surface
;
451 quad
.ul
.u
= glyph
->allocation
->x
;
452 quad
.br
.u
= quad
.ul
.u
+ glyph
->width
;
453 quad
.ul
.v
= glyph
->allocation
->y
;
454 quad
.br
.v
= quad
.ul
.v
+ glyph
->height
;
461 int fntRenderString(int id
, int x
, int y
, short aligned
, size_t width
, size_t height
, const unsigned char* string
, u64 colour
) {
462 // wait for font lock to unlock
463 WaitSema(gFontSemaId
);
464 font_t
*font
= &fonts
[id
];
465 SignalSema(gFontSemaId
);
469 x
-= min(fntCalcDimensions(id
, string
), width
) >> 1;
471 x
-= fntCalcDimensions(id
, string
) >> 1;
473 y
-= MENU_ITEM_HEIGHT
>> 1;
476 rmApplyShiftRatio(&y
);
480 int xmax
= x
+ width
;
481 int ymax
= y
+ height
;
483 use_kerning
= FT_HAS_KERNING(font
->face
);
487 // Note: We need to change this so that we'll accumulate whole word before doing a layout with it
488 // for now this method breaks on any character - which is a bit ugly
490 // I don't want to do anything complicated though so I'd say
491 // we should instead have a dynamic layout routine that'll replace spaces with newlines as appropriate
492 // because that'll make the code run only once per N frames, not every frame
494 // cache glyphs and render as we go
495 for (; *string
; ++string
) {
496 if (utf8Decode(&state
, &codepoint
, *string
)) // accumulate the codepoint value
499 glyph
= fntCacheGlyph(font
, codepoint
);
504 if (use_kerning
&& previous
) {
505 glyph_index
= FT_Get_Char_Index(font
->face
, codepoint
);
507 FT_Get_Kerning(font
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
508 pen_x
+= delta
.x
>> 6;
510 previous
= glyph_index
;
514 if (codepoint
== '\n') {
516 y
+= MENU_ITEM_HEIGHT
; // hmax is too tight and unordered, generally
520 if (y
> ymax
) // stepped over the max
523 if (pen_x
+ glyph
->width
> xmax
) {
524 pen_x
= xmax
+ 1; // to be sure no other cahr will be written (even not a smaller one just following)
529 fntRenderGlyph(glyph
, pen_x
, y
);
530 pen_x
+= glyph
->shx
>> 6;
537 static void fntRenderSubRTL(font_t
*font
, const unsigned char* startRTL
, const unsigned char* string
, fnt_glyph_cache_entry_t
* glyph
, int x
, int y
) {
539 x
-= glyph
->shx
>> 6;
540 fntRenderGlyph(glyph
, x
, y
);
543 for (; startRTL
!= string
; ++startRTL
) {
544 if (utf8Decode(&state
, &codepoint
, *startRTL
))
547 glyph
= fntCacheGlyph(font
, codepoint
);
551 if (use_kerning
&& previous
) {
552 glyph_index
= FT_Get_Char_Index(font
->face
, codepoint
);
554 FT_Get_Kerning(font
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
557 previous
= glyph_index
;
560 x
-= glyph
->shx
>> 6;
561 fntRenderGlyph(glyph
, x
, y
);
565 int fntRenderString(int id
, int x
, int y
, short aligned
, size_t width
, size_t height
, const unsigned char* string
, u64 colour
) {
566 // wait for font lock to unlock
567 WaitSema(gFontSemaId
);
568 font_t
*font
= &fonts
[id
];
569 SignalSema(gFontSemaId
);
573 x
-= min(fntCalcDimensions(id
, string
), width
) >> 1;
575 x
-= fntCalcDimensions(id
, string
) >> 1;
577 y
-= MENU_ITEM_HEIGHT
>> 1;
580 rmApplyShiftRatio(&y
);
584 /*int xmax = x + width;
585 int ymax = y + height;*/
587 use_kerning
= FT_HAS_KERNING(font
->face
);
592 int delta_x
, pen_xRTL
= 0;
593 fnt_glyph_cache_entry_t
* glyphRTL
= NULL
;
594 const unsigned char* startRTL
= NULL
;
596 // cache glyphs and render as we go
597 for (; *string
; ++string
) {
598 if (utf8Decode(&state
, &codepoint
, *string
)) // accumulate the codepoint value
601 glyph
= fntCacheGlyph(font
, codepoint
);
607 if (use_kerning
&& previous
) {
608 glyph_index
= FT_Get_Char_Index(font
->face
, codepoint
);
610 FT_Get_Kerning(font
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
611 delta_x
= delta
.x
>> 6;
613 previous
= glyph_index
;
618 if (codepoint == '\n') {
620 y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
624 if ((x + glyph_w > xmax) || (y > ymax)) // stepped over the max
628 if (codepoint
> 0xFF) {
633 startRTL
= string
+ 1;
635 } else if ((codepoint
> 96 && codepoint
< 123) || (codepoint
> 64 && codepoint
< 91)) {
636 if (inRTL
) { // render RTL
639 fntRenderSubRTL(font
, startRTL
, string
, glyphRTL
, pen_xRTL
, y
);
644 pen_xRTL
+= delta_x
+ (glyph
->shx
>> 6);
647 fntRenderGlyph(glyph
, pen_x
, y
);
648 pen_x
+= glyph
->shx
>> 6;
655 fntRenderSubRTL(font
, startRTL
, string
, glyphRTL
, pen_xRTL
, y
);
662 void fntFitString(int id
, unsigned char *string
, size_t width
) {
664 unsigned char *str
= string
;
665 size_t spacewidth
= fntCalcDimensions(id
, " ");
666 unsigned char *psp
= NULL
;
669 // scan forward to the next whitespace
670 unsigned char *sp
= str
;
671 for (; *sp
&& *sp
!= ' ' && *sp
!= '\n'; ++sp
);
673 // store what was there before
674 unsigned char osp
= *sp
;
676 // newline resets the situation
684 // terminate after the word
687 // Calc the font's width...
688 // NOTE: The word was terminated, so we're seeing a single word
690 size_t ww
= fntCalcDimensions(id
, str
);
692 if (cw
+ ww
> width
) {
694 // we have a prev space to utilise (wrap on it)
700 // no prev. space to hijack, must break after the word
701 // this will mean overflowed text...
716 int fntCalcDimensions(int id
, const unsigned char* str
) {
719 WaitSema(gFontSemaId
);
720 font_t
*font
= &fonts
[id
];
721 SignalSema(gFontSemaId
);
724 uint32_t state
= UTF8_ACCEPT
;
725 FT_Bool use_kerning
= FT_HAS_KERNING(font
->face
);
726 FT_UInt glyph_index
, previous
= 0;
729 // cache glyphs and render as we go
730 for (; *str
; ++str
) {
731 if (utf8Decode(&state
, &codepoint
, *str
)) // accumulate the codepoint value
734 // Could just as well only get the glyph dimensions
735 // but it is probable the glyphs will be needed anyway
736 fnt_glyph_cache_entry_t
* glyph
= fntCacheGlyph(font
, codepoint
);
741 if (use_kerning
&& previous
) {
742 glyph_index
= FT_Get_Char_Index(font
->face
, codepoint
);
744 FT_Get_Kerning(font
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
747 previous
= glyph_index
;
750 w
+= glyph
->shx
>> 6;