/* $Id: tottfvar.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 "fontanvil.h"
#include "ttf.h"
#include <math.h>
#include <ustring.h>

static int PtNumbersAreSet(SplineChar *sc) {
   struct splinecharlist *dep;

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

   if (sc->ttf_instrs != NULL)
      return (true);
   for (dep=sc->dependents; dep != NULL; dep=dep->next)
      if (dep->sc->ttf_instrs != NULL)
	 return (true);

   return (false);
}

static int AssignPtNumbers(MMSet *mm,int gid) {
   /* None of the instances has fixed point numbers. Make them match */
   int cnt=0;
   SplineSet **ss;
   SplinePoint **sp;
   int i;
   int allavg, alllines, stillmore, ret=true;

   ss=malloc((mm->instance_count+1)*sizeof(SplineSet *));
   sp=malloc((mm->instance_count+1)*sizeof(SplinePoint *));
   for (i=0; i<mm->instance_count; ++i)
      ss[i]=mm->instances[i]->glyphs[gid]->layers[ly_fore].splines;
   ss[i]=mm->normal->glyphs[gid]->layers[ly_fore].splines;

   if (ss[0]==NULL) {
      stillmore=false;
      for (i=0; i <= mm->instance_count; ++i)
	 if (ss[i] != NULL)
	    stillmore=true;
      if (stillmore)
	 return (false);
      return (true);
   } else {
      stillmore=true;
      for (i=0; i <= mm->instance_count; ++i)
	 if (ss[i]==NULL)
	    stillmore=false;
      if (!stillmore)
	 return (false);
   }

   while (1) {
      for (i=0; i <= mm->instance_count; ++i)
	 sp[i]=ss[i]->first;
      while (1) {
	 allavg=alllines=true;
	 for (i=0; i <= mm->instance_count; ++i) {
	    if (!RealNear
		(sp[i]->me.x, (sp[i]->nextcp.x+sp[i]->prevcp.x)/2)
		|| !RealNear(sp[i]->me.y,
			     (sp[i]->nextcp.y+sp[i]->prevcp.y)/2))
	       allavg=false;
	    if (!sp[i]->nonextcp)
	       alllines=false;
	 }
	 if (sp[0]==ss[0]->first)
	    allavg=false;
	 for (i=0; i <= mm->instance_count; ++i) {
	    if (allavg)
	       sp[i]->ttfindex=0xffff;
	    else
	       sp[i]->ttfindex=cnt;
	 }
	 if (!allavg)
	    ++cnt;
	 for (i=0; i <= mm->instance_count; ++i) {
	    if (alllines)
	       sp[i]->nextcpindex=0xffff;
	    else
	       sp[i]->nextcpindex=cnt;
	 }
	 if (!alllines)
	    ++cnt;

	 if (sp[0]->next==NULL) {
	    stillmore=false;
	    for (i=1; i <= mm->instance_count; ++i)
	       if (sp[i]->next != NULL)
		  stillmore=true;
	    if (stillmore)
	       ret=false;
	    break;
	 }
	 for (i=1; i <= mm->instance_count; ++i)
	    if (sp[i]->next==NULL)
	       stillmore=false;
	 if (!stillmore) {
	    ret=false;
	    break;
	 }
	 sp[0]=sp[0]->next->to;
	 for (i=1; i <= mm->instance_count; ++i)
	    sp[i]=sp[i]->next->to;
	 if (sp[0]==ss[0]->first) {
	    stillmore=false;
	    for (i=1; i <= mm->instance_count; ++i)
	       if (sp[i] != ss[i]->first)
		  stillmore=true;
	    if (stillmore)
	       ret=false;
	    break;
	 }
	 for (i=1; i <= mm->instance_count; ++i) {
	    if (sp[i]==ss[i]->first)
	       stillmore=false;
	 }
	 if (!stillmore) {
	    ret=false;
	    break;
	 }
      }
      if (!ret)
	 break;
      stillmore=true;
      for (i=0; i <= mm->instance_count; ++i)
	 ss[i]=ss[i]->next;
      if (ss[0]==NULL) {
	 stillmore=false;
	 for (i=1; i <= mm->instance_count; ++i)
	    if (ss[i] != NULL)
	       stillmore=true;
	 if (stillmore)
	    ret=true;
	 break;
      }
      for (i=1; i <= mm->instance_count; ++i)
	 if (ss[i]==NULL)
	    stillmore=false;
      if (!stillmore) {
	 ret=true;
	 break;
      }
   }
   free(ss);
   free(sp);
   return (ret);
}

