// -*- related-file-name: "../include/efont/otf.hh" -*-

/* otf.{cc,hh} -- OpenType font basics
 *
 * Copyright (c) 2002-2010 Eddie Kohler
 *
 * 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.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <efont/otf.hh>
#include <lcdf/error.hh>
#include <lcdf/straccum.hh>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <efont/otfdata.hh>	// for ntohl()
#include <efont/otfname.hh>

#ifndef static_assert
#define static_assert(c) switch (c) case 0: case (c):
#endif

#define USHORT_AT(d)		(ntohs(*(const uint16_t *)(d)))
#define ULONG_AT(d)		(ntohl(*(const uint32_t *)(d)))
#define ULONG_AT2(d)		((USHORT_AT((d)) << 16) | USHORT_AT((d)+2))

namespace Efont { namespace OpenType {

Vector<PermString> debug_glyph_names;

Font::Font(const String &s, ErrorHandler *errh)
    : _str(s)
{
    _str.align(4);
    _error = parse_header(errh ? errh : ErrorHandler::silent_handler());
}

int
Font::parse_header(ErrorHandler *errh)
{
    // HEADER FORMAT:
    // Fixed	sfnt version
    // USHORT	numTables
    // USHORT	searchRange
    // USHORT	entrySelector
    // USHORT	rangeShift
    int len = length();
    const uint8_t *data = this->data();
    if (HEADER_SIZE > len)
	return errh->error("OTF too small for header"), -EFAULT;
    if ((data[0] != 'O' || data[1] != 'T' || data[2] != 'T' || data[3] != 'O')
	&& (data[0] != '\000' || data[1] != '\001'))
	return errh->error("not an OpenType font (bad magic number)"), -ERANGE;
    int ntables = USHORT_AT(data + 4);
    if (ntables == 0)
	return errh->error("OTF contains no tables"), -EINVAL;
    if (HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * ntables > len)
	return errh->error("OTF table directory out of range"), -EFAULT;

    // TABLE DIRECTORY ENTRY FORMAT:
    // ULONG	tag
    // ULONG	checksum
    // ULONG	offset
    // ULONG	length
    uint32_t last_tag = 0U;
    for (int i = 0; i < ntables; i++) {
	int loc = HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * i;
	uint32_t tag = ULONG_AT(data + loc);
	uint32_t offset = ULONG_AT(data + loc + 8);
	uint32_t length = ULONG_AT(data + loc + 12);
	if (tag <= last_tag)
	    return errh->error("tags out of order"), -EINVAL;
	if (offset + length > (uint32_t) len)
	    return errh->error("OTF data for '%s' out of range", Tag(tag).text().c_str()), -EFAULT;
	last_tag = tag;
    }

    return 0;
}

bool
Font::check_checksums(ErrorHandler *errh) const
{
    if (error() < 0)
	return false;
    int nt = ntables();
    bool ok = true;
    for (int i = 0; i < nt; i++) {
	const uint8_t *entry = data() + HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * i;
	String tbl = _str.substring(ULONG_AT(entry + 8), ULONG_AT(entry + 12));
	uint32_t sum = checksum(tbl);
	if (ULONG_AT(entry) == 0x68656164	// 'head'
	    && tbl.length() >= 12)
	    sum -= ULONG_AT(tbl.data() + 8);
	if (sum != ULONG_AT(entry + 4)) {
	    if (errh)
		errh->error("table '%s' checksum error: %x vs. %x", Tag(ULONG_AT(entry)).text().c_str(), sum, ULONG_AT(entry + 4));
	    ok = false;
	}
    }
    return ok;
}

int
Font::ntables() const
{
    if (error() < 0)
	return 0;
    else
	return USHORT_AT(data() + 4);
}

String
Font::table(Tag tag) const
{
    if (error() < 0)
	return String();
    const uint8_t *entry = tag.table_entry(data() + HEADER_SIZE, USHORT_AT(data() + 4), TABLE_DIR_ENTRY_SIZE);
    if (entry)
	return _str.substring(ULONG_AT(entry + 8), ULONG_AT(entry + 12));
    else
	return String();
}

bool
Font::has_table(Tag tag) const
{
    const uint8_t *entry = 0;
    if (error() >= 0)
	entry = tag.table_entry(data() + HEADER_SIZE, USHORT_AT(data() + 4), TABLE_DIR_ENTRY_SIZE);
    return entry != 0;
}

uint32_t
Font::table_checksum(Tag tag) const
{
    if (error() < 0)
	return 0;
    const uint8_t *entry = tag.table_entry(data() + HEADER_SIZE, USHORT_AT(data() + 4), TABLE_DIR_ENTRY_SIZE);
    if (entry)
	return ULONG_AT(entry + 4);
    else
	return 0;
}

Tag
Font::table_tag(int i) const
{
    if (error() < 0 || i < 0 || i >= ntables())
	return Tag();
    else
	return Tag(ULONG_AT(data() + HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * i));
}

uint32_t
Font::checksum(const uint8_t *begin, const uint8_t *end)
{
    uint32_t sum = 0;
    if (reinterpret_cast<uintptr_t>(begin) % 4)
	for (; begin + 3 < end; begin += 4)
	    sum += (begin[0] << 24) + (begin[1] << 16) + (begin[2] << 8) + begin[3];
    else
	for (; begin + 3 < end; begin += 4)
	    sum += ULONG_AT(begin);
    uint32_t leftover = 0;
    for (int i = 0; i < 4; i++)
	leftover = (leftover << 8) + (begin < end ? *begin++ : 0);
    return sum + leftover;
}

uint32_t
Font::checksum(const String &s)
{
    return checksum(reinterpret_cast<const uint8_t *>(s.begin()), reinterpret_cast<const uint8_t *>(s.end()));
}

namespace {
class TagCompar { public:
    TagCompar(const Vector<Tag> &tags) : _tags(tags) { }
    bool operator()(int a, int b) { return _tags[a] < _tags[b]; }
    const Vector<Tag> &_tags;
};
}

Font
Font::make(bool truetype, const Vector<Tag>& tags, const Vector<String>& data)
{
    StringAccum sa;

    // create offset table
    {
	union {
	    uint8_t c[HEADER_SIZE];
	    uint16_t s[HEADER_SIZE / 2];
	    uint32_t l[HEADER_SIZE / 4];
	} hdr;
	hdr.l[0] = (truetype ? htonl(0x00010000) : htonl(0x4F54544F));
	hdr.s[2] = htons(tags.size());
	int entrySelector;
	for (entrySelector = 0; (2 << entrySelector) <= tags.size(); entrySelector++)
	    /* nada */;
	hdr.s[3] = htons((1 << entrySelector) * 16);
	hdr.s[4] = htons(entrySelector);
	hdr.s[5] = htons((tags.size() - (1 << entrySelector)) * 16);
	sa.append(&hdr.c[0], HEADER_SIZE);
    }

    // sort tags
    Vector<int> permut;
    for (int i = 0; i < tags.size(); i++)
	permut.push_back(i);
    std::sort(permut.begin(), permut.end(), TagCompar(tags));

    // table listing
    uint32_t offset = HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * tags.size();
    for (int *tp = permut.begin(); tp < permut.end(); tp++) {
	union {
	    uint8_t c[TABLE_DIR_ENTRY_SIZE];
	    uint32_t l[TABLE_DIR_ENTRY_SIZE / 4];
	} tdir;
	tdir.l[0] = htonl(tags[*tp].value());

	// discount current checksum adjustment in head table
	uint32_t sum = checksum(data[*tp]);
	if (tags[*tp] == Tag("head") && data[*tp].length() >= 12) {
	    const uint8_t *x = data[*tp].udata() + 8;
	    sum -= (x[0] << 24) + (x[1] << 16) + (x[2] << 8) + x[3];
	}
	tdir.l[1] = htonl(sum);

	tdir.l[2] = htonl(offset);
	tdir.l[3] = htonl(data[*tp].length());
	sa.append(&tdir.c[0], TABLE_DIR_ENTRY_SIZE);
	offset += (data[*tp].length() + 3) & ~3;
    }

    // actual tables
    for (int *tp = permut.begin(); tp < permut.end(); tp++) {
	sa << data[*tp];
	while (sa.length() % 4)
	    sa << '\0';
    }

    // fix 'head' table
    for (int i = 0; i < tags.size(); i++) {
	char *thdr = sa.data() + HEADER_SIZE + TABLE_DIR_ENTRY_SIZE * i;
	if (ULONG_AT(thdr) == 0x68656164 && ULONG_AT(thdr + 12) >= 12) {
	    uint32_t offset = ULONG_AT(thdr + 8);
	    char *head = sa.data() + offset;
	    memset(head + 8, '\0', 4);
	    uint32_t allsum = checksum(reinterpret_cast<uint8_t *>(sa.data()), reinterpret_cast<uint8_t *>(sa.data() + sa.length()));
	    uint32_t *adj = reinterpret_cast<uint32_t *>(head + 8);
	    *adj = htonl(0xB1B0AFBA - allsum);
	}
    }

    return Font(sa.take_string());
}


/**************************
 * Tag                    *
 *                        *
 **************************/

Tag::Tag(const char *s)
    : _tag(0)
{
    if (!s)
	s = "";
    for (int i = 0; i < 4; i++)
	if (*s == 0)
	    _tag = (_tag << 8) | 0x20;
	else if (*s < 32 || *s > 126) { // don't care if s is signed
	    _tag = 0;
	    return;
	} else
	    _tag = (_tag << 8) | *s++;
    if (*s)
	_tag = 0;
}

Tag::Tag(const String &s)
    : _tag(0)
{
    if (s.length() <= 4) {
	const char *ss = s.data();
	for (int i = 0; i < s.length(); i++, ss++)
	    if (*ss < 32 || *ss > 126) {
		_tag = 0;
		return;
	    } else
		_tag = (_tag << 8) | *ss;
	for (int i = s.length(); i < 4; i++)
	    _tag = (_tag << 8) | 0x20;
    }
}

bool
Tag::valid() const
{
    uint32_t tag = _tag;
    for (int i = 0; i < 4; i++, tag >>= 8)
	if ((tag & 255) < 32 || (tag & 255) > 126)
	    return false;
    return true;
}

String
Tag::text() const
{
    StringAccum sa;
    uint32_t tag = _tag;
    for (int i = 0; i < 4; i++, tag = (tag << 8) | 0x20)
	if (tag != 0x20202020) {
	    uint8_t c = (tag >> 24) & 255;
	    if (c < 32 || c > 126)
		sa.snprintf(6, "\\%03o", c);
	    else
		sa << c;
	}
    return sa.take_string();
}

String
Tag::langsys_text(Tag script, Tag langsys)
{
    if (!langsys.null())
	return script.text() + "." + langsys.text();
    else
	return script.text();
}

const uint8_t *
Tag::table_entry(const uint8_t *table, int n, int entry_size) const
{
    assert(((uintptr_t)table & 1) == 0);
    int l = 0, r = n;
    while (l < r) {
	int m = l + (r - l) / 2;
	const uint8_t *entry = table + m * entry_size;
	uint32_t m_tag = ULONG_AT2(entry);
	if (_tag < m_tag)
	    r = m;
	else if (_tag == m_tag)
	    return entry;
	else
	    l = m + 1;
    }
    return 0;
}



/**************************
 * ScriptList             *
 *                        *
 **************************/

int
ScriptList::assign(const String &str, ErrorHandler *errh)
{
    _str = str;
    _str.align(4);
    int result = check_header(errh ? errh : ErrorHandler::silent_handler());
    if (result < 0)
	_str = String();
    return result;
}

