/* Copyright (C) 2007,2008 by George Williams */
/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.

 * The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
#include "fontforgevw.h"
#include <chardata.h>
#include <utype.h>
#include <ustring.h>
#include <math.h>
#include <locale.h>
#include <stdlib.h>
#include "ttf.h"
#include "lookups.h"

struct opentype_feature_friendlynames friendlies[] = {
#if 0		/* They get stuffed into the 'MATH' table now */
/* I added these first three features to allow round-trip conversion of tfm files */
    { CHR('I','T','L','C'),	"ITLC", N_("Italic Correction"),	gpos_single_mask },
    { CHR('T','C','H','L'),	"TCHL", N_("TeX Glyphlist"),		gsub_alternate_mask },
    { CHR('T','E','X','L'),	"TEXL", N_("TeX Extension List"),	gsub_multiple_mask },
#endif
/* Normal OpenType features follow */
    { CHR('a','a','l','t'),	"aalt", N_("Access All Alternates"),	gsub_single_mask|gsub_alternate_mask },
    { CHR('a','b','v','f'),	"abvf", N_("Above Base Forms"),		gsub_single_mask },
    { CHR('a','b','v','m'),	"abvm", N_("Above Base Mark"),		gpos_mark2base_mask|gpos_mark2ligature_mask },
    { CHR('a','b','v','s'),	"abvs", N_("Above Base Substitutions"),	gsub_ligature_mask },
    { CHR('a','f','r','c'),	"afrc", N_("Vertical Fractions"),	gsub_ligature_mask },
    { CHR('a','k','h','n'),	"akhn", N_("Akhand"),			gsub_ligature_mask },
    { CHR('a','l','i','g'),	"alig", N_("Ancient Ligatures"),	gsub_ligature_mask },
    { CHR('b','l','w','f'),	"blwf", N_("Below Base Forms"),		gsub_ligature_mask },
    { CHR('b','l','w','m'),	"blwm", N_("Below Base Mark"),		gpos_mark2base_mask|gpos_mark2ligature_mask },
    { CHR('b','l','w','s'),	"blws", N_("Below Base Substitutions"),	gsub_ligature_mask },
    { CHR('c','2','p','c'),	"c2pc", N_("Capitals to Petite Capitals"),	gsub_single_mask },
    { CHR('c','2','s','c'),	"c2sc", N_("Capitals to Small Capitals"),	gsub_single_mask },
    { CHR('c','a','l','t'),	"calt", N_("Contextual Alternates"),	gsub_context_mask|gsub_contextchain_mask },
    { CHR('c','a','s','e'),	"case", N_("Case-Sensitive Forms"),	gsub_single_mask|gpos_single_mask },
    { CHR('c','c','m','p'),	"ccmp", N_("Glyph Composition/Decomposition"),	gsub_multiple_mask|gsub_ligature_mask },
    { CHR('c','l','i','g'),	"clig", N_("Contextual Ligatures"),	gsub_reversecchain_mask },
    { CHR('c','p','c','t'),	"cpct", N_("Centered CJK Punctuation"),	gpos_single_mask },
    { CHR('c','p','s','p'),	"cpsp", N_("Capital Spacing"),		gpos_single_mask },
    { CHR('c','s','w','h'),	"cswh", N_("Contextual Swash"),		gsub_reversecchain_mask },
    { CHR('c','u','r','s'),	"curs", N_("Cursive Attachment"),	gpos_cursive_mask },
    { CHR('c','v','0','0'),	"cv00", N_("Character Variants 00"),	gsub_single_mask },
    { CHR('c','v','0','1'),	"cv01", N_("Character Variants 01"),	gsub_single_mask },
    { CHR('c','v','0','2'),	"cv02", N_("Character Variants 02"),	gsub_single_mask },
    { CHR('c','v','0','3'),	"cv03", N_("Character Variants 03"),	gsub_single_mask },
    { CHR('c','v','0','4'),	"cv04", N_("Character Variants 04"),	gsub_single_mask },
    { CHR('c','v','0','5'),	"cv05", N_("Character Variants 05"),	gsub_single_mask },
    { CHR('c','v','0','6'),	"cv06", N_("Character Variants 06"),	gsub_single_mask },
    { CHR('c','v','0','7'),	"cv07", N_("Character Variants 07"),	gsub_single_mask },
    { CHR('c','v','0','8'),	"cv08", N_("Character Variants 08"),	gsub_single_mask },
    { CHR('c','v','0','9'),	"cv09", N_("Character Variants 09"),	gsub_single_mask },
    { CHR('c','v','1','0'),	"cv10", N_("Character Variants 10"),	gsub_single_mask },
    { CHR('c','v','9','9'),	"cv99", N_("Character Variants 99"),	gsub_single_mask },
    { CHR('d','c','a','p'),	"dcap", N_("Drop Caps"),		gsub_single_mask },
    { CHR('d','i','s','t'),	"dist", N_("Distance"),			gpos_pair_mask },
    { CHR('d','l','i','g'),	"dlig", N_("Discretionary Ligatures"),	gsub_ligature_mask },
    { CHR('d','n','o','m'),	"dnom", N_("Denominators"),		gsub_single_mask },
    { CHR('d','p','n','g'),	"dpng", N_("Dipthongs (Obsolete)"),	gsub_ligature_mask },
    { CHR('e','x','p','t'),	"expt", N_("Expert Forms"),		gsub_single_mask },
    { CHR('f','a','l','t'),	"falt", N_("Final Glyph On Line"),	gsub_alternate_mask },
    { CHR('f','i','n','2'),	"fin2", N_("Terminal Forms #2"),	gsub_context_mask|gsub_contextchain_mask },
    { CHR('f','i','n','3'),	"fin3", N_("Terminal Forms #3"),	gsub_context_mask|gsub_contextchain_mask },
    { CHR('f','i','n','a'),	"fina", N_("Terminal Forms"),		gsub_single_mask },
    { CHR('f','l','a','c'),	"flac", N_("Flattened Accents over Capitals"),	gsub_single_mask|gsub_ligature_mask },
    { CHR('f','r','a','c'),	"frac", N_("Diagonal Fractions"),	gsub_single_mask|gsub_ligature_mask },
    { CHR('f','w','i','d'),	"fwid", N_("Full Widths"),		gsub_single_mask|gpos_single_mask },
    { CHR('h','a','l','f'),	"half", N_("Half Forms"),		gsub_ligature_mask },
    { CHR('h','a','l','n'),	"haln", N_("Halant Forms"),		gsub_ligature_mask },
    { CHR('h','a','l','t'),	"halt", N_("Alternative Half Widths"),	gpos_single_mask },
    { CHR('h','i','s','t'),	"hist", N_("Historical Forms"),		gsub_single_mask },
    { CHR('h','k','n','a'),	"hkna", N_("Horizontal Kana Alternatives"),	gsub_single_mask },
    { CHR('h','l','i','g'),	"hlig", N_("Historic Ligatures"),	gsub_ligature_mask },
    { CHR('h','n','g','l'),	"hngl", N_("Hanja to Hangul"),		gsub_single_mask|gsub_alternate_mask },
    { CHR('h','o','j','o'),	"hojo", N_("Hojo (JIS X 0212-1990) Kanji Forms"),	gsub_single_mask },
    { CHR('h','w','i','d'),	"hwid", N_("Half Widths"),		gsub_single_mask|gpos_single_mask },
    { CHR('i','n','i','t'),	"init", N_("Initial Forms"),		gsub_single_mask },
    { CHR('i','s','o','l'),	"isol", N_("Isolated Forms"),		gsub_single_mask },
    { CHR('i','t','a','l'),	"ital", N_("Italics"),			gsub_single_mask },
    { CHR('j','a','l','t'),	"jalt", N_("Justification Alternatives"),	gsub_alternate_mask },
    { CHR('j','a','j','p'),	"jajp", N_("Japanese Forms (Obsolete"),	gsub_single_mask|gsub_alternate_mask },
    { CHR('j','p','7','8'),	"jp78", N_("JIS78 Forms"),		gsub_single_mask|gsub_alternate_mask },
    { CHR('j','p','8','3'),	"jp83", N_("JIS83 Forms"),		gsub_single_mask },
    { CHR('j','p','9','0'),	"jp90", N_("JIS90 Forms"),		gsub_single_mask },
    { CHR('k','e','r','n'),	"kern", N_("Horizontal Kerning"),	gpos_pair_mask|gpos_context_mask|gpos_contextchain_mask },
    { CHR('l','f','b','d'),	"lfbd", N_("Left Bounds"),		gpos_single_mask },
    { CHR('l','i','g','a'),	"liga", N_("Standard Ligatures"),	gsub_ligature_mask },
    { CHR('l','j','m','o'),	"ljmo", N_("Leading Jamo Forms"),	gsub_ligature_mask },
    { CHR('l','n','u','m'),	"lnum", N_("Lining Figures"),		gsub_single_mask },
    { CHR('l','o','c','l'),	"locl", N_("Localized Forms"),		gsub_single_mask },
    { CHR('m','a','r','k'),	"mark", N_("Mark Positioning"),		gpos_mark2base_mask|gpos_mark2ligature_mask },
    { CHR('m','e','d','2'),	"med2", N_("Medial Forms 2"),		gsub_context_mask|gsub_contextchain_mask },
    { CHR('m','e','d','i'),	"medi", N_("Medial Forms"),		gsub_single_mask },
    { CHR('m','g','r','k'),	"mgrk", N_("Mathematical Greek"),	gsub_single_mask },
    { CHR('m','k','m','k'),	"mkmk", N_("Mark to Mark"),		gpos_mark2mark_mask },
    { CHR('m','s','e','t'),	"mset", N_("Mark Positioning via Substitution"),	gsub_context_mask|gsub_contextchain_mask },
    { CHR('n','a','l','t'),	"nalt", N_("Alternate Annotation Forms"),	gsub_single_mask|gsub_alternate_mask },
    { CHR('n','u','k','t'),	"nukt", N_("Nukta Forms"),		gsub_ligature_mask },
    { CHR('n','u','m','r'),	"numr", N_("Numerators"),		gsub_single_mask },
    { CHR('o','n','u','m'),	"onum", N_("Oldstyle Figures"),		gsub_single_mask },
    { CHR('o','p','b','d'),	"opbd", N_("Optical Bounds"),		gpos_single_mask },
    { CHR('o','r','d','n'),	"ordn", N_("Ordinals"),			gsub_ligature_mask|gsub_context_mask|gsub_contextchain_mask },
    { CHR('o','r','n','m'),	"ornm", N_("Ornaments"),		gsub_single_mask|gsub_alternate_mask },
    { CHR('p','a','l','t'),	"palt", N_("Proportional Alternate Metrics"),	gpos_single_mask },
    { CHR('p','c','a','p'),	"pcap", N_("Lowercase to Petite Capitals"),	gsub_single_mask },
    { CHR('p','k','n','a'),	"pkna", N_("Proportional Kana"),	gpos_single_mask },
    { CHR('p','n','u','m'),	"pnum", N_("Proportional Numbers"),	gsub_single_mask },
    { CHR('p','r','e','f'),	"pref", N_("Pre Base Forms"),		gsub_ligature_mask },
    { CHR('p','r','e','s'),	"pres", N_("Pre Base Substitutions"),	gsub_ligature_mask|gsub_context_mask|gsub_contextchain_mask },
    { CHR('p','s','t','f'),	"pstf", N_("Post Base Forms"),		gsub_ligature_mask },
    { CHR('p','s','t','s'),	"psts", N_("Post Base Substitutions"),	gsub_ligature_mask },
    { CHR('p','w','i','d'),	"pwid", N_("Proportional Width"),	gsub_single_mask },
    { CHR('q','w','i','d'),	"qwid", N_("Quarter Widths"),		gsub_single_mask|gpos_single_mask },
    { CHR('r','a','n','d'),	"rand", N_("Randomize"),		gsub_alternate_mask },
    { CHR('r','k','r','f'),	"rkrf", N_("Rakar Forms"),		gsub_ligature_mask },
    { CHR('r','l','i','g'),	"rlig", N_("Required Ligatures"),	gsub_ligature_mask },
    { CHR('r','p','h','f'),	"rphf", N_("Reph Form"),		gsub_ligature_mask },
    { CHR('r','t','b','d'),	"rtbd", N_("Right Bounds"),		gpos_single_mask },
    { CHR('r','t','l','a'),	"rtla", N_("Right to Left Alternates"),	gsub_single_mask },
    { CHR('r','u','b','y'),	"ruby", N_("Ruby Notational Forms"),	gsub_single_mask },
    { CHR('s','a','l','t'),	"salt", N_("Stylistic Alternatives"),	gsub_single_mask|gsub_alternate_mask },
    { CHR('s','i','n','f'),	"sinf", N_("Scientific Inferiors"),	gsub_single_mask },
    { CHR('s','m','c','p'),	"smcp", N_("Lowercase to Small Capitals"),	gsub_single_mask },
    { CHR('s','m','p','l'),	"smpl", N_("Simplified Forms"),		gsub_single_mask },
    { CHR('s','s','0','1'),	"ss01", N_("Style Set 1"),		gsub_single_mask },
    { CHR('s','s','0','2'),	"ss02", N_("Style Set 2"),		gsub_single_mask },
    { CHR('s','s','0','3'),	"ss03", N_("Style Set 3"),		gsub_single_mask },
    { CHR('s','s','0','4'),	"ss04", N_("Style Set 4"),		gsub_single_mask },
    { CHR('s','s','0','5'),	"ss05", N_("Style Set 5"),		gsub_single_mask },
    { CHR('s','s','0','6'),	"ss06", N_("Style Set 6"),		gsub_single_mask },
    { CHR('s','s','0','7'),	"ss07", N_("Style Set 7"),		gsub_single_mask },
    { CHR('s','s','0','8'),	"ss08", N_("Style Set 8"),		gsub_single_mask },
    { CHR('s','s','0','9'),	"ss09", N_("Style Set 9"),		gsub_single_mask },
    { CHR('s','s','1','0'),	"ss10", N_("Style Set 10"),		gsub_single_mask },
    { CHR('s','s','1','1'),	"ss11", N_("Style Set 11"),		gsub_single_mask },
    { CHR('s','s','1','2'),	"ss12", N_("Style Set 12"),		gsub_single_mask },
    { CHR('s','s','1','3'),	"ss13", N_("Style Set 13"),		gsub_single_mask },
    { CHR('s','s','1','4'),	"ss14", N_("Style Set 14"),		gsub_single_mask },
    { CHR('s','s','1','5'),	"ss15", N_("Style Set 15"),		gsub_single_mask },
    { CHR('s','s','1','6'),	"ss16", N_("Style Set 16"),		gsub_single_mask },
    { CHR('s','s','1','7'),	"ss17", N_("Style Set 17"),		gsub_single_mask },
    { CHR('s','s','1','8'),	"ss18", N_("Style Set 18"),		gsub_single_mask },
    { CHR('s','s','1','9'),	"ss19", N_("Style Set 19"),		gsub_single_mask },
    { CHR('s','s','2','0'),	"ss20", N_("Style Set 20"),		gsub_single_mask },
    { CHR('s','s','t','y'),	"ssty", N_("Script Style"),		gsub_single_mask },
    { CHR('s','u','b','s'),	"subs", N_("Subscript"),		gsub_single_mask },
    { CHR('s','u','p','s'),	"sups", N_("Superscript"),		gsub_single_mask },
    { CHR('s','w','s','h'),	"swsh", N_("Swash"),			gsub_single_mask|gsub_alternate_mask },
    { CHR('t','i','t','l'),	"titl", N_("Titling"),			gsub_single_mask },
    { CHR('t','j','m','o'),	"tjmo", N_("Trailing Jamo Forms"),	gsub_ligature_mask },
    { CHR('t','n','a','m'),	"tnam", N_("Traditional Name Forms"),	gsub_single_mask },
    { CHR('t','n','u','m'),	"tnum", N_("Tabular Numbers"),		gsub_single_mask },
    { CHR('t','r','a','d'),	"trad", N_("Traditional Forms"),	gsub_single_mask|gsub_alternate_mask },
    { CHR('t','w','i','d'),	"twid", N_("Third Widths"),		gsub_single_mask|gpos_single_mask },
    { CHR('u','n','i','c'),	"unic", N_("Unicase"),			gsub_single_mask },
    { CHR('v','a','l','t'),	"valt", N_("Alternate Vertical Metrics"),	gpos_single_mask },
    { CHR('v','a','t','u'),	"vatu", N_("Vattu Variants"),		gsub_ligature_mask },
    { CHR('v','e','r','t'),	"vert", N_("Vertical Alternates (obs)"),	gsub_single_mask },
    { CHR('v','h','a','l'),	"vhal", N_("Alternate Vertical Half Metrics"),	gpos_single_mask },
    { CHR('v','j','m','o'),	"vjmo", N_("Vowel Jamo Forms"),		gsub_ligature_mask },
    { CHR('v','k','n','a'),	"vkna", N_("Vertical Kana Alternates"),	gsub_single_mask },
    { CHR('v','k','r','n'),	"vkrn", N_("Vertical Kerning"),		gpos_pair_mask|gpos_context_mask|gpos_contextchain_mask },
    { CHR('v','p','a','l'),	"vpal", N_("Proportional Alternate Vertical Metrics"),	gpos_single_mask },
    { CHR('v','r','t','2'),	"vrt2", N_("Vertical Rotation & Alternates"),	gsub_single_mask },
    { CHR('z','e','r','o'),	"zero", N_("Slashed Zero"),		gsub_single_mask },
/* This is my hack for setting the "Required feature" field of a script */
    { CHR(' ','R','Q','D'),	" RQD", N_("Required feature"),		gsub_single_mask|gsub_multiple_mask|gsub_alternate_mask|gsub_ligature_mask|gsub_context_mask|gsub_contextchain_mask|gsub_reversecchain_mask|gpos_single_mask|gpos_pair_mask|gpos_cursive_mask|gpos_mark2base_mask|gpos_mark2ligature_mask|gpos_mark2mark_mask|gpos_context_mask|gpos_contextchain_mask },
    { 0, NULL, 0, 0 }
};

static int uint32_cmp(const void *_ui1, const void *_ui2) {
    if ( *(uint32 *) _ui1 > *(uint32 *)_ui2 )
return( 1 );
    if ( *(uint32 *) _ui1 < *(uint32 *)_ui2 )
return( -1 );

return( 0 );
}

static int lang_cmp(const void *_ui1, const void *_ui2) {
    /* The default language is magic, and should come first in the list even */
    /*  if that is not true alphabetical order */
    if ( *(uint32 *) _ui1 == DEFAULT_LANG )
return( -1 );
    if ( *(uint32 *) _ui2 == DEFAULT_LANG )
return( 1 );

    if ( *(uint32 *) _ui1 > *(uint32 *)_ui2 )
return( 1 );
    if ( *(uint32 *) _ui1 < *(uint32 *)_ui2 )
return( -1 );

return( 0 );
}

FeatureScriptLangList *FindFeatureTagInFeatureScriptList(uint32 tag, FeatureScriptLangList *fl) {

    while ( fl!=NULL ) {
	if ( fl->featuretag==tag )
return( fl );
	fl = fl->next;
    }
return( NULL );
}

int FeatureTagInFeatureScriptList(uint32 tag, FeatureScriptLangList *fl) {

    while ( fl!=NULL ) {
	if ( fl->featuretag==tag )
return( true );
	fl = fl->next;
    }
return( false );
}

int ScriptInFeatureScriptList(uint32 script, FeatureScriptLangList *fl) {
    struct scriptlanglist *sl;

    if ( fl==NULL )		/* No features bound to lookup? (nested?) don't restrict by script */
return( true );

    while ( fl!=NULL ) {
	for ( sl=fl->scripts; sl!=NULL; sl=sl->next ) {
	    if ( sl->script == script )
return( true );
	}
	fl = fl->next;
    }
return( false );
}

int FeatureScriptTagInFeatureScriptList(uint32 feature, uint32 script, FeatureScriptLangList *fl) {
    struct scriptlanglist *sl;

    while ( fl!=NULL ) {
	if ( fl->featuretag == feature ) {
	    for ( sl=fl->scripts; sl!=NULL; sl=sl->next ) {
		if ( sl->script == script )
return( true );
	    }
	}
	fl = fl->next;
    }
return( false );
}

int DefaultLangTagInOneScriptList(struct scriptlanglist *sl) {
    int l;

    for ( l=0; l<sl->lang_cnt; ++l ) {
	uint32 lang = l<MAX_LANG ? sl->langs[l] : sl->morelangs[l-MAX_LANG];
	if ( lang==DEFAULT_LANG )
return( true );
    }
return( false );
}

