Permalink
Fetching contributors…
Cannot retrieve contributors at this time
4333 lines (4097 sloc) 193 KB
/* Copyright (C) 2003-2012 by George Williams */
/*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <fontforge-config.h>
#ifndef _NO_PYTHON
# include "Python.h"
# include "structmember.h"
#else
# include <utype.h>
#endif
#include "autohint.h"
#include "dumppfa.h"
#include "featurefile.h"
#include "fontforgevw.h"
#include "fvfonts.h"
#include "fvfonts.h"
#include "lookups.h"
#include "splinesaveafm.h"
#include "splineutil.h"
#include "splineutil2.h"
#include <unistd.h>
#include <math.h>
#include <time.h>
#include <locale.h>
#include <chardata.h>
#include <gfile.h>
#include <ustring.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#ifndef _NO_PYTHON
# include "ffpython.h"
#endif
#include <stdarg.h>
#include <string.h>
#undef extended /* used in xlink.h */
#include <libxml/tree.h>
#ifdef FF_UTHASH_GLIF_NAMES
#include "glif_name_hash.h"
#endif
/* The UFO (Unified Font Object) format ( http://unifiedfontobject.org/ ) */
/* is a directory containing a bunch of (mac style) property lists and another*/
/* directory containing glif files (and contents.plist). */
/* Property lists contain one <dict> element which contains <key> elements */
/* each followed by an <integer>, <real>, <true/>, <false/> or <string> element, */
/* or another <dict> */
/* UFO format 2.0 includes an adobe feature file "features.fea" and slightly */
/* different/more tags in fontinfo.plist */
static char *buildname(const char *basedir, const char *sub) {
char *fname = malloc(strlen(basedir)+strlen(sub)+2);
strcpy(fname, basedir);
if ( fname[strlen(fname)-1]!='/' )
strcat(fname,"/");
strcat(fname,sub);
return( fname );
}
static void extractNumericVersion(const char * textVersion, int * versionMajor, int * versionMinor) {
// We extract integer values for major and minor versions from a version string.
*versionMajor = -1; *versionMinor = -1;
if (textVersion == NULL) return;
char *nextText1 = NULL;
char *nextText2 = NULL;
int tempVersion = -1;
tempVersion = strtol(textVersion, &nextText1, 10);
if (tempVersion != -1 && nextText1 != NULL && (nextText1[0] == '\0' || nextText1[0] == ' ' || nextText1[0] == '.')) {
*versionMajor = tempVersion;
} else return;
if (nextText1[0] == '\0') return;
tempVersion = strtol(nextText1+1, &nextText2, 10);
if (tempVersion != -1 && nextText2 != NULL && (nextText2[0] == '\0' || nextText2[0] == ' ' || nextText2[0] == '.')) {
*versionMinor = tempVersion;
} else return;
return;
}
static void injectNumericVersion(char ** textVersion, int versionMajor, int versionMinor) {
// We generate a version string from numeric values if available.
if (versionMajor == -1) asprintf(textVersion, "%s", "");
else if (versionMinor == -1) asprintf(textVersion, "%d", versionMajor);
else asprintf(textVersion, "%d.%d", versionMajor, versionMinor);
return;
}
/* The spec does not really require padding the version str but it clears */
/* 1.8 vs. 1.008 ambiguities and makes us inline with the AFDKO. */
char* paddedVersionStr(const char * textVersion, char * buffer) {
int major = -1;
int minor = -1;
extractNumericVersion(textVersion, &major, &minor);
if (major < 0 || minor < 0) return (char *) textVersion;
snprintf(buffer, 6, "%d.%03d", major, minor);
return buffer;
}
const char * DOS_reserved[12] = {"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM1", "COM2", "COM3", "COM4", "LPT1", "LPT2", "LPT3"};
const int DOS_reserved_count = 12;
int polyMatch(const char * input, int reference_count, const char ** references) {
for (off_t pos = 0; pos < reference_count; pos++) {
if (strcmp(references[pos], input) == 0) return 1;
}
return 0;
}
int is_DOS_drive(char * input) {
if ((input != NULL) &&
(strlen(input) == 2) &&
(((input[0] >= 'A') && (input[0] <= 'Z')) || ((input[0] >= 'a') && (input[0] <= 'z'))) &&
(input[1] == ':'))
return 1;
return 0;
}
char * ufo_name_mangle(const char * input, const char * prefix, const char * suffix, int flags) {
// This does not append the prefix or the suffix.
// flags & 1 determines whether to post-pad caps (something implemented in the standard).
// flags & 2 determines whether to replace a leading '.' (standard).
// flags & 4 determines whether to restrict DOS names (standard).
// flags & 8 determines whether to implement additional character restrictions.
// The specification lists '"' '*' '+' '/' ':' '<' '>' '?' '[' '\\' ']' '|'
// and also anything in the range 0x00-0x1F and 0x7F.
// Our additional restriction list includes '\'' '&' '%' '$' '#' '`' '=' '!' ';'
// Standard behavior comes from passing a flags value of 7.
const char * standard_restrict = "\"*+/:<>?[]\\]|";
const char * extended_restrict = "\'&%$#`=!;";
size_t prefix_length = strlen(prefix);
size_t max_length = 255 - prefix_length - strlen(suffix);
size_t input_length = strlen(input);
size_t stop_pos = ((max_length < input_length) ? max_length : input_length); // Minimum.
size_t output_length_1 = input_length;
if (flags & 1) output_length_1 += count_caps(input); // Add space for underscore pads on caps.
off_t output_pos = 0;
char * output = malloc(output_length_1 + 1);
for (int i = 0; i < input_length; i++) {
if (strchr(standard_restrict, input[i]) || (input[i] <= 0x1F) || (input[i] >= 0x7F)) {
output[output_pos++] = '_'; // If the character is restricted, place an underscore.
} else if ((flags & 8) && strchr(extended_restrict, input[i])) {
output[output_pos++] = '_'; // If the extended restriction list is enabled and matches, ....
} else if ((flags & 1) && (input[i] >= 'A') && (input[i] <= 'Z')) {
output[output_pos++] = input[i];
output[output_pos++] = '_'; // If we have a capital letter, we post-pad if desired.
} else if ((flags & 2) && (i == 0) && (prefix_length == 0) && (input[i] == '.')) {
output[output_pos++] = '_'; // If we have a leading '.', we convert to an underscore.
} else {
output[output_pos++] = input[i];
}
}
output[output_pos] = '\0';
if (output_pos > max_length) {
output[max_length] = '\0';
}
char * output2 = NULL;
off_t output2_pos = 0;
{
char * disposable = malloc(output_length_1 + 1);
strcpy(disposable, output); // strtok rewrites the input string, so we make a copy.
output2 = malloc((2 * output_length_1) + 1); // It's easier to pad than to calculate.
output2_pos = 0;
char * saveptr = NULL;
char * current = strtok_r(disposable, ".", &saveptr); // We get the first name part.
while (current != NULL) {
char * uppered = upper_case(output);
if (polyMatch(uppered, DOS_reserved_count, DOS_reserved) || is_DOS_drive(uppered)) {
output2[output2_pos++] = '_'; // Prefix an underscore if it's a reserved name.
}
free(uppered); uppered = NULL;
for (off_t parti = 0; current[parti] != '\0'; parti++) {
output2[output2_pos++] = current[parti];
}
current = strtok_r(NULL, ".", &saveptr);
if (current != NULL) output2[output2_pos++] = '.';
}
output2[output2_pos] = '\0';
output2 = realloc(output2, output2_pos + 1);
free(disposable); disposable = NULL;
}
free(output); output = NULL;
return output2;
}
char * ufo_name_number(
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index * glif_name_hash,
#else
void * glif_name_hash,
#endif
int index, const char * input, const char * prefix, const char * suffix, int flags) {
// This does not append the prefix or the suffix.
// The specification deals with name collisions by appending a 15-digit decimal number to the name.
// But the name length cannot exceed 255 characters, so it is necessary to crop the base name if it is too long.
// Name exclusions are case insensitive, so we uppercase.
// flags & 16 forces appending a number.
char * name_numbered = upper_case(input);
char * full_name_base = same_case(input); // This is in case we do not need a number added.
if (strlen(input) > (255 - strlen(prefix) - strlen(suffix))) {
// If the numbered name base is too long, we crop it, even if we are not numbering.
full_name_base[(255 - strlen(suffix))] = '\0';
full_name_base = realloc(full_name_base, ((255 - strlen(prefix) - strlen(suffix)) + 1));
}
char * name_base = same_case(input); // This is in case we need a number added.
long int name_number = 0;
#ifdef FF_UTHASH_GLIF_NAMES
if (glif_name_hash != NULL) {
if (strlen(input) > (255 - 15 - strlen(prefix) - strlen(suffix))) {
// If the numbered name base is too long, we crop it.
name_base[(255 - 15 - strlen(suffix))] = '\0';
name_base = realloc(name_base, ((255 - 15 - strlen(prefix) - strlen(suffix)) + 1));
}
int number_once = ((flags & 16) ? 1 : 0);
// Check the resulting name against a hash table of names.
if (glif_name_search_glif_name(glif_name_hash, name_numbered) != NULL || number_once) {
// If the name is taken, we must make space for a 15-digit number.
char * name_base_upper = upper_case(name_base);
while (glif_name_search_glif_name(glif_name_hash, name_numbered) != NULL || number_once) {
name_number++; // Remangle the name until we have no more matches.
free(name_numbered); name_numbered = NULL;
asprintf(&name_numbered, "%s%015ld", name_base_upper, name_number);
number_once = 0;
}
free(name_base_upper); name_base_upper = NULL;
}
// Insert the result into the hash table.
glif_name_track_new(glif_name_hash, index, name_numbered);
}
#endif
// Now we want the correct capitalization.
free(name_numbered); name_numbered = NULL;
if (name_number > 0) {
asprintf(&name_numbered, "%s%015ld", name_base, name_number);
} else {
asprintf(&name_numbered, "%s", full_name_base);
}
free(name_base); name_base = NULL;
free(full_name_base); full_name_base = NULL;
return name_numbered;
}
static xmlNodePtr xmlNewChildInteger(xmlNodePtr parent, xmlNsPtr ns, const xmlChar * name, long int value) {
char * valtmp = NULL;
// Textify the value to be enclosed.
if (asprintf(&valtmp, "%ld", value) >= 0) {
xmlNodePtr childtmp = xmlNewChild(parent, NULL, BAD_CAST name, BAD_CAST valtmp); // Make a text node for the value.
free(valtmp); valtmp = NULL; // Free the temporary text store.
return childtmp;
}
return NULL;
}
static xmlNodePtr xmlNewNodeInteger(xmlNsPtr ns, const xmlChar * name, long int value) {
char * valtmp = NULL;
xmlNodePtr childtmp = xmlNewNode(NULL, BAD_CAST name); // Create a named node.
// Textify the value to be enclosed.
if (asprintf(&valtmp, "%ld", value) >= 0) {
xmlNodePtr valtmpxml = xmlNewText(BAD_CAST valtmp); // Make a text node for the value.
xmlAddChild(childtmp, valtmpxml); // Attach the text node as content of the named node.
free(valtmp); valtmp = NULL; // Free the temporary text store.
} else {
xmlFreeNode(childtmp); childtmp = NULL;
}
return childtmp;
}
static xmlNodePtr xmlNewChildFloat(xmlNodePtr parent, xmlNsPtr ns, const xmlChar * name, double value) {
char * valtmp = NULL;
// Textify the value to be enclosed.
if (asprintf(&valtmp, "%g", value) >= 0) {
xmlNodePtr childtmp = xmlNewChild(parent, NULL, BAD_CAST name, BAD_CAST valtmp); // Make a text node for the value.
free(valtmp); valtmp = NULL; // Free the temporary text store.
return childtmp;
}
return NULL;
}
static xmlNodePtr xmlNewNodeFloat(xmlNsPtr ns, const xmlChar * name, double value) {
char * valtmp = NULL;
xmlNodePtr childtmp = xmlNewNode(NULL, BAD_CAST name); // Create a named node.
// Textify the value to be enclosed.
if (asprintf(&valtmp, "%g", value) >= 0) {
xmlNodePtr valtmpxml = xmlNewText(BAD_CAST valtmp); // Make a text node for the value.
xmlAddChild(childtmp, valtmpxml); // Attach the text node as content of the named node.
free(valtmp); valtmp = NULL; // Free the temporary text store.
} else {
xmlFreeNode(childtmp); childtmp = NULL;
}
return NULL;
}
static xmlNodePtr xmlNewChildString(xmlNodePtr parent, xmlNsPtr ns, const xmlChar * name, char * value) {
xmlNodePtr childtmp = xmlNewTextChild(parent, ns, BAD_CAST name, BAD_CAST value); // Make a text node for the value.
return childtmp;
}
static xmlNodePtr xmlNewNodeString(xmlNsPtr ns, const xmlChar * name, char * value) {
xmlNodePtr childtmp = xmlNewNode(NULL, BAD_CAST name); // Create a named node.
xmlNodePtr valtmpxml = xmlNewText(BAD_CAST value); // Make a text node for the value.
xmlAddChild(childtmp, valtmpxml); // Attach the text node as content of the named node.
return childtmp;
}
static xmlNodePtr xmlNewNodeVPrintf(xmlNsPtr ns, const xmlChar * name, char * format, va_list arguments) {
char * valtmp = NULL;
if (vasprintf(&valtmp, format, arguments) < 0) {
return NULL;
}
xmlNodePtr childtmp = xmlNewNode(NULL, BAD_CAST name); // Create a named node.
xmlNodePtr valtmpxml = xmlNewText(BAD_CAST valtmp); // Make a text node for the value.
xmlAddChild(childtmp, valtmpxml); // Attach the text node as content of the named node.
free(valtmp); valtmp = NULL; // Free the temporary text store.
return childtmp;
}
static xmlNodePtr xmlNewNodePrintf(xmlNsPtr ns, const xmlChar * name, char * format, ...) {
va_list arguments;
va_start(arguments, format);
xmlNodePtr output = xmlNewNodeVPrintf(ns, name, format, arguments);
va_end(arguments);
return output;
}
static xmlNodePtr xmlNewChildVPrintf(xmlNodePtr parent, xmlNsPtr ns, const xmlChar * name, char * format, va_list arguments) {
xmlNodePtr output = xmlNewNodeVPrintf(ns, name, format, arguments);
xmlAddChild(parent, output);
return output;
}
static xmlNodePtr xmlNewChildPrintf(xmlNodePtr parent, xmlNsPtr ns, const xmlChar * name, char * format, ...) {
va_list arguments;
va_start(arguments, format);
xmlNodePtr output = xmlNewChildVPrintf(parent, ns, name, format, arguments);
va_end(arguments);
return output;
}
static void xmlSetPropVPrintf(xmlNodePtr target, const xmlChar * name, char * format, va_list arguments) {
char * valtmp = NULL;
// Generate the value.
if (vasprintf(&valtmp, format, arguments) < 0) {
return;
}
xmlSetProp(target, name, valtmp); // Set the property.
free(valtmp); valtmp = NULL; // Free the temporary text store.
return;
}
static void xmlSetPropPrintf(xmlNodePtr target, const xmlChar * name, char * format, ...) {
va_list arguments;
va_start(arguments, format);
xmlSetPropVPrintf(target, name, format, arguments);
va_end(arguments);
return;
}
/* ************************************************************************** */
/* ************************* Python lib Output ************************* */
/* ************************************************************************** */
#ifndef _NO_PYTHON
static int PyObjDumpable(PyObject *value, int has_lists);
xmlNodePtr PyObjectToXML( PyObject *value, int has_lists );
#endif
xmlNodePtr PythonLibToXML(void *python_persistent, const SplineChar *sc, int has_lists) {
int has_hints = (sc!=NULL && (sc->hstem!=NULL || sc->vstem!=NULL ));
xmlNodePtr retval = NULL, dictnode = NULL, keynode = NULL, valnode = NULL;
// retval = xmlNewNode(NULL, BAD_CAST "lib"); // "<lib>"
dictnode = xmlNewNode(NULL, BAD_CAST "dict"); // " <dict>"
if ( has_hints
#ifndef _NO_PYTHON
|| (python_persistent!=NULL && PyMapping_Check((PyObject *)python_persistent))
#endif
) {
xmlAddChild(retval, dictnode);
/* Not officially part of the UFO/glif spec, but used by robofab */
if ( has_hints ) {
// Remember that the value of the plist key is in the node that follows it in the dict (not an x.m.l. child).
xmlNewChild(dictnode, NULL, BAD_CAST "key", BAD_CAST "com.fontlab.hintData"); // Label the hint data block.
// " <key>com.fontlab.hintData</key>\n"
// " <dict>"
xmlNodePtr hintdict = xmlNewChild(dictnode, NULL, BAD_CAST "dict", NULL);
if ( sc != NULL ) {
if ( sc->hstem!=NULL ) {
StemInfo *h;
xmlNewChild(hintdict, NULL, BAD_CAST "key", BAD_CAST "hhints");
// " <key>hhints</key>"
// " <array>"
xmlNodePtr hintarray = xmlNewChild(hintdict, NULL, BAD_CAST "array", NULL);
for ( h = sc->hstem; h!=NULL; h=h->next ) {
char * valtmp = NULL;
xmlNodePtr stemdict = xmlNewChild(hintarray, NULL, BAD_CAST "dict", NULL);
// " <dict>"
xmlNewChild(stemdict, NULL, BAD_CAST "key", "position");
// " <key>position</key>"
xmlNewChildInteger(stemdict, NULL, BAD_CAST "integer", (int) rint(h->start));
// " <integer>%d</integer>\n" ((int) rint(h->start))
xmlNewChild(stemdict, NULL, BAD_CAST "key", "width");
// " <key>width</key>"
xmlNewChildInteger(stemdict, NULL, BAD_CAST "integer", (int) rint(h->width));
// " <integer>%d</integer>\n" ((int) rint(h->width))
// " </dict>\n"
}
// " </array>\n"
}
if ( sc->vstem!=NULL ) {
StemInfo *h;
xmlNewChild(hintdict, NULL, BAD_CAST "key", BAD_CAST "vhints");
// " <key>vhints</key>"
// " <array>"
xmlNodePtr hintarray = xmlNewChild(hintdict, NULL, BAD_CAST "array", NULL);
for ( h = sc->vstem; h!=NULL; h=h->next ) {
char * valtmp = NULL;
xmlNodePtr stemdict = xmlNewChild(hintarray, NULL, BAD_CAST "dict", NULL);
// " <dict>"
xmlNewChild(stemdict, NULL, BAD_CAST "key", "position");
// " <key>position</key>"
xmlNewChildInteger(stemdict, NULL, BAD_CAST "integer", (int) rint(h->start));
// " <integer>%d</integer>\n" ((int) rint(h->start))
xmlNewChild(stemdict, NULL, BAD_CAST "key", "width");
// " <key>width</key>"
xmlNewChildInteger(stemdict, NULL, BAD_CAST "integer", (int) rint(h->width));
// " <integer>%d</integer>\n" ((int) rint(h->width))
// " </dict>\n"
}
// " </array>\n"
}
}
// " </dict>"
}
#ifndef _NO_PYTHON
PyObject *dict = python_persistent, *items, *key, *value;
/* Ok, look at the persistent data and output it (all except for a */
/* hint entry -- we've already handled that with the real hints, */
/* no point in retaining out of date hints too */
if ( dict != NULL ) {
if (!PyMapping_Check((PyObject *)python_persistent)) fprintf(stderr, "python_persistent is not a mapping.\n");
else {
int i, len;
char *str;
items = PyMapping_Items(dict);
len = PySequence_Size(items);
for ( i=0; i<len; ++i ) {
// According to the Python reference manual,
// PySequence_GetItem returns a reference that we must release,
// but PyTuple_GetItem returns a borrowed reference.
PyObject *item = PySequence_GetItem(items,i);
key = PyTuple_GetItem(item,0);
if ( !PyBytes_Check(key)) /* Keys need not be strings */
{ Py_DECREF(item); item = NULL; continue; }
str = PyBytes_AsString(key);
if ( !str || (strcmp(str,"com.fontlab.hintData")==0 && sc!=NULL) ) /* Already done */
{ Py_DECREF(item); item = NULL; continue; }
value = PyTuple_GetItem(item,1);
if ( !value || !PyObjDumpable(value, has_lists))
{ Py_DECREF(item); item = NULL; continue; }
// "<key>%s</key>" str
xmlNewChild(dictnode, NULL, BAD_CAST "key", str);
xmlNodePtr tmpNode = PyObjectToXML(value, has_lists);
xmlAddChild(dictnode, tmpNode);
// "<...>...</...>"
Py_DECREF(item); item = NULL;
}
}
}
#endif
}
// " </dict>"
// // "</lib>"
return dictnode;
}
#ifndef _NO_PYTHON
static int PyObjDumpable(PyObject *value, int has_lists) {
if ( PyTuple_Check(value))
return( true ); // Note that this means two different things depending upon has_lists.
if ( PyList_Check(value))
return( true );
if ( PyInt_Check(value))
return( true );
if ( PyFloat_Check(value))
return( true );
if ( PyDict_Check(value))
return( true );
if ( PyBytes_Check(value))
return( true );
if ( has_lists && PyList_Check(value))
return( true );
if ( PyMapping_Check(value))
return( true );
if ( PyBool_Check(value))
return( true );
if ( value == Py_None )
return( true );
return( false );
}
xmlNodePtr PyObjectToXML( PyObject *value, int has_lists ) {
xmlNodePtr childtmp = NULL;
xmlNodePtr valtmpxml = NULL;
char * valtmp = NULL;
if (has_lists && PyTuple_Check(value) && (PyTuple_Size(value) == 3) &&
PyBytes_Check(PyTuple_GetItem(value,0)) && PyBytes_Check(PyTuple_GetItem(value,1))) {
// This is a chunk of unrecognized data.
// Since there's no equivalent in X. M. L., we can use the Tuple for this special case.
// But we can only do this if the arrays are being mapped as lists rather than as tuples.
// So we miss foreign data in old S. F. D. versions.
// childtmp = xmlNewNode(NULL, (xmlChar*)(PyBytes_AsString(PyTuple_GetItem(value,0)))); // Set name.
// xmlNodeSetContent(childtmp, (xmlChar*)(PyBytes_AsString(PyTuple_GetItem(value,1)))); // Set contents.
xmlDocPtr innerdoc = xmlReadMemory((xmlChar*)(PyBytes_AsString(PyTuple_GetItem(value,1))),
PyBytes_Size(PyTuple_GetItem(value,1)), "noname.xml", NULL, 0);
childtmp = xmlCopyNode(xmlDocGetRootElement(innerdoc), 1);
xmlFreeDoc(innerdoc); innerdoc = NULL;
} else if (PyDict_Check(value)) {
childtmp = PythonLibToXML(value,NULL,has_lists);
} else if ( PyMapping_Check(value)) {
childtmp = PythonLibToXML(value,NULL,has_lists);
} else if ( PyBytes_Check(value)) { /* Must precede the sequence check */
char *str = PyBytes_AsString(value);
if (str != NULL) {
childtmp = xmlNewNodeString(NULL, BAD_CAST "string", str); // Create a string node.
// "<string>%s</string>" str
}
} else if ( value==Py_True )
childtmp = xmlNewNode(NULL, BAD_CAST "true"); // "<true/>"
else if ( value==Py_False )
childtmp = xmlNewNode(NULL, BAD_CAST "false"); // "<false/>"
else if ( value==Py_None )
childtmp = xmlNewNode(NULL, BAD_CAST "none"); // "<none/>"
else if (PyInt_Check(value)) {
childtmp = xmlNewNodeInteger(NULL, BAD_CAST "integer", PyInt_AsLong(value)); // Create an integer node.
// "<integer>%ld</integer>"
} else if (PyFloat_Check(value)) {
childtmp = xmlNewNode(NULL, BAD_CAST "real");
if (asprintf(&valtmp, "%g", PyFloat_AsDouble(value)) >= 0) {
valtmpxml = xmlNewText(BAD_CAST valtmp);
xmlAddChild(childtmp, valtmpxml);
free(valtmp); valtmp = NULL;
} else {
xmlFreeNode(childtmp); childtmp = NULL;
}
// "<real>%g</real>"
} else if ((!has_lists && PyTuple_Check(value)) || (has_lists && PyList_Check(value))) {
// Note that PyList is an extension of PySequence, so the original PySequence code would work either way.
// But we want to be able to detect mismatches.
int i, len = ( has_lists ? PyList_Size(value) : PyTuple_Size(value) );
xmlNodePtr itemtmp = NULL;
childtmp = xmlNewNode(NULL, BAD_CAST "array");
// "<array>"
for ( i=0; i<len; ++i ) {
PyObject *obj = ( has_lists ? PyList_GetItem(value,i) : PyTuple_GetItem(value,i) );
if (obj != NULL) {
if ( PyObjDumpable(obj, has_lists)) {
itemtmp = PyObjectToXML(obj, has_lists);
xmlAddChild(childtmp, itemtmp);
}
obj = NULL; // PyTuple_GetItem and PyList_GetItem both return borrowed references.
}
}
// "</array>"
}
return childtmp;
}
#endif
/* ************************************************************************** */
/* **************************** GLIF Output **************************** */
/* ************************************************************************** */
static int refcomp(const void *_r1, const void *_r2) {
const RefChar *ref1 = *(RefChar * const *)_r1;
const RefChar *ref2 = *(RefChar * const *)_r2;
return( strcmp( ref1->sc->name, ref2->sc->name) );
}
xmlNodePtr _GlifToXML(const SplineChar *sc,int layer) {
if (layer > sc->layer_cnt) return NULL;
const struct altuni *altuni;
int isquad = sc->layers[layer].order2;
const SplineSet *spl;
const SplinePoint *sp;
const AnchorPoint *ap;
const RefChar *ref;
int err;
char * stringtmp = NULL;
char numstring[32];
memset(numstring, 0, sizeof(numstring));
// "<?xml version=\"1.0\" encoding=\"UTF-8\""
/* No DTD for these guys??? */
// Is there a DTD for glif data? (asks Frank)
// Perhaps we need to make one.
xmlNodePtr topglyphxml = xmlNewNode(NULL, BAD_CAST "glyph"); // Create the glyph node.
xmlSetProp(topglyphxml, "name", sc->name); // Set the name for the glyph.
xmlSetProp(topglyphxml, "format", "1"); // Set the format of the glyph.
// "<glyph name=\"%s\" format=\"1\">" sc->name
xmlNodePtr tmpxml2 = xmlNewChild(topglyphxml, NULL, BAD_CAST "advance", NULL); // Create the advance node.
xmlSetPropPrintf(tmpxml2, BAD_CAST "width", "%d", sc->width);
if ( sc->parent->hasvmetrics ) {
if (asprintf(&stringtmp, "%d", sc->width) >= 0) {
xmlSetProp(tmpxml2, BAD_CAST "height", stringtmp);
free(stringtmp); stringtmp = NULL;
}
}
// "<advance width=\"%d\" height=\"%d\"/>" sc->width sc->vwidth
if ( sc->unicodeenc!=-1 ) {
xmlNodePtr unicodexml = xmlNewChild(topglyphxml, NULL, BAD_CAST "unicode", NULL);
xmlSetPropPrintf(unicodexml, BAD_CAST "hex", "%04X", sc->unicodeenc);
}
// "<unicode hex=\"%04X\"/>\n" sc->unicodeenc
for ( altuni = sc->altuni; altuni!=NULL; altuni = altuni->next )
if ( altuni->vs==-1 && altuni->fid==0 ) {
xmlNodePtr unicodexml = xmlNewChild(topglyphxml, NULL, BAD_CAST "unicode", NULL);
xmlSetPropPrintf(unicodexml, BAD_CAST "hex", "%04X", altuni->unienc);
}
// "<unicode hex=\"%04X\"/>" altuni->unienc
if ( sc->layers[layer].refs!=NULL || sc->layers[layer].splines!=NULL ) {
xmlNodePtr outlinexml = xmlNewChild(topglyphxml, NULL, BAD_CAST "outline", NULL);
// "<outline>"
for ( ap=sc->anchor; ap!=NULL; ap=ap->next ) {
int ismark = (ap->type==at_mark || ap->type==at_centry);
xmlNodePtr contourxml = xmlNewChild(outlinexml, NULL, BAD_CAST "contour", NULL);
// "<contour>"
xmlNodePtr pointxml = xmlNewChild(contourxml, NULL, BAD_CAST "point", NULL);
xmlSetPropPrintf(pointxml, BAD_CAST "x", "%g", ap->me.x);
xmlSetPropPrintf(pointxml, BAD_CAST "y", "%g", ap->me.y);
xmlSetPropPrintf(pointxml, BAD_CAST "type", "move");
xmlSetPropPrintf(pointxml, BAD_CAST "name", "%s%s", ismark ? "_" : "", ap->anchor->name);
// "<point x=\"%g\" y=\"%g\" type=\"move\" name=\"%s%s\"/>" ap->me.x ap->me.y (ismark ? "_" : "") ap->anchor->name
// "</contour>"
}
for ( spl=sc->layers[layer].splines; spl!=NULL; spl=spl->next ) {
xmlNodePtr contourxml = xmlNewChild(outlinexml, NULL, BAD_CAST "contour", NULL);
// "<contour>"
// We write any leading control points.
if (spl->start_offset == -2) {
if (spl->first && spl->first->prev && spl->first->prev->from && !spl->first->prev->from->nonextcp && !spl->first->prev->order2) {
xmlNodePtr pointxml = xmlNewChild(contourxml, NULL, BAD_CAST "point", NULL);
xmlSetPropPrintf(pointxml, BAD_CAST "x", "%g", (double)spl->first->prev->from->nextcp.x);
xmlSetPropPrintf(pointxml, BAD_CAST "y", "%g", (double)spl->first->prev->from->nextcp.y);
// "<point x=\"%g\" y=\"%g\"/>\n" (double)sp->prevcp.x (double)sp->prevcp.y
}
}
if (spl->start_offset <= -1) {
if (spl->first && !spl->first->noprevcp) {
xmlNodePtr pointxml = xmlNewChild(contourxml, NULL, BAD_CAST "point", NULL);
xmlSetPropPrintf(pointxml, BAD_CAST "x", "%g", (double)spl->first->prevcp.x);
xmlSetPropPrintf(pointxml, BAD_CAST "y", "%g", (double)spl->first->prevcp.y);
// "<point x=\"%g\" y=\"%g\"/>\n" (double)sp->prevcp.x (double)sp->prevcp.y
}
}
for ( sp=spl->first; sp!=NULL; ) {
/* Undocumented fact: If a contour contains a series of off-curve points with no on-curve then treat as quadratic even if no qcurve */
// We write the next on-curve point.
if (!isquad || sp->ttfindex != 0xffff || !SPInterpolate(sp) || sp->pointtype!=pt_curve || sp->name != NULL) {
xmlNodePtr pointxml = xmlNewChild(contourxml, NULL, BAD_CAST "point", NULL);
xmlSetPropPrintf(pointxml, BAD_CAST "x", "%g", (double)sp->me.x);
xmlSetPropPrintf(pointxml, BAD_CAST "y", "%g", (double)sp->me.y);
xmlSetPropPrintf(pointxml, BAD_CAST "type", BAD_CAST (
sp->prev==NULL ? "move" :
sp->noprevcp ? "line" :
isquad ? "qcurve" :
"curve"));
if (sp->pointtype != pt_corner) xmlSetProp(pointxml, BAD_CAST "smooth", BAD_CAST "yes");
if (sp->name !=NULL) xmlSetProp(pointxml, BAD_CAST "name", BAD_CAST sp->name);
// "<point x=\"%g\" y=\"%g\" type=\"%s\"%s%s%s%s/>\n" (double)sp->me.x (double)sp->me.y
// (sp->prev==NULL ? "move" : sp->prev->knownlinear ? "line" : isquad ? "qcurve" : "curve")
// (sp->pointtype!=pt_corner?" smooth=\"yes\"":"")
// (sp->name?" name=\"":"") (sp->name?sp->name:"") (sp->name?"\"":"")
}
if ( sp->next==NULL )
break;
// We write control points.
// The conditionals regarding the start offset avoid duplicating points previously written.
if (sp && !sp->nonextcp && sp->next && (sp->next->to != spl->first || spl->start_offset > -2) && sp->next && !sp->next->order2) {
xmlNodePtr pointxml = xmlNewChild(contourxml, NULL, BAD_CAST "point", NULL);
xmlSetPropPrintf(pointxml, BAD_CAST "x", "%g", (double)sp->nextcp.x);
xmlSetPropPrintf(pointxml, BAD_CAST "y", "%g", (double)sp->nextcp.y);
// "<point x=\"%g\" y=\"%g\"/>\n" (double)sp->nextcp.x (double)sp->nextcp.y
}
sp = sp->next->to;
if (sp && !sp->noprevcp && (sp != spl->first || spl->start_offset > -1)) {
xmlNodePtr pointxml = xmlNewChild(contourxml, NULL, BAD_CAST "point", NULL);
xmlSetPropPrintf(pointxml, BAD_CAST "x", "%g", (double)sp->prevcp.x);
xmlSetPropPrintf(pointxml, BAD_CAST "y", "%g", (double)sp->prevcp.y);
// "<point x=\"%g\" y=\"%g\"/>\n" (double)sp->prevcp.x (double)sp->prevcp.y
}
if ( sp==spl->first )
break;
}
// "</contour>"
}
/* RoboFab outputs components in alphabetic (case sensitive) order. */
/* Somebody asked George to do that too (as in the disabled code below). */
/* But it seems important to leave the ordering as it is. */
/* And David Raymond advises that tampering with the ordering can break things. */
if ( sc->layers[layer].refs!=NULL ) {
const RefChar **refs;
int i, cnt;
for ( cnt=0, ref = sc->layers[layer].refs; ref!=NULL; ref=ref->next ) if ((SCWorthOutputting(ref->sc) || SCHasData(ref->sc) || ref->sc->glif_name != NULL))
++cnt;
refs = malloc(cnt*sizeof(RefChar *));
for ( cnt=0, ref = sc->layers[layer].refs; ref!=NULL; ref=ref->next ) if ((SCWorthOutputting(ref->sc) || SCHasData(ref->sc) || ref->sc->glif_name != NULL))
refs[cnt++] = ref;
// It seems that sorting these breaks something.
#if 0
if ( cnt>1 )
qsort(refs,cnt,sizeof(RefChar *),refcomp);
#endif // 0
for ( i=0; i<cnt; ++i ) {
ref = refs[i];
xmlNodePtr componentxml = xmlNewChild(outlinexml, NULL, BAD_CAST "component", NULL);
xmlSetPropPrintf(componentxml, BAD_CAST "base", "%s", ref->sc->name);
// "<component base=\"%s\"" ref->sc->name
char *floattmp = NULL;
if ( ref->transform[0]!=1 ) {
xmlSetPropPrintf(componentxml, BAD_CAST "xScale", "%g", (double) ref->transform[0]);
// "xScale=\"%g\"" (double)ref->transform[0]
}
if ( ref->transform[3]!=1 ) {
xmlSetPropPrintf(componentxml, BAD_CAST "yScale", "%g", (double) ref->transform[3]);
// "yScale=\"%g\"" (double)ref->transform[3]
}
if ( ref->transform[1]!=0 ) {
xmlSetPropPrintf(componentxml, BAD_CAST "xyScale", "%g", (double) ref->transform[1]);
// "xyScale=\"%g\"" (double)ref->transform[1]
}
if ( ref->transform[2]!=0 ) {
xmlSetPropPrintf(componentxml, BAD_CAST "yxScale", "%g", (double) ref->transform[2]);
// "yxScale=\"%g\"" (double)ref->transform[2]
}
if ( ref->transform[4]!=0 ) {
xmlSetPropPrintf(componentxml, BAD_CAST "xOffset", "%g", (double) ref->transform[4]);
// "xOffset=\"%g\"" (double)ref->transform[4]
}
if ( ref->transform[5]!=0 ) {
xmlSetPropPrintf(componentxml, BAD_CAST "yOffset", "%g", (double) ref->transform[5]);
// "yOffset=\"%g\"" (double)ref->transform[5]
}
// "/>"
}
free(refs);
}
// "</outline>"
}
if (sc->layers[layer].python_persistent != NULL || (layer == ly_fore && (sc->hstem!=NULL || sc->vstem!=NULL ))) {
// If the layer has lib data or if this is the foreground and the glyph has hints, we output lib data.
xmlNodePtr libxml = xmlNewChild(topglyphxml, NULL, BAD_CAST "lib", NULL);
xmlNodePtr pythonblob = PythonLibToXML(sc->layers[layer].python_persistent, (layer == ly_fore ? sc : NULL), sc->layers[layer].python_persistent_has_lists);
xmlAddChild(libxml, pythonblob);
}
return topglyphxml;
}
static int GlifDump(const char *glyphdir, const char *gfname, const SplineChar *sc, int layer) {
char *gn = buildname(glyphdir,gfname);
xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
if (doc == NULL) {
free(gn);
return 0;
}
xmlNodePtr root_node = _GlifToXML(sc, layer);
if (root_node == NULL) {xmlFreeDoc(doc); doc = NULL; free(gn); return 0;}
xmlDocSetRootElement(doc, root_node);
int ret = (xmlSaveFormatFileEnc(gn, doc, "UTF-8", 1) != -1);
xmlFreeDoc(doc); doc = NULL;
free(gn);
return ret;
}
int _ExportGlif(FILE *glif,SplineChar *sc,int layer) {
xmlDocPtr doc = xmlNewDoc(BAD_CAST "1.0");
xmlNodePtr root_node = _GlifToXML(sc, layer);
xmlDocSetRootElement(doc, root_node);
int output_status = xmlDocFormatDump(glif, doc, 1);
xmlFreeDoc(doc); doc = NULL;
return ( output_status != -1 );
}
/* ************************************************************************** */
/* **************************** UFO Output **************************** */
/* ************************************************************************** */
void clear_cached_ufo_paths(SplineFont * sf) {
// We cache the glif names and the layer paths.
// This is helpful for preserving the structure of a U. F. O. to be edited.
// But it may be desirable to purge that data on final output for consistency.
// This function does that.
int i;
// First we clear the glif names.
for (i = 0; i < sf->glyphcnt; i++) {
struct splinechar * sc = sf->glyphs[i];
if (sc->glif_name != NULL) { free(sc->glif_name); sc->glif_name = NULL; }
}
// Then we clear the layer names.
for (i = 0; i < sf->layer_cnt; i++) {
struct layerinfo * ly = &(sf->layers[i]);
if (ly->ufo_path != NULL) { free(ly->ufo_path); ly->ufo_path = NULL; }
}
}
void clear_cached_ufo_point_starts(SplineFont * sf) {
// We store the offset from the leading spline point at which to start output
// so as to be able to start curves on control points as some incoming U. F. O. files do.
// But we may want to clear these sometimes.
int splinechar_index;
for (splinechar_index = 0; splinechar_index < sf->glyphcnt; splinechar_index ++) {
struct splinechar *sc = sf->glyphs[splinechar_index];
if (sc != NULL) {
int layer_index;
for (layer_index = 0; layer_index < sc->layer_cnt; layer_index ++) {
// We look at the actual shapes for this layer.
{
struct splinepointlist *spl;
for (spl = sc->layers[layer_index].splines; spl != NULL; spl = spl->next) {
spl->start_offset = 0;
}
}
// And then we go hunting for shapes in the refchars.
{
struct refchar *rc;
for (rc = sc->layers[layer_index].refs; rc != NULL; rc = rc->next) {
int reflayer_index;
for (reflayer_index = 0; reflayer_index < rc->layer_cnt; reflayer_index ++) {
struct splinepointlist *spl;
for (spl = rc->layers[reflayer_index].splines; spl != NULL; spl = spl->next) {
spl->start_offset = 0;
}
}
}
}
}
}
}
// The SplineFont also has a grid.
{
struct splinepointlist *spl;
for (spl = sf->grid.splines; spl != NULL; spl = spl->next) {
spl->start_offset = 0;
}
}
}
xmlDocPtr PlistInit() {
// Some of this code is pasted from libxml2 samples.
xmlDocPtr doc = NULL;
xmlNodePtr root_node = NULL, dict_node = NULL;
xmlDtdPtr dtd = NULL;
char buff[256];
int i, j;
LIBXML_TEST_VERSION;
doc = xmlNewDoc(BAD_CAST "1.0");
dtd = xmlCreateIntSubset(doc, BAD_CAST "plist", BAD_CAST "-//Apple Computer//DTD PLIST 1.0//EN", BAD_CAST "http://www.apple.com/DTDs/PropertyList-1.0.dtd");
root_node = xmlNewNode(NULL, BAD_CAST "plist");
xmlSetProp(root_node, BAD_CAST "version", BAD_CAST "1.0");
xmlDocSetRootElement(doc, root_node);
return doc;
}
static void PListAddInteger(xmlNodePtr parent, const char *key, int value) {
xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key);
xmlNewChildPrintf(parent, NULL, BAD_CAST "integer", "%d", value);
}
static void PListAddReal(xmlNodePtr parent, const char *key, double value) {
xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key);
xmlNewChildPrintf(parent, NULL, BAD_CAST "real", "%g", value);
}
static void PListAddIntegerOrReal(xmlNodePtr parent, const char *key, double value) {
if (value == floor(value)) PListAddInteger(parent, key, (int)value);
else PListAddReal(parent, key, value);
}
static void PListAddBoolean(xmlNodePtr parent, const char *key, int value) {
xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key);
xmlNewChild(parent, NULL, BAD_CAST (value ? "true": "false"), NULL);
}
static void PListAddDate(xmlNodePtr parent, const char *key, time_t timestamp) {
/* openTypeHeadCreated = string format as \"YYYY/MM/DD HH:MM:SS\". */
/* \"YYYY/MM/DD\" is year/month/day. The month is in the range 1-12 and */
/* the day is in the range 1-end of month. */
/* \"HH:MM:SS\" is hour:minute:second. The hour is in the range 0:23. */
/* Minutes and seconds are in the range 0-59. */
struct tm *tm = gmtime(&timestamp);
xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key);
xmlNewChildPrintf(parent, NULL, BAD_CAST "string",
"%4d/%02d/%02d %02d:%02d:%02d", tm->tm_year+1900, tm->tm_mon+1,
tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
}
int count_occurrence(const char* big, const char* little) {
const char * tmp = big;
int output = 0;
while (tmp = strstr(tmp, little)) { output ++; tmp ++; }
return output;
}
static char* normalizeToASCII(char *str) {
if ( str!=NULL && !AllAscii(str))
return StripToASCII(str);
else
return copy(str);
}
static char* fetchTTFAttribute(const SplineFont *sf, int strid) {
char *value=NULL, *nonenglish=NULL;
struct ttflangname *nm;
for ( nm=sf->names; nm!=NULL; nm=nm->next ) {
if ( nm->names[strid]!=NULL ) {
nonenglish = nm->names[strid];
if ( nm->lang == 0x409 ) {
value = nm->names[strid];
break;
}
}
}
if ( value==NULL ) value=nonenglish;
return value;
}
void PListAddString(xmlNodePtr parent, const char *key, const char *value) {
if ( value==NULL ) value = "";
xmlNodePtr keynode = xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key); // "<key>%s</key>" key
#ifdef ESCAPE_LIBXML_STRINGS
size_t main_size = strlen(value) +
(count_occurrence(value, "<") * (strlen("&lt")-strlen("<"))) +
(count_occurrence(value, ">") * (strlen("&gt")-strlen("<"))) +
(count_occurrence(value, "&") * (strlen("&amp")-strlen("<")));
char *tmpstring = malloc(main_size + 1); tmpstring[0] = '\0';
off_t pos1 = 0;
while ( *value ) {
if ( *value=='<' ) {
strcat(tmpstring, "&lt;");
pos1 += strlen("&lt;");
} else if ( *value=='>' ) {
strcat(tmpstring, "&gt;");
pos1 += strlen("&gt;");
} else if ( *value == '&' ) {
strcat(tmpstring, "&amp;");
pos1 += strlen("&amp;");
} else {
tmpstring[pos1++] = *value;
tmpstring[pos1] = '\0';
}
++value;
}
xmlNodePtr valnode = xmlNewChild(parent, NULL, BAD_CAST "string", tmpstring); // "<string>%s</string>" tmpstring
#else
xmlNodePtr valnode = xmlNewTextChild(parent, NULL, BAD_CAST "string", value); // "<string>%s</string>" tmpstring
#endif
}
static void PListAddNameString(xmlNodePtr parent, const char *key, const SplineFont *sf, int strid) {
char *value=NULL, *nonenglish=NULL, *freeme=NULL;
struct ttflangname *nm;
for ( nm=sf->names; nm!=NULL; nm=nm->next ) {
if ( nm->names[strid]!=NULL ) {
nonenglish = nm->names[strid];
if ( nm->lang == 0x409 ) {
value = nm->names[strid];
break;
}
}
}
if ( value==NULL && strid==ttf_version && sf->version!=NULL ) {
char versionStr[6];
paddedVersionStr(sf->version, versionStr);
value = freeme = strconcat("Version ", versionStr);
}
if ( value==NULL && strid==ttf_copyright && sf->copyright!=NULL )
value = sf->copyright;
if ( value==NULL )
value=nonenglish;
if ( value!=NULL ) {
PListAddString(parent,key,value);
}
free(freeme);
}
static void PListAddIntArray(xmlNodePtr parent, const char *key, const char *entries, int len) {
int i;
xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key);
xmlNodePtr arrayxml = xmlNewChild(parent, NULL, BAD_CAST "array", NULL);
for ( i=0; i<len; ++i ) {
xmlNewChildInteger(arrayxml, NULL, BAD_CAST "integer", entries[i]);
}
}
static void PListAddPrivateArray(xmlNodePtr parent, const char *key, struct psdict *private) {
char *value;
if ( private==NULL )
return;
value = PSDictHasEntry(private,key);
if ( value==NULL )
return;
xmlNewChildPrintf(parent, NULL, BAD_CAST "key", "postscript%s", key); // "<key>postscript%s</key>" key
xmlNodePtr arrayxml = xmlNewChild(parent, NULL, BAD_CAST "array", NULL); // "<array>"
while ( *value==' ' || *value=='[' ) ++value;
for (;;) {
int havedot=0;
int skipping=0;
size_t tmpsize = 8;
char * tmp = malloc(tmpsize);
off_t tmppos = 0;
while ( *value!=']' && *value!='\0' && *value!=' ' && tmp!=NULL) {
// We now deal with non-integers as necessary.
if (*value=='.') {
if (havedot) skipping = true;
else havedot = 1;
}
if (skipping)
++value;
else
tmp[tmppos++] = *value++;
if (tmppos == tmpsize) { tmpsize *= 2; tmp = realloc(tmp, tmpsize); }
}
tmp[tmppos] = '\0';
if (tmp != NULL) {
if (havedot)
xmlNewChildString(arrayxml, NULL, BAD_CAST "real", BAD_CAST tmp); // "<real>%s</real>" tmp
else
xmlNewChildString(arrayxml, NULL, BAD_CAST "integer", BAD_CAST tmp); // "<integer>%s</integer>" tmp
free(tmp); tmp = NULL;
}
while ( *value==' ' ) ++value;
if ( *value==']' || *value=='\0' ) break;
}
// "</array>"
}
static void PListAddPrivateThing(xmlNodePtr parent, const char *key, struct psdict *private, char *type) {
char *value;
if ( private==NULL ) return;
value = PSDictHasEntry(private,key);
if ( value==NULL ) return;
while ( *value==' ' || *value=='[' ) ++value;
xmlNewChildPrintf(parent, NULL, BAD_CAST "key", "postscript%s", key); // "<key>postscript%s</key>" key
while ( *value==' ' || *value=='[' ) ++value;
{
int havedot=0;
int skipping=0;
size_t tmpsize = 8;
char * tmp = malloc(tmpsize);
off_t tmppos = 0;
while ( *value!=']' && *value!='\0' && *value!=' ' && tmp!=NULL) {
// We now deal with non-integers as necessary.
if (*value=='.') {
if (havedot) skipping = true;
else havedot = 1;
}
if (skipping)
++value;
else
tmp[tmppos++] = *value++;
if (tmppos == tmpsize) { tmpsize *= 2; tmp = realloc(tmp, tmpsize); }
}
tmp[tmppos] = '\0';
if (tmp != NULL) {
if (havedot)
xmlNewChildString(parent, NULL, BAD_CAST "real", BAD_CAST tmp); // "<real>%s</real>" tmp
else
xmlNewChildString(parent, NULL, BAD_CAST "integer", BAD_CAST tmp); // "<integer>%s</integer>" tmp
free(tmp); tmp = NULL;
}
while ( *value==' ' ) ++value;
}
}
static int UFOOutputMetaInfo(const char *basedir,SplineFont *sf) {
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Add the dict.
PListAddString(dictnode,"creator","net.GitHub.FontForge");
PListAddInteger(dictnode,"formatVersion",2);
char *fname = buildname(basedir, "metainfo.plist"); // Build the file name.
xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return true;
}
static int UFOOutputFontInfo(const char *basedir, SplineFont *sf, int layer) {
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Add the dict.
DBounds bb;
double test;
/* Same keys in both formats */
PListAddString(dictnode,"familyName",sf->familyname_with_timestamp ? sf->familyname_with_timestamp : sf->familyname);
PListAddString(dictnode,"styleName",SFGetModifiers(sf));
{
char* preferredFamilyName = fetchTTFAttribute(sf,ttf_preffamilyname);
char* preferredSubfamilyName = fetchTTFAttribute(sf,ttf_prefmodifiers);
char* styleMapFamily = NULL;
if (sf->styleMapFamilyName != NULL) {
/* Empty styleMapStyleName means we imported a UFO that does not have this field. Bypass the fallback. */
if (sf->styleMapFamilyName[0]!='\0')
styleMapFamily = sf->styleMapFamilyName;
} else if (preferredFamilyName != NULL && preferredSubfamilyName != NULL) {
styleMapFamily = malloc(strlen(preferredFamilyName)+strlen(preferredSubfamilyName)+2);
strcpy(styleMapFamily, preferredFamilyName);
strcat(styleMapFamily, " ");
strcat(styleMapFamily, preferredSubfamilyName);
} else if (sf->fullname != NULL) styleMapFamily = sf->fullname;
if (styleMapFamily != NULL) PListAddString(dictnode,"styleMapFamilyName", styleMapFamily);
}
{
char* styleMapName = NULL;
if (sf->pfminfo.stylemap != -1) {
if (sf->pfminfo.stylemap == 0x21) styleMapName = "bold italic";
else if (sf->pfminfo.stylemap == 0x20) styleMapName = "bold";
else if (sf->pfminfo.stylemap == 0x01) styleMapName = "italic";
else if (sf->pfminfo.stylemap == 0x40) styleMapName = "regular";
} else {
/* Figure out styleMapStyleName automatically. */
if (sf->pfminfo.weight == 700 && sf->italicangle < 0) styleMapName = "bold italic";
else if (sf->italicangle < 0) styleMapName = "italic";
else if (sf->pfminfo.weight == 700) styleMapName = "bold";
else if (sf->pfminfo.weight == 400) styleMapName = "regular";
}
if (styleMapName != NULL) PListAddString(dictnode,"styleMapStyleName", styleMapName);
}
{
// We attempt to get numeric major and minor versions for U. F. O. out of the FontForge version string.
int versionMajor = -1;
int versionMinor = -1;
if (sf->version != NULL) extractNumericVersion(sf->version, &versionMajor, &versionMinor);
if (versionMajor >= 0) PListAddInteger(dictnode,"versionMajor", versionMajor);
if (versionMinor >= 0) PListAddInteger(dictnode,"versionMinor", versionMinor);
}
PListAddNameString(dictnode,"copyright",sf,ttf_copyright);
PListAddNameString(dictnode,"trademark",sf,ttf_trademark);
PListAddInteger(dictnode,"unitsPerEm",sf->ascent+sf->descent);
// We decided that it would be more helpful to round-trip the U. F. O. data.
#if 0
test = SFXHeight(sf,layer,true);
if ( test>0 )
PListAddInteger(dictnode,"xHeight",(int) rint(test));
test = SFCapHeight(sf,layer,true);
if ( test>0 )
PListAddInteger(dictnode,"capHeight",(int) rint(test));
#else
if (sf->pfminfo.os2_capheight) PListAddInteger(dictnode,"capHeight",sf->pfminfo.os2_capheight);
if (sf->pfminfo.os2_xheight) PListAddInteger(dictnode,"xHeight",sf->pfminfo.os2_xheight);
#endif // 0
if ( sf->invalidem ) {
PListAddIntegerOrReal(dictnode,"ascender",sf->ufo_ascent);
PListAddIntegerOrReal(dictnode,"descender",sf->ufo_descent);
} else {
PListAddIntegerOrReal(dictnode,"ascender",sf->ascent);
PListAddIntegerOrReal(dictnode,"descender",-sf->descent);
}
PListAddReal(dictnode,"italicAngle",sf->italicangle);
PListAddString(dictnode,"note",sf->comments);
PListAddDate(dictnode,"openTypeHeadCreated",sf->creationtime);
SplineFontFindBounds(sf,&bb);
if ( sf->pfminfo.hheadascent_add )
PListAddInteger(dictnode,"openTypeHheaAscender",bb.maxy+sf->pfminfo.hhead_ascent);
else
PListAddInteger(dictnode,"openTypeHheaAscender",sf->pfminfo.hhead_ascent);
if ( sf->pfminfo.hheaddescent_add )
PListAddInteger(dictnode,"openTypeHheaDescender",bb.miny+sf->pfminfo.hhead_descent);
else
PListAddInteger(dictnode,"openTypeHheaDescender",sf->pfminfo.hhead_descent);
PListAddInteger(dictnode,"openTypeHheaLineGap",sf->pfminfo.linegap);
PListAddNameString(dictnode,"openTypeNameDesigner",sf,ttf_designer);
PListAddNameString(dictnode,"openTypeNameDesignerURL",sf,ttf_designerurl);
PListAddNameString(dictnode,"openTypeNameManufacturer",sf,ttf_manufacturer);
PListAddNameString(dictnode,"openTypeNameManufacturerURL",sf,ttf_venderurl);
PListAddNameString(dictnode,"openTypeNameLicense",sf,ttf_license);
PListAddNameString(dictnode,"openTypeNameLicenseURL",sf,ttf_licenseurl);
PListAddNameString(dictnode,"openTypeNameVersion",sf,ttf_version);
PListAddNameString(dictnode,"openTypeNameUniqueID",sf,ttf_uniqueid);
PListAddNameString(dictnode,"openTypeNameDescription",sf,ttf_descriptor);
PListAddNameString(dictnode,"openTypeNamePreferredFamilyName",sf,ttf_preffamilyname);
PListAddNameString(dictnode,"openTypeNamePreferredSubfamilyName",sf,ttf_prefmodifiers);
PListAddNameString(dictnode,"openTypeNameCompatibleFullName",sf,ttf_compatfull);
PListAddNameString(dictnode,"openTypeNameSampleText",sf,ttf_sampletext);
PListAddNameString(dictnode,"openTypeWWSFamilyName",sf,ttf_wwsfamily);
PListAddNameString(dictnode,"openTypeWWSSubfamilyName",sf,ttf_wwssubfamily);
if ( sf->pfminfo.panose_set )
PListAddIntArray(dictnode,"openTypeOS2Panose",sf->pfminfo.panose,10);
if ( sf->pfminfo.pfmset ) {
char vendor[8], fc[2];
PListAddInteger(dictnode,"openTypeOS2WidthClass",sf->pfminfo.width);
PListAddInteger(dictnode,"openTypeOS2WeightClass",sf->pfminfo.weight);
memcpy(vendor,sf->pfminfo.os2_vendor,4);
vendor[4] = 0;
PListAddString(dictnode,"openTypeOS2VendorID",vendor);
fc[0] = sf->pfminfo.os2_family_class>>8; fc[1] = sf->pfminfo.os2_family_class&0xff;
PListAddIntArray(dictnode,"openTypeOS2FamilyClass",fc,2);
if ( sf->pfminfo.fstype!=-1 ) {
int fscnt,i;
char fstype[16];
for ( i=fscnt=0; i<16; ++i )
if ( sf->pfminfo.fstype&(1<<i) )
fstype[fscnt++] = i;
/*
* Note that value 0x0 is represented by an empty bit, so in that case
* we output an empty array, otherwise compilers will fallback to their
* default fsType value.
*/
PListAddIntArray(dictnode,"openTypeOS2Type",fstype,fscnt);
}
if ( sf->pfminfo.typoascent_add )
PListAddInteger(dictnode,"openTypeOS2TypoAscender",sf->ascent+sf->pfminfo.os2_typoascent);
else
PListAddInteger(dictnode,"openTypeOS2TypoAscender",sf->pfminfo.os2_typoascent);
if ( sf->pfminfo.typodescent_add )
PListAddInteger(dictnode,"openTypeOS2TypoDescender",-sf->descent+sf->pfminfo.os2_typodescent);
else
PListAddInteger(dictnode,"openTypeOS2TypoDescender",sf->pfminfo.os2_typodescent);
PListAddInteger(dictnode,"openTypeOS2TypoLineGap",sf->pfminfo.os2_typolinegap);
if ( sf->pfminfo.winascent_add )
PListAddInteger(dictnode,"openTypeOS2WinAscent",bb.maxy+sf->pfminfo.os2_winascent);
else
PListAddInteger(dictnode,"openTypeOS2WinAscent",sf->pfminfo.os2_winascent);
if ( sf->pfminfo.windescent_add )
PListAddInteger(dictnode,"openTypeOS2WinDescent",-bb.miny+sf->pfminfo.os2_windescent);
else
PListAddInteger(dictnode,"openTypeOS2WinDescent",sf->pfminfo.os2_windescent);
}
if ( sf->pfminfo.subsuper_set ) {
PListAddInteger(dictnode,"openTypeOS2SubscriptXSize",sf->pfminfo.os2_subxsize);
PListAddInteger(dictnode,"openTypeOS2SubscriptYSize",sf->pfminfo.os2_subysize);
PListAddInteger(dictnode,"openTypeOS2SubscriptXOffset",sf->pfminfo.os2_subxoff);
PListAddInteger(dictnode,"openTypeOS2SubscriptYOffset",sf->pfminfo.os2_subyoff);
PListAddInteger(dictnode,"openTypeOS2SuperscriptXSize",sf->pfminfo.os2_supxsize);
PListAddInteger(dictnode,"openTypeOS2SuperscriptYSize",sf->pfminfo.os2_supysize);
PListAddInteger(dictnode,"openTypeOS2SuperscriptXOffset",sf->pfminfo.os2_supxoff);
PListAddInteger(dictnode,"openTypeOS2SuperscriptYOffset",sf->pfminfo.os2_supyoff);
PListAddInteger(dictnode,"openTypeOS2StrikeoutSize",sf->pfminfo.os2_strikeysize);
PListAddInteger(dictnode,"openTypeOS2StrikeoutPosition",sf->pfminfo.os2_strikeypos);
}
if ( sf->pfminfo.vheadset )
PListAddInteger(dictnode,"openTypeVheaTypoLineGap",sf->pfminfo.vlinegap);
if ( sf->pfminfo.hasunicoderanges ) {
char ranges[128];
int i, j, c = 0;
for ( i = 0; i<4; i++ )
for ( j = 0; j<32; j++ )
if ( sf->pfminfo.unicoderanges[i] & (1 << j) )
ranges[c++] = i*32+j;
if ( c!=0 )
PListAddIntArray(dictnode,"openTypeOS2UnicodeRanges",ranges,c);
}
if ( sf->pfminfo.hascodepages ) {
char pages[64];
int i, j, c = 0;
for ( i = 0; i<2; i++)
for ( j=0; j<32; j++ )
if ( sf->pfminfo.codepages[i] & (1 << j) )
pages[c++] = i*32+j;
if ( c!=0 )
PListAddIntArray(dictnode,"openTypeOS2CodePageRanges",pages,c);
}
PListAddString(dictnode,"postscriptFontName",sf->fontname);
PListAddString(dictnode,"postscriptFullName",sf->fullname);
PListAddString(dictnode,"postscriptWeightName",sf->weight);
/* Spec defines a "postscriptSlantAngle" but I don't think postscript does*/
/* PS does define an italicAngle, but presumably that's the general italic*/
/* angle we output earlier */
/* UniqueID is obsolete */
PListAddInteger(dictnode,"postscriptUnderlineThickness",sf->uwidth);
PListAddInteger(dictnode,"postscriptUnderlinePosition",sf->upos);
if ( sf->private!=NULL ) {
char *pt;
PListAddPrivateArray(dictnode, "BlueValues", sf->private);
PListAddPrivateArray(dictnode, "OtherBlues", sf->private);
PListAddPrivateArray(dictnode, "FamilyBlues", sf->private);
PListAddPrivateArray(dictnode, "FamilyOtherBlues", sf->private);
PListAddPrivateArray(dictnode, "StemSnapH", sf->private);
PListAddPrivateArray(dictnode, "StemSnapV", sf->private);
PListAddPrivateThing(dictnode, "BlueFuzz", sf->private, "integer");
PListAddPrivateThing(dictnode, "BlueShift", sf->private, "integer");
PListAddPrivateThing(dictnode, "BlueScale", sf->private, "real");
if ( (pt=PSDictHasEntry(sf->private,"ForceBold"))!=NULL )
PListAddBoolean(dictnode, "postscriptForceBold", strstr(pt,"true")!=NULL ? true : false );
}
if ( sf->fondname!=NULL )
PListAddString(dictnode,"macintoshFONDName",sf->fondname);
// TODO: Output unrecognized data.
char *fname = buildname(basedir, "fontinfo.plist"); // Build the file name.
xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return true;
}
int kernclass_for_groups_plist(struct splinefont *sf, struct kernclass *kc, int flags) {
// Note that this is not a complete logical inverse of sister function kernclass_for_feature_file.
return ((flags & FF_KERNCLASS_FLAG_NATIVE) ||
(!(flags & FF_KERNCLASS_FLAG_FEATURE) && !kc->feature && (sf->preferred_kerning & 1)));
}
void ClassKerningAddExtensions(struct kernclass * target) {
if (target->firsts_names == NULL && target->first_cnt) target->firsts_names = calloc(target->first_cnt, sizeof(char *));
if (target->seconds_names == NULL && target->second_cnt) target->seconds_names = calloc(target->second_cnt, sizeof(char *));
if (target->firsts_flags == NULL && target->first_cnt) target->firsts_flags = calloc(target->first_cnt, sizeof(int));
if (target->seconds_flags == NULL && target->second_cnt) target->seconds_flags = calloc(target->second_cnt, sizeof(int));
if (target->offsets_flags == NULL && (target->first_cnt * target->second_cnt)) target->offsets_flags = calloc(target->first_cnt * target->second_cnt, sizeof(int));
}
void UFONameKerningClasses(SplineFont *sf) {
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _class_name_hash;
struct glif_name_index * class_name_hash = &_class_name_hash; // Open the hash table.
memset(class_name_hash, 0, sizeof(struct glif_name_index));
#else
void * class_name_hash = NULL;
#endif
struct kernclass *current_kernclass;
int isv;
int isr;
int i;
int absolute_index = 0; // This gives us a unique index for each kerning class.
// First we catch the existing names.
#ifdef FF_UTHASH_GLIF_NAMES
HashKerningClassNamesCaps(sf, class_name_hash); // Note that we use the all-caps hasher for compatibility with the official naming scheme and the following code.
#endif
// Next we create names for the unnamed. Note that we currently avoid naming anything that might go into the feature file (since that handler currently creates its own names).
absolute_index = 0;
for (isv = 0; isv < 2; isv++)
for ( current_kernclass = (isv ? sf->vkerns : sf->kerns); current_kernclass != NULL; current_kernclass = current_kernclass->next )
for (isr = 0; isr < 2; isr++) {
// If the special class kerning storage blocks are unallocated, we allocate them if using native U. F. O. class kerning or skip the naming otherwise.
if ( (isr ? current_kernclass->seconds_names : current_kernclass->firsts_names) == NULL ) {
if ( !(current_kernclass->feature) && (sf->preferred_kerning & 1) ) {
ClassKerningAddExtensions(current_kernclass);
} else {
continue;
}
}
for ( i=0; i< (isr ? current_kernclass->second_cnt : current_kernclass->first_cnt); i++ )
if ( ((isr ? current_kernclass->seconds_names[i] : current_kernclass->firsts_names[i]) == NULL) &&
kernclass_for_groups_plist(sf, current_kernclass, (isr ? current_kernclass->seconds_flags[i] : current_kernclass->firsts_flags[i]))
) {
int classflags = isr ? current_kernclass->seconds_flags[i] : current_kernclass->firsts_flags[i];
// If the splinefont is not forcing a default group type and the group is already flagged for a feature file or to have a feature-compatible name, we give it a feature-compatible name.
// Otherwise, we give it a native U. F. O. name.
char * startname =
(
(
(
(sf->preferred_kerning == 0) &&
(classflags & (FF_KERNCLASS_FLAG_FEATURE | FF_KERNCLASS_FLAG_NAMETYPE))
) ||
(sf->preferred_kerning & 2) || (sf->preferred_kerning & 4)
) ?
(
isv ?
(isr ? "@MMK_B_FF" : "@MMK_A_FF") :
(isr ? "@MMK_R_FF" : "@MMK_L_FF")
) :
(
isv ?
(isr ? "public.vkern2.FF" : "public.vkern1.FF") :
(isr ? "public.kern2.FF" : "public.kern1.FF")
)
);
// We must flag the group as being destined for the native file if it has a non-feature-compatible name. Otherwise, we might corrupt the feature file if it ever starts using the names.
if (startname[0] != '@') {
if (isr) { current_kernclass->seconds_flags[i] |= FF_KERNCLASS_FLAG_NATIVE; current_kernclass->seconds_flags[i] &= ~FF_KERNCLASS_FLAG_FEATURE; }
else { current_kernclass->firsts_flags[i] |= FF_KERNCLASS_FLAG_NATIVE; current_kernclass->firsts_flags[i] &= ~FF_KERNCLASS_FLAG_FEATURE; }
}
// Number the name uniquely. (Note the number-forcing flag.)
// And add it to the hash table with its index.
char * numberedname = ufo_name_number(class_name_hash, absolute_index + i, startname, "", "", 23);
if (isr) current_kernclass->seconds_names[i] = numberedname;
else current_kernclass->firsts_names[i] = numberedname;
numberedname = NULL;
}
absolute_index +=i;
}
#ifdef FF_UTHASH_GLIF_NAMES
glif_name_hash_destroy(class_name_hash); // Close the hash table.
#endif
}
static int UFOOutputGroups(const char *basedir, SplineFont *sf) {
SplineChar *sc;
int has_content = 0;
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Add the dict.
// In order to preserve ordering, we do some very icky and inefficient things.
// First, we assign names to any unnamed kerning groups using the public.kern syntax if the splinefont is set to use native U. F. O. kerning.
// We assign @MMK names otherwise.
// In assigning names, we check collisions both with other kerning classes and with natively listed groups.
UFONameKerningClasses(sf);
// Once names are assigned, we start outputting the native groups, with some exceptions.
// Since we consider kerning groups to be natively handled, any group with a kerning-style name gets checked against kerning classes.
// If it exists, we output it and flag it as output in a temporary array. If it does not exist, we skip it.
// When this is complete, we go through the left and right kerning classes and output any that have not been output, adding them to the native list as we do so.
int kerning_class_count = CountKerningClasses(sf);
char *output_done = kerning_class_count ? calloc(kerning_class_count, sizeof(char)) : NULL;
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _class_name_hash;
struct glif_name_index * class_name_hash = &_class_name_hash; // Open the hash table.
memset(class_name_hash, 0, sizeof(struct glif_name_index));
HashKerningClassNames(sf, class_name_hash);
#else
void * class_name_hash = NULL;
#endif
struct ff_glyphclasses *current_group;
for (current_group = sf->groups; current_group != NULL; current_group = current_group->next) {
if (current_group->classname != NULL) {
int grouptype = GroupNameType(current_group->classname);
if (grouptype > 0) {
// We skip the group if it has a corrupt feature-like name.
if (grouptype & (GROUP_NAME_KERNING_UFO | GROUP_NAME_KERNING_FEATURE)) {
// If the group is a kerning group, we defer to the native kerning data for existence and content.
struct glif_name *class_name_record = glif_name_search_glif_name(class_name_hash, current_group->classname);
if (class_name_record != NULL) {
int absolute_index = class_name_record->gid; // This gives us a unique index for the kerning class.
struct kernclass *kc;
int isv;
int isr;
int i;
int offset;
if (KerningClassSeekByAbsoluteIndex(sf, absolute_index, &kc, &isv, &isr, &offset)) {
// The group still exists.
xmlNewChild(dictnode, NULL, BAD_CAST "key", current_group->classname);
xmlNodePtr grouparray = xmlNewChild(dictnode, NULL, BAD_CAST "array", NULL);
// We use the results of the preceding search in order to get the list.
char *rawglyphlist = (
(isr ? kc->seconds[offset] : kc->firsts[offset])
);
// We need to convert from the space-delimited string to something more easily accessed on a per-item basis.
char **glyphlist = StringExplode(rawglyphlist, ' ');
// We will then output only those entries for which SFGetChar succeeds.
int index = 0;
while (glyphlist[index] != NULL) {
if (SFGetChar(sf, -1, glyphlist[index]))
xmlNewChild(grouparray, NULL, BAD_CAST "string", glyphlist[index]);
index++;
}
ExplodedStringFree(glyphlist);
// We flag the output of this kerning class as complete.
output_done[absolute_index] |= 1;
has_content = 1;
}
}
}
} else {
// If the group is not a kerning group, we just output it raw (for now).
xmlNewChild(dictnode, NULL, BAD_CAST "key", current_group->classname);
xmlNodePtr grouparray = xmlNewChild(dictnode, NULL, BAD_CAST "array", NULL);
// We need to convert from the space-delimited string to something more easily accessed on a per-item basis.
char **glyphlist = StringExplode(current_group->glyphs, ' ');
// We will then output only those entries for which SFGetChar succeeds.
int index = 0;
while (glyphlist[index] != NULL) {
if (SFGetChar(sf, -1, glyphlist[index]))
xmlNewChild(grouparray, NULL, BAD_CAST "string", glyphlist[index]);
index++;
}
ExplodedStringFree(glyphlist);
has_content = 1;
}
}
}
{
// Oh. But we've not finished yet. Some kerning classes may not be in the groups listing.
struct kernclass *current_kernclass;
int isv;
int isr;
int i;
int absolute_index = 0;
for (isv = 0; isv < 2; isv++)
for (current_kernclass = (isv ? sf->vkerns : sf->kerns); current_kernclass != NULL; current_kernclass = current_kernclass->next)
for (isr = 0; isr < 2; isr++)
if (isr ? current_kernclass->seconds_names : current_kernclass->firsts_names) {
for (i=0; i < (isr ? current_kernclass->second_cnt : current_kernclass->first_cnt); ++i) {
const char *classname = (isr ? current_kernclass->seconds_names[i] : current_kernclass->firsts_names[i]);
const char *rawglyphlist = (isr ? current_kernclass->seconds[i] : current_kernclass->firsts[i]);
int classflags = (isr ? current_kernclass->seconds_flags[i] : current_kernclass->firsts_flags[i]);
// Note that we only output if the kernclass is destined for U. F. O. and has not already met said fate.
if (classname != NULL && rawglyphlist != NULL &&
!(output_done[absolute_index + i]) && kernclass_for_groups_plist(sf, current_kernclass, classflags)) {
xmlNewChild(dictnode, NULL, BAD_CAST "key", classname);
xmlNodePtr grouparray = xmlNewChild(dictnode, NULL, BAD_CAST "array", NULL);
// We need to convert from the space-delimited string to something more easily accessed on a per-item basis.
char **glyphlist = StringExplode(rawglyphlist, ' ');
// We will then output only those entries for which SFGetChar succeeds.
int index = 0;
while (glyphlist[index] != NULL) {
if (SFGetChar(sf, -1, glyphlist[index]))
xmlNewChild(grouparray, NULL, BAD_CAST "string", glyphlist[index]);
index++;
}
ExplodedStringFree(glyphlist);
// We flag the output of this kerning class as complete.
output_done[absolute_index + i] |= 1;
has_content = 1;
}
}
absolute_index +=i;
}
}
#ifdef FF_UTHASH_GLIF_NAMES
glif_name_hash_destroy(class_name_hash); // Close the hash table.
#endif
if (output_done != NULL) { free(output_done); output_done = NULL; }
char *fname = buildname(basedir, "groups.plist"); // Build the file name.
if (has_content) xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return true;
}
static void KerningPListAddGlyph(xmlNodePtr parent, const char *key, const KernPair *kp) {
xmlNewChild(parent, NULL, BAD_CAST "key", BAD_CAST key); // "<key>%s</key>" key
xmlNodePtr dictxml = xmlNewChild(parent, NULL, BAD_CAST "dict", NULL); // "<dict>"
while ( kp!=NULL ) {
PListAddInteger(dictxml, kp->sc->name, kp->off); // "<key>%s</key><integer>%d</integer>" kp->sc->name kp->off
kp = kp->next;
}
}
#ifndef FF_UTHASH_GLIF_NAMES
static int UFOOutputKerning(const char *basedir, const SplineFont *sf) {
SplineChar *sc;
int i;
int has_content = 0;
if (!(sf->preferred_kerning & 1)) return true; // This goes into the feature file by default now.
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Add the dict.
for ( i=0; i<sf->glyphcnt; ++i ) if ( SCWorthOutputting(sc=sf->glyphs[i]) && sc->kerns!=NULL ) {
KerningPListAddGlyph(dictnode,sc->name,sc->kerns);
has_content = 1;
}
char *fname = buildname(basedir, "kerning.plist"); // Build the file name.
if (has_content) xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document if it's not empty.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return true;
}
static int UFOOutputVKerning(const char *basedir, const SplineFont *sf) {
SplineChar *sc;
int i;
int has_content = 0;
if (!(sf->preferred_kerning & 1)) return true; // This goes into the feature file by default now.
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Add the dict.
for ( i=sf->glyphcnt-1; i>=0; --i ) if ( SCWorthOutputting(sc=sf->glyphs[i]) && sc->vkerns!=NULL ) break;
if ( i<0 ) return( true );
for ( i=0; i<sf->glyphcnt; ++i ) if ( (sc=sf->glyphs[i])!=NULL && sc->vkerns!=NULL ) {
KerningPListAddGlyph(dictnode,sc->name,sc->vkerns);
has_content = 1;
}
char *fname = buildname(basedir, "vkerning.plist"); // Build the file name.
if (has_content) xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document if it's not empty.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return true;
}
static int UFOOutputKerning2(const char *basedir, const SplineFont *sf, int isv) {
if (isv) return UFOOutputVKerning(basedir, sf);
else return UFOOutputKerning(basedir, sf);
}
#else // FF_UTHASH_GLIF_NAMES
// New approach.
// We will build a tree.
struct ufo_kerning_tree_left;
struct ufo_kerning_tree_right;
struct ufo_kerning_tree_left {
char *name;
struct ufo_kerning_tree_right *first_right;
struct ufo_kerning_tree_right *last_right;
struct ufo_kerning_tree_left *next;
};
struct ufo_kerning_tree_right {
char *name;
int value;
struct ufo_kerning_tree_right *next;
};
struct ufo_kerning_tree_session {
struct ufo_kerning_tree_left *first_left;
struct ufo_kerning_tree_left *last_left;
int left_group_count;
struct glif_name_index _left_group_name_hash;
int class_pair_count;
struct glif_name_index _class_pair_hash;
};
void ufo_kerning_tree_destroy_contents(struct ufo_kerning_tree_session *session) {
struct ufo_kerning_tree_left *current_left;
struct ufo_kerning_tree_left *next_left;
for (current_left = session->first_left; current_left != NULL; current_left = next_left) {
next_left = current_left->next;
struct ufo_kerning_tree_right *current_right;
struct ufo_kerning_tree_right *next_right;
for (current_right = current_left->first_right; current_right != NULL; current_right = next_right) {
next_right = current_right->next;
if (current_right->name != NULL) free(current_right->name);
free(current_right);
}
if (current_left->name != NULL) free(current_left->name);
free(current_left);
}
memset(session, 0, sizeof(struct ufo_kerning_tree_session));
}
int ufo_kerning_tree_attempt_insert(struct ufo_kerning_tree_session *session, const char *left_name, const char *right_name, int value) {
struct glif_name_index *left_group_name_hash = &(session->_left_group_name_hash);
struct glif_name_index *class_pair_hash = &(session->_class_pair_hash);
char *tmppairname = NULL;
asprintf(&tmppairname, "%s %s", left_name, right_name);
struct ufo_kerning_tree_left *first_left = NULL;
struct ufo_kerning_tree_left *last_left = NULL;
if (!glif_name_search_glif_name(class_pair_hash, tmppairname)) {
struct ufo_kerning_tree_left *current_left;
// We look for a tree node matching the left side of the pair.
for (current_left = session->first_left; current_left != NULL &&
(current_left->name == NULL || strcmp(current_left->name, left_name));
current_left = current_left->next);
// If the search fails, we make a new node.
if (current_left == NULL) {
current_left = calloc(1, sizeof(struct ufo_kerning_tree_left));
current_left->name = copy(left_name);
if (session->last_left != NULL) session->last_left->next = current_left;
else session->first_left = current_left;
session->last_left = current_left;
}
{
// We already know from the pair hash search that this pair does not exist.
// So we go right to the end.
struct ufo_kerning_tree_right *current_right = calloc(1, sizeof(struct ufo_kerning_tree_right));
current_right->name = copy(right_name);
current_right->value = value;
if (current_left->last_right != NULL) current_left->last_right->next = current_right;
else current_left->first_right = current_right;
current_left->last_right = current_right;
char *newpairname = NULL;
asprintf(&newpairname, "%s %s", left_name, right_name);
glif_name_track_new(class_pair_hash, session->class_pair_count++, newpairname);
free(newpairname); newpairname = NULL;
}
}
free(tmppairname); tmppairname = NULL;
return 0;
}
static int UFOOutputKerning2(const char *basedir, SplineFont *sf, int isv) {
int i, j;
int has_content = 0;
if (!(sf->preferred_kerning & 1)) return true; // This goes into the feature file by default now.
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Add the dict.
// In order to preserve ordering, we do some very icky and inefficient things.
// First, we assign names to any unnamed kerning groups using the public.kern syntax if the splinefont is set to use native U. F. O. kerning.
// We assign @MMK names otherwise.
// In assigning names, we check collisions both with other kerning classes and with natively listed groups.
UFONameKerningClasses(sf);
// Once names are assigned, we start outputting the native groups, with some exceptions.
// Since we consider kerning groups to be natively handled, any group with a kerning-style name gets checked against kerning classes.
// If it exists, we output it and flag it as output in a temporary array. If it does not exist, we skip it.
// When this is complete, we go through the left and right kerning classes and output any that have not been output, adding them to the native list as we do so.
int kerning_class_count = CountKerningClasses(sf);
int kerning_class_pair_count = kerning_class_count * kerning_class_count;
char *output_done = kerning_class_pair_count ? calloc(kerning_class_pair_count, sizeof(char)) : NULL;
struct ufo_kerning_tree_session _session;
memset(&_session, 0, sizeof(struct ufo_kerning_tree_session));
struct ufo_kerning_tree_session *session = &_session;
struct glif_name_index _class_name_hash;
struct glif_name_index * class_name_hash = &_class_name_hash; // Open the hash table.
memset(class_name_hash, 0, sizeof(struct glif_name_index));
HashKerningClassNames(sf, class_name_hash);
// We process the raw kerning list first in order to give preference to the original ordering.
struct ff_rawoffsets *current_groupkern;
for (current_groupkern = (isv ? sf->groupvkerns : sf->groupkerns); current_groupkern != NULL; current_groupkern = current_groupkern->next) {
if (current_groupkern->left != NULL && current_groupkern->right != NULL) {
int left_grouptype = GroupNameType(current_groupkern->left);
int right_grouptype = GroupNameType(current_groupkern->right);
int offset = 0;
int valid = 0;
if (left_grouptype > 0 && right_grouptype > 0) {
// It's a pure class look-up.
struct glif_name *left_class_name_record = glif_name_search_glif_name(class_name_hash, current_groupkern->left);
struct glif_name *right_class_name_record = glif_name_search_glif_name(class_name_hash, current_groupkern->right);
if ((left_grouptype & right_grouptype & (GROUP_NAME_KERNING_UFO | GROUP_NAME_KERNING_FEATURE)) &&
!(left_grouptype & GROUP_NAME_RIGHT) && (right_grouptype & GROUP_NAME_RIGHT) &&
((left_grouptype & GROUP_NAME_VERTICAL) == (isv * GROUP_NAME_VERTICAL)) &&
((right_grouptype & GROUP_NAME_VERTICAL) == (isv * GROUP_NAME_VERTICAL)) &&
left_class_name_record != NULL && right_class_name_record != NULL) {
// If the group is a kerning group, we defer to the native kerning data for existence and content.
{
int left_absolute_index = left_class_name_record->gid; // This gives us a unique index for the kerning class.
int right_absolute_index = right_class_name_record->gid; // This gives us a unique index for the kerning class.
struct kernclass *left_kc, *right_kc;
int left_isv, right_isv;
int left_isr, right_isr;
int left_offset, right_offset;
if (KerningClassSeekByAbsoluteIndex(sf, left_absolute_index, &left_kc, &left_isv, &left_isr, &left_offset) &&
KerningClassSeekByAbsoluteIndex(sf, right_absolute_index, &right_kc, &right_isv, &right_isr, &right_offset) &&
left_kc == right_kc) {
offset = left_kc->offsets[left_offset*left_kc->second_cnt+right_offset];
valid = 1;
}
}
}
} else if (left_grouptype == 0 && right_grouptype == 0) {
// It's a plain kerning pair.
struct splinechar *sc = SFGetChar(sf, -1, current_groupkern->left);
struct splinechar *ssc = SFGetChar(sf, -1, current_groupkern->right);
if (sc && ssc) {
struct kernpair *current_kernpair = (isv ? sc->vkerns : sc->kerns);
while (current_kernpair != NULL && current_kernpair->sc != ssc) current_kernpair = current_kernpair->next;
if (current_kernpair != NULL && current_kernpair->sc == ssc) {
offset = current_kernpair->off;
valid = 1;
}
}
} else if (left_grouptype == 0 && right_grouptype > 0) {
// It's a mixed pair. FontForge does not handle these natively right now, so, if the two references are valid, we output the raw value.
struct splinechar *sc = SFGetChar(sf, -1, current_groupkern->left);
struct glif_name *right_class_name_record = glif_name_search_glif_name(class_name_hash, current_groupkern->right);
if ((right_grouptype & (GROUP_NAME_KERNING_UFO | GROUP_NAME_KERNING_FEATURE)) &&
(right_grouptype & GROUP_NAME_RIGHT) &&
((right_grouptype & GROUP_NAME_VERTICAL) == (isv * GROUP_NAME_VERTICAL)) &&
right_class_name_record != NULL &&
sc != NULL) {
offset = current_groupkern->offset;
valid = 1;
}
} else if (left_grouptype > 0 && right_grouptype == 0) {
// It's a mixed pair. FontForge does not handle these natively right now, so, if the two references are valid, we output the raw value.
struct splinechar *ssc = SFGetChar(sf, -1, current_groupkern->right);
struct glif_name *left_class_name_record = glif_name_search_glif_name(class_name_hash, current_groupkern->left);
if ((left_grouptype & (GROUP_NAME_KERNING_UFO | GROUP_NAME_KERNING_FEATURE)) &&
!(left_grouptype & GROUP_NAME_RIGHT) &&
((left_grouptype & GROUP_NAME_VERTICAL) == (isv * GROUP_NAME_VERTICAL)) &&
left_class_name_record != NULL &&
ssc != NULL) {
offset = current_groupkern->offset;
valid = 1;
}
} else {
// Something is wrong.
}
if (valid) {
ufo_kerning_tree_attempt_insert(session, current_groupkern->left, current_groupkern->right, offset);
}
}
}
{
// Oh. But we've not finished yet. New class kerns may not be in the original list.
struct kernclass *kc;
char *left_name;
char *right_name;
for (kc = isv ? sf->vkerns : sf->kerns; kc != NULL; kc = kc->next)
if (kc->firsts_names && kc->seconds_names && kc->firsts_flags && kc->seconds_flags &&
kc->offsets_flags)
for ( i=0; i<kc->first_cnt; ++i ) if ( kc->firsts[i]!=NULL && kc->firsts_names[i]!=NULL && kernclass_for_groups_plist(sf, kc, kc->firsts_flags[i]))
for ( j=0; j<kc->second_cnt; ++j ) if ( kc->seconds[j]!=NULL && kc->seconds_names[j]!=NULL && kernclass_for_groups_plist(sf, kc, kc->firsts_flags[j]))
if (kernclass_for_groups_plist(sf, kc, kc->offsets_flags[i*kc->second_cnt+j]) && kc->offsets[i*kc->second_cnt+j] != 0)
ufo_kerning_tree_attempt_insert(session, kc->firsts_names[i], kc->seconds_names[j], kc->offsets[i*kc->second_cnt+j]);
}
{
// And don't forget about pair kerns.
for ( i=0; i<sf->glyphcnt; ++i ) {
struct splinechar *sc = sf->glyphs[i];
struct kernpair *kp;
if ( (SCWorthOutputting(sc) || SCHasData(sc) || sc->glif_name != NULL) && (isv ? sc->vkerns : sc->kerns ) !=NULL )
for (kp = (isv ? sc->vkerns : sc->kerns); kp != NULL; kp = kp->next)
if (kp->sc != NULL && sc->name != NULL && kp->sc->name != NULL)
ufo_kerning_tree_attempt_insert(session, sc->name, kp->sc->name, kp->off);
}
}
{
// Output time has arrived.
struct ufo_kerning_tree_left *current_left;
for (current_left = session->first_left; current_left != NULL; current_left = current_left->next) {
if (current_left->name != NULL) {
xmlNewChild(dictnode, NULL, BAD_CAST "key", BAD_CAST current_left->name); // "<key>%s</key>" key
xmlNodePtr dictxml = xmlNewChild(dictnode, NULL, BAD_CAST "dict", NULL); // "<dict>"
struct ufo_kerning_tree_right *current_right;
for (current_right = current_left->first_right; current_right != NULL; current_right = current_right->next)
if (current_right->name != NULL) PListAddInteger(dictxml, current_right->name, current_right->value);
has_content = 1;
}
}
}
glif_name_hash_destroy(class_name_hash); // Close the hash table.
ufo_kerning_tree_destroy_contents(session);
if (output_done != NULL) { free(output_done); output_done = NULL; }
char *fname = buildname(basedir, (isv ? "vkerning.plist" : "kerning.plist")); // Build the file name.
if (has_content) xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return true;
}
#endif // FF_UTHASH_GLIF_NAMES
static int UFOOutputLib(const char *basedir, const SplineFont *sf) {
#ifndef _NO_PYTHON
if ( sf->python_persistent==NULL || PyMapping_Check(sf->python_persistent) == 0) return true;
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) return false; // Find the root node.
xmlNodePtr dictnode = PythonLibToXML(sf->python_persistent,NULL,sf->python_persistent_has_lists);
xmlAddChild(rootnode, dictnode);
char *fname = buildname(basedir, "lib.plist"); // Build the file name.
xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
#endif
return( true );
}
static int UFOOutputFeatures(const char *basedir, SplineFont *sf) {
char *fname = buildname(basedir,"features.fea");
FILE *feats = fopen( fname, "w" );
int err;
free(fname);
if ( feats==NULL )
return( false );
FeatDumpFontLookups(feats,sf);
err = ferror(feats);
fclose(feats);
return( !err );
}
int WriteUFOLayer(const char * glyphdir, SplineFont * sf, int layer) {
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr dictnode = xmlNewChild(rootnode, NULL, BAD_CAST "dict", NULL); if (dictnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Make the dict.
GFileMkDir( glyphdir );
int i;
SplineChar * sc;
int err = 0;
for ( i=0; i<sf->glyphcnt; ++i ) if ( SCLWorthOutputtingOrHasData(sc=sf->glyphs[i], layer) ||
( layer == ly_fore && (SCWorthOutputting(sc) || SCHasData(sc) || sc->glif_name != NULL) ) ) {
// TODO: Optionally skip rewriting an untouched glyph.
// Do we track modified glyphs carefully enough for this?
char * final_name;
asprintf(&final_name, "%s%s%s", "", sc->glif_name, ".glif"); // Generate the final name with prefix and suffix.
PListAddString(dictnode,sc->name,final_name); // Add the glyph to the table of contents.
err |= !GlifDump(glyphdir,final_name,sc,layer);
free(final_name); final_name = NULL;
}
char *fname = buildname(glyphdir, "contents.plist"); // Build the file name for the contents.
xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
return err;
}
int WriteUFOFontFlex(const char *basedir, SplineFont *sf, enum fontformat ff,int flags,
const EncMap *map,int layer, int all_layers) {
char *glyphdir, *gfname;
int err;
FILE *plist;
int i;
SplineChar *sc;
/* Clean it out, if it exists */
if (!GFileRemove(basedir, true)) {
LogError(_("Error clearing %s."), basedir);
}
/* Create it */
if (GFileMkDir( basedir ) == -1) return false;
locale_t tmplocale; locale_t oldlocale; // Declare temporary locale storage.
switch_to_c_locale(&tmplocale, &oldlocale); // Switch to the C locale temporarily and cache the old locale.
err = !UFOOutputMetaInfo(basedir,sf);
err |= !UFOOutputFontInfo(basedir,sf,layer);
err |= !UFOOutputGroups(basedir,sf);
err |= !UFOOutputKerning2(basedir,sf,0); // Horizontal.
err |= !UFOOutputKerning2(basedir,sf,1); // Vertical.
err |= !UFOOutputLib(basedir,sf);
err |= !UFOOutputFeatures(basedir,sf);
if ( err ) {
switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
return false;
}
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _glif_name_hash;
struct glif_name_index * glif_name_hash = &_glif_name_hash; // Open the hash table.
memset(glif_name_hash, 0, sizeof(struct glif_name_index));
#else
void * glif_name_hash = NULL;
#endif
// First we generate glif names.
for ( i=0; i<sf->glyphcnt; ++i ) if ( SCWorthOutputting(sc=sf->glyphs[i]) || SCHasData(sf->glyphs[i]) ) {
char * startname = NULL;
if (sc->glif_name != NULL)
startname = strdup(sc->glif_name); // If the splinechar has a glif name, try to use that.
else
startname = ufo_name_mangle(sc->name, "", ".glif", 7); // If not, call the mangler.
// Number the name (as the specification requires) if there is a collision.
// And add it to the hash table with its index.
char * numberedname = ufo_name_number(glif_name_hash, i, startname, "", ".glif", 7);
free(startname); startname = NULL;
// We update the saved glif_name only if it is different (so as to minimize churn).
if ((sc->glif_name != NULL) && (strcmp(sc->glif_name, numberedname) != 0)) {
free(sc->glif_name); sc->glif_name = NULL;
}
if (sc->glif_name == NULL) {
sc->glif_name = numberedname;
} else {
free(numberedname);
}
numberedname = NULL;
}
#ifdef FF_UTHASH_GLIF_NAMES
glif_name_hash_destroy(glif_name_hash); // Close the hash table.
#endif
if (all_layers) {
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _layer_name_hash;
struct glif_name_index * layer_name_hash = &_layer_name_hash; // Open the hash table.
memset(layer_name_hash, 0, sizeof(struct glif_name_index));
struct glif_name_index _layer_path_hash;
struct glif_name_index * layer_path_hash = &_layer_path_hash; // Open the hash table.
memset(layer_path_hash, 0, sizeof(struct glif_name_index));
#else
void * layer_name_hash = NULL;
#endif
switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
xmlDocPtr plistdoc = PlistInit(); if (plistdoc == NULL) return false; // Make the document.
xmlNodePtr rootnode = xmlDocGetRootElement(plistdoc); if (rootnode == NULL) { xmlFreeDoc(plistdoc); return false; } // Find the root node.
xmlNodePtr arraynode = xmlNewChild(rootnode, NULL, BAD_CAST "array", NULL); if (arraynode == NULL) { xmlFreeDoc(plistdoc); return false; } // Make the dict.
switch_to_c_locale(&tmplocale, &oldlocale); // Switch to the C locale temporarily and cache the old locale.
int layer_pos;
for (layer_pos = 0; layer_pos < sf->layer_cnt; layer_pos++) {
// We don't want to emit the default background layer unless it has stuff in it or was in the input U. F. O..
if (layer_pos == ly_back && !LayerWorthOutputting(sf, layer_pos) && sf->layers[layer_pos].ufo_path == NULL) continue;
// We start building the layer contents entry.
xmlNodePtr layernode = xmlNewChild(arraynode, NULL, BAD_CAST "array", NULL);
// We make the layer name.
char * layer_name_start = NULL;
if (layer_pos == ly_fore) layer_name_start = "public.default";
else if (layer_pos == ly_back) layer_name_start = "public.background";
else layer_name_start = sf->layers[layer_pos].name;
if (layer_name_start == NULL) layer_name_start = "unnamed"; // The remangle step adds any needed numbers.
char * numberedlayername = ufo_name_number(layer_name_hash, layer_pos, layer_name_start, "", "", 7);
// We make the layer path.
char * layer_path_start = NULL;
char * numberedlayerpath = NULL;
char * numberedlayerpathwithglyphs = NULL;
if (layer_pos == ly_fore) {
numberedlayerpath = strdup("glyphs");
asprintf(&numberedlayerpathwithglyphs, "%s", numberedlayerpath);
} else if (sf->layers[layer_pos].ufo_path != NULL) {
layer_path_start = strdup(sf->layers[layer_pos].ufo_path);
numberedlayerpath = ufo_name_number(layer_path_hash, layer_pos, layer_path_start, "", "", 7);
asprintf(&numberedlayerpathwithglyphs, "%s", numberedlayerpath);
} else {
layer_path_start = ufo_name_mangle(sf->layers[layer_pos].name, "glyphs.", "", 7);
numberedlayerpath = ufo_name_number(layer_path_hash, layer_pos, layer_path_start, "glyphs.", "", 7);
asprintf(&numberedlayerpathwithglyphs, "glyphs.%s", numberedlayerpath);
}
if (layer_path_start != NULL) { free(layer_path_start); layer_path_start = NULL; }
// We write to the layer contents.
xmlNewTextChild(layernode, NULL, BAD_CAST "string", numberedlayername);
xmlNewTextChild(layernode, NULL, BAD_CAST "string", numberedlayerpathwithglyphs);
glyphdir = buildname(basedir, numberedlayerpathwithglyphs);
// We write the glyph directory.
err |= WriteUFOLayer(glyphdir, sf, layer_pos);
free(numberedlayername); numberedlayername = NULL;
free(numberedlayerpath); numberedlayerpath = NULL;
free(numberedlayerpathwithglyphs); numberedlayerpathwithglyphs = NULL;
free(glyphdir); glyphdir = NULL;
}
char *fname = buildname(basedir, "layercontents.plist"); // Build the file name for the contents.
xmlSaveFormatFileEnc(fname, plistdoc, "UTF-8", 1); // Store the document.
free(fname); fname = NULL;
xmlFreeDoc(plistdoc); // Free the memory.
xmlCleanupParser();
#ifdef FF_UTHASH_GLIF_NAMES
glif_name_hash_destroy(layer_name_hash); // Close the hash table.
glif_name_hash_destroy(layer_path_hash); // Close the hash table.
#endif
} else {
glyphdir = buildname(basedir,"glyphs");
WriteUFOLayer(glyphdir, sf, layer);
free(glyphdir); glyphdir = NULL;
}
switch_to_old_locale(&tmplocale, &oldlocale); // Switch to the cached locale.
return !err;
}
int SplineFontHasUFOLayerNames(SplineFont *sf) {
if (sf == NULL || sf->layers == NULL) return 0;
int layer_pos = 0;
for (layer_pos = 0; layer_pos < sf->layer_cnt; layer_pos++) {
if (sf->layers[layer_pos].ufo_path != NULL) return 1;
}
return 0;
}
int WriteUFOFont(const char *basedir, SplineFont *sf, enum fontformat ff, int flags,
const EncMap *map, int layer) {
if (SplineFontHasUFOLayerNames(sf))
return WriteUFOFontFlex(basedir, sf, ff, flags, map, layer, 1);
else
return WriteUFOFontFlex(basedir, sf, ff, flags, map, layer, 0);
}
/* ************************************************************************** */
/* ***************************** UFO Input **************************** */
/* ************************************************************************** */
static char *get_thingy(FILE *file,char *buffer,char *tag) {
int ch;
char *pt;
for (;;) {
while ( (ch=getc(file))!='<' && ch!=EOF );
if ( ch==EOF )
return( NULL );
while ( (ch=getc(file))!=EOF && isspace(ch) );
pt = tag;
while ( ch==*pt || tolower(ch)==*pt ) {
++pt;
ch = getc(file);
}
if ( *pt=='\0' )
continue;
if ( ch==EOF )
return( NULL );
while ( isspace(ch)) ch=getc(file);
if ( ch!='>' )
continue;
pt = buffer;
while ( (ch=getc(file))!='<' && ch!=EOF && pt<buffer+1000)
*pt++ = ch;
*pt = '\0';
return( buffer );
}
}
char **NamesReadUFO(char *filename) {
char *fn = buildname(filename,"fontinfo.plist");
FILE *info = fopen(fn,"r");
char buffer[1024];
char **ret;
free(fn);
if ( info==NULL )
return( NULL );
while ( get_thingy(info,buffer,"key")!=NULL ) {
if ( strcmp(buffer,"fontName")!=0 ) {
if ( get_thingy(info,buffer,"string")!=NULL ) {
ret = calloc(2,sizeof(char *));
ret[0] = copy(buffer);
fclose(info);
return( ret );
}
fclose(info);
return( NULL );
}
}
fclose(info);
return( NULL );
}
#include <libxml/parser.h>
static int libxml_init_base() {
return( true );
}
static xmlNodePtr FindNode(xmlNodePtr kids,char *name) {
while ( kids!=NULL ) {
if ( xmlStrcmp(kids->name,(const xmlChar *) name)== 0 )
return( kids );
kids = kids->next;
}
return( NULL );
}
#ifndef _NO_PYTHON
static PyObject *XMLEntryToPython(xmlDocPtr doc, xmlNodePtr entry, int has_lists);
static PyObject *LibToPython(xmlDocPtr doc, xmlNodePtr dict, int has_lists) {
// This function is responsible for parsing keys in dicts.
PyObject *pydict = PyDict_New();
PyObject *item = NULL;
xmlNodePtr keys, temp;
// Get the first item, then iterate through all items in the dict.
for ( keys=dict->children; keys!=NULL; keys=keys->next ) {
// See that the item is in fact a key.
if ( xmlStrcmp(keys->name,(const xmlChar *) "key")== 0 ) {
// Fetch the key name, which, according to the libxml specification, is the first child of the key entry.
char *keyname = (char *) xmlNodeListGetString(doc,keys->children,true);
// In a property list, the value entry is a sibling of the key entry. The value itself is a child.
// Iterate through the following siblings (including keys (!)) until we find a text entry.
for ( temp=keys->next; temp!=NULL; temp=temp->next ) {
if ( xmlStrcmp(temp->name,(const xmlChar *) "text")!=0 ) break;
}
// Convert the X.M.L. entry into a Python object.
item = NULL;
if ( temp!=NULL) item = XMLEntryToPython(doc,temp,has_lists);
if ( item!=NULL ) PyDict_SetItemString(pydict, keyname, item );
if ( temp==NULL ) break;
else if ( xmlStrcmp(temp->name,(const xmlChar *) "key")!=0 ) keys = temp;
// If and only if the parsing succeeds, jump over any entries we read when searching for a text block.
free(keyname);
}
}
return( pydict );
}
static PyObject *XMLEntryToPython(xmlDocPtr doc,xmlNodePtr entry, int has_lists) {
char *contents;
if ( xmlStrcmp(entry->name,(const xmlChar *) "true")==0 ) {
Py_INCREF(Py_True);
return( Py_True );
}
if ( xmlStrcmp(entry->name,(const xmlChar *) "false")==0 ) {
Py_INCREF(Py_False);
return( Py_False );
}
if ( xmlStrcmp(entry->name,(const xmlChar *) "none")==0 ) {
Py_INCREF(Py_None);
return( Py_None );
}
if ( xmlStrcmp(entry->name,(const xmlChar *) "dict")==0 )
return( LibToPython(doc,entry, has_lists));
if ( xmlStrcmp(entry->name,(const xmlChar *) "array")==0 ) {
xmlNodePtr sub;
int cnt;
PyObject *ret, *item;
/* I'm translating "Arrays" as tuples... not sure how to deal with */
/* actual python arrays. But these look more like tuples than arrays*/
/* since each item gets its own type */
for ( cnt=0, sub=entry->children; sub!=NULL; sub=sub->next ) {
if ( xmlStrcmp(sub->name,(const xmlChar *) "text")==0 )
continue;
++cnt;
}
ret = ( has_lists ? PyList_New(cnt) : PyTuple_New(cnt) );
for ( cnt=0, sub=entry->children; sub!=NULL; sub=sub->next ) {
if ( xmlStrcmp(sub->name,(const xmlChar *) "text")==0 )
continue;
item = XMLEntryToPython(doc,sub,has_lists);
if ( item==NULL ) {
item = Py_None;
Py_INCREF(item);
}
if (has_lists) PyList_SetItem(ret,cnt,item);
else PyTuple_SetItem(ret,cnt,item);
++cnt;
}
return( ret );
}
if ((entry->children != NULL) && ((contents = (char *) xmlNodeListGetString(doc,entry->children,true)) != NULL) &&
(( xmlStrcmp(entry->name,(const xmlChar *) "integer")==0 ) || ( xmlStrcmp(entry->name,(const xmlChar *) "real")==0 ) ||
( xmlStrcmp(entry->name,(const xmlChar *) "string")==0 ))) {
contents = (char *) xmlNodeListGetString(doc,entry->children,true);
if ( xmlStrcmp(entry->name,(const xmlChar *) "integer")==0 ) {
long val = strtol(contents,NULL,0);
free(contents);
return( Py_BuildValue("i",val));
}
if ( xmlStrcmp(entry->name,(const xmlChar *) "real")==0 ) {
double val = strtod(contents,NULL);
free(contents);
return( Py_BuildValue("d",val));
}
if ( xmlStrcmp(entry->name,(const xmlChar *) "string")==0 ) {
PyObject *ret = Py_BuildValue("s",contents);
free(contents);
return( ret );
}
free( contents );
}
if (has_lists) {
// This special handler for unrecognized content depends
// on the fact that there's no X. M. L. equivalent for the Python tuple.
xmlBufferPtr buf = xmlBufferCreate(); // Create a buffer for dumping this portion of the tree.
xmlNodeDump(buf, doc, entry, 0, 0); // Dump the tree into the buffer.
const xmlChar* tmpcontent = xmlBufferContent(buf); // Get the string from the buffer.
PyObject* ret = PyTuple_New(3); // Make a tuple in the Python tree.
PyTuple_SetItem(ret, 0, PyBytes_FromString((const char*)entry->name)); // Store the node name.
PyTuple_SetItem(ret, 1, PyBytes_FromString((const char*)tmpcontent)); // Store the node content.
PyTuple_SetItem(ret, 2, Py_None);
Py_INCREF(Py_None);
xmlBufferFree(buf);
return ret;
}
LogError(_("Unknown python type <%s> when reading UFO/GLIF lib data."), (char *) entry->name);
return( NULL );
}
#endif
static StemInfo *GlifParseHints(xmlDocPtr doc,xmlNodePtr dict,char *hinttype) {
StemInfo *head=NULL, *last=NULL, *h;
xmlNodePtr keys, array, kids, poswidth,temp;
double pos, width;
for ( keys=dict->children; keys!=NULL; keys=keys->next ) {
if ( xmlStrcmp(keys->name,(const xmlChar *) "key")== 0 ) {
char *keyname = (char *) xmlNodeListGetString(doc,keys->children,true);
int found = strcmp(keyname,hinttype)==0;
free(keyname);
if ( found ) {
for ( array=keys->next; array!=NULL; array=array->next ) {
if ( xmlStrcmp(array->name,(const xmlChar *) "array")==0 )
break;
}
if ( array!=NULL ) {
for ( kids = array->children; kids!=NULL; kids=kids->next ) {
if ( xmlStrcmp(kids->name,(const xmlChar *) "dict")==0 ) {
pos = -88888888; width = 0;
for ( poswidth=kids->children; poswidth!=NULL; poswidth=poswidth->next ) {
if ( xmlStrcmp(poswidth->name,(const xmlChar *) "key")==0 ) {
char *keyname2 = (char *) xmlNodeListGetString(doc,poswidth->children,true);
int ispos = strcmp(keyname2,"position")==0, iswidth = strcmp(keyname2,"width")==0;
double value;
free(keyname2);
for ( temp=poswidth->next; temp!=NULL; temp=temp->next ) {
if ( xmlStrcmp(temp->name,(const xmlChar *) "text")!=0 )
break;
}
if ( temp!=NULL ) {
char *valname = (char *) xmlNodeListGetString(doc,temp->children,true);
if ( xmlStrcmp(temp->name,(const xmlChar *) "integer")==0 )
value = strtol(valname,NULL,10);
else if ( xmlStrcmp(temp->name,(const xmlChar *) "real")==0 )
value = strtod(valname,NULL);
else
ispos = iswidth = false;
free(valname);
if ( ispos )
pos = value;
else if ( iswidth )
width = value;
poswidth = temp;
}
}
}
if ( pos!=-88888888 && width!=0 ) {
h = chunkalloc(sizeof(StemInfo));
h->start = pos;
h->width = width;
if ( width==-20 || width==-21 )
h->ghost = true;
if ( head==NULL )
head = last = h;
else {
last->next = h;
last = h;
}
}
}
}
}
}
}
}
return( head );
}
static SplineChar *_UFOLoadGlyph(SplineFont *sf, xmlDocPtr doc, char *glifname, char* glyphname, SplineChar* existingglyph, int layerdest) {
xmlNodePtr glyph, kids, contour, points;
SplineChar *sc;
xmlChar *format, *width, *height, *u;
char *name, *tmpname;
int uni;
char *cpt;
int newsc = 0;
glyph = xmlDocGetRootElement(doc);
format = xmlGetProp(glyph,(xmlChar *) "format");
if ( xmlStrcmp(glyph->name,(const xmlChar *) "glyph")!=0 ||
(format!=NULL && xmlStrcmp(format,(xmlChar *) "1")!=0 && xmlStrcmp(format,(xmlChar *) "2")!=0)) {
LogError(_("Expected glyph file with format==1 or 2"));
xmlFreeDoc(doc);
free(format);
return( NULL );
}
free(format);
tmpname = (char *) xmlGetProp(glyph,(xmlChar *) "name");
if (glyphname != NULL) {
// We use the provided name from the glyph listing since the specification says to trust that one more.
name = copy(glyphname);
// But we still fetch the internally listed name for verification and fail on a mismatch.
if ((name == NULL) || ((name != NULL) && (tmpname != NULL) && (strcmp(glyphname, name) != 0))) {
LogError(_("Bad glyph name."));
if ( tmpname != NULL ) { free(tmpname); tmpname = NULL; }
if ( name != NULL ) { free(name); name = NULL; }
xmlFreeDoc(doc);
return NULL;
}
if ( tmpname != NULL ) { free(tmpname); tmpname = NULL; }
} else {
name = tmpname;
}
if ( name==NULL && glifname!=NULL ) {
char *pt = strrchr(glifname,'/');
name = copy(pt+1);
for ( pt=cpt=name; *cpt!='\0'; ++cpt ) {
if ( *cpt!='_' )
*pt++ = *cpt;
else if ( islower(*name))
*name = toupper(*name);
}
*pt = '\0';
} else if ( name==NULL )
name = copy("nameless");
// We assign a placeholder name if no name exists.
// We create a new SplineChar
if (existingglyph != NULL) {
sc = existingglyph;
free(name); name = NULL;
} else {
sc = SplineCharCreate(2);
sc->name = name;
newsc = 1;
}
if (sc == NULL) {
xmlFreeDoc(doc);
return NULL;
}
// Check layer availability here.
if ( layerdest>=sc->layer_cnt ) {
sc->layers = realloc(sc->layers,(layerdest+1)*sizeof(Layer));
memset(sc->layers+sc->layer_cnt,0,(layerdest+1-sc->layer_cnt)*sizeof(Layer));
sc->layer_cnt = layerdest + 1;
}
if (sc->layers == NULL) {
if ((newsc == 1) && (sc != NULL)) {
SplineCharFree(sc);
}
xmlFreeDoc(doc);
return NULL;
}
// We track the last splineset so that we can add to the end of the chain.
SplineSet *last = sc->layers[layerdest].splines;
while (last != NULL && last->next != NULL) last = last->next;
// We track the last anchor point.
AnchorPoint *lastap = sc->anchor;
while (lastap != NULL && lastap->next != NULL) lastap = lastap->next;
// We track the last reference.
RefChar *lastref = sc->layers[layerdest].refs;
while (lastref != NULL && lastref->next != NULL) lastref = lastref->next;
for ( kids = glyph->children; kids!=NULL; kids=kids->next ) {
if ( xmlStrcmp(kids->name,(const xmlChar *) "advance")==0 ) {
if ((layerdest == ly_fore) || newsc) {
width = xmlGetProp(kids,(xmlChar *) "width");
height = xmlGetProp(kids,(xmlChar *) "height");
if ( width!=NULL )
sc->width = strtol((char *) width,NULL,10);
if ( height!=NULL )
sc->vwidth = strtol((char *) height,NULL,10);
sc->widthset = true;
free(width); free(height);
}
} else if ( xmlStrcmp(kids->name,(const xmlChar *) "unicode")==0 ) {
if ((layerdest == ly_fore) || newsc) {
u = xmlGetProp(kids,(xmlChar *) "hex");
uni = strtol((char *) u,NULL,16);
if ( sc->unicodeenc == -1 )
sc->unicodeenc = uni;
else
AltUniAdd(sc,uni);
free(u);
}
} else if ( xmlStrcmp(kids->name,(const xmlChar *) "outline")==0 ) {
for ( contour = kids->children; contour!=NULL; contour=contour->next ) {
if ( xmlStrcmp(contour->name,(const xmlChar *) "component")==0 ) {
// We have a reference.
char *base = (char *) xmlGetProp(contour,(xmlChar *) "base"),
*xs = (char *) xmlGetProp(contour,(xmlChar *) "xScale"),
*ys = (char *) xmlGetProp(contour,(xmlChar *) "yScale"),
*xys = (char *) xmlGetProp(contour,(xmlChar *) "xyScale"),
*yxs = (char *) xmlGetProp(contour,(xmlChar *) "yxScale"),
*xo = (char *) xmlGetProp(contour,(xmlChar *) "xOffset"),
*yo = (char *) xmlGetProp(contour,(xmlChar *) "yOffset");
RefChar *r;
if ( base==NULL || strcmp(base,"") == 0 )
LogError(_("component with no base glyph"));
else {
r = RefCharCreate();
r->sc = SplineCharCreate(0);
r->sc->name = base;
r->transform[0] = r->transform[3] = 1;
if ( xs!=NULL )
r->transform[0] = strtod(xs,NULL);
if ( ys!=NULL )
r->transform[3] = strtod(ys,NULL);
if ( xys!=NULL )
r->transform[1] = strtod(xys,NULL);
if ( yxs!=NULL )
r->transform[2] = strtod(yxs,NULL);
if ( xo!=NULL )
r->transform[4] = strtod(xo,NULL);
if ( yo!=NULL )
r->transform[5] = strtod(yo,NULL);
if ( lastref==NULL ) {
// If there are no existing references, we point the main spline reference to this one.
sc->layers[layerdest].refs = r;
} else {
// If there are existing references, we attach to the last one.
lastref->next = r;
}
lastref = r;
}
if (xs) free(xs); if (ys) free(ys); if (xys) free(xys); if (yxs) free(yxs); if (xo) free(xo); if (yo) free(yo);
} else if ( xmlStrcmp(contour->name,(const xmlChar *) "contour")==0 ) {
xmlNodePtr npoints;
// We now look for anchor points.
char *sname;
for ( points=contour->children; points!=NULL; points=points->next )
if ( xmlStrcmp(points->name,(const xmlChar *) "point")==0 )
break;
for ( npoints=points->next; npoints!=NULL; npoints=npoints->next )
if ( xmlStrcmp(npoints->name,(const xmlChar *) "point")==0 )
break;
// If the contour has a single point without another point after it, we assume it to be an anchor point.
if ( points!=NULL && npoints==NULL ) {
sname = (char *) xmlGetProp(points, (xmlChar *) "name");
if ( sname!=NULL) {
/* make an AP and if necessary an AC */
AnchorPoint *ap = chunkalloc(sizeof(AnchorPoint));
AnchorClass *ac;
char *namep = *sname=='_' ? sname + 1 : sname;
char *xs = (char *) xmlGetProp(points, (xmlChar *) "x");
char *ys = (char *) xmlGetProp(points, (xmlChar *) "y");
if (xs) { ap->me.x = strtod(xs,NULL); free(xs); }
if (ys) { ap->me.y = strtod(ys,NULL); free(ys); }
ac = SFFindOrAddAnchorClass(sf,namep,NULL);
if (*sname=='_')
ap->type = ac->type==act_curs ? at_centry : at_mark;
else
ap->type = ac->type==act_mkmk ? at_basemark :
ac->type==act_curs ? at_cexit :
ac->type==act_mklg ? at_baselig :
at_basechar;
ap->anchor = ac;
if ( lastap==NULL ) {
// If there are no existing anchors, we point the main spline reference to this one.
sc->anchor = ap;
} else {
// If there are existing anchors, we attach to the last one.
lastap->next = ap;
}
lastap = ap;
free(sname);
continue; // We stop processing the contour at this point.
}
}
// If we have not identified the contour as holding an anchor point, we continue processing it as a rendered shape.
SplineSet *ss;
SplinePoint *sp;
SplinePoint *sp2;
BasePoint pre[2], init[4];
int precnt=0, initcnt=0, open=0;
// precnt seems to count control points leading into the next on-curve point. pre stores those points.
// initcnt counts the control points that appear before the first on-curve point. This can get updated at the beginning and/or the end of the list.
// This is important for determining the order of the closing curve.
// A further improvement would be to prefetch the entire list so as to know the declared order of a curve before processing the point.
int wasquad = -1; // This tracks whether we identified the previous curve as quadratic. (-1 means undefined.)
int firstpointsaidquad = -1; // This tracks the declared order of the curve leading into the first on-curve point.
ss = chunkalloc(sizeof(SplineSet));
ss->first = NULL;
for ( points = contour->children; points!=NULL; points=points->next ) {
char *xs, *ys, *type, *pname, *smooths;
double x,y;
int smooth = 0;
// We discard any entities in the splineset that are not points.
if ( xmlStrcmp(points->name,(const xmlChar *) "point")!=0 )
continue;
// Read as strings from xml.
xs = (char *) xmlGetProp(points,(xmlChar *) "x");
ys = (char *) xmlGetProp(points,(xmlChar *) "y");
type = (char *) xmlGetProp(points,(xmlChar *) "type");
pname = (char *) xmlGetProp(points,(xmlChar *) "name");
smooths = (char *) xmlGetProp(points,(xmlChar *) "smooth");
if (smooths != NULL) {
if (strcmp(smooths,"yes") == 0) smooth = 1;
free(smooths); smooths=NULL;
}
if ( xs==NULL || ys == NULL ) {
if (xs != NULL) { free(xs); xs = NULL; }
if (ys != NULL) { free(ys); ys = NULL; }
if (type != NULL) { free(type); type = NULL; }
if (pname != NULL) { free(pname); pname = NULL; }
continue;
}
x = strtod(xs,NULL); y = strtod(ys,NULL);
if ( type!=NULL && (strcmp(type,"move")==0 ||
strcmp(type,"line")==0 ||
strcmp(type,"curve")==0 ||
strcmp(type,"qcurve")==0 )) {
// This handles only actual points.
// We create and label the point.
sp = SplinePointCreate(x,y);
sp->dontinterpolate = 1;
if (pname != NULL) {
sp->name = copy(pname);
}
if (smooth == 1) sp->pointtype = pt_curve;
else sp->pointtype = pt_corner;
if ( ss->first==NULL ) {
// So this is the first real point!
ss->first = ss->last = sp;
// We move the lead-in points to the init buffer as we may need them for the final curve.
memcpy(init,pre,sizeof(pre));
initcnt = precnt;
if ( strcmp(type,"move")==0 ) {
open = true;
if (initcnt != 0) LogError(_("We cannot have lead-in points for an open curve.\n"));
}
}
if ( strcmp(type,"move")==0 ) {
if (ss->first != sp) {
LogError(_("The move point must be at the beginning of the contour.\n"));
SplinePointFree(sp); sp = NULL;
}
} else if ( strcmp(type,"line")==0 ) {
SplineMake(ss->last,sp,false);
ss->last = sp;
} else if ( strcmp(type,"curve")==0 ) {
wasquad = false;
if (ss->first == sp) {
firstpointsaidquad = false;
}
if ( precnt==2 && ss->first != sp ) {
ss->last->nextcp = pre[0];
ss->last->nonextcp = false;
sp->prevcp = pre[1];
sp->noprevcp = false;
SplineMake(ss->last,sp,false);
}
ss->last = sp;
} else if ( strcmp(type,"qcurve")==0 ) {
wasquad = true;
if (ss->first == sp) {
firstpointsaidquad = true;
}
if ( precnt>0 && precnt<=2 ) {
if ( precnt==2 ) {
// If we have two cached control points and the end point is quadratic, we need an implied point between the two control points.
sp2 = SplinePointCreate((pre[1].x+pre[0].x)/2,(pre[1].y+pre[0].y)/2);
sp2->prevcp = ss->last->nextcp = pre[0];
sp2->noprevcp = ss->last->nonextcp = false;
sp2->ttfindex = 0xffff;
SplineMake(ss->last,sp2,true);
ss->last = sp2;
}
// Now we connect the real point.
sp->prevcp = ss->last->nextcp = pre[precnt-1];
sp->noprevcp = ss->last->nonextcp = false;
}
SplineMake(ss->last,sp,true);
ss->last = sp;
} else {
SplinePointFree(sp); sp = NULL;
}
precnt = 0;
} else {
// This handles off-curve points (control points).
if ((wasquad == true || wasquad==-1) && precnt==2 ) {
// We don't know whether the current curve is quadratic or cubic, but, if we're hitting three off-curve points in a row, something is off.
// As mentioned below, we assume in this case that we're dealing with a quadratic TrueType curve that needs implied points.
// We create those points since they are adjustable in Fontforge.
// There is not a valid case as far as Frank knows in which a cubic curve would have implied points.
/* Undocumented fact: If there are no on-curve points (and therefore no indication of quadratic/cubic), assume truetype implied points */
// We make the point between the two already cached control points.
sp = SplinePointCreate((pre[1].x+pre[0].x)/2,(pre[1].y+pre[0].y)/2);
sp->ttfindex = 0xffff;
if (pname != NULL) {
sp->name = copy(pname);
}
sp->nextcp = pre[1];
sp->nonextcp = false;
if ( ss->first==NULL ) {
// This is indeed possible if the first three points are control points.
ss->first = sp;
memcpy(init,pre,sizeof(pre));
initcnt = 1;
} else {
ss->last->nextcp = sp->prevcp = pre[0];
ss->last->nonextcp = sp->noprevcp = false;
initcnt = 0;
SplineMake(ss->last,sp,true);
}
ss->last = sp;
#if 1
// We make the point between the previously cached control point and the new control point.
// We have decided that the curve is quadratic, so we can make the next implied point as well.
sp = SplinePointCreate((x+pre[1].x)/2,(y+pre[1].y)/2);
sp->prevcp = pre[1];
sp->noprevcp = false;
sp->ttfindex = 0xffff;
SplineMake(ss->last,sp,true);
ss->last = sp;
pre[0].x = x; pre[0].y = y;
precnt = 1;
#else
// Let us instead save the second implied point for later.
pre[0].x = pre[1].x; pre[0].y = pre[1].y;
pre[1].x = x; pre[1].y = y;
precnt = 2;
#endif
wasquad = true;
} else if ( wasquad==true && precnt==1) {
// Frank thinks that this might generate false positives for qcurves.
// This seems not to be the best way to handle it, but mixed-order spline sets are rare.
sp = SplinePointCreate((x+pre[0].x)/2,(y+pre[0].y)/2);
if (pname != NULL) {
sp->name = copy(pname);
}
sp->prevcp = pre[0];
sp->noprevcp = false;
sp->ttfindex = 0xffff;
if ( ss->first==NULL ) {
ss->first = sp;
memcpy(init,pre,sizeof(pre));
initcnt = 1;
} else {
ss->last->nextcp = sp->prevcp;
ss->last->nonextcp = false;
SplineMake(ss->last,sp,true);
}
ss->last = sp;
pre[0].x = x; pre[0].y = y;
} else if ( precnt<2 ) {
pre[precnt].x = x;
pre[precnt].y = y;
++precnt;
}
}
if (xs != NULL) { free(xs); xs = NULL; }
if (ys != NULL) { free(ys); ys = NULL; }
if (type != NULL) { free(type); type = NULL; }
if (pname != NULL) { free(pname); pname = NULL; }
}
// We are finished looping, so it's time to close the curve if it is to be closed.
if ( !open && ss->first != NULL ) {
ss->start_offset = -initcnt;
// init has a list of control points leading into the first point. pre has a list of control points trailing the last processed on-curve point.
// We merge pre into init and use init as the list of control points between the last processed on-curve point and the first on-curve point.
if ( precnt!=0 ) {
BasePoint temp[2];
memcpy(temp,init,sizeof(temp));
memcpy(init,pre,sizeof(pre));
memcpy(init+precnt,temp,sizeof(temp));
initcnt += precnt;
}
if ( ((firstpointsaidquad==true || (firstpointsaidquad == -1 && wasquad == true)) && initcnt>0) || initcnt==1 ) {
// If the final curve is declared quadratic or is assumed to be by control point count, we proceed accordingly.
int i;
for ( i=0; i<initcnt-1; ++i ) {
// If the final curve is declared quadratic but has more than one control point, we add implied points.
sp = SplinePointCreate((init[i+1].x+init[i].x)/2,(init[i+1].y+init[i].y)/2);
sp->prevcp = ss->last->nextcp = init[i];
sp->noprevcp = ss->last->nonextcp = false;
sp->ttfindex = 0xffff;
SplineMake(ss->last,sp,true);
ss->last = sp;
}
ss->last->nextcp = ss->first->prevcp = init[initcnt-1];
ss->last->nonextcp = ss->first->noprevcp = false;
wasquad = true;
} else if ( initcnt==2 ) {
ss->last->nextcp = init[0];
ss->first->prevcp = init[1];
ss->last->nonextcp = ss->first->noprevcp = false;
wasquad = false;
}
SplineMake(ss->last, ss->first, (firstpointsaidquad==true || (firstpointsaidquad == -1 && wasquad == true)));
ss->last = ss->first;
}
if (ss->first == NULL) {
LogError(_("This spline set has no points.\n"));
SplinePointListFree(ss); ss = NULL;
} else {
if ( last==NULL ) {
// If there are no existing spline sets, we point the main spline reference to this set.
sc->layers[layerdest].splines = ss;
} else {
// If there are existing spline sets, we attach to the last one.
last->next = ss;
}
last = ss;
}
}
}
} else if ( xmlStrcmp(kids->name,(const xmlChar *) "lib")==0 ) {
xmlNodePtr keys, temp, dict = FindNode(kids->children,"dict");
if ( dict!=NULL ) {
for ( keys=dict->children; keys!=NULL; keys=keys->next ) {
if ( xmlStrcmp(keys->name,(const xmlChar *) "key")== 0 ) {
char *keyname = (char *) xmlNodeListGetString(doc,keys->children,true);
if ( strcmp(keyname,"com.fontlab.hintData")==0 ) {
for ( temp=keys->next; temp!=NULL; temp=temp->next ) {
if ( xmlStrcmp(temp->name,(const xmlChar *) "dict")==0 )
break;
}
if ( temp!=NULL ) {
if (layerdest == ly_fore) {
if (sc->hstem == NULL) {
sc->hstem = GlifParseHints(doc,temp,"hhints");
SCGuessHHintInstancesList(sc,ly_fore);
}
if (sc->vstem == NULL) {
sc->vstem = GlifParseHints(doc,temp,"vhints");
SCGuessVHintInstancesList(sc,ly_fore);
}
}
}
break;
}
free(keyname);
}
}
#ifndef _NO_PYTHON
if (sc->layers[layerdest].python_persistent == NULL) {
sc->layers[layerdest].python_persistent = LibToPython(doc,dict,1);
sc->layers[layerdest].python_persistent_has_lists = 1;
} else LogError(_("Duplicate lib data.\n"));
#endif
}
}
}
xmlFreeDoc(doc);
SPLCategorizePointsKeepCorners(sc->layers[layerdest].splines);
return( sc );
}
static SplineChar *UFOLoadGlyph(SplineFont *sf,char *glifname, char* glyphname, SplineChar* existingglyph, int layerdest) {
xmlDocPtr doc;
doc = xmlParseFile(glifname);
if ( doc==NULL ) {
LogError(_("Bad glif file %s"), glifname);
return( NULL );
}
return( _UFOLoadGlyph(sf,doc,glifname,glyphname,existingglyph,layerdest));
}
static void UFORefFixup(SplineFont *sf, SplineChar *sc, int layer ) {
RefChar *r, *prev;
SplineChar *rsc;
if ( sc==NULL || sc->ticked )
return;
sc->ticked = true;
prev = NULL;
// For each reference, attempt to locate the real splinechar matching the name stored in the fake splinechar.
// Free the fake splinechar afterwards.
r=sc->layers[layer].refs;
while ( r!=NULL ) {
if (r->sc->name == NULL || strcmp(r->sc->name, "") == 0) {
LogError(_("There's a reference to a glyph with no name."));
prev = r; r = r->next; continue;
}
if (r->sc->ticked) {
// We've already fixed this one.
prev = r; r = r->next; continue;
}
rsc = SFGetChar(sf,-1, r->sc->name);
if ( rsc==NULL || rsc->name == NULL || strcmp(rsc->name,"") == 0 ) {
if (rsc != NULL) {
LogError(_("Invalid glyph for %s when fixing up references."), r->sc->name);
} else
LogError(_("Failed to find glyph %s when fixing up references."), r->sc->name);
SplineCharFree(r->sc); // Delete the fake glyph.
r->sc = NULL;
// Elide r from the list and free it.
if ( prev==NULL ) sc->layers[layer].refs = r->next;
else prev->next = r->next;
RefCharFree(r);
if ( prev==NULL ) r = sc->layers[layer].refs;
else r = prev->next;
} else {
UFORefFixup(sf,rsc, layer);
if (r->sc->layer_cnt > 0) {
fprintf(stderr, "Danger!\n");
}
SplineCharFree(r->sc);
r->sc = rsc;
SCReinstanciateRefChar(sc,r,layer);
prev = r; r = r->next;
}
}
}
static void UFOLoadGlyphs(SplineFont *sf,char *glyphdir, int layerdest) {
char *glyphlist = buildname(glyphdir,"contents.plist");
xmlDocPtr doc;
xmlNodePtr plist, dict, keys, value;
char *valname, *glyphfname;
int i;
SplineChar *sc;
int tot;
doc = xmlParseFile(glyphlist);
free(glyphlist);
if ( doc==NULL ) {
LogError(_("Bad contents.plist"));
return;
}
plist = xmlDocGetRootElement(doc);
dict = FindNode(plist->children,"dict");
if ( xmlStrcmp(plist->name,(const xmlChar *) "plist")!=0 || dict == NULL ) {
LogError(_("Expected property list file"));
xmlFreeDoc(doc);
return;
}
// Count glyphs for the benefit of measuring progress.
for ( tot=0, keys=dict->children; keys!=NULL; keys=keys->next ) {
if ( xmlStrcmp(keys->name,(const xmlChar *) "key")==0 )
++tot;
}
ff_progress_change_total(tot);
// Start reading in glyph name to file name mappings.
for ( keys=dict->children; keys!=NULL; keys=keys->next ) {
for ( value = keys->next; value!=NULL && xmlStrcmp(value->name,(const xmlChar *) "text")==0;
value = value->next );
if ( value==NULL )
break;
if ( xmlStrcmp(keys->name,(const xmlChar *) "key")==0 ) {
char * glyphname = (char *) xmlNodeListGetString(doc,keys->children,true);
int newsc = 0;
SplineChar* existingglyph = NULL;
if (glyphname != NULL) {
existingglyph = SFGetChar(sf,-1,glyphname);
if (existingglyph == NULL) newsc = 1;
valname = (char *) xmlNodeListGetString(doc,value->children,true);
glyphfname = buildname(glyphdir,valname);
sc = UFOLoadGlyph(sf, glyphfname, glyphname, existingglyph, layerdest);
// We want to stash the glif name (minus the extension) for future use.
if (sc != NULL && sc->glif_name == NULL && valname != NULL) {
char * tmppos = strrchr(valname, '.'); if (tmppos) *tmppos = '\0';
sc->glif_name = copy(valname);
if (tmppos) *tmppos = '.';
}
free(valname);
if ( ( sc!=NULL ) && newsc ) {
sc->parent = sf;
if ( sf->glyphcnt>=sf->glyphmax )
sf->glyphs = realloc(sf->glyphs,(sf->glyphmax+=100)*sizeof(SplineChar *));
sc->orig_pos = sf->glyphcnt;
sf->glyphs[sf->glyphcnt++] = sc;
}
}
keys = value;
ff_progress_next();
}
}
xmlFreeDoc(doc);
GlyphHashFree(sf);
for ( i=0; i<sf->glyphcnt; ++i )
UFORefFixup(sf,sf->glyphs[i], layerdest);
}
static struct ff_glyphclasses *GlyphGroupDeduplicate(struct ff_glyphclasses *group_base, struct splinefont *sf, int check_kerns) {
// This removes internal duplicates from the specified group and also, if desired, the groups duplicating entities already named as kerning classes.
// It takes the list head as its argument and returns the new list head (which may be the same unless the first item duplicates a kerning class).
int temp_index = 0;
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _group_name_hash;
struct glif_name_index * group_name_hash = &_group_name_hash; // Open the group hash table.
memset(group_name_hash, 0, sizeof(struct glif_name_index));
struct glif_name_index _class_name_hash;
struct glif_name_index * class_name_hash = &_class_name_hash; // Open the class hash table.
memset(class_name_hash, 0, sizeof(struct glif_name_index));
if (check_kerns && sf) HashKerningClassNames(sf, class_name_hash);
struct ff_glyphclasses *group_current = group_base;
struct ff_glyphclasses *group_prev = NULL;
while (group_current != NULL) {
if (group_current->classname == NULL || group_current->classname[0] == '\0' ||
glif_name_search_glif_name(group_name_hash, group_current->classname) ||
(check_kerns && sf && glif_name_search_glif_name(class_name_hash, group_current->classname))) {
if (group_prev != NULL) group_prev->next = group_current->next;
else group_base = group_current->next;
GlyphGroupFree(group_current);
if (group_prev != NULL) group_current = group_prev->next;
else group_current = group_base;
} else {
glif_name_track_new(group_name_hash, temp_index++, group_current->classname);
group_prev = group_current; group_current = group_current->next;
}
}
glif_name_hash_destroy(class_name_hash);
glif_name_hash_destroy(group_name_hash);
#endif
return group_base;
}
static uint32 script_from_glyph_list(SplineFont *sf, const char *glyph_names) {
uint32 script = DEFAULT_SCRIPT;
char *delimited_names;
off_t name_char_pos;
name_char_pos = 0;
delimited_names = delimit_null(glyph_names, ' ');
while (script == DEFAULT_SCRIPT && glyph_names[name_char_pos] != '\0') {
SplineChar *sc = SFGetChar(sf, -1, delimited_names + name_char_pos);
script = SCScriptFromUnicode(sc);
name_char_pos += strlen(delimited_names + name_char_pos);
if (glyph_names[name_char_pos] != '\0') name_char_pos ++;
}
free(delimited_names); delimited_names = NULL;
return script;
}
#define GROUP_NAME_KERNING_UFO 1
#define GROUP_NAME_KERNING_FEATURE 2
#define GROUP_NAME_VERTICAL 4 // Otherwise horizontal.
#define GROUP_NAME_RIGHT 8 // Otherwise left (or above).
static void MakeKerningClasses(SplineFont *sf, struct ff_glyphclasses *group_base) {
// This silently ignores already extant groups for now but avoids duplicates unless group_base has internal duplication.
int left_count = 0, right_count = 0, above_count = 0, below_count = 0;
int left_start = 0, right_start = 0, above_start = 0, below_start = 0;
#ifdef FF_UTHASH_GLIF_NAMES
struct glif_name_index _class_name_hash;
struct glif_name_index * class_name_hash = &_class_name_hash; // Open the hash table.
memset(class_name_hash, 0, sizeof(struct glif_name_index));
HashKerningClassNames(sf, class_name_hash);
#else
void * class_name_hash = NULL;
#endif
// It is very difficult to create speculative indices for the unmerged group members during the size calculation.
// So we expect that the incoming group list has no duplicates (as after a run through GlyphGroupDeduplicate).
struct ff_glyphclasses *current_group;
for (current_group = group_base; current_group != NULL; current_group = current_group->next) {
int group_type = GroupNameType(current_group->classname);
if ((group_type != -1) && (group_type & (GROUP_NAME_KERNING_UFO|GROUP_NAME_KERNING_FEATURE))) {
if (group_type & GROUP_NAME_VERTICAL) {
if (group_type & GROUP_NAME_RIGHT) {
below_count++;
} else {
above_count++;
}
} else {
if (group_type & GROUP_NAME_RIGHT) {
right_count++;
} else {
left_count++;
}
}
}
}
// Allocate lookups if needed.
if (sf->kerns == NULL && (left_count || right_count)) {
sf->kerns = calloc(1, sizeof(struct kernclass));
sf->kerns->subtable = SFSubTableFindOrMake(sf, CHR('k','e','r','n'), DEFAULT_SCRIPT, gpos_pair);
sf->kerns->firsts = calloc(1, sizeof(char *));
sf->kerns->firsts_names = calloc(1, sizeof(char *));
sf->kerns->firsts_flags = calloc(1, sizeof(int));
sf->kerns->seconds = calloc(1, sizeof(char *));
sf->kerns->seconds_names = calloc(1, sizeof(char *));
sf->kerns->seconds_flags = calloc(1, sizeof(int));
sf->kerns->offsets = calloc(1, sizeof(int16));
sf->kerns->offsets_flags = calloc(1, sizeof(int));
sf->kerns->first_cnt = 1;
sf->kerns->second_cnt = 1;
}
if (sf->vkerns == NULL && (above_count || below_count)) {
sf->vkerns = calloc(1, sizeof(struct kernclass));
sf->vkerns->subtable = SFSubTableFindOrMake(sf, CHR('v','k','r','n'), DEFAULT_SCRIPT, gpos_pair);
sf->vkerns->firsts = calloc(1, sizeof(char *));
sf->vkerns->firsts_names = calloc(1, sizeof(char *));
sf->vkerns->firsts_flags = calloc(1, sizeof(int));
sf->vkerns->seconds = calloc(1, sizeof(char *));
sf->vkerns->seconds_names = calloc(1, sizeof(char *));
sf->vkerns->seconds_flags = calloc(1, sizeof(int));
sf->vkerns->offsets = calloc(1, sizeof(int16));
sf->vkerns->offsets_flags = calloc(1, sizeof(int));
sf->vkerns->first_cnt = 1;
sf->vkerns->second_cnt = 1;
}
// Set starts.
if (sf->kerns != NULL) { left_start = sf->kerns->first_cnt; right_start = sf->kerns->second_cnt; }
if (sf->vkerns != NULL) { above_start = sf->vkerns->first_cnt; below_start = sf->vkerns->second_cnt; }
// Make space for the new entries.
// We start by allocating space for the offsets and offsets flags. We then copy the old contents, row-by-row.
if ((left_count > 0 || right_count > 0) && ((sf->kerns->first_cnt + left_count) * (sf->kerns->second_cnt + right_count) > 0)) {
// Offsets.
int16 *tmp_offsets = calloc((sf->kerns->first_cnt + left_count) * (sf->kerns->second_cnt + right_count), sizeof(int16));
if (sf->kerns->offsets) {
int rowpos;
for (rowpos = 0; rowpos < sf->kerns->first_cnt; rowpos ++) {
memcpy((void *)tmp_offsets + (rowpos * (sf->kerns->second_cnt + right_count)) * sizeof(int16), (void *)(sf->kerns->offsets) + (rowpos * sf->kerns->second_cnt) * sizeof(int16), sf->kerns->second_cnt * sizeof(int16));
}
free(sf->kerns->offsets);
}
sf->kerns->offsets = tmp_offsets;
// Offset flags.
int *tmp_offsets_flags = calloc((sf->kerns->first_cnt + left_count) * (sf->kerns->second_cnt + right_count), sizeof(int));
if (sf->kerns->offsets_flags) {
int rowpos;
for (rowpos = 0; rowpos < sf->kerns->first_cnt; rowpos ++) {
memcpy((void *)tmp_offsets_flags + (rowpos * (sf->kerns->second_cnt + right_count)) * sizeof(int), (void *)(sf->kerns->offsets_flags) + (rowpos * sf->kerns->second_cnt) * sizeof(int), sf->kerns->second_cnt * sizeof(int));
}
free(sf->kerns->offsets_flags);
}
sf->kerns->offsets_flags = tmp_offsets_flags;
// Adjusts.
DeviceTable *tmp_adjusts = calloc((sf->kerns->first_cnt + left_count) * (sf->kerns->second_cnt + right_count), sizeof(DeviceTable));
if (sf->kerns->adjusts) {
int rowpos;
for (rowpos = 0; rowpos < sf->kerns->first_cnt; rowpos ++) {
memcpy((void *)tmp_adjusts + (rowpos * (sf->kerns->second_cnt + right_count)) * sizeof(DeviceTable), (void *)(sf->kerns->adjusts) + (rowpos * sf->kerns->second_cnt) * sizeof(DeviceTable), sf->kerns->second_cnt * sizeof(DeviceTable));
}
free(sf->kerns->adjusts);
}
sf->kerns->adjusts = tmp_adjusts;
}
if ((above_count > 0 || below_count > 0) && ((sf->vkerns->first_cnt + above_count) * (sf->vkerns->second_cnt + below_count) > 0)) {
// Offsets.
int16 *tmp_offsets = calloc((sf->vkerns->first_cnt + above_count) * (sf->vkerns->second_cnt + below_count), sizeof(int16));
if (sf->vkerns->offsets) {
int rowpos;
for (rowpos = 0; rowpos < sf->vkerns->first_cnt; rowpos ++) {
memcpy((void *)tmp_offsets + (rowpos * (sf->vkerns->second_cnt + below_count)) * sizeof(int16), (void *)(sf->vkerns->offsets) + (rowpos * sf->vkerns->second_cnt) * sizeof(int16), sf->vkerns->second_cnt * sizeof(int16));
}
free(sf->vkerns->offsets);
}
sf->vkerns->offsets = tmp_offsets;
// Offset flags.
int *tmp_offsets_flags = calloc((sf->vkerns->first_cnt + above_count) * (sf->vkerns->second_cnt + below_count), sizeof(int));
if (sf->vkerns->offsets_flags) {
int rowpos;
for (rowpos = 0; rowpos < sf->vkerns->first_cnt; rowpos ++) {
memcpy((void *)tmp_offsets_flags + (rowpos * (sf->vkerns->second_cnt + below_count)) * sizeof(int), (void *)(sf->vkerns->offsets_flags) + (rowpos * sf->vkerns->second_cnt) * sizeof(int), sf->vkerns->second_cnt * sizeof(int));
}
free(sf->vkerns->offsets_flags);
}
sf->vkerns->offsets_flags = tmp_offsets_flags;
// Adjusts.
DeviceTable *tmp_adjusts = calloc((sf->vkerns->first_cnt + above_count) * (sf->vkerns->second_cnt + below_count), sizeof(DeviceTable));
if (sf->vkerns->adjusts) {
int rowpos;
for (rowpos = 0; rowpos < sf->vkerns->first_cnt; rowpos ++) {
memcpy((void *)tmp_adjusts + (rowpos * (sf->vkerns->second_cnt + above_count)) * sizeof(DeviceTable), (void *)(sf->vkerns->adjusts) + (rowpos * sf->vkerns->second_cnt) * sizeof(DeviceTable), sf->vkerns->second_cnt * sizeof(DeviceTable));
}
free(sf->vkerns->adjusts);
}
sf->vkerns->adjusts = tmp_adjusts;
}
// Since the linear data need no repositioning, we can just use realloc. But it's important that we zero the new space in case it does not get filled.
if (left_count > 0) {
sf->kerns->firsts = realloc(sf->kerns->firsts, sizeof(char *) * (sf->kerns->first_cnt + left_count));
memset((void*)sf->kerns->firsts + sf->kerns->first_cnt * sizeof(char *), 0, left_count * sizeof(char *));
sf->kerns->firsts_names = realloc(sf->kerns->firsts_names, sizeof(char *) * (sf->kerns->first_cnt + left_count));
memset((void*)sf->kerns->firsts_names + sf->kerns->first_cnt * sizeof(char *), 0, left_count * sizeof(char *));
sf->kerns->firsts_flags = realloc(sf->kerns->firsts_flags, sizeof(int) * (sf->kerns->first_cnt + left_count));
memset((void*)sf->kerns->firsts_flags + sf->kerns->first_cnt * sizeof(int), 0, left_count * sizeof(int));
sf->kerns->first_cnt += left_count;
}
if (right_count > 0) {
sf->kerns->seconds = realloc(sf->kerns->seconds, sizeof(char *) * (sf->kerns->second_cnt + right_count));
memset((void*)sf->kerns->seconds + sf->kerns->second_cnt * sizeof(char *), 0, right_count * sizeof(char *));
sf->kerns->seconds_names = realloc(sf->kerns->seconds_names, sizeof(char *) * (sf->kerns->second_cnt + right_count));
memset((void*)sf->kerns->seconds_names + sf->kerns->second_cnt * sizeof(char *), 0, right_count * sizeof(char *));
sf->kerns->seconds_flags = realloc(sf->kerns->seconds_flags, sizeof(int) * (sf->kerns->second_cnt + right_count));
memset((void*)sf->kerns->seconds_flags + sf->kerns->second_cnt * sizeof(int), 0, right_count * sizeof(int));
sf->kerns->second_cnt += right_count;
}
if (above_count > 0) {
sf->vkerns->firsts = realloc(sf->vkerns->firsts, sizeof(char *) * (sf->vkerns->first_cnt + above_count));
memset((void*)sf->vkerns->firsts + sf->vkerns->first_cnt * sizeof(char *), 0, above_count * sizeof(char *));
sf->vkerns->firsts_names = realloc(sf->vkerns->firsts_names, sizeof(char *) * (sf->vkerns->first_cnt + above_count));
memset((void*)sf->vkerns->firsts_names + sf->vkerns->first_cnt * sizeof(char *), 0, above_count * sizeof(char *));
sf->vkerns->firsts_flags = realloc(sf->vkerns->firsts_flags, sizeof(int) * (sf->vkerns->first_cnt + above_count));
memset((void*)sf->vkerns->firsts_flags + sf->vkerns->first_cnt * sizeof(int), 0, above_count * sizeof(int));
sf->vkerns->first_cnt += above_count;
}
if (below_count > 0) {
sf->vkerns->seconds = realloc(sf->vkerns->seconds, sizeof(char *) * (sf->vkerns->second_cnt + below_count));
memset((void*)sf->vkerns->seconds + sf->vkerns->second_cnt * sizeof(char *), 0, below_count * sizeof(char *));
sf->vkerns->seconds_names = realloc(sf->vkerns->seconds_names, sizeof(char *) * (sf->vkerns->second_cnt + below_count));
memset((void*)sf->vkerns->seconds_names + sf->vkerns->second_cnt * sizeof(char *), 0, below_count * sizeof(char *));
sf->vkerns->seconds_flags = realloc(sf->vkerns->seconds_flags, sizeof(int) * (sf->vkerns->second_cnt + below_count));
memset((void*)sf->vkerns->seconds_flags + sf->vkerns->second_cnt * sizeof(char *), 0, below_count * sizeof(int));
sf->vkerns->second_cnt += below_count;
}
// Start copying.
left_count = 0; right_count = 0; above_count = 0; below_count = 0;
for (current_group = group_base; current_group != NULL; current_group = current_group->next) {
int group_type = GroupNameType(current_group->classname);
// This function only gets used in processing groups.plist right now, so it assumes that the groups are native as below.
if ((group_type != -1) && (group_type & (GROUP_NAME_KERNING_UFO|GROUP_NAME_KERNING_FEATURE))) {
if (group_type & GROUP_NAME_VERTICAL) {
if (group_type & GROUP_NAME_RIGHT) {
sf->vkerns->seconds[below_start + below_count] = copy(current_group->glyphs);
sf->vkerns->seconds_names[below_start + below_count] = copy(current_group->classname);
sf->vkerns->seconds_flags[below_start + below_count] = FF_KERNCLASS_FLAG_NATIVE | ((group_type & GROUP_NAME_KERNING_FEATURE) ? FF_KERNCLASS_FLAG_NAMETYPE : 0);
below_count++;
} else {
sf->vkerns->firsts[above_start + above_count] = copy(current_group->glyphs);
sf->vkerns->firsts_names[above_start + above_count] = copy(current_group->classname);
sf->vkerns->firsts_flags[above_start + above_count] = FF_KERNCLASS_FLAG_NATIVE | ((group_type & GROUP_NAME_KERNING_FEATURE) ? FF_KERNCLASS_FLAG_NAMETYPE : 0);
above_count++;
}
} else {
if (group_type & GROUP_NAME_RIGHT) {
sf->kerns->seconds[right_start + right_count] = copy(current_group->glyphs);
sf->kerns->seconds_names[right_start + right_count] = copy(current_group->classname);
sf->kerns->seconds_flags[right_start + right_count] = FF_KERNCLASS_FLAG_NATIVE | ((group_type & GROUP_NAME_KERNING_FEATURE) ? FF_KERNCLASS_FLAG_NAMETYPE : 0);
right_count++;
} else {
sf->kerns->firsts[left_start + left_count] = copy(current_group->glyphs);
sf->kerns->firsts_names[left_start + left_count] = copy(current_group->classname);
sf->kerns->firsts_flags[left_start + left_count] = FF_KERNCLASS_FLAG_NATIVE | ((group_type & GROUP_NAME_KERNING_FEATURE) ? FF_KERNCLASS_FLAG_NAMETYPE : 0);
left_count++;
}
}
}
}
#ifdef UFO_GUESS_SCRIPTS
// Check the script in each element of each group (for each polarity) until a character is of a script other than DFLT.
if (sf->kerns != NULL) {
uint32 script = DEFAULT_SCRIPT;
int class_index;
class_index = 0;
while (script == DEFAULT_SCRIPT && class_index < sf->kerns->first_cnt) {
if (sf->kerns->firsts[class_index] != NULL) script = script_from_glyph_list(sf, sf->kerns->firsts[class_index]);
class_index++;
}
class_index = 0;
while (script == DEFAULT_SCRIPT && class_index < sf->kerns->second_cnt) {
if (sf->kerns->seconds[class_index] != NULL) script = script_from_glyph_list(sf, sf->kerns->seconds[class_index]);
class_index++;
}
sf->kerns->subtable = SFSubTableFindOrMake(sf, CHR('k','e','r','n'), script, gpos_pair);
}
if (sf->vkerns != NULL) {
uint32 script = DEFAULT_SCRIPT;
int class_index;
class_index = 0;
while (script == DEFAULT_SCRIPT && class_index < sf->vkerns->first_cnt) {
if (sf->vkerns->firsts[class_index] != NULL) script = script_from_glyph_list(sf, sf->vkerns->firsts[class_index]);
class_index++;
}
class_index = 0;
while (script == DEFAULT_SCRIPT && class_index < sf->vkerns->second_cnt) {
if (sf->vkerns->seconds[class_index] != NULL) script = script_from_glyph_list(sf, sf->vkerns->seconds[class_index]);
class_index++;
}
sf->vkerns->subtable = SFSubTableFindOrMake(sf, CHR('v','k','r','n'), script, gpos_pair);
}
#else
// Some test cases have proven that FontForge would do best to avoid classifying these.
uint32 script = DEFAULT_SCRIPT;
if (sf->kerns != NULL) {
sf->kerns->subtable = SFSubTableFindOrMake(sf, CHR('k','e','r','n'), script, gpos_pair);
}
if (sf->vkerns != NULL) {
sf->vkerns->subtable = SFSubTableFindOrMake(sf, CHR('v','k','r','n'), script, gpos_pair);
}
#endif // UFO_GUESS_SCRIPTS
}
static void UFOHandleGroups(SplineFont *sf, char *basedir) {
char *fname = buildname(basedir, "groups.plist");
xmlDocPtr doc=NULL;
xmlNodePtr plist,dict,keys,value,subkeys;
char *keyname, *valname;
struct ff_glyphclasses *current_group = NULL;
// We want to start at the end of the list of groups already in the SplineFont (probably not any right now).
for (current_group = sf->groups; current_group != NULL && current_group->next != NULL; current_group = current_group->next);
int group_count;
if ( GFileExists(fname))
doc = xmlParseFile(fname);
free(fname);
if ( doc==NULL )
return;
plist = xmlDocGetRootElement(doc);
dict = FindNode(plist->children,"dict");
if ( xmlStrcmp(plist->name,(const xmlChar *) "plist")!=0 || dict==NULL ) {
LogError(_("Expected property list file"));
xmlFreeDoc(doc);
return;
}
for ( keys=dict->children; keys!=NULL; keys=keys->next ) {
for ( value = keys->next; value!=NULL && xmlStrcmp(value->name,(const xmlChar *) "text")==0;
value = value->next );
if ( value==NULL )
break;
if ( xmlStrcmp(keys->name,(const xmlChar *) "key")==0 ) {
keyname = (char *) xmlNodeListGetString(doc,keys->children,true);
SplineChar *sc = SFGetChar(sf,-1,keyname);
if ( sc!=NULL ) { LogError(_("Skipping group %s with same name as a glyph.\n"), keyname); free(keyname); keyname = NULL; continue; }
struct ff_glyphclasses *sfg = SFGetGroup(sf,-1,keyname);
if ( sfg!=NULL ) { LogError(_("Skipping duplicate group %s.\n"), keyname); free(keyname); keyname = NULL; continue; }
sfg = calloc(1, sizeof(struct ff_glyphclasses)); // We allocate space for the new group.
sfg->classname = keyname; keyname = NULL; // We name it.
if (current_group == NULL) sf->groups = sfg;
else current_group->next = sfg;
current_group = sfg; // And we put it at the end of the list.
// We prepare to populate it. We will match to native glyphs first (in order to validate) and then convert back to strings later.
RefChar *members_native = NULL;
RefChar *member_native_current = NULL;
int member_count = 0;
int member_list_length = 0; // This makes it easy to allocate a string at the end.
// We fetch the contents now. They are in an array, but we do not verify that.
keys = value;
for ( subkeys = keys->children; subkeys!=NULL; subkeys = subkeys->next ) {
if ( xmlStrcmp(subkeys->name,(const xmlChar *) "string")==0 ) {
keyname = (char *) xmlNodeListGetString(doc,subkeys->children,true); // Get the member name.
SplineChar *ssc = SFGetChar(sf,-1,keyname); // Try to match an existing glyph.
if ( ssc==NULL ) { LogError(_("Skipping non-existent glyph %s in group %s.\n"), keyname, current_group->classname); free(keyname); keyname = NULL; continue; }
member_list_length += strlen(keyname) + 1; member_count++; // Make space for its name.
free(keyname); // Free the name for now. (We get it directly from the SplineChar later.)
RefChar *member_native_temp = calloc(1, sizeof(RefChar)); // Make an entry in the list for the native reference.
member_native_temp->sc = ssc; ssc = NULL;
if (member_native_current == NULL) members_native = member_native_temp;
else member_native_current->next = member_native_temp;
member_native_current = member_native_temp; // Add it to the end of the list.
}
}
if (member_list_length == 0) member_list_length++; // We must have space for a zero-terminator even if the list is empty. A non-empty list has space for a space at the end that we can use.
current_group->glyphs = malloc(member_list_length); // We allocate space for the list.
current_group->glyphs[0] =