Permalink
Fetching contributors…
Cannot retrieve contributors at this time
7380 lines (6916 sloc) 232 KB
/* Copyright (C) 2000-2012 by George Williams */
/* Copyright (C) 2012-2013 by Khaled Hosny */
/* Copyright (C) 2013 by Matthew Skala */
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <fontforge-config.h>
#include "featurefile.h"
#include "encoding.h"
#include "fontforgevw.h"
#include "fvfonts.h"
#include "lookups.h"
#include "namelist.h"
#include "ttf.h"
#include "splineutil.h"
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#ifdef __need_size_t
/* This is a bug on the mac, someone defines this and leaves it defined */
/* that means when I load stddef.h it only defines size_t and doesn't */
/* do offset_of, which is what I need */
# undef __need_size_t
#endif
#include <stddef.h>
#include <string.h>
#include <utype.h>
#include <ustring.h>
#include <locale.h>
#include "ffglib.h"
/* Adobe's opentype feature file */
/* Which suffers incompatible changes according to Adobe's whim */
/* Currently trying to support the version of december 2008, Version 1.8. */
/* Ambiguities */
/* In section 4.d we are told that MarkAttachmentType/UseMarkFilteringSet */
/* both take <glyph class name>s, yet in 9.b we are told they take */
/* <mark class name>s */
/* Read Roberts says that either will be accepted */
/* In 6.e a NULL anchor is defined by <anchorNULL>, shouldn't this be */
/* <anchor NULL>? In that same example the NULL anchor is not followed by */
/* a mark keyword and class, is this correct syntax? */
/* Read Roberts says <anchorNULL> is a typo, but that not having a mark class*/
/* is correct */
/* 2.e.vi The syntax for contourpoints has changed. In v1.6 it was */
/* "<contourpoint 2>", but in v1.8 it is "contourpoint 2". Yet this change */
/* is not mentioned in the v1.8 changelog, was it intentional? */
/* Adobe says glyph names are 31 chars, but Mangal and Source Code Sans use longer names. */
#define MAXG 63
/* ************************************************************************** */
/* ******************************* Output feat ****************************** */
/* ************************************************************************** */
/* Do GPOS and GSUB features have to share the same feature statement? */
static void dump_ascii(FILE *out, char *name) {
/* Strip out any characters which don't fit in a name token */
while ( *name ) {
if ( *name==' ' )
putc('_',out);
else if ( *name&0x80 )
/* Skip */;
else if ( isalnum(*name) || *name=='.' || *name=='_' )
putc(*name,out);
++name;
}
}
static void dump_glyphname(FILE *out, SplineChar *sc) {
if ( sc->parent->cidmaster!=NULL )
fprintf( out, "\\%d", sc->orig_pos /* That's the CID */ );
else
fprintf( out, "\\%s", sc->name );
}
static void dump_glyphbyname(FILE *out, SplineFont *sf, char *name) {
SplineChar *sc = SFGetChar(sf,-1,name);
if ( sc==NULL )
LogError(_("No glyph named %s."), name );
if ( sc==NULL || sc->parent->cidmaster==NULL )
fprintf( out, "\\%s", name );
else
fprintf( out, "\\%s", sc->name );
}
static void dump_glyphnamelist(FILE *out, SplineFont *sf, char *names) {
char *pt, *start;
int ch;
SplineChar *sc2;
int len=0;
char cidbuf[20], *nm;
if ( sf->subfontcnt==0 ) {
for ( pt=names; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( start=pt; *pt!=' ' && *pt!='\0'; ++pt );
ch = *pt; *pt = '\0';
if ( pt-start+len+1 >72 ) {
fprintf( out, "\n\t" );
len = 8;
}
fprintf( out, "\\%s ", start );
len += strlen(start)+1;
*pt = ch;
}
} else {
for ( pt=names; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( start=pt; *pt!=' ' && *pt!='\0'; ++pt );
ch = *pt; *pt = '\0';
sc2 = SFGetChar(sf,-1,start);
if ( sc2==NULL ) {
LogError(_("No CID named %s"), start);
nm = start;
} else {
sprintf( cidbuf, "\\%d", sc2->orig_pos );
nm = cidbuf;
}
if ( strlen(nm)+len+1 >72 ) {
fprintf( out, "\n\t" );
len = 8;
}
fprintf( out, "%s ", nm );
len += strlen(nm)+1;
*pt = ch;
}
}
}
static int MarkNeeded(uint8 *needed,uint8 *setsneeded,OTLookup *otl) {
int index = (otl->lookup_flags>>8)&0xff;
int sindex = (otl->lookup_flags>>16)&0xffff;
int any = false;
struct lookup_subtable *sub;
int i,l;
if ( index!=0 ) {
any |= 1;
needed[index] = true;
}
if ( otl->lookup_flags&pst_usemarkfilteringset ) {
any |= 2;
setsneeded[sindex] = true;
}
for ( sub = otl->subtables; sub!=NULL; sub=sub->next ) {
if ( sub->fpst!=NULL ) {
for ( i=sub->fpst->rule_cnt-1; i>=0; --i ) {
struct fpst_rule *r = &sub->fpst->rules[i];
for ( l=0; l<r->lookup_cnt; ++l )
any |= MarkNeeded(needed,setsneeded,r->lookups[l].lookup);
}
}
}
return( any );
}
static void gdef_markclasscheck(FILE *out,SplineFont *sf,OTLookup *otl) {
uint8 *needed;
uint8 *setsneeded;
int any = false;
int gpos;
if ( sf->mark_class_cnt==0 && sf->mark_set_cnt==0 )
return;
needed = calloc(sf->mark_class_cnt,1);
setsneeded = calloc(sf->mark_set_cnt,1);
if ( otl!=NULL ) {
any = MarkNeeded(needed,setsneeded,otl);
} else {
for ( gpos=0; gpos<2; ++gpos ) {
for ( otl=gpos?sf->gpos_lookups:sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
int index = (otl->lookup_flags>>8)&0xff;
int sindex = (otl->lookup_flags>>16)&0xffff;
if ( index!=0 ) {
any |= 1;
needed[index] = true;
}
if ( otl->lookup_flags&pst_usemarkfilteringset ) {
any |= 2;
setsneeded[sindex] = true;
}
}
}
}
if ( any&1 ) {
int i;
fprintf( out, "# GDEF Mark Attachment Classes\n" );
for ( i=1; i<sf->mark_class_cnt; ++i ) if ( needed[i] ) {
putc( '@',out );
dump_ascii( out, sf->mark_class_names[i]);
putc( '=',out );
putc( '[',out );
dump_glyphnamelist(out,sf,sf->mark_classes[i]);
fprintf( out, "];\n" );
}
fprintf( out,"\n" );
}
if ( any&2 ) {
int i;
fprintf( out, "# GDEF Mark Attachment Sets\n" );
for ( i=0; i<sf->mark_set_cnt; ++i ) if ( setsneeded[i] ) {
putc( '@',out );
dump_ascii( out, sf->mark_set_names[i]);
putc( '=',out );
putc( '[',out );
dump_glyphnamelist(out,sf,sf->mark_sets[i]);
fprintf( out, "];\n" );
}
fprintf( out,"\n" );
}
free(needed);
free(setsneeded);
}
static void dump_fpst_everythingelse(FILE *out, SplineFont *sf,char **classes,
int ccnt, OTLookup *otl) {
/* Every now and then we need to dump out class 0, the "Everything Else" */
/* Opentype class. So... find all the glyphs which aren't listed in one */
/* of the other classes, and dump 'em. Even less frequently we will be */
/* asked to apply a transformation to those elements, so we must dump */
/* out the transformed things in the same order */
int gid, i, k, ch;
char *pt, *start;
const char *text;
SplineFont *sub;
SplineChar *sc;
PST *pst;
int len = 8;
if ( sf->subfontcnt==0 ) {
for ( gid=0; gid<sf->glyphcnt; ++gid ) if ( (sc=sf->glyphs[gid])!=NULL)
sc->ticked = false;
} else {
for ( k=0; k<sf->subfontcnt; ++k ) {
sub = sf->subfonts[k];
for ( gid=0; gid<sub->glyphcnt; ++gid ) if ( (sc=sub->glyphs[gid])!=NULL)
sc->ticked = false;
}
}
for ( i=1; i<ccnt; ++i ) {
for ( pt=classes[i] ; *pt ; ) {
while ( *pt==' ' ) ++pt;
start = pt;
while ( *pt!=' ' && *pt!='\0' ) ++pt;
if ( pt==start )
break;
ch = *pt; *pt = '\0';
sc = SFGetChar(sf,-1,start);
if ( sc!=NULL )
sc->ticked = true;
*pt = ch;
}
}
if ( sf->subfontcnt==0 ) {
for ( gid=0; gid<sf->glyphcnt; ++gid ) if ( (sc=sf->glyphs[gid])!=NULL && !sc->ticked ) {
if ( otl!=NULL ) {
for ( pst=sc->possub; pst!=NULL; pst=pst->next )
if ( pst->subtable!=NULL && pst->subtable->lookup == otl )
break;
if ( pst!=NULL )
text = pst->u.subs.variant;
else
text = NULL;
} else
text = sc->name;
if ( (text==NULL && len+6 > 72 ) ||
( text!=NULL && strlen(text)+len+2 > 72 )) {
fprintf( out, "\n\t" );
len = 8;
}
if ( text==NULL ) {
fprintf( out, "NULL " );
len += 5;
} else {
fprintf( out, "\\%s ", text );
len += strlen(text)+2;
}
}
} else {
for ( k=0; k<sf->subfontcnt; ++k ) {
sub = sf->subfonts[k];
for ( gid=0; gid<sub->glyphcnt; ++gid ) if ( (sc=sub->glyphs[gid])!=NULL && !sc->ticked ) {
if ( otl!=NULL ) {
for ( pst=sc->possub; pst!=NULL; pst=pst->next )
if ( pst->subtable!=NULL && pst->subtable->lookup == otl )
break;
if ( pst!=NULL )
sc = SFGetChar(sf,-1,pst->u.subs.variant);
else
sc = NULL;
}
if ( len+8> 76 ) {
fprintf( out, "\n\t" );
len = 8;
}
if ( sc!=NULL ) {
fprintf( out, "\\%d ", sc->orig_pos );
len += 8;
} else {
fprintf( out, "NULL " );
len += 5;
}
}
}
}
}
static char *lookupname(OTLookup *otl) {
char *pt1, *pt2;
static char space[MAXG+1];
if ( otl->tempname != NULL )
return( otl->tempname );
for ( pt1=otl->lookup_name,pt2=space; *pt1 && pt2<space+MAXG; ++pt1 ) {
if ( !(*pt1&0x80) && (isalpha(*pt1) || *pt1=='_' || *pt1=='.' ||
(pt1!=otl->lookup_name && isdigit(*pt1))))
*pt2++ = *pt1;
}
*pt2 = '\0';
return( space );
}
static void dumpdevice(FILE *out,DeviceTable *devtab) {
int i, any = false;
fprintf( out, "<device " );
if ( devtab!=NULL && devtab->corrections!=NULL ) {
for ( i=devtab->first_pixel_size; i<=devtab->last_pixel_size; ++i ) if ( devtab->corrections[i-devtab->first_pixel_size]!=0 ) {
if ( any )
putc(',',out);
else
any = true;
fprintf( out, "%d %d", i, devtab->corrections[i-devtab->first_pixel_size]);
}
}
if ( any )
fprintf( out, ">" );
else
fprintf( out, "NULL>" );
}
static void dump_valuerecord(FILE *out, struct vr *vr) {
fprintf( out, "<%d %d %d %d", vr->xoff, vr->yoff, vr->h_adv_off, vr->v_adv_off );
if ( vr->adjust!=NULL ) {
putc( ' ', out);
dumpdevice(out,&vr->adjust->xadjust);
putc( ' ', out);
dumpdevice(out,&vr->adjust->yadjust);
putc( ' ', out);
dumpdevice(out,&vr->adjust->xadv);
putc( ' ', out);
dumpdevice(out,&vr->adjust->yadv);
}
putc('>',out);
}
static void dump_anchorpoint(FILE *out,AnchorPoint *ap) {
if ( ap==NULL ) {
fprintf( out, "<anchor NULL>" );
return;
}
fprintf( out, "<anchor %g %g", rint(ap->me.x), rint(ap->me.y) );
if ( ap->has_ttf_pt )
fprintf( out, " contourpoint %d", ap->ttf_pt_index );
else if ( ap->xadjust.corrections!=NULL || ap->yadjust.corrections!=NULL ) {
putc(' ',out);
dumpdevice(out,&ap->xadjust);
putc(' ',out);
dumpdevice(out,&ap->yadjust);
}
putc('>',out);
}
static int kernclass_for_feature_file(struct splinefont *sf, struct kernclass *kc, int flags) {
// Note that this is not a complete logical inverse of sister function kernclass_for_groups_plist.
return ((flags & FF_KERNCLASS_FLAG_FEATURE) ||
(!(flags & FF_KERNCLASS_FLAG_NATIVE) && (kc->feature || sf->preferred_kerning != 1)));
}
static void dump_kernclass(FILE *out,SplineFont *sf,struct lookup_subtable *sub) {
int i,j;
KernClass *kc = sub->kc;
// We only export classes and rules here that have not been emitted in groups.plist and kerning.plist.
// The feature file can reference classes from groups.plist, but kerning.plist cannot reference groups from the feature file.
for ( i=0; i<kc->first_cnt; ++i ) if ( kc->firsts[i]!=NULL && kernclass_for_feature_file(sf, kc, kc->firsts_flags ? kc->firsts_flags[i] : 0)) {
fprintf( out, " @kc%d_first_%d = [", sub->subtable_offset, i );
dump_glyphnamelist(out,sf,kc->firsts[i] );
fprintf( out, "];\n" );
}
for ( i=0; i<kc->second_cnt; ++i ) if ( kc->seconds[i]!=NULL && kernclass_for_feature_file(sf, kc, kc->seconds_flags ? kc->seconds_flags[i] : 0)) {
fprintf( out, " @kc%d_second_%d = [", sub->subtable_offset, i );
dump_glyphnamelist(out,sf,kc->seconds[i] );
fprintf( out, "];\n" );
}
for ( i=0; i<kc->first_cnt; ++i ) if ( kc->firsts[i]!=NULL ) {
for ( j=0; j<kc->second_cnt; ++j ) if ( kc->seconds[j]!=NULL ) {
if ( kc->offsets[i*kc->second_cnt+j]!=0 && kernclass_for_feature_file(sf, kc, kc->offsets_flags ? kc->offsets_flags[i] : 0) )
fprintf( out, " pos @kc%d_first_%d @kc%d_second_%d %d;\n",
sub->subtable_offset, i,
sub->subtable_offset, j,
kc->offsets[i*kc->second_cnt+j]);
}
}
}
static char *nameend_from_class(char *glyphclass) {
char *pt = glyphclass;
while ( *pt==' ' ) ++pt;
while ( *pt!=' ' && *pt!='\0' ) ++pt;
return( pt );
}
static OTLookup *lookup_in_rule(struct fpst_rule *r,int seq,int *index, int *pos) {
int i;
OTLookup *otl;
/* Suppose there are two lookups which affect the same sequence pos? */
/* That's legal in otf, but I don't think it can be specified in the */
/* feature file. It doesn't seem likely to be used so I ignore it */
for ( i=0; i<r->lookup_cnt && seq<r->lookups[i].seq; ++i );
if ( i>=r->lookup_cnt )
return( NULL );
*index = i;
*pos = seq-r->lookups[i].seq;
otl = r->lookups[i].lookup;
if ( seq==r->lookups[i].seq )
return( otl );
if ( otl->lookup_type == gsub_ligature )
return( otl );
else if ( otl->lookup_type == gpos_pair && *pos==1 )
return( otl );
return( NULL );
}
static PST *pst_from_single_lookup(SplineFont *sf, OTLookup *otl, char *name ) {
SplineChar *sc = SFGetChar(sf,-1,name);
PST *pst;
if ( sc==NULL )
return( NULL );
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
if ( pst->subtable!=NULL && pst->subtable->lookup == otl )
return( pst );
}
return( NULL );
}
static PST *pst_from_pos_pair_lookup(SplineFont *sf, OTLookup *otl, char *name1, char *name2, PST *space ) {
SplineChar *sc = SFGetChar(sf,-1,name1), *sc2;
PST *pst;
int isv;
KernPair *kp;
if ( sc==NULL )
return( NULL );
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
if ( pst->subtable!=NULL && pst->subtable->lookup == otl &&
strcmp(pst->u.pair.paired,name2)==0 )
return( pst );
}
sc2 = SFGetChar(sf,-1,name2);
if ( sc2==NULL )
return( NULL );
/* Ok. not saved as a pst. If a kp then build a pst of it */
for ( isv=0; isv<2; ++isv ) {
for ( kp= isv ? sc->vkerns : sc->kerns; kp!=NULL; kp=kp->next ) {
if ( kp->subtable->lookup==otl && kp->sc == sc2 ) {
memset(space->u.pair.vr,0,2*sizeof(struct vr));
/* !!! Bug. Lose device tables here */
if ( isv )
space->u.pair.vr[0].v_adv_off = kp->off;
else if ( otl->lookup_flags&pst_r2l )
space->u.pair.vr[1].h_adv_off = kp->off;
else
space->u.pair.vr[0].h_adv_off = kp->off;
return( space );
}
}
}
return( NULL );
}
static PST *pst_from_ligature(SplineFont *sf, OTLookup *otl, char *components ) {
PST *pst;
SplineFont *_sf;
int k,gid;
SplineChar *sc;
k=0;
do {
_sf = sf->subfontcnt==0 ? sf : sf->subfonts[k];
for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (sc = _sf->glyphs[gid])!=NULL ) {
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
if ( pst->subtable!=NULL && pst->subtable->lookup == otl &&
strcmp(pst->u.lig.components,components)==0 ) {
pst->u.lig.lig = sc;
return( pst );
}
}
}
++k;
} while ( k<sf->subfontcnt );
return( NULL );
}
static PST *pst_any_from_otl(SplineFont *sf, OTLookup *otl ) {
PST *pst;
SplineFont *_sf;
int k,gid;
SplineChar *sc;
k=0;
do {
_sf = sf->subfontcnt==0 ? sf : sf->subfonts[k];
for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (sc = _sf->glyphs[gid])!=NULL ) {
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
if ( pst->subtable!=NULL && pst->subtable->lookup == otl ) {
if ( otl->lookup_type == gsub_ligature )
pst->u.lig.lig = sc;
return( pst );
}
}
}
++k;
} while ( k<sf->subfontcnt );
return( NULL );
}
static AnchorPoint *apfind_entry_exit(SplineFont *sf,OTLookup *otl, char *name,
AnchorPoint **_exit) {
AnchorPoint *entry = NULL, *exit = NULL;
AnchorPoint *ap;
SplineChar *sc = SFGetChar(sf,-1,name);
if ( sc==NULL )
return( NULL );
for ( ap = sc->anchor ; ap!=NULL; ap=ap->next ) {
if ( ap->anchor->subtable->lookup == otl ) {
if ( ap->type == at_centry )
entry = ap;
else if ( ap->type == at_cexit )
exit = ap;
}
}
*_exit = exit;
return( entry );
}
static void dump_contextpstglyphs(FILE *out,SplineFont *sf,
struct lookup_subtable *sub, struct fpst_rule *r) {
int i, j, pos, index;
OTLookup *otl;
struct vr pairvr[2];
PST *pst, space;
char *start, *pt, *last_start, *last_end;
int ch, ch2, uses_lookups=false;
space.u.pair.vr = pairvr;
if ( r->u.glyph.back!=NULL ) {
char *temp = reverseGlyphNames(r->u.glyph.back);
dump_glyphnamelist(out,sf,temp);
free (temp);
putc(' ',out);
}
last_start = last_end = NULL;
for ( pt=r->u.glyph.names; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( start=pt; *pt!=' ' && *pt!='\0'; ++pt );
ch = *pt; *pt = '\0';
otl = lookup_in_rule(r,i,&index, &pos);
if ( otl!=NULL && !otl->ticked ) {
if ( otl->lookup_type == gpos_cursive )
fprintf( out, "cursive " );
else if ( otl->lookup_type == gpos_mark2mark )
fprintf( out, "mark " );
}
dump_glyphbyname(out,sf,start);
putc('\'',out);
if ( otl!=NULL ) {
if ( otl->ticked ) {
fprintf(out, "lookup %s ", lookupname(otl) );
uses_lookups = true;
} else if ( otl->lookup_type==gpos_single ) {
pst = pst_from_single_lookup(sf, otl, start);
if ( pst!=NULL )
dump_valuerecord(out,&pst->u.pos);
} else if ( otl->lookup_type==gpos_pair ) {
if ( pos==1 ) {
ch2 = *last_end; *last_end = '\0';
pst = pst_from_pos_pair_lookup(sf,otl,last_start,start,&space);
*last_end = ch2;
} else {
char *next_start, *next_end;
next_start = pt;
while ( *next_start==' ' ) ++next_start;
if ( *next_start!='\0' ) {
for ( next_end=next_start; *next_end!=' ' && *next_end!='\0'; ++next_end );
ch2 = *next_end; *next_end = '\0';
pst = pst_from_pos_pair_lookup(sf,otl,start,next_start,&space);
*next_end = ch2;
} else
pst = NULL;
}
if ( pst!=NULL )
dump_valuerecord(out,&pst->u.pair.vr[pos]);
} else if ( otl->lookup_type==gpos_cursive ) {
AnchorPoint *entry, *exit;
entry = apfind_entry_exit(sf,otl,start,&exit);
dump_anchorpoint(out,entry);
putc(' ',out);
dump_anchorpoint(out,exit);
putc(' ',out);
} else if ( otl->lookup_type>gpos_start && otl->ticked )
fprintf(out, "<lookup %s> ", lookupname(otl) );
}
last_start = start; last_end = pt;
*pt = ch;
putc(' ',out);
}
if ( r->u.glyph.fore!=NULL ) {
dump_glyphnamelist(out,sf,r->u.glyph.fore );
putc(' ',out);
}
if ( r->lookup_cnt!=0 && sub->lookup->lookup_type<gpos_start && !uses_lookups ) {
fprintf( out, " by " );
for ( i=0; i<r->lookup_cnt; ++i ) {
otl = r->lookups[i].lookup;
for ( pt=r->u.glyph.names, j=0; ; j++ ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' || j>=r->lookups[i].seq )
break;
while ( *pt!=' ' && *pt!='\0' ) ++pt;
}
if ( otl->lookup_type == gsub_single ) {
pst = pst_from_single_lookup(sf,otl,pt);
if ( pst!=NULL )
dump_glyphbyname(out,sf,pst->u.subs.variant );
} else if ( otl->lookup_type==gsub_ligature ) {
pst = pst_from_ligature(sf,otl,pt);
if ( pst!=NULL )
dump_glyphname(out,pst->u.lig.lig);
putc( ' ',out );
} else if ( otl->lookup_type<gpos_start && otl->ticked )
fprintf(out, "<lookup %s> ", lookupname(otl) );
}
}
putc(';',out); putc('\n',out);
}
static void dump_contextpstcoverage(FILE *out,SplineFont *sf,
struct lookup_subtable *sub, struct fpst_rule *r, int in_ignore) {
int i, pos, index;
OTLookup *otl;
PST *pst, space;
char *start, *pt, *last_end;
int ch, ch2, uses_lookups=false;
/* bcovers glyph classes are in reverse order, but they should be in */
/* natural order in the feature file, so we dump them in the reverse */
/* order */
for ( i=r->u.coverage.bcnt-1; i>=0; --i ) {
putc('[',out);
dump_glyphnamelist(out,sf,r->u.coverage.bcovers[i] );
fprintf( out, "] ");
}
for ( i=0; i<r->u.coverage.ncnt; ++i ) {
putc('[',out);
dump_glyphnamelist(out,sf,r->u.coverage.ncovers[i] );
putc(']',out);
if ( in_ignore )
putc('\'',out);
else {
otl = lookup_in_rule(r,i,&index, &pos);
putc( '\'', out );
if ( otl!=NULL ) {
/* Ok, I don't see any way to specify a class of value records */
/* so just assume all vr will be the same for the class */
pt = nameend_from_class(r->u.coverage.ncovers[i]);
ch = *pt; *pt = '\0';
if ( otl->ticked ) {
fprintf(out, "lookup %s ", lookupname(otl) );
uses_lookups = true;
} else if ( otl->lookup_type==gpos_single ) {
pst = pst_from_single_lookup(sf,otl,r->u.coverage.ncovers[i]);
if ( pst!=NULL )
dump_valuerecord(out,&pst->u.pos);
} else if ( otl->lookup_type==gpos_pair ) {
if ( pos==1 ) {
last_end = nameend_from_class(r->u.coverage.ncovers[i-1]);
ch2 = *last_end; *last_end = '\0';
pst = pst_from_pos_pair_lookup(sf,otl,r->u.coverage.ncovers[i-1],r->u.coverage.ncovers[i],&space);
*last_end = ch2;
} else if ( i+1<r->u.coverage.ncnt ) {
char *next_end;
next_end = nameend_from_class(r->u.coverage.ncovers[i+1]);
ch2 = *next_end; *next_end = '\0';
pst = pst_from_pos_pair_lookup(sf,otl,r->u.coverage.ncovers[i],r->u.coverage.ncovers[i+1],&space);
*next_end = ch2;
} else
pst = NULL;
if ( pst!=NULL )
dump_valuerecord(out,&pst->u.pair.vr[pos]);
}
*pt = ch;
}
}
putc(' ',out);
}
for ( i=0; i<r->u.coverage.fcnt; ++i ) {
putc('[',out);
dump_glyphnamelist(out,sf,r->u.coverage.fcovers[i] );
fprintf( out, "] ");
}
if ( sub->lookup->lookup_type == gsub_reversecchain ) {
fprintf( out, " by " );
if ( strchr(r->u.rcoverage.replacements,' ')==NULL )
dump_glyphnamelist(out,sf,r->u.rcoverage.replacements);
else {
putc('[',out);
dump_glyphnamelist(out,sf,r->u.rcoverage.replacements);
putc(']',out);
}
} else if ( r->lookup_cnt!=0 && sub->lookup->lookup_type<gpos_start && !uses_lookups ) {
fprintf( out, " by " );
for ( i=0; i<r->lookup_cnt; ++i ) {
int len;
otl = r->lookups[i].lookup;
if ( otl->lookup_type==gsub_single ) {
len = 40;
putc('[',out);
for ( pt=r->u.coverage.ncovers[r->lookups[i].seq]; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( start=pt; *pt!=' ' && *pt!='\0'; ++pt );
ch = *pt; *pt = '\0';
pst = pst_from_single_lookup(sf,otl,start);
if ( len+(pst==NULL?4:strlen(pst->u.subs.variant))>80 ) {
fprintf(out,"\n\t" );
len = 8;
}
if ( pst!=NULL ) {
dump_glyphbyname(out,sf,pst->u.subs.variant);
len += strlen(pst->u.subs.variant);
} else {
fprintf( out, "NULL" );
len += 4;
}
*pt = ch;
putc(' ',out);
++len;
}
putc(']',out);
} else if ( otl->lookup_type==gsub_ligature ) {
/* If we get here assume there is only one ligature */
/* or there is only one combination of input glyphs (all coverage tables contain one glyph) */
if ( sub->fpst->effectively_by_glyphs ) {
/* Build up THE combination of input glyphs */
int len, n;
char *start;
for ( n=len=0; n<r->u.coverage.ncnt; ++n )
len += strlen(r->u.coverage.ncovers[n])+1;
start = malloc(len+1);
for ( n=len=0; n<r->u.coverage.ncnt; ++n ) {
strcpy(start+len,r->u.coverage.ncovers[n]);
len += strlen(r->u.coverage.ncovers[n]);
if ( start[len-1]!=' ' )
start[len++] = ' ';
}
if ( len!=0 )
start[len-1] = '\0';
pst = pst_from_ligature(sf,otl,start);
free( start );
} else
pst = pst_any_from_otl(sf,otl);
if ( pst!=NULL )
dump_glyphname(out,pst->u.lig.lig);
}
}
}
putc(';',out); putc('\n',out);
}
static int ClassUsed(FPST *fpst, int which, int class_num) {
int i,j;
for ( j=0; j<fpst->rule_cnt; ++j ) {
struct fpst_rule *r = &fpst->rules[j];
int cnt = which==0? r->u.class.ncnt : which==1 ? r->u.class.bcnt : r->u.class.fcnt;
uint16 *checkme = which==0? r->u.class.nclasses : which==1 ? r->u.class.bclasses : r->u.class.fclasses;
for ( i=0; i<cnt; ++i )
if ( checkme[i] == class_num )
return( true );
}
return( false );
}
static void dump_contextpstclass(FILE *out,SplineFont *sf,
struct lookup_subtable *sub, struct fpst_rule *r, int in_ignore) {
FPST *fpst = sub->fpst;
int i, pos, index;
OTLookup *otl;
PST *pst, space;
char *start, *pt, *last_start, *last_end;
int ch, ch2, uses_lookups=false;
for ( i=0; i<r->u.class.bcnt; ++i )
fprintf( out, "@cc%d_back_%d ", sub->subtable_offset, r->u.class.bclasses[i] );
for ( i=0; i<r->u.class.ncnt; ++i ) {
if ( i==0 && fpst->nclass[r->u.class.nclasses[i]]==NULL )
continue;
fprintf( out, "@cc%d_match_%d", sub->subtable_offset, r->u.class.nclasses[i] );
if ( in_ignore )
putc( '\'',out );
else {
otl = lookup_in_rule(r,i,&index, &pos);
putc( '\'', out );
if ( otl!=NULL ) {
/* Ok, I don't see any way to specify a class of value records */
/* so just assume all vr will be the same for the class */
start = fpst->nclass[r->u.class.nclasses[i]];
pt = nameend_from_class(start);
ch = *pt; *pt = '\0';
if ( otl->ticked ) {
fprintf(out, "lookup %s ", lookupname(otl) );
uses_lookups = true;
} else if ( otl->lookup_type==gpos_single ) {
pst = pst_from_single_lookup(sf,otl,start);
if ( pst!=NULL )
dump_valuerecord(out,&pst->u.pos);
} else if ( otl->lookup_type==gpos_pair ) {
if ( pos==1 ) {
last_start = fpst->nclass[r->u.class.nclasses[i-1]];
last_end = nameend_from_class(last_start);
ch2 = *last_end; *last_end = '\0';
pst = pst_from_pos_pair_lookup(sf,otl,last_start,start,&space);
*last_end = ch2;
} else if ( i+1<r->u.coverage.ncnt ) {
char *next_start, *next_end;
next_start = fpst->nclass[r->u.class.nclasses[i+1]];
next_end = nameend_from_class(next_start);
ch2 = *next_end; *next_end = '\0';
pst = pst_from_pos_pair_lookup(sf,otl,start,next_start,&space);
*next_end = ch2;
} else
pst = NULL;
if ( pst!=NULL )
dump_valuerecord(out,&pst->u.pair.vr[pos]);
}
*pt = ch;
}
}
putc(' ',out);
}
for ( i=0; i<r->u.class.fcnt; ++i )
fprintf( out, "@cc%d_ahead_%d ", sub->subtable_offset, r->u.class.fclasses[i] );
if ( r->lookup_cnt!=0 && sub->lookup->lookup_type<gpos_start && !uses_lookups ) {
fprintf( out, " by " );
for ( i=0; i<r->lookup_cnt; ++i ) {
otl = r->lookups[i].lookup;
if ( otl->lookup_type==gsub_single ) {
putc('[',out);
if ( fpst->nclass[r->u.class.nclasses[r->lookups[i].seq]]!=NULL ) {
for ( pt=fpst->nclass[r->u.class.nclasses[r->lookups[i].seq]]; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( start=pt; *pt!=' ' && *pt!='\0'; ++pt );
ch = *pt; *pt = '\0';
pst = pst_from_single_lookup(sf,otl,start);
if ( pst!=NULL )
dump_glyphbyname(out,sf,pst->u.subs.variant);
else
fprintf( out, "NULL" );
*pt = ch;
putc(' ',out);
}
} else
dump_fpst_everythingelse(out,sf,fpst->nclass,fpst->nccnt,otl);
putc(']',out);
} else if ( otl->lookup_type==gsub_ligature ) {
/* I don't think it is possible to do this correctly */
/* adobe doesn't say how to order glyphs for [f longs]' [i l]' */
/* so just assume there is only one ligature */
pst = pst_any_from_otl(sf,otl);
if ( pst!=NULL )
dump_glyphname(out,pst->u.lig.lig);
}
putc( ' ',out );
}
}
putc( ';',out ); putc( '\n',out );
}
static void dump_contextpst(FILE *out,SplineFont *sf,struct lookup_subtable *sub) {
FPST *fpst = sub->fpst;
int i,j,k;
if ( fpst->format==pst_reversecoverage ) {
IError( "I can find no documentation on how to describe reverse context chaining lookups. Lookup %s will be skipped.",
sub->lookup->lookup_name );
return;
}
if ( fpst->format==pst_class ) {
if ( fpst->nclass!=NULL ) {
for ( i=0; i<fpst->nccnt; ++i ) {
if ( fpst->nclass[i]!=NULL || (i==0 && ClassUsed(fpst,0,0))) {
fprintf( out, " @cc%d_match_%d = [", sub->subtable_offset, i );
if ( fpst->nclass[i]!=NULL )
dump_glyphnamelist(out,sf,fpst->nclass[i] );
else
dump_fpst_everythingelse(out,sf,fpst->nclass,fpst->nccnt,NULL);
fprintf( out, "];\n" );
}
}
}
if ( fpst->bclass!=NULL ) {
for ( i=0; i<fpst->bccnt; ++i ) {
if ( fpst->bclass[i]!=NULL || (i==0 && ClassUsed(fpst,1,0))) {
fprintf( out, " @cc%d_back_%d = [", sub->subtable_offset, i );
if ( fpst->bclass[i]!=NULL )
dump_glyphnamelist(out,sf,fpst->bclass[i] );
else
dump_fpst_everythingelse(out,sf,fpst->bclass,fpst->bccnt,NULL);
fprintf( out, "];\n" );
}
}
}
if ( fpst->fclass!=NULL ) {
for ( i=0; i<fpst->fccnt; ++i ) {
if ( fpst->fclass[i]!=NULL || (i==0 && ClassUsed(fpst,2,0))) {
fprintf( out, " @cc%d_ahead_%d = [", sub->subtable_offset, i );
if ( fpst->fclass[i]!=NULL )
dump_glyphnamelist(out,sf,fpst->fclass[i] );
else
dump_fpst_everythingelse(out,sf,fpst->fclass,fpst->fccnt,NULL);
fprintf( out, "];\n" );
}
}
}
}
for ( j=0; j<fpst->rule_cnt; ++j ) {
struct fpst_rule *r = &fpst->rules[j];
for ( i=0; i<r->lookup_cnt; ++i ) for ( k=i+1; k<r->lookup_cnt; ++k ) {
if ( r->lookups[i].seq > r->lookups[k].seq ) {
int s = r->lookups[i].seq;
OTLookup *otl = r->lookups[i].lookup;
r->lookups[i].seq = r->lookups[k].seq;
r->lookups[k].seq = s;
r->lookups[i].lookup = r->lookups[k].lookup;
r->lookups[k].lookup = otl;
}
}
if ( sub->lookup->lookup_type>=gpos_start )
fprintf( out, r->lookup_cnt==0 ? " ignore pos " : " pos " );
else if ( sub->lookup->lookup_type==gsub_reversecchain )
fprintf( out, r->lookup_cnt==0 ? " ignore reversesub " : " reversesub " );
else
fprintf( out, r->lookup_cnt==0 ? " ignore sub " : " sub " );
if ( fpst->format==pst_class ) {
dump_contextpstclass(out,sf,sub,r,r->lookup_cnt==0);
} else if ( fpst->format==pst_glyphs ) {
dump_contextpstglyphs(out,sf,sub,r);
} else { /* And reverse coverage */
dump_contextpstcoverage(out,sf,sub,r,r->lookup_cnt==0);
}
}
}
static int HasBaseAP(SplineChar *sc,struct lookup_subtable *sub) {
AnchorPoint *ap;
for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) {
if ( ap->anchor->subtable==sub && ap->type!=at_mark )
return( true );
}
return( false );
}
static int SameBaseAP(SplineChar *sc1,SplineChar *sc2,struct lookup_subtable *sub) {
AnchorPoint *ap1, *ap2;
for ( ap1=sc1->anchor; ap1!=NULL; ap1=ap1->next ) {
if ( ap1->anchor->subtable == sub &&
(ap1->type==at_basechar || ap1->type==at_basemark)) {
for ( ap2=sc2->anchor; ap2!=NULL ; ap2=ap2->next ) {
if ( ap1->anchor==ap2->anchor &&
(ap2->type==at_basechar || ap2->type==at_basemark)) {
if ( ap1->me.x!=ap2->me.x || ap1->me.y!=ap2->me.y ||
ap1->has_ttf_pt!=ap2->has_ttf_pt ||
(ap1->has_ttf_pt && ap1->ttf_pt_index != ap2->ttf_pt_index ))
return( false );
else
break;
}
}
return( ap2!=NULL );
}
}
return( false );
}
static void dump_anchors(FILE *out,SplineFont *sf,struct lookup_subtable *sub) {
int i, j, k;
SplineFont *_sf;
SplineChar *sc;
AnchorClass *ac;
AnchorPoint *ap, *ap_entry, *ap_exit;
for ( ac = sf->anchor; ac!=NULL; ac=ac->next ) if ( ac->subtable==sub ) {
if ( sub->lookup->lookup_type==gpos_cursive ) {
k=0;
do {
_sf = k<sf->subfontcnt ? sf->subfonts[k] : sf;
for ( i=0; i<_sf->glyphcnt; ++i ) if ( (sc=_sf->glyphs[i])!=NULL ) {
ap_entry = ap_exit = NULL;
for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) if ( ap->anchor==ac ) {
if ( ap->type == at_centry )
ap_entry = ap;
else if ( ap->type == at_cexit )
ap_exit = ap;
}
if ( ap_entry!=NULL || ap_exit!=NULL ) {
fprintf( out, " pos cursive " );
dump_glyphname(out,sc);
putc(' ',out);
dump_anchorpoint(out,ap_entry);
putc(' ',out);
dump_anchorpoint(out,ap_exit);
putc(';',out);
putc('\n',out);
}
}
++k;
} while ( k<sf->subfontcnt );
} else {
struct amarks { SplineChar *sc; AnchorPoint *ap; } *marks = NULL;
int cnt, max;
cnt = max = 0;
k=0;
/* Gather all the marks in this class */
do {
_sf = k<sf->subfontcnt ? sf->subfonts[k] : sf;
for ( i=0; i<_sf->glyphcnt; ++i ) if ( (sc=_sf->glyphs[i])!=NULL ) {
for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) {
if ( ap->anchor==ac && ap->type==at_mark ) {
if ( cnt>=max )
marks = realloc(marks,(max+=20)*sizeof(struct amarks));
marks[cnt].sc = sc;
marks[cnt].ap = ap;
sc->ticked = false;
++cnt;
break;
}
}
}
++k;
} while ( k<sf->subfontcnt );
if ( cnt==0 )
continue; /* No marks? Nothing to be done */
/* Now output the marks */
for ( k=0; k<cnt; ++k ) if ( !(sc = marks[k].sc)->ticked ) {
SplineChar *osc;
ap = marks[k].ap;
fprintf( out, " markClass [" );
for ( j=k; j<cnt; ++j ) if ( !(osc = marks[j].sc)->ticked ) {
AnchorPoint *other = marks[j].ap;
if ( other->me.x == ap->me.x && other->me.y == ap->me.y &&
(other->has_ttf_pt == ap->has_ttf_pt &&
(!ap->has_ttf_pt || ap->ttf_pt_index==other->ttf_pt_index))) {
dump_glyphname(out,osc);
putc(' ',out);
osc->ticked = true;
}
}
fprintf( out, "] " );
dump_anchorpoint(out,ap);
fprintf( out, " @" );
dump_ascii( out, ac->name );
fprintf( out, ";\n" );
}
/* When positioning, we dump out all of a base glyph's anchors */
/* for the sub-table at once rather than class by class */
free(marks);
}
}
if ( sub->lookup->lookup_type==gpos_cursive )
return;
k=0;
do {
_sf = k<sf->subfontcnt ? sf->subfonts[k] : sf;
for ( i=0; i<_sf->glyphcnt; ++i ) if ( (sc=_sf->glyphs[i])!=NULL ) {
sc->ticked = false;
}
++k;
} while ( k<sf->subfontcnt );
k=0;
do {
_sf = k<sf->subfontcnt ? sf->subfonts[k] : sf;
for ( i=0; i<_sf->glyphcnt; ++i ) {
if ( (sc=_sf->glyphs[i])!=NULL && !sc->ticked && HasBaseAP(sc,sub)) {
fprintf( out, " pos %s ",
sub->lookup->lookup_type==gpos_mark2base ? "base" :
sub->lookup->lookup_type==gpos_mark2mark ? "mark" :
"ligature" );
if ( sub->lookup->lookup_type!=gpos_mark2ligature ) {
putc('[',out);
for ( j=i; j<_sf->glyphcnt; ++j ) {
SplineChar *osc;
if ( (osc=_sf->glyphs[j])!=NULL && !osc->ticked && SameBaseAP(osc,sc,sub)) {
osc->ticked = true;
dump_glyphname(out,osc);
putc(' ',out);
}
}
fprintf(out, "] " );
} else {
dump_glyphname(out,sc);
putc(' ',out);
}
if ( sub->lookup->lookup_type!=gpos_mark2ligature ) {
int first = true;
for ( ap = sc->anchor; ap!=NULL; ap=ap->next ) if ( ap->anchor->subtable==sub && ap->type!=at_mark ) {
if ( !first )
fprintf(out,"\n\t");
first = false;
dump_anchorpoint(out,ap);
fprintf( out, " mark @");
dump_ascii(out, ap->anchor->name );
}
fprintf(out,";\n");
} else {
int li, anymore=true, any;
for ( li=0; anymore; ++li ) {
any = anymore = false;
for ( ap = sc->anchor; ap!=NULL; ap=ap->next ) if ( ap->anchor->subtable==sub ) {
if ( ap->lig_index>li )
anymore = true;
else if ( ap->lig_index==li ) {
if ( li!=0 && !any )
fprintf( out, "\n ligComponent\n " );
dump_anchorpoint(out,ap);
fprintf( out, " mark @");
dump_ascii(out, ap->anchor->name );
putc( ' ',out );
any = true;
}
}
if ( !any && anymore ) {
if ( li!=0 )
fprintf( out, "\n ligComponent\n " );
fprintf( out, "<anchor NULL>" ); /* In adobe's example no anchor class is given */
}
}
fprintf(out,";\n");
}
}
}
++k;
} while ( k<sf->subfontcnt );
}
static void number_subtables(SplineFont *sf) {
OTLookup *otl;
struct lookup_subtable *sub;
int isgpos, cnt;
cnt = 0;
for ( isgpos=0; isgpos<2; ++isgpos ) {
for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl = otl->next ) {
for ( sub = otl->subtables; sub!=NULL; sub=sub->next )
sub->subtable_offset = cnt++;
}
}
}
static int fea_bad_contextual_nestedlookup(SplineFont *sf,FPST *fpst, OTLookup *nested) {
int gid, k;
SplineFont *_sf;
SplineChar *sc;
PST *pst, *found;
/* non-cursive anchors can't currently be expressed in contextuals */
/* No longer true, but it's still easier to do them outside */
/* Nor can other contextuals */
/* Nor can alternate/multiple substitutions */
/* In v1.8, nor can contextuals with more than one nested lookup */
/* If we are class based or coverage table based (and interesting classes */
/* contain more than one glyph name) then we can't express single_pos, */
/* kerning, ligatures, cursive anchors (unless everything in the lookup */
/* results in the same transformation) */
switch ( nested->lookup_type ) {
case gsub_single:
return( false ); /* This is the one thing that can always be expressed */
case gsub_multiple:
case gsub_alternate:
case gsub_context:
case gsub_contextchain:
case gsub_reversecchain:
case gpos_mark2base:
case gpos_mark2ligature:
case gpos_mark2mark:
case gpos_contextchain:
case gpos_context:
return( true ); /* These can never be expressed */
case gpos_cursive:
case gpos_pair:
return( fpst->format!=pst_glyphs /* && !fpst->effectively_by_glyphs*/ );
case gsub_ligature:
case gpos_single:
if ( fpst->format==pst_glyphs || fpst->effectively_by_glyphs )
return( false );
/* One can conceive of a fraction lookup */
/* [one one.proportion] [slash fraction] [two two.lining] => onehalf */
/* where all inputs go to one output. That can be expressed */
/* One can also conceive of a positioning lookup (subscript->superscript) */
/* where all value records are the same */
found = NULL;
k=0;
do {
_sf = sf->subfontcnt==0 ? sf : sf->subfonts[k];
for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (sc = _sf->glyphs[gid])!=NULL ) {
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
if ( pst->subtable!=NULL && pst->subtable->lookup==nested ) {
if ( found==NULL ) {
found = pst;
break;
} else if ( nested->lookup_type==gsub_ligature )
return( true ); /* Different glyphs */
else if ( found->u.pos.xoff!=pst->u.pos.xoff ||
found->u.pos.yoff!=pst->u.pos.yoff ||
found->u.pos.h_adv_off!=pst->u.pos.h_adv_off ||
found->u.pos.v_adv_off!=pst->u.pos.v_adv_off )
return( true );
else
break;
}
}
}
++k;
} while ( k<sf->subfontcnt );
if ( found==NULL )
return( true );
return( false );
default:
return( true );
}
}
static void dump_lookup(FILE *out, SplineFont *sf, OTLookup *otl);
static void dump_needednestedlookups(FILE *out, SplineFont *sf, OTLookup *otl) {
struct lookup_subtable *sub;
int r, s, n;
/* So we cheat, and extend the fea format to allow us to specify a lookup */
/* in contextuals */
/* With 1.8 this is part of the spec, but done differently from what I used*/
/* to do */
for ( sub = otl->subtables; sub!=NULL; sub=sub->next ) {
FPST *fpst = sub->fpst;
fpst->effectively_by_glyphs = false;
if ( fpst->format==pst_coverage ) {
fpst->effectively_by_glyphs = true;
for ( r=0; r<fpst->rule_cnt; ++r ) {
for ( n=0; n<fpst->rules[r].u.coverage.ncnt; ++n ) {
if ( strchr(fpst->rules[r].u.coverage.ncovers[n],' ')!=NULL )
fpst->effectively_by_glyphs = false;
break;
}
}
}
for ( r=0; r<fpst->rule_cnt; ++r ) {
/* In 1.8 if a contextual lookup invokes more than one nested */
/* lookup, then all lookups must be specified with the lookup */
/* keyword */
if ( fpst->rules[r].lookup_cnt>1 ) {
for ( s=0; s<fpst->rules[r].lookup_cnt; ++s ) {
OTLookup *nested = fpst->rules[r].lookups[s].lookup;
if ( nested!=NULL && nested->features==NULL && !nested->ticked )
dump_lookup(out,sf,nested);
}
} else if ( fpst->rules[r].lookup_cnt==1 ) {
OTLookup *nested = fpst->rules[r].lookups[0].lookup;
if ( nested!=NULL && nested->features==NULL && !nested->ticked &&
/* The number of times the lookup is used is stored in "lookup_length" */
/* so any lookup used in two places will be output even if it could be */
/* expressed inline */
(nested->lookup_length>1 ||
fea_bad_contextual_nestedlookup(sf,fpst,nested)))
dump_lookup(out,sf,nested);
}
}
}
}
static void dump_lookup(FILE *out, SplineFont *sf, OTLookup *otl) {
struct lookup_subtable *sub;
static char *flagnames[] = { "RightToLeft", "IgnoreBaseGlyphs", "IgnoreLigatures", "IgnoreMarks", NULL };
int i, k, first;
SplineFont *_sf;
SplineChar *sc;
PST *pst;
int isv;
KernPair *kp;
if ( otl->lookup_type == morx_indic || otl->lookup_type==morx_context ||
otl->lookup_type==morx_insert || otl->lookup_type==kern_statemachine )
return; /* No support for apple "lookups" */
otl->ticked = true;
if ( otl->lookup_type==gsub_context || otl->lookup_type==gsub_contextchain ||
otl->lookup_type==gpos_context || otl->lookup_type==gpos_contextchain )
dump_needednestedlookups(out,sf,otl);
if ( sf->cidmaster ) sf = sf->cidmaster;
number_subtables(sf);
fprintf( out, "\nlookup %s {\n", lookupname(otl) );
if ( otl->lookup_flags==0 || (otl->lookup_flags&0xe0)!=0 )
fprintf( out, " lookupflag %d;\n", otl->lookup_flags );
else {
fprintf( out, " lookupflag" );
first = true;
for ( i=0; i<4; ++i ) if ( otl->lookup_flags&(1<<i)) {
if ( !first ) {
// putc(',',out); // The specification says to do this, but Adobe's software uses spaces.
} else
first = false;
fprintf( out, " %s", flagnames[i] );
}
if ( (otl->lookup_flags&0xff00)!=0 ) {
int index = (otl->lookup_flags>>8)&0xff;
if ( index<sf->mark_class_cnt ) {
// if ( !first )
// putc(',',out);
fprintf( out, " MarkAttachmentType @" );
dump_ascii( out, sf->mark_class_names[index]);
}
}
if ( otl->lookup_flags&pst_usemarkfilteringset ) {
int index = (otl->lookup_flags>>16)&0xffff;
if ( index<sf->mark_set_cnt ) {
// if ( !first )
// putc(',',out);
fprintf( out, " UseMarkFilteringSet @" );
dump_ascii( out, sf->mark_set_names[index]);
}
}
putc(';',out);
putc('\n',out);
}
for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
/* The `subtable` keyword is only supported in class kerning lookups. */
if ( sub!=otl->subtables && sub->kc!=NULL )
fprintf( out, " subtable;\n" );
if ( sub->kc!=NULL )
dump_kernclass(out,sf,sub);
else if ( sub->fpst!=NULL )
dump_contextpst(out,sf,sub);
else if ( sub->anchor_classes )
dump_anchors(out,sf,sub);
else {
k=0;
do {
_sf = k<sf->subfontcnt ? sf->subfonts[k] : sf;
for ( i=0; i<_sf->glyphcnt; ++i ) if ( (sc=_sf->glyphs[i])!=NULL ) {
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) if ( pst->subtable==sub ) {
switch ( otl->lookup_type ) {
case gsub_single:
case gsub_multiple:
fprintf( out, " sub " );
dump_glyphname(out,sc);
fprintf( out, " by " );
dump_glyphnamelist(out,sf,pst->u.subs.variant );
fprintf( out,";\n" );
break;
case gsub_alternate:
fprintf( out, " sub " );
dump_glyphname(out,sc);
fprintf( out, " from [" );
dump_glyphnamelist(out,sf,pst->u.alt.components );
fprintf( out,"];\n" );
break;
case gsub_ligature:
fprintf( out, " sub " );
dump_glyphnamelist(out,sf,pst->u.lig.components );
fprintf( out, " by " );
dump_glyphname(out,sc);
fprintf( out,";\n" );
break;
case gpos_single:
fprintf( out, " pos " );
dump_glyphname(out,sc);
putc(' ',out);
dump_valuerecord(out,&pst->u.pos);
fprintf( out,";\n" );
break;
case gpos_pair:
fprintf( out, " pos " );
dump_glyphname(out,sc);
putc(' ',out);
dump_valuerecord(out,&pst->u.pair.vr[0]);
putc(' ',out);
dump_glyphnamelist(out,sf,pst->u.pair.paired );
putc(' ',out);
dump_valuerecord(out,&pst->u.pair.vr[0]);
fprintf( out,";\n" );
break;
default:
/* Eh? How'd we get here? An anchor class with */
/* no anchors perhaps? */
break;
}
}
// We skip outputting these here if the SplineFont says to use native kerning.
if (sf->preferred_kerning != 1) for ( isv=0; isv<2; ++isv ) {
for ( kp=isv ? sc->vkerns : sc->kerns; kp!=NULL; kp=kp->next ) if ( kp->subtable==sub ) {
fprintf( out, " pos " );
dump_glyphname(out,sc);
putc(' ',out);
if ( kp->adjust!=NULL ) {
/* If we have a device table then we must use the long */
/* format */
if ( isv ) {
fprintf( out," < 0 0 0 %d <device NULL> <device NULL> <device NULL> ",
kp->off );
dumpdevice(out,kp->adjust);
fprintf( out, " > " );
dump_glyphname(out,kp->sc);
fprintf( out, " < 0 0 0 0 >;\n" );
} else if ( otl->lookup_flags&pst_r2l ) {
fprintf( out, " < 0 0 0 0 > " );
dump_glyphname(out,kp->sc);
fprintf( out," < 0 0 %d 0 <device NULL> <device NULL> ",
kp->off );
dumpdevice(out,kp->adjust);
fprintf( out, " <device NULL>>;\n" );
} else {
fprintf( out," < 0 0 %d 0 <device NULL> <device NULL> ",
kp->off );
dumpdevice(out,kp->adjust);
fprintf( out, " <device NULL>> " );
dump_glyphname(out,kp->sc);
fprintf( out, " < 0 0 0 0 >;\n" );
}
} else
if ( otl->lookup_flags&pst_r2l ) {
fprintf( out, " < 0 0 0 0 > " );
dump_glyphname(out,kp->sc);
fprintf( out," < 0 0 %d 0 >;\n", kp->off );
} else {
dump_glyphname(out,kp->sc);
putc(' ',out);
fprintf( out, "%d;\n", kp->off );
}
}
} /* End isv/kp loops */
}
++k;
} while ( k<sf->subfontcnt );
}
} /* End subtables */
fprintf( out, "} %s;\n", lookupname(otl) );
}
static void note_nested_lookups_used_twice(OTLookup *base) {
OTLookup *otl;
struct lookup_subtable *sub;
int r,s;
for ( otl=base; otl!=NULL; otl=otl->next )
otl->lookup_length = 0;
for ( otl=base; otl!=NULL; otl=otl->next ) {
if ( otl->lookup_type==gsub_context || otl->lookup_type==gsub_contextchain ||
otl->lookup_type==gpos_context || otl->lookup_type==gpos_contextchain ) {
for ( sub = otl->subtables; sub!=NULL; sub=sub->next ) {
FPST *fpst = sub->fpst;
for ( r=0; r<fpst->rule_cnt; ++r ) {
for ( s=0; s<fpst->rules[r].lookup_cnt; ++s ) {
OTLookup *nested = fpst->rules[r].lookups[s].lookup;
++ nested->lookup_length;
}
}
}
}
}
}
static void untick_lookups(SplineFont *sf) {
OTLookup *otl;
int isgpos;
for ( isgpos=0; isgpos<2; ++isgpos )
for ( otl= isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next )
otl->ticked = false;
}
void FeatDumpOneLookup(FILE *out,SplineFont *sf, OTLookup *otl) {
FeatureScriptLangList *fl;
struct scriptlanglist *sl;
int l;
untick_lookups(sf);
gdef_markclasscheck(out,sf,otl);
dump_lookup(out,sf,otl);
for ( fl = otl->features; fl!=NULL; fl=fl->next ) {
fprintf( out, "\nfeature %c%c%c%c {\n", fl->featuretag>>24, fl->featuretag>>16, fl->featuretag>>8, fl->featuretag );
for ( sl = fl->scripts; sl!=NULL; sl=sl->next ) {
fprintf( out, " script %c%c%c%c;\n",
sl->script>>24, sl->script>>16, sl->script>>8, sl->script );
for ( l=0; l<sl->lang_cnt; ++l ) {
uint32 lang = l<MAX_LANG ? sl->langs[l] : sl->morelangs[l-MAX_LANG];
fprintf( out, " language %c%c%c%c %s;\n",
lang>>24, lang>>16, lang>>8, lang,
lang!=DEFAULT_LANG ? "exclude_dflt" : "" );
fprintf( out, " lookup %s;\n", lookupname(otl));
}
}
fprintf( out, "\n} %c%c%c%c;\n", fl->featuretag>>24, fl->featuretag>>16, fl->featuretag>>8, fl->featuretag );
}
}
static void dump_gdef(FILE *out,SplineFont *sf) {
PST *pst;
int i,gid,j,k,l, lcnt, needsclasses, hasclass[4], clsidx;
SplineChar *sc;
SplineFont *_sf;
struct lglyphs { SplineChar *sc; PST *pst; } *glyphs;
static char *clsnames[] = { "@GDEF_Simple", "@GDEF_Ligature", "@GDEF_Mark", "@GDEF_Component" };
glyphs = NULL;
needsclasses = false;
memset(hasclass,0,sizeof(hasclass));
for ( l=0; l<2; ++l ) {
lcnt = 0;
k=0;
do {
_sf = sf->subfontcnt==0 ? sf : sf->subfonts[k];
for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (sc=_sf->glyphs[gid])!=NULL ) {
if ( l==0 ) {
clsidx = sc->glyph_class!=0 ? sc->glyph_class: gdefclass(sc)+1;
if (clsidx > 1) needsclasses = hasclass[clsidx-2] = true;
}
for ( pst=sc->possub; pst!=NULL; pst=pst->next ) {
if ( pst->type == pst_lcaret ) {
for ( j=pst->u.lcaret.cnt-1; j>=0; --j )
if ( pst->u.lcaret.carets[j]!=0 )
break;
if ( j!=-1 )
break;
}
}
if ( pst!=NULL ) {
if ( glyphs!=NULL ) { glyphs[lcnt].sc = sc; glyphs[lcnt].pst = pst; }
++lcnt;
}
}
++k;
} while ( k<sf->subfontcnt );
if ( lcnt==0 )
break;
if ( glyphs!=NULL )
break;
glyphs = malloc((lcnt+1)*sizeof(struct lglyphs));
glyphs[lcnt].sc = NULL;
}
if ( !needsclasses && lcnt==0 && sf->mark_class_cnt==0 )
return; /* No anchor positioning, no ligature carets */
if ( sf->mark_class_cnt!=0 ) {
fprintf( out, "#Mark attachment classes (defined in GDEF, used in lookupflags)\n" );
for ( i=1; i<sf->mark_class_cnt; ++i ) {
putc('@',out);
dump_ascii( out, sf->mark_class_names[i]);
fprintf( out, " = [ %s ];\n", sf->mark_classes[i] );
}
}
if ( needsclasses ) {
int len;
putc('\n',out);
/* Class definitions must go outside the table itself. Stupid */
for ( i=0; i<4; ++i ) {
len = strlen(clsnames[i])+8;
k=0;
if ( hasclass[i] ) {
k = 0;
fprintf( out, "%s = [", clsnames[i] );
do {
_sf = sf->subfontcnt==0 ? sf : sf->subfonts[k];
for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (sc=_sf->glyphs[gid])!=NULL ) {
if ( sc->glyph_class==i+2 || (sc->glyph_class==0 && gdefclass(sc)==i+1 )) {
if ( len+strlen(sc->name)+1 >80 ) {
putc('\n',out); putc('\t',out);
len = 8;
}
dump_glyphname(out,sc);
putc(' ',out);
len += strlen(sc->name)+1;
}
}
++k;
} while ( k<sf->subfontcnt );
fprintf( out, "];\n");
}
}
}
fprintf( out, "\ntable GDEF {\n" );
if ( needsclasses ) {
/* AFDKO does't like empty classes, there should be just a placeholder */
fprintf( out, " GlyphClassDef %s, %s, %s, %s;\n\n",
hasclass[0]? clsnames[0]: "",
hasclass[1]? clsnames[1]: "",
hasclass[2]? clsnames[2]: "",
hasclass[3]? clsnames[3]: "");
}
for ( i=0; i<lcnt; ++i ) {
PST *pst = glyphs[i].pst;
fprintf( out, " LigatureCaretByPos " ); //by position
dump_glyphname(out,glyphs[i].sc);
for ( k=0; k<pst->u.lcaret.cnt; ++k ) {
fprintf( out, " %d", pst->u.lcaret.carets[k]); //output <caret %d>
}
fprintf( out, ";\n" );
}
free( glyphs );
/* no way to specify mark classes */
fprintf( out, "} GDEF;\n\n" );
}
static void dump_baseaxis(FILE *out,struct Base *axis,char *key) {
int i;
struct basescript *script;
if ( axis==NULL )
return;
fprintf( out, " %sAxis.BaseTagList", key );
for ( i=0; i<axis->baseline_cnt; ++i ) {
uint32 tag = axis->baseline_tags[i];
fprintf( out, " %c%c%c%c", tag>>24, tag>>16, tag>>8, tag );
}
fprintf( out, ";\n");
fprintf( out, " %sAxis.BaseScriptList\n", key );
for ( script=axis->scripts; script!=NULL; script=script->next ) {
uint32 scrtag = script->script;
uint32 tag = axis->baseline_tags[script->def_baseline];
fprintf( out, "\t%c%c%c%c", scrtag>>24, scrtag>>16, scrtag>>8, scrtag );
fprintf( out, " %c%c%c%c", tag>>24, tag>>16, tag>>8, tag );
for ( i=0; i<axis->baseline_cnt; ++i )
fprintf( out, " %d", script->baseline_pos[i]);
if ( script->next!=NULL )
fprintf( out, ",");
else
fprintf( out, ";");
fprintf( out, "\n" );
};
/* The spec for MinMax only allows for one script, and only one language */
/* in that script, and only one feature in the language. Presumably this*/
/* is an error in the spec, but since I can't guess what they intend */
/* and they provide no example, and it is unimplemented (so subject to */
/* change) I shan't support it either. */
}
static void dump_base(FILE *out,SplineFont *sf) {
if ( sf->horiz_base==NULL && sf->vert_base==NULL )
return;
fprintf( out, "table BASE {\n" );
dump_baseaxis(out,sf->horiz_base,"Horiz");
dump_baseaxis(out,sf->vert_base,"Vert");
fprintf( out, "} BASE;\n\n" );
}
static void UniOut(FILE *out,char *name ) {
int ch;
while ( (ch = utf8_ildb((const char **) &name))!=0 ) {
if ( ch<' ' || ch>='\177' || ch=='\\' || ch=='"' )
fprintf( out, "\\%04x", ch );
else
putc(ch,out);
}
}
static gboolean dump_header_languagesystem_hash_fe( gpointer key,
gpointer value,
gpointer user_data )
{
FILE *out = (FILE*)user_data;
fprintf( out, "languagesystem %s;\n", (char*)key );
return 0;
}
static gint tree_strcasecmp (gconstpointer a, gconstpointer b, gpointer user_data) {
(void)user_data;
return g_ascii_strcasecmp (a, b);
}
static void dump_header_languagesystem(FILE *out, SplineFont *sf) {
int isgpos;
int i,l,s, subl;
OTLookup *otl;
FeatureScriptLangList *fl;
struct scriptlanglist *sl;
int has_DFLT = 0;
GTree* ht = g_tree_new_full( tree_strcasecmp, 0, free, NULL );
for ( isgpos=0; isgpos<2; ++isgpos ) {
uint32 *feats = SFFeaturesInScriptLang(sf,isgpos,0xffffffff,0xffffffff);
if ( feats[0]!=0 ) {
uint32 *scripts = SFScriptsInLookups(sf,isgpos);
note_nested_lookups_used_twice(isgpos ? sf->gpos_lookups : sf->gsub_lookups);
for ( i=0; feats[i]!=0; ++i ) {
for ( s=0; scripts[s]!=0; ++s ) {
uint32 *langs = SFLangsInScript(sf,isgpos,scripts[s]);
for ( l=0; langs[l]!=0; ++l ) {
for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
for ( fl=otl->features; fl!=NULL; fl=fl->next ) if ( fl->featuretag==feats[i] ) {
for ( sl=fl->scripts; sl!=NULL; sl=sl->next ) if ( sl->script==scripts[s] ) {
for ( subl=0; subl<sl->lang_cnt; ++subl ) {
char key[100];
const uint32 DFLT_int = (uint32)'D' << 24 | (uint32)'F' << 16 |
(uint32)'L' << 8 | (uint32)'T';
const uint32 dflt_int = (uint32)'d' << 24 | (uint32)'f' << 16 |
(uint32)'l' << 8 | (uint32)'t';
if ((scripts[s] == DFLT_int) && (langs[l] == dflt_int)) {
has_DFLT = 1;
} else {
snprintf(key,sizeof key,"%c%c%c%c %c%c%c%c",
scripts[s]>>24, scripts[s]>>16, scripts[s]>>8, scripts[s],
langs[l]>>24, langs[l]>>16, langs[l]>>8, langs[l] );
g_tree_insert( ht, copy(key), "" );
}
}
}
}
}
}
}
}
}
}
if (has_DFLT) { dump_header_languagesystem_hash_fe((gpointer)"DFLT dflt", (gpointer)"", (gpointer)out); }
g_tree_foreach( ht, dump_header_languagesystem_hash_fe, out );
fprintf( out, "\n" );
}
static void dump_gsubgpos(FILE *out, SplineFont *sf) {
int isgpos;
int i,l,s, subl;
OTLookup *otl;
FeatureScriptLangList *fl;
struct scriptlanglist *sl;
struct otffeatname *fn;
struct otfname *on;
for ( isgpos=0; isgpos<2; ++isgpos ) {
uint32 *feats = SFFeaturesInScriptLang(sf,isgpos,0xffffffff,0xffffffff);
if ( feats[0]!=0 ) {
uint32 *scripts = SFScriptsInLookups(sf,isgpos);
fprintf( out, "\n# %s \n\n", isgpos ? "GPOS" : "GSUB" );
note_nested_lookups_used_twice(isgpos ? sf->gpos_lookups : sf->gsub_lookups);
for ( otl= isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next )
if ( otl->features!=NULL && !otl->unused ) /* Nested lookups will be output with the lookups which invoke them */
dump_lookup( out, sf, otl );
for ( i=0; feats[i]!=0; ++i ) {
fprintf( out, "\nfeature %c%c%c%c {\n", feats[i]>>24, feats[i]>>16, feats[i]>>8, feats[i] );
if ( feats[i]>=CHR('s','s','0','1') && feats[i]<=CHR('s','s','2','0') &&
(fn = findotffeatname(feats[i],sf))!=NULL ) {
fprintf( out, " featureNames {\n" );
for ( on = fn->names; on!=NULL; on=on->next ) {
fprintf( out, " name 3 1 0x%x \"", on->lang );
UniOut(out,on->name );
fprintf( out, "\";\n" );
}
fprintf( out, " };\n" );
}
if ( feats[i]==CHR('s','i','z','e') ) {
struct otfname *nm;
fprintf( out, " parameters %.1f", sf->design_size/10.0 );
if ( sf->fontstyle_id!=0 ) {
fprintf( out, " %d %.1f %.1f;\n",
sf->fontstyle_id,
sf->design_range_bottom/10.0, sf->design_range_top/10.0 );
for ( nm = sf->fontstyle_name; nm!=NULL; nm=nm->next ) {
char *pt;
fprintf( out, " sizemenuname 3 1 0x%x \"", nm->lang );
for ( pt = nm->name; ; ) {
int ch = utf8_ildb((const char **) &pt);
if ( ch=='\0' )
break;
if ( ch>=' ' && ch<=0x7f && ch!='"' && ch!='\\' )
putc(ch,out);
else
fprintf( out, "\\%04x", ch );
}
fprintf( out, "\";\n" );
}
} else
fprintf( out, " 0 0 0;\n" );
} else for ( s=0; scripts[s]!=0; ++s ) {
uint32 *langs = SFLangsInScript(sf,isgpos,scripts[s]);
int firsts = true;
for ( l=0; langs[l]!=0; ++l ) {
int first = true;
for ( otl = isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
for ( fl=otl->features; fl!=NULL; fl=fl->next ) if ( fl->featuretag==feats[i] ) {
for ( sl=fl->scripts; sl!=NULL; sl=sl->next ) if ( sl->script==scripts[s] ) {
for ( subl=0; subl<sl->lang_cnt; ++subl ) {
uint32 lang = subl<MAX_LANG ? sl->langs[subl] : sl->morelangs[subl-MAX_LANG];
if ( lang==langs[l] )
goto found;
}
}
}
found:
if ( fl!=NULL ) {
if ( firsts ) {
fprintf( out, "\n script %c%c%c%c;\n",
scripts[s]>>24, scripts[s]>>16, scripts[s]>>8, scripts[s] );
firsts = false;
}
if ( first ) {
fprintf( out, " language %c%c%c%c %s;\n",
langs[l]>>24, langs[l]>>16, langs[l]>>8, langs[l],
langs[l]!=DEFAULT_LANG ? "exclude_dflt" : "" );
first = false;
}
fprintf( out, " lookup %s;\n", lookupname(otl));
}
}
}
free(langs);
}
fprintf( out, "} %c%c%c%c;\n", feats[i]>>24, feats[i]>>16, feats[i]>>8, feats[i] );
}
free(scripts);
}
free(feats);
}
}
/* Sadly the lookup names I generate are often not distinguishable in the */
/* first 31 characters. So provide a fall-back mechanism if we get a name */
/* collision */
static void preparenames(SplineFont *sf) {
int isgpos, cnt, try, i;
OTLookup *otl;
char **names, *name;
char namebuf[MAXG+1], featbuf[8], scriptbuf[8], *feat, *script;
struct scriptlanglist *sl;
cnt = 0;
for ( isgpos=0; isgpos<2; ++isgpos )
for ( otl=isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next )
++cnt;
if ( cnt==0 )
return;
names = malloc(cnt*sizeof(char *));
featbuf[4] = scriptbuf[4] = 0;
cnt = 0;
for ( isgpos=0; isgpos<2; ++isgpos ) {
for ( otl=isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
name = lookupname(otl);
for ( try=0; ; ++try ) {
for ( i=0; i<cnt; ++i )
if ( strcmp(name,names[i])==0 )
break;
if ( i==cnt ) {
/* It's a unique name, use it */
otl->tempname = names[cnt++] = copy(name);
break;
}
feat = ""; script = "";
if ( otl->features!=NULL ) {
featbuf[0] = otl->features->featuretag>>24;
featbuf[1] = otl->features->featuretag>>16;
featbuf[2] = otl->features->featuretag>>8;
featbuf[3] = otl->features->featuretag&0xff;
feat = featbuf;
for ( sl=otl->features->scripts; sl!=NULL; sl=sl->next ) {
if ( sl->script!=DEFAULT_SCRIPT ) {
scriptbuf[0] = sl->script>>24;
scriptbuf[1] = sl->script>>16;
scriptbuf[2] = sl->script>>8;
scriptbuf[3] = sl->script&0xff;
script = scriptbuf;
break;
}
}
}
sprintf( namebuf, "%s_%s_%s%s_%d", isgpos ? "pos" : "sub",
otl->lookup_type== gsub_single ? "single" :
otl->lookup_type== gsub_multiple ? "mult" :
otl->lookup_type== gsub_alternate ? "alt" :
otl->lookup_type== gsub_ligature ? "ligature" :
otl->lookup_type== gsub_context ? "context" :
otl->lookup_type== gsub_contextchain ? "chain" :
otl->lookup_type== gsub_reversecchain ? "reversecc" :
otl->lookup_type== gpos_single ? "single" :
otl->lookup_type== gpos_pair ? "pair" :
otl->lookup_type== gpos_cursive ? "cursive" :
otl->lookup_type== gpos_mark2base ? "mark2base" :
otl->lookup_type== gpos_mark2ligature ? "mark2liga" :
otl->lookup_type== gpos_mark2mark ? "mark2mark" :
otl->lookup_type== gpos_context ? "context" :
otl->lookup_type== gpos_contextchain ? "chain" :
"unknown",
feat, script, try++ );
name = namebuf;
}
}
}
free(names);
}
static void cleanupnames(SplineFont *sf) {
int isgpos;
OTLookup *otl;
for ( isgpos=0; isgpos<2; ++isgpos )
for ( otl=isgpos ? sf->gpos_lookups : sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
free( otl->tempname );
otl->tempname = NULL;
}
}
void FeatDumpFontLookups(FILE *out,SplineFont *sf) {
if ( sf->cidmaster!=NULL ) sf=sf->cidmaster;
SFFindUnusedLookups(sf);
locale_t tmplocale; locale_t oldlocale; // Declare temporary locale storage.
switch_to_c_locale(&tmplocale, &oldlocale); // Switch to the C locale temporarily and cache the old locale.
untick_lookups(sf);
preparenames(sf);
gdef_markclasscheck(out,sf,NULL);
dump_header_languagesystem(out,sf);
dump_gsubgpos(out,sf);
dump_gdef(out,sf);
dump_base(out,sf);
cleanupnames(sf);
switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
}
/* ************************************************************************** */
/* ******************************* Parse feat ******************************* */
/* ************************************************************************** */
#include <gfile.h>
struct nameid {
uint16 strid;
uint16 platform, specific, language;
char *utf8_str;
struct nameid *next;
};
struct tablekeywords {
char *name;
int size; /* 1=>byte, 2=>short, 4=>int32 */
int cnt; /* normally 1, but 10 for panose, -1 for infinite */
int offset; /* -1 => parse but don't store */
};
#define TABLEKEYWORDS_EMPTY { NULL, 0, 0, 0 }
struct tablevalues {
int index; /* in the table key structure above */
int value;
uint8 panose_vals[10];
struct tablevalues *next;
};
enum feat_type { ft_lookup_start, ft_lookup_end, ft_feat_start, ft_feat_end,
ft_table, ft_names, ft_gdefclasses, ft_lcaret, ft_tablekeys,
ft_sizeparams,
ft_subtable, ft_script, ft_lang, ft_lookupflags, ft_langsys,
ft_pst, ft_pstclass, ft_fpst, ft_ap, ft_lookup_ref, ft_featname };
struct feat_item {
uint16 /* enum feat_type */ type;
uint8 ticked;
union {
SplineChar *sc; /* For psts, aps */
char *class; /* List of glyph names for kerning by class, lcarets */
char *lookup_name; /* for lookup_start/ lookup_ref */
uint32 tag; /* for feature/script/lang tag */
int *params; /* size params */
struct tablekeywords *offsets;
char **gdef_classes;
} u1;
union {
PST *pst;
/* For kerning by class we'll generate an invalid pst with the class as the "paired" field */
FPST *fpst;
AnchorPoint *ap;
int lookupflags; /* Low order: traditional flags, High order: markset index */
struct scriptlanglist *sl; /* Default langsyses for features/langsys */
int exclude_dflt; /* for lang tags */
struct nameid *names; /* size params */
struct tablevalues *tvals;
int16 *lcaret;
struct otffeatname *featnames;
} u2;
struct gpos_mark *mclass; /* v1.8 For mark to base-ligature-mark, names of all marks which attach to this anchor */
struct feat_item *next, *lookup_next;
};
static int strcmpD(const void *_str1, const void *_str2) {
const char *str1 = *(const char **)_str1, *str2 = *(const char **) _str2;
return( strcmp(str1,str2));
}
/* Order glyph classes just so we can do a simple string compare to check for */
/* class match. So the order doesn't really matter, just so it is consistent */
static char *fea_canonicalClassOrder(char *class) {
int name_cnt, i;
char *pt, **names, *cpt;
char *temp = copy(class);
name_cnt = 0;
for ( pt = class; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( ; *pt!=' ' && *pt!='\0'; ++pt );
++name_cnt;
}
names = malloc(name_cnt*sizeof(char *));
name_cnt = 0;
for ( pt = temp; ; ) {
while ( *pt==' ' ) ++pt;
if ( *pt=='\0' )
break;
for ( names[name_cnt++]=pt ; *pt!=' ' && *pt!='\0'; ++pt );
if ( *pt==' ' )
*pt++ = '\0';
}
qsort(names,name_cnt,sizeof(char *),strcmpD);
cpt = class;
for ( i=0; i<name_cnt; ++i ) {
strcpy(cpt,names[i]);
cpt += strlen(cpt);
*cpt++ = ' ';
}
if ( name_cnt!=0 )
cpt[-1] = '\0';
free(names);
free(temp);
return( class );
}
#ifdef FF_UTHASH_GLIF_NAMES
#include "glif_name_hash.h"
#endif
static int fea_classesIntersect(char *class1, char *class2) {
char *pt1, *start1, *pt2, *start2;
int ch1, ch2;
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _glif_name_hash;
struct glif_name_index * glif_name_hash = &_glif_name_hash; // Open the hash table.
memset(glif_name_hash, 0, sizeof(struct glif_name_index));
long int index = 0;
long int break_point = 0;
int output = 0;
if (class1[0] == '\0' || class2[0] == '\0') return 0; // We cancel further action if one list is blank.
// Parse the first input.
for ( pt1=class1 ; output == 0 && pt1[0] != '\0'; ) {
while ( *pt1==' ' ) ++pt1;
for ( start1 = pt1; *pt1!=' ' && *pt1!='\0'; ++pt1 );
ch1 = *pt1; *pt1 = '\0'; // Cache the byte and terminate.
// We do not want to add the same name twice. It breaks the hash.
if (glif_name_search_glif_name(glif_name_hash, start1) == NULL) {
glif_name_track_new(glif_name_hash, index++, start1);
}
*pt1 = ch1; // Restore the byte.
}
break_point = index; // Divide the entries from the two sources by index.
// Parse the second input.
for ( pt2=class2 ; output == 0 && pt2[0] != '\0'; ) {
while ( *pt2==' ' ) ++pt2;
for ( start2 = pt2; *pt2!=' ' && *pt2!='\0'; ++pt2 );
ch2 = *pt2; *pt2 = '\0'; // Cache the byte and terminate.
struct glif_name * tmp = NULL;
if ((tmp = glif_name_search_glif_name(glif_name_hash, start2)) == NULL) {
glif_name_track_new(glif_name_hash, index++, start2);
} else if (tmp->gid < break_point) {
output = 1;
}
*pt2 = ch2; // Restore the byte.
}
glif_name_hash_destroy(glif_name_hash); // Close the hash table.
if (output == 1) return 1;
return 0;
#else
for ( pt1=class1 ; ; ) {
while ( *pt1==' ' ) ++pt1;
if ( *pt1=='\0' )
return( 0 );
for ( start1 = pt1; *pt1!=' ' && *pt1!='\0'; ++pt1 );
ch1 = *pt1; *pt1 = '\0';
for ( pt2=class2 ; ; ) {
while ( *pt2==' ' ) ++pt2;
if ( *pt2=='\0' )
break;
for ( start2 = pt2; *pt2!=' ' && *pt2!='\0'; ++pt2 );
ch2 = *pt2; *pt2 = '\0';
if ( strcmp(start1,start2)==0 ) {
*pt2 = ch2; *pt1 = ch1;
return( 1 );
}
*pt2 = ch2;
}
*pt1 = ch1;
}
#endif
}
#define SKIP_SPACES(s, i) \
do { \
while ((s)[i] == ' ') \
i++; \
} \
while (0)
#define FIND_SPACE(s, i) \
do { \
while ((s)[i] != ' ' && (s)[i] != '\0') \
i++; \
} \
while (0)
static char *fea_classesSplit(char *class1, char *class2) {
char *intersection;
int len = strlen(class1), len2 = strlen(class2);
int ix;
int i, j, i_end, j_end;
int length;
int match_found;
if ( len2>len ) len = len2;
intersection = malloc(len+1);
ix = 0;
i = 0;
SKIP_SPACES(class1, i);
while (class1[i] != '\0') {
i_end = i;
FIND_SPACE(class1, i_end);
length = i_end - i;
match_found = 0;
j = 0;
SKIP_SPACES(class2, j);
while (!match_found && class2[j] != '\0') {
j_end = j;
FIND_SPACE(class2, j_end);
if (length == j_end - j && strncmp(class1 + i, class2 + j, length) == 0) {
match_found = 1;
if (ix != 0) {
intersection[ix] = ' ';
ix++;
}
memcpy(intersection + ix, class1 + i, length * sizeof (char));
ix += length;
SKIP_SPACES(class1, i_end);
memmove(class1 + i, class1 + i_end, (strlen(class1 + i_end) + 1) * sizeof (char));
SKIP_SPACES(class2, j_end);
memmove(class2 + j, class2 + j_end, (strlen(class2 + j_end) + 1) * sizeof (char));
} else {
j = j_end;
SKIP_SPACES(class2, j);
}
}
if (!match_found) {
i = i_end;
SKIP_SPACES(class1, i);
}
}
intersection[ix] = '\0';
return( intersection );
}
enum toktype { tk_name, tk_class, tk_int, tk_char, tk_cid, tk_eof,
/* keywords */
tk_firstkey,
tk_anchor=tk_firstkey, tk_anonymous, tk_by, tk_caret, tk_cursive, tk_device,
tk_enumerate, tk_excludeDFLT, tk_exclude_dflt, tk_feature, tk_from,
tk_ignore, tk_ignoreDFLT, tk_ignoredflt, tk_IgnoreBaseGlyphs,
tk_IgnoreLigatures, tk_IgnoreMarks, tk_include, tk_includeDFLT,
tk_include_dflt, tk_language, tk_languagesystem, tk_lookup,
tk_lookupflag, tk_mark, tk_nameid, tk_NULL, tk_parameters, tk_position,
tk_required, tk_RightToLeft, tk_script, tk_substitute, tk_subtable,
tk_table, tk_useExtension,
/* Additional keywords in the 2008 draft */
tk_anchorDef, tk_valueRecordDef, tk_contourpoint,
tk_MarkAttachmentType, tk_UseMarkFilteringSet,
tk_markClass, tk_reversesub, tk_base, tk_ligature, tk_ligComponent,
tk_featureNames
};
struct glyphclasses {
char *classname, *glyphs;
struct glyphclasses *next;
};
struct namedanchor {
char *name;
AnchorPoint *ap;
struct namedanchor *next;
};
struct namedvalue {
char *name;
struct vr *vr;
struct namedvalue *next;
};
struct gdef_mark { char *name; int index; char *glyphs; };
/* GPOS mark classes may have multiple definitions each added a glyph
* class and anchor, these are linked under "same" */
struct gpos_mark {
char *name;
char *glyphs;
AnchorPoint *ap;
struct gpos_mark *same, *next;
int name_used; /* Same "markClass" can be used in any mark type lookup, or indeed in multiple lookups of the same type */
} *gpos_mark;
#define MAXT 80
#define MAXI 5
struct parseState {
char tokbuf[MAXT+1];
long value;
enum toktype type;
uint32 tag;
int could_be_tag;
FILE *inlist[MAXI];
int inc_depth;
int line[MAXI];
char *filename[MAXI];
int err_count;
unsigned int warned_about_not_cid: 1;
unsigned int lookup_in_sf_warned: 1;
unsigned int in_vkrn: 1;
unsigned int backedup: 1;
unsigned int skipping: 1;
SplineFont *sf;
struct scriptlanglist *def_langsyses;
struct glyphclasses *classes; // TODO: This eventually needs to merge with the SplineFont group storage. For now, it needs to copy from it at first invocation.
struct namedanchor *namedAnchors;
struct namedvalue *namedValueRs;
struct feat_item *sofar;
int base; /* normally numbers are base 10, but in the case of languages in stringids, they can be octal or hex */
OTLookup *created, *last; /* Ordered, but not sorted into GSUB, GPOS yet */
AnchorClass *accreated;
int gm_cnt[2], gm_max[2], gm_pos[2];
struct gdef_mark *gdef_mark[2];
struct gpos_mark *gpos_mark;
};
static struct keywords {
char *name;
enum toktype tok;
} fea_keywords[] = {
/* non-keyword tokens (must come first) */
{ "name", tk_name }, { "glyphclass", tk_class }, { "integer", tk_int },
{ "random character", tk_char}, { "cid", tk_cid }, { "EOF", tk_eof },
/* keywords now */
{ "anchor", tk_anchor },
{ "anonymous", tk_anonymous },
{ "by", tk_by },
{ "caret", tk_caret },
{ "cursive", tk_cursive },
{ "device", tk_device },
{ "enumerate", tk_enumerate },
{ "excludeDFLT", tk_excludeDFLT },
{ "exclude_dflt", tk_exclude_dflt },
{ "feature", tk_feature },
{ "from", tk_from },
{ "ignore", tk_ignore },
{ "IgnoreBaseGlyphs", tk_IgnoreBaseGlyphs },
{ "IgnoreLigatures", tk_IgnoreLigatures },
{ "IgnoreMarks", tk_IgnoreMarks },
{ "include", tk_include },
{ "includeDFLT", tk_includeDFLT },
{ "include_dflt", tk_include_dflt },
{ "language", tk_language },
{ "languagesystem", tk_languagesystem },
{ "lookup", tk_lookup },
{ "lookupflag", tk_lookupflag },
{ "mark", tk_mark },
{ "nameid", tk_nameid },
{ "NULL", tk_NULL },
{ "parameters", tk_parameters },
{ "position", tk_position },
{ "required", tk_required },
{ "RightToLeft", tk_RightToLeft },
{ "script", tk_script },
{ "substitute", tk_substitute },
{ "subtable", tk_subtable },
{ "table", tk_table },
{ "useExtension", tk_useExtension },
/* Additional keywords in the 2008 draft */
{ "anchorDef", tk_anchorDef },
{ "valueRecordDef", tk_valueRecordDef },
{ "contourpoint", tk_contourpoint },
{ "MarkAttachmentType", tk_MarkAttachmentType },
{ "UseMarkFilteringSet", tk_UseMarkFilteringSet },
{ "reversesub", tk_reversesub },
{ "markClass", tk_markClass },
{ "base", tk_base },
{ "ligature", tk_ligature },
{ "ligComponent", tk_ligComponent },
{ "featureNames", tk_featureNames },
/* synonyms */
{ "sub", tk_substitute },
{ "pos", tk_position },
{ "rsub", tk_reversesub },
{ "enum", tk_enumerate },
{ "anon", tk_anonymous },
{ NULL, 0 }
};
static struct tablekeywords hhead_keys[] = {
{ "CaretOffset", sizeof(short), 1, -1 }, /* Don't even know what this is! */
{ "Ascender", sizeof(short), 1, offsetof(struct pfminfo,hhead_ascent)+offsetof(SplineFont,pfminfo) },
{ "Descender", sizeof(short), 1, offsetof(struct pfminfo,hhead_descent)+offsetof(SplineFont,pfminfo) },
{ "LineGap", sizeof(short), 1, offsetof(struct pfminfo,linegap)+offsetof(SplineFont,pfminfo) },
TABLEKEYWORDS_EMPTY
};
static struct tablekeywords vhead_keys[] = {
{ "VertTypoAscender", sizeof(short), 1, -1 },
{ "VertTypoDescender", sizeof(short), 1, -1 },
{ "VertTypoLineGap", sizeof(short), 1, offsetof(struct pfminfo,vlinegap)+offsetof(SplineFont,pfminfo) },
TABLEKEYWORDS_EMPTY
};
static struct tablekeywords os2_keys[] = {
{ "FSType", sizeof(short), 1, offsetof(struct pfminfo,fstype)+offsetof(SplineFont,pfminfo) },
{ "Panose", sizeof(uint8), 10, offsetof(struct pfminfo,panose)+offsetof(SplineFont,pfminfo) },
{ "UnicodeRange", sizeof(short), -1, -1 },
{ "CodePageRange", sizeof(short), -1, -1 },
{ "TypoAscender", sizeof(short), 1, offsetof(struct pfminfo,os2_typoascent)+offsetof(SplineFont,pfminfo) },
{ "TypoDescender", sizeof(short), 1, offsetof(struct pfminfo,os2_typodescent)+offsetof(SplineFont,pfminfo) },
{ "TypoLineGap", sizeof(short), 1, offsetof(struct pfminfo,os2_typolinegap)+offsetof(SplineFont,pfminfo) },
{ "winAscent", sizeof(short), 1, offsetof(struct pfminfo,os2_winascent)+offsetof(SplineFont,pfminfo) },
{ "winDescent", sizeof(short), 1, offsetof(struct pfminfo,os2_windescent)+offsetof(SplineFont,pfminfo) },
{ "XHeight", sizeof(short), 1, -1 },
{ "CapHeight", sizeof(short), 1, -1 },
{ "WeightClass", sizeof(short), 1, offsetof(struct pfminfo,weight)+offsetof(SplineFont,pfminfo) },
{ "WidthClass", sizeof(short), 1, offsetof(struct pfminfo,width)+offsetof(SplineFont,pfminfo) },
{ "Vendor", sizeof(short), 1, offsetof(struct pfminfo,os2_vendor)+offsetof(SplineFont,pfminfo) },
TABLEKEYWORDS_EMPTY
};
static void fea_ParseTok(struct parseState *tok);
static void fea_handle_include(struct parseState *tok) {
FILE *in;
char namebuf[1025], *pt, *filename;
int ch;
fea_ParseTok(tok);
if ( tok->type!=tk_char || tok->tokbuf[0]!='(' ) {
LogError(_("Unparseable include on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return;
}
in = tok->inlist[tok->inc_depth];
ch = getc(in);
while ( isspace(ch))
ch = getc(in);
pt = namebuf;
while ( ch!=EOF && ch!=')' && pt<namebuf+sizeof(namebuf)-1 ) {
*pt++ = ch;
ch = getc(in);
}
if ( ch!=EOF && ch!=')' ) {
while ( ch!=EOF && ch!=')' )
ch = getc(in);
LogError(_("Include filename too long on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
while ( pt>=namebuf+1 && isspace(pt[-1]) )
--pt;
*pt = '\0';
if ( ch!=')' ) {
if ( ch==EOF )
LogError(_("End of file in include on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
else
LogError(_("Missing close parenthesis in include on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return;
}
if ( pt==namebuf ) {
LogError(_("No filename specified in include on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return;
}
if ( tok->inc_depth>=MAXI-1 ) {
LogError(_("Includes nested too deeply on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return;
}
if ( *namebuf=='/' ||
( pt = strrchr(tok->filename[tok->inc_depth],'/') )==NULL )
filename=copy(namebuf);
else {
*pt = '\0';
filename = GFileAppendFile(tok->filename[tok->inc_depth],namebuf,false);
*pt = '/';
}
in = fopen(filename,"r");
if ( in==NULL ) {
LogError(_("Could not open include file (%s) on line %d of %s"),
filename, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
free(filename);
return;
}
++tok->inc_depth;
tok->filename[tok->inc_depth] = filename;
tok->inlist[tok->inc_depth] = in;
tok->line[tok->inc_depth] = 1;
fea_ParseTok(tok);
}
static void fea_ParseTokWithKeywords(struct parseState *tok, int do_keywords) {
FILE *in = tok->inlist[tok->inc_depth];
int ch, peekch;
char *pt, *start;
if ( tok->backedup ) {
tok->backedup = false;
return;
}
skip_whitespace:
ch = getc(in);
while ( isspace(ch) || ch=='#' ) {
if ( ch=='#' )
while ( (ch=getc(in))!=EOF && ch!='\n' && ch!='\r' );
if ( ch=='\n' || ch=='\r' ) {
if ( ch=='\r' ) {
ch = getc(in);
if ( ch!='\n' )
ungetc(ch,in);
}
++tok->line[tok->inc_depth];
}
ch = getc(in);
}
tok->could_be_tag = 0;
if ( ch==EOF ) {
if ( tok->inc_depth>0 ) {
fclose(tok->inlist[tok->inc_depth]);
free(tok->filename[tok->inc_depth]);
in = tok->inlist[--tok->inc_depth];
goto skip_whitespace;
}
tok->type = tk_eof;
strcpy(tok->tokbuf,"EOF");
return;
}
start = pt = tok->tokbuf;
if ( ch=='\\' || ch=='-' ) {
peekch=getc(in);
ungetc(peekch,in);
}
if ( isdigit(ch) || ch=='+' || ((ch=='-' || ch=='\\') && isdigit(peekch)) ) {
tok->type = tk_int;
if ( ch=='-' || ch=='+' ) {
if ( ch=='-' ) {
*pt++ = ch;
start = pt;
}
ch = getc(in);
} else if ( ch=='\\' ) {
ch = getc(in);
tok->type = tk_cid;
}
while ( (isdigit( ch ) ||
(tok->base==0 && (ch=='x' || ch=='X' || (ch>='a' && ch<='f') || (ch>='A' && ch<='F'))))
&& pt<tok->tokbuf+15 ) {
*pt++ = ch;
ch = getc(in);
}
if ( isdigit(ch)) {
LogError(_("Number too long on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
} else if ( pt==start ) {
LogError(_("Missing number on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
ungetc(ch,in);
*pt = '\0';
tok->value = strtol(tok->tokbuf,NULL,tok->base);
return;
} else if ( ch=='@' || ch=='_' || ch=='\\' || isalnum(ch) || ch=='.') { /* Most names can't start with dot */
int check_keywords = true;
tok->type = tk_name;
if ( ch=='@' ) {
tok->type = tk_class;
*pt++ = ch;
start = pt;
ch = getc(in);
check_keywords = false;
} else if ( ch=='\\' ) {
ch = getc(in);
check_keywords = false;
}
while ( isalnum(ch) || ch=='_' || ch=='.' ) {
if ( pt<tok->tokbuf+MAXT )
*pt++ = ch;
ch = getc(in);
}
*pt = '\0';
ungetc(ch,in);
// We are selective about names starting with a dot. .notdef and .null are acceptable.
if ((start[0] == '.') && (strcmp(start, ".notdef") != 0) && (strcmp(start, ".null") != 0)) {
if ( !tok->skipping ) {
LogError(_("Unexpected character (0x%02X) on line %d of %s"), start[0], tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
} else {
/* Adobe says glyphnames are 31 chars, but Mangal uses longer names */
if ( pt>start+MAXG ) {
LogError(_("Name, %s%s, too long on line %d of %s"),
tok->tokbuf, pt>=tok->tokbuf+MAXT?"...":"",
tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
if ( pt>=tok->tokbuf+MAXT )
++tok->err_count;
} else if ( pt==start ) {
LogError(_("Missing name on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
if ( check_keywords && do_keywords) {
int i;
for ( i=tk_firstkey; fea_keywords[i].name!=NULL; ++i ) {
if ( strcmp(fea_keywords[i].name,tok->tokbuf)==0 ) {
tok->type = fea_keywords[i].tok;
break;
}
}
if ( tok->type==tk_include )
fea_handle_include(tok);
}
if ( tok->type==tk_name && pt-tok->tokbuf<=4 && pt!=tok->tokbuf ) {
unsigned char tag[4];
tok->could_be_tag = true;
memset(tag,' ',4);
tag[0] = tok->tokbuf[0];
if ( tok->tokbuf[1]!='\0' ) {
tag[1] = tok->tokbuf[1];
if ( tok->tokbuf[2]!='\0' ) {
tag[2] = tok->tokbuf[2];
if ( tok->tokbuf[3]!='\0' )
tag[3] = tok->tokbuf[3];
}
}
tok->tag = (tag[0]<<24) | (tag[1]<<16) | (tag[2]<<8) | tag[3];
}
}
} else {
/* I've already handled the special characters # @ and \ */
/* so don't treat them as errors here, if they occur they will be out of context */
if ( ch==';' || ch==',' || ch=='-' || ch=='=' || ch=='\'' || ch=='"' ||
ch=='{' || ch=='}' ||
ch=='[' || ch==']' ||
ch=='<' || ch=='>' ||
ch=='(' || ch==')' ) {
tok->type = tk_char;
tok->tokbuf[0] = ch;
tok->tokbuf[1] = '\0';
} else {
if ( !tok->skipping ) {
LogError(_("Unexpected character (0x%02X) on line %d of %s"), ch, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
goto skip_whitespace;
}
}
}
static void fea_ParseTok(struct parseState *tok) {
fea_ParseTokWithKeywords(tok, true);
}
static void fea_ParseTag(struct parseState *tok) {
/* The tag used for OS/2 doesn't get parsed properly */
/* So if we know we are looking for a tag do some fixups */
fea_ParseTok(tok);
if ( tok->type==tk_name && tok->could_be_tag &&
tok->tag==CHR('O','S',' ',' ') ) {
FILE *in = tok->inlist[tok->inc_depth];
int ch;
ch = getc(in);
if ( ch=='/' ) {
ch = getc(in);
if ( ch=='2' ) {
tok->tag = CHR('O','S','/','2');
} else {
tok->tag = CHR('O','S','/',' ');
ungetc(ch,in);
}
} else
ungetc(ch,in);
}
if ( tok->type!=tk_name && tok->type!=tk_eof &&
strlen(tok->tokbuf)==4 && isalnum(tok->tokbuf[0])) {
tok->type = tk_name;
tok->could_be_tag = true;
tok->tag = CHR(tok->tokbuf[0], tok->tokbuf[1], tok->tokbuf[2], tok->tokbuf[3]);
}
}
static void fea_UnParseTok(struct parseState *tok) {
tok->backedup = true;
}
static int fea_ParseDeciPoints(struct parseState *tok) {
/* When parsing size features floating point numbers are allowed */
/* but they should be converted to ints by multiplying by 10 */
/* (not my convention) */
fea_ParseTok(tok);
if ( tok->type==tk_int ) {
FILE *in = tok->inlist[tok->inc_depth];
char *pt = tok->tokbuf + strlen(tok->tokbuf);
int ch;
ch = getc(in);
if ( ch=='.' ) {
*pt++ = ch;
while ( (ch = getc(in))!=EOF && isdigit(ch)) {
if ( pt<tok->tokbuf+sizeof(tok->tokbuf)-1 )
*pt++ = ch;
}
*pt = '\0';
tok->value = rint(strtod(tok->tokbuf,NULL)*10.0);
}
if ( ch!=EOF )
ungetc(ch,in);
} else {
LogError(_("Expected '%s' on line %d of %s"), fea_keywords[tk_int].name,
tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
tok->value = -1;
}
return( tok->value );
}
static void fea_TokenMustBe(struct parseState *tok, enum toktype type, int ch) {
int tk;
fea_ParseTok(tok);
if ( type==tk_char && (tok->type!=type || tok->tokbuf[0]!=ch) ) {
LogError(_("Expected '%c' on line %d of %s"), ch, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
} else if ( type!=tk_char && tok->type!=type ) {
for ( tk=0; fea_keywords[tk].name!=NULL; ++tk )
if ( fea_keywords[tk].tok==type )
break;
if ( fea_keywords[tk].name!=NULL )
LogError(_("Expected '%s' on line %d of %s"), fea_keywords[tk].name,
tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
else
LogError(_("Expected unknown token (internal error) on line %d of %s"),
tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
}
static void fea_skip_to_semi(struct parseState *tok) {
int nest=0;
while ( tok->type!=tk_char || tok->tokbuf[0]!=';' || nest>0 ) {
fea_ParseTok(tok);
if ( tok->type==tk_char ) {
if ( tok->tokbuf[0]=='{' ) ++nest;
else if ( tok->tokbuf[0]=='}' ) --nest;
if ( nest<0 )
break;
}
if ( tok->type==tk_eof )
break;
}
}
static void fea_skip_to_close_curly(struct parseState *tok) {
int nest=0;
tok->skipping = true;
/* The table blocks have slightly different syntaxes and can take strings */
/* and floating point numbers. So don't complain about unknown chars when */
/* in a table (that's skipping) */
while ( tok->type!=tk_char || tok->tokbuf[0]!='}' || nest>0 ) {
fea_ParseTok(tok);
if ( tok->type==tk_char ) {
if ( tok->tokbuf[0]=='{' ) ++nest;
else if ( tok->tokbuf[0]=='}' ) --nest;
}
if ( tok->type==tk_eof )
break;
}
tok->skipping = false;
}
static void fea_now_semi(struct parseState *tok) {
if ( tok->type!=tk_char || tok->tokbuf[0]!=';' ) {
LogError(_("Expected ';' at statement end on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
fea_skip_to_semi(tok);
++tok->err_count;
return;
}
}
static void fea_end_statement(struct parseState *tok) {
fea_ParseTok(tok);
fea_now_semi(tok);
}
static struct glyphclasses *fea_lookup_class(struct parseState *tok,char *classname) {
struct glyphclasses *test;
for ( test=tok->classes; test!=NULL; test=test->next ) {
if ( strcmp(classname,test->classname)==0 )
return( test );
}
return( NULL );
}
static char *fea_lookup_class_complain(struct parseState *tok,char *classname) {
struct glyphclasses *test;
struct gpos_mark *mtest;
for ( test=tok->classes; test!=NULL; test=test->next ) {
if ( strcmp(classname,test->classname)==0 )
return( copy( test->glyphs) );
}
/* Mark classes can also be used as normal classes */
for ( mtest=tok->gpos_mark; mtest!=NULL; mtest=mtest->next ) {
if ( strcmp(classname,mtest->name)==0 ) {
struct gpos_mark *sames;
int len=0;
char *ret, *pt;
for ( sames=mtest; sames!=NULL; sames=sames->same )
len += strlen(sames->glyphs)+1;
pt = ret = malloc(len+1);
for ( sames=mtest; sames!=NULL; sames=sames->same ) {
strcpy(pt,sames->glyphs);
pt += strlen(pt);
if ( sames->next!=NULL )
*pt++ = ' ';
}
return( ret );
}
}
LogError(_("Use of undefined glyph class, %s, on line %d of %s"), classname, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return( NULL );
}
static struct gpos_mark *fea_lookup_markclass_complain(struct parseState *tok,char *classname) {
struct gpos_mark *test;
for ( test=tok->gpos_mark; test!=NULL; test=test->next ) {
if ( strcmp(classname,test->name)==0 )
return( test );
}
LogError(_("Use of undefined mark class, %s, on line %d of %s"), classname, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return( NULL );
}
static void fea_AddClassDef(struct parseState *tok,char *classname,char *contents) {
struct glyphclasses *test;
test = fea_lookup_class(tok,classname);
if ( test==NULL ) {
test=chunkalloc(sizeof(struct glyphclasses));
test->classname = classname;
test->next = tok->classes;
tok->classes = test;
} else {
free(classname);
free(test->glyphs);
}
test->glyphs = contents;
}
static int fea_AddGlyphs(char **_glyphs, int *_max, int cnt, char *contents ) {
int len = strlen(contents);
char *glyphs = *_glyphs;
/* Append a glyph name, etc. to a glyph class */
if ( glyphs==NULL ) {
glyphs = copy(contents);
cnt = *_max = len;
} else {
if ( *_max-cnt <= len+1 )
glyphs = realloc(glyphs,(*_max+=200+len+1)+1);
glyphs[cnt++] = ' ';
strcpy(glyphs+cnt,contents);
cnt += strlen(contents);
}
free(contents);
*_glyphs = glyphs;
return( cnt );
}
static char *fea_cid_validate(struct parseState *tok,int cid) {
int i, max;
SplineFont *maxsf;
SplineChar *sc;
EncMap *map;
if ( tok->sf->subfontcnt==0 ) {
if ( !tok->warned_about_not_cid ) {
LogError(_("Reference to a CID in a non-CID-keyed font on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
tok->warned_about_not_cid = true;
}
++tok->err_count;
return(NULL);
}
max = 0; maxsf = NULL;
for ( i=0; i<tok->sf->subfontcnt; ++i ) {
SplineFont *sub = tok->sf->subfonts[i];
if ( cid<sub->glyphcnt && sub->glyphs[cid]!=NULL )
return( sub->glyphs[cid]->name );
if ( sub->glyphcnt>max ) {
max = sub->glyphcnt;
maxsf = sub;
}
}
/* Not defined, try to create it */
if ( maxsf==NULL ) /* No subfonts */
return( NULL );
if ( cid>=maxsf->glyphcnt ) {
struct cidmap *cidmap = FindCidMap(tok->sf->cidregistry,tok->sf->ordering,tok->sf->supplement,tok->sf);
if ( cidmap==NULL || cid>=MaxCID(cidmap) )
return( NULL );
SFExpandGlyphCount(maxsf,MaxCID(cidmap));
}
if ( cid>=maxsf->glyphcnt )
return( NULL );
map = EncMap1to1(maxsf->glyphcnt);
sc = SFMakeChar(maxsf,map,cid);
EncMapFree(map);
if ( sc==NULL )
return( NULL );
return( copy( sc->name ));
}
static SplineChar *fea_glyphname_get(struct parseState *tok,char *name) {
SplineFont *sf = tok->sf;
EncMap *map = sf->fv==NULL ? sf->map : sf->fv->map;
SplineChar *sc = SFGetChar(sf,-1,name);
int enc, gid;
if ( sf->subfontcnt!=0 ) {
LogError(_("Reference to a glyph name in a CID-keyed font on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return(sc);
}
if ( sc!=NULL || strcmp(name,"NULL")==0 )
return( sc );
enc = SFFindSlot(sf,map,-1,name);
if ( enc!=-1 ) {
#if 0
sc = SFMakeChar(sf,map,enc);
if ( sc!=NULL ) {
sc->widthset = true;
free(sc->name);
sc->name = copy(name);
}
#else
sc = SFGetChar(sf,enc,NULL);
#endif // 0
if (sc != NULL) return( sc );
}
// It is unclear why the first call to SFGetChar would not find this.
for ( gid=sf->glyphcnt-1; gid>=0; --gid ) if ( (sc=sf->glyphs[gid])!=NULL ) {
if ( strcmp(sc->name,name)==0 )
return( sc );
}
#if 0
// Adding a blank glyph based upon a bad reference in a feature file seems to be bad practice.
// And the method of extending the encoding here is dangerous.
/* Not in the encoding, so add it */
enc = map->enccount;
sc = SFMakeChar(sf,map,enc);
if ( sc!=NULL ) {
sc->widthset = true;
free(sc->name);
sc->name = copy(name);
sc->unicodeenc = UniFromName(name,ui_none,&custom);
}
return( sc );
#else
LogError(_("Reference to a non-existent glyph name on line %d of %s."), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
return NULL;
#endif // 0
}
static char *fea_glyphname_validate(struct parseState *tok,char *name) {
SplineChar *sc = fea_glyphname_get(tok,name);
if ( sc==NULL )
return( NULL );
return( copy( sc->name ));
}
static char *fea_ParseGlyphClass(struct parseState *tok) {
char *glyphs = NULL;
if ( tok->type==tk_class ) {
// If the class references another class, just copy that.
glyphs = fea_lookup_class_complain(tok,tok->tokbuf);
} else if ( tok->type!=tk_char || tok->tokbuf[0]!='[' ) {
// If it is not a class, we want a list to parse. Anything else is wrong.
LogError(_("Expected '[' in glyph class definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
return( NULL );
} else {
// Start parsing the list.
char *contents = NULL; // This is a temporary buffer used for each cycle below.
int cnt=0, max=0;
int last_val, range_type, range_len;
char last_glyph[MAXT+1];
char *pt1, *start1, *pt2, *start2;
int v1, v2;
last_val = -1; last_glyph[0] = '\0';
for (;;) {
fea_ParseTok(tok);
if ( tok->type==tk_char && tok->tokbuf[0]==']' )
break; // End of list.
if ( tok->type==tk_class ) {
// Stash the entire contents of the referenced class for inclusion in this class (later).
contents = fea_lookup_class_complain(tok,tok->tokbuf);
last_val=-1; last_glyph[0] = '\0';
} else if ( tok->type==tk_cid ) {
last_val = tok->value; last_glyph[0] = '\0';
contents = fea_cid_validate(tok,tok->value);
} else if ( tok->type==tk_name ) {
strcpy(last_glyph,tok->tokbuf); last_val = -1;
contents = fea_glyphname_validate(tok,tok->tokbuf);
} else if ( tok->type==tk_char && tok->tokbuf[0]=='-' ) {
// It's a range extending from the previous token.
fea_ParseTok(tok);
if ( last_val!=-1 && tok->type==tk_cid ) {
if ( last_val>=tok->value ) {
LogError(_("Invalid CID range in glyph class on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
/* Last val has already been added to the class */
/* and we'll add the current value later */
for ( ++last_val; last_val<tok->value; ++last_val ) {
contents = fea_cid_validate(tok,last_val);
if ( contents!=NULL ) {
cnt = fea_AddGlyphs(&glyphs,&max,cnt,contents); contents = NULL;
}
}
contents = fea_cid_validate(tok,tok->value);
} else if ( last_glyph[0]!='\0' && tok->type==tk_name ) {
range_type=0;
if ( strlen(last_glyph)==strlen(tok->tokbuf) &&
strcmp(last_glyph,tok->tokbuf)<0 ) {
start1=NULL;
for ( pt1=last_glyph, pt2=tok->tokbuf;
*pt1!='\0'; ++pt1, ++pt2 ) {
if ( *pt1!=*pt2 ) {
if ( start1!=NULL ) {
range_type=0;
break;
}
start1 = pt1; start2 = pt2;
if ( !isdigit(*pt1) || !isdigit(*pt2))
range_type = 1;
else {
for ( range_len=0; range_len<3 && isdigit(*pt1) && isdigit(*pt2);
++range_len, ++pt1, ++pt2 );
range_type = 2;
--pt1; --pt2;
}
}
}
}
if ( range_type==0 ) {
LogError(_("Invalid glyph name range in glyph class on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
} else if ( range_type==1 || range_len==1 ) {
/* Single letter changes */
v1 = *start1; v2 = *start2;
for ( ++v1; v1<=v2; ++v1 ) {
sprintf( last_glyph, "%.*s%c%s", (int) (start2-tok->tokbuf),
tok->tokbuf, v1, start2+1);
contents = fea_glyphname_validate(tok,last_glyph);
if ( v1==v2 )
break;
if ( contents!=NULL ) {
cnt = fea_AddGlyphs(&glyphs,&max,cnt,contents); contents = NULL;
}
}
} else {
v1 = strtol(start1,NULL,10);
v2 = strtol(start2,NULL,10);
for ( ++v1; v1<=v2; ++v1 ) {
if ( range_len==2 )
sprintf( last_glyph, "%.*s%02d%s", (int) (start2-tok->tokbuf),
tok->tokbuf, v1, start2+2 );
else
sprintf( last_glyph, "%.*s%03d%s", (int) (start2-tok->tokbuf),
tok->tokbuf, v1, start2+3 );
contents = fea_glyphname_validate(tok,last_glyph);
if ( v1==v2 )
break;
if ( contents!=NULL ) {
cnt = fea_AddGlyphs(&glyphs,&max,cnt,contents); contents = NULL;
}
}
}
} else {
LogError(_("Unexpected token in glyph class range on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
if ( tok->type==tk_char && tok->tokbuf[0]==']' )
break;
}
last_val=-1; last_glyph[0] = '\0';
} else if ( tok->type == tk_NULL ) {
contents = copy("NULL");
} else {
LogError(_("Expected glyph name, cid, or class in glyph class definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
break;
}
if ( contents!=NULL ) {
cnt = fea_AddGlyphs(&glyphs,&max,cnt,contents); contents = NULL;
}
}
if ( glyphs==NULL )
glyphs = copy(""); /* Is it legal to have an empty class? I can't think of any use for one */
}
return( glyphs );
}
static char *fea_ParseGlyphClassGuarded(struct parseState *tok) {
char *ret = fea_ParseGlyphClass(tok);
if ( ret==NULL )
ret = copy("");
return( ret );
}
static int fea_ParseMarkAttachClass(struct parseState *tok, int is_set) {
int i;
SplineFont *sf = tok->sf;
char *glyphs;
for ( i=0; i<tok->gm_cnt[is_set]; ++i ) {
if ( strcmp(tok->tokbuf,tok->gdef_mark[is_set][i].name)==0 )
return( tok->gdef_mark[is_set][i].index << 8 );
}
glyphs = fea_lookup_class_complain(tok,tok->tokbuf);
if ( glyphs==NULL )
return( 0 );
if ( tok->gm_cnt[is_set]>=tok->gm_max[is_set] ) {
tok->gdef_mark[is_set] = realloc(tok->gdef_mark[is_set],(tok->gm_max[is_set]+=30)*sizeof(struct gdef_mark));
if ( tok->gm_pos[is_set]==0 ) {
memset(&tok->gdef_mark[is_set][0],0,sizeof(tok->gdef_mark[is_set][0]));
if ( is_set )
tok->gm_pos[is_set] = sf->mark_set_cnt;
else
tok->gm_pos[is_set] = sf->mark_class_cnt==0 ? 1 : sf->mark_class_cnt;
}
}
tok->gdef_mark[is_set][tok->gm_cnt[is_set]].name = copy(tok->tokbuf);
tok->gdef_mark[is_set][tok->gm_cnt[is_set]].glyphs = glyphs;
/* see if the mark class is already in the font? */
if ( is_set ) {
for ( i=sf->mark_set_cnt-1; i>=0; --i ) {
if ( strcmp(sf->mark_set_names[i],tok->tokbuf+1)==0 ||
strcmp(sf->mark_sets[i],glyphs)==0 )
break;
}
} else {
for ( i=sf->mark_class_cnt-1; i>0; --i ) {
if ( strcmp(sf->mark_class_names[i],tok->tokbuf+1)==0 ||
strcmp(sf->mark_classes[i],glyphs)==0 )
break;
}
if ( i==0 ) i=-1;
}
if ( i>=0 )
tok->gdef_mark[is_set][tok->gm_cnt[is_set]].index = i;
else
tok->gdef_mark[is_set][tok->gm_cnt[is_set]].index = tok->gm_pos[is_set]++;
if ( is_set )
return( (tok->gdef_mark[is_set][tok->gm_cnt[is_set]++].index << 16) | pst_usemarkfilteringset );
else
return( tok->gdef_mark[is_set][tok->gm_cnt[is_set]++].index << 8 );
}
static void fea_ParseLookupFlags(struct parseState *tok) {
int val = 0;
struct feat_item *item;
fea_ParseTok(tok);
if ( tok->type==tk_int ) {
val = tok->value;
fea_end_statement(tok);
} else {
while ( tok->type==tk_RightToLeft || tok->type==tk_IgnoreBaseGlyphs ||
tok->type==tk_IgnoreMarks || tok->type==tk_IgnoreLigatures ||
tok->type==tk_MarkAttachmentType || tok->type==tk_UseMarkFilteringSet ) {
if ( tok->type == tk_RightToLeft )
val |= pst_r2l;
else if ( tok->type == tk_IgnoreBaseGlyphs )
val |= pst_ignorebaseglyphs;
else if ( tok->type == tk_IgnoreMarks )
val |= pst_ignorecombiningmarks;
else if ( tok->type == tk_IgnoreLigatures )
val |= pst_ignoreligatures;
else {
int is_set = tok->type == tk_UseMarkFilteringSet;
fea_TokenMustBe(tok,tk_class,'\0');
val |= fea_ParseMarkAttachClass(tok,is_set);
}
fea_ParseTok(tok);
if ( tok->type == tk_char && tok->tokbuf[0]==';' )
break;
else if ( tok->type==tk_char && tok->tokbuf[0]!=',' ) {
LogError(_("Expected ';' in lookupflags on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
fea_skip_to_semi(tok);
break;
}
fea_ParseTok(tok);
}
if ( tok->type != tk_char || tok->tokbuf[0]!=';' ) {
LogError(_("Unexpected token in lookupflags on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
fea_skip_to_semi(tok);
} else if ( val==0 ) {
LogError(_("No flags specified in lookupflags on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
}
item = chunkalloc(sizeof(struct feat_item));
item->type = ft_lookupflags;
item->u2.lookupflags = val;
item->next = tok->sofar;
tok->sofar = item;
}
static void fea_ParseGlyphClassDef(struct parseState *tok) {
char *classname = copy(tok->tokbuf );
char *contents;
fea_ParseTok(tok);
if ( tok->type!=tk_char || tok->tokbuf[0]!='=' ) {
LogError(_("Expected '=' in glyph class definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
fea_skip_to_semi(tok);
return;
}
fea_ParseTok(tok);
contents = fea_ParseGlyphClass(tok); // Make a list of referenced glyphs.
if ( contents==NULL ) {
fea_skip_to_semi(tok);
return;
}
fea_AddClassDef(tok,classname,copy(contents)); // Put the list into a class.
fea_end_statement(tok);
}
static void fea_ParseLangSys(struct parseState *tok, int inside_feat) {
uint32 script, lang;
struct scriptlanglist *sl;
int l;
fea_ParseTok(tok);
if ( tok->type!=tk_name || !tok->could_be_tag ) {
LogError(_("Expected tag in languagesystem on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
fea_skip_to_semi(tok);
return;
}
script = tok->tag;
fea_ParseTok(tok);
if ( tok->type!=tk_name || !tok->could_be_tag ) {
LogError(_("Expected tag in languagesystem on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
fea_skip_to_semi(tok);
return;
}
lang = tok->tag;
for ( sl=tok->def_langsyses; sl!=NULL && sl->script!=script; sl=sl->next );
if ( sl==NULL ) {
sl = chunkalloc(sizeof(struct scriptlanglist));
sl->script = script;
sl->next = tok->def_langsyses;
tok->def_langsyses = sl;
}
for ( l=0; l<sl->lang_cnt; ++l ) {
uint32 language = l<MAX_LANG ? sl->langs[l] : sl->morelangs[l-MAX_LANG];
if ( language==lang )
break;
}
if ( l<sl->lang_cnt )
/* Er... this combination is already in the list. I guess that's ok */;
else if ( sl->lang_cnt<MAX_LANG )
sl->langs[sl->lang_cnt++] = lang;
else {
sl->morelangs = realloc(sl->morelangs,(sl->lang_cnt+1)*sizeof(uint32));
sl->morelangs[sl->lang_cnt++ - MAX_LANG] = lang;
}
fea_end_statement(tok);
if ( inside_feat ) {
struct feat_item *item = chunkalloc(sizeof(struct feat_item));
item->type = ft_langsys;
item->u2.sl = SListCopy(tok->def_langsyses);
item->next = tok->sofar;
tok->sofar = item;
}
}
struct apmark {
AnchorPoint *ap;
struct gpos_mark *mark_class;
uint16 mark_count;
};
struct ligcomponent {
int apm_cnt;
struct apmark *apmark;
};
struct markedglyphs {
unsigned int has_marks: 1; /* Are there any marked glyphs in the entire sequence? */
unsigned int is_cursive: 1; /* Only in a position sequence */
unsigned int is_mark: 1; /* Only in a position sequence/mark keyword=>mark2mark */
unsigned int is_lookup: 1; /* Or a lookup when parsing a subs replacement list */
unsigned int is_mark2base: 1;
unsigned int is_mark2mark: 1;
unsigned int is_mark2lig: 1;
unsigned int is_name: 1; /* Otherwise a class */
unsigned int hidden_marked_glyphs: 1;/* for glyphs with marked marks in a mark2base sequence */
uint16 mark_count; /* 0=>unmarked, 1=>first mark, etc. */
char *name_or_class; /* Glyph name / class contents */
struct vr *vr; /* A value record. Only in position sequences */
int ap_cnt; /* Number of anchor points */
AnchorPoint **anchors;
int apm_cnt;
struct apmark *apmark;
int lc_cnt;
struct ligcomponent *ligcomp;
char *lookupname;
struct markedglyphs *next;
};
static void NameIdFree(struct nameid *nm);
static void fea_ParseDeviceTable(struct parseState *tok,DeviceTable *adjust)
{
int first = true;
int min=0, max= -1;
int8 values[512];
memset(values,0,sizeof(values));
fea_TokenMustBe(tok,tk_device,'\0');
if ( tok->type!=tk_device )
return;
for (;;) {
fea_ParseTok(tok);
if ( first && tok->type==tk_NULL ) {
fea_TokenMustBe(tok,tk_char,'>');
break;
} else if ( tok->type==tk_char && tok->tokbuf[0]=='>' ) {
break;
} else if ( tok->type!=tk_int ) {
LogError(_("Expected integer in device table on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
} else {
int pixel = tok->value;
fea_TokenMustBe(tok,tk_int,'\0');
if ( pixel>=sizeof(values) || pixel<0 )
LogError(_("Pixel size too big in device table on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
else {
values[pixel] = tok->value;
if ( max==-1 )
min=max=pixel;
else if ( pixel<min ) min = pixel;
else if ( pixel>max ) max = pixel;
}
fea_ParseTok(tok);
if ( tok->type==tk_char && tok->tokbuf[0]==',' )
/* That's right... */;
else if ( tok->type==tk_char && tok->tokbuf[0]=='>' )
break;
else {
LogError(_("Expected comma in device table on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
}
first = false;
}
if ( max!=-1 ) {
int i;
adjust->first_pixel_size = min;
adjust->last_pixel_size = max;
adjust->corrections = malloc(max-min+1);
for ( i=min; i<=max; ++i )
adjust->corrections[i-min] = values[i];
}
}
static void fea_ParseCaret(struct parseState *tok) {
int val=0;
fea_TokenMustBe(tok,tk_caret,'\0');
if ( tok->type!=tk_caret )
return;
fea_ParseTok(tok);
if ( tok->type!=tk_int ) {
LogError(_("Expected integer in caret on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
} else
val = tok->value;
fea_ParseTok(tok);
if ( tok->type!=tk_char || tok->tokbuf[0]!='>' ) {
LogError(_("Expected '>' in caret on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
tok->value = val;
}
static AnchorPoint *fea_ParseAnchor(struct parseState *tok) {
AnchorPoint *ap = NULL;
struct namedanchor *nap;
if ( tok->type==tk_anchor || tok->type==tk_anchorDef ) {
fea_ParseTok(tok);
if ( tok->type==tk_NULL ) {
ap = NULL;
fea_ParseTok(tok);
} else if ( tok->type==tk_name ) {
for ( nap=tok->namedAnchors; nap!=NULL; nap=nap->next ) {
if ( strcmp(nap->name,tok->tokbuf)==0 ) {
ap = AnchorPointsCopy(nap->ap);
break;
}
}
if ( nap==NULL ) {
LogError(_("\"%s\" is not the name of a known named anchor on line %d of %s."),
tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
fea_ParseTok(tok);
} else if ( tok->type==tk_int ) {
ap = chunkalloc(sizeof(AnchorPoint));
ap->me.x = tok->value;
fea_TokenMustBe(tok,tk_int,'\0');
ap->me.y = tok->value;
fea_ParseTok(tok);
if ( tok->type == tk_contourpoint )
fea_TokenMustBe(tok,tk_int,' ');
/* FF had a bug and produced anchor points with x y c instead of x y <contourpoint c> */
if ( tok->type==tk_int ) {
ap->ttf_pt_index = tok->value;
ap->has_ttf_pt = true;
fea_ParseTok(tok);
} else if ( tok->type==tk_char && tok->tokbuf[0]=='<' ) {
fea_ParseTok(tok);
if ( tok->type == tk_contourpoint ) {
fea_TokenMustBe(tok,tk_int,' ');
ap->ttf_pt_index = tok->value;
ap->has_ttf_pt = true;
fea_TokenMustBe(tok,tk_int,'>');
} else {
fea_UnParseTok(tok);
fea_ParseDeviceTable(tok,&ap->xadjust);
fea_TokenMustBe(tok,tk_char,'<');
fea_ParseDeviceTable(tok,&ap->yadjust);
}
fea_ParseTok(tok);
}
} else {
LogError(_("Expected integer in anchor on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
} else {
LogError(_("Expected 'anchor' keyword in anchor on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
return( ap );
}
static AnchorPoint *fea_ParseAnchorClosed(struct parseState *tok) {
int ecnt = tok->err_count;
AnchorPoint *ap = fea_ParseAnchor(tok);
if ( tok->err_count==ecnt && ( tok->type!=tk_char || tok->tokbuf[0]!='>' )) {
LogError(_("Expected '>' in anchor on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
return( ap );
}
static void fea_ParseAnchorDef(struct parseState *tok) {
AnchorPoint *ap;
struct namedanchor *nap;
ap = fea_ParseAnchor(tok);
if ( tok->type!=tk_name ) {
LogError(_("Expected name in anchor definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
fea_skip_to_semi(tok);
return;
}
for ( nap=tok->namedAnchors; nap!=NULL; nap=nap->next )
if ( strcmp(nap->name,tok->tokbuf)==0 )
break;
if ( nap!=NULL ) {
LogError(_("Attempt to redefine anchor definition of \"%s\" on line %d of %s"),
tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
} else {
nap = chunkalloc(sizeof(struct namedanchor));
nap->next = tok->namedAnchors;
tok->namedAnchors = nap;
nap->name = copy(tok->tokbuf);
}
nap->ap = ap;
fea_end_statement(tok);
}
static int fea_findLookup(struct parseState *tok,char *name ) {
struct feat_item *feat;
for ( feat=tok->sofar; feat!=NULL; feat=feat->next ) {
if ( feat->type==ft_lookup_start && strcmp(name,feat->u1.lookup_name)==0 )
return( true );
}
if ( SFFindLookup(tok->sf,name)!=NULL ) {
if ( !tok->lookup_in_sf_warned ) {
ff_post_notice(_("Refers to Font"),_("Reference to a lookup which is not in the feature file but which is in the font, %.50s"), name );
tok->lookup_in_sf_warned = true;
}
return( true );
}
return( false );
}
static struct vr *ValueRecordCopy(struct vr *ovr) {
struct vr *nvr;
nvr = chunkalloc(sizeof(*nvr));
memcpy(nvr,ovr,sizeof(struct vr));
nvr->adjust = ValDevTabCopy(ovr->adjust);
return( nvr );
}
static struct vr *fea_ParseValueRecord(struct parseState *tok) {
struct vr *vr=NULL;
struct namedvalue *nvr;
if ( tok->type==tk_name ) {
for ( nvr=tok->namedValueRs; nvr!=NULL; nvr=nvr->next ) {
if ( strcmp(nvr->name,tok->tokbuf)==0 ) {
vr = ValueRecordCopy(nvr->vr);
break;
}
}
if ( nvr==NULL ) {
LogError(_("\"%s\" is not the name of a known named value record on line %d of %s."),
tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
fea_ParseTok(tok);
} else if ( tok->type==tk_int ) {
vr = chunkalloc(sizeof( struct vr ));
vr->xoff = tok->value;
fea_ParseTok(tok);
if ( tok->type==tk_int ) {
vr->yoff = tok->value;
fea_TokenMustBe(tok,tk_int,'\0');
vr->h_adv_off = tok->value;
fea_TokenMustBe(tok,tk_int,'\0');
vr->v_adv_off = tok->value;
fea_ParseTok(tok);
if ( tok->type==tk_char && tok->tokbuf[0]=='<' ) {
vr->adjust = chunkalloc(sizeof(struct valdev));
fea_ParseDeviceTable(tok,&vr->adjust->xadjust);
fea_TokenMustBe(tok,tk_char,'<');
fea_ParseDeviceTable(tok,&vr->adjust->yadjust);
fea_TokenMustBe(tok,tk_char,'<');
fea_ParseDeviceTable(tok,&vr->adjust->xadv);
fea_TokenMustBe(tok,tk_char,'<');
fea_ParseDeviceTable(tok,&vr->adjust->yadv);
fea_ParseTok(tok);
}
} else if ( tok->type==tk_char && tok->tokbuf[0]=='>' ) {
if ( tok->in_vkrn )
vr->v_adv_off = vr->xoff;
else
vr->h_adv_off = vr->xoff;
vr->xoff = 0;
}
} else {
LogError(_("Unexpected token in value record on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
++tok->err_count;
}
return( vr );
}
static void fea_ParseValueRecordDef(struct parseState *tok) {
struct vr *vr;
struct namedvalue *nvr;
fea_ParseTok(tok);
vr = fea_ParseValueRecord(tok);
if ( tok->type!=tk_name ) {
LogError(_("Expected name in value record definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );