mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-11 15:47:06 +08:00
fix: TextContent 的各种标签
This commit is contained in:
parent
72f02726ba
commit
d0dae40a5a
@ -90,6 +90,7 @@
|
||||
"56": "Method '$1' has been deprecated. Consider using '$2' instead.",
|
||||
"57": "Repeated UI controller on item '$1', new controller will not work.",
|
||||
"58": "Fail to set ellipse round rect, since length of 'ellipse' property should only be 2, 4, 6 or 8. delivered: $1",
|
||||
"59": "Unknown icon '$1' in parsing text content.",
|
||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||
}
|
||||
|
@ -131,24 +131,30 @@ export const TextContent = defineComponent<
|
||||
const ctx = canvas.ctx;
|
||||
ctx.textBaseline = 'top';
|
||||
renderable.forEach(v => {
|
||||
if (v.type === TextContentType.Text) {
|
||||
ctx.fillStyle = v.fillStyle;
|
||||
ctx.strokeStyle = v.strokeStyle;
|
||||
ctx.font = v.font;
|
||||
const text = v.text.slice(0, v.pointer);
|
||||
switch (v.type) {
|
||||
case TextContentType.Text: {
|
||||
if (v.text.length === 0) return;
|
||||
ctx.fillStyle = v.fillStyle;
|
||||
ctx.strokeStyle = v.strokeStyle;
|
||||
ctx.font = v.font;
|
||||
const text = v.text.slice(0, v.pointer);
|
||||
|
||||
if (props.fill ?? true) {
|
||||
ctx.fillText(text, v.x, v.y);
|
||||
if (props.fill ?? true) {
|
||||
ctx.fillText(text, v.x, v.y);
|
||||
}
|
||||
if (props.stroke) {
|
||||
ctx.strokeText(text, v.x, v.y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (props.stroke) {
|
||||
ctx.strokeText(text, v.x, v.y);
|
||||
case TextContentType.Icon: {
|
||||
const { renderable: r, x: dx, y: dy, width, height } = v;
|
||||
const render = r.render;
|
||||
const [x, y, w, h] = render[0];
|
||||
const icon = r.autotile ? r.image[0] : r.image;
|
||||
ctx.drawImage(icon, x, y, w, h, dx, dy, width, height);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
const r = v.renderable;
|
||||
const render = r.render;
|
||||
const [x, y, w, h] = render[0];
|
||||
const icon = r.autotile ? r.image[0] : r.image;
|
||||
ctx.drawImage(icon, x, y, w, h, v.x, v.y, v.width, v.height);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -135,7 +135,16 @@ export interface TyperIconRenderable {
|
||||
renderable: RenderableData | AutotileRenderable;
|
||||
}
|
||||
|
||||
export type TyperRenderable = TyperTextRenderable | TyperIconRenderable;
|
||||
export interface TyperWaitRenderable {
|
||||
type: TextContentType.Wait;
|
||||
wait: number;
|
||||
waited: number;
|
||||
}
|
||||
|
||||
export type TyperRenderable =
|
||||
| TyperTextRenderable
|
||||
| TyperIconRenderable
|
||||
| TyperWaitRenderable;
|
||||
|
||||
interface TextContentTyperEvent {
|
||||
typeStart: [];
|
||||
@ -271,49 +280,64 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
||||
private createTyperData(index: number, line: number) {
|
||||
const renderable = this.renderObject.data[index];
|
||||
if (!renderable) return false;
|
||||
if (renderable.type === TextContentType.Text) {
|
||||
if (line < 0 || line > renderable.splitLines.length) {
|
||||
return false;
|
||||
switch (renderable.type) {
|
||||
case TextContentType.Text: {
|
||||
if (line < 0 || line > renderable.splitLines.length) {
|
||||
return false;
|
||||
}
|
||||
const start = renderable.splitLines[line - 1] ?? -1;
|
||||
const end =
|
||||
renderable.splitLines[line] ?? renderable.text.length - 1;
|
||||
|
||||
const data: TyperTextRenderable = {
|
||||
type: TextContentType.Text,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
text: renderable.text.slice(start + 1, end + 1),
|
||||
font: renderable.font,
|
||||
fillStyle: renderable.fillStyle,
|
||||
strokeStyle: this.config.strokeStyle,
|
||||
pointer: 0
|
||||
};
|
||||
this.processingData = data;
|
||||
this.renderData.push(data);
|
||||
return true;
|
||||
}
|
||||
const start = renderable.splitLines[line - 1] ?? 0;
|
||||
const end = renderable.splitLines[line] ?? renderable.text.length;
|
||||
const data: TyperTextRenderable = {
|
||||
type: TextContentType.Text,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
text: renderable.text.slice(start, end),
|
||||
font: renderable.font,
|
||||
fillStyle: renderable.fillStyle,
|
||||
strokeStyle: this.config.strokeStyle,
|
||||
pointer: 0
|
||||
};
|
||||
this.processingData = data;
|
||||
this.renderData.push(data);
|
||||
return true;
|
||||
} else {
|
||||
const tex = texture.getRenderable(renderable.icon!);
|
||||
if (!tex) return false;
|
||||
const { render } = tex;
|
||||
const [, , width, height] = render[0];
|
||||
const aspect = width / height;
|
||||
let iconWidth = 0;
|
||||
if (aspect < 1) {
|
||||
// 这时候应该把高度限定在当前字体大小
|
||||
iconWidth = width * (renderable.fontSize / height);
|
||||
} else {
|
||||
iconWidth = renderable.fontSize;
|
||||
case TextContentType.Icon: {
|
||||
const tex = texture.getRenderable(renderable.icon!);
|
||||
if (!tex) return false;
|
||||
const { render } = tex;
|
||||
const [, , width, height] = render[0];
|
||||
const aspect = width / height;
|
||||
let iconWidth = 0;
|
||||
if (aspect < 1) {
|
||||
// 这时候应该把高度限定在当前字体大小
|
||||
iconWidth = width * (renderable.fontSize / height);
|
||||
} else {
|
||||
iconWidth = renderable.fontSize;
|
||||
}
|
||||
const data: TyperIconRenderable = {
|
||||
type: TextContentType.Icon,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: iconWidth,
|
||||
height: iconWidth / aspect,
|
||||
renderable: tex
|
||||
};
|
||||
this.processingData = data;
|
||||
this.renderData.push(data);
|
||||
return true;
|
||||
}
|
||||
case TextContentType.Wait: {
|
||||
const data: TyperWaitRenderable = {
|
||||
type: TextContentType.Wait,
|
||||
wait: renderable.wait!,
|
||||
waited: 0
|
||||
};
|
||||
this.processingData = data;
|
||||
this.renderData.push(data);
|
||||
return true;
|
||||
}
|
||||
const data: TyperIconRenderable = {
|
||||
type: TextContentType.Icon,
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: iconWidth,
|
||||
height: iconWidth / aspect,
|
||||
renderable: tex
|
||||
};
|
||||
this.processingData = data;
|
||||
this.renderData.push(data);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,62 +363,64 @@ export class TextContentTyper extends EventEmitter<TextContentTyperEvent> {
|
||||
return true;
|
||||
}
|
||||
const lineHeight = this.renderObject.lineHeights[this.nowLine];
|
||||
if (now.type === TextContentType.Text) {
|
||||
const restChars = now.text.length - now.pointer;
|
||||
if (restChars <= rest) {
|
||||
// 当前这段 renderable 打字完成后,刚好结束或还有内容
|
||||
rest -= restChars;
|
||||
now.pointer = now.text.length;
|
||||
if (this.dataLine === renderable.splitLines.length) {
|
||||
// 如果是最后一行
|
||||
if (isNil(renderable.lastLineWidth)) {
|
||||
const ctx = this.parser.testCanvas.ctx;
|
||||
ctx.font = now.font;
|
||||
const metrics = ctx.measureText(now.text);
|
||||
renderable.lastLineWidth = metrics.width;
|
||||
switch (now.type) {
|
||||
case TextContentType.Text: {
|
||||
const restChars = now.text.length - now.pointer;
|
||||
if (restChars <= rest) {
|
||||
// 当前这段 renderable 打字完成后,刚好结束或还有内容
|
||||
rest -= restChars;
|
||||
now.pointer = now.text.length;
|
||||
if (this.dataLine === renderable.splitLines.length) {
|
||||
// 如果是最后一行
|
||||
if (isNil(renderable.lastLineWidth)) {
|
||||
const ctx = this.parser.testCanvas.ctx;
|
||||
ctx.font = now.font;
|
||||
const metrics = ctx.measureText(now.text);
|
||||
renderable.lastLineWidth = metrics.width;
|
||||
}
|
||||
this.x += renderable.lastLineWidth;
|
||||
this.dataLine = 0;
|
||||
this.pointer++;
|
||||
} else {
|
||||
// 不是最后一行,那么换行
|
||||
this.x = 0;
|
||||
this.y += lineHeight + this.config.lineHeight;
|
||||
this.dataLine++;
|
||||
this.nowLine++;
|
||||
}
|
||||
this.x += renderable.lastLineWidth;
|
||||
this.dataLine = 0;
|
||||
this.pointer++;
|
||||
const success = this.createTyperData(
|
||||
this.pointer,
|
||||
this.dataLine
|
||||
);
|
||||
if (!success) return true;
|
||||
} else {
|
||||
// 不是最后一行,那么换行
|
||||
now.pointer += rest;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TextContentType.Icon: {
|
||||
rest--;
|
||||
this.pointer++;
|
||||
if (renderable.splitLines[0] === 0) {
|
||||
// 如果图标换行
|
||||
this.x = 0;
|
||||
this.y += lineHeight + this.config.lineHeight;
|
||||
this.dataLine++;
|
||||
this.nowLine++;
|
||||
const success = this.createTyperData(
|
||||
this.pointer,
|
||||
this.dataLine
|
||||
);
|
||||
if (!success) return true;
|
||||
this.dataLine = 0;
|
||||
now.x = 0;
|
||||
now.y = this.y;
|
||||
} else {
|
||||
this.x += now.width;
|
||||
}
|
||||
} else {
|
||||
now.pointer += rest;
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
rest--;
|
||||
this.pointer++;
|
||||
if (renderable.splitLines[0] === 0) {
|
||||
// 如果图标换行
|
||||
this.x = 0;
|
||||
this.y += lineHeight + this.config.lineHeight;
|
||||
this.nowLine++;
|
||||
this.dataLine = 0;
|
||||
now.x = 0;
|
||||
now.y = this.y;
|
||||
case TextContentType.Wait: {
|
||||
now.waited += num;
|
||||
if (now.waited > now.wait) {
|
||||
// 等待结束
|
||||
this.pointer++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
const success = this.createTyperData(
|
||||
this.pointer,
|
||||
this.dataLine
|
||||
);
|
||||
if (!success) return true;
|
||||
}
|
||||
const success = this.createTyperData(this.pointer, this.dataLine);
|
||||
if (!success) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -563,7 +589,7 @@ export class TextContentParser {
|
||||
const end = this.indexParam(start);
|
||||
if (end === -1) {
|
||||
// 标签结束
|
||||
return ['', start];
|
||||
return ['', start - 1];
|
||||
} else {
|
||||
// 标签开始
|
||||
return [this.text.slice(start + 1, end), end];
|
||||
@ -634,43 +660,43 @@ export class TextContentParser {
|
||||
}
|
||||
|
||||
private parseFillStyle(pointer: number) {
|
||||
const [param, end] = this.getChildableTagParam(pointer + 1);
|
||||
const [param, end] = this.getChildableTagParam(pointer + 2);
|
||||
if (!param) {
|
||||
// 参数为空或没有参数,视为标签结束
|
||||
const color = this.fillStyleStack.pop();
|
||||
if (!color) {
|
||||
logger.warn(54, '\\r', pointer.toString());
|
||||
return pointer;
|
||||
return end;
|
||||
}
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fillStyle = color;
|
||||
return pointer;
|
||||
return end;
|
||||
} else {
|
||||
// 标签开始
|
||||
this.fillStyleStack.push(this.status.fillStyle);
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fillStyle = param;
|
||||
return end;
|
||||
}
|
||||
}
|
||||
|
||||
private parseFontSize(pointer: number) {
|
||||
const [param, end] = this.getChildableTagParam(pointer + 1);
|
||||
const [param, end] = this.getChildableTagParam(pointer + 2);
|
||||
if (!param) {
|
||||
// 参数为空或没有参数,视为标签结束
|
||||
const size = this.fontSizeStack.pop();
|
||||
if (!size) {
|
||||
logger.warn(54, '\\c', pointer.toString());
|
||||
return pointer;
|
||||
return end;
|
||||
}
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fontSize = size;
|
||||
this.font = this.buildFont();
|
||||
return pointer;
|
||||
return end;
|
||||
} else {
|
||||
// 标签开始
|
||||
this.fontSizeStack.push(this.status.fontSize);
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fontSize = parseFloat(param);
|
||||
this.font = this.buildFont();
|
||||
return end;
|
||||
@ -678,22 +704,22 @@ export class TextContentParser {
|
||||
}
|
||||
|
||||
private parseFontFamily(pointer: number) {
|
||||
const [param, end] = this.getChildableTagParam(pointer + 1);
|
||||
const [param, end] = this.getChildableTagParam(pointer + 2);
|
||||
if (!param) {
|
||||
// 参数为空或没有参数,视为标签结束
|
||||
const font = this.fontFamilyStack.pop();
|
||||
if (!font) {
|
||||
logger.warn(54, '\\g', pointer.toString());
|
||||
return pointer;
|
||||
return end;
|
||||
}
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fontFamily = font;
|
||||
this.font = this.buildFont();
|
||||
return pointer;
|
||||
return end;
|
||||
} else {
|
||||
// 标签开始
|
||||
this.fontFamilyStack.push(this.status.fontFamily);
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fontFamily = param;
|
||||
this.font = this.buildFont();
|
||||
return end;
|
||||
@ -701,20 +727,20 @@ export class TextContentParser {
|
||||
}
|
||||
|
||||
private parseFontWeight() {
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fontWeight = this.status.fontWeight > 500 ? 500 : 700;
|
||||
this.font = this.buildFont();
|
||||
}
|
||||
|
||||
private parseFontItalic() {
|
||||
this.addTextRenderable();
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
this.status.fontItalic = !this.status.fontItalic;
|
||||
this.font = this.buildFont();
|
||||
}
|
||||
|
||||
private parseWait(pointer: number) {
|
||||
this.addTextRenderable();
|
||||
const [param, end] = this.getTagParam(pointer);
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
const [param, end] = this.getTagParam(pointer + 2);
|
||||
if (!param) {
|
||||
logger.warn(55, '\\z');
|
||||
return pointer;
|
||||
@ -725,10 +751,10 @@ export class TextContentParser {
|
||||
}
|
||||
|
||||
private parseIcon(pointer: number) {
|
||||
this.addTextRenderable();
|
||||
const [param, end] = this.getTagParam(pointer);
|
||||
if (this.resolved.length > 0) this.addTextRenderable();
|
||||
const [param, end] = this.getTagParam(pointer + 2);
|
||||
if (!param) {
|
||||
logger.warn(55, '\\z');
|
||||
logger.warn(55, '\\i');
|
||||
return pointer;
|
||||
}
|
||||
if (/^\d+$/.test(param)) {
|
||||
@ -741,6 +767,10 @@ export class TextContentParser {
|
||||
this.addIconRenderable(num as AllNumbers);
|
||||
} else {
|
||||
const num = texture.idNumberMap[param as AllIds];
|
||||
if (num === void 0) {
|
||||
logger.warn(59, param);
|
||||
return end;
|
||||
}
|
||||
this.addIconRenderable(num);
|
||||
}
|
||||
}
|
||||
@ -844,9 +874,11 @@ export class TextContentParser {
|
||||
break;
|
||||
case 'd':
|
||||
this.parseFontWeight();
|
||||
pointer++;
|
||||
break;
|
||||
case 'e':
|
||||
this.parseFontItalic();
|
||||
pointer++;
|
||||
break;
|
||||
case 'z':
|
||||
pointer = this.parseWait(pointer);
|
||||
@ -863,7 +895,7 @@ export class TextContentParser {
|
||||
// 表达式
|
||||
pointer++;
|
||||
inExpression = true;
|
||||
expStart = pointer;
|
||||
expStart = pointer + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -902,10 +934,14 @@ export class TextContentParser {
|
||||
// 如果大于猜测,那么算长度
|
||||
const data = this.renderable[this.nowRenderable];
|
||||
const ctx = this.testCanvas.ctx;
|
||||
ctx.font = this.font;
|
||||
ctx.font = data.font;
|
||||
const metrics = ctx.measureText(
|
||||
data.text.slice(this.lineStart, pointer + 1)
|
||||
);
|
||||
const height = this.getHeight(metrics);
|
||||
if (height > this.lineHeight) {
|
||||
this.lineHeight = height;
|
||||
}
|
||||
if (metrics.width < rest) {
|
||||
// 实际宽度小于剩余宽度时,将猜测增益乘以剩余总宽度与当前宽度的比值的若干倍
|
||||
this.guessGain *= (rest / metrics.width) * (1.1 + 1 / length);
|
||||
@ -950,7 +986,7 @@ export class TextContentParser {
|
||||
const data = this.renderable[index];
|
||||
const { wordBreak } = data;
|
||||
const ctx = this.testCanvas.ctx;
|
||||
ctx.font = this.font;
|
||||
ctx.font = data.font;
|
||||
while (true) {
|
||||
const mid = Math.floor((start + end) / 2);
|
||||
if (mid === start) {
|
||||
@ -961,9 +997,10 @@ export class TextContentParser {
|
||||
}
|
||||
const text = data.text.slice(
|
||||
wordBreak[this.bsStart],
|
||||
wordBreak[mid]
|
||||
wordBreak[mid] + 1
|
||||
);
|
||||
const metrics = ctx.measureText(text);
|
||||
height = this.getHeight(metrics);
|
||||
if (metrics.width > width) {
|
||||
end = mid;
|
||||
} else if (metrics.width === width) {
|
||||
@ -974,21 +1011,19 @@ export class TextContentParser {
|
||||
} else {
|
||||
start = mid;
|
||||
}
|
||||
height = this.getHeight(metrics);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查剩余字符能否分行
|
||||
*/
|
||||
private checkRestLine(width: number, guess: number) {
|
||||
private checkRestLine(width: number, guess: number, pointer: number) {
|
||||
if (this.wordBreak.length === 0) return true;
|
||||
const last = this.nowRenderable - 1;
|
||||
if (last === -1) {
|
||||
const now = this.renderable[this.nowRenderable];
|
||||
return this.checkLineWidth(width, guess, now.text.length);
|
||||
if (pointer === -1) {
|
||||
return this.checkLineWidth(width, guess, 0);
|
||||
}
|
||||
const data = this.renderable[last];
|
||||
const isLast = this.renderable.length - 1 === pointer;
|
||||
const data = this.renderable[pointer];
|
||||
const rest = width - this.lineWidth;
|
||||
if (data.type === TextContentType.Text) {
|
||||
const wordBreak = data.wordBreak;
|
||||
@ -996,7 +1031,7 @@ export class TextContentParser {
|
||||
const lastIndex = isNil(lastLine) ? 0 : lastLine;
|
||||
const restText = data.text.slice(lastIndex);
|
||||
const ctx = this.testCanvas.ctx;
|
||||
ctx.font = this.font;
|
||||
ctx.font = data.font;
|
||||
const metrics = ctx.measureText(restText);
|
||||
// 如果剩余内容不能构成完整的行
|
||||
if (metrics.width < rest) {
|
||||
@ -1014,12 +1049,12 @@ export class TextContentParser {
|
||||
this.bsEnd = lastBreak;
|
||||
let maxWidth = rest;
|
||||
while (true) {
|
||||
const index = this.bsLineWidth(maxWidth, last);
|
||||
const index = this.bsLineWidth(maxWidth, pointer);
|
||||
data.splitLines.push(this.wordBreak[index]);
|
||||
this.lineHeights.push(this.lineHeight);
|
||||
this.bsStart = index;
|
||||
const text = data.text.slice(this.wordBreak[index]);
|
||||
if (text.length < guess / 4) {
|
||||
if (!isLast && text.length < guess / 4) {
|
||||
// 如果剩余文字很少,几乎不可能会单独成一行时,直接结束循环
|
||||
this.lastBreakIndex = index;
|
||||
break;
|
||||
@ -1046,9 +1081,15 @@ export class TextContentParser {
|
||||
} else {
|
||||
iconWidth = this.status.fontSize;
|
||||
}
|
||||
this.lineWidth += iconWidth;
|
||||
const iconHeight = iconWidth / aspect;
|
||||
if (iconHeight > this.lineHeight) {
|
||||
this.lineHeight = iconHeight;
|
||||
}
|
||||
if (iconWidth > rest) {
|
||||
data.splitLines.push(0);
|
||||
this.lineHeights.push(this.lineHeight);
|
||||
this.lineWidth = 0;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
@ -1076,8 +1117,6 @@ export class TextContentParser {
|
||||
|
||||
const allBreak = this.wordBreakRule === WordBreak.All;
|
||||
|
||||
// debugger;
|
||||
|
||||
for (let i = 0; i < this.renderable.length; i++) {
|
||||
const data = this.renderable[i];
|
||||
const { wordBreak, fontSize } = data;
|
||||
@ -1086,7 +1125,7 @@ export class TextContentParser {
|
||||
this.wordBreak = wordBreak;
|
||||
|
||||
if (data.type === TextContentType.Icon) {
|
||||
this.checkRestLine(width, guess);
|
||||
this.checkRestLine(width, guess, i - 1);
|
||||
continue;
|
||||
} else if (data.type === TextContentType.Wait) {
|
||||
continue;
|
||||
@ -1128,7 +1167,7 @@ export class TextContentParser {
|
||||
}
|
||||
}
|
||||
|
||||
this.checkRestLine(width, guess);
|
||||
this.checkRestLine(width, guess, i);
|
||||
}
|
||||
|
||||
return {
|
||||
|
Loading…
Reference in New Issue
Block a user