From b1058a677d02938b5e823a03900c7fa3984f160b Mon Sep 17 00:00:00 2001 From: dragoncoder047 <101021094+dragoncoder047@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:09:55 -0400 Subject: [PATCH] fix: rewrite compileStyledText parser (#472) --- examples/audio.js | 2 +- examples/weirdTextTags.js | 24 +++++++++ src/constants.ts | 1 - src/gfx/formatText.ts | 108 +++++++++++++++++--------------------- 4 files changed, 74 insertions(+), 61 deletions(-) create mode 100644 examples/weirdTextTags.js diff --git a/examples/audio.js b/examples/audio.js index d41d79a1..66cc69c2 100644 --- a/examples/audio.js +++ b/examples/audio.js @@ -33,7 +33,7 @@ Time: ${music.time().toFixed(2)} Volume: ${music.volume.toFixed(2)} Speed: ${music.speed.toFixed(2)} -[space] play/pause +\\[space] play/pause [up/down] volume [left/right] speed `.trim(); diff --git a/examples/weirdTextTags.js b/examples/weirdTextTags.js new file mode 100644 index 00000000..36ddfe34 --- /dev/null +++ b/examples/weirdTextTags.js @@ -0,0 +1,24 @@ +kaplay(); + +const txtEl = add([ + text("", { + styles: { + pink: { + color: MAGENTA, + }, + } + }), + pos(100, 100), +]); +const base = "[pink]hello\n[/pink]ohhi\n"; +const txt = "foo\n\\[1]\nbar"; +var i = -1; +const c = loop(0.5, () => { + if (txt[i] === "\\") i++; + i++; + txtEl.text = base + txt.slice(0, i) + "[pink]bye[/pink]"; + if (i > txt.length) { + console.log(txtEl.text); + c.cancel(); + } +}); diff --git a/src/constants.ts b/src/constants.ts index 782ce3f5..eea279d0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -96,7 +96,6 @@ export const COMP_EVENTS = new Set([ "inspect", "drawInspect", ]); -export const MULTI_WORD_RE = /^\w+$/; export const DEF_OFFSCREEN_DIS = 200; // maximum y velocity with body() export const DEF_JUMP_FORCE = 640; diff --git a/src/gfx/formatText.ts b/src/gfx/formatText.ts index ccfc1065..3a7c11fe 100644 --- a/src/gfx/formatText.ts +++ b/src/gfx/formatText.ts @@ -4,7 +4,6 @@ import { DEF_TEXT_CACHE_SIZE, FONT_ATLAS_HEIGHT, FONT_ATLAS_WIDTH, - MULTI_WORD_RE, } from "../constants"; import { fontCacheC2d, fontCacheCanvas, gfx } from "../kaplay"; import { Color } from "../math/color"; @@ -45,65 +44,56 @@ export function compileStyledText(text: string): { } { const charStyleMap = {} as Record; let renderText = ""; - let styleStack: [string, number][] = []; - let lastIndex = 0; - let skipCount = 0; + let styleStack: string[] = []; - for (let i = 0; i < text.length; i++) { - if (i !== lastIndex + 1) skipCount += i - lastIndex; - lastIndex = i; - - if (text[i] === "\\" && text[i + 1] === "[") continue; - - if ((i === 0 || text[i - 1] !== "\\") && text[i] === "[") { - const start = i; - - i++; - - let isClosing = text[i] === "/"; - let style = ""; - - if (isClosing) i++; - - while (i < text.length && text[i] !== "]") { - style += text[i++]; - } - - if ( - !MULTI_WORD_RE.test(style) - || i >= text.length - || text[i] !== "]" - || (isClosing - && (styleStack.length === 0 - || styleStack[styleStack.length - 1][0] !== style)) - ) { - i = start; - } - else { - if (!isClosing) styleStack.push([style, start]); - else styleStack.pop(); + const emit = (ch: string) => { + if (styleStack.length > 0) + charStyleMap[renderText.length] = styleStack.slice(); + renderText += ch; + }; + while (text !== "") { + if (text[0] === "\\") { + if (text.length === 1) + throw new Error("Styled text error: \\ at end of string"); + emit(text[1]); + text = text.slice(2); + continue; + } + if (text[0] === "[") { + const execResult = /^\[(\/)?(\w+?)\]/.exec(text); + if (!execResult) { + // xxx: should throw an error here? + emit(text[0]); + text = text.slice(1); continue; } + const [m, e, gn] = execResult; + if (e !== undefined) { + const x = styleStack.pop(); + if (x !== gn) { + if (x !== undefined) + throw new Error( + "Styled text error: mismatched tags. " + + `Expected [/${x}], got [/${gn}]`); + else throw new Error( + `Styled text error: stray end tag [/${gn}]`) + } + } else styleStack.push(gn); + text = text.slice(m.length); + continue; } - - renderText += text[i]; - if (styleStack.length > 0) { - charStyleMap[i - skipCount] = styleStack.map(([name]) => name); - } + emit(text[0]); + text = text.slice(1); } - if (styleStack.length > 0) { - while (styleStack.length > 0) { - const [_, start] = styleStack.pop()!; - text = text.substring(0, start) + "\\" + text.substring(start); - } - - return compileStyledText(text); - } + if (styleStack.length > 0) + throw new Error( + `Styled text error: unclosed tags ${styleStack}` + ); return { - charStyleMap: charStyleMap, + charStyleMap, text: renderText, }; } @@ -138,14 +128,14 @@ export function formatText(opt: DrawTextOpt): FormattedText { outline: Outline | null; filter: TexFilter; } = font instanceof FontData - ? { - outline: font.outline, - filter: font.filter, - } - : { - outline: null, - filter: DEF_FONT_FILTER, - }; + ? { + outline: font.outline, + filter: font.filter, + } + : { + outline: null, + filter: DEF_FONT_FILTER, + }; // TODO: customizable font tex filter const atlas: FontAtlas = fontAtlases[fontName] ?? {