diff --git a/app/components/Admin/PlaylistCardGenerator.vue b/app/components/Admin/PlaylistCardGenerator.vue index 64ddb84..e3a78f3 100644 --- a/app/components/Admin/PlaylistCardGenerator.vue +++ b/app/components/Admin/PlaylistCardGenerator.vue @@ -91,30 +91,69 @@ const songLeftX = 44 const songRightX = 360 const songAreaWidth = songRightX - songLeftX +const isBrowser = typeof document !== 'undefined' + +let _sharedCtx = null +const getSharedCtx = () => { + if (!isBrowser) return null + if (_sharedCtx) return _sharedCtx + try { + const c = document.createElement('canvas') + _sharedCtx = c.getContext('2d') + } catch { _sharedCtx = null } + return _sharedCtx +} + const fitText = (ctx, text, fontSize, maxWidth, lineHeight) => { - ctx.font = `${fontSize}px "Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif` - const chars = Array.from(text) + const hasCtx = !!ctx + const fontFamily = `"Inter", "system-ui", "PingFang SC", "Microsoft YaHei", sans-serif` + const avgCharWidth = fontSize * 0.55 + const cjkCharWidth = fontSize * 1.05 + const splitLines = String(text || '').split(/\r?\n/) const lines = [] - let current = '' - for (const ch of chars) { - const tryStr = current + ch - const w = ctx.measureText(tryStr).width - if (w > maxWidth && current) { - lines.push(current) - current = ch + for (const rawLine of splitLines) { + if (hasCtx) { + ctx.font = `${fontSize}px ${fontFamily}` + const chars = Array.from(rawLine) + let current = '' + for (const ch of chars) { + const tryStr = current + ch + const w = ctx.measureText(tryStr).width + if (w > maxWidth && current) { + lines.push(current) + current = ch + } else { + current = tryStr + } + } + if (current) lines.push(current) } else { - current = tryStr + const chars = Array.from(rawLine) + let current = '' + let curW = 0 + for (const ch of chars) { + const code = ch.codePointAt(0) || 0 + const isCJK = code > 0x2e80 + const w = isCJK ? cjkCharWidth : avgCharWidth + if (curW + w > maxWidth && current) { + lines.push(current) + current = ch + curW = w + } else { + current += ch + curW += w + } + } + if (current) lines.push(current) } } - if (current) lines.push(current) return { lines, lineHeight } } const canvasHeight = computed(() => { const count = Math.min(props.songs.length, maxDisplaySongs) if (!count) return 600 - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') + const ctx = getSharedCtx() let totalH = 0 const maxWidthTitle = songAreaWidth const maxWidthArtist = Math.max(100, songAreaWidth - 10)