/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.

   Copyright (C) 2002-2016 by Jin-Hwan Cho and Shunsaku Hirata,
   the dvipdfmx project team.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/

#include "dpx-cff.h"

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

#include "dpx-cff_dict.h"
#include "dpx-cff_stdstr.h"
#include "dpx-cff_types.h"
#include "dpx-error.h"
#include "dpx-mem.h"
#include "dpx-numbers.h"

#define CFF_DEBUG     5
#define CFF_DEBUG_STR "CFF"

static unsigned int
get_unsigned (rust_input_handle_t handle, int n)
{
    unsigned int v = 0;

    while (n-- > 0)
        v = v*0x100u + tt_get_unsigned_byte(handle);

    return v;
}

#define get_offset(s, n) get_unsigned((s), (n))

/*
 * Read Header, Name INDEX, Top DICT INDEX, and String INDEX.
 */
cff_font *cff_open(rust_input_handle_t handle, int offset, int n)
{
    cff_font  *cff;
    cff_index *idx;

    cff = NEW(1, cff_font);

    cff->fontname = NULL;
    cff->index    = n;
    cff->handle   = handle;
    cff->offset   = offset;
    cff->filter   = 0;      /* not used */
    cff->flag     = 0;

    cff->name     = NULL;
    cff->topdict  = NULL;
    cff->gsubr    = NULL;
    cff->encoding = NULL;
    cff->charsets = NULL;
    cff->fdselect = NULL;
    cff->cstrings = NULL;
    cff->fdarray  = NULL;
    cff->private  = NULL;
    cff->subrs    = NULL;

    cff->num_glyphs = 0;
    cff->num_fds    = 0;
    cff->string     = NULL;
    cff->_string    = NULL;

    cff_seek_set(cff, 0);
    cff->header.major    = tt_get_unsigned_byte(cff->handle);
    cff->header.minor    = tt_get_unsigned_byte(cff->handle);
    cff->header.hdr_size = tt_get_unsigned_byte(cff->handle);
    cff->header.offsize  = tt_get_unsigned_byte(cff->handle);
    if (cff->header.offsize < 1 ||
        cff->header.offsize > 4)
        _tt_abort("invalid offsize data");

    if (cff->header.major > 1 ||
        cff->header.minor > 0) {
        dpx_warning("%s: CFF version %u.%u not supported.",
                    CFF_DEBUG_STR, cff->header.major, cff->header.minor);
        cff_close(cff);
        return NULL;
    }

    cff_seek_set(cff, (cff->header).hdr_size);

    /* Name INDEX */
    idx = cff_get_index(cff);
    if (n > idx->count - 1) {
        dpx_warning("%s: Invalid CFF fontset index number.", CFF_DEBUG_STR);
        cff_close(cff);
        return NULL;
    }

    cff->name = idx;

    cff->fontname = cff_get_name(cff);

    /* Top DICT INDEX */
    idx = cff_get_index(cff);
    if (n > idx->count - 1)
        _tt_abort("CFF Top DICT not exist...");
    cff->topdict = cff_dict_unpack(idx->data + idx->offset[n] - 1,
                                   idx->data + idx->offset[n + 1] - 1);
    if (!cff->topdict)
        _tt_abort("Parsing CFF Top DICT data failed...");
    cff_release_index(idx);

    if (cff_dict_known(cff->topdict, "CharstringType") &&
        cff_dict_get(cff->topdict, "CharstringType", 0) != 2) {
        dpx_warning("Only Type 2 Charstrings supported...");
        cff_close(cff);
        return NULL;
    }

    if (cff_dict_known(cff->topdict, "SyntheticBase")) {
        dpx_warning("CFF Synthetic font not supported.");
        cff_close(cff);
        return NULL;
    }

    /* String INDEX */
    cff->string = cff_get_index(cff);

    /* offset to GSubr */
    cff->gsubr_offset = cff_tell(cff) - offset;

    /* Number of glyphs */
    offset = cff_dict_get(cff->topdict, "CharStrings", 0);
    cff_seek_set(cff, offset);
    cff->num_glyphs = tt_get_unsigned_pair(cff->handle);

    /* Check for font type */
    if (cff_dict_known(cff->topdict, "ROS")) {
        cff->flag |= FONTTYPE_CIDFONT;
    } else {
        cff->flag |= FONTTYPE_FONT;
    }

    /* Check for encoding */
    if (cff_dict_known(cff->topdict, "Encoding")) {
        offset = cff_dict_get(cff->topdict, "Encoding", 0);
        if (offset == 0) { /* predefined */
            cff->flag |= ENCODING_STANDARD;
        } else if (offset == 1) {
            cff->flag |= ENCODING_EXPERT;
        }
    } else {
        cff->flag |= ENCODING_STANDARD;
    }

    /* Check for charset */
    if (cff_dict_known(cff->topdict, "charset")) {
        offset = cff_dict_get(cff->topdict, "charset", 0);
        if (offset == 0) { /* predefined */
            cff->flag |= CHARSETS_ISOADOBE;
        } else if (offset == 1) {
            cff->flag |= CHARSETS_EXPERT;
        } else if (offset == 2) {
            cff->flag |= CHARSETS_EXPSUB;
        }
    } else {
        cff->flag |= CHARSETS_ISOADOBE;
    }

    cff_seek_set(cff, cff->gsubr_offset); /* seek back to GSubr */

    return cff;
}