int
ScriptList::check_header(ErrorHandler *errh)
{
    // HEADER FORMAT:
    // USHORT	scriptCount
    // 6bytes	scriptRecord[]
    int scriptCount;
    if (_str.length() < SCRIPTLIST_HEADERSIZE
	|| (scriptCount = USHORT_AT(_str.udata()),
	    _str.length() < SCRIPTLIST_HEADERSIZE + scriptCount*SCRIPT_RECSIZE))
	return errh->error("OTF ScriptList too short");

    // XXX check that scripts are sorted

    return 0;
}

int
ScriptList::script_offset(Tag script) const
{
    if (_str.length() == 0)
	return -1;
    const uint8_t *data = _str.udata();
    if (const uint8_t *entry = script.table_entry(data + SCRIPTLIST_HEADERSIZE, USHORT_AT(data), SCRIPT_RECSIZE))
	return USHORT_AT(entry + 4);
    else
	return 0;
}

int
ScriptList::check_script(Tag tag, int script_off, ErrorHandler *errh) const
{
    const uint8_t *data = _str.udata();
    int langSysCount;
    if (_str.length() < script_off + SCRIPT_HEADERSIZE
	|| (langSysCount = USHORT_AT(data + script_off + 2),
	    (_str.length() < script_off + SCRIPT_HEADERSIZE + langSysCount*LANGSYS_RECSIZE)))
	return (errh ? errh->error("OTF Script table for '%s' out of range", tag.text().c_str()) : -1);
    // XXX check that langsys are sorted
    return 0;
}

int
ScriptList::langsys_offset(Tag script, Tag langsys, ErrorHandler *errh) const
{
    int script_off = script_offset(script);
    if (script_off == 0) {
	script = Tag("DFLT");
	script_off = script_offset(script);
    }
    if (script_off <= 0)
	return script_off;

    // check script bounds
    if (check_script(script, script_off, errh) < 0)
	return -1;

    // search script table
    const uint8_t *data = _str.udata();
    int langSysCount = USHORT_AT(data + script_off + 2);
    if (const uint8_t *entry = langsys.table_entry(data + script_off + SCRIPT_HEADERSIZE, langSysCount, LANGSYS_RECSIZE))
	return script_off + USHORT_AT(entry + 4);

    // return default
    int defaultLangSys = USHORT_AT(data + script_off);
    if (defaultLangSys != 0)
	return script_off + defaultLangSys;
    else
	return 0;
}

int
ScriptList::language_systems(Vector<Tag> &script, Vector<Tag> &langsys, ErrorHandler *errh) const
{
    script.clear();
    langsys.clear();

    const uint8_t *data = _str.udata();
    int nscripts = USHORT_AT(data);
    for (int i = 0; i < nscripts; i++) {
	Tag script_tag(ULONG_AT2(data + SCRIPTLIST_HEADERSIZE + i*SCRIPT_RECSIZE));
	int script_off = USHORT_AT(data + SCRIPTLIST_HEADERSIZE + i*SCRIPT_RECSIZE + 4);
	if (check_script(script_tag, script_off, errh) < 0)
	    return -1;
	const uint8_t *script_table = data + script_off;
	if (USHORT_AT(script_table) != 0) // default LangSys
	    script.push_back(script_tag), langsys.push_back(Tag());
	int nlangsys = USHORT_AT(script_table + 2);
	for (int j = 0; j < nlangsys; j++) {
	    Tag langsys_tag(ULONG_AT2(script_table + SCRIPT_HEADERSIZE + j*LANGSYS_RECSIZE));
	    script.push_back(script_tag), langsys.push_back(langsys_tag);
	}
    }

    return 0;
}

int
ScriptList::features(Tag script, Tag langsys, int &required_fid, Vector<int> &fids, ErrorHandler *errh, bool clear_fids) const
{
    required_fid = -1;
    if (clear_fids)
	fids.clear();

    int offset = langsys_offset(script, langsys);
    if (offset <= 0)
	return offset;

    // check langsys bounds
    const uint8_t *data = _str.udata();
    int featureCount;
    if (_str.length() < offset + LANGSYS_HEADERSIZE
	|| (featureCount = USHORT_AT(data + offset + 4),
	    (_str.length() < offset + LANGSYS_HEADERSIZE + featureCount*FEATURE_RECSIZE)))
	return (errh ? errh->error("OTF LangSys table for '%s/%s' out of range", script.text().c_str(), langsys.text().c_str()) : -1);

    // search langsys table
    int f = USHORT_AT(data + offset + 2);
    if (f != 0xFFFF)
	required_fid = f;
    data += offset + 6;
    for (int i = 0; i < featureCount; i++, data += FEATURE_RECSIZE)
	fids.push_back(USHORT_AT(data));

    return 0;
}



/**************************
 * FeatureList            *
 *                        *
 **************************/

int
FeatureList::assign(const String &str, ErrorHandler *errh)
{
    _str = str;
    _str.align(2);
    int result = check_header(errh ? errh : ErrorHandler::silent_handler());
    if (result < 0)
	_str = String();
    return result;
}

int
FeatureList::check_header(ErrorHandler *errh)
{
    int featureCount;
    if (_str.length() < FEATURELIST_HEADERSIZE
	|| (featureCount = USHORT_AT(_str.udata()),
	    _str.length() < FEATURELIST_HEADERSIZE + featureCount*FEATURE_RECSIZE))
	return errh->error("OTF FeatureList too short");
    return 0;
}

Tag
FeatureList::tag(int fid) const
{
    if (_str.length() == 0)
	return Tag();
    const uint8_t *data = _str.udata();
    int nfeatures = USHORT_AT(data);
    if (fid >= 0 && fid < nfeatures)
	return Tag(ULONG_AT2(data + FEATURELIST_HEADERSIZE + fid*FEATURE_RECSIZE));
    else
	return Tag();
}

