/*
 * << Haru Free PDF Library >> -- hpdf_font.c
 *
 * URL: http://libharu.org
 *
 * Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>
 * Copyright (c) 2007-2009 Antony Dovgal <tony@daylessday.org>
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 * It is provided "as is" without express or implied warranty.
 *
 */

#include "hpdf_conf.h"
#include "hpdf_utils.h"
#include "hpdf.h"

#ifdef LIBHPDF_ENABLE_BIDI
# include "fribidi.h"
#endif


static const HPDF_UINT8 MINBYTES[] = {
    1,
    1,
    2,
    4,
    2,
    4,
};

static HPDF_UTFBytes_Func BYTES_FNS[] = {
    NULL,
    HPDF_UTF8Bytes,
    HPDF_UTF16BEBytes,
    HPDF_UTF32BEBytes,
    HPDF_UTF16LEBytes,
    HPDF_UTF32LEBytes,
};

static HPDF_UTFToUcs4_Func TOUCS4_FNS[] = {
    NULL,
    HPDF_UTF8ToUcs4,
    HPDF_UTF16BEToUcs4,
    HPDF_UTF32BEToUcs4,
    HPDF_UTF16LEToUcs4,
    HPDF_UTF32LEToUcs4,
};

static HPDF_Ucs4ToUTF_Func UCS4TO_FNS[] = {
    NULL,
    HPDF_Ucs4ToUTF8,
    HPDF_Ucs4ToUTF16BE,
    HPDF_Ucs4ToUTF32BE,
    HPDF_Ucs4ToUTF16LE,
    HPDF_Ucs4ToUTF32LE,
};


static HPDF_BOOL
IsCIDText  (HPDF_Font font,
            HPDF_UINT index);


static HPDF_UINT
MeasureTextCache  (HPDF_Font           font,
                   HPDF_REAL           line_width,
                   HPDF_REAL           font_size,
                   HPDF_REAL           char_space,
                   HPDF_REAL           word_space,
                   HPDF_INT            options,
                   HPDF_UINT           cache_begin,
                   HPDF_TextLineWidth *width);


static HPDF_BOOL
CanBreakAfter  (HPDF_UCS4 b);


static HPDF_BOOL
CanBreakBefore  (HPDF_UCS4 b);


static HPDF_UINT
GetUncovertedBytes  (HPDF_Font           font,
                     const char         *text,
                     HPDF_UINT           cache_begin,
                     HPDF_TextLineWidth *width);


static HPDF_CharEnc
GetSourceCharEnc  (HPDF_Font font);


static void
SetReliefFontIndex  (HPDF_Font font);


HPDF_EXPORT(HPDF_TextWidth)
HPDF_Font_TextWidth  (HPDF_Font        font,
                      const HPDF_BYTE *text,
                      HPDF_UINT        len)
{
    HPDF_TextWidth tw = {0, 0, 0, 0};
    HPDF_FontAttr attr;

    HPDF_PTRACE ((" HPDF_Font_TextWidth\n"));

    if (!HPDF_Font_Validate(font))
        return tw;

    if (len > HPDF_LIMIT_MAX_STRING_LEN) {
        HPDF_RaiseError (font->error, HPDF_STRING_OUT_OF_RANGE, 0);
        return tw;
    }

    attr = (HPDF_FontAttr)font->attr;

    if (!attr->char_width_fn) {
        HPDF_SetError (font->error, HPDF_INVALID_OBJECT, 0);
        return tw;
    }

    if (text) {
        HPDF_Font_CheckBiDi (font, HPDF_FALSE);
        if (HPDF_Font_ConvertText (font, 0, (const char *)text, len) != HPDF_OK)
            return tw;
    } else if (attr->tw_cache.width) {
        return attr->tw_cache;
    }

    attr->tw_cache = HPDF_Font_TextCacheWidth (font, HPDF_FALSE,
            0, attr->text_cache_len);

    return attr->tw_cache;
}


HPDF_TextWidth
HPDF_Font_TextCacheWidth  (HPDF_Font font,
                           HPDF_BOOL ignore_flags,
                           HPDF_UINT cache_begin,
                           HPDF_UINT cache_end)
{
    HPDF_TextWidth tw = {0, 0, 0, 0};
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    const HPDF_BYTE *text = attr->text_cache;
    HPDF_BYTE *pirf = attr->text_cache + (attr->text_cache_allocated / 2);
    HPDF_BYTE irf;
    HPDF_UCS4 b = 0;
    HPDF_UINT i, bytes;

    HPDF_PTRACE ((" HPDF_Font_TextCacheWidth\n"));

    for (i = cache_begin; i < cache_end; i += bytes) {
        HPDF_UINT cw;

        irf = pirf[i] & HPDF_RELIEF_FONT_INDEX_MASK;
        cw = attr->char_width_fn (font, HPDF_TRUE, irf, text+i, &bytes, &b);

        if (!ignore_flags && (pirf[i] & HPDF_INTERLINEAR_ANNOTATION))
            continue;

        tw.width += cw;
        tw.numchars++;

        if (b == 0x20 && bytes == 1 && !IsCIDText (font, irf)) {
            tw.numwords++;
            tw.numspace++;
        }
    }

    /* 2006.08.19 add. */
    if (b == 0x20 && bytes == 1 && !IsCIDText (font, irf))
        ; /* do nothing. */
    else
        tw.numwords++;

    return tw;
}


HPDF_EXPORT(HPDF_UINT)
HPDF_Font_MeasureText (HPDF_Font          font,
                       const HPDF_BYTE   *text,
                       HPDF_UINT          len,
                       HPDF_REAL          width,
                       HPDF_REAL          font_size,
                       HPDF_REAL          char_space,
                       HPDF_REAL          word_space,
                       HPDF_INT           options,
                       HPDF_REAL         *real_width)
{
    HPDF_TextLineWidth tlw;
    HPDF_UINT lines;

    HPDF_PTRACE ((" HPDF_Font_MeasureText\n"));

    lines = HPDF_Font_MeasureTextLines (font, (const char *)text, len, width,
            font_size, char_space, word_space, options, &tlw, 1);
    if (!lines)
        return 0;

    if (real_width)
        *real_width = tlw.width;
    return tlw.linebytes;
}


