/* $Id: mm.c 4532 2015-12-22 13:18:53Z mskala $ */
/* Copyright (C) 2003-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 <ustring.h>
#include <math.h>
#include <utype.h>
#include "ttf.h"
#include "mm.h"

char *MMAxisAbrev(char *axis_name) {
   if (strcmp(axis_name, "Weight")==0)
      return ("wt");
   if (strcmp(axis_name, "Width")==0)
      return ("wd");
   if (strcmp(axis_name, "OpticalSize")==0)
      return ("op");
   if (strcmp(axis_name, "Slant")==0)
      return ("sl");

   return (axis_name);
}

double MMAxisUnmap(MMSet * mm, int axis, double ncv) {
   struct axismap *axismap=&mm->axismaps[axis];
   int j;

   if (ncv <= axismap->blends[0])
      return (axismap->designs[0]);

   for (j=1; j<axismap->points; ++j) {
      if (ncv <= axismap->blends[j]) {
	 double t =
	    (ncv-axismap->blends[j-1])/(axismap->blends[j] -
					      axismap->blends[j-1]);
	 return (axismap->designs[j-1] +
		 t * (axismap->designs[j]-axismap->designs[j-1]));
      }
   }

   return (axismap->designs[axismap->points-1]);
}

static char *_MMMakeFontname(MMSet *mm,double *normalized,char **fullname) {
   char *pt, *pt2, *hyphen=NULL;
   char *ret=NULL;
   int i, j;

   if (mm->apple) {
      for (i=0; i<mm->named_instance_count; ++i) {
	 for (j=0; j<mm->axis_count; ++j) {
	    if ((normalized[j]==-1 &&
		 RealApprox(mm->named_instances[i].coords[j],
			    mm->axismaps[j].min)) || (normalized[j]==0
						      && RealApprox(mm->
								    named_instances
								    [i].
								    coords[j],
								    mm->
								    axismaps
								    [j].def))
		|| (normalized[j]==1
		    && RealApprox(mm->named_instances[i].coords[j],
				  mm->axismaps[j].max)))
	       /* A match so far */ ;
	    else
	       break;
	 }
	 if (j==mm->axis_count)
	    break;
      }
      if (i != mm->named_instance_count) {
	 char *styles=PickNameFromMacName(mm->named_instances[i].names);

	 if (styles==NULL)
	    styles=FindEnglishNameInMacName(mm->named_instances[i].names);
	 if (styles != NULL) {
	    ret=malloc(strlen(mm->normal->familyname)+strlen(styles)+3);
	    strcpy(ret, mm->normal->familyname);
	    hyphen=ret+strlen(ret);
	    strcpy(hyphen, " ");
	    strcpy(hyphen+1, styles);
	    free(styles);
	 }
      }
   }

   if (ret==NULL) {
      pt=ret =
	 malloc(strlen(mm->normal->familyname)+mm->axis_count*15+1);
      strcpy(pt, mm->normal->familyname);
      pt += strlen(pt);
      *pt++='_';
      for (i=0; i<mm->axis_count; ++i) {
	 if (!mm->apple)
	    sprintf(pt, " %d%s",
		    (int) rint(MMAxisUnmap(mm, i, normalized[i])),
		    MMAxisAbrev(mm->axes[i]));
	 else
	    sprintf(pt, " %.1f%s", (double) MMAxisUnmap(mm, i, normalized[i]),
		    MMAxisAbrev(mm->axes[i]));
	 pt += strlen(pt);
      }
      if (pt>ret && pt[-1]==' ')
	 --pt;
      *pt='\0';
   }

   *fullname=ret;

   ret=fastrdup(ret);
   for (pt=*fullname, pt2=ret; *pt != '\0'; ++pt)
      if (pt==hyphen)
	 *pt2++='-';
      else if (*pt != ' ')
	 *pt2++=*pt;
   *pt2='\0';
   return (ret);
}

char *MMMakeMasterFontname(MMSet * mm, int ipos, char **fullname) {
   return (_MMMakeFontname
	   (mm, &mm->positions[ipos * mm->axis_count], fullname));
}

static char *_MMGuessWeight(MMSet *mm,double *normalized,char *def) {
   int i;
   char *ret;
   double design;

   for (i=0; i<mm->axis_count; ++i) {
      if (strcmp(mm->axes[i], "Weight")==0)
	 break;
   }
   if (i==mm->axis_count)
      return (def);
   design=MMAxisUnmap(mm, i, normalized[i]);
   if (design<50 || design>1500)	/* Er... Probably not the 0...1000 range I expect */
      return (def);
   ret=NULL;
   if (design<150)
      ret="Thin";
   else if (design<350)
      ret="Light";
   else if (design<550)
      ret="Medium";
   else if (design<650)
      ret="DemiBold";
   else if (design<750)
      ret="Bold";
   else if (design<850)
      ret="Heavy";
   else
      ret="Black";
   free(def);
   return (fastrdup(ret));
}

char *MMGuessWeight(MMSet * mm, int ipos, char *def) {
   return (_MMGuessWeight(mm, &mm->positions[ipos * mm->axis_count], def));
}

/* Given a postscript array of scalars, what's the ipos'th element? */
char *MMExtractNth(char *pt, int ipos) {
   int i;
   char *end;

   while (*pt==' ')
      ++pt;
   if (*pt=='[')
      ++pt;
   for (i=0; *pt != ']' && *pt != '\0'; ++i) {
      while (*pt==' ')
	 ++pt;
      if (*pt==']' || *pt=='\0')
	 return (NULL);
      for (end=pt; *end != ' ' && *end != ']' && *end != '\0'; ++end);
      if (i==ipos)
	 return (copyn(pt, end-pt));
      pt=end;
   }
   return (NULL);
}