struct scriptlanglist *DefaultLangTagInScriptList(struct scriptlanglist *sl, int DFLT_ok) {

    while ( sl!=NULL ) {
	if ( DFLT_ok || sl->script!=DEFAULT_SCRIPT ) {
	    if ( DefaultLangTagInOneScriptList(sl))
return( sl );
	}
	sl = sl->next;
    }
return( NULL );
}

uint32 *SFScriptsInLookups(SplineFont *sf,int gpos) {
    /* Presumes that either SFFindUnusedLookups or SFFindClearUnusedLookupBits */
    /*  has been called first */
    /* Since MS will sometimes ignore a script if it isn't found in both */
    /*  GPOS and GSUB we want to return the same script list no matter */
    /*  what the setting of gpos ... so we totally ignore that argument */
    /*  and always look at both sets of lookups */

/* Sergey Malkin from MicroSoft tells me:
    Each shaping engine in Uniscribe can decide on its requirements for
    layout tables - some of them require both GSUB and GPOS, in some cases
    any table present is enough, or it can work without any table. 

    Sometimes, purpose of the check is to determine if font is supporting
    particular script - if required tables are not there font is just
    rejected by this shaping engine. Sometimes, shaping engine can not just
    reject the font because there are fonts using older shaping technologies
    we still have to support, so it uses some logic when to fallback to
    legacy layout code. 

    In your case this is Hebrew, where both tables are required to use
    OpenType processing. Arabic requires both tables too, Latin requires
    GSUB to execute GPOS. But in general, if you have both tables you should
    be safe with any script to get fully featured OpenType shaping.

In other words, if we have a Hebrew font with just GPOS features they won't work,
and MS will not use the font at all. We must add a GSUB table. In the unlikely
event that we had a hebrew font with only GSUB it would not work either.

So if we want our lookups to have a chance of executing under Uniscribe we
better make sure that both tables have the same script set.

(Sergey says we could optimize a little: A Latin GSUB table will run without
a GPOS, but he says the GPOS won't work without a GSUB.)
*/
    int cnt=0, tot=0, i;
    uint32 *scripts = NULL;
    OTLookup *test;
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;

    /* So here always give scripts for both (see comment above) no */
    /*  matter what they asked for */
    for ( gpos=0; gpos<2; ++gpos ) {
	for ( test = gpos ? sf->gpos_lookups : sf->gsub_lookups; test!=NULL; test = test->next ) {
	    if ( test->unused )
	continue;
	    for ( fl=test->features; fl!=NULL; fl=fl->next ) {
		for ( sl=fl->scripts ; sl!=NULL; sl=sl->next ) {
		    for ( i=0; i<cnt; ++i ) {
			if ( sl->script==scripts[i] )
		    break;
		    }
		    if ( i==cnt ) {
			if ( cnt>=tot )
			    scripts = grealloc(scripts,(tot+=10)*sizeof(uint32));
			scripts[cnt++] = sl->script;
		    }
		}
	    }
	}
    }

    if ( cnt==0 )
return( NULL );

    /* We want our scripts in alphabetic order */
    qsort(scripts,cnt,sizeof(uint32),uint32_cmp);
    /* add a 0 entry to mark the end of the list */
    if ( cnt>=tot )
	scripts = grealloc(scripts,(tot+1)*sizeof(uint32));
    scripts[cnt] = 0;
return( scripts );
}

uint32 *SFLangsInScript(SplineFont *sf,int gpos,uint32 script) {
    /* However, the language lists (I think) are distinct */
    /* But giving a value of -1 for gpos will give us the set of languages in */
    /*  both tables (for this script) */
    int cnt=0, tot=0, i, g, l;
    uint32 *langs = NULL;
    OTLookup *test;
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;

    for ( g=0; g<2; ++g ) {
	if (( gpos==0 && g==1 ) || ( gpos==1 && g==0 ))
    continue;
	for ( test = gpos ? sf->gpos_lookups : sf->gsub_lookups; test!=NULL; test = test->next ) {
	    if ( test->unused )
	continue;
	    for ( fl=test->features; fl!=NULL; fl=fl->next ) {
		for ( sl=fl->scripts ; sl!=NULL; sl=sl->next ) {
		    if ( sl->script==script ) {
			for ( l=0; l<sl->lang_cnt; ++l ) {
			    unsigned lang;
			    if ( l<MAX_LANG )
				lang = sl->langs[l];
			    else
				lang = sl->morelangs[l-MAX_LANG];
			    for ( i=0; i<cnt; ++i ) {
				if ( lang==langs[i] )
			    break;
			    }
			    if ( i==cnt ) {
				if ( cnt>=tot )
				    langs = grealloc(langs,(tot+=10)*sizeof(uint32));
				langs[cnt++] = lang;
			    }
			}
		    }
		}
	    }
	}
    }

    if ( cnt==0 ) {
	/* We add dummy script entries. Because Uniscribe will refuse to */
	/*  process some scripts if they don't have an entry in both GPOS */
	/*  an GSUB. So if a script appears in either table, force it to */
	/*  appear in both. That means we can get scripts with no lookups */
	/*  and hence no languages. It seems that Uniscribe doesn't like */
	/*  that either. So give each such script a dummy default language */
	/*  entry. This is what VOLT does */
	langs = gcalloc(2,sizeof(uint32));
	langs[0] = DEFAULT_LANG;
return( langs );
    }

    /* We want our languages in alphabetic order */
    qsort(langs,cnt,sizeof(uint32),lang_cmp);
    /* add a 0 entry to mark the end of the list */
    if ( cnt>=tot )
	langs = grealloc(langs,(tot+1)*sizeof(uint32));
    langs[cnt] = 0;
return( langs );
}

uint32 *SFFeaturesInScriptLang(SplineFont *sf,int gpos,uint32 script,uint32 lang) {
    int cnt=0, tot=0, i, l, isg;
    uint32 *features = NULL;
    OTLookup *test;
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;
    /* gpos==0 => GSUB, gpos==1 => GPOS, gpos==-1 => both, gpos==-2 => Both & kern */

    if ( sf->cidmaster ) sf=sf->cidmaster;
    for ( isg = 0; isg<2; ++isg ) {
	if ( gpos>=0 && isg!=gpos )
    continue;
	for ( test = isg ? sf->gpos_lookups : sf->gsub_lookups; test!=NULL; test = test->next ) {
	    if ( test->unused )
	continue;
	    for ( fl=test->features; fl!=NULL; fl=fl->next ) {
		if ( script==0xffffffff ) {
		    for ( i=0; i<cnt; ++i ) {
			if ( fl->featuretag==features[i] )
		    break;
		    }
		    if ( i==cnt ) {
			if ( cnt>=tot )
			    features = grealloc(features,(tot+=10)*sizeof(uint32));
			features[cnt++] = fl->featuretag;
		    }
		} else for ( sl=fl->scripts ; sl!=NULL; sl=sl->next ) {
		    if ( sl->script==script ) {
			int matched = false;
			for ( l=0; l<sl->lang_cnt; ++l ) {
			    unsigned testlang;
			    if ( l<MAX_LANG )
				testlang = sl->langs[l];
			    else
				testlang = sl->morelangs[l-MAX_LANG];
			    if ( testlang==lang ) {
				matched = true;
			break;
			    }
			}
			if ( matched ) {
			    for ( i=0; i<cnt; ++i ) {
				if ( fl->featuretag==features[i] )
			    break;
			    }
			    if ( i==cnt ) {
				if ( cnt>=tot )
				    features = grealloc(features,(tot+=10)*sizeof(uint32));
				features[cnt++] = fl->featuretag;
			    }
			}
		    }
		}
	    }
	}
    }

    if ( sf->design_size!=0 && gpos ) {
	/* The 'size' feature is like no other. It has no lookups and so */
	/*  we will never find it in the normal course of events. If the */
	/*  user has specified a design size, then every script/lang combo */
	/*  gets a 'size' feature which contains no lookups but feature */
	/*  params */
	if ( cnt>=tot )
	    features = grealloc(features,(tot+=2)*sizeof(uint32));
	features[cnt++] = CHR('s','i','z','e');
    }

    if ( cnt==0 )
return( gcalloc(1,sizeof(uint32)) );

    /* We don't care if our features are in alphabetical order here */
    /*  all that matters is whether the complete list of features is */
    /*  ordering here would be irrelevant */
    /* qsort(features,cnt,sizeof(uint32),uint32_cmp); */

    /* add a 0 entry to mark the end of the list */
    if ( cnt>=tot )
	features = grealloc(features,(tot+1)*sizeof(uint32));
    features[cnt] = 0;
return( features );
}

OTLookup **SFLookupsInScriptLangFeature(SplineFont *sf,int gpos,uint32 script,uint32 lang, uint32 feature) {
    int cnt=0, tot=0, l;
    OTLookup **lookups = NULL;
    OTLookup *test;
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;

    for ( test = gpos ? sf->gpos_lookups : sf->gsub_lookups; test!=NULL; test = test->next ) {
	if ( test->unused )
    continue;
	for ( fl=test->features; fl!=NULL; fl=fl->next ) {
	    if ( fl->featuretag==feature ) {
		for ( sl=fl->scripts ; sl!=NULL; sl=sl->next ) {
		    if ( sl->script==script ) {
			for ( l=0; l<sl->lang_cnt; ++l ) {
			    unsigned testlang;
			    if ( l<MAX_LANG )
				testlang = sl->langs[l];
			    else
				testlang = sl->morelangs[l-MAX_LANG];
			    if ( testlang==lang ) {
				if ( cnt>=tot )
				    lookups = grealloc(lookups,(tot+=10)*sizeof(OTLookup *));
				lookups[cnt++] = test;
	goto found;
			    }
			}
		    }
		}
	    }
	}
	found:;
    }

    if ( cnt==0 )
return( NULL );

    /* lookup order is irrelevant here. might as well leave it in invocation order */
    /* add a 0 entry to mark the end of the list */
    if ( cnt>=tot )
	lookups = grealloc(lookups,(tot+1)*sizeof(OTLookup *));
    lookups[cnt] = 0;
return( lookups );
}

static int LigaturesFirstComponentGID(SplineFont *sf,char *components) {
    int gid, ch;
    char *pt;

    for ( pt = components; *pt!='\0' && *pt!=' '; ++pt );
    ch = *pt; *pt = '\0';
    gid = SFFindExistingSlot(sf,-1,components);
    *pt = ch;
return( gid );
}

static int PSTValid(SplineFont *sf,PST *pst) {
    char *start, *pt, ch;
    int ret;
    (void)sf;
    switch ( pst->type ) {
      case pst_position:
return( true );
      case pst_pair:
return( SCWorthOutputting(SFGetChar(sf,-1,pst->u.pair.paired)) );
      case pst_substitution: case pst_alternate: case pst_multiple:
      case pst_ligature:
	for ( start = pst->u.mult.components; *start ; ) {
	    for ( pt=start; *pt && *pt!=' '; ++pt );
	    ch = *pt; *pt = '\0';
	    ret = SCWorthOutputting(SFGetChar(sf,-1,start));
	    *pt = ch;
	    if ( !ret )
return( false );
	    if ( ch==0 )
		start = pt;
	    else
		start = pt+1;
	}
    }
return( true );
}

SplineChar **SFGlyphsWithPSTinSubtable(SplineFont *sf,struct lookup_subtable *subtable) {
    uint8 *used = gcalloc(sf->glyphcnt,sizeof(uint8));
    SplineChar **glyphs, *sc;
    int i, k, gid, cnt;
    KernPair *kp;
    PST *pst;
    int ispair = subtable->lookup->lookup_type == gpos_pair;
    int isliga = subtable->lookup->lookup_type == gsub_ligature;
    sc = NULL;
    for ( i=0; i<sf->glyphcnt; ++i ) if ( SCWorthOutputting(sc = sf->glyphs[i]) ) {
	if ( ispair ) {
	    for ( k=0; k<2; ++k ) {
		for ( kp= k ? sc->kerns : sc->vkerns; kp!=NULL ; kp=kp->next ) {
		    if ( !SCWorthOutputting(kp->sc))
		continue;
		    if ( kp->subtable == subtable ) {
			used[i] = true;
    goto continue_;
		    }
		}
	    }
	}
	for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
	    if ( pst->subtable == subtable && PSTValid(sf,pst)) {
		if ( !isliga ) {
		    used[i] = true;
    goto continue_;
		} else {
		    gid = LigaturesFirstComponentGID(sf,pst->u.lig.components);
		    pst->u.lig.lig = sc;
		    if ( gid!=-1 )
			used[gid] = true;
		    /* can't continue here. ffi might be "f+f+i" and "ff+i" */
		    /*  and we need to mark both "f" and "ff" as used */
		}
	    }
	}
    continue_: ;
    }

    for ( i=cnt=0 ; i<sf->glyphcnt; ++i )
	if ( used[i] )
	    ++cnt;

    if ( cnt==0 ) {
	free(used);
return( NULL );
    }
    glyphs = galloc((cnt+1)*sizeof(SplineChar *));
    for ( i=cnt=0 ; i<sf->glyphcnt; ++i ) {
	if ( used[i] )
	    glyphs[cnt++] = sf->glyphs[i];
    }
    glyphs[cnt] = NULL;
    free(used);
return( glyphs );
}

SplineChar **SFGlyphsWithLigatureinLookup(SplineFont *sf,struct lookup_subtable *subtable) {
    uint8 *used = gcalloc(sf->glyphcnt,sizeof(uint8));
    SplineChar **glyphs, *sc;
    int i, cnt;
    PST *pst;
    sc=NULL;
    /* for ( i=0; i<sf->glyphcnt; ++i ) if ( SCWorthOutputting(sc = sf->glyphs[i]) ) { */
    /* 	for ( pst=sc->possub; pst!=NULL; pst=pst->next ) { */
    /* 	    if ( pst->subtable == subtable ) { */
    /* 		used[i] = true; */
    /* goto continue_; */
    /* 	    } */
    /* 	} */
    /* continue_: ; */
    /* } */
    /* Issue 866 : currently #define SCWorthOutputting(a) 1 so we drop it */
    for ( i=0; i<sf->glyphcnt; ++i ) {
      sc = sf->glyphs[i];
      if ( sc ) {
    	for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
	  if ( pst->subtable == subtable ) {
	    used[i] = true;
      goto continue_;
	  }
    	}
      continue_: ;
      }/* end if ( sc ) */
    }/*end for */

    for ( i=cnt=0 ; i<sf->glyphcnt; ++i )
	if ( used[i] )
	    ++cnt;

    if ( cnt==0 ) {
	free(used);
return( NULL );
    }

    glyphs = galloc((cnt+1)*sizeof(SplineChar *));
    for ( i=cnt=0 ; i<sf->glyphcnt; ++i ) {
	if ( used[i] )
	    glyphs[cnt++] = sf->glyphs[i];
    }
    glyphs[cnt] = NULL;
    free(used);
return( glyphs );
}

void SFFindUnusedLookups(SplineFont *sf) {
    OTLookup *test;
    struct lookup_subtable *sub;
    int gpos;
    AnchorClass *ac;
    AnchorPoint *ap;
    SplineChar *sc;
    KernPair *kp;
    PST *pst;
    int k,gid,isv;
    SplineFont *_sf = sf;
    sc=NULL;
    if ( _sf->cidmaster ) _sf = _sf->cidmaster;

    /* Some things are obvious. If a subtable consists of a kernclass or some */
    /*  such, then obviously it is used. But more distributed info takes more */
    /*  work. So mark anything easy as used, and anything difficult as unused */
    /* We'll work on the difficult things later */
    for ( gpos=0; gpos<2; ++gpos ) {
	for ( test = gpos ? _sf->gpos_lookups : _sf->gsub_lookups; test!=NULL; test = test->next ) {
	    for ( sub = test->subtables; sub!=NULL; sub=sub->next ) {
		if ( sub->kc!=NULL || sub->fpst!=NULL ) {
		    sub->unused = false;
	    continue;
		}
		sub->unused = true;
		/* We'll turn the following bit back on if there turns out */
		/*  to be an anchor class attached to it -- that is subtly */
		/*  different than being unused -- unused will be set if all */
		/*  acs are unused, this bit will be on if there are unused */
		/*  classes that still refer to us. */
		sub->anchor_classes = false;
	    }
	}
    }

    /* To be useful an anchor class must have both at least one base and one mark */
    /*  (for cursive anchors that means at least one entry and at least one exit) */
    /* Start by assuming the worst */
    for ( ac = _sf->anchor; ac!=NULL; ac=ac->next )
	ac->has_mark = ac->has_base = false;

    /* Ok, for each glyph, look at all lookups (or anchor classes) it affects */
    /*  and mark the appropriate parts of them as used */
    k = 0;
    do {
	sf = _sf->subfontcnt==0 ? _sf : _sf->subfonts[k];
	for ( gid=0; gid<sf->glyphcnt; ++gid ) if ( SCWorthOutputting(sc = sf->glyphs[gid]) ) {
	    for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) {
		switch ( ap->type ) {
		  case at_mark: case at_centry:
		    ap->anchor->has_mark = true;
		  break;
		  case at_basechar: case at_baselig: case at_basemark:
		  case at_cexit:
		    ap->anchor->has_base = true;
		  break;
		}
	    }
	    for ( isv=0; isv<2; ++isv ) {
		for ( kp= isv ? sc->kerns : sc->vkerns ; kp!=NULL; kp=kp->next ) {
		    if ( SCWorthOutputting(kp->sc))
			kp->subtable->unused = false;
		}
	    }
	    for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
		if ( pst->subtable==NULL )
	    continue;
		if ( !PSTValid(sf,pst))
	    continue;
		pst->subtable->unused = false;
	    }
	}
	++k;
    } while ( k<_sf->subfontcnt );

    /* Finally for any anchor class that has both a mark and a base then it is */
    /*  used, and its lookup is also used */
    /* Also, even if unused, as long as the anchor class exists we must keep */
    /*  the subtable around */
    for ( ac = _sf->anchor; ac!=NULL; ac=ac->next ) {
	ac->subtable->anchor_classes = true;
	if ( ac->has_mark && ac->has_base )
	    ac->subtable->unused = false;
    }

    /* Now for each lookup, a lookup is unused if ALL subtables are unused */
    for ( gpos=0; gpos<2; ++gpos ) {
	for ( test = gpos ? _sf->gpos_lookups : _sf->gsub_lookups; test!=NULL; test = test->next ) {
	    test->unused = test->empty = true;
	    for ( sub=test->subtables; sub!=NULL; sub=sub->next ) {
		if ( !sub->unused )
		    test->unused = false;
		if ( !sub->unused && !sub->anchor_classes ) {
		    test->empty = false;
	    break;
		}
	    }
	}
    }
}

void SFFindClearUnusedLookupBits(SplineFont *sf) {
    OTLookup *test;
    int gpos;

    for ( gpos=0; gpos<2; ++gpos ) {
	for ( test = gpos ? sf->gpos_lookups : sf->gsub_lookups; test!=NULL; test = test->next ) {
	    test->unused = false;
	    test->empty = false;
	    test->def_lang_checked = false;
	}
    }
}

static void SFRemoveAnchorPointsOfAC(SplineFont *sf,AnchorClass *ac) {
    int gid;
    SplineChar *sc;
    AnchorPoint *ap, *prev, *next;

    for ( gid=0; gid<sf->glyphcnt; ++gid ) if ( (sc = sf->glyphs[gid])!=NULL ) {
	for ( prev=NULL, ap=sc->anchor; ap!=NULL; ap=next ) {
	    next = ap->next;
	    if ( ap->anchor!=ac )
		prev = ap;
	    else {
		if ( prev==NULL )
		    sc->anchor = next;
		else
		    prev->next = next;
		ap->next = NULL;
		AnchorPointsFree(ap);
	    }
	}
    }
}

static void RemoveNestedReferences(SplineFont *sf,int isgpos,OTLookup *dying) {
    OTLookup *otl;
    struct lookup_subtable *sub;
    int i,j,k;
    (void)dying;
    for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl = otl->next ) {
	/* Reverse chaining tables do not reference lookups. The match pattern*/
	/*  is a (exactly one) coverage table, and each glyph in that table   */
	/*  as an inline replacement. There is no lookup to do the replacement*/
	/* (so we ignore it here) */
	if ( otl->lookup_type==gsub_context || otl->lookup_type==gsub_contextchain ||
		otl->lookup_type==gpos_context || otl->lookup_type==gpos_contextchain ) {
	    for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
		FPST *fpst = sub->fpst;
		for ( i=0; i<fpst->rule_cnt; ++i ) {
		    for ( j=0; j<fpst->rules[i].lookup_cnt; ++j ) {
			if ( fpst->rules[i].lookups[j].lookup == otl ) {
			    for ( k=j+1; k<fpst->rules[i].lookup_cnt; ++k )
				fpst->rules[i].lookups[k-1] = fpst->rules[i].lookups[k];
			    --fpst->rules[i].lookup_cnt;
			    --j;
			}
		    }
		}
	    }
	}
    }
}