void
cff_close (cff_font *cff)
{
    card16 i;

    if (cff) {
        free(cff->fontname);
        if (cff->name) cff_release_index(cff->name);
        if (cff->topdict) cff_release_dict(cff->topdict);
        if (cff->string) cff_release_index(cff->string);
        if (cff->gsubr) cff_release_index(cff->gsubr);
        if (cff->encoding) cff_release_encoding(cff->encoding);
        if (cff->charsets) cff_release_charsets(cff->charsets);
        if (cff->fdselect) cff_release_fdselect(cff->fdselect);
        if (cff->cstrings) cff_release_index(cff->cstrings);
        if (cff->fdarray) {
            for (i=0;i<cff->num_fds;i++) {
                if (cff->fdarray[i]) cff_release_dict(cff->fdarray[i]);
            }
            free(cff->fdarray);
        }
        if (cff->private) {
            for (i=0;i<cff->num_fds;i++) {
                if (cff->private[i]) cff_release_dict(cff->private[i]);
            }
            free(cff->private);
        }
        if (cff->subrs) {
            for (i=0;i<cff->num_fds;i++) {
                if (cff->subrs[i]) cff_release_index(cff->subrs[i]);
            }
            free(cff->subrs);
        }
        if (cff->_string)
            cff_release_index(cff->_string);

        free(cff);
    }

    return;
}

char *
cff_get_name (cff_font *cff)
{
    char      *fontname;
    l_offset   len;
    cff_index *idx;

    idx = cff->name;
    len = idx->offset[cff->index + 1] - idx->offset[cff->index];
    fontname = NEW(len + 1, char);
    memcpy(fontname, idx->data + idx->offset[cff->index] - 1, len);
    fontname[len] = '\0';

    return fontname;
}

int
cff_set_name (cff_font *cff, char *name)
{
    cff_index *idx;

    if (strlen(name) > 127)
        _tt_abort("FontName string length too large...");

    if (cff->name)
        cff_release_index(cff->name);

    cff->name = idx = NEW(1, cff_index);
    idx->count   = 1;
    idx->offsize = 1;
    idx->offset  = NEW(2, l_offset);
    (idx->offset)[0] = 1;
    (idx->offset)[1] = strlen(name) + 1;
    idx->data = NEW(strlen(name), card8);
    memmove(idx->data, name, strlen(name)); /* no trailing '\0' */

    return 5 + strlen(name);
}

int
cff_put_header (cff_font *cff, card8 *dest, int destlen)
{
    if (destlen < 4)
        _tt_abort("Not enough space available...");

    *(dest++) = cff->header.major;
    *(dest++) = cff->header.minor;
    *(dest++) = 4; /* Additional data in between header and
                    * Name INDEX ignored.
                    */
    /* We will set all offset (0) to four-byte integer. */
    *(dest++) = 4;
    cff->header.offsize = 4;

    return 4;
}

/* Only read header part but not body */
cff_index *
cff_get_index_header (cff_font *cff)
{
    cff_index *idx;
    card16     i, count;

    idx = NEW(1, cff_index);

    idx->count = count = tt_get_unsigned_pair(cff->handle);
    if (count > 0) {
        idx->offsize = tt_get_unsigned_byte(cff->handle);
        if (idx->offsize < 1 || idx->offsize > 4)
            _tt_abort("invalid offsize data");

        idx->offset = NEW(count+1, l_offset);
        for (i=0;i<count;i++) {
            (idx->offset)[i] = get_offset(cff->handle, idx->offsize);
        }
        if (count == 0xFFFF)
            cff_seek(cff, cff_tell(cff) + idx->offsize);
        else
            (idx->offset)[i] = get_offset(cff->handle, idx->offsize);

        if (idx->offset[0] != 1)
            _tt_abort("cff_get_index(): invalid index data");

        idx->data = NULL;
    } else {
        idx->offsize = 0;
        idx->offset = NULL;
        idx->data = NULL;
    }

    return idx;
}

cff_index *
cff_get_index (cff_font *cff)
{
    cff_index *idx;
    card16     i, count;
    int        length, nb_read, offset;

    idx = NEW(1, cff_index);

    idx->count = count = tt_get_unsigned_pair(cff->handle);
    if (count > 0) {
        idx->offsize = tt_get_unsigned_byte(cff->handle);
        if (idx->offsize < 1 || idx->offsize > 4)
            _tt_abort("invalid offsize data");

        idx->offset = NEW(count + 1, l_offset);
        for (i = 0 ; i < count + 1; i++) {
            idx->offset[i] = get_offset(cff->handle, idx->offsize);
        }

        if (idx->offset[0] != 1)
            _tt_abort("Invalid CFF Index offset data");

        length = idx->offset[count] - idx->offset[0];

        idx->data = NEW(length, card8);
        offset    = 0;
        while (length > 0) {
            nb_read = cff_read_data(idx->data + offset, length, cff);
            offset += nb_read;
            length -= nb_read;
        }
    } else {
        idx->offsize = 0;
        idx->offset  = NULL;
        idx->data    = NULL;
    }

    return idx;
}