static int MatchPoints(SplineFont *sffixed,SplineFont *sfother,int gid) {
   SplineChar *fixed, *other;
   SplineSet *ss1, *ss2;
   SplinePoint *sp1, *sp2;

   fixed=sffixed->glyphs[gid];
   other=sfother->glyphs[gid];

   if (PtNumbersAreSet(other)) {
      /* Point numbers must match exactly, both are fixed */
      for (ss1=fixed->layers[ly_fore].splines,
	   ss2=other->layers[ly_fore].splines;
	   ss1 != NULL && ss2 != NULL; ss1=ss1->next, ss2=ss2->next) {
	 for (sp1=ss1->first, sp2=ss2->first;;) {
	    if (sp1->ttfindex != sp2->ttfindex ||
		sp1->nextcpindex != sp2->nextcpindex)
	       return (false);
	    if (sp1->next==NULL || sp2->next==NULL) {
	       if (sp1->next != NULL || sp2->next != NULL)
		  return (false);
	       break;
	    }
	    sp1=sp1->next->to;
	    sp2=sp2->next->to;
	    if (sp1==ss1->first || sp2==ss2->first) {
	       if (sp1 != ss1->first || sp2 != ss2->first)
		  return (false);
	       break;
	    }
	 }
      }
      return (ss1==NULL && ss2==NULL);
   } else {
      for (ss1=fixed->layers[ly_fore].splines,
	   ss2=other->layers[ly_fore].splines;
	   ss1 != NULL && ss2 != NULL; ss1=ss1->next, ss2=ss2->next) {
	 for (sp1=ss1->first, sp2=ss2->first;;) {
	    if (sp1->ttfindex != 0xffff)
	       sp2->ttfindex=sp1->ttfindex;
	    else if (!RealNear(sp2->me.x, (sp2->nextcp.x+sp2->prevcp.x)/2)
		     || !RealNear(sp2->me.y,
				  (sp2->nextcp.y+sp2->prevcp.y)/2))
	       return (false);
	    else
	       sp2->ttfindex=0xffff;
	    if (sp1->nextcpindex != 0xffff)
	       sp2->nextcpindex=sp1->nextcpindex;
	    else if (!sp2->nonextcp)
	       return (false);
	    else
	       sp2->nextcpindex=0xffff;
	    if (sp1->next==NULL || sp2->next==NULL) {
	       if (sp1->next != NULL || sp2->next != NULL)
		  return (false);
	       break;
	    }
	    sp1=sp1->next->to;
	    sp2=sp2->next->to;
	    if (sp1==ss1->first || sp2==ss2->first) {
	       if (sp1 != ss1->first || sp2 != ss2->first)
		  return (false);
	       break;
	    }
	 }
      }
      return (ss1==NULL && ss2==NULL);
   }
}

