import { Canvas, Image } from 'skia-canvas'; import { drawRoundedRectWithText } from '@/qqbot/plugins/bangDream/tsugu/canvas/rect'; import { drawText, drawTextWithImages, } from '@/qqbot/plugins/bangDream/tsugu/canvas/text'; import { drawDottedLine } from '@/qqbot/plugins/bangDream/tsugu/canvas/dotted-line'; import { Server, getServerByPriority, getIcon, } from '@/qqbot/plugins/bangDream/tsugu/models/server'; import { stackImageHorizontal } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/image-stack'; import { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config'; import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme'; import { createHorizontalSeparatorSpec, createVerticalSeparatorSpec, } 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()); interface ListOptions { key?: string; text?: string; content?: Array; textSize?: number; lineHeight?: number; spacing?: number; color?: string; maxWidth?: number; } //画表格中的一行 /** * 在图片布局层中绘制列表。 * * @param options1 - options1参数。 * @returns 渲染或资源结果。 */ export function drawList({ key, text, content, textSize = BANGDREAM_LIST_FRAME_SPEC.text.defaultSize, lineHeight = getListFrameLineHeight(textSize), spacing = getListFrameSpacing(textSize), color = BANGDREAM_RENDER_THEME.color.primaryText, maxWidth = BANGDREAM_RENDER_THEME.layout.contentWidth, }: ListOptions): Canvas { const xmax = getListFrameTextMaxWidth(maxWidth); const keyImage = drawRoundedRectWithText({ text: key, textSize: BANGDREAM_LIST_FRAME_SPEC.text.labelSize, }); let textImage: Canvas; if (typeof text == 'string') { textImage = drawText({ text, maxWidth: xmax, lineHeight }); } else if (content != undefined) { textImage = drawTextWithImages({ content, maxWidth: xmax, lineHeight, textSize, spacing, color, }); } else { textImage = new Canvas( BANGDREAM_LIST_FRAME_SPEC.list.emptyTextWidth, BANGDREAM_LIST_FRAME_SPEC.list.emptyTextHeight, ); } if (key == undefined) { return stackImageHorizontal([ new Canvas( BANGDREAM_RENDER_THEME.layout.listIndent, BANGDREAM_LIST_FRAME_SPEC.list.noKeySpacerHeight, ), textImage, ]); } const layout = createKeyedListFrameLayout({ keyHeight: keyImage.height, maxWidth, textHeight: textImage.height, }); const canvas = new Canvas(layout.width, layout.height); const ctx = canvas.getContext('2d'); ctx.drawImage(keyImage, layout.keyX, layout.keyY); if (textImage.height != 0) { ctx.drawImage(textImage, layout.textX, layout.textY); } return canvas; } interface tipsOptions { text?: string; content?: Array; textSize?: number; lineHeight?: number; spacing?: number; } /** * 在图片布局层中绘制TipsIn列表。 * * @param options1 - options1参数。 */ export function drawTipsInList({ text, content, textSize = BANGDREAM_LIST_FRAME_SPEC.tips.defaultTextSize, lineHeight = getListFrameLineHeight(textSize), spacing = getListFrameSpacing(textSize), }: tipsOptions) { const layout = createTipsInListLayout(0); const xmax = layout.textMaxWidth; let textImage: Canvas; if (typeof text == 'string') { textImage = drawText({ text, textSize, maxWidth: xmax, lineHeight }); } else if (content != undefined) { textImage = drawTextWithImages({ textSize, content, maxWidth: xmax, lineHeight, spacing, }); } else { textImage = new Canvas( BANGDREAM_LIST_FRAME_SPEC.tips.emptyTextWidth, BANGDREAM_LIST_FRAME_SPEC.tips.emptyTextHeight, ); } const textLayout = createTipsInListLayout(textImage.height); const canvas = new Canvas(textLayout.width, textLayout.height); const ctx = canvas.getContext('2d'); ctx.fillStyle = BANGDREAM_RENDER_THEME.color.subtlePanel; ctx.fillRect( textLayout.backgroundX, textLayout.backgroundY, textLayout.backgroundWidth, textLayout.backgroundHeight, ); ctx.drawImage(textImage, textLayout.textX, textLayout.textY); return canvas; } /** * 在图片布局层中绘制列表By服务器列表。 * * @param content - content参数。 * @param key - 当前字段键名,未传入时使用默认值。 * @param serverList - 服务器列表参数,未传入时使用默认值。 * @param maxWidth - max宽度参数,未传入时使用默认值。 */ export async function drawListByServerList( content: Array, key?: string, serverList: Server[] = globalDefaultServer, maxWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth, ) { const tempcontent: Array = []; // 获取每个服务器的内容对应关系 const contentMap = new Map(); // 分组服务器,根据相同的内容将服务器归类 for (let i = 0; i < serverList.length; i++) { const tempServer = serverList[i]; const serverContent = content[tempServer]; if (serverContent == null) { continue; } if (!contentMap.has(serverContent)) { contentMap.set(serverContent, []); } contentMap.get(serverContent)?.push(tempServer); } // 遍历内容分组 for (const [serverContent, servers] of contentMap) { if (servers.length > 0) { // 对于同一组内容,只需要绘制一次图标和内容 for (let i = 0; i < servers.length; i++) { tempcontent.push(await getIcon(servers[i])); } // 添加对应的内容 tempcontent.push(serverContent); tempcontent.push('\n'); } } // 如果所有服务器内容都为空,选择优先级最高的服务器 if (tempcontent.length == 0) { const tempServer = getServerByPriority(content, serverList); tempcontent.push(await getIcon(tempServer)); tempcontent.push(content[tempServer]); tempcontent.push('\n'); } // 去掉最后一个换行符 tempcontent.pop(); const canvas = drawList({ key: key, content: tempcontent, maxWidth, }); return canvas; } //横向组合较短list,高度为最高的list,宽度平分 /** * 在图片布局层中绘制列表Merge。 * * @param imageList - 图片列表参数。 * @returns 渲染或资源结果。 */ export function drawListMerge(imageList: Array): Canvas { let maxHeight = 0; for (let i = 0; i < imageList.length; i++) { const element = imageList[i]; if (element.height > maxHeight) { maxHeight = element.height; } } const canvas = new Canvas( BANGDREAM_LIST_FRAME_SPEC.merge.defaultWidth, maxHeight, ); const ctx = canvas.getContext('2d'); let x = 0; const columnWidth = getMergedListColumnWidth(imageList.length); for (let i = 0; i < imageList.length; i++) { const element = imageList[i]; ctx.drawImage(element, x, 0); x += columnWidth; } return canvas; } //横向组合image/canvas array,居中,超过宽度则换行 /** * 在图片布局层中绘制图片列表Center。 * * @param imageList - 图片列表参数。 * @param maxWidth - max宽度参数,未传入时使用默认值。 * @returns 渲染或资源结果。 */ export function drawImageListCenter( imageList: Array, maxWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth, ): Canvas { if (imageList.length == 0) { return new Canvas( BANGDREAM_LIST_FRAME_SPEC.imageList.emptyWidth, BANGDREAM_LIST_FRAME_SPEC.imageList.emptyHeight, ); } const lineList = createCenteredImageRows(imageList, maxWidth); const height = getCenteredImageRowsHeight(lineList); const canvas = new Canvas(maxWidth, height); const ctx = canvas.getContext('2d'); //画每一行 const middleWidth = maxWidth / 2; let y = 0; for (let i = 0; i < lineList.length; i++) { const element = lineList[i]; let x = middleWidth - element.width / 2; for (let j = 0; j < element.imageList.length; j++) { const image = element.imageList[j]; ctx.drawImage(image, x, y); x += image.width; } y += element.height; } return canvas; } //画左侧有竖线的排版,用于画block时展示数据 /** * 在图片布局层中绘制列表With线条。 * * @param textImageList - 文本图片列表参数。 * @returns 渲染或资源结果。 */ export function drawListWithLine(textImageList: Array): Canvas { let height = 0; for (let i = 0; i < textImageList.length; i++) { const element = textImageList[i]; height += element.height; } const layout = createListWithLineLayout(height); const canvas = new Canvas(layout.canvasWidth, layout.canvasHeight); const ctx = canvas.getContext('2d'); ctx.fillStyle = BANGDREAM_RENDER_THEME.color.separator; const lineSpec = createVerticalSeparatorSpec(layout.lineHeight); ctx.fillRect( lineSpec.startX, layout.lineY, layout.lineWidth, layout.lineHeight, ); let y = layout.contentY; for (let i = 0; i < textImageList.length; i++) { const element = textImageList[i]; ctx.drawImage(element, layout.contentX, y); y += element.height; } return canvas; }