diff --git a/.travis.yml b/.travis.yml index 19f78a482..1e395771f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,7 @@ addons: - libbluetooth-dev - libcurl4-gnutls-dev - libfreetype6-dev + - libharfbuzz-dev - libfribidi-dev - libgl1-mesa-dev - libjpeg-dev @@ -46,7 +47,7 @@ before_script: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew bundle; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo mkdir -p /usr/local/include/; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then sudo ln -s /System/Library/Frameworks/OpenGL.framework/Versions/A/Headers/ /usr/local/include/GL; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CMAKE_PREFIX_PATH=/usr/local/opt/freetype/:/usr/local/opt/curl/:/usr/local/opt/libogg/:/usr/local/opt/libogg/:/usr/local/opt/libvorbis/:/usr/local/opt/openssl\@1.1/:/usr/local/opt/glew/:/usr/local/opt/fribidi/; fi + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export CMAKE_PREFIX_PATH=/usr/local/opt/freetype/:/usr/local/opt/curl/:/usr/local/opt/libogg/:/usr/local/opt/libogg/:/usr/local/opt/libvorbis/:/usr/local/opt/openssl\@1.1/:/usr/local/opt/glew/:/usr/local/opt/fribidi/:/usr/local/opt/harfbuzz/; fi script: - mkdir "build" diff --git a/Brewfile b/Brewfile index 5cfb74b04..a4bd8b72d 100644 --- a/Brewfile +++ b/Brewfile @@ -7,3 +7,4 @@ brew "curl" brew "nettle" brew "fribidi" brew "glew" +brew "harfbuzz" diff --git a/CMakeLists.txt b/CMakeLists.txt index 40e7790fe..186080c4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,8 +29,6 @@ option(USE_SQLITE3 "Use sqlite to manage server stats and ban list." ON) option(USE_CRYPTO_OPENSSL "Use OpenSSL instead of Nettle for cryptography in STK." OFF) CMAKE_DEPENDENT_OPTION(BUILD_RECORDER "Build opengl recorder" ON "NOT SERVER_ONLY;NOT APPLE" OFF) -CMAKE_DEPENDENT_OPTION(USE_FRIBIDI "Support for right-to-left languages" ON - "NOT SERVER_ONLY" OFF) CMAKE_DEPENDENT_OPTION(USE_SYSTEM_SQUISH "Use system Squish library instead of the built-in version, when available." ON "NOT SERVER_ONLY" OFF) CMAKE_DEPENDENT_OPTION(USE_WIIUSE "Support for wiimote input devices" ON @@ -42,6 +40,7 @@ if(APPLE) include_directories(/usr/local/opt/openssl@1.1/include/) include_directories(/usr/local/opt/openssl@1.1/include/openssl/) include_directories(/usr/local/opt/freetype/include/freetype2/) + include_directories(/usr/local/opt/harfbuzz/include/harfbuzz/) endif() if((UNIX AND NOT APPLE) AND NOT SERVER_ONLY) @@ -187,17 +186,8 @@ elseif(NOT USE_GLES2 AND NOT SERVER_ONLY) endif() endif() -if(MSVC) - # Build zlib library - add_subdirectory("${PROJECT_SOURCE_DIR}/lib/zlib") - include_directories("${PROJECT_SOURCE_DIR}/lib/zlib") - - set(ZLIB_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lib/zlib" "${PROJECT_BINARY_DIR}/lib/zlib/") - set(ZLIB_LIBRARY zlibstatic) -endif() - if (NOT SERVER_ONLY) - if(MSVC OR APPLE) + if (APPLE) # Build png library set(SKIP_INSTALL_ALL TRUE) set(PNG_STATIC TRUE CACHE BOOL "Build static lib") @@ -328,27 +318,50 @@ if(NOT SERVER_ONLY) add_definitions(-DENABLE_SOUND) endif() -# Freetype +# Text handling in STK (We use freetype, harfbuzz, fribidi and libraqm for i18n text handling) if (NOT SERVER_ONLY) + # Freetype find_package(Freetype) if(FREETYPE_FOUND) include_directories(${FREETYPE_INCLUDE_DIRS}) else() message(FATAL_ERROR "Freetype not found. " - "Freetype is required to display characters in SuperTuxKart. ") + "Freetype is required to display characters in SuperTuxKart.") endif() -endif() -# Fribidi -if(USE_FRIBIDI) + # Harfbuzz + find_library(HARFBUZZ_LIBRARY NAMES harfbuzz libharfbuzz) + find_path(HARFBUZZ_INCLUDEDIR NAMES harfbuzz/hb.h hb.h PATHS) + if (NOT HARFBUZZ_LIBRARY OR NOT HARFBUZZ_INCLUDEDIR) + message(FATAL_ERROR "Harfbuzz not found. " + "Harfbuzz is required to display characters in SuperTuxKart.") + else() + include_directories("${HARFBUZZ_INCLUDEDIR}") + MESSAGE(STATUS "Use system harfbuzz: ${HARFBUZZ_LIBRARY}") + endif() + + # Fribidi find_package(Fribidi) if(FRIBIDI_FOUND) include_directories(${FRIBIDI_INCLUDE_DIRS}) else() message(FATAL_ERROR "Fribidi not found. " - "Either install fribidi or disable bidi support with -DUSE_FRIBIDI=0 " - "(if you don't use a right-to-left language then you don't need this).") + "Fribidi is required to display characters in SuperTuxKart.") endif() + + # Libraqm + find_library(RAQM_LIBRARY NAMES raqm libraqm) + find_path(RAQM_INCLUDEDIR NAMES raqm.h PATHS) + if (NOT RAQM_LIBRARY OR NOT RAQM_INCLUDEDIR) + add_subdirectory("${PROJECT_SOURCE_DIR}/lib/libraqm") + include_directories("${PROJECT_SOURCE_DIR}/lib/libraqm") + SET(RAQM_LIBRARY raqm) + message(STATUS "System libraqm not found, use the bundled one.") + else() + include_directories("${RAQM_INCLUDEDIR}") + MESSAGE(STATUS "Use system libraqm: ${RAQM_LIBRARY}") + endif() + endif() # OpenGL @@ -559,10 +572,13 @@ if(NOT SERVER_ONLY) target_link_libraries(supertuxkart ${SQUISH_LIBRARY} - ${FREETYPE_LIBRARIES} ${JPEG_LIBRARIES} ${OGGVORBIS_LIBRARIES} ${OPENAL_LIBRARY} + ${RAQM_LIBRARY} + ${FRIBIDI_LIBRARIES} + ${FREETYPE_LIBRARIES} + ${HARFBUZZ_LIBRARY} graphics_utils) endif() @@ -585,11 +601,6 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") endif() -if(USE_FRIBIDI) - target_link_libraries(supertuxkart ${FRIBIDI_LIBRARIES}) - add_definitions(-DENABLE_BIDI) -endif() - # Wiiuse # ------ if(USE_WIIUSE) diff --git a/INSTALL.md b/INSTALL.md index d0aa9a716..67469bcef 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,6 +18,8 @@ To build SuperTuxKart from source, you'll need to install the following packages * Ogg (libogg-dev) * Vorbis (libvorbis-dev) * Freetype (libfreetype6-dev) + * Harfbuzz (libharfbuzz-dev) + * Fribidi (libfribidi-dev) * libcurl (libcurl-devel) * libbluetooth (bluez-devel) * libnettle (nettle-dev) @@ -29,14 +31,14 @@ Fedora command: ```bash sudo dnf install @development-tools cmake bluez-libs-devel \ -openssl-devel libcurl-devel freetype-devel fribidi-devel mesa-libGL-devel \ +openssl-devel libcurl-devel freetype-devel harfbuzz-devel fribidi-devel mesa-libGL-devel \ libjpeg-turbo-devel libogg-devel openal-soft-devel libpng-devel \ libvorbis-devel libXrandr-devel libGLEW nettle-devel pkgconf zlib-devel ``` Mageia 6 command: ```bash -su -c 'urpmi gcc-c++ cmake openssl-devel libcurl-devel freetype-devel \ +su -c 'urpmi gcc-c++ cmake openssl-devel libcurl-devel freetype-devel harfbuzz-devel \ fribidi-devel libjpeg-turbo-devel libogg-devel openal-soft-devel \ libpng-devel libvorbis-devel nettle-devel zlib-devel git subversion \ mesa-comon-devel libxrandr-devel libbluez-devel libfreetype6-devel' @@ -45,7 +47,7 @@ OpenSUSE command: ```bash sudo zypper install gcc-c++ cmake openssl-devel libcurl-devel \ -freetype-devel fribidi-devel libogg-devel openal-soft-devel libpng-devel \ +freetype-devel harfbuzz-devel fribidi-devel libogg-devel openal-soft-devel libpng-devel \ libvorbis-devel libXrandr-devel pkgconf zlib-devel enet-devel glew-devel \ libjpeg-devel bluez-devel freetype2-devel glu-devel ``` @@ -53,7 +55,7 @@ Ubuntu command: ```bash sudo apt-get install build-essential cmake libbluetooth-dev \ -libcurl4-openssl-dev libenet-dev libfreetype6-dev libfribidi-dev \ +libcurl4-openssl-dev libenet-dev libfreetype6-dev libharfbuzz-dev libfribidi-dev \ libgl1-mesa-dev libglew-dev libjpeg-dev libogg-dev libopenal-dev libpng-dev \ libssl-dev libvorbis-dev libxrandr-dev libx11-dev nettle-dev pkg-config zlib1g-dev ``` diff --git a/cmake/FindFreetype.cmake b/cmake/FindFreetype.cmake index 3e15de92e..294b6103e 100644 --- a/cmake/FindFreetype.cmake +++ b/cmake/FindFreetype.cmake @@ -11,7 +11,7 @@ if(WIN32) find_path(FREETYPE_INCLUDE_DIRS NAMES freetype/freetype.h PATHS "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/include") - find_library(FREETYPE_LIBRARY NAMES freetype PATHS "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib") + find_library(FREETYPE_LIBRARY NAMES freetype libfreetype PATHS "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib") set(FREETYPE_FOUND 1) set(FREETYPE_LIBRARIES ${FREETYPE_LIBRARY}) elseif(APPLE) diff --git a/cmake/FindFribidi.cmake b/cmake/FindFribidi.cmake index 70fa2cd8c..542554a68 100644 --- a/cmake/FindFribidi.cmake +++ b/cmake/FindFribidi.cmake @@ -18,7 +18,7 @@ endif() if(NOT FRIBIDI_FOUND) find_path(FRIBIDI_INCLUDE_DIR NAMES fribidi/fribidi.h PATHS /Library/Frameworks/fribidi.framework/Headers "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/include") - find_library(FRIBIDI_LIBRARY NAMES fribidi PATHS /Library/Frameworks/fribidi.framework "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib") + find_library(FRIBIDI_LIBRARY NAMES fribidi libfribidi PATHS /Library/Frameworks/fribidi.framework "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib") include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Fribidi DEFAULT_MSG FRIBIDI_INCLUDE_DIR FRIBIDI_LIBRARY) diff --git a/lib/irrlicht/CMakeLists.txt b/lib/irrlicht/CMakeLists.txt index 92a881c81..4bec6c9e9 100644 --- a/lib/irrlicht/CMakeLists.txt +++ b/lib/irrlicht/CMakeLists.txt @@ -2,7 +2,7 @@ if(NOT SERVER_ONLY) find_package(PNG REQUIRED) find_package(JPEG REQUIRED) - + find_package(ZLIB REQUIRED) include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include/" "${JPEG_INCLUDE_DIR}" "${PNG_INCLUDE_DIRS}" @@ -62,12 +62,8 @@ if(NOT SERVER_ONLY) endif() else() include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include/") - if(MSVC) - include_directories("${CMAKE_CURRENT_BINARY_DIR}/../zlib/") - else() - find_package(ZLIB REQUIRED) - include_directories("${ZLIB_INCLUDE_DIR}") - endif() + find_package(ZLIB REQUIRED) + include_directories("${ZLIB_INCLUDE_DIR}") add_definitions(-DNO_IRR_COMPILE_WITH_LIBPNG_) add_definitions(-DNO_IRR_COMPILE_WITH_LIBJPEG_) add_definitions(-DNO_IRR_COMPILE_WITH_BMP_LOADER_) diff --git a/lib/libraqm/CMakeLists.txt b/lib/libraqm/CMakeLists.txt new file mode 100644 index 000000000..7aed4fb00 --- /dev/null +++ b/lib/libraqm/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 2.6) + +if (NOT MSVC) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99") +endif() + +include(CheckFunctionExists) +include(CheckCSourceCompiles) +set(CMAKE_REQUIRED_INCLUDES ${HARFBUZZ_INCLUDEDIR}) +set(CMAKE_REQUIRED_LIBRARIES ${HARFBUZZ_LIBRARY}) + +CHECK_FUNCTION_EXISTS(hb_ft_font_create_referenced HAVE_hb_ft_font_create_referenced) +if (HAVE_hb_ft_font_create_referenced) + add_definitions(-DHAVE_HB_FT_FONT_CREATE_REFERENCED) +endif() + +CHECK_FUNCTION_EXISTS(hb_ft_font_set_load_flags HAVE_hb_ft_font_set_load_flags) +if (HAVE_hb_ft_font_set_load_flags) + add_definitions(-DHAVE_HB_FT_FONT_SET_LOAD_FLAGS) +endif() + +CHECK_FUNCTION_EXISTS(hb_buffer_set_invisible_glyph HAVE_hb_buffer_set_invisible_glyph) +if (HAVE_hb_buffer_set_invisible_glyph) + add_definitions(-DHAVE_HB_BUFFER_SET_INVISIBLE_GLYPH) +endif() + +CHECK_C_SOURCE_COMPILES( +" +#include +int main () +{ +#ifndef HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + (void)HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; +#endif + return 0; +} +" +HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) +if (HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) + add_definitions(-DHAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) +endif() + +add_library(raqm STATIC raqm.c) diff --git a/lib/libraqm/raqm-version.h b/lib/libraqm/raqm-version.h new file mode 100644 index 000000000..d9d21147e --- /dev/null +++ b/lib/libraqm/raqm-version.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#ifndef _RAQM_H_IN_ +#error "Include instead." +#endif + +#ifndef _RAQM_VERSION_H_ +#define _RAQM_VERSION_H_ + +#define RAQM_VERSION_MAJOR 0 +#define RAQM_VERSION_MINOR 7 +#define RAQM_VERSION_MICRO 0 + +#define RAQM_VERSION_STRING "0.7.0" + +#define RAQM_VERSION_ATLEAST(major,minor,micro) \ + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + +#endif /* _RAQM_VERSION_H_ */ diff --git a/lib/libraqm/raqm-version.h.in b/lib/libraqm/raqm-version.h.in new file mode 100644 index 000000000..93b20d23f --- /dev/null +++ b/lib/libraqm/raqm-version.h.in @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Google, Inc. + * + * This is part of HarfBuzz, a text shaping library. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and its documentation for any purpose, provided that the + * above copyright notice and the following two paragraphs appear in + * all copies of this software. + * + * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES + * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN + * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS + * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + * Google Author(s): Behdad Esfahbod + */ + +#ifndef _RAQM_H_IN_ +#error "Include instead." +#endif + +#ifndef _RAQM_VERSION_H_ +#define _RAQM_VERSION_H_ + +#define RAQM_VERSION_MAJOR @RAQM_VERSION_MAJOR@ +#define RAQM_VERSION_MINOR @RAQM_VERSION_MINOR@ +#define RAQM_VERSION_MICRO @RAQM_VERSION_MICRO@ + +#define RAQM_VERSION_STRING "@RAQM_VERSION@" + +#define RAQM_VERSION_ATLEAST(major,minor,micro) \ + ((major)*10000+(minor)*100+(micro) <= \ + RAQM_VERSION_MAJOR*10000+RAQM_VERSION_MINOR*100+RAQM_VERSION_MICRO) + +#endif /* _RAQM_VERSION_H_ */ diff --git a/lib/libraqm/raqm.c b/lib/libraqm/raqm.c new file mode 100644 index 000000000..627eb06b1 --- /dev/null +++ b/lib/libraqm/raqm.c @@ -0,0 +1,2080 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#undef HAVE_CONFIG_H // Workaround for Fribidi 1.0.5 and earlier +#endif + +#include +#include + +#include +#include +#include + +#include "raqm.h" + +#if FRIBIDI_MAJOR_VERSION >= 1 +#define USE_FRIBIDI_EX_API +#endif + +/** + * SECTION:raqm + * @title: Raqm + * @short_description: A library for complex text layout + * @include: raqm.h + * + * Raqm is a light weight text layout library with strong emphasis on + * supporting languages and writing systems that require complex text layout. + * + * The main object in Raqm API is #raqm_t, it stores all the states of the + * input text, its properties, and the output of the layout process. + * + * To start, you create a #raqm_t object, add text and font(s) to it, run the + * layout process, and finally query about the output. For example: + * + * |[ + * #include "raqm.h" + * + * int + * main (int argc, char *argv[]) + * { + * const char *fontfile; + * const char *text; + * const char *direction; + * const char *language; + * int ret = 1; + * + * FT_Library library = NULL; + * FT_Face face = NULL; + * + * if (argc < 5) + * { + * printf ("Usage: %s FONT_FILE TEXT DIRECTION LANG\n", argv[0]); + * return 1; + * } + * + * fontfile = argv[1]; + * text = argv[2]; + * direction = argv[3]; + * language = argv[4]; + * + * if (FT_Init_FreeType (&library) == 0) + * { + * if (FT_New_Face (library, fontfile, 0, &face) == 0) + * { + * if (FT_Set_Char_Size (face, face->units_per_EM, 0, 0, 0) == 0) + * { + * raqm_t *rq = raqm_create (); + * if (rq != NULL) + * { + * raqm_direction_t dir = RAQM_DIRECTION_DEFAULT; + * + * if (strcmp (direction, "r") == 0) + * dir = RAQM_DIRECTION_RTL; + * else if (strcmp (direction, "l") == 0) + * dir = RAQM_DIRECTION_LTR; + * + * if (raqm_set_text_utf8 (rq, text, strlen (text)) && + * raqm_set_freetype_face (rq, face) && + * raqm_set_par_direction (rq, dir) && + * raqm_set_language (rq, language, 0, strlen (text)) && + * raqm_layout (rq)) + * { + * size_t count, i; + * raqm_glyph_t *glyphs = raqm_get_glyphs (rq, &count); + * + * ret = !(glyphs != NULL || count == 0); + * + * printf("glyph count: %zu\n", count); + * for (i = 0; i < count; i++) + * { + * printf ("gid#%d off: (%d, %d) adv: (%d, %d) idx: %d\n", + * glyphs[i].index, + * glyphs[i].x_offset, + * glyphs[i].y_offset, + * glyphs[i].x_advance, + * glyphs[i].y_advance, + * glyphs[i].cluster); + * } + * } + * + * raqm_destroy (rq); + * } + * } + * + * FT_Done_Face (face); + * } + * + * FT_Done_FreeType (library); + * } + * + * return ret; + * } + * ]| + * To compile this example: + * |[ + * cc -o test test.c `pkg-config --libs --cflags raqm` + * ]| + */ + +/* For enabling debug mode */ +/*#define RAQM_DEBUG 1*/ +#ifdef RAQM_DEBUG +#define RAQM_DBG(...) fprintf (stderr, __VA_ARGS__) +#else +#define RAQM_DBG(...) +#endif + +#ifdef RAQM_TESTING +# define RAQM_TEST(...) printf (__VA_ARGS__) +# define SCRIPT_TO_STRING(script) \ + char buff[5]; \ + hb_tag_to_string (hb_script_to_iso15924_tag (script), buff); \ + buff[4] = '\0'; +#else +# define RAQM_TEST(...) +#endif + +typedef enum { + RAQM_FLAG_NONE = 0, + RAQM_FLAG_UTF8 = 1 << 0 +} _raqm_flags_t; + +typedef struct { + FT_Face ftface; + hb_language_t lang; + hb_script_t script; +} _raqm_text_info; + +typedef struct _raqm_run raqm_run_t; + +struct _raqm { + int ref_count; + + uint32_t *text; + char *text_utf8; + size_t text_len; + + _raqm_text_info *text_info; + + raqm_direction_t base_dir; + raqm_direction_t resolved_dir; + + hb_feature_t *features; + size_t features_len; + + raqm_run_t *runs; + raqm_glyph_t *glyphs; + + _raqm_flags_t flags; + + int ft_loadflags; + int invisible_glyph; +}; + +struct _raqm_run { + int pos; + int len; + + hb_direction_t direction; + hb_script_t script; + hb_font_t *font; + hb_buffer_t *buffer; + + raqm_run_t *next; +}; + +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index); + +static bool +_raqm_init_text_info (raqm_t *rq) +{ + hb_language_t default_lang; + + if (rq->text_info) + return true; + + rq->text_info = malloc (sizeof (_raqm_text_info) * rq->text_len); + if (!rq->text_info) + return false; + + default_lang = hb_language_get_default (); + for (size_t i = 0; i < rq->text_len; i++) + { + rq->text_info[i].ftface = NULL; + rq->text_info[i].lang = default_lang; + rq->text_info[i].script = HB_SCRIPT_INVALID; + } + + return true; +} + +static void +_raqm_free_text_info (raqm_t *rq) +{ + if (!rq->text_info) + return; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + } + + free (rq->text_info); + rq->text_info = NULL; +} + +static bool +_raqm_compare_text_info (_raqm_text_info a, + _raqm_text_info b) +{ + if (a.ftface != b.ftface) + return false; + + if (a.lang != b.lang) + return false; + + if (a.script != b.script) + return false; + + return true; +} + +/** + * raqm_create: + * + * Creates a new #raqm_t with all its internal states initialized to their + * defaults. + * + * Return value: + * A newly allocated #raqm_t with a reference count of 1. The initial reference + * count should be released with raqm_destroy() when you are done using the + * #raqm_t. Returns %NULL in case of error. + * + * Since: 0.1 + */ +raqm_t * +raqm_create (void) +{ + raqm_t *rq; + + rq = malloc (sizeof (raqm_t)); + if (!rq) + return NULL; + + rq->ref_count = 1; + + rq->text = NULL; + rq->text_utf8 = NULL; + rq->text_len = 0; + + rq->text_info = NULL; + + rq->base_dir = RAQM_DIRECTION_DEFAULT; + rq->resolved_dir = RAQM_DIRECTION_DEFAULT; + + rq->features = NULL; + rq->features_len = 0; + + rq->runs = NULL; + rq->glyphs = NULL; + + rq->flags = RAQM_FLAG_NONE; + + rq->ft_loadflags = -1; + rq->invisible_glyph = 0; + + return rq; +} + +/** + * raqm_reference: + * @rq: a #raqm_t. + * + * Increases the reference count on @rq by one. This prevents @rq from being + * destroyed until a matching call to raqm_destroy() is made. + * + * Return value: + * The referenced #raqm_t. + * + * Since: 0.1 + */ +raqm_t * +raqm_reference (raqm_t *rq) +{ + if (rq) + rq->ref_count++; + + return rq; +} + +static void +_raqm_free_runs (raqm_t *rq) +{ + raqm_run_t *runs = rq->runs; + while (runs) + { + raqm_run_t *run = runs; + runs = runs->next; + + hb_buffer_destroy (run->buffer); + hb_font_destroy (run->font); + free (run); + } +} + +/** + * raqm_destroy: + * @rq: a #raqm_t. + * + * Decreases the reference count on @rq by one. If the result is zero, then @rq + * and all associated resources are freed. + * See cairo_reference(). + * + * Since: 0.1 + */ +void +raqm_destroy (raqm_t *rq) +{ + if (!rq || --rq->ref_count != 0) + return; + + free (rq->text); + free (rq->text_utf8); + _raqm_free_text_info (rq); + _raqm_free_runs (rq); + free (rq->glyphs); + free (rq); +} + +/** + * raqm_set_text: + * @rq: a #raqm_t. + * @text: a UTF-32 encoded text string. + * @len: the length of @text. + * + * Adds @text to @rq to be used for layout. It must be a valid UTF-32 text, any + * invalid character will be replaced with U+FFFD. The text should typically + * represent a full paragraph, since doing the layout of chunks of text + * separately can give improper output. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len) +{ + if (!rq || !text) + return false; + + rq->text_len = len; + + /* Empty string, don’t fail but do nothing */ + if (!len) + return true; + + free (rq->text); + + rq->text = malloc (sizeof (uint32_t) * rq->text_len); + if (!rq->text) + return false; + + _raqm_free_text_info (rq); + if (!_raqm_init_text_info (rq)) + return false; + + memcpy (rq->text, text, sizeof (uint32_t) * rq->text_len); + + return true; +} + +/** + * raqm_set_text_utf8: + * @rq: a #raqm_t. + * @text: a UTF-8 encoded text string. + * @len: the length of @text. + * + * Same as raqm_set_text(), but for text encoded in UTF-8 encoding. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len) +{ + uint32_t *unicode; + size_t ulen; + bool ok; + + if (!rq || !text) + return false; + + /* Empty string, don’t fail but do nothing */ + if (!len) + { + rq->text_len = len; + return true; + } + + RAQM_TEST ("Text is: %s\n", text); + + rq->flags |= RAQM_FLAG_UTF8; + + rq->text_utf8 = malloc (sizeof (char) * len); + if (!rq->text_utf8) + return false; + + unicode = malloc (sizeof (uint32_t) * len); + if (!unicode) + return false; + + memcpy (rq->text_utf8, text, sizeof (char) * len); + + ulen = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + text, len, unicode); + + ok = raqm_set_text (rq, unicode, ulen); + + free (unicode); + return ok; +} + +/** + * raqm_set_par_direction: + * @rq: a #raqm_t. + * @dir: the direction of the paragraph. + * + * Sets the paragraph direction, also known as block direction in CSS. For + * horizontal text, this controls the overall direction in the Unicode + * Bidirectional Algorithm, so when the text is mainly right-to-left (with or + * without some left-to-right) text, then the base direction should be set to + * #RAQM_DIRECTION_RTL and vice versa. + * + * The default is #RAQM_DIRECTION_DEFAULT, which determines the paragraph + * direction based on the first character with strong bidi type (see [rule + * P2](http://unicode.org/reports/tr9/#P2) in Unicode Bidirectional Algorithm), + * which can be good enough for many cases but has problems when a mainly + * right-to-left paragraph starts with a left-to-right character and vice versa + * as the detected paragraph direction will be the wrong one, or when text does + * not contain any characters with string bidi types (e.g. only punctuation or + * numbers) as this will default to left-to-right paragraph direction. + * + * For vertical, top-to-bottom text, #RAQM_DIRECTION_TTB should be used. Raqm, + * however, provides limited vertical text support and does not handle rotated + * horizontal text in vertical text, instead everything is treated as vertical + * text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir) +{ + if (!rq) + return false; + + rq->base_dir = dir; + + return true; +} + +/** + * raqm_set_language: + * @rq: a #raqm_t. + * @lang: a BCP47 language code. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets a [BCP47 language + * code](https://www.w3.org/International/articles/language-tags/) to be used + * for @len-number of characters staring at @start. The @start and @len are + * input string array indices (i.e. counting bytes in UTF-8 and scaler values + * in UTF-32). + * + * This method can be used repeatedly to set different languages for different + * parts of the text. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Stability: + * Unstable + * + * Since: 0.2 + */ +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len) +{ + hb_language_t language; + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + language = hb_language_from_string (lang, -1); + for (size_t i = start; i < end; i++) + { + rq->text_info[i].lang = language; + } + + return true; +} + +/** + * raqm_add_font_feature: + * @rq: a #raqm_t. + * @feature: (transfer none): a font feature string. + * @len: length of @feature, -1 for %NULL-terminated. + * + * Adds a font feature to be used by the #raqm_t during text layout. This is + * usually used to turn on optional font features that are not enabled by + * default, for example `dlig` or `ss01`, but can be also used to turn off + * default font features. + * + * @feature is string representing a single font feature, in the syntax + * understood by hb_feature_from_string(). + * + * This function can be called repeatedly, new features will be appended to the + * end of the features list and can potentially override previous features. + * + * Return value: + * %true if parsing @feature succeeded, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len) +{ + hb_bool_t ok; + hb_feature_t fea; + + if (!rq) + return false; + + ok = hb_feature_from_string (feature, len, &fea); + if (ok) + { + rq->features_len++; + rq->features = realloc (rq->features, + sizeof (hb_feature_t) * (rq->features_len)); + if (!rq->features) + return false; + + rq->features[rq->features_len - 1] = fea; + } + + return ok; +} + +static hb_font_t * +_raqm_create_hb_font (raqm_t *rq, + FT_Face face) +{ + hb_font_t *font; + +#ifdef HAVE_HB_FT_FONT_CREATE_REFERENCED + font = hb_ft_font_create_referenced (face); +#else + FT_Reference_Face (face); + font = hb_ft_font_create (face, (hb_destroy_func_t) FT_Done_Face); +#endif + +#ifdef HAVE_HB_FT_FONT_SET_LOAD_FLAGS + if (rq->ft_loadflags >= 0) + hb_ft_font_set_load_flags (font, rq->ft_loadflags); +#else + (void)rq; +#endif + + return font; +} + +static bool +_raqm_set_freetype_face (raqm_t *rq, + FT_Face face, + size_t start, + size_t end) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (start >= rq->text_len || end > rq->text_len) + return false; + + if (!rq->text_info) + return false; + + for (size_t i = start; i < end; i++) + { + if (rq->text_info[i].ftface) + FT_Done_Face (rq->text_info[i].ftface); + rq->text_info[i].ftface = face; + FT_Reference_Face (face); + } + + return true; +} + +/** + * raqm_set_freetype_face: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * + * Sets an #FT_Face to be used for all characters in @rq. + * + * See also raqm_set_freetype_face_range(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face) +{ + return _raqm_set_freetype_face (rq, face, 0, rq->text_len); +} + +/** + * raqm_set_freetype_face_range: + * @rq: a #raqm_t. + * @face: an #FT_Face. + * @start: index of first character that should use @face. + * @len: number of characters using @face. + * + * Sets an #FT_Face to be used for @len-number of characters staring at @start. + * The @start and @len are input string array indices (i.e. counting bytes in + * UTF-8 and scaler values in UTF-32). + * + * This method can be used repeatedly to set different faces for different + * parts of the text. It is the responsibility of the client to make sure that + * face ranges cover the whole text. + * + * See also raqm_set_freetype_face(). + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len) +{ + size_t end = start + len; + + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (rq->flags & RAQM_FLAG_UTF8) + { + start = _raqm_u8_to_u32_index (rq, start); + end = _raqm_u8_to_u32_index (rq, end); + } + + return _raqm_set_freetype_face (rq, face, start, end); +} + +/** + * raqm_set_freetype_load_flags: + * @rq: a #raqm_t. + * @flags: FreeType load flags. + * + * Sets the load flags passed to FreeType when loading glyphs, should be the + * same flags used by the client when rendering FreeType glyphs. + * + * This requires version of HarfBuzz that has hb_ft_font_set_load_flags(), for + * older version the flags will be ignored. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.3 + */ +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags) +{ + if (!rq) + return false; + + rq->ft_loadflags = flags; + + return true; +} + +/** + * raqm_set_invisible_glyph: + * @rq: a #raqm_t. + * @gid: glyph id to use for invisible glyphs. + * + * Sets the glyph id to be used for invisible glyhphs. + * + * If @gid is negative, invisible glyphs will be suppressed from the output. + * This requires HarfBuzz 1.8.0 or later. If raqm is used with an earlier + * HarfBuzz version, the return value will be %false and the shaping behavior + * does not change. + * + * If @gid is zero, invisible glyphs will be rendered as space. + * This works on all versions of HarfBuzz. + * + * If @gid is a positive number, it will be used for invisible glyphs. + * This requires a version of HarfBuzz that has + * hb_buffer_set_invisible_glyph(). For older versions, the return value + * will be %false and the shaping behavior does not change. + * + * Return value: + * %true if no errors happened, %false otherwise. + * + * Since: 0.6 + */ +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid) +{ + if (!rq) + return false; + +#ifndef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (gid > 0) + return false; +#endif + +#if !defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) || \ + !HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (gid < 0) + return false; +#endif + + rq->invisible_glyph = gid; + return true; +} + +static bool +_raqm_itemize (raqm_t *rq); + +static bool +_raqm_shape (raqm_t *rq); + +/** + * raqm_layout: + * @rq: a #raqm_t. + * + * Run the text layout process on @rq. This is the main Raqm function where the + * Unicode Bidirectional Text algorithm will be applied to the text in @rq, + * text shaping, and any other part of the layout process. + * + * Return value: + * %true if the layout process was successful, %false otherwise. + * + * Since: 0.1 + */ +bool +raqm_layout (raqm_t *rq) +{ + if (!rq) + return false; + + if (!rq->text_len) + return true; + + if (!rq->text_info) + return false; + + for (size_t i = 0; i < rq->text_len; i++) + { + if (!rq->text_info[i].ftface) + return false; + } + + if (!_raqm_itemize (rq)) + return false; + + if (!_raqm_shape (rq)) + return false; + + return true; +} + +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index); + +/** + * raqm_get_glyphs: + * @rq: a #raqm_t. + * @length: (out): output array length. + * + * Gets the final result of Raqm layout process, an array of #raqm_glyph_t + * containing the glyph indices in the font, their positions and other possible + * information. + * + * Return value: (transfer none): + * An array of #raqm_glyph_t, or %NULL in case of error. This is owned by @rq + * and must not be freed. + * + * Since: 0.1 + */ +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length) +{ + size_t count = 0; + + if (!rq || !rq->runs || !length) + { + if (length) + *length = 0; + return NULL; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + count += hb_buffer_get_length (run->buffer); + + *length = count; + + if (rq->glyphs) + free (rq->glyphs); + + rq->glyphs = malloc (sizeof (raqm_glyph_t) * count); + if (!rq->glyphs) + { + *length = 0; + return NULL; + } + + RAQM_TEST ("Glyph information:\n"); + + count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + rq->glyphs[count + i].index = info[i].codepoint; + rq->glyphs[count + i].cluster = info[i].cluster; + rq->glyphs[count + i].x_advance = position[i].x_advance; + rq->glyphs[count + i].y_advance = position[i].y_advance; + rq->glyphs[count + i].x_offset = position[i].x_offset; + rq->glyphs[count + i].y_offset = position[i].y_offset; + rq->glyphs[count + i].ftface = rq->text_info[info[i].cluster].ftface; + + RAQM_TEST ("glyph [%d]\tx_offset: %d\ty_offset: %d\tx_advance: %d\tfont: %s\n", + rq->glyphs[count + i].index, rq->glyphs[count + i].x_offset, + rq->glyphs[count + i].y_offset, rq->glyphs[count + i].x_advance, + rq->glyphs[count + i].ftface->family_name); + } + + count += len; + } + + if (rq->flags & RAQM_FLAG_UTF8) + { +#ifdef RAQM_TESTING + RAQM_TEST ("\nUTF-32 clusters:"); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + + for (size_t i = 0; i < count; i++) + rq->glyphs[i].cluster = _raqm_u32_to_u8_index (rq, + rq->glyphs[i].cluster); + +#ifdef RAQM_TESTING + RAQM_TEST ("UTF-8 clusters: "); + for (size_t i = 0; i < count; i++) + RAQM_TEST (" %02d", rq->glyphs[i].cluster); + RAQM_TEST ("\n"); +#endif + } + return rq->glyphs; +} + +static bool +_raqm_resolve_scripts (raqm_t *rq); + +static hb_direction_t +_raqm_hb_dir (raqm_t *rq, FriBidiLevel level) +{ + hb_direction_t dir = HB_DIRECTION_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + dir = HB_DIRECTION_TTB; + else if (FRIBIDI_LEVEL_IS_RTL (level)) + dir = HB_DIRECTION_RTL; + + return dir; +} + +typedef struct { + size_t pos; + size_t len; + FriBidiLevel level; +} _raqm_bidi_run; + +static void +_raqm_reverse_run (_raqm_bidi_run *run, const size_t len) +{ + assert (run); + + for (size_t i = 0; i < len / 2; i++) + { + _raqm_bidi_run temp = run[i]; + run[i] = run[len - 1 - i]; + run[len - 1 - i] = temp; + } +} + +static _raqm_bidi_run * +_raqm_reorder_runs (const FriBidiCharType *types, + const size_t len, + const FriBidiParType base_dir, + /* input and output */ + FriBidiLevel *levels, + /* output */ + size_t *run_count) +{ + FriBidiLevel level; + FriBidiLevel last_level = -1; + FriBidiLevel max_level = 0; + size_t run_start = 0; + size_t run_index = 0; + _raqm_bidi_run *runs = NULL; + size_t count = 0; + + if (len == 0) + { + *run_count = 0; + return NULL; + } + + assert (types); + assert (levels); + + /* L1. Reset the embedding levels of some chars: + 4. any sequence of white space characters at the end of the line. */ + for (int i = len - 1; + i >= 0 && FRIBIDI_IS_EXPLICIT_OR_BN_OR_WS (types[i]); i--) + { + levels[i] = FRIBIDI_DIR_TO_LEVEL (base_dir); + } + + /* Find max_level of the line. We don't reuse the paragraph + * max_level, both for a cleaner API, and that the line max_level + * may be far less than paragraph max_level. */ + for (int i = len - 1; i >= 0; i--) + { + if (levels[i] > max_level) + max_level = levels[i]; + } + + for (size_t i = 0; i < len; i++) + { + if (levels[i] != last_level) + count++; + + last_level = levels[i]; + } + + runs = malloc (sizeof (_raqm_bidi_run) * count); + + while (run_start < len) + { + size_t run_end = run_start; + while (run_end < len && levels[run_start] == levels[run_end]) + { + run_end++; + } + + runs[run_index].pos = run_start; + runs[run_index].level = levels[run_start]; + runs[run_index].len = run_end - run_start; + run_start = run_end; + run_index++; + } + + /* L2. Reorder. */ + for (level = max_level; level > 0; level--) + { + for (int i = count - 1; i >= 0; i--) + { + if (runs[i].level >= level) + { + int end = i; + for (i--; (i >= 0 && runs[i].level >= level); i--) + ; + _raqm_reverse_run (runs + i + 1, end - i); + } + } + } + + *run_count = count; + return runs; +} + +static bool +_raqm_itemize (raqm_t *rq) +{ + FriBidiParType par_type = FRIBIDI_PAR_ON; + FriBidiCharType *types; +#ifdef USE_FRIBIDI_EX_API + FriBidiBracketType *btypes; +#endif + FriBidiLevel *levels; + _raqm_bidi_run *runs = NULL; + raqm_run_t *last; + int max_level; + size_t run_count; + bool ok = true; + +#ifdef RAQM_TESTING + switch (rq->base_dir) + { + case RAQM_DIRECTION_RTL: + RAQM_TEST ("Direction is: RTL\n\n"); + break; + case RAQM_DIRECTION_LTR: + RAQM_TEST ("Direction is: LTR\n\n"); + break; + case RAQM_DIRECTION_TTB: + RAQM_TEST ("Direction is: TTB\n\n"); + break; + case RAQM_DIRECTION_DEFAULT: + default: + RAQM_TEST ("Direction is: DEFAULT\n\n"); + break; + } +#endif + + types = calloc (rq->text_len, sizeof (FriBidiCharType)); +#ifdef USE_FRIBIDI_EX_API + btypes = calloc (rq->text_len, sizeof (FriBidiBracketType)); +#endif + levels = calloc (rq->text_len, sizeof (FriBidiLevel)); + if (!types || !levels +#ifdef USE_FRIBIDI_EX_API + || !btypes +#endif + ) + { + ok = false; + goto done; + } + + if (rq->base_dir == RAQM_DIRECTION_RTL) + par_type = FRIBIDI_PAR_RTL; + else if (rq->base_dir == RAQM_DIRECTION_LTR) + par_type = FRIBIDI_PAR_LTR; + + if (rq->base_dir == RAQM_DIRECTION_TTB) + { + /* Treat every thing as LTR in vertical text */ + max_level = 1; + memset (types, FRIBIDI_TYPE_LTR, rq->text_len); + memset (levels, 0, rq->text_len); + rq->resolved_dir = RAQM_DIRECTION_LTR; + } + else + { + fribidi_get_bidi_types (rq->text, rq->text_len, types); +#ifdef USE_FRIBIDI_EX_API + fribidi_get_bracket_types (rq->text, rq->text_len, types, btypes); + max_level = fribidi_get_par_embedding_levels_ex (types, btypes, + rq->text_len, &par_type, + levels); +#else + max_level = fribidi_get_par_embedding_levels (types, rq->text_len, + &par_type, levels); +#endif + + if (par_type == FRIBIDI_PAR_LTR) + rq->resolved_dir = RAQM_DIRECTION_LTR; + else + rq->resolved_dir = RAQM_DIRECTION_RTL; + } + + if (max_level == 0) + { + ok = false; + goto done; + } + + if (!_raqm_resolve_scripts (rq)) + { + ok = false; + goto done; + } + + /* Get the number of bidi runs */ + runs = _raqm_reorder_runs (types, rq->text_len, par_type, levels, &run_count); + if (!runs) + { + ok = false; + goto done; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("Number of runs before script itemization: %zu\n\n", run_count); + + RAQM_TEST ("Fribidi Runs:\n"); + for (size_t i = 0; i < run_count; i++) + { + RAQM_TEST ("run[%zu]:\t start: %zu\tlength: %zu\tlevel: %d\n", + i, runs[i].pos, runs[i].len, runs[i].level); + } + RAQM_TEST ("\n"); +#endif + + last = NULL; + for (size_t i = 0; i < run_count; i++) + { + raqm_run_t *run = calloc (1, sizeof (raqm_run_t)); + if (!run) + { + ok = false; + goto done; + } + + if (!rq->runs) + rq->runs = run; + + if (last) + last->next = run; + + run->direction = _raqm_hb_dir (rq, runs[i].level); + + if (HB_DIRECTION_IS_BACKWARD (run->direction)) + { + run->pos = runs[i].pos + runs[i].len - 1; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (int j = runs[i].len - 1; j >= 0; j--) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + { + run->len++; + run->pos = runs[i].pos + j; + } + } + } + else + { + run->pos = runs[i].pos; + run->script = rq->text_info[run->pos].script; + run->font = _raqm_create_hb_font (rq, rq->text_info[run->pos].ftface); + for (size_t j = 0; j < runs[i].len; j++) + { + _raqm_text_info info = rq->text_info[runs[i].pos + j]; + if (!_raqm_compare_text_info (rq->text_info[run->pos], info)) + { + raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t)); + if (!newrun) + { + ok = false; + goto done; + } + newrun->pos = runs[i].pos + j; + newrun->len = 1; + newrun->direction = _raqm_hb_dir (rq, runs[i].level); + newrun->script = info.script; + newrun->font = _raqm_create_hb_font (rq, info.ftface); + run->next = newrun; + run = newrun; + } + else + run->len++; + } + } + + last = run; + last->next = NULL; + } + +#ifdef RAQM_TESTING + run_count = 0; + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + run_count++; + RAQM_TEST ("Number of runs after script itemization: %zu\n\n", run_count); + + run_count = 0; + RAQM_TEST ("Final Runs:\n"); + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + SCRIPT_TO_STRING (run->script); + RAQM_TEST ("run[%zu]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\tfont: %s\n", + run_count++, run->pos, run->len, + hb_direction_to_string (run->direction), buff, + rq->text_info[run->pos].ftface->family_name); + } + RAQM_TEST ("\n"); +#endif + +done: + free (runs); + free (types); +#ifdef USE_FRIBIDI_EX_API + free (btypes); +#endif + free (levels); + + return ok; +} + +/* Stack to handle script detection */ +typedef struct { + size_t capacity; + size_t size; + int *pair_index; + hb_script_t *script; +} _raqm_stack_t; + +/* Special paired characters for script detection */ +static size_t paired_len = 34; +static const FriBidiChar paired_chars[] = +{ + 0x0028, 0x0029, /* ascii paired punctuation */ + 0x003c, 0x003e, + 0x005b, 0x005d, + 0x007b, 0x007d, + 0x00ab, 0x00bb, /* guillemets */ + 0x2018, 0x2019, /* general punctuation */ + 0x201c, 0x201d, + 0x2039, 0x203a, + 0x3008, 0x3009, /* chinese paired punctuation */ + 0x300a, 0x300b, + 0x300c, 0x300d, + 0x300e, 0x300f, + 0x3010, 0x3011, + 0x3014, 0x3015, + 0x3016, 0x3017, + 0x3018, 0x3019, + 0x301a, 0x301b +}; + +static void +_raqm_stack_free (_raqm_stack_t *stack) +{ + free (stack->script); + free (stack->pair_index); + free (stack); +} + +/* Stack handling functions */ +static _raqm_stack_t * +_raqm_stack_new (size_t max) +{ + _raqm_stack_t *stack; + stack = calloc (1, sizeof (_raqm_stack_t)); + if (!stack) + return NULL; + + stack->script = malloc (sizeof (hb_script_t) * max); + if (!stack->script) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->pair_index = malloc (sizeof (int) * max); + if (!stack->pair_index) + { + _raqm_stack_free (stack); + return NULL; + } + + stack->size = 0; + stack->capacity = max; + + return stack; +} + +static bool +_raqm_stack_pop (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return false; + } + + stack->size--; + + return true; +} + +static hb_script_t +_raqm_stack_top (_raqm_stack_t *stack) +{ + if (!stack->size) + { + RAQM_DBG ("Stack is Empty\n"); + return HB_SCRIPT_INVALID; /* XXX: check this */ + } + + return stack->script[stack->size]; +} + +static bool +_raqm_stack_push (_raqm_stack_t *stack, + hb_script_t script, + int pair_index) +{ + if (stack->size == stack->capacity) + { + RAQM_DBG ("Stack is Full\n"); + return false; + } + + stack->size++; + stack->script[stack->size] = script; + stack->pair_index[stack->size] = pair_index; + + return true; +} + +static int +_get_pair_index (const FriBidiChar ch) +{ + int lower = 0; + int upper = paired_len - 1; + + while (lower <= upper) + { + int mid = (lower + upper) / 2; + if (ch < paired_chars[mid]) + upper = mid - 1; + else if (ch > paired_chars[mid]) + lower = mid + 1; + else + return mid; + } + + return -1; +} + +#define STACK_IS_EMPTY(script) ((script)->size <= 0) +#define IS_OPEN(pair_index) (((pair_index) & 1) == 0) + +/* Resolve the script for each character in the input string, if the character + * script is common or inherited it takes the script of the character before it + * except paired characters which we try to make them use the same script. We + * then split the BiDi runs, if necessary, on script boundaries. + */ +static bool +_raqm_resolve_scripts (raqm_t *rq) +{ + int last_script_index = -1; + int last_set_index = -1; + hb_script_t last_script = HB_SCRIPT_INVALID; + _raqm_stack_t *stack = NULL; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + for (size_t i = 0; i < rq->text_len; ++i) + rq->text_info[i].script = hb_unicode_script (unicode_funcs, rq->text[i]); + +#ifdef RAQM_TESTING + RAQM_TEST ("Before script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + stack = _raqm_stack_new (rq->text_len); + if (!stack) + return false; + + for (int i = 0; i < (int) rq->text_len; i++) + { + if (rq->text_info[i].script == HB_SCRIPT_COMMON && last_script_index != -1) + { + int pair_index = _get_pair_index (rq->text[i]); + if (pair_index >= 0) + { + if (IS_OPEN (pair_index)) + { + /* is a paired character */ + rq->text_info[i].script = last_script; + last_set_index = i; + _raqm_stack_push (stack, rq->text_info[i].script, pair_index); + } + else + { + /* is a close paired character */ + /* find matching opening (by getting the last even index for current + * odd index) */ + while (!STACK_IS_EMPTY (stack) && + stack->pair_index[stack->size] != (pair_index & ~1)) + { + _raqm_stack_pop (stack); + } + if (!STACK_IS_EMPTY (stack)) + { + rq->text_info[i].script = _raqm_stack_top (stack); + last_script = rq->text_info[i].script; + last_set_index = i; + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + } + else + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + } + else if (rq->text_info[i].script == HB_SCRIPT_INHERITED && + last_script_index != -1) + { + rq->text_info[i].script = last_script; + last_set_index = i; + } + else + { + for (int j = last_set_index + 1; j < i; ++j) + rq->text_info[j].script = rq->text_info[i].script; + last_script = rq->text_info[i].script; + last_script_index = i; + last_set_index = i; + } + } + + /* Loop backwards and change any remaining Common or Inherit characters to + * take the script if the next character. + * https://github.com/HOST-Oman/libraqm/issues/95 + */ + for (int i = rq->text_len - 2; i >= 0; --i) + { + if (rq->text_info[i].script == HB_SCRIPT_INHERITED || + rq->text_info[i].script == HB_SCRIPT_COMMON) + rq->text_info[i].script = rq->text_info[i + 1].script; + } + +#ifdef RAQM_TESTING + RAQM_TEST ("After script detection:\n"); + for (size_t i = 0; i < rq->text_len; ++i) + { + SCRIPT_TO_STRING (rq->text_info[i].script); + RAQM_TEST ("script for ch[%zu]\t%s\n", i, buff); + } + RAQM_TEST ("\n"); +#endif + + _raqm_stack_free (stack); + + return true; +} + +static bool +_raqm_shape (raqm_t *rq) +{ + hb_buffer_flags_t hb_buffer_flags = HB_BUFFER_FLAG_BOT | HB_BUFFER_FLAG_EOT; + +#if defined(HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES) && \ + HAVE_DECL_HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES + if (rq->invisible_glyph < 0) + hb_buffer_flags |= HB_BUFFER_FLAG_REMOVE_DEFAULT_IGNORABLES; +#endif + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + run->buffer = hb_buffer_create (); + + hb_buffer_add_utf32 (run->buffer, rq->text, rq->text_len, + run->pos, run->len); + hb_buffer_set_script (run->buffer, run->script); + hb_buffer_set_language (run->buffer, rq->text_info[run->pos].lang); + hb_buffer_set_direction (run->buffer, run->direction); + hb_buffer_set_flags (run->buffer, hb_buffer_flags); + +#ifdef HAVE_HB_BUFFER_SET_INVISIBLE_GLYPH + if (rq->invisible_glyph > 0) + hb_buffer_set_invisible_glyph (run->buffer, rq->invisible_glyph); +#endif + + hb_shape_full (run->font, run->buffer, rq->features, rq->features_len, + NULL); + } + + return true; +} + +/* Convert index from UTF-32 to UTF-8 */ +static uint32_t +_raqm_u32_to_u8_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + char *output = malloc ((sizeof (char) * 4 * index) + 1); + + length = fribidi_unicode_to_charset (FRIBIDI_CHAR_SET_UTF8, + rq->text, + index, + output); + + free (output); + return length; +} + +/* Convert index from UTF-8 to UTF-32 */ +static uint32_t +_raqm_u8_to_u32_index (raqm_t *rq, + uint32_t index) +{ + FriBidiStrIndex length; + uint32_t *output = malloc (sizeof (uint32_t) * (index + 1)); + + length = fribidi_charset_to_unicode (FRIBIDI_CHAR_SET_UTF8, + rq->text_utf8, + index, + output); + + free (output); + return length; +} + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char); + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch); + +/** + * raqm_index_to_position: + * @rq: a #raqm_t. + * @index: (inout): character index. + * @x: (out): output x position. + * @y: (out): output y position. + * + * Calculates the cursor position after the character at @index. If the character + * is right-to-left, then the cursor will be at the left of it, whereas if the + * character is left-to-right, then the cursor will be at the right of it. + * + * Return value: + * %true if the process was successful, %false otherwise. + * + * Since: 0.2 + */ +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y) +{ + /* We don't currently support multiline, so y is always 0 */ + *y = 0; + *x = 0; + + if (rq == NULL) + return false; + + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u8_to_u32_index (rq, *index); + + if (*index >= rq->text_len) + return false; + + RAQM_TEST ("\n"); + + while (*index < rq->text_len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], rq->text[*index + 1])) + break; + + ++*index; + } + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + *x += position[i].x_advance; + + if (run->direction == HB_DIRECTION_LTR) + { + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + } + else + { + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + } + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + if (*index < next_cluster && *index >= curr_cluster) + { + if (run->direction == HB_DIRECTION_RTL) + *x -= position[i].x_advance; + *index = curr_cluster; + goto found; + } + } + } + +found: + if (rq->flags & RAQM_FLAG_UTF8) + *index = _raqm_u32_to_u8_index (rq, *index); + RAQM_TEST ("The position is %d at index %zu\n",*x ,*index); + return true; +} + +/** + * raqm_position_to_index: + * @rq: a #raqm_t. + * @x: x position. + * @y: y position. + * @index: (out): output character index. + * + * Returns the @index of the character at @x and @y position within text. + * If the position is outside the text, the last character is chosen as + * @index. + * + * Return value: + * %true if the process was successful, %false in case of error. + * + * Since: 0.2 + */ +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index) +{ + int delta_x = 0, current_x = 0; + (void)y; + + if (rq == NULL) + return false; + + if (x < 0) /* Get leftmost index */ + { + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = rq->text_len; + else + *index = 0; + return true; + } + + RAQM_TEST ("\n"); + + for (raqm_run_t *run = rq->runs; run != NULL; run = run->next) + { + size_t len; + hb_glyph_info_t *info; + hb_glyph_position_t *position; + len = hb_buffer_get_length (run->buffer); + info = hb_buffer_get_glyph_infos (run->buffer, NULL); + position = hb_buffer_get_glyph_positions (run->buffer, NULL); + + for (size_t i = 0; i < len; i++) + { + delta_x = position[i].x_advance; + if (x < (current_x + delta_x)) + { + bool before = false; + if (run->direction == HB_DIRECTION_LTR) + before = (x < current_x + (delta_x / 2)); + else + before = (x > current_x + (delta_x / 2)); + + if (before) + *index = info[i].cluster; + else + { + uint32_t curr_cluster = info[i].cluster; + uint32_t next_cluster = curr_cluster; + if (run->direction == HB_DIRECTION_LTR) + for (size_t j = i + 1; j < len && next_cluster == curr_cluster; j++) + next_cluster = info[j].cluster; + else + for (int j = i - 1; i != 0 && j >= 0 && next_cluster == curr_cluster; + j--) + next_cluster = info[j].cluster; + + if (next_cluster == curr_cluster) + next_cluster = run->pos + run->len; + + *index = next_cluster; + } + if (_raqm_allowed_grapheme_boundary (rq->text[*index],rq->text[*index + 1])) + { + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + + while (*index < (unsigned)run->pos + run->len) + { + if (_raqm_allowed_grapheme_boundary (rq->text[*index], + rq->text[*index + 1])) + { + *index += 1; + break; + } + *index += 1; + } + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + return true; + } + else + current_x += delta_x; + } + } + + /* Get rightmost index*/ + if (rq->resolved_dir == RAQM_DIRECTION_RTL) + *index = 0; + else + *index = rq->text_len; + + RAQM_TEST ("The start-index is %zu at position %d \n", *index, x); + + return true; +} + +typedef enum +{ + RAQM_GRAPHEM_CR, + RAQM_GRAPHEM_LF, + RAQM_GRAPHEM_CONTROL, + RAQM_GRAPHEM_EXTEND, + RAQM_GRAPHEM_REGIONAL_INDICATOR, + RAQM_GRAPHEM_PREPEND, + RAQM_GRAPHEM_SPACING_MARK, + RAQM_GRAPHEM_HANGUL_SYLLABLE, + RAQM_GRAPHEM_OTHER +} _raqm_grapheme_t; + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char) +{ + hb_unicode_general_category_t l_category; + hb_unicode_general_category_t r_category; + _raqm_grapheme_t l_grapheme, r_grapheme; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + l_category = hb_unicode_general_category (unicode_funcs, l_char); + r_category = hb_unicode_general_category (unicode_funcs, r_char); + l_grapheme = _raqm_get_grapheme_break (l_char, l_category); + r_grapheme = _raqm_get_grapheme_break (r_char, r_category); + + if (l_grapheme == RAQM_GRAPHEM_CR && r_grapheme == RAQM_GRAPHEM_LF) + return false; /*Do not break between a CR and LF GB3*/ + if (l_grapheme == RAQM_GRAPHEM_CONTROL || l_grapheme == RAQM_GRAPHEM_CR || + l_grapheme == RAQM_GRAPHEM_LF || r_grapheme == RAQM_GRAPHEM_CONTROL || + r_grapheme == RAQM_GRAPHEM_CR || r_grapheme == RAQM_GRAPHEM_LF) + return true; /*Break before and after CONTROL GB4, GB5*/ + if (r_grapheme == RAQM_GRAPHEM_HANGUL_SYLLABLE) + return false; /*Do not break Hangul syllable sequences. GB6, GB7, GB8*/ + if (l_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR && + r_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR) + return false; /*Do not break between regional indicator symbols. GB8a*/ + if (r_grapheme == RAQM_GRAPHEM_EXTEND) + return false; /*Do not break before extending characters. GB9*/ + /*Do not break before SpacingMarks, or after Prepend characters.GB9a, GB9b*/ + if (l_grapheme == RAQM_GRAPHEM_PREPEND) + return false; + if (r_grapheme == RAQM_GRAPHEM_SPACING_MARK) + return false; + return true; /*Otherwise, break everywhere. GB1, GB2, GB10*/ +} + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category) +{ + _raqm_grapheme_t gb_type; + + gb_type = RAQM_GRAPHEM_OTHER; + switch ((int)category) + { + case HB_UNICODE_GENERAL_CATEGORY_FORMAT: + if (ch == 0x200C || ch == 0x200D) + gb_type = RAQM_GRAPHEM_EXTEND; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_CONTROL: + if (ch == 0x000D) + gb_type = RAQM_GRAPHEM_CR; + else if (ch == 0x000A) + gb_type = RAQM_GRAPHEM_LF; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_SURROGATE: + case HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: + if ((ch >= 0xFFF0 && ch <= 0xFFF8) || + (ch >= 0xE0000 && ch <= 0xE0FFF)) + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: + if (ch != 0x102B || ch != 0x102C || ch != 0x1038 || + (ch <= 0x1062 && ch >= 0x1064) || (ch <= 0x1067 && ch >= 0x106D) || + ch != 0x1083 || (ch <= 0x1087 && ch >= 0x108C) || ch != 0x108F || + (ch <= 0x109A && ch >= 0x109C) || ch != 0x1A61 || ch != 0x1A63 || + ch != 0x1A64 || ch != 0xAA7B || ch != 0xAA70 || ch != 0x11720 || + ch != 0x11721) /**/ + gb_type = RAQM_GRAPHEM_SPACING_MARK; + + else if (ch == 0x09BE || ch == 0x09D7 || + ch == 0x0B3E || ch == 0x0B57 || ch == 0x0BBE || ch == 0x0BD7 || + ch == 0x0CC2 || ch == 0x0CD5 || ch == 0x0CD6 || + ch == 0x0D3E || ch == 0x0D57 || ch == 0x0DCF || ch == 0x0DDF || + ch == 0x1D165 || (ch >= 0x1D16E && ch <= 0x1D172)) + gb_type = RAQM_GRAPHEM_EXTEND; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: + if (ch == 0x0E33 || ch == 0x0EB3) + gb_type = RAQM_GRAPHEM_SPACING_MARK; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: + if (ch >= 0x1F1E6 && ch <= 0x1F1FF) + gb_type = RAQM_GRAPHEM_REGIONAL_INDICATOR; + break; + + default: + gb_type = RAQM_GRAPHEM_OTHER; + break; + } + + if (_raqm_in_hangul_syllable (ch)) + gb_type = RAQM_GRAPHEM_HANGUL_SYLLABLE; + + return gb_type; +} + +static bool +_raqm_in_hangul_syllable (hb_codepoint_t ch) +{ + (void)ch; + return false; +} + +/** + * raqm_version: + * @major: (out): Library major version component. + * @minor: (out): Library minor version component. + * @micro: (out): Library micro version component. + * + * Returns library version as three integer components. + * + * Since: 0.7 + **/ +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro) +{ + *major = RAQM_VERSION_MAJOR; + *minor = RAQM_VERSION_MINOR; + *micro = RAQM_VERSION_MICRO; +} + +/** + * raqm_version_string: + * + * Returns library version as a string with three components. + * + * Return value: library version string. + * + * Since: 0.7 + **/ +const char * +raqm_version_string (void) +{ + return RAQM_VERSION_STRING; +} + +/** + * raqm_version_atleast: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro) +{ + return RAQM_VERSION_ATLEAST (major, minor, micro); +} + +/** + * RAQM_VERSION_ATLEAST: + * @major: Library major version component. + * @minor: Library minor version component. + * @micro: Library micro version component. + * + * Checks if library version is less than or equal the specified version. + * + * Return value: + * %true if library version is less than or equal the specfied version, %false + * otherwise. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_STRING: + * + * Library version as a string with three components. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MAJOR: + * + * Library major version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MINOR: + * + * Library minor version component. + * + * Since: 0.7 + **/ + +/** + * RAQM_VERSION_MICRO: + * + * Library micro version component. + * + * Since: 0.7 + **/ diff --git a/lib/libraqm/raqm.h b/lib/libraqm/raqm.h new file mode 100644 index 000000000..1a33fe8ba --- /dev/null +++ b/lib/libraqm/raqm.h @@ -0,0 +1,185 @@ +/* + * Copyright © 2015 Information Technology Authority (ITA) + * Copyright © 2016 Khaled Hosny + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef _RAQM_H_ +#define _RAQM_H_ +#define _RAQM_H_IN_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include FT_FREETYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "raqm-version.h" + +/** + * raqm_t: + * + * This is the main object holding all state of the currently processed text as + * well as its output. + * + * Since: 0.1 + */ +typedef struct _raqm raqm_t; + +/** + * raqm_direction_t: + * @RAQM_DIRECTION_DEFAULT: Detect paragraph direction automatically. + * @RAQM_DIRECTION_RTL: Paragraph is mainly right-to-left text. + * @RAQM_DIRECTION_LTR: Paragraph is mainly left-to-right text. + * @RAQM_DIRECTION_TTB: Paragraph is mainly vertical top-to-bottom text. + * + * Base paragraph direction, see raqm_set_par_direction(). + * + * Since: 0.1 + */ +typedef enum +{ + RAQM_DIRECTION_DEFAULT, + RAQM_DIRECTION_RTL, + RAQM_DIRECTION_LTR, + RAQM_DIRECTION_TTB +} raqm_direction_t; + +/** + * raqm_glyph_t: + * @index: the index of the glyph in the font file. + * @x_advance: the glyph advance width in horizontal text. + * @y_advance: the glyph advance width in vertical text. + * @x_offset: the horizontal movement of the glyph from the current point. + * @y_offset: the vertical movement of the glyph from the current point. + * @cluster: the index of original character in input text. + * @ftface: the @FT_Face of the glyph. + * + * The structure that holds information about output glyphs, returned from + * raqm_get_glyphs(). + */ +typedef struct raqm_glyph_t { + unsigned int index; + int x_advance; + int y_advance; + int x_offset; + int y_offset; + uint32_t cluster; + FT_Face ftface; +} raqm_glyph_t; + +raqm_t * +raqm_create (void); + +raqm_t * +raqm_reference (raqm_t *rq); + +void +raqm_destroy (raqm_t *rq); + +bool +raqm_set_text (raqm_t *rq, + const uint32_t *text, + size_t len); + +bool +raqm_set_text_utf8 (raqm_t *rq, + const char *text, + size_t len); + +bool +raqm_set_par_direction (raqm_t *rq, + raqm_direction_t dir); + +bool +raqm_set_language (raqm_t *rq, + const char *lang, + size_t start, + size_t len); + +bool +raqm_add_font_feature (raqm_t *rq, + const char *feature, + int len); + +bool +raqm_set_freetype_face (raqm_t *rq, + FT_Face face); + +bool +raqm_set_freetype_face_range (raqm_t *rq, + FT_Face face, + size_t start, + size_t len); + +bool +raqm_set_freetype_load_flags (raqm_t *rq, + int flags); + +bool +raqm_set_invisible_glyph (raqm_t *rq, + int gid); + +bool +raqm_layout (raqm_t *rq); + +raqm_glyph_t * +raqm_get_glyphs (raqm_t *rq, + size_t *length); + +bool +raqm_index_to_position (raqm_t *rq, + size_t *index, + int *x, + int *y); + +bool +raqm_position_to_index (raqm_t *rq, + int x, + int y, + size_t *index); + +void +raqm_version (unsigned int *major, + unsigned int *minor, + unsigned int *micro); + +const char * +raqm_version_string (void); + +bool +raqm_version_atleast (unsigned int major, + unsigned int minor, + unsigned int micro); + + +#ifdef __cplusplus +} +#endif +#undef _RAQM_H_IN_ +#endif /* _RAQM_H_ */