int ContourPtNumMatch(MMSet * mm, int gid) {
   SplineFont *sf;
   int i;

   if (!mm->apple)
      return (false);

   if (gid >= mm->normal->glyphcnt)
      return (false);
   if (!SCWorthOutputting(mm->normal->glyphs[gid])) {
      for (i=0; i<mm->instance_count; ++i) {
	 if (gid >= mm->instances[i]->glyphcnt)
	    return (false);
	 if (SCWorthOutputting(mm->instances[i]->glyphs[gid]))
	    return (false);
      }
      return (true);		/* None is not worth outputting, and that's ok, they match */
   } else {
      for (i=0; i<mm->instance_count; ++i) {
	 if (gid >= mm->instances[i]->glyphcnt)
	    return (false);
	 if (!SCWorthOutputting(mm->instances[i]->glyphs[gid]))
	    return (false);
      }
      /* All are worth outputting */
   }

   if (mm->normal->glyphs[gid]->layers[ly_fore].refs != NULL
       && mm->normal->glyphs[gid]->layers[ly_fore].splines != NULL)
      return (false);
   for (i=0; i<mm->instance_count; ++i) {
      if (mm->instances[i]->glyphs[gid]->layers[ly_fore].refs != NULL
	  && mm->instances[i]->glyphs[gid]->layers[ly_fore].splines != NULL)
	 return (false);
   }
   if (mm->normal->glyphs[gid]->layers[ly_fore].refs != NULL) {
      RefChar *r;
      int cnt, c;

      for (r=mm->normal->glyphs[gid]->layers[ly_fore].refs, cnt=0;
	   r != NULL; r=r->next)
	 ++cnt;
      for (i=0; i<mm->instance_count; ++i) {
	 for (r=mm->instances[i]->glyphs[gid]->layers[ly_fore].refs, c=0;
	      r != NULL; r=r->next)
	    ++c;
	 if (c != cnt)
	    return (false);
      }
   }

   sf=NULL;
   if (PtNumbersAreSet(mm->normal->glyphs[gid]))
      sf=mm->normal;
   else {
      for (i=0; i<mm->instance_count; ++i) {
	 if (PtNumbersAreSet(mm->instances[i]->glyphs[gid])) {
	    sf=mm->instances[i];
	    break;
	 }
      }
   }
   if (sf==NULL)
      /* No instance has fixed points. Make sure all fonts are consistent */
      return (AssignPtNumbers(mm, gid));

   if (sf != mm->normal && !MatchPoints(sf, mm->normal, gid))
      return (false);
   for (i=0; i<mm->instance_count; ++i)
      if (sf != mm->instances[i]) {
	 if (!MatchPoints(sf, mm->instances[i], gid))
	    return (false);
      }
   return (true);
}

static int SCPointCount(SplineChar *sc) {
   int ptcnt=0;
   RefChar *r;

   ptcnt=SSTtfNumberPoints(sc->layers[ly_fore].splines);
   for (r=sc->layers[ly_fore].refs; r != NULL; r=r->next)
      ++ptcnt;
   return (ptcnt);
}

