This commit is contained in:
be5invis 2016-03-10 04:27:25 +08:00
parent 07360801e4
commit e8a1b9b305
4 changed files with 185 additions and 185 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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

View File

@ -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;
};
};