├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake └── glm.cmake └── src ├── CMakeLists.txt ├── alfons ├── alfons.cpp ├── alfons.h ├── appleFontConverter │ ├── FontConverter.h │ └── fontConverter.mm ├── appleFontFace.h ├── appleFontFace.mm ├── atlas.cpp ├── atlas.h ├── font.cpp ├── font.h ├── fontFace.cpp ├── fontFace.h ├── fontManager.cpp ├── fontManager.h ├── freetypeHelper.h ├── glyph.h ├── inputSource.h ├── langHelper.cpp ├── langHelper.h ├── lineLayout.h ├── path │ ├── ASPC.h │ ├── lineSampler.cpp │ ├── lineSampler.h │ ├── splinePath.cpp │ └── splinePath.h ├── quadMatrix.cpp ├── quadMatrix.h ├── scrptrun.cpp ├── scrptrun.h ├── textBatch.cpp ├── textBatch.h ├── textShaper.cpp ├── textShaper.h └── utils.h ├── linebreak ├── linebreak.c ├── linebreak.h ├── linebreakdata.c ├── linebreakdef.c └── linebreakdef.h └── logger ├── logger.h └── tinyformat.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | deps/glm/ 3 | 4 | # vim files 5 | *.swp 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hjanetzek/alfons/26a4dc693ad35609d4909141f763f1bad32e681f/.gitmodules -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | 3 | if (APPLE) 4 | option(HB_HAVE_CORETEXT "Enable CoreText shaper backend on macOS" ON) 5 | endif () 6 | 7 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/glm.cmake) 8 | 9 | if (NOT DEFINED ALFONS_DEPS_LIBRARIES) 10 | 11 | include(FindPkgConfig) 12 | pkg_check_modules(ICU REQUIRED icu-uc) 13 | pkg_check_modules(FREETYPE REQUIRED freetype2) 14 | if (APPLE) 15 | pkg_check_modules(HARFBUZZ REQUIRED harfbuzz>=1.7.2) 16 | else() 17 | pkg_check_modules(HARFBUZZ REQUIRED harfbuzz) 18 | endif() 19 | 20 | set(ALFONS_DEPS_LIBRARIES 21 | ${ICU_LDFLAGS} 22 | ${FREETYPE_LDFLAGS} 23 | ${HARFBUZZ_LDFLAGS} 24 | CACHE INTERNAL "alfons-libs" FORCE) 25 | 26 | set(ALFONS_DEPS_INCLUDE_DIRS 27 | ${ICU_INCLUDE_DIRS} 28 | ${FREETYPE_INCLUDE_DIRS} 29 | ${HARFBUZZ_INCLUDE_DIRS} 30 | CACHE INTERNAL "alfons-includes" FORCE) 31 | 32 | endif() 33 | 34 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++1y") 35 | 36 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Ariel Malka 2 | Copyright 2015 Hannes Janetzek 3 | Copyright 2016 Karim Naaji 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alfons 2 | Text rendering utilities 3 | 4 | This Library is based on the 'New Chronotext Toolkit', extracting only the text 5 | rendering parts and removing the Cinder dependency. 6 | 7 | Alfons bakes your text and fonts to delicious vertices and textures (best served with some OpenGL) 8 | 9 | For examples see https://github.com/hjanetzek/alfons-demo 10 | 11 | ### Credits 12 | 13 | - Chronotext (https://github.com/arielm/wandering-chronotext-toolkit, http://chronotext.org/) 14 | - Fontstash (https://github.com/memononen/fontstash) 15 | - fontstash-es (https://github.com/tangrams/fontstash-es) 16 | - Cinder (https://github.com/cinder/Cinder) 17 | -------------------------------------------------------------------------------- /cmake/glm.cmake: -------------------------------------------------------------------------------- 1 | if (NOT DEFINED ${GLM_INCLUDE_DIR}) 2 | message(STATUS "Looking for GLM") 3 | 4 | find_path(GLM_INCLUDE_DIR glm/glm.hpp 5 | HINTS 6 | ${GLM_ROOT} 7 | PATH_SUFFIXES include 8 | PATHS 9 | ${GLM_ROOT}) 10 | 11 | 12 | if(${GLM_INCLUDE_DIR} STREQUAL "GLM_INCLUDE_DIR-NOTFOUND") 13 | message(STATUS "Checkout GLM") 14 | 15 | include(ExternalProject) 16 | 17 | ExternalProject_Add(glm 18 | GIT_REPOSITORY https://github.com/g-truc/glm.git 19 | GIT_TAG 6e5f42b 20 | 21 | # put checkout into build-dir/glm 22 | PREFIX deps/glm 23 | SOURCE_DIR glm 24 | # Use source dir for build dir 25 | BUILD_IN_SOURCE 1 26 | 27 | UPDATE_COMMAND "" 28 | CONFIGURE_COMMAND "" 29 | BUILD_COMMAND "" 30 | INSTALL_COMMAND "") 31 | 32 | set(GLM_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/glm) 33 | 34 | set(USE_OWN_GLM 1) 35 | 36 | endif() 37 | endif() 38 | 39 | message(STATUS "GLM include ${GLM_INCLUDE_DIR}") 40 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(POLICY CMP0054) 2 | cmake_policy(SET CMP0054 NEW) 3 | endif() 4 | 5 | add_library (linebreak 6 | linebreak/linebreak.c 7 | linebreak/linebreakdata.c 8 | linebreak/linebreakdef.c) 9 | 10 | target_include_directories (linebreak 11 | PUBLIC 12 | ${CMAKE_CURRENT_SOURCE_DIR}/linebreak) 13 | 14 | set(ALFONS_SRC 15 | alfons/alfons.cpp 16 | alfons/fontManager.cpp 17 | alfons/fontFace.cpp 18 | alfons/langHelper.cpp 19 | alfons/font.cpp 20 | alfons/textBatch.cpp 21 | alfons/atlas.cpp 22 | alfons/textShaper.cpp 23 | alfons/quadMatrix.cpp 24 | alfons/path/lineSampler.cpp 25 | alfons/path/splinePath.cpp 26 | # TODO part of icu-extras 27 | alfons/scrptrun.cpp) 28 | 29 | if (APPLE AND HB_HAVE_CORETEXT) 30 | list(APPEND ALFONS_SRC alfons/appleFontConverter/fontConverter.mm) 31 | list(APPEND ALFONS_SRC alfons/appleFontFace.mm) 32 | 33 | set(ALFONS_APPLE_LIBRARIES 34 | "-framework CoreGraphics" 35 | "-framework Foundation") 36 | if(NOT IOS) 37 | set(ALFONS_APPLE_LIBRARIES 38 | ${ALFONS_APPLE_LIBRARIES} 39 | "-framework AppKit") 40 | endif() 41 | message(STATUS "ALFONS_APPLE_LIBRARIES: " ${ALFONS_APPLE_LIBRARIES}) 42 | 43 | add_definitions(-DHAVE_CORETEXT) 44 | endif() 45 | 46 | add_library(alfons ${ALFONS_SRC}) 47 | 48 | target_include_directories (alfons 49 | PUBLIC 50 | ${CMAKE_CURRENT_SOURCE_DIR} 51 | ${ALFONS_DEPS_INCLUDE_DIRS} 52 | ${GLM_INCLUDE_DIR} 53 | ${CMAKE_CURRENT_SOURCE_DIR}/logger) 54 | 55 | target_link_libraries (alfons 56 | LINK_PUBLIC 57 | linebreak 58 | ${ALFONS_APPLE_LIBRARIES} 59 | ${ALFONS_DEPS_LIBRARIES}) 60 | 61 | if (USE_OWN_GLM) 62 | add_dependencies(alfons glm) 63 | endif() 64 | 65 | ### check for undefined symbols 66 | if(BUILD_SHARED_LIBS AND CMAKE_COMPILER_IS_GNUCXX) 67 | target_link_libraries( alfons PRIVATE "-z defs") 68 | endif() 69 | 70 | if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 71 | target_compile_options(alfons 72 | PRIVATE 73 | -D_USE_MATH_DEFINES 74 | -utf-8 75 | -wd4267 76 | -wd4244) 77 | endif() 78 | 79 | -------------------------------------------------------------------------------- /src/alfons/alfons.cpp: -------------------------------------------------------------------------------- 1 | #if 0 2 | #include "alfons.h" 3 | 4 | namespace alfons { 5 | 6 | Alfons::Alfons() 7 | : m_atlas(*this, 512), 8 | m_batcher(m_atlas, *this) { 9 | 10 | } 11 | 12 | } 13 | #endif 14 | -------------------------------------------------------------------------------- /src/alfons/alfons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace alfons { 7 | 8 | struct AtlasGlyph; 9 | using AtlasID = size_t; 10 | 11 | struct Quad { 12 | float x1; 13 | float y1; 14 | float x2; 15 | float y2; 16 | float x3; 17 | float y3; 18 | float x4; 19 | float y4; 20 | }; 21 | 22 | struct Rect { 23 | float x1; 24 | float y1; 25 | float x2; 26 | float y2; 27 | }; 28 | 29 | struct MeshCallback { 30 | virtual void drawGlyph(const Quad& quad, const AtlasGlyph& glyph) = 0; 31 | virtual void drawGlyph(const Rect& rect, const AtlasGlyph& glyph) = 0; 32 | }; 33 | 34 | struct TextureCallback { 35 | virtual void addTexture(AtlasID id, uint16_t textureWidth, uint16_t textureHeight) = 0; 36 | virtual void addGlyph(AtlasID id, uint16_t gx, uint16_t gy, uint16_t gw, uint16_t gh, 37 | const unsigned char* src, uint16_t padding) = 0; 38 | }; 39 | 40 | #if 0 41 | class Alfons : protected MeshCallback, protected TextureCallback { 42 | public: 43 | Alfons(); 44 | 45 | protected: 46 | 47 | // virtual void addTexture(AtlasID id, uint32_t textureWidth, uint32_t textureHeight) = 0; 48 | // virtual void addGlyph(AtlasID id, uint gx, uint gy, uint gw, uint gh, 49 | // const unsigned char* src, uint padding) = 0; 50 | 51 | GlyphAtlas m_atlas; 52 | TextBatch m_batcher; 53 | TextShaper m_shaper; 54 | FontManager m_fontManager; 55 | }; 56 | #endif 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/alfons/appleFontConverter/FontConverter.h: -------------------------------------------------------------------------------- 1 | // 2 | // FontConverter.h 3 | // 4 | // Created by Karim Naaji on 11/01/16. 5 | // Copyright (c) 2017 Mapzen. All rights reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface FontConverter : NSObject 16 | 17 | + (std::vector)fontDataForCGFont:(CGFontRef)cgFont; 18 | 19 | NS_ASSUME_NONNULL_END 20 | 21 | @end 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/alfons/appleFontConverter/fontConverter.mm: -------------------------------------------------------------------------------- 1 | // 2 | // FontConverter.mm 3 | // 4 | // Created by Karim Naaji on 11/01/16. 5 | // Copyright (c) 2017 Mapzen. All rights reserved. 6 | // 7 | 8 | #import "FontConverter.h" 9 | 10 | #import 11 | 12 | struct FontHeader { 13 | int32_t version; 14 | uint16_t numTables; 15 | uint16_t searchRange; 16 | uint16_t entrySelector; 17 | uint16_t rangeShift; 18 | }; 19 | 20 | typedef struct FontHeader FontHeader; 21 | 22 | struct TableEntry { 23 | uint32_t tag; 24 | uint32_t checkSum; 25 | uint32_t offset; 26 | uint32_t length; 27 | }; 28 | 29 | typedef struct TableEntry TableEntry; 30 | 31 | static uint32_t calcTableCheckSum(const uint32_t* table, uint32_t numberOfBytesInTable) 32 | { 33 | uint32_t sum = 0; 34 | uint32_t nLongs = (numberOfBytesInTable + 3) / 4; 35 | while (nLongs-- > 0) { 36 | sum += CFSwapInt32HostToBig(*table++); 37 | } 38 | return sum; 39 | } 40 | 41 | static uint32_t calcTableDataRefCheckSum(CFDataRef dataRef) 42 | { 43 | const uint32_t* dataBuff = (const uint32_t *)CFDataGetBytePtr(dataRef); 44 | uint32_t dataLength = (uint32_t)CFDataGetLength(dataRef); 45 | return calcTableCheckSum(dataBuff, dataLength); 46 | } 47 | 48 | @implementation FontConverter 49 | 50 | // References: 51 | // https://skia.googlesource.com/skia/+/master/src/ports/SkFontHost_mac.cpp 52 | // https://gist.github.com/Jyczeal/1892760 53 | 54 | + (std::vector)fontDataForCGFont:(CGFontRef)cgFont 55 | { 56 | if (!cgFont) { 57 | return {}; 58 | } 59 | 60 | CFRetain(cgFont); 61 | 62 | CFArrayRef tags = CGFontCopyTableTags(cgFont); 63 | int tableCount = CFArrayGetCount(tags); 64 | 65 | std::vector tableSizes; 66 | tableSizes.resize(tableCount); 67 | std::vector dataRefs; 68 | dataRefs.resize(tableCount); 69 | 70 | BOOL containsCFFTable = NO; 71 | 72 | size_t totalSize = sizeof(FontHeader) + sizeof(TableEntry) * tableCount; 73 | 74 | for (int index = 0; index < tableCount; ++index) { 75 | size_t tableSize = 0; 76 | intptr_t aTag = (intptr_t)CFArrayGetValueAtIndex(tags, index); 77 | 78 | if (aTag == 'CFF ' && !containsCFFTable) { 79 | containsCFFTable = YES; 80 | } 81 | 82 | dataRefs[index] = CGFontCopyTableForTag(cgFont, aTag); 83 | 84 | if (dataRefs[index] != NULL) { 85 | tableSize = CFDataGetLength(dataRefs[index]); 86 | } 87 | 88 | totalSize += (tableSize + 3) & ~3; 89 | 90 | tableSizes[index] = tableSize; 91 | } 92 | 93 | std::vector data; 94 | data.resize(totalSize); 95 | unsigned char* stream = reinterpret_cast(data.data()); 96 | 97 | char* dataStart = (char*)stream; 98 | char* dataPtr = dataStart; 99 | 100 | // Write font header (also called sfnt header, offset subtable) 101 | FontHeader* offsetTable = (FontHeader*)dataPtr; 102 | 103 | // Compute font header entries 104 | // c.f: Organization of an OpenType Font in: 105 | // https://www.microsoft.com/typography/otspec/otff.htm 106 | { 107 | // (Maximum power of 2 <= numTables) x 16 108 | uint16_t entrySelector = 0; 109 | // Log2(maximum power of 2 <= numTables). 110 | uint16_t searchRange = 1; 111 | 112 | while (searchRange < tableCount >> 1) { 113 | entrySelector++; 114 | searchRange <<= 1; 115 | } 116 | searchRange <<= 4; 117 | 118 | // NumTables x 16-searchRange. 119 | uint16_t rangeShift = (tableCount << 4) - searchRange; 120 | 121 | // OpenType Font contains CFF Table use 'OTTO' as version, and with .otf extension 122 | // otherwise 0001 0000 123 | offsetTable->version = containsCFFTable ? 'OTTO' : CFSwapInt16HostToBig(1); 124 | offsetTable->numTables = CFSwapInt16HostToBig((uint16_t)tableCount); 125 | offsetTable->searchRange = CFSwapInt16HostToBig((uint16_t)searchRange); 126 | offsetTable->entrySelector = CFSwapInt16HostToBig((uint16_t)entrySelector); 127 | offsetTable->rangeShift = CFSwapInt16HostToBig((uint16_t)rangeShift); 128 | } 129 | 130 | dataPtr += sizeof(FontHeader); 131 | 132 | // Write tables 133 | TableEntry* entry = (TableEntry*)dataPtr; 134 | dataPtr += sizeof(TableEntry) * tableCount; 135 | 136 | for (int index = 0; index < tableCount; ++index) { 137 | 138 | intptr_t aTag = (intptr_t)CFArrayGetValueAtIndex(tags, index); 139 | CFDataRef tableDataRef = dataRefs[index]; 140 | 141 | if (tableDataRef == NULL) { continue; } 142 | 143 | size_t tableSize = CFDataGetLength(tableDataRef); 144 | 145 | memcpy(dataPtr, CFDataGetBytePtr(tableDataRef), tableSize); 146 | 147 | entry->tag = CFSwapInt32HostToBig((uint32_t)aTag); 148 | entry->checkSum = CFSwapInt32HostToBig(calcTableCheckSum((uint32_t *)dataPtr, tableSize)); 149 | 150 | uint32_t offset = dataPtr - dataStart; 151 | entry->offset = CFSwapInt32HostToBig((uint32_t)offset); 152 | entry->length = CFSwapInt32HostToBig((uint32_t)tableSize); 153 | dataPtr += (tableSize + 3) & ~3; 154 | ++entry; 155 | CFRelease(tableDataRef); 156 | } 157 | 158 | CFRelease(tags); 159 | CFRelease(cgFont); 160 | 161 | return data; 162 | } 163 | 164 | @end 165 | -------------------------------------------------------------------------------- /src/alfons/appleFontFace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "fontFace.h" 4 | 5 | namespace alfons { 6 | 7 | class AppleFontFace : public FontFace { 8 | 9 | public: 10 | 11 | AppleFontFace(FreetypeHelper& _ft, FaceID faceId, const Descriptor& descriptor, float baseSize); 12 | 13 | bool load() override; 14 | 15 | }; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/alfons/appleFontFace.mm: -------------------------------------------------------------------------------- 1 | #import 2 | #import "appleFontConverter/FontConverter.h" 3 | #include 4 | 5 | #include "appleFontFace.h" 6 | #include "logger.h" 7 | 8 | #include 9 | 10 | #if TARGET_OS_OSX 11 | #import 12 | #endif 13 | 14 | 15 | namespace alfons { 16 | 17 | AppleFontFace::AppleFontFace(FreetypeHelper& _ft, FaceID _faceId, const Descriptor& _descriptor, 18 | float _baseSize) 19 | : FontFace(_ft, _faceId, _descriptor, _baseSize) {} 20 | 21 | bool AppleFontFace::load() { 22 | 23 | if (m_loaded) { return true; } 24 | if (m_invalid) { return false; } 25 | 26 | if (!m_descriptor.source.isValid() || !m_descriptor.source.isSystemFont()) { 27 | m_invalid = true; 28 | return false; 29 | } 30 | 31 | auto &fontName = m_descriptor.source.uri(); 32 | 33 | CFStringRef name = CFStringCreateWithCString(nullptr, fontName.c_str(), kCFStringEncodingUTF8); 34 | CGFontRef cgFont = CGFontCreateWithFontName(name); 35 | CFRelease(name); 36 | 37 | #if TARGET_OS_OSX 38 | if (!cgFont) { 39 | // On macOS 10.12+ some default system font names start with `.AppleSystemUIFont`, for such fonts we need 40 | // NSFont to get CGFontRef 41 | NSString* nsFontName = [NSString stringWithUTF8String:fontName.c_str()]; 42 | NSFont *font = [NSFont fontWithName:nsFontName size:1.0]; 43 | if (font) { 44 | cgFont = CTFontCopyGraphicsFont(CTFontRef(font), nullptr); 45 | } 46 | } 47 | #endif 48 | 49 | if (!cgFont) { 50 | LOGE("Cannot create font with name '%s'", fontName.c_str()); 51 | m_invalid = true; 52 | return false; 53 | } 54 | 55 | if (!m_descriptor.source.hasData()) { 56 | std::vector fontData = [FontConverter fontDataForCGFont:cgFont]; 57 | if (fontData.empty()) { 58 | LOGE("Font data read from apple system font '%s' is empty.", fontName.c_str()); 59 | m_invalid = true; 60 | return false; 61 | } 62 | 63 | // Use raw system fontData to create freetype face for glyph rendering 64 | m_descriptor.source.setData(fontData); 65 | } else { 66 | LOGD("Reusing converted font data"); 67 | } 68 | 69 | auto &buffer = m_descriptor.source.buffer(); 70 | FT_Error error = FT_New_Memory_Face(m_ft.getLib(), reinterpret_cast(buffer.data()), 71 | buffer.size(), m_descriptor.faceIndex, &m_ftFace); 72 | 73 | if (error) { 74 | LOGE("Could not create font: error: %d", error); 75 | m_invalid = true; 76 | return false; 77 | } 78 | 79 | if (force_ucs2_charmap(m_ftFace)) { 80 | LOGE("Font is broken or irrelevant..."); 81 | // ...but DroisSansJapan still seems to work! 82 | // FT_Done_Face(m_ftFace); 83 | // m_ftFace = nullptr; 84 | // return false; 85 | } 86 | 87 | int dpi = 72; 88 | FT_Set_Char_Size(m_ftFace, 89 | m_baseSize * 64, // char_width in 26.6 fixed-point 90 | m_baseSize * 64, // char_height in 26.6 fixed-point 91 | dpi, // horizontal_resolution 92 | dpi); // vertical_resolution 93 | 94 | // Set font metrics from cgFont and ctFont 95 | CTFontRef ctFont = CTFontCreateWithGraphicsFont(cgFont, m_baseSize, nullptr, nullptr); 96 | 97 | // Create harfbuzz font context using CTFont (New API available in hb 1.7.2) 98 | m_hbFont = hb_coretext_font_create(ctFont); 99 | 100 | hb_font_set_scale(m_hbFont, 101 | (static_cast(m_ftFace->size->metrics.x_scale) * 102 | static_cast(m_ftFace->units_per_EM)) >> 16, 103 | (static_cast(m_ftFace->size->metrics.y_scale) * 104 | static_cast(m_ftFace->units_per_EM)) >> 16); 105 | hb_font_set_ppem(m_hbFont, m_ftFace->size->metrics.x_ppem, m_ftFace->size->metrics.y_ppem); 106 | 107 | m_metrics.height = CGFontGetCapHeight(cgFont) / (float)::CGFontGetUnitsPerEm(cgFont) * m_baseSize; 108 | m_metrics.ascent = CGFontGetAscent(cgFont) / (float)::CGFontGetUnitsPerEm(cgFont) * m_baseSize; 109 | m_metrics.descent = -CGFontGetDescent(cgFont) / (float)::CGFontGetUnitsPerEm(cgFont) * m_baseSize; 110 | 111 | m_metrics.lineThickness = CTFontGetUnderlineThickness(ctFont); 112 | m_metrics.underlineOffset = -CTFontGetUnderlinePosition(ctFont); 113 | 114 | CFRelease(ctFont); 115 | CGFontRelease(cgFont); 116 | 117 | LOGI("LOADED Apple System Font: %s size: %d", getFullName(), m_baseSize); 118 | 119 | m_loaded = true; 120 | return true; 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/alfons/atlas.cpp: -------------------------------------------------------------------------------- 1 | // Ported from fontstash 2 | // 3 | // Atlas based on Skyline Bin Packer by Jukka Jylänki 4 | 5 | #include "atlas.h" 6 | #include "font.h" 7 | 8 | #include "logger.h" 9 | 10 | #include 11 | 12 | namespace alfons { 13 | 14 | Atlas::Atlas(int w, int h) { 15 | reset(w, h); 16 | } 17 | 18 | void Atlas::expand(int w, int h) { 19 | // Insert node for empty space 20 | if (w > width) { 21 | nodes.insert(nodes.end(), {width, 0, w - width}); 22 | } 23 | width = w; 24 | height = h; 25 | } 26 | 27 | void Atlas::reset(int w, int h) { 28 | width = w; 29 | height = h; 30 | 31 | // Init root node. 32 | nodes.clear(); 33 | nodes.push_back({0, 0, w}); 34 | 35 | glyphMap.clear(); 36 | } 37 | 38 | void Atlas::addSkylineLevel(uint32_t idx, int x, int y, int w, int h) { 39 | 40 | // Insert new node 41 | nodes.insert(nodes.begin() + idx, {x, y + h, w}); 42 | 43 | // Delete skyline segments that fall under the shadow of the new segment. 44 | for (size_t i = idx + 1; i < nodes.size(); i++) { 45 | 46 | int shrink = (nodes[i - 1].x + nodes[i - 1].width) - nodes[i].x; 47 | if (shrink > 0) { 48 | 49 | int nw = nodes[i].width - shrink; 50 | if (nw > 0) { 51 | nodes[i].x += shrink; 52 | nodes[i].width = nw; 53 | break; 54 | } else { 55 | nodes.erase(nodes.begin() + i); 56 | i--; 57 | } 58 | } else { break; } 59 | } 60 | 61 | // Merge same height skyline segments that are next to each other. 62 | for (size_t i = 0; i < nodes.size() - 1; i++) { 63 | if (nodes[i].y == nodes[i + 1].y) { 64 | nodes[i].width += nodes[i + 1].width; 65 | nodes.erase(nodes.begin() + i + 1); 66 | i--; 67 | } 68 | } 69 | } 70 | 71 | /// Checks if there is enough space at the location of skyline span 'i', and 72 | /// return the max height of all skyline spans under that at that location, 73 | /// (think tetris block being dropped at that position). Or -1 if no space 74 | /// found. 75 | int Atlas::rectFits(uint32_t i, int w, int h) { 76 | 77 | if (nodes[i].x + w > width) { return -1; } 78 | 79 | int spaceLeft = w; 80 | int y = nodes[i].y; 81 | while (spaceLeft > 0) { 82 | if (i == nodes.size()) { return -1; } 83 | 84 | y = std::max(y, nodes[i].y); 85 | if (y + h > height) { return -1; } 86 | 87 | spaceLeft -= nodes[i].width; 88 | ++i; 89 | } 90 | return y; 91 | } 92 | 93 | bool Atlas::addRect(int w, int h, int* rx, int* ry) { 94 | int besth = height, bestw = width; 95 | int bestx = -1, besty = -1, besti = -1; 96 | 97 | // Bottom left fit heuristic. 98 | for (size_t i = 0; i < nodes.size(); i++) { 99 | int y = rectFits(i, w, h); 100 | if (y != -1) { 101 | if ((y + h < besth) || 102 | ((y + h == besth) && (nodes[i].width < bestw))) { 103 | besti = i; 104 | bestw = nodes[i].width; 105 | besth = y + h; 106 | bestx = nodes[i].x; 107 | besty = y; 108 | } 109 | } 110 | } 111 | 112 | if (besti == -1) { return false; } 113 | 114 | // Perform the actual packing. 115 | addSkylineLevel(besti, bestx, besty, w, h); 116 | 117 | *rx = bestx; 118 | *ry = besty; 119 | 120 | return true; 121 | } 122 | 123 | bool GlyphAtlas::getGlyph(const Font& _font, const GlyphKey& _key, AtlasGlyph& _entry) { 124 | AtlasID id = 0; 125 | 126 | for (auto& a : m_atlas) { 127 | auto it = a.glyphMap.find(_key); 128 | if (it != a.glyphMap.end()) { 129 | //a->usage++; 130 | _entry.atlas = id; 131 | _entry.glyph = &it->second; 132 | return true; 133 | } 134 | id++; 135 | } 136 | return createGlyph(_font, _key, _entry); 137 | } 138 | 139 | bool GlyphAtlas::createGlyph(const Font& _font, const GlyphKey& _key, AtlasGlyph& _entry) { 140 | 141 | if (_key.codepoint == 0) { return false; } 142 | 143 | auto& fontFace = _font.face(_key.font); 144 | 145 | const auto* gd = fontFace.createGlyph(_key.codepoint); 146 | if (!gd) { return false; } 147 | 148 | unsigned int pad = m_padding; 149 | int w = gd->x1 - gd->x0; 150 | int h = gd->y1 - gd->y0; 151 | int texW = w + pad * 2; 152 | int texH = h + pad * 2; 153 | 154 | if (texW > m_textureSize || texH > m_textureSize) { 155 | return false; 156 | } 157 | 158 | int x, y; 159 | Atlas* atlas = nullptr; 160 | 161 | AtlasID id = 0; 162 | for (auto& a : m_atlas) { 163 | if (a.addRect(texW, texH, &x, &y)) { 164 | atlas = &a; 165 | break; 166 | } 167 | id++; 168 | } 169 | if (!atlas) { 170 | m_atlas.emplace_back(m_textureSize, m_textureSize); 171 | atlas = &m_atlas.back(); 172 | m_textureCb.addTexture(id, m_textureSize, m_textureSize); 173 | 174 | if (!atlas->addRect(texW, texH, &x, &y)) { 175 | // TODO glyph does not fit into atlas size - check beforehand 176 | return false; 177 | } 178 | } 179 | 180 | m_textureCb.addGlyph(id, x, y, w, h, gd->getBuffer(), pad); 181 | 182 | auto glyphItem = atlas->glyphMap.emplace(_key, Glyph(x, y, w + pad * 2, h + pad * 2, 183 | glm::vec2(gd->x0, gd->y0) - float(pad), 184 | glm::vec2(w, h) + float(pad * 2))); 185 | _entry.atlas = id; 186 | _entry.glyph = &(glyphItem.first->second); 187 | 188 | return true; 189 | } 190 | 191 | void GlyphAtlas::clear(AtlasID _atlasId) { 192 | if (_atlasId >= m_atlas.size()) { return; } 193 | 194 | m_atlas[_atlasId].reset(m_textureSize, m_textureSize); 195 | } 196 | 197 | 198 | } 199 | -------------------------------------------------------------------------------- /src/alfons/atlas.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "alfons.h" 4 | #include "glyph.h" 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace alfons { 11 | 12 | struct GlyphKey { 13 | uint32_t font; 14 | uint32_t codepoint; 15 | 16 | GlyphKey(uint32_t font, uint32_t codepoint) 17 | : font(font), codepoint(codepoint) {} 18 | 19 | bool operator==(const GlyphKey& other) const { 20 | return (font == other.font && codepoint == other.codepoint); 21 | } 22 | }; 23 | } 24 | 25 | namespace std { 26 | template <> 27 | struct hash { 28 | std::size_t operator()(const alfons::GlyphKey& k) const { 29 | return ((std::hash()(k.font) ^ 30 | std::hash()(k.codepoint) << 1)) >> 1; 31 | } 32 | }; 33 | } 34 | 35 | namespace alfons { 36 | 37 | class Atlas { 38 | struct Node { 39 | int x, y, width; 40 | }; 41 | 42 | public: 43 | Atlas(int w, int h); 44 | 45 | bool addRect(int rw, int rh, int* rx, int* ry); 46 | 47 | void expand(int w, int h); 48 | 49 | void reset(int w, int h); 50 | 51 | void addSkylineLevel(uint32_t idx, int x, int y, int w, int h); 52 | 53 | int rectFits(uint32_t i, int w, int h); 54 | 55 | int width, height; 56 | std::vector nodes; 57 | 58 | std::unordered_map glyphMap; 59 | }; 60 | 61 | using AtlasID = size_t; 62 | 63 | struct AtlasGlyph { 64 | AtlasID atlas; 65 | Glyph* glyph; 66 | }; 67 | 68 | class LineLayout; 69 | class Font; 70 | 71 | class GlyphAtlas { 72 | 73 | public: 74 | GlyphAtlas(TextureCallback& _textureCb, uint16_t _textureSize = 512, int _glyphPadding = 1) 75 | : m_textureSize(_textureSize), 76 | m_padding(_glyphPadding), 77 | m_textureCb(_textureCb) {} 78 | 79 | bool prepare(LineLayout& lineLayout); 80 | bool getGlyph(const Font& font, const GlyphKey& key, AtlasGlyph& entry); 81 | 82 | bool createGlyph(const Font& font, const GlyphKey& key, AtlasGlyph& entry); 83 | 84 | void clear(AtlasID atlasId); 85 | private: 86 | std::vector m_atlas; 87 | 88 | int m_textureSize; 89 | int m_padding; 90 | 91 | TextureCallback& m_textureCb; 92 | 93 | }; 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/alfons/font.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "font.h" 12 | #include "logger.h" 13 | 14 | namespace alfons { 15 | 16 | Font::Font(const Properties& properties) 17 | : m_properties(properties) {} 18 | 19 | auto Font::addFace(std::shared_ptr _face, hb_language_t _lang) -> bool { 20 | 21 | if (!_face) { return false; } 22 | 23 | if (_lang == HB_LANGUAGE_INVALID) { 24 | m_faces.push_back(_face); 25 | return true; 26 | } 27 | 28 | for (auto& face : m_fontFaceMap[_lang]) { 29 | if (face == _face) { return false; } 30 | } 31 | 32 | m_fontFaceMap[_lang].push_back(_face); 33 | return true; 34 | } 35 | 36 | auto Font::getFontSet(hb_language_t lang) const -> const Faces& { 37 | if (lang == HB_LANGUAGE_INVALID) { 38 | return m_faces; 39 | } 40 | 41 | auto it = m_fontFaceMap.find(lang); 42 | if (it == m_fontFaceMap.end()) { 43 | return m_faces; 44 | } 45 | 46 | return it->second; 47 | } 48 | 49 | void Font::addFaces(const Font& _other) { 50 | m_faces.insert(m_faces.end(), _other.m_faces.begin(), _other.m_faces.end()); 51 | } 52 | 53 | auto Font::styleStringToEnum(const std::string& style) -> Font::Style { 54 | if (style == "bold") 55 | return Font::Style::bold; 56 | if (style == "italic") 57 | return Font::Style::italic; 58 | if (style == "bold-italic") 59 | return Font::Style::bold_italic; 60 | 61 | return Font::Style::regular; 62 | } 63 | 64 | auto Font::styleEnumToString(Font::Style style) -> std::string { 65 | switch (style) { 66 | case Font::Style::bold: 67 | return "bold"; 68 | case Font::Style::italic: 69 | return "italic"; 70 | case Font::Style::bold_italic: 71 | return "bold-italic"; 72 | case Font::Style::regular: 73 | default: 74 | return "regular"; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/alfons/font.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "fontFace.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace alfons { 21 | 22 | class Font { 23 | 24 | public: 25 | using Faces = std::vector>; 26 | 27 | enum class Style { 28 | regular, 29 | bold, 30 | italic, 31 | bold_italic 32 | }; 33 | 34 | struct Properties { 35 | float baseSize; 36 | Style style; 37 | 38 | Properties(float _baseSize, Style _style = Style::regular) 39 | : baseSize(_baseSize), style(_style) {} 40 | 41 | bool operator<(const Properties& rhs) const { 42 | return std::tie(baseSize, style) < std::tie(rhs.baseSize, rhs.style); 43 | } 44 | }; 45 | 46 | Font(const Properties& properties); 47 | 48 | float size() const { return m_properties.baseSize; } 49 | 50 | bool addFace(std::shared_ptr face, hb_language_t lang = HB_LANGUAGE_INVALID); 51 | 52 | static Style styleStringToEnum(const std::string& style); 53 | static std::string styleEnumToString(Style style); 54 | 55 | const Faces& getFontSet(hb_language_t lang) const; 56 | 57 | const FontFace& face(FaceID _faceId) const { 58 | for (size_t id = 0; id < m_faces.size(); id++) { 59 | if (m_faces[id]->id() == _faceId) { return *m_faces[id]; } 60 | } 61 | assert(false); 62 | return *m_faces[0]; 63 | }; 64 | 65 | auto& faces() const { return m_faces; }; 66 | 67 | bool hasFaces() const { return !m_faces.empty(); }; 68 | 69 | int maxFaceId() const { return m_faces.size()-1; }; 70 | 71 | void addFaces(const Font& _other); 72 | 73 | const std::map& fontFaceMap() { return m_fontFaceMap; } 74 | 75 | protected: 76 | Properties m_properties; 77 | 78 | Faces m_faces; 79 | std::map m_fontFaceMap; 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/alfons/fontFace.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "alfons.h" 12 | #include "fontFace.h" 13 | #include "lineLayout.h" 14 | #include "logger.h" 15 | 16 | #include 17 | 18 | 19 | 20 | static const FT_ULong SPACE_SEPARATORS[] = { 21 | 0x0020, 0x00A0, 0x1680, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 22 | 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000}; 23 | 24 | static const size_t SPACE_SEPARATORS_COUNT = sizeof(SPACE_SEPARATORS) / sizeof(FT_ULong); 25 | 26 | namespace alfons { 27 | 28 | // See http://www.microsoft.com/typography/otspec/name.htm for a list of some 29 | // possible platform-encoding pairs. We're interested in 0-3 aka 3-1 - UCS-2. 30 | // Otherwise, fail. If a font has some unicode map, but lacks UCS-2 - it is a 31 | // broken or irrelevant font. What exactly Freetype will select on face load 32 | // (it promises most wide unicode, and if that will be slower that UCS-2 - 33 | // left as an excercise to check.) 34 | 35 | FT_Error FontFace::force_ucs2_charmap(FT_Face face) { 36 | for (int i = 0; i < face->num_charmaps; i++) { 37 | auto platform_id = face->charmaps[i]->platform_id; 38 | auto encoding_id = face->charmaps[i]->encoding_id; 39 | 40 | if (((platform_id == 0) && (encoding_id == 3)) || 41 | ((platform_id == 3) && (encoding_id == 1))) { 42 | return FT_Set_Charmap(face, face->charmaps[i]); 43 | } 44 | } 45 | 46 | return -1; 47 | } 48 | 49 | FontFace::FontFace(FreetypeHelper& _ft, FaceID _faceId, 50 | const Descriptor& _descriptor, float _baseSize) 51 | : m_ft(_ft), m_id(_faceId), m_descriptor(_descriptor), 52 | m_baseSize(_baseSize * _descriptor.scale), 53 | m_loaded(false), 54 | m_invalid(false), 55 | m_ftFace(nullptr), 56 | m_hbFont(nullptr) { 57 | } 58 | 59 | FontFace::~FontFace() { 60 | unload(); 61 | } 62 | 63 | bool FontFace::isSpace(hb_codepoint_t codepoint) const { 64 | for (auto cp : m_spaceSeparators) { 65 | if (cp == codepoint) { return true; } 66 | } 67 | return false; 68 | } 69 | 70 | hb_codepoint_t 71 | FontFace::getCodepoint(FT_ULong charCode) const { 72 | if (m_ftFace) { 73 | return (hb_codepoint_t)FT_Get_Char_Index(m_ftFace, charCode); 74 | } else { 75 | return 0; 76 | } 77 | } 78 | 79 | std::string 80 | FontFace::getFullName() const { 81 | if (m_ftFace) { 82 | return std::string(m_ftFace->family_name) + " " + m_ftFace->style_name; 83 | } else { 84 | return ""; 85 | } 86 | } 87 | 88 | bool FontFace::load() { 89 | if (m_loaded) { return true; } 90 | if (m_invalid) { return false; } 91 | 92 | if (!m_descriptor.source.isValid() || m_descriptor.source.isSystemFont()) { 93 | m_invalid = true; 94 | return false; 95 | } 96 | 97 | FT_Error error; 98 | 99 | if (m_descriptor.source.hasSourceCallback()) { 100 | if (!m_descriptor.source.resolveSource()) { 101 | LOGE("Invalid data loaded by source callback"); 102 | m_invalid = true; 103 | return false; 104 | } 105 | } 106 | 107 | if (m_descriptor.source.isUri()) { 108 | error = FT_New_Face(m_ft.getLib(), 109 | m_descriptor.source.uri().c_str(), 110 | m_descriptor.faceIndex, &m_ftFace); 111 | if (error) { 112 | LOGE("Missing font: error: %d %s", error, m_descriptor.source.uri()); 113 | m_invalid = true; 114 | return false; 115 | } 116 | 117 | } else { 118 | auto& buffer = m_descriptor.source.buffer(); 119 | error = FT_New_Memory_Face(m_ft.getLib(), reinterpret_cast(buffer.data()), 120 | buffer.size(), m_descriptor.faceIndex, &m_ftFace); 121 | if (error) { 122 | LOGE("Could not create font: error: %d", error); 123 | m_invalid = true; 124 | return false; 125 | } 126 | } 127 | 128 | if (force_ucs2_charmap(m_ftFace)) { 129 | LOGE("Font is broken or irrelevant..."); 130 | // ...but DroisSansJapan still seems to work! 131 | // FT_Done_Face(m_ftFace); 132 | // m_ftFace = nullptr; 133 | // return false; 134 | } 135 | 136 | // Docs for Pixels, points and device resolutions 137 | // http://www.freetype.org/freetype2/docs/glyphs/glyphs-2.html 138 | // - 1 point equals 1/72th of an inch 139 | // - pixel_size = point_size * resolution_dpi / 72 140 | // - coordinates are expressed in 1/64th of a pixel 141 | // (also known as 26.6 fixed-point numbers). 142 | 143 | // This basiclly sets the pixel size since dpi == 72 144 | int dpi = 72; 145 | FT_Set_Char_Size(m_ftFace, 146 | m_baseSize * 64, // char_width in 26.6 fixed-point 147 | m_baseSize * 64, // char_height in 26.6 fixed-point 148 | dpi, // horizontal_resolution 149 | dpi); // vertical_resolution 150 | 151 | 152 | // This must take place after ftFace is properly scaled and transformed 153 | m_hbFont = hb_ft_font_create(m_ftFace, nullptr); 154 | 155 | m_metrics.height = m_ftFace->size->metrics.height / 64.f; 156 | m_metrics.ascent = m_ftFace->size->metrics.ascender / 64.f; 157 | m_metrics.descent = -m_ftFace->size->metrics.descender / 64.f; 158 | 159 | m_metrics.lineThickness = m_ftFace->underline_thickness / 64.f; 160 | m_metrics.underlineOffset = -m_ftFace->underline_position / 64.f; 161 | 162 | // auto os2 = static_cast(FT_Get_Sfnt_Table(ftFace, ft_sfnt_os2)); 163 | // if (os2 && (os2->version != 0xFFFF) && (os2->yStrikeoutPosition != 0)) { 164 | // metrics.strikethroughOffset = FT_MulFix(os2->yStrikeoutPosition, 165 | // ftFace->size->metrics.y_scale) * 166 | // scale.y; 167 | // } else { 168 | // metrics.strikethroughOffset = 0.5f * (metrics.ascent - metrics.descent); 169 | // } 170 | 171 | // The following is necessary because the codepoints provided by 172 | // freetype for space-separators are somehow not unicode values, e.g. 173 | // depending on the font, the codepoint for a "regular space" can be 174 | // 2 or 3 (instead of 32) 175 | if (m_spaceSeparators.empty()) { 176 | for (size_t i = 0; i < SPACE_SEPARATORS_COUNT; i++) { 177 | auto codepoint = static_cast( 178 | FT_Get_Char_Index(m_ftFace, SPACE_SEPARATORS[i])); 179 | 180 | if (codepoint) { 181 | if (std::find(m_spaceSeparators.begin(), 182 | m_spaceSeparators.end(), 183 | codepoint) == m_spaceSeparators.end()) { 184 | m_spaceSeparators.push_back(codepoint); 185 | } 186 | } 187 | } 188 | } 189 | 190 | LOGI("LOADED Font: %s size: %d", getFullName(), m_baseSize); 191 | 192 | m_loaded = true; 193 | return true; 194 | } 195 | 196 | void FontFace::unload() { 197 | if (m_loaded) { 198 | m_loaded = false; 199 | 200 | hb_font_destroy(m_hbFont); 201 | m_hbFont = nullptr; 202 | 203 | FT_Done_Face(m_ftFace); 204 | m_ftFace = nullptr; 205 | } 206 | } 207 | 208 | const GlyphData* FontFace::createGlyph(hb_codepoint_t codepoint) const { 209 | 210 | if (!m_loaded) 211 | return nullptr; 212 | 213 | // FIXME mutex 214 | 215 | return m_ft.loadGlyph(m_ftFace, codepoint); 216 | } 217 | 218 | 219 | } 220 | -------------------------------------------------------------------------------- /src/alfons/fontFace.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "freetypeHelper.h" 14 | #include "glyph.h" 15 | #include "inputSource.h" 16 | 17 | #include 18 | 19 | #include "hb.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace alfons { 26 | 27 | class Alfons; 28 | struct GlyphData; 29 | struct Shape; 30 | 31 | using FaceID = uint16_t; 32 | 33 | class FontFace { 34 | public: 35 | struct Descriptor { 36 | InputSource source; 37 | int faceIndex; 38 | float scale; 39 | 40 | Descriptor(InputSource source, int faceIndex = 0, float scale = 1) 41 | : source(source), 42 | faceIndex(faceIndex), 43 | scale(scale) {} 44 | }; 45 | 46 | struct Key { 47 | std::string uri; 48 | int faceIndex; 49 | float baseSize; 50 | 51 | Key(const Descriptor& descriptor, float baseSize) 52 | : uri(descriptor.source.uri()), 53 | faceIndex(descriptor.faceIndex), 54 | baseSize(baseSize * descriptor.scale) {} 55 | 56 | bool operator<(const Key& rhs) const { 57 | return tie(uri, faceIndex, baseSize) < 58 | tie(rhs.uri, rhs.faceIndex, rhs.baseSize); 59 | } 60 | }; 61 | 62 | struct Metrics { 63 | float height = 0; 64 | float ascent = 0; 65 | float descent = 0; 66 | float underlineOffset = 0; 67 | float strikethroughOffset = 0; 68 | float lineThickness = 0; 69 | 70 | Metrics& operator*(float s) { 71 | height *= s; 72 | ascent *= s; 73 | descent *= s; 74 | lineThickness *= s; 75 | underlineOffset *= s; 76 | strikethroughOffset *= s; 77 | return *this; 78 | } 79 | }; 80 | 81 | 82 | FontFace(FreetypeHelper& _ft, FaceID faceId, 83 | const Descriptor& descriptor, float baseSize); 84 | 85 | virtual ~FontFace(); 86 | 87 | bool isSpace(hb_codepoint_t codepoint) const; 88 | hb_codepoint_t getCodepoint(FT_ULong charCode) const; 89 | std::string getFullName() const; 90 | 91 | virtual bool load(); 92 | void unload(); 93 | 94 | const GlyphData* createGlyph(hb_codepoint_t codepoint) const; 95 | 96 | FaceID id() const { return m_id; } 97 | 98 | hb_font_t* hbFont() const { return m_hbFont; } 99 | 100 | const Metrics& metrics() const { return m_metrics; } 101 | 102 | const std::vector& scripts() const { 103 | return m_scripts; 104 | } 105 | const std::vector& languages() const { 106 | return m_languages; 107 | } 108 | 109 | Descriptor& descriptor() { 110 | return m_descriptor; 111 | } 112 | 113 | protected: 114 | 115 | FreetypeHelper& m_ft; 116 | 117 | const FaceID m_id; 118 | 119 | Descriptor m_descriptor; 120 | float m_baseSize; 121 | 122 | Metrics m_metrics; 123 | bool m_loaded; 124 | bool m_invalid; 125 | 126 | FT_Face m_ftFace; 127 | hb_font_t* m_hbFont; 128 | 129 | std::vector m_spaceSeparators; 130 | 131 | std::vector m_scripts; 132 | std::vector m_languages; 133 | 134 | static FT_Error force_ucs2_charmap(FT_Face face); 135 | }; 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/alfons/fontManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "fontManager.h" 12 | #include "appleFontFace.h" 13 | #include "logger.h" 14 | 15 | #include 16 | #include 17 | 18 | namespace alfons { 19 | 20 | std::shared_ptr FontManager::addFont(std::string _name, Font::Properties _properties, 21 | InputSource _source) { 22 | 23 | auto key = make_pair(_name, _properties); 24 | auto it = m_fonts.find(key); 25 | 26 | if (it != m_fonts.end()) { 27 | return it->second; 28 | } 29 | 30 | auto font = std::make_shared(_properties); 31 | m_fonts.emplace(std::move(key), font); 32 | 33 | if (_source.isValid()) { 34 | auto descriptor = FontFace::Descriptor(_source, 0, 1); 35 | font->addFace(addFontFace(descriptor, _properties.baseSize)); 36 | } 37 | 38 | return font; 39 | } 40 | 41 | std::shared_ptr FontManager::getFont(std::string _name, Font::Properties _properties) { 42 | 43 | auto key = make_pair(_name, _properties); 44 | auto it = m_fonts.find(key); 45 | 46 | if (it != m_fonts.end()) { 47 | return it->second; 48 | } 49 | 50 | auto font = std::make_shared(_properties); 51 | m_fonts.emplace(std::move(key), font); 52 | 53 | return font; 54 | } 55 | 56 | #if 0 57 | std::shared_ptr FontManager::addFont(std::string name, std::string path, 58 | float size, Font::Style style) { 59 | 60 | m_globalMap[make_pair(name, style)] = std::make_pair(path, 0); 61 | 62 | auto properties = Font::Properties(size, Font::Style::regular); 63 | 64 | return getFont(name, properties); 65 | } 66 | 67 | std::shared_ptr FontManager::getFont(const std::string& name, 68 | const Font::Properties& prop) { 69 | 70 | auto key = make_tuple(name, prop); 71 | auto cached = m_shortcuts.find(key); 72 | 73 | if (cached != m_shortcuts.end()) 74 | return cached->second; 75 | 76 | // auto alias = m_aliases.find(name); 77 | // std::string nameOrAlias = (alias == m_aliases.end()) ? name : alias->second; 78 | // auto it = globalMap.find({nameOrAlias, style}); 79 | 80 | auto it = m_globalMap.find({name, prop.style}); 81 | 82 | if (it != m_globalMap.end()) { 83 | float baseSize = prop.baseSize; 84 | 85 | // Trying to use the base-size attribute at the font-config level/ 86 | if (baseSize == 0) { baseSize = it->second.second; } 87 | 88 | std::string& uri = it->second.first; 89 | auto font = getCachedFont(InputSource(uri), 90 | Font::Properties(baseSize, prop.style)); 91 | 92 | // Allowing caching upon further access. 93 | m_shortcuts[key] = font; 94 | return font; 95 | } 96 | 97 | 98 | // Basic system for handling undefined font names and styles. 99 | /*if (m_defaultFont) { 100 | if (name != m_defaultFont) { 101 | auto font = getFont(defaultFontName, prop); 102 | m_shortcuts[key] = font; 103 | return font; 104 | } 105 | if (prop.style != defaultFontStyle) { 106 | 107 | auto font = getFont(defaultFontName, {prop.baseSize, defaultFontStyle}); 108 | m_shortcuts[key] = font; 109 | return font; 110 | } 111 | }*/ 112 | 113 | return nullptr; 114 | } 115 | 116 | std::shared_ptr FontManager::getCachedFont(InputSource source, 117 | const Font::Properties& prop) { 118 | 119 | auto key = make_pair(source.uri(), prop); 120 | auto it = m_fonts.find(key); 121 | 122 | if (it != m_fonts.end()) { return it->second; } 123 | log("getCachedFont: %s", source.uri()); 124 | 125 | auto font = std::make_shared(prop); 126 | 127 | m_fonts.emplace(std::move(key), font); 128 | 129 | auto descriptor = FontFace::Descriptor(source, 0, 1); 130 | 131 | font->addFace(getFontFace(descriptor, prop.baseSize)); 132 | 133 | return font; 134 | } 135 | #endif 136 | 137 | void FontManager::unload(Font& font) { 138 | 139 | // for (auto it = m_shortcuts.begin(); it != m_shortcuts.end(); ++it) { 140 | // if (it->second == font) { m_shortcuts.erase(it); } 141 | // } 142 | // for (auto it = m_fonts.begin(); it != m_fonts.end(); ++it) { 143 | // if (it->second == font) { m_fonts.erase(it); } 144 | // } 145 | 146 | std::set inUse; 147 | 148 | for (auto& font : m_fonts) { 149 | for (auto& entry : font.second->fontFaceMap()) { 150 | for (auto& face : entry.second) { 151 | inUse.insert(face->id()); 152 | } 153 | } 154 | } 155 | 156 | for (auto& face : m_faces) { 157 | if (!inUse.count(face->id())) { 158 | face->unload(); 159 | } 160 | } 161 | } 162 | 163 | void FontManager::unload() { 164 | 165 | for (auto& face : m_faces) { 166 | face->unload(); 167 | } 168 | } 169 | 170 | std::shared_ptr FontManager::addFontFace(const FontFace::Descriptor& descriptor, 171 | float baseSize) { 172 | 173 | if (m_maxFontId == std::numeric_limits::max()) { 174 | LOGE("addFontFace failed: Reached maximum FontFace ID"); 175 | return nullptr; 176 | } 177 | 178 | std::shared_ptr face = nullptr; 179 | 180 | if (descriptor.source.isSystemFont()) { 181 | #ifdef HAVE_CORETEXT 182 | face = std::make_shared(m_ftHelper, m_maxFontId++, descriptor, baseSize); 183 | #else 184 | return nullptr; 185 | #endif 186 | } else { 187 | face = std::make_shared(m_ftHelper, m_maxFontId++, descriptor, baseSize); 188 | } 189 | 190 | m_faces.push_back(face); 191 | 192 | return face; 193 | } 194 | 195 | } 196 | -------------------------------------------------------------------------------- /src/alfons/fontManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "font.h" 14 | #include "inputSource.h" 15 | 16 | namespace alfons { 17 | 18 | class FontManager { 19 | 20 | public: 21 | 22 | ~FontManager() {} 23 | 24 | 25 | std::shared_ptr addFont(std::string _name, Font::Properties _properties, InputSource _source = {}); 26 | 27 | std::shared_ptr getFont(std::string _name, Font::Properties _properties); 28 | 29 | #if 0 30 | std::shared_ptr addFont(std::string name, std::string path, float baseSize, 31 | Font::Style style = Font::Style::regular); 32 | 33 | std::shared_ptr addFontFromMemory(std::string name, unsigned char* blob, size_t dataSize, 34 | float size, Font::Style style = Font::Style::regular); 35 | 36 | Sets the font fallback stack, ordered by priorities (vector.begin() more important than vector.end()) 37 | void setFallbackStack(std::vector> fontfallbacks); 38 | 39 | Get the font for a given name, returns nullptr if no font for the given name & properties is found 40 | std::shared_ptr getFont(const std::string& name, 41 | const Font::Properties& props = Font::Properties(0, Font::Style::regular)); 42 | 43 | std::shared_ptr getCachedFont(InputSource source, 44 | const Font::Properties& properties); 45 | #endif 46 | 47 | void unload(Font& font); 48 | 49 | void unload(); 50 | 51 | std::shared_ptr addFontFace(const FontFace::Descriptor& descriptor, float baseSize); 52 | 53 | 54 | private: 55 | FreetypeHelper m_ftHelper; 56 | 57 | FaceID m_maxFontId = 0; 58 | 59 | #if 0 60 | // Font face name and style 61 | using FontFaceKey = std::pair; 62 | 63 | // Path and base-size 64 | using FontFaceEntry = std::pair; 65 | 66 | std::map m_globalMap; 67 | 68 | // Font name, Style and Properties 69 | using ShortcutKey = std::tuple; 70 | 71 | // 72 | std::map> m_shortcuts; 73 | 74 | // // Alias -> font name 75 | // std::map m_aliases; 76 | #endif 77 | 78 | // Path and Properties 79 | using FontKey = std::pair; 80 | 81 | std::map> m_fonts; 82 | 83 | std::vector> m_faces; 84 | 85 | }; 86 | } 87 | -------------------------------------------------------------------------------- /src/alfons/freetypeHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaptation to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | #include FT_GLYPH_H 17 | #include FT_TRUETYPE_TABLES_H 18 | 19 | namespace alfons { 20 | 21 | // WARNING: 22 | // If the GlyphData is not for "immediate consumption", invoke 23 | // getBufferCopy() otherwise, the data will become 24 | // corrupted with the next FT_Get_Glyph() operation. 25 | 26 | struct GlyphData { 27 | int x0, y0, x1, y1; 28 | 29 | GlyphData() 30 | : x0(0), y0(0), x1(0), y1(0), 31 | ftGlyph(nullptr), 32 | ftSlot(nullptr) {} 33 | 34 | bool isValid() const { return bool(ftGlyph); } 35 | 36 | unsigned char* getBuffer() const { 37 | if (ftGlyph) 38 | return ftSlot->bitmap.buffer; 39 | 40 | return nullptr; 41 | } 42 | 43 | std::vector getBufferCopy() { 44 | if (!ftGlyph) 45 | return {}; 46 | 47 | int dataSize = (x1 - x0) * (y1 - y0); 48 | std::vector data; 49 | data.resize(dataSize); 50 | 51 | memcpy(&data[0], ftSlot->bitmap.buffer, dataSize); 52 | FT_Done_Glyph(ftGlyph); 53 | ftGlyph = nullptr; 54 | 55 | return data; 56 | } 57 | 58 | bool loadGlyph(FT_Face ftFace, FT_UInt codepoint) { 59 | if (ftGlyph) { 60 | FT_Done_Glyph(ftGlyph); 61 | ftGlyph = nullptr; 62 | } 63 | 64 | if (codepoint == 0) 65 | return false; 66 | 67 | if (FT_Load_Glyph(ftFace, codepoint, 68 | FT_LOAD_DEFAULT | FT_LOAD_FORCE_AUTOHINT) != 0) 69 | return false; 70 | 71 | ftSlot = ftFace->glyph; 72 | 73 | if (FT_Get_Glyph(ftSlot, &ftGlyph) != 0) 74 | return false; 75 | 76 | FT_Render_Glyph(ftSlot, FT_RENDER_MODE_NORMAL); 77 | 78 | if (ftSlot->bitmap.width <= 0 || ftSlot->bitmap.rows <= 0) { 79 | FT_Done_Glyph(ftGlyph); 80 | ftGlyph = nullptr; 81 | return false; 82 | } 83 | x0 = ftSlot->bitmap_left; 84 | x1 = x0 + ftSlot->bitmap.width; 85 | y0 = -ftSlot->bitmap_top; 86 | y1 = y0 + ftSlot->bitmap.rows; 87 | 88 | return true; 89 | } 90 | 91 | FT_Glyph ftGlyph; 92 | 93 | // Slot belongs to most recently used ftFace! 94 | FT_GlyphSlot ftSlot; 95 | }; 96 | 97 | class FreetypeHelper { 98 | 99 | GlyphData glyphData; 100 | 101 | FT_Library library; 102 | 103 | public: 104 | FreetypeHelper() { 105 | FT_Init_FreeType(&library); 106 | } 107 | 108 | ~FreetypeHelper() { 109 | FT_Done_FreeType(library); 110 | } 111 | 112 | FT_Library getLib() const { return library; } 113 | 114 | const GlyphData* loadGlyph(FT_Face ftFace, FT_UInt codepoint) { 115 | if (!glyphData.loadGlyph(ftFace, codepoint)) 116 | return nullptr; 117 | 118 | return &glyphData; 119 | } 120 | 121 | }; 122 | 123 | } 124 | -------------------------------------------------------------------------------- /src/alfons/glyph.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace alfons { 16 | 17 | struct Glyph { 18 | Glyph(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, glm::vec2 offset, glm::vec2 size) 19 | : offset(offset), size(size) { 20 | u1 = x0; 21 | v1 = y0; 22 | u2 = x0 + x1; 23 | v2 = y0 + y1; 24 | } 25 | 26 | glm::vec2 offset; 27 | glm::vec2 size; 28 | 29 | uint16_t u1; 30 | uint16_t v1; 31 | uint16_t u2; 32 | uint16_t v2; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/alfons/inputSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace alfons { 19 | 20 | using LoadSourceHandle = std::function()>; 21 | 22 | class InputSource { 23 | public: 24 | 25 | InputSource() {} 26 | 27 | InputSource(const std::string& _uri, bool systemFontName = false) 28 | : m_uri(_uri), m_data(std::make_shared()), m_systemFontName(systemFontName) {} 29 | 30 | explicit InputSource(LoadSourceHandle _loadSource) 31 | : m_data(std::make_shared(_loadSource)) {} 32 | 33 | explicit InputSource(const std::vector& _data) 34 | : m_data(std::make_shared(_data)) {} 35 | 36 | explicit InputSource(std::vector&& _data) 37 | : m_data(std::make_shared(std::move(_data))) {} 38 | 39 | explicit InputSource(const char* data, size_t len) 40 | : m_data(std::make_shared(std::vector{data, data + len})) {} 41 | 42 | const std::string& uri() const { return m_uri; } 43 | 44 | const std::vector& buffer() const { 45 | return m_data->buffer; 46 | } 47 | 48 | bool isUri() const { return !m_systemFontName && !m_uri.empty(); } 49 | 50 | bool isSystemFont() const { return m_systemFontName; } 51 | 52 | bool hasSourceCallback() { return m_data && bool(m_data->loadSource); } 53 | 54 | bool resolveSource() { 55 | if (!m_data || !bool(m_data->loadSource)) { 56 | return false; 57 | } 58 | 59 | if (!m_data->buffer.empty()) { 60 | return true; 61 | } 62 | 63 | m_data->buffer = m_data->loadSource(); 64 | 65 | if (m_data->buffer.empty()) { 66 | return false; 67 | } 68 | 69 | return true; 70 | } 71 | 72 | bool isValid() { 73 | if (!m_uri.empty()) { return true; } 74 | 75 | if (m_data) { 76 | if (!m_data->buffer.empty()) { return true; } 77 | 78 | 79 | if (resolveSource()) { 80 | return true; 81 | } 82 | } 83 | return false; 84 | } 85 | 86 | void setData(std::vector buffer) { 87 | std::swap(m_data->buffer, buffer); 88 | } 89 | 90 | bool hasData() { return bool(m_data) && !m_data->buffer.empty(); } 91 | 92 | void clearData() { m_data->buffer.clear(); } 93 | 94 | protected: 95 | std::string m_uri = ""; 96 | 97 | struct Data { 98 | Data() {} 99 | explicit Data(const std::vector& buffer) : buffer(buffer), loadSource(nullptr) {} 100 | explicit Data(std::vector&& buffer) : buffer(std::move(buffer)), loadSource(nullptr) {} 101 | explicit Data(LoadSourceHandle source) : buffer(), loadSource(source) {} 102 | 103 | std::vector buffer; 104 | LoadSourceHandle loadSource; 105 | }; 106 | 107 | std::shared_ptr m_data; 108 | 109 | bool m_systemFontName = false; 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /src/alfons/langHelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | /* 12 | * Includes data from pango: 13 | * https://developer.gnome.org/pango/stable/pango-Scripts-and-Languages.html 14 | */ 15 | 16 | #include "langHelper.h" 17 | 18 | #include 19 | 20 | namespace alfons { 21 | const std::string DEFAULT_LANGUAGES = "en:zh-cn"; // GIVING PRIORITY (BY DEFAULT) TO CHINESE OVER JAPANESE 22 | 23 | struct HBScriptForLang { 24 | const char lang[7]; 25 | hb_script_t scripts[3]; 26 | }; 27 | 28 | /* 29 | * DATA FROM pango-script-lang-table.h 30 | */ 31 | static const HBScriptForLang HB_SCRIPT_FOR_LANG[] = { 32 | {"aa", {HB_SCRIPT_LATIN /*62*/}}, 33 | {"ab", {HB_SCRIPT_CYRILLIC /*90*/}}, 34 | {"af", {HB_SCRIPT_LATIN /*69*/}}, 35 | {"ak", {HB_SCRIPT_LATIN /*70*/}}, 36 | {"am", {HB_SCRIPT_ETHIOPIC /*264*/}}, 37 | {"an", {HB_SCRIPT_LATIN /*66*/}}, 38 | {"ar", {HB_SCRIPT_ARABIC /*125*/}}, 39 | {"as", {HB_SCRIPT_BENGALI /*64*/}}, 40 | {"ast", {HB_SCRIPT_LATIN /*66*/}}, 41 | {"av", {HB_SCRIPT_CYRILLIC /*67*/}}, 42 | {"ay", {HB_SCRIPT_LATIN /*60*/}}, 43 | {"az-az", {HB_SCRIPT_LATIN /*66*/}}, 44 | {"az-ir", {HB_SCRIPT_ARABIC /*129*/}}, 45 | {"ba", {HB_SCRIPT_CYRILLIC /*82*/}}, 46 | {"be", {HB_SCRIPT_CYRILLIC /*68*/}}, 47 | {"ber-dz", {HB_SCRIPT_LATIN /*70*/}}, 48 | {"ber-ma", {HB_SCRIPT_TIFINAGH /*32*/}}, 49 | {"bg", {HB_SCRIPT_CYRILLIC /*60*/}}, 50 | {"bh", {HB_SCRIPT_DEVANAGARI /*68*/}}, 51 | {"bho", {HB_SCRIPT_DEVANAGARI /*68*/}}, 52 | {"bi", {HB_SCRIPT_LATIN /*58*/}}, 53 | {"bin", {HB_SCRIPT_LATIN /*76*/}}, 54 | {"bm", {HB_SCRIPT_LATIN /*60*/}}, 55 | {"bn", {HB_SCRIPT_BENGALI /*63*/}}, 56 | {"bo", {HB_SCRIPT_TIBETAN /*95*/}}, 57 | {"br", {HB_SCRIPT_LATIN /*64*/}}, 58 | {"bs", {HB_SCRIPT_LATIN /*62*/}}, 59 | {"bua", {HB_SCRIPT_CYRILLIC /*70*/}}, 60 | {"byn", {HB_SCRIPT_ETHIOPIC /*255*/}}, 61 | {"ca", {HB_SCRIPT_LATIN /*74*/}}, 62 | {"ce", {HB_SCRIPT_CYRILLIC /*67*/}}, 63 | {"ch", {HB_SCRIPT_LATIN /*58*/}}, 64 | {"chm", {HB_SCRIPT_CYRILLIC /*76*/}}, 65 | {"chr", {HB_SCRIPT_CHEROKEE /*85*/}}, 66 | {"co", {HB_SCRIPT_LATIN /*84*/}}, 67 | {"crh", {HB_SCRIPT_LATIN /*68*/}}, 68 | {"cs", {HB_SCRIPT_LATIN /*82*/}}, 69 | {"csb", {HB_SCRIPT_LATIN /*74*/}}, 70 | {"cu", {HB_SCRIPT_CYRILLIC /*103*/}}, 71 | {"cv", {HB_SCRIPT_CYRILLIC /*72*/, HB_SCRIPT_LATIN /*2*/}}, 72 | {"cy", {HB_SCRIPT_LATIN /*78*/}}, 73 | {"da", {HB_SCRIPT_LATIN /*70*/}}, 74 | {"de", {HB_SCRIPT_LATIN /*59*/}}, 75 | {"dv", {HB_SCRIPT_THAANA /*49*/}}, 76 | {"dz", {HB_SCRIPT_TIBETAN /*95*/}}, 77 | {"ee", {HB_SCRIPT_LATIN /*96*/}}, 78 | {"el", {HB_SCRIPT_GREEK /*69*/}}, 79 | {"en", {HB_SCRIPT_LATIN /*72*/}}, 80 | {"eo", {HB_SCRIPT_LATIN /*64*/}}, 81 | {"es", {HB_SCRIPT_LATIN /*66*/}}, 82 | {"et", {HB_SCRIPT_LATIN /*64*/}}, 83 | {"eu", {HB_SCRIPT_LATIN /*56*/}}, 84 | {"fa", {HB_SCRIPT_ARABIC /*129*/}}, 85 | {"fat", {HB_SCRIPT_LATIN /*70*/}}, 86 | {"ff", {HB_SCRIPT_LATIN /*62*/}}, 87 | {"fi", {HB_SCRIPT_LATIN /*62*/}}, 88 | {"fil", {HB_SCRIPT_LATIN /*84*/}}, 89 | {"fj", {HB_SCRIPT_LATIN /*52*/}}, 90 | {"fo", {HB_SCRIPT_LATIN /*68*/}}, 91 | {"fr", {HB_SCRIPT_LATIN /*84*/}}, 92 | {"fur", {HB_SCRIPT_LATIN /*66*/}}, 93 | {"fy", {HB_SCRIPT_LATIN /*75*/}}, 94 | {"ga", {HB_SCRIPT_LATIN /*80*/}}, 95 | {"gd", {HB_SCRIPT_LATIN /*70*/}}, 96 | {"gez", {HB_SCRIPT_ETHIOPIC /*218*/}}, 97 | {"gl", {HB_SCRIPT_LATIN /*66*/}}, 98 | {"gn", {HB_SCRIPT_LATIN /*70*/}}, 99 | {"gu", {HB_SCRIPT_GUJARATI /*68*/}}, 100 | {"gv", {HB_SCRIPT_LATIN /*54*/}}, 101 | {"ha", {HB_SCRIPT_LATIN /*60*/}}, 102 | {"haw", {HB_SCRIPT_LATIN /*62*/}}, 103 | {"he", {HB_SCRIPT_HEBREW /*27*/}}, 104 | {"hi", {HB_SCRIPT_DEVANAGARI /*68*/}}, 105 | {"hne", {HB_SCRIPT_DEVANAGARI /*68*/}}, 106 | {"ho", {HB_SCRIPT_LATIN /*52*/}}, 107 | {"hr", {HB_SCRIPT_LATIN /*62*/}}, 108 | {"hsb", {HB_SCRIPT_LATIN /*72*/}}, 109 | {"ht", {HB_SCRIPT_LATIN /*56*/}}, 110 | {"hu", {HB_SCRIPT_LATIN /*70*/}}, 111 | {"hy", {HB_SCRIPT_ARMENIAN /*77*/}}, 112 | {"hz", {HB_SCRIPT_LATIN /*56*/}}, 113 | {"ia", {HB_SCRIPT_LATIN /*52*/}}, 114 | {"id", {HB_SCRIPT_LATIN /*54*/}}, 115 | {"ie", {HB_SCRIPT_LATIN /*52*/}}, 116 | {"ig", {HB_SCRIPT_LATIN /*58*/}}, 117 | {"ii", {HB_SCRIPT_YI /*1165*/}}, 118 | {"ik", {HB_SCRIPT_CYRILLIC /*68*/}}, 119 | {"io", {HB_SCRIPT_LATIN /*52*/}}, 120 | {"is", {HB_SCRIPT_LATIN /*70*/}}, 121 | {"it", {HB_SCRIPT_LATIN /*72*/}}, 122 | {"iu", {HB_SCRIPT_CANADIAN_ABORIGINAL /*161*/}}, 123 | {"ja", {HB_SCRIPT_HAN /*6356*/, HB_SCRIPT_KATAKANA /*88*/, HB_SCRIPT_HIRAGANA /*85*/}}, 124 | {"jv", {HB_SCRIPT_LATIN /*56*/}}, 125 | {"ka", {HB_SCRIPT_GEORGIAN /*33*/}}, 126 | {"kaa", {HB_SCRIPT_CYRILLIC /*78*/}}, 127 | {"kab", {HB_SCRIPT_LATIN /*70*/}}, 128 | {"ki", {HB_SCRIPT_LATIN /*56*/}}, 129 | {"kj", {HB_SCRIPT_LATIN /*52*/}}, 130 | {"kk", {HB_SCRIPT_CYRILLIC /*77*/}}, 131 | {"kl", {HB_SCRIPT_LATIN /*81*/}}, 132 | {"km", {HB_SCRIPT_KHMER /*63*/}}, 133 | {"kn", {HB_SCRIPT_KANNADA /*70*/}}, 134 | {"ko", {HB_SCRIPT_HANGUL /*2443*/}}, 135 | {"kok", {HB_SCRIPT_DEVANAGARI /*68*/}}, 136 | {"kr", {HB_SCRIPT_LATIN /*56*/}}, 137 | {"ks", {HB_SCRIPT_ARABIC /*145*/}}, 138 | {"ku-am", {HB_SCRIPT_CYRILLIC /*64*/}}, 139 | {"ku-iq", {HB_SCRIPT_ARABIC /*32*/}}, 140 | {"ku-ir", {HB_SCRIPT_ARABIC /*32*/}}, 141 | {"ku-tr", {HB_SCRIPT_LATIN /*62*/}}, 142 | {"kum", {HB_SCRIPT_CYRILLIC /*66*/}}, 143 | {"kv", {HB_SCRIPT_CYRILLIC /*70*/}}, 144 | {"kw", {HB_SCRIPT_LATIN /*64*/}}, 145 | {"kwm", {HB_SCRIPT_LATIN /*52*/}}, 146 | {"ky", {HB_SCRIPT_CYRILLIC /*70*/}}, 147 | {"la", {HB_SCRIPT_LATIN /*68*/}}, 148 | {"lb", {HB_SCRIPT_LATIN /*75*/}}, 149 | {"lez", {HB_SCRIPT_CYRILLIC /*67*/}}, 150 | {"lg", {HB_SCRIPT_LATIN /*54*/}}, 151 | {"li", {HB_SCRIPT_LATIN /*62*/}}, 152 | {"ln", {HB_SCRIPT_LATIN /*78*/}}, 153 | {"lo", {HB_SCRIPT_LAO /*55*/}}, 154 | {"lt", {HB_SCRIPT_LATIN /*70*/}}, 155 | {"lv", {HB_SCRIPT_LATIN /*78*/}}, 156 | {"mai", {HB_SCRIPT_DEVANAGARI /*68*/}}, 157 | {"mg", {HB_SCRIPT_LATIN /*56*/}}, 158 | {"mh", {HB_SCRIPT_LATIN /*62*/}}, 159 | {"mi", {HB_SCRIPT_LATIN /*64*/}}, 160 | {"mk", {HB_SCRIPT_CYRILLIC /*42*/}}, 161 | {"ml", {HB_SCRIPT_MALAYALAM /*68*/}}, 162 | {"mn-cn", {HB_SCRIPT_MONGOLIAN /*130*/}}, 163 | {"mn-mn", {HB_SCRIPT_CYRILLIC /*70*/}}, 164 | {"mo", {HB_SCRIPT_CYRILLIC /*66*/, HB_SCRIPT_LATIN /*62*/}}, 165 | {"mr", {HB_SCRIPT_DEVANAGARI /*68*/}}, 166 | {"ms", {HB_SCRIPT_LATIN /*52*/}}, 167 | {"mt", {HB_SCRIPT_LATIN /*72*/}}, 168 | {"my", {HB_SCRIPT_MYANMAR /*48*/}}, 169 | {"na", {HB_SCRIPT_LATIN /*60*/}}, 170 | {"nb", {HB_SCRIPT_LATIN /*70*/}}, 171 | {"nds", {HB_SCRIPT_LATIN /*59*/}}, 172 | {"ne", {HB_SCRIPT_DEVANAGARI /*68*/}}, 173 | {"ng", {HB_SCRIPT_LATIN /*52*/}}, 174 | {"nl", {HB_SCRIPT_LATIN /*82*/}}, 175 | {"nn", {HB_SCRIPT_LATIN /*76*/}}, 176 | {"no", {HB_SCRIPT_LATIN /*70*/}}, 177 | {"nr", {HB_SCRIPT_LATIN /*52*/}}, 178 | {"nso", {HB_SCRIPT_LATIN /*58*/}}, 179 | {"nv", {HB_SCRIPT_LATIN /*70*/}}, 180 | {"ny", {HB_SCRIPT_LATIN /*54*/}}, 181 | {"oc", {HB_SCRIPT_LATIN /*70*/}}, 182 | {"om", {HB_SCRIPT_LATIN /*52*/}}, 183 | {"or", {HB_SCRIPT_ORIYA /*68*/}}, 184 | {"os", {HB_SCRIPT_CYRILLIC /*66*/}}, 185 | {"ota", {HB_SCRIPT_ARABIC /*37*/}}, 186 | {"pa-in", {HB_SCRIPT_GURMUKHI /*63*/}}, 187 | {"pa-pk", {HB_SCRIPT_ARABIC /*145*/}}, 188 | {"pap-an", {HB_SCRIPT_LATIN /*72*/}}, 189 | {"pap-aw", {HB_SCRIPT_LATIN /*54*/}}, 190 | {"pl", {HB_SCRIPT_LATIN /*70*/}}, 191 | {"ps-af", {HB_SCRIPT_ARABIC /*49*/}}, 192 | {"ps-pk", {HB_SCRIPT_ARABIC /*49*/}}, 193 | {"pt", {HB_SCRIPT_LATIN /*82*/}}, 194 | {"qu", {HB_SCRIPT_LATIN /*54*/}}, 195 | {"rm", {HB_SCRIPT_LATIN /*66*/}}, 196 | {"rn", {HB_SCRIPT_LATIN /*52*/}}, 197 | {"ro", {HB_SCRIPT_LATIN /*62*/}}, 198 | {"ru", {HB_SCRIPT_CYRILLIC /*66*/}}, 199 | {"rw", {HB_SCRIPT_LATIN /*52*/}}, 200 | {"sa", {HB_SCRIPT_DEVANAGARI /*68*/}}, 201 | {"sah", {HB_SCRIPT_CYRILLIC /*76*/}}, 202 | {"sc", {HB_SCRIPT_LATIN /*62*/}}, 203 | {"sco", {HB_SCRIPT_LATIN /*56*/}}, 204 | {"sd", {HB_SCRIPT_ARABIC /*54*/}}, 205 | {"se", {HB_SCRIPT_LATIN /*66*/}}, 206 | {"sel", {HB_SCRIPT_CYRILLIC /*66*/}}, 207 | {"sg", {HB_SCRIPT_LATIN /*72*/}}, 208 | {"sh", {HB_SCRIPT_CYRILLIC /*94*/, HB_SCRIPT_LATIN /*62*/}}, 209 | {"shs", {HB_SCRIPT_LATIN /*46*/}}, 210 | {"si", {HB_SCRIPT_SINHALA /*73*/}}, 211 | {"sid", {HB_SCRIPT_ETHIOPIC /*281*/}}, 212 | {"sk", {HB_SCRIPT_LATIN /*86*/}}, 213 | {"sl", {HB_SCRIPT_LATIN /*62*/}}, 214 | {"sm", {HB_SCRIPT_LATIN /*52*/}}, 215 | {"sma", {HB_SCRIPT_LATIN /*60*/}}, 216 | {"smj", {HB_SCRIPT_LATIN /*60*/}}, 217 | {"smn", {HB_SCRIPT_LATIN /*68*/}}, 218 | {"sms", {HB_SCRIPT_LATIN /*80*/}}, 219 | {"sn", {HB_SCRIPT_LATIN /*52*/}}, 220 | {"so", {HB_SCRIPT_LATIN /*52*/}}, 221 | {"sq", {HB_SCRIPT_LATIN /*56*/}}, 222 | {"sr", {HB_SCRIPT_CYRILLIC /*60*/}}, 223 | {"ss", {HB_SCRIPT_LATIN /*52*/}}, 224 | {"st", {HB_SCRIPT_LATIN /*52*/}}, 225 | {"su", {HB_SCRIPT_LATIN /*54*/}}, 226 | {"sv", {HB_SCRIPT_LATIN /*68*/}}, 227 | {"sw", {HB_SCRIPT_LATIN /*52*/}}, 228 | {"syr", {HB_SCRIPT_SYRIAC /*45*/}}, 229 | {"ta", {HB_SCRIPT_TAMIL /*48*/}}, 230 | {"te", {HB_SCRIPT_TELUGU /*70*/}}, 231 | {"tg", {HB_SCRIPT_CYRILLIC /*78*/}}, 232 | {"th", {HB_SCRIPT_THAI /*73*/}}, 233 | {"ti-er", {HB_SCRIPT_ETHIOPIC /*255*/}}, 234 | {"ti-et", {HB_SCRIPT_ETHIOPIC /*281*/}}, 235 | {"tig", {HB_SCRIPT_ETHIOPIC /*221*/}}, 236 | {"tk", {HB_SCRIPT_LATIN /*68*/}}, 237 | {"tl", {HB_SCRIPT_LATIN /*84*/}}, 238 | {"tn", {HB_SCRIPT_LATIN /*58*/}}, 239 | {"to", {HB_SCRIPT_LATIN /*52*/}}, 240 | {"tr", {HB_SCRIPT_LATIN /*70*/}}, 241 | {"ts", {HB_SCRIPT_LATIN /*52*/}}, 242 | {"tt", {HB_SCRIPT_CYRILLIC /*76*/}}, 243 | {"tw", {HB_SCRIPT_LATIN /*70*/}}, 244 | {"ty", {HB_SCRIPT_LATIN /*64*/}}, 245 | {"tyv", {HB_SCRIPT_CYRILLIC /*70*/}}, 246 | {"ug", {HB_SCRIPT_ARABIC /*125*/}}, 247 | {"uk", {HB_SCRIPT_CYRILLIC /*72*/}}, 248 | {"ur", {HB_SCRIPT_ARABIC /*145*/}}, 249 | {"uz", {HB_SCRIPT_LATIN /*52*/}}, 250 | {"ve", {HB_SCRIPT_LATIN /*62*/}}, 251 | {"vi", {HB_SCRIPT_LATIN /*186*/}}, 252 | {"vo", {HB_SCRIPT_LATIN /*54*/}}, 253 | {"vot", {HB_SCRIPT_LATIN /*62*/}}, 254 | {"wa", {HB_SCRIPT_LATIN /*70*/}}, 255 | {"wal", {HB_SCRIPT_ETHIOPIC /*281*/}}, 256 | {"wen", {HB_SCRIPT_LATIN /*76*/}}, 257 | {"wo", {HB_SCRIPT_LATIN /*66*/}}, 258 | {"xh", {HB_SCRIPT_LATIN /*52*/}}, 259 | {"yap", {HB_SCRIPT_LATIN /*58*/}}, 260 | {"yi", {HB_SCRIPT_HEBREW /*27*/}}, 261 | {"yo", {HB_SCRIPT_LATIN /*114*/}}, 262 | {"za", {HB_SCRIPT_LATIN /*52*/}}, 263 | {"zh-cn", {HB_SCRIPT_HAN /*6763*/}}, 264 | {"zh-hk", {HB_SCRIPT_HAN /*2213*/}}, 265 | {"zh-mo", {HB_SCRIPT_HAN /*2213*/}}, 266 | {"zh-sg", {HB_SCRIPT_HAN /*6763*/}}, 267 | {"zh-tw", {HB_SCRIPT_HAN /*13063*/}}, 268 | {"zu", {HB_SCRIPT_LATIN /*52*/}}}; 269 | 270 | static void 271 | initScriptMap(std::map>& scriptMap) { 272 | size_t entryCount = sizeof(HB_SCRIPT_FOR_LANG) / sizeof(HBScriptForLang); 273 | 274 | for (size_t i = 0; i < entryCount; i++) { 275 | auto entry = HB_SCRIPT_FOR_LANG[i]; 276 | 277 | size_t scriptCount = sizeof(entry.scripts) / sizeof(hb_script_t); 278 | std::vector scripts; 279 | 280 | for (size_t j = 0; j < scriptCount; j++) { 281 | if (entry.scripts[j]) { 282 | scripts.push_back(entry.scripts[j]); 283 | } else { 284 | break; 285 | } 286 | } 287 | 288 | assert(scripts.size() > 0); 289 | scriptMap[entry.lang] = scripts; 290 | } 291 | 292 | /* 293 | * DEFAULT-VALUE 294 | */ 295 | std::vector invalid; 296 | invalid.push_back(HB_SCRIPT_INVALID); 297 | scriptMap[""] = invalid; 298 | } 299 | 300 | static void 301 | initSampleLanguageMap(std::map& sampleLanguageMap) { 302 | sampleLanguageMap[HB_SCRIPT_ARABIC] = "ar"; 303 | sampleLanguageMap[HB_SCRIPT_ARMENIAN] = "hy"; 304 | sampleLanguageMap[HB_SCRIPT_BENGALI] = "bn"; 305 | sampleLanguageMap[HB_SCRIPT_CHEROKEE] = "chr"; 306 | sampleLanguageMap[HB_SCRIPT_COPTIC] = "cop"; 307 | sampleLanguageMap[HB_SCRIPT_CYRILLIC] = "ru"; 308 | sampleLanguageMap[HB_SCRIPT_DEVANAGARI] = "hi"; 309 | sampleLanguageMap[HB_SCRIPT_ETHIOPIC] = "am"; 310 | sampleLanguageMap[HB_SCRIPT_GEORGIAN] = "ka"; 311 | sampleLanguageMap[HB_SCRIPT_GREEK] = "el"; 312 | sampleLanguageMap[HB_SCRIPT_GUJARATI] = "gu"; 313 | sampleLanguageMap[HB_SCRIPT_GURMUKHI] = "pa"; 314 | sampleLanguageMap[HB_SCRIPT_HANGUL] = "ko"; 315 | sampleLanguageMap[HB_SCRIPT_HEBREW] = "he"; 316 | sampleLanguageMap[HB_SCRIPT_HIRAGANA] = "ja"; 317 | sampleLanguageMap[HB_SCRIPT_KANNADA] = "kn"; 318 | sampleLanguageMap[HB_SCRIPT_KATAKANA] = "ja"; 319 | sampleLanguageMap[HB_SCRIPT_KHMER] = "km"; 320 | sampleLanguageMap[HB_SCRIPT_LAO] = "lo"; 321 | sampleLanguageMap[HB_SCRIPT_LATIN] = "en"; 322 | sampleLanguageMap[HB_SCRIPT_MALAYALAM] = "ml"; 323 | sampleLanguageMap[HB_SCRIPT_MONGOLIAN] = "mn"; 324 | sampleLanguageMap[HB_SCRIPT_MYANMAR] = "my"; 325 | sampleLanguageMap[HB_SCRIPT_ORIYA] = "or"; 326 | sampleLanguageMap[HB_SCRIPT_SINHALA] = "si"; 327 | sampleLanguageMap[HB_SCRIPT_SYRIAC] = "syr"; 328 | sampleLanguageMap[HB_SCRIPT_TAMIL] = "ta"; 329 | sampleLanguageMap[HB_SCRIPT_TELUGU] = "te"; 330 | sampleLanguageMap[HB_SCRIPT_THAANA] = "dv"; 331 | sampleLanguageMap[HB_SCRIPT_THAI] = "th"; 332 | sampleLanguageMap[HB_SCRIPT_TIBETAN] = "bo"; 333 | sampleLanguageMap[HB_SCRIPT_CANADIAN_ABORIGINAL] = "iu"; 334 | sampleLanguageMap[HB_SCRIPT_TAGALOG] = "tl"; 335 | sampleLanguageMap[HB_SCRIPT_HANUNOO] = "hnn"; 336 | sampleLanguageMap[HB_SCRIPT_BUHID] = "bku"; 337 | sampleLanguageMap[HB_SCRIPT_TAGBANWA] = "tbw"; 338 | sampleLanguageMap[HB_SCRIPT_UGARITIC] = "uga"; 339 | sampleLanguageMap[HB_SCRIPT_BUGINESE] = "bug"; 340 | sampleLanguageMap[HB_SCRIPT_SYLOTI_NAGRI] = "syl"; 341 | sampleLanguageMap[HB_SCRIPT_OLD_PERSIAN] = "peo"; 342 | sampleLanguageMap[HB_SCRIPT_NKO] = "nqo"; 343 | 344 | /* 345 | * Default-value 346 | */ 347 | sampleLanguageMap[HB_SCRIPT_INVALID] = ""; 348 | }; 349 | 350 | LangHelper::LangHelper() { 351 | initScriptMap(scriptMap); 352 | initSampleLanguageMap(sampleLanguageMap); 353 | setDefaultLanguages(DEFAULT_LANGUAGES); 354 | } 355 | 356 | void LangHelper::setDefaultLanguages(const std::string& languages) { 357 | defaultLanguageSet.clear(); 358 | #if 0 359 | for (auto &lang : split(languages, ":")) {defaultLanguageSet.insert(lang); 360 | } 361 | #endif 362 | } 363 | 364 | const std::vector& 365 | LangHelper::getScriptsForLang(const std::string& lang) const { 366 | auto it = scriptMap.find(lang); 367 | 368 | if (it == scriptMap.end()) { 369 | it = scriptMap.find(""); 370 | } 371 | 372 | return it->second; 373 | } 374 | 375 | bool LangHelper::includesScript(const std::string& lang, hb_script_t script) const { 376 | for (auto& value : getScriptsForLang(lang)) { 377 | if (value == script) { 378 | return true; 379 | } 380 | } 381 | 382 | return false; 383 | } 384 | 385 | const std::string& 386 | LangHelper::getDefaultLanguage(hb_script_t script) const { 387 | const static std::string EMPTY = ""; 388 | 389 | for (auto& lang : defaultLanguageSet) { 390 | for (auto& value : getScriptsForLang(lang)) { 391 | if (value == script) { 392 | return lang; 393 | } 394 | } 395 | } 396 | 397 | return EMPTY; 398 | } 399 | 400 | const std::string& 401 | LangHelper::getSampleLanguage(hb_script_t script) const { 402 | auto it = sampleLanguageMap.find(script); 403 | 404 | if (it == sampleLanguageMap.end()) { 405 | it = sampleLanguageMap.find(HB_SCRIPT_INVALID); 406 | } 407 | 408 | return it->second; 409 | } 410 | 411 | auto LangHelper::matchLanguage(hb_script_t script, const std::string& langHint) const -> bool { 412 | // 1. Can @script be used to write @langHint? 413 | if (!langHint.empty() && includesScript(langHint, script)) { 414 | return true; 415 | } 416 | return false; 417 | } 418 | 419 | auto LangHelper::detectLanguage(hb_script_t script) const -> const std::string& { 420 | 421 | // 2. Can @script be used to write one of the "default languages"? 422 | const auto& defaultLanguage = getDefaultLanguage(script); 423 | if (!defaultLanguage.empty()) { 424 | return defaultLanguage; 425 | } 426 | // 3. Is there a predominant language that is likely for @script? 427 | return getSampleLanguage(script); 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/alfons/langHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace alfons { 21 | 22 | class LangHelper { 23 | public: 24 | LangHelper(); 25 | 26 | /* 27 | * Expects a list languages separated by colons 28 | */ 29 | void setDefaultLanguages(const std::string& languages); 30 | 31 | /* 32 | * Determines the scripts used to write @lang 33 | * 34 | * Quoting pango: 35 | * Most languages use only one script for writing, but there are 36 | * some that use two (Latin and Cyrillic for example), and a few 37 | * use three (Japanese for example). 38 | */ 39 | const std::vector& getScriptsForLang(const std::string& lang) const; 40 | 41 | /* 42 | * Determines if @script may be used to write @lang 43 | */ 44 | bool includesScript(const std::string& lang, hb_script_t script) const; 45 | 46 | /* 47 | * Returns the resolved language if @script may be used to write one of the 48 | * "default languages" 49 | */ 50 | const std::string& getDefaultLanguage(hb_script_t script) const; 51 | 52 | /* 53 | * Quoting pango: 54 | * Given a script, finds a language tag that is reasonably 55 | * representative of that script. This will usually be the 56 | * most widely spoken or used language written in that script: 57 | * for instance, the sample language for %HB_SCRIPT_CYRILLIC 58 | * is ru (Russian), the sample language 59 | * for %HB_SCRIPT_ARABIC is ar. 60 | * 61 | * For some scripts, no sample language will be returned because 62 | * there is no language that is sufficiently representative. 63 | * The best example of this is %HB_SCRIPT_HAN, where various 64 | * different variants of written Chinese, Japanese, and Korean 65 | * all use significantly different sets of Han characters and 66 | * forms of shared characters. No sample language can be provided 67 | * for many historical scripts as well. 68 | */ 69 | const std::string& getSampleLanguage(hb_script_t script) const; 70 | 71 | /* 72 | * Trying to detect a language for @script by asking 3 questions: 73 | * 1. Can @script be used to write @langHint? 74 | */ 75 | bool matchLanguage(hb_script_t script, const std::string& langHint) const; 76 | 77 | /* 78 | * 2. Can @script be used to write one of the "default languages"? 79 | * 3. Is there a predominant language that is likely for @script? 80 | */ 81 | const std::string& detectLanguage(hb_script_t script) const; 82 | 83 | protected: 84 | std::map> scriptMap; 85 | std::map sampleLanguageMap; 86 | std::set defaultLanguageSet; 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /src/alfons/lineLayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "glyph.h" 14 | #include "font.h" 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | namespace alfons { 23 | class Font; 24 | 25 | enum class Alignment { 26 | middle, 27 | left, 28 | right, 29 | top, 30 | baseline, 31 | bottom, 32 | block 33 | }; 34 | 35 | struct Shape { 36 | // Reference to face within a Font. 37 | uint16_t face; 38 | union { 39 | uint8_t flags; 40 | struct { 41 | // Marks the start of a cluster: 42 | // When a codepoint uses multiple glyphs 43 | // only the first is set. 44 | unsigned char cluster : 1; 45 | // Linebreak values, determine which breaks 46 | // are possible *after* this glyph. 47 | unsigned char mustBreak : 1; 48 | unsigned char canBreak : 1; 49 | unsigned char noBreak : 1; 50 | 51 | unsigned char isSpace : 1; 52 | }; 53 | }; 54 | 55 | float advance; 56 | 57 | uint32_t codepoint; 58 | glm::vec2 position; 59 | 60 | Shape() : face(0), flags(0), advance(0), codepoint(0) {} 61 | 62 | Shape(uint16_t faceID, uint32_t codepoint, glm::vec2 offset, 63 | float advance, uint8_t flags) 64 | : face(faceID), 65 | flags(flags), 66 | advance(advance), 67 | codepoint(codepoint), 68 | position(offset) {} 69 | }; 70 | 71 | class LineLayout { 72 | std::shared_ptr m_font; 73 | std::vector m_shapes; 74 | hb_direction_t m_direction = HB_DIRECTION_INVALID; 75 | FontFace::Metrics m_metrics; 76 | 77 | float m_advance = 0; 78 | float m_middleLineFactor = 1; 79 | float m_scale = 1; 80 | 81 | bool m_missingGlyphs = false; 82 | 83 | public: 84 | 85 | LineLayout() {} 86 | 87 | LineLayout(std::shared_ptr _font) 88 | : m_font(std::move(_font)) {} 89 | 90 | LineLayout(std::shared_ptr _font, std::vector _shapes, 91 | FontFace::Metrics _metrics, hb_direction_t _direction) 92 | : m_font(std::move(_font)), 93 | m_shapes(std::move(_shapes)), 94 | m_direction(_direction), 95 | m_metrics(_metrics), 96 | m_advance(0), 97 | m_middleLineFactor(1.0), 98 | m_scale(1) { 99 | 100 | for (auto& shape : m_shapes) { 101 | m_advance += shape.advance; 102 | } 103 | } 104 | 105 | void addShapes(const std::vector& _shapes) { 106 | for (auto& shape : _shapes) { 107 | m_advance += shape.advance; 108 | } 109 | m_shapes.insert(m_shapes.end(), _shapes.begin(), _shapes.end()); 110 | } 111 | 112 | void removeShapes(size_t _start, size_t _end) { 113 | if (_start > _end || _end > shapes().size()) { return; } 114 | 115 | auto it = m_shapes.begin() + _start; 116 | const auto end = m_shapes.begin() + _end; 117 | 118 | for (; it != end; ++it) { m_advance -= it->advance;} 119 | 120 | m_shapes.erase(m_shapes.begin() + _start, end); 121 | } 122 | 123 | std::vector& shapes() { return m_shapes; } 124 | const std::vector& shapes() const { return m_shapes; } 125 | 126 | const Font& font() const { return *m_font; } 127 | 128 | FontFace::Metrics& metrics() { return m_metrics; } 129 | 130 | glm::vec2 getOffset(Alignment alignX, Alignment alignY) const { 131 | return glm::vec2(offsetX(alignX), offsetY(alignY)); 132 | } 133 | 134 | float scale() const { 135 | return m_scale; 136 | } 137 | 138 | hb_direction_t direction() const { 139 | return m_direction; 140 | } 141 | 142 | float height() const { 143 | return m_metrics.height * m_scale; 144 | } 145 | 146 | float ascent() const { 147 | return m_metrics.ascent * m_scale; 148 | } 149 | 150 | float descent() const { 151 | return m_metrics.descent * m_scale; 152 | } 153 | 154 | float lineThickness() const { 155 | return m_metrics.lineThickness * m_scale; 156 | } 157 | 158 | float advance() const { 159 | return m_advance * m_scale; 160 | } 161 | 162 | float advance(const Shape& shape) const { 163 | return shape.advance * m_scale; 164 | } 165 | 166 | void setScale(float scale) { m_scale = scale; } 167 | 168 | float scale() { return m_scale; } 169 | 170 | float offsetY(Alignment align) const { 171 | switch (align) { 172 | case Alignment::middle: 173 | return m_middleLineFactor * 174 | (m_metrics.ascent - m_metrics.descent) * m_scale; 175 | case Alignment::top: 176 | return +ascent(); 177 | case Alignment::bottom: 178 | return -descent(); 179 | default: 180 | return 0; 181 | } 182 | } 183 | 184 | float offsetX(Alignment align) const { 185 | switch (align) { 186 | case Alignment::middle: 187 | return -0.5f * advance(); 188 | 189 | case Alignment::right: 190 | return -advance(); 191 | 192 | default: 193 | return 0; 194 | } 195 | } 196 | 197 | // Default-value is 0, otherwise getOffsetY() for "ALIGN_MIDDLE" 198 | // will return middleLineFactor * (getAscent() - getDescent()) 199 | void setMiddleLineFactor(float factor) { m_middleLineFactor = factor; } 200 | 201 | void setMissingGlyphs() { m_missingGlyphs = true; } 202 | bool missingGlyphs() const { return m_missingGlyphs; } 203 | }; 204 | 205 | } 206 | -------------------------------------------------------------------------------- /src/alfons/path/ASPC.h: -------------------------------------------------------------------------------- 1 | /* 2 | * From: 3 | * THE NEW CHRONOTEXT TOOLKIT: https://github.com/arielm/new-chronotext-toolkit 4 | * COPYRIGHT (C) 2014, ARIEL MALKA ALL RIGHTS RESERVED. 5 | * 6 | * THE FOLLOWING SOURCE-CODE IS DISTRIBUTED UNDER THE SIMPLIFIED BSD LICENSE: 7 | * https://github.com/arielm/new-chronotext-toolkit/blob/master/LICENSE.md 8 | */ 9 | 10 | /* 11 | * REFERENCES: 12 | * 13 | * "Adaptive Sampling of Parametric Curves" BY Luiz Henrique de Figueiredo 14 | * http://ariel.chronotext.org/dd/defigueiredo93adaptive.pdf 15 | */ 16 | 17 | #pragma once 18 | 19 | 20 | #include "glm/glm.hpp" 21 | #include "glm/gtx/norm.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace alfons { 29 | 30 | class ASPC { 31 | public: 32 | ASPC(std::function gamma, std::vector& path, float tol = 1) 33 | : gamma(gamma), 34 | path(path), 35 | tol(tol) { 36 | /* 37 | * The original algorithm requires randomness, but we want to avoid the noise 38 | * introduced by the same randomness when successively sampling the same points 39 | * 40 | * Requirements: 41 | * - Multiple ASPC instances should not be processed concurently 42 | * - Sampling should take place right after aspc is created 43 | */ 44 | srand(1); 45 | } 46 | 47 | void segment(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& p2, const glm::vec2& p3) { 48 | in[0] = p0; 49 | in[1] = p1; 50 | in[2] = p2; 51 | in[3] = p3; 52 | 53 | float pt = 0; 54 | auto p = gamma(pt, in.data()); 55 | 56 | float qt = 1; 57 | auto q = gamma(qt, in.data()); 58 | 59 | sample(pt, p, qt, q); 60 | } 61 | 62 | protected: 63 | std::function gamma; 64 | std::vector& path; 65 | float tol; 66 | 67 | std::array in; 68 | 69 | void sample(float t0, const glm::vec2& p0, float t1, const glm::vec2& p1) { 70 | float t = 0.45f + 0.1f * rand() / RAND_MAX; 71 | float rt = t0 + t * (t1 - t0); 72 | auto r = gamma(rt, in.data()); 73 | 74 | // float cross = (p0 - r).cross(p1 - r); 75 | float cross = glm::dot(glm::vec3(p0 - r, 0.0f), glm::vec3(p1 - r, 0.0f)); 76 | 77 | if (cross * cross < tol) { 78 | path.push_back(p0); 79 | } else { 80 | sample(t0, p0, rt, r); 81 | sample(rt, r, t1, p1); 82 | } 83 | } 84 | }; 85 | } 86 | -------------------------------------------------------------------------------- /src/alfons/path/lineSampler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "lineSampler.h" 12 | 13 | #include "alfons/path/splinePath.h" 14 | #include "alfons/utils.h" 15 | 16 | #include "glm/gtx/norm.hpp" 17 | 18 | namespace alfons { 19 | 20 | static float boundf(float value, float range) { 21 | float bound = fmodf(value, range); 22 | return (bound < 0) ? (bound + range) : bound; 23 | } 24 | 25 | LineSampler::LineSampler(int capacity) 26 | : mode(Mode::tangent) { 27 | if (capacity > 0) { 28 | extendCapacity(capacity); 29 | } 30 | } 31 | 32 | LineSampler::LineSampler(const std::vector& points) 33 | : mode(Mode::tangent) { 34 | add(points); 35 | } 36 | 37 | void LineSampler::add(const std::vector& newPoints) { 38 | extendCapacity(newPoints.size()); 39 | 40 | for (auto& point : newPoints) { 41 | add(point); 42 | } 43 | } 44 | 45 | void LineSampler::add(const glm::vec2& point) { 46 | if (points.empty()) { 47 | lengths.push_back(0); 48 | } else { 49 | glm::vec2 delta = point - points.back(); 50 | 51 | // Ignore zero-length segments 52 | if (delta == glm::vec2(0)) 53 | return; 54 | 55 | lengths.push_back(lengths.back() + glm::length(delta)); 56 | } 57 | points.push_back(point); 58 | } 59 | 60 | void LineSampler::clear() { 61 | points.clear(); 62 | lengths.clear(); 63 | } 64 | 65 | int LineSampler::size() const { 66 | return points.size(); 67 | } 68 | 69 | bool LineSampler::empty() const { 70 | return points.empty(); 71 | } 72 | 73 | float LineSampler::getLength() const { 74 | if (!points.empty()) { 75 | return lengths.back(); 76 | } else { 77 | return 0; 78 | } 79 | } 80 | 81 | Rect LineSampler::getBounds() const { 82 | float minX = std::numeric_limits::max(); 83 | float minY = std::numeric_limits::max(); 84 | float maxX = std::numeric_limits::min(); 85 | float maxY = std::numeric_limits::min(); 86 | 87 | for (auto& point : points) { 88 | if (point.x < minX) 89 | minX = point.x; 90 | if (point.y < minY) 91 | minY = point.y; 92 | 93 | if (point.x > maxX) 94 | maxX = point.x; 95 | if (point.y > maxY) 96 | maxY = point.y; 97 | } 98 | 99 | return Rect{minX, minY, maxX, maxY}; 100 | } 101 | 102 | void LineSampler::close() { 103 | if (size() > 2) { 104 | if (points.front() != points.back()) { 105 | add(points.front()); 106 | } 107 | } 108 | } 109 | 110 | bool LineSampler::isClosed() const { 111 | return (size() > 2) && (points.front() == points.back()); 112 | } 113 | 114 | void LineSampler::setMode(Mode mode) { 115 | this->mode = mode; 116 | } 117 | 118 | LineSampler::Mode LineSampler::getMode() const { 119 | return mode; 120 | } 121 | 122 | LineSampler::Value LineSampler::offset2Value(float offset) const { 123 | Value value; 124 | 125 | float length = getLength(); 126 | if (length <= 0) { 127 | value.angle = 0; 128 | value.offset = 0; 129 | value.index = 0; 130 | return value; 131 | } 132 | 133 | if ((mode == Mode::loop) || (mode == Mode::modulo)) { 134 | offset = boundf(offset, length); 135 | } else { 136 | if (offset <= 0) { 137 | if (mode == Mode::bounded) { 138 | offset = 0; 139 | } 140 | } else if (offset >= length) { 141 | if (mode == Mode::bounded) { 142 | offset = length; 143 | } 144 | } 145 | } 146 | 147 | int index = search(lengths, offset, 1, size()); 148 | 149 | auto& p0 = points[index]; 150 | auto& p1 = points[index + 1]; 151 | 152 | float ratio = (offset - lengths[index]) / (lengths[index + 1] - lengths[index]); 153 | 154 | value.position = p0 + (p1 - p0) * ratio; 155 | value.angle = atan2(p1.y - p0.y, p1.x - p0.x); 156 | value.offset = offset; 157 | value.index = index; 158 | 159 | return value; 160 | } 161 | 162 | bool LineSampler::get(float _offset, glm::vec2& _position, float& _angle) const { 163 | float length = getLength(); 164 | 165 | if (length > 0) { 166 | if ((mode == Mode::loop) || (mode == Mode::modulo)) { 167 | _offset = boundf(_offset, length); 168 | } else { 169 | if (_offset <= 0) { 170 | if (mode == Mode::bounded) { 171 | _offset = 0; 172 | } 173 | } else if (_offset >= length) { 174 | if (mode == Mode::bounded) { 175 | _offset = length; 176 | } 177 | } 178 | } 179 | 180 | int index = search(lengths, _offset, 1, size()); 181 | auto& p0 = points[index]; 182 | auto& p1 = points[index + 1]; 183 | 184 | float ratio = (_offset - lengths[index]) / (lengths[index + 1] - lengths[index]); 185 | _position = p0 + (p1 - p0) * ratio; 186 | 187 | _angle = atan2(p1.y - p0.y, p1.x - p0.x); 188 | 189 | return true; 190 | } else { 191 | _position = glm::vec2{0}; 192 | _angle = 0; 193 | return false; 194 | } 195 | } 196 | glm::vec2 LineSampler::offset2Position(float offset) const { 197 | float length = getLength(); 198 | 199 | if (length > 0) { 200 | if ((mode == Mode::loop) || (mode == Mode::modulo)) { 201 | offset = boundf(offset, length); 202 | } else { 203 | if (offset <= 0) { 204 | if (mode == Mode::bounded) { 205 | return points.front(); 206 | } 207 | } else if (offset >= length) { 208 | if (mode == Mode::bounded) { 209 | return points.back(); 210 | } 211 | } 212 | } 213 | 214 | int index = search(lengths, offset, 1, size()); 215 | auto& p0 = points[index]; 216 | auto& p1 = points[index + 1]; 217 | 218 | float ratio = (offset - lengths[index]) / (lengths[index + 1] - lengths[index]); 219 | return p0 + (p1 - p0) * ratio; 220 | } else { 221 | return glm::vec2(0); 222 | } 223 | } 224 | 225 | float LineSampler::offset2Angle(float offset) const { 226 | float length = getLength(); 227 | 228 | if (length > 0) { 229 | if ((mode == Mode::loop) || (mode == Mode::modulo)) { 230 | offset = boundf(offset, length); 231 | } else { 232 | if (offset <= 0) { 233 | if (mode == Mode::bounded) { 234 | offset = 0; 235 | } 236 | } else if (offset >= length) { 237 | if (mode == Mode::bounded) { 238 | offset = length; 239 | } 240 | } 241 | } 242 | 243 | int index = search(lengths, offset, 1, size()); 244 | auto& p0 = points[index]; 245 | auto& p1 = points[index + 1]; 246 | 247 | return atan2(p1.y - p0.y, p1.x - p0.x); 248 | } else { 249 | return 0; 250 | } 251 | } 252 | 253 | float LineSampler::offset2SampledAngle(float offset, float sampleSize) const { 254 | glm::vec2 gradient = offset2Gradient(offset, sampleSize); 255 | 256 | /* 257 | * We use an epsilon value to avoid degenerated results in some cases 258 | * (E.g. close to 180 degree diff. between two segments) 259 | */ 260 | if (glm::length2(gradient) > 1) { 261 | return atan2(gradient.y, gradient.x); 262 | } else { 263 | return offset2Angle(offset); 264 | } 265 | } 266 | 267 | glm::vec2 LineSampler::offset2Gradient(float offset, float sampleSize) const { 268 | glm::vec2 pm = offset2Position(offset - sampleSize * 0.5f); 269 | glm::vec2 pp = offset2Position(offset + sampleSize * 0.5f); 270 | 271 | return (pp - pm) * 0.5f; 272 | } 273 | 274 | /* 275 | * Returns false if closest-point is farther than 'threshold' distance 276 | * 277 | * Reference: "Minimum Distance between a Point and a Line" BY Paul Bourke 278 | * http://paulbourke.net/geometry/pointlineplane 279 | */ 280 | bool LineSampler::findClosestPoint(const glm::vec2& input, float threshold, ClosePoint& output) const { 281 | 282 | float sqMin = threshold * threshold; 283 | 284 | int end = size(); 285 | int index = -1; 286 | glm::vec2 position; 287 | float offset; 288 | 289 | for (int i = 0; i < end; i++) { 290 | int i0, i1; 291 | 292 | if (i == end - 1) { 293 | i0 = i - 1; 294 | i1 = i; 295 | } else { 296 | i0 = i; 297 | i1 = i + 1; 298 | } 299 | 300 | auto& p0 = points[i0]; 301 | auto& p1 = points[i1]; 302 | 303 | glm::vec2 delta = p1 - p0; 304 | float length = lengths[i1] - lengths[i0]; 305 | float u = glm::dot(delta, (input - p0)) / (length * length); 306 | 307 | if (u >= 0 && u <= 1) { 308 | glm::vec2 p = p0 + u * delta; 309 | float mag = glm::length2(p - input); 310 | 311 | if (mag < sqMin) { 312 | sqMin = mag; 313 | index = i0; 314 | 315 | position = p; 316 | offset = lengths[index] + u * length; 317 | } 318 | } else { 319 | float mag0 = glm::length2(p0 - input); 320 | float mag1 = glm::length2(p1 - input); 321 | 322 | if ((mag0 < sqMin) && (mag0 < mag1)) { 323 | sqMin = mag0; 324 | index = i0; 325 | 326 | position = points[i0]; 327 | offset = lengths[index]; 328 | } else if ((mag1 < sqMin) && (mag1 < mag0)) { 329 | sqMin = mag1; 330 | index = i1; 331 | 332 | position = points[i1]; 333 | offset = lengths[index]; 334 | } 335 | } 336 | } 337 | 338 | if (index != -1) { 339 | output.position = position; 340 | output.offset = offset; 341 | output.distance = sqrt(sqMin); 342 | 343 | return true; 344 | } 345 | 346 | return false; 347 | } 348 | 349 | /* Reference: "Minimum Distance between a Point and a Line" BY Paul Bourke 350 | * http://paulbourke.net/geometry/pointlineplane 351 | */ 352 | LineSampler::ClosePoint LineSampler::closestPointFromSegment(const glm::vec2& input, int segmentIndex) const { 353 | LineSampler::ClosePoint output; 354 | 355 | if ((segmentIndex >= 0) && (segmentIndex + 1 < size())) { 356 | int i0 = segmentIndex; 357 | int i1 = segmentIndex + 1; 358 | 359 | auto& p0 = points[i0]; 360 | auto& p1 = points[i1]; 361 | 362 | glm::vec2 delta = p1 - p0; 363 | float length = lengths[i1] - lengths[i0]; 364 | float u = glm::dot(delta, (input - p0)) / (length * length); 365 | 366 | if (u >= 0 && u <= 1) { 367 | glm::vec2 p = p0 + u * delta; 368 | float mag = glm::length2(p - input); 369 | 370 | output.position = p; 371 | output.offset = lengths[i0] + u * length; 372 | output.distance = sqrt(mag); 373 | } else { 374 | float mag0 = glm::length2(p0 - input); 375 | float mag1 = glm::length2(p1 - input); 376 | 377 | if (mag0 < mag1) { 378 | output.position = p0; 379 | output.offset = lengths[i0]; 380 | output.distance = sqrt(mag0); 381 | } else { 382 | output.position = p1; 383 | output.offset = lengths[i1]; 384 | output.distance = sqrt(mag1); 385 | } 386 | } 387 | } else { 388 | output.distance = std::numeric_limits::max(); 389 | output.offset = 0; 390 | } 391 | 392 | return output; 393 | } 394 | 395 | void LineSampler::extendCapacity(int amount) { 396 | int newCapacity = size() + amount; 397 | points.reserve(newCapacity); 398 | lengths.reserve(newCapacity); 399 | } 400 | 401 | void LineSampler::sampleSpline(const SplinePath &path, SplineType type, float tolerance) { 402 | points.clear(); 403 | lengths.clear(); 404 | 405 | path.flush(type, points, tolerance); 406 | 407 | if (points.size() < 2) { return; } 408 | 409 | 410 | lengths.push_back(0); 411 | 412 | size_t end = points.size(); 413 | for (size_t i = 1; i < end; ) { 414 | glm::vec2 delta = points[i] - points[i-1]; 415 | 416 | // Ignore zero-length segments 417 | if (delta == glm::vec2(0)) { 418 | points.erase(points.begin() + i); 419 | end -= 1; 420 | continue; 421 | } 422 | lengths.push_back(lengths.back() + glm::length(delta)); 423 | 424 | i++; 425 | } 426 | 427 | if (path.isClosed()) { 428 | close(); 429 | setMode(LineSampler::Mode::loop); 430 | } 431 | 432 | } 433 | 434 | } 435 | -------------------------------------------------------------------------------- /src/alfons/path/lineSampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include "alfons/alfons.h" 16 | 17 | #include 18 | 19 | namespace alfons { 20 | 21 | class SplinePath; 22 | enum class SplineType; 23 | 24 | class LineSampler { 25 | public: 26 | struct Value { 27 | glm::vec2 position; 28 | float angle; 29 | float offset; 30 | int index; 31 | }; 32 | 33 | struct ClosePoint { 34 | glm::vec2 position; // position of closest-point on path 35 | float offset; // offset of closest-point on path 36 | float distance; // distance to closest-point on path 37 | }; 38 | 39 | enum class Mode { 40 | bounded, 41 | loop, 42 | tangent, 43 | modulo, 44 | }; 45 | 46 | LineSampler(int capacity = 0); 47 | LineSampler(const std::vector& points); 48 | 49 | void sampleSpline(const SplinePath &path, SplineType type, float tolerance); 50 | 51 | void add(const std::vector& points); 52 | void add(const glm::vec2& point); 53 | inline void add(float x, float y) { add(glm::vec2(x, y)); } 54 | 55 | const std::vector& getPoints() const { return points; } 56 | const std::vector& getLengths() const { return lengths; } 57 | 58 | std::vector& getPoints() { return points; } 59 | std::vector& getLengths() { return lengths; } 60 | 61 | void clear(); 62 | int size() const; 63 | bool empty() const; 64 | 65 | float getLength() const; 66 | Rect getBounds() const; 67 | 68 | void close(); 69 | bool isClosed() const; 70 | 71 | void setMode(Mode mode); 72 | Mode getMode() const; 73 | 74 | Value offset2Value(float offset) const; 75 | glm::vec2 offset2Position(float offset) const; 76 | float offset2Angle(float offset) const; 77 | float offset2SampledAngle(float offset, float sampleSize) const; 78 | glm::vec2 offset2Gradient(float offset, float sampleSize) const; 79 | 80 | bool get(float _offset, glm::vec2& _position, float& _angle) const; 81 | 82 | bool findClosestPoint(const glm::vec2& input, float threshold, 83 | ClosePoint& output) const; 84 | ClosePoint closestPointFromSegment(const glm::vec2& input, 85 | int segmentIndex) const; 86 | 87 | protected: 88 | Mode mode; 89 | 90 | std::vector points; 91 | std::vector lengths; 92 | 93 | void extendCapacity(int amount); 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /src/alfons/path/splinePath.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "splinePath.h" 12 | #include "ASPC.h" 13 | 14 | #include 15 | 16 | namespace alfons { 17 | SplinePath::SplinePath(int capacity) 18 | : closed(false) { 19 | if (capacity > 0) { 20 | points.reserve(capacity); 21 | } 22 | } 23 | 24 | SplinePath::SplinePath(const std::vector& points) 25 | : closed(false) { 26 | add(points); 27 | } 28 | 29 | void SplinePath::add(const std::vector& newPoints) { 30 | points.reserve(size() + newPoints.size()); 31 | 32 | for (auto& point : newPoints) { 33 | add(point); 34 | } 35 | } 36 | 37 | void SplinePath::add(const glm::vec2& point) { 38 | if (!points.empty() && point == points.back()) { 39 | return; 40 | } 41 | 42 | points.emplace_back(point); 43 | } 44 | 45 | const std::vector& SplinePath::getPoints() const { 46 | return points; 47 | } 48 | 49 | void SplinePath::clear() { 50 | points.clear(); 51 | } 52 | 53 | int SplinePath::size() const { 54 | return points.size(); 55 | } 56 | 57 | bool SplinePath::empty() const { 58 | return points.empty(); 59 | } 60 | 61 | void SplinePath::close() { 62 | if (size() > 2) { 63 | closed = true; 64 | 65 | if (points.front() == points.back()) { 66 | points.pop_back(); 67 | } 68 | } 69 | } 70 | 71 | bool SplinePath::isClosed() const { 72 | return closed; 73 | } 74 | 75 | void SplinePath::flush(SplineType type, std::vector& path, float tol) const { 76 | std::function gamma; 77 | 78 | switch (type) { 79 | case SplineType::bspline: 80 | gamma = GammaBSpline; 81 | break; 82 | 83 | case SplineType::catmull_rom: 84 | gamma = GammaCatmullRom; 85 | break; 86 | 87 | default: 88 | return; 89 | } 90 | 91 | int size = points.size(); 92 | 93 | if (size > 2) { 94 | ASPC aspc(gamma, path, tol); 95 | 96 | if (closed) { 97 | aspc.segment(points[size - 1], points[0], points[1], points[2]); 98 | } else { 99 | if (type == SplineType::bspline) { 100 | aspc.segment(points[0], points[0], points[0], points[1]); 101 | } 102 | 103 | aspc.segment(points[0], points[0], points[1], points[2]); 104 | } 105 | 106 | for (int i = 0; i < size - 3; i++) { 107 | aspc.segment(points[i], points[i + 1], points[i + 2], points[i + 3]); 108 | } 109 | 110 | if (closed) { 111 | aspc.segment(points[size - 3], points[size - 2], points[size - 1], points[0]); 112 | aspc.segment(points[size - 2], points[size - 1], points[0], points[1]); 113 | } else { 114 | aspc.segment(points[size - 3], points[size - 2], points[size - 1], points[size - 1]); 115 | aspc.segment(points[size - 2], points[size - 1], points[size - 1], points[size - 1]); 116 | 117 | if (type == SplineType::bspline) { 118 | aspc.segment(points[size - 1], points[size - 1], points[size - 1], points[size - 1]); 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/alfons/path/splinePath.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | /* 12 | * References: 13 | * 14 | * "Spline Curves" BY Tim Lambert 15 | * http://www.cse.unsw.edu.au/~lambert/splines 16 | */ 17 | 18 | /* 19 | * TODO: 20 | * 21 | * Implementing the hermite curve (with bias and tension) would be a must: 22 | * http://paulbourke.net/miscellaneous/interpolation 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "glm/glm.hpp" 28 | 29 | #include 30 | 31 | namespace alfons { 32 | 33 | enum class SplineType { 34 | bspline, 35 | catmull_rom 36 | }; 37 | 38 | class SplinePath { 39 | public: 40 | 41 | SplinePath(int capacity = 0); 42 | SplinePath(const std::vector& points); 43 | void add(const std::vector& points); 44 | void add(const glm::vec2& point); 45 | inline void add(float x, float y) { add(glm::vec2(x, y)); } 46 | 47 | const std::vector& getPoints() const; 48 | 49 | void clear(); 50 | int size() const; 51 | bool empty() const; 52 | 53 | void close(); 54 | bool isClosed() const; 55 | 56 | void flush(SplineType type, std::vector& path, float tol = 1) const; 57 | 58 | protected: 59 | std::vector points; 60 | bool closed; 61 | }; 62 | 63 | static inline glm::vec2 GammaBSpline(float t, glm::vec2* in) { 64 | float w0 = ((3 - t) * t - 3) * t + 1; 65 | float w1 = ((3 * t - 6) * t) * t + 4; 66 | float w2 = ((3 - 3 * t) * t + 3) * t + 1; 67 | float w3 = t * t * t; 68 | 69 | return glm::vec2(w0 * in[0].x + w1 * in[1].x + w2 * in[2].x + w3 * in[3].x, 70 | w0 * in[0].y + w1 * in[1].y + w2 * in[2].y + w3 * in[3].y) / 71 | 6.0f; 72 | } 73 | 74 | static inline glm::vec2 GammaCatmullRom(float t, glm::vec2* in) { 75 | float w0 = ((2 - t) * t - 1) * t; 76 | float w1 = ((3 * t - 5) * t) * t + 2; 77 | float w2 = ((4 - 3 * t) * t + 1) * t; 78 | float w3 = (t - 1) * t * t; 79 | 80 | return glm::vec2(w0 * in[0].x + w1 * in[1].x + w2 * in[2].x + w3 * in[3].x, 81 | w0 * in[0].y + w1 * in[1].y + w2 * in[2].y + w3 * in[3].y) / 82 | 2.0f; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/alfons/quadMatrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "quadMatrix.h" 12 | 13 | #include "glm/gtx/norm.hpp" 14 | #include "glm/gtx/transform.hpp" 15 | 16 | #include 17 | 18 | namespace alfons { 19 | 20 | QuadMatrix::QuadMatrix() { 21 | setIdentity(); 22 | } 23 | 24 | void QuadMatrix::load(const glm::mat4& matrix) { 25 | m = matrix; 26 | } 27 | 28 | void QuadMatrix::setIdentity() { 29 | m = glm::mat4(); 30 | } 31 | 32 | void QuadMatrix::push() { 33 | stack.push_back(m); 34 | } 35 | 36 | void QuadMatrix::pop() { 37 | if (!stack.empty()) { 38 | m = stack.back(); 39 | stack.pop_back(); 40 | } 41 | } 42 | 43 | void QuadMatrix::setTranslation(float x, float y, float z) { 44 | m = glm::mat4(); 45 | m[3][0] = x; 46 | m[3][1] = y; 47 | m[3][2] = z; 48 | } 49 | 50 | void QuadMatrix::translate(float x, float y, float z) { 51 | m = glm::translate(m, glm::vec3(x, y, z)); 52 | } 53 | 54 | void QuadMatrix::scale(float x, float y, float z) { 55 | m = glm::scale(m, glm::vec3(x, y, z)); 56 | } 57 | 58 | void QuadMatrix::rotateX(float a) { 59 | m = glm::rotate(m, a, glm::vec3(1, 0, 0)); 60 | } 61 | 62 | void QuadMatrix::rotateY(float a) { 63 | m = glm::rotate(m, a, glm::vec3(0, 1, 0)); 64 | } 65 | 66 | void QuadMatrix::rotateZ(float a) { 67 | float c = ::cos(a); 68 | float s = ::sin(a); 69 | 70 | float r00 = m00 * c + m01 * s; 71 | float r01 = m01 * c - m00 * s; 72 | 73 | float r10 = m10 * c + m11 * s; 74 | float r11 = m11 * c - m10 * s; 75 | 76 | float r20 = m20 * c + m21 * s; 77 | float r21 = m21 * c - m20 * s; 78 | 79 | float r30 = m30 * c + m31 * s; 80 | float r31 = m31 * c - m30 * s; 81 | 82 | m00 = r00; 83 | m01 = r01; 84 | m10 = r10; 85 | m11 = r11; 86 | m20 = r20; 87 | m21 = r21; 88 | m30 = r30; 89 | m31 = r31; 90 | //m = glm::rotate(m, a, glm::vec3(0, 0, 1)); 91 | } 92 | 93 | void QuadMatrix::rotateXY(float sx, float sy) { 94 | 95 | float cx = ::sqrt(1.0f - sx * sx); 96 | float cy = ::sqrt(1.0f - sy * sy); 97 | 98 | float cxy = cx + cy; 99 | 100 | float r00 = m00 * cy - m02 * sy; 101 | float r01 = m01 * cx + m02 * sx; 102 | float r02 = m00 * sy + m02 * cxy - m01 * sx; 103 | 104 | float r10 = m10 * cy - m12 * sy; 105 | float r11 = m11 * cx + m12 * sx; 106 | float r12 = m10 * sy + m12 * cxy - m11 * sx; 107 | 108 | float r20 = m20 * cy - m22 * sy; 109 | float r21 = m21 * cx + m22 * sx; 110 | float r22 = m20 * sy + m22 * cxy - m21 * sx; 111 | 112 | float r30 = m30 * cy - m32 * sy; 113 | float r31 = m31 * cx + m32 * sx; 114 | float r32 = m30 * sy + m32 * cxy - m31 * sx; 115 | 116 | m00 = r00; 117 | m01 = r01; 118 | m02 = r02; 119 | m10 = r10; 120 | m11 = r11; 121 | m12 = r12; 122 | m20 = r20; 123 | m21 = r21; 124 | m22 = r22; 125 | m30 = r30; 126 | m31 = r31; 127 | m32 = r32; 128 | } 129 | 130 | glm::vec3 QuadMatrix::transform(float x, float y) const { 131 | float x00 = x * m00; 132 | float x10 = x * m10; 133 | float x20 = x * m20; 134 | 135 | float y01 = y * m01; 136 | float y11 = y * m11; 137 | float y21 = y * m21; 138 | 139 | return glm::vec3(x00 + y01 + m03, x10 + y11 + m13, x20 + y21 + m23); 140 | } 141 | 142 | void QuadMatrix::transformRect(const Rect& _rect, Quad& out) const { 143 | float x100 = _rect.x1 * m00; 144 | float x110 = _rect.x1 * m10; 145 | 146 | float y101 = _rect.y1 * m01; 147 | float y111 = _rect.y1 * m11; 148 | 149 | float x200 = _rect.x2 * m00; 150 | float x210 = _rect.x2 * m10; 151 | 152 | float y201 = _rect.y2 * m01; 153 | float y211 = _rect.y2 * m11; 154 | 155 | out.x1 = x100 + y101 + m03; 156 | out.y1 = x110 + y111 + m13; 157 | 158 | out.x2 = x100 + y201 + m03; 159 | out.y2 = x110 + y211 + m13; 160 | 161 | out.x3 = x200 + y201 + m03; 162 | out.y3 = x210 + y211 + m13; 163 | 164 | out.x4 = x200 + y101 + m03; 165 | out.y4 = x210 + y111 + m13; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/alfons/quadMatrix.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "alfons.h" 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | namespace alfons { 23 | 24 | class QuadMatrix { 25 | public: 26 | union { 27 | glm::mat4 m; 28 | 29 | struct { 30 | float m00, m10, m20, m30; 31 | float m01, m11, m21, m31; 32 | float m02, m12, m22, m32; 33 | float m03, m13, m23, m33; 34 | }; 35 | }; 36 | 37 | QuadMatrix(); 38 | 39 | void load(const glm::mat4& matrix); 40 | 41 | void setIdentity(); 42 | 43 | void push(); 44 | void pop(); 45 | 46 | inline void setTranslation(const glm::vec2& t) { setTranslation(t.x, t.y, 0); } 47 | inline void setTranslation(const glm::vec3& t) { 48 | setTranslation(t.x, t.y, t.z); 49 | } 50 | void setTranslation(float x, float y, float z = 0); 51 | 52 | inline void translate(const glm::vec2& t) { translate(t.x, t.y, 0); } 53 | inline void translate(const glm::vec3& t) { translate(t.x, t.y, t.z); } 54 | void translate(float x, float y, float z = 0); 55 | 56 | inline void scale(float s) { scale(s, s, s); } 57 | void scale(float x, float y, float z = 1); 58 | 59 | void rotateX(float a); 60 | void rotateY(float a); 61 | void rotateZ(float a); 62 | void rotateXY(float sx, float sy); 63 | 64 | glm::vec3 transform(float x, float y) const; 65 | 66 | void transformRect(const Rect& rect, Quad& out) const; 67 | 68 | protected: 69 | std::vector stack; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/alfons/scrptrun.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ******************************************************************************* 3 | * 4 | * Copyright (C) 1999-2001, International Business Machines 5 | * Corporation and others. All Rights Reserved. 6 | * 7 | ******************************************************************************* 8 | * file name: scrptrun.cpp 9 | * 10 | * created on: 10/17/2001 11 | * created by: Eric R. Mader 12 | */ 13 | 14 | #include "unicode/utypes.h" 15 | #include "unicode/uscript.h" 16 | 17 | #include "scrptrun.h" 18 | 19 | #define ARRAY_SIZE(array) (sizeof array / sizeof array[0]) 20 | 21 | const char ScriptRun::fgClassID = 0; 22 | 23 | UChar32 ScriptRun::pairedChars[] = { 24 | 0x0028, 0x0029, // ascii paired punctuation 25 | 0x003c, 0x003e, 26 | 0x005b, 0x005d, 27 | 0x007b, 0x007d, 28 | 0x00ab, 0x00bb, // guillemets 29 | 0x2018, 0x2019, // general punctuation 30 | 0x201c, 0x201d, 31 | 0x2039, 0x203a, 32 | 0x3008, 0x3009, // chinese paired punctuation 33 | 0x300a, 0x300b, 34 | 0x300c, 0x300d, 35 | 0x300e, 0x300f, 36 | 0x3010, 0x3011, 37 | 0x3014, 0x3015, 38 | 0x3016, 0x3017, 39 | 0x3018, 0x3019, 40 | 0x301a, 0x301b}; 41 | 42 | const int32_t ScriptRun::pairedCharCount = ARRAY_SIZE(pairedChars); 43 | const int32_t ScriptRun::pairedCharPower = 1 << highBit(pairedCharCount); 44 | const int32_t ScriptRun::pairedCharExtra = pairedCharCount - pairedCharPower; 45 | 46 | int8_t ScriptRun::highBit(int32_t value) { 47 | if (value <= 0) { 48 | return -32; 49 | } 50 | 51 | int8_t bit = 0; 52 | 53 | if (value >= 1 << 16) { 54 | value >>= 16; 55 | bit += 16; 56 | } 57 | 58 | if (value >= 1 << 8) { 59 | value >>= 8; 60 | bit += 8; 61 | } 62 | 63 | if (value >= 1 << 4) { 64 | value >>= 4; 65 | bit += 4; 66 | } 67 | 68 | if (value >= 1 << 2) { 69 | value >>= 2; 70 | bit += 2; 71 | } 72 | 73 | if (value >= 1 << 1) { 74 | //value >>= 1; 75 | bit += 1; 76 | } 77 | 78 | return bit; 79 | } 80 | 81 | int32_t ScriptRun::getPairIndex(UChar32 ch) { 82 | int32_t probe = pairedCharPower; 83 | int32_t index = 0; 84 | 85 | if (ch >= pairedChars[pairedCharExtra]) { 86 | index = pairedCharExtra; 87 | } 88 | 89 | while (probe > (1 << 0)) { 90 | probe >>= 1; 91 | 92 | if (ch >= pairedChars[index + probe]) { 93 | index += probe; 94 | } 95 | } 96 | 97 | if (pairedChars[index] != ch) { 98 | index = -1; 99 | } 100 | 101 | return index; 102 | } 103 | 104 | UBool ScriptRun::sameScript(int32_t scriptOne, int32_t scriptTwo) { 105 | return scriptOne <= USCRIPT_INHERITED || scriptTwo <= USCRIPT_INHERITED || scriptOne == scriptTwo; 106 | } 107 | 108 | UBool ScriptRun::next() { 109 | int32_t startSP = parenSP; // used to find the first new open character 110 | UErrorCode error = U_ZERO_ERROR; 111 | 112 | // if we've fallen off the end of the text, we're done 113 | if (scriptEnd >= charLimit) { 114 | return false; 115 | } 116 | 117 | scriptCode = USCRIPT_COMMON; 118 | 119 | for (scriptStart = scriptEnd; scriptEnd < charLimit; scriptEnd += 1) { 120 | UChar high = charArray[scriptEnd]; 121 | UChar32 ch = high; 122 | 123 | // if the character is a high surrogate and it's not the last one 124 | // in the text, see if it's followed by a low surrogate 125 | if (high >= 0xD800 && high <= 0xDBFF && scriptEnd < charLimit - 1) { 126 | UChar low = charArray[scriptEnd + 1]; 127 | 128 | // if it is followed by a low surrogate, 129 | // consume it and form the full character 130 | if (low >= 0xDC00 && low <= 0xDFFF) { 131 | ch = (high - 0xD800) * 0x0400 + low - 0xDC00 + 0x10000; 132 | scriptEnd += 1; 133 | } 134 | } 135 | 136 | UScriptCode sc = uscript_getScript(ch, &error); 137 | int32_t pairIndex = getPairIndex(ch); 138 | 139 | // Paired character handling: 140 | // 141 | // if it's an open character, push it onto the stack. 142 | // if it's a close character, find the matching open on the 143 | // stack, and use that script code. Any non-matching open 144 | // characters above it on the stack will be poped. 145 | if (pairIndex >= 0) { 146 | if ((pairIndex & 1) == 0) { 147 | parenStack[++parenSP].pairIndex = pairIndex; 148 | parenStack[parenSP].scriptCode = scriptCode; 149 | } else if (parenSP >= 0) { 150 | int32_t pi = pairIndex & ~1; 151 | 152 | while (parenSP >= 0 && parenStack[parenSP].pairIndex != pi) { 153 | parenSP -= 1; 154 | } 155 | 156 | if (parenSP < startSP) { 157 | startSP = parenSP; 158 | } 159 | 160 | if (parenSP >= 0) { 161 | sc = parenStack[parenSP].scriptCode; 162 | } 163 | } 164 | } 165 | 166 | if (sameScript(scriptCode, sc)) { 167 | if (scriptCode <= USCRIPT_INHERITED && sc > USCRIPT_INHERITED) { 168 | scriptCode = sc; 169 | 170 | // now that we have a final script code, fix any open 171 | // characters we pushed before we knew the script code. 172 | while (startSP < parenSP) { 173 | parenStack[++startSP].scriptCode = scriptCode; 174 | } 175 | } 176 | 177 | // if this character is a close paired character, 178 | // pop it from the stack 179 | if (pairIndex >= 0 && (pairIndex & 1) != 0 && parenSP >= 0) { 180 | parenSP -= 1; 181 | startSP -= 1; 182 | } 183 | } else { 184 | // if the run broke on a surrogate pair, 185 | // end it before the high surrogate 186 | if (ch >= 0x10000) { 187 | scriptEnd -= 1; 188 | } 189 | 190 | break; 191 | } 192 | } 193 | 194 | return true; 195 | } 196 | -------------------------------------------------------------------------------- /src/alfons/scrptrun.h: -------------------------------------------------------------------------------- 1 | /* 2 | ******************************************************************************* 3 | * 4 | * Copyright (C) 1999-2003, International Business Machines 5 | * Corporation and others. All Rights Reserved. 6 | * 7 | ******************************************************************************* 8 | * file name: scrptrun.h 9 | * 10 | * created on: 10/17/2001 11 | * created by: Eric R. Mader 12 | */ 13 | 14 | #pragma once 15 | 16 | #include "unicode/utypes.h" 17 | #include "unicode/uobject.h" 18 | #include "unicode/uscript.h" 19 | 20 | struct ScriptRecord { 21 | UChar32 startChar; 22 | UChar32 endChar; 23 | UScriptCode scriptCode; 24 | }; 25 | 26 | struct ParenStackEntry { 27 | int32_t pairIndex; 28 | UScriptCode scriptCode; 29 | }; 30 | 31 | class ScriptRun : public icu::UObject { 32 | public: 33 | ScriptRun(); 34 | 35 | ScriptRun(const UChar chars[], int32_t length); 36 | 37 | ScriptRun(const UChar chars[], int32_t start, int32_t length); 38 | 39 | void reset(); 40 | 41 | void reset(int32_t start, int32_t count); 42 | 43 | void reset(const UChar chars[], int32_t start, int32_t length); 44 | 45 | int32_t getScriptStart(); 46 | 47 | int32_t getScriptEnd(); 48 | 49 | UScriptCode getScriptCode(); 50 | 51 | UBool next(); 52 | 53 | /** 54 | * ICU "poor man's RTTI", returns a UClassID for the actual class. 55 | * 56 | * @stable ICU 2.2 57 | */ 58 | virtual inline UClassID getDynamicClassID() const { 59 | return getStaticClassID(); 60 | } 61 | 62 | /** 63 | * ICU "poor man's RTTI", returns a UClassID for this class. 64 | * 65 | * @stable ICU 2.2 66 | */ 67 | static inline UClassID getStaticClassID() { 68 | return (UClassID)&fgClassID; 69 | } 70 | 71 | private: 72 | static UBool sameScript(int32_t scriptOne, int32_t scriptTwo); 73 | 74 | int32_t charStart; 75 | int32_t charLimit; 76 | const UChar* charArray; 77 | 78 | int32_t scriptStart; 79 | int32_t scriptEnd; 80 | UScriptCode scriptCode; 81 | 82 | ParenStackEntry parenStack[128]; 83 | int32_t parenSP; 84 | 85 | static int8_t highBit(int32_t value); 86 | static int32_t getPairIndex(UChar32 ch); 87 | 88 | static UChar32 pairedChars[]; 89 | static const int32_t pairedCharCount; 90 | static const int32_t pairedCharPower; 91 | static const int32_t pairedCharExtra; 92 | 93 | /** 94 | * The address of this static class variable serves as this class's ID 95 | * for ICU "poor man's RTTI". 96 | */ 97 | static const char fgClassID; 98 | }; 99 | 100 | inline ScriptRun::ScriptRun() { 101 | reset(NULL, 0, 0); 102 | } 103 | 104 | inline ScriptRun::ScriptRun(const UChar chars[], int32_t length) { 105 | reset(chars, 0, length); 106 | } 107 | 108 | inline ScriptRun::ScriptRun(const UChar chars[], int32_t start, int32_t length) { 109 | reset(chars, start, length); 110 | } 111 | 112 | inline int32_t ScriptRun::getScriptStart() { 113 | return scriptStart; 114 | } 115 | 116 | inline int32_t ScriptRun::getScriptEnd() { 117 | return scriptEnd; 118 | } 119 | 120 | inline UScriptCode ScriptRun::getScriptCode() { 121 | return scriptCode; 122 | } 123 | 124 | inline void ScriptRun::reset() { 125 | scriptStart = charStart; 126 | scriptEnd = charStart; 127 | scriptCode = USCRIPT_INVALID_CODE; 128 | parenSP = -1; 129 | } 130 | 131 | inline void ScriptRun::reset(int32_t start, int32_t length) { 132 | charStart = start; 133 | charLimit = start + length; 134 | 135 | reset(); 136 | } 137 | 138 | inline void ScriptRun::reset(const UChar chars[], int32_t start, int32_t length) { 139 | charArray = chars; 140 | 141 | reset(start, length); 142 | } 143 | -------------------------------------------------------------------------------- /src/alfons/textBatch.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "textBatch.h" 12 | #include "font.h" 13 | #include "alfons.h" 14 | #include "atlas.h" 15 | #include "utils.h" 16 | 17 | #include "logger.h" 18 | 19 | #include 20 | 21 | namespace alfons { 22 | 23 | LineMetrics NO_METRICS; 24 | 25 | TextBatch::TextBatch(GlyphAtlas& _atlas, MeshCallback& _mesh) 26 | : m_atlas(_atlas), m_mesh(_mesh) { 27 | m_clip.x1 = 0; 28 | m_clip.y1 = 0; 29 | m_clip.x2 = 0; 30 | m_clip.y2 = 0; 31 | } 32 | 33 | void TextBatch::setClip(const Rect& _clipRect) { 34 | m_clip = _clipRect; 35 | m_hasClip = true; 36 | } 37 | 38 | void TextBatch::setClip(float x1, float y1, float x2, float y2) { 39 | m_clip.x1 = x1; 40 | m_clip.y1 = y1; 41 | m_clip.x2 = x2; 42 | m_clip.y2 = y2; 43 | 44 | m_hasClip = true; 45 | } 46 | 47 | bool TextBatch::clip(Rect& _rect) const { 48 | if (_rect.x1 > m_clip.x2 || _rect.x2 < m_clip.x1 || 49 | _rect.y1 > m_clip.y2 || _rect.y2 < m_clip.y1) { 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | bool TextBatch::clip(Quad& _quad) const { 56 | return ((_quad.x1 > m_clip.x2 && _quad.x2 > m_clip.x2 && 57 | _quad.x3 > m_clip.x2 && _quad.x4 > m_clip.x2) || 58 | (_quad.y1 > m_clip.y2 && _quad.y2 > m_clip.y2 && 59 | _quad.y3 > m_clip.y2 && _quad.y4 > m_clip.y2) || 60 | (_quad.x1 < m_clip.x1 && _quad.x2 < m_clip.x1 && 61 | _quad.x3 < m_clip.x1 && _quad.x4 < m_clip.x1) || 62 | (_quad.y1 < m_clip.y1 && _quad.y2 < m_clip.y1 && 63 | _quad.y3 < m_clip.y1 && _quad.y4 < m_clip.y1)); 64 | } 65 | 66 | void TextBatch::setupRect(const Shape& _shape, const glm::vec2& _position, 67 | float _sizeRatio, Rect& _rect, AtlasGlyph& _atlasGlyph) { 68 | 69 | glm::vec2 ul = _position + (_shape.position + _atlasGlyph.glyph->offset) * _sizeRatio; 70 | _rect.x1 = ul.x; 71 | _rect.y1 = ul.y; 72 | _rect.x2 = ul.x + _atlasGlyph.glyph->size.x * _sizeRatio; 73 | _rect.y2 = ul.y + _atlasGlyph.glyph->size.y * _sizeRatio; 74 | } 75 | 76 | void TextBatch::drawShape(const Font& _font, const Shape& _shape, 77 | const glm::vec2& _position, float _scale, 78 | LineMetrics& _metrics) { 79 | 80 | AtlasGlyph atlasGlyph; 81 | if (!m_atlas.getGlyph(_font, {_shape.face, _shape.codepoint}, atlasGlyph)) { 82 | return; 83 | } 84 | 85 | Rect rect; 86 | setupRect(_shape, _position, _scale, rect, atlasGlyph); 87 | 88 | if (m_hasClip && clip(rect)) { 89 | return; 90 | } 91 | 92 | m_mesh.drawGlyph(rect, atlasGlyph); 93 | 94 | if (&_metrics != &NO_METRICS) { 95 | _metrics.addExtents({rect.x1, rect.y1, rect.x2, rect.y2}); 96 | } 97 | } 98 | 99 | void TextBatch::drawTransformedShape(const Font& _font, const Shape& _shape, 100 | const glm::vec2& _position, float _scale, 101 | LineMetrics& _metrics) { 102 | AtlasGlyph atlasGlyph; 103 | if (!m_atlas.getGlyph(_font, {_shape.face, _shape.codepoint}, atlasGlyph)) { 104 | return; 105 | } 106 | 107 | Rect rect; 108 | setupRect(_shape, _position, _scale, rect, atlasGlyph); 109 | 110 | Quad quad; 111 | m_matrix.transformRect(rect, quad); 112 | 113 | if (m_hasClip && clip(quad)) { 114 | return; 115 | } 116 | 117 | m_mesh.drawGlyph(quad, atlasGlyph); 118 | 119 | // FIXME: account for matrix transform 120 | // return glm::vec4(atlasGlyph.glyph->u1, 121 | // atlasGlyph.glyph->u2, 122 | // atlasGlyph.glyph->v1, 123 | // atlasGlyph.glyph->v2); 124 | } 125 | 126 | glm::vec2 TextBatch::draw(const LineLayout& _line, glm::vec2 _position, LineMetrics& _metrics) { 127 | return draw(_line, 0, _line.shapes().size(), _position, _metrics); 128 | } 129 | 130 | glm::vec2 TextBatch::drawShapeRange(const LineLayout& _line, size_t _start, size_t _end, 131 | glm::vec2 _position, LineMetrics& _metrics) { 132 | 133 | for (size_t j = _start; j < _end; j++) { 134 | auto& c = _line.shapes()[j]; 135 | if (!c.isSpace) { 136 | drawShape(_line.font(), c, _position, _line.scale(), _metrics); 137 | } 138 | 139 | _position.x += _line.advance(c); 140 | } 141 | return _position; 142 | } 143 | 144 | glm::vec2 TextBatch::draw(const LineLayout& _line, size_t _start, size_t _end, 145 | glm::vec2 _position, LineMetrics& _metrics) { 146 | 147 | float startX = _position.x; 148 | 149 | for (size_t j = _start; j < _end; j++) { 150 | auto& c = _line.shapes()[j]; 151 | if (!c.isSpace) { 152 | drawShape(_line.font(), c, _position, _line.scale(), _metrics); 153 | } 154 | 155 | _position.x += _line.advance(c); 156 | if (c.mustBreak) { 157 | _position.x = startX; 158 | _position.y += _line.height(); 159 | } 160 | } 161 | 162 | _position.y += _line.height(); 163 | 164 | return _position; 165 | } 166 | 167 | glm::vec2 TextBatch::draw(const LineLayout& _line, glm::vec2 _position, float _width, LineMetrics& _metrics) { 168 | 169 | if (_line.shapes().empty()) { return _position; } 170 | 171 | float lineWidth = 0; 172 | float startX = _position.x; 173 | 174 | float adv = 0; 175 | size_t shapeCount = 0; 176 | 177 | float lastWidth = 0; 178 | size_t lastShape = 0; 179 | size_t startShape = 0; 180 | 181 | for (auto& shape : _line.shapes()) { 182 | 183 | if (!shape.cluster) { 184 | shapeCount++; 185 | lineWidth += _line.advance(shape); 186 | continue; 187 | } 188 | 189 | shapeCount++; 190 | lineWidth += _line.advance(shape); 191 | 192 | // is break - or must break? 193 | if (shape.canBreak || shape.mustBreak) { 194 | lastShape = shapeCount; 195 | lastWidth = lineWidth; 196 | } 197 | 198 | if (lastShape != 0 && (lineWidth > _width || shape.mustBreak)) { 199 | auto& endShape = _line.shapes()[lastShape-1]; 200 | if (endShape.isSpace) { 201 | lineWidth -= _line.advance(endShape); 202 | lastWidth -= _line.advance(endShape); 203 | } 204 | 205 | adv = std::max(adv, drawShapeRange(_line, startShape, lastShape, 206 | _position, _metrics).x); 207 | 208 | lineWidth -= lastWidth; 209 | 210 | startShape = lastShape; 211 | lastShape = 0; 212 | 213 | _position.y += _line.height(); 214 | _position.x = startX; 215 | } 216 | } 217 | 218 | if (startShape < shapeCount) { 219 | adv = std::max(adv, drawShapeRange(_line, startShape, shapeCount, 220 | _position, _metrics).x); 221 | _position.y += _line.height(); 222 | } 223 | 224 | _position.x = adv; 225 | return _position; 226 | } 227 | 228 | float TextBatch::draw(const LineLayout& _line, const LineSampler& _path, 229 | float _offsetX, float _offsetY) { 230 | 231 | bool reverse = false; //(line.direction() == HB_DIRECTION_RTL); 232 | float direction = reverse ? -1 : 1; 233 | // float sampleSize = 0.1 * line.height(); 234 | 235 | auto& font = _line.font(); 236 | float scale = _line.scale(); 237 | 238 | glm::vec2 position; 239 | float angle; 240 | 241 | for (auto& shape : DirectionalRange(_line.shapes(), reverse)) { 242 | //float half = 0.5f * line.advance(shape) * direction; 243 | float half = 0.5f * shape.advance * scale * direction; 244 | _offsetX += half; 245 | 246 | if (!shape.isSpace) { 247 | _path.get(_offsetX, position, angle); 248 | 249 | m_matrix.setTranslation(position); 250 | 251 | //m_matrix.rotateZ(path.offset2SampledAngle(offsetX, sampleSize)); 252 | m_matrix.rotateZ(angle); 253 | 254 | drawTransformedShape(font, shape, -half, _offsetY, scale); 255 | } 256 | _offsetX += half; 257 | } 258 | return _offsetX; 259 | } 260 | 261 | } 262 | -------------------------------------------------------------------------------- /src/alfons/textBatch.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adaption to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "lineLayout.h" 14 | #include "quadMatrix.h" 15 | 16 | #include "path/lineSampler.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace alfons { 24 | 25 | struct MeshCallback; 26 | struct AtlasGlyph; 27 | class GlyphAtlas; 28 | 29 | // Declared as a struct for possible other informations about the line 30 | struct LineMetrics { 31 | glm::vec4 aabb = { 32 | std::numeric_limits::max(), 33 | std::numeric_limits::max(), 34 | std::numeric_limits::min(), 35 | std::numeric_limits::min() 36 | }; 37 | float height() { 38 | if (aabb[1] != std::numeric_limits::max()) { 39 | return aabb[3] - aabb[1]; 40 | } 41 | // Not initialized 42 | return 0; 43 | } 44 | 45 | void addExtents(glm::vec4 other) { 46 | aabb.x = std::min(aabb.x, other.x); 47 | aabb.y = std::min(aabb.y, other.y); 48 | aabb.z = std::max(aabb.z, other.z); 49 | aabb.w = std::max(aabb.w, other.w); 50 | } 51 | }; 52 | 53 | extern LineMetrics NO_METRICS; 54 | 55 | class TextBatch { 56 | public: 57 | TextBatch(GlyphAtlas& _atlas, MeshCallback& _mesh); 58 | 59 | void setClip(const Rect& clipRect); 60 | void setClip(float x1, float y1, float x2, float y2); 61 | 62 | void clearClip() { m_hasClip = false; } 63 | 64 | /* Use current QuadMatrix for transform */ 65 | void drawTransformedShape(const Font& font, const Shape& shape, const glm::vec2& position, 66 | float scale, LineMetrics& metrics = NO_METRICS); 67 | 68 | /* Use current QuadMatrix for transform */ 69 | inline void drawTransformedShape(const Font& font, const Shape& shape, float x, float y, 70 | float scale, LineMetrics& metrics = NO_METRICS) { 71 | drawTransformedShape(font, shape, glm::vec2(x, y), scale, metrics); 72 | } 73 | 74 | void drawShape(const Font& font, const Shape& shape, const glm::vec2& position, 75 | float scale, LineMetrics& metrics = NO_METRICS); 76 | 77 | glm::vec2 drawShapeRange(const LineLayout& _line, size_t _start, size_t _end, 78 | glm::vec2 _position, LineMetrics& _metrics = NO_METRICS); 79 | 80 | glm::vec2 draw(const LineLayout& line, glm::vec2 position, LineMetrics& metrics = NO_METRICS); 81 | 82 | glm::vec2 draw(const LineLayout& line, float x, float y, LineMetrics& metrics = NO_METRICS) { 83 | return draw(line, {x, y}, metrics); 84 | } 85 | 86 | glm::vec2 draw(const LineLayout& line, glm::vec2 position, float width, 87 | LineMetrics& metrics = NO_METRICS); 88 | 89 | glm::vec2 draw(const LineLayout& line, size_t start, size_t end, glm::vec2 position, 90 | LineMetrics& metrics = NO_METRICS); 91 | 92 | float draw(const LineLayout& line, const LineSampler& path, 93 | float offsetX = 0, float offsetY = 0); 94 | 95 | QuadMatrix& matrix() { return m_matrix; } 96 | 97 | protected: 98 | GlyphAtlas& m_atlas; 99 | MeshCallback& m_mesh; 100 | 101 | bool m_hasClip = false; 102 | Rect m_clip; 103 | 104 | QuadMatrix m_matrix; 105 | 106 | inline bool clip(Rect& rect) const; 107 | inline bool clip(Quad& quad) const; 108 | 109 | inline void setupRect(const Shape& shape, const glm::vec2& position, 110 | float sizeRatio, Rect& rect, AtlasGlyph& glyph); 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /src/alfons/textShaper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #include "textShaper.h" 12 | 13 | #include 14 | #include "linebreak.h" 15 | #include "scrptrun.h" 16 | #include "unicode/unistr.h" 17 | #include "unicode/uscript.h" 18 | #include "unicode/ubidi.h" 19 | 20 | #include "logger.h" 21 | 22 | #define FT_INV_SCALE (1.f/64.f) 23 | 24 | namespace alfons { 25 | 26 | struct TextRun { 27 | size_t start = 0; 28 | size_t end = 0; 29 | 30 | hb_script_t script = HB_SCRIPT_INVALID; 31 | hb_language_t language = HB_LANGUAGE_INVALID; 32 | hb_direction_t direction = HB_DIRECTION_INVALID; 33 | 34 | TextRun() {} 35 | 36 | size_t length() const { return end - start; } 37 | 38 | TextRun(size_t _start, size_t _end, hb_script_t _script, 39 | hb_language_t _language, hb_direction_t _direction) 40 | : start(_start), 41 | end(_end), 42 | script(_script), 43 | language(_language), 44 | direction(_direction) { } 45 | }; 46 | 47 | struct TextLine { 48 | 49 | template 50 | struct Item { 51 | size_t start; 52 | size_t end; 53 | T data; 54 | 55 | Item(size_t _start, size_t _end, T _data) 56 | : start(_start), end(_end), data(_data) {} 57 | }; 58 | 59 | using ScriptAndLanguageItem = Item>; 60 | using DirectionItem = Item; 61 | using LineItem = Item; 62 | 63 | // Input 64 | icu::UnicodeString* text; 65 | size_t offset; 66 | hb_language_t langHint; 67 | hb_direction_t overallDirection; 68 | 69 | // Intermediate 70 | std::vector scriptLangItems; 71 | std::vector directionItems; 72 | 73 | // Result 74 | std::vector runs; 75 | 76 | void set(icu::UnicodeString& _input, size_t _offset, 77 | hb_language_t _langHint = HB_LANGUAGE_INVALID, 78 | hb_direction_t _overallDirection = HB_DIRECTION_INVALID) { 79 | runs.clear(); 80 | directionItems.clear(); 81 | scriptLangItems.clear(); 82 | 83 | text = &_input; 84 | offset = _offset; 85 | langHint = _langHint; 86 | overallDirection = _overallDirection; 87 | } 88 | }; 89 | 90 | class TextItemizer { 91 | public: 92 | 93 | TextItemizer(const LangHelper& _langHelper); 94 | ~TextItemizer(); 95 | 96 | void processLine(TextLine& _line); 97 | 98 | protected: 99 | const LangHelper& langHelper; 100 | UBiDi* bidi; 101 | int bidiBufferSize = 0; 102 | 103 | void itemizeScriptAndLanguage(TextLine& _line) const; 104 | void itemizeDirection(TextLine& _line); 105 | void mergeItems(TextLine& _line) const; 106 | }; 107 | 108 | template 109 | typename T::const_iterator findEnclosingRange(const T& _items, uint32_t _position) { 110 | for (auto it = _items.begin(); it != _items.end(); ++it) { 111 | if ((it->start <= _position) && (it->end > _position)) 112 | return it; 113 | } 114 | return _items.end(); 115 | } 116 | 117 | auto icuScriptToHB(UScriptCode _script) -> hb_script_t { 118 | if (_script == USCRIPT_INVALID_CODE) { 119 | return HB_SCRIPT_INVALID; 120 | } 121 | 122 | return hb_script_from_string(uscript_getShortName(_script), -1); 123 | } 124 | 125 | auto icuDirectionToHB(UBiDiDirection _direction) -> hb_direction_t { 126 | return (_direction == UBIDI_RTL) ? HB_DIRECTION_RTL : HB_DIRECTION_LTR; 127 | } 128 | 129 | TextItemizer::TextItemizer(const LangHelper& _langHelper) 130 | : langHelper(_langHelper), bidi(nullptr) { 131 | } 132 | 133 | TextItemizer::~TextItemizer() { 134 | if (bidiBufferSize > 0) 135 | ubidi_close(bidi); 136 | } 137 | 138 | void TextItemizer::processLine(TextLine& _line) { 139 | if (_line.scriptLangItems.empty()) 140 | itemizeScriptAndLanguage(_line); 141 | 142 | if (_line.directionItems.empty()) 143 | itemizeDirection(_line); 144 | 145 | mergeItems(_line); 146 | 147 | if (!_line.runs.empty()) { 148 | if (_line.langHint == HB_LANGUAGE_INVALID) 149 | _line.langHint = _line.runs.front().language; 150 | 151 | if (_line.overallDirection == HB_DIRECTION_INVALID) 152 | _line.overallDirection = _line.runs.front().direction; 153 | } 154 | } 155 | 156 | void TextItemizer::itemizeScriptAndLanguage(TextLine& _line) const { 157 | ScriptRun scriptRun(_line.text->getBuffer(), _line.text->length()); 158 | 159 | while (scriptRun.next()) { 160 | auto start = scriptRun.getScriptStart(); 161 | auto end = scriptRun.getScriptEnd(); 162 | auto code = scriptRun.getScriptCode(); 163 | 164 | auto script = icuScriptToHB(code); 165 | const char* lang = hb_language_to_string(_line.langHint); 166 | 167 | if (lang != nullptr && langHelper.matchLanguage(script, std::string(lang))) { 168 | _line.scriptLangItems.emplace_back(start, end, std::make_pair(script, _line.langHint)); 169 | continue; 170 | 171 | } 172 | auto& language = langHelper.detectLanguage(script); 173 | 174 | _line.scriptLangItems.emplace_back(start, end, std::make_pair(script, 175 | hb_language_from_string(language.c_str(), -1))); 176 | } 177 | } 178 | 179 | void TextItemizer::itemizeDirection(TextLine& _line) { 180 | /* 181 | * If overallDirection is undefined: 182 | * The paragraph-level will be determined from the text. 183 | * 184 | * http://www.icu-project.org/apiref/icu4c/ubidi_8h.html#abdfe9e113a19dd8521d3b7ac8220fe11 185 | */ 186 | UBiDiLevel paraLevel = (_line.overallDirection == HB_DIRECTION_INVALID) 187 | ? UBIDI_DEFAULT_LTR 188 | : ((_line.overallDirection == HB_DIRECTION_RTL) ? 1 : 0); 189 | 190 | auto length = _line.text->length(); 191 | UErrorCode error = U_ZERO_ERROR; 192 | if (length == 0) { 193 | _line.directionItems.emplace_back(0, length, HB_DIRECTION_LTR); 194 | return; 195 | } 196 | 197 | if ((!bidi) || (length > bidiBufferSize)) { 198 | if (bidiBufferSize > 0) { 199 | ubidi_close(bidi); 200 | bidi = nullptr; 201 | } 202 | 203 | int size = length < 256 ? 256 : length; 204 | 205 | // FIXME maxRuns too large? 206 | bidi = ubidi_openSized(size, 10, &error); 207 | if (!U_SUCCESS(error)) { 208 | LOGE("UBIDI error alloc: %d (%d - %s)", size, error, u_errorName(error)); 209 | _line.directionItems.emplace_back(0, length, HB_DIRECTION_LTR); 210 | bidi = nullptr; 211 | return; 212 | } 213 | bidiBufferSize = size; 214 | } 215 | 216 | ubidi_setPara(bidi, _line.text->getBuffer(), length, paraLevel, 0, &error); 217 | if (!U_SUCCESS(error)) { 218 | LOGE("UBIDI error setPara %d (%d - %s)", length, error, u_errorName(error)); 219 | _line.directionItems.emplace_back(0, length, HB_DIRECTION_LTR); 220 | return; 221 | } 222 | 223 | auto direction = ubidi_getDirection(bidi); 224 | 225 | if (direction != UBIDI_MIXED) { 226 | _line.directionItems.emplace_back(0, length, icuDirectionToHB(direction)); 227 | 228 | } else { 229 | auto count = ubidi_countRuns(bidi, &error); 230 | _line.directionItems.reserve(count); 231 | 232 | for (int i = 0; i < count; ++i) { 233 | int32_t start, length; 234 | direction = ubidi_getVisualRun(bidi, i, &start, &length); 235 | _line.directionItems.emplace_back(start, start + length, 236 | icuDirectionToHB(direction)); 237 | } 238 | } 239 | } 240 | 241 | void TextItemizer::mergeItems(TextLine& _line) const { 242 | 243 | // Go through text directions 244 | for (auto& directionIt : _line.directionItems) { 245 | auto position = directionIt.start; 246 | auto end = directionIt.end; 247 | auto direction = directionIt.data; 248 | 249 | auto rtlIt = _line.runs.end(); 250 | 251 | auto scriptIt = findEnclosingRange(_line.scriptLangItems, position); 252 | 253 | while (position < end) { 254 | TextRun run; 255 | run.start = position; 256 | run.end = std::min(scriptIt->end, end); 257 | 258 | run.script = scriptIt->data.first; 259 | run.language = scriptIt->data.second; 260 | 261 | run.direction = direction; 262 | 263 | if (direction == HB_DIRECTION_LTR) { 264 | _line.runs.push_back(run); 265 | } else { 266 | rtlIt = _line.runs.insert(rtlIt, run); 267 | } 268 | 269 | position = run.end; 270 | 271 | if (scriptIt->end == position) { 272 | // next script within direction 273 | ++scriptIt; 274 | } 275 | } 276 | } 277 | } 278 | 279 | 280 | TextShaper::TextShaper() : 281 | m_itemizer(std::make_unique(m_langHelper)), 282 | m_textLine(std::make_unique()), 283 | m_hbBuffer(hb_buffer_create()) { 284 | 285 | static bool lineBreakInitialized = false; 286 | 287 | if (!lineBreakInitialized) { 288 | lineBreakInitialized = true; 289 | init_linebreak(); 290 | } 291 | } 292 | 293 | TextShaper::~TextShaper() { 294 | hb_buffer_destroy(m_hbBuffer); 295 | } 296 | 297 | bool TextShaper::processRun(const FontFace& _face, const TextRun& _run, 298 | size_t lineBreakOffset, FontFace::Metrics& _lineMetrics) { 299 | 300 | hb_shape(_face.hbFont(), m_hbBuffer, NULL, 0); 301 | 302 | auto glyphCount = hb_buffer_get_length(m_hbBuffer); 303 | const auto* glyphInfos = hb_buffer_get_glyph_infos(m_hbBuffer, NULL); 304 | const auto* glyphPositions = hb_buffer_get_glyph_positions(m_hbBuffer, NULL); 305 | 306 | bool missingGlyphs = false; 307 | bool addedGlyphs = false; 308 | 309 | for (size_t pos = 0; pos < glyphCount; pos++) { 310 | hb_codepoint_t codepoint = glyphInfos[pos].codepoint; 311 | uint32_t clusterId = glyphInfos[pos].cluster; 312 | 313 | uint32_t id; 314 | // Map cluster position to visual LTR order 315 | if (_run.direction == HB_DIRECTION_RTL) { 316 | id = _run.end - clusterId - 1; 317 | } else { 318 | id = clusterId - _run.start; 319 | } 320 | 321 | if (codepoint == 0) { 322 | if (!m_glyphAdded[id] && m_linebreaks[lineBreakOffset + clusterId] != LINEBREAK_MUSTBREAK) { 323 | missingGlyphs = true; 324 | } 325 | continue; 326 | } 327 | 328 | if (m_glyphAdded[id] && m_shapes[id].face != _face.id()) { 329 | // cluster found, with another font (e.g. 'space') 330 | continue; 331 | } 332 | 333 | auto offset = glm::vec2(glyphPositions[pos].x_offset * FT_INV_SCALE, 334 | -glyphPositions[pos].y_offset * FT_INV_SCALE); 335 | 336 | float advance = glyphPositions[pos].x_advance * FT_INV_SCALE; 337 | 338 | if (m_glyphAdded[id]) { 339 | m_glyphAdded[id] = 2; 340 | 341 | if (m_clusters.size() < m_shapes.size()) { 342 | m_clusters.resize(m_shapes.size()); 343 | } 344 | m_clusters[id].emplace_back(_face.id(), codepoint, offset, advance, 0); 345 | 346 | } else { 347 | addedGlyphs = true; 348 | m_glyphAdded[id] = 1; 349 | 350 | uint8_t breakmode = m_linebreaks[lineBreakOffset + clusterId]; 351 | 352 | uint8_t flags = 1 | // cluster start 353 | ((breakmode == LINEBREAK_MUSTBREAK) ? 2 : 0) | 354 | ((breakmode == LINEBREAK_ALLOWBREAK) ? 4 : 0) | 355 | ((breakmode == LINEBREAK_NOBREAK) ? 8 : 0) | 356 | (_face.isSpace(codepoint) ? 16 : 0); 357 | 358 | m_shapes[id] = Shape(_face.id(), codepoint, offset, advance, flags); 359 | } 360 | } 361 | 362 | if (addedGlyphs) { 363 | auto setMax = [](float& a, float b){ if (b > a) a = b; }; 364 | auto &metrics = _face.metrics(); 365 | setMax(_lineMetrics.height, metrics.height); 366 | setMax(_lineMetrics.ascent, metrics.ascent); 367 | setMax(_lineMetrics.descent, metrics.descent); 368 | setMax(_lineMetrics.lineThickness, metrics.lineThickness); 369 | setMax(_lineMetrics.underlineOffset, metrics.underlineOffset); 370 | } 371 | 372 | return !missingGlyphs; 373 | } 374 | 375 | bool TextShaper::shape(std::shared_ptr& _font, const TextLine& _line, 376 | const std::vector& _range, LineLayout& _layout) { 377 | 378 | if (_range.empty()) { return true; } 379 | 380 | hb_language_t defaultLang = hb_language_get_default(); 381 | 382 | int numChars = _line.text->length(); 383 | 384 | std::vector shapes; 385 | shapes.reserve(numChars); 386 | 387 | for (const TextRun& run : _range) { 388 | size_t length = run.end - run.start; 389 | 390 | m_shapes.assign(length, {}); 391 | m_glyphAdded.assign(length, 0); 392 | 393 | bool missingGlyphs = true; 394 | 395 | for (auto& face : _font->getFontSet(run.language)) { 396 | 397 | if (!face->load()) { continue; } 398 | 399 | // Setup harfbuzz buffer with current TextRun 400 | // TODO check why the contents must be set again! 401 | // - check if it is possible to only reset the hb_buffer position 402 | // - or determine the font used for each run in advance. 403 | hb_buffer_clear_contents(m_hbBuffer); 404 | hb_buffer_add_utf16(m_hbBuffer, (const uint16_t*)_line.text->getBuffer(), 405 | _line.text->length(), 406 | run.start, run.end - run.start); 407 | 408 | hb_buffer_set_script(m_hbBuffer, run.script); 409 | hb_buffer_set_direction(m_hbBuffer, run.direction); 410 | if (run.language == HB_LANGUAGE_INVALID) { 411 | hb_buffer_set_language(m_hbBuffer, defaultLang); 412 | } else { 413 | hb_buffer_set_language(m_hbBuffer, run.language); 414 | } 415 | 416 | if (processRun(*face, run, _line.offset, _layout.metrics())) { 417 | missingGlyphs = false; 418 | break; 419 | } 420 | } 421 | 422 | if (missingGlyphs) { _layout.setMissingGlyphs(); } 423 | 424 | for (size_t i = 0; i < length; i++) { 425 | if (m_glyphAdded[i] && m_shapes[i].codepoint != 0) { 426 | 427 | shapes.push_back(m_shapes[i]); 428 | 429 | if (m_glyphAdded[i] == 2) { 430 | for (auto& shape : m_clusters[i]) { 431 | shapes.push_back(shape); 432 | } 433 | m_clusters[i].clear(); 434 | } 435 | } 436 | } 437 | } 438 | 439 | if (shapes.empty()) { return false; } 440 | 441 | // Last char on line: Must break at end of line 442 | for (auto it = shapes.rbegin(); it != shapes.rend(); ++it) { 443 | if (it->cluster) { 444 | it->mustBreak = true; 445 | break; 446 | } 447 | } 448 | 449 | _layout.addShapes(shapes); 450 | 451 | return true; 452 | } 453 | 454 | LineLayout TextShaper::shape(std::shared_ptr& _font, const std::string& _text, 455 | hb_language_t _langHint, hb_direction_t _direction) { 456 | 457 | auto text = icu::UnicodeString::fromUTF8(_text); 458 | return shapeICU(_font, text, 1, 0, _langHint, _direction); 459 | } 460 | 461 | LineLayout TextShaper::shapeICU(std::shared_ptr& _font, const icu::UnicodeString& _text, 462 | int _minLineChars, int _maxLineChars, 463 | hb_language_t _langHint, hb_direction_t _direction) { 464 | LineLayout layout(_font); 465 | 466 | int numChars = _text.length(); 467 | 468 | const char* language = nullptr; 469 | if (_langHint != HB_LANGUAGE_INVALID) { 470 | language = hb_language_to_string(_langHint); 471 | } 472 | 473 | m_linebreaks.resize(numChars); 474 | set_linebreaks_utf16((const uint16_t*)_text.getBuffer(), 475 | numChars, language, 476 | m_linebreaks.data()); 477 | 478 | auto &line = *m_textLine; 479 | int start = 0; 480 | int lastBreak = -1; 481 | 482 | for (int pos = 0; pos < numChars;) { 483 | 484 | bool breakLine = m_linebreaks[pos] == LINEBREAK_MUSTBREAK; 485 | if (breakLine) { 486 | lastBreak = pos; 487 | 488 | // Last char is always MUSTBREAK 489 | // Remove linebreak after final char, this interfers with RTL text. 490 | if (pos == numChars - 1) { 491 | m_linebreaks[pos] = LINEBREAK_NOBREAK; 492 | } 493 | } 494 | 495 | if (_maxLineChars > 0) { 496 | if (m_linebreaks[pos] == LINEBREAK_ALLOWBREAK) { 497 | lastBreak = pos; 498 | } 499 | // Break if line got longer than maxLineChars and there is 500 | // a new ALLOWBREAK position since last break. 501 | if (pos - start >= _maxLineChars-1 && lastBreak - start >= _minLineChars) { 502 | breakLine = true; 503 | } 504 | } 505 | 506 | if (!breakLine) { 507 | pos++; 508 | continue; 509 | } 510 | 511 | auto cur = _text.tempSubStringBetween(start, lastBreak+1); 512 | 513 | line.set(cur, start, _langHint, _direction); 514 | m_itemizer->processLine(line); 515 | 516 | shape(_font, line, line.runs, layout); 517 | 518 | pos = start = lastBreak+1; 519 | } 520 | 521 | return layout; 522 | } 523 | 524 | } 525 | -------------------------------------------------------------------------------- /src/alfons/textShaper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include "lineLayout.h" 14 | #include "font.h" 15 | #include "langHelper.h" 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | struct hb_buffer_t; 23 | namespace alfons { 24 | 25 | struct TextLine; 26 | struct TextRun; 27 | class TextItemizer; 28 | 29 | class TextShaper { 30 | public: 31 | TextShaper(); 32 | ~TextShaper(); 33 | 34 | 35 | LineLayout shapeICU(std::shared_ptr& font, const icu::UnicodeString& text, 36 | int minLineChars = 1, int maxLineChars = 0, 37 | hb_language_t langHint = HB_LANGUAGE_INVALID, 38 | hb_direction_t direction = HB_DIRECTION_INVALID); 39 | 40 | LineLayout shape(std::shared_ptr& font, const std::string& text, 41 | hb_language_t langHint = HB_LANGUAGE_INVALID, 42 | hb_direction_t direction = HB_DIRECTION_INVALID); 43 | 44 | LineLayout shape(std::shared_ptr& font, const std::string& text, 45 | const std::string& langHint, 46 | hb_direction_t direction = HB_DIRECTION_INVALID) { 47 | return shape(font, text, hb_language_from_string(langHint.c_str(), -1), 48 | direction); 49 | } 50 | 51 | protected: 52 | 53 | bool shape(std::shared_ptr& font, const TextLine& line, 54 | const std::vector& range, LineLayout& layout); 55 | 56 | bool shape(std::shared_ptr& font, TextLine& line, LineLayout& layout); 57 | 58 | bool processRun(const FontFace& face, const TextRun& run, size_t lineBreakOffset, FontFace::Metrics& _lineMetrics); 59 | 60 | LangHelper m_langHelper; 61 | std::unique_ptr m_itemizer; 62 | std::unique_ptr m_textLine; 63 | 64 | hb_buffer_t* m_hbBuffer; 65 | 66 | std::vector m_shapes; 67 | // Storage for additional Glyphs in a cluster. 68 | // https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Characters_grapheme_clusters_and_glyphs 69 | std::vector> m_clusters; 70 | 71 | std::vector m_glyphAdded; 72 | std::vector m_linebreaks; 73 | 74 | }; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/alfons/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Based on The New Chronotext Toolkit 3 | * Copyright (C) 2014, Ariel Malka - All rights reserved. 4 | * 5 | * Adapted to Alfons 6 | * Copyright (C) 2015, Hannes Janetzek 7 | * 8 | * The following source-code is distributed under the Simplified BSD License. 9 | */ 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace alfons { 19 | 20 | template 21 | static int search(T* array, T value, int min, int max) { 22 | int mid = (min + max) >> 1; 23 | 24 | while (min < mid) { 25 | if (array[mid - 1] < value) { 26 | min = mid; 27 | } else if (array[mid - 1] > value) { 28 | max = mid; 29 | } else { 30 | min = max = mid; 31 | } 32 | mid = (min + max) >> 1; 33 | } 34 | 35 | return mid - 1; 36 | } 37 | 38 | template 39 | static inline int search(const std::vector& array, float value, int min, 40 | int max) { 41 | return search((T*)array.data(), value, min, max); 42 | } 43 | 44 | template 45 | struct Iterator { 46 | T& container; 47 | bool reverse; 48 | 49 | typedef typename T::const_iterator I; 50 | 51 | // Works for const and non-const std containers 52 | typedef typename std::iterator_traits::reference R; 53 | 54 | struct InnerIterator { 55 | I i; 56 | bool reverse; 57 | 58 | InnerIterator(I i, bool reverse) : i(i), reverse(reverse) {} 59 | R operator*() { return *i; } 60 | I operator++() { return (reverse ? --i : ++i); } 61 | bool operator!=(const InnerIterator& o) { return i != o.i; } 62 | }; 63 | 64 | Iterator(T& container, bool reverse) 65 | : container(container), reverse(reverse) {} 66 | InnerIterator begin() { 67 | return InnerIterator(reverse ? --container.end() : container.begin(), 68 | reverse); 69 | } 70 | InnerIterator end() { 71 | return InnerIterator(reverse ? --container.begin() : container.end(), 72 | reverse); 73 | } 74 | }; 75 | template 76 | Iterator DirectionalRange(T& container, bool reverse = false) { 77 | return Iterator(container, reverse); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/linebreak/linebreak.c: -------------------------------------------------------------------------------- 1 | /* vim: set tabstop=4 shiftwidth=4: */ 2 | 3 | /* 4 | * Line breaking in a Unicode sequence. Designed to be used in a 5 | * generic text renderer. 6 | * 7 | * Copyright (C) 2008-2011 Wu Yongwei 8 | * 9 | * This software is provided 'as-is', without any express or implied 10 | * warranty. In no event will the author be held liable for any damages 11 | * arising from the use of this software. 12 | * 13 | * Permission is granted to anyone to use this software for any purpose, 14 | * including commercial applications, and to alter it and redistribute 15 | * it freely, subject to the following restrictions: 16 | * 17 | * 1. The origin of this software must not be misrepresented; you must 18 | * not claim that you wrote the original software. If you use this 19 | * software in a product, an acknowledgement in the product 20 | * documentation would be appreciated but is not required. 21 | * 2. Altered source versions must be plainly marked as such, and must 22 | * not be misrepresented as being the original software. 23 | * 3. This notice may not be removed or altered from any source 24 | * distribution. 25 | * 26 | * The main reference is Unicode Standard Annex 14 (UAX #14): 27 | * 28 | * 29 | * When this library was designed, this annex was at Revision 19, for 30 | * Unicode 5.0.0: 31 | * 32 | * 33 | * This library has been updated according to Revision 26, for 34 | * Unicode 6.0.0: 35 | * 36 | * 37 | * The Unicode Terms of Use are available at 38 | * 39 | */ 40 | 41 | /** 42 | * @file linebreak.c 43 | * 44 | * Implementation of the line breaking algorithm as described in Unicode 45 | * Standard Annex 14. 46 | * 47 | * @version 2.1, 2011/05/07 48 | * @author Wu Yongwei 49 | */ 50 | 51 | #include 52 | #include 53 | #include 54 | #include "linebreak.h" 55 | #include "linebreakdef.h" 56 | 57 | /** 58 | * Size of the second-level index to the line breaking properties. 59 | */ 60 | #define LINEBREAK_INDEX_SIZE 40 61 | 62 | /** 63 | * Version number of the library. 64 | */ 65 | const int linebreak_version = LINEBREAK_VERSION; 66 | 67 | /** 68 | * Enumeration of break actions. They are used in the break action 69 | * pair table below. 70 | */ 71 | enum BreakAction 72 | { 73 | DIR_BRK, /**< Direct break opportunity */ 74 | IND_BRK, /**< Indirect break opportunity */ 75 | CMI_BRK, /**< Indirect break opportunity for combining marks */ 76 | CMP_BRK, /**< Prohibited break for combining marks */ 77 | PRH_BRK /**< Prohibited break */ 78 | }; 79 | 80 | /** 81 | * Break action pair table. This is a direct mapping of Table 2 of 82 | * Unicode Standard Annex 14, Revision 24. 83 | */ 84 | static enum BreakAction baTable[LBP_JT][LBP_JT] = { 85 | { /* OP */ 86 | PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, 87 | PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, 88 | PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, CMP_BRK, 89 | PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK, PRH_BRK }, 90 | { /* CL */ 91 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, PRH_BRK, PRH_BRK, 92 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 93 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 94 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 95 | { /* CP */ 96 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, PRH_BRK, PRH_BRK, 97 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, DIR_BRK, 98 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 99 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 100 | { /* QU */ 101 | PRH_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 102 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, 103 | IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, CMI_BRK, 104 | PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK }, 105 | { /* GL */ 106 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 107 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, 108 | IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, CMI_BRK, 109 | PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK }, 110 | { /* NS */ 111 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 112 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 113 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 114 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 115 | { /* EX */ 116 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 117 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 118 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 119 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 120 | { /* SY */ 121 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 122 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, 123 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 124 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 125 | { /* IS */ 126 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 127 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, 128 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 129 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 130 | { /* PR */ 131 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 132 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK, IND_BRK, 133 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 134 | PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK }, 135 | { /* PO */ 136 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 137 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, 138 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 139 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 140 | { /* NU */ 141 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 142 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, DIR_BRK, 143 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 144 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 145 | { /* AL */ 146 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 147 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, 148 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 149 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 150 | { /* ID */ 151 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 152 | PRH_BRK, PRH_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 153 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 154 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 155 | { /* IN */ 156 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 157 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 158 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 159 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 160 | { /* HY */ 161 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, DIR_BRK, IND_BRK, PRH_BRK, 162 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, 163 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 164 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 165 | { /* BA */ 166 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, DIR_BRK, IND_BRK, PRH_BRK, 167 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 168 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 169 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 170 | { /* BB */ 171 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 172 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, 173 | IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, CMI_BRK, 174 | PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK }, 175 | { /* B2 */ 176 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 177 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 178 | DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, PRH_BRK, PRH_BRK, CMI_BRK, 179 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 180 | { /* ZW */ 181 | DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 182 | DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 183 | DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, PRH_BRK, DIR_BRK, 184 | DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 185 | { /* CM */ 186 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 187 | PRH_BRK, PRH_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK, DIR_BRK, 188 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 189 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK }, 190 | { /* WJ */ 191 | IND_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 192 | PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, 193 | IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, CMI_BRK, 194 | PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK }, 195 | { /* H2 */ 196 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 197 | PRH_BRK, PRH_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 198 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 199 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK }, 200 | { /* H3 */ 201 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 202 | PRH_BRK, PRH_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 203 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 204 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, IND_BRK }, 205 | { /* JL */ 206 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 207 | PRH_BRK, PRH_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 208 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 209 | PRH_BRK, IND_BRK, IND_BRK, IND_BRK, IND_BRK, DIR_BRK }, 210 | { /* JV */ 211 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 212 | PRH_BRK, PRH_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 213 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 214 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, IND_BRK, IND_BRK }, 215 | { /* JT */ 216 | DIR_BRK, PRH_BRK, PRH_BRK, IND_BRK, IND_BRK, IND_BRK, PRH_BRK, 217 | PRH_BRK, PRH_BRK, DIR_BRK, IND_BRK, DIR_BRK, DIR_BRK, DIR_BRK, 218 | IND_BRK, IND_BRK, IND_BRK, DIR_BRK, DIR_BRK, PRH_BRK, CMI_BRK, 219 | PRH_BRK, DIR_BRK, DIR_BRK, DIR_BRK, DIR_BRK, IND_BRK } 220 | }; 221 | 222 | /** 223 | * Struct for the second-level index to the line breaking properties. 224 | */ 225 | struct LineBreakPropertiesIndex 226 | { 227 | utf32_t end; /**< End coding point */ 228 | struct LineBreakProperties *lbp;/**< Pointer to line breaking properties */ 229 | }; 230 | 231 | /** 232 | * Second-level index to the line breaking properties. 233 | */ 234 | static struct LineBreakPropertiesIndex lb_prop_index[LINEBREAK_INDEX_SIZE] = 235 | { 236 | { 0xFFFFFFFF, lb_prop_default } 237 | }; 238 | 239 | /** 240 | * Initializes the second-level index to the line breaking properties. 241 | * If it is not called, the performance of #get_char_lb_class_lang (and 242 | * thus the main functionality) can be pretty bad, especially for big 243 | * code points like those of Chinese. 244 | */ 245 | void init_linebreak(void) 246 | { 247 | size_t i; 248 | size_t iPropDefault; 249 | size_t len; 250 | size_t step; 251 | 252 | len = 0; 253 | while (lb_prop_default[len].prop != LBP_Undefined) 254 | ++len; 255 | step = len / LINEBREAK_INDEX_SIZE; 256 | iPropDefault = 0; 257 | for (i = 0; i < LINEBREAK_INDEX_SIZE; ++i) 258 | { 259 | lb_prop_index[i].lbp = lb_prop_default + iPropDefault; 260 | iPropDefault += step; 261 | lb_prop_index[i].end = lb_prop_default[iPropDefault].start - 1; 262 | } 263 | lb_prop_index[--i].end = 0xFFFFFFFF; 264 | } 265 | 266 | /** 267 | * Gets the language-specific line breaking properties. 268 | * 269 | * @param lang language of the text 270 | * @return pointer to the language-specific line breaking 271 | * properties array if found; \c NULL otherwise 272 | */ 273 | static struct LineBreakProperties *get_lb_prop_lang(const char *lang) 274 | { 275 | struct LineBreakPropertiesLang *lbplIter; 276 | if (lang != NULL) 277 | { 278 | for (lbplIter = lb_prop_lang_map; lbplIter->lang != NULL; ++lbplIter) 279 | { 280 | if (strncmp(lang, lbplIter->lang, lbplIter->namelen) == 0) 281 | { 282 | return lbplIter->lbp; 283 | } 284 | } 285 | } 286 | return NULL; 287 | } 288 | 289 | /** 290 | * Gets the line breaking class of a character from a line breaking 291 | * properties array. 292 | * 293 | * @param ch character to check 294 | * @param lbp pointer to the line breaking properties array 295 | * @return the line breaking class if found; \c LBP_XX otherwise 296 | */ 297 | static enum LineBreakClass get_char_lb_class( 298 | utf32_t ch, 299 | struct LineBreakProperties *lbp) 300 | { 301 | while (lbp->prop != LBP_Undefined && ch >= lbp->start) 302 | { 303 | if (ch <= lbp->end) 304 | return lbp->prop; 305 | ++lbp; 306 | } 307 | return LBP_XX; 308 | } 309 | 310 | /** 311 | * Gets the line breaking class of a character from the default line 312 | * breaking properties array. 313 | * 314 | * @param ch character to check 315 | * @return the line breaking class if found; \c LBP_XX otherwise 316 | */ 317 | static enum LineBreakClass get_char_lb_class_default( 318 | utf32_t ch) 319 | { 320 | size_t i = 0; 321 | while (ch > lb_prop_index[i].end) 322 | ++i; 323 | assert(i < LINEBREAK_INDEX_SIZE); 324 | return get_char_lb_class(ch, lb_prop_index[i].lbp); 325 | } 326 | 327 | /** 328 | * Gets the line breaking class of a character for a specific 329 | * language. This function will check the language-specific data first, 330 | * and then the default data if there is no language-specific property 331 | * available for the character. 332 | * 333 | * @param ch character to check 334 | * @param lbpLang pointer to the language-specific line breaking 335 | * properties array 336 | * @return the line breaking class if found; \c LBP_XX 337 | * otherwise 338 | */ 339 | static enum LineBreakClass get_char_lb_class_lang( 340 | utf32_t ch, 341 | struct LineBreakProperties *lbpLang) 342 | { 343 | enum LineBreakClass lbcResult; 344 | 345 | /* Find the language-specific line breaking class for a character */ 346 | if (lbpLang) 347 | { 348 | lbcResult = get_char_lb_class(ch, lbpLang); 349 | if (lbcResult != LBP_XX) 350 | return lbcResult; 351 | } 352 | 353 | /* Find the generic language-specific line breaking class, if no 354 | * language context is provided, or language-specific data are not 355 | * available for the specific character in the specified language */ 356 | return get_char_lb_class_default(ch); 357 | } 358 | 359 | /** 360 | * Resolves the line breaking class for certain ambiguous or complicated 361 | * characters. They are treated in a simplistic way in this 362 | * implementation. 363 | * 364 | * @param lbc line breaking class to resolve 365 | * @param lang language of the text 366 | * @return the resolved line breaking class 367 | */ 368 | static enum LineBreakClass resolve_lb_class( 369 | enum LineBreakClass lbc, 370 | const char *lang) 371 | { 372 | switch (lbc) 373 | { 374 | case LBP_AI: 375 | if (lang != NULL && 376 | (strncmp(lang, "zh", 2) == 0 || /* Chinese */ 377 | strncmp(lang, "ja", 2) == 0 || /* Japanese */ 378 | strncmp(lang, "ko", 2) == 0)) /* Korean */ 379 | { 380 | return LBP_ID; 381 | } 382 | /* Fall through */ 383 | case LBP_SA: 384 | case LBP_SG: 385 | case LBP_XX: 386 | return LBP_AL; 387 | default: 388 | return lbc; 389 | } 390 | } 391 | 392 | /** 393 | * Gets the next Unicode character in a UTF-8 sequence. The index will 394 | * be advanced to the next complete character, unless the end of string 395 | * is reached in the middle of a UTF-8 sequence. 396 | * 397 | * @param[in] s input UTF-8 string 398 | * @param[in] len length of the string in bytes 399 | * @param[in,out] ip pointer to the index 400 | * @return the Unicode character beginning at the index; or 401 | * #EOS if end of input is encountered 402 | */ 403 | utf32_t lb_get_next_char_utf8( 404 | const utf8_t *s, 405 | size_t len, 406 | size_t *ip) 407 | { 408 | utf8_t ch; 409 | utf32_t res; 410 | 411 | assert(*ip <= len); 412 | if (*ip == len) 413 | return EOS; 414 | ch = s[*ip]; 415 | 416 | if (ch < 0xC2 || ch > 0xF4) 417 | { /* One-byte sequence, tail (should not occur), or invalid */ 418 | *ip += 1; 419 | return ch; 420 | } 421 | else if (ch < 0xE0) 422 | { /* Two-byte sequence */ 423 | if (*ip + 2 > len) 424 | return EOS; 425 | res = ((ch & 0x1F) << 6) + (s[*ip + 1] & 0x3F); 426 | *ip += 2; 427 | return res; 428 | } 429 | else if (ch < 0xF0) 430 | { /* Three-byte sequence */ 431 | if (*ip + 3 > len) 432 | return EOS; 433 | res = ((ch & 0x0F) << 12) + 434 | ((s[*ip + 1] & 0x3F) << 6) + 435 | ((s[*ip + 2] & 0x3F)); 436 | *ip += 3; 437 | return res; 438 | } 439 | else 440 | { /* Four-byte sequence */ 441 | if (*ip + 4 > len) 442 | return EOS; 443 | res = ((ch & 0x07) << 18) + 444 | ((s[*ip + 1] & 0x3F) << 12) + 445 | ((s[*ip + 2] & 0x3F) << 6) + 446 | ((s[*ip + 3] & 0x3F)); 447 | *ip += 4; 448 | return res; 449 | } 450 | } 451 | 452 | /** 453 | * Gets the next Unicode character in a UTF-16 sequence. The index will 454 | * be advanced to the next complete character, unless the end of string 455 | * is reached in the middle of a UTF-16 surrogate pair. 456 | * 457 | * @param[in] s input UTF-16 string 458 | * @param[in] len length of the string in words 459 | * @param[in,out] ip pointer to the index 460 | * @return the Unicode character beginning at the index; or 461 | * #EOS if end of input is encountered 462 | */ 463 | utf32_t lb_get_next_char_utf16( 464 | const utf16_t *s, 465 | size_t len, 466 | size_t *ip) 467 | { 468 | utf16_t ch; 469 | 470 | assert(*ip <= len); 471 | if (*ip == len) 472 | return EOS; 473 | ch = s[(*ip)++]; 474 | 475 | if (ch < 0xD800 || ch > 0xDBFF) 476 | { /* If the character is not a high surrogate */ 477 | return ch; 478 | } 479 | if (*ip == len) 480 | { /* If the input ends here (an error) */ 481 | --(*ip); 482 | return EOS; 483 | } 484 | if (s[*ip] < 0xDC00 || s[*ip] > 0xDFFF) 485 | { /* If the next character is not the low surrogate (an error) */ 486 | return ch; 487 | } 488 | /* Return the constructed character and advance the index again */ 489 | return (((utf32_t)ch & 0x3FF) << 10) + (s[(*ip)++] & 0x3FF) + 0x10000; 490 | } 491 | 492 | /** 493 | * Gets the next Unicode character in a UTF-32 sequence. The index will 494 | * be advanced to the next character. 495 | * 496 | * @param[in] s input UTF-32 string 497 | * @param[in] len length of the string in dwords 498 | * @param[in,out] ip pointer to the index 499 | * @return the Unicode character beginning at the index; or 500 | * #EOS if end of input is encountered 501 | */ 502 | utf32_t lb_get_next_char_utf32( 503 | const utf32_t *s, 504 | size_t len, 505 | size_t *ip) 506 | { 507 | assert(*ip <= len); 508 | if (*ip == len) 509 | return EOS; 510 | return s[(*ip)++]; 511 | } 512 | 513 | /** 514 | * Sets the line breaking information for a generic input string. 515 | * 516 | * @param[in] s input string 517 | * @param[in] len length of the input 518 | * @param[in] lang language of the input 519 | * @param[out] brks pointer to the output breaking data, 520 | * containing #LINEBREAK_MUSTBREAK, 521 | * #LINEBREAK_ALLOWBREAK, #LINEBREAK_NOBREAK, 522 | * or #LINEBREAK_INSIDEACHAR 523 | * @param[in] get_next_char function to get the next UTF-32 character 524 | */ 525 | void set_linebreaks( 526 | const void *s, 527 | size_t len, 528 | const char *lang, 529 | char *brks, 530 | get_next_char_t get_next_char) 531 | { 532 | utf32_t ch; 533 | enum LineBreakClass lbcCur; 534 | enum LineBreakClass lbcNew; 535 | enum LineBreakClass lbcLast; 536 | struct LineBreakProperties *lbpLang; 537 | size_t posCur = 0; 538 | size_t posLast = 0; 539 | 540 | --posLast; /* To be ++'d later */ 541 | ch = get_next_char(s, len, &posCur); 542 | if (ch == EOS) 543 | return; 544 | lbpLang = get_lb_prop_lang(lang); 545 | lbcCur = resolve_lb_class(get_char_lb_class_lang(ch, lbpLang), lang); 546 | lbcNew = LBP_Undefined; 547 | 548 | nextline: 549 | 550 | /* Special treatment for the first character */ 551 | switch (lbcCur) 552 | { 553 | case LBP_LF: 554 | case LBP_NL: 555 | lbcCur = LBP_BK; 556 | break; 557 | case LBP_CB: 558 | lbcCur = LBP_BA; 559 | break; 560 | case LBP_SP: 561 | lbcCur = LBP_WJ; 562 | break; 563 | default: 564 | break; 565 | } 566 | 567 | /* Process a line till an explicit break or end of string */ 568 | for (;;) 569 | { 570 | for (++posLast; posLast < posCur - 1; ++posLast) 571 | { 572 | brks[posLast] = LINEBREAK_INSIDEACHAR; 573 | } 574 | assert(posLast == posCur - 1); 575 | lbcLast = lbcNew; 576 | ch = get_next_char(s, len, &posCur); 577 | if (ch == EOS) 578 | break; 579 | lbcNew = get_char_lb_class_lang(ch, lbpLang); 580 | if (lbcCur == LBP_BK || (lbcCur == LBP_CR && lbcNew != LBP_LF)) 581 | { 582 | brks[posLast] = LINEBREAK_MUSTBREAK; 583 | lbcCur = resolve_lb_class(lbcNew, lang); 584 | goto nextline; 585 | } 586 | 587 | switch (lbcNew) 588 | { 589 | case LBP_SP: 590 | brks[posLast] = LINEBREAK_NOBREAK; 591 | continue; 592 | case LBP_BK: 593 | case LBP_LF: 594 | case LBP_NL: 595 | brks[posLast] = LINEBREAK_NOBREAK; 596 | lbcCur = LBP_BK; 597 | continue; 598 | case LBP_CR: 599 | brks[posLast] = LINEBREAK_NOBREAK; 600 | lbcCur = LBP_CR; 601 | continue; 602 | case LBP_CB: 603 | brks[posLast] = LINEBREAK_ALLOWBREAK; 604 | lbcCur = LBP_BA; 605 | continue; 606 | default: 607 | break; 608 | } 609 | 610 | lbcNew = resolve_lb_class(lbcNew, lang); 611 | 612 | assert(lbcCur <= LBP_JT); 613 | assert(lbcNew <= LBP_JT); 614 | switch (baTable[lbcCur - 1][lbcNew - 1]) 615 | { 616 | case DIR_BRK: 617 | brks[posLast] = LINEBREAK_ALLOWBREAK; 618 | break; 619 | case CMI_BRK: 620 | case IND_BRK: 621 | if (lbcLast == LBP_SP) 622 | { 623 | brks[posLast] = LINEBREAK_ALLOWBREAK; 624 | } 625 | else 626 | { 627 | brks[posLast] = LINEBREAK_NOBREAK; 628 | } 629 | break; 630 | case CMP_BRK: 631 | brks[posLast] = LINEBREAK_NOBREAK; 632 | if (lbcLast != LBP_SP) 633 | continue; 634 | break; 635 | case PRH_BRK: 636 | brks[posLast] = LINEBREAK_NOBREAK; 637 | break; 638 | } 639 | 640 | lbcCur = lbcNew; 641 | } 642 | 643 | assert(posLast == posCur - 1 && posCur <= len); 644 | /* Break after the last character */ 645 | brks[posLast] = LINEBREAK_MUSTBREAK; 646 | /* When the input contains incomplete sequences */ 647 | while (posCur < len) 648 | { 649 | brks[posCur++] = LINEBREAK_INSIDEACHAR; 650 | } 651 | } 652 | 653 | /** 654 | * Sets the line breaking information for a UTF-8 input string. 655 | * 656 | * @param[in] s input UTF-8 string 657 | * @param[in] len length of the input 658 | * @param[in] lang language of the input 659 | * @param[out] brks pointer to the output breaking data, containing 660 | * #LINEBREAK_MUSTBREAK, #LINEBREAK_ALLOWBREAK, 661 | * #LINEBREAK_NOBREAK, or #LINEBREAK_INSIDEACHAR 662 | */ 663 | void set_linebreaks_utf8( 664 | const utf8_t *s, 665 | size_t len, 666 | const char *lang, 667 | char *brks) 668 | { 669 | set_linebreaks(s, len, lang, brks, 670 | (get_next_char_t)lb_get_next_char_utf8); 671 | } 672 | 673 | /** 674 | * Sets the line breaking information for a UTF-16 input string. 675 | * 676 | * @param[in] s input UTF-16 string 677 | * @param[in] len length of the input 678 | * @param[in] lang language of the input 679 | * @param[out] brks pointer to the output breaking data, containing 680 | * #LINEBREAK_MUSTBREAK, #LINEBREAK_ALLOWBREAK, 681 | * #LINEBREAK_NOBREAK, or #LINEBREAK_INSIDEACHAR 682 | */ 683 | void set_linebreaks_utf16( 684 | const utf16_t *s, 685 | size_t len, 686 | const char *lang, 687 | char *brks) 688 | { 689 | set_linebreaks(s, len, lang, brks, 690 | (get_next_char_t)lb_get_next_char_utf16); 691 | } 692 | 693 | /** 694 | * Sets the line breaking information for a UTF-32 input string. 695 | * 696 | * @param[in] s input UTF-32 string 697 | * @param[in] len length of the input 698 | * @param[in] lang language of the input 699 | * @param[out] brks pointer to the output breaking data, containing 700 | * #LINEBREAK_MUSTBREAK, #LINEBREAK_ALLOWBREAK, 701 | * #LINEBREAK_NOBREAK, or #LINEBREAK_INSIDEACHAR 702 | */ 703 | void set_linebreaks_utf32( 704 | const utf32_t *s, 705 | size_t len, 706 | const char *lang, 707 | char *brks) 708 | { 709 | set_linebreaks(s, len, lang, brks, 710 | (get_next_char_t)lb_get_next_char_utf32); 711 | } 712 | 713 | /** 714 | * Tells whether a line break can occur between two Unicode characters. 715 | * This is a wrapper function to expose a simple interface. Generally 716 | * speaking, it is better to use #set_linebreaks_utf32 instead, since 717 | * complicated cases involving combining marks, spaces, etc. cannot be 718 | * correctly processed. 719 | * 720 | * @param char1 the first Unicode character 721 | * @param char2 the second Unicode character 722 | * @param lang language of the input 723 | * @return one of #LINEBREAK_MUSTBREAK, #LINEBREAK_ALLOWBREAK, 724 | * #LINEBREAK_NOBREAK, or #LINEBREAK_INSIDEACHAR 725 | */ 726 | int is_line_breakable( 727 | utf32_t char1, 728 | utf32_t char2, 729 | const char* lang) 730 | { 731 | utf32_t s[2]; 732 | char brks[2]; 733 | s[0] = char1; 734 | s[1] = char2; 735 | set_linebreaks_utf32(s, 2, lang, brks); 736 | return brks[0]; 737 | } 738 | -------------------------------------------------------------------------------- /src/linebreak/linebreak.h: -------------------------------------------------------------------------------- 1 | /* vim: set tabstop=4 shiftwidth=4: */ 2 | 3 | /* 4 | * Line breaking in a Unicode sequence. Designed to be used in a 5 | * generic text renderer. 6 | * 7 | * Copyright (C) 2008-2011 Wu Yongwei 8 | * 9 | * This software is provided 'as-is', without any express or implied 10 | * warranty. In no event will the author be held liable for any damages 11 | * arising from the use of this software. 12 | * 13 | * Permission is granted to anyone to use this software for any purpose, 14 | * including commercial applications, and to alter it and redistribute 15 | * it freely, subject to the following restrictions: 16 | * 17 | * 1. The origin of this software must not be misrepresented; you must 18 | * not claim that you wrote the original software. If you use this 19 | * software in a product, an acknowledgement in the product 20 | * documentation would be appreciated but is not required. 21 | * 2. Altered source versions must be plainly marked as such, and must 22 | * not be misrepresented as being the original software. 23 | * 3. This notice may not be removed or altered from any source 24 | * distribution. 25 | * 26 | * The main reference is Unicode Standard Annex 14 (UAX #14): 27 | * 28 | * 29 | * When this library was designed, this annex was at Revision 19, for 30 | * Unicode 5.0.0: 31 | * 32 | * 33 | * This library has been updated according to Revision 26, for 34 | * Unicode 6.0.0: 35 | * 36 | * 37 | * The Unicode Terms of Use are available at 38 | * 39 | */ 40 | 41 | /** 42 | * @file linebreak.h 43 | * 44 | * Header file for the line breaking algorithm. 45 | * 46 | * @version 2.1, 2011/05/07 47 | * @author Wu Yongwei 48 | */ 49 | 50 | #ifndef LINEBREAK_H 51 | #define LINEBREAK_H 52 | 53 | #include 54 | 55 | #ifdef __cplusplus 56 | extern "C" { 57 | #endif 58 | 59 | #define LINEBREAK_VERSION 0x0201 /**< Version of the library linebreak */ 60 | extern const int linebreak_version; 61 | 62 | #ifndef LINEBREAK_UTF_TYPES_DEFINED 63 | #define LINEBREAK_UTF_TYPES_DEFINED 64 | typedef unsigned char utf8_t; /**< Type for UTF-8 data points */ 65 | typedef unsigned short utf16_t; /**< Type for UTF-16 data points */ 66 | typedef unsigned int utf32_t; /**< Type for UTF-32 data points */ 67 | #endif 68 | 69 | #define LINEBREAK_MUSTBREAK 0 /**< Break is mandatory */ 70 | #define LINEBREAK_ALLOWBREAK 1 /**< Break is allowed */ 71 | #define LINEBREAK_NOBREAK 2 /**< No break is possible */ 72 | #define LINEBREAK_INSIDEACHAR 3 /**< A UTF-8/16 sequence is unfinished */ 73 | 74 | void init_linebreak(void); 75 | void set_linebreaks_utf8( 76 | const utf8_t* s, size_t len, const char* lang, char* brks); 77 | void set_linebreaks_utf16( 78 | const utf16_t* s, size_t len, const char* lang, char* brks); 79 | void set_linebreaks_utf32( 80 | const utf32_t* s, size_t len, const char* lang, char* brks); 81 | int is_line_breakable(utf32_t char1, utf32_t char2, const char* lang); 82 | 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | #endif /* LINEBREAK_H */ 88 | -------------------------------------------------------------------------------- /src/linebreak/linebreakdef.c: -------------------------------------------------------------------------------- 1 | /* vim: set tabstop=4 shiftwidth=4: */ 2 | 3 | /* 4 | * Line breaking in a Unicode sequence. Designed to be used in a 5 | * generic text renderer. 6 | * 7 | * Copyright (C) 2008-2011 Wu Yongwei 8 | * 9 | * This software is provided 'as-is', without any express or implied 10 | * warranty. In no event will the author be held liable for any damages 11 | * arising from the use of this software. 12 | * 13 | * Permission is granted to anyone to use this software for any purpose, 14 | * including commercial applications, and to alter it and redistribute 15 | * it freely, subject to the following restrictions: 16 | * 17 | * 1. The origin of this software must not be misrepresented; you must 18 | * not claim that you wrote the original software. If you use this 19 | * software in a product, an acknowledgement in the product 20 | * documentation would be appreciated but is not required. 21 | * 2. Altered source versions must be plainly marked as such, and must 22 | * not be misrepresented as being the original software. 23 | * 3. This notice may not be removed or altered from any source 24 | * distribution. 25 | * 26 | * The main reference is Unicode Standard Annex 14 (UAX #14): 27 | * 28 | * 29 | * When this library was designed, this annex was at Revision 19, for 30 | * Unicode 5.0.0: 31 | * 32 | * 33 | * This library has been updated according to Revision 26, for 34 | * Unicode 6.0.0: 35 | * 36 | * 37 | * The Unicode Terms of Use are available at 38 | * 39 | */ 40 | 41 | /** 42 | * @file linebreakdef.c 43 | * 44 | * Definition of language-specific data. 45 | * 46 | * @version 2.1, 2011/05/07 47 | * @author Wu Yongwei 48 | */ 49 | 50 | #include "linebreak.h" 51 | #include "linebreakdef.h" 52 | 53 | /** 54 | * English-specifc data over the default Unicode rules. 55 | */ 56 | static struct LineBreakProperties lb_prop_English[] = { 57 | { 0x2018, 0x2018, LBP_OP }, /* Left single quotation mark: opening */ 58 | { 0x201C, 0x201C, LBP_OP }, /* Left double quotation mark: opening */ 59 | { 0x201D, 0x201D, LBP_CL }, /* Right double quotation mark: closing */ 60 | { 0, 0, LBP_Undefined } 61 | }; 62 | 63 | /** 64 | * German-specifc data over the default Unicode rules. 65 | */ 66 | static struct LineBreakProperties lb_prop_German[] = { 67 | { 0x00AB, 0x00AB, LBP_CL }, /* Left double angle quotation mark: closing */ 68 | { 0x00BB, 0x00BB, LBP_OP }, /* Right double angle quotation mark: opening */ 69 | { 0x2018, 0x2018, LBP_CL }, /* Left single quotation mark: closing */ 70 | { 0x201C, 0x201C, LBP_CL }, /* Left double quotation mark: closing */ 71 | { 0x2039, 0x2039, LBP_CL }, /* Left single angle quotation mark: closing */ 72 | { 0x203A, 0x203A, LBP_OP }, /* Right single angle quotation mark: opening */ 73 | { 0, 0, LBP_Undefined } 74 | }; 75 | 76 | /** 77 | * Spanish-specifc data over the default Unicode rules. 78 | */ 79 | static struct LineBreakProperties lb_prop_Spanish[] = { 80 | { 0x00AB, 0x00AB, LBP_OP }, /* Left double angle quotation mark: opening */ 81 | { 0x00BB, 0x00BB, LBP_CL }, /* Right double angle quotation mark: closing */ 82 | { 0x2018, 0x2018, LBP_OP }, /* Left single quotation mark: opening */ 83 | { 0x201C, 0x201C, LBP_OP }, /* Left double quotation mark: opening */ 84 | { 0x201D, 0x201D, LBP_CL }, /* Right double quotation mark: closing */ 85 | { 0x2039, 0x2039, LBP_OP }, /* Left single angle quotation mark: opening */ 86 | { 0x203A, 0x203A, LBP_CL }, /* Right single angle quotation mark: closing */ 87 | { 0, 0, LBP_Undefined } 88 | }; 89 | 90 | /** 91 | * French-specifc data over the default Unicode rules. 92 | */ 93 | static struct LineBreakProperties lb_prop_French[] = { 94 | { 0x00AB, 0x00AB, LBP_OP }, /* Left double angle quotation mark: opening */ 95 | { 0x00BB, 0x00BB, LBP_CL }, /* Right double angle quotation mark: closing */ 96 | { 0x2018, 0x2018, LBP_OP }, /* Left single quotation mark: opening */ 97 | { 0x201C, 0x201C, LBP_OP }, /* Left double quotation mark: opening */ 98 | { 0x201D, 0x201D, LBP_CL }, /* Right double quotation mark: closing */ 99 | { 0x2039, 0x2039, LBP_OP }, /* Left single angle quotation mark: opening */ 100 | { 0x203A, 0x203A, LBP_CL }, /* Right single angle quotation mark: closing */ 101 | { 0, 0, LBP_Undefined } 102 | }; 103 | 104 | /** 105 | * Russian-specifc data over the default Unicode rules. 106 | */ 107 | static struct LineBreakProperties lb_prop_Russian[] = { 108 | { 0x00AB, 0x00AB, LBP_OP }, /* Left double angle quotation mark: opening */ 109 | { 0x00BB, 0x00BB, LBP_CL }, /* Right double angle quotation mark: closing */ 110 | { 0x201C, 0x201C, LBP_CL }, /* Left double quotation mark: closing */ 111 | { 0, 0, LBP_Undefined } 112 | }; 113 | 114 | /** 115 | * Chinese-specifc data over the default Unicode rules. 116 | */ 117 | static struct LineBreakProperties lb_prop_Chinese[] = { 118 | { 0x2018, 0x2018, LBP_OP }, /* Left single quotation mark: opening */ 119 | { 0x2019, 0x2019, LBP_CL }, /* Right single quotation mark: closing */ 120 | { 0x201C, 0x201C, LBP_OP }, /* Left double quotation mark: opening */ 121 | { 0x201D, 0x201D, LBP_CL }, /* Right double quotation mark: closing */ 122 | { 0, 0, LBP_Undefined } 123 | }; 124 | 125 | /** 126 | * Association data of language-specific line breaking properties with 127 | * language names. This is the definition for the static data in this 128 | * file. If you want more flexibility, or do not need the data here, 129 | * you may want to redefine \e lb_prop_lang_map in your C source file. 130 | */ 131 | struct LineBreakPropertiesLang lb_prop_lang_map[] = { 132 | { "en", 2, lb_prop_English }, 133 | { "de", 2, lb_prop_German }, 134 | { "es", 2, lb_prop_Spanish }, 135 | { "fr", 2, lb_prop_French }, 136 | { "ru", 2, lb_prop_Russian }, 137 | { "zh", 2, lb_prop_Chinese }, 138 | { NULL, 0, NULL } 139 | }; 140 | -------------------------------------------------------------------------------- /src/linebreak/linebreakdef.h: -------------------------------------------------------------------------------- 1 | /* vim: set tabstop=4 shiftwidth=4: */ 2 | 3 | /* 4 | * Line breaking in a Unicode sequence. Designed to be used in a 5 | * generic text renderer. 6 | * 7 | * Copyright (C) 2008-2011 Wu Yongwei 8 | * 9 | * This software is provided 'as-is', without any express or implied 10 | * warranty. In no event will the author be held liable for any damages 11 | * arising from the use of this software. 12 | * 13 | * Permission is granted to anyone to use this software for any purpose, 14 | * including commercial applications, and to alter it and redistribute 15 | * it freely, subject to the following restrictions: 16 | * 17 | * 1. The origin of this software must not be misrepresented; you must 18 | * not claim that you wrote the original software. If you use this 19 | * software in a product, an acknowledgement in the product 20 | * documentation would be appreciated but is not required. 21 | * 2. Altered source versions must be plainly marked as such, and must 22 | * not be misrepresented as being the original software. 23 | * 3. This notice may not be removed or altered from any source 24 | * distribution. 25 | * 26 | * The main reference is Unicode Standard Annex 14 (UAX #14): 27 | * 28 | * 29 | * When this library was designed, this annex was at Revision 19, for 30 | * Unicode 5.0.0: 31 | * 32 | * 33 | * This library has been updated according to Revision 26, for 34 | * Unicode 6.0.0: 35 | * 36 | * 37 | * The Unicode Terms of Use are available at 38 | * 39 | */ 40 | 41 | /** 42 | * @file linebreakdef.h 43 | * 44 | * Definitions of internal data structures, declarations of global 45 | * variables, and function prototypes for the line breaking algorithm. 46 | * 47 | * @version 2.1, 2011/05/07 48 | * @author Wu Yongwei 49 | */ 50 | 51 | /** 52 | * Constant value to mark the end of string. It is not a valid Unicode 53 | * character. 54 | */ 55 | #define EOS 0xFFFF 56 | 57 | /** 58 | * Line break classes. This is a direct mapping of Table 1 of Unicode 59 | * Standard Annex 14, Revision 26. 60 | */ 61 | enum LineBreakClass { 62 | /* This is used to signal an error condition. */ 63 | LBP_Undefined, /**< Undefined */ 64 | 65 | /* The following break classes are treated in the pair table. */ 66 | LBP_OP, /**< Opening punctuation */ 67 | LBP_CL, /**< Closing punctuation */ 68 | LBP_CP, /**< Closing parenthesis */ 69 | LBP_QU, /**< Ambiguous quotation */ 70 | LBP_GL, /**< Glue */ 71 | LBP_NS, /**< Non-starters */ 72 | LBP_EX, /**< Exclamation/Interrogation */ 73 | LBP_SY, /**< Symbols allowing break after */ 74 | LBP_IS, /**< Infix separator */ 75 | LBP_PR, /**< Prefix */ 76 | LBP_PO, /**< Postfix */ 77 | LBP_NU, /**< Numeric */ 78 | LBP_AL, /**< Alphabetic */ 79 | LBP_ID, /**< Ideographic */ 80 | LBP_IN, /**< Inseparable characters */ 81 | LBP_HY, /**< Hyphen */ 82 | LBP_BA, /**< Break after */ 83 | LBP_BB, /**< Break before */ 84 | LBP_B2, /**< Break on either side (but not pair) */ 85 | LBP_ZW, /**< Zero-width space */ 86 | LBP_CM, /**< Combining marks */ 87 | LBP_WJ, /**< Word joiner */ 88 | LBP_H2, /**< Hangul LV */ 89 | LBP_H3, /**< Hangul LVT */ 90 | LBP_JL, /**< Hangul L Jamo */ 91 | LBP_JV, /**< Hangul V Jamo */ 92 | LBP_JT, /**< Hangul T Jamo */ 93 | 94 | /* The following break classes are not treated in the pair table */ 95 | LBP_AI, /**< Ambiguous (alphabetic or ideograph) */ 96 | LBP_BK, /**< Break (mandatory) */ 97 | LBP_CB, /**< Contingent break */ 98 | LBP_CR, /**< Carriage return */ 99 | LBP_LF, /**< Line feed */ 100 | LBP_NL, /**< Next line */ 101 | LBP_SA, /**< South-East Asian */ 102 | LBP_SG, /**< Surrogates */ 103 | LBP_SP, /**< Space */ 104 | LBP_XX /**< Unknown */ 105 | }; 106 | 107 | /** 108 | * Struct for entries of line break properties. The array of the 109 | * entries \e must be sorted. 110 | */ 111 | struct LineBreakProperties { 112 | utf32_t start; /**< Starting coding point */ 113 | utf32_t end; /**< End coding point */ 114 | enum LineBreakClass prop; /**< The line breaking property */ 115 | }; 116 | 117 | /** 118 | * Struct for association of language-specific line breaking properties 119 | * with language names. 120 | */ 121 | struct LineBreakPropertiesLang { 122 | const char* lang; /**< Language name */ 123 | size_t namelen; /**< Length of name to match */ 124 | struct LineBreakProperties* lbp; /**< Pointer to associated data */ 125 | }; 126 | 127 | /** 128 | * Abstract function interface for #lb_get_next_char_utf8, 129 | * #lb_get_next_char_utf16, and #lb_get_next_char_utf32. 130 | */ 131 | typedef utf32_t (*get_next_char_t)(const void*, size_t, size_t*); 132 | 133 | /* Declarations */ 134 | extern struct LineBreakProperties lb_prop_default[]; 135 | extern struct LineBreakPropertiesLang lb_prop_lang_map[]; 136 | 137 | /* Function Prototype */ 138 | utf32_t lb_get_next_char_utf8(const utf8_t* s, size_t len, size_t* ip); 139 | utf32_t lb_get_next_char_utf16(const utf16_t* s, size_t len, size_t* ip); 140 | utf32_t lb_get_next_char_utf32(const utf32_t* s, size_t len, size_t* ip); 141 | void set_linebreaks( 142 | const void* s, 143 | size_t len, 144 | const char* lang, 145 | char* brks, 146 | get_next_char_t get_next_char); 147 | -------------------------------------------------------------------------------- /src/logger/logger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tinyformat.h" 4 | 5 | #ifdef PLATFORM_ANDROID 6 | #define TAG "Alfons" 7 | 8 | #include 9 | 10 | template 11 | void log(const char* fmt, const Args&... args) { 12 | __android_log_print(ANDROID_LOG_DEBUG, TAG, "%s", tfm::format(fmt, args...).c_str()); 13 | } 14 | #define LOGD(...) \ 15 | (void)(__android_log_print(ANDROID_LOG_DEBUG, TAG, \ 16 | "%s", tfm::format(__VA_ARGS__).c_str())) 17 | #define LOGI(...) \ 18 | (void)(__android_log_print(ANDROID_LOG_INFO, TAG, \ 19 | "%s", tfm::format(__VA_ARGS__).c_str())) 20 | #define LOGE(...) \ 21 | (void)(__android_log_print(ANDROID_LOG_ERROR, TAG, \ 22 | "%s", tfm::format(__VA_ARGS__).c_str())) 23 | 24 | #else 25 | 26 | template 27 | void log(const char* fmt, const Args&... args) { 28 | tfm::printfln(fmt, args...); 29 | } 30 | 31 | #define LOGD(...) (void)(tfm::printfln(__VA_ARGS__)) 32 | #define LOGI(...) (void)(tfm::printfln(__VA_ARGS__)) 33 | #define LOGE(...) (void)(tfm::printfln(__VA_ARGS__)) 34 | #endif 35 | --------------------------------------------------------------------------------