@pie-players/pie-assessment-toolkit 0.3.47 → 0.3.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attempt/AssessmentSession.js +2 -1
- package/dist/attempt/AssessmentSession.js.map +1 -1
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +12 -8
- package/dist/attempt/adapters/activity-to-test-attempt-session.js.map +1 -1
- package/dist/components/ItemToolBar.custom-element.js +56 -17
- package/dist/components/PieAssessmentToolkit.custom-element.js +84 -29
- package/dist/components/SectionToolBar.custom-element.js +55 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/policy/core/pnp-policy-inputs.js +1 -2
- package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
- package/dist/policy/core/provenance.js.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.js +1 -1
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
- package/dist/runtime/core/cohort.js.map +1 -1
- package/dist/runtime/core/engine-input.js.map +1 -1
- package/dist/runtime/core/engine-readiness.js.map +1 -1
- package/dist/runtime/core/engine-resolver.js.map +1 -1
- package/dist/runtime/core/engine-transition.js +1 -3
- package/dist/runtime/core/engine-transition.js.map +1 -1
- package/dist/runtime/registration-events.js.map +1 -1
- package/dist/runtime/session-event-emitter-policy.js.map +1 -1
- package/dist/services/HighlightCoordinator.js +2 -1
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/I18nService.d.ts +1 -1
- package/dist/services/I18nService.js.map +1 -1
- package/dist/services/SSMLExtractor.js.map +1 -1
- package/dist/services/TTSService.js +5 -2
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ThemeProvider.js.map +1 -1
- package/dist/services/ToolRegistry.d.ts +1 -1
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.js +21 -11
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/createDefaultToolRegistry.js +4 -2
- package/dist/services/createDefaultToolRegistry.js.map +1 -1
- package/dist/services/framework-error-bus.js.map +1 -1
- package/dist/services/framework-error.js.map +1 -1
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/section-controller-types.js.map +1 -1
- package/dist/services/tool-config-defaults.js +7 -1
- package/dist/services/tool-config-defaults.js.map +1 -1
- package/dist/services/tool-config-validation.js +3 -1
- package/dist/services/tool-config-validation.js.map +1 -1
- package/dist/services/tool-instance-id.js.map +1 -1
- package/dist/services/tool-providers/DesmosToolProvider.js.map +1 -1
- package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
- package/dist/services/toolbar-items.js.map +1 -1
- package/dist/services/tools-config-normalizer.js +1 -2
- package/dist/services/tools-config-normalizer.js.map +1 -1
- package/dist/services/tts/browser-provider.js +60 -18
- package/dist/services/tts/browser-provider.js.map +1 -1
- package/dist/services/tts/catalog-span-alignment.js +8 -3
- package/dist/services/tts/catalog-span-alignment.js.map +1 -1
- package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -1
- package/dist/services/tts/generated-speech/index.d.ts +1 -1
- package/dist/services/tts/generated-speech/index.js +1 -1
- package/dist/services/tts/generated-speech/index.js.map +1 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js +3 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -1
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -1
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -1
- package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -1
- package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -1
- package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -1
- package/dist/services/tts/math-aware-text-processing.js +6 -3
- package/dist/services/tts/math-aware-text-processing.js.map +1 -1
- package/dist/services/tts/math-speech.js +3 -1
- package/dist/services/tts/math-speech.js.map +1 -1
- package/dist/services/tts/visual-line-ranges.js.map +1 -1
- package/dist/services/tts-runtime-config.js +6 -2
- package/dist/services/tts-runtime-config.js.map +1 -1
- package/dist/tools/calculators/desmos-provider.js.map +1 -1
- package/dist/tools/default-tool-module-loaders.js.map +1 -1
- package/dist/tools/registrations/accessibility-tools.js.map +1 -1
- package/dist/tools/registrations/calculator.js +1 -1
- package/dist/tools/registrations/calculator.js.map +1 -1
- package/dist/tools/registrations/interaction-tools.js.map +1 -1
- package/dist/tools/registrations/measurement-tools.js.map +1 -1
- package/dist/tools/registrations/subject-specific-tools.js +1 -1
- package/dist/tools/registrations/subject-specific-tools.js.map +1 -1
- package/dist/tools/registrations/tts.js +4 -2
- package/dist/tools/registrations/tts.js.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/variant-resolver.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemble-plan.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/assemble-plan.ts"],"names":[],"mappings":"AACA,OAAO,EACN,2BAA2B,GAG3B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,gBAAgB,IAAI,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAmBrF,MAAM,yBAAyB,GAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CACxE,2BAA2B,CAAC,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AAE/C,MAAM,eAAe,GAAG,CAAC,QAAiB,EAAU,EAAE,CACrD,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC;AAExD,MAAM,yBAAyB,GAAG,IAAI,GAAG,CAAC;IACzC,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,OAAO;IACP,QAAQ;CACR,CAAC,CAAC;AAEH,MAAM,0BAA0B,GAAG,IAAI,GAAG,CAAC;IAC1C,SAAS;IACT,UAAU;IACV,QAAQ;IACR,OAAO;CACP,CAAC,CAAC;AAEH,MAAM,wBAAwB,GAAG,
|
|
1
|
+
{"version":3,"file":"assemble-plan.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/assemble-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MathAwareSpeechChunk } from \"../math-aware-text-processing.js\";\nimport {\n\tresolveMathSpeechFromChunks,\n\ttype ResolvedMathSpeech,\n\ttype SREMathSpeechOptions,\n} from \"../math-speech.js\";\nimport { normalizeTextForSpeech } from \"../text-processing.js\";\nimport { segmentSentences as segmentTextToSentences } from \"../text-segmentation.js\";\nimport type { AssembledSegment, AssembledSpeech } from \"./types.js\";\n\ntype MathChunk = Extract<MathAwareSpeechChunk, { type: \"math\" }>;\n\n/**\n * Resolves spoken text for a single math chunk. Injectable so the runtime can\n * supply a memoized resolver (deterministic caching) and tests can stub SRE.\n * Mirrors `resolveMathSpeechFromChunks([chunk], options)`.\n */\nexport type MathSpeechResolver = (\n\tchunk: MathChunk,\n\toptions: {\n\t\tlanguage?: string;\n\t\tproduceSsml?: boolean;\n\t\tmathSpeech?: SREMathSpeechOptions;\n\t},\n) => Promise<ResolvedMathSpeech>;\n\nconst defaultMathSpeechResolver: MathSpeechResolver = (chunk, options) =>\n\tresolveMathSpeechFromChunks([chunk], options);\n\nconst normalizeLocale = (language?: string): string =>\n\t(language || \"en\").split(\"-\")[0].toLowerCase() || \"en\";\n\nconst PRESERVE_PROSE_CHUNK_TAGS = new Set([\n\t\"H1\",\n\t\"H2\",\n\t\"H3\",\n\t\"H4\",\n\t\"H5\",\n\t\"H6\",\n\t\"LI\",\n\t\"LABEL\",\n\t\"BUTTON\",\n]);\n\nconst PRESERVE_PROSE_CHUNK_ROLES = new Set([\n\t\"heading\",\n\t\"listitem\",\n\t\"option\",\n\t\"radio\",\n]);\n\nconst shouldPreserveProseChunk = (chunk: MathAwareSpeechChunk): boolean => {\n\tif (chunk.type !== \"text\" || !chunk.sourceElement) return false;\n\tconst tagName = chunk.sourceElement.tagName.toUpperCase();\n\tconst role = (chunk.sourceElement.getAttribute(\"role\") || \"\").toLowerCase();\n\treturn (\n\t\tPRESERVE_PROSE_CHUNK_TAGS.has(tagName) ||\n\t\tPRESERVE_PROSE_CHUNK_ROLES.has(role)\n\t);\n};\n\n/**\n * Assemble an anchor-free speech plan from already-collected math-aware chunks.\n *\n * PURE: no DOM walking, no app-service imports. Preserves the spoken-text and\n * visible-span bookkeeping from the inline generated path, so the runtime output\n * is stable once serialized to plain text.\n */\nexport const assembleGeneratedSpeech = async (args: {\n\tchunks: ReadonlyArray<MathAwareSpeechChunk>;\n\tvisibleText: string;\n\tlanguage?: string;\n\tmathSpeech?: SREMathSpeechOptions;\n\t/** Request SRE SSML per math equation (used by the SSML playback format). */\n\tproduceSsml?: boolean;\n\tresolveMathSpeech?: MathSpeechResolver;\n}): Promise<AssembledSpeech> => {\n\tconst resolveMathSpeech = args.resolveMathSpeech ?? defaultMathSpeechResolver;\n\tconst { visibleText } = args;\n\tconst segments: AssembledSegment[] = [];\n\n\t// Cursor-based span tracking:\n\t// locate each chunk's normalized text within the aggregate visible text,\n\t// advancing a cursor so math fallbacks reserve their span even though only\n\t// prose spans are sliced for highlighting.\n\tlet visibleCursor = 0;\n\tconst nextVisibleSpan = (text: string): { start: number; end: number } => {\n\t\tconst normalized = normalizeTextForSpeech(text);\n\t\tconst found = visibleText.indexOf(normalized, visibleCursor);\n\t\tconst start = found >= 0 ? found : visibleCursor;\n\t\tconst end = start + normalized.length;\n\t\tvisibleCursor = end;\n\t\treturn { start, end };\n\t};\n\n\tconst speechParts: string[] = [];\n\n\tfor (let index = 0; index < args.chunks.length; index++) {\n\t\tconst chunk = args.chunks[index];\n\t\tif (chunk.type === \"text\") {\n\t\t\tconst proseSegments = shouldPreserveProseChunk(chunk)\n\t\t\t\t? [{ text: chunk.text, offset: 0 }]\n\t\t\t\t: segmentTextToSentences(chunk.text, {\n\t\t\t\t\t\tlocale: args.language,\n\t\t\t\t\t});\n\t\t\tfor (const prose of proseSegments) {\n\t\t\t\tconst text = normalizeTextForSpeech(prose.text);\n\t\t\t\tif (!text) continue;\n\t\t\t\tconst visibleSpan = nextVisibleSpan(text);\n\t\t\t\tsegments.push({\n\t\t\t\t\tsourceChunkIndex: index,\n\t\t\t\t\tsegment: {\n\t\t\t\t\t\tkind: \"prose\",\n\t\t\t\t\t\tspokenText: text,\n\t\t\t\t\t\tvisibleText: text,\n\t\t\t\t\t\tvisibleSpan,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tspeechParts.push(text);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst visibleSpan = nextVisibleSpan(chunk.fallbackText);\n\t\tconst generated = await resolveMathSpeech(chunk, {\n\t\t\tlanguage: args.language,\n\t\t\tproduceSsml: args.produceSsml,\n\t\t\tmathSpeech: args.mathSpeech,\n\t\t});\n\t\tconst usedFallback = !generated.speechText;\n\t\tconst spokenText = generated.speechText || chunk.fallbackText;\n\t\tsegments.push({\n\t\t\tsourceChunkIndex: index,\n\t\t\tsegment: {\n\t\t\t\tkind: \"math\",\n\t\t\t\tspokenText,\n\t\t\t\tmathml: chunk.mathml,\n\t\t\t\tfallbackText: chunk.fallbackText,\n\t\t\t\tvisibleSpan,\n\t\t\t\tusedFallback,\n\t\t\t\tssml: generated.ssml,\n\t\t\t},\n\t\t});\n\t\tspeechParts.push(spokenText);\n\t}\n\n\treturn {\n\t\tsegments,\n\t\tlocale: normalizeLocale(args.language),\n\t\tvisibleText,\n\t\tplainSpeechText: normalizeTextForSpeech(speechParts.join(\" \")),\n\t};\n};\n"]}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* chunks for `TTSService`.
|
|
11
11
|
*/
|
|
12
12
|
export * from "./types.js";
|
|
13
|
-
export { assembleGeneratedSpeech, type MathSpeechResolver } from "./assemble-plan.js";
|
|
13
|
+
export { assembleGeneratedSpeech, type MathSpeechResolver, } from "./assemble-plan.js";
|
|
14
14
|
export { createMemoizedMathSpeechResolver, type MathSpeechCacheOptions, } from "./math-speech-cache.js";
|
|
15
15
|
export { buildGeneratedSpeechFromRoot, type BuildGeneratedSpeechResult, } from "./dom/build-dom-plan.js";
|
|
16
16
|
export { planToCompositionChunkInputs } from "./dom/to-chunk-inputs.js";
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* chunks for `TTSService`.
|
|
11
11
|
*/
|
|
12
12
|
export * from "./types.js";
|
|
13
|
-
export { assembleGeneratedSpeech } from "./assemble-plan.js";
|
|
13
|
+
export { assembleGeneratedSpeech, } from "./assemble-plan.js";
|
|
14
14
|
export { createMemoizedMathSpeechResolver, } from "./math-speech-cache.js";
|
|
15
15
|
export { buildGeneratedSpeechFromRoot, } from "./dom/build-dom-plan.js";
|
|
16
16
|
export { planToCompositionChunkInputs } from "./dom/to-chunk-inputs.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,cAAc,YAAY,CAAC;AAC3B,OAAO,EACN,uBAAuB,GAEvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,gCAAgC,GAEhC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,4BAA4B,GAE5B,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,4BAA4B,EAAE,MAAM,0BAA0B,CAAC","sourcesContent":["/**\n * Generated-speech module (PIE-623): runtime speech composition for math items\n * that ship without authored SSML / `accessibilityCatalogs`.\n *\n * Layering:\n * - Pure core (`./assemble-plan`, `./math-speech-cache`, `./types`): no DOM\n * walking, no runtime/Svelte imports. Enforced by\n * `scripts/check-speech-composition-purity.mjs`.\n * - DOM adapter (`./dom/*`): binds the plan to live DOM and emits playback\n * chunks for `TTSService`.\n */\n\nexport * from \"./types.js\";\nexport {\n\tassembleGeneratedSpeech,\n\ttype MathSpeechResolver,\n} from \"./assemble-plan.js\";\nexport {\n\tcreateMemoizedMathSpeechResolver,\n\ttype MathSpeechCacheOptions,\n} from \"./math-speech-cache.js\";\nexport {\n\tbuildGeneratedSpeechFromRoot,\n\ttype BuildGeneratedSpeechResult,\n} from \"./dom/build-dom-plan.js\";\nexport { planToCompositionChunkInputs } from \"./dom/to-chunk-inputs.js\";\nexport type { DomAnchor, GeneratedSpeechChunk } from \"./dom/types.js\";\n"]}
|
|
@@ -25,7 +25,9 @@ const stableStringify = (value) => {
|
|
|
25
25
|
if (typeof value === "bigint")
|
|
26
26
|
return `bigint:${value.toString()}`;
|
|
27
27
|
if (typeof value === "number") {
|
|
28
|
-
return Number.isFinite(value)
|
|
28
|
+
return Number.isFinite(value)
|
|
29
|
+
? `number:${value}`
|
|
30
|
+
: `number:${String(value)}`;
|
|
29
31
|
}
|
|
30
32
|
return `${typeof value}:${JSON.stringify(value)}`;
|
|
31
33
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"math-speech-cache.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/math-speech-cache.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,6BAA6B,EAC7B,2BAA2B,GAG3B,MAAM,mBAAmB,CAAC;AAG3B,MAAM,eAAe,GAAG,CAAC,QAAiB,EAAU,EAAE,CACrD,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC;AAExD,0EAA0E;AAC1E,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG,QAAQ,CAAC;AAEnC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEhC,MAAM,eAAe,GAAG,CAAC,KAAc,EAAU,EAAE;IAClD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,UAAU,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC1D,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,OAAO,WAAW,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;aAChE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;aACzE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAChB,CAAC;IACD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,WAAW,CAAC;IACvC,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO,YAAY,CAAC;IACtD,IAAI,OAAO,KAAK,KAAK,UAAU;
|
|
1
|
+
{"version":3,"file":"math-speech-cache.js","sourceRoot":"","sources":["../../../../src/services/tts/generated-speech/math-speech-cache.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tnormalizeSREMathSpeechOptions,\n\tresolveMathSpeechFromChunks,\n\ttype ResolvedMathSpeech,\n\ttype SREMathSpeechOptions,\n} from \"../math-speech.js\";\nimport type { MathSpeechResolver } from \"./assemble-plan.js\";\n\nconst normalizeLocale = (language?: string): string =>\n\t(language || \"en\").split(\"-\")[0].toLowerCase() || \"en\";\n\n// Bump when speech-rule-engine output semantics change in a way that must\n// invalidate cached spoken text (e.g. an SRE upgrade with different phrasing).\nconst SRE_CACHE_VERSION = \"sre-v1\";\n\nconst DEFAULT_MAX_ENTRIES = 256;\n\nconst stableStringify = (value: unknown): string => {\n\tif (Array.isArray(value)) {\n\t\treturn `array:[${value.map(stableStringify).join(\",\")}]`;\n\t}\n\tif (value && typeof value === \"object\") {\n\t\treturn `object:{${Object.entries(value as Record<string, unknown>)\n\t\t\t.sort(([left], [right]) => left.localeCompare(right))\n\t\t\t.map(([key, entry]) => `${JSON.stringify(key)}:${stableStringify(entry)}`)\n\t\t\t.join(\",\")}}`;\n\t}\n\tif (value === null) return \"null:null\";\n\tif (typeof value === \"undefined\") return \"undefined:\";\n\tif (typeof value === \"function\")\n\t\treturn `function:${value.name || \"anonymous\"}`;\n\tif (typeof value === \"symbol\") return `symbol:${String(value)}`;\n\tif (typeof value === \"bigint\") return `bigint:${value.toString()}`;\n\tif (typeof value === \"number\") {\n\t\treturn Number.isFinite(value)\n\t\t\t? `number:${value}`\n\t\t\t: `number:${String(value)}`;\n\t}\n\treturn `${typeof value}:${JSON.stringify(value)}`;\n};\n\nconst cacheKeyFor = (\n\tlanguage: string | undefined,\n\tproduceSsml: boolean,\n\tmathSpeech: SREMathSpeechOptions | undefined,\n\tmathml: string,\n): string =>\n\t`${SRE_CACHE_VERSION}\\u0000${normalizeLocale(language)}\\u0000${\n\t\tproduceSsml ? \"ssml\" : \"plain\"\n\t}\\u0000${stableStringify(mathSpeech ?? null)}\\u0000${mathml}`;\n\nexport interface MathSpeechCacheOptions {\n\tmaxEntries?: number;\n\t/** Underlying resolver; defaults to the real SRE call. Injectable for tests. */\n\tresolve?: MathSpeechResolver;\n}\n\n/**\n * Build a memoized {@link MathSpeechResolver}.\n *\n * Caches only DOM-free spoken-text results keyed by\n * `(SRE version, locale, output mode, SRE mathSpeech options, MathML source)`.\n * Never caches DOM nodes or maps. Bounded by a simple LRU so long sessions do\n * not grow unbounded.\n */\nexport const createMemoizedMathSpeechResolver = (\n\toptions: MathSpeechCacheOptions = {},\n): MathSpeechResolver => {\n\tconst maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_ENTRIES);\n\tconst resolve =\n\t\toptions.resolve ??\n\t\t((chunk, opts) => resolveMathSpeechFromChunks([chunk], opts));\n\tconst cache = new Map<string, ResolvedMathSpeech>();\n\tconst setCached = (key: string, value: ResolvedMathSpeech): void => {\n\t\tcache.delete(key);\n\t\tcache.set(key, value);\n\t\twhile (cache.size > maxEntries) {\n\t\t\tconst oldest = cache.keys().next().value;\n\t\t\tif (oldest !== undefined) cache.delete(oldest);\n\t\t}\n\t};\n\n\treturn async (chunk, opts) => {\n\t\tconst mathSpeech = normalizeSREMathSpeechOptions(opts.mathSpeech);\n\t\tconst produceSsml = opts.produceSsml === true;\n\t\tconst key = cacheKeyFor(\n\t\t\topts.language,\n\t\t\tproduceSsml,\n\t\t\tmathSpeech,\n\t\t\tchunk.mathml,\n\t\t);\n\t\tconst cached = cache.get(key);\n\t\tif (cached) {\n\t\t\t// Refresh LRU recency.\n\t\t\tcache.delete(key);\n\t\t\tcache.set(key, cached);\n\t\t\treturn cached;\n\t\t}\n\t\tconst resolved = await resolve(chunk, { ...opts, mathSpeech });\n\t\tif (produceSsml && resolved.ssml && maxEntries > 1) {\n\t\t\tsetCached(cacheKeyFor(opts.language, false, mathSpeech, chunk.mathml), {\n\t\t\t\tspeechText: resolved.speechText,\n\t\t\t\tusedMathSpeech: resolved.usedMathSpeech,\n\t\t\t\tusedFallback: resolved.usedFallback,\n\t\t\t});\n\t\t}\n\t\tsetCached(key, resolved);\n\t\treturn resolved;\n\t};\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boundary-normalizer.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/boundary-normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EACN,4BAA4B,EAC5B,oBAAoB,GACpB,MAAM,uCAAuC,CAAC;AAO/C,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAAiB,EAAE;IAC7D,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,CACN,oBAAoB,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,IAAI,IAAI,CACxE,CAAC;AACH,CAAC,CAAC;AAEF,uEAAuE;AACvE,4EAA4E;AAC5E,8EAA8E;AAC9E,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,4EAA4E;AAC5E,MAAM,oBAAoB,GAAG,CAC5B,KAAwB,EACxB,KAAuB,
|
|
1
|
+
{"version":3,"file":"boundary-normalizer.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/boundary-normalizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,EACN,4BAA4B,EAC5B,oBAAoB,GACpB,MAAM,uCAAuC,CAAC;AAO/C,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAAiB,EAAE;IAC7D,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,CACN,oBAAoB,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,UAAU,IAAI,IAAI,CACxE,CAAC;AACH,CAAC,CAAC;AAEF,uEAAuE;AACvE,4EAA4E;AAC5E,8EAA8E;AAC9E,4EAA4E;AAC5E,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,sEAAsE;AACtE,4EAA4E;AAC5E,MAAM,oBAAoB,GAAG,CAC5B,KAAwB,EACxB,KAAuB,EAItB,EAAE;IACH,MAAM,YAAY,GAAG,oBAAoB,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAC5E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,CAAC,UAAU,EAAE,CAAC;IAChE,CAAC;IACD,MAAM,QAAQ,GAAG,4BAA4B,CAAC;QAC7C,YAAY;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,YAAY,EAAE,KAAK,CAAC,IAAI;KACxB,CAAC,CAAC;IACH,OAAO;QACN,QAAQ,EAAE,QAAQ;YACjB,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;YACpD,CAAC,CAAC,IAAI;QACP,UAAU,EAAE,YAAY,CAAC,UAAU;KACnC,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC5B,UAAkB,EAClB,KAAa,EACb,cAA6B,EACnB,EAAE;IACZ,IAAI,CAAC,cAAc;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,KAAK,GAAG,oBAAoB,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAC7D,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,SAAS,CAAC,GAAG,CAChE,CAAC;IACF,OAAO,KAAK,EAAE,UAAU,KAAK,cAAc,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACrC,KAAwB,EACxB,KAAuB,EACG,EAAE;IAC5B,MAAM,cAAc,GAAG,qBAAqB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QAChC,OAAO;YACN,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,cAAc;YACd,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,CAAC;YACb,MAAM,EAAE,iDAAiD;SACzD,CAAC;IACH,CAAC;IACD,IACC,KAAK,CAAC,mBAAmB,KAAK,SAAS;QACvC,KAAK,CAAC,WAAW,KAAK,aAAa,EAClC,CAAC;QACF,OAAO;YACN,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,cAAc;YACd,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,CAAC;YACb,MAAM,EAAE,sCAAsC;SAC9C,CAAC;IACH,CAAC;IACD,IAAI,QAAkD,CAAC;IACvD,IAAI,UAAkB,CAAC;IACvB,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC5B,QAAQ,GAAG,2BAA2B,CACrC,KAAK,CAAC,gBAAgB,EACtB,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,IAAI,CACV,CAAC;QACF,UAAU,GAAG,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC;IAChD,CAAC;SAAM,CAAC;QACP,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACpD,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;QAC7B,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;IAClC,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO;YACN,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,cAAc;YACd,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,CAAC;YACb,MAAM,EAAE,qDAAqD;SAC7D,CAAC;IACH,CAAC;IACD,IAAI,CAAC,oBAAoB,CAAC,UAAU,EAAE,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC,EAAE,CAAC;QACvE,OAAO;YACN,OAAO,EAAE,KAAK,CAAC,EAAE;YACjB,cAAc;YACd,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,IAAI;YACpB,UAAU,EAAE,CAAC;YACb,MAAM,EAAE,sDAAsD;SAC9D,CAAC;IACH,CAAC;IACD,OAAO;QACN,OAAO,EAAE,KAAK,CAAC,EAAE;QACjB,cAAc;QACd,gBAAgB,EAAE,QAAQ,CAAC,KAAK;QAChC,cAAc,EAAE,QAAQ,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM;QAChD,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QACrC,MAAM,EAAE,KAAK,CAAC,gBAAgB;YAC7B,CAAC,CAAC,oCAAoC;YACtC,CAAC,CAAC,sCAAsC;KACzC,CAAC;AACH,CAAC,CAAC","sourcesContent":["import { resolveSpokenBoundaryOffset } from \"../catalog-span-alignment.js\";\nimport {\n\tresolveBoundaryToSpeechToken,\n\ttokenizeSpeechSource,\n} from \"../math-alignment/speech-tokenizer.js\";\nimport type {\n\tNormalizedBoundaryEvent,\n\tTTSBoundaryEvent,\n\tTTSHighlightChunk,\n} from \"./types.js\";\n\nconst normalizeBoundaryWord = (word: string): string | null => {\n\tif (!word || /^<[^>]+>$/.test(word.trim())) return null;\n\treturn (\n\t\ttokenizeSpeechSource({ speechText: word }).tokens[0]?.normalized || null\n\t);\n};\n\n// Resolve a provider boundary for a chunk that carries no catalog span\n// alignment — notably the generated-SSML math path, whose `speechText` is a\n// raw `<speak>` document. The provider's offset is an index into `speechText`\n// exactly as it was sent (which may include SSML tags), so we tokenize that\n// same string and let the shared speech tokenizer translate the offset into the\n// extracted spoken text. Returns the boundary in spoken-text space alongside\n// that spoken text, so the downstream word/offset checks share one coordinate\n// system. The previous \"direct\" path treated the raw-SSML offset as a\n// spoken-text offset and rejected every math boundary (PIE-623 regression).\nconst speechSourceBoundary = (\n\tchunk: TTSHighlightChunk,\n\tevent: TTSBoundaryEvent,\n): {\n\tboundary: { start: number; length: number } | null;\n\tspokenText: string;\n} => {\n\tconst tokenization = tokenizeSpeechSource({ speechText: chunk.speechText });\n\tif (!Number.isFinite(event.position)) {\n\t\treturn { boundary: null, spokenText: tokenization.spokenText };\n\t}\n\tconst resolved = resolveBoundaryToSpeechToken({\n\t\ttokenization,\n\t\tposition: event.position,\n\t\tlength: event.length,\n\t\tboundaryWord: event.word,\n\t});\n\treturn {\n\t\tboundary: resolved\n\t\t\t? { start: resolved.start, length: resolved.length }\n\t\t\t: null,\n\t\tspokenText: tokenization.spokenText,\n\t};\n};\n\nconst tokenAtOffsetMatches = (\n\tspeechText: string,\n\tstart: number,\n\tnormalizedWord: string | null,\n): boolean => {\n\tif (!normalizedWord) return true;\n\tconst token = tokenizeSpeechSource({ speechText }).tokens.find(\n\t\t(candidate) => candidate.start <= start && start < candidate.end,\n\t);\n\treturn token?.normalized === normalizedWord;\n};\n\nexport const normalizeBoundaryEvent = (\n\tchunk: TTSHighlightChunk,\n\tevent: TTSBoundaryEvent,\n): NormalizedBoundaryEvent => {\n\tconst normalizedWord = normalizeBoundaryWord(event.word);\n\tif (chunk.id !== event.chunkId) {\n\t\treturn {\n\t\t\tchunkId: event.chunkId,\n\t\t\tnormalizedWord,\n\t\t\tchunkSpokenStart: null,\n\t\t\tchunkSpokenEnd: null,\n\t\t\tconfidence: 0,\n\t\t\treason: \"boundary chunk id did not match highlight chunk\",\n\t\t};\n\t}\n\tif (\n\t\tevent.providerOffsetSpace === \"unknown\" ||\n\t\tchunk.offsetSpace === \"unsupported\"\n\t) {\n\t\treturn {\n\t\t\tchunkId: chunk.id,\n\t\t\tnormalizedWord,\n\t\t\tchunkSpokenStart: null,\n\t\t\tchunkSpokenEnd: null,\n\t\t\tconfidence: 0,\n\t\t\treason: \"boundary offset space is unsupported\",\n\t\t};\n\t}\n\tlet boundary: { start: number; length: number } | null;\n\tlet spokenText: string;\n\tif (chunk.catalogAlignment) {\n\t\tboundary = resolveSpokenBoundaryOffset(\n\t\t\tchunk.catalogAlignment,\n\t\t\tevent.position,\n\t\t\tevent.length,\n\t\t\tevent.word,\n\t\t);\n\t\tspokenText = chunk.catalogAlignment.spokenText;\n\t} else {\n\t\tconst resolved = speechSourceBoundary(chunk, event);\n\t\tboundary = resolved.boundary;\n\t\tspokenText = resolved.spokenText;\n\t}\n\tif (!boundary) {\n\t\treturn {\n\t\t\tchunkId: chunk.id,\n\t\t\tnormalizedWord,\n\t\t\tchunkSpokenStart: null,\n\t\t\tchunkSpokenEnd: null,\n\t\t\tconfidence: 0,\n\t\t\treason: \"could not resolve provider boundary to chunk speech\",\n\t\t};\n\t}\n\tif (!tokenAtOffsetMatches(spokenText, boundary.start, normalizedWord)) {\n\t\treturn {\n\t\t\tchunkId: chunk.id,\n\t\t\tnormalizedWord,\n\t\t\tchunkSpokenStart: null,\n\t\t\tchunkSpokenEnd: null,\n\t\t\tconfidence: 0,\n\t\t\treason: \"could not resolve provider boundary to matching word\",\n\t\t};\n\t}\n\treturn {\n\t\tchunkId: chunk.id,\n\t\tnormalizedWord,\n\t\tchunkSpokenStart: boundary.start,\n\t\tchunkSpokenEnd: boundary.start + boundary.length,\n\t\tconfidence: normalizedWord ? 1 : 0.75,\n\t\treason: chunk.catalogAlignment\n\t\t\t? \"resolved through catalog alignment\"\n\t\t\t: \"resolved through speech tokenization\",\n\t};\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-highlight-plan.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/create-highlight-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBoundaryEvent } from \"./boundary-normalizer.js\";\nimport { createHighlightDecision } from \"./highlight-policy.js\";\nimport { resolveMathBoundaryTarget } from \"./math-semantic-resolver.js\";\nimport { resolveProseBoundaryTarget } from \"./prose-boundary-resolver.js\";\nimport {\n\tclassifyMathHighlightCapability,\n\tcreateRenderedMathTargetResolver,\n\ttype MathHighlightCapability,\n\ttype RenderedMathTargetResolver,\n} from \"./rendered-math-target-resolver.js\";\nimport type {\n\tHighlightDecision,\n\tRenderableHighlightTarget,\n\tTTSBoundaryEvent,\n\tTTSHighlightChunk,\n\tTTSHighlightPlan,\n} from \"./types.js\";\n\nexport interface CreateTTSHighlightPlanArgs {\n\tchunks: TTSHighlightChunk[];\n\t// When false, math is never broken into per-token highlights: every equation\n\t// is painted as a single block — the same expression/region fallback used\n\t// whenever token alignment is not confident. Prose word tracking is\n\t// unaffected. Defaults to true.\n\tmathTokenHighlighting?: boolean;\n}\n\ninterface PlannedChunk {\n\tchunk: TTSHighlightChunk;\n\tregionTarget: RenderableHighlightTarget | null;\n\trenderedMathTargetResolver: RenderedMathTargetResolver;\n\t// Capability decided once per equation (keyed by its source math element).\n\t// Sticky for the whole chunk so the same equation never switches between\n\t// token tracking and whole-expression highlighting mid-read.\n\tmathCapabilityByElement: Map<Element, MathHighlightCapability>;\n\t// The last precise token painted for each token-mode equation. Lets a gap\n\t// (SSML break / pause / unmappable boundary) hold the last token instead of\n\t// flashing the whole expression.\n\theldTokenByElement: Map<Element, RenderableHighlightTarget>;\n}\n\nconst regionTargetFor = (\n\tchunk: TTSHighlightChunk,\n): RenderableHighlightTarget | null =>\n\tchunk.regionRange\n\t\t? {\n\t\t\t\ttype: \"range\",\n\t\t\t\tquality: \"region\",\n\t\t\t\trange: chunk.regionRange,\n\t\t\t}\n\t\t: chunk.regionElement\n\t\t\t? {\n\t\t\t\t\ttype: \"element\",\n\t\t\t\t\tquality: \"region\",\n\t\t\t\t\telement: chunk.regionElement,\n\t\t\t\t}\n\t\t\t: null;\n\nconst expressionTargetFor = (\n\tplanned: PlannedChunk,\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"] = planned.chunk\n\t\t.mathAlignments,\n): RenderableHighlightTarget | null => {\n\tif (candidates.length !== 1) return null;\n\tconst fallback = candidates[0]?.alignment.result.fallbackTarget;\n\treturn fallback ? planned.renderedMathTargetResolver(fallback) : null;\n};\n\nconst mathCandidatesForTarget = (\n\ttarget: RenderableHighlightTarget | null,\n\tchunk: TTSHighlightChunk,\n): TTSHighlightChunk[\"mathAlignments\"] => {\n\tif (!target) return chunk.mathAlignments;\n\treturn chunk.mathAlignments.filter(({ element }) => {\n\t\ttry {\n\t\t\tif (target.type === \"range\") return target.range.intersectsNode(element);\n\t\t\tif (target.type === \"element\") {\n\t\t\t\treturn target.element === element || target.element.contains(element);\n\t\t\t}\n\t\t\treturn target.node.parentElement?.contains(element) || false;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t});\n};\n\nconst missingChunkDecision = (): HighlightDecision => ({\n\tactiveTarget: null,\n\tregionTarget: null,\n\tquality: \"region\",\n\tconfidence: 0,\n\treason: \"boundary chunk id was not found\",\n});\n\nconst soleEquationElement = (\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"],\n): Element | null => (candidates.length === 1 ? candidates[0].element : null);\n\nconst everyCandidateIsTokenMode = (\n\tplanned: PlannedChunk,\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"],\n): boolean =>\n\tcandidates.length > 0 &&\n\tcandidates.every(\n\t\t({ element }) => planned.mathCapabilityByElement.get(element) === \"token\",\n\t);\n\nconst TEXT_NODE = 3;\n\n// Safety policy for token-mode equations. A prose \"range\" target comes from the\n// catalog span aligner, whose spoken→visible mapping inside an equation is\n// heuristic and not trustworthy at glyph granularity. A multi-node range fully\n// inside a token-mode equation is therefore rejected here in favor of holding\n// the last token — painting it would be a likely false positive, and \"false\n// positives are worse than coarse highlighting.\" A single-text-node prose range\n// is kept: it underlines one token cleanly and usefully fills in any token the\n// math resolver missed. (The highlight coordinator no longer escalates word\n// ranges to the whole <math> / <mjx-container>, so this is now a precision\n// policy rather than flash-suppression — see HighlightCoordinator\n// WORD_FALLBACK_SELECTOR.)\nconst wouldEscalateInsideTokenMath = (\n\tplanned: PlannedChunk,\n\ttarget: RenderableHighlightTarget | null,\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"],\n): boolean => {\n\tif (!target || target.type !== \"range\") return false;\n\tconst { startContainer, endContainer } = target.range;\n\tconst singleTextNode =\n\t\tstartContainer === endContainer && startContainer.nodeType === TEXT_NODE;\n\tif (singleTextNode) return false;\n\treturn candidates.some(\n\t\t({ element }) =>\n\t\t\tplanned.mathCapabilityByElement.get(element) === \"token\" &&\n\t\t\telement.contains(startContainer) &&\n\t\t\telement.contains(endContainer),\n\t);\n};\n\nconst createPlannedChunk = (\n\tchunk: TTSHighlightChunk,\n\tmathTokenHighlighting: boolean,\n): PlannedChunk => ({\n\tchunk,\n\tregionTarget: regionTargetFor(chunk),\n\trenderedMathTargetResolver: createRenderedMathTargetResolver(),\n\tmathCapabilityByElement: new Map(\n\t\tchunk.mathAlignments.map(({ element }) => [\n\t\t\telement,\n\t\t\t// Forcing \"expression\" when per-token math highlighting is disabled\n\t\t\t// routes every equation through the sticky whole-block path, so the\n\t\t\t// formula is highlighted as one unit instead of glyph by glyph.\n\t\t\tmathTokenHighlighting\n\t\t\t\t? classifyMathHighlightCapability(element)\n\t\t\t\t: \"expression\",\n\t\t]),\n\t),\n\theldTokenByElement: new Map(),\n});\n\nexport const createTTSHighlightPlan = (\n\targs: CreateTTSHighlightPlanArgs,\n): TTSHighlightPlan => {\n\tconst mathTokenHighlighting = args.mathTokenHighlighting !== false;\n\tconst plannedChunksById = new Map(\n\t\targs.chunks.map((chunk) => [\n\t\t\tchunk.id,\n\t\t\tcreatePlannedChunk(chunk, mathTokenHighlighting),\n\t\t]),\n\t);\n\treturn {\n\t\tchunks: args.chunks,\n\t\tresolveInitial(chunkId: string): HighlightDecision {\n\t\t\tconst planned = plannedChunksById.get(chunkId);\n\t\t\tif (!planned) return missingChunkDecision();\n\t\t\tconst alignments = planned.chunk.mathAlignments;\n\t\t\tconst soleElement = alignments.length === 1 ? alignments[0].element : null;\n\t\t\tconst capability = soleElement\n\t\t\t\t? planned.mathCapabilityByElement.get(soleElement)\n\t\t\t\t: null;\n\t\t\t// Sticky mode. Only an equation we already know cannot be tracked per\n\t\t\t// token gets the whole-expression paint up front. A token-mode equation\n\t\t\t// stays on the region layer until its first token resolves, so the full\n\t\t\t// equation never flashes before (or between) tokens.\n\t\t\tif (capability === \"expression\") {\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: null,\n\t\t\t\t\texpressionTarget: expressionTargetFor(planned),\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: 0.5,\n\t\t\t\t\treason: \"initial expression (sticky expression mode)\",\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn createHighlightDecision({\n\t\t\t\tsemanticTarget: null,\n\t\t\t\texpressionTarget: null,\n\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\tconfidence: 0,\n\t\t\t\treason:\n\t\t\t\t\tcapability === \"token\"\n\t\t\t\t\t\t? \"initial region (sticky token mode, awaiting first token)\"\n\t\t\t\t\t\t: \"initial region fallback\",\n\t\t\t});\n\t\t},\n\t\tresolveBoundary(event: TTSBoundaryEvent): HighlightDecision {\n\t\t\tconst planned = plannedChunksById.get(event.chunkId);\n\t\t\tif (!planned) return missingChunkDecision();\n\t\t\tconst { chunk } = planned;\n\t\t\tconst normalized = normalizeBoundaryEvent(chunk, event);\n\t\t\tconst proseTarget = resolveProseBoundaryTarget(chunk, normalized);\n\t\t\tconst mathCandidates = mathCandidatesForTarget(proseTarget, chunk);\n\t\t\tconst mathTarget =\n\t\t\t\t(!proseTarget || mathCandidates.length > 0\n\t\t\t\t\t? resolveMathBoundaryTarget(\n\t\t\t\t\t\t\tchunk,\n\t\t\t\t\t\t\tnormalized,\n\t\t\t\t\t\t\tevent,\n\t\t\t\t\t\t\tmathCandidates,\n\t\t\t\t\t\t\tplanned.renderedMathTargetResolver,\n\t\t\t\t\t\t)\n\t\t\t\t\t: null) || null;\n\t\t\tconst mathTokenTarget =\n\t\t\t\tmathTokenHighlighting &&\n\t\t\t\tmathTarget &&\n\t\t\t\tmathTarget.quality !== \"expression\"\n\t\t\t\t\t? mathTarget\n\t\t\t\t\t: null;\n\t\t\tconst mathExpressionFallback =\n\t\t\t\tmathTarget && mathTarget.quality === \"expression\" ? mathTarget : null;\n\n\t\t\t// (1) A precise token always wins, and becomes the held target for its\n\t\t\t// equation so a later gap can keep it lit instead of flashing the whole\n\t\t\t// expression.\n\t\t\tif (mathTokenTarget) {\n\t\t\t\tconst equationElement = soleEquationElement(mathCandidates);\n\t\t\t\tif (equationElement) {\n\t\t\t\t\tplanned.heldTokenByElement.set(equationElement, mathTokenTarget);\n\t\t\t\t}\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: mathTokenTarget,\n\t\t\t\t\texpressionTarget: null,\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\t\treason: normalized.reason,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// (2) A spoken word that maps to visible prose. The prose anchor is\n\t\t\t// precise; a coarse math expression fallback must never override it.\n\t\t\t// Exception: a multi-node prose range inside a token-mode equation\n\t\t\t// would escalate to the whole expression, so it is rejected here and\n\t\t\t// falls through to the token hold below. When per-token math\n\t\t\t// highlighting is disabled, any prose range that lands inside a\n\t\t\t// formula is likewise rejected so the formula stays a single block.\n\t\t\tif (\n\t\t\t\tproseTarget &&\n\t\t\t\t(mathTokenHighlighting\n\t\t\t\t\t? !wouldEscalateInsideTokenMath(planned, proseTarget, mathCandidates)\n\t\t\t\t\t: mathCandidates.length === 0)\n\t\t\t) {\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: proseTarget,\n\t\t\t\t\texpressionTarget: null,\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\t\treason: normalized.reason,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// (3) A boundary inside a token-mode equation that did not map to a\n\t\t\t// token — an SSML break, a pause, or a gap between spoken tokens. Hold\n\t\t\t// the last token instead of flashing the whole expression; before the\n\t\t\t// first token there is nothing to hold, so fall to the region layer.\n\t\t\t// The whole expression is deliberately never painted in token mode.\n\t\t\tif (everyCandidateIsTokenMode(planned, mathCandidates)) {\n\t\t\t\tconst equationElement = soleEquationElement(mathCandidates);\n\t\t\t\tconst heldToken = equationElement\n\t\t\t\t\t? (planned.heldTokenByElement.get(equationElement) ?? null)\n\t\t\t\t\t: null;\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: heldToken,\n\t\t\t\t\texpressionTarget: null,\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\t\treason: heldToken\n\t\t\t\t\t\t? \"hold last token (sticky token mode)\"\n\t\t\t\t\t\t: \"region (sticky token mode, awaiting first token)\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// (4) An expression-mode (or mixed/ambiguous) math region: paint the\n\t\t\t// whole equation. Stable for the equation's full duration because\n\t\t\t// tokens never resolve here, so there is no flicker.\n\t\t\treturn createHighlightDecision({\n\t\t\t\tsemanticTarget: null,\n\t\t\t\texpressionTarget:\n\t\t\t\t\tmathExpressionFallback ??\n\t\t\t\t\texpressionTargetFor(planned, mathCandidates),\n\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\treason: normalized.reason,\n\t\t\t});\n\t\t},\n\t};\n};\n"]}
|
|
1
|
+
{"version":3,"file":"create-highlight-plan.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/create-highlight-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBoundaryEvent } from \"./boundary-normalizer.js\";\nimport { createHighlightDecision } from \"./highlight-policy.js\";\nimport { resolveMathBoundaryTarget } from \"./math-semantic-resolver.js\";\nimport { resolveProseBoundaryTarget } from \"./prose-boundary-resolver.js\";\nimport {\n\tclassifyMathHighlightCapability,\n\tcreateRenderedMathTargetResolver,\n\ttype MathHighlightCapability,\n\ttype RenderedMathTargetResolver,\n} from \"./rendered-math-target-resolver.js\";\nimport type {\n\tHighlightDecision,\n\tRenderableHighlightTarget,\n\tTTSBoundaryEvent,\n\tTTSHighlightChunk,\n\tTTSHighlightPlan,\n} from \"./types.js\";\n\nexport interface CreateTTSHighlightPlanArgs {\n\tchunks: TTSHighlightChunk[];\n\t// When false, math is never broken into per-token highlights: every equation\n\t// is painted as a single block — the same expression/region fallback used\n\t// whenever token alignment is not confident. Prose word tracking is\n\t// unaffected. Defaults to true.\n\tmathTokenHighlighting?: boolean;\n}\n\ninterface PlannedChunk {\n\tchunk: TTSHighlightChunk;\n\tregionTarget: RenderableHighlightTarget | null;\n\trenderedMathTargetResolver: RenderedMathTargetResolver;\n\t// Capability decided once per equation (keyed by its source math element).\n\t// Sticky for the whole chunk so the same equation never switches between\n\t// token tracking and whole-expression highlighting mid-read.\n\tmathCapabilityByElement: Map<Element, MathHighlightCapability>;\n\t// The last precise token painted for each token-mode equation. Lets a gap\n\t// (SSML break / pause / unmappable boundary) hold the last token instead of\n\t// flashing the whole expression.\n\theldTokenByElement: Map<Element, RenderableHighlightTarget>;\n}\n\nconst regionTargetFor = (\n\tchunk: TTSHighlightChunk,\n): RenderableHighlightTarget | null =>\n\tchunk.regionRange\n\t\t? {\n\t\t\t\ttype: \"range\",\n\t\t\t\tquality: \"region\",\n\t\t\t\trange: chunk.regionRange,\n\t\t\t}\n\t\t: chunk.regionElement\n\t\t\t? {\n\t\t\t\t\ttype: \"element\",\n\t\t\t\t\tquality: \"region\",\n\t\t\t\t\telement: chunk.regionElement,\n\t\t\t\t}\n\t\t\t: null;\n\nconst expressionTargetFor = (\n\tplanned: PlannedChunk,\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"] = planned.chunk\n\t\t.mathAlignments,\n): RenderableHighlightTarget | null => {\n\tif (candidates.length !== 1) return null;\n\tconst fallback = candidates[0]?.alignment.result.fallbackTarget;\n\treturn fallback ? planned.renderedMathTargetResolver(fallback) : null;\n};\n\nconst mathCandidatesForTarget = (\n\ttarget: RenderableHighlightTarget | null,\n\tchunk: TTSHighlightChunk,\n): TTSHighlightChunk[\"mathAlignments\"] => {\n\tif (!target) return chunk.mathAlignments;\n\treturn chunk.mathAlignments.filter(({ element }) => {\n\t\ttry {\n\t\t\tif (target.type === \"range\") return target.range.intersectsNode(element);\n\t\t\tif (target.type === \"element\") {\n\t\t\t\treturn target.element === element || target.element.contains(element);\n\t\t\t}\n\t\t\treturn target.node.parentElement?.contains(element) || false;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t});\n};\n\nconst missingChunkDecision = (): HighlightDecision => ({\n\tactiveTarget: null,\n\tregionTarget: null,\n\tquality: \"region\",\n\tconfidence: 0,\n\treason: \"boundary chunk id was not found\",\n});\n\nconst soleEquationElement = (\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"],\n): Element | null => (candidates.length === 1 ? candidates[0].element : null);\n\nconst everyCandidateIsTokenMode = (\n\tplanned: PlannedChunk,\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"],\n): boolean =>\n\tcandidates.length > 0 &&\n\tcandidates.every(\n\t\t({ element }) => planned.mathCapabilityByElement.get(element) === \"token\",\n\t);\n\nconst TEXT_NODE = 3;\n\n// Safety policy for token-mode equations. A prose \"range\" target comes from the\n// catalog span aligner, whose spoken→visible mapping inside an equation is\n// heuristic and not trustworthy at glyph granularity. A multi-node range fully\n// inside a token-mode equation is therefore rejected here in favor of holding\n// the last token — painting it would be a likely false positive, and \"false\n// positives are worse than coarse highlighting.\" A single-text-node prose range\n// is kept: it underlines one token cleanly and usefully fills in any token the\n// math resolver missed. (The highlight coordinator no longer escalates word\n// ranges to the whole <math> / <mjx-container>, so this is now a precision\n// policy rather than flash-suppression — see HighlightCoordinator\n// WORD_FALLBACK_SELECTOR.)\nconst wouldEscalateInsideTokenMath = (\n\tplanned: PlannedChunk,\n\ttarget: RenderableHighlightTarget | null,\n\tcandidates: TTSHighlightChunk[\"mathAlignments\"],\n): boolean => {\n\tif (!target || target.type !== \"range\") return false;\n\tconst { startContainer, endContainer } = target.range;\n\tconst singleTextNode =\n\t\tstartContainer === endContainer && startContainer.nodeType === TEXT_NODE;\n\tif (singleTextNode) return false;\n\treturn candidates.some(\n\t\t({ element }) =>\n\t\t\tplanned.mathCapabilityByElement.get(element) === \"token\" &&\n\t\t\telement.contains(startContainer) &&\n\t\t\telement.contains(endContainer),\n\t);\n};\n\nconst createPlannedChunk = (\n\tchunk: TTSHighlightChunk,\n\tmathTokenHighlighting: boolean,\n): PlannedChunk => ({\n\tchunk,\n\tregionTarget: regionTargetFor(chunk),\n\trenderedMathTargetResolver: createRenderedMathTargetResolver(),\n\tmathCapabilityByElement: new Map(\n\t\tchunk.mathAlignments.map(({ element }) => [\n\t\t\telement,\n\t\t\t// Forcing \"expression\" when per-token math highlighting is disabled\n\t\t\t// routes every equation through the sticky whole-block path, so the\n\t\t\t// formula is highlighted as one unit instead of glyph by glyph.\n\t\t\tmathTokenHighlighting\n\t\t\t\t? classifyMathHighlightCapability(element)\n\t\t\t\t: \"expression\",\n\t\t]),\n\t),\n\theldTokenByElement: new Map(),\n});\n\nexport const createTTSHighlightPlan = (\n\targs: CreateTTSHighlightPlanArgs,\n): TTSHighlightPlan => {\n\tconst mathTokenHighlighting = args.mathTokenHighlighting !== false;\n\tconst plannedChunksById = new Map(\n\t\targs.chunks.map((chunk) => [\n\t\t\tchunk.id,\n\t\t\tcreatePlannedChunk(chunk, mathTokenHighlighting),\n\t\t]),\n\t);\n\treturn {\n\t\tchunks: args.chunks,\n\t\tresolveInitial(chunkId: string): HighlightDecision {\n\t\t\tconst planned = plannedChunksById.get(chunkId);\n\t\t\tif (!planned) return missingChunkDecision();\n\t\t\tconst alignments = planned.chunk.mathAlignments;\n\t\t\tconst soleElement =\n\t\t\t\talignments.length === 1 ? alignments[0].element : null;\n\t\t\tconst capability = soleElement\n\t\t\t\t? planned.mathCapabilityByElement.get(soleElement)\n\t\t\t\t: null;\n\t\t\t// Sticky mode. Only an equation we already know cannot be tracked per\n\t\t\t// token gets the whole-expression paint up front. A token-mode equation\n\t\t\t// stays on the region layer until its first token resolves, so the full\n\t\t\t// equation never flashes before (or between) tokens.\n\t\t\tif (capability === \"expression\") {\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: null,\n\t\t\t\t\texpressionTarget: expressionTargetFor(planned),\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: 0.5,\n\t\t\t\t\treason: \"initial expression (sticky expression mode)\",\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn createHighlightDecision({\n\t\t\t\tsemanticTarget: null,\n\t\t\t\texpressionTarget: null,\n\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\tconfidence: 0,\n\t\t\t\treason:\n\t\t\t\t\tcapability === \"token\"\n\t\t\t\t\t\t? \"initial region (sticky token mode, awaiting first token)\"\n\t\t\t\t\t\t: \"initial region fallback\",\n\t\t\t});\n\t\t},\n\t\tresolveBoundary(event: TTSBoundaryEvent): HighlightDecision {\n\t\t\tconst planned = plannedChunksById.get(event.chunkId);\n\t\t\tif (!planned) return missingChunkDecision();\n\t\t\tconst { chunk } = planned;\n\t\t\tconst normalized = normalizeBoundaryEvent(chunk, event);\n\t\t\tconst proseTarget = resolveProseBoundaryTarget(chunk, normalized);\n\t\t\tconst mathCandidates = mathCandidatesForTarget(proseTarget, chunk);\n\t\t\tconst mathTarget =\n\t\t\t\t(!proseTarget || mathCandidates.length > 0\n\t\t\t\t\t? resolveMathBoundaryTarget(\n\t\t\t\t\t\t\tchunk,\n\t\t\t\t\t\t\tnormalized,\n\t\t\t\t\t\t\tevent,\n\t\t\t\t\t\t\tmathCandidates,\n\t\t\t\t\t\t\tplanned.renderedMathTargetResolver,\n\t\t\t\t\t\t)\n\t\t\t\t\t: null) || null;\n\t\t\tconst mathTokenTarget =\n\t\t\t\tmathTokenHighlighting &&\n\t\t\t\tmathTarget &&\n\t\t\t\tmathTarget.quality !== \"expression\"\n\t\t\t\t\t? mathTarget\n\t\t\t\t\t: null;\n\t\t\tconst mathExpressionFallback =\n\t\t\t\tmathTarget && mathTarget.quality === \"expression\" ? mathTarget : null;\n\n\t\t\t// (1) A precise token always wins, and becomes the held target for its\n\t\t\t// equation so a later gap can keep it lit instead of flashing the whole\n\t\t\t// expression.\n\t\t\tif (mathTokenTarget) {\n\t\t\t\tconst equationElement = soleEquationElement(mathCandidates);\n\t\t\t\tif (equationElement) {\n\t\t\t\t\tplanned.heldTokenByElement.set(equationElement, mathTokenTarget);\n\t\t\t\t}\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: mathTokenTarget,\n\t\t\t\t\texpressionTarget: null,\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\t\treason: normalized.reason,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// (2) A spoken word that maps to visible prose. The prose anchor is\n\t\t\t// precise; a coarse math expression fallback must never override it.\n\t\t\t// Exception: a multi-node prose range inside a token-mode equation\n\t\t\t// would escalate to the whole expression, so it is rejected here and\n\t\t\t// falls through to the token hold below. When per-token math\n\t\t\t// highlighting is disabled, any prose range that lands inside a\n\t\t\t// formula is likewise rejected so the formula stays a single block.\n\t\t\tif (\n\t\t\t\tproseTarget &&\n\t\t\t\t(mathTokenHighlighting\n\t\t\t\t\t? !wouldEscalateInsideTokenMath(planned, proseTarget, mathCandidates)\n\t\t\t\t\t: mathCandidates.length === 0)\n\t\t\t) {\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: proseTarget,\n\t\t\t\t\texpressionTarget: null,\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\t\treason: normalized.reason,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// (3) A boundary inside a token-mode equation that did not map to a\n\t\t\t// token — an SSML break, a pause, or a gap between spoken tokens. Hold\n\t\t\t// the last token instead of flashing the whole expression; before the\n\t\t\t// first token there is nothing to hold, so fall to the region layer.\n\t\t\t// The whole expression is deliberately never painted in token mode.\n\t\t\tif (everyCandidateIsTokenMode(planned, mathCandidates)) {\n\t\t\t\tconst equationElement = soleEquationElement(mathCandidates);\n\t\t\t\tconst heldToken = equationElement\n\t\t\t\t\t? (planned.heldTokenByElement.get(equationElement) ?? null)\n\t\t\t\t\t: null;\n\t\t\t\treturn createHighlightDecision({\n\t\t\t\t\tsemanticTarget: heldToken,\n\t\t\t\t\texpressionTarget: null,\n\t\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\t\treason: heldToken\n\t\t\t\t\t\t? \"hold last token (sticky token mode)\"\n\t\t\t\t\t\t: \"region (sticky token mode, awaiting first token)\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// (4) An expression-mode (or mixed/ambiguous) math region: paint the\n\t\t\t// whole equation. Stable for the equation's full duration because\n\t\t\t// tokens never resolve here, so there is no flicker.\n\t\t\treturn createHighlightDecision({\n\t\t\t\tsemanticTarget: null,\n\t\t\t\texpressionTarget:\n\t\t\t\t\tmathExpressionFallback ??\n\t\t\t\t\texpressionTargetFor(planned, mathCandidates),\n\t\t\t\tregionTarget: planned.regionTarget,\n\t\t\t\tconfidence: normalized.confidence,\n\t\t\t\treason: normalized.reason,\n\t\t\t});\n\t\t},\n\t};\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mathml-tokenizer.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/mathml-tokenizer.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tHighlightTarget,\n\tMathAlignmentToken,\n\tMathAlignmentTokenKind,\n\tMathMLTokenizationResult,\n} from \"./types.js\";\n\nconst OPERATOR_ALIASES = new Map<string, string[]>([\n\t[\"+\", [\"plus\"]],\n\t[\"-\", [\"minus\", \"negative\"]],\n\t[\"−\", [\"minus\", \"negative\"]],\n\t[\"×\", [\"times\", \"multiplied by\"]],\n\t[\"÷\", [\"divided by\"]],\n\t[\"=\", [\"equals\", \"equal to\"]],\n\t[\"≠\", [\"is not equal to\", \"not equal to\"]],\n\t[\"≈\", [\"approximately equals\"]],\n\t[\"≤\", [\"less than or equal to\"]],\n\t[\"≥\", [\"greater than or equal to\"]],\n\t[\"<\", [\"less than\"]],\n\t[\">\", [\"greater than\"]],\n\t[\"∠\", [\"angle\"]],\n\t[\"△\", [\"triangle\"]],\n\t[\"□\", [\"box\"]],\n\t[\"○\", [\"circle\"]],\n\t[\"°\", [\"degrees\", \"degree\"]],\n\t[\"%\", [\"percent\"]],\n\t[\"'\", [\"prime\"]],\n\t[\"′\", [\"prime\"]],\n\t[\"•\", [\"times\"]],\n\t[\"·\", [\"times\"]],\n\t[\"\\u2062\", [\"times\", \"multiplied by\"]],\n]);\n\n// Invisible operators (no glyph). A TTS engine usually does not voice them, so\n// they are tokenized as OPTIONAL — they only consume a spoken token when one\n// happens to match (e.g. an invisible-times read aloud as \"times\"). Marking\n// them required would force a coarse fallback for any function call (sin, f(x))\n// or implicit product.\nconst INVISIBLE_OPERATOR_ROLES = new Map<string, string>([\n\t[\"\\u2061\", \"function-application\"],\n\t[\"\\u2062\", \"implicit-multiply\"],\n\t[\"\\u2063\", \"invisible-separator\"],\n\t[\"\\u2064\", \"invisible-plus\"],\n]);\n\n// Exponents a TTS engine reliably reads as a single power word. Other exponents\n// (variables, 4 and up, nested) are voiced in ways we cannot anchor, so they\n// recurse and let the all-or-nothing cover check fall back to the expression.\nconst SUPERSCRIPT_ALIASES = new Map<string, string[]>([\n\t[\"2\", [\"squared\"]],\n\t[\"3\", [\"cubed\"]],\n]);\n\n// 2D / scripted containers we do not attempt to track per token. Each becomes a\n// single opaque, unmatchable structure token so any expression containing one\n// degrades to a coarse whole-expression highlight rather than risking a\n// misaligned per-glyph highlight from flattened inner tokens.\nconst OPAQUE_STRUCTURE_ROLES = new Map<string, string>([\n\t[\"msqrt\", \"radical\"],\n\t[\"mroot\", \"radical\"],\n\t[\"msub\", \"subscript\"],\n\t[\"msubsup\", \"subsupscript\"],\n\t[\"munder\", \"underscript\"],\n\t[\"mover\", \"overscript\"],\n\t[\"munderover\", \"underoverscript\"],\n\t[\"mmultiscripts\", \"multiscript\"],\n\t[\"menclose\", \"enclosure\"],\n]);\n\nconst normalizeWhitespace = (value: string): string =>\n\tvalue.replace(/\\s+/g, \" \").trim();\n\nconst localNameOf = (element: Element): string =>\n\t(element.localName || element.tagName || \"\").toLowerCase();\n\nconst isElement = (node: Node): node is Element =>\n\tnode.nodeType === Node.ELEMENT_NODE;\n\nconst elementChildren = (element: Element): Element[] =>\n\tArray.from(element.childNodes).filter(isElement);\n\nconst textContentOf = (element: Element): string =>\n\tnormalizeWhitespace(element.textContent || \"\");\n\nconst elementTarget = (\n\telement: Element,\n\tquality: \"element-range\" | \"region-fallback\",\n): HighlightTarget => ({\n\ttype: \"element-range\",\n\tquality,\n\telement,\n});\n\nconst tokenKindFor = (tagName: string): MathAlignmentTokenKind => {\n\tswitch (tagName) {\n\t\tcase \"mi\":\n\t\t\treturn \"identifier\";\n\t\tcase \"mn\":\n\t\t\treturn \"number\";\n\t\tcase \"mo\":\n\t\t\treturn \"operator\";\n\t\tcase \"mtext\":\n\t\tcase \"ms\":\n\t\t\treturn \"text\";\n\t\tcase \"mspace\":\n\t\t\treturn \"space\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n};\n\nconst normalizeToken = (text: string, kind: MathAlignmentTokenKind): string => {\n\tif (kind === \"operator\") {\n\t\tif (text === \"−\") return \"-\";\n\t\treturn text;\n\t}\n\treturn text.toLowerCase();\n};\n\nconst aliasesFor = (\n\ttext: string,\n\tkind: MathAlignmentTokenKind,\n\trole: string,\n): string[] => {\n\tif (role === \"fraction\") return [\"over\", \"divided by\"];\n\tif (role === \"superscript\") return SUPERSCRIPT_ALIASES.get(text) ?? [];\n\tif (text === \"|\") return [\"absolute value\"];\n\tif (kind === \"operator\") return OPERATOR_ALIASES.get(text) || [];\n\treturn [];\n};\n\ninterface TokenizerState {\n\ttokens: MathAlignmentToken[];\n\tlayoutTargets: MathAlignmentToken[];\n\tnextId: number;\n}\n\nconst createToken = (args: {\n\tstate: TokenizerState;\n\tkind: MathAlignmentTokenKind;\n\trole: string;\n\ttext: string;\n\tnormalized?: string;\n\tsourceElement: Element;\n\tpath: string;\n\ttarget?: HighlightTarget;\n\toptional?: boolean;\n}): MathAlignmentToken => {\n\tconst normalized =\n\t\targs.normalized ?? normalizeToken(args.text, args.kind || \"unknown\");\n\treturn {\n\t\tid: `math-token-${args.state.nextId++}`,\n\t\tkind: args.kind,\n\t\trole: args.role,\n\t\ttext: args.text,\n\t\tnormalized,\n\t\tspokenAliases: aliasesFor(args.text, args.kind, args.role),\n\t\tsourceElement: args.sourceElement,\n\t\tpath: args.path,\n\t\ttarget: args.target || elementTarget(args.sourceElement, \"element-range\"),\n\t\toptional: args.optional,\n\t};\n};\n\nconst pushToken = (\n\tstate: TokenizerState,\n\ttoken: MathAlignmentToken,\n\tcollection: \"tokens\" | \"layoutTargets\" = \"tokens\",\n): void => {\n\tstate[collection].push(token);\n};\n\nconst visitChildren = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\telementChildren(element).forEach((child, index) =>\n\t\tvisitElement(child, state, `${path}/${localNameOf(child)}[${index}]`),\n\t);\n};\n\nconst visitFraction = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst children = elementChildren(element);\n\tif (children[0]) visitElement(children[0], state, `${path}/numerator`);\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"structure\",\n\t\t\trole: \"fraction\",\n\t\t\ttext: \"/\",\n\t\t\tsourceElement: element,\n\t\t\tpath,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n\tif (children[1]) visitElement(children[1], state, `${path}/denominator`);\n};\n\nconst visitFenced = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst open = element.getAttribute(\"open\") || \"(\";\n\tconst close = element.getAttribute(\"close\") || \")\";\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"operator\",\n\t\t\trole: open === \"|\" ? \"absolute-value-fence\" : \"open-fence\",\n\t\t\ttext: open,\n\t\t\tsourceElement: element,\n\t\t\tpath: `${path}/open`,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n\tvisitChildren(element, state, path);\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"operator\",\n\t\t\trole: close === \"|\" ? \"absolute-value-fence\" : \"close-fence\",\n\t\t\ttext: close,\n\t\t\tsourceElement: element,\n\t\t\tpath: `${path}/close`,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n};\n\nconst visitSuperscript = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst children = elementChildren(element);\n\tif (children[0]) visitElement(children[0], state, `${path}/base`);\n\tconst exponent = children[1];\n\tif (!exponent) return;\n\tconst exponentText = textContentOf(exponent);\n\tif (SUPERSCRIPT_ALIASES.has(exponentText)) {\n\t\t// One token aliased to the power word (\"squared\" / \"cubed\"), targeting the\n\t\t// whole superscript so the spoken power word highlights the base-and-\n\t\t// exponent unit.\n\t\tpushToken(\n\t\t\tstate,\n\t\t\tcreateToken({\n\t\t\t\tstate,\n\t\t\t\tkind: \"structure\",\n\t\t\t\trole: \"superscript\",\n\t\t\t\ttext: exponentText,\n\t\t\t\tnormalized: exponentText,\n\t\t\t\tsourceElement: exponent,\n\t\t\t\tpath: `${path}/exponent`,\n\t\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\tvisitElement(exponent, state, `${path}/exponent`);\n};\n\nconst visitOpaqueStructure = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n\trole: string,\n): void => {\n\t// A single required, unmatchable token. The NUL-prefixed normalized value can\n\t// never equal a spoken token, so the cover check always falls back to the\n\t// whole-expression target when this structure is present — no per-glyph\n\t// guessing inside fractions-under-radicals, subscripts, accents, etc.\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"structure\",\n\t\t\trole,\n\t\t\ttext: textContentOf(element) || role,\n\t\t\tnormalized: `\\u0000${role}`,\n\t\t\tsourceElement: element,\n\t\t\tpath,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n};\n\nconst visitTable = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tlet rowNumber = 0;\n\tfor (const child of elementChildren(element)) {\n\t\tif (localNameOf(child) === \"mtr\" || localNameOf(child) === \"mlabeledtr\") {\n\t\t\trowNumber++;\n\t\t\tpushToken(\n\t\t\t\tstate,\n\t\t\t\tcreateToken({\n\t\t\t\t\tstate,\n\t\t\t\t\tkind: \"layout\",\n\t\t\t\t\trole: \"table-row\",\n\t\t\t\t\ttext: `row-${rowNumber}`,\n\t\t\t\t\tnormalized: `row-${rowNumber}`,\n\t\t\t\t\tsourceElement: child,\n\t\t\t\t\tpath: `${path}/row[${rowNumber}]`,\n\t\t\t\t\ttarget: elementTarget(child, \"region-fallback\"),\n\t\t\t\t}),\n\t\t\t\t\"layoutTargets\",\n\t\t\t);\n\t\t}\n\t\tvisitElement(child, state, `${path}/${localNameOf(child)}[${rowNumber}]`);\n\t}\n};\n\nconst visitTokenElement = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst text = textContentOf(element);\n\tif (!text) return;\n\tconst kind = tokenKindFor(localNameOf(element));\n\tconst invisibleRole =\n\t\tkind === \"operator\" ? INVISIBLE_OPERATOR_ROLES.get(text) : undefined;\n\tconst role =\n\t\tinvisibleRole ??\n\t\t(kind === \"operator\" && (text === \"(\" || text === \")\")\n\t\t\t? \"grouping-fence\"\n\t\t\t: kind);\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind,\n\t\t\trole,\n\t\t\ttext,\n\t\t\tsourceElement: element,\n\t\t\tpath,\n\t\t\ttarget:\n\t\t\t\tinvisibleRole && element.parentElement\n\t\t\t\t\t? elementTarget(element.parentElement, \"region-fallback\")\n\t\t\t\t\t: undefined,\n\t\t\toptional: Boolean(invisibleRole) || role === \"grouping-fence\",\n\t\t}),\n\t);\n};\n\nconst visitElement = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst tagName = localNameOf(element);\n\tswitch (tagName) {\n\t\tcase \"annotation\":\n\t\tcase \"annotation-xml\":\n\t\t\treturn;\n\t\tcase \"semantics\": {\n\t\t\tconst presentation = elementChildren(element).find((child) => {\n\t\t\t\tconst childName = localNameOf(child);\n\t\t\t\treturn childName !== \"annotation\" && childName !== \"annotation-xml\";\n\t\t\t});\n\t\t\tif (presentation) visitElement(presentation, state, `${path}/presentation`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"mi\":\n\t\tcase \"mn\":\n\t\tcase \"mo\":\n\t\tcase \"mtext\":\n\t\tcase \"ms\":\n\t\tcase \"mspace\":\n\t\t\tvisitTokenElement(element, state, path);\n\t\t\treturn;\n\t\tcase \"mfrac\":\n\t\t\tvisitFraction(element, state, path);\n\t\t\treturn;\n\t\tcase \"msup\":\n\t\t\tvisitSuperscript(element, state, path);\n\t\t\treturn;\n\t\tcase \"mfenced\":\n\t\t\tvisitFenced(element, state, path);\n\t\t\treturn;\n\t\tcase \"mtable\":\n\t\t\tvisitTable(element, state, path);\n\t\t\treturn;\n\t\tcase \"mphantom\":\n\t\t\t// Invisible spacing content — must not become spoken tokens.\n\t\t\treturn;\n\t\tcase \"maction\": {\n\t\t\t// Only the shown alternative (first child) is rendered/spoken.\n\t\t\tconst shown = elementChildren(element)[0];\n\t\t\tif (shown) visitElement(shown, state, `${path}/action`);\n\t\t\treturn;\n\t\t}\n\t\tdefault: {\n\t\t\tconst opaqueRole = OPAQUE_STRUCTURE_ROLES.get(tagName);\n\t\t\tif (opaqueRole) {\n\t\t\t\tvisitOpaqueStructure(element, state, path, opaqueRole);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvisitChildren(element, state, path);\n\t\t}\n\t}\n};\n\nexport const tokenizeMathML = (mathElement: Element): MathMLTokenizationResult => {\n\tconst state: TokenizerState = {\n\t\ttokens: [],\n\t\tlayoutTargets: [],\n\t\tnextId: 0,\n\t};\n\tvisitElement(mathElement, state, \"/math\");\n\n\treturn {\n\t\ttokens: state.tokens,\n\t\tlayoutTargets: state.layoutTargets,\n\t\texpressionTarget: elementTarget(mathElement, \"region-fallback\"),\n\t};\n};\n"]}
|
|
1
|
+
{"version":3,"file":"mathml-tokenizer.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/mathml-tokenizer.ts"],"names":[],"mappings":"AAOA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAmB;IAClD,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;IACf,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC5B,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC5B,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;IACjC,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC;IACrB,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC7B,CAAC,GAAG,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;IAC1C,CAAC,GAAG,EAAE,CAAC,sBAAsB,CAAC,CAAC;IAC/B,CAAC,GAAG,EAAE,CAAC,uBAAuB,CAAC,CAAC;IAChC,CAAC,GAAG,EAAE,CAAC,0BAA0B,CAAC,CAAC;IACnC,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC;IACpB,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;IACvB,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAChB,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,CAAC;IACnB,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;IACd,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC;IACjB,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC5B,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAClB,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAChB,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAChB,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAChB,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;IAChB,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;CACtC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,gFAAgF;AAChF,uBAAuB;AACvB,MAAM,wBAAwB,GAAG,IAAI,GAAG,CAAiB;IACxD,CAAC,QAAQ,EAAE,sBAAsB,CAAC;IAClC,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IAC/B,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IACjC,CAAC,QAAQ,EAAE,gBAAgB,CAAC;CAC5B,CAAC,CAAC;AAEH,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAmB;IACrD,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAClB,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC;CAChB,CAAC,CAAC;AAEH,gFAAgF;AAChF,8EAA8E;AAC9E,wEAAwE;AACxE,8DAA8D;AAC9D,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAiB;IACtD,CAAC,OAAO,EAAE,SAAS,CAAC;IACpB,CAAC,OAAO,EAAE,SAAS,CAAC;IACpB,CAAC,MAAM,EAAE,WAAW,CAAC;IACrB,CAAC,SAAS,EAAE,cAAc,CAAC;IAC3B,CAAC,QAAQ,EAAE,aAAa,CAAC;IACzB,CAAC,OAAO,EAAE,YAAY,CAAC;IACvB,CAAC,YAAY,EAAE,iBAAiB,CAAC;IACjC,CAAC,eAAe,EAAE,aAAa,CAAC;IAChC,CAAC,UAAU,EAAE,WAAW,CAAC;CACzB,CAAC,CAAC;AAEH,MAAM,mBAAmB,GAAG,CAAC,KAAa,EAAU,EAAE,CACrD,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAEnC,MAAM,WAAW,GAAG,CAAC,OAAgB,EAAU,EAAE,CAChD,CAAC,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAE5D,MAAM,SAAS,GAAG,CAAC,IAAU,EAAmB,EAAE,CACjD,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY,CAAC;AAErC,MAAM,eAAe,GAAG,CAAC,OAAgB,EAAa,EAAE,CACvD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAElD,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAU,EAAE,CAClD,mBAAmB,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;AAEhD,MAAM,aAAa,GAAG,CACrB,OAAgB,EAChB,OAA4C,EAC1B,EAAE,CAAC,CAAC;IACtB,IAAI,EAAE,eAAe;IACrB,OAAO;IACP,OAAO;CACP,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,CAAC,OAAe,EAA0B,EAAE;IAChE,QAAQ,OAAO,EAAE,CAAC;QACjB,KAAK,IAAI;YACR,OAAO,YAAY,CAAC;QACrB,KAAK,IAAI;YACR,OAAO,QAAQ,CAAC;QACjB,KAAK,IAAI;YACR,OAAO,UAAU,CAAC;QACnB,KAAK,OAAO,CAAC;QACb,KAAK,IAAI;YACR,OAAO,MAAM,CAAC;QACf,KAAK,QAAQ;YACZ,OAAO,OAAO,CAAC;QAChB;YACC,OAAO,SAAS,CAAC;IACnB,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,IAA4B,EAAU,EAAE;IAC7E,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,GAAG,CAAC;QAC7B,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAClB,IAAY,EACZ,IAA4B,EAC5B,IAAY,EACD,EAAE;IACb,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACvD,IAAI,IAAI,KAAK,aAAa;QAAE,OAAO,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACvE,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,UAAU;QAAE,OAAO,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACjE,OAAO,EAAE,CAAC;AACX,CAAC,CAAC;AAQF,MAAM,WAAW,GAAG,CAAC,IAUpB,EAAsB,EAAE;IACxB,MAAM,UAAU,GACf,IAAI,CAAC,UAAU,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC;IACtE,OAAO;QACN,EAAE,EAAE,cAAc,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE;QACvC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,UAAU;QACV,aAAa,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC;QAC1D,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC;QACzE,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACvB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CACjB,KAAqB,EACrB,KAAyB,EACzB,aAAyC,QAAQ,EAC1C,EAAE;IACT,KAAK,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CACrB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,eAAe,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CACjD,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CACrE,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CACrB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,CAAC,CAAC;QAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,YAAY,CAAC,CAAC;IACvE,SAAS,CACR,KAAK,EACL,WAAW,CAAC;QACX,KAAK;QACL,IAAI,EAAE,WAAW;QACjB,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,GAAG;QACT,aAAa,EAAE,OAAO;QACtB,IAAI;QACJ,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC;KAC/C,CAAC,CACF,CAAC;IACF,IAAI,QAAQ,CAAC,CAAC,CAAC;QAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC;AAC1E,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CACnB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,MAAM,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC;IACnD,SAAS,CACR,KAAK,EACL,WAAW,CAAC;QACX,KAAK;QACL,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,YAAY;QAC1D,IAAI,EAAE,IAAI;QACV,aAAa,EAAE,OAAO;QACtB,IAAI,EAAE,GAAG,IAAI,OAAO;QACpB,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC;KAC/C,CAAC,CACF,CAAC;IACF,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACpC,SAAS,CACR,KAAK,EACL,WAAW,CAAC;QACX,KAAK;QACL,IAAI,EAAE,UAAU;QAChB,IAAI,EAAE,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,aAAa;QAC5D,IAAI,EAAE,KAAK;QACX,aAAa,EAAE,OAAO;QACtB,IAAI,EAAE,GAAG,IAAI,QAAQ;QACrB,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC;KAC/C,CAAC,CACF,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CACxB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,CAAC,CAAC;QAAE,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,QAAQ;QAAE,OAAO;IACtB,MAAM,YAAY,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,mBAAmB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3C,2EAA2E;QAC3E,sEAAsE;QACtE,iBAAiB;QACjB,SAAS,CACR,KAAK,EACL,WAAW,CAAC;YACX,KAAK;YACL,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,YAAY;YAClB,UAAU,EAAE,YAAY;YACxB,aAAa,EAAE,QAAQ;YACvB,IAAI,EAAE,GAAG,IAAI,WAAW;YACxB,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC;SAC/C,CAAC,CACF,CAAC;QACF,OAAO;IACR,CAAC;IACD,YAAY,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC5B,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACZ,IAAY,EACL,EAAE;IACT,8EAA8E;IAC9E,0EAA0E;IAC1E,wEAAwE;IACxE,sEAAsE;IACtE,SAAS,CACR,KAAK,EACL,WAAW,CAAC;QACX,KAAK;QACL,IAAI,EAAE,WAAW;QACjB,IAAI;QACJ,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,IAAI,IAAI;QACpC,UAAU,EAAE,SAAS,IAAI,EAAE;QAC3B,aAAa,EAAE,OAAO;QACtB,IAAI;QACJ,MAAM,EAAE,aAAa,CAAC,OAAO,EAAE,eAAe,CAAC;KAC/C,CAAC,CACF,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAClB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,YAAY,EAAE,CAAC;YACzE,SAAS,EAAE,CAAC;YACZ,SAAS,CACR,KAAK,EACL,WAAW,CAAC;gBACX,KAAK;gBACL,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,OAAO,SAAS,EAAE;gBACxB,UAAU,EAAE,OAAO,SAAS,EAAE;gBAC9B,aAAa,EAAE,KAAK;gBACpB,IAAI,EAAE,GAAG,IAAI,QAAQ,SAAS,GAAG;gBACjC,MAAM,EAAE,aAAa,CAAC,KAAK,EAAE,iBAAiB,CAAC;aAC/C,CAAC,EACF,eAAe,CACf,CAAC;QACH,CAAC;QACD,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC;IAC3E,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CACzB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI;QAAE,OAAO;IAClB,MAAM,IAAI,GAAG,YAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAChD,MAAM,aAAa,GAClB,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,wBAAwB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,MAAM,IAAI,GACT,aAAa;QACb,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC;YACrD,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,IAAI,CAAC,CAAC;IACV,SAAS,CACR,KAAK,EACL,WAAW,CAAC;QACX,KAAK;QACL,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,aAAa,EAAE,OAAO;QACtB,IAAI;QACJ,MAAM,EACL,aAAa,IAAI,OAAO,CAAC,aAAa;YACrC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,aAAa,EAAE,iBAAiB,CAAC;YACzD,CAAC,CAAC,SAAS;QACb,QAAQ,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,IAAI,KAAK,gBAAgB;KAC7D,CAAC,CACF,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CACpB,OAAgB,EAChB,KAAqB,EACrB,IAAY,EACL,EAAE;IACT,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,QAAQ,OAAO,EAAE,CAAC;QACjB,KAAK,YAAY,CAAC;QAClB,KAAK,gBAAgB;YACpB,OAAO;QACR,KAAK,WAAW,CAAC,CAAC,CAAC;YAClB,MAAM,YAAY,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC5D,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;gBACrC,OAAO,SAAS,KAAK,YAAY,IAAI,SAAS,KAAK,gBAAgB,CAAC;YACrE,CAAC,CAAC,CAAC;YACH,IAAI,YAAY;gBACf,YAAY,CAAC,YAAY,EAAE,KAAK,EAAE,GAAG,IAAI,eAAe,CAAC,CAAC;YAC3D,OAAO;QACR,CAAC;QACD,KAAK,IAAI,CAAC;QACV,KAAK,IAAI,CAAC;QACV,KAAK,IAAI,CAAC;QACV,KAAK,OAAO,CAAC;QACb,KAAK,IAAI,CAAC;QACV,KAAK,QAAQ;YACZ,iBAAiB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACxC,OAAO;QACR,KAAK,OAAO;YACX,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACpC,OAAO;QACR,KAAK,MAAM;YACV,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACvC,OAAO;QACR,KAAK,SAAS;YACb,WAAW,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YAClC,OAAO;QACR,KAAK,QAAQ;YACZ,UAAU,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACjC,OAAO;QACR,KAAK,UAAU;YACd,6DAA6D;YAC7D,OAAO;QACR,KAAK,SAAS,CAAC,CAAC,CAAC;YAChB,+DAA+D;YAC/D,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1C,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,SAAS,CAAC,CAAC;YACxD,OAAO;QACR,CAAC;QACD,OAAO,CAAC,CAAC,CAAC;YACT,MAAM,UAAU,GAAG,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,UAAU,EAAE,CAAC;gBAChB,oBAAoB,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;gBACvD,OAAO;YACR,CAAC;YACD,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;IACF,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAC7B,WAAoB,EACO,EAAE;IAC7B,MAAM,KAAK,GAAmB;QAC7B,MAAM,EAAE,EAAE;QACV,aAAa,EAAE,EAAE;QACjB,MAAM,EAAE,CAAC;KACT,CAAC;IACF,YAAY,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAE1C,OAAO;QACN,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,gBAAgB,EAAE,aAAa,CAAC,WAAW,EAAE,iBAAiB,CAAC;KAC/D,CAAC;AACH,CAAC,CAAC","sourcesContent":["import type {\n\tHighlightTarget,\n\tMathAlignmentToken,\n\tMathAlignmentTokenKind,\n\tMathMLTokenizationResult,\n} from \"./types.js\";\n\nconst OPERATOR_ALIASES = new Map<string, string[]>([\n\t[\"+\", [\"plus\"]],\n\t[\"-\", [\"minus\", \"negative\"]],\n\t[\"−\", [\"minus\", \"negative\"]],\n\t[\"×\", [\"times\", \"multiplied by\"]],\n\t[\"÷\", [\"divided by\"]],\n\t[\"=\", [\"equals\", \"equal to\"]],\n\t[\"≠\", [\"is not equal to\", \"not equal to\"]],\n\t[\"≈\", [\"approximately equals\"]],\n\t[\"≤\", [\"less than or equal to\"]],\n\t[\"≥\", [\"greater than or equal to\"]],\n\t[\"<\", [\"less than\"]],\n\t[\">\", [\"greater than\"]],\n\t[\"∠\", [\"angle\"]],\n\t[\"△\", [\"triangle\"]],\n\t[\"□\", [\"box\"]],\n\t[\"○\", [\"circle\"]],\n\t[\"°\", [\"degrees\", \"degree\"]],\n\t[\"%\", [\"percent\"]],\n\t[\"'\", [\"prime\"]],\n\t[\"′\", [\"prime\"]],\n\t[\"•\", [\"times\"]],\n\t[\"·\", [\"times\"]],\n\t[\"\\u2062\", [\"times\", \"multiplied by\"]],\n]);\n\n// Invisible operators (no glyph). A TTS engine usually does not voice them, so\n// they are tokenized as OPTIONAL — they only consume a spoken token when one\n// happens to match (e.g. an invisible-times read aloud as \"times\"). Marking\n// them required would force a coarse fallback for any function call (sin, f(x))\n// or implicit product.\nconst INVISIBLE_OPERATOR_ROLES = new Map<string, string>([\n\t[\"\\u2061\", \"function-application\"],\n\t[\"\\u2062\", \"implicit-multiply\"],\n\t[\"\\u2063\", \"invisible-separator\"],\n\t[\"\\u2064\", \"invisible-plus\"],\n]);\n\n// Exponents a TTS engine reliably reads as a single power word. Other exponents\n// (variables, 4 and up, nested) are voiced in ways we cannot anchor, so they\n// recurse and let the all-or-nothing cover check fall back to the expression.\nconst SUPERSCRIPT_ALIASES = new Map<string, string[]>([\n\t[\"2\", [\"squared\"]],\n\t[\"3\", [\"cubed\"]],\n]);\n\n// 2D / scripted containers we do not attempt to track per token. Each becomes a\n// single opaque, unmatchable structure token so any expression containing one\n// degrades to a coarse whole-expression highlight rather than risking a\n// misaligned per-glyph highlight from flattened inner tokens.\nconst OPAQUE_STRUCTURE_ROLES = new Map<string, string>([\n\t[\"msqrt\", \"radical\"],\n\t[\"mroot\", \"radical\"],\n\t[\"msub\", \"subscript\"],\n\t[\"msubsup\", \"subsupscript\"],\n\t[\"munder\", \"underscript\"],\n\t[\"mover\", \"overscript\"],\n\t[\"munderover\", \"underoverscript\"],\n\t[\"mmultiscripts\", \"multiscript\"],\n\t[\"menclose\", \"enclosure\"],\n]);\n\nconst normalizeWhitespace = (value: string): string =>\n\tvalue.replace(/\\s+/g, \" \").trim();\n\nconst localNameOf = (element: Element): string =>\n\t(element.localName || element.tagName || \"\").toLowerCase();\n\nconst isElement = (node: Node): node is Element =>\n\tnode.nodeType === Node.ELEMENT_NODE;\n\nconst elementChildren = (element: Element): Element[] =>\n\tArray.from(element.childNodes).filter(isElement);\n\nconst textContentOf = (element: Element): string =>\n\tnormalizeWhitespace(element.textContent || \"\");\n\nconst elementTarget = (\n\telement: Element,\n\tquality: \"element-range\" | \"region-fallback\",\n): HighlightTarget => ({\n\ttype: \"element-range\",\n\tquality,\n\telement,\n});\n\nconst tokenKindFor = (tagName: string): MathAlignmentTokenKind => {\n\tswitch (tagName) {\n\t\tcase \"mi\":\n\t\t\treturn \"identifier\";\n\t\tcase \"mn\":\n\t\t\treturn \"number\";\n\t\tcase \"mo\":\n\t\t\treturn \"operator\";\n\t\tcase \"mtext\":\n\t\tcase \"ms\":\n\t\t\treturn \"text\";\n\t\tcase \"mspace\":\n\t\t\treturn \"space\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n};\n\nconst normalizeToken = (text: string, kind: MathAlignmentTokenKind): string => {\n\tif (kind === \"operator\") {\n\t\tif (text === \"−\") return \"-\";\n\t\treturn text;\n\t}\n\treturn text.toLowerCase();\n};\n\nconst aliasesFor = (\n\ttext: string,\n\tkind: MathAlignmentTokenKind,\n\trole: string,\n): string[] => {\n\tif (role === \"fraction\") return [\"over\", \"divided by\"];\n\tif (role === \"superscript\") return SUPERSCRIPT_ALIASES.get(text) ?? [];\n\tif (text === \"|\") return [\"absolute value\"];\n\tif (kind === \"operator\") return OPERATOR_ALIASES.get(text) || [];\n\treturn [];\n};\n\ninterface TokenizerState {\n\ttokens: MathAlignmentToken[];\n\tlayoutTargets: MathAlignmentToken[];\n\tnextId: number;\n}\n\nconst createToken = (args: {\n\tstate: TokenizerState;\n\tkind: MathAlignmentTokenKind;\n\trole: string;\n\ttext: string;\n\tnormalized?: string;\n\tsourceElement: Element;\n\tpath: string;\n\ttarget?: HighlightTarget;\n\toptional?: boolean;\n}): MathAlignmentToken => {\n\tconst normalized =\n\t\targs.normalized ?? normalizeToken(args.text, args.kind || \"unknown\");\n\treturn {\n\t\tid: `math-token-${args.state.nextId++}`,\n\t\tkind: args.kind,\n\t\trole: args.role,\n\t\ttext: args.text,\n\t\tnormalized,\n\t\tspokenAliases: aliasesFor(args.text, args.kind, args.role),\n\t\tsourceElement: args.sourceElement,\n\t\tpath: args.path,\n\t\ttarget: args.target || elementTarget(args.sourceElement, \"element-range\"),\n\t\toptional: args.optional,\n\t};\n};\n\nconst pushToken = (\n\tstate: TokenizerState,\n\ttoken: MathAlignmentToken,\n\tcollection: \"tokens\" | \"layoutTargets\" = \"tokens\",\n): void => {\n\tstate[collection].push(token);\n};\n\nconst visitChildren = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\telementChildren(element).forEach((child, index) =>\n\t\tvisitElement(child, state, `${path}/${localNameOf(child)}[${index}]`),\n\t);\n};\n\nconst visitFraction = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst children = elementChildren(element);\n\tif (children[0]) visitElement(children[0], state, `${path}/numerator`);\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"structure\",\n\t\t\trole: \"fraction\",\n\t\t\ttext: \"/\",\n\t\t\tsourceElement: element,\n\t\t\tpath,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n\tif (children[1]) visitElement(children[1], state, `${path}/denominator`);\n};\n\nconst visitFenced = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst open = element.getAttribute(\"open\") || \"(\";\n\tconst close = element.getAttribute(\"close\") || \")\";\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"operator\",\n\t\t\trole: open === \"|\" ? \"absolute-value-fence\" : \"open-fence\",\n\t\t\ttext: open,\n\t\t\tsourceElement: element,\n\t\t\tpath: `${path}/open`,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n\tvisitChildren(element, state, path);\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"operator\",\n\t\t\trole: close === \"|\" ? \"absolute-value-fence\" : \"close-fence\",\n\t\t\ttext: close,\n\t\t\tsourceElement: element,\n\t\t\tpath: `${path}/close`,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n};\n\nconst visitSuperscript = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst children = elementChildren(element);\n\tif (children[0]) visitElement(children[0], state, `${path}/base`);\n\tconst exponent = children[1];\n\tif (!exponent) return;\n\tconst exponentText = textContentOf(exponent);\n\tif (SUPERSCRIPT_ALIASES.has(exponentText)) {\n\t\t// One token aliased to the power word (\"squared\" / \"cubed\"), targeting the\n\t\t// whole superscript so the spoken power word highlights the base-and-\n\t\t// exponent unit.\n\t\tpushToken(\n\t\t\tstate,\n\t\t\tcreateToken({\n\t\t\t\tstate,\n\t\t\t\tkind: \"structure\",\n\t\t\t\trole: \"superscript\",\n\t\t\t\ttext: exponentText,\n\t\t\t\tnormalized: exponentText,\n\t\t\t\tsourceElement: exponent,\n\t\t\t\tpath: `${path}/exponent`,\n\t\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t\t}),\n\t\t);\n\t\treturn;\n\t}\n\tvisitElement(exponent, state, `${path}/exponent`);\n};\n\nconst visitOpaqueStructure = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n\trole: string,\n): void => {\n\t// A single required, unmatchable token. The NUL-prefixed normalized value can\n\t// never equal a spoken token, so the cover check always falls back to the\n\t// whole-expression target when this structure is present — no per-glyph\n\t// guessing inside fractions-under-radicals, subscripts, accents, etc.\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind: \"structure\",\n\t\t\trole,\n\t\t\ttext: textContentOf(element) || role,\n\t\t\tnormalized: `\\u0000${role}`,\n\t\t\tsourceElement: element,\n\t\t\tpath,\n\t\t\ttarget: elementTarget(element, \"element-range\"),\n\t\t}),\n\t);\n};\n\nconst visitTable = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tlet rowNumber = 0;\n\tfor (const child of elementChildren(element)) {\n\t\tif (localNameOf(child) === \"mtr\" || localNameOf(child) === \"mlabeledtr\") {\n\t\t\trowNumber++;\n\t\t\tpushToken(\n\t\t\t\tstate,\n\t\t\t\tcreateToken({\n\t\t\t\t\tstate,\n\t\t\t\t\tkind: \"layout\",\n\t\t\t\t\trole: \"table-row\",\n\t\t\t\t\ttext: `row-${rowNumber}`,\n\t\t\t\t\tnormalized: `row-${rowNumber}`,\n\t\t\t\t\tsourceElement: child,\n\t\t\t\t\tpath: `${path}/row[${rowNumber}]`,\n\t\t\t\t\ttarget: elementTarget(child, \"region-fallback\"),\n\t\t\t\t}),\n\t\t\t\t\"layoutTargets\",\n\t\t\t);\n\t\t}\n\t\tvisitElement(child, state, `${path}/${localNameOf(child)}[${rowNumber}]`);\n\t}\n};\n\nconst visitTokenElement = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst text = textContentOf(element);\n\tif (!text) return;\n\tconst kind = tokenKindFor(localNameOf(element));\n\tconst invisibleRole =\n\t\tkind === \"operator\" ? INVISIBLE_OPERATOR_ROLES.get(text) : undefined;\n\tconst role =\n\t\tinvisibleRole ??\n\t\t(kind === \"operator\" && (text === \"(\" || text === \")\")\n\t\t\t? \"grouping-fence\"\n\t\t\t: kind);\n\tpushToken(\n\t\tstate,\n\t\tcreateToken({\n\t\t\tstate,\n\t\t\tkind,\n\t\t\trole,\n\t\t\ttext,\n\t\t\tsourceElement: element,\n\t\t\tpath,\n\t\t\ttarget:\n\t\t\t\tinvisibleRole && element.parentElement\n\t\t\t\t\t? elementTarget(element.parentElement, \"region-fallback\")\n\t\t\t\t\t: undefined,\n\t\t\toptional: Boolean(invisibleRole) || role === \"grouping-fence\",\n\t\t}),\n\t);\n};\n\nconst visitElement = (\n\telement: Element,\n\tstate: TokenizerState,\n\tpath: string,\n): void => {\n\tconst tagName = localNameOf(element);\n\tswitch (tagName) {\n\t\tcase \"annotation\":\n\t\tcase \"annotation-xml\":\n\t\t\treturn;\n\t\tcase \"semantics\": {\n\t\t\tconst presentation = elementChildren(element).find((child) => {\n\t\t\t\tconst childName = localNameOf(child);\n\t\t\t\treturn childName !== \"annotation\" && childName !== \"annotation-xml\";\n\t\t\t});\n\t\t\tif (presentation)\n\t\t\t\tvisitElement(presentation, state, `${path}/presentation`);\n\t\t\treturn;\n\t\t}\n\t\tcase \"mi\":\n\t\tcase \"mn\":\n\t\tcase \"mo\":\n\t\tcase \"mtext\":\n\t\tcase \"ms\":\n\t\tcase \"mspace\":\n\t\t\tvisitTokenElement(element, state, path);\n\t\t\treturn;\n\t\tcase \"mfrac\":\n\t\t\tvisitFraction(element, state, path);\n\t\t\treturn;\n\t\tcase \"msup\":\n\t\t\tvisitSuperscript(element, state, path);\n\t\t\treturn;\n\t\tcase \"mfenced\":\n\t\t\tvisitFenced(element, state, path);\n\t\t\treturn;\n\t\tcase \"mtable\":\n\t\t\tvisitTable(element, state, path);\n\t\t\treturn;\n\t\tcase \"mphantom\":\n\t\t\t// Invisible spacing content — must not become spoken tokens.\n\t\t\treturn;\n\t\tcase \"maction\": {\n\t\t\t// Only the shown alternative (first child) is rendered/spoken.\n\t\t\tconst shown = elementChildren(element)[0];\n\t\t\tif (shown) visitElement(shown, state, `${path}/action`);\n\t\t\treturn;\n\t\t}\n\t\tdefault: {\n\t\t\tconst opaqueRole = OPAQUE_STRUCTURE_ROLES.get(tagName);\n\t\t\tif (opaqueRole) {\n\t\t\t\tvisitOpaqueStructure(element, state, path, opaqueRole);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tvisitChildren(element, state, path);\n\t\t}\n\t}\n};\n\nexport const tokenizeMathML = (\n\tmathElement: Element,\n): MathMLTokenizationResult => {\n\tconst state: TokenizerState = {\n\t\ttokens: [],\n\t\tlayoutTargets: [],\n\t\tnextId: 0,\n\t};\n\tvisitElement(mathElement, state, \"/math\");\n\n\treturn {\n\t\ttokens: state.tokens,\n\t\tlayoutTargets: state.layoutTargets,\n\t\texpressionTarget: elementTarget(mathElement, \"region-fallback\"),\n\t};\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequence-aligner.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/sequence-aligner.ts"],"names":[],"mappings":"AASA,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAOvC,+EAA+E;AAC/E,+EAA+E;AAC/E,iFAAiF;AACjF,8EAA8E;AAC9E,8EAA8E;AAC9E,sEAAsE;AACtE,MAAM,mBAAmB,GAAG,CAAC,IAAwB,EAAc,EAAE;IACpE,MAAM,OAAO,GAAe,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAG,CAAC,KAAa,EAAQ,EAAE;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC,CAAC;IACF,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa;QAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACnD,OAAO,OAAO,CAAC;AAChB,CAAC,CAAC;AAEF,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,UAAU;AACV,MAAM,mBAAmB,GAAG,CAC3B,YAAoC,EACpC,UAAkB,EAClB,OAAmB,EACV,EAAE;IACX,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC9B,IAAI,MAAM,CAAC,MAAM,IAAI,IAAI;YAAE,SAAS;QACpC,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,YAAY,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;YAC3C,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5D,OAAO,GAAG,KAAK,CAAC;gBAChB,MAAM;YACP,CAAC;QACF,CAAC;QACD,IAAI,OAAO;YAAE,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC,CAAC;AAEF,4EAA4E;AAC5E,+EAA+E;AAC/E,yEAAyE;AACzE,gFAAgF;AAChF,oBAAoB;AACpB,MAAM,uBAAuB,GAAG,CAC/B,YAAoC,EACpC,UAAgC,EAChB,EAAE;IAClB,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1C,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,QAAQ,GAAG,mBAAmB,CAAC,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;YACzE,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;gBAClB,OAAO,CAAC,IAAI,CAAC;oBACZ,YAAY,EAAE,YAAY,CAAC,KAAK,
|
|
1
|
+
{"version":3,"file":"sequence-aligner.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/sequence-aligner.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAlignmentResult,\n\tAlignmentSegment,\n\tMathAlignmentToken,\n\tMathMLTokenizationResult,\n\tSpeechAlignmentToken,\n} from \"./types.js\";\nimport type { SpeechSourceTokenization } from \"./speech-tokenizer.js\";\n\nconst MIN_EXACT_WORD_CONFIDENCE = 0.95;\n\ninterface PhraseMatch {\n\tspeechTokens: SpeechAlignmentToken[];\n\tmath: MathAlignmentToken;\n}\n\n// Candidate spoken forms for a math token: its own normalized value plus every\n// spoken alias, each split into the sequence of words a TTS engine emits (e.g.\n// \"÷\" → [\"divided\", \"by\"], \"≤\" → [\"less\", \"than\", \"or\", \"equal\", \"to\"]). This is\n// what lets multi-word operator names match — a single speech token can never\n// equal \"divided by\". Speech tokens are already number-word normalized by the\n// speech tokenizer (two → 2), so no numeric mapping is repeated here.\nconst candidatePhrasesFor = (math: MathAlignmentToken): string[][] => {\n\tconst phrases: string[][] = [];\n\tconst seen = new Set<string>();\n\tconst add = (value: string): void => {\n\t\tconst words = value.toLowerCase().trim().split(/\\s+/).filter(Boolean);\n\t\tif (words.length === 0) return;\n\t\tconst key = words.join(\" \");\n\t\tif (seen.has(key)) return;\n\t\tseen.add(key);\n\t\tphrases.push(words);\n\t};\n\tadd(math.normalized);\n\tfor (const alias of math.spokenAliases) add(alias);\n\treturn phrases;\n};\n\n// Length (in speech tokens) of the longest candidate phrase that matches the\n// run of speech tokens starting at `startIndex`, or 0 if none match. Longest\n// match wins so a multi-word alias is preferred over a coincidental one-word\n// prefix.\nconst matchPhraseLengthAt = (\n\tspeechTokens: SpeechAlignmentToken[],\n\tstartIndex: number,\n\tphrases: string[][],\n): number => {\n\tlet best = 0;\n\tfor (const phrase of phrases) {\n\t\tif (phrase.length <= best) continue;\n\t\tlet matched = true;\n\t\tfor (let i = 0; i < phrase.length; i++) {\n\t\t\tconst token = speechTokens[startIndex + i];\n\t\t\tif (!token || token.normalized.toLowerCase() !== phrase[i]) {\n\t\t\t\tmatched = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (matched) best = phrase.length;\n\t}\n\treturn best;\n};\n\n// Greedy forward (monotonic) pass: each math token consumes the next run of\n// speech tokens that spells one of its candidate phrases. Optional math tokens\n// (invisible operators, grouping fences) are consumed only when present.\n// Unmatched speech tokens are skipped, which the perfect-cover check below then\n// treats as a miss.\nconst computeMonotonicMatches = (\n\tspeechTokens: SpeechAlignmentToken[],\n\tmathTokens: MathAlignmentToken[],\n): PhraseMatch[] => {\n\tconst matches: PhraseMatch[] = [];\n\tlet speechIndex = 0;\n\n\tfor (const math of mathTokens) {\n\t\tconst phrases = candidatePhrasesFor(math);\n\t\tif (math.optional) {\n\t\t\tconst consumed = matchPhraseLengthAt(speechTokens, speechIndex, phrases);\n\t\t\tif (consumed > 0) {\n\t\t\t\tmatches.push({\n\t\t\t\t\tspeechTokens: speechTokens.slice(speechIndex, speechIndex + consumed),\n\t\t\t\t\tmath,\n\t\t\t\t});\n\t\t\t\tspeechIndex += consumed;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\twhile (speechIndex < speechTokens.length) {\n\t\t\tconst consumed = matchPhraseLengthAt(speechTokens, speechIndex, phrases);\n\t\t\tif (consumed > 0) {\n\t\t\t\tmatches.push({\n\t\t\t\t\tspeechTokens: speechTokens.slice(speechIndex, speechIndex + consumed),\n\t\t\t\t\tmath,\n\t\t\t\t});\n\t\t\t\tspeechIndex += consumed;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tspeechIndex++;\n\t\t}\n\t}\n\n\treturn matches;\n};\n\n// Word-level highlighting is all-or-nothing: it is emitted only when every\n// required math token AND every spoken token is accounted for. Anything less\n// falls back to a coarse whole-expression highlight, because \"false positives\n// are worse than coarse highlighting\" — a partial alignment risks underlining\n// the wrong glyph.\nconst isPerfectCover = (\n\tmatches: PhraseMatch[],\n\tspeechTokens: SpeechAlignmentToken[],\n\tmathTokens: MathAlignmentToken[],\n): boolean => {\n\tconst requiredTokenCount = mathTokens.filter(\n\t\t(token) => !token.optional,\n\t).length;\n\tconst requiredMatched = matches.filter(\n\t\t(match) => !match.math.optional,\n\t).length;\n\tconst consumedSpeech = matches.reduce(\n\t\t(total, match) => total + match.speechTokens.length,\n\t\t0,\n\t);\n\treturn (\n\t\trequiredTokenCount > 0 &&\n\t\trequiredMatched === requiredTokenCount &&\n\t\tconsumedSpeech === speechTokens.length\n\t);\n};\n\nconst createSegments = (matches: PhraseMatch[]): AlignmentSegment[] =>\n\tmatches.map(({ speechTokens, math }) => ({\n\t\t// Every spoken word that spells this math token maps back to it, so a\n\t\t// boundary on any of those words (\"divided\" or \"by\") resolves to the\n\t\t// same glyph.\n\t\tspeechTokenIds: speechTokens.map((token) => token.id),\n\t\tmathTokenIds: [math.id],\n\t\tscore: 1,\n\t\tconfidence: 1,\n\t\ttarget: math.target,\n\t}));\n\nexport const alignSpeechToMath = (args: {\n\tspeech: SpeechSourceTokenization;\n\tmath: MathMLTokenizationResult;\n\tminExactWordConfidence?: number;\n}): AlignmentResult => {\n\tconst minConfidence =\n\t\targs.minExactWordConfidence ?? MIN_EXACT_WORD_CONFIDENCE;\n\tconst matches = computeMonotonicMatches(args.speech.tokens, args.math.tokens);\n\tconst confidence = isPerfectCover(\n\t\tmatches,\n\t\targs.speech.tokens,\n\t\targs.math.tokens,\n\t)\n\t\t? 1\n\t\t: 0;\n\n\treturn {\n\t\tsegments: confidence >= minConfidence ? createSegments(matches) : [],\n\t\tconfidence,\n\t\tfallbackTarget: args.math.expressionTarget,\n\t};\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"speech-tokenizer.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/speech-tokenizer.ts"],"names":[],"mappings":"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","sourcesContent":["import { extractSpokenText } from \"../ssml/spoken-text.js\";\nimport type {\n\tBoundaryOffsetSpace,\n\tSpeechAlignmentToken,\n} from \"./types.js\";\nimport { createSpeechAlignmentTokenPattern } from \"../text-processing.js\";\n\nexport interface SpeechSourceTokenization {\n\tspeechText: string;\n\tspokenText: string;\n\ttokens: SpeechAlignmentToken[];\n\trawToSpokenOffsetMap: Map<number, number>;\n\tboundaryOffsetSpace: BoundaryOffsetSpace;\n\tunsupportedSemantic: boolean;\n\thasMarkup: boolean;\n}\n\nexport interface ResolvedSpeechBoundary {\n\ttoken: SpeechAlignmentToken;\n\tstart: number;\n\tlength: number;\n\tconfidence: number;\n}\n\n// Shared Unicode-aware tokenizer, identical to the catalog span aligner's, so\n// spoken text tokenizes the same way on both paths (see\n// `createSpeechAlignmentTokenPattern`).\nconst TOKEN_PATTERN = createSpeechAlignmentTokenPattern();\n\nconst NUMERIC_WORDS = new Map<string, string>([\n\t[\"zero\", \"0\"],\n\t[\"one\", \"1\"],\n\t[\"two\", \"2\"],\n\t[\"three\", \"3\"],\n\t[\"four\", \"4\"],\n\t[\"five\", \"5\"],\n\t[\"six\", \"6\"],\n\t[\"seven\", \"7\"],\n\t[\"eight\", \"8\"],\n\t[\"nine\", \"9\"],\n\t[\"ten\", \"10\"],\n]);\n\nconst normalizedTokenValue = (value: string): string => {\n\tconst normalized = value.toLowerCase();\n\treturn NUMERIC_WORDS.get(normalized) || normalized;\n};\n\nconst tokenizeSpokenText = (spokenText: string): SpeechAlignmentToken[] => {\n\tconst tokens: SpeechAlignmentToken[] = [];\n\tfor (const match of spokenText.matchAll(TOKEN_PATTERN)) {\n\t\tconst text = match[0];\n\t\tconst start = match.index ?? 0;\n\t\ttokens.push({\n\t\t\tid: `speech-token-${tokens.length}`,\n\t\t\ttext,\n\t\t\tnormalized: normalizedTokenValue(text),\n\t\t\tstart,\n\t\t\tend: start + text.length,\n\t\t\tsourceStart: start,\n\t\t\tsourceEnd: start + text.length,\n\t\t\tcoordinateSystem: \"normalized-speech\",\n\t\t});\n\t}\n\treturn tokens;\n};\n\nexport const tokenizeSpeechSource = (args: {\n\tspeechText: string;\n}): SpeechSourceTokenization => {\n\tconst extracted = extractSpokenText(args.speechText);\n\treturn {\n\t\tspeechText: args.speechText,\n\t\tspokenText: extracted.spokenText,\n\t\ttokens: tokenizeSpokenText(extracted.spokenText),\n\t\trawToSpokenOffsetMap: extracted.rawToSpokenOffsetMap,\n\t\tboundaryOffsetSpace: extracted.unsupportedSemantic\n\t\t\t? \"unsupported\"\n\t\t\t: extracted.hasMarkup\n\t\t\t\t? \"raw-ssml\"\n\t\t\t\t: \"plain-spoken-text\",\n\t\tunsupportedSemantic: extracted.unsupportedSemantic,\n\t\thasMarkup: extracted.hasMarkup,\n\t};\n};\n\nconst normalizeBoundaryWord = (word?: string): string | null => {\n\tif (!word) return null;\n\tif (/^<[^>]+>$/.test(word.trim())) return null;\n\tconst match = word.match(TOKEN_PATTERN);\n\tif (!match?.[0]) return null;\n\treturn normalizedTokenValue(match[0]);\n};\n\nconst findTokenAtOffset = (\n\ttokens: SpeechAlignmentToken[],\n\toffset: number,\n): SpeechAlignmentToken | null =>\n\ttokens.find((token) => token.start <= offset && offset < token.end) || null;\n\nconst mapRawOffsetToSpokenOffset = (\n\ttokenization: SpeechSourceTokenization,\n\tposition: number,\n): number | null => {\n\tconst direct = tokenization.rawToSpokenOffsetMap.get(position);\n\tif (direct !== undefined) return direct;\n\tfor (let offset = position; offset < tokenization.speechText.length; offset++) {\n\t\tconst mapped = tokenization.rawToSpokenOffsetMap.get(offset);\n\t\tif (mapped !== undefined) return mapped;\n\t\tif (tokenization.speechText[offset] === \">\") break;\n\t}\n\treturn null;\n};\n\nconst candidateForOffset = (\n\ttokenization: SpeechSourceTokenization,\n\toffset: number | null,\n\tlength: number,\n): ResolvedSpeechBoundary | null => {\n\tif (offset === null) return null;\n\tconst token = findTokenAtOffset(tokenization.tokens, offset);\n\tif (!token) return null;\n\treturn {\n\t\ttoken,\n\t\tstart: offset,\n\t\tlength: Math.max(1, Math.min(length, token.end - offset)),\n\t\tconfidence: 0.75,\n\t};\n};\n\nconst candidateMatchesWord = (\n\tcandidate: ResolvedSpeechBoundary | null,\n\tboundaryWord?: string,\n): boolean => {\n\tconst normalizedBoundary = normalizeBoundaryWord(boundaryWord);\n\treturn Boolean(\n\t\tcandidate &&\n\t\t\tnormalizedBoundary &&\n\t\t\tcandidate.token.normalized === normalizedBoundary,\n\t);\n};\n\nexport const resolveBoundaryToSpeechToken = (args: {\n\ttokenization: SpeechSourceTokenization;\n\tposition: number;\n\tlength?: number;\n\tboundaryWord?: string;\n}): ResolvedSpeechBoundary | null => {\n\tif (\n\t\targs.tokenization.boundaryOffsetSpace === \"unsupported\" ||\n\t\t!Number.isFinite(args.position) ||\n\t\t(args.boundaryWord && /^<[^>]+>$/.test(args.boundaryWord.trim()))\n\t) {\n\t\treturn null;\n\t}\n\tconst safeLength = Math.max(1, Number.isFinite(args.length) ? args.length! : 1);\n\tconst rawCandidate = candidateForOffset(\n\t\targs.tokenization,\n\t\tmapRawOffsetToSpokenOffset(args.tokenization, args.position),\n\t\tsafeLength,\n\t);\n\tconst plainCandidate = candidateForOffset(\n\t\targs.tokenization,\n\t\targs.position >= 0 && args.position < args.tokenization.spokenText.length\n\t\t\t? args.position\n\t\t\t: null,\n\t\tsafeLength,\n\t);\n\n\tif (candidateMatchesWord(rawCandidate, args.boundaryWord)) {\n\t\treturn { ...rawCandidate!, confidence: 1 };\n\t}\n\tif (candidateMatchesWord(plainCandidate, args.boundaryWord)) {\n\t\treturn { ...plainCandidate!, confidence: 1 };\n\t}\n\tif (args.boundaryWord && normalizeBoundaryWord(args.boundaryWord)) {\n\t\treturn null;\n\t}\n\n\treturn args.tokenization.boundaryOffsetSpace === \"raw-ssml\"\n\t\t? rawCandidate || plainCandidate\n\t\t: plainCandidate || rawCandidate;\n};\n\nexport const resolveSpokenOffsetToSpeechToken = (args: {\n\ttokenization: SpeechSourceTokenization;\n\tposition: number;\n\tlength?: number;\n\tboundaryWord?: string;\n}): ResolvedSpeechBoundary | null => {\n\tif (\n\t\targs.tokenization.boundaryOffsetSpace === \"unsupported\" ||\n\t\t!Number.isFinite(args.position) ||\n\t\t(args.boundaryWord && /^<[^>]+>$/.test(args.boundaryWord.trim()))\n\t) {\n\t\treturn null;\n\t}\n\tconst safeLength = Math.max(1, Number.isFinite(args.length) ? args.length! : 1);\n\tconst spokenCandidate = candidateForOffset(\n\t\targs.tokenization,\n\t\targs.position >= 0 && args.position < args.tokenization.spokenText.length\n\t\t\t? args.position\n\t\t\t: null,\n\t\tsafeLength,\n\t);\n\tif (candidateMatchesWord(spokenCandidate, args.boundaryWord)) {\n\t\treturn { ...spokenCandidate!, confidence: 1 };\n\t}\n\tif (args.boundaryWord && normalizeBoundaryWord(args.boundaryWord)) {\n\t\treturn null;\n\t}\n\treturn spokenCandidate;\n};\n"]}
|
|
1
|
+
{"version":3,"file":"speech-tokenizer.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/speech-tokenizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAmB1E,8EAA8E;AAC9E,wDAAwD;AACxD,wCAAwC;AACxC,MAAM,aAAa,GAAG,iCAAiC,EAAE,CAAC;AAE1D,MAAM,aAAa,GAAG,IAAI,GAAG,CAAiB;IAC7C,CAAC,MAAM,EAAE,GAAG,CAAC;IACb,CAAC,KAAK,EAAE,GAAG,CAAC;IACZ,CAAC,KAAK,EAAE,GAAG,CAAC;IACZ,CAAC,OAAO,EAAE,GAAG,CAAC;IACd,CAAC,MAAM,EAAE,GAAG,CAAC;IACb,CAAC,MAAM,EAAE,GAAG,CAAC;IACb,CAAC,KAAK,EAAE,GAAG,CAAC;IACZ,CAAC,OAAO,EAAE,GAAG,CAAC;IACd,CAAC,OAAO,EAAE,GAAG,CAAC;IACd,CAAC,MAAM,EAAE,GAAG,CAAC;IACb,CAAC,KAAK,EAAE,IAAI,CAAC;CACb,CAAC,CAAC;AAEH,MAAM,oBAAoB,GAAG,CAAC,KAAa,EAAU,EAAE;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IACvC,OAAO,aAAa,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC;AACpD,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,UAAkB,EAA0B,EAAE;IACzE,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC;YACX,EAAE,EAAE,gBAAgB,MAAM,CAAC,MAAM,EAAE;YACnC,IAAI;YACJ,UAAU,EAAE,oBAAoB,CAAC,IAAI,CAAC;YACtC,KAAK;YACL,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM;YACxB,WAAW,EAAE,KAAK;YAClB,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM;YAC9B,gBAAgB,EAAE,mBAAmB;SACrC,CAAC,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAEpC,EAA4B,EAAE;IAC9B,MAAM,SAAS,GAAG,iBAAiB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrD,OAAO;QACN,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,SAAS,CAAC,UAAU;QAChC,MAAM,EAAE,kBAAkB,CAAC,SAAS,CAAC,UAAU,CAAC;QAChD,oBAAoB,EAAE,SAAS,CAAC,oBAAoB;QACpD,mBAAmB,EAAE,SAAS,CAAC,mBAAmB;YACjD,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,SAAS,CAAC,SAAS;gBACpB,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,mBAAmB;QACvB,mBAAmB,EAAE,SAAS,CAAC,mBAAmB;QAClD,SAAS,EAAE,SAAS,CAAC,SAAS;KAC9B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,IAAa,EAAiB,EAAE;IAC9D,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7B,OAAO,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvC,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CACzB,MAA8B,EAC9B,MAAc,EACgB,EAAE,CAChC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AAE7E,MAAM,0BAA0B,GAAG,CAClC,YAAsC,EACtC,QAAgB,EACA,EAAE;IAClB,MAAM,MAAM,GAAG,YAAY,CAAC,oBAAoB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/D,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,KACC,IAAI,MAAM,GAAG,QAAQ,EACrB,MAAM,GAAG,YAAY,CAAC,UAAU,CAAC,MAAM,EACvC,MAAM,EAAE,EACP,CAAC;QACF,MAAM,MAAM,GAAG,YAAY,CAAC,oBAAoB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7D,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACxC,IAAI,YAAY,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,GAAG;YAAE,MAAM;IACpD,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAC1B,YAAsC,EACtC,MAAqB,EACrB,MAAc,EACkB,EAAE;IAClC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,KAAK,GAAG,iBAAiB,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO;QACN,KAAK;QACL,KAAK,EAAE,MAAM;QACb,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC;QACzD,UAAU,EAAE,IAAI;KAChB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC5B,SAAwC,EACxC,YAAqB,EACX,EAAE;IACZ,MAAM,kBAAkB,GAAG,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAC/D,OAAO,OAAO,CACb,SAAS;QACR,kBAAkB;QAClB,SAAS,CAAC,KAAK,CAAC,UAAU,KAAK,kBAAkB,CAClD,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,IAK5C,EAAiC,EAAE;IACnC,IACC,IAAI,CAAC,YAAY,CAAC,mBAAmB,KAAK,aAAa;QACvD,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC/B,CAAC,IAAI,CAAC,YAAY,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,EAChE,CAAC;QACF,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAC1B,CAAC,EACD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC,CAAC,CAAC,CAC/C,CAAC;IACF,MAAM,YAAY,GAAG,kBAAkB,CACtC,IAAI,CAAC,YAAY,EACjB,0BAA0B,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,QAAQ,CAAC,EAC5D,UAAU,CACV,CAAC;IACF,MAAM,cAAc,GAAG,kBAAkB,CACxC,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM;QACxE,CAAC,CAAC,IAAI,CAAC,QAAQ;QACf,CAAC,CAAC,IAAI,EACP,UAAU,CACV,CAAC;IAEF,IAAI,oBAAoB,CAAC,YAAY,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC3D,OAAO,EAAE,GAAG,YAAa,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC5C,CAAC;IACD,IAAI,oBAAoB,CAAC,cAAc,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7D,OAAO,EAAE,GAAG,cAAe,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,IAAI,qBAAqB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC;IACb,CAAC;IAED,OAAO,IAAI,CAAC,YAAY,CAAC,mBAAmB,KAAK,UAAU;QAC1D,CAAC,CAAC,YAAY,IAAI,cAAc;QAChC,CAAC,CAAC,cAAc,IAAI,YAAY,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,IAKhD,EAAiC,EAAE;IACnC,IACC,IAAI,CAAC,YAAY,CAAC,mBAAmB,KAAK,aAAa;QACvD,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC/B,CAAC,IAAI,CAAC,YAAY,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,EAChE,CAAC;QACF,OAAO,IAAI,CAAC;IACb,CAAC;IACD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAC1B,CAAC,EACD,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC,CAAC,CAAC,CAC/C,CAAC;IACF,MAAM,eAAe,GAAG,kBAAkB,CACzC,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,MAAM;QACxE,CAAC,CAAC,IAAI,CAAC,QAAQ;QACf,CAAC,CAAC,IAAI,EACP,UAAU,CACV,CAAC;IACF,IAAI,oBAAoB,CAAC,eAAe,EAAE,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9D,OAAO,EAAE,GAAG,eAAgB,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,IAAI,CAAC,YAAY,IAAI,qBAAqB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACnE,OAAO,IAAI,CAAC;IACb,CAAC;IACD,OAAO,eAAe,CAAC;AACxB,CAAC,CAAC","sourcesContent":["import { extractSpokenText } from \"../ssml/spoken-text.js\";\nimport type { BoundaryOffsetSpace, SpeechAlignmentToken } from \"./types.js\";\nimport { createSpeechAlignmentTokenPattern } from \"../text-processing.js\";\n\nexport interface SpeechSourceTokenization {\n\tspeechText: string;\n\tspokenText: string;\n\ttokens: SpeechAlignmentToken[];\n\trawToSpokenOffsetMap: Map<number, number>;\n\tboundaryOffsetSpace: BoundaryOffsetSpace;\n\tunsupportedSemantic: boolean;\n\thasMarkup: boolean;\n}\n\nexport interface ResolvedSpeechBoundary {\n\ttoken: SpeechAlignmentToken;\n\tstart: number;\n\tlength: number;\n\tconfidence: number;\n}\n\n// Shared Unicode-aware tokenizer, identical to the catalog span aligner's, so\n// spoken text tokenizes the same way on both paths (see\n// `createSpeechAlignmentTokenPattern`).\nconst TOKEN_PATTERN = createSpeechAlignmentTokenPattern();\n\nconst NUMERIC_WORDS = new Map<string, string>([\n\t[\"zero\", \"0\"],\n\t[\"one\", \"1\"],\n\t[\"two\", \"2\"],\n\t[\"three\", \"3\"],\n\t[\"four\", \"4\"],\n\t[\"five\", \"5\"],\n\t[\"six\", \"6\"],\n\t[\"seven\", \"7\"],\n\t[\"eight\", \"8\"],\n\t[\"nine\", \"9\"],\n\t[\"ten\", \"10\"],\n]);\n\nconst normalizedTokenValue = (value: string): string => {\n\tconst normalized = value.toLowerCase();\n\treturn NUMERIC_WORDS.get(normalized) || normalized;\n};\n\nconst tokenizeSpokenText = (spokenText: string): SpeechAlignmentToken[] => {\n\tconst tokens: SpeechAlignmentToken[] = [];\n\tfor (const match of spokenText.matchAll(TOKEN_PATTERN)) {\n\t\tconst text = match[0];\n\t\tconst start = match.index ?? 0;\n\t\ttokens.push({\n\t\t\tid: `speech-token-${tokens.length}`,\n\t\t\ttext,\n\t\t\tnormalized: normalizedTokenValue(text),\n\t\t\tstart,\n\t\t\tend: start + text.length,\n\t\t\tsourceStart: start,\n\t\t\tsourceEnd: start + text.length,\n\t\t\tcoordinateSystem: \"normalized-speech\",\n\t\t});\n\t}\n\treturn tokens;\n};\n\nexport const tokenizeSpeechSource = (args: {\n\tspeechText: string;\n}): SpeechSourceTokenization => {\n\tconst extracted = extractSpokenText(args.speechText);\n\treturn {\n\t\tspeechText: args.speechText,\n\t\tspokenText: extracted.spokenText,\n\t\ttokens: tokenizeSpokenText(extracted.spokenText),\n\t\trawToSpokenOffsetMap: extracted.rawToSpokenOffsetMap,\n\t\tboundaryOffsetSpace: extracted.unsupportedSemantic\n\t\t\t? \"unsupported\"\n\t\t\t: extracted.hasMarkup\n\t\t\t\t? \"raw-ssml\"\n\t\t\t\t: \"plain-spoken-text\",\n\t\tunsupportedSemantic: extracted.unsupportedSemantic,\n\t\thasMarkup: extracted.hasMarkup,\n\t};\n};\n\nconst normalizeBoundaryWord = (word?: string): string | null => {\n\tif (!word) return null;\n\tif (/^<[^>]+>$/.test(word.trim())) return null;\n\tconst match = word.match(TOKEN_PATTERN);\n\tif (!match?.[0]) return null;\n\treturn normalizedTokenValue(match[0]);\n};\n\nconst findTokenAtOffset = (\n\ttokens: SpeechAlignmentToken[],\n\toffset: number,\n): SpeechAlignmentToken | null =>\n\ttokens.find((token) => token.start <= offset && offset < token.end) || null;\n\nconst mapRawOffsetToSpokenOffset = (\n\ttokenization: SpeechSourceTokenization,\n\tposition: number,\n): number | null => {\n\tconst direct = tokenization.rawToSpokenOffsetMap.get(position);\n\tif (direct !== undefined) return direct;\n\tfor (\n\t\tlet offset = position;\n\t\toffset < tokenization.speechText.length;\n\t\toffset++\n\t) {\n\t\tconst mapped = tokenization.rawToSpokenOffsetMap.get(offset);\n\t\tif (mapped !== undefined) return mapped;\n\t\tif (tokenization.speechText[offset] === \">\") break;\n\t}\n\treturn null;\n};\n\nconst candidateForOffset = (\n\ttokenization: SpeechSourceTokenization,\n\toffset: number | null,\n\tlength: number,\n): ResolvedSpeechBoundary | null => {\n\tif (offset === null) return null;\n\tconst token = findTokenAtOffset(tokenization.tokens, offset);\n\tif (!token) return null;\n\treturn {\n\t\ttoken,\n\t\tstart: offset,\n\t\tlength: Math.max(1, Math.min(length, token.end - offset)),\n\t\tconfidence: 0.75,\n\t};\n};\n\nconst candidateMatchesWord = (\n\tcandidate: ResolvedSpeechBoundary | null,\n\tboundaryWord?: string,\n): boolean => {\n\tconst normalizedBoundary = normalizeBoundaryWord(boundaryWord);\n\treturn Boolean(\n\t\tcandidate &&\n\t\t\tnormalizedBoundary &&\n\t\t\tcandidate.token.normalized === normalizedBoundary,\n\t);\n};\n\nexport const resolveBoundaryToSpeechToken = (args: {\n\ttokenization: SpeechSourceTokenization;\n\tposition: number;\n\tlength?: number;\n\tboundaryWord?: string;\n}): ResolvedSpeechBoundary | null => {\n\tif (\n\t\targs.tokenization.boundaryOffsetSpace === \"unsupported\" ||\n\t\t!Number.isFinite(args.position) ||\n\t\t(args.boundaryWord && /^<[^>]+>$/.test(args.boundaryWord.trim()))\n\t) {\n\t\treturn null;\n\t}\n\tconst safeLength = Math.max(\n\t\t1,\n\t\tNumber.isFinite(args.length) ? args.length! : 1,\n\t);\n\tconst rawCandidate = candidateForOffset(\n\t\targs.tokenization,\n\t\tmapRawOffsetToSpokenOffset(args.tokenization, args.position),\n\t\tsafeLength,\n\t);\n\tconst plainCandidate = candidateForOffset(\n\t\targs.tokenization,\n\t\targs.position >= 0 && args.position < args.tokenization.spokenText.length\n\t\t\t? args.position\n\t\t\t: null,\n\t\tsafeLength,\n\t);\n\n\tif (candidateMatchesWord(rawCandidate, args.boundaryWord)) {\n\t\treturn { ...rawCandidate!, confidence: 1 };\n\t}\n\tif (candidateMatchesWord(plainCandidate, args.boundaryWord)) {\n\t\treturn { ...plainCandidate!, confidence: 1 };\n\t}\n\tif (args.boundaryWord && normalizeBoundaryWord(args.boundaryWord)) {\n\t\treturn null;\n\t}\n\n\treturn args.tokenization.boundaryOffsetSpace === \"raw-ssml\"\n\t\t? rawCandidate || plainCandidate\n\t\t: plainCandidate || rawCandidate;\n};\n\nexport const resolveSpokenOffsetToSpeechToken = (args: {\n\ttokenization: SpeechSourceTokenization;\n\tposition: number;\n\tlength?: number;\n\tboundaryWord?: string;\n}): ResolvedSpeechBoundary | null => {\n\tif (\n\t\targs.tokenization.boundaryOffsetSpace === \"unsupported\" ||\n\t\t!Number.isFinite(args.position) ||\n\t\t(args.boundaryWord && /^<[^>]+>$/.test(args.boundaryWord.trim()))\n\t) {\n\t\treturn null;\n\t}\n\tconst safeLength = Math.max(\n\t\t1,\n\t\tNumber.isFinite(args.length) ? args.length! : 1,\n\t);\n\tconst spokenCandidate = candidateForOffset(\n\t\targs.tokenization,\n\t\targs.position >= 0 && args.position < args.tokenization.spokenText.length\n\t\t\t? args.position\n\t\t\t: null,\n\t\tsafeLength,\n\t);\n\tif (candidateMatchesWord(spokenCandidate, args.boundaryWord)) {\n\t\treturn { ...spokenCandidate!, confidence: 1 };\n\t}\n\tif (args.boundaryWord && normalizeBoundaryWord(args.boundaryWord)) {\n\t\treturn null;\n\t}\n\treturn spokenCandidate;\n};\n"]}
|
|
@@ -274,7 +274,11 @@ const collectMathAware = (root, options) => {
|
|
|
274
274
|
const flushTextChunk = () => {
|
|
275
275
|
const text = normalizeTextForSpeech(acc.chars.slice(textChunkStart, acc.chars.length).join(""));
|
|
276
276
|
if (text) {
|
|
277
|
-
chunks.push({
|
|
277
|
+
chunks.push({
|
|
278
|
+
type: "text",
|
|
279
|
+
text,
|
|
280
|
+
sourceElement: textChunkSourceElement,
|
|
281
|
+
});
|
|
278
282
|
}
|
|
279
283
|
textChunkStart = acc.chars.length;
|
|
280
284
|
textChunkSourceElement = undefined;
|
|
@@ -284,8 +288,7 @@ const collectMathAware = (root, options) => {
|
|
|
284
288
|
return;
|
|
285
289
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
286
290
|
const sourceElement = resolveTextChunkSourceElement(node, root);
|
|
287
|
-
if (textChunkSourceElement &&
|
|
288
|
-
sourceElement !== textChunkSourceElement) {
|
|
291
|
+
if (textChunkSourceElement && sourceElement !== textChunkSourceElement) {
|
|
289
292
|
flushTextChunk();
|
|
290
293
|
}
|
|
291
294
|
textChunkSourceElement = sourceElement;
|