summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/autohint.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/autohint.c')
-rw-r--r--Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/autohint.c2143
1 files changed, 2143 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/autohint.c b/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/autohint.c
new file mode 100644
index 00000000000..a55a3d3d637
--- /dev/null
+++ b/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/autohint.c
@@ -0,0 +1,2143 @@
+/* Copyright (C) 2000-2008 by George Williams */
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+
+ * The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "pfaedit.h"
+#include <stdio.h>
+#include <math.h>
+#include "splinefont.h"
+#include "views.h"
+#include "stemdb.h"
+#include <utype.h>
+#include <chardata.h>
+#include "edgelist.h"
+
+/* to create a type 1 font we must come up with the following entries for the
+ private dictionary:
+ BlueValues -- an array of 2n entries where Blue[2i]<Blue[2i+1] max n=7, Blue[i]>0
+ OtherBlues -- (optional) OtherBlue[i]<0
+ (blue zones should be at least 3 units appart)
+ StdHW -- (o) array with one entry, standard hstem height
+ StdVW -- (o) ditto vstem width
+ StemSnapH -- (o) up to 12 numbers containing various hstem heights (includes StdHW), small widths first
+ StemSnapV -- (o) ditto, vstem
+This file has routines to figure out at least some of these
+
+Also other routines to guess at good per-character hints
+*/
+
+static void AddBlue(real val, real array[5], int force) {
+ val = rint(val);
+ if ( !force && (val<array[0]-array[1] || val >array[0]+array[1] ))
+return; /* Outside of one sd */
+ if ( array[3]==0 && array[4]==0 )
+ array[3] = array[4] = val;
+ else if ( val>array[4] )
+ array[4] = val;
+ else if ( val<array[3] )
+ array[3] = val;
+}
+
+static void MergeZones(real zone1[5], real zone2[5]) {
+ if ( zone1[2]!=0 && zone2[2]!=0 &&
+ ((zone1[4]+3>zone2[3] && zone1[3]<=zone2[3]) ||
+ (zone2[4]+3>zone1[3] && zone2[3]<=zone1[3]) )) {
+ if (( zone2[0]<zone1[0]-zone1[1] || zone2[0] >zone1[0]+zone1[1] ) &&
+ ( zone1[0]<zone2[0]-zone2[1] || zone1[0] >zone2[0]+zone2[1] ))
+ /* the means of the zones are too far appart, don't merge em */;
+ else {
+ if ( zone1[0]<zone2[0] ) zone2[0] = zone1[0];
+ if ( zone1[1]>zone2[1] ) zone2[1] = zone1[1];
+ }
+ zone1[2] = 0;
+ }
+}
+
+/* I can deal with latin, greek and cyrillic because the they all come from */
+/* the same set of letter shapes and have all evolved together and have */
+/* various common features (ascenders, descenders, lower case, etc.). Other */
+/* scripts don't fit */
+void FindBlues( SplineFont *sf, int layer, real blues[14], real otherblues[10]) {
+ real caph[5], xh[5], ascenth[5], digith[5], descenth[5], base[5];
+ real otherdigits[5];
+ int i, j, k;
+ DBounds b;
+
+ /* Go through once to get some idea of the average value so we can weed */
+ /* out undesireables */
+ caph[0] = caph[1] = caph[2] = xh[0] = xh[1] = xh[2] = 0;
+ ascenth[0] = ascenth[1] = ascenth[2] = digith[0] = digith[1] = digith[2] = 0;
+ descenth[0] = descenth[1] = descenth[2] = base[0] = base[1] = base[2] = 0;
+ otherdigits[0] = otherdigits[1] = otherdigits[2] = 0;
+ for ( i=0; i<sf->glyphcnt; ++i ) {
+ if ( sf->glyphs[i]!=NULL && sf->glyphs[i]->layers[layer].splines!=NULL ) {
+ int enc = sf->glyphs[i]->unicodeenc;
+ const unichar_t *upt;
+ if ( enc<0x10000 && isalnum(enc) &&
+ ((enc>=32 && enc<128 ) || enc == 0xfe || enc==0xf0 || enc==0xdf ||
+ enc==0x131 ||
+ (enc>=0x391 && enc<=0x3f3 ) ||
+ (enc>=0x400 && enc<=0x4e9 ) )) {
+ /* no accented characters (or ligatures) */
+ if ( unicode_alternates[enc>>8]!=NULL &&
+ (upt =unicode_alternates[enc>>8][enc&0xff])!=NULL &&
+ upt[1]!='\0' )
+ continue;
+ SplineCharFindBounds(sf->glyphs[i],&b);
+ if ( b.miny==0 && b.maxy==0 )
+ continue;
+ if ( enc=='g' || enc=='j' || enc=='p' || enc=='q' || enc=='y' ||
+ enc==0xfe ||
+ enc==0x3c1 /* rho */ ||
+ enc==0x3c6 /* phi */ ||
+ enc==0x3c7 /* chi */ ||
+ enc==0x3c8 /* psi */ ||
+ enc==0x440 /* cyr er */ ||
+ enc==0x443 /* cyr u */ ||
+ enc==0x444 /* cyr ef */) {
+ descenth[0] += b.miny;
+ descenth[1] += b.miny*b.miny;
+ ++descenth[2];
+ } else if ( enc=='x' || enc=='r' || enc=='o' || enc=='e' ||
+ enc=='s' || enc=='c' || enc=='h' || enc=='k' ||
+ enc=='l' || enc=='m' || enc=='n' ||
+ enc==0x3b5 /* epsilon */ ||
+ enc==0x3b9 /* iota */ ||
+ enc==0x3ba /* kappa */ ||
+ enc==0x3bf /* omicron */ ||
+ enc==0x3c3 /* sigma */ ||
+ enc==0x3c5 /* upsilon */ ||
+ enc==0x430 /* cyr a */ ||
+ enc==0x432 /* cyr ve */ ||
+ enc==0x433 /* cyr ge */ ||
+ enc==0x435 /* cyr e */ ||
+ enc==0x436 /* cyr zhe */ ||
+ enc==0x438 /* cyr i */ ||
+ enc==0x43a /* cyr ka */ ||
+ enc==0x43d /* cyr en */ ||
+ enc==0x43e /* cyr o */ ||
+ enc==0x441 /* cyr es */ ||
+ enc==0x445 /* cyr ha */ ||
+ enc==0x447 /* cyr che */ ||
+ enc==0x448 /* cyr sha */ ||
+ enc==0x44f /* cyr ya */ ){
+ base[0] += b.miny;
+ base[1] += b.miny*b.miny;
+ ++base[2];
+ }
+ /* careful of lowercase digits, 6 and 8 should be ascenders */
+ if ( enc=='6' || enc=='8' ) {
+ digith[0] += b.maxy;
+ digith[1] += b.maxy*b.maxy;
+ ++digith[2];
+ } else if ( enc<0x10000 && isdigit(enc) ) {
+ otherdigits[0] += b.maxy;
+ otherdigits[1] += b.maxy*b.maxy;
+ ++otherdigits[2];
+ } else if ( enc<0x10000 && isupper(enc) && enc!=0x462 && enc!=0x490 ) {
+ caph[0] += b.maxy;
+ caph[1] += b.maxy*b.maxy;
+ ++caph[2];
+ } else if ( enc=='b' || enc=='d' || enc=='f' || enc=='h' || enc=='k' ||
+ enc == 'l' || enc==0xf0 || enc==0xfe || enc == 0xdf ||
+ enc == 0x3b2 || enc==0x3b6 || enc==0x3b8 || enc==0x3bb ||
+ enc == 0x3be ||
+ enc == 0x431 /* cyr be */ /* || enc == 0x444 - ef may have varible height */) {
+ ascenth[0] += b.maxy;
+ ascenth[1] += b.maxy*b.maxy;
+ ++ascenth[2];
+ } else if ( enc=='c' || enc=='e' || enc=='o' || enc=='s' || enc=='u' ||
+ enc=='u' || enc=='v' || enc=='w' || enc=='x' || enc=='y' ||
+ enc=='z' ||
+ enc==0x3b5 /* epsilon */ ||
+ enc==0x3b9 /* iota */ ||
+ enc==0x3ba /* kappa */ ||
+ enc==0x3bc /* mu */ ||
+ enc==0x3bd /* nu */ ||
+ enc==0x3bf /* omicron */ ||
+ enc==0x3c0 /* pi */ ||
+ enc==0x3c1 /* rho */ ||
+ enc==0x3c5 /* upsilon */ ||
+ enc==0x433 /* cyr ge */ ||
+ enc==0x435 /* cyr e */ ||
+ enc==0x436 /* cyr zhe */ ||
+ enc==0x438 /* cyr i */ ||
+ enc==0x43b /* cyr el */ ||
+ enc==0x43d /* cyr en */ ||
+ enc==0x43e /* cyr o */ ||
+ enc==0x43f /* cyr pe */ ||
+ enc==0x440 /* cyr er */ ||
+ enc==0x441 /* cyr es */ ||
+ enc==0x442 /* cyr te */ ||
+ enc==0x443 /* cyr u */ ||
+ enc==0x445 /* cyr ha */ ||
+ enc==0x446 /* cyr tse */ ||
+ enc==0x447 /* cyr che */ ||
+ enc==0x448 /* cyr sha */ ||
+ enc==0x449 /* cyr shcha */ ||
+ enc==0x44a /* cyr hard sign */ ||
+ enc==0x44b /* cyr yery */ ||
+ enc==0x44c /* cyr soft sign */ ||
+ enc==0x44d /* cyr reversed e */ ||
+ enc==0x44f /* cyr ya */ ) {
+ xh[0] += b.maxy;
+ xh[1] += b.maxy*b.maxy;
+ ++xh[2];
+ }
+ }
+ }
+ if ( !ff_progress_next())
+ break;
+ }
+ if ( otherdigits[2]>0 && digith[2]>0 ) {
+ if ( otherdigits[0]/otherdigits[2] >= .95*digith[0]/digith[2] ) {
+ /* all digits are about the same height, not lowercase */
+ digith[0] += otherdigits[0];
+ digith[1] += otherdigits[1];
+ digith[2] += otherdigits[2];
+ }
+ }
+
+ if ( xh[2]>1 ) {
+ xh[1] = sqrt((xh[1]-xh[0]*xh[0]/xh[2])/(xh[2]-1));
+ xh[0] /= xh[2];
+ }
+ if ( ascenth[2]>1 ) {
+ ascenth[1] = sqrt((ascenth[1]-ascenth[0]*ascenth[0]/ascenth[2])/(ascenth[2]-1));
+ ascenth[0] /= ascenth[2];
+ }
+ if ( caph[2]>1 ) {
+ caph[1] = sqrt((caph[1]-caph[0]*caph[0]/caph[2])/(caph[2]-1));
+ caph[0] /= caph[2];
+ }
+ if ( digith[2]>1 ) {
+ digith[1] = sqrt((digith[1]-digith[0]*digith[0]/digith[2])/(digith[2]-1));
+ digith[0] /= digith[2];
+ }
+ if ( base[2]>1 ) {
+ base[1] = sqrt((base[1]-base[0]*base[0]/base[2])/(base[2]-1));
+ base[0] /= base[2];
+ }
+ if ( descenth[2]>1 ) {
+ descenth[1] = sqrt((descenth[1]-descenth[0]*descenth[0]/descenth[2])/(descenth[2]-1));
+ descenth[0] /= descenth[2];
+ }
+
+ /* we'll accept values between +/- 1sd of the mean */
+ /* array[0] == mean, array[1] == sd, array[2] == cnt, array[3]=min, array[4]==max */
+ if ( base[0]+base[1]<0 ) base[1] = -base[0]; /* Make sure 0 is within the base bluezone */
+ caph[3] = caph[4] = 0;
+ xh[3] = xh[4] = 0;
+ ascenth[3] = ascenth[4] = 0;
+ digith[3] = digith[4] = 0;
+ descenth[3] = descenth[4] = 0;
+ base[3] = base[4] = 0;
+ for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) {
+ int enc = sf->glyphs[i]->unicodeenc;
+ const unichar_t *upt;
+ if ( enc<0x10000 && isalnum(enc) &&
+ ((enc>=32 && enc<128 ) || enc == 0xfe || enc==0xf0 || enc==0xdf ||
+ (enc>=0x391 && enc<=0x3f3 ) ||
+ (enc>=0x400 && enc<=0x4e9 ) )) {
+ /* no accented characters (or ligatures) */
+ if ( unicode_alternates[enc>>8]!=NULL &&
+ (upt =unicode_alternates[enc>>8][enc&0xff])!=NULL &&
+ upt[1]!='\0' )
+ continue;
+ SplineCharFindBounds(sf->glyphs[i],&b);
+ if ( b.miny==0 && b.maxy==0 )
+ continue;
+ if ( enc=='g' || enc=='j' || enc=='p' || enc=='q' || enc=='y' ||
+ enc==0xfe || enc == 0x3c6 || enc==0x3c8 ||
+ enc==0x440 || enc==0x443 || enc==0x444) {
+ AddBlue(b.miny,descenth,false);
+ } else {
+ /* O and o get forced into the baseline blue value even if they*/
+ /* are beyond 1 sd */
+ AddBlue(b.miny,base,enc=='O' || enc=='o');
+ }
+ if ( enc<0x10000 && isdigit(enc)) {
+ AddBlue(b.maxy,digith,false);
+ } else if ( enc<0x10000 && isupper(enc)) {
+ AddBlue(b.maxy,caph,enc=='O');
+ } else if ( enc=='b' || enc=='d' || enc=='f' || enc=='h' || enc=='k' ||
+ enc == 'l' || enc=='t' || enc==0xf0 || enc==0xfe || enc == 0xdf ||
+ enc == 0x3b2 || enc==0x3b6 || enc==0x3b8 || enc==0x3bb ||
+ enc == 0x3be ||
+ enc == 0x431 ) {
+ AddBlue(b.maxy,ascenth,false);
+ } else if ( enc=='c' || enc=='e' || enc=='o' || enc=='s' || enc=='u' ||
+ enc=='u' || enc=='v' || enc=='w' || enc=='x' || enc=='y' ||
+ enc=='z' ||
+ enc==0x3b5 /* epsilon */ ||
+ enc==0x3b9 /* iota */ ||
+ enc==0x3ba /* kappa */ ||
+ enc==0x3bc /* mu */ ||
+ enc==0x3bd /* nu */ ||
+ enc==0x3bf /* omicron */ ||
+ enc==0x3c0 /* pi */ ||
+ enc==0x3c1 /* rho */ ||
+ enc==0x3c5 /* upsilon */ ||
+ enc==0x433 /* cyr ge */ ||
+ enc==0x435 /* cyr e */ ||
+ enc==0x436 /* cyr zhe */ ||
+ enc==0x438 /* cyr i */ ||
+ enc==0x43b /* cyr el */ ||
+ enc==0x43d /* cyr en */ ||
+ enc==0x43e /* cyr o */ ||
+ enc==0x43f /* cyr pe */ ||
+ enc==0x440 /* cyr er */ ||
+ enc==0x441 /* cyr es */ ||
+ enc==0x442 /* cyr te */ ||
+ enc==0x443 /* cyr u */ ||
+ enc==0x445 /* cyr ha */ ||
+ enc==0x446 /* cyr tse */ ||
+ enc==0x447 /* cyr che */ ||
+ enc==0x448 /* cyr sha */ ||
+ enc==0x449 /* cyr shcha */ ||
+ enc==0x44a /* cyr hard sign */ ||
+ enc==0x44b /* cyr yery */ ||
+ enc==0x44c /* cyr soft sign */ ||
+ enc==0x44d /* cyr reversed e */ ||
+ enc==0x44f /* cyr ya */ ) {
+ AddBlue(b.maxy,xh,enc=='o' || enc=='x');
+ }
+ }
+ }
+
+ /* the descent blue zone merges into the base zone */
+ MergeZones(descenth,base);
+ MergeZones(xh,base);
+ MergeZones(ascenth,caph);
+ MergeZones(digith,caph);
+ MergeZones(xh,caph);
+ MergeZones(ascenth,digith);
+ MergeZones(xh,digith);
+
+ if ( otherblues!=NULL )
+ for ( i=0; i<10; ++i )
+ otherblues[i] = 0;
+ for ( i=0; i<14; ++i )
+ blues[i] = 0;
+
+ if ( otherblues!=NULL && descenth[2]!=0 ) {
+ otherblues[0] = descenth[3];
+ otherblues[1] = descenth[4];
+ }
+ i = 0;
+ if ( base[2]==0 && (xh[2]!=0 || ascenth[2]!=0 || caph[2]!=0 || digith[2]!=0 )) {
+ /* base line blue value must be present if any other value is */
+ /* make one up if we don't have one */
+ blues[0] = -20;
+ blues[1] = 0;
+ i = 2;
+ } else if ( base[2]!=0 ) {
+ blues[0] = base[3];
+ blues[1] = base[4];
+ i = 2;
+ }
+ if ( xh[2]!=0 ) {
+ blues[i++] = xh[3];
+ blues[i++] = xh[4];
+ }
+ if ( caph[2]!=0 ) {
+ blues[i++] = caph[3];
+ blues[i++] = caph[4];
+ }
+ if ( digith[2]!=0 ) {
+ blues[i++] = digith[3];
+ blues[i++] = digith[4];
+ }
+ if ( ascenth[2]!=0 ) {
+ blues[i++] = ascenth[3];
+ blues[i++] = ascenth[4];
+ }
+
+ for ( j=0; j<i; j+=2 ) for ( k=j+2; k<i; k+=2 ) {
+ if ( blues[j]>blues[k] ) {
+ real temp = blues[j];
+ blues[j]=blues[k];
+ blues[k] = temp;
+ temp = blues[j+1];
+ blues[j+1] = blues[k+1];
+ blues[k+1] = temp;
+ }
+ }
+}
+
+void ElFreeEI(EIList *el) {
+ EI *e, *next;
+
+ for ( e = el->edges; e!=NULL; e = next ) {
+ next = e->next;
+ free(e);
+ }
+}
+
+static int EIAddEdge(Spline *spline, real tmin, real tmax, EIList *el) {
+ EI *new = gcalloc(1,sizeof(EI));
+ real min, max, temp;
+ Spline1D *s;
+ real dxdtmin, dxdtmax, dydtmin, dydtmax;
+
+ new->spline = spline;
+ new->tmin = tmin;
+ new->tmax = tmax;
+
+ s = &spline->splines[1];
+ if (( dydtmin = (3*s->a*tmin + 2*s->b)*tmin + s->c )<0 ) dydtmin = -dydtmin;
+ if (( dydtmax = (3*s->a*tmax + 2*s->b)*tmax + s->c )<0 ) dydtmax = -dydtmax;
+ s = &spline->splines[0];
+ if (( dxdtmin = (3*s->a*tmin + 2*s->b)*tmin + s->c )<0 ) dxdtmin = -dxdtmin;
+ if (( dxdtmax = (3*s->a*tmax + 2*s->b)*tmax + s->c )<0 ) dxdtmax = -dxdtmax;
+
+ /*s = &spline->splines[0];*/
+ min = ((s->a * tmin + s->b)* tmin + s->c)* tmin + s->d;
+ max = ((s->a * tmax + s->b)* tmax + s->c)* tmax + s->d;
+ if ( tmax==1 ) max = spline->to->me.x; /* beware rounding errors */
+ if ( !el->leavetiny && floor(min)==floor(max) ) { /* If it doesn't cross a pixel boundary then it might as well be vertical */
+ if ( tmin==0 ) max = min;
+ else if ( tmax==1 ) min = max;
+ else max = min;
+ }
+ if ( min==max )
+ new->vert = true;
+ else if ( min<max )
+ new->hup = true;
+ else {
+ temp = min; min = max; max=temp;
+ }
+ if ( !el->leavetiny && min+1>max ) new->almostvert = true;
+ if ( 40*dxdtmin<dydtmin ) new->vertattmin = true;
+ if ( 40*dxdtmax<dydtmax ) new->vertattmax = true;
+ /*if ( new->vertattmin && new->vertattmax && s->a==0 && s->b==0 ) new->almostvert = true;*/
+ new->coordmin[0] = min; new->coordmax[0] = max;
+ if ( el->coordmin[0]>min )
+ el->coordmin[0] = min;
+ if ( el->coordmax[0]<max )
+ el->coordmax[0] = max;
+
+ s = &spline->splines[1];
+ min = ((s->a * tmin + s->b)* tmin + s->c)* tmin + s->d;
+ max = ((s->a * tmax + s->b)* tmax + s->c)* tmax + s->d;
+ if ( tmax==1 ) max = spline->to->me.y;
+ if ( !el->leavetiny && floor(min)==floor(max) ) { /* If it doesn't cross a pixel boundary then it might as well be horizontal */
+ if ( tmin==0 ) max = min;
+ else if ( tmax==1 ) min = max;
+ else max = min;
+ }
+ if ( min==max )
+ new->hor = true;
+ else if ( min<max )
+ new->vup = true;
+ else {
+ temp = min; min = max; max=temp;
+ }
+ if ( !el->leavetiny && min+1>max ) new->almosthor = true;
+ if ( 40*dydtmin<dxdtmin ) new->horattmin = true;
+ if ( 40*dydtmax<dxdtmax ) new->horattmax = true;
+ /*if ( new->horattmin && new->horattmax && s->a==0 && s->b==0 ) new->almosthor = true;*/
+ new->coordmin[1] = min; new->coordmax[1] = max;
+ if ( el->coordmin[1]>min )
+ el->coordmin[1] = min;
+ if ( el->coordmax[1]<max )
+ el->coordmax[1] = max;
+
+ if ( new->hor && new->vert ) {
+ /* This spline is too small for us to notice */
+ free(new);
+return( false );
+ } else {
+ new->next = el->edges;
+ el->edges = new;
+
+ if ( el->splinelast!=NULL )
+ el->splinelast->splinenext = new;
+ el->splinelast = new;
+ if ( el->splinefirst==NULL )
+ el->splinefirst = new;
+
+return( true );
+ }
+}
+
+static void EIAddSpline(Spline *spline, EIList *el) {
+ extended ts[6], temp;
+ int i, j, base, last;
+
+ ts[0] = 0; ts[5] = 1.0;
+ SplineFindExtrema(&spline->splines[0],&ts[1],&ts[2]);
+ SplineFindExtrema(&spline->splines[1],&ts[3],&ts[4]);
+ /* avoid teeny tiny segments, they just confuse us */
+ SplineRemoveExtremaTooClose(&spline->splines[0],&ts[1],&ts[2]);
+ SplineRemoveExtremaTooClose(&spline->splines[1],&ts[3],&ts[4]);
+ for ( i=0; i<4; ++i ) for ( j=i+1; j<5; ++j ) {
+ if ( ts[i]>ts[j] ) {
+ temp = ts[i];
+ ts[i] = ts[j];
+ ts[j] = temp;
+ }
+ }
+ for ( base=0; ts[base]==-1; ++base);
+ for ( i=5; i>base ; --i ) {
+ if ( ts[i]==ts[i-1] ) {
+ for ( j=i-1; j>base; --j )
+ ts[j] = ts[j-1];
+ ts[j] = -1;
+ ++base;
+ }
+ }
+ last = base;
+ for ( i=base; i<5 ; ++i )
+ if ( EIAddEdge(spline,ts[last],ts[i+1],el) )
+ last = i+1;
+}
+
+void ELFindEdges(SplineChar *sc, EIList *el) {
+ Spline *spline, *first;
+ SplineSet *spl;
+
+ el->sc = sc;
+ if ( sc->layers[el->layer].splines==NULL )
+return;
+
+ el->coordmin[0] = el->coordmax[0] = sc->layers[el->layer].splines->first->me.x;
+ el->coordmin[1] = el->coordmax[1] = sc->layers[el->layer].splines->first->me.y;
+
+ for ( spl = sc->layers[el->layer].splines; spl!=NULL; spl = spl->next ) if ( spl->first->prev!=NULL && spl->first->prev->from!=spl->first ) {
+ first = NULL;
+ for ( spline = spl->first->next; spline!=NULL && spline!=first; spline=spline->to->next ) {
+ EIAddSpline(spline,el);
+ if ( first==NULL ) first = spline;
+ }
+ if ( el->splinefirst!=NULL && spl->first->prev!=NULL )
+ el->splinelast->splinenext = el->splinefirst;
+ el->splinelast = NULL; el->splinefirst = NULL;
+ }
+}
+
+static int IsBiggerSlope(EI *test, EI *base, int major) {
+ int other = !major;
+ real tdo, tdm, bdo, bdm, t;
+
+ if (( major && test->vup ) || (!major && test->hup))
+ t = test->tmin;
+ else
+ t = test->tmax;
+ tdm = (3*test->spline->splines[major].a*t + 2*test->spline->splines[major].b)*t + test->spline->splines[major].c;
+ tdo = (3*test->spline->splines[other].a*t + 2*test->spline->splines[other].b)*t + test->spline->splines[other].c;
+
+ if (( major && base->vup ) || (!major && base->hup))
+ t = base->tmin;
+ else
+ t = base->tmax;
+ bdm = (3*base->spline->splines[major].a*t + 2*base->spline->splines[major].b)*t + base->spline->splines[major].c;
+ bdo = (3*base->spline->splines[other].a*t + 2*base->spline->splines[other].b)*t + base->spline->splines[other].c;
+
+ if ( tdm==0 && bdm==0 )
+return( tdo > bdo );
+ if ( tdo==0 )
+return( tdo>0 );
+ else if ( bdo==0 )
+return( bdo>0 );
+
+return( tdo/tdm > bdo/bdm );
+}
+
+void ELOrder(EIList *el, int major ) {
+ int other = !major;
+ int pos;
+ EI *ei, *prev, *test;
+
+ el->low = floor(el->coordmin[major]); el->high = ceil(el->coordmax[major]);
+ el->cnt = el->high-el->low+1;
+ el->ordered = gcalloc(el->cnt,sizeof(EI *));
+ el->ends = gcalloc(el->cnt,1);
+
+ for ( ei = el->edges; ei!=NULL ; ei=ei->next ) {
+ pos = ceil(ei->coordmax[major])-el->low;
+ el->ends[pos] = true;
+ pos = floor(ei->coordmin[major])-el->low;
+ ei->ocur = (ei->hup == ei->vup)?ei->coordmin[other]:ei->coordmax[other];
+ ei->tcur = ((major && ei->vup) || (!major && ei->hup)) ?
+ ei->tmin: ei->tmax;
+ if ( major ) {
+ ei->up = ei->vup;
+ ei->hv = (ei->vert || ei->almostvert);
+ ei->hvbottom = ei->vup ? ei->vertattmin : ei->vertattmax;
+ ei->hvtop =!ei->vup ? ei->vertattmin : ei->vertattmax;
+ if ( ei->hor || ei->almosthor)
+ continue;
+ } else {
+ ei->up = ei->hup;
+ ei->hv = (ei->hor || ei->almosthor);
+ ei->hvbottom = ei->hup ? ei->horattmin : ei->horattmax;
+ ei->hvtop =!ei->hup ? ei->horattmin : ei->horattmax;
+ if ( ei->vert || ei->almostvert)
+ continue;
+ }
+ if ( el->ordered[pos]==NULL || ei->ocur<el->ordered[pos]->ocur ) {
+ ei->ordered = el->ordered[pos];
+ el->ordered[pos] = ei;
+ } else {
+ for ( prev=el->ordered[pos], test = prev->ordered; test!=NULL;
+ prev = test, test = test->ordered ) {
+ if ( test->ocur>ei->ocur ||
+ (test->ocur==ei->ocur && IsBiggerSlope(test,ei,major)))
+ break;
+ }
+ ei->ordered = test;
+ prev->ordered = ei;
+ }
+ }
+}
+
+static HintInstance *HIMerge(HintInstance *into, HintInstance *hi) {
+ HintInstance *n, *first = NULL, *last = NULL;
+
+ while ( into!=NULL && hi!=NULL ) {
+ if ( into->begin<hi->begin ) {
+ n = into;
+ into = into->next;
+ } else {
+ n = hi;
+ hi = hi->next;
+ }
+ if ( first==NULL )
+ first = n;
+ else
+ last->next = n;
+ last = n;
+ }
+ if ( into!=NULL ) {
+ if ( first==NULL )
+ first = into;
+ else
+ last->next = into;
+ } else if ( hi!=NULL ) {
+ if ( first==NULL )
+ first = hi;
+ else
+ last->next = hi;
+ }
+return( first );
+}
+
+StemInfo *HintCleanup(StemInfo *stem,int dosort,int instance_count) {
+ StemInfo *s, *p=NULL, *t, *pt, *sn;
+ int swap;
+
+ for ( s=stem; s!=NULL; p=s, s=s->next ) {
+ if ( s->width<0 ) {
+ s->start += s->width;
+ s->width = -s->width;
+ s->ghost = true;
+ }
+ s->reordered = false;
+ if ( p!=NULL && p->start> s->start )
+ dosort = true;
+ }
+ if ( dosort ) {
+ for ( p=NULL, s=stem; s!=NULL; p=s, s = sn ) {
+ sn = s->next;
+ for ( pt=s, t=sn; t!=NULL; pt=t, t=t->next ) {
+ if ( instance_count>1 &&
+ t->u.unblended!=NULL && s->u.unblended!=NULL ) {
+ int temp = UnblendedCompare((*t->u.unblended)[0],(*s->u.unblended)[0],instance_count);
+ if ( temp==0 )
+ swap = UnblendedCompare((*t->u.unblended)[1],(*s->u.unblended)[1],instance_count);
+ else
+ swap = temp<0;
+ } else if ( t->start<s->start )
+ swap=true;
+ else if ( t->start>s->start )
+ swap = false;
+ else
+ swap = (t->width<s->width);
+ if ( swap ) {
+ s->next = t->next;
+ if ( pt==s ) {
+ t->next = s;
+ sn = s;
+ } else {
+ t->next = sn;
+ pt->next = s;
+ }
+ if ( p==NULL )
+ stem = t;
+ else
+ p->next = t;
+ pt = s; /* swap s and t */
+ s = t;
+ t = pt;
+ }
+ }
+ }
+ /* Remove duplicates */
+ if ( stem!=NULL ) for ( p=stem, s=stem->next; s!=NULL; s = sn ) {
+ sn = s->next;
+ if ( p->start==s->start && p->width==s->width && p->hintnumber==s->hintnumber ) {
+ p->where = HIMerge(p->where,s->where);
+ s->where = NULL;
+ p->next = sn;
+ StemInfoFree(s);
+ } else
+ p = s;
+ }
+ }
+return( stem );
+}
+
+real EITOfNextMajor(EI *e, EIList *el, real sought_m ) {
+ /* We want to find t so that Mspline(t) = sought_m */
+ /* the curve is monotonic */
+ Spline1D *msp = &e->spline->splines[el->major];
+ real new_t;
+ real found_m;
+ real t_mmax, t_mmin;
+
+ if ( msp->a==0 && msp->b==0 ) {
+ if ( msp->c == 0 ) {
+ IError("Hor/Vert line when not expected");
+return( 0 );
+ }
+ new_t = (sought_m-msp->d)/(msp->c);
+return( new_t );
+ }
+
+ t_mmax = e->up?e->tmax:e->tmin;
+ t_mmin = e->up?e->tmin:e->tmax;
+ /* sought_m += el->low; */
+
+ while ( 1 ) {
+ new_t = (t_mmin+t_mmax)/2;
+ found_m = ( ((msp->a*new_t+msp->b)*new_t+msp->c)*new_t + msp->d );
+ if ( found_m>sought_m-.001 && found_m<sought_m+.001 )
+return( new_t );
+ if ( found_m > sought_m ) {
+ t_mmax = new_t;
+ } else {
+ t_mmin = new_t;
+ }
+ if ( t_mmax==t_mmin ) {
+ IError("EITOfNextMajor failed! on %s", el->sc!=NULL?el->sc->name:"Unknown" );
+return( new_t );
+ }
+ }
+}
+
+EI *EIActiveListReorder(EI *active,int *change) {
+ int any;
+ EI *pr, *apt;
+
+ *change = false;
+ if ( active!=NULL ) {
+ any = true;
+ while ( any ) {
+ any = false;
+ for ( pr=NULL, apt=active; apt->aenext!=NULL; ) {
+ if ( apt->ocur <= apt->aenext->ocur ) {
+ /* still ordered */;
+ pr = apt;
+ apt = apt->aenext;
+ } else if ( pr==NULL ) {
+ active = apt->aenext;
+ apt->aenext = apt->aenext->aenext;
+ active->aenext = apt;
+ *change = true;
+ /* don't need to set any, since this reorder can't disorder the list */
+ pr = active;
+ } else {
+ pr->aenext = apt->aenext;
+ apt->aenext = apt->aenext->aenext;
+ pr->aenext->aenext = apt;
+ any = *change = true;
+ pr = pr->aenext;
+ }
+ }
+ }
+ }
+return( active );
+}
+
+EI *EIActiveEdgesRefigure(EIList *el, EI *active,real i,int major, int *_change) {
+ EI *apt, *pr, *npt;
+ int change = false, subchange;
+ int other = !major;
+
+ /* first remove any entry which doesn't intersect the new scan line */
+ /* (ie. stopped on last line) */
+ for ( pr=NULL, apt=active; apt!=NULL; apt = apt->aenext ) {
+ if ( apt->coordmax[major]<i+el->low ) {
+ if ( pr==NULL )
+ active = apt->aenext;
+ else
+ pr->aenext = apt->aenext;
+ change = true;
+ } else
+ pr = apt;
+ }
+ /* then move the active list to the next line */
+ for ( apt=active; apt!=NULL; apt = apt->aenext ) {
+ Spline1D *osp = &apt->spline->splines[other];
+ apt->tcur = EITOfNextMajor(apt,el,i+el->low);
+ apt->ocur = ( ((osp->a*apt->tcur+osp->b)*apt->tcur+osp->c)*apt->tcur + osp->d );
+ }
+ /* reorder list */
+ active = EIActiveListReorder(active,&subchange);
+ if ( subchange ) change = true;
+
+ /* Insert new nodes */
+ if ( el->ordered[(int) i]!=NULL ) change = true;
+ for ( pr=NULL, apt=active, npt=el->ordered[(int) i]; apt!=NULL && npt!=NULL; ) {
+ if ( npt->ocur<apt->ocur ) {
+ npt->aenext = apt;
+ if ( pr==NULL )
+ active = npt;
+ else
+ pr->aenext = npt;
+ pr = npt;
+ npt = npt->ordered;
+ } else {
+ pr = apt;
+ apt = apt->aenext;
+ }
+ }
+ while ( npt!=NULL ) {
+ npt->aenext = NULL;
+ if ( pr==NULL )
+ active = npt;
+ else
+ pr->aenext = npt;
+ pr = npt;
+ npt = npt->ordered;
+ }
+ *_change = change;
+return( active );
+}
+
+
+int EISkipExtremum(EI *e, real i, int major) {
+ EI *n = e->aenext, *t;
+
+ if ( n==NULL )
+return( false );
+ if (
+ (ceil(e->coordmin[major])==i || floor(e->coordmin[major])==i || floor(e->coordmax[major])==i || ceil(e->coordmax[major])==i) &&
+ (ceil(n->coordmin[major])==i || floor(n->coordmin[major])==i || floor(n->coordmax[major])==i || ceil(n->coordmax[major])==i) ) {
+ if (
+ (n==e->splinenext && n->tmin==e->tmax &&
+ n->tcur<n->tmin+.2 && e->tcur>e->tmax-.2 ) ||
+ (n->splinenext==e && n->tmax==e->tmin &&
+ n->tcur>n->tmax-.2 && e->tcur<e->tmin+.2 ) )
+return( n->up!=e->up );
+ /* can be separated by a horizontal/vertical line in the other direction */
+ if ( n->tmax==1 && e->tmin==0 && n->tcur>.8 && e->tcur<.2) {
+ t = n;
+ while ( (t = t->splinenext)!=e ) {
+ if ( t==NULL || t==n ||
+ (major && !t->hor) || ( !major && !t->vert ))
+return( false );
+ }
+return( n->up!=e->up );
+ } else if ( n->tmin==0 && e->tmax==1 && n->tcur<.2 && e->tcur>.8) {
+ t = e;
+ while ( (t = t->splinenext)!=n ) {
+ if ( t==NULL || t==e ||
+ (major && !t->hor) || ( !major && !t->vert ))
+return( false );
+ }
+return( n->up!=e->up );
+ }
+ }
+return( false );
+}
+
+
+real HIlen( StemInfo *stems) {
+ HintInstance *hi;
+ real len = 0;
+
+ for ( hi=stems->where; hi!=NULL; hi = hi->next )
+ len += hi->end-hi->begin;
+return( len );
+}
+
+real HIoverlap( HintInstance *mhi, HintInstance *thi) {
+ HintInstance *hi;
+ real len = 0;
+ real s, e;
+
+ for ( ; mhi!=NULL; mhi = mhi->next ) {
+ for ( hi = thi; hi!=NULL && hi->begin<=mhi->end; hi = hi->next ) {
+ if ( hi->end<mhi->begin ) {
+ thi = hi;
+ continue;
+ }
+ s = hi->begin<mhi->begin?mhi->begin:hi->begin;
+ e = hi->end>mhi->end?mhi->end:hi->end;
+ if ( e<s )
+ continue; /* Shouldn't happen */
+ len += e-s;
+ }
+ }
+return( len );
+}
+
+
+int StemListAnyConflicts(StemInfo *stems) {
+ StemInfo *s;
+ int any= false;
+ double end;
+
+ for ( s=stems; s!=NULL ; s=s->next )
+ s->hasconflicts = false;
+ while ( stems!=NULL ) {
+ end = stems->width<0 ? stems->start : stems->start+stems->width;
+ for ( s=stems->next; s!=NULL && (s->width>0 ? s->start : s->start+s->width)<end; s=s->next ) {
+ stems->hasconflicts = true;
+ s->hasconflicts = true;
+ any = true;
+ }
+ stems = stems->next;
+ }
+return( any );
+}
+
+static HintInstance *SCGuessHintPoints(SplineChar *sc, int layer, StemInfo *stem,int major, int off) {
+ SplinePoint *starts[20], *ends[20];
+ int spt=0, ept=0;
+ SplinePointList *spl;
+ SplinePoint *sp, *np;
+ int sm, wm, i, j, val;
+ real coord;
+ HintInstance *head, *test, *cur, *prev;
+
+ for ( spl=sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
+ for ( sp=spl->first; ; sp = np ) {
+ coord = (major?sp->me.x:sp->me.y);
+ sm = coord>=stem->start-off && coord<=stem->start+off;
+ wm = coord>=stem->start+stem->width-off && coord<=stem->start+stem->width+off;
+ if ( sm && spt<20 )
+ starts[spt++] = sp;
+ if ( wm && ept<20 )
+ ends[ept++] = sp;
+ if ( sp->next==NULL )
+ break;
+ np = sp->next->to;
+ if ( np==spl->first )
+ break;
+ }
+ }
+
+ head = NULL;
+ for ( i=0; i<spt; ++i ) {
+ val = 0x80000000;
+ for ( j=0; j<ept; ++j ) {
+ if ( major && starts[i]->me.y>=ends[j]->me.y-1 && starts[i]->me.y<=ends[j]->me.y+1 ) {
+ val = starts[i]->me.y;
+ break;
+ } else if ( !major && starts[i]->me.x>=ends[j]->me.x-1 && starts[i]->me.x<=ends[j]->me.x+1 ) {
+ val = starts[i]->me.x;
+ break;
+ }
+ }
+ if ( (unsigned)val!=0x80000000 ) {
+ for ( prev=NULL, test=head; test!=NULL && val>test->begin; prev=test, test=test->next );
+ if ( test==NULL || val!=test->begin ) {
+ cur = chunkalloc(sizeof(HintInstance));
+ cur->begin = cur->end = val;
+ cur->next = test;
+ if ( prev==NULL ) head = cur;
+ else prev->next = cur;
+ }
+ }
+ }
+return( head );
+}
+
+static void SCGuessHintInstancesLight(SplineChar *sc, int layer, StemInfo *stem,int major) {
+ SplinePointList *spl;
+ SplinePoint *sp, *np;
+ int sm, wm, off;
+ real ob, oe;
+ HintInstance *s=NULL, *w=NULL, *cur, *p, *t, *n, *w2;
+ /* We've got a hint (from somewhere, old data, reading in a font, user specified etc.) */
+ /* but we don't have HintInstance info. So see if we can find those data */
+ /* Will get confused by stems with holes in them (for example if you make */
+ /* a hint from one side of an H to the other, it will get the whole thing */
+ /* not just the cross stem) */
+
+ for ( spl=sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
+ for ( sp=spl->first; ; sp = np ) {
+ sm = (major?sp->me.x:sp->me.y)==stem->start;
+ wm = (major?sp->me.x:sp->me.y)==stem->start+stem->width;
+ if ( sp->next==NULL )
+ break;
+ np = sp->next->to;
+ if ( sm || wm ) {
+ if ( !major ) {
+ if ( np->me.y==sp->me.y ) {
+ ob = sp->me.x; oe = np->me.x;
+ } else if ( sp->nextcp.y==sp->me.y ) {
+ ob = sp->me.x; oe = (sp->me.x+sp->nextcp.x)/2;
+ if ( sp->prevcp.y==sp->me.y )
+ ob = (sp->prevcp.x+sp->me.x)/2;
+ } else if ( sp->prevcp.y==sp->me.y ) {
+ ob = sp->me.x; oe = (sp->prevcp.x+sp->me.x)/2;
+ } else
+ sm = wm = false;
+ } else {
+ if ( np->me.x==sp->me.x ) {
+ ob = sp->me.y; oe = np->me.y;
+ } else if ( sp->nextcp.x==sp->me.x ) {
+ ob = sp->me.y; oe = (sp->nextcp.y+sp->me.y)/2;
+ if ( sp->prevcp.x==sp->me.x )
+ ob = (sp->prevcp.y+sp->me.y)/2;
+ } else if ( sp->prevcp.x==sp->me.x ) {
+ ob = sp->me.y; oe = (sp->prevcp.y+sp->me.y)/2;
+ } else
+ sm = wm = false;
+ }
+ }
+ if ( sm || wm ) {
+ cur = chunkalloc(sizeof(HintInstance));
+ if ( ob>oe ) { real temp=ob; ob=oe; oe=temp;}
+ cur->begin = ob;
+ cur->end = oe;
+ if ( sm ) {
+ if ( s==NULL || s->begin>cur->begin ) {
+ cur->next = s;
+ s = cur;
+ } else {
+ p = s;
+ for ( t=s->next; t!=NULL && t->begin<cur->begin; p=t, t=t->next );
+ p->next = cur; cur->next = t;
+ }
+ } else {
+ if ( w==NULL || w->begin>cur->begin ) {
+ cur->next = w;
+ w = cur;
+ } else {
+ p = w;
+ for ( t=w->next; t!=NULL && t->begin<cur->begin; p=t, t=t->next );
+ p->next = cur; cur->next = t;
+ }
+ }
+ }
+ if ( np==spl->first )
+ break;
+ }
+ }
+
+ /* Now we know what the right side of the stem looks like, and we know */
+ /* what the left side looks like. They may not look the same (H for example) */
+ /* Figure out the set where both are active */
+ /* Unless it's a ghost hint */
+ if ( stem->width==20 && s==NULL && w!=NULL ) {
+ s = w;
+ w = NULL;
+ } else if ( stem->width==21 && s!=NULL && w==NULL) {
+ /* Just use s */;
+ } else for ( p=NULL, t=s; t!=NULL; t=n ) {
+ n = t->next;
+ for ( w2=w; w2!=NULL && w2->begin<t->end ; w2=w2->next ) {
+ if ( w2->end<=t->begin )
+ continue;
+ if ( w2->begin<=t->begin && w2->end>=t->end ) {
+ /* Perfect match */
+ break;
+ }
+ if ( w2->begin>=t->begin )
+ t->begin = w2->begin;
+ if ( w2->end<=t->end ) {
+ cur = chunkalloc(sizeof(HintInstance));
+ cur->begin = w2->end;
+ cur->end = t->end;
+ cur->next = n;
+ t->next = cur;
+ n = cur;
+ t->end = w2->end;
+ }
+ break;
+ }
+ if ( w2!=NULL && w2->begin>=t->end )
+ w2 = NULL;
+ if ( w2==NULL && w!=NULL ) {
+ HintInstance *best = NULL;
+ double best_off=1e10, off;
+ for ( w2=w; w2!=NULL ; w2=w2->next ) {
+ if ( w2->end<=t->begin )
+ off = t->begin - w2->end;
+ else
+ off = w2->begin - t->end;
+ if ( best==NULL && off<best_off ) {
+ best = w2;
+ best_off = off;
+ }
+ }
+ if ( best!=NULL && best_off<stem->width ) {
+ w2 = best;
+ if( w2->begin<t->begin )
+ t->begin = w2->begin;
+ if ( w2->end>t->end )
+ t->end = w2->end;
+ }
+ }
+ if ( w2==NULL ) {
+ /* No match for t (or if there were it wasn't complete) get rid */
+ /* of what's left of t */
+ chunkfree(t,sizeof(*t));
+ if ( p==NULL )
+ s = n;
+ else
+ p->next = n;
+ } else
+ p = t;
+ }
+ while ( w!=NULL ) {
+ n = w->next;
+ chunkfree(w,sizeof(*w));
+ w=n;
+ }
+
+ /* If we couldn't find anything, then see if there are two points which */
+ /* have the same x or y value and whose other coordinates match those of */
+ /* the hint */
+ /* Surprisingly many fonts have hints which don't accurately match the */
+ /* points. Perhaps BlueFuzz (default 1) applies here too */
+ for ( off=0; off<1 && s==NULL; ++off )
+ s = SCGuessHintPoints(sc,layer, stem,major,off);
+
+ stem->where = s;
+}
+
+void SCGuessHHintInstancesList(SplineChar *sc,int layer) {
+ StemInfo *h;
+ for ( h= sc->hstem; h!=NULL; h=h->next ) {
+ if ( h->where==NULL ) {
+ SCGuessHintInstancesLight( sc,layer,h,false );
+ }
+ }
+}
+
+void SCGuessVHintInstancesList(SplineChar *sc,int layer) {
+ StemInfo *h;
+ for ( h= sc->vstem; h!=NULL; h=h->next ) {
+ if ( h->where==NULL ) {
+ SCGuessHintInstancesLight( sc,layer,h,true );
+ }
+ }
+}
+
+
+static void _SCClearHintMasks(SplineChar *sc,int layer, int counterstoo) {
+ SplineSet *spl;
+ SplinePoint *sp;
+ RefChar *ref;
+
+ if ( counterstoo ) {
+ free(sc->countermasks);
+ sc->countermasks = NULL; sc->countermask_cnt = 0;
+ }
+
+ for ( spl = sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
+ for ( sp = spl->first ; ; ) {
+ chunkfree(sp->hintmask,sizeof(HintMask));
+ sp->hintmask = NULL;
+ if ( sp->next==NULL )
+ break;
+ sp = sp->next->to;
+ if ( sp==spl->first )
+ break;
+ }
+ }
+
+ for ( ref = sc->layers[layer].refs; ref!=NULL; ref=ref->next ) {
+ for ( spl = ref->layers[0].splines; spl!=NULL; spl=spl->next ) {
+ for ( sp = spl->first ; ; ) {
+ chunkfree(sp->hintmask,sizeof(HintMask));
+ sp->hintmask = NULL;
+ if ( sp->next==NULL )
+ break;
+ sp = sp->next->to;
+ if ( sp==spl->first )
+ break;
+ }
+ }
+ }
+}
+
+
+static void SCFigureSimpleCounterMasks(SplineChar *sc) {
+ SplineChar *scs[MmMax];
+ int hadh3, hadv3, i, vbase;
+ HintMask mask;
+ StemInfo *h;
+
+ if ( sc->countermask_cnt!=0 )
+return;
+
+ scs[0] = sc;
+ hadh3 = CvtPsStem3(NULL,scs,1,true,false);
+ hadv3 = CvtPsStem3(NULL,scs,1,false,false);
+ if ( hadh3 || hadv3 ) {
+ memset(mask,0,sizeof(mask));
+ if ( hadh3 ) mask[0] = 0x80|0x40|0x20;
+ if ( hadv3 ) {
+ for ( h=sc->hstem, vbase=0; h!=NULL; h=h->next, ++vbase );
+ for ( i=0; i<3 ; ++i ) {
+ int j = i+vbase;
+ mask[j>>3] |= (0x80>>(j&7));
+ }
+ }
+ sc->countermask_cnt = 1;
+ sc->countermasks = galloc(sizeof(HintMask));
+ memcpy(sc->countermasks[0],mask,sizeof(HintMask));
+return;
+ }
+}
+
+void SCClearHintMasks(SplineChar *sc,int layer,int counterstoo) {
+ MMSet *mm = sc->parent->mm;
+ int i;
+
+ if ( mm==NULL )
+ _SCClearHintMasks(sc,layer,counterstoo);
+ else {
+ for ( i=0; i<mm->instance_count; ++i ) {
+ if ( sc->orig_pos<mm->instances[i]->glyphcnt )
+ _SCClearHintMasks(mm->instances[i]->glyphs[sc->orig_pos],layer,counterstoo);
+ }
+ if ( sc->orig_pos<mm->normal->glyphcnt )
+ _SCClearHintMasks(mm->normal->glyphs[sc->orig_pos],layer,counterstoo);
+ }
+}
+
+static StemInfo *OnHHint(SplinePoint *sp, StemInfo *s) {
+ StemInfo *possible=NULL;
+ HintInstance *hi;
+
+ if ( sp==NULL )
+return( NULL );
+
+ for ( ; s!=NULL; s=s->next ) {
+ if ( sp->me.y<s->start )
+return( possible );
+ if ( s->start==sp->me.y || s->start+s->width==sp->me.y ) {
+ if ( !s->hasconflicts )
+return( s );
+ for ( hi=s->where; hi!=NULL; hi=hi->next ) {
+ if ( hi->begin<=sp->me.x && hi->end>=sp->me.x )
+return( s );
+ }
+ if ( !s->used )
+ possible = s;
+ }
+ }
+return( possible );
+}
+
+static StemInfo *OnVHint(SplinePoint *sp, StemInfo *s) {
+ StemInfo *possible=NULL;
+ HintInstance *hi;
+
+ if ( sp==NULL )
+return( NULL );
+
+ for ( ; s!=NULL; s=s->next ) {
+ if ( sp->me.x<s->start )
+return( possible );
+ if ( s->start==sp->me.x || s->start+s->width==sp->me.x ) {
+ if ( !s->hasconflicts )
+return( s );
+ for ( hi=s->where; hi!=NULL; hi=hi->next ) {
+ if ( hi->begin<=sp->me.y && hi->end>=sp->me.y )
+return( s );
+ }
+ if ( !s->used )
+ possible = s;
+ }
+ }
+return( possible );
+}
+
+/* Does h have a conflict with any of the stems in the list which have bits */
+/* set in the mask */
+static int ConflictsWithMask(StemInfo *stems, HintMask mask,StemInfo *h) {
+ while ( stems!=NULL && stems->start<h->start+h->width ) {
+ if ( stems->start+stems->width>=h->start && stems!=h ) {
+ if ( stems->hintnumber!=-1 &&
+ (mask[stems->hintnumber>>3]&(0x80>>(stems->hintnumber&7))) )
+return( true );
+ }
+ stems = stems->next;
+ }
+return( false );
+}
+
+/* All instances of a MM set must have the same hint mask at all points */
+static void FigureHintMask(SplineChar *scs[MmMax], SplinePoint *to[MmMax], int instance_count,
+ HintMask mask) {
+ StemInfo *s;
+ int i;
+ SplinePoint *sp;
+
+ memset(mask,'\0',sizeof(HintMask));
+
+ /* Install all hints that are always active */
+ i=0; {
+ SplineChar *sc = scs[i];
+
+ if ( sc==NULL )
+return;
+
+ for ( s=sc->hstem; s!=NULL; s=s->next )
+ if ( s->hintnumber!=-1 && !s->hasconflicts )
+ mask[s->hintnumber>>3] |= (0x80>>(s->hintnumber&7));
+ for ( s=sc->vstem; s!=NULL; s=s->next )
+ if ( s->hintnumber!=-1 && !s->hasconflicts )
+ mask[s->hintnumber>>3] |= (0x80>>(s->hintnumber&7));
+
+ if ( sc->hconflicts ) {
+ for ( sp=to[i]; sp!=NULL; ) {
+ s = OnHHint(sp,sc->hstem);
+ if ( s!=NULL && s->hintnumber!=-1 ) {
+ if ( ConflictsWithMask(scs[i]->hstem,mask,s))
+ break;
+ mask[s->hintnumber>>3] |= (0x80>>(s->hintnumber&7));
+ }
+ if ( sp->next==NULL )
+ break;
+ sp = sp->next->to;
+ if ( to[i]==sp )
+ break;
+ }
+ }
+ if ( sc->vconflicts ) {
+ for ( sp=to[i]; sp!=NULL; ) {
+ s = OnVHint(sp,sc->vstem);
+ if ( s!=NULL && s->hintnumber!=-1 ) {
+ if ( ConflictsWithMask(scs[i]->vstem,mask,s))
+ break;
+ mask[s->hintnumber>>3] |= (0x80>>(s->hintnumber&7));
+ }
+ if ( sp->next==NULL )
+ break;
+ sp = sp->next->to;
+ if ( to[i]==sp )
+ break;
+ }
+ }
+ }
+ for ( i=0; i<instance_count; ++i ) if ( to[i]!=NULL ) {
+ chunkfree(to[i]->hintmask,sizeof(HintMask));
+ to[i]->hintmask = chunkalloc(sizeof(HintMask));
+ memcpy(to[i]->hintmask,mask,sizeof(HintMask));
+ }
+}
+
+static int TestHintMask(SplineChar *scs[MmMax], SplinePoint *to[MmMax], int instance_count,
+ HintMask mask) {
+ StemInfo *h=NULL, *v=NULL;
+ int i;
+
+ for ( i=0; i<instance_count; ++i ) {
+ SplineChar *sc = scs[i];
+
+ if ( sc==NULL || (!sc->hconflicts && !sc->vconflicts ))
+ continue;
+
+ /* Does this point lie on any hints? */
+ if ( scs[i]->hconflicts )
+ h = OnHHint(to[i],sc->hstem);
+ if ( scs[i]->vconflicts )
+ v = OnVHint(to[i],sc->vstem);
+
+ /* Need to set this hint */
+ if ( (h!=NULL && h->hintnumber!=-1 && (mask[h->hintnumber>>3]&(0x80>>(h->hintnumber&7)))==0 ) ||
+ (v!=NULL && v->hintnumber!=-1 && (mask[v->hintnumber>>3]&(0x80>>(v->hintnumber&7)))==0 ))
+ break;
+ }
+ if ( i==instance_count ) /* All hint masks were ok */
+return( false );
+
+ FigureHintMask(scs,to,instance_count,mask);
+return( true );
+}
+
+static void UnnumberHints(SplineChar *sc) {
+ StemInfo *h;
+
+ for ( h=sc->hstem; h!=NULL; h=h->next )
+ h->hintnumber = -1;
+ for ( h=sc->vstem; h!=NULL; h=h->next )
+ h->hintnumber = -1;
+}
+
+static int NumberHints(SplineChar *sc) {
+ StemInfo *h;
+ int hcnt=0;
+
+ for ( h=sc->hstem; h!=NULL; h=h->next )
+ h->hintnumber = hcnt>=HntMax ? -1 : hcnt++;
+ for ( h=sc->vstem; h!=NULL; h=h->next )
+ h->hintnumber = hcnt>=HntMax ? -1 : hcnt++;
+return( hcnt );
+}
+
+static void UntickHints(SplineChar *sc) {
+ StemInfo *h;
+
+ for ( h=sc->hstem; h!=NULL; h=h->next )
+ h->used = false;
+ for ( h=sc->vstem; h!=NULL; h=h->next )
+ h->used = false;
+}
+
+struct coords {
+ real coords[MmMax];
+ struct coords *next;
+};
+
+typedef struct mmh {
+ StemInfo *hints[MmMax], *map[MmMax];
+ struct coords *where;
+ struct mmh *next;
+} MMH;
+
+static void AddCoord(MMH *mmh,SplinePoint *sps[MmMax],int instance_count, int ish) {
+ struct coords *coords;
+ int i;
+
+ coords = chunkalloc(sizeof(struct coords));
+ coords->next = mmh->where;
+ mmh->where = coords;
+ if ( ish )
+ for ( i=0; i<instance_count; ++i )
+ coords->coords[i] = sps[i]->me.x;
+ else
+ for ( i=0; i<instance_count; ++i )
+ coords->coords[i] = sps[i]->me.y;
+}
+
+static MMH *AddHintSet(MMH *hints,StemInfo *h[MmMax], int instance_count,
+ SplinePoint *sps[MmMax], int ish) {
+ int i, cnt, bestc;
+ MMH *test, *best;
+
+ for ( i=0; i<instance_count; ++i )
+ if ( h[i]==NULL )
+return( hints );
+
+ best = NULL; bestc = 0;
+ for ( test=hints; test!=NULL; test=test->next ) {
+ cnt = 0;
+ for ( i=0; i<instance_count; ++i )
+ if ( test->hints[i]==h[i] )
+ ++cnt;
+ if ( cnt==instance_count ) {
+ AddCoord(test,sps,instance_count,ish);
+return( hints );
+ }
+ if ( cnt>bestc ) {
+ bestc = cnt;
+ best = test;
+ }
+ }
+ test = chunkalloc(sizeof(MMH));
+ test->next = hints;
+ AddCoord(test,sps,instance_count,ish);
+ for ( i=0; i<instance_count; ++i )
+ test->hints[i]=h[i];
+ if ( bestc!=0 ) {
+ for ( i=0; i<instance_count; ++i ) {
+ if ( best->hints[i]==h[i] ) {
+ h[i]->hasconflicts = true;
+ test->map[i] = chunkalloc(sizeof(StemInfo));
+ *test->map[i] = *h[i];
+ test->map[i]->where = NULL;
+ test->map[i]->used = true;
+ h[i]->next = test->map[i];
+ } else
+ test->map[i] = h[i];
+ }
+ } else {
+ for ( i=0; i<instance_count; ++i )
+ test->map[i]=h[i];
+ }
+return( test );
+}
+
+static int CompareMMH(MMH *mmh1,MMH *mmh2, int instance_count) {
+ int i;
+
+ if ( mmh1->map[0]==NULL )
+return( 1 );
+ if ( mmh2->map[0]==NULL )
+return( -1 );
+
+ for ( i=0; i<instance_count; ++i ) {
+ if ( mmh1->map[i]->start!=mmh2->map[i]->start ) {
+ if ( mmh1->map[i]->start > mmh2->map[i]->start )
+return( 1 );
+ else
+return( -1 );
+ }
+ }
+ for ( i=0; i<instance_count; ++i ) {
+ if ( mmh1->map[i]->width!=mmh2->map[i]->width ) {
+ if ( mmh1->map[i]->width > mmh2->map[i]->width )
+return( 1 );
+ else
+return( -1 );
+ }
+ }
+return( 0 );
+}
+
+static MMH *SortMMH(MMH *head,int instance_count) {
+ MMH *mmh, *p, *smallest, *psmallest, *test, *ptest;
+
+ for ( mmh = head, p=NULL; mmh!=NULL ; ) {
+ smallest = mmh; psmallest = p;
+ ptest = mmh; test = mmh->next;
+ while ( test!=NULL ) {
+ if ( CompareMMH(test,smallest,instance_count)<0 ) {
+ smallest = test;
+ psmallest = ptest;
+ }
+ ptest = test;
+ test = test->next;
+ }
+ if ( smallest!=mmh ) {
+ if ( p==NULL )
+ head = smallest;
+ else
+ p->next = smallest;
+ if ( mmh->next==smallest ) {
+ mmh->next = smallest->next;
+ smallest->next = mmh;
+ } else {
+ test = mmh->next;
+ mmh->next = smallest->next;
+ smallest->next = test;
+ psmallest->next = mmh;
+ }
+ }
+ p = smallest;
+ mmh = smallest->next;
+ }
+return( head );
+}
+
+static int NumberMMH(MMH *mmh,int hstart,int instance_count) {
+ int i;
+ HintInstance *hi, *n;
+ struct coords *coords;
+
+ while ( mmh!=NULL ) {
+ for ( i=0; i<instance_count; ++i ) {
+ StemInfo *h = mmh->map[i];
+ if ( h==NULL )
+ continue;
+
+ h->hintnumber = hstart;
+
+ for ( hi=h->where; hi!=NULL; hi=n ) {
+ n = hi->next;
+ chunkfree(hi,sizeof(HintInstance));
+ }
+ h->where = NULL;
+ for ( coords=mmh->where; coords!=NULL; coords = coords->next ) {
+ hi = chunkalloc(sizeof(HintInstance));
+ hi->next = h->where;
+ h->where = hi;
+ hi->begin = coords->coords[i]-1;
+ hi->end = coords->coords[i]+1;
+ }
+ }
+ if ( mmh->map[0]!=NULL ) ++hstart;
+ mmh = mmh->next;
+ }
+return( hstart );
+}
+
+static void SortMMH2(SplineChar *scs[MmMax],MMH *mmh,int instance_count,int ish) {
+ int i;
+ StemInfo *h, *n;
+ MMH *m;
+
+ for ( i=0; i<instance_count; ++i ) {
+ for ( h= ish ? scs[i]->hstem : scs[i]->vstem; h!=NULL; h=n ) {
+ n = h->next;
+ if ( h->hintnumber==-1 )
+ StemInfoFree(h);
+ }
+ n = NULL;
+ for ( m = mmh ; m!=NULL; m=m->next ) {
+ h = m->map[i];
+ if ( n!=NULL )
+ n->next = h;
+ else if ( ish )
+ scs[i]->hstem = h;
+ else
+ scs[i]->vstem = h;
+ n = h;
+ }
+ if ( n!=NULL )
+ n->next = NULL;
+ else if ( ish )
+ scs[i]->hstem = NULL;
+ else
+ scs[i]->vstem = NULL;
+ }
+}
+
+static void MMHFreeList(MMH *mmh) {
+ MMH *mn;
+ struct coords *c, *n;
+
+ for ( ; mmh!=NULL; mmh = mn ) {
+ mn = mmh->next;
+ for ( c=mmh->where; c!=NULL; c=n ) {
+ n = c->next;
+ chunkfree(c,sizeof(struct coords));
+ }
+ chunkfree(mmh,sizeof(struct coords));
+ }
+}
+
+static void SplResolveSplitHints(SplineChar *scs[MmMax], SplineSet *spl[MmMax],
+ int instance_count, MMH **hs, MMH **vs) {
+ SplinePoint *to[MmMax];
+ StemInfo *h[MmMax], *v[MmMax];
+ int i, anymore;
+
+ forever {
+ for ( i=0; i<instance_count; ++i ) {
+ if ( spl[i]!=NULL )
+ to[i] = spl[i]->first;
+ else
+ to[i] = NULL;
+ }
+ forever {
+ for ( i=0; i<instance_count; ++i ) {
+ h[i] = OnHHint(to[i],scs[i]->hstem);
+ v[i] = OnVHint(to[i],scs[i]->vstem);
+ }
+ *hs = AddHintSet(*hs,h,instance_count,to,true);
+ *vs = AddHintSet(*vs,v,instance_count,to,false);
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) if ( to[i]!=NULL ) {
+ if ( to[i]->next==NULL ) to[i] = NULL;
+ else {
+ to[i] = to[i]->next->to;
+ if ( to[i]==spl[i]->first ) to[i] = NULL;
+ }
+ if ( to[i]!=NULL ) anymore = true;
+ }
+ if ( !anymore )
+ break;
+ }
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) {
+ if ( spl[i]!=NULL )
+ spl[i] = spl[i]->next;
+ if ( spl[i]!=NULL ) anymore = true;
+ }
+ if ( !anymore )
+ break;
+ }
+}
+
+static void ResolveSplitHints(SplineChar *scs[16],int layer,int instance_count) {
+ /* It is possible for a single hint in one mm instance to split into two */
+ /* in a different MM set. For example, we have two stems which happen */
+ /* to line up in one instance but which do not in another instance. */
+ /* It is even possible that there could be no instance with any conflicts */
+ /* but some of the intermediate forms might conflict. */
+ /* We can't deal (nor can postscript) with the case where hints change order*/
+ SplinePointList *spl[MmMax];
+ RefChar *ref[MmMax];
+ int i, hcnt, hmax=0, anymore;
+ MMH *hs=NULL, *vs=NULL;
+
+ for ( i=0; i<instance_count; ++i ) {
+ hcnt = NumberHints(scs[i]);
+ UntickHints(scs[i]);
+ if ( hcnt>hmax ) hmax = hcnt;
+ spl[i] = scs[i]->layers[layer].splines;
+ }
+ if ( hmax==0 )
+return;
+
+ SplResolveSplitHints(scs,spl,instance_count,&hs,&vs);
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) {
+ ref[i] = scs[i]->layers[layer].refs;
+ if ( ref[i]!=NULL ) anymore = true;
+ }
+ while ( anymore ) {
+ for ( i=0; i<instance_count; ++i )
+ spl[i] = ( ref[i]!=NULL ) ? ref[i]->layers[0].splines : NULL;
+ SplResolveSplitHints(scs,spl,instance_count,&hs,&vs);
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) {
+ if ( ref[i]!=NULL ) {
+ ref[i] = ref[i]->next;
+ if ( ref[i]!=NULL ) anymore = true;
+ }
+ }
+ }
+
+ for ( i=0; i<instance_count; ++i )
+ UnnumberHints(scs[i]);
+ hs = SortMMH(hs,instance_count);
+ vs = SortMMH(vs,instance_count);
+ hcnt = NumberMMH(hs,0,instance_count);
+ hcnt = NumberMMH(vs,hcnt,instance_count);
+ SortMMH2(scs,hs,instance_count,true);
+ SortMMH2(scs,vs,instance_count,false);
+ MMHFreeList(hs);
+ MMHFreeList(vs);
+}
+
+static int SplFigureHintMasks(SplineChar *scs[MmMax], SplineSet *spl[MmMax],
+ int instance_count, HintMask mask, int inited) {
+ SplinePoint *to[MmMax];
+ int i, anymore;
+
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) {
+ if ( spl[i]!=NULL ) {
+ SplineSetReverse(spl[i]);
+ to[i] = spl[i]->first;
+ anymore = true;
+ } else
+ to[i] = NULL;
+ }
+
+ /* Assign the initial hint mask */
+ if ( anymore && !inited ) {
+ FigureHintMask(scs,to,instance_count,mask);
+ inited = true;
+ }
+
+ forever {
+ for ( i=0; i<instance_count; ++i ) {
+ if ( spl[i]!=NULL )
+ to[i] = spl[i]->first;
+ else
+ to[i] = NULL;
+ }
+ forever {
+ TestHintMask(scs,to,instance_count,mask);
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) if ( to[i]!=NULL ) {
+ if ( to[i]->next==NULL ) to[i] = NULL;
+ else {
+ to[i] = to[i]->next->to;
+ if ( to[i]==spl[i]->first ) to[i] = NULL;
+ }
+ if ( to[i]!=NULL ) anymore = true;
+ }
+ if ( !anymore )
+ break;
+ }
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) {
+ if ( spl[i]!=NULL ) {
+ SplineSetReverse(spl[i]);
+ spl[i] = spl[i]->next;
+ }
+ if ( spl[i]!=NULL ) {
+ anymore = true;
+ SplineSetReverse(spl[i]);
+ }
+ }
+ if ( !anymore )
+ break;
+ }
+return( inited );
+}
+
+void SCFigureHintMasks(SplineChar *sc,int layer) {
+ SplineChar *scs[MmMax];
+ SplinePointList *spl[MmMax];
+ RefChar *ref[MmMax];
+ MMSet *mm = sc->parent->mm;
+ int i, instance_count, conflicts, anymore, inited;
+ HintMask mask;
+
+ if ( mm==NULL ) {
+ scs[0] = sc;
+ instance_count = 1;
+ SCClearHintMasks(sc,layer,false);
+ } else {
+ if ( mm->apple )
+return;
+ instance_count = mm->instance_count;
+ for ( i=0; i<instance_count; ++i )
+ if ( sc->orig_pos < mm->instances[i]->glyphcnt ) {
+ scs[i] = mm->instances[i]->glyphs[sc->orig_pos];
+ SCClearHintMasks(scs[i],layer,false);
+ }
+ ResolveSplitHints(scs,layer,instance_count);
+ }
+ conflicts = false;
+ for ( i=0; i<instance_count; ++i ) {
+ NumberHints(scs[i]);
+ if ( scs[i]->hconflicts || scs[i]->vconflicts )
+ conflicts = true;
+ }
+ if ( !conflicts && instance_count==1 ) { /* All hints always active */
+ SCFigureSimpleCounterMasks(sc);
+return; /* In an MM font we may still need to resolve things like different numbers of hints */
+ }
+
+ for ( i=0; i<instance_count; ++i ) {
+ spl[i] = scs[i]->layers[layer].splines;
+ ref[i] = scs[i]->layers[layer].refs;
+ }
+ inited = SplFigureHintMasks(scs,spl,instance_count,mask,false);
+ forever {
+ for ( i=0; i<instance_count; ++i ) {
+ if ( ref[i]!=NULL )
+ spl[i] = ref[i]->layers[0].splines;
+ }
+ inited = SplFigureHintMasks(scs,spl,instance_count,mask,inited);
+ anymore = false;
+ for ( i=0; i<instance_count; ++i ) {
+ if ( ref[i]!=NULL ) {
+ ref[i] = ref[i]->next;
+ if ( ref[i]!=NULL ) anymore = true;
+ }
+ }
+ if ( !anymore )
+ break;
+ }
+ if ( instance_count==1 )
+ SCFigureSimpleCounterMasks(sc);
+}
+
+
+
+static void FigureStems( SplineFont *sf, real snaps[12], real cnts[12],
+ int which ) {
+ int i, j, k, cnt, smax=0, smin=2000;
+ real stemwidths[2000];
+ StemInfo *stems, *test;
+ int len;
+ HintInstance *hi;
+
+ memset(stemwidths,'\0',sizeof(stemwidths));
+
+ for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) {
+ stems = which?sf->glyphs[i]->hstem:sf->glyphs[i]->vstem;
+ for ( test=stems; test!=NULL; test = test->next ) if ( !test->ghost ) {
+ if ( (j=test->width)<0 ) j= -j;
+ if ( j<2000 ) {
+ len = 0;
+ for ( hi=test->where; hi!=NULL; hi=hi->next )
+ len += hi->end-hi->begin;
+ if ( len==0 ) len = 100;
+ stemwidths[j] += len;
+ if ( smax<j ) smax=j;
+ if ( smin>j ) smin=j;
+ }
+ }
+ }
+
+ for ( i=smin, cnt=0; i<=smax; ++i ) {
+ if ( stemwidths[i]!=0 )
+ ++cnt;
+ }
+
+ if ( cnt>12 ) {
+ /* Merge width windows */
+ int windsize=3, j;
+ for ( i=smin; i<=smax; ++i ) if ( stemwidths[i]!=0 ) {
+ if ( (j = i-windsize)<0 ) j=0;
+ for ( ; j<smax && j<=i+windsize; ++j )
+ if ( stemwidths[i]<stemwidths[j] )
+ break;
+ if ( j==smax || j>i+windsize ) {
+ if ( (j = i-windsize)<0 ) j=0;
+ for ( ; j<smax && j<=i+windsize; ++j ) if ( j!=i ) {
+ stemwidths[i] += stemwidths[j];
+ stemwidths[j] = 0;
+ }
+ }
+ }
+ /* Merge adjacent widths */
+ for ( i=smin; i<=smax; ++i ) {
+ if ( stemwidths[i]!=0 && i<=smax-1 && stemwidths[i+1]!=0 ) {
+ if ( stemwidths[i]>stemwidths[i+1] ) {
+ stemwidths[i] += stemwidths[i+1];
+ stemwidths[i+1] = 0;
+ } else {
+ if ( i<=smax-2 && stemwidths[i+2] && stemwidths[i+2]<stemwidths[i+1] ) {
+ stemwidths[i+1] += stemwidths[i+2];
+ stemwidths[i+2] = 0;
+ }
+ stemwidths[i+1] += stemwidths[i];
+ stemwidths[i] = 0;
+ ++i;
+ }
+ }
+ }
+ for ( i=smin, cnt=0; i<=smax; ++i ) {
+ if ( stemwidths[i]!=0 )
+ ++cnt;
+ }
+ }
+ if ( cnt<=12 ) {
+ for ( i=smin, cnt=0; i<=smax; ++i ) {
+ if ( stemwidths[i]!=0 ) {
+ snaps[cnt] = i;
+ cnts[cnt++] = stemwidths[i];
+ }
+ }
+ } else { real firstbiggest=0;
+ for ( cnt = 0; cnt<12; ++cnt ) {
+ int biggesti=0;
+ real biggest=0;
+ for ( i=smin; i<=smax; ++i ) {
+ if ( stemwidths[i]>biggest ) { biggest = stemwidths[i]; biggesti=i; }
+ }
+ /* array must be sorted */
+ if ( biggest<firstbiggest/6 )
+ break;
+ for ( j=0; j<cnt; ++j )
+ if ( snaps[j]>biggesti )
+ break;
+ for ( k=cnt-1; k>=j; --k ) {
+ snaps[k+1] = snaps[k];
+ cnts[k+1]=cnts[k];
+ }
+ snaps[j] = biggesti;
+ cnts[j] = biggest;
+ stemwidths[biggesti] = 0;
+ if ( firstbiggest==0 ) firstbiggest = biggest;
+ }
+ }
+ for ( ; cnt<12; ++cnt ) {
+ snaps[cnt] = 0;
+ cnts[cnt] = 0;
+ }
+}
+
+void FindHStems( SplineFont *sf, real snaps[12], real cnt[12]) {
+ FigureStems(sf,snaps,cnt,1);
+}
+
+void FindVStems( SplineFont *sf, real snaps[12], real cnt[12]) {
+ FigureStems(sf,snaps,cnt,0);
+}
+
+static int IsFlexSmooth(SplinePoint *sp) {
+ BasePoint nvec, pvec;
+ double proj_same, proj_normal;
+
+ if ( sp->nonextcp || sp->noprevcp )
+return( false ); /* No continuity of slopes */
+
+ nvec.x = sp->nextcp.x - sp->me.x; nvec.y = sp->nextcp.y - sp->me.y;
+ pvec.x = sp->me.x - sp->prevcp.x; pvec.y = sp->me.y - sp->prevcp.y;
+
+ /* Avoid cases where the slopes are 180 out of phase */
+ if ( (proj_same = nvec.x*pvec.x + nvec.y*pvec.y)<=0 )
+return( false );
+ if ( (proj_normal = nvec.x*pvec.y - nvec.y*pvec.x)<0 )
+ proj_normal = -proj_normal;
+
+ /* Something is smooth if the normal projection is 0. Let's allow for */
+ /* some rounding errors */
+ if ( proj_same >= 16*proj_normal )
+return( true );
+
+return( false );
+}
+
+static int _SplineCharIsFlexible(SplineChar *sc, int layer, int blueshift) {
+ /* Need two splines
+ outer endpoints have same x (or y) values
+ inner point must be less than 20 horizontal (v) units from the outer points
+ inner point must also be less than BlueShift units (defaults to 7=>6)
+ (can increase BlueShift up to 21)
+ the inner point must be a local extremum
+ the inner point's cps must be at the x (or y) value as the extremum
+ (I think)
+ */
+ /* We want long, nearly straight stems. If the end-points should not have
+ continuous slopes, or if they do, they must be horizontal/vertical.
+ This is an heuristic requirement, not part of Adobe's spec.
+ */
+ SplineSet *spl;
+ SplinePoint *sp, *np, *pp;
+ int max=0, val;
+ RefChar *r;
+
+ if ( sc==NULL )
+return(false);
+
+ for ( spl = sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
+ if ( spl->first->prev==NULL ) {
+ /* Mark everything on the open path as inflexible */
+ sp=spl->first;
+ while ( 1 ) {
+ sp->flexx = sp->flexy = false;
+ if ( sp->next==NULL )
+ break;
+ sp = sp->next->to;
+ }
+ continue; /* Ignore open paths */
+ }
+ sp=spl->first;
+ do {
+ if ( sp->next==NULL || sp->prev==NULL )
+ break;
+ np = sp->next->to;
+ pp = sp->prev->from;
+ if ( !pp->flexx && !pp->flexy ) {
+ sp->flexy = sp->flexx = 0;
+ val = 0;
+ if ( RealNear(sp->nextcp.x,sp->me.x) &&
+ RealNear(sp->prevcp.x,sp->me.x) &&
+ RealNear(np->me.x,pp->me.x) &&
+ !RealNear(np->me.x,sp->me.x) &&
+ (!IsFlexSmooth(pp) || RealNear(pp->nextcp.x,pp->me.x)) &&
+ (!IsFlexSmooth(np) || RealNear(np->prevcp.x,np->me.x)) &&
+ np->me.x-sp->me.x < blueshift &&
+ np->me.x-sp->me.x > -blueshift ) {
+ if ( (np->me.x>sp->me.x &&
+ np->prevcp.x<=np->me.x && np->prevcp.x>=sp->me.x &&
+ pp->nextcp.x<=pp->me.x && pp->prevcp.x>=sp->me.x ) ||
+ (np->me.x<sp->me.x &&
+ np->prevcp.x>=np->me.x && np->prevcp.x<=sp->me.x &&
+ pp->nextcp.x>=pp->me.x && pp->prevcp.x<=sp->me.x )) {
+ sp->flexx = true;
+ val = np->me.x-sp->me.x;
+ }
+ }
+ if ( RealNear(sp->nextcp.y,sp->me.y) &&
+ RealNear(sp->prevcp.y,sp->me.y) &&
+ RealNear(np->me.y,pp->me.y) &&
+ !RealNear(np->me.y,sp->me.y) &&
+ (!IsFlexSmooth(pp) || RealNear(pp->nextcp.y,pp->me.y)) &&
+ (!IsFlexSmooth(np) || RealNear(np->prevcp.y,np->me.y)) &&
+ np->me.y-sp->me.y < blueshift &&
+ np->me.y-sp->me.y > -blueshift ) {
+ if ( (np->me.y>sp->me.y &&
+ np->prevcp.y<=np->me.y && np->prevcp.y>=sp->me.y &&
+ pp->nextcp.y<=pp->me.y && pp->nextcp.y>=sp->me.y ) ||
+ (np->me.y<sp->me.y &&
+ np->prevcp.y>=np->me.y && np->prevcp.y<=sp->me.y &&
+ pp->nextcp.y>=pp->me.y && pp->nextcp.y<=sp->me.y )) {
+ sp->flexy = true;
+ val = np->me.y-sp->me.y;
+ }
+ }
+ if ( val<0 ) val = -val;
+ if ( val>max ) max = val;
+ }
+ sp = np;
+ } while ( sp!=spl->first );
+ }
+ sc->layers[layer].anyflexes = max>0;
+ if ( max==0 )
+ for ( r = sc->layers[layer].refs; r!=NULL ; r=r->next )
+ if ( r->sc->layers[layer].anyflexes ) {
+ sc->layers[layer].anyflexes = true;
+ break;
+ }
+return( max );
+}
+
+
+
+static void SCUnflex(SplineChar *sc, int layer) {
+ SplineSet *spl;
+ SplinePoint *sp;
+
+ for ( spl = sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
+ /* Mark everything on the path as inflexible */
+ sp=spl->first;
+ while ( 1 ) {
+ sp->flexx = sp->flexy = false;
+ if ( sp->next==NULL )
+ break;
+ sp = sp->next->to;
+ if ( sp==spl->first )
+ break;
+ }
+ }
+ sc->layers[layer].anyflexes = false;
+}
+
+static void FlexDependents(SplineChar *sc,int layer) {
+ struct splinecharlist *scl;
+
+ sc->layers[layer].anyflexes = true;
+ for ( scl = sc->dependents; scl!=NULL; scl=scl->next )
+ FlexDependents(scl->sc,layer);
+}
+
+int SplineFontIsFlexible(SplineFont *sf,int layer, int flags) {
+ int i;
+ int max=0, val;
+ char *pt;
+ int blueshift;
+ /* if the return value is bigger than 6 and we don't have a BlueShift */
+ /* then we must set BlueShift to ret+1 before saving private dictionary */
+ /* If the first point in a spline set is flexible, then we must rotate */
+ /* the splineset */
+
+ if ( flags&(ps_flag_nohints|ps_flag_noflex)) {
+ for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL )
+ SCUnflex(sf->glyphs[i],layer);
+return( 0 );
+ }
+
+ pt = PSDictHasEntry(sf->private,"BlueShift");
+ blueshift = 21; /* maximum posible flex, not default */
+ if ( pt!=NULL ) {
+ blueshift = strtol(pt,NULL,10);
+ if ( blueshift>21 ) blueshift = 21;
+ } else if ( PSDictHasEntry(sf->private,"BlueValues")!=NULL )
+ blueshift = 7; /* The BlueValues array may depend on BlueShift having its default value */
+
+ for ( i=0; i<sf->glyphcnt; ++i )
+ if ( sf->glyphs[i]!=NULL ) if ( sf->glyphs[i]!=NULL ) {
+ val = _SplineCharIsFlexible(sf->glyphs[i],layer,blueshift);
+ if ( val>max ) max = val;
+ if ( sf->glyphs[i]->layers[layer].anyflexes )
+ FlexDependents(sf->glyphs[i],layer);
+ }
+return( max );
+}
+
+