Skip to content

Instantly share code, notes, and snippets.

@be5invis
Last active June 28, 2020 16:58
Show Gist options
  • Save be5invis/3b7cb80680af4c7488b1 to your computer and use it in GitHub Desktop.
Save be5invis/3b7cb80680af4c7488b1 to your computer and use it in GitHub Desktop.
Automatic gridfit generator for Han characters.
var upm = 1000;
function Point(x, y, on, interpolated){
this.xori = x;
this.yori = y;
this.xtouch = x;
this.ytouch = y;
this.touched = false;
this.donttouch = false;
this.on = on;
this.interpolated = interpolated;
}
function Contour(){
this.points = []
this.ccw = false
}
Contour.prototype.stat = function(){
var xoris = this.points.map(function(p){ return p.xori })
var yoris = this.points.map(function(p){ return p.yori })
this.xmax = Math.max.apply(Math, xoris)
this.ymax = Math.max.apply(Math, yoris)
this.xmin = Math.min.apply(Math, xoris)
this.ymin = Math.min.apply(Math, yoris)
this.orient()
}
Contour.prototype.orient = function() {
// Findout PYmin
var jm = 0, ym = this.points[0].yori
for(var j = 0; j < this.points.length - 1; j++) if(this.points[j].yori < ym){
jm = j; ym = this.points[j].yori;
}
var p0 = this.points[(jm ? jm - 1 : this.points.length - 2)], p1 = this.points[jm], p2 = this.points[jm + 1];
var x = ((p0.xori - p1.xori) * (p2.yori - p1.yori) - (p0.yori - p1.yori) * (p2.xori - p1.xori))
if(x < 0) this.ccw = true;
else if(x === 0) this.ccw = p2.xori > p1.xori
}
var inPoly = function (point, vs) {
// ray-casting algorithm based on
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
var x = point.xori, y = point.yori;
var inside = false;
for (var i = 0, j = vs.length - 2; i < vs.length - 1; j = i++) {
var xi = vs[i].xori, yi = vs[i].yori;
var xj = vs[j].xori, yj = vs[j].yori;
var intersect = ((yi > y) != (yj > y))
&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
};
Contour.prototype.includes = function(that){
for(var j = 0; j < that.points.length - 1; j++){
if(!inPoly(that.points[j], this.points)) return false
}
return true;
}
function tx(x){ return x * 0.75 }
function ty(y){ return (0.95 * upm - y) * 0.75 }
function drawOutline(hDC, contours, xselector, yselector, controlcolor, strokecolor, vertexcolor, touchcolor) {
var nPoints = 0;
// Untouched
for(var j = 0; j < contours.length; j++){
var contour = contours[j];
// Layer 1 : Control outline
hDC.strokeStyle = controlcolor;
hDC.beginPath();
hDC.moveTo(tx(contour.points[0][xselector]), ty(contour.points[0][yselector]))
for(var k = 1; k < contour.points.length; k++){
hDC.lineTo(tx(contour.points[k][xselector]), ty(contour.points[k][yselector]))
}
hDC.closePath();
hDC.stroke()
// Layer 2 : Character outline
hDC.strokeStyle = strokecolor;
hDC.beginPath();
hDC.moveTo(tx(contour.points[0][xselector]), ty(contour.points[0][yselector]))
for(var k = 1; k < contour.points.length; k++){
if(contour.points[k].on) hDC.lineTo(tx(contour.points[k][xselector]), ty(contour.points[k][yselector]))
else {
hDC.quadraticCurveTo(
tx(contour.points[k][xselector]),
ty(contour.points[k][yselector]),
tx(contour.points[k + 1][xselector]),
ty(contour.points[k + 1][yselector]))
k += 1;
}
}
hDC.closePath();
hDC.stroke()
hDC.font = "10px 'Sayo UV DL'"
for(var k = 0; k < contour.points.length - 1; k++){
var point = contour.points[k]
if(!contour.points[k].interpolated) {
hDC.strokeStyle = hDC.fillStyle = point.touched ? touchcolor || vertexcolor : vertexcolor
hDC.fillText('' + (nPoints++), tx(point[xselector]), ty(point[yselector] + 1))
if(point.on) {
hDC.beginPath()
hDC.arc(tx(point[xselector]), ty(point[yselector]), 2, 0, Math.PI*2);
hDC.fill()
} else {
hDC.beginPath()
hDC.moveTo(tx(point[xselector] - 3), ty(point[yselector] - 3))
hDC.lineTo(tx(point[xselector] + 3), ty(point[yselector] + 3))
hDC.stroke()
hDC.beginPath()
hDC.moveTo(tx(point[xselector] - 3), ty(point[yselector] + 3))
hDC.lineTo(tx(point[xselector] + 3), ty(point[yselector] - 3))
hDC.stroke()
}
}
}
}
}
function drawContours(hDC, contours){
// Fill
hDC.fillStyle = 'rgba(0, 0, 0, 0.1)'
hDC.beginPath();
for(var j = 0; j < contours.length; j++){
var contour = contours[j];
// Layer 1 : Control outline
hDC.moveTo(tx(contour.points[0].xtouch), ty(contour.points[0].ytouch))
for(var k = 1; k < contour.points.length; k++){
if(contour.points[k].on) hDC.lineTo(tx(contour.points[k].xtouch), ty(contour.points[k].ytouch))
else {
hDC.quadraticCurveTo(tx(contour.points[k].xtouch), ty(contour.points[k].ytouch), tx(contour.points[k + 1].xtouch), ty(contour.points[k + 1].ytouch))
k += 1;
}
}
hDC.closePath();
}
hDC.fill('nonzero');
// Outlines
drawOutline(hDC, contours, 'xori', 'yori', 'rgba(0, 0, 0, 0.1)', 'rgba(0, 0, 0, 0.3)', '#aaa')
drawOutline(hDC, contours, 'xtouch', 'ytouch', 'rgba(0, 0, 255, 0.3)', 'black', 'green', 'red')
}
function drawArrowhead(hDC, x, y, angle, color){
hDC.save();
hDC.translate(x, y);
hDC.rotate(angle);
hDC.fillStyle = color;
hDC.beginPath();
hDC.moveTo(0, 0);
hDC.lineTo(-12, 6);
hDC.lineTo(-12, -6);
hDC.fill();
hDC.restore();
}
function drawLabel(hDC, x, y, text, color){
var textMetric = hDC.measureText(text);
hDC.fillStyle = "white";
hDC.strokeStyle = color;
hDC.fillRect(x - textMetric.width / 2 - 4, y - 8, textMetric.width + 8, 16);
hDC.strokeRect(x - textMetric.width / 2 - 4, y - 8, textMetric.width + 8, 16);
hDC.fillStyle = color;
hDC.fillText(text, x - textMetric.width / 2, y + 5)
}
function drawInstructions(hDC, instrs){
instrs = instrs.blueZoneAlignments.concat(instrs.roundings, instrs.stemTopAlignments, instrs.stemBottomAlignments, instrs.interpolations, instrs.instemAlignments)
var roundColor = "blue";
var ipColor = "green";
var alignColor = "black";
var labels = [];
for(var j = 0; j < instrs.length; j++) {
var instr = instrs[j];
switch(instr[0]) {
case "ROUNDUP": case "ROUNDDOWN": case "ROUNDUP2": {
var p = instr[1], align = instr[2];
var direction = (instr[0] === "ROUNDDOWN" ? -1 : 1);
hDC.fillStyle = "white";
hDC.strokeStyle = roundColor;
hDC.beginPath();
hDC.moveTo(tx(p.xtouch), ty(p.ytouch));
hDC.lineTo(tx(p.xtouch) - 6, ty(p.ytouch) + direction * 12);
hDC.lineTo(tx(p.xtouch) + 6, ty(p.ytouch) + direction * 12);
hDC.closePath();
hDC.fill();
hDC.stroke();
if(align) labels.push([hDC, tx(p.xtouch), ty(p.ytouch) + direction * 24, "blue:" + align.toFixed(0), roundColor]);
else if(instr[0] === "ROUNDUP2") labels.push([hDC, tx(p.xtouch), ty(p.ytouch) + direction * 24, "SHPIX y:1", roundColor])
break;
}
case "ALIGN0": case "ALIGNW": {
var from = instr[1], to = instr[2], width = instr[3];
hDC.strokeStyle = alignColor;
hDC.beginPath();
var mx = (tx(from.xtouch) + tx(to.xtouch)) / 2;
var my = (ty(from.ytouch) + ty(to.ytouch)) / 2
hDC.moveTo(tx(from.xtouch), ty(from.ytouch));
hDC.quadraticCurveTo(mx, my - 20, tx(to.xtouch), ty(to.ytouch))
hDC.stroke();
var angle = Math.atan((my - 20 - ty(to.ytouch)) / (mx - tx(to.xtouch)));
if(angle < 0) angle += Math.PI
drawArrowhead(hDC, tx(to.xtouch), ty(to.ytouch), angle, alignColor);
if(width) labels.push([hDC, mx, my - 12, "y:" + width.toFixed(0), alignColor])
break;
}
case "IP": {
var a = instr[1], b = instr[2], c = instr[3];
hDC.strokeStyle = ipColor;
hDC.beginPath();
hDC.moveTo(tx(a.xtouch), ty(a.ytouch));
var mx = (tx(a.xtouch) + tx(c.xtouch)) / 2;
var my = (ty(a.ytouch) + ty(c.ytouch)) / 2;
hDC.quadraticCurveTo(mx + 20, my, tx(c.xtouch), ty(c.ytouch))
var mx = (tx(b.xtouch) + tx(c.xtouch)) / 2;
var my = (ty(b.ytouch) + ty(c.ytouch)) / 2;
hDC.quadraticCurveTo(mx + 20, my, tx(b.xtouch), ty(b.ytouch))
hDC.stroke();
labels.push([hDC, tx(c.xtouch), ty(c.ytouch) + 15, "IP", ipColor])
}
}
}
for(var j = 0; j < labels.length; j++){
drawLabel.apply(null, labels[j])
}
}
function drawPreview(hDC, contours, ppem, dx, dy, SUPERSAMPLING){
function txp(x){ return (x / upm * ppem + dx) * 3 * SUPERSAMPLING}
function typ(y){ return ((- y) / upm * ppem + dy) * 3}
// Fill
hDC.fillStyle = 'black'
hDC.beginPath();
for(var j = 0; j < contours.length; j++){
var contour = contours[j];
// Layer 1 : Control outline
hDC.moveTo(txp(contour.points[0].xtouch), typ(contour.points[0].ytouch))
for(var k = 1; k < contour.points.length; k++){
if(contour.points[k].on) hDC.lineTo(txp(contour.points[k].xtouch), typ(contour.points[k].ytouch))
else {
hDC.quadraticCurveTo(txp(contour.points[k].xtouch), typ(contour.points[k].ytouch), txp(contour.points[k + 1].xtouch), typ(contour.points[k + 1].ytouch))
k += 1;
}
}
hDC.closePath();
}
hDC.fill('nonzero');
}
function drawPixelGrid(hDC, ppem){
var uppx = upm / ppem
// Vertical lines
for(var j = 0; j <= ppem; j++){
hDC.strokeStyle = '#ddd'
hDC.beginPath();
hDC.moveTo(tx(j * uppx), ty(upm))
hDC.lineTo(tx(j * uppx), ty(-upm))
hDC.stroke()
}
// Horizontal lines
for(var j = -ppem; j <= ppem; j++){
hDC.strokeStyle = j ? '#ddd' : 'black'
hDC.beginPath();
hDC.moveTo(tx(0), ty(j * uppx))
hDC.lineTo(tx(upm), ty(j * uppx))
hDC.stroke()
}
// Crosses
for(var j = -ppem + 0.5; j <= ppem; j++) {
for(var k = 0.5; k <= ppem; k++) {
hDC.strokeStyle = '#ddd'
hDC.beginPath();
hDC.moveTo(tx(k * uppx) - 2, ty(j * uppx) - 2)
hDC.lineTo(tx(k * uppx) + 2, ty(j * uppx) + 2)
hDC.stroke()
hDC.beginPath();
hDC.moveTo(tx(k * uppx) + 2, ty(j * uppx) - 2)
hDC.lineTo(tx(k * uppx) - 2, ty(j * uppx) + 2)
hDC.stroke()
}
}
}
function parseSFD(input){
var contours = [], currentContour = null
input = input.trim().split('\n');
for(var j = 0; j < input.length; j++){
var line = input[j].trim().split(/ +/);
if(line[2] === 'm'){
// Moveto
if(currentContour) contours.push(currentContour);
currentContour = new Contour();
currentContour.points.push(new Point(line[0] - 0, line[1] - 0, true))
} else if(line[2] === 'l' && currentContour){
// Lineto
currentContour.points.push(new Point(line[0] - 0, line[1] - 0, true))
} else if(line[6] === 'c' && currentContour){
// curveTo
currentContour.points.push(new Point(line[0] - 0, line[1] - 0, false))
currentContour.points.push(new Point(line[4] - 0, line[5] - 0, true, /^128,/.test(line[7])))
}
}
if(currentContour) contours.push(currentContour);
contours.forEach(function(c){ c.stat() })
return contours;
}
function overlapRatio(a, b){
var events = []
for(var j = 0; j < a.length; j++){
var low = Math.min(a[j][0].xori, a[j][a[j].length - 1].xori)
var high = Math.max(a[j][0].xori, a[j][a[j].length - 1].xori)
events.push({at: low, on: true, a: true})
events.push({at: high, on: false, a: true})
}
var probeb = new Array(upm);
for(var j = 0; j < b.length; j++){
var low = Math.min(b[j][0].xori, b[j][b[j].length - 1].xori)
var high = Math.max(b[j][0].xori, b[j][b[j].length - 1].xori)
events.push({at: low, on: true, a: false})
events.push({at: high, on: false, a: false})
}
events.sort(function(p, q){ return p.at - q.at })
var len = 0, la = 0, lb = 0;
var st = 0, sa = 0, sb = 0;
var ac = 0;
var bc = 0;
for(var j = 0; j < events.length; j++){
var e = events[j]
var intersectBefore = ac * bc;
var ab = ac, bb = bc;
if(e.a) { if(e.on) ac += 1; else ac -= 1 }
else { if(e.on) bc += 1; else bc -= 1 }
if(ac * bc && !intersectBefore) st = e.at;
if(!(ac * bc) && intersectBefore) len += e.at - st;
if(ac && !ab) sa = e.at;
if(!ac && ab) la += e.at - sa;
if(bc && !bb) sb += e.at;
if(!bc && bb) lb += e.at - sb;
}
return len / Math.max(la, lb)
}
function enoughOverlapBetweenSegments(a, b, ratio){
return overlapRatio(a, b) >= ratio
}
function enoughOverlapBetweenStems(a, b){
return enoughOverlapBetweenSegments(a.low, b.low, MIN_STEM_OVERLAP_RATIO)
|| enoughOverlapBetweenSegments(a.high, b.high, MIN_STEM_OVERLAP_RATIO)
|| enoughOverlapBetweenSegments(a.low, b.high, MIN_STEM_OVERLAP_RATIO)
|| enoughOverlapBetweenSegments(a.high, b.low, MIN_STEM_OVERLAP_RATIO)
}
var blueFuzz = 15
var MIN_OVERLAP_RATIO = 0.3;
var MIN_STEM_OVERLAP_RATIO = 0.2;
var Y_FUZZ = 3
var SLOPE_FUZZ = 0.02
function findStems(contours, MIN_STEM_WIDTH, MAX_STEM_WIDTH) {
function statGlyph(contours){
var points = []
points = points.concat.apply(points, contours.map(function(c){ return c.points }));
var ys = points.map(function(p){ return p.yori })
var xs = points.map(function(p){ return p.xori })
return {
xmax: Math.max.apply(Math, xs),
ymax: Math.max.apply(Math, ys),
xmin: Math.min.apply(Math, xs),
ymin: Math.min.apply(Math, ys)
}
}
function rootof(radical){
if(radical.root === radical) return radical;
else {
// Path compression
var r = rootof(radical.root);
radical.root = r;
return r;
}
}
function findRadicals(contours){
var radicals = []
for(var j = 0; j < contours.length; j++){
radicals[j] = {
contour: contours[j],
parts: []
}
radicals[j].root = radicals[j];
}
// Merge disjoint sets
for(var j = 0; j < radicals.length; j++){
for(var k = 0; k < radicals.length; k++){
if(rootof(radicals[j]).contour.includes(radicals[k].contour)) {
radicals[k].root = radicals[j].root;
}
}
};
for(var j = 0; j < radicals.length; j++){
rootof(radicals[j]).parts.push(radicals[j].contour)
};
return radicals.filter(function(r){ return r.parts.length }).map(function(r){ return { parts: r.parts, outline: r.contour } })
}
function findHorizontalSegments(radicals){
var segments = []
for(var r = 0; r < radicals.length; r++) {
radicals[r].mergedSegments = []
for(var j = 0; j < radicals[r].parts.length; j++){
var contour = radicals[r].parts[j];
var lastPoint = contour.points[0]
var segment = [lastPoint];
segment.radical = r;
for(var k = 1; k < contour.points.length - 1; k++) if(!contour.points[k].interpolated) {
if(Math.abs((contour.points[k].yori - lastPoint.yori) / (contour.points[k].xori - lastPoint.xori)) <= SLOPE_FUZZ) {
segment.push(contour.points[k])
} else {
if(segment.length > 1) segments.push(segment)
lastPoint = contour.points[k];
segment = [lastPoint]
segment.radical = r;
}
};
if(Math.abs((contour.points[0].yori - lastPoint.yori) / (contour.points[0].xori - lastPoint.xori)) <= SLOPE_FUZZ) {
segment.push(contour.points[0])
segment.push(contour.points[contour.points.length - 1])
}
if(segment.length > 1) segments.push(segment)
}
}
segments = segments.sort(function(p, q){ return p.xori - q.xori })
for(var j = 0; j < segments.length; j++) if(segments[j]){
var pivot = [segments[j]];
var pivotRadical = segments[j].radical;
var orientation = pivot[0][1].xori > pivot[0][0].xori
segments[j] = null;
for(var k = j + 1; k < segments.length; k++) if(segments[k] && Math.abs(segments[k][0].yori - pivot[0][0].yori) <= Y_FUZZ && segments[k].radical === pivotRadical && orientation === (segments[k][1].xori > segments[k][0].xori)){
var r = pivot.radical;
pivot.push(segments[k])
segments[k] = null;
}
radicals[pivotRadical].mergedSegments.push(pivot.sort(function(s1, s2){
return orientation ? s1[0].xori - s2[0].xori : s2[0].xori - s1[0].xori}))
}
}
function stemAtRadicalTop(stem, radical){
var a0 = stem.low[0][0].xori, az = stem.low[stem.low.length - 1][stem.low[stem.low.length - 1].length - 1].xori
var b0 = stem.high[0][0].xori, bz = stem.high[stem.high.length - 1][stem.high[stem.high.length - 1].length - 1].xori
var xmin = Math.min(a0, b0, az, bz), xmax = Math.max(a0, b0, az, bz);
for(var j = 0; j < radical.parts.length; j++) for(var k = 0; k < radical.parts[j].points.length; k++) {
var point = radical.parts[j].points[k];
if(point.yori > stem.yori + blueFuzz && point.xori <= xmax && point.xori >= xmin) return false;
}
//return stem.yori >= radical.outline.ymax - MAX_STEM_WIDTH;
return true
}
function stemSegments(radicals){
var stems = [];
for(var r = 0; r < radicals.length; r++) {
var radicalStems = [];
var segs = radicals[r].mergedSegments.sort(function(a, b){ return a[0][0].yori - b[0][0].yori});
var ori = radicals[r].outline.ccw;
// We stem segments bottom-up.
for(var j = 0; j < segs.length; j++) if(segs[j] && ori === (segs[j][0][0].xori < segs[j][0][segs[j][0].length - 1].xori)) {
var stem = {low: segs[j]};
for(var k = j + 1; k < segs.length; k++) if(segs[k] && overlapRatio(segs[j], segs[k]) >= MIN_OVERLAP_RATIO) {
if(ori !== (segs[k][0][0].xori < segs[k][0][segs[k][0].length - 1].xori)
&& segs[k][0][0].yori - segs[j][0][0].yori <= MAX_STEM_WIDTH
&& segs[k][0][0].yori - segs[j][0][0].yori >= MIN_STEM_WIDTH) {
// A stem is found
stem.high = segs[k]
stem.yori = stem.high[0][0].yori
stem.width = Math.abs(segs[k][0][0].yori - segs[j][0][0].yori)
stem.atRadicalTop = stemAtRadicalTop(stem, radicals[r])
stem.atGlyphTop = stem.high[0][0].yori >= stats.ymax;
stem.belongRadical = radicals[r];
segs[j] = segs[k] = null;
radicalStems.push(stem);
}
break;
}
};
for(var k = 0; k < radicalStems.length; k++) {
for(var j = 0; j < radicalStems.length; j++) {
if(enoughOverlapBetweenStems(radicalStems[j], radicalStems[k]) && radicalStems[j].yori > radicalStems[k].yori)
radicalStems[k].atRadicalTop = radicalStems[k].atGlyphTop = false;
}
}
stems = stems.concat(radicalStems)
}
return stems;
}
var radicals = findRadicals(contours);
var stats = statGlyph(contours);
findHorizontalSegments(radicals);
var stems = stemSegments(radicals);
return stems;
}
function autohint(contours, stems, ppem){
var uppx = upm / ppem;
var glyfBottom = -round(0.075 * upm)
var glyfTop = round(0.84 * upm)
function round(xori){ return Math.round(xori / upm * ppem) / ppem * upm }
function roundDown(xori){ return Math.floor(xori / upm * ppem) / ppem * upm }
function roundUp(xori){ return Math.ceil(xori / upm * ppem) / ppem * upm }
function roundDownStem(stem){
stem.roundMethod = -1; // Positive for round up, negative for round down
stem.ytouch = roundDown(stem.yori);
stem.deltaY = 0
}
function roundUpStem(stem){
stem.roundMethod = 1;
stem.ytouch = roundUp(stem.yori);
stem.deltaY = 0
}
function alignStem(stem, that){
while(that.alignTo) that = that.alignTo;
stem.roundMethod = 0;
stem.alignTo = that;
stem.ytouch = that.ytouch;
}
var WIDTH_FACTOR_X = 2
var MIN_ADJUST_PPEM = 12
var MAX_ADJUST_PPEM = 32
function clamp(x){ return Math.min(1, Math.max(0, x)) }
function calculateWidth(w){
if(ppem < 20) return uppx;
if(w < uppx) return uppx;
else if (w < 2 * uppx) return uppx * WIDTH_FACTOR_X
* (w / uppx / WIDTH_FACTOR_X + clamp((ppem - MIN_ADJUST_PPEM) / (MAX_ADJUST_PPEM - MIN_ADJUST_PPEM)) * (1 - w / uppx / WIDTH_FACTOR_X));
else return w;
}
function initStemTouches(stems){
for(var j = 0; j < stems.length; j++) {
var w = calculateWidth(stems[j].width);
if(w < 1.9 * uppx) w = uppx
// stems[j].touchwidth = w;
stems[j].touchwidth = uppx;
stems[j].alignTo = null;
roundDownStem(stems[j])
if(stems[j].ytouch - roundUp(w) < glyfBottom){
roundUpStem(stems[j])
}
}
}
var COLLISION_FUZZ = 1.04;
var HIGHLY_COLLISION_FUZZ = 0.3;
function collideWith(stems, transitions, j, k){
return transitions[j][k] && (stems[j].ytouch > stems[k].ytouch
? stems[j].ytouch - stems[k].ytouch <= stems[j].touchwidth * COLLISION_FUZZ
: stems[k].ytouch - stems[j].ytouch <= stems[k].touchwidth * COLLISION_FUZZ)
}
function highlyCollideWith(stems, transitions, j, k){
return transitions[j][k] && (stems[j].ytouch > stems[k].ytouch
? stems[j].ytouch - stems[k].ytouch <= stems[j].touchwidth * HIGHLY_COLLISION_FUZZ
: stems[k].ytouch - stems[j].ytouch <= stems[k].touchwidth * HIGHLY_COLLISION_FUZZ)
}
function spaceBelow(stems, transitions, k, bottom){
var space = stems[k].ytouch - stems[k].touchwidth + bottom;
for(var j = k - 1; j >= 0; j--){
if(transitions[j][k] && Math.abs(stems[k].ytouch - stems[j].ytouch) - stems[k].touchwidth < space)
space = stems[k].ytouch - stems[j].ytouch - stems[k].touchwidth
}
return space;
}
function spaceAbove(stems, transitions, k, top){
var space = top - stems[k].ytouch;
for(var j = k + 1; j < stems.length; j++){
if(transitions[k][j] && Math.abs(stems[j].ytouch - stems[k].ytouch) - stems[j].touchwidth < space)
space = stems[j].ytouch - stems[k].ytouch - stems[j].touchwidth
}
return space;
}
function canBeAdjustedUp(stems, transitions, k, distance){
for(var j = k + 1; j < stems.length; j++){
if(transitions[j][k] && Math.abs(stems[j].ytouch - stems[k].ytouch) - stems[j].touchwidth <= distance)
return false
}
return true;
}
function adjustDownward(stems, transitions, k, bottom){
var s = spaceBelow(stems, transitions, k, bottom);
if(s >= 2 * uppx || s < 0.3 * uppx) {
// There is enough space below stem k, just bring it downward
if(stems[k].roundMethod === 1 && stems[k].ytouch > bottom) {
roundDownStem(stems[k]);
return true;
}
}
for(var j = 0; j < k; j++){
if(!adjustDownward(stems, transitions, j, bottom)) return false;
}
return false;
}
// Collision resolving
function uncollide(stems){
// In this procedure we move some segment stems to resolve collisions between them.
// A "collision" means that two stems meet togther after gridfitting.
// We will merge some of these stems to preserve the outfit of glyph while leaving
// space between strokes;
if(!stems.length) return;
stems = stems.sort(function(a, b){ return a.yori - b.yori });
var transitions = [];
for(var j = 0; j < stems.length; j++){
transitions[j] = []
for(var k = 0; k < stems.length; k++){
transitions[j][k] = enoughOverlapBetweenStems(stems[j], stems[k])
}
}
// Step 0 : Adjust top and bottom stems
var ytouchmin0 = stems[0].ytouch;
var ytouchmin = ytouchmin0;
for(var j = 0; j < stems.length; j++) {
if(stems[j].roundMethod === -1 && stems[j].ytouch === ytouchmin0 && stems[j].ytouch - stems[j].touchwidth >= -1
&& stems[j].yori - stems[j].ytouch >= 0.5 * uppx) {
ytouchmin = ytouchmin0 + uppx;
roundUpStem(stems[j])
}
}
// Avoid stem merging at the bottom
for(var j = 0; j < stems.length; j++) if(stems[j].ytouch === ytouchmin) for(var k = 0; k < j; k++) {
if(transitions[j][k] && stems[j].roundMethod === -1) roundUpStem(stems[j]);
}
var ytouchmax0 = stems[stems.length - 1].ytouch;
var ytouchmax = ytouchmax0;
var hasStemAtGlyphTop = stems[stems.length - 1].atGlyphTop
for(var j = stems.length - 1; j >= 0; j--) {
var stem = stems[j];
if(stem.ytouch === ytouchmax0) {
var canAdjustUpToGlyphTop = stem.ytouch < glyfTop - blueFuzz && stem.ytouch >= glyfTop - uppx - 1
if(stem.roundMethod === -1 && stem.atRadicalTop
? stem.ytouch < glyfTop - blueFuzz && stem.yori - stem.ytouch >= 0.47 * uppx
: stem.ytouch + uppx < glyfTop - blueFuzz && stem.yori - stem.ytouch >= 0.47 * uppx) {
ytouchmax = ytouchmax0 + uppx;
roundUpStem(stem);
};
var canAdjustUpToGlyphTop = stem.ytouch < glyfTop - blueFuzz && stem.ytouch >= glyfTop - uppx - 1
if(hasStemAtGlyphTop && stem.atRadicalTop && canAdjustUpToGlyphTop && stem.yori - stem.ytouch >= 0.2 * uppx) {
if(stem.roundMethod === -1) {
ytouchmax = ytouchmax0 + uppx;
roundUpStem(stem);
}
};
if(stem.atRadicalTop && canAdjustUpToGlyphTop) {
stem.allowMoveUpward = true
}
}
}
// Step 1: Uncollide
// We will perform stem movement using greedy method
// Not always works but okay for most characters
for(var j = 0; j < stems.length; j++){
debugger;
if(stems[j].ytouch <= ytouchmin) {
// Stems[j] is a bottom stem
// DON'T MOVE IT
} else if(stems[j].ytouch >= ytouchmax) {
// Stems[j] is a top stem
// It should not be moved, but we can uncollide stems below it.
for(var k = j - 1; k >= 0; k--) if(collideWith(stems, transitions, j, k)) {
if(highlyCollideWith(stems, transitions, j, k)) {
alignStem(stems[k], stems[j])
continue
}
var r = adjustDownward(stems, transitions, k, ytouchmin)
if(r) continue;
if(stems[j].roundMethod === -1 && stems[j].allowMoveUpward) {
roundUpStem(stems[j]);
break;
}
}
} else {
// Stems[j] is a middle stem
for(var k = j - 1; k >= 0; k--) if(collideWith(stems, transitions, j, k)) {
if(highlyCollideWith(stems, transitions, j, k)) {
alignStem(stems[j], stems[k])
break;
}
var r = adjustDownward(stems, transitions, k, ytouchmin);
if(r) continue;
if(stems[j].roundMethod === -1) {
roundUpStem(stems[j]);
break;
}
}
}
};
// Stem 2 : Alignment reduction
// In this step we will move aligned stems two pixels upward there is enough space.
for(var j = stems.length - 1; j >= 0; j--) if(stems[j].ytouch <= Math.min(ytouchmax - 1, glyfTop - 2 * uppx) && stems[j].ytouch > ytouchmin) {
if(canBeAdjustedUp(stems, transitions, j, 2.8 * uppx) && stems[j].roundMethod === 0) {
stems[j].alignTo = null;
roundUpStem(stems[j]);
stems[j].roundMethod = 2;
stems[j].ytouch += uppx;
}
}
// Step 3 : In-radical alignment reduction
// Once we find a in-radical alignment......
for(var j = stems.length - 1; j >= 0; j--) if(stems[j].atRadicalTop && stems[j].roundMethod === 0 && stems[j].belongRadical === stems[j].alignTo.belongRadical) {
// Find a proper stem above it......
for(var k = j; k < stems.length; k++) if(stems[k].ytouch === stems[j].ytouch + 2 * uppx && stems[k].belongRadical !== stems[j].belongRadical) {
// And align them.
alignStem(stems[j], stems[k]);
break;
}
}
// Step 4 : Position Rebalance
// Stems are rounded down by default, may cause improper movements
// Therefore we bring them upward one pixel when there is enough space
// above.
for(var j = stems.length - 1; j >= 0; j--) if(stems[j].ytouch < ytouchmax && stems[j].ytouch > ytouchmin) {
if(canBeAdjustedUp(stems, transitions, j, 1.8 * uppx) && stems[j].yori - stems[j].ytouch > 0.5 * uppx) {
if(stems[j].roundMethod === -1) { roundUpStem(stems[j]) }
}
}
// Stem 5 : Stem Width Allocation
// In this step we will adjust stem width when there is enough space below the stem.
for(var j = stems.length - 1; j >= 0; j--) {
debugger;
var sb = spaceBelow(stems, transitions, j, ytouchmin + uppx * 3);
var sa = spaceAbove(stems, transitions, j, ytouchmax + uppx * 3);
var w = Math.round(Math.min(stems[j].touchwidth + sa + sb - 2 * uppx, calculateWidth(stems[j].width)) / uppx) * uppx;
if(w <= uppx) continue;
if(sb >= 1.75 * uppx && stems[j].ytouch - w >= glyfBottom - 1) {
stems[j].touchwidth = w;
} else if (sa > 1.6 * uppx && stems[j].roundMethod === -1 && stems[j].ytouch - w + uppx >= glyfBottom - 1) {
roundUpStem(stems[j]);
stems[j].touchwidth = w
}
}
}
var instructions = {
roundings : [],
stemTopAlignments: [],
stemBottomAlignments: [],
blueZoneAlignments: [],
instemAlignments: [],
interpolations: []
};
// Touching procedure
function touchStemPoints(stems){
for(var j = 0; j < stems.length; j++){
var stem = stems[j], w = stem.touchwidth;
// Top edge of a stem
for(var k = 0; k < stem.high.length; k++) for(var p = 0; p < stem.high[k].length; p++) {
if(p === 0) {
stem.high[k][p].ytouch = stem.ytouch
stem.high[k][p].touched = true;
stem.high[k][p].keypoint = true;
if(k === 0) {
if(stem.roundMethod === 1) instructions.roundings.push(['ROUNDUP', stem.high[0][0]])
else if(stem.roundMethod === -1) instructions.roundings.push(['ROUNDDOWN', stem.high[0][0]])
else if(stem.roundMethod === 2) instructions.roundings.push(['ROUNDUP2', stem.high[0][0]])
else if(stem.alignTo) instructions.stemTopAlignments.push(['ALIGN0', stem.alignTo.high[0][0], stem.high[0][0]])
} else {
instructions.instemAlignments.push(['ALIGN0', stem.high[0][0], stem.high[k][0]])
}
} else {
stem.high[k][p].donttouch = true;
}
}
for(var k = 0; k < stem.low.length; k++) for(var p = 0; p < stem.low[k].length; p++) {
if(p === 0) {
stem.low[k][p].ytouch = stem.ytouch - w;
stem.low[k][p].touched = true;
stem.low[k][p].keypoint = true;
if(k === 0) {
instructions.stemBottomAlignments.push(['ALIGNW', stem.high[0][0], stem.low[0][0], stem.touchwidth])
} else {
instructions.instemAlignments.push(['ALIGN0', stem.low[0][0], stem.low[k][0]])
}
} else {
stem.low[k][p].donttouch = true;
}
}
}
}
function touchBlueZonePoints(contours) {
function flushBottom(seq){
var mink = 0;
for(var s = 0; s < seq.length; s++) if(seq[s].yori < seq[mink].yori) mink = s;
seq[mink].touched = true;
seq[mink].ytouch = glyfBottom;
seq[mink].keypoint = true;
instructions.blueZoneAlignments.push([seq[mink].yori > seq[mink].ytouch ? "ROUNDDOWN" : "ROUNDUP", seq[mink], glyfBottom])
}
function flushTop(seq){
var mink = 0;
for(var s = 0; s < seq.length; s++) if(seq[s].yori > seq[mink].yori) mink = s;
seq[mink].touched = true;
seq[mink].ytouch = glyfTop;
seq[mink].keypoint = true;
instructions.blueZoneAlignments.push([seq[mink].yori > seq[mink].ytouch ? "ROUNDDOWN" : "ROUNDUP", seq[mink], glyfTop])
}
for(var j = 0; j < contours.length; j++) {
var seq = []
for(var k = 0; k < contours[j].points.length; k++){
var point = contours[j].points[k];
if(point.yori <= -65){
if(!point.touched && !point.donttouch) seq.push(point);
} else if(seq.length){
flushBottom(seq); seq = [];
}
}
if(seq.length){
flushBottom(seq); seq = [];
}
var seq = []
for(var k = 0; k < contours[j].points.length; k++){
var point = contours[j].points[k];
if(point.yori >= 825){
if(!point.touched && !point.donttouch) seq.push(point);
} else if(seq.length){
flushTop(seq); seq = [];
}
}
if(seq.length){
flushTop(seq); seq = [];
}
}
}
function interpolate(a, b, c, touch){
c.touched = touch;
if(c.yori <= a.yori) c.ytouch = c.yori - a.yori + a.ytouch;
else if(c.yori >= b.yori) c.ytouch = c.yori - b.yori + b.ytouch;
else c.ytouch = (c.yori - a.yori) / (b.yori - a.yori) * (b.ytouch - a.ytouch) + a.ytouch;
if(touch) {
instructions.interpolations.push(['IP', a, b, c])
}
}
function interpolatedUntouchedTopBottomPoints(contours){
var touchedPoints = [];
for(var j = 0; j < contours.length; j++) for(var k = 0; k < contours[j].points.length; k++) if(contours[j].points[k].touched && contours[j].points.keypoint) {
touchedPoints.push(contours[j].points[k]);
}
touchedPoints = touchedPoints.sort(function(p, q){ return p.yori - q.yori })
out: for(var j = 0; j < contours.length; j++) {
var localtopp = null, localbottomp = null;
for(var k = 0; k < contours[j].points.length; k++) {
var point = contours[j].points[k];
if(!localtopp || point.yori > localtopp.yori) localtopp = point;
if(!localbottomp || point.yori < localbottomp.yori) localbottomp = point;
}
if(!localtopp.touched && !localtopp.donttouch) for(var k = 1; k < touchedPoints.length; k++) {
if(touchedPoints[k].yori > localtopp.yori && touchedPoints[k - 1].yori <= localtopp.yori) {
interpolate(touchedPoints[k], touchedPoints[k - 1], localtopp, true);
break;
}
}
if(!localbottomp.touched && !localbottomp.donttouch) for(var k = 1; k < touchedPoints.length; k++) {
if(touchedPoints[k].yori > localbottomp.yori && touchedPoints[k - 1].yori <= localbottomp.yori) {
interpolate(touchedPoints[k], touchedPoints[k - 1], localbottomp, true);
break;
}
}
}
}
// IUPy interpolates untouched points just like TT instructions.
function IUPy(contours){
for(var j = 0; j < contours.length; j++){
var contour = contours[j];
var k = 0;
while(k < contour.points.length && !contour.points[k].touched) k++;
if(contour.points[k]) {
// Found a touched point in contour
var kleft = k, k0 = k;
var untoucheds = []
for(var k = 0; k <= contour.points.length; k++){
var ki = (k + k0) % contour.points.length;
if(contour.points[ki].touched){
var pleft = contour.points[kleft];
var pright = contour.points[ki];
var lower = pleft.yori < pright.yori ? pleft : pright
var higher = pleft.yori < pright.yori ? pright : pleft
for(var w = 0; w < untoucheds.length; w++) interpolate(lower, higher, untoucheds[w])
untoucheds = []
kleft = ki;
} else {
untoucheds.push(contour.points[ki])
}
}
}
}
}
function untouchAll(contours) {
for(var j = 0; j < contours.length; j++) for(var k = 0; k < contours[j].points.length; k++) {
contours[j].points[k].touched = false;
contours[j].points[k].donttouch = false;
contours[j].points[k].ytouch = contours[j].points[k].yori;
}
}
untouchAll(contours);
initStemTouches(stems);
uncollide(stems);
touchStemPoints(stems);
touchBlueZonePoints(contours);
interpolatedUntouchedTopBottomPoints(contours);
IUPy(contours);
return {
contours: contours,
instructions: instructions
}
}
<!DOCTYPE html>
<div id="panel"><textarea id="input">
307 716 m 1,0,-1
263 661 l 1,1,2
237 687 237 687 187.5 719.5 c 128,-1,3
138 752 138 752 92 774 c 1,4,-1
136 827 l 1,5,6
251 775 251 775 307 721 c 1,7,-1
307 750 l 1,8,-1
467 750 l 1,9,-1
467 836 l 1,10,-1
535 836 l 1,11,-1
535 750 l 1,12,-1
700 750 l 1,13,-1
700 836 l 1,14,-1
770 836 l 1,15,-1
770 750 l 1,16,-1
943 750 l 1,17,-1
943 687 l 1,18,-1
770 687 l 1,19,-1
770 616 l 1,20,-1
700 616 l 1,21,-1
700 687 l 1,22,-1
535 687 l 1,23,-1
535 616 l 1,24,-1
467 616 l 1,25,-1
467 687 l 1,26,-1
307 687 l 1,27,-1
307 716 l 1,0,-1
259 462 m 1,28,-1
215 401 l 1,29,30
156 456 156 456 42 508 c 1,31,-1
85 562 l 1,32,33
199 515 199 515 259 462 c 1,28,-1
232 304 m 1,34,-1
289 256 l 1,35,36
212 76 212 76 133 -65 c 1,37,-1
69 -16 l 1,38,39
147 111 147 111 232 304 c 1,34,-1
428 431 m 1,40,-1
582 431 l 1,41,-1
582 538 l 1,42,-1
428 538 l 1,43,-1
428 431 l 1,40,-1
815 431 m 1,44,-1
815 538 l 1,45,-1
655 538 l 1,46,-1
655 431 l 1,47,-1
815 431 l 1,44,-1
950 124 m 1,48,-1
693 124 l 1,49,50
777 13 777 13 962 -18 c 1,51,52
932 -46 932 -46 915 -82 c 1,53,54
702 -35 702 -35 616 124 c 1,55,-1
615 124 l 1,56,57
541 -10 541 -10 322 -75 c 1,58,59
302 -41 302 -41 277 -18 c 1,60,61
461 31 461 31 531 124 c 1,62,-1
296 124 l 1,63,-1
296 184 l 1,64,-1
563 184 l 1,65,66
576 215 576 215 579 252 c 1,67,-1
338 252 l 1,68,-1
338 312 l 1,69,-1
582 312 l 1,70,-1
582 376 l 1,71,-1
362 376 l 1,72,-1
362 594 l 1,73,-1
884 594 l 1,74,-1
884 376 l 1,75,-1
655 376 l 1,76,-1
655 312 l 1,77,-1
908 312 l 1,78,-1
908 252 l 1,79,-1
652 252 l 1,80,81
649 217 649 217 639 184 c 1,82,-1
950 184 l 1,83,-1
950 124 l 1,48,-1
//
372 800 m 1,0,-1
372 740 l 1,1,-1
97 740 l 1,2,-1
97 800 l 1,3,-1
372 800 l 1,0,-1
313 197 m 1,4,-1
155 197 l 1,5,-1
155 29 l 1,6,-1
313 29 l 1,7,-1
313 197 l 1,4,-1
377 258 m 1,8,-1
377 -33 l 1,9,-1
155 -33 l 1,10,-1
155 -77 l 1,11,-1
90 -77 l 1,12,-1
90 258 l 1,13,-1
377 258 l 1,8,-1
91 334 m 1,14,-1
91 394 l 1,15,-1
375 394 l 1,16,-1
375 334 l 1,17,-1
91 334 l 1,14,-1
375 530 m 1,18,-1
375 471 l 1,19,-1
91 471 l 1,20,-1
91 530 l 1,21,-1
375 530 l 1,18,-1
45 606 m 1,22,-1
45 667 l 1,23,-1
408 667 l 1,24,-1
408 606 l 1,25,-1
45 606 l 1,22,-1
827 213 m 1,26,-1
549 213 l 1,27,-1
549 36 l 1,28,-1
827 36 l 1,29,-1
827 213 l 1,26,-1
479 -80 m 1,30,-1
479 280 l 1,31,-1
900 280 l 1,32,-1
900 -76 l 1,33,-1
827 -76 l 1,34,-1
827 -31 l 1,35,-1
549 -31 l 1,36,-1
549 -80 l 1,37,-1
479 -80 l 1,30,-1
805 567 m 1,38,-1
636 567 l 1,39,40
624 498 624 498 608 422 c 1,41,-1
805 422 l 1,42,-1
805 567 l 1,38,-1
876 422 m 1,43,-1
961 422 l 1,44,-1
961 355 l 1,45,-1
409 355 l 1,46,-1
409 422 l 1,47,-1
536 422 l 1,48,49
546 468 546 468 563 567 c 1,50,-1
453 567 l 1,51,-1
453 631 l 1,52,-1
574 631 l 1,53,54
575 635 575 635 590 733 c 1,55,-1
438 733 l 1,56,-1
438 798 l 1,57,-1
928 798 l 1,58,-1
928 733 l 1,59,-1
664 733 l 1,60,61
660 709 660 709 647 631 c 1,62,-1
876 631 l 1,63,-1
876 422 l 1,43,-1
//
919 644 m 1,0,-1
859 601 l 1,1,2
806 681 806 681 702 771 c 1,3,-1
759 810 l 1,4,5
866 720 866 720 919 644 c 1,0,-1
829 426 m 1,6,-1
895 399 l 1,7,8
828 273 828 273 721 164 c 1,9,10
782 -5 782 -5 854 -5 c 0,11,12
874 -5 874 -5 884 36 c 128,-1,13
894 77 894 77 899 174 c 1,14,15
928 146 928 146 962 134 c 1,16,17
952 10 952 10 927.5 -34.5 c 128,-1,18
903 -79 903 -79 847 -79 c 0,19,20
736 -79 736 -79 662 108 c 1,21,22
562 19 562 19 451 -39 c 1,23,24
431 -6 431 -6 400 20 c 1,25,26
532 84 532 84 637 182 c 1,27,28
601 307 601 307 581 472 c 1,29,-1
346 472 l 1,30,-1
346 312 l 1,31,32
376 318 376 318 436 331 c 128,-1,33
496 344 496 344 525 351 c 1,34,-1
530 284 l 1,35,36
483 272 483 272 346 241 c 1,37,-1
346 19 l 2,38,39
346 -17 346 -17 336 -35.5 c 128,-1,40
326 -54 326 -54 303 -63 c 0,41,42
267 -77 267 -77 137 -77 c 1,43,44
130 -42 130 -42 109 -2 c 1,45,46
196 -6 196 -6 249 -3 c 0,47,48
263 -3 263 -3 268 2 c 128,-1,49
273 7 273 7 273 20 c 2,50,-1
273 223 l 1,51,52
232 214 232 214 161.5 198 c 128,-1,53
91 182 91 182 66 177 c 1,54,-1
45 252 l 1,55,56
99 261 99 261 273 297 c 1,57,-1
273 472 l 1,58,-1
59 472 l 1,59,-1
59 543 l 1,60,-1
273 543 l 1,61,-1
273 702 l 1,62,63
186 685 186 685 89 672 c 1,64,65
82 703 82 703 65 734 c 1,66,67
322 773 322 773 460 824 c 1,68,-1
513 763 l 1,69,70
439 737 439 737 346 717 c 1,71,-1
346 543 l 1,72,-1
575 543 l 1,73,74
563 668 563 668 561 828 c 1,75,-1
637 828 l 1,76,77
637 676 637 676 649 543 c 1,78,-1
943 543 l 1,79,-1
943 472 l 1,80,-1
657 472 l 1,81,82
672 345 672 345 698 244 c 1,83,84
778 330 778 330 829 426 c 1,6,-1
//
838 397 m 2,0,-1
672 397 l 2,1,2
600 397 600 397 575 418.5 c 128,-1,3
550 440 550 440 550 503 c 2,4,-1
550 836 l 1,5,-1
623 836 l 1,6,-1
623 668 l 1,7,8
776 720 776 720 854 764 c 1,9,-1
905 710 l 1,10,11
803 661 803 661 623 606 c 1,12,-1
623 503 l 2,13,14
623 480 623 480 633 473 c 128,-1,15
643 466 643 466 678 466 c 2,16,-1
832 466 l 2,17,18
861 466 861 466 870 486.5 c 128,-1,19
879 507 879 507 882 581 c 1,20,21
911 561 911 561 951 552 c 1,22,23
944 459 944 459 921.5 428 c 128,-1,24
899 397 899 397 838 397 c 2,0,-1
173 187 m 1,25,-1
383 187 l 1,26,-1
383 277 l 1,27,-1
173 277 l 1,28,-1
173 187 l 1,25,-1
383 422 m 1,29,-1
173 422 l 1,30,-1
173 336 l 1,31,-1
383 336 l 1,32,-1
383 422 l 1,29,-1
457 484 m 1,33,-1
457 13 l 2,34,35
457 -44 457 -44 422 -61 c 0,36,37
390 -73 390 -73 288 -73 c 1,38,39
283 -36 283 -36 263 -1 c 1,40,41
337 -4 337 -4 367 -2 c 0,42,43
383 -2 383 -2 383 14 c 2,44,-1
383 129 l 1,45,-1
173 129 l 1,46,-1
173 -71 l 1,47,-1
103 -71 l 1,48,-1
103 484 l 1,49,-1
457 484 l 1,33,-1
333 746 m 1,50,-1
391 774 l 1,51,52
489 648 489 648 524 550 c 1,53,-1
462 518 l 1,54,55
452 545 452 545 437 576 c 1,56,57
366 572 366 572 234 565 c 128,-1,58
102 558 102 558 51 555 c 1,59,-1
43 626 l 1,60,-1
115 628 l 1,61,62
171 729 171 729 210 837 c 1,63,-1
283 815 l 1,64,65
228 696 228 696 192 632 c 1,66,67
230 633 230 633 305.5 636.5 c 128,-1,68
381 640 381 640 402 641 c 1,69,70
359 711 359 711 333 746 c 1,50,-1
680 -3 m 2,71,-1
840 -3 l 2,72,73
871 -3 871 -3 880.5 21 c 128,-1,74
890 45 890 45 894 130 c 1,75,76
921 111 921 111 962 101 c 1,77,78
955 -1 955 -1 932 -35 c 128,-1,79
909 -69 909 -69 845 -69 c 2,80,-1
674 -69 l 2,81,82
600 -69 600 -69 575 -47.5 c 128,-1,83
550 -26 550 -26 550 36 c 2,84,-1
550 375 l 1,85,-1
623 375 l 1,86,-1
623 217 l 1,87,88
788 272 788 272 866 323 c 1,89,-1
915 268 l 1,90,91
819 213 819 213 623 155 c 1,92,-1
623 36 l 2,93,94
623 11 623 11 633 4 c 128,-1,95
643 -3 643 -3 680 -3 c 2,71,-1
//
287 316 m 1,0,-1
719 316 l 1,1,2
628 393 628 393 577 486 c 1,3,-1
419 486 l 1,4,5
364 391 364 391 287 316 c 1,0,-1
284 38 m 1,6,-1
717 38 l 1,7,-1
717 247 l 1,8,-1
284 247 l 1,9,-1
284 38 l 1,6,-1
946 486 m 1,10,-1
656 486 l 1,11,12
708 410 708 410 789 351 c 128,-1,13
870 292 870 292 965 261 c 1,14,15
944 230 944 230 934 186 c 1,16,17
860.5 214.5 860.5 214.5 796 258 c 1,18,-1
796 -74 l 1,19,-1
717 -74 l 1,20,-1
717 -32 l 1,21,-1
284 -32 l 1,22,-1
284 -76 l 1,23,-1
209 -76 l 1,24,-1
209 249 l 1,25,26
148 205 148 205 80 172 c 1,27,28
61 211 61 211 37 236 c 1,29,30
221 318 221 318 331 486 c 1,31,-1
55 486 l 1,32,-1
55 555 l 1,33,-1
372 555 l 1,34,35
412 633 412 633 434 712 c 1,36,-1
114 712 l 1,37,-1
114 781 l 1,38,-1
890 781 l 1,39,-1
890 712 l 1,40,-1
518 712 l 1,41,42
495 638 495 638 457 555 c 1,43,-1
946 555 l 1,44,-1
946 486 l 1,10,-1
//
942 688 m 1,0,-1
520 688 l 1,1,-1
520 512 l 1,2,-1
533 527 l 1,3,4
627 480 627 480 730 420 c 128,-1,5
833 360 833 360 889 317 c 1,6,-1
837 248 l 1,7,8
729 338 729 338 520 451 c 1,9,-1
520 -71 l 1,10,-1
441 -71 l 1,11,-1
441 688 l 1,12,-1
59 688 l 1,13,-1
59 763 l 1,14,-1
942 763 l 1,15,-1
942 688 l 1,0,-1
//
343 214 m 1,0,-1
355 147 l 1,1,2
191 81 191 81 58 31 c 1,3,-1
42 101 l 1,4,5
64 108 64 108 172 148 c 1,6,-1
172 413 l 1,7,-1
60 413 l 1,8,-1
60 482 l 1,9,-1
172 482 l 1,10,-1
172 699 l 1,11,-1
50 699 l 1,12,-1
50 769 l 1,13,-1
353 769 l 1,14,-1
353 699 l 1,15,-1
241 699 l 1,16,-1
241 482 l 1,17,-1
332 482 l 1,18,-1
332 413 l 1,19,-1
241 413 l 1,20,-1
241 174 l 1,21,-1
343 214 l 1,0,-1
813 381 m 1,22,-1
543 381 l 1,23,24
591 250 591 250 681 157 c 1,25,26
768 250 768 250 813 381 c 1,22,-1
620 620 m 1,27,-1
464 620 l 1,28,-1
464 447 l 1,29,-1
620 447 l 1,30,-1
620 620 l 1,27,-1
854 450 m 1,31,-1
900 432 l 1,32,33
851 239 851 239 733 110 c 1,34,35
836 26 836 26 963 -8 c 1,36,37
936 -34 936 -34 915 -73 c 1,38,39
780 -28 780 -28 681 59 c 1,40,41
585 -25 585 -25 453 -77 c 1,42,43
436 -42 436 -42 412 -17 c 1,44,45
537 26 537 26 630 108 c 1,46,47
529 220 529 220 473 381 c 1,48,-1
463 381 l 1,49,50
455 81 455 81 340 -69 c 1,51,52
324 -45 324 -45 286 -22 c 1,53,54
394 125 394 125 394 429 c 2,55,-1
394 690 l 1,56,-1
620 690 l 1,57,-1
620 836 l 1,58,-1
691 836 l 1,59,-1
691 690 l 1,60,-1
881 690 l 1,61,-1
893 693 l 1,62,-1
946 679 l 1,63,64
906 538 906 538 881 479 c 1,65,-1
816 494 l 1,66,67
833 536 833 536 855 620 c 1,68,-1
691 620 l 1,69,-1
691 447 l 1,70,-1
841 447 l 1,71,-1
854 450 l 1,31,-1
//
230 175 m 1,0,-1
358 210 l 1,1,-1
369 144 l 1,2,3
315 128 315 128 208.5 97.5 c 128,-1,4
102 67 102 67 58 54 c 1,5,-1
35 125 l 1,6,-1
163 157 l 1,7,-1
163 402 l 1,8,-1
59 402 l 1,9,-1
59 469 l 1,10,-1
163 469 l 1,11,-1
163 687 l 1,12,-1
46 687 l 1,13,-1
46 754 l 1,14,-1
351 754 l 1,15,-1
351 687 l 1,16,-1
230 687 l 1,17,-1
230 469 l 1,18,-1
337 469 l 1,19,-1
337 402 l 1,20,-1
230 402 l 1,21,-1
230 175 l 1,0,-1
895 298 m 1,22,-1
895 297 l 1,23,-1
929 297 l 1,24,-1
929 -3 l 2,25,26
929 -54 929 -54 897 -68 c 0,27,28
870 -79 870 -79 772 -79 c 1,29,30
766 -44 766 -44 750 -15 c 1,31,32
793 -16 793 -16 844 -16 c 0,33,34
854 -15 854 -15 857 -12.5 c 128,-1,35
860 -10 860 -10 860 -2 c 2,36,-1
860 233 l 1,37,-1
632 233 l 1,38,39
601 139 601 139 592 113 c 1,40,41
706 121 706 121 736 123 c 1,42,43
722 153 722 153 702 187 c 1,44,-1
746 206 l 1,45,46
808 106 808 106 825 37 c 1,47,-1
778 16 l 1,48,49
772 37 772 37 761 68 c 1,50,51
540 48 540 48 484 44 c 1,52,-1
477 106 l 1,53,-1
532 109 l 1,54,55
544 145 544 145 568 233 c 1,56,-1
454 233 l 1,57,-1
454 -77 l 1,58,-1
384 -77 l 1,59,-1
384 298 l 1,60,-1
584 298 l 1,61,62
593 332 593 332 599 366 c 1,63,-1
424 366 l 1,64,-1
424 646 l 1,65,-1
489 646 l 1,66,-1
489 425 l 1,67,-1
825 425 l 1,68,-1
825 646 l 1,69,-1
893 646 l 1,70,-1
893 366 l 1,71,-1
671 366 l 1,72,-1
651 298 l 1,73,-1
895 298 l 1,22,-1
793 483 m 1,74,-1
758 447 l 1,75,76
725 477 725 477 663 518 c 1,77,78
602 465 602 465 540 431 c 1,79,80
522 460 522 460 504 473 c 1,81,82
568 505 568 505 619 546 c 1,83,84
571 574 571 574 524 598 c 1,85,-1
557 630 l 1,86,87
604 608 604 608 656 578 c 1,88,89
696 617 696 617 721 659 c 1,90,-1
771 644 l 1,91,92
745 598 745 598 699 551 c 1,93,94
760 513 760 513 793 483 c 1,74,-1
689 745 m 1,95,-1
950 745 l 1,96,-1
950 680 l 1,97,-1
363 680 l 1,98,-1
363 745 l 1,99,-1
616 745 l 1,100,-1
616 832 l 1,101,-1
689 832 l 1,102,-1
689 745 l 1,95,-1
//
947 710 m 1,0,-1
533 710 l 1,1,2
514 643 514 643 487 565 c 1,3,-1
896 565 l 1,4,-1
896 17 l 2,5,6
896 -16 896 -16 887.5 -32.5 c 128,-1,7
879 -49 879 -49 858 -58 c 0,8,9
826 -71 826 -71 706 -71 c 1,10,11
699 -34 699 -34 680 -2 c 1,12,13
760 -5 760 -5 802 -2 c 0,14,15
813 -1 813 -1 817.5 3 c 128,-1,16
822 7 822 7 822 18 c 2,17,-1
822 495 l 1,18,-1
652 495 l 1,19,-1
652 -44 l 1,20,-1
579 -44 l 1,21,-1
579 495 l 1,22,-1
414 495 l 1,23,-1
414 -44 l 1,24,-1
341 -44 l 1,25,-1
341 495 l 1,26,-1
183 495 l 1,27,-1
183 -77 l 1,28,-1
108 -77 l 1,29,-1
108 565 l 1,30,-1
409 565 l 1,31,32
430 635 430 635 444 710 c 1,33,-1
57 710 l 1,34,-1
57 784 l 1,35,-1
947 784 l 1,36,-1
947 710 l 1,0,-1
//
927 692 m 1,0,-1
610 692 l 1,1,2
586 651 586 651 539 582 c 1,3,-1
539 -74 l 1,4,-1
460 -74 l 1,5,-1
460 482 l 1,6,7
296 295 296 295 98 191 c 1,8,9
84 215 84 215 48 256 c 1,10,11
193 329 193 329 316 445 c 128,-1,12
439 561 439 561 515 692 c 1,13,-1
73 692 l 1,14,-1
73 766 l 1,15,-1
927 766 l 1,16,-1
927 692 l 1,0,-1
559 477 m 1,17,-1
614 525 l 1,18,19
847 374 847 374 955 262 c 1,20,-1
896 205 l 1,21,22
844 261 844 261 746.5 339 c 128,-1,23
649 417 649 417 559 477 c 1,17,-1
//
486 491 m 1,0,-1
486 432 l 1,1,-1
797 432 l 1,2,-1
797 491 l 1,3,-1
486 491 l 1,0,-1
486 593 m 1,4,-1
486 535 l 1,5,-1
797 535 l 1,6,-1
797 593 l 1,7,-1
486 593 l 1,4,-1
418 385 m 1,8,-1
418 634 l 1,9,10
382 580 382 580 343 538 c 1,11,12
320 561 320 561 286 581 c 1,13,14
389 681 389 681 448 836 c 1,15,-1
515 818 l 1,16,17
501 781 501 781 485 750 c 1,18,-1
939 750 l 1,19,-1
939 692 l 1,20,-1
454 692 l 1,21,22
444 674 444 674 422 640 c 1,23,-1
867 640 l 1,24,-1
867 385 l 1,25,-1
418 385 l 1,8,-1
265 832 m 1,26,-1
335 812 l 1,27,28
291 697 291 697 232 597 c 1,29,-1
232 -71 l 1,30,-1
162 -71 l 1,31,-1
162 488 l 1,32,33
114 421 114 421 62 367 c 1,34,35
42 408 42 408 16 435 c 1,36,37
91 507 91 507 157 612.5 c 128,-1,38
223 718 223 718 265 832 c 1,26,-1
959 279 m 1,39,-1
531 279 l 1,40,41
518 259 518 259 488 221 c 1,42,-1
930 221 l 1,43,44
929 201 929 201 927 191 c 0,45,46
905 -16 905 -16 866 -55 c 0,47,48
844 -74 844 -74 807 -77 c 0,49,50
763 -79 763 -79 714 -75 c 1,51,52
712 -45 712 -45 696 -18 c 1,53,54
742 -23 742 -23 782 -23 c 0,55,56
804 -23 804 -23 813 -15 c 0,57,58
840 10 840 10 859 166 c 1,59,-1
774 166 l 1,60,61
707 7 707 7 580 -89 c 1,62,63
560 -65 560 -65 532 -46 c 1,64,65
647 34 647 34 706 166 c 1,66,-1
603 166 l 1,67,68
520 22 520 22 380 -69 c 1,69,70
359 -43 359 -43 334 -25 c 1,71,72
460 49 460 49 534 166 c 1,73,-1
438 166 l 1,74,75
374 104 374 104 316 67 c 1,76,77
296 90 296 90 269 114 c 1,78,79
384 179 384 179 458 279 c 1,80,-1
292 279 l 1,81,-1
292 337 l 1,82,-1
959 337 l 1,83,-1
959 279 l 1,39,-1
//
288 374 m 1,0,-1
288 272 l 1,1,2
319 274 319 274 664 295 c 1,3,4
670 300 670 300 681 310 c 128,-1,5
692 320 692 320 698 325 c 1,6,-1
698 374 l 1,7,-1
288 374 l 1,0,-1
698 672 m 1,8,-1
288 672 l 1,9,-1
288 580 l 1,10,-1
698 580 l 1,11,-1
698 672 l 1,8,-1
698 431 m 1,12,-1
698 522 l 1,13,-1
288 522 l 1,14,-1
288 431 l 1,15,-1
698 431 l 1,12,-1
867 518 m 1,16,-1
937 481 l 1,17,18
871 384 871 384 773 291 c 1,19,-1
773 29 l 2,20,21
773 -11 773 -11 762 -31.5 c 128,-1,22
751 -52 751 -52 724 -62 c 0,23,24
685 -75 685 -75 524 -75 c 1,25,26
516 -32 516 -32 495 3 c 1,27,28
546 1 546 1 670 1 c 0,29,30
686 2 686 2 692 8 c 128,-1,31
698 14 698 14 698 29 c 2,32,-1
698 227 l 1,33,34
458 37 458 37 97 -68 c 1,35,36
73 -26 73 -26 50 1 c 1,37,38
347 77 347 77 565 223 c 1,39,40
257 198 257 198 70 185 c 1,41,-1
58 259 l 1,42,43
67 259 67 259 214 267 c 1,44,-1
214 738 l 1,45,-1
419 738 l 1,46,47
444 792 444 792 459 839 c 1,48,-1
542 816 l 1,49,50
518 770 518 770 498 738 c 1,51,-1
773 738 l 1,52,-1
773 398 l 1,53,54
824 453 824 453 867 518 c 1,16,-1
//
859 624 m 1,0,-1
859 692 l 1,1,-1
784 692 l 1,2,-1
784 624 l 1,3,-1
859 624 l 1,0,-1
859 506 m 1,4,-1
859 574 l 1,5,-1
784 574 l 1,6,-1
784 506 l 1,7,-1
859 506 l 1,4,-1
552 574 m 1,8,-1
552 506 l 1,9,-1
620 506 l 1,10,-1
620 574 l 1,11,-1
552 574 l 1,8,-1
552 692 m 1,12,-1
552 624 l 1,13,-1
620 624 l 1,14,-1
620 692 l 1,15,-1
552 692 l 1,12,-1
737 574 m 1,16,-1
667 574 l 1,17,-1
667 506 l 1,18,-1
737 506 l 1,19,-1
737 574 l 1,16,-1
667 624 m 1,20,-1
737 624 l 1,21,-1
737 692 l 1,22,-1
667 692 l 1,23,-1
667 624 l 1,20,-1
918 744 m 1,24,-1
918 454 l 1,25,-1
494 454 l 1,26,-1
494 744 l 1,27,-1
612 744 l 1,28,-1
612 835 l 1,29,-1
668 835 l 1,30,-1
668 744 l 1,31,-1
738 744 l 1,32,-1
738 835 l 1,33,-1
795 835 l 1,34,-1
795 744 l 1,35,-1
918 744 l 1,24,-1
335 349 m 1,36,-1
185 349 l 1,37,-1
185 278 l 1,38,-1
335 278 l 1,39,-1
335 349 l 1,36,-1
185 152 m 1,40,-1
335 152 l 1,41,-1
335 225 l 1,42,-1
185 225 l 1,43,-1
185 152 l 1,40,-1
180 742 m 1,44,-1
180 523 l 1,45,-1
230 523 l 1,46,-1
230 678 l 1,47,-1
343 678 l 1,48,-1
343 742 l 1,49,-1
180 742 l 1,44,-1
343 523 m 1,50,-1
343 630 l 1,51,-1
278 630 l 1,52,-1
278 523 l 1,53,-1
343 523 l 1,50,-1
463 379 m 1,54,-1
404 379 l 1,55,-1
404 470 l 1,56,-1
123 470 l 1,57,-1
123 403 l 1,58,-1
400 403 l 1,59,-1
400 -12 l 2,60,61
400 -59 400 -59 372 -70 c 0,62,63
349 -81 349 -81 267 -81 c 1,64,65
262 -53 262 -53 245 -23 c 1,66,67
279 -24 279 -24 322 -24 c 0,68,69
335 -23 335 -23 335 -12 c 2,70,-1
335 98 l 1,71,-1
185 98 l 1,72,-1
185 -82 l 1,73,-1
122 -82 l 1,74,-1
122 379 l 1,75,-1
65 379 l 1,76,-1
65 523 l 1,77,-1
122 523 l 1,78,-1
122 799 l 1,79,-1
403 799 l 1,80,-1
403 523 l 1,81,-1
464 523 l 1,82,-1
464 405 l 1,83,-1
953 405 l 1,84,-1
953 351 l 1,85,-1
463 351 l 1,86,-1
463 379 l 1,54,-1
789 114 m 1,87,-1
615 114 l 1,88,89
648 60 648 60 659 1 c 1,90,-1
650 -2 l 1,91,-1
741 -2 l 1,92,93
771 55 771 55 789 114 c 1,87,-1
575 253 m 1,94,-1
575 164 l 1,95,-1
837 164 l 1,96,-1
837 253 l 1,97,-1
575 253 l 1,94,-1
798 -2 m 1,98,-1
959 -2 l 1,99,-1
959 -59 l 1,100,-1
446 -59 l 1,101,-1
446 -2 l 1,102,-1
598 -2 l 1,103,104
587 48 587 48 560 98 c 1,105,-1
608 114 l 1,106,-1
513 114 l 1,107,-1
513 303 l 1,108,-1
902 303 l 1,109,-1
902 114 l 1,110,-1
798 114 l 1,111,-1
850 95 l 1,112,113
845 86 845 86 826.5 50 c 128,-1,114
808 14 808 14 798 -2 c 1,98,-1
//
424 592 m 1,0,-1
424 571 l 1,1,-1
580 571 l 1,2,-1
580 772 l 1,3,-1
424 772 l 1,4,-1
424 748 l 1,5,-1
540 748 l 1,6,-1
540 712 l 1,7,-1
424 712 l 1,8,-1
424 690 l 1,9,-1
534 690 l 1,10,-1
534 592 l 1,11,-1
424 592 l 1,0,-1
424 657 m 1,12,-1
424 625 l 1,13,-1
500 625 l 1,14,-1
500 657 l 1,15,-1
424 657 l 1,12,-1
148 522 m 1,16,-1
148 426 l 1,17,-1
81 426 l 1,18,-1
81 571 l 1,19,-1
171 571 l 1,20,-1
159 807 l 1,21,-1
213 794 l 1,22,23
278 813 278 813 324 836 c 1,24,-1
367 796 l 1,25,26
321 776 321 776 226 750 c 1,27,-1
227 722 l 1,28,-1
343 722 l 1,29,-1
343 683 l 1,30,-1
229 683 l 1,31,-1
230 648 l 1,32,-1
344 648 l 1,33,-1
344 609 l 1,34,-1
232 609 l 1,35,-1
234 571 l 1,36,-1
372 571 l 1,37,-1
372 812 l 1,38,-1
633 812 l 1,39,-1
633 571 l 1,40,-1
766 571 l 1,41,-1
769 609 l 1,42,-1
666 609 l 1,43,-1
666 648 l 1,44,-1
772 648 l 1,45,-1
774 685 l 1,46,-1
666 685 l 1,47,-1
666 724 l 1,48,-1
776 724 l 1,49,-1
778 760 l 1,50,-1
665 760 l 1,51,-1
665 805 l 1,52,-1
779 805 l 1,53,-1
779 806 l 1,54,-1
843 806 l 1,55,56
838 677 838 677 830 571 c 1,57,-1
916 571 l 1,58,-1
916 426 l 1,59,-1
845 426 l 1,60,-1
845 522 l 1,61,-1
148 522 l 1,16,-1
541 449 m 1,62,-1
451 449 l 1,63,64
450 443 450 443 448.5 433 c 128,-1,65
447 423 447 423 447 418 c 1,66,-1
541 418 l 1,67,-1
541 449 l 1,62,-1
769 351 m 1,68,-1
769 380 l 1,69,-1
597 380 l 1,70,-1
597 369 l 2,71,72
597 356 597 356 603.5 352.5 c 128,-1,73
610 349 610 349 632 349 c 2,74,-1
746 349 l 2,75,76
767 349 767 349 769 351 c 1,68,-1
228 229 m 1,77,-1
769 229 l 1,78,-1
769 258 l 1,79,-1
228 258 l 1,80,-1
228 229 l 1,77,-1
755 307 m 1,81,-1
628 307 l 2,82,83
577 307 577 307 559 319.5 c 128,-1,84
541 332 541 332 541 369 c 2,85,-1
541 380 l 1,86,-1
436 380 l 1,87,88
410 317 410 317 309 293 c 1,89,-1
769 293 l 1,90,-1
769 307 l 1,91,-1
755 307 l 1,81,-1
375 380 m 1,92,-1
228 380 l 1,93,-1
228 293 l 1,94,-1
270 293 l 1,95,96
259 314 259 314 246 326 c 1,97,98
348 340 348 340 375 380 c 1,92,-1
164 418 m 1,99,-1
391 418 l 1,100,101
395 435 395 435 396 449 c 1,102,-1
213 449 l 1,103,-1
213 490 l 1,104,-1
773 490 l 1,105,-1
773 449 l 1,106,-1
597 449 l 1,107,-1
597 418 l 1,108,-1
837 418 l 1,109,-1
837 191 l 1,110,-1
164 191 l 1,111,-1
164 418 l 1,99,-1
269 162 m 1,112,-1
336 148 l 1,113,114
259 75 259 75 88 25 c 1,115,116
72 51 72 51 44 69 c 1,117,118
200 102 200 102 269 162 c 1,112,-1
631 166 m 1,119,-1
699 166 l 1,120,121
738 134 738 134 811 108 c 128,-1,122
884 82 884 82 960 69 c 1,123,124
934 43 934 43 919 15 c 1,125,126
841 35 841 35 774 62 c 1,127,128
774 61 774 61 773.5 59 c 128,-1,129
773 57 773 57 772.5 55.5 c 128,-1,130
772 54 772 54 771.5 52.5 c 128,-1,131
771 51 771 51 771 50 c 0,132,133
744 -44 744 -44 709 -67 c 0,134,135
686 -81 686 -81 644 -83 c 0,136,137
573 -83 573 -83 525 -79 c 1,138,139
523 -54 523 -54 510 -29 c 1,140,141
557 -34 557 -34 624 -34 c 0,142,143
649 -34 649 -34 660 -28 c 0,144,145
681 -12 681 -12 697 35 c 1,146,-1
479 35 l 1,147,148
441 -16 441 -16 355.5 -41.5 c 128,-1,149
270 -67 270 -67 147 -82 c 1,150,151
138 -59 138 -59 108 -26 c 1,152,153
331 -16 331 -16 397 35 c 1,154,-1
236 35 l 1,155,-1
236 78 l 1,156,-1
709 78 l 1,157,-1
709 81 l 1,158,-1
731 81 l 1,159,160
692 100 692 100 663 121 c 1,161,-1
371 121 l 1,162,-1
371 163 l 1,163,-1
631 163 l 1,164,-1
631 166 l 1,119,-1
//
533 388 m 1,0,-1
533 322 l 1,1,-1
767 322 l 1,2,-1
767 388 l 1,3,-1
533 388 l 1,0,-1
533 505 m 1,4,-1
533 446 l 1,5,-1
767 446 l 1,6,-1
767 505 l 1,7,-1
533 505 l 1,4,-1
842 266 m 1,8,-1
533 266 l 1,9,-1
533 209 l 1,10,-1
867 209 l 1,11,-1
867 151 l 1,12,-1
533 151 l 1,13,-1
533 89 l 1,14,-1
930 89 l 1,15,-1
930 30 l 1,16,-1
533 30 l 1,17,-1
533 -73 l 1,18,-1
459 -73 l 1,19,-1
459 30 l 1,20,-1
70 30 l 1,21,-1
70 89 l 1,22,-1
459 89 l 1,23,-1
459 151 l 1,24,-1
133 151 l 1,25,-1
133 209 l 1,26,-1
459 209 l 1,27,-1
459 266 l 1,28,-1
166 266 l 1,29,-1
166 322 l 1,30,-1
459 322 l 1,31,-1
459 388 l 1,32,-1
53 388 l 1,33,-1
53 446 l 1,34,-1
459 446 l 1,35,-1
459 505 l 1,36,-1
172 505 l 1,37,-1
172 561 l 1,38,-1
459 561 l 1,39,-1
459 586 l 1,40,-1
456 584 l 1,41,42
426 612 426 612 397 627 c 1,43,44
441 657 441 657 477 693 c 1,45,-1
318 693 l 1,46,47
349 647 349 647 364 611 c 1,48,-1
301 586 l 1,49,50
283 633 283 633 242 693 c 1,51,-1
203 693 l 1,52,53
151 615 151 615 101 568 c 1,54,55
74 593 74 593 43 611 c 1,56,57
144 701 144 701 202 837 c 1,58,-1
271 818 l 1,59,60
253 779 253 779 238 751 c 1,61,-1
491 751 l 1,62,-1
491 708 l 1,63,64
545 768 545 768 575 837 c 1,65,-1
644 819 l 1,66,67
632 792 632 792 608 751 c 1,68,-1
942 751 l 1,69,-1
942 693 l 1,70,-1
713 693 l 1,71,72
748 652 748 652 765 619 c 1,73,-1
704 587 l 1,74,75
681 634 681 634 631 693 c 1,76,-1
567 693 l 1,77,78
535 653 535 653 494 615 c 1,79,-1
533 615 l 1,80,-1
533 561 l 1,81,-1
842 561 l 1,82,-1
842 446 l 1,83,-1
945 446 l 1,84,-1
945 388 l 1,85,-1
842 388 l 1,86,-1
842 266 l 1,8,-1
//
940 705 m 1,0,-1
848 640 l 1,1,2
814 700 814 700 727 785 c 1,3,-1
812 844 l 1,4,5
906 758 906 758 940 705 c 1,0,-1
549 87 m 1,6,-1
549 73 l 1,7,8
588 105 588 105 634 155 c 1,9,10
595 302 595 302 582 539 c 1,11,-1
363 539 l 1,12,-1
363 494 l 1,13,-1
550 494 l 1,14,-1
550 410 l 1,15,-1
363 410 l 1,16,-1
363 372 l 1,17,-1
533 372 l 1,18,-1
533 125 l 1,19,-1
361 125 l 1,20,-1
361 87 l 1,21,-1
549 87 l 1,6,-1
178 221 m 1,22,-1
178 183 l 1,23,-1
266 183 l 1,24,-1
266 221 l 1,25,-1
178 221 l 1,22,-1
178 314 m 1,26,-1
178 277 l 1,27,-1
266 277 l 1,28,-1
266 314 l 1,29,-1
178 314 l 1,26,-1
353 277 m 1,30,-1
446 277 l 1,31,-1
446 314 l 1,32,-1
353 314 l 1,33,-1
353 277 l 1,30,-1
353 183 m 1,34,-1
446 183 l 1,35,-1
446 221 l 1,36,-1
353 221 l 1,37,-1
353 183 l 1,34,-1
816 494 m 1,38,-1
921 462 l 1,39,40
860 270 860 270 759 133 c 1,41,42
794 28 794 28 836 28 c 0,43,44
864 28 864 28 871 191 c 1,45,46
912 149 912 149 966 128 c 1,47,48
954 1 954 1 923.5 -43.5 c 128,-1,49
893 -88 893 -88 826 -88 c 0,50,51
735 -88 735 -88 676 34 c 1,52,53
610 -33.5 610 -33.5 529 -86 c 1,54,55
500 -41 500 -41 453 0 c 1,56,-1
361 0 l 1,57,-1
361 -91 l 1,58,-1
258 -91 l 1,59,-1
258 0 l 1,60,-1
62 0 l 1,61,-1
62 87 l 1,62,-1
258 87 l 1,63,-1
258 125 l 1,64,-1
94 125 l 1,65,-1
94 372 l 1,66,-1
256 372 l 1,67,-1
256 410 l 1,68,-1
76 410 l 1,69,-1
76 494 l 1,70,-1
256 494 l 1,71,-1
256 539 l 1,72,-1
49 539 l 1,73,-1
49 636 l 1,74,-1
254 636 l 1,75,-1
254 688 l 1,76,-1
93 688 l 1,77,-1
93 779 l 1,78,-1
254 779 l 1,79,-1
254 846 l 1,80,-1
367 846 l 1,81,-1
367 779 l 1,82,-1
531 779 l 1,83,-1
531 688 l 1,84,-1
367 688 l 1,85,-1
367 636 l 1,86,-1
577 636 l 1,87,88
573 727 573 727 573 845 c 1,89,-1
692 845 l 1,90,91
689 755 689 755 693 636 c 1,92,-1
953 636 l 1,93,-1
953 539 l 1,94,-1
697 539 l 1,95,96
707 388 707 388 725 283 c 1,97,98
779 376 779 376 816 494 c 1,38,-1
</textarea></div>
<canvas id="output" width="750" height="800"></canvas>
<canvas id="preview" width="750" height="800"></canvas>
<script src="autohint.js"></script>
<script src="main.js"></script>
<style>
canvas { border: 1px solid #ddd }
#output {
position: absolute;
top: 0;
left: 300px;
}
#preview {
position: absolute;
top: 0;
left: 1050px;
}
#panel {
display: inline-block;
width: 300px;
height: 800px;
position: absolute;
top: 0;
left: 0;
}
#panel textarea {
display: block;
position: absolute;
top: 3em; left: 0; right: 0; bottom: 0;
width: 300px; height: auto;
box-sizing: border-box;
-webkit-box-sizing: border-box;
}
</style>
var hOutput = document.getElementById('output').getContext('2d');
var hPreview = document.getElementById('preview').getContext('2d');
var minsw = 20;
var maxsw = 140;
var input = document.getElementById('input').value.split(/^\/\//m).map(parseSFD);
var inputStems = input.map(function(contours){ return findStems(contours, minsw, maxsw) });
var ppem = 14;
var cs = autohint(input[13], inputStems[13], ppem);
drawPixelGrid(hOutput, ppem)
drawContours(hOutput, cs.contours);
drawInstructions(hOutput, cs.instructions);
debugger;
var CANVASWIDTH = 750;
var CANVASHEIGHT = 800;
var SUPERSAMPLING = 8;
var eTemp = document.createElement('canvas')
eTemp.width = CANVASWIDTH * 3 * SUPERSAMPLING;
eTemp.height = CANVASHEIGHT * 3;
var hTemp = eTemp.getContext('2d')
hTemp.fillStyle = "white";
hTemp.fillRect(0, 0, CANVASWIDTH * 3 * SUPERSAMPLING, CANVASHEIGHT * 3);
var dy = 40
for(var ppem = 12; ppem <= 30; ppem++) {
var dx = 10;
for(var k = 0; k < input.length; k++) if(input[k].length){
var c = autohint(input[k], inputStems[k], ppem)
drawPreview(hTemp, c.contours, ppem, dx, dy, SUPERSAMPLING);
dx += ppem;
}
dy += 40;
}
setTimeout(function(){
var GAMMA = 2.2;
var ori = hTemp.getImageData(0, 0, CANVASWIDTH * 3 * SUPERSAMPLING, CANVASHEIGHT * 3);
var aa = hPreview.createImageData(CANVASWIDTH, CANVASHEIGHT);
var w = 4 * 3 * SUPERSAMPLING * CANVASWIDTH;
var h = []; for(var j = 0; j < 3 * SUPERSAMPLING; j++) h[j] = 1;
var jSample = 0;
var a = 3 * SUPERSAMPLING;
for(var j = 0; j < CANVASHEIGHT; j++) {
for(var k = 0; k < CANVASWIDTH; k++) {
for(var component = 0; component < 3; component++) {
for(var ss = 0; ss < SUPERSAMPLING; ss++) {
var d = ori.data[w] < 128 ? 0 : 1;
a += d
a -= h[jSample]
h[jSample] = d;
w += 4;
jSample += 1;
if(jSample >= 3 * SUPERSAMPLING) jSample = 0;
}
var alpha = a / (3 * SUPERSAMPLING);
aa.data[(j * CANVASWIDTH + k) * 4 + component] = 255 * Math.pow(alpha, 1 / 1.8)
}
aa.data[(j * CANVASWIDTH + k) * 4 + 3] = 255
}
w += 4 * 2 * 3 * SUPERSAMPLING * CANVASWIDTH
}
hPreview.putImageData(aa, 0, 0)
}, 10)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment