mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-01-19 04:19:30 +08:00
feat: 文本框自动分词断行
This commit is contained in:
parent
b679cadd1b
commit
39baab94ae
1
src/core/render/components/index.ts
Normal file
1
src/core/render/components/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './textbox';
|
191
src/core/render/components/textbox.tsx
Normal file
191
src/core/render/components/textbox.tsx
Normal 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;
|
||||
}
|
@ -87,3 +87,4 @@ export * from './shader';
|
||||
export * from './sprite';
|
||||
export * from './transform';
|
||||
export * from './utils';
|
||||
export * from './components';
|
||||
|
Loading…
Reference in New Issue
Block a user