int
cff_pack_index (cff_index *idx, card8 *dest, int destlen)
{
    int     len = 0;
    size_t  datalen;
    card16  i;

    if (idx->count < 1) {
        if (destlen < 2)
            _tt_abort("Not enough space available...");
        memset(dest, 0, 2);
        return 2;
    }

    len     = cff_index_size(idx);
    datalen = idx->offset[idx->count] - 1;

    if (destlen < len)
        _tt_abort("Not enough space available...");

    *(dest++) = (idx->count >> 8) & 0xff;
    *(dest++) = idx->count & 0xff;

    if (datalen < 0xffUL) {
        idx->offsize = 1;
        *(dest++)    = 1;
        for (i = 0; i <= idx->count; i++) {
            *(dest++) = (card8) (idx->offset[i] & 0xff);
        }
    } else if (datalen < 0xffffUL) {
        idx->offsize = 2;
        *(dest++)    = 2;
        for (i = 0; i <= idx->count; i++) {
            *(dest++) = (card8) ((idx->offset[i] >> 8) & 0xff);
            *(dest++) = (card8) ( idx->offset[i] & 0xff);
        }
    } else if (datalen < 0xffffffUL) {
        idx->offsize = 3;
        *(dest++)    = 3;
        for (i = 0; i <= idx->count; i++) {
            *(dest++) = (card8)((idx->offset[i] >> 16) & 0xff);
            *(dest++) = (card8)((idx->offset[i] >> 8) & 0xff);
            *(dest++) = (card8)(idx->offset[i] & 0xff);
        }
    } else {
        idx->offsize = 4;
        *(dest++)    = 4;
        for (i = 0; i <= idx->count; i++) {
            *(dest++) = (card8)((idx->offset[i] >> 24) & 0xff);
            *(dest++) = (card8)((idx->offset[i] >> 16) & 0xff);
            *(dest++) = (card8)((idx->offset[i] >> 8) & 0xff);
            *(dest++) = (card8)(idx->offset[i] & 0xff);
        }
    }

    memmove(dest, idx->data, idx->offset[idx->count] - 1);

    return len;
}

int
cff_index_size (cff_index *idx)
{
    if (idx->count > 0) {
        l_offset datalen;

        datalen = idx->offset[idx->count] - 1;
        if (datalen < 0xffUL) {
            idx->offsize = 1;
        } else if (datalen < 0xffffUL) {
            idx->offsize = 2;
        } else if (datalen < 0xffffffUL) {
            idx->offsize = 3;
        } else {
            idx->offsize = 4;
        }
        return (3 + (idx->offsize)*(idx->count + 1) + datalen);
    } else {
        return 2;
    }
}


cff_index *cff_new_index (card16 count)
{
    cff_index *idx;

    idx = NEW(1, cff_index);
    idx->count = count;
    idx->offsize = 0;

    if (count > 0) {
        idx->offset = NEW(count + 1, l_offset);
        (idx->offset)[0] = 1;
    } else {
        idx->offset = NULL;
    }
    idx->data = NULL;

    return idx;
}

void cff_release_index (cff_index *idx)
{
    if (idx) {
        if (idx->data) {
            free(idx->data);
        }
        if (idx->offset) {
            free(idx->offset);
        }
        free(idx);
    }
}

/* Strings */
char *cff_get_string (cff_font *cff, s_SID id)
{
    char *result = NULL;
    int len;

    if (id < CFF_STDSTR_MAX) {
        len = strlen(cff_stdstr[id]);
        result = NEW(len+1, char);
        memcpy(result, cff_stdstr[id], len);
        result[len] = '\0';
    } else if (cff && cff->string) {
        cff_index *strings = cff->string;
        id -= CFF_STDSTR_MAX;
        if (id < strings->count) {
            len = (strings->offset)[id+1] - (strings->offset)[id];
            result = NEW(len + 1, char);
            memmove(result, strings->data + (strings->offset)[id] - 1, len);
            result[len] = '\0';
        }
    }

    return result;
}

int cff_get_sid (cff_font *cff, const char *str)
{
    card16 i;

    if (!cff || !str)
        return -1;

    /* I search String INDEX first. */
    if (cff && cff->string) {
        cff_index *idx = cff->string;
        for (i = 0; i < idx->count; i++) {
            if (strlen(str) == (idx->offset)[i+1] - (idx->offset)[i] &&
                !memcmp(str, (idx->data)+(idx->offset)[i]-1, strlen(str)))
                return (i + CFF_STDSTR_MAX);
        }
    }

    for (i = 0; i < CFF_STDSTR_MAX; i++) {
        if (streq_ptr(str, cff_stdstr[i]))
            return i;
    }

    return -1;
}

int cff_get_seac_sid (cff_font *cff, const char *str)
{
    card16 i;

    if (!cff || !str)
        return -1;

    for (i = 0; i < CFF_STDSTR_MAX; i++) {
        if (streq_ptr(str, cff_stdstr[i]))
            return i;
    }

    return -1;
}

static int cff_match_string (cff_font *cff, const char *str, s_SID sid)
{
    card16 i;

    if (sid < CFF_STDSTR_MAX) {
        return ((streq_ptr(str, cff_stdstr[sid])) ? 1 : 0);
    } else {
        i = sid - CFF_STDSTR_MAX;
        if (cff == NULL || cff->string == NULL || i >= cff->string->count)
            _tt_abort("Invalid SID");
        if (strlen(str) == (cff->string->offset)[i+1] - (cff->string->offset)[i])
            return (!memcmp(str,
                            (cff->string->data)+(cff->string->offset)[i]-1,
                            strlen(str))) ? 1 : 0;
    }

    return 0;
}

void cff_update_string (cff_font *cff)
{
    if (cff == NULL)
        _tt_abort("CFF font not opened.");

    if (cff->string)
        cff_release_index(cff->string);
    cff->string  = cff->_string;
    cff->_string = NULL;
}