/* Given a postscript array of arrays, such as those found in Blend Private BlueValues */
/* return the array composed of the ipos'th element of each sub-array */
char *MMExtractArrayNth(char *pt, int ipos) {
   char *hold[40], *ret;
   int i, j, len;

   while (*pt==' ')
      ++pt;
   if (*pt=='[')
      ++pt;
   i=0;
   while (*pt != ']' && *pt != ' ') {
      while (*pt==' ')
	 ++pt;
      if (*pt=='[') {
	 if (i<sizeof(hold)/sizeof(hold[0]))
	    hold[i++]=MMExtractNth(pt, ipos);
	 ++pt;
	 while (*pt != ']' && *pt != '\0')
	    ++pt;
      }
      if (*pt != '\0')
	 ++pt;
   }
   if (i==0)
      return (NULL);
   for (j=len=0; j<i; ++j) {
      if (hold[j]==NULL) {
	 for (j=0; j<i; ++j)
	    free(hold[j]);
	 return (NULL);
      }
      len += strlen(hold[j])+1;
   }

   pt=ret=malloc(len+4);
   *pt++='[';
   for (j=0; j<i; ++j) {
      strcpy(pt, hold[j]);
      free(hold[j]);
      pt += strlen(pt);
      if (j != i-1)
	 *pt++=' ';
   }
   *pt++=']';
   *pt++='\0';
   return (ret);
}

void MMKern(SplineFont *sf, SplineChar * first, SplineChar * second,
	    int diff, struct lookup_subtable *sub, KernPair * oldkp) {
   MMSet *mm=sf->mm;
   KernPair *kp;
   int i;

   /* If the user creates a kern pair in one font of a multiple master set */
   /*  then we should create the same kern pair in all the others. Similarly */
   /*  if s/he modifies a kern pair in the weighted version then apply that */
   /*  mod to all the others */

   if (mm==NULL)
      return;
   if (sf==mm->normal || oldkp==NULL) {
      for (i=-1; i<mm->instance_count; ++i) {
	 SplineFont *cur=i==-1?mm->normal:mm->instances[i];
	 SplineChar *psc, *ssc;

	 if (cur==sf)		/* Done in caller */
	    continue;
	 psc=cur->glyphs[first->orig_pos];
	 ssc=cur->glyphs[second->orig_pos];
	 if (psc==NULL || ssc==NULL)	/* Should never happen */
	    continue;
	 for (kp=psc->kerns; kp != NULL && kp->sc != ssc; kp=kp->next);
	 /* No mm support for vertical kerns */
	 if (kp==NULL) {
	    kp=chunkalloc(sizeof(KernPair));
	    if (oldkp != NULL)
	       *kp=*oldkp;
	    else {
	       kp->off=diff;
	       if (sub==NULL)
		  sub=SFSubTableFindOrMake(cur,
					     CHR('k', 'e', 'r', 'n'),
					     SCScriptFromUnicode(psc),
					     gpos_pair);
	       kp->subtable=sub;
	    }
	    kp->sc=ssc;
	    kp->next=psc->kerns;
	    psc->kerns=kp;
	 } else
	    kp->off += diff;
      }
   }
}

static SplineChar *SFMakeGlyphLike(SplineFont *sf,int gid,
				   SplineFont *base) {
   SplineChar *sc=SFSplineCharCreate(sf), *bsc=base->glyphs[gid];

   sc->orig_pos=gid;
   sf->glyphs[gid]=sc;

   sc->width=bsc->width;
   sc->widthset=true;
   sc->vwidth=bsc->vwidth;
   free(sc->name);
   sc->name=fastrdup(bsc->name);
   sc->unicodeenc=bsc->unicodeenc;
   return (sc);
}