int16_t **SCFindDeltas(MMSet * mm, int gid, int *_ptcnt) {
   /* When figuring out the deltas the first thing we must do is figure */
   /*  out each point's number */
   int i, j, k, l, cnt, ptcnt;
   int16_t **deltas;
   SplineSet *ss1, *ss2;
   SplinePoint *sp1, *sp2;
   RefChar *r1, *r2;

   if (!ContourPtNumMatch(mm, gid))
      return (NULL);
   if (!SCWorthOutputting(mm->normal->glyphs[gid]))
      return (NULL);

   *_ptcnt=ptcnt=SCPointCount(mm->normal->glyphs[gid])+4;
   deltas=malloc(2*mm->instance_count * sizeof(int16_t *));
   for (i=0; i<2*mm->instance_count; ++i)
      deltas[i]=calloc(ptcnt, sizeof(int16_t));
   for (i=0; i<mm->instance_count; ++i) {
      for (ss1=mm->normal->glyphs[gid]->layers[ly_fore].splines,
	   ss2=mm->instances[i]->glyphs[gid]->layers[ly_fore].splines;
	   ss1 != NULL && ss2 != NULL; ss1=ss1->next, ss2=ss2->next) {
	 for (sp1=ss1->first, sp2=ss2->first;;) {
	    if (sp1->ttfindex != 0xffff) {
	       deltas[2*i][sp1->ttfindex] =
		  rint(sp2->me.x)-rint(sp1->me.x);
	       deltas[2*i+1][sp1->ttfindex] =
		  rint(sp2->me.y)-rint(sp1->me.y);
	    }
	    if (sp1->nextcpindex != 0xffff) {
	       deltas[2*i][sp1->nextcpindex] =
		  rint(sp2->nextcp.x)-rint(sp1->nextcp.x);
	       deltas[2*i+1][sp1->nextcpindex] =
		  rint(sp2->nextcp.y)-rint(sp1->nextcp.y);
	    }
	    if (sp1->next==NULL)
	       break;
	    sp1=sp1->next->to;
	    sp2=sp2->next->to;
	    if (sp1==ss1->first)
	       break;
	 }
      }
      for (cnt=0,
	   r1=mm->normal->glyphs[gid]->layers[ly_fore].refs,
	   r2=mm->instances[i]->glyphs[gid]->layers[ly_fore].refs;
	   r1 != NULL && r2 != NULL; r1=r1->next, r2=r2->next, ++cnt) {
	 deltas[2*i][cnt]=r2->transform[4]-r1->transform[4];
	 deltas[2*i+1][cnt]=r2->transform[5]-r1->transform[5];
      }
      /* Phantom points */
      deltas[2*i][ptcnt-4]=0;
      deltas[2*i+1][ptcnt-4]=0;	/* lbearing */
      deltas[2*i][ptcnt-3] =
	 mm->instances[i]->glyphs[gid]->width -
	 mm->normal->glyphs[gid]->width;
      deltas[2*i+1][ptcnt-3]=0;	/* horizontal advance */
      deltas[2*i][ptcnt-2]=0;
      deltas[2*i+1][ptcnt-2]=0;	/* top bearing */
      deltas[2*i][ptcnt-1]=0;	/* vertical advance */
      deltas[2*i+1][ptcnt-1]=mm->instances[i]->glyphs[gid]->vwidth-mm->normal->glyphs[gid]->vwidth;	/* horizontal advance */
   }

   /* Ok, each delta now contains the difference between the instance[i] points */
   /*  and the base points. But that isn't good enough. We must subtract */
   /*  [0,1] and [1,0] from [1,1], and then subtract [1,1,0] [1,0,1] [0,1,1] */
   /*  from [1,1,1] and so on (also [-1,0] from [-1,1], etc.) */
   for (j=1; j<mm->axis_count; ++j) {
      for (i=0; i<mm->instance_count; ++i) {
	 for (k=cnt=0; k<mm->axis_count; ++k)
	    if (mm->positions[i * mm->axis_count+k] != 0)
	       ++cnt;
	 if (cnt==j) {
	    for (l=0; l<mm->instance_count; ++l)
	       if (l != i) {
		  for (k=0; k<mm->axis_count; ++k)
		     if (mm->positions[i * mm->axis_count+k] != 0 &&
			 mm->positions[l * mm->axis_count+k] !=
			 mm->positions[i * mm->axis_count+k])
			break;
		  if (k==mm->axis_count) {
		     for (k=0; k<ptcnt; ++k) {
			deltas[2*l][k] -= deltas[2*i][k];
			deltas[2*l+1][k] -= deltas[2*i+1][k];
		     }
		  }
	       }
	 }
      }
   }

   /* If all variants of the glyph are the same, no point in having a gvar */
   /*  entry for it */
   for (i=0; i<mm->instance_count; ++i) {
      for (j=0; j<ptcnt; ++j)
	 if (deltas[i][j] != 0)
	    break;
      if (j != ptcnt)
	 break;
   }
   if (i==mm->instance_count) {
      /* All zeros */
      for (i=0; i<mm->instance_count; ++i)
	 free(deltas[i]);
      free(deltas);
      return (NULL);
   }

   return (deltas);
}

