/* Copyright (C) 2000-2012 by George Williams, Michal Nowakowski & Alexey Kryukov */ /* * 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 "fontforgevw.h" #define _USE_MATH_DEFINES #include #include #include "ttf.h" #include "splinefont.h" #include "stemdb.h" extern int autohint_before_generate; int instruct_diagonal_stems = 1, instruct_serif_stems = 1, instruct_ball_terminals = 1, interpolate_strong = 1, interpolate_more_strong = 1, /* not applicable if interpolate_strong==0 */ control_counters = 0; /* non-optimized instructions will be using a stack of depth 6, allowing * for easy testing whether the code leaves trash on the stack or not. */ #define OPTIMIZE_TTF_INSTRS 1 #if OPTIMIZE_TTF_INSTRS #define STACK_DEPTH 256 #else #define STACK_DEPTH 6 #endif /* define some often used instructions */ #define SVTCA_y (0x00) #define SVTCA_x (0x01) #define SRP0 (0x10) #define SRP1 (0x11) #define SRP2 (0x12) #define SZP0 (0x13) #define SLOOP (0x17) #define RTG (0x18) #define SMD (0x1a) #define DUP (0x20) #define DEPTH (0x24) #define CALL (0x2b) #define MDAP (0x2e) #define MDAP_rnd (0x2f) #define IUP_y (0x30) #define IUP_x (0x31) #define SHP_rp2 (0x32) #define SHP_rp1 (0x33) #define SHPIX (0x38) #define IP (0x39) #define ALIGNRP (0x3c) #define MIAP_rnd (0x3f) #define ADD (0x60) #define MUL (0x63) #define NEG (0x65) #define SROUND (0x76) #define FLIPPT (0x80) #define MDRP_grey (0xc0) #define MDRP_min_black (0xc9) #define MDRP_min_white (0xca) #define MDRP_min_rnd_black (0xcd) #define MDRP_rp0_rnd_white (0xd6) #define MDRP_rp0_min_rnd_grey (0xdc) #define MDRP_rp0_min_rnd_black (0xdd) #define MIRP_min_black (0xe9) #define MIRP_min_rnd_black (0xed) #define MIRP_rp0_min_black (0xf9) #define MIRP_rp0_min_rnd_black (0xfd) /****************************************************************************** * * Low-level routines to add data for PUSHes to bytecode instruction stream. * pushheader() adds PUSH preamble, then repeating addpoint() adds items. * * Numbers larger than 65535 are not supported (according to TrueType spec, * there can't be more points in a glyph, simple or compound). Negative * numbers aren't supported, either. So don't use these functions as they * are - there are higher-level ones further below, that handle things nicely. * ******************************************************************************/ static uint8 *pushheader(uint8 *instrs, int isword, int tot) { if ( isword ) { if ( tot>8 ) { *instrs++ = 0x41; /* N(next word) Push words */ *instrs++ = tot; } else *instrs++ = 0xb8+(tot-1); /* Push Words */ } else { if ( tot>8 ) { *instrs++ = 0x40; /* N(next byte) Push bytes */ *instrs++ = tot; } else *instrs++ = 0xb0+(tot-1); /* Push bytes */ } return( instrs ); } static uint8 *addpoint(uint8 *instrs,int isword,int pt) { if ( !isword ) { *instrs++ = pt; } else { *instrs++ = pt>>8; *instrs++ = pt&0xff; } return( instrs ); } /* Exemplary high-level routines to add PUSH-es to bytecode instruction * stream. They handle negative numbers correctly. As they are used * in various roles here, some aliases are defined, so that the name * speaks for itself in the code. */ static uint8 *pushpoint(uint8 *instrs,int pt) { instrs = pushheader(instrs,(pt>255)||(pt<0),1); return( addpoint(instrs,(pt>255)||(pt<0),pt)); } #define pushnum(a, b) pushpoint(a, b) static uint8 *pushpointstem(uint8 *instrs, int pt, int stem) { int isword = pt>255 || stem>255 || pt<0 || stem<0; instrs = pushheader(instrs,isword,2); instrs = addpoint(instrs,isword,pt); return( addpoint(instrs,isword,stem)); } #define push2points(a, b, c) pushpointstem(a, b, c) #define push2nums(a, b, c) pushpointstem(a, b, c) /* Push a bunch of point numbers (or other numbers) onto the stack. * TODO! * Possible strategies: * - push point by point (poor space efficiency) * - push all the stock at once (currently used, better, but has * poor space efficiency in case of a word among several bytes). * - push bytes and words separately */ static uint8 *pushpoints(uint8 *instrs, int ptcnt, const int *pts) { int i, isword = 0; for (i=0; i255 || pts[i]<0) isword=1; /* It's an error to push more than STACK_DEPTH points. */ if (ptcnt > STACK_DEPTH) IError("Truetype stack overflow will occur."); if (ptcnt > 255 && !isword) { instrs = pushpoints(instrs, 255, pts); ptcnt-=255; pts+=255; } instrs = pushheader(instrs,isword,ptcnt); for (i=0; i 3); instrs = pushpoints(instrs, ptcnt=STACK_DEPTH) instrs=instructpoints(instrs, ptcnt-(STACK_DEPTH-1), pts+(STACK_DEPTH-1), command); return( instrs ); } /****************************************************************************** * * Low-level routines for getting a cvt index for a stem width, assuming there * are any numbers in cvt. Includes legacy code for importing PS Private into * CVT. * ******************************************************************************/ struct ttf_table *SFFindTable(SplineFont *sf,uint32 tag) { struct ttf_table *tab; for ( tab=sf->ttf_tables; tab!=NULL && tab->tag!=tag; tab=tab->next ); return( tab ); } int TTF__getcvtval(SplineFont *sf,int val) { int i; struct ttf_table *cvt_tab = SFFindTable(sf,CHR('c','v','t',' ')); if ( cvt_tab==NULL ) { cvt_tab = chunkalloc(sizeof(struct ttf_table)); cvt_tab->tag = CHR('c','v','t',' '); cvt_tab->maxlen = 200; cvt_tab->data = malloc(100*sizeof(short)); cvt_tab->next = sf->ttf_tables; sf->ttf_tables = cvt_tab; } for ( i=0; (int)sizeof(uint16)*ilen; ++i ) { int tval = (int16) memushort(cvt_tab->data,cvt_tab->len, sizeof(uint16)*i); if ( val>=tval-1 && val<=tval+1 ) return( i ); } if ( (int)sizeof(uint16)*i>=cvt_tab->maxlen ) { if ( cvt_tab->maxlen==0 ) cvt_tab->maxlen = cvt_tab->len; cvt_tab->maxlen += 200; cvt_tab->data = realloc(cvt_tab->data,cvt_tab->maxlen); } memputshort(cvt_tab->data,sizeof(uint16)*i,val); cvt_tab->len += sizeof(uint16); return( i ); } /* by default sign is unimportant in the cvt * For some instructions anyway, but not for MIAP so this routine has * been broken in two. */ int TTF_getcvtval(SplineFont *sf,int val) { if ( val<0 ) val = -val; return( TTF__getcvtval(sf,val)); } /* We are given a stem weight and try to find matching one in CVT. * If none found, we return -1. */ static StdStem *CVTSeekStem(int xdir, GlobalInstrCt *gic, double value, int can_fail) { StdStem *mainstem = xdir?&(gic->stdvw):&(gic->stdhw); StdStem *otherstems = xdir?gic->stemsnapv:gic->stemsnaph; StdStem *closest = NULL; int otherstemcnt = xdir?gic->stemsnapvcnt:gic->stemsnaphcnt; int i; double mindelta=1e20, delta, closestwidth=1e20; if (mainstem->width == -1) return NULL; value = fabs(value); delta = fabs(mainstem->width - value); if (delta < mindelta) { mindelta = delta; closestwidth = rint(mainstem->width); closest = mainstem; } for (i=0; ifudge) return closest; if (value/closestwidth < 1.11 && value/closestwidth > 0.9) return closest; if (can_fail) return NULL; return closest; } /****************************************************************************** ****************************************************************************** ** ** We need to initialize global instructing context before autoinstructing ** a glyph, because we want to be sure that global hinting tables (cvt, prep, ** fpgm) were (or weren't) properly set up. ** ****************************************************************************** ******************************************************************************/ /* Helper routines: read PS private entry and return its contents. */ static int GetBlueFuzz(SplineFont *sf) { char *str, *end; if ( sf->private==NULL || (str=PSDictHasEntry(sf->private,"BlueFuzz"))==NULL || !isdigit(str[0]) ) return 1; return strtod(str, &end); } /* Return BlueScale as PPEM at which we have to stop suppressing overshoots */ static int GetBlueScale(SplineFont *sf) { char *str, *end; double bs; int result; if ( sf->private==NULL || (str=PSDictHasEntry(sf->private,"BlueScale"))==NULL ) return 42; bs = strtod(str, &end); if (end==str || bs<=0.0) bs=0.039625; bs*=240; bs+=0.49; bs*=300.0/72.0; result = (int)rint(bs); if (result>255) result = 255; /* Who would need such blue scale??? */ return result; } static real *ParsePSArray(const char *str, int *rescnt) { char *end; real d, *results=NULL; if ((rescnt == NULL) || (str == NULL)) return NULL; *rescnt = 0; while (*str) { while (!isdigit(*str) && *str!='-' && *str!='+' && *str!='.' && *str!='\0') ++str; if ( *str=='\0' ) break; d = strtod(str, &end); if ( d>=-32768 && d<=32767 ) { if (*rescnt) { results = realloc(results, sizeof(real)*(++(*rescnt))); results[*rescnt-1] = d; } else (results = calloc(*rescnt=1, sizeof(real)))[0] = d; } str = end; } return results; } static real *GetNParsePSArray(SplineFont *sf, const char *name, int *rescnt) { return ParsePSArray(PSDictHasEntry(sf->private, name), rescnt); } /* Tell if the two segments, [b1,o1] and [b2,o2] intersect. * This can be used to determine whether blues or stems overlap. */ static int SegmentsOverlap(real b1, real o1, real b2, real o2) { real t; if (b1 > o1) { t = o1; o1 = b1; b1 = t; } if (b2 > o2) { t = o2; o2 = b2; b2 = t; } return !((b2 > o1) || (o2 < b1)); } /* To be used with qsort() - sorts BlueZone array by base in ascending order. */ static int SortBlues(const void *a, const void *b) { return ((BlueZone *)a)->base > ((BlueZone *)b)->base; } /* Import blue data into global instructing context. Include family blues too. * We assume that blues are needed for family blues to make sense. If there are * only family blues, we treat them as normal blues. Otherwise, if a family blue * zone doesn't match any normal blue zone, or if they match perfectly, * it is ignored. */ static void GICImportBlues(GlobalInstrCt *gic) { int bluecnt = 0; int i, j, cnt; real *values; int HasPSBlues = (PSDictHasEntry(gic->sf->private, "BlueValues") != NULL) || (PSDictHasEntry(gic->sf->private, "OtherBlues") != NULL); int HasPSFamilyBlues = (PSDictHasEntry(gic->sf->private, "FamilyBlues") != NULL) || (PSDictHasEntry(gic->sf->private, "FamilyOtherBlues") != NULL); const char *PrimaryBlues = HasPSBlues ? "BlueValues" : "FamilyBlues"; const char *OtherBlues = HasPSBlues ? "OtherBlues" : "FamilyOtherBlues"; if (HasPSBlues || HasPSFamilyBlues){ values = GetNParsePSArray(gic->sf, PrimaryBlues, &cnt); cnt /= 2; if (cnt > 7) cnt = 7; if (values != NULL) { gic->bluecnt = bluecnt = cnt; /* First pair is a bottom zone (see Type1 specification). */ gic->blues[0].base = values[1]; gic->blues[0].overshoot = values[0]; gic->blues[0].family_base = strtod("NAN", NULL); /* Next pairs are top zones (see Type1 specification). */ for (i=1; iblues[i].family_base = strtod("NAN", NULL); gic->blues[i].base = values[2*i]; gic->blues[i].overshoot = values[2*i+1]; } free(values); } values = GetNParsePSArray(gic->sf, OtherBlues, &cnt); cnt /= 2; if (cnt > 5) cnt = 5; if (values != NULL) { gic->bluecnt += cnt; /* All pairs are bottom zones (see Type1 specification). */ for (i=0; iblues[i+bluecnt].family_base = strtod("NAN", NULL); gic->blues[i+bluecnt].base = values[2*i+1]; gic->blues[i+bluecnt].overshoot = values[2*i]; } free(values); bluecnt += cnt; } /* Add family data to blues */ if (HasPSBlues && HasPSFamilyBlues) { values = GetNParsePSArray(gic->sf, "FamilyBlues", &cnt); cnt /= 2; if (cnt > 7) cnt = 7; if (values != NULL) { /* First pair is a bottom zone (see Type1 specification). */ for (j=0; jblues[j].family_base)) continue; else if (values[1] != gic->blues[j].base && SegmentsOverlap(gic->blues[j].base, gic->blues[j].overshoot, values[0], values[1])) gic->blues[j].family_base = values[1]; /* Next pairs are top zones (see Type1 specification). */ for (i=1; iblues[j].family_base)) continue; else if (values[2*i] != gic->blues[j].base && SegmentsOverlap(gic->blues[j].base, gic->blues[j].overshoot, values[2*i], values[2*i+1])) gic->blues[j].family_base = values[2*i]; } free(values); } values = GetNParsePSArray(gic->sf, "FamilyOtherBlues", &cnt); cnt /= 2; if (cnt > 5) cnt = 5; if (values != NULL) { /* All pairs are bottom zones (see Type1 specification). */ for (i=0; iblues[j].family_base)) continue; else if (values[2*i+1] != gic->blues[j].base && SegmentsOverlap(gic->blues[j].base, gic->blues[j].overshoot, values[2*i], values[2*i+1])) gic->blues[j].family_base = values[2*i+1]; } free(values); } } } else if (gic->bd->bluecnt) { /* If there are no PS private entries, we have */ /* to use FF's quickly guessed fallback blues. */ gic->bluecnt = bluecnt = gic->bd->bluecnt; for (i=0; iblues[i].family_base = strtod("NAN", NULL); gic->blues[i].family_cvtindex = -1; if (gic->bd->blues[i][1] <= 0) { gic->blues[i].base = gic->bd->blues[i][1]; gic->blues[i].overshoot = gic->bd->blues[i][0]; } else { gic->blues[i].base = gic->bd->blues[i][0]; gic->blues[i].overshoot = gic->bd->blues[i][1]; } } } /* 'highest' and 'lowest' are not to be set yet. */ for (i=0; ibluecnt; i++) gic->blues[i].highest = gic->blues[i].lowest = -1; /* I assume ascending order in snap_to_blues(). */ qsort(gic->blues, gic->bluecnt, sizeof(BlueZone), SortBlues); } /* To be used with qsort() - sorts StdStem array by width in ascending order. */ static int SortStems(const void *a, const void *b) { return ((StdStem *)a)->width > ((StdStem *)b)->width; } /* Import stem data into global instructing context. We deal only with * horizontal or vertical stems (xdir decides) here. If Std*W is not specified, * but there exists StemSnap*, we'll make up a fake Std*V as a fallback. * Subtle manipulations with Std*W's value can result in massive change of * font appearance at some pixel sizes, because it's used as a base for * normalization of all other stems. */ static void GICImportStems(int xdir, GlobalInstrCt *gic) { int i, cnt, next; real *values; const char *s_StdW = xdir?"StdVW":"StdHW"; const char *s_StemSnap = xdir?"StemSnapV":"StemSnapH"; StdStem *stdw = xdir?&(gic->stdvw):&(gic->stdhw); StdStem **stemsnap = xdir?&(gic->stemsnapv):&(gic->stemsnaph); int *stemsnapcnt = xdir?&(gic->stemsnapvcnt):&(gic->stemsnaphcnt); if ((values = GetNParsePSArray(gic->sf, s_StdW, &cnt)) != NULL) { stdw->width = *values; free(values); } if ((values = GetNParsePSArray(gic->sf, s_StemSnap, &cnt)) != NULL) { *stemsnap = (StdStem *)calloc(cnt, sizeof(StdStem)); for (next=i=0; istdhw.width) (*stemsnap)[next++].width = values[i]; if (!next) { free(*stemsnap); *stemsnap = NULL; } *stemsnapcnt = next; free(values); /* I assume ascending order here and in normalize_stems(). */ qsort(*stemsnap, *stemsnapcnt, sizeof(StdStem), SortStems); } /* No StdW, but StemSnap exists? */ if (stdw->width == -1 && *stemsnap != NULL) { cnt = *stemsnapcnt; i = cnt/2; stdw->width = (*stemsnap)[i].width; memmove((*stemsnap)+i, (*stemsnap)+i+1, cnt-i-1); if (--(*stemsnapcnt) == 0) { free(*stemsnap); *stemsnap = NULL; } } } /* Assign CVT indices to blues and stems in global instructing context. In case * we can't implant it because of already existent cvt table, reassign the cvt * indices, picking them from existing cvt table (thus a cvt value can't be * considered 'horizontal' or 'vertical', and reliable stem normalization is * thus impossible) and adding some for new values. */ static void init_cvt(GlobalInstrCt *gic) { int i, cvtindex, cvtsize; struct ttf_table *tab; uint8 *cvt; cvtsize = 1; if (gic->stdhw.width != -1) cvtsize++; if (gic->stdvw.width != -1) cvtsize++; cvtsize += gic->stemsnaphcnt; cvtsize += gic->stemsnapvcnt; cvtsize += gic->bluecnt * 2; /* possible family blues */ cvt = calloc(cvtsize, cvtsize * sizeof(int16)); cvtindex = 0; /* Assign cvt indices */ for (i=0; ibluecnt; i++) { gic->blues[i].cvtindex = cvtindex; memputshort(cvt, 2*cvtindex++, rint(gic->blues[i].base)); if (isfinite(gic->blues[i].family_base)) { gic->blues[i].family_cvtindex = cvtindex; memputshort(cvt, 2*cvtindex++, rint(gic->blues[i].family_base)); } } if (gic->stdhw.width != -1) { gic->stdhw.cvtindex = cvtindex; memputshort(cvt, 2*cvtindex++, rint(gic->stdhw.width)); } for (i=0; istemsnaphcnt; i++) { gic->stemsnaph[i].cvtindex = cvtindex; memputshort(cvt, 2*cvtindex++, rint(gic->stemsnaph[i].width)); } if (gic->stdvw.width != -1) { gic->stdvw.cvtindex = cvtindex; memputshort(cvt, 2*cvtindex++, rint(gic->stdvw.width)); } for (i=0; istemsnapvcnt; i++) { gic->stemsnapv[i].cvtindex = cvtindex; memputshort(cvt, 2*cvtindex++, rint(gic->stemsnapv[i].width)); } cvtsize = cvtindex; cvt = realloc(cvt, cvtsize * sizeof(int16)); /* Try to implant the new cvt table */ gic->cvt_done = 0; tab = SFFindTable(gic->sf, CHR('c','v','t',' ')); if ( tab==NULL ) { tab = chunkalloc(sizeof(struct ttf_table)); tab->next = gic->sf->ttf_tables; gic->sf->ttf_tables = tab; tab->tag = CHR('c','v','t',' '); tab->len = tab->maxlen = cvtsize * sizeof(int16); if (tab->maxlen >256) tab->maxlen = 256; tab->data = cvt; gic->cvt_done = 1; } else { if (tab->len >= cvtsize * (int)sizeof(int16) && memcmp(cvt, tab->data, cvtsize * sizeof(int16)) == 0) gic->cvt_done = 1; free(cvt); if (!gic->cvt_done) { ff_post_error(_("Can't insert 'cvt'"), _("There already exists a 'cvt' table, perhaps legacy. " "FontForge can use it, but can't make any assumptions on " "values stored there, so generated instructions will be of " "lower quality. If legacy hinting is to be scrapped, it is " "suggested to clear the `cvt` and repeat autoinstructing. " )); } } if (gic->cvt_done) return; /* Fallback mode starts here. */ for (i=0; ibluecnt; i++) gic->blues[i].cvtindex = TTF_getcvtval(gic->sf, gic->blues[i].base); if (gic->stdhw.width != -1) gic->stdhw.cvtindex = TTF_getcvtval(gic->sf, gic->stdhw.width); for (i=0; istemsnaphcnt; i++) gic->stemsnaph[i].cvtindex = TTF_getcvtval(gic->sf, gic->stemsnaph[i].width); if (gic->stdvw.width != -1) gic->stdvw.cvtindex = TTF_getcvtval(gic->sf, gic->stdvw.width); for (i=0; istemsnapvcnt; i++) gic->stemsnapv[i].cvtindex = TTF_getcvtval(gic->sf, gic->stemsnapv[i].width); } /* We'll need at least STACK_DEPTH stack levels and a twilight point (and thus * also a twilight zone). We also currently define some functions in fpgm. * We must ensure this is indicated in the 'maxp' table. * * We also need two storage cells. As we now use SPVFS to set projection * vector for diagonal hinting, we have to adjust values taken by SPVFS, * so that diagonals look cleanly in all aspect ratios. Adjustments are * not trivial to compute, so we do this once (in prep) and store them * in storage[0] (for X direction) and storage[1] (for Y direction). */ static void init_maxp(GlobalInstrCt *gic) { struct ttf_table *tab = SFFindTable(gic->sf, CHR('m','a','x','p')); uint16 zones, twpts, store, fdefs, stack; if ( tab==NULL ) { tab = chunkalloc(sizeof(struct ttf_table)); tab->next = gic->sf->ttf_tables; gic->sf->ttf_tables = tab; tab->tag = CHR('m','a','x','p'); } if ( tab->len<32 ) { tab->data = realloc(tab->data,32); memset(tab->data+tab->len,0,32-tab->len); tab->len = tab->maxlen = 32; } zones = memushort(tab->data, 32, 7*sizeof(uint16)); twpts = memushort(tab->data, 32, 8*sizeof(uint16)); store = memushort(tab->data, 32, 9*sizeof(uint16)); fdefs = memushort(tab->data, 32, 10*sizeof(uint16)); stack = memushort(tab->data, 32, 12*sizeof(uint16)); if (gic->fpgm_done && zones<2) zones=2; if (gic->fpgm_done && twpts<1) twpts=1; if (gic->fpgm_done && gic->prep_done && store<2) store=2; if (gic->fpgm_done && fdefs<22) fdefs=22; if (stackdata, 7*sizeof(uint16), zones); memputshort(tab->data, 8*sizeof(uint16), twpts); memputshort(tab->data, 9*sizeof(uint16), store); memputshort(tab->data,10*sizeof(uint16), fdefs); memputshort(tab->data,12*sizeof(uint16), stack); } /* Other hinting software puts certain actions in FPGM to ease developer's life * and compress the code. I feel that having a 'standard' library of functions * could also help FF users. * * Caution! This code is heavily relied by autohinting. Any other code should * be placed below it. It's good to first clear font's hinting tables, then * autohint it, and then insert user's own code and do the manual hinting of * glyphs that do need it. */ static void init_fpgm(GlobalInstrCt *gic) { uint8 new_fpgm[] = { /* Function 0: position a point within a blue zone (given via cvt). * Note: in case of successful init of 'cvt' and 'prep' this function * could be much simpler. * Syntax: PUSHB_3 point cvt_of_blue 0 CALL */ 0xb0, // PUSHB_1 0x00, // 0 0x2c, // FDEF 0xb0, // PUSHB_1 0x00, // 0 0x13, // SZP0 0x4b, // MPPEM 0xb0, // PUSHB_1 - under this ppem blues will be specially rounded GetBlueScale(gic->sf), 0x50, // LT 0x58, // IF 0xb0, // PUSHB_0 0x4a, // 74 0x76, // SROUND - round blues a bit up to grid 0x59, // EIF 0xb0, // PUSHB_1 0x00, // 0 0x23, // SWAP 0x3f, // MIAP[rnd] - blue zone positioned here 0x18, // RTG - round state for overshoots in monochrome mode 0xb0, // PUSHB_1 0x06, // 6 0x2b, // CALL 0x58, // IF 0x3d, // RTDG - round state for overshoots in antialiased mode 0x59, // EIF 0x4b, // MPPEM 0xb0, // PUSHB_1 - under following ppem overshoots will be suppressed GetBlueScale(gic->sf), 0x50, // LT 0x58, // IF 0x7d, // RDTG - suppress overshoots 0x59, // EIF 0x20, // DUP 0xd4, // MDRP[rp0,rnd,grey] 0xb0, // PUSHB_1 0x01, // 1 0x13, // SZP0 0x2e, // MDAP[no-rnd] 0x18, // RTG 0x2d, // ENDF /* Function 1: Place given point relatively to previous, maintaining the * minimum distance. Then call FPGM 12 to check if the point's gridfitted * position is too far from its original position, and correct it, if necessary. * Syntax: PUSB_2 point 1 CALL */ 0xb0, // PUSHB_1 0x01, // 1 0x2c, // FDEF 0x20, // DUP 0xda, // MDRP[rp0,min,white] 0xb0, // PUSHB_1 0x0c, // 12 0x2b, // CALL 0x2d, // ENDF /* Function 2: Below given ppem, substitute the width with cvt entry. * Leave the resulting width on the stack. Used as the first step in * normalizing cvt stems, see normalize_stem(). * Syntax: PUSHX_3 width cvt_index ppem 2 CALL */ 0xb0, // PUSHB_1 0x02, // 2 0x2c, // FDEF 0x4b, // MPPEM 0x52, // GT 0x58, // IF 0x45, // RCVT 0x23, // SWAP 0x59, // EIF 0x21, // POP 0x2d, // ENDF /* Function 3: round a stack element as a black distance, respecting * minimum distance of 1px. This is used for rounding stems after width * normalization. Often preceeded with SROUND, so finally sets RTG. * Leaves the rounded width on the stack. * Syntax: PUSHX_2 width_to_be_rounded 3 CALL */ 0xb0, // PUSHB_1 0x03, // 3 0x2c, // FDEF 0x69, // ROUND[black] 0x18, // RTG 0x20, // DUP 0xb0, // PUSHB_1 0x40, // 64, that's one pixel as F26Dot6 0x50, // LT 0x58, // IF 0x21, // POP 0xb0, // PUSHB_1 0x40, // 64 0x59, // EIF 0x2d, // ENDF /* Function 4: Position the second edge of a stem that is not normally * regularized via cvt (but we snap it to cvt width below given ppem). * Vertical stems need special round state when not snapped to cvt * (basically, they are shortened by 0.25px before being rounded). * Syntax: PUSHX_5 pt cvt_index chg_rp0 ppem 4 CALL */ 0xb0, // PUSHB_1 0x04, // 4 0x2c, // FDEF 0xb0, // PUSHB_1 0x06, // 6 0x2b, // CALL 0x58, // IF 0x21, // POP 0x23, // SWAP 0x21, // POP 0x7a, // ROFF 0x58, // IF 0xdd, // MDRP[rp0,min,rnd,black] 0x1b, // ELSE 0xcd, // MDRP[min,rnd,black] 0x59, // EIF 0x1b, // ELSE 0x4b, // MPPEM 0x52, // GT 0x58, // IF 0x58, // IF 0xfd, // MIRP[rp0,min,rnd,black] 0x1b, // ELSE 0xed, // MIRP[min,rnd,black] 0x59, // EIF 0x1b, // ELSE 0x23, // SWAP 0x21, // POP 0xb0, // PUSHB_1 0x05, // 5 0x2b, // CALL 0x58, // IF 0xb0, // PUSHB_1 0x46, // 70 0x76, // SROUND 0x59, // EIF 0x58, // IF 0xdd, // MDRP[rp0,min,rnd,black] 0x1b, // ELSE 0xcd, // MDRP[min,rnd,black] 0x59, // EIF 0x59, // EIF 0x59, // EIF 0x18, // RTG 0x2d, // ENDF /* Function 5: determine if we are hinting vertically. The function * is crude and it's use is limited to conditions set by SVTCA[]. * Syntax: PUSHB_1 5 CALL; leaves boolean on the stack. */ 0xb0, // PUSHB_1 0x05, // 5 0x2c, // FDEF 0x0d, // GFV 0x5c, // NOT 0x5a, // AND 0x2d, // ENDF /* Function 6: check if we are hinting in grayscale. * CAUTION! Older FreeType versions lie if asked. * Syntax: PUSHB_1 6 CALL; leaves boolean on the stack. */ 0xb0, // PUSHB_1 0x06, // 6 0x2c, // FDEF 0xb1, // PUSHB_2 0x22, // 34 0x01, // 1 0x88, // GETINFO 0x50, // LT 0x58, // IF 0xb0, // PUSHB_1 0x20, // 32 0x88, // GETINFO 0x5c, // NOT 0x5c, // NOT 0x1b, // ELSE 0xb0, // PUSHB_1 0x00, // 0 0x59, // EIF 0x2d, // ENDF /* Function 7: check if we are hinting in cleartype. * CAUTION! FreeType doesn't support that, as subpixel * filtering is usually done by higher level library. * Syntax: PUSHB_1 7 CALL; leaves boolean on the stack. */ 0xb0, // PUSHB_1 0x07, // 7 0x2c, // FDEF 0xb1, // PUSHB_2 0x24, // 36 0x01, // 1 0x88, // GETINFO 0x50, // LT 0x58, // IF 0xb0, // PUSHB_1 0x40, // 64 0x88, // GETINFO 0x5c, // NOT 0x5c, // NOT 0x1b, // ELSE 0xb0, // PUSHB_1 0x00, // 0 0x59, // EIF 0x2d, // ENDF /* Function 8: Interpolate a point between * two other points and snap it to the grid. * Syntax: PUSHX_4 pt_to_ip rp1 rp2 8 CALL; */ 0xb0, // PUSHB_1 0x08, // 8 0x2c, // FDEF 0x12, // SRP2 0x11, // SRP1 0x20, // DUP 0x39, // IP 0x2f, // MDAP[rnd] 0x2d, // ENDF /* Function 9: Link a serif-like element edge to the opposite * edge of the base stem when rounding down to grid, but ensure * that its distance from the reference point is larger than * the base stem width at least to a specified amount of pixels. * Syntax: PUSHX_3 min_dist inner_pt outer_pt CALL; */ 0xb0, // PUSHB_1 0x09, // 9 0x2c, // FDEF 0x20, // DUP 0x7d, // RDTG 0xb0, // PUSHB_1 0x06, // 6 0x2b, // CALL 0x58, // IF 0xc4, // MDRP[min,grey] 0x1b, // ELSE 0xcd, // MDRP[min,rnd,black] 0x59, // EIF 0x20, // DUP 0xb0, // PUSHB_1 0x03, // 3 0x25, // CINDEX 0x49, // MD[grid] 0x23, // SWAP 0x20, // DUP 0xb0, // PUSHB_1 0x04, // 4 0x26, // MINDEX 0x4a, // MD[orig] 0xb0, // PUSHB_1 0x00, // 0 0x50, // LT 0x58, // IF 0x8a, // ROLL 0x65, // NEG 0x8a, // ROLL 0x61, // SUB 0x20, // DUP 0xb0, // PUSHB_1 0x00, // 0 0x50, // LT 0x58, // IF 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x1b, // ELSE 0x8a, // ROLL 0x8a, // ROLL 0x61, // SUB 0x20, // DUP 0xb0, // PUSHB_1 0x00, // 0 0x52, // GT 0x58, // IF 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x59, // EIF 0x18, // RTG 0x2d, // ENDF /* Function 10: depending from the hinting mode (grayscale or mono) set * rp0 either to pt1 or to pt2. This is used to link serif-like elements * either to the opposite side of the base stem or to the same side (i. e. * left-to-left and right-to-right). * Syntax: PUSHX_3 pt2 pt1 10 CALL */ 0xb0, // PUSHB_1 0x0a, // 10 0x2c, // FDEF 0xb0, // PUSHB_1 0x06, // 6 0x2b, // CALL 0x58, // IF 0x21, // POP 0x10, // SRP0 0x1b, // ELSE 0x10, // SRP0 0x21, // POP 0x59, // EIF 0x2d, // ENDF /* Function 11: similar to FPGM 1, but places a point without * maintaining the minimum distance. * Syntax: PUSHX_2 point 11 CALL */ 0xb0, // PUSHB_1 0x0b, // 11 0x2c, // FDEF 0x20, // DUP 0xd2, // MDRP[rp0,white] 0xb0, // PUSHB_1 0x0c, // 12 0x2b, // CALL 0x2d, // ENDF /* Function 12: Check if the gridfitted position of the point is too far * from its original position, and shift it, if necessary. The function is * used to place vertical stems, it assures almost linear advance width * to PPEM scaling. Shift amount is capped to at most 1 px to prevent some * weird artifacts at very small ppems. In cleartype mode, no shift * is made at all. * Syntax: PUSHX_2 point 12 CALL */ 0xb0, // PUSHB_1 0x0c, // 12 0x2c, // FDEF 0x20, // DUP 0x2f, // MDAP[rnd], this is needed for grayscale mode 0xb0, // PUSHB_1 0x07, // 7 0x2b, // CALL 0x5c, // NOT 0x58, // IF 0x20, // DUP 0x20, // DUP 0x47, // GC[cur] 0x23, // SWAP 0x46, // GC[orig] 0x61, // SUB 0x6a, // ROUND[white] 0x20, // DUP 0x58, // IF 0x20, // DUP 0x64, // ABS 0x62, // DIV 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x1b, // ELSE 0x21, // POP 0x59, // EIF 0x2d, // ENDF /* Function 13: Interpolate a HStem edge's reference point between two other points * and snap it to the grid. Then compare its new position with the ungridfitted * position of the second edge. If the gridfitted point belongs to the bottom edge * and now it is positioned above the top edge's original coordinate, then shift it * one pixel down; similarly, if the interpolation resulted in positioning the top * edge below the original coordinate of the bottom edge, shift it one pixel up. * Syntax: PUSHX_6 other_edge_refpt pt_to_ip rp1 rp2 13 CALL */ 0xb0, // PUSHB_1 0x0d, // 13 0x2c, // FDEF 0x12, // SRP2 0x11, // SRP1 0x20, // DUP 0x20, // DUP 0x39, // IP 0x2f, // MDAP[rnd] 0x20, // DUP 0x8a, // ROLL 0x20, // DUP 0x47, // GC[orig] 0x8a, // ROLL 0x46, // GC[cur] 0x61, // SUB 0x23, // SWAP 0x8a, // ROLL 0x20, // DUP 0x8a, // ROLL 0x23, // SWAP 0x4A, // MD[orig] 0xb0, // PUSHB_1 0x00, // 0 0x50, // LT 0x58, // IF 0x23, // SWAP 0xb0, // PUSHB_1 0x00, // 0 0x52, // GT 0x58, // IF 0xb0, // PUSHB_1 0x40, // 64 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x59, // EIF 0x1b, // ELSE 0x23, // SWAP 0xb0, // PUSHB_1 0x00, // 0 0x50, // LT 0x58, // IF 0xb0, // PUSHB_1 0x40, // 64 0x65, // NEG 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x59, // EIF 0x59, // EIF 0x2d, // ENDF /* Function 14: Link two points using MDRP without maintaining * the minimum distance. In antialiased mode use rounding to * double grid for this operation, otherwise ensure there is no * distance between those two points below the given PPEM (i. e. * points are aligned). The function is used for linking nested * stems to each other, and guarantees their relative positioning * is preserved in the gridfitted outline. * Syntax: PUSHX_4 ppem ref_pt base_pt 14 CALL; */ 0xb0, // PUSHB_1 0x0e, // 14 0x2c, // FDEF 0xb0, // PUSHB_1 0x06, // 6 0x2b, // CALL 0x58, // IF 0x3d, // RTDG 0xd6, // MDRP[rp0,rnd,white] 0x18, // RTG 0x21, // POP 0x21, // POP 0x1b, // ELSE 0x20, // DUP 0xd6, // MDRP[rp0,rnd,white] 0x8a, // ROLL 0x4b, // MPPEM 0x52, // GT 0x58, // IF 0x20, // DUP 0x8a, // ROLL 0x23, // SWAP 0x49, // MD[grid] 0x20, // DUP 0xb0, // PUSHB_1 0x00, // 0 0x55, // NEQ 0x58, // IF 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x59, // EIF 0x2d, // ENDF /* Function 15: similar to FPGM 1, but used to position a stem * relatively to the previous stem preserving the counter width * equal to the distance between another pair of previously positioned * stems. Thus it serves nearly the same purpose as PS counter hints. * Syntax: PUSHX_6 master_counter_start_pt master_counter_end_pt * current_counter_start_pt current_counter_end_pt ppem 15 CALL; */ 0xb0, // PUSHB_1 0x0f, // 15 0x2c, // FDEF 0x23, // SWAP 0x20, // DUP 0xd6, // MDRP[rp0,rnd,white] 0x20, // DUP 0x2f, // MDAP[rnd], this is needed for grayscale mode 0xb0, // PUSHB_1 0x07, // 7 0x2b, // CALL 0x5c, // NOT 0x58, // IF 0x23, // SWAP 0x20, // DUP 0x58, // IF 0x4b, // MPPEM 0x53, // GTEQ 0x1b, // ELSE 0x21, // POP 0xb0, // PUSHB_1 0x01, // 1 0x59, // EIF 0x58, // IF 0x8a, // ROLL 0xb0, // PUSHB_1 0x04, // 4 0x26, // MINDEX 0x49, // MD[grid] 0x23, // SWAP 0x8a, // ROLL 0x23, // SWAP 0x20, // DUP 0x8a, // ROLL 0x49, // MD[grid] 0x8a, // ROLL 0x23, // SWAP 0x61, // SUB 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x21, // POP 0x21, // POP 0x59, // EIF 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x21, // POP 0x21, // POP 0x21, // POP 0x59, // EIF 0x2d, // ENDF /* Function 16: Same as FPGM 1, but calls FPGM 18 rather than FPGM 12 * and thus takes 3 arguments. * Syntax: PUSHX_3 ref_point point 16 CALL */ 0xb0, // PUSHB_1 0x10, // 16 0x2c, // FDEF 0x20, // DUP 0xda, // MDRP[rp0,min,white] 0xb0, // PUSHB_1 0x12, // 18 0x2b, // CALL 0x2d, // ENDF /* Function 17: Same as FPGM 11, but calls FPGM 18 rather than FPGM 12 * and thus takes 3 arguments. * Syntax: PUSHX_3 ref_point point 17 CALL */ 0xb0, // PUSHB_1 0x11, // 17 0x2c, // FDEF 0x20, // DUP 0xd2, // MDRP[rp0,white] 0xb0, // PUSHB_1 0x12, // 18 0x2b, // CALL 0x2d, // ENDF /* Function 18: this is a special version of FPGM 12, used when the counter * control is enabled but doesn't directly affect the stem which is going to * be positioned. Unlike FPGM 12, it doesn't just attempt to position a point * closely enough to its original coordinate, but also checks if the previous * stem has already been shifted relatively to its "ideal" position FPGM 12 would * determine. If so, then the desired point position is corrected relatively to * the current placement of the previous stem. * Syntax: PUSHX_3 ref_point point 18 CALL */ 0xb0, // PUSHB_1 0x12, // 18 0x2c, // FDEF 0x20, // DUP 0x2f, // MDAP[rnd], this is needed for grayscale mode 0xb0, // PUSHB_1 0x07, // 7 0x2b, // CALL 0x5c, // NOT 0x58, // IF 0x20, // DUP 0x20, // DUP 0x47, // GC[cur] 0x23, // SWAP 0x46, // GC[orig] 0x61, // SUB 0x6a, // ROUND[white] 0x8a, // ROLL 0x20, // DUP 0x47, // GC[cur] 0x23, // SWAP 0x46, // GC[orig] 0x23, // SWAP 0x61, // SUB 0x6a, // ROUND[white] 0x60, // ADD 0x20, // DUP 0x58, // IF 0x20, // DUP 0x64, // ABS 0x62, // DIV 0x38, // SHPIX 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x1b, // ELSE 0x21, // POP 0x21, // POP 0x59, // EIF 0x2d, // ENDF /* Function 19: used to align a point relatively to a diagonal line, * specified by two other points. First we check if the point going * to be positioned doesn't deviate too far from the line in the original * outline. If the deviation is small enough to neglect it, we use ALIGNRP * to position the point, otherwise MDRP is used instead. We can't just * always use MDRP, because this command may produce wrong results at * small PPEMs, if the original and gridfitted coordinates of the line end * points specify slightly different unit vectors. * Syntax: point diag_start_point diag_end_point 19 CALL */ 0xb0, // PUSHB_1 0x13, // 19 0x2c, // FDEF 0x20, // DUP 0x8a, // ROLL 0x20, // DUP 0x8a, // ROLL 0x87, // SDPVTL[orthogonal] 0x20, // DUP 0xb0, // PUSHB_1 0x03, // 4 0x25, // CINDEX 0x4a, // MD[orig] 0x64, // ABS 0x23, // SWAP 0x8a, // ROLL 0x07, // SPVTL[orthogonal] 0xb0, // PUSHB_1 0x20, // 32 0x50, // LT 0x58, // IF 0x3c, // ALIGNRP 0x1b, // ELSE 0xc0, // MDRP[grey] 0x59, // EIF 0x2d, // ENDF /* Function 20: compute adjustments for X and Y components of projection * vector, for aspect ratios different than 1:1, and store them * in storage[0] and storage[1] respectively. * Syntax: 20 CALL (use it only ONCE, from PREP table). */ 0xb0, // PUSHB_1 0x14, // 20 0x2c, // FDEF 0xb3, // PUSHB_4 (we normally need no adjustments) 0x00, // 0 0x40, // 1.0 (F26Dot6) 0x01, // 1 0x40, // 1.0 (F26Dot6) 0x42, // WS 0x42, // WS 0x01, // SVTCA[x-axis] 0x4b, // MPPEM 0xb8, // PUSHW_1 0x10, // 4096 0x00, // ...still that 4096 0x63, // MUL (so we have PPEM along X casted to F26Dot6) 0x00, // SVTCA[y-axis] 0x4b, // MPPEM 0xb8, // PUSHW_1 0x10, // 4096 0x00, // ...still that 4096 0x63, // MUL (so we have PPEM along Y casted to F26Dot6) 0x20, // DUP 0x8a, // ROLL 0x20, // DUP 0x8a, // ROLL 0x55, // NEQ 0x58, // IF (if PPEM along X != PPEM along Y) 0x20, // DUP 0x8a, // ROLL 0x20, // DUP 0x8a, // ROLL 0x52, // GT 0x58, // IF (if PPEM along X < PPEM along Y) 0x23, // SWAP 0x62, // DIV 0x20, // DUP 0xb0, // PUSHB_1 0x00, // 0 0x23, // SWAP 0x42, // WS 0x1b, // ELSE (if PPEM along X > PPEM along Y) 0x62, // DIV 0x20, // DUP 0xb0, // PUSHB_1 0x01, // 1 0x23, // SWAP 0x42, // WS 0x59, // EIF 0x20, // DUP [A LOOP STARTS HERE] 0xb0, // PUSHB_1 0x40, // 1.0 (F26Dot6) 0x52, // GT 0x58, // IF (bigger adjustment is greater than 1.0 => needs fixing) 0xb2, // PUSHB_3 0x00, // 0 0x20, // 0.5 (F26Dot6) 0x00, // 0 0x43, // RS 0x63, // MUL 0x42, // WS (we halved adjustment for X) 0xb2, // PUSHB_3 0x01, // 1 0x20, // 0.5 (F26Dot6) 0x01, // 1 0x43, // RS 0x63, // MUL 0x42, // WS (we halved adjustment for Y) 0xb0, // PUSHB_1 0x20, // 0.5 (F26Dot6) 0x63, // MUL (we halved the bigger adjustment) 0xb0, // PUSHB_1 0x19, // 25 0x65, // NEG 0x1c, // JMPR (go back to the start of the loop) 0x21, // POP 0x59, // EIF 0x1b, // ELSE (if PPEM along X == PPEM along Y) 0x21, // POP 0x21, // POP 0x59, // EIF 0x2d, // ENDF /* Function 21: call it before SFVFS or SPVFS, so that the vector * passed is aspect-ratio corrected. * Syntax: x y 21 CALL */ 0xb0, // PUSHB_1 0x15, // 21 0x2c, // FDEF 0xb0, // PUSHB_1 0x01, // 1 0x43, // RS 0x63, // MUL 0x23, // SWAP 0xb0, // PUSHB_1 0x00, // 0 0x43, // RS 0x63, // MUL 0x23, // SWAP 0x2d // ENDF }; struct ttf_table *tab = SFFindTable(gic->sf, CHR('f','p','g','m')); if ( tab==NULL ) { /* We have to create such table. */ tab = chunkalloc(sizeof(struct ttf_table)); tab->next = gic->sf->ttf_tables; gic->sf->ttf_tables = tab; tab->tag = CHR('f','p','g','m'); tab->len = 0; } if (tab->len==0 || (tab->len < (int)sizeof(new_fpgm) && !memcmp(tab->data, new_fpgm, tab->len))) { /* We can safely update font program. */ tab->len = tab->maxlen = sizeof(new_fpgm); tab->data = realloc(tab->data, sizeof(new_fpgm)); memmove(tab->data, new_fpgm, sizeof(new_fpgm)); gic->fpgm_done = 1; } else { /* there already is a font program. */ gic->fpgm_done = 0; if (tab->len >= (int)sizeof(new_fpgm)) if (!memcmp(tab->data, new_fpgm, sizeof(new_fpgm))) gic->fpgm_done = 1; /* it's ours. */ /* Log warning message. */ if (!gic->fpgm_done) ff_post_error(_("Can't insert 'fpgm'"), _("There exists a 'fpgm' code that seems incompatible with " "FontForge's. Instructions generated will be of lower " "quality. If legacy hinting is to be scrapped, it is " "suggested to clear the `fpgm` and repeat autoinstructing. " "It will be then possible to append user's code to " "FontForge's 'fpgm', but due to possible future updates, " "it is extremely advised to use high numbers for user's " "functions." )); } } /* When initializing global instructing context, we want to set up the 'prep' * table in order to apply family blues and normalize stem widths for monochrome * display. * * The stem normalizer is heavily based on simple concept from FreeType2. * * First round the StdW. Then for each StemSnap (going outwards from StdW) check * if it's within 1px from its already rounded neighbor, and if so, snap it * before rounding. From all vertical stems (but not StdHW itself), 0.25px is * subtracted before rounding. Similar method is used for non-cvt stems, they're * snapped to the closest standard width if possible. * * NOTE: because of tiny scaling issues, we have to compute ppem at which each * stem stops being snapped to its already-rounded neighbor here instead of * relegating this to the truetype bytecide interpreter. We can't simply rely * on cvt cut-in. */ static int compute_blue_height(real val, int EM, int bluescale, int ppem) { int scaled_val = rint((rint(fabs(val)) * ppem * 64)/EM); if (ppem < bluescale) scaled_val += 16; return (scaled_val + 32) / 64 * (val / fabs(val)); } static uint8 *use_family_blues(uint8 *prep_head, GlobalInstrCt *gic) { int i, h1, h2, stopat; int bs = GetBlueScale(gic->sf); int EM = gic->sf->ascent + gic->sf->descent; int callargs[3]; for (i=0; ibluecnt; i++) { if (isfinite(gic->blues[i].family_base)) { for (stopat=0; stopat<32768; stopat++) { h1 = compute_blue_height(gic->blues[i].base, EM, bs, stopat); h2 = compute_blue_height(gic->blues[i].family_base, EM, bs, stopat); if (abs(h1 - h2) > 1) break; } callargs[0] = gic->blues[i].family_cvtindex; callargs[1] = stopat; callargs[2] = 2; prep_head = pushnum(prep_head, gic->blues[i].cvtindex); *prep_head++ = DUP; *prep_head++ = 0x45; //RCVT prep_head = pushnums(prep_head, 3, callargs); *prep_head++ = CALL; *prep_head++ = 0x44; //WCVTP } } return prep_head; } /* Return width (in pixels) of given stem, taking snaps into account. */ #define SNAP_THRESHOLD (64) static int compute_stem_width(int xdir, StdStem *stem, int EM, int ppem) { int scaled_width; /* in 1/64th pixels */ int snapto_width; /* in 1/64th pixels */ scaled_width = (int)rint((rint(fabs(stem->width)) * ppem * 64.0)/EM); if (scaled_width < 64) scaled_width = 64; if (stem->snapto != NULL) { if (stem->stopat > ppem) { snapto_width = 64*compute_stem_width(xdir, stem->snapto, EM, ppem); if (abs(snapto_width - scaled_width) < SNAP_THRESHOLD) scaled_width = snapto_width; } if (xdir) scaled_width -= 16; } return (scaled_width + 32) / 64; } /* Normalize a single stem. The code generated assumes there is a scaled stem * width on bytecode interpreter's stack, and leaves normalized width there. */ static uint8 *normalize_stem(uint8 *prep_head, int xdir, StdStem *stem, GlobalInstrCt *gic) { int callargs[3]; int i; stem->stopat = 32767; if (stem->snapto != NULL) { /* compute ppem at which to stop snapping stem to stem->snapto */ int EM = gic->sf->ascent + gic->sf->descent; for (i=7; i<32768; i++) { int width_parent = compute_stem_width(xdir, stem->snapto, EM, i); int width_me = compute_stem_width(xdir, stem, EM, i); if (width_parent != width_me) { stem->stopat = i; break; } } /* snap if below given ppem */ callargs[0] = stem->snapto->cvtindex; callargs[1] = stem->stopat; callargs[2] = 2; prep_head = pushnums(prep_head, 3, callargs); *prep_head++ = CALL; /* Round[black], respecting minimum distance of 1 px */ /* Vertical stems (but not StdVW) use special rounding threshold. */ /* The rounding function restores default round state at the end. */ if (xdir) { prep_head = push2nums(prep_head, 3, 70); *prep_head++ = SROUND; } else prep_head = pushnum(prep_head, 3); *prep_head++ = CALL; } else { /* simply round[black] respecting minimum distance of 1 px */ prep_head = pushnum(prep_head, 3); *prep_head++ = CALL; } return prep_head; } /* Append the code for normalizing standard stems' widths to 'prep'. */ static uint8 *normalize_stems(uint8 *prep_head, int xdir, GlobalInstrCt *gic) { int i, t; StdStem *mainstem = xdir?&(gic->stdvw):&(gic->stdhw); StdStem *otherstems = xdir?gic->stemsnapv:gic->stemsnaph; int otherstemcnt = xdir?gic->stemsnapvcnt:gic->stemsnaphcnt; if (mainstem->width == -1) return prep_head; /* set up the standard width */ mainstem->snapto = NULL; *prep_head++ = xdir?SVTCA_x:SVTCA_y; prep_head = pushnum(prep_head, mainstem->cvtindex); *prep_head++ = DUP; *prep_head++ = 0x45; //RCVT prep_head = normalize_stem(prep_head, xdir, mainstem, gic); *prep_head++ = 0x44; //WCVTP /* set up other standard widths */ for (i=0; iwidth; i++); t = i-1; for (i=t; i>=0; i--) { otherstems[i].snapto = i==t?mainstem:otherstems+i+1; prep_head = pushnum(prep_head, otherstems[i].cvtindex); *prep_head++ = DUP; *prep_head++ = 0x45; //RCVT prep_head = normalize_stem(prep_head, xdir, otherstems+i, gic); *prep_head++ = 0x44; //WCVTP } for (i=t+1; icvt_done) { prepmaxlen += 48 + 38*(gic->stemsnaphcnt + gic->stemsnapvcnt); prepmaxlen += 14*(gic->bluecnt); } if (gic->fpgm_done) prepmaxlen += 3; new_prep = calloc(prepmaxlen, sizeof(uint8)); memmove(new_prep, new_prep_preamble, preplen*sizeof(uint8)); prep_head = new_prep + preplen; if (gic->cvt_done && gic->fpgm_done) { /* Apply family blues. */ prep_head = use_family_blues(prep_head, gic); /* Normalize stems (only in monochrome mode) */ prep_head = pushnum(prep_head, 6); *prep_head++ = CALL; *prep_head++ = 0x5c; // NOT *prep_head++ = 0x58; // IF prep_head = normalize_stems(prep_head, 0, gic); prep_head = normalize_stems(prep_head, 1, gic); *prep_head++ = 0x59; // EIF } /* compute adjustments for projection vector */ if (gic->fpgm_done) { prep_head = pushnum(prep_head, 20); *prep_head++ = CALL; } preplen = prep_head - new_prep; tab = SFFindTable(gic->sf, CHR('p','r','e','p')); if ( tab==NULL ) { /* We have to create such table. */ tab = chunkalloc(sizeof(struct ttf_table)); tab->next = gic->sf->ttf_tables; gic->sf->ttf_tables = tab; tab->tag = CHR('p','r','e','p'); tab->len = 0; } if (tab->len==0 || (tab->len < preplen && !memcmp(tab->data, new_prep, tab->len))) { /* We can safely update cvt program. */ tab->len = tab->maxlen = preplen; tab->data = realloc(tab->data, preplen); memmove(tab->data, new_prep, preplen); gic->prep_done = 1; } else { /* there already is a font program. */ gic->prep_done = 0; if (tab->len >= preplen) if (!memcmp(tab->data, new_prep, preplen)) gic->prep_done = 1; /* it's ours */ /* Log warning message. */ if (!gic->prep_done) ff_post_error(_("Can't insert 'prep'"), _("There exists a 'prep' code incompatible with FontForge's. " "It can't be guaranteed it will work well. It is suggested " "to allow FontForge to insert its code and then append user" "'s own." )); } free(new_prep); } /* * Initialize Global Instructing Context */ #define EDGE_FUZZ (500.0) void InitGlobalInstrCt(GlobalInstrCt *gic, SplineFont *sf, int layer, BlueData *bd) { BlueData _bd; if (bd == NULL) { QuickBlues(sf,layer,&_bd); bd = &_bd; } gic->sf = sf; gic->bd = bd; gic->layer = layer; gic->fudge = (sf->ascent+sf->descent)/EDGE_FUZZ; gic->cvt_done = false; gic->fpgm_done = false; gic->prep_done = false; gic->bluecnt = 0; gic->stdhw.width = -1; gic->stemsnaph = NULL; gic->stemsnaphcnt = 0; gic->stdvw.width = -1; gic->stemsnapv = NULL; gic->stemsnapvcnt = 0; GICImportBlues(gic); GICImportStems(0, gic); /* horizontal stems */ GICImportStems(1, gic); /* vertical stems */ init_cvt(gic); init_fpgm(gic); init_prep(gic); init_maxp(gic); } /* * Finalize Global Instructing Context */ void FreeGlobalInstrCt(GlobalInstrCt *gic) { gic->sf = NULL; gic->bd = NULL; gic->fudge = 0; gic->cvt_done = false; gic->fpgm_done = false; gic->prep_done = false; gic->bluecnt = 0; gic->stdhw.width = -1; if (gic->stemsnaphcnt != 0) free(gic->stemsnaph); gic->stemsnaphcnt = 0; gic->stemsnaph = NULL; gic->stdvw.width = -1; if (gic->stemsnapvcnt != 0) free(gic->stemsnapv); gic->stemsnapvcnt = 0; gic->stemsnapv = NULL; } /****************************************************************************** ****************************************************************************** ** ** Stuff for managing global instructing context ends here. Now we'll deal ** with single glyphs. ** ** Many functions here need large or similar sets of arguments. I decided to ** define an '(local) instructing context' to have them in one place and keep ** functions' argument lists reasonably short. I first need to define some ** internal sub-structures for instructing diagonal stems. Similar structures ** for CVT management (based on PS Private) are defined in splinefont.h, and ** were initialized handled above. ** ****************************************************************************** ******************************************************************************/ /* A line, described by two points */ typedef struct pointvector { PointData *pd1, *pd2; int done; } PointVector; /* In this structure we store information about diagonales, relatively to which the given point should be positioned */ typedef struct diagpointinfo { struct pointvector line[2]; int count; } DiagPointInfo; typedef struct instrct { /* Things that are global for font and should be initialized before instructing particular glyph. */ GlobalInstrCt *gic; /* Here things for this particular glyph start. */ SplineChar *sc; SplineSet *ss; /* instructions */ uint8 *instrs; /* the beginning of the instructions */ uint8 *pt; /* the current position in the instructions */ /* properties indexed by contour number */ int *contourends; /* points ending their contours. Null-terminated. */ uint8 *clockwise; /* is given contour clockwise? */ /* properties, indexed by ttf point index. Some could be compressed. */ int ptcnt; /* number of points in this glyph */ BasePoint *bp; /* point coordinates */ uint8 *touched; /* touchflags; points explicitly instructed */ uint8 *affected; /* touchflags; almost touched, but optimized out */ /* data from stem detector */ GlyphData *gd; /* stuff for hinting diagonals */ int diagcnt; StemData **diagstems; DiagPointInfo *diagpts; /* indexed by ttf point index */ /* stuff for hinting edges (stems, blues, strong point interpolation). */ int xdir; /* direction flag: x=true, y=false */ int cdir; /* is current contour outer? - blues need this */ struct __edge { real base; /* where the edge is */ int refpt; /* best ref. point for an edge, ttf index, -1 if none */ int refscore; /* its quality, for searching better one; 0 if none */ int othercnt; /* count of other points to instruct for this edge */ int *others; /* their ttf indices, optimize_edge() is advised */ } edge; /* Some variables for tracking graphics state */ int rp0; } InstrCt; /****************************************************************************** * * Low-level routines for manipulting and classifying splinepoints * ******************************************************************************/ /* Find previous point index on the contour. */ static int PrevOnContour(int *contourends, int p) { int i; if (p == 0) return contourends[0]; else { for (i=0; contourends[i+1]; i++) if (contourends[i]+1 == p) return contourends[i+1]; return p-1; } } /* Find next point index on the contour. */ static int NextOnContour(int *contourends, int p) { int i; if (p == 0) return 1; else { for (i=0; contourends[i]; i++) { if (contourends[i] == p) { if (i==0) return 0; else return contourends[i-1]+1; } } return p+1; } } /* For hinting stems, I found it needed to check if candidate point for * instructing is pararell to hint's direction to avoid snapping wrong points. * I splitted the routine into two, as sometimes it may be needed to check * the angle to be strictly almost the same, not just pararell. */ static int __same_angle(int *contourends, BasePoint *bp, int p, double angle) { int PrevPoint, NextPoint; double PrevTangent, NextTangent; PrevPoint = PrevOnContour(contourends, p); NextPoint = NextOnContour(contourends, p); PrevTangent = atan2(bp[p].y - bp[PrevPoint].y, bp[p].x - bp[PrevPoint].x); NextTangent = atan2(bp[NextPoint].y - bp[p].y, bp[NextPoint].x - bp[p].x); /* If at least one of the tangents is close to the given angle, return */ /* true. 'Close' means about 5 deg, i.e. about 0.087 rad. */ PrevTangent = fabs(PrevTangent-angle); NextTangent = fabs(NextTangent-angle); while (PrevTangent > M_PI) PrevTangent -= 2*M_PI; while (NextTangent > M_PI) NextTangent -= 2*M_PI; return (fabs(PrevTangent) <= 0.087) || (fabs(NextTangent) <= 0.087); } static int same_angle(int *contourends, BasePoint *bp, int p, double angle) { return __same_angle(contourends, bp, p, angle) || __same_angle(contourends, bp, p, angle+M_PI); } /* I found it needed to write some simple functions to classify points snapped * to hint's edges. Classification helps to establish the most accurate leading * point for an edge. */ static int _IsExtremum(int xdir, SplinePoint *sp) { return xdir? (!sp->nonextcp && !sp->noprevcp && sp->nextcp.x==sp->me.x && sp->prevcp.x==sp->me.x): (!sp->nonextcp && !sp->noprevcp && sp->nextcp.y==sp->me.y && sp->prevcp.y==sp->me.y); } static int IsExtremum(int xdir, int p, SplinePoint *sp) { int ret = _IsExtremum(xdir, sp); if ((sp->nextcpindex == p) && (sp->next != NULL) && (sp->next->to != NULL)) ret = ret || _IsExtremum(xdir, sp->next->to); else if ((sp->ttfindex != p) && (sp->prev != NULL) && (sp->prev->from != NULL)) ret = ret || _IsExtremum(xdir, sp->prev->from); return ret; } static int IsCornerExtremum(int xdir, int *contourends, BasePoint *bp, int p) { int PrevPoint = PrevOnContour(contourends, p); int NextPoint = NextOnContour(contourends, p); return xdir? ((bp[PrevPoint].x > bp[p].x && bp[NextPoint].x > bp[p].x) || (bp[PrevPoint].x < bp[p].x && bp[NextPoint].x < bp[p].x)): ((bp[PrevPoint].y > bp[p].y && bp[NextPoint].y > bp[p].y) || (bp[PrevPoint].y < bp[p].y && bp[NextPoint].y < bp[p].y)); } static int IsAnglePoint(int *contourends, BasePoint *bp, SplinePoint *sp) { int PrevPoint, NextPoint, p=sp->ttfindex; double PrevTangent, NextTangent; if ((sp->pointtype != pt_corner) || (p == 0xffff)) return 0; PrevPoint = PrevOnContour(contourends, p); NextPoint = NextOnContour(contourends, p); PrevTangent = atan2(bp[p].y - bp[PrevPoint].y, bp[p].x - bp[PrevPoint].x); NextTangent = atan2(bp[NextPoint].y - bp[p].y, bp[NextPoint].x - bp[p].x); return fabs(PrevTangent - NextTangent) > 0.261; } static int IsInflectionPoint(int *contourends, BasePoint *bp, SplinePoint *sp) { double CURVATURE_THRESHOLD = 1e-9; struct spline *prev, *next; double in, out; if (IsAnglePoint(contourends, bp, sp)) return 0; /* point of a single-point contour can't be an inflection point. */ if (sp->prev != NULL && sp->prev->from != NULL && sp->prev->from == sp) return 0; prev = sp->prev; in = 0; while (prev != NULL && fabs(in) < CURVATURE_THRESHOLD) { in = SplineCurvature(prev, 1); if (fabs(in) < CURVATURE_THRESHOLD) in = SplineCurvature(prev, 0); if (fabs(in) < CURVATURE_THRESHOLD) prev = prev->from->prev; if ((prev != NULL && IsAnglePoint(contourends, bp, prev->to)) || (prev == sp->prev)) break; } next = sp->next; out = 0; while (next != NULL && fabs(out) < CURVATURE_THRESHOLD) { out = SplineCurvature(next, 0); if (fabs(out) < CURVATURE_THRESHOLD) out = SplineCurvature(next, 1); if (fabs(out) < CURVATURE_THRESHOLD) next = next->to->next; if ((next != NULL && IsAnglePoint(contourends, bp, next->from)) || (next == sp->next)) break; } if (in==0 || out==0 || (prev != sp->prev && next != sp->next)) return 0; in/=fabs(in); out/=fabs(out); return (in*out < 0); } /****************************************************************************** * * I found it easier to write an iterator that calls given function for each * point worth instructing than repeating the same loops all the time. * * The control points are not skipped, but runmes often eliminate them as * instructing them seems to cause more damages than profits. They are included * here because edge optimizer cam be simpler and work more reliably then. * * The contour_direction option is for blues - snapping internal contour to a * blue zone is plain wrong, unless there is a stem hint tat don't fit to any * other blue zone. * ******************************************************************************/ #define EXTERNAL_CONTOURS 0 #define ALL_CONTOURS 1 #define INTERNAL_CONTOURS 2 static void RunOnPoints(InstrCt *ct, int contour_direction, void (*runme)(int p, SplinePoint *sp, InstrCt *ct)) { SplineSet *ss = ct->ss; SplinePoint *sp; uint8 *done; int c, p; done = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); for ( c=0; ss!=NULL; ss=ss->next, ++c ) { ct->cdir = ct->clockwise[c]; if (((contour_direction == EXTERNAL_CONTOURS) && !ct->cdir) || ((contour_direction == INTERNAL_CONTOURS) && ct->cdir)) continue; for ( sp=ss->first; ; ) { if (sp->ttfindex != 0xffff) { if (!sp->noprevcp && !done[p = PrevOnContour(ct->contourends, sp->ttfindex)]) { runme(p, sp, ct); done[p] = true; } if (!done[p = sp->ttfindex]) { runme(p, sp, ct); done[p] = true; } if (!sp->nonextcp && !done[p = sp->nextcpindex]) { runme(p, sp, ct); done[p] = true; } } else if (!sp->nonextcp) { if (!done[p = PrevOnContour(ct->contourends, sp->nextcpindex)]) { runme(p, sp, ct); done[p] = true; } if (!done[p = sp->nextcpindex]) { runme(p, sp, ct); done[p] = true; } } if ( sp->next==NULL ) break; sp = sp->next->to; if ( sp==ss->first ) break; } } free(done); } /****************************************************************************** * * Hinting is mostly aligning 'edges' (in FreeType's sense). Each stem hint * consists of two edges (or one, for ghost hints). And each blue zone can be * represented as an edge with extended fudge (overshoot). * * Hinting a stem edge is broken in two steps. First: init_stem_edge() seeks for * points to snap and chooses one that will be used as a reference point - it * should be then instructed elsewhere (a general method of edge positioning). * Old init_edge() is still used instead for blue zones and strong points. * Finally, finish_edge() instructs the rest of points found with given command, * using instructpoints(). It normally optimizes an edge before instructing, * but not in presence of diagonal hints. * * The contour_direction option of init_edge() is for hinting blues - snapping * internal contour to a bluezone seems just plainly wrong. * ******************************************************************************/ /* The following operations have been separated from search_edge(), */ /* because sometimes it is important to be able to determine, if the */ /* given point is about to be gridfitted or interpolated */ static int value_point(InstrCt *ct, int p, SplinePoint *sp, real fudge) { int score = 0; int EM = ct->gic->sf->ascent + ct->gic->sf->descent; uint8 touchflag = ct->xdir?tf_x:tf_y; if (IsCornerExtremum(ct->xdir, ct->contourends, ct->bp, p) || IsExtremum(ct->xdir, p, sp)) score+=4; if (same_angle(ct->contourends, ct->bp, p, ct->xdir?0.5*M_PI:0.0)) score++; if (p == sp->ttfindex && IsAnglePoint(ct->contourends, ct->bp, sp)) score++; if (interpolate_more_strong && (fudge > (EM/EDGE_FUZZ+0.0001))) if (IsExtremum(!ct->xdir, p, sp)) score++; if (IsInflectionPoint(ct->contourends, ct->bp, sp)) score++; if (score && ct->gd->points[p].sp != NULL) /* oncurve */ score+=2; if (!score) return( 0 ); if (ct->diagstems != NULL && ct->diagpts[p].count) score+=9; if (ct->touched[p] & touchflag) score+=26; return( score ); } /* search for points to be snapped to an edge - to be used in RunOnPoints() */ static void search_edge(int p, SplinePoint *sp, InstrCt *ct) { int tmp, score; real fudge = ct->gic->fudge; uint8 touchflag = ct->xdir?tf_x:tf_y; real refcoord, coord = ct->xdir?ct->bp[p].x:ct->bp[p].y; if (fabs(coord - ct->edge.base) <= fudge) { score = value_point(ct, p, sp, ct->gic->fudge); if (!score) return; else if (ct->edge.refpt == -1) { ct->edge.refpt = p; ct->edge.refscore = score; return; } refcoord = ct->xdir?ct->bp[ct->edge.refpt].x:ct->bp[ct->edge.refpt].y; if ((score > ct->edge.refscore) || (score == ct->edge.refscore && fabs(coord - ct->edge.base) < fabs(refcoord - ct->edge.base))) { tmp = ct->edge.refpt; ct->edge.refpt = p; ct->edge.refscore = score; p = tmp; } if ((p!=-1) && !((ct->touched[p] | ct->affected[p]) & touchflag)) { ct->edge.othercnt++; if (ct->edge.othercnt==1) ct->edge.others=(int *)calloc(1, sizeof(int)); else ct->edge.others=(int *)realloc(ct->edge.others, ct->edge.othercnt*sizeof(int)); ct->edge.others[ct->edge.othercnt-1] = p; } } } static int StemPreferredForPoint(PointData *pd, StemData *stem,int is_next ) { StemData **stems; BasePoint bp; real off, bestoff; int i, is_l, best=0, *stemcnt; stems = ( is_next ) ? pd->nextstems : pd->prevstems; stemcnt = ( is_next) ? &pd->nextcnt : &pd->prevcnt; bestoff = 1e4; for ( i=0; i<*stemcnt; i++ ) { /* Ghost hints are always assigned to both sides of a point, no matter * what the next/previous spline direction is. So we need an additional * check for stem unit parallelity */ if (stems[i]->toobig > stem->toobig || stems[i]->unit.x != stem->unit.x || stems[i]->unit.y != stem->unit.y) continue; is_l = is_next ? pd->next_is_l[i] : pd->prev_is_l[i]; bp = is_l ? stems[i]->left : stems[i]->right; off = fabs(( pd->base.x - bp.x )*stem->l_to_r.x + ( pd->base.y - bp.y )*stem->l_to_r.y ); if (off < bestoff || (RealNear(off, bestoff) && stems[i] == stem)) { best = i; bestoff = off; } } if (best < *stemcnt && stem == stems[best]) return( best ); return( -1 ); } static int has_valid_dstem( PointData *pd,int next ) { int i, cnt; StemData *test; cnt = next ? pd->nextcnt : pd->prevcnt; for ( i=0; inextstems[i] : pd->prevstems[i]; if ( !test->toobig && test->lpcnt > 1 && test->rpcnt > 1 && fabs( test->unit.x ) > .05 && fabs( test->unit.y ) > .05 ) return( i ); } return( -1 ); } /* init_stem_edge(): Initialize the InstrCt for instructing given edge. * * Finds points that should be snapped to this hint's given edge. * It will return two types of points: a 'chosen one' ct->edge.refpt, that * should be used as a reference for this hint, and ct->edge.others that should * be positioned after ct.refpt with, for example, SHP. * * assign_points_to_edge() is a helper function, only to use from init_stem_edge(). */ static void assign_points_to_edge(InstrCt *ct, StemData *stem, int is_l, int *refidx) { int i, previdx, nextidx, test_l, dint_inner = false, flag; PointData *pd; flag = RealNear( stem->unit.y,1 ) ? tf_x : tf_y; for ( i=0; igd->realcnt; i++ ) { pd = &ct->gd->points[i]; previdx = StemPreferredForPoint( pd,stem,false ); nextidx = StemPreferredForPoint( pd,stem,true ); if (!pd->ticked && (previdx != -1 || nextidx != -1)) { pd->ticked = true; /* Don't attempt to position inner points at diagonal intersections: * our diagonal stem hinter will handle them better */ if ( ct->diagcnt > 0 && ( ( stem->unit.y == 1 && pd->x_corner == 2 ) || ( stem->unit.x == 1 && pd->y_corner == 2 ))) { dint_inner= has_valid_dstem( pd,true ) != -1 && has_valid_dstem( pd,false ) != -1; } test_l = (nextidx != -1) ? pd->next_is_l[nextidx] : pd->prev_is_l[previdx]; if (test_l == is_l && !dint_inner && !(ct->touched[pd->ttfindex] & flag) && !(ct->affected[pd->ttfindex] & flag)) { ct->edge.others = (int *)realloc( ct->edge.others, (ct->edge.othercnt+1)*sizeof(int)); ct->edge.others[ct->edge.othercnt++] = pd->ttfindex; if ( *refidx == -1 ) *refidx = pd->ttfindex; } } } } static void init_stem_edge(InstrCt *ct, StemData *stem, int is_l) { real left, right, base; struct dependent_stem *slave; PointData *rpd = NULL; int i, *refidx = NULL; left = ( stem->unit.x == 0 ) ? stem->left.x : stem->left.y; right = ( stem->unit.x == 0 ) ? stem->right.x : stem->right.y; base = ( is_l ) ? left : right; ct->edge.base = base; ct->edge.refpt = -1; ct->edge.refscore = 0; ct->edge.othercnt = 0; ct->edge.others = NULL; refidx = ( is_l ) ? &stem->leftidx : &stem->rightidx; if ( *refidx != -1 ) rpd = &ct->gd->points[*refidx]; /* Don't attempt to position inner points at diagonal intersections: * our diagonal stem hinter will handle them better */ if ( rpd != NULL && ct->diagcnt > 0 && ( ( stem->unit.y == 1 && rpd->x_corner == 2 ) || ( stem->unit.x == 1 && rpd->y_corner == 2 )) && has_valid_dstem( rpd,true ) != -1 && has_valid_dstem( rpd,false ) != -1 ) *refidx = -1; for ( i=0; igd->realcnt; i++ ) ct->gd->points[i].ticked = false; assign_points_to_edge(ct, stem, is_l, refidx); for ( i=0; idep_cnt; i++ ) { slave = &stem->dependent[i]; if (slave->dep_type == 'a' && ((is_l && slave->lbase) || (!is_l && !slave->lbase))) { if ( is_l ) slave->stem->leftidx = *refidx; else slave->stem->rightidx = *refidx; assign_points_to_edge(ct, slave->stem, is_l, refidx); } } ct->edge.refpt = *refidx; } /* Initialize the InstrCt for instructing given edge. */ static void init_edge(InstrCt *ct, real base, int contour_direction) { ct->edge.base = base; ct->edge.refpt = -1; ct->edge.refscore = 0; ct->edge.othercnt = 0; ct->edge.others = NULL; RunOnPoints(ct, contour_direction, &search_edge); } /* Apparatus for edge hinting optimization. For given 'others' in ct, * it detects 'segments' (in FreeType's sense) and leaves only one point per * segment. A segment to which refpt belong is completely removed (refpt is * enough). * * optimize_edge() is the right high-level function to call with instructing * context (an edge must be previously initialized with init_edge). It calls * optimize_segment() internally - a function that is otherwise unsafe. * * optimize_blue() is even higher-level function to call before optimize_edge * if init_edge() was used to collect points in a blue zone (or other narrow * zone). * * Optimizers keep points used by diagonal hinter. * * optimize_strongpts() is used instead of two routines above when hinting * inter-stem zones (see interpolate_strong option). It's invoked after * instructing diagonal stems. */ /* To be used with qsort() - sorts integer array in ascending order. */ static int sortbynum(const void *a, const void *b) { return *(int *)a > *(int *)b; } /* Find element's index within an array - return -1 if element not found. */ static int findoffs(const int *elems, int elemcnt, int val) { int i; for (i=0; iedge.others; int touchflag = (ct->xdir)?tf_x:tf_y; int ondiags = 0; if (segstart==segend) return; /* purely for aesthetic reasons - can be safely removed. */ qsort(others+segstart, segend+1-segstart, sizeof(int), sortbynum); /* are there any to be used with dstems? */ if (ct->diagstems) for (i=segstart; !ondiags && i<=segend; i++) ondiags = ct->diagpts[others[i]].count; if (ondiags) { for (i=segstart; i<=segend; i++) ct->affected[others[i]] |= ct->diagpts[others[i]].count?0:touchflag; } else { for (i=segstart; i<=segend && ct->gd->points[others[i]].sp == NULL; i++); if (i<=segend) local_refpt = others[i]; if (findoffs(others+segstart, segend+1-segstart, ct->edge.refpt) != -1) local_refpt = ct->edge.refpt; if (local_refpt==-1) local_refpt = others[segstart]; for (i=segstart; i<=segend; i++) ct->affected[others[i]] |= local_refpt==others[i]?0:touchflag; } } /* Subdivide an edge into segments and optimize segments separately. * A segment consists oh a point, his neighbours, their neighbours... */ static void optimize_edge(InstrCt *ct) { int i, p, segstart, next; int refpt = ct->edge.refpt; int *others = ct->edge.others; int othercnt = ct->edge.othercnt; int touchflag = (ct->xdir)?tf_x:tf_y; if (othercnt == 0) return; /* add edge.refpt to edge.others */ ct->edge.othercnt = ++othercnt; ct->edge.others = others = (int *)realloc(others, othercnt*sizeof(int)); others[othercnt-1]=refpt; next = 0; while (next < othercnt) { p = others[segstart = next++]; while((next < othercnt) && (i = findoffs(others+next, othercnt-next, NextOnContour(ct->contourends, p))) != -1) { p = others[i+=next]; others[i] = others[next]; others[next++] = p; } p=others[segstart]; while((next < othercnt) && (i = findoffs(others+next, othercnt-next, PrevOnContour(ct->contourends, p))) != -1) { p = others[i+=next]; others[i] = others[next]; others[next++] = p; } optimize_segment(segstart, next-1, ct); } for (i=next=0; iaffected[others[i]] & touchflag) && (others[i] != refpt)) others[next++] = others[i]; if ((ct->edge.othercnt = next) == 0) { free(others); ct->edge.others = NULL; } else /* purely for aesthetic reasons - could be safely removed. */ qsort(others, ct->edge.othercnt, sizeof(int), sortbynum); } /* For any given point on edge, if there exists a path to other point snapped * or to-be-snapped in that zone, such that any points on this path are within * that zone, then this given point may be optimized out. */ static void optimize_blue(InstrCt *ct) { int i, j, curr; int *others = ct->edge.others; int othercnt = ct->edge.othercnt; int touchflag = (ct->xdir)?tf_x:tf_y; int *contourends = ct->contourends; uint8 *touched = ct->touched; uint8 *affected = ct->affected; uint8 *tosnap; if (othercnt == 0) return; tosnap = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); for(i=0; iedge.othercnt; i++) { if (ct->diagpts && ct->diagpts[others[i]].count) continue; /* check path forward */ curr=NextOnContour(contourends, others[i]); while(curr!=others[i]) { double coord = (ct->xdir) ? ct->bp[curr].x : ct->bp[curr].y; if (fabs(ct->edge.base - coord) > ct->gic->fudge) break; if ((touched[curr] | affected[curr]) & touchflag || tosnap[curr]) { affected[others[i]] |= touchflag; break; } curr=NextOnContour(contourends, curr); } if (affected[others[i]] & touchflag) continue; /* check path backward */ curr=PrevOnContour(contourends, others[i]); while(curr!=others[i]) { double coord = (ct->xdir) ? ct->bp[curr].x : ct->bp[curr].y; if (fabs(ct->edge.base - coord) > ct->gic->fudge) break; if ((touched[curr] | affected[curr]) & touchflag || tosnap[curr]) { affected[others[i]] |= touchflag; break; } curr=PrevOnContour(contourends, curr); } if (!(affected[others[i]] & touchflag)) tosnap[others[i]] = 1; } free(tosnap); /* remove optimized-out points from list to be instructed. */ for(i=0; iedge.othercnt; i++) if (affected[others[i]]) { ct->edge.othercnt--; for(j=i; jedge.othercnt; j++) others[j] = others[j+1]; i--; } } /* For any strong point, check whether it's position can rely on other * points (if so, we don't have to instruct it explicitly). * This optimization is two-pass. 'Obvious' Off-curve points are sweeped * first. Some remaining unneeded points (off- and on-curve) may then be * optimized out in second pass. * * TODO! This optimizer could be even more aggressive - it currently * skips some features too small or unexposed to benefit from hinting. */ static void optimize_strongpts_step1(InstrCt *ct); static void optimize_strongpts_step2(InstrCt *ct); static void optimize_strongpts(InstrCt *ct) { optimize_strongpts_step1(ct); optimize_strongpts_step2(ct); } static void optimize_strongpts_step1(InstrCt *ct) { int i, j; int *others = ct->edge.others; int othercnt = ct->edge.othercnt; int *contourends = ct->contourends; uint8 *tocull, *tocheck; if (othercnt == 0) return; tocull = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); tocheck = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); for(i=0; iedge.othercnt; i++) tocheck[ct->edge.others[i]] = 1; /* for each point of "edge" (would be better called "zone") */ for(i=0; iedge.othercnt; i++) { int pt = others[i]; double pt_x = ct->bp[pt].x; double pt_y = ct->bp[pt].y; int pt_next = NextOnContour(contourends, pt); double pt_next_x = ct->bp[pt_next].x; double pt_next_y = ct->bp[pt_next].y; int pt_prev = PrevOnContour(contourends, pt); double pt_prev_x = ct->bp[pt_prev].x; double pt_prev_y = ct->bp[pt_prev].y; /* We sweep only off-curve points here */ if (ct->gd->points[pt].sp != NULL) continue; if (IsCornerExtremum(ct->xdir, ct->contourends, ct->bp, pt)) continue; /* Some off-curve points may 'belong' to extrema from other zone. */ if (/*tocheck[pt_next] &&*/ (ct->gd->points[pt_next].sp != NULL) && (pt_x == pt_next_x || pt_y == pt_next_y)) tocull[pt] = 1; if (/*tocheck[pt_prev] &&*/ (ct->gd->points[pt_prev].sp != NULL) && (pt_x == pt_prev_x || pt_y == pt_prev_y)) tocull[pt] = 1; } /* remove optimized-out points from list to be instructed. */ for(i=0; iedge.othercnt; i++) if (tocull[others[i]]) { ct->edge.othercnt--; for(j=i; jedge.othercnt; j++) others[j] = others[j+1]; i--; } free(tocull); free(tocheck); } static void optimize_strongpts_step2(InstrCt *ct) { int pass, i, j, forward; int next_closed, prev_closed; int next_pt_max, next_pt_min, prev_pt_max, prev_pt_min; int next_coord_max, next_coord_min, prev_coord_max, prev_coord_min; int *others = ct->edge.others; int othercnt = ct->edge.othercnt; int touchflag = (ct->xdir)?tf_x:tf_y; int *contourends = ct->contourends; uint8 *touched = ct->touched; uint8 *affected = ct->affected; uint8 *toinstr, *tocull, *tocheck; if (othercnt == 0) return; toinstr = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); tocull = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); tocheck = (uint8 *)calloc(ct->ptcnt, sizeof(uint8)); for(i=0; iedge.othercnt; i++) tocheck[ct->edge.others[i]] = 1; /* two passes... */ for(pass=0; pass<2; pass++) { /* ...for each point of "edge" (would be better called "zone" here) */ for(i=0; iedge.othercnt; i++) { int pt = others[i]; double pt_coord = (ct->xdir) ? ct->bp[pt].x : ct->bp[pt].y; /* In first pass, we sweep only off-curve points */ if ((pass==0) && (ct->gd->points[pt].sp != NULL)) continue; if (tocull[pt] || toinstr[pt]) continue; /* check path backward and forward */ for (forward=0; forward<2; forward++) { int closed = 0; int pt_max = pt, pt_min = pt; double coord_max = pt_coord, coord_min = pt_coord; int curr = forward ? NextOnContour(contourends, pt): PrevOnContour(contourends, pt); while(curr!=pt) { double coord = (ct->xdir) ? ct->bp[curr].x : ct->bp[curr].y; if (fabs(ct->edge.base - coord) > ct->gic->fudge) break; if ((touched[curr] | affected[curr]) & touchflag || tocheck[curr]) { if (coord > coord_max) { coord_max = coord; pt_max = curr; } else if ((coord == coord_max) && (curr < pt_max)) pt_max = curr; if (coord < coord_min) { coord_min = coord; pt_min = curr; } else if ((coord == coord_min) && (curr < pt_min)) pt_min = curr; closed = 1; } if ((touched[curr] | affected[curr]) & touchflag || toinstr[curr]) break; curr = forward ? NextOnContour(contourends, curr): PrevOnContour(contourends, curr); } if (forward) { next_closed = closed; next_pt_max = pt_max; next_pt_min = pt_min; next_coord_max = coord_max; next_coord_min = coord_min; } else { prev_closed = closed; prev_pt_max = pt_max; prev_pt_min = pt_min; prev_coord_max = coord_max; prev_coord_min = coord_min; } } if (prev_closed && next_closed && ( (prev_coord_max >= pt_coord && pt != prev_pt_max && next_coord_min <= pt_coord && pt != next_pt_min) || (prev_coord_min <= pt_coord && pt != prev_pt_min && next_coord_max >= pt_coord && pt != next_pt_max))) tocull[pt] = 1; else toinstr[pt] = 1; } } /* remove optimized-out points from list to be instructed. */ for(i=0; iedge.othercnt; i++) if (tocull[others[i]]) { ct->edge.othercnt--; for(j=i; jedge.othercnt; j++) others[j] = others[j+1]; i--; } free(tocheck); free(toinstr); free(tocull); } /* Finish instructing the edge. Try to hint only those points on edge that are * necessary - IUP should do the rest. */ static void finish_edge(InstrCt *ct, uint8 command) { int i; optimize_edge(ct); if (ct->edge.othercnt==0) return; ct->pt=instructpoints(ct->pt, ct->edge.othercnt, ct->edge.others, command); for(i=0; iedge.othercnt; i++) ct->touched[ct->edge.others[i]] |= (ct->xdir?tf_x:tf_y); free(ct->edge.others); ct->edge.others=NULL; ct->edge.othercnt = 0; } /****************************************************************************** * * Routines for hinting single stems. * ******************************************************************************/ /* Each stem hint has 'ldone' and 'rdone' flag, indicating whether 'left' * or 'right' edge is hinted or not. This functions marks as done all edges at * specified coordinate, starting from given hint (hints sometimes share edges). */ static void mark_startenddones(StemData *stem, int is_l ) { struct dependent_stem *slave; int i; uint8 *done; done = is_l ? &stem->ldone : &stem->rdone; *done = true; for (i=0; idep_cnt; i++) { slave = &stem->dependent[i]; if ( slave->dep_type == 'a' && slave->lbase == is_l ) { done = is_l ? &slave->stem->ldone : &slave->stem->rdone; *done = true; } } } static void build_cvt_stem(InstrCt *ct, real width, StdStem *cvt_stem) { int i, width_parent, width_me; int EM = ct->gic->sf->ascent + ct->gic->sf->descent; cvt_stem->width = (int)rint(fabs(width)); cvt_stem->stopat = 32767; cvt_stem->snapto = CVTSeekStem(ct->xdir, ct->gic, width, false); for (i=7; i<32768; i++) { width_parent = compute_stem_width(ct->xdir, cvt_stem->snapto, EM, i); width_me = compute_stem_width(ct->xdir, cvt_stem, EM, i); if (width_parent != width_me) { cvt_stem->stopat = i; break; } } } /* This function has been separated from finish_stem(), because sometimes * it is necessary to maintain the distance between two points (usually on * opposite stem edges) without instructing the whole stem. Currently we use this * to achieve proper positioning of the left edge of a vertical stem in antialiased * mode, if instructing this stem has to be started from the right edge */ static void maintain_black_dist(InstrCt *ct, real width, int refpt, int chg_rp0) { int callargs[5]; StdStem *StdW = ct->xdir?&(ct->gic->stdvw):&(ct->gic->stdhw); StdStem *ClosestStem; StdStem cvt_stem; ClosestStem = CVTSeekStem(ct->xdir, ct->gic, width, true); if (ClosestStem != NULL) { ct->pt = push2nums(ct->pt, refpt, ClosestStem->cvtindex); if (ct->gic->cvt_done && ct->gic->fpgm_done && ct->gic->prep_done) *(ct->pt)++ = chg_rp0?MIRP_rp0_min_black:MIRP_min_black; else *(ct->pt)++ = chg_rp0?MIRP_min_rnd_black:MIRP_rp0_min_rnd_black; } else { if (ct->gic->cvt_done && ct->gic->fpgm_done && ct->gic->prep_done && StdW->width!=-1) { build_cvt_stem(ct, width, &cvt_stem); callargs[0] = ct->edge.refpt; callargs[1] = cvt_stem.snapto->cvtindex; callargs[2] = chg_rp0?1:0; callargs[3] = cvt_stem.stopat; callargs[4] = 4; ct->pt = pushnums(ct->pt, 5, callargs); *(ct->pt)++ = CALL; } else { ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = chg_rp0?MDRP_rp0_min_rnd_black:MDRP_min_rnd_black; } } } /* Given the refpt for one of this hint's edges is already positioned, this * function aligns 'others' (SHP with given shp_rp) for this edge and positions * the second edge, optionally setting its refpt as rp0. It frees edge.others * and sets edge.othercnt to zero, but it leaves edge.refpt set to last * instructed edge. */ #define use_rp1 (true) #define use_rp2 (false) #define set_new_rp0 (true) #define keep_old_rp0 (false) static void finish_stem(StemData *stem, int shp_rp1, int chg_rp0, InstrCt *ct) { int is_l, basedone, oppdone, reverse; real hleft, hright, width; if (stem == NULL) return; hleft = ((real *) &stem->left.x)[!ct->xdir]; hright= ((real *) &stem->right.x)[!ct->xdir]; is_l = (fabs(hleft - ct->edge.base) < fabs(hright - ct->edge.base)); basedone = ( is_l && stem->ldone ) || ( !is_l && stem->rdone ); oppdone = ( is_l && stem->rdone ) || ( !is_l && stem->ldone ); reverse = ( ct->xdir && !is_l && !stem->ldone && !stem->ghost ); width = stem->width; if ( !reverse && !basedone ) { ct->touched[ct->edge.refpt] |= ct->xdir?tf_x:tf_y; finish_edge(ct, shp_rp1?SHP_rp1:SHP_rp2); mark_startenddones(stem, is_l ); } if (oppdone || (stem->ghost && ((stem->width==20) || (stem->width==21)))) { stem->ldone = stem->rdone = 1; return; } init_stem_edge(ct, stem, !is_l); if (ct->edge.refpt == -1) { /* We have skipped the right edge to start instructing this stem from * left. But its left edge appears to have no points to be instructed. * So return to the right edge and instruct it before exiting */ if ( reverse && !basedone ) { init_stem_edge(ct, stem, is_l); ct->touched[ct->edge.refpt] |= ct->xdir?tf_x:tf_y; finish_edge(ct, shp_rp1?SHP_rp1:SHP_rp2); mark_startenddones(stem, is_l ); } return; } maintain_black_dist(ct, width, ct->edge.refpt, chg_rp0); if ( reverse ) { is_l = !is_l; ct->rp0 = ct->edge.refpt; ct->pt = pushpoint(ct->pt, ct->rp0); *(ct->pt)++ = MDAP_rnd; ct->touched[ct->edge.refpt] |= ct->xdir?tf_x:tf_y; finish_edge(ct, SHP_rp1); mark_startenddones( stem, is_l ); if ( !stem->rdone ) { init_stem_edge(ct, stem, false); if (ct->edge.refpt == -1) return; maintain_black_dist(ct, width, ct->edge.refpt, chg_rp0); } } if (chg_rp0) ct->rp0 = ct->edge.refpt; ct->touched[ct->edge.refpt] |= ct->xdir?tf_x:tf_y; finish_edge(ct, SHP_rp2); mark_startenddones( stem, !is_l ); } static void mark_points_affected(InstrCt *ct,StemData *target,PointData *opd,int next) { Spline *s; PointData *pd, *cpd; int cpidx; s = next ? opd->sp->next : opd->sp->prev; pd = next ? &ct->gd->points[s->to->ptindex] : &ct->gd->points[s->from->ptindex]; while (IsStemAssignedToPoint(pd, target, !next) == -1) { if (pd->ttfindex < ct->gd->realcnt && value_point(ct, pd->ttfindex, pd->sp, ct->gd->emsize)) ct->affected[pd->ttfindex] |= ct->xdir?tf_x:tf_y; if (!pd->sp->noprevcp) { cpidx = pd->sp->prev->from->nextcpindex; cpd = &ct->gd->points[cpidx]; if (value_point(ct, cpd->ttfindex, pd->sp, ct->gd->emsize)) ct->affected[cpd->ttfindex] |= ct->xdir?tf_x:tf_y; } if (!pd->sp->nonextcp) { cpidx = pd->sp->nextcpindex; cpd = &ct->gd->points[cpidx]; if (value_point(ct, cpd->ttfindex, pd->sp, ct->gd->emsize)) ct->affected[cpd->ttfindex] |= ct->xdir?tf_x:tf_y; } s = next ? pd->sp->next : pd->sp->prev; pd = next ? &ct->gd->points[s->to->ptindex] : &ct->gd->points[s->from->ptindex]; if ( pd == opd ) { IError( "The ball terminal with a key point at %.3f,%.3f\n" "appears to be incorrectly linked to the %s stem\n" "<%.3f, %.3f>", pd->base.x,pd->base.y, ct->xdir?"vertical":"horizontal", ct->xdir?target->left.x:target->right.y,target->width ); break; } } } static void finish_serif(StemData *slave, StemData *master, int lbase, int is_ball, InstrCt *ct) { int inner_pt, callargs[4]; struct stem_chunk *chunk; PointData *opd; int i; if (slave == NULL || master == NULL) return; inner_pt = ( lbase ) ? master->rightidx : master->leftidx; init_stem_edge(ct, slave, !lbase); if (ct->edge.refpt == -1) return; if (ct->gic->fpgm_done) { callargs[0] = is_ball ? 0 : 64; callargs[1] = inner_pt; callargs[2] = ct->edge.refpt; callargs[3] = 9; ct->pt = pushnums(ct->pt, 4, callargs); *(ct->pt)++ = CALL; } else { *(ct->pt)++ = 0x7D; /* RDTG */ ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = MDRP_min_rnd_black; *(ct->pt)++ = 0x18; /* RTG */ } ct->touched[ct->edge.refpt] |= ct->xdir?tf_x:tf_y; finish_edge(ct, SHP_rp2); mark_startenddones( slave, !lbase ); if ( !interpolate_strong || !instruct_ball_terminals ) return; /* Preserve points on ball terminals from being interpolated * between edges by marking them as affected */ for ( i=0; ichunk_cnt; i++ ) { chunk = &slave->chunks[i]; opd = lbase ? chunk->r : chunk->l; if (chunk->is_ball && opd != NULL) { mark_points_affected(ct, chunk->ball_m, opd, true); mark_points_affected(ct, chunk->ball_m, opd, false); } } } static void link_serifs_to_edge(InstrCt *ct, StemData *stem, int is_l) { int i, callargs[3]; struct dependent_serif *serif; /* We use an FPGM function to set rp0, and thus the exact value * is not known at the compilation time. So it is safer to reset * ct->rp0 to -1 */ if ( ct->gic->fpgm_done ) { ct->rp0 = -1; callargs[0] = is_l ? stem->rightidx : stem->leftidx; callargs[1] = is_l ? stem->leftidx : stem->rightidx; callargs[2] = 10; ct->pt = pushnums(ct->pt, 3, callargs); *(ct->pt)++ = CALL; } else { init_stem_edge(ct, stem, !is_l); if ( ct->rp0 != ct->edge.refpt ) { ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = SRP0; ct->rp0 = ct->edge.refpt; } } for (i=0; iserif_cnt; i++) { serif = &stem->serifs[i]; if (serif->lbase == is_l && ((serif->is_ball && instruct_ball_terminals) || (!serif->is_ball && instruct_serif_stems))) finish_serif( serif->stem,stem,is_l,serif->is_ball,ct ); } } static void instruct_serifs(InstrCt *ct, StemData *stem) { int i, lcnt, rcnt; struct dependent_serif *serif; if ( stem->leftidx == -1 || stem->rightidx == -1 ) return; lcnt = rcnt = 0; for (i=0; iserif_cnt; i++) { serif = &stem->serifs[i]; if ((serif->is_ball && !instruct_ball_terminals) || (!serif->is_ball && !instruct_serif_stems)) continue; if ( serif->lbase ) lcnt++; else if ( !serif->lbase ) rcnt++; } if (stem->ldone && lcnt > 0) link_serifs_to_edge(ct, stem, true); if (stem->rdone && rcnt > 0) link_serifs_to_edge(ct, stem, false); } static void instruct_dependent(InstrCt *ct, StemData *stem) { int i, j, rp, rp1, rp2, stopat, callargs[4]; struct dependent_stem *slave; int w_master, w_slave; StdStem *std_master, *std_slave, norm_master, norm_slave; StdStem *StdW = ct->xdir?&(ct->gic->stdvw):&(ct->gic->stdhw); for (i=0; idep_cnt; i++) { slave = &stem->dependent[i]; if (slave->stem->master == NULL) continue; init_stem_edge(ct, slave->stem, slave->lbase); if (ct->edge.refpt == -1) continue; if (slave->dep_type == 'i' && stem->ldone && stem->rdone) { rp1 = ct->xdir ? stem->leftidx : stem->rightidx; rp2 = ct->xdir ? stem->rightidx : stem->leftidx; callargs[0] = ct->edge.refpt; callargs[1] = rp2; callargs[2] = rp1; if (ct->gic->fpgm_done) { callargs[3] = 8; ct->pt = pushpoints(ct->pt, 4, callargs); *(ct->pt)++ = CALL; } else { ct->pt = pushpoints(ct->pt, 3, callargs); *(ct->pt)++ = SRP1; *(ct->pt)++ = SRP2; *(ct->pt)++ = DUP; *(ct->pt)++ = IP; *(ct->pt)++ = MDAP_rnd; } } else if (slave->dep_type == 'm' && ((slave->lbase && stem->ldone) || (!slave->lbase && stem->rdone))) { rp = slave->lbase ? stem->leftidx : stem->rightidx; if ( rp != ct->rp0 ) { ct->pt = pushpoint(ct->pt, rp); *(ct->pt)++ = SRP0; ct->rp0 = rp; } /* It is possible that at certain PPEMs both the master and slave stems are * regularized, say, to 1 pixel, but the difference between their positions * is rounded to 1 pixel too. Thus one stem is shifted relatively to another, * so that the overlap disappears. This looks especially odd for nesting/nested * stems. We use a special FPGM function to prevent this. */ if ( ct->gic->cvt_done && ct->gic->fpgm_done && ct->gic->prep_done && StdW->width!=-1 && ( ((&stem->left.x)[!ct->xdir] <= (&slave->stem->left.x)[!ct->xdir] && ( &stem->right.x)[!ct->xdir] >= (&slave->stem->right.x)[!ct->xdir] ) || ((&stem->left.x)[!ct->xdir] >= (&slave->stem->left.x)[!ct->xdir] && ( &stem->right.x)[!ct->xdir] <= (&slave->stem->right.x)[!ct->xdir] ))) { std_master = CVTSeekStem(ct->xdir, ct->gic, stem->width, true); std_slave = CVTSeekStem(ct->xdir, ct->gic, slave->stem->width, true); if ( std_master == NULL ) { build_cvt_stem(ct, stem->width, &norm_master); std_master = &norm_master; } if ( std_slave == NULL ) { build_cvt_stem(ct, slave->stem->width, &norm_slave); std_slave = &norm_slave; } stopat = 32768; for (j=7; j<=stopat; j++) { w_master = compute_stem_width(ct->xdir, std_master, ct->gd->emsize, j); w_slave = compute_stem_width(ct->xdir, std_slave , ct->gd->emsize, j); if (w_master != w_slave) stopat = j; } callargs[0] = stopat; callargs[1] = ct->rp0; callargs[2] = ct->edge.refpt; callargs[3] = 14; ct->pt = pushpoints(ct->pt, 4, callargs); *(ct->pt)++ = CALL; } else { ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = DUP; *(ct->pt)++ = MDRP_rp0_rnd_white; *(ct->pt)++ = SRP1; } } else if (slave->dep_type == 'a' && ((slave->lbase && stem->ldone) || (!slave->lbase && stem->rdone))) { if ( ct->edge.refpt != ct->rp0 ) { ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = SRP0; } } else continue; ct->rp0 = ct->edge.refpt; finish_stem(slave->stem, use_rp1, keep_old_rp0, ct); if ( instruct_serif_stems || instruct_ball_terminals ) instruct_serifs(ct, slave->stem); instruct_dependent(ct, slave->stem); } } /****************************************************************************** * * I decided to do snapping to blues at the very beginning of the instructing. * * Blues are processed in certain (important) order: baseline, descenders * (from deeper to shorter), ascenders (from taller to shorter). * * For each blue, one of the edges is put into CVT: lower if is't > zero, * the upper otherwise. A twilight point 0 is established at this height. All * the glyph's points decided to be worth snapping are then moved relative to * this twilight point, being subject to rounding 'down-to-int'. Space taken * is at most 8*ptcnt. * * For each blue, all yet unprocessed HStems affected are instructed. Ghost * hints are reckognised. If there is at least one stem hint in given blue zone, * autoinstructor will seek for other interesting features, so there is no need * to hint them explicitly. * * TODO! We currently instruct hints dependent on those controlled by blues. * This may be not always corrrect (e.g. if a dependent hint is itself * controlled by blue zone - possibly even different). Research needed. * * Important notes: * * The zone count must be set to 2, the twilight point count must be nonzero. * This is done automagically in init_maxp(), otherwise this method wouldn't * work at all. Currently there is only one twilight point used, but there * may be needed one or even two points per blue zone if some advanced snapping * and counter managing is to be done. * * Snapping relies on function 0 in FPGM, see init_fpgm(). * * Using MIAP (single cvt, relying on cut-in) instead of twilight points * causes overshoots to appear/disappear inconsistently at small pixel sizes. * This flickering is disastrous to soft, wavy horizontal lines. We could use * any glyph's point at needed height, but we're not certain we'll find any. * * The inner (leftwards) contours aren't snapped to the blue zone. * This could have created weird artifacts. Of course this will fail for * glyphs with wrong direction, but I won't handle it for now. * * TODO! Remind the user to correct direction or do it for him. * TODO! Try to instruct 'free points' with single push and LOOPCALL. * * If we didn't snapped any point to a blue zone, we shouldn't mark any HStem * edges done. This could made some important points on inner contours missed. * ******************************************************************************/ /* Each blue zone has two TTF point indices associated with it: 'highest' and * 'lowest'. These have to be points with highest and lowest Y coordinates that * are snapped to that blue zone (directly or by horizontal stem). Currently * we register only edge.refpt. These points are later to be used for horizontal * stems' positioning. */ static void update_blue_pts(int blueindex, InstrCt *ct) { BasePoint *bp = ct->bp; BlueZone *blues = ct->gic->blues; if (ct->edge.refpt == -1) return; if (blues[blueindex].highest == -1 || bp[ct->edge.refpt].y > bp[blues[blueindex].highest].y) blues[blueindex].highest = ct->edge.refpt; if (blues[blueindex].lowest == -1 || bp[ct->edge.refpt].y < bp[blues[blueindex].lowest].y) blues[blueindex].lowest = ct->edge.refpt; } /* It is theoretically possible that 'highest' and 'lowest' points of neighbour * blue zones overlap, and thus may spoil horizontal stems' positioning. * Here we fix this up. */ static void fixup_blue_pts(BlueZone *b1, BlueZone *b2) { if (b1->lowest > b2->lowest) b1->lowest = b2->lowest; if (b1->highest < b2->highest) b1->highest = b2->highest; } static void check_blue_pts(InstrCt *ct) { BasePoint *bp = ct->bp; BlueZone *blues = ct->gic->blues; int i, j, bluecnt = ct->gic->bluecnt; for (i=0; igic->sf); StemData *slave; /* Which edge to start at? */ /* Starting at the other would usually be wrong. */ if (blue->overshoot < blue->base && ( !stem->ghost || stem->width == 21 )) { is_l = false; base = stem->right.y; advance = stem->left.y; } else { is_l = true; base = stem->left.y; advance = stem->right.y; } /* This is intended as a fallback if the base edge wasn't within * this bluezone, and advance edge was. */ if (!stem->ghost && !SegmentsOverlap(base+fuzz, base-fuzz, blue->base, blue->overshoot) && SegmentsOverlap(advance+fuzz, advance-fuzz, blue->base, blue->overshoot)) { tmp = base; base = advance; advance = tmp; is_l = !is_l; } /* instruct the stem */ init_stem_edge(ct, stem, is_l); if (ct->edge.refpt == -1) { for ( i=0; idep_cnt; i++ ) { slave = stem->dependent[i].stem; /* A hack which allows single-edge hints to tie features * to remote blue zones. */ if ( stem->ghost ) slave->blue = idx; if ( slave->blue == idx ) ret += snap_stem_to_blue(ct, slave, blue, idx); } return( ret ); } update_blue_pts(idx, ct); callargs[0] = ct->rp0 = ct->edge.refpt; callargs[1] = blue->cvtindex; if (ct->gic->fpgm_done) { ct->pt = pushpoints(ct->pt, 3, callargs); *(ct->pt)++ = CALL; } else { ct->pt = pushpoints(ct->pt, 2, callargs); *(ct->pt)++ = MIAP_rnd; } finish_stem(stem, use_rp1, keep_old_rp0, ct); for ( i=0; idep_cnt; i++ ) { slave = stem->dependent[i].stem; if ( slave->blue == idx ) { ret += snap_stem_to_blue(ct, slave, blue, idx); slave->master = NULL; } } if( instruct_serif_stems || instruct_ball_terminals ) instruct_serifs(ct, stem); instruct_dependent(ct, stem); update_blue_pts(idx, ct); /* this uses only refpt: who cares? */ return( ret + 1 ); } /* Snap stems and perhaps also some other points to given bluezone and set up * its 'highest' and 'lowest' point indices. */ static void snap_to_blues(InstrCt *ct) { int i, j; int therewerestems; /* were there any HStems snapped to this blue? */ StemData *stem; /* for HStems affected by blues */ real base; /* for the hint */ int callargs[3] = { 0/*pt*/, 0/*cvt*/, 0 }; real fudge; int bluecnt=ct->gic->bluecnt; int queue[12]; /* Blue zones' indices in processing order */ BlueZone *blues = ct->gic->blues; real fuzz = GetBlueFuzz(ct->gic->sf); if (bluecnt == 0) return; /* Fill the processing queue - baseline goes first, then bottom zones */ /* sorted by base in ascending order, then top zones sorted in descending */ /* order. I assume the blues are sorted in ascending order first. */ for (i=0; (i < bluecnt) && (blues[i].base < 0); i++); queue[0] = i; for (i=0; igd->hbundle->cnt; j++ ) { stem = ct->gd->hbundle->stemlist[j]; if (stem->master != NULL || stem->blue != queue[i] || stem->ldone || stem->rdone) continue; therewerestems += snap_stem_to_blue(ct, stem, &blues[queue[i]], queue[i]); } /* Now I'll try to find points not snapped by any previous stem hint. */ if (therewerestems) { base = (blues[queue[i]].base + blues[queue[i]].overshoot) / 2.0; fudge = ct->gic->fudge; ct->gic->fudge = fabs(base - blues[queue[i]].base) + fuzz; init_edge(ct, base, EXTERNAL_CONTOURS); optimize_blue(ct); optimize_edge(ct); if (ct->edge.refpt == -1) { ct->gic->fudge = fudge; continue; } if (!(ct->touched[ct->edge.refpt]&tf_y || ct->affected[ct->edge.refpt]&tf_y)) { callargs[0] = ct->rp0 = ct->edge.refpt; if (ct->gic->fpgm_done) { ct->pt = pushpoints(ct->pt, 3, callargs); *(ct->pt)++ = CALL; } else { ct->pt = pushpoints(ct->pt, 2, callargs); *(ct->pt)++ = MIAP_rnd; } ct->touched[ct->edge.refpt] |= tf_y; } for (j=0; jedge.othercnt; j++) { callargs[0] = ct->rp0 = ct->edge.others[j]; if (ct->gic->fpgm_done) { ct->pt = pushpoints(ct->pt, 3, callargs); *(ct->pt)++ = CALL; } else { ct->pt = pushpoints(ct->pt, 2, callargs); *(ct->pt)++ = MIAP_rnd; } ct->touched[ct->edge.others[j]] |= tf_y; } update_blue_pts(queue[i], ct); if (ct->edge.others != NULL) { free(ct->edge.others); ct->edge.others = NULL; ct->edge.othercnt = 0; } ct->gic->fudge = fudge; } } check_blue_pts(ct); } static int get_counters_cut_in(InstrCt *ct, int m1, int m2, int c1, int c2) { real s1, e1, s2, e2, width1, width2; int i, swidth1, swidth2; int EM = ct->gic->sf->ascent + ct->gic->sf->descent; s1 = (&ct->gd->points[m1].base.x)[!ct->xdir]; e1 = (&ct->gd->points[m2].base.x)[!ct->xdir]; s2 = (&ct->gd->points[c1].base.x)[!ct->xdir]; e2 = (&ct->gd->points[c2].base.x)[!ct->xdir]; width1 = e1 - s1; width2 = e2 - s2; if ( RealNear( width1, width2 )) return( 0 ); for (i=7; i<32768; i++) { swidth1 = (int)rint((rint(fabs(width1)) * i * 64.0)/EM); swidth2 = (int)rint((rint(fabs(width2)) * i * 64.0)/EM); if ( fabs(swidth1 - swidth2) >= SNAP_THRESHOLD ) break; } return( i ); } /****************************************************************************** * * High-level functions for instructing horizontal and vertical stems. * Both use 'geninstrs' for positioning single, elementary stems. * ******************************************************************************/ /* geninstrs's main burden is to choose the better of two reference points * found by init_stem_edge() - one for each edge - and position it relatively * to other stems (if not already done). * * If none of the edges is positioned: * If this hint is the first, previously overlapped, or simply horizontal, * position the reference point at the base where it is using MDAP; otherwise * position the hint's base rp0 relatively to the previous hint's end using * MDRP with white minimum distance (fpgm function 1). * * Calling finish_stem() will deal with the rest of points needing explicit * positioning. Then we instruct serifs and dependent stems, if wanted. */ static void geninstrs(InstrCt *ct, StemData *stem, StemData *prev, int lbase) { int shp_rp1, chg_rp0, c_m_pt1 = -1, c_m_pt2 = -1; int callargs[6]; real prev_pos = 0, cur_pos; if (stem->ldone && stem->rdone) return; if ((lbase && stem->rdone) || (!lbase && stem->ldone)) lbase = !lbase; init_stem_edge(ct, stem, lbase); if (ct->edge.refpt == -1) { lbase = !lbase; init_stem_edge(ct, stem, lbase); } if (ct->edge.refpt == -1) return; if (ct->rp0 < ct->gd->realcnt && ct->rp0 >= 0) prev_pos = (&ct->gd->points[ct->rp0].base.x)[!ct->xdir]; cur_pos = (&ct->gd->points[ct->edge.refpt].base.x)[!ct->xdir]; if (prev != NULL && stem->prev_c_m != NULL && prev->next_c_m != NULL ) { c_m_pt1 = ct->xdir ? prev->next_c_m->rightidx : prev->next_c_m->leftidx; c_m_pt2 = ct->xdir ? stem->prev_c_m->leftidx : stem->prev_c_m->rightidx; } /* Now the stem's origin must be placed in respect to others... */ /* TODO! What's really needed here is an iterative procedure that */ /* would preserve counters and widths, like in freetype2. */ /* For horizontal stems, interpolating between blues is being be done. */ if (stem->ldone || stem->rdone ) { ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = MDAP; /* sets rp0 and rp1 */ shp_rp1 = use_rp1; chg_rp0 = (ct->xdir && !lbase) || (!ct->xdir && lbase); } else if (!ct->xdir) { /* horizontal stem */ ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = MDAP_rnd; shp_rp1 = use_rp1; chg_rp0 = keep_old_rp0; } else if (prev == NULL) { /* first vertical stem */ ct->pt = pushpoint(ct->pt, ct->edge.refpt); *(ct->pt)++ = MDRP_rp0_rnd_white; shp_rp1 = use_rp2; chg_rp0 = keep_old_rp0; } else { if (ct->gic->fpgm_done) { if ( control_counters && c_m_pt1 != -1 && c_m_pt2 != -1 ) { callargs[0] = c_m_pt1; callargs[1] = c_m_pt2; callargs[2] = ct->rp0; callargs[3] = ct->edge.refpt; callargs[4] = get_counters_cut_in(ct, c_m_pt1, c_m_pt2, ct->rp0, ct->edge.refpt); callargs[5] = 15; ct->pt = pushpoints(ct->pt, 6, callargs); } else if ( control_counters && prev != NULL && prev->leftidx != -1 && prev->rightidx != -1 ) { callargs[0] = ct->xdir ? prev->leftidx : prev->rightidx; callargs[1] = ct->edge.refpt; callargs[2] = ( cur_pos - prev_pos ) > ct->gic->fudge ? 16 : 17; ct->pt = pushpoints(ct->pt, 3, callargs); } else if ( fabs( cur_pos - prev_pos ) > ct->gic->fudge ) { ct->pt = push2nums(ct->pt, ct->edge.refpt, 1); } else { ct->pt = push2nums(ct->pt, ct->edge.refpt, 11); } *(ct->pt)++ = CALL; } else { ct->pt = pushpoint(ct->pt, ct->edge.refpt); if ( fabs( cur_pos - prev_pos ) > ct->gic->fudge ) *(ct->pt)++ = MDRP_rp0_min_rnd_grey; else *(ct->pt)++ = MDRP_rp0_rnd_white; } shp_rp1 = use_rp2; /* Don't switch rp0 to the second edge. Thus, relative distance * to the next stem is be larger, and errors are hopefully lesser. * TODO! This is disputable. * TODO! For the last vstem, we probably want to switch rp0 anyway. */ chg_rp0 = keep_old_rp0; } ct->rp0 = ct->edge.refpt; finish_stem(stem, shp_rp1, chg_rp0, ct); if ( instruct_serif_stems || instruct_ball_terminals ) instruct_serifs(ct, stem); instruct_dependent(ct, stem); } /* High-level function for instructing horizontal stems. * * It is assumed that blues (and hstems associated with them) are already * done so that remaining stems can be interpolated between them. * * TODO! CJK hinting will probably need different function (HStemGeninstCJK?) * TODO! Instruct top and bottom bearings for fonts which have them. */ static void HStemGeninst(InstrCt *ct) { BlueZone *blues = ct->gic->blues; int bluecnt = ct->gic->bluecnt; BasePoint *bp = ct->bp; StemData *stem; int i, j, rp1, rp2, opp, bpt, ept; double hbase, hend; int mdrp_end, mdrp_base, ip_base, *rpts1, *rpts2; int callargs[5]; if ( ct->gd->hbundle == NULL ) return; rpts1 = calloc(ct->gd->hbundle->cnt, sizeof(int)); rpts2 = calloc(ct->gd->hbundle->cnt, sizeof(int)); /* Interpolating between blues is splitted to two stages: first * we determine which stems can be interpolated and which cannot * and store the numbers of reference points, and then (in the * second cycle) proceed to generating actual instructions. The reason is * that we need a special handling for dependent stems: if they * can be interpolated, we process them separately, but otherwise * the normal algorithm for positioning dependent stems relatively * to their "masters" is used. It is necessary to know which method * to prefer for each stem at the time instructions are generated. */ for ( i=0; igd->hbundle->cnt; i++ ) { stem = ct->gd->hbundle->stemlist[i]; if (!stem->ldone && !stem->rdone) { /* Set up upper edge (hend) and lower edge (hbase). */ hbase = stem->right.y; hend = stem->left.y; /* Find two points to interpolate the HStem between. rp1 = lower, rp2 = upper. */ rp1 = -1; rp2 = -1; for (j=0; j hend) if (rp2==-1 || bp[rp2].y > bp[blues[j].highest].y) rp2=blues[j].highest; } rpts1[i] = rp1; rpts2[i] = rp2; /* If a dependent stem has to be positioned by interpolating * one of its edges between the edges of the master stem and * we have found reference points to interpolate it between * blues, then we prefer to interpolate it between blues. However * we keep the standard handling for other types of dependent * stems, since usually positioning relatively to the "master" * stem is more important than positioning relatively to blues * in such cases. * Exception: nested stems marked for interpolation should be * positioned by interpolating between the edges of the nesting * stem. */ if (rp1!=-1 && rp2!=-1 && stem->master != NULL) for (j=0; jmaster->dep_cnt; j++) { if (stem->master->dependent[j].stem == stem && stem->master->dependent[j].dep_type == 'i' && (stem->master->left.y <= stem->left.y || stem->master->right.y >= stem->right.y)) { stem->master = NULL; break; } } } } for ( i=0; igd->hbundle->cnt; i++ ) { stem = ct->gd->hbundle->stemlist[i]; if ( stem->master != NULL ) continue; if (!stem->ldone && !stem->rdone) { hbase = stem->right.y; hend = stem->left.y; rp1 = rpts1[i]; rp2 = rpts2[i]; /* Reference points not found? Fall back to old method. */ if (rp1==-1 || rp2==-1) { geninstrs(ct, stem, NULL, false); continue; } bpt = ept = -1; if ( !stem->ghost || stem->width == 21 ) { init_stem_edge(ct, stem, false); bpt = ct->edge.refpt; } if ( !stem->ghost || stem->width == 20 ) { init_stem_edge(ct, stem, true); ept = ct->edge.refpt; } if ( bpt == -1 && ept == -1 ) continue; /* Align the stem relatively to rp0 and rp1. */ mdrp_end = ept != -1 && fabs(bp[rp2].y - hbase) < 0.2*fabs(bp[rp2].y - bp[rp1].y); mdrp_base = bpt != -1 && fabs(bp[rp1].y - hend) < 0.2*fabs(bp[rp2].y - bp[rp1].y); if (mdrp_end || mdrp_base) { if (mdrp_end) init_stem_edge(ct, stem, true); else init_stem_edge(ct, stem, false); if (ct->edge.refpt == -1) continue; if (mdrp_end) ct->pt = push2points(ct->pt, ct->edge.refpt, rp2); else ct->pt = push2points(ct->pt, ct->edge.refpt, rp1); *(ct->pt)++ = SRP0; *(ct->pt)++ = DUP; *(ct->pt)++ = MDRP_grey; *(ct->pt)++ = MDAP_rnd; } else if ( bpt == -1 || ept == -1 ) { ip_base = ( ept == -1 ); init_stem_edge(ct, stem, !ip_base); if ( ct->gic->fpgm_done ) { callargs[0] = ct->edge.refpt; callargs[1] = rp1; callargs[2] = rp2; callargs[3] = 8; ct->pt = pushnums(ct->pt, 4, callargs); *(ct->pt)++ = CALL; } else { callargs[0] = ct->edge.refpt; callargs[1] = rp1; callargs[2] = rp2; ct->pt = pushnums(ct->pt, 3, callargs); *(ct->pt)++ = SRP2; *(ct->pt)++ = SRP1; *(ct->pt)++ = DUP; *(ct->pt)++ = IP; *(ct->pt)++ = MDAP_rnd; } } else { ip_base = fabs(bp[rp2].y - hend) < fabs(bp[rp1].y - hbase); opp = ip_base ? ept : bpt; init_stem_edge(ct, stem, !ip_base); if (ct->edge.refpt == -1) continue; if ( ct->gic->fpgm_done ) { callargs[0] = opp; callargs[1] = ct->edge.refpt; callargs[2] = rp1; callargs[3] = rp2; callargs[4] = 13; ct->pt = pushnums(ct->pt, 5, callargs); *(ct->pt)++ = CALL; } else { callargs[0] = ct->edge.refpt; callargs[1] = rp1; callargs[2] = rp2; ct->pt = pushnums(ct->pt, 3, callargs); *(ct->pt)++ = SRP2; *(ct->pt)++ = SRP1; *(ct->pt)++ = DUP; *(ct->pt)++ = IP; *(ct->pt)++ = MDAP_rnd; } } ct->rp0 = ct->edge.refpt; finish_stem(stem, use_rp1, keep_old_rp0, ct); if ( instruct_serif_stems || instruct_ball_terminals ) instruct_serifs(ct, stem); instruct_dependent(ct, stem); } } free(rpts1); free(rpts2); } /* * High-level function for instructing vertical stems. * * TODO! CJK hinting may need different function (VStemGeninstCJK?) */ static void VStemGeninst(InstrCt *ct) { StemData *stem, *prev=NULL; int i; if (ct->rp0 != ct->ptcnt) { ct->pt = pushpoint(ct->pt, ct->ptcnt); *(ct->pt)++ = MDAP_rnd; ct->rp0 = ct->ptcnt; } if ( ct->gd->vbundle != NULL ) { for ( i=0; igd->vbundle->cnt; i++ ) { stem = ct->gd->vbundle->stemlist[i]; if ((!stem->ldone || !stem->rdone) && stem->master == NULL) { if (prev != NULL && prev->rightidx != -1 && ct->rp0 != prev->rightidx) { ct->pt = pushpoint(ct->pt, prev->rightidx); *(ct->pt)++ = SRP0; ct->rp0 = prev->rightidx; } geninstrs(ct, stem, prev, true); prev = stem; } } } /* instruct right sidebearing */ if (ct->sc->width != 0) { if ( ct->gic->fpgm_done && !control_counters ) { ct->pt = push2nums(ct->pt, ct->ptcnt+1, 1); *(ct->pt)++ = CALL; } else { /* select rp0 at the right edge of last stem - geninstrs() didn't. */ /* TODO! after some time, move this to geninstrs(), to save space. */ if (prev != NULL && prev->rightidx != -1 && ct->rp0 != prev->rightidx) { ct->pt = pushpoint(ct->pt, prev->rightidx); *(ct->pt)++ = SRP0; ct->rp0 = prev->rightidx; } ct->pt = pushpoint(ct->pt, ct->ptcnt+1); *(ct->pt)++ = MDRP_rp0_rnd_white; } ct->rp0 = ct->ptcnt+1; } } /****************************************************************************** * * Everything related with diagonal hinting goes here * ******************************************************************************/ #define DIAG_MIN_DISTANCE (0.84375) static int ds_cmp( const void *_s1, const void *_s2 ) { StemData * const *s1 = _s1, * const *s2 = _s2; BasePoint *bp1, *bp2; bp1 = (*s1)->unit.y > 0 ? &(*s1)->keypts[0]->base : &(*s1)->keypts[2]->base; bp2 = (*s2)->unit.y > 0 ? &(*s2)->keypts[0]->base : &(*s2)->keypts[2]->base; if ( bp1->x < bp2->x || ( bp1->x == bp2->x && bp1->y < bp2->y )) return( -1 ); else if ( bp2->x < bp1->x || ( bp2->x == bp1->x && bp2->y < bp1->y )) return( 1 ); return( 0 ); } /* Takes a line defined by two points and returns a vector decribed as a * pair of x and y values, such that the value (x2 + y2) is equal to 1. * Note that the BasePoint structure is used to store the vector, although * it is not a point itself. This is just because that structure has "x" * and "y" fields which can be used for our purpose. */ static BasePoint GetVector ( BasePoint *top,BasePoint *bottom,int orth ) { real catx, caty, hyp, temp; BasePoint ret; catx = top->x - bottom->x; caty = top->y - bottom->y; hyp = sqrt(( catx*catx ) + ( caty*caty )); ret.y = caty/hyp; ret.x = catx/hyp; if( orth ) { temp = ret.x; ret.x = -ret.y; ret.y = temp; } return( ret ); } static int SetDStemKeyPoint( InstrCt *ct,StemData *stem,PointData *pd,int aindex ) { int nextidx, previdx, cpidx, prev_outer, next_outer, is_start; int nsidx, psidx, sidx; uint8 flag; PointData *ncpd, *pcpd, *cpd, *best = NULL; real prevdot, nextdot, cpdist; if ( pd == NULL ) return( false ); flag = fabs( stem->unit.y ) > fabs( stem->unit.x ) ? tf_y : tf_x; is_start = ( aindex == 0 || aindex == 2 ); prevdot = ( pd->prevunit.x * stem->unit.x ) + ( pd->prevunit.y * stem->unit.y ); nextdot = ( pd->nextunit.x * stem->unit.x ) + ( pd->nextunit.y * stem->unit.y ); prev_outer = IsStemAssignedToPoint( pd,stem,false ) != -1 && (( is_start && prevdot < 0 ) || ( !is_start && prevdot > 0 )); next_outer = IsStemAssignedToPoint( pd,stem,true ) != -1 && (( is_start && nextdot < 0 ) || ( !is_start && nextdot > 0 )); if ( pd->ttfindex >= ct->gd->realcnt ) { nextidx = pd->sp->nextcpindex; previdx = pd->sp->prev->from->nextcpindex; ncpd = &ct->gd->points[nextidx]; pcpd = &ct->gd->points[previdx]; psidx = IsStemAssignedToPoint( pcpd,stem,true ); nsidx = IsStemAssignedToPoint( ncpd,stem,false ); if ( psidx == -1 && nsidx == -1 ) return( false ); if ( psidx > -1 && nsidx > -1 ) best = ( prev_outer ) ? pcpd : ncpd; else best = ( psidx > -1 ) ? pcpd : ncpd; } else if (( !pd->sp->nonextcp && next_outer ) || ( !pd->sp->noprevcp && prev_outer )) { cpidx = ( prev_outer ) ? pd->sp->prev->from->nextcpindex : pd->sp->nextcpindex; cpd = &ct->gd->points[cpidx]; sidx = IsStemAssignedToPoint( cpd,stem,prev_outer ); if ( sidx != -1 ) { cpdist = fabs(( pd->base.x - cpd->base.x ) * stem->unit.x + ( pd->base.y - cpd->base.y ) * stem->unit.y ); if (( cpdist > stem->clen/2 ) || (!(ct->touched[pd->ttfindex] & flag) && !(ct->affected[pd->ttfindex] & flag) && ( ct->touched[cpd->ttfindex] & flag || ct->affected[cpd->ttfindex] & flag ))) best = cpd; } if ( best == NULL ) best = pd; } else best = pd; stem->keypts[aindex] = best; return( true ); } static void AssignLineToPoint( DiagPointInfo *diagpts,StemData *stem,int idx,int is_l ) { int num, base, i; PointData *pd1, *pd2; num = diagpts[idx].count; base = ( is_l ) ? 0 : 2; pd1 = stem->keypts[base]; pd2 = stem->keypts[base+1]; for ( i=0; ikeypts[base]; diagpts[idx].line[num].pd2 = stem->keypts[base+1]; diagpts[idx].line[num].done = false; diagpts[idx].count++; return; } /* Convert the existing diagonal stem layout to glyph data, containing * information about points assigned to each stem. Then run on stem chunks * and associate with each point the line it should be aligned by. Note that * we have to do this on a relatively early stage, as it may be important * to know, if the given point is subject to the subsequent diagonale hinting, * before any actual processing of diagonal stems is started. */ static void InitDStemData( InstrCt *ct ) { DiagPointInfo *diagpts = ct->diagpts; int i, j, idx, previdx, nextidx, num1, num2, psidx, nsidx, is_l, cnt=0; real prevlsp, prevrsp, prevlep, prevrep, lpos, rpos; GlyphData *gd; StemData *stem; PointData *ls, *rs, *le, *re, *tpd, *ppd, *npd; struct stem_chunk *chunk; gd = ct->gd; for ( i=0; istemcnt; i++ ) { stem = &gd->stems[i]; 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; prevlsp = prevrsp = 1e4; prevlep = prevrep = -1e4; ls = rs = le = re = NULL; for ( j=0; jchunk_cnt; j++ ) { chunk = &stem->chunks[j]; if ( chunk->l != NULL ) { lpos = ( chunk->l->base.x - stem->left.x )*stem->unit.x + ( chunk->l->base.y - stem->left.y )*stem->unit.y; if ( lpos < prevlsp ) { ls = chunk->l; prevlsp = lpos; } if ( lpos > prevlep ) { le = chunk->l; prevlep = lpos; } } if ( chunk->r != NULL ) { rpos = ( chunk->r->base.x - stem->right.x )*stem->unit.x + ( chunk->r->base.y - stem->right.y )*stem->unit.y; if ( rpos < prevrsp ) { rs = chunk->r; prevrsp = rpos; } if ( rpos > prevrep ) { re = chunk->r; prevrep = rpos; } } } /* Swap "left" and "right" sides for vectors pointing north-east, * so that the "left" side is always determined along the x axis * rather than relatively to the vector direction */ num1 = ( stem->unit.y > 0 ) ? 0 : 2; num2 = ( stem->unit.y > 0 ) ? 2 : 0; if (!SetDStemKeyPoint( ct,stem,ls,num1 ) || !SetDStemKeyPoint( ct,stem,rs,num2 )) continue; num1 = ( stem->unit.y > 0 ) ? 1 : 3; num2 = ( stem->unit.y > 0 ) ? 3 : 1; if (!SetDStemKeyPoint( ct,stem,le,num1 ) || !SetDStemKeyPoint( ct,stem,re,num2 )) continue; for ( j=0; jpcnt; j++ ) gd->points[j].ticked = false; for ( j=0; jpcnt; j++ ) if ( gd->points[j].sp != NULL ) { tpd = &gd->points[j]; idx = tpd->ttfindex; psidx = nsidx = -1; if ( idx < gd->realcnt ) { if ( !tpd->ticked && diagpts[idx].count < 2 && ( ( psidx = IsStemAssignedToPoint( tpd,stem,false )) > -1 || ( nsidx = IsStemAssignedToPoint( tpd,stem,true )) > -1)) { is_l = ( nsidx > -1 ) ? tpd->next_is_l[nsidx] : tpd->prev_is_l[psidx]; if ( stem->unit.y < 0 ) is_l = !is_l; AssignLineToPoint( diagpts,stem,idx,is_l ); tpd->ticked = true; } } else { previdx = tpd->sp->prev->from->nextcpindex; nextidx = tpd->sp->nextcpindex; ppd = &gd->points[previdx]; npd = &gd->points[nextidx]; if (!ppd->ticked && diagpts[previdx].count < 2 && ( nsidx = IsStemAssignedToPoint( ppd,stem,true )) > -1 ) { is_l = ppd->next_is_l[nsidx]; if ( stem->unit.y < 0 ) is_l = !is_l; AssignLineToPoint( diagpts,stem,previdx,is_l ); ppd->ticked = true; } if (!npd->ticked && diagpts[nextidx].count < 2 && ( psidx = IsStemAssignedToPoint( npd,stem,false )) > -1 ) { is_l = npd->prev_is_l[psidx]; if ( stem->unit.y < 0 ) is_l = !is_l; AssignLineToPoint( diagpts,stem,nextidx,is_l ); npd->ticked = true; } } } ct->diagstems[cnt++] = stem; } qsort( ct->diagstems,cnt,sizeof( StemData *),ds_cmp ); ct->diagcnt = cnt; } /* Usually we have to start doing each diagonal stem from the point which * is most touched in any directions. */ static int FindDiagStartPoint( StemData *stem, uint8 *touched ) { int i; for ( i=0; i<4; ++i ) { if (( touched[stem->keypts[i]->ttfindex] & tf_x ) && ( touched[stem->keypts[i]->ttfindex] & tf_y )) return( i ); } for ( i=0; i<4; ++i ) { if (( stem->unit.x > stem->unit.y && touched[stem->keypts[i]->ttfindex] & tf_y ) || ( stem->unit.y > stem->unit.x && touched[stem->keypts[i]->ttfindex] & tf_x )) return( i ); } for ( i=0; i<4; ++i ) { if ( touched[stem->keypts[i]->ttfindex] & ( tf_x | tf_y )) return( i ); } return( 0 ); } /* Check the directions at which the given point still can be moved * (i. e. has not yet been touched) and set freedom vector to that * direction in case it has not already been set. */ static int SetFreedomVector( uint8 **instrs,int pnum, uint8 *touched,DiagPointInfo *diagpts,BasePoint *norm,BasePoint *fv,int pvset,int fpgm_ok ) { int i, pushpts[3]; PointData *start=NULL, *end=NULL; BasePoint newfv; if (( touched[pnum] & tf_d ) && !( touched[pnum] & tf_x ) && !( touched[pnum] & tf_y )) { for ( i=0 ; ibase,&end->base,false ); if ( !UnitsParallel( fv,&newfv,true )) { fv->x = newfv.x; fv->y = newfv.y; pushpts[0] = start->ttfindex; pushpts[1] = end->ttfindex; *instrs = pushpoints( *instrs,2,pushpts ); *(*instrs)++ = 0x08; /*SFVTL[parallel]*/ } return( true ); } else if ( touched[pnum] & tf_x && !(touched[pnum] & tf_d) && !(touched[pnum] & tf_y)) { if (!( RealNear( fv->x,0 ) && RealNear( fv->y,1 ))) { *(*instrs)++ = 0x04; /*SFVTCA[y-axis]*/ fv->x = 0; fv->y = 1; } return( true ); } else if ( touched[pnum] & tf_y && !(touched[pnum] & tf_d) && !(touched[pnum] & tf_x)) { if (!( RealNear( fv->x,1 ) && RealNear( fv->y,0 ))) { *(*instrs)++ = 0x05; /*SFVTCA[x-axis]*/ fv->x = 1; fv->y = 0; } return( true ); } else if ( !(touched[pnum] & (tf_x|tf_y|tf_d))) { if ( !UnitsParallel( fv,norm,true )) { fv->x = norm->x; fv->y = norm->y; if ( pvset ) *(*instrs)++ = 0x0E; /*SFVTPV*/ else { pushpts[0] = EF2Dot14(norm->x); pushpts[1] = EF2Dot14(norm->y); if ( fpgm_ok ) { pushpts[2] = 21; *instrs = pushpoints( *instrs,3,pushpts ); *(*instrs)++ = CALL; /* aspect-ratio correction */ } else *instrs = pushpoints( *instrs,2,pushpts ); *(*instrs)++ = 0x0b; /* SFVFS */ } } return( true ); } return( false ); } static int MarkLineFinished( int pnum,int startnum,int endnum,DiagPointInfo *diagpts ) { int i; for ( i=0; ittfindex == startnum ) && ( diagpts[pnum].line[i].pd2->ttfindex == endnum )) { diagpts[pnum].line[i].done = 2; return( true ); } } return( false ); } static uint8 *FixDStemPoint ( InstrCt *ct,StemData *stem, int pt,int refpt,int firstedge,int cvt,BasePoint *fv ) { uint8 *instrs, *touched; DiagPointInfo *diagpts; diagpts = ct->diagpts; touched = ct->touched; instrs = ct->pt; if ( SetFreedomVector( &instrs,pt,touched,diagpts,&stem->l_to_r,fv,true, ct->gic->fpgm_done && ct->gic->prep_done )) { if ( refpt == -1 ) { if (( fv->x == 1 && !( touched[pt] & tf_x )) || ( fv->y == 1 && !( touched[pt] & tf_y ))) { instrs = pushpoint( instrs,pt ); *instrs++ = MDAP; } else { instrs = pushpoint( instrs,pt ); *instrs++ = SRP0; } ct->rp0 = pt; } else { if ( refpt != ct->rp0 ) { instrs = pushpoint( instrs,refpt ); *instrs++ = SRP0; ct->rp0 = refpt; } if ( cvt < 0 ) { instrs = pushpoint( instrs,pt ); *instrs++ = MDRP_grey; } else { instrs = pushpointstem( instrs,pt,cvt ); *instrs++ = MIRP_rp0_min_black; ct->rp0 = pt; } } touched[pt] |= tf_d; if (!MarkLineFinished( pt,stem->keypts[0]->ttfindex,stem->keypts[1]->ttfindex,diagpts )) MarkLineFinished( pt,stem->keypts[2]->ttfindex,stem->keypts[3]->ttfindex,diagpts ); } return( instrs ); } static int DStemHasSnappableCorners ( InstrCt *ct,StemData *stem,PointData *pd1,PointData *pd2 ) { uint8 *touched = ct->touched; /* We should be dealing with oncurve points */ if ( pd1->sp == NULL || pd2->sp == NULL ) return( false ); /* points should not be lined up vertically or horizontally */ if (fabs( pd1->base.x - pd2->base.x ) <= ct->gic->fudge || fabs( pd1->base.y - pd2->base.y ) <= ct->gic->fudge ) return( false ); if (( pd1->x_corner == 1 && !( touched[pd1->ttfindex] & tf_y ) && pd2->y_corner == 1 && !( touched[pd2->ttfindex] & tf_x )) || ( pd1->y_corner == 1 && !( touched[pd1->ttfindex] & tf_x ) && pd2->x_corner == 1 && !( touched[pd2->ttfindex] & tf_y ))) return( true ); return( false ); } static uint8 *SnapDStemCorners ( InstrCt *ct,StemData *stem,PointData *pd1,PointData *pd2,BasePoint *fv ) { uint8 *instrs, *touched; int xbase, ybase; instrs = ct->pt; touched = ct->touched; if ( pd1->x_corner && pd2->y_corner ) { xbase = pd1->ttfindex; ybase = pd2->ttfindex; } else { xbase = pd2->ttfindex; ybase = pd1->ttfindex; } *(ct->pt)++ = SVTCA_x; ct->pt = push2points( ct->pt,ybase,xbase ); *(ct->pt)++ = touched[xbase] & tf_x ? MDAP : MDAP_rnd; *(ct->pt)++ = MDRP_min_black; *(ct->pt)++ = SVTCA_y; ct->pt = push2points( ct->pt,xbase,ybase ); *(ct->pt)++ = touched[ybase] & tf_y ? MDAP : MDAP_rnd; *(ct->pt)++ = MDRP_min_black; touched[xbase] |= ( tf_x | tf_y ); touched[ybase] |= ( tf_x | tf_y ); fv->x = 0; fv->y = 1; return( instrs ); } /* A basic algorithm for hinting diagonal stems: * -- iterate through diagonal stems, ordered from left to right; * -- for each stem, find the most touched point, to start from, * and fix that point. TODO: the positioning should be done * relatively to points already touched by x or y; * -- position the second point on the same edge, using dual projection * vector; * -- link to the second edge and repeat the same operation. * * For each point we first determine a direction at which it still can * be moved. If a point has already been positioned relatively to another * diagonal line, then we move it along that diagonale. Thus this algorithm * can handle things like "V" where one line's ending point is another * line's starting point without special exceptions. */ static uint8 *FixDstem( InstrCt *ct, StemData *ds, BasePoint *fv ) { int startnum, a1, a2, b1, b2, firstedge, cvt; int x_ldup, y_ldup, x_edup, y_edup, dsc1, dsc2; PointData *v1, *v2; uint8 *touched; int pushpts[3]; if ( ds->ldone && ds->rdone ) return( ct->pt ); touched = ct->touched; dsc1 = DStemHasSnappableCorners( ct,ds,ds->keypts[0],ds->keypts[2] ); dsc2 = DStemHasSnappableCorners( ct,ds,ds->keypts[1],ds->keypts[3] ); if ( dsc1 || dsc2 ) { ct->pt = pushF26Dot6( ct->pt,.59662 ); *(ct->pt)++ = SMD; if ( dsc1 ) SnapDStemCorners( ct,ds,ds->keypts[0],ds->keypts[2],fv ); if ( dsc2 ) SnapDStemCorners( ct,ds,ds->keypts[1],ds->keypts[3],fv ); ct->pt = pushF26Dot6( ct->pt,DIAG_MIN_DISTANCE ); *(ct->pt)++ = SMD; } if ( !dsc1 || !dsc2 ) { startnum = FindDiagStartPoint( ds,touched ); a1 = ds->keypts[startnum]->ttfindex; if (( startnum == 0 ) || ( startnum == 1 )) { firstedge = true; v1 = ds->keypts[0]; v2 = ds->keypts[1]; a2 = ( startnum == 1 ) ? ds->keypts[0]->ttfindex : ds->keypts[1]->ttfindex; b1 = ( startnum == 1 ) ? ds->keypts[3]->ttfindex : ds->keypts[2]->ttfindex; b2 = ( startnum == 1 ) ? ds->keypts[2]->ttfindex : ds->keypts[3]->ttfindex; } else { firstedge = false; v1 = ds->keypts[2]; v2 = ds->keypts[3]; a2 = ( startnum == 3 ) ? ds->keypts[2]->ttfindex : ds->keypts[3]->ttfindex; b1 = ( startnum == 3 ) ? ds->keypts[1]->ttfindex : ds->keypts[0]->ttfindex; b2 = ( startnum == 3 ) ? ds->keypts[0]->ttfindex : ds->keypts[1]->ttfindex; } /* Always put the calculated stem width into the CVT table, unless it is * already there. This approach would be wrong for vertical or horizontal * stems, but for diagonales it is just unlikely that we can find an * acceptable predefined value in StemSnapH or StemSnapV */ cvt = TTF_getcvtval( ct->gic->sf,ds->width ); pushpts[0] = EF2Dot14(ds->l_to_r.x); pushpts[1] = EF2Dot14(ds->l_to_r.y); if ( ct->gic->fpgm_done && ct->gic->prep_done ) { pushpts[2] = 21; ct->pt = pushnums( ct->pt, 3, pushpts ); *(ct->pt)++ = CALL; /* Aspect ratio correction */ } else ct->pt = pushnums( ct->pt, 2, pushpts ); *(ct->pt)++ = 0x0A; /* SPVFS */ pushpts[0] = v1->ttfindex; pushpts[1] = v2->ttfindex; x_ldup =( touched[a1] & tf_x && touched[a2] & tf_x ) || ( touched[b1] & tf_x && touched[b2] & tf_x ); y_ldup =( touched[a1] & tf_y && touched[a2] & tf_y ) || ( touched[b1] & tf_y && touched[b2] & tf_y ); x_edup =( touched[a1] & tf_x && touched[b1] & tf_x ) || ( touched[a2] & tf_x && touched[b2] & tf_x ); y_edup =( touched[a1] & tf_y && touched[b1] & tf_y ) || ( touched[a2] & tf_y && touched[b2] & tf_y ); if (( x_ldup && !y_edup ) || ( y_ldup && !x_edup)) { ct->pt = FixDStemPoint ( ct,ds,a1,-1,firstedge,-1,fv ); ct->pt = FixDStemPoint ( ct,ds,b2,-1,firstedge,-1,fv ); ct->pt = FixDStemPoint ( ct,ds,b1,a1,firstedge,cvt,fv ); ct->pt = FixDStemPoint ( ct,ds,a2,b2,firstedge,cvt,fv ); } else { ct->pt = FixDStemPoint ( ct,ds,a1,-1,firstedge,-1,fv ); ct->pt = FixDStemPoint ( ct,ds,a2,a1,firstedge,-1,fv ); ct->pt = FixDStemPoint ( ct,ds,b1,a1,firstedge,cvt,fv ); ct->pt = FixDStemPoint ( ct,ds,b2,b1,firstedge,-1,fv ); } } ds->ldone = ds->rdone = true; return( ct->pt ); } static uint8 *FixPointOnLine ( DiagPointInfo *diagpts,PointVector *line, PointData *pd,InstrCt *ct,BasePoint *fv,BasePoint *pv,int *rp1,int *rp2 ) { uint8 *instrs, *touched; BasePoint newpv; int pushpts[4]; touched = ct->touched; instrs = ct->pt; newpv = GetVector( &line->pd1->base,&line->pd2->base,true ); if ( SetFreedomVector( &instrs,pd->ttfindex,touched,diagpts,&newpv,fv,false, ct->gic->fpgm_done && ct->gic->prep_done )) { if ( ct->rp0 != line->pd1->ttfindex ) { instrs = pushpoint( instrs,line->pd1->ttfindex ); *instrs++ = SRP0; ct->rp0 = line->pd1->ttfindex; } if ( ct->gic->fpgm_done ) { pv->x = newpv.x; pv->y = newpv.y; pushpts[0] = pd->ttfindex; pushpts[1] = line->pd1->ttfindex; pushpts[2] = line->pd2->ttfindex; pushpts[3] = 19; instrs = pushpoints( instrs,4,pushpts ); *instrs++ = CALL; } else { if ( !UnitsParallel( pv,&newpv,true )) { pv->x = newpv.x; pv->y = newpv.y; pushpts[0] = line->pd1->ttfindex; pushpts[1] = line->pd2->ttfindex; instrs = pushpoints( instrs,2,pushpts ); *instrs++ = 0x07; /*SPVTL[orthogonal]*/ } instrs = pushpoint( instrs,pd->ttfindex ); *instrs++ = MDRP_grey; } } return( instrs ); } /* If a point has to be positioned just relatively to the diagonal * line (no intersections, no need to maintain other directions), * then we can interpolate it along that line. This usually produces * better results for things like a Danish slashed "O". */ static uint8 *InterpolateAlongDiag ( DiagPointInfo *diagpts,PointVector *line, PointData *pd,InstrCt *ct,BasePoint *fv,BasePoint *pv,int *rp1,int *rp2 ) { uint8 *instrs, *touched; BasePoint newpv; int pushpts[3]; touched = ct->touched; instrs = ct->pt; if (diagpts[pd->ttfindex].count != 1 || touched[pd->ttfindex] & ( tf_x|tf_y ) || diagpts[pd->ttfindex].line[0].done > 1 ) return( instrs ); newpv = GetVector( &line->pd1->base,&line->pd2->base,false ); if ( !UnitsParallel( pv,&newpv,false ) || *rp1 != line->pd1->ttfindex || *rp2 != line->pd1->ttfindex ) { pushpts[0] = pd->ttfindex; pushpts[1] = line->pd1->ttfindex; pushpts[2] = line->pd2->ttfindex; instrs = pushpoints( instrs,3,pushpts ); } else instrs = pushpoint ( instrs,pd->ttfindex ); if ( !UnitsParallel( pv,&newpv,true )) { pv->x = newpv.x; pv->y = newpv.y; if ( *rp1 != line->pd1->ttfindex || *rp2 != line->pd1->ttfindex ) { *instrs++ = DUP; *instrs++ = 0x8a; /* ROLL */ *instrs++ = DUP; *instrs++ = 0x8a; /* ROLL */ *instrs++ = 0x23; /* SWAP */ } *instrs++ = 0x06; /* SPVTL[parallel] */ } if ( !UnitsParallel( fv,&newpv,true )) { *instrs++ = 0x0E; /* SFVTPV */ fv->x = newpv.x; fv->y = newpv.y; } if ( *rp1 != line->pd1->ttfindex || *rp2 != line->pd1->ttfindex ) { *rp1 = line->pd1->ttfindex; *rp2 = line->pd1->ttfindex; *instrs++ = SRP1; *instrs++ = SRP2; } *instrs++ = IP; touched[pd->ttfindex] |= tf_d; diagpts[pd->ttfindex].line[0].done = 2; return( instrs ); } /* When all stem edges have already been positioned, run through other * points which are known to be related with some diagonales and position * them too. This may include both intersections and points which just * lie on a diagonal line. This function does not care about starting/ending * points of stems, unless they should be additionally positioned relatively * to another stem. Thus is can handle things like "X" or "K". */ static uint8 *MovePointsToIntersections( InstrCt *ct,BasePoint *fv ) { int i, j, ptcnt, rp1=-1, rp2=-1; uint8 *touched; BasePoint pv; PointData *curpd, *npd, *ppd; DiagPointInfo *diagpts; StemData *ds; touched = ct->touched; ptcnt = ct->gd->realcnt; diagpts = ct->diagpts; pv.x = 1; pv.y = 0; for ( i=0; i 0 ) { for ( j=0; jgd->points[i]; ct->pt = FixPointOnLine( diagpts,&diagpts[i].line[j], curpd,ct,fv,&pv,&rp1,&rp2 ); diagpts[i].line[j].done = true; touched[i] |= tf_d; } } } } /* Second pass to interpolate points lying on diagonal lines (but not * starting/ending stem points) along those lines. This operation, unlike * moving points to diagonals, requires vectors to be set parallel to lines, * and this is the reason for which it is done in a separate cycle */ for ( i=0; idiagcnt; i++ ) { ds = ct->diagstems[i]; if ( ds->ldone ) { for ( j=0; jchunk_cnt; j++ ) if (( curpd = ds->chunks[j].l ) != NULL ) { if ( curpd->ttfindex < ct->ptcnt ) { ct->pt = InterpolateAlongDiag ( diagpts,&diagpts[curpd->ttfindex].line[0], curpd,ct,fv,&pv,&rp1,&rp2 ); } else { ppd = &ct->gd->points[curpd->sp->prev->from->nextcpindex]; npd = &ct->gd->points[curpd->sp->nextcpindex]; if ( IsStemAssignedToPoint(ppd, ds, true) != -1 ) ct->pt = InterpolateAlongDiag ( diagpts,&diagpts[ppd->ttfindex].line[0], ppd,ct,fv,&pv,&rp1,&rp2 ); if ( IsStemAssignedToPoint(npd, ds, false) != -1 ) ct->pt = InterpolateAlongDiag ( diagpts,&diagpts[npd->ttfindex].line[0], npd,ct,fv,&pv,&rp1,&rp2 ); } } } if ( ds->rdone ) { for ( j=0; jchunk_cnt; j++ ) if (( curpd = ds->chunks[j].r ) != NULL ) { if ( curpd->ttfindex < ct->ptcnt ) { ct->pt = InterpolateAlongDiag ( diagpts,&diagpts[curpd->ttfindex].line[0], curpd,ct,fv,&pv,&rp1,&rp2 ); } else { ppd = &ct->gd->points[curpd->sp->prev->from->nextcpindex]; npd = &ct->gd->points[curpd->sp->nextcpindex]; if ( IsStemAssignedToPoint(ppd, ds, true) != -1 ) ct->pt = InterpolateAlongDiag ( diagpts,&diagpts[ppd->ttfindex].line[0], ppd,ct,fv,&pv,&rp1,&rp2 ); if ( IsStemAssignedToPoint(npd, ds, false) != -1 ) ct->pt = InterpolateAlongDiag ( diagpts,&diagpts[npd->ttfindex].line[0], npd,ct,fv,&pv,&rp1,&rp2 ); } } } } return( ct->pt ); } static void TouchControlPoint( InstrCt *ct,PointData *pd, int next,int *tobefixedy,int *tobefixedx,int *numx,int *numy ) { int idx, cpidx; PointData *cpd; uint8 *touched = ct->touched; idx = pd->ttfindex; cpidx = next ? pd->sp->nextcpindex : pd->sp->prev->from->nextcpindex; cpd = &ct->gd->points[cpidx]; if ( has_valid_dstem( cpd, !next ) != -1 ) { /* if this control point is used to describe an implied spline * point, then it is instructed as if it was an oncurve point */ if ( idx == 0xffff && touched[cpidx] & tf_d ) { if (!( touched[cpidx] & tf_y )) { tobefixedy[(*numy)++] = cpidx; touched[cpidx] |= tf_y; } if (!( touched[cpidx] & tf_x )) { tobefixedx[(*numx)++] = cpidx; touched[cpidx] |= tf_x; } /* otherwise we just mark it as affected to prevent undesired * interpolations */ } else if ( idx < ct->gd->realcnt && touched[idx] & tf_d ) { ct->affected[cpidx] |= tf_x; ct->affected[cpidx] |= tf_y; } } } /* Finally explicitly touch all affected points by X and Y (unless they * have already been), so that subsequent IUP's can't distort our stems. */ static uint8 *TouchDStemPoints( InstrCt *ct,BasePoint *fv ) { int i, ptcnt, numx=0, numy=0, idx; int *tobefixedy, *tobefixedx; uint8 *instrs, *touched; PointData *pd; touched = ct->touched; instrs = ct->pt; ptcnt = ct->gd->pcnt; tobefixedy = calloc( ptcnt,sizeof( int )); tobefixedx = calloc( ptcnt,sizeof( int )); /* Ensure that the projection vector is no longer set to a diagonal line */ if ( fv->x == 1 && fv->y == 0 ) *instrs++ = 0x03; /* SPVTCA[x] */ else if ( fv->x == 0 && fv->y == 1 ) *instrs++ = 0x02; /* SPVTCA[y] */ for ( i=0; igd->points[i].sp != NULL ) { pd = &ct->gd->points[i]; if (( has_valid_dstem( pd,false )) != -1 || ( has_valid_dstem( pd,true )) != -1 ) { idx = pd->ttfindex; if ( idx < ct->gd->realcnt && touched[idx] & tf_d ) { if (!( touched[idx] & tf_y )) { tobefixedy[numy++] = idx; touched[idx] |= tf_y; } if (!( touched[idx] & tf_x )) { tobefixedx[numx++] = idx; touched[idx] |= tf_x; } } if ( !pd->sp->noprevcp ) TouchControlPoint( ct,pd,false,tobefixedy,tobefixedx,&numx,&numy ); if ( !pd->sp->nonextcp ) TouchControlPoint( ct,pd,true,tobefixedy,tobefixedx,&numx,&numy ); } } if ( numy>0 ) { if ( !(fv->x == 0 && fv->y == 1) ) *instrs++ = SVTCA_y; instrs = instructpoints ( instrs,numy,tobefixedy,MDAP ); } if ( numx>0 ) { if ( !(fv->x == 1 && fv->y == 0) || numy > 0 ) *instrs++ = SVTCA_x; instrs = instructpoints ( instrs,numx,tobefixedx,MDAP ); } if ( numx == 0 && numy == 0 ) *instrs++ = SVTCA_x; free( tobefixedy ); free( tobefixedx ); return( instrs ); } static void DStemInfoGeninst( InstrCt *ct ) { BasePoint fv; int i; if (ct->diagcnt == 0) return; fv.x = 1; fv.y = 0; ct->pt = pushF26Dot6( ct->pt,DIAG_MIN_DISTANCE ); *(ct->pt)++ = SMD; /* Set Minimum Distance */ for ( i=0; idiagcnt; i++ ) ct->pt = FixDstem ( ct,ct->diagstems[i],&fv ); ct->pt = MovePointsToIntersections( ct,&fv ); ct->pt = TouchDStemPoints ( ct,&fv); ct->pt = pushF26Dot6( ct->pt,1.0 ); *(ct->pt)++ = SMD; /* Set Minimum Distance */ ct->xdir = fv.x; } /****************************************************************************** * * Strong point interpolation * * TODO! Better optimization, if possible. * TODO! leftmost and righmost bounds, if not already controlled by stems. * ******************************************************************************/ /* To be used with qsort() - sorts edge array in ascending order. */ struct stemedge { int refpt; double pos; }; /* To be used with qsort() - sorts edge array in ascending order. */ static int sortedges(const void *_e1, const void *_e2) { const struct stemedge *e1 = _e1, *e2 = _e2; return ( e1->pos > e2->pos ); } static int AddEdge(InstrCt *ct, StemData *stem, int is_l, struct stemedge *edgelist, int cnt) { real coord; int i, skip, refidx; if (!stem->ghost || (is_l && stem->width == 20) || (!is_l && stem->width == 21)) { coord = is_l ? ((real *) &stem->left.x)[!ct->xdir] : ((real *) &stem->right.x)[!ct->xdir]; refidx = is_l ? stem->leftidx : stem->rightidx; for (i=skip=0; igic->fudge || edgelist[i].refpt == refidx) { skip=1; break; } if (!skip && refidx != -1) { edgelist[cnt ].refpt = refidx; edgelist[cnt++].pos = coord; } } return( cnt ); } /* Optional feature: tries to maintain relative position of some important * points between stems' edges, so that glyph's shape is mostly preserved * when strongly gridfitted. This in terms of FreeType is called 'Strong Point * Interpolation'. It now does more or else what it should, but generates large * and sometimes incomplete code - see 'todos' above, and optimize_strongpts(). * Note: it would affect diagonals if done before instructing them. * * TODO: it now intrpolates strong points only between hints' edges. * What about between leftmost/rightmost edge and leftmost/rightmost * glyph extents, if they protrude beyond the edges? */ static void InterpolateStrongPoints(InstrCt *ct) { StemBundle *bundle; StemData *stem; uint8 touchflag = ct->xdir?tf_x:tf_y; real fudge; struct stemedge edgelist[192]; int edgecnt=0, i, j; int lpoint = -1, ledge=0; int rpoint = -1; int nowrp1 = 1; int ldone = 0; bundle = ( ct->xdir ) ? ct->gd->vbundle : ct->gd->hbundle; if (bundle == NULL || bundle->cnt == 0) return; /* List all stem edges. List only active edges for ghost hints. */ for(i=0; icnt; i++) { stem = bundle->stemlist[i]; edgecnt = AddEdge(ct, stem, ct->xdir, edgelist, edgecnt); edgecnt = AddEdge(ct, stem, !ct->xdir, edgelist, edgecnt); } if (edgecnt < 2) return; qsort(edgelist, edgecnt, sizeof(struct stemedge), sortedges); /* Interpolate important points between subsequent edges */ for (i=0; itouched[rpoint] & touchflag)) continue; if (lpoint==-1) { /* first edge */ lpoint = rpoint; ledge = i; } else { fudge = ct->gic->fudge; ct->gic->fudge = (edgelist[i].pos-edgelist[ledge].pos)/2; init_edge(ct, (edgelist[i].pos+edgelist[ledge].pos)/2, ALL_CONTOURS); optimize_strongpts(ct); /* Special way is needed here. */ ct->gic->fudge = fudge; if (!ct->edge.othercnt) { nowrp1 = 1; lpoint = rpoint; ledge = i; ldone = 0; } else if (ct->edge.refscore) { if (!ldone) { ct->pt = push2points(ct->pt, rpoint, lpoint); *ct->pt++ = SRP1; *ct->pt++ = SRP2; } else { ct->pt = pushpoint(ct->pt, rpoint); if (nowrp1) *ct->pt++ = SRP1; else *ct->pt++ = SRP2; nowrp1 = !nowrp1; } lpoint = rpoint; ledge = i; ldone = 1; /* instruct points */ ct->pt = instructpoints(ct->pt, ct->edge.othercnt, ct->edge.others, IP); for (j=0; jedge.othercnt; j++) ct->touched[ct->edge.others[j]] |= touchflag; } if (ct->edge.othercnt) { free(ct->edge.others); ct->edge.othercnt = 0; } } } } /****************************************************************************** * * Generate instructions for a glyph. * ******************************************************************************/ static uint8 *dogeninstructions(InstrCt *ct) { StemData *stem; int max, i; DStemInfo *dstem; BlueData nbd; /* Fill a temporary BlueData structure basing on the data stored in the global * instruction context. This is needed for GlyphDataBuild(), as it accepts * blue data only in this format */ for ( i=0; igic->bluecnt; i++ ) { if ( ct->gic->blues[i].base < ct->gic->blues[i].overshoot ) { nbd.blues[i][0] = ct->gic->blues[i].base; nbd.blues[i][1] = ct->gic->blues[i].overshoot; } else { nbd.blues[i][0] = ct->gic->blues[i].overshoot; nbd.blues[i][1] = ct->gic->blues[i].base; } } nbd.bluecnt = ct->gic->bluecnt; ct->gd = GlyphDataBuild( ct->sc,ct->gic->layer,&nbd,instruct_diagonal_stems ); /* Maximum instruction length is 6 bytes for each point in each dimension * 2 extra bytes to finish up. And one byte to switch from x to y axis * Diagonal take more space because we need to set the orientation on * each stem, and worry about intersections, etc. * That should be an over-estimate */ max=2; if ( ct->gd->hbundle!=NULL ) max += ct->ptcnt*8; if ( ct->gd->vbundle!=NULL ) max += ct->ptcnt*8+4; for ( dstem=ct->sc->dstem; dstem!=NULL; max+=7+4*6+100, dstem=dstem->next ); if ( ct->sc->md!=NULL ) max += ct->ptcnt*12; max += ct->ptcnt*6; /* in case there are any rounds */ max += ct->ptcnt*6; /* paranoia */ ct->instrs = ct->pt = malloc(max); /* Initially no stem hints are done */ if ( ct->gd->hbundle!=NULL ) { for ( i=0; igd->hbundle->cnt; i++ ) { stem = ct->gd->hbundle->stemlist[i]; stem->ldone = stem->rdone = false; } } if ( ct->gd->vbundle!=NULL ) { for ( i=0; igd->vbundle->cnt; i++ ) { stem = ct->gd->vbundle->stemlist[i]; stem->ldone = stem->rdone = false; } } if ( instruct_diagonal_stems ) { /* Prepare info about diagonal stems to be used during edge optimization. */ /* These contents need to be explicitly freed after hinting diagonals. */ ct->diagstems = calloc(ct->gd->stemcnt, sizeof(StemData *)); ct->diagpts = calloc(ct->ptcnt, sizeof(struct diagpointinfo)); InitDStemData(ct); } /* We start from instructing horizontal features (=> movement in y) * Do this first so that the diagonal hinter will have everything moved * properly when it sets the projection vector * Even if we aren't doing the diagonals, we do the blues. */ ct->xdir = false; *(ct->pt)++ = SVTCA_y; snap_to_blues(ct); HStemGeninst(ct); /* Next instruct vertical features (=> movement in x). */ ct->xdir = true; *(ct->pt)++ = SVTCA_x; VStemGeninst(ct); /* Then instruct diagonal stems (=> movement in x) * This is done after vertical stems because it involves * moving some points out-of their vertical stems. */ if (instruct_diagonal_stems && ct->diagcnt > 0) DStemInfoGeninst(ct); if ( interpolate_strong ) { /* Adjust important points between hint edges. */ if (ct->xdir == false) *(ct->pt)++ = SVTCA_x; ct->xdir = true; InterpolateStrongPoints(ct); ct->xdir = false; *(ct->pt)++ = SVTCA_y; InterpolateStrongPoints(ct); } /* Interpolate untouched points */ *(ct->pt)++ = IUP_y; *(ct->pt)++ = IUP_x; if ((ct->pt)-(ct->instrs) > max) IError( "We're about to crash.\n" "We miscalculated the glyph's instruction set length\n" "When processing TTF instructions (hinting) of %s", ct->sc->name ); if ( instruct_diagonal_stems ) { free(ct->diagstems); free(ct->diagpts); } GlyphDataFree( ct->gd ); ct->sc->ttf_instrs_len = (ct->pt)-(ct->instrs); ct->sc->instructions_out_of_date = false; return ct->sc->ttf_instrs = realloc(ct->instrs,(ct->pt)-(ct->instrs)); } void NowakowskiSCAutoInstr(GlobalInstrCt *gic, SplineChar *sc) { int cnt, contourcnt; BasePoint *bp; int *contourends; uint8 *clockwise; uint8 *touched; uint8 *affected; SplineSet *ss; RefChar *ref; InstrCt ct; int i; if ( !sc->layers[gic->layer].order2 ) return; if ( sc->layers[gic->layer].refs!=NULL && sc->layers[gic->layer].splines!=NULL ) { ff_post_error(_("Can't instruct this glyph"), _("TrueType does not support mixed references and contours.\nIf you want instructions for %.30s you should either:\n * Unlink the reference(s)\n * Copy the inline contours into their own (unencoded\n glyph) and make a reference to that."), sc->name ); return; } for ( ref = sc->layers[gic->layer].refs; ref!=NULL; ref=ref->next ) { if ( ref->transform[0]>=2 || ref->transform[0]<-2 || ref->transform[1]>=2 || ref->transform[1]<-2 || ref->transform[2]>=2 || ref->transform[2]<-2 || ref->transform[3]>=2 || ref->transform[3]<-2 ) break; } if ( ref!=NULL ) { ff_post_error(_("Can't instruct this glyph"), _("TrueType does not support references which\nare scaled by more than 200%%. But %1$.30s\nhas been in %2$.30s. Any instructions\nadded would be meaningless."), ref->sc->name, sc->name ); return; } if ( sc->ttf_instrs ) { free(sc->ttf_instrs); sc->ttf_instrs = NULL; sc->ttf_instrs_len = 0; } SCNumberPoints(sc,gic->layer); if ( autohint_before_generate && sc->changedsincelasthinted && !sc->manualhints ) SplineCharAutoHint(sc,gic->layer,NULL); if ( sc->vstem==NULL && sc->hstem==NULL && sc->dstem==NULL && sc->md==NULL) return; /* TODO! * * We're having problems with references utilizing 'use my metrics' that are * rotated or flipped horizontally. Basically, such glyphs can get negative * width and behave strangely when the glyph referred is instructed. Such * widths are treated very differently under Freetype (OK) and Windows * (terribly shifted), and I suppose other rasterizers can also complain. * Perhaps we should advise turning 'use my metrics' off. */ if ( sc->layers[gic->layer].splines==NULL ) return; /* Start dealing with the glyph */ contourcnt = 0; for ( ss=sc->layers[gic->layer].splines; ss!=NULL; ss=ss->next, ++contourcnt ); cnt = SSTtfNumberPoints(sc->layers[gic->layer].splines); contourends = malloc((contourcnt+1)*sizeof(int)); clockwise = calloc(contourcnt,1); bp = malloc(cnt*sizeof(BasePoint)); touched = calloc(cnt,1); affected = calloc(cnt,1); contourcnt = cnt = 0; for ( ss=sc->layers[gic->layer].splines; ss!=NULL; ss=ss->next ) { touched[cnt] |= tf_startcontour; cnt = SSAddPoints(ss,cnt,bp,NULL); touched[cnt-1] |= tf_endcontour; contourends[contourcnt] = cnt-1; clockwise[contourcnt++] = SplinePointListIsClockwise(ss); } contourends[contourcnt] = 0; for (i=0; ibluecnt; i++) gic->blues[i].highest = gic->blues[i].lowest = -1; ct.gic = gic; ct.sc = sc; ct.ss = sc->layers[gic->layer].splines; ct.instrs = NULL; ct.pt = NULL; ct.ptcnt = cnt; ct.contourends = contourends; ct.clockwise = clockwise; ct.bp = bp; ct.touched = touched; ct.affected = affected; ct.diagstems = NULL; ct.diagpts = NULL; ct.rp0 = 0; dogeninstructions(&ct); free(touched); free(affected); free(bp); free(contourends); free(clockwise); }