static char *_MMBlendChar(MMSet *mm,int gid) {
   int i, j, worthit=-1;
   int all, any, any2, all2, anyend, allend, diff;
   SplineChar *sc;
   SplinePointList *spls[MmMax], *spl, *spllast;
   SplinePoint *tos[MmMax], *to;
   RefChar *refs[MmMax], *ref, *reflast;
   KernPair *kp0, *kptest, *kp, *kplast;
   StemInfo *hs[MmMax], *h, *hlast;
   double width;

   for (i=0; i<mm->instance_count; ++i) {
      if (mm->instances[i]->layers[ly_fore].order2)
	 return "One of the multiple master instances contains quadratic splines. It must be converted to cubic splines before it can be used in a multiple master";
      if (gid >= mm->instances[i]->glyphcnt)
	 return "The different instances of this mm have a different number of glyphs";
      if (SCWorthOutputting(mm->instances[i]->glyphs[gid])) {
	 if (worthit==-1)
	    worthit=true;
	 else if (worthit != true)
	    return "This glyph is defined in one instance font but not in another";
      } else {
	 if (worthit==-1)
	    worthit=false;
	 else if (worthit != false)
	    return "This glyph is defined in one instance font but not in another";
      }
   }

   sc=mm->normal->glyphs[gid];
   if (sc != NULL) {
      SCClearContents(sc, ly_fore);
      KernPairsFree(sc->kerns);
      sc->kerns=NULL;
      KernPairsFree(sc->vkerns);
      sc->vkerns=NULL;
   }

   if (!worthit)
      return (0);

   if (sc==NULL)
      sc=SFMakeGlyphLike(mm->normal, gid, mm->instances[0]);

   /* Blend references => blend transformation matrices */
   diff=false;
   any=false;
   all=true;
   for (i=0; i<mm->instance_count; ++i) {
      refs[i]=mm->instances[i]->glyphs[gid]->layers[ly_fore].refs;
      if (refs[i] != NULL)
	 any=true;
      else
	 all=false;
   }
   reflast=NULL;
   while (all) {
      ref=RefCharCreate();
      *ref=*refs[0];
      ref->layers[0].splines=NULL;
      ref->next=NULL;
      memset(ref->transform, 0, sizeof(ref->transform));
      ref->sc=mm->normal->glyphs[refs[0]->sc->orig_pos];
      for (i=0; i<mm->instance_count; ++i) {
	 if (ref->sc->orig_pos != refs[i]->sc->orig_pos)
	    diff=true;
	 for (j=0; j<6; ++j)
	    ref->transform[j] += refs[i]->transform[j] * mm->defweights[i];
      }
      if (reflast==NULL)
	 sc->layers[ly_fore].refs=ref;
      else
	 reflast->next=ref;
      reflast=ref;
      any=false;
      all=true;
      for (i=0; i<mm->instance_count; ++i) {
	 refs[i]=refs[i]->next;
	 if (refs[i] != NULL)
	    any=true;
	 else
	    all=false;
      }
   }
   if (any)
      return "This glyph contains a different number of references in different instances";
   if (diff)
      return "A reference in this glyph refers to a different encoding in different instances";

   /* Blend Width */
   width=0;
   for (i=0; i<mm->instance_count; ++i)
      width += mm->instances[i]->glyphs[gid]->width * mm->defweights[i];
   sc->width=width;
   width=0;
   for (i=0; i<mm->instance_count; ++i)
      width += mm->instances[i]->glyphs[gid]->vwidth * mm->defweights[i];
   sc->vwidth=width;

   /* Blend Splines */
   any=false;
   all=true;
   for (i=0; i<mm->instance_count; ++i) {
      spls[i]=mm->instances[i]->glyphs[gid]->layers[ly_fore].splines;
      if (spls[i] != NULL)
	 any=true;
      else
	 all=false;
   }
   spllast=NULL;
   while (all) {
      spl=chunkalloc(sizeof(SplinePointList));
      if (spllast==NULL)
	 sc->layers[ly_fore].splines=spl;
      else
	 spllast->next=spl;
      spllast=spl;
      for (i=0; i<mm->instance_count; ++i)
	 tos[i]=spls[i]->first;
      all2=true;
      spl->last=NULL;
      while (all2) {
	 to=chunkalloc(sizeof(SplinePoint));
	 to->nonextcp=tos[0]->nonextcp;
	 to->noprevcp=tos[0]->noprevcp;
	 to->nextcpdef=tos[0]->nextcpdef;
	 to->prevcpdef=tos[0]->prevcpdef;
	 to->pointtype=tos[0]->pointtype;
	 if (tos[0]->hintmask != NULL) {
	    to->hintmask=chunkalloc(sizeof(HintMask));
	    memcpy(to->hintmask, tos[0]->hintmask, sizeof(HintMask));
	 }
	 for (i=0; i<mm->instance_count; ++i) {
	    to->me.x += tos[i]->me.x * mm->defweights[i];
	    to->me.y += tos[i]->me.y * mm->defweights[i];
	    to->nextcp.x += tos[i]->nextcp.x * mm->defweights[i];
	    to->nextcp.y += tos[i]->nextcp.y * mm->defweights[i];
	    to->prevcp.x += tos[i]->prevcp.x * mm->defweights[i];
	    to->prevcp.y += tos[i]->prevcp.y * mm->defweights[i];
	 }
	 if (spl->last==NULL)
	    spl->first=to;
	 else
	    SplineMake3(spl->last, to);
	 spl->last=to;
	 any2=false;
	 all2=true;
	 for (i=0; i<mm->instance_count; ++i) {
	    if (tos[i]->next==NULL)
	       tos[i]=NULL;
	    else
	       tos[i]=tos[i]->next->to;
	    if (tos[i] != NULL)
	       any2=true;
	    else
	       all2=false;
	 }
	 if (!all2 && any2)
	    return "A contour in this glyph contains a different number of points in different instances";
	 anyend=false;
	 allend=true;
	 for (i=0; i<mm->instance_count; ++i) {
	    if (tos[i]==spls[i]->first)
	       anyend=true;
	    else
	       allend=false;
	 }
	 if (allend) {
	    SplineMake3(spl->last, spl->first);
	    spl->last=spl->first;
	    break;
	 }
	 if (anyend)
	    return "A contour in this glyph contains a different number of points in different instances";
      }
      any=false;
      all=true;
      for (i=0; i<mm->instance_count; ++i) {
	 spls[i]=spls[i]->next;
	 if (spls[i] != NULL)
	    any=true;
	 else
	    all=false;
      }
   }
   if (any)
      return "This glyph contains a different number of contours in different instances";

   /* Blend hints */
   for (j=0; j<2; ++j) {
      any=false;
      all=true;
      for (i=0; i<mm->instance_count; ++i) {
	 hs[i] =
	    j?mm->instances[i]->glyphs[gid]->hstem:mm->instances[i]->
	    glyphs[gid]->vstem;
	 if (hs[i] != NULL)
	    any=true;
	 else
	    all=false;
      }
      hlast=NULL;
      while (all) {
	 h=chunkalloc(sizeof(StemInfo));
	 *h=*hs[0];
	 h->where=NULL;
	 h->next=NULL;
	 h->start=h->width=0;
	 for (i=0; i<mm->instance_count; ++i) {
	    h->start += hs[i]->start * mm->defweights[i];
	    h->width += hs[i]->width * mm->defweights[i];
	 }
	 if (hlast != NULL)
	    hlast->next=h;
	 else if (j)
	    sc->hstem=h;
	 else
	    sc->vstem=h;
	 hlast=h;
	 any=false;
	 all=true;
	 for (i=0; i<mm->instance_count; ++i) {
	    hs[i]=hs[i]->next;
	    if (hs[i] != NULL)
	       any=true;
	    else
	       all=false;
	 }
      }
      if (any)
	 return "This glyph contains a different number of hints in different instances";
   }

   /* Blend kernpairs */
   /* I'm not requiring ordered kerning pairs */
   /* I'm not bothering with vertical kerning */
   kp0=mm->instances[0]->glyphs[gid]->kerns;
   kplast=NULL;
   while (kp0 != NULL) {
      int off=kp0->off * mm->defweights[0];

      for (i=1; i<mm->instance_count; ++i) {
	 for (kptest=mm->instances[i]->glyphs[gid]->kerns; kptest != NULL;
	      kptest=kptest->next)
	    if (kptest->sc->orig_pos==kp0->sc->orig_pos)
	       break;
	 if (kptest==NULL)
	    return "This glyph contains different kerning pairs in different instances";
	 off += kptest->off * mm->defweights[i];
      }
      kp=chunkalloc(sizeof(KernPair));
      kp->sc=mm->normal->glyphs[kp0->sc->orig_pos];
      kp->off=off;
      kp->subtable=kp0->subtable;
      if (kplast != NULL)
	 kplast->next=kp;
      else
	 sc->kerns=kp;
      kplast=kp;
      kp0=kp0->next;
   }
   return (0);
}

