Ejemplo n.º 1
0
static void get_bidi_levels(FriBidiChar *in,int length,int is_rtl,FriBidiLevel *embed)
{
	FriBidiCharType *types = utl_malloc(sizeof(FriBidiCharType)*length);
	FriBidiParType direction = is_rtl ? FRIBIDI_PAR_RTL : FRIBIDI_PAR_LTR;

	fribidi_get_bidi_types(in,length,types);
	fribidi_get_par_embedding_levels(types,length,&direction,embed);

	utl_free(types);
}
Ejemplo n.º 2
0
static int AnalyzeParagraph( paragraph_t *p_paragraph )
{
    fribidi_get_bidi_types(  p_paragraph->p_code_points,
                             p_paragraph->i_size,
                             p_paragraph->p_types );
    fribidi_get_par_embedding_levels( p_paragraph->p_types,
                                      p_paragraph->i_size,
                                      &p_paragraph->paragraph_type,
                                      p_paragraph->p_levels );

#ifdef HAVE_HARFBUZZ
    hb_unicode_funcs_t *p_funcs = hb_unicode_funcs_get_default();
    for( int i = 0; i < p_paragraph->i_size; ++i )
        p_paragraph->p_scripts[ i ] =
            hb_unicode_script( p_funcs, p_paragraph->p_code_points[ i ] );

    hb_script_t i_last_script;
    int i_last_script_index = -1;
    int i_last_set_index = -1;

    /*
     * For shaping to work, characters that are assigned HB_SCRIPT_COMMON or
     * HB_SCRIPT_INHERITED should be resolved to the last encountered valid
     * script value, if any, and to the first one following them otherwise
     */
    for( int i = 0; i < p_paragraph->i_size; ++i )
    {
        if( p_paragraph->p_scripts[ i ] == HB_SCRIPT_COMMON
            || p_paragraph->p_scripts[ i ] == HB_SCRIPT_INHERITED)
        {
            if( i_last_script_index != -1)
            {
                p_paragraph->p_scripts[ i ] = i_last_script;
                i_last_set_index = i;
            }
        }
        else
        {
            for( int j = i_last_set_index + 1; j < i; ++j )
                p_paragraph->p_scripts[ j ] = p_paragraph->p_scripts[ i ];

            i_last_script = p_paragraph->p_scripts[ i ];
            i_last_script_index = i;
            i_last_set_index = i;
        }
    }
#endif //HAVE_HARFBUZZ

    return VLC_SUCCESS;
}
Ejemplo n.º 3
0
int
main (int argc, char **argv)
{
    GIOChannel *channel;
    GIOStatus status;
    GError *error;
    gchar *line = NULL;
    gsize length, terminator_pos;
    FriBidiStrIndex *expected_ltor = NULL;
    FriBidiStrIndex expected_ltor_len = 0;
    FriBidiStrIndex *ltor = NULL;
    FriBidiStrIndex ltor_len = 0;
    FriBidiCharType *types = NULL;
    FriBidiStrIndex types_len = 0;
    FriBidiLevel *expected_levels = NULL;
    FriBidiLevel expected_levels_len = 0;
    FriBidiLevel *levels = NULL;
    FriBidiStrIndex levels_len = 0;
    int base_dir_flags, base_dir_mode;
    int numerrs = 0;
    int numtests = 0;
    int line_no = 0;
    gboolean debug = FALSE;
    const char *filename;
    int next_arg;

    if (argc < 2) {
	g_printerr ("usage: %s [--debug] test-file-name\n", argv[0]);
	exit (1);
    }

    next_arg = 1;
    if (!strcmp (argv[next_arg], "--debug")) {
	debug = TRUE;
	next_arg++;
    }

    filename = argv[next_arg++];

    error = NULL;
    channel = g_io_channel_new_file (filename, "r", &error);
    if (!channel) {
	g_printerr ("%s\n", error->message);
	exit (1);
    }

    while (TRUE) {
	error = NULL;
	g_free (line);
	status = g_io_channel_read_line (channel, &line, &length, &terminator_pos, &error);
	switch (status) {
        case G_IO_STATUS_ERROR:
            g_printerr ("%s\n", error->message);
            exit (1);

        case G_IO_STATUS_EOF:
	    goto done;

        case G_IO_STATUS_AGAIN:
            continue;

        case G_IO_STATUS_NORMAL:
            line[terminator_pos] = '\0';
            break;
	}

	line_no++;

	if (line[0] == '#' || line[0] == '\0')
	    continue;

	if (line[0] == '@')
	{
	    if (!strncmp (line, "@Reorder:", 9)) {
		g_free (expected_ltor);
		expected_ltor = parse_reorder_line (line, &expected_ltor_len);
		continue;
	    }
	    if (!strncmp (line, "@Levels:", 8)) {
		g_free (expected_levels);
		expected_levels = parse_levels_line (line, &expected_levels_len);
		continue;
	    }
	    continue;
	}

	/* Test line */
	g_free (types);
	types = parse_test_line (line, &types_len, &base_dir_flags);

	g_free (levels);
	levels = g_malloc (sizeof (FriBidiLevel) * types_len);
	levels_len = types_len;

	g_free (ltor);
	ltor = g_malloc (sizeof (FriBidiStrIndex) * types_len);

	/* Test it */
	for (base_dir_mode = 0; base_dir_mode < 3; base_dir_mode++) {
	    FriBidiParType base_dir;
	    int i, j;
	    gboolean matches;

	    if ((base_dir_flags & (1<<base_dir_mode)) == 0)
		continue;

            numtests++;

	    switch (base_dir_mode) {
	    case 0: base_dir = FRIBIDI_PAR_ON;  break;
	    case 1: base_dir = FRIBIDI_PAR_LTR; break;
	    case 2: base_dir = FRIBIDI_PAR_RTL; break;
	    }

	    fribidi_get_par_embedding_levels (types, types_len,
					      &base_dir,
					      levels);

	    for (i = 0; i < types_len; i++)
	        ltor[i] = i;

	    fribidi_reorder_line (0 /*FRIBIDI_FLAG_REORDER_NSM*/,
				  types, types_len,
				  0, base_dir,
				  levels,
				  NULL,
				  ltor);

	    j = 0;
	    for (i = 0; i < types_len; i++)
	    	if (!FRIBIDI_IS_EXPLICIT_OR_BN (types[ltor[i]]))
		    ltor[j++] = ltor[i];
	    ltor_len = j;

	    /* Compare */
	    matches = TRUE;
	    if (levels_len != expected_levels_len)
		matches = FALSE;
	    if (matches)
		for (i = 0; i < levels_len; i++)
		    if (levels[i] != expected_levels[i] &&
			expected_levels[i] != (FriBidiLevel) -1) {
			matches = FALSE;
			break;
		    }

	    if (ltor_len != expected_ltor_len)
		matches = FALSE;
	    if (matches)
		for (i = 0; i < ltor_len; i++)
		    if (ltor[i] != expected_ltor[i]) {
			matches = FALSE;
			break;
		    }

	    if (!matches)
	    {
		numerrs++;

		g_printerr ("failure on line %d\n", line_no);
		g_printerr ("input is: %s\n", line);
		g_printerr ("base dir: %s\n", base_dir_mode==0 ? "auto"
					    : base_dir_mode==1 ? "LTR" : "RTL");

		g_printerr ("expected levels:");
		for (i = 0; i < expected_levels_len; i++)
		    if (expected_levels[i] == (FriBidiLevel) -1)
			g_printerr (" x");
		    else
			g_printerr (" %d", expected_levels[i]);
		g_printerr ("\n");
		g_printerr ("returned levels:");
		for (i = 0; i < levels_len; i++)
		    g_printerr (" %d", levels[i]);
		g_printerr ("\n");

		g_printerr ("expected order:");
		for (i = 0; i < expected_ltor_len; i++)
		    g_printerr (" %d", expected_ltor[i]);
		g_printerr ("\n");
		g_printerr ("returned order:");
		for (i = 0; i < ltor_len; i++)
		    g_printerr (" %d", ltor[i]);
		g_printerr ("\n");

		if (debug) {
		    FriBidiParType base_dir;

		    fribidi_set_debug (1);

		    switch (base_dir_mode) {
		    case 0: base_dir = FRIBIDI_PAR_ON;  break;
		    case 1: base_dir = FRIBIDI_PAR_LTR; break;
		    case 2: base_dir = FRIBIDI_PAR_RTL; break;
		    }

		    fribidi_get_par_embedding_levels (types, types_len,
						      &base_dir,
						      levels);

		    fribidi_set_debug (0);
		}

		g_printerr ("\n");
	    }
	}
    }

done:
    g_free (ltor);
    g_free (levels);
    g_free (expected_ltor);
    g_free (types);
    g_free (line);
    g_io_channel_unref (channel);
    if (error)
	g_error_free (error);

    if (numerrs)
	g_printerr ("%d errors out of %d total tests\n", numerrs, numtests);
    return numerrs;
}
Ejemplo n.º 4
0
/**
 * pango_log2vis_get_embedding_levels:
 * @text:      the text to itemize.
 * @length:    the number of bytes (not characters) to process, or -1
 *             if @text is nul-terminated and the length should be calculated.
 * @pbase_dir: input base direction, and output resolved direction.
 *
 * This will return the bidirectional embedding levels of the input paragraph
 * as defined by the Unicode Bidirectional Algorithm available at:
 *
 *   http://www.unicode.org/reports/tr9/
 *
 * If the input base direction is a weak direction, the direction of the
 * characters in the text will determine the final resolved direction.
 *
 * Return value: a newly allocated array of embedding levels, one item per
 *               character (not byte), that should be freed using g_free.
 *
 * Since: 1.4
 */