s_SID cff_add_string (cff_font *cff, const char *str, int unique)
/* Setting unique == 1 eliminates redundant or predefined strings. */
{
    card16 idx;
    cff_index *strings;
    l_offset offset, size;
    size_t len = strlen(str);

    if (cff == NULL)
        _tt_abort("CFF font not opened.");

    if (cff->_string == NULL)
        cff->_string = cff_new_index(0);
    strings = cff->_string;

    if (unique) {
        /* TODO: do binary search to speed things up */
        for (idx = 0; idx < CFF_STDSTR_MAX; idx++) {
            if (streq_ptr(cff_stdstr[idx], str))
                return idx;
        }
        for (idx = 0; idx < strings->count; idx++) {
            size   = strings->offset[idx+1] - strings->offset[idx];
            offset = strings->offset[idx];
            if (size == len && !memcmp(strings->data+offset-1, str, len))
                return (idx + CFF_STDSTR_MAX);
        }
    }

    offset = (strings->count > 0) ? strings->offset[strings->count] : 1;
    strings->offset = RENEW(strings->offset, strings->count+2, l_offset);
    if (strings->count == 0)
        strings->offset[0] = 1;
    idx = strings->count;
    strings->count += 1;
    strings->offset[strings->count] = offset + len;
    strings->data = RENEW(strings->data, offset+len-1, card8);
    memcpy(strings->data+offset-1, str, len);

    return (idx + CFF_STDSTR_MAX);
}

/*
 * Encoding and Charset
 *
 *  Encoding and Charset arrays always begin with GID = 1.
 */
int cff_read_encoding (cff_font *cff)
{
    cff_encoding *encoding;
    int offset, length;
    card8 i;

    if (cff->topdict == NULL) {
        _tt_abort("Top DICT data not found");
    }

    if (!cff_dict_known(cff->topdict, "Encoding")) {
        cff->flag |= ENCODING_STANDARD;
        cff->encoding = NULL;
        return 0;
    }

    offset = cff_dict_get(cff->topdict, "Encoding", 0);
    if (offset == 0) { /* predefined */
        cff->flag |= ENCODING_STANDARD;
        cff->encoding = NULL;
        return 0;
    } else if (offset == 1) {
        cff->flag |= ENCODING_EXPERT;
        cff->encoding = NULL;
        return 0;
    }

    cff_seek_set(cff, offset);
    cff->encoding = encoding = NEW(1, cff_encoding);
    encoding->format = tt_get_unsigned_byte(cff->handle);
    length = 1;

    switch (encoding->format & (~0x80)) {
    case 0:
        encoding->num_entries = tt_get_unsigned_byte(cff->handle);
        (encoding->data).codes = NEW(encoding->num_entries, card8);
        for (i=0;i<(encoding->num_entries);i++) {
            (encoding->data).codes[i] = tt_get_unsigned_byte(cff->handle);
        }
        length += encoding->num_entries + 1;
        break;
    case 1:
    {
        cff_range1 *ranges;
        encoding->num_entries = tt_get_unsigned_byte(cff->handle);
        encoding->data.range1 = ranges
            = NEW(encoding->num_entries, cff_range1);
        for (i=0;i<(encoding->num_entries);i++) {
            ranges[i].first = tt_get_unsigned_byte(cff->handle);
            ranges[i].n_left = tt_get_unsigned_byte(cff->handle);
        }
        length += (encoding->num_entries) * 2 + 1;
    }
    break;
    default:
        free(encoding);
        _tt_abort("Unknown Encoding format");
        break;
    }

    /* Supplementary data */
    if ((encoding->format) & 0x80) {
        cff_map *map;
        encoding->num_supps = tt_get_unsigned_byte(cff->handle);
        encoding->supp = map = NEW(encoding->num_supps, cff_map);
        for (i=0;i<(encoding->num_supps);i++) {
            map[i].code = tt_get_unsigned_byte(cff->handle);
            map[i].glyph = tt_get_unsigned_pair(cff->handle); /* SID */
        }
        length += (encoding->num_supps) * 3 + 1;
    } else {
        encoding->num_supps = 0;
        encoding->supp = NULL;
    }

    return length;
}

int cff_pack_encoding (cff_font *cff, card8 *dest, int destlen)
{
    int len = 0;
    cff_encoding *encoding;
    card16 i;

    if (cff->flag & HAVE_STANDARD_ENCODING || cff->encoding == NULL)
        return 0;

    if (destlen < 2)
        _tt_abort("in cff_pack_encoding(): Buffer overflow");

    encoding = cff->encoding;

    dest[len++] = encoding->format;
    dest[len++] = encoding->num_entries;
    switch (encoding->format & (~0x80)) {
    case 0:
        if (destlen < len + encoding->num_entries)
            _tt_abort("in cff_pack_encoding(): Buffer overflow");
        for (i=0;i<(encoding->num_entries);i++) {
            dest[len++] = (encoding->data).codes[i];
        }
        break;
    case 1:
    {
        if (destlen < len + (encoding->num_entries)*2)
            _tt_abort("in cff_pack_encoding(): Buffer overflow");
        for (i=0;i<(encoding->num_entries);i++) {
            dest[len++] = (encoding->data).range1[i].first & 0xff;
            dest[len++] = (encoding->data).range1[i].n_left;
        }
    }
    break;
    default:
        _tt_abort("Unknown Encoding format");
        break;
    }

    if ((encoding->format) & 0x80) {
        if (destlen < len + (encoding->num_supps)*3 + 1)
            _tt_abort("in cff_pack_encoding(): Buffer overflow");
        dest[len++] = encoding->num_supps;
        for (i=0;i<(encoding->num_supps);i++) {
            dest[len++] = (encoding->supp)[i].code;
            dest[len++] = ((encoding->supp)[i].glyph >> 8) & 0xff;
            dest[len++] = (encoding->supp)[i].glyph & 0xff;
        }
    }

    return len;
}