char *MMBlendChar(MMSet * mm, int gid) {
   char *ret;
   RefChar *ref;

   if (gid >= mm->normal->glyphcnt)
      return "The different instances of this mm have a different number of glyphs";
   ret=_MMBlendChar(mm, gid);
   if (mm->normal->glyphs[gid] != NULL) {
      SplineChar *sc=mm->normal->glyphs[gid];

      for (ref=sc->layers[ly_fore].refs; ref != NULL; ref=ref->next) {
	 SCReinstantiateRefChar(sc, ref, ly_fore);
	 SCMakeDependent(sc, ref->sc);
      }
   }
   return (ret);
}

static struct psdict *BlendPrivate(struct psdict *private,MMSet *mm) {
   struct psdict *other;
   double sum, val;
   char *data;
   int i, j, k, cnt;
   char *values[MmMax], buffer[32], *space, *pt, *end;

   other=mm->instances[0]->private;
   if (other==NULL)
      return (private);

   if (private==NULL)
      private=calloc(1, sizeof(struct psdict));

   i=PSDictFindEntry(private, "ForceBoldThreshold");
   if (i != -1) {
      val=strtod(private->values[i], NULL);
      sum=0;
      for (j=0; j<mm->instance_count; ++j) {
	 i=PSDictFindEntry(mm->instances[j]->private, "ForceBold");
	 if (i != -1
	     && strcmp(mm->instances[j]->private->values[i], "true")==0)
	    sum += mm->defweights[j];
      }
      data=(sum >= val)?"true":"false";
      PSDictChangeEntry(private, "ForceBold", data);
   }
   for (i=0; i<other->next; ++i) {
      if (*other->values[i] != '[' && !isdigit(*other->values[i])
	  && *other->values[i] != '.')
	 continue;
      for (j=0; j<mm->instance_count; ++j) {
	 k=PSDictFindEntry(mm->instances[j]->private, other->keys[i]);
	 if (k==-1)
	    break;
	 values[j]=mm->instances[j]->private->values[k];
      }
      if (j != mm->instance_count)
	 continue;
      if (*other->values[i] != '[') {
	 /* blend a real number */
	 sum=0;
	 for (j=0; j<mm->instance_count; ++j) {
	    val=strtod(values[j], &end);
	    if (end==values[j])
	       break;
	    sum += val * mm->defweights[j];
	 }
	 if (j != mm->instance_count)
	    continue;
	 sprintf(buffer, "%g", (double) sum);
	 PSDictChangeEntry(private, other->keys[i], buffer);
      } else {
	 /* Blend an array of numbers */
	 for (pt=values[0], cnt=0; *pt != '\0' && *pt != ']'; ++pt) {
	    if (*pt==' ') {
	       while (*pt==' ')
		  ++pt;
	       --pt;
	       ++cnt;
	    }
	 }
	 space=pt=malloc((cnt+2)*24+4);
	 *pt++='[';
	 for (j=0; j<mm->instance_count; ++j)
	    if (*values[j]=='[')
	       ++values[j];
	 while (*values[0] != ']') {
	    sum=0;
	    for (j=0; j<mm->instance_count; ++j) {
	       val=strtod(values[j], &end);
	       sum += val * mm->defweights[j];
	       while (*end==' ')
		  ++end;
	       values[j]=end;
	    }
	    sprintf(pt, "%g ", (double) sum);
	    pt += strlen(pt);
	 }
	 if (pt[-1]==' ')
	    --pt;
	 *pt++=']';
	 *pt='\0';
	 PSDictChangeEntry(private, other->keys[i], space);
	 free(space);
      }
   }

   return (private);
}

