import fs from "fs"; import path from "path"; import * as url from "url"; import { parseLigationData } from "../export-data/ligation-data.mjs"; import { getCharMapAndSupportedLanguageList } from "../export-data/supported-languages.mjs"; import { parseVariantsData } from "../export-data/variants-data.mjs"; const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); export default main; async function main(argv) { let readme = await fs.promises.readFile(argv.mdFilePath, "utf-8"); const dirs = { images: path.posix.relative(path.dirname(argv.mdFilePath), "images") }; readme = (await processSsOt(dirs)).apply(readme); readme = (await processCherryPickingStyles(dirs)).apply(readme); readme = (await processSsStyles()).apply(readme); readme = (await processCvOt(dirs)).apply(readme); readme = (await processLigSetCherryPicking()).apply(readme); readme = (await processLigSetPreDef()).apply(readme); readme = (await processLigSetOt(dirs, 1, g => !isLanguageSpecificLigTag(g.tag))).apply(readme); readme = (await processLigSetOt(dirs, 2, g => isLanguageSpecificLigTag(g.tag))).apply(readme); readme = (await processLangList(argv)).apply(readme); readme = (await processPrivateBuildPlans()).apply(readme); readme = (await processCvInfluences(argv)).apply(readme); await fs.promises.writeFile(argv.mdFilePath, readme); } ///////////////////////////////////////////////////////////////////////////////////////////////////// async function processSsOt(dirs) { const variantsData = await parseVariantsData(); const md = new MdCol("Section-OT-Stylistic-Sets"); md.log(``); for (const ss of variantsData.composites) { if (!ss.rank) continue; { md.log(``); md.log(``); md.log(``); } { md.log( ``, ``, ``, `` ); } } md.log(`
${ss.tag} — ${ss.description}
${ImgX(`${dirs.images}/ss-u-${ss.tag}-${ss.rank}`)}${ImgX(`${dirs.images}/ss-i-${ss.tag}-${ss.rank}`)}
`); return md; } async function processCvOt(dirs) { const variantsData = await parseVariantsData(); const md = new MdCol("Section-OT-Character-Variants"); const TableColumns = 12; md.log(``); for (const cv of variantsData.primes) { if (!cv.tag) continue; let effVariants = []; for (const cvv of cv.variants) if (cvv.rank) effVariants.push(cvv); const entryWidth = sampleImageCountEmOfCv(cv); const imgWidth = 32 * entryWidth; const entriesPerRow = Math.floor(TableColumns / entryWidth); const rowsNeeded = Math.ceil(effVariants.length / entriesPerRow); const itemColSpanHtml = entryWidth > 1 ? ` colspan="${entryWidth}"` : ``; for (let rid = 0; rid < rowsNeeded; rid++) { const entriesInThisRow = Math.min( entriesPerRow, effVariants.length - rid * entriesPerRow ); const tailBlankColumnsCount = TableColumns - entryWidth * entriesInThisRow; // Image row md.log(``); if (rid === 0) md.log(``); for (let cid = 0; cid < entriesPerRow; cid++) { const iCvv = rid * entriesPerRow + cid; if (iCvv >= effVariants.length) continue; const cvv = effVariants[iCvv]; const imageID = `${dirs.images}/cv-${cv.key}-${cvv.key}`; const image = ImgX(imageID, imgWidth); md.log(`${image}`); } if (tailBlankColumnsCount > 0) md.log(``); md.log(``); // CV ID row md.log(``); for (let cid = 0; cid < entriesPerRow; cid++) { const iCvv = rid * entriesPerRow + cid; if (iCvv >= effVariants.length) continue; const cvv = effVariants[iCvv]; md.log(`${cvv.rank}`); } if (tailBlankColumnsCount > 0) md.log(``); md.log(``); } } md.log(`
${cv.tag}
`); return md; } async function processSsStyles() { const variantsData = await parseVariantsData(); const md = new MdCol("Section-Stylistic-Sets"); const headerPath = path.resolve(__dirname, "fragments/description-stylistic-sets.md"); md.log(await fs.promises.readFile(headerPath, "utf-8")); for (const gr of variantsData.composites) { if (!gr.rank) continue; md.log(` - \`${gr.tag}\`: Set character variant to “${gr.description}”.`); } return md; } async function processCherryPickingStyles(dirs) { const variantsData = await parseVariantsData(); const md = new MdCol("Section-Cherry-Picking-Styles"); const headerPath = path.resolve(__dirname, "fragments/description-cheery-picking-styles.md"); md.log(await fs.promises.readFile(headerPath, "utf-8")); for (const cv of [...variantsData.specials, ...variantsData.primes]) { if (!cv.tag && !cv.isSpecial) continue; const sampleText = cv.descSampleText .map(c => (c === "`" ? "`` ` ``" : `\`${c}\``)) .join(", "); const explainText = cv.samplerExplain ? ` (${cv.samplerExplain})` : ``; const info = { introMD: cv.description || `Styles for ${sampleText + explainText}`, sampleImageCountEm: sampleImageCountEmOfCv(cv), alternatives: [] }; const defaults = figureOutDefaults(variantsData, cv); for (const cvv of cv.variants) { if (!cvv.rank && !cv.isSpecial) continue; if (cv.tag) { info.alternatives.push({ imageId: `${cv.key}-${cvv.key}`, selectors: [`${cv.key} = '${cvv.key}'`, `${cv.tag} = ${cvv.rank}`], description: formatDescription(cvv.description) + formatDefaults(cvv.key, defaults) }); } else { info.alternatives.push({ imageId: `${cv.key}-${cvv.key}`, selectors: [`${cv.key} = '${cvv.key}'`], description: formatDescription(cvv.description) + formatDefaults(cvv.key, defaults) }); } } formatCv(md, dirs, info); } return md; } function sampleImageCountEmOfCv(cv) { return cv.hotChars.length * (cv.slopeDependent ? 2 : 1); } function formatCv(md, dirs, info) { md.log(` - ${info.introMD}:`); const imgWidth = 32 * info.sampleImageCountEm; let sTable = " "; for (const alt of info.alternatives) { const imageId = `${dirs.images}/cv-${alt.imageId}`; const image = ImgX(imageId, imgWidth); const selectorText = alt.selectors.map(x => `${x}`).join(", "); sTable += `` + ``; sTable += ``; } sTable += "
${image}${selectorText}
${alt.description}
"; md.log(sTable); } function formatDescription(s) { return s .replace(/`` (\S+?) ``/g, ($0, $1) => `${escapeHtml($1)}`) .replace(/`([^`]+?)`/g, ($0, $1) => `${escapeHtml($1)}`); } function escapeHtml(s) { return s.replace(/&/g, "&").replace(//g, ">"); } async function processPrivateBuildPlans() { const md = new MdCol("Section-Private-Build-Plan-Sample"); const tomlPath = path.resolve(__dirname, "../../private-build-plans.sample.toml"); const toml = await fs.promises.readFile(tomlPath, "utf-8"); md.log("```toml\n" + toml + "```"); return md; } function formatDefaults(selector, defaults) { let dcs = [], mask = 0; for (const dc of defaults) { if (dc.result !== selector) continue; dcs.push(dc); mask |= dc.mask; } if (!dcs.length) return ""; if (mask === 0xf) return ` (default)`; if (mask === 0x5) return ` (default for Upright)`; if (mask === 0xa) return ` (default for Italic)`; if (mask === 0x3) return ` (default for Sans)`; if (mask === 0xc) return ` (default for Slab)`; return ` (default for ${dcs.map(x => x.desc).join(", ")})`; } function figureOutDefaults(variantsData, gr) { const defaultConfigs = [ { desc: "Sans Upright", mask: 1, result: null, composition: { ...variantsData.defaults.sans.upright } }, { desc: "Sans Italic", mask: 2, result: null, composition: { ...variantsData.defaults.sans.italic } }, { desc: "Slab Upright", mask: 4, result: null, composition: { ...variantsData.defaults.slab.upright } }, { desc: "Slab Italic", mask: 8, result: null, composition: { ...variantsData.defaults.slab.italic } } ]; for (const variant of gr.variants) { for (const dc of defaultConfigs) { if (variant.key === dc.composition[gr.key]) dc.result = variant.key; } } return defaultConfigs; } ///////////////////////////////////////////////////////////////////////////////////////////////////// async function processLigSetCherryPicking() { const ligData = await parseLigationData(); const md = new MdCol("Section-Cherry-Picking-Ligation-Sets"); const headerPath = path.resolve( __dirname, "fragments/description-cherry-picking-ligation-sets.md" ); md.log(await fs.promises.readFile(headerPath, "utf-8")); for (const gr in ligData.cherry) { md.log(` - \`${gr}\`: ${ligData.cherry[gr].desc}.`); } return md; } async function processLigSetPreDef() { const ligData = await parseLigationData(); const md = new MdCol("Section-Predefined-Ligation-Sets"); const headerPath = path.resolve(__dirname, "fragments/description-predefined-ligation-sets.md"); md.log(await fs.promises.readFile(headerPath, "utf-8")); for (const gr in ligData.rawSets) { const readmeDesc = ligData.rawSets[gr].readmeDesc || `Default ligation set would be assigned to ${ligData.rawSets[gr].desc}`; md.log(` - \`${gr}\`: ${readmeDesc}.`); } return md; } async function processLigSetOt(dirs, index, fn) { const ligData = await parseLigationData(); const md = new MdCol(`Section-OT-Ligation-Tags-${index}`); md.log(``); for (const ls of ligData.sets) { if (!fn(ls)) continue; { md.log(``); if (ls.tagName) md.log(``); else md.log(``); md.log(``); md.log(``); } { const imageId = `${dirs.images}/ligset-${ls.tag}-${ls.rank}`; md.log(``); md.log(``); md.log(``); } } md.log(`
${ls.tagName.map(x => `${x}`).join("; ")}${ls.tag} off${ls.desc}
${ImgX(imageId)}
`); return md; } function isLanguageSpecificLigTag(tag) { return tag !== "calt" && tag !== "dlig"; } ///////////////////////////////////////////////////////////////////////////////////////////////////// async function processCvInfluences(argv) { const cl = await getCharMapAndSupportedLanguageList( argv.charMapPath, argv.charMapItalicPath, argv.charMapObliquePath ); let m = { upright: new Map(), italic: new Map() }; for (const block of cl.unicodeCoverage) { for (const ch of block.characters) { if (!ch.inFont) continue; addToCvInfluenceMap(cl.featureSeries, m.upright, ch.lch, ch.cvFeatureSetsUpright); addToCvInfluenceMap(cl.featureSeries, m.italic, ch.lch, ch.cvFeatureSetsItalic); } } const md = new MdCol("Section-CV-Influences"); md.log(`### Upright CV influences`); md.log(``); logCvInfluenceMap(md, m.upright); md.log(``); md.log(`### Italic CV influences`); md.log(``); logCvInfluenceMap(md, m.italic); md.log(``); return md; } function addToCvInfluenceMap(featureSeries, m, lch, ids) { if (!ids || !ids.length) return; for (const id of ids) { let fs = featureSeries[id]; if (!fs) continue; let s = m.get(fs.name); if (!s) { s = new Set(); m.set(fs.name, s); } s.add(lch); } } function logCvInfluenceMap(md, m) { let a = Array.from(m).sort((a, b) => a[0].toUpperCase().localeCompare(b[0].toUpperCase())); for (const [tag, chars] of a) { md.log(`- \`${tag}\`:`); md.log(``); md.log(` ` + Array.from(chars).map(formatLch).join(", ")); md.log(``); } } function formatLch(lch) { return mdEscape(lch) + " (`U+" + lch.toString(16).padStart(4, "0").toUpperCase() + "`)"; } function mdEscape(lch) { let ch = String.fromCodePoint(lch); if (ch === "\\") return "\\\\"; if (ch === "`") return "\\`"; if (ch === "*") return "\\*"; if (ch === "_") return "\\_"; if (ch === "{") return "\\{"; if (ch === "}") return "\\}"; if (ch === "[") return "\\["; if (ch === "]") return "\\]"; if (ch === "(") return "\\("; if (ch === ")") return "\\)"; if (ch === "#") return "\\#"; if (ch === "+") return "\\+"; if (ch === "-") return "\\-"; if (ch === ".") return "\\."; if (ch === "!") return "\\!"; return ch; } ///////////////////////////////////////////////////////////////////////////////////////////////////// async function processLangList(argv) { const cl = await getCharMapAndSupportedLanguageList( argv.charMapPath, argv.charMapItalicPath, argv.charMapObliquePath ); const md = new MdCol("Section-Language-List"); md.log(`${cl.languages.length} Supported Languages: \n`); md.log(cl.languages.join(", ")); return md; } ///////////////////////////////////////////////////////////////////////////////////////////////////// class MdCol { constructor(sectionName) { this.data = ""; this.sectionName = sectionName; this.matchRegex = new RegExp( `^([ \\t]*)\\n[\\s\\S]*?\\n`, `m` ); } log(...s) { this.data += s.join("") + "\n"; } apply(s) { return s.replace(this.matchRegex, (m, $1) => { return ( `\n` + `\n\n` + this.data + `\n\n` ).replace(/^/gm, $1); }); } } function ImgX(path, w) { const widthProp = w ? ` width=${w}` : ``; return ( `` + `` ); }