guint8 *
pango_log2vis_get_embedding_levels (const gchar    *text,
				    int             length,
				    PangoDirection *pbase_dir)
{
  glong n_chars, i;
  guint8 *embedding_levels_list;
  const gchar *p;
  FriBidiParType fribidi_base_dir;
  FriBidiCharType *bidi_types;
#ifdef USE_FRIBIDI_EX_API
  FriBidiBracketType *bracket_types;
#endif
  FriBidiLevel max_level;

  G_STATIC_ASSERT (sizeof (FriBidiLevel) == sizeof (guint8));
  G_STATIC_ASSERT (sizeof (FriBidiChar) == sizeof (gunichar));

  switch (*pbase_dir)
    {
    case PANGO_DIRECTION_LTR:
    case PANGO_DIRECTION_TTB_RTL:
      fribidi_base_dir = FRIBIDI_PAR_LTR;
      break;
    case PANGO_DIRECTION_RTL:
    case PANGO_DIRECTION_TTB_LTR:
      fribidi_base_dir = FRIBIDI_PAR_RTL;
      break;
    case PANGO_DIRECTION_WEAK_RTL:
      fribidi_base_dir = FRIBIDI_PAR_WRTL;
      break;
    case PANGO_DIRECTION_WEAK_LTR:
    case PANGO_DIRECTION_NEUTRAL:
    default:
      fribidi_base_dir = FRIBIDI_PAR_WLTR;
      break;
    }

  if (length < 0)
    length = strlen (text);

  n_chars = g_utf8_strlen (text, length);

  bidi_types = g_new (FriBidiCharType, n_chars);
#ifdef USE_FRIBIDI_EX_API
  bracket_types = g_new (FriBidiBracketType, n_chars);
#endif
  embedding_levels_list = g_new (guint8, n_chars);

  for (i = 0, p = text; p < text + length; p = g_utf8_next_char(p), i++)
    {
      gunichar ch = g_utf8_get_char (p);
      bidi_types[i] = fribidi_get_bidi_type (ch);
#ifdef USE_FRIBIDI_EX_API
      if (G_UNLIKELY(bidi_types[i] == FRIBIDI_TYPE_ON))
        bracket_types[i] = fribidi_get_bracket (ch);
      else
        bracket_types[i] = FRIBIDI_NO_BRACKET;
#endif
    }

#ifdef USE_FRIBIDI_EX_API
  max_level = fribidi_get_par_embedding_levels_ex (bidi_types, bracket_types, n_chars,
						   &fribidi_base_dir,
						   (FriBidiLevel*)embedding_levels_list);
  g_free (bracket_types);
#else
  max_level = fribidi_get_par_embedding_levels (bidi_types, n_chars,
						&fribidi_base_dir,
						(FriBidiLevel*)embedding_levels_list);
#endif

  g_free (bidi_types);

  if (G_UNLIKELY(max_level == 0))
    {
      /* fribidi_get_par_embedding_levels() failed,
       * is this the best thing to do? */
      memset (embedding_levels_list, 0, length);
    }

  *pbase_dir = (fribidi_base_dir == FRIBIDI_PAR_LTR) ?  PANGO_DIRECTION_LTR : PANGO_DIRECTION_RTL;

  return embedding_levels_list;
}
Ejemplo n.º 5
0
static int shape_text(AVFilterContext *ctx)
{
    DrawTextContext *s = ctx->priv;
    uint8_t *tmp;
    int ret = AVERROR(ENOMEM);
    static const FriBidiFlags flags = FRIBIDI_FLAGS_DEFAULT |
                                      FRIBIDI_FLAGS_ARABIC;
    FriBidiChar *unicodestr = NULL;
    FriBidiStrIndex len;
    FriBidiParType direction = FRIBIDI_PAR_LTR;
    FriBidiStrIndex line_start = 0;
    FriBidiStrIndex line_end = 0;
    FriBidiLevel *embedding_levels = NULL;
    FriBidiArabicProp *ar_props = NULL;
    FriBidiCharType *bidi_types = NULL;
    FriBidiStrIndex i,j;

    len = strlen(s->text);
    if (!(unicodestr = av_malloc_array(len, sizeof(*unicodestr)))) {
        goto out;
    }
    len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8,
                                     s->text, len, unicodestr);

    bidi_types = av_malloc_array(len, sizeof(*bidi_types));
    if (!bidi_types) {
        goto out;
    }

    fribidi_get_bidi_types(unicodestr, len, bidi_types);

    embedding_levels = av_malloc_array(len, sizeof(*embedding_levels));
    if (!embedding_levels) {
        goto out;
    }

    if (!fribidi_get_par_embedding_levels(bidi_types, len, &direction,
                                          embedding_levels)) {
        goto out;
    }

    ar_props = av_malloc_array(len, sizeof(*ar_props));
    if (!ar_props) {
        goto out;
    }

    fribidi_get_joining_types(unicodestr, len, ar_props);
    fribidi_join_arabic(bidi_types, len, embedding_levels, ar_props);
    fribidi_shape(flags, embedding_levels, len, ar_props, unicodestr);

    for (line_end = 0, line_start = 0; line_end < len; line_end++) {
        if (is_newline(unicodestr[line_end]) || line_end == len - 1) {
            if (!fribidi_reorder_line(flags, bidi_types,
                                      line_end - line_start + 1, line_start,
                                      direction, embedding_levels, unicodestr,
                                      NULL)) {
                goto out;
            }
            line_start = line_end + 1;
        }
    }

    /* Remove zero-width fill chars put in by libfribidi */
    for (i = 0, j = 0; i < len; i++)
        if (unicodestr[i] != FRIBIDI_CHAR_FILL)
            unicodestr[j++] = unicodestr[i];
    len = j;

    if (!(tmp = av_realloc(s->text, (len * 4 + 1) * sizeof(*s->text)))) {
        /* Use len * 4, as a unicode character can be up to 4 bytes in UTF-8 */
        goto out;
    }

    s->text = tmp;
    len = fribidi_unicode_to_charset(FRIBIDI_CHAR_SET_UTF8,
                                     unicodestr, len, s->text);
    ret = 0;

