Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"scripts": {
"start": "node dist/app.js",
"build": "tsc -p tsconfig.json --pretty",
"test": "npm run build && node --test test/*.test.js",
"dev": "tsc -p tsconfig.json --pretty --watch",
"format": "prettier --check --ignore-path .gitignore .",
"format:fix": "prettier --write --ignore-path .gitignore ."
Expand Down
14 changes: 13 additions & 1 deletion src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { copyFluent } from "./extractors/fluent-generic.js"
import { copyTwemojiTo } from "./extractors/twemoji.js"
import { copyNotoTo } from "./extractors/noto.js"
import { copyMutantTo } from "./extractors/mutant.js"
import { ensureVariationSelectorAliases } from "./variation-selectors.js"

//#region Constants and Setup

Expand All @@ -23,7 +24,9 @@ if (!fluentExists) {
export const outDir = joinPath(cwd, "emoji")

if (exists(outDir)) await fs.rm(outDir, { recursive: true })
fs.mkdir(outDir)
await fs.mkdir(outDir)

const generatedPackDirs: string[] = []

//#endregion Constants and Setup

Expand All @@ -34,6 +37,7 @@ console.log("pack-twemoji: Generating pack twemoji...")

const twemojiOutDir = joinPath(outDir, "twemoji")
await copyTwemojiTo(twemojiOutDir)
generatedPackDirs.push(twemojiOutDir)

console.timeEnd("pack-twemoji")

Expand All @@ -59,6 +63,7 @@ for (const fluentType of fluentTypes) {
`pack-${flavorId}: Generating twemoji placeholders for missing files...`
)
await copyTwemojiTo(packOutDir)
generatedPackDirs.push(packOutDir)

console.timeEnd(`pack-${flavorId}`)
}
Expand All @@ -75,6 +80,7 @@ await copyNotoTo(notoOutDir)

console.log(`pack-noto: Generating twemoji placeholders for missing files...`)
await copyTwemojiTo(notoOutDir)
generatedPackDirs.push(notoOutDir)

console.timeEnd("pack-noto")

Expand All @@ -90,7 +96,13 @@ await copyMutantTo(mutantOutDir)

console.log(`pack-mutant: Generating twemoji placeholders for missing files...`)
await copyTwemojiTo(mutantOutDir)
generatedPackDirs.push(mutantOutDir)

console.timeEnd("pack-mutant")

//#endregion Mutant Remix

console.time("variation-selector-aliases")
console.log("Generating variation-selector aliases...")
await ensureVariationSelectorAliases(generatedPackDirs)
console.timeEnd("variation-selector-aliases")
47 changes: 35 additions & 12 deletions src/extractors/fluent-generic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { packsDir } from "../app.js"
import {
FLUENT_TONE_DIRS,
FLUENT_TONE_DIR_TO_CODEPOINT,
VARIANT_SELECTOR_EMOJI,
} from "../constants.js"
import { MSFTMetadataFile } from "../types.js"
import { withAndWithoutVariationSelectors } from "../variation-selectors.js"

// We embed the 3D PNGs in an SVG so that we can use the same URLs for every pack
const embedPngInSvg = (
Expand Down Expand Up @@ -49,13 +49,24 @@ const copyWithSkinTones = async (
)[0]

const inPath = joinPath(toneDir, fileName)
const outPath = joinPath(copyTo, codepointOfEmoji.join("-") + ".svg")
const outputFilenames = withAndWithoutVariationSelectors(
codepointOfEmoji.join("-") + ".svg"
)

if (fileName.endsWith(".png")) {
const pngData = await readFile(inPath)
await writeFile(outPath, embedPngInSvg(pngData))
const svg = embedPngInSvg(pngData)
await Promise.all(
outputFilenames.map((filename) =>
writeFile(joinPath(copyTo, filename), svg)
)
)
} else {
await copyFile(inPath, outPath)
await Promise.all(
outputFilenames.map((filename) =>
copyFile(inPath, joinPath(copyTo, filename))
)
)
}
}
}
Expand All @@ -80,17 +91,24 @@ const copySingle = async (
)[0]

const inPath = joinPath(assetDir, fileName)
const outPath = joinPath(
copyTo,
codepoints.filter((x) => x !== VARIANT_SELECTOR_EMOJI).join("-") +
".svg"
const outputFilenames = withAndWithoutVariationSelectors(
codepoints.join("-") + ".svg"
)

if (fileName.endsWith(".png")) {
const pngData = await readFile(inPath)
await writeFile(outPath, embedPngInSvg(pngData))
const svg = embedPngInSvg(pngData)
await Promise.all(
outputFilenames.map((filename) =>
writeFile(joinPath(copyTo, filename), svg)
)
)
} else {
await copyFile(inPath, outPath)
await Promise.all(
outputFilenames.map((filename) =>
copyFile(inPath, joinPath(copyTo, filename))
)
)
}
}

