Blame src/font.c

f63775
f63775
#include <ft2build.h></ft2build.h>
f63775
#include FT_FREETYPE_H
f63775
f63775
#include "private.h"
f63775
#include "font.h"
a20939
#include "drawing.h"
f63775
f63775
f63775
f63775
#define FONT_MAP_GLYPHSIZE      64
f63775
#define FONT_MAP_BORDERSIZE     16
f63775
#define FONT_MAP_CNT            16
f63775
#define FONT_MAP_GLYPHWORKSIZE  (FONT_MAP_GLYPHSIZE - FONT_MAP_BORDERSIZE)
f63775
#define FONT_MAP_FULLCNT        (FONT_MAP_CNT*FONT_MAP_CNT)
f63775
#define FONT_MAP_TEXSIZE        (FONT_MAP_CNT*FONT_MAP_GLYPHSIZE)
f63775
#define FONT_MAX_GLYPHS         (256*256)
f63775
#define FONT_BASE_SIZE          72
f63775
#define FONT_BADCHAR_CODE       0xFFFD
f63775
f63775
f63775
f63775
typedef struct _HeliFontMap HeliFontMap;
f63775
typedef struct _HeliFontInstance HeliFontInstance;
f63775
typedef struct _HeliCharDesc HeliCharDesc;
f63775
f63775
typedef struct _HeliGlyph {
f63775
	HeliFontMap *map;
f63775
	HeliFontInstance *fi;
f63775
	int code;
f63775
	int index;
f63775
	double l, t, r, b;
f63775
	double advance;
f63775
} HeliGlyph;
f63775
f63775
struct _HeliFontMap {
f63775
	HeliFontMap *prev, *next;
f63775
	unsigned int texid;
f63775
	int count;
f63775
	HeliGlyph glyphs[FONT_MAP_FULLCNT];
f63775
};
f63775
f63775
struct _HeliFontInstance {
f63775
	char *path;
f63775
	FT_Face face;
f63775
	double height;
47ef74
	double xheight;
47ef74
	double ascender;
47ef74
	double descender;
f63775
	HeliGlyph *glyphs[FONT_MAX_GLYPHS];
f63775
	int refcount;
f63775
};
f63775
f63775
struct _HeliCharDesc {
f63775
	HeliGlyph *glyph;
da4619
	int charpos;
f63775
	double pos;
f63775
	double x, y;
f63775
	HeliCharDesc *next;
f63775
};
f63775
f63775
typedef struct _HeliLineDesc {
f63775
	int begin, end;
f63775
	double x, y;
f63775
	double l, t, r, b;
47ef74
	double xheight;
47ef74
	double ascender;
47ef74
	double descender;
f63775
	double height;
5a7c99
	double width;
f63775
} HeliLineDesc;
f63775
da4619
struct _TextLayout {
da4619
	Font font;
da4619
	int linesCount;
da4619
	HeliLineDesc *lines;
da4619
	HeliCharDesc *chars;
da4619
	double l, t, r, b;
5a7c99
	double ll, lt, lr, lb;
da4619
};
da4619
f63775
struct _Font {
f63775
	HeliFontInstance *instance;
f63775
};
f63775
f63775
f63775
f63775
static int ftInitialized = 0;
f63775
static FT_Library ftLibrary = NULL;
f63775
static HeliArray fontCache;
f63775
static HeliFontMap *fontMapFirst, *fontMapLast;
f63775
static HeliGlyph blankGlyph;
f63775
static const char blankPath[] = "";
3954ba
static Font defaultFont = NULL;
2fcd94
static Font unicodeFont = NULL;
ca6bde
static int memFontIndex = 0;
f63775
f63775
static HeliFontMap* createFontMap() {
f63775
	HeliFontMap *map = calloc(1, sizeof(*map));
f63775
	map->prev = fontMapLast;
f63775
	*(fontMapLast ? &fontMapLast->next : &fontMapFirst) = map;
f63775
	fontMapLast = map;
f63775
	
f63775
	for(int i = 0; i < FONT_MAP_FULLCNT; ++i)
f63775
		map->glyphs[i].map = map;
f63775
	
0cbd57
	unsigned int prevtex = 0;
0cbd57
	glGetIntegerv(GL_TEXTURE_BINDING_2D, (int*)&prevtex);
f63775
	glGenTextures(1, &map->texid);
f63775
	glBindTexture(GL_TEXTURE_2D, map->texid);
502515
	
f63775
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
f63775
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
f63775
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
f63775
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
b9c036
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.7f);
b9c036
	
502515
	unsigned char *buffer = (unsigned char*)calloc(1, FONT_MAP_TEXSIZE*FONT_MAP_TEXSIZE);
502515
	for(int level = 0, size = FONT_MAP_TEXSIZE; size > 0; ++level, size >>= 1)
b9c036
		glTexImage2D(GL_TEXTURE_2D, level, GL_ALPHA, size, size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, buffer);
f63775
	free(buffer);
f63775
	
502515
	int maxLevel = 0;
502515
	for(int border = FONT_MAP_BORDERSIZE; border > 0; border >>= 1) ++maxLevel;
502515
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, maxLevel - 1);
502515
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, maxLevel - 1);
502515
	
0cbd57
	glBindTexture(GL_TEXTURE_2D, prevtex);
f63775
	return map;
