Source

Open PS2 Loader / src / fntsys.c

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
/*
 Copyright 2010, Volca
 Licenced under Academic Free License version 3.0
 Review OpenUsbLd README & LICENSE files for further details.
 */

#include "include/integers.h"
#include "include/usbld.h"
#include "include/fntsys.h"
#include "include/renderman.h"
#include "include/ioman.h"
#include "include/utf8.h"
#include "include/util.h"
#include "include/atlas.h"

#include <ft2build.h>

#include FT_FREETYPE_H

extern void* freesansfont_raw;
extern int size_freesansfont_raw;

/// Maximal count of atlases per font
#define ATLAS_MAX 4
/// Atlas width in pixels
#define ATLAS_WIDTH 128
/// Atlas height in pixels
#define ATLAS_HEIGHT 128

// freetype vars
static FT_Library font_library;

static int gCharHeight = 16;

static s32 gFontSemaId;
static ee_sema_t gFontSema;

static GSCLUT fontClut;

/** Single entry in the glyph cache */
typedef struct {
	int isValid;
	// size in pixels of the glyph
	int width, height;
	// offsetting of the glyph
	int ox, oy;
	// advancements in pixels after rendering this glyph
	int shx, shy;
	
	// atlas for which the allocation was done
    atlas_t* atlas;

	// atlas allocation position
	struct atlas_allocation_t *allocation;

} fnt_glyph_cache_entry_t;

/** A whole font definition */
typedef struct {
	/** GLYPH CACHE. Every glyph in the ASCII range is cached when first used
	 * this means no additional memory aside from the one needed to render the
	 * character set is used.
	 */
	fnt_glyph_cache_entry_t **glyphCache;

	/// Maximal font cache page index
	int cacheMaxPageID;

	/// Font face
	FT_Face face;

	/// Nonzero if font is used
	int isValid;

	/// Texture atlases (default to NULL)
	atlas_t *atlases[ATLAS_MAX];
	
	/// Pointer to data, if allocation takeover was selected (will be freed)
	void *dataPtr;
} font_t;

#define FNT_MAX_COUNT (16)

/// Array of font definitions
static font_t fonts[FNT_MAX_COUNT];

static rm_quad_t quad;
static uint32_t codepoint, state;
static fnt_glyph_cache_entry_t* glyph;
static FT_Bool use_kerning;
static FT_UInt glyph_index, previous;
static FT_Vector delta;

#define GLYPH_CACHE_PAGE_SIZE 256

#define GLYPH_PAGE_OK(font,page) ((pageid <= font->cacheMaxPageID) && (font->glyphCache[page]))

static void fntCacheFlushPage(fnt_glyph_cache_entry_t *page) {
	int i;

	for (i = 0; i < GLYPH_CACHE_PAGE_SIZE; ++i, ++page) {
		page->isValid = 0;
		// we're not doing any atlasFree or such - atlas has to be rebuild
		page->allocation = NULL;
		page->atlas = NULL;
	}
}

static void fntCacheFlush(font_t *font) {
	// Release all the glyphs from the cache
	int i;
	for (i = 0; i <= font->cacheMaxPageID; ++i) {
		if (font->glyphCache[i]) {
			fntCacheFlushPage(font->glyphCache[i]);
			free(font->glyphCache[i]);
			font->glyphCache[i] = NULL;
		}
	}

	free(font->glyphCache);
	font->glyphCache = NULL;
	font->cacheMaxPageID = -1;

	// free all atlasses too, they're invalid now anyway
	int aid;
	for(aid = 0; aid < ATLAS_MAX; ++aid) {
		atlasFree(font->atlases[aid]);
		font->atlases[aid] = NULL;
	}
}

static int fntPrepareGlyphCachePage(font_t *font, int pageid) {
	if (pageid > font->cacheMaxPageID) {
		fnt_glyph_cache_entry_t **np = realloc(font->glyphCache, (pageid + 1) * sizeof(fnt_glyph_cache_entry_t *));

		if (!np)
			return 0;

		font->glyphCache = np;

		unsigned int page;
		for (page = font->cacheMaxPageID + 1; page <= pageid; ++page)
			font->glyphCache[page] = NULL;

		font->cacheMaxPageID = pageid;
	}

	// if it already was allocated, skip this
	if (font->glyphCache[pageid])
		return 1;

	// allocate the page
	font->glyphCache[pageid] = malloc(sizeof(fnt_glyph_cache_entry_t) * GLYPH_CACHE_PAGE_SIZE);

	int i;
	for (i = 0; i < GLYPH_CACHE_PAGE_SIZE; ++i) {
		font->glyphCache[pageid][i].isValid = 0;
		font->glyphCache[pageid][i].atlas = NULL;
		font->glyphCache[pageid][i].allocation = NULL;
	}

	return 1;
}

static void fntPrepareCLUT() {
	fontClut.PSM = GS_PSM_T8;
	fontClut.ClutPSM = GS_PSM_CT32;
	fontClut.Clut = memalign(128, 256 * 4);
	fontClut.VramClut = 0;

	// generate the clut table
	size_t i;
	u32 *clut = fontClut.Clut;
	for (i = 0; i < 256; ++i) {
		u8 alpha =  i > 0x080 ? 0x080 : i;

		*clut = alpha << 24 | i << 16 | i << 8 | i;
		clut++;
	}
}

static void fntDestroyCLUT() {
	free(fontClut.Clut);
	fontClut.Clut = NULL;
}

static void fntInitSlot(font_t *font) {
	font->face = NULL;
	font->glyphCache = NULL;
	font->cacheMaxPageID = -1;
	font->dataPtr = NULL;
	font->isValid = 0;

	int aid = 0;
	for(; aid < ATLAS_MAX; ++aid)
		font->atlases[aid] = NULL;
}

static void fntDeleteSlot(font_t *font) {
	// free the glyph cache, atlases, unload the font
	fntCacheFlush(font);

	FT_Done_Face(font->face);
	font->face = NULL;

	if (font->dataPtr) {
		free(font->dataPtr);
		font->dataPtr = NULL;
	}

	font->isValid = 0;
}

void fntRelease(int id) {
	if (id > FNT_DEFAULT && id < FNT_MAX_COUNT)
		fntDeleteSlot(&fonts[id]);
}

static int fntLoadSlot(font_t *font, char* path) {
	void* buffer = NULL;
	int bufferSize = -1;

	fntInitSlot(font);

	if (path) {
		buffer = readFile(path, -1, &bufferSize);
		if (!buffer) {
			LOG("FNTSYS Font file loading failed: %s\n", path);
			return FNT_ERROR;
		}
		font->dataPtr = buffer;
	} else {
		buffer = &freesansfont_raw;
		bufferSize = size_freesansfont_raw;
	}

	// load the font via memory handle
	int error = FT_New_Memory_Face(font_library, (FT_Byte*) buffer, bufferSize, 0, &font->face);
	if (error) {
		LOG("FNTSYS Freetype font loading failed with %x!\n", error);
		fntDeleteSlot(font);
		return FNT_ERROR;
	}

	error = FT_Set_Char_Size(font->face, 0, gCharHeight * 16, 300, 300);
	/*error = FT_Set_Pixel_Sizes( face, 0, // pixel_width gCharHeight ); // pixel_height */
	if (error) {
		LOG("FNTSYS Freetype error setting font pixel size with %x!\n", error);
		fntDeleteSlot(font);
		return FNT_ERROR;
	}

	font->isValid = 1;
	return 0;
}

void fntInit() {
	LOG("FNTSYS Init\n");
	int error = FT_Init_FreeType(&font_library);
	if (error) {
		// just report over the ps2link
		LOG("FNTSYS Freetype init failed with %x!\n", error);
		// SleepThread();
	}

	fntPrepareCLUT();

	gFontSema.init_count = 1;
	gFontSema.max_count = 1;
	gFontSema.option = 0;
	gFontSemaId = CreateSema(&gFontSema);

	int i = 0;
	for (; i < FNT_MAX_COUNT; ++i)
		fntInitSlot(&fonts[i]);

	fntLoadDefault(NULL);
}

int fntLoadFile(char* path) {
	font_t *font;
	int i = 1;
	for (; i < FNT_MAX_COUNT; i++) {
		font = &fonts[i];
		if (!font->isValid) {
			if (fntLoadSlot(font, path) != FNT_ERROR)
				return i;
			break;
		}
	}

	return FNT_ERROR;
}

void fntLoadDefault(char* path) {
	font_t newFont, oldFont;

	if (fntLoadSlot(&newFont, path) != FNT_ERROR) {
		// copy over the new font definition
		// we have to lock this phase, as the old font may still be used
		// Note: No check for concurrency is done here, which is kinda funky!
		WaitSema(gFontSemaId);
		memcpy(&oldFont, &fonts[FNT_DEFAULT], sizeof(font_t));
		memcpy(&fonts[FNT_DEFAULT], &newFont, sizeof(font_t));
		SignalSema(gFontSemaId);

		// delete the old font
		fntDeleteSlot(&oldFont);
	}
}