void SFRemoveUnusedLookupSubTables(SplineFont *sf,
	int remove_incomplete_anchorclasses,
	int remove_unused_lookups) {
    int gpos;
    struct lookup_subtable *sub, *subnext, *prev;
    AnchorClass *ac, *acprev, *acnext;
    OTLookup *otl, *otlprev, *otlnext;
    otlprev=NULL;
    /* Presumes someone has called SFFindUnusedLookups first */

    if ( remove_incomplete_anchorclasses ) {
	for ( acprev=NULL, ac=sf->anchor; ac!=NULL; ac=acnext ) {
	    acnext = ac->next;
	    if ( ac->has_mark && ac->has_base )
		acprev = ac;
	    else {
		SFRemoveAnchorPointsOfAC(sf,ac);
		ac->next = NULL;
		AnchorClassesFree(ac);
		if ( acprev==NULL )
		    sf->anchor = acnext;
		else
		    acprev = acnext;
	    }
	}
    }

    for ( gpos=0; gpos<2; ++gpos ) {
	for ( otl = gpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl = otlnext ) {
	    otlnext = otl->next;
	    if ( remove_unused_lookups && (otl->empty ||
		    (otl->unused && remove_incomplete_anchorclasses)) ) {
		if ( otlprev!=NULL )
		    otlprev->next = otlnext;
		else if ( gpos )
		    sf->gpos_lookups = otlnext;
		else
		    sf->gsub_lookups = otlnext;
		RemoveNestedReferences(sf,gpos,otl);
		OTLookupFree(otl);
	    } else {
		for ( prev=NULL, sub=otl->subtables; sub!=NULL; sub=subnext ) {
		    subnext = sub->next;
		    if ( sub->unused &&
			    (!sub->anchor_classes ||
			      remove_incomplete_anchorclasses )) {
			if ( prev==NULL )
			    otl->subtables = subnext;
			else
			    prev->next = subnext;
			free(sub->subtable_name);
			chunkfree(sub,sizeof(*sub));
		    } else
			prev = sub;
		}
	    }
	}
    }
}

void SFRemoveLookupSubTable(SplineFont *sf,struct lookup_subtable *sub) {
    OTLookup *otl = sub->lookup;
    struct lookup_subtable *subprev, *subtest;

    if ( sf->cidmaster!=NULL ) sf = sf->cidmaster;

    if ( sub->fpst!=NULL ) {
	FPST *prev = NULL, *test;
	for ( test=sf->possub; test!=NULL && test!=sub->fpst; prev=test, test=test->next );
	if ( prev==NULL )
	    sf->possub = sub->fpst->next;
	else
	    prev->next = sub->fpst->next;
	sub->fpst->next = NULL;
	FPSTFree(sub->fpst);
	sub->fpst = NULL;
    } else if ( sub->kc!=NULL ) {
	KernClass *prev = NULL, *test;
	for ( test=sf->kerns; test!=NULL && test!=sub->kc; prev=test, test=test->next );
	if ( test!=NULL ) {
	    if ( prev==NULL )
		sf->kerns = sub->kc->next;
	    else
		prev->next = sub->kc->next;
	} else {
	    for ( prev=NULL,test=sf->vkerns; test!=NULL && test!=sub->kc; prev=test, test=test->next );
	    if ( prev==NULL )
		sf->vkerns = sub->kc->next;
	    else
		prev->next = sub->kc->next;
	}
	sub->kc->next = NULL;
	KernClassListFree(sub->kc);
	sub->kc = NULL;
    } else if ( otl->lookup_type==gpos_cursive || otl->lookup_type==gpos_mark2base ||
	    otl->lookup_type==gpos_mark2ligature || otl->lookup_type==gpos_mark2mark ) {
	AnchorClass *ac, *acnext;
	for ( ac=sf->anchor; ac!=NULL; ac=acnext ) {
	    acnext = ac->next;
	    if ( ac->subtable==sub )
		SFRemoveAnchorClass(sf,ac);
	}
    } else {
	int i,k,v;
	SplineChar *sc;
	SplineFont *_sf;
	PST *pst, *prev, *next;
	KernPair *kp, *kpprev, *kpnext;
	k=0; i=0;
	do {
	    _sf = sf->subfontcnt==0 ? sf : sf->subfonts[i];
	    for ( i=0; i<_sf->glyphcnt; ++i ) if ( (sc=_sf->glyphs[i])!=NULL ) {
		for ( pst=sc->possub, prev=NULL ; pst!=NULL; pst=next ) {
		    next = pst->next;
		    if ( pst->subtable==sub ) {
			if ( prev==NULL )
			    sc->possub = next;
			else
			    prev->next = next;
			pst->next = NULL;
			PSTFree(pst);
		    } else
			prev = pst;
		}
		for ( v=0; v<2; ++v ) {
		    for ( kp=v ? sc->vkerns : sc->kerns, kpprev=NULL ; kp!=NULL; kp=kpnext ) {
			kpnext = kp->next;
			if ( kp->subtable==sub ) {
			    if ( kpprev!=NULL )
				kpprev->next = kpnext;
			    else if ( v )
				sc->vkerns = kpnext;
			    else
				sc->kerns = kpnext;
			    kp->next = NULL;
			    KernPairsFree(kp);
			} else
			    kpprev = kp;
		    }
		}
	    }
	    ++k;
	} while ( k<sf->subfontcnt );
    }

    subprev = NULL;
    for ( subtest = otl->subtables; subtest!=NULL && subtest!=sub; subprev = subtest, subtest=subtest->next );
    if ( subprev==NULL )
	otl->subtables = sub->next;
    else
	subprev->next = sub->next;
    free(sub->subtable_name);
    free(sub->suffix);
    chunkfree(sub,sizeof(struct lookup_subtable));
}
	
void SFRemoveLookup(SplineFont *sf,OTLookup *otl) {
    OTLookup *test, *prev;
    int isgpos;
    struct lookup_subtable *sub, *subnext;

    if ( sf->cidmaster ) sf = sf->cidmaster;

    for ( sub = otl->subtables; sub!=NULL; sub=subnext ) {
	subnext = sub->next;
	SFRemoveLookupSubTable(sf,sub);
    }

    for ( prev=NULL, test=sf->gpos_lookups; test!=NULL && test!=otl; prev=test, test=test->next );
    if ( test==NULL ) {
	isgpos = false;
	for ( prev=NULL, test=sf->gsub_lookups; test!=NULL && test!=otl; prev=test, test=test->next );
    } else
	isgpos = true;
    if ( prev!=NULL )
	prev->next = otl->next;
    else if ( isgpos )
	sf->gpos_lookups = otl->next;
    else
	sf->gsub_lookups = otl->next;

    RemoveNestedReferences(sf,isgpos,otl);

    otl->next = NULL;
    OTLookupFree(otl);
}

struct lookup_subtable *SFFindLookupSubtable(SplineFont *sf,char *name) {
    int isgpos;
    OTLookup *otl;
    struct lookup_subtable *sub;

    if ( sf->cidmaster ) sf = sf->cidmaster;

    if ( name==NULL )
return( NULL );

    for ( isgpos=0; isgpos<2; ++isgpos ) {
	for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups ; otl!=NULL; otl=otl->next ) {
	    for ( sub = otl->subtables; sub!=NULL; sub=sub->next ) {
		if ( strcmp(name,sub->subtable_name)==0 )
return( sub );
	    }
	}
    }
return( NULL );
}

struct lookup_subtable *SFFindLookupSubtableAndFreeName(SplineFont *sf,char *name) {
    struct lookup_subtable *sub = SFFindLookupSubtable(sf,name);
    free(name);
return( sub );
}

OTLookup *SFFindLookup(SplineFont *sf,char *name) {
    int isgpos;
    OTLookup *otl;

    if ( sf->cidmaster ) sf = sf->cidmaster;

    if ( name==NULL )
return( NULL );

    for ( isgpos=0; isgpos<2; ++isgpos ) {
	for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups ; otl!=NULL; otl=otl->next ) {
	    if ( strcmp(name,otl->lookup_name)==0 )
return( otl );
	}
    }
return( NULL );
}

void FListAppendScriptLang(FeatureScriptLangList *fl,uint32 script_tag,uint32 lang_tag) {
    struct scriptlanglist *sl;
    int l;

    for ( sl = fl->scripts; sl!=NULL && sl->script!=script_tag; sl=sl->next );
    if ( sl==NULL ) {
	sl = chunkalloc(sizeof(struct scriptlanglist));
	sl->script = script_tag;
	sl->next = fl->scripts;
	fl->scripts = sl;
    }
    for ( l=0; l<MAX_LANG && l<sl->lang_cnt && sl->langs[l]!=lang_tag; ++l );
    if ( l>=MAX_LANG && l<sl->lang_cnt ) {
	while ( l<sl->lang_cnt && sl->morelangs[l-MAX_LANG]!=lang_tag )
	    ++l;
    }
    if ( l>=sl->lang_cnt ) {
	if ( l<MAX_LANG )
	    sl->langs[l] = lang_tag;
	else {
	    if ( l%MAX_LANG == 0 )
		sl->morelangs = grealloc(sl->morelangs,l*sizeof(uint32));
		/* We've just allocated MAX_LANG-1 more than we need */
		/*  so we don't do quite some many allocations */
	    sl->morelangs[l-MAX_LANG] = lang_tag;
	}
	++sl->lang_cnt;
    }
}

void FListsAppendScriptLang(FeatureScriptLangList *fl,uint32 script_tag,uint32 lang_tag) {
    while ( fl!=NULL ) {
	FListAppendScriptLang(fl,script_tag,lang_tag);
	fl=fl->next;
    }
}

char *SuffixFromTags(FeatureScriptLangList *fl) {
    static struct { uint32 tag; char *suffix; } tags2suffix[] = {
	{ CHR('v','r','t','2'), "vert" },	/* Will check for vrt2 later */
	{ CHR('o','n','u','m'), "oldstyle" },
	{ CHR('s','u','p','s'), "superior" },
	{ CHR('s','u','b','s'), "inferior" },
	{ CHR('s','w','s','h'), "swash" },
	{ CHR('f','w','i','d'), "full" },
	{ CHR('h','w','i','d'), "hw" },
	{ 0 , NULL }
	};
    int i;

    while ( fl!=NULL ) {
	for ( i=0; tags2suffix[i].tag!=0; ++i )
	    if ( tags2suffix[i].tag==fl->featuretag )
return( copy( tags2suffix[i].suffix ));
	fl = fl->next;
    }
return( NULL );
}
#ifdef LUA_FF_LIB
/* ma = State Machine, un = Unknown */
char *lookup_type_names[2][10] =
            { { "us", "ss", "ms", "as", "ls", "cs", "ks", "es", "rk" },
              { "up", "sp", "pp", "ca", "mb", "ml", "mm", "cp", "kp","ep" }};
#else
char *lookup_type_names[2][10] =
	    { { N_("Undefined substitution"), N_("Single Substitution"), N_("Multiple Substitution"),
		N_("Alternate Substitution"), N_("Ligature Substitution"), N_("Contextual Substitution"),
		N_("Contextual Chaining Substitution"), N_("Extension"),
		N_("Reverse Contextual Chaining Substitution") },
	      { N_("Undefined positioning"), N_("Single Positioning"), N_("Pairwise Positioning (kerning)"),
		N_("Cursive attachment"), N_("Mark to base attachment"),
		N_("Mark to Ligature attachment"), N_("Mark to Mark attachment"),
		N_("Contextual Positioning"), N_("Contextual Chaining Positioning"),
		N_("Extension") }};
#endif

/* This is a non-ui based copy of a similar list in lookupui.c */
static struct {
    char *text;
    uint32 tag;
} localscripts[] = {
    { N_("Arabic"), CHR('a','r','a','b') },
    { N_("Aramaic"), CHR('a','r','a','m') }, /* Only in ff  */
    { N_("Armenian"), CHR('a','r','m','n') },
    { N_("Avestan"), CHR('a','v','e','s') }, /* Only in ff  */
    { N_("Balinese"), CHR('b','a','l','i') },
    { N_("Batak"), CHR('b','a','t','k') },
    { N_("Bengali"), CHR('b','e','n','g') },
    { N_("Bengali2"), CHR('b','n','g','2') },
    { N_("Bliss Symbolics"), CHR('b','l','i','s') }, /* Only in ff  */
    { N_("Bopomofo"), CHR('b','o','p','o') },
    { N_("Brāhmī"), CHR('b','r','a','h') },
    { N_("Braille"), CHR('b','r','a','i') },
    { N_("Buginese"), CHR('b','u','g','i') },
    { N_("Buhid"), CHR('b','u','h','d') },
    { N_("Byzantine Music"), CHR('b','y','z','m') },
    { N_("Canadian Syllabics"), CHR('c','a','n','s') },
    { N_("Carian"), CHR('c','a','r','i') },
    { N_("Cherokee"), CHR('c','h','a','m') },
    { N_("Cham"), CHR('c','h','a','m') },
    { N_("Cherokee"), CHR('c','h','e','r') },
    { N_("Cirth"), CHR('c','i','r','t') }, /* Only in ff  */
    { N_("CJK Ideographic"), CHR('h','a','n','i') },
    { N_("Coptic"), CHR('c','o','p','t') },
    { N_("Cypro-Minoan"), CHR('c','p','r','t') },
    { N_("Cypriot syllabary"), CHR('c','p','m','n') }, /* Only in ff  */
    { N_("Cyrillic"), CHR('c','y','r','l') },
    { N_("Default"), CHR('D','F','L','T') },
    { N_("Deseret (Mormon)"), CHR('d','s','r','t') },
    { N_("Devanagari"), CHR('d','e','v','a') },
    { N_("Devanagari2"), CHR('d','e','v','2') },
/*  { N_("Egyptian demotic"), CHR('e','g','y','d') }, */
/*  { N_("Egyptian hieratic"), CHR('e','g','y','h') }, */
/* GT: Someone asked if FontForge actually was prepared generate hieroglyph output */
/* GT: because of this string. No. But OpenType and Unicode have placeholders for */
/* GT: dealing with these scripts against the day someone wants to use them. So */
/* GT: FontForge must be prepared to deal with those placeholders if nothing else. */
/*  { N_("Egyptian hieroglyphs"), CHR('e','g','y','p') }, */
    { N_("Ethiopic"), CHR('e','t','h','i') },
    { N_("Georgian"), CHR('g','e','o','r') },
    { N_("Glagolitic"), CHR('g','l','a','g') },
    { N_("Gothic"), CHR('g','o','t','h') },
    { N_("Greek"), CHR('g','r','e','k') },
    { N_("Gujarati"), CHR('g','u','j','r') },
    { N_("Gujarati2"), CHR('g','j','r','2') },
    { N_("Gurmukhi"), CHR('g','u','r','u') },
    { N_("Gurmukhi2"), CHR('g','u','r','2') },
    { N_("Hangul Jamo"), CHR('j','a','m','o') },
    { N_("Hangul"), CHR('h','a','n','g') },
    { N_("Hanunóo"), CHR('h','a','n','o') },
    { N_("Hebrew"), CHR('h','e','b','r') },
/*  { N_("Pahawh Hmong"), CHR('h','m','n','g') },*/
/*  { N_("Indus (Harappan)"), CHR('i','n','d','s') },*/
    { N_("Javanese"), CHR('j','a','v','a') },
    { N_("Kayah Li"), CHR('k','a','l','i') },
    { N_("Hiragana & Katakana"), CHR('k','a','n','a') },
    { N_("Kharoṣṭhī"), CHR('k','h','a','r') },
    { N_("Kannada"), CHR('k','n','d','a') },
    { N_("Kannada2"), CHR('k','n','d','2') },
    { N_("Khmer"), CHR('k','h','m','r') },
    { N_("Kharosthi"), CHR('k','h','a','r') },
    { N_("Lao") , CHR('l','a','o',' ') },
    { N_("Latin"), CHR('l','a','t','n') },
    { N_("Lepcha (Róng)"), CHR('l','e','p','c') },
    { N_("Limbu"), CHR('l','i','m','b') },	/* Not in ISO 15924 !!!!!, just guessing */
    { N_("Linear A"), CHR('l','i','n','a') }, /* Only in ff  */
    { N_("Linear B"), CHR('l','i','n','b') },
    { N_("Lycian"), CHR('l','y','c','i') },
    { N_("Lydian"), CHR('l','y','d','i') },
    { N_("Mandaean"), CHR('m','a','n','d') },
/*  { N_("Mayan hieroglyphs"), CHR('m','a','y','a') },*/
    { N_("Malayālam"), CHR('m','l','y','m') },
    { N_("Malayālam2"), CHR('m','l','y','2') }, /* Only in ff, should be  mlm2 */
    { N_("Mathematical Alphanumeric Symbols"), CHR('m','a','t','h') },
    { N_("Mongolian"), CHR('m','o','n','g') },
    { N_("Musical"), CHR('m','u','s','i') }, /* Only in ff, should be musc   */
    { N_("Myanmar"), CHR('m','y','m','r') },
    { N_("New Tai Lue"), CHR('t','a','l','u') },
    { N_("N'Ko"), CHR('n','k','o',' ') },
    { N_("Ogham"), CHR('o','g','a','m') },
    { N_("Ol Chiki"), CHR('o','l','c','k') },
    { N_("Old Italic (Etruscan, Oscan, etc.)"), CHR('i','t','a','l') },
    { N_("Old Permic"), CHR('p','e','r','m') }, /* Only in ff  */
    { N_("Old Persian cuneiform"), CHR('x','p','e','o') },
    { N_("Oriya"), CHR('o','r','y','a') },
    { N_("Oriya2"), CHR('o','r','y','2') },
    { N_("Osmanya"), CHR('o','s','m','a') },
    { N_("Pahlavi"), CHR('p','a','l','v') }, /* Only  in ff */
    { N_("Phags-pa"), CHR('p','h','a','g') },
    { N_("Phoenician"), CHR('p','h','n','x') },
    { N_("Phaistos"), CHR('p','h','s','t') },  /* Only in ff  */
    { N_("Pollard Phonetic"), CHR('p','l','r','d') }, /* Only in ff  */
    { N_("Rejang"), CHR('r','j','n','g') },
    { N_("Rongorongo"), CHR('r','o','r','o') }, /* Only in ff  */
    { N_("Runic"), CHR('r','u','n','r') },
    { N_("Saurashtra"), CHR('s','a','u','r') },
    { N_("Shavian"), CHR('s','h','a','w') },
    { N_("Sinhala"), CHR('s','i','n','h') },
    { N_("Sumero-Akkadian Cuneiform"), CHR('x','s','u','x') },
    { N_("Sundanese"), CHR('s','u','n','d') },
    { N_("Syloti Nagri"), CHR('s','y','l','o') },
    { N_("Syriac"), CHR('s','y','r','c') },
    { N_("Tagalog"), CHR('t','g','l','g') },
    { N_("Tagbanwa"), CHR('t','a','g','b') },
    { N_("Tai Le"), CHR('t','a','l','e') },	/* Not in ISO 15924 !!!!!, just guessing */
    { N_("Tai Lu"), CHR('t','a','l','a') },	/* Not in ISO 15924 !!!!!, just guessing */
    { N_("Tamil"), CHR('t','a','m','l') },
    { N_("Tamil2"), CHR('t','m','l','2') },
    { N_("Telugu"), CHR('t','e','l','u') },
    { N_("Telugu2"), CHR('t','e','l','2') },
    { N_("Tengwar"), CHR('t','e','n','g') },    /* Only in ff  */
    { N_("Thaana"), CHR('t','h','a','a') },
    { N_("Thai"), CHR('t','h','a','i') },
    { N_("Tibetan"), CHR('t','i','b','t') },
    { N_("Tifinagh (Berber)"), CHR('t','f','n','g') },
    { N_("Ugaritic"), CHR('u','g','r','t') },	/* Only in ff, should be ugar  */
    { N_("Vai"), CHR('v','a','i',' ') },
/*  { N_("Visible Speech"), CHR('v','i','s','p') },*/
    { N_("Cuneiform, Ugaritic"), CHR('x','u','g','a') }, /* Only in ff  */
    { N_("Yi")  , CHR('y','i',' ',' ') },
/*  { N_("Private Use Script 1"), CHR('q','a','a','a') },*/
/*  { N_("Private Use Script 2"), CHR('q','a','a','b') },*/
/*  { N_("Undetermined Script"), CHR('z','y','y','y') },*/
/*  { N_("Uncoded Script"), CHR('z','z','z','z') },*/
    { NULL, 0 }
};