int16_t **CvtFindDeltas(MMSet * mm, int *_ptcnt) {
   int i, j, k, l, cnt, ptcnt;
   int16_t **deltas;
   struct ttf_table *cvt, *icvt;

   for (cvt=mm->normal->ttf_tables;
	cvt != NULL && cvt->tag != CHR('c', 'v', 't', ' '); cvt=cvt->next);

   if (cvt==NULL)
      return (NULL);

   icvt=NULL;
   for (i=0; i<mm->instance_count; ++i)
      if ((icvt=mm->instances[i]->ttf_tables) != NULL)
	 break;
   if (icvt==NULL)		/* No other cvt tables => no variation */
      return (NULL);

   *_ptcnt=ptcnt=cvt->len/2;
   deltas=calloc(mm->instance_count, sizeof(int16_t *));
   for (i=0; i<mm->instance_count; ++i)
      if ((icvt=mm->instances[i]->ttf_tables) != NULL) {
	 deltas[i]=calloc(ptcnt, sizeof(int16_t));
	 for (j=0; j<ptcnt; ++j)
	    deltas[i][j] =
	       memushort(icvt->data, icvt->len,
			 sizeof(uint16_t)*j)-memushort(cvt->data, cvt->len,
							 sizeof(uint16_t)*j);
      }

   /* Ok, each delta now contains the difference between the instance[i] points */
   /*  and the base points. But that isn't good enough. We must subtract */
   /*  [0,1] and [1,0] from [1,1], and then subtract [1,1,0] [1,0,1] [0,1,1] */
   /*  from [1,1,1] and so on (also [-1,0] from [-1,1], etc.) */
   for (j=1; j<mm->axis_count; ++j) {
      for (i=0; i<mm->instance_count; ++i)
	 if (deltas[i] != NULL) {
	    for (k=cnt=0; k<mm->axis_count; ++k)
	       if (mm->positions[i * mm->axis_count+k] != 0)
		  ++cnt;
	    if (cnt==j) {
	       for (l=0; l<mm->instance_count; ++l)
		  if (l != i && deltas[l] != NULL) {
		     for (k=0; k<mm->axis_count; ++k)
			if (mm->positions[i * mm->axis_count+k] != 0 &&
			    mm->positions[l * mm->axis_count+k] !=
			    mm->positions[i * mm->axis_count+k])
			   break;
		     if (k==mm->axis_count) {
			for (k=0; k<ptcnt; ++k)
			   deltas[l][k] -= deltas[i][k];
		     }
		  }
	    }
	 }
   }

   /* If all variants of the cvt are the same, no point in having a gvar */
   /*  entry for it */
   for (i=0; i<mm->instance_count; ++i)
      if (deltas[i] != NULL) {
	 for (j=0; j<ptcnt; ++j)
	    if (deltas[i][j] != 0)
	       break;
	 if (j==ptcnt) {
	    free(deltas[i]);
	    deltas[i]=NULL;
	 }
      }
   for (i=0; i<mm->instance_count; ++i)
      if (deltas[i] != NULL)
	 break;
   if (i==mm->instance_count) {
      /* All zeros */
      free(deltas);
      return (NULL);
   }

   return (deltas);
}

