From 76ee903d84d5b477016fb6ed6bdee1a21e237903 Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Sun, 7 Sep 2014 15:01:56 +0200 Subject: [PATCH] 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 +- .../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 52d5f557..baae3edf 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 897c5820..aad392ca 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 87248a68..4d6f3178 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 407e18b3..29f16908 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 0d547a6e..95c48a51 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 0778692c..428880d7 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 bfb1a3a3..6f6c2bd3 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 ae25d33c..fe55dfdb 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 9703881e..8561b2dd 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 d8d8a7d5..3e8c9f13 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 4cc25dd8..294e5951 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 ca0d8838..cb0c12e1 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 5a705a0e..99532edc 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 a9be8c98..f6685294 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 57baf5ff..cccd5b3b 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 aad98229..940ad433 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 ea0cf77c..dce1d884 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 58d6c9df..37c3e26d 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 f44998f6..a7ff1928 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 b074931b..e0036bfc 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 0ee0e94f..9acb7a11 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 0000000000000000000000000000000000000000..b9fc49c95b0675c7bf972da903c7409b03c350b9 GIT binary patch literal 125332 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 OTq~7h)i|A0>VE)w&S2#L literal 0 HcmV?d00001 diff --git a/gui/fileselector.cpp b/gui/fileselector.cpp index 4f90ca3a..cf7a9a90 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 61b0cff1..84ee17b1 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 e09ec539..851b3734 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 317e1780..2d464e1b 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 8d430b1e..4fce0ca4 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; - if (ExtractResource(pZip, "fonts", file, ".dat", TMP_RESOURCE_NAME) == 0) + file = attr->value(); + +#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 874836e5..3cb52817 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 5b4d57f1..700d7d5d 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 c594f482..29d7ad94 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 ba81f272..bc4e054b 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 79b1e9aa..9926904e 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 f04f518b..cb9f8a38 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 00000000..8e0df42e --- /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 1526a9f3..07962c54 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