@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,143 @@
|
|
|
1
|
+
import { extractSpokenText } from "../ssml/spoken-text.js";
|
|
2
|
+
import { createSpeechAlignmentTokenPattern } from "../text-processing.js";
|
|
3
|
+
// Shared Unicode-aware tokenizer, identical to the catalog span aligner's, so
|
|
4
|
+
// spoken text tokenizes the same way on both paths (see
|
|
5
|
+
// `createSpeechAlignmentTokenPattern`).
|
|
6
|
+
const TOKEN_PATTERN = createSpeechAlignmentTokenPattern();
|
|
7
|
+
const NUMERIC_WORDS = new Map([
|
|
8
|
+
["zero", "0"],
|
|
9
|
+
["one", "1"],
|
|
10
|
+
["two", "2"],
|
|
11
|
+
["three", "3"],
|
|
12
|
+
["four", "4"],
|
|
13
|
+
["five", "5"],
|
|
14
|
+
["six", "6"],
|
|
15
|
+
["seven", "7"],
|
|
16
|
+
["eight", "8"],
|
|
17
|
+
["nine", "9"],
|
|
18
|
+
["ten", "10"],
|
|
19
|
+
]);
|
|
20
|
+
const normalizedTokenValue = (value) => {
|
|
21
|
+
const normalized = value.toLowerCase();
|
|
22
|
+
return NUMERIC_WORDS.get(normalized) || normalized;
|
|
23
|
+
};
|
|
24
|
+
const tokenizeSpokenText = (spokenText) => {
|
|
25
|
+
const tokens = [];
|
|
26
|
+
for (const match of spokenText.matchAll(TOKEN_PATTERN)) {
|
|
27
|
+
const text = match[0];
|
|
28
|
+
const start = match.index ?? 0;
|
|
29
|
+
tokens.push({
|
|
30
|
+
id: `speech-token-${tokens.length}`,
|
|
31
|
+
text,
|
|
32
|
+
normalized: normalizedTokenValue(text),
|
|
33
|
+
start,
|
|
34
|
+
end: start + text.length,
|
|
35
|
+
sourceStart: start,
|
|
36
|
+
sourceEnd: start + text.length,
|
|
37
|
+
coordinateSystem: "normalized-speech",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return tokens;
|
|
41
|
+
};
|
|
42
|
+
export const tokenizeSpeechSource = (args) => {
|
|
43
|
+
const extracted = extractSpokenText(args.speechText);
|
|
44
|
+
return {
|
|
45
|
+
speechText: args.speechText,
|
|
46
|
+
spokenText: extracted.spokenText,
|
|
47
|
+
tokens: tokenizeSpokenText(extracted.spokenText),
|
|
48
|
+
rawToSpokenOffsetMap: extracted.rawToSpokenOffsetMap,
|
|
49
|
+
boundaryOffsetSpace: extracted.unsupportedSemantic
|
|
50
|
+
? "unsupported"
|
|
51
|
+
: extracted.hasMarkup
|
|
52
|
+
? "raw-ssml"
|
|
53
|
+
: "plain-spoken-text",
|
|
54
|
+
unsupportedSemantic: extracted.unsupportedSemantic,
|
|
55
|
+
hasMarkup: extracted.hasMarkup,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
const normalizeBoundaryWord = (word) => {
|
|
59
|
+
if (!word)
|
|
60
|
+
return null;
|
|
61
|
+
if (/^<[^>]+>$/.test(word.trim()))
|
|
62
|
+
return null;
|
|
63
|
+
const match = word.match(TOKEN_PATTERN);
|
|
64
|
+
if (!match?.[0])
|
|
65
|
+
return null;
|
|
66
|
+
return normalizedTokenValue(match[0]);
|
|
67
|
+
};
|
|
68
|
+
const findTokenAtOffset = (tokens, offset) => tokens.find((token) => token.start <= offset && offset < token.end) || null;
|
|
69
|
+
const mapRawOffsetToSpokenOffset = (tokenization, position) => {
|
|
70
|
+
const direct = tokenization.rawToSpokenOffsetMap.get(position);
|
|
71
|
+
if (direct !== undefined)
|
|
72
|
+
return direct;
|
|
73
|
+
for (let offset = position; offset < tokenization.speechText.length; offset++) {
|
|
74
|
+
const mapped = tokenization.rawToSpokenOffsetMap.get(offset);
|
|
75
|
+
if (mapped !== undefined)
|
|
76
|
+
return mapped;
|
|
77
|
+
if (tokenization.speechText[offset] === ">")
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
};
|
|
82
|
+
const candidateForOffset = (tokenization, offset, length) => {
|
|
83
|
+
if (offset === null)
|
|
84
|
+
return null;
|
|
85
|
+
const token = findTokenAtOffset(tokenization.tokens, offset);
|
|
86
|
+
if (!token)
|
|
87
|
+
return null;
|
|
88
|
+
return {
|
|
89
|
+
token,
|
|
90
|
+
start: offset,
|
|
91
|
+
length: Math.max(1, Math.min(length, token.end - offset)),
|
|
92
|
+
confidence: 0.75,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
const candidateMatchesWord = (candidate, boundaryWord) => {
|
|
96
|
+
const normalizedBoundary = normalizeBoundaryWord(boundaryWord);
|
|
97
|
+
return Boolean(candidate &&
|
|
98
|
+
normalizedBoundary &&
|
|
99
|
+
candidate.token.normalized === normalizedBoundary);
|
|
100
|
+
};
|
|
101
|
+
export const resolveBoundaryToSpeechToken = (args) => {
|
|
102
|
+
if (args.tokenization.boundaryOffsetSpace === "unsupported" ||
|
|
103
|
+
!Number.isFinite(args.position) ||
|
|
104
|
+
(args.boundaryWord && /^<[^>]+>$/.test(args.boundaryWord.trim()))) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const safeLength = Math.max(1, Number.isFinite(args.length) ? args.length : 1);
|
|
108
|
+
const rawCandidate = candidateForOffset(args.tokenization, mapRawOffsetToSpokenOffset(args.tokenization, args.position), safeLength);
|
|
109
|
+
const plainCandidate = candidateForOffset(args.tokenization, args.position >= 0 && args.position < args.tokenization.spokenText.length
|
|
110
|
+
? args.position
|
|
111
|
+
: null, safeLength);
|
|
112
|
+
if (candidateMatchesWord(rawCandidate, args.boundaryWord)) {
|
|
113
|
+
return { ...rawCandidate, confidence: 1 };
|
|
114
|
+
}
|
|
115
|
+
if (candidateMatchesWord(plainCandidate, args.boundaryWord)) {
|
|
116
|
+
return { ...plainCandidate, confidence: 1 };
|
|
117
|
+
}
|
|
118
|
+
if (args.boundaryWord && normalizeBoundaryWord(args.boundaryWord)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return args.tokenization.boundaryOffsetSpace === "raw-ssml"
|
|
122
|
+
? rawCandidate || plainCandidate
|
|
123
|
+
: plainCandidate || rawCandidate;
|
|
124
|
+
};
|
|
125
|
+
export const resolveSpokenOffsetToSpeechToken = (args) => {
|
|
126
|
+
if (args.tokenization.boundaryOffsetSpace === "unsupported" ||
|
|
127
|
+
!Number.isFinite(args.position) ||
|
|
128
|
+
(args.boundaryWord && /^<[^>]+>$/.test(args.boundaryWord.trim()))) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const safeLength = Math.max(1, Number.isFinite(args.length) ? args.length : 1);
|
|
132
|
+
const spokenCandidate = candidateForOffset(args.tokenization, args.position >= 0 && args.position < args.tokenization.spokenText.length
|
|
133
|
+
? args.position
|
|
134
|
+
: null, safeLength);
|
|
135
|
+
if (candidateMatchesWord(spokenCandidate, args.boundaryWord)) {
|
|
136
|
+
return { ...spokenCandidate, confidence: 1 };
|
|
137
|
+
}
|
|
138
|
+
if (args.boundaryWord && normalizeBoundaryWord(args.boundaryWord)) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return spokenCandidate;
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=speech-tokenizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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;AAK3D,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,KAAK,IAAI,MAAM,GAAG,QAAQ,EAAE,MAAM,GAAG,YAAY,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;QAC/E,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,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,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,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,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 {\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"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type HighlightTargetQuality = "exact-word" | "element-range" | "region-fallback";
|
|
2
|
+
export type HighlightTarget = {
|
|
3
|
+
type: "text-range";
|
|
4
|
+
quality: "exact-word";
|
|
5
|
+
node: Text;
|
|
6
|
+
startOffset: number;
|
|
7
|
+
endOffset: number;
|
|
8
|
+
} | {
|
|
9
|
+
type: "element-range";
|
|
10
|
+
quality: Exclude<HighlightTargetQuality, "exact-word">;
|
|
11
|
+
element: Element;
|
|
12
|
+
} | {
|
|
13
|
+
type: "region";
|
|
14
|
+
quality: "region-fallback";
|
|
15
|
+
element: Element;
|
|
16
|
+
};
|
|
17
|
+
export type MathAlignmentTokenKind = "identifier" | "number" | "operator" | "text" | "space" | "structure" | "layout" | "unknown";
|
|
18
|
+
export interface MathAlignmentToken {
|
|
19
|
+
id: string;
|
|
20
|
+
kind: MathAlignmentTokenKind;
|
|
21
|
+
role: string;
|
|
22
|
+
text: string;
|
|
23
|
+
normalized: string;
|
|
24
|
+
spokenAliases: string[];
|
|
25
|
+
sourceElement: Element;
|
|
26
|
+
path: string;
|
|
27
|
+
target: HighlightTarget;
|
|
28
|
+
optional?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface MathMLTokenizationResult {
|
|
31
|
+
tokens: MathAlignmentToken[];
|
|
32
|
+
layoutTargets: MathAlignmentToken[];
|
|
33
|
+
expressionTarget: HighlightTarget;
|
|
34
|
+
}
|
|
35
|
+
export type BoundaryCoordinateSystem = "normalized-speech" | "raw-ssml" | "provider-speech-marks";
|
|
36
|
+
export type BoundaryOffsetSpace = "plain-spoken-text" | "raw-ssml" | "unsupported";
|
|
37
|
+
export interface SpeechAlignmentToken {
|
|
38
|
+
id: string;
|
|
39
|
+
text: string;
|
|
40
|
+
normalized: string;
|
|
41
|
+
start: number;
|
|
42
|
+
end: number;
|
|
43
|
+
sourceStart: number;
|
|
44
|
+
sourceEnd: number;
|
|
45
|
+
coordinateSystem: BoundaryCoordinateSystem;
|
|
46
|
+
}
|
|
47
|
+
export interface AlignmentSegment {
|
|
48
|
+
speechTokenIds: string[];
|
|
49
|
+
mathTokenIds: string[];
|
|
50
|
+
score: number;
|
|
51
|
+
confidence: number;
|
|
52
|
+
target: HighlightTarget;
|
|
53
|
+
}
|
|
54
|
+
export interface AlignmentResult {
|
|
55
|
+
segments: AlignmentSegment[];
|
|
56
|
+
confidence: number;
|
|
57
|
+
fallbackTarget: HighlightTarget;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/services/tts/math-alignment/types.ts"],"names":[],"mappings":"","sourcesContent":["export type HighlightTargetQuality =\n\t| \"exact-word\"\n\t| \"element-range\"\n\t| \"region-fallback\";\n\nexport type HighlightTarget =\n\t| {\n\t\t\ttype: \"text-range\";\n\t\t\tquality: \"exact-word\";\n\t\t\tnode: Text;\n\t\t\tstartOffset: number;\n\t\t\tendOffset: number;\n\t }\n\t| {\n\t\t\ttype: \"element-range\";\n\t\t\tquality: Exclude<HighlightTargetQuality, \"exact-word\">;\n\t\t\telement: Element;\n\t }\n\t| {\n\t\t\ttype: \"region\";\n\t\t\tquality: \"region-fallback\";\n\t\t\telement: Element;\n\t };\n\nexport type MathAlignmentTokenKind =\n\t| \"identifier\"\n\t| \"number\"\n\t| \"operator\"\n\t| \"text\"\n\t| \"space\"\n\t| \"structure\"\n\t| \"layout\"\n\t| \"unknown\";\n\nexport interface MathAlignmentToken {\n\tid: string;\n\tkind: MathAlignmentTokenKind;\n\trole: string;\n\ttext: string;\n\tnormalized: string;\n\tspokenAliases: string[];\n\tsourceElement: Element;\n\tpath: string;\n\ttarget: HighlightTarget;\n\toptional?: boolean;\n}\n\nexport interface MathMLTokenizationResult {\n\ttokens: MathAlignmentToken[];\n\tlayoutTargets: MathAlignmentToken[];\n\texpressionTarget: HighlightTarget;\n}\n\nexport type BoundaryCoordinateSystem =\n\t| \"normalized-speech\"\n\t| \"raw-ssml\"\n\t| \"provider-speech-marks\";\n\nexport type BoundaryOffsetSpace =\n\t| \"plain-spoken-text\"\n\t| \"raw-ssml\"\n\t| \"unsupported\";\n\nexport interface SpeechAlignmentToken {\n\tid: string;\n\ttext: string;\n\tnormalized: string;\n\tstart: number;\n\tend: number;\n\tsourceStart: number;\n\tsourceEnd: number;\n\tcoordinateSystem: BoundaryCoordinateSystem;\n}\n\nexport interface AlignmentSegment {\n\tspeechTokenIds: string[];\n\tmathTokenIds: string[];\n\tscore: number;\n\tconfidence: number;\n\ttarget: HighlightTarget;\n}\n\nexport interface AlignmentResult {\n\tsegments: AlignmentSegment[];\n\tconfidence: number;\n\tfallbackTarget: HighlightTarget;\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type NormalizedTextMap, type TextProcessingOptions } from "./text-processing.js";
|
|
2
|
+
export type MathAwareSpeechChunk = {
|
|
3
|
+
type: "text";
|
|
4
|
+
text: string;
|
|
5
|
+
sourceElement?: Element;
|
|
6
|
+
} | {
|
|
7
|
+
type: "math";
|
|
8
|
+
mathml: string;
|
|
9
|
+
fallbackText: string;
|
|
10
|
+
sourceElement?: Element;
|
|
11
|
+
};
|
|
12
|
+
export interface MathAwareTextResult {
|
|
13
|
+
visibleText: string;
|
|
14
|
+
map: NormalizedTextMap;
|
|
15
|
+
chunks: MathAwareSpeechChunk[];
|
|
16
|
+
containsMathMarkup: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const collectMathAwareTextAndMap: (element: Element, options?: TextProcessingOptions) => MathAwareTextResult;
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { canonicalizeMathML } from "./mathml-sanitization.js";
|
|
2
|
+
import { collectVisibleTextAndMap, isNodeHiddenForTTS, normalizeTextForSpeech, shouldInsertWordBoundarySpace, } from "./text-processing.js";
|
|
3
|
+
const createAccumulator = (options) => ({
|
|
4
|
+
chars: [],
|
|
5
|
+
map: new Map(),
|
|
6
|
+
position: 0,
|
|
7
|
+
inLeadingWhitespace: true,
|
|
8
|
+
lastCharWasWhitespace: false,
|
|
9
|
+
lastMapped: null,
|
|
10
|
+
options,
|
|
11
|
+
});
|
|
12
|
+
const isMathJaxElement = (element) => element.classList?.contains("MathJax") ||
|
|
13
|
+
element.tagName?.toLowerCase() === "mjx-container";
|
|
14
|
+
const isAssistiveMathElement = (element) => element.tagName?.toLowerCase() === "mjx-assistive-mml";
|
|
15
|
+
const isAnnotationElement = (element) => {
|
|
16
|
+
const tagName = element.tagName?.toLowerCase();
|
|
17
|
+
return tagName === "annotation" || tagName === "annotation-xml";
|
|
18
|
+
};
|
|
19
|
+
const getDataMathML = (element) => {
|
|
20
|
+
const value = element.getAttribute("data-mathml");
|
|
21
|
+
return value && value.trim() ? value : null;
|
|
22
|
+
};
|
|
23
|
+
const findAssistiveMathML = (element) => {
|
|
24
|
+
const assistive = element.querySelector("mjx-assistive-mml math");
|
|
25
|
+
return assistive?.outerHTML || null;
|
|
26
|
+
};
|
|
27
|
+
const findCanonicalMathML = (element) => {
|
|
28
|
+
if (element.tagName.toLowerCase() === "math") {
|
|
29
|
+
return canonicalizeMathML(element.outerHTML);
|
|
30
|
+
}
|
|
31
|
+
const dataMath = getDataMathML(element);
|
|
32
|
+
if (dataMath) {
|
|
33
|
+
const canonical = canonicalizeMathML(dataMath);
|
|
34
|
+
if (canonical)
|
|
35
|
+
return canonical;
|
|
36
|
+
}
|
|
37
|
+
if (isMathJaxElement(element)) {
|
|
38
|
+
const assistive = findAssistiveMathML(element);
|
|
39
|
+
if (assistive) {
|
|
40
|
+
const canonical = canonicalizeMathML(assistive);
|
|
41
|
+
if (canonical)
|
|
42
|
+
return canonical;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
};
|
|
47
|
+
const isMathContainer = (element) => Boolean(findCanonicalMathML(element));
|
|
48
|
+
const TEXT_CHUNK_SOURCE_TAGS = new Set([
|
|
49
|
+
"P",
|
|
50
|
+
"H1",
|
|
51
|
+
"H2",
|
|
52
|
+
"H3",
|
|
53
|
+
"H4",
|
|
54
|
+
"H5",
|
|
55
|
+
"H6",
|
|
56
|
+
"LI",
|
|
57
|
+
"TD",
|
|
58
|
+
"TH",
|
|
59
|
+
"DD",
|
|
60
|
+
"DT",
|
|
61
|
+
"FIGCAPTION",
|
|
62
|
+
"LABEL",
|
|
63
|
+
"BUTTON",
|
|
64
|
+
]);
|
|
65
|
+
const TEXT_CHUNK_SOURCE_ROLES = new Set([
|
|
66
|
+
"heading",
|
|
67
|
+
"listitem",
|
|
68
|
+
"option",
|
|
69
|
+
"radio",
|
|
70
|
+
"row",
|
|
71
|
+
"cell",
|
|
72
|
+
"columnheader",
|
|
73
|
+
"rowheader",
|
|
74
|
+
]);
|
|
75
|
+
const resolveTextChunkSourceElement = (textNode, root) => {
|
|
76
|
+
let current = textNode.parentElement;
|
|
77
|
+
let best = null;
|
|
78
|
+
while (current && current !== root) {
|
|
79
|
+
const role = (current.getAttribute("role") || "").toLowerCase();
|
|
80
|
+
const tagName = current.tagName.toUpperCase();
|
|
81
|
+
if (TEXT_CHUNK_SOURCE_TAGS.has(tagName) ||
|
|
82
|
+
TEXT_CHUNK_SOURCE_ROLES.has(role)) {
|
|
83
|
+
best = current;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
current = current.parentElement;
|
|
87
|
+
}
|
|
88
|
+
return best || root;
|
|
89
|
+
};
|
|
90
|
+
const hasMathCandidate = (element) => Boolean(typeof element.querySelector === "function" &&
|
|
91
|
+
element.querySelector("math, [data-mathml], .MathJax, mjx-container, mjx-assistive-mml")) ||
|
|
92
|
+
element.tagName?.toLowerCase() === "math" ||
|
|
93
|
+
element.hasAttribute?.("data-mathml") ||
|
|
94
|
+
isMathJaxElement(element);
|
|
95
|
+
const appendCharacter = (acc, character, mapping) => {
|
|
96
|
+
if (/[.,;:!?]/.test(character) &&
|
|
97
|
+
acc.chars.length > 0 &&
|
|
98
|
+
acc.chars[acc.chars.length - 1] === " ") {
|
|
99
|
+
acc.chars.pop();
|
|
100
|
+
acc.position--;
|
|
101
|
+
acc.map.delete(acc.position);
|
|
102
|
+
}
|
|
103
|
+
acc.chars.push(character);
|
|
104
|
+
if (mapping) {
|
|
105
|
+
acc.map.set(acc.position, mapping);
|
|
106
|
+
acc.lastMapped = mapping;
|
|
107
|
+
}
|
|
108
|
+
else if (acc.lastMapped) {
|
|
109
|
+
acc.map.set(acc.position, acc.lastMapped);
|
|
110
|
+
}
|
|
111
|
+
acc.position++;
|
|
112
|
+
};
|
|
113
|
+
const shouldInsertBoundarySpace = (acc, nextCharacter) => {
|
|
114
|
+
if (acc.inLeadingWhitespace ||
|
|
115
|
+
acc.lastCharWasWhitespace ||
|
|
116
|
+
acc.chars.length === 0) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
const previous = acc.chars[acc.chars.length - 1];
|
|
120
|
+
// Defer to the shared locale-aware decision (Intl.Segmenter, falling back to
|
|
121
|
+
// alnum) so adjacent text nodes split into words consistently with the main
|
|
122
|
+
// visible-text collector, instead of an ASCII-only heuristic.
|
|
123
|
+
return shouldInsertWordBoundarySpace(previous, nextCharacter, acc.options);
|
|
124
|
+
};
|
|
125
|
+
const appendTextNode = (acc, textNode) => {
|
|
126
|
+
const raw = textNode.textContent || "";
|
|
127
|
+
let appendedNonWhitespaceInNode = false;
|
|
128
|
+
for (let i = 0; i < raw.length; i++) {
|
|
129
|
+
const character = raw[i];
|
|
130
|
+
const isWhitespace = /\s/.test(character);
|
|
131
|
+
if (acc.inLeadingWhitespace) {
|
|
132
|
+
if (!isWhitespace) {
|
|
133
|
+
acc.inLeadingWhitespace = false;
|
|
134
|
+
appendCharacter(acc, character, { node: textNode, offset: i });
|
|
135
|
+
appendedNonWhitespaceInNode = true;
|
|
136
|
+
acc.lastCharWasWhitespace = false;
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (isWhitespace) {
|
|
141
|
+
if (!acc.lastCharWasWhitespace) {
|
|
142
|
+
appendCharacter(acc, " ", { node: textNode, offset: i });
|
|
143
|
+
}
|
|
144
|
+
acc.lastCharWasWhitespace = true;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (!appendedNonWhitespaceInNode &&
|
|
148
|
+
shouldInsertBoundarySpace(acc, character)) {
|
|
149
|
+
appendCharacter(acc, " ", { node: textNode, offset: i });
|
|
150
|
+
}
|
|
151
|
+
appendCharacter(acc, character, { node: textNode, offset: i });
|
|
152
|
+
appendedNonWhitespaceInNode = true;
|
|
153
|
+
acc.lastCharWasWhitespace = false;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const appendCollectedText = (acc, collected) => {
|
|
157
|
+
for (let i = 0; i < collected.text.length; i++) {
|
|
158
|
+
const character = collected.text[i];
|
|
159
|
+
const isWhitespace = /\s/.test(character);
|
|
160
|
+
const mapping = collected.map.get(i) || null;
|
|
161
|
+
const syntheticNode = mapping?.node || acc.lastMapped?.node || null;
|
|
162
|
+
const syntheticOffset = mapping?.offset ?? acc.lastMapped?.offset ?? 0;
|
|
163
|
+
const normalizedMapping = mapping
|
|
164
|
+
? { node: mapping.node, offset: mapping.offset }
|
|
165
|
+
: syntheticNode
|
|
166
|
+
? { node: syntheticNode, offset: syntheticOffset }
|
|
167
|
+
: null;
|
|
168
|
+
if (acc.inLeadingWhitespace) {
|
|
169
|
+
if (isWhitespace)
|
|
170
|
+
continue;
|
|
171
|
+
acc.inLeadingWhitespace = false;
|
|
172
|
+
}
|
|
173
|
+
if (isWhitespace) {
|
|
174
|
+
if (!acc.lastCharWasWhitespace) {
|
|
175
|
+
appendCharacter(acc, " ", normalizedMapping);
|
|
176
|
+
}
|
|
177
|
+
acc.lastCharWasWhitespace = true;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (i === 0 && shouldInsertBoundarySpace(acc, character)) {
|
|
181
|
+
appendCharacter(acc, " ", normalizedMapping);
|
|
182
|
+
}
|
|
183
|
+
appendCharacter(acc, character, normalizedMapping);
|
|
184
|
+
acc.lastCharWasWhitespace = false;
|
|
185
|
+
if (mapping) {
|
|
186
|
+
acc.map.set(acc.position - 1, {
|
|
187
|
+
node: mapping.node,
|
|
188
|
+
offset: mapping.offset,
|
|
189
|
+
});
|
|
190
|
+
acc.lastMapped = { node: mapping.node, offset: mapping.offset };
|
|
191
|
+
}
|
|
192
|
+
else if (syntheticNode) {
|
|
193
|
+
acc.map.set(acc.position - 1, {
|
|
194
|
+
node: syntheticNode,
|
|
195
|
+
offset: syntheticOffset,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
const textFallbackFromMathML = (mathml) => {
|
|
201
|
+
if (typeof DOMParser === "undefined")
|
|
202
|
+
return "";
|
|
203
|
+
const parsed = new DOMParser().parseFromString(mathml, "application/xml");
|
|
204
|
+
if (parsed.getElementsByTagName("parsererror").length > 0)
|
|
205
|
+
return "";
|
|
206
|
+
const parts = [];
|
|
207
|
+
const visit = (node) => {
|
|
208
|
+
if (node.nodeType === 3) {
|
|
209
|
+
const text = node.textContent || "";
|
|
210
|
+
if (text.trim())
|
|
211
|
+
parts.push(text);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (node.nodeType !== 1)
|
|
215
|
+
return;
|
|
216
|
+
const element = node;
|
|
217
|
+
if (isAnnotationElement(element))
|
|
218
|
+
return;
|
|
219
|
+
for (const child of Array.from(element.childNodes)) {
|
|
220
|
+
visit(child);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
visit(parsed.documentElement);
|
|
224
|
+
return normalizeTextForSpeech(parts.join(" "));
|
|
225
|
+
};
|
|
226
|
+
const collectVisibleMathFallback = (element, canonicalMathML, options) => {
|
|
227
|
+
const mathAcc = createAccumulator(options);
|
|
228
|
+
const visit = (node) => {
|
|
229
|
+
if (isNodeHiddenForTTS(node, element))
|
|
230
|
+
return;
|
|
231
|
+
if (node.nodeType === 3) {
|
|
232
|
+
appendTextNode(mathAcc, node);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (node.nodeType !== 1)
|
|
236
|
+
return;
|
|
237
|
+
const childElement = node;
|
|
238
|
+
if (isAssistiveMathElement(childElement) ||
|
|
239
|
+
isAnnotationElement(childElement)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
for (const child of Array.from(childElement.childNodes)) {
|
|
243
|
+
visit(child);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
visit(element);
|
|
247
|
+
trimTrailingWhitespace(mathAcc);
|
|
248
|
+
const text = normalizeTextForSpeech(mathAcc.chars.join(""));
|
|
249
|
+
if (text) {
|
|
250
|
+
return { text, map: mathAcc.map };
|
|
251
|
+
}
|
|
252
|
+
const fallbackText = textFallbackFromMathML(canonicalMathML);
|
|
253
|
+
if (!fallbackText) {
|
|
254
|
+
return collectVisibleTextAndMap(element, options);
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
text: fallbackText,
|
|
258
|
+
map: new Map(),
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
const trimTrailingWhitespace = (acc) => {
|
|
262
|
+
while (acc.chars.length > 0 && /\s/.test(acc.chars[acc.chars.length - 1])) {
|
|
263
|
+
acc.chars.pop();
|
|
264
|
+
acc.position--;
|
|
265
|
+
acc.map.delete(acc.position);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
const collectMathAware = (root, options) => {
|
|
269
|
+
const acc = createAccumulator(options);
|
|
270
|
+
const chunks = [];
|
|
271
|
+
let textChunkStart = 0;
|
|
272
|
+
let textChunkSourceElement;
|
|
273
|
+
let containsMathMarkup = false;
|
|
274
|
+
const flushTextChunk = () => {
|
|
275
|
+
const text = normalizeTextForSpeech(acc.chars.slice(textChunkStart, acc.chars.length).join(""));
|
|
276
|
+
if (text) {
|
|
277
|
+
chunks.push({ type: "text", text, sourceElement: textChunkSourceElement });
|
|
278
|
+
}
|
|
279
|
+
textChunkStart = acc.chars.length;
|
|
280
|
+
textChunkSourceElement = undefined;
|
|
281
|
+
};
|
|
282
|
+
const processNode = (node) => {
|
|
283
|
+
if (isNodeHiddenForTTS(node, root))
|
|
284
|
+
return;
|
|
285
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
286
|
+
const sourceElement = resolveTextChunkSourceElement(node, root);
|
|
287
|
+
if (textChunkSourceElement &&
|
|
288
|
+
sourceElement !== textChunkSourceElement) {
|
|
289
|
+
flushTextChunk();
|
|
290
|
+
}
|
|
291
|
+
textChunkSourceElement = sourceElement;
|
|
292
|
+
appendTextNode(acc, node);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
296
|
+
return;
|
|
297
|
+
const element = node;
|
|
298
|
+
if (isAssistiveMathElement(element))
|
|
299
|
+
return;
|
|
300
|
+
const canonicalMathML = findCanonicalMathML(element);
|
|
301
|
+
if (canonicalMathML) {
|
|
302
|
+
flushTextChunk();
|
|
303
|
+
const collected = collectVisibleMathFallback(element, canonicalMathML, options);
|
|
304
|
+
appendCollectedText(acc, collected);
|
|
305
|
+
const fallbackText = normalizeTextForSpeech(collected.text);
|
|
306
|
+
chunks.push({
|
|
307
|
+
type: "math",
|
|
308
|
+
mathml: canonicalMathML,
|
|
309
|
+
fallbackText,
|
|
310
|
+
sourceElement: element,
|
|
311
|
+
});
|
|
312
|
+
textChunkStart = acc.chars.length;
|
|
313
|
+
containsMathMarkup = true;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
for (const child of Array.from(element.childNodes)) {
|
|
317
|
+
processNode(child);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
for (const child of Array.from(root.childNodes)) {
|
|
321
|
+
processNode(child);
|
|
322
|
+
}
|
|
323
|
+
trimTrailingWhitespace(acc);
|
|
324
|
+
flushTextChunk();
|
|
325
|
+
const visibleText = acc.chars.join("");
|
|
326
|
+
while (acc.map.has(visibleText.length)) {
|
|
327
|
+
acc.map.delete(visibleText.length);
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
visibleText,
|
|
331
|
+
map: acc.map,
|
|
332
|
+
chunks,
|
|
333
|
+
containsMathMarkup,
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
export const collectMathAwareTextAndMap = (element, options) => {
|
|
337
|
+
if (!hasMathCandidate(element)) {
|
|
338
|
+
const { text, map } = collectVisibleTextAndMap(element, options);
|
|
339
|
+
return {
|
|
340
|
+
visibleText: text,
|
|
341
|
+
map,
|
|
342
|
+
chunks: text ? [{ type: "text", text }] : [],
|
|
343
|
+
containsMathMarkup: false,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return collectMathAware(element, options);
|
|
347
|
+
};
|
|
348
|
+
//# sourceMappingURL=math-aware-text-processing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"math-aware-text-processing.js","sourceRoot":"","sources":["../../../src/services/tts/math-aware-text-processing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EACN,wBAAwB,EAGxB,kBAAkB,EAClB,sBAAsB,EACtB,6BAA6B,GAC7B,MAAM,sBAAsB,CAAC;AAgC9B,MAAM,iBAAiB,GAAG,CACzB,OAA+B,EACb,EAAE,CAAC,CAAC;IACtB,KAAK,EAAE,EAAE;IACT,GAAG,EAAE,IAAI,GAAG,EAAE;IACd,QAAQ,EAAE,CAAC;IACX,mBAAmB,EAAE,IAAI;IACzB,qBAAqB,EAAE,KAAK;IAC5B,UAAU,EAAE,IAAI;IAChB,OAAO;CACP,CAAC,CAAC;AAEH,MAAM,gBAAgB,GAAG,CAAC,OAAgB,EAAW,EAAE,CACtD,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC;IACtC,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,CAAC;AAEpD,MAAM,sBAAsB,GAAG,CAAC,OAAgB,EAAW,EAAE,CAC5D,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,CAAC;AAExD,MAAM,mBAAmB,GAAG,CAAC,OAAgB,EAAW,EAAE;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,CAAC;IAC/C,OAAO,OAAO,KAAK,YAAY,IAAI,OAAO,KAAK,gBAAgB,CAAC;AACjE,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,OAAgB,EAAiB,EAAE;IACzD,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC;IAClD,OAAO,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,OAAgB,EAAiB,EAAE;IAC/D,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,wBAAwB,CAAC,CAAC;IAClE,OAAO,SAAS,EAAE,SAAS,IAAI,IAAI,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,OAAgB,EAAiB,EAAE;IAC/D,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,MAAM,EAAE,CAAC;QAC9C,OAAO,kBAAkB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IACxC,IAAI,QAAQ,EAAE,CAAC;QACd,MAAM,SAAS,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAC/C,IAAI,SAAS;YAAE,OAAO,SAAS,CAAC;IACjC,CAAC;IACD,IAAI,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,SAAS,EAAE,CAAC;YACf,MAAM,SAAS,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;YAChD,IAAI,SAAS;gBAAE,OAAO,SAAS,CAAC;QACjC,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAC;AACb,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,OAAgB,EAAW,EAAE,CACrD,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;AAEvC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACtC,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,YAAY;IACZ,OAAO;IACP,QAAQ;CACR,CAAC,CAAC;AAEH,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC;IACvC,SAAS;IACT,UAAU;IACV,QAAQ;IACR,OAAO;IACP,KAAK;IACL,MAAM;IACN,cAAc;IACd,WAAW;CACX,CAAC,CAAC;AAEH,MAAM,6BAA6B,GAAG,CACrC,QAAc,EACd,IAAa,EACH,EAAE;IACZ,IAAI,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC;IACrC,IAAI,IAAI,GAAmB,IAAI,CAAC;IAChC,OAAO,OAAO,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAChE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC9C,IACC,sBAAsB,CAAC,GAAG,CAAC,OAAO,CAAC;YACnC,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,EAChC,CAAC;YACF,IAAI,GAAG,OAAO,CAAC;YACf,MAAM;QACP,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,IAAI,IAAI,CAAC;AACrB,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,OAAgB,EAAW,EAAE,CACtD,OAAO,CACN,OAAO,OAAO,CAAC,aAAa,KAAK,UAAU;IAC1C,OAAO,CAAC,aAAa,CACpB,iEAAiE,CACjE,CACF;IACD,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,KAAK,MAAM;IACzC,OAAO,CAAC,YAAY,EAAE,CAAC,aAAa,CAAC;IACrC,gBAAgB,CAAC,OAAO,CAAC,CAAC;AAE3B,MAAM,eAAe,GAAG,CACvB,GAAoB,EACpB,SAAiB,EACjB,OAA8C,EACvC,EAAE;IACT,IACC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC;QAC1B,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QACpB,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,EACtC,CAAC;QACF,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,QAAQ,EAAE,CAAC;QACf,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IACD,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1B,IAAI,OAAO,EAAE,CAAC;QACb,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnC,GAAG,CAAC,UAAU,GAAG,OAAO,CAAC;IAC1B,CAAC;SAAM,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QAC3B,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;IACD,GAAG,CAAC,QAAQ,EAAE,CAAC;AAChB,CAAC,CAAC;AAEF,MAAM,yBAAyB,GAAG,CACjC,GAAoB,EACpB,aAAqB,EACX,EAAE;IACZ,IACC,GAAG,CAAC,mBAAmB;QACvB,GAAG,CAAC,qBAAqB;QACzB,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EACrB,CAAC;QACF,OAAO,KAAK,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjD,6EAA6E;IAC7E,4EAA4E;IAC5E,8DAA8D;IAC9D,OAAO,6BAA6B,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;AAC5E,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,GAAoB,EAAE,QAAc,EAAQ,EAAE;IACrE,MAAM,GAAG,GAAG,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC;IACvC,IAAI,2BAA2B,GAAG,KAAK,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,GAAG,CAAC,mBAAmB,EAAE,CAAC;YAC7B,IAAI,CAAC,YAAY,EAAE,CAAC;gBACnB,GAAG,CAAC,mBAAmB,GAAG,KAAK,CAAC;gBAChC,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC/D,2BAA2B,GAAG,IAAI,CAAC;gBACnC,GAAG,CAAC,qBAAqB,GAAG,KAAK,CAAC;YACnC,CAAC;YACD,SAAS;QACV,CAAC;QACD,IAAI,YAAY,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC;gBAChC,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAC1D,CAAC;YACD,GAAG,CAAC,qBAAqB,GAAG,IAAI,CAAC;YACjC,SAAS;QACV,CAAC;QACD,IACC,CAAC,2BAA2B;YAC5B,yBAAyB,CAAC,GAAG,EAAE,SAAS,CAAC,EACxC,CAAC;YACF,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1D,CAAC;QACD,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/D,2BAA2B,GAAG,IAAI,CAAC;QACnC,GAAG,CAAC,qBAAqB,GAAG,KAAK,CAAC;IACnC,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAC3B,GAAoB,EACpB,SAAmD,EAC5C,EAAE;IACT,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAC7C,MAAM,aAAa,GAAG,OAAO,EAAE,IAAI,IAAI,GAAG,CAAC,UAAU,EAAE,IAAI,IAAI,IAAI,CAAC;QACpE,MAAM,eAAe,GAAG,OAAO,EAAE,MAAM,IAAI,GAAG,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,CAAC;QACvE,MAAM,iBAAiB,GAAG,OAAO;YAChC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YAChD,CAAC,CAAC,aAAa;gBACd,CAAC,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE;gBAClD,CAAC,CAAC,IAAI,CAAC;QACT,IAAI,GAAG,CAAC,mBAAmB,EAAE,CAAC;YAC7B,IAAI,YAAY;gBAAE,SAAS;YAC3B,GAAG,CAAC,mBAAmB,GAAG,KAAK,CAAC;QACjC,CAAC;QACD,IAAI,YAAY,EAAE,CAAC;YAClB,IAAI,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC;gBAChC,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,CAAC;YAC9C,CAAC;YACD,GAAG,CAAC,qBAAqB,GAAG,IAAI,CAAC;YACjC,SAAS;QACV,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,yBAAyB,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,CAAC;YAC1D,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,iBAAiB,CAAC,CAAC;QAC9C,CAAC;QACD,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,iBAAiB,CAAC,CAAC;QACnD,GAAG,CAAC,qBAAqB,GAAG,KAAK,CAAC;QAClC,IAAI,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE;gBAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,MAAM,EAAE,OAAO,CAAC,MAAM;aACtB,CAAC,CAAC;YACH,GAAG,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;QACjE,CAAC;aAAM,IAAI,aAAa,EAAE,CAAC;YAC1B,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE;gBAC7B,IAAI,EAAE,aAAa;gBACnB,MAAM,EAAE,eAAe;aACvB,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,MAAc,EAAU,EAAE;IACzD,IAAI,OAAO,SAAS,KAAK,WAAW;QAAE,OAAO,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1E,IAAI,MAAM,CAAC,oBAAoB,CAAC,aAAa,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACrE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,IAAU,EAAQ,EAAE;QAClC,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,IAAI,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,OAAO,GAAG,IAAe,CAAC;QAChC,IAAI,mBAAmB,CAAC,OAAO,CAAC;YAAE,OAAO;QACzC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACpD,KAAK,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACF,CAAC,CAAC;IACF,KAAK,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAC9B,OAAO,sBAAsB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,0BAA0B,GAAG,CAClC,OAAgB,EAChB,eAAuB,EACvB,OAA+B,EACY,EAAE;IAC7C,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,CAAC,IAAU,EAAQ,EAAE;QAClC,IAAI,kBAAkB,CAAC,IAAI,EAAE,OAAO,CAAC;YAAE,OAAO;QAC9C,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;YACzB,cAAc,CAAC,OAAO,EAAE,IAAY,CAAC,CAAC;YACtC,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,YAAY,GAAG,IAAe,CAAC;QACrC,IACC,sBAAsB,CAAC,YAAY,CAAC;YACpC,mBAAmB,CAAC,YAAY,CAAC,EAChC,CAAC;YACF,OAAO;QACR,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YACzD,KAAK,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACF,CAAC,CAAC;IACF,KAAK,CAAC,OAAO,CAAC,CAAC;IACf,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,sBAAsB,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5D,IAAI,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;IACnC,CAAC;IACD,MAAM,YAAY,GAAG,sBAAsB,CAAC,eAAe,CAAC,CAAC;IAC7D,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,OAAO,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;IACD,OAAO;QACN,IAAI,EAAE,YAAY;QAClB,GAAG,EAAE,IAAI,GAAG,EAAE;KACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,GAAoB,EAAQ,EAAE;IAC7D,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3E,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAChB,GAAG,CAAC,QAAQ,EAAE,CAAC;QACf,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CACxB,IAAa,EACb,OAA+B,EACT,EAAE;IACxB,MAAM,GAAG,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACvC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,IAAI,sBAA2C,CAAC;IAChD,IAAI,kBAAkB,GAAG,KAAK,CAAC;IAE/B,MAAM,cAAc,GAAG,GAAG,EAAE;QAC3B,MAAM,IAAI,GAAG,sBAAsB,CAClC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAC1D,CAAC;QACF,IAAI,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,sBAAsB,EAAE,CAAC,CAAC;QAC5E,CAAC;QACD,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;QAClC,sBAAsB,GAAG,SAAS,CAAC;IACpC,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,IAAU,EAAQ,EAAE;QACxC,IAAI,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC;YAAE,OAAO;QAC3C,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,SAAS,EAAE,CAAC;YACtC,MAAM,aAAa,GAAG,6BAA6B,CAAC,IAAY,EAAE,IAAI,CAAC,CAAC;YACxE,IACC,sBAAsB;gBACtB,aAAa,KAAK,sBAAsB,EACvC,CAAC;gBACF,cAAc,EAAE,CAAC;YAClB,CAAC;YACD,sBAAsB,GAAG,aAAa,CAAC;YACvC,cAAc,CAAC,GAAG,EAAE,IAAY,CAAC,CAAC;YAClC,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,YAAY;YAAE,OAAO;QAChD,MAAM,OAAO,GAAG,IAAe,CAAC;QAChC,IAAI,sBAAsB,CAAC,OAAO,CAAC;YAAE,OAAO;QAC5C,MAAM,eAAe,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACrD,IAAI,eAAe,EAAE,CAAC;YACrB,cAAc,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,0BAA0B,CAC3C,OAAO,EACP,eAAe,EACf,OAAO,CACP,CAAC;YACF,mBAAmB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACpC,MAAM,YAAY,GAAG,sBAAsB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAM;gBACZ,MAAM,EAAE,eAAe;gBACvB,YAAY;gBACZ,aAAa,EAAE,OAAO;aACtB,CAAC,CAAC;YACH,cAAc,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;YAClC,kBAAkB,GAAG,IAAI,CAAC;YAC1B,OAAO;QACR,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;YACpD,WAAW,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACF,CAAC,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QACjD,WAAW,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IAED,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC5B,cAAc,EAAE,CAAC;IACjB,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvC,OAAO,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACxC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC;IACD,OAAO;QACN,WAAW;QACX,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,MAAM;QACN,kBAAkB;KAClB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACzC,OAAgB,EAChB,OAA+B,EACT,EAAE;IACxB,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,wBAAwB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACjE,OAAO;YACN,WAAW,EAAE,IAAI;YACjB,GAAG;YACH,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE;YAC5C,kBAAkB,EAAE,KAAK;SACzB,CAAC;IACH,CAAC;IACD,OAAO,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC,CAAC","sourcesContent":["import { canonicalizeMathML } from \"./mathml-sanitization.js\";\nimport {\n\tcollectVisibleTextAndMap,\n\ttype NormalizedTextMap,\n\ttype TextProcessingOptions,\n\tisNodeHiddenForTTS,\n\tnormalizeTextForSpeech,\n\tshouldInsertWordBoundarySpace,\n} from \"./text-processing.js\";\n\nexport type MathAwareSpeechChunk =\n\t| {\n\t\t\ttype: \"text\";\n\t\t\ttext: string;\n\t\t\tsourceElement?: Element;\n\t }\n\t| {\n\t\t\ttype: \"math\";\n\t\t\tmathml: string;\n\t\t\tfallbackText: string;\n\t\t\tsourceElement?: Element;\n\t };\n\nexport interface MathAwareTextResult {\n\tvisibleText: string;\n\tmap: NormalizedTextMap;\n\tchunks: MathAwareSpeechChunk[];\n\tcontainsMathMarkup: boolean;\n}\n\ninterface TextAccumulator {\n\tchars: string[];\n\tmap: NormalizedTextMap;\n\tposition: number;\n\tinLeadingWhitespace: boolean;\n\tlastCharWasWhitespace: boolean;\n\tlastMapped: { node: Text; offset: number } | null;\n\toptions?: TextProcessingOptions;\n}\n\nconst createAccumulator = (\n\toptions?: TextProcessingOptions,\n): TextAccumulator => ({\n\tchars: [],\n\tmap: new Map(),\n\tposition: 0,\n\tinLeadingWhitespace: true,\n\tlastCharWasWhitespace: false,\n\tlastMapped: null,\n\toptions,\n});\n\nconst isMathJaxElement = (element: Element): boolean =>\n\telement.classList?.contains(\"MathJax\") ||\n\telement.tagName?.toLowerCase() === \"mjx-container\";\n\nconst isAssistiveMathElement = (element: Element): boolean =>\n\telement.tagName?.toLowerCase() === \"mjx-assistive-mml\";\n\nconst isAnnotationElement = (element: Element): boolean => {\n\tconst tagName = element.tagName?.toLowerCase();\n\treturn tagName === \"annotation\" || tagName === \"annotation-xml\";\n};\n\nconst getDataMathML = (element: Element): string | null => {\n\tconst value = element.getAttribute(\"data-mathml\");\n\treturn value && value.trim() ? value : null;\n};\n\nconst findAssistiveMathML = (element: Element): string | null => {\n\tconst assistive = element.querySelector(\"mjx-assistive-mml math\");\n\treturn assistive?.outerHTML || null;\n};\n\nconst findCanonicalMathML = (element: Element): string | null => {\n\tif (element.tagName.toLowerCase() === \"math\") {\n\t\treturn canonicalizeMathML(element.outerHTML);\n\t}\n\tconst dataMath = getDataMathML(element);\n\tif (dataMath) {\n\t\tconst canonical = canonicalizeMathML(dataMath);\n\t\tif (canonical) return canonical;\n\t}\n\tif (isMathJaxElement(element)) {\n\t\tconst assistive = findAssistiveMathML(element);\n\t\tif (assistive) {\n\t\t\tconst canonical = canonicalizeMathML(assistive);\n\t\t\tif (canonical) return canonical;\n\t\t}\n\t}\n\treturn null;\n};\n\nconst isMathContainer = (element: Element): boolean =>\n\tBoolean(findCanonicalMathML(element));\n\nconst TEXT_CHUNK_SOURCE_TAGS = new Set([\n\t\"P\",\n\t\"H1\",\n\t\"H2\",\n\t\"H3\",\n\t\"H4\",\n\t\"H5\",\n\t\"H6\",\n\t\"LI\",\n\t\"TD\",\n\t\"TH\",\n\t\"DD\",\n\t\"DT\",\n\t\"FIGCAPTION\",\n\t\"LABEL\",\n\t\"BUTTON\",\n]);\n\nconst TEXT_CHUNK_SOURCE_ROLES = new Set([\n\t\"heading\",\n\t\"listitem\",\n\t\"option\",\n\t\"radio\",\n\t\"row\",\n\t\"cell\",\n\t\"columnheader\",\n\t\"rowheader\",\n]);\n\nconst resolveTextChunkSourceElement = (\n\ttextNode: Text,\n\troot: Element,\n): Element => {\n\tlet current = textNode.parentElement;\n\tlet best: Element | null = null;\n\twhile (current && current !== root) {\n\t\tconst role = (current.getAttribute(\"role\") || \"\").toLowerCase();\n\t\tconst tagName = current.tagName.toUpperCase();\n\t\tif (\n\t\t\tTEXT_CHUNK_SOURCE_TAGS.has(tagName) ||\n\t\t\tTEXT_CHUNK_SOURCE_ROLES.has(role)\n\t\t) {\n\t\t\tbest = current;\n\t\t\tbreak;\n\t\t}\n\t\tcurrent = current.parentElement;\n\t}\n\treturn best || root;\n};\n\nconst hasMathCandidate = (element: Element): boolean =>\n\tBoolean(\n\t\ttypeof element.querySelector === \"function\" &&\n\t\t\telement.querySelector(\n\t\t\t\t\"math, [data-mathml], .MathJax, mjx-container, mjx-assistive-mml\",\n\t\t\t),\n\t) ||\n\telement.tagName?.toLowerCase() === \"math\" ||\n\telement.hasAttribute?.(\"data-mathml\") ||\n\tisMathJaxElement(element);\n\nconst appendCharacter = (\n\tacc: TextAccumulator,\n\tcharacter: string,\n\tmapping: { node: Text; offset: number } | null,\n): void => {\n\tif (\n\t\t/[.,;:!?]/.test(character) &&\n\t\tacc.chars.length > 0 &&\n\t\tacc.chars[acc.chars.length - 1] === \" \"\n\t) {\n\t\tacc.chars.pop();\n\t\tacc.position--;\n\t\tacc.map.delete(acc.position);\n\t}\n\tacc.chars.push(character);\n\tif (mapping) {\n\t\tacc.map.set(acc.position, mapping);\n\t\tacc.lastMapped = mapping;\n\t} else if (acc.lastMapped) {\n\t\tacc.map.set(acc.position, acc.lastMapped);\n\t}\n\tacc.position++;\n};\n\nconst shouldInsertBoundarySpace = (\n\tacc: TextAccumulator,\n\tnextCharacter: string,\n): boolean => {\n\tif (\n\t\tacc.inLeadingWhitespace ||\n\t\tacc.lastCharWasWhitespace ||\n\t\tacc.chars.length === 0\n\t) {\n\t\treturn false;\n\t}\n\tconst previous = acc.chars[acc.chars.length - 1];\n\t// Defer to the shared locale-aware decision (Intl.Segmenter, falling back to\n\t// alnum) so adjacent text nodes split into words consistently with the main\n\t// visible-text collector, instead of an ASCII-only heuristic.\n\treturn shouldInsertWordBoundarySpace(previous, nextCharacter, acc.options);\n};\n\nconst appendTextNode = (acc: TextAccumulator, textNode: Text): void => {\n\tconst raw = textNode.textContent || \"\";\n\tlet appendedNonWhitespaceInNode = false;\n\tfor (let i = 0; i < raw.length; i++) {\n\t\tconst character = raw[i];\n\t\tconst isWhitespace = /\\s/.test(character);\n\t\tif (acc.inLeadingWhitespace) {\n\t\t\tif (!isWhitespace) {\n\t\t\t\tacc.inLeadingWhitespace = false;\n\t\t\t\tappendCharacter(acc, character, { node: textNode, offset: i });\n\t\t\t\tappendedNonWhitespaceInNode = true;\n\t\t\t\tacc.lastCharWasWhitespace = false;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (isWhitespace) {\n\t\t\tif (!acc.lastCharWasWhitespace) {\n\t\t\t\tappendCharacter(acc, \" \", { node: textNode, offset: i });\n\t\t\t}\n\t\t\tacc.lastCharWasWhitespace = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\t!appendedNonWhitespaceInNode &&\n\t\t\tshouldInsertBoundarySpace(acc, character)\n\t\t) {\n\t\t\tappendCharacter(acc, \" \", { node: textNode, offset: i });\n\t\t}\n\t\tappendCharacter(acc, character, { node: textNode, offset: i });\n\t\tappendedNonWhitespaceInNode = true;\n\t\tacc.lastCharWasWhitespace = false;\n\t}\n};\n\nconst appendCollectedText = (\n\tacc: TextAccumulator,\n\tcollected: { text: string; map: NormalizedTextMap },\n): void => {\n\tfor (let i = 0; i < collected.text.length; i++) {\n\t\tconst character = collected.text[i];\n\t\tconst isWhitespace = /\\s/.test(character);\n\t\tconst mapping = collected.map.get(i) || null;\n\t\tconst syntheticNode = mapping?.node || acc.lastMapped?.node || null;\n\t\tconst syntheticOffset = mapping?.offset ?? acc.lastMapped?.offset ?? 0;\n\t\tconst normalizedMapping = mapping\n\t\t\t? { node: mapping.node, offset: mapping.offset }\n\t\t\t: syntheticNode\n\t\t\t\t? { node: syntheticNode, offset: syntheticOffset }\n\t\t\t\t: null;\n\t\tif (acc.inLeadingWhitespace) {\n\t\t\tif (isWhitespace) continue;\n\t\t\tacc.inLeadingWhitespace = false;\n\t\t}\n\t\tif (isWhitespace) {\n\t\t\tif (!acc.lastCharWasWhitespace) {\n\t\t\t\tappendCharacter(acc, \" \", normalizedMapping);\n\t\t\t}\n\t\t\tacc.lastCharWasWhitespace = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (i === 0 && shouldInsertBoundarySpace(acc, character)) {\n\t\t\tappendCharacter(acc, \" \", normalizedMapping);\n\t\t}\n\t\tappendCharacter(acc, character, normalizedMapping);\n\t\tacc.lastCharWasWhitespace = false;\n\t\tif (mapping) {\n\t\t\tacc.map.set(acc.position - 1, {\n\t\t\t\tnode: mapping.node,\n\t\t\t\toffset: mapping.offset,\n\t\t\t});\n\t\t\tacc.lastMapped = { node: mapping.node, offset: mapping.offset };\n\t\t} else if (syntheticNode) {\n\t\t\tacc.map.set(acc.position - 1, {\n\t\t\t\tnode: syntheticNode,\n\t\t\t\toffset: syntheticOffset,\n\t\t\t});\n\t\t}\n\t}\n};\n\nconst textFallbackFromMathML = (mathml: string): string => {\n\tif (typeof DOMParser === \"undefined\") return \"\";\n\tconst parsed = new DOMParser().parseFromString(mathml, \"application/xml\");\n\tif (parsed.getElementsByTagName(\"parsererror\").length > 0) return \"\";\n\tconst parts: string[] = [];\n\tconst visit = (node: Node): void => {\n\t\tif (node.nodeType === 3) {\n\t\t\tconst text = node.textContent || \"\";\n\t\t\tif (text.trim()) parts.push(text);\n\t\t\treturn;\n\t\t}\n\t\tif (node.nodeType !== 1) return;\n\t\tconst element = node as Element;\n\t\tif (isAnnotationElement(element)) return;\n\t\tfor (const child of Array.from(element.childNodes)) {\n\t\t\tvisit(child);\n\t\t}\n\t};\n\tvisit(parsed.documentElement);\n\treturn normalizeTextForSpeech(parts.join(\" \"));\n};\n\nconst collectVisibleMathFallback = (\n\telement: Element,\n\tcanonicalMathML: string,\n\toptions?: TextProcessingOptions,\n): { text: string; map: NormalizedTextMap } => {\n\tconst mathAcc = createAccumulator(options);\n\tconst visit = (node: Node): void => {\n\t\tif (isNodeHiddenForTTS(node, element)) return;\n\t\tif (node.nodeType === 3) {\n\t\t\tappendTextNode(mathAcc, node as Text);\n\t\t\treturn;\n\t\t}\n\t\tif (node.nodeType !== 1) return;\n\t\tconst childElement = node as Element;\n\t\tif (\n\t\t\tisAssistiveMathElement(childElement) ||\n\t\t\tisAnnotationElement(childElement)\n\t\t) {\n\t\t\treturn;\n\t\t}\n\t\tfor (const child of Array.from(childElement.childNodes)) {\n\t\t\tvisit(child);\n\t\t}\n\t};\n\tvisit(element);\n\ttrimTrailingWhitespace(mathAcc);\n\tconst text = normalizeTextForSpeech(mathAcc.chars.join(\"\"));\n\tif (text) {\n\t\treturn { text, map: mathAcc.map };\n\t}\n\tconst fallbackText = textFallbackFromMathML(canonicalMathML);\n\tif (!fallbackText) {\n\t\treturn collectVisibleTextAndMap(element, options);\n\t}\n\treturn {\n\t\ttext: fallbackText,\n\t\tmap: new Map(),\n\t};\n};\n\nconst trimTrailingWhitespace = (acc: TextAccumulator): void => {\n\twhile (acc.chars.length > 0 && /\\s/.test(acc.chars[acc.chars.length - 1])) {\n\t\tacc.chars.pop();\n\t\tacc.position--;\n\t\tacc.map.delete(acc.position);\n\t}\n};\n\nconst collectMathAware = (\n\troot: Element,\n\toptions?: TextProcessingOptions,\n): MathAwareTextResult => {\n\tconst acc = createAccumulator(options);\n\tconst chunks: MathAwareSpeechChunk[] = [];\n\tlet textChunkStart = 0;\n\tlet textChunkSourceElement: Element | undefined;\n\tlet containsMathMarkup = false;\n\n\tconst flushTextChunk = () => {\n\t\tconst text = normalizeTextForSpeech(\n\t\t\tacc.chars.slice(textChunkStart, acc.chars.length).join(\"\"),\n\t\t);\n\t\tif (text) {\n\t\t\tchunks.push({ type: \"text\", text, sourceElement: textChunkSourceElement });\n\t\t}\n\t\ttextChunkStart = acc.chars.length;\n\t\ttextChunkSourceElement = undefined;\n\t};\n\n\tconst processNode = (node: Node): void => {\n\t\tif (isNodeHiddenForTTS(node, root)) return;\n\t\tif (node.nodeType === Node.TEXT_NODE) {\n\t\t\tconst sourceElement = resolveTextChunkSourceElement(node as Text, root);\n\t\t\tif (\n\t\t\t\ttextChunkSourceElement &&\n\t\t\t\tsourceElement !== textChunkSourceElement\n\t\t\t) {\n\t\t\t\tflushTextChunk();\n\t\t\t}\n\t\t\ttextChunkSourceElement = sourceElement;\n\t\t\tappendTextNode(acc, node as Text);\n\t\t\treturn;\n\t\t}\n\t\tif (node.nodeType !== Node.ELEMENT_NODE) return;\n\t\tconst element = node as Element;\n\t\tif (isAssistiveMathElement(element)) return;\n\t\tconst canonicalMathML = findCanonicalMathML(element);\n\t\tif (canonicalMathML) {\n\t\t\tflushTextChunk();\n\t\t\tconst collected = collectVisibleMathFallback(\n\t\t\t\telement,\n\t\t\t\tcanonicalMathML,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tappendCollectedText(acc, collected);\n\t\t\tconst fallbackText = normalizeTextForSpeech(collected.text);\n\t\t\tchunks.push({\n\t\t\t\ttype: \"math\",\n\t\t\t\tmathml: canonicalMathML,\n\t\t\t\tfallbackText,\n\t\t\t\tsourceElement: element,\n\t\t\t});\n\t\t\ttextChunkStart = acc.chars.length;\n\t\t\tcontainsMathMarkup = true;\n\t\t\treturn;\n\t\t}\n\t\tfor (const child of Array.from(element.childNodes)) {\n\t\t\tprocessNode(child);\n\t\t}\n\t};\n\n\tfor (const child of Array.from(root.childNodes)) {\n\t\tprocessNode(child);\n\t}\n\n\ttrimTrailingWhitespace(acc);\n\tflushTextChunk();\n\tconst visibleText = acc.chars.join(\"\");\n\twhile (acc.map.has(visibleText.length)) {\n\t\tacc.map.delete(visibleText.length);\n\t}\n\treturn {\n\t\tvisibleText,\n\t\tmap: acc.map,\n\t\tchunks,\n\t\tcontainsMathMarkup,\n\t};\n};\n\nexport const collectMathAwareTextAndMap = (\n\telement: Element,\n\toptions?: TextProcessingOptions,\n): MathAwareTextResult => {\n\tif (!hasMathCandidate(element)) {\n\t\tconst { text, map } = collectVisibleTextAndMap(element, options);\n\t\treturn {\n\t\t\tvisibleText: text,\n\t\t\tmap,\n\t\t\tchunks: text ? [{ type: \"text\", text }] : [],\n\t\t\tcontainsMathMarkup: false,\n\t\t};\n\t}\n\treturn collectMathAware(element, options);\n};\n"]}
|