text.c
#ifdef LIL_USE_FREETYPE
#include "text.h"
static FT_Library ft;
static FcConfig* fontConfig;
wchar_t* lil_utf8ToWchar(const unsigned char* const text, const size_t textLen, size_t* const lenRet){
size_t i = 0;
size_t c = 0;
wchar_t* wtext = malloc((textLen + 1) * sizeof(wchar_t)); while(i < textLen){
wchar_t cp;
if(text[i] < 127){
cp = text[i++];
} else if((text[i] & 0b11100000) == 0b11000000){
if(i + 2 > textLen)
break;
cp = ((text[i] & 0b00011111) << 6) | (text[i+1] & 0b00111111);
i += 2;
} else if((text[i] & 0b11110000) == 0b11100000){
if(i + 3 > textLen)
break;
cp = ((text[i] & 0b00001111) << 12) | ((text[i+1] & 0b00111111) << 6) | (text[i+2] & 0b00111111);
i += 3;
} else if((text[i] & 0b11111000) == 0b11110000){
if(i + 4 > textLen)
break;
cp = ((text[i+1] & 0b00111111) << 12) | ((text[i+2] & 0b00111111) << 6) | (text[i+3] & 0b00111111);
i += 4;
} else {
continue;
}
if(cp < 0x20){
if(cp == '\n' || cp == '\t')
wtext[c++] = cp;
} else {
wtext[c++] = cp;
}
}
wtext[c] = '\0';
if(lenRet)
*lenRet = c;
return wtext;
}
char* lil_findFontPath(const char* patStr){
FcResult r;
FcPattern* pat = FcNameParse((FcChar8*) patStr);
if(!pat)
return NULL;
FcConfigSubstitute(fontConfig, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
FcFontSet* fs = FcFontSetCreate();
FcObjectSet* os = FcObjectSetBuild(FC_FILE, NULL);
FcFontSet* fontPatterns = FcFontSort(fontConfig, pat, FcTrue, 0, &r);
if(!fontPatterns || fontPatterns->nfont == 0){
FcObjectSetDestroy(os);
return NULL;
}
FcPattern* fontPattern = FcFontRenderPrepare(fontConfig, pat, fontPatterns->fonts[0]);
if(!fontPattern){
FcObjectSetDestroy(os);
return NULL;
}
FcFontSetAdd(fs, fontPattern);
FcFontSetSortDestroy(fontPatterns);
FcPatternDestroy(pat);
if(!fs || fs->nfont == 0){
FcObjectSetDestroy(os);
return NULL;
}
FcValue v;
FcPattern* font = FcPatternFilter(fs->fonts[0], os);
FcPatternGet(font, FC_FILE, 0, &v);
const char* path = (char*)v.u.f;
const size_t len = strlen(path);
char* pathClone = malloc(len+1);
strcpy(pathClone, path);
FcPatternDestroy(font);
return pathClone;
}
lil_Font* lil_getFont(lua_State* L, int i){
lil_Font* font = *((lil_Font**)luaL_checkudata(L, i, LIL_FONT_UD_MT));
return font;
}
lil_Font* lil_newFont(const char* path, int w, int h, lil_Colour c){
const int faceNum = 0; FT_Face face;
if(FT_New_Face(ft, path, faceNum, &face))
return NULL;
if(FT_Set_Pixel_Sizes(face, w, h))
return 0;
lil_Font* font = malloc(sizeof(lil_Font));
font->face = face;
font->vector = false;
font->colour = c;
font->w = w;
font->h = h;
return font;
}
void lil_freeFont(lil_Font* font){
FT_Done_Face(font->face);
free(font);
}
int lil_initFreetype(){
if(FT_Init_FreeType(&ft))
return 1;
if(!(fontConfig = FcInitLoadConfigAndFonts()))
return 1;
return 0;
}
void lil_freeFreetype(){
FT_Done_FreeType(ft);
FcConfigDestroy(fontConfig);
}
void lil_getTextSize(const lil_Font* font, const wchar_t* text, size_t len, int* w, int* h){
FT_Face face = font->face;
int baseHeight = face->size->metrics.height >> 6;
int neededWidth = 0;
int widest = 0;
int neededHeight = baseHeight;
for(int i = 0; i < len; i++){
wchar_t cha = text[i];
if(cha == '\n'){
neededHeight += baseHeight;
if(neededWidth > widest)
widest = neededWidth;
neededWidth = 0;
} else if(cha == '\t'){
FT_Load_Char(face, ' ', 0);
neededWidth += (face->glyph->advance.x >> 6) * 4;
} else {
FT_Load_Char(face, cha, 0);
neededWidth += face->glyph->advance.x >> 6;
}
}
if(widest > neededWidth)
neededWidth = widest;
if(w) *w = neededWidth;
if(h) *h = neededHeight;
}
void lil_writeTextToImage(const lil_Font* font, const wchar_t* wtext, size_t len, int neededWidth, int neededHeight, const lil_Image* img, enum lil_BlendMode bm, int imgOffsetX, int imgOffsetY){
if(neededWidth == 0 || neededHeight == 0)
lil_getTextSize(font, wtext, len, &neededWidth, &neededHeight);
FT_Face face = font->face;
lil_Colour c = font->colour;
uint8_t ca = c.a;
int height = face->size->metrics.height >> 6;
int offsetX = 0;
int offsetY = -(height * max((neededHeight / height) - 1, 0));
int bottom = face->size->metrics.descender >> 6;
for(int i = 0; i < len; i++){
wchar_t cha = wtext[i];
if(cha == '\n'){
offsetY += face->size->metrics.height >> 6;
offsetX = 0;
} else if(cha == '\t'){
FT_Load_Char(face, ' ', 0);
offsetX += (face->glyph->advance.x >> 6) * 4;
} else {
if(FT_Load_Char(face, cha, FT_LOAD_RENDER))
continue;
FT_GlyphSlot glyph = face->glyph;
int bitMapLeft = glyph->bitmap_left;
int bitMapTop = glyph->bitmap_top;
int xp = offsetX + bitMapLeft;
int yp = offsetY - (bitMapTop - neededHeight) + bottom;
for(int fy = 0; fy < glyph->bitmap.rows; fy++){
for(int fx = 0; fx < glyph->bitmap.width; fx++){
if(yp+fy > neededHeight - 1)
continue;
int a = glyph->bitmap.buffer[(fy * glyph->bitmap.pitch) + fx];
int x = fx + xp + imgOffsetX;
int y = fy + yp + imgOffsetY;
if(x >= 0 && x < img->w && y >= 0 && y < img->h){
c.a = (a * ca) / 255;
size_t o = XY(img, x, y);
lil_composite(bm, &img->d[o], &c);
}
}
}
offsetX += glyph->advance.x >> 6;
}
}
}
static LUAFUNC(font_ud_text){
const lil_Font* font = lil_getFont(L, 1);
const char* text = luaL_checkstring(L, 2);
size_t len;
wchar_t* wtext = lil_utf8ToWchar((const unsigned char*)text, strlen(text), &len);
int neededWidth = 0;
int neededHeight = 0;
lil_getTextSize(font, wtext, len, &neededWidth, &neededHeight);
lil_Image* img = lil_newImageCalloc(L, neededWidth, neededHeight);
if(!img){
free(wtext);
return 0;
}
lil_writeTextToImage(font, wtext, len, neededWidth, neededHeight, img, LIL_BMREPLACE, 0, 0);
lil_pushImage(L, img);
free(wtext);
return 1;
}
static LUAFUNC(font_ud_size){
lil_Font* font = lil_getFont(L, 1);
const int w = luaL_checkinteger(L, 2);
const int h = luaL_optinteger(L, 3, w);
if(FT_Set_Pixel_Sizes(font->face, w, h))
luaL_error(L, "Failed to set size");
font->w = w;
font->h = h;
lua_pushvalue(L, 1);
return 1;
}
static LUAFUNC(font_ud_colour){
lil_Font* font = lil_getFont(L, 1);
const lil_Colour col = lil_getColour(L, 2, NULL);
font->colour = col;
lua_pushvalue(L, 1);
return 1;
}
static LUAFUNC(font_ud_getSize){
lil_Font* font = lil_getFont(L, 1);
lua_pushinteger(L, font->w);
lua_pushinteger(L, font->h);
return 2;
}
static LUAFUNC(font_ud_getColour){
lil_Font* font = lil_getFont(L, 1);
lil_Colour* col = &font->colour;
lil_Number fc[4];
#pragma omp simd
for(int i = 0; i < 4; i++)
fc[i] = (lil_Number)col->arr[i] / (lil_Number)0xff;
lua_pushnumber(L, fc[0]);
lua_pushnumber(L, fc[1]);
lua_pushnumber(L, fc[2]);
lua_pushnumber(L, fc[3]);
return 4;
}
static LUAFUNC(font_ud_measure){
int w, h;
size_t len;
lil_Font* font = lil_getFont(L, 1);
const char* text = luaL_checkstring(L, 2);
const wchar_t* wtext = lil_utf8ToWchar((const unsigned char*)text, strlen(text), &len);
lil_getTextSize(font, wtext, len, &w, &h);
lua_pushinteger(L, w);
lua_pushinteger(L, h);
return 2;
}
static LUAFUNC(font_ud_gc){
lil_Font* font = lil_getFont(L, 1);
lil_freeFont(font);
return 0;
};
LUAREG(fontUdLib) = {
{ "text", LUAFUNCD(font_ud_text) },
{ "size", LUAFUNCD(font_ud_size) },
{ "colour", LUAFUNCD(font_ud_colour) },
{ "getSize", LUAFUNCD(font_ud_getSize) },
{ "getColour", LUAFUNCD(font_ud_getColour) },
{ "measure", LUAFUNCD(font_ud_measure) },
{ "__gc", LUAFUNCD(font_ud_gc) },
{ NULL, NULL }
};
#endif