diff --git a/bddy.js b/bddy.js index 8d3bca31f..7017e717f 100644 --- a/bddy.js +++ b/bddy.js @@ -86,6 +86,7 @@ module.exports = function(ctx, forany, argv, bddy) { forany.file(`build/kanji0`).def(ctx.ensureDir); forany.file(`build/punct0`).def(ctx.ensureDir); forany.file(`build/pass0`).def(ctx.ensureDir); + forany.file(`build/ws0`).def(ctx.ensureDir); forany.file(`build/pass1`).def(ctx.ensureDir); forany.file(`build/out`).def(ctx.ensureDir); forany.file(`build/ttc`).def(ctx.ensureDir); @@ -111,13 +112,15 @@ module.exports = function(ctx, forany, argv, bddy) { }); forany.file(`build/pass1/*.ttf`).def(async function(target) { await this.check(`${target.dir}`); - const [$1, $2] = await this.need( + const [$1, $2, $3] = await this.need( `sources/iosevka/iosevka-${styleOf(target.name)}.ttf`, - `build/punct0/${deItalizedNameOf(target.name)}.ttf` + `build/punct0/${deItalizedNameOf(target.name)}.ttf`, + `build/ws0/${deItalizedNameOf(target.name)}.ttf` ); await runBuildTask.call(this, "build-pass1/build.js", { main: $1, asian: $2, + ws: $3, o: target + ".tmp.ttf", italize: deItalizedNameOf(target.name) === target.name ? false : true }); @@ -127,7 +130,15 @@ module.exports = function(ctx, forany, argv, bddy) { await this.check(`${target.dir}`); const [$1] = await this.need(`sources/shs/${target.name}.otf`); const tmpOTD = `${target.dir}/${target.name}.otd`; - await runBuildTask.call(this, "build-punct/build.js", { main: $1, o: tmpOTD }); + await runBuildTask.call(this, "build-punct/as.js", { main: $1, o: tmpOTD }); + await this.run("otfccbuild", tmpOTD, "-o", target, "-q"); + await this.rm(tmpOTD); + }); + forany.file(`build/ws0/*.ttf`).def(async function(target) { + await this.check(`${target.dir}`); + const [$1] = await this.need(`sources/shs/${target.name}.otf`); + const tmpOTD = `${target.dir}/${target.name}.otd`; + await runBuildTask.call(this, "build-punct/ws.js", { main: $1, o: tmpOTD }); await this.run("otfccbuild", tmpOTD, "-o", target, "-q"); await this.rm(tmpOTD); }); diff --git a/build-pass1/build.js b/build-pass1/build.js index c9cceae2d..6245232ed 100644 --- a/build-pass1/build.js +++ b/build-pass1/build.js @@ -1,6 +1,12 @@ "use strict"; -const { quadify, introduce, build, gc, merge: { above: merge } } = require("megaminx"); +const { + quadify, + introduce, + build, + gc, + merge: { above: mergeAbove, below: mergeBelow } +} = require("megaminx"); const { isKanji } = require("caryll-iddb"); const italize = require("../common/italize"); @@ -15,6 +21,11 @@ async function pass(ctx, config, argv) { prefix: "b", ignoreHints: true }); + const c = await ctx.run(introduce, "c", { + from: argv.ws, + prefix: "c", + ignoreHints: true + }); // vhea a.vhea = b.vhea; @@ -27,7 +38,9 @@ async function pass(ctx, config, argv) { if (argv.italize) italize(b, 10); // merge and build - await ctx.run(merge, "a", "a", "b", { mergeOTL: true }); + await ctx.run(mergeBelow, "a", "a", "c", { mergeOTL: true }); + await ctx.run(mergeAbove, "a", "a", "b", { mergeOTL: true }); + await ctx.run(gc, "a"); await ctx.run(build, "a", { to: config.o, optimize: true }); } diff --git a/build-punct/as.js b/build-punct/as.js new file mode 100644 index 000000000..8186c10d5 --- /dev/null +++ b/build-punct/as.js @@ -0,0 +1,31 @@ +"use strict"; + +const { quadify, introduce, build, gc, manip: { glyph: glyphManip } } = require("megaminx"); +const { isKanji } = require("caryll-iddb"); +const { isWestern, isWS, isKorean, sanitizeSymbols, removeUnusedFeatures } = require("./common"); + +async function pass(ctx, config, argv) { + const a = await ctx.run(introduce, "a", { + from: argv.main, + prefix: "a", + ignoreHints: true + }); + await ctx.run(quadify, "a"); + a.cmap_uvs = null; + for (let c in a.cmap) { + if (isKanji(c - 0) || isWestern(c - 0) || isKorean(c - 0) || isWS(c - 0)) { + a.cmap[c] = null; + } + } + + removeUnusedFeatures(ctx.items.a); + await ctx.run(gc, "a", { ignoreAltSub: true }); + await ctx.run(glyphManip, "a", sanitizeSymbols, config); + + await ctx.run(build, "a", { to: config.o, optimize: true }); + ctx.remove("a"); +} + +module.exports = async function makeFont(ctx, config, argv) { + await pass(ctx, { o: argv.o }, argv); +}; diff --git a/build-punct/build.js b/build-punct/build.js deleted file mode 100644 index a830e49ac..000000000 --- a/build-punct/build.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; - -const { quadify, introduce, build, gc, manip: { glyph: glyphManip } } = require("megaminx"); -const { isKanji } = require("caryll-iddb"); - -function isWestern(c) { - return c < 0x2000; -} - -function isKorean(c) { - return ( - (c >= 0xac00 && c <= 0xd7af) || - (c >= 0x3130 && c <= 0x318f) || - (c >= 0x3200 && c <= 0x321e) || - (c >= 0xffa1 && c <= 0xffdc) || - (c >= 0x3260 && c <= 0x327f) || - (c >= 0xa960 && c <= 0xd7ff) - ); -} - -async function sanitizeSymbols(config) { - for (let g in this.font.glyf) { - const glyph = this.font.glyf[g]; - if (!glyph) continue; - const targetW = Math.ceil(glyph.advanceWidth / (this.em / 2)) * (this.em / 2); - const shift = (targetW - glyph.advanceWidth) / 2; - if (!glyph.contours) continue; - for (let c of glyph.contours) for (let z of c) z.x += shift; - glyph.advanceWidth = targetW; - } -} - -async function pass(ctx, config, argv) { - const a = await ctx.run(introduce, "a", { - from: argv.main, - prefix: "a", - ignoreHints: true - }); - await ctx.run(quadify, "a"); - a.cmap_uvs = null; - for (let c in a.cmap) { - if (isKanji(c - 0) || isWestern(c - 0) || isKorean(c - 0)) { - a.cmap[c] = null; - } - } - - await ctx.run(gc, "a"); - await ctx.run(glyphManip, "a", sanitizeSymbols, config); - - await ctx.run(build, "a", { to: config.o, optimize: true }); - ctx.remove("a"); -} - -module.exports = async function makeFont(ctx, config, argv) { - await pass(ctx, { o: argv.o }, argv); -}; diff --git a/build-punct/common.js b/build-punct/common.js new file mode 100644 index 000000000..2e34755f6 --- /dev/null +++ b/build-punct/common.js @@ -0,0 +1,97 @@ +"use strict"; + +exports.isWestern = c => c <= 0x2000; +exports.isKorean = c => + (c >= 0xac00 && c <= 0xd7af) || + (c >= 0x3130 && c <= 0x318f) || + (c >= 0x3200 && c <= 0x321e) || + (c >= 0xffa1 && c <= 0xffdc) || + (c >= 0x3260 && c <= 0x327f) || + (c >= 0xa960 && c <= 0xd7ff); + +exports.isWS = c => c >= 0x20a0 && c < 0x3000 && !(c >= 0x2e3a && c <= 0x2e3b); + +function deleteGPOS(font, gid) { + if (!font.GPOS) return; + for (let l in font.GPOS.lookups) { + let lut = font.GPOS.lookups[l]; + switch (lut.type) { + case "gpos_single": + for (let st of lut.subtables) st[gid] = null; + break; + } + } +} + +const sanitizers = {}; +sanitizers.auto = function(glyph) { + const targetW = Math.ceil(glyph.advanceWidth / (this.em / 2)) * (this.em / 2); + const shift = (targetW - glyph.advanceWidth) / 2; + if (!glyph.contours) return; + for (let c of glyph.contours) for (let z of c) z.x += shift; + glyph.advanceWidth = targetW; +}; +sanitizers.halfLeft = function(glyph, gid) { + glyph.advanceWidth = this.em / 2; + deleteGPOS(this.font, gid); +}; +sanitizers.halfRight = function(glyph, gid) { + if (!glyph.contours) return; + for (let c of glyph.contours) for (let z of c) z.x -= glyph.advanceWidth - this.em / 2; + glyph.advanceWidth = this.em / 2; + deleteGPOS(this.font, gid); +}; +sanitizers.halfComp = function(glyph, gid) { + const targetW = Math.round(glyph.advanceWidth / this.em) * (this.em / 2); + if (!glyph.contours) return; + for (let c of glyph.contours) for (let z of c) z.x *= targetW / glyph.advanceWidth; + glyph.advanceWidth = targetW; + deleteGPOS(this.font, gid); +}; + +const sanitizerTypes = { + "“": "halfRight", + "‘": "halfRight", + "’": "halfLeft", + "”": "halfLeft", + "—": "halfComp", + "\u2013": "halfComp", + "\u2011": "halfComp", + "\u2012": "halfComp", + "\u2010": "halfComp", + "\u2e3a": "halfComp", + "\u2e3b": "halfComp" +}; + +async function sanitizeSymbols(config) { + let san = new Map(); + for (let c in this.font.cmap) { + if (!this.font.cmap[c]) continue; + const stt = sanitizerTypes[String.fromCodePoint(c - 0)]; + if (stt) san.set(this.font.cmap[c], stt); + } + for (let g in this.font.glyf) { + let sanitizer = sanitizers[san.has(g) ? san.get(g) : "auto"]; + const glyph = this.font.glyf[g]; + if (!glyph) continue; + sanitizer.call(this, glyph, g); + } +} +exports.sanitizeSymbols = sanitizeSymbols; + +exports.removeUnusedFeatures = function(a) { + for (let f in a.GSUB.features) { + if ( + f.slice(0, 4) === "pwid" || + f.slice(0, 4) === "hwid" || + f.slice(0, 4) === "fwid" || + f.slice(0, 4) === "twid" || + f.slice(0, 4) === "qwid" + ) { + for (let l of a.GSUB.features[f]) { + a.GSUB.lookups[l] = null; + } + a.GSUB.features[f] = null; + } + } +}; diff --git a/build-punct/ws.js b/build-punct/ws.js new file mode 100644 index 000000000..274410024 --- /dev/null +++ b/build-punct/ws.js @@ -0,0 +1,30 @@ +"use strict"; + +const { quadify, introduce, build, gc, manip: { glyph: glyphManip } } = require("megaminx"); +const { isKanji } = require("caryll-iddb"); +const { isWestern, isWS, isKorean, sanitizeSymbols, removeUnusedFeatures } = require("./common"); + +async function pass(ctx, config, argv) { + const a = await ctx.run(introduce, "a", { + from: argv.main, + prefix: "a", + ignoreHints: true + }); + await ctx.run(quadify, "a"); + a.cmap_uvs = null; + for (let c in a.cmap) { + if (isKanji(c - 0) || isWestern(c - 0) || isKorean(c - 0) || !isWS(c - 0)) { + a.cmap[c] = null; + } + } + removeUnusedFeatures(ctx.items.a); + await ctx.run(gc, "a", { ignoreAltSub: true }); + await ctx.run(glyphManip, "a", sanitizeSymbols, config); + + await ctx.run(build, "a", { to: config.o, optimize: true }); + ctx.remove("a"); +} + +module.exports = async function makeFont(ctx, config, argv) { + await pass(ctx, { o: argv.o }, argv); +}; diff --git a/package.json b/package.json index 1ee3896d9..eb77b423a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "ideohint": "^0.99.4", "inquirer": "^3.2.3", "inquirer-file-path": "^1.0.0", - "megaminx": "^0.4.3", + "megaminx": "^0.4.5", "otfcc-ttcize": "^0.5.0", "scc-config": "^0.8.1", "which": "^1.2.14",