UNPKG

@meta2d/core

Version:

@meta2d/core: Powerful, Beautiful, Simple, Open - Web-Based 2D At Its Best .

366 lines 12.9 kB
import { calcRightBottom } from '../rect'; import { getFont } from './render'; import { isEmptyText } from '../utils/tool'; export function calcTextRect(pen) { const { paddingTop, paddingBottom, paddingLeft, paddingRight, worldRect, canvas, text } = pen.calculative; // if(!text) return; let { textLeft, textTop, textWidth, textHeight } = pen.calculative; let x = paddingLeft; let y = paddingTop; // if (textLeft && Math.abs(textLeft) < 1) { // textLeft *= worldRect.width; // } // if (textTop && Math.abs(textTop) < 1) { // textTop *= worldRect.height; // } const width = worldRect.width - paddingLeft - paddingRight; const height = worldRect.height - paddingTop - paddingBottom; if (textWidth && textWidth < 1) { textWidth *= worldRect.width; } if (textHeight && textHeight < 1) { textHeight *= worldRect.height; } if (textWidth < pen.calculative.fontSize) { textWidth = pen.calculative.fontSize; } // 默认居左,居上 x += (textLeft || 0) + worldRect.x; y += (textTop || 0) + worldRect.y; const textAlign = pen.textAlign || canvas.store.options.textAlign; const textBaseline = pen.textBaseline || canvas.store.options.textBaseline; switch (textAlign) { case 'center': x += (width - (textWidth || width)) / 2; break; case 'right': x += width - (textWidth || width); break; } switch (textBaseline) { case 'middle': y += (height - (textHeight || height)) / 2; break; case 'bottom': y += height - (textHeight || height); break; } const rect = { x, y, width: textWidth || width, height: textHeight || height, }; calcRightBottom(rect); pen.calculative.worldTextRect = rect; calcTextLines(pen); pen.calculative.textDrawRect = undefined; } export function calcTextDrawRect(ctx, pen) { // By default, the text is center aligned. const calc = pen.calculative; if (isEmptyText(calc.text)) return; if (!calc.textLines) { calcTextLines(pen); } const { worldTextRect: rect, textLines, fontSize, lineHeight, canvas } = calc; if (!textLines) return; const lineHeightValue = fontSize * lineHeight; const h = textLines.length * lineHeightValue; if (pen.calculative.fontsChecked !== false) { if (!document.fonts.check(ctx.font)) { pen.calculative.fontsChecked = true; } else { pen.calculative.fontsChecked = false; } } const textWidth = calcTextAdaptionWidth(ctx, pen); // 多行文本最大宽度 let x = rect.x + (rect.width - textWidth) / 2; let y = rect.y + (rect.height - h) / 2; const { options } = canvas.store; const textAlign = pen.textAlign || options.textAlign; switch (textAlign) { case 'left': x = rect.x; break; case 'right': x = rect.x + rect.width - textWidth; break; } const textBaseline = pen.textBaseline || options.textBaseline; switch (textBaseline) { case 'top': y = rect.y; break; case 'bottom': y = rect.ey - h; break; } pen.calculative.textDrawRect = { x, y, width: textWidth, height: h, }; calcRightBottom(pen.calculative.textDrawRect); } export function calcTextLines(pen, text = pen.calculative.text) { const calc = pen.calculative; if (isEmptyText(text)) { calc.textLines = undefined; return; } let textStr = typeof text === 'string' ? text : text.toString(); const { whiteSpace, ellipsis } = pen; // 这里本来想过滤text或者rectangle图元,但是tablePlus图元会调用calcTextLines,导致拿不到返回值,所以先注释 // if((whiteSpace === 'pre-line' || whiteSpace === 'break-all') && !textStr.includes('\n')){ // calc.textLines = [textStr]; // return; // } const { keepDecimal } = calc; if (keepDecimal != undefined && textStr.trim() !== "") { const textNum = Number(textStr); if (!isNaN(textNum)) { textStr = textNum.toFixed(keepDecimal); } } let lines = []; const oneRowHeight = calc.fontSize * calc.lineHeight; const textHeight = calc.worldTextRect.height; const calcRows = Math.floor(textHeight / oneRowHeight); // 最小值为 1 const maxRows = calcRows > 1 ? calcRows : 1; switch (whiteSpace) { case 'nowrap': if (ellipsis !== false) { const allLines = wrapLines([...textStr], pen); if (allLines[0]) { lines.push(allLines[0]); if (allLines.length > 1) { // 存在第二行,说明宽度超出 setEllipsisOnLastLine(lines); } } } else { lines.push(textStr); } break; case 'pre-line': lines = textStr.split(/[\n]/g); if (ellipsis !== false && lines.length > maxRows) { lines = lines.slice(0, maxRows); setEllipsisOnLastLine(lines); } break; case 'break-all': default: const paragraphs = textStr.split(/[\n]/g); let currentRow = 0; outer: for (const paragraph of paragraphs) { const words = whiteSpace === 'break-all' ? paragraph.split('') : getWords(paragraph); let items = wrapLines(words, pen); // 空行换行的情况 if (items.length === 0) items = ['']; if (ellipsis != false) { for (const l of items) { currentRow++; if (currentRow > maxRows) { setEllipsisOnLastLine(lines); break outer; } else { lines.push(l); } } } else { lines.push(...items); } } break; } /* const keepDecimal = pen.calculative.keepDecimal; if (keepDecimal != undefined) { lines.forEach((text, i) => { const textNum = Number(text); if (!isNaN(textNum)) { lines[i] = textNum.toFixed(keepDecimal); } }); } */ calc.textLines = lines; return lines; } export function getWords(txt = '') { const words = []; let word = ''; for (let i = 0; i < txt.length; ++i) { const ch = txt.charCodeAt(i); if (ch < 33 || ch > 126) { if (word) { words.push(word); word = ''; } words.push(txt[i]); } else { word += txt[i]; } } if (word) { words.push(word); } return words; } export function wrapLines(words, pen) { const canvas = pen.calculative.canvas; const ctx = canvas.offscreen.getContext('2d'); const { fontStyle, fontWeight, fontSize, fontFamily, lineHeight } = pen.calculative; ctx.save(); const lines = []; let currentLine = words[0] || ''; for (let i = 1; i < words.length; ++i) { const word = words[i] || ''; const text = currentLine + word; let currentWidth = 0; if (canvas.store.options.measureTextWidth) { ctx.font = getFont({ fontStyle, fontWeight, fontFamily: fontFamily || canvas.store.options.fontFamily, fontSize, lineHeight, }); currentWidth = ctx.measureText(text).width; } else { // 近似计算 const chinese = text.match(/[^\x00-\xff]/g) || ''; const chineseWidth = chinese.length * fontSize; // 中文占用的宽度 const spaces = text.match(/\s/g) || ''; const spaceWidth = spaces.length * fontSize * 0.3; // 空格占用的宽度 const otherWidth = (text.length - chinese.length - spaces.length) * fontSize * 0.6; // 其他字符占用的宽度 currentWidth = chineseWidth + spaceWidth + otherWidth; } const textWidth = pen.calculative.worldTextRect.width; if (currentWidth <= textWidth + 0.1) { currentLine += word; } else { currentLine.length && lines.push(currentLine); currentLine = word; } } currentLine.length && lines.push(currentLine); ctx.restore(); return lines; } export function calcTextAdaptionWidth(ctx, pen) { let maxWidth = 0; pen.calculative.textLineWidths = []; pen.calculative.textLines && pen.calculative.textLines.forEach((text) => { let width; if (pen.calculative.textType) { // 文字渐变 measureText 计算有误 width = getFontWith(text, pen) + text.length * pen.calculative.letterSpacing; } else { width = ctx.measureText(text).width + text.length * pen.calculative.letterSpacing; } pen.calculative.textLineWidths.push(width); maxWidth < width && (maxWidth = width); }); return maxWidth; } function getFontWith(text, pen) { const fontSize = pen.calculative.fontSize; const chinese = text.match(/[^\x00-\xff]/g) || ''; const chineseWidth = chinese.length * fontSize; // 中文占用的宽度 const spaces = text.match(/\s/g) || ''; const spaceWidth = spaces.length * fontSize * (pen.CSmultiple || 0.5); // 空格占用的宽度 CSmultiple表示中文字符:空格字符像素倍数 const otherWidth = (text.length - chinese.length - spaces.length) * fontSize * (pen.CEmultiple || 0.54); // 其他字符占用的宽度 CEmultiple 表示中文字符:英文字符像素倍数。 return chineseWidth + spaceWidth + otherWidth; } /** * 副作用函数,会修改传入的参数 * 把最后一行的最后变成 ... * TODO: 中文的三个字符宽度比 . 大,显示起来像是删多了 * @param lines */ function setEllipsisOnLastLine(lines) { lines[lines.length - 1] = lines[lines.length - 1].slice(0, -3) + '...'; } export function calcTextAutoWidth(pen) { let arr = pen.text.split('\n'); const canvas = pen.calculative.canvas; const ctx = canvas.offscreen.getContext('2d'); const { fontStyle, fontWeight, fontSize, fontFamily, lineHeight } = pen.calculative; let textWidth = 0; // pen.calculative.worldTextRect.width; let currentWidth = 0; // textWidth; ctx.save(); for (let i = 0; i < arr.length; i++) { if (canvas.store.options.measureTextWidth) { ctx.font = getFont({ fontStyle, fontWeight, fontFamily: fontFamily || canvas.store.options.fontFamily, fontSize, lineHeight, }); currentWidth = ctx.measureText(arr[i]).width + arr[i].length * pen.calculative.letterSpacing; //* scale; } else { // 近似计算 const chinese = arr[i].match(/[^\x00-\xff]/g) || ''; const chineseWidth = chinese.length * fontSize; // 中文占用的宽度 const spaces = arr[i].match(/\s/g) || ''; const spaceWidth = spaces.length * fontSize * 0.3; // 空格占用的宽度 const otherWidth = (arr[i].length - chinese.length - spaces.length) * fontSize * 0.6; // 其他字符占用的宽度 currentWidth = chineseWidth + spaceWidth + otherWidth + arr[i].length * pen.calculative.letterSpacing; } if (currentWidth > textWidth) { textWidth = currentWidth; //* scale; } } ctx.restore(); let textHeight = arr.length * fontSize * lineHeight; if (pen.textAlign === 'left') { // pen.x = pen.x; } else if (pen.textAlign === 'right') { pen.x = pen.x - (textWidth - pen.width); } else { pen.x = pen.x - (textWidth - pen.width) / 2; } if (pen.textBaseline === 'top') { } else if (pen.textBaseline === 'bottom') { pen.y = pen.y - (textHeight - pen.height); } else { pen.y = pen.y - (textHeight - pen.height) / 2; } // if (textHeight > pen.height) { pen.height = textHeight + 5; // } pen.width = textWidth + 5; //误差 pen.calculative.canvas.updatePenRect(pen); pen.calculative.canvas.calcActiveRect(); } //# sourceMappingURL=text.js.map