String
FeatureList::params(int fid, int length, ErrorHandler *errh, bool old_style_offset) const
{
    if (_str.length() == 0 || length < 0)
	return String();
    if (errh == 0)
	errh = ErrorHandler::silent_handler();

    const uint8_t *data = _str.udata();
    int len = _str.length();
    int nfeatures = USHORT_AT(data);
    if (fid < 0 || fid >= nfeatures)
	return errh->error("OTF feature ID '%d' out of range", fid), String();
    int foff = USHORT_AT(data + FEATURELIST_HEADERSIZE + fid*FEATURE_RECSIZE + 4);
    if (len < foff + FEATURE_HEADERSIZE)
	return errh->error("OTF LookupList for feature ID '%d' too short", fid), String();
    int poff = USHORT_AT(data + foff);
    if (poff == 0)
	return String();
    if (!old_style_offset)
	poff += foff;
    if (len < poff + length)
	return errh->error("OTF feature parameters for feature ID '%d' out of range", fid), String();
    else
	return _str.substring(poff, length);
}

String
FeatureList::size_params(int fid, const Name &name, ErrorHandler *errh) const
{
    // implement 'size' checks from Read Roberts
    for (int i = 0; i < 2; i++) {
	String s = params(fid, 10, errh, i != 0);
	const uint8_t *data = s.udata();
	// errh->message("trying %d %d %d %d %d\n", USHORT_AT(data), USHORT_AT(data + 2), USHORT_AT(data + 4), USHORT_AT(data + 6), USHORT_AT(data + 8));
	if (USHORT_AT(data) == 0)		// design size == 0
	    continue;
	if (USHORT_AT(data + 2) == 0		// subfamily ID == 0
	    && USHORT_AT(data + 6) == 0		// range start == 0
	    && USHORT_AT(data + 8) == 0		// range end == 0
	    && USHORT_AT(data + 4) == 0)	// menu name ID == 0
	    return s;
	if (USHORT_AT(data + 6) >= USHORT_AT(data + 8) // range start >= range end
	    || USHORT_AT(data + 4) < 256	// menu name ID < 256
	    || USHORT_AT(data + 4) > 32767	// menu name ID > 32767
	    || !name.english_name(USHORT_AT(data + 4))) // menu name ID is a name ID defined by the font
	    continue;
	if (USHORT_AT(data) + 1 >= USHORT_AT(data + 6) // design size >= range start (with 1 dp grace)
	    && USHORT_AT(data) <= USHORT_AT(data + 8) + 1) // design size <= range end (with 1 dp grace)
	    return s;
	else if (i == 1				// old-style feature
		 && USHORT_AT(data + 8) <= 1440	// range end <= 144 point
		 && USHORT_AT(data) <= 1440) {	// design size <= 144 point
	    // some old fonts define a bogus feature with design size
	    // not in range (John Owens, Read Roberts)
	    if (errh)
		errh->warning("invalid 'size' feature: design size not in range");
	    return s;
	}
    }
    if (errh)
	errh->error("no valid 'size' feature data in the 'size' feature");
    return String();
}

int
FeatureList::find(Tag tag, const Vector<int> &fids) const
{
    if (fids.size() == 0 || _str.length() == 0)
	return -1;

    const uint8_t *data = _str.udata();
    int nfeatures = USHORT_AT(data);
    for (const int *fidp = fids.begin(); fidp != fids.end(); fidp++)
	if (*fidp >= 0 && *fidp < nfeatures) {
	    uint32_t ftag = ULONG_AT2(data + FEATURELIST_HEADERSIZE + (*fidp)*FEATURE_RECSIZE);
	    if (ftag == tag.value())
		return *fidp;
	}

    return -1;
}

void
FeatureList::filter(Vector<int> &fids, const Vector<Tag> &sorted_ftags) const
{
    if (_str.length() == 0)
	fids.clear();
    else {
	std::sort(fids.begin(), fids.end()); // sort fids

	int i = 0, j = 0;
	while (i < fids.size() && fids[i] < 0)
	    fids[i++] = 0x7FFFFFFF;

	// XXX check that feature list is in alphabetical order

	const uint8_t *data = _str.udata();
	int nfeatures = USHORT_AT(data);
	while (i < fids.size() && j < sorted_ftags.size() && fids[i] < nfeatures) {
	    uint32_t ftag = ULONG_AT2(data + FEATURELIST_HEADERSIZE + fids[i]*FEATURE_RECSIZE);
	    if (ftag < sorted_ftags[j].value()) { // not an interesting feature
		// replace featureID with a large number, remove later
		fids[i] = 0x7FFFFFFF;
		i++;
	    } else if (ftag == sorted_ftags[j].value()) // interesting feature
		i++;
	    else		// an interesting feature is not available
		j++;
	}

	fids.resize(i);		// remove remaining uninteresting features
	std::sort(fids.begin(), fids.end()); // resort, to move bad ones last
	while (fids.size() && fids.back() == 0x7FFFFFFF)
	    fids.pop_back();
    }
}

int
FeatureList::lookups(int fid, Vector<int> &results, ErrorHandler *errh, bool clear_results) const
{
    if (clear_results)
	results.clear();
    if (_str.length() == 0)
	return -1;
    if (errh == 0)
	errh = ErrorHandler::silent_handler();

    const uint8_t *data = _str.udata();
    int len = _str.length();
    int nfeatures = USHORT_AT(data);
    if (fid < 0 || fid >= nfeatures)
	return errh->error("OTF feature ID '%d' out of range", fid);
    int foff = USHORT_AT(data + FEATURELIST_HEADERSIZE + fid*FEATURE_RECSIZE + 4);
    int lookupCount;
    if (len < foff + FEATURE_HEADERSIZE
	|| (lookupCount = USHORT_AT(data + foff + 2),
	    len < foff + FEATURE_HEADERSIZE + lookupCount*LOOKUPLIST_RECSIZE))
	return errh->error("OTF LookupList for feature ID '%d' too short", fid);
    const uint8_t *ldata = data + foff + FEATURE_HEADERSIZE;
    for (int j = 0; j < lookupCount; j++, ldata += LOOKUPLIST_RECSIZE)
	results.push_back(USHORT_AT(ldata));

    return 0;
}

