/* $Id: tottfgpos.c 4532 2015-12-22 13:18:53Z mskala $ */
/* Copyright (C) 2000-2012  George Williams
 * Copyright (C) 2015  Matthew Skala
 *
 * 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 "fontanvilvw.h"
#include <utype.h>
#include <ustring.h>

int coverageformatsallowed=3;

int use_second_indic_scripts=false;

#include "ttf.h"

/* This file contains routines to create the otf gpos and gsub tables and their */
/*  attendant subtables */

/* Undocumented fact: ATM (which does kerning for otf fonts in Word) can't handle features with multiple lookups */

/* Undocumented fact: Only one feature with a given tag allowed per script/lang */
/*  So if we have multiple lookups with the same tag they must be merged into */
/*  one feature with many lookups */

/* scripts (for opentype) that I understand */
    /* see also list in lookups.c mapping script tags to friendly names */

static uint32_t scripts[][15]={
/* Arabic */ {CHR('a', 'r', 'a', 'b'), 0x0600, 0x06ff, 0xfb50, 0xfdff,
		 0xfe70, 0xfefe},
/* Aramaic */ {CHR('a', 'r', 'a', 'm'), 0x820, 0x83f},
/* Armenian */ {CHR('a', 'r', 'm', 'n'), 0x0530, 0x058f, 0xfb13, 0xfb17},
/* Balinese */ {CHR('b', 'a', 'l', 'i'), 0x1b00, 0x1b7f},
/* Bengali */ {CHR('b', 'e', 'n', 'g'), 0x0980, 0x09ff},
/* Bliss symb */ {CHR('b', 'l', 'i', 's'), 0x12200, 0x124ff},
/* Bopomofo */ {CHR('b', 'o', 'p', 'o'), 0x3100, 0x312f, 0x31a0, 0x31bf},
/* Braille */ {CHR('b', 'r', 'a', 'i'), 0x2800, 0x28ff},
/* Buginese */ {CHR('b', 'u', 'g', 'i'), 0x1a00, 0x1a1f},
/* Buhid */ {CHR('b', 'u', 'h', 'd'), 0x1740, 0x1753},
/* Byzantine M*/ {CHR('b', 'y', 'z', 'm'), 0x1d000, 0x1d0ff},
/* Canadian Syl*/ {CHR('c', 'a', 'n', 's'), 0x1400, 0x167f},
/* Carian */ {CHR('c', 'a', 'r', 'i'), 0x0, 0x0},
/* Cham */ {CHR('c', 'h', 'a', 'm'), 0x0, 0x0},
/* Cherokee */ {CHR('c', 'h', 'e', 'r'), 0x13a0, 0x13ff},
/* Cirth */ {CHR('c', 'i', 'r', 't'), 0x12080, 0x120ff},
/* CJKIdeogra */ {CHR('h', 'a', 'n', 'i'), 0x3300, 0x9fff, 0xf900, 0xfaff,
		     0x020000, 0x02ffff},
/* Coptic */ {CHR('c', 'o', 'p', 't'), 0x2c80, 0x2cff},
/* Cypriot */ {CHR('c', 'p', 'm', 'n'), 0x10800, 0x1083f},
/* Cyrillic */ {CHR('c', 'y', 'r', 'l'), 0x0400, 0x052f, 0x1d2b, 0x1d2b,
		   0x1d78, 0x1d78,
		0x2de0, 0x2dff, 0xa640, 0xa6ff},
/* Deseret */ {CHR('d', 's', 'r', 't'), 0x10400, 0x1044f},
/* Devanagari */ {CHR('d', 'e', 'v', 'a'), 0x0900, 0x097f},
/* Ethiopic */ {CHR('e', 't', 'h', 'i'), 0x1200, 0x139f},
/* Georgian */ {CHR('g', 'e', 'o', 'r'), 0x1080, 0x10ff},
/* Glagolitic */ {CHR('g', 'l', 'a', 'g'), 0x1080, 0x10ff},
/* Gothic */ {CHR('g', 'o', 't', 'h'), 0x10330, 0x1034a},
/* Greek */ {CHR('g', 'r', 'e', 'k'), 0x0370, 0x03ff, 0x1f00, 0x1fff},
/* Gujarati */ {CHR('g', 'u', 'j', 'r'), 0x0a80, 0x0aff},
/* Gurmukhi */ {CHR('g', 'u', 'r', 'u'), 0x0a00, 0x0a7f},
/* Hangul */ {CHR('h', 'a', 'n', 'g'), 0xac00, 0xd7af, 0x3130, 0x319f,
		 0xffa0, 0xff9f},
/* Hanunoo */ {CHR('h', 'a', 'n', 'o'), 0x1720, 0x1734},
   /* I'm not sure what the difference is between the 'hang' tag and the 'jamo' */
   /*  tag. 'Jamo' is said to be the precomposed forms, but what's 'hang'? */
/* Hebrew */ {CHR('h', 'e', 'b', 'r'), 0x0590, 0x05ff, 0xfb1e, 0xfb4f},
/* Hiragana used to have its own tag 'hira', but has since been merged with katakana */
/* Hangul Jamo*/ {CHR('j', 'a', 'm', 'o'), 0x1100, 0x11ff, 0x3130, 0x319f,
		     0xffa0, 0xffdf},
						/* Javanese */ {CHR('j', 'a', 'v', 'a'), 0},
						/* MS has a tag, but there is no unicode range */
/* Katakana */ {CHR('k', 'a', 'n', 'a'), 0x3040, 0x30ff, 0xff60, 0xff9f},
/* Kayah Li */ {CHR('k', 'a', 'l', 'i'), 0},
/* Kannada */ {CHR('k', 'n', 'd', 'a'), 0x0c80, 0x0cff},
/* Kharosthi */ {CHR('k', 'h', 'a', 'r'), 0x10a00, 0x10a5f},
/* Khmer */ {CHR('k', 'h', 'm', 'r'), 0x1780, 0x17ff},
/* Latin */ {CHR('l', 'a', 't', 'n'), 0x0041, 0x005a, 0x0061, 0x007a,
	     0x00c0, 0x02af, 0x1d00, 0x1eff, 0xfb00, 0xfb0f, 0xff00, 0xff5f,
	     0xa770, 0xa7ff},
/* Lao */ {CHR('l', 'a', 'o', ' '), 0x0e80, 0x0eff},
/* Lepcha */ {CHR('l', 'e', 'p', 'c'), 0},
/* Limbu */ {CHR('l', 'i', 'm', 'b'), 0x1900, 0x194f},
   /* Linear A */ /*{ CHR('l','i','n','a'), 0x10180, 0x102cf }, */ /* What happened to linear A? */
/* Linear B */ {CHR('l', 'i', 'n', 'b'), 0x10000, 0x100fa},
/* Lycian */ {CHR('l', 'y', 'c', 'i'), 0},
/* Lydian */ {CHR('l', 'y', 'd', 'i'), 0},
/* Malayalam */ {CHR('m', 'l', 'y', 'm'), 0x0d00, 0x0d7f},
/* Mathematical Alphanumeric Symbols */
   {CHR('m', 'a', 't', 'h'), 0x1d400, 0x1d7ff},
/* Mongolian */ {CHR('m', 'o', 'n', 'g'), 0x1800, 0x18af},
/* Musical */ {CHR('m', 'u', 's', 'c'), 0x1d100, 0x1d1ff},
/* Myanmar */ {CHR('m', 'y', 'm', 'r'), 0x1000, 0x107f},
/* New Tai Lue*/ {CHR('t', 'a', 'l', 'u'), 0},
/* N'Ko */ {CHR('n', 'k', 'o', ' '), 0x07c0, 0x07fa},
/* Ogham */ {CHR('o', 'g', 'a', 'm'), 0x1680, 0x169f},
/* Ol Chiki */ {CHR('o', 'l', 'c', 'k'), 0},
/* Old Italic */ {CHR('i', 't', 'a', 'l'), 0x10300, 0x1031e},
/* Old Permic */ {CHR('p', 'e', 'r', 'm'), 0x10350, 0x1037f},
/* Old Persian cuneiform */
   {CHR('x', 'p', 'e', 'o'), 0x103a0, 0x103df},
/* Oriya */ {CHR('o', 'r', 'y', 'a'), 0x0b00, 0x0b7f},
/* Osmanya */ {CHR('o', 's', 'm', 'a'), 0x10480, 0x104a9},
/* Phags-pa */ {CHR('p', 'h', 'a', 'g'), 0xa840, 0xa87f},
/* Phoenician */ {CHR('p', 'h', 'n', 'x'), 0x10900, 0x1091f},
/* Pollard */ {CHR('p', 'l', 'r', 'd'), 0x104b0, 0x104d9},
/* Rejang */ {CHR('r', 'j', 'n', 'g'), 0},
/* Rongorongo */ {CHR('r', 'o', 'r', 'o'), 0},
/* Runic */ {CHR('r', 'u', 'n', 'r'), 0x16a0, 0x16ff},
/* Saurashtra*/ {CHR('s', 'a', 'u', 'r'), 0},
/* Shavian */ {CHR('s', 'h', 'a', 'w'), 0x10450, 0x1047f},
/* Sinhala */ {CHR('s', 'i', 'n', 'h'), 0x0d80, 0x0dff},
/* Sumero-Akkadian Cuneiform */
   {CHR('x', 's', 'u', 'x'), 0x12000, 0x1236e},
/* Sundanese */ {CHR('s', 'u', 'n', 'd'), 0},
/* Syloti Nagri */
   {CHR('s', 'y', 'l', 'o'), 0xa800, 0xa82f},
/* Syriac */ {CHR('s', 'y', 'r', 'c'), 0x0700, 0x074f},
/* Tagalog */ {CHR('t', 'a', 'g', 'l'), 0x1700, 0x1714},
/* Tagbanwa */ {CHR('t', 'a', 'g', 'b'), 0x1760, 0x1773},
/* Tai Le */ {CHR('t', 'a', 'l', 'e'), 0x1950, 0x1974},
/* Tai Lu */ {CHR('t', 'a', 'l', 'u'), 0x1980, 0x19df},
/* Tamil */ {CHR('t', 'a', 'm', 'l'), 0x0b80, 0x0bff},
/* Telugu */ {CHR('t', 'e', 'l', 'u'), 0x0c00, 0x0c7f},
/* Tengwar */ {CHR('t', 'e', 'n', 'g'), 0x12000, 0x1207f},
/* Thaana */ {CHR('t', 'h', 'a', 'a'), 0x0780, 0x07bf},
/* Thai */ {CHR('t', 'h', 'a', 'i'), 0x0e00, 0x0e7f},
/* Tibetan */ {CHR('t', 'i', 'b', 't'), 0x0f00, 0x0fff},
/* Tifinagh */ {CHR('t', 'f', 'n', 'g'), 0x2d30, 0x2d7f},
/* Ugaritic */ {CHR('u', 'g', 'a', 'r'), 0x10380, 0x1039d},
/* Yi */ {CHR('y', 'i', ' ', ' '), 0xa000, 0xa4c6},
   {0}
};

void ScriptMainRange(uint32_t script, int *start, int *end) {
   int i;

   for (i=0; scripts[i][0] != 0; ++i) {
      if (scripts[i][0]==script) {
	 *start=scripts[i][1];
	 *end=scripts[i][2];
	 return;
      }
   }
   *start=*end=-1;
}

int ScriptIsRightToLeft(uint32_t script) {
   if (script==CHR('a', 'r', 'a', 'b') || script==CHR('h', 'e', 'b', 'r')
       || script==CHR('c', 'p', 'm', 'n')
       || script==CHR('k', 'h', 'a', 'r')
       || script==CHR('s', 'y', 'r', 'c')
       || script==CHR('t', 'h', 'a', 'a')
       || script==CHR('n', 'k', 'o', ' '))
      return (true);

   return (false);
}

uint32_t ScriptFromUnicode(int u, SplineFont *sf) {
   int s, k;

   if (u != -1) {
      for (s=0; scripts[s][0] != 0; ++s) {
	 for (k=1; scripts[s][k+1] != 0; k += 2)
	    if (u >= scripts[s][k] && u <= scripts[s][k+1])
	       break;
	 if (scripts[s][k+1] != 0)
	    break;
      }
      if (scripts[s][0] != 0) {
	 uint32_t script=scripts[s][0];

	 if (use_second_indic_scripts) {
	    /* MS has a parallel set of script tags for their new */
	    /*  Indic font shaper */
	    if (script==CHR('b', 'e', 'n', 'g'))
	       script=CHR('b', 'n', 'g', '2');
	    else if (script==CHR('d', 'e', 'v', 'a'))
	       script=CHR('d', 'e', 'v', '2');
	    else if (script==CHR('g', 'u', 'j', 'r'))
	       script=CHR('g', 'j', 'r', '2');
	    else if (script==CHR('g', 'u', 'r', 'u'))
	       script=CHR('g', 'u', 'r', '2');
	    else if (script==CHR('k', 'n', 'd', 'a'))
	       script=CHR('k', 'n', 'd', '2');
	    else if (script==CHR('m', 'l', 'y', 'm'))
	       script=CHR('m', 'l', 'm', '2');
	    else if (script==CHR('o', 'r', 'y', 'a'))
	       script=CHR('o', 'r', 'y', '2');
	    else if (script==CHR('t', 'a', 'm', 'l'))
	       script=CHR('t', 'm', 'l', '2');
	    else if (script==CHR('t', 'e', 'l', 'u'))
	       script=CHR('t', 'e', 'l', '2');
	 }
	 return (script);
      }
   } else if (sf != NULL) {
      if (sf->cidmaster != NULL || sf->subfontcnt != 0) {
	 if (sf->cidmaster != NULL)
	    sf=sf->cidmaster;
	 if (strmatch(sf->ordering, "Identity")==0)
	    return (DEFAULT_SCRIPT);
	 else if (strmatch(sf->ordering, "Korean")==0)
	    return (CHR('h', 'a', 'n', 'g'));
	 else
	    return (CHR('h', 'a', 'n', 'i'));
      }
   }

   return (DEFAULT_SCRIPT);
}

uint32_t SCScriptFromUnicode(SplineChar * sc) {
   char *pt;
   PST *pst;
   SplineFont *sf;
   int i;
   unsigned uni;
   FeatureScriptLangList *features;

   if (sc==NULL)
      return (DEFAULT_SCRIPT);

   sf=sc->parent;
   if (sc->unicodeenc != -1 &&
       !(sc->unicodeenc >= 0xe000 && sc->unicodeenc<0xf8ff) &&
       !(sc->unicodeenc >= 0xf0000 && sc->unicodeenc<0x10ffff))
      return (ScriptFromUnicode(sc->unicodeenc, sf));

   pt=sc->name;
   if (*pt)
      for (++pt; *pt != '\0' && *pt != '_' && *pt != '.'; ++pt);
   if (*pt != '\0') {
      char *str=copyn(sc->name, pt-sc->name);
      int uni=sf==NULL
	 || sf->fv==NULL?UniFromName(str, ui_none,
					 &custom):UniFromName(str,
								sf->
								uni_interp,
								sf->fv->map->
								enc);
      free(str);
      if (uni != -1)
	 return (ScriptFromUnicode(uni, sf));
   }
   /* Adobe ligature uniXXXXXXXX */
   if (strncmp(sc->name, "uni", 3)==0
       && sscanf(sc->name+3, "%4x", &uni)==1)
      return (ScriptFromUnicode(uni, sf));

   if (sf==NULL)
      return (DEFAULT_SCRIPT);

   if (sf->cidmaster)
      sf=sf->cidmaster;
   else if (sf->mm != NULL)
      sf=sf->mm->normal;
   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->type==pst_lcaret)
	continue;
      for (features=pst->subtable->lookup->features; features != NULL;
	   features=features->next) {
	 if (features->scripts != NULL)
	   return (features->scripts->script);
      }
   }
   return (ScriptFromUnicode(sc->unicodeenc, sf));
}

int SCRightToLeft(SplineChar * sc) {

   if (sc->unicodeenc >= 0x10800 && sc->unicodeenc <= 0x10fff)
      return (true);		/* Supplemental Multilingual Plane, RTL scripts */
   if (sc->unicodeenc != -1 && sc->unicodeenc<0x10000)
      return (isrighttoleft(sc->unicodeenc));

   return (ScriptIsRightToLeft(SCScriptFromUnicode(sc)));
}

static void GlyphMapFree(SplineChar *** map) {
   int i;

   if (map==NULL)
      return;
   for (i=0; map[i] != NULL; ++i)
      free(map[i]);
   free(map);
}

static SplineChar **FindSubs(SplineChar *sc,struct lookup_subtable *sub) {
   SplineChar *spc[30], **space=spc;
   int max=sizeof(spc)/sizeof(spc[0]);
   int cnt=0;
   char *pt, *start;
   SplineChar *subssc, **ret;
   PST *pst;

   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->subtable==sub) {
	 pt=pst->u.subs.variant;
	 while (1) {
	    while (*pt==' ')
	       ++pt;		/* Burn leading spaces. */
	    /* Start tokenizing the space-delimited list of references. */
	    start=pt;		/* Note the beginning of the current item. */
	    pt=strchr(start, ' ');	/* Find the end of the current item. */
	    if (pt != NULL)
	       *pt='\0';	/* Temporarily terminate the item. */
	    subssc=SFGetChar(sc->parent, -1, start);	/* Find the corresponding SplineChar. */
	    if (subssc != NULL && subssc->ttf_glyph != -1) {
	       /* Extend the list if necessary. */
	       if (cnt >= max) {
		  if (spc==space) {
		     space=malloc((max += 30)*sizeof(SplineChar *));
		     memcpy(space, spc, (max-30)*sizeof(SplineChar *));
		  } else
		     space =
			realloc(space, (max += 30)*sizeof(SplineChar *));
	       }
	       /* Write the SplineChar to the list. */
	       space[cnt++]=subssc;
	    }
	    if (pt==NULL)
	       break;		/* No more items. */
	    *pt=' ';		/* Repair the string from the tokenization process. */
	 }
      }
   }
   /* Returning NULL causes problems and seems to be unnecessary for now. */
   ret=malloc((cnt+1)*sizeof(SplineChar *));
   memcpy(ret, space, cnt * sizeof(SplineChar *));
   ret[cnt]=NULL;
   if (space != spc)
      free(space);		/* Free the temp space only if it is dynamically allocated. */
   return (ret);
}

static SplineChar ***generateMapList(SplineChar ** glyphs,
				     struct lookup_subtable *sub) {
   int cnt;
   SplineChar *sc;
   int i;
   SplineChar ***maps=NULL;

   for (cnt=0; glyphs[cnt] != NULL; ++cnt);
   maps=malloc((cnt+1)*sizeof(SplineChar **));
   for (i=0; i<cnt; ++i) {
      sc=glyphs[i];
      maps[i]=FindSubs(sc, sub);
      if (maps[i]==NULL) {
	 ErrorMsg(1,
		 "maps[%d] is null; glyphs[%d] is \"%s\"; lookup name is \"%s\".\n",
		 i, i, (glyphs[i]->name?glyphs[i]->name:""),
		 sub->subtable_name);
      }
   }
   maps[cnt]=NULL;
   return (maps);
}

void AnchorClassDecompose(SplineFont *sf, AnchorClass * _ac, int classcnt,
			  int *subcnts, SplineChar *** marks,
			  SplineChar *** base, SplineChar *** lig,
			  SplineChar *** mkmk, struct glyphinfo *gi) {
   /* Run through the font finding all characters with this anchor class */
   /*  (and the cnt-1 classes after it) */
   /*  and distributing in the four possible anchor types */
   int i, j, k, gid, gmax;
   struct sclist {
      int cnt;
      SplineChar **glyphs;
   } heads[at_max];
   AnchorPoint *test;
   AnchorClass *ac;

   memset(heads, 0, sizeof(heads));
   memset(subcnts, 0, classcnt * sizeof(int));
   memset(marks, 0, classcnt * sizeof(SplineChar **));
   gmax=gi==NULL?sf->glyphcnt:gi->gcnt;
   for (j=0; j<2; ++j) {
      for (i=0; i<gmax; ++i)
	 if ((gid=gi==NULL?i:gi->bygid[i]) != -1
	     && sf->glyphs[gid] != NULL) {
	    for (ac=_ac, k=0; k<classcnt; ac=ac->next)
	       if (ac->matches) {
		  for (test=sf->glyphs[gid]->anchor; test != NULL;
		       test=test->next) {
		     if (test->anchor==ac) {
			if (test->type==at_mark) {
			   if (j)
			      marks[k][subcnts[k]]=sf->glyphs[gid];
			   ++subcnts[k];
			   if (ac->type != act_mkmk)
			      break;
			} else if (test->type != at_centry
				   && test->type != at_cexit) {
			   if (heads[test->type].glyphs != NULL) {
			      /* If we have multiple mark classes, we may use the same base glyph */
			      /* with more than one mark class. But it should only appear once in */
			      /* the output */
			      if (heads[test->type].cnt==0 ||
				  heads[test->type].glyphs[heads[test->type].
							   cnt-1] !=
				  sf->glyphs[gid]) {
				 heads[test->type].glyphs[heads[test->type].
							  cnt] =
				    sf->glyphs[gid];
				 ++heads[test->type].cnt;
			      }
			   } else
			      ++heads[test->type].cnt;
			   if (ac->type != act_mkmk)
			      break;
			}
		     }
		  }
		  ++k;
	       }
	 }
      if (j==1)
	 break;
      for (i=0; i<4; ++i)
	 if (heads[i].cnt != 0) {
	    heads[i].glyphs =
	       malloc((heads[i].cnt+1)*sizeof(SplineChar *));
	    /* I used to set glyphs[cnt] to NULL here. But it turns out */
	    /*  cnt may be an overestimate on the first pass. So we can */
	    /*  only set it at the end of the second pass */
	    heads[i].cnt=0;
	 }
      for (k=0; k<classcnt; ++k)
	 if (subcnts[k] != 0) {
	    marks[k]=malloc((subcnts[k]+1)*sizeof(SplineChar *));
	    marks[k][subcnts[k]]=NULL;
	    subcnts[k]=0;
	 }
   }
   for (i=0; i<4; ++i)
      if (heads[i].glyphs != NULL)
	 heads[i].glyphs[heads[i].cnt]=NULL;

   *base=heads[at_basechar].glyphs;
   *lig=heads[at_baselig].glyphs;
   *mkmk=heads[at_basemark].glyphs;
}

SplineChar **EntryExitDecompose(SplineFont *sf, AnchorClass * ac,
				struct glyphinfo *gi) {
   /* Run through the font finding all characters with this anchor class */
   int i, j, cnt, gmax, gid;
   SplineChar **array;
   AnchorPoint *test;

   array=NULL;
   gmax=gi==NULL?sf->glyphcnt:gi->gcnt;
   for (j=0; j<2; ++j) {
      cnt=0;
      for (i=0; i<gmax; ++i)
	 if ((gid=gi==NULL?i:gi->bygid[i]) != -1
	     && sf->glyphs[gid] != NULL) {
	    for (test=sf->glyphs[gid]->anchor;
		 test != NULL && test->anchor != ac; test=test->next);
	    if (test != NULL
		&& (test->type==at_centry || test->type==at_cexit)) {
	       if (array != NULL)
		  array[cnt]=sf->glyphs[gid];
	       ++cnt;
	    }
	 }
      if (cnt==0)
	 return (NULL);
      if (j==1)
	 break;
      array=malloc((cnt+1)*sizeof(SplineChar *));
      array[cnt]=NULL;
   }
   return (array);
}

static void AnchorGuessContext(SplineFont *sf,struct alltabs *at) {
   int i;
   int maxbase=0, maxmark=0, basec, markc;
   AnchorPoint *ap;
   int hascursive=0;

   /* the order in which we examine the glyphs does not matter here, so */
   /*  we needn't add the complexity running though in gid order */
   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i]) {
	 basec=markc=0;
	 for (ap=sf->glyphs[i]->anchor; ap != NULL; ap=ap->next)
	    if (ap->type==at_basemark)
	       ++markc;
	    else if (ap->type==at_basechar || ap->type==at_baselig)
	       ++basec;
	    else if (ap->type==at_centry)
	       hascursive=true;
	 if (basec>maxbase)
	    maxbase=basec;
	 if (markc>maxmark)
	    maxmark=markc;
      }

   if (maxbase * (maxmark+1)>at->os2.maxContext)
      at->os2.maxContext=maxbase * (maxmark+1);
   if (hascursive && at->os2.maxContext<2)
      at->os2.maxContext=2;
}

