stk-code_catmod/lib/libraqm/raqm.c

2081 lines
50 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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
**/