prototypo-library
Version:
This is prototypo parametric font library
236 lines (212 loc) • 6.81 kB
JavaScript
import _difference from 'lodash/difference';
import _get from 'lodash/get';
import _uniq from 'lodash/uniq';
import _reduce from 'lodash/reduce';
import _find from 'lodash/find';
import _keys from 'lodash/keys';
export default class Formula {
constructor(formula, cursor) {
this.cursor = cursor;
this.dependencies = formula._dependencies; // eslint-disable-line no-underscore-dangle
/* eslint-disable no-new-func */
this.operation = new Function(
...['contours', 'anchors', 'parentAnchors', 'Utils']
.concat(formula._parameters || []) // eslint-disable-line no-underscore-dangle
.concat(
(typeof formula._operation === 'string' // eslint-disable-line no-underscore-dangle
&& formula._operation.indexOf('return ') === -1 // eslint-disable-line no-underscore-dangle
? 'return '
: '')
// The operation might be wrapped in a function (e.g. multi-
// line code for debugging purpose). In this case, return
// must be explicit
+ formula._operation
.toString() // eslint-disable-line no-underscore-dangle
// [\s\S] need to be used instead of . because
// javascript doesn't have a dotall flag (s)
.replace(/^function\s*\(\)\s*\{([\s\S]*?)\}$/, '$1')
.trim(),
/* +
// add sourceURL pragma to help debugging
// TODO: restore sourceURL pragma if it proves necessary
'\n\n//# sourceURL=' + path */
),
);
/* eslint-enable no-new-func */
this.parameters = formula._parameters; // eslint-disable-line no-underscore-dangle
this.analyzing = false;
}
analyzeDependency(glyph, graph = []) {
graph.push(this.cursor);
if (this.analyzing) {
throw new Error(`There is a circular dependency for glyph ${
glyph.name.value
} following the subsequent graph:
${graph.join(' => ')}
`);
}
this.analyzing = true;
this.dependencies.forEach((dependency) => {
try {
if (dependency.indexOf('parentAnchors') === -1) {
const formula = glyph.getFromXPath(dependency);
formula.analyzeDependency(glyph, graph);
}
}
catch (e) {
throw new Error(`There was an error while checking glyph ${
glyph.name.value
} dependencies for cursor: ${dependency}.
${e.message}`);
}
graph.pop();
});
this.analyzing = false;
}
getResult(parameters, contours, anchors, parentAnchors, utils) {
/* #if dev */
const missingParam = _difference(this.parameters, _keys(parameters));
if (missingParam.length > 0) {
console.error(`parameters are missing: ${missingParam}`); // eslint-disable-line no-console
}
/* #end */
const args = [contours, anchors, parentAnchors, utils];
for (let i = 0; i < this.parameters.length; i++) {
const name = this.parameters[i];
args.push(parameters[name]);
}
const result = this.operation.apply(this, args);
if (typeof result === 'number' && isNaN(result)) {
/* eslint-disable no-console */
console.error(`Operation returned NaN
operation is:
${this.operation.toString()}
parameters value are:
${this.parameters.map(name => `${name}: ${parameters[name]}`)}
cursor used are:
${this.dependencies.map(name => `${name}: ${_get(contours, name)}`)}`);
/* eslint-enable no-console */
}
return result;
}
solveOperationOrder(glyph, operationOrder) {
const result = [];
const operationsToSolve = _difference(
_uniq(this.dependencies),
operationOrder,
);
if (operationsToSolve.length > 0) {
result.push(
..._reduce(
operationsToSolve,
(acc, xpath) => {
const expandedIndex = xpath.indexOf('expandedTo');
const processedOps = [...operationOrder, ...result, ...acc];
// We don't have to compute dependcy on parentAnchors they are not
// our responsability and should be provided by parent
if (xpath.indexOf('parentAnchors') !== -1) {
return acc;
}
if (xpath.match(/handle(Out|In)/)) {
const contourPath = xpath
.split('.')
.slice(0, 2)
.join('.');
const contour = glyph.getFromXPath(contourPath);
acc.push(
...contour.solveOperationOrder(glyph, [...processedOps]),
);
}
else if (expandedIndex !== -1) {
/* eslint-disable no-negated-condition, max-depth */
const base = xpath.substr(0, expandedIndex - 1);
const node = glyph.getFromXPath(`${base}`);
if (node.expandedTo) {
if (process.env.TESTING_FONT === 'yes') {
if (!glyph.getFromXPath(xpath)) {
console.log(`${glyph.name.value} on cursor ${xpath}`); // eslint-disable-line no-console
}
}
acc.push(
...glyph
.getFromXPath(xpath)
.solveOperationOrder(glyph, [...processedOps]),
);
}
else {
if (process.env.TESTING_FONT === 'yes') {
if (
!glyph.getFromXPath(`${base}.expand.width`)
|| !glyph.getFromXPath(`${base}.expand.distr`)
|| !glyph.getFromXPath(`${base}.expand.angle`)
|| !glyph.getFromXPath(`${base}.x`)
|| !glyph.getFromXPath(`${base}.y`)
) {
console.log(`${glyph.name.value} on cursor ${base}`); // eslint-disable-line no-console
}
}
const expandResult = glyph
.getFromXPath(`${base}.expand.width`)
.solveOperationOrder(glyph, processedOps);
expandResult.push(
...glyph
.getFromXPath(`${base}.expand.distr`)
.solveOperationOrder(glyph, [
...processedOps,
...expandResult,
]),
);
expandResult.push(
...glyph
.getFromXPath(`${base}.expand.angle`)
.solveOperationOrder(glyph, [
...processedOps,
...expandResult,
]),
);
expandResult.push(
...glyph
.getFromXPath(`${base}.x`)
.solveOperationOrder(glyph, [
...processedOps,
...expandResult,
]),
);
expandResult.push(
...glyph
.getFromXPath(`${base}.y`)
.solveOperationOrder(glyph, [
...processedOps,
...expandResult,
]),
);
const opToAdd = {
action: 'expand',
cursor: base,
};
if (!_find([...processedOps, ...expandResult], opToAdd)) {
expandResult.push(opToAdd);
}
acc.push(...expandResult);
}
}
else {
/* eslint-disable no-negated-condition, max-depth */
acc.push(
...glyph
.getFromXPath(xpath)
.solveOperationOrder(glyph, processedOps),
);
}
return acc;
},
[],
),
);
}
if ([...operationOrder, ...result].indexOf(this.cursor) === -1) {
result.push(this.cursor);
}
return result;
}
}