From 76ee903d84d5b477016fb6ed6bdee1a21e237903 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Sun, 7 Sep 2014 15:01:56 +0200 Subject: Add support for TrueType fonts * Keeps original font system in place * Uses the same API as original font system: - You can render only one line at a time - You can only use one font and color for one gr_text* call * Caches all rendered text, with a string cache limited to 400 entries, then it trucates to 250, which results in memory usage hovering around 5-10MB Change-Id: I36107b9dcd8d57bae4486fce8b8f64e49ef3d906 Signed-off-by: Vojtech Bocek --- gui/Android.mk | 11 + gui/console.cpp | 2 +- gui/devices/1024x600/res/ui.xml | 2 +- gui/devices/1024x768/res/ui.xml | 2 +- gui/devices/1080x1920/res/ui.xml | 6 +- gui/devices/1200x1920/res/ui.xml | 6 +- gui/devices/1280x800/res/ui.xml | 2 +- gui/devices/1440x2560/res/ui.xml | 6 +- gui/devices/1600x2560/res/ui.xml | 6 +- gui/devices/1920x1200/res/ui.xml | 2 +- gui/devices/240x240/res/ui.xml | 6 +- gui/devices/2560x1600/res/ui.xml | 2 +- gui/devices/280x280/res/ui.xml | 6 +- gui/devices/320x320/res/ui.xml | 6 +- gui/devices/320x480/res/ui.xml | 6 +- gui/devices/480x800/res/ui.xml | 6 +- gui/devices/480x854/res/ui.xml | 6 +- gui/devices/540x960/res/ui.xml | 6 +- gui/devices/720x1280/res/ui.xml | 6 +- gui/devices/800x1280/res/ui.xml | 6 +- gui/devices/800x480/res/ui.xml | 2 +- .../common/res/fonts/RobotoCondensed-Regular.ttf | Bin 0 -> 125332 bytes gui/fileselector.cpp | 2 +- gui/input.cpp | 2 +- gui/listbox.cpp | 2 +- gui/partitionlist.cpp | 2 +- gui/resources.cpp | 67 +- gui/resources.hpp | 9 + gui/slidervalue.cpp | 2 +- gui/text.cpp | 2 +- minuitwrp/Android.mk | 12 +- minuitwrp/graphics.c | 101 ++- minuitwrp/minui.h | 21 +- minuitwrp/truetype.c | 731 +++++++++++++++++++++ prebuilt/Android.mk | 4 + 35 files changed, 938 insertions(+), 122 deletions(-) create mode 100644 gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf create mode 100644 minuitwrp/truetype.c diff --git a/gui/Android.mk b/gui/Android.mk index 52d5f5573..baae3edf8 100644 --- a/gui/Android.mk +++ b/gui/Android.mk @@ -59,6 +59,9 @@ endif ifeq ($(TW_OEM_BUILD), true) LOCAL_CFLAGS += -DTW_OEM_BUILD endif +ifeq ($(TW_DISABLE_TTF), true) + LOCAL_CFLAGS += -DTW_DISABLE_TTF +endif ifeq ($(DEVICE_RESOLUTION),) $(warning ********************************************************************************) @@ -104,6 +107,13 @@ ifeq ($(TW_CUSTOM_THEME),) else TWRP_THEME_LOC := $(TW_CUSTOM_THEME) endif + +ifeq ($(TW_DISABLE_TTF), true) + TWRP_REMOVE_FONT := rm -f $(TARGET_RECOVERY_ROOT_OUT)/res/fonts/*.ttf +else + TWRP_REMOVE_FONT := rm -f $(TARGET_RECOVERY_ROOT_OUT)/res/fonts/*.dat +endif + TWRP_RES_GEN := $(intermediates)/twrp ifneq ($(TW_USE_TOOLBOX), true) TWRP_SH_TARGET := /sbin/busybox @@ -116,6 +126,7 @@ $(TWRP_RES_GEN): cp -fr $(TWRP_RES_LOC)/* $(TARGET_RECOVERY_ROOT_OUT)/res/ cp -fr $(TWRP_THEME_LOC)/* $(TARGET_RECOVERY_ROOT_OUT)/res/ $(TWRP_COMMON_XML) + $(TWRP_REMOVE_FONT) mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/sbin/ ln -sf $(TWRP_SH_TARGET) $(TARGET_RECOVERY_ROOT_OUT)/sbin/sh ln -sf /sbin/pigz $(TARGET_RECOVERY_ROOT_OUT)/sbin/gzip diff --git a/gui/console.cpp b/gui/console.cpp index 897c58203..aad392ca3 100644 --- a/gui/console.cpp +++ b/gui/console.cpp @@ -177,7 +177,7 @@ GUIConsole::GUIConsole(xml_node<>* node) : GUIObject(node) } } - gr_getFontDetails(mFont, &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); SetActionPos(mRenderX, mRenderY, mRenderW, mRenderH); SetRenderPos(mConsoleX, mConsoleY); return; diff --git a/gui/devices/1024x600/res/ui.xml b/gui/devices/1024x600/res/ui.xml index 87248a686..4d6f3178f 100644 --- a/gui/devices/1024x600/res/ui.xml +++ b/gui/devices/1024x600/res/ui.xml @@ -14,7 +14,7 @@ - + diff --git a/gui/devices/1024x768/res/ui.xml b/gui/devices/1024x768/res/ui.xml index 407e18b31..29f169089 100644 --- a/gui/devices/1024x768/res/ui.xml +++ b/gui/devices/1024x768/res/ui.xml @@ -14,7 +14,7 @@ - + diff --git a/gui/devices/1080x1920/res/ui.xml b/gui/devices/1080x1920/res/ui.xml index 0d547a6e9..95c48a513 100644 --- a/gui/devices/1080x1920/res/ui.xml +++ b/gui/devices/1080x1920/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/1200x1920/res/ui.xml b/gui/devices/1200x1920/res/ui.xml index 0778692c7..428880d76 100644 --- a/gui/devices/1200x1920/res/ui.xml +++ b/gui/devices/1200x1920/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/1280x800/res/ui.xml b/gui/devices/1280x800/res/ui.xml index bfb1a3a3a..6f6c2bd36 100644 --- a/gui/devices/1280x800/res/ui.xml +++ b/gui/devices/1280x800/res/ui.xml @@ -14,7 +14,7 @@ - + diff --git a/gui/devices/1440x2560/res/ui.xml b/gui/devices/1440x2560/res/ui.xml index ae25d33c8..fe55dfdbf 100644 --- a/gui/devices/1440x2560/res/ui.xml +++ b/gui/devices/1440x2560/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/1600x2560/res/ui.xml b/gui/devices/1600x2560/res/ui.xml index 9703881e3..8561b2ddf 100644 --- a/gui/devices/1600x2560/res/ui.xml +++ b/gui/devices/1600x2560/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/1920x1200/res/ui.xml b/gui/devices/1920x1200/res/ui.xml index d8d8a7d5b..3e8c9f13f 100644 --- a/gui/devices/1920x1200/res/ui.xml +++ b/gui/devices/1920x1200/res/ui.xml @@ -14,7 +14,7 @@ - + diff --git a/gui/devices/240x240/res/ui.xml b/gui/devices/240x240/res/ui.xml index 4cc25dd87..294e5951b 100644 --- a/gui/devices/240x240/res/ui.xml +++ b/gui/devices/240x240/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/2560x1600/res/ui.xml b/gui/devices/2560x1600/res/ui.xml index ca0d8838e..cb0c12e1f 100644 --- a/gui/devices/2560x1600/res/ui.xml +++ b/gui/devices/2560x1600/res/ui.xml @@ -14,7 +14,7 @@ - + diff --git a/gui/devices/280x280/res/ui.xml b/gui/devices/280x280/res/ui.xml index 5a705a0e9..99532edcb 100644 --- a/gui/devices/280x280/res/ui.xml +++ b/gui/devices/280x280/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/320x320/res/ui.xml b/gui/devices/320x320/res/ui.xml index a9be8c985..f66852942 100644 --- a/gui/devices/320x320/res/ui.xml +++ b/gui/devices/320x320/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/320x480/res/ui.xml b/gui/devices/320x480/res/ui.xml index 57baf5ff3..cccd5b3b4 100644 --- a/gui/devices/320x480/res/ui.xml +++ b/gui/devices/320x480/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/480x800/res/ui.xml b/gui/devices/480x800/res/ui.xml index aad982291..940ad4333 100644 --- a/gui/devices/480x800/res/ui.xml +++ b/gui/devices/480x800/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/480x854/res/ui.xml b/gui/devices/480x854/res/ui.xml index ea0cf77cd..dce1d8843 100644 --- a/gui/devices/480x854/res/ui.xml +++ b/gui/devices/480x854/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/540x960/res/ui.xml b/gui/devices/540x960/res/ui.xml index 58d6c9df6..37c3e26d5 100644 --- a/gui/devices/540x960/res/ui.xml +++ b/gui/devices/540x960/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/720x1280/res/ui.xml b/gui/devices/720x1280/res/ui.xml index f44998f66..a7ff19285 100644 --- a/gui/devices/720x1280/res/ui.xml +++ b/gui/devices/720x1280/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/800x1280/res/ui.xml b/gui/devices/800x1280/res/ui.xml index b074931b6..e0036bfc5 100644 --- a/gui/devices/800x1280/res/ui.xml +++ b/gui/devices/800x1280/res/ui.xml @@ -14,9 +14,9 @@ - - - + + + diff --git a/gui/devices/800x480/res/ui.xml b/gui/devices/800x480/res/ui.xml index 0ee0e94fc..9acb7a112 100644 --- a/gui/devices/800x480/res/ui.xml +++ b/gui/devices/800x480/res/ui.xml @@ -14,7 +14,7 @@ - + diff --git a/gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf b/gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf new file mode 100644 index 000000000..b9fc49c95 Binary files /dev/null and b/gui/devices/common/res/fonts/RobotoCondensed-Regular.ttf differ diff --git a/gui/fileselector.cpp b/gui/fileselector.cpp index 4f90ca3a8..cf7a9a908 100644 --- a/gui/fileselector.cpp +++ b/gui/fileselector.cpp @@ -333,7 +333,7 @@ GUIFileSelector::GUIFileSelector(xml_node<>* node) : GUIObject(node) } // Retrieve the line height - gr_getFontDetails(mFont ? mFont->GetResource() : NULL, &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); mLineHeight = mFontHeight; mHeaderH = mFontHeight; diff --git a/gui/input.cpp b/gui/input.cpp index 61b0cff1f..84ee17b16 100644 --- a/gui/input.cpp +++ b/gui/input.cpp @@ -139,7 +139,7 @@ GUIInput::GUIInput(xml_node<>* node) attr = child->first_attribute("resource"); if (attr) { mFont = PageManager::FindResource(attr->value()); - gr_getFontDetails(mFont ? mFont->GetResource() : NULL, &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); } } diff --git a/gui/listbox.cpp b/gui/listbox.cpp index e09ec5391..851b37348 100644 --- a/gui/listbox.cpp +++ b/gui/listbox.cpp @@ -271,7 +271,7 @@ GUIListBox::GUIListBox(xml_node<>* node) : GUIObject(node) } // Retrieve the line height - gr_getFontDetails(mFont ? mFont->GetResource() : NULL, &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); mLineHeight = mFontHeight; mHeaderH = mFontHeight; diff --git a/gui/partitionlist.cpp b/gui/partitionlist.cpp index 317e17805..2d464e1b6 100644 --- a/gui/partitionlist.cpp +++ b/gui/partitionlist.cpp @@ -273,7 +273,7 @@ GUIPartitionList::GUIPartitionList(xml_node<>* node) : GUIObject(node) } // Retrieve the line height - gr_getFontDetails(mFont ? mFont->GetResource() : NULL, &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); mLineHeight = mFontHeight; mHeaderH = mFontHeight; diff --git a/gui/resources.cpp b/gui/resources.cpp index 8d430b1ec..4fce0ca44 100644 --- a/gui/resources.cpp +++ b/gui/resources.cpp @@ -65,27 +65,82 @@ FontResource::FontResource(xml_node<>* node, ZipArchive* pZip) : Resource(node, pZip) { std::string file; + xml_attribute<>* attr; mFont = NULL; if (!node) return; - if (node->first_attribute("filename")) - file = node->first_attribute("filename")->value(); + attr = node->first_attribute("filename"); + if (!attr) + return; + + file = attr->value(); - if (ExtractResource(pZip, "fonts", file, ".dat", TMP_RESOURCE_NAME) == 0) +#ifndef TW_DISABLE_TTF + if(file.size() >= 4 && file.compare(file.size()-4, 4, ".ttf") == 0) { - mFont = gr_loadFont(TMP_RESOURCE_NAME); - unlink(TMP_RESOURCE_NAME); + m_type = TYPE_TTF; + + attr = node->first_attribute("size"); + if(!attr) + return; + + int size = atoi(attr->value()); + int dpi = 300; + + attr = node->first_attribute("dpi"); + if(attr) + dpi = atoi(attr->value()); + + if (ExtractResource(pZip, "fonts", file, "", TMP_RESOURCE_NAME) == 0) + { + mFont = gr_ttf_loadFont(TMP_RESOURCE_NAME, size, dpi); + unlink(TMP_RESOURCE_NAME); + } + else + { + file = std::string("/res/fonts/") + file; + mFont = gr_ttf_loadFont(file.c_str(), size, dpi); + } } else +#endif { - mFont = gr_loadFont(file.c_str()); + m_type = TYPE_TWRP; + + if(file.size() >= 4 && file.compare(file.size()-4, 4, ".ttf") == 0) + { + attr = node->first_attribute("fallback"); + if (!attr) + return; + + file = attr->value(); + } + + if (ExtractResource(pZip, "fonts", file, ".dat", TMP_RESOURCE_NAME) == 0) + { + mFont = gr_loadFont(TMP_RESOURCE_NAME); + unlink(TMP_RESOURCE_NAME); + } + else + { + mFont = gr_loadFont(file.c_str()); + } } } FontResource::~FontResource() { + if(mFont) + { +#ifndef TW_DISABLE_TTF + if(m_type == TYPE_TTF) + gr_ttf_freeFont(mFont); + else +#endif + gr_freeFont(mFont); + } } ImageResource::ImageResource(xml_node<>* node, ZipArchive* pZip) diff --git a/gui/resources.hpp b/gui/resources.hpp index 874836e52..3cb528176 100644 --- a/gui/resources.hpp +++ b/gui/resources.hpp @@ -38,6 +38,14 @@ typedef enum { class FontResource : public Resource { public: + enum Type + { + TYPE_TWRP, +#ifndef TW_DISABLE_TTF + TYPE_TTF, +#endif + }; + FontResource(xml_node<>* node, ZipArchive* pZip); virtual ~FontResource(); @@ -46,6 +54,7 @@ public: protected: void* mFont; + Type m_type; }; class ImageResource : public Resource diff --git a/gui/slidervalue.cpp b/gui/slidervalue.cpp index 5b4d57f1c..700d7d5d7 100644 --- a/gui/slidervalue.cpp +++ b/gui/slidervalue.cpp @@ -198,7 +198,7 @@ GUISliderValue::GUISliderValue(xml_node<>* node) : GUIObject(node) } } - gr_getFontDetails(mFont ? mFont->GetResource() : NULL, (unsigned*) &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); if(mShowCurr) { diff --git a/gui/text.cpp b/gui/text.cpp index c594f4824..29d7ad948 100644 --- a/gui/text.cpp +++ b/gui/text.cpp @@ -97,7 +97,7 @@ GUIText::GUIText(xml_node<>* node) mLastValue = parseText(); if (mLastValue != mText) mIsStatic = 0; - gr_getFontDetails(mFont ? mFont->GetResource() : NULL, (unsigned*) &mFontHeight, NULL); + mFontHeight = gr_getMaxFontHeight(mFont ? mFont->GetResource() : NULL); return; } diff --git a/minuitwrp/Android.mk b/minuitwrp/Android.mk index ba81f2723..bc4e054b3 100644 --- a/minuitwrp/Android.mk +++ b/minuitwrp/Android.mk @@ -107,8 +107,16 @@ ifneq ($(TW_WHITELIST_INPUT),) LOCAL_CFLAGS += -DWHITELIST_INPUT=$(TW_WHITELIST_INPUT) endif -LOCAL_SHARED_LIBRARIES += libz libc libcutils libjpeg -LOCAL_STATIC_LIBRARIES += libpng libpixelflinger_static +ifeq ($(TW_DISABLE_TTF), true) + LOCAL_CFLAGS += -DTW_DISABLE_TTF +else + LOCAL_SHARED_LIBRARIES += libft2 + LOCAL_C_INCLUDES += external/freetype/include + LOCAL_SRC_FILES += truetype.c +endif + +LOCAL_SHARED_LIBRARIES += libz libc libcutils libjpeg libpng +LOCAL_STATIC_LIBRARIES += libpixelflinger_static LOCAL_MODULE_TAGS := eng LOCAL_MODULE := libminuitwrp diff --git a/minuitwrp/graphics.c b/minuitwrp/graphics.c index 79b1e9aa5..9926904ef 100644 --- a/minuitwrp/graphics.c +++ b/minuitwrp/graphics.c @@ -58,6 +58,7 @@ // #define PRINT_SCREENINFO 1 // Enables printing of screen info to log typedef struct { + int type; GGLSurface texture; unsigned offset[97]; unsigned cheight; @@ -392,6 +393,11 @@ int gr_measureEx(const char *s, void* font) if (!fnt) fnt = gr_font; +#ifndef TW_DISABLE_TTF + if(fnt->type == FONT_TYPE_TTF) + return gr_ttf_measureEx(s, font); +#endif + while ((off = *s++)) { off -= 32; @@ -410,6 +416,11 @@ int gr_maxExW(const char *s, void* font, int max_width) if (!fnt) fnt = gr_font; +#ifndef TW_DISABLE_TTF + if(fnt->type == FONT_TYPE_TTF) + return gr_ttf_maxExW(s, font, max_width); +#endif + while ((off = *s++)) { off -= 32; @@ -425,21 +436,6 @@ int gr_maxExW(const char *s, void* font, int max_width) return total; } -unsigned character_width(const char *s, void* pFont) -{ - GRFont *font = (GRFont*) pFont; - unsigned off; - - /* Handle default font */ - if (!font) font = gr_font; - - off = *s - 32; - if (off == 0) - return 0; - - return font->offset[off+1] - font->offset[off]; -} - int gr_textEx(int x, int y, const char *s, void* pFont) { GGLContext *gl = gr_context; @@ -450,6 +446,11 @@ int gr_textEx(int x, int y, const char *s, void* pFont) /* Handle default font */ if (!font) font = gr_font; +#ifndef TW_DISABLE_TTF + if(font->type == FONT_TYPE_TTF) + return gr_ttf_textExWH(gl, x, y, s, pFont, -1, -1); +#endif + gl->bindTexture(gl, &font->texture); 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); @@ -480,6 +481,11 @@ int gr_textExW(int x, int y, const char *s, void* pFont, int max_width) /* Handle default font */ if (!font) font = gr_font; +#ifndef TW_DISABLE_TTF + if(font->type == FONT_TYPE_TTF) + return gr_ttf_textExWH(gl, x, y, s, pFont, max_width, -1); +#endif + gl->bindTexture(gl, &font->texture); 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); @@ -518,6 +524,11 @@ int gr_textExWH(int x, int y, const char *s, void* pFont, int max_width, int max /* Handle default font */ if (!font) font = gr_font; +#ifndef TW_DISABLE_TTF + if(font->type == FONT_TYPE_TTF) + return gr_ttf_textExWH(gl, x, y, s, pFont, max_width, max_height); +#endif + gl->bindTexture(gl, &font->texture); 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); @@ -549,34 +560,6 @@ int gr_textExWH(int x, int y, const char *s, void* pFont, int max_width, int max return x; } -int twgr_text(int x, int y, const char *s) -{ - GGLContext *gl = gr_context; - GRFont *font = gr_font; - unsigned off; - unsigned cwidth = 0; - - y -= font->ascent; - - gl->bindTexture(gl, &font->texture); - 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); - - while((off = *s++)) { - off -= 32; - if (off < 96) { - cwidth = font->offset[off+1] - font->offset[off]; - gl->texCoord2i(gl, (off * cwidth) - x, 0 - y); - gl->recti(gl, x, y, x + cwidth, y + font->cheight); - } - x += cwidth; - } - - return x; -} - void gr_fill(int x, int y, int w, int h) { GGLContext *gl = gr_context; @@ -682,33 +665,32 @@ void* gr_loadFont(const char* fontName) ftex->stride = width; ftex->data = (void*) bits; ftex->format = GGL_PIXEL_FORMAT_A_8; + font->type = FONT_TYPE_TWRP; font->cheight = height; font->ascent = height - 2; return (void*) font; } -int gr_getFontDetails(void* font, unsigned* cheight, unsigned* maxwidth) +void gr_freeFont(void *font) +{ + GRFont *f = font; + free(f->texture.data); + free(f); +} + +int gr_getMaxFontHeight(void *font) { GRFont *fnt = (GRFont*) font; if (!fnt) fnt = gr_font; if (!fnt) return -1; - if (cheight) *cheight = fnt->cheight; - if (maxwidth) - { - int pos; - *maxwidth = 0; - for (pos = 0; pos < 96; pos++) - { - unsigned int width = fnt->offset[pos+1] - fnt->offset[pos]; - if (width > *maxwidth) - { - *maxwidth = width; - } - } - } - return 0; +#ifndef TW_DISABLE_TTF + if(fnt->type == FONT_TYPE_TTF) + return gr_ttf_getMaxFontHeight(font); +#endif + + return fnt->cheight; } static void gr_init_font(void) @@ -746,6 +728,7 @@ static void gr_init_font(void) ftex->stride = width; ftex->data = (void*) bits; ftex->format = GGL_PIXEL_FORMAT_A_8; + gr_font->type = FONT_TYPE_TWRP; gr_font->cheight = height; gr_font->ascent = height - 2; return; diff --git a/minuitwrp/minui.h b/minuitwrp/minui.h index f04f518b5..cb9f8a385 100644 --- a/minuitwrp/minui.h +++ b/minuitwrp/minui.h @@ -20,6 +20,12 @@ typedef void* gr_surface; typedef unsigned short gr_pixel; +#define FONT_TYPE_TWRP 0 + +#ifndef TW_DISABLE_TTF +#define FONT_TYPE_TTF 1 +#endif + int gr_init(void); void gr_exit(void); @@ -35,16 +41,25 @@ void gr_fill(int x, int y, int w, int h); int gr_textEx(int x, int y, const char *s, void* font); int gr_textExW(int x, int y, const char *s, void* font, int max_width); int gr_textExWH(int x, int y, const char *s, void* pFont, int max_width, int max_height); -int twgr_text(int x, int y, const char *s); static inline int gr_text(int x, int y, const char *s) { return gr_textEx(x, y, s, NULL); } int gr_measureEx(const char *s, void* font); static inline int gr_measure(const char *s) { return gr_measureEx(s, NULL); } int gr_maxExW(const char *s, void* font, int max_width); -int gr_getFontDetails(void* font, unsigned* cheight, unsigned* maxwidth); -static inline void gr_font_size(int *x, int *y) { gr_getFontDetails(NULL, (unsigned*) y, (unsigned*) x); } +int gr_getMaxFontHeight(void *font); void* gr_loadFont(const char* fontName); +void gr_freeFont(void *font); + +#ifndef TW_DISABLE_TTF +void *gr_ttf_loadFont(const char *filename, int size, int dpi); +void gr_ttf_freeFont(void *font); +int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height); +int gr_ttf_measureEx(const char *s, void *font); +int gr_ttf_maxExW(const char *s, void *font, int max_width); +int gr_ttf_getMaxFontHeight(void *font); +void gr_ttf_dump_stats(void); +#endif void gr_blit(gr_surface source, int sx, int sy, int w, int h, int dx, int dy); unsigned int gr_get_width(gr_surface surface); diff --git a/minuitwrp/truetype.c b/minuitwrp/truetype.c new file mode 100644 index 000000000..8e0df42ea --- /dev/null +++ b/minuitwrp/truetype.c @@ -0,0 +1,731 @@ +#include +#include +#include + +#include +#include + +#include "minui.h" + +#include +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + +#include +#include + +#define STRING_CACHE_MAX_ENTRIES 400 +#define STRING_CACHE_TRUNCATE_ENTRIES 150 + +typedef struct +{ + int size; + int dpi; + char *path; +} TrueTypeFontKey; + +typedef struct +{ + int type; + int refcount; + int size; + int dpi; + int max_height; + int base; + FT_Face face; + Hashmap *glyph_cache; + Hashmap *string_cache; + struct StringCacheEntry *string_cache_head; + struct StringCacheEntry *string_cache_tail; + pthread_mutex_t mutex; + TrueTypeFontKey *key; +} TrueTypeFont; + +typedef struct +{ + FT_BBox bbox; + FT_BitmapGlyph glyph; +} TrueTypeCacheEntry; + +typedef struct +{ + char *text; + int max_width; +} StringCacheKey; + +struct StringCacheEntry +{ + GGLSurface surface; + int rendered_len; + StringCacheKey *key; + struct StringCacheEntry *prev; + struct StringCacheEntry *next; +}; + +typedef struct StringCacheEntry StringCacheEntry; + +typedef struct +{ + FT_Library ft_library; + Hashmap *fonts; + pthread_mutex_t mutex; +} FontData; + +static FontData font_data = { + .ft_library = NULL, + .fonts = NULL, + .mutex = PTHREAD_MUTEX_INITIALIZER, +}; + +#define MIN(X,Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X,Y) ((X) > (Y) ? (X) : (Y)) + +// 32bit FNV-1a hash algorithm +// http://isthe.com/chongo/tech/comp/fnv/#FNV-1a +static const uint32_t FNV_prime = 16777619U; +static const uint32_t offset_basis = 2166136261U; + +static uint32_t fnv_hash(void *data, uint32_t len) +{ + uint8_t *d8 = data; + uint32_t *d32 = data; + uint32_t i, max; + uint32_t hash = offset_basis; + + max = len/4; + + // 32 bit data + for(i = 0; i < max; ++i) + { + hash ^= *d32++; + hash *= FNV_prime; + } + + // last bits + for(i *= 4; i < len; ++i) + { + hash ^= (uint32_t) d8[i]; + hash *= FNV_prime; + } + return hash; +} + +static inline uint32_t fnv_hash_add(uint32_t cur_hash, uint32_t word) +{ + cur_hash ^= word; + cur_hash *= FNV_prime; + return cur_hash; +} + +static bool gr_ttf_string_cache_equals(void *keyA, void *keyB) +{ + StringCacheKey *a = keyA; + StringCacheKey *b = keyB; + return a->max_width == b->max_width && strcmp(a->text, b->text) == 0; +} + +static int gr_ttf_string_cache_hash(void *key) +{ + StringCacheKey *k = key; + return fnv_hash(k->text, strlen(k->text)); +} + +static bool gr_ttf_font_cache_equals(void *keyA, void *keyB) +{ + TrueTypeFontKey *a = keyA; + TrueTypeFontKey *b = keyB; + return (a->size == b->size) && (a->dpi == b->dpi) && !strcmp(a->path, b->path); +} + +static int gr_ttf_font_cache_hash(void *key) +{ + TrueTypeFontKey *k = key; + + uint32_t hash = fnv_hash(k->path, strlen(k->path)); + hash = fnv_hash_add(hash, k->size); + hash = fnv_hash_add(hash, k->dpi); + return hash; +} + +void *gr_ttf_loadFont(const char *filename, int size, int dpi) +{ + int error; + TrueTypeFont *res = NULL; + + pthread_mutex_lock(&font_data.mutex); + + if(font_data.fonts) + { + TrueTypeFontKey k = { + .size = size, + .dpi = dpi, + .path = (char*)filename + }; + + res = hashmapGet(font_data.fonts, &k); + if(res) + { + ++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 = malloc(sizeof(TrueTypeFont)); + memset(res, 0, sizeof(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; + res->glyph_cache = hashmapCreate(32, hashmapIntHash, hashmapIntEquals); + res->string_cache = hashmapCreate(128, gr_ttf_string_cache_hash, gr_ttf_string_cache_equals); + pthread_mutex_init(&res->mutex, 0); + + if(!font_data.fonts) + font_data.fonts = hashmapCreate(4, gr_ttf_font_cache_hash, gr_ttf_font_cache_equals); + + TrueTypeFontKey *key = malloc(sizeof(TrueTypeFontKey)); + memset(key, 0, sizeof(TrueTypeFontKey)); + key->path = strdup(filename); + key->size = size; + key->dpi = dpi; + + res->key = key; + + hashmapPut(font_data.fonts, key, res); + +exit: + pthread_mutex_unlock(&font_data.mutex); + return res; +} + +static bool gr_ttf_freeFontCache(void *key, void *value, void *context) +{ + TrueTypeCacheEntry *e = value; + FT_Done_Glyph((FT_Glyph)e->glyph); + free(e); + free(key); + return true; +} + +static bool gr_ttf_freeStringCache(void *key, void *value, void *context) +{ + StringCacheKey *k = key; + free(k->text); + free(k); + + StringCacheEntry *e = value; + free(e->surface.data); + free(e); + return true; +} + +void gr_ttf_freeFont(void *font) +{ + pthread_mutex_lock(&font_data.mutex); + + TrueTypeFont *d = font; + + if(--d->refcount == 0) + { + hashmapRemove(font_data.fonts, d->key); + + if(hashmapSize(font_data.fonts) == 0) + { + hashmapFree(font_data.fonts); + font_data.fonts = NULL; + } + + free(d->key->path); + free(d->key); + + FT_Done_Face(d->face); + hashmapForEach(d->string_cache, gr_ttf_freeStringCache, NULL); + hashmapFree(d->string_cache); + hashmapForEach(d->glyph_cache, gr_ttf_freeFontCache, NULL); + hashmapFree(d->glyph_cache); + pthread_mutex_destroy(&d->mutex); + free(d); + } + + pthread_mutex_unlock(&font_data.mutex); +} + +static TrueTypeCacheEntry *gr_ttf_glyph_cache_peek(TrueTypeFont *font, int char_index) +{ + return hashmapGet(font->glyph_cache, &char_index); +} + +static TrueTypeCacheEntry *gr_ttf_glyph_cache_get(TrueTypeFont *font, int char_index) +{ + TrueTypeCacheEntry *res = hashmapGet(font->glyph_cache, &char_index); + if(!res) + { + 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 NULL; + } + + 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 NULL; + } + + res = malloc(sizeof(TrueTypeCacheEntry)); + memset(res, 0, sizeof(TrueTypeCacheEntry)); + res->glyph = glyph; + FT_Glyph_Get_CBox((FT_Glyph)glyph, FT_GLYPH_BBOX_PIXELS, &res->bbox); + + int *key = malloc(sizeof(int)); + *key = char_index; + + hashmapPut(font->glyph_cache, key, res); + } + + return res; +} + +static int gr_ttf_copy_glyph_to_surface(GGLSurface *dest, FT_BitmapGlyph glyph, int offX, int offY, int base) +{ + int 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); + + 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; +} + +static int gr_ttf_render_text(TrueTypeFont *font, GGLSurface *surface, const char *text, int max_width) +{ + TrueTypeFont *f = font; + TrueTypeCacheEntry *ent; + int max_len = 0, total_w = 0; + char c; + int i, x, diff, char_idx, prev_idx = 0; + int height, base; + FT_Vector delta; + uint8_t *data = NULL; + const char *text_itr = text; + + while((c = *text_itr++)) + { + char_idx = FT_Get_Char_Index(f->face, c); + 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; + ++max_len; + } + + if(font->max_height == -1) + gr_ttf_getMaxFontHeight(font); + + if(font->max_height == -1) + return -1; + + height = font->max_height; + + data = 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 = (void*)data; + surface->format = GGL_PIXEL_FORMAT_A_8; + + for(i = 0; i < max_len; ++i) + { + char_idx = FT_Get_Char_Index(f->face, text[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; + } + + return max_len; +} + +static StringCacheEntry *gr_ttf_string_cache_peek(TrueTypeFont *font, const char *text, int max_width) +{ + StringCacheEntry *res; + StringCacheKey k = { + .text = (char*)text, + .max_width = max_width + }; + + return hashmapGet(font->string_cache, &k); +} + +static StringCacheEntry *gr_ttf_string_cache_get(TrueTypeFont *font, const char *text, int max_width) +{ + StringCacheEntry *res; + StringCacheKey k = { + .text = (char*)text, + .max_width = max_width + }; + + res = hashmapGet(font->string_cache, &k); + if(!res) + { + res = malloc(sizeof(StringCacheEntry)); + memset(res, 0, sizeof(StringCacheEntry)); + res->rendered_len = gr_ttf_render_text(font, &res->surface, text, max_width); + if(res->rendered_len < 0) + { + free(res); + return NULL; + } + + StringCacheKey *new_key = malloc(sizeof(StringCacheKey)); + memset(new_key, 0, sizeof(StringCacheKey)); + new_key->max_width = max_width; + new_key->text = strdup(text); + + res->key = new_key; + + if(font->string_cache_tail) + { + res->prev = font->string_cache_tail; + res->prev->next = res; + } + else + font->string_cache_head = res; + font->string_cache_tail = res; + + hashmapPut(font->string_cache, new_key, res); + } + else if(res->next) + { + // move this entry to the tail of the linked list + // if it isn't already there + if(res->prev) + res->prev->next = res->next; + + res->next->prev = res->prev; + + if(!res->prev) + font->string_cache_head = res->next; + + res->next = NULL; + res->prev = font->string_cache_tail; + res->prev->next = res; + font->string_cache_tail = res; + + // truncate old entries + if(hashmapSize(font->string_cache) >= STRING_CACHE_MAX_ENTRIES) + { + printf("Truncating string cache entries.\n"); + int i; + StringCacheEntry *ent; + for(i = 0; i < STRING_CACHE_TRUNCATE_ENTRIES; ++i) + { + ent = font->string_cache_head; + font->string_cache_head = ent->next; + font->string_cache_head->prev = NULL; + + hashmapRemove(font->string_cache, ent->key); + + gr_ttf_freeStringCache(ent->key, ent, NULL); + } + } + } + return res; +} + +int gr_ttf_measureEx(const char *s, void *font) +{ + TrueTypeFont *f = font; + int res = -1; + + pthread_mutex_lock(&f->mutex); + StringCacheEntry *e = gr_ttf_string_cache_get(font, s, -1); + if(e) + res = e->surface.width; + pthread_mutex_unlock(&f->mutex); + + return res; +} + +int gr_ttf_maxExW(const char *s, void *font, int max_width) +{ + TrueTypeFont *f = font; + TrueTypeCacheEntry *ent; + int max_len = 0, total_w = 0; + char c; + int char_idx, prev_idx = 0; + FT_Vector delta; + StringCacheEntry *e; + + pthread_mutex_lock(&f->mutex); + + e = gr_ttf_string_cache_peek(font, s, max_width); + if(e) + { + max_len = e->rendered_len; + pthread_mutex_unlock(&f->mutex); + return max_len; + } + + for(; (c = *s++); ++max_len) + { + char_idx = FT_Get_Char_Index(f->face, c); + 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) + break; + + ent = gr_ttf_glyph_cache_get(f, char_idx); + if(!ent) + continue; + + total_w += ent->glyph->root.advance.x >> 16; + } + pthread_mutex_unlock(&f->mutex); + return max_len > 0 ? max_len - 1 : 0; +} + +int gr_ttf_textExWH(void *context, int x, int y, const char *s, void *pFont, int max_width, int max_height) +{ + GGLContext *gl = context; + TrueTypeFont *font = pFont; + + // 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; + } + + int y_bottom = y + e->surface.height; + int res = e->rendered_len; + + if(max_height != -1 && max_height < y_bottom) + { + y_bottom = max_height; + if(y_bottom <= y) + { + pthread_mutex_unlock(&font->mutex); + return 0; + } + } + + 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, -x, -y); + gl->recti(gl, x, y, x + e->surface.width, y_bottom); + + pthread_mutex_unlock(&font->mutex); + return res; +} + +int gr_ttf_getMaxFontHeight(void *font) +{ + int res; + TrueTypeFont *f = font; + + pthread_mutex_lock(&f->mutex); + + if(f->max_height == -1) + { + 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; + } + + res = f->max_height; + + pthread_mutex_unlock(&f->mutex); + return res; +} + +static bool gr_ttf_dump_stats_count_string_cache(void *key, void *value, void *context) +{ + int *string_cache_size = context; + StringCacheEntry *e = value; + *string_cache_size += e->surface.height*e->surface.width + sizeof(StringCacheEntry); + return true; +} + +static bool gr_ttf_dump_stats_font(void *key, void *value, void *context) +{ + TrueTypeFontKey *k = key; + TrueTypeFont *f = value; + int *total_string_cache_size = context; + int string_cache_size = 0; + + pthread_mutex_lock(&f->mutex); + + hashmapForEach(f->string_cache, gr_ttf_dump_stats_count_string_cache, &string_cache_size); + + printf(" Font %s (size %d, dpi %d):\n" + " refcount: %d\n" + " max_height: %d\n" + " base: %d\n" + " glyph_cache: %d entries\n" + " string_cache: %d entries (%.2f kB)\n", + k->path, k->size, k->dpi, + f->refcount, f->max_height, f->base, + hashmapSize(f->glyph_cache), + hashmapSize(f->string_cache), ((double)string_cache_size)/1024); + + pthread_mutex_unlock(&f->mutex); + + *total_string_cache_size += string_cache_size; + return true; +} + +void gr_ttf_dump_stats(void) +{ + pthread_mutex_lock(&font_data.mutex); + + printf("TrueType fonts system stats: "); + if(!font_data.fonts) + printf("no truetype fonts loaded.\n"); + else + { + int total_string_cache_size = 0; + printf("%d fonts loaded.\n", hashmapSize(font_data.fonts)); + hashmapForEach(font_data.fonts, gr_ttf_dump_stats_font, &total_string_cache_size); + printf(" Total string cache size: %.2f kB\n", ((double)total_string_cache_size)/1024); + } + + pthread_mutex_unlock(&font_data.mutex); +} diff --git a/prebuilt/Android.mk b/prebuilt/Android.mk index 1526a9f31..07962c546 100644 --- a/prebuilt/Android.mk +++ b/prebuilt/Android.mk @@ -40,6 +40,7 @@ RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_e2p.so RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2fs.so RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_profile.so RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libext2_uuid.so +RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libpng.so RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/liblog.so RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libm.so RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libstdc++.so @@ -122,6 +123,9 @@ endif ifneq ($(wildcard system/core/reboot/Android.mk),) RELINK_SOURCE_FILES += $(TARGET_OUT_EXECUTABLES)/reboot endif +ifneq ($(TW_DISABLE_TTF), true) + RELINK_SOURCE_FILES += $(TARGET_OUT_SHARED_LIBRARIES)/libft2.so +endif TWRP_AUTOGEN := $(intermediates)/teamwin -- cgit v1.2.3