/* input: code, output: glyph index */
card16 cff_encoding_lookup (cff_font *cff, card8 code)
{
    card16 gid = 0;
    cff_encoding *encoding;
    card16 i;

    if (cff->flag & (ENCODING_STANDARD|ENCODING_EXPERT)) {
        _tt_abort("Predefined CFF encoding not supported yet");
    } else if (cff->encoding == NULL) {
        _tt_abort("Encoding data not available");
    }

    encoding = cff->encoding;

    gid = 0;
    switch (encoding->format & (~0x80)) {
    case 0:
        for (i = 0; i < encoding->num_entries; i++) {
            if (code == (encoding->data).codes[i]) {
                gid = i + 1;
                break;
            }
        }
        break;
    case 1:
        for (i = 0; i < encoding->num_entries; i++) {
            if (code >= (encoding->data).range1[i].first &&
                code <= (encoding->data).range1[i].first + (encoding->data).range1[i].n_left) {
                gid += code - (encoding->data).range1[i].first + 1;
                break;
            }
            gid += (encoding->data).range1[i].n_left + 1;
        }
        if (i == encoding->num_entries)
            gid = 0;
        break;
    default:
        _tt_abort("Unknown Encoding format.");
    }

    /* Supplementary data */
    if (gid == 0 && ((encoding->format) & 0x80)) {
        cff_map *map;
        if (!encoding->supp)
            _tt_abort("No CFF supplementary encoding data read.");
        map = encoding->supp;
        for (i=0;i<(encoding->num_supps);i++) {
            if (code == map[i].code) {
                gid = cff_charsets_lookup(cff, map[i].glyph);
                break;
            }
        }
    }

    return gid;
}

void cff_release_encoding (cff_encoding *encoding)
{
    if (encoding) {
        switch (encoding->format & (~0x80)) {
        case 0:
            free(encoding->data.codes);
            break;
        case 1:
            free(encoding->data.range1);
            break;
        default:
            _tt_abort("Unknown Encoding format.");
        }
        if (encoding->format & 0x80) {
            free(encoding->supp);
        }
        free(encoding);
    }
}

int cff_read_charsets (cff_font *cff)
{
    cff_charsets *charset;
    int offset, length;
    card16 count, i;

    if (cff->topdict == NULL)
        _tt_abort("Top DICT not available");

    if (!cff_dict_known(cff->topdict, "charset")) {
        cff->flag |= CHARSETS_ISOADOBE;
        cff->charsets = NULL;
        return 0;
    }

    offset = cff_dict_get(cff->topdict, "charset", 0);

    if (offset == 0) { /* predefined */
        cff->flag |= CHARSETS_ISOADOBE;
        cff->charsets = NULL;
        return 0;
    } else if (offset == 1) {
        cff->flag |= CHARSETS_EXPERT;
        cff->charsets = NULL;
        return 0;
    } else if (offset == 2) {
        cff->flag |= CHARSETS_EXPSUB;
        cff->charsets = NULL;
        return 0;
    }

    cff_seek_set(cff, offset);
    cff->charsets = charset = NEW(1, cff_charsets);
    charset->format = tt_get_unsigned_byte(cff->handle);
    charset->num_entries = 0;

    count = cff->num_glyphs - 1;
    length = 1;

    /* Not sure. Not well documented. */
    switch (charset->format) {
    case 0:
        charset->num_entries = cff->num_glyphs - 1; /* no .notdef */
        charset->data.glyphs = NEW(charset->num_entries, s_SID);
        length += (charset->num_entries) * 2;
        for (i=0;i<(charset->num_entries);i++) {
            charset->data.glyphs[i] = tt_get_unsigned_pair(cff->handle);
        }
        count = 0;
        break;
    case 1:
    {
        cff_range1 *ranges = NULL;
        while (count > 0 && charset->num_entries < cff->num_glyphs) {
            ranges = RENEW(ranges, charset->num_entries + 1, cff_range1);
            ranges[charset->num_entries].first = tt_get_unsigned_pair(cff->handle);
            ranges[charset->num_entries].n_left = tt_get_unsigned_byte(cff->handle);
            count -= ranges[charset->num_entries].n_left + 1; /* no-overrap */
            charset->num_entries += 1;
            charset->data.range1 = ranges;
        }
        length += (charset->num_entries) * 3;
    }
    break;
    case 2:
    {
        cff_range2 *ranges = NULL;
        while (count > 0 && charset->num_entries < cff->num_glyphs) {
            ranges = RENEW(ranges, charset->num_entries + 1, cff_range2);
            ranges[charset->num_entries].first = tt_get_unsigned_pair(cff->handle);
            ranges[charset->num_entries].n_left = tt_get_unsigned_pair(cff->handle);
            count -= ranges[charset->num_entries].n_left + 1; /* non-overrapping */
            charset->num_entries += 1;
        }
        charset->data.range2 = ranges;
        length += (charset->num_entries) * 4;
    }
    break;
    default:
        free(charset);
        _tt_abort("Unknown Charset format");
        break;
    }

    if (count > 0)
        _tt_abort("Charset data possibly broken");

    return length;
}