f63775
}
f63775
f63775
static void destroyFontMap(HeliFontMap *map) {
f63775
	assert(!map->count);
f63775
	glDeleteTextures(1, &map->texid);
f63775
	*(map->prev ? &map->prev->next : &fontMapFirst) = map->next;
f63775
	*(map->next ? &map->next->prev : &fontMapLast ) = map->prev;
f63775
	free(map);
f63775
}
f63775
f63775
f63775
static HeliGlyph* loadGlyph(HeliFontInstance *fi, int code) {
f63775
	int index = FT_Get_Char_Index(fi->face, (unsigned int)code);
f63775
	if (!index) return &blankGlyph;
f63775
	
f63775
	if (FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING))
f63775
		return &blankGlyph;
f63775
	
f63775
	HeliFontMap *map;
f63775
	for(map = fontMapFirst; map; map = map->next)
f63775
		if (map->count < FONT_MAP_FULLCNT) break;
f63775
	if (!map) map = createFontMap();
f63775
	
f63775
	HeliGlyph *g = NULL;
f63775
	for(int i = 0; i < FONT_MAP_FULLCNT; ++i)
f63775
		if (!map->glyphs[i].fi) { g = &map->glyphs[i]; break; }
f63775
	assert(g);
f63775
	
f63775
	const double ftkf = 1/64.0;
f63775
	const FT_Glyph_Metrics *metrics = &fi->face->glyph->metrics;
f63775
	g->l = metrics->horiBearingX*ftkf;
f63775
	g->r = (metrics->horiBearingX + metrics->width)*ftkf;
f63775
	g->t = -metrics->horiBearingY*ftkf;
f63775
	g->b = (-metrics->horiBearingY + metrics->height)*ftkf;
f63775
	g->advance = metrics->horiAdvance*ftkf;
f63775
	
502515
	unsigned char buffer[FONT_MAP_GLYPHSIZE*FONT_MAP_GLYPHSIZE] = {};
b9c036
	if (metrics->width > 0 && metrics->height > 0) {
3e56ed
		FT_Pos size = metrics->width > metrics->height ? metrics->width : metrics->height;
b9c036
		FT_Matrix matrix = {};
3e56ed
		matrix.xx = (FONT_MAP_GLYPHWORKSIZE-2)*64*65536/size;
3e56ed
		matrix.yy = (FONT_MAP_GLYPHWORKSIZE-2)*64*65536/size;
b9c036
		FT_Vector offset = {};
c629d4
		offset.x = 64 - metrics->horiBearingX;
b9c036
		offset.y = -(64 + metrics->horiBearingY - metrics->height);
b9c036
		FT_Set_Transform(fi->face, &matrix, &offset);
b9c036
		int success = !FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING)
502515
		           && !FT_Render_Glyph(fi->face->glyph, FT_RENDER_MODE_NORMAL);
b9c036
		FT_Set_Transform(fi->face, NULL, NULL);
b9c036
		if (!success) return &blankGlyph;
b9c036
		
b9c036
		const FT_Bitmap *bitmap = &fi->face->glyph->bitmap;
c629d4
		int dr = 0;//fi->face->glyph->bitmap_top + 1 - FONT_MAP_GLYPHWORKSIZE;
c629d4
		int dc = 0;//-fi->face->glyph->bitmap_left;
b9c036
		int r0 = -dr;
b9c036
		int r1 = r0 + bitmap->rows;
b9c036
		int c0 = -dc;
b9c036
		int c1 = c0 + bitmap->width;
b9c036
		if (r0 < 0) r0 = 0;
b9c036
		if (c0 < 0) c0 = 0;
b9c036
		if (r1 > FONT_MAP_GLYPHWORKSIZE) r1 = FONT_MAP_GLYPHWORKSIZE;
b9c036
		if (c1 > FONT_MAP_GLYPHWORKSIZE) c1 = FONT_MAP_GLYPHWORKSIZE;
b9c036
		for(int r = r0; r < r1; ++r)
b9c036
			for(int c = c0; c < c1; ++c)
b9c036
				buffer[r*FONT_MAP_GLYPHSIZE + c] = *(bitmap->buffer + bitmap->pitch*(r + dr) + c + dc);
b9c036
	}
f63775
	
502515
	unsigned int prevtex = 0;
502515
	glGetIntegerv(GL_TEXTURE_BINDING_2D, (int*)&prevtex);
f63775
	glBindTexture(GL_TEXTURE_2D, g->map->texid);
f63775
	int gi = g - g->map->glyphs;
f63775
	int r = gi/FONT_MAP_CNT;
f63775
	int c = gi%FONT_MAP_CNT;
b9c036
	int size = FONT_MAP_GLYPHSIZE;
b9c036
	for(int level = 0, border = FONT_MAP_BORDERSIZE; border > 0; border >>= 1, ++level) {
f63775
		glTexSubImage2D(
f63775
			GL_TEXTURE_2D,
f63775
			level,
b9c036
			c*size,
b9c036
			r*size,
f63775
			size,
f63775
			size,
f63775
			GL_ALPHA,
f63775
			GL_UNSIGNED_BYTE,
f63775
			buffer );
f63775
		if (border > 1) {
b9c036
			size >>= 1;
f63775
			for(int r = 0; r < size; ++r)
f63775
				for(int c = 0; c < size; ++c)
f63775
					buffer[r*size + c] = (unsigned char)((
b9c036
					    (unsigned int)buffer[((r*2 + 0)*size + c)*2 + 0]
b9c036
					  + (unsigned int)buffer[((r*2 + 0)*size + c)*2 + 1]
b9c036
					  + (unsigned int)buffer[((r*2 + 1)*size + c)*2 + 0]
b9c036
					  + (unsigned int)buffer[((r*2 + 1)*size + c)*2 + 1] + 2) >> 2 );
f63775
		}
f63775
	}
502515
	glBindTexture(GL_TEXTURE_2D, prevtex);