void LookupInit(void) {
    static int done = false;
    int i, j;

    if ( done )
return;
    done = true;
    for ( j=0; j<2; ++j ) {
	for ( i=0; i<10; ++i )
	    if ( lookup_type_names[j][i]!=NULL )
		lookup_type_names[j][i] = _((char *) lookup_type_names[j][i]);
    }
    for ( i=0; localscripts[i].text!=NULL; ++i )
	localscripts[i].text = _(localscripts[i].text);
    for ( i=0; friendlies[i].friendlyname!=NULL; ++i )
	friendlies[i].friendlyname = _(friendlies[i].friendlyname);
}

char *TagFullName(SplineFont *sf,uint32 tag, int onlyifknown) {
    char ubuf[200], *end = ubuf+sizeof(ubuf);
    int k;

	unsigned stag = tag;
	if ( tag==CHR('n','u','t','f') )	/* early name that was standardize later as... */
	    stag = CHR('a','f','r','c');	/*  Stood for nut fractions. "nut" meaning "fits in an en" in old typography-speak => vertical fractions rather than diagonal ones */
	if ( tag==REQUIRED_FEATURE ) {
	    strcpy(ubuf,_("Required Feature"));
	} else {
	    LookupInit();
	    for ( k=0; friendlies[k].tag!=0; ++k ) {
		if ( friendlies[k].tag == stag )
	    break;
	    }
	    ubuf[0] = '\'';
	    ubuf[1] = tag>>24;
	    ubuf[2] = (tag>>16)&0xff;
	    ubuf[3] = (tag>>8)&0xff;
	    ubuf[4] = tag&0xff;
	    ubuf[5] = '\'';
	    ubuf[6] = ' ';
	    if ( friendlies[k].tag!=0 )
		strncpy(ubuf+7, (char *) friendlies[k].friendlyname,end-ubuf-7);
	    else if ( onlyifknown )
return( NULL );
	    else
		ubuf[7]='\0';
	}
return( copy( ubuf ));
}


void NameOTLookup(OTLookup *otl,SplineFont *sf) {
    char *userfriendly = NULL, *script;
    FeatureScriptLangList *fl;
    char *lookuptype;
    char *format;
    struct lookup_subtable *subtable;
    int k;
    (void)sf;

    LookupInit();

    if ( otl->lookup_name==NULL ) {
	for ( k=0; k<2; ++k ) {
#ifndef LUA_FF_LIB
	    for ( fl=otl->features; fl!=NULL ; fl=fl->next ) {
		/* look first for a feature attached to a default language */
		if ( k==1 || DefaultLangTagInScriptList(fl->scripts,false)!=NULL ) {
		    userfriendly = TagFullName(sf,fl->featuretag, true);
		    if ( userfriendly!=NULL )
	    break;
		}
	    }
	    if ( userfriendly!=NULL )
	break;
#endif
	}
	if ( userfriendly==NULL ) {
	    if ( (otl->lookup_type>>8)<2 && (otl->lookup_type&0xff)<10 )
		lookuptype = _(lookup_type_names[otl->lookup_type>>8][otl->lookup_type&0xff]);
	    else
#ifdef LUA_FF_LIB
        lookuptype = "un";
#else
		lookuptype = _("Unknown");
#endif
	    for ( fl=otl->features; fl!=NULL; fl=fl->next );
	    if ( fl==NULL )
		userfriendly = copy(lookuptype);
	    else {
		userfriendly = galloc( strlen(lookuptype) + 40);
#ifdef LUA_FF_LIB
        if ( (otl->lookup_type&0xff)>= 0xf0 ) {
          sprintf( userfriendly, "%s_<%d,%d>", lookuptype,
                 (fl->featuretag>>16), (fl->featuretag&0xffff));
        } else {
          sprintf( userfriendly, "%s_%c%c%c%c", lookuptype,
		    fl->featuretag>>24,
		    fl->featuretag>>16,
		    fl->featuretag>>8 ,
		    fl->featuretag );
        }
#else
		sprintf( userfriendly, "%s '%c%c%c%c'", lookuptype,
		    fl->featuretag>>24,
		    fl->featuretag>>16,
		    fl->featuretag>>8 ,
		    fl->featuretag );
#endif
	    }
	}
	script = NULL;
	if ( fl==NULL ) fl = otl->features;
	if ( fl!=NULL && fl->scripts!=NULL ) {
	    char buf[8];
	    int j;
	    struct scriptlanglist *sl, *found, *found2;
	    uint32 script_tag = fl->scripts->script;
	    found = found2 = NULL;
	    for ( sl = fl->scripts; sl!=NULL; sl=sl->next ) {
		if ( sl->script == DEFAULT_SCRIPT )
		    /* Ignore it */;
		else if ( DefaultLangTagInOneScriptList(sl)) {
		    if ( found==NULL )
			found = sl;
		    else {
			found = found2 = NULL;
	    break;
		    }
		} else if ( found2 == NULL )
		    found2 = sl;
		else
		    found2 = (struct scriptlanglist *) -1;
	    }
	    if ( found==NULL && found2!=NULL && found2 != (struct scriptlanglist *) -1 )
		found = found2;
	    if ( found!=NULL ) {
		script_tag = found->script;
		for ( j=0; localscripts[j].text!=NULL && script_tag!=localscripts[j].tag; ++j ) {;}
#ifdef LUA_FF_LIB
            buf[0] = fl->scripts->script>>24;
            buf[1] = (fl->scripts->script>>16)&0xff;
            buf[2] = (fl->scripts->script>>8)&0xff;
            buf[3] = fl->scripts->script&0xff;
            buf[4] = 0;
		    script = copy(buf);
#else
		if ( localscripts[j].text!=NULL )
		    script = copy( _((char *) localscripts[j].text) );
		else {
		    buf[0] = '\'';
		    buf[1] = fl->scripts->script>>24;
		    buf[2] = (fl->scripts->script>>16)&0xff;
		    buf[3] = (fl->scripts->script>>8)&0xff;
		    buf[4] = fl->scripts->script&0xff;
		    buf[5] = '\'';
		    buf[6] = 0;
		    script = copy(buf);
		}
#endif
	    }
	}
	if ( script!=NULL ) {
/* GT: This string is used to generate a name for each OpenType lookup. */
/* GT: The %s will be filled with the user friendly name of the feature used to invoke the lookup */
/* GT: The second %s (if present) is the script */
/* GT: While the %d is the index into the lookup list and is used to disambiguate it */
/* GT: In case that is needed */
#ifdef LUA_FF_LIB
        format = "%s_%s_l_%d";
#else
	    format = _("%s in %s lookup %d");
#endif
	    otl->lookup_name = galloc( strlen(userfriendly)+strlen(format)+strlen(script)+10 );
	    sprintf( otl->lookup_name, format, userfriendly, script, otl->lookup_index );
	} else {
#ifdef LUA_FF_LIB
        format = "%s_l_%d";
#else
	    format = _("%s lookup %d");
#endif
	    otl->lookup_name = galloc( strlen(userfriendly)+strlen(format)+10 );
	    sprintf( otl->lookup_name, format, userfriendly, otl->lookup_index );
	}
	free(script);
	free(userfriendly);
    }

    if ( otl->subtables==NULL )
	/* IError( _("Lookup with no subtables"))*/;
    else {
	int cnt = 0;
	for ( subtable = otl->subtables; subtable!=NULL; subtable=subtable->next, ++cnt )
		if ( subtable->subtable_name==NULL ) {
	    if ( subtable==otl->subtables && subtable->next==NULL )
/* GT: This string is used to generate a name for an OpenType lookup subtable. */
/* GT:  %s is the lookup name */
#ifdef LUA_FF_LIB
        format = "%s_s";
#else
		format = _("%s subtable");
#endif
	    else if ( subtable->per_glyph_pst_or_kern )
/* GT: This string is used to generate a name for an OpenType lookup subtable. */
/* GT:  %s is the lookup name, %d is the index of the subtable in the lookup */
#ifdef LUA_FF_LIB
        format = "%s_g_%d";
#else
		format = _("%s per glyph data %d");
#endif
	    else if ( subtable->kc!=NULL )
#ifdef LUA_FF_LIB
        format = "%s_k_%d";
#else
		format = _("%s kerning class %d");
#endif
	    else if ( subtable->fpst!=NULL )
#ifdef LUA_FF_LIB
        format = "%s_c_%d";
#else
		format = _("%s contextual %d");
#endif
	    else if ( subtable->anchor_classes )
#ifdef LUA_FF_LIB
        format = "%s_a_%d";
#else
		format = _("%s anchor %d");
#endif
	    else {
		IError("Subtable status not filled in for %dth subtable of %s", cnt, otl->lookup_name );
		format = "%s !!!!!!!! %d";
	    }
	    subtable->subtable_name = galloc( strlen(otl->lookup_name)+strlen(format)+10 );
	    sprintf( subtable->subtable_name, format, otl->lookup_name, cnt );
	}
    }
    if ( otl->lookup_type==gsub_ligature ) {
	for ( fl=otl->features; fl!=NULL; fl=fl->next )
	    if ( fl->featuretag==CHR('l','i','g','a') || fl->featuretag==CHR('r','l','i','g'))
		otl->store_in_afm = true;
    }

    if ( otl->lookup_type==gsub_single )
	for ( subtable = otl->subtables; subtable!=NULL; subtable=subtable->next )
	    subtable->suffix = SuffixFromTags(otl->features);
}

static void LangOrder(struct scriptlanglist *sl) {
    int i,j;
    uint32 lang, lang2;

    for ( i=0; i<sl->lang_cnt; ++i ) {
	lang = i<MAX_LANG ? sl->langs[i] : sl->morelangs[i-MAX_LANG];
	for ( j=i+1; j<sl->lang_cnt; ++j ) {
	    lang2 = j<MAX_LANG ? sl->langs[j] : sl->morelangs[j-MAX_LANG];
	    if ( lang>lang2 ) {
		if ( i<MAX_LANG )
		    sl->langs[i] = lang2;
		else
		    sl->morelangs[i-MAX_LANG] = lang2;
		if ( j<MAX_LANG )
		    sl->langs[j] = lang;
		else
		    sl->morelangs[j-MAX_LANG] = lang;
		lang = lang2;
	    }
	}
    }
}
	
static struct scriptlanglist *SLOrder(struct scriptlanglist *sl) {
    int i,j, cnt;
    struct scriptlanglist *sl2, *space[30], **allocked=NULL, **test = space;

    for ( sl2=sl, cnt=0; sl2!=NULL; sl2=sl2->next, ++cnt )
	LangOrder(sl2);
    if ( cnt<=1 )
return( sl );
    if ( cnt>30 )
	test = allocked = galloc(cnt*sizeof(struct scriptlanglist *));
    for ( sl2=sl, cnt=0; sl2!=NULL; sl2=sl2->next, ++cnt )
	test[cnt] = sl2;
    for ( i=0; i<cnt; ++i ) for ( j=i+1; j<cnt; ++j ) {
	if ( test[i]->script > test[j]->script ) {
	    struct scriptlanglist *temp;
	    temp = test[i];
	    test[i] = test[j];
	    test[j] = temp;
	}
    }
    sl = test[0];
    for ( i=1; i<cnt; ++i )
	test[i-1]->next = test[i];
    test[i-1]->next = NULL;
    free( allocked );
return( sl );
}
    
FeatureScriptLangList *FLOrder(FeatureScriptLangList *fl) {
    int i,j, cnt;
    FeatureScriptLangList *fl2, *space[30], **allocked=NULL, **test = space;

    for ( fl2=fl, cnt=0; fl2!=NULL; fl2=fl2->next, ++cnt )
	fl2->scripts = SLOrder(fl2->scripts);
    if ( cnt<=1 )
return( fl );
    if ( cnt>30 )
	test = allocked = galloc(cnt*sizeof(FeatureScriptLangList *));
    for ( fl2=fl, cnt=0; fl2!=NULL; fl2=fl2->next, ++cnt )
	test[cnt] = fl2;
    for ( i=0; i<cnt; ++i ) for ( j=i+1; j<cnt; ++j ) {
	if ( test[i]->featuretag > test[j]->featuretag ) {
	    FeatureScriptLangList *temp;
	    temp = test[i];
	    test[i] = test[j];
	    test[j] = temp;
	}
    }
    fl = test[0];
    for ( i=1; i<cnt; ++i )
	test[i-1]->next = test[i];
    test[i-1]->next = NULL;
    free( allocked );
return( fl );
}

struct scriptlanglist *SLCopy(struct scriptlanglist *sl) {
    struct scriptlanglist *newsl;

    newsl = chunkalloc(sizeof(struct scriptlanglist));
    *newsl = *sl;
    newsl->next = NULL;

    if ( sl->lang_cnt>MAX_LANG ) {
	newsl->morelangs = galloc((newsl->lang_cnt-MAX_LANG)*sizeof(uint32));
	memcpy(newsl->morelangs,sl->morelangs,(newsl->lang_cnt-MAX_LANG)*sizeof(uint32));
    }
return( newsl );
}

struct scriptlanglist *SListCopy(struct scriptlanglist *sl) {
    struct scriptlanglist *head=NULL, *last=NULL, *cur;

    for ( ; sl!=NULL; sl=sl->next ) {
	cur = SLCopy(sl);
	if ( head==NULL )
	    head = cur;
	else
	    last->next = cur;
	last = cur;
    }
return( head );
}

FeatureScriptLangList *FeatureListCopy(FeatureScriptLangList *fl) {
    FeatureScriptLangList *newfl;

    if ( fl==NULL )
return( NULL );

    newfl = chunkalloc(sizeof(FeatureScriptLangList));
    *newfl = *fl;
    newfl->next = NULL;

    newfl->scripts = SListCopy(fl->scripts);
return( newfl );
}
    
static void LangMerge(struct scriptlanglist *into, struct scriptlanglist *from) {
    int i,j;
    uint32 flang, tlang;

    for ( i=0 ; i<from->lang_cnt; ++i ) {
	flang = i<MAX_LANG ? from->langs[i] : from->morelangs[i-MAX_LANG];
	for ( j=0; j<into->lang_cnt; ++j ) {
	    tlang = j<MAX_LANG ? into->langs[j] : into->morelangs[j-MAX_LANG];
	    if ( tlang==flang )
	break;
	}
	if ( j==into->lang_cnt ) {
	    if ( into->lang_cnt<MAX_LANG )
		into->langs[into->lang_cnt++] = flang;
	    else {
		into->morelangs = grealloc(into->morelangs,(into->lang_cnt+1-MAX_LANG)*sizeof(uint32));
		into->morelangs[into->lang_cnt++-MAX_LANG] = flang;
	    }
	}
    }
}

void SLMerge(FeatureScriptLangList *into, struct scriptlanglist *fsl) {
    struct scriptlanglist *isl;

    for ( ; fsl!=NULL; fsl = fsl->next ) {
	for ( isl=into->scripts; isl!=NULL; isl=isl->next ) {
	    if ( fsl->script==isl->script )
	break;
	}
	if ( isl!=NULL )
	    LangMerge(isl,fsl);
	else {
	    isl = SLCopy(fsl);
	    isl->next = into->scripts;
	    into->scripts = isl;
	}
    }
}

void FLMerge(OTLookup *into, OTLookup *from) {
    /* Merge the feature list from "from" into "into" */
    FeatureScriptLangList *ifl, *ffl;

    /* first check for common featuretags and merge the scripts of each */
    for ( ffl = from->features; ffl!=NULL; ffl = ffl->next ) {
	for ( ifl=into->features; ifl!=NULL; ifl=ifl->next ) {
	    if ( ffl->featuretag==ifl->featuretag )
	break;
	}
	if ( ifl!=NULL )
	    SLMerge(ffl,ifl->scripts);
	else {
	    ifl = FeatureListCopy(ffl);
	    ifl->next = into->features;
	    into->features = ifl;
	}
    }
    into->features = FLOrder(into->features);
}

void SFSubTablesMerge(SplineFont *_sf,struct lookup_subtable *subfirst,
	struct lookup_subtable *subsecond) {
    unsigned lookup_type = subfirst->lookup->lookup_type;
    int gid,k,isv;
    SplineChar *sc;
    SplineFont *sf = _sf;
    PST *pst, *fpst, *spst, *pstprev, *pstnext;
    KernPair *fkp, *skp, *kpprev, *kpnext;
    AnchorClass *ac;

    if ( lookup_type != subsecond->lookup->lookup_type ) {
	IError("Attempt to merge lookup subtables with mismatch types");
return;
    }
    if ( lookup_type != gsub_single &&
	    lookup_type != gsub_multiple &&
	    lookup_type != gsub_alternate &&
	    lookup_type != gsub_ligature &&
	    lookup_type != gpos_single &&
	    lookup_type != gpos_pair &&
	    lookup_type != gpos_cursive &&
	    lookup_type != gpos_mark2base &&
	    lookup_type != gpos_mark2ligature &&
	    lookup_type != gpos_mark2mark ) {
	IError("Attempt to merge lookup subtables with bad types");
return;
    } else if ( subfirst->kc!=NULL || subsecond->kc != NULL ) {
	IError("Attempt to merge lookup subtables with kerning classes");
return;
    }

    if ( lookup_type==gpos_cursive || lookup_type==gpos_mark2base ||
	    lookup_type==gpos_mark2ligature || lookup_type==gpos_mark2mark ) {
	for ( ac = sf->anchor; ac!=NULL ; ac=ac->next )
	    if ( ac->subtable == subsecond )
		ac->subtable = subfirst;
    } else {
	k=0;
	do {
	    sf = _sf->subfontcnt==0 ? _sf : _sf->subfonts[k];
	    for ( gid=0; gid<sf->glyphcnt; ++gid ) if ( (sc=sf->glyphs[gid])!=NULL ) {
		if ( lookup_type==gsub_single || lookup_type==gsub_multiple ||
			lookup_type==gsub_alternate || lookup_type==gpos_single ) {
		    fpst = spst = NULL;
		    for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
			if ( pst->subtable == subfirst ) {
			    fpst = pst;
			    if ( spst!=NULL )
		    break;
			} else if ( pst->subtable == subsecond ) {
			    spst = pst;
			    if ( fpst!=NULL )
		    break;
			}
		    }
		    if ( fpst==NULL && spst!=NULL )
			spst->subtable = subfirst;
		    else if ( spst!=NULL ) {
			LogError(_("The glyph, %s, contains a %s from %s and one from %s.\nThe one from %s will be removed.\n"),
				sc->name,
			        lookup_type==gpos_single ? _("positioning") : _("substitution"),
			        subfirst->subtable_name, subsecond->subtable_name,
			        subsecond->subtable_name );
			pstprev = NULL;
			for ( pst=sc->possub; pst!=NULL && pst!=spst; pstprev=pst, pst=pst->next );
			if ( pstprev==NULL )
			    sc->possub = spst->next;
			else
			    pstprev = spst->next;
			spst->next = NULL;
			PSTFree(spst);
		    }
		} else if ( lookup_type==gsub_ligature || lookup_type==gpos_pair ) {
		    pstprev = NULL;
		    for ( spst=sc->possub; spst!=NULL ; spst = pstnext ) {
			pstnext = spst->next;
			if ( spst->subtable == subsecond ) {
			    for ( fpst=sc->possub; fpst!=NULL; fpst=fpst->next ) {
				if ( fpst->subtable == subfirst &&
					strcmp(fpst->u.lig.components,spst->u.lig.components)==0 )
			    break;
			    }
			    if ( fpst==NULL )
				spst->subtable = subfirst;
			    else {
				LogError(_("The glyph, %s, contains the same %s from %s and from %s.\nThe one from %s will be removed.\n"),
					sc->name,
			                lookup_type==gsub_ligature ? _("ligature") : _("kern pair"),
					subfirst->subtable_name, subsecond->subtable_name,
					subsecond->subtable_name );
				if ( pstprev==NULL )
				    sc->possub = pstnext;
				else
				    pstprev->next = pstnext;
				spst->next = NULL;
			        PSTFree(spst);
			        spst = pstprev;
			    }
			}
			pstprev = spst;
		    }
		    for ( isv=0; isv<2; ++isv ) {
			kpprev = NULL;
			for ( skp=isv ? sc->vkerns : sc->kerns; skp!=NULL ; skp = kpnext ) {
			    kpnext = skp->next;
			    if ( skp->subtable == subsecond ) {
				for ( fkp=isv ? sc->vkerns : sc->kerns; fkp!=NULL; fkp=fkp->next ) {
				    if ( fkp->subtable == subfirst && fkp->sc==skp->sc )
				break;
				}
				if ( fkp==NULL )
				    skp->subtable = subfirst;
				else {
				    LogError(_("The glyph, %s, contains the same kern pair from %s and from %s.\nThe one from %s will be removed.\n"),
					    sc->name,
					    subfirst->subtable_name, subsecond->subtable_name,
					    subsecond->subtable_name );
				    if ( kpprev!=NULL )
					kpprev->next = kpnext;
				    else if ( isv )
					sc->vkerns = kpnext;
				    else
					sc->kerns = kpnext;
				    skp->next = NULL;
				    KernPairsFree(skp);
				    skp = kpprev;
				}
			    }
			    kpprev = skp;
			}
		    }
		}
	    }
	    ++k;
	} while ( k<_sf->subfontcnt );
    }
}

