/** * 画个标 · 海报生成引擎:版式模板 + 配色 + 元素构造 * 决策(paletteId/layoutId/title/sub/icon)→ 元素数组,供 renderer 渲染。 */ const HGB_PosterGenerator = (function () { 'use strict'; const PALETTES = [ { id: 'indigo', name: '靛蓝', bg: '#1e1b4b', primary: '#ffffff', accent: '#f59e0b' }, { id: 'crimson', name: '深红', bg: '#7f1d1d', primary: '#ffffff', accent: '#fbbf24' }, { id: 'ocean', name: '海洋', bg: '#0c4a6e', primary: '#ffffff', accent: '#38bdf8' }, { id: 'forest', name: '森林', bg: '#14532d', primary: '#ffffff', accent: '#86efac' }, { id: 'minimal', name: '极简', bg: '#ffffff', primary: '#111827', accent: '#4f46e5' } ]; function uid(p) { return p + '_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); } function rect(id, x, y, w, h, fill) { return { id, type: 'rect', x, y, w, h, fill }; } function circle(id, cx, cy, r, fill) { return { id, type: 'circle', cx, cy, r, fill }; } function text(id, text, x, y, fontSize, fontWeight, fill, align) { return { id, type: 'text', text, x, y, fontSize, fontWeight, fill, align: align || 'start' }; } const LAYOUTS = [ { id: 'centered', name: '居中对称', emoji: '🎯', build(spec, o) { const cw = spec.w, ch = spec.h, els = []; els.push(rect(uid('bg'), 0, 0, cw, ch, o.palette.bg)); els.push(circle(uid('dot'), cw / 2, ch * 0.30, ch * 0.12, o.palette.accent)); const fs = Math.round(cw * 0.075); els.push(text(uid('title'), o.title, cw / 2, ch * 0.50, fs, 800, o.palette.primary, 'middle')); if (o.sub) els.push(text(uid('sub'), o.sub, cw / 2, ch * 0.56, Math.round(fs * 0.38), 400, o.palette.primary, 'middle')); els.push(rect(uid('bar'), cw / 2 - cw * 0.12, ch * 0.62, cw * 0.24, 8, o.palette.accent)); return els; } }, { id: 'left', name: '左对齐', emoji: '◀️', build(spec, o) { const cw = spec.w, ch = spec.h, els = []; els.push(rect(uid('bg'), 0, 0, cw, ch, o.palette.bg)); els.push(rect(uid('side'), 0, 0, cw * 0.06, ch, o.palette.accent)); const mx = cw * 0.13; const fs = Math.round(cw * 0.08); els.push(text(uid('title'), o.title, mx, ch * 0.44, fs, 800, o.palette.primary, 'start')); if (o.sub) els.push(text(uid('sub'), o.sub, mx, ch * 0.52, Math.round(fs * 0.36), 400, o.palette.primary, 'start')); els.push(rect(uid('bar'), mx, ch * 0.58, cw * 0.18, 8, o.palette.accent)); return els; } }, { id: 'banner', name: '顶部横幅', emoji: '📢', build(spec, o) { const cw = spec.w, ch = spec.h, els = []; els.push(rect(uid('bg'), 0, 0, cw, ch, o.palette.bg)); els.push(rect(uid('band'), 0, ch * 0.18, cw, ch * 0.16, o.palette.accent)); const fs = Math.round(cw * 0.07); els.push(text(uid('title'), o.title, cw / 2, ch * 0.26, fs, 800, o.palette.bg, 'middle')); if (o.sub) els.push(text(uid('sub'), o.sub, cw / 2, ch * 0.55, Math.round(fs * 0.42), 400, o.palette.primary, 'middle')); return els; } } ]; /** 决策 → 元素数组 */ function generate(spec, decision) { const palette = PALETTES.find(p => p.id === decision.paletteId) || PALETTES[0]; const layout = LAYOUTS.find(l => l.id === decision.layoutId) || LAYOUTS[0]; const o = { palette, title: decision.title || '海报标题', sub: decision.sub || '' }; return layout.build(spec, o); } function defaultDecision(title, sub) { return { paletteId: 'indigo', layoutId: 'centered', title: title || '海报标题', sub: sub || '' }; } return { PALETTES, LAYOUTS, generate, defaultDecision }; })();