static void dumpcoveragetable(AFILE *gpos,SplineChar ** glyphs) {
   int i, last=-2, range_cnt=0, start, r;

   /* the glyph list should already be sorted */
   /* figure out whether it is better (smaller) to use an array of glyph ids */
   /*  or a set of glyph id ranges */

   for (i=0; glyphs[i] != NULL; ++i) {
      if (glyphs[i]->ttf_glyph <= last)
	 ErrorMsg(2,"Glyphs must be ordered when creating coverage table\n");
      if (glyphs[i]->ttf_glyph != last+1)
	 ++range_cnt;
      last=glyphs[i]->ttf_glyph;
   }
   /* I think Windows will only accept format 2 coverage tables? */
   if (!(coverageformatsallowed&2)
       || ((coverageformatsallowed&1) && i <= 3*range_cnt)) {
      /* We use less space with a list of glyphs than with a set of ranges */
      aput_int16_be_checked(1,gpos);	/* Coverage format=1 => glyph list */
      aput_int16_be_checked(i,gpos);	/* count of glyphs */
      for (i=0; glyphs[i] != NULL; ++i)
	 aput_int16_be_checked(glyphs[i]->ttf_glyph,gpos);	/* array of glyph IDs */
   } else {
      aput_int16_be_checked(2,gpos);	/* Coverage format=2 => range list */
      aput_int16_be_checked(range_cnt,gpos);	/* count of ranges */
      last=-2;
      start=-2;		/* start is a index in our glyph array, last is ttf_glyph */
      r=0;
      for (i=0; glyphs[i] != NULL; ++i) {
	 if (glyphs[i]->ttf_glyph != last+1) {
	    if (last != -2) {
	       aput_int16_be_checked(glyphs[start]->ttf_glyph,gpos);	/* start glyph ID */
	       aput_int16_be_checked(last,gpos);	/* end glyph ID */
	       aput_int16_be_checked(start,gpos);	/* coverage index of start glyph */
	       ++r;
	    }
	    start=i;
	 }
	 last=glyphs[i]->ttf_glyph;
      }
      if (last != -2) {
	 aput_int16_be_checked(glyphs[start]->ttf_glyph,gpos);	/* start glyph ID */
	 aput_int16_be_checked(last,gpos);	/* end glyph ID */
	 aput_int16_be_checked(start,gpos);	/* coverage index of start glyph */
	 ++r;
      }
      if (r != range_cnt)
	 ErrorMsg(2,"Miscounted ranges in format 2 coverage table output\n");
   }
}

static int sc_ttf_order(const void *_sc1,const void *_sc2) {
   const SplineChar *sc1=*(const SplineChar **) _sc1, *sc2 =
      *(const SplineChar **) _sc2;
   return (sc1->ttf_glyph-sc2->ttf_glyph);
}

static SplineChar **SFOrderedGlyphsWithPSTinSubtable(SplineFont *sf,
						     struct lookup_subtable
						     *sub) {
   SplineChar **glyphs=SFGlyphsWithPSTinSubtable(sf, sub);
   int cnt, i, k;

   if (glyphs==NULL)
      return (NULL);
   for (cnt=0; glyphs[cnt] != NULL; ++cnt);
   qsort(glyphs, cnt, sizeof(SplineChar *), sc_ttf_order);
   if (glyphs[0]->ttf_glyph==-1) {
      /* Not sure if this can happen, but it's easy to fix */
      for (k=0; k<cnt && glyphs[k]->ttf_glyph==-1; ++k);
      for (i=0; i <= cnt-k; ++i)
	 glyphs[i]=glyphs[i+k];
   }
   return (glyphs);
}

SplineChar **SFGlyphsFromNames(SplineFont *sf, char *names) {
   int cnt, ch;
   char *pt, *end;
   SplineChar *sc, **glyphs;

   if (names==NULL)
      return (calloc(1, sizeof(SplineChar *)));

   cnt=0;
   for (pt=names; *pt; pt=end+1) {
      ++cnt;
      end=strchr(pt, ' ');
      if (end==NULL)
	 break;
   }

   glyphs=malloc((cnt+1)*sizeof(SplineChar *));
   cnt=0;
   for (pt=names; *pt; pt=end+1) {
      end=strchr(pt, ' ');
      if (end==NULL)
	 end=pt+strlen(pt);
      ch=*end;
      *end='\0';
      sc=SFGetChar(sf, -1, pt);
      if (sc != NULL && sc->ttf_glyph != -1)
	 glyphs[cnt++]=sc;
      *end=ch;
      if (ch=='\0')
	 break;
   }
   glyphs[cnt]=NULL;
   return (glyphs);
}

static SplineChar **OrderedGlyphsFromNames(SplineFont *sf,char *names) {
   SplineChar **glyphs=SFGlyphsFromNames(sf, names);
   int i, j;

   if (glyphs==NULL || glyphs[0]==NULL)
      return (glyphs);

   for (i=0; glyphs[i+1] != NULL; ++i)
      for (j=i+1; glyphs[j] != NULL; ++j) {
	 if (glyphs[i]->ttf_glyph>glyphs[j]->ttf_glyph) {
	    SplineChar *sc=glyphs[i];

	    glyphs[i]=glyphs[j];
	    glyphs[j]=sc;
	 }
      }
   if (glyphs[0] != NULL) {	/* Glyphs should not appear twice in the name list, just just in case they do... */
      for (i=0; glyphs[i+1] != NULL; ++i) {
	 if (glyphs[i]==glyphs[i+1]) {
	    for (j=i+1; glyphs[j] != NULL; ++j)
	       glyphs[j]=glyphs[j+1];
	 }
      }
   }
   return (glyphs);
}

static void gposvrmaskeddump(AFILE *gpos,int vf1,int mask,int offset) {
   if (vf1&1)
      aput_int16_be_checked(mask&1?offset:0,gpos);
   if (vf1&2)
      aput_int16_be_checked(mask&2?offset:0,gpos);
   if (vf1&4)
      aput_int16_be_checked(mask&4?offset:0,gpos);
   if (vf1&8)
      aput_int16_be_checked(mask&8?offset:0,gpos);
}

static int devtaboffsetsize(DeviceTable *dt) {
   int type=1, i;

   for (i=dt->last_pixel_size-dt->first_pixel_size; i >= 0; --i) {
      if (dt->corrections[i] >= 8 || dt->corrections[i]<-8)
	 return (3);
      else if (dt->corrections[i] >= 2 || dt->corrections[i]<-2)
	 type=2;
   }
   return (type);
}

static void dumpgposdevicetable(AFILE *gpos,DeviceTable *dt) {
   int type;
   int i, cnt, b;

   if (dt==NULL || dt->corrections==NULL)
      return;
   type=devtaboffsetsize(dt);
   aput_int16_be_checked(dt->first_pixel_size,gpos);
   aput_int16_be_checked(dt->last_pixel_size,gpos);
   aput_int16_be_checked(type,gpos);
   cnt=dt->last_pixel_size-dt->first_pixel_size+1;
   if (type==3) {
      for (i=0; i<cnt; ++i)
	 aputc(dt->corrections[i], gpos);
      if (cnt&1)
	 aputc(0, gpos);
   } else if (type==2) {
      for (i=0; i<cnt; i += 4) {
	 int val=0;

	 for (b=0; b<4 && i+b<cnt; ++b)
	    val |= (dt->corrections[i+b]&0x000f) << (12-b*4);
	 aput_int16_be_checked(val,gpos);
      }
   } else {
      for (i=0; i<cnt; i += 8) {
	 int val=0;

	 for (b=0; b<8 && i+b<cnt; ++b)
	    val |= (dt->corrections[i+b]&0x0003) << (14-b*2);
	 aput_int16_be_checked(val,gpos);
      }
   }
}

static int DevTabLen(DeviceTable *dt) {
   int type;
   int cnt;

   if (dt==NULL || dt->corrections==NULL)
      return (0);
   cnt=dt->last_pixel_size-dt->first_pixel_size+1;
   type=devtaboffsetsize(dt);
   if (type==3)
      cnt=(cnt+1)/2;
   else if (type==2)
      cnt=(cnt+3)/4;
   else
      cnt=(cnt+7)/8;
   cnt += 3;			/* first, last, type */
   return (sizeof(uint16_t)*cnt);
}

static int ValDevTabLen(ValDevTab *vdt) {

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

   return (DevTabLen(&vdt->xadjust)+DevTabLen(&vdt->yadjust) +
	   DevTabLen(&vdt->xadv)+DevTabLen(&vdt->yadv));
}

static int gposdumpvaldevtab(AFILE *gpos,ValDevTab *vdt,int bits,
			     int next_dev_tab) {

   if (bits&0x10) {
      if (vdt==NULL || vdt->xadjust.corrections==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(&vdt->xadjust);
      }
   }
   if (bits&0x20) {
      if (vdt==NULL || vdt->yadjust.corrections==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(&vdt->yadjust);
      }
   }
   if (bits&0x40) {
      if (vdt==NULL || vdt->xadv.corrections==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(&vdt->xadv);
      }
   }
   if (bits&0x80) {
      if (vdt==NULL || vdt->yadv.corrections==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(&vdt->yadv);
      }
   }
   return (next_dev_tab);
}

static int gposmaskeddumpdevtab(AFILE *gpos,DeviceTable *dt,int bits,
				int mask, int next_dev_tab) {

   if (bits&0x10) {
      if (!(mask&0x10) || dt==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(dt);
      }
   }
   if (bits&0x20) {
      if (!(mask&0x20) || dt==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(dt);
      }
   }
   if (bits&0x40) {
      if (!(mask&0x40) || dt==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(dt);
      }
   }
   if (bits&0x80) {
      if (!(mask&0x80) || dt==NULL)
	 aput_int16_be_checked(0,gpos);
      else {
	 aput_int16_be_checked(next_dev_tab,gpos);
	 next_dev_tab += DevTabLen(dt);
      }
   }
   return (next_dev_tab);
}

static int DevTabsSame(DeviceTable *dt1,DeviceTable *dt2) {
   DeviceTable _dt;
   int i;

   if (dt1==NULL && dt2==NULL)
      return (true);
   if (dt1==NULL) {
      memset(&_dt, 0, sizeof(_dt));
      dt1=&_dt;
   }
   if (dt2==NULL) {
      memset(&_dt, 0, sizeof(_dt));
      dt2=&_dt;
   }
   if (dt1->corrections==NULL && dt2->corrections==NULL)
      return (true);
   if (dt1->corrections==NULL || dt2->corrections==NULL)
      return (false);
   if (dt1->first_pixel_size != dt2->first_pixel_size ||
       dt1->last_pixel_size != dt2->last_pixel_size)
      return (false);
   for (i=dt2->last_pixel_size-dt1->first_pixel_size; i >= 0; --i)
      if (dt1->corrections[i] != dt2->corrections[i])
	 return (false);

   return (true);
}

static int ValDevTabsSame(ValDevTab *vdt1,ValDevTab *vdt2) {
   ValDevTab _vdt;

   if (vdt1==NULL && vdt2==NULL)
      return (true);
   if (vdt1==NULL) {
      memset(&_vdt, 0, sizeof(_vdt));
      vdt1=&_vdt;
   }
   if (vdt2==NULL) {
      memset(&_vdt, 0, sizeof(_vdt));
      vdt2=&_vdt;
   }
   return (DevTabsSame(&vdt1->xadjust, &vdt2->xadjust) &&
	   DevTabsSame(&vdt1->yadjust, &vdt2->yadjust) &&
	   DevTabsSame(&vdt1->xadv, &vdt2->xadv) &&
	   DevTabsSame(&vdt1->yadv, &vdt2->yadv));
}

static void dumpGPOSsimplepos(AFILE *gpos,SplineFont *sf,
			      struct lookup_subtable *sub) {
   int cnt, cnt2;
   int32_t coverage_pos, end;
   PST *pst, *first=NULL;
   int bits=0, same=true;
   SplineChar **glyphs;

   glyphs=SFOrderedGlyphsWithPSTinSubtable(sf, sub);
   for (cnt=cnt2=0; glyphs[cnt] != NULL; ++cnt) {
      for (pst=glyphs[cnt]->possub; pst != NULL; pst=pst->next) {
	 if (pst->subtable==sub && pst->type==pst_position) {
	    if (first==NULL)
	       first=pst;
	    else if (same) {
	       if (first->u.pos.xoff != pst->u.pos.xoff ||
		   first->u.pos.yoff != pst->u.pos.yoff ||
		   first->u.pos.h_adv_off != pst->u.pos.h_adv_off ||
		   first->u.pos.v_adv_off != pst->u.pos.v_adv_off)
		  same=false;
	       if (!ValDevTabsSame(pst->u.pos.adjust, first->u.pos.adjust))
		  same=false;
	    }
	    if (pst->u.pos.xoff != 0)
	       bits |= 1;
	    if (pst->u.pos.yoff != 0)
	       bits |= 2;
	    if (pst->u.pos.h_adv_off != 0)
	       bits |= 4;
	    if (pst->u.pos.v_adv_off != 0)
	       bits |= 8;
	    if (pst->u.pos.adjust != NULL) {
	       if (pst->u.pos.adjust->xadjust.corrections != NULL)
		  bits |= 0x10;
	       if (pst->u.pos.adjust->yadjust.corrections != NULL)
		  bits |= 0x20;
	       if (pst->u.pos.adjust->xadv.corrections != NULL)
		  bits |= 0x40;
	       if (pst->u.pos.adjust->yadv.corrections != NULL)
		  bits |= 0x80;
	    }
	    ++cnt2;
	    break;
	 }
      }
   }
   if (bits==0)
      bits=1;
   if (cnt != cnt2)
      ErrorMsg(2,"Count mismatch in dumpGPOSsimplepos#1 %d vs %d\n", cnt, cnt2);

   aput_int16_be_checked(same?1:2,gpos);	/* 1 means all value records same */
   coverage_pos=aftell(gpos);
   aput_int16_be_checked(0,gpos);		/* offset to coverage table */
   aput_int16_be_checked(bits,gpos);
   if (same) {
      if (bits&1)
	 aput_int16_be_checked(first->u.pos.xoff,gpos);
      if (bits&2)
	 aput_int16_be_checked(first->u.pos.yoff,gpos);
      if (bits&4)
	 aput_int16_be_checked(first->u.pos.h_adv_off,gpos);
      if (bits&8)
	 aput_int16_be_checked(first->u.pos.v_adv_off,gpos);
      if (bits&0xf0) {
	 int next_dev_tab=aftell(gpos)-coverage_pos+2 +
	    sizeof(int16_t)*((bits&0x10?1:0)+(bits&0x20?1:0) +
			     (bits&0x40?1:0)+(bits&0x80?1:0));
	 if (bits&0x10) {
	    aput_int16_be_checked(next_dev_tab,gpos);
	    next_dev_tab += DevTabLen(&first->u.pos.adjust->xadjust);
	 }
	 if (bits&0x20) {
	    aput_int16_be_checked(next_dev_tab,gpos);
	    next_dev_tab += DevTabLen(&first->u.pos.adjust->yadjust);
	 }
	 if (bits&0x40) {
	    aput_int16_be_checked(next_dev_tab,gpos);
	    next_dev_tab += DevTabLen(&first->u.pos.adjust->xadv);
	 }
	 if (bits&0x80) {
	    aput_int16_be_checked(next_dev_tab,gpos);
	    next_dev_tab += DevTabLen(&first->u.pos.adjust->yadv);
	 }
	 if (bits&0x10)
	    dumpgposdevicetable(gpos, &first->u.pos.adjust->xadjust);
	 if (bits&0x20)
	    dumpgposdevicetable(gpos, &first->u.pos.adjust->yadjust);
	 if (bits&0x40)
	    dumpgposdevicetable(gpos, &first->u.pos.adjust->xadv);
	 if (bits&0x80)
	    dumpgposdevicetable(gpos, &first->u.pos.adjust->yadv);
	 if (next_dev_tab != aftell(gpos)-coverage_pos+2)
	    ErrorMsg(2,"Device Table offsets wrong in simple positioning 2\n");
      }
   } else {
      int vr_size =
	 sizeof(int16_t)*((bits&0x1?1:0)+(bits&0x2?1:0) +
			  (bits&0x4?1:0)+(bits&0x8?1:0) +
			  (bits&0x10?1:0)+(bits&0x20?1:0) +
			  (bits&0x40?1:0)+(bits&0x80?1:0));
      int next_dev_tab=aftell(gpos)-coverage_pos+2+2+vr_size * cnt;

      aput_int16_be_checked(cnt,gpos);
      for (cnt2=0; glyphs[cnt2] != NULL; ++cnt2) {
	 for (pst=glyphs[cnt2]->possub; pst != NULL; pst=pst->next) {
	    if (pst->subtable==sub && pst->type==pst_position) {
	       if (bits&1)
		  aput_int16_be_checked(pst->u.pos.xoff,gpos);
	       if (bits&2)
		  aput_int16_be_checked(pst->u.pos.yoff,gpos);
	       if (bits&4)
		  aput_int16_be_checked(pst->u.pos.h_adv_off,gpos);
	       if (bits&8)
		  aput_int16_be_checked(pst->u.pos.v_adv_off,gpos);
	       next_dev_tab=gposdumpvaldevtab(gpos, pst->u.pos.adjust, bits,
						next_dev_tab);
	       break;
	    }
	 }
      }
      if (cnt != cnt2)
	 ErrorMsg(2,"Count mismatch in dumpGPOSsimplepos#3 %d vs %d\n", cnt,
		cnt2);
      if (bits&0xf0) {
	 for (cnt2=0; glyphs[cnt2] != NULL; ++cnt2) {
	    for (pst=glyphs[cnt2]->possub; pst != NULL; pst=pst->next) {
	       if (pst->subtable==sub && pst->type==pst_position) {
		  if (bits&0x10)
		     dumpgposdevicetable(gpos, &first->u.pos.adjust->xadjust);
		  if (bits&0x20)
		     dumpgposdevicetable(gpos, &first->u.pos.adjust->yadjust);
		  if (bits&0x40)
		     dumpgposdevicetable(gpos, &first->u.pos.adjust->xadv);
		  if (bits&0x80)
		     dumpgposdevicetable(gpos, &first->u.pos.adjust->yadv);
	       }
	    }
	 }
      }
      if (next_dev_tab != aftell(gpos)-coverage_pos+2)
	 ErrorMsg(2,"Device Table offsets wrong in simple positioning 2\n");
   }
   end=aftell(gpos);
   afseek(gpos, coverage_pos, SEEK_SET);
   aput_int16_be_checked(end-coverage_pos+2,gpos);
   afseek(gpos, end, SEEK_SET);
   dumpcoveragetable(gpos, glyphs);
   afseek(gpos, 0, SEEK_END);
   free(glyphs);
}

struct sckppst {
   uint16_t samewas;
   uint16_t devtablen;
   uint16_t tot;
   uint8_t isv;
   uint8_t subtable_too_big;
/* The first few fields are only meaningful in the first structure in the array*/
/*   and provide information about the entire rest of the array */
   uint16_t other_gid;
   SplineChar *sc;
   KernPair *kp;
   PST *pst;
};

static int cmp_gid(const void *_s1,const void *_s2) {
   const struct sckppst *s1=_s1, *s2=_s2;

   return (((int) s1->other_gid)-((int) s2->other_gid));
}

static void dumpGPOSpairpos(AFILE *gpos,SplineFont *sf,
			    struct lookup_subtable *sub) {
   int cnt;
   int32_t coverage_pos, offset_pos, end, start, pos;
   PST *pst;
   KernPair *kp;
   int vf1=0, vf2=0, i, j, k, tot, bit_cnt, v;
   int start_cnt, end_cnt;
   int chunk_cnt, chunk_max;
   SplineChar *sc, **glyphs, *gtemp;
   struct sckppst **seconds;
   int devtablen;
   int next_dev_tab;

   /* Figure out all the data we need. First the glyphs with kerning info */
   /*  then the glyphs to which they kern, and by how much */
   glyphs=SFOrderedGlyphsWithPSTinSubtable(sf, sub);
   for (cnt=0; glyphs[cnt] != NULL; ++cnt);
   seconds=malloc(cnt * sizeof(struct sckppst *));
   for (cnt=0; glyphs[cnt] != NULL; ++cnt) {
      for (k=0; k<2; ++k) {
	 devtablen=0;
	 tot=0;
	 for (pst=glyphs[cnt]->possub; pst != NULL; pst=pst->next) {
	    if (pst->subtable==sub && pst->type==pst_pair &&
		(sc=SFGetChar(sf, -1, pst->u.pair.paired)) != NULL &&
		sc->ttf_glyph != -1) {
	       if (k) {
		  seconds[cnt][tot].sc=sc;
		  seconds[cnt][tot].other_gid=sc->ttf_glyph;
		  seconds[cnt][tot].pst=pst;
		  devtablen += ValDevTabLen(pst->u.pair.vr[0].adjust) +
		     ValDevTabLen(pst->u.pair.vr[1].adjust);

	       }
	       ++tot;
	    }
	 }
	 for (v=0; v<2; ++v) {
	    for (kp=v?glyphs[cnt]->vkerns:glyphs[cnt]->kerns;
		 kp != NULL; kp=kp->next) {
	       if (kp->sc->ttf_glyph != -1) {
		  if (k) {
		     seconds[cnt][tot].other_gid=kp->sc->ttf_glyph;
		     seconds[cnt][tot].sc=kp->sc;
		     seconds[cnt][tot].kp=kp;
		     seconds[cnt][tot].isv=v;
		     devtablen += DevTabLen(kp->adjust);
		  }
		  ++tot;
	       }
	    }
	 }
	 if (k==0) {
	    seconds[cnt]=calloc(tot+1, sizeof(struct sckppst));
	 } else {
	    qsort(seconds[cnt], tot, sizeof(struct sckppst), cmp_gid);
	    seconds[cnt][0].tot=tot;
	    /* Devtablen is 0 unless we are configured for device tables */
	    seconds[cnt][0].devtablen=devtablen;
	    seconds[cnt][0].samewas=0xffff;
	 }
      }
   }

   /* Some fonts do a primitive form of class based kerning, several glyphs */
   /*  can share the same list of second glyphs&offsets */
   for (cnt=0; glyphs[cnt] != NULL; ++cnt) {
      struct sckppst *test=seconds[cnt], *test2;

      for (i=cnt-1; i >= 0; --i) {
	 test2=seconds[i];
	 if (test[0].tot != test2[0].tot || test2[0].samewas != 0xffff)
	    continue;
	 for (j=test[0].tot-1; j >= 0; --j) {
	    if (test[j].other_gid != test2[j].other_gid)
	       break;
	    if (test[j].kp != NULL && test2[j].kp != NULL &&
		test[j].kp->off==test2[j].kp->off
		&& DevTabsSame(test[j].kp->adjust, test2[j].kp->adjust)
	       )
	       /* So far, so good */ ;
	    else if (test[j].pst != NULL && test2[j].pst != NULL &&
		     test[j].pst->u.pair.vr[0].xoff ==
		     test2[j].pst->u.pair.vr[0].xoff
		     && test[j].pst->u.pair.vr[0].yoff ==
		     test2[j].pst->u.pair.vr[0].yoff
		     && test[j].pst->u.pair.vr[0].h_adv_off ==
		     test2[j].pst->u.pair.vr[0].h_adv_off
		     && test[j].pst->u.pair.vr[0].v_adv_off ==
		     test2[j].pst->u.pair.vr[0].v_adv_off
		     && test[j].pst->u.pair.vr[1].xoff ==
		     test2[j].pst->u.pair.vr[1].xoff
		     && test[j].pst->u.pair.vr[1].yoff ==
		     test2[j].pst->u.pair.vr[1].yoff
		     && test[j].pst->u.pair.vr[1].h_adv_off ==
		     test2[j].pst->u.pair.vr[1].h_adv_off
		     && test[j].pst->u.pair.vr[1].v_adv_off ==
		     test2[j].pst->u.pair.vr[1].v_adv_off
		     && ValDevTabsSame(test[j].pst->u.pair.vr[0].adjust,
				       test2[j].pst->u.pair.vr[0].adjust)
		     && ValDevTabsSame(test[j].pst->u.pair.vr[1].adjust,
				       test2[j].pst->u.pair.vr[1].adjust)
	       )
	       /* That's ok too. */ ;
	    else
	       break;
	 }
	 if (j >= 0)
	    continue;
	 test[0].samewas=i;
	 break;
      }
   }

   /* Ok, how many offsets must we output? Normal kerning will just use */
   /*  one offset (with perhaps a device table), but the standard allows */
   /*  us to adjust 8 different values (with 8 different device tables) */
   /* Find out which we need */
   for (cnt=0; glyphs[cnt] != NULL; ++cnt) {
      for (tot=0; tot<seconds[cnt][0].tot; ++tot) {
	 if ((pst=seconds[cnt][tot].pst) != NULL) {
	    if (pst->subtable==sub && pst->type==pst_pair) {
	       if (pst->u.pair.vr[0].xoff != 0)
		  vf1 |= 1;
	       if (pst->u.pair.vr[0].yoff != 0)
		  vf1 |= 2;
	       if (pst->u.pair.vr[0].h_adv_off != 0)
		  vf1 |= 4;
	       if (pst->u.pair.vr[0].v_adv_off != 0)
		  vf1 |= 8;
	       if (pst->u.pair.vr[0].adjust != NULL) {
		  if (pst->u.pair.vr[0].adjust->xadjust.corrections != NULL)
		     vf1 |= 0x10;
		  if (pst->u.pair.vr[0].adjust->yadjust.corrections != NULL)
		     vf1 |= 0x20;
		  if (pst->u.pair.vr[0].adjust->xadv.corrections != NULL)
		     vf1 |= 0x40;
		  if (pst->u.pair.vr[0].adjust->yadv.corrections != NULL)
		     vf1 |= 0x80;
	       }
	       if (pst->u.pair.vr[1].xoff != 0)
		  vf2 |= 1;
	       if (pst->u.pair.vr[1].yoff != 0)
		  vf2 |= 2;
	       if (pst->u.pair.vr[1].h_adv_off != 0)
		  vf2 |= 4;
	       if (pst->u.pair.vr[1].v_adv_off != 0)
		  vf2 |= 8;
	       if (pst->u.pair.vr[1].adjust != NULL) {
		  if (pst->u.pair.vr[1].adjust->xadjust.corrections != NULL)
		     vf2 |= 0x10;
		  if (pst->u.pair.vr[1].adjust->yadjust.corrections != NULL)
		     vf2 |= 0x20;
		  if (pst->u.pair.vr[1].adjust->xadv.corrections != NULL)
		     vf2 |= 0x40;
		  if (pst->u.pair.vr[1].adjust->yadv.corrections != NULL)
		     vf2 |= 0x80;
	       }
	    }
	 }
	 if ((kp=seconds[cnt][tot].kp) != NULL) {
	    int mask=0, mask2=0;

	    if (seconds[cnt][tot].isv)
	       mask=0x0008;
	    else
	       mask=0x0004;
	    if (kp->adjust != NULL) {
	       mask |= mask << 4;
	       mask2 |= mask2 << 4;
	    }
	    vf1 |= mask;
	    vf2 |= mask2;
	 }
      }
   }
   if (vf1==0 && vf2==0)
      vf1=1;
   bit_cnt=0;
   for (i=0; i<8; ++i) {
      if (vf1&(1 << i))
	 ++bit_cnt;
      if (vf2&(1 << i))
	 ++bit_cnt;
   }

   chunk_max=chunk_cnt=0;
   for (start_cnt=0; start_cnt<cnt; start_cnt=end_cnt) {
      int len=5*2;		/* Subtable header */

      for (end_cnt=start_cnt; end_cnt<cnt; ++end_cnt) {
	 int glyph_len=2;	/* For the glyph's offset */

	 if (seconds[end_cnt][0].samewas==0xffff
	     || seconds[end_cnt][0].samewas<start_cnt)
	    glyph_len += (bit_cnt*2+2)*seconds[end_cnt][0].tot+seconds[end_cnt][0].devtablen+2;	/* Number of secondary glyphs */
	 if (glyph_len>65535 && end_cnt==start_cnt) {
	    ErrorMsg(2,"Lookup subtable %s contains a glyph %s whose kerning information takes up more than 64k bytes\n",
		     sub->subtable_name, glyphs[start_cnt]->name);
	    len += glyph_len;
	 } else if (len+glyph_len>65535) {
	    if (start_cnt==0)
	       ErrorMsg(2,"Lookup subtable %s had to be split into several subtables\nbecause it was too big.\n",
			sub->subtable_name);
	    break;
	 } else
	    len += glyph_len;
      }
      if (start_cnt != 0 || end_cnt != cnt) {
	 if (chunk_cnt >= chunk_max)
	    sub->extra_subtables =
	       realloc(sub->extra_subtables,
		       ((chunk_max += 10)+1)*sizeof(uint32_t));
	 sub->extra_subtables[chunk_cnt++]=aftell(gpos);
	 sub->extra_subtables[chunk_cnt]=-1;
      }

      start=aftell(gpos);
      aput_int16_be_checked(1,gpos);	/* 1 means char pairs (ie. not classes) */
      coverage_pos=aftell(gpos);
      aput_int16_be_checked(0,gpos);	/* offset to coverage table */
      aput_int16_be_checked(vf1,gpos);
      aput_int16_be_checked(vf2,gpos);
      aput_int16_be_checked(end_cnt-start_cnt,gpos);
      offset_pos=aftell(gpos);
      for (i=start_cnt; i<end_cnt; ++i)
	 aput_int16_be_checked(0,gpos);	/* Fill in later */
      for (i=start_cnt; i<end_cnt; ++i) {
	 if (seconds[i][0].samewas >= start_cnt
	     && seconds[i][0].samewas != 0xffff) {
	    /* It's the same as the glyph at samewas, so just copy the */
	    /*  offset from there. We don't need to do anything else */
	    int offset;

	    afseek(gpos,
		  offset_pos+(seconds[i][0].samewas -
				start_cnt)*sizeof(uint16_t), SEEK_SET);
	    offset=aget_uint16_be(gpos);
	    afseek(gpos, offset_pos+(i-start_cnt)*sizeof(uint16_t),
		  SEEK_SET);
	    aput_int16_be_checked(offset,gpos);
	    afseek(gpos, 0, SEEK_END);
	    continue;
	 }
	 next_dev_tab=aftell(gpos)-start;
	 if ((vf1&0xf0) || (vf2&0xf0)) {
	    for (tot=0; tot<seconds[i][0].tot; ++tot) {
	       if ((pst=seconds[i][tot].pst) != NULL) {
		  if (pst->u.pair.vr[0].adjust != NULL) {
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[0].adjust->xadjust);
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[0].adjust->yadjust);
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[0].adjust->xadv);
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[0].adjust->yadv);
		  }
		  if (pst->u.pair.vr[1].adjust != NULL) {
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[1].adjust->xadjust);
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[1].adjust->yadjust);
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[1].adjust->xadv);
		     dumpgposdevicetable(gpos,
					 &pst->u.pair.vr[1].adjust->yadv);
		  }
	       }
	       if ((kp=seconds[i][tot].kp) != NULL && kp->adjust != NULL)
		  dumpgposdevicetable(gpos, kp->adjust);
	    }
	 }
	 pos=aftell(gpos);
	 afseek(gpos, offset_pos+(i-start_cnt)*sizeof(uint16_t), SEEK_SET);
	 aput_int16_be_checked(pos-start,gpos);
	 afseek(gpos, pos, SEEK_SET);

	 aput_int16_be_checked(seconds[i][0].tot,gpos);
	 for (tot=0; tot<seconds[i][0].tot; ++tot) {
	    aput_int16_be_checked(seconds[i][tot].other_gid,gpos);
	    if ((pst=seconds[i][tot].pst) != NULL) {
	       if (vf1&1)
		  aput_int16_be_checked(pst->u.pair.vr[0].xoff,gpos);
	       if (vf1&2)
		  aput_int16_be_checked(pst->u.pair.vr[0].yoff,gpos);
	       if (vf1&4)
		  aput_int16_be_checked(pst->u.pair.vr[0].h_adv_off,gpos);
	       if (vf1&8)
		  aput_int16_be_checked(pst->u.pair.vr[0].v_adv_off,gpos);
	       next_dev_tab =
		  gposdumpvaldevtab(gpos, pst->u.pair.vr[0].adjust, vf1,
				    next_dev_tab);
	       if (vf2&1)
		  aput_int16_be_checked(pst->u.pair.vr[1].xoff,gpos);
	       if (vf2&2)
		  aput_int16_be_checked(pst->u.pair.vr[1].yoff,gpos);
	       if (vf2&4)
		  aput_int16_be_checked(pst->u.pair.vr[1].h_adv_off,gpos);
	       if (vf2&8)
		  aput_int16_be_checked(pst->u.pair.vr[1].v_adv_off,gpos);
	       next_dev_tab =
		  gposdumpvaldevtab(gpos, pst->u.pair.vr[1].adjust, vf2,
				    next_dev_tab);
	    } else if ((kp=seconds[i][tot].kp) != NULL) {
	       int mask=0, mask2=0;

	       if (seconds[i][tot].isv)
		  mask=0x8;
	       else
		  mask=0x4;
	       gposvrmaskeddump(gpos, vf1, mask, kp->off);
	       next_dev_tab =
		  gposmaskeddumpdevtab(gpos, kp->adjust, vf1, mask << 4,
				       next_dev_tab);
	       gposvrmaskeddump(gpos, vf2, mask2, kp->off);
	       next_dev_tab =
		  gposmaskeddumpdevtab(gpos, kp->adjust, vf2, mask2 << 4,
				       next_dev_tab);
	    }
	 }
      }
      end=aftell(gpos);
      afseek(gpos, coverage_pos, SEEK_SET);
      if (end-start>65535)
	 ErrorMsg(2,"The size of subtable %s was miscalculated.  "
                    "This means the kerning output is wrong.\n",
                  sub->subtable_name);
      aput_int16_be_checked(end-start,gpos);
      afseek(gpos, end, SEEK_SET);
      gtemp=glyphs[end_cnt];
      glyphs[end_cnt]=NULL;
      dumpcoveragetable(gpos, glyphs+start_cnt);
      glyphs[end_cnt]=gtemp;
   }
   for (i=0; i<cnt; ++i)
      free(seconds[i]);
   free(seconds);
   free(glyphs);
}

uint16_t *ClassesFromNames(SplineFont *sf, char **classnames, int class_cnt,
			 int numGlyphs, SplineChar *** glyphs, int apple_kc) {
   uint16_t *class;
   int i;
   char *pt, *end, ch;
   SplineChar *sc, **gs=NULL;
   int offset=(apple_kc && classnames[0] != NULL);

   class=calloc(numGlyphs, sizeof(uint16_t));
   if (glyphs)
      *glyphs=gs=calloc(numGlyphs, sizeof(SplineChar *));
   for (i=0; i<class_cnt; ++i) {
      if (i==0 && classnames[0]==NULL)
	 continue;
      for (pt=classnames[i]; *pt; pt=end+1) {
	 while (*pt==' ')
	    ++pt;
	 if (*pt=='\0')
	    break;
	 end=strchr(pt, ' ');
	 if (end==NULL)
	    end=pt+strlen(pt);
	 ch=*end;
	 *end='\0';
	 sc=SFGetChar(sf, -1, pt);
	 if (sc != NULL && sc->ttf_glyph != -1) {
	    class[sc->ttf_glyph]=i+offset;
	    if (gs != NULL)
	       gs[sc->ttf_glyph]=sc;
	 }
	 *end=ch;
	 if (ch=='\0')
	    break;
      }
   }
   return (class);
}

static SplineChar **GlyphsFromClasses(SplineChar ** gs,int numGlyphs) {
   int i, cnt;
   SplineChar **glyphs;

   for (i=cnt=0; i<numGlyphs; ++i)
      if (gs[i] != NULL)
	 ++cnt;
   glyphs=malloc((cnt+1)*sizeof(SplineChar *));
   for (i=cnt=0; i<numGlyphs; ++i)
      if (gs[i] != NULL)
	 glyphs[cnt++]=gs[i];
   glyphs[cnt++]=NULL;
   free(gs);
   return (glyphs);
}

static SplineChar **GlyphsFromInitialClasses(SplineChar ** gs,int numGlyphs,
					     uint16_t * classes,
					     uint16_t * initial) {
   int i, j, cnt;
   SplineChar **glyphs;

   for (i=cnt=0; i<numGlyphs; ++i) {
      for (j=0; initial[j] != 0xffff; ++j)
	 if (initial[j]==classes[i])
	    break;
      if (initial[j] != 0xffff && gs[i] != NULL)
	 ++cnt;
   }
   glyphs=malloc((cnt+1)*sizeof(SplineChar *));
   for (i=cnt=0; i<numGlyphs; ++i) {
      for (j=0; initial[j] != 0xffff; ++j)
	 if (initial[j]==classes[i])
	    break;
      if (initial[j] != 0xffff && gs[i] != NULL)
	 glyphs[cnt++]=gs[i];
   }
   glyphs[cnt++]=NULL;
   return (glyphs);
}

static void DumpClass(AFILE *gpos,uint16_t *class,int numGlyphs) {
   int ranges, i, cur, first=-1, last=-1, istart;

   for (i=ranges=0; i<numGlyphs;) {
      istart=i;
      cur=class[i];
      while (i<numGlyphs && class[i]==cur)
	 ++i;
      if (cur != 0) {
	 ++ranges;
	 if (first==-1)
	    first=istart;
	 last=i-1;
      }
   }
   if (ranges*3+1>last-first+1+2 || first==-1) {
      if (first==-1)
	 first=last=0;
      aput_int16_be_checked(1,gpos);	/* Format 1, list of all posibilities */
      aput_int16_be_checked(first,gpos);
      aput_int16_be_checked(last-first+1,gpos);
      for (i=first; i <= last; ++i)
	 aput_int16_be_checked(class[i],gpos);
   } else {
      aput_int16_be_checked(2,gpos);	/* Format 2, series of ranges */
      aput_int16_be_checked(ranges,gpos);
      for (i=0; i<numGlyphs;) {
	 istart=i;
	 cur=class[i];
	 while (i<numGlyphs && class[i]==cur)
	    ++i;
	 if (cur != 0) {
	    aput_int16_be_checked(istart,gpos);
	    aput_int16_be_checked(i-1,gpos);
	    aput_int16_be_checked(cur,gpos);
	 }
      }
   }
}

static void dumpgposkernclass(AFILE *gpos,SplineFont *sf,
			      struct lookup_subtable *sub,
			      struct alltabs *at) {
   uint32_t begin_off=aftell(gpos), pos;
   uint16_t *class1, *class2;
   KernClass *kc=sub->kc, *test;
   SplineChar **glyphs;
   int i, isv;
   int anydevtab=false;
   int next_devtab;

   aput_int16_be_checked(2,gpos);		/* format 2 of the pair adjustment subtable */
   aput_int16_be_checked(0,gpos);		/* offset to coverage table */
   for (i=0; i<kc->first_cnt * kc->second_cnt; ++i) {
      if (kc->adjusts[i].corrections != NULL) {
	 anydevtab=true;
	 break;
      }
   }

   for (test=sf->vkerns; test != NULL && test != kc; test=test->next);
   isv=test==kc;

   if (isv) {
      /* As far as I know there is no "bottom to top" writing direction */
      /*  Oh. There is. Ogham, Runic */
      aput_int16_be_checked(anydevtab?0x0088:0x0008,gpos);	/* Alter YAdvance of first character */
      aput_int16_be_checked(0x0000,gpos);	/* leave second char alone */
   } else {
      aput_int16_be_checked(anydevtab?0x0044:0x0004,gpos);	/* Alter XAdvance of first character */
      aput_int16_be_checked(0x0000,gpos);	/* leave second char alone */
   }
   class1 =
      ClassesFromNames(sf, kc->firsts, kc->first_cnt, at->maxp.numGlyphs,
		       &glyphs, false);
   glyphs=GlyphsFromClasses(glyphs, at->maxp.numGlyphs);
   class2 =
      ClassesFromNames(sf, kc->seconds, kc->second_cnt, at->maxp.numGlyphs,
		       NULL, false);
   aput_int16_be_checked(0,gpos);		/* offset to first glyph classes */
   aput_int16_be_checked(0,gpos);		/* offset to second glyph classes */
   aput_int16_be_checked(kc->first_cnt,gpos);
   aput_int16_be_checked(kc->second_cnt,gpos);
   next_devtab =
      aftell(gpos)-begin_off +
      kc->first_cnt * kc->second_cnt*2*sizeof(uint16_t);
   for (i=0; i<kc->first_cnt * kc->second_cnt; ++i) {
      aput_int16_be_checked(kc->offsets[i],gpos);
      if (anydevtab && kc->adjusts[i].corrections != NULL) {
	 aput_int16_be_checked(next_devtab,gpos);
	 next_devtab += DevTabLen(&kc->adjusts[i]);
      } else if (anydevtab)
	 aput_int16_be_checked(0,gpos);
   }
   if (anydevtab) {
      for (i=0; i<kc->first_cnt * kc->second_cnt; ++i) {
	 if (kc->adjusts[i].corrections != NULL)
	    dumpgposdevicetable(gpos, &kc->adjusts[i]);
      }
      if (next_devtab != aftell(gpos)-begin_off)
	 ErrorMsg(2,"Device table offsets screwed up in kerning class\n");
   }
   pos=aftell(gpos);
   afseek(gpos, begin_off+4*sizeof(uint16_t), SEEK_SET);
   aput_int16_be_checked(pos-begin_off,gpos);
   afseek(gpos, pos, SEEK_SET);
   DumpClass(gpos, class1, at->maxp.numGlyphs);

   pos=aftell(gpos);
   afseek(gpos, begin_off+5*sizeof(uint16_t), SEEK_SET);
   aput_int16_be_checked(pos-begin_off,gpos);
   afseek(gpos, pos, SEEK_SET);
   DumpClass(gpos, class2, at->maxp.numGlyphs);

   pos=aftell(gpos);
   afseek(gpos, begin_off+sizeof(uint16_t), SEEK_SET);
   aput_int16_be_checked(pos-begin_off,gpos);
   afseek(gpos, pos, SEEK_SET);
   dumpcoveragetable(gpos, glyphs);

   free(glyphs);
   free(class1);
   free(class2);
}

static void dumpanchor(AFILE *gpos,AnchorPoint *ap,int is_ttf) {
   int base=aftell(gpos);

   if (ap->xadjust.corrections != NULL || ap->yadjust.corrections != NULL)
      aput_int16_be_checked(3,gpos);	/* format 3 w/ device tables */
   else if (ap->has_ttf_pt && is_ttf)
      aput_int16_be_checked(2,gpos);	/* format 2 w/ a matching ttf point index */
   else
      aput_int16_be_checked(1,gpos);	/* Anchor format 1 just location */
   aput_int16_be_checked(ap->me.x,gpos);	/* X coord of attachment */
   aput_int16_be_checked(ap->me.y,gpos);	/* Y coord of attachment */
   if (ap->xadjust.corrections != NULL || ap->yadjust.corrections != NULL) {
      aput_int16_be_checked(ap->xadjust.corrections==NULL?0 :
	       aftell(gpos)-base+4,gpos);
      aput_int16_be_checked(ap->yadjust.corrections==NULL?0 :
	       aftell(gpos)-base+2+DevTabLen(&ap->xadjust),gpos);
      dumpgposdevicetable(gpos, &ap->xadjust);
      dumpgposdevicetable(gpos, &ap->yadjust);
   } else if (ap->has_ttf_pt && is_ttf)
      aput_int16_be_checked(ap->ttf_pt_index,gpos);
}

static void dumpgposCursiveAttach(AFILE *gpos,SplineFont *sf,
				  struct lookup_subtable *sub,
				  struct glyphinfo *gi) {
   AnchorClass *ac, *testac;
   SplineChar **entryexit;
   int cnt, offset, j;
   AnchorPoint *ap, *entry, *exit;
   uint32_t coverage_offset, start;

   ac=NULL;
   for (testac=sf->anchor; testac != NULL; testac=testac->next) {
      if (testac->subtable==sub) {
	 if (ac==NULL)
	    ac=testac;
	 else {
	    ErrorMsg(2,"Two cursive anchor classes in the same subtable, %s\n",
                     sub->subtable_name);
	    break;
	 }
      }
   }
   if (ac==NULL) {
      ErrorMsg(2,"Missing anchor class for %s\n", sub->subtable_name);
      return;
   }
   entryexit=EntryExitDecompose(sf, ac, gi);
   if (entryexit==NULL)
      return;

   for (cnt=0; entryexit[cnt] != NULL; ++cnt);

   start=aftell(gpos);
   aput_int16_be_checked(1,gpos);		/* format 1 for this subtable */
   aput_int16_be_checked(0,gpos);		/* Fill in later, offset to coverage table */
   aput_int16_be_checked(cnt,gpos);		/* number of glyphs */

   offset=6+2*2*cnt;
   for (j=0; j<cnt; ++j) {
      entry=exit=NULL;
      for (ap=entryexit[j]->anchor; ap != NULL; ap=ap->next) {
	 if (ap->anchor==ac && ap->type==at_centry)
	    entry=ap;
	 if (ap->anchor==ac && ap->type==at_cexit)
	    exit=ap;
      }
      if (entry != NULL) {
	 aput_int16_be_checked(offset,gpos);
	 offset += 6;
	 if (entry->xadjust.corrections != NULL
	     || entry->yadjust.corrections != NULL)
	    offset +=
	       4+DevTabLen(&entry->xadjust)+DevTabLen(&entry->yadjust);
	 if (gi->is_ttf && entry->has_ttf_pt)
	    offset += 2;
      } else
	 aput_int16_be_checked(0,gpos);
      if (exit != NULL) {
	 aput_int16_be_checked(offset,gpos);
	 offset += 6;
	 if (exit->xadjust.corrections != NULL
	     || exit->yadjust.corrections != NULL)
	    offset +=
	       4+DevTabLen(&exit->xadjust)+DevTabLen(&exit->yadjust);
	 else if (gi->is_ttf && exit->has_ttf_pt)
	    offset += 2;
      } else
	 aput_int16_be_checked(0,gpos);
   }
   for (j=0; j<cnt; ++j) {
      entry=exit=NULL;
      for (ap=entryexit[j]->anchor; ap != NULL; ap=ap->next) {
	 if (ap->anchor==ac && ap->type==at_centry)
	    entry=ap;
	 if (ap->anchor==ac && ap->type==at_cexit)
	    exit=ap;
      }
      if (entry != NULL)
	 dumpanchor(gpos, entry, gi->is_ttf);
      if (exit != NULL)
	 dumpanchor(gpos, exit, gi->is_ttf);
   }
   coverage_offset=aftell(gpos);
   dumpcoveragetable(gpos, entryexit);
   afseek(gpos, start+2, SEEK_SET);
   aput_int16_be_checked(coverage_offset-start,gpos);
   afseek(gpos, 0, SEEK_END);

   free(entryexit);
}

static int orderglyph(const void *_sc1,const void *_sc2) {
   SplineChar *const *sc1=_sc1, *const *sc2=_sc2;

   return ((*sc1)->ttf_glyph-(*sc2)->ttf_glyph);
}

static SplineChar **allmarkglyphs(SplineChar *** glyphlist,int classcnt) {
   SplineChar **glyphs;
   int i, tot, k;

   if (classcnt==1)
      return (glyphlist[0]);

   for (i=tot=0; i<classcnt; ++i) {
      for (k=0; glyphlist[i][k] != NULL; ++k);
      tot += k;
   }
   glyphs=malloc((tot+1)*sizeof(SplineChar *));
   for (i=tot=0; i<classcnt; ++i) {
      for (k=0; glyphlist[i][k] != NULL; ++k)
	 glyphs[tot++]=glyphlist[i][k];
   }
   qsort(glyphs, tot, sizeof(SplineChar *), orderglyph);
   for (i=k=0; i<tot; ++i) {
      while (i+1<tot && glyphs[i]==glyphs[i+1])
	 ++i;
      glyphs[k++]=glyphs[i];
   }
   glyphs[k]=NULL;
   return (glyphs);
}