int MMReblend(FontViewBase * fv, MMSet * mm) {
   char *olderr, *err;
   int i, first=-1;
   SplineFont *sf=mm->instances[0];
   RefChar *ref;

   olderr=NULL;
   for (i=0; i<sf->glyphcnt; ++i) {
      if (i >= mm->normal->glyphcnt)
	 break;
      err=MMBlendChar(mm, i);
      if (mm->normal->glyphs[i] != NULL)
	 SCCharChangedUpdate(mm->normal->glyphs[i], ly_fore, -1);
      if (err==NULL)
	 continue;
      if (olderr==NULL) {
	 if (fv != NULL)
	    FVDeselectAll(fv);
	 first=i;
      }
      if (olderr==NULL || olderr==err)
	 olderr=err;
      else
	 olderr=(char *) -1;
      if (fv != NULL) {
	 int enc=fv->map->backmap[i];

	 if (enc != -1)
	    fv->selected[enc]=true;
      }
   }

   sf=mm->normal;
   for (i=0; i<sf->glyphcnt; ++i)
      if (sf->glyphs[i] != NULL) {
	 for (ref=sf->glyphs[i]->layers[ly_fore].refs; ref != NULL;
	      ref=ref->next) {
	    SCReinstantiateRefChar(sf->glyphs[i], ref, ly_fore);
	    SCMakeDependent(sf->glyphs[i], ref->sc);
	 }
      }
   sf->private=BlendPrivate(sf->private, mm);

   if (olderr==NULL)		/* No Errors */
      return (true);

   if (fv != NULL) {
      if (olderr==(char *) -1)
        ErrorMsg(2,"Unspecified multiple master errors\n");
      else
         ErrorMsg(2,"The following multiple master error occurred "
                    "on the selected glyphs:  %.100s\n",olderr);
   }
   return (false);
}

void MMWeightsUnMap(double weights[MmMax], double axiscoords[4], int axis_count) {

   if (axis_count==1)
      axiscoords[0]=weights[1];
   else if (axis_count==2) {
      axiscoords[0]=weights[3]+weights[1];
      axiscoords[1]=weights[3]+weights[2];
   } else if (axis_count==3) {
      axiscoords[0]=weights[7]+weights[5]+weights[3]+weights[1];
      axiscoords[1]=weights[7]+weights[6]+weights[3]+weights[2];
      axiscoords[2]=weights[7]+weights[6]+weights[5]+weights[4];
   } else {
      axiscoords[0]=weights[15]+weights[13]+weights[11]+weights[9] +
	 weights[7]+weights[5]+weights[3]+weights[1];
      axiscoords[1]=weights[15]+weights[14]+weights[11]+weights[10] +
	 weights[7]+weights[6]+weights[3]+weights[2];
      axiscoords[2]=weights[15]+weights[14]+weights[13]+weights[12] +
	 weights[7]+weights[6]+weights[5]+weights[4];
      axiscoords[3]=weights[15]+weights[14]+weights[13]+weights[12] +
	 weights[11]+weights[10]+weights[9]+weights[8];
   }
}

SplineFont *_MMNewFont(MMSet * mm, int index, char *familyname,
		       double * normalized) {
   SplineFont *sf, *base;
   char *pt1, *pt2;
   int i;

   sf=SplineFontNew();
   sf->layers[ly_fore].order2=sf->layers[ly_back].order2=sf->grid.order2 =
      mm->apple;
   free(sf->fontname);
   free(sf->familyname);
   free(sf->fullname);
   free(sf->weight);
   sf->familyname=fastrdup(familyname);
   if (index==-1) {
      sf->fontname=fastrdup(familyname);
      for (pt1=pt2=sf->fontname; *pt1; ++pt1)
	 if (*pt1 != ' ')
	    *pt2++=*pt1;
      *pt2='\0';
      sf->fullname=fastrdup(familyname);
   } else
      sf->fontname=_MMMakeFontname(mm, normalized, &sf->fullname);
   sf->weight=fastrdup("All");

   base=NULL;
   if (mm->normal != NULL)
      base=mm->normal;
   else {
      for (i=0; i<mm->instance_count; ++i)
	 if (mm->instances[i] != NULL) {
	    base=mm->instances[i];
	    break;
	 }
   }

   if (base != NULL) {
      free(sf->xuid);
      sf->xuid=fastrdup(base->xuid);
      free(sf->glyphs);
      sf->glyphs=calloc(base->glyphcnt, sizeof(SplineChar *));
      sf->glyphcnt=sf->glyphmax=base->glyphcnt;
      sf->new=base->new;
      sf->ascent=base->ascent;
      sf->descent=base->descent;
      free(sf->origname);
      sf->origname=fastrdup(base->origname);
      if (index<0) {
	 free(sf->copyright);
	 sf->copyright=fastrdup(base->copyright);
      }
      /* Make sure we get the encoding exactly right */
      for (i=0; i<base->glyphcnt; ++i)
	 if (base->glyphs[i] != NULL)
	    SFMakeGlyphLike(sf, i, base);
   }
   sf->onlybitmaps=false;
   sf->mm=mm;
   return (sf);
}

SplineFont *MMNewFont(MMSet * mm, int index, char *familyname) {
   return (_MMNewFont
	   (mm, index, familyname, &mm->positions[index * mm->axis_count]));
}

