// copy from kodo-web /** * @desc Paragraph utils functions. * @author hovenjay */ /** * 获取节点的字体相关样式 * * @param node - DOM 节点 */ export function getFontStyles(node: HTMLElement): string { const styles = window.getComputedStyle(node) const fontStyles = [styles.fontStyle, styles.fontWeight, styles.fontSize, styles.fontFamily] return fontStyles.filter(record => Boolean(record)).join(' ') } /** * 测量文本被渲染后的实际宽度 * * @param canvas - HTML Canvas 元素 * @param text - 被渲染的文本内容 * @param fontStyles - 被渲染的文本的样式 */ function measureTextWidth( text: string, fontStyles: string, canvas: HTMLCanvasElement = document.createElement('canvas') ) { const context = canvas.getContext('2d') if (!context) { throw new Error('Can not get canvas context.') } context.font = fontStyles const metrics = context.measureText(text) return metrics.width } export type TextInfo = { /* 文本 */ text: string /* 文本的渲染宽度 */ width: number } /* 文本的完整宽度 */ export type FullWidth = number /** * 获取省略中间内容后的文本 * * @param text - 原始文本 * @param fontStyles - 文本渲染样式,语法同 CSS 的 font 属性 * @param maxRows - 最多显示的行数 * @param maxWidth - 单行文本的最大宽度 * @param reserveWidth - 保留宽度 */ export function getMiddleEllipsisText( text: string, fontStyles: string, maxWidth: number, maxRows: number, reserveWidth = 0 ): [TextInfo, FullWidth] { /* 不显示的情况 */ if (maxWidth === 0 || maxRows < 1) { return [{ text: '', width: 0 }, 0] } const canvas = document.createElement('canvas') const fullTextWidth = measureTextWidth(text, fontStyles, canvas) /* 仅显示一行且未超出最大宽度 */ if (maxRows === 1 && fullTextWidth + reserveWidth < maxWidth) { return [{ text, width: fullTextWidth }, fullTextWidth] } const ellipsis = '…' const ellipsisWidth = measureTextWidth('…', fontStyles, canvas) let maxCharacterWidth = 0 const characters: Array = [] // TODO: 不用每个都测量,等在下面使用到的时候再测量,max 可以简单的使用 fontsize for (const character of text) { const characterWidth = measureTextWidth(character, fontStyles, canvas) if (characterWidth > maxCharacterWidth) { maxCharacterWidth = characterWidth } characters.push({ text: character, width: characterWidth, visible: false }) } const totalWidth = maxWidth * maxRows - reserveWidth /* 矫正宽度:多行显示时,行与行之间预留一个最大字符宽度来避免由于行尾宽度不够放下下一个字符时导致的宽度计算误差 */ const correctedWidth = (maxRows - 1) * maxCharacterWidth /* 显示多行且未超出总宽度 */ if (fullTextWidth <= totalWidth - correctedWidth) { return [{ text, width: fullTextWidth }, fullTextWidth] } characters[Math.ceil(characters.length / 2)] = { text: ellipsis, width: ellipsisWidth, visible: true } let remainingWidth = totalWidth - correctedWidth - ellipsisWidth // TODO: es 规范 string 编码为 utf-16,不能使用 index,改换迭代器 // TODO: 可以提前 break for (let i = 0, j = characters.length - 1; i < characters.length / 2; i += 1, j -= 1) { remainingWidth -= characters[i].width if (remainingWidth >= 0) { characters[i].visible = true } remainingWidth -= characters[j].width if (remainingWidth >= 0) { characters[j].visible = true } } let middleEllipsisText = '' characters.forEach(record => { if (record.visible) { middleEllipsisText = middleEllipsisText.concat(record.text) } }) return [{ text: middleEllipsisText, width: measureTextWidth(middleEllipsisText, fontStyles, canvas) }, fullTextWidth] }