static void dumpgposAnchorData(AFILE *gpos,AnchorClass *_ac,
			       enum anchor_type at,
			       SplineChar *** marks, SplineChar ** base,
			       int classcnt, struct glyphinfo *gi) {
   AnchorClass *ac=NULL;
   int j, cnt, k, l, pos, offset, tot, max;
   uint32_t coverage_offset, markarray_offset, subtable_start;
   AnchorPoint *ap, **aps;
   SplineChar **markglyphs;

   for (cnt=0; base[cnt] != NULL; ++cnt);

   subtable_start=aftell(gpos);
   aput_int16_be_checked(1,gpos);		/* format 1 for this subtable */
   aput_int16_be_checked(0,gpos);		/* Fill in later, offset to mark coverage table */
   aput_int16_be_checked(0,gpos);		/* Fill in later, offset to base coverage table */
   aput_int16_be_checked(classcnt,gpos);
   aput_int16_be_checked(0,gpos);		/* Fill in later, offset to mark array */
   aput_int16_be_checked(12,gpos);		/* Offset to base array */
   /* Base array */
   aput_int16_be_checked(cnt,gpos);		/* Number of entries in array */
   if (at==at_basechar || at==at_basemark) {
      offset=2;
      for (l=0; l<3; ++l) {
	 for (j=0; j<cnt; ++j) {
	    for (k=0, ac=_ac; k<classcnt; ac=ac->next)
	       if (ac->matches) {
		  if (!ac->has_mark || !ac->has_base)
		     continue;
		  for (ap=base[j]->anchor;
		       ap != NULL && (ap->anchor != ac || ap->type != at);
		       ap=ap->next);
		  switch (l) {
		    case 0:
		       offset += 2;
		       break;
		    case 1:
		       if (ap==NULL)
			  aput_int16_be_checked(0,gpos);
		       else {
			  aput_int16_be_checked(offset,gpos);
			  offset += 6;
			  if (ap->xadjust.corrections != NULL
			      || ap->yadjust.corrections != NULL)
			     offset +=
				4+DevTabLen(&ap->xadjust) +
				DevTabLen(&ap->yadjust);
			  else if (gi->is_ttf && ap->has_ttf_pt)
			     offset += 2;
		       }
		       break;
		    case 2:
		       if (ap != NULL)
			  dumpanchor(gpos, ap, gi->is_ttf);
		       break;
		  }
		  ++k;
	       }
	 }
      }
   } else {
      offset=2+2*cnt;
      max=0;
      for (j=0; j<cnt; ++j) {
	 aput_int16_be_checked(offset,gpos);
	 pos=tot=0;
	 for (ap=base[j]->anchor; ap != NULL; ap=ap->next)
	    for (k=0, ac=_ac; k<classcnt; ac=ac->next)
	       if (ac->matches) {
		  if (ap->anchor==ac) {
		     if (ap->lig_index>pos)
			pos=ap->lig_index;
		     ++tot;
		  }
		  ++k;
	       }
	 if (pos>max)
	    max=pos;
	 offset += 2+(pos+1)*classcnt*2+tot*6;
	 /* 2 for component count, for each component an offset to an offset to an anchor record */
      }
      ++max;
      aps=malloc((classcnt * max)*sizeof(AnchorPoint *));
      for (j=0; j<cnt; ++j) {
	 memset(aps, 0, (classcnt * max)*sizeof(AnchorPoint *));
	 pos=0;
	 for (ap=base[j]->anchor; ap != NULL; ap=ap->next)
	    for (k=0, ac=_ac; k<classcnt; ac=ac->next)
	       if (ac->matches) {
		  if (ap->anchor==ac) {
		     if (ap->lig_index>pos)
			pos=ap->lig_index;
		     aps[k * max+ap->lig_index]=ap;
		  }
		  ++k;
	       }
	 ++pos;
	 aput_int16_be_checked(pos,gpos);
	 offset=2+2*pos * classcnt;
	 for (l=0; l<pos; ++l) {
	    for (k=0; k<classcnt; ++k) {
	       if (aps[k * max+l]==NULL)
		  aput_int16_be_checked(0,gpos);
	       else {
		  aput_int16_be_checked(offset,gpos);
		  offset += 6;
		  if (aps[k * max+l]->xadjust.corrections != NULL
		      || aps[k * max+l]->yadjust.corrections != NULL)
		     offset +=
			4+DevTabLen(&aps[k * max+l]->xadjust) +
			DevTabLen(&aps[k * max+l]->yadjust);
		  else if (gi->is_ttf && aps[k * max+l]->has_ttf_pt)
		     offset += 2;
	       }
	    }
	 }
	 for (l=0; l<pos; ++l) {
	    for (k=0; k<classcnt; ++k) {
	       if (aps[k * max+l] != NULL) {
		  dumpanchor(gpos, aps[k * max+l], gi->is_ttf);
	       }
	    }
	 }
      }
      free(aps);
   }
   coverage_offset=aftell(gpos);
   afseek(gpos, subtable_start+4, SEEK_SET);
   aput_int16_be_checked(coverage_offset-subtable_start,gpos);
   afseek(gpos, 0, SEEK_END);
   dumpcoveragetable(gpos, base);

   /* We tried sharing the mark table, (among all these sub-tables) but */
   /*  that doesn't work because we need to be able to reorder the sub-tables */
   markglyphs=allmarkglyphs(marks, classcnt);
   coverage_offset=aftell(gpos);
   dumpcoveragetable(gpos, markglyphs);
   markarray_offset=aftell(gpos);
   for (cnt=0; markglyphs[cnt] != NULL; ++cnt);
   aput_int16_be_checked(cnt,gpos);
   offset=2+4*cnt;
   for (j=0; j<cnt; ++j) {
      if (classcnt==0) {
	 aput_int16_be_checked(0,gpos);	/* Only one class */
	 ap=NULL;
      } else {
	 for (k=0, ac=_ac; k<classcnt; ac=ac->next) {
	    if (ac->matches) {
	       for (ap=markglyphs[j]->anchor;
		    ap != NULL && (ap->anchor != ac || ap->type != at_mark);
		    ap=ap->next);
	       if (ap != NULL)
		  break;
	       ++k;
	    }
	 }
	 aput_int16_be_checked(k,gpos);
      }
      aput_int16_be_checked(offset,gpos);
      offset += 6;
      if (ap != NULL
	  && (ap->xadjust.corrections != NULL
	      || ap->yadjust.corrections != NULL))
	 offset += 4+DevTabLen(&ap->xadjust)+DevTabLen(&ap->yadjust);
      else if (gi->is_ttf && ap->has_ttf_pt)
	 offset += 2;
   }
   for (j=0; j<cnt; ++j) {
      for (k=0, ac=_ac; k<classcnt; ac=ac->next) {
	 if (ac->matches) {
	    for (ap=markglyphs[j]->anchor;
		 ap != NULL && (ap->anchor != ac || ap->type != at_mark);
		 ap=ap->next);
	    if (ap != NULL)
	       break;
	    ++k;
	 }
      }
      dumpanchor(gpos, ap, gi->is_ttf);
   }
   if (markglyphs != marks[0])
      free(markglyphs);

   afseek(gpos, subtable_start+2, SEEK_SET);	/* mark coverage table offset */
   aput_int16_be_checked(coverage_offset-subtable_start,gpos);
   afseek(gpos, 4, SEEK_CUR);
   aput_int16_be_checked(markarray_offset-subtable_start,gpos);

   afseek(gpos, 0, SEEK_END);
}

static void dumpGSUBsimplesubs(AFILE *gsub,SplineFont *sf,
			       struct lookup_subtable *sub) {
   int cnt;
   int32_t coverage_pos,end;
   SplineChar **glyphs,***maps;

   glyphs=SFOrderedGlyphsWithPSTinSubtable(sf,sub);
   maps=generateMapList(glyphs,sub);

   for (cnt=0;glyphs[cnt]!=NULL;++cnt); /* count non-null glyphs */

   /* delta format may be a problem for vertical writing in XP,
    * so we use list format instead */
   aput_int16_be_checked(2,gsub);	/* glyph list format */
   coverage_pos=aftell(gsub);
   aput_int16_be_checked(0,gsub);	/* offset to coverage table */
   aput_int16_be_checked(cnt,gsub);
   for (cnt=0;glyphs[cnt]!=NULL;++cnt)
     aput_int16_be_checked((*maps[cnt])->ttf_glyph,gsub);

   end=aftell(gsub);
   afseek(gsub,coverage_pos,SEEK_SET);
   aput_int16_be_checked(end-coverage_pos+2,gsub);
   afseek(gsub,end,SEEK_SET);
   dumpcoveragetable(gsub,glyphs);

   free(glyphs);
   GlyphMapFree(maps);
}

static void dumpGSUBmultiplesubs(AFILE *gsub,SplineFont *sf,
				 struct lookup_subtable *sub) {
   int cnt, offset;
   int32_t coverage_pos, end;
   int gc;
   SplineChar **glyphs, ***maps;

   glyphs=SFOrderedGlyphsWithPSTinSubtable(sf, sub);
   maps=generateMapList(glyphs, sub);
   for (cnt=0; glyphs[cnt] != NULL; ++cnt);

   aput_int16_be_checked(1,gsub);		/* glyph list format */
   coverage_pos=aftell(gsub);
   aput_int16_be_checked(0,gsub);		/* offset to coverage table */
   aput_int16_be_checked(cnt,gsub);
   offset=6+2*cnt;
   for (cnt=0; glyphs[cnt] != NULL; ++cnt) {
      aput_int16_be_checked(offset,gsub);
      if (maps[cnt]==NULL) {
	 ErrorMsg(1,
		 "maps[%d] is null; glyphs[%d] is \"%s\"; lookup name is \"%s\".\n",
		 cnt, cnt, (glyphs[cnt]->name?glyphs[cnt]->name:""),
		 sub->subtable_name);
      }
      for (gc=0; maps[cnt][gc] != NULL; ++gc);
      offset += 2+2*gc;
   }
   for (cnt=0; glyphs[cnt] != NULL; ++cnt) {
      for (gc=0; maps[cnt][gc] != NULL; ++gc);
      aput_int16_be_checked(gc,gsub);
      for (gc=0; maps[cnt][gc] != NULL; ++gc)
	 aput_int16_be_checked(maps[cnt][gc]->ttf_glyph,gsub);
   }
   end=aftell(gsub);
   afseek(gsub, coverage_pos, SEEK_SET);
   aput_int16_be_checked(end-coverage_pos+2,gsub);
   afseek(gsub, end, SEEK_SET);
   dumpcoveragetable(gsub, glyphs);

   free(glyphs);
   GlyphMapFree(maps);
}

static int AllToBeOutput(LigList *lig) {
   struct splinecharlist *cmp;

   if (lig->lig->u.lig.lig->ttf_glyph==-1 || lig->first->ttf_glyph==-1)
      return (0);
   for (cmp=lig->components; cmp != NULL; cmp=cmp->next)
      if (cmp->sc->ttf_glyph==-1)
	 return (0);
   return (true);
}

static void dumpGSUBligdata(AFILE *gsub,SplineFont *sf,
			    struct lookup_subtable *sub, struct alltabs *at) {
   int32_t coverage_pos, next_val_pos, here, lig_list_start;
   int cnt, i, pcnt, lcnt, max=100, j;
   uint16_t *offsets=NULL, *ligoffsets=malloc(max * sizeof(uint16_t));
   SplineChar **glyphs;
   LigList *ll;
   struct splinecharlist *scl;

   glyphs=SFOrderedGlyphsWithPSTinSubtable(sf, sub);
   cnt=0;
   if (glyphs != NULL)
      for (; glyphs[cnt] != NULL; ++cnt);

   aput_int16_be_checked(1,gsub);		/* only one format for ligatures */
   coverage_pos=aftell(gsub);
   aput_int16_be_checked(0,gsub);		/* offset to coverage table */
   aput_int16_be_checked(cnt,gsub);
   next_val_pos=aftell(gsub);
   if (glyphs != NULL)
      offsets=malloc(cnt * sizeof(int16_t));
   for (i=0; i<cnt; ++i)
      aput_int16_be_checked(0,gsub);
   for (i=0; i<cnt; ++i) {
      offsets[i]=aftell(gsub)-coverage_pos+2;
      for (pcnt=0, ll=glyphs[i]->ligofme; ll != NULL; ll=ll->next)
	 if (ll->lig->subtable==sub && AllToBeOutput(ll))
	    ++pcnt;
      aput_int16_be_checked(pcnt,gsub);
      if (pcnt >= max) {
	 max=pcnt+100;
	 ligoffsets=realloc(ligoffsets, max * sizeof(int));
      }
      lig_list_start=aftell(gsub);
      for (j=0; j<pcnt; ++j)
	 aput_int16_be_checked(0,gsub);	/* Place holders */
      for (pcnt=0, ll=glyphs[i]->ligofme; ll != NULL; ll=ll->next) {
	 if (ll->lig->subtable==sub && AllToBeOutput(ll)) {
	    ligoffsets[pcnt]=aftell(gsub)-lig_list_start+2;
	    aput_int16_be_checked(ll->lig->u.lig.lig->ttf_glyph,gsub);
	    for (lcnt=0, scl=ll->components; scl != NULL; scl=scl->next)
	       ++lcnt;
	    aput_int16_be_checked(lcnt+1,gsub);
	    if (lcnt+1>at->os2.maxContext)
	       at->os2.maxContext=lcnt+1;
	    for (scl=ll->components; scl != NULL; scl=scl->next)
	       aput_int16_be_checked(scl->sc->ttf_glyph,gsub);
	    ++pcnt;
	 }
      }
      afseek(gsub, lig_list_start, SEEK_SET);
      for (j=0; j<pcnt; ++j)
	 aput_int16_be_checked(ligoffsets[j],gsub);
      afseek(gsub, 0, SEEK_END);
   }
   free(ligoffsets);
   if (glyphs != NULL) {
      here=aftell(gsub);
      afseek(gsub, coverage_pos, SEEK_SET);
      aput_int16_be_checked(here-coverage_pos+2,gsub);
      afseek(gsub, next_val_pos, SEEK_SET);
      for (i=0; i<cnt; ++i)
	 aput_int16_be_checked(offsets[i],gsub);
      afseek(gsub, here, SEEK_SET);
      dumpcoveragetable(gsub, glyphs);
      free(glyphs);
      free(offsets);
   }
}

static int ui16cmp(const void *_i1,const void *_i2) {
   if (*(const uint16_t *) _i1>*(const uint16_t *) _i2)
      return (1);
   if (*(const uint16_t *) _i1<*(const uint16_t *) _i2)
      return (-1);

   return (0);
}

static uint16_t *FigureInitialClasses(FPST *fpst) {
   uint16_t *initial=malloc((fpst->nccnt+1)*sizeof(uint16_t));
   int i, cnt, j;

   for (i=cnt=0; i<fpst->rule_cnt; ++i) {
      for (j=0; j<cnt; ++j)
	 if (initial[j]==fpst->rules[i].u.class.nclasses[0])
	    break;
      if (j==cnt)
	 initial[cnt++]=fpst->rules[i].u.class.nclasses[0];
   }
   qsort(initial, cnt, sizeof(uint16_t), ui16cmp);
   initial[cnt]=0xffff;
   return (initial);
}

static SplineChar **OrderedInitialGlyphs(SplineFont *sf,FPST *fpst) {
   SplineChar **glyphs, *sc;
   int i, j, cnt, ch;
   char *pt, *names;

   glyphs=malloc((fpst->rule_cnt+1)*sizeof(SplineChar *));
   for (i=cnt=0; i<fpst->rule_cnt; ++i) {
      names=fpst->rules[i].u.glyph.names;
      pt=strchr(names, ' ');
      if (pt==NULL)
	 pt=names+strlen(names);
      ch=*pt;
      *pt='\0';
      sc=SFGetChar(sf, -1, names);
      *pt=ch;
      for (j=0; j<cnt; ++j)
	 if (glyphs[j]==sc)
	    break;
      if (j==cnt && sc != NULL)
	 glyphs[cnt++]=sc;
   }
   glyphs[cnt]=NULL;
   if (cnt==0)
      return (glyphs);

   for (i=0; glyphs[i+1] != NULL; ++i)
      for (j=i+1; glyphs[j] != NULL; ++j) {
	 if (glyphs[i]->ttf_glyph>glyphs[j]->ttf_glyph) {
	    sc=glyphs[i];
	    glyphs[i]=glyphs[j];
	    glyphs[j]=sc;
	 }
      }
   return (glyphs);
}

static int NamesStartWith(SplineChar *sc,char *names) {
   char *pt;

   pt=strchr(names, ' ');
   if (pt==NULL)
      pt=names+strlen(names);
   if (pt-names != strlen(sc->name))
      return (false);

   return (strncmp(sc->name, names, pt-names)==0);
}

static int CntRulesStartingWith(FPST *fpst,SplineChar *sc) {
   int i, cnt;

   for (i=cnt=0; i<fpst->rule_cnt; ++i) {
      if (NamesStartWith(sc, fpst->rules[i].u.glyph.names))
	 ++cnt;
   }
   return (cnt);
}

static int CntRulesStartingWithClass(FPST *fpst,uint16_t cval) {
   int i, cnt;

   for (i=cnt=0; i<fpst->rule_cnt; ++i) {
      if (fpst->rules[i].u.class.nclasses[0]==cval)
	 ++cnt;
   }
   return (cnt);
}

static void dumpg___ContextChainGlyphs(AFILE *lfile,SplineFont *sf,
				       struct lookup_subtable *sub,
				       struct alltabs *at) {
   FPST *fpst=sub->fpst;
   int iscontext=fpst->type==pst_contextpos
      || fpst->type==pst_contextsub;
   uint32_t base=aftell(lfile);
   int i, cnt, subcnt, j, k, l, maxcontext, curcontext;
   SplineChar **glyphs, **subglyphs;
   int lc;

   glyphs=OrderedInitialGlyphs(sf, fpst);
   for (cnt=0; glyphs[cnt] != NULL; ++cnt);

   aput_int16_be_checked(1,lfile);		/* Sub format 1 => glyph lists */
   aput_int16_be_checked((3+cnt)*sizeof(short),lfile);	/* offset to coverage */
   aput_int16_be_checked(cnt,lfile);
   for (i=0; i<cnt; ++i)
      aput_int16_be_checked(0,lfile);	/* Offset to rule */
   dumpcoveragetable(lfile, glyphs);

   maxcontext=0;

   for (i=0; i<cnt; ++i) {
      uint32_t pos=aftell(lfile);

      afseek(lfile, base+(3+i)*sizeof(short), SEEK_SET);
      aput_int16_be_checked(pos-base,lfile);
      afseek(lfile, pos, SEEK_SET);
      subcnt=CntRulesStartingWith(fpst, glyphs[i]);
      aput_int16_be_checked(subcnt,lfile);
      for (j=0; j<subcnt; ++j)
	 aput_int16_be_checked(0,lfile);
      for (j=k=0; k<fpst->rule_cnt; ++k)
	 if (NamesStartWith(glyphs[i], fpst->rules[k].u.glyph.names)) {
	    uint32_t subpos=aftell(lfile);

	    afseek(lfile, pos+(1+j)*sizeof(short), SEEK_SET);
	    aput_int16_be_checked(subpos-pos,lfile);
	    afseek(lfile, subpos, SEEK_SET);

	    for (l=lc=0; l<fpst->rules[k].lookup_cnt; ++l)
	       if (fpst->rules[k].lookups[l].lookup->lookup_index != -1)
		  ++lc;
	    if (iscontext) {
	       subglyphs =
		  SFGlyphsFromNames(sf, fpst->rules[k].u.glyph.names);
	       for (l=0; subglyphs[l] != NULL; ++l);
	       aput_int16_be_checked(l,lfile);
	       curcontext=l;
	       aput_int16_be_checked(lc,lfile);
	       for (l=1; subglyphs[l] != NULL; ++l)
		  aput_int16_be_checked(subglyphs[l]->ttf_glyph,lfile);
	       free(subglyphs);
	    } else {
	       subglyphs=SFGlyphsFromNames(sf, fpst->rules[k].u.glyph.back);
	       for (l=0; subglyphs[l] != NULL; ++l);
	       aput_int16_be_checked(l,lfile);
	       curcontext=l;
	       for (l=0; subglyphs[l] != NULL; ++l)
		  aput_int16_be_checked(subglyphs[l]->ttf_glyph,lfile);
	       free(subglyphs);
	       subglyphs =
		  SFGlyphsFromNames(sf, fpst->rules[k].u.glyph.names);
	       for (l=0; subglyphs[l] != NULL; ++l);
	       aput_int16_be_checked(l,lfile);
	       curcontext += l;
	       for (l=1; subglyphs[l] != NULL; ++l)
		  aput_int16_be_checked(subglyphs[l]->ttf_glyph,lfile);
	       free(subglyphs);
	       subglyphs=SFGlyphsFromNames(sf, fpst->rules[k].u.glyph.fore);
	       for (l=0; subglyphs[l] != NULL; ++l);
	       aput_int16_be_checked(l,lfile);
	       curcontext += l;
	       for (l=0; subglyphs[l] != NULL; ++l)
		  aput_int16_be_checked(subglyphs[l]->ttf_glyph,lfile);
	       free(subglyphs);
	       aput_int16_be_checked(lc,lfile);
	    }
	    for (l=0; l<fpst->rules[k].lookup_cnt; ++l)
	       if (fpst->rules[k].lookups[l].lookup->lookup_index != -1) {
		  aput_int16_be_checked(fpst->rules[k].lookups[l].seq,lfile);
		  aput_int16_be_checked(
			   fpst->rules[k].lookups[l].lookup->lookup_index,lfile);
	       }
	    ++j;
	    if (curcontext>maxcontext)
	       maxcontext=curcontext;
	 }
   }
   free(glyphs);

   if (maxcontext>at->os2.maxContext)
      at->os2.maxContext=maxcontext;
}

static void dumpg___ContextChainClass(AFILE *lfile,SplineFont *sf,
				      struct lookup_subtable *sub,
				      struct alltabs *at) {
   FPST *fpst=sub->fpst;
   int iscontext=fpst->type==pst_contextpos
      || fpst->type==pst_contextsub;
   uint32_t base=aftell(lfile), rulebase, pos, subpos, npos;
   uint16_t *initialclasses, *iclass, *bclass, *lclass;
   SplineChar **iglyphs, **bglyphs, **lglyphs, **glyphs;
   int i, ii, cnt, subcnt, j, k, l, maxcontext, curcontext;
   int lc;

   aput_int16_be_checked(2,lfile);		/* Sub format 2 => class */
   aput_int16_be_checked(0,lfile);		/* offset to coverage table */
   if (iscontext)
      aput_int16_be_checked(0,lfile);	/* offset to input classdef */
   else {
      aput_int16_be_checked(0,lfile);	/* offset to backtrack classdef */
      aput_int16_be_checked(0,lfile);	/* offset to input classdef */
      aput_int16_be_checked(0,lfile);	/* offset to lookahead classdef */
   }
   initialclasses=FigureInitialClasses(fpst);
   aput_int16_be_checked(fpst->nccnt,lfile);
   rulebase=aftell(lfile);
   for (cnt=0; cnt<fpst->nccnt; ++cnt)
      aput_int16_be_checked(0,lfile);

   iclass =
      ClassesFromNames(sf, fpst->nclass, fpst->nccnt, at->maxp.numGlyphs,
		       &iglyphs, false);
   lglyphs=bglyphs=NULL;
   bclass=lclass=NULL;
   if (!iscontext) {
      bclass =
	 ClassesFromNames(sf, fpst->bclass, fpst->bccnt, at->maxp.numGlyphs,
			  &bglyphs, false);
      lclass =
	 ClassesFromNames(sf, fpst->fclass, fpst->fccnt, at->maxp.numGlyphs,
			  &lglyphs, false);
   }
   pos=aftell(lfile);
   afseek(lfile, base+sizeof(uint16_t), SEEK_SET);
   aput_int16_be_checked(pos-base,lfile);
   afseek(lfile, pos, SEEK_SET);
   glyphs =
      GlyphsFromInitialClasses(iglyphs, at->maxp.numGlyphs, iclass,
			       initialclasses);
   dumpcoveragetable(lfile, glyphs);
   free(glyphs);
   free(iglyphs);
   free(bglyphs);
   free(lglyphs);

   if (iscontext) {
      pos=aftell(lfile);
      afseek(lfile, base+2*sizeof(uint16_t), SEEK_SET);
      aput_int16_be_checked(pos-base,lfile);
      afseek(lfile, pos, SEEK_SET);
      DumpClass(lfile, iclass, at->maxp.numGlyphs);
      free(iclass);
   } else {
      pos=aftell(lfile);
      afseek(lfile, base+2*sizeof(uint16_t), SEEK_SET);
      aput_int16_be_checked(pos-base,lfile);
      afseek(lfile, pos, SEEK_SET);
      DumpClass(lfile, bclass, at->maxp.numGlyphs);
      if (ClassesMatch(fpst->bccnt, fpst->bclass, fpst->nccnt, fpst->nclass)) {
	 npos=pos;
	 afseek(lfile, base+3*sizeof(uint16_t), SEEK_SET);
	 aput_int16_be_checked(npos-base,lfile);
	 afseek(lfile, 0, SEEK_END);
      } else {
	 npos=aftell(lfile);
	 afseek(lfile, base+3*sizeof(uint16_t), SEEK_SET);
	 aput_int16_be_checked(npos-base,lfile);
	 afseek(lfile, npos, SEEK_SET);
	 DumpClass(lfile, iclass, at->maxp.numGlyphs);
      }
      if (ClassesMatch(fpst->fccnt, fpst->fclass, fpst->bccnt, fpst->bclass)) {
	 afseek(lfile, base+4*sizeof(uint16_t), SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, 0, SEEK_END);
      } else
	 if (ClassesMatch
	     (fpst->fccnt, fpst->fclass, fpst->nccnt, fpst->nclass)) {
	 afseek(lfile, base+4*sizeof(uint16_t), SEEK_SET);
	 aput_int16_be_checked(npos-base,lfile);
	 afseek(lfile, 0, SEEK_END);
      } else {
	 pos=aftell(lfile);
	 afseek(lfile, base+4*sizeof(uint16_t), SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, pos, SEEK_SET);
	 DumpClass(lfile, lclass, at->maxp.numGlyphs);
      }
      free(iclass);
      free(bclass);
      free(lclass);
   }

   ii=0;
   for (i=0; i<fpst->nccnt; ++i) {
      if (initialclasses[ii] != i) {
	 /* This class isn't an initial one, so leave its rule pointer NULL */
      } else {
	 ++ii;
	 pos=aftell(lfile);
	 afseek(lfile, rulebase+i * sizeof(short), SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, pos, SEEK_SET);
	 subcnt=CntRulesStartingWithClass(fpst, i);
	 aput_int16_be_checked(subcnt,lfile);
	 for (j=0; j<subcnt; ++j)
	    aput_int16_be_checked(0,lfile);
	 for (j=k=0; k<fpst->rule_cnt; ++k)
	    if (i==fpst->rules[k].u.class.nclasses[0]) {
	       subpos=aftell(lfile);
	       afseek(lfile, pos+(1+j)*sizeof(short), SEEK_SET);
	       aput_int16_be_checked(subpos-pos,lfile);
	       afseek(lfile, subpos, SEEK_SET);

	       for (l=lc=0; l<fpst->rules[k].lookup_cnt; ++l)
		  if (fpst->rules[k].lookups[l].lookup->lookup_index != -1)
		     ++lc;
	       if (iscontext) {
		  aput_int16_be_checked(fpst->rules[k].u.class.ncnt,lfile);
		  aput_int16_be_checked(lc,lfile);
		  for (l=1; l<fpst->rules[k].u.class.ncnt; ++l)
		     aput_int16_be_checked(fpst->rules[k].u.class.nclasses[l],lfile);
	       } else {
		  aput_int16_be_checked(fpst->rules[k].u.class.bcnt,lfile);
		  for (l=0; l<fpst->rules[k].u.class.bcnt; ++l)
		     aput_int16_be_checked(fpst->rules[k].u.class.bclasses[l],lfile);
		  aput_int16_be_checked(fpst->rules[k].u.class.ncnt,lfile);
		  for (l=1; l<fpst->rules[k].u.class.ncnt; ++l)
		     aput_int16_be_checked(fpst->rules[k].u.class.nclasses[l],lfile);
		  aput_int16_be_checked(fpst->rules[k].u.class.fcnt,lfile);
		  for (l=0; l<fpst->rules[k].u.class.fcnt; ++l)
		     aput_int16_be_checked(fpst->rules[k].u.class.fclasses[l],lfile);
		  aput_int16_be_checked(lc,lfile);
	       }
	       for (l=0; l<fpst->rules[k].lookup_cnt; ++l)
		  if (fpst->rules[k].lookups[l].lookup->lookup_index != -1) {
		     aput_int16_be_checked(fpst->rules[k].lookups[l].seq,lfile);
		     aput_int16_be_checked(
			      fpst->rules[k].lookups[l].lookup->lookup_index,lfile);
		  }
	       ++j;
	    }
      }
   }
   free(initialclasses);

   maxcontext=0;
   for (i=0; i<fpst->rule_cnt; ++i) {
      curcontext =
	 fpst->rules[i].u.class.ncnt+fpst->rules[i].u.class.bcnt +
	 fpst->rules[i].u.class.fcnt;
      if (curcontext>maxcontext)
	 maxcontext=curcontext;
   }
   if (maxcontext>at->os2.maxContext)
      at->os2.maxContext=maxcontext;
}

static void dumpg___ContextChainCoverage(AFILE *lfile,SplineFont *sf,
					 struct lookup_subtable *sub,
					 struct alltabs *at) {
   FPST *fpst=sub->fpst;
   int iscontext=fpst->type==pst_contextpos
      || fpst->type==pst_contextsub;
   uint32_t base=aftell(lfile), ibase, lbase, bbase;
   int i, l;
   SplineChar **glyphs;
   int curcontext;
   int lc;

   if (fpst->rule_cnt != 1)
      ErrorMsg(2,"Bad rule cnt in coverage context lookup\n");
   if (fpst->format==pst_reversecoverage
       && fpst->rules[0].u.rcoverage.always1 != 1)
      ErrorMsg(2,"Bad input count in reverse coverage lookup\n");

   aput_int16_be_checked(3,lfile);		/* Sub format 3 => coverage */
   for (l=lc=0; l<fpst->rules[0].lookup_cnt; ++l)
      if (fpst->rules[0].lookups[l].lookup->lookup_index != -1)
	 ++lc;
   if (iscontext) {
      aput_int16_be_checked(fpst->rules[0].u.coverage.ncnt,lfile);
      aput_int16_be_checked(lc,lfile);
      for (i=0; i<fpst->rules[0].u.coverage.ncnt; ++i)
	 aput_int16_be_checked(0,lfile);
      for (i=0; i<fpst->rules[0].lookup_cnt; ++i)
	 if (fpst->rules[0].lookups[i].lookup->lookup_index != -1) {
	    aput_int16_be_checked(fpst->rules[0].lookups[i].seq,lfile);
	    aput_int16_be_checked(fpst->rules[0].lookups[i].lookup->lookup_index,lfile);
	 }
      for (i=0; i<fpst->rules[0].u.coverage.ncnt; ++i) {
	 uint32_t pos=aftell(lfile);

	 afseek(lfile, base+6+2*i, SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, pos, SEEK_SET);
	 glyphs =
	    OrderedGlyphsFromNames(sf, fpst->rules[0].u.coverage.ncovers[i]);
	 dumpcoveragetable(lfile, glyphs);
	 free(glyphs);
      }
   } else {
      if (fpst->format==pst_reversecoverage) {
	 ibase=aftell(lfile);
	 aput_int16_be_checked(0,lfile);
      }
      aput_int16_be_checked(fpst->rules[0].u.coverage.bcnt,lfile);
      bbase=aftell(lfile);
      for (i=0; i<fpst->rules[0].u.coverage.bcnt; ++i)
	 aput_int16_be_checked(0,lfile);
      if (fpst->format==pst_coverage) {
	 aput_int16_be_checked(fpst->rules[0].u.coverage.ncnt,lfile);
	 ibase=aftell(lfile);
	 for (i=0; i<fpst->rules[0].u.coverage.ncnt; ++i)
	    aput_int16_be_checked(0,lfile);
      }
      aput_int16_be_checked(fpst->rules[0].u.coverage.fcnt,lfile);
      lbase=aftell(lfile);
      for (i=0; i<fpst->rules[0].u.coverage.fcnt; ++i)
	 aput_int16_be_checked(0,lfile);
      if (fpst->format==pst_coverage) {
	 aput_int16_be_checked(lc,lfile);
	 for (i=0; i<fpst->rules[0].lookup_cnt; ++i)
	    if (fpst->rules[0].lookups[i].lookup->lookup_index != -1) {
	       aput_int16_be_checked(fpst->rules[0].lookups[i].seq,lfile);
	       aput_int16_be_checked(
			fpst->rules[0].lookups[i].lookup->lookup_index,lfile);
	    }
      } else {			/* reverse coverage */
	 glyphs =
	    SFGlyphsFromNames(sf, fpst->rules[0].u.rcoverage.replacements);
	 for (i=0; glyphs[i] != 0; ++i);
	 aput_int16_be_checked(i,lfile);
	 for (i=0; glyphs[i] != 0; ++i)
	    aput_int16_be_checked(glyphs[i]->ttf_glyph,lfile);
      }
      for (i=0; i<fpst->rules[0].u.coverage.ncnt; ++i) {
	 uint32_t pos=aftell(lfile);

	 afseek(lfile, ibase+2*i, SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, pos, SEEK_SET);
	 glyphs =
	    OrderedGlyphsFromNames(sf, fpst->rules[0].u.coverage.ncovers[i]);
	 dumpcoveragetable(lfile, glyphs);
	 free(glyphs);
      }
      for (i=0; i<fpst->rules[0].u.coverage.bcnt; ++i) {
	 uint32_t pos=aftell(lfile);

	 afseek(lfile, bbase+2*i, SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, pos, SEEK_SET);
	 glyphs =
	    OrderedGlyphsFromNames(sf, fpst->rules[0].u.coverage.bcovers[i]);
	 dumpcoveragetable(lfile, glyphs);
	 free(glyphs);
      }
      for (i=0; i<fpst->rules[0].u.coverage.fcnt; ++i) {
	 uint32_t pos=aftell(lfile);

	 afseek(lfile, lbase+2*i, SEEK_SET);
	 aput_int16_be_checked(pos-base,lfile);
	 afseek(lfile, pos, SEEK_SET);
	 glyphs =
	    OrderedGlyphsFromNames(sf, fpst->rules[0].u.coverage.fcovers[i]);
	 dumpcoveragetable(lfile, glyphs);
	 free(glyphs);
      }
   }

   curcontext =
      fpst->rules[0].u.coverage.ncnt+fpst->rules[0].u.coverage.bcnt +
      fpst->rules[0].u.coverage.fcnt;
   if (curcontext>at->os2.maxContext)
      at->os2.maxContext=curcontext;
}

static void dumpg___ContextChain(AFILE *lfile,SplineFont *sf,
				 struct lookup_subtable *sub,
				 struct alltabs *at) {
   FPST *fpst=sub->fpst;

   switch (fpst->format) {
     case pst_glyphs:
	dumpg___ContextChainGlyphs(lfile, sf, sub, at);
	break;
     case pst_class:
	dumpg___ContextChainClass(lfile, sf, sub, at);
	break;
     case pst_coverage:
     case pst_reversecoverage:
	dumpg___ContextChainCoverage(lfile, sf, sub, at);
	break;
   }

   afseek(lfile, 0, SEEK_END);

}

static void AnchorsAway(AFILE *lfile,SplineFont *sf,
			struct lookup_subtable *sub, struct glyphinfo *gi) {
   SplineChar **base, **lig, **mkmk;
   AnchorClass *ac, *acfirst;
   SplineChar ***marks;
   int *subcnts;
   int cmax, classcnt;
   int i;

   marks=malloc((cmax=20)*sizeof(SplineChar **));
   subcnts=malloc(cmax * sizeof(int));

   classcnt=0;
   acfirst=NULL;
   for (ac=sf->anchor; ac != NULL; ac=ac->next) {
      ac->matches=false;
      if (ac->subtable==sub && !ac->processed) {
	 if (acfirst==NULL)
	    acfirst=ac;
	 if (ac->type==act_curs)
	    continue;
	 else if (ac->has_mark && ac->has_base) {
	    ac->matches=ac->processed=true;
	    ++classcnt;
	 }
      }
   }
   if (classcnt>cmax) {
      marks=realloc(marks, (cmax=classcnt+10)*sizeof(SplineChar **));
      subcnts=realloc(subcnts, cmax * sizeof(int));
   }
   AnchorClassDecompose(sf, acfirst, classcnt, subcnts, marks, &base, &lig,
			&mkmk, gi);
   switch (sub->lookup->lookup_type) {
     case gpos_mark2base:
	if (marks[0] != NULL && base != NULL)
	   dumpgposAnchorData(lfile, acfirst, at_basechar, marks, base,
			      classcnt, gi);
	break;
     case gpos_mark2ligature:
	if (marks[0] != NULL && lig != NULL)
	   dumpgposAnchorData(lfile, acfirst, at_baselig, marks, lig,
			      classcnt, gi);
	break;
     case gpos_mark2mark:
	if (marks[0] != NULL && mkmk != NULL)
	   dumpgposAnchorData(lfile, acfirst, at_basemark, marks, mkmk,
			      classcnt, gi);
	break;
   }
   for (i=0; i<classcnt; ++i)
      free(marks[i]);
   free(base);
   free(lig);
   free(mkmk);
   free(marks);
   free(subcnts);
}

static int lookup_size_cmp(const void *_l1,const void *_l2) {
   const OTLookup *l1=*(OTLookup **) _l1, *l2=*(OTLookup **) _l2;

   return (l1->lookup_length-l2->lookup_length);
}

static int FPSTRefersToOTL(FPST *fpst,OTLookup *otl) {
   int i, j;

   if (fpst==NULL || fpst->type==pst_reversesub)
      return (false);
   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)
	    return (true);
   }
   return (false);
}

static int OnlyMac(OTLookup *otl,OTLookup *all) {
   FeatureScriptLangList *features=otl->features;
   int anymac=0;
   struct lookup_subtable *sub;

   switch (otl->lookup_type) {
	/* These two lookup types are mac only */
     case kern_statemachine:
     case morx_indic:
     case morx_context:
     case morx_insert:
	return (true);
	/* These lookup types are OpenType only */
     case gsub_multiple:
     case gsub_alternate:
     case gsub_context:
     case gsub_contextchain:
     case gsub_reversecchain:
     case gpos_single:
     case gpos_cursive:
     case gpos_mark2base:
     case gpos_mark2ligature:
     case gpos_mark2mark:
     case gpos_context:
     case gpos_contextchain:
	return (false);
	/* These two can be expressed in both, and might be either */
     case gpos_pair:
     case gsub_single:
     case gsub_ligature:
	for (features=otl->features; features != NULL;
	     features=features->next) {
	   if (!features->ismac)
	      return (false);
	   else
	      anymac=true;
	}
	/* Either it has no features at all (nested), or all its features */
	/*  are mac feature settings. Even if all are mac feature settings it */
	/*  might still be used as under control of a contextual feature */
	/*  so in both cases check for nested */
	while (all != NULL) {
	   if (all != otl && !all->unused && (all->lookup_type==gpos_context || all->lookup_type==gpos_contextchain || all->lookup_type==gsub_context || all->lookup_type==gsub_contextchain	/*||
																									   all->lookup_type==gsub_reversecchain */
					      )) {
	      for (sub=all->subtables; sub != NULL; sub=sub->next)
		 if (!sub->unused && sub->fpst != NULL) {
		    if (FPSTRefersToOTL(sub->fpst, otl))
		       return (false);
		 }
	   }
	   all=all->next;
	}
	if (anymac)
	   return (true);
	/* As far as I can tell, this lookup isn't used at all */
	/*  Let's output it anyway, just in case we ever support some other */
	/*  table that uses GPOS/GSUB lookups (I think JUST) */
	return (false);
   }
   /* Should never get here, but gcc probably thinks we might */
   return (true);
}

static void otf_dumpALookup(AFILE *lfile,OTLookup *otl,SplineFont *sf,
			    struct alltabs *at) {
   struct lookup_subtable *sub;

   otl->lookup_offset=aftell(lfile);
   for (sub=otl->subtables; sub != NULL; sub=sub->next) {
      sub->extra_subtables=NULL;
      if (sub->unused)
	 sub->subtable_offset=-1;
      else {
	 sub->subtable_offset=aftell(lfile);
	 switch (otl->lookup_type) {
	      /* GPOS lookup types */
	   case gpos_single:
	      dumpGPOSsimplepos(lfile, sf, sub);
	      break;

	   case gpos_pair:
	      if (at->os2.maxContext<2)
		 at->os2.maxContext=2;
	      if (sub->kc != NULL)
		 dumpgposkernclass(lfile, sf, sub, at);
	      else
		 dumpGPOSpairpos(lfile, sf, sub);
	      break;

	   case gpos_cursive:
	      dumpgposCursiveAttach(lfile, sf, sub, &at->gi);
	      break;

	   case gpos_mark2base:
	   case gpos_mark2ligature:
	   case gpos_mark2mark:
	      AnchorsAway(lfile, sf, sub, &at->gi);
	      break;

	   case gpos_contextchain:
	   case gpos_context:
	      dumpg___ContextChain(lfile, sf, sub, at);
	      break;

	      /* GSUB lookup types */
	   case gsub_single:
	      dumpGSUBsimplesubs(lfile, sf, sub);
	      break;

	   case gsub_multiple:
	   case gsub_alternate:
	      dumpGSUBmultiplesubs(lfile, sf, sub);
	      break;

	   case gsub_ligature:
	      dumpGSUBligdata(lfile, sf, sub, at);
	      break;

	   case gsub_contextchain:
	   case gsub_context:
	   case gsub_reversecchain:
	      dumpg___ContextChain(lfile, sf, sub, at);
	      break;
	 }
	 if (aftell(lfile)-sub->subtable_offset==0) {
	    ErrorMsg(2,"Lookup sub table, %s in %s, contains no data.\n",
		   sub->subtable_name, sub->lookup->lookup_name);
	    sub->unused=true;
	    sub->subtable_offset=-1;
	 } else if (sub->extra_subtables==NULL &&
		    aftell(lfile)-sub->subtable_offset>65535)
	    ErrorMsg(2,"Lookup sub table, %s in %s, is too big. Will not be useable.\n",
		sub->subtable_name, sub->lookup->lookup_name);
      }
   }
   otl->lookup_length=aftell(lfile)-otl->lookup_offset;
}

static AFILE *G___figureLookups(SplineFont *sf,int is_gpos,
			       struct alltabs *at) {
   OTLookup *otl;
   struct lookup_subtable *sub;
   int index, i, j;
   AFILE *final;
   AFILE *lfile=atmpfile();
   OTLookup **sizeordered;
   OTLookup *all=sf->gsplookups[is_gpos];
   char *buffer;
   int len;

   index=0;
   for (otl=all; otl != NULL; otl=otl->next) {
      if (otl->unused || OnlyMac(otl, all) || otl->only_jstf
	  || otl->temporary_kern)
	 otl->lookup_index=-1;
      else
	 otl->lookup_index=index++;
   }
   for (otl=all; otl != NULL; otl=otl->next) {
      if (otl->lookup_index != -1) {
	 otf_dumpALookup(lfile, otl, sf, at);
      }
   }
   if (is_gpos)
      AnchorGuessContext(sf, at);

   /* We don't need to reorder short files */
   if (aftell(lfile)<65536)
      return (lfile);

   /* Order the lookups so that the smallest ones come first */
   /*  thus we are less likely to need extension tables */
   /* I think it's better to order the entire lookup rather than ordering the */
   /*  subtables -- since the extension subtable would be required for all */
   /*  subtables in the lookup, so might as well keep them all together */
   sizeordered=malloc(index * sizeof(OTLookup *));
   for (otl=sf->gsplookups[is_gpos]; otl != NULL;
	otl=otl->next)
      if (otl->lookup_index != -1)
	 sizeordered[otl->lookup_index]=otl;
   qsort(sizeordered, index, sizeof(OTLookup *), lookup_size_cmp);

   final=atmpfile();
   buffer=malloc(32768);
   for (i=0; i<index; ++i) {
      uint32_t diff;

      otl=sizeordered[i];
      afseek(lfile, otl->lookup_offset, SEEK_SET);
      diff=aftell(final)-otl->lookup_offset;
      otl->lookup_offset=aftell(final);
      len=otl->lookup_length;
      while (len >= 32768) {
	 int done=afread(buffer, 1, 32768, lfile);

	 if (done==0)		/* fread returns 0 on error, not EOF */
	    break;
	 afwrite(buffer, 1, done, final);
	 len -= done;
      }
      if (len>0 && len <= 32768) {
	 int done=afread(buffer, 1, len, lfile);

	 if (done==0)
	    break;
	 afwrite(buffer, 1, done, final);
      }
      for (sub=otl->subtables; sub != NULL; sub=sub->next) {
	 if (!sub->unused) {
	    sub->subtable_offset += diff;
	    if (sub->extra_subtables != NULL) {
	       for (j=0; sub->extra_subtables[j] != -1; ++j)
		  sub->extra_subtables[j] += diff;
	    }
	 }
      }
   }
   free(buffer);
   free(sizeordered);
   afclose(lfile);
   return (final);
}

struct feat_lookups {
   uint32_t tag;
   int lcnt;
   OTLookup **lookups;
   int feature_id;		/* Initially consecutive, but may be rearranged by sorting */
   uint32_t name_param_ptr;
};

struct langsys {
   uint32_t lang;
   int fc;
   int *feature_id;
   int same_as;
   int32_t offset;
};

struct scriptset {
   uint32_t script;
   int lc;
   struct langsys *langsys;
};

struct ginfo {
   int fmax, fcnt;
   struct feat_lookups *feat_lookups;
   int sc;
   struct scriptset *scripts;
};

static int FindOrMakeNewFeatureLookup(struct ginfo *ginfo,
				      OTLookup ** lookups, uint32_t tag) {
   int i, j;

   for (i=0; i<ginfo->fcnt; ++i) {
      if (ginfo->feat_lookups[i].tag != tag)
	 continue;
      if (lookups==NULL && ginfo->feat_lookups[i].lookups==NULL)	/* 'size' feature */
	 return (i);
      if (lookups==NULL || ginfo->feat_lookups[i].lookups==NULL)
	 continue;
      for (j=0;
	   lookups[j] != NULL && ginfo->feat_lookups[i].lookups[j] != NULL;
	   ++j)
	 if (ginfo->feat_lookups[i].lookups[j] != lookups[j])
	    break;
      if (ginfo->feat_lookups[i].lookups[j]==lookups[j]) {
	 free(lookups);
	 return (i);
      }
   }
   if (ginfo->fcnt >= ginfo->fmax)
      ginfo->feat_lookups =
	 realloc(ginfo->feat_lookups,
		 (ginfo->fmax += 20)*sizeof(struct feat_lookups));
   ginfo->feat_lookups[i].feature_id=i;
   ginfo->feat_lookups[i].tag=tag;
   ginfo->feat_lookups[i].lookups=lookups;
   j=0;
   if (lookups != NULL)
      for (; lookups[j] != NULL; ++j);
   ginfo->feat_lookups[i].lcnt=j;
   ++ginfo->fcnt;
   return (i);
}

static int feat_alphabetize(const void *_fl1,const void *_fl2) {
   const struct feat_lookups *fl1=_fl1, *fl2=_fl2;

   if (fl1->tag<fl2->tag)
      return (-1);
   if (fl1->tag>fl2->tag)
      return (1);

   return (0);
}

static int numeric_order(const void *_i1,const void *_i2) {
   int i1=*(const int *) _i1, i2=*(const int *) _i2;

   if (i1<i2)
      return (-1);
   if (i1>i2)
      return (1);

   return (0);
}

static int LangSysMatch(struct scriptset *s,int ils1,int ils2) {
   struct langsys *ls1=&s->langsys[ils1], *ls2=&s->langsys[ils2];
   int i;

   if (ls1->fc != ls2->fc)
      return (false);
   for (i=0; i<ls1->fc; ++i)
      if (ls1->feature_id[i] != ls2->feature_id[i])
	 return (false);

   return (true);
}

static void FindFeatures(SplineFont *sf,int is_gpos,struct ginfo *ginfo) {
   uint32_t *scripts, *langs, *features;
   OTLookup **lookups;
   int sc, lc, fc, j;

   memset(ginfo, 0, sizeof(struct ginfo));

   scripts=SFScriptsInLookups(sf, is_gpos);
   if (scripts==NULL)		/* All lookups unused */
      return;
   for (sc=0; scripts[sc] != 0; ++sc);
   ginfo->scripts=malloc(sc * sizeof(struct scriptset));
   ginfo->sc=sc;
   for (sc=0; scripts[sc] != 0; ++sc) {
      langs=SFLangsInScript(sf, is_gpos, scripts[sc]);
      for (lc=0; langs[lc] != 0; ++lc);
      ginfo->scripts[sc].script=scripts[sc];
      ginfo->scripts[sc].lc=lc;
      ginfo->scripts[sc].langsys=malloc(lc * sizeof(struct langsys));
      for (lc=0; langs[lc] != 0; ++lc) {
	 features =
	    SFFeaturesInScriptLang(sf, is_gpos, scripts[sc], langs[lc]);
	 for (fc=0; features[fc] != 0; ++fc);
	 ginfo->scripts[sc].langsys[lc].lang=langs[lc];
	 ginfo->scripts[sc].langsys[lc].fc=fc;
	 ginfo->scripts[sc].langsys[lc].feature_id=malloc(fc * sizeof(int));
	 ginfo->scripts[sc].langsys[lc].same_as=-1;
	 for (fc=0; features[fc] != 0; ++fc) {
	    lookups =
	       SFLookupsInScriptLangFeature(sf, is_gpos, scripts[sc],
					    langs[lc], features[fc]);
	    ginfo->scripts[sc].langsys[lc].feature_id[fc] =
	       FindOrMakeNewFeatureLookup(ginfo, lookups, features[fc]);
	    /* lookups is freed or used by FindOrMakeNewFeatureLookup */
	 }
	 free(features);
      }
      free(langs);
   }
   free(scripts);

   qsort(ginfo->feat_lookups, ginfo->fcnt, sizeof(struct feat_lookups),
	 feat_alphabetize);

   /* Now we've disordered the features. Find each feature_id and turn it back */
   /*  into a feature number */
   for (sc=0; sc<ginfo->sc; ++sc) {
      for (lc=0; lc<ginfo->scripts[sc].lc; ++lc) {
	 int fcmax=ginfo->scripts[sc].langsys[lc].fc;
	 int *feature_id=ginfo->scripts[sc].langsys[lc].feature_id;

	 for (fc=0; fc<fcmax; ++fc) {
	    int id=feature_id[fc];

	    for (j=0; j<ginfo->fcnt; ++j)
	       if (id==ginfo->feat_lookups[j].feature_id)
		  break;
	    feature_id[fc]=j;
	 }
	 qsort(feature_id, fcmax, sizeof(int), numeric_order);
      }
      /* See if there are langsys tables which use exactly the same features */
      /*  They can use the same entry in the file. This optimization seems  */
      /*  to be required for Japanese vertical writing to work in Uniscribe. */
      for (lc=0; lc<ginfo->scripts[sc].lc; ++lc) {
	 for (j=0; j<lc; ++j)
	    if (LangSysMatch(&ginfo->scripts[sc], j, lc)) {
	       ginfo->scripts[sc].langsys[lc].same_as=j;
	       break;
	    }
      }
   }
}


static void dump_script_table(AFILE *g___,struct scriptset *ss,
			      struct ginfo *ginfo) {
   int i, lcnt, dflt_lang=-1;
   uint32_t base;
   int j, req_index;
   uint32_t offset;

   /* Count the languages, and find default */
   for (lcnt=0; lcnt<ss->lc; ++lcnt)
      if (ss->langsys[lcnt].lang==DEFAULT_LANG)
	 dflt_lang=lcnt;
   if (dflt_lang != -1)
      --lcnt;

   base=aftell(g___);
   aput_int16_be_checked(0,g___);		/* fill in later, Default Lang Sys */
   aput_int16_be_checked(lcnt,g___);
   for (i=0; i<ss->lc; ++i)
      if (i != dflt_lang) {
	 putlong(g___, ss->langsys[i].lang);	/* Language tag */
	 aput_int16_be_checked(0,g___);	/* Fill in later, offset to langsys */
      }

   for (lcnt=0; lcnt<ss->lc; ++lcnt) {
      if (ss->langsys[lcnt].same_as != -1)
	 offset=ss->langsys[ss->langsys[lcnt].same_as].offset;
      else {
	 offset=aftell(g___);
	 ss->langsys[lcnt].offset=offset;
      }
      afseek(g___, lcnt==dflt_lang?base :
	    lcnt<dflt_lang || dflt_lang==-1?base+4+lcnt*6+4 :
	    base+4+(lcnt-1)*6+4, SEEK_SET);
      aput_int16_be_checked(offset-base,g___);
      afseek(g___, 0, SEEK_END);
      if (ss->langsys[lcnt].same_as==-1) {
	 req_index=-1;
	 for (j=0; j<ss->langsys[lcnt].fc; ++j) {
	    if (ginfo->feat_lookups[ss->langsys[lcnt].feature_id[j]].tag ==
		REQUIRED_FEATURE) {
	       req_index=ss->langsys[lcnt].feature_id[j];
	       break;
	    }
	 }
	 aput_int16_be_checked(0,g___);	/* LookupOrder, always NULL */
	 aput_int16_be_checked(req_index,g___);	/* index of required feature, if any */
	 aput_int16_be_checked(ss->langsys[lcnt].fc-(req_index != -1),g___);
	 /* count of non-required features */
	 for (j=0; j<ss->langsys[lcnt].fc; ++j)
	    if (ss->langsys[lcnt].feature_id[j] != req_index)
	       aput_int16_be_checked(ss->langsys[lcnt].feature_id[j],g___);
      }
   }
}