FontViewBase *MMCreateBlendedFont(MMSet * mm, FontViewBase * fv,
				  double blends[MmMax], int tonew) {
   double oldblends[MmMax];
   SplineFont *hold=mm->normal;
   int i;
   double axispos[4];

   for (i=0; i<mm->instance_count; ++i) {
      oldblends[i]=mm->defweights[i];
      mm->defweights[i]=blends[i];
   }
   if (tonew) {
      SplineFont *newsf;
      FontViewBase *oldfv=hold->fv;
      char *fn, *full;

      mm->normal=newsf=MMNewFont(mm, -1, hold->familyname);
      MMWeightsUnMap(blends, axispos, mm->axis_count);
      fn=_MMMakeFontname(mm, axispos, &full);
      free(newsf->fontname);
      free(newsf->fullname);
      newsf->fontname=fn;
      newsf->fullname=full;
      newsf->weight=_MMGuessWeight(mm, axispos, newsf->weight);
      newsf->private=BlendPrivate(PSDictCopy(hold->private), mm);
      newsf->fv=NULL;
      fv=FontViewCreate(newsf, false);
      MMReblend(fv, mm);
      newsf->mm=NULL;
      mm->normal=hold;
      for (i=0; i<mm->instance_count; ++i) {
	 mm->defweights[i]=oldblends[i];
	 mm->instances[i]->fv=oldfv;
      }
      hold->fv=oldfv;
   } else {
      for (i=0; i<mm->instance_count; ++i)
	 mm->defweights[i]=blends[i];
      mm->changed=true;
      MMReblend(fv, mm);
   }
   return (fv);
}

/******************************************************************************/
/*                                MM Validation                               */
/******************************************************************************/

static int ContourCount(SplineChar *sc) {
   SplineSet *spl;
   int i;

   for (spl=sc->layers[ly_fore].splines, i=0; spl != NULL;
	spl=spl->next, ++i);
   return (i);
}

static int ContourPtMatch(SplineChar *sc1,SplineChar *sc2) {
   SplineSet *spl1, *spl2;
   SplinePoint *sp1, *sp2;

   for (spl1=sc1->layers[ly_fore].splines, spl2 =
	sc2->layers[ly_fore].splines; spl1 != NULL && spl2 != NULL;
	spl1=spl1->next, spl2=spl2->next) {
      for (sp1=spl1->first, sp2=spl2->first;;) {
	 if (sp1->nonextcp != sp2->nonextcp || sp1->noprevcp != sp2->noprevcp)
	    return (false);
	 if (sp1->next==NULL || sp2->next==NULL) {
	    if (sp1->next==NULL && sp2->next==NULL)
	       break;
	    return (false);
	 }
	 sp1=sp1->next->to;
	 sp2=sp2->next->to;
	 if (sp1==spl1->first || sp2==spl2->first) {
	    if (sp1==spl1->first && sp2==spl2->first)
	       break;
	    return (false);
	 }
      }
   }
   return (true);
}

static int ContourDirMatch(SplineChar *sc1,SplineChar *sc2) {
   SplineSet *spl1, *spl2;

   for (spl1=sc1->layers[ly_fore].splines, spl2 =
	sc2->layers[ly_fore].splines; spl1 != NULL && spl2 != NULL;
	spl1=spl1->next, spl2=spl2->next) {
      if (SplinePointListIsClockwise(spl1) !=
	  SplinePointListIsClockwise(spl2))
	 return (false);
   }
   return (true);
}

static int ContourHintMaskMatch(SplineChar *sc1,SplineChar *sc2) {
   SplineSet *spl1, *spl2;
   SplinePoint *sp1, *sp2;

   for (spl1=sc1->layers[ly_fore].splines, spl2 =
	sc2->layers[ly_fore].splines; spl1 != NULL && spl2 != NULL;
	spl1=spl1->next, spl2=spl2->next) {
      for (sp1=spl1->first, sp2=spl2->first;;) {
	 if ((sp1->hintmask==NULL) != (sp2->hintmask==NULL))
	    return (false);
	 if (sp1->hintmask != NULL
	     && memcmp(sp1->hintmask, sp2->hintmask, sizeof(HintMask)) != 0)
	    return (false);
	 if (sp1->next==NULL || sp2->next==NULL) {
	    if (sp1->next==NULL && sp2->next==NULL)
	       break;
	    return (false);
	 }
	 sp1=sp1->next->to;
	 sp2=sp2->next->to;
	 if (sp1==spl1->first || sp2==spl2->first) {
	    if (sp1==spl1->first && sp2==spl2->first)
	       break;
	    return (false);
	 }
      }
   }
   return (true);
}

static int RefMatch(SplineChar *sc1,SplineChar *sc2) {
   RefChar *ref1, *ref2;

   /* I don't require the reference list to be ordered */

   for (ref1=sc1->layers[ly_fore].refs, ref2=sc2->layers[ly_fore].refs;
	ref1 != NULL && ref2 != NULL; ref1=ref1->next, ref2=ref2->next)
      ref2->checked=false;
   if (ref1 != NULL || ref2 != NULL)
      return (false);

   for (ref1=sc1->layers[ly_fore].refs; ref1 != NULL; ref1=ref1->next) {
      for (ref2=sc2->layers[ly_fore].refs; ref2 != NULL; ref2=ref2->next) {
	 if (ref2->sc->orig_pos==ref1->sc->orig_pos && !ref2->checked)
	    break;
      }
      if (ref2==NULL)
	 return (false);
      ref2->checked=true;
   }

   return (true);
}

static int RefTransformsMatch(SplineChar *sc1,SplineChar *sc2) {
   /* Apple only provides a means to change the translation of a reference */
   /*  so if rotation, skewing, scaling, etc. differ then we can't deal with */
   /*  it. */
   RefChar *r1=sc1->layers[ly_fore].refs;
   RefChar *r2=sc2->layers[ly_fore].refs;

   while (r1 != NULL && r2 != NULL) {
      if (r1->transform[0] != r2->transform[0] ||
	  r1->transform[1] != r2->transform[1] ||
	  r1->transform[2] != r2->transform[2] ||
	  r1->transform[3] != r2->transform[3])
	 return (false);
      r1=r1->next;
      r2=r2->next;
   }
   return (true);
}

