(function(){function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}return e})()({1:[function(require,module,exports){
const roundings = require("../../support/roundings");
const { decideDeltaShift, getSWCFG } = require("../../instructor/delta");

function interpolate(a, b, c) {
	if (c.y <= a.y) c.ytouch = c.y - a.y + a.ytouch;
	else if (c.y >= b.y) c.ytouch = c.y - b.y + b.ytouch;
	else c.ytouch = (c.y - a.y) / (b.y - a.y) * (b.ytouch - a.ytouch) + a.ytouch;
}
function yRound(z, upm, ppem) {
	z.touched = true;
	z.ytouch = roundings.rtg(z.y, upm, ppem);
}
function interpolateIP(a, b, c, upm, ppem) {
	if (!a.touched) yRound(a, upm, ppem);
	if (!b.touched) yRound(b, upm, ppem);
	c.touched = true;
	if (a.y === b.y) {
		c.ytouch = c.y - a.y + a.ytouch;
	} else {
		c.ytouch = (c.y - a.y) / (b.y - a.y) * (b.ytouch - a.ytouch) + a.ytouch;
	}
}
function IUPy(contours) {
	for (let j = 0; j < contours.length; j++) {
		let contour = contours[j];
		let k = 0;
		while (k < contour.points.length && !contour.points[k].touched) k++;
		if (contour.points[k]) {
			// Found a touched point in contour
			// Copy coordinates for first/last point
			if (contour.points[0].touched && !contour.points[contour.points.length - 1].touched) {
				contour.points[contour.points.length - 1].touched = true;
				contour.points[contour.points.length - 1].ytouch = contour.points[0].ytouch;
			} else if (
				!contour.points[0].touched &&
				contour.points[contour.points.length - 1].touched
			) {
				contour.points[0].touched = true;
				contour.points[0].ytouch = contour.points[contour.points.length - 1].ytouch;
			}
			let kleft = k,
				k0 = k;
			let untoucheds = [];
			for (let k = 0; k <= contour.points.length; k++) {
				let ki = (k + k0) % contour.points.length;
				if (contour.points[ki].touched) {
					let pleft = contour.points[kleft];
					let pright = contour.points[ki];
					let lower = pleft.y < pright.y ? pleft : pright;
					let higher = pleft.y < pright.y ? pright : pleft;
					for (let w = 0; w < untoucheds.length; w++)
						interpolate(lower, higher, untoucheds[w]);
					untoucheds = [];
					kleft = ki;
				} else {
					untoucheds.push(contour.points[ki]);
				}
			}
		}
	}
}
function untouchAll(contours) {
	for (let j = 0; j < contours.length; j++)
		for (let 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].y;
		}
}

function calculateYW(upm, ppem, stem, action, swcfgCtx) {
	const uppx = upm / ppem;
	let h, l;
	if (stem.posKeyAtTop) {
		h = stem.posKey;
		l = stem.advKey;
	} else {
		h = stem.advKey;
		l = stem.posKey;
	}
	const keyDX = h.x - l.x;
	let [y, w, strict, stacked] = action;
	const h_ytouch = y * uppx;
	const l_ytouch = (y - w) * uppx - keyDX * stem.slope;

	const swcfg = getSWCFG(swcfgCtx, 1, ppem);

	if (stem.posKeyAtTop) {
		const delta = decideDeltaShift(
			8,
			-1,
			strict,
			stacked,
			0,
			h.y - l.y,
			0,
			w * uppx,
			upm,
			ppem,
			swcfg
		);
		return { h, l, h_ytouch, l_ytouch: h_ytouch - (h.y - l.y) + delta / 8 * uppx };
	} else {
		const delta = decideDeltaShift(
			8,
			1,
			strict,
			stacked,
			0,
			h.y - l.y,
			0,
			w * uppx,
			upm,
			ppem,
			swcfg
		);
		return { h, l, l_ytouch, h_ytouch: l_ytouch + (h.y - l.y) + delta / 8 * uppx };
	}
}

function interpretIDH(glyph, si, actions, strategy, ppem) {
	const rtg = roundings.Rtg(strategy.UPM, ppem);

	untouchAll(glyph.contours);

	// Bottom blues
	si.blue.bottomZs.forEach(function(z) {
		glyph.indexedPoints[z.id].touched = true;
		glyph.indexedPoints[z.id].ytouch = rtg(strategy.BLUEZONE_BOTTOM_CENTER);
	});
	// Top blues
	si.blue.topZs.forEach(function(z) {
		glyph.indexedPoints[z.id].touched = true;
		//glyph.indexedPoints[z.id].ytouch = rtg(strategy.BLUEZONE_TOP_CENTER);
		glyph.indexedPoints[z.id].ytouch = Math.round(
			rtg(strategy.BLUEZONE_BOTTOM_CENTER) +
				rtg(strategy.BLUEZONE_TOP_CENTER - strategy.BLUEZONE_BOTTOM_CENTER)
		);
	});
	for (let j = 0; j < si.stems.length; j++) {
		const stem = si.stems[j];
		const action = actions.y[j];
		const { h, l, h_ytouch, l_ytouch } = calculateYW(strategy.UPM, ppem, si.stems[j], action, {
			minSW: strategy.MINIMAL_STROKE_WIDTH_PIXELS || 1 / 8,
			maxSWOverflowCpxs: strategy.MAX_SW_OVERFLOW_CPXS,
			maxSWShrinkCpxs: strategy.MAX_SW_SHRINK_CPXS
		});
		glyph.indexedPoints[h.id].touched = glyph.indexedPoints[l.id].touched = true;
		glyph.indexedPoints[h.id].ytouch = h_ytouch;
		glyph.indexedPoints[l.id].ytouch = l_ytouch;
		for (let pt of stem.posAlign) {
			pt = glyph.indexedPoints[pt.id];
			pt.touched = true;
			const key = glyph.indexedPoints[stem.posKey.id];
			pt.ytouch = key.ytouch + pt.y - key.y;
		}
		for (let pt of stem.advAlign) {
			pt = glyph.indexedPoints[pt.id];
			pt.touched = true;
			const key = glyph.indexedPoints[stem.advKey.id];
			pt.ytouch = key.ytouch + pt.y - key.y;
		}
	}

	// diagaligns
	for (let da of si.diagAligns) {
		for (let z of da.zs) {
			interpolateIP(
				glyph.indexedPoints[da.l],
				glyph.indexedPoints[da.r],
				glyph.indexedPoints[z],
				strategy.UPM,
				ppem
			);
		}
	}
	// ipsacalls
	for (let c of si.ipsacalls) {
		if (c.length === 2) {
			interpolateIP(
				glyph.indexedPoints[c[0]],
				glyph.indexedPoints[c[0]],
				glyph.indexedPoints[c[1]],
				strategy.UPM,
				ppem
			);
		} else {
			interpolateIP(
				glyph.indexedPoints[c[0]],
				glyph.indexedPoints[c[1]],
				glyph.indexedPoints[c[2]],
				strategy.UPM,
				ppem
			);
		}
	}

	// IUPy
	IUPy(glyph.contours);
	return glyph.contours;
}

exports.interpretIDH = interpretIDH;
exports.IUPy = IUPy;

},{"../../instructor/delta":2,"../../support/roundings":5}],2:[function(require,module,exports){
"use strict";

const roundings = require("../support/roundings");
const { xclamp, toVQ } = require("../support/common");

const STRICT_CUTOFF = 1 / 8;

function decideDelta(gear, original, target, upm, ppem) {
	return Math.round(gear * (target - original) / (upm / ppem));
}

const GAMMA = 2;
function corSW(x) {
	const integral = Math.floor(x);
	const fraction = x - integral;
	const corrected =
		integral > 1 || fraction > 1 / 2 ? fraction : 1 / 2 * Math.pow(fraction * 2, 1 / GAMMA);
	return integral + corrected;
}

/**
 * Decide the delta of a link
 * @param {number} gear
 * @param {number} sign
 * @param {boolean} isHard
 * @param {boolean} isStacked
 * @param {number} base0
 * @param {number} dist0
 * @param {number} base1
 * @param {number} dist1
 * @param {number} upm
 * @param {number} ppem
 * @param {number} addpxs
 */
function decideDeltaShift(
	gear,
	sign,
	isHard,
	isStacked,
	base0,
	dist0,
	base1,
	dist1,
	upm,
	ppem,
	swcfg
) {
	const { minSW, maxOverflow, maxShrink } = swcfg || {};
	const uppx = upm / ppem;
	const y1 = base0 + sign * dist0;
	const y2 = base1 + sign * dist1;
	const origPixels = corSW(dist0 / uppx);
	const desiredWidthPixels = Math.min(
		origPixels,
		isStacked ? Math.max(0, Math.floor((origPixels - uppx / 2) / uppx)) + 3 / 4 : origPixels,
		Math.max(isStacked ? 3 / 8 : 0, origPixels * (isStacked ? 5 / 8 : 1))
	);
	const yDesired = base1 + desiredWidthPixels * sign * uppx;
	const deltaStart = Math.round(gear * (y2 - y1) / uppx);
	const deltaDesired = Math.round(gear * (yDesired - y1) / uppx);
	let delta = deltaStart - deltaDesired;
	// We will try to reduce delta to 0 when there is "enough space".
	while (delta) {
		const delta1 = delta > 0 ? delta - 1 : delta + 1;
		const y2a = y1 + (deltaDesired + delta1) * uppx / gear;
		const d = Math.abs(base1 - y2a);
		if (!isStacked && d < (minSW || 0) * uppx) break;
		if (isStacked && dist1 < uppx * 1.01) {
			// pass
		} else {
			if (
				roundings.rtgDiff(y2, base1, upm, ppem) !== roundings.rtgDiff(y2a, base1, upm, ppem)
			)
				break; // wrong pixel!
			if (!isStacked) {
				if (
					isHard &&
					(sign > 0 ||
						Math.abs(y2a - roundings.rtg(y2, upm, ppem)) > STRICT_CUTOFF * uppx)
				)
					break;
				if (Math.abs(y2a - base1) - Math.abs(y2 - base1) > (maxOverflow || 1 / 2) * uppx)
					break;
				if (Math.abs(y2 - base1) - Math.abs(y2a - base1) > (maxShrink || 1 / 2) * uppx)
					break;
			}
		}
		delta = delta > 0 ? delta - 1 : delta + 1;
	}
	return delta + deltaDesired;
}

exports.decideDelta = decideDelta;
exports.decideDeltaShift = decideDeltaShift;

exports.getSWCFG = function(ctx, darkness, ppem) {
	return {
		minSW: toVQ(ctx.minSW || 3 / 4, ppem) * darkness,
		maxOverflow: xclamp(1 / 32, toVQ(ctx.maxSWOverflowCpxs || 50, ppem) / 100, 1 / 2),
		maxShrink: xclamp(1 / 32, toVQ(ctx.maxSWShrinkCpxs || 50, ppem) / 100, 1 / 2)
	};
};

},{"../support/common":3,"../support/roundings":5}],3:[function(require,module,exports){
"use strict";

const monoip = require("./monotonic-interpolate");

exports.mix = function(x, y, a) {
	return x + (y - x) * a;
};
exports.mixz = function(p, q, x) {
	return { x: p.x + (q.x - p.x) * x, y: p.y + (q.y - p.y) * x };
};
exports.lerp = function(x, x1, x2, y1, y2) {
	return (x - x1) / (x2 - x1) * (y2 - y1) + y1;
};
exports.xlerp = function(x, x1, x2, x3, y1, y2, y3) {
	if (x <= x2) {
		return (x - x1) / (x2 - x1) * (y2 - y1) + y1;
	} else {
		return (x - x2) / (x3 - x2) * (y3 - y2) + y2;
	}
};
exports.xclamp = function(low, x, high) {
	return x < low ? low : x > high ? high : x;
};

exports.toVQ = function toVQ(v, ppem) {
	if (v && v instanceof Array) {
		return monoip(v)(ppem);
	} else {
		return v;
	}
};

},{"./monotonic-interpolate":4}],4:[function(require,module,exports){
"use strict";
module.exports = function(terms) {
	let xs = terms.map(x => x[0]);
	let ys = terms.map(x => x[1]);
	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() {
			return 0;
		};
	}
	if (length === 1) {
		// Impl: Precomputing the result prevents problems if ys is mutated later and allows garbage collection of ys
		// Impl: Unary plus properly converts values to numbers
		var result = +ys[0];
		return function() {
			return result;
		};
	}

	// Rearrange xs and ys so that xs is sorted
	var indexes = [];
	for (i = 0; i < length; i++) {
		indexes.push(i);
	}
	indexes.sort(function(a, b) {
		return xs[a] < xs[b] ? -1 : 1;
	});
	var oldXs = xs,
		oldYs = ys;
	// Impl: Creating new arrays also prevents problems if the input arrays are mutated later
	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);
	}

	// 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) {
			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(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);
	}

	// 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));
			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;
	};
};

},{}],5:[function(require,module,exports){
"use strict";

function toF26D6(x) {
	return Math.round(x * 64);
}
function toF26D6P(x, upm, ppem) {
	return toF26D6(x / upm * ppem) / 64;
}
// from freetype
function rtg(x, upm, ppem) {
	var distance = toF26D6(x / upm * ppem);
	var val;
	if (distance > 0) {
		val = distance + 32;
		if (val > 0 && distance) {
			val &= ~63;
		} else {
			val = 0;
		}
	} else {
		val = -((32 - distance) & ~63);
		if (val > 0) val = 0;
	}
	return val * (upm / ppem) / 64;
}
function rtgDiff(x1, x2, upm, ppem) {
	const x1_ = toF26D6(x1 / upm * ppem);
	const x2_ = toF26D6(x2 / upm * ppem);
	var distance = x1_ - x2_;
	var val;
	if (distance > 0) {
		val = distance + 32;
		if (val > 0 && distance) {
			val &= ~63;
		} else {
			val = 0;
		}
	} else {
		val = -((32 - distance) & ~63);
		if (val > 0) val = 0;
	}
	return val * (upm / ppem) / 64;
}
function rtg1(x, upm, ppem) {
	if (x >= 0) return Math.max(upm / ppem, rtg(x, upm, ppem));
	else return -Math.max(upm / ppem, rtg(-x, upm, ppem));
}
function rtg_raw(y, upm, ppem) {
	return Math.round(y / upm * ppem) / ppem * upm;
}

function Rtg(upm, ppem) {
	return function(x) {
		return rtg(x, upm, ppem);
	};
}
function rutg(x, upm, ppem) {
	var distance = toF26D6(x / upm * ppem);
	var val;
	if (distance > 0) {
		val = distance + 63;
		if (val > 0 && distance) {
			val &= ~63;
		} else {
			val = 0;
		}
	} else {
		val = -((63 - distance) & ~63);
		if (val > 0) val = 0;
	}
	return val * (upm / ppem) / 64;
}
function Rutg(upm, ppem) {
	return function(x) {
		return rutg(x, upm, ppem);
	};
}
function rdtg(x, upm, ppem) {
	var distance = toF26D6(x / upm * ppem);
	var val;
	if (distance > 0) {
		val = distance;
		if (val > 0 && distance) {
			val &= ~63;
		} else {
			val = 0;
		}
	} else {
		val = -((0 - distance) & ~63);
		if (val > 0) val = 0;
	}
	return val * (upm / ppem) / 64;
}
function Rdtg(upm, ppem) {
	return function(x) {
		return rdtg(x, upm, ppem);
	};
}

exports.toF26D6P = toF26D6P;
exports.rtg = rtg;
exports.rtg1 = rtg1;
exports.rutg = rutg;
exports.rdtg = rdtg;
exports.rtg_raw = rtg_raw;
exports.Rtg = Rtg;
exports.Rutg = Rutg;
exports.Rdtg = Rdtg;
exports.rtgDiff = rtgDiff;

},{}],6:[function(require,module,exports){
const monoip = require("../support/monotonic-interpolate");
const Renderer = require("./render");

// models
let config = {};
let input;
let canvas;

const render = (function() {
	var worker = null;
	function render() {
		if (worker) {
			worker.terminate();
		}
		if (!input || !input.length) return;
		worker = new Worker("./worker-hint.packed.js");
		worker.onmessage = function(message) {
			// worker = null;
			console.log(message.data);
			Renderer.renderPreview(canvas, message.data, config.strategy);
		};
		worker.postMessage({ input, strategy: config.strategy });
	}
	return render;
})();

var strategyControlTypes = {
	RISE: "VQ",
	SINK: "VQ",
	RISE_DIAGH: "VQ",
	SINK_DIAGL: "VQ",
	TOP_CUT: "VQ",
	BOTTOM_CUT: "VQ",
	TOP_CUT_DIAGH: "VQ",
	TOP_CUT_DIAG_DIST: "VQ",
	BOTTOM_CUT_DIAGL: "VQ",
	BOTTOM_CUT_DIAG_DIST: "VQ",
	GRAVITY: "VQ",
	CONCENTRATE: "VQ",
	CHEBYSHEV_4: "VQ",
	CHEBYSHEV_5: "VQ",
	CANONICAL_STEM_WIDTH: "VQ",
	CANONICAL_STEM_WIDTH_DENSE: "VQ",
	TOP_UNIFY_FORCE: "VQ",
	TOP_UNIFY_FORCE_DIAG: "VQ",
	BOTTOM_UNIFY_FORCE: "VQ",
	BOTTOM_UNIFY_FORCE_DIAG: "VQ",
	MAX_SW_OVERFLOW_CPXS: "VQ",
	MAX_SW_SHRINK_CPXS: "VQ"
};

var strategyControlGroups = [
	["UPM", "BLUEZONE_WIDTH"],
	[
		"BLUEZONE_TOP_CENTER",
		"BLUEZONE_TOP_LIMIT",
		"BLUEZONE_BOTTOM_CENTER",
		"BLUEZONE_BOTTOM_LIMIT"
	],
	[
		"TOP_CUT",
		"BOTTOM_CUT",
		"TOP_CUT_DIAGH",
		"BOTTOM_CUT_DIAGL",
		"TOP_CUT_DIAG_DIST",
		"BOTTOM_CUT_DIAG_DIST",
		"TOP_UNIFY_FORCE",
		"TOP_UNIFY_FORCE_DIAG",
		"BOTTOM_UNIFY_FORCE",
		"BOTTOM_UNIFY_FORCE_DIAG"
	],
	[
		"RISE",
		"SINK",
		"RISE_DIAGH",
		"SINK_DIAGL",
		"GRAVITY",
		"CONCENTRATE",
		"CHEBYSHEV_4",
		"CHEBYSHEV_5"
	],
	[
		"CANONICAL_STEM_WIDTH",
		"CANONICAL_STEM_WIDTH_DENSE",
		"MAX_SW_OVERFLOW_CPXS",
		"MAX_SW_SHRINK_CPXS"
	],
	[
		"STEM_SIDE_MIN_RISE",
		"STEM_SIDE_MIN_DESCENT",
		"STEM_CENTER_MIN_RISE",
		"STEM_CENTER_MIN_DESCENT",
		"STEM_SIDE_MIN_DIST_RISE",
		"STEM_SIDE_MIN_DIST_DESCENT"
	],
	[
		"ABSORPTION_LIMIT",
		"SLOPE_FUZZ",
		"SLOPE_FUZZ_R",
		"SLOPE_FUZZ_POS",
		"SLOPE_FUZZ_POST",
		"SLOPE_FUZZ_NEG",
		"Y_FUZZ",
		"Y_FUZZ_DIAG"
	]
];

let controls = {};
controls.NUMERIC = function(ol, key, strategy, initVal, callback) {
	var d = document.createElement("li");
	d.innerHTML += "<span>" + key + "</span>";
	var input = document.createElement("input");
	input.value = initVal;
	input.type = "number";

	input.onchange = function() {
		return callback(input.value - 0);
	};
	function btn(shift) {
		var button = document.createElement("button");
		button.innerHTML = shift > 0 ? "+" + shift : "-" + -shift;
		button.onclick = function() {
			input.value = input.value - 0 + shift;
			return callback(input.value - 0);
		};
		d.appendChild(button);
	}
	btn(-100), btn(-50), btn(-10), btn(-5), btn(-1), btn(-0.1);
	d.appendChild(input);
	btn(0.1), btn(1), btn(5), btn(10), btn(50), btn(100);
	ol.appendChild(d);
};
controls.VQ = function(ol, key, strategy, initVal, callback) {
	var d = document.createElement("li");
	d.className = "VQ";
	d.innerHTML += "<span>" + key + "</span>";

	let vqModel = [],
		panels = [];
	for (let j = strategy.PPEM_MIN; j <= strategy.PPEM_MAX; j++) {
		vqModel[j] = {
			focus: false,
			val: 0
		};
		let panel = document.createElement("label");
		panel.className = "vq-panel";
		panel.innerHTML += j;
		let input = document.createElement("input");
		input.value = vqModel[j].val;
		input.setAttribute("size", 1);
		input.onfocus = function(e) {
			input.value = "";
		};
		input.onchange = function() {
			vqModel[j].val = input.value - 0 || 0;
			vqModel[j].focus = true;
			update();
		};
		panel.oncontextmenu = function(e) {
			vqModel[j].focus = !vqModel[j].focus;
			e.stopPropagation();
			e.preventDefault();
			update();
		};
		panel.onwheel = function(e) {
			if (e.deltaY > 0) {
				vqModel[j].val = (input.value - 0 || 0) - 1;
				vqModel[j].focus = true;
				update();
			} else if (e.deltaY < 0) {
				vqModel[j].val = (input.value - 0 || 0) + 1;
				vqModel[j].focus = true;
				update();
			}
			e.stopPropagation();
			e.preventDefault();
		};
		panels[j] = {
			panel: panel,
			input: input
		};
		panel.appendChild(input);
		d.appendChild(panel);
	}

	if (initVal && initVal instanceof Array) {
		const f = monoip(initVal);
		for (let k of initVal)
			if (vqModel[k[0]]) {
				vqModel[k[0]].focus = true;
				vqModel[k[0]].val = f(k[0]);
			}
	} else if (typeof initVal === "number") {
		vqModel[strategy.PPEM_MIN].focus = true;
		vqModel[strategy.PPEM_MIN].val = initVal;
		vqModel[strategy.PPEM_MAX].focus = true;
		vqModel[strategy.PPEM_MAX].val = initVal;
	}

	function update(nocb, initVal) {
		let a = [];
		if (initVal && initVal instanceof Array) {
			a = [...initVal];
			for (let j = strategy.PPEM_MIN; j <= strategy.PPEM_MAX; j++) {
				panels[j].input.className = vqModel[j].focus ? "focus" : "interpolated";
			}
		} else {
			for (let j = strategy.PPEM_MIN; j <= strategy.PPEM_MAX; j++) {
				if (vqModel[j].focus) a.push([j, vqModel[j].val || 0]);
				panels[j].input.className = vqModel[j].focus ? "focus" : "interpolated";
			}
		}
		const f = monoip(a);
		for (let j = strategy.PPEM_MIN; j <= strategy.PPEM_MAX; j++) {
			panels[j].input.value = vqModel[j].val = Math.round(f(j));
		}
		if (!nocb) {
			setTimeout(function() {
				callback(a);
			}, 0);
		}
	}
	ol.appendChild(d);
	update(true, initVal);
};

function createAdjusters() {
	var container = document.getElementById("adjusters");
	saveloadpanels: {
		const table = document.createElement("table");
		const tr = document.createElement("tr");
		savebutton: {
			const pane = document.createElement("td");
			pane.className = "saver pane";
			const save = document.createElement("button");
			save.innerHTML = "Save Parameters";
			save.onclick = function(e) {
				let buf = ["[hinting]"];
				for (let k in config.strategy) {
					if (config.strategy[k] !== config.defaultStrategy[k] && k !== "gears") {
						buf.push(k + " = " + JSON.stringify(config.strategy[k]));
					}
				}
				$.post(
					"/save",
					{
						to: config.paramPath,
						content: buf.join("\n")
					},
					function() {}
				);
				e.preventDefault();
				e.stopPropagation();
			};
			pane.appendChild(save);
			pane.appendChild(document.createTextNode(" → " + config.paramPath));
			tr.appendChild(pane);
		}
		initLoadButton: {
			const pane = document.createElement("td");
			pane.className = "loader pane";

			const chars = document.createElement("input");
			chars.value = config.w;
			const load = document.createElement("button");
			load.innerHTML = "Load Sample";
			load.onclick = function(e) {
				loadSamples(chars.value);
				e.preventDefault();
				e.stopPropagation();
			};
			pane.appendChild(chars);
			pane.appendChild(load);
			tr.appendChild(pane);
		}
		table.appendChild(tr);
		container.appendChild(table);
	}
	// parameter controllers
	for (var g = 0; g < strategyControlGroups.length; g++) {
		var ol = document.createElement("ol");
		for (var j = 0; j < strategyControlGroups[g].length; j++) {
			const key = strategyControlGroups[g][j];
			const keyType = strategyControlTypes[key] || "NUMERIC";
			controls[keyType](ol, key, config.strategy, config.strategy[key], function(x) {
				config.strategy[key] = x;
				setTimeout(render, 0);
			});
		}
		container.appendChild(ol);
	}
}

function loadSamples(w) {
	Renderer.renderLoading(canvas);
	$.getJSON(
		"/chars?w=" + encodeURIComponent(w) + "&f=" + encodeURIComponent(config.input),
		function(data) {
			input = data.filter(x => x);
			Renderer.clean(canvas);
			setTimeout(render, 0);
		}
	);
}

$.getJSON("/config", function(conf) {
	config = conf;
	canvas = document.getElementById("preview");
	createAdjusters();
	loadSamples(config.w);
});

},{"../support/monotonic-interpolate":4,"./render":7}],7:[function(require,module,exports){
const BG_COLOR = "white";
const { interpretIDH } = require("../core/interpreter/interpretIDH");

function interpretTT(glyphs, strategy, ppem) {
	for (var j = 0; j < glyphs.length; j++) {
		var glyph = glyphs[j].glyph,
			si = glyphs[j].si;
		var actions = glyphs[j].hints[ppem];

		interpretIDH(glyph, si, actions, strategy, ppem);
	}
}

const SUPERSAMPLING = 8;
const SAMPLING_Y = 4;
const DPI = 2;
const GAMMA = 1.5;

function renderTTFCurve(h, zs, m, txp, typ) {
	if (zs.length < 3) return;
	if (!zs[0].on) {
		// the contour starts at an off point
		if (zs[1].on) {
			zs = [...zs.slice(1), zs[0]];
		} else {
			zs = [
				{
					xtouch: (zs[0].xtouch + zs[1].xtouch) / 2,
					ytouch: (zs[0].ytouch + zs[1].ytouch) / 2,
					on: true
				},
				...zs.slice(1),
				zs[0]
			];
		}
	}
	zs.push(zs[0]);
	h.moveTo(txp(zs[0].xtouch, m), typ(zs[0].ytouch));
	for (var k = 1; k < zs.length; k++) {
		if (zs[k].on || !zs[k + 1]) {
			h.lineTo(txp(zs[k].xtouch, m), typ(zs[k].ytouch));
		} else {
			if (zs[k + 1].on) {
				h.quadraticCurveTo(
					txp(zs[k].xtouch, m),
					typ(zs[k].ytouch),
					txp(zs[k + 1].xtouch, m),
					typ(zs[k + 1].ytouch)
				);
				k += 1;
			} else {
				h.quadraticCurveTo(
					txp(zs[k].xtouch, m),
					typ(zs[k].ytouch),
					txp((zs[k].xtouch + zs[k + 1].xtouch) / 2, m),
					typ((zs[k].ytouch + zs[k + 1].ytouch) / 2)
				);
			}
		}
	}
}

function RenderPreviewForPPEM(glyphs, strategy, hdc, basex, basey, ppem) {
	const uppx = strategy.UPM / ppem;

	interpretTT(glyphs, strategy, ppem);

	// Create a temp canvas
	const hpixels = ppem * glyphs.length;
	const vpixels = ppem + 1;
	var eTemp = document.createElement("canvas");
	eTemp.width = hpixels * 3 * SUPERSAMPLING;
	eTemp.height = vpixels * SAMPLING_Y;
	var hTemp = eTemp.getContext("2d");
	hTemp.fillStyle = "white";
	hTemp.fillRect(0, 0, eTemp.width, eTemp.height);

	function txp(x, m) {
		let v = (x + m * strategy.UPM) / uppx * 3 * SUPERSAMPLING;
		if (!isFinite(v)) v = 0;
		return v;
	}
	function typ(y) {
		let v = (-y / uppx + Math.round(strategy.BLUEZONE_TOP_CENTER / uppx) + 1) * SAMPLING_Y;
		if (!isFinite(v)) v = 0;
		return v;
	}
	// Fill
	hTemp.fillStyle = "black";
	for (var m = 0; m < glyphs.length; m++) {
		hTemp.beginPath();
		for (var j = 0; j < glyphs[m].glyph.contours.length; j++) {
			renderTTFCurve(hTemp, glyphs[m].glyph.contours[j].points.slice(0), m, txp, typ);
			hTemp.closePath();
		}
		hTemp.fill("nonzero");
	}

	// Downsampling
	const ori = hTemp.getImageData(0, 0, eTemp.width, eTemp.height);
	const eAA = document.createElement("canvas");
	eAA.width = hpixels;
	eAA.height = vpixels;
	const hAA = eAA.getContext("2d");

	for (var j = 0; j < vpixels; j++) {
		let aa = hAA.createImageData(hpixels, 1);
		for (var k = 0; k < hpixels; k++) {
			aa.data[k * 4] = 0xff;
			aa.data[k * 4 + 1] = 0;
			aa.data[k * 4 + 2] = 0;
			aa.data[k * 4 + 3] = 0xff;
			for (var component = 0; component < 3; component++) {
				let coverage = 0;
				for (let ssy = 0; ssy < SAMPLING_Y; ssy++)
					for (let ss = -SUPERSAMPLING; ss < SUPERSAMPLING * 2; ss++) {
						const origRow = j * SAMPLING_Y + ssy;
						let origCol = (k * 3 + component) * SUPERSAMPLING + ss;
						if (origCol < 0) origCol = 0;
						if (origCol >= eTemp.width) origCol = eTemp.width - 1;
						const origPixelId = eTemp.width * origRow + origCol;
						const raw = ori.data[origPixelId * 4];
						coverage += raw < 128 ? 1 : 0;
					}
				const alpha = coverage / (3 * SUPERSAMPLING * SAMPLING_Y);
				aa.data[k * 4 + component] = 255 * Math.pow(1 - alpha, 1 / GAMMA);
			}
		}
		hAA.putImageData(aa, 0, j);
	}
	hdc.imageSmoothingEnabled = false;
	hdc.drawImage(eAA, basex, basey, eAA.width * DPI, eAA.height * DPI);
}

let renderHandle = { handle: null };

function renderPreview(canvas, glyphs, strategy) {
	if (!canvas) return;
	const hPreview = canvas.getContext("2d");
	hPreview.font = 12 * DPI + "px sans-serif";
	let y = 10 * DPI;
	let ppem = strategy.PPEM_MIN;
	function renderView() {
		// fill with white
		hPreview.fillStyle = BG_COLOR;
		hPreview.fillRect(0, y, 128 + glyphs.length * DPI * ppem, y + DPI * ppem);
		// render
		RenderPreviewForPPEM(glyphs, strategy, hPreview, 128, y, ppem);
		hPreview.fillStyle = "black";
		hPreview.fillText(
			ppem + "",
			0,
			y + ppem * (strategy.BLUEZONE_TOP_CENTER / strategy.UPM) * DPI
		);
		y += Math.round(ppem * 1.2) * DPI;
		ppem += 1;
		if (ppem <= strategy.PPEM_MAX) {
			if (renderHandle.handle) {
				clearTimeout(renderHandle.handle);
			}
			setTimeout(renderView, 0);
		} else {
			renderHandle.handle = null;
		}
	}
	if (renderHandle.handle) {
		clearTimeout(renderHandle.handle);
	}
	setTimeout(renderView, 0);
}

function clean(canvas) {
	if (!canvas) return;
	const hPreview = canvas.getContext("2d");
	hPreview.fillStyle = BG_COLOR;
	hPreview.fillRect(0, 0, canvas.width, canvas.height);
}

function renderLoading(canvas) {
	if (!canvas) return;
	const hPreview = canvas.getContext("2d");
	hPreview.fillStyle = BG_COLOR;
	hPreview.fillRect(0, 0, canvas.width, canvas.height);
	hPreview.font = "24px sans-serif";
	hPreview.fillStyle = "black";
	hPreview.fillText("Loading...", 24, 24);
}

exports.renderPreview = renderPreview;
exports.clean = clean;
exports.renderLoading = renderLoading;

},{"../core/interpreter/interpretIDH":1}]},{},[6]);