static AFILE *g___FigureExtensionSubTables(OTLookup *all,int startoffset,
					  int is_gpos) {
   OTLookup *otf;
   struct lookup_subtable *sub;
   int len, len2, gotmore;
   AFILE *efile;
   int i, offset, cnt;
   int any=false;

   if (all==NULL)
      return (NULL);
   gotmore=true;
   cnt=len=0;
   while (gotmore) {
      gotmore=false;
      offset=startoffset+8*cnt;
      for (otf=all; otf != NULL; otf=otf->next)
	 if (otf->lookup_index != -1) {
	    if (otf->needs_extension)
	       continue;
	    for (sub=otf->subtables; sub != NULL; sub=sub->next) {
	       if (sub->subtable_offset==-1)
		  continue;
	       if (sub->extra_subtables != NULL) {
		  for (i=0; sub->extra_subtables[i] != -1; ++i) {
		     if (sub->extra_subtables[i]+offset>65535)
			break;
		  }
		  if (sub->extra_subtables[i] != -1)
		     break;
	       } else if (sub->subtable_offset+offset>65535)
		  break;
	    }
	    if (sub != NULL) {
	       if (!any) {
		  ErrorMsg(1,
			   "Lookup %s has an offset bigger than "
			   "65535 bytes.  This means\n"
			   "FontAnvil must use an extension lookup "
			   "to write it.  Not all\n"
			   "applications support extension lookups.\n",
                           otf->lookup_name);
		  any=true;
	       }
	       otf->needs_extension=true;
	       gotmore=true;
	       len += 8*otf->subcnt;
	       ++cnt;
	    }
	    offset -= 6+2*otf->subcnt;
	 }
   }

   if (cnt==0)		/* No offset overflows */
      return (NULL);

   /* Now we've worked out which lookups need extension tables and marked them */
   /* Generate the extension tables, and update the offsets to reflect the size */
   /* of the extensions */
   efile=atmpfile();

   len2=0;
   for (otf=all; otf != NULL; otf=otf->next)
      if (otf->lookup_index != -1) {
	 for (sub=otf->subtables; sub != NULL; sub=sub->next) {
	    if (sub->subtable_offset==-1)
	       continue;
	    if (sub->extra_subtables != NULL) {
	       for (i=0; sub->extra_subtables[i] != -1; ++i) {
		  sub->extra_subtables[i] += len;
		  if (otf->needs_extension) {
		     int off=aftell(efile);

		     aput_int16_be_checked(1,efile);	/* exten subtable format (there's only one) */
		     aput_int16_be_checked(otf->lookup_type&0xff,efile);
		     putlong(efile, sub->extra_subtables[i]-len2);
		     sub->extra_subtables[i]=off;
		     len2 += 8;
		  }
	       }
	    } else {
	       sub->subtable_offset += len;
	       if (otf->needs_extension) {
		  int off=aftell(efile);

		  aput_int16_be_checked(1,efile);	/* exten subtable format (there's only one) */
		  aput_int16_be_checked(otf->lookup_type&0xff,efile);
		  putlong(efile, sub->subtable_offset-len2);
		  sub->subtable_offset=off;
		  len2 += 8;
	       }
	    }
	 }
      }

   return (efile);
}

struct otffeatname *findotffeatname(uint32_t tag, SplineFont *sf) {
   struct otffeatname *fn;

   for (fn=sf->feat_names; fn != NULL && fn->tag != tag; fn=fn->next);
   return (fn);
}

static AFILE *dumpg___info(struct alltabs *at,SplineFont *sf,int is_gpos) {
   /* Dump out either a gpos or a gsub table. gpos handles kerns, gsub ligs */
   /*  we assume that SFFindUnusedLookups has been called */
   AFILE *lfile, *g___, *efile;
   uint32_t lookup_list_table_start, feature_list_table_start, here,
      scripts_start_offset;
   struct ginfo ginfo;
   int32_t size_params_loc, size_params_ptr;
   int i, j, cnt, scnt, offset;
   OTLookup *otf, *all;
   struct lookup_subtable *sub;
   char *buf;
   struct otffeatname *fn;

   for (fn=sf->feat_names; fn != NULL; fn=fn->next)
      fn->nid=0;

   FindFeatures(sf, is_gpos, &ginfo);
   if (ginfo.sc==0)
      return (NULL);
   lfile=G___figureLookups(sf, is_gpos, at);

   if (ginfo.sc==0 && aftell(lfile)==0) {
      /* aftell(lfile)==0 => There are no lookups for this table */
      /* ginfo.sc==0 => There are no scripts. */
      /* If both are true then we don't need to output the table */
      /* It is perfectly possible to have lookups without scripts */
      /*  (if some other table refered to them -- we don't currently */
      /*  support this, but we might some day). */
      /* It is also possible to have scripts without lookups (to get */
      /*  around a bug in Uniscribe which only processes some scripts */
      /*  if both GPOS and GSUB entries are present. So we share scripts */
      /*  between the two tables */
      afclose(lfile);
      /* if ginfo.sc==0 then there will be nothing to free in the ginfo struct */
      return (NULL);
   }

   g___=atmpfile();

   putlong(g___, 0x10000);	/* version number */
   aput_int16_be_checked(10,g___);		/* offset to script table */
   aput_int16_be_checked(0,g___);		/* offset to features. Come back for this */
   aput_int16_be_checked(0,g___);		/* offset to lookups.  Come back for this */
/* Now the scripts */
   scripts_start_offset=aftell(g___);
   aput_int16_be_checked(ginfo.sc,g___);
   for (i=0; i<ginfo.sc; ++i) {
      putlong(g___, ginfo.scripts[i].script);
      aput_int16_be_checked(0,g___);	/* fix up later */
   }

   /* Ok, that was the script_list_table which gives each script an offset */
   /* Now for each script we provide a Script table which contains an */
   /*  offset to a bunch of features for the default language, and a */
   /*  a more complex situation for non-default languages. */
   offset=2+4;		/* To the script pointer at the start of table */
   for (i=0; i<ginfo.sc; ++i) {
      here=aftell(g___);
      afseek(g___, scripts_start_offset+offset, SEEK_SET);
      aput_int16_be_checked(here-scripts_start_offset,g___);
      offset += 6;
      afseek(g___, here, SEEK_SET);
      dump_script_table(g___, &ginfo.scripts[i], &ginfo);
   }
   /* And that should finish all the scripts/languages */

   /* so free the ginfo script/lang data */
   for (i=0; i<ginfo.sc; ++i) {
      for (j=0; j<ginfo.scripts[i].lc; ++j) {
	 free(ginfo.scripts[i].langsys[j].feature_id);
      }
      free(ginfo.scripts[i].langsys);
   }
   free(ginfo.scripts);

/* Now the features */
   feature_list_table_start=aftell(g___);
   afseek(g___, 6, SEEK_SET);
   aput_int16_be_checked(feature_list_table_start,g___);
   afseek(g___, 0, SEEK_END);
   aput_int16_be_checked(ginfo.fcnt,g___);	/* Number of features */
   offset=2+6*ginfo.fcnt;	/* Offset to start of first feature table from beginning of feature_list */
   for (i=0; i<ginfo.fcnt; ++i) {
      putlong(g___, ginfo.feat_lookups[i].tag);
      aput_int16_be_checked(offset,g___);
      offset += 4+2*ginfo.feat_lookups[i].lcnt;
   }
   /* for each feature, one feature table */
   size_params_ptr=0;
   for (i=0; i<ginfo.fcnt; ++i) {
      ginfo.feat_lookups[i].name_param_ptr=0;
      if (ginfo.feat_lookups[i].tag==CHR('s', 'i', 'z', 'e'))
	 size_params_ptr=aftell(g___);
      else if (ginfo.feat_lookups[i].tag >= CHR('s', 's', '0', '1')
	       && ginfo.feat_lookups[i].tag <= CHR('s', 's', '2', '0'))
	 ginfo.feat_lookups[i].name_param_ptr=aftell(g___);
      aput_int16_be_checked(0,g___);	/* No feature params (we'll come back for 'size') */
      aput_int16_be_checked(ginfo.feat_lookups[i].lcnt,g___);	/* this many lookups */
      for (j=0; j<ginfo.feat_lookups[i].lcnt; ++j)
	 aput_int16_be_checked(ginfo.feat_lookups[i].lookups[j]->lookup_index,g___);
      /* index of each lookup */
   }
   if (size_params_ptr != 0) {
      size_params_loc=aftell(g___);
      afseek(g___, size_params_ptr, SEEK_SET);
      /* When Adobe first released fonts containing the 'size' feature */
      /*  they did not follow the spec, and the offset to the size parameters */
      /*  was relative to the wrong location. They claim (Aug 2006) that */
      /*  this has been fixed. FF used to do what Adobe did. Many programs */
      /*  expect broken sizes tables now, but we don't supply them. */
      aput_int16_be_checked(size_params_loc-size_params_ptr,g___);
      afseek(g___, size_params_loc, SEEK_SET);
      aput_int16_be_checked(sf->design_size,g___);
      if (sf->fontstyle_id != 0 || sf->fontstyle_name != NULL) {
	 aput_int16_be_checked(sf->fontstyle_id,g___);
	 at->fontstyle_name_strid=at->next_strid++;
	 aput_int16_be_checked(at->fontstyle_name_strid,g___);
      } else {
	 aput_int16_be_checked(0,g___);
	 aput_int16_be_checked(0,g___);
      }
      aput_int16_be_checked(sf->design_range_bottom,g___);
      aput_int16_be_checked(sf->design_range_top,g___);
   }
   for (i=0; i<ginfo.fcnt; ++i) {
      if (ginfo.feat_lookups[i].name_param_ptr != 0 &&
	  (fn=findotffeatname(ginfo.feat_lookups[i].tag, sf)) != NULL) {
	 if (fn->nid==0)
	    fn->nid=at->next_strid++;
	 uint32_t name_param_loc=aftell(g___);

	 afseek(g___, ginfo.feat_lookups[i].name_param_ptr, SEEK_SET);
	 aput_int16_be_checked(
		  name_param_loc-ginfo.feat_lookups[i].name_param_ptr,g___);
	 afseek(g___, name_param_loc, SEEK_SET);
	 aput_int16_be_checked(0,g___);	/* Minor version number */
	 aput_int16_be_checked(fn->nid,g___);
      }
   }
   /* And that should finish all the features */

   /* so free the ginfo feature data */
   for (i=0; i<ginfo.fcnt; ++i)
      free(ginfo.feat_lookups[i].lookups);
   free(ginfo.feat_lookups);

/* Now the lookups */
   all=sf->gsplookups[is_gpos];
   for (cnt=0, otf=all; otf != NULL; otf=otf->next) {
      if (otf->lookup_index != -1)
	 ++cnt;
   }
   lookup_list_table_start=aftell(g___);
   afseek(g___, 8, SEEK_SET);
   aput_int16_be_checked(lookup_list_table_start,g___);
   afseek(g___, 0, SEEK_END);
   aput_int16_be_checked(cnt,g___);
   offset=2+2*cnt;	/* Offset to start of first lookup table from beginning of lookup list */
   for (otf=all; otf != NULL; otf=otf->next)
      if (otf->lookup_index != -1) {
	 aput_int16_be_checked(offset,g___);
	 for (scnt=0, sub=otf->subtables; sub != NULL; sub=sub->next) {
	    if (sub->subtable_offset==-1)
	       continue;
	    else if (sub->extra_subtables != NULL) {
	       for (i=0; sub->extra_subtables[i] != -1; ++i)
		  ++scnt;
	    } else
	       ++scnt;
	 }
	 otf->subcnt=scnt;
	 offset += 6+2*scnt;	/* 6 bytes header +2 per lookup */
	 if (otf->lookup_flags&pst_usemarkfilteringset)
	    offset += 2;	/* For mark filtering set, if used */
      }
   offset -= 2+2*cnt;
   /* now the lookup tables */
   /* do we need any extension sub-tables? */
   efile=g___FigureExtensionSubTables(all, offset, is_gpos);
   for (otf=all; otf != NULL; otf=otf->next)
      if (otf->lookup_index != -1) {
	 aput_int16_be_checked(!otf->needs_extension?(otf->lookup_type&0xff)
		 :is_gpos?9:7,g___);
	 aput_int16_be_checked((otf->lookup_flags&0xffff),g___);
	 aput_int16_be_checked(otf->subcnt,g___);
	 for (sub=otf->subtables; sub != NULL; sub=sub->next) {
	    if (sub->subtable_offset==-1)
	       continue;
	    else if (sub->extra_subtables != NULL) {
	       for (i=0; sub->extra_subtables[i] != -1; ++i)
		  aput_int16_be_checked(offset+sub->extra_subtables[i],g___);
	    } else
	       aput_int16_be_checked(offset+sub->subtable_offset,g___);

	    /* Offset to lookup data which is in the temp file */
	    /* we keep adjusting offset so it reflects the distance between */
	    /* here and the place where the temp file will start, and then */
	    /* we need to skip l->offset bytes in the temp file */
	    /* If it's a big GPOS/SUB table we may also need some extension */
	    /*  pointers, but FigureExtension will adjust for that */
	 }
	 offset -= 6+2*otf->subcnt;
	 if (otf->lookup_flags&pst_usemarkfilteringset) {
	    aput_int16_be_checked(otf->lookup_flags >> 16,g___);
	    offset -= 2;
	 }
      }

   buf=malloc(8096);
   if (efile != NULL) {
      afseek(efile,0,SEEK_SET);
      while ((i=afread(buf, 1, 8096, efile))>0)
	 afwrite(buf, 1, i, g___);
      afclose(efile);
   }
   afseek(lfile,0,SEEK_SET);
   while ((i=afread(buf, 1, 8096, lfile))>0)
      afwrite(buf, 1, i, g___);
   afclose(lfile);
   free(buf);
   for (otf=all; otf != NULL; otf=otf->next)
      if (otf->lookup_index != -1) {
	 for (sub=otf->subtables; sub != NULL; sub=sub->next) {
	    free(sub->extra_subtables);
	    sub->extra_subtables=NULL;
	 }
	 otf->needs_extension=false;
      }
   return (g___);
}

void otf_dumpgpos(struct alltabs *at, SplineFont *sf) {
   /* Open Type, bless its annoying little heart, doesn't store kern info */
   /*  in the kern table. Of course not, how silly of me to think it might */
   /*  be consistent. It stores it in the much more complicated gpos table */
   AnchorClass *ac;

   for (ac=sf->anchor; ac != NULL; ac=ac->next)
      ac->processed=false;

   at->gpos=dumpg___info(at, sf, true);
   if (at->gpos != NULL) {
      at->gposlen=aftell(at->gpos);
      if (at->gposlen&1)
	 aputc('\0', at->gpos);
      if ((at->gposlen+1)&2)
	 aput_int16_be_checked(0,at->gpos);
   }
}

void otf_dumpgsub(struct alltabs *at, SplineFont *sf) {
   /* substitutions such as: Ligatures, cjk vertical rotation replacement, */
   /*  arabic forms, small caps, ... */
   SFLigaturePrepare(sf);
   at->gsub=dumpg___info(at, sf, false);
   if (at->gsub != NULL) {
      at->gsublen=aftell(at->gsub);
      if (at->gsublen&1)
	 aputc('\0', at->gsub);
      if ((at->gsublen+1)&2)
	 aput_int16_be_checked(0,at->gsub);
   }
   SFLigatureCleanup(sf);
}

int LigCaretCnt(SplineChar * sc) {
   PST *pst;
   int j, cnt;

   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->type==pst_lcaret) {
	 if (sc->lig_caret_cnt_fixed)
	    return (pst->u.lcaret.cnt);
	 else {
	    /* only output non-zero carets */
	    cnt=0;
	    for (j=pst->u.lcaret.cnt-1; j >= 0; --j)
	       if (pst->u.lcaret.carets[j] != 0)
		  ++cnt;
	    return (cnt);
	 }
      }
   }
   return (0);
}

static void DumpLigCarets(AFILE *gdef,SplineChar *sc) {
   PST *pst;
   int i, j, offset, cnt;

   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->type==pst_lcaret)
	 break;
   }
   if (pst==NULL)
      return;
   cnt=LigCaretCnt(sc);
   if (cnt==0)
      return;

   if (SCRightToLeft(sc)) {
      for (i=0; i<pst->u.lcaret.cnt-1; ++i)
	 for (j=i+1; j<pst->u.lcaret.cnt; ++j)
	    if (pst->u.lcaret.carets[i]<pst->u.lcaret.carets[j]) {
	       int16_t temp=pst->u.lcaret.carets[i];

	       pst->u.lcaret.carets[i]=pst->u.lcaret.carets[j];
	       pst->u.lcaret.carets[j]=temp;
	    }
   } else {
      for (i=0; i<pst->u.lcaret.cnt-1; ++i)
	 for (j=i+1; j<pst->u.lcaret.cnt; ++j)
	    if (pst->u.lcaret.carets[i]>pst->u.lcaret.carets[j]) {
	       int16_t temp=pst->u.lcaret.carets[i];

	       pst->u.lcaret.carets[i]=pst->u.lcaret.carets[j];
	       pst->u.lcaret.carets[j]=temp;
	    }
   }

   aput_int16_be_checked(cnt,gdef);		/* this many carets */
   offset=sizeof(uint16_t)+sizeof(uint16_t)*cnt;
   for (i=0; i<cnt; ++i) {
      aput_int16_be_checked(offset,gdef);
      offset += 4;
   }
   for (i=0; i<pst->u.lcaret.cnt; ++i) {
      if (sc->lig_caret_cnt_fixed || pst->u.lcaret.carets[i] != 0) {
	 aput_int16_be_checked(1,gdef);	/* Format 1 */
	 aput_int16_be_checked(pst->u.lcaret.carets[i],gdef);
      }
   }
}

static int glyphnameinlist(char *haystack,char *name) {
   char *start, *pt;
   int ch, match, slen=strlen(name);

   for (pt=haystack;;) {
      while (*pt==' ')
	 ++pt;
      if (*pt=='\0')
	 return (false);
      start=pt;
      while (*pt != ' ' && *pt != '\0')
	 ++pt;
      if (pt-start==slen) {
	 ch=*pt;
	 *pt='\0';
	 match=strcmp(start, name);
	 *pt=ch;
	 if (match==0)
	    return (true);
      }
   }
}

static int ReferencedByGSUB(SplineChar *sc) {
   PST *pst;
   SplineFont *sf=sc->parent;
   int gid;
   SplineChar *testsc;
   char *name=sc->name;

   /* If it is itself a ligature it will be referenced by GSUB */
   /* (because we store ligatures on the glyph generated) */
   for (pst=sc->possub; pst != NULL; pst=pst->next)
      if (pst->type==pst_ligature)
	 return (true);

   for (gid=0; gid<sf->glyphcnt; ++gid)
      if ((testsc=sf->glyphs[gid]) != NULL) {
	 for (pst=testsc->possub; pst != NULL; pst=pst->next) {
	    if (pst->type==pst_substitution || pst->type==pst_alternate ||
		pst->type==pst_multiple) {
	       if (glyphnameinlist(pst->u.mult.components, name))
		  return (true);
	    }
	 }
      }
   return (false);
}

int gdefclass(SplineChar * sc) {
   PST *pst;
   AnchorPoint *ap;

   if (sc->glyph_class != 0)
      return (sc->glyph_class-1);

   if (strcmp(sc->name, ".notdef")==0)
      return (0);

   /* It isn't clear to me what should be done if a glyph is both a ligature */
   /*  and a mark (There are some greek accent ligatures, it is probably more */
   /*  important that they be indicated as marks). Here I chose mark rather  */
   /*  than ligature as the mark class is far more likely to be used */
   ap=sc->anchor;
   while (ap != NULL && (ap->type==at_centry || ap->type==at_cexit))
      ap=ap->next;
   if (ap != NULL && (ap->type==at_mark || ap->type==at_basemark))
      return (3);

   for (pst=sc->possub; pst != NULL; pst=pst->next) {
      if (pst->type==pst_ligature)
	 return (2);		/* Ligature */
   }

   /* I not quite sure what a componant glyph is. Probably something */
   /*  that is not in the cmap table and is referenced in other glyphs */
   /* (I've never seen it used by others) */
   /* (Note: No glyph in a CID font can be components as all CIDs mean */
   /*  something) (I think) */
   if (sc->unicodeenc==-1 && sc->dependents != NULL &&
       sc->parent->cidmaster != NULL && !ReferencedByGSUB(sc))
      return (4);
   else
      return (1);
}

void otf_dumpgdef(struct alltabs *at, SplineFont *sf) {
   /* In spite of what the open type docs say, this table does appear to be */
   /*  required (at least the glyph class def table) if we do mark to base */
   /*  positioning */
   /* I was wondering at the apperant contradiction: something can be both a */
   /*  base glyph and a ligature component, but it appears that the component */
   /*  class is unused and everything is a base unless it is a ligature or */
   /*  mark */
   /* All my example fonts ignore the attachment list subtable and the mark */
   /*  attach class def subtable, so I shall too */
   /* Ah. Some indic fonts need the mark attach class subtable for greater */
   /*  control of lookup flags */
   /* All my example fonts contain a ligature caret list subtable, which is */
   /*  empty. Odd, but perhaps important */
   int i, j, k, lcnt, needsclass;
   int pos, offset;
   int cnt, start, last, lastval;
   SplineChar **glyphs, *sc;

   /* Don't look in the cidmaster if we are only dumping one subfont */
   if (sf->cidmaster && sf->cidmaster->glyphs != NULL)
      sf=sf->cidmaster;
   else if (sf->mm != NULL)
      sf=sf->mm->normal;

   glyphs=NULL;
   for (k=0; k<2; ++k) {
      lcnt=0;
      needsclass=false;
      for (i=0; i<at->gi.gcnt; ++i)
	 if (at->gi.bygid[i] != -1) {
	    SplineChar *sc=sf->glyphs[at->gi.bygid[i]];

	    if (sc->glyph_class != 0 || gdefclass(sc) != 1)
	       needsclass=true;
	    if (LigCaretCnt(sc) != 0) {
	       if (glyphs != NULL)
		  glyphs[lcnt]=sc;
	       ++lcnt;
	    }
	 }
      if (lcnt==0)
	 break;
      if (glyphs != NULL)
	 break;
      glyphs=malloc((lcnt+1)*sizeof(SplineChar *));
      glyphs[lcnt]=NULL;
   }
   if (!needsclass && lcnt==0 && sf->mark_class_cnt==0
       && sf->mark_set_cnt==0)
      return;			/* No anchor positioning, no ligature carets */

   at->gdef=atmpfile();
   if (sf->mark_set_cnt==0)
      putlong(at->gdef, 0x00010000);	/* Version */
   else
      putlong(at->gdef, 0x00010002);	/* Version with mark sets */
   aput_int16_be_checked(needsclass?14:0,at->gdef);	/* glyph class defn table */
   aput_int16_be_checked(0,at->gdef);	/* attachment list table */
   aput_int16_be_checked(0,at->gdef);	/* ligature caret table (come back and fix up later) */
   aput_int16_be_checked(0,at->gdef);	/* mark attachment class table */
   aput_int16_be_checked(0,at->gdef);	/* mark attachment set table only meaningful if version is 0x10002, but doesn't hurt to output always */

   /* Glyph class subtable */
   if (needsclass) {
      /* Mark shouldn't conflict with anything */
      /* Ligature is more important than Base */
      /* Component is not used */
      /* ttx can't seem to support class format type 1 so let's output type 2 */
      for (j=0; j<2; ++j) {
	 cnt=0;
	 for (i=0; i<at->gi.gcnt; ++i)
	    if (at->gi.bygid[i] != -1) {
	       sc=sf->glyphs[at->gi.bygid[i]];
	       if (sc != NULL && sc->ttf_glyph != -1) {
		  lastval=gdefclass(sc);
		  start=last=i;
		  for (; i<at->gi.gcnt; ++i)
		     if (at->gi.bygid[i] != -1) {
			sc=sf->glyphs[at->gi.bygid[i]];
			if (gdefclass(sc) != lastval)
			   break;
			last=i;
		     }
		  --i;
		  if (lastval != 0) {
		     if (j==1) {
			aput_int16_be_checked(start,at->gdef);
			aput_int16_be_checked(last,at->gdef);
			aput_int16_be_checked(lastval,at->gdef);
		     }
		     ++cnt;
		  }
	       }
	    }
	 if (j==0) {
	    aput_int16_be_checked(2,at->gdef);	/* class format 2, range list by class */
	    aput_int16_be_checked(cnt,at->gdef);
	 }
      }
   }

   /* Ligature caret subtable. Always include this if we have a GDEF */
   pos=aftell(at->gdef);
   afseek(at->gdef, 8, SEEK_SET);	/* location of lig caret table offset */
   aput_int16_be_checked(pos,at->gdef);
   afseek(at->gdef, 0, SEEK_END);
   if (lcnt==0) {
      /* It always seems to be present, even if empty */
      aput_int16_be_checked(4,at->gdef);	/* Offset to (empty) coverage table */
      aput_int16_be_checked(0,at->gdef);	/* no ligatures */
      aput_int16_be_checked(2,at->gdef);	/* coverage table format 2 */
      aput_int16_be_checked(0,at->gdef);	/* no ranges in coverage table */
   } else {
      pos=aftell(at->gdef);	/* coverage location */
      aput_int16_be_checked(0,at->gdef);	/* Offset to coverage table (fix up later) */
      aput_int16_be_checked(lcnt,at->gdef);
      offset=2*lcnt+4;
      for (i=0; i<lcnt; ++i) {
	 aput_int16_be_checked(offset,at->gdef);
	 offset += 2+6*LigCaretCnt(glyphs[i]);
      }
      for (i=0; i<lcnt; ++i)
	 DumpLigCarets(at->gdef, glyphs[i]);
      offset=aftell(at->gdef);
      afseek(at->gdef, pos, SEEK_SET);
      aput_int16_be_checked(offset-pos,at->gdef);
      afseek(at->gdef, 0, SEEK_END);
      dumpcoveragetable(at->gdef, glyphs);
   }

   /* Mark Attachment Class Subtable */
   if (sf->mark_class_cnt>0) {
      uint16_t *mclasses =
	 ClassesFromNames(sf, sf->mark_classes, sf->mark_class_cnt,
			  at->maxp.numGlyphs, NULL, false);
      pos=aftell(at->gdef);
      afseek(at->gdef, 10, SEEK_SET);	/* location of mark attach table offset */
      aput_int16_be_checked(pos,at->gdef);
      afseek(at->gdef, 0, SEEK_END);
      DumpClass(at->gdef, mclasses, at->maxp.numGlyphs);
      free(mclasses);
   }

   /* Mark Attachment Class Subtable */
   if (sf->mark_set_cnt>0) {
      pos=aftell(at->gdef);
      afseek(at->gdef, 12, SEEK_SET);	/* location of mark attach table offset */
      aput_int16_be_checked(pos,at->gdef);
      afseek(at->gdef, 0, SEEK_END);
      aput_int16_be_checked(1,at->gdef);	/* Version number */
      aput_int16_be_checked(sf->mark_set_cnt,at->gdef);
      for (i=0; i<sf->mark_set_cnt; ++i)
	 putlong(at->gdef, 0);
      for (i=0; i<sf->mark_set_cnt; ++i) {
	 int here=aftell(at->gdef);

	 afseek(at->gdef, pos+4+4*i, SEEK_SET);
	 putlong(at->gdef, here-pos);
	 afseek(at->gdef, 0, SEEK_END);
	 glyphs=OrderedGlyphsFromNames(sf, sf->mark_sets[i]);
	 dumpcoveragetable(at->gdef, glyphs);
	 free(glyphs);
      }
   }

   at->gdeflen=aftell(at->gdef);
   if (at->gdeflen&1)
      aputc('\0', at->gdef);
   if ((at->gdeflen+1)&2)
      aput_int16_be_checked(0,at->gdef);
}