static void ttf_dumpcvar(struct alltabs *at,MMSet *mm) {
   int16_t **deltas;
   int ptcnt, cnt, pcnt;
   int i, j, rj, big;
   int tuple_size;
   uint32_t start, end;
   uint16_t *pts;

   deltas=CvtFindDeltas(mm, &ptcnt);
   for (i=cnt=0; i<mm->instance_count; ++i)
      if (deltas[i] != NULL)
	 ++cnt;
   if (cnt==0) {
      free(deltas);
      return;
   }

   tuple_size=4+2*mm->axis_count;
   at->cvar=atmpfile();
   putlong(at->cvar, 0x00010000);	/* Format */
   aput_int16_be_checked(cnt,at->cvar);	/* Number of instances with cvt tables (tuple count of interesting tuples) */
   aput_int16_be_checked(8+cnt * tuple_size,at->cvar);	/* Offset to data */

   for (i=0; i<mm->instance_count; ++i)
      if (deltas[i] != NULL) {
	 aput_int16_be_checked(0,at->cvar);	/* tuple data size, figure out later */
	 aput_int16_be_checked(0xa000,at->cvar);	/* tuple coords follow, private points in data */
	 for (j=0; j<mm->axis_count; ++j)
	    aput_int16_be_checked(
		     rint(16384*mm->positions[i * mm->axis_count+j]),at->cvar);
      }
   if (aftell(at->cvar) != 8+cnt * tuple_size)
      ErrorMsg(2,"Data offset wrong\n");

   for (i=cnt=0; i<mm->instance_count; ++i)
      if (deltas[i] != NULL) {
	 start=aftell(at->cvar);
	 for (j=pcnt=0; j<ptcnt; ++j)
	    if (deltas[i][j] != 0)
	       ++pcnt;
	 pts=malloc(pcnt * sizeof(uint16_t));
	 for (j=pcnt=0; j<ptcnt; ++j)
	    if (deltas[i][j] != 0)
	       pts[pcnt++]=j;

	 if (pcnt>0x7f) {
	    aputc(0x80|(pcnt >> 8), at->cvar);
	    aputc(pcnt&0xff, at->cvar);
	 } else
	    aputc(pcnt, at->cvar);
	 for (j=0; j<pcnt;) {
	    big=pts[j] >= 0x80?0x80:0;
	    for (rj=j+1; rj<j+0x80 && rj<pcnt && !big; ++rj)
	       if (pts[rj]-pts[rj-1] >= 0x80)
		  big=0x80;

	    aputc((rj-j-1)|big, at->cvar);
	    if (big) {
	       aput_int16_be_checked(pts[j],at->cvar);
	       for (++j; j<rj; ++j)
		  aput_int16_be_checked(pts[j]-pts[j-1],at->cvar);
	    } else {
	       aputc(pts[j], at->cvar);
	       for (++j; j<rj; ++j)
		  aputc(pts[j]-pts[j-1], at->cvar);
	    }
	 }
	 /* Now output the corresponding deltas for those points */
	 for (j=0; j<pcnt;) {
	    if (deltas[i][j]>0x7f || deltas[i][j]<0x80) {
	       for (rj=j+1; rj<j+0x40 && rj<pcnt; ++rj) {
		  if (deltas[i][pts[rj]]>0x7f || deltas[i][pts[rj]]<0x80
		      || (rj+1<j+0x40 && rj+1<pcnt
			  && (deltas[i][pts[rj+1]]>0x7f
			      || deltas[i][pts[rj+1]]<0x80)))
		     /* Keep going with a big run */ ;
		  else
		     break;
	       }
	       aputc((rj-j-1)|0x40, at->cvar);
	       for (; j<rj; ++j)
		  aput_int16_be_checked(deltas[i][pts[j]],at->cvar);
	    } else {
	       for (rj=j+1; rj<j+0x40 && rj<pcnt; ++rj) {
		  if (deltas[i][pts[rj]]>0x7f || deltas[i][pts[rj]]<0x80)
		     break;
	       }
	       aputc(rj-j-1, at->cvar);
	       for (; j<rj; ++j)
		  aputc(deltas[i][pts[j]], at->cvar);
	    }
	 }
	 free(pts);
	 end=aftell(at->cvar);
	 afseek(at->cvar, 8+cnt * tuple_size, SEEK_SET);
	 aput_int16_be_checked(end-start,at->cvar);
	 afseek(at->cvar, end, SEEK_SET);
	 ++cnt;
      }

   for (i=0; i<mm->instance_count; ++i)
      free(deltas[i]);
   free(deltas);

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

static void dumpdeltas(struct alltabs *at,int16_t *deltas,int ptcnt) {
   int j, rj;

   for (j=0; j<ptcnt;) {
      for (rj=j; rj<ptcnt && rj<j+0x40 && deltas[rj]==0; ++rj);
      if (rj != j) {
	 aputc((rj-j-1)|0x80, at->gvar);
	 j=rj;
	 continue;
      }
      if (deltas[j]>0x7f || deltas[j]<0x80) {
	 for (rj=j+1; rj<j+0x40 && rj<ptcnt; ++rj) {
	    if (deltas[rj]>0x7f || deltas[rj]<0x80 ||
		(rj+1<j+0x40 && rj+1<ptcnt
		 && (deltas[rj+1]>0x7f || deltas[rj+1]<0x80)))
	       /* Keep going with a big run */ ;
	    else
	       break;
	 }
	 aputc((rj-j-1)|0x40, at->gvar);
	 for (; j<rj; ++j)
	    aput_int16_be_checked(deltas[j],at->gvar);
      } else {
	 for (rj=j+1; rj<j+0x40 && rj<ptcnt; ++rj) {
	    if (deltas[rj]>0x7f || deltas[rj]<0x80 ||
		(deltas[rj]==0 && rj+1<j+0x40 && rj+1<ptcnt &&
		 deltas[rj+1] <= 0x7f && deltas[rj+1] >= 0x80
		 && deltas[rj+1] != 0))
	       break;
	 }
	 aputc(rj-j-1, at->gvar);
	 for (; j<rj; ++j)
	    aputc(deltas[j], at->gvar);
      }
   }
}

static void ttf_dumpgvar(struct alltabs *at,MMSet *mm) {
   int i, j, last;
   uint32_t gcoordoff, glyphoffs, start, here, tupledataend, tupledatastart;
   int16_t **deltas;
   int ptcnt;

   at->gvar=atmpfile();
   putlong(at->gvar, 0x00010000);	/* Format */
   aput_int16_be_checked(mm->axis_count,at->gvar);
   aput_int16_be_checked(mm->instance_count,at->gvar);	/* Number of global tuples */
   gcoordoff=aftell(at->gvar);
   putlong(at->gvar, 0);	/* Offset to global tuples, fix later */
   aput_int16_be_checked(at->maxp.numGlyphs,at->gvar);
   aput_int16_be_checked(1,at->gvar);	/* always output 32bit offsets */
   putlong(at->gvar, aftell(at->gvar)+4+(at->maxp.numGlyphs+1)*4);
   glyphoffs=aftell(at->gvar);
   for (i=0; i <= at->maxp.numGlyphs; ++i)
      putlong(at->gvar, 0);

   start=aftell(at->gvar);
   last=-1;
   for (i=0; i<at->gi.gcnt; ++i)
      if (at->gi.bygid[i] != -1) {
	 deltas=SCFindDeltas(mm, at->gi.bygid[i], &ptcnt);
	 if (deltas==NULL)
	    continue;
	 here=aftell(at->gvar);
	 afseek(at->gvar, glyphoffs+(last+1)*4, SEEK_SET);
	 for (; last<i; ++last)
	    putlong(at->gvar, here-start);
	 afseek(at->gvar, here, SEEK_SET);
	 aput_int16_be_checked(mm->instance_count,at->gvar);
	 aput_int16_be_checked(4+4*mm->instance_count,at->gvar);	/* offset to data */
	 for (j=0; j<mm->instance_count; ++j) {
	    aput_int16_be_checked(0,at->gvar);	/* tuple data size, fix later */
	    aput_int16_be_checked(0x2000|j,at->gvar);	/* private points, tuple i */
	 }
	 for (j=0; j<mm->instance_count; ++j) {
	    tupledatastart=aftell(at->gvar);
	    aputc('\0', at->gvar);	/* Point list, all points */
	    dumpdeltas(at, deltas[2*j], ptcnt);
	    dumpdeltas(at, deltas[2*j+1], ptcnt);
	    tupledataend=aftell(at->gvar);
	    afseek(at->gvar, here+4+4*j, SEEK_SET);
	    aput_int16_be_checked(tupledataend-tupledatastart,at->gvar);
	    afseek(at->gvar, tupledataend, SEEK_SET);
	    free(deltas[2*j]);
	    free(deltas[2*j+1]);
	 }
	 free(deltas);
      }
   here=aftell(at->gvar);
   afseek(at->gvar, glyphoffs+(last+1)*4, SEEK_SET);
   for (; last<at->maxp.numGlyphs; ++last)
      putlong(at->gvar, here-start);
   afseek(at->gvar, gcoordoff, SEEK_SET);
   putlong(at->gvar, here);
   afseek(at->gvar, here, SEEK_SET);
   for (j=0; j<mm->instance_count; ++j) {
      for (i=0; i<mm->axis_count; ++i)
	 aput_int16_be_checked(
		  rint(16384*mm->positions[j * mm->axis_count+i]),at->gvar);
   }

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


static void ttf_dumpavar(struct alltabs *at,MMSet *mm) {
   int i, j;

   for (i=0; i<mm->axis_count; ++i) {
      if (mm->axismaps[i].points>3)
	 break;
   }
   if (i==mm->axis_count)	/* We only have simple axes */
      return;			/* No need for a variation table */

   at->avar=atmpfile();
   putlong(at->avar, 0x00010000);	/* Format */
   putlong(at->avar, mm->axis_count);
   for (i=0; i<mm->axis_count; ++i) {
      aput_int16_be_checked(mm->axismaps[i].points,at->avar);
      for (j=0; j<mm->axismaps[i].points; ++j) {
	 if (mm->axismaps[i].designs[j]<mm->axismaps[i].def)
	    aput_int16_be_checked(
		     (mm->axismaps[i].designs[j] -
		      mm->axismaps[i].def)*16384/(mm->axismaps[i].def -
						      mm->axismaps[i].min),at->avar);
	 else
	    aput_int16_be_checked(
		     (mm->axismaps[i].designs[j] -
		      mm->axismaps[i].def)*16384/(mm->axismaps[i].max -
						      mm->axismaps[i].def),at->avar);
	 aput_int16_be_checked(mm->axismaps[i].blends[j]*16384,at->avar);
      }
   }

   at->avarlen=aftell(at->avar);
   if (at->avarlen&2)
      aput_int16_be_checked(0,at->avar);
}

static uint32_t AxisNameToTag(char *name) {
   char buf[4];
   int i;

   if (strmatch(name, "Weight")==0)
      return (CHR('w', 'g', 'h', 't'));
   if (strmatch(name, "Width")==0)
      return (CHR('w', 'd', 't', 'h'));
   if (strmatch(name, "OpticalSize")==0)
      return (CHR('o', 'p', 's', 'z'));
   if (strmatch(name, "Slant")==0)
      return (CHR('s', 'l', 'n', 't'));

   memset(buf, 0, sizeof(buf));
   for (i=0; i<4 && name[i] != '\0'; ++i)
      buf[i]=name[i];
   return (CHR(buf[0], buf[1], buf[2], buf[3]));
}

static int AllocateStrId(struct alltabs *at,struct macname *mn) {
   struct other_names *on;

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

   on=chunkalloc(sizeof(struct other_names));
   on->strid=at->next_strid++;
   on->mn=mn;
   on->next=at->other_names;
   at->other_names=on;
   return (on->strid);
}

static void ttf_dumpfvar(struct alltabs *at,MMSet *mm) {
   int i, j;

   at->fvar=atmpfile();
   putlong(at->fvar, 0x00010000);	/* Format */
   aput_int16_be_checked(16,at->fvar);	/* Offset to first axis data */
   aput_int16_be_checked(2,at->fvar);	/* Size count pairs */
   aput_int16_be_checked(mm->axis_count,at->fvar);
   aput_int16_be_checked(20,at->fvar);	/* Size of each axis record */
   aput_int16_be_checked(mm->named_instance_count,at->fvar);
   aput_int16_be_checked(4+4*mm->axis_count,at->fvar);

   /* For each axis ... */
   for (i=0; i<mm->axis_count; ++i) {
      putlong(at->fvar, AxisNameToTag(mm->axes[i]));
      putlong(at->fvar, rint(mm->axismaps[i].min*65536));
      putlong(at->fvar, rint(mm->axismaps[i].def*65536));
      putlong(at->fvar, rint(mm->axismaps[i].max*65536));
      aput_int16_be_checked(0,at->fvar);	/* No flags defined for axes */
      aput_int16_be_checked(AllocateStrId(at, mm->axismaps[i].axisnames),at->fvar);
   }

   /* For each named font ... */
   for (i=0; i<mm->named_instance_count; ++i) {
      aput_int16_be_checked(AllocateStrId(at, mm->named_instances[i].names),at->fvar);
      aput_int16_be_checked(0,at->fvar);	/* No flags here either */
      for (j=0; j<mm->axis_count; ++j)
	 putlong(at->fvar, rint(65536*mm->named_instances[i].coords[j]));
   }

   at->fvarlen=aftell(at->fvar);
   if (at->fvarlen&2)		/* I don't think this is ever hit */
      aput_int16_be_checked(0,at->fvar);
}

void ttf_dumpvariations(struct alltabs *at, SplineFont *sf) {
   MMSet *mm=sf->mm;
   int i, j;

   for (j=0; j<sf->glyphcnt; ++j)
      if (sf->glyphs[j] != NULL) {
	 for (i=0; i<mm->instance_count; ++i)
	    if (mm->instances[i]->glyphs[j] != NULL)
	       mm->instances[i]->glyphs[j]->ttf_glyph =
		  sf->glyphs[j]->ttf_glyph;
      }

   ttf_dumpfvar(at, mm);
   ttf_dumpgvar(at, mm);
   ttf_dumpcvar(at, mm);
   ttf_dumpavar(at, mm);
}
