feat: 文本框自动分词断行

This commit is contained in:
unanmed 2024-12-09 23:34:23 +08:00
parent b679cadd1b
commit 39baab94ae
3 changed files with 193 additions and 0 deletions

View File

@ -0,0 +1 @@
export * from './textbox';

View File

@ -0,0 +1,191 @@
import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
import { defineComponent } from 'vue';
export const enum WordBreak {
/** 不换行 */
None,
/** 仅空格和连字符等可换行CJK 字符可任意换行,默认值 */
Space,
/** 所有字符都可以换行 */
All
}
export interface TextContentProps {
text: string;
x?: number;
y?: number;
width?: number;
height?: number;
font?: string;
/** 打字机时间间隔,即两个字出现之间相隔多长时间 */
interval?: number;
/** 行高 */
lineHeight?: number;
/** 分词规则 */
wordBreak?: WordBreak;
/** 行首忽略字符,即不会出现在行首的字符 */
ignoreLineStart?: Iterable<string>;
/** 行尾忽略字符,即不会出现在行尾的字符 */
ignoreLineEnd?: Iterable<string>;
}
interface TextContentData {
text: string;
width: number;
font: string;
/** 分词规则 */
wordBreak: WordBreak;
/** 行首忽略字符,即不会出现在行首的字符 */
ignoreLineStart: Set<string>;
/** 行尾忽略字符,即不会出现在行尾的字符 */
ignoreLineEnd: Set<string>;
/** 会被分词规则识别的文字 */
breakChars: Set<string>;
}
export const TextContent = defineComponent((props, ctx) => {
return () => {};
});
export const Textbox = defineComponent((props, ctx) => {
return () => {};
});
let testCanvas: MotaOffscreenCanvas2D;
Mota.require('var', 'loading').once('coreInit', () => {
testCanvas = new MotaOffscreenCanvas2D(false);
testCanvas.withGameScale(false);
testCanvas.setHD(false);
testCanvas.size(32, 32);
testCanvas.freeze();
});
/**
*
* @param data
*/
function splitLines(data: TextContentData) {
const words = breakWords(data);
if (words.length === 1) return [words[0]];
// 对文字二分,然后计算长度
const text = data.text;
let start = 0;
let end = words.length;
let resolved = 0;
let mid = 0;
const res: number[] = [];
const ctx = testCanvas.ctx;
ctx.font = data.font;
console.time();
while (1) {
const diff = end - start;
if (diff === 1) {
const data1 = ctx.measureText(
text.slice(words[resolved], words[end])
);
if (data1.width <= data.width) {
res.push(words[end - 1]);
} else {
res.push(words[start]);
}
if (end === words.length) break;
resolved = start;
end = words.length;
} else {
mid = Math.floor((start + end) / 2);
const chars = text.slice(words[resolved], words[mid]);
const { width } = ctx.measureText(chars);
if (width <= data.width) {
start = mid;
if (start === end) end++;
} else {
end = mid;
if (start === end) end++;
}
}
}
console.timeEnd();
return res;
}
const defaultsBreak = ' -,.)]}?!;:,。)】?!;:';
const breakSet = new Set(defaultsBreak);
/**
* CJK
* @param char
*/
function isCJK(char: number) {
// 参考自 https://blog.csdn.net/brooksychen/article/details/2755395
return (
(char >= 0x4e00 && char <= 0x9fff) ||
(char >= 0x3000 && char <= 0x30ff) ||
(char >= 0xac00 && char <= 0xd7af) ||
(char >= 0xf900 && char <= 0xfaff) ||
(char >= 0x3400 && char <= 0x4dbf) ||
(char >= 0x20000 && char <= 0x2ebef) ||
(char >= 0x30000 && char <= 0x323af) ||
(char >= 0x2e80 && char <= 0x2eff) ||
(char >= 0x31c0 && char <= 0x31ef)
);
}
/**
*
* @param data
*/
function breakWords(data: TextContentData) {
let allBreak = false;
const breakChars = breakSet.union(data.breakChars);
switch (data.wordBreak) {
case WordBreak.None: {
return [data.text.length];
}
case WordBreak.Space: {
allBreak = false;
break;
}
case WordBreak.All: {
allBreak = true;
break;
}
}
console.time();
const res: number[] = [0];
const text = data.text;
const { ignoreLineStart, ignoreLineEnd } = data;
for (let pointer = 0; pointer < text.length; pointer++) {
const char = text[pointer];
const next = text[pointer + 1];
if (!ignoreLineEnd.has(char) && ignoreLineEnd.has(next)) {
res.push(pointer);
continue;
}
if (ignoreLineStart.has(char) && !ignoreLineStart.has(next)) {
res.push(pointer);
continue;
}
if (
breakChars.has(char) ||
allBreak ||
char === '\n' ||
isCJK(char.charCodeAt(0))
) {
res.push(pointer);
continue;
}
}
res.push(text.length);
console.timeEnd();
return res;
}

View File

@ -87,3 +87,4 @@ export * from './shader';
export * from './sprite'; export * from './sprite';
export * from './transform'; export * from './transform';
export * from './utils'; export * from './utils';
export * from './components';