/******************************************************************************/
/* ******************************* MATH Table ******************************* */
/* ********************** (Not strictly OpenType yet) *********************** */
/******************************************************************************/
enum math_bits { mb_constants=0x01, mb_italic=0x02, mb_topaccent=0x04,
   mb_extended=0x08, mb_mathkern=0x10, mb_vertvariant=0x20,
   mb_horizvariant=0x40,
   mb_all=0x7f,
   mb_gi=(mb_italic|mb_topaccent|mb_extended|mb_mathkern),
   mb_gv=(mb_vertvariant|mb_horizvariant)
};

static int MathBits(struct alltabs *at,SplineFont *sf) {
   int i, gid, ret;
   SplineChar *sc;

   ret=sf->MATH?mb_constants:0;

   for (i=0; i<at->gi.gcnt; ++i) {
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL) {
	 if (sc->italic_correction != TEX_UNDEF)
	    ret |= mb_italic;
	 if (sc->top_accent_horiz != TEX_UNDEF)
	    ret |= mb_topaccent;
	 if (sc->is_extended_shape)
	    ret |= mb_extended;
	 if (sc->mathkern != NULL)
	    ret |= mb_mathkern;
	 if (sc->vert_variants != NULL)
	    ret |= mb_vertvariant;
	 if (sc->horiz_variants != NULL)
	    ret |= mb_horizvariant;
	 if (ret==mb_all)
	    return (mb_all);
      }
   }
   return (ret);
}

static void ttf_math_dump_italic_top(AFILE *mathf,struct alltabs *at,
				     SplineFont *sf, int is_italic) {
   int i, gid, len;
   SplineChar *sc, **glyphs;
   uint32_t coverage_pos, coverage_table;
   uint32_t devtab_offset;
   DeviceTable *devtab;

   /* Figure out our glyph list (and count) */
   for (i=len=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL)
	 if ((is_italic && sc->italic_correction != TEX_UNDEF)
	     || (!is_italic && sc->top_accent_horiz != TEX_UNDEF))
	    ++len;
   glyphs=malloc((len+1)*sizeof(SplineChar *));
   for (i=len=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL)
	 if ((is_italic && sc->italic_correction != TEX_UNDEF)
	     || (!is_italic && sc->top_accent_horiz != TEX_UNDEF))
	    glyphs[len++]=sc;
   glyphs[len]=NULL;

   coverage_pos=aftell(mathf);
   aput_int16_be_checked(0,mathf);		/* Coverage table, return to this */
   aput_int16_be_checked(len,mathf);
   devtab_offset=4+4*len;
   for (i=0; i<len; ++i) {
      aput_int16_be_checked(
	       is_italic?glyphs[i]->italic_correction:glyphs[i]->
	       top_accent_horiz,mathf);
      devtab =
	 is_italic?glyphs[i]->italic_adjusts:glyphs[i]->
	 top_accent_adjusts;
      if (devtab != NULL) {
	 aput_int16_be_checked(devtab_offset,mathf);
	 devtab_offset += DevTabLen(devtab);
      } else
	 aput_int16_be_checked(0,mathf);
   }
   for (i=0; i<len; ++i) {
      devtab =
	 is_italic?glyphs[i]->italic_adjusts:glyphs[i]->
	 top_accent_adjusts;
      if (devtab != NULL)
	 dumpgposdevicetable(mathf, devtab);
   }
   if (devtab_offset != aftell(mathf)-coverage_pos)
      ErrorMsg(2,"Actual end did not match expected end in %s table, expected=%d, actual=%d\n",
	  is_italic?"italic":"top accent", devtab_offset,
	  aftell(mathf)-coverage_pos);
   coverage_table=aftell(mathf);
   afseek(mathf, coverage_pos, SEEK_SET);
   aput_int16_be_checked(coverage_table-coverage_pos,mathf);
   afseek(mathf, coverage_table, SEEK_SET);
   dumpcoveragetable(mathf, glyphs);
   free(glyphs);
}

static void ttf_math_dump_extended(AFILE *mathf,struct alltabs *at,
				   SplineFont *sf) {
   int i, gid, len;
   SplineChar *sc, **glyphs;

   for (i=len=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL)
	 if (sc->is_extended_shape)
	    ++len;
   glyphs=malloc((len+1)*sizeof(SplineChar *));
   for (i=len=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL)
	 if (sc->is_extended_shape)
	    glyphs[len++]=sc;
   glyphs[len]=NULL;
   dumpcoveragetable(mathf, glyphs);
   free(glyphs);
}

static int mkv_len(struct mathkernvertex *mkv) {
   return (2+8*mkv->cnt-4);
}

static int ttf_math_dump_mathkernvertex(AFILE *mathf,
					struct mathkernvertex *mkv,
					int devtab_pos) {
   int i;
   uint32_t here=aftell(mathf);

   aput_int16_be_checked(mkv->cnt-1,mathf);

   for (i=0; i<mkv->cnt-1; ++i) {
      aput_int16_be_checked(mkv->mkd[i].height,mathf);
      if (mkv->mkd[i].height_adjusts != NULL) {
	 aput_int16_be_checked(devtab_pos-here,mathf);
	 devtab_pos += DevTabLen(mkv->mkd[i].height_adjusts);
      } else
	 aput_int16_be_checked(0,mathf);
   }
   for (i=0; i<mkv->cnt; ++i) {
      aput_int16_be_checked(mkv->mkd[i].kern,mathf);
      if (mkv->mkd[i].kern_adjusts != NULL) {
	 aput_int16_be_checked(devtab_pos-here,mathf);
	 devtab_pos += DevTabLen(mkv->mkd[i].kern_adjusts);
      } else
	 aput_int16_be_checked(0,mathf);
   }
   return (devtab_pos);
}

static void ttf_math_dump_mathkerndevtab(AFILE *mathf,
					 struct mathkernvertex *mkv) {
   int i;

   for (i=0; i<mkv->cnt-1; ++i)
      if (mkv->mkd[i].height_adjusts != NULL)
	 dumpgposdevicetable(mathf, mkv->mkd[i].height_adjusts);
   for (i=0; i<mkv->cnt; ++i)
      if (mkv->mkd[i].kern_adjusts != NULL)
	 dumpgposdevicetable(mathf, mkv->mkd[i].kern_adjusts);
}

static void ttf_math_dump_mathkern(AFILE *mathf,struct alltabs *at,
				   SplineFont *sf) {
   int i, gid, len;
   SplineChar *sc, **glyphs;
   uint32_t coverage_pos, coverage_table, kr_pos, midpos2;

   /* Figure out our glyph list (and count) */
   for (i=len=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL)
	 if (sc->mathkern != NULL)
	    ++len;
   glyphs=malloc((len+1)*sizeof(SplineChar *));
   for (i=len=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL)
	 if (sc->mathkern != NULL)
	    glyphs[len++]=sc;
   glyphs[len]=NULL;

   coverage_pos=aftell(mathf);
   aput_int16_be_checked(0,mathf);		/* Coverage table, return to this */
   aput_int16_be_checked(len,mathf);
   kr_pos=coverage_pos+4+8*len;
   for (i=0; i<len; ++i) {
      struct mathkern *mk=glyphs[i]->mathkern;

      if (mk->top_right.cnt==0)
	 aput_int16_be_checked(0,mathf);
      else {
	 aput_int16_be_checked(kr_pos-coverage_pos,mathf);
	 kr_pos += mkv_len(&mk->top_right);
      }
      if (mk->top_left.cnt==0)
	 aput_int16_be_checked(0,mathf);
      else {
	 aput_int16_be_checked(kr_pos-coverage_pos,mathf);
	 kr_pos += mkv_len(&mk->top_left);
      }
      if (mk->bottom_right.cnt==0)
	 aput_int16_be_checked(0,mathf);
      else {
	 aput_int16_be_checked(kr_pos-coverage_pos,mathf);
	 kr_pos += mkv_len(&mk->bottom_right);
      }
      if (mk->bottom_left.cnt==0)
	 aput_int16_be_checked(0,mathf);
      else {
	 aput_int16_be_checked(kr_pos-coverage_pos,mathf);
	 kr_pos += mkv_len(&mk->bottom_left);
      }
   }
   if (aftell(mathf) != coverage_pos+4+8*len)
      ErrorMsg(2,"Actual midpoint1 did not match expected midpoint1 in mathkern table, expected=%d, actual=%d\n",
	  coverage_pos+4+8*len, aftell(mathf));

   midpos2=kr_pos;
   for (i=0; i<len; ++i) {
      struct mathkern *mk=glyphs[i]->mathkern;

      if (mk->top_right.cnt != 0)
	 kr_pos=ttf_math_dump_mathkernvertex(mathf, &mk->top_right, kr_pos);
      if (mk->top_left.cnt != 0)
	 kr_pos=ttf_math_dump_mathkernvertex(mathf, &mk->top_left, kr_pos);
      if (mk->bottom_right.cnt != 0)
	 kr_pos =
	    ttf_math_dump_mathkernvertex(mathf, &mk->bottom_right, kr_pos);
      if (mk->bottom_left.cnt != 0)
	 kr_pos =
	    ttf_math_dump_mathkernvertex(mathf, &mk->bottom_left, kr_pos);
   }
   if (aftell(mathf) != midpos2)
      ErrorMsg(2,"Actual midpoint2 did not match expected midpoint2 in mathkern table, expected=%d, actual=%d\n",
	  midpos2, aftell(mathf));

   for (i=0; i<len; ++i) {
      struct mathkern *mk=glyphs[i]->mathkern;

      if (mk->top_right.cnt != 0)
	 ttf_math_dump_mathkerndevtab(mathf, &mk->top_right);
      if (mk->top_left.cnt != 0)
	 ttf_math_dump_mathkerndevtab(mathf, &mk->top_left);
      if (mk->bottom_right.cnt != 0)
	 ttf_math_dump_mathkerndevtab(mathf, &mk->bottom_right);
      if (mk->bottom_left.cnt != 0)
	 ttf_math_dump_mathkerndevtab(mathf, &mk->bottom_left);
   }
   if (kr_pos != aftell(mathf))
      ErrorMsg(2,"Actual end did not match expected end in mathkern table, expected=%d, actual=%d\n",
	  kr_pos, aftell(mathf));

   coverage_table=aftell(mathf);
   afseek(mathf, coverage_pos, SEEK_SET);
   aput_int16_be_checked(coverage_table-coverage_pos,mathf);
   afseek(mathf, coverage_table, SEEK_SET);
   dumpcoveragetable(mathf, glyphs);
   free(glyphs);
}

static int gv_len(SplineFont *sf,struct glyphvariants *gv) {
   char *pt, *start;
   int ch, cnt;
   SplineChar *sc;

   if (gv==NULL || (gv->variants==NULL && gv->part_cnt==0))
      return (0);
   if (gv->variants==NULL)
      return (4);		/* No variants, but we've got parts to assemble */
   cnt=0;
   for (start=gv->variants;;) {
      while (*start==' ')
	 ++start;
      if (*start=='\0')
	 return (4+4*cnt);	/* MathGlyphConstructionTable */
      for (pt=start; *pt != ' ' && *pt != '\0'; ++pt);
      ch=*pt;
      *pt='\0';
      sc=SFGetChar(sf, -1, start);
      *pt=ch;
      if (sc != NULL)
	 ++cnt;
      start=pt;
   }
}

static int gvc_len(struct glyphvariants *gv) {
   if (gv->part_cnt==0)
      return (0);

   return (6+10*gv->part_cnt);
}

static uint32_t ttf_math_dump_mathglyphconstructiontable(AFILE *mathf,
						       struct glyphvariants
						       *gv, SplineFont *sf,
						       uint32_t pos, int is_v) {
   char *pt, *start;
   int ch, cnt;
   SplineChar *sc;
   uint32_t here=aftell(mathf);
   DBounds b;

   aput_int16_be_checked(gv->part_cnt==0?0:pos-here,mathf);
   if (gv->variants==NULL) {
      aput_int16_be_checked(0,mathf);
   } else {
      cnt=0;
      for (start=gv->variants;;) {
	 while (*start==' ')
	    ++start;
	 if (*start=='\0')
	    break;
	 for (pt=start; *pt != ' ' && *pt != '\0'; ++pt);
	 ch=*pt;
	 *pt='\0';
	 sc=SFGetChar(sf, -1, start);
	 *pt=ch;
	 if (sc != NULL)
	    ++cnt;
	 start=pt;
      }
      aput_int16_be_checked(cnt,mathf);
      for (start=gv->variants;;) {
	 while (*start==' ')
	    ++start;
	 if (*start=='\0')
	    break;
	 for (pt=start; *pt != ' ' && *pt != '\0'; ++pt);
	 ch=*pt;
	 *pt='\0';
	 sc=SFGetChar(sf, -1, start);
	 *pt=ch;
	 if (sc != NULL) {
	    aput_int16_be_checked(sc->ttf_glyph,mathf);
	    SplineCharFindBounds(sc, &b);
	    /* Don't ask me why I have a plus one here. In the MS font */
	    /*  CambriaMath all of these values are one more than I would */
	    /*  expect */
	    if (is_v)
	       aput_int16_be_checked(b.maxy-b.miny+1,mathf);
	    else
	       aput_int16_be_checked(b.maxx-b.minx+1,mathf);
	 }
	 start=pt;
      }
   }
   return (pos+gvc_len(gv));
}

static uint32_t ttf_math_dump_mathglyphassemblytable(AFILE *mathf,
						   struct glyphvariants *gv,
						   SplineFont *sf,
						   uint32_t devtab_pos) {
   SplineChar *sc;
   uint32_t here=aftell(mathf);
   int i;

   if (gv->part_cnt==0)
      return (devtab_pos);
   aput_int16_be_checked(gv->italic_correction,mathf);
   if (gv->italic_adjusts != NULL) {
      aput_int16_be_checked(devtab_pos-here,mathf);
      devtab_pos += DevTabLen(gv->italic_adjusts);
   } else
      aput_int16_be_checked(0,mathf);
   aput_int16_be_checked(gv->part_cnt,mathf);
   for (i=0; i<gv->part_cnt; ++i) {
      sc=SFGetChar(sf, -1, gv->parts[i].component);
      if (sc==NULL)
	 aput_int16_be_checked(0,mathf);	/* .notdef */
      else
	 aput_int16_be_checked(sc->ttf_glyph,mathf);
      aput_int16_be_checked(gv->parts[i].startConnectorLength,mathf);
      aput_int16_be_checked(gv->parts[i].endConnectorLength,mathf);
      aput_int16_be_checked(gv->parts[i].fullAdvance,mathf);
      aput_int16_be_checked(gv->parts[i].is_extender,mathf);
   }
   return (devtab_pos);
}

static void ttf_math_dump_glyphvariant(AFILE *mathf,struct alltabs *at,
				       SplineFont *sf) {
   int i, gid, vlen, hlen;
   SplineChar *sc, **vglyphs, **hglyphs;
   uint32_t coverage_pos, coverage_table, offset, pos, assembly_pos;

   /* Figure out our glyph list (and count) */
   for (i=vlen=hlen=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL) {
	 if (sc->vert_variants != NULL)
	    ++vlen;
	 if (sc->horiz_variants != NULL)
	    ++hlen;
      }

   vglyphs=malloc((vlen+1)*sizeof(SplineChar *));
   hglyphs=malloc((hlen+1)*sizeof(SplineChar *));
   for (i=vlen=hlen=0; i<at->gi.gcnt; ++i)
      if ((gid=at->gi.bygid[i]) != -1 && (sc=sf->glyphs[gid]) != NULL) {
	 if (sc->vert_variants != NULL)
	    vglyphs[vlen++]=sc;
	 if (sc->horiz_variants != NULL)
	    hglyphs[hlen++]=sc;
      }
   vglyphs[vlen]=NULL;
   hglyphs[hlen]=NULL;

   aput_int16_be_checked(
	    sf->MATH ==
	    NULL?(sf->ascent +
		    sf->descent)/50:sf->MATH->MinConnectorOverlap,mathf);
   coverage_pos=aftell(mathf);
   aput_int16_be_checked(0,mathf);		/* Vertical Coverage table, return to this */
   aput_int16_be_checked(0,mathf);		/* Horizontal Coverage table, return to this */
   aput_int16_be_checked(vlen,mathf);
   aput_int16_be_checked(hlen,mathf);
   offset=5*2+vlen*2+hlen*2;
   for (i=0; i<vlen; ++i) {
      aput_int16_be_checked(offset,mathf);
      offset += gv_len(sf, vglyphs[i]->vert_variants);
   }
   for (i=0; i<hlen; ++i) {
      aput_int16_be_checked(offset,mathf);
      offset += gv_len(sf, hglyphs[i]->horiz_variants);
   }
   assembly_pos=pos=(coverage_pos-2)+offset;
   for (i=0; i<vlen; ++i) {
      /*uint32_t start=aftell(mathf); */
      pos=ttf_math_dump_mathglyphconstructiontable(mathf,
						     vglyphs[i]->
						     vert_variants, sf, pos,
						     true);
      /*if (aftell(mathf)-start != gv_len(sf,vglyphs[i]->vert_variants)) */
      /*ErrorMsg(2,"v gv_len incorrect\n"); */
   }
   for (i=0; i<hlen; ++i) {
      /*uint32_t start=aftell(mathf); */
      pos=ttf_math_dump_mathglyphconstructiontable(mathf,
						     hglyphs[i]->
						     horiz_variants, sf, pos,
						     false);
      /*if (aftell(mathf)-start != gv_len(sf,hglyphs[i]->horiz_variants)) */
      /*ErrorMsg(2,"h gv_len incorrect: %s\n", hglyphs[i]->name); */
   }
   if (aftell(mathf) != assembly_pos)
      ErrorMsg(2,"assembly tables at wrong place\n");

   for (i=0; i<vlen; ++i)
      pos=ttf_math_dump_mathglyphassemblytable(mathf,
						 vglyphs[i]->vert_variants,
						 sf, pos);
   for (i=0; i<hlen; ++i)
      pos=ttf_math_dump_mathglyphassemblytable(mathf,
						 hglyphs[i]->horiz_variants,
						 sf, pos);
   for (i=0; i<vlen; ++i)
      if (vglyphs[i]->vert_variants->part_cnt != 0 &&
	  vglyphs[i]->vert_variants->italic_adjusts != NULL)
	 dumpgposdevicetable(mathf,
			     vglyphs[i]->vert_variants->italic_adjusts);
   for (i=0; i<hlen; ++i)
      if (hglyphs[i]->horiz_variants->part_cnt != 0 &&
	  hglyphs[i]->horiz_variants->italic_adjusts != NULL)
	 dumpgposdevicetable(mathf,
			     hglyphs[i]->horiz_variants->italic_adjusts);
   if (vlen != 0) {
      coverage_table=aftell(mathf);
      afseek(mathf, coverage_pos, SEEK_SET);
      aput_int16_be_checked(coverage_table-(coverage_pos-2),mathf);
      afseek(mathf, coverage_table, SEEK_SET);
      dumpcoveragetable(mathf, vglyphs);
   }
   free(vglyphs);
   if (hlen != 0) {
      coverage_table=aftell(mathf);
      afseek(mathf, coverage_pos+2, SEEK_SET);
      aput_int16_be_checked(coverage_table-(coverage_pos-2),mathf);
      afseek(mathf, coverage_table, SEEK_SET);
      dumpcoveragetable(mathf, hglyphs);
   }
   free(hglyphs);
}

void otf_dump_math(struct alltabs *at, SplineFont *sf) {
   AFILE *mathf;
   int i;
   uint32_t devtab_offsets[60], const_start, gi_start, v_start;
   int bits=MathBits(at, sf);

   if (sf->MATH==NULL)
      return;

   at->math=mathf=atmpfile();

   putlong(mathf, 0x00010000);	/* Version 1 */
   aput_int16_be_checked(10,mathf);		/* Offset to constants */
   aput_int16_be_checked(0,mathf);		/* GlyphInfo, fix later */
   aput_int16_be_checked(0,mathf);		/* Variants, fix later */

   /* Start on constants */
   memset(devtab_offsets, 0, sizeof(devtab_offsets));
   const_start=aftell(mathf);
   for (i=0; math_constants_descriptor[i].script_name != NULL; ++i) {
      int16_t *pos =
	 (int16_t *) (((char *) (sf->MATH)) +
		    math_constants_descriptor[i].offset);
      if (pos==(int16_t *)&sf->MATH->MinConnectorOverlap)
	 continue;		/* Actually lives in the Variant table, not here */
      aput_int16_be_checked(*pos,mathf);
      if (math_constants_descriptor[i].devtab_offset != -1) {
	 devtab_offsets[i]=aftell(mathf);
	 aput_int16_be_checked(0,mathf);	/* Fix up later if we support device tables */
      }
   }
   for (i=0; math_constants_descriptor[i].script_name != NULL; ++i) {
      int16_t *pos =
	 (int16_t *) (((char *) (sf->MATH)) +
		    math_constants_descriptor[i].offset);
      DeviceTable **devtab =
	 (DeviceTable **) (((char *) (sf->MATH)) +
			   math_constants_descriptor[i].devtab_offset);
      if (pos==(int16_t *)&sf->MATH->MinConnectorOverlap)
	 continue;		/* Actually lives in the Variant table, not here */
      if (math_constants_descriptor[i].devtab_offset >= 0 && *devtab != NULL) {
	 uint32_t here=aftell(mathf);

	 afseek(mathf, devtab_offsets[i], SEEK_SET);
	 aput_int16_be_checked(here-const_start,mathf);
	 afseek(mathf, here, SEEK_SET);
	 dumpgposdevicetable(mathf, *devtab);
      }
   }

   /* The spec does not say this can be NULL */
   if (1 /* bits&mb_gi */ ) {
      gi_start=aftell(mathf);
      afseek(mathf, 6, SEEK_SET);
      aput_int16_be_checked(gi_start,mathf);
      afseek(mathf, gi_start, SEEK_SET);

      aput_int16_be_checked(0,mathf);	/* Italics correction */
      aput_int16_be_checked(0,mathf);	/* top accent */
      aput_int16_be_checked(0,mathf);	/* is extended shape */
      aput_int16_be_checked(0,mathf);	/* math kern info */

      if (bits&mb_italic) {
	 v_start=aftell(mathf);
	 afseek(mathf, gi_start, SEEK_SET);
	 aput_int16_be_checked(v_start-gi_start,mathf);
	 afseek(mathf, v_start, SEEK_SET);

	 ttf_math_dump_italic_top(mathf, at, sf, true);
      }

      if (bits&mb_topaccent) {
	 v_start=aftell(mathf);
	 afseek(mathf, gi_start+2, SEEK_SET);
	 aput_int16_be_checked(v_start-gi_start,mathf);
	 afseek(mathf, v_start, SEEK_SET);

	 ttf_math_dump_italic_top(mathf, at, sf, false);
      }

      if (bits&mb_extended) {
	 v_start=aftell(mathf);
	 afseek(mathf, gi_start+4, SEEK_SET);
	 aput_int16_be_checked(v_start-gi_start,mathf);
	 afseek(mathf, v_start, SEEK_SET);

	 ttf_math_dump_extended(mathf, at, sf);
      }

      if (bits&mb_mathkern) {
	 v_start=aftell(mathf);
	 afseek(mathf, gi_start+6, SEEK_SET);
	 aput_int16_be_checked(v_start-gi_start,mathf);
	 afseek(mathf, v_start, SEEK_SET);

	 ttf_math_dump_mathkern(mathf, at, sf);
      }
   }

   /* The spec does not say this can be NULL */
   if (1 /* bits&mb_gv */ ) {
      v_start=aftell(mathf);
      afseek(mathf, 8, SEEK_SET);
      aput_int16_be_checked(v_start,mathf);
      afseek(mathf, v_start, SEEK_SET);

      ttf_math_dump_glyphvariant(mathf, at, sf);
   }

   at->mathlen=aftell(mathf);
   if (aftell(mathf)&1)
      aputc('\0', mathf);
   if (aftell(mathf)&2)
      aput_int16_be_checked(0,mathf);
}

