/*
 * libfbf.c - FBF font decoder
 *
 * Copyright (c) 2000, 2001, 2002 Fabrice Bellard.
 *
 * 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.
 */

#include <stdlib.h>
#include <string.h>

#include "libfbf.h"

//#define DEBUG

#define free(ptr)    do_not_use_free
#define malloc(size) do_not_use_malloc
#define index  index__

static void init_context_tables(void);
static int unicode_to_johab(int code);
static int decode_hangul_glyph(UniFontData *uf,
                               GlyphEntry **glyph_entry_ptr,
                               int code);

static inline void *uf_malloc(UniFontData *uf, int size)
{
    return uf->fbf_malloc(uf->mem_opaque, size);
}

static inline void uf_free(UniFontData *uf, void *ptr)
{
    uf->fbf_free(uf->mem_opaque, ptr);
}

/* totally unoptimised get bit functions */
static void get_bit_init(UniFontData *uf)
{
    uf->bitcnt = 0;
}

static int get_bit(UniFontData *uf)
{
    if (uf->bitcnt == 0) {
        uf->bitbuf = uf->fbf_getc(uf->infile);
        uf->bitcnt = 7;
    } else {
        uf->bitcnt--;
    }
    return (uf->bitbuf >> uf->bitcnt) & 1;
}

static int get_bits(UniFontData *uf, int n)
{
    int val, i;

    val = 0;
    for (i = 0; i < n; i++) {
        val = (val << 1) | get_bit(uf);
    }
    return val;
}

/* read log encoded number */
static int read_num(UniFontData *uf)
{
    int l, n;

    l = 0;
    while (get_bit(uf) != 0)
        l++;
    if (l <= 1)
        return l;
    n = get_bits(uf, l - 1) | (1 << (l -1));
    return n;
}

static void read_segments(UniFontData *uf)
{
    int n, code, delta, size, seg;

    get_bit_init(uf);
    seg = 0;
    n = 0;
    code = 0;
    while (n < uf->nb_glyphs && seg < uf->nb_segs) {
        /* read one segment */
        delta = read_num(uf);
        size = read_num(uf) + 1;
        code += delta;
        uf->seg_table[seg].glyph = n;
        uf->seg_table[seg].start = code;
        uf->seg_table[seg].size = size;
        //printf("%05x: %d\n", code, size);
        code += size + 1;
        n += size;
        seg++;
    }
}

static int get_be16(UniFontData *uf)
{
    int val;
    val = uf->fbf_getc(uf->infile) << 8;
    val |= uf->fbf_getc(uf->infile);
    return val;
}

static int get_be32(UniFontData *uf)
{
    int val;
    val = uf->fbf_getc(uf->infile) << 24;
    val |= uf->fbf_getc(uf->infile) << 16;
    val |= uf->fbf_getc(uf->infile) << 8;
    val |= uf->fbf_getc(uf->infile);
    return val;
}

static void fbf_get_str(UniFontData *uf, char *buf, int buf_size)
{
    int i, len, c;
    char *q;

    len = uf->fbf_getc(uf->infile);
    q = buf;
    for (i = 0; i < len; i++) {
        c = uf->fbf_getc(uf->infile);
        if ((q - buf) < (buf_size - 1))
            *q++ = c;
    }
    *q = '\0';
}