/* ************************************************************************** */
/* ******************************* copy lookup ****************************** */
/* ************************************************************************** */

static char **ClassCopy(int class_cnt,char **classes) {
    char **newclasses;
    int i;

    if ( classes==NULL || class_cnt==0 )
return( NULL );
    newclasses = galloc(class_cnt*sizeof(char *));
    for ( i=0; i<class_cnt; ++i )
	newclasses[i] = copy(classes[i]);
return( newclasses );
}

static OTLookup *_OTLookupCopyInto(struct sfmergecontext *mc,
	OTLookup *from_otl, OTLookup *before, int do_contents);
static OTLookup *OTLookupCopyNested(struct sfmergecontext *mc,
	OTLookup *from_otl) {
    char *newname;
    OTLookup *to_nested_otl;
    int l;

    if ( from_otl==NULL )
return( NULL );

    for ( l=0; l<mc->lcnt; ++l ) {
	if ( mc->lks[l].from == from_otl )
return( mc->lks[l].to );
    }

    newname = strconcat(mc->prefix,from_otl->lookup_name);
    to_nested_otl = SFFindLookup(mc->sf_to,newname);
    free(newname);
    if ( to_nested_otl==NULL )
	to_nested_otl = _OTLookupCopyInto(mc, from_otl, (OTLookup *) -1, true );
return( to_nested_otl );
}

static KernClass *SF_AddKernClass(struct sfmergecontext *mc,KernClass *kc,
	struct lookup_subtable *sub ) {
    KernClass *newkc;

    newkc = chunkalloc(sizeof(KernClass));
    *newkc = *kc;
    newkc->subtable = sub;
    if ( sub->vertical_kerning ) {
	newkc->next = mc->sf_to->vkerns;
	mc->sf_to->vkerns = newkc;
    } else {
	newkc->next = mc->sf_to->kerns;
	mc->sf_to->kerns = newkc;
    }

    newkc->firsts = ClassCopy(newkc->first_cnt,newkc->firsts);
    newkc->seconds = ClassCopy(newkc->second_cnt,newkc->seconds);
    newkc->offsets = galloc(newkc->first_cnt*newkc->second_cnt*sizeof(int16));
    memcpy(newkc->offsets,kc->offsets,newkc->first_cnt*newkc->second_cnt*sizeof(int16));
return( newkc );
}

static FPST *SF_AddFPST(struct sfmergecontext *mc,FPST *fpst,
	struct lookup_subtable *sub ) {
    FPST *newfpst;
    int i, k, cur;

    newfpst = chunkalloc(sizeof(FPST));
    *newfpst = *fpst;
    newfpst->subtable = sub;
    newfpst->next = mc->sf_to->possub;
    mc->sf_to->possub = newfpst;

    newfpst->nclass = ClassCopy(newfpst->nccnt,newfpst->nclass);
    newfpst->bclass = ClassCopy(newfpst->bccnt,newfpst->bclass);
    newfpst->fclass = ClassCopy(newfpst->fccnt,newfpst->fclass);

    newfpst->rules = galloc(newfpst->rule_cnt*sizeof(struct fpst_rule));
    memcpy(newfpst->rules,fpst->rules,newfpst->rule_cnt*sizeof(struct fpst_rule));

    cur = 0;
    for ( i=0; i<newfpst->rule_cnt; ++i ) {
	struct fpst_rule *r = &newfpst->rules[i], *oldr = &fpst->rules[i];

	r->lookups = galloc(r->lookup_cnt*sizeof(struct seqlookup));
	memcpy(r->lookups,oldr->lookups,r->lookup_cnt*sizeof(struct seqlookup));
	for ( k=0; k<r->lookup_cnt; ++k ) {
	    r->lookups[k].lookup = OTLookupCopyNested(mc,
		r->lookups[k].lookup);
	}

	switch ( newfpst->format ) {
	  case pst_glyphs:
	    r->u.glyph.names = copy( r->u.glyph.names );
	    r->u.glyph.back = copy( r->u.glyph.back );
	    r->u.glyph.fore = copy( r->u.glyph.fore );
	  break;
	  case pst_class:
	    r->u.class.nclasses = galloc( r->u.class.ncnt*sizeof(uint16));
	    memcpy(r->u.class.nclasses,oldr->u.class.nclasses, r->u.class.ncnt*sizeof(uint16));
	    r->u.class.bclasses = galloc( r->u.class.bcnt*sizeof(uint16));
	    memcpy(r->u.class.bclasses,oldr->u.class.bclasses, r->u.class.ncnt*sizeof(uint16));
	    r->u.class.fclasses = galloc( r->u.class.fcnt*sizeof(uint16));
	    memcpy(r->u.class.fclasses,oldr->u.class.fclasses, r->u.class.fcnt*sizeof(uint16));
	  break;
	  case pst_coverage:
	    r->u.coverage.ncovers = ClassCopy( r->u.coverage.ncnt, r->u.coverage.ncovers );
	    r->u.coverage.bcovers = ClassCopy( r->u.coverage.bcnt, r->u.coverage.bcovers );
	    r->u.coverage.fcovers = ClassCopy( r->u.coverage.fcnt, r->u.coverage.fcovers );
	  break;
	  case pst_reversecoverage:
	    r->u.rcoverage.ncovers = ClassCopy( r->u.rcoverage.always1, r->u.rcoverage.ncovers );
	    r->u.rcoverage.bcovers = ClassCopy( r->u.rcoverage.bcnt, r->u.rcoverage.bcovers );
	    r->u.rcoverage.fcovers = ClassCopy( r->u.rcoverage.fcnt, r->u.rcoverage.fcovers );
	    r->u.rcoverage.replacements = copy( r->u.rcoverage.replacements );
	  break;
	}
    }
return( newfpst );
}

static SplineChar *SCFindOrMake(SplineFont *into,SplineChar *fromsc) {
    int to_index;

    if ( into->cidmaster==NULL && into->fv!=NULL ) {
	to_index = SFFindSlot(into,into->fv->map,fromsc->unicodeenc,fromsc->name);
	if ( to_index==-1 )
return( NULL );
return( SFMakeChar(into,into->fv->map,to_index));
    }
return( SFGetChar(into,fromsc->unicodeenc,fromsc->name));
}

static void SF_SCAddAP(SplineChar *tosc,AnchorPoint *ap, AnchorClass *newac) {
    AnchorPoint *newap;

    newap = chunkalloc(sizeof(AnchorPoint));
    *newap = *ap;
    newap->anchor = newac;
    newap->next = tosc->anchor;
    tosc->anchor = newap;
}

static void SF_AddAnchorClasses(struct sfmergecontext *mc,
	struct lookup_subtable *from_sub, struct lookup_subtable *sub ) {
    AnchorClass *ac, *nac;
    int k, gid;
    SplineFont *fsf;
    AnchorPoint *ap;
    SplineChar *fsc, *tsc;

    for ( ac=mc->sf_from->anchor; ac!=NULL; ac=ac->next ) if ( ac->subtable==from_sub ) {
	nac = chunkalloc(sizeof(AnchorClass));
	*nac = *ac;
	nac->subtable = sub;
	nac->name = strconcat(mc->prefix,nac->name);
	nac->next = mc->sf_to->anchor;
	mc->sf_to->anchor = nac;

	k=0;
	do {
	    fsf = mc->sf_from->subfontcnt==0 ? mc->sf_from : mc->sf_from->subfonts[k];
	    for ( gid = 0; gid<fsf->glyphcnt; ++gid ) if ( (fsc = fsf->glyphs[gid])!=NULL ) {
		for ( ap=fsc->anchor; ap!=NULL; ap=ap->next ) {
		    if ( ap->anchor==ac ) {
			tsc = SCFindOrMake(mc->sf_to,fsc);
			if ( tsc==NULL )
		break;
			SF_SCAddAP(tsc,ap,nac);
		    }
		}
	    }
	    ++k;
	} while ( k<mc->sf_from->subfontcnt );
    }
}

static int SF_SCAddPST(SplineChar *tosc,PST *pst,struct lookup_subtable *sub) {
    PST *newpst;

    newpst = chunkalloc(sizeof(PST));
    *newpst = *pst;
    newpst->subtable = sub;
    newpst->next = tosc->possub;
    tosc->possub = newpst;

    switch( newpst->type ) {
      case pst_pair:
	newpst->u.pair.paired = copy(pst->u.pair.paired);
	newpst->u.pair.vr = chunkalloc(sizeof(struct vr [2]));
	memcpy(newpst->u.pair.vr,pst->u.pair.vr,sizeof(struct vr [2]));
      break;
      case pst_ligature:
	newpst->u.lig.lig = tosc;
	/* Fall through */
      case pst_substitution:
      case pst_alternate:
      case pst_multiple:
	newpst->u.subs.variant = copy(pst->u.subs.variant);
      break;
    }
return( true );
}

static int SF_SCAddKP(SplineChar *tosc,KernPair *kp,struct lookup_subtable *sub,
	int isvkern, SplineFont *to_sf ) {
    SplineChar *tosecond;
    KernPair *newkp;

    tosecond = SFGetChar(to_sf,kp->sc->unicodeenc,kp->sc->name);
    if ( tosecond==NULL )
return( false );

    newkp = chunkalloc(sizeof(KernPair));
    *newkp = *kp;
    newkp->subtable = sub;
    newkp->sc = tosecond;
    if ( isvkern ) {
	newkp->next = tosc->vkerns;
	tosc->vkerns = newkp;
    } else {
	newkp->next = tosc->kerns;
	tosc->kerns = newkp;
    }
return(true);
}

static void SF_AddPSTKern(struct sfmergecontext *mc,struct lookup_subtable *from_sub, struct lookup_subtable *sub) {
    int k, gid, isv;
    SplineFont *fsf;
    SplineChar *fsc, *tsc;
    PST *pst;
    KernPair *kp;
    int iskern = sub->lookup->lookup_type==gpos_pair;

    k=0;
    do {
	fsf = mc->sf_from->subfontcnt==0 ? mc->sf_from : mc->sf_from->subfonts[k];
	for ( gid = 0; gid<fsf->glyphcnt; ++gid ) if ( (fsc = fsf->glyphs[gid])!=NULL ) {
	    tsc = (SplineChar *) -1;
	    for ( pst = fsc->possub; pst!=NULL; pst=pst->next ) {
		if ( pst->subtable==from_sub ) {
		    if ( tsc==(SplineChar *) -1 ) {
			tsc = SCFindOrMake(mc->sf_to,fsc);
			if ( tsc==NULL )
	    break;
		    }
		    SF_SCAddPST(tsc,pst,sub);
		}
	    }
	    if ( tsc!=NULL && iskern ) {
		for ( isv=0; isv<2 && tsc!=NULL; ++isv ) {
		    for ( kp= isv ? fsc->vkerns : fsc->kerns; kp!=NULL; kp=kp->next ) {
			if ( kp->subtable==sub ) {
			    /* Kerning data tend to be individualistic. Only copy if */
			    /*  glyphs exist */
			    if ( tsc==(SplineChar *) -1 ) {
				tsc = SFGetChar(mc->sf_to,fsc->unicodeenc,fsc->name);
				if ( tsc==NULL )
		    break;
			    }
			    SF_SCAddKP(tsc,kp,sub,isv,mc->sf_to);
			}
		    }
		}
	    }
	}
	++k;
    } while ( k<mc->sf_from->subfontcnt );
}

int _FeatureOrderId( int isgpos,uint32 tag ) {
    /* This is the order in which features should be executed */
    
    if ( !isgpos ) switch ( tag ) {
/* GSUB ordering */
      case CHR('c','c','m','p'):	/* Must be first? */
return( -2 );
      case CHR('l','o','c','l'):	/* Language dependent letter forms (serbian uses some different glyphs than russian) */
return( -1 );
      case CHR('i','s','o','l'):
return( 0 );
      case CHR('j','a','l','t'):		/* must come after 'isol' */
return( 1 );
      case CHR('f','i','n','a'):
return( 2 );
      case CHR('f','i','n','2'):
      case CHR('f','a','l','t'):		/* must come after 'fina' */
return( 3 );
      case CHR('f','i','n','3'):
return( 4 );
      case CHR('m','e','d','i'):
return( 5 );
      case CHR('m','e','d','2'):
return( 6 );
      case CHR('i','n','i','t'):
return( 7 );

      case CHR('r','t','l','a'):
return( 100 );
      case CHR('s','m','c','p'): case CHR('c','2','s','c'):
return( 200 );

      case CHR('r','l','i','g'):
return( 300 );
      case CHR('c','a','l','t'):
return( 301 );
      case CHR('l','i','g','a'):
return( 302 );
      case CHR('d','l','i','g'): case CHR('h','l','i','g'):
return( 303 );
      case CHR('c','s','w','h'):
return( 304 );
      case CHR('m','s','e','t'):
return( 305 );

      case CHR('f','r','a','c'):
return( 306 );

/* Indic processing */
      case CHR('n','u','k','t'):
      case CHR('p','r','e','f'):
return( 301 );
      case CHR('a','k','h','n'):
return( 302 );
      case CHR('r','p','h','f'):
return( 303 );
      case CHR('b','l','w','f'):
return( 304 );
      case CHR('h','a','l','f'):
      case CHR('a','b','v','f'):
return( 305 );
      case CHR('p','s','t','f'):
return( 306 );
      case CHR('v','a','t','u'):
return( 307 );

      case CHR('p','r','e','s'):
return( 310 );
      case CHR('b','l','w','s'):
return( 311 );
      case CHR('a','b','v','s'):
return( 312 );
      case CHR('p','s','t','s'):
return( 313 );
      case CHR('c','l','i','g'):
return( 314 );
      
      case CHR('h','a','l','n'):
return( 320 );
/* end indic ordering */

      case CHR('a','f','r','c'):
      case CHR('l','j','m','o'):
      case CHR('v','j','m','o'):
return( 350 );
      case CHR('v','r','t','2'): case CHR('v','e','r','t'):
return( 1010 );		/* Documented to come last */

/* Unknown things come after everything but vert/vrt2 */
      default:
return( 1000 );

    } else switch ( tag ) {
/* GPOS ordering */
      case CHR('c','u','r','s'):
return( 0 );
      case CHR('d','i','s','t'):
return( 100 );
      case CHR('b','l','w','m'):
return( 201 );
      case CHR('a','b','v','m'):
return( 202 );
      case CHR('k','e','r','n'):
return( 300 );
      case CHR('m','a','r','k'):
return( 400 );
      case CHR('m','k','m','k'):
return( 500 );
/* Unknown things come after everything  */
      default:
return( 1000 );
    }
}

int FeatureOrderId( int isgpos,FeatureScriptLangList *fl ) {
    int pos = 9999, temp;

    if ( fl==NULL )
return( 0 );

    while ( fl!=NULL ) {
	temp = _FeatureOrderId(isgpos,fl->featuretag );
	if ( temp<pos ) pos = temp;
	fl = fl->next;
    }
return( pos );
}

void SortInsertLookup(SplineFont *sf, OTLookup *newotl) {
    int isgpos = newotl->lookup_type>=gpos_start;
    int pos;
    OTLookup *prev, *otl;

    pos = FeatureOrderId(isgpos,newotl->features);
    for ( prev=NULL, otl= isgpos ? sf->gpos_lookups : sf->gsub_lookups ;
	    otl!=NULL && FeatureOrderId(isgpos,newotl->features)<pos;
	    prev = otl, otl=otl->next );
    newotl->next = otl;
    if ( prev!=NULL )
	prev->next = newotl;
    else if ( isgpos )
	sf->gpos_lookups = newotl;
    else
	sf->gsub_lookups = newotl;
}

/* Before may be:
    * A lookup in into_sf, in which case insert new lookup before it
    * NULL               , in which case insert new lookup at end
    * -1                 , in which case insert new lookup at start
    * -2                 , try to guess a good position
*/
static void OrderNewLookup(SplineFont *into_sf,OTLookup *otl,OTLookup *before) {
    int isgpos = otl->lookup_type>=gpos_start;
    OTLookup **head = isgpos ? &into_sf->gpos_lookups : &into_sf->gsub_lookups;
    OTLookup *prev;

    if ( before == (OTLookup *) -2 )
	SortInsertLookup(into_sf,otl);
    else if ( before == (OTLookup *) -1 || *head==NULL || *head==before ) {
	otl->next = *head;
	*head = otl;
    } else {
	for ( prev= *head; prev->next!=NULL && prev->next!=before ; prev=prev->next );
	otl->next = prev->next;
	prev->next = otl;
    }
}

static OTLookup *_OTLookupCopyInto(struct sfmergecontext *mc,
	OTLookup *from_otl, OTLookup *before, int do_contents) {
    OTLookup *otl;
    struct lookup_subtable *sub, *last, *from_sub;
    int scnt, l;

    for ( l=0; l<mc->lcnt; ++l ) {
	if ( mc->lks[l].from == from_otl ) {
	    if ( mc->lks[l].old )
return( mc->lks[l].to );
	    else
    break;
	}
    }

    if ( l>=mc->lmax )
	mc->lks = grealloc(mc->lks,(mc->lmax += 20)*sizeof(struct lookup_cvt));
    mc->sf_to->changed = true;

    if ( l>=mc->lcnt ) {
	otl = chunkalloc(sizeof(OTLookup));
	*otl = *from_otl;
	memset(&mc->lks[l],0,sizeof(mc->lks[l]));
	mc->lks[l].from = from_otl; mc->lks[l].to = otl; ++mc->lcnt;
	otl->lookup_name = strconcat(mc->prefix,from_otl->lookup_name);
	otl->features = FeatureListCopy(from_otl->features);
	otl->next = NULL; otl->subtables = NULL;
	OrderNewLookup(mc->sf_to,otl,before);
	if ( !do_contents )
	    FIOTLookupCopyInto(mc->sf_to,mc->sf_from, from_otl, otl, 0, before);
    } else
	otl = mc->lks[l].to;
    if ( !do_contents )
return( otl );

    last = NULL;
    scnt = 0;
    for ( from_sub = from_otl->subtables; from_sub!=NULL; from_sub=from_sub->next ) {
	sub = chunkalloc(sizeof(struct lookup_subtable));
	*sub = *from_sub;
	sub->lookup = otl;
	sub->subtable_name = strconcat(mc->prefix,from_sub->subtable_name);
	sub->suffix = copy(sub->suffix);
	if ( last==NULL )
	    otl->subtables = sub;
	else
	    last->next = sub;
	last = sub;
	if ( from_sub->kc!=NULL )
	    sub->kc = SF_AddKernClass(mc, from_sub->kc, sub);
	else if ( from_sub->fpst!=NULL )
	    sub->fpst = SF_AddFPST(mc, from_sub->fpst, sub);
	else if ( from_sub->anchor_classes )
	    SF_AddAnchorClasses(mc, from_sub, sub);
	else
	    SF_AddPSTKern(mc, from_sub, sub);
	++scnt;
    }
    FIOTLookupCopyInto(mc->sf_to,mc->sf_from, from_otl, otl, scnt, before);
return( otl );
}

