diff --git a/.eslintrc.json b/.eslintrc.json index 5fa3a5a59..75a0d07cf 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,7 @@ "experimentalObjectRestSpread": true } }, - "extends": "eslint:recommended", + "extends": ["eslint:recommended", "plugin:prettier/recommended"], "rules": { "indent": ["error", "tab", { "SwitchCase": 1 }], "quotes": ["error", "double", { "allowTemplateLiterals": true }], diff --git a/gen/build-font.js b/gen/build-font.js index 9ebd9c62f..c2c44487d 100644 --- a/gen/build-font.js +++ b/gen/build-font.js @@ -1,3 +1,5 @@ +"use strict"; + const EmptyFont = require("./empty-font.js"); const buildGlyphs = require("./build-glyphs.js"); @@ -6,6 +8,7 @@ const { assignFontNames } = require("../meta/naming"); const { setFontMetrics } = require("../meta/aesthetics"); const regulateGlyphs = require("../support/regulate-glyph"); +const gcFont = require("./gc"); module.exports = function(para) { const font = EmptyFont(); @@ -39,6 +42,8 @@ module.exports = function(para) { font.glyf = glyf; font.cmap = cmap; + gcFont(font); + return font; }; diff --git a/gen/empty-font.js b/gen/empty-font.js index 7f47c0b66..f5b2061f0 100644 --- a/gen/empty-font.js +++ b/gen/empty-font.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function() { return { head: { diff --git a/gen/gc.js b/gen/gc.js index b49b8bb57..2d645e2c7 100644 --- a/gen/gc.js +++ b/gen/gc.js @@ -1,18 +1,83 @@ "use strict"; -const { Workflow, introduce, build, gc } = require("megaminx"); -const argv = require("yargs").argv; +module.exports = function gcFont(font) { + let sink = new Set(); + let glyphCount = 0; -async function recipe(ctx, config, argv) { - const a = await ctx.run(introduce, "a", { from: argv.i }); - await ctx.run(gc, "a"); - await ctx.run(build, "a", { to: argv.o, optimize: true }); + sink.add(".notdef", ".null"); + + if (font.cmap) { + for (const k in font.cmap) { + if (font.cmap[k]) sink.add(font.cmap[k]); + } + } + if (font.cmap_uvs) { + for (const k in font.cmap_uvs) { + if (font.cmap_uvs[k]) sink.add(font.cmap_uvs[k]); + } + } + + do { + glyphCount = sink.size; + + if (font.GSUB) { + for (const l in font.GSUB.lookups) { + const lookup = font.GSUB.lookups[l]; + if (!lookup || !lookup.subtables) continue; + if (lookup && lookup.subtables) { + for (let st of lookup.subtables) { + markSubtable(sink, lookup.type, st); + } + } + } + } + + if (font.glyf) { + for (const g in font.glyf) { + const glyph = font.glyf[g]; + if (!glyph || !glyph.references) continue; + for (const ref of glyph.references) if (ref && ref.glyph) sink.add(ref.glyph); + } + } + + let glyphCount1 = sink.size; + if (glyphCount1 === glyphCount) break; + } while (true); + + if (font.glyf) { + const filteredGlyf = {}; + for (const key in font.glyf) { + if (sink.has(key)) filteredGlyf[key] = font.glyf[key]; + } + font.glyf = filteredGlyf; + } else { + font.glyf = {}; + } +}; + +function markSubtable(sink, type, st) { + switch (type) { + case "gsub_single": + case "gsub_multi": + case "gsub_alternate": + for (const k in st) if (sink.has(k) && st[k]) sink.add(st[k]); + break; + case "gsub_ligature": + for (const sub of st.substitutions) { + let check = true; + for (const g of sub.from) if (!sink.has(g)) check = false; + if (check && sub.to) sink.add(sub.to); + } + break; + case "gsub_chaining": + break; + case "gsub_reverse": + if (st.match && st.to) { + const matchCoverage = st.match[st.inputIndex]; + for (let j = 0; j < matchCoverage.length; j++) { + if (sink.has(matchCoverage[j]) && st.to[j]) sink.add(st.to[j]); + } + } + break; + } } - -async function main() { - const config = {}; - const flow = new Workflow(config); - await flow.run(recipe, config, argv); -} - -main(); diff --git a/glyphs/overmarks.ptl b/glyphs/overmarks.ptl index 7954ffd93..146146bca 100644 --- a/glyphs/overmarks.ptl +++ b/glyphs/overmarks.ptl @@ -4,7 +4,7 @@ import '../support/transform' as : Transform && [object [transformPoint tp]] import [curveToContour OffsetCurve] from '../support/curve-util' import [mix linreg clamp fallback] from '../support/utils' import [designParameters] from '../meta/aesthetics' -import [CubicBezierCurve autoQuadify] from "primitive-quadify-off-curves" +import [Curve Quadify] from "typo-geom" glyph-module @@ -209,7 +209,7 @@ glyph-block Overmarks : begin define z2 : tp currentGlyph.gizmo : object [x : mix leftEnd rightEnd tildeWaveX] [y : mix tbot ttop tildeWave] define z3 : tp currentGlyph.gizmo : object [x : mix leftEnd rightEnd (1 - tildeWaveX)] [y : mix tbot ttop (1 - tildeWave)] define z4 : tp currentGlyph.gizmo : object [x rightEnd] [y ttop] - define bone : new CubicBezierCurve z1 z2 z3 z4 + define bone : new Curve.Bez3 z1 z2 z3 z4 define inner : curveToContour [new OffsetCurve bone (+hs) HVCONTRAST] (1 / 256) define outer : curveToContour [new OffsetCurve bone (-hs) HVCONTRAST] (1 / 256) diff --git a/otl/gsub-ccmp.ptl b/otl/gsub-ccmp.ptl index 834f2d92c..342d286bc 100644 --- a/otl/gsub-ccmp.ptl +++ b/otl/gsub-ccmp.ptl @@ -36,13 +36,13 @@ export : define [buildCCMP sink glyphs markGlyphs] : begin define lookupCcmp2 : add-lookup sink : object .type 'gsub_ligature' - .subtables : list : object - psilivaria {'commaAbove' 'graveAbove'} - psilioxia {'commaAbove' 'acuteAbove'} - psiliperispomeni {'commaAbove' 'perispomeniAbove'} - dasiavaria {'revCommaAbove' 'graveAbove'} - dasiaoxia {'revCommaAbove' 'acuteAbove'} - dasiaperispomeni {'revCommaAbove' 'perispomeniAbove'} + .subtables : list : object : substitutions : list + object [from {'commaAbove' 'graveAbove'}] [to 'psilivaria'] + object [from {'commaAbove' 'acuteAbove'}] [to 'psilioxia'] + object [from {'commaAbove' 'perispomeniAbove'}] [to 'psiliperispomeni'] + object [from {'revCommaAbove' 'graveAbove'}] [to 'dasiavaria'] + object [from {'revCommaAbove' 'acuteAbove'}] [to 'dasiaoxia'] + object [from {'revCommaAbove' 'perispomeniAbove'}] [to 'dasiaperispomeni'] ccmp.lookups.push lookupCcmp1 lookupCcmp2 add-common-feature sink ccmp diff --git a/package.json b/package.json index 9d2404fda..4098c9b1f 100644 --- a/package.json +++ b/package.json @@ -12,28 +12,27 @@ }, "dependencies": { "caryll-shapeops": "^0.3.1", - "megaminx": "^0.9.0", "object-assign": "^4.1.1", "otfcc-ttcize": "^0.9.6", - "primitive-quadify-off-curves": "^0.6.3", + "typo-geom": "0.5.1", "spiro": "^1.1.0", "toml": "^3.0.0", - "topsort": "0.0.2", + "topsort": "^0.0.2", "ttf2woff": "^2.0.1", "ttf2woff2": "^3.0.0", "unorm": "^1.6.0", - "verda": "^1.0.0-12", - "fs-extra": "^8.1.0", - "yargs": "^15.1.0" + "verda": "^1.0.0", + "fs-extra": "^9.0.0", + "yargs": "^15.3.1" }, "devDependencies": { "cldr": "^5.5.4", - "colors": "^1.3.3", + "colors": "^1.4.0", "ejs": "^3.0.1", - "eslint": "^5.2.0", + "eslint": "^6.8.0", + "eslint-config-prettier": "^6.10.0", "patel": "^0.33.1", - "patrisika-scopes": "^0.11.1", - "semver": "^7.1.1", - "stylus": "^0.54.5" + "semver": "^7.1.3", + "stylus": "^0.54.7" } } diff --git a/support/curve-util.js b/support/curve-util.js index af8f3483d..c7ecc2522 100644 --- a/support/curve-util.js +++ b/support/curve-util.js @@ -1,6 +1,8 @@ "use strict"; -const quadify = require("primitive-quadify-off-curves"); +const typoGeom = require("typo-geom"); +const Point = require("./point"); +const { mix } = require("./utils"); exports.OffsetCurve = class OffsetCurve { constructor(bone, offset, contrast) { @@ -38,7 +40,7 @@ exports.curveToContour = function(curve, err) { cubic: false, on: true }); - const offPoints = quadify.autoQuadify(curve, err || 1); + const offPoints = typoGeom.Quadify.auto(curve, err || 1); for (let k = 0; k < offPoints.length; k++) { const z = offPoints[k]; if (k > 0) { @@ -64,3 +66,211 @@ exports.curveToContour = function(curve, err) { }); return exitPoints; }; + +function removeMids(contour) { + for (let rounds = 0; rounds < 255; rounds++) { + const n0 = contour.length; + let last = contour.length - 1; + for (let j = 0; j < contour.length - 1; j++) { + if ( + Math.abs(contour[j].x - contour[j + 1].x) < 1 && + Math.abs(contour[j].y - contour[j + 1].y) < 1 + ) { + contour[j + 1].rem = true; + contour[j].on = true; + } + } + while ( + last > 0 && + Math.abs(contour[0].x - contour[last].x) < 1 && + Math.abs(contour[0].y - contour[last].y) < 1 + ) { + contour[last].rem = true; + contour[0].on = true; + last -= 1; + } + contour = contour.filter(x => !x.rem); + + last = contour.length - 1; + for (let j = 1; j < contour.length - 1; j++) { + if (!contour[j - 1].on && contour[j].on && !contour[j + 1].on) { + const mx = contour[j - 1].x + contour[j + 1].x; + const my = contour[j - 1].y + contour[j + 1].y; + const dy = contour[j - 1].y - contour[j + 1].y; + if ( + Math.abs(dy) >= 1 && + Math.abs(contour[j].x * 2 - mx) < 1 && + Math.abs(contour[j].y * 2 - my) < 1 + ) { + contour[j].rem = true; + } + } + } + if (!contour[last].rem && !contour[last].on && contour[0].on && !contour[1].on) { + const mx = contour[last].x + contour[1].x; + const my = contour[last].y + contour[1].y; + if (Math.abs(contour[0].x * 2 - mx) < 1 && Math.abs(contour[0].y * 2 - my) < 1) { + contour[0].rem = true; + } + } + contour = contour.filter(x => !x.rem); + const n = contour.length; + if (n >= n0) break; + } + return contour; +} +function extPrior(a, b) { + return a.y < b.y || (a.y === b.y && ((a.on && !b.on) || (a.on === b.on && a.x < b.x))); +} + +function canonicalStart(_points) { + const points = _points.reverse().map(z => { + z.x = Math.round(z.x * 1024) / 1024; + z.y = Math.round(z.y * 1024) / 1024; + return z; + }); + let jm = 0; + for (var j = 0; j < points.length * 2; j++) { + if (extPrior(points[j % points.length], points[jm])) { + jm = j % points.length; + } + } + return points.slice(jm).concat(points.slice(0, jm)); +} + +function colinear(x1, y1, x2, y2, x3, y3, err) { + const det = x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2); + return det <= err && det >= -err; +} + +function inspan(a, b, c) { + if (a > c) return inspan(c, b, a); + return a <= b && b <= c; +} + +function handle(z1, z2, z3, z4, err) { + if ( + colinear(z1.x, z1.y, z2.x, z2.y, z4.x, z4.y, err) && + colinear(z1.x, z1.y, z3.x, z3.y, z4.x, z4.y, err) && + inspan(z1.x, z2.x, z4.x) && + inspan(z1.y, z2.y, z4.y) && + inspan(z1.x, z3.x, z4.x) && + inspan(z1.y, z3.y, z4.y) + ) { + return []; + } + + const curve = new typoGeom.Curve.Bez3(z1, z2, z3, z4); + const offPoints = typoGeom.Quadify.auto(curve, err); + const ans = []; + for (const z of offPoints) { + ans.push(Point.offFrom(z)); + } + return ans; +} + +function convertContourToTt(contour, err) { + if (contour.length === 0) return []; + if (contour.length === 1) return [contour[0]]; + err = err || 1 / 4; + + const newContour = []; + let z0 = contour[0]; + newContour.push(Point.cornerFrom(z0)); + + for (let j = 1; j < contour.length; j++) { + const z = contour[j]; + if (z.on) { + newContour.push(Point.cornerFrom(z)); + z0 = z; + } else if (z.cubic) { + const z1 = z; + const z2 = contour[j + 1]; + const z3 = contour[j + 2]; + const quadZs = handle(z0, z1, z2, z3, err); + for (const z of quadZs) newContour.push(z); + newContour.push(Point.cornerFrom(z3)); + z0 = z3; + j += 2; + } else { + const zc = z; + let zf = contour[j + 1] ? contour[j + 1] : contour[0]; + if (!zf.on) { + zf = Point.cornerFromXY(mix(zc.x, zf.x, 0.5), mix(zc.y, zf.y, 0.5)); + } + newContour.push(Point.offFrom(zc)); + newContour.push(Point.cornerFrom(zf)); + z0 = zf; + j++; + } + } + return newContour; +} + +function byFirstPointCoord(a, b) { + if (!a.length) return -1; + if (!b.length) return 1; + let z1 = a[0]; + let z2 = b[0]; + return z1.y !== z2.y + ? z1.y - z2.y + : z1.x !== z2.x + ? z1.x - z2.x + : byFirstPointCoord(a.slice(1), b.slice(1)); +} +function convertContourListToTt(contours, err) { + err = err || 1 / 4; + let ans = []; + for (let c of contours) { + ans.push(canonicalStart(removeMids(convertContourToTt(c, err)))); + } + return ans.sort(byFirstPointCoord); +} + +function convertContourToCubic(contour) { + if (!contour || !contour.length) return []; + + const newContour = []; + let z0 = contour[0]; + newContour.push(Point.cornerFrom(z0)); + + for (let j = 1; j < contour.length; j++) { + const z = contour[j]; + if (z.on) { + newContour.push(Point.cornerFrom(z)); + z0 = z; + } else if (z.cubic) { + const z1 = z; + const z2 = contour[j + 1]; + const z3 = contour[j + 2]; + newContour.push(Point.cubicOffFrom(z1)); + newContour.push(Point.cubicOffFrom(z2)); + newContour.push(Point.cornerFrom(z3)); + z0 = z3; + j += 2; + } else { + const zc = z; + let zf = contour[j + 1] ? contour[j + 1] : contour[0]; + if (!zf.on) { + zf = Point.cornerFromXY(mix(zc.x, zf.x, 0.5), mix(zc.y, zf.y, 0.5)); + } + + const x1 = mix(z0.x, zc.x, 2 / 3); + const y1 = mix(z0.y, zc.y, 2 / 3); + const x2 = mix(zf.x, zc.x, 2 / 3); + const y2 = mix(zf.y, zc.y, 2 / 3); + + newContour.push(Point.cubicOffFromXY(x1, y1)); + newContour.push(Point.cubicOffFromXY(x2, y2)); + newContour.push(Point.cornerFrom(zf)); + z0 = zf; + j++; + } + } + + return newContour; +} + +exports.convertContourToTt = convertContourToTt; +exports.convertContourToCubic = convertContourToCubic; +exports.convertContourListToTt = convertContourListToTt; diff --git a/support/fairify.js b/support/fairify.js index 806324a64..2ecbb7c6f 100644 --- a/support/fairify.js +++ b/support/fairify.js @@ -1,7 +1,7 @@ "use strict"; const Transform = require("./transform.js"); -const quadify = require("primitive-quadify-off-curves"); +const typoGeom = require("typo-geom"); const SMALL = 1e-4; @@ -197,7 +197,7 @@ class BezierCurveCluster { z4 = zs[j]; const z2 = mix(z1, z4, 1 / 3); const z3 = mix(z1, z4, 2 / 3); - const seg = new quadify.CubicBezierCurve(z1, z2, z3, z4); + const seg = new typoGeom.Curve.Bez3(z1, z2, z3, z4); segments.push(seg); lengths.push(this.measureLength(seg)); last = z4; @@ -206,7 +206,7 @@ class BezierCurveCluster { z2 = zs[j], z3 = zs[j + 1], z4 = zs[j + 2]; - const seg = new quadify.CubicBezierCurve(z1, z2, z3, z4); + const seg = new typoGeom.Curve.Bez3(z1, z2, z3, z4); segments.push(seg); lengths.push(this.measureLength(seg)); last = z4; @@ -217,7 +217,7 @@ class BezierCurveCluster { z4 = zs[j + 1]; const z2 = mix(zm, z1, 1 / 3); const z3 = mix(zm, z4, 1 / 3); - const seg = new quadify.CubicBezierCurve(z1, z2, z3, z4); + const seg = new typoGeom.Curve.Bez3(z1, z2, z3, z4); segments.push(seg); lengths.push(this.measureLength(seg)); last = z4; @@ -307,7 +307,7 @@ function buildCurve(curve) { if (nPtsOffPoints > 0) { const curve = new BezierCurveCluster(pts); if (curve.isAlmostLinear(1)) continue; - const offPoints = quadify.autoQuadify(curve, 1 / 4); + const offPoints = typoGeom.Quadify.auto(curve, 1 / 4); if (!offPoints) continue; for (let k = 0; k < offPoints.length; k++) { const z = offPoints[k]; diff --git a/support/glyph.ptl b/support/glyph.ptl index bfbedf94c..85299924b 100644 --- a/support/glyph.ptl +++ b/support/glyph.ptl @@ -177,38 +177,3 @@ export all : class Glyph this.start-from x y this.start-from mbx mby return this - - static [contourToStandardCubic contour] : begin - local c {} - if (!contour || !contour.length) : return c - - local z0 contour.0 - c.push : new Point z0.x z0.y true false false - for [local j 1] (j < contour.length) [inc j] : begin - local point contour.(j) - piecewise - point.on : begin - c.push : new Point point.x point.y true false false - set z0 point - point.cubic : begin - local z1 point - local z2 contour.(j + 1) - local z3 contour.(j + 2) - c.push : new Point z1.x z1.y false true false - c.push : new Point z2.x z2.y false true false - c.push : new Point z3.x z3.y true false false - set z0 z3 - set j : j + 2 - true : begin - local zc point - local zf : if contour.(j + 1) contour.(j + 1) contour.0 - local x1 : mix z0.x zc.x (2 / 3) - local y1 : mix z0.y zc.y (2 / 3) - local x2 : mix zf.x zc.x (2 / 3) - local y2 : mix zf.y zc.y (2 / 3) - c.push : new Point x1 y1 false true false - c.push : new Point x2 y2 false true false - c.push : new Point zf.x zf.y true false false - set z0 zf - inc j - return c diff --git a/support/ligation-data.js b/support/ligation-data.js index 847e79c59..742073a36 100644 --- a/support/ligation-data.js +++ b/support/ligation-data.js @@ -1,3 +1,5 @@ +"use strict"; + module.exports = function formVariantData(data, para) { const defaultBuildup = {}; diff --git a/support/mask-bit.js b/support/mask-bit.js index f93c8adc5..5aa505c00 100644 --- a/support/mask-bit.js +++ b/support/mask-bit.js @@ -1,5 +1,5 @@ -function maskBit(x, y) { - return x & (1 << y); -} - -module.exports = maskBit; +"use strict"; + +module.exports = function maskBit(x, y) { + return x & (1 << y); +}; diff --git a/support/monotonic-interpolate.js b/support/monotonic-interpolate.js index fb78907f3..adf01f8a8 100644 --- a/support/monotonic-interpolate.js +++ b/support/monotonic-interpolate.js @@ -1,112 +1,114 @@ -module.exports = function(xs, ys) { - let 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 - let result = +ys[0]; - return function() { - return result; - }; - } - - // Rearrange xs and ys so that xs is sorted - let indexes = []; - for (i = 0; i < length; i++) { - indexes.push(i); - } - indexes.sort(function(a, b) { - return xs[a] < xs[b] ? -1 : 1; - }); - let 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 - let dys = [], - dxs = [], - ms = []; - for (i = 0; i < length - 1; i++) { - const 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 - let c1s = [ms[0]]; - for (i = 0; i < dxs.length - 1; i++) { - const m = ms[i], - mNext = ms[i + 1]; - if (m * mNext <= 0) { - c1s.push(0); - } else { - const 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 - let c2s = [], - c3s = []; - for (i = 0; i < c1s.length - 1; i++) { - const 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 - let 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 - let low = 0, - mid, - high = c3s.length - 1; - while (low <= high) { - mid = Math.floor(0.5 * (low + high)); - let 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 - let diff = x - xs[i], - diffSq = diff * diff; - return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq; - }; -}; +"use strict"; + +module.exports = function(xs, ys) { + let 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 + let result = +ys[0]; + return function() { + return result; + }; + } + + // Rearrange xs and ys so that xs is sorted + let indexes = []; + for (i = 0; i < length; i++) { + indexes.push(i); + } + indexes.sort(function(a, b) { + return xs[a] < xs[b] ? -1 : 1; + }); + let 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 + let dys = [], + dxs = [], + ms = []; + for (i = 0; i < length - 1; i++) { + const 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 + let c1s = [ms[0]]; + for (i = 0; i < dxs.length - 1; i++) { + const m = ms[i], + mNext = ms[i + 1]; + if (m * mNext <= 0) { + c1s.push(0); + } else { + const 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 + let c2s = [], + c3s = []; + for (i = 0; i < c1s.length - 1; i++) { + const 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 + let 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 + let low = 0, + mid, + high = c3s.length - 1; + while (low <= high) { + mid = Math.floor(0.5 * (low + high)); + let 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 + let diff = x - xs[i], + diffSq = diff * diff; + return ys[i] + c1s[i] * diff + c2s[i] * diffSq + c3s[i] * diff * diffSq; + }; +}; diff --git a/support/param-blend.js b/support/param-blend.js index 3781f03a7..ab1b9c3a2 100644 --- a/support/param-blend.js +++ b/support/param-blend.js @@ -1,3 +1,5 @@ +"use strict"; + const blend = require("./monotonic-interpolate"); module.exports = function(aspect, hive, params, sink) { diff --git a/support/point.ptl b/support/point.ptl index df9e4450f..b8917689d 100644 --- a/support/point.ptl +++ b/support/point.ptl @@ -1,14 +1,28 @@ -export all : class Point - public [new x y on cubic subdivided] : begin - this.x = x - this.y = y - this.on = on || false - this.subdivided = subdivided || false - this.cubic = cubic || false - - static [transformed tfm x y on cubic subdivided] : new Point - * x * tfm.xx + y * tfm.yx + tfm.x - * x * tfm.xy + y * tfm.yy + tfm.y - * on - * cubic - * subdivided \ No newline at end of file +export all : class Point + public [new x y on cubic subdivided] : begin + this.x = x + this.y = y + this.on = on || false + this.cubic = cubic || false + this.subdivided = subdivided || false + + static [from z on cubic subdivided] : new Point + * z.x + * z.y + * on + * cubic + * subdivided + + static [cornerFrom z] : new Point z.x z.y true false false + static [offFrom z] : new Point z.x z.y false false false + static [cubicOffFrom z] : new Point z.x z.y false true false + static [cornerFromXY x y] : new Point x y true false false + static [offFromXY x y] : new Point x y false false false + static [cubicOffFromXY x y] : new Point x y false true false + + static [transformed tfm x y on cubic subdivided] : new Point + * x * tfm.xx + y * tfm.yx + tfm.x + * x * tfm.xy + y * tfm.yy + tfm.y + * on + * cubic + * subdivided diff --git a/support/regulate-glyph.js b/support/regulate-glyph.js index 4408dc4d6..15fb4b723 100644 --- a/support/regulate-glyph.js +++ b/support/regulate-glyph.js @@ -1,7 +1,9 @@ +"use strict"; + const Glyph = require("./glyph"); const autoRef = require("./autoref"); const caryllShapeOps = require("caryll-shapeops"); -const c2q = require("megaminx").geometry.c2q; +const curveUtil = require("./curve-util"); function regulateGlyph(g, skew) { if (!g.contours) return; @@ -47,9 +49,11 @@ function regulateGlyph(g, skew) { function simplifyContours(contours) { const source = []; for (const contour of contours) { - if (contour.length > 2) source.push(Glyph.contourToStandardCubic(contour)); + if (contour.length > 2) source.push(curveUtil.convertContourToCubic(contour)); } - const simplified = c2q.contours(caryllShapeOps.removeOverlap(source, 1, 1 << 17, true)); + const simplified = curveUtil.convertContourListToTt( + caryllShapeOps.removeOverlap(source, 1, 1 << 17, true) + ); const result = []; for (const contour of simplified) { if (contour.length > 2) result.push(contour); diff --git a/support/spirokit.ptl b/support/spirokit.ptl index d234da87b..dbfcf8e6d 100644 --- a/support/spirokit.ptl +++ b/support/spirokit.ptl @@ -2,6 +2,7 @@ import 'spiro' as SpiroJs import './spiroexpand' as SpiroExpansionContext import './fairify' as fairify import 'caryll-shapeops' as ShapeOps +import './curve-util' as CurveUtil import './transform' as Transform @@ -213,14 +214,14 @@ export : define [SetupBuilders args] : begin if (k.length == 0) : return g g.include k.0 - set g.contours : g.contours.map Glyph.contourToStandardCubic + set g.contours : g.contours.map CurveUtil.convertContourToCubic foreach [item : items-of : k.slice 1] : begin local g1 : new Glyph set g1.gizmo : this.gizmo || globalTransform g1.include item - set g1.contours : g1.contours.map Glyph.contourToStandardCubic + set g1.contours : g1.contours.map CurveUtil.convertContourToCubic local c1 : ShapeOps.boole operator g.contours g1.contours ShapeOps.fillRules.nonzero ShapeOps.fillRules.nonzero 16384 - set g.contours : c1.map : lambda [c] : [Glyph.contourToStandardCubic c].reverse + set g.contours : c1.map : lambda [c] : [CurveUtil.convertContourToCubic c].reverse if [not dontinc] : this.include g return g diff --git a/support/variant-data.js b/support/variant-data.js index 328e1cbf2..37ba8d80e 100644 --- a/support/variant-data.js +++ b/support/variant-data.js @@ -1,3 +1,5 @@ +"use strict"; + const objectAssign = require("object-assign"); function mergeVSHive(_target, source) { diff --git a/verdafile.js b/verdafile.js index 01fea4b34..9bb390815 100644 --- a/verdafile.js +++ b/verdafile.js @@ -21,7 +21,6 @@ const OTF2OTC = "otf2otc"; const PATEL_C = ["node", "./node_modules/patel/bin/patel-c"]; const TTCIZE = ["node", "./node_modules/otfcc-ttcize/bin/_startup"]; const GENERATE = ["node", "gen/generator"]; -const GC = ["node", "gen/gc"]; const webfontFormats = [ ["woff2", "woff2"], ["woff", "woff"], @@ -319,8 +318,6 @@ const BuildTTF = file.make( { hives, family, shapeWeight, menuWeight, menuStyle, menuWidth }, version ] = await target.need(HivesOf(fn), Version); - const otd = output.dir + "/" + output.name + ".otd"; - const ttfTmp = output.dir + "/" + output.name + ".tmp.ttf"; const otdTmp = output.dir + "/" + output.name + ".tmp.otd"; const charmap = output.dir + "/" + output.name + ".charmap"; await target.need(Scripts, fu`parameters.toml`, de`${output.dir}`); @@ -336,12 +333,13 @@ const BuildTTF = file.make( ["--menu-width", menuWidth], hives ); - await run("otfccbuild", otdTmp, "-o", ttfTmp, "-O3", "--keep-average-char-width"); - await run(GC, ["-i", ttfTmp], ["-o", otd]); - await run("otfccbuild", otd, "-o", output.full, "-O3", "--keep-average-char-width", "-q"); + await run( + "otfccbuild", + otdTmp, + ["-o", output.full], + ["-O3", "--keep-average-char-width", "-q"] + ); await rm(otdTmp); - await rm(ttfTmp); - await rm(otd); } ); const BuildCM = file.make(