f63775
	
f63775
	g->fi = fi;
f63775
	g->code = code;
f63775
	g->index = index;
b9c036
	++g->map->count;
f63775
	return g;
f63775
}
f63775
f63775
f63775
static void unloadGlyph(HeliGlyph *g) {
f63775
	g->fi = NULL;
f63775
	g->code = 0;
f63775
	g->index = 0;
f63775
	if (g->map && --g->map->count <= 0) 
f63775
		destroyFontMap(g->map);
f63775
}
f63775
f63775
f63775
static HeliFontInstance* loadFont(const char *path) {
f63775
	HeliFontInstance *fi = calloc(1, sizeof(*fi));
f63775
	fi->path = heliStringCopy(path);
f63775
	
f63775
	if (!ftInitialized) {
f63775
		if (FT_Init_FreeType(&ftLibrary))
3954ba
			fprintf(stderr, "helianthus: cannot initialize FreeType library\n");
f63775
		ftInitialized = TRUE;
f63775
	}
f63775
	if (ftLibrary) {
ca6bde
		if (strncmp(path, "helimem:", 8) == 0) {
ca6bde
			void *data = NULL;
ca6bde
			int size = 0;
ca6bde
			fi->face = NULL;
ca6bde
			if (sscanf(path, "helimem:%p:%d:", &data, &size) != EOF && data && size > 0) {
ca6bde
				FT_Open_Args args = {};
ca6bde
				args.flags = FT_OPEN_MEMORY;
ca6bde
				args.memory_base = (FT_Byte*)data;
ca6bde
				args.memory_size = (FT_Long)size;
ca6bde
				if (FT_Open_Face(ftLibrary, &args, 0, &fi->face))
ca6bde
					fi->face = NULL;
f63775
			}
ca6bde
			if (!fi->face)
ca6bde
				fprintf(stderr, "helianthus: cannot load font from memory\n");
f63775
		} else {
ca6bde
			if (FT_New_Face(ftLibrary, path, 0, &fi->face)) {
ca6bde
				fprintf(stderr, "helianthus: cannot load font from file: %s\n", path);
f63775
				fi->face = NULL;
f63775
			}
f63775
		}
ca6bde
		
f63775
		if (fi->face) {
f63775
			FT_Set_Char_Size(fi->face, 0, FONT_BASE_SIZE*64, 72, 72);
f63775
			fi->height = fi->face->size->metrics.height/64.0;
47ef74
			double ascender = fi->face->size->metrics.ascender/64.0;
47ef74
			
47ef74
			unsigned int xchars[] = { 'x', 'z', 'v' };
47ef74
			int count = 0;
47ef74
			double xheight = 0;
47ef74
			for(int i = 0; i < (int)(sizeof(xchars)/sizeof(*xchars)); ++i) {
47ef74
				int index = FT_Get_Char_Index(fi->face, xchars[i]);
47ef74
				if (!index) continue;
47ef74
				if (FT_Load_Glyph(fi->face, index, FT_LOAD_NO_HINTING))
47ef74
					continue;
47ef74
				xheight += fi->face->glyph->metrics.horiBearingY/64.0;
47ef74
				++count;
47ef74
			}
47ef74
			if (count > 0) xheight /= count;
47ef74
			if (xheight <= HELI_PRECISION || xheight > ascender)
47ef74
				xheight = ascender;
47ef74
			
47ef74
			fi->ascender = ascender;
47ef74
			fi->xheight = xheight;
47ef74
			fi->descender = fi->face->size->metrics.descender/64.0;
f63775
		}
f63775
	}
f63775
	
f63775
	return fi;
f63775
}
f63775
f63775
f63775
static void unloadFont(HeliFontInstance *fi) {
f63775
	assert(!fi->refcount);
f63775
	for(int i = 0; i < FONT_MAX_GLYPHS; ++i)
f63775
		if (fi->glyphs[i]) unloadGlyph(fi->glyphs[i]);
f63775
	free(fi->path);
f63775
	if (fi->face) FT_Done_Face(fi->face);
f63775
	free(fi);
f63775
}
f63775
f63775
f63775
static HeliGlyph* getGlyph(HeliFontInstance *fi, int code) {
f63775
	if (code < 0 || code >= FONT_MAX_GLYPHS) return &blankGlyph;
a20939
	if (!fi->glyphs[code]) fi->glyphs[code] = loadGlyph(fi, code);
f63775
	return fi->glyphs[code];
f63775
}
f63775
f63775
f63775
Font createFont(const char *path) {
f63775
	if (!heliInitialized) return NULL;
f63775
	if (!path) path = blankPath;
f63775
	Font f = calloc(1, sizeof(*f));
da4619
	HeliPair *item = heliStringmapGet(&fontCache, path);
da4619
	if (!item) item = heliStringmapAdd(&fontCache, path, loadFont(path), (HeliFreeCallback)&unloadFont);
da4619
	f->instance = (HeliFontInstance*)item->value;
f63775
	++f->instance->refcount;
f63775
	heliObjectRegister(f, (HeliFreeCallback)&fontDestroy);
f63775
	return f;
f63775
}
f63775
ca6bde
ca6bde
Font createFontFromMemory(const void *data, int size) {
ca6bde
	char buffer[1024] = {};
ca6bde
	sprintf(buffer, "helimem:%p:%d:%d", data, size, ++memFontIndex);
ca6bde
	return createFont(buffer);
ca6bde
}
ca6bde
ca6bde
ca6bde
Font fontClone(Font font) {
ca6bde
	if (!font) return NULL;
ca6bde
	return createFont(font->instance ? font->instance->path : NULL);
ca6bde
}
ca6bde
ca6bde
f63775
void fontDestroy(Font f) {
f63775
	heliObjectUnregister(f);
3954ba
	HeliDrawingState *ss = heliDrawingGetState();
3954ba
	for(HeliDrawingState *s = heliDrawingGetStateStack(); s <= ss; ++s)
3954ba
		{ if (ss->font == f) ss->font = NULL; }
f63775
	--f->instance->refcount;
f63775
	if (f->instance->refcount <= 0)
f63775
		heliStringmapRemove(&fontCache, f->instance->path);
f63775
	free(f);
f63775
}
f63775
3954ba
void textAlign(HAlign hor, VAlign vert) {
3954ba
	HeliDrawingState *s = heliDrawingGetState();
3954ba
	s->horAlign = hor;
3954ba
	s->vertAlign = vert;
3954ba
}
f63775
void textFontDefault()
3954ba
	{ heliDrawingGetState()->font = NULL; }
