/* Copyright (C) 2000-2012 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 "fontforge.h" #include #include "stemdb.h" #include #include #include "edgelist.h" float OpenTypeLoadHintEqualityTolerance = 0.0; /* 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]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 && (valarray[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 ( valzone2[3] && zone1[3]<=zone2[3]) || (zone2[4]+3>zone1[3] && zone2[3]<=zone1[3]) )) { if (( zone2[0]zone1[0]+zone1[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[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 undesirables */ 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; iglyphcnt; ++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=='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; iglyphcnt; ++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=='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; jblues[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; } } } static int PVAddBlues(BlueData *bd,unsigned bcnt,char *pt) { char *end; real val1, val2; unsigned i,j; if ( pt==NULL ) return( bcnt ); while ( isspace(*pt) || *pt=='[' ) ++pt; while ( *pt!=']' && *pt!='\0' ) { val1 = strtod(pt,&end); if ( *end=='\0' || end==pt ) break; for ( pt=end; isspace(*pt) ; ++pt ); val2 = strtod(pt,&end); if ( end==pt ) break; if ( bcnt==0 || val1>bd->blues[bcnt-1][0] ) i = bcnt; else { for ( i=0; ibd->blues[i][0]; ++i ); for ( j=bcnt; j>i; --j ) { bd->blues[j][0] = bd->blues[j-1][0]; bd->blues[j][1] = bd->blues[j-1][1]; } } bd->blues[i][0] = val1; bd->blues[i][1] = val2; ++bcnt; if ( bcnt>=sizeof(bd->blues)/sizeof(bd->blues[0])) break; for ( pt=end; isspace(*pt) ; ++pt ); } return( bcnt ); } /* Quick and dirty (and sometimes wrong) approach to figure out the common */ /* alignment zones in latin (greek, cyrillic) alphabets */ void QuickBlues(SplineFont *_sf, int layer, BlueData *bd) { real xheight = -1e10, caph = -1e10, ascent = -1e10, descent = 1e10, max, min; real xheighttop = -1e10, caphtop = -1e10; real numh = -1e10, numhtop = -1e10; real base = -1e10, basebelow = 1e10; SplineFont *sf; SplinePoint *sp; SplineSet *spl; int i,j, bcnt; SplineChar *t; char *pt; /* Get the alignment zones we care most about */ /* be careful of cid fonts */ if ( _sf->cidmaster!=NULL ) _sf = _sf->cidmaster; j=0; do { sf = ( _sf->subfontcnt==0 )? _sf : _sf->subfonts[j]; for ( i=0; iglyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) { int enc = sf->glyphs[i]->unicodeenc; if ( enc=='I' || enc=='O' || enc=='x' || enc=='o' || enc=='p' || enc=='l' || /* Jean-Christophe Dubacq points out that x-height should be calculated from */ /* various characters and not just x and o. Italic "x"s often have strange */ /* shapes */ enc=='A' || enc==0x391 || enc==0x410 || enc=='V' || enc=='u' || enc=='v' || enc=='w' || enc=='y' || enc=='z' || enc=='7' || enc=='8' || /* numbers with ascenders */ enc==0x399 || enc==0x39f || enc==0x3ba || enc==0x3bf || enc==0x3c1 || enc==0x3be || enc==0x3c7 || enc==0x41f || enc==0x41e || enc==0x43e || enc==0x43f || enc==0x440 || enc==0x452 || enc==0x445 ) { t = sf->glyphs[i]; while ( t->layers[layer].splines==NULL && t->layers[layer].refs!=NULL ) t = t->layers[layer].refs->sc; max = -1e10; min = 1e10; if ( t->layers[layer].splines!=NULL ) { for ( spl=t->layers[layer].splines; spl!=NULL; spl=spl->next ) { for ( sp=spl->first; ; ) { if ( sp->me.y > max ) max = sp->me.y; if ( sp->me.y < min ) min = sp->me.y; if ( sp->next==NULL ) break; sp = sp->next->to; if ( sp==spl->first ) break; } } if ( enc>0x400 ) { /* Only use ascent and descent here if we don't have anything better */ if ( enc==0x41f ) { caph = max; base = min; } else if ( enc==0x41e ) { if ( max>caphtop ) caphtop = max; basebelow = min; } else if ( enc==0x410 ) { if ( max>caphtop ) caphtop = max; } else if ( enc==0x43f && xheight<0 ) xheight = max; else if ( enc==0x445 && xheight<0 ) xheight = max; else if ( enc==0x43e ) xheighttop = max; else if ( enc==0x452 && ascent<0 ) ascent = max; else if ( enc==0x440 && descent>0 ) descent = min; } else if ( enc>0x300 ) { if ( enc==0x399 ) { caph = max; base = min; } else if ( enc==0x391 ) { if ( max>caphtop ) caphtop = max; } else if ( enc==0x39f ) { if ( max>caphtop ) caphtop = max; basebelow = min; } else if ( enc==0x3ba && xheight<0 ) xheight = max; else if ( enc==0x3c7 && xheight<0 ) xheight = max; else if ( enc==0x3bf ) xheighttop = max; else if ( enc==0x3be && ascent<0 ) ascent = max; else if ( enc==0x3c1 && descent>0 ) descent = min; } else { if ( enc=='I' ) { caph = max; base = min; } else if ( enc=='O' ) { if ( max>caphtop ) caphtop = max; if ( basebelowcaphtop ) caphtop = max; } else if ( enc=='7' ) numh = max; else if ( enc=='0' ) numhtop = max; else if ( enc=='x' || enc=='o' || enc=='u' || enc=='v' || enc =='w' || enc=='y' || enc=='z' ) { if ( xheighttop==-1e10 ) xheighttop = max; if ( xheight==-1e10 ) xheight = max; if ( max > xheighttop ) xheighttop = max; else if ( max20 ) xheight = max; if ( enc=='y' && descent==1e10 ) descent = min; } else if ( enc=='l' ) ascent = max; else descent = min; } } } } ++j; } while ( j<_sf->subfontcnt ); if ( basebelow==1e10 ) basebelow=-1e10; if ( caphtopbase ) basebelow = base; else if ( base==-1e10 ) base=basebelow; if ( base==-1e10 ) { base=basebelow = 0; } if ( xheighttopxheight = xheight; bd->xheighttop = xheighttop; bd->caph = caph; bd->caphtop = caphtop; bd->numh = numh; bd->numhtop = numhtop; bd->ascent = ascent; bd->descent = descent; bd->base = base; bd->basebelow = basebelow; bcnt = 0; if ( (pt=PSDictHasEntry(sf->private,"BlueValues"))!=NULL ) bcnt = PVAddBlues(bd,bcnt,pt); if ( (pt=PSDictHasEntry(sf->private,"OtherBlues"))!=NULL ) bcnt = PVAddBlues(bd,bcnt,pt); if ( bcnt==0 ) { if ( basebelow==-1e10 ) basebelow = base; if ( base==-1e10 ) base = basebelow; if ( xheight==-1e10 ) xheight = xheighttop; if ( xheighttop==-1e10 ) xheighttop = xheight; if ( caph==-1e10 ) caph = caphtop; if ( caphtop==-1e10 ) caphtop = caph; if ( numh==-1e10 ) numh = numhtop; if ( numhtop==-1e10 ) numhtop = numh; if ( numh!=-1e10 && (numhtop>caph-2 && numhcaphtop ) caphtop = numhtop; numh = numhtop = -1e10; } if ( ascent!=-1e10 && (ascent>caph-2 && ascentcaphtop ) caphtop = ascent; ascent = -1e10; } if ( ascent!=-1e10 && (ascent>numh-2 && ascentnumhtop ) numhtop = ascent; ascent = -1e10; if ( numhtop>caph-2 && numhcaphtop ) caphtop = numhtop; numh = numhtop = -1e10; } } if ( descent!=1e10 ) { bd->blues[0][0] = bd->blues[0][1] = descent; ++bcnt; } if ( basebelow!=-1e10 ) { bd->blues[bcnt][0] = basebelow; bd->blues[bcnt][1] = base; ++bcnt; } if ( xheight!=-1e10 ) { bd->blues[bcnt][0] = xheight; bd->blues[bcnt][1] = xheighttop; ++bcnt; } if ( numh!=-1e10 ) { bd->blues[bcnt][0] = numh; bd->blues[bcnt][1] = numhtop; ++bcnt; } if ( caph!=-1e10 ) { bd->blues[bcnt][0] = caph; bd->blues[bcnt][1] = caphtop; ++bcnt; } if ( ascent!=-1e10 ) { bd->blues[bcnt][0] = bd->blues[bcnt][1] = ascent; ++bcnt; } } bd->bluecnt = bcnt; } 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 = calloc(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 ( minhup = true; else { temp = min; min = max; max=temp; } if ( !el->leavetiny && min+1>max ) new->almostvert = true; if ( 40*dxdtminvertattmin = true; if ( 40*dxdtmaxvertattmax = 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]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 ( minvup = true; else { temp = min; min = max; max=temp; } if ( !el->leavetiny && min+1>max ) new->almosthor = true; if ( 40*dydtminhorattmin = true; if ( 40*dydtmaxhorattmax = 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]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 = calloc(el->cnt,sizeof(EI *)); el->ends = calloc(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->ocurordered[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->beginbegin ) { 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 ); } 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 ) { 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]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->ocurocur ) { 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 ); } /* Should I consider e and n to be a continuation of the same spline? */ /* If we are at an intersection (and it's the same intersection on both) */ /* and they go in vaguely the same direction then we should */ /* Ah, but also if they are at different intersections and are connected */ /* by a series of horizontal/vertical lines (whichever are invisible to major)*/ /* then we still should. */ int EISameLine(EI *e, EI *n, real i, int major) { EI *t; if ( n!=NULL && /*n->up==e->up &&*/ (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->tcurtmin+.2 && e->tcur>e->tmax-.2 ) || (n->splinenext==e && n->tmax==e->tmin && n->tcur>n->tmax-.2 && e->tcurtmin+.2 ) ) return( true ); /* 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 ); } 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->tcurtmin+.2 && e->tcur>e->tmax-.2 ) || (n->splinenext==e && n->tmax==e->tmin && n->tcur>n->tmax-.2 && e->tcurtmin+.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 ); } static StemInfo *StemRemoveFlexCandidates(StemInfo *stems) { StemInfo *s, *t, *sn; const real BlueShift = 7; /* Suppose we have something that is a flex candidate */ /* We might get two hints from it... one from the two end points */ /* and one from the internal point */ if ( stems==NULL ) return( NULL ); for ( s=stems; (sn = s->next)!=NULL; s = sn ) { if ( s->start+BlueShift > sn->start && s->width>0 && sn->width>0 && s->start+s->width-BlueShift < sn->start+sn->width && s->start+s->width+BlueShift > sn->start+sn->width && s->where != NULL && sn->where != NULL && s->where->next!=NULL && sn->where->next==NULL ) { t = sn->next; sn->next = NULL; StemInfoFree(sn); s->next = t; sn = t; if ( t==NULL ) break; } } return( stems ); } 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->endbegin ) { thi = hi; continue; } s = hi->beginbegin?mhi->begin:hi->begin; e = hi->end>mhi->end?mhi->end:hi->end; if ( enext ) 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 ); } HintInstance *HICopyTrans(HintInstance *hi, real mul, real offset) { HintInstance *first=NULL, *last, *cur, *p; while ( hi!=NULL ) { cur = chunkalloc(sizeof(HintInstance)); if ( mul>0 ) { cur->begin = hi->begin*mul+offset; cur->end = hi->end*mul+offset; if ( first==NULL ) first = cur; else last->next = cur; last = cur; } else { cur->begin = hi->end*mul+offset; cur->end = hi->begin*mul+offset; if ( first==NULL || cur->beginbegin ) { cur->next = first; first = cur; } else { for ( p=first, last=p->next; last!=NULL && cur->begin>last->begin; last=last->next ); p->next = cur; cur->next = last; } } hi = hi->next; } return( first ); } static HintInstance *StemAddHIFromActive(struct stemdata *stem,int major) { int i; HintInstance *head = NULL, *cur, *t; double mino, maxo; double dir = ((real *) &stem->unit.x)[major]<0 ? -1 : 1; for ( i=0; iactivecnt; ++i ) { mino = dir*stem->active[i].start + ((real *) &stem->left.x)[major]; maxo = dir*stem->active[i].end + ((real *) &stem->left.x)[major]; cur = chunkalloc(sizeof(HintInstance)); if ( dir>0 ) { cur->begin = mino; cur->end = maxo; if ( head==NULL ) head = cur; else t->next = cur; t = cur; } else { cur->begin = maxo; cur->end = mino; cur->next = head; head = cur; } } return( head ); } static HintInstance *DStemAddHIFromActive( struct stemdata *stem ) { int i; HintInstance *head = NULL, *cur, *t; for ( i=0; iactivecnt; ++i ) { cur = chunkalloc( sizeof( HintInstance )); cur->begin = stem->active[i].start; cur->end = stem->active[i].end; if ( head == NULL ) head = cur; else t->next = cur; t = cur; } return( head ); } void SCGuessHintInstancesList( SplineChar *sc,int layer,StemInfo *hstem,StemInfo *vstem,DStemInfo *dstem, int hvforce,int dforce ) { struct glyphdata *gd; struct stemdata *sd; int i, cnt=0, hneeds_gd=false, vneeds_gd=false, dneeds_gd=false; StemInfo *test; DStemInfo *dtest; double em_size = ( sc->parent != NULL ) ? sc->parent->ascent + sc->parent->descent : 1000; if ( hstem == NULL && vstem == NULL && dstem == NULL ) return; /* If all stems already have active zones assigned (actual for .sfd */ /* files), then there is no need to wast time generating glyph data for */ /* this glyph */ test = hstem; while ( !hneeds_gd && test != NULL ) { if ( test->where == NULL || hvforce ) hneeds_gd = true; test = test->next; } test = vstem; while ( !vneeds_gd && test != NULL ) { if ( test->where == NULL || hvforce ) vneeds_gd = true; test = test->next; } dtest = dstem; while ( !dneeds_gd && dtest != NULL ) { if ( dtest->where == NULL || dforce ) dneeds_gd = true; dtest = dtest->next; } if ( !hneeds_gd && !vneeds_gd && !dneeds_gd ) return; gd = GlyphDataInit( sc,layer,em_size,!dneeds_gd ); if ( gd == NULL ) return; cnt = 0; if ( hstem != NULL && hneeds_gd ) { gd = StemInfoToStemData( gd,hstem,false ); for ( i=cnt; istemcnt; i++ ) { sd = &gd->stems[i]; if ( hstem == NULL ) break; if ( hstem->where == NULL || hvforce ) hstem->where = StemAddHIFromActive( sd,false ); hstem = hstem->next; } } cnt = gd->stemcnt; if ( vstem != NULL && vneeds_gd ) { gd = StemInfoToStemData( gd,vstem,true ); for ( i=cnt; istemcnt; i++ ) { sd = &gd->stems[i]; if ( vstem == NULL ) break; if ( vstem->where == NULL || hvforce ) vstem->where = StemAddHIFromActive( sd,true ); vstem = vstem->next; } } cnt = gd->stemcnt; if ( dstem != NULL && dneeds_gd ) { gd = DStemInfoToStemData( gd,dstem ); for ( i=cnt; istemcnt; i++ ) { sd = &gd->stems[i]; if ( dstem == NULL ) break; dstem->left = sd->left; dstem->right = sd->right; if ( dstem->where == NULL || dforce ) dstem->where = DStemAddHIFromActive( sd ); dstem = dstem->next; } } GlyphDataFree( gd ); return; } /* We have got (either from a file or user specified) a diagonal stem, described by 4 base points (pairs of x and y coordinates). Some additional tests are required before we can add this stem to the given glyph. */ int MergeDStemInfo( SplineFont *sf,DStemInfo **ds,DStemInfo *test ) { DStemInfo *dn, *cur, *prev, *next, *temp; double dot, loff, roff, soff, dist_error_diag ; double ibegin, iend; int overlap; BasePoint *base, *nbase, *pbase; HintInstance *hi; if ( *ds == NULL ) { *ds = test; return( true ); } dist_error_diag = ( sf->ascent + sf->descent ) * 0.0065; cur = prev = NULL; for ( dn=*ds ; dn!=NULL ; dn=dn->next ) { prev = cur; cur = dn; /* Compare the given stem with each of the existing diagonal stem * hints. First ensure that it is not an exact duplicate of an already * added stem. Then test if unit vectors are parallel and edges colinear. * In this case we should either preserve the existing stem or replace * it with the new one, but not keep them both */ if (test->unit.x == dn->unit.x && test->unit.y == dn->unit.y && test->left.x == dn->left.x && test->left.y == dn->left.y && test->right.x == dn->right.x && test->right.y == dn->right.y ) { DStemInfoFree( test ); return( false ); } dot = ( test->unit.x * dn->unit.y ) - ( test->unit.y * dn->unit.x ); if ( dot <= -0.5 || dot >= 0.5 ) continue; loff = ( test->left.x - dn->left.x ) * dn->unit.y - ( test->left.y - dn->left.y ) * dn->unit.x; roff = ( test->right.x - dn->right.x ) * dn->unit.y - ( test->right.y - dn->right.y ) * dn->unit.x; if (loff <= -dist_error_diag || loff >= dist_error_diag || roff <= -dist_error_diag || roff >= dist_error_diag ) continue; soff = ( test->left.x - dn->left.x ) * dn->unit.x + ( test->left.y - dn->left.y ) * dn->unit.y; overlap = false; if ( dn->where != NULL && test->where != NULL && test->where->next == NULL ) { ibegin = test->where->begin + soff; iend = test->where->end + soff; for ( hi = dn->where; hi != NULL; hi = hi->next ) { if (( hi->begin <= ibegin && ibegin <= hi->end ) || ( hi->begin <= iend && iend <= hi->end ) || ( ibegin <= hi->begin && hi->end <= iend )) { overlap = true; break; } } } else overlap = true; /* It's probably a colinear dstem, as in older SFD files. Treat */ /* it as one more instance for the already added stem */ if ( !overlap ) { for ( hi=dn->where; hi->next != NULL; hi = hi->next ) ; hi->next = chunkalloc( sizeof( HintInstance )); hi->next->begin = ibegin; hi->next->end = iend; DStemInfoFree( test ); return( false ); /* The found stem is close but not identical to the stem we */ /* are going to add. So just replace the older stem with the */ /* new one */ } else { test->next = dn->next; if ( prev == NULL ) *ds = test; else prev->next = test; DStemInfoFree( dn ); return( true ); } } /* Insert the given stem to the list by such a way that diagonal * stems are ordered by the X coordinate of the left edge key point, and * by Y if X is the same. The order is arbitrary, but may be essential for * things like "W". So we should be sure that the autoinstructor will * process diagonals from left to right. */ base = ( test->unit.y < 0 ) ? &test->right : &test->left; nbase = ( (*ds)->unit.y < 0 ) ? &(*ds)->right : &(*ds)->left; if ( base->x < nbase->x || ( base->x == nbase->x && base->y >= nbase->y )) { temp = *ds; *ds = test; (*ds)->next = temp; } else { for ( dn=*ds ; dn!=NULL && dn!=test ; dn=dn->next ) { next = dn->next; pbase = ( dn->unit.y < 0 ) ? &dn->right : &dn->left; if ( next != NULL ) nbase = ( next->unit.y < 0 ) ? &next->right : &next->left; if (( pbase->x < base->x || ( pbase->x == base->x && pbase->y >= base->y )) && ( next == NULL || base->x < nbase->x || ( base->x == nbase->x && base->y >= nbase->y ))) { test->next = next; dn->next = test; break; } } } return( true ); } static StemInfo *RefHintsMerge(StemInfo *into, StemInfo *rh, real mul, real offset, real omul, real oofset) { StemInfo *prev, *h, *n; real start, width; for ( ; rh!=NULL; rh=rh->next ) { start = rh->start*mul + offset; width = rh->width *mul; if ( width<0 ) { start += width; width = -width; } for ( h=into, prev=NULL; h!=NULL && (start>h->start || (start==h->start && width>h->width)); prev=h, h=h->next ); if ( h==NULL || start!=h->start || width!=h->width ) { n = chunkalloc(sizeof(StemInfo)); n->start = start; n->width = width; n->ghost = rh->ghost; n->next = h; if ( prev==NULL ) into = n; else prev->next = n; n->where = HICopyTrans(rh->where,omul,oofset); } else h->where = HIMerge(h->where,HICopyTrans(rh->where,omul,oofset)); } return( into ); } static DStemInfo *RefDHintsMerge( SplineFont *sf,DStemInfo *into,DStemInfo *rh, real xmul,real xoffset,real ymul,real yoffset ) { DStemInfo *new; double dmul; for ( ; rh!=NULL; rh=rh->next ) { new = chunkalloc( sizeof( DStemInfo )); *new = *rh; new->left.x = xmul*new->left.x + xoffset; new->right.x = xmul*new->right.x + xoffset; new->left.y = ymul*new->left.y + yoffset; new->right.y = ymul*new->right.y + yoffset; new->next = NULL; if (( xmul < 0 && ymul > 0 ) || ( xmul > 0 && ymul < 0 )) new->unit.y = -new->unit.y; new->unit.x *= fabs( xmul ); new->unit.y *= fabs( ymul ); dmul = sqrt( pow( new->unit.x,2 ) + pow( new->unit.y,2 )); new->unit.x /= dmul; new->unit.y /= dmul; if ( xmul < 0 ) dmul = -dmul; new->where = HICopyTrans( rh->where,dmul,0 ); MergeDStemInfo( sf,&into,new ); } return( into ); } static void __SplineCharAutoHint( SplineChar *sc, int layer, BlueData *bd, int gen_undoes ); static void AutoHintRefs(SplineChar *sc,int layer, BlueData *bd, int picky, int gen_undoes) { RefChar *ref; /* Add hints for base characters before accent hints => if there are any */ /* conflicts, the base characters win */ for ( ref=sc->layers[layer].refs; ref!=NULL; ref=ref->next ) { if ( ref->transform[1]==0 && ref->transform[2]==0 ) { if ( picky ) { if ( !ref->sc->manualhints && ref->sc->changedsincelasthinted && (ref->sc->layers[layer].refs!=NULL && ref->sc->layers[layer].splines==NULL)) AutoHintRefs(ref->sc,layer,bd,true,gen_undoes); } else if ( !ref->sc->manualhints && ref->sc->changedsincelasthinted ) __SplineCharAutoHint(ref->sc,layer,bd,gen_undoes); if ( ref->sc->unicodeenc!=-1 && ref->sc->unicodeenc<0x10000 && isalnum(ref->sc->unicodeenc) ) { sc->hstem = RefHintsMerge(sc->hstem,ref->sc->hstem,ref->transform[3], ref->transform[5], ref->transform[0], ref->transform[4]); sc->vstem = RefHintsMerge(sc->vstem,ref->sc->vstem,ref->transform[0], ref->transform[4], ref->transform[3], ref->transform[5]); sc->dstem = RefDHintsMerge(sc->parent,sc->dstem,ref->sc->dstem,ref->transform[0], ref->transform[4], ref->transform[3], ref->transform[5]); } } } for ( ref=sc->layers[layer].refs; ref!=NULL; ref=ref->next ) { if ( ref->transform[1]==0 && ref->transform[2]==0 && (ref->sc->unicodeenc==-1 || ref->sc->unicodeenc>=0x10000 || !isalnum(ref->sc->unicodeenc)) ) { sc->hstem = RefHintsMerge(sc->hstem,ref->sc->hstem,ref->transform[3], ref->transform[5], ref->transform[0], ref->transform[4]); sc->vstem = RefHintsMerge(sc->vstem,ref->sc->vstem,ref->transform[0], ref->transform[4], ref->transform[3], ref->transform[5]); sc->dstem = RefDHintsMerge(sc->parent,sc->dstem,ref->sc->dstem,ref->transform[0], ref->transform[4], ref->transform[3], ref->transform[5]); } } sc->vconflicts = StemListAnyConflicts(sc->vstem); sc->hconflicts = StemListAnyConflicts(sc->hstem); } static void _SCClearHintMasks(SplineChar *sc,int layer, int counterstoo) { SplineSet *spl; SplinePoint *sp; RefChar *ref; if ( layer<0 || layer>=sc->layer_cnt ) return; 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 = malloc(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; iinstance_count; ++i ) { if ( sc->orig_posinstances[i]->glyphcnt ) _SCClearHintMasks(mm->instances[i]->glyphs[sc->orig_pos],layer,counterstoo); } if ( sc->orig_posnormal->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.ystart ) 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.xstart ) 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; ihintmask,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; ihconflicts && !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; icoords[i] = sps[i]->me.x; else for ( i=0; icoords[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; inext ) { cnt = 0; for ( i=0; ihints[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; ihints[i]=h[i]; if ( bestc!=0 ) { for ( i=0; ihints[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; imap[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; imap[i]->start!=mmh2->map[i]->start ) { if ( mmh1->map[i]->start > mmh2->map[i]->start ) return( 1 ); else return( -1 ); } } for ( i=0; imap[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; imap[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; ihstem : 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; for (;;) { for ( i=0; ifirst; else to[i] = NULL; } for (;;) { for ( i=0; ihstem); 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; inext==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; inext; 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; ihmax ) 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; ilayers[layer].refs; if ( ref[i]!=NULL ) anymore = true; } while ( anymore ) { for ( i=0; ilayers[0].splines : NULL; SplResolveSplitHints(scs,spl,instance_count,&hs,&vs); anymore = false; for ( i=0; inext; if ( ref[i]!=NULL ) anymore = true; } } } for ( i=0; ifirst; anymore = true; } else to[i] = NULL; } /* Assign the initial hint mask */ if ( anymore && !inited ) { FigureHintMask(scs,to,instance_count,mask); inited = true; } for (;;) { for ( i=0; ifirst; else to[i] = NULL; } for (;;) { TestHintMask(scs,to,instance_count,mask); anymore = false; for ( i=0; inext==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; inext; } 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; iorig_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; ihconflicts || 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; ilayers[layer].splines; ref[i] = scs[i]->layers[layer].refs; } inited = SplFigureHintMasks(scs,spl,instance_count,mask,false); for (;;) { for ( i=0; ilayers[0].splines; } inited = SplFigureHintMasks(scs,spl,instance_count,mask,inited); anymore = false; for ( i=0; inext; if ( ref[i]!=NULL ) anymore = true; } } if ( !anymore ) break; } if ( instance_count==1 ) SCFigureSimpleCounterMasks(sc); } static StemInfo *GDFindStems(struct glyphdata *gd, int major) { int i; StemInfo *head = NULL, *cur, *p, *t; StemBundle *bundle = major ? gd->vbundle : gd->hbundle; StemData *stem; int other = !major; double l, r; for ( i=0; icnt; ++i ) { stem = bundle->stemlist[i]; l = (&stem->left.x)[other]; r = (&stem->right.x)[other]; cur = chunkalloc( sizeof( StemInfo )); if ( lstart = l; cur->width = r - l; cur->haspointleft = stem->lpcnt > 0; cur->haspointright = stem->rpcnt > 0; } else { cur->start = r; cur->width = l - r; cur->haspointleft = stem->rpcnt > 0; cur->haspointright = stem->lpcnt > 0; } cur->ghost = stem->ghost; for ( p=NULL, t=head; t!=NULL ; p=t, t=t->next ) { if ( cur->start<=t->start ) break; } cur->next = t; if ( p==NULL ) head = cur; else p->next = cur; cur->where = StemAddHIFromActive(stem,major); } head = StemRemoveFlexCandidates(head); return( head ); } static DStemInfo *GDFindDStems(struct glyphdata *gd) { int i; DStemInfo *head = NULL, *cur ; struct stemdata *stem; for ( i=0; istemcnt; ++i ) { stem = &gd->stems[i]; /* A real diagonal stem should consist of one or more continuous * ranges. Thus the number of active zones should be less then the * number of stem chunks (i. e. pairs of the opposite points). If * each chunk has its own active zone, then we probably have got * not a real stem, but rather two (or more) separate point pairs, * which occasionally happened to have nearly the same vectors and * to be positioned on the same lines */ if ( stem->toobig ) continue; if (( stem->unit.y > -.05 && stem->unit.y < .05 ) || ( stem->unit.x > -.05 && stem->unit.x < .05 )) continue; if ( stem->lpcnt < 2 || stem->rpcnt < 2 ) continue; cur = chunkalloc( sizeof(DStemInfo) ); cur->left = stem->left; cur->right = stem->right; cur->unit = stem->unit; cur->where = DStemAddHIFromActive( stem ); MergeDStemInfo(gd->sf, &head, cur); } return( head ); } static bool inorder( real a, real b, real c ) { return a < b && b < c; } /** * If fluffy is near enough to exact then clamp to exact. * If fluffy is more than Tolerance away from exact then * just return fluffy (no change). */ static real clampToIfNear( real exact, real fluffy, real Tolerance ) { if( inorder( exact - Tolerance, fluffy, exact + Tolerance )) return exact; return fluffy; } void _SplineCharAutoHint( SplineChar *sc, int layer, BlueData *bd, struct glyphdata *gd2, int gen_undoes ) { struct glyphdata *gd; StemInfosFree(sc->vstem); sc->vstem=NULL; StemInfosFree(sc->hstem); sc->hstem=NULL; DStemInfosFree(sc->dstem); sc->dstem=NULL; MinimumDistancesFree(sc->md); sc->md=NULL; free(sc->countermasks); sc->countermasks = NULL; sc->countermask_cnt = 0; /* We'll free the hintmasks when we call SCFigureHintMasks */ sc->changedsincelasthinted = false; sc->manualhints = false; if ( (gd=gd2)==NULL ) gd = GlyphDataBuild( sc,layer,bd,false ); if ( gd!=NULL ) { sc->vstem = GDFindStems(gd,1); sc->hstem = GDFindStems(gd,0); if ( !gd->only_hv ) sc->dstem = GDFindDStems(gd); if ( gd2==NULL ) GlyphDataFree(gd); } real AutohintRoundingTolerance = 0.005; StemInfo* s = sc->hstem; for( ; s; s = s->next ) { s->width = clampToIfNear( 20.0, s->width, AutohintRoundingTolerance ); s->width = clampToIfNear( 21.0, s->width, AutohintRoundingTolerance ); } AutoHintRefs(sc,layer,bd,false,gen_undoes); } static void __SplineCharAutoHint( SplineChar *sc, int layer, BlueData *bd, int gen_undoes ) { MMSet *mm = sc->parent->mm; int i; if ( mm==NULL ) _SplineCharAutoHint(sc,layer,bd,NULL,gen_undoes); else { for ( i=0; iinstance_count; ++i ) if ( sc->orig_pos < mm->instances[i]->glyphcnt ) _SplineCharAutoHint(mm->instances[i]->glyphs[sc->orig_pos],layer,NULL,NULL,gen_undoes); if ( sc->orig_pos < mm->normal->glyphcnt ) _SplineCharAutoHint(mm->normal->glyphs[sc->orig_pos],layer,NULL,NULL,gen_undoes); } SCFigureHintMasks(sc,layer); } void SplineCharAutoHint( SplineChar *sc, int layer, BlueData *bd ) { __SplineCharAutoHint(sc,layer,bd,true); } void SFSCAutoHint( SplineChar *sc, int layer, BlueData *bd ) { RefChar *ref; if ( sc->ticked ) return; for ( ref=sc->layers[ly_fore].refs; ref!=NULL; ref=ref->next ) { if ( !ref->sc->ticked ) SFSCAutoHint(ref->sc,layer,bd); } sc->ticked = true; SplineCharAutoHint(sc,layer,bd); } void SplineFontAutoHint( SplineFont *_sf,int layer) { int i,k; SplineFont *sf; BlueData *bd = NULL, _bd; SplineChar *sc; if ( _sf->mm==NULL ) { QuickBlues(_sf,layer,&_bd); bd = &_bd; } /* Tick the ones we don't want to AH, untick the ones that need AH */ k=0; do { sf = _sf->subfontcnt==0 ? _sf : _sf->subfonts[k]; for ( i=0; iglyphcnt; ++i ) if ( (sc = sf->glyphs[i])!=NULL ) sc->ticked = ( !sc->changedsincelasthinted || sc->manualhints ); ++k; } while ( k<_sf->subfontcnt ); k=0; do { sf = _sf->subfontcnt==0 ? _sf : _sf->subfonts[k]; for ( i=0; iglyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) { if ( sf->glyphs[i]->changedsincelasthinted && !sf->glyphs[i]->manualhints ) SFSCAutoHint(sf->glyphs[i],layer,bd); if ( !ff_progress_next()) { k = _sf->subfontcnt+1; break; } } ++k; } while ( k<_sf->subfontcnt ); } void SplineFontAutoHintRefs( SplineFont *_sf,int layer) { int i,k; SplineFont *sf; BlueData *bd = NULL, _bd; SplineChar *sc; if ( _sf->mm==NULL ) { QuickBlues(_sf,layer,&_bd); bd = &_bd; } k=0; do { sf = _sf->subfontcnt==0 ? _sf : _sf->subfonts[k]; for ( i=0; iglyphcnt; ++i ) if ( (sc = sf->glyphs[i])!=NULL ) { if ( sc->changedsincelasthinted && !sc->manualhints && (sc->layers[layer].refs!=NULL && sc->layers[layer].splines==NULL)) { StemInfosFree(sc->vstem); sc->vstem=NULL; StemInfosFree(sc->hstem); sc->hstem=NULL; AutoHintRefs(sc,layer,bd,true,true); } } ++k; } while ( k<_sf->subfontcnt ); } 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; iglyphcnt; ++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 ( smaxj ) 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 ( ; ji+windsize ) { if ( (j = i-windsize)<0 ) j=0; for ( ; jstemwidths[i+1] ) { stemwidths[i] += stemwidths[i+1]; stemwidths[i+1] = 0; } else { if ( i<=smax-2 && stemwidths[i+2] && stemwidths[i+2]biggest ) { biggest = stemwidths[i]; biggesti=i; } } /* array must be sorted */ if ( biggestbiggesti ) 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.xme.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.yme.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; iglyphcnt; ++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; iglyphcnt; ++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 ); }