int cff_pack_charsets (cff_font *cff, card8 *dest, int destlen)
{
    int len = 0;
    card16 i;
    cff_charsets *charset;

    if (cff->flag & HAVE_STANDARD_CHARSETS || cff->charsets == NULL)
        return 0;

    if (destlen < 1)
        _tt_abort("in cff_pack_charsets(): Buffer overflow");

    charset = cff->charsets;

    dest[len++] = charset->format;
    switch (charset->format) {
    case 0:
        if (destlen < len + (charset->num_entries)*2)
            _tt_abort("in cff_pack_charsets(): Buffer overflow");
        for (i=0;i<(charset->num_entries);i++) {
            s_SID sid = (charset->data).glyphs[i]; /* or CID */
            dest[len++] = (sid >> 8) & 0xff;
            dest[len++] = sid & 0xff;
        }
        break;
    case 1:
    {
        if (destlen < len + (charset->num_entries)*3)
            _tt_abort("in cff_pack_charsets(): Buffer overflow");
        for (i=0;i<(charset->num_entries);i++) {
            dest[len++] = ((charset->data).range1[i].first >> 8) & 0xff;
            dest[len++] = (charset->data).range1[i].first & 0xff;
            dest[len++] = (charset->data).range1[i].n_left;
        }
    }
    break;
    case 2:
    {
        if (destlen < len + (charset->num_entries)*4)
            _tt_abort("in cff_pack_charsets(): Buffer overflow");
        for (i=0;i<(charset->num_entries);i++) {
            dest[len++] = ((charset->data).range2[i].first >> 8) & 0xff;
            dest[len++] = (charset->data).range2[i].first & 0xff;
            dest[len++] = ((charset->data).range2[i].n_left >> 8) & 0xff;
            dest[len++] = (charset->data).range2[i].n_left & 0xff;
        }
    }
    break;
    default:
        _tt_abort("Unknown Charset format");
        break;
    }

    return len;
}

char* cff_get_glyphname (cff_font *cff, card16 gid)
{
    s_SID sid;

    sid = cff_charsets_lookup_inverse(cff, gid);
    return cff_get_string(cff, sid);
}

card16 cff_glyph_lookup (cff_font *cff, const char *glyph)
{
    card16        gid;
    cff_charsets *charset;
    card16        i, n;

    if (cff->flag & (CHARSETS_ISOADOBE|CHARSETS_EXPERT|CHARSETS_EXPSUB)) {
        _tt_abort("Predefined CFF charsets not supported yet");
    } else if (cff->charsets == NULL) {
        _tt_abort("Charsets data not available");
    }

    /* .notdef always have glyph index 0 */
    if (!glyph || streq_ptr(glyph, ".notdef")) {
        return 0;
    }

    charset = cff->charsets;

    gid = 0;
    switch (charset->format) {
    case 0:
        for (i = 0; i < charset->num_entries; i++) {
            gid++;
            if (cff_match_string(cff, glyph, charset->data.glyphs[i])) {
                return gid;
            }
        }
        break;
    case 1:
        for (i = 0; i < charset->num_entries; i++) {
            for (n = 0;
                 n <= charset->data.range1[i].n_left; n++) {
                gid++;
                if (cff_match_string(cff, glyph,
                                     (s_SID)(charset->data.range1[i].first + n))) {
                    return gid;
                }
            }
        }
        break;
    case 2:
        for (i = 0; i <charset->num_entries; i++) {
            for (n = 0;
                 n <= charset->data.range2[i].n_left; n++) {
                gid++;
                if (cff_match_string(cff, glyph,
                                     (s_SID)(charset->data.range2[i].first + n))) {
                    return gid;
                }
            }
        }
        break;
    default:
        _tt_abort("Unknown Charset format");
    }

    return 0; /* not found, returns .notdef */
}

/* Input : SID or CID (16-bit unsigned int)
 * Output: glyph index
 */
card16
cff_charsets_lookup (cff_font *cff, card16 cid)
{
    if (cff->flag & (CHARSETS_ISOADOBE|CHARSETS_EXPERT|CHARSETS_EXPSUB)) {
        _tt_abort("Predefined CFF charsets not supported yet");
    } else if (cff->charsets == NULL) {
        _tt_abort("Charsets data not available");
    }

    return cff_charsets_lookup_gid(cff->charsets, cid);
}

card16 cff_charsets_lookup_gid (cff_charsets *charset, card16 cid)
{
    card16 gid = 0;
    card16 i;

    if (cid == 0) {
        return 0; /* GID 0 (.notdef) */
    }

    switch (charset->format) {
    case 0:
        for (i = 0; i <charset->num_entries; i++) {
            if (cid == charset->data.glyphs[i]) {
                gid = i + 1;
                return gid;
            }
        }
        break;
    case 1:
        for (i = 0; i < charset->num_entries; i++) {
            if (cid >= charset->data.range1[i].first &&
                cid <= charset->data.range1[i].first + charset->data.range1[i].n_left) {
                gid += cid - charset->data.range1[i].first + 1;
                return gid;
            }
            gid += charset->data.range1[i].n_left + 1;
        }
        break;
    case 2:
        for (i = 0; i < charset->num_entries; i++) {
            if (cid >= charset->data.range2[i].first &&
                cid <= charset->data.range2[i].first + charset->data.range2[i].n_left) {
                gid += cid - charset->data.range2[i].first + 1;
                return gid;
            }
            gid += charset->data.range2[i].n_left + 1;
        }
        break;
    default:
        _tt_abort("Unknown Charset format");
    }

    return 0; /* not found */
}

/* Input : GID
 * Output: SID/CID (card16)
 */
card16
cff_charsets_lookup_inverse (cff_font *cff, card16 gid)
{
    if (cff->flag & (CHARSETS_ISOADOBE|CHARSETS_EXPERT|CHARSETS_EXPSUB)) {
        _tt_abort("Predefined CFF charsets not supported yet");
    } else if (cff->charsets == NULL) {
        _tt_abort("Charsets data not available");
    }

    if (gid == 0) {
        return 0;  /* .notdef */
    }

    return cff_charsets_lookup_cid(cff->charsets, gid);
}

