diff --git a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md index aabc184..3c09539 100644 --- a/docs/qqbot-bangdream-tsugu-global-refactor-plan.md +++ b/docs/qqbot-bangdream-tsugu-global-refactor-plan.md @@ -395,6 +395,7 @@ export interface TsuguHook { - 已新增 `render-blocks/list-card-sd-character-spec.ts`,收口卡牌详情演出缩略图的 SD 角色 sprite 裁切网格、列表缩放行高、字号和间距;`list-card-sd-character.ts` 改为消费 spec,素材加载、四帧裁切和列表绘制顺序保持不变;`list-card-sd-character-spec.spec.ts` 覆盖四帧裁切坐标、单帧裁切计算和列表规格,本地 `/查卡 472` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-card-prefix-spec.ts`,收口卡牌详情顶部标题块的画布、背景、乐队 Logo 等比缩放、卡牌标题和角色名文字布局;`list-card-prefix.ts` 改为消费 spec,乐队/角色/服务器优先级读取和绘制顺序保持不变;`list-card-prefix-spec.spec.ts` 覆盖标题块尺寸、背景、文字坐标和 Logo 缩放,本地 `/查卡 472` 图片 smoke 输出非空。 - 已新增 `render-blocks/list-stat-spec.ts`,收口卡牌/玩家综合力列表的间隔、数值行画布、文字规格、进度条布局和条宽算法;`list-stat.ts` 改为消费 spec,综合力计算、突破加成和绘制顺序保持不变;`list-stat-spec.spec.ts` 覆盖间隔/画布/文字/进度条规格、文本生成和条宽布局,本地 `/查卡 472` 图片 smoke 输出非空。 +- 已新增 `render-blocks/title-spec.ts`,收口通用标题条的背景偏移、两行文字字号/行高/颜色/字体/坐标;`title.ts` 改为消费 spec,标题底图加载、标题文案和绘制顺序保持不变;`title-spec.spec.ts` 覆盖背景偏移、两行文字布局、绘制参数和坐标,本地 `/查卡 472` 图片 smoke 输出非空。 - smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。 ### Phase 6:策略 policy 和时间/档线规则 diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/title-spec.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/title-spec.ts new file mode 100644 index 0000000..0b8371c --- /dev/null +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/title-spec.ts @@ -0,0 +1,66 @@ +import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme'; + +export const BANGDREAM_TITLE_SPEC = { + background: { + x: 0, + y: 0, + }, + text: { + first: { + color: BANGDREAM_RENDER_THEME.color.surface, + font: BANGDREAM_RENDER_THEME.font.body, + lineHeight: 50, + maxWidth: 900, + textSize: 30, + x: 74, + y: 0, + }, + second: { + color: BANGDREAM_RENDER_THEME.color.labelBackground, + font: BANGDREAM_RENDER_THEME.font.body, + lineHeight: 68, + maxWidth: 900, + textSize: 40, + x: 74, + y: 42, + }, + }, +} as const; + +export type BangDreamTitleTextSlot = keyof typeof BANGDREAM_TITLE_SPEC.text; + +/** + * 生成标题文字绘制参数。 + * + * @param text - 标题文本。 + * @param slot - 标题第几行。 + */ +export function createTitleTextDrawOptions( + text: string, + slot: BangDreamTitleTextSlot, +) { + const spec = BANGDREAM_TITLE_SPEC.text[slot]; + + return { + color: spec.color, + font: spec.font, + lineHeight: spec.lineHeight, + maxWidth: spec.maxWidth, + text, + textSize: spec.textSize, + }; +} + +/** + * 获取标题文字绘制位置。 + * + * @param slot - 标题第几行。 + */ +export function getTitleTextPosition(slot: BangDreamTitleTextSlot) { + const spec = BANGDREAM_TITLE_SPEC.text[slot]; + + return { + x: spec.x, + y: spec.y, + }; +} diff --git a/src/qqbot/plugins/bangDream/tsugu/render-blocks/title.ts b/src/qqbot/plugins/bangDream/tsugu/render-blocks/title.ts index 6406f7a..524f46c 100644 --- a/src/qqbot/plugins/bangDream/tsugu/render-blocks/title.ts +++ b/src/qqbot/plugins/bangDream/tsugu/render-blocks/title.ts @@ -2,7 +2,11 @@ import { Canvas, Image } from 'skia-canvas'; import { drawText } from '@/qqbot/plugins/bangDream/tsugu/canvas/text'; import { loadImageFromPath } from '@/qqbot/plugins/bangDream/tsugu/canvas/image-utils'; import { getBangDreamAssetPath } from '@/qqbot/plugins/bangDream/tsugu/runtime/asset-manifest'; -import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme'; +import { + BANGDREAM_TITLE_SPEC, + createTitleTextDrawOptions, + getTitleTextPosition, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/title-spec'; let titleImage: Image; /** @@ -23,24 +27,16 @@ loadImageOnce(); export function drawTitle(title1: string, title2: string): Canvas { const canvas = new Canvas(titleImage.width, titleImage.height); const ctx = canvas.getContext('2d'); - ctx.drawImage(titleImage, 0, 0); - const text1 = drawText({ - text: title1, - maxWidth: 900, - lineHeight: 50, - textSize: 30, - color: BANGDREAM_RENDER_THEME.color.surface, - font: BANGDREAM_RENDER_THEME.font.body, - }); - const text2 = drawText({ - text: title2, - maxWidth: 900, - lineHeight: 68, - textSize: 40, - color: BANGDREAM_RENDER_THEME.color.labelBackground, - font: BANGDREAM_RENDER_THEME.font.body, - }); - ctx.drawImage(text1, 74, 0); - ctx.drawImage(text2, 74, 42); + ctx.drawImage( + titleImage, + BANGDREAM_TITLE_SPEC.background.x, + BANGDREAM_TITLE_SPEC.background.y, + ); + const text1 = drawText(createTitleTextDrawOptions(title1, 'first')); + const text2 = drawText(createTitleTextDrawOptions(title2, 'second')); + const firstPosition = getTitleTextPosition('first'); + const secondPosition = getTitleTextPosition('second'); + ctx.drawImage(text1, firstPosition.x, firstPosition.y); + ctx.drawImage(text2, secondPosition.x, secondPosition.y); return canvas; } diff --git a/test/qqbot/plugins/bangDream/tsugu/title-spec.spec.ts b/test/qqbot/plugins/bangDream/tsugu/title-spec.spec.ts new file mode 100644 index 0000000..3d44779 --- /dev/null +++ b/test/qqbot/plugins/bangDream/tsugu/title-spec.spec.ts @@ -0,0 +1,57 @@ +import { + BANGDREAM_TITLE_SPEC, + createTitleTextDrawOptions, + getTitleTextPosition, +} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/title-spec'; +import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme'; + +describe('BangDream title spec', () => { + it('keeps the historical title background offset stable', () => { + expect(BANGDREAM_TITLE_SPEC.background).toEqual({ + x: 0, + y: 0, + }); + }); + + it('keeps the historical first and second title text layout stable', () => { + expect(BANGDREAM_TITLE_SPEC.text.first).toEqual({ + color: BANGDREAM_RENDER_THEME.color.surface, + font: BANGDREAM_RENDER_THEME.font.body, + lineHeight: 50, + maxWidth: 900, + textSize: 30, + x: 74, + y: 0, + }); + expect(BANGDREAM_TITLE_SPEC.text.second).toEqual({ + color: BANGDREAM_RENDER_THEME.color.labelBackground, + font: BANGDREAM_RENDER_THEME.font.body, + lineHeight: 68, + maxWidth: 900, + textSize: 40, + x: 74, + y: 42, + }); + }); + + it('creates title text draw options and positions from the shared spec', () => { + expect(createTitleTextDrawOptions('查询', 'first')).toEqual({ + color: BANGDREAM_RENDER_THEME.color.surface, + font: BANGDREAM_RENDER_THEME.font.body, + lineHeight: 50, + maxWidth: 900, + text: '查询', + textSize: 30, + }); + expect(createTitleTextDrawOptions('卡牌', 'second')).toEqual({ + color: BANGDREAM_RENDER_THEME.color.labelBackground, + font: BANGDREAM_RENDER_THEME.font.body, + lineHeight: 68, + maxWidth: 900, + text: '卡牌', + textSize: 40, + }); + expect(getTitleTextPosition('first')).toEqual({ x: 74, y: 0 }); + expect(getTitleTextPosition('second')).toEqual({ x: 74, y: 42 }); + }); +});