static int HintsMatch(StemInfo *h1,StemInfo *h2) {
   while (h1 != NULL && h2 != NULL) {
      h1=h1->next;
      h2=h2->next;
   }
   return (h1==NULL && h2==NULL);
}

static int KernsMatch(SplineChar *sc1,SplineChar *sc2) {
   /* I don't require the kern list to be ordered */
   /* Only interested in kerns that go into afm files (ie. no kernclasses) */
   KernPair *k1, *k2;

   for (k1=sc1->kerns, k2=sc2->kerns; k1 != NULL && k2 != NULL;
	k1=k1->next, k2=k2->next)
      k2->kcid=false;
   if (k1 != NULL || k2 != NULL)
      return (false);

   for (k1=sc1->kerns; k1 != NULL; k1=k1->next) {
      for (k2=sc2->kerns; k2 != NULL; k2=k2->next) {
	 if (k2->sc->orig_pos==k1->sc->orig_pos && !k2->kcid)
	    break;
      }
      if (k2==NULL)
	 return (false);
      k2->kcid=true;
   }

   return (true);
}

static int ArrayCount(char *val) {
   char *end;
   int cnt;

   if (val==NULL)
      return (0);
   while (*val==' ')
      ++val;
   if (*val=='[')
      ++val;
   cnt=0;
   while (*val) {
      strtod(val, &end);
      if (val==end)
	 break;
      ++cnt;
      val=end;
   }
   return (cnt);
}

