diff --git a/.gitignore b/.gitignore index 31c5119b4..f135c7fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -43,8 +43,7 @@ release-archives/ testdrive/iosevka* # Generated scripts -font-src/gen/build-glyphs.js -font-src/gen/kits/**/*.js +font-src/kits/**/*.js font-src/meta/**/*.js font-src/otl/**/*.js font-src/glyphs/**/*.js diff --git a/README.md b/README.md index 076339e11..23607efd3 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Iosevka supports Language-Specific Ligations, which is the ligation set enabled To build Iosevka you should: -1. Ensure that [`nodejs`](http://nodejs.org) (≥ 12.16.0), [`ttfautohint`](http://www.freetype.org/ttfautohint/), [`otfcc`](https://github.com/caryll/otfcc) (≥ 0.10.3-alpha) and [`otf2otc`](https://github.com/adobe-type-tools/afdko) are present. +1. Ensure that [`nodejs`](http://nodejs.org) (≥ 12.16.0) and [`ttfautohint`](http://www.freetype.org/ttfautohint/) are present. 2. Install necessary libs by `npm install`. If you’ve installed them, upgrade to the latest. 3. `npm run build -- contents::iosevka`. diff --git a/font-src/gen/build-font.js b/font-src/gen/build-font.js index ff05e948c..3ed6cc66f 100644 --- a/font-src/gen/build-font.js +++ b/font-src/gen/build-font.js @@ -1,24 +1,25 @@ "use strict"; -const EmptyFont = require("./empty-font.js"); -const buildGlyphs = require("./build-glyphs.js"); +const EmptyFont = require("./empty-font"); +const buildGlyphs = require("../glyphs/index"); const finalizeFont = require("./finalize/index"); +const convertOtd = require("./otd-conv/index"); const { buildOtl } = require("../otl/index"); const { assignFontNames } = require("../meta/naming"); const { setFontMetrics } = require("../meta/aesthetics"); module.exports = function (para) { - const font = EmptyFont(); + const otd = EmptyFont(); const gs = buildGlyphs(para); - assignFontNames(para, gs.metrics, font); - setFontMetrics(para, gs.metrics, font); + assignFontNames(para, gs.metrics, otd); + setFontMetrics(para, gs.metrics, otd); const otl = buildOtl(para, gs.glyphStore); - font.GSUB = otl.GSUB; - font.GPOS = otl.GPOS; - font.GDEF = otl.GDEF; + otd.GSUB = otl.GSUB; + otd.GPOS = otl.GPOS; + otd.GDEF = otl.GDEF; // Regulate const excludeChars = new Set(); @@ -28,6 +29,7 @@ module.exports = function (para) { } } - const finalGs = finalizeFont(para, gs.glyphStore, excludeChars, font); + const finalGs = finalizeFont(para, gs.glyphStore, excludeChars, otd); + const font = convertOtd(otd, finalGs); return { font, glyphStore: finalGs }; }; diff --git a/font-src/gen/empty-font.js b/font-src/gen/empty-font.js index 57a8cc2b5..77ce6851a 100644 --- a/font-src/gen/empty-font.js +++ b/font-src/gen/empty-font.js @@ -76,20 +76,8 @@ module.exports = function () { ySuperscriptXSize: 665, ySuperscriptYOffset: 491, ySuperscriptYSize: 716, - ulCodePageRange1: { - latin1: true, - latin2: true, - greek: true, - cyrillic: true, - turkish: true, - vietnamese: true, - macRoman: true - }, - ulCodePageRange2: { - cp852: true, - cp850: true, - ascii: true - } + ulCodePageRange1: 0x2000011f, + ulCodePageRange2: 0xc4000000 }, post: { version: 2, diff --git a/font-src/gen/finalize/autoref.js b/font-src/gen/finalize/autoref.js deleted file mode 100644 index d1bc2b9b3..000000000 --- a/font-src/gen/finalize/autoref.js +++ /dev/null @@ -1,165 +0,0 @@ -"use strict"; - -const Point = require("../../support/point"); -const { AnyCv } = require("../../support/gr"); - -function autoref(glyphStore) { - suppressNaN(glyphStore); - hashContours(glyphStore); - - const glyphEntryList = getGlyphEntryList(glyphStore); - linkRefl(glyphEntryList); - linkComponent(glyphEntryList); - unlinkHybrid(glyphStore); -} - -function suppressNaN(glyphStore) { - for (const g of glyphStore.glyphs()) { - if (!g.contours) continue; - for (let k = 0; k < g.contours.length; k++) { - let contour = g.contours[k]; - for (let z of contour) { - if (!isFinite(z.x)) z.x = 0; - if (!isFinite(z.y)) z.y = 0; - } - } - } -} - -function hashContours(glyphStore) { - for (const g of glyphStore.glyphs()) { - if (!g.contours) continue; - for (let k = 0; k < g.contours.length; k++) { - const contour = g.contours[k]; - contour.hash = contourHash(contour); - } - } -} -function contourHash(c) { - if (!c || c.length < 2) return "."; - let lx = c[0].x, - ly = c[0].y; - let buf = ""; - for (let j = 1; j < c.length; j++) { - const z = c[j]; - buf += `${z.on ? "o" : "f"}${z.cubic ? "c" : "q"}${delta(z.x, lx)},${delta(z.y, ly)};`; - (lx = z.x), (ly = z.y); - } - return buf; -} -function delta(a, b) { - return Math.round((a - b) * 32); -} - -function getGlyphEntryList(glyphStore) { - const excludeUnicode = new Set(); - excludeUnicode.add(0x80); - for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c); - - for (const [j, gn, g] of glyphStore.indexedNamedEntries()) { - if (AnyCv.query(g).length) g.autoRefPriority = -1; - const us = glyphStore.queryUnicodeOf(g); - if (us) { - for (const u of us) if (excludeUnicode.has(u)) g.avoidBeingComposite = true; - } - } - - return Array.from(glyphStore.indexedNamedEntries()).sort(byGlyphPriority); -} - -function byGlyphPriority([ja, gna, a], [jb, gnb, b]) { - const pri1 = a.autoRefPriority || 0; - const pri2 = b.autoRefPriority || 0; - if (pri1 > pri2) return -1; - if (pri1 < pri2) return 1; - if (a.contours && b.contours && a.contours.length < b.contours.length) return 1; - if (a.contours && b.contours && a.contours.length > b.contours.length) return -1; - return 0; -} - -function linkRefl(glyphEntryList) { - for (let j = 0; j < glyphEntryList.length; j++) { - const [, gnj, gj] = glyphEntryList[j]; - if (!gj.contours.length || (gj.references && gj.references.length)) continue; - for (let k = j + 1; k < glyphEntryList.length; k++) { - const [, gnk, gk] = glyphEntryList[k]; - if (gj.contours.length === gk.contours.length) { - match(gnj, gj, gnk, gk); - } - } - } -} -function linkComponent(glyphEntryList) { - for (let j = 0; j < glyphEntryList.length; j++) { - const [, gnj, gj] = glyphEntryList[j]; - if (gj.autoRefPriority < 0) continue; - if (!gj.contours.length) continue; - if (gj.references && gj.references.length) continue; - for (let k = glyphEntryList.length - 1; k >= 0; k--) { - const [, gnk, gk] = glyphEntryList[k]; - if (gj.contours.length > gk.contours.length) continue; - if ( - gj.contours.length === gk.contours.length && - !(gk.references && gk.references.length) - ) { - continue; - } - while (match(gnj, gj, gnk, gk)) "pass"; - } - } -} -function match(gn1, g1, gn2, g2) { - for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) { - let found = true; - for (let k = j; k < g2.contours.length && k - j < g1.contours.length; k++) { - if ( - g1.contours[k - j].hash !== g2.contours[k].hash || - !( - k <= j || - (delta(g1.contours[k - j][0].x, g1.contours[k - j - 1][0].x) === - delta(g2.contours[k][0].x, g2.contours[k - 1][0].x) && - delta(g1.contours[k - j][0].y, g1.contours[k - j - 1][0].y) === - delta(g2.contours[k][0].y, g2.contours[k - 1][0].y)) - ) - ) { - found = false; - break; - } - } - if (found) { - const refX = g2.contours[j][0].x - g1.contours[0][0].x || 0; - const refY = g2.contours[j][0].y - g1.contours[0][0].y || 0; - if (Math.abs(refY) > 1 && g1.advanceWidth > 1) { - continue; - } - if (!g2.references) g2.references = []; - g2.references.push({ glyph: gn1, x: refX, y: refY, roundToGrid: false }); - g2.contours.splice(j, g1.contours.length); - return true; - } - } - return false; -} - -function unlinkHybrid(glyphStore) { - for (const g of glyphStore.glyphs()) { - if (!g.references || g.references.length === 0) continue; - if (!g.avoidBeingComposite && g.contours.length === 0) continue; - g.contours = unlinkRef(g, 0, 0, glyphStore); - g.references = []; - } -} - -function unlinkRef(g, dx, dy, glyphStore) { - let contours = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic))); - if (g.references) { - for (let r of g.references) { - contours = contours.concat( - unlinkRef(glyphStore.queryByName(r.glyph), r.x + dx, r.y + dy, glyphStore) - ); - } - } - return contours; -} - -module.exports = autoref; diff --git a/font-src/gen/finalize/gc.js b/font-src/gen/finalize/gc.js index d004caada..273c97cb7 100644 --- a/font-src/gen/finalize/gc.js +++ b/font-src/gen/finalize/gc.js @@ -6,7 +6,7 @@ module.exports = function gcFont(glyphStore, excludedChars, restFont, cfg) { markSweepOtl(restFont.GSUB); markSweepOtl(restFont.GPOS); const sink = mark(glyphStore, excludedChars, restFont, cfg); - return sweep(glyphStore, restFont, sink); + return sweep(glyphStore, sink); }; function markSweepOtl(table) { @@ -30,6 +30,44 @@ function markSweepOtl(table) { } table.features = features1; } + +function mark(glyphStore, excludedChars, restFont, cfg) { + const sink = markInitial(glyphStore, excludedChars); + while (markStep(glyphStore, sink, restFont, cfg)); + return sink; +} + +function markInitial(glyphStore, excludedChars) { + let sink = new Set(); + for (const [gName, g] of glyphStore.namedEntries()) { + if (!g) continue; + if (g.glyphRank > 0) sink.add(gName); + if (Radical.get(g)) sink.add(gName); + const unicodeSet = glyphStore.queryUnicodeOf(g); + if (unicodeSet) { + for (const u of unicodeSet) { + if (!excludedChars.has(u)) sink.add(gName); + } + } + } + return sink; +} + +function markStep(glyphStore, sink, restFont, cfg) { + const glyphCount = sink.size; + if (restFont.GSUB) { + for (const l in restFont.GSUB.lookups) { + const lookup = restFont.GSUB.lookups[l]; + if (!lookup || !lookup.subtables) continue; + for (let st of lookup.subtables) { + markSubtable(sink, lookup.type, st, cfg); + } + } + } + const glyphCount1 = sink.size; + return glyphCount1 > glyphCount; +} + function markLookups(table, sink) { if (!table || !table.features) return; for (let f in table.features) { @@ -57,43 +95,6 @@ function markLookups(table, sink) { } while (loop < 0xff && lookupSetChanged); } -function mark(glyphStore, excludedChars, restFont, cfg) { - const sink = markInitial(glyphStore, excludedChars); - while (markStep(sink, restFont, cfg)); - return sink; -} - -function markInitial(glyphStore, excludedChars) { - let sink = new Set(); - for (const [gName, g] of glyphStore.namedEntries()) { - if (!g) continue; - if (g.glyphRank > 0) sink.add(gName); - if (Radical.get(g)) sink.add(gName); - const unicodeSet = glyphStore.queryUnicodeOf(g); - if (unicodeSet) { - for (const u of unicodeSet) { - if (!excludedChars.has(u)) sink.add(gName); - } - } - } - return sink; -} - -function markStep(sink, restFont, cfg) { - const glyphCount = sink.size; - if (restFont.GSUB) { - for (const l in restFont.GSUB.lookups) { - const lookup = restFont.GSUB.lookups[l]; - if (!lookup || !lookup.subtables) continue; - for (let st of lookup.subtables) { - markSubtable(sink, lookup.type, st, cfg); - } - } - } - const glyphCount1 = sink.size; - return glyphCount1 > glyphCount; -} - function markSubtable(sink, type, st, cfg) { switch (type) { case "gsub_single": @@ -127,156 +128,6 @@ function markSubtable(sink, type, st, cfg) { } } -function sweep(glyphStore, restFont, gnSet) { - sweepOtl(restFont.GSUB, gnSet); - sweepOtl(restFont.GPOS, gnSet); +function sweep(glyphStore, gnSet) { return glyphStore.filterByName(gnSet); } - -function sweepOtl(table, gnSet) { - if (!table || !table.lookups) return; - for (const lid in table.lookups) { - const lookup = table.lookups[lid]; - if (!lookup.subtables) continue; - const newSubtables = []; - for (const st of lookup.subtables) { - const keep = sweepSubtable(st, lookup.type, gnSet); - if (keep) newSubtables.push(st); - } - lookup.subtables = newSubtables; - } -} - -function sweepSubtable(st, type, gnSet) { - switch (type) { - case "gsub_single": - return sweep_GsubSingle(st, gnSet); - case "gsub_multiple": - case "gsub_alternate": - return sweep_GsubMultiple(st, gnSet); - case "gsub_ligature": - return sweep_GsubLigature(st, gnSet); - case "gsub_chaining": - return sweep_GsubChaining(st, gnSet); - case "gsub_reverse": - return sweep_gsubReverse(st, gnSet); - case "gpos_mark_to_base": - case "gpos_mark_to_mark": - return sweep_gposMark(st, gnSet); - default: - return true; - } -} - -function sweep_GsubSingle(st, gnSet) { - let nonEmpty = false; - let from = Object.keys(st); - for (const gidFrom of from) { - if (!gnSet.has(gidFrom) || !gnSet.has(st[gidFrom])) { - delete st[gidFrom]; - } else { - nonEmpty = true; - } - } - return nonEmpty; -} - -function sweep_GsubMultiple(st, gnSet) { - let nonEmpty = false; - let from = Object.keys(st); - for (const gidFrom of from) { - let include = gnSet.has(gidFrom); - if (st[gidFrom]) { - for (const gidTo of st[gidFrom]) { - include = include && gnSet.has(gidTo); - } - } else { - include = false; - } - if (!include) { - delete st[gidFrom]; - } else { - nonEmpty = true; - } - } - return nonEmpty; -} - -function sweep_GsubLigature(st, gnSet) { - if (!st.substitutions) return false; - let newSubst = []; - for (const rule of st.substitutions) { - let include = true; - if (!gnSet.has(rule.to)) include = false; - for (const from of rule.from) if (!gnSet.has(from)) include = false; - if (include) newSubst.push(rule); - } - st.substitutions = newSubst; - return true; -} - -function sweep_GsubChaining(st, gnSet) { - const newMatch = []; - for (let j = 0; j < st.match.length; j++) { - newMatch[j] = []; - for (let k = 0; k < st.match[j].length; k++) { - const gidFrom = st.match[j][k]; - if (gnSet.has(gidFrom)) { - newMatch[j].push(gidFrom); - } - } - if (!newMatch[j].length) return false; - } - st.match = newMatch; - return true; -} - -function sweep_gsubReverse(st, gnSet) { - const newMatch = [], - newTo = []; - for (let j = 0; j < st.match.length; j++) { - newMatch[j] = []; - for (let k = 0; k < st.match[j].length; k++) { - const gidFrom = st.match[j][k]; - let include = gnSet.has(gidFrom); - if (j === st.inputIndex) { - include = include && gnSet.has(st.to[k]); - if (include) { - newMatch[j].push(gidFrom); - newTo.push(st.to[k]); - } - } else { - if (include) newMatch[j].push(gidFrom); - } - } - if (!newMatch[j].length) return false; - } - st.match = newMatch; - st.to = newTo; - return true; -} - -function sweep_gposMark(st, gnSet) { - let marks = st.marks || {}, - newMarks = {}, - hasMarks = false; - let bases = st.bases || {}, - newBases = {}, - hasBases = true; - - for (const gid in marks) { - if (gnSet.has(gid) && marks[gid]) { - newMarks[gid] = marks[gid]; - hasMarks = true; - } - } - for (const gid in bases) { - if (gnSet.has(gid) && bases[gid]) { - newBases[gid] = bases[gid]; - hasBases = true; - } - } - st.marks = newMarks; - st.bases = newBases; - return hasMarks && hasBases; -} diff --git a/font-src/gen/finalize/glyphs.js b/font-src/gen/finalize/glyphs.js new file mode 100644 index 000000000..d3588a9b0 --- /dev/null +++ b/font-src/gen/finalize/glyphs.js @@ -0,0 +1,144 @@ +"use strict"; + +const TypoGeom = require("typo-geom"); +const Point = require("../../support/point"); +const CurveUtil = require("../../support/curve-util"); + +module.exports = finalizeGlyphs; +function finalizeGlyphs(para, glyphStore) { + suppressNaN(glyphStore); + const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI); + regulateGlyphStore(skew, glyphStore); + return glyphStore; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +function suppressNaN(glyphStore) { + for (const g of glyphStore.glyphs()) { + if (!g.contours) continue; + for (let k = 0; k < g.contours.length; k++) { + let contour = g.contours[k]; + for (let z of contour) { + if (!isFinite(z.x)) z.x = 0; + if (!isFinite(z.y)) z.y = 0; + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +function regulateGlyphStore(skew, glyphStore) { + for (const g of glyphStore.glyphs()) { + if (!g.semanticInclusions || !g.contours) continue; + if (g.isPureComposite()) regulateCompositeGlyph(glyphStore, g); + } + for (const g of glyphStore.glyphs()) regulateSimpleGlyph(g, skew); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +function regulateCompositeGlyph(glyphStore, g) { + const references = []; + for (const sr of g.semanticInclusions) { + const gn = glyphStore.queryNameOf(sr.glyph); + if (!gn || sr.glyph.autoRefPriority < 0) return; + references.push({ glyph: gn, x: sr.x, y: sr.y }); + } + + g.semanticInclusions = []; + g.contours = []; + g.references = references; +} + +function regulateSimpleGlyph(g, skew) { + if (!g.contours || !g.contours.length) return; + for (const contour of g.contours) for (const z of contour) z.x -= z.y * skew; + g.contours = simplifyContours(g.contours); + for (const contour of g.contours) for (const z of contour) z.x += z.y * skew; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +function simplifyContours(source) { + const sink = new FairizedShapeSink(); + + TypoGeom.ShapeConv.transferGenericShape( + TypoGeom.Fairize.fairizeBezierShape( + TypoGeom.Boolean.removeOverlap( + CurveUtil.convertShapeToArcs(source), + TypoGeom.Boolean.PolyFillType.pftNonZero, + CurveUtil.BOOLE_RESOLUTION + ) + ), + sink, + CurveUtil.GEOMETRY_PRECISION + ); + + return sink.contours; +} + +class FairizedShapeSink { + constructor() { + this.contours = []; + this.lastContour = []; + } + beginShape() {} + endShape() { + if (this.lastContour.length > 2) { + const zFirst = this.lastContour[0], + zLast = this.lastContour[this.lastContour.length - 1]; + if (zFirst.on && zLast.on && zFirst.x === zLast.x && zFirst.y === zLast.y) { + this.lastContour.pop(); + } + this.contours.push(this.lastContour); + } + this.lastContour = []; + } + moveTo(x, y) { + this.endShape(); + this.lineTo(x, y); + } + lineTo(x, y) { + const z = Point.cornerFromXY(x, y).round(CurveUtil.RECIP_GEOMETRY_PRECISION); + while (this.lastContour.length >= 2) { + const a = this.lastContour[this.lastContour.length - 2], + b = this.lastContour[this.lastContour.length - 1]; + if (isLineExtend(a, b, z)) { + this.lastContour.pop(); + } else { + break; + } + } + this.lastContour.push(z); + } + arcTo(arc, x, y) { + const offPoints = TypoGeom.Quadify.auto(arc, 1, 16); + if (offPoints) { + for (const z of offPoints) + this.lastContour.push(Point.offFrom(z).round(CurveUtil.RECIP_GEOMETRY_PRECISION)); + } + this.lineTo(x, y); + } +} +function isLineExtend(a, b, c) { + return ( + a.on && + c.on && + ((aligned(a.x, b.x, c.x) && between(a.y, b.y, c.y)) || + (aligned(a.y, b.y, c.y) && between(a.x, b.x, c.x))) + ); +} +function geometryPrecisionEqual(a, b) { + return ( + Math.round(a * CurveUtil.RECIP_GEOMETRY_PRECISION) === + Math.round(b * CurveUtil.RECIP_GEOMETRY_PRECISION) + ); +} +function aligned(a, b, c) { + return geometryPrecisionEqual(a, b) && geometryPrecisionEqual(b, c); +} +function between(a, b, c) { + return (a <= b && b <= c) || (a >= b && b >= c); +} diff --git a/font-src/gen/finalize/index.js b/font-src/gen/finalize/index.js index 9deb12023..ee98b1f20 100644 --- a/font-src/gen/finalize/index.js +++ b/font-src/gen/finalize/index.js @@ -1,17 +1,12 @@ "use strict"; -const autoRef = require("./autoref"); -const TypoGeom = require("typo-geom"); -const Point = require("../../support/point"); - -const CurveUtil = require("../../support/curve-util"); +const finalizeGlyphs = require("./glyphs"); const gcFont = require("./gc"); -module.exports = function finalizeFont(para, glyphStore, excludedCodePoints, font) { +module.exports = function finalizeFont(para, glyphStore, excludedCodePoints, restFont) { glyphStore = forceMonospaceIfNeeded(para, glyphStore); - glyphStore = gcFont(glyphStore, excludedCodePoints, font, {}); - glyphStore = regulateGlyphStore(para, glyphStore); - extractGlyfCmap(glyphStore, font); + glyphStore = gcFont(glyphStore, excludedCodePoints, restFont, {}); + glyphStore = finalizeGlyphs(para, glyphStore); return glyphStore; }; @@ -25,121 +20,3 @@ function forceMonospaceIfNeeded(para, glyphStore) { } }); } - -function extractGlyfCmap(glyphStore, font) { - const glyf = {}; - const cmap = {}; - const sortedEntries = Array.from(glyphStore.indexedNamedEntries()).sort(byRank); - for (const [origIndex, name, g] of sortedEntries) { - glyf[name] = g; - const us = glyphStore.queryUnicodeOf(g); - if (us) { - for (const u of us) if (isFinite(u - 0) && u) cmap[u] = name; - } - } - font.glyf = glyf; - font.cmap = cmap; -} - -function regulateGlyphStore(para, glyphStore) { - autoRef(glyphStore); - - // regulate - const skew = Math.tan(((para.slopeAngle || 0) / 180) * Math.PI); - for (let g of glyphStore.glyphs()) regulateGlyph(g, skew); - - return glyphStore; -} - -function byRank([ja, gna, a], [jb, gnb, b]) { - return (b.glyphRank || 0) - (a.glyphRank || 0) || (ja || 0) - (jb || 0); -} - -function regulateGlyph(g, skew) { - if (!g.contours || !g.contours.length) return; - for (const contour of g.contours) for (const z of contour) z.x -= z.y * skew; - g.contours = simplifyContours(g.contours); - for (const contour of g.contours) for (const z of contour) z.x += z.y * skew; -} - -function simplifyContours(source) { - const sink = new FairizedShapeSink(); - - TypoGeom.ShapeConv.transferGenericShape( - TypoGeom.Fairize.fairizeBezierShape( - TypoGeom.Boolean.removeOverlap( - CurveUtil.convertShapeToArcs(source), - TypoGeom.Boolean.PolyFillType.pftNonZero, - CurveUtil.BOOLE_RESOLUTION - ) - ), - sink, - CurveUtil.GEOMETRY_PRECISION - ); - - return sink.contours; -} - -class FairizedShapeSink { - constructor() { - this.contours = []; - this.lastContour = []; - } - beginShape() {} - endShape() { - if (this.lastContour.length > 2) { - const zFirst = this.lastContour[0], - zLast = this.lastContour[this.lastContour.length - 1]; - if (zFirst.on && zLast.on && zFirst.x === zLast.x && zFirst.y === zLast.y) { - this.lastContour.pop(); - } - this.contours.push(this.lastContour); - } - this.lastContour = []; - } - moveTo(x, y) { - this.endShape(); - this.lineTo(x, y); - } - lineTo(x, y) { - const z = Point.cornerFromXY(x, y).round(CurveUtil.RECIP_GEOMETRY_PRECISION); - while (this.lastContour.length >= 2) { - const a = this.lastContour[this.lastContour.length - 2], - b = this.lastContour[this.lastContour.length - 1]; - if (isLineExtend(a, b, z)) { - this.lastContour.pop(); - } else { - break; - } - } - this.lastContour.push(z); - } - arcTo(arc, x, y) { - const offPoints = TypoGeom.Quadify.auto(arc, 1, 16); - if (offPoints) { - for (const z of offPoints) - this.lastContour.push(Point.offFrom(z).round(CurveUtil.RECIP_GEOMETRY_PRECISION)); - } - this.lineTo(x, y); - } -} -function isLineExtend(a, b, c) { - return ( - a.on && - c.on && - ((aligned(a.x, b.x, c.x) && between(a.y, b.y, c.y)) || - (aligned(a.y, b.y, c.y) && between(a.x, b.x, c.x))) - ); -} -function geometryPrecisionEqual(a, b) { - return ( - Math.round(a * CurveUtil.RECIP_GEOMETRY_PRECISION) === - Math.round(b * CurveUtil.RECIP_GEOMETRY_PRECISION) - ); -} -function aligned(a, b, c) { - return geometryPrecisionEqual(a, b) && geometryPrecisionEqual(b, c); -} -function between(a, b, c) { - return (a <= b && b <= c) || (a >= b && b >= c); -} diff --git a/font-src/gen/otd-conv/glyphs.js b/font-src/gen/otd-conv/glyphs.js new file mode 100644 index 000000000..5c31c0cc1 --- /dev/null +++ b/font-src/gen/otd-conv/glyphs.js @@ -0,0 +1,94 @@ +const { Ot } = require("ot-builder"); + +class NamedGlyphStore { + constructor() { + this.m_mapping = new Map(); + } + declare(name) { + const g = new Ot.Glyph(); + g.name = name; + this.m_mapping.set(name, g); + } + query(name) { + return this.m_mapping.get(name); + } + + decideOrder() { + const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]); + return gs.decideOrder(); + } + fill(name, data) { + const g = this.query(name); + if (!g) return; + + g.horizontal = { start: 0, end: data.advanceWidth }; + if (data.references && data.references.length) { + this.fillReferences(g, data); + } else if (data.contours && data.contours.length) { + this.fillContours(g, data); + } + } + + fillReferences(g, data) { + const gl = new Ot.Glyph.GeometryList(); + for (const ref of data.references) { + const target = this.query(ref.glyph); + if (!target) continue; + const tfm = Ot.Glyph.Transform2X3.Translate(ref.x, ref.y); + gl.items.push(new Ot.Glyph.TtReference(target, tfm)); + } + g.geometry = gl; + } + fillContours(g, data) { + const cs = new Ot.Glyph.ContourSet(); + for (const c of data.contours) { + const c1 = []; + for (const z of c) { + c1.push( + Ot.Glyph.Point.create( + z.x, + z.y, + z.on ? Ot.Glyph.PointType.Corner : Ot.Glyph.PointType.Quad + ) + ); + } + cs.contours.push(c1); + } + g.geometry = cs; + } +} + +module.exports = convertGlyphs; +function convertGlyphs(gsOrig) { + const sortedEntries = Array.from(gsOrig.indexedNamedEntries()) + .map(([j, gn, g]) => [j, gn, queryOrderingUnicode(gsOrig, g), g]) + .sort(byRank); + + const gs = new NamedGlyphStore(); + const cmap = new Ot.Cmap.Table(); + + for (const [origIndex, name, uOrd, g] of sortedEntries) { + gs.declare(name); + const us = gsOrig.queryUnicodeOf(g); + if (us) { + for (const u of us) { + if (isFinite(u - 0) && u) { + cmap.unicode.set(u, gs.query(name)); + } + } + } + } + for (const [origIndex, name, uOrd, g] of sortedEntries) gs.fill(name, g); + + return { glyphs: gs, cmap }; +} +function queryOrderingUnicode(gs, g) { + const us = gs.queryUnicodeOf(g); + if (us && us.size) return Array.from(us).sort((a, b) => a - b)[0]; + else return 0xffffff; +} +function byRank([ja, gna, ua, a], [jb, gnb, ub, b]) { + return ( + (b.glyphRank || 0) - (a.glyphRank || 0) || (ua || 0) - (ub || 0) || (ja || 0) - (jb || 0) + ); +} diff --git a/font-src/gen/otd-conv/index.js b/font-src/gen/otd-conv/index.js new file mode 100644 index 000000000..43a562de7 --- /dev/null +++ b/font-src/gen/otd-conv/index.js @@ -0,0 +1,22 @@ +const Metadata = require("./metadata"); +const convertGlyphs = require("./glyphs"); +const convertName = require("./name"); +const { convertGsub, convertGpos, convertGdef } = require("./layout"); + +module.exports = function (otdRestFont, gs) { + const head = Metadata.convertHead(otdRestFont.head); + const hhea = Metadata.convertHhea(otdRestFont.hhea); + const post = Metadata.convertPost(otdRestFont.post); + const maxp = Metadata.convertMaxp(otdRestFont.maxp); + const os2 = Metadata.convertOs2(otdRestFont.OS_2); + + const name = convertName(otdRestFont.name); + + const { glyphs, cmap } = convertGlyphs(gs); + + const gsub = convertGsub(otdRestFont.GSUB, glyphs); + const gpos = convertGpos(otdRestFont.GPOS, glyphs); + const gdef = convertGdef(otdRestFont.GDEF, glyphs); + + return { glyphs, head, hhea, post, maxp, os2, name, cmap, gsub, gpos, gdef }; +}; diff --git a/font-src/gen/otd-conv/layout.js b/font-src/gen/otd-conv/layout.js new file mode 100644 index 000000000..cc6c028f0 --- /dev/null +++ b/font-src/gen/otd-conv/layout.js @@ -0,0 +1,331 @@ +const { Ot } = require("ot-builder"); + +class LookupStore { + constructor(handlers, glyphs) { + this.glyphs = glyphs; + this.m_handlers = handlers; + this.m_mapping = new Map(); + } + extract() { + return Array.from(this.m_mapping.values()); + } + query(id) { + return this.m_mapping.get(id); + } + + declare(id, otdLookup) { + if (this.m_mapping.has(id)) return; + const handler = this.m_handlers[otdLookup.type]; + if (!handler) return; + this.m_mapping.set(id, handler.init()); + } + fill(id, otdLookup) { + const dst = this.query(id); + const handler = this.m_handlers[otdLookup.type]; + if (!dst || !handler) return; + + handler.fill(dst, otdLookup, this); + } +} + +const GsubSingleHandler = { + init() { + return new Ot.Gsub.Single(); + }, + fill(dst, src, store) { + for (const st of src.subtables) { + for (const k in st) { + const from = store.glyphs.query(k); + const to = store.glyphs.query(st[k]); + if (from && to) dst.mapping.set(from, to); + } + } + } +}; +const GsubMultipleHandler = { + init() { + return new Ot.Gsub.Multiple(); + }, + fill(dst, src, store) { + for (const st of src.subtables) { + out: for (const k in st) { + const from = store.glyphs.query(k); + const to = mapGlyphListAll(st[k], store); + if (!from || !to) continue out; + dst.mapping.set(from, to); + } + } + } +}; +const GsubAlternateHandler = { + init() { + return new Ot.Gsub.Alternate(); + }, + fill: GsubMultipleHandler.fill +}; +const GsubLigatureHandler = { + init() { + return new Ot.Gsub.Ligature(); + }, + fill(dst, src, store) { + for (const st of src.subtables) { + for (const { from: _from, to: _to } of st.substitutions) { + const to = store.glyphs.query(_to); + const from = mapGlyphListAll(_from, store); + if (!from || !to) continue; + dst.mapping.push({ from, to }); + } + } + } +}; + +const GsubChainingHandler = { + init() { + return new Ot.Gsub.Chaining(); + }, + fill(dst, src, store) { + out: for (const st of src.subtables) { + const match = []; + for (const m of st.match) { + const m1 = mapGlyphListSome(m, store); + if (!m1) continue out; + match.push(new Set(m1)); + } + const inputBegins = st.inputBegins; + const inputEnds = st.inputEnds; + const applications = []; + for (const ap of st.apply) { + const lookup = store.query(ap.lookup); + if (!lookup) continue out; + applications.push({ at: ap.at - inputBegins, apply: lookup }); + } + dst.rules.push({ match, inputBegins, inputEnds, applications }); + } + } +}; + +const GsubReverseHandler = { + init() { + return new Ot.Gsub.ReverseSub(); + }, + fill(dst, src, store) { + out: for (const st of src.subtables) { + const match = []; + const doSubAt = st.inputIndex; + const replacement = new Map(); + for (let j = 0; j < st.match.length; j++) { + { + const m1 = new Set(); + for (let k = 0; k < st.match[j].length; k++) { + const gFrom = store.glyphs.query(st.match[j][k]); + if (gFrom) m1.add(gFrom); + } + if (!m1.size) continue out; + match.push(m1); + } + + if (j === doSubAt) { + for (let k = 0; k < st.match[j].length; k++) { + const gFrom = store.glyphs.query(st.match[j][k]); + const gTo = store.glyphs.query(st.to[k]); + if (!gFrom) continue; + if (gTo) { + replacement.set(gFrom, gTo); + } else { + replacement.set(gFrom, gFrom); + } + } + } + } + dst.rules.push({ match, doSubAt, replacement }); + } + } +}; + +function mapGlyphListAll(gl, store) { + const out = []; + for (const item of gl) { + const fg = store.glyphs.query(item); + if (!fg) return null; + out.push(fg); + } + return out; +} +function mapGlyphListSome(gl, store) { + const out = []; + for (const item of gl) { + const fg = store.glyphs.query(item); + if (!fg) continue; + out.push(fg); + } + if (!out.length) return null; + return out; +} + +const GsubHandlers = { + gsub_single: GsubSingleHandler, + gsub_multiple: GsubMultipleHandler, + gsub_alternate: GsubAlternateHandler, + gsub_ligature: GsubLigatureHandler, + gsub_chaining: GsubChainingHandler, + gsub_reverse: GsubReverseHandler +}; + +const GposMarkToBaseHandler = { + init() { + return new Ot.Gpos.MarkToBase(); + }, + fill(dst, src, store) { + const st = src.subtables[0]; + const mm = collectClassMap(st.marks); + dst.marks = convertMarkRecords(st.marks, mm, store); + dst.bases = convertBaseRecords(st.bases, mm, store); + } +}; +const GposMarkToMarkHandler = { + init() { + return new Ot.Gpos.MarkToMark(); + }, + fill(dst, src, store) { + const st = src.subtables[0]; + const mm = collectClassMap(st.marks); + dst.marks = convertMarkRecords(st.marks, mm, store); + dst.baseMarks = convertBaseRecords(st.bases, mm, store); + } +}; +function collectClassMap(marks) { + let n = 0; + const m = new Map(); + for (const gn in marks) { + const mark = marks[gn]; + if (!m.has(mark.class)) { + m.set(mark.class, n); + n++; + } + } + return m; +} +function convertMarkRecords(marks, mm, store) { + const out = new Map(); + for (const gn in marks) { + const mark = marks[gn]; + const g = store.glyphs.query(gn); + if (!g) continue; + let markAnchors = []; + markAnchors[mm.get(mark.class)] = { x: mark.x, y: mark.y }; + out.set(g, { markAnchors: markAnchors }); + } + return out; +} +function convertBaseRecords(bases, mm, store) { + const out = new Map(); + for (const gn in bases) { + const baseObj = bases[gn]; + const g = store.glyphs.query(gn); + if (!g) continue; + const baseArray = []; + for (const bkStr in baseObj) { + baseArray[mm.get(bkStr)] = baseObj[bkStr]; + } + out.set(g, { baseAnchors: baseArray }); + } + return out; +} +const GposHandlers = { + gpos_mark_to_base: GposMarkToBaseHandler, + gpos_mark_to_mark: GposMarkToMarkHandler +}; + +class FeatureStore { + constructor(lookups) { + this.lookupStore = lookups; + this.m_mapping = new Map(); + } + extract() { + return Array.from(this.m_mapping.values()); + } + query(id) { + return this.m_mapping.get(id); + } + fill(id, data) { + const tag = id.slice(0, 4); + const lookups = []; + for (const lid of data) { + const lookup = this.lookupStore.query(lid); + if (lookup) lookups.push(lookup); + } + this.m_mapping.set(id, { tag, lookups }); + } +} + +class ScriptLanguageStore { + constructor(features) { + this.featureStore = features; + this.m_scriptMapping = new Map(); + } + extract() { + return this.m_scriptMapping; + } + fill(id, data) { + const scriptTag = id.slice(0, 4); + const languageTag = id.slice(5, 9).padEnd(4); + let sr = this.m_scriptMapping.get(scriptTag); + if (!sr) { + sr = { defaultLanguage: null, languages: new Map() }; + this.m_scriptMapping.set(scriptTag, sr); + } + + const lr = this.createLanguageRecord(data); + if (languageTag === "dflt" || languageTag === "DFLT") sr.defaultLanguage = lr; + else sr.languages.set(languageTag, lr); + } + createLanguageRecord(data) { + const features = []; + for (const fid of data.features) { + const feature = this.featureStore.query(fid); + if (feature) features.push(feature); + } + return { + requiredFeature: this.featureStore.query(data.requiredFeature) || null, + features: features + }; + } +} + +exports.convertGsub = ConvertGsubGposT(GsubHandlers, Ot.Gsub.Table); +exports.convertGpos = ConvertGsubGposT(GposHandlers, Ot.Gpos.Table); + +function ConvertGsubGposT(handlers, T) { + return function (table, glyphs) { + if (!table) return null; + const ls = new LookupStore(handlers, glyphs); + if (table.lookups) { + if (table.lookupOrder) { + for (const l of table.lookupOrder) ls.declare(l, table.lookups[l]); + } + for (const l in table.lookups) ls.declare(l, table.lookups[l]); + for (const l in table.lookups) ls.fill(l, table.lookups[l]); + } + const fs = new FeatureStore(ls); + if (table.features) { + for (const f in table.features) fs.fill(f, table.features[f]); + } + const ss = new ScriptLanguageStore(fs); + if (table.languages) { + for (const sl in table.languages) ss.fill(sl, table.languages[sl]); + } + + return new T(ss.extract(), fs.extract(), ls.extract()); + }; +} + +exports.convertGdef = convertGdef; +function convertGdef(otdGdef, glyphs) { + const gdef = new Ot.Gdef.Table(); + gdef.glyphClassDef = new Map(); + for (const gn in otdGdef.glyphClassDef) { + const g = glyphs.query(gn); + if (g) gdef.glyphClassDef.set(g, otdGdef.glyphClassDef[gn]); + } + return gdef; +} diff --git a/font-src/gen/otd-conv/metadata.js b/font-src/gen/otd-conv/metadata.js new file mode 100644 index 000000000..1fd2d15ea --- /dev/null +++ b/font-src/gen/otd-conv/metadata.js @@ -0,0 +1,98 @@ +const { Ot } = require("ot-builder"); + +exports.convertHead = convertHead; +function convertHead(otdHead) { + const head = new Ot.Head.Table(); + head.fontRevision = otdHead.fontRevision; + head.flags = otdHead.flags; + head.fontDirectionHint = otdHead.fontDirectionHint; + head.unitsPerEm = otdHead.unitsPerEm; + head.macStyle = macStyleObjToEnum(otdHead.macStyle); + head.lowestRecPPEM = otdHead.lowestRecPPEM; + head.glyphDataFormat = otdHead.glyphDataFormat; + return head; +} + +exports.convertHhea = convertHhea; +function convertHhea(otdHhea) { + const hhea = new Ot.MetricHead.Hhea(); + hhea.ascender = otdHhea.ascender; + hhea.descender = otdHhea.descender; + hhea.lineGap = otdHhea.lineGap; + hhea.caretSlopeRise = otdHhea.caretSlopeRise; + hhea.caretSlopeRun = otdHhea.caretSlopeRun; + hhea.caretOffset = otdHhea.caretOffset; + return hhea; +} + +exports.convertPost = convertPost; +function convertPost(otdPost) { + const post = new Ot.Post.Table(3, 0); + post.italicAngle = otdPost.italicAngle; + post.underlinePosition = otdPost.underlinePosition; + post.underlineThickness = otdPost.underlineThickness; + post.isFixedPitch = otdPost.isFixedPitch; + return post; +} + +exports.convertMaxp = convertMaxp; +function convertMaxp(otdMaxp) { + const maxp = Ot.Maxp.Table.TrueType(); + return maxp; +} + +exports.convertOs2 = convertOs2; +function convertOs2(otdOs2) { + const os2 = new Ot.Os2.Table(4); + os2.achVendID = otdOs2.achVendID; + os2.panose = otdOs2.panose; + os2.fsSelection = fsSelectionObjToEnum(otdOs2.fsSelection); + os2.fsType = otdOs2.fsType; + os2.sCapHeight = otdOs2.sCapHeight; + os2.sFamilyClass = otdOs2.sFamilyClass; + os2.sTypoAscender = otdOs2.sTypoAscender; + os2.sTypoDescender = otdOs2.sTypoDescender; + os2.sTypoLineGap = otdOs2.sTypoLineGap; + os2.sxHeight = otdOs2.sxHeight; + os2.usBreakChar = otdOs2.usBreakChar; + os2.usDefaultChar = otdOs2.usDefaultChar; + os2.usFirstCharIndex = otdOs2.usFirstCharIndex; + os2.usLastCharIndex = otdOs2.usLastCharIndex; + os2.usMaxContext = otdOs2.usMaxContext; + os2.usWeightClass = otdOs2.usWeightClass; + os2.usWidthClass = otdOs2.usWidthClass; + os2.usWinAscent = otdOs2.usWinAscent; + os2.usWinDescent = otdOs2.usWinDescent; + os2.xAvgCharWidth = otdOs2.xAvgCharWidth; + os2.yStrikeoutPosition = otdOs2.yStrikeoutPosition; + os2.yStrikeoutSize = otdOs2.yStrikeoutSize; + os2.ySubscriptXOffset = otdOs2.ySubscriptXOffset; + os2.ySubscriptXSize = otdOs2.ySubscriptXSize; + os2.ySubscriptYOffset = otdOs2.ySubscriptYOffset; + os2.ySubscriptYSize = otdOs2.ySubscriptYSize; + os2.ySuperscriptXOffset = otdOs2.ySuperscriptXOffset; + os2.ySuperscriptXSize = otdOs2.ySuperscriptXSize; + os2.ySuperscriptYOffset = otdOs2.ySuperscriptYOffset; + os2.ySuperscriptYSize = otdOs2.ySuperscriptYSize; + os2.ulCodePageRange1 = otdOs2.ulCodePageRange1; + os2.ulCodePageRange2 = otdOs2.ulCodePageRange2; + return os2; +} + +function macStyleObjToEnum(o) { + return ( + (o.bold ? Ot.Head.MacStyle.Bold : 0) | + (o.italic ? Ot.Head.MacStyle.Italic : 0) | + (o.condensed ? Ot.Head.MacStyle.Condensed : 0) | + (o.extended ? Ot.Head.MacStyle.Extended : 0) + ); +} +function fsSelectionObjToEnum(o) { + return ( + (o.oblique ? Ot.Os2.FsSelection.OBLIQUE : 0) | + (o.bold ? Ot.Os2.FsSelection.BOLD : 0) | + (o.italic ? Ot.Os2.FsSelection.ITALIC : 0) | + (o.regular ? Ot.Os2.FsSelection.REGULAR : 0) | + (o.useTypoMetrics ? Ot.Os2.FsSelection.USE_TYPO_METRICS : 0) + ); +} diff --git a/font-src/gen/otd-conv/name.js b/font-src/gen/otd-conv/name.js new file mode 100644 index 000000000..390516b8c --- /dev/null +++ b/font-src/gen/otd-conv/name.js @@ -0,0 +1,20 @@ +const { Ot } = require("ot-builder"); + +module.exports = convertName; +function convertName(otdName) { + const name = new Ot.Name.Table(); + for (const entry of otdName) { + name.records.push({ + platformID: entry.platformID, + encodingID: entry.encodingID, + languageID: entry.languageID, + nameID: entry.nameID, + value: + entry.platformID === 3 && entry.encodingID === 1 + ? entry.nameString + : Buffer.from(entry.nameString, "utf-8") + }); + } + + return name; +} diff --git a/font-src/glyphs/auto-build/accents.ptl b/font-src/glyphs/auto-build/accents.ptl index befaec1f1..ab376bee5 100644 --- a/font-src/glyphs/auto-build/accents.ptl +++ b/font-src/glyphs/auto-build/accents.ptl @@ -114,14 +114,15 @@ glyph-block AutoBuild-Accents : begin define construction : glyph-proc include s_parts.0 AS_BASE ALSO_METRICS + local nonTrivial false foreach part [items-of : s_parts.slice 1] : if part : begin include part if (part.markAnchors && part.markAnchors.bottomright) : begin eject-contour 'serifRB' - if [AnyDerivingCv.query s_parts.0] : begin + set nonTrivial true + if ([not nonTrivial] && [AnyDerivingCv.query s_parts.0]) : begin local decomposeParts { } - foreach part [items-of s_parts] : begin - recursivelyDecompose part decomposeParts + foreach part [items-of s_parts] : recursivelyDecompose part decomposeParts CvDecompose.set currentGlyph decomposeParts define [RootGlyphProc goalName code parts] : begin @@ -195,12 +196,8 @@ define customDecompositions : object ."\u0247" "e\u0337" ."\u0290" "z\u0322" - ."\u0256" "d\u0322" - ."\u0273" "n\u0322" ."\u01AE" "T\u0322" - ."\u1D8F" "a\u0322" ."\u1D90" "\u0251\u0322" - ."\u1D91" "\u0257\u0322" ."\u1E10" "D\u0326" ."\u1E11" "d\u0326" diff --git a/font-src/glyphs/auto-build/composite.ptl b/font-src/glyphs/auto-build/composite.ptl index b0471573d..68776f926 100644 --- a/font-src/glyphs/auto-build/composite.ptl +++ b/font-src/glyphs/auto-build/composite.ptl @@ -3,6 +3,8 @@ $$include '../../meta/macros.ptl' import [mix linreg clamp fallback] from '../../support/utils' import [getGrMesh AnyDerivingCv CvDecompose] from "../../support/gr" +extern Map +extern Set glyph-module @@ -27,12 +29,16 @@ glyph-block Autobuild-Enclosure-Shared : begin glyph-block-export EnsureComponentGlyphT define [EnsureComponentGlyphT gidPart fnBuildup] : begin + local rs : new Set local gniPart : fnBuildup gidPart local grs : AnyDerivingCv.query [query-glyph gidPart] if grs : foreach gr [items-of grs] : begin local relatedGidPart : gr.get [query-glyph gidPart] local gniRelated : fnBuildup relatedGidPart - if [query-glyph gniPart] : gr.set [query-glyph gniPart] gniRelated + if [query-glyph gniPart] : begin + gr.set [query-glyph gniPart] gniRelated + if [query-glyph gniRelated] : begin + [query-glyph gniPart].tryBecomeMirrorOf [query-glyph gniRelated] rs return gniPart glyph-block-export CollectJobs @@ -78,8 +84,11 @@ glyph-block Autobuild-Enclosure-Shared : begin glyph-block-export applyRelations define [applyRelations relApplications] : begin + local rs : new Set foreach {gr f t} [items-of relApplications] : begin - if [query-glyph f] : gr.set [query-glyph f] t + if [query-glyph f] : begin + gr.set [query-glyph f] t + if [query-glyph t] : [query-glyph t].tryBecomeMirrorOf [query-glyph f] rs glyph-block AutoBuild-Enclosure : begin glyph-block-import CommonShapes @@ -91,18 +100,30 @@ glyph-block AutoBuild-Enclosure : begin define [AdjustDigitCount digits width] : Math.max 1 (digits * Width / width) define [EnclosureStrokeScale dscale digits width] : dscale / [Math.pow [AdjustDigitCount digits width] 0.66] + define aliasableInnerSubGlyphMap : new Map define [EnsureInnerSubGlyphImpl miniatureFont prefix finalPlacement dscale xCompress shift] : lambda [gidPart] : begin + define gniPartNoPlc : '.ci.' + gidPart + '@' + [{ prefix dscale xCompress shift }.join '/'] define gniPart : '.ci.' + gidPart + '@' + [{ prefix finalPlacement dscale xCompress shift }.join '/'] - if [not : query-glyph gniPart] : create-glyph gniPart : glyph-proc - set-width 0 - include : miniatureFont.queryByNameEnsured gidPart - include : Upright - include : Scale (dscale * xCompress) dscale - include : Translate 0 (dscale * (-CAP / 2 + shift)) - include : Translate 0 (CAP / 2 * dscale) - include : Translate 0 (SymbolMid - CAP * dscale / 2) - include : Italify - include : Translate finalPlacement 0 + define aliasGoal : aliasableInnerSubGlyphMap.get gniPartNoPlc + if [not : query-glyph gniPart] : begin + if aliasGoal : begin + create-glyph gniPart : composite-proc + refer-glyph aliasGoal.gn + Translate (finalPlacement - aliasGoal.placement) 0 + : else : begin + create-glyph gniPart : glyph-proc + set-width 0 + include : miniatureFont.queryByNameEnsured gidPart + include : Upright + include : Scale (dscale * xCompress) dscale + include : Translate 0 (dscale * (-CAP / 2 + shift)) + include : Translate 0 (CAP / 2 * dscale) + include : Translate 0 (SymbolMid - CAP * dscale / 2) + include : Italify + include : Translate finalPlacement 0 + aliasableInnerSubGlyphMap.set gniPartNoPlc : object + gn gniPart + placement finalPlacement return gniPart define [EnsureInnerSubGlyphSeq miniatureFont prefix job dimens] : begin @@ -136,12 +157,13 @@ glyph-block AutoBuild-Enclosure : begin define { gn unicode parts w bal baly } job define [object width mockInnerWidth dscale] dimens local finalParts : EnsureInnerSubGlyphSeq miniatureFont prefix job dimens - local inner : new-glyph : glyph-proc + if gnEnclosure : return : glyph-proc + foreach [gidPart : items-of finalParts] : include [refer-glyph gidPart] + include : Translate width 0 + CvDecompose.set currentGlyph [{gnEnclosure}.concat finalParts] + : else : return : new-glyph : glyph-proc foreach [gidPart : items-of finalParts] : include [refer-glyph gidPart] include : Translate width 0 - return : glyph-proc - include inner - if (gnEnclosure) : CvDecompose.set currentGlyph [{gnEnclosure}.concat finalParts] define [CircCrowd digits width] : 2 + 2 * [Math.pow [AdjustDigitCount digits width] 0.5] * [Math.max 1 (HalfUPM / Width)] define [CircScale digits width] : 0.65 / [Math.pow [AdjustDigitCount digits width] 0.5] @@ -350,8 +372,8 @@ glyph-block AutoBuild-Enclosure : begin define dimens : bracedDottdeDimens digits ww define [object width mockInnerWidth dscale] dimens set-width width - include : refer-glyph gnb include : EnclosureInner [if jobDecomposable gnb null] miniatureFont prefix job dimens + include : refer-glyph gnb foreach job [items-of jobs.decomposableJobs] : CreateGlyphImpl true job foreach job [items-of jobs.nonDecomposable] : CreateGlyphImpl false job diff --git a/font-src/glyphs/auto-build/transformed.ptl b/font-src/glyphs/auto-build/transformed.ptl index b260f5da7..a117f3127 100644 --- a/font-src/glyphs/auto-build/transformed.ptl +++ b/font-src/glyphs/auto-build/transformed.ptl @@ -3,6 +3,7 @@ $$include '../../meta/macros.ptl' import [linreg clamp mix fallback] from '../../support/utils' import [AnyDerivingCv DotlessOrNot getGrTree getGrMesh CvDecompose] from "../../support/gr" +extern Set glyph-module @@ -46,13 +47,17 @@ glyph-block Autobuild-Transformed : begin set relatedRecord.1 relSrcName relatedRecords.push relatedRecord - relSets.push {gr origDstName relDstName} + relSets.push { gr origDstName relDstName } - return {[records.concat relatedRecords] relSets targetNameMap} - - define [link-relations relSets] : foreach [{gr origDstName relDstName} : items-of relSets] - if [query-glyph origDstName] : gr.set [query-glyph origDstName] relDstName + return { [records.concat relatedRecords] relSets targetNameMap } + define [link-relations relSets] : begin + local rs : new Set + foreach [{gr origDstName relDstName} : items-of relSets] + if [query-glyph origDstName] : begin + gr.set [query-glyph origDstName] relDstName + if [query-glyph relDstName] : begin + [query-glyph origDstName].tryBecomeMirrorOf [query-glyph relDstName] rs define [createSuperscripts _records] : begin local {records relSets targetNameMap} : extendRelatedGlyphs _records 'sup' @@ -254,7 +259,7 @@ glyph-block Autobuild-Transformed : begin list 0x1DAC 'mltail' list 0x1DAD 'turnmleg' list 0x1DAE 'nltail' - list 0x1DAF 'nrtailBR' + list 0x1DAF 'nHookBottom' list 0x1DB0 'smcpN' list 0x1DB1 'obar' list 0x1DB2 'latinphi' diff --git a/font-src/glyphs/common/derivatives.ptl b/font-src/glyphs/common/derivatives.ptl index 49d45932d..11c2c8aa6 100644 --- a/font-src/glyphs/common/derivatives.ptl +++ b/font-src/glyphs/common/derivatives.ptl @@ -90,27 +90,19 @@ glyph-block Common-Derivatives : begin if [not newid] : throw : new Error "Target ID not specified" return : HCombine newid unicode id id spacing - define [vcombine newid unicode id1 id2 spacing] : begin + define [VCombine newid unicode id1 id2 spacing] : begin if [not newid] : throw : new Error "Target ID not specified" create-glyph newid unicode : glyph-proc - set-width [query-glyph id1].advanceWidth - currentGlyph.dependsOn [query-glyph id1] - currentGlyph.dependsOn [query-glyph id2] - include : new-glyph : glyph-proc - include [refer-glyph id2] - include : Upright - include : Translate 0 (-spacing) - include : new-glyph : glyph-proc - include [refer-glyph id1] - include : Upright - include : Translate 0 (spacing / 2) - include : Italify + include [refer-glyph id2] AS_BASE ALSO_METRICS + include : Translate (-spacing * TanSlope) (-spacing) + include [refer-glyph id1] + include : Translate (spacing * TanSlope / 2) (spacing / 2) define [VDual newid unicode id spacing] : begin if [not newid] : throw : new Error "Target ID not specified" - return : vcombine newid unicode id id spacing + return : VCombine newid unicode id id spacing glyph-block-export select-variant italic-variant refer-glyph query-glyph with-related-glyphs - glyph-block-export alias turned HDual HCombine VDual vcombine glyph-is-needed + glyph-block-export alias turned HDual HCombine VDual VCombine glyph-is-needed glyph-block Recursive-Build : begin define [Fork gs ps] : begin diff --git a/font-src/gen/build-glyphs.ptl b/font-src/glyphs/index.ptl similarity index 79% rename from font-src/gen/build-glyphs.ptl rename to font-src/glyphs/index.ptl index 6bf23c1a3..8e3beafa5 100644 --- a/font-src/gen/build-glyphs.ptl +++ b/font-src/glyphs/index.ptl @@ -1,8 +1,8 @@ import '../support/glyph' as Glyph import '../support/glyph-store' as GlyphStore import '../support/point' as Point -import './kits/spiro-kit' as spirokit -import './kits/boole-kit' as BooleKit +import '../kits/spiro-kit' as spirokit +import '../kits/boole-kit' as BooleKit import '../support/anchor' as Anchor import [ designParameters ] from "../meta/aesthetics" @@ -115,35 +115,35 @@ export all : define [buildGlyphs para recursive recursiveCodes] : begin define $$Capture$$ : object [metrics : Object.create metrics] $NamedParameterPair$ $donothing$ para recursive recursiveCodes variantSelector glyphStore $createAndSaveGlyphImpl$ spirofns booleFns MarkSet AS_BASE ALSO_METRICS pickHash buildGlyphs tagged DivFrame fontMetrics $assignUnicodeImpl$ ### HERE WE GO - run-glyph-module '../glyphs/common/shapes.js' - run-glyph-module '../glyphs/common/derivatives.js' - run-glyph-module '../glyphs/space/index.js' - run-glyph-module '../glyphs/marks/index.js' + run-glyph-module './common/shapes.js' + run-glyph-module './common/derivatives.js' + run-glyph-module './space/index.js' + run-glyph-module './marks/index.js' # Unified letters - run-glyph-module '../glyphs/letter/latin.js' - run-glyph-module '../glyphs/letter/greek.js' - run-glyph-module '../glyphs/letter/cyrillic.js' + run-glyph-module './letter/latin.js' + run-glyph-module './letter/greek.js' + run-glyph-module './letter/cyrillic.js' # Numbers - run-glyph-module '../glyphs/number/index.js' + run-glyph-module './number/index.js' # Symbols - run-glyph-module '../glyphs/symbol/punctuation.js' - run-glyph-module '../glyphs/symbol/arrow.js' - run-glyph-module '../glyphs/symbol/geometric.js' - run-glyph-module '../glyphs/symbol/enclosure.js' - run-glyph-module '../glyphs/symbol/math.js' - run-glyph-module '../glyphs/symbol/letter.js' - run-glyph-module '../glyphs/symbol/braille.js' - run-glyph-module '../glyphs/symbol/mosaic.js' - run-glyph-module '../glyphs/symbol/pictograph.js' - run-glyph-module '../glyphs/symbol/ligation.js' + run-glyph-module './symbol/punctuation.js' + run-glyph-module './symbol/arrow.js' + run-glyph-module './symbol/geometric.js' + run-glyph-module './symbol/enclosure.js' + run-glyph-module './symbol/math.js' + run-glyph-module './symbol/letter.js' + run-glyph-module './symbol/braille.js' + run-glyph-module './symbol/mosaic.js' + run-glyph-module './symbol/pictograph.js' + run-glyph-module './symbol/ligation.js' # Autobuilds - run-glyph-module '../glyphs/auto-build/accents.js' - run-glyph-module '../glyphs/auto-build/composite.js' - run-glyph-module '../glyphs/auto-build/transformed.js' + run-glyph-module './auto-build/accents.js' + run-glyph-module './auto-build/composite.js' + run-glyph-module './auto-build/transformed.js' return : object metrics glyphStore diff --git a/font-src/glyphs/marks/index.ptl b/font-src/glyphs/marks/index.ptl index ea8317ff0..e95f9434b 100644 --- a/font-src/glyphs/marks/index.ptl +++ b/font-src/glyphs/marks/index.ptl @@ -1160,7 +1160,6 @@ glyph-block Marks : begin curl (Middle + dx - Width) (XH / 2 + dy) create-glyph 'arrVStrokeOver' : glyph-proc - include : ForceUpright set-width 0 local fine : 0.375 * OverlayStroke local dx : Width * 0 @@ -1171,7 +1170,6 @@ glyph-block Marks : begin curl (Middle + dx - Width) (XH / 2 + dy) create-glyph 'arrDblVStrokeOver' : glyph-proc - include : ForceUpright set-width 0 local fine : 0.3 * OverlayStroke local dx : Width * 0 @@ -1186,7 +1184,6 @@ glyph-block Marks : begin curl (Middle + dx - Width + gap) (XH / 2 + dy) create-glyph 'arrHStrokeOver' : glyph-proc - include : ForceUpright set-width 0 local fine : 0.375 * OverlayStroke local dx : Width * 0.3 @@ -1196,7 +1193,6 @@ glyph-block Marks : begin curl (Middle - Width + dx) (XH / 2) create-glyph 'arrDblHStrokeOver' : glyph-proc - include : ForceUpright set-width 0 local fine : 0.3 * OverlayStroke local dx : Width * 0.3 diff --git a/font-src/glyphs/space/index.ptl b/font-src/glyphs/space/index.ptl index 887929a13..da566ff1e 100644 --- a/font-src/glyphs/space/index.ptl +++ b/font-src/glyphs/space/index.ptl @@ -13,16 +13,11 @@ glyph-block Symbol-Mosaic-NotDef : begin set currentGlyph.autoRefPriority (-9998) set currentGlyph.glyphRank (9998) - create-glyph 'nonmarkingreturn' 0x000D : glyph-proc - set-width 0 - set currentGlyph.autoRefPriority (-1) - create-glyph 'space' ' ' : glyph-proc set currentGlyph.autoRefPriority (-100) local df : DivFrame para.diversityI set-width df.width - create-glyph 'markDemoBaseSpace' 0xE00E : glyph-proc local df : DivFrame 1 set-width df.width @@ -41,7 +36,8 @@ glyph-block Symbol-Mosaic-NotDef : begin set-width 0 set currentGlyph.autoRefPriority (-9999) - alias 'cgj' 0x034F '.null' + alias 'nonmarkingreturn' 0x000D 'zwsp' + alias 'cgj' 0x034F 'zwsp' alias 'nbsp' 0xA0 'space' alias 'threePerEmsp' 0x2004 'space' alias 'fourPerEmsp' 0x2005 'space' diff --git a/font-src/glyphs/symbol/arrow.ptl b/font-src/glyphs/symbol/arrow.ptl index 791ddf661..c9150e05d 100644 --- a/font-src/glyphs/symbol/arrow.ptl +++ b/font-src/glyphs/symbol/arrow.ptl @@ -443,17 +443,17 @@ glyph-block Symbol-Arrow : begin do local hcDist : Math.max (halfstroke * 5) (arrowWidth / 2) - vcombine [MangleName 'uni21C4'] [MangleUnicode 0x21C4] [MangleName 'arrowright'] [MangleName 'arrowleft'] (arrowSize * 1.75) + VCombine [MangleName 'uni21C4'] [MangleUnicode 0x21C4] [MangleName 'arrowright'] [MangleName 'arrowleft'] (arrowSize * 1.75) HCombine [MangleName 'uni21C5'] [MangleUnicode 0x21C5] [MangleName 'arrowup'] [MangleName 'arrowdown'] hcDist - vcombine [MangleName 'uni21C6'] [MangleUnicode 0x21C6] [MangleName 'arrowleft'] [MangleName 'arrowright'] (arrowSize * 1.75) - vcombine [MangleName 'uni21C7'] [MangleUnicode 0x21C7] [MangleName 'arrowleft'] [MangleName 'arrowleft'] (arrowSize * 1.75) + VCombine [MangleName 'uni21C6'] [MangleUnicode 0x21C6] [MangleName 'arrowleft'] [MangleName 'arrowright'] (arrowSize * 1.75) + VCombine [MangleName 'uni21C7'] [MangleUnicode 0x21C7] [MangleName 'arrowleft'] [MangleName 'arrowleft'] (arrowSize * 1.75) HCombine [MangleName 'uni21C8'] [MangleUnicode 0x21C8] [MangleName 'arrowup'] [MangleName 'arrowup'] hcDist - vcombine [MangleName 'uni21C9'] [MangleUnicode 0x21C9] [MangleName 'arrowright'] [MangleName 'arrowright'] (arrowSize * 1.75) + VCombine [MangleName 'uni21C9'] [MangleUnicode 0x21C9] [MangleName 'arrowright'] [MangleName 'arrowright'] (arrowSize * 1.75) HCombine [MangleName 'uni21CA'] [MangleUnicode 0x21CA] [MangleName 'arrowdown'] [MangleName 'arrowdown'] hcDist HCombine [MangleName 'uni21F5'] [MangleUnicode 0x21F5] [MangleName 'arrowdown'] [MangleName 'arrowup'] hcDist - vcombine [MangleName 'uni21CB'] [MangleUnicode 0x21CB] [MangleName 'arrowleftHR'] [MangleName 'arrowrightHR'] (arrowSize) - vcombine [MangleName 'uni21CC'] [MangleUnicode 0x21CC] [MangleName 'arrowrightHL'] [MangleName 'arrowleftHL'] (arrowSize) - vcombine [MangleName 'uni21B9'] [MangleUnicode 0x21B9] [MangleName 'arrowbarleft'] [MangleName 'arrowbarright'] (arrowSize * 1.75) + VCombine [MangleName 'uni21CB'] [MangleUnicode 0x21CB] [MangleName 'arrowleftHR'] [MangleName 'arrowrightHR'] (arrowSize) + VCombine [MangleName 'uni21CC'] [MangleUnicode 0x21CC] [MangleName 'arrowrightHL'] [MangleName 'arrowleftHL'] (arrowSize) + VCombine [MangleName 'uni21B9'] [MangleUnicode 0x21B9] [MangleName 'arrowbarleft'] [MangleName 'arrowbarright'] (arrowSize * 1.75) do "Legacy computing arrows" define bodyWidth : Math.min arrowWidth (arrowHeight * 0.6) diff --git a/font-src/glyphs/symbol/braille.ptl b/font-src/glyphs/symbol/braille.ptl index 37b792156..71fd54874 100644 --- a/font-src/glyphs/symbol/braille.ptl +++ b/font-src/glyphs/symbol/braille.ptl @@ -3,9 +3,7 @@ $$include '../../meta/macros.ptl' import [mix linreg clamp fallback] from '../../support/utils' import [designParameters] from '../../meta/aesthetics' - -# Needed for bitwise operators -import '../../support/mask-bit' as maskBit +import [maskBit maskBits popCountByte] from '../../support/mask-bit' glyph-module @@ -21,6 +19,39 @@ glyph-block Symbol-Braille : begin local stress : 0.5 * [adviceBlackness 3.375] local dotRadius : PeriodRadius * [Math.min 1 (stress / HalfStroke)] + define [BrailleYCoord y] (SymbolMid + y * offsetY) + define [BrailleOutlineShape byte] : glyph-proc + foreach [bit : range 0 8] : if [maskBit byte bit] : begin + include : match bit + 0 : DotAt left [BrailleYCoord (-3)] dotRadius + 1 : DotAt left [BrailleYCoord (-1)] dotRadius + 2 : DotAt left [BrailleYCoord 1 ] dotRadius + 3 : DotAt right [BrailleYCoord (-3)] dotRadius + 4 : DotAt right [BrailleYCoord (-1)] dotRadius + 5 : DotAt right [BrailleYCoord 1 ] dotRadius + 6 : DotAt left [BrailleYCoord 3 ] dotRadius + 7 : DotAt right [BrailleYCoord 3 ] dotRadius + + define [BrailleReferenceShape byte] : glyph-proc + foreach [bit : range 0 8] : if [maskBit byte bit] + include : refer-glyph ['braille' + (bit + 1)] + + define [BrailleHalvesShape byte] : glyph-proc + local leftHalf : maskBits byte 0x47 + local rightHalf : maskBits byte 0xB8 + include : refer-glyph : brailleGlyphName leftHalf + include : refer-glyph : brailleGlyphName rightHalf + + define [brailleBuildStage byte] : begin + if (1 == [popCountByte byte]) : return 1 + if (![maskBits byte 0x47] || ![maskBits byte 0xB8]) : return 2 + return 3 + + define [brailleGlyphName byte] : begin + local dots {} + foreach [bit : range 0 8] : if [maskBit byte bit] : dots.push (bit + 1) + return ("braille" + [dots.join '']) + ### Computed Braille Patterns ### ### Bit to Dot mapping: @@ -28,52 +59,12 @@ glyph-block Symbol-Braille : begin ### 1 4 ### 2 5 ### 6 7 - ### - foreach [byte : range 1 256] : begin - local dots {} - foreach [bit : range 0 8] : if [maskBit byte bit] : dots.push (bit + 1) - create-glyph ("braille" + [dots.join '']) (0x2800 + byte) : glyph-proc - set currentGlyph.autoRefPriority (-101) - local [y-coord y] (SymbolMid + y * offsetY) - # Is there a way to directly use (byte & (1 << bit)) here? - foreach [bit : range 0 8] : if [maskBit byte bit] : begin - include : match bit - 0 : DotAt left [y-coord (-3)] dotRadius - 1 : DotAt left [y-coord (-1)] dotRadius - 2 : DotAt left [y-coord 1 ] dotRadius - 3 : DotAt right [y-coord (-3)] dotRadius - 4 : DotAt right [y-coord (-1)] dotRadius - 5 : DotAt right [y-coord 1 ] dotRadius - 6 : DotAt left [y-coord 3 ] dotRadius - 7 : DotAt right [y-coord 3 ] dotRadius - if [query-glyph 'braille7'] : set [query-glyph 'braille7'].autoRefPriority 101 - if [query-glyph 'braille37'] : set [query-glyph 'braille37'].autoRefPriority 102 - if [query-glyph 'braille3'] : set [query-glyph 'braille3'].autoRefPriority 101 - if [query-glyph 'braille237'] : set [query-glyph 'braille237'].autoRefPriority 103 - if [query-glyph 'braille27'] : set [query-glyph 'braille27'].autoRefPriority 102 - if [query-glyph 'braille23'] : set [query-glyph 'braille23'].autoRefPriority 102 - if [query-glyph 'braille2'] : set [query-glyph 'braille2'].autoRefPriority 101 - if [query-glyph 'braille1237'] : set [query-glyph 'braille1237'].autoRefPriority 104 - if [query-glyph 'braille123'] : set [query-glyph 'braille123'].autoRefPriority 103 - if [query-glyph 'braille127'] : set [query-glyph 'braille127'].autoRefPriority 103 - if [query-glyph 'braille137'] : set [query-glyph 'braille137'].autoRefPriority 103 - if [query-glyph 'braille13'] : set [query-glyph 'braille13'].autoRefPriority 102 - if [query-glyph 'braille12'] : set [query-glyph 'braille12'].autoRefPriority 102 - if [query-glyph 'braille17'] : set [query-glyph 'braille17'].autoRefPriority 102 - if [query-glyph 'braille1'] : set [query-glyph 'braille1'].autoRefPriority 101 - if [query-glyph 'braille7'] : set [query-glyph 'braille7'].avoidBeingComposite true - if [query-glyph 'braille37'] : set [query-glyph 'braille37'].avoidBeingComposite true - if [query-glyph 'braille3'] : set [query-glyph 'braille3'].avoidBeingComposite true - if [query-glyph 'braille237'] : set [query-glyph 'braille237'].avoidBeingComposite true - if [query-glyph 'braille27'] : set [query-glyph 'braille27'].avoidBeingComposite true - if [query-glyph 'braille23'] : set [query-glyph 'braille23'].avoidBeingComposite true - if [query-glyph 'braille2'] : set [query-glyph 'braille2'].avoidBeingComposite true - if [query-glyph 'braille1237'] : set [query-glyph 'braille1237'].avoidBeingComposite true - if [query-glyph 'braille123'] : set [query-glyph 'braille123'].avoidBeingComposite true - if [query-glyph 'braille127'] : set [query-glyph 'braille127'].avoidBeingComposite true - if [query-glyph 'braille137'] : set [query-glyph 'braille137'].avoidBeingComposite true - if [query-glyph 'braille13'] : set [query-glyph 'braille13'].avoidBeingComposite true - if [query-glyph 'braille12'] : set [query-glyph 'braille12'].avoidBeingComposite true - if [query-glyph 'braille17'] : set [query-glyph 'braille17'].avoidBeingComposite true - if [query-glyph 'braille1'] : set [query-glyph 'braille1'].avoidBeingComposite true + define stageConfig : list + list 1 BrailleOutlineShape + list 2 BrailleReferenceShape + list 3 BrailleHalvesShape + + foreach { stage F } [items-of stageConfig] : begin + foreach [byte : range 1 256] : if (stage == [brailleBuildStage byte]) + create-glyph [brailleGlyphName byte] (0x2800 + byte) : F byte diff --git a/font-src/glyphs/symbol/geometric.ptl b/font-src/glyphs/symbol/geometric.ptl index 9afd28992..4d0be5c16 100644 --- a/font-src/glyphs/symbol/geometric.ptl +++ b/font-src/glyphs/symbol/geometric.ptl @@ -305,7 +305,7 @@ glyph-block Symbol-Geometric : for-width-kinds WideWidth1 corner (cx - size * triangleOvershoot) (cy - size) define [TriangularVeeFill cx cy size] : intersection - TriangleUpFill cx cy size + TriangleDownFill cx cy size union dispiro widths.lhs GeometryStroke diff --git a/font-src/glyphs/symbol/pictograph.ptl b/font-src/glyphs/symbol/pictograph.ptl index 436af69b4..e555fb0c6 100644 --- a/font-src/glyphs/symbol/pictograph.ptl +++ b/font-src/glyphs/symbol/pictograph.ptl @@ -4,7 +4,7 @@ $$include '../../meta/macros.ptl' import [mix linreg clamp fallback] from '../../support/utils' import [designParameters] from '../../meta/aesthetics' import '../../support/transform' as Transform -import '../../support/mask-bit' as maskBit +import [maskBit] from '../../support/mask-bit' glyph-module diff --git a/font-src/glyphs/symbol/punctuation.ptl b/font-src/glyphs/symbol/punctuation.ptl index eaa66116b..b3d115e2c 100644 --- a/font-src/glyphs/symbol/punctuation.ptl +++ b/font-src/glyphs/symbol/punctuation.ptl @@ -1135,27 +1135,17 @@ glyph-block Symbol-Punctuation-Quotes-And-Primes : begin include : FlipAround Middle [mix [mix PeriodSize commaLow 0.5] yCurlyQuotes 0.5] include : FlipAround Middle yCurlyQuotes - create-glyph 'lowDoubleQuote' 0x201E : glyph-proc + define [DoubleQuoteProc gn] : glyph-proc local dist : Width * 0.225 - include : refer-glyph "lowSingleQuote" + include : refer-glyph gn include : Translate (-dist * 2) 0 - include : refer-glyph "lowSingleQuote" + include : refer-glyph gn include : Translate dist 0 - create-glyph 'openDoubleQuote' 0x201C : glyph-proc - include : refer-glyph "lowDoubleQuote" - include : FlipAround Middle [mix [mix PeriodSize commaLow 0.5] yCurlyQuotes 0.5] - - create-glyph 'closeDoubleQuote' 0x201D : glyph-proc - include : refer-glyph "openDoubleQuote" - include : FlipAround Middle yCurlyQuotes - - create-glyph 'revertDoubleQuote' 0x201F : glyph-proc - local dist : Width * 0.225 - include : refer-glyph "revertSingleQuote" - include : Translate (-dist * 2) 0 - include : refer-glyph "revertSingleQuote" - include : Translate dist 0 + create-glyph 'lowDoubleQuote' 0x201E : DoubleQuoteProc 'lowSingleQuote' + create-glyph 'openDoubleQuote' 0x201C : DoubleQuoteProc 'openSingleQuote' + create-glyph 'closeDoubleQuote' 0x201D : DoubleQuoteProc 'closeSingleQuote' + create-glyph 'revertDoubleQuote' 0x201F : DoubleQuoteProc 'revertSingleQuote' create-glyph 'prime' 0x2032 : glyph-proc local wide : Width * 0.1 @@ -1592,15 +1582,18 @@ glyph-block Symbol-Punctuation-Ligation-Variants : begin * "question" * "exclam" - foreach [baseName : items-of dotLikePunctuations] : if [query-glyph baseName] - create-glyph : glyph-proc - include [refer-glyph baseName] AS_BASE ALSO_METRICS - local delta : Math.max 0 : Width / 2 - (XH - PeriodSize) / 2 - create-derived (baseName + '.lig.dRight') : Translate (delta * 1.0 ) 0 - create-derived (baseName + '.lig.dRightHalf') : Translate (delta * 0.5 ) 0 - create-derived (baseName + ".lig.dMid") : Translate 0 0 - create-derived (baseName + '.lig.dLeftHalf') : Translate (delta * (-0.5)) 0 - create-derived (baseName + '.lig.dLeft') : Translate (delta * (-1.0)) 0 + define [LigD baseName suffix pDelta] : if [query-glyph baseName] : begin + local delta : Math.max 0 : Width / 2 - (XH - PeriodSize) / 2 + create-glyph (baseName + '.lig.' + suffix) : composite-proc + refer-glyph baseName + Translate (delta * pDelta ) 0 + + foreach [baseName : items-of dotLikePunctuations] : begin + LigD baseName 'dRight' 1.0 + LigD baseName 'dRightHalf' 0.5 + LigD baseName "dMid" 0 + LigD baseName 'dLeftHalf' (-0.5) + LigD baseName 'dLeft' (-1.0) glyph-block Symbol-Other-Phonetic : begin glyph-block-import CommonShapes diff --git a/font-src/index.js b/font-src/index.js index 7d845bc59..50246cd71 100644 --- a/font-src/index.js +++ b/font-src/index.js @@ -2,6 +2,7 @@ const fs = require("fs-extra"); const path = require("path"); +const { FontIo } = require("ot-builder"); const Toml = require("@iarna/toml"); @@ -15,7 +16,7 @@ module.exports = async function main(argv) { const para = await getParameters(argv); const { font, glyphStore } = BuildFont(para); if (argv.oCharMap) await saveCharMap(argv, glyphStore); - if (argv.o) await saveOTD(argv, font); + if (argv.o) await saveTTF(argv, font); }; // Parameter preparation @@ -81,9 +82,14 @@ async function tryParseToml(str) { } } -// Save OTD -async function saveOTD(argv, font) { - await fs.writeJSON(argv.o, font); +// Save TTF +async function saveTTF(argv, font) { + const sfnt = FontIo.writeFont(font, { + glyphStore: { statOs2XAvgCharWidth: false }, + generateDummyDigitalSignature: true + }); + const buf = FontIo.writeSfntOtf(sfnt); + await fs.writeFile(argv.o, buf); } // Save character map file diff --git a/font-src/gen/kits/boole-kit.ptl b/font-src/kits/boole-kit.ptl similarity index 96% rename from font-src/gen/kits/boole-kit.ptl rename to font-src/kits/boole-kit.ptl index 9bf8a131b..5e64d6d9d 100644 --- a/font-src/gen/kits/boole-kit.ptl +++ b/font-src/kits/boole-kit.ptl @@ -1,5 +1,5 @@ import 'typo-geom' as TypoGeom -import '../../support/curve-util' as CurveUtil +import '../support/curve-util' as CurveUtil export : define [SetupBuilders args] : begin define [object Glyph GlobalTransform] args diff --git a/font-src/gen/kits/spiro-kit.ptl b/font-src/kits/spiro-kit.ptl similarity index 97% rename from font-src/gen/kits/spiro-kit.ptl rename to font-src/kits/spiro-kit.ptl index d1eb225b1..0df51f645 100644 --- a/font-src/gen/kits/spiro-kit.ptl +++ b/font-src/kits/spiro-kit.ptl @@ -1,8 +1,8 @@ import 'spiro' as SpiroJs -import '../../support/spiro-expand' as SpiroExpansionContext -import '../../support/curve-util' as CurveUtil -import '../../support/transform' as Transform -import '../../support/utils' as [object fallback mix bez2 bez3] +import '../support/spiro-expand' as SpiroExpansionContext +import '../support/curve-util' as CurveUtil +import '../support/transform' as Transform +import '../support/utils' as [object fallback mix bez2 bez3] export : define [SetupBuilders args] : begin define [object para Glyph Contrast GlobalTransform Stroke] args diff --git a/font-src/meta/macros.ptl b/font-src/meta/macros.ptl index 952203026..dbae7f1e4 100644 --- a/font-src/meta/macros.ptl +++ b/font-src/meta/macros.ptl @@ -107,7 +107,7 @@ define-macro glyph-block-import : syntax-rules define allExports : object Common-Derivatives `[select-variant italic-variant refer-glyph query-glyph alias turned - HDual HCombine VDual vcombine with-related-glyphs glyph-is-needed] + HDual HCombine VDual VCombine with-related-glyphs glyph-is-needed] CommonShapes `[Rect SquareAt Ring RingAt DotAt RingStroke RingStrokeAt DotStrokeAt CircleRing CircleRingAt CircleDotAt OShape OShapeOutline OBarLeftShape OBarRightShape diff --git a/font-src/meta/naming.ptl b/font-src/meta/naming.ptl index 2fc2d2e69..2de403c63 100644 --- a/font-src/meta/naming.ptl +++ b/font-src/meta/naming.ptl @@ -140,9 +140,6 @@ export : define [assignFontNames para metrics font] : begin nameFont font WWS_PREFERRED_FAMILY family # WWS Preferred Family nameFont font WWS_PREFERRED_STYLE style # WWS Preferred Style - set font.name.preferredFamily family - set font.name.preferredSubFamily style - local {compatStyle compatFamilySuffix shortCompatFamilySuffix} : getStyleLinkedStyles para.naming.weight para.naming.width para.naming.slope local compatFamily family if (compatFamilySuffix != "Regular") : set compatFamily : family + ' ' + compatFamilySuffix diff --git a/font-src/otl/gpos-mark-mkmk.ptl b/font-src/otl/gpos-mark-mkmk.ptl index f498ddf03..d608116b3 100644 --- a/font-src/otl/gpos-mark-mkmk.ptl +++ b/font-src/otl/gpos-mark-mkmk.ptl @@ -18,17 +18,17 @@ export : define [buildMarkMkmk sink glyphStore] : begin foreach markCls [items-of MarkClasses] : begin local [object markSubtable mkmkSubtable] : createMTSubtables glyphStore { markCls } if ([objectIsNotEmpty markSubtable.marks] && [objectIsNotEmpty markSubtable.bases]) : begin - local markLookup : add-lookup sink {.type 'gpos_mark_to_base' .subtables { markSubtable }} - mark.lookups.push markLookup - markLookupNames.push markLookup + local lidMark : add-lookup sink {.type 'gpos_mark_to_base' .subtables { markSubtable }} + mark.lookups.push lidMark + markLookupNames.push lidMark if ([objectIsNotEmpty mkmkSubtable.marks] && [objectIsNotEmpty mkmkSubtable.bases]) : begin - local mkmkLookup : add-lookup sink {.type 'gpos_mark_to_mark' .subtables { mkmkSubtable }} - mkmk.lookups.push mkmkLookup - mkmkLookupNames.push mkmkLookup + local lidMkmk : add-lookup sink {.type 'gpos_mark_to_mark' .subtables { mkmkSubtable }} + mkmk.lookups.push lidMkmk + mkmkLookupNames.push lidMkmk - foreach markLookup [items-of markLookupNames] : foreach mkmkLookup [items-of mkmkLookupNames] - sink.lookupDep.push { markLookup mkmkLookup } + foreach lidMark [items-of markLookupNames] : foreach lidMkmk [items-of mkmkLookupNames] + sink.lookupDep.push { lidMark lidMkmk } define [createMTSubtables glyphStore markClasses] : begin local markSubtable {.marks {.} .bases {.}} diff --git a/font-src/support/glyph.js b/font-src/support/glyph.js index 5ffd9414f..d8e6d8a12 100644 --- a/font-src/support/glyph.js +++ b/font-src/support/glyph.js @@ -13,6 +13,7 @@ module.exports = class Glyph { this.markAnchors = {}; this.baseAnchors = {}; this.gizmo = Transform.Id(); + this.semanticInclusions = []; this.dependencies = []; this.defaultTag = null; } @@ -51,6 +52,7 @@ module.exports = class Glyph { this.contours[j++] = this.contours[i]; } this.contours.length = j; + this.semanticInclusions = []; } // Inclusion include(component, copyAnchors, copyWidth) { @@ -83,10 +85,18 @@ module.exports = class Glyph { } } - this.includeGeometry(g, shift.x, shift.y); + const newContours = this.includeGeometry(g, shift.x, shift.y); if (copyAnchors || g.isMarkSet) this.copyAnchors(g); if (copyWidth && g.advanceWidth >= 0) this.advanceWidth = g.advanceWidth; this.dependsOn(g); + if (g._m_identifier && newContours && newContours.length) { + this.semanticInclusions.push({ + glyph: g, + x: shift.x, + y: shift.y, + contours: newContours + }); + } } cloneFromGlyph(g) { this.includeGlyph(g, true, true); @@ -102,15 +112,56 @@ module.exports = class Glyph { this.glyphRank = g.glyphRank; this.avoidBeingComposite = g.avoidBeingComposite; } + includeGeometry(geom, shiftX, shiftY) { - if (!geom || !geom.contours) return; + if (!geom || !geom.contours || !geom.contours.length) return null; + if (this.includeGeometryAsTransparentReferences(geom, shiftX, shiftY)) return null; + return this.includeGeometryImpl(geom, shiftX, shiftY); + } + + isPureComposite() { + if (!this.semanticInclusions || !this.semanticInclusions.length) return false; + const origContourSet = new Set(this.contours); + let handledContours = new Set(); + for (const sr of this.semanticInclusions) { + for (const c of sr.contours) { + if (!origContourSet.has(c) || handledContours.has(c)) return false; + handledContours.add(c); + } + } + for (const c of this.contours) if (!handledContours.has(c)) return false; + return true; + } + + includeGeometryAsTransparentReferences(geom, shiftX, shiftY) { + if (!(geom instanceof Glyph && !geom._m_identifier)) return false; + if (!geom.isPureComposite()) return false; + + for (const sr of geom.semanticInclusions) { + const cs = this.includeGeometryImpl(sr.glyph, sr.x + shiftX, sr.y + shiftY); + if (cs) { + this.semanticInclusions.push({ + glyph: sr.glyph, + x: sr.x + shiftX, + y: sr.y + shiftY, + contours: cs + }); + } + } + return true; + } + includeGeometryImpl(geom, shiftX, shiftY) { + let newContours = []; for (const contour of geom.contours) { let c = []; c.tag = contour.tag || geom.tag || this.defaultTag; for (const z of contour) c.push(Point.translated(z, shiftX, shiftY)); this.contours.push(c); + newContours.push(c); } + return newContours; } + combineAnchor(shift, baseThis, markThat, basesThat) { if (!baseThis || !markThat) return; shift.x = baseThis.x - markThat.x; @@ -129,10 +180,20 @@ module.exports = class Glyph { if (g.baseAnchors) for (const k in g.baseAnchors) this.baseAnchors[k] = g.baseAnchors[k]; } applyTransform(tfm, alsoAnchors) { - for (const c of this.contours) - for (let j = 0; j < c.length; j++) { - c[j] = Point.transformed(tfm, c[j]); + for (const c of this.contours) { + for (let k = 0; k < c.length; k++) { + c[k] = Point.transformed(tfm, c[k]); } + } + if (Transform.isTranslate(tfm)) { + for (const sr of this.semanticInclusions) { + sr.x += tfm.x; + sr.y += tfm.y; + } + } else { + // Applying a non-trivial inclusion will unlink all the SIs + this.semanticInclusions = []; + } if (alsoAnchors) { for (const k in this.baseAnchors) this.baseAnchors[k] = Anchor.transform(tfm, this.baseAnchors[k]); @@ -140,6 +201,35 @@ module.exports = class Glyph { this.markAnchors[k] = Anchor.transform(tfm, this.markAnchors[k]); } } + + tryBecomeMirrorOf(dst, rankSet) { + if (rankSet.has(this) || rankSet.has(dst)) return; + if (this.contours.length !== dst.contours.length) return; + for (let j = 0; j < this.contours.length; j++) { + const c1 = this.contours[j], + c2 = dst.contours[j]; + if (c1.length !== c2.length) return; + } + for (let j = 0; j < this.contours.length; j++) { + const c1 = this.contours[j], + c2 = dst.contours[j]; + for (let k = 0; k < c1.length; k++) { + const z1 = c1[k], + z2 = c2[k]; + if (z1.x !== z2.x || z1.y !== z2.y || z1.on !== z2.on) return; + } + } + this.semanticInclusions = [ + { + glyph: dst, + x: 0, + y: 0, + contours: [...this.contours] + } + ]; + rankSet.add(this); + } + // Anchors setBaseAnchor(id, x, y) { this.baseAnchors[id] = new Anchor(x, y).transform(this.gizmo); diff --git a/font-src/support/mask-bit.js b/font-src/support/mask-bit.js index 5aa505c00..6a3b7fa16 100644 --- a/font-src/support/mask-bit.js +++ b/font-src/support/mask-bit.js @@ -1,5 +1,14 @@ "use strict"; -module.exports = function maskBit(x, y) { +exports.maskBit = function maskBit(x, y) { return x & (1 << y); }; + +exports.maskBits = function maskBits(x, y) { + return x & y; +}; + +const pcNibbleLookup = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; +exports.popCountByte = function (x) { + return pcNibbleLookup[x & 0x0f] + pcNibbleLookup[(x >>> 4) & 0x0f]; +}; diff --git a/font-src/support/transform.js b/font-src/support/transform.js index fd3a42ae3..656a299a6 100644 --- a/font-src/support/transform.js +++ b/font-src/support/transform.js @@ -46,4 +46,8 @@ module.exports = class Transform { -(-this.x * this.xy + this.y * this.xx) / denom ); } + + static isTranslate(tfm) { + return tfm.xx === 1 && tfm.yy === 1 && tfm.xy === 0 && tfm.yx === 0; + } }; diff --git a/package.json b/package.json index 8167ab093..f2956de1d 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "ejs": "^3.1.3", "fs-extra": "^9.0.1", "object-assign": "^4.1.1", - "otfcc-ttcize": "^0.10.2", + "ot-builder": "^0.10.28", + "otb-ttc-bundle": "^0.10.28", "patel": "^0.33.1", "semver": "^7.3.2", "spiro": "^2.0.0", diff --git a/utility/check-env.js b/utility/check-env.js index 0304e2a83..1df9ff20b 100644 --- a/utility/check-env.js +++ b/utility/check-env.js @@ -7,11 +7,7 @@ const FgYellow = "\x1b[33m"; const Reset = "\x1b[0m"; console.log("Checking External Dependencies"); -check("otfccdump"); -check("otfccbuild"); check("ttfautohint"); -check("otf2otc"); -checkOptional("ttx"); function check(util) { try { @@ -21,11 +17,3 @@ function check(util) { console.error(FgRed + ` * External dependency <${util}> is not found.` + Reset); } } -function checkOptional(util) { - try { - which.sync(util); - console.error(FgGreen + ` * Optional external dependency <${util}> is present.` + Reset); - } catch (e) { - console.error(FgYellow + ` * Optional external dependency <${util}> is not found.` + Reset); - } -} diff --git a/verdafile.js b/verdafile.js index fdf420117..ac4a10577 100644 --- a/verdafile.js +++ b/verdafile.js @@ -12,7 +12,7 @@ module.exports = build; /////////////////////////////////////////////////////////// -const path = require("path"); +const Path = require("path"); const toml = require("@iarna/toml"); const BUILD = ".build"; @@ -21,11 +21,13 @@ const SNAPSHOT_TMP = ".build/snapshot"; const DIST_SUPER_TTC = "dist/.super-ttc"; const ARCHIVE_DIR = "release-archives"; -const OTF2OTC = "otf2otc"; -const OTFCC_BUILD = "otfccbuild"; const TTX = "ttx"; const PATEL_C = ["node", "./node_modules/patel/bin/patel-c"]; -const TTCIZE = ["node", "./node_modules/otfcc-ttcize/bin/_startup"]; +const TTCIZE = [ + "node", + "--max-old-space-size=8192", + "node_modules/otb-ttc-bundle/bin/otb-ttc-bundle" +]; const webfontFormats = [ ["woff2", "woff2"], ["woff", "woff"], @@ -41,11 +43,8 @@ const WEIGHT_NORMAL = "regular"; const SLOPE_NORMAL = "upright"; const DEFAULT_SUBFAMILY = "regular"; -const BUILD_PLANS = path.relative(__dirname, path.resolve(__dirname, "./build-plans.toml")); -const PRIVATE_BUILD_PLANS = path.relative( - __dirname, - path.resolve(__dirname, "./private-build-plans.toml") -); +const BUILD_PLANS = "build-plans.toml"; +const PRIVATE_BUILD_PLANS = "private-build-plans.toml"; // Save journal to build/ build.setJournal(`${BUILD}/.verda-build-journal`); @@ -56,8 +55,9 @@ build.setSelfTracking(); ////// Oracles ////// /////////////////////////////////////////////////////////// -const Version = oracle(`oracle:version`, async () => { - const package_json = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"))); +const Version = oracle(`oracle:version`, async target => { + const [pj] = await target.need(sfu`package.json`); + const package_json = JSON.parse(await fs.promises.readFile(pj.full, "utf-8")); return package_json.version; }); @@ -422,15 +422,9 @@ const BuildRawTtf = file.make( const [fi] = await target.need(FontInfoOf(fn), Version); const charmap = output.dir + "/" + fn + ".charmap"; await target.need(Scripts, Parameters, de`${output.dir}`); - const otdPath = `${output.dir}/${output.name}.otd`; - await node("font-src/index", { o: otdPath, oCharMap: charmap, ...fi }); - await optimizedOtfcc(otdPath, output.full); - await rm(otdPath); + await node("font-src/index", { o: output.full, oCharMap: charmap, ...fi }); } ); -function optimizedOtfcc(from, to) { - return run(OTFCC_BUILD, from, ["-o", `${to}`], ["-O3", "--keep-average-char-width", "-q"]); -} const BuildTTF = file.make( (gr, fn) => `${BUILD}/ttf/${gr}/${fn}.ttf`, @@ -557,25 +551,17 @@ const glyfTtc = file.make( ); async function buildGlyfTtc(target, parts, out) { - const [useFilter, useTtx] = await target.need( - OptimizeWithFilter, - OptimizeWithTtx, - de`${out.dir}` - ); + await target.need(de`${out.dir}`); const [ttfInputs] = await target.need(parts.map(part => BuildTTF(part.dir, part.file))); const tmpTtc = `${out.dir}/${out.name}.unhinted.ttc`; const ttfInputPaths = ttfInputs.map(p => p.full); - const optimization = useFilter ? ["--filter-loop", useFilter] : useTtx ? ["--ttx-loop"] : []; - await run(TTCIZE, optimization, ["-o", tmpTtc], ttfInputPaths); + await run(TTCIZE, "-u", ["-o", tmpTtc], ttfInputPaths); await run("ttfautohint", tmpTtc, out.full); await rm(tmpTtc); } async function buildCompositeTtc(out, inputs) { - await run( - OTF2OTC, - ["-o", out.full], - inputs.map(f => f.full) - ); + const inputPaths = inputs.map(f => f.full); + await run(TTCIZE, ["-o", out.full], inputPaths); } const ExportSuperTtc = file.make( @@ -664,7 +650,7 @@ const GroupArchive = task.group(`archive`, async (target, gid) => { /////////////////////////////////////////////////////////// const PagesDir = oracle(`pages-dir-path`, async target => { - const pagesDir = path.resolve(__dirname, "../Iosevka-Pages"); + const pagesDir = Path.resolve(__dirname, "../Iosevka-Pages"); if (!fs.existsSync(pagesDir)) { return ""; } else { @@ -686,7 +672,7 @@ const PagesDataExport = task(`pages:data-export`, async target => { cm.full, cmi.full, cmo.full, - path.resolve(pagesDir, "shared/data-import/iosevka.json") + Path.resolve(pagesDir, "shared/data-import/iosevka.json") ); }); @@ -701,7 +687,7 @@ const PagesFontExport = task(`pages:font-export`, async target => { GroupContents`iosevka-sparkle` ); for (const dir of dirs) { - await cp(`${DIST}/${dir}`, path.resolve(pagesDir, "shared/font-import", dir)); + await cp(`${DIST}/${dir}`, Path.resolve(pagesDir, "shared/font-import", dir)); } }); @@ -710,7 +696,7 @@ const PagesFastFontExport = task(`pages:fast-font-export`, async target => { if (!pagesDir) return; const dirs = await target.need(GroupContents`iosevka`); for (const dir of dirs) { - await cp(`${DIST}/${dir}`, path.resolve(pagesDir, "shared/font-import", dir)); + await cp(`${DIST}/${dir}`, Path.resolve(pagesDir, "shared/font-import", dir)); } }); @@ -889,28 +875,31 @@ const ScriptFiles = computed.group("script-files", async (target, ext) => { }); const JavaScriptFromPtl = computed("scripts-js-from-ptl", async target => { const [ptl] = await target.need(ScriptFiles("ptl")); - return ptl.map(x => x.replace(/\.ptl$/g, ".js")); + return ptl.map(x => replaceExt(".js", x)); }); +function replaceExt(extNew, file) { + return Path.join(Path.dirname(file), Path.basename(file, Path.extname(file)) + extNew); +} -const ScriptJS = file.glob(`font-src/**/*.js`, async (target, path) => { - const [jsFromPtl] = await target.need(JavaScriptFromPtl); - if (jsFromPtl.indexOf(path.full) >= 0) { - const ptl = path.full.replace(/\.js$/g, ".ptl"); - if (/\/glyphs\//.test(path.full)) { - await target.need(MARCOS); - } - await target.need(fu`${ptl}`); - await run(PATEL_C, "--strict", ptl, "-o", path.full); - } else { - await target.need(fu`${path.full}`); +const CompiledJs = file.make( + p => p, + async (target, out) => { + const ptl = replaceExt(".ptl", out.full); + if (/\/glyphs\//.test(out.full)) await target.need(MARCOS); + await target.need(sfu(ptl)); + await run(PATEL_C, "--strict", ptl, "-o", out.full); } -}); +); const Scripts = task("scripts", async target => { await target.need(Parameters); - const [jsFromPtl] = await target.need(JavaScriptFromPtl); - await target.need(jsFromPtl); + const [_jsFromPtl] = await target.need(JavaScriptFromPtl); const [js] = await target.need(ScriptFiles("js")); - await target.need(js.map(ScriptJS)); + const jsFromPtl = new Set(_jsFromPtl); + + let subGoals = []; + for (const item of jsFromPtl) subGoals.push(CompiledJs(item)); + for (const item of js) if (!jsFromPtl.has(js)) subGoals.push(sfu(item)); + await target.need(subGoals); }); const UtilScripts = task("util-scripts", async target => { const [files] = await target.need(UtilScriptFiles);