Expand All @@ -114,9 +132,14 @@ export const copyFluent = async (flavorName: string, toPath: string) => {
const hasSkinTones = emojiDirContents.includes("Medium-Dark") // name unlikely to be reused

if (hasSkinTones) {
copyWithSkinTones(toPath, flavorName, emojiPath, codepoints)
await copyWithSkinTones(
toPath,
flavorName,
emojiPath,
codepoints
)
} else {
copySingle(toPath, flavorName, emojiPath, codepoints)
await copySingle(toPath, flavorName, emojiPath, codepoints)
}
}
}
15 changes: 4 additions & 11 deletions src/extractors/mutant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from "fs"
import { copyFile, mkdir, readdir } from "fs/promises"
import { join as joinPath } from "path"
import { packsDir } from "../app.js"
import { VARIANT_SELECTOR_EMOJI } from "../constants.js"
import { withAndWithoutVariationSelectors } from "../variation-selectors.js"

export const copyMutantTo = async (outDir: string) => {
const mutantDir = joinPath(packsDir, "mutant-remix", "emoji")
Expand All @@ -14,15 +14,8 @@ export const copyMutantTo = async (outDir: string) => {
for (const emoji of mutantSvgs) {
const inPath = joinPath(mutantDir, emoji)

const codepoints = emoji.split("-")
const normalizedFilename =
codepoints.length === 2
? codepoints
.filter((x) => x !== VARIANT_SELECTOR_EMOJI)
.join("-")
: codepoints.join("-")
const outPath = joinPath(outDir, normalizedFilename)

await copyFile(inPath, outPath)
for (const filename of withAndWithoutVariationSelectors(emoji)) {
await copyFile(inPath, joinPath(outDir, filename))
}
}
}
13 changes: 11 additions & 2 deletions src/extractors/twemoji.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { copyFile, mkdir, readdir } from "fs/promises"
import { existsSync } from "fs"
import { join as joinPath } from "path"
import { packsDir } from "../app.js"
import { withAndWithoutVariationSelectors } from "../variation-selectors.js"

export const getAllExistingTwemoji = async () => {
let out: string[] = []
Expand All @@ -26,7 +27,15 @@ export const copyTwemojiTo = async (outDir: string) => {

for (const emoji of twemojiSvgs) {
const inPath = joinPath(twemojiDir, emoji)
const outPath = joinPath(outDir, emoji)
if (!existsSync(outPath)) await copyFile(inPath, outPath)
const outputFilenames = withAndWithoutVariationSelectors(emoji)
const existingPackAsset = outputFilenames
.map((filename) => joinPath(outDir, filename))
.find((path) => existsSync(path))
const source = existingPackAsset ?? inPath

for (const filename of outputFilenames) {
const outPath = joinPath(outDir, filename)
if (!existsSync(outPath)) await copyFile(source, outPath)
}
}
}
66 changes: 66 additions & 0 deletions src/variation-selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { existsSync } from "fs"
import { copyFile, readdir } from "fs/promises"
import { basename, extname, join as joinPath } from "path"
import { VARIANT_SELECTOR_EMOJI } from "./constants.js"

export const withAndWithoutVariationSelectors = (filename: string) => {
const extension = extname(filename)
const codepoints = basename(filename, extension).split("-")

if (!codepoints.includes(VARIANT_SELECTOR_EMOJI)) return [filename]

const withoutVariationSelectors =
codepoints
.filter((codepoint) => codepoint !== VARIANT_SELECTOR_EMOJI)
.join("-") + extension

return [filename, withoutVariationSelectors]
}

export const ensureVariationSelectorAliases = async (packDirs: string[]) => {
const filenamesByPack = await Promise.all(
packDirs.map((dir) => readdir(dir)),
)
const aliases = new Map<string, string>()

for (const filenames of filenamesByPack) {
for (const filename of filenames) {
const [withVariationSelectors, withoutVariationSelectors] =
withAndWithoutVariationSelectors(filename)

if (withoutVariationSelectors !== undefined) {
aliases.set(withVariationSelectors, withoutVariationSelectors)
}
}
}

for (let index = 0; index < packDirs.length; index++) {
const packDir = packDirs[index]
const filenames = new Set(filenamesByPack[index])

for (const [
withVariationSelectors,
withoutVariationSelectors,
] of aliases) {
const hasWith = filenames.has(withVariationSelectors)
const hasWithout = filenames.has(withoutVariationSelectors)

if (hasWith === hasWithout) continue

const source = hasWith
? withVariationSelectors
: withoutVariationSelectors
const destination = hasWith
? withoutVariationSelectors
: withVariationSelectors

if (!existsSync(joinPath(packDir, destination))) {
await copyFile(
joinPath(packDir, source),
joinPath(packDir, destination),
)
filenames.add(destination)
}
}
}
}
Loading