void fntEnd() {
	LOG("FNTSYS End\n");
	// release all the fonts
	int id;
	for (id = 0; id < FNT_MAX_COUNT; ++id)
		fntDeleteSlot(&fonts[id]);

	// deinit freetype system
	FT_Done_FreeType(font_library);

	DeleteSema(gFontSemaId);
	
	fntDestroyCLUT();
}

static atlas_t *fntNewAtlas() {
	atlas_t *atl = atlasNew(ATLAS_WIDTH, ATLAS_HEIGHT, GS_PSM_T8);
	
	atl->surface.ClutPSM = GS_PSM_CT32;
	atl->surface.Clut = (u32*)(&fontClut);

	return atl;
}

static int fntGlyphAtlasPlace(font_t *font, fnt_glyph_cache_entry_t* glyph) {
	FT_GlyphSlot slot = font->face->glyph;
	
 	//LOG("FNTSYS GlyphAtlasPlace: Placing the glyph... %d x %d\n", slot->bitmap.width, slot->bitmap.rows);
	
	if (slot->bitmap.width == 0 || slot->bitmap.rows == 0) {
		// no bitmap glyph, just skip
		return 1;
	}
	
	int aid = 0;
	for (; aid < ATLAS_MAX; aid++) {
		//LOG("FNTSYS Placing aid %d...\n", aid);
		atlas_t **atl = &font->atlases[aid];
		if (!*atl) { // atlas slot not yet used
			//LOG("FNTSYS aid %d is new...\n", aid);
			*atl = fntNewAtlas();
		}
		
		glyph->allocation =	atlasPlace(*atl, slot->bitmap.width, slot->bitmap.rows, slot->bitmap.buffer);
		if (glyph->allocation) {
			//LOG("FNTSYS Found placement\n", aid);
			glyph->atlas = *atl;
			
			return 1;
		}
	}
	
	LOG("FNTSYS No atlas free\n", aid);
	return 0;
}

/** Internal method. Makes sure the bitmap data for particular character are pre-rendered to the glyph cache */
static fnt_glyph_cache_entry_t* fntCacheGlyph(font_t *font, uint32_t gid) {
	// calc page id and in-page index from glyph id
	int pageid = gid / GLYPH_CACHE_PAGE_SIZE;
	int idx = gid % GLYPH_CACHE_PAGE_SIZE;

	// do not call on every char of every font rendering call
	if (!GLYPH_PAGE_OK(font, pageid))
		if (!fntPrepareGlyphCachePage(font, pageid)) // failed to prepare the page...
			return NULL;

	fnt_glyph_cache_entry_t *page = font->glyphCache[pageid];
	/* Should never happen. 
	if (!page) // safeguard
		return NULL;
	*/

	fnt_glyph_cache_entry_t* glyph = &page[idx];
	if (glyph->isValid)
		return glyph;

	// not cached but valid. Cache
	if (!font->face) {
		LOG("FNTSYS Face is NULL!\n");
	}

	int error = FT_Load_Char(font->face, gid, FT_LOAD_RENDER);
	if (error) {
		LOG("FNTSYS Error loading glyph - %d\n", error);
		return NULL;
	}

	// find atlas placement for the glyph
	if  (!fntGlyphAtlasPlace(font, glyph))
		return NULL;

	FT_GlyphSlot slot = font->face->glyph;
	glyph->width = slot->bitmap.width;
	glyph->height = slot->bitmap.rows;
	glyph->shx = slot->advance.x;
	glyph->shy = slot->advance.y;
	glyph->ox = slot->bitmap_left;
	glyph->oy = gCharHeight - slot->bitmap_top;

	glyph->isValid = 1;

	return glyph;
}

void fntSetAspectRatio(float aw, float ah) {
	// flush cache - it will be invalid after the setting
	int i = 0;
	for (; i < FNT_MAX_COUNT; i++) {
		if (fonts[i].isValid)
			fntCacheFlush(&fonts[i]);
	}

	// TODO: set new aspect ratio (Is this correct, I wonder?)
	// error = FT_Set_Char_Size(face, 0, gCharHeight*64, ah*300, aw*300);
}