out:
    av_free(unicodestr);
    av_free(embedding_levels);
    av_free(ar_props);
    av_free(bidi_types);
    return ret;
}
Ejemplo n.º 6
0
/**
 * @internal
 * Allocates bidi properties according to ustr. First checks to see if the
 * passed has rtl chars, if not, it returns NULL.
 *
 * Assumes all the segment_idxs are either -1 or legal, and > 0 indexes.
 * Also assumes that the characters at the override points are of weak/neutral
 * bidi type, otherwise unexpected results may occur.
 *
 * @param ustr The string to update according to.
 * @param len The length of the string
 * @param segment_idxs A -1 terminated array of points to start a new bidi analysis at (used for section high level bidi overrides). - NULL means none.
 * @return returns allocated paragraph props on success, NULL otherwise.
 */
Evas_BiDi_Paragraph_Props *
evas_bidi_paragraph_props_get(const Eina_Unicode *eina_ustr, size_t len,
      int *segment_idxs)
{
   Evas_BiDi_Paragraph_Props *bidi_props = NULL;
   EvasBiDiCharType *char_types = NULL;
   EvasBiDiLevel *embedding_levels = NULL;
   const FriBidiChar *ustr;
   FriBidiChar *base_ustr = NULL;

   if (!eina_ustr)
      return NULL;


   if (!evas_bidi_is_rtl_str(eina_ustr)) /* No need to handle bidi */
     {
        len = -1;
        goto cleanup;
     }

   len = eina_unicode_strlen(eina_ustr);
   /* The size of fribidichar s different than eina_unicode, convert */
#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
   base_ustr = calloc(len + 1, sizeof(FriBidiChar));
   base_ustr = _evas_bidi_unicode_to_fribidichar(base_ustr, eina_ustr);
   ustr = base_ustr;
#else
   ustr = (const FriBidiChar *) eina_ustr;
#endif

   bidi_props = evas_bidi_paragraph_props_new();

   /* Prep work for reordering */
   char_types = (EvasBiDiCharType *) malloc(sizeof(EvasBiDiCharType) * len);
   if (!char_types)
      {
         len = -2;
         goto cleanup;
      }
   fribidi_get_bidi_types(ustr, len, char_types);

   embedding_levels = (EvasBiDiLevel *)malloc(sizeof(EvasBiDiLevel) * len);
   if (!embedding_levels)
     {
        len = -2;
        goto cleanup;
     }

   if (segment_idxs)
     {
        size_t pos = 0;
        int *itr;
        EvasBiDiLevel base_level = 0;
        EvasBiDiParType direction;

        for (itr = segment_idxs ; *itr > 0 ; itr++)
          {
             direction = EVAS_BIDI_PARAGRAPH_NEUTRAL;
             if (!fribidi_get_par_embedding_levels(char_types + pos,
                      *itr - pos,
                      &direction,
                      embedding_levels + pos))
               {
                  len = -2;
                  goto cleanup;
               }

             /* Only on the first run */
             if (itr == segment_idxs)
               {
                  bidi_props->direction = direction;
                  /* adjust base_level to be 1 for rtl paragraphs, and 0 for
                   * ltr paragraphs. */
                  base_level =
                     EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(bidi_props) ? 1 : 0;
               }

             /* We want those chars at the override points to be on the base
              * level and we also remove -2 cause we later increment them,
              * just for simpler code paths */
             embedding_levels[*itr] = base_level - 2;
             pos = *itr + 1;
          }

        direction = EVAS_BIDI_PARAGRAPH_NEUTRAL;
        if (!fribidi_get_par_embedding_levels(char_types + pos,
                 len - pos,
                 &direction,
                 embedding_levels + pos))
          {
             len = -2;
             goto cleanup;
          }

        /* Increment all levels by 2 to emulate embedding. */
          {
             EvasBiDiLevel *bitr = embedding_levels, *end;
             end = bitr + len;
             for ( ; bitr < end ; bitr++)
               {
                  *bitr += 2;
               }
          }
     }
   else
     {
        if (!fribidi_get_par_embedding_levels(char_types, len,
                 &bidi_props->direction, embedding_levels))
          {
             len = -2;
             goto cleanup;
          }
     }


   /* clean up */
   if (bidi_props->embedding_levels)
     {
        free(bidi_props->embedding_levels);
     }
   bidi_props->embedding_levels = embedding_levels;

   /* clean up */

   if (bidi_props->char_types)
     {
        free(bidi_props->char_types);
     }
   bidi_props->char_types = char_types;

   if (base_ustr) free(base_ustr);


   return bidi_props;

/* Cleanup */
cleanup:
   if (char_types) free(char_types);
   if (embedding_levels) free(embedding_levels);
   if (base_ustr) free(base_ustr);
   if (bidi_props) evas_bidi_paragraph_props_unref(bidi_props); /* Clean up the bidi props */
   return NULL;
}
Ejemplo n.º 7
0
bool GlyphString::analyze(bool resolveScripts, bool breakOnLevelChange)
{
    if (mState != Initialized)
        return false;

    fribidi_get_bidi_types(mCodePoints, mSize, mTypes);
    fribidi_get_par_embedding_levels(mTypes, mSize, &mParType, mLevels);

    hb_unicode_funcs_t *ufuncs = hb_unicode_funcs_get_default();
    for (int i = 0; i < mSize; ++i)
        mScripts[i] = hb_unicode_script(ufuncs, mCodePoints[i]);

    if (resolveScripts) {
        hb_script_t lastScriptValue;
        int lastScriptIndex = -1;
        int lastSetIndex = -1;

        for (int i = 0; i < mSize; ++i) {
            if (mScripts[i] == HB_SCRIPT_COMMON || mScripts[i] == HB_SCRIPT_INHERITED) {
                if (lastScriptIndex != -1) {
                    mScripts[i] = lastScriptValue;
                    lastSetIndex = i;
                }
            } else {
                for (int j = lastSetIndex + 1; j < i; ++j)
                    mScripts[j] = mScripts[i];
                lastScriptValue = mScripts[i];
                lastScriptIndex = i;
                lastSetIndex = i;
            }
        }
    }

    int runID = 0;
    hb_script_t lastScript = mScripts[0];
    int lastLevel = mLevels[0];
    int lastRunStart = 0;
    for (int i = 0; i <= mSize; ++i) {
        if (i == mSize || mScripts[i] != lastScript ||
                (breakOnLevelChange && mLevels[i] != lastLevel))
        {
            ++runID;
            RunInfo run;
            run.startOffset = lastRunStart;
            run.endOffset = i;
            run.script = lastScript;
            run.direction = lastLevel & 1 ? HB_DIRECTION_RTL : HB_DIRECTION_LTR;
            mRunInfos.push_back(run);
            if (i < mSize) {
                lastScript = mScripts[i];
                lastLevel = mLevels[i];
                lastRunStart = i;
            } else {
                break;
            }
        }
        mRuns[i] = runID;
    }

    mState = Analyzed;
    return true;
}
Ejemplo n.º 8
0
static bool
_raqm_itemize (raqm_t *rq)
{
  FriBidiParType par_type = FRIBIDI_PAR_ON;
#ifdef _MSC_VER
  FriBidiCharType *types = _alloca (rq->text_len);
  FriBidiLevel *levels = _alloca (rq->text_len);
#else
  FriBidiCharType types[rq->text_len];
  FriBidiLevel levels[rq->text_len];
#endif
  FriBidiRun *runs = NULL;
  raqm_run_t *last;
  int max_level;
  int 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

  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 = 0;
    memset (types, FRIBIDI_TYPE_LTR, rq->text_len);
    memset (levels, 0, rq->text_len);
  }
  else
  {
    fribidi_get_bidi_types (rq->text, rq->text_len, types);
    max_level = fribidi_get_par_embedding_levels (types, rq->text_len,
                                                  &par_type, levels);
  }

  if (max_level < 0)
    return false;

  if (!_raqm_resolve_scripts (rq))
    return false;

  /* Get the number of bidi runs */
  run_count = fribidi_reorder_runs (types, rq->text_len, par_type,
                                    levels, NULL);

  /* Populate bidi runs array */
  runs = malloc (sizeof (FriBidiRun) * (size_t)run_count);
  if (!runs)
    return false;

  run_count = fribidi_reorder_runs (types, rq->text_len, par_type,
                                    levels, runs);