int fbf_load_font(UniFontData *uf)
{
    UnifontHeader h;
    int i;
    static int init_done = 0;

    if (!init_done) {
        init_context_tables();
        init_done = 1;
    }

    h.magic = get_be32(uf);
    if (h.magic != UNIFONT_MAGIC)
        return -1;
    h.version = get_be32(uf);
    if (h.version != 1)
        return -1;
    h.nb_glyphs = get_be32(uf);
    h.compressed_segment_size = get_be32(uf);
    h.flags = get_be32(uf);
    h.max_width = get_be16(uf);
    h.max_height = get_be16(uf);
    uf->x_res = get_be16(uf);
    uf->y_res = get_be16(uf);
    uf->pt_size = get_be16(uf);
    uf->ascent = get_be16(uf);
    uf->descent = get_be16(uf);
    uf->line_height = get_be16(uf);
    uf->underline_position = get_be16(uf);
    uf->underline_thickness = get_be16(uf);
    h.nb_segments = get_be16(uf);
    uf->family_type = uf->fbf_getc(uf->infile);
    uf->fbf_getc(uf->infile);

    fbf_get_str(uf, uf->family_name, sizeof(uf->family_name));

    uf->nb_glyphs = h.nb_glyphs;
    uf->compressed_segment_size = h.compressed_segment_size;
    uf->max_width = h.max_width;
    uf->max_height = h.max_height;
    uf->flags = h.flags;
    uf->nb_segs = h.nb_segments;

    /* compressed segments offsets */

    uf->nb_csegs = (uf->nb_glyphs + uf->compressed_segment_size - 1) /
        uf->compressed_segment_size;
    uf->csegs_offsets = uf_malloc(uf, uf->nb_csegs * 2 * sizeof(int));
    if (!uf->csegs_offsets)
        goto fail;
    uf->msegs_offsets = uf->csegs_offsets + uf->nb_csegs;
    for (i = 0; i < uf->nb_csegs; i++) {
        uf->msegs_offsets[i] = get_be32(uf);
    }

    for (i = 0; i < uf->nb_csegs; i++) {
        uf->csegs_offsets[i] = get_be32(uf);
    }

    /* unicode to glyph index conversion table */

    uf->seg_table = uf_malloc(uf, uf->nb_segs * sizeof(SegData));
    if (!uf->seg_table)
        goto fail;
    read_segments(uf);

    /* decoding context */
    if (uf->fbf_read(uf->infile, uf->ctx1, sizeof(uf->ctx1)) != sizeof(uf->ctx1))
        goto fail;

    uf->nb_glyphs_total = uf->nb_glyphs;
    /* if hangul composite glyphs, increase virtual number of glyphs */
    if (uf->flags & UF_FLAG_HANGUL) {
        uf->nb_glyphs_total += 11172;
    }
    return 0;

 fail:
    uf_free(uf, uf->seg_table);
    if (uf) {
        if (uf->csegs_offsets)
            uf_free(uf, uf->csegs_offsets);
    }
    return -1;
}

void fbf_free_font(UniFontData *uf)
{
    GlyphSegment *gseg;
    int i;

    for (i = 0; i < CSEG_CACHE_SIZE; i++) {
        gseg = uf->cseg_cache[i];
        if (gseg) {
            uf_free(uf, gseg->bitmap_table);
            uf_free(uf, gseg);
        }
    }
    uf_free(uf, uf->csegs_offsets);
    uf_free(uf, uf->seg_table);
}

/* arithmetic font decoder */

static unsigned char ctx_incr[2][256];
static unsigned char ctx_shift[256];

static void init_context_tables(void)
{
    int ctxval, val, bit, freq0, freq1, shift;

    /* table for context frequency update */
    for (bit = 0; bit < 2; bit++) {
        for (ctxval = 0; ctxval < 256; ctxval++) {
            freq0 = 2*(ctxval >> 4) + 1;
            freq1 = 2*(ctxval & 0xf) + 1;
            if (!bit)
                freq0 += 2;
            else
                freq1 += 2;
            if (freq0 > 31 || freq1 > 31) {
                freq0 = (freq0 + 1) >> 1;
                freq1 = (freq1 + 1) >> 1;
            }
            val = (((freq0 - 1) >> 1) << 4) |
                ((freq1 - 1) >> 1);
            ctx_incr[bit][ctxval] = val;
            //            printf("%d %02x %02x\n", bit, ctxval, val);
        }
    }

    /* table for arith coding */
    for (ctxval = 0; ctxval < 256; ctxval++) {
        unsigned int sum, m, s;

        freq0 = 2*(ctxval >> 4) + 1;
        freq1 = 2*(ctxval & 0xf) + 1;
        val = 0;

        sum = freq0 + freq1;
        /* invert bit */
        if (freq0 > freq1) {
            val = 0x80;
            freq0 = freq1;
        }

        shift = 0;
        m=freq0*8;
        s=sum*5;
        while (m<s) {
            shift++;
            m<<=1;
        }
        if (m<sum*7) {
            shift += 2;
            val |= 0x40;
        }
        ctx_shift[ctxval] = val | shift;
        //        printf("%02x %02x\n", ctxval, ctx_shift[ctxval]);
    }
}

static void arith_init(UniFontData *uf)
{
    uf->arange = 0x1000000;
    /* data buffer */
    uf->alow = uf->fbf_getc(uf->infile) << 16;
    uf->alow |= uf->fbf_getc(uf->infile) << 8;
    uf->alow |= uf->fbf_getc(uf->infile);
}