HPDF_EXPORT(HPDF_UINT)
HPDF_Font_MeasureTextLines  (HPDF_Font           font,
                             const char         *text,
                             HPDF_UINT           len,
                             HPDF_REAL           line_width,
                             HPDF_REAL           font_size,
                             HPDF_REAL           char_space,
                             HPDF_REAL           word_space,
                             HPDF_INT            options,
                             HPDF_TextLineWidth *width,
                             HPDF_UINT           max_lines)
{
    HPDF_FontAttr attr;
    HPDF_UINT line, begin, cvt_len;
    HPDF_REAL margin;

    HPDF_PTRACE ((" HPDF_Font_MeasureTextLines\n"));

    if (!HPDF_Font_Validate(font) || !text || !width)
        return 0;

    if (len > HPDF_LIMIT_MAX_STRING_LEN) {
        HPDF_RaiseError (font->error, HPDF_STRING_OUT_OF_RANGE, 0);
        return 0;
    }

    attr = (HPDF_FontAttr)font->attr;

    if (!attr->char_width_fn) {
        HPDF_RaiseError (font->error, HPDF_INVALID_OBJECT, 0);
        return 0;
    }

    margin = 1.25F;             /* margin 25% */
    do { /* estimate result length and convert only the length+margin */
        HPDF_UINT cache_begin, cache_end;
        HPDF_REAL w = 0;

        if (!attr->width_per_byte) /* expect average width is missing_width/2 */
            attr->width_per_byte = (HPDF_REAL)attr->fontdef->missing_width / 2 /
                    MINBYTES[GetSourceCharEnc (font)];

        cvt_len = (HPDF_UINT)(line_width * max_lines / font_size * 1000 *
                margin / attr->width_per_byte);
        if (len < cvt_len)
            cvt_len = len;

        if (HPDF_Font_ConvertText (font, HPDF_CONVERT_HOLD_CHARACTERS,
                text, cvt_len) != HPDF_OK)
            return 0;

        for (begin = 0, cache_begin = 0, line = 0;
                line < max_lines &&
                begin < cvt_len &&
                !(line && (width[line - 1].flags & HPDF_TLW_PAGE_BREAK));
                line++) {
            MeasureTextCache (font, line_width, font_size, char_space,
                    word_space, options, cache_begin, width + line);
            w += width[line].width;
            if (line_width < width[line].width)
                width[line].flags |= HPDF_TLW_SHORTEN;

            cache_end = cache_begin + width[line].linebytes;
            GetUncovertedBytes (font, text + begin, cache_begin, width + line);

            begin += width[line].linebytes;
            cache_begin = cache_end;
        }

        if (8 < begin)          /* ignore too short sample */
            attr->width_per_byte = w / font_size * 1000 / begin;
        margin += 0.25F;        /* margin +25% */
    } while (cvt_len <= begin + 4 && cvt_len < len); /* while misestimate */

    return line;
}


static HPDF_UINT
MeasureTextCache  (HPDF_Font           font,
                   HPDF_REAL           line_width,
                   HPDF_REAL           font_size,
                   HPDF_REAL           char_space,
                   HPDF_REAL           word_space,
                   HPDF_INT            options,
                   HPDF_UINT           cache_begin,
                   HPDF_TextLineWidth *width)
{
    HPDF_FontAttr attr;
    HPDF_REAL w;
    HPDF_REAL v;
    HPDF_UINT nch, nsp, ntt;
    HPDF_UINT len, tmp_len, i;
    HPDF_BOOL can_break = HPDF_FALSE;
    HPDF_BYTE *text;
    HPDF_BYTE *pirf;
    HPDF_REAL hyphen_w = 0;
    HPDF_UINT bytes;
    HPDF_UINT tw;

    HPDF_PTRACE ((" MeasureTextEx\n"));

    attr = (HPDF_FontAttr)font->attr;

    text = attr->text_cache + cache_begin;
    pirf = attr->text_cache + cache_begin + (attr->text_cache_allocated / 2);
    len  = attr->text_cache_len - cache_begin;
    HPDF_MemSet (width, 0, sizeof *width);
    tmp_len = 0;
    nch = nsp = ntt = 0;
    tw = 0;
    w = v = 0;

    for (i = 0; i < len; i += bytes) {
        HPDF_REAL tmp_w;
        HPDF_UCS4 b;
        HPDF_BYTE irf;
        HPDF_INT cw;

        irf = pirf[i] & HPDF_RELIEF_FONT_INDEX_MASK;
        cw = attr->char_width_fn (font, HPDF_TRUE, irf, text+i, &bytes, &b);

        if (pirf[i] & HPDF_INTERLINEAR_ANNOTATION)
            continue;

        if (HPDF_IS_WHITE_SPACE(b) || b == 0x200B /* ZWSP */) {
            width->linebytes = i + bytes;
            width->numbytes = i;
            width->numchars = nch;
            width->numspaces = nsp;
            width->numtatweels = ntt;
            width->charswidth = tw;
            width->width = w;

            if (b == 0x0A || b == 0x0D)
                return (width->flags |= HPDF_TLW_PRAGRAPH_BREAK);
            if (b == 0x0C)
                return (width->flags |= HPDF_TLW_PAGE_BREAK);
            if (line_width < w)
                return (width->flags |= HPDF_TLW_WORD_WRAP);

            if (b != 0x20) {    /* TAB, ZWSP */
                continue;
            } else if (bytes == 1 && !IsCIDText (font, irf)) {
                nsp++;
                w += word_space;
            }
        } else if (b == 0xAD) { /* SHY */
            tmp_w = 0;
            if (options & HPDF_MEASURE_WORD_WRAP) {
                if (!hyphen_w) {
                    HPDF_BYTE hyphen[8] = {'-'};
                    if (attr->encoder->charenc != HPDF_CHARENC_UNSUPPORTED)
                        UCS4TO_FNS[attr->encoder->charenc] (hyphen, '-');
                    cw = attr->char_width_fn (font, HPDF_TRUE, 0, hyphen, 0, 0);
                    hyphen_w = (HPDF_REAL)cw * font_size / 1000;
                    hyphen_w += char_space;
                }
                tmp_w = hyphen_w;
                if (line_width < v + tmp_w ||
                    (!(options & HPDF_MEASURE_CAN_SHORTEN) &&
                     line_width < w + tmp_w))
                    return (width->flags |= HPDF_TLW_WORD_WRAP);
            }

            width->linebytes = i + bytes;
            width->numbytes = i + bytes;
            width->numchars = nch;
            width->numspaces = nsp;
            width->numtatweels = ntt;
            width->charswidth = tw + cw;
            width->width = w + tmp_w;

            if (line_width < w + tmp_w)
                return (width->flags |=
                        (HPDF_TLW_WORD_WRAP | HPDF_TLW_HYPHENATION));

            continue;
        } else if ((b <= 0x001F) ||
                   (0x007F <= b && b <= 0x009F) ||
                   (0x200C <= b && b <= 0x200F) ||
                   (0x202A <= b && b <= 0x202E) ||
                   (0xFE00 <= b && b <= 0xFE0F) ||
                   (0xFEFF == b)                ||
                   (0xFFF0 <= b && b <= 0xFFFF)) { /* control codes */
            continue;
        } else if (!(options & HPDF_MEASURE_WORD_WRAP) ||
                   (!(pirf[i] & HPDF_INTERLINEAR_ANNOTATED) &&
                    can_break && CanBreakBefore (b))) {
            width->linebytes = i;
            width->numbytes = i;
            width->numchars = nch;
            width->numspaces = nsp;
            width->numtatweels = ntt;
            width->charswidth = tw;
            width->width = w;

            if (line_width < w)
                return width->flags;
        }

        if ((options & HPDF_MEASURE_WORD_WRAP) &&
                (attr->type == HPDF_FONT_TYPE0_CID ||
                 attr->type == HPDF_FONT_TYPE0_TT))
            can_break = CanBreakAfter (b);

        tmp_w = (HPDF_REAL)cw * font_size / 1000;

        if (b == 0x0640) {      /* Arabic tatweel */
            ntt++;
            if (options & HPDF_MEASURE_IGNORE_TATWEEL) {
                width->flags |= HPDF_TLW_IGNORE_TATWEEL;
            } else {
                nch++;
                tw += cw;
                w += tmp_w;
                if (0 < i)
                    w += char_space;
            }
        } else {
            nch++;
            tw += cw;
            w += tmp_w;
            v += tmp_w;
            if (0 < i)
                w += char_space;
        }

        if (line_width < v ||
                (!(options & HPDF_MEASURE_CAN_SHORTEN) && line_width < w))
            return (width->flags |= HPDF_TLW_WORD_WRAP);
    }

    /* all of text can be put in the specified width */
    width->linebytes = i;
    width->numbytes = i;
    width->numchars = nch;
    width->numspaces = nsp;
    width->numtatweels = ntt;
    width->charswidth = tw;
    width->width = w;

    return width->flags;
}