static int NeedsPrefix(SplineFont *into_sf,SplineFont *from_sf, OTLookup **list) {
    struct lookup_subtable *from_sub;
    int i,j,k;
    OTLookup *sublist[2];

    sublist[1] = NULL;

    if ( list==NULL || list[0]==NULL )
return( false );
    for ( k=0; list[k]!=NULL; ++k ) {
	OTLookup *from_otl = list[k];
	if ( SFFindLookup(into_sf,from_otl->lookup_name)!=NULL )
return( true );
	for ( from_sub = from_otl->subtables; from_sub!=NULL; from_sub=from_sub->next ) {
	    if ( from_sub->fpst!=NULL ) {
		for ( i=0; i<from_sub->fpst->rule_cnt; ++i ) {
		    struct fpst_rule *r = &from_sub->fpst->rules[i];
		    for ( j=0; j<r->lookup_cnt; ++j ) {
			sublist[0] = r->lookups[j].lookup;
			if ( NeedsPrefix(into_sf,from_sf, sublist))
return( true );
		    }
		}
	    }
	}
    }
return( false );
}

OTLookup *OTLookupCopyInto(SplineFont *into_sf,SplineFont *from_sf, OTLookup *from_otl) {
    OTLookup *newotl, *list[2];
    struct sfmergecontext mc;

    memset(&mc,0,sizeof(mc));
    mc.sf_from = from_sf; mc.sf_to = into_sf;

    list[0] = from_otl; list[1] = NULL;
    mc.prefix = NeedsPrefix(into_sf,from_sf,list)
	    ? strconcat(from_sf->fontname,"-") : copy("");
    newotl = _OTLookupCopyInto(&mc,from_otl,(OTLookup *) -2,true);
    free(mc.lks);
    free(mc.prefix);
return( newotl );
}

void OTLookupsCopyInto(SplineFont *into_sf,SplineFont *from_sf,
	OTLookup **list, OTLookup *before) {
    int i, do_contents;
    struct sfmergecontext mc;

    memset(&mc,0,sizeof(mc));
    mc.sf_from = from_sf; mc.sf_to = into_sf;

    mc.prefix = NeedsPrefix(into_sf,from_sf,list)
	    ? strconcat(from_sf->fontname,"-") : copy("");
    for ( i=0; list[i]!=NULL; ++i );
    mc.lks = galloc((mc.lmax=i+5)*sizeof(struct lookup_cvt));
    /* First create all the lookups and position them in the right order */
    /*  then create subtables (which may in turn create some new lookups */
    /*  for contextual lookups which invoke other lookups, don't care how */
    /*  those nested lookups are ordered) */
    for ( do_contents=0; do_contents<2; ++do_contents )
	for ( i=0; list[i]!=NULL; ++i )
	    (void) _OTLookupCopyInto(&mc,list[i],before,do_contents);
    free(mc.lks);
    free(mc.prefix);
}

/* ************************************************************************** */
/* ****************************** Apply lookups ***************************** */
/* ************************************************************************** */

struct lookup_data {
    struct opentype_str *str;
    int cnt, max;

    uint32 script;
    SplineFont *sf;

    struct lookup_subtable *lig_owner;
    int lcnt, lmax;
    SplineChar ***ligs;		/* For each ligature we have an array of SplineChars that are its components preceded by the ligature glyph itself */
				/*  NULL terminated */
    int pixelsize;
    double scale;
};

static int ApplyLookupAtPos(uint32 tag, OTLookup *otl,struct lookup_data *data,int pos);

static int GlyphNameInClass(char *name,char *class ) {
    char *pt;
    int len = strlen(name);

    if ( class==NULL )
return( false );

    pt = class;
    while ( (pt=strstr(pt,name))!=NULL ) {
	if ( pt==NULL )
return( false );
	if ( (pt==class || pt[-1]==' ') && (pt[len]=='\0' || pt[len]==' '))
return( true );
	pt+=len;
    }

return( false );
}

/* ************************************************************************** */
/* ************************* Apply OpenType Lookups ************************* */
/* ************************************************************************** */

static void LigatureFree(struct lookup_data *data) {
    int i;

    if ( data->ligs==NULL )
return;
    for ( i=0; data->ligs[i]!=NULL; ++i )
	free(data->ligs[i]);
}

static void LigatureSearch(struct lookup_subtable *sub, struct lookup_data *data) {
    SplineFont *sf = data->sf;
    int gid, ccnt, cnt, ch, err;
    SplineChar *sc;
    PST *pst;
    char *pt, *start;

    LigatureFree(data);
    cnt = 0;
    for ( gid=0; gid<sf->glyphcnt; ++gid ) if ( (sc=sf->glyphs[gid])!=NULL ) {
	for ( pst=sc->possub; pst!=NULL; pst=pst->next ) if ( pst->subtable==sub ) {
	    for ( pt = pst->u.lig.components, ccnt=0; *pt; ++pt )
		if ( *pt==' ' )
		    ++ccnt;
	    if ( cnt>=data->lmax )
		data->ligs = grealloc(data->ligs,(data->lmax+=100)*sizeof(SplineChar **));
	    data->ligs[cnt] = galloc((ccnt+3)*sizeof(SplineChar *));
	    data->ligs[cnt][0] = sc;
	    ccnt = 1;
	    err = 0;
	    for ( pt = pst->u.lig.components; *pt; ) {
		while ( *pt==' ' ) ++pt;
		if ( *pt=='\0' )
	    break;
		for ( start=pt; *pt!='\0' && *pt!=' '; ++pt );
		ch = *pt; *pt = '\0';
		data->ligs[cnt][ccnt++] = SFGetChar(sf,-1,start);
		*pt = ch;
		if ( data->ligs[cnt][ccnt-1]==NULL )
		    err = 1;
	    }
	    if ( !err )
		data->ligs[cnt++][ccnt] = NULL;
	}
    }
    if ( cnt>=data->lmax )
	data->ligs = grealloc(data->ligs,(data->lmax+=1)*sizeof(SplineChar **));
    data->ligs[cnt] = NULL;
    data->lcnt = cnt;
}

static int skipglyphs(int lookup_flags, struct lookup_data *data, int pos) {
    int mc, glyph_class;
    /* The lookup flags tell us what glyphs to ignore. Skip over anything we */
    /*  should ignore */

    if ( lookup_flags==0 )
return( pos );
    mc = (lookup_flags>>8);
    if ( mc<0 || mc>=data->sf->mark_class_cnt )
	mc = 0;
    while ( pos<data->cnt ) {
	glyph_class = gdefclass(data->str[pos].sc);
	/* 1=>base, 2=>ligature, 3=>mark, 4=>component?, 0=>.notdef */
	if ( (glyph_class==1 && (lookup_flags&pst_ignorebaseglyphs)) ||
		(glyph_class==2 && (lookup_flags&pst_ignoreligatures)) ||
		(glyph_class==3 && (lookup_flags&pst_ignorecombiningmarks)) ||
		(glyph_class==3 && mc!=0 &&
			!GlyphNameInClass(data->str[pos].sc->name,data->sf->mark_classes[mc])) ) {
	    ++pos;
	} else
    break;
    }
return( pos );
}

static int bskipmarkglyphs(int lookup_flags, struct lookup_data *data, int pos) {
    int mc, glyph_class;
    /* The lookup flags tell us what glyphs to ignore. Skip over anything we */
    /*  should ignore. Here we skip backward */

    mc = (lookup_flags>>8);
    if ( mc<0 || mc>=data->sf->mark_class_cnt )
	mc = 0;
    while ( pos>=0 ) {
	glyph_class = gdefclass(data->str[pos].sc);
	/* 1=>base, 2=>ligature, 3=>mark, 4=>component?, 0=>.notdef */
	if ( glyph_class==3 )
	    --pos;
	else if ( (glyph_class==1 && (lookup_flags&pst_ignorebaseglyphs)) ||
		(glyph_class==2 && (lookup_flags&pst_ignoreligatures)) ||
		(glyph_class==3 && (lookup_flags&pst_ignorecombiningmarks)) ||
		(glyph_class==3 && mc!=0 &&
			!GlyphNameInClass(data->str[pos].sc->name,data->sf->mark_classes[mc])) ) {
	    --pos;
	} else
    break;
    }
return( pos );
}

static int bskipglyphs(int lookup_flags, struct lookup_data *data, int pos) {
    int mc, glyph_class;
    /* The lookup flags tell us what glyphs to ignore. Skip over anything we */
    /*  should ignore. Here we skip backward */

    if ( lookup_flags==0 )
return( pos );
    mc = (lookup_flags>>8);
    if ( mc<0 || mc>=data->sf->mark_class_cnt )
	mc = 0;
    while ( pos>=0 ) {
	glyph_class = gdefclass(data->str[pos].sc);
	/* 1=>base, 2=>ligature, 3=>mark, 4=>component?, 0=>.notdef */
	if ( (glyph_class==1 && (lookup_flags&pst_ignorebaseglyphs)) ||
		(glyph_class==2 && (lookup_flags&pst_ignoreligatures)) ||
		(glyph_class==3 && (lookup_flags&pst_ignorecombiningmarks)) ||
		(glyph_class==3 && mc!=0 &&
			!GlyphNameInClass(data->str[pos].sc->name,data->sf->mark_classes[mc])) ) {
	    --pos;
	} else
    break;
    }
return( pos );
}

static int ContextualMatch(struct lookup_subtable *sub,struct lookup_data *data,
	int pos, struct fpst_rule **_rule) {
    int i, cpos, retpos, r;
    FPST *fpst = sub->fpst;
    int lookup_flags = sub->lookup->lookup_flags;
    char *pt;

    /* If we should skip the current glyph then don't try for a match here */
    cpos = skipglyphs(lookup_flags,data,pos);
    if ( cpos!=pos )
return( 0 );

    for ( r=0; r<fpst->rule_cnt; ++r ) {
	struct fpst_rule *rule = &fpst->rules[r];
	for ( i=pos; i<data->cnt; ++i )
	    data->str[i].context_pos = -1;

/* Handle backtrack (backtrace in the rule is stored in reverse textual order) */
	if ( fpst->type == pst_chainpos || fpst->type == pst_chainsub ) {
	    if ( fpst->format==pst_glyphs ) {
		pt = rule->u.glyph.back;
		for ( i=bskipglyphs(lookup_flags,data,pos-1), cpos=0; i>=0; i = bskipglyphs(lookup_flags,data,i-1)) {
		    char *name = data->str[i].sc->name;
		    int len = strlen( name );
		    if ( strncmp(name,pt,len)!=0 || (pt[len]!='\0' && pt[len]!=' '))
		break;
		    pt += len;
		    while ( *pt==' ' ) ++pt;
		}
		if ( *pt!='\0' )
    continue;		/* didn't match */
	    } else if ( fpst->format==pst_class ) {
		for ( i=bskipglyphs(lookup_flags,data,pos-1), cpos=0; i>=0 && cpos<rule->u.class.bcnt; i = bskipglyphs(lookup_flags,data,i-1)) {
		    if ( !GlyphNameInClass(data->str[i].sc->name,fpst->bclass[rule->u.class.bclasses[cpos]]) )
		break;
		    ++cpos;
		}
		if ( cpos!=rule->u.class.bcnt )
    continue;		/* didn't match */
	    } else if ( fpst->format==pst_coverage ) {
		for ( i=bskipglyphs(lookup_flags,data,pos-1), cpos=0; i>=0 && cpos<rule->u.coverage.bcnt; i = bskipglyphs(lookup_flags,data,i-1)) {
		    if ( !GlyphNameInClass(data->str[i].sc->name,rule->u.coverage.bcovers[cpos]) )
		break;
		    ++cpos;
		}
		if ( cpos<rule->u.coverage.bcnt )
    continue;		/* didn't match */
	    }
	}
/* Handle Match */
	if ( fpst->format==pst_glyphs ) {
	    pt = rule->u.glyph.names;
	    for ( i=pos, cpos=0; i<data->cnt && *pt!='\0'; i = skipglyphs(lookup_flags,data,i+1)) {
		char *name = data->str[i].sc->name;
		int len = strlen( name );
		if ( strncmp(name,pt,len)!=0 || (pt[len]!='\0' && pt[len]!=' '))
	    break;
		data->str[i].context_pos = cpos++;
		pt += len;
		while ( *pt==' ' ) ++pt;
	    }
	    if ( *pt!='\0' )
    continue;		/* didn't match */
	} else if ( fpst->format==pst_class ) {
	    for ( i=pos, cpos=0; i<data->cnt && cpos<rule->u.class.ncnt; i = skipglyphs(lookup_flags,data,i+1)) {
		int class = rule->u.class.nclasses[cpos];
		if ( class!=0 ) {
		    if ( !GlyphNameInClass(data->str[i].sc->name,fpst->nclass[class]) )
	    break;
		} else {
		    int c;
		    /* Ok, to match class 0 we must fail to match all other classes */
		    for ( c=1; c<fpst->nccnt; ++c )
			if ( !GlyphNameInClass(data->str[i].sc->name,fpst->nclass[c]) )
		    break;
		    if ( c!=fpst->nccnt )
	    break;		/* It matched another class => not in class 0 */
		}
		data->str[i].context_pos = cpos++;
	    }
	    if ( cpos<rule->u.class.ncnt )
    continue;		/* didn't match */
	} else if ( fpst->format==pst_coverage ) {
	    for ( i=pos, cpos=0; i<data->cnt && cpos<rule->u.coverage.ncnt; i = skipglyphs(lookup_flags,data,i+1)) {
		if ( !GlyphNameInClass(data->str[i].sc->name,rule->u.coverage.ncovers[cpos]) )
	    break;
		data->str[i].context_pos = cpos++;
	    }
	    if ( cpos<rule->u.coverage.ncnt )
    continue;		/* didn't match */
	} else
return( 0 );		/* Not ready to deal with reverse chainging */

	retpos = i;
/* Handle lookahead */
	if ( fpst->type == pst_chainpos || fpst->type == pst_chainsub ) {
	    if ( fpst->format==pst_glyphs ) {
		pt = rule->u.glyph.fore;
		for ( i=retpos; i<data->cnt && *pt!='\0'; i = skipglyphs(lookup_flags,data,i+1)) {
		    char *name = data->str[i].sc->name;
		    int len = strlen( name );
		    if ( strncmp(name,pt,len)!=0 || (pt[len]!='\0' && pt[len]!=' '))
		break;
		    pt += len;
		    while ( *pt==' ' ) ++pt;
		}
		if ( *pt!='\0' )
    continue;		/* didn't match */
	    } else if ( fpst->format==pst_class ) {
		for ( i=retpos, cpos=0; i<data->cnt && cpos<rule->u.class.fcnt; i = skipglyphs(lookup_flags,data,i+1)) {
		    if ( !GlyphNameInClass(data->str[i].sc->name,fpst->fclass[rule->u.class.fclasses[cpos]]) )
		break;
		    cpos++;
		}
		if ( cpos<rule->u.class.fcnt )
    continue;		/* didn't match */
	    } else if ( fpst->format==pst_coverage ) {
		for ( i=retpos, cpos=0; i<data->cnt && cpos<rule->u.coverage.fcnt; i = skipglyphs(lookup_flags,data,i+1)) {
		    if ( !GlyphNameInClass(data->str[i].sc->name,rule->u.coverage.fcovers[cpos]) )
		break;
		    cpos++;
		}
		if ( cpos<rule->u.coverage.fcnt )
    continue;		/* didn't match */
	    }
	}
	*_rule = rule;
return( retpos );
    }
return( 0 );
}

static int ApplySingleSubsAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    PST *pst;
    SplineChar *sc;

    for ( pst=data->str[pos].sc->possub; pst!=NULL && pst->subtable!=sub; pst=pst->next );
    if ( pst==NULL )
return( 0 );

    sc = SFGetChar(data->sf,-1,pst->u.subs.variant);
    if ( sc!=NULL ) {
	data->str[pos].sc = sc;
return( pos+1 );
    } else if ( strcmp(pst->u.subs.variant,MAC_DELETED_GLYPH_NAME)==0 ) {
	/* Under AAT we delete the glyph. But OpenType doesn't have that concept */
	int i;
	for ( i=pos+1; i<data->cnt; ++i )
	    data->str[pos-1] = data->str[pos];
	--data->cnt;
return( pos );
    } else {
return( 0 );
    }
}

static int ApplyMultSubsAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    PST *pst;
    SplineChar *sc;
    char *start, *pt;
    int mcnt, ch, i;
    SplineChar *mults[20];

    for ( pst=data->str[pos].sc->possub; pst!=NULL && pst->subtable!=sub; pst=pst->next );
    if ( pst==NULL )
return( 0 );

    mcnt = 0;
    for ( start = pst->u.alt.components; *start==' '; ++start);
    for ( ; *start; ) {
	for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
	ch = *pt; *pt = '\0';
	sc = SFGetChar(data->sf,-1,start);
	*pt = ch;
	if ( sc==NULL )
return( 0 );
	if ( mcnt<20 ) mults[mcnt++] = sc;
	while ( *pt==' ' ) ++pt;
	start = pt;
    }

    if ( mcnt==0 ) {
	/* Is this legal? that is can we remove a glyph with an empty multiple? */
	for ( i=pos+1; i<data->cnt; ++i )
	    data->str[i-1] = data->str[i];
	--data->cnt;
return( pos );
    } else if ( mcnt==1 ) {
	data->str[pos].sc = mults[0];
return( pos+1 );
    } else {
	if ( data->cnt+mcnt-1 >= data->max )
	    data->str = grealloc(data->str,(data->max+=mcnt) * sizeof( struct opentype_str ));
	for ( i=data->cnt-1; i>pos; --i )
	    data->str[i+mcnt-1] = data->str[i];
	memset(data->str+pos,0,mcnt*sizeof(struct opentype_str));
	for ( i=0; i<mcnt; ++i ) {
	    data->str[pos+i].sc = mults[i];
	    data->str[pos+i].orig_index = data->str[pos].orig_index;
	}
	data->cnt += (mcnt-1);
return( pos+mcnt );
    }
}

static int ApplyAltSubsAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    PST *pst;
    SplineChar *sc;
    char *start, *pt, ch;

    for ( pst=data->str[pos].sc->possub; pst!=NULL && pst->subtable!=sub; pst=pst->next );
    if ( pst==NULL )
return( 0 );

    for ( start = pst->u.alt.components; *start==' '; ++start);
    for ( ; *start; ) {
	for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
	ch = *pt; *pt = '\0';
	sc = SFGetChar(data->sf,-1,start);
	*pt = ch;
	if ( sc!=NULL ) {
	    data->str[pos].sc = sc;
return( pos+1 );
	}
	while ( *pt==' ' ) ++pt;
	start = pt;
    }
return( 0 );
}

static int ApplyLigatureSubsAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    int i,k, lpos, npos;
    int lookup_flags = sub->lookup->lookup_flags;
    int match_found = -1, match_len=0;

    if ( data->lig_owner!=sub )
	LigatureSearch(sub,data);
    for ( i=0; i<data->lcnt; ++i ) {
	if ( data->ligs[i][1]==data->str[pos].sc ) {
	    lpos = 0;
	    npos = pos+1;
	    for ( k=2; data->ligs[i][k]!=NULL; ++k ) {
		npos = skipglyphs(lookup_flags,data,npos);
		if ( npos>=data->cnt || data->str[npos].sc != data->ligs[i][k] )
	    break;
		++npos;
	    }
	    if ( data->ligs[i][k]==NULL ) {
		if ( match_found==-1 || k>match_len ) {
		    match_found = i;
		    match_len = k;
		}
	    }
	}
    }
    if ( match_found!=-1 ) {
	/* Matched. Remove the component glyphs, and note which component */
	/*  any intervening marks should be attached to */
	data->str[pos].sc = data->ligs[match_found][0];
	npos = pos+1;
	for ( k=2; data->ligs[match_found][k]!=NULL; ++k ) {
	    lpos = skipglyphs(lookup_flags,data,npos);
	    for ( ; npos<lpos; ++npos )
		data->str[npos].lig_pos = k-2;
	    /* Remove this glyph (copy the final NUL too) */
	    for ( ++lpos; lpos<=data->cnt; ++lpos )
		data->str[lpos-1] = data->str[lpos];
	    --data->cnt;
	}
	/* Any marks after the last component (which should be attached */
	/*  to it) will not have been tagged, so do that now */
	lpos = skipglyphs(lookup_flags,data,npos);
	for ( ; npos<lpos; ++npos )
	    data->str[npos].lig_pos = k-2;
return( pos+1 );
    }

return( 0 );
}
		    
