2081 lines
50 KiB
C
2081 lines
50 KiB
C
|
/*
|
|||
|
* 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, 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
|
|||
|
**/
|