int
FeatureList::lookups(const Vector<int> &fids, Vector<int> &results, ErrorHandler *errh) const
{
    results.clear();
    if (_str.length() == 0)
	return -1;

    for (int i = 0; i < fids.size(); i++)
	if (lookups(fids[i], results, errh, false) < 0)
	    return -1;

    // sort results and remove duplicates
    std::sort(results.begin(), results.end());
    int *unique_result = std::unique(results.begin(), results.end());
    results.resize(unique_result - results.begin());
    return 0;
}

int
FeatureList::lookups(const Vector<int> &required_fids, const Vector<int> &fids, const Vector<Tag> &sorted_ftags, Vector<int> &results, ErrorHandler *errh) const
{
    Vector<int> fidsx(fids);
    filter(fidsx, sorted_ftags);
    for (int i = 0; i < required_fids.size(); i++)
	fidsx.push_back(required_fids[i]);
    return lookups(fidsx, results, errh);
}

int
FeatureList::lookups(int required_fid, const Vector<int> &fids, const Vector<Tag> &sorted_ftags, Vector<int> &results, ErrorHandler *errh) const
{
    Vector<int> fidsx(fids);
    filter(fidsx, sorted_ftags);
    if (required_fid >= 0)
	fidsx.push_back(required_fid);
    return lookups(fidsx, results, errh);
}

int
FeatureList::lookups(const ScriptList &script_list, Tag script, Tag langsys, const Vector<Tag> &sorted_ftags, Vector<int> &results, ErrorHandler *errh) const
{
    int required_fid;
    Vector<int> fids;
    int result = script_list.features(script, langsys, required_fid, fids, errh);
    if (result >= 0) {
	filter(fids, sorted_ftags);
	if (required_fid >= 0)
	    fids.push_back(required_fid);
	result = lookups(fids, results, errh);
    }
    return result;
}


/**************************
 * Coverage               *
 *                        *
 **************************/

Coverage::Coverage() throw ()
{
}

Coverage::Coverage(Glyph first, Glyph last) throw ()
{
    if (first <= last) {
	_str = String("\000\002\000\001\000\000\000\000\000\000", 10);
	uint8_t *data = _str.mutable_udata();
	data[4] = first >> 8;
	data[5] = first & 255;
	data[6] = last >> 8;
	data[7] = last & 255;
	_str.align(2);
    }
}

Coverage::Coverage(const String &str, ErrorHandler *errh, bool do_check) throw ()
    : _str(str)
{
    _str.align(2);
    if (do_check) {
	if (check(errh ? errh : ErrorHandler::silent_handler()) < 0)
	    _str = String();
    } else {			// check()'s shorten-string side effect
	const uint8_t *data = _str.udata();
	int count = USHORT_AT(data + 2);
	if (data[1] == T_LIST)
	    _str = _str.substring(0, HEADERSIZE + count*LIST_RECSIZE);
	else
	    _str = _str.substring(0, HEADERSIZE + count*RANGES_RECSIZE);
    }
}

int
Coverage::check(ErrorHandler *errh)
    // side effect: shorten string to cover coverage
{
    // HEADER FORMAT:
    // USHORT	coverageFormat
    // USHORT	glyphCount
    const uint8_t *data = _str.udata();
    if (_str.length() < HEADERSIZE)
	return errh->error("OTF coverage table too small for header");
    int coverageFormat = USHORT_AT(data);
    int count = USHORT_AT(data + 2);

    int len;
    switch (coverageFormat) {
      case T_LIST:
	len = HEADERSIZE + count*LIST_RECSIZE;
	if (_str.length() < len)
	    return errh->error("OTF coverage table too short (format 1)");
	// XXX don't check sorting
	break;
      case T_RANGES:
	len = HEADERSIZE + count*RANGES_RECSIZE;
	if (_str.length() < len)
	    return errh->error("OTF coverage table too short (format 2)");
	// XXX don't check sorting
	// XXX don't check startCoverageIndexes
	break;
      default:
	return errh->error("OTF coverage table has unknown format %d", coverageFormat);
    }

    _str = _str.substring(0, len);
    return 0;
}

int
Coverage::size() const throw ()
{
    if (_str.length() == 0)
	return -1;
    const uint8_t *data = _str.udata();
    if (data[1] == T_LIST)
	return (_str.length() - HEADERSIZE) / LIST_RECSIZE;
    else if (data[1] == T_RANGES) {
	data += _str.length() - RANGES_RECSIZE;
	return USHORT_AT(data + 4) + USHORT_AT(data + 2) - USHORT_AT(data) + 1;
    } else
	return -1;
}

int
Coverage::coverage_index(Glyph g) const throw ()
{
    if (_str.length() == 0)
	return -1;

    const uint8_t *data = _str.udata();
    int count = USHORT_AT(data + 2);
    if (data[1] == T_LIST) {
	int l = 0, r = count;
	data += HEADERSIZE;
	while (l < r) {
	    int m = l + (r - l) / 2;
	    int mval = USHORT_AT(data + m * LIST_RECSIZE);
	    if (g < mval)
		r = m;
	    else if (g == mval)
		return m;
	    else
		l = m + 1;
	}
	return -1;
    } else if (data[1] == T_RANGES) {
	int l = 0, r = count;
	data += HEADERSIZE;
	while (l < r) {
	    int m = l + (r - l) / 2;
	    const uint8_t *rec = data + m * RANGES_RECSIZE;
	    if (g < USHORT_AT(rec))
		r = m;
	    else if (g <= USHORT_AT(rec + 2))
		return USHORT_AT(rec + 4) + g - USHORT_AT(rec);
	    else
		l = m + 1;
	}
	return -1;
    } else
	return -1;
}