card16
cff_charsets_lookup_cid(cff_charsets *charset, card16 gid)
{
    card16 sid = 0;
    card16 i;

    switch (charset->format) {
    case 0:
        if (gid - 1 >= charset->num_entries)
            _tt_abort("Invalid GID.");
        sid = charset->data.glyphs[gid - 1];
        break;
    case 1:
        for (i = 0; i < charset->num_entries; i++) {
            if (gid <= charset->data.range1[i].n_left + 1) {
                sid = gid + charset->data.range1[i].first - 1;
                break;
            }
            gid -= charset->data.range1[i].n_left + 1;
        }
        if (i == charset->num_entries)
            _tt_abort("Invalid GID");
        break;
    case 2:
        for (i = 0; i < charset->num_entries; i++) {
            if (gid <= charset->data.range2[i].n_left + 1) {
                sid = gid + charset->data.range2[i].first - 1;
                break;
            }
            gid -= charset->data.range2[i].n_left + 1;
        }
        if (i == charset->num_entries)
            _tt_abort("Invalid GID");
        break;
    default:
        _tt_abort("Unknown Charset format");
    }

    return sid;
}

void
cff_release_charsets (cff_charsets *charset)
{
    if (charset) {
        switch (charset->format) {
        case 0:
            free(charset->data.glyphs);
            break;
        case 1:
            free(charset->data.range1);
            break;
        case 2:
            free(charset->data.range2);
            break;
        default:
            break;
        }
        free(charset);
    }
}

/* CID-Keyed font specific */
int cff_read_fdselect (cff_font *cff)
{
    cff_fdselect *fdsel;
    int offset, length;
    card16 i;

    if (cff->topdict == NULL)
        _tt_abort("Top DICT not available");

    if (!(cff->flag & FONTTYPE_CIDFONT))
        return 0;

    offset = cff_dict_get(cff->topdict, "FDSelect", 0);
    cff_seek_set(cff, offset);
    cff->fdselect = fdsel = NEW(1, cff_fdselect);
    fdsel->format = tt_get_unsigned_byte(cff->handle);

    length = 1;

    switch (fdsel->format) {
    case 0:
        fdsel->num_entries = cff->num_glyphs;
        (fdsel->data).fds = NEW(fdsel->num_entries, card8);
        for (i=0;i<(fdsel->num_entries);i++) {
            (fdsel->data).fds[i] = tt_get_unsigned_byte(cff->handle);
        }
        length += fdsel->num_entries;
        break;
    case 3:
    {
        cff_range3 *ranges;
        fdsel->num_entries = tt_get_unsigned_pair(cff->handle);
        fdsel->data.ranges = ranges = NEW(fdsel->num_entries, cff_range3);
        for (i=0;i<(fdsel->num_entries);i++) {
            ranges[i].first = tt_get_unsigned_pair(cff->handle);
            ranges[i].fd = tt_get_unsigned_byte(cff->handle);
        }
        if (ranges[0].first != 0)
            _tt_abort("Range not starting with 0.");
        if (cff->num_glyphs != tt_get_unsigned_pair(cff->handle))
            _tt_abort("Sentinel value mismatched with number of glyphs.");
        length += (fdsel->num_entries) * 3 + 4;
    }
    break;
    default:
        free(fdsel);
        _tt_abort("Unknown FDSelect format.");
        break;
    }

    return length;
}

int cff_pack_fdselect (cff_font *cff, card8 *dest, int destlen)
{
    cff_fdselect *fdsel;
    int len = 0;
    card16 i;

    if (cff->fdselect == NULL)
        return 0;

    if (destlen < 1)
        _tt_abort("in cff_pack_fdselect(): Buffur overflow");

    fdsel = cff->fdselect;

    dest[len++] = fdsel->format;
    switch (fdsel->format) {
    case 0:
        if (fdsel->num_entries != cff->num_glyphs)
            _tt_abort("in cff_pack_fdselect(): Invalid data");
        if (destlen < len + fdsel->num_entries)
            _tt_abort("in cff_pack_fdselect(): Buffer overflow");
        for (i=0;i<fdsel->num_entries;i++) {
            dest[len++] = (fdsel->data).fds[i];
        }
        break;
    case 3:
    {
        if (destlen < len + 2)
            _tt_abort("in cff_pack_fdselect(): Buffer overflow");
        len += 2;
        for (i=0;i<(fdsel->num_entries);i++) {
            if (destlen < len + 3)
                _tt_abort("in cff_pack_fdselect(): Buffer overflow");
            dest[len++] = ((fdsel->data).ranges[i].first >> 8) & 0xff;
            dest[len++] = (fdsel->data).ranges[i].first & 0xff;
            dest[len++] = (fdsel->data).ranges[i].fd;
        }
        if (destlen < len + 2)
            _tt_abort("in cff_pack_fdselect(): Buffer overflow");
        dest[len++]  = (cff->num_glyphs >> 8) & 0xff;
        dest[len++]  = cff->num_glyphs & 0xff;
        dest[1] = ((len/3 - 1) >> 8) & 0xff;
        dest[2] = (len/3 - 1) & 0xff;
    }
    break;
    default:
        _tt_abort("Unknown FDSelect format.");
        break;
    }

    return len;
}

void cff_release_fdselect (cff_fdselect *fdselect)
{
    if (fdselect) {
        if (fdselect->format == 0) {
            free(fdselect->data.fds);
        } else if (fdselect->format == 3) {
            free(fdselect->data.ranges);
        }
        free(fdselect);
    }
}