static inline unsigned int decode_ctx(UniFontData *uf, unsigned char *ctx)
{
    unsigned int ctxval, range, shift, b;
    unsigned int alow, arange;

    alow = uf->alow;
    arange = uf->arange;

    ctxval = ctx[0];
#ifdef DEBUG
    printf("low=%x range=%x ctx=%x ", alow, arange, ctxval);
#endif

    /* compute decision level */
    shift = ctx_shift[ctxval];
    range = arange;
    if (shift & 0x40)
        range = range * 3;
    range = range >> (shift & 0x3f);

    /* which bit is was encoded ? */
    b = (alow >= range);
    if (b) {
        alow -= range;
        arange -= range;
    } else {
        arange = range;
    }

    /* increment context */
    b = b ^ (shift >> 7);
    ctx[0] = ctx_incr[b][ctxval];

    /* renormalize arith state */
    if (arange < RANGE_MIN) {
        alow = ((alow << 8) | uf->fbf_getc(uf->infile)) & 0xffffff;
        arange <<= 8;
    }

    uf->arange = arange;
    uf->alow = alow;
#ifdef DEBUG
    printf("b=%u\n", b);
#endif
    return b;
}


/* glyph decoder */

static inline int get_ctx(int x, int y, unsigned char *p)
{
    int v;

    v = 0;

    v = v * 3;
    if (y >= 4) {
        v += p[-3 * WRAP] << 0;  // M
    } else {
        v += 2;
    }
    v = v  * 3;
    if (x >= 4) {
        v += (p[-3]) << 0;  // T
    } else {
        v += 2;
    }

    /* distance of 2 */
    v = v << 2;
    v += p[-2 * WRAP + 2] << 0; // K
    v += p[-2 * WRAP - 2] << 1;     // G

    v = v << 1;
    v += p[-2] << 0;            // E

    v = v << 3;
    v += p[-2 * WRAP - 1] << 0;  // H
    v += p[-2 * WRAP] << 1;      // I
    v += p[-2 * WRAP + 1] << 2;  // J

    v = v << 4;
    v += p[-WRAP - 1] << 0;     // B
    v += p[-WRAP + 1] << 1;      // D
    v += p[-WRAP + 2] << 2;     // L
    v += p[-WRAP - 2] << 3;     // F

    v = v << 2;
    v += p[-1] << 0;                    // A
    v += p[-WRAP] << 1;          // C

    return v;
}

static void decode_glyph(UniFontData *uf,
                         unsigned char *ctx1,
                         unsigned char *ctx_adapt,
                         unsigned char *outbuf,
                         int w, int h)
{
    unsigned char *p;
    unsigned char bitmap[WRAP * (MAXHEIGHT + MAXDIST)];
    int v, v1, x, y, b, i;
    unsigned int lbuf;
    //unsigned int w1;

    memset(bitmap, 1, sizeof(bitmap));
    p = bitmap + MAXDIST + WRAP * MAXDIST;
    //w1 = (w + 7) >> 3;
    for (y = 0; y < h; y++) {
        lbuf = 0;
        for (x = 0; x < w; x++) {
            v = get_ctx(x, y, p);
            if (ctx_adapt[v] == 0x00 ||
                ctx_adapt[v] == 0x10 ||
                ctx_adapt[v] == 0x01) {
                v1 = v & (NB_CTX1 - 1);
                b = decode_ctx(uf, &ctx1[v1]);
                ctx_adapt[v] = ctx_incr[b][ctx_adapt[v]];
            } else {
                b = decode_ctx(uf, &ctx_adapt[v]);
            }
            *p++ = b;
            lbuf = (lbuf << 1) | b;
            if ((x & 7) == 7) {
                *outbuf++ = lbuf;
            }
        }
        p += WRAP - w;
        /* output remaning bits */
        i = (w & 7);
        if (i != 0) {
            *outbuf++ = lbuf << (8 - i);
        }
    }
}

typedef struct EncodeLogContext {
    unsigned char log_ctx[16];
    unsigned char sign_ctx;
} EncodeLogContext;

static int read_num1(UniFontData *uf, EncodeLogContext *c, int is_signed)
{
    int l, n, i;
    unsigned char ctx;

    l = 0;
    while (decode_ctx(uf, &c->log_ctx[l]) != 0)
        l++;
    if (l == 0) {
        return 0;
    }
    n = 1 << (l - 1);
    for (i = l-2; i >= 0; i--) {
        ctx = 0;
        n |= decode_ctx(uf, &ctx) << i;
    }
    if (is_signed) {
        if (decode_ctx(uf, &c->sign_ctx))
            n = -n;
    }
    return n;
}