static HPDF_BOOL
CanBreakAfter  (HPDF_UCS4 b)
{
  return ((0x03000 <= b && b <= 0x03007) ||
          (0x03008 <= b && b <= 0x0301B && (b & 1)) ||
          (0x0301C == b) ||
          (0x0301E <= b && b <= 0x09FFF) ||
          (0x0AC00 <= b && b <= 0x0D7AF) ||
          (0x0F900 <= b && b <= 0x0FAFF) ||
          (0x20000 <= b && b <= 0x2FFFF));
}


static HPDF_BOOL
CanBreakBefore  (HPDF_UCS4 b)
{
    if      (0x030A1 <= b && b <= 0x030F6)
        b -= 0x030A1 - 0x03041;
    else if (0x0FF01 <= b && b <= 0x0FF5E)
        b -= 0x0FF01 - 0x00021;

    return !(('!' == b) ||
             (')' == b) ||
             (',' == b) ||
             ('-' == b) ||
             ('.' == b) ||
             (':' == b) ||
             (';' == b) ||
             ('=' == b) ||
             ('?' == b) ||
             (']' == b) ||
             ('}' == b) ||
             (0x03001 <= b && b <= 0x03005) ||
             (0x03008 <= b && b <= 0x0301B && (b & 1)) ||
             (0x0301F == b) ||
             (0x03031 <= b && b <= 0x03035) ||
             (0x03041 <= b && b <= 0x0304A && (b & 1)) ||
             (0x03063 == b) ||
             (0x03083 == b) ||
             (0x03085 == b) ||
             (0x03087 == b) ||
             (0x0308E == b) ||
             (0x03095 == b) ||
             (0x03096 == b) ||
             (0x03099 <= b && b <= 0x0309E) ||
             (0x030FB <= b && b <= 0x030FE) ||
             (0x0FF60 == b) ||
             (0x0FF61 == b) ||
             (0x0FF63 <= b && b <= 0x0FF65) ||
             (0x0FF66 <= b && b <= 0x0FF70) ||
             (0x0FF9E == b) ||
             (0x0FF9F == b));
}


HPDF_EXPORT(const char*)
HPDF_Font_GetFontName  (HPDF_Font font)
{
    HPDF_FontAttr attr;

    HPDF_PTRACE((" HPDF_Font_GetFontName\n"));

    if (!HPDF_Font_Validate(font))
        return NULL;

    attr = (HPDF_FontAttr)font->attr;

    return attr->fontdef->base_font;
}


HPDF_EXPORT(const char*)
HPDF_Font_GetEncodingName  (HPDF_Font font)
{
    HPDF_FontAttr attr;

    HPDF_PTRACE((" HPDF_Font_GetEncodingName\n"));

    if (!HPDF_Font_Validate(font))
        return NULL;

    attr = (HPDF_FontAttr)font->attr;

    return attr->encoder->name;
}


HPDF_EXPORT(HPDF_INT)
HPDF_Font_GetUnicodeWidth  (HPDF_Font       font,
                            HPDF_UNICODE    code)
{
    HPDF_PTRACE((" HPDF_Font_GetUnicodeWidth\n"));

    return HPDF_Font_GetUcs4Width (font, code);
}


HPDF_EXPORT(HPDF_INT)
HPDF_Font_GetUcs4Width  (HPDF_Font       font,
                         HPDF_UCS4       ucs4)
{
    HPDF_FontAttr attr;
    HPDF_FontDef fontdef;

    HPDF_PTRACE((" HPDF_Font_GetUcs4Width\n"));

    if (!HPDF_Font_Validate(font))
        return 0;

    if (!(font = HPDF_Font_GetReliefFont (font, ucs4, NULL)))
        return 0;

    attr = (HPDF_FontAttr)font->attr;
    fontdef = attr->fontdef;

    if (fontdef->type == HPDF_FONTDEF_TYPE_TYPE1) {
        return HPDF_Type1FontDef_GetWidth (fontdef, ucs4);
    } else if (fontdef->type == HPDF_FONTDEF_TYPE_TRUETYPE) {
        return HPDF_TTFontDef_GetCharWidth (fontdef, ucs4);
    } else if (fontdef->type == HPDF_FONTDEF_TYPE_CID) {
        HPDF_CID cid = HPDF_CMapEncoder_ToCID(attr->encoder, ucs4);
        if (0 < cid)
            return HPDF_CIDFontDef_GetCIDWidth (fontdef, cid);
    }

    HPDF_PTRACE((" HPDF_Font_GetUcs4Width not found (0x%08X)\n", ucs4));

    return 0;
}


HPDF_EXPORT(HPDF_INT)
HPDF_Font_GetCharWidth  (HPDF_Font   font,
                         const char *text,
                         HPDF_UINT  *bytes,
                         HPDF_UCS4  *ucs4)
{
    HPDF_FontAttr attr;

    HPDF_PTRACE((" HPDF_Font_GetCharWidth\n"));

    if (!HPDF_Font_Validate(font) || !text)
        return 0;

    attr = (HPDF_FontAttr)font->attr;

    return attr->char_width_fn (font, HPDF_FALSE, 0, text, bytes, ucs4);
}


HPDF_EXPORT(HPDF_Box)
HPDF_Font_GetBBox  (HPDF_Font  font)
{
    HPDF_Box bbox = {0, 0, 0, 0};

    HPDF_PTRACE((" HPDF_Font_GetBBox\n"));
    if (HPDF_Font_Validate(font))
        return ((HPDF_FontAttr)font->attr)->fontdef->font_bbox;

    return bbox;
}

HPDF_EXPORT(HPDF_INT)
HPDF_Font_GetAscent  (HPDF_Font  font)
{
    HPDF_PTRACE((" HPDF_Font_GetAscent\n"));

    if (HPDF_Font_Validate(font))
        return ((HPDF_FontAttr)font->attr)->fontdef->ascent;

    return 0;
}

HPDF_EXPORT(HPDF_INT)
HPDF_Font_GetDescent  (HPDF_Font  font)
{
    HPDF_PTRACE((" HPDF_Font_GetDescent\n"));

    if (HPDF_Font_Validate(font))
        return ((HPDF_FontAttr)font->attr)->fontdef->descent;

    return 0;
}

HPDF_EXPORT(HPDF_UINT)
HPDF_Font_GetXHeight  (HPDF_Font  font)
{
    HPDF_PTRACE((" HPDF_Font_GetXHeight\n"));

    if (HPDF_Font_Validate(font))
        return ((HPDF_FontAttr)font->attr)->fontdef->x_height;

    return 0;
}

HPDF_EXPORT(HPDF_UINT)
HPDF_Font_GetCapHeight  (HPDF_Font  font)
{
    HPDF_PTRACE((" HPDF_Font_GetCapHeight\n"));

    if (HPDF_Font_Validate(font))
        return ((HPDF_FontAttr)font->attr)->fontdef->cap_height;

    return 0;
}