f63775
void textFont(Font f)
3954ba
	{ heliDrawingGetState()->font = f; }
f63775
void textSize(double size)
3954ba
	{ heliDrawingGetState()->fontSize = size; }
f63775
f63775
da4619
static void expand(double *min, double *max, double x) {
da4619
	if (*min > x) *min = x;
da4619
	if (*max < x) *max = x;
da4619
}
da4619
da4619
b6ebe9
TextLayout createTextLayoutEx(const char *text, int length) {
da4619
	TextLayout layout = calloc(1, sizeof(*layout));
f63775
	
b6ebe9
	int len = 0;
b6ebe9
	while(len < length && text[len]) ++len;
b6ebe9
	if (!text || !len) return layout;
534343
	
3954ba
	HeliDrawingState *drawingState = heliDrawingGetState();
3954ba
	
9c0711
	if (!defaultFont && heliBlobDefaultFontSize)
ca6bde
		defaultFont = createFontFromMemory(heliBlobDefaultFont, heliBlobDefaultFontSize);
9c0711
	if (!unicodeFont && heliBlobUnicodeFontSize)
ca6bde
		unicodeFont = createFontFromMemory(heliBlobUnicodeFont, heliBlobUnicodeFontSize);
2fcd94
	
2fcd94
	HeliFontInstance *fonts[10];
2fcd94
	int fontsCount = 0;
2fcd94
	if ( drawingState->font
2fcd94
	  && drawingState->font->instance
2fcd94
	  && drawingState->font->instance->face )
2fcd94
		fonts[fontsCount++] = drawingState->font->instance;
2fcd94
	if ( defaultFont
2fcd94
	  && defaultFont->instance
2fcd94
	  && defaultFont->instance->face )
2fcd94
		fonts[fontsCount++] = defaultFont->instance;
2fcd94
	if ( unicodeFont
2fcd94
	  && unicodeFont->instance
2fcd94
	  && unicodeFont->instance->face )
2fcd94
		fonts[fontsCount++] = unicodeFont->instance;
2fcd94
2fcd94
	if (!fontsCount)
2fcd94
		return layout;
f63775
	
f63775
	int linesAllocated = 256;
da4619
	layout->linesCount = 1;
da4619
	layout->lines = calloc(linesAllocated, sizeof(HeliLineDesc));
da4619
	HeliLineDesc *line = layout->lines;
2fcd94
	line->height = fonts[0]->height;
47ef74
	line->xheight = fonts[0]->xheight;
47ef74
	line->ascender = fonts[0]->ascender;
47ef74
	line->descender = fonts[0]->descender;
b6ebe9
	layout->chars = calloc(len + 1, sizeof(HeliCharDesc));
ca6bde
	if ( drawingState->font
ca6bde
	  && drawingState->font != defaultFont
ca6bde
	  && drawingState->font != unicodeFont
ca6bde
	  && drawingState->font->instance == fonts[0] )
ca6bde
		layout->font = fontClone(drawingState->font);
f63775
	
f63775
	// gather glyphs
b6ebe9
	const char *c = text, *e = c + len, *e1 = e - 1, *e2 = e - 2;
f63775
	while(1) {
f63775
		int code = FONT_BADCHAR_CODE;
b6ebe9
		while(c < e && (*c & 0xC0) == 0x80) ++c;
b6ebe9
		const char *charpos = c;
b6ebe9
		if (c < e) {
b6ebe9
			if ((*c & 0x80) == 0x00) {
b6ebe9
				code = *c;
b6ebe9
			} else
b6ebe9
			if ((*c & 0xE0) == 0xC0) {
b6ebe9
				if (c < e1 && (c[1] & 0xC0) == 0x80) {
b6ebe9
					code = ((c[0] & 0x1F) << 6) | (c[1] & 0x3F);
b6ebe9
					c += 1;
b6ebe9
				}
b6ebe9
			} else
b6ebe9
			if ((*c & 0xF0) == 0xE0) {
b6ebe9
				if (c < e2 && (c[1] & 0xC0) == 0x80 && (c[2] & 0xC0) == 0x80) {
b6ebe9
					code = ((c[0] & 0x0F) << 12) | ((c[1] & 0x3F) << 6) | (c[2] & 0x3F);
b6ebe9
					c += 2;
b6ebe9
				}
f63775
			}
b6ebe9
			++c;
b6ebe9
		} else {
b6ebe9
			code = 0;
f63775
		}
f63775
		
f63775
		HeliGlyph *glyph = &blankGlyph;
49ee44
		int special = code == 0 || code == 10 || code == 13;
49ee44
		int code2 = special ? 32 : code;
2fcd94
		for(int i = 0; i < fontsCount && !glyph->fi; ++i)
2fcd94
			glyph = getGlyph(fonts[i], code2);
2fcd94
		for(int i = 0; i < fontsCount && !glyph->fi; ++i)
2fcd94
			glyph = getGlyph(fonts[i], FONT_BADCHAR_CODE);
f63775
		if (!glyph->fi) continue;
f63775
		
da4619
		HeliCharDesc *cc = &layout->chars[line->end++];
da4619
		cc->charpos = charpos - text;
f63775
		cc->glyph = glyph;
47ef74
		if (line->height    < glyph->fi->height   ) line->height    = glyph->fi->height;
47ef74
		if (line->xheight   < glyph->fi->xheight  ) line->xheight   = glyph->fi->xheight;
47ef74
		if (line->ascender  < glyph->fi->ascender ) line->ascender  = glyph->fi->ascender;
47ef74
		if (line->descender < glyph->fi->descender) line->descender = glyph->fi->descender;
da4619
		if (line->begin < line->end - 1) {
f63775
			HeliCharDesc *cp = cc - 1;
f63775
			cc->pos = cp->pos + cp->glyph->advance;
f63775
			FT_Vector kerning = {};
f63775
			if ( cp->glyph->fi == cc->glyph->fi 
f63775
			  && !FT_Get_Kerning(
f63775
					cc->glyph->fi->face,
f63775
					cp->glyph->index,
f63775
					cc->glyph->index,
f63775
					FT_KERNING_UNFITTED,
f63775
					&kerning ))
f63775
				cc->pos += kerning.x/64.0;
5a7c99
			if (line->width < cc->pos + cc->glyph->advance)
5a7c99
				line->width = cc->pos + cc->glyph->advance;
f63775
		}
47ef74
		
49ee44
		if (!special && cc->glyph->l + HELI_PRECISION < cc->glyph->r) {
49ee44
			// expand line for the visible glyph
47ef74
			expand(&line->l, &line->r, cc->pos + cc->glyph->l);
47ef74
			expand(&line->l, &line->r, cc->pos + cc->glyph->r);
49ee44
		} else
49ee44
		if (!special && isspace(code)) {
49ee44
			// expand line for the space char
49ee44
			expand(&line->l, &line->r, cc->pos + cc->glyph->advance);
49ee44
		} else
49ee44
		if (line->begin >= line->end - 1) {
49ee44
			// is glyph is not space and not visible then expand line only for the first char in the line
49ee44
			expand(&line->l, &line->r, cc->pos);
47ef74
		}
49ee44
		
da4619
		expand(&line->t, &line->b, cc->glyph->t);
da4619
		expand(&line->t, &line->b, cc->glyph->b);
da4619
		expand(&line->t, &line->b, -glyph->fi->height);
da4619
		
da4619
		if (code == 10 || code == 13) {
da4619
			if ((code == 10 && *c == 13) || (code == 13 && *c == 10)) ++c;
da4619
			if (layout->linesCount >= linesAllocated) {
da4619
				linesAllocated += linesAllocated/4;
da4619
				layout->lines = realloc(layout->lines, linesAllocated*sizeof(HeliLineDesc));
da4619
				memset(layout->lines + layout->linesCount, 0, (linesAllocated - layout->linesCount)*sizeof(HeliLineDesc));
da4619
				line = &layout->lines[layout->linesCount - 1];
da4619
			}
da4619
			++line;
da4619
			++layout->linesCount;
da4619
			line->begin = line->end = (line - 1)->end;
2fcd94
			line->height = fonts[0]->height;
47ef74
			line->xheight = fonts[0]->xheight;
47ef74
			line->ascender = fonts[0]->ascender;
47ef74
			line->descender = fonts[0]->descender;
da4619
		}
da4619
		if (code == 0) break;
f63775
	}
f63775
	
da4619
	int charsCount = layout->lines[layout->linesCount-1].end;
da4619
	if (charsCount <= 0) return layout;
534343
f63775
	// bounds
47ef74
	double lineHeight = layout->lines->height;
47ef74
	if (drawingState->vertAlign == VALIGN_CENTER)
47ef74
		lineHeight = layout->lines->xheight;
534343
	double l = 0;
da4619
	double t = -layout->lines->height;
534343
	double r = 0;
534343
	double b = 0;
5a7c99
	double ll = 0;
47ef74
	double lt = -lineHeight;
5a7c99
	double lr = 0;
5a7c99
	double lb = 0;
da4619
	for(int i = 0; i < layout->linesCount; ++i) {
da4619
		HeliLineDesc *line = layout->lines + i;
534343
		if (i > 0) line->y = (line-1)->y + line->height;
5a7c99
		
f63775
		if (l > line->l) l = line->l;
f63775
		if (r < line->r) r = line->r;
f63775
		if (t > line->t + line->y) t = line->t + line->y;
f63775
		if (b < line->b + line->y) b = line->b + line->y;
5a7c99
		
47ef74
		if (lr < line->r) lr = line->r;
47ef74
		if (lt > line->y - lineHeight) lt = line->y - lineHeight;
5a7c99
		if (lb < line->y) lb = line->y;
f63775
	}
f63775
	
f63775
	// alignment
5a7c99
	double dx = drawingState->horAlign  == HALIGN_RIGHT  ? -lr
5a7c99
	          : drawingState->horAlign  == HALIGN_CENTER ? -0.5*(ll + lr)
5a7c99
	          :                                            -ll;
5a7c99
	double dy = drawingState->vertAlign == VALIGN_BOTTOM ? -lb
5a7c99
	          : drawingState->vertAlign == VALIGN_CENTER ? -0.5*(lt + lb)
5a7c99
	          :                                            -lt;
da4619
	for(int i = 0; i < layout->linesCount; ++i) {
da4619
		HeliLineDesc *line = layout->lines + i;
47ef74
		line->x = drawingState->horAlign == HALIGN_RIGHT  ? -line->r
47ef74
		        : drawingState->horAlign == HALIGN_CENTER ? -0.5*line->r
5a7c99
		        :                                           0;
f63775
		line->y += dy;
f63775
		for(int j = line->begin; j < line->end; ++j) {
da4619
			HeliCharDesc *cc = layout->chars + j;
f63775
			cc->x = cc->pos + line->x;
f63775
			cc->y = line->y;
f63775
		}
f63775
	}
f63775
	
da4619
	// bounds
da4619
	layout->l = l + dx;
da4619
	layout->t = t + dy;
da4619
	layout->r = r + dx;
da4619
	layout->b = b + dy;
da4619
	
5a7c99
	layout->ll = ll + dx;
5a7c99
	layout->lt = lt + dy;
5a7c99
	layout->lr = lr + dx;
5a7c99
	layout->lb = lb + dy;
5a7c99
	
f63775
	// sort by textures	
b9c036
	for(int i = 0; i < charsCount-1; ++i)
da4619
		layout->chars[i].next = &layout->chars[i+1];
da4619
	HeliCharDesc *first = NULL, *last = NULL, *current = layout->chars;
f63775
	while(current) {
f63775
		if (!current->next) {
f63775
			current = current->next = first;
f63775
		} else
f63775
		if (current->glyph->map != current->next->glyph->map) {
f63775
			*(last ? &last->next : &first) = current->next;
f63775
			last = current->next;
f63775
			current->next = current->next->next;
f63775
			last->next = NULL;
f63775
		} else {
f63775
			current = current->next;
f63775
		}
f63775
	}
da4619
da4619
	return layout;
da4619
}
da4619
da4619
b6ebe9
TextLayout createTextLayout(const char *text)
b6ebe9
	{ return createTextLayoutEx(text, strlen(text)); }
