classify-artboards.mjs 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  1. // 输出画板分类所需信息:顶层图层、文本样本、图片填充、字体与色板汇总
  2. import { readFileSync, writeFileSync, readdirSync } from 'node:fs'
  3. import { join } from 'node:path'
  4. const dir = '.scratch/lanhu-cockpit-asset-replacement/research/raw'
  5. const files = readdirSync(dir).filter((f) => /^desktop-\d+\.json$/.test(f)).sort()
  6. function hex(c) {
  7. const h = (v) => Math.round(v * 255).toString(16).padStart(2, '0')
  8. return `#${h(c.r)}${h(c.g)}${h(c.b)}`
  9. }
  10. const detail = []
  11. const globalColors = new Map()
  12. const globalFonts = new Map()
  13. let totalAssets = 0
  14. let totalImageFills = 0
  15. let flagImage = 0
  16. let flagDds = 0
  17. for (const file of files) {
  18. const j = JSON.parse(readFileSync(join(dir, file), 'utf8'))
  19. const ab = j.artboard
  20. const texts = []
  21. const images = []
  22. const fonts = new Map()
  23. function walk(node) {
  24. if (node.hasExportImage) flagImage += 1
  25. if (node.hasExportDDSImage) flagDds += 1
  26. if (node.type === 'textLayer' && node.text?.style?.content && texts.length < 40) {
  27. texts.push(String(node.text.style.content).slice(0, 24))
  28. }
  29. if (node.type === 'textLayer' && node.text?.style?.font?.name) {
  30. const fam = node.text.style.font.name
  31. fonts.set(fam, (fonts.get(fam) || 0) + 1)
  32. globalFonts.set(fam, (globalFonts.get(fam) || 0) + 1)
  33. }
  34. for (const f of node.style?.fills || []) {
  35. if (f.type === 'color' && f.color) {
  36. const c = hex(f.color)
  37. globalColors.set(c, (globalColors.get(c) || 0) + 1)
  38. }
  39. const url = f.ddsImage?.imageUrl || f.image?.imageUrl || f.imageUrl
  40. if (f.type === 'image' && url) {
  41. images.push({ name: node.name, path: '', w: Math.round(node.frame?.width ?? 0), h: Math.round(node.frame?.height ?? 0), url })
  42. }
  43. }
  44. for (const ch of node.layers || []) walk(ch)
  45. }
  46. for (const l of ab.layers || []) walk(l)
  47. totalAssets += (j.assets || []).length
  48. totalImageFills += images.length
  49. detail.push({
  50. file,
  51. artboard: ab.name,
  52. frame: { w: ab.frame.width, h: ab.frame.height },
  53. topLayers: (ab.layers || []).map((l) => `${l.type}:${l.name}`),
  54. texts,
  55. images,
  56. fonts: [...fonts.entries()].sort((a, b) => b[1] - a[1]),
  57. assetFiles: j.assets || [],
  58. })
  59. }
  60. writeFileSync(join(dir, 'inventory-detail.json'), JSON.stringify(detail, null, 1))
  61. for (const d of detail) {
  62. console.log(`\n=== ${d.artboard} (${d.frame.w}x${d.frame.h})`)
  63. console.log(' top:', d.topLayers.join(', ').slice(0, 300))
  64. console.log(' texts:', d.texts.slice(0, 26).join(' | ').slice(0, 320))
  65. console.log(' images:', d.images.map((i) => `${i.name}(${i.w}x${i.h})`).join(', ').slice(0, 260))
  66. console.log(' fonts:', d.fonts.map(([f, c]) => `${f}×${c}`).join(', ').slice(0, 260))
  67. }
  68. console.log('\n=== GLOBAL')
  69. console.log('assets files:', totalAssets, '| image fills:', totalImageFills, '| flagImage:', flagImage, '| flagDds:', flagDds)
  70. console.log('fonts:', [...globalFonts.entries()].sort((a, b) => b[1] - a[1]).map(([f, c]) => `${f}×${c}`).join(', '))
  71. console.log('palette:', [...globalColors.entries()].sort((a, b) => b[1] - a[1]).slice(0, 40).map(([c, n]) => `${c}×${n}`).join(' '))