typedef struct {
    EncodeLogContext metric_ctx[6];
    int last_w, last_h, last_x, last_y, last_xincr;
} MetricContext;

static void decode_glyph_metric(UniFontData *uf, MetricContext *m,
                                GlyphEntry *g)
{
    g->w = read_num1(uf, &m->metric_ctx[1], 1) + m->last_w;
    g->h = read_num1(uf, &m->metric_ctx[2],  1) + m->last_h;
    g->x = read_num1(uf, &m->metric_ctx[3], 1) + m->last_x;
    g->y = read_num1(uf, &m->metric_ctx[4], 1) + m->last_y;
    g->xincr = read_num1(uf, &m->metric_ctx[5], 1) + m->last_xincr;

    m->last_w = g->w;
    m->last_h = g->h;
    m->last_x = g->x;
    m->last_y = g->y;
    m->last_xincr = g->xincr;
}

/* decode one segment of 'compressed_segment_size' glyph metrics */
static GlyphSegment *decode_metrics_segment(UniFontData *uf, int segment)
{
    int i, glyph_start, glyph_end, nb_glyphs;
    GlyphSegment *g;
    GlyphEntry *m;
    MetricContext metric_ctx;

    glyph_start = segment * uf->compressed_segment_size;
    if (glyph_start >= uf->nb_glyphs)
        return NULL;
    glyph_end = glyph_start + uf->compressed_segment_size;
    if (glyph_end > uf->nb_glyphs)
        glyph_end = uf->nb_glyphs;
    nb_glyphs = glyph_end - glyph_start;

    g = uf_malloc(uf, sizeof(GlyphSegment) +
                  (nb_glyphs - 1) * sizeof(GlyphEntry));
    if (!g)
        return NULL;

    g->first_glyph = glyph_start;
    g->nb_glyphs = nb_glyphs;
    g->bitmap_table = NULL;

    uf->fbf_seek(uf->infile, uf->msegs_offsets[segment]);

    arith_init(uf);

    memset(&metric_ctx, 0, sizeof(metric_ctx));
    for (i = 0; i < nb_glyphs; i++) {
        m = &g->metrics[i];
        decode_glyph_metric(uf, &metric_ctx, m);
    }
    return g;
}

/* decode one segment of 'compressed_segment_size' glyphs */
static int decode_glyphs_segment(UniFontData *uf, GlyphSegment *g, int segment)
{
    int glyph_start, glyph_end, size, nb_glyphs, bitmap_size, i;
    unsigned char *ctx, ctx1[NB_CTX1], *data;
    GlyphEntry *m;

    glyph_start = segment * uf->compressed_segment_size;
    if (glyph_start >= uf->nb_glyphs)
        return -1;
    glyph_end = glyph_start + uf->compressed_segment_size;
    if (glyph_end > uf->nb_glyphs)
        glyph_end = uf->nb_glyphs;
    nb_glyphs = glyph_end - glyph_start;

    uf->fbf_seek(uf->infile, uf->csegs_offsets[segment]);

    /* allocate bitmap */
    bitmap_size = 0;
    for (i = 0; i < nb_glyphs; i++) {
        m = &g->metrics[i];
        size = ((m->w + 7) >> 3) * m->h;
        bitmap_size += size;
    }
    //    printf("segment=%d bitmap_size=%d\n", segment, bitmap_size);
    g->bitmap_table = uf_malloc(uf, bitmap_size);
    if (!g->bitmap_table)
        return -1;

    /* allocate compression context */
    ctx = uf_malloc(uf, NB_CTX);
    if (!ctx)
        return -1;
    memset(ctx, 0, NB_CTX);
    memcpy(ctx1, uf->ctx1, NB_CTX1);

    arith_init(uf);
    data = g->bitmap_table;
    for (i = 0; i < nb_glyphs; i++) {
        m = &g->metrics[i];
        m->bitmap = data;
        decode_glyph(uf, ctx1, ctx, data, m->w, m->h);
        size = ((m->w + 7) >> 3) * m->h;
        data += size;
    }

    uf_free(uf, ctx);
    return 0;
}