static void fntRenderGlyph(fnt_glyph_cache_entry_t* glyph, int pen_x, int pen_y) {
	// only if glyph has atlas placement
	if (glyph->allocation) {
		/* TODO: Ineffective on many parts:
		 * 1. Usage of floats for UV - fixed point should suffice (and is used internally by GS for UV)
		 *
		 * 2. GS_SETREG_TEX0 for every quad - why? gsKit should only set texture if demanded
		 *    We should prepare a special fnt render method that would step over most of the
		 *    performance problems under - beginning with rmSetupQuad and continuing into gsKit
		 *    - this method would handle the preparation of the quads and GS upload itself,
		 *    without the use of prim_quad_texture and rmSetupQuad...
		 *
		 * 3. We should use clut to keep the memory allocations sane - we're linear in the 32bit buffers
		 *    anyway, no reason to waste like we do!
		 */
		quad.ul.x = pen_x + glyph->ox;
		quad.br.x = quad.ul.x + glyph->width;
		quad.ul.y = pen_y + glyph->oy;
		quad.br.y = quad.ul.y + glyph->height;

		// UV is our own, custom thing here
		quad.txt = &glyph->atlas->surface;
		quad.ul.u = glyph->allocation->x;
		quad.br.u = quad.ul.u + glyph->width;
		quad.ul.v = glyph->allocation->y;
		quad.br.v = quad.ul.v + glyph->height;

		rmDrawQuad(&quad);
	}
}

#ifndef __RTL
int fntRenderString(int id, int x, int y, short aligned, size_t width, size_t height, const unsigned char* string, u64 colour) {
	// wait for font lock to unlock
	WaitSema(gFontSemaId);
	font_t *font = &fonts[id];
	SignalSema(gFontSemaId);

	if (aligned) {
		if (width) {
			x -= min(fntCalcDimensions(id, string), width) >> 1;
		} else {
			x -= fntCalcDimensions(id, string) >> 1;
		}
		y -= MENU_ITEM_HEIGHT >> 1;
	}

	rmApplyShiftRatio(&y);
	quad.color = colour;

	int pen_x = x;
	int xmax = x + width;
	int ymax = y + height;

	use_kerning = FT_HAS_KERNING(font->face);
	state = UTF8_ACCEPT;
	previous = 0;

	// Note: We need to change this so that we'll accumulate whole word before doing a layout with it
	// for now this method breaks on any character - which is a bit ugly

	// I don't want to do anything complicated though so I'd say
	// we should instead have a dynamic layout routine that'll replace spaces with newlines as appropriate
	// because that'll make the code run only once per N frames, not every frame

	// cache glyphs and render as we go
	for (; *string; ++string) {
		if (utf8Decode(&state, &codepoint, *string)) // accumulate the codepoint value
			continue;

		glyph = fntCacheGlyph(font, codepoint);
		if (!glyph)
			continue;

		// kerning
		if (use_kerning && previous) {
			glyph_index = FT_Get_Char_Index(font->face, codepoint);
			if (glyph_index) {
				FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
				pen_x += delta.x >> 6;
			}
			previous = glyph_index;
		}

		if (width) {
			if (codepoint == '\n') {
				pen_x = x;
				y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
				continue;
			}

			if (y > ymax) // stepped over the max
				break;

			if (pen_x + glyph->width > xmax) {
				pen_x = xmax + 1; // to be sure no other cahr will be written (even not a smaller one just following)
				continue;
			}
		}

		fntRenderGlyph(glyph, pen_x, y);
		pen_x += glyph->shx >> 6;
	}

	return pen_x;
}

#else
static void fntRenderSubRTL(font_t *font, const unsigned char* startRTL, const unsigned char* string, fnt_glyph_cache_entry_t* glyph, int x, int y) {
	if (glyph) {
		x -= glyph->shx >> 6;
		fntRenderGlyph(glyph, x, y);
	}

	for (; startRTL != string; ++startRTL) {
		if (utf8Decode(&state, &codepoint, *startRTL))
			continue;

		glyph = fntCacheGlyph(font, codepoint);
		if (!glyph)
			continue;

		if (use_kerning && previous) {
			glyph_index = FT_Get_Char_Index(font->face, codepoint);
			if (glyph_index) {
				FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
				x -= delta.x >> 6;
			}
			previous = glyph_index;
		}

		x -= glyph->shx >> 6;
		fntRenderGlyph(glyph, x, y);
	}
}