static int ApplyContextual(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    /* On this level there is no difference between GPOS/GSUB contextuals */
    /*  If the contextual matches, then we apply the lookups, otherwise we */
    /*  don't. Now the lookups will be different, but we don't care here */
    struct fpst_rule *rule;
    int retpos, i,j;

    retpos = ContextualMatch(sub,data,pos,&rule);
    if ( retpos==0 )
return( 0 );
    for ( i=0; i<rule->lookup_cnt; ++i ) {
	for ( j=pos; j<data->cnt; ++j ) {
	    if ( data->str[j].context_pos == rule->lookups[i].seq ) {
		ApplyLookupAtPos(0,rule->lookups[i].lookup,data,j);
	break;
	    }
	}
    }
return( retpos );
}

#ifdef FONTFORGE_CONFIG_DEVICETABLES
static int FigureDeviceTable(DeviceTable *dt,int pixelsize) {

    if ( dt==NULL || dt->corrections==NULL || pixelsize<dt->first_pixel_size ||
	    pixelsize>dt->last_pixel_size )
return( 0 );

return( dt->corrections[pixelsize - dt->last_pixel_size] );
}
#endif

static int ApplySinglePosAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    PST *pst;

    for ( pst=data->str[pos].sc->possub; pst!=NULL && pst->subtable!=sub; pst=pst->next );
    if ( pst==NULL )
return( 0 );

    data->str[pos].vr.xoff += rint( pst->u.pos.xoff * data->scale );
    data->str[pos].vr.yoff += rint( pst->u.pos.yoff * data->scale );
    data->str[pos].vr.h_adv_off += rint( pst->u.pos.h_adv_off * data->scale );
    data->str[pos].vr.v_adv_off += rint( pst->u.pos.v_adv_off * data->scale );
#ifdef FONTFORGE_CONFIG_DEVICETABLES
    if ( pst->u.pos.adjust!=NULL ) {
	data->str[pos].vr.xoff += FigureDeviceTable(&pst->u.pos.adjust->xadjust,data->pixelsize);
	data->str[pos].vr.yoff += FigureDeviceTable(&pst->u.pos.adjust->yadjust,data->pixelsize);
	data->str[pos].vr.h_adv_off += FigureDeviceTable(&pst->u.pos.adjust->xadv,data->pixelsize);
	data->str[pos].vr.v_adv_off += FigureDeviceTable(&pst->u.pos.adjust->yadv,data->pixelsize);
    }
#endif
return( pos+1 );
}

static int ApplyPairPosAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    PST *pst;
    int npos, isv, within;
    KernPair *kp;

    npos = skipglyphs(sub->lookup->lookup_flags,data,pos+1);
    if ( npos>=data->cnt )
return( 0 );
    if ( sub->kc!=NULL ) {
	within = KCFindIndex(sub->kc,data->str[pos].sc->name,data->str[npos].sc->name);
	if ( within==-1 )
return( 0 );
	data->str[pos].kc_index = within;
	data->str[pos].kc = sub->kc;
	if ( sub->vertical_kerning ) {
	    data->str[pos].vr.v_adv_off += rint( sub->kc->offsets[within] * data->scale );
#ifdef FONTFORGE_CONFIG_DEVICETABLES
	    data->str[pos].vr.v_adv_off += FigureDeviceTable(&sub->kc->adjusts[within],data->pixelsize);
#endif
	} else if ( sub->lookup->lookup_flags & pst_r2l ) {
	    data->str[npos].vr.h_adv_off += rint( sub->kc->offsets[within] * data->scale );
#ifdef FONTFORGE_CONFIG_DEVICETABLES
	    data->str[npos].vr.h_adv_off += FigureDeviceTable(&sub->kc->adjusts[within],data->pixelsize);
#endif
	} else {
	    data->str[pos].vr.h_adv_off += rint( sub->kc->offsets[within] * data->scale );
#ifdef FONTFORGE_CONFIG_DEVICETABLES
	    data->str[pos].vr.h_adv_off += FigureDeviceTable(&sub->kc->adjusts[within],data->pixelsize);
#endif
	}
return( pos+1 );
    } else {
	for ( pst=data->str[pos].sc->possub; pst!=NULL; pst=pst->next ) {
	    if ( pst->subtable==sub && strcmp(pst->u.pair.paired,data->str[npos].sc->name)==0 ) {
		data->str[pos].vr.xoff += rint( pst->u.pair.vr[0].xoff * data->scale);
		data->str[pos].vr.yoff += rint( pst->u.pair.vr[0].yoff * data->scale);
		data->str[pos].vr.h_adv_off += rint( pst->u.pair.vr[0].h_adv_off * data->scale);
		data->str[pos].vr.v_adv_off += rint( pst->u.pair.vr[0].v_adv_off * data->scale);
		data->str[npos].vr.xoff += rint( pst->u.pair.vr[1].xoff * data->scale);
		data->str[npos].vr.yoff += rint( pst->u.pair.vr[1].yoff * data->scale);
		data->str[npos].vr.h_adv_off += rint( pst->u.pair.vr[1].h_adv_off * data->scale);
		data->str[npos].vr.v_adv_off += rint( pst->u.pair.vr[1].v_adv_off * data->scale);
#ifdef FONTFORGE_CONFIG_DEVICETABLES
		/* I got bored. I should do all of them */
		if ( pst->u.pair.vr[0].adjust!=NULL ) {
		    data->str[pos].vr.h_adv_off += FigureDeviceTable(&pst->u.pair.vr[0].adjust->xadv,data->pixelsize);
		}
#endif
return( pos+1 );	/* We do NOT want to return npos+1 */
	    }
	}
	for ( isv = 0; isv<2; ++isv ) {
	    for ( kp = isv ? data->str[pos].sc->vkerns : data->str[pos].sc->kerns; kp!=NULL; kp=kp->next ) {
		if ( kp->subtable == sub && kp->sc == data->str[npos].sc ) {
		    data->str[pos].kp = kp;
		    if ( isv ) {
			data->str[pos].vr.v_adv_off += rint( kp->off * data->scale);
#ifdef FONTFORGE_CONFIG_DEVICETABLES
			data->str[pos].vr.v_adv_off += FigureDeviceTable(kp->adjust,data->pixelsize);
#endif
		    } else if ( sub->lookup->lookup_flags & pst_r2l ) {
			data->str[npos].vr.h_adv_off += rint( kp->off * data->scale);
#ifdef FONTFORGE_CONFIG_DEVICETABLES
			data->str[npos].vr.h_adv_off += FigureDeviceTable(kp->adjust,data->pixelsize);
#endif
		    } else {
			data->str[pos].vr.h_adv_off += rint( kp->off * data->scale);
#ifdef FONTFORGE_CONFIG_DEVICETABLES
			data->str[pos].vr.h_adv_off += FigureDeviceTable(kp->adjust,data->pixelsize);
#endif
		    }
return( pos+1 );
		}
	    }
	}
    }
	    
return( 0 );
}

static int ApplyAnchorPosAtPos(struct lookup_subtable *sub,struct lookup_data *data,int pos) {
    AnchorPoint *ap1, *ap2;
    int bpos;

    /* Anchors do not position the base glyph, but the mark (or second glyph */
    /*  of a cursive attachment). This means we don't apply the attachment when*/
    /*  we meet the first glyph, but wait until we meet the second, and then */
    /*  walk backwards */
    /* The backwards walk is different depending on the lookup type (I think) */
    /*  mark to base and mark to ligature lookups will skip all marks even if */
    /*  lookup flags don't specify that */
    /* mark to mark, and cursive attachment only skip what the lookup flags */
    /*  tell them to skip. */
    for ( ap2=data->str[pos].sc->anchor; ap2!=NULL ; ap2=ap2->next ) {
	if ( ap2->anchor->subtable==sub && (ap2->type == at_mark || ap2->type == at_centry))
    break;
    }
    if ( ap2==NULL ) {
	/* This subtable is not used by this glyph ... at least this glyph is */
	/*  neither a mark nor an entry point for this subtable */
return( 0 );
    }

    /* There's only going to be one mark anchor on a glyph in a given subtable*/
    /* And cursive attachments only allow one anchor class per subtable */
    /* in either case we have already found the only attachment site possible */
    /*  in the current glyph */

    if ( sub->lookup->lookup_type == gpos_mark2base ||
	    sub->lookup->lookup_type == gpos_mark2ligature )
	bpos = bskipmarkglyphs(sub->lookup->lookup_flags,data,pos-1);
    else
	bpos = bskipglyphs(sub->lookup->lookup_flags,data,pos-1);
    if ( bpos==-1 )
return( 0 );		/* No match */

    if ( sub->lookup->lookup_type == gpos_cursive ) {
	for ( ap1=data->str[bpos].sc->anchor; ap1!=NULL ; ap1=ap1->next ) {
	    if ( ap1->anchor==ap2->anchor && ap1->type==at_cexit )
	break;
	}
    } else if ( sub->lookup->lookup_type == gpos_mark2ligature ) {
	for ( ap1=data->str[bpos].sc->anchor; ap1!=NULL ; ap1=ap1->next ) {
	    if ( ap1->anchor==ap2->anchor && ap1->type==at_baselig &&
		    ap1->lig_index == data->str[pos].lig_pos )
	break;
	}
    } else {
	for ( ap1=data->str[bpos].sc->anchor; ap1!=NULL ; ap1=ap1->next ) {
	    if ( ap1->anchor==ap2->anchor &&
		    (ap1->type==at_basechar || ap1->type==at_basemark) )
	break;
	}
    }
    if ( ap1==NULL )
return( 0 );		/* No match */

/* This probably doesn't work for vertical text */
    data->str[pos].vr.yoff = data->str[bpos].vr.yoff +
	    rint((ap1->me.y - ap2->me.y) * data->scale);
#ifdef FONTFORGE_CONFIG_DEVICETABLES
    data->str[pos].vr.yoff += FigureDeviceTable(&ap1->yadjust,data->pixelsize)-
		FigureDeviceTable(&ap2->yadjust,data->pixelsize);
#endif
    if ( sub->lookup->lookup_flags&pst_r2l ) {
	data->str[pos].vr.xoff = data->str[bpos].vr.xoff +
		rint( -(ap1->me.x - ap2->me.x)*data->scale );
#ifdef FONTFORGE_CONFIG_DEVICETABLES
	data->str[pos].vr.xoff -= FigureDeviceTable(&ap1->xadjust,data->pixelsize)-
		    FigureDeviceTable(&ap2->xadjust,data->pixelsize);
#endif
    } else {
	data->str[pos].vr.xoff = data->str[bpos].vr.xoff +
		rint( (ap1->me.x - ap2->me.x - data->str[bpos].sc->width)*data->scale -
		data->str[bpos].vr.h_adv_off);
#ifdef FONTFORGE_CONFIG_DEVICETABLES
	data->str[pos].vr.xoff += FigureDeviceTable(&ap1->xadjust,data->pixelsize)-
		    FigureDeviceTable(&ap2->xadjust,data->pixelsize);
#endif
    }

return( pos+1 );
}

static int ConditionalTagOk(uint32 tag, OTLookup *otl,struct lookup_data *data,int pos) {
    int npos, bpos;
    uint32 script;
    int before_in_script, after_in_script;

    if ( tag==CHR('i','n','i','t') || tag==CHR('i','s','o','l') ||
	    tag==CHR('f','i','n','a') || tag==CHR('m','e','d','i') ) {
	npos = skipglyphs(otl->lookup_flags,data,pos+1);
	bpos = bskipglyphs(otl->lookup_flags,data,pos-1);
	script = SCScriptFromUnicode(data->str[pos].sc);
	before_in_script = (bpos>=0 && SCScriptFromUnicode(data->str[bpos].sc)==script);
	after_in_script = (npos<data->cnt && SCScriptFromUnicode(data->str[npos].sc)==script);
	if ( tag==CHR('i','n','i','t') )
return( !before_in_script && after_in_script );
	else if ( tag==CHR('i','s','o','l') )
return( !before_in_script && !after_in_script );
	else if ( tag==CHR('f','i','n','a') )
return( before_in_script && !after_in_script );
	else
return( before_in_script && after_in_script );
    }

return( true );
}

static int ApplyLookupAtPos(uint32 tag, OTLookup *otl,struct lookup_data *data,int pos) {
    struct lookup_subtable *sub;
    int newpos;

    /* Some tags imply a conditional check. Do that now */
    if ( !ConditionalTagOk(tag,otl,data,pos))
return( 0 );

    for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
	switch ( otl->lookup_type ) {
	  case gsub_single:
	    newpos = ApplySingleSubsAtPos(sub,data,pos);
	  break;
	  case gsub_multiple:
	    newpos = ApplyMultSubsAtPos(sub,data,pos);
	  break;
	  case gsub_alternate:
	    newpos = ApplyAltSubsAtPos(sub,data,pos);
	  break;
	  case gsub_ligature:
	    newpos = ApplyLigatureSubsAtPos(sub,data,pos);
	  break;
	  case gsub_context:
	    newpos = ApplyContextual(sub,data,pos);
	  break;
	  case gsub_contextchain:
	    newpos = ApplyContextual(sub,data,pos);
	  break;
	  case gsub_reversecchain:
	    newpos = ApplySingleSubsAtPos(sub,data,pos);
	  break;

	  case gpos_single:
	    newpos = ApplySinglePosAtPos(sub,data,pos);
	  break;
	  case gpos_pair:
	    newpos = ApplyPairPosAtPos(sub,data,pos);
	  break;
	  case gpos_cursive:
	    newpos = ApplyAnchorPosAtPos(sub,data,pos);
	  break;
	  case gpos_mark2base:
	    newpos = ApplyAnchorPosAtPos(sub,data,pos);
	  break;
	  case gpos_mark2ligature:
	    newpos = ApplyAnchorPosAtPos(sub,data,pos);
	  break;
	  case gpos_mark2mark:
	    newpos = ApplyAnchorPosAtPos(sub,data,pos);
	  break;
	  case gpos_context:
	    newpos = ApplyContextual(sub,data,pos);
	  break;
	  case gpos_contextchain:
	    newpos = ApplyContextual(sub,data,pos);
	  break;
	  default:
	    newpos = 0;
	  break;
	}
	/* if a subtable worked, we don't try to apply the next one */
	if ( newpos!=0 )
return( newpos );
    }
return( 0 );
}

static void ApplyLookup(uint32 tag, OTLookup *otl,struct lookup_data *data) {
    int pos, npos;
	/* OpenType */
	for ( pos = 0; pos<data->cnt; ) {
	    npos = ApplyLookupAtPos(tag,otl,data,pos);
	    if ( npos<=pos)		/* !!!!! */
		npos = pos+1;
	    pos = npos;
	}
}

static uint32 FSLLMatches(FeatureScriptLangList *fl,uint32 *flist,uint32 script,uint32 lang) {
    int i,l;
    struct scriptlanglist *sl;

    if ( flist==NULL )
return( 0 );

    while ( fl!=NULL ) {
	for ( i=0; flist[i]!=0; ++i ) {
	    if ( fl->featuretag==flist[i] )
	break;
	}
	if ( flist[i]!=0 ) {
	    for ( sl=fl->scripts; sl!=NULL; sl=sl->next ) {
		if ( sl->script == script ) {
		    for ( l=0; l<sl->lang_cnt; ++l )
			if ( (l<MAX_LANG && sl->langs[l]==lang) ||
				(l>=MAX_LANG && sl->morelangs[l-MAX_LANG]==lang))
return( fl->featuretag );
		}
	    }
	}
	fl = fl->next;
    }
return( 0 );
}

/* This routine takes a string of glyphs and applies the opentype transformations */
/*  indicated by the features (and script and language) we are passed, it returns */
/*  a transformed string with substitutions applied and containing positioning */
/*  info */
struct opentype_str *ApplyTickedFeatures(SplineFont *sf,uint32 *flist, uint32 script, uint32 lang,
	int pixelsize, SplineChar **glyphs) {
    int isgpos, cnt;
    OTLookup *otl;
    struct lookup_data data;
    uint32 *langs, templang;
    int i;

    memset(&data,0,sizeof(data));
    for ( cnt=0; glyphs[cnt]!=NULL; ++cnt );
    data.str = gcalloc(cnt+1,sizeof(struct opentype_str));
    data.cnt = data.max = cnt;
    for ( cnt=0; glyphs[cnt]!=NULL; ++cnt ) {
	data.str[cnt].sc = glyphs[cnt];
	data.str[cnt].orig_index = cnt;
	data.str[cnt].lig_pos = data.str[cnt].context_pos = -1;
    }
    if ( sf->cidmaster!=NULL ) sf=sf->cidmaster;
    data.sf = sf;
    data.pixelsize = pixelsize;
    data.scale = pixelsize/(double) (sf->ascent+sf->descent);

    /* Indic glyph reordering???? */
    for ( isgpos=0; isgpos<2; ++isgpos ) {
	/* Check that this table has an entry for this language */
	/*  if it doesn't use the default language */
	/* GPOS/GSUB may have different language sets, so we must be prepared */
	templang = lang;
	langs = SFLangsInScript(sf,isgpos,script);
	for ( i=0; langs[i]!=0 && langs[i]!=lang; ++i );
	if ( langs[i]==0 )
	    templang = DEFAULT_LANG;
	free(langs);

	for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL ; otl = otl->next ) {
	    uint32 tag;
	    if ( (tag=FSLLMatches(otl->features,flist,script,templang))!=0 )
		ApplyLookup(tag,otl,&data);
	}
    }
    LigatureFree(&data);
    free(data.ligs);

    data.str = grealloc(data.str,(data.cnt+1)*sizeof(struct opentype_str));
    memset(&data.str[data.cnt],0,sizeof(struct opentype_str));
return( data.str );
}

static void doreplace(char **haystack,char *start,char *search,char *rpl,int slen) {
    int rlen;
    char *pt = start+slen;
    (void)search;
    rlen = strlen(rpl);
    if ( slen>=rlen ) {
	memcpy(start,rpl,rlen);
	if ( slen>rlen ) {
	    int diff = slen-rlen;
	    for ( ; *pt ; ++pt )
		pt[-diff] = *pt;
	    pt[-diff] = '\0';
	}
    } else {
	char *base = *haystack;
	char *new = galloc(pt-base+strlen(pt)+rlen-slen+1);
	memcpy(new,base,start-base);
	memcpy(new+(start-base),rpl,rlen);
	strcpy(new+(start-base)+rlen,pt);
	free( base );
	*haystack = new;
    }
}

static int rplstr(char **haystack,char *search, char *rpl,int multipleoccurances) {
    char *start, *pt, *base = *haystack;
    int ch, match, slen = strlen(search);
    int any = 0;

    if ( base==NULL )
return( false );

    for ( pt=base ; ; ) {
	while ( *pt==' ' ) ++pt;
	if ( *pt=='\0' )
return( any );
	start=pt;
	while ( *pt!=' ' && *pt!='\0' ) ++pt;
	if ( pt-start!=slen )
	    match = -1;
	else {
	    ch = *pt; *pt='\0';
	    match = strcmp(start,search);
	    *pt = ch;
	}
	if ( match==0 ) {
	    doreplace(haystack,start,search,rpl,slen);
	    if ( !multipleoccurances )
return( true );
	    any = true;
	    if ( base!=*haystack ) {
		pt = *haystack + (start-base)+strlen(rpl);
		base = *haystack;
	    } else
		pt = start+strlen(rpl);
	}
    }
}

static int rplglyphname(char **haystack,char *search, char *rpl) {
    /* If we change "f" to "uni0066" then we should also change "f.sc" to */
    /*  "uni0066.sc" and "f_f_l" to "uni0066_uni0066_l" */
    char *start, *pt, *base = *haystack;
    int ch, match;
    unsigned slen = strlen(search);
    int any = 0;

    if ( slen>=strlen( base ))
return( false );

    for ( pt=base ; ; ) {
	while ( *pt=='_' ) ++pt;
	if ( *pt=='\0' || *pt=='.' )
return( any );
	start=pt;
	while ( *pt!='_' && *pt!='\0' && *pt!='.' ) ++pt;
	if ( *pt=='\0' && start==base )	/* Don't change any unsegmented names */
return( false );			/* In particular don't rename ourselves*/
	if ( pt-start!=slen )
	    match = -1;
	else {
	    ch = *pt; *pt='\0';
	    match = strcmp(start,search);
	    *pt = ch;
	}
	if ( match==0 ) {
	    doreplace(haystack,start,search,rpl,slen);
	    any = true;
	    if ( base!=*haystack ) {
		pt = *haystack + (start-base) + strlen(rpl);
		base = *haystack;
	    } else
		pt = start+strlen(rpl);
	}
    }
}