#ifdef RAQM_TESTING
  RAQM_TEST ("Number of runs before script itemization: %d\n\n", run_count);

  RAQM_TEST ("Fribidi Runs:\n");
  for (int i = 0; i < run_count; i++)
  {
    RAQM_TEST ("run[%d]:\t start: %d\tlength: %d\tlevel: %d\n",
               i, runs[i].pos, runs[i].len, runs[i].level);
  }
  RAQM_TEST ("\n");
#endif

  last = NULL;
  for (int i = 0; i < run_count; i++)
  {
    raqm_run_t *run = calloc (1, sizeof (raqm_run_t));
    if (!run)
      return false;

    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->scripts[run->pos];
      for (int j = runs[i].len - 1; j >= 0; j--)
      {
        hb_script_t script = rq->scripts[runs[i].pos + j];
        if (script != run->script)
        {
          raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t));
          if (!newrun)
            return false;
          newrun->pos = runs[i].pos + j;
          newrun->len = 1;
          newrun->direction = _raqm_hb_dir (rq, runs[i].level);
          newrun->script = script;
          run->next = newrun;
          run = newrun;
        }
        else
        {
          run->len++;
          run->pos = runs[i].pos + j;
        }
      }
    }
    else
    {
      run->pos = runs[i].pos;
      run->script = rq->scripts[run->pos];
      for (int j = 0; j < runs[i].len; j++)
      {
        hb_script_t script = rq->scripts[runs[i].pos + j];
        if (script != run->script)
        {
          raqm_run_t *newrun = calloc (1, sizeof (raqm_run_t));
          if (!newrun)
            return false;
          newrun->pos = runs[i].pos + j;
          newrun->len = 1;
          newrun->direction = _raqm_hb_dir (rq, runs[i].level);
          newrun->script = script;
          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: %d\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[%d]:\t start: %d\tlength: %d\tdirection: %s\tscript: %s\n",
               run_count++, run->pos, run->len,
               hb_direction_to_string (run->direction), buff);
  }
  RAQM_TEST ("\n");
#endif

  free (runs);

  return ok;
}