Add complex text layout support

This pull request (based on Asma's works) adds support for languages that require [complex text
layout](https://en.wikipedia.org/wiki/Complex_text_layout).

We are using [libraqm](https://github.com/HOST-Oman/libraqm), a small source
code-only library that wraps FriBidi (for bidirectional text support) and
HarfBuzz (for text shaping), and does proper BiDi and script itemization.

The CTL support is enabled by default but can be disabled at compiling time,
and we provide a fallback function that uses your original code without CTL
support.
master
Khalid Alqassabi 2016-06-30 13:19:25 +04:00
parent 3fe0a7128b
commit 1dbccb737c
2 changed files with 160 additions and 44 deletions

View File

@ -233,6 +233,14 @@ if test "$gd_with_LIBFREETYPE" = "yes"; then
AC_DEFINE([HAVE_FT2BUILD_H], [1], [Define if you have the ft2build.h header.])
fi
dnl Check for Raqm support.
GD_LIB_PKG_CHECK([LIBRAQM], [RAQM], [raqm], [raqm], [
AC_CHECK_LIB([raqm], [raqm_create], [dnl
AS_VAR_APPEND([LIBRAQM_LIBS], [" -lraqm -lfreetype"])
gd_found_lib=yes
])
])
dnl Check for fontconfig support.
GD_LIB_PKG_CHECK([LIBFONTCONFIG], [FONTCONFIG], [fontconfig], [fontconfig], [
AC_CHECK_LIB([fontconfig], [FcInit], [dnl
@ -318,6 +326,7 @@ AC_MSG_RESULT([
Support for Fontconfig library: $gd_with_LIBFONTCONFIG
Support for Xpm library: $gd_with_LIBXPM
Support for liq library: $gd_with_LIBIMAGEQUANT
Support for complext text: $gd_with_LIBRAQM
Support for pthreads: $ax_pthread_ok
])

View File

@ -431,6 +431,107 @@ gdTcl_UtfToUniChar (char *str, Tcl_UniChar * chPtr)
return 1;
}
#ifdef HAVE_LIBRAQM
#include <raqm.h>
#endif
typedef struct {
unsigned int index;
FT_Pos x_advance;
FT_Pos x_offset;
FT_Pos y_offset;
uint32_t cluster;
} glyphInfo;
static size_t
textLayout(uint32_t *text, int len,
FT_Face face, gdFTStringExtraPtr strex,
glyphInfo **glyph_info)
{
size_t count;
glyphInfo *info;
#ifdef HAVE_LIBRAQM
size_t i;
raqm_glyph_t *glyphs;
raqm_t *rq = raqm_create ();
if (!rq || !raqm_set_text (rq, text, len) ||
!raqm_set_freetype_face (rq, face) ||
!raqm_set_par_direction (rq, RAQM_DIRECTION_DEFAULT) ||
!raqm_layout (rq)) {
raqm_destroy (rq);
return 0;
}
glyphs = raqm_get_glyphs (rq, &count);
if (!glyphs) {
raqm_destroy (rq);
return 0;
}
info = (glyphInfo*) gdMalloc (sizeof (glyphInfo) * count);
if (!info) {
raqm_destroy (rq);
return 0;
}
for (i = 0; i < count; i++) {
info[i].index = glyphs[i].index;
info[i].x_offset = glyphs[i].x_offset;
info[i].y_offset = glyphs[i].y_offset;
info[i].x_advance = glyphs[i].x_advance;
info[i].cluster = glyphs[i].cluster;
}
raqm_destroy (rq);
#else
FT_UInt glyph_index = 0, previous = 0;
FT_Vector delta;
FT_Error err;
info = (glyphInfo*) gdMalloc (sizeof (glyphInfo) * len);
if (!info) {
return 0;
}
for (count = 0; count < len; count++) {
/* Convert character code to glyph index */
glyph_index = FT_Get_Char_Index (face, text[count]);
/* retrieve kerning distance */
if (! (strex && (strex->flags & gdFTEX_DISABLE_KERNING))
&& ! FT_IS_FIXED_WIDTH(face)
&& FT_HAS_KERNING(face)
&& previous
&& glyph_index)
FT_Get_Kerning (face, previous, glyph_index, ft_kerning_default, &delta);
else
delta.x = delta.y = 0;
err = FT_Load_Glyph (face, glyph_index, FT_LOAD_DEFAULT);
if (err) {
gdFree (info);
return 0;
}
info[count].index = glyph_index;
info[count].x_offset = 0;
info[count].y_offset = 0;
if (delta.x != 0)
info[count - 1].x_advance += delta.x;
info[count].x_advance = face->glyph->metrics.horiAdvance;
info[count].cluster = count;
/* carriage returns or newlines */
if (text[count] == '\r' || text[count] == '\n')
previous = 0; /* clear kerning flag */
else
previous = glyph_index;
}
#endif
*glyph_info = info;
return count;
}
/********************************************************************/
/* font cache functions */
@ -944,20 +1045,23 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, char *f
gdFTStringExtraPtr strex)
{
FT_Matrix matrix;
FT_Vector penf, oldpenf, delta, total_min = {0,0}, total_max = {0,0}, glyph_min, glyph_max;
FT_Vector penf, oldpenf, total_min = {0,0}, total_max = {0,0}, glyph_min, glyph_max;
FT_Face face;
FT_CharMap charmap = NULL;
FT_Glyph image;
FT_GlyphSlot slot;
FT_Error err;
FT_UInt glyph_index, previous;
FT_UInt glyph_index;
double sin_a = sin (angle);
double cos_a = cos (angle);
int len, i, ch;
int i, ch;
font_t *font;
fontkey_t fontkey;
char *next;
char *tmpstr = 0;
uint32_t *text;
glyphInfo *info = NULL;
size_t count;
int render = (im && (im->trueColor || (fg <= 255 && fg >= -255)));
FT_BitmapGlyph bm;
/* 2.0.13: Bob Ostermann: don't force autohint, that's just for testing
@ -1170,31 +1274,11 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, char *f
oldpenf.x = oldpenf.y = 0; /* for postscript xshow operator */
penf.x = penf.y = 0; /* running position of non-rotated glyphs */
previous = 0; /* index of previous glyph for kerning calculations */
for (i=0; *next; i++) {
FT_Activate_Size (platform_independent);
text = (uint32_t*) gdCalloc (sizeof (uint32_t), strlen(next));
i = 0;
while (*next) {
int len;
ch = *next;
/* carriage returns */
if (ch == '\r') {
penf.x = 0;
previous = 0; /* clear kerning flag */
next++;
continue;
}
/* newlines */
if (ch == '\n') {
/* 2.0.13: reset penf.x. Christopher J. Grayce */
penf.x = 0;
penf.y += linespace * ptsize * 64 * METRIC_RES / 72;
penf.y &= ~63; /* round down to 1/METRIC_RES */
previous = 0; /* clear kerning flag */
next++;
continue;
}
switch (encoding) {
case gdFTEX_Unicode: {
/* use UTF-8 mapping from ASCII */
@ -1270,22 +1354,43 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, char *f
next++;
break;
}
text[i] = ch;
i++;
}
/* Convert character code to glyph index */
glyph_index = FT_Get_Char_Index (face, ch);
FT_Activate_Size (platform_independent);
/* retrieve kerning distance */
if ( ! (strex && (strex->flags & gdFTEX_DISABLE_KERNING))
&& ! FT_IS_FIXED_WIDTH(face)
&& FT_HAS_KERNING(face)
&& previous
&& glyph_index)
FT_Get_Kerning (face, previous, glyph_index, ft_kerning_default, &delta);
else
delta.x = delta.y = 0;
count = textLayout (text , i, face, strex, &info);
gdFree (text);
penf.x += delta.x;
if (!count) {
gdFree (tmpstr);
gdCacheDelete (tc_cache);
gdMutexUnlock (gdFontCacheMutex);
return "Problem doing text layout";
}
for (i = 0; i < count; i++) {
FT_Activate_Size (platform_independent);
ch = text[info[i].cluster];
/* carriage returns */
if (ch == '\r') {
penf.x = 0;
continue;
}
/* newlines */
if (ch == '\n') {
/* 2.0.13: reset penf.x. Christopher J. Grayce */
penf.x = 0;
penf.y += linespace * ptsize * 64 * METRIC_RES / 72;
penf.y &= ~63; /* round down to 1/METRIC_RES */
continue;
}
glyph_index = info[i].index;
/* When we know the position of the second or subsequent character,
save the (kerned) advance from the preceeding character in the
xshow vector */
@ -1329,7 +1434,7 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, char *f
return "Problem loading glyph";
}
horiAdvance = slot->metrics.horiAdvance;
horiAdvance = info[i].x_advance;
if (brect) {
/* only if need brect */
@ -1395,19 +1500,21 @@ BGD_DECLARE(char *) gdImageStringFTEx (gdImage * im, int *brect, int fg, char *f
bm = (FT_BitmapGlyph) image;
/* position rounded down to nearest pixel at current dpi
(the estimate was rounded up to next 1/METRIC_RES, so this should fit) */
FT_Pos pen_x = penf.x + info[i].x_offset;
FT_Pos pen_y = penf.y - info[i].y_offset;
gdft_draw_bitmap (tc_cache, im, fg, bm->bitmap,
(int)(x + (penf.x * cos_a + penf.y * sin_a)*hdpi/(METRIC_RES*64) + bm->left),
(int)(y - (penf.x * sin_a - penf.y * cos_a)*vdpi/(METRIC_RES*64) - bm->top));
(int)(x + (pen_x * cos_a + pen_y * sin_a)*hdpi/(METRIC_RES*64) + bm->left),
(int)(y - (pen_x * sin_a - pen_y * cos_a)*vdpi/(METRIC_RES*64) - bm->top));
FT_Done_Glyph (image);
}
/* record current glyph index for kerning */
previous = glyph_index;
penf.x += horiAdvance;
}
gdFree(info);
/* Save the (unkerned) advance from the last character in the xshow vector */
if (strex && (strex->flags & gdFTEX_XSHOW) && strex->xshow) {
sprintf(strex->xshow + xshow_pos, "%g",