HPDF_BOOL
HPDF_Font_Validate  (HPDF_Font font)
{
    HPDF_PTRACE((" HPDF_Font_Validate\n"));

    if (!font || !font->attr || font->header.obj_class !=
            (HPDF_OSUBCLASS_FONT | HPDF_OCLASS_DICT))
        return HPDF_FALSE;

    return HPDF_TRUE;
}


static HPDF_CharEnc
GetSourceCharEnc  (HPDF_Font font)
{
    HPDF_FontAttr attr;
    HPDF_List list;
    HPDF_Converter converter;
    HPDF_CharEnc charenc;

    HPDF_PTRACE ((" GetSourceCharEnc\n"));

    attr = (HPDF_FontAttr)font->attr;
    list = attr->converters_list[attr->converters_index];

    if (list && list->count) {
        converter = (HPDF_Converter)HPDF_List_ItemAt (list, list->count - 1);
        charenc = converter->src_charenc;
    } else {
        if (!attr->text_cache)
            HPDF_NormalizeCharEnc (&attr->encoder->charenc);
        charenc = attr->encoder->charenc;
    }

    return charenc;
}


HPDF_UINT
HPDF_Font_StrLen  (HPDF_Font        font,
                   const char      *s,
                   HPDF_INT         maxlen)
{
    HPDF_INT i, min, len;

    HPDF_PTRACE ((" HPDF_Font_StrLen\n"));

    if (!s)
        return 0;

    min = MINBYTES[GetSourceCharEnc (font)];
    len = 0;
    while (maxlen < 0 || len < maxlen) {
        for (i = 0; i < min && s[i] == 0; i++)
            ;
        if (i == min)
            break;
        s   += min;
        len += min;
    }

    return (HPDF_UINT)len;
}


#ifdef LIBHPDF_ENABLE_BIDI
typedef struct _HPDF_ConverterBiDi_Rec *HPDF_ConverterBiDi;

typedef struct _HPDF_ConverterBiDi_Rec {
    HPDF_Converter_Rec            hdr;
    HPDF_ConverterBiDi_Param_Rec *par;
    HPDF_Alloc_Func               alloc_fn;
    HPDF_Free_Func                free_fn;
    HPDF_UINT                     dst_tatweels;
    HPDF_UINT                     src_tatweels;
} HPDF_ConverterBiDi_Rec;


static void
BiDi_FreeArrays  (HPDF_ConverterBiDi bidi)
{
    if (bidi->par->bidi_types)
        bidi->free_fn (bidi->par->bidi_types);
    if (bidi->par->ar_props)
        bidi->free_fn (bidi->par->ar_props);
    if (bidi->par->embedding_levels)
        bidi->free_fn (bidi->par->embedding_levels);
    if (bidi->par->positions_L_to_V)
        bidi->free_fn (bidi->par->positions_L_to_V);
    if (bidi->par->positions_V_to_L)
        bidi->free_fn (bidi->par->positions_V_to_L);

    bidi->par->bidi_types       = NULL;
    bidi->par->ar_props         = NULL;
    bidi->par->embedding_levels = NULL;
    bidi->par->positions_L_to_V = NULL;
    bidi->par->positions_V_to_L = NULL;

    bidi->par->max_chars = 0;
}


static HPDF_UINT HPDF_STDCALL
BiDi  (HPDF_Converter   converter,
       HPDF_UINT32      flags,
       const HPDF_BYTE *src,
       HPDF_UINT        src_bytes,
       HPDF_BYTE       *dst)
{
    HPDF_ConverterBiDi bidi = (HPDF_ConverterBiDi)converter;
    const HPDF_UCS4 *s = (const HPDF_UCS4 *)src;
    HPDF_UINT len = src_bytes / sizeof (HPDF_UCS4);
    HPDF_UCS4 *d = (HPDF_UCS4 *)dst;

    if (bidi->par->max_chars < len) {
        HPDF_UINT32 max_chars = bidi->par->max_chars;
        if (!max_chars)
            max_chars = HPDF_TEXT_DEFAULT_LEN / sizeof (HPDF_UCS4);
        while (max_chars < len)
            max_chars *= 2;

        BiDi_FreeArrays (bidi);

        bidi->par->bidi_types       = bidi->alloc_fn
                (max_chars * sizeof bidi->par->bidi_types[0]);
        bidi->par->ar_props         = bidi->alloc_fn
                (max_chars * sizeof bidi->par->ar_props[0]);
        bidi->par->embedding_levels = bidi->alloc_fn
                (max_chars * sizeof bidi->par->embedding_levels[0]);
        bidi->par->positions_L_to_V = bidi->alloc_fn
                (max_chars * sizeof bidi->par->positions_L_to_V[0]);
        bidi->par->positions_V_to_L = bidi->alloc_fn
                (max_chars * sizeof bidi->par->positions_V_to_L[0]);

        if (!bidi->par->bidi_types       ||
            !bidi->par->ar_props         ||
            !bidi->par->embedding_levels ||
            !bidi->par->positions_L_to_V ||
            !bidi->par->positions_V_to_L)
            return 0;

        bidi->par->max_chars = max_chars;
    }

    fribidi_get_bidi_types (s, len, (FriBidiCharType *)bidi->par->bidi_types);
    fribidi_get_par_embedding_levels ((FriBidiCharType *)bidi->par->bidi_types,
            len, (FriBidiParType *)&bidi->par->base_dir,
            bidi->par->embedding_levels);

    HPDF_MemCpy (dst, src, src_bytes);

    /* Arabic joining */
    fribidi_get_joining_types (s, len, bidi->par->ar_props);
    fribidi_join_arabic ((FriBidiCharType *)bidi->par->bidi_types, len,
            bidi->par->embedding_levels, bidi->par->ar_props);
    fribidi_shape ((FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC),
            bidi->par->embedding_levels, len, bidi->par->ar_props, d);

    if (!(flags & HPDF_CONVERT_HOLD_CHARACTERS)) {
        HPDF_UINT i;

        for (i = 0; i < len; i++)
            bidi->par->positions_V_to_L[i] = i;

        fribidi_reorder_line ((FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC),
                (FriBidiCharType *)bidi->par->bidi_types, len, 0,
                (FriBidiParType)bidi->par->base_dir,
                bidi->par->embedding_levels, d,
                bidi->par->positions_V_to_L);

        if (bidi->dst_tatweels < bidi->src_tatweels) {
            s = d;
            for (i = 0; i < len; i++) {
                if (!bidi->src_tatweels || *s != 0x0640) {
                    *d++ = *s++;
                } else {
                    HPDF_UINT ntt = bidi->dst_tatweels / bidi->src_tatweels;
                    s++;
                    bidi->src_tatweels--;
                    while (ntt-- && bidi->dst_tatweels) {
                        *d++ = 0x0640;
                        bidi->dst_tatweels--;
                    }
                }
            }
            len = (HPDF_UINT)(d - (HPDF_UCS4 *)dst);
        } else if (bidi->src_tatweels < bidi->dst_tatweels) {
            s = d + len;
            len += bidi->dst_tatweels - bidi->src_tatweels;
            d += len;
            while ((HPDF_UCS4 *)dst < d) {
                if (!bidi->src_tatweels || s[-1] != 0x0640) {
                    *--d = *--s;
                } else {
                    HPDF_UINT ntt = bidi->dst_tatweels / bidi->src_tatweels;
                    --s;
                    bidi->src_tatweels--;
                    while (ntt-- && bidi->dst_tatweels &&
                            (HPDF_UCS4 *)dst < d) {
                        *--d = 0x0640;
                        bidi->dst_tatweels--;
                    }
                }
            }
        }
    }
    bidi->src_tatweels = bidi->dst_tatweels = 0;
    bidi->hdr.chars_factor = 1;

    return len * sizeof (HPDF_UCS4);
}


