text: /*
  		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;
  }
  