card8 cff_fdselect_lookup (cff_font *cff, card16 gid)
{
    card8 fd = 0xff;
    cff_fdselect *fdsel;

    if (cff->fdselect == NULL)
        _tt_abort("in cff_fdselect_lookup(): FDSelect not available");

    fdsel = cff->fdselect;

    if (gid >= cff->num_glyphs)
        _tt_abort("in cff_fdselect_lookup(): Invalid glyph index");

    switch (fdsel->format) {
    case 0:
        fd = fdsel->data.fds[gid];
        break;
    case 3:
    {
        if (gid == 0) {
            fd = (fdsel->data).ranges[0].fd;
        } else {
            card16 i;
            for (i=1;i<(fdsel->num_entries);i++) {
                if (gid < (fdsel->data).ranges[i].first)
                    break;
            }
            fd = (fdsel->data).ranges[i-1].fd;
        }
    }
    break;
    default:
        _tt_abort("in cff_fdselect_lookup(): Invalid FDSelect format");
        break;
    }

    if (fd >= cff->num_fds)
        _tt_abort("in cff_fdselect_lookup(): Invalid Font DICT index");

    return fd;
}

int cff_read_subrs (cff_font *cff)
{
    int len = 0;
    int offset;
    int i;

    if ((cff->flag & FONTTYPE_CIDFONT) && cff->fdarray == NULL) {
        cff_read_fdarray(cff);
    }

    if (cff->private == NULL)
        cff_read_private(cff);

    if (cff->gsubr == NULL) {
        cff_seek_set(cff, cff->gsubr_offset);
        cff->gsubr = cff_get_index(cff);
    }

    cff->subrs = NEW(cff->num_fds, cff_index *);
    if (cff->flag & FONTTYPE_CIDFONT) {
        for (i=0;i<cff->num_fds;i++) {
            if (cff->private[i] == NULL ||
                !cff_dict_known(cff->private[i], "Subrs")) {
                (cff->subrs)[i] = NULL;
            } else {
                offset = cff_dict_get(cff->fdarray[i], "Private", 1);
                offset += cff_dict_get(cff->private[i], "Subrs", 0);
                cff_seek_set(cff, offset);
                (cff->subrs)[i] = cff_get_index(cff);
                len += cff_index_size((cff->subrs)[i]);
            }
        }
    } else {
        if (cff->private[0] == NULL ||
            !cff_dict_known(cff->private[0], "Subrs")) {
            (cff->subrs)[0] = NULL;
        } else {
            offset = cff_dict_get(cff->topdict, "Private", 1);
            offset += cff_dict_get(cff->private[0], "Subrs", 0);
            cff_seek_set(cff, offset);
            (cff->subrs)[0] = cff_get_index(cff);
            len += cff_index_size((cff->subrs)[0]);
        }
    }

    return len;
}

int cff_read_fdarray (cff_font *cff)
{
    int len = 0;
    cff_index *idx;
    int offset, size;
    card16 i;

    if (cff->topdict == NULL)
        _tt_abort("in cff_read_fdarray(): Top DICT not found");

    if (!(cff->flag & FONTTYPE_CIDFONT))
        return 0;

    /* must exist */
    offset = cff_dict_get(cff->topdict, "FDArray", 0);
    cff_seek_set(cff, offset);
    idx = cff_get_index(cff);
    cff->num_fds = (card8)idx->count;
    cff->fdarray = NEW(idx->count, cff_dict *);
    for (i=0;i<idx->count;i++) {
        card8 *data = idx->data + (idx->offset)[i] - 1;
        size = (idx->offset)[i+1] - (idx->offset)[i];
        if (size > 0) {
            (cff->fdarray)[i] = cff_dict_unpack(data, data+size);
        } else {
            (cff->fdarray)[i] = NULL;
        }
    }
    len = cff_index_size(idx);
    cff_release_index(idx);

    return len;
}

int cff_read_private (cff_font *cff)
{
    int len = 0;
    card8 *data;
    int offset, size;

    if (cff->flag & FONTTYPE_CIDFONT) {
        int i;

        if (cff->fdarray == NULL)
            cff_read_fdarray(cff);

        cff->private = NEW(cff->num_fds, cff_dict *);
        for (i=0;i<cff->num_fds;i++) {
            if (cff->fdarray[i] != NULL &&
                cff_dict_known(cff->fdarray[i], "Private") &&
                (size = cff_dict_get(cff->fdarray[i], "Private", 0))
                > 0) {
                offset = cff_dict_get(cff->fdarray[i], "Private", 1);
                cff_seek_set(cff, offset);
                data = NEW(size, card8);
                if (cff_read_data(data, size, cff) != size)
                    _tt_abort("reading file failed");
                (cff->private)[i] = cff_dict_unpack(data, data+size);
                free(data);
                len += size;
            } else {
                (cff->private)[i] = NULL;
            }
        }
    } else {
        cff->num_fds = 1;
        cff->private = NEW(1, cff_dict *);
        if (cff_dict_known(cff->topdict, "Private") &&
            (size = cff_dict_get(cff->topdict, "Private", 0)) > 0) {
            offset = cff_dict_get(cff->topdict, "Private", 1);
            cff_seek_set(cff, offset);
            data = NEW(size, card8);
            if (cff_read_data(data, size, cff) != size)
                _tt_abort("reading file failed");
            cff->private[0] = cff_dict_unpack(data, data+size);
            free(data);
            len += size;
        } else {
            (cff->private)[0] = NULL;
            len = 0;
        }
    }

    return len;
}