int MMValid(MMSet * mm, int complain) {
   int i, j;
   SplineFont *sf;
   static char *arrnames[] =
      { "BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues",
"StdHW", "StdVW", "StemSnapH", "StemSnapV", NULL };

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

   for (i=0; i<mm->instance_count; ++i)
      if (mm->instances[i]->layers[ly_fore].order2 != mm->apple) {
	 if (complain) {
	    if (mm->apple)
              ErrorMsg(2,"The font %.30s contains cubic splines.  "
                         "It must be converted to quadratic splines before "
                         "it can be used in an Apple distortable font.\n",
                       mm->instances[i]->fontname);
	    else
              ErrorMsg(2,"The font %.30s contains quadratic splines.  "
                         "It must be converted to cubic splines before "
                         "it can be used in a Multiple Master font.\n",
                       mm->instances[i]->fontname);
	 }
	 return (false);
      }

   sf=mm->apple?mm->normal:mm->instances[0];

   if (!mm->apple && PSDictHasEntry(sf->private, "ForceBold") != NULL &&
       PSDictHasEntry(mm->normal->private, "ForceBoldThreshold")==NULL) {
      if (complain)
	 ErrorMsg(2,"There is no ForceBoldThreshold entry in the "
	            "weighted font, but there is a ForceBold entry in "
	            "font %30s.\n",sf->fontname);
      return (false);
   }

   for (j=mm->apple?0:1; j<mm->instance_count; ++j) {
      if (sf->glyphcnt != mm->instances[j]->glyphcnt) {
	 if (complain)
            ErrorMsg(2,"The fonts %1$.30s and %2$.30s have a different "
                       "number of glyphs, or different encodings.\n",
                       sf->fontname,mm->instances[j]->fontname);
	 return (false);
      } else if (sf->layers[ly_fore].order2 !=
		 mm->instances[j]->layers[ly_fore].order2) {
	 if (complain)
            ErrorMsg(2,"The fonts %1$.30s and %2$.30s have different "
                       "spline orders.\n",
                       sf->fontname,mm->instances[j]->fontname);
	 return (false);
      }
      if (!mm->apple) {
	 if (PSDictHasEntry(mm->instances[j]->private, "ForceBold") != NULL &&
	     PSDictHasEntry(mm->normal->private,
			    "ForceBoldThreshold")==NULL) {
	    if (complain)
	       ErrorMsg(2,"There is no ForceBoldThreshold entry in the "
	                  "weighted font, but there is a ForceBold entry in "
	                  "font %30s.\n",mm->instances[j]->fontname);
	    return (false);
	 }
	 for (i=0; arrnames[i] != NULL; ++i) {
	    if (ArrayCount
		(PSDictHasEntry(mm->instances[j]->private, arrnames[i])) !=
		ArrayCount(PSDictHasEntry(sf->private, arrnames[i]))) {
	       if (complain)
		  ErrorMsg(2,"The entry \"%1$.20s\" is not present in the "
		             "private dictionary of both %2$.30s and "
		             "%3$.30s\n",
                             arrnames[i], sf->fontname,
                             mm->instances[j]->fontname);
	       return (false);
	    }
	 }
      }
   }

   for (i=0; i<sf->glyphcnt; ++i) {
      for (j=mm->apple?0:1; j<mm->instance_count; ++j) {
	 if (SCWorthOutputting(sf->glyphs[i]) !=
	     SCWorthOutputting(mm->instances[j]->glyphs[i])) {
	    if (complain) {
	       if (SCWorthOutputting(sf->glyphs[i]))
		  ErrorMsg(2,"The glyph %1$.30s is defined in font %2$.30s but not in %3$.30s\n",
				sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       else
		  ErrorMsg(2,"The glyph %1$.30s is defined in font %2$.30s but not in %3$.30s\n",
				mm->instances[j]->glyphs[i]->name,
				mm->instances[j]->fontname, sf->fontname);
	    }
	    return (false);
	 }
      }
      if (SCWorthOutputting(sf->glyphs[i])) {
	 if (mm->apple && sf->glyphs[i]->layers[ly_fore].refs != NULL
	     && sf->glyphs[i]->layers[ly_fore].splines != NULL) {
	    if (complain) {
	       ErrorMsg(2,"The glyph %1$.30s in %2$.30s has both references and contours.  This is not supported in a font with variations.\n",
			     sf->glyphs[i]->name, sf->fontname);
	    }
	    return (false);
	 }
	 for (j=mm->apple?0:1; j<mm->instance_count; ++j) {
	    if (mm->apple
		&& mm->instances[j]->glyphs[i]->layers[ly_fore].refs != NULL
		&& mm->instances[j]->glyphs[i]->layers[ly_fore].splines !=
		NULL) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s in %2$.30s has both references and contours.  This is not supported in a font with variations.\n",
				sf->glyphs[i]->name,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    }
	    if (ContourCount(sf->glyphs[i]) !=
		ContourCount(mm->instances[j]->glyphs[i])) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s has a different number of contours in font %2$.30s than in %3$.30s\n",
				sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    } else if (!mm->apple
		       && !ContourPtMatch(sf->glyphs[i],
					  mm->instances[j]->glyphs[i])) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s in font %2$.30s has a different number of points (or control points) on its contours than in %3$.30s\n",
				sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    } else
	       if (!ContourDirMatch
		   (sf->glyphs[i], mm->instances[j]->glyphs[i])) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s in font %2$.30s has contours running in a different direction than in %3$.30s\n",
				sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    } else if (!RefMatch(sf->glyphs[i], mm->instances[j]->glyphs[i])) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s in font %2$.30s has a different number of references than in %3$.30s\n",
				sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    } else if (mm->apple
		       && !RefTransformsMatch(sf->glyphs[i],
					      mm->instances[j]->glyphs[i])) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s in font %2$.30s has references with different scaling or rotation (etc.) than in %3$.30s\n",
				sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    } else if (!mm->apple
		       && !KernsMatch(sf->glyphs[i],
				      mm->instances[j]->glyphs[i])) {
	       if (complain) {
		  ErrorMsg(2,"The glyph %1$.30s in font %2$.30s has a different set of kern pairs than in %3$.30s\n",
				"vertical", sf->glyphs[i]->name, sf->fontname,
				mm->instances[j]->fontname);
	       }
	       return (false);
	    }
	 }
	 if (mm->apple && !ContourPtNumMatch(mm, i)) {
	    if (complain) {
	       ErrorMsg(2,"The glyph %1$.30s has a different numbering of points (and control points) on its contours than in the various instances of the font\n",
			     sf->glyphs[i]->name);
	    }
	    return (false);
	 }
	 if (!mm->apple) {
	    for (j=1; j<mm->instance_count; ++j) {
	       if (!HintsMatch
		   (sf->glyphs[i]->hstem,
		    mm->instances[j]->glyphs[i]->hstem)) {
		  if (complain) {
		     ErrorMsg(2,"The %1$s hints in glyph \"%2$.30s\" in font %3$.30s do not match those in %4$.30s (different number or different overlap criteria)\n",
				   "horizontal", sf->glyphs[i]->name,
				   sf->fontname, mm->instances[j]->fontname);
		  }
		  return (false);
	       } else
		  if (!HintsMatch
		      (sf->glyphs[i]->vstem,
		       mm->instances[j]->glyphs[i]->vstem)) {
		  if (complain) {
		     ErrorMsg(2,"The %1$s hints in glyph \"%2$.30s\" in font %3$.30s do not match those in %4$.30s (different number or different overlap criteria)\n",
				   "vertical", sf->glyphs[i]->name,
				   sf->fontname, mm->instances[j]->fontname);
		  }
		  return (false);
	       }
	    }
	    for (j=1; j<mm->instance_count; ++j) {
	       if (!ContourHintMaskMatch
		   (sf->glyphs[i], mm->instances[j]->glyphs[i])) {
		  if (complain) {
		     ErrorMsg(2,"The glyph %1$.30s in font %2$.30s has a different hint mask on its contours than in %3$.30s\n",
				   sf->glyphs[i]->name, sf->fontname,
				   mm->instances[j]->fontname);
		  }
		  return (false);
	       }
	    }
	 }
      }
   }
   if (mm->apple) {
      struct ttf_table *cvt;

      for (cvt=mm->normal->ttf_tables;
	   cvt != NULL && cvt->tag != CHR('c', 'v', 't', ' ');
	   cvt=cvt->next);
      if (cvt==NULL) {
	 for (j=0; j<mm->instance_count; ++j) {
	    if (mm->instances[j]->ttf_tables != NULL) {
	       if (complain)
		  ErrorMsg(2,"The default font does not have a 'cvt ' table, but the instance %.30s does\n",
				mm->instances[j]->fontname);
	       return (false);
	    }
	 }
      } else {
	 /* Not all instances are required to have cvts, but any that do */
	 /*  must be the same size */
	 for (j=0; j<mm->instance_count; ++j) {
	    if (mm->instances[j]->ttf_tables != NULL &&
		(mm->instances[j]->ttf_tables->next != NULL ||
		 mm->instances[j]->ttf_tables->tag != CHR('c', 'v', 't',
							  ' '))) {
	       if (complain)
		  ErrorMsg(2,"Instance fonts may only contain a 'cvt ' table, but %.30s has some other truetype table as well\n",
				mm->instances[j]->fontname);
	       return (false);
	    }
	    if (mm->instances[j]->ttf_tables != NULL &&
		mm->instances[j]->ttf_tables->len != cvt->len) {
	       if (complain)
		  ErrorMsg(2,"The 'cvt ' table in instance %.30s is a different size from that in the default font\n",
				mm->instances[j]->fontname);
	       return (false);
	    }
	 }
      }
   }

   return (true);
}
