refactor: 收口 BangDream 列表框架规格
This commit is contained in:
parent
08627646e5
commit
ef1140bd22
@ -14,7 +14,7 @@
|
|||||||
## 当前事实
|
## 当前事实
|
||||||
|
|
||||||
- Tsugu 源码目录:`src/qqbot/plugins/bangDream/tsugu`
|
- Tsugu 源码目录:`src/qqbot/plugins/bangDream/tsugu`
|
||||||
- TS 文件:初始基线 92;当前 `tsugu` 源码 126
|
- TS 文件:初始基线 92;当前 `tsugu` 源码 127
|
||||||
- 函数节点:481,其中稳定函数 410,匿名/内联回调 71
|
- 函数节点:481,其中稳定函数 410,匿名/内联回调 71
|
||||||
- 源码 JSDoc:稳定函数 410/410 已覆盖
|
- 源码 JSDoc:稳定函数 410/410 已覆盖
|
||||||
- 变量声明:1896
|
- 变量声明:1896
|
||||||
@ -356,6 +356,8 @@ export interface TsuguHook {
|
|||||||
- 已新增 `card-art-spec.spec.ts`,覆盖 rarity=1 属性边框、其他稀有度边框和关键尺寸;本地生成 `card-art-spec-card-472.jpg`,验证卡牌详情图在规格收口后仍能正常输出。
|
- 已新增 `card-art-spec.spec.ts`,覆盖 rarity=1 属性边框、其他稀有度边框和关键尺寸;本地生成 `card-art-spec-card-472.jpg`,验证卡牌详情图在规格收口后仍能正常输出。
|
||||||
- 已新增 `render-blocks/detail-block-spec.ts`,收口歌曲详情、歌曲 meta、角色半身块和玩家详情头图的尺寸、间距、字号与相对分数舍入规则;`detail-blocks.ts` 改为消费 spec,数据查询和绘制顺序保持不变。
|
- 已新增 `render-blocks/detail-block-spec.ts`,收口歌曲详情、歌曲 meta、角色半身块和玩家详情头图的尺寸、间距、字号与相对分数舍入规则;`detail-blocks.ts` 改为消费 spec,数据查询和绘制顺序保持不变。
|
||||||
- 已新增 `detail-block-spec.spec.ts`,覆盖歌曲详情尺寸、角色/玩家详情尺寸和 meta 相对百分比舍入;本地生成 `detail-spec-song-136.jpg`、`detail-spec-event-50.jpg`、`detail-spec-character-1.jpg`,验证详情区块规格收口后查曲/查活动/查角色图片输出正常。
|
- 已新增 `detail-block-spec.spec.ts`,覆盖歌曲详情尺寸、角色/玩家详情尺寸和 meta 相对百分比舍入;本地生成 `detail-spec-song-136.jpg`、`detail-spec-event-50.jpg`、`detail-spec-character-1.jpg`,验证详情区块规格收口后查曲/查活动/查角色图片输出正常。
|
||||||
|
- 已新增 `render-blocks/list-frame-spec.ts`,收口通用列表行、tips、横向合并列、居中图片列表和左侧竖线的字号、间距、兜底尺寸与布局计算;`list-frame.ts` 改为消费 spec,服务器分组、换行和绘制顺序保持不变。
|
||||||
|
- 已新增 `list-frame-spec.spec.ts`,覆盖列表正文宽度、标签/tips 偏移、合并列宽、居中图片换行和左侧竖线尺寸;本地生成 `list-frame-spec-song-136.jpg`、`list-frame-spec-event-50.jpg`、`list-frame-spec-character-1.jpg`,验证列表框架规格收口后查曲/查活动/查角色图片输出正常。
|
||||||
|
|
||||||
### Phase 6:策略 policy 和时间/档线规则
|
### Phase 6:策略 policy 和时间/档线规则
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,213 @@
|
|||||||
|
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
||||||
|
|
||||||
|
interface ImageLike {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KeyedListLayoutOptions {
|
||||||
|
keyHeight: number;
|
||||||
|
maxWidth?: number;
|
||||||
|
textHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListFrameImageRow<T extends ImageLike> {
|
||||||
|
height: number;
|
||||||
|
imageList: T[];
|
||||||
|
width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BANGDREAM_LIST_FRAME_SPEC = {
|
||||||
|
imageList: {
|
||||||
|
emptyHeight: 10,
|
||||||
|
emptyWidth: 1,
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
emptyTextHeight: 0,
|
||||||
|
emptyTextWidth: 0,
|
||||||
|
keyGapHeight: 10,
|
||||||
|
noKeySpacerHeight: 1,
|
||||||
|
textHorizontalInset: 40,
|
||||||
|
},
|
||||||
|
merge: {
|
||||||
|
defaultWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
defaultSize: 40,
|
||||||
|
labelSize: 30,
|
||||||
|
lineHeightRatio: 1.5,
|
||||||
|
spacingRatio: 1 / 3,
|
||||||
|
},
|
||||||
|
tips: {
|
||||||
|
backgroundOffsetY: 10,
|
||||||
|
defaultTextSize: 30,
|
||||||
|
emptyTextHeight: 1,
|
||||||
|
emptyTextWidth: 1,
|
||||||
|
},
|
||||||
|
withLine: {
|
||||||
|
canvasExtraHeight: 10,
|
||||||
|
contentX: 10,
|
||||||
|
contentY: 10,
|
||||||
|
lineExtraHeight: 20,
|
||||||
|
lineWidth: 5,
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算列表文本默认行高。
|
||||||
|
*
|
||||||
|
* @param textSize - 文本字号。
|
||||||
|
*/
|
||||||
|
export function getListFrameLineHeight(textSize: number) {
|
||||||
|
return textSize * BANGDREAM_LIST_FRAME_SPEC.text.lineHeightRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算列表文本和图片之间的默认间距。
|
||||||
|
*
|
||||||
|
* @param textSize - 文本字号。
|
||||||
|
*/
|
||||||
|
export function getListFrameSpacing(textSize: number) {
|
||||||
|
return textSize * BANGDREAM_LIST_FRAME_SPEC.text.spacingRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算列表正文最大宽度。
|
||||||
|
*
|
||||||
|
* @param maxWidth - 列表总宽度。
|
||||||
|
*/
|
||||||
|
export function getListFrameTextMaxWidth(
|
||||||
|
maxWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
) {
|
||||||
|
return maxWidth - BANGDREAM_LIST_FRAME_SPEC.list.textHorizontalInset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算带字段标签的列表行布局。
|
||||||
|
*
|
||||||
|
* @param options - 标签高度、正文高度和总宽度。
|
||||||
|
*/
|
||||||
|
export function createKeyedListFrameLayout({
|
||||||
|
keyHeight,
|
||||||
|
maxWidth = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
textHeight,
|
||||||
|
}: KeyedListLayoutOptions) {
|
||||||
|
const textY = keyHeight + BANGDREAM_LIST_FRAME_SPEC.list.keyGapHeight;
|
||||||
|
return {
|
||||||
|
height: textHeight + textY,
|
||||||
|
keyX: 0,
|
||||||
|
keyY: 0,
|
||||||
|
textX: BANGDREAM_RENDER_THEME.layout.listIndent,
|
||||||
|
textY,
|
||||||
|
width: maxWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算 tips 行布局。
|
||||||
|
*
|
||||||
|
* @param textHeight - tips 文本高度。
|
||||||
|
*/
|
||||||
|
export function createTipsInListLayout(textHeight: number) {
|
||||||
|
return {
|
||||||
|
backgroundHeight: textHeight,
|
||||||
|
backgroundWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
backgroundX: 0,
|
||||||
|
backgroundY: BANGDREAM_LIST_FRAME_SPEC.tips.backgroundOffsetY,
|
||||||
|
height: textHeight + BANGDREAM_LIST_FRAME_SPEC.tips.backgroundOffsetY,
|
||||||
|
textMaxWidth:
|
||||||
|
BANGDREAM_RENDER_THEME.layout.contentWidth -
|
||||||
|
BANGDREAM_RENDER_THEME.layout.listIndent * 2,
|
||||||
|
textX: BANGDREAM_RENDER_THEME.layout.listIndent,
|
||||||
|
textY: BANGDREAM_LIST_FRAME_SPEC.tips.backgroundOffsetY,
|
||||||
|
width: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算横向合并列表的列宽。
|
||||||
|
*
|
||||||
|
* @param itemCount - 合并项数量。
|
||||||
|
*/
|
||||||
|
export function getMergedListColumnWidth(itemCount: number) {
|
||||||
|
if (itemCount <= 0) return 0;
|
||||||
|
return BANGDREAM_LIST_FRAME_SPEC.merge.defaultWidth / itemCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算居中图片列表的换行结果。
|
||||||
|
*
|
||||||
|
* @param imageList - 图片或画布尺寸列表。
|
||||||
|
* @param maxWidth - 每行最大宽度。
|
||||||
|
*/
|
||||||
|
export function createCenteredImageRows<T extends ImageLike>(
|
||||||
|
imageList: T[],
|
||||||
|
maxWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
): ListFrameImageRow<T>[] {
|
||||||
|
const lineList: ListFrameImageRow<T>[] = [];
|
||||||
|
let tempWidth = 0;
|
||||||
|
let tempHeight = 0;
|
||||||
|
let tempImageList: T[] = [];
|
||||||
|
|
||||||
|
const newLine = () => {
|
||||||
|
lineList.push({
|
||||||
|
height: tempHeight,
|
||||||
|
imageList: tempImageList,
|
||||||
|
width: tempWidth,
|
||||||
|
});
|
||||||
|
tempWidth = 0;
|
||||||
|
tempHeight = 0;
|
||||||
|
tempImageList = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const element of imageList) {
|
||||||
|
if (element.width > maxWidth) {
|
||||||
|
newLine();
|
||||||
|
tempImageList.push(element);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (tempWidth + element.width > maxWidth) {
|
||||||
|
newLine();
|
||||||
|
}
|
||||||
|
tempWidth += element.width;
|
||||||
|
if (element.height > tempHeight) {
|
||||||
|
tempHeight = element.height;
|
||||||
|
}
|
||||||
|
tempImageList.push(element);
|
||||||
|
}
|
||||||
|
if (tempImageList.length > 0) {
|
||||||
|
newLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return lineList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算居中图片列表总高度。
|
||||||
|
*
|
||||||
|
* @param lineList - 居中图片行。
|
||||||
|
*/
|
||||||
|
export function getCenteredImageRowsHeight(
|
||||||
|
lineList: Array<ListFrameImageRow<ImageLike>>,
|
||||||
|
) {
|
||||||
|
return lineList.reduce((total, element) => total + element.height, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算左侧竖线列表布局。
|
||||||
|
*
|
||||||
|
* @param contentHeight - 内容总高度。
|
||||||
|
*/
|
||||||
|
export function createListWithLineLayout(contentHeight: number) {
|
||||||
|
return {
|
||||||
|
canvasHeight:
|
||||||
|
contentHeight + BANGDREAM_LIST_FRAME_SPEC.withLine.canvasExtraHeight,
|
||||||
|
canvasWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
|
contentX: BANGDREAM_LIST_FRAME_SPEC.withLine.contentX,
|
||||||
|
contentY: BANGDREAM_LIST_FRAME_SPEC.withLine.contentY,
|
||||||
|
lineHeight:
|
||||||
|
contentHeight + BANGDREAM_LIST_FRAME_SPEC.withLine.lineExtraHeight,
|
||||||
|
lineWidth: BANGDREAM_LIST_FRAME_SPEC.withLine.lineWidth,
|
||||||
|
lineY: BANGDREAM_LIST_FRAME_SPEC.withLine.contentY,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -17,6 +17,18 @@ import {
|
|||||||
createHorizontalSeparatorSpec,
|
createHorizontalSeparatorSpec,
|
||||||
createVerticalSeparatorSpec,
|
createVerticalSeparatorSpec,
|
||||||
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
|
||||||
|
import {
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC,
|
||||||
|
createCenteredImageRows,
|
||||||
|
createKeyedListFrameLayout,
|
||||||
|
createListWithLineLayout,
|
||||||
|
createTipsInListLayout,
|
||||||
|
getCenteredImageRowsHeight,
|
||||||
|
getListFrameLineHeight,
|
||||||
|
getListFrameSpacing,
|
||||||
|
getListFrameTextMaxWidth,
|
||||||
|
getMergedListColumnWidth,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame-spec';
|
||||||
|
|
||||||
//表格用默认虚线
|
//表格用默认虚线
|
||||||
export const line: Canvas = drawDottedLine(createHorizontalSeparatorSpec());
|
export const line: Canvas = drawDottedLine(createHorizontalSeparatorSpec());
|
||||||
@ -43,16 +55,16 @@ export function drawList({
|
|||||||
key,
|
key,
|
||||||
text,
|
text,
|
||||||
content,
|
content,
|
||||||
textSize = 40,
|
textSize = BANGDREAM_LIST_FRAME_SPEC.text.defaultSize,
|
||||||
lineHeight = textSize * 1.5,
|
lineHeight = getListFrameLineHeight(textSize),
|
||||||
spacing = textSize / 3,
|
spacing = getListFrameSpacing(textSize),
|
||||||
color = BANGDREAM_RENDER_THEME.color.primaryText,
|
color = BANGDREAM_RENDER_THEME.color.primaryText,
|
||||||
maxWidth = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
}: ListOptions): Canvas {
|
}: ListOptions): Canvas {
|
||||||
const xmax = maxWidth - 40;
|
const xmax = getListFrameTextMaxWidth(maxWidth);
|
||||||
const keyImage = drawRoundedRectWithText({
|
const keyImage = drawRoundedRectWithText({
|
||||||
text: key,
|
text: key,
|
||||||
textSize: 30,
|
textSize: BANGDREAM_LIST_FRAME_SPEC.text.labelSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
let textImage: Canvas;
|
let textImage: Canvas;
|
||||||
@ -68,24 +80,30 @@ export function drawList({
|
|||||||
color,
|
color,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
textImage = new Canvas(0, 0);
|
textImage = new Canvas(
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.list.emptyTextWidth,
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.list.emptyTextHeight,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (key == undefined) {
|
if (key == undefined) {
|
||||||
return stackImageHorizontal([
|
return stackImageHorizontal([
|
||||||
new Canvas(BANGDREAM_RENDER_THEME.layout.listIndent, 1),
|
new Canvas(
|
||||||
|
BANGDREAM_RENDER_THEME.layout.listIndent,
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.list.noKeySpacerHeight,
|
||||||
|
),
|
||||||
textImage,
|
textImage,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
const ymax = textImage.height + keyImage.height + 10;
|
const layout = createKeyedListFrameLayout({
|
||||||
const canvas = new Canvas(maxWidth, ymax);
|
keyHeight: keyImage.height,
|
||||||
|
maxWidth,
|
||||||
|
textHeight: textImage.height,
|
||||||
|
});
|
||||||
|
const canvas = new Canvas(layout.width, layout.height);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(keyImage, 0, 0);
|
ctx.drawImage(keyImage, layout.keyX, layout.keyY);
|
||||||
if (textImage.height != 0) {
|
if (textImage.height != 0) {
|
||||||
ctx.drawImage(
|
ctx.drawImage(textImage, layout.textX, layout.textY);
|
||||||
textImage,
|
|
||||||
BANGDREAM_RENDER_THEME.layout.listIndent,
|
|
||||||
keyImage.height + 10,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
@ -105,13 +123,12 @@ interface tipsOptions {
|
|||||||
export function drawTipsInList({
|
export function drawTipsInList({
|
||||||
text,
|
text,
|
||||||
content,
|
content,
|
||||||
textSize = 30,
|
textSize = BANGDREAM_LIST_FRAME_SPEC.tips.defaultTextSize,
|
||||||
lineHeight = textSize * 1.5,
|
lineHeight = getListFrameLineHeight(textSize),
|
||||||
spacing = textSize / 3,
|
spacing = getListFrameSpacing(textSize),
|
||||||
}: tipsOptions) {
|
}: tipsOptions) {
|
||||||
const xmax =
|
const layout = createTipsInListLayout(0);
|
||||||
BANGDREAM_RENDER_THEME.layout.contentWidth -
|
const xmax = layout.textMaxWidth;
|
||||||
BANGDREAM_RENDER_THEME.layout.listIndent * 2;
|
|
||||||
let textImage: Canvas;
|
let textImage: Canvas;
|
||||||
if (typeof text == 'string') {
|
if (typeof text == 'string') {
|
||||||
textImage = drawText({ text, textSize, maxWidth: xmax, lineHeight });
|
textImage = drawText({ text, textSize, maxWidth: xmax, lineHeight });
|
||||||
@ -124,21 +141,22 @@ export function drawTipsInList({
|
|||||||
spacing,
|
spacing,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
textImage = new Canvas(1, 1);
|
textImage = new Canvas(
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.tips.emptyTextWidth,
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.tips.emptyTextHeight,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const canvas = new Canvas(
|
const textLayout = createTipsInListLayout(textImage.height);
|
||||||
BANGDREAM_RENDER_THEME.layout.contentWidth,
|
const canvas = new Canvas(textLayout.width, textLayout.height);
|
||||||
textImage.height + 10,
|
|
||||||
);
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.fillStyle = BANGDREAM_RENDER_THEME.color.subtlePanel;
|
ctx.fillStyle = BANGDREAM_RENDER_THEME.color.subtlePanel;
|
||||||
ctx.fillRect(
|
ctx.fillRect(
|
||||||
0,
|
textLayout.backgroundX,
|
||||||
10,
|
textLayout.backgroundY,
|
||||||
BANGDREAM_RENDER_THEME.layout.contentWidth,
|
textLayout.backgroundWidth,
|
||||||
textImage.height,
|
textLayout.backgroundHeight,
|
||||||
);
|
);
|
||||||
ctx.drawImage(textImage, BANGDREAM_RENDER_THEME.layout.listIndent, 10);
|
ctx.drawImage(textImage, textLayout.textX, textLayout.textY);
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,15 +242,16 @@ export function drawListMerge(imageList: Array<Canvas | Image>): Canvas {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const canvas = new Canvas(
|
const canvas = new Canvas(
|
||||||
BANGDREAM_RENDER_THEME.layout.contentWidth,
|
BANGDREAM_LIST_FRAME_SPEC.merge.defaultWidth,
|
||||||
maxHeight,
|
maxHeight,
|
||||||
);
|
);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
let x = 0;
|
let x = 0;
|
||||||
|
const columnWidth = getMergedListColumnWidth(imageList.length);
|
||||||
for (let i = 0; i < imageList.length; i++) {
|
for (let i = 0; i < imageList.length; i++) {
|
||||||
const element = imageList[i];
|
const element = imageList[i];
|
||||||
ctx.drawImage(element, x, 0);
|
ctx.drawImage(element, x, 0);
|
||||||
x += BANGDREAM_RENDER_THEME.layout.contentWidth / imageList.length;
|
x += columnWidth;
|
||||||
}
|
}
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
@ -249,60 +268,15 @@ export function drawImageListCenter(
|
|||||||
imageList: Array<Canvas | Image>,
|
imageList: Array<Canvas | Image>,
|
||||||
maxWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||||
): Canvas {
|
): Canvas {
|
||||||
interface imageLine {
|
|
||||||
imageList: Array<Canvas | Image>;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
}
|
|
||||||
const lineList: Array<imageLine> = [];
|
|
||||||
let tempWidth = 0;
|
|
||||||
let tempHeight = 0;
|
|
||||||
let tempImageList: Array<Canvas | Image> = [];
|
|
||||||
//换行函数
|
|
||||||
/**
|
|
||||||
* 在图片布局层中处理new线条。
|
|
||||||
*/
|
|
||||||
function newLine() {
|
|
||||||
lineList.push({
|
|
||||||
imageList: tempImageList,
|
|
||||||
width: tempWidth,
|
|
||||||
height: tempHeight,
|
|
||||||
});
|
|
||||||
tempWidth = 0;
|
|
||||||
tempHeight = 0;
|
|
||||||
tempImageList = [];
|
|
||||||
}
|
|
||||||
if (imageList.length == 0) {
|
if (imageList.length == 0) {
|
||||||
return new Canvas(1, 10);
|
return new Canvas(
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.imageList.emptyWidth,
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC.imageList.emptyHeight,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
//遍历imageList,计算每一行的宽度,高度,imageList
|
const lineList = createCenteredImageRows(imageList, maxWidth);
|
||||||
for (let i = 0; i < imageList.length; i++) {
|
const height = getCenteredImageRowsHeight(lineList);
|
||||||
const element = imageList[i];
|
const canvas = new Canvas(maxWidth, height);
|
||||||
if (element.width > maxWidth) {
|
|
||||||
newLine();
|
|
||||||
tempImageList.push(element);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (tempWidth + element.width > maxWidth) {
|
|
||||||
newLine();
|
|
||||||
}
|
|
||||||
tempWidth += element.width;
|
|
||||||
if (element.height > tempHeight) {
|
|
||||||
tempHeight = element.height;
|
|
||||||
}
|
|
||||||
tempImageList.push(element);
|
|
||||||
}
|
|
||||||
if (tempImageList.length > 0) {
|
|
||||||
//最后一行
|
|
||||||
newLine();
|
|
||||||
}
|
|
||||||
//计算总高度,生成canvas
|
|
||||||
let Height = 0;
|
|
||||||
for (let i = 0; i < lineList.length; i++) {
|
|
||||||
const element = lineList[i];
|
|
||||||
Height += element.height;
|
|
||||||
}
|
|
||||||
const canvas = new Canvas(maxWidth, Height);
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
//画每一行
|
//画每一行
|
||||||
const middleWidth = maxWidth / 2;
|
const middleWidth = maxWidth / 2;
|
||||||
@ -328,24 +302,26 @@ export function drawImageListCenter(
|
|||||||
* @returns 渲染或资源结果。
|
* @returns 渲染或资源结果。
|
||||||
*/
|
*/
|
||||||
export function drawListWithLine(textImageList: Array<Canvas | Image>): Canvas {
|
export function drawListWithLine(textImageList: Array<Canvas | Image>): Canvas {
|
||||||
const x = 10;
|
|
||||||
let y = 10;
|
|
||||||
let height = 0;
|
let height = 0;
|
||||||
for (let i = 0; i < textImageList.length; i++) {
|
for (let i = 0; i < textImageList.length; i++) {
|
||||||
const element = textImageList[i];
|
const element = textImageList[i];
|
||||||
height += element.height;
|
height += element.height;
|
||||||
}
|
}
|
||||||
const canvas = new Canvas(
|
const layout = createListWithLineLayout(height);
|
||||||
BANGDREAM_RENDER_THEME.layout.contentWidth,
|
const canvas = new Canvas(layout.canvasWidth, layout.canvasHeight);
|
||||||
height + 10,
|
|
||||||
);
|
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.fillStyle = BANGDREAM_RENDER_THEME.color.separator;
|
ctx.fillStyle = BANGDREAM_RENDER_THEME.color.separator;
|
||||||
const lineSpec = createVerticalSeparatorSpec(height + 20);
|
const lineSpec = createVerticalSeparatorSpec(layout.lineHeight);
|
||||||
ctx.fillRect(lineSpec.startX, 10, 5, height + 20);
|
ctx.fillRect(
|
||||||
|
lineSpec.startX,
|
||||||
|
layout.lineY,
|
||||||
|
layout.lineWidth,
|
||||||
|
layout.lineHeight,
|
||||||
|
);
|
||||||
|
let y = layout.contentY;
|
||||||
for (let i = 0; i < textImageList.length; i++) {
|
for (let i = 0; i < textImageList.length; i++) {
|
||||||
const element = textImageList[i];
|
const element = textImageList[i];
|
||||||
ctx.drawImage(element, x, y);
|
ctx.drawImage(element, layout.contentX, y);
|
||||||
y += element.height;
|
y += element.height;
|
||||||
}
|
}
|
||||||
return canvas;
|
return canvas;
|
||||||
|
|||||||
93
test/qqbot/plugins/bangDream/tsugu/list-frame-spec.spec.ts
Normal file
93
test/qqbot/plugins/bangDream/tsugu/list-frame-spec.spec.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import {
|
||||||
|
BANGDREAM_LIST_FRAME_SPEC,
|
||||||
|
createCenteredImageRows,
|
||||||
|
createKeyedListFrameLayout,
|
||||||
|
createListWithLineLayout,
|
||||||
|
createTipsInListLayout,
|
||||||
|
getCenteredImageRowsHeight,
|
||||||
|
getListFrameLineHeight,
|
||||||
|
getListFrameSpacing,
|
||||||
|
getListFrameTextMaxWidth,
|
||||||
|
getMergedListColumnWidth,
|
||||||
|
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame-spec';
|
||||||
|
|
||||||
|
describe('BangDream list frame spec', () => {
|
||||||
|
it('keeps text sizing and content width stable', () => {
|
||||||
|
expect(BANGDREAM_LIST_FRAME_SPEC.text.defaultSize).toBe(40);
|
||||||
|
expect(BANGDREAM_LIST_FRAME_SPEC.text.labelSize).toBe(30);
|
||||||
|
expect(getListFrameLineHeight(40)).toBe(60);
|
||||||
|
expect(getListFrameSpacing(30)).toBe(10);
|
||||||
|
expect(getListFrameTextMaxWidth(800)).toBe(760);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates keyed list and tips layouts with historical offsets', () => {
|
||||||
|
expect(
|
||||||
|
createKeyedListFrameLayout({
|
||||||
|
keyHeight: 30,
|
||||||
|
maxWidth: 800,
|
||||||
|
textHeight: 120,
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
height: 160,
|
||||||
|
keyX: 0,
|
||||||
|
keyY: 0,
|
||||||
|
textX: 20,
|
||||||
|
textY: 40,
|
||||||
|
width: 800,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createTipsInListLayout(90)).toEqual({
|
||||||
|
backgroundHeight: 90,
|
||||||
|
backgroundWidth: 800,
|
||||||
|
backgroundX: 0,
|
||||||
|
backgroundY: 10,
|
||||||
|
height: 100,
|
||||||
|
textMaxWidth: 760,
|
||||||
|
textX: 20,
|
||||||
|
textY: 10,
|
||||||
|
width: 800,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps merge column width and centered image rows deterministic', () => {
|
||||||
|
expect(getMergedListColumnWidth(2)).toBe(400);
|
||||||
|
|
||||||
|
const rows = createCenteredImageRows(
|
||||||
|
[
|
||||||
|
{ height: 20, width: 300 },
|
||||||
|
{ height: 30, width: 500 },
|
||||||
|
{ height: 40, width: 200 },
|
||||||
|
],
|
||||||
|
800,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(rows).toEqual([
|
||||||
|
{
|
||||||
|
height: 30,
|
||||||
|
imageList: [
|
||||||
|
{ height: 20, width: 300 },
|
||||||
|
{ height: 30, width: 500 },
|
||||||
|
],
|
||||||
|
width: 800,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
height: 40,
|
||||||
|
imageList: [{ height: 40, width: 200 }],
|
||||||
|
width: 200,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(getCenteredImageRowsHeight(rows)).toBe(70);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps left-line list dimensions stable', () => {
|
||||||
|
expect(createListWithLineLayout(120)).toEqual({
|
||||||
|
canvasHeight: 130,
|
||||||
|
canvasWidth: 800,
|
||||||
|
contentX: 10,
|
||||||
|
contentY: 10,
|
||||||
|
lineHeight: 140,
|
||||||
|
lineWidth: 5,
|
||||||
|
lineY: 10,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user