Glyph
Coverage::operator[](int cindex) const throw ()
{
    if (_str.length() == 0 || cindex < 0)
	return 0;

    const uint8_t *data = _str.udata();
    int count = USHORT_AT(data + 2);
    if (data[1] == T_LIST)
	return (cindex < count ? USHORT_AT(data + cindex * LIST_RECSIZE) : 0);
    else if (data[1] == T_RANGES) {
	int l = 0, r = count;
	data += HEADERSIZE;
	while (l < r) {
	    int m = l + (r - l) / 2;
	    const uint8_t *rec = data + m * RANGES_RECSIZE;
	    int start_cindex = USHORT_AT(rec + 4);
	    if (cindex < start_cindex)
		r = m;
	    else if (cindex < start_cindex + USHORT_AT(rec + 2) - USHORT_AT(rec))
		return USHORT_AT(rec) + cindex - start_cindex;
	    else
		l = m + 1;
	}
	return 0;
    } else
	return 0;
}

void
Coverage::unparse(StringAccum &sa) const throw ()
{
    const uint8_t *data = _str.udata();
    if (_str.length() == 0)
	sa << "@*#!";
    else if (data[1] == T_LIST) {
	int count = USHORT_AT(data + 2);
	for (int i = 0; i < count; i++) {
	    if (i) sa << ',';
	    sa << USHORT_AT(data + HEADERSIZE + i*LIST_RECSIZE);
	}
    } else {
	for (int pos = HEADERSIZE; pos < _str.length(); pos += RANGES_RECSIZE) {
	    Glyph start = USHORT_AT(data + pos);
	    Glyph end = USHORT_AT(data + pos + 2);
	    if (pos > HEADERSIZE) sa << ',';
	    sa << start;
	    if (end != start) sa << '.' << '.' << end;
	}
    }
}

String
Coverage::unparse() const throw ()
{
    StringAccum sa;
    unparse(sa);
    return sa.take_string();
}

Coverage
operator&(const Coverage &a, const Coverage &b)
{
    StringAccum sa;
    sa << '\000' << '\001' << '\000' << '\000';
    Coverage::iterator ai = a.begin(), bi = b.begin();
    while (ai && bi) {
	if (*ai < *bi)
	    ai.forward_to(*bi);
	else if (*ai == *bi) {
	    uint16_t x = *ai;
	    sa << (char)(x >> 8) << (char)(x & 0xFF);
	    ai++, bi++;
	} else
	    bi.forward_to(*ai);
    }
    int n = (sa.length() - 4) / 2;
    sa[2] = (n >> 8);
    sa[3] = (n & 0xFF);
    return Coverage(sa.take_string(), 0, false);
}

bool
operator<=(const Coverage &a, const Coverage &b)
{
    Coverage::iterator ai = a.begin(), bi = b.begin();
    while (ai && bi) {
	if (*ai != *bi && !bi.forward_to(*ai))
	    return false;
	ai++, bi++;
    }
    return bi || !ai;
}


/************************
 * Coverage::iterator   *
 *                      *
 ************************/

Coverage::iterator::iterator(const String &str, int pos)
    : _str(str), _pos(pos), _value(0)
{
    // XXX assume _str has been checked

    // shrink _str to fit the coverage table
    const uint8_t *data = _str.udata();
    if (_str.length()) {
	int n = USHORT_AT(data + 2);
	switch (USHORT_AT(data)) {
	  case T_LIST:
	    _str = _str.substring(0, HEADERSIZE + n*LIST_RECSIZE);
	    break;
	  case T_RANGES:
	    _str = _str.substring(0, HEADERSIZE + n*RANGES_RECSIZE);
	    break;
	  default:
	    _str = String();
	    break;
	}
    }
    if (_pos >= _str.length())
	_pos = _str.length();
    else {
	// now, move _pos into the coverage table
	_pos = HEADERSIZE;
	_value = (_pos >= _str.length() ? 0 : USHORT_AT(data + _pos));
    }
}

int
Coverage::iterator::coverage_index() const
{
    const uint8_t *data = _str.udata();
    assert(_pos < _str.length());
    if (data[1] == T_LIST)
	return (_pos - HEADERSIZE) / LIST_RECSIZE;
    else
	return USHORT_AT(data + _pos + 4) + _value - USHORT_AT(data + _pos);
}

void
Coverage::iterator::operator++(int)
{
    const uint8_t *data = _str.udata();
    int len = _str.length();
    if (_pos >= len
	|| (data[1] == T_RANGES && ++_value <= USHORT_AT(data + _pos + 2)))
	return;
    _pos += (data[1] == T_LIST ? LIST_RECSIZE : RANGES_RECSIZE);
    _value = (_pos >= len ? 0 : USHORT_AT(data + _pos));
}

