- 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
269 lines
8.2 KiB
TypeScript
269 lines
8.2 KiB
TypeScript
#!/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);
|
|
});
|