probe-v2-palette.mjs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // 只读盘点:新版(raw-v2,2026-10-12)9 画板的色板 / 字号 / 字体 / 间距候选
  2. // 用于票 15(批次 2 token 替换)的取值依据;不改任何业务代码。
  3. import { readFileSync, readdirSync } from 'node:fs'
  4. import { join } from 'node:path'
  5. const dir = '.scratch/lanhu-cockpit-asset-replacement/research/raw-v2'
  6. const files = readdirSync(dir).filter((f) => /^desktop-\d+\.json$/.test(f)).sort()
  7. function hex(c) {
  8. const h = (v) => Math.round(v * 255).toString(16).padStart(2, '0')
  9. return `#${h(c.r)}${h(c.g)}${h(c.b)}`
  10. }
  11. const colors = new Map() // key -> {count, boards:Set, sample:path}
  12. const colorAlpha = new Map()
  13. const fonts = new Map()
  14. const fontSizes = new Map()
  15. const strokes = new Map()
  16. function note(map, key, extra) {
  17. const cur = map.get(key) || { count: 0, boards: new Set(), sample: '' }
  18. cur.count += 1
  19. if (extra) {
  20. cur.boards.add(extra.board)
  21. if (!cur.sample) cur.sample = extra.path
  22. }
  23. map.set(key, cur)
  24. }
  25. for (const file of files) {
  26. const j = JSON.parse(readFileSync(join(dir, file), 'utf8'))
  27. const ab = j.artboard
  28. const board = ab.name
  29. ;(function walk(node, path = '') {
  30. const p = path ? `${path}/${node.name}` : node.name
  31. for (const f of node.style?.fills || []) {
  32. if (f.type === 'color' && f.color) {
  33. const c = hex(f.color)
  34. note(colors, c, { board, path: p })
  35. const a = f.color.a ?? 1
  36. if (a < 0.999) note(colorAlpha, `${c}@${a.toFixed(2)}`, { board, path: p })
  37. }
  38. }
  39. for (const s of node.style?.strokes || []) {
  40. if (s.type === 'color' && s.color) {
  41. note(strokes, hex(s.color), { board, path: p })
  42. }
  43. }
  44. if (node.type === 'textLayer' && node.text) {
  45. const f = node.text.style?.font
  46. if (f?.name) note(fonts, f.name, { board, path: p })
  47. if (f?.name && f?.size) note(fontSizes, `${f.name} | ${f.size}px`, { board, path: p })
  48. }
  49. for (const ch of node.layers || []) walk(ch, p)
  50. })(ab, '')
  51. }
  52. const fmt = (m, n) =>
  53. [...m.entries()]
  54. .sort((a, b) => b[1].count - a[1].count)
  55. .slice(0, n)
  56. .map(([k, v]) => `${k} ×${v.count} [${[...v.boards].join(',')}]`)
  57. console.log('=== 全稿色值 Top 45(新版 v3/v4)')
  58. console.log(fmt(colors, 45).join('\n'))
  59. console.log('\n=== 带透明度色值 Top 25')
  60. console.log(fmt(colorAlpha, 25).join('\n'))
  61. console.log('\n=== 描边色 Top 15')
  62. console.log(fmt(strokes, 15).join('\n'))
  63. console.log('\n=== 字体族')
  64. console.log(fmt(fonts, 30).join('\n'))
  65. console.log('\n=== 字号 Top 30')
  66. console.log(fmt(fontSizes, 30).join('\n'))
  67. console.log(`\n总色值种类 ${colors.size},总字号种类 ${fontSizes.size}`)