int fbf_decode_glyph(UniFontData *uf,
                     GlyphEntry **glyph_entry_ptr,
                     int index)
{
    GlyphSegment *gseg, **pgseg;
    int i, segment, use_count_min, cseg_min;

    if (index >= uf->nb_glyphs) {
        /* special case for algorithmically generated hangul glyphs */
        return decode_hangul_glyph(uf, glyph_entry_ptr, index - uf->nb_glyphs);
    }

 redo:
    for (i = 0; i < CSEG_CACHE_SIZE; i++) {
        gseg = uf->cseg_cache[i];
        if (gseg &&
            index >= gseg->first_glyph &&
            index < gseg->first_glyph + gseg->nb_glyphs) {
            gseg->use_count++;
            *glyph_entry_ptr = &gseg->metrics[index - gseg->first_glyph];
            return 0;
        }
    }
    /* no glyph found : decode a new segment */

    /* free least used segment selected segment */
    cseg_min = 0;
    use_count_min = 0x7fffffff;
    for (i = 0; i < CSEG_CACHE_SIZE; i++) {
        if (uf->cseg_cache[i] == NULL) {
            cseg_min = i;
            break;
        }
        if (uf->cseg_cache[i]->use_count < use_count_min) {
            use_count_min = uf->cseg_cache[i]->use_count;
            cseg_min = i;
        }
    }

    pgseg = &uf->cseg_cache[cseg_min];
    gseg = *pgseg;
    if (gseg) {
        uf_free(uf, gseg->bitmap_table);
        uf_free(uf, gseg);
        *pgseg = NULL;
    }

    /* create a glyph segment and decode only its metrics */
    segment = index / uf->compressed_segment_size;
    gseg = decode_metrics_segment(uf, segment);
    if (!gseg)
        return -1;
    /* decode the glyphs */
    if (decode_glyphs_segment(uf, gseg, segment) < 0)
        return -1;
    gseg->use_count = 0;
    *pgseg = gseg;
    goto redo;
}

/* XXX: could be faster with a table for the high order bits of 'code' */
int fbf_unicode_to_glyph(UniFontData *uf, int code)
{
    int start, end, mid, size, k;

    /* special hangul case */
    if (code >= 0x1100 && code <= 0x11ff) {
        code = unicode_to_johab(code);
    } else if (code >= 0xAC00 && code < (0xAC00 + 11172)) {
        /* map to composite glyph area */
        return code - 0xAC00 + uf->nb_glyphs;
    }

    start = 0;
    end = uf->nb_segs - 1;
    while (end >= start) {
        mid = (start + end) >> 1;
        k = uf->seg_table[mid].start;
        size = uf->seg_table[mid].size;
        if (code >= k && code < (k + size)) {
            return uf->seg_table[mid].glyph + code - k;
        } else if (code < k)
            end = mid - 1;
        else {
            start = mid + 1;
        }
    }
    return -1;
}

/************************************************************/
/* HANGUL composite glyph handling */

// The base font index for leading consonants
static const unsigned char lconBase[] = {
        1, 11, 21, 31, 41, 51,
        61, 71, 81, 91, 101, 111,
        121, 131, 141, 151, 161, 171,
        181
};

// The base font index for vowels

static const unsigned short vowBase[] = {
    0,311,314,317,320,323,   //  (Fill), A, AE, YA, YAE, EO
    326,329,332,335,339,343, //  E, YEO, YE, O, WA, WAE
    347,351,355,358,361,364, //  OI, YO, U, WEO, WE, WI
    367,370,374,378          //  YU, EU, UI, I
};

  // The base font index for trailing consonants

static const unsigned short tconBase[] = {
    // modern trailing consonants (filler + 27)
    0,
    405, 409, 413, 417, 421,
    425, 429, 433, 437, 441,
    445, 449, 453, 457, 461,
    465, 469, 473, 477, 481,
    485, 489, 493, 497, 501,
    505, 509
};

// The mapping from vowels to leading consonant type
// in absence of trailing consonant

static const unsigned char lconMap1[] = {
    0,0,0,0,0,0,     // (Fill), A, AE, YA, YAE, EO
    0,0,0,1,3,3,     // E, YEO, YE, O, WA, WAE
    3,1,2,4,4,4,     // OI, YO, U, WEO, WE, WI
    2,1,3,0          // YU, EU, UI, I
};

// The mapping from vowels to leading consonant type
// in presence of trailing consonant

