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
;
34 static int screenWidth
;
35 static int screenHeight
;
37 static s32 gFontSemaId
;
38 static ee_sema_t gFontSema
;
40 static GSCLUT fontClut
;
42 /** Single entry in the glyph cache */
45 // size in pixels of the glyph
47 // offsetting of the glyph
49 // advancements in pixels after rendering this glyph
52 // atlas for which the allocation was done
55 // atlas allocation position
56 struct atlas_allocation_t
*allocation
;
58 } fnt_glyph_cache_entry_t
;
60 /** A whole font definition */
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
74 /// Nonzero if font is used
77 /// Nonzero for custom fonts
80 /// Texture atlases (default to NULL)
81 atlas_t
*atlases
[ATLAS_MAX
];
83 /// Pointer to data, if allocation takeover was selected (will be freed)
87 #define FNT_MAX_COUNT (16)
89 /// Array of font definitions
90 static font_t fonts
[FNT_MAX_COUNT
];
92 static rm_quad_t quad
;
93 static uint32_t codepoint
, state
;
94 static fnt_glyph_cache_entry_t
* glyph
;
95 static FT_Bool use_kerning
;
96 static FT_UInt glyph_index
, previous
;
97 static FT_Vector delta
;
99 #define GLYPH_CACHE_PAGE_SIZE 256
101 #define GLYPH_PAGE_OK(font,page) ((pageid <= font->cacheMaxPageID) && (font->glyphCache[page]))
103 void fntReloadScreenExtents() {
104 rmGetScreenExtents(&screenWidth
, &screenHeight
);
107 static int fntPrepareGlyphCachePage(font_t
*font
, int pageid
) {
108 if (pageid
> font
->cacheMaxPageID
) {
109 fnt_glyph_cache_entry_t
**np
= realloc(font
->glyphCache
, (pageid
+ 1) * sizeof(fnt_glyph_cache_entry_t
*));
114 font
->glyphCache
= np
;
117 for (page
= font
->cacheMaxPageID
+ 1; page
<= pageid
; ++page
)
118 font
->glyphCache
[page
] = NULL
;
120 font
->cacheMaxPageID
= pageid
;
123 // if it already was allocated, skip this
124 if (font
->glyphCache
[pageid
])
128 font
->glyphCache
[pageid
] = malloc(sizeof(fnt_glyph_cache_entry_t
) * GLYPH_CACHE_PAGE_SIZE
);
131 for (i
= 0; i
< GLYPH_CACHE_PAGE_SIZE
; ++i
) {
132 font
->glyphCache
[pageid
][i
].isValid
= 0;
133 font
->glyphCache
[pageid
][i
].atlas
= NULL
;
134 font
->glyphCache
[pageid
][i
].allocation
= NULL
;
140 static void fntResetFontDef(font_t
*fnt
) {
141 LOG("fntResetFontDef\n");
142 fnt
->glyphCache
= NULL
;
143 fnt
->cacheMaxPageID
= -1;
148 for(aid
= 0; aid
< ATLAS_MAX
; ++aid
)
149 fnt
->atlases
[aid
] = NULL
;
154 static void fntPrepareCLUT() {
155 fontClut
.PSM
= GS_PSM_T8
;
156 fontClut
.ClutPSM
= GS_PSM_CT32
;
157 fontClut
.Clut
= memalign(128, 256 * 4);
158 fontClut
.VramClut
= 0;
160 // generate the clut table
162 u32
*clut
= fontClut
.Clut
;
163 for (i
= 0; i
< 256; ++i
) {
164 u8 alpha
= i
> 0x080 ? 0x080 : i
;
166 *clut
= alpha
<< 24 | i
<< 16 | i
<< 8 | i
;
171 static void fntDestroyCLUT() {
173 fontClut
.Clut
= NULL
;
178 int error
= FT_Init_FreeType(&font_library
);
181 // just report over the ps2link
182 LOG("Freetype init failed with %x!\n", error
);
188 gFontSema
.init_count
= 1;
189 gFontSema
.max_count
= 1;
190 gFontSema
.option
= 0;
191 gFontSemaId
= CreateSema(&gFontSema
);
193 fntReloadScreenExtents();
196 for (i
= 0; i
< FNT_MAX_COUNT
; ++i
)
197 fntResetFontDef(&fonts
[i
]);
199 // load the default font (will be id=0)
200 fntLoad(NULL
, -1, 0);
203 static void fntCacheFlushPage(fnt_glyph_cache_entry_t
*page
) {
204 LOG("fntCacheFlushPage\n");
207 for (i
= 0; i
< GLYPH_CACHE_PAGE_SIZE
; ++i
, ++page
) {
209 // we're not doing any atlasFree or such - atlas has to be rebuild
210 page
->allocation
= NULL
;
215 static void fntCacheFlush(font_t
*fnt
) {
216 LOG("fntCacheFlush\n");
220 // Release all the glyphs from the cache
221 for (i
= 0; i
<= fnt
->cacheMaxPageID
; ++i
) {
222 if (fnt
->glyphCache
[i
]) {
223 fntCacheFlushPage(fnt
->glyphCache
[i
]);
224 free(fnt
->glyphCache
[i
]);
225 fnt
->glyphCache
[i
] = NULL
;
229 free(fnt
->glyphCache
);
230 fnt
->glyphCache
= NULL
;
231 fnt
->cacheMaxPageID
= -1;
233 // free all atlasses too, they're invalid now anyway
235 for(aid
= 0; aid
< ATLAS_MAX
; ++aid
) {
236 atlasFree(fnt
->atlases
[aid
]);
237 fnt
->atlases
[aid
] = NULL
;
241 static int fntNewFont() {
244 for (i
= 0; i
< FNT_MAX_COUNT
; ++i
) {
245 if (fonts
[i
].isValid
== 0) {
246 fntResetFontDef(&fonts
[i
]);
254 void fntDeleteFont(font_t
*font
) {
255 LOG("fntDeleteFont\n");
257 // skip already deleted fonts
261 // free the glyph cache, atlases, unload the font
264 FT_Done_Face(font
->face
);
268 font
->dataPtr
= NULL
;
274 int fntLoadSlot(font_t
*fnt
, void* buffer
, int bufferSize
) {
275 LOG("fntLoadSlot\n");
278 buffer
= &freesansfont_raw
;
279 bufferSize
= size_freesansfont_raw
;
283 // load the font via memory handle
284 int error
= FT_New_Memory_Face(font_library
, (FT_Byte
*) buffer
, bufferSize
, 0, &fnt
->face
);
287 // just report over the ps2link
288 LOG("Freetype: Font loading failed with %x!\n", error
);
295 error
= FT_Set_Char_Size(fnt
->face
, 0, gCharHeight
* 16, 300, 300);
296 /*error = FT_Set_Pixel_Sizes( face,
298 gCharHeight ); // pixel_height*/
301 // just report over the ps2link
302 LOG("Freetype: Error setting font pixel size with %x!\n", error
);
311 int fntLoad(void* buffer
, int bufferSize
, int takeover
) {
314 // we need a new slot in the font array
315 int fontID
= fntNewFont();
317 if (fontID
== FNT_ERROR
)
320 font_t
*fnt
= &fonts
[fontID
];
322 if (fntLoadSlot(fnt
, buffer
, bufferSize
) == FNT_ERROR
)
326 fnt
->dataPtr
= buffer
;
331 int fntLoadFile(char* path
) {
332 LOG("fntLoadFile\n");
333 // load the buffer with font
335 void* customFont
= readFile(path
, -1, &size
);
340 int fontID
= fntLoad(customFont
, size
, 1);
345 void fntRelease(int id
) {
347 if (id
< FNT_MAX_COUNT
)
348 fntDeleteFont(&fonts
[id
]);
351 /** Terminates the font rendering system */
354 // release all the fonts
356 for (id
= 0; id
< FNT_MAX_COUNT
; ++id
)
357 fntDeleteFont(&fonts
[id
]);
359 // deinit freetype system
360 FT_Done_FreeType(font_library
);
362 DeleteSema(gFontSemaId
);
367 static atlas_t
*fntNewAtlas() {
368 atlas_t
*atl
= atlasNew(ATLAS_WIDTH
, ATLAS_HEIGHT
, GS_PSM_T8
);
370 atl
->surface
.ClutPSM
= GS_PSM_CT32
;
371 atl
->surface
.Clut
= (u32
*)(&fontClut
);
376 static int fntGlyphAtlasPlace(font_t
*fnt
, fnt_glyph_cache_entry_t
* glyph
) {
377 FT_GlyphSlot slot
= fnt
->face
->glyph
;
379 LOG("fntGlyphAtlasPlace: Placing the glyph... %d x %d\n", slot
->bitmap
.width
, slot
->bitmap
.rows
);
381 if (slot
->bitmap
.width
== 0 || slot
->bitmap
.rows
== 0) {
382 // no bitmap glyph, just skip
388 for (aid
= 0; aid
< ATLAS_MAX
; ++aid
) {
389 LOG(" * Placing aid %d...\n", aid
);
390 atlas_t
**atl
= &fnt
->atlases
[aid
];
391 if (!*atl
) { // atlas slot not yet used
392 LOG(" * aid %d is new...\n", aid
);
393 *atl
= fntNewAtlas();
397 atlasPlace(*atl
, slot
->bitmap
.width
, slot
->bitmap
.rows
, slot
->bitmap
.buffer
);
399 if (glyph
->allocation
) {
400 LOG(" * found placement\n", aid
);
407 LOG(" * ! no atlas free\n", aid
);
411 /** Internal method. Makes sure the bitmap data for particular character are pre-rendered to the glyph cache */
412 static fnt_glyph_cache_entry_t
* fntCacheGlyph(font_t
*fnt
, uint32_t gid
) {
413 // calc page id and in-page index from glyph id
414 int pageid
= gid
/ GLYPH_CACHE_PAGE_SIZE
;
415 int idx
= gid
% GLYPH_CACHE_PAGE_SIZE
;
417 // do not call on every char of every font rendering call
418 if (!GLYPH_PAGE_OK(fnt
,pageid
))
419 if (!fntPrepareGlyphCachePage(fnt
, pageid
)) // failed to prepare the page...
422 fnt_glyph_cache_entry_t
*page
= fnt
->glyphCache
[pageid
];
424 /* Should never happen.
425 if (!page) // safeguard
429 fnt_glyph_cache_entry_t
* glyph
= &page
[idx
];
434 // not cached but valid. Cache
436 LOG("FNT: Face is NULL!\n");
439 int error
= FT_Load_Char(fnt
->face
, gid
, FT_LOAD_RENDER
);
442 LOG("FNT: Error loading glyph - %d\n", error
);
446 // find atlas placement for the glyph
447 if (!fntGlyphAtlasPlace(fnt
, glyph
))
450 FT_GlyphSlot slot
= fnt
->face
->glyph
;
451 glyph
->width
= slot
->bitmap
.width
;
452 glyph
->height
= slot
->bitmap
.rows
;
453 glyph
->shx
= slot
->advance
.x
;
454 glyph
->shy
= slot
->advance
.y
;
455 glyph
->ox
= slot
->bitmap_left
;
456 glyph
->oy
= gCharHeight
- slot
->bitmap_top
;
463 void fntSetAspectRatio(float aw
, float ah
) {
464 LOG("fntSetAspectRatio\n");
465 // flush cache - it will be invalid after the setting
468 for (i
= 0; i
< FNT_MAX_COUNT
; ++i
) {
469 if (fonts
[i
].isValid
)
470 fntCacheFlush(&fonts
[i
]);
473 // set new aspect ratio (Is this correct, I wonder?)
474 // TODO: error = FT_Set_Char_Size(face, 0, gCharHeight*64, ah*300, aw*300);
477 static void fntRenderGlyph(fnt_glyph_cache_entry_t
* glyph
, int pen_x
, int pen_y
) {
478 // only if glyph has atlas placement
479 if (glyph
->allocation
) {
480 /* TODO: Ineffective on many parts:
481 * 1. Usage of floats for UV - fixed point should suffice (and is used internally by GS for UV)
483 * 2. GS_SETREG_TEX0 for every quad - why? gsKit should only set texture if demanded
484 * We should prepare a special fnt render method that would step over most of the
485 * performance problems under - begining with rmSetupQuad and continuing into gsKit
486 * - this method would handle the preparetion of the quads and GS upload itself,
487 * without the use of prim_quad_texture and rmSetupQuad...
489 * 3. We should use clut to keep the memory allocations sane - we're linear in the 32bit buffers
490 * anyway, no reason to waste like we do!
492 quad
.ul
.x
= pen_x
+ glyph
->ox
;
493 quad
.br
.x
= quad
.ul
.x
+ glyph
->width
;
494 quad
.ul
.y
= pen_y
+ glyph
->oy
;
495 quad
.br
.y
= quad
.ul
.y
+ glyph
->height
;
497 // UV is our own, custom thing here
498 quad
.txt
= &glyph
->atlas
->surface
;
499 quad
.ul
.u
= glyph
->allocation
->x
;
500 quad
.br
.u
= quad
.ul
.u
+ glyph
->width
;
501 quad
.ul
.v
= glyph
->allocation
->y
;
502 quad
.br
.v
= quad
.ul
.v
+ glyph
->height
;
509 int fntRenderString(int font
, int x
, int y
, short aligned
, size_t width
, size_t height
, const unsigned char* string
, u64 colour
) {
510 // wait for font lock to unlock
511 WaitSema(gFontSemaId
);
512 font_t
*fnt
= &fonts
[font
];
513 SignalSema(gFontSemaId
);
517 x
-= min(fntCalcDimensions(font
, string
), width
) >> 1;
519 x
-= fntCalcDimensions(font
, string
) >> 1;
521 y
-= MENU_ITEM_HEIGHT
>> 1;
524 rmApplyShiftRatio(&y
);
528 int xmax
= x
+ width
;
529 int ymax
= y
+ height
;
531 use_kerning
= FT_HAS_KERNING(fnt
->face
);
535 // Note: We need to change this so that we'll accumulate whole word before doing a layout with it
536 // for now this method breaks on any character - which is a bit ugly
538 // I don't want to do anything complicated though so I'd say
539 // we should instead have a dynamic layout routine that'll replace spaces with newlines as appropriate
540 // because that'll make the code run only once per N frames, not every frame
542 // cache glyphs and render as we go
543 for (; *string
; ++string
) {
544 if (utf8Decode(&state
, &codepoint
, *string
)) // accumulate the codepoint value
547 glyph
= fntCacheGlyph(fnt
, codepoint
);
552 if (use_kerning
&& previous
) {
553 glyph_index
= FT_Get_Char_Index(fnt
->face
, codepoint
);
555 FT_Get_Kerning(fnt
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
556 pen_x
+= delta
.x
>> 6;
558 previous
= glyph_index
;
562 if (codepoint
== '\n') {
564 y
+= MENU_ITEM_HEIGHT
; // hmax is too tight and unordered, generally
568 if (y
> ymax
) // stepped over the max
571 if (pen_x
+ glyph
->width
> xmax
) {
572 pen_x
= xmax
+ 1; // to be sure no other cahr will be written (even not a smaller one just following)
577 fntRenderGlyph(glyph
, pen_x
, y
);
578 pen_x
+= glyph
->shx
>> 6;
585 static void fntRenderSubRTL(font_t
*fnt
, const unsigned char* startRTL
, const unsigned char* string
, fnt_glyph_cache_entry_t
* glyph
, int x
, int y
) {
587 x
-= glyph
->shx
>> 6;
588 fntRenderGlyph(glyph
, x
, y
);
591 for (; startRTL
!= string
; ++startRTL
) {
592 if (utf8Decode(&state
, &codepoint
, *startRTL
))
595 glyph
= fntCacheGlyph(fnt
, codepoint
);
599 if (use_kerning
&& previous
) {
600 glyph_index
= FT_Get_Char_Index(fnt
->face
, codepoint
);
602 FT_Get_Kerning(fnt
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
605 previous
= glyph_index
;
608 x
-= glyph
->shx
>> 6;
609 fntRenderGlyph(glyph
, x
, y
);
613 int fntRenderString(int font
, int x
, int y
, short aligned
, size_t width
, size_t height
, const unsigned char* string
, u64 colour
) {
614 // wait for font lock to unlock
615 WaitSema(gFontSemaId
);
616 font_t
*fnt
= &fonts
[font
];
617 SignalSema(gFontSemaId
);
621 x
-= min(fntCalcDimensions(font
, string
), width
) >> 1;
623 x
-= fntCalcDimensions(font
, string
) >> 1;
625 y
-= MENU_ITEM_HEIGHT
>> 1;
628 rmApplyShiftRatio(&y
);
632 /*int xmax = x + width;
633 int ymax = y + height;*/
635 use_kerning
= FT_HAS_KERNING(fnt
->face
);
640 int delta_x
, pen_xRTL
= 0;
641 fnt_glyph_cache_entry_t
* glyphRTL
= NULL
;
642 const unsigned char* startRTL
= NULL
;
644 // cache glyphs and render as we go
645 for (; *string
; ++string
) {
646 if (utf8Decode(&state
, &codepoint
, *string
)) // accumulate the codepoint value
649 glyph
= fntCacheGlyph(fnt
, codepoint
);
655 if (use_kerning
&& previous
) {
656 glyph_index
= FT_Get_Char_Index(fnt
->face
, codepoint
);
658 FT_Get_Kerning(fnt
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
659 delta_x
= delta
.x
>> 6;
661 previous
= glyph_index
;
666 if (codepoint == '\n') {
668 y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
672 if ((x + glyph_w > xmax) || (y > ymax)) // stepped over the max
676 if (codepoint
> 0xFF) {
681 startRTL
= string
+ 1;
683 } else if ((codepoint
> 96 && codepoint
< 123) || (codepoint
> 64 && codepoint
< 91)) {
684 if (inRTL
) { // render RTL
687 fntRenderSubRTL(fnt
, startRTL
, string
, glyphRTL
, pen_xRTL
, y
);
692 pen_xRTL
+= delta_x
+ (glyph
->shx
>> 6);
695 fntRenderGlyph(glyph
, pen_x
, y
);
696 pen_x
+= glyph
->shx
>> 6;
703 fntRenderSubRTL(fnt
, startRTL
, string
, glyphRTL
, pen_xRTL
, y
);
710 void fntFitString(int font
, unsigned char *string
, size_t width
) {
712 unsigned char *str
= string
;
713 size_t spacewidth
= fntCalcDimensions(font
, " ");
714 unsigned char *psp
= NULL
;
717 // scan forward to the next whitespace
718 unsigned char *sp
= str
;
719 for (; *sp
&& *sp
!= ' ' && *sp
!= '\n'; ++sp
);
721 // store what was there before
722 unsigned char osp
= *sp
;
724 // newline resets the situation
732 // terminate after the word
735 // Calc the font's width...
736 // NOTE: The word was terminated, so we're seeing a single word
738 size_t ww
= fntCalcDimensions(font
, str
);
740 if (cw
+ ww
> width
) {
742 // we have a prev space to utilise (wrap on it)
748 // no prev. space to hijack, must break after the word
749 // this will mean overflowed text...
764 int fntCalcDimensions(int font
, const unsigned char* str
) {
767 WaitSema(gFontSemaId
);
768 font_t
*fnt
= &fonts
[font
];
769 SignalSema(gFontSemaId
);
772 uint32_t state
= UTF8_ACCEPT
;
773 FT_Bool use_kerning
= FT_HAS_KERNING(fnt
->face
);
774 FT_UInt glyph_index
, previous
= 0;
777 // cache glyphs and render as we go
778 for (; *str
; ++str
) {
779 if (utf8Decode(&state
, &codepoint
, *str
)) // accumulate the codepoint value
782 // Could just as well only get the glyph dimensions
783 // but it is probable the glyphs will be needed anyway
784 fnt_glyph_cache_entry_t
* glyph
= fntCacheGlyph(fnt
, codepoint
);
789 if (use_kerning
&& previous
) {
790 glyph_index
= FT_Get_Char_Index(fnt
->face
, codepoint
);
792 FT_Get_Kerning(fnt
->face
, previous
, glyph_index
, FT_KERNING_DEFAULT
, &delta
);
795 previous
= glyph_index
;
798 w
+= glyph
->shx
>> 6;
804 void fntReplace(int id
, void* buffer
, int bufferSize
, int takeover
, int asDefault
) {
805 font_t
*fnt
= &fonts
[id
];
807 font_t ndefault
, old
;
808 fntResetFontDef(&ndefault
);
809 fntLoadSlot(&ndefault
, buffer
, bufferSize
);
810 ndefault
.isDefault
= asDefault
;
812 // copy over the new font definition
813 // we have to lock this phase, as the old font may still be used
814 WaitSema(gFontSemaId
);
815 memcpy(&old
, fnt
, sizeof(font_t
));
816 memcpy(fnt
, &ndefault
, sizeof(font_t
));
819 fnt
->dataPtr
= buffer
;
821 SignalSema(gFontSemaId
);
823 // delete the old font
827 void fntSetDefault(int id
) {
828 font_t
*fnt
= &fonts
[id
];
834 font_t ndefault
, old
;
835 fntResetFontDef(&ndefault
);
836 fntLoadSlot(&ndefault
, NULL
, -1);
838 // copy over the new font definition
839 // we have to lock this phase, as the old font may still be used
840 // Note: No check for concurrency is done here, which is kinda funky!
841 WaitSema(gFontSemaId
);
842 memcpy(&old
, fnt
, sizeof(font_t
));
843 memcpy(fnt
, &ndefault
, sizeof(font_t
));
844 SignalSema(gFontSemaId
);
846 // delete the old font