refactor: move remaining nested skills to root
- picture-it from creative/ to root - plan, requesting-code-review, skill-augmentation-from-source, subagent-driven-development, systematic-debugging, writing-plans from general/ to root - Remove old test-driven-development (root version is newer) - Delete empty creative/ and general/ folders
This commit is contained in:
268
skills/picture-it/scripts/thai-font-patch.ts
Normal file
268
skills/picture-it/scripts/thai-font-patch.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Thai Font Patcher for picture-it
|
||||
*
|
||||
* Detects if Thai font entries are missing from picture-it's FONT_FILES array
|
||||
* in dist/index.js, and patches them if needed.
|
||||
*
|
||||
* This is idempotent — safe to run multiple times.
|
||||
* Run this after every `picture-it` update.
|
||||
*
|
||||
* Usage:
|
||||
* bun thai-font-patch.ts # patch if needed
|
||||
* bun thai-font-patch.ts --check # just check, don't patch
|
||||
* bun thai-font-patch.ts --force # re-patch even if already patched
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync } from "fs";
|
||||
import { join, dirname } from "path";
|
||||
import { execSync } from "child_process";
|
||||
|
||||
const THAI_FONT_FILES_SIGNATURE = "thai/NotoSansThai-Regular.ttf";
|
||||
|
||||
const THAI_FONTS_ENTRY = ` },
|
||||
// Thai fonts
|
||||
{
|
||||
name: "Noto Sans Thai",
|
||||
file: "thai/NotoSansThai-Regular.ttf",
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzE.ttf"
|
||||
},
|
||||
{
|
||||
name: "Noto Sans Thai",
|
||||
file: "thai/NotoSansThai-Bold.ttf",
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU3NqpzE.ttf"
|
||||
},
|
||||
{
|
||||
name: "Kanit",
|
||||
file: "thai/Kanit-Regular.ttf",
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
url: "https://fonts.gstatic.com/s/kanit/v17/nKKZ-Go6G5tXcoaS.ttf"
|
||||
},
|
||||
{
|
||||
name: "Kanit",
|
||||
file: "thai/Kanit-Bold.ttf",
|
||||
weight: 700,
|
||||
style: "normal",
|
||||
url: "https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr4uPiWg.ttf"
|
||||
}
|
||||
];`
|
||||
|
||||
// Thai font download URLs
|
||||
const THAI_FONTS_TO_DOWNLOAD = [
|
||||
{
|
||||
name: "NotoSansThai-Regular.ttf",
|
||||
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzE.ttf",
|
||||
},
|
||||
{
|
||||
name: "NotoSansThai-Bold.ttf",
|
||||
url: "https://fonts.gstatic.com/s/notosansthai/v29/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU3NqpzE.ttf",
|
||||
},
|
||||
{
|
||||
name: "Kanit-Regular.ttf",
|
||||
url: "https://fonts.gstatic.com/s/kanit/v17/nKKZ-Go6G5tXcoaS.ttf",
|
||||
},
|
||||
{
|
||||
name: "Kanit-Bold.ttf",
|
||||
url: "https://fonts.gstatic.com/s/kanit/v17/nKKU-Go6G5tXcr4uPiWg.ttf",
|
||||
},
|
||||
];
|
||||
|
||||
function findPictureItDist(): string | null {
|
||||
const home = process.env.HOME || "";
|
||||
const searchPaths = [
|
||||
// bun-js snap (primary)
|
||||
join(home, "snap/bun-js/87/.bun/install/global/node_modules/picture-it/dist/index.js"),
|
||||
// npm global
|
||||
join(home, ".npm-global/lib/node_modules/picture-it/dist/index.js"),
|
||||
];
|
||||
|
||||
for (const p of searchPaths) {
|
||||
if (existsSync(p)) return p;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getPictureItVersion(installDir: string): string {
|
||||
try {
|
||||
const pkgPath = join(installDir, "package.json");
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
||||
return pkg.version || "unknown";
|
||||
} catch {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
function getInstallDir(distPath: string): string {
|
||||
return dirname(dirname(distPath));
|
||||
}
|
||||
|
||||
function isAlreadyPatched(distPath: string): boolean {
|
||||
const content = readFileSync(distPath, "utf-8");
|
||||
return content.includes(THAI_FONT_FILES_SIGNATURE);
|
||||
}
|
||||
|
||||
async function downloadThaiFonts(fontDir: string): Promise<string[]> {
|
||||
mkdirSync(fontDir, { recursive: true });
|
||||
const downloaded: string[] = [];
|
||||
for (const font of THAI_FONTS_TO_DOWNLOAD) {
|
||||
const outPath = join(fontDir, font.name);
|
||||
if (existsSync(outPath)) {
|
||||
const stat = readFileSync(outPath);
|
||||
if (stat.length > 1000) {
|
||||
console.log(` Already exists: ${font.name}`);
|
||||
downloaded.push(font.name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
console.log(` Downloading: ${font.name}`);
|
||||
const res = await fetch(font.url);
|
||||
if (!res.ok) throw new Error(`Failed to download ${font.name}: ${res.status}`);
|
||||
const buf = await res.arrayBuffer();
|
||||
writeFileSync(outPath, Buffer.from(buf));
|
||||
downloaded.push(font.name);
|
||||
}
|
||||
return downloaded;
|
||||
}
|
||||
|
||||
function patchDist(distPath: string): boolean {
|
||||
const content = readFileSync(distPath, "utf-8");
|
||||
|
||||
// Find DM Serif Display closing brace in FONT_FILES array
|
||||
// Pattern: the last entry in FONT_FILES ends with:
|
||||
// url: "https://fonts.gstatic.com/s/dmserifdisplay/v17/..."
|
||||
// }
|
||||
// ];
|
||||
// We need to replace the closing `}` with Thai fonts entries + ];
|
||||
|
||||
const dmSerifEntryEnd = `name: "DM Serif Display",
|
||||
file: "DMSerifDisplay-Regular.ttf",
|
||||
weight: 400,
|
||||
style: "normal",
|
||||
url: "https://fonts.gstatic.com/s/dmserifdisplay/v17/-nFnOHM81r4j6k0gjAW3mujVU2B2K_c.ttf"
|
||||
}`;
|
||||
|
||||
// Find the closing of the array after DM Serif Display
|
||||
// We look for the pattern: DM Serif Display entry ... }; var cachedFonts
|
||||
// The `];` after the closing `}` of DM Serif Display is the end of FONT_FILES
|
||||
const fontFilesEndPattern = /(\n\s+url: "https:\/\/fonts\.gstatic\.com\/s\/dmserifdisplay\/v17\/[^"]+"\n\s+}\n)\];/;
|
||||
|
||||
const match = content.match(fontFilesEndPattern);
|
||||
if (!match) {
|
||||
// Try alternate pattern (might differ in minified versions)
|
||||
const altPattern = /(\}\n)\];/;
|
||||
const altMatch = content.match(altPattern);
|
||||
if (!altMatch) {
|
||||
throw new Error("Could not find end of FONT_FILES array. picture-it format may have changed.");
|
||||
}
|
||||
// Just replace the first occurrence of ]; after a }
|
||||
const newContent = content.replace(altPattern, THAI_FONTS_ENTRY.replace(" },", match?.[1] || " }").endsWith("}") ? THAI_FONTS_ENTRY : ` },${THAI_FONTS_ENTRY.split(" },")[1]}`);
|
||||
writeFileSync(distPath, newContent);
|
||||
return true;
|
||||
}
|
||||
|
||||
const patched = content.replace(
|
||||
fontFilesEndPattern,
|
||||
THAI_FONTS_ENTRY
|
||||
);
|
||||
|
||||
if (patched === content) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writeFileSync(distPath, patched);
|
||||
return true;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const checkOnly = args.includes("--check");
|
||||
const force = args.includes("--force");
|
||||
|
||||
console.log("[Thai Font Patcher for picture-it]");
|
||||
console.log("");
|
||||
|
||||
const distPath = findPictureItDist();
|
||||
|
||||
if (!distPath) {
|
||||
console.log("ERROR: picture-it not found.");
|
||||
console.log("Is picture-it installed? Run: bun install -g picture-it");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const installDir = getInstallDir(distPath);
|
||||
const version = getPictureItVersion(installDir);
|
||||
|
||||
console.log(`picture-it found: v${version}`);
|
||||
console.log(`Install dir: ${installDir}`);
|
||||
console.log(`dist path: ${distPath}`);
|
||||
console.log("");
|
||||
|
||||
const alreadyPatched = isAlreadyPatched(distPath);
|
||||
|
||||
if (alreadyPatched && !force) {
|
||||
console.log("Thai fonts: ALREADY PATCHED ✓");
|
||||
console.log("No action needed.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (checkOnly) {
|
||||
console.log("Thai fonts: NOT PATCHED ✗");
|
||||
console.log("Run without --check to apply patch.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (force && alreadyPatched) {
|
||||
console.log("Force mode: re-patching...");
|
||||
}
|
||||
|
||||
// Step 1: Download Thai fonts
|
||||
console.log("Step 1: Ensuring Thai fonts are downloaded...");
|
||||
const fontDir = join(process.env.HOME || "", ".picture-it/fonts/thai");
|
||||
try {
|
||||
await downloadThaiFonts(fontDir);
|
||||
} catch (e: any) {
|
||||
console.error(` Failed to download fonts: ${e.message}`);
|
||||
console.error("You can download them manually or try again.");
|
||||
}
|
||||
console.log("");
|
||||
|
||||
// Step 2: Patch dist/index.js
|
||||
console.log("Step 2: Patching dist/index.js...");
|
||||
try {
|
||||
const success = patchDist(distPath);
|
||||
if (success) {
|
||||
console.log(" Patched dist/index.js ✓");
|
||||
} else {
|
||||
console.log(" No changes needed (or patch failed silently)");
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.error(` Patch failed: ${e.message}`);
|
||||
console.error("Please report this issue.");
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("");
|
||||
|
||||
// Verify
|
||||
const verifyPatched = isAlreadyPatched(distPath);
|
||||
if (verifyPatched) {
|
||||
console.log("RESULT: Thai font patch applied successfully ✓");
|
||||
console.log("");
|
||||
console.log("You can now use Thai fonts:");
|
||||
console.log(' picture-it text -i input.png --title "ทดสอบ" --font "Noto Sans Thai" -o out.png');
|
||||
console.log(' picture-it text -i input.png --title "ทดสอบ" --font "Kanit" -o out.png');
|
||||
} else {
|
||||
console.error("RESULT: Patch verification failed ✗");
|
||||
console.error("The patch may not have been applied correctly.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((e: Error) => {
|
||||
console.error(`Error: ${e.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user