summaryrefslogtreecommitdiff
path: root/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/featurefile.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/featurefile.c')
-rw-r--r--Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/featurefile.c4208
1 files changed, 4208 insertions, 0 deletions
diff --git a/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/featurefile.c b/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/featurefile.c
new file mode 100644
index 00000000000..bf89c45f6ca
--- /dev/null
+++ b/Build/source/texk/web2c/luatexdir/luafontloader/fontforge/fontforge/featurefile.c
@@ -0,0 +1,4208 @@
+/* Copyright (C) 2000-2008 by George Williams */
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+
+ * The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include "fontforgevw.h"
+#include "ttf.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>
+
+
+
+/* ************************************************************************** */
+/* ******************************* 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 */
+};
+
+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 };
+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;
+ struct scriptlanglist *sl; /* Default langsyses for features/langsys */
+ int exclude_dflt; /* for lang tags */
+ struct nameid *names; /* size params */
+ struct tablevalues *tvals;
+ int16 *lcaret;
+ } u2;
+ char *mark_class; /* 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 = galloc(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 );
+}
+
+static int fea_classesIntersect(char *class1, char *class2) {
+ char *pt1, *start1, *pt2, *start2;
+ int ch1, ch2;
+
+ 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;
+ }
+}
+
+
+#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 = galloc(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 );
+}
+
+#define MAXT 40
+#define MAXI 5
+struct parseState {
+ char tokbuf[MAXT+1];
+ long value;
+ 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
+ } 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 { char *classname, *glyphs; struct glyphclasses *next; } *classes;
+ 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;
+};
+
+static struct keywords {
+ char *name;
+ enum toktype tok;
+} fea_keywords[] = {
+/* list must be in toktype order */
+ { "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 },
+/* synonyms */
+ { "sub", tk_substitute },
+ { "pos", tk_position },
+ { "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) },
+ { NULL, 0, 0, 0 }
+};
+
+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) },
+ { NULL, 0, 0, 0 }
+};
+
+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) },
+ { NULL, 0, 0, 0 }
+};
+
+
+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_ParseTok(struct parseState *tok) {
+ FILE *in = tok->inlist[tok->inc_depth];
+ int ch, peekch = 0;
+ 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)) { /* 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=='.' ) && pt<start+31 ) {
+ *pt++ = ch;
+ ch = getc(in);
+ }
+ *pt = '\0';
+ ungetc(ch,in);
+ if ( isalnum(ch) || ch=='_' || ch=='.' ) {
+ LogError(_("Name 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 name on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+
+ if ( check_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_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);
+ }
+}
+
+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],
+ 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) {
+ 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 ) {
+ LogError(_("Expected '%s' on line %d of %s"), fea_keywords[type],
+ 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;
+
+ for ( test=tok->classes; test!=NULL; test=test->next ) {
+ if ( strcmp(classname,test->classname)==0 )
+return( copy( test->glyphs) );
+ }
+ 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 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 = grealloc(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;
+ 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,sf->fv->map,-1,name);
+ if ( enc!=-1 ) {
+ sc = SFMakeChar(sf,sf->fv->map,enc);
+ if ( sc!=NULL ) {
+ sc->widthset = true;
+ free(sc->name);
+ sc->name = copy(name);
+ }
+return( sc );
+ }
+
+ for ( gid=sf->glyphcnt-1; gid>=0; --gid ) if ( (sc=sf->glyphs[gid])!=NULL ) {
+ if ( strcmp(sc->name,name)==0 )
+return( sc );
+ }
+
+/* Don't encode it (not in current encoding), just add it, so we needn't */
+/* mess with maps or selections */
+ SFExpandGlyphCount(sf,sf->glyphcnt+1);
+ sc = SFSplineCharCreate(sf);
+ sc->name = copy(name);
+ sc->unicodeenc = UniFromName(name,ui_none,&custom);
+ sc->parent = sf;
+ sc->vwidth = (sf->ascent+sf->descent);
+ sc->width = 6*sc->vwidth/10;
+ sc->widthset = true; /* So we don't lose the glyph */
+ sc->orig_pos = sf->glyphcnt-1;
+ sf->glyphs[sc->orig_pos] = sc;
+return( sc );
+}
+
+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 ) {
+ glyphs = fea_lookup_class_complain(tok,tok->tokbuf);
+ } else 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;
+return( NULL );
+ } else {
+ char *contents = NULL;
+ int cnt=0, max=0;
+ int last_val = 0, range_type, range_len = 0;
+ char last_glyph[MAXT+1];
+ char *pt1, *start1, *pt2, *start2 = NULL;
+ int v1, v2;
+
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type==tk_char && tok->tokbuf[0]==']' )
+ break;
+ if ( tok->type==tk_class ) {
+ 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]=='-' ) {
+ 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 = 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 ) {
+ *start1 = v1;
+ contents = fea_glyphname_validate(tok,start1);
+ if ( v1==v2 )
+ break;
+ if ( contents!=NULL )
+ cnt = fea_AddGlyphs(&glyphs,&max,cnt,contents);
+ }
+ } 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);
+ }
+ }
+ } 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);
+ }
+ 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 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 ) {
+ 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;
+ 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);
+ if ( contents==NULL ) {
+ fea_skip_to_semi(tok);
+return;
+ }
+ fea_AddClassDef(tok,classname,copy(contents));
+ 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 = grealloc(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 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_name: 1; /* Otherwise a class */
+ unsigned int is_lookup: 1; /* Or a lookup when parsing a subs replacement list */
+ 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;
+ char *lookupname;
+ struct markedglyphs *next;
+};
+
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+static void fea_ParseDeviceTable(struct parseState *tok,DeviceTable *adjust)
+#else
+static void fea_ParseDeviceTable(struct parseState *tok)
+#endif
+ {
+ int first = true;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ int min=0, max= -1;
+ int8 values[512];
+
+ memset(values,0,sizeof(values));
+#endif
+
+ fea_TokenMustBe(tok,tk_device,'\0');
+ if ( tok->type!=tk_device )
+return;
+
+ forever {
+ 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 {
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ int pixel = tok->value;
+#endif
+ fea_TokenMustBe(tok,tk_int,'\0');
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ 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;
+ }
+#endif
+ }
+ first = false;
+ }
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( max!=-1 ) {
+ int i;
+ adjust->first_pixel_size = min;
+ adjust->last_pixel_size = max;
+ adjust->corrections = galloc(max-min+1);
+ for ( i=min; i<=max; ++i )
+ adjust->corrections[i-min] = values[i];
+ }
+#endif
+}
+
+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;
+
+ if ( tok->type==tk_anchor ) {
+ fea_ParseTok(tok);
+ if ( tok->type==tk_NULL )
+ ap = NULL;
+ 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_int ) {
+ ap->ttf_pt_index = tok->value;
+ ap->has_ttf_pt = true;
+ fea_TokenMustBe(tok,tk_char,'>');
+ } else if ( tok->type==tk_char && tok->tokbuf[0]=='<' ) {
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ fea_ParseDeviceTable(tok,&ap->xadjust);
+ fea_TokenMustBe(tok,tk_char,'<');
+ fea_ParseDeviceTable(tok,&ap->yadjust);
+#else
+ fea_ParseDeviceTable(tok);
+ fea_TokenMustBe(tok,tk_char,'<');
+ fea_ParseDeviceTable(tok);
+#endif
+ fea_TokenMustBe(tok,tk_char,'>');
+ } else if ( 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;
+ }
+ } 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 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 void fea_ParseBroket(struct parseState *tok,struct markedglyphs *last) {
+ /* We've read the open broket. Might find: value record, anchor, lookup */
+ /* (lookups are my extension) */
+ struct vr *vr;
+
+ fea_ParseTok(tok);
+ if ( tok->type==tk_lookup ) {
+ fea_TokenMustBe(tok,tk_name,'\0');
+ if ( last->mark_count==0 ) {
+ LogError(_("Lookups may only be specified after marked glyphs on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ if ( !fea_findLookup(tok,tok->tokbuf) ) {
+ LogError(_("Lookups must be defined before being used on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else
+ last->lookupname = copy(tok->tokbuf);
+ fea_TokenMustBe(tok,tk_char,'>');
+ } else if ( tok->type==tk_anchor ) {
+ last->anchors = grealloc(last->anchors,(++last->ap_cnt)*sizeof(AnchorPoint *));
+ last->anchors[last->ap_cnt-1] = fea_ParseAnchor(tok);
+ } else if ( tok->type==tk_NULL ) {
+ /* NULL value record. Adobe documents it and doesn't implement it */
+ /* Not sure what it's good for */
+ fea_TokenMustBe(tok,tk_char,'>');
+ } else if ( tok->type==tk_int ) {
+ last->vr = vr = chunkalloc(sizeof( struct vr ));
+ vr->xoff = tok->value;
+ fea_ParseTok(tok);
+ 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 if ( tok->type!=tk_int ) {
+ LogError(_("Expected integer in value record on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else {
+ 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]=='<' ) {
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ 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);
+#else
+ fea_ParseDeviceTable(tok);
+ fea_TokenMustBe(tok,tk_char,'<');
+ fea_ParseDeviceTable(tok);
+ fea_TokenMustBe(tok,tk_char,'<');
+ fea_ParseDeviceTable(tok);
+ fea_TokenMustBe(tok,tk_char,'<');
+ fea_ParseDeviceTable(tok);
+#endif
+ fea_TokenMustBe(tok,tk_char,'>');
+ } else if ( tok->type!=tk_char || tok->tokbuf[0]!='>' ) {
+ LogError(_("Expected '>' in value record on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ }
+ }
+}
+
+static struct markedglyphs *fea_ParseMarkedGlyphs(struct parseState *tok,
+ int is_pos, int allow_marks, int allow_lookups) {
+ int mark_cnt = 0, last_mark=0, is_cursive = false, is_mark=false;
+ struct markedglyphs *head=NULL, *last=NULL, *prev=NULL, *cur;
+ int first = true;
+ char *contents;
+
+ forever {
+ fea_ParseTok(tok);
+ cur = NULL;
+ if ( first && is_pos && tok->type == tk_cursive )
+ is_cursive = true;
+ else if ( first && is_pos && tok->type == tk_mark )
+ is_mark = true;
+ else if ( tok->type==tk_name || tok->type == tk_cid ) {
+ if ( tok->type == tk_name )
+ contents = fea_glyphname_validate(tok,tok->tokbuf);
+ else
+ contents = fea_cid_validate(tok,tok->value);
+ if ( contents!=NULL ) {
+ cur = chunkalloc(sizeof(struct markedglyphs));
+ cur->is_cursive = is_cursive;
+ cur->is_mark = is_mark;
+ cur->is_name = true;
+ cur->name_or_class = contents;
+ }
+ } else if ( tok->type == tk_class || (tok->type==tk_char && tok->tokbuf[0]=='[')) {
+ cur = chunkalloc(sizeof(struct markedglyphs));
+ cur->is_cursive = is_cursive;
+ cur->is_mark = is_mark;
+ cur->is_name = false;
+ cur->name_or_class = fea_ParseGlyphClassGuarded(tok);
+ } else if ( allow_marks && tok->type==tk_char &&
+ (tok->tokbuf[0]=='\'' || tok->tokbuf[0]=='"') && last!=NULL ) {
+ if ( last_mark!=tok->tokbuf[0] || (prev!=NULL && prev->mark_count==0)) {
+ ++mark_cnt;
+ last_mark = tok->tokbuf[0];
+ }
+ last->mark_count = mark_cnt;
+ } else if ( is_pos && last!=NULL && last->vr==NULL && tok->type == tk_int ) {
+ last->vr = chunkalloc(sizeof(struct vr));
+ if ( tok->in_vkrn )
+ last->vr->v_adv_off = tok->value;
+ else
+ last->vr->h_adv_off = tok->value;
+ } else if ( is_pos && last!=NULL && tok->type == tk_char && tok->tokbuf[0]=='<' ) {
+ fea_ParseBroket(tok,last);
+ } else if ( !is_pos && allow_lookups && tok->type == tk_char && tok->tokbuf[0]=='<' ) {
+ fea_TokenMustBe(tok,tk_lookup,'\0');
+ fea_TokenMustBe(tok,tk_name,'\0');
+ cur = chunkalloc(sizeof(struct markedglyphs));
+ cur->is_name = false;
+ cur->is_lookup = true;
+ cur->lookupname = copy(tok->tokbuf);
+ fea_TokenMustBe(tok,tk_char,'>');
+ } else
+ break;
+ if ( cur!=NULL ) {
+ prev = last;
+ if ( last==NULL )
+ head = cur;
+ else
+ last->next = cur;
+ last = cur;
+ }
+ first = false;
+ }
+ if ( head!=NULL && mark_cnt!=0 )
+ head->has_marks = true;
+ fea_UnParseTok(tok);
+return( head );
+}
+
+static void fea_markedglyphsFree(struct markedglyphs *gl) {
+ struct markedglyphs *next;
+ int i;
+
+ while ( gl!=NULL ) {
+ next = gl->next;
+ free(gl->name_or_class);
+ free(gl->lookupname);
+ for ( i=0; i<gl->ap_cnt; ++i )
+ AnchorPointsFree(gl->anchors[i]);
+ free(gl->anchors);
+ if ( gl->vr!=NULL ) {
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ ValDevFree(gl->vr->adjust);
+#endif
+ chunkfree(gl->vr,sizeof(struct vr));
+ }
+ gl = next;
+ }
+}
+
+static struct feat_item *fea_AddAllLigPosibilities(struct parseState *tok,struct markedglyphs *glyphs,
+ SplineChar *sc,char *sequence_start,char *next, struct feat_item *sofar) {
+ char *start, *pt, ch;
+ SplineChar *temp;
+ char *after;
+ struct feat_item *item;
+
+ start = glyphs->name_or_class;
+ forever {
+ while ( *start==' ' ) ++start;
+ if ( *start=='\0' )
+ break;
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ temp = fea_glyphname_get(tok,start);
+ *pt = ch; start = pt;
+ if ( temp==NULL )
+ continue;
+ strcpy(next,temp->name);
+ after = next+strlen(next);
+ if ( glyphs->next!=NULL ) {
+ *after++ = ' ';
+ sofar = fea_AddAllLigPosibilities(tok,glyphs->next,sc,sequence_start,after,sofar);
+ } else {
+ *after = '\0';
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = sofar;
+ sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_ligature;
+ item->u2.pst->u.lig.components = copy(sequence_start);
+ item->u2.pst->u.lig.lig = sc;
+ }
+ }
+return( sofar );
+}
+
+static struct markedglyphs *fea_glyphs_to_names(struct markedglyphs *glyphs,
+ int cnt,char **to) {
+ struct markedglyphs *g;
+ int len, i;
+ char *names, *pt;
+
+ len = 0;
+ for ( g=glyphs, i=0; i<cnt; ++i, g=g->next )
+ len += strlen( g->name_or_class ) +1;
+ names = pt = galloc(len+1);
+ for ( g=glyphs, i=0; i<cnt; ++i, g=g->next ) {
+ strcpy(pt,g->name_or_class);
+ pt += strlen( pt );
+ *pt++ = ' ';
+ }
+ if ( pt!=names )
+ pt[-1] = '\0';
+ else
+ *pt = '\0';
+ *to = names;
+return( g );
+}
+
+static struct feat_item *fea_process_pos_single(struct parseState *tok,
+ struct markedglyphs *glyphs, struct feat_item *sofar) {
+ char *start, *pt, ch;
+ struct feat_item *item;
+ SplineChar *sc;
+
+ start = glyphs->name_or_class;
+ forever {
+ while ( *start==' ' ) ++start;
+ if ( *start=='\0' )
+ break;
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = fea_glyphname_get(tok,start);
+ *pt = ch; start = pt;
+ if ( sc!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = sofar;
+ sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_position;
+ item->u2.pst->u.pos = glyphs->vr[0];
+ }
+ }
+return( sofar );
+}
+
+static struct feat_item *fea_process_pos_pair(struct parseState *tok,
+ struct markedglyphs *glyphs, struct feat_item *sofar, int enumer) {
+ char *start, *pt, ch, *start2, *pt2, ch2;
+ struct feat_item *item;
+ struct vr vr[2];
+ SplineChar *sc, *sc2;
+
+ memset(vr,0,sizeof(vr));
+ if ( glyphs->vr==NULL )
+ vr[0] = *glyphs->next->vr;
+ else {
+ vr[0] = *glyphs->vr;
+ if ( glyphs->next->vr!=NULL )
+ vr[1] = *glyphs->next->vr;
+ }
+ if ( enumer || (glyphs->is_name && glyphs->next->is_name)) {
+ start = glyphs->name_or_class;
+ forever {
+ while ( *start==' ' ) ++start;
+ if ( *start=='\0' )
+ break;
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = fea_glyphname_get(tok,start);
+ *pt = ch; start = pt;
+ if ( sc!=NULL ) {
+ start2 = glyphs->next->name_or_class;
+ forever {
+ while ( *start2==' ' ) ++start2;
+ if ( *start2=='\0' )
+ break;
+ for ( pt2=start2; *pt2!='\0' && *pt2!=' '; ++pt2 );
+ ch2 = *pt2; *pt2 = '\0';
+ sc2 = fea_glyphname_get(tok,start2);
+ *pt2 = ch2; start2 = pt2;
+ if ( sc2!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = sofar;
+ sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_pair;
+ item->u2.pst->u.pair.paired = copy(sc2->name);
+ item->u2.pst->u.pair.vr = chunkalloc(sizeof( struct vr[2]));
+ memcpy(item->u2.pst->u.pair.vr,vr,sizeof(vr));
+ }
+ }
+ }
+ }
+ } else {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pstclass;
+ item->next = sofar;
+ sofar = item;
+ item->u1.class = copy(glyphs->name_or_class);
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_pair;
+ item->u2.pst->u.pair.paired = copy(glyphs->next->name_or_class);
+ item->u2.pst->u.pair.vr = chunkalloc(sizeof( struct vr[2]));
+ memcpy(item->u2.pst->u.pair.vr,vr,sizeof(vr));
+ }
+return( sofar );
+}
+
+static struct feat_item *fea_process_sub_single(struct parseState *tok,
+ struct markedglyphs *glyphs, struct markedglyphs *rpl,
+ struct feat_item *sofar ) {
+ char *start, *pt, ch, *start2, *pt2, ch2;
+ struct feat_item *item;
+ SplineChar *sc, *temp;
+
+ if ( rpl->is_name ) {
+ temp = fea_glyphname_get(tok,rpl->name_or_class);
+ if ( temp!=NULL ) {
+ start = glyphs->name_or_class;
+ if ( start==NULL ) {
+ LogError(_("Internal state messed up on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+return( sofar );
+ }
+ forever {
+ while ( *start==' ' ) ++start;
+ if ( *start=='\0' )
+ break;
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = fea_glyphname_get(tok,start);
+ *pt = ch; start = pt;
+ if ( sc!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = sofar;
+ sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_substitution;
+ item->u2.pst->u.subs.variant = copy(temp->name);
+ }
+ }
+ }
+ } else if ( !glyphs->is_name ) {
+ start = glyphs->name_or_class;
+ start2 = rpl->name_or_class;
+ forever {
+ while ( *start==' ' ) ++start;
+ while ( *start2==' ' ) ++start2;
+ if ( *start=='\0' && *start2=='\0' )
+ break;
+ else if ( *start=='\0' || *start2=='\0' ) {
+ LogError(_("When a single substitution is specified by glyph classes, those classes must be of the same length on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ break;
+ }
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ for ( pt2=start2; *pt2!='\0' && *pt2!=' '; ++pt2 );
+ ch2 = *pt2; *pt2 = '\0';
+ sc = fea_glyphname_get(tok,start);
+ temp = fea_glyphname_get(tok,start2);
+ *pt = ch; start = pt;
+ *pt2 = ch2; start2 = pt2;
+ if ( sc==NULL || temp==NULL )
+ continue;
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = sofar;
+ sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_substitution;
+ item->u2.pst->u.subs.variant = copy(temp->name);
+ }
+ } else {
+ LogError(_("When a single substitution's replacement is specified by a glyph class, the thing being replaced must also be a class on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+return( sofar );
+}
+
+static struct feat_item *fea_process_sub_ligature(struct parseState *tok,
+ struct markedglyphs *glyphs, struct markedglyphs *rpl,
+ struct feat_item *sofar ) {
+ SplineChar *sc;
+ struct markedglyphs *g;
+
+ /* I store ligatures backwards, in the ligature glyph not the glyphs being substituted */
+ sc = fea_glyphname_get(tok,rpl->name_or_class);
+ if ( sc!=NULL ) {
+ int len=0;
+ char *space;
+ for ( g=glyphs; g!=NULL && g->mark_count==glyphs->mark_count; g=g->next )
+ len += strlen(g->name_or_class)+1;
+ space = galloc(len+1);
+ sofar = fea_AddAllLigPosibilities(tok,glyphs,sc,space,space,sofar);
+ free(space);
+ }
+return( sofar );
+}
+
+static FPST *fea_markedglyphs_to_fpst(struct parseState *tok,struct markedglyphs *glyphs,
+ int is_pos,int is_ignore) {
+ struct markedglyphs *g;
+ int bcnt=0, ncnt=0, fcnt=0, cnt;
+ int all_single=true;
+ int mmax = 0;
+ int i;
+ FPST *fpst;
+ struct fpst_rule *r;
+ struct feat_item *item, *head = NULL;
+
+ for ( g=glyphs; g!=NULL && g->mark_count==0; g=g->next ) {
+ ++bcnt;
+ if ( !g->is_name ) all_single = false;
+ }
+ for ( ; g!=NULL ; g=g->next ) {
+ if ( !g->is_name ) all_single = false;
+ if ( g->mark_count==0 )
+ ++fcnt;
+ else {
+ /* if we found some unmarked glyphs between two runs of marked */
+ /* they don't count as lookaheads */
+ ncnt += fcnt + 1;
+ fcnt = 0;
+ if ( g->mark_count>mmax ) mmax = g->mark_count;
+ }
+ }
+
+ fpst = chunkalloc(sizeof(FPST));
+ fpst->type = is_pos ? pst_chainpos : pst_chainsub;
+ fpst->format = all_single ? pst_glyphs : pst_coverage;
+ fpst->rule_cnt = 1;
+ fpst->rules = r = gcalloc(1,sizeof(struct fpst_rule));
+ if ( is_ignore )
+ mmax = 0;
+ r->lookup_cnt = mmax;
+ r->lookups = gcalloc(mmax,sizeof(struct seqlookup));
+ for ( i=0; i<mmax; ++i )
+ r->lookups[i].seq = i;
+
+ if ( all_single ) {
+ g = fea_glyphs_to_names(glyphs,bcnt,&r->u.glyph.back);
+ g = fea_glyphs_to_names(g,ncnt,&r->u.glyph.names);
+ g = fea_glyphs_to_names(g,fcnt,&r->u.glyph.fore);
+ } else {
+ r->u.coverage.ncnt = ncnt;
+ r->u.coverage.bcnt = bcnt;
+ r->u.coverage.fcnt = fcnt;
+ r->u.coverage.ncovers = galloc(ncnt*sizeof(char*));
+ r->u.coverage.bcovers = galloc(bcnt*sizeof(char*));
+ r->u.coverage.fcovers = galloc(fcnt*sizeof(char*));
+ for ( i=0, g=glyphs; i<bcnt; ++i, g=g->next )
+ r->u.coverage.bcovers[i] = copy(g->name_or_class);
+ for ( i=0; i<ncnt; ++i, g=g->next )
+ r->u.coverage.ncovers[i] = copy(g->name_or_class);
+ for ( i=0; i<fcnt; ++i, g=g->next )
+ r->u.coverage.fcovers[i] = copy(g->name_or_class);
+ }
+
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_fpst;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ item->u2.fpst = fpst;
+
+ if ( is_pos ) {
+ for ( g=glyphs; g!=NULL && g->mark_count==0; g=g->next );
+ for ( i=0; g!=NULL; ++i ) {
+ head = NULL;
+ if ( g->lookupname!=NULL ) {
+ head = chunkalloc(sizeof(struct feat_item));
+ head->type = ft_lookup_ref;
+ head->u1.lookup_name = copy(g->lookupname);
+ } else if ( (g->next==NULL || g->next->mark_count!=g->mark_count) &&
+ g->vr!=NULL ) {
+ head = fea_process_pos_single(tok,g,NULL);
+ } else if ( g->next!=NULL && g->mark_count==g->next->mark_count &&
+ (g->vr!=NULL || g->next->vr!=NULL ) &&
+ ( g->next->next==NULL || g->next->next->mark_count!=g->mark_count)) {
+ head = fea_process_pos_pair(tok,g,NULL,false);
+ } else {
+ LogError(_("Unparseable contextual sequence on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ r->lookups[i].lookup = (OTLookup *) head;
+ cnt = g->mark_count;
+ while ( g!=NULL && g->mark_count == cnt ) /* skip everything involved here */
+ g=g->next;
+ for ( ; g!=NULL && g->mark_count==0; g=g->next ); /* skip any uninvolved glyphs */
+ }
+ }
+
+return( fpst );
+}
+
+static void fea_ParseIgnore(struct parseState *tok) {
+ struct markedglyphs *glyphs;
+ int is_pos;
+ FPST *fpst;
+ /* ignore [pos|sub] <marked glyph sequence> (, <marked g sequence>)* */
+
+ fea_ParseTok(tok);
+ if ( tok->type==tk_position )
+ is_pos = true;
+ else if ( tok->type == tk_substitute )
+ is_pos = false;
+ else {
+ LogError(_("The ignore keyword must be followed by either position or substitute on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ is_pos = true;
+ }
+ forever {
+ glyphs = fea_ParseMarkedGlyphs(tok,false/* don't parse value records, etc*/,
+ true/*allow marks*/,false/* no lookups */);
+ fpst = fea_markedglyphs_to_fpst(tok,glyphs,false,true);
+ if ( is_pos )
+ fpst->type = pst_chainpos;
+ fea_markedglyphsFree(glyphs);
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_char || tok->tokbuf[0]!=',' )
+ break;
+ }
+
+ fea_now_semi(tok);
+}
+
+static void fea_ParseSubstitute(struct parseState *tok) {
+ /* name by name => single subs */
+ /* class by name => single subs */
+ /* class by class => single subs */
+ /* name by <glyph sequence> => multiple subs */
+ /* name from <class> => alternate subs */
+ /* <glyph sequence> by name => ligature */
+ /* <marked glyph sequence> by <name> => context chaining */
+ /* <marked glyph sequence> by <lookup name>* => context chaining */
+ /* [ignore sub] <marked glyph sequence> (, <marked g sequence>)* */
+ struct markedglyphs *glyphs = fea_ParseMarkedGlyphs(tok,false,true,false),
+ *g, *rpl, *rp;
+ int cnt, i;
+ SplineChar *sc;
+ struct feat_item *item, *head;
+
+ fea_ParseTok(tok);
+ for ( cnt=0, g=glyphs; g!=NULL; g=g->next, ++cnt );
+ if ( glyphs==NULL ) {
+ LogError(_("Empty subsitute on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else if ( !glyphs->has_marks ) {
+ /* Non-contextual */
+ if ( cnt==1 && glyphs->is_name && tok->type==tk_from ) {
+ /* Alternate subs */
+ char *alts;
+ fea_ParseTok(tok);
+ alts = fea_ParseGlyphClassGuarded(tok);
+ sc = fea_glyphname_get(tok,glyphs->name_or_class);
+ if ( sc!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_alternate;
+ item->u2.pst->u.alt.components = alts;
+ }
+ } else if ( cnt>=1 && tok->type==tk_by ) {
+ rpl = fea_ParseMarkedGlyphs(tok,false,false,false);
+ if ( rpl==NULL ) {
+ LogError(_("No substitution specified on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else if ( rpl->has_marks ) {
+ LogError(_("No marked glyphs allowed in replacement on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else {
+ if ( cnt==1 && rpl->next==NULL ) {
+ tok->sofar = fea_process_sub_single(tok,glyphs,rpl,tok->sofar);
+ } else if ( cnt==1 && glyphs->is_name && rpl->next!=NULL && rpl->is_name ) {
+ /* Multiple substitution */
+ int len=0;
+ char *mult;
+ for ( g=rpl; g!=NULL; g=g->next )
+ len += strlen(g->name_or_class)+1;
+ mult = galloc(len+1);
+ len = 0;
+ for ( g=rpl; g!=NULL; g=g->next ) {
+ strcpy(mult+len,g->name_or_class);
+ len += strlen(g->name_or_class);
+ mult[len++] = ' ';
+ }
+ mult[len-1] = '\0';
+ sc = fea_glyphname_get(tok,glyphs->name_or_class);
+ if ( sc!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_pst;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ item->u1.sc = sc;
+ item->u2.pst = chunkalloc(sizeof(PST));
+ item->u2.pst->type = pst_multiple;
+ item->u2.pst->u.mult.components = mult;
+ }
+ } else if ( cnt>1 && rpl->is_name && rpl->next==NULL ) {
+ tok->sofar = fea_process_sub_ligature(tok,glyphs,rpl,tok->sofar);
+ /* Ligature */
+ } else {
+ LogError(_("Unparseable glyph sequence in substitution on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ }
+ fea_markedglyphsFree(rpl);
+ } else {
+ LogError(_("Expected 'by' or 'from' keywords in substitution on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ } else {
+ /* Contextual */
+ FPST *fpst = fea_markedglyphs_to_fpst(tok,glyphs,false,false);
+ struct fpst_rule *r = fpst->rules;
+ if ( tok->type!=tk_by ) {
+ LogError(_("Expected 'by' keyword in substitution on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ rpl = fea_ParseMarkedGlyphs(tok,false,false,true);
+ for ( g=glyphs; g!=NULL && g->mark_count==0; g=g->next );
+ for ( i=0, rp=rpl; g!=NULL && rp!=NULL; ++i, rp=rp->next ) {
+ if ( rp->lookupname!=NULL ) {
+ head = chunkalloc(sizeof(struct feat_item));
+ head->type = ft_lookup_ref;
+ head->u1.lookup_name = copy(rp->lookupname);
+ } else if ( g->next==NULL || g->next->mark_count!=g->mark_count ) {
+ head = fea_process_sub_single(tok,g,rp,NULL);
+ } else if ( g->next!=NULL && g->mark_count==g->next->mark_count ) {
+ head = fea_process_sub_ligature(tok,g,rpl,NULL);
+ } else {
+ LogError(_("Unparseable contextual sequence on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ r->lookups[i].lookup = (OTLookup *) head;
+ cnt = g->mark_count;
+ while ( g!=NULL && g->mark_count == cnt ) /* skip everything involved here */
+ g=g->next;
+ for ( ; g!=NULL && g->mark_count!=0; g=g->next ); /* skip any uninvolved glyphs */
+ }
+
+ fea_markedglyphsFree(rpl);
+ }
+
+ fea_end_statement(tok);
+ fea_markedglyphsFree(glyphs);
+}
+
+static void fea_ParseMarks(struct parseState *tok) {
+ /* mark name|class <anchor> */
+ char *contents = NULL;
+ SplineChar *sc = NULL;
+ AnchorPoint *ap;
+ char *start, *pt;
+ int ch;
+
+ fea_ParseTok(tok);
+ if ( tok->type==tk_name )
+ sc = fea_glyphname_get(tok,tok->tokbuf);
+ else if ( tok->type==tk_class )
+ contents = fea_lookup_class_complain(tok,tok->tokbuf);
+ else if ( tok->type==tk_char && tok->tokbuf[0]=='[' )
+ contents = fea_ParseGlyphClass(tok);
+ else {
+ LogError(_("Expected glyph name or class in mark statement on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+return;
+ }
+ if ( sc==NULL && contents==NULL ) {
+ fea_skip_to_semi(tok);
+return;
+ }
+
+ fea_TokenMustBe(tok,tk_char,'<');
+ fea_TokenMustBe(tok,tk_anchor,'\0');
+ ap = fea_ParseAnchor(tok);
+ ap->type = at_mark;
+ fea_end_statement(tok);
+
+ if ( ap!=NULL ) {
+ pt = contents;
+ forever {
+ struct feat_item *item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_ap;
+ item->u2.ap = ap;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ start = pt;
+ if ( contents==NULL ) {
+ item->u1.sc = sc;
+ break;
+ }
+ while ( *pt!='\0' && *pt!=' ' )
+ ++pt;
+ ch = *pt; *pt = '\0';
+ sc = fea_glyphname_get(tok,start);
+ *pt = ch;
+ while ( isspace(*pt)) ++pt;
+ if ( sc==NULL ) {
+ tok->sofar = item->next; /* Oops, remove it */
+ chunkfree(item,sizeof(*item));
+ if ( *pt=='\0' ) {
+ AnchorPointsFree(ap);
+ break;
+ }
+ } else {
+ item->u1.sc = sc;
+ if ( *pt=='\0' )
+ break;
+ ap = AnchorPointsCopy(ap);
+ }
+ }
+ }
+ free(contents);
+}
+
+static void fea_ParsePosition(struct parseState *tok, int enumer) {
+ /* name <vr> => single pos */
+ /* class <vr> => single pos */
+ /* name|class <vr> name|class <vr> => pair pos */
+ /* name|class name|class <vr> => pair pos */
+ /* cursive name|class <anchor> <anchor> => cursive positioning */
+ /* name|class <anchor> mark name|class => mark to base pos */
+ /* Must be preceded by a mark statement */
+ /* name|class <anchor> <anchor>+ mark name|class => mark to ligature pos */
+ /* Must be preceded by a mark statement */
+ /* mark name|class <anchor> mark name|class => mark to base pos */
+ /* Must be preceded by a mark statement */
+ /* <marked glyph pos sequence> => context chaining */
+ /* [ignore pos] <marked glyph sequence> (, <marked g sequence>)* */
+ struct markedglyphs *glyphs = fea_ParseMarkedGlyphs(tok,true,true,false), *g;
+ int cnt, i;
+ struct feat_item *item;
+ char *start, *pt, ch;
+ SplineChar *sc;
+
+ fea_ParseTok(tok);
+ for ( cnt=0, g=glyphs; g!=NULL; g=g->next, ++cnt );
+ if ( glyphs==NULL ) {
+ LogError(_("Empty position on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else if ( !glyphs->has_marks ) {
+ /* Non-contextual */
+ if ( glyphs->is_cursive ) {
+ if ( cnt!=1 || glyphs->ap_cnt!=2 ) {
+ LogError(_("Invalid cursive position on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else {
+ start = glyphs->name_or_class;
+ if ( glyphs->anchors[1]!=NULL )
+ glyphs->anchors[1]->type = at_cexit;
+ forever {
+ while ( *start==' ' ) ++start;
+ if ( *start=='\0' )
+ break;
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = fea_glyphname_get(tok,start);
+ *pt = ch; start = pt;
+ if ( sc!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_ap;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ item->u1.sc = sc;
+ if ( glyphs->anchors[0]!=NULL ) {
+ glyphs->anchors[0]->type = at_centry;
+ glyphs->anchors[0]->next = glyphs->anchors[1];
+ item->u2.ap = AnchorPointsCopy(glyphs->anchors[0]);
+ } else
+ item->u2.ap = AnchorPointsCopy(glyphs->anchors[1]);
+ }
+ }
+ }
+ } else if ( cnt==1 && glyphs->vr!=NULL ) {
+ tok->sofar = fea_process_pos_single(tok,glyphs,tok->sofar);
+ } else if ( cnt==2 && (glyphs->vr!=NULL || glyphs->next->vr!=NULL) ) {
+ tok->sofar = fea_process_pos_pair(tok,glyphs,tok->sofar, enumer);
+ } else if ( cnt==1 && glyphs->ap_cnt>=1 && tok->type == tk_mark ) {
+ /* Mark to base, mark to mark, mark to ligature */
+ char *mark_class;
+ AnchorPoint *head=NULL, *last=NULL;
+ if ( tok->type!=tk_mark ) {
+ LogError(_("A mark glyph (or class of marks) must be specified here on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ fea_ParseTok(tok);
+ if ( tok->type==tk_name )
+ mark_class = copy(tok->tokbuf);
+ else
+ mark_class = fea_canonicalClassOrder(fea_ParseGlyphClassGuarded(tok));
+ fea_ParseTok(tok);
+ if ( glyphs->is_mark && glyphs->ap_cnt>1 ) {
+ LogError(_("Mark to base anchor statements may only have one anchor on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ if ( mark_class!=NULL ) {
+ for ( i=0; i<glyphs->ap_cnt; ++i ) {
+ if ( glyphs->anchors[i]==NULL )
+ /* Nothing to be done */;
+ else {
+ if ( glyphs->ap_cnt>1 ) {
+ glyphs->anchors[i]->type = at_baselig;
+ glyphs->anchors[i]->lig_index = i;
+ } else if ( glyphs->is_mark )
+ glyphs->anchors[i]->type = at_basemark;
+ else
+ glyphs->anchors[i]->type = at_basechar;
+ if ( head==NULL )
+ head = glyphs->anchors[i];
+ else
+ last->next = glyphs->anchors[i];
+ last = glyphs->anchors[i];
+ }
+ }
+
+ start = glyphs->name_or_class;
+ forever {
+ while ( *start==' ' ) ++start;
+ if ( *start=='\0' )
+ break;
+ for ( pt=start; *pt!='\0' && *pt!=' '; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = fea_glyphname_get(tok,start);
+ *pt = ch; start = pt;
+ if ( sc!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_ap;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ item->u1.sc = sc;
+ item->u2.ap = AnchorPointsCopy(head);
+ item->mark_class = copy(mark_class);
+ }
+ }
+
+ /* So we can free them properly */
+ for ( ; head!=NULL; head = last ) {
+ last = head->next;
+ head->next = NULL;
+ }
+ }
+ } else {
+ LogError(_("Unparseable glyph sequence in position on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ } else {
+ /* Contextual */
+ (void) fea_markedglyphs_to_fpst(tok,glyphs,true,false);
+ }
+ fea_now_semi(tok);
+ fea_markedglyphsFree(glyphs);
+}
+
+static enum otlookup_type fea_LookupTypeFromItem(struct feat_item *item) {
+ switch ( item->type ) {
+ case ft_pst: case ft_pstclass:
+ switch ( item->u2.pst->type ) {
+ case pst_position:
+return( gpos_single );
+ case pst_pair:
+return( gpos_pair );
+ case pst_substitution:
+return( gsub_single );
+ case pst_alternate:
+return( gsub_alternate );
+ case pst_multiple:
+return( gsub_multiple );
+ case pst_ligature:
+return( gsub_ligature );
+ default:
+return( ot_undef ); /* Can't happen */
+ }
+ break;
+ case ft_ap:
+ switch( item->u2.ap->type ) {
+ case at_centry: case at_cexit:
+return( gpos_cursive );
+ case at_mark:
+return( ot_undef ); /* Can be used in three different lookups. Not enough info */
+ case at_basechar:
+return( gpos_mark2base );
+ case at_baselig:
+return( gpos_mark2ligature );
+ case at_basemark:
+return( gpos_mark2mark );
+ default:
+return( ot_undef ); /* Can't happen */
+ }
+ break;
+ case ft_fpst:
+ switch( item->u2.fpst->type ) {
+ case pst_chainpos:
+return( gpos_contextchain );
+ case pst_chainsub:
+return( gsub_contextchain );
+ default:
+return( ot_undef ); /* Can't happen */
+ }
+ break;
+ default:
+return( ot_undef ); /* Can happen */
+ }
+}
+
+static struct feat_item *fea_AddFeatItem(struct parseState *tok,enum feat_type type,uint32 tag) {
+ struct feat_item *item;
+
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = type;
+ item->u1.tag = tag;
+ item->next = tok->sofar;
+ tok->sofar = item;
+return( item );
+}
+
+static int fea_LookupSwitch(struct parseState *tok) {
+ int enumer = false;
+
+ switch ( tok->type ) {
+ case tk_class:
+ fea_ParseGlyphClassDef(tok);
+ break;
+ case tk_lookupflag:
+ fea_ParseLookupFlags(tok);
+ break;
+ case tk_mark:
+ fea_ParseMarks(tok);
+ break;
+ case tk_ignore:
+ fea_ParseIgnore(tok);
+ break;
+ case tk_enumerate:
+ fea_TokenMustBe(tok,tk_position,'\0');
+ enumer = true;
+ /* Fall through */;
+ case tk_position:
+ fea_ParsePosition(tok,enumer);
+ break;
+ case tk_substitute:
+ fea_ParseSubstitute(tok);
+ enumer = false;
+ break;
+ case tk_subtable:
+ fea_AddFeatItem(tok,ft_subtable,0);
+ fea_TokenMustBe(tok,tk_char,';');
+ break;
+ case tk_char:
+ if ( tok->tokbuf[0]=='}' )
+return( 2 );
+ /* Fall through */
+ default:
+return( 0 );
+ }
+return( 1 );
+}
+
+static void fea_ParseLookupDef(struct parseState *tok, int could_be_stat ) {
+ char *lookup_name;
+ struct feat_item *item, *first_after_mark;
+ enum otlookup_type lookuptype;
+ int has_marks;
+ int ret;
+
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name ) {
+ LogError(_("Expected name in lookup on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+return;
+ }
+ lookup_name = copy(tok->tokbuf);
+ fea_ParseTok(tok);
+ if ( could_be_stat && tok->type==tk_char && tok->tokbuf[0]==';' ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_lookup_ref;
+ item->u1.lookup_name = lookup_name;
+ item->next = tok->sofar;
+ tok->sofar = item;
+return;
+ } else if ( tok->type==tk_useExtension ) /* I just ignore this */
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='{' ) {
+ LogError(_("Expected '{' in feature 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;
+ }
+
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_lookup_start;
+ item->u1.lookup_name = lookup_name;
+ item->next = tok->sofar;
+ tok->sofar = item;
+
+ first_after_mark = NULL;
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->err_count>100 )
+ break;
+ if ( tok->type==tk_eof ) {
+ LogError(_("Unexpected end of file in lookup definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+return;
+ } else if ( (ret = fea_LookupSwitch(tok))==0 ) {
+ LogError(_("Unexpected token, %s, in lookup definition on line %d of %s"), tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ if ( tok->type==tk_name && strcmp(tok->tokbuf,"subs")==0 )
+ LogError(_(" Perhaps you meant to use the keyword 'sub' rather than 'subs'?") );
+ ++tok->err_count;
+return;
+ } else if ( ret==2 )
+ break;
+ /* Ok, mark classes must either contain exactly the same glyphs, or */
+ /* they may not intersect at all */
+ /* Mark2* lookups are not well documented (because adobe FDK doesn't */
+ /* support them) but I'm going to assume that if I have some mark */
+ /* statements, then some pos statement, then another mark statement */
+ /* that I begin a new subtable with the second set of marks (and a */
+ /* different set of mark classes) */
+ if ( tok->sofar!=NULL && tok->sofar->type==ft_subtable )
+ first_after_mark = NULL;
+ else if ( tok->sofar!=NULL && tok->sofar->type==ft_ap ) {
+ if ( tok->sofar->u2.ap->type == at_mark )
+ first_after_mark = NULL;
+ else if ( tok->sofar->mark_class==NULL )
+ /* we don't have to worry about Cursive connections */;
+ else if ( first_after_mark == NULL )
+ first_after_mark = tok->sofar;
+ else {
+ struct feat_item *f;
+ for ( f = tok->sofar->next; f!=NULL; f=f->next ) {
+ if ( f->type==ft_lookup_start || f->type==ft_subtable )
+ break;
+ if ( f->type!=ft_ap || f->mark_class==NULL )
+ continue;
+ if ( strcmp(tok->sofar->mark_class,f->mark_class)==0 )
+ continue; /* same glyphs, that's ok */
+ else if ( fea_classesIntersect(tok->sofar->mark_class,f->mark_class)) {
+ LogError(_("Mark classes must either be exactly the same or contain no common glyphs\n But the class on line %d of %s contains a match."),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ if ( f==first_after_mark )
+ break;
+ }
+ }
+ }
+ }
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name || strcmp(tok->tokbuf,lookup_name)!=0 ) {
+ LogError(_("Expected %s in lookup definition on line %d of %s"),
+ lookup_name, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ fea_end_statement(tok);
+
+ /* Make sure all entries in this lookup of the same lookup type */
+ lookuptype = ot_undef;
+ has_marks = false;
+ for ( item=tok->sofar ; item!=NULL && item->type!=ft_lookup_start; item=item->next ) {
+ enum otlookup_type cur = fea_LookupTypeFromItem(item);
+ if ( item->type==ft_ap && item->u2.ap->type == at_mark )
+ has_marks = true;
+ if ( cur==ot_undef ) /* Some entries in the list (lookupflags) have no type */
+ /* Tum, ty, tum tum */;
+ else if ( lookuptype==ot_undef )
+ lookuptype = cur;
+ else if ( lookuptype!=cur ) {
+ LogError(_("All entries in a lookup must have the same type on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ break;
+ }
+ }
+ if ( lookuptype==ot_undef ) {
+ LogError(_("This lookup has no effect, I can't figure out its type on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else if ( has_marks && lookuptype!=gpos_mark2base &&
+ lookuptype!=gpos_mark2mark &&
+ lookuptype!=gpos_mark2ligature ) {
+ LogError(_("Mark glyphs may not be specified with this type of lookup 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_lookup_end;
+ /* item->u1.lookup_name = lookup_name; */
+ item->next = tok->sofar;
+ tok->sofar = item;
+}
+
+static struct nameid *fea_ParseNameId(struct parseState *tok,int strid) {
+ int platform = 3, specific = 1, language = 0x409;
+ struct nameid *nm;
+ char *start, *pt;
+ int max, ch, value;
+ FILE *in = tok->inlist[tok->inc_depth];
+ /* nameid <id> [<string attibute>] string; */
+ /* "nameid" and <id> will already have been parsed when we get here */
+ /* <string attribute> := <platform> | <platform> <specific> <language> */
+ /* <patform>==3 => <specific>=1 <language>=0x409 */
+ /* <platform>==1 => <specific>=0 <lang>=0 */
+ /* string in double quotes \XXXX escapes to UCS2 (for 3) */
+ /* string in double quotes \XX escapes to MacLatin (for 1) */
+ /* I only store 3,1 strings */
+
+ fea_ParseTok(tok);
+ if ( tok->type == tk_int ) {
+ if ( tok->value!=3 && tok->value!=1 ) {
+ LogError(_("Invalid platform for string on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else if ( tok->value==1 ) {
+ specific = language = 0;
+ }
+ fea_ParseTok(tok);
+ if ( tok->type == tk_int ) {
+ specific = tok->value;
+ tok->base = 0;
+ fea_TokenMustBe(tok,tk_int,'\0');
+ language = tok->value;
+ tok->base = 10;
+ fea_ParseTok(tok);
+ }
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='"' ) {
+ LogError(_("Expected string on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+ nm = NULL;
+ } else {
+ if ( platform==3 && specific==1 ) {
+ nm = chunkalloc(sizeof(struct nameid));
+ nm->strid = strid;
+ nm->platform = platform;
+ nm->specific = specific;
+ nm->language = language;
+ } else
+ nm = NULL;
+ max = 0;
+ pt = start = NULL;
+ while ( (ch=getc(in))!=EOF && ch!='"' ) {
+ if ( ch=='\n' || ch=='\r' )
+ continue; /* Newline characters are ignored here */
+ /* may be specified with backslashes */
+ if ( ch=='\\' ) {
+ int i, dmax = platform==3 ? 4 : 2;
+ value = 0;
+ for ( i=0; i<dmax; ++i ) {
+ ch = getc(in);
+ if ( !ishexdigit(ch)) {
+ ungetc(ch,in);
+ break;
+ }
+ if ( ch>='a' && ch<='f' )
+ ch -= ('a'-10);
+ else if ( ch>='A' && ch<='F' )
+ ch -= ('A'-10);
+ else
+ ch -= '0';
+ value <<= 4;
+ value |= ch;
+ }
+ } else
+ value = ch;
+ if ( nm!=NULL ) {
+ if ( pt-start+3>=max ) {
+ int off = pt-start;
+ start = grealloc(start,(max+=100)+1);
+ pt = start+off;
+ }
+ pt = utf8_idpb(pt,value);
+ }
+ }
+ if ( nm!=NULL ) {
+ if ( pt==NULL )
+ nm->utf8_str = copy("");
+ else {
+ *pt = '\0';
+ nm->utf8_str = copy(start);
+ free(start);
+ }
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='"' ) {
+ LogError(_("End of file found in string on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else
+ fea_end_statement(tok);
+ }
+return( nm );
+}
+
+static struct feat_item *fea_ParseParameters(struct parseState *tok, struct feat_item *feat) {
+ /* Ok. The only time a parameter keyword may be used is inside a 'size' */
+ /* feature and then it takes 4 numbers */
+ /* The first, third and fourth are in decipoints and may be either */
+ /* integers or floats (in which case we must multiply them by 10) */
+ int params[4];
+ int i;
+
+ memset(params,0,sizeof(params));
+ for ( i=0; i<4; ++i ) {
+ params[i] = fea_ParseDeciPoints(tok);
+ if ( tok->type==tk_char && tok->tokbuf[0]==';' )
+ break;
+ }
+ fea_end_statement(tok);
+
+ if ( feat==NULL ) {
+ feat = chunkalloc(sizeof(struct feat_item));
+ feat->type = ft_sizeparams;
+ feat->next = tok->sofar;
+ tok->sofar = feat;
+ }
+ feat->u1.params = galloc(sizeof(params));
+ memcpy(feat->u1.params,params,sizeof(params));
+return( feat );
+}
+
+static struct feat_item *fea_ParseSizeMenuName(struct parseState *tok, struct feat_item *feat) {
+ /* Sizemenuname takes either 0, 1 or 3 numbers and a string */
+ /* if no numbers are given (or the first number is 3) then the string is */
+ /* unicode. Otherwise a mac encoding, treated as single byte */
+ /* Since fontforge only supports windows strings here I shall parse and */
+ /* ignore mac strings */
+ struct nameid *string;
+
+ string = fea_ParseNameId(tok,-1);
+
+ if ( string!=NULL ) {
+ if ( feat==NULL ) {
+ feat = chunkalloc(sizeof(struct feat_item));
+ feat->type = ft_sizeparams;
+ feat->next = tok->sofar;
+ tok->sofar = feat;
+ }
+ string->next = feat->u2.names;
+ feat->u2.names = string;
+ }
+return( feat );
+}
+
+static void fea_ParseFeatureDef(struct parseState *tok) {
+ uint32 feat_tag;
+ struct feat_item *item, *size_item = NULL;
+ int type, ret;
+
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name || !tok->could_be_tag ) {
+ LogError(_("Expected tag in feature on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+return;
+ }
+ feat_tag = tok->tag;
+ tok->in_vkrn = feat_tag == CHR('v','k','r','n');
+
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_feat_start;
+ item->u1.tag = feat_tag;
+ if ( tok->def_langsyses!=NULL )
+ item->u2.sl = SListCopy(tok->def_langsyses);
+ else {
+ item->u2.sl = chunkalloc(sizeof(struct scriptlanglist));
+ item->u2.sl->script = DEFAULT_SCRIPT;
+ item->u2.sl->lang_cnt = 1;
+ item->u2.sl->langs[0] = DEFAULT_LANG;
+ }
+ item->next = tok->sofar;
+ tok->sofar = item;
+
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='{' ) {
+ LogError(_("Expected '{' in feature 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;
+ }
+
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->err_count>100 )
+ break;
+ if ( tok->type==tk_eof ) {
+ LogError(_("Unexpected end of file in feature definition on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+return;
+ } else if ( (ret = fea_LookupSwitch(tok))==0 ) {
+ switch ( tok->type ) {
+ case tk_lookup:
+ fea_ParseLookupDef(tok,true);
+ break;
+ case tk_languagesystem:
+ fea_ParseLangSys(tok,true);
+ break;
+ case tk_feature:
+ /* can appear inside an 'aalt' feature. I don't support it, but */
+ /* just parse and ignore it */
+ if ( feat_tag!=CHR('a','a','l','t')) {
+ LogError(_("Features inside of other features are only permitted for 'aalt' features on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name || !tok->could_be_tag ) {
+ LogError(_("Expected tag on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ fea_end_statement(tok);
+ break;
+ case tk_script:
+ case tk_language:
+ /* If no lang specified after script use 'dflt', if no script specified before a language use 'latn' */
+ type = tok->type==tk_script ? ft_script : ft_lang;
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name || !tok->could_be_tag ) {
+ LogError(_("Expected tag on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ } else {
+ item = fea_AddFeatItem(tok,type,tok->tag);
+ if ( type==ft_lang ) {
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type==tk_include_dflt )
+ /* Unneeded */;
+ else if ( tok->type==tk_exclude_dflt )
+ item->u2.exclude_dflt = true;
+ else if ( tok->type==tk_required )
+ /* Not supported by adobe (or me) */;
+ else if ( tok->type==tk_char && tok->tokbuf[0]==';' )
+ break;
+ else {
+ LogError(_("Expected ';' on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ break;
+ }
+ }
+ } else
+ fea_end_statement(tok);
+ }
+ break;
+ case tk_parameters:
+ if ( feat_tag==CHR('s','i','z','e') ) {
+ size_item = fea_ParseParameters(tok, size_item);
+ break;
+ }
+ /* Fall on through */
+ case tk_name:
+ if ( feat_tag==CHR('s','i','z','e') && strcmp(tok->tokbuf,"sizemenuname")==0 ) {
+ size_item = fea_ParseSizeMenuName(tok, size_item);
+ break;
+ }
+ /* Fall on through */
+ default:
+ LogError(_("Unexpected token, %s, in feature definition on line %d of %s"), tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+return;
+ }
+ } else if ( ret==2 )
+ break;
+ }
+
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name || !tok->could_be_tag || tok->tag!=feat_tag ) {
+ LogError(_("Expected '%c%c%c%c' in lookup definition on line %d of %s"),
+ feat_tag>>24, feat_tag>>16, feat_tag>>8, feat_tag,
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+ fea_end_statement(tok);
+
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_feat_end;
+ item->u1.tag = feat_tag;
+ item->next = tok->sofar;
+ tok->sofar = item;
+
+ tok->in_vkrn = false;
+}
+
+static void fea_ParseNameTable(struct parseState *tok) {
+ struct nameid *head=NULL, *string;
+ struct feat_item *item;
+ /* nameid <id> [<string attibute>] string; */
+
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type != tk_nameid )
+ break;
+ fea_TokenMustBe(tok,tk_int,'\0');
+ string = fea_ParseNameId(tok,tok->value);
+ if ( string!=NULL ) {
+ string->next = head;
+ head = string;
+ }
+ }
+
+ if ( head!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_names;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ item->u2.names = head;
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='}' ) {
+ LogError(_("Expected closing curly brace on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ }
+}
+
+static void fea_ParseTableKeywords(struct parseState *tok, struct tablekeywords *keys) {
+ int index;
+ struct tablevalues *tv, *head = NULL;
+ int i;
+ struct feat_item *item;
+
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type != tk_name )
+ break;
+ for ( index=0; keys[index].name!=NULL; ++index ) {
+ if ( strcmp(keys[index].name,tok->tokbuf)==0 )
+ break;
+ }
+ if ( keys[index].name==NULL ) {
+ LogError(_("Unknown field %s on line %d of %s"), tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ index = -1;
+ }
+ if ( index!=-1 && keys[index].offset!=-1 ) {
+ tv = chunkalloc(sizeof(struct tablevalues));
+ tv->index = index;
+ } else
+ tv = NULL;
+ fea_ParseTok(tok);
+ if ( strcmp(tok->tokbuf,"Vendor")==0 && tv!=NULL) {
+ /* This takes a 4 character string */
+ /* of course strings aren't part of the syntax, but it takes one anyway */
+ if ( tok->type==tk_name && tok->could_be_tag )
+ /* Accept a normal tag, since that's what it really is */
+ tv->value = tok->tag;
+ else if ( tok->type==tk_char && tok->tokbuf[0]=='"' ) {
+ uint8 foo[4]; int ch;
+ FILE *in = tok->inlist[tok->inc_depth];
+ memset(foo,' ',sizeof(foo));
+ for ( i=0; i<4; ++i ) {
+ ch = getc(in);
+ if ( ch==EOF )
+ break;
+ else if ( ch=='"' ) {
+ ungetc(ch,in);
+ break;
+ }
+ foo[i] = ch;
+ }
+ while ( (ch=getc(in))!=EOF && ch!='"' );
+ tok->value=(foo[0]<<24) | (foo[1]<<16) | (foo[2]<<8) | foo[3];
+ } else {
+ LogError(_("Expected string on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ chunkfree(tv,sizeof(*tv));
+ tv = NULL;
+ }
+ fea_ParseTok(tok);
+ } else {
+ if ( tok->type!=tk_int ) {
+ LogError(_("Expected integer on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ chunkfree(tv,sizeof(*tv));
+ tv = NULL;
+ fea_ParseTok(tok);
+ } else {
+ if ( tv!=NULL )
+ tv->value = tok->value;
+ if ( strcmp(keys[index].name,"FontRevision")==0 ) {
+ /* Can take a float */
+ FILE *in = tok->inlist[tok->inc_depth];
+ int ch = getc(in);
+ if ( ch=='.' )
+ for ( ch=getc(in); isdigit(ch); ch=getc(in));
+ ungetc(ch,in);
+ }
+ if ( index!=-1 && keys[index].cnt!=1 ) {
+ int is_panose = strcmp(keys[index].name,"Panose")==0 && tv!=NULL;
+ if ( is_panose )
+ tv->panose_vals[0] = tv->value;
+ for ( i=1; ; ++i ) {
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_int )
+ break;
+ if ( is_panose && i<10 && tv!=NULL )
+ tv->panose_vals[i] = tok->value;
+ }
+ } else
+ fea_ParseTok(tok);
+ }
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!=';' ) {
+ LogError(_("Expected semicolon on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_close_curly(tok);
+ chunkfree(tv,sizeof(*tv));
+ break;
+ }
+ if ( tv!=NULL ) {
+ tv->next = head;
+ head = tv;
+ }
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='}' ) {
+ LogError(_("Expected '}' on line %d of %s"),
+ tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_close_curly(tok);
+ }
+ if ( head!=NULL ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_tablekeys;
+ item->u1.offsets = keys;
+ item->u2.tvals = head;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ }
+}
+
+static void fea_ParseGDEFTable(struct parseState *tok) {
+ /* GlyphClassDef <base> <lig> <mark> <component>; */
+ /* Attach <glyph>|<glyph class> <number>+; */ /* parse & ignore */
+ /* LigatureCaret <glyph>|<glyph class> <caret value>+ */
+ int i;
+ struct feat_item *item;
+ int16 *carets=NULL; int len=0, max=0;
+
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_name )
+ break;
+ if ( strcmp(tok->tokbuf,"Attach")==0 ) {
+ fea_ParseTok(tok);
+ /* Bug. Not parsing inline classes */
+ if ( tok->type!=tk_class && tok->type!=tk_name ) {
+ LogError(_("Expected name or class on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+ } else {
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type!=tk_int )
+ break;
+ }
+ }
+ } else if ( strcmp(tok->tokbuf,"LigatureCaret")==0 ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_lcaret;
+ item->next = tok->sofar;
+ tok->sofar = item;
+
+ fea_ParseTok(tok);
+ if ( tok->type==tk_name )
+ item->u1.class = fea_glyphname_validate(tok,tok->tokbuf);
+ else if ( tok->type==tk_cid )
+ item->u1.class = fea_cid_validate(tok,tok->value);
+ else if ( tok->type == tk_class || (tok->type==tk_char && tok->tokbuf[0]=='['))
+ item->u1.class = fea_ParseGlyphClassGuarded(tok);
+ else {
+ LogError(_("Expected name or class on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+ continue;
+ }
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->type==tk_int )
+ /* Not strictly cricket, but I'll accept it */;
+ else if ( tok->type==tk_char && tok->tokbuf[0]=='<' )
+ fea_ParseCaret(tok);
+ else
+ break;
+ if ( len>=max )
+ carets = grealloc(carets,(max+=10)*sizeof(int16));
+ carets[len++] = tok->value;
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!=';' ) {
+ LogError(_("Expected semicolon on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+ }
+ item->u2.lcaret = galloc((len+1)*sizeof(int16));
+ memcpy(item->u2.lcaret,carets,len*sizeof(int16));
+ item->u2.lcaret[len] = 0;
+ } else if ( strcmp(tok->tokbuf,"GlyphClassDef")==0 ) {
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_gdefclasses;
+ item->u1.gdef_classes = chunkalloc(sizeof(char *[4]));
+ item->next = tok->sofar;
+ tok->sofar = item;
+ for ( i=0; i<4; ++i ) {
+ fea_ParseTok(tok);
+ item->u1.gdef_classes[i] = fea_ParseGlyphClassGuarded(tok);
+ }
+ fea_ParseTok(tok);
+ } else {
+ LogError(_("Expected Attach or LigatureCaret or GlyphClassDef on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ break;
+ }
+ }
+ if ( tok->type!=tk_char || tok->tokbuf[0]!='}' ) {
+ LogError(_("Unexpected token in GDEF on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_close_curly(tok);
+ }
+ free(carets);
+}
+
+static void fea_ParseTableDef(struct parseState *tok) {
+ uint32 table_tag;
+ struct feat_item *item;
+
+ fea_ParseTag(tok);
+ if ( tok->type!=tk_name || !tok->could_be_tag ) {
+ LogError(_("Expected tag in table on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ fea_skip_to_semi(tok);
+return;
+ }
+ table_tag = tok->tag;
+
+ item = chunkalloc(sizeof(struct feat_item));
+ item->type = ft_table;
+ item->u1.tag = table_tag;
+ item->next = tok->sofar;
+ tok->sofar = item;
+ fea_TokenMustBe(tok,tk_char,'{');
+ switch ( table_tag ) {
+ case CHR('G','D','E','F'):
+ fea_ParseGDEFTable(tok);
+ break;
+ case CHR('n','a','m','e'):
+ fea_ParseNameTable(tok);
+ break;
+
+ case CHR('h','h','e','a'):
+ fea_ParseTableKeywords(tok,hhead_keys);
+ break;
+ case CHR('v','h','e','a'):
+ fea_ParseTableKeywords(tok,vhead_keys);
+ break;
+ case CHR('O','S','/','2'):
+ fea_ParseTableKeywords(tok,os2_keys);
+ break;
+
+ case CHR('h','e','a','d'):
+ /* FontRevision <number>.<number>; */
+ /* Only one field here, and I don't really support it */
+ case CHR('v','m','t','x'):
+ /* I don't support 'vmtx' tables */
+ case CHR('B','A','S','E'):
+ /* I don't support 'BASE' tables */
+ default:
+ fea_skip_to_close_curly(tok);
+ break;
+ }
+
+ fea_ParseTag(tok);
+ if ( tok->type!=tk_name || !tok->could_be_tag || tok->tag!=table_tag ) {
+ LogError(_("Expected matching tag in table 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_end_statement(tok);
+}
+
+/* ************************************************************************** */
+/* ******************************* Free feat ******************************** */
+/* ************************************************************************** */
+
+static void NameIdFree(struct nameid *nm) {
+ struct nameid *nmnext;
+
+ while ( nm!=NULL ) {
+ nmnext = nm->next;
+ free( nm->utf8_str );
+ chunkfree(nm,sizeof(*nm));
+ nm = nmnext;
+ }
+}
+
+static void TableValsFree(struct tablevalues *tb) {
+ struct tablevalues *tbnext;
+
+ while ( tb!=NULL ) {
+ tbnext = tb->next;
+ chunkfree(tb,sizeof(*tb));
+ tb = tbnext;
+ }
+}
+
+static void fea_featitemFree(struct feat_item *item) {
+ struct feat_item *next;
+ int i,j;
+
+ while ( item!=NULL ) {
+ next = item->next;
+ switch ( item->type ) {
+ case ft_lookup_end:
+ case ft_feat_end:
+ case ft_table:
+ case ft_subtable:
+ case ft_script:
+ case ft_lang:
+ case ft_lookupflags:
+ /* Nothing needs freeing */;
+ break;
+ case ft_feat_start:
+ case ft_langsys:
+ ScriptLangListFree( item->u2.sl);
+ break;
+ case ft_lookup_start:
+ case ft_lookup_ref:
+ free( item->u1.lookup_name );
+ break;
+ case ft_sizeparams:
+ free( item->u1.params );
+ NameIdFree( item->u2.names );
+ break;
+ case ft_names:
+ NameIdFree( item->u2.names );
+ break;
+ case ft_gdefclasses:
+ for ( i=0; i<4; ++i )
+ free(item->u1.gdef_classes[i]);
+ chunkfree(item->u1.gdef_classes,sizeof(char *[4]));
+ break;
+ case ft_lcaret:
+ free( item->u2.lcaret );
+ break;
+ case ft_tablekeys:
+ TableValsFree( item->u2.tvals );
+ break;
+ case ft_pst:
+ PSTFree( item->u2.pst );
+ break;
+ case ft_pstclass:
+ free( item->u1.class );
+ PSTFree( item->u2.pst );
+ break;
+ case ft_ap:
+ AnchorPointsFree( item->u2.ap );
+ free( item->mark_class );
+ break;
+ case ft_fpst:
+ if ( item->u2.fpst!=NULL ) {
+ for ( i=0; i<item->u2.fpst->rule_cnt; ++i ) {
+ struct fpst_rule *r = &item->u2.fpst->rules[i];
+ for ( j=0; j<r->lookup_cnt; ++j ) {
+ if ( r->lookups[j].lookup!=NULL ) {
+ struct feat_item *nested = (struct feat_item *) (r->lookups[j].lookup);
+ fea_featitemFree(nested);
+ r->lookups[j].lookup = NULL;
+ }
+ }
+ }
+ FPSTFree(item->u2.fpst);
+ }
+ break;
+ default:
+ IError("Don't know how to free a feat_item of type %d", item->type );
+ break;
+ }
+ chunkfree(item,sizeof(*item));
+ item = next;
+ }
+}
+
+static void fea_ParseFeatureFile(struct parseState *tok) {
+
+ forever {
+ fea_ParseTok(tok);
+ if ( tok->err_count>100 )
+ break;
+ switch ( tok->type ) {
+ case tk_class:
+ fea_ParseGlyphClassDef(tok);
+ break;
+ case tk_lookup:
+ fea_ParseLookupDef(tok,false);
+ break;
+ case tk_languagesystem:
+ fea_ParseLangSys(tok,false);
+ break;
+ case tk_feature:
+ fea_ParseFeatureDef(tok);
+ break;
+ case tk_table:
+ fea_ParseTableDef(tok);
+ break;
+ case tk_anonymous:
+ LogError(_("FontForge does not support anonymous tables on line %d of %s"), tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ fea_skip_to_close_curly(tok);
+ break;
+ case tk_eof:
+ goto end_loop;
+ default:
+ LogError(_("Unexpected token, %s, on line %d of %s"), tok->tokbuf, tok->line[tok->inc_depth], tok->filename[tok->inc_depth] );
+ ++tok->err_count;
+ goto end_loop;
+ }
+ }
+ end_loop:;
+}
+
+/* ************************************************************************** */
+/* ******************************* Apply feat ******************************* */
+/* ************************************************************************** */
+static int fea_FeatItemEndsLookup(enum feat_type type) {
+return( type==ft_lookup_end || type == ft_feat_end ||
+ type == ft_table || type == ft_script ||
+ type == ft_lang || type == ft_langsys ||
+ type == ft_lookup_ref );
+}
+
+static struct feat_item *fea_SetLookupLink(struct feat_item *nested,
+ enum otlookup_type type) {
+ struct feat_item *prev = NULL;
+ enum otlookup_type found_type;
+
+ while ( nested!=NULL ) {
+ /* Stop when we find something which forces a new lookup */
+ if ( fea_FeatItemEndsLookup(nested->type) )
+ break;
+ if ( nested->ticked ) {
+ nested = nested->next;
+ continue;
+ }
+ found_type = fea_LookupTypeFromItem(nested);
+ if ( type==ot_undef || found_type == ot_undef || found_type == type ) {
+ if ( nested->type!=ft_ap || nested->u2.ap->type!=at_mark )
+ nested->ticked = true; /* Marks might get used in more than one lookup */
+ if ( prev!=NULL )
+ prev->lookup_next = nested;
+ prev = nested;
+ }
+ nested = nested->next;
+ }
+return( nested );
+}
+
+static void fea_ApplyLookupListPST(struct parseState *tok,
+ struct feat_item *lookup_data,OTLookup *otl) {
+ struct lookup_subtable *sub = NULL, *last=NULL;
+ struct feat_item *l;
+ (void)tok;
+ /* Simple pst's are easy. We just attach them to their proper glyphs */
+ /* and then clear the feat_item pst slot (so we don't free them later) */
+ /* There might be a subtable break */
+ /* There might be a lookupflags */
+
+ for ( l = lookup_data; l!=NULL; l=l->lookup_next ) {
+ switch ( l->type ) {
+ case ft_lookup_start:
+ case ft_lookupflags:
+ /* Ignore these, already handled them */;
+ break;
+ case ft_subtable:
+ sub = NULL;
+ break;
+ case ft_pst:
+ if ( sub==NULL ) {
+ sub = chunkalloc(sizeof(struct lookup_subtable));
+ sub->lookup = otl;
+ sub->per_glyph_pst_or_kern = true;
+ if ( last==NULL )
+ otl->subtables = sub;
+ else
+ last->next = sub;
+ last = sub;
+ }
+ l->u2.pst->subtable = sub;
+ l->u2.pst->next = l->u1.sc->possub;
+ l->u1.sc->possub = l->u2.pst;
+ l->u2.pst = NULL; /* So we don't free it later */
+ break;
+ default:
+ IError("Unexpected feature type %d in a PST feature", l->type );
+ break;
+ }
+ }
+}
+
+static OTLookup *fea_ApplyLookupList(struct parseState *tok,
+ struct feat_item *lookup_data,int lookup_flag);
+
+static void fea_ApplyLookupListContextual(struct parseState *tok,
+ struct feat_item *lookup_data,OTLookup *otl) {
+ struct lookup_subtable *sub = NULL, *last=NULL;
+ struct feat_item *l;
+ int i,j;
+ /* Fpst's are almost as easy as psts. We don't worry about subtables */
+ /* (every fpst gets a new subtable, so the statement is irrelevant) */
+ /* the only complication is that we must recursively handle a lookup list */
+ /* There might be a lookupflags */
+
+ for ( l = lookup_data; l!=NULL; l=l->lookup_next ) {
+ switch ( l->type ) {
+ case ft_lookup_start:
+ case ft_lookupflags:
+ case ft_subtable:
+ /* Ignore these, already handled them */;
+ break;
+ case ft_fpst:
+ sub = chunkalloc(sizeof(struct lookup_subtable));
+ sub->lookup = otl;
+ if ( last==NULL )
+ otl->subtables = sub;
+ else
+ last->next = sub;
+ last = sub;
+ sub->fpst = l->u2.fpst;
+ l->u2.fpst->next = tok->sf->possub;
+ tok->sf->possub = l->u2.fpst;
+ l->u2.fpst = NULL;
+ sub->fpst->subtable = sub;
+ for ( i=0; i<sub->fpst->rule_cnt; ++i ) {
+ struct fpst_rule *r = &sub->fpst->rules[i];
+ for ( j=0; j<r->lookup_cnt; ++j ) {
+ if ( r->lookups[j].lookup!=NULL ) {
+ struct feat_item *nested = (struct feat_item *) (r->lookups[j].lookup);
+ fea_SetLookupLink(nested,ot_undef);
+ r->lookups[j].lookup = fea_ApplyLookupList(tok,nested,otl->lookup_flags); /* Not really sure what the lookup flag should be here. */
+ fea_featitemFree(nested);
+ }
+ }
+ }
+ break;
+ default:
+ IError("Unexpected feature type %d in a FPST feature", l->type );
+ break;
+ }
+ }
+}
+
+static void fea_ApplyLookupListCursive(struct parseState *tok,
+ struct feat_item *lookup_data,OTLookup *otl) {
+ struct lookup_subtable *sub = NULL, *last=NULL;
+ struct feat_item *l;
+ AnchorPoint *aplast, *ap;
+ AnchorClass *ac = NULL;
+ /* Cursive's are also easy. There might be two ap's in the list so slight */
+ /* care needed when adding them to a glyph, and we must create an anchorclass */
+ /* There might be a subtable break */
+ /* There might be a lookupflags */
+
+ for ( l = lookup_data; l!=NULL; l=l->lookup_next ) {
+ switch ( l->type ) {
+ case ft_lookup_start:
+ case ft_lookupflags:
+ /* Ignore these, already handled them */;
+ break;
+ case ft_subtable:
+ sub = NULL;
+ break;
+ case ft_ap:
+ if ( sub==NULL ) {
+ sub = chunkalloc(sizeof(struct lookup_subtable));
+ sub->lookup = otl;
+ sub->anchor_classes = true;
+ if ( last==NULL )
+ otl->subtables = sub;
+ else
+ last->next = sub;
+ last = sub;
+ ac = chunkalloc(sizeof(AnchorClass));
+ ac->subtable = sub;
+ ac->type = act_curs;
+ ac->next = tok->accreated;
+ tok->accreated = ac;
+ }
+ aplast = NULL;
+ for ( ap=l->u2.ap; ap!=NULL; ap=ap->next ) {
+ aplast = ap;
+ ap->anchor = ac;
+ }
+ aplast->next = l->u1.sc->anchor;
+ l->u1.sc->anchor = l->u2.ap;
+ l->u2.ap = NULL; /* So we don't free them later */
+ break;
+ default:
+ IError("Unexpected feature type %d in a cursive feature", l->type );
+ break;
+ }
+ }
+}
+
+static void fea_ApplyLookupListMark2(struct parseState *tok,
+ struct feat_item *lookup_data,int mcnt,OTLookup *otl) {
+ /* Mark2* lookups are not well documented (because adobe FDK doesn't */
+ /* support them) but I'm going to assume that if I have some mark */
+ /* statements, then some pos statement, then another mark statement */
+ /* that I begin a new subtable with the second set of marks (and a */
+ /* different set of mark classes) */
+ char **classes;
+ AnchorClass **acs;
+ int ac_cnt, i;
+ struct lookup_subtable *sub = NULL, *last=NULL;
+ struct feat_item *mark_start, *l;
+ AnchorPoint *ap, *aplast;
+
+ classes = galloc(mcnt*sizeof(char *));
+ acs = galloc(mcnt*sizeof(AnchorClass *));
+ ac_cnt = 0;
+ while ( lookup_data != NULL && lookup_data->type!=ft_lookup_end ) {
+ struct feat_item *orig = lookup_data;
+ sub = NULL;
+ /* Skip any subtable marks */
+ while ( lookup_data!=NULL &&
+ (lookup_data->type==ft_subtable ||
+ lookup_data->type==ft_lookup_start ||
+ lookup_data->type==ft_lookupflags ) )
+ lookup_data = lookup_data->lookup_next;
+
+ /* Skip over the marks, we'll deal with them after we know the mark classes */
+ mark_start = lookup_data;
+ while ( lookup_data!=NULL &&
+ ((lookup_data->type==ft_ap && lookup_data->u2.ap->type==at_mark) ||
+ lookup_data->type==ft_lookup_start ||
+ lookup_data->type==ft_lookupflags ) )
+ lookup_data = lookup_data->lookup_next;
+
+ /* Now process the base glyphs and figure out the mark classes */
+ while ( lookup_data!=NULL &&
+ ((lookup_data->type==ft_ap && lookup_data->mark_class!=NULL) ||
+ lookup_data->type==ft_lookup_start ||
+ lookup_data->type==ft_lookupflags ) ) {
+ if ( lookup_data->type == ft_ap ) {
+ for ( i=0; i<ac_cnt; ++i ) {
+ if ( strcmp(lookup_data->mark_class,classes[i])==0 )
+ break;
+ }
+ if ( i==ac_cnt ) {
+ ++ac_cnt;
+ classes[i] = lookup_data->mark_class;
+ acs[i] = chunkalloc(sizeof(AnchorClass));
+ if ( sub==NULL ) {
+ sub = chunkalloc(sizeof(struct lookup_subtable));
+ sub->lookup = otl;
+ sub->anchor_classes = true;
+ if ( last==NULL )
+ otl->subtables = sub;
+ else
+ last->next = sub;
+ last = sub;
+ }
+ acs[i]->subtable = sub;
+ acs[i]->type = otl->lookup_type==gpos_mark2mark ? act_mkmk :
+ otl->lookup_type==gpos_mark2base ? act_mark :
+ act_mklg;
+ acs[i]->next = tok->accreated;
+ tok->accreated = acs[i];
+ }
+ aplast = NULL;
+ for ( ap=lookup_data->u2.ap; ap!=NULL; ap=ap->next ) {
+ aplast = ap;
+ ap->anchor = acs[i];
+ }
+ aplast->next = lookup_data->u1.sc->anchor;
+ lookup_data->u1.sc->anchor = lookup_data->u2.ap;
+ lookup_data->u2.ap = NULL; /* So we don't free them later */
+ }
+ lookup_data = lookup_data->next;
+ }
+
+ /* Now go back and assign the marks to the correct anchor classes */
+ for ( l=mark_start; l!=NULL &&
+ /* The base aps will have been set to NULL above */
+ ((l->type==ft_ap && l->u2.ap!=NULL && l->u2.ap->type==at_mark) ||
+ l->type==ft_lookup_start ||
+ l->type==ft_lookupflags ) ;
+ l = l->lookup_next ) {
+ if ( l->type==ft_ap ) {
+ for ( i=0; i<ac_cnt; ++i ) {
+ if ( fea_classesIntersect(l->u1.sc->name,classes[i])) {
+ AnchorPoint *ap = AnchorPointsCopy(l->u2.ap);
+ /* We make a copy of this anchor point because marks */
+ /* might be used in more than one lookup. It makes */
+ /* sense for a user to define a set of marks to be */
+ /* used with both a m2base and a m2lig lookup within */
+ /* a feature */
+ ap->anchor = acs[i];
+ ap->next = l->u1.sc->anchor;
+ l->u1.sc->anchor = ap;
+ break;
+ }
+ }
+ }
+ }
+ if ( lookup_data==orig )
+ break;
+ }
+}
+
+
+static int is_blank(const char *s) {
+ int i;
+
+ i = 0;
+ while (s[i] != '\0' && s[i] == ' ')
+ i++;
+ return( s[i] == '\0');
+}
+
+struct class_set {
+ char **classes;
+ int cnt, max;
+};
+
+/* We've got a set of glyph classes -- but they are the classes that make sense */
+/* to the user and so there's no guarantee that there aren't two classes with */
+/* the same glyph(s) */
+/* Simplify the list so that: There are no duplicates classes and each name */
+/* appears in at most one class. This is what we need */
+static void fea_canonicalClassSet(struct class_set *set) {
+ int i,j,k;
+
+ /* Remove any duplicate classes */
+ qsort(set->classes,set->cnt,sizeof(char *), strcmpD);
+ for ( i=0; i<set->cnt; ++i ) {
+ for ( j=i+1; j<set->cnt; ++j )
+ if ( strcmp(set->classes[i],set->classes[j])!=0 )
+ break;
+ if ( j>i+1 ) {
+ int off = j-(i+1);
+ for ( k=i+1; k<j; ++k )
+ free(set->classes[k]);
+ for ( k=j ; k<set->cnt; ++k )
+ set->classes[k-off] = set->classes[k];
+ set->cnt -= off;
+ }
+ }
+
+ for ( i=0; i < set->cnt - 1; ++i ) {
+ for ( j=i+1; j < set->cnt; ++j ) {
+ if ( fea_classesIntersect(set->classes[i],set->classes[j]) ) {
+ if ( set->cnt>=set->max )
+ set->classes = grealloc(set->classes,(set->max+=20)*sizeof(char *));
+ set->classes[set->cnt++] = fea_classesSplit(set->classes[i],set->classes[j]);
+ }
+ }
+ }
+
+ /* Remove empty classes */
+ i = 0;
+ while (i < set->cnt) {
+ if (is_blank(set->classes[i])) {
+ free(set->classes[i]);
+ for ( k=i+1 ; k < set->cnt; ++k )
+ set->classes[k-1] = set->classes[k];
+ set->cnt -= 1;
+ } else {
+ i++;
+ }
+ }
+}
+
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+static void KCFillDevTab(KernClass *kc,int index,DeviceTable *dt) {
+ if ( dt==NULL || dt->corrections == NULL )
+return;
+ if ( kc->adjusts == NULL )
+ kc->adjusts = gcalloc(kc->first_cnt*kc->second_cnt,sizeof(DeviceTable));
+ kc->adjusts[index] = *dt;
+ kc->adjusts[index].corrections = galloc(dt->last_pixel_size-dt->first_pixel_size+1);
+ memcpy(kc->adjusts[index].corrections,dt->corrections,dt->last_pixel_size-dt->first_pixel_size+1);
+
+}
+
+static void KPFillDevTab(KernPair *kp,DeviceTable *dt) {
+ if ( dt==NULL || dt->corrections == NULL )
+return;
+ kp->adjust = chunkalloc(sizeof(DeviceTable));
+ *kp->adjust = *dt;
+ kp->adjust->corrections = galloc(dt->last_pixel_size-dt->first_pixel_size+1);
+ memcpy(kp->adjust->corrections,dt->corrections,dt->last_pixel_size-dt->first_pixel_size+1);
+}
+#endif
+
+static void fea_fillKernClass(KernClass *kc,struct feat_item *l) {
+ int i,j;
+ PST *pst;
+
+ while ( l!=NULL && l->type!=ft_subtable ) {
+ if ( l->type==ft_pstclass ) {
+ pst = l->u2.pst;
+ for ( i=1; i<kc->first_cnt; ++i ) {
+ if ( fea_classesIntersect(kc->firsts[i],l->u1.class) ) {
+ for ( j=1; j<kc->second_cnt; ++j ) {
+ if ( fea_classesIntersect(kc->seconds[j],pst->u.pair.paired) ) {
+ /* FontForge only supports kerning classes in one direction at a time, not full value records */
+ if ( pst->u.pair.vr[0].h_adv_off != 0 ) {
+ kc->offsets[i*kc->second_cnt+j] = pst->u.pair.vr[0].h_adv_off;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( pst->u.pair.vr[0].adjust!=NULL )
+ KCFillDevTab(kc,i*kc->second_cnt+j,&pst->u.pair.vr[0].adjust->xadv);
+#endif
+ } else if ( pst->u.pair.vr[0].v_adv_off != 0 ) {
+ kc->offsets[i*kc->second_cnt+j] = pst->u.pair.vr[0].v_adv_off;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( pst->u.pair.vr[0].adjust!=NULL )
+ KCFillDevTab(kc,i*kc->second_cnt+j,&pst->u.pair.vr[0].adjust->yadv);
+#endif
+ } else if ( pst->u.pair.vr[1].h_adv_off != 0 ) {
+ kc->offsets[i*kc->second_cnt+j] = pst->u.pair.vr[1].h_adv_off;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( pst->u.pair.vr[1].adjust!=NULL )
+ KCFillDevTab(kc,i*kc->second_cnt+j,&pst->u.pair.vr[1].adjust->xadv);
+#endif
+ }
+ if ( strcmp(kc->seconds[j],pst->u.pair.paired)==0 )
+ break;
+ }
+ }
+ if ( strcmp(kc->firsts[i],l->u1.class)==0 )
+ break;
+ }
+ }
+ }
+ l = l->lookup_next;
+ }
+}
+
+static void SFKernClassRemoveFree(SplineFont *sf,KernClass *kc) {
+ KernClass *prev;
+
+ if ( sf->kerns==kc )
+ sf->kerns = kc->next;
+ else if ( sf->vkerns==kc )
+ sf->vkerns = kc->next;
+ else {
+ prev = NULL;
+ if ( sf->kerns!=NULL )
+ for ( prev=sf->kerns; prev!=NULL && prev->next!=kc; prev=prev->next );
+ if ( prev==NULL && sf->vkerns!=NULL )
+ for ( prev=sf->vkerns; prev!=NULL && prev->next!=kc; prev=prev->next );
+ if ( prev!=NULL )
+ prev->next = kc->next;
+ }
+ kc->next = NULL;
+ KernClassListFree(kc);
+}
+
+static void fea_ApplyLookupListPair(struct parseState *tok,
+ struct feat_item *lookup_data,int kmax,OTLookup *otl) {
+ /* kcnt is the number of left/right glyph-name-lists we must sort into classes */
+ struct feat_item *l, *first;
+ struct class_set lefts, rights;
+ struct lookup_subtable *sub = NULL, *lastsub=NULL;
+ SplineChar *sc, *other;
+ PST *pst;
+ KernPair *kp;
+ KernClass *kc;
+ int vkern, kcnt, i;
+
+ memset(&lefts,0,sizeof(lefts));
+ memset(&rights,0,sizeof(rights));
+ if ( kmax!=0 ) {
+ lefts.classes = galloc(kmax*sizeof(char *));
+ rights.classes = galloc(kmax*sizeof(char *));
+ lefts.max = rights.max = kmax;
+ }
+ vkern = false;
+ for ( l = lookup_data; l!=NULL; ) {
+ first = l;
+ kcnt = 0;
+ while ( l!=NULL && l->type!=ft_subtable ) {
+ if ( l->type == ft_pst ) {
+ if ( sub==NULL ) {
+ sub = chunkalloc(sizeof(struct lookup_subtable));
+ sub->lookup = otl;
+ sub->per_glyph_pst_or_kern = true;
+ if ( lastsub==NULL )
+ otl->subtables = sub;
+ else
+ lastsub->next = sub;
+ lastsub = sub;
+ }
+ pst = l->u2.pst;
+ sc = l->u1.sc;
+ l->u2.pst = NULL;
+ kp = NULL;
+ other = SFGetChar(sc->parent,-1,pst->u.pair.paired);
+ if ( pst->u.pair.vr[0].xoff==0 && pst->u.pair.vr[0].yoff==0 &&
+ pst->u.pair.vr[1].xoff==0 && pst->u.pair.vr[1].yoff==0 &&
+ pst->u.pair.vr[1].v_adv_off==0 &&
+ other!=NULL ) {
+ if ( (otl->lookup_flags&pst_r2l) &&
+ (pst->u.pair.vr[0].h_adv_off==0 && pst->u.pair.vr[0].v_adv_off==0 )) {
+ kp = chunkalloc(sizeof(KernPair));
+ kp->off = pst->u.pair.vr[1].h_adv_off;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( pst->u.pair.vr[1].adjust!=NULL )
+ KPFillDevTab(kp,&pst->u.pair.vr[1].adjust->xadv);
+#endif
+ } else if ( !(otl->lookup_flags&pst_r2l) &&
+ (pst->u.pair.vr[1].h_adv_off==0 && pst->u.pair.vr[0].v_adv_off==0 )) {
+ kp = chunkalloc(sizeof(KernPair));
+ kp->off = pst->u.pair.vr[0].h_adv_off;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( pst->u.pair.vr[0].adjust!=NULL )
+ KPFillDevTab(kp,&pst->u.pair.vr[0].adjust->xadv);
+#endif
+ } else if ( (pst->u.pair.vr[0].h_adv_off==0 && pst->u.pair.vr[1].h_adv_off==0 )) {
+ vkern = sub->vertical_kerning = true;
+ kp = chunkalloc(sizeof(KernPair));
+ kp->off = pst->u.pair.vr[0].v_adv_off;
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ if ( pst->u.pair.vr[0].adjust!=NULL )
+ KPFillDevTab(kp,&pst->u.pair.vr[0].adjust->yadv);
+#endif
+ }
+ }
+ if ( kp!=NULL ) {
+ kp->sc = other;
+ kp->subtable = sub;
+ if ( vkern ) {
+ kp->next = sc->vkerns;
+ sc->vkerns = kp;
+ } else {
+ kp->next = sc->kerns;
+ sc->kerns = kp;
+ }
+ PSTFree(pst);
+ } else {
+ pst->subtable = sub;
+ pst->next = sc->possub;
+ sc->possub = pst;
+ }
+ } else if ( l->type == ft_pstclass ) {
+ lefts.classes[kcnt] = copy(fea_canonicalClassOrder(l->u1.class));
+ rights.classes[kcnt++] = copy(fea_canonicalClassOrder(l->u2.pst->u.pair.paired));
+ }
+ l = l->lookup_next;
+ }
+ if ( kcnt!=0 ) {
+ lefts.cnt = rights.cnt = kcnt;
+ fea_canonicalClassSet(&lefts);
+ fea_canonicalClassSet(&rights);
+
+ sub = chunkalloc(sizeof(struct lookup_subtable));
+ sub->lookup = otl;
+ if ( lastsub==NULL )
+ otl->subtables = sub;
+ else
+ lastsub->next = sub;
+ lastsub = sub;
+
+ if ( sub->kc!=NULL )
+ SFKernClassRemoveFree(tok->sf,sub->kc);
+ sub->kc = kc = chunkalloc(sizeof(KernClass));
+ kc->first_cnt = lefts.cnt+1; kc->second_cnt = rights.cnt+1;
+ kc->firsts = galloc(kc->first_cnt*sizeof(char *));
+ kc->seconds = galloc(kc->second_cnt*sizeof(char *));
+ kc->firsts[0] = kc->seconds[0] = NULL;
+ for ( i=0; i<lefts.cnt; ++i )
+ kc->firsts[i+1] = lefts.classes[i];
+ for ( i=0; i<rights.cnt; ++i )
+ kc->seconds[i+1] = rights.classes[i];
+ kc->subtable = sub;
+ kc->offsets = gcalloc(kc->first_cnt*kc->second_cnt,sizeof(int16));
+#ifdef FONTFORGE_CONFIG_DEVICETABLES
+ kc->adjusts = gcalloc(kc->first_cnt*kc->second_cnt,sizeof(DeviceTable));
+#endif
+ fea_fillKernClass(kc,first);
+ if ( sub->vertical_kerning ) {
+ kc->next = tok->sf->vkerns;
+ tok->sf->vkerns = kc;
+ } else {
+ kc->next = tok->sf->kerns;
+ tok->sf->kerns = kc;
+ }
+ }
+ sub = NULL;
+ while ( l!=NULL && l->type==ft_subtable )
+ l = l->lookup_next;
+ }
+ if ( kmax!=0 ) {
+ free(lefts.classes);
+ free(rights.classes);
+ }
+}
+
+static OTLookup *fea_ApplyLookupList(struct parseState *tok,
+ struct feat_item *lookup_data,int lookup_flag) {
+ /* A lookup list might consist just of a lookup_ref so find the lookup named u1.lookup_name */
+ /* A lookup_start is optional and provides the lookup name */
+ /* A lookupflags is optional and may occur anywhere u2.lookupflags */
+ /* An ap is for mark2 types for the mark u1.sc and u2.ap (not grouped in anchor classes yet) */
+ /* A fpst is for contextuals u2.fpst (rule.lookups[i].lookup are lookup lists in their own rights that need to become lookups) */
+ /* A subtable means a subtable break, make up a new name, ignore multiple subtable entries */
+ /* A pst is for simple things u1.sc, u2.pst */
+ /* A pstclass is for kerning classes u1.class, u2.pst (paired may be a class list too) */
+ /* An ap is for cursive types for the u1.sc and u2.ap (an entry and an exit ap) */
+ /* An ap is for mark2 types for the base u1.sc and u2.ap and mark_class */
+ OTLookup *otl;
+ int kcnt, mcnt;
+ struct feat_item *l;
+ enum otlookup_type temp;
+
+ if ( lookup_data->type == ft_lookup_ref ) {
+ for ( otl=tok->created; otl!=NULL; otl=otl->next )
+ if ( otl->lookup_name!=NULL &&
+ strcmp(otl->lookup_name,lookup_data->u1.lookup_name)==0)
+return( otl );
+ otl = SFFindLookup(tok->sf,lookup_data->u1.lookup_name);
+ if ( otl==NULL )
+ LogError( _("No lookup named %s"),lookup_data->u1.lookup_name );
+ /* Can't give a line number, this is second pass */
+return( otl );
+ }
+
+ otl = chunkalloc(sizeof(OTLookup));
+ otl->lookup_flags = lookup_flag;
+ otl->lookup_type = ot_undef;
+ if ( tok->last==NULL )
+ tok->created = otl;
+ else
+ tok->last->next = otl;
+ tok->last = otl;
+
+ /* Search first for class counts */
+ kcnt = mcnt = 0;
+ for ( l = lookup_data; l!=NULL; l=l->lookup_next ) {
+ if ( l->type == ft_ap && l->mark_class!=NULL )
+ ++mcnt;
+ else if ( l->type == ft_pstclass )
+ ++kcnt;
+ else if ( l->type == ft_lookupflags )
+ otl->lookup_flags = l->u2.lookupflags;
+ else if ( l->type == ft_lookup_start ) {
+ otl->lookup_name = l->u1.lookup_name;
+ l->u1.lookup_name = NULL; /* So we don't free it later */
+ }
+ temp = fea_LookupTypeFromItem(l);
+ if ( temp==ot_undef )
+ /* Tum ty tum tum. No information */;
+ else if ( otl->lookup_type == ot_undef )
+ otl->lookup_type = temp;
+ else if ( otl->lookup_type != temp )
+ IError(_("Mismatch lookup types inside a parsed lookup"));
+ }
+ if ( otl->lookup_type==gpos_mark2base ||
+ otl->lookup_type==gpos_mark2ligature ||
+ otl->lookup_type==gpos_mark2mark )
+ fea_ApplyLookupListMark2(tok,lookup_data,mcnt,otl);
+ else if ( mcnt!=0 )
+ IError(_("Mark anchors provided when nothing can use them"));
+ else if ( otl->lookup_type==gpos_cursive )
+ fea_ApplyLookupListCursive(tok,lookup_data,otl);
+ else if ( otl->lookup_type==gpos_pair )
+ fea_ApplyLookupListPair(tok,lookup_data,kcnt,otl);
+ else if ( otl->lookup_type==gpos_contextchain ||
+ otl->lookup_type==gsub_contextchain )
+ fea_ApplyLookupListContextual(tok,lookup_data,otl);
+ else
+ fea_ApplyLookupListPST(tok,lookup_data,otl);
+return( otl );
+}
+
+static struct otfname *fea_NameID2OTFName(struct nameid *names) {
+ struct otfname *head=NULL, *cur;
+
+ while ( names!=NULL ) {
+ cur = chunkalloc(sizeof(struct otfname));
+ cur->lang = names->language;
+ cur->name = names->utf8_str;
+ names->utf8_str = NULL;
+ cur->next = head;
+ head = cur;
+ names = names->next;
+ }
+return( head );
+}
+
+static void fea_AttachFeatureToLookup(OTLookup *otl,uint32 feat_tag,
+ struct scriptlanglist *sl) {
+ FeatureScriptLangList *fl;
+
+ if ( otl==NULL )
+return;
+
+ for ( fl = otl->features; fl!=NULL && fl->featuretag!=feat_tag; fl=fl->next );
+ if ( fl==NULL ) {
+ fl = chunkalloc(sizeof(FeatureScriptLangList));
+ fl->next = otl->features;
+ otl->features = fl;
+ fl->featuretag = feat_tag;
+ fl->scripts = SListCopy(sl);
+ } else
+ SLMerge(fl,sl);
+}
+
+static void fea_NameID2NameTable(SplineFont *sf, struct nameid *names) {
+ struct ttflangname *cur;
+
+ while ( names!=NULL ) {
+ for ( cur = sf->names; cur!=NULL && cur->lang!=names->language; cur=cur->next );
+ if ( cur==NULL ) {
+ cur = chunkalloc(sizeof(struct ttflangname));
+ cur->lang = names->language;
+ cur->next = sf->names;
+ sf->names = cur;
+ }
+ free(cur->names[names->strid]);
+ cur->names[names->strid] = names->utf8_str;
+ names->utf8_str = NULL;
+ names = names->next;
+ }
+}
+
+static void fea_TableByKeywords(SplineFont *sf, struct feat_item *f) {
+ struct tablevalues *tv;
+ struct tablekeywords *offsets = f->u1.offsets, *cur;
+ int i;
+
+ if ( !sf->pfminfo.pfmset ) {
+ SFDefaultOS2Info(&sf->pfminfo,sf,sf->fontname);
+ sf->pfminfo.pfmset = sf->pfminfo.subsuper_set = sf->pfminfo.panose_set =
+ sf->pfminfo.hheadset = sf->pfminfo.vheadset = true;
+ }
+ for ( tv = f->u2.tvals; tv!=NULL; tv=tv->next ) {
+ cur = &offsets[tv->index];
+ if ( cur->offset==-1 )
+ /* We don't support this guy, whatever he may be, but we did parse it */;
+ else if ( cur->cnt==1 ) {
+ if ( cur->size==4 )
+ *((uint32 *) (((uint8 *) sf) + cur->offset)) = tv->value;
+ else if ( cur->size==2 )
+ *((uint16 *) (((uint8 *) sf) + cur->offset)) = tv->value;
+ else
+ *((uint8 *) (((uint8 *) sf) + cur->offset)) = tv->value;
+ if ( strcmp(cur->name,"Ascender")==0 )
+ sf->pfminfo.hheadascent_add = false;
+ else if ( strcmp(cur->name,"Descender")==0 )
+ sf->pfminfo.hheaddescent_add = false;
+ else if ( strcmp(cur->name,"winAscent")==0 )
+ sf->pfminfo.winascent_add = false;
+ else if ( strcmp(cur->name,"winDescent")==0 )
+ sf->pfminfo.windescent_add = false;
+ else if ( strcmp(cur->name,"TypoAscender")==0 )
+ sf->pfminfo.typoascent_add = false;
+ else if ( strcmp(cur->name,"TypoDescender")==0 )
+ sf->pfminfo.typodescent_add = false;
+ } else if ( cur->cnt==10 && cur->size==1 ) {
+ for ( i=0; i<10; ++i )
+ (((uint8 *) sf) + cur->offset)[i] = tv->panose_vals[i];
+ }
+ }
+}
+
+static void fea_GDefGlyphClasses(SplineFont *sf, struct feat_item *f) {
+ int i, ch;
+ char *pt, *start;
+ SplineChar *sc;
+
+ for ( i=0; i<4; ++i ) if ( f->u1.gdef_classes[i]!=NULL ) {
+ for ( pt=f->u1.gdef_classes[i]; ; ) {
+ while ( *pt==' ' ) ++pt;
+ if ( *pt=='\0' )
+ break;
+ for ( start = pt; *pt!=' ' && *pt!='\0'; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = SFGetChar(sf,-1,start);
+ *pt = ch;
+ if ( sc!=NULL )
+ sc->glyph_class = i+1;
+ }
+ }
+}
+
+static void fea_GDefLigCarets(SplineFont *sf, struct feat_item *f) {
+ int i, ch;
+ char *pt, *start;
+ SplineChar *sc;
+ PST *pst, *prev, *next;
+
+ for ( pt=f->u1.class; ; ) {
+ while ( *pt==' ' ) ++pt;
+ if ( *pt=='\0' )
+ break;
+ for ( start = pt; *pt!=' ' && *pt!='\0'; ++pt );
+ ch = *pt; *pt = '\0';
+ sc = SFGetChar(sf,-1,start);
+ *pt = ch;
+ if ( sc!=NULL ) {
+ for ( prev=NULL, pst=sc->possub; pst!=NULL; pst=next ) {
+ next = pst->next;
+ if ( pst->type!=pst_lcaret )
+ prev = pst;
+ else {
+ if ( prev==NULL )
+ sc->possub = next;
+ else
+ prev->next = next;
+ pst->next = NULL;
+ PSTFree(pst);
+ }
+ }
+ for ( i=0; f->u2.lcaret[i]!=0; ++i );
+ pst = chunkalloc(sizeof(PST));
+ pst->next = sc->possub;
+ sc->possub = pst;
+ pst->type = pst_lcaret;
+ pst->u.lcaret.cnt = i;
+ pst->u.lcaret.carets = f->u2.lcaret;
+ f->u2.lcaret = NULL;
+ }
+ }
+}
+
+static struct feat_item *fea_ApplyFeatureList(struct parseState *tok,
+ struct feat_item *feat_data) {
+ int lookup_flags = 0;
+ uint32 feature_tag = feat_data->u1.tag;
+ struct scriptlanglist *sl = feat_data->u2.sl;
+ struct feat_item *f, *start;
+ OTLookup *otl;
+ int saw_script = false;
+ enum otlookup_type ltype;
+
+ feat_data->u2.sl = NULL;
+
+ for ( f=feat_data->next; f!=NULL && f->type!=ft_feat_end ; ) {
+ if ( f->ticked ) {
+ f = f->next;
+ continue;
+ }
+ switch ( f->type ) {
+ case ft_lookupflags:
+ lookup_flags = f->u2.lookupflags;
+ f = f->next;
+ continue;
+ case ft_lookup_ref:
+ otl = fea_ApplyLookupList(tok,f,lookup_flags);
+ fea_AttachFeatureToLookup(otl,feature_tag,sl);
+ f = f->next;
+ continue;
+ case ft_lookup_start:
+ start = f;
+ start->lookup_next = f->next;
+ f = fea_SetLookupLink(start->next,ot_undef);
+ if ( f!=NULL && f->type == ft_lookup_end )
+ f = f->next;
+ otl = fea_ApplyLookupList(tok,start,lookup_flags);
+ fea_AttachFeatureToLookup(otl,feature_tag,sl);
+ continue;
+ case ft_script:
+ ScriptLangListFree(sl);
+ sl = chunkalloc(sizeof(struct scriptlanglist));
+ sl->script = f->u1.tag;
+ sl->lang_cnt = 1;
+ sl->langs[0] = DEFAULT_LANG;
+ saw_script = true;
+ f = f->next;
+ continue;
+ case ft_lang:
+ if ( !saw_script ) {
+ ScriptLangListFree(sl);
+ sl = chunkalloc(sizeof(struct scriptlanglist));
+ sl->script = CHR('l','a','t','n');
+ }
+ sl->langs[0] = f->u1.tag;
+ sl->lang_cnt = 1;
+ if ( !f->u2.exclude_dflt ) {
+ if ( sl->langs[0]!=DEFAULT_LANG ) {
+ sl->langs[1] = DEFAULT_LANG;
+ sl->lang_cnt = 2;
+ }
+ }
+ f = f->next;
+ continue;
+ case ft_langsys:
+ ScriptLangListFree(sl);
+ saw_script = false;
+ sl = f->u2.sl;
+ f->u2.sl = NULL;
+ f = f->next;
+ continue;
+ case ft_sizeparams:
+ if ( f->u1.params!=NULL ) {
+ tok->sf->design_size = f->u1.params[0];
+ tok->sf->fontstyle_id = f->u1.params[1];
+ tok->sf->design_range_bottom = f->u1.params[2];
+ tok->sf->design_range_top = f->u1.params[3];
+ }
+ OtfNameListFree(tok->sf->fontstyle_name);
+ tok->sf->fontstyle_name = fea_NameID2OTFName(f->u2.names);
+ f = f->next;
+ continue;
+ case ft_subtable:
+ f = f->next;
+ continue;
+ case ft_pst:
+ case ft_pstclass:
+ case ft_ap:
+ case ft_fpst:
+ if ( f->type==ft_ap && f->u2.ap->type==at_mark ) {
+ struct feat_item *n, *a;
+ /* skip over the marks */
+ for ( n=f; n!=NULL && n->type == ft_ap && n->u2.ap->type==at_mark; n=n->next );
+ /* find the next thing which can use those marks (might not be anything) */
+ for ( a=n; a!=NULL; a=a->next ) {
+ if ( a->ticked )
+ continue;
+ if ( fea_FeatItemEndsLookup(a->type) ||
+ a->type==ft_subtable || a->type==ft_ap )
+ break;
+ }
+ if ( a==NULL || fea_FeatItemEndsLookup(a->type) || a->type==ft_subtable ||
+ (a->type==ft_ap && a->u2.ap->type == at_mark )) {
+ /* There's nothing else that can use these marks so we are */
+ /* done with them. Skip over all of them */
+ f = n;
+ continue;
+ }
+ ltype = fea_LookupTypeFromItem(a);
+ } else
+ ltype = fea_LookupTypeFromItem(f);
+ start = f;
+ f = fea_SetLookupLink(start,ltype);
+ otl = fea_ApplyLookupList(tok,start,lookup_flags);
+ fea_AttachFeatureToLookup(otl,feature_tag,sl);
+ continue;
+ default:
+ IError("Unexpected feature item in feature definition %d", f->type );
+ f = f->next;
+ }
+ }
+ if ( f!=NULL && f->type == ft_feat_end )
+ f = f->next;
+return( f );
+}
+
+static void fea_ApplyFile(struct parseState *tok, struct feat_item *item) {
+ struct feat_item *f, *start;
+
+ for ( f=item; f!=NULL ; ) {
+ switch ( f->type ) {
+ case ft_lookup_start:
+ start = f;
+ start->lookup_next = f->next;
+ f = fea_SetLookupLink(start->next,ot_undef);
+ if ( f!=NULL && f->type == ft_lookup_end )
+ f = f->next;
+ fea_ApplyLookupList(tok,start,0);
+ continue;
+ case ft_feat_start:
+ f = fea_ApplyFeatureList(tok,f);
+ continue;
+ case ft_table:
+ /* I store things all mushed together, so this tag is useless to me*/
+ /* ignore it. The stuff inside the table matters though... */
+ f = f->next;
+ continue;
+ case ft_names:
+ fea_NameID2NameTable(tok->sf,f->u2.names);
+ f = f->next;
+ continue;
+ case ft_tablekeys:
+ fea_TableByKeywords(tok->sf,f);
+ f = f->next;
+ continue;
+ case ft_gdefclasses:
+ fea_GDefGlyphClasses(tok->sf,f);
+ f = f->next;
+ continue;
+ case ft_lcaret:
+ fea_GDefLigCarets(tok->sf,f);
+ f = f->next;
+ continue;
+ default:
+ IError("Unexpected feature item in feature file %d", f->type );
+ f = f->next;
+ }
+ }
+}
+
+static struct feat_item *fea_reverseList(struct feat_item *f) {
+ struct feat_item *n = NULL, *p = NULL;
+
+ p = NULL;
+ while ( f!=NULL ) {
+ n = f->next;
+ f->next = p;
+ p = f;
+ f = n;
+ }
+return( p );
+}
+
+static void fea_NameLookups(struct parseState *tok) {
+ SplineFont *sf = tok->sf;
+ OTLookup *gpos_last=NULL, *gsub_last=NULL, *otl, *otlnext;
+ int gp_cnt=0, gs_cnt=0, acnt;
+ AnchorClass *ac, *acnext, *an;
+
+ for ( otl = sf->gpos_lookups; otl!=NULL; otl=otl->next ) {
+ otl->lookup_index = gp_cnt++;
+ gpos_last = otl;
+ }
+ for ( otl = sf->gsub_lookups; otl!=NULL; otl=otl->next ) {
+ otl->lookup_index = gs_cnt++;
+ gsub_last = otl;
+ }
+
+ for ( otl = tok->created; otl!=NULL; otl=otlnext ) {
+ otlnext = otl->next;
+ otl->next = NULL;
+ if ( otl->lookup_name!=NULL && SFFindLookup(sf,otl->lookup_name)!=NULL ) {
+ int cnt=0;
+ char *namebuf = galloc(strlen( otl->lookup_name )+8 );
+ /* Name already in use, modify it */
+ do {
+ sprintf(namebuf,"%s-%d", otl->lookup_name, cnt++ );
+ } while ( SFFindLookup(sf,namebuf)!=NULL );
+ free(otl->lookup_name);
+ otl->lookup_name = namebuf;
+ }
+ if ( otl->lookup_type < gpos_start ) {
+ if ( gsub_last==NULL )
+ sf->gsub_lookups = otl;
+ else
+ gsub_last->next = otl;
+ gsub_last = otl;
+ otl->lookup_index = gs_cnt++;
+ } else {
+ if ( gpos_last==NULL )
+ sf->gpos_lookups = otl;
+ else
+ gpos_last->next = otl;
+ gpos_last = otl;
+ otl->lookup_index = gp_cnt++;
+ }
+ NameOTLookup(otl,sf); /* But only if it has no name */
+ }
+
+ /* Now name and attach any unnamed anchor classes (order here doesn't matter) */
+ acnt = 0;
+ for ( ac=tok->accreated; ac!=NULL; ac=acnext ) {
+ acnext = ac->next;
+ if ( ac->name==NULL ) {
+ char buf[50];
+ do {
+ snprintf(buf,sizeof(buf),_("Anchor-%d"), acnt++ );
+ for ( an=sf->anchor; an!=NULL && strcmp(an->name,buf)!=0; an=an->next );
+ } while ( an!=NULL );
+ ac->name = copy(buf);
+ }
+ ac->next = sf->anchor;
+ sf->anchor = ac;
+ }
+
+ sf->changed = true;
+ FVSetTitles(sf);
+ FVRefreshAll(sf);
+}
+
+void SFApplyFeatureFile(SplineFont *sf,FILE *file,char *filename) {
+ struct parseState tok;
+ struct glyphclasses *gc, *gcnext;
+
+ memset(&tok,0,sizeof(tok));
+ tok.line[0] = 1;
+ tok.filename[0] = filename;
+ tok.inlist[0] = file;
+ tok.base = 10;
+ if ( sf->cidmaster ) sf = sf->cidmaster;
+ tok.sf = sf;
+
+ fea_ParseFeatureFile(&tok);
+ if ( tok.err_count==0 ) {
+ tok.sofar = fea_reverseList(tok.sofar);
+ fea_ApplyFile(&tok, tok.sofar);
+ fea_NameLookups(&tok);
+ } else
+ ff_post_error("Not applied","There were errors when parsing the feature file and the features have not been applied");
+ fea_featitemFree(tok.sofar);
+ ScriptLangListFree(tok.def_langsyses);
+ for ( gc = tok.classes; gc!=NULL; gc=gcnext ) {
+ gcnext = gc->next;
+ free(gc->classname); free(gc->glyphs);
+ chunkfree(gc,sizeof(struct glyphclasses));
+ }
+}
+
+void SFApplyFeatureFilename(SplineFont *sf,char *filename) {
+ FILE *in = fopen(filename,"r");
+
+ if ( in==NULL ) {
+ ff_post_error(_("Cannot open file"),_("Cannot open feature file %.120s"), filename );
+return;
+ }
+ SFApplyFeatureFile(sf,in,filename);
+ fclose(in);
+}