@pie-players/pie-assessment-toolkit 0.3.44 → 0.3.45
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/README.md +54 -69
- package/dist/components/ItemToolBar.custom-element.js +479 -324
- package/dist/components/PieAssessmentToolkit.custom-element.js +19498 -3590
- package/dist/components/SectionToolBar.custom-element.js +761 -507
- package/dist/index.d.ts +3 -6
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +1 -2
- package/dist/policy/core/ToolPolicyEngine.js +1 -2
- package/dist/policy/core/ToolPolicyEngine.js.map +1 -1
- package/dist/policy/core/pnp-policy-inputs.d.ts +2 -3
- package/dist/policy/core/pnp-policy-inputs.js +2 -3
- package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
- package/dist/policy/core/provenance.d.ts +7 -34
- package/dist/policy/core/provenance.js +4 -21
- package/dist/policy/core/provenance.js.map +1 -1
- package/dist/policy/sources/PnpPolicySource.d.ts +6 -19
- package/dist/policy/sources/PnpPolicySource.js +6 -19
- package/dist/policy/sources/PnpPolicySource.js.map +1 -1
- package/dist/runtime/SectionRuntimeEngine.d.ts +13 -28
- package/dist/runtime/SectionRuntimeEngine.js +13 -27
- package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +2 -2
- package/dist/runtime/adapter/SectionEngineAdapter.js +2 -2
- package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -1
- package/dist/runtime/adapter/coordinator-bridge.d.ts +1 -1
- package/dist/runtime/adapter/coordinator-bridge.js +1 -1
- package/dist/runtime/adapter/coordinator-bridge.js.map +1 -1
- package/dist/runtime/adapter/dom-event-bridge.d.ts +1 -1
- package/dist/runtime/adapter/dom-event-bridge.js +1 -1
- package/dist/runtime/adapter/dom-event-bridge.js.map +1 -1
- package/dist/runtime/catalog-registration.d.ts +12 -0
- package/dist/runtime/catalog-registration.js +54 -0
- package/dist/runtime/catalog-registration.js.map +1 -0
- package/dist/runtime/core/engine-output.d.ts +2 -2
- package/dist/runtime/core/engine-output.js +2 -2
- package/dist/runtime/core/engine-output.js.map +1 -1
- package/dist/runtime/core/engine-readiness.d.ts +2 -2
- package/dist/runtime/core/engine-readiness.js.map +1 -1
- package/dist/runtime/core/engine-resolver.d.ts +13 -41
- package/dist/runtime/core/engine-resolver.js +19 -76
- package/dist/runtime/core/engine-resolver.js.map +1 -1
- package/dist/runtime/core/engine-transition.d.ts +1 -1
- package/dist/runtime/core/engine-transition.js +1 -1
- package/dist/runtime/core/engine-transition.js.map +1 -1
- package/dist/runtime/engine.d.ts +1 -1
- package/dist/runtime/engine.js.map +1 -1
- package/dist/runtime/section-runtime-engine-host-context.d.ts +22 -15
- package/dist/runtime/section-runtime-engine-host-context.js +10 -10
- package/dist/runtime/section-runtime-engine-host-context.js.map +1 -1
- package/dist/runtime/stage-emit-gate.d.ts +4 -4
- package/dist/runtime/stage-emit-gate.js +3 -3
- package/dist/runtime/stage-emit-gate.js.map +1 -1
- package/dist/services/AccessibilityCatalogResolver.d.ts +23 -0
- package/dist/services/AccessibilityCatalogResolver.js +200 -28
- package/dist/services/AccessibilityCatalogResolver.js.map +1 -1
- package/dist/services/HighlightCoordinator.d.ts +25 -0
- package/dist/services/HighlightCoordinator.js +125 -0
- package/dist/services/HighlightCoordinator.js.map +1 -1
- package/dist/services/SSMLExtractor.d.ts +16 -1
- package/dist/services/SSMLExtractor.js +86 -7
- package/dist/services/SSMLExtractor.js.map +1 -1
- package/dist/services/TTSService.d.ts +30 -4
- package/dist/services/TTSService.js +652 -159
- package/dist/services/TTSService.js.map +1 -1
- package/dist/services/ToolRegistry.d.ts +2 -2
- package/dist/services/ToolRegistry.js +1 -1
- package/dist/services/ToolRegistry.js.map +1 -1
- package/dist/services/ToolkitCoordinator.d.ts +16 -73
- package/dist/services/ToolkitCoordinator.js +34 -113
- package/dist/services/ToolkitCoordinator.js.map +1 -1
- package/dist/services/interfaces.d.ts +20 -2
- package/dist/services/interfaces.js.map +1 -1
- package/dist/services/tool-config-validation.d.ts +1 -1
- package/dist/services/tool-config-validation.js +7 -7
- package/dist/services/tool-config-validation.js.map +1 -1
- package/dist/services/tool-instance-id.js +1 -1
- package/dist/services/tool-instance-id.js.map +1 -1
- package/dist/services/tool-providers/TTSToolProvider.js +3 -3
- package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
- package/dist/services/tools-config-normalizer.d.ts +13 -2
- package/dist/services/tools-config-normalizer.js +3 -8
- package/dist/services/tools-config-normalizer.js.map +1 -1
- package/dist/services/tts/browser-provider.js +2 -0
- package/dist/services/tts/browser-provider.js.map +1 -1
- package/dist/services/tts/catalog-span-alignment.d.ts +39 -0
- package/dist/services/tts/catalog-span-alignment.js +300 -0
- package/dist/services/tts/catalog-span-alignment.js.map +1 -0
- package/dist/services/tts/generated-speech/assemble-plan.d.ts +33 -0
- package/dist/services/tts/generated-speech/assemble-plan.js +111 -0
- package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +25 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.js +66 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.js.map +1 -0
- package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +19 -0
- package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js +71 -0
- package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js.map +1 -0
- package/dist/services/tts/generated-speech/dom/types.d.ts +37 -0
- package/dist/services/tts/generated-speech/dom/types.js +2 -0
- package/dist/services/tts/generated-speech/dom/types.js.map +1 -0
- package/dist/services/tts/generated-speech/index.d.ts +17 -0
- package/dist/services/tts/generated-speech/index.js +17 -0
- package/dist/services/tts/generated-speech/index.js.map +1 -0
- package/dist/services/tts/generated-speech/math-speech-cache.d.ts +15 -0
- package/dist/services/tts/generated-speech/math-speech-cache.js +78 -0
- package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -0
- package/dist/services/tts/generated-speech/types.d.ts +104 -0
- package/dist/services/tts/generated-speech/types.js +18 -0
- package/dist/services/tts/generated-speech/types.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.d.ts +2 -0
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +106 -0
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.d.ts +6 -0
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +217 -0
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/highlight-policy.d.ts +2 -0
- package/dist/services/tts/highlight-pipeline/highlight-policy.js +32 -0
- package/dist/services/tts/highlight-pipeline/highlight-policy.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/index.d.ts +9 -0
- package/dist/services/tts/highlight-pipeline/index.js +9 -0
- package/dist/services/tts/highlight-pipeline/index.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/math-semantic-resolver.d.ts +3 -0
- package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js +32 -0
- package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.d.ts +29 -0
- package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +44 -0
- package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.d.ts +2 -0
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +20 -0
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/readable-region.d.ts +1 -0
- package/dist/services/tts/highlight-pipeline/readable-region.js +19 -0
- package/dist/services/tts/highlight-pipeline/readable-region.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.d.ts +6 -0
- package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js +198 -0
- package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/types.d.ts +72 -0
- package/dist/services/tts/highlight-pipeline/types.js +2 -0
- package/dist/services/tts/highlight-pipeline/types.js.map +1 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +2 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.js +22 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.js.map +1 -0
- package/dist/services/tts/math-alignment/index.d.ts +30 -0
- package/dist/services/tts/math-alignment/index.js +66 -0
- package/dist/services/tts/math-alignment/index.js.map +1 -0
- package/dist/services/tts/math-alignment/mathml-tokenizer.d.ts +2 -0
- package/dist/services/tts/math-alignment/mathml-tokenizer.js +317 -0
- package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -0
- package/dist/services/tts/math-alignment/range-resolver.d.ts +10 -0
- package/dist/services/tts/math-alignment/range-resolver.js +13 -0
- package/dist/services/tts/math-alignment/range-resolver.js.map +1 -0
- package/dist/services/tts/math-alignment/sequence-aligner.d.ts +7 -0
- package/dist/services/tts/math-alignment/sequence-aligner.js +119 -0
- package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -0
- package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +31 -0
- package/dist/services/tts/math-alignment/speech-tokenizer.js +143 -0
- package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -0
- package/dist/services/tts/math-alignment/types.d.ts +58 -0
- package/dist/services/tts/math-alignment/types.js +2 -0
- package/dist/services/tts/math-alignment/types.js.map +1 -0
- package/dist/services/tts/math-aware-text-processing.d.ts +18 -0
- package/dist/services/tts/math-aware-text-processing.js +348 -0
- package/dist/services/tts/math-aware-text-processing.js.map +1 -0
- package/dist/services/tts/math-speech.d.ts +44 -0
- package/dist/services/tts/math-speech.js +147 -0
- package/dist/services/tts/math-speech.js.map +1 -0
- package/dist/services/tts/mathml-sanitization.d.ts +1 -0
- package/dist/services/tts/mathml-sanitization.js +170 -0
- package/dist/services/tts/mathml-sanitization.js.map +1 -0
- package/dist/services/tts/ssml/spoken-text.d.ts +33 -0
- package/dist/services/tts/ssml/spoken-text.js +159 -0
- package/dist/services/tts/ssml/spoken-text.js.map +1 -0
- package/dist/services/tts/text-processing.d.ts +3 -0
- package/dist/services/tts/text-processing.js +30 -4
- package/dist/services/tts/text-processing.js.map +1 -1
- package/dist/services/tts-runtime-config.d.ts +17 -1
- package/dist/services/tts-runtime-config.js +11 -1
- package/dist/services/tts-runtime-config.js.map +1 -1
- package/dist/tools/registrations/accessibility-tools.js +0 -1
- package/dist/tools/registrations/accessibility-tools.js.map +1 -1
- package/dist/tools/registrations/calculator.js +41 -2
- package/dist/tools/registrations/calculator.js.map +1 -1
- package/dist/tools/tool-tag-map.js +0 -1
- package/dist/tools/tool-tag-map.js.map +1 -1
- package/package.json +8 -11
- package/dist/services/PNPToolResolver.d.ts +0 -280
- package/dist/services/PNPToolResolver.js +0 -514
- package/dist/services/PNPToolResolver.js.map +0 -1
- package/dist/services/pnp-provenance.d.ts +0 -166
- package/dist/services/pnp-provenance.js +0 -231
- package/dist/services/pnp-provenance.js.map +0 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const READABLE_REGION_SELECTOR = [
|
|
2
|
+
"[data-pie-tts-region]",
|
|
3
|
+
"[data-catalog-region]",
|
|
4
|
+
"li",
|
|
5
|
+
"p",
|
|
6
|
+
"label",
|
|
7
|
+
"section",
|
|
8
|
+
"[role='group']",
|
|
9
|
+
"[role='region']",
|
|
10
|
+
"div",
|
|
11
|
+
].join(",");
|
|
12
|
+
export const resolveReadableRegion = (sourceElement, contentRoot) => {
|
|
13
|
+
const region = sourceElement.closest(READABLE_REGION_SELECTOR);
|
|
14
|
+
if (region && contentRoot.contains(region)) {
|
|
15
|
+
return region;
|
|
16
|
+
}
|
|
17
|
+
return sourceElement;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=readable-region.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readable-region.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/readable-region.ts"],"names":[],"mappings":"AAAA,MAAM,wBAAwB,GAAG;IAChC,uBAAuB;IACvB,uBAAuB;IACvB,IAAI;IACJ,GAAG;IACH,OAAO;IACP,SAAS;IACT,gBAAgB;IAChB,iBAAiB;IACjB,KAAK;CACL,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEZ,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACpC,aAAsB,EACtB,WAAoB,EACV,EAAE;IACZ,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,wBAAwB,CAAC,CAAC;IAC/D,IAAI,MAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,OAAO,MAAM,CAAC;IACf,CAAC;IACD,OAAO,aAAa,CAAC;AACtB,CAAC,CAAC","sourcesContent":["const READABLE_REGION_SELECTOR = [\n\t\"[data-pie-tts-region]\",\n\t\"[data-catalog-region]\",\n\t\"li\",\n\t\"p\",\n\t\"label\",\n\t\"section\",\n\t\"[role='group']\",\n\t\"[role='region']\",\n\t\"div\",\n].join(\",\");\n\nexport const resolveReadableRegion = (\n\tsourceElement: Element,\n\tcontentRoot: Element,\n): Element => {\n\tconst region = sourceElement.closest(READABLE_REGION_SELECTOR);\n\tif (region && contentRoot.contains(region)) {\n\t\treturn region;\n\t}\n\treturn sourceElement;\n};\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SemanticHighlightTarget, RenderableHighlightTarget } from "./types.js";
|
|
2
|
+
export type MathHighlightCapability = "token" | "expression";
|
|
3
|
+
export declare const classifyMathHighlightCapability: (mathElement: Element) => MathHighlightCapability;
|
|
4
|
+
export type RenderedMathTargetResolver = (target: SemanticHighlightTarget) => RenderableHighlightTarget | null;
|
|
5
|
+
export declare const resolveRenderedMathTarget: RenderedMathTargetResolver;
|
|
6
|
+
export declare const createRenderedMathTargetResolver: () => RenderedMathTargetResolver;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { isNodeHiddenForTTS } from "../text-processing.js";
|
|
2
|
+
const MATHML_LEAF_TO_MATHJAX = new Map([
|
|
3
|
+
["mi", "mjx-mi"],
|
|
4
|
+
["mn", "mjx-mn"],
|
|
5
|
+
["mo", "mjx-mo"],
|
|
6
|
+
["mtext", "mjx-mtext"],
|
|
7
|
+
]);
|
|
8
|
+
const MATHML_LEAF_SELECTOR = "mi,mn,mo,mtext";
|
|
9
|
+
const MATHJAX_LEAF_SELECTOR = "mjx-mi,mjx-mn,mjx-mo,mjx-mtext";
|
|
10
|
+
// 2D layouts MathJax renders with extra, glyph-only nodes (radical surds,
|
|
11
|
+
// fraction bars drawn as borders, table scaffolding) that have no source-MathML
|
|
12
|
+
// leaf counterpart. They break the 1:1 token correspondence we rely on below,
|
|
13
|
+
// so we refuse positional mapping inside them and fall back to the whole
|
|
14
|
+
// expression instead of guessing.
|
|
15
|
+
const UNSUPPORTED_MATHJAX_LAYOUT_SELECTOR = [
|
|
16
|
+
"mjx-mfrac",
|
|
17
|
+
"mjx-msqrt",
|
|
18
|
+
"mjx-mroot",
|
|
19
|
+
"mjx-mtable",
|
|
20
|
+
"mjx-munder",
|
|
21
|
+
"mjx-mover",
|
|
22
|
+
"mjx-munderover",
|
|
23
|
+
].join(",");
|
|
24
|
+
const nearestElement = (target) => {
|
|
25
|
+
if (target.type === "text-range")
|
|
26
|
+
return target.node.parentElement;
|
|
27
|
+
return target.element;
|
|
28
|
+
};
|
|
29
|
+
const owningMathJaxContainer = (element) => {
|
|
30
|
+
let current = element;
|
|
31
|
+
while (current) {
|
|
32
|
+
if (current.localName?.toLowerCase() === "mjx-container")
|
|
33
|
+
return current;
|
|
34
|
+
current = current.parentElement;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
};
|
|
38
|
+
const isVisibleNativeTarget = (element) => {
|
|
39
|
+
if (!element)
|
|
40
|
+
return false;
|
|
41
|
+
if (owningMathJaxContainer(element))
|
|
42
|
+
return false;
|
|
43
|
+
return !isNodeHiddenForTTS(element);
|
|
44
|
+
};
|
|
45
|
+
const hasVisibleGeometry = (element) => {
|
|
46
|
+
const rect = element.getBoundingClientRect?.();
|
|
47
|
+
return Boolean(rect && rect.width > 0 && rect.height > 0);
|
|
48
|
+
};
|
|
49
|
+
const closestMathElement = (element) => {
|
|
50
|
+
let current = element;
|
|
51
|
+
while (current) {
|
|
52
|
+
if (current.localName?.toLowerCase() === "math")
|
|
53
|
+
return current;
|
|
54
|
+
current = current.parentElement;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
};
|
|
58
|
+
// MathJax CHTML renders each source-MathML token (mi/mn/mo/mtext) as exactly
|
|
59
|
+
// one mjx-* token in the same document order — including invisible operators,
|
|
60
|
+
// which it keeps as zero-size nodes. The original <math> survives, clipped, in
|
|
61
|
+
// mjx-assistive-mml, so its glyph text nodes are not visible and the visible
|
|
62
|
+
// CHTML tokens carry no readable textContent (glyphs come from web fonts).
|
|
63
|
+
// We therefore map a source token to its rendered glyph by *structural
|
|
64
|
+
// position* rather than text, but only after proving the two leaf lists are
|
|
65
|
+
// isomorphic (equal length, matching token type at every index). When the
|
|
66
|
+
// structure diverges we refuse to guess and let the caller fall back to the
|
|
67
|
+
// whole expression. "False positives are worse than coarse highlighting."
|
|
68
|
+
const tokensAreIsomorphic = (mathLeaves, chtmlLeaves) => {
|
|
69
|
+
if (mathLeaves.length === 0)
|
|
70
|
+
return false;
|
|
71
|
+
if (mathLeaves.length !== chtmlLeaves.length)
|
|
72
|
+
return false;
|
|
73
|
+
for (let index = 0; index < mathLeaves.length; index += 1) {
|
|
74
|
+
const expected = MATHML_LEAF_TO_MATHJAX.get(mathLeaves[index].localName.toLowerCase());
|
|
75
|
+
if (!expected)
|
|
76
|
+
return false;
|
|
77
|
+
if (chtmlLeaves[index].localName.toLowerCase() !== expected)
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
};
|
|
82
|
+
const resolveMathJaxTokenByPosition = (semanticElement, container) => {
|
|
83
|
+
if (!semanticElement)
|
|
84
|
+
return null;
|
|
85
|
+
if (!MATHML_LEAF_TO_MATHJAX.has(semanticElement.localName.toLowerCase()))
|
|
86
|
+
return null;
|
|
87
|
+
if (container.querySelector(UNSUPPORTED_MATHJAX_LAYOUT_SELECTOR))
|
|
88
|
+
return null;
|
|
89
|
+
const sourceMath = closestMathElement(semanticElement);
|
|
90
|
+
const renderedMath = container.querySelector("mjx-math");
|
|
91
|
+
if (!sourceMath || !renderedMath)
|
|
92
|
+
return null;
|
|
93
|
+
const mathLeaves = Array.from(sourceMath.querySelectorAll(MATHML_LEAF_SELECTOR));
|
|
94
|
+
const index = mathLeaves.indexOf(semanticElement);
|
|
95
|
+
if (index < 0)
|
|
96
|
+
return null;
|
|
97
|
+
const chtmlLeaves = Array.from(renderedMath.querySelectorAll(MATHJAX_LEAF_SELECTOR));
|
|
98
|
+
if (!tokensAreIsomorphic(mathLeaves, chtmlLeaves))
|
|
99
|
+
return null;
|
|
100
|
+
const token = chtmlLeaves[index];
|
|
101
|
+
// The matched token must be a real, visible glyph. We deliberately do NOT
|
|
102
|
+
// run isNodeHiddenForTTS here: MathJax marks its visual CHTML
|
|
103
|
+
// (mjx-math) with aria-hidden="true" because the accessible tree lives in
|
|
104
|
+
// the clipped mjx-assistive-mml. aria-hidden is correct for screen readers
|
|
105
|
+
// but irrelevant for a visual highlight — the glyph is painted on screen.
|
|
106
|
+
// Visible geometry is the right test: it rejects display:none nodes and
|
|
107
|
+
// zero-size invisible operators (e.g. U+2062 invisible-times, which is
|
|
108
|
+
// never spoken anyway) without discarding the real, on-screen tokens.
|
|
109
|
+
if (!token || !hasVisibleGeometry(token))
|
|
110
|
+
return null;
|
|
111
|
+
return token;
|
|
112
|
+
};
|
|
113
|
+
export const classifyMathHighlightCapability = (mathElement) => {
|
|
114
|
+
const container = owningMathJaxContainer(mathElement);
|
|
115
|
+
if (container) {
|
|
116
|
+
// 2D MathJax layouts add glyph-only nodes with no source-MathML
|
|
117
|
+
// counterpart, so positional mapping is unsafe — commit to expression.
|
|
118
|
+
if (container.querySelector(UNSUPPORTED_MATHJAX_LAYOUT_SELECTOR)) {
|
|
119
|
+
return "expression";
|
|
120
|
+
}
|
|
121
|
+
const sourceMath = closestMathElement(mathElement) ?? mathElement;
|
|
122
|
+
const renderedMath = container.querySelector("mjx-math");
|
|
123
|
+
if (!renderedMath)
|
|
124
|
+
return "expression";
|
|
125
|
+
const mathLeaves = Array.from(sourceMath.querySelectorAll(MATHML_LEAF_SELECTOR));
|
|
126
|
+
const chtmlLeaves = Array.from(renderedMath.querySelectorAll(MATHJAX_LEAF_SELECTOR));
|
|
127
|
+
if (!tokensAreIsomorphic(mathLeaves, chtmlLeaves))
|
|
128
|
+
return "expression";
|
|
129
|
+
// At least one rendered token must paint a real glyph; otherwise a
|
|
130
|
+
// per-token highlight would have nothing visible to land on.
|
|
131
|
+
return chtmlLeaves.some((leaf) => hasVisibleGeometry(leaf))
|
|
132
|
+
? "token"
|
|
133
|
+
: "expression";
|
|
134
|
+
}
|
|
135
|
+
// Native MathML: every leaf is a real, browser-positioned element, so
|
|
136
|
+
// per-token highlighting is reliable even inside 2D layouts (fractions,
|
|
137
|
+
// radicals). It is token-capable as long as a visible leaf exists.
|
|
138
|
+
const sourceMath = closestMathElement(mathElement) ?? mathElement;
|
|
139
|
+
const leaves = Array.from(sourceMath.querySelectorAll(MATHML_LEAF_SELECTOR));
|
|
140
|
+
return leaves.some((leaf) => !isNodeHiddenForTTS(leaf))
|
|
141
|
+
? "token"
|
|
142
|
+
: "expression";
|
|
143
|
+
};
|
|
144
|
+
const resolveRenderedMathTargetUncached = (target) => {
|
|
145
|
+
const element = nearestElement(target);
|
|
146
|
+
const mathJaxContainer = owningMathJaxContainer(element);
|
|
147
|
+
if (mathJaxContainer) {
|
|
148
|
+
const visibleToken = resolveMathJaxTokenByPosition(element, mathJaxContainer);
|
|
149
|
+
if (visibleToken) {
|
|
150
|
+
return {
|
|
151
|
+
type: "element",
|
|
152
|
+
quality: "semantic-token",
|
|
153
|
+
element: visibleToken,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
type: "element",
|
|
158
|
+
quality: "expression",
|
|
159
|
+
element: mathJaxContainer,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (target.type === "text-range") {
|
|
163
|
+
if (!isVisibleNativeTarget(target.node.parentElement))
|
|
164
|
+
return null;
|
|
165
|
+
return {
|
|
166
|
+
type: "text-range",
|
|
167
|
+
quality: "semantic-token",
|
|
168
|
+
node: target.node,
|
|
169
|
+
startOffset: target.startOffset,
|
|
170
|
+
endOffset: target.endOffset,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!isVisibleNativeTarget(target.element))
|
|
174
|
+
return null;
|
|
175
|
+
return {
|
|
176
|
+
type: "element",
|
|
177
|
+
quality: target.quality === "region-fallback" ? "expression" : "semantic-token",
|
|
178
|
+
element: target.element,
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
export const resolveRenderedMathTarget = resolveRenderedMathTargetUncached;
|
|
182
|
+
export const createRenderedMathTargetResolver = () => {
|
|
183
|
+
const mathJaxTargetCache = new WeakMap();
|
|
184
|
+
return (target) => {
|
|
185
|
+
const element = nearestElement(target);
|
|
186
|
+
const mathJaxContainer = owningMathJaxContainer(element);
|
|
187
|
+
if (!element || !mathJaxContainer) {
|
|
188
|
+
return resolveRenderedMathTargetUncached(target);
|
|
189
|
+
}
|
|
190
|
+
if (mathJaxTargetCache.has(element)) {
|
|
191
|
+
return mathJaxTargetCache.get(element) ?? null;
|
|
192
|
+
}
|
|
193
|
+
const rendered = resolveRenderedMathTargetUncached(target);
|
|
194
|
+
mathJaxTargetCache.set(element, rendered);
|
|
195
|
+
return rendered;
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
//# sourceMappingURL=rendered-math-target-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rendered-math-target-resolver.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/rendered-math-target-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import { isNodeHiddenForTTS } from \"../text-processing.js\";\nimport type {\n\tSemanticHighlightTarget,\n\tRenderableHighlightTarget,\n} from \"./types.js\";\n\nconst MATHML_LEAF_TO_MATHJAX = new Map([\n\t[\"mi\", \"mjx-mi\"],\n\t[\"mn\", \"mjx-mn\"],\n\t[\"mo\", \"mjx-mo\"],\n\t[\"mtext\", \"mjx-mtext\"],\n]);\n\nconst MATHML_LEAF_SELECTOR = \"mi,mn,mo,mtext\";\nconst MATHJAX_LEAF_SELECTOR = \"mjx-mi,mjx-mn,mjx-mo,mjx-mtext\";\n\n// 2D layouts MathJax renders with extra, glyph-only nodes (radical surds,\n// fraction bars drawn as borders, table scaffolding) that have no source-MathML\n// leaf counterpart. They break the 1:1 token correspondence we rely on below,\n// so we refuse positional mapping inside them and fall back to the whole\n// expression instead of guessing.\nconst UNSUPPORTED_MATHJAX_LAYOUT_SELECTOR = [\n\t\"mjx-mfrac\",\n\t\"mjx-msqrt\",\n\t\"mjx-mroot\",\n\t\"mjx-mtable\",\n\t\"mjx-munder\",\n\t\"mjx-mover\",\n\t\"mjx-munderover\",\n].join(\",\");\n\nconst nearestElement = (target: SemanticHighlightTarget): Element | null => {\n\tif (target.type === \"text-range\") return target.node.parentElement;\n\treturn target.element;\n};\n\nconst owningMathJaxContainer = (element: Element | null): Element | null => {\n\tlet current: Element | null = element;\n\twhile (current) {\n\t\tif (current.localName?.toLowerCase() === \"mjx-container\") return current;\n\t\tcurrent = current.parentElement;\n\t}\n\treturn null;\n};\n\nconst isVisibleNativeTarget = (element: Element | null): boolean => {\n\tif (!element) return false;\n\tif (owningMathJaxContainer(element)) return false;\n\treturn !isNodeHiddenForTTS(element);\n};\n\nconst hasVisibleGeometry = (element: Element): boolean => {\n\tconst rect = (element as HTMLElement).getBoundingClientRect?.();\n\treturn Boolean(rect && rect.width > 0 && rect.height > 0);\n};\n\nconst closestMathElement = (element: Element | null): Element | null => {\n\tlet current: Element | null = element;\n\twhile (current) {\n\t\tif (current.localName?.toLowerCase() === \"math\") return current;\n\t\tcurrent = current.parentElement;\n\t}\n\treturn null;\n};\n\n// MathJax CHTML renders each source-MathML token (mi/mn/mo/mtext) as exactly\n// one mjx-* token in the same document order — including invisible operators,\n// which it keeps as zero-size nodes. The original <math> survives, clipped, in\n// mjx-assistive-mml, so its glyph text nodes are not visible and the visible\n// CHTML tokens carry no readable textContent (glyphs come from web fonts).\n// We therefore map a source token to its rendered glyph by *structural\n// position* rather than text, but only after proving the two leaf lists are\n// isomorphic (equal length, matching token type at every index). When the\n// structure diverges we refuse to guess and let the caller fall back to the\n// whole expression. \"False positives are worse than coarse highlighting.\"\nconst tokensAreIsomorphic = (\n\tmathLeaves: Element[],\n\tchtmlLeaves: Element[],\n): boolean => {\n\tif (mathLeaves.length === 0) return false;\n\tif (mathLeaves.length !== chtmlLeaves.length) return false;\n\tfor (let index = 0; index < mathLeaves.length; index += 1) {\n\t\tconst expected = MATHML_LEAF_TO_MATHJAX.get(\n\t\t\tmathLeaves[index].localName.toLowerCase(),\n\t\t);\n\t\tif (!expected) return false;\n\t\tif (chtmlLeaves[index].localName.toLowerCase() !== expected) return false;\n\t}\n\treturn true;\n};\n\nconst resolveMathJaxTokenByPosition = (\n\tsemanticElement: Element | null,\n\tcontainer: Element,\n): Element | null => {\n\tif (!semanticElement) return null;\n\tif (!MATHML_LEAF_TO_MATHJAX.has(semanticElement.localName.toLowerCase()))\n\t\treturn null;\n\tif (container.querySelector(UNSUPPORTED_MATHJAX_LAYOUT_SELECTOR)) return null;\n\tconst sourceMath = closestMathElement(semanticElement);\n\tconst renderedMath = container.querySelector(\"mjx-math\");\n\tif (!sourceMath || !renderedMath) return null;\n\tconst mathLeaves = Array.from(\n\t\tsourceMath.querySelectorAll(MATHML_LEAF_SELECTOR),\n\t);\n\tconst index = mathLeaves.indexOf(semanticElement);\n\tif (index < 0) return null;\n\tconst chtmlLeaves = Array.from(\n\t\trenderedMath.querySelectorAll(MATHJAX_LEAF_SELECTOR),\n\t);\n\tif (!tokensAreIsomorphic(mathLeaves, chtmlLeaves)) return null;\n\tconst token = chtmlLeaves[index];\n\t// The matched token must be a real, visible glyph. We deliberately do NOT\n\t// run isNodeHiddenForTTS here: MathJax marks its visual CHTML\n\t// (mjx-math) with aria-hidden=\"true\" because the accessible tree lives in\n\t// the clipped mjx-assistive-mml. aria-hidden is correct for screen readers\n\t// but irrelevant for a visual highlight — the glyph is painted on screen.\n\t// Visible geometry is the right test: it rejects display:none nodes and\n\t// zero-size invisible operators (e.g. U+2062 invisible-times, which is\n\t// never spoken anyway) without discarding the real, on-screen tokens.\n\tif (!token || !hasVisibleGeometry(token)) return null;\n\treturn token;\n};\n\n// Per-equation highlight capability, decided once before playback. \"token\"\n// means we are confident every spoken token can be mapped to a visible glyph,\n// so the equation tracks word-by-word and the whole expression is never\n// painted. \"expression\" means we cannot reliably map tokens (a 2D MathJax\n// layout, a non-isomorphic render, or nothing visible), so the equation is\n// highlighted as one stable block for its entire spoken duration. Deciding\n// once and committing is what stops the highlight from flickering between a\n// token and the whole expression during breaks/pauses.\nexport type MathHighlightCapability = \"token\" | \"expression\";\n\nexport const classifyMathHighlightCapability = (\n\tmathElement: Element,\n): MathHighlightCapability => {\n\tconst container = owningMathJaxContainer(mathElement);\n\tif (container) {\n\t\t// 2D MathJax layouts add glyph-only nodes with no source-MathML\n\t\t// counterpart, so positional mapping is unsafe — commit to expression.\n\t\tif (container.querySelector(UNSUPPORTED_MATHJAX_LAYOUT_SELECTOR)) {\n\t\t\treturn \"expression\";\n\t\t}\n\t\tconst sourceMath = closestMathElement(mathElement) ?? mathElement;\n\t\tconst renderedMath = container.querySelector(\"mjx-math\");\n\t\tif (!renderedMath) return \"expression\";\n\t\tconst mathLeaves = Array.from(\n\t\t\tsourceMath.querySelectorAll(MATHML_LEAF_SELECTOR),\n\t\t);\n\t\tconst chtmlLeaves = Array.from(\n\t\t\trenderedMath.querySelectorAll(MATHJAX_LEAF_SELECTOR),\n\t\t);\n\t\tif (!tokensAreIsomorphic(mathLeaves, chtmlLeaves)) return \"expression\";\n\t\t// At least one rendered token must paint a real glyph; otherwise a\n\t\t// per-token highlight would have nothing visible to land on.\n\t\treturn chtmlLeaves.some((leaf) => hasVisibleGeometry(leaf))\n\t\t\t? \"token\"\n\t\t\t: \"expression\";\n\t}\n\t// Native MathML: every leaf is a real, browser-positioned element, so\n\t// per-token highlighting is reliable even inside 2D layouts (fractions,\n\t// radicals). It is token-capable as long as a visible leaf exists.\n\tconst sourceMath = closestMathElement(mathElement) ?? mathElement;\n\tconst leaves = Array.from(sourceMath.querySelectorAll(MATHML_LEAF_SELECTOR));\n\treturn leaves.some((leaf) => !isNodeHiddenForTTS(leaf))\n\t\t? \"token\"\n\t\t: \"expression\";\n};\n\nexport type RenderedMathTargetResolver = (\n\ttarget: SemanticHighlightTarget,\n) => RenderableHighlightTarget | null;\n\nconst resolveRenderedMathTargetUncached = (\n\ttarget: SemanticHighlightTarget,\n): RenderableHighlightTarget | null => {\n\tconst element = nearestElement(target);\n\tconst mathJaxContainer = owningMathJaxContainer(element);\n\tif (mathJaxContainer) {\n\t\tconst visibleToken = resolveMathJaxTokenByPosition(\n\t\t\telement,\n\t\t\tmathJaxContainer,\n\t\t);\n\t\tif (visibleToken) {\n\t\t\treturn {\n\t\t\t\ttype: \"element\",\n\t\t\t\tquality: \"semantic-token\",\n\t\t\t\telement: visibleToken,\n\t\t\t};\n\t\t}\n\t\treturn {\n\t\t\ttype: \"element\",\n\t\t\tquality: \"expression\",\n\t\t\telement: mathJaxContainer,\n\t\t};\n\t}\n\tif (target.type === \"text-range\") {\n\t\tif (!isVisibleNativeTarget(target.node.parentElement)) return null;\n\t\treturn {\n\t\t\ttype: \"text-range\",\n\t\t\tquality: \"semantic-token\",\n\t\t\tnode: target.node,\n\t\t\tstartOffset: target.startOffset,\n\t\t\tendOffset: target.endOffset,\n\t\t};\n\t}\n\tif (!isVisibleNativeTarget(target.element)) return null;\n\treturn {\n\t\ttype: \"element\",\n\t\tquality:\n\t\t\ttarget.quality === \"region-fallback\" ? \"expression\" : \"semantic-token\",\n\t\telement: target.element,\n\t};\n};\n\nexport const resolveRenderedMathTarget: RenderedMathTargetResolver =\n\tresolveRenderedMathTargetUncached;\n\nexport const createRenderedMathTargetResolver =\n\t(): RenderedMathTargetResolver => {\n\t\tconst mathJaxTargetCache = new WeakMap<\n\t\t\tElement,\n\t\t\tRenderableHighlightTarget | null\n\t\t>();\n\t\treturn (target) => {\n\t\t\tconst element = nearestElement(target);\n\t\t\tconst mathJaxContainer = owningMathJaxContainer(element);\n\t\t\tif (!element || !mathJaxContainer) {\n\t\t\t\treturn resolveRenderedMathTargetUncached(target);\n\t\t\t}\n\t\t\tif (mathJaxTargetCache.has(element)) {\n\t\t\t\treturn mathJaxTargetCache.get(element) ?? null;\n\t\t\t}\n\t\t\tconst rendered = resolveRenderedMathTargetUncached(target);\n\t\t\tmathJaxTargetCache.set(element, rendered);\n\t\t\treturn rendered;\n\t\t};\n\t};\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { CatalogSpanAlignment } from "../catalog-span-alignment.js";
|
|
2
|
+
import type { MathAwareAlignment, HighlightTarget } from "../math-alignment/index.js";
|
|
3
|
+
import type { NormalizedTextMap } from "../text-processing.js";
|
|
4
|
+
export type ProviderOffsetSpace = "plain-spoken-text" | "raw-ssml" | "provider-speech-marks" | "unknown";
|
|
5
|
+
export type ChunkOffsetSpace = Exclude<ProviderOffsetSpace, "unknown"> | "unsupported";
|
|
6
|
+
export interface TTSHighlightChunk {
|
|
7
|
+
id: string;
|
|
8
|
+
speechText: string;
|
|
9
|
+
visibleText: string;
|
|
10
|
+
sourceElement: Element | null;
|
|
11
|
+
contentRoot: Element;
|
|
12
|
+
regionElement: Element | null;
|
|
13
|
+
regionRange?: Range;
|
|
14
|
+
visibleMap?: NormalizedTextMap;
|
|
15
|
+
catalogAlignment?: CatalogSpanAlignment;
|
|
16
|
+
mathAlignments: Array<{
|
|
17
|
+
element: Element;
|
|
18
|
+
alignment: MathAwareAlignment;
|
|
19
|
+
}>;
|
|
20
|
+
offsetSpace: ChunkOffsetSpace;
|
|
21
|
+
}
|
|
22
|
+
export interface TTSBoundaryEvent {
|
|
23
|
+
chunkId: string;
|
|
24
|
+
word: string;
|
|
25
|
+
position: number;
|
|
26
|
+
length?: number;
|
|
27
|
+
providerOffsetSpace: ProviderOffsetSpace;
|
|
28
|
+
}
|
|
29
|
+
export interface NormalizedBoundaryEvent {
|
|
30
|
+
chunkId: string;
|
|
31
|
+
normalizedWord: string | null;
|
|
32
|
+
chunkSpokenStart: number | null;
|
|
33
|
+
chunkSpokenEnd: number | null;
|
|
34
|
+
confidence: number;
|
|
35
|
+
reason: string;
|
|
36
|
+
}
|
|
37
|
+
export type RenderableHighlightTarget = {
|
|
38
|
+
type: "text-range";
|
|
39
|
+
quality: "exact-word" | "semantic-token";
|
|
40
|
+
node: Text;
|
|
41
|
+
startOffset: number;
|
|
42
|
+
endOffset: number;
|
|
43
|
+
} | {
|
|
44
|
+
type: "range";
|
|
45
|
+
quality: "exact-word" | "semantic-token" | "expression" | "region";
|
|
46
|
+
range: Range;
|
|
47
|
+
} | {
|
|
48
|
+
type: "element";
|
|
49
|
+
quality: "semantic-token" | "expression" | "region";
|
|
50
|
+
element: Element;
|
|
51
|
+
};
|
|
52
|
+
export type HighlightDecisionQuality = "exact-word" | "semantic-token" | "expression" | "region";
|
|
53
|
+
export interface HighlightDecision {
|
|
54
|
+
activeTarget: RenderableHighlightTarget | null;
|
|
55
|
+
regionTarget: RenderableHighlightTarget | null;
|
|
56
|
+
quality: HighlightDecisionQuality;
|
|
57
|
+
confidence: number;
|
|
58
|
+
reason: string;
|
|
59
|
+
}
|
|
60
|
+
export interface HighlightDecisionInput {
|
|
61
|
+
semanticTarget: RenderableHighlightTarget | null;
|
|
62
|
+
expressionTarget: RenderableHighlightTarget | null;
|
|
63
|
+
regionTarget: RenderableHighlightTarget | null;
|
|
64
|
+
confidence: number;
|
|
65
|
+
reason: string;
|
|
66
|
+
}
|
|
67
|
+
export interface TTSHighlightPlan {
|
|
68
|
+
chunks: TTSHighlightChunk[];
|
|
69
|
+
resolveInitial(chunkId: string): HighlightDecision;
|
|
70
|
+
resolveBoundary(event: TTSBoundaryEvent): HighlightDecision;
|
|
71
|
+
}
|
|
72
|
+
export type SemanticHighlightTarget = HighlightTarget;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { CatalogSpanAlignment } from \"../catalog-span-alignment.js\";\nimport type {\n\tMathAwareAlignment,\n\tHighlightTarget,\n} from \"../math-alignment/index.js\";\nimport type { NormalizedTextMap } from \"../text-processing.js\";\n\nexport type ProviderOffsetSpace =\n\t| \"plain-spoken-text\"\n\t| \"raw-ssml\"\n\t| \"provider-speech-marks\"\n\t| \"unknown\";\n\nexport type ChunkOffsetSpace =\n\t| Exclude<ProviderOffsetSpace, \"unknown\">\n\t| \"unsupported\";\n\nexport interface TTSHighlightChunk {\n\tid: string;\n\tspeechText: string;\n\tvisibleText: string;\n\tsourceElement: Element | null;\n\tcontentRoot: Element;\n\tregionElement: Element | null;\n\tregionRange?: Range;\n\tvisibleMap?: NormalizedTextMap;\n\tcatalogAlignment?: CatalogSpanAlignment;\n\tmathAlignments: Array<{ element: Element; alignment: MathAwareAlignment }>;\n\toffsetSpace: ChunkOffsetSpace;\n}\n\nexport interface TTSBoundaryEvent {\n\tchunkId: string;\n\tword: string;\n\tposition: number;\n\tlength?: number;\n\tproviderOffsetSpace: ProviderOffsetSpace;\n}\n\nexport interface NormalizedBoundaryEvent {\n\tchunkId: string;\n\tnormalizedWord: string | null;\n\tchunkSpokenStart: number | null;\n\tchunkSpokenEnd: number | null;\n\tconfidence: number;\n\treason: string;\n}\n\nexport type RenderableHighlightTarget =\n\t| {\n\t\t\ttype: \"text-range\";\n\t\t\tquality: \"exact-word\" | \"semantic-token\";\n\t\t\tnode: Text;\n\t\t\tstartOffset: number;\n\t\t\tendOffset: number;\n\t }\n\t| {\n\t\t\ttype: \"range\";\n\t\t\tquality: \"exact-word\" | \"semantic-token\" | \"expression\" | \"region\";\n\t\t\trange: Range;\n\t }\n\t| {\n\t\t\ttype: \"element\";\n\t\t\tquality: \"semantic-token\" | \"expression\" | \"region\";\n\t\t\telement: Element;\n\t };\n\nexport type HighlightDecisionQuality =\n\t| \"exact-word\"\n\t| \"semantic-token\"\n\t| \"expression\"\n\t| \"region\";\n\nexport interface HighlightDecision {\n\tactiveTarget: RenderableHighlightTarget | null;\n\tregionTarget: RenderableHighlightTarget | null;\n\tquality: HighlightDecisionQuality;\n\tconfidence: number;\n\treason: string;\n}\n\nexport interface HighlightDecisionInput {\n\tsemanticTarget: RenderableHighlightTarget | null;\n\texpressionTarget: RenderableHighlightTarget | null;\n\tregionTarget: RenderableHighlightTarget | null;\n\tconfidence: number;\n\treason: string;\n}\n\nexport interface TTSHighlightPlan {\n\tchunks: TTSHighlightChunk[];\n\tresolveInitial(chunkId: string): HighlightDecision;\n\tresolveBoundary(event: TTSBoundaryEvent): HighlightDecision;\n}\n\nexport type SemanticHighlightTarget = HighlightTarget;\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const createRangeFromVisibleMap = (visibleMap, start, end) => {
|
|
2
|
+
if (!visibleMap || typeof document === "undefined")
|
|
3
|
+
return null;
|
|
4
|
+
if (end <= start)
|
|
5
|
+
return null;
|
|
6
|
+
const startMapping = visibleMap.get(start);
|
|
7
|
+
let endMapping = visibleMap.get(Math.max(start, end - 1));
|
|
8
|
+
if (!startMapping)
|
|
9
|
+
return null;
|
|
10
|
+
if (!endMapping)
|
|
11
|
+
endMapping = startMapping;
|
|
12
|
+
try {
|
|
13
|
+
const range = document.createRange();
|
|
14
|
+
range.setStart(startMapping.node, startMapping.offset);
|
|
15
|
+
range.setEnd(endMapping.node, endMapping.offset + 1);
|
|
16
|
+
return range;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=visible-map-range.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visible-map-range.js","sourceRoot":"","sources":["../../../../src/services/tts/highlight-pipeline/visible-map-range.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACxC,UAAyC,EACzC,KAAa,EACb,GAAW,EACI,EAAE;IACjB,IAAI,CAAC,UAAU,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAChE,IAAI,GAAG,IAAI,KAAK;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,CAAC,UAAU;QAAE,UAAU,GAAG,YAAY,CAAC;IAC3C,IAAI,CAAC;QACJ,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QACrC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;QACvD,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrD,OAAO,KAAK,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC,CAAC","sourcesContent":["import type { NormalizedTextMap } from \"../text-processing.js\";\n\nexport const createRangeFromVisibleMap = (\n\tvisibleMap: NormalizedTextMap | undefined,\n\tstart: number,\n\tend: number,\n): Range | null => {\n\tif (!visibleMap || typeof document === \"undefined\") return null;\n\tif (end <= start) return null;\n\tconst startMapping = visibleMap.get(start);\n\tlet endMapping = visibleMap.get(Math.max(start, end - 1));\n\tif (!startMapping) return null;\n\tif (!endMapping) endMapping = startMapping;\n\ttry {\n\t\tconst range = document.createRange();\n\t\trange.setStart(startMapping.node, startMapping.offset);\n\t\trange.setEnd(endMapping.node, endMapping.offset + 1);\n\t\treturn range;\n\t} catch {\n\t\treturn null;\n\t}\n};\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type SpeechSourceTokenization } from "./speech-tokenizer.js";
|
|
2
|
+
import type { AlignmentResult, HighlightTarget, MathMLTokenizationResult } from "./types.js";
|
|
3
|
+
export interface MathAwareAlignment {
|
|
4
|
+
speech: SpeechSourceTokenization;
|
|
5
|
+
math: MathMLTokenizationResult;
|
|
6
|
+
result: AlignmentResult;
|
|
7
|
+
}
|
|
8
|
+
export declare const createMathAwareAlignment: (args: {
|
|
9
|
+
mathElement: Element;
|
|
10
|
+
speechText: string;
|
|
11
|
+
}) => MathAwareAlignment;
|
|
12
|
+
export declare const resolveHighlightTargetForBoundary: (alignment: MathAwareAlignment, args: {
|
|
13
|
+
position: number;
|
|
14
|
+
length?: number;
|
|
15
|
+
boundaryWord?: string;
|
|
16
|
+
}) => HighlightTarget;
|
|
17
|
+
export declare const resolveMatchedHighlightTargetForBoundary: (alignment: MathAwareAlignment, args: {
|
|
18
|
+
position: number;
|
|
19
|
+
length?: number;
|
|
20
|
+
boundaryWord?: string;
|
|
21
|
+
}) => HighlightTarget | null;
|
|
22
|
+
export declare const resolveMatchedHighlightTargetForSpokenBoundary: (alignment: MathAwareAlignment, args: {
|
|
23
|
+
position: number;
|
|
24
|
+
length?: number;
|
|
25
|
+
boundaryWord?: string;
|
|
26
|
+
}) => HighlightTarget | null;
|
|
27
|
+
export declare const resolveUniqueMathTargetForBoundaryWord: (alignment: MathAwareAlignment, args: {
|
|
28
|
+
boundaryWord?: string;
|
|
29
|
+
}) => HighlightTarget | null;
|
|
30
|
+
export type * from "./types.js";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { tokenizeMathML } from "./mathml-tokenizer.js";
|
|
2
|
+
import { resolveMatchedHighlightTargetForBoundary as resolveMatchedTargetForBoundary, resolveHighlightTargetForBoundary as resolveTargetForBoundary, } from "./range-resolver.js";
|
|
3
|
+
import { alignSpeechToMath } from "./sequence-aligner.js";
|
|
4
|
+
import { resolveBoundaryToSpeechToken, resolveSpokenOffsetToSpeechToken, tokenizeSpeechSource, } from "./speech-tokenizer.js";
|
|
5
|
+
export const createMathAwareAlignment = (args) => {
|
|
6
|
+
const speech = tokenizeSpeechSource({ speechText: args.speechText });
|
|
7
|
+
const math = tokenizeMathML(args.mathElement);
|
|
8
|
+
return {
|
|
9
|
+
speech,
|
|
10
|
+
math,
|
|
11
|
+
result: alignSpeechToMath({ speech, math }),
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export const resolveHighlightTargetForBoundary = (alignment, args) => {
|
|
15
|
+
const boundary = resolveBoundaryToSpeechToken({
|
|
16
|
+
tokenization: alignment.speech,
|
|
17
|
+
position: args.position,
|
|
18
|
+
length: args.length,
|
|
19
|
+
boundaryWord: args.boundaryWord,
|
|
20
|
+
});
|
|
21
|
+
return resolveTargetForBoundary({
|
|
22
|
+
alignment: alignment.result,
|
|
23
|
+
boundary,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
export const resolveMatchedHighlightTargetForBoundary = (alignment, args) => {
|
|
27
|
+
const boundary = resolveBoundaryToSpeechToken({
|
|
28
|
+
tokenization: alignment.speech,
|
|
29
|
+
position: args.position,
|
|
30
|
+
length: args.length,
|
|
31
|
+
boundaryWord: args.boundaryWord,
|
|
32
|
+
});
|
|
33
|
+
return resolveMatchedTargetForBoundary({
|
|
34
|
+
alignment: alignment.result,
|
|
35
|
+
boundary,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
export const resolveMatchedHighlightTargetForSpokenBoundary = (alignment, args) => {
|
|
39
|
+
const boundary = resolveSpokenOffsetToSpeechToken({
|
|
40
|
+
tokenization: alignment.speech,
|
|
41
|
+
position: args.position,
|
|
42
|
+
length: args.length,
|
|
43
|
+
boundaryWord: args.boundaryWord,
|
|
44
|
+
});
|
|
45
|
+
return resolveMatchedTargetForBoundary({
|
|
46
|
+
alignment: alignment.result,
|
|
47
|
+
boundary,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
export const resolveUniqueMathTargetForBoundaryWord = (alignment, args) => {
|
|
51
|
+
if (!args.boundaryWord || /^<[^>]+>$/.test(args.boundaryWord.trim())) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const boundaryToken = tokenizeSpeechSource({
|
|
55
|
+
speechText: args.boundaryWord,
|
|
56
|
+
}).tokens[0];
|
|
57
|
+
if (!boundaryToken)
|
|
58
|
+
return null;
|
|
59
|
+
const matches = alignment.math.tokens.filter((token) => {
|
|
60
|
+
const normalized = boundaryToken.normalized.toLowerCase();
|
|
61
|
+
return (token.normalized.toLowerCase() === normalized ||
|
|
62
|
+
token.spokenAliases.some((alias) => alias.toLowerCase() === normalized));
|
|
63
|
+
});
|
|
64
|
+
return matches.length === 1 ? matches[0].target : null;
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { tokenizeMathML } from \"./mathml-tokenizer.js\";\nimport {\n\tresolveMatchedHighlightTargetForBoundary as resolveMatchedTargetForBoundary,\n\tresolveHighlightTargetForBoundary as resolveTargetForBoundary,\n} from \"./range-resolver.js\";\nimport { alignSpeechToMath } from \"./sequence-aligner.js\";\nimport {\n\tresolveBoundaryToSpeechToken,\n\tresolveSpokenOffsetToSpeechToken,\n\ttokenizeSpeechSource,\n\ttype SpeechSourceTokenization,\n} from \"./speech-tokenizer.js\";\nimport type {\n\tAlignmentResult,\n\tHighlightTarget,\n\tMathMLTokenizationResult,\n} from \"./types.js\";\n\nexport interface MathAwareAlignment {\n\tspeech: SpeechSourceTokenization;\n\tmath: MathMLTokenizationResult;\n\tresult: AlignmentResult;\n}\n\nexport const createMathAwareAlignment = (args: {\n\tmathElement: Element;\n\tspeechText: string;\n}): MathAwareAlignment => {\n\tconst speech = tokenizeSpeechSource({ speechText: args.speechText });\n\tconst math = tokenizeMathML(args.mathElement);\n\treturn {\n\t\tspeech,\n\t\tmath,\n\t\tresult: alignSpeechToMath({ speech, math }),\n\t};\n};\n\nexport const resolveHighlightTargetForBoundary = (\n\talignment: MathAwareAlignment,\n\targs: {\n\t\tposition: number;\n\t\tlength?: number;\n\t\tboundaryWord?: string;\n\t},\n): HighlightTarget => {\n\tconst boundary = resolveBoundaryToSpeechToken({\n\t\ttokenization: alignment.speech,\n\t\tposition: args.position,\n\t\tlength: args.length,\n\t\tboundaryWord: args.boundaryWord,\n\t});\n\treturn resolveTargetForBoundary({\n\t\talignment: alignment.result,\n\t\tboundary,\n\t});\n};\n\nexport const resolveMatchedHighlightTargetForBoundary = (\n\talignment: MathAwareAlignment,\n\targs: {\n\t\tposition: number;\n\t\tlength?: number;\n\t\tboundaryWord?: string;\n\t},\n): HighlightTarget | null => {\n\tconst boundary = resolveBoundaryToSpeechToken({\n\t\ttokenization: alignment.speech,\n\t\tposition: args.position,\n\t\tlength: args.length,\n\t\tboundaryWord: args.boundaryWord,\n\t});\n\treturn resolveMatchedTargetForBoundary({\n\t\talignment: alignment.result,\n\t\tboundary,\n\t});\n};\n\nexport const resolveMatchedHighlightTargetForSpokenBoundary = (\n\talignment: MathAwareAlignment,\n\targs: {\n\t\tposition: number;\n\t\tlength?: number;\n\t\tboundaryWord?: string;\n\t},\n): HighlightTarget | null => {\n\tconst boundary = resolveSpokenOffsetToSpeechToken({\n\t\ttokenization: alignment.speech,\n\t\tposition: args.position,\n\t\tlength: args.length,\n\t\tboundaryWord: args.boundaryWord,\n\t});\n\treturn resolveMatchedTargetForBoundary({\n\t\talignment: alignment.result,\n\t\tboundary,\n\t});\n};\n\nexport const resolveUniqueMathTargetForBoundaryWord = (\n\talignment: MathAwareAlignment,\n\targs: {\n\t\tboundaryWord?: string;\n\t},\n): HighlightTarget | null => {\n\tif (!args.boundaryWord || /^<[^>]+>$/.test(args.boundaryWord.trim())) {\n\t\treturn null;\n\t}\n\tconst boundaryToken = tokenizeSpeechSource({\n\t\tspeechText: args.boundaryWord,\n\t}).tokens[0];\n\tif (!boundaryToken) return null;\n\tconst matches = alignment.math.tokens.filter((token) => {\n\t\tconst normalized = boundaryToken.normalized.toLowerCase();\n\t\treturn (\n\t\t\ttoken.normalized.toLowerCase() === normalized ||\n\t\t\ttoken.spokenAliases.some((alias) => alias.toLowerCase() === normalized)\n\t\t);\n\t});\n\treturn matches.length === 1 ? matches[0].target : null;\n};\n\nexport type * from \"./types.js\";\n"]}
|