static void HPDF_STDCALL
BiDi_Delete  (HPDF_Converter   converter,
              HPDF_Free_Func   free_fn)
{
    HPDF_ConverterBiDi bidi = (HPDF_ConverterBiDi)converter;

    HPDF_PTRACE((" BiDi_Delete\n"));

    BiDi_FreeArrays (bidi);
    free_fn (bidi);
}


static HPDF_Converter HPDF_STDCALL
BiDi_New   (HPDF_Alloc_Func alloc_fn,
            HPDF_Free_Func  free_fn,
            void           *param)
{
    HPDF_ConverterBiDi_Param_Rec *par;
    HPDF_ConverterBiDi bidi;
    HPDF_UINT size;

    HPDF_PTRACE((" BiDi_New\n"));

    par = (HPDF_ConverterBiDi_Param_Rec *)param;
    bidi = 0;
    size = sizeof *bidi;
    if (!par)
        size += sizeof *par;
    bidi = (HPDF_ConverterBiDi)alloc_fn (size);
    if (!bidi)
        return NULL;

    HPDF_MemSet (bidi, 0, size);

    if (par) {
        HPDF_MemSet (par, 0, sizeof *par);
        bidi->par = par;
    } else {
        bidi->par = (HPDF_ConverterBiDi_Param_Rec *)(bidi + 1);
    }

    bidi->hdr.convert_fn   = BiDi;
    bidi->hdr.delete_fn    = BiDi_Delete;
    bidi->hdr.src_charenc  = HPDF_CHARENC_UCS4;
    bidi->hdr.dst_charenc  = HPDF_CHARENC_UCS4;
    bidi->hdr.bytes_factor = 1;
    bidi->hdr.chars_factor = 1;
    bidi->par->base_dir = 0x111; /* Letter strong RTL */
    bidi->alloc_fn = alloc_fn;
    bidi->free_fn  = free_fn;

    return &bidi->hdr;
}
#endif /* LIBHPDF_ENABLE_BIDI */


static HPDF_UINT HPDF_STDCALL
EncConv  (HPDF_Converter   converter,
          HPDF_UINT32      flags,
          const HPDF_BYTE *src,
          HPDF_UINT        src_bytes,
          HPDF_BYTE       *dst)
{
    HPDF_UINT dst_bytes, bytes, i;
    HPDF_UCS4 ucs4;
    HPDF_UTFBytes_Func  bytes_fn  =  BYTES_FNS[converter->src_charenc];
    HPDF_UTFToUcs4_Func toucs4_fn = TOUCS4_FNS[converter->src_charenc];
    HPDF_Ucs4ToUTF_Func ucs4to_fn = UCS4TO_FNS[converter->dst_charenc];

    HPDF_UNUSED (flags);

    dst_bytes = 0;
    i = 0;

    while (i < src_bytes) {
        bytes = bytes_fn (src + i);
        if (src_bytes < i + bytes)
            break;
        ucs4 = toucs4_fn (src + i, bytes);
        i += bytes;
        dst_bytes += ucs4to_fn (dst + dst_bytes, ucs4);
    }

    return dst_bytes;
}


static HPDF_UINT HPDF_STDCALL
Swap16  (HPDF_Converter   converter,
         HPDF_UINT32      flags,
         const HPDF_BYTE *src,
         HPDF_UINT        src_bytes,
         HPDF_BYTE       *dst)
{
    HPDF_INT i;

    HPDF_UNUSED (converter);
    HPDF_UNUSED (flags);

    for (i = 0; i < (HPDF_INT)src_bytes - 1; i += 2) {
        dst[i + 0] = src[i + 1];
        dst[i + 1] = src[i + 0];
    }

    return src_bytes;
}


static HPDF_UINT HPDF_STDCALL
Swap32  (HPDF_Converter   converter,
         HPDF_UINT32      flags,
         const HPDF_BYTE *src,
         HPDF_UINT        src_bytes,
         HPDF_BYTE       *dst)
{
    HPDF_INT i;

    HPDF_UNUSED (converter);
    HPDF_UNUSED (flags);

    for (i = 0; i < (HPDF_INT)src_bytes - 3; i += 4) {
        dst[i + 0] = src[i + 3];
        dst[i + 1] = src[i + 2];
        dst[i + 2] = src[i + 1];
        dst[i + 3] = src[i + 0];
    }

    return src_bytes;
}


static HPDF_Converter_Convert_Func ENCCONV_FNS[6][6] = {
    /* dst:    UTF8     UTF16BE  UTF32BE  UTF16LE  UTF32LE */
    { NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    }, /* src:    */
    { NULL,    NULL,    EncConv, EncConv, EncConv, EncConv, }, /* UTF8    */
    { NULL,    EncConv, NULL,    EncConv, Swap16,  EncConv, }, /* UTF16BE */
    { NULL,    EncConv, EncConv, NULL,    EncConv, Swap32,  }, /* UTF32BE */
    { NULL,    EncConv, Swap16,  EncConv, NULL,    EncConv, }, /* UTF16LE */
    { NULL,    EncConv, EncConv, Swap32,  EncConv, NULL,    }, /* UTF32LE */
};

static const HPDF_UINT8 ENCCONV_FCTS[6][6] = {
    /* dst:    UTF8     UTF16BE  UTF32BE  UTF16LE  UTF32LE */
    { 1,       1,       1,       1,       1,       1,       }, /* src:    */
    { 1,       1,       2,       4,       2,       4,       }, /* UTF8    */
    { 1,       2,       1,       2,       1,       2,       }, /* UTF16BE */
    { 1,       1,       1,       1,       1,       1,       }, /* UTF32BE */
    { 1,       2,       1,       2,       1,       2,       }, /* UTF16LE */
    { 1,       1,       1,       1,       1,       1,       }, /* UTF32LE */
};

static struct _HPDF_BuiltInConverter_Rec {
  char                    name[8];
  HPDF_Converter_New_Func new_fn;
} BUILTIN[] = {
#ifdef LIBHPDF_ENABLE_BIDI
    { "BiDi",    BiDi_New,    },
#endif
    { "",        NULL,        },
};

typedef struct _HPDF_ConverterEnc_Param_Rec {
    HPDF_CharEnc                src_charenc;
    HPDF_CharEnc                dst_charenc;
} HPDF_ConverterEnc_Param_Rec;


static void HPDF_STDCALL
EncConv_Delete  (HPDF_Converter   converter,
                 HPDF_Free_Func   free_fn)
{
    HPDF_PTRACE((" EncConv_Delete\n"));

    free_fn (converter);
}


static HPDF_Converter HPDF_STDCALL
EncConv_New   (HPDF_Alloc_Func alloc_fn,
               HPDF_Free_Func  free_fn,
               void           *param)
{
    HPDF_Converter converter;
    const HPDF_ConverterEnc_Param_Rec *par;

    HPDF_PTRACE((" EncConv_New\n"));

    if (!(converter = (HPDF_Converter)alloc_fn (sizeof (HPDF_Converter_Rec))))
        return NULL;

    par = (const HPDF_ConverterEnc_Param_Rec *)param;

    converter->convert_fn = ENCCONV_FNS[par->src_charenc][par->dst_charenc];
    converter->delete_fn = EncConv_Delete;
    converter->src_charenc = par->src_charenc;
    converter->dst_charenc = par->dst_charenc;
    converter->bytes_factor = ENCCONV_FCTS[par->src_charenc][par->dst_charenc];
    converter->chars_factor = 1;

    return converter;
}