static const unsigned char lconMap2[] = {
    5,5,5,5,5,5,     //  (Fill), A, AE, YA, YAE, EO
    5,5,5,6,8,8,     //  E, YEO, YE, O, WA, WAE
    8,6,7,9,9,9,     //  OI, YO, U, WEO, WE, WI
    7,6,8,5          //  YU, EU, UI, I
};

//  vowel type ; 1 = o and its alikes, 0 = others

static const unsigned char vowType[] = {
    0,0,0,0,0,0,
    0,0,0,1,1,1,
    1,1,0,0,0,0,
    0,1,1,0
};

//  The mapping from trailing consonants to vowel type

static const unsigned char tconType[] = {
    0, 1, 1, 1, 2, 1,
    1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1,
    1, 1, 1, 1
};

//  The mapping from vowels to trailing consonant type

static const unsigned char tconMap[] = {
    0, 0, 2, 0, 2, 1,  //  (Fill), A, AE, YA, YAE, EO
    2, 1, 2, 3, 0, 0,  //  E, YEO, YE, O, WA, WAE
    0, 3, 3, 1, 1, 1,  //  OI, YO, U, WEO, WE, WI
    3, 3, 0, 1         //  YU, EU, UI, I
};

/* put src at position (x,y) in dst. src has a size of (w, h) */
static inline void bitmap_or(unsigned char *dst, int dst_wrap,
                             unsigned char *src, int src_wrap,
                             int x, int y,
                             int w, int h)
{
    int i, j, bit, x1;
    dst += dst_wrap * y;
    for (i = 0; i < h; i++) {
        for (j = 0; j < w; j++) {
            bit = (src[j >> 3] >> (7 - (j & 7))) & 1;
            x1 = x + j;
            dst[x1 >> 3] |= bit << (7 - (x1 & 7));
        }
        dst += dst_wrap;
        src += src_wrap;
    }
}

/* XXX: suppress that by modifying unicode to glyph mapping in
   ufencode */
static int unicode_to_johab(int code)
{
    int j;

    if (code >= 0x1100 && code <= 0x1100 + 18) {
        j = code - 0x1100;
        code = JOHAB_BASE + lconBase[j] + 9;
    } else if (code >= 0x1161 && code < 0x1161 + 21) {
        j = code - 0x1161 + 1;
        code = JOHAB_BASE + vowBase[j]+1+vowType[j];
    } else if (code >= 0x11A8 && code < 0x11A8 + 27) {
        j = code - 0x11A8 + 1;
        code = JOHAB_BASE + tconBase[j] + 3;
    }
    return code;
}

/* 0 <= code < 11172 */
static int decode_hangul_glyph(UniFontData *uf,
                               GlyphEntry **glyph_entry_ptr,
                               int code)
{
    int i, index, l, m, f, ind[3], wrap;
    GlyphEntry *glyph_entry, *glyph1;

    glyph_entry = &uf->tmp_glyph_entry;
    glyph_entry->bitmap = uf->tmp_buffer;
    /* XXX: size is hardcoded for unifont */
    glyph_entry->w = 16;
    glyph_entry->h = 16;
    glyph_entry->x = 0;
    glyph_entry->y = 0;
    glyph_entry->xincr = 16;
    wrap = (glyph_entry->w + 7) >> 3;

    l = code / (21 * 28);
    m = ((code / 28) % 21) + 1;
    f = code % 28;

    /* first glyph */
    ind[0] = lconBase[l] + ((f > 0) ? lconMap2[m] : lconMap1[m]);

    /* second glyph */
    ind[1] = vowBase[m];
    if (vowType[m] == 1) {
        ind[1] += ((l == 0 || l == 15) ? 0 : 1) + (f > 0 ? 2 : 0);
    } else {
        ind[1] += tconType[f];
    }

    /* third glyph */
    ind[2] = f ? tconBase[f] + tconMap[m] : 0;

    /* render the three glyphs & supperpose them */
    memset(glyph_entry->bitmap, 0, glyph_entry->h * wrap);
    for (i = 0; i < 3; i++) {
        index = fbf_unicode_to_glyph(uf, JOHAB_BASE + ind[i]);
        if (index < 0)
            continue;
        if (fbf_decode_glyph(uf, &glyph1, index) < 0)
            continue;
        bitmap_or(glyph_entry->bitmap, wrap,
                  glyph1->bitmap, (glyph1->w + 7) >> 3,
                  glyph1->x, glyph1->y,
                  glyph1->w, glyph1->h);
    }
    *glyph_entry_ptr = glyph_entry;
    return 0;
}