bool
Coverage::iterator::forward_to(Glyph find)
{
    // XXX really should check that this works

    if (find <= _value)
	return find == _value;
    else if (_pos >= _str.length())
	return false;

    const uint8_t *data = _str.udata();
    if (data[1] == T_LIST) {
	// check for "common" case: next element
	_pos += LIST_RECSIZE;
	if (_pos >= _str.length())
	    return false;
	else if (find <= USHORT_AT(data + _pos)) {
	    _value = USHORT_AT(data + _pos);
	    return find == _value;
	}

	// otherwise, binary search over remaining area
	int l = ((_pos - HEADERSIZE) / LIST_RECSIZE) + 1;
	int r = (_str.length() - HEADERSIZE) / LIST_RECSIZE;
	data += HEADERSIZE;
	while (l < r) {
	    int m = l + (r - l) / 2;
	    Glyph g = USHORT_AT(data + m * LIST_RECSIZE);
	    if (find < g)
		r = m;
	    else if (find == g)
		l = r = m;
	    else
		l = m + 1;
	}
	_pos = HEADERSIZE + l * LIST_RECSIZE;
	_value = (_pos >= _str.length() ? 0 : USHORT_AT(data - HEADERSIZE + _pos));
    } else {		// data[1] == T_RANGES
	// check for "common" case: this or next element
	if (find <= USHORT_AT(data + _pos + 2)) {
	    assert(find >= USHORT_AT(data + _pos));
	    _value = find;
	    return true;
	}
	_pos += RANGES_RECSIZE;
	if (_pos >= _str.length())
	    return false;
	else if (find <= USHORT_AT(data + _pos + 2)) {
	    _value = (find >= USHORT_AT(data + _pos) ? find : USHORT_AT(data + _pos));
	    return find == _value;
	}

	// otherwise, binary search over remaining area
	int l = ((_pos - HEADERSIZE) / RANGES_RECSIZE) + 1;
	int r = (_str.length() - HEADERSIZE) / RANGES_RECSIZE;
	data += HEADERSIZE;
	while (l < r) {
	    int m = l + (r - l) / 2;
	    if (find < USHORT_AT(data + m * RANGES_RECSIZE))
		r = m;
	    else if (find <= USHORT_AT(data + m * RANGES_RECSIZE + 2)) {
		_pos = HEADERSIZE + m * RANGES_RECSIZE;
		_value = find;
		return true;
	    } else
		l = m + 1;
	}
	_pos = HEADERSIZE + l * LIST_RECSIZE;
	_value = (_pos >= _str.length() ? 0 : USHORT_AT(data - HEADERSIZE + _pos));
    }

    return find == _value;
}


/**************************
 * GlyphSet               *
 *                        *
 **************************/

GlyphSet::GlyphSet()
{
    memset(_v, 0, sizeof(_v));
}

GlyphSet::GlyphSet(const GlyphSet &o)
{
    for (int i = 0; i < VLEN; i++)
	if (o._v[i]) {
	    _v[i] = new uint32_t[VULEN];
	    memcpy(_v[i], o._v[i], sizeof(uint32_t) * VULEN);
	} else
	    _v[i] = 0;
}

GlyphSet::~GlyphSet()
{
    for (int i = 0; i < VLEN; i++)
	delete[] _v[i];
}

int
GlyphSet::change(Glyph g, bool value)
{
    if ((unsigned)g > MAXGLYPH)
	return -1;
    uint32_t *&u = _v[g >> SHIFT];
    if (!u) {
	u = new uint32_t[VULEN];
	memset(u, 0, sizeof(uint32_t) * VULEN);
    }
    uint32_t mask = (1 << (g & 0x1F));
    if (value)
	u[(g & MASK) >> 5] |= mask;
    else
	u[(g & MASK) >> 5] &= ~mask;
    return 0;
}

GlyphSet &
GlyphSet::operator=(const GlyphSet &o)
{
    if (&o != this) {
	for (int i = 0; i < VLEN; i++)
	    if (o._v[i]) {
		if (!_v[i])
		    _v[i] = new uint32_t[VULEN];
		memcpy(_v[i], o._v[i], sizeof(uint32_t) * VULEN);
	    } else if (_v[i])
		memset(_v[i], 0, sizeof(uint32_t) * VULEN);
    }
    return *this;
}


/**************************
 * ClassDef               *
 *                        *
 **************************/

ClassDef::ClassDef(const String &str, ErrorHandler *errh) throw ()
    : _str(str)
{
    _str.align(2);
    if (check(errh ? errh : ErrorHandler::silent_handler()) < 0)
	_str = String();
}

int
ClassDef::check(ErrorHandler *errh)
{
    // HEADER FORMAT:
    // USHORT	coverageFormat
    // USHORT	glyphCount
    const uint8_t *data = _str.udata();
    if (_str.length() < 6)	// NB: prevents empty format-2 tables
	return errh->error("OTF class def table too small for header");
    int classFormat = USHORT_AT(data);

    int len;
    if (classFormat == T_LIST) {
	int count = USHORT_AT(data + 4);
	len = LIST_HEADERSIZE + count*LIST_RECSIZE;
	// XXX don't check sorting
    } else if (classFormat == T_RANGES) {
	int count = USHORT_AT(data + 2);
	len = RANGES_HEADERSIZE + count*RANGES_RECSIZE;
	// XXX don't check sorting
    } else
	return errh->error("OTF class def table has unknown format %d", classFormat);

    if (len > _str.length())
	return errh->error("OTF class def table too short");
    else {
	_str = _str.substring(0, len);
	return 0;
    }
}

int
ClassDef::lookup(Glyph g) const throw ()
{
    if (_str.length() == 0)
	return -1;

    const uint8_t *data = _str.udata();
    int coverageFormat = USHORT_AT(data);

    if (coverageFormat == T_LIST) {
	Glyph start = USHORT_AT(data + 2);
	int count = USHORT_AT(data + 4);
	if (g < start || g >= start + count)
	    return 0;
	else
	    return USHORT_AT(data + LIST_HEADERSIZE + (g - start) * LIST_RECSIZE);
    } else if (coverageFormat == T_RANGES) {
	int l = 0, r = USHORT_AT(data + 2);
	data += RANGES_HEADERSIZE;
	while (l < r) {
	    int m = l + (r - l) / 2;
	    const uint8_t *rec = data + m * RANGES_RECSIZE;
	    if (g < USHORT_AT(rec))
		r = m;
	    else if (g <= USHORT_AT(rec + 2))
		return USHORT_AT(rec + 4);
	    else
		l = m + 1;
	}
	return 0;
    } else
	return 0;
}

