Generate TTFAutohint control files for better glyph display for variant glyphs (#1963).
This commit is contained in:
parent
6fed1572c1
commit
25ee0bcc50
9 changed files with 932 additions and 31 deletions
|
@ -5,3 +5,4 @@
|
||||||
- COMBINING DOUBLE VERTICAL STROKE OVERLAY (`U+20E6`).
|
- COMBINING DOUBLE VERTICAL STROKE OVERLAY (`U+20E6`).
|
||||||
- COMBINING LONG DOUBLE SOLIDUS OVERLAY (`U+20EB`).
|
- COMBINING LONG DOUBLE SOLIDUS OVERLAY (`U+20EB`).
|
||||||
* Improve glyphs for COLON SIGN (`U+20A1`), GUARANI SIGN (`U+20B2`), and CEDI SIGN (`U+20B5`).
|
* Improve glyphs for COLON SIGN (`U+20A1`), GUARANI SIGN (`U+20B2`), and CEDI SIGN (`U+20B5`).
|
||||||
|
* Generate TTFAutohint control files for better glyph display for variant glyphs (#1963).
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { finalizeFont } from "./finalize/index.mjs";
|
||||||
import { CreateEmptyFont } from "./meta/empty-font.mjs";
|
import { CreateEmptyFont } from "./meta/empty-font.mjs";
|
||||||
import { assignFontNames } from "./meta/naming.mjs";
|
import { assignFontNames } from "./meta/naming.mjs";
|
||||||
import { convertOtd } from "./otd-conv/index.mjs";
|
import { convertOtd } from "./otd-conv/index.mjs";
|
||||||
|
import { generateTtfaControls } from "./ttfa-controls/index.mjs";
|
||||||
|
|
||||||
export async function buildFont(argv, para) {
|
export async function buildFont(argv, para) {
|
||||||
const baseFont = CreateEmptyFont(argv);
|
const baseFont = CreateEmptyFont(argv);
|
||||||
|
@ -30,6 +31,7 @@ export async function buildFont(argv, para) {
|
||||||
if (cache.isUpdated()) {
|
if (cache.isUpdated()) {
|
||||||
await Caching.save(argv.oCache, argv.menu.version, cache, true);
|
await Caching.save(argv.oCache, argv.menu.version, cache, true);
|
||||||
}
|
}
|
||||||
const font = convertOtd(baseFont, otl, finalGs);
|
const font = await convertOtd(baseFont, otl, finalGs);
|
||||||
return { font, glyphStore: finalGs, cacheUpdated: cache.isUpdated() };
|
const ttfaControls = await generateTtfaControls(finalGs, font.glyphs);
|
||||||
|
return { font, glyphStore: finalGs, cacheUpdated: cache.isUpdated(), ttfaControls };
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Ot } from "ot-builder";
|
||||||
import { Point } from "../../support/geometry/point.mjs";
|
import { Point } from "../../support/geometry/point.mjs";
|
||||||
import * as Gr from "../../support/gr.mjs";
|
import * as Gr from "../../support/gr.mjs";
|
||||||
|
|
||||||
import { byCode, bySpacing, byGr, byBuildOrder } from "./glyph-name.mjs";
|
import { byBuildOrder, byCode, byGr, bySpacing } from "./glyph-name.mjs";
|
||||||
|
|
||||||
function byRank([gna, a], [gnb, b]) {
|
function byRank([gna, a], [gnb, b]) {
|
||||||
return (
|
return (
|
||||||
|
@ -28,9 +28,11 @@ class MappedGlyphStore {
|
||||||
this.m_primaryUnicodeMapping.set(u, source);
|
this.m_primaryUnicodeMapping.set(u, source);
|
||||||
}
|
}
|
||||||
queryBySourceGlyph(source) {
|
queryBySourceGlyph(source) {
|
||||||
|
if (!source) return undefined;
|
||||||
return this.m_mapping.get(source);
|
return this.m_mapping.get(source);
|
||||||
}
|
}
|
||||||
queryByName(name) {
|
queryByName(name) {
|
||||||
|
if (!name) return undefined;
|
||||||
return this.m_nameMapping.get(name);
|
return this.m_nameMapping.get(name);
|
||||||
}
|
}
|
||||||
decideOrder() {
|
decideOrder() {
|
||||||
|
|
56
font-src/gen/ttfa-controls/index.mjs
Normal file
56
font-src/gen/ttfa-controls/index.mjs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import ttfaRanges from "../../generated/ttfa-ranges.mjs";
|
||||||
|
import * as Gr from "../../support/gr.mjs";
|
||||||
|
import { ArrayUtil } from "../../support/utils.mjs";
|
||||||
|
|
||||||
|
export async function generateTtfaControls(gsOrig, gs) {
|
||||||
|
let ttfaControls = [];
|
||||||
|
|
||||||
|
for (const alignment of ttfaRanges) {
|
||||||
|
ttfaControls.push(generateTTFAAlignments(alignment, gsOrig, gs));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ttfaControls;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateTTFAAlignments(alignment, gsOrig, gsTtf) {
|
||||||
|
let collectedGlyphs = new Map();
|
||||||
|
for (const [lo, hi] of alignment.ranges) {
|
||||||
|
for (let lch = lo; lch <= hi; lch++) {
|
||||||
|
const go = gsOrig.queryByUnicode(lch);
|
||||||
|
if (!go) continue;
|
||||||
|
const gd = gsTtf.queryBySourceGlyph(go);
|
||||||
|
if (!gd) continue;
|
||||||
|
collectedGlyphs.set(go, gd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
let sizeBefore = collectedGlyphs.size;
|
||||||
|
|
||||||
|
for (const [go, gd] of collectedGlyphs) {
|
||||||
|
const cvs = Gr.AnyCv.query(go);
|
||||||
|
for (const gr of cvs) {
|
||||||
|
const gnLinked = gr.get(go);
|
||||||
|
if (!gnLinked) continue;
|
||||||
|
const goLinked = gsOrig.queryByName(gnLinked);
|
||||||
|
if (!goLinked) continue;
|
||||||
|
const gdLinked = gsTtf.queryBySourceGlyph(goLinked);
|
||||||
|
if (!gdLinked) continue;
|
||||||
|
collectedGlyphs.set(goLinked, gdLinked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sizeAfter = collectedGlyphs.size;
|
||||||
|
if (sizeAfter <= sizeBefore) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gOrd = gsTtf.decideOrder();
|
||||||
|
const glyphIndices = Array.from(collectedGlyphs.values()).map(gd => gOrd.reverse(gd));
|
||||||
|
const glyphIndicesRangesStr = ArrayUtil.toRanges(glyphIndices)
|
||||||
|
.map(([lo, hi]) => (lo === hi ? `${lo}` : `${lo}-${hi}`))
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
const styleAdjustLine = `${alignment.scriptTag} ${alignment.featureTag} @ ${glyphIndicesRangesStr}`;
|
||||||
|
|
||||||
|
return styleAdjustLine;
|
||||||
|
}
|
514
font-src/generated/ttfa-ranges.mjs
Normal file
514
font-src/generated/ttfa-ranges.mjs
Normal file
|
@ -0,0 +1,514 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
// Machine generated. Do not modify。
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
"scriptTag": "latn",
|
||||||
|
"featureTag": "dflt",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
177
|
||||||
|
],
|
||||||
|
[
|
||||||
|
180,
|
||||||
|
184
|
||||||
|
],
|
||||||
|
[
|
||||||
|
186,
|
||||||
|
442
|
||||||
|
],
|
||||||
|
[
|
||||||
|
444,
|
||||||
|
447
|
||||||
|
],
|
||||||
|
[
|
||||||
|
452,
|
||||||
|
452
|
||||||
|
],
|
||||||
|
[
|
||||||
|
454,
|
||||||
|
455
|
||||||
|
],
|
||||||
|
[
|
||||||
|
457,
|
||||||
|
458
|
||||||
|
],
|
||||||
|
[
|
||||||
|
460,
|
||||||
|
497
|
||||||
|
],
|
||||||
|
[
|
||||||
|
499,
|
||||||
|
659
|
||||||
|
],
|
||||||
|
[
|
||||||
|
661,
|
||||||
|
687
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7424,
|
||||||
|
7461
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7531,
|
||||||
|
7543
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7545,
|
||||||
|
7578
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7680,
|
||||||
|
7935
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8192,
|
||||||
|
8303
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8352,
|
||||||
|
8399
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8450,
|
||||||
|
8479
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8483,
|
||||||
|
8506
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8508,
|
||||||
|
8524
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8526,
|
||||||
|
8527
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8579,
|
||||||
|
8580
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11360,
|
||||||
|
11387
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11390,
|
||||||
|
11391
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11776,
|
||||||
|
11903
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42786,
|
||||||
|
42863
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42865,
|
||||||
|
42887
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42891,
|
||||||
|
42894
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42896,
|
||||||
|
42954
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42960,
|
||||||
|
42961
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42963,
|
||||||
|
42963
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42965,
|
||||||
|
42969
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42997,
|
||||||
|
42998
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43002,
|
||||||
|
43002
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43824,
|
||||||
|
43866
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43872,
|
||||||
|
43876
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43878,
|
||||||
|
43880
|
||||||
|
],
|
||||||
|
[
|
||||||
|
64256,
|
||||||
|
64262
|
||||||
|
],
|
||||||
|
[
|
||||||
|
122624,
|
||||||
|
122633
|
||||||
|
],
|
||||||
|
[
|
||||||
|
122635,
|
||||||
|
122654
|
||||||
|
],
|
||||||
|
[
|
||||||
|
122661,
|
||||||
|
122666
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "latb",
|
||||||
|
"featureTag": "dflt",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
178,
|
||||||
|
179
|
||||||
|
],
|
||||||
|
[
|
||||||
|
185,
|
||||||
|
185
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7522,
|
||||||
|
7525
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8304,
|
||||||
|
8304
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8308,
|
||||||
|
8318
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8320,
|
||||||
|
8334
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8336,
|
||||||
|
8348
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11388,
|
||||||
|
11388
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "latp",
|
||||||
|
"featureTag": "dflt",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
688,
|
||||||
|
696
|
||||||
|
],
|
||||||
|
[
|
||||||
|
736,
|
||||||
|
740
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7468,
|
||||||
|
7516
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7579,
|
||||||
|
7614
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8305,
|
||||||
|
8305
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8319,
|
||||||
|
8319
|
||||||
|
],
|
||||||
|
[
|
||||||
|
11389,
|
||||||
|
11389
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42864,
|
||||||
|
42864
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42994,
|
||||||
|
42996
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43000,
|
||||||
|
43001
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43868,
|
||||||
|
43871
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43881,
|
||||||
|
43881
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67456,
|
||||||
|
67461
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67463,
|
||||||
|
67504
|
||||||
|
],
|
||||||
|
[
|
||||||
|
67506,
|
||||||
|
67514
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "grek",
|
||||||
|
"featureTag": "dflt",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
880,
|
||||||
|
883
|
||||||
|
],
|
||||||
|
[
|
||||||
|
886,
|
||||||
|
887
|
||||||
|
],
|
||||||
|
[
|
||||||
|
891,
|
||||||
|
893
|
||||||
|
],
|
||||||
|
[
|
||||||
|
895,
|
||||||
|
895
|
||||||
|
],
|
||||||
|
[
|
||||||
|
902,
|
||||||
|
902
|
||||||
|
],
|
||||||
|
[
|
||||||
|
904,
|
||||||
|
906
|
||||||
|
],
|
||||||
|
[
|
||||||
|
908,
|
||||||
|
908
|
||||||
|
],
|
||||||
|
[
|
||||||
|
910,
|
||||||
|
929
|
||||||
|
],
|
||||||
|
[
|
||||||
|
931,
|
||||||
|
993
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1008,
|
||||||
|
1013
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1015,
|
||||||
|
1023
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7462,
|
||||||
|
7466
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7936,
|
||||||
|
7957
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7960,
|
||||||
|
7965
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7968,
|
||||||
|
8005
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8008,
|
||||||
|
8013
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8016,
|
||||||
|
8023
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8025,
|
||||||
|
8025
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8027,
|
||||||
|
8027
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8029,
|
||||||
|
8029
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8031,
|
||||||
|
8061
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8064,
|
||||||
|
8071
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8080,
|
||||||
|
8087
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8096,
|
||||||
|
8103
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8112,
|
||||||
|
8116
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8118,
|
||||||
|
8123
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8126,
|
||||||
|
8126
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8130,
|
||||||
|
8132
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8134,
|
||||||
|
8139
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8144,
|
||||||
|
8147
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8150,
|
||||||
|
8155
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8160,
|
||||||
|
8172
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8178,
|
||||||
|
8180
|
||||||
|
],
|
||||||
|
[
|
||||||
|
8182,
|
||||||
|
8187
|
||||||
|
],
|
||||||
|
[
|
||||||
|
43877,
|
||||||
|
43877
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "grek",
|
||||||
|
"featureTag": "sups",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
890,
|
||||||
|
890
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7517,
|
||||||
|
7521
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7615,
|
||||||
|
7615
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "cyrl",
|
||||||
|
"featureTag": "dflt",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
1024,
|
||||||
|
1153
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1162,
|
||||||
|
1327
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7296,
|
||||||
|
7304
|
||||||
|
],
|
||||||
|
[
|
||||||
|
7467,
|
||||||
|
7467
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42560,
|
||||||
|
42605
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42624,
|
||||||
|
42651
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "grek",
|
||||||
|
"featureTag": "subs",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
7526,
|
||||||
|
7530
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "cyrl",
|
||||||
|
"featureTag": "sups",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
7544,
|
||||||
|
7544
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42623,
|
||||||
|
42623
|
||||||
|
],
|
||||||
|
[
|
||||||
|
42652,
|
||||||
|
42653
|
||||||
|
],
|
||||||
|
[
|
||||||
|
122928,
|
||||||
|
122960
|
||||||
|
],
|
||||||
|
[
|
||||||
|
122987,
|
||||||
|
122989
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scriptTag": "cyrl",
|
||||||
|
"featureTag": "subs",
|
||||||
|
"ranges": [
|
||||||
|
[
|
||||||
|
122961,
|
||||||
|
122986
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
|
@ -22,10 +22,13 @@ export default main;
|
||||||
async function main(argv) {
|
async function main(argv) {
|
||||||
const paraT = await getParameters();
|
const paraT = await getParameters();
|
||||||
const para = paraT(argv);
|
const para = paraT(argv);
|
||||||
const { font, glyphStore, cacheUpdated } = await buildFont(argv, para);
|
const { font, glyphStore, cacheUpdated, ttfaControls } = await buildFont(argv, para);
|
||||||
if (argv.oCharMap) {
|
if (argv.oCharMap) {
|
||||||
await saveCharMap(argv, glyphStore);
|
await saveCharMap(argv, glyphStore);
|
||||||
}
|
}
|
||||||
|
if (argv.oTtfaControls) {
|
||||||
|
await fs.promises.writeFile(argv.oTtfaControls, ttfaControls.join("\n") + "\n");
|
||||||
|
}
|
||||||
if (argv.o) {
|
if (argv.o) {
|
||||||
if (para.compatibilityLigatures) await buildCompatLigatures(para, font);
|
if (para.compatibilityLigatures) await buildCompatLigatures(para, font);
|
||||||
await saveTTF(argv.o, font);
|
await saveTTF(argv.o, font);
|
||||||
|
|
|
@ -114,5 +114,27 @@ export const ArrayUtil = {
|
||||||
},
|
},
|
||||||
insertSliceAt(a, i, b) {
|
insertSliceAt(a, i, b) {
|
||||||
a.splice(i, 0, ...b);
|
a.splice(i, 0, ...b);
|
||||||
|
},
|
||||||
|
// Convert character array to array of ranges. Input may be unsorted.
|
||||||
|
// The output ranges has both ends inclusive.
|
||||||
|
toRanges(chars) {
|
||||||
|
chars.sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const ranges = [];
|
||||||
|
let range = null;
|
||||||
|
|
||||||
|
for (const ch of chars) {
|
||||||
|
if (!range) {
|
||||||
|
range = [ch, ch];
|
||||||
|
ranges.push(range);
|
||||||
|
} else if (ch === range[1] + 1) {
|
||||||
|
range[1] = ch;
|
||||||
|
} else {
|
||||||
|
range = [ch, ch];
|
||||||
|
ranges.push(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
264
utility/generate-ttfa-ranges.mjs
Normal file
264
utility/generate-ttfa-ranges.mjs
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import { ArrayUtil } from "../font-src/support/utils.mjs";
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
main().catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
for (const target of Targets) {
|
||||||
|
await target.filter.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = new Map();
|
||||||
|
|
||||||
|
nextChar: for (let lch = 0; lch < 0x20000; lch++) {
|
||||||
|
for (const target of Targets) {
|
||||||
|
if (target.filter.has(lch)) {
|
||||||
|
const resultKey = `${target.scriptTag}-${target.featureTag}`;
|
||||||
|
let result = results.get(resultKey);
|
||||||
|
if (!result) {
|
||||||
|
result = [];
|
||||||
|
results.set(resultKey, result);
|
||||||
|
}
|
||||||
|
result.push(lch);
|
||||||
|
|
||||||
|
continue nextChar;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let out = [];
|
||||||
|
|
||||||
|
for (const [key, value] of results) {
|
||||||
|
out.push({
|
||||||
|
scriptTag: key.split("-")[0],
|
||||||
|
featureTag: key.split("-")[1],
|
||||||
|
ranges: ArrayUtil.toRanges(value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
"font-src/generated/ttfa-ranges.mjs",
|
||||||
|
`/* eslint-disable */\n` +
|
||||||
|
`// Machine generated. Do not modify。\n` +
|
||||||
|
`export default ` +
|
||||||
|
JSON.stringify(out, null, "\t") +
|
||||||
|
";\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class InUnicodeDataSet {
|
||||||
|
constructor(subpath) {
|
||||||
|
this.subpath = subpath;
|
||||||
|
this.dataset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
if (this.dataset) return;
|
||||||
|
const d = (await import(`@unicode/unicode-15.0.0/${this.subpath}/code-points.js`)).default;
|
||||||
|
this.dataset = new Set(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
has(lch) {
|
||||||
|
return this.dataset.has(lch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InScriptDataSet extends InUnicodeDataSet {
|
||||||
|
constructor(script) {
|
||||||
|
super(`Script/${script}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InBlockDataSet extends InUnicodeDataSet {
|
||||||
|
constructor(block) {
|
||||||
|
super(`Block/${block}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InGeneralCategoryDataSet extends InUnicodeDataSet {
|
||||||
|
constructor(general_category) {
|
||||||
|
super(`General_Category/${general_category}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InString {
|
||||||
|
constructor(s) {
|
||||||
|
this.s = s;
|
||||||
|
this.dataset = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
if (this.dataset) return;
|
||||||
|
this.dataset = new Set(this.s);
|
||||||
|
}
|
||||||
|
|
||||||
|
has(lch) {
|
||||||
|
return this.dataset.has(String.fromCodePoint(lch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Negation {
|
||||||
|
constructor(operand) {
|
||||||
|
this.operand = operand;
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
await this.operand.load();
|
||||||
|
}
|
||||||
|
has(lch) {
|
||||||
|
return !this.operand.has(lch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Conjunct {
|
||||||
|
constructor(operands) {
|
||||||
|
this.operands = operands;
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
for (const operand of this.operands) await operand.load();
|
||||||
|
}
|
||||||
|
has(lch) {
|
||||||
|
for (const operand of this.operands) if (!operand.has(lch)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Disjunct {
|
||||||
|
constructor(operands) {
|
||||||
|
this.operands = operands;
|
||||||
|
}
|
||||||
|
async load() {
|
||||||
|
for (const operand of this.operands) await operand.load();
|
||||||
|
}
|
||||||
|
has(lch) {
|
||||||
|
for (const operand of this.operands) if (operand.has(lch)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const Script = s => new InScriptDataSet(s);
|
||||||
|
const Block = b => new InBlockDataSet(b);
|
||||||
|
const GeneralCategory = gc => new InGeneralCategoryDataSet(gc);
|
||||||
|
const In = s => new InString(s);
|
||||||
|
|
||||||
|
const All = (...operands) => new Conjunct(operands);
|
||||||
|
const Either = (...operands) => new Disjunct(operands);
|
||||||
|
const Not = operand => new Negation(operand);
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const LatinBase = {
|
||||||
|
scriptTag: "latn",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(
|
||||||
|
All(
|
||||||
|
Script("Latin"),
|
||||||
|
Either(GeneralCategory("Uppercase_Letter"), GeneralCategory("Lowercase_Letter")),
|
||||||
|
Not(Block("Halfwidth_And_Fullwidth_Forms"))
|
||||||
|
),
|
||||||
|
Block("Currency_Symbols"),
|
||||||
|
All(Block("Letterlike_Symbols"), Not(In("℀℁⅍℠℡™℻")))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
const CyrillicBase = {
|
||||||
|
scriptTag: "cyrl",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: All(
|
||||||
|
Script("Cyrillic"),
|
||||||
|
Either(GeneralCategory("Uppercase_Letter"), GeneralCategory("Lowercase_Letter"))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
const GreekBase = {
|
||||||
|
scriptTag: "grek",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: All(
|
||||||
|
Script("Greek"),
|
||||||
|
Either(GeneralCategory("Uppercase_Letter"), GeneralCategory("Lowercase_Letter"))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const LatinSubscript = {
|
||||||
|
scriptTag: "latb",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(In("ₐₑₔₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ"))
|
||||||
|
};
|
||||||
|
const GreekSubscript = {
|
||||||
|
scriptTag: "grek",
|
||||||
|
featureTag: "subs",
|
||||||
|
filter: Either(In("ᵦᵧᵨᵩᵪ"))
|
||||||
|
};
|
||||||
|
const CyrillicSubscript = {
|
||||||
|
scriptTag: "cyrl",
|
||||||
|
featureTag: "subs",
|
||||||
|
filter: Either(In("𞁑𞁒𞁓𞁔𞁧𞁕𞁖𞁗𞁘𞁩𞁙𞁨𞁚𞁛𞁜𞁝𞁞𞁟𞁠𞁡𞁢𞁣𞁪𞁤𞁥𞁦"))
|
||||||
|
};
|
||||||
|
|
||||||
|
const LatinSuperscript = {
|
||||||
|
scriptTag: "latp",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(All(Script("Latin"), Either(GeneralCategory("Modifier_Letter"))))
|
||||||
|
};
|
||||||
|
const GreekSuperscript = {
|
||||||
|
scriptTag: "grek",
|
||||||
|
featureTag: "sups",
|
||||||
|
filter: All(Script("Greek"), Either(GeneralCategory("Modifier_Letter")))
|
||||||
|
};
|
||||||
|
const CyrillicSuperscript = {
|
||||||
|
scriptTag: "cyrl",
|
||||||
|
featureTag: "sups",
|
||||||
|
filter: All(Script("Cyrillic"), Either(GeneralCategory("Modifier_Letter")))
|
||||||
|
};
|
||||||
|
|
||||||
|
const DigitBase = {
|
||||||
|
scriptTag: "latn",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(In("0123456789"))
|
||||||
|
};
|
||||||
|
const DigitSubscript = {
|
||||||
|
scriptTag: "latb",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(In("₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎"))
|
||||||
|
};
|
||||||
|
const DigitSuperscript = {
|
||||||
|
scriptTag: "latb",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(In("⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾"))
|
||||||
|
};
|
||||||
|
|
||||||
|
const LatinPunctuation = {
|
||||||
|
scriptTag: "latn",
|
||||||
|
featureTag: "dflt",
|
||||||
|
filter: Either(
|
||||||
|
Block("Basic_Latin"),
|
||||||
|
Block("Latin_1_Supplement"),
|
||||||
|
Block("General_Punctuation"),
|
||||||
|
Block("Supplemental_Punctuation")
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const Targets = [
|
||||||
|
LatinBase,
|
||||||
|
LatinSubscript,
|
||||||
|
LatinSuperscript,
|
||||||
|
GreekBase,
|
||||||
|
GreekSubscript,
|
||||||
|
GreekSuperscript,
|
||||||
|
CyrillicBase,
|
||||||
|
CyrillicSubscript,
|
||||||
|
CyrillicSuperscript,
|
||||||
|
DigitBase,
|
||||||
|
DigitSubscript,
|
||||||
|
DigitSuperscript,
|
||||||
|
LatinPunctuation
|
||||||
|
];
|
|
@ -379,22 +379,25 @@ const DistUnhintedTTF = file.make(
|
||||||
|
|
||||||
const charMapDir = `${BUILD}/ttf/${gr}`;
|
const charMapDir = `${BUILD}/ttf/${gr}`;
|
||||||
const charMapPath = `${charMapDir}/${fn}.charmap.mpz`;
|
const charMapPath = `${charMapDir}/${fn}.charmap.mpz`;
|
||||||
const noGcTtfPath = `${charMapDir}/${fn}.no-gc.ttf`;
|
const ttfaControlsPath = `${charMapDir}/${fn}.ttfa.txt`;
|
||||||
|
|
||||||
if (fi.spacingDerive) {
|
if (fi.spacingDerive) {
|
||||||
// The font is a spacing variant, and is derivable form an existing
|
// The font is a spacing variant, and is derivable form an existing
|
||||||
// normally-spaced variant.
|
// normally-spaced variant.
|
||||||
|
|
||||||
|
const noGcTtfPath = `${charMapDir}/${fn}.no-gc.unhinted.ttf`;
|
||||||
|
|
||||||
const spD = fi.spacingDerive;
|
const spD = fi.spacingDerive;
|
||||||
const [deriveFrom] = await target.need(
|
const [deriveFrom] = await target.need(
|
||||||
DistUnhintedTTF(spD.prefix, spD.fileName),
|
DistUnhintedTTF(spD.prefix, spD.fileName),
|
||||||
de(charMapDir)
|
de(charMapDir)
|
||||||
);
|
);
|
||||||
|
|
||||||
echo.action(echo.hl.command(`Create TTF`), out.full);
|
echo.action(echo.hl.command(`Hint TTF`), out.full);
|
||||||
await silently.node(`font-src/derive-spacing.mjs`, {
|
await silently.node(`font-src/derive-spacing.mjs`, {
|
||||||
i: deriveFrom.full,
|
i: deriveFrom.full,
|
||||||
oNoGc: noGcTtfPath,
|
|
||||||
o: out.full,
|
o: out.full,
|
||||||
|
oNoGc: noGcTtfPath,
|
||||||
...fi
|
...fi
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -415,6 +418,7 @@ const DistUnhintedTTF = file.make(
|
||||||
const { cacheUpdated } = await silently.node("font-src/index.mjs", {
|
const { cacheUpdated } = await silently.node("font-src/index.mjs", {
|
||||||
o: out.full,
|
o: out.full,
|
||||||
oCharMap: charMapPath,
|
oCharMap: charMapPath,
|
||||||
|
oTtfaControls: ttfaControlsPath,
|
||||||
cacheFreshAgeKey: ageKey,
|
cacheFreshAgeKey: ageKey,
|
||||||
iCache: cachePath,
|
iCache: cachePath,
|
||||||
oCache: cacheDiffPath,
|
oCache: cacheDiffPath,
|
||||||
|
@ -437,13 +441,66 @@ const DistUnhintedTTF = file.make(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const BuildNoGcTtfImpl = file.make(
|
const BuildCM = file.make(
|
||||||
(gr, f) => `${BUILD}/ttf/${gr}/${f}.no-gc.ttf`,
|
(gr, f) => `${BUILD}/ttf/${gr}/${f}.charmap.mpz`,
|
||||||
async (target, output, gr, f) => {
|
async (target, output, gr, f) => {
|
||||||
await target.need(DistUnhintedTTF(gr, f));
|
await target.need(DistUnhintedTTF(gr, f));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const BuildTtfaControls = file.make(
|
||||||
|
(gr, f) => `${BUILD}/ttf/${gr}/${f}.ttfa.txt`,
|
||||||
|
async (target, output, gr, f) => {
|
||||||
|
await target.need(DistUnhintedTTF(gr, f));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const DistHintedTTF = file.make(
|
||||||
|
(gr, fn) => `${DIST}/${gr}/ttf/${fn}.ttf`,
|
||||||
|
async (target, out, gr, fn) => {
|
||||||
|
const [fi, hint] = await target.need(
|
||||||
|
FontInfoOf(fn),
|
||||||
|
CheckTtfAutoHintExists,
|
||||||
|
de`${out.dir}`
|
||||||
|
);
|
||||||
|
if (fi.spacingDerive) {
|
||||||
|
// The font is a spacing variant, and is derivable form an existing
|
||||||
|
// normally-spaced variant.
|
||||||
|
|
||||||
|
const spD = fi.spacingDerive;
|
||||||
|
const charMapDir = `${BUILD}/ttf/${gr}`;
|
||||||
|
const noGcTtfPath = `${charMapDir}/${fn}.no-gc.hinted.ttf`;
|
||||||
|
|
||||||
|
const [deriveFrom] = await target.need(
|
||||||
|
DistHintedTTF(spD.prefix, spD.fileName),
|
||||||
|
de(charMapDir)
|
||||||
|
);
|
||||||
|
|
||||||
|
echo.action(echo.hl.command(`Create TTF`), out.full);
|
||||||
|
await silently.node(`font-src/derive-spacing.mjs`, {
|
||||||
|
i: deriveFrom.full,
|
||||||
|
oNoGc: noGcTtfPath,
|
||||||
|
o: out.full,
|
||||||
|
...fi
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const [from, ttfaControls] = await target.need(
|
||||||
|
DistUnhintedTTF(gr, fn),
|
||||||
|
BuildTtfaControls(gr, fn)
|
||||||
|
);
|
||||||
|
echo.action(echo.hl.command(`Hint TTF`), out.full, echo.hl.operator("<-"), from.full);
|
||||||
|
await silently.run(hint, fi.hintParams, "-m", ttfaControls.full, from.full, out.full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const BuildNoGcTtfImpl = file.make(
|
||||||
|
(gr, f) => `${BUILD}/ttf/${gr}/${f}.no-gc.hinted.ttf`,
|
||||||
|
async (target, output, gr, f) => {
|
||||||
|
await target.need(DistHintedTTF(gr, f));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const BuildNoGcTtf = task.make(
|
const BuildNoGcTtf = task.make(
|
||||||
(gr, fn) => `BuildNoGcTtf::${gr}/${fn}`,
|
(gr, fn) => `BuildNoGcTtf::${gr}/${fn}`,
|
||||||
async (target, gr, fn) => {
|
async (target, gr, fn) => {
|
||||||
|
@ -452,29 +509,12 @@ const BuildNoGcTtf = task.make(
|
||||||
const [noGc] = await target.need(BuildNoGcTtfImpl(gr, fn));
|
const [noGc] = await target.need(BuildNoGcTtfImpl(gr, fn));
|
||||||
return noGc;
|
return noGc;
|
||||||
} else {
|
} else {
|
||||||
const [distUnhinted] = await target.need(DistUnhintedTTF(gr, fn));
|
const [distUnhinted] = await target.need(DistHintedTTF(gr, fn));
|
||||||
return distUnhinted;
|
return distUnhinted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const BuildCM = file.make(
|
|
||||||
(gr, f) => `${BUILD}/ttf/${gr}/${f}.charmap.mpz`,
|
|
||||||
async (target, output, gr, f) => {
|
|
||||||
await target.need(DistUnhintedTTF(gr, f));
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const DistHintedTTF = file.make(
|
|
||||||
(gr, fn) => `${DIST}/${gr}/ttf/${fn}.ttf`,
|
|
||||||
async (target, out, gr, fn) => {
|
|
||||||
const [fi, hint] = await target.need(FontInfoOf(fn), CheckTtfAutoHintExists);
|
|
||||||
const [from] = await target.need(DistUnhintedTTF(gr, fn), de`${out.dir}`);
|
|
||||||
echo.action(echo.hl.command(`Hint TTF`), out.full, echo.hl.operator("<-"), from.full);
|
|
||||||
await silently.run(hint, fi.hintParams, from.full, out.full);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function formatSuffix(fmt, unhinted) {
|
function formatSuffix(fmt, unhinted) {
|
||||||
return fmt + (unhinted ? "-unhinted" : "");
|
return fmt + (unhinted ? "-unhinted" : "");
|
||||||
}
|
}
|
||||||
|
@ -712,12 +752,9 @@ async function buildCompositeTtc(out, inputs) {
|
||||||
async function buildGlyphSharingTtc(target, parts, out) {
|
async function buildGlyphSharingTtc(target, parts, out) {
|
||||||
await target.need(de`${out.dir}`);
|
await target.need(de`${out.dir}`);
|
||||||
const [ttfInputs] = await target.need(parts.map(part => BuildNoGcTtf(part.dir, part.file)));
|
const [ttfInputs] = await target.need(parts.map(part => BuildNoGcTtf(part.dir, part.file)));
|
||||||
const tmpTtc = `${out.dir}/${out.name}.unhinted.ttc`;
|
|
||||||
const ttfInputPaths = ttfInputs.map(p => p.full);
|
const ttfInputPaths = ttfInputs.map(p => p.full);
|
||||||
echo.action(echo.hl.command(`Create TTC`), out.full, echo.hl.operator("<-"), ttfInputPaths);
|
echo.action(echo.hl.command(`Create TTC`), out.full, echo.hl.operator("<-"), ttfInputPaths);
|
||||||
await silently.run(TTCIZE, "-u", ["-o", tmpTtc], ttfInputPaths);
|
await silently.run(TTCIZE, "-u", ["-o", out.full], ttfInputPaths);
|
||||||
await silently.run("ttfautohint", tmpTtc, out.full);
|
|
||||||
await rm(tmpTtc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue