blob: d224fbb120af57eb2770cc58905aea1e5273cab6 [file] [log] [blame]
/*
Copyright 2012 to 2020 TeamWin
This file is part of TWRP/TeamWin Recovery Project.
TWRP is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
TWRP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with TWRP. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <pthread.h>
#include <algorithm>
#include <string>
#include "minuitwrp/truetype.hpp"
extern unsigned int gr_rotation;
static FontData font_data = {
.ft_library = NULL,
.mutex = PTHREAD_MUTEX_INITIALIZER
};
twrpTruetype::twrpTruetype(void) {
}
int twrpTruetype::utf8_to_unicode(const char* pIn, unsigned int *pOut) {
int utf_bytes = 1;
unsigned int unicode = 0;
unsigned char tmp;
tmp = (unsigned char)*pIn++;
if (tmp < 0x80)
{
*pOut = tmp;
}
else
{
unsigned int high_bit_mask = 0x3F;
unsigned int high_bit_shift = 0;
int total_bits = 0;
while((tmp & 0xC0) == 0xC0)
{
utf_bytes ++;
if(utf_bytes > 6)
{
*pOut = tmp;
return 1;
}
tmp = 0xFF & (tmp << 1);
total_bits += 6;
high_bit_mask >>= 1;
high_bit_shift++;
unicode <<= 6;
unicode |= (*pIn++) & 0x3F;
}
unicode |= ((tmp >> high_bit_shift) & high_bit_mask) << total_bits;
*pOut = unicode;
}
return utf_bytes;
}
void* twrpTruetype::gr_ttf_loadFont(const char *filename, int size, int dpi) {
int error;
TrueTypeFont* res = nullptr;
TrueTypeFontKey* key;
std::string fontFileName(filename);
pthread_mutex_lock(&font_data.mutex);
TrueTypeFontKey k = {
.size = size,
.dpi = dpi,
.path = fontFileName
};
TrueTypeFontMap::iterator ttfIter = font_data.fonts.find(k);
if (ttfIter != font_data.fonts.end())
{
res = ttfIter->second;
++res->refcount;
goto exit;
}
if(!font_data.ft_library)
{
error = FT_Init_FreeType(&font_data.ft_library);
if(error)
{
fprintf(stderr, "Failed to init libfreetype! %d\n", error);
goto exit;
}
}
FT_Face face;
error = FT_New_Face(font_data.ft_library, filename, 0, &face);
if(error)
{
fprintf(stderr, "Failed to load truetype face %s: %d\n", filename, error);
goto exit;
}
error = FT_Set_Char_Size(face, 0, size*16, dpi, dpi);
if(error)
{
fprintf(stderr, "Failed to set truetype face size to %d, dpi %d: %d\n", size, dpi, error);
FT_Done_Face(face);
goto exit;
}
res = new TrueTypeFont;
res->type = FONT_TYPE_TTF;
res->size = size;
res->dpi = dpi;
res->face = face;
res->max_height = -1;
res->base = -1;
res->refcount = 1;
pthread_mutex_init(&res->mutex, 0);
key = new TrueTypeFontKey;
key->path = strdup(filename);
key->size = size;
key->dpi = dpi;
res->key = key;
font_data.fonts[*key] = res;
exit:
pthread_mutex_unlock(&font_data.mutex);
return res;
}
void* twrpTruetype::gr_ttf_scaleFont(void *font, int max_width, int measured_width) {
if (!font)
return nullptr;
TrueTypeFont *f = (TrueTypeFont *)font;
float scale_value = (float)(max_width) / (float)(measured_width);
int new_size = ((int)((float)f->size * scale_value)) - 1;
if (new_size < 1)
new_size = 1;
const char* file = f->key->path.c_str();
int dpi = f->dpi;
return gr_ttf_loadFont(file, new_size, dpi);
}
static bool gr_ttf_freeFontCache(void *value, void *context __unused)
{
TrueTypeCacheEntry *e = (TrueTypeCacheEntry *)value;
FT_Done_Glyph((FT_Glyph)e->glyph);
delete e;
return true;
}
void twrpTruetype::gr_ttf_freeStringCache(void *key, void *value, void *context __unused) {
StringCacheKey *k = (StringCacheKey *)key;
delete k;
StringCacheEntry *e = (StringCacheEntry *)value;
free(e->surface.data);
delete e;
}
void twrpTruetype::gr_ttf_freeFont(void *font) {
pthread_mutex_lock(&font_data.mutex);
TrueTypeFont *d = (TrueTypeFont *)font;
if(--d->refcount == 0)
{
delete d->key;
FT_Done_Face(d->face);
StringCacheMap::iterator stringCacheEntryIt = d->string_cache.begin();
while (stringCacheEntryIt != d->string_cache.end()) {
gr_ttf_freeStringCache(stringCacheEntryIt->second->key, stringCacheEntryIt->second, nullptr);
stringCacheEntryIt = d->string_cache.erase(stringCacheEntryIt);
}
TrueTypeCacheEntryMap::iterator ttcIt = d->glyph_cache.begin();
while(ttcIt != d->glyph_cache.end()) {
gr_ttf_freeFontCache(ttcIt->second, nullptr);
ttcIt = d->glyph_cache.erase(ttcIt);
}
pthread_mutex_destroy(&d->mutex);
TrueTypeFontMap::iterator trueTypeFontIt = font_data.fonts.find(*(d->key));
delete d;
font_data.fonts.erase(trueTypeFontIt);
}
pthread_mutex_unlock(&font_data.mutex);
}
TrueTypeCacheEntry* twrpTruetype::gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index) {
TrueTypeCacheEntryMap::iterator glyphCacheItr = font->glyph_cache.find(char_index);
if(glyphCacheItr != font->glyph_cache.end()) {
return font->glyph_cache[char_index];
}
return nullptr;
}
TrueTypeCacheEntry* twrpTruetype::gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index) {
TrueTypeCacheEntryMap::iterator glyphCacheItr = font->glyph_cache.find(char_index);
TrueTypeCacheEntry* res = nullptr;
if(glyphCacheItr == font->glyph_cache.end())
{
int error = FT_Load_Glyph(font->face, char_index, FT_LOAD_RENDER);
if(error)
{
fprintf(stderr, "Failed to load glyph idx %d: %d\n", char_index, error);
return nullptr;
}
FT_BitmapGlyph glyph;
error = FT_Get_Glyph(font->face->glyph, (FT_Glyph*)&glyph);
if(error)
{
fprintf(stderr, "Failed to copy glyph %d: %d\n", char_index, error);
return nullptr;
}
res = new TrueTypeCacheEntry;
res->glyph = glyph;
FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox);
font->glyph_cache[char_index] = res;
}
else {
res = glyphCacheItr->second;
}
return res;
}
int twrpTruetype::gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base) {
unsigned y;
uint8_t *src_itr = glyph->bitmap.buffer;
uint8_t *dest_itr = dest->data;
if(glyph->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
{
fprintf(stderr, "Unsupported pixel mode in FT_BitmapGlyph %d\n", glyph->bitmap.pixel_mode);
return -1;
}
dest_itr += (offY + base - glyph->top)*dest->stride + (offX + glyph->left);
// FIXME: if glyph->left is negative and everything else is 0 (e.g. letter 'j' in Roboto-Regular),
// the result might end up being before the buffer - I'm not sure how to properly handle this.
if(dest_itr < dest->data)
dest_itr = dest->data;
for(y = 0; y < glyph->bitmap.rows; ++y)
{
memcpy(dest_itr, src_itr, glyph->bitmap.width);
src_itr += glyph->bitmap.pitch;
dest_itr += dest->stride;
}
return 0;
}
void twrpTruetype::gr_ttf_calcMaxFontHeight(TrueTypeFont *f) {
char c;
int char_idx;
int error;
FT_Glyph glyph;
FT_BBox bbox;
FT_BBox bbox_glyph;
TrueTypeCacheEntry *ent;
bbox.yMin = bbox_glyph.yMin = LONG_MAX;
bbox.yMax = bbox_glyph.yMax = LONG_MIN;
for(c = '!'; c <= '~'; ++c)
{
char_idx = FT_Get_Char_Index(f->face, c);
ent = gr_ttf_glyph_cache_peek(f, char_idx);
if(ent)
{
bbox.yMin = MIN(bbox.yMin, ent->bbox.yMin);
bbox.yMax = MAX(bbox.yMax, ent->bbox.yMax);
}
else
{
error = FT_Load_Glyph(f->face, char_idx, 0);
if(error)
continue;
error = FT_Get_Glyph(f->face->glyph, &glyph);
if(error)
continue;
FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS, &bbox_glyph);
bbox.yMin = MIN(bbox.yMin, bbox_glyph.yMin);
bbox.yMax = MAX(bbox.yMax, bbox_glyph.yMax);
FT_Done_Glyph(glyph);
}
}
if(bbox.yMin > bbox.yMax)
bbox.yMin = bbox.yMax = 0;
f->max_height = bbox.yMax - bbox.yMin;
f->base = bbox.yMax;
// FIXME: twrp fonts have some padding on top, I'll add it here
// Should be fixed in the themes
f->max_height += f->size / 4;
f->base += f->size / 4;
}
// returns number of bytes from const char *text rendered to fit max_width, not number of UTF8 characters!
int twrpTruetype::gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const std::string text, int max_width) {
TrueTypeFont *f = font;
TrueTypeCacheEntry *ent;
int bytes_rendered = 0, total_w = 0;
int utf_bytes = 0;
unsigned int unicode = 0;
int i, x, diff, char_idx, prev_idx = 0;
int height;
FT_Vector delta;
uint8_t *data = NULL;
const char *text_itr = text.c_str();
int *char_idxs;
int char_idxs_len = 0;
char_idxs = new int[text.length()];
while(*text_itr)
{
utf_bytes = utf8_to_unicode(text_itr, &unicode);
text_itr += utf_bytes;
bytes_rendered += utf_bytes;
char_idx = FT_Get_Char_Index(f->face, unicode);
char_idxs[char_idxs_len] = char_idx;
ent = gr_ttf_glyph_cache_get(f, char_idx);
if(ent)
{
diff = ent->glyph->root.advance.x >> 16;
if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
{
FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
diff += delta.x >> 6;
}
if(max_width != -1 && total_w + diff > max_width)
break;
total_w += diff;
}
prev_idx = char_idx;
++char_idxs_len;
}
if(font->max_height == -1)
gr_ttf_calcMaxFontHeight(font);
if(font->max_height == -1)
{
delete [] char_idxs;
return -1;
}
height = font->max_height;
data = (uint8_t *)malloc(total_w*height);
memset(data, 0, total_w*height); x = 0;
prev_idx = 0;
surface->version = sizeof(*surface);
surface->width = total_w;
surface->height = height;
surface->stride = total_w;
surface->data = (GGLubyte*)data;
surface->format = GGL_PIXEL_FORMAT_A_8;
for(i = 0; i < char_idxs_len; ++i)
{
char_idx = char_idxs[i];
if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
{
FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
x += delta.x >> 6;
}
ent = gr_ttf_glyph_cache_get(f, char_idx);
if(ent)
{
gr_ttf_copy_glyph_to_surface(surface, ent->glyph, x, 0, font->base);
x += ent->glyph->root.advance.x >> 16;
}
prev_idx = char_idx;
}
delete [] char_idxs;
return bytes_rendered;
}
StringCacheEntry* twrpTruetype::gr_ttf_string_cache_peek(TrueTypeFont *font,
const std::string text,
__attribute__((unused)) int max_width) {
StringCacheKey k = {
.text = text,
.max_width = max_width
};
StringCacheMap::iterator stringCacheItr = font->string_cache.find(k);
if (stringCacheItr != font->string_cache.end()) {
return stringCacheItr->second;
}
else {
return nullptr;
}
}
void twrpTruetype::gr_ttf_string_cache_truncate(TrueTypeFont *font) {
StringCacheMap::iterator stringCacheItr;
if (font->string_cache.size() == STRING_CACHE_MAX_ENTRIES) {
StringCacheEntry *truncateEntry = nullptr;
stringCacheItr = font->string_cache.begin();
int deleteCtr = 0;
while (stringCacheItr != font->string_cache.end() || deleteCtr == (STRING_CACHE_MAX_ENTRIES - 1)) {
truncateEntry = stringCacheItr->second;
gr_ttf_freeStringCache(truncateEntry->key, truncateEntry, nullptr);
stringCacheItr = font->string_cache.erase(stringCacheItr);
deleteCtr++;
}
}
}
StringCacheEntry* twrpTruetype::gr_ttf_string_cache_get(TrueTypeFont *font, const std::string text, int max_width) {
StringCacheEntry *res = nullptr;
StringCacheMap::iterator stringCacheItr;
StringCacheKey k = {
.text = text,
.max_width = max_width
};
stringCacheItr = font->string_cache.find(k);
if (stringCacheItr == font->string_cache.end()) {
res = new StringCacheEntry;
res->rendered_bytes = gr_ttf_render_text(font, &res->surface, text, max_width);
if(res->rendered_bytes < 0) {
delete res;
return nullptr;
}
StringCacheKey *new_key = new StringCacheKey;
new_key->max_width = max_width;
new_key->text = text;
res->key = new_key;
font->string_cache[*new_key] = res;
}
else
{
res = stringCacheItr->second;
}
return res;
}
int twrpTruetype::gr_ttf_measureEx(const char *s, void *font) {
TrueTypeFont *f = (TrueTypeFont *)font;
int res = -1;
pthread_mutex_lock(&f->mutex);
gr_ttf_string_cache_truncate(f);
StringCacheEntry *e = gr_ttf_string_cache_get(f, s, -1);
if(e)
res = e->surface.width;
pthread_mutex_unlock(&f->mutex);
return res;
}
int twrpTruetype::gr_ttf_maxExW(const char *s, void *font, int max_width) {
TrueTypeFont *f = (TrueTypeFont *)font;
TrueTypeCacheEntry *ent;
int max_bytes = 0, total_w = 0;
int utf_bytes, prev_utf_bytes = 0;
unsigned int unicode = 0;
int char_idx, prev_idx = 0;
FT_Vector delta;
StringCacheEntry *e;
pthread_mutex_lock(&f->mutex);
e = gr_ttf_string_cache_peek(f, s, max_width);
if (e) {
max_bytes = e->rendered_bytes;
pthread_mutex_unlock(&f->mutex);
return max_bytes;
}
while(*s) {
utf_bytes = utf8_to_unicode(s, &unicode);
s += utf_bytes;
char_idx = FT_Get_Char_Index(f->face, unicode);
if(FT_HAS_KERNING(f->face) && prev_idx && char_idx)
{
FT_Get_Kerning(f->face, prev_idx, char_idx, FT_KERNING_DEFAULT, &delta);
total_w += delta.x >> 6;
}
prev_idx = char_idx;
if(total_w > max_width)
{
max_bytes -= prev_utf_bytes;
break;
}
prev_utf_bytes = utf_bytes;
ent = gr_ttf_glyph_cache_get(f, char_idx);
if(!ent)
continue;
total_w += ent->glyph->root.advance.x >> 16;
max_bytes += utf_bytes;
}
pthread_mutex_unlock(&f->mutex);
return max_bytes;
}
int twrpTruetype::gr_ttf_textExWH(void *context, int x, int y,
const char *s, void *pFont,
int max_width, int max_height,
const gr_surface gr_draw_surface) {
GGLContext *gl = (GGLContext *)context;
TrueTypeFont *font = (TrueTypeFont *)pFont;
const GRSurface *gr_draw = (const GRSurface*) gr_draw_surface;
// not actualy max width, but max_width + x
if(max_width != -1)
{
max_width -= x;
if(max_width <= 0)
return 0;
}
pthread_mutex_lock(&font->mutex);
StringCacheEntry *e = gr_ttf_string_cache_get(font, s, max_width);
if (!e) {
pthread_mutex_unlock(&font->mutex);
return -1;
}
GGLSurface string_surface_rotated;
if (gr_rotation != 0) {
// Do not perform relatively expensive operation if not needed
string_surface_rotated.version = sizeof(string_surface_rotated);
// Skip the **(gr_rotation == 0)** || (gr_rotation == 180) check
// because we are under a gr_rotation != 0 conditional compilation statement
string_surface_rotated.width = (gr_rotation == 180) ? e->surface.width : e->surface.height;
string_surface_rotated.height = (gr_rotation == 180) ? e->surface.height : e->surface.width;
string_surface_rotated.stride = string_surface_rotated.width;
string_surface_rotated.format = e->surface.format;
// e->surface.format is GGL_PIXEL_FORMAT_A_8 (grayscale)
string_surface_rotated.data = (GGLubyte*) malloc(string_surface_rotated.stride * string_surface_rotated.height * 1);
surface_ROTATION_transform((gr_surface) &string_surface_rotated, (const gr_surface) &e->surface, 1);
}
int y_bottom = y + e->surface.height;
int res = e->rendered_bytes;
if(max_height != -1 && max_height < y_bottom)
{
y_bottom = max_height;
if(y_bottom <= y)
{
pthread_mutex_unlock(&font->mutex);
return 0;
}
}
// Figuring out display coordinates works for gr_rotation == 0 too,
// and isn't as expensive as allocating and rotating another surface,
// so we do this anyway.
int x0_disp, y0_disp, x1_disp, y1_disp;
int l_disp, r_disp, t_disp, b_disp;
x0_disp = ROTATION_X_DISP(x, y, gr_draw->width);
y0_disp = ROTATION_Y_DISP(x, y, gr_draw->height);
x1_disp = ROTATION_X_DISP(x + e->surface.width, y_bottom, gr_draw->width);
y1_disp = ROTATION_Y_DISP(x + e->surface.width, y_bottom, gr_draw->height);
l_disp = std::min(x0_disp, x1_disp);
r_disp = std::max(x0_disp, x1_disp);
t_disp = std::min(y0_disp, y1_disp);
b_disp = std::max(y0_disp, y1_disp);
if (gr_rotation != 0) {
gl->bindTexture(gl, &string_surface_rotated);
} else {
gl->bindTexture(gl, &e->surface);
}
gl->texEnvi(gl, GGL_TEXTURE_ENV, GGL_TEXTURE_ENV_MODE, GGL_REPLACE);
gl->texGeni(gl, GGL_S, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
gl->texGeni(gl, GGL_T, GGL_TEXTURE_GEN_MODE, GGL_ONE_TO_ONE);
gl->enable(gl, GGL_TEXTURE_2D);
gl->texCoord2i(gl, -l_disp, -t_disp);
gl->recti(gl, l_disp, t_disp, r_disp, b_disp);
gl->disable(gl, GGL_TEXTURE_2D);
if (gr_rotation != 0)
free(string_surface_rotated.data);
pthread_mutex_unlock(&font->mutex);;
return res;
}
int twrpTruetype::gr_ttf_getMaxFontHeight(void *font) {
int res;
TrueTypeFont *f = (TrueTypeFont *)font;
pthread_mutex_lock(&f->mutex);
if(f->max_height == -1)
gr_ttf_calcMaxFontHeight(f);
res = f->max_height;
pthread_mutex_unlock(&f->mutex);
return res;
}