stk-code_catmod/lib/libraqm/raqm.c

2081 lines
50 KiB
C
Raw Normal View History

/*
* Copyright © 2015 Information Technology Authority (ITA) <foss@ita.gov.om>
* Copyright © 2016 Khaled Hosny <khaledhosny@eglug.org>
*
* 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 <assert.h>
#include <string.h>
#include <fribidi/fribidi.h>
#include <harfbuzz/hb.h>
#include <harfbuzz/hb-ft.h>
#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:
*
* |[<!-- language="C" -->
* #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:
* |[<prompt>
* 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, dont 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, dont 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
**/