void
ClassDef::unparse(StringAccum &sa) const throw ()
{
    const uint8_t *data = _str.udata();
    if (_str.length() == 0)
	sa << "@*#!";
    else if (data[1] == T_LIST) {
	Glyph start = USHORT_AT(data + 2);
	int count = USHORT_AT(data + 4);
	for (int i = 0; i < count; i++) {
	    if (i) sa << ',';
	    sa << start + i << '=' << USHORT_AT(data + LIST_HEADERSIZE + i*LIST_RECSIZE);
	}
    } else {
	for (int pos = RANGES_HEADERSIZE; pos < _str.length(); pos += RANGES_RECSIZE) {
	    Glyph start = USHORT_AT(data + pos);
	    Glyph end = USHORT_AT(data + pos + 2);
	    if (pos > RANGES_HEADERSIZE) sa << ',';
	    sa << start;
	    if (end != start) sa << '.' << '.' << end;
	    sa << '=' << USHORT_AT(data + pos + 4);
	}
    }
}

String
ClassDef::unparse() const throw ()
{
    StringAccum sa;
    unparse(sa);
    return sa.take_string();
}


/******************************
 * ClassDef::class_iterator   *
 *                            *
 ******************************/

ClassDef::class_iterator::class_iterator(const String &str, int pos, int the_class, const Coverage::iterator &coviter)
    : _str(str), _pos(pos), _class(the_class), _coviter(coviter)
{
    // XXX assume _str has been checked

    // cannot handle the_class == 0 when coviter doesn't exist
    if (_class == 0 && !_coviter)
	throw Error("cannot iterate over ClassDef class 0");

    // shrink _str to fit the coverage table, and create a fake coverage
    // iterator if necessary
    const uint8_t *data = _str.udata();
    if (_str.length()) {
	switch (USHORT_AT(data)) {
	  case T_LIST: {
	      Glyph start = USHORT_AT(data + 2);
	      int nglyphs = USHORT_AT(data + 4);
	      _str = _str.substring(0, LIST_HEADERSIZE + nglyphs*LIST_RECSIZE);
	      if (!_coviter)
		  _coviter = Coverage(start, start + nglyphs - 1).begin();
	      if (_class)
		  _coviter.forward_to(start);
	      break;
	  }
	  case T_RANGES: {
	      Glyph start = USHORT_AT(data + RANGES_HEADERSIZE);
	      int nranges = USHORT_AT(data + 2);
	      _str = _str.substring(0, RANGES_HEADERSIZE + nranges*RANGES_RECSIZE);
	      if (!_coviter) {
		  Glyph end = USHORT_AT(data + RANGES_HEADERSIZE + 2 + (nranges - 1)*RANGES_RECSIZE);
		  _coviter = Coverage(start, end).begin();
	      }
	      if (_class)
		  _coviter.forward_to(start);
	      break;
	  }
	  default:
	    _str = String();
	    break;
	}
    }

    // move to the first relevant glyph
    if (_pos >= _str.length())
	_pos = _str.length();
    else {
	_pos = 0;
	(*this)++;
    }
}

void
ClassDef::class_iterator::increment_class0()
{
    const uint8_t *data = _str.udata();
    int len = _str.length();
    bool is_list = (data[1] == T_LIST);

    if (_pos != 0)
	_coviter++;
    else
	_pos = FIRST_POS;

    if (_pos == FIRST_POS && _coviter) {
	if (*_coviter < USHORT_AT(data + (is_list ? 2 : RANGES_HEADERSIZE)))
	    return;
	_pos = (is_list ? LIST_HEADERSIZE : RANGES_HEADERSIZE);
    }

    while (_pos > 0 && _pos < len && _coviter) {
	Glyph g = *_coviter;
	if (is_list) {
	    _pos = LIST_HEADERSIZE + LIST_RECSIZE*(g - USHORT_AT(data + 2));
	    if (_pos >= len)
		break;
	    else if (USHORT_AT(data + _pos) == 0) // _class == 0
		return;
	    _coviter++;
	} else {
	    if (g < USHORT_AT(data + _pos)) // in a zero range
		return;
	    else if (g > USHORT_AT(data + _pos + 2))
		_pos += RANGES_RECSIZE;
	    else if (USHORT_AT(data + _pos + 4) == 0) // _class == 0
		return;
	    else
		_coviter.forward_to(USHORT_AT(data + _pos + 2) + 1);
	}
    }

    if (_coviter)
	_pos = LAST_POS;
    else
	_pos = len;
}

void
ClassDef::class_iterator::operator++(int)
{
    if (_class == 0) {
	increment_class0();
	return;
    }

    const uint8_t *data = _str.udata();
    int len = _str.length();
    bool is_list = (data[1] == T_LIST);

    if (_pos != 0)
	_coviter++;
    else
	_pos = (is_list ? LIST_HEADERSIZE : RANGES_HEADERSIZE);

    while (_pos < len && _coviter) {
	Glyph g = *_coviter;
	if (is_list) {
	    _pos = LIST_HEADERSIZE + LIST_RECSIZE*(g - USHORT_AT(data + 2));
	    if (_pos >= len || USHORT_AT(data + _pos) == _class)
		return;
	    _coviter++;
	} else {
	    while (_pos < len && (g > USHORT_AT(data + _pos + 2)
				  || USHORT_AT(data + _pos + 4) != _class))
		_pos += RANGES_RECSIZE;
	    // now, _pos >= len, or g <= rec.end && class == rec.class
	    if (_pos >= len || g >= USHORT_AT(data + _pos))
		return;
	    _coviter.forward_to(USHORT_AT(data + _pos));
	}
    }

    _pos = len;
}


}}


// template instantiations
#include <lcdf/vector.cc>
