format
This commit is contained in:
parent
07360801e4
commit
e8a1b9b305
92
generator.js
92
generator.js
|
@ -3,22 +3,22 @@ var path = require('path');
|
|||
|
||||
var TTFWriter = require('node-sfnt').TTFWriter;
|
||||
var argv = require('yargs').argv;
|
||||
var buildGlyphs = require('./buildglyphs.js');
|
||||
var buildGlyphs = require('./buildglyphs.js');
|
||||
var parameters = require('./parameters');
|
||||
var toml = require('toml');
|
||||
|
||||
var Glyph = require('./support/glyph');
|
||||
|
||||
function hasv(obj){
|
||||
if(!obj) return false;
|
||||
for(var k in obj) if(obj[k]) return true;
|
||||
function hasv(obj) {
|
||||
if (!obj) return false;
|
||||
for (var k in obj) if (obj[k]) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Font building
|
||||
var font = function(){
|
||||
var font = function() {
|
||||
var parametersData = toml.parse(fs.readFileSync(path.join(path.dirname(require.main.filename), 'parameters.toml'), 'utf-8'));
|
||||
var emptyFont = toml.parse(fs.readFileSync(path.join(path.dirname(require.main.filename), 'emptyfont.toml'), 'utf-8'));
|
||||
var emptyFont = toml.parse(fs.readFileSync(path.join(path.dirname(require.main.filename), 'emptyfont.toml'), 'utf-8'));
|
||||
var para = parameters.build(parametersData, argv._);
|
||||
var fontUniqueName = para.family + ' ' + para.style + ' ' + para.version + ' (' + para.codename + ')'
|
||||
|
||||
|
@ -28,11 +28,11 @@ var font = function(){
|
|||
font.features.sscompose = para.sscompose;
|
||||
font.parameters = para;
|
||||
return font;
|
||||
}();
|
||||
} ();
|
||||
|
||||
if(argv.charmap) (function(){
|
||||
if (argv.charmap) (function() {
|
||||
console.log(' Writing character map -> ' + argv.charmap);
|
||||
fs.writeFileSync(argv.charmap, JSON.stringify(font.glyf.map(function(glyph){
|
||||
fs.writeFileSync(argv.charmap, JSON.stringify(font.glyf.map(function(glyph) {
|
||||
return [
|
||||
glyph.name,
|
||||
glyph.unicode,
|
||||
|
@ -41,13 +41,13 @@ if(argv.charmap) (function(){
|
|||
})), 'utf8')
|
||||
})();
|
||||
|
||||
if(argv.feature) (function(){
|
||||
if (argv.feature) (function() {
|
||||
console.log(' Writing feature file -> ' + argv.feature);
|
||||
var featurefile = '\n\n';
|
||||
// markGlyphs
|
||||
for(var feature in font.features.cv) {
|
||||
for (var feature in font.features.cv) {
|
||||
var base = [], replace = [];
|
||||
for(var key in font.features.cv[feature]) {
|
||||
for (var key in font.features.cv[feature]) {
|
||||
base.push(key);
|
||||
replace.push(font.features.cv[feature][key]);
|
||||
};
|
||||
|
@ -55,37 +55,37 @@ if(argv.feature) (function(){
|
|||
featurefile += 'lookup ' + lookupName + ' { sub [' + base.join(' ') + '] by [' + replace.join(' ') + '];} ' + lookupName + ';\n\n';
|
||||
featurefile += 'feature ' + feature + ' { script latn; lookup ' + lookupName + '; script grek; lookup ' + lookupName + '; script cyrl; lookup ' + lookupName + '; script DFLT; lookup ' + lookupName + '; } ' + feature + ';\n\n';
|
||||
}
|
||||
for(var feature in font.features.sscompose) {
|
||||
var stmt = font.features.sscompose[feature].map(function(lookup){ return 'lookup ' + lookup + 'Auto'}).join(';')
|
||||
for (var feature in font.features.sscompose) {
|
||||
var stmt = font.features.sscompose[feature].map(function(lookup) { return 'lookup ' + lookup + 'Auto' }).join(';')
|
||||
featurefile += 'feature ' + feature + ' { script latn; ' + stmt + '; script grek; ' + stmt + '; script cyrl; ' + stmt + '; script DFLT; ' + stmt + '; } ' + feature + ';\n\n';
|
||||
}
|
||||
for(var key in font.features.markGlyphs){
|
||||
for (var key in font.features.markGlyphs) {
|
||||
featurefile += '@MG_' + key + '= [' + font.features.markGlyphs[key].join(' ') + '];\n'
|
||||
}
|
||||
// mark
|
||||
var mark = font.features.mark;
|
||||
for(var id in mark) {
|
||||
for (var id in mark) {
|
||||
var lookup = mark[id];
|
||||
var lookupName = 'markAuto_' + id;
|
||||
featurefile += 'lookup ' + lookupName + ' {' + lookup.marks.join(';\n') + ';\n'
|
||||
+ lookup.bases.join(';\n') + ';} ' + lookupName + ';'
|
||||
}
|
||||
|
||||
|
||||
// mkmk
|
||||
var mkmk = font.features.mkmk;
|
||||
featurefile += 'lookup mkmkAuto {' + mkmk.marks.join(';\n') + ';\n'
|
||||
+ mkmk.bases.join(';\n') + ';} mkmkAuto;'
|
||||
|
||||
|
||||
// gdef
|
||||
var gdef = font.features.gdef;
|
||||
featurefile += '@GDEF_Simple = [' + gdef.simple.join(' \n') + '];\n'
|
||||
+ '@GDEF_Ligature =[' + gdef.ligature.join(' \n') + '];\n'
|
||||
+ '@GDEF_Mark = [' + gdef.mark.join(' \n') + '];\n'
|
||||
+ 'table GDEF { GlyphClassDef @GDEF_Simple, @GDEF_Ligature, @GDEF_Mark, ;} GDEF;'
|
||||
|
||||
|
||||
featurefile += fs.readFileSync(__dirname + '/features/common.fea', 'utf-8');
|
||||
featurefile += fs.readFileSync(__dirname + '/features/' + (font.parameters.isItalic ? 'italiconly.fea' : 'uprightonly.fea'), 'utf-8');
|
||||
if(!font.parameters.disableLigation)
|
||||
if (!font.parameters.disableLigation)
|
||||
featurefile += fs.readFileSync(__dirname + '/features/ligation.fea', 'utf-8');
|
||||
fs.writeFileSync(argv.feature, featurefile, 'utf-8');
|
||||
})();
|
||||
|
@ -136,14 +136,14 @@ if(argv.ttf) (function(){
|
|||
})();
|
||||
*/
|
||||
|
||||
if(argv.svg) (function(){
|
||||
if (argv.svg) (function() {
|
||||
console.log(' Writing outline as SVG -> ' + argv.svg);
|
||||
|
||||
|
||||
var foundNaN = false;
|
||||
var glyfname = '';
|
||||
function cov(x) {
|
||||
if(!isFinite(x)){
|
||||
if(!foundNaN) {
|
||||
if (!isFinite(x)) {
|
||||
if (!foundNaN) {
|
||||
console.log("*** NaN value found in " + argv.svg + '(' + glyfname + ')' + " ***")
|
||||
foundNaN = true
|
||||
}
|
||||
|
@ -151,33 +151,33 @@ if(argv.svg) (function(){
|
|||
}
|
||||
return Math.round(x * 10000) / 10000
|
||||
};
|
||||
function mix(a, b, p){ return a + (b - a) * p };
|
||||
|
||||
function toSVGPath(glyph){
|
||||
function mix(a, b, p) { return a + (b - a) * p };
|
||||
|
||||
function toSVGPath(glyph) {
|
||||
var buf = '';
|
||||
foundNaN = false;
|
||||
glyfname = glyph.name;
|
||||
if(glyph.contours) for(var j = 0; j < glyph.contours.length; j++) {
|
||||
if (glyph.contours) for (var j = 0; j < glyph.contours.length; j++) {
|
||||
var contour = glyph.contours[j];
|
||||
var lx = 0;
|
||||
var ly = 0;
|
||||
if(contour.length) {
|
||||
if (contour.length) {
|
||||
lx = contour[0].x;
|
||||
ly = contour[0].y;
|
||||
buf += 'M' + cov(lx) + ' ' + cov(ly);
|
||||
for(var k = 1; k < contour.length; k++) if(contour[k].onCurve){
|
||||
for (var k = 1; k < contour.length; k++) if (contour[k].onCurve) {
|
||||
lx = contour[k].x;
|
||||
ly = contour[k].y;
|
||||
buf += 'L' + cov(lx) + ' ' + cov(ly);
|
||||
} else if(contour[k].cubic) {
|
||||
} else if (contour[k].cubic) {
|
||||
var rx = contour[k + 2].x;
|
||||
var ry = contour[k + 2].y;
|
||||
buf += 'C' + [contour[k].x, contour[k].y, contour[k + 1].x, contour[k + 1].y, rx, ry].map(cov).join(' ');
|
||||
lx = rx;
|
||||
ly = ry;
|
||||
k += 2;
|
||||
} else if(contour[k + 1]) {
|
||||
if(contour[k + 1].onCurve){
|
||||
} else if (contour[k + 1]) {
|
||||
if (contour[k + 1].onCurve) {
|
||||
var rx = contour[k + 1].x;
|
||||
var ry = contour[k + 1].y;
|
||||
} else {
|
||||
|
@ -188,11 +188,11 @@ if(argv.svg) (function(){
|
|||
var y1 = mix(ly, contour[k].y, 2 / 3);
|
||||
var x2 = mix(rx, contour[k].x, 2 / 3);
|
||||
var y2 = mix(ry, contour[k].y, 2 / 3);
|
||||
|
||||
|
||||
buf += 'C' + [cov(x1), cov(y1), cov(x2), cov(y2), cov(rx), cov(ry)].join(' ');
|
||||
lx = rx;
|
||||
ly = ry;
|
||||
if(contour[k + 1].onCurve) k += 1;
|
||||
if (contour[k + 1].onCurve) k += 1;
|
||||
} else {
|
||||
var rx = contour[0].x;
|
||||
var ry = contour[0].y;
|
||||
|
@ -206,18 +206,18 @@ if(argv.svg) (function(){
|
|||
return buf;
|
||||
}
|
||||
var svg = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" ><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"><defs><font id="' + font.name.postScriptName + '">'
|
||||
|
||||
|
||||
var skew = (argv.uprightify ? 1 : 0) * Math.tan((font.post.italicAngle || 0) / 180 * Math.PI);
|
||||
var scale = (argv.upm || 1000) / 1000;
|
||||
|
||||
|
||||
svg += '<font-face font-family="' + font.name.fontFamily + '" font-weight="' + font['OS/2'].usWeightClass + '" font-stretch="normal" units-per-em="' + (1000 * scale) + '"/>'
|
||||
|
||||
for(var j = 0; j < font.glyf.length; j++){
|
||||
|
||||
for (var j = 0; j < font.glyf.length; j++) {
|
||||
var g = font.glyf[j];
|
||||
if(g.contours) {
|
||||
for(var k = 0; k < g.contours.length; k++) {
|
||||
if (g.contours) {
|
||||
for (var k = 0; k < g.contours.length; k++) {
|
||||
var contour = g.contours[k];
|
||||
for(var p = 0; p < contour.length; p++) {
|
||||
for (var p = 0; p < contour.length; p++) {
|
||||
contour[p].x += contour[p].y * skew;
|
||||
contour[p].x *= scale;
|
||||
contour[p].y *= scale;
|
||||
|
@ -226,20 +226,20 @@ if(argv.svg) (function(){
|
|||
g.advanceWidth *= scale;
|
||||
//Glyph.prototype.cleanup.call(g, 0.25);
|
||||
}
|
||||
var gd = '<' + (j === 0 ? 'missing-glyph' : 'glyph') + ' glyph-name="' + g.name + '" horiz-adv-x="' + g.advanceWidth + '" '+ (g.unicode && g.unicode.length ? 'unicode="&#x' + g.unicode[0].toString(16) + ';"' : '') +' d="' + toSVGPath(g) + '" />'
|
||||
var gd = '<' + (j === 0 ? 'missing-glyph' : 'glyph') + ' glyph-name="' + g.name + '" horiz-adv-x="' + g.advanceWidth + '" ' + (g.unicode && g.unicode.length ? 'unicode="&#x' + g.unicode[0].toString(16) + ';"' : '') + ' d="' + toSVGPath(g) + '" />'
|
||||
svg += gd;
|
||||
}
|
||||
svg += '</font></defs></svg>'
|
||||
fs.writeFileSync(argv.svg, svg, 'utf-8')
|
||||
})();
|
||||
|
||||
if(argv.meta) (function(){
|
||||
if (argv.meta) (function() {
|
||||
console.log(' Writing metadata as JSON -> ' + argv.meta);
|
||||
if(argv.svg){
|
||||
if (argv.svg) {
|
||||
font.glyf = null;
|
||||
font.glyfMap = null;
|
||||
}
|
||||
if(argv.feature){
|
||||
if (argv.feature) {
|
||||
font.features = null;
|
||||
}
|
||||
fs.writeFileSync(argv.meta, JSON.stringify(font));
|
||||
|
|
|
@ -8,35 +8,35 @@ var argv = require('yargs').argv;
|
|||
var param = toml.parse(fs.readFileSync(path.join(path.dirname(require.main.filename), 'parameters.toml'), 'utf-8'))
|
||||
|
||||
function toArrayBuffer(buffer) {
|
||||
var length = buffer.length;
|
||||
var view = new DataView(new ArrayBuffer(length), 0, length);
|
||||
for (var i = 0, l = length; i < l; i++) {
|
||||
view.setUint8(i, buffer[i], false);
|
||||
}
|
||||
return view.buffer;
|
||||
var length = buffer.length;
|
||||
var view = new DataView(new ArrayBuffer(length), 0, length);
|
||||
for (var i = 0, l = length; i < l; i++) {
|
||||
view.setUint8(i, buffer[i], false);
|
||||
}
|
||||
return view.buffer;
|
||||
}
|
||||
function toBuffer(arrayBuffer) {
|
||||
var length = arrayBuffer.byteLength;
|
||||
var view = new DataView(arrayBuffer, 0, length);
|
||||
var buffer = new Buffer(length);
|
||||
for (var i = 0, l = length; i < l; i++) {
|
||||
buffer[i] = view.getUint8(i, false);
|
||||
}
|
||||
return buffer;
|
||||
var length = arrayBuffer.byteLength;
|
||||
var view = new DataView(arrayBuffer, 0, length);
|
||||
var buffer = new Buffer(length);
|
||||
for (var i = 0, l = length; i < l; i++) {
|
||||
buffer[i] = view.getUint8(i, false);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
var options = {preserveOS2Version: true, preserveXAvgCharWidth: true, writeUnknownTables: true, hinting: true}
|
||||
var options = { preserveOS2Version: true, preserveXAvgCharWidth: true, writeUnknownTables: true, hinting: true }
|
||||
|
||||
function readttf(file) {
|
||||
var data = fs.readFileSync(file);
|
||||
var buffer = toArrayBuffer(data);
|
||||
var ttf = (new TTFReader(options)).read(buffer);
|
||||
return ttf;
|
||||
var data = fs.readFileSync(file);
|
||||
var buffer = toArrayBuffer(data);
|
||||
var ttf = (new TTFReader(options)).read(buffer);
|
||||
return ttf;
|
||||
}
|
||||
|
||||
function writettf(ttf, file){
|
||||
var buffer = new TTFWriter(options).write(ttf);
|
||||
fs.writeFileSync(file, toBuffer(buffer));
|
||||
function writettf(ttf, file) {
|
||||
var buffer = new TTFWriter(options).write(ttf);
|
||||
fs.writeFileSync(file, toBuffer(buffer));
|
||||
}
|
||||
|
||||
var glyfsource = readttf(argv._[0]);
|
||||
|
@ -44,8 +44,8 @@ var ttf = JSON.parse(fs.readFileSync(argv._[1], 'utf-8'));
|
|||
|
||||
ttf.post.format = 3
|
||||
ttf.DSIG = { // add a dummy SDIG
|
||||
name: 'DSIG',
|
||||
content: [0, 0, 0, 1, 0, 0, 0, 0]
|
||||
name: 'DSIG',
|
||||
content: [0, 0, 0, 1, 0, 0, 0, 0]
|
||||
}
|
||||
ttf.glyf = glyfsource.glyf;
|
||||
ttf.GDEF = glyfsource.GDEF;
|
||||
|
|
|
@ -8,40 +8,40 @@ var CROWD = 4;
|
|||
var LOOSE = 6;
|
||||
var SMALL = 1e-4
|
||||
var CLOSE = 1;
|
||||
function cross(z1, z2, z3){
|
||||
function cross(z1, z2, z3) {
|
||||
return (z2.x - z1.x) * (z3.y - z1.y) - (z3.x - z1.x) * (z2.y - z1.y)
|
||||
};
|
||||
function dot(z1, z2, z3){
|
||||
function dot(z1, z2, z3) {
|
||||
return (z2.x - z1.x) * (z3.x - z1.x) + (z3.y - z1.y) * (z2.y - z1.y)
|
||||
};
|
||||
function solveTS(a, b, c, out, flag){
|
||||
function solveTS(a, b, c, out, flag) {
|
||||
var delta = b * b - 4 * a * c;
|
||||
if(delta > 0){
|
||||
if (delta > 0) {
|
||||
var t1 = (Math.sqrt(delta) - b) / (2 * a)
|
||||
var t2 = (-Math.sqrt(delta) - b) / (2 * a)
|
||||
if(flag){
|
||||
if(t1 >= 0 && t1 <= 1) out.push(t1)
|
||||
if(t2 >= 0 && t2 <= 1) out.push(t2)
|
||||
if (flag) {
|
||||
if (t1 >= 0 && t1 <= 1) out.push(t1)
|
||||
if (t2 >= 0 && t2 <= 1) out.push(t2)
|
||||
} else {
|
||||
if(t1 > 0 && t1 < 1) out.push(t1)
|
||||
if(t2 > 0 && t2 < 1) out.push(t2)
|
||||
if (t1 > 0 && t1 < 1) out.push(t1)
|
||||
if (t2 > 0 && t2 < 1) out.push(t2)
|
||||
}
|
||||
} else if (delta === 0){
|
||||
} else if (delta === 0) {
|
||||
var t = -b / (2 * a)
|
||||
if(flag){
|
||||
if(t >= 0 && t <= 1) out.push(t)
|
||||
if (flag) {
|
||||
if (t >= 0 && t <= 1) out.push(t)
|
||||
} else {
|
||||
if(t > 0 && t < 1) out.push(t)
|
||||
if (t > 0 && t < 1) out.push(t)
|
||||
}
|
||||
};
|
||||
}
|
||||
function findExtrema(z1, z2, z3, z4, out){
|
||||
function findExtrema(z1, z2, z3, z4, out) {
|
||||
var a = 3 * (-z1.y + 3 * z2.y - 3 * z3.y + z4.y);
|
||||
var b = 6 * (z1.y - 2 * z2.y + z3.y);
|
||||
var c = 3 * (z2.y - z1.y);
|
||||
solveTS(a, b, c, out);
|
||||
};
|
||||
function findInflections(z1, z2, z3, z4, out){
|
||||
function findInflections(z1, z2, z3, z4, out) {
|
||||
var ax = z2.x - z1.x;
|
||||
var ay = z2.y - z1.y;
|
||||
var bx = z3.x - z2.x - ax;
|
||||
|
@ -50,18 +50,18 @@ function findInflections(z1, z2, z3, z4, out){
|
|||
var cy = z4.y - z3.y - ay - 2 * by;
|
||||
solveTS(bx * cy - by * cx, ax * cy - ay * cx, ax * by - ay * bx, out, true)
|
||||
}
|
||||
function rotate(z, angle){
|
||||
function rotate(z, angle) {
|
||||
var c = Math.cos(angle), s = Math.sin(angle);
|
||||
return {
|
||||
x: c * z.x + s * z.y,
|
||||
y : -s * z.x + c * z.y
|
||||
y: -s * z.x + c * z.y
|
||||
}
|
||||
};
|
||||
function ASCEND(a, b){ return a - b }
|
||||
function fineAllExtremae(z1, z2, z3, z4, angles){
|
||||
function ASCEND(a, b) { return a - b }
|
||||
function fineAllExtremae(z1, z2, z3, z4, angles) {
|
||||
var exs = [];
|
||||
findInflections(z1, z2, z3, z4, exs);
|
||||
for(var a = 0; a < angles; a += 1){
|
||||
for (var a = 0; a < angles; a += 1) {
|
||||
findExtrema(z1, z2, z3, z4, exs);
|
||||
z1 = rotate(z1, Math.PI / angles);
|
||||
z2 = rotate(z2, Math.PI / angles);
|
||||
|
@ -70,145 +70,145 @@ function fineAllExtremae(z1, z2, z3, z4, angles){
|
|||
}
|
||||
return exs.sort(ASCEND)
|
||||
};
|
||||
function mix(z1, z2, t){
|
||||
return {x: (1 - t) * z1.x + t * z2.x, y: (1 - t) * z1.y + t * z2.y}
|
||||
function mix(z1, z2, t) {
|
||||
return { x: (1 - t) * z1.x + t * z2.x, y: (1 - t) * z1.y + t * z2.y }
|
||||
};
|
||||
function bez2(z1, z2, z3, t){
|
||||
function bez2(z1, z2, z3, t) {
|
||||
return {
|
||||
x: (1 - t) * (1 - t) * z1.x + 2 * (1 - t) * t * z2.x + t * t * z3.x,
|
||||
y: (1 - t) * (1 - t) * z1.y + 2 * (1 - t) * t * z2.y + t * t * z3.y
|
||||
}
|
||||
};
|
||||
function bez3(z1, z2, z3, z4, t){
|
||||
function bez3(z1, z2, z3, z4, t) {
|
||||
return {
|
||||
x: (1 - t) * (1 - t) * (1 - t) * z1.x
|
||||
+ 3 * t * (1 - t) * (1 - t) * z2.x
|
||||
+ 3 * t * t * (1 - t) * z3.x
|
||||
+ t * t * t *z4.x,
|
||||
+ 3 * t * (1 - t) * (1 - t) * z2.x
|
||||
+ 3 * t * t * (1 - t) * z3.x
|
||||
+ t * t * t * z4.x,
|
||||
y: (1 - t) * (1 - t) * (1 - t) * z1.y
|
||||
+ 3 * t * (1 - t) * (1 - t) * z2.y
|
||||
+ 3 * t * t * (1 - t) * z3.y
|
||||
+ t * t * t *z4.y
|
||||
+ 3 * t * (1 - t) * (1 - t) * z2.y
|
||||
+ 3 * t * t * (1 - t) * z3.y
|
||||
+ t * t * t * z4.y
|
||||
}
|
||||
};
|
||||
function splitBefore(z1, z2, z3, z4, t){
|
||||
function splitBefore(z1, z2, z3, z4, t) {
|
||||
return [z1, mix(z1, z2, t), bez2(z1, z2, z3, t), bez3(z1, z2, z3, z4, t)]
|
||||
};
|
||||
function splitAfter(z1, z2, z3, z4, t){
|
||||
function splitAfter(z1, z2, z3, z4, t) {
|
||||
return [bez3(z1, z2, z3, z4, t), bez2(z2, z3, z4, t), mix(z3, z4, t), z4]
|
||||
};
|
||||
function splitAtExtremae(z1, z2, z3, z4, angles, splitpoints){
|
||||
function splitAtExtremae(z1, z2, z3, z4, angles, splitpoints) {
|
||||
var ts = fineAllExtremae(z1, z2, z3, z4, angles);
|
||||
if(ts[0] < SMALL) {
|
||||
if (ts[0] < SMALL) {
|
||||
ts[0] = 0;
|
||||
} else {
|
||||
ts.unshift(0);
|
||||
}
|
||||
if(ts[ts.length - 1] > 1 - SMALL) {
|
||||
if (ts[ts.length - 1] > 1 - SMALL) {
|
||||
ts[ts.length - 1] = 1;
|
||||
} else {
|
||||
ts.push(1);
|
||||
};
|
||||
for(var k = 0; k < ts.length; k++){
|
||||
if(k > 0){
|
||||
for (var k = 0; k < ts.length; k++) {
|
||||
if (k > 0) {
|
||||
var t1 = ts[k - 1]; var t2 = ts[k];
|
||||
var bef = splitBefore(z1, z2, z3, z4, t2);
|
||||
var seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2);
|
||||
seg[1].onCurve = seg[2].onCurve = false;
|
||||
seg[1].cubic = seg[2].cubic = true;
|
||||
seg[3].onCurve = true;
|
||||
splitpoints.push(seg[1],seg[2],seg[3]);
|
||||
splitpoints.push(seg[1], seg[2], seg[3]);
|
||||
}
|
||||
};
|
||||
};
|
||||
function splitSegment(z1, z2, z3, z4, angles, splitpoints){
|
||||
function splitSegment(z1, z2, z3, z4, angles, splitpoints) {
|
||||
var ts = [];
|
||||
var inflectAtEnd = false;
|
||||
findInflections(z1, z2, z3, z4, ts);
|
||||
ts = ts.sort(ASCEND);
|
||||
if(ts[0] < SMALL) {
|
||||
if (ts[0] < SMALL) {
|
||||
ts[0] = 0;
|
||||
splitpoints[splitpoints.length - 1].inflect = true;
|
||||
} else {
|
||||
ts.unshift(0);
|
||||
}
|
||||
if(ts[ts.length - 1] > 1 - SMALL) {
|
||||
if (ts[ts.length - 1] > 1 - SMALL) {
|
||||
inflectAtEnd = true;
|
||||
ts[ts.length - 1] = 1;
|
||||
} else {
|
||||
ts.push(1);
|
||||
};
|
||||
for(var k = 0; k < ts.length; k++){
|
||||
if(k > 0) {
|
||||
for (var k = 0; k < ts.length; k++) {
|
||||
if (k > 0) {
|
||||
var t1 = ts[k - 1]; var t2 = ts[k];
|
||||
var bef = splitBefore(z1, z2, z3, z4, t2);
|
||||
var seg = splitAfter(bef[0], bef[1], bef[2], bef[3], t1 / t2);
|
||||
splitAtExtremae(seg[0], seg[1], seg[2], seg[3], angles, splitpoints);
|
||||
if(t2 < 1 || inflectAtEnd) splitpoints[splitpoints.length - 1].inflect = true;
|
||||
if (t2 < 1 || inflectAtEnd) splitpoints[splitpoints.length - 1].inflect = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
function fitpts(p1, c1, c2, p2){
|
||||
var d1 = {x : c1.x - p1.x, y: c1.y - p1.y}
|
||||
var d2 = {x : c2.x - p2.x, y: c2.y - p2.y}
|
||||
|
||||
function fitpts(p1, c1, c2, p2) {
|
||||
var d1 = { x: c1.x - p1.x, y: c1.y - p1.y }
|
||||
var d2 = { x: c2.x - p2.x, y: c2.y - p2.y }
|
||||
|
||||
var det = d2.x * d1.y - d2.y * d1.x;
|
||||
if(Math.abs(det) < 1e-6) return null;
|
||||
if (Math.abs(det) < 1e-6) return null;
|
||||
var u = ((p2.y - p1.y) * d2.x - (p2.x - p1.x) * d2.y) / det
|
||||
var v = ((p2.y - p1.y) * d1.x - (p2.x - p1.x) * d1.y) / det
|
||||
if(u <= 0 || v <= 0) return null;
|
||||
if (u <= 0 || v <= 0) return null;
|
||||
var mid = {
|
||||
x: p1.x + d1.x * u,
|
||||
y: p1.y + d1.y * u
|
||||
}
|
||||
return [mix(mid, p1, 1/3), mix(mid, p2, 1/3)]
|
||||
return [mix(mid, p1, 1 / 3), mix(mid, p2, 1 / 3)]
|
||||
};
|
||||
function distance(z1, z2){
|
||||
function distance(z1, z2) {
|
||||
return Math.sqrt((z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y))
|
||||
}
|
||||
function veryclose(z1, z2){
|
||||
function veryclose(z1, z2) {
|
||||
return (z1.x - z2.x) * (z1.x - z2.x) + (z1.y - z2.y) * (z1.y - z2.y) <= SMALL
|
||||
};
|
||||
function angleBetween(z1, z2, z3, z4){
|
||||
function angleBetween(z1, z2, z3, z4) {
|
||||
return (Math.atan2(z2.y - z1.y, z2.x - z1.x) - Math.atan2(z4.y - z3.y, z4.x - z3.x)) % Math.PI;
|
||||
};
|
||||
function pldistance(z1, z2, z){
|
||||
function pldistance(z1, z2, z) {
|
||||
return Math.abs((z2.y - z1.y) * z.x - (z2.x - z1.x) * z.y + z2.x * z1.y - z2.y * z1.x) / Math.sqrt((z2.x - z1.x) * (z2.x - z1.x) + (z2.y - z1.y) * (z2.y - z1.y))
|
||||
}
|
||||
function estimateSegments(z1, z2){
|
||||
function estimateSegments(z1, z2) {
|
||||
var hspan = Math.abs(z1.x - z2.x);
|
||||
var vspan = Math.abs(z1.y - z2.y);
|
||||
return hspan <= 5 * CROWD || vspan <= 5 * CROWD ? VERYCROWD : hspan <= 10 * LOOSE || vspan <= 10 * LOOSE ? CROWD : LOOSE;
|
||||
return hspan <= 5 * CROWD || vspan <= 5 * CROWD ? VERYCROWD : hspan <= 10 * LOOSE || vspan <= 10 * LOOSE ? CROWD : LOOSE;
|
||||
};
|
||||
function enoughRotate(bef, z0, z1, z2, aft, cleanMore, flagl, flagr){
|
||||
function enoughRotate(bef, z0, z1, z2, aft, cleanMore, flagl, flagr) {
|
||||
var angleRotatedBefore = Math.abs(angleBetween(bef.next || z1, bef, z1, z0));
|
||||
var angleRotatedAfter = Math.abs(angleBetween(aft.prev || z1, aft, z1, z2));
|
||||
var smallanglel = (cleanMore ? SMALLANGLE_CLEANMORE : SMALLANGLE) * (flagl && cleanMore ? 1 : 2);
|
||||
var smallangler = (cleanMore ? SMALLANGLE_CLEANMORE : SMALLANGLE) * (flagr && cleanMore ? 1 : 2);
|
||||
return !((angleRotatedBefore < smallanglel || angleRotatedBefore > Math.PI - smallanglel) && pldistance(z1, z0, bef) <= CLOSE && distance(z1, bef) <= 5 * CROWD
|
||||
|| (flagr ? false : (angleRotatedAfter < smallangler || angleRotatedAfter > Math.PI - smallangler) && pldistance(z1, z2, aft) <= CLOSE && distance(z1, aft) <= 5 * CROWD))
|
||||
|| (flagr ? false : (angleRotatedAfter < smallangler || angleRotatedAfter > Math.PI - smallangler) && pldistance(z1, z2, aft) <= CLOSE && distance(z1, aft) <= 5 * CROWD))
|
||||
};
|
||||
function fairify(scurve, gizmo, denseQ, cleanMore){
|
||||
for(var j = 0; j < scurve.length; j++){
|
||||
function fairify(scurve, gizmo, denseQ, cleanMore) {
|
||||
for (var j = 0; j < scurve.length; j++) {
|
||||
scurve[j] = Transform.untransform(gizmo, scurve[j])
|
||||
}
|
||||
var splitpoints = [scurve[0]];
|
||||
var last = scurve[0];
|
||||
for(var j = 1; j < scurve.length; j++){
|
||||
if(scurve[j].onCurve){
|
||||
for (var j = 1; j < scurve.length; j++) {
|
||||
if (scurve[j].onCurve) {
|
||||
splitpoints.push(last = scurve[j])
|
||||
} else if(scurve[j].cubic) {
|
||||
} else if (scurve[j].cubic) {
|
||||
var z1 = last, z2 = scurve[j], z3 = scurve[j + 1], z4 = scurve[j + 2];
|
||||
if(!(veryclose(z1, z2) && veryclose(z2, z3) && veryclose(z3, z4))) {
|
||||
if (!(veryclose(z1, z2) && veryclose(z2, z3) && veryclose(z3, z4))) {
|
||||
splitSegment(z1, z2, z3, z4, ANGLES, splitpoints);
|
||||
last = z4;
|
||||
}
|
||||
j += 2;
|
||||
} else {
|
||||
var z1 = last, zm = scurve[j], z4 = scurve[j + 1]
|
||||
if(!(veryclose(z1, zm) && veryclose(zm, z4))) {
|
||||
var z2 = mix(zm, z1, 1/3);
|
||||
var z3 = mix(zm, z4, 1/3);
|
||||
if (!(veryclose(z1, zm) && veryclose(zm, z4))) {
|
||||
var z2 = mix(zm, z1, 1 / 3);
|
||||
var z3 = mix(zm, z4, 1 / 3);
|
||||
splitSegment(z1, z2, z3, z4, ANGLES, splitpoints);
|
||||
last = z4;
|
||||
}
|
||||
|
@ -216,31 +216,31 @@ function fairify(scurve, gizmo, denseQ, cleanMore){
|
|||
}
|
||||
};
|
||||
// Mark corners and extremae
|
||||
for(var j = 1; j < splitpoints.length - 1; j++) {
|
||||
if(splitpoints[j].onCurve && !splitpoints[j - 1].onCurve){
|
||||
for (var j = 1; j < splitpoints.length - 1; j++) {
|
||||
if (splitpoints[j].onCurve && !splitpoints[j - 1].onCurve) {
|
||||
splitpoints[j].prev = splitpoints[j - 1]
|
||||
}
|
||||
if(splitpoints[j].onCurve && !splitpoints[j + 1].onCurve){
|
||||
if (splitpoints[j].onCurve && !splitpoints[j + 1].onCurve) {
|
||||
splitpoints[j].next = splitpoints[j + 1]
|
||||
}
|
||||
if(splitpoints[j].onCurve && !splitpoints[j - 1].onCurve && !splitpoints[j + 1].onCurve){
|
||||
if (splitpoints[j].onCurve && !splitpoints[j - 1].onCurve && !splitpoints[j + 1].onCurve) {
|
||||
var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1]
|
||||
if(cross(z1, z0, z2) < 1e-6 && dot(z1, z0, z2) < 0){
|
||||
if (cross(z1, z0, z2) < 1e-6 && dot(z1, z0, z2) < 0) {
|
||||
var angle0 = Math.atan2(z2.y - z0.y, z2.x - z0.x);
|
||||
var angle = Math.abs(angle0 / Math.PI * 2 % 1);
|
||||
if(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL){
|
||||
if (Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL) {
|
||||
z1.mark = true; // extrema
|
||||
z1.inflect = false;
|
||||
} else {
|
||||
var isInflection = false;
|
||||
if(j > 2 && j < splitpoints.length - 2){
|
||||
if (j > 2 && j < splitpoints.length - 2) {
|
||||
var za = bez3(z1, z0, splitpoints[j - 2], splitpoints[j - 3], SMALL);
|
||||
var zb = bez3(z1, z2, splitpoints[j + 2], splitpoints[j + 3], SMALL);
|
||||
var inflect = ((z0.y-z2.y)*(za.x-z0.x) + (z2.x-z0.x)*(za.y-z0.y))
|
||||
* ((z0.y-z2.y)*(zb.x-z0.x) + (z2.x-z0.x)*(zb.y-z0.y));
|
||||
if(inflect < 0) isInflection = true;
|
||||
var inflect = ((z0.y - z2.y) * (za.x - z0.x) + (z2.x - z0.x) * (za.y - z0.y))
|
||||
* ((z0.y - z2.y) * (zb.x - z0.x) + (z2.x - z0.x) * (zb.y - z0.y));
|
||||
if (inflect < 0) isInflection = true;
|
||||
};
|
||||
if(z1.inflect || isInflection) {
|
||||
if (z1.inflect || isInflection) {
|
||||
z1.mark = true;
|
||||
z1.asinflect = true;
|
||||
}
|
||||
|
@ -248,59 +248,59 @@ function fairify(scurve, gizmo, denseQ, cleanMore){
|
|||
} else {
|
||||
z1.mark = true; // also corner
|
||||
}
|
||||
} else if(splitpoints[j].onCurve) {
|
||||
} else if (splitpoints[j].onCurve) {
|
||||
splitpoints[j].mark = true // corner
|
||||
}
|
||||
};
|
||||
splitpoints[0].mark = splitpoints[splitpoints.length - 1].mark = true;
|
||||
// Mark cleanup inflections
|
||||
for(var pass = 0; pass < 2; pass++){
|
||||
for(var j = 1; j < splitpoints.length - 1; j++) {
|
||||
if(splitpoints[j].mark){
|
||||
for(var k = j - 1; k >= 0 && !splitpoints[k].mark; k--);
|
||||
for (var pass = 0; pass < 2; pass++) {
|
||||
for (var j = 1; j < splitpoints.length - 1; j++) {
|
||||
if (splitpoints[j].mark) {
|
||||
for (var k = j - 1; k >= 0 && !splitpoints[k].mark; k--);
|
||||
lastmark = splitpoints[k];
|
||||
for(var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
||||
for (var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
||||
nextmark = splitpoints[k];
|
||||
}
|
||||
if(splitpoints[j].mark && splitpoints[j].asinflect){
|
||||
if (splitpoints[j].mark && splitpoints[j].asinflect) {
|
||||
var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1]
|
||||
if(!(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark, cleanMore, lastmark.asinflect, nextmark.asinflect))) {
|
||||
if (!(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark, cleanMore, lastmark.asinflect, nextmark.asinflect))) {
|
||||
//z1.mark = false;
|
||||
z0.remove = z1.remove = z2.remove = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
for(var j = 0; j < splitpoints.length; j++) if(splitpoints[j].remove) splitpoints[j].mark = false;
|
||||
for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j].remove) splitpoints[j].mark = false;
|
||||
};
|
||||
// Mark diagonals
|
||||
var lastmark = splitpoints[0];
|
||||
for(var k = 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
||||
for (var k = 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
||||
var nextmark = splitpoints[k];
|
||||
var segments = estimateSegments(lastmark, nextmark);
|
||||
for(var j = 1; j < splitpoints.length - 1; j++) {
|
||||
if(splitpoints[j].mark){
|
||||
for (var j = 1; j < splitpoints.length - 1; j++) {
|
||||
if (splitpoints[j].mark) {
|
||||
lastmark = splitpoints[j];
|
||||
for(var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
||||
for (var k = j + 1; k < splitpoints.length && !splitpoints[k].mark; k++);
|
||||
nextmark = splitpoints[k];
|
||||
segments = estimateSegments(lastmark, nextmark);
|
||||
}
|
||||
if(splitpoints[j].onCurve && !splitpoints[j].mark){
|
||||
if (splitpoints[j].onCurve && !splitpoints[j].mark) {
|
||||
var z1 = splitpoints[j], z0 = splitpoints[j - 1], z2 = splitpoints[j + 1];
|
||||
var angle0 = Math.atan2(z2.y - z0.y, z2.x - z0.x);
|
||||
var angle = Math.abs(angle0 / Math.PI * segments % 1);
|
||||
var angleRotatedBefore = Math.abs(angleBetween(z1, lastmark, z1, z0));
|
||||
var angleRotatedAfter = Math.abs(angleBetween(z1, nextmark, z1, z2));
|
||||
if(!(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL)
|
||||
|| !(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark))){
|
||||
if (!(Math.abs(Math.abs(angle0) - Math.PI / 2) <= SMALL || angle <= SMALL || angle >= 1 - SMALL)
|
||||
|| !(denseQ || enoughRotate(lastmark, z0, z1, z2, nextmark))) {
|
||||
z1.remove = z0.remove = z2.remove = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
for(var j = 0; j < splitpoints.length; j++) if(splitpoints[j].onCurve && !splitpoints[j].remove && splitpoints[j + 1] && !splitpoints[j + 1].onCurve){
|
||||
for(var k = j + 2; k < splitpoints.length && splitpoints[k].remove; k++);
|
||||
if(k - j > 2){
|
||||
for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j].onCurve && !splitpoints[j].remove && splitpoints[j + 1] && !splitpoints[j + 1].onCurve) {
|
||||
for (var k = j + 2; k < splitpoints.length && splitpoints[k].remove; k++);
|
||||
if (k - j > 2) {
|
||||
var zs = fitpts(splitpoints[j], splitpoints[j + 1], splitpoints[k], splitpoints[k + 1]);
|
||||
if(zs){
|
||||
if (zs) {
|
||||
zs[0].onCurve = zs[1].onCurve = false;
|
||||
zs[0].cubic = zs[1].cubic = true;
|
||||
splitpoints[j + 1] = zs[0];
|
||||
|
@ -310,7 +310,7 @@ function fairify(scurve, gizmo, denseQ, cleanMore){
|
|||
j = k;
|
||||
};
|
||||
var ans = []
|
||||
for(var j = 0; j < splitpoints.length; j++) if(splitpoints[j] && !splitpoints[j].remove) {
|
||||
for (var j = 0; j < splitpoints.length; j++) if (splitpoints[j] && !splitpoints[j].remove) {
|
||||
ans.push(Transform.transformPoint(gizmo, splitpoints[j]))
|
||||
}
|
||||
return ans
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = function(xs, ys) {
|
||||
var i, length = xs.length;
|
||||
|
||||
|
||||
// Deal with length issues
|
||||
if (length != ys.length) { throw 'Need an equal count of xs and ys.'; }
|
||||
if (length === 0) { return function(x) { return 0; }; }
|
||||
|
@ -10,7 +10,7 @@ module.exports = function(xs, ys) {
|
|||
var result = +ys[0];
|
||||
return function(x) { return result; };
|
||||
}
|
||||
|
||||
|
||||
// Rearrange xs and ys so that xs is sorted
|
||||
var indexes = [];
|
||||
for (i = 0; i < length; i++) { indexes.push(i); }
|
||||
|
@ -20,53 +20,53 @@ module.exports = function(xs, ys) {
|
|||
xs = []; ys = [];
|
||||
// Impl: Unary plus properly converts values to numbers
|
||||
for (i = 0; i < length; i++) { xs.push(+oldXs[indexes[i]]); ys.push(+oldYs[indexes[i]]); }
|
||||
|
||||
|
||||
// Get consecutive differences and slopes
|
||||
var dys = [], dxs = [], ms = [];
|
||||
for (i = 0; i < length - 1; i++) {
|
||||
var dx = xs[i + 1] - xs[i], dy = ys[i + 1] - ys[i];
|
||||
dxs.push(dx); dys.push(dy); ms.push(dy/dx);
|
||||
dxs.push(dx); dys.push(dy); ms.push(dy / dx);
|
||||
}
|
||||
|
||||
|
||||
// Get degree-1 coefficients
|
||||
var c1s = [ms[0]];
|
||||
for (i = 0; i < dxs.length - 1; i++) {
|
||||
var m = ms[i], mNext = ms[i + 1];
|
||||
if (m*mNext <= 0) {
|
||||
if (m * mNext <= 0) {
|
||||
c1s.push(0);
|
||||
} else {
|
||||
var dx = dxs[i], dxNext = dxs[i + 1], common = dx + dxNext;
|
||||
c1s.push(3*common/((common + dxNext)/m + (common + dx)/mNext));
|
||||
c1s.push(3 * common / ((common + dxNext) / m + (common + dx) / mNext));
|
||||
}
|
||||
}
|
||||
c1s.push(ms[ms.length - 1]);
|
||||
|
||||
|
||||
// Get degree-2 and degree-3 coefficients
|
||||
var c2s = [], c3s = [];
|
||||
for (i = 0; i < c1s.length - 1; i++) {
|
||||
var c1 = c1s[i], m = ms[i], invDx = 1/dxs[i], common = c1 + c1s[i + 1] - m - m;
|
||||
c2s.push((m - c1 - common)*invDx); c3s.push(common*invDx*invDx);
|
||||
var c1 = c1s[i], m = ms[i], invDx = 1 / dxs[i], common = c1 + c1s[i + 1] - m - m;
|
||||
c2s.push((m - c1 - common) * invDx); c3s.push(common * invDx * invDx);
|
||||
}
|
||||
|
||||
|
||||
// Return interpolant function
|
||||
return function(x) {
|
||||
// The rightmost point in the dataset should give an exact result
|
||||
var i = xs.length - 1;
|
||||
if (x == xs[i]) { return ys[i]; }
|
||||
|
||||
|
||||
// Search for the interval x is in, returning the corresponding y if x is one of the original xs
|
||||
var low = 0, mid, high = c3s.length - 1;
|
||||
while (low <= high) {
|
||||
mid = Math.floor(0.5*(low + high));
|
||||
mid = Math.floor(0.5 * (low + high));
|
||||
var xHere = xs[mid];
|
||||
if (xHere < x) { low = mid + 1; }
|
||||
else if (xHere > x) { high = mid - 1; }
|
||||
else { return ys[mid]; }
|
||||
}
|
||||
i = Math.max(0, high);
|
||||
|
||||
|
||||
// Interpolate
|
||||
var diff = x - xs[i], diffSq = diff*diff;
|
||||
return ys[i] + c1s[i]*diff + c2s[i]*diffSq + c3s[i]*diff*diffSq;
|
||||
var diff = x - xs[i], diffSq = diff * diff;
|
||||
return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq;
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue
Block a user