Skip to content

Commit

Permalink
Add support for reading designspace files/design units
Browse files Browse the repository at this point in the history
  • Loading branch information
skef committed Jun 4, 2024
1 parent d5fbac1 commit 7b07c6b
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 10 deletions.
11 changes: 9 additions & 2 deletions c/addfeatures/cb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ struct cbCtx_ {
hotMacData mac; /* Mac-specific data from database */

std::shared_ptr<GOADB> goadb;
std::shared_ptr<Designspace> ds;
std::shared_ptr<slogger> logger;
};

Expand Down Expand Up @@ -375,6 +376,11 @@ void cbAliasDBRead(cbCtx h, const char *filename) {
cbFatal(h, "Problem reading GlyphOrderAndAliasDB file");
}

void cbDesignspaceRead(cbCtx h, const char *filename) {
if (!h->ds->read(filename))
cbFatal(h, "Problem reading Designspace file");
}

// ------------------------ Font Conversion Database -----------------------

// [fcdb callback] Refill current database input file buffer.
Expand Down Expand Up @@ -474,7 +480,8 @@ cbCtx cbNew(const char *progname, const char *indir, const char *outdir,
fcdbCallbacks fcdbcb;

h->logger = slogger::getLogger("addfeatures");
h->goadb = std::make_shared<GOADB>(h->logger); // XXX use final names?
h->goadb = std::make_shared<GOADB>(h->logger);
h->ds = std::make_shared<Designspace>(h->logger);

/* Initialize context */
h->progname = progname;
Expand All @@ -484,7 +491,7 @@ cbCtx cbNew(const char *progname, const char *indir, const char *outdir,

h->dnactx = mainDnaCtx;

h->hot.ctx = hotNew(&h->hot.cb, h->goadb, h->logger);
h->hot.ctx = hotNew(&h->hot.cb, h->goadb, h->ds, h->logger);
dnaINIT(mainDnaCtx, h->tmpbuf, 32, 32);
h->mac.encoding = NULL;
h->mac.cmapScript = HOT_CMAP_UNKNOWN;
Expand Down
1 change: 1 addition & 0 deletions c/addfeatures/cb.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ void cbConvert(cbCtx h, int flags, const char *clientVers,

void cbFCDBRead(cbCtx h, const char *filename);
void cbAliasDBRead(cbCtx h, const char *filename);
void cbDesignspaceRead(cbCtx h, const char *filename);
void cbFree(cbCtx h);

/* --- Utility functions --- */
Expand Down
15 changes: 9 additions & 6 deletions c/addfeatures/hotconv/FeatVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1540,17 +1540,20 @@ bool FeatVisitor::addAxisLocationLiteral(FeatParser::AxisLocationLiteralContext
std::vector<var_F2dot14> &l) {
Tag tag = getTag(ctx->tag());
int16_t axisIndex = fc->axisTagToIndex(tag);
if (axisIndex < 0)
if (axisIndex < 0) {
fc->featMsg(sERROR, "Axis not found in font");
return false;
}
assert(axisIndex < (int16_t)l.size());

std::string unit = ctx->AXISUNIT() ? TOK(ctx->AXISUNIT())->getText() : fc->defAxisUnit;
if (unit == "d") {
fc->featMsg(sFATAL, "XXX No support for design units yet.");
return false;
}
Fixed v = getFixed<Fixed>(ctx->fixedNum());
if (unit == "u") {
if (unit == "d") {
assert(fc->g->ds != nullptr);
v = fc->g->ds->userizeCoord(axisIndex, v);
assert(fc->g->ctx.axes != nullptr);
fc->g->ctx.axes->normalizeCoord(axisIndex, v, v);
} else if (unit == "u") {
assert(fc->g->ctx.axes != nullptr);
fc->g->ctx.axes->normalizeCoord(axisIndex, v, v);
} else {
Expand Down
1 change: 1 addition & 0 deletions c/addfeatures/hotconv/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ struct hotCtx_ {

std::shared_ptr<slogger> logger;
std::shared_ptr<GOADB> goadb;
std::shared_ptr<Designspace> ds;
};

/* Functions */
Expand Down
8 changes: 8 additions & 0 deletions c/addfeatures/hotconv/hot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ void hotMakeSSC(hotCtx g, ctlSharedStmCallbacks &c) {
}

hotCtx hotNew(hotCallbacks *hotcb, std::shared_ptr<GOADB> goadb,
std::shared_ptr<Designspace> ds,
std::shared_ptr<slogger> logger) {
time_t now;
hotCtx g = new hotCtx_();
Expand Down Expand Up @@ -227,6 +228,9 @@ hotCtx hotNew(hotCallbacks *hotcb, std::shared_ptr<GOADB> goadb,

g->logger = std::make_shared<hotlogger>(g, logger);
g->goadb = goadb;
g->ds = ds;
if (g->ds != nullptr)
g->ds->setLogger(g->logger);

return g;
}
Expand Down Expand Up @@ -352,6 +356,8 @@ const char *hotReadFont(hotCtx g, int flags, bool &isROS) {
if (g->ctx.MVAR == nullptr)
g->ctx.MVAR = new var_MVAR {};
g->ctx.MVAR->setAxisCount(g->ctx.feat->getAxisCount());
if (g->ctx.axes != nullptr && g->ds != nullptr)
g->ds->checkAxes(*g->ctx.axes);

/* Copy conversion flags */
g->font.flags = 0;
Expand Down Expand Up @@ -759,11 +765,13 @@ static void setVBounds(hotCtx g) {
static unsigned int dsigCnt = 0;

static void hotReuse(hotCtx g) {
/*
g->ctx.feat->dumpLocationDefs();
if (g->ctx.locMap != nullptr)
g->ctx.locMap->toerr();
if (g->ctx.GDEFp != nullptr)
g->ctx.GDEFp->ivs.toerr();
*/

g->hadError = false;
g->convertFlags = 0;
Expand Down
2 changes: 2 additions & 0 deletions c/addfeatures/include/hotconv.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This software is licensed as OpenSource, under the Apache License, Version 2.0.
#include <memory>

#include "ctlshare.h"
#include "designspace.h"
#include "goadb.h"
#include "slogger.h"

Expand Down Expand Up @@ -142,6 +143,7 @@ typedef struct hotCtx_ *hotCtx; /* Opaque library context */
typedef struct hotCallbacks_ hotCallbacks;

hotCtx hotNew(hotCallbacks *cb, std::shared_ptr<GOADB> goadb = {},
std::shared_ptr<Designspace> ds = {},
std::shared_ptr<slogger> logger = {});

/* hotNew() initializes the library and returns an opaque context that is
Expand Down
11 changes: 10 additions & 1 deletion c/addfeatures/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ static void printUsage(void) {
"-mf <path> : Specify path for the FontMenuNameDB file.\n"
"-gf <path> : Specify path for the GlyphOrderAndAliasDB file. This is only\n"
" used as a source for unicode mappings\n"
"-ds <path> : Specify path to a Designspace file. This is only used as a\n"
" source for mappings of user to design units\n"
"-lookupFinal : Look for final GlyphOrderAndAliasDB names when determining\n"
" Unicode values\n"
"-showFinal : Display final GlyphOrderAndAliasDB names even when not\n"
Expand Down Expand Up @@ -297,6 +299,13 @@ static void parseArgs(int argc, char *argv[], int inScript) {
convert.otherflags |= OTHERFLAGS_DOUBLE_MAP_GLYPHS;
}
break;
case 's':
if (arg[3] != '\0' || argsleft == 0) {
showUsage();
}
cbDesignspaceRead(cbctx, argv[++i]);
break;


default:
cbFatal(cbctx, "unrecognized option (%s)", arg);
Expand Down Expand Up @@ -345,7 +354,7 @@ static void parseArgs(int argc, char *argv[], int inScript) {

case 'g': /* Glyph name alias database */
switch (arg[2]) {
case 'f': /* [-c] CMap directory */
case 'f':
if (arg[3] != '\0' || argsleft == 0) {
showUsage();
}
Expand Down
2 changes: 2 additions & 0 deletions c/shared/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ add_library(shared OBJECT
cffwrite/cffwrite_varstore.h
ctutil.c
da.c
designspace.cpp
dynarr.c
goadb.cpp
support/except.c
Expand Down Expand Up @@ -76,6 +77,7 @@ add_library(shared OBJECT
include/ctlshare.h
include/ctutil.h
include/da.h
include/designspace.h
include/dynarr.h
include/goadb.h
include/namesupport.h
Expand Down
211 changes: 211 additions & 0 deletions c/shared/designspace.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/* Copyright 2024 Adobe Systems Incorporated (http://www.adobe.com/). All Rights Reserved.
This software is licensed as OpenSource, under the Apache License, Version 2.0.
This license is available at: http://opensource.org/licenses/Apache-2.0. */

#include "designspace.h"

#include <libxml/tree.h>
#include <libxml/parser.h>

#include <algorithm>
#include <cstdlib>
#include <iostream>

#include "supportfp.h"

bool Designspace::checkAxes(var_axes &va) {
if (!readFile)
return true;
if (va.getAxisCount() != axes.size()) {
logger->log(sFATAL, "designspace file has different number of axes than "
"fvar table in font");
return false;
}

for (auto &a : axes) {
a.var_axes_index = va.getAxisIndex(a.tag);
if (a.var_axes_index == -1) {
logger->log(sFATAL, "axis '%s' from designspace file not found in fvar",
a.name.c_str());
return false;
}
}
std::sort(axes.begin(), axes.end(), [](const axis &a, const axis &b) { return a.var_axes_index < b.var_axes_index; });
for (size_t i = 0; i < axes.size(); i++) {
auto &vaa = va.axes[i];
auto &a = axes[i];
assert(a.var_axes_index == i);
if (vaa.defaultValue != a.defaultValue || vaa.minValue != a.minValue ||
vaa.maxValue != a.maxValue) {
logger->log(sFATAL, "axis '%s' has different values in designspace versus fvar",
a.name.c_str());
return false;
}
}
return true;
}

Fixed Designspace::userizeCoord(uint16_t axisIndex, Fixed v) {
if (!readFile) {
logger->log(sERROR, "Design unit value used without designspace file");
return v;
}
assert(axisIndex < axes.size());
auto &a = axes[axisIndex];
if (a.valueMap.size() == 0)
return v;
auto i = a.valueMap.lower_bound(v);
if (i == a.valueMap.end()) {
logger->log(sWARNING, "Design value %g greater than highest map value for axis '%s', "
"pinning to maximum axis value",
(float) v / 65536.0, a.name.c_str());
return a.maxValue;
}
if (i->first == v)
return i->second;
assert(i->first > v);
if (i == a.valueMap.begin()) {
logger->log(sWARNING, "Design value %g less than lowest map value for axis '%s', "
"pinning to minimum axis value",
(float) v / 65536.0, a.name.c_str());
return a.minValue;
}
Fixed highD = i->first, highU = i->second;
i--;
Fixed lowD = i->first, lowU = i->second;
assert(lowD < v);
float adj = (float)(highU - lowU) * (float) (v - lowD) / (float) (highD - lowD);
return lowU + roundf(adj);
}

bool Designspace::read(const char *filename) {
auto doc = xmlReadFile(filename, NULL, 0);
if (doc == nullptr) {
logger->log(sERROR, "Error reading designspace file '%s'", filename);
return false;
}
auto root = xmlDocGetRootElement(doc);
if (root == nullptr) {
logger->log(sERROR, "Empty designspace file '%s'", filename);
xmlFreeDoc(doc);
return false;
}
readFile = true;
auto cur = root->xmlChildrenNode;
while (cur != nullptr) {
if (xmlStrcmp(cur->name, (const xmlChar *) "axes") == 0)
if (!readAxes(doc, cur))
return false;
cur = cur->next;
}
xmlFreeDoc(doc);
return true;
}

bool Designspace::readAxes(xmlDocPtr doc, xmlNodePtr axesptr) {
assert(axesptr != nullptr);
auto cur = axesptr->xmlChildrenNode;
while (cur != nullptr) {
axis a;
if (xmlStrcmp(cur->name, (const xmlChar *) "axis")) {
cur = cur->next;
continue;
}
auto attr = cur->properties;
while (attr != nullptr) {
if (xmlStrcmp(attr->name, (const xmlChar *) "name") == 0) {
a.name = (char*) attr->children->content;
} else if (xmlStrcmp(attr->name, (const xmlChar *) "tag") == 0) {
a.tag = str2tag((char *) attr->children->content);
if (a.tag == 0)
return false;
} else if (xmlStrcmp(attr->name, (const xmlChar *) "hidden") == 0) {
a.hidden = attr->children->content[0] == '1';
} else if (xmlStrcmp(attr->name, (const xmlChar *) "values") == 0) {
a.discrete = true;
} else if (xmlStrcmp(attr->name, (const xmlChar *) "default") == 0) {
if (!getFixed(attr, a.defaultValue))
return false;
} else if (xmlStrcmp(attr->name, (const xmlChar *) "minimum") == 0) {
if (!getFixed(attr, a.minValue))
return false;
} else if (xmlStrcmp(attr->name, (const xmlChar *) "maximum") == 0) {
if (!getFixed(attr, a.maxValue))
return false;
}
attr = attr->next;
}
auto child = cur->xmlChildrenNode;
while (child != nullptr) {
if (xmlStrcmp(child->name, (const xmlChar *) "map") == 0) {
Fixed userV, designV;
attr = child->properties;
while (attr != nullptr) {
if (xmlStrcmp(attr->name, (const xmlChar *) "input") == 0) {
if (!getFixed(attr, userV))
return false;
} else if (xmlStrcmp(attr->name, (const xmlChar *) "output") == 0) {
if (!getFixed(attr, designV))
return false;
}
attr = attr->next;
}
a.valueMap.insert({designV, userV});
}
child = child->next;
}
axes.push_back(std::move(a));
cur = cur->next;
}
toerr();
return true;
}

void Designspace::toerr() {
for (auto &a : axes) {
std::cout << "Designspace Axis " << a.name << "(tag " << a.tag << "):" << std::endl;
std::cout << " min: " << a.minValue / 65536.0 << ", default: " << a.defaultValue / 65536.0 << ", max: " << a.maxValue / 65536.0 << std::endl;
std::cout << " map:";
for (auto [designV, userV] : a.valueMap) {
std::cout << userV / 65536.0 << " -> " << designV / 65536.0 << ", ";
}
std::cout << std::endl;
}
}

bool Designspace::getFixed(xmlAttr *attr, Fixed &r) {
char *end;
float f = strtof((char *) attr->children->content, &end);
if (end == (char *) attr->children->content) {
logger->log(sERROR, "Invalid numeric attribute in axis '%s'", attr->children->content);
return false;
}
r = pflttofix(&f);
return true;
}

// XXX Pretty much copied from FeatCtx.cpp
ctlTag Designspace::str2tag(const std::string &tagName) {
if (tagName.length() == 0) {
logger->log(sERROR, "Empty tag in designspace axis element");
return 0;
}

if ( tagName.length() > 4 )
logger->log(sERROR, "Tag %s exceeds 4 characters", tagName.c_str());

char buf[4];
size_t l = tagName.length();
for (size_t i = 0; i < 4; i++) {
char c {' '};
if (i < l) {
c = tagName[i];
if (c < 0x20 || c > 0x7E) {
logger->log(sERROR, "Invalid character value %hhx in tag string", c);
c = 0;
}
}
buf[i] = c;
}
return CTL_TAG(buf[0], buf[1], buf[2], buf[3]);
}
Loading

0 comments on commit 7b07c6b

Please sign in to comment.