|
|
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 |
|
|
|
da4619 |
TextLayout createTextLayout(const char *text) {
|
|
|
da4619 |
TextLayout layout = calloc(1, sizeof(*layout));
|
|
|
f63775 |
|
|
|
f8dca4 |
if (!text) 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;
|
|
|
da4619 |
layout->chars = calloc(strlen(text) + 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
|
|
|
f63775 |
const char *c = text;
|
|
|
f63775 |
while(1) {
|
|
|
da4619 |
const char *charpos = c;
|
|
|
f63775 |
int code = FONT_BADCHAR_CODE;
|
|
|
f63775 |
while((*c & 0xC0) == 0x80) ++c;
|
|
|
f63775 |
if ((*c & 0x80) == 0x00) {
|
|
|
f63775 |
code = *c;
|
|
|
f63775 |
} else
|
|
|
f63775 |
if ((*c & 0xE0) == 0xC0) {
|
|
|
f63775 |
if ((c[1] & 0xC0) == 0x80) {
|
|
|
f63775 |
code = ((c[0] & 0x1F) << 6) | (c[1] & 0x3F);
|
|
|
f63775 |
c += 1;
|
|
|
f63775 |
}
|
|
|
f63775 |
} else
|
|
|
f63775 |
if ((*c & 0xF0) == 0xE0) {
|
|
|
f63775 |
if ((c[1] & 0xC0) == 0x80 && (c[2] & 0xC0) == 0x80) {
|
|
|
f63775 |
code = ((c[0] & 0x0F) << 12) | ((c[1] & 0x3F) << 6) | (c[2] & 0x3F);
|
|
|
f63775 |
c += 2;
|
|
|
f63775 |
}
|
|
|
f63775 |
}
|
|
|
f63775 |
++c;
|
|
|
f63775 |
|
|
|
f63775 |
HeliGlyph *glyph = &blankGlyph;
|
|
|
da4619 |
int code2 = (code == 0 || code == 10 || code == 13) ? 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 |
|
|
|
47ef74 |
if ( line->begin >= line->end - 1
|
|
|
47ef74 |
|| ( cc->glyph->l + HELI_PRECISION < cc->glyph->r
|
|
|
47ef74 |
&& code != 10
|
|
|
47ef74 |
&& code != 13
|
|
|
47ef74 |
&& code != 0 ))
|
|
|
47ef74 |
{
|
|
|
47ef74 |
expand(&line->l, &line->r, cc->pos + cc->glyph->l);
|
|
|
47ef74 |
expand(&line->l, &line->r, cc->pos + cc->glyph->r);
|
|
|
47ef74 |
}
|
|
|
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 |
|
|
|
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 |
}
|