HPDF_EXPORT(HPDF_STATUS)
HPDF_Font_SelectConverters  (HPDF_Font  font,
                             HPDF_INT   index)
{
    HPDF_FontAttr attr;

    HPDF_PTRACE((" HPDF_Font_SelectConverters\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

    if (index < 0)
        index = 0;
    else if (HPDF_MAX_CONVERTERS < index)
        index = HPDF_MAX_CONVERTERS;

    attr = (HPDF_FontAttr)font->attr;
    attr->converters_index = index;

    return HPDF_OK;
}


HPDF_EXPORT(HPDF_STATUS)
HPDF_Font_SetCharacterEncoding  (HPDF_Font     font,
                                 HPDF_CharEnc  charenc)
{
    HPDF_ConverterEnc_Param_Rec param;
    HPDF_STATUS ret;

    HPDF_PTRACE((" HPDF_Font_SetCharacterEncoding\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

    HPDF_NormalizeCharEnc (&charenc);
    param.src_charenc = charenc;
    param.dst_charenc = GetSourceCharEnc (font);

    if (param.src_charenc == HPDF_CHARENC_UNSUPPORTED ||
        param.dst_charenc == HPDF_CHARENC_UNSUPPORTED)
        return HPDF_RaiseError (font->error, HPDF_NOT_UTF_ENCODING, 0);

    if (param.src_charenc == param.dst_charenc)
        return HPDF_OK;

    ret = HPDF_Font_PushConverter (font, EncConv_New, &param);

    return ret;
}


HPDF_EXPORT(HPDF_STATUS)
HPDF_Font_PushConverter  (HPDF_Font                font,
                          HPDF_Converter_New_Func  new_fn,
                          void                    *param)
{
    HPDF_FontAttr attr;
    HPDF_List list;
    HPDF_Converter converter;
    HPDF_STATUS ret;

    HPDF_PTRACE((" HPDF_Font_PushConverter\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

    attr = (HPDF_FontAttr)font->attr;
    list = attr->converters_list[attr->converters_index];

    if (!list) {
        list = HPDF_List_New (font->mmgr, HPDF_DEF_ITEMS_PER_BLOCK);
        if (!list)
            return HPDF_CheckError (font->error);

        attr->converters_list[attr->converters_index] = list;
    }

    converter = new_fn (font->mmgr->alloc_fn, font->mmgr->free_fn, param);
    if (!converter)
        return HPDF_RaiseError (font->error, HPDF_FAILD_TO_NEW_CONVERTER, 0);

    HPDF_NormalizeCharEnc (&converter->src_charenc);
    HPDF_NormalizeCharEnc (&converter->dst_charenc);

    if ((ret = HPDF_Font_SetCharacterEncoding (font, converter->dst_charenc))
            != HPDF_OK) {
        converter->delete_fn (converter, font->mmgr->free_fn);
        return ret;
    }

    if ((ret = HPDF_List_Add (list, converter)) != HPDF_OK) {
        converter->delete_fn (converter, font->mmgr->free_fn);
        return HPDF_RaiseError (font->error, ret, 0);
    }

    return HPDF_OK;
}


HPDF_EXPORT(HPDF_STATUS)
HPDF_Font_PushBuiltInConverter  (HPDF_Font   font,
                                 const char *name,
                                 void       *param)
{
    HPDF_UINT i;

    HPDF_PTRACE((" HPDF_Font_PushBuiltInConverter\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

    for (i = 0; BUILTIN[i].name[0]; i++)
        if (HPDF_StrCmp (name, BUILTIN[i].name) == 0)
            break;

    if (!BUILTIN[i].name[0])
        return HPDF_RaiseError (font->error, HPDF_CONVERTER_NOT_FOUND, 0);

    return HPDF_Font_PushConverter (font, BUILTIN[i].new_fn, param);
}


void
HPDF_Font_FreeConvertersListAll (HPDF_Font font)
{
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    HPDF_INT i, j;

    HPDF_PTRACE((" HPDF_Font_FreeConvertersListAll\n"));

    if (!attr)
        return;

    if (attr->text_cache)
        HPDF_FreeMem (font->mmgr, attr->text_cache);
    attr->text_cache = NULL;
    attr->text_cache_len = 0;
    attr->text_cache_allocated = 0;
    HPDF_MemSet (&attr->tw_cache, 0, sizeof attr->tw_cache);

    for (i = 0; i <= HPDF_MAX_CONVERTERS; i++) {
        HPDF_List list = attr->converters_list[i];

        if (!list)
            continue;

        for (j = list->count - 1; 0 <= j; --j) {
            HPDF_Converter converter;
            converter = (HPDF_Converter)HPDF_List_ItemAt (list, j);
            converter->delete_fn (converter, font->mmgr->free_fn);
        }

        HPDF_List_Free (list);

        attr->converters_list[i] = NULL;
    }
}


HPDF_STATUS
HPDF_Font_ConvertText  (HPDF_Font        font,
                        HPDF_UINT32      flags,
                        const char      *text,
                        HPDF_UINT        len)
{
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    HPDF_List list;
    HPDF_Converter converter;
    HPDF_UINT bytes_factor, chars_factor, allocated, i;
    HPDF_BYTE *dst;

    HPDF_PTRACE((" HPDF_Font_ConvertText\n"));

    if (!text)
        return HPDF_OK;

    if (!len)
        len = HPDF_Font_StrLen(font, text, HPDF_LIMIT_MAX_STRING_LEN + 1);

    if (!attr->text_cache)
        HPDF_NormalizeCharEnc (&attr->encoder->charenc);

    attr->text_cache_len = 0;
    HPDF_MemSet (&attr->tw_cache, 0, sizeof attr->tw_cache);

    list = attr->converters_list[attr->converters_index];

    bytes_factor = 1;
    chars_factor = 1;
    if (list && list->count) {
        for (i = 0; i < (HPDF_INT)list->count; i++) {
            converter = (HPDF_Converter)HPDF_List_ItemAt (list, i);
            if (bytes_factor < converter->bytes_factor)
                bytes_factor = converter->bytes_factor;
            chars_factor *= converter->chars_factor;
        }
    }

    attr->text_cache_len = len;
    len *= bytes_factor;
    len *= chars_factor;
    len *= 2;

    allocated = attr->text_cache_allocated;
    if (!attr->text_cache_allocated)
        attr->text_cache_allocated = HPDF_TEXT_DEFAULT_LEN;
    while (attr->text_cache_allocated < len)
        attr->text_cache_allocated *= 2;
    if (attr->text_cache_allocated != allocated) {
        if (attr->text_cache)
            HPDF_FreeMem (font->mmgr, attr->text_cache);
        attr->text_cache = HPDF_GetMem (font->mmgr, attr->text_cache_allocated);
        if (!attr->text_cache)
            return HPDF_CheckError (font->error);
    }

    /* do convert */
    if (!list || list->count <= 0) {
        HPDF_MemCpy (attr->text_cache, text, attr->text_cache_len);
        text = (char *)(dst = attr->text_cache);
    } else {
        for (i = list->count; i--;) {
            dst = attr->text_cache;
            if (i & 1)
                dst += (attr->text_cache_allocated / 2);
            converter = (HPDF_Converter)HPDF_List_ItemAt (list, i);
            attr->text_cache_len = converter->convert_fn (converter, flags,
                    text, attr->text_cache_len, dst);
            text = (char *)dst;
        }
    }

    /* set relief font index */
    if (attr->relief_font)
        SetReliefFontIndex (font);

    /* remove control character */
    if (!(flags & HPDF_CONVERT_HOLD_CHARACTERS)) {
        HPDF_UINT bytes;
        HPDF_UCS4 b = 0;
        HPDF_UINT irfofs = attr->text_cache_allocated / 2;

        for (i = 0; i < attr->text_cache_len;) {
            b = HPDF_Encoder_GetUcs4 (attr->encoder, (const HPDF_BYTE *)text,
                    &bytes);
            if (!bytes)
                bytes = 1;
            i += bytes;
            if ((b <= 0x001F) ||
                (0x007F <= b && b <= 0x009F) ||
                (b == 0x00AD) ||
                (0x200B <= b && b <= 0x200F) ||
                (0x202A <= b && b <= 0x202E) ||
                (0xFE00 <= b && b <= 0xFE0F) ||
                (0xFEFF == b)                ||
                (0xFFF0 <= b && b <= 0xFFFF)) /* control codes */
                text += bytes;
            else
                while (bytes--) {
                    dst[irfofs] = ((const HPDF_BYTE *)text)[irfofs];
                    *dst++ = *(const HPDF_BYTE *)text++;
                }
        }
        if (b == 0x00AD) {
            HPDF_BYTE irf;
            HPDF_Font_GetReliefFont (font, '-', &irf);
            bytes = 1;
            if (attr->encoder->charenc == HPDF_CHARENC_UNSUPPORTED ||
                attr->encoder->charenc == HPDF_CHARENC_UTF8)
                *dst = '-';
            else
                bytes = UCS4TO_FNS[attr->encoder->charenc] (dst, '-');
            while (bytes--)
                (dst++)[irfofs] = irf;
        }
        attr->text_cache_len = (HPDF_UINT)(dst - attr->text_cache);
    }

    return HPDF_OK;
}


static HPDF_UINT
GetUncovertedBytes  (HPDF_Font           font,
                     const char         *text,
                     HPDF_UINT           cache_begin,
                     HPDF_TextLineWidth *width)
{
    HPDF_FontAttr attr;
    HPDF_CharEnc src_charenc;
    HPDF_CharEnc dst_charenc;
    HPDF_UINT i, j, numbytes;

    attr = (HPDF_FontAttr)font->attr;
    dst_charenc = attr->encoder->charenc;

    if (dst_charenc == HPDF_CHARENC_UNSUPPORTED)
        return width->linebytes;

    src_charenc = GetSourceCharEnc (font);

    i = j = numbytes = 0;
    while (i < width->linebytes) {
        i += BYTES_FNS[dst_charenc] (attr->text_cache + cache_begin + i);
        j += BYTES_FNS[src_charenc] ((const HPDF_BYTE *)text + j);
        if (width->numbytes == i)
            numbytes = j;
    }

    width->numbytes = numbytes;
    width->linebytes = j;

    return j;
}


HPDF_BOOL
HPDF_Font_IsRtL  (HPDF_Font font)
{
    HPDF_BOOL ret = HPDF_FALSE;
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    HPDF_List list = attr->converters_list[attr->converters_index];

    HPDF_PTRACE ((" HPDF_Font_IsRtL\n"));

#ifdef LIBHPDF_ENABLE_BIDI
    if (list) {
        HPDF_UINT i;
        HPDF_ConverterBiDi bidi;

        for (i = 0; i < list->count; i++) {
            bidi = (HPDF_ConverterBiDi)HPDF_List_ItemAt (list, i);
            if (bidi->hdr.convert_fn == BiDi) {
                ret = ((bidi->par->base_dir & FRIBIDI_MASK_RTL) != 0);
                break;
            }
        }
    }
#endif

    return ret;
}


void
HPDF_Font_CheckBiDi  (HPDF_Font font,
                      HPDF_BOOL even_users)
{
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    HPDF_List list = attr->converters_list[attr->converters_index];

    HPDF_PTRACE ((" HPDF_Font_CheckBiDi\n"));

#ifdef LIBHPDF_ENABLE_BIDI
    if (list) {
        HPDF_UINT i;
        HPDF_ConverterBiDi bidi;

        for (i = 0; i < list->count; i++) {
            bidi = (HPDF_ConverterBiDi)HPDF_List_ItemAt (list, i);
            if (bidi->hdr.convert_fn == BiDi) {
                if (even_users ||
                        bidi->par == (HPDF_ConverterBiDi_Param_Rec *)(bidi + 1))
                    bidi->par->base_dir = 0;
                break;
            }
        }
    }
#endif
}


HPDF_EXPORT(HPDF_STATUS)
HPDF_Font_SetTatweelCount (HPDF_Font font,
                           HPDF_UINT dst_tatweels,
                           HPDF_UINT src_tatweels,
                           HPDF_UINT numchars)
{
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    HPDF_List list = attr->converters_list[attr->converters_index];

    HPDF_PTRACE ((" HPDF_Font_SetTatweelCount\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

#ifdef LIBHPDF_ENABLE_BIDI
    if (list) {
        HPDF_UINT i;
        HPDF_ConverterBiDi bidi;

        for (i = 0; i < list->count; i++) {
            bidi = (HPDF_ConverterBiDi)HPDF_List_ItemAt (list, i);
            if (bidi->hdr.convert_fn == BiDi) {
                bidi->dst_tatweels = dst_tatweels;
                bidi->src_tatweels = src_tatweels;
                bidi->hdr.chars_factor = (numchars - 1 +
                        numchars + dst_tatweels - src_tatweels) / numchars;
                if (!bidi->hdr.chars_factor)
                    bidi->hdr.chars_factor = 1;
                break;
            }
        }
    }
#endif

    return HPDF_OK;
}


void
HPDF_Font_SetParseText  (HPDF_Font            font,
                         HPDF_ParseText_Rec  *state,
                         const char          *text,
                         HPDF_UINT            len)
{
    HPDF_FontAttr attr;

    HPDF_PTRACE ((" HPDF_Font_SetParseText\n"));

    attr = (HPDF_FontAttr)font->attr;
    HPDF_Encoder_SetParseText (attr->encoder, state, (const HPDF_BYTE *)text, len);

    state->charenc = GetSourceCharEnc (font);
}


HPDF_EXPORT(HPDF_ByteType)
HPDF_Font_GetByteType  (HPDF_Font     font,
                        const char   *text,
                        HPDF_UINT     index)
{
    HPDF_Encoder encoder;
    HPDF_ParseText_Rec parse_state;
    HPDF_ByteType btype;
    HPDF_UINT bytes;

    HPDF_PTRACE ((" HPDF_Font_GetByteType\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

    if (HPDF_Font_StrLen (font, text, index + 4) < index)
        return HPDF_BYTE_TYPE_UNKNOWN;

    encoder = ((HPDF_FontAttr)font->attr)->encoder;

    if (encoder->type != HPDF_ENCODER_TYPE_MULTI_BYTE)
        return HPDF_BYTE_TYPE_SINGLE;

    HPDF_Font_SetParseText (font, &parse_state, text, index + 4);

    for (;;) {
        btype = HPDF_CMapEncoder_ByteType (encoder, &parse_state, &bytes);

        if (index == 0)
            break;

        text++;
        index--;
    }

    return btype;
}


HPDF_EXPORT(HPDF_UCS4)
HPDF_Font_GetUcs4  (HPDF_Font   font,
                    const char *text,
                    HPDF_UINT  *bytes)
{
    HPDF_FontAttr attr;
    HPDF_CharEnc charenc;
    HPDF_UCS4 ucs4;
    HPDF_UINT tmp_bytes;

    HPDF_PTRACE ((" HPDF_Font_GetUcs4\n"));

    if (!HPDF_Font_Validate (font))
        return 0;

    attr = (HPDF_FontAttr)font->attr;

    charenc = GetSourceCharEnc (font);

    if (charenc == HPDF_CHARENC_UNSUPPORTED)
        return HPDF_Encoder_GetUcs4 (attr->encoder, (const HPDF_BYTE *)text,
                bytes);

    tmp_bytes = BYTES_FNS[charenc] ((const HPDF_BYTE *)text);
    ucs4 = TOUCS4_FNS[charenc] ((const HPDF_BYTE *)text, tmp_bytes);

    if (bytes)
        *bytes = tmp_bytes;

    return ucs4;
}


HPDF_EXPORT(HPDF_STATUS)
HPDF_Font_SetReliefFont  (HPDF_Font   font,
                          HPDF_Font   relief_font)
{
    HPDF_FontAttr attr, rf_attr;

    HPDF_PTRACE ((" HPDF_Font_SetReliefFont\n"));

    if (!HPDF_Font_Validate (font))
        return HPDF_INVALID_FONT;

    attr = (HPDF_FontAttr)font->attr;

    if (!relief_font) {
        attr->relief_font = relief_font;
        return HPDF_OK;
    }

    if (!HPDF_Font_Validate (relief_font))
        return HPDF_INVALID_FONT;

    rf_attr = (HPDF_FontAttr)relief_font->attr;

    if (attr->type == HPDF_FONT_TYPE1 ||
        rf_attr->type == HPDF_FONT_TYPE1)
        return HPDF_RaiseError (font->error,
            HPDF_UNSUPPORTED_FONT_TYPE, 0);

    if (!attr->text_cache)
        HPDF_NormalizeCharEnc (&attr->encoder->charenc);
    if (!rf_attr->text_cache)
        HPDF_NormalizeCharEnc (&rf_attr->encoder->charenc);

    if (rf_attr->encoder != attr->encoder &&
        (rf_attr->encoder->charenc == HPDF_CHARENC_UNSUPPORTED ||
         rf_attr->encoder->charenc != attr->encoder->charenc))
        return HPDF_RaiseError (font->error,
            HPDF_UNMATCHED_RELIEF_FONT, 0);

    for (font = relief_font; font;
            font = ((HPDF_FontAttr)font->attr)->relief_font)
        if ((HPDF_FontAttr)font->attr == attr)
            return HPDF_RaiseError (font->error,
                    HPDF_LOOPED_RELIEF_FONT, 0);

    attr->relief_font = relief_font;

    return HPDF_OK;
}


HPDF_Font
HPDF_Font_GetReliefFont  (HPDF_Font  font,
                          HPDF_UCS4  ucs4,
                          HPDF_BYTE *index)
{
    HPDF_BYTE irf = 0;
    HPDF_Font rf = font;
    HPDF_FontAttr rfattr = (HPDF_FontAttr)rf->attr;

    if (rfattr->fontdef->type != HPDF_FONT_TYPE1) {
        for (; rf && irf <= HPDF_RELIEF_FONT_INDEX_MASK;
                    rf = rfattr->relief_font, irf++) {
            rfattr = (HPDF_FontAttr)rf->attr;
            if (rfattr->fontdef->type == HPDF_FONTDEF_TYPE_CID) {
                /* cid-based font */
                if (HPDF_CMapEncoder_ToCID (rfattr->encoder, ucs4))
                    break;
            } else {
                /* unicode-based font */
                HPDF_UINT16 gid;
                HPDF_TTFontDefAttr ttattr;
                ttattr = (HPDF_TTFontDefAttr)rfattr->fontdef->attr;
                if (ttattr)
                {
                  gid = HPDF_TTFontDef_GetGlyphid (rfattr->fontdef, ucs4);
                  if (gid && gid < ttattr->num_glyphs)
                    break;
                }
            }
        }
    }

    if (HPDF_RELIEF_FONT_INDEX_MASK < irf)
        rf = NULL;

    if (index)
        *index = (rf? irf: 0);

    return rf;
}


static void
SetReliefFontIndex  (HPDF_Font font)
{
    HPDF_FontAttr attr = (HPDF_FontAttr)font->attr;
    HPDF_Encoder encoder = attr->encoder;
    HPDF_BYTE irf = 0;
    HPDF_BYTE irff = 0;
    HPDF_ParseText_Rec parse_state;
    HPDF_UINT i;
    HPDF_BYTE *pirf = attr->text_cache + (attr->text_cache_allocated / 2);

    HPDF_PTRACE ((" HPDF_CIDFont_SetReliefFontIndex\n"));

    HPDF_Encoder_SetParseText (encoder, &parse_state, attr->text_cache,
            attr->text_cache_len);

    for (i = 0; i < attr->text_cache_len; i++) {
        HPDF_UINT bytes;
        HPDF_ByteType btype;
        btype = HPDF_CMapEncoder_ByteType (encoder, &parse_state, &bytes);

        if (btype != HPDF_BYTE_TYPE_TRIAL) {
            HPDF_UCS4 b = HPDF_Encoder_ToUcs4 (encoder,
                    attr->text_cache + i, bytes);

            HPDF_Font_GetReliefFont (font, b, &irf);

            switch (b) {
                case 0xFFF9:    /* IAA */
                    irff &= ~(HPDF_INTERLINEAR_ANNOTATED |
                              HPDF_INTERLINEAR_ANNOTATION);
                    irff |= HPDF_INTERLINEAR_ANNOTATED;
                    break;
                case 0xFFFA:    /* IAS */
                    irff &= ~(HPDF_INTERLINEAR_ANNOTATED |
                              HPDF_INTERLINEAR_ANNOTATION);
                    irff |= HPDF_INTERLINEAR_ANNOTATION;
                    break;
                case 0xFFFB:    /* IAT */
                    irff &= ~(HPDF_INTERLINEAR_ANNOTATED |
                              HPDF_INTERLINEAR_ANNOTATION);
                    break;
            }
        }
        pirf[i] = irff | irf;
    }
}


static HPDF_BOOL
IsCIDText  (HPDF_Font font,
            HPDF_UINT index)
{
    HPDF_FontAttr rfattr = (HPDF_FontAttr)font->attr;

    while (index-- && rfattr) {
        if (!(font = rfattr->relief_font))
            break;
        rfattr = (HPDF_FontAttr)font->attr;
    }

    return (rfattr->type == HPDF_FONT_TYPE0_TT &&
            (((HPDF_TTFontDefAttr)rfattr->fontdef->attr)->options &
             HPDF_FONTOPT_WITHOUT_CID_MAP));
}
