Files
drawlogo/js/core/poster-generator.js

80 lines
3.7 KiB
JavaScript

/**
* 画个标 · 海报生成引擎:版式模板 + 配色 + 元素构造
* 决策(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 };
})();