static int glyphnameIsComponent(char *haystack,char *search) {
    /* Check for a glyph name in ligature names and dotted names */
    char *start, *pt;
    unsigned slen = strlen(search);

    if ( slen>=strlen( haystack ))
return( false );

    for ( pt=haystack ; ; ) {
	while ( *pt=='_' ) ++pt;
	if ( *pt=='\0' || *pt=='.' )
return( false );
	start=pt;
	while ( *pt!='_' && *pt!='\0' && *pt!='.' ) ++pt;
	if ( *pt=='\0' && start==haystack )/* Don't change any unsegmented names */
return( false );			/* In particular don't rename ourselves*/
	if ( pt-start==slen && strncmp(start,search,slen)==0 )
return( true );
    }
}

static int gvfixup(struct glyphvariants *gv,char *old, char *new) {
    int i;
    int ret=0;

    if ( gv==NULL )
return( false );
    ret = rplstr(&gv->variants,old,new,false);
    for ( i=0; i<gv->part_cnt; ++i ) {
	if ( strcmp(gv->parts[i].component,old)==0 ) {
	    free( gv->parts[i].component);
	    gv->parts[i].component = copy(new);
	    ret = true;
	}
    }
return( ret );
}

struct lookup_subtable *SFSubTableFindOrMake(SplineFont *sf,uint32 tag,uint32 script,
	int lookup_type ) {
    OTLookup **base;
    OTLookup *otl, *found=NULL;
    int isgpos = lookup_type>=gpos_start;
    struct lookup_subtable *sub;
    int isnew = false;

    if ( sf->cidmaster ) sf = sf->cidmaster;
    base = isgpos ? &sf->gpos_lookups : &sf->gsub_lookups;
    for ( otl= *base; otl!=NULL; otl=otl->next ) {
      if ( otl->lookup_type==(unsigned)lookup_type &&
		FeatureScriptTagInFeatureScriptList(tag,script,otl->features) ) {
	    for ( sub = otl->subtables; sub!=NULL; sub=sub->next )
		if ( sub->kc==NULL )
return( sub );
	    found = otl;
	}
    }

    if ( found==NULL ) {
	found = chunkalloc(sizeof(OTLookup));
	found->lookup_type = lookup_type;
	found->features = chunkalloc(sizeof(FeatureScriptLangList));
	found->features->featuretag = tag;
	found->features->scripts = chunkalloc(sizeof(struct scriptlanglist));
	found->features->scripts->script = script;
	found->features->scripts->langs[0] = DEFAULT_LANG;
	found->features->scripts->lang_cnt = 1;

	SortInsertLookup(sf, found);
	isnew = true;
    }

    sub = chunkalloc(sizeof(struct lookup_subtable));
    sub->next = found->subtables;
    found->subtables = sub;
    sub->lookup = found;
    sub->per_glyph_pst_or_kern = true;

    NameOTLookup(found,sf);
return( sub );
}

struct lookup_subtable *SFSubTableMake(SplineFont *sf,uint32 tag,uint32 script,
	int lookup_type ) {
    OTLookup **base;
    OTLookup *otl, *found=NULL;
    int isgpos = lookup_type>=gpos_start;
    struct lookup_subtable *sub;
    int isnew = false;

    if ( sf->cidmaster ) sf = sf->cidmaster;
    base = isgpos ? &sf->gpos_lookups : &sf->gsub_lookups;
    for ( otl= *base; otl!=NULL; otl=otl->next ) {
      if ( otl->lookup_type==(unsigned)lookup_type &&
		FeatureScriptTagInFeatureScriptList(tag,script,otl->features) ) {
	    found = otl;
	}
    }

    if ( found==NULL ) {
	found = chunkalloc(sizeof(OTLookup));
	found->lookup_type = lookup_type;
	found->features = chunkalloc(sizeof(FeatureScriptLangList));
	found->features->featuretag = tag;
	found->features->scripts = chunkalloc(sizeof(struct scriptlanglist));
	found->features->scripts->script = script;
	found->features->scripts->langs[0] = DEFAULT_LANG;
	found->features->scripts->lang_cnt = 1;

	SortInsertLookup(sf, found);
	isnew = true;
    }

    sub = chunkalloc(sizeof(struct lookup_subtable));
    sub->next = found->subtables;
    found->subtables = sub;
    sub->lookup = found;

    if ( isnew )
	NameOTLookup(found,sf);
return( sub );
}

int LookupUsedNested(SplineFont *sf,OTLookup *checkme) {
    OTLookup *otl;
    struct lookup_subtable *sub;
    int r,c;

    if ( checkme->lookup_type>=gpos_start )
	otl = sf->gpos_lookups;
    else
	otl = sf->gsub_lookups;
    while ( otl!=NULL ) {
	for ( sub = otl->subtables; sub!=NULL; sub=sub->next ) {
	    if ( sub->fpst!=NULL ) {
		for ( r=0; r<sub->fpst->rule_cnt; ++r ) {
		    struct fpst_rule *rule = &sub->fpst->rules[r];
		    for ( c=0; c<rule->lookup_cnt; ++c ) {
			if ( rule->lookups[c].lookup == checkme )
return( true );
		    }
		}
	    }
	}
    }
return( false );
}

static void AALTRemoveOld(SplineFont *sf) {
    FeatureScriptLangList *fl, *prev;
    OTLookup *otl, *otlnext;

    for ( otl=sf->gsub_lookups; otl!=NULL; otl=otlnext ) {
	otlnext = otl->next;
	prev = NULL;
	for ( fl = otl->features; fl!=NULL; prev=fl, fl=fl->next ) {
	    if ( fl->featuretag==CHR('a','a','l','t') ) {
		if ( fl==otl->features && fl->next==NULL && !LookupUsedNested(sf,otl))
		    SFRemoveLookup(sf,otl);
		else {
		    if ( prev==NULL )
			otl->features = fl->next;
		    else
			prev->next = fl->next;
		    fl->next = NULL;
		    FeatureScriptLangListFree(fl);
		}
	break;
	    }
	}
    }
}

void SllkFree(struct sllk *sllk,int sllk_cnt) {
    int i;

    for ( i=0; i<sllk_cnt; ++i ) {
	free( sllk[i].langs );
	free( sllk[i].lookups );
    }
    free(sllk);
}

static void AddOTLToSllk(struct sllk *sllk, OTLookup *otl, struct scriptlanglist *sl) {
    int i,j,k,l;

    if ( otl->lookup_type==gsub_single || otl->lookup_type==gsub_alternate ) {
	for ( i=0; i<sllk->cnt; ++i )
	    if ( sllk->lookups[i]==otl )
	break;
	if ( i==sllk->cnt ) {
	    if ( sllk->cnt>=sllk->max )
		sllk->lookups = grealloc(sllk->lookups,(sllk->max+=5)*sizeof(OTLookup *));
	    sllk->lookups[sllk->cnt++] = otl;
	    for ( l=0; l<sl->lang_cnt; ++l ) {
		uint32 lang = l<MAX_LANG ? sl->langs[l] : sl->morelangs[l-MAX_LANG];
		for ( j=0; j<sllk->lcnt; ++j )
		    if ( sllk->langs[j]==lang )
		break;
		if ( j==sllk->lcnt ) {
		    if ( sllk->lcnt>=sllk->lmax )
			sllk->langs = grealloc(sllk->langs,(sllk->lmax+=sl->lang_cnt+MAX_LANG)*sizeof(uint32));
		    sllk->langs[sllk->lcnt++] = lang;
		}
	    }
	}
    } else if ( otl->lookup_type==gsub_context || otl->lookup_type==gsub_contextchain ) {
	struct lookup_subtable *sub;
	for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
	    FPST *fpst = sub->fpst;
	    for ( j=0; j<fpst->rule_cnt; ++j ) {
		struct fpst_rule *r = &fpst->rules[j];
		for ( k=0; k<r->lookup_cnt; ++k )
		    AddOTLToSllk(sllk,r->lookups[k].lookup,sl);
	    }
	}
    }
    /* reverse contextual chaining is weird and I shall ignore it. Adobe does too*/
}

static char *ComponentsFromPSTs(PST **psts,int pcnt) {
    char **names=NULL;
    int ncnt=0, nmax=0;
    int i,j,len;
    char *ret;

    /* First find all the names */
    for ( i=0; i<pcnt; ++i ) {
	char *nlist = psts[i]->u.alt.components;
	char *start, *pt, ch;

	for ( start = nlist; ; ) {
	    while ( *start==' ' )
		++start;
	    if ( *start=='\0' )
	break;
	    for ( pt=start; *pt!=' ' && *pt!='\0'; ++pt );
	    ch = *pt; *pt = '\0';
	    for ( j=0; j<ncnt; ++j )
		if ( strcmp( start,names[j])==0 )
	    break;
	    if ( j==ncnt ) {
		if ( ncnt>=nmax )
		    names = grealloc(names,(nmax+=10)*sizeof(char *));
		names[ncnt++] = copy(start);
	    }
	    *pt = ch;
	    start = pt;
	}
    }

    len = 0;
    for ( i=0; i<ncnt; ++i )
	len += strlen(names[i])+1;
    if ( len==0 ) len=1;
    ret = galloc(len);
    len = 0;
    for ( i=0; i<ncnt; ++i ) {
	strcpy(ret+len,names[i]);
	len += strlen(names[i]);
	ret[len++] = ' ';
    }
    if ( len==0 )
	*ret = '\0';
    else
	ret[len-1] = '\0';

    for ( i=0; i<ncnt; ++i )
	free(names[i]);
    free(names);
return( ret );
}

static int SllkMatch(struct sllk *sllk,int s1,int s2) {
    int i;

    if ( sllk[s1].cnt != sllk[s2].cnt )
return( false );

    for ( i=0; i<sllk[s1].cnt; ++i ) {
	if ( sllk[s1].lookups[i] != sllk[s2].lookups[i] )
return( false );
    }

return( true );
}

struct sllk *AddOTLToSllks( OTLookup *otl, struct sllk *sllk,
	int *_sllk_cnt, int *_sllk_max ) {
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;
    int s;

    for ( fl=otl->features; fl!=NULL; fl=fl->next ) {
	for ( sl=fl->scripts; sl!=NULL; sl=sl->next ) {
	    for ( s=0; s<*_sllk_cnt; ++s )
		if ( sl->script == sllk[s].script )
	    break;
	    if ( s==*_sllk_cnt ) {
		if ( *_sllk_cnt>=*_sllk_max )
		    sllk = grealloc(sllk,((*_sllk_max)+=10)*sizeof(struct sllk));
		memset(&sllk[*_sllk_cnt],0,sizeof(struct sllk));
		sllk[(*_sllk_cnt)++].script = sl->script;
	    }
	    AddOTLToSllk(&sllk[s], otl,sl);
	}
    }
return( sllk );
}

OTLookup *NewAALTLookup(SplineFont *sf,struct sllk *sllk, int sllk_cnt, int i) {
    OTLookup *otl;
    struct lookup_subtable *sub;
    FeatureScriptLangList *fl;
    struct scriptlanglist *sl;
    PST **psts, *pst;
    int j,k,l;
    int gid,pcnt;
    SplineFont *_sf;
    SplineChar *sc;

    /* Make the new lookup (and all its supporting data structures) */
    otl = chunkalloc(sizeof(OTLookup));
    otl->lookup_type = gsub_alternate;
    otl->lookup_flags = sllk[i].lookups[0]->lookup_flags & pst_r2l;
    otl->features = fl = chunkalloc(sizeof(FeatureScriptLangList));
    fl->featuretag = CHR('a','a','l','t');
    /* Any other scripts with the same lookup set? */
    for ( j=i; j<sllk_cnt; ++j ) {
	if ( i==j || SllkMatch(sllk,i,j)) {
	    sl = chunkalloc(sizeof(struct scriptlanglist));
	    sl->next = fl->scripts;
	    fl->scripts = sl;
	    sl->script = sllk[j].script;
	    sl->lang_cnt = sllk[j].lcnt;
	    if ( sl->lang_cnt>MAX_LANG )
		sl->morelangs = galloc((sl->lang_cnt-MAX_LANG)*sizeof(uint32));
	    for ( l=0; l<sl->lang_cnt; ++l )
		if ( l<MAX_LANG )
		    sl->langs[l] = sllk[j].langs[l];
		else
		    sl->morelangs[l-MAX_LANG] = sllk[j].langs[l];
	    if ( i!=j ) sllk[j].cnt = 0;	/* Mark as processed */
	}
    }
    otl->subtables = sub = chunkalloc(sizeof(struct lookup_subtable));
    sub->lookup = otl;
    sub->per_glyph_pst_or_kern = true;

    /* Add it to the various lists it needs to be in */
    otl->next = sf->gsub_lookups;
    sf->gsub_lookups = otl;

    /* Now look at every glyph in the font, and see if it has any of the */
    /*  lookups we are interested in, and if it does, build a new pst */
    /*  containing all posibilities listed on any of them */
    if ( sf->cidmaster ) sf = sf->cidmaster;
    psts = galloc(sllk[i].cnt*sizeof(PST *));
    k=0;
    do {
	_sf = k<sf->subfontcnt ? sf->subfonts[k] : sf;
	for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (sc = _sf->glyphs[gid])!=NULL ) {
	    pcnt = 0;
	    for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
		if ( pst->subtable==NULL )
	    continue;
		for ( j=0; j<sllk[i].cnt; ++j )
		    if ( pst->subtable->lookup == sllk[i].lookups[j] )
		break;
		if ( j<sllk[i].cnt )
		    psts[pcnt++] = pst;
	    }
	    if ( pcnt==0 )
	continue;
	    pst = chunkalloc(sizeof(PST));
	    pst->subtable = sub;
	    pst->type = pst_alternate;
	    pst->next = sc->possub;
	    sc->possub = pst;
	    pst->u.alt.components = ComponentsFromPSTs(psts,pcnt);
	}
	++k;
    } while ( k<sf->subfontcnt );
    free(psts);
    NameOTLookup(otl,sf);
return( otl );
}

void AddNewAALTFeatures(SplineFont *sf) {
    /* different script/lang combinations may need different 'aalt' lookups */
    /*  well, let's just say different script combinations */
    /* for each script/lang combo find all single/alternate subs for each */
    /*  glyph. Merge those choices and create new lookup with that info */
    struct sllk *sllk = NULL;
    int sllk_cnt=0, sllk_max = 0;
    int i;
    OTLookup *otl;

    AALTRemoveOld(sf);

    /* Find all scripts, and all the single/alternate lookups for each */
    /*  and all the languages used for these in each script */
    for ( otl=sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
	sllk = AddOTLToSllks( otl, sllk, &sllk_cnt, &sllk_max );
    }
    /* Each of these gets its own gsub_alternate lookup which gets inserted */
    /*  at the head of the lookup list. Each lookup has one subtable */
    for ( i=0; i<sllk_cnt; ++i ) {
	if ( sllk[i].cnt==0 )		/* Script used, but provides no alternates */
    continue;
	NewAALTLookup(sf,sllk,sllk_cnt,i);
    }

    SllkFree(sllk,sllk_cnt);
}


int VerticalKernFeature(SplineFont *sf, OTLookup *otl, int ask) {
    FeatureScriptLangList *fl;
    struct lookup_subtable *sub;
    KernClass *kc;
    char *buts[3];

    for ( fl=otl->features; fl!=NULL; fl=fl->next ) {
	if ( fl->featuretag==CHR('k','e','r','n') )
return( false );
	else if ( fl->featuretag==CHR('v','k','r','n') )
return( true );
    }

    for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
	if ( sub->kc!=NULL ) {
	    for ( kc=sf->kerns; kc!=NULL; kc=kc->next )
		if ( kc==sub->kc )
return( false );
	    for ( kc=sf->vkerns; kc!=NULL; kc=kc->next )
		if ( kc==sub->kc )
return( true );
	}
    }

    if ( !ask )
return( -1 );

    buts[0] = _("_Horizontal"); buts[1] = _("_Vertical"); buts[2] = NULL;
return( ff_ask(_("Kerning direction"),(const char **) buts,0,1,_("Is this horizontal or vertical kerning data?")) );
}

int IsAnchorClassUsed(SplineChar *sc,AnchorClass *an) {
    AnchorPoint *ap;
    int waslig=0, sawentry=0, sawexit=0;

    for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) {
	if ( ap->anchor==an ) {
	    if ( ap->type==at_centry )
		sawentry = true;
	    else if ( ap->type==at_cexit )
		sawexit = true;
	    else if ( an->type==act_mkmk ) {
		if ( ap->type==at_basemark )
		    sawexit = true;
		else
		    sawentry = true;
	    } else if ( ap->type!=at_baselig )
return( -1 );
	    else if ( waslig<ap->lig_index+1 )
		waslig = ap->lig_index+1;
	}
    }
    if ( sawentry && sawexit )
return( -1 );
    else if ( sawentry )
return( -2 );
    else if ( sawexit )
return( -3 );
return( waslig );
}

int PSTContains(const char *components,const char *name) {
    const char *pt;
    int len = strlen(name);

    for ( pt = strstr(components,name); pt!=NULL; pt = strstr(pt+len,name)) {
	if (( pt==components || pt[-1]==' ') && (pt[len]==' ' || pt[len]=='\0'))
return( true );
    }
return( false );
}

int KernClassContains(KernClass *kc, char *name1, char *name2, int ordered ) {
    int infirst=0, insecond=0, scpos1=0, kwpos1=0, scpos2=0, kwpos2=0;
    int i;

    for ( i=1; i<kc->first_cnt; ++i ) {
	if ( PSTContains(kc->firsts[i],name1) ) {
	    scpos1 = i;
	    if ( ++infirst>=3 )		/* The name occurs twice??? */
    break;
	} else if ( PSTContains(kc->firsts[i],name2) ) {
	    kwpos1 = i;
	    if ( (infirst+=2)>=3 )
    break;
	}
    }
    if ( infirst==0 || infirst>3 )
return( 0 );
    for ( i=1; i<kc->second_cnt; ++i ) {
	if ( PSTContains(kc->seconds[i],name1) ) {
	    scpos2 = i;
	    if ( ++insecond>=3 )
    break;
	} else if ( PSTContains(kc->seconds[i],name2) ) {
	    kwpos2 = i;
	    if ( (insecond+=2)>=3 )
    break;
	}
    }
    if ( insecond==0 || insecond>3 )
return( 0 );
    if ( (infirst&1) && (insecond&2) ) {
	if ( kc->offsets[scpos1*kc->second_cnt+kwpos2]!=0 )
return( kc->offsets[scpos1*kc->second_cnt+kwpos2] );
    }
    if ( !ordered ) {
	if ( (infirst&2) && (insecond&1) ) {
	    if ( kc->offsets[kwpos1*kc->second_cnt+scpos2]!=0 )
return( kc->offsets[kwpos1*kc->second_cnt+scpos2] );
	}
    }
return( 0 );
}

int KCFindName(char *name, char **classnames, int cnt ) {
    int i;
    char *pt, *end, ch;

    for ( i=0; i<cnt; ++i ) {
	if ( classnames[i]==NULL )
    continue;
	for ( pt = classnames[i]; *pt; pt=end+1 ) {
	    end = strchr(pt,' ');
	    if ( end==NULL ) end = pt+strlen(pt);
	    ch = *end;
	    *end = '\0';
	    if ( strcmp(pt,name)==0 ) {
		*end = ch;
return( i );
	    }
	    *end = ch;
	    if ( ch=='\0' )
	break;
	}
    }
return( 0 );
}

int KCFindIndex(KernClass *kc,char *name1, char *name2) {
    int f,l;

    f = KCFindName(name1,kc->firsts,kc->first_cnt);
    l = KCFindName(name2,kc->seconds,kc->second_cnt);
    if ( (f!=0 || kc->firsts[0]!=NULL) && l!=0 )
return( f*kc->second_cnt+l );

return( -1 );
}


static void NOFI_SortInsertLookup(SplineFont *sf, OTLookup *newotl) {
  (void)sf;
  (void)newotl;
}

static void NOFI_OTLookupCopyInto(SplineFont *into_sf,SplineFont *from_sf,
	OTLookup *from_otl, OTLookup *to_otl, int scnt, OTLookup *before ) {
  (void)into_sf;
  (void)from_sf;
  (void)from_otl;
  (void)to_otl;
  (void)scnt;
  (void)before;
}

static void NOFI_Destroy(SplineFont *sf) {
  (void)sf;
}

struct fi_interface noui_fi = {
    NOFI_SortInsertLookup,
    NOFI_OTLookupCopyInto,
    NOFI_Destroy
};

struct fi_interface *fi_interface = &noui_fi;

void FF_SetFIInterface(struct fi_interface *fii) {
    fi_interface = fii;
}