b6ebe9
b6ebe9
7ebc4f
TextLayout createTextLayoutf(const char *format, ...) {
7ebc4f
	va_list args;
7ebc4f
	va_start(args, format);
7ebc4f
	
7ebc4f
	va_list args_copy;
7ebc4f
	va_copy(args_copy, args);
7ebc4f
	int size = vsnprintf(NULL, 0, format, args_copy);
7ebc4f
	va_end(args_copy);
7ebc4f
	if (size < 0) size = 0;
7ebc4f
	++size;
7ebc4f
	char *buffer = calloc(1, size);
7ebc4f
	vsnprintf(buffer, size, format, args);
7ebc4f
	va_end(args);
7ebc4f
	
7ebc4f
	TextLayout layout = createTextLayout(buffer);
7ebc4f
	free(buffer);
7ebc4f
	return layout;
7ebc4f
}
7ebc4f
7ebc4f
da4619
void textLayoutDestroy(TextLayout layout) {
da4619
	if (layout) {
da4619
		free(layout->lines);
da4619
		free(layout->chars);
da4619
		if (layout->font) fontDestroy(layout->font);
da4619
	}
da4619
	free(layout);
da4619
}
da4619
da4619
da4619
void textLayoutDrawSubstr(TextLayout layout, double x, double y, int start, int length) {
da4619
	resetPath();
da4619
	
da4619
	if (layout->linesCount <= 0) return;
da4619
	assert(layout->lines);
da4619
	assert(layout->chars);
da4619
	int charsCount = layout->lines[layout->linesCount - 1].end;
da4619
	if (charsCount <= 0) return;
da4619
	
da4619
	int textEnd = layout->chars[charsCount - 1].charpos;
da4619
	int begin = start;
da4619
	if (begin < 0) begin = 0;
da4619
	long long endll = (long long)begin + length; // to avoid overflow on length == INT_MAX and begin > 0
da4619
	if (endll > textEnd) endll = textEnd;
da4619
	int end = (int)endll;
da4619
	if (begin >= end) return;
da4619
	
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
f63775
	
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	if (fontScale <= HELI_PRECISION) return;
deef1d
	if ( drawingState->strokeColor[3] <= HELI_PRECISION
deef1d
	  && drawingState->fillColor[3] <= HELI_PRECISION ) return;
da4619
f63775
	const double borderK = 0.5*FONT_MAP_BORDERSIZE/FONT_MAP_GLYPHWORKSIZE;
f63775
	const double texStep = 1.0/FONT_MAP_CNT;
3e56ed
	const double texBorder = 0.5*FONT_MAP_BORDERSIZE/FONT_MAP_TEXSIZE;
3e56ed
	const double texGlyphSize = (double)FONT_MAP_GLYPHWORKSIZE/FONT_MAP_TEXSIZE;
f63775
	
b4b587
	if (drawingState->antialiasing && !heliGLIsIntegerClipping())
b4b587
		glEnable(GL_MULTISAMPLE);
f63775
	glPushMatrix();
f63775
	glTranslated(x, y, 0);
f63775
	glScaled(fontScale, fontScale, 1);
da4619
	
deef1d
	if (drawingState->fillColor[3] > HELI_PRECISION) {
deef1d
		double strokeAlpha = drawingState->strokeColor[3];
deef1d
		drawingState->strokeColor[3] = 0;
da4619
		for(int i = 0; i < layout->linesCount; ++i) {
da4619
			HeliLineDesc *line = layout->lines + i;
da4619
			int found = FALSE;
da4619
			double l = 0, r = 0;
da4619
			for(int j = line->begin; j < line->end; ++j) {
da4619
				HeliCharDesc *cc = layout->chars + j;
da4619
				if (cc->charpos < begin || cc->charpos > end) continue;
da4619
				if (!found) { l = r = cc->x; found = TRUE; }
da4619
				expand(&l, &r, cc->x);
da4619
				if (cc->glyph && cc->charpos < end) {
da4619
					expand(&l, &r, cc->x + cc->glyph->l);
da4619
					expand(&l, &r, cc->x + cc->glyph->r);
da4619
				}
da4619
			}
da4619
			if (found)
da4619
				rect(l, line->t + line->y, r-l, line->b - line->t);
f63775
		}
deef1d
		drawingState->strokeColor[3] = strokeAlpha;
f63775
	}
da4619
	
deef1d
	if (drawingState->strokeColor[3] > HELI_PRECISION) {
deef1d
		glColor4dv(drawingState->strokeColor);
da4619
		HeliFontMap *currentMap = NULL;
da4619
		glEnable(GL_TEXTURE_2D);
da4619
		glBegin(GL_QUADS);
da4619
		for(HeliCharDesc *cc = layout->chars; cc; cc = cc->next) {
da4619
			if (cc->charpos < begin || cc->charpos >= end) continue;
da4619
			if ( !cc->glyph->map
da4619
			|| cc->glyph->r - cc->glyph->l <= HELI_PRECISION
da4619
			|| cc->glyph->b - cc->glyph->t <= HELI_PRECISION )
da4619
				continue;
da4619
			if (currentMap != cc->glyph->map) {
da4619
				glEnd();
da4619
				currentMap = cc->glyph->map;
da4619
				glBindTexture(GL_TEXTURE_2D, currentMap->texid);
da4619
				glBegin(GL_QUADS);
da4619
			}
da4619
			
3e56ed
			double gw = cc->glyph->r - cc->glyph->l;
3e56ed
			double gh = cc->glyph->b - cc->glyph->t;
3e56ed
			double gs = gw > gh ? gw : gh;
3e56ed
			double dx = gs*borderK;
3e56ed
			double dy = gs*borderK;
da4619
			double l = cc->x + cc->glyph->l - dx;
da4619
			double t = cc->y + cc->glyph->t - dy;
da4619
			double r = cc->x + cc->glyph->r + dx;
da4619
			double b = cc->y + cc->glyph->b + dy;
da4619
			
3e56ed
			double ttx = gh > gw ? gw/gh : 1;
3e56ed
			double tty = gw > gh ? gh/gw : 1;
3e56ed
			
da4619
			int gi = cc->glyph - cc->glyph->map->glyphs;
3e56ed
			double tl = (gi % FONT_MAP_CNT)*texStep - texBorder;
3e56ed
			double tt = (gi / FONT_MAP_CNT)*texStep - texBorder;
3e56ed
			double tr = tl + texGlyphSize*ttx + 2*texBorder;
3e56ed
			double tb = tt + texGlyphSize*tty + 2*texBorder;
da4619
			
da4619
			glTexCoord2d(tl, tt); glVertex2d(l, t);
da4619
			glTexCoord2d(tr, tt); glVertex2d(r, t);
da4619
			glTexCoord2d(tr, tb); glVertex2d(r, b);
da4619
			glTexCoord2d(tl, tb); glVertex2d(l, b);
da4619
		}
da4619
		glEnd();
da4619
		glBindTexture(GL_TEXTURE_2D, 0);
da4619
		glDisable(GL_TEXTURE_2D);
da4619
	}
da4619
	
f63775
	glPopMatrix();
8bc1f1
	glDisable(GL_MULTISAMPLE);
f63775
	glColor4d(1, 1, 1, 1);
f63775
}
a20939
a20939
da4619
void textLayoutDrawFrom(TextLayout layout, double x, double y, int start)
da4619
	{ textLayoutDrawSubstr(layout, x, y, start, INT_MAX); }