int fntRenderString(int id, int x, int y, short aligned, size_t width, size_t height, const unsigned char* string, u64 colour) {
	// wait for font lock to unlock
	WaitSema(gFontSemaId);
	font_t *font = &fonts[id];
	SignalSema(gFontSemaId);

	if (aligned) {
		if (width) {
			x -= min(fntCalcDimensions(id, string), width) >> 1;
		} else {
			x -= fntCalcDimensions(id, string) >> 1;
		}
		y -= MENU_ITEM_HEIGHT >> 1;
	}

	rmApplyShiftRatio(&y);
	quad.color = colour;

	int pen_x = x;
	/*int xmax = x + width;
	int ymax = y + height;*/

	use_kerning = FT_HAS_KERNING(font->face);
	state = UTF8_ACCEPT;
	previous = 0;

	short inRTL = 0;
	int delta_x, pen_xRTL = 0;
	fnt_glyph_cache_entry_t* glyphRTL = NULL;
	const unsigned char* startRTL = NULL;

	// cache glyphs and render as we go
	for (; *string; ++string) {
		if (utf8Decode(&state, &codepoint, *string)) // accumulate the codepoint value
			continue;

		glyph = fntCacheGlyph(font, codepoint);
		if (!glyph)
			continue;

		// kerning
		delta_x = 0;
		if (use_kerning && previous) {
			glyph_index = FT_Get_Char_Index(font->face, codepoint);
			if (glyph_index) {
				FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
				delta_x = delta.x >> 6;
			}
			previous = glyph_index;
		}


		/*if (width) {
			if (codepoint == '\n') {
				x = xori;
				y += MENU_ITEM_HEIGHT; // hmax is too tight and unordered, generally
				continue;
			}

			if ((x + glyph_w > xmax) || (y > ymax)) // stepped over the max
				break;
		}*/

		if (codepoint > 0xFF) {
			if (!inRTL) {
				inRTL = 1;
				pen_xRTL = pen_x;
				glyphRTL = glyph;
				startRTL = string + 1;
			}
		} else if ((codepoint > 96 && codepoint < 123) || (codepoint > 64 && codepoint < 91)) {
			if (inRTL) { // render RTL
				inRTL = 0;
				pen_x = pen_xRTL;
				fntRenderSubRTL(font, startRTL, string, glyphRTL, pen_xRTL, y);
			}
		}

		if (inRTL) {
			pen_xRTL += delta_x + (glyph->shx >> 6);
		} else {
			pen_x += delta_x;
			fntRenderGlyph(glyph, pen_x, y);
			pen_x += glyph->shx >> 6;
		}

	}

	if (inRTL) {
		pen_x = pen_xRTL;
		fntRenderSubRTL(font, startRTL, string, glyphRTL, pen_xRTL, y);
	}

	return pen_x;
}
#endif

void fntFitString(int id, unsigned char *string, size_t width) {
	size_t cw = 0;
	unsigned char *str = string;
	size_t spacewidth = fntCalcDimensions(id, " ");
	unsigned char *psp = NULL;
	
	while (*str) {
		// scan forward to the next whitespace
		unsigned char *sp = str;
		for (; *sp && *sp != ' ' && *sp != '\n'; ++sp);
		
		// store what was there before
		unsigned char osp = *sp;
		
		// newline resets the situation
		if (osp == '\n') {
			cw = 0;
			str = ++sp;
			psp = NULL;
			continue;
		}
		
		// terminate after the word
		*sp = '\0';
		
		// Calc the font's width...
		// NOTE: The word was terminated, so we're seeing a single word
		// on that position
		size_t ww = fntCalcDimensions(id, str);
		
		if (cw + ww > width) {
			if (psp) {
				// we have a prev space to utilise (wrap on it)
				*psp = '\n';
				*sp = osp;
				cw = ww;
				psp = sp;
			} else {
				// no prev. space to hijack, must break after the word
				// this will mean overflowed text...
				*sp = '\n';
				cw = 0;
			}
		} else {
			cw += ww;
			*sp = osp;
			psp = sp;
		}
		
		cw += spacewidth;
		str = ++sp;
	}
}

int fntCalcDimensions(int id, const unsigned char* str) {
	int w = 0;

	WaitSema(gFontSemaId);
	font_t *font = &fonts[id];
	SignalSema(gFontSemaId);

	uint32_t codepoint;
	uint32_t state = UTF8_ACCEPT;
	FT_Bool use_kerning = FT_HAS_KERNING(font->face);
	FT_UInt glyph_index, previous = 0;
	FT_Vector delta;

	// cache glyphs and render as we go
	for (; *str; ++str) {
		if (utf8Decode(&state, &codepoint, *str)) // accumulate the codepoint value
			continue;

		// Could just as well only get the glyph dimensions
		// but it is probable the glyphs will be needed anyway
		fnt_glyph_cache_entry_t* glyph = fntCacheGlyph(font, codepoint);
		if (!glyph)
			continue;

		// kerning
		if (use_kerning && previous) {
			glyph_index = FT_Get_Char_Index(font->face, codepoint);
			if (glyph_index) {
				FT_Get_Kerning(font->face, previous, glyph_index, FT_KERNING_DEFAULT, &delta);
				w += delta.x >> 6;
			}
			previous = glyph_index;
		}

		w += glyph->shx >> 6;
	}

	return w;
}