diff --git a/data/fonts_whitelist.txt b/data/fonts_whitelist.txt index c035644479..e67d30a408 100644 --- a/data/fonts_whitelist.txt +++ b/data/fonts_whitelist.txt @@ -3,7 +3,6 @@ Basic_Latin 01_dejavusans.ttf Basic_Latin /system/fonts/DroidSans.ttf Basic_Latin /system/fonts/Roboto-Regular.ttf Latin-1_Supplement /usr/share/fonts/truetype/roboto/Roboto-Regular.ttf -Latin-1_Supplement 00_roboto_regular.ttf Latin-1_Supplement 01_dejavusans.ttf Latin-1_Supplement /system/fonts/DroidSans.ttf Latin-1_Supplement /system/fonts/Roboto-Regular.ttf diff --git a/drape/drape.pro b/drape/drape.pro index 5b4e73ba0e..90b932aab0 100644 --- a/drape/drape.pro +++ b/drape/drape.pro @@ -17,8 +17,6 @@ include($$ROOT_DIR/common.pri) DRAPE_DIR = . include($$DRAPE_DIR/drape_common.pri) -INCLUDEPATH *= $$ROOT_DIR/3party/expat/lib - SOURCES += glfunctions.cpp OTHER_FILES += \ diff --git a/drape/drape_common.pri b/drape/drape_common.pri index c1cd86623d..ac047a2402 100644 --- a/drape/drape_common.pri +++ b/drape/drape_common.pri @@ -1,6 +1,9 @@ CMDRES = $$system(python ../tools/autobuild/shader_preprocessor.py $$SHADER_COMPILE_ARGS) message($$CMDRES) +INCLUDEPATH *= $$ROOT_DIR/3party/freetype/include +INCLUDEPATH *= $$ROOT_DIR/3party/expat/lib + SOURCES += \ $$DRAPE_DIR/data_buffer.cpp \ $$DRAPE_DIR/binding_info.cpp \ @@ -37,6 +40,7 @@ SOURCES += \ $$DRAPE_DIR/attribute_buffer_mutator.cpp \ $$DRAPE_DIR/stipple_pen_resource.cpp \ $$DRAPE_DIR/texture_of_colors.cpp \ + $$DRAPE_DIR/glyph_manager.cpp \ HEADERS += \ $$DRAPE_DIR/data_buffer.hpp \ @@ -83,3 +87,4 @@ HEADERS += \ $$DRAPE_DIR/texture_of_colors.hpp \ $$DRAPE_DIR/glsl_types.hpp \ $$DRAPE_DIR/glsl_func.hpp \ + $$DRAPE_DIR/glyph_manager.hpp \ diff --git a/drape/drape_tests/drape_tests.pro b/drape/drape_tests/drape_tests.pro index decb8d076e..bc5508eaf0 100644 --- a/drape/drape_tests/drape_tests.pro +++ b/drape/drape_tests/drape_tests.pro @@ -10,12 +10,12 @@ CONFIG -= app_bundle TEMPLATE = app DEFINES += OGL_TEST_ENABLED GTEST_DONT_DEFINE_TEST COMPILER_TESTS -DEPENDENCIES = platform coding base gmock expat tomcrypt +DEPENDENCIES = platform coding base gmock freetype expat tomcrypt ROOT_DIR = ../.. SHADER_COMPILE_ARGS = $$PWD/../shaders shader_index.txt shader_def include($$ROOT_DIR/common.pri) -QT *= core +QT *= core gui widgets DRAPE_DIR = .. include($$DRAPE_DIR/drape_common.pri) @@ -40,6 +40,7 @@ SOURCES += \ bingind_info_tests.cpp \ stipple_pen_tests.cpp \ texture_of_colors_tests.cpp \ + glyph_mng_tests.cpp \ HEADERS += \ glmock_functions.hpp \ diff --git a/drape/drape_tests/glyph_mng_tests.cpp b/drape/drape_tests/glyph_mng_tests.cpp new file mode 100644 index 0000000000..b9d47cde85 --- /dev/null +++ b/drape/drape_tests/glyph_mng_tests.cpp @@ -0,0 +1,130 @@ +#include "../../testing/testing.hpp" + + +#include +#include +#include +#include + +#include "../glyph_manager.hpp" +#include "../../platform/platform.hpp" +#include "../../base/scope_guard.hpp" + +#include "../../std/cstring.hpp" +#include "../../std/function.hpp" +#include "../../std/bind.hpp" + +namespace +{ + class TestMainLoop : public QObject + { + public: + typedef function TRednerFn; + TestMainLoop(TRednerFn const & fn) : m_renderFn(fn) {} + + void exec(char const * testName) + { + char * buf = (char *)malloc(strlen(testName) + 1); + MY_SCOPE_GUARD(argvFreeFun, [&buf](){ free(buf); }) + strcpy(buf, testName); + + int argc = 1; + QApplication app(argc, &buf); + QTimer::singleShot(3000, &app, SLOT(quit())); + + QWidget w; + w.setWindowTitle(testName); + w.show(); + w.installEventFilter(this); + + app.exec(); + } + + protected: + bool eventFilter(QObject * obj, QEvent * event) + { + if (event->type() == QEvent::Paint) + { + m_renderFn(qobject_cast(obj)); + return true; + } + + return false; + } + + private: + TRednerFn m_renderFn; + }; + + class GlyphRenderer + { + public: + GlyphRenderer() + { + dp::GlyphManager::Params args; + args.m_uniBlocks = "unicode_blocks.txt"; + args.m_whitelist = "fonts_whitelist.txt"; + args.m_blacklist = "fonts_blacklist.txt"; + GetPlatform().GetFontNames(args.m_fonts); + + m_mng = new dp::GlyphManager(args); + } + + ~GlyphRenderer() + { + delete m_mng; + } + + void RenderGlyphs(QPaintDevice * device) + { + vector glyphs; + m_mng->GetGlyphs({0x58, 0x79, 0x439, 0x00}, glyphs); + + QPainter painter(device); + painter.fillRect(QRectF(0.0, 0.0, device->width(), device->height()), Qt::white); + + QPoint pen(100, 100); + for (dp::GlyphManager::Glyph & g : glyphs) + { + if (!g.m_image.m_data) + continue; + + uint8_t * d = SharedBufferManager::GetRawPointer(g.m_image.m_data); + int pitch = 32 * (((g.m_image.m_pitch - 1) / 32) + 1); + int byteCount = pitch * g.m_image.m_height; + unsigned char * buf = (unsigned char *)malloc(byteCount); + memset(buf, 0, byteCount); + for (int i = 0; i < g.m_image.m_height; ++i) + memcpy(buf + pitch * i, d + g.m_image.m_pitch * i, g.m_image.m_pitch); + + QImage img = QImage(buf, + pitch, + g.m_image.m_height, + QImage::Format_Indexed8); + + img.setColorCount(0xFF); + for (int i = 0; i < 256; ++i) + img.setColor(i, qRgb(255 - i, 255 - i, 255 - i)); + QPoint currentPen = pen; + currentPen.rx() += g.m_metrics.m_xOffset; + currentPen.ry() -= g.m_metrics.m_yOffset; + painter.drawImage(currentPen, img, QRect(0, 0, g.m_image.m_width, g.m_image.m_height)); + pen.rx() += g.m_metrics.m_xAdvance; + pen.ry() += g.m_metrics.m_yAdvance; + + free(buf); + g.m_image.Destroy(); + } + } + + private: + dp::GlyphManager * m_mng; + }; +} + +UNIT_TEST(GlyphLoadingTest) +{ + GlyphRenderer renderer; + TestMainLoop loop(bind(&GlyphRenderer::RenderGlyphs, &renderer, _1)); + loop.exec("GlyphLoadingTest"); +} diff --git a/drape/glyph_manager.cpp b/drape/glyph_manager.cpp new file mode 100644 index 0000000000..205a60a4ff --- /dev/null +++ b/drape/glyph_manager.cpp @@ -0,0 +1,477 @@ +#include "glyph_manager.hpp" + +#include "../platform/platform.hpp" + +#include "../coding/reader.hpp" + +#include "../base/string_utils.hpp" +#include "../base/logging.hpp" +#include "../base/math.hpp" + +#include +#include FT_TYPES_H +#include FT_SYSTEM_H +#include FT_FREETYPE_H +#include FT_STROKER_H +#include FT_CACHE_H + +#ifdef DEBUG + #undef __FTERRORS_H__ + #define FT_ERRORDEF(e, v, s) {e, s}, + #define FT_ERROR_START_LIST { + #define FT_ERROR_END_LIST {0, 0}}; + struct FreetypeError + { + int m_code; + char const * m_message; + } g_FT_Errors[] + #include FT_ERRORS_H + + #define FREETYPE_CHECK(x) \ + do \ + { \ + FT_Error const err = (x); \ + if (err) \ + LOG(LWARNING, ("Freetype:", g_FT_Errors[err].m_code, g_FT_Errors[err].m_message)); \ + } while (false) + + #define FREETYPE_CHECK_RETURN(x, msg) \ + do \ + { \ + FT_Error const err = (x); \ + if (err) \ + { \ + LOG(LWARNING, ("Freetype", g_FT_Errors[err].m_code, g_FT_Errors[err].m_message, msg)); \ + return; \ + } \ + } while (false) +#else + #define FREETYPE_CHECK(x) x + #define FREETYPE_CHECK_RETURN(x, msg) FREETYPE_CHECK(x) +#endif + +namespace dp +{ + +namespace +{ + +template +void ParseUniBlocks(string const & uniBlocksFile, ToDo toDo) +{ + string uniBlocks; + try + { + ReaderPtr(GetPlatform().GetReader(uniBlocksFile)).ReadAsString(uniBlocks); + } + catch (RootException const & e) + { + LOG(LCRITICAL, ("Error reading uniblock description: ", e.what())); + return; + } + + istringstream fin(uniBlocks); + while (true) + { + string name; + strings::UniChar start; + strings::UniChar end; + fin >> name >> std::hex >> start >> std::hex >> end; + if (!fin) + break; + + toDo(name, start, end); + } +} + +template +void ParseFontList(string const & fontListFile, ToDo toDo) +{ + string fontList; + try + { + ReaderPtr(GetPlatform().GetReader(fontListFile)).ReadAsString(fontList); + } + catch(RootException const & e) + { + LOG(LWARNING, ("Error reading font list ", fontListFile, " : ", e.what())); + return; + } + + istringstream fin(fontList); + while (true) + { + string ubName; + string fontName; + fin >> ubName >> fontName; + if (!fin) + break; + + toDo(ubName, fontName); + } +} + +class Font +{ +public: + Font(ReaderPtr fontReader, FT_Library lib) + : m_fontReader(fontReader) + { + m_stream.base = 0; + m_stream.size = m_fontReader.Size(); + m_stream.pos = 0; + m_stream.descriptor.pointer = &m_fontReader; + m_stream.pathname.pointer = 0; + m_stream.read = &Font::Read; + m_stream.close = &Font::Close; + m_stream.memory = 0; + m_stream.cursor = 0; + m_stream.limit = 0; + + FT_Open_Args args; + args.flags = FT_OPEN_STREAM; + args.memory_base = 0; + args.memory_size = 0; + args.pathname = 0; + args.stream = &m_stream; + args.driver = 0; + args.num_params = 0; + args.params = 0; + + FREETYPE_CHECK(FT_Open_Face(lib, &args, 0, &m_fontFace)); + } + + void DestroyFont() + { + FREETYPE_CHECK(FT_Done_Face(m_fontFace)); + m_fontFace = nullptr; + } + + bool HasGlyph(strings::UniChar unicodePoint) const + { + return FT_Get_Char_Index(m_fontFace, unicodePoint) != 0; + } + + GlyphManager::Glyph GetGlyph(strings::UniChar unicodePoint) const + { + FREETYPE_CHECK(FT_Set_Pixel_Sizes(m_fontFace, 0, 20)); + FREETYPE_CHECK(FT_Load_Glyph(m_fontFace, FT_Get_Char_Index(m_fontFace, unicodePoint), FT_LOAD_RENDER)); + + FT_Glyph glyph; + FREETYPE_CHECK(FT_Get_Glyph(m_fontFace->glyph, &glyph)); + + FT_BBox bbox; + FT_Glyph_Get_CBox(glyph, FT_GLYPH_BBOX_PIXELS , &bbox); + + FT_Bitmap bitmap = m_fontFace->glyph->bitmap; + + SharedBufferManager::shared_buffer_ptr_t data; + size_t byteSize = bitmap.rows * bitmap.pitch; + size_t bufferSize = my::NextPowOf2(byteSize); + if (bitmap.buffer != nullptr) + { + data = SharedBufferManager::instance().reserveSharedBuffer(bufferSize); + memcpy(SharedBufferManager::GetRawPointer(data), bitmap.buffer, byteSize); + } + + GlyphManager::Glyph result; + result.m_image = GlyphManager::GlyphImage + { + bitmap.width, + bitmap.rows, + bitmap.pitch, + data, + bufferSize + }; + + result.m_metrics = GlyphManager::GlyphMetrics + { + static_cast(glyph->advance.x >> 16), + static_cast(glyph->advance.y >> 16), + static_cast(bbox.xMin), + static_cast(bbox.yMin), + static_cast(bbox.xMax - bbox.xMin), + static_cast(bbox.yMax - bbox.yMin) + }; + + FT_Done_Glyph(glyph); + + return result; + } + + void GetCharcodes(vector & charcodes) + { + FT_UInt gindex; + charcodes.push_back(FT_Get_First_Char(m_fontFace, &gindex)); + while (gindex) + charcodes.push_back(FT_Get_Next_Char(m_fontFace, charcodes.back(), &gindex)); + + sort(charcodes.begin(), charcodes.end()); + charcodes.erase(unique(charcodes.begin(), charcodes.end()), charcodes.end()); + } + + static unsigned long Read(FT_Stream stream, unsigned long offset, unsigned char * buffer, unsigned long count) + { + if (count != 0) + { + ReaderPtr * reader = reinterpret_cast *>(stream->descriptor.pointer); + reader->Read(offset, buffer, count); + } + + return count; + } + + static void Close(FT_Stream){} + +private: + ReaderPtr m_fontReader; + FT_StreamRec_ m_stream; + FT_Face m_fontFace; +}; + +} + +/// Information about single unicode block. +struct UnicodeBlock +{ + string m_name; + + strings::UniChar m_start; + strings::UniChar m_end; + vector m_fontsWeight; + + UnicodeBlock(string const & name, strings::UniChar start, strings::UniChar end) + : m_name(name) + , m_start(start) + , m_end(end) + { + } + + int GetFontOffset(int idx) const + { + ASSERT(!m_fontsWeight.empty(), ()); + int maxWight = 0; + int upperBoundWeight = numeric_limits::max(); + if (idx != -1) + upperBoundWeight = m_fontsWeight[idx]; + + int index = -1; + for (size_t i = 0; i < m_fontsWeight.size(); ++i) + { + int w = m_fontsWeight[i]; + if (w < upperBoundWeight && w > maxWight) + { + maxWight = w; + index = i; + } + } + + return index; + } + + bool HasSymbol(strings::UniChar sym) const + { + return (m_start <= sym) && (m_end >= sym); + } +}; + +typedef vector TUniBlocks; +typedef TUniBlocks::const_iterator TUniBlockIter; + +struct GlyphManager::Impl +{ + FT_Library m_library; + TUniBlocks m_blocks; + vector m_fonts; +}; + +GlyphManager::GlyphManager(GlyphManager::Params const & params) + : m_impl(new Impl()) +{ + typedef pair TFontAndBlockName; + typedef buffer_vector TFontLst; + + TFontLst whitelst; + TFontLst blacklst; + + m_impl->m_blocks.reserve(160); + ParseUniBlocks(params.m_uniBlocks, [this](string const & name, strings::UniChar start, strings::UniChar end) + { + m_impl->m_blocks.push_back(UnicodeBlock(name, start, end)); + }); + + ParseFontList(params.m_whitelist, [&whitelst](string const & ubName, string const & fontName) + { + whitelst.push_back(TFontAndBlockName(fontName, ubName)); + }); + + ParseFontList(params.m_blacklist, [&blacklst](string const & ubName, string const & fontName) + { + blacklst.push_back(TFontAndBlockName(fontName, ubName)); + }); + + m_impl->m_fonts.reserve(params.m_fonts.size()); + + FREETYPE_CHECK(FT_Init_FreeType(&m_impl->m_library)); + + for (string const & fontName : params.m_fonts) + { + bool ignoreFont = false; + for_each(blacklst.begin(), blacklst.end(), [&ignoreFont, &fontName](TFontAndBlockName const & p) + { + if (p.first == fontName && p.second == "*") + ignoreFont = true; + }); + + if (ignoreFont) + continue; + + vector charCodes; + try + { + m_impl->m_fonts.emplace_back(GetPlatform().GetReader(fontName), m_impl->m_library); + m_impl->m_fonts.back().GetCharcodes(charCodes); + } + catch(RootException const & e) + { + LOG(LWARNING, ("Error read font file : ", e.what())); + } + + typedef size_t TBlockIndex; + typedef int TCharCounter; + typedef pair TCoverNode; + typedef vector TCoverInfo; + + size_t currentUniBlock = 0; + TCoverInfo coverInfo; + for (FT_ULong const & charCode : charCodes) + { + while(currentUniBlock < m_impl->m_blocks.size()) + { + if (m_impl->m_blocks[currentUniBlock].HasSymbol(charCode)) + break; + ++currentUniBlock; + } + + if (currentUniBlock >= m_impl->m_blocks.size()) + break; + + if (coverInfo.empty() || coverInfo.back().first != currentUniBlock) + coverInfo.push_back(make_pair(currentUniBlock, 1)); + else + ++coverInfo.back().second; + } + + typedef function TUpdateCoverInfoFn; + auto enumerateFn = [this, &coverInfo, &fontName] (TFontLst const & lst, TUpdateCoverInfoFn const & fn) + { + for (TFontAndBlockName const & b : lst) + { + if (b.first != fontName) + continue; + + for (TCoverNode & node : coverInfo) + { + UnicodeBlock const & uniBlock = m_impl->m_blocks[node.first]; + if (uniBlock.m_name == b.second) + { + fn(uniBlock, node); + break; + } + else if (b.second == "*") + fn(uniBlock, node); + } + } + }; + + enumerateFn(blacklst, [](UnicodeBlock const &, TCoverNode & node) + { + node.second = 0; + }); + + enumerateFn(whitelst, [this](UnicodeBlock const & uniBlock, TCoverNode & node) + { + node.second = uniBlock.m_end + 1 - uniBlock.m_start + m_impl->m_fonts.size(); + }); + + for (TCoverNode & node : coverInfo) + { + UnicodeBlock & uniBlock = m_impl->m_blocks[node.first]; + uniBlock.m_fontsWeight.resize(m_impl->m_fonts.size(), 0); + uniBlock.m_fontsWeight.back() = node.second; + } + } +} + +GlyphManager::~GlyphManager() +{ + for (Font & f : m_impl->m_fonts) + f.DestroyFont(); + + FREETYPE_CHECK(FT_Done_FreeType(m_impl->m_library)); + delete m_impl; +} + +void GlyphManager::GetGlyphs(vector const & unicodePoints, vector & glyphs) +{ + glyphs.reserve(unicodePoints.size()); + + TUniBlockIter iter = m_impl->m_blocks.end(); + int fontIndex = -1; + for (strings::UniChar const & unicodePoint : unicodePoints) + { + if (iter == m_impl->m_blocks.end() || !iter->HasSymbol(unicodePoint)) + { + iter = lower_bound(m_impl->m_blocks.begin(), m_impl->m_blocks.end(), unicodePoint, + [](UnicodeBlock const & block, strings::UniChar const & v) + { + return block.m_end < v; + }); + fontIndex = -1; + } + + if (iter == m_impl->m_blocks.end()) + { + glyphs.push_back(GetInvalidGlyph()); + continue; + } + + UnicodeBlock const & block = *iter; + ASSERT(block.HasSymbol(unicodePoint), ()); + do + { + if (fontIndex != -1) + { + ASSERT_LESS(fontIndex, m_impl->m_fonts.size(), ()); + Font const & f = m_impl->m_fonts[fontIndex]; + if (f.HasGlyph(unicodePoint)) + { + glyphs.push_back(f.GetGlyph(unicodePoint)); + break; + } + } + + fontIndex = block.GetFontOffset(fontIndex); + } while(fontIndex != -1); + + if (fontIndex == -1) + glyphs.push_back(GetInvalidGlyph()); + } +} + +GlyphManager::Glyph const & GlyphManager::GetInvalidGlyph() const +{ + static bool s_inited = false; + static Glyph s_glyph; + + if (!s_inited) + { + ASSERT(!m_impl->m_fonts.empty(), ()); + s_glyph = m_impl->m_fonts[0].GetGlyph(0); + s_inited = true; + } + + return s_glyph; +} + +} diff --git a/drape/glyph_manager.hpp b/drape/glyph_manager.hpp new file mode 100644 index 0000000000..2e67f2741e --- /dev/null +++ b/drape/glyph_manager.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "../base/shared_buffer_manager.hpp" +#include "../base/string_utils.hpp" + +#include "../std/unique_ptr.hpp" +#include "../std/string.hpp" +#include "../std/vector.hpp" + +namespace dp +{ + +class GlyphManager +{ +public: + struct Params + { + string m_uniBlocks; + string m_whitelist; + string m_blacklist; + + vector m_fonts; + }; + + struct GlyphMetrics + { + int m_xAdvance; + int m_yAdvance; + int m_xOffset; + int m_yOffset; + int m_width; + int m_height; + }; + + struct GlyphImage + { + ~GlyphImage() + { + ASSERT(!m_data.unique(), ()); + } + + void Destroy() + { + SharedBufferManager::instance().freeSharedBuffer(m_bufferSize, m_data); + } + + int m_width; + int m_height; + int m_pitch; + + SharedBufferManager::shared_buffer_ptr_t m_data; + size_t m_bufferSize; + }; + + struct Glyph + { + GlyphMetrics m_metrics; + GlyphImage m_image; + }; + + GlyphManager(Params const & params); + ~GlyphManager(); + + void GetGlyphs(vector const & unicodePoints, vector & glyphs); + +private: + Glyph const & GetInvalidGlyph() const; + +private: + struct Impl; + Impl * m_impl; +}; + +}