da4619
void textLayoutDraw(TextLayout layout, double x, double y)
da4619
	{ textLayoutDrawFrom(layout, x, y, 0); }
da4619
da4619
5d2371
void text(double x, double y, const char *text) {
da4619
	resetPath();
5d2371
	
5d2371
	if (!text || !*text) return;
da4619
	
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	if (fontScale < HELI_PRECISION) return;
deef1d
	if ( drawingState->strokeColor[3] < HELI_PRECISION
deef1d
	  && drawingState->fillColor[3] < HELI_PRECISION ) return;
da4619
	
da4619
	TextLayout layout = createTextLayout(text);
da4619
	textLayoutDraw(layout, x, y);
da4619
	textLayoutDestroy(layout);
da4619
}
da4619
5d2371
void textf(double x, double y, const char *format, ...) {
5d2371
	va_list args;
5d2371
	va_start(args, format);
5d2371
	
5d2371
	va_list args_copy;
5d2371
	va_copy(args_copy, args);
5d2371
	int size = vsnprintf(NULL, 0, format, args_copy);
5d2371
	va_end(args_copy);
5d2371
	if (size < 0) size = 0;
5d2371
	++size;
5d2371
	char *buffer = calloc(1, size);
5d2371
	vsnprintf(buffer, size, format, args);
5d2371
	va_end(args);
5d2371
	
5d2371
	text(x, y, buffer);
5d2371
	free(buffer);
5d2371
}
5d2371
da4619
47ef74
double textLayoutGetLeft(TextLayout layout) {
47ef74
	HeliDrawingState *drawingState = heliDrawingGetState();
47ef74
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
47ef74
	return layout->l*fontScale;
47ef74
}
47ef74
47ef74
47ef74
double textLayoutGetTop(TextLayout layout) {
47ef74
	HeliDrawingState *drawingState = heliDrawingGetState();
47ef74
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
47ef74
	return layout->t*fontScale;
47ef74
}
47ef74
47ef74
da4619
double textLayoutGetWidth(TextLayout layout) {
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	return (layout->r - layout->l)*fontScale;
da4619
}
da4619
da4619
da4619
double textLayoutGetHeight(TextLayout layout) {
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	return (layout->b - layout->t)*fontScale;
da4619
}
da4619
da4619
47ef74
double textLayoutGetTopAscenderLine(TextLayout layout) {
47ef74
	HeliDrawingState *drawingState = heliDrawingGetState();
47ef74
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
47ef74
	return (layout->lines->y - layout->lines->xheight)*fontScale;
47ef74
}
47ef74
47ef74
47ef74
double textLayoutGetTopXLine(TextLayout layout) {
47ef74
	HeliDrawingState *drawingState = heliDrawingGetState();
47ef74
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
47ef74
	return (layout->lines->y - layout->lines->ascender)*fontScale;
47ef74
}
47ef74
47ef74
47ef74
double textLayoutGetBottomBaseline(TextLayout layout) {	
47ef74
	HeliDrawingState *drawingState = heliDrawingGetState();
47ef74
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
47ef74
	return layout->lines[layout->linesCount - 1].y * fontScale;
47ef74
}
47ef74
47ef74
a20939
void heliFontFinish() {
2fcd94
	defaultFont = NULL;
2fcd94
	unicodeFont = NULL;
a20939
	heliArrayDestroy(&fontCache);
a20939
	if (ftInitialized && ftLibrary) FT_Done_FreeType(ftLibrary);
a20939
	ftLibrary = NULL;
a20939
	ftInitialized = FALSE;
a20939
}
da4619
da4619
static void findChar(TextLayout layout, int cursor, int *lineIndex, int *charIndex) {
da4619
	*lineIndex = 0;
da4619
	*charIndex = 0;
da4619
	if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return;
da4619
	
da4619
	*lineIndex = layout->linesCount - 1;
da4619
	*charIndex = layout->lines[*lineIndex].end - 1;
da4619
	for(int i = 0; i < layout->linesCount; ++i) {
da4619
		HeliLineDesc *line = layout->lines + i;
da4619
		if (line->begin < line->end) {
da4619
			if (layout->chars[line->end-1].charpos >= cursor) {
da4619
				*lineIndex = i;
da4619
				*charIndex = line->end-1;
da4619
				for(int j = line->begin; j < line->end; ++j)
da4619
					if (layout->chars[j].charpos >= cursor)
da4619
						{ *charIndex = j; break; }
da4619
				return;
da4619
			}
da4619
		}
da4619
	}
da4619
}
da4619
da4619
static int moveCursorVert(TextLayout layout, int cursor, int offset) {
da4619
	if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0;
da4619
	
da4619
	int li = 0, ci = 0;
da4619
	findChar(layout, cursor, &li, &ci);
da4619
	
da4619
	li += offset;
da4619
	if (li < 0)
da4619
			return 0;
da4619
	if (li >= layout->linesCount)
da4619
		return layout->chars[ layout->lines[layout->linesCount-1].end - 1 ].charpos;
da4619
	
da4619
	double x = layout->chars[ci].x;
da4619
	HeliLineDesc *line = layout->lines + li;
da4619
	for(int j = line->begin; j < line->end; ++j) {
da4619
		HeliCharDesc *cc1 = layout->chars + j;
da4619
		double xx = layout->chars[j].x; 
da4619
		if (xx + HELI_PRECISION >= x || j == line->end - 1) {
da4619
			if (j > line->begin) {
da4619
				HeliCharDesc *cc0 = cc1 - 1;
da4619
				if (fabs(cc0->x - x) < fabs(xx - x)) return cc0->charpos;
da4619
			}
da4619
			return cc1->charpos;
da4619
		}
da4619
	}
da4619
	return cursor;
da4619
}
da4619
da4619
int textLayoutCursorUp(TextLayout layout, int cursor)
da4619
	{ return moveCursorVert(layout, cursor, -1); }
da4619
da4619
int textLayoutCursorDown(TextLayout layout, int cursor)
da4619
	{ return moveCursorVert(layout, cursor, 1); }
da4619
da4619
double textLayoutCursorGetX(TextLayout layout, int cursor) {
da4619
	if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0;
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	int li = 0, ci = 0;
da4619
	findChar(layout, cursor, &li, &ci);
da4619
	return layout->chars[ci].x * fontScale;
da4619
}
da4619
da4619
double textLayoutCursorGetY(TextLayout layout, int cursor) {
da4619
	if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0;
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	int li = 0, ci = 0;
da4619
	findChar(layout, cursor, &li, &ci);
da4619
	return layout->chars[ci].y * fontScale;
da4619
}
da4619
da4619
double textLayoutCursorGetHeight(TextLayout layout, int cursor) {
da4619
	if (!layout || layout->linesCount <= 0 || !layout->lines || !layout->chars) return 0;
da4619
	HeliDrawingState *drawingState = heliDrawingGetState();
da4619
	double fontScale = drawingState->fontSize/FONT_BASE_SIZE;
da4619
	int li = 0, ci = 0;
da4619
	findChar(layout, cursor, &li, &ci);
da4619
	return layout->lines[li].height * fontScale;
da4619
}