struct taglist {
   uint32_t tag;
   struct taglist *next;
};

static int taglistcompar(const void *_cv1,const void *_cv2) {
   const struct taglist *const *tl1=_cv1, *const *tl2=_cv2;

   if ((*tl1)->tag==(*tl2)->tag)
      return (0);
   if ((*tl1)->tag>(*tl2)->tag)
      return (1);

   return (-1);
}

static int langlistcompar(const void *_cv1,const void *_cv2) {
   const struct taglist *const *tl1=_cv1, *const *tl2=_cv2;

   if ((*tl1)->tag==(*tl2)->tag)
      return (0);
   if ((*tl1)->tag==DEFAULT_LANG)
      return (-1);
   if ((*tl2)->tag==DEFAULT_LANG)
      return (1);
   if ((*tl1)->tag>(*tl2)->tag)
      return (1);

   return (-1);
}

static struct taglist *sorttaglist(struct taglist *list,
				   int (*compar) (const void *,
						  const void *)) {
   struct taglist *t, **array;
   int i, cnt;

   if (list==NULL || list->next==NULL)
      return (list);

   for (t=list, cnt=0; t != NULL; t=t->next, ++cnt);
   array=malloc(cnt * sizeof(struct taglist *));
   for (t=list, cnt=0; t != NULL; t=t->next, ++cnt)
      array[cnt]=t;
   qsort(array, cnt, sizeof(struct taglist *), compar);
   for (i=1; i<cnt; ++i)
      array[i-1]->next=array[i];
   array[cnt-1]->next=NULL;
   list=array[0];
   free(array);
   return (list);
}

static void _base_sort(struct Base *base) {
   /* Sort the base lines. Which can reorder the def_baseline index in the */
   /*  script, and the baseline_pos lists */
   /* Sort the script list */
   /* Sort the language lists in each script */
   /* Sort the feature lists in each language */
   int i, j, pos, tag;
   struct basescript *bs;
   struct baselangextent *langs;

   if (base==NULL)
      return;

   if (base->baseline_cnt != 0) {
      for (i=0; i<base->baseline_cnt; ++i)
	 for (j=i+1; j<base->baseline_cnt; ++j) {
	    if (base->baseline_tags[i]>base->baseline_tags[j]) {
	       tag=base->baseline_tags[i];
	       base->baseline_tags[i]=base->baseline_tags[j];
	       base->baseline_tags[j]=tag;
	       for (bs=base->scripts; bs != NULL; bs=bs->next) {
		  if (bs->def_baseline==i)
		     bs->def_baseline=j;
		  else if (bs->def_baseline==j)
		     bs->def_baseline=i;
		  pos=bs->baseline_pos[i];
		  bs->baseline_pos[i]=bs->baseline_pos[j];
		  bs->baseline_pos[j]=pos;
	       }
	    }
	 }
   }
   base->scripts =
      (struct basescript *) sorttaglist((struct taglist *) base->scripts,
					taglistcompar);
   for (bs=base->scripts; bs != NULL; bs=bs->next) {
      bs->langs =
	 (struct baselangextent *) sorttaglist((struct taglist *) bs->langs,
					       langlistcompar);
      for (langs=bs->langs; langs != NULL; langs=langs->next)
	 langs->features =
	    (struct baselangextent *) sorttaglist((struct taglist *) langs->
						  features, taglistcompar);
   }
}

void SFBaseSort(SplineFont *sf) {
   _base_sort(sf->horiz_base);
   _base_sort(sf->vert_base);
}

static void dump_minmax(AFILE *basef,struct baselangextent *bl) {
   struct baselangextent *fl;
   int fcnt;

   aput_int16_be_checked(bl->descent,basef);
   aput_int16_be_checked(bl->ascent,basef);
   for (fl=bl->features, fcnt=0; fl != NULL; fl=fl->next, ++fcnt);
   aput_int16_be_checked(fcnt,basef);
   for (fl=bl->features; fl != NULL; fl=fl->next) {
      putlong(basef, fl->lang);	/* feature tag really */
      aput_int16_be_checked(fl->descent,basef);
      aput_int16_be_checked(fl->ascent,basef);
   }
}

void otf_dumpbase(struct alltabs *at, SplineFont *sf) {
   AFILE *basef;
   int i, j, cnt, lcnt;
   uint32_t here, bsl;
   struct basescript *bs;
   struct baselangextent *bl, *dflt;
   int offset;

   if (sf->horiz_base==NULL && sf->vert_base==NULL)
      return;

   SFBaseSort(sf);

   at->base=basef=atmpfile();

   putlong(basef, 0x00010000);	/* Version 1 */
   aput_int16_be_checked(0,basef);		/* offset to horizontal baselines, fill in later */
   aput_int16_be_checked(0,basef);		/* offset to vertical baselines, fill in later */

   for (i=0; i<2; ++i) {
      struct Base *base=i==0?sf->horiz_base:sf->vert_base;

      if (base==NULL)
	 continue;
      here=aftell(basef);
      afseek(basef, 4+2*i, SEEK_SET);
      aput_int16_be_checked(here-0,basef);
      afseek(basef, here, SEEK_SET);

      /* axis table */
      aput_int16_be_checked(base->baseline_cnt==0?0:4,basef);
      aput_int16_be_checked(base->baseline_cnt==0?4 :
	       4+2+4*base->baseline_cnt,basef);

      if (base->baseline_cnt != 0) {
	 /* BaseTagList table */
	 aput_int16_be_checked(base->baseline_cnt,basef);
	 for (j=0; j<base->baseline_cnt; ++j)
	    putlong(basef, base->baseline_tags[j]);
      }

      /* BaseScriptList table */
      bsl=aftell(basef);
      for (bs=base->scripts, cnt=0; bs != NULL; bs=bs->next, ++cnt);
      aput_int16_be_checked(cnt,basef);
      for (bs=base->scripts; bs != NULL; bs=bs->next) {
	 putlong(basef, bs->script);
	 aput_int16_be_checked(0,basef);
      }

      /* BaseScript table */
      for (bs=base->scripts, cnt=0; bs != NULL; bs=bs->next, ++cnt) {
	 uint32_t bst=aftell(basef);

	 afseek(basef, bsl+2+6*cnt+4, SEEK_SET);
	 aput_int16_be_checked(bst-bsl,basef);
	 afseek(basef, bst, SEEK_SET);

	 for (bl=bs->langs, dflt=NULL, lcnt=0; bl != NULL;
	      bl=bl->next) {
	    if (bl->lang==DEFAULT_LANG)
	       dflt=bl;
	    else
	       ++lcnt;
	 }
	 offset=6+6*lcnt;
	 aput_int16_be_checked(base->baseline_cnt==0?0:offset,basef);
	 if (base->baseline_cnt != 0)
	    offset += 4+2*base->baseline_cnt+4*base->baseline_cnt;
	 aput_int16_be_checked(dflt==NULL?0:offset,basef);
	 aput_int16_be_checked(lcnt,basef);
	 for (bl=bs->langs; bl != NULL; bl=bl->next)
	    if (bl->lang != DEFAULT_LANG) {
	       putlong(basef, bl->lang);
	       aput_int16_be_checked(0,basef);
	    }

	 /* Base Values table */
	 if (base->baseline_cnt != 0) {
	    offset=4+2*base->baseline_cnt;
	    aput_int16_be_checked(bs->def_baseline,basef);
	    aput_int16_be_checked(base->baseline_cnt,basef);
	    for (j=0; j<base->baseline_cnt; ++j) {
	       aput_int16_be_checked(offset,basef);
	       offset += 2*2;
	    }
	    for (j=0; j<base->baseline_cnt; ++j) {
	       aput_int16_be_checked(1,basef);	/* format 1 */
	       aput_int16_be_checked(bs->baseline_pos[j],basef);
	    }
	 }

	 if (dflt != NULL)
	    dump_minmax(basef, dflt);
	 for (bl=bs->langs, dflt=NULL, lcnt=0; bl != NULL;
	      bl=bl->next)
	    if (bl->lang != DEFAULT_LANG) {
	       uint32_t here=aftell(basef);

	       afseek(basef, bst+6+6*lcnt+4, SEEK_SET);
	       aput_int16_be_checked(here-bst,basef);
	       afseek(basef, here, SEEK_SET);
	       dump_minmax(basef, bl);
	    }
      }
   }

   at->baselen=aftell(basef);
   if (aftell(basef)&1)
      aputc('\0', basef);
   if (aftell(basef)&2)
      aput_int16_be_checked(0,basef);
}

static int jscriptsort(const void *_s1,const void *_s2) {
   const Justify *const *__s1=(const Justify * const *) _s1;
   const Justify *const *__s2=(const Justify * const *) _s2;
   const Justify *s1=*__s1;
   const Justify *s2=*__s2;

   if (s1->script>s2->script)
      return (1);
   else if (s1->script<s2->script)
      return (-1);
   else
      return (0);
}

static int jlangsort(const void *_s1,const void *_s2) {
   const struct jstf_lang *const *__s1 =
      (const struct jstf_lang * const *) _s1;
   const struct jstf_lang *const *__s2 =
      (const struct jstf_lang * const *) _s2;
   const struct jstf_lang *s1=*__s1;
   const struct jstf_lang *s2=*__s2;

   if (s1->lang==s2->lang)
      return (0);

   if (s1->lang==DEFAULT_LANG)
      return (-1);
   if (s2->lang==DEFAULT_LANG)
      return (1);

   if (s1->lang>s2->lang)
      return (1);
   else
      return (-1);
}

static int lookup_order(const void *_s1,const void *_s2) {
   const OTLookup *const *__s1=(const OTLookup * const *) _s1;
   const OTLookup *const *__s2=(const OTLookup * const *) _s2;
   const OTLookup *s1=*__s1;
   const OTLookup *s2=*__s2;

   if (s1->lookup_index>s2->lookup_index)
      return (1);
   else if (s1->lookup_index<s2->lookup_index)
      return (-1);
   else
      return (0);
}

static void SFJstfSort(SplineFont *sf) {
   /* scripts must be ordered */
   /* languages must be ordered within scripts */
   /* lookup lists must be ordered */
   Justify *jscript, **scripts;
   int i, cnt, lmax;
   struct jstf_lang **langs;

   for (cnt=0, jscript=sf->justify; jscript != NULL;
	++cnt, jscript=jscript->next);
   if (cnt>1) {
      scripts=malloc(cnt * sizeof(Justify *));
      for (i=0, jscript=sf->justify; jscript != NULL;
	   ++i, jscript=jscript->next)
	 scripts[i]=jscript;
      qsort(scripts, cnt, sizeof(Justify *), jscriptsort);
      for (i=1; i<cnt; ++i)
	 scripts[i-1]->next=scripts[i];
      scripts[cnt-1]->next=NULL;
      sf->justify=scripts[0];
      free(scripts);
   }

   langs=NULL;
   lmax=0;
   for (jscript=sf->justify; jscript != NULL; jscript=jscript->next) {
      struct jstf_lang *jlang;

      for (cnt=0, jlang=jscript->langs; jlang != NULL;
	   ++cnt, jlang=jlang->next);
      if (cnt>1) {
	 if (cnt>lmax)
	    langs =
	       realloc(langs, (lmax=cnt+10)*sizeof(struct jstf_lang *));
	 for (i=0, jlang=jscript->langs; jlang != NULL;
	      ++i, jlang=jlang->next)
	    langs[i]=jlang;
	 qsort(langs, cnt, sizeof(Justify *), jlangsort);
	 for (i=1; i<cnt; ++i)
	    langs[i-1]->next=langs[i];
	 langs[cnt-1]->next=NULL;
	 jscript->langs=langs[0];
      }
   }
   free(langs);

   /* don't bother to sort the lookup lists yet. We need to separate them into */
   /* GPOS/GSUB first, might as well do it all at once later */
}

static void jstf_SplitTables(OTLookup ** mixed,OTLookup *** _SUB,
			     OTLookup *** _POS) {
   /* (later is now, see comment above) */
   /* mixed contains both gsub and gpos lookups. put them into their own */
   /* lists, and then sort them */
   int cnt, s, p;
   OTLookup **SUB, **POS;

   if (mixed==NULL || mixed[0]==NULL) {
      *_SUB=NULL;
      *_POS=NULL;
      return;
   }

   for (cnt=0; mixed[cnt] != NULL; ++cnt);
   SUB=malloc((cnt+1)*sizeof(OTLookup *));
   POS=malloc((cnt+1)*sizeof(OTLookup *));
   for (cnt=s=p=0; mixed[cnt] != NULL; ++cnt) {
      if (mixed[cnt]->lookup_index==-1)	/* Not actually used */
	 continue;
      if (mixed[cnt]->lookup_type >= gpos_start)
	 POS[p++]=mixed[cnt];
      else
	 SUB[s++]=mixed[cnt];
   }
   POS[p]=SUB[s]=NULL;

   if (p>1)
      qsort(POS, p, sizeof(OTLookup *), lookup_order);
   if (s>1)
      qsort(SUB, s, sizeof(OTLookup *), lookup_order);
   if (p==0) {
      free(POS);
      POS=NULL;
   }
   if (s==0) {
      free(SUB);
      SUB=NULL;
   }
   *_SUB=SUB;
   *_POS=POS;
}

static uint32_t jstf_dumplklist(AFILE *jstf,OTLookup ** PS,uint32_t base) {
   uint32_t here;
   int i;

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

   here=aftell(jstf);
   for (i=0; PS[i] != NULL; ++i);
   aput_int16_be_checked(i,jstf);		/* Lookup cnt */
   for (i=0; PS[i] != NULL; ++i)
      aput_int16_be_checked(PS[i]->lookup_index,jstf);
   free(PS);
   return (here-base);
}

static uint32_t jstf_dumpmaxlookups(AFILE *jstf,SplineFont *sf,
				  struct alltabs *at, OTLookup ** maxes,
				  uint32_t base) {
   uint32_t here, lbase;
   int cnt, i;
   int scnt, j;
   struct lookup_subtable *sub;

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

   for (cnt=i=0; maxes[i] != NULL; ++i)
      if (!maxes[i]->unused)
	 ++cnt;
   if (cnt==0)
      return (0);

   if ((here=aftell(jstf))<0)
      return (0);

   aput_int16_be_checked(cnt,jstf);
   for (i=0; maxes[i] != NULL; ++i)
      if (!maxes[i]->unused)
	 aput_int16_be_checked(0,jstf);
   for (cnt=i=0; maxes[i] != NULL; ++i)
      if (!maxes[i]->unused) {
	 if ((lbase=aftell(jstf))<0)
	    return (0);
	 afseek(jstf, here+2+2*cnt, SEEK_SET);
	 aput_int16_be_checked(lbase-here,jstf);
	 afseek(jstf, lbase, SEEK_SET);

	 aput_int16_be_checked(maxes[i]->lookup_type-gpos_start,jstf);
	 aput_int16_be_checked(maxes[i]->lookup_flags,jstf);

	 for (scnt=0, sub=maxes[i]->subtables; sub != NULL;
	      sub=sub->next)
	    if (!sub->unused)
	       ++scnt;
	 aput_int16_be_checked(scnt,jstf);
	 for (j=0; j<scnt; ++j)
	    aput_int16_be_checked(0,jstf);
	 /* I don't think extension lookups get a MarkAttachmentType, I guess */
	 /*  that inherits from the parent? */

	 otf_dumpALookup(jstf, maxes[i], sf, at);
	 afseek(jstf, lbase+6, SEEK_SET);
	 for (sub=maxes[i]->subtables; sub != NULL; sub=sub->next)
	    if (!sub->unused)
	       aput_int16_be_checked(sub->subtable_offset-lbase,jstf);
	 ++cnt;
      }

   return (here-base);
}

void otf_dumpjstf(struct alltabs *at, SplineFont *sf) {
   AFILE *jstf;
   int i, cnt, lcnt, offset;
   uint32_t here, base;
   Justify *jscript;
   struct jstf_lang *jlang;

   if (sf->justify==NULL)
      return;

   SFJstfSort(sf);
   for (jscript=sf->justify, cnt=0; jscript != NULL;
	jscript=jscript->next, ++cnt);

   at->jstf=jstf=atmpfile();

   putlong(jstf, 0x00010000);	/* Version 1 */
   aput_int16_be_checked(cnt,jstf);		/* script count */
   for (jscript=sf->justify; jscript != NULL; jscript=jscript->next) {
      putlong(jstf, jscript->script);
      aput_int16_be_checked(0,jstf);	/* Come back to this later */
   }
   for (jscript=sf->justify, cnt=0; jscript != NULL;
	jscript=jscript->next, ++cnt) {
      base=aftell(jstf);
      if (base>0xffff)
	 ErrorMsg(2,"Offset in JSTF table is too big.  The font will not work.\n");
      afseek(jstf, 6+6*cnt+4, SEEK_SET);
      aput_int16_be_checked(base,jstf);
      afseek(jstf, base, SEEK_SET);

      aput_int16_be_checked(0,jstf);	/* extender glyphs */
      aput_int16_be_checked(0,jstf);	/* default lang */
      for (jlang=jscript->langs, lcnt=0; jlang != NULL;
	   jlang=jlang->next, ++lcnt);
      if (lcnt>0 && jscript->langs->lang==DEFAULT_LANG)
	 --lcnt;
      aput_int16_be_checked(lcnt,jstf);	/* count of non-default languages */
      jlang=jscript->langs;
      if (jlang != NULL && jlang->lang==DEFAULT_LANG)
	 jlang=jlang->next;
      for (; jlang != NULL; jlang=jlang->next) {
	 putlong(jstf, jlang->lang);
	 aput_int16_be_checked(0,jstf);	/* Come back to this later */
      }

      if (jscript->extenders != NULL) {
	 SplineChar **glyphs;
	 int gcnt, g;

	 here=aftell(jstf);
	 afseek(jstf, base, SEEK_SET);
	 aput_int16_be_checked(here-base,jstf);
	 afseek(jstf, here, SEEK_SET);

	 glyphs=OrderedGlyphsFromNames(sf, jscript->extenders);
	 if (glyphs==NULL)
	    gcnt=0;
	 else
	    for (gcnt=0; glyphs[gcnt] != NULL; ++gcnt);
	 aput_int16_be_checked(gcnt,jstf);
	 for (g=0; g<gcnt; ++g)
	    aput_int16_be_checked(glyphs[g]->ttf_glyph,jstf);
	 free(glyphs);
      }

      offset=0;
      for (jlang=jscript->langs, lcnt=0; jlang != NULL;
	   jlang=jlang->next, ++lcnt) {
	 here=aftell(jstf);
	 if (jlang->lang==DEFAULT_LANG) {
	    afseek(jstf, base+2, SEEK_SET);
	    offset=-6;
	 } else
	    afseek(jstf, base+offset+10+lcnt*6, SEEK_SET);
	 aput_int16_be_checked(here-base,jstf);
	 afseek(jstf, here, SEEK_SET);

	 aput_int16_be_checked(jlang->cnt,jstf);
	 for (i=0; i<jlang->cnt; ++i)
	    aput_int16_be_checked(0,jstf);
	 for (i=0; i<jlang->cnt; ++i) {
	    OTLookup **enSUB, **enPOS, **disSUB, **disPOS;
	    uint32_t enSUBoff, enPOSoff, disSUBoff, disPOSoff, maxOff;
	    uint32_t pbase;

	    pbase=aftell(jstf);
	    afseek(jstf, here+2+i*2, SEEK_SET);
	    aput_int16_be_checked(pbase-here,jstf);
	    afseek(jstf, pbase, SEEK_SET);

	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);
	    aput_int16_be_checked(0,jstf);

	    jstf_SplitTables(jlang->prios[i].enableShrink, &enSUB, &enPOS);
	    jstf_SplitTables(jlang->prios[i].disableShrink, &disSUB, &disPOS);
	    enSUBoff=jstf_dumplklist(jstf, enSUB, pbase);
	    disSUBoff=jstf_dumplklist(jstf, disSUB, pbase);
	    enPOSoff=jstf_dumplklist(jstf, enPOS, pbase);
	    disPOSoff=jstf_dumplklist(jstf, disPOS, pbase);
	    maxOff =
	       jstf_dumpmaxlookups(jstf, sf, at, jlang->prios[i].maxShrink,
				   pbase);

	    afseek(jstf, pbase, SEEK_SET);
	    aput_int16_be_checked(enSUBoff,jstf);
	    aput_int16_be_checked(disSUBoff,jstf);
	    aput_int16_be_checked(enPOSoff,jstf);
	    aput_int16_be_checked(disPOSoff,jstf);
	    aput_int16_be_checked(maxOff,jstf);

	    afseek(jstf, 0, SEEK_END);
	    jstf_SplitTables(jlang->prios[i].enableExtend, &enSUB, &enPOS);
	    jstf_SplitTables(jlang->prios[i].disableExtend, &disSUB, &disPOS);
	    enSUBoff=jstf_dumplklist(jstf, enSUB, pbase);
	    disSUBoff=jstf_dumplklist(jstf, disSUB, pbase);
	    enPOSoff=jstf_dumplklist(jstf, enPOS, pbase);
	    disPOSoff=jstf_dumplklist(jstf, disPOS, pbase);
	    maxOff =
	       jstf_dumpmaxlookups(jstf, sf, at, jlang->prios[i].maxExtend,
				   pbase);

	    afseek(jstf, pbase+10, SEEK_SET);
	    aput_int16_be_checked(enSUBoff,jstf);
	    aput_int16_be_checked(disSUBoff,jstf);
	    aput_int16_be_checked(enPOSoff,jstf);
	    aput_int16_be_checked(disPOSoff,jstf);
	    aput_int16_be_checked(maxOff,jstf);
	    afseek(jstf, 0, SEEK_END);
	 }
      }
   }

   afseek(jstf, 0, SEEK_END);
   at->jstflen=aftell(jstf);
   if (aftell(jstf)&1)
      aputc('\0', jstf);
   if (aftell(jstf)&2)
      aput_int16_be_checked(0,jstf);
}

void otf_dump_dummydsig(struct alltabs *at, SplineFont *sf) {
   AFILE *dsigf;

   /* I think the DSIG table is a big crock. At best the most it can do is */
   /*  tell you that the font hasn't changed since it was signed. It gives */
   /*  no guarantee that the data are reasonable. I think it's stupid. */
   /* I think it is even more stupid that MS choses this useless table as a */
   /*  mark of whether a ttf font is OpenType or not. */
   /* But users want their fonts to show up as OpenType under MS. And I'm  */
   /*  told an empty DSIG table works for that. So... a truely pointless   */
   /*  instance of a pointless table. I suppose that's a bit ironic. */

   at->dsigf=dsigf=atmpfile();
   putlong(dsigf, 0x00000001);	/* Standard version (and why isn't it 0x10000 like everything else?) */
   aput_int16_be_checked(0,dsigf);		/* No signatures in my signature table */
   aput_int16_be_checked(0,dsigf);		/* No flags */

   at->dsiglen=aftell(dsigf);
   if (aftell(dsigf)&1)
      aputc('\0', dsigf);
   if (aftell(dsigf)&2)
      aput_int16_be_checked(0,dsigf);
}
