@pie-players/pie-assessment-toolkit 0.3.73 → 0.3.75
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 +469 -318
- package/dist/attempt/AssessmentSession.d.ts +1 -5
- package/dist/attempt/AssessmentSession.js +0 -29
- package/dist/attempt/TestSession.d.ts +1 -20
- package/dist/attempt/TestSession.js +8 -99
- package/dist/components/ItemScope.custom-element.d.ts +1 -0
- package/dist/components/ItemScope.custom-element.js +1 -0
- package/dist/components/ItemToolBar.custom-element.d.ts +1 -0
- package/dist/components/ItemToolBar.custom-element.js +1 -1
- package/dist/components/PieAssessmentToolkit.custom-element.d.ts +1 -0
- package/dist/components/PieAssessmentToolkit.custom-element.js +13 -13
- package/dist/components/SectionToolBar.custom-element.d.ts +1 -0
- package/dist/components/SectionToolBar.custom-element.js +1 -1
- package/dist/components/chunks/ItemToolBar-7tt7v2xp.js +4 -0
- package/dist/components/chunks/ItemToolBar-97hx60z4.js +2 -0
- package/dist/components/chunks/ItemToolBar-bxwyhr5p.js +2 -0
- package/dist/components/chunks/ItemToolBar-jx38w99s.js +2 -0
- package/dist/components/chunks/ItemToolBar-rd2bb4p6.js +40 -0
- package/dist/components/chunks/ItemToolBar-vdkyn9f3.js +2 -0
- package/dist/components/chunks/sre-engine-jd8cc08x.js +1 -0
- package/dist/components/item-scope-element.d.ts +2 -0
- package/dist/components/item-scope-element.js +2 -0
- package/dist/context/assessment-toolkit-context.d.ts +32 -6
- package/dist/index.d.ts +23 -48
- package/dist/index.js +12 -37
- package/dist/policy/core/PolicySource.d.ts +1 -1
- package/dist/policy/core/ToolPolicyEngine.d.ts +85 -34
- package/dist/policy/core/ToolPolicyEngine.js +148 -46
- package/dist/policy/core/compose-decision.d.ts +28 -7
- package/dist/policy/core/compose-decision.js +75 -54
- package/dist/policy/core/decision-types.d.ts +65 -34
- package/dist/policy/core/feature-decision.d.ts +16 -9
- package/dist/policy/core/feature-decision.js +14 -8
- package/dist/policy/core/pnp-policy-inputs.d.ts +24 -33
- package/dist/policy/core/pnp-policy-inputs.js +28 -56
- package/dist/policy/core/provenance.d.ts +9 -4
- package/dist/policy/core/provenance.js +1 -1
- package/dist/policy/engine.d.ts +7 -23
- package/dist/policy/engine.js +5 -20
- package/dist/policy/sources/PnpPolicySource.d.ts +44 -61
- package/dist/policy/sources/PnpPolicySource.js +117 -118
- package/dist/runtime/SectionControllerBinding.d.ts +279 -0
- package/dist/runtime/SectionControllerBinding.js +450 -0
- package/dist/runtime/SectionRuntimeEngine.d.ts +26 -289
- package/dist/runtime/SectionRuntimeEngine.js +30 -372
- package/dist/runtime/adapter/SectionEngineAdapter.d.ts +19 -80
- package/dist/runtime/adapter/SectionEngineAdapter.js +19 -92
- package/dist/runtime/adapter/dom-event-bridge.d.ts +15 -30
- package/dist/runtime/adapter/dom-event-bridge.js +17 -44
- package/dist/runtime/adapter/subscriber-fanout.d.ts +1 -2
- package/dist/runtime/adapter/subscriber-fanout.js +1 -2
- package/dist/runtime/catalog-context.d.ts +19 -0
- package/dist/runtime/catalog-context.js +28 -0
- package/dist/runtime/composition-emit-scheduler.d.ts +9 -15
- package/dist/runtime/composition-emit-scheduler.js +9 -15
- package/dist/runtime/content-language.d.ts +32 -0
- package/dist/runtime/content-language.js +46 -0
- package/dist/runtime/content-region.d.ts +17 -0
- package/dist/runtime/content-region.js +29 -0
- package/dist/runtime/content-registration.d.ts +13 -0
- package/dist/runtime/content-registration.js +25 -0
- package/dist/runtime/core/SectionEngineCore.d.ts +2 -2
- package/dist/runtime/core/SectionEngineCore.js +2 -2
- package/dist/runtime/core/cohort.d.ts +1 -1
- package/dist/runtime/core/cohort.js +1 -1
- package/dist/runtime/core/engine-input.d.ts +18 -30
- package/dist/runtime/core/engine-input.js +11 -12
- package/dist/runtime/core/engine-output.d.ts +16 -40
- package/dist/runtime/core/engine-output.js +9 -21
- package/dist/runtime/core/engine-readiness.d.ts +5 -37
- package/dist/runtime/core/engine-readiness.js +8 -41
- package/dist/runtime/core/engine-resolver.d.ts +46 -74
- package/dist/runtime/core/engine-resolver.js +18 -43
- package/dist/runtime/core/engine-state.d.ts +28 -36
- package/dist/runtime/core/engine-state.js +18 -21
- package/dist/runtime/core/engine-transition.d.ts +14 -19
- package/dist/runtime/core/engine-transition.js +71 -51
- package/dist/runtime/engine.d.ts +16 -19
- package/dist/runtime/engine.js +12 -19
- package/dist/runtime/page-warnings.d.ts +13 -0
- package/dist/runtime/page-warnings.js +22 -0
- package/dist/runtime/policy-input-forwarding.d.ts +25 -0
- package/dist/runtime/policy-input-forwarding.js +25 -0
- package/dist/runtime/registration-events.d.ts +14 -0
- package/dist/runtime/runtime-event-claim.d.ts +8 -0
- package/dist/runtime/runtime-event-claim.js +15 -0
- package/dist/runtime/shell-event-bridge.d.ts +45 -0
- package/dist/runtime/shell-event-bridge.js +127 -0
- package/dist/runtime/shell-registration.d.ts +73 -0
- package/dist/runtime/shell-registration.js +95 -0
- package/dist/runtime/shell-scope.d.ts +68 -0
- package/dist/runtime/shell-scope.js +188 -0
- package/dist/runtime/tool-host-contract.d.ts +28 -10
- package/dist/runtime/tool-host-contract.js +31 -10
- package/dist/runtime/unclaimed-registration-watch.d.ts +7 -0
- package/dist/runtime/unclaimed-registration-watch.js +23 -0
- package/dist/services/ElementToolStateStore.d.ts +14 -10
- package/dist/services/ElementToolStateStore.js +37 -13
- package/dist/services/HighlightCoordinator.d.ts +22 -28
- package/dist/services/HighlightCoordinator.js +431 -327
- package/dist/services/RangeSerializer.d.ts +10 -1
- package/dist/services/RangeSerializer.js +87 -14
- package/dist/services/TTSService.d.ts +130 -69
- package/dist/services/TTSService.js +713 -570
- package/dist/services/ToolCoordinator.d.ts +43 -66
- package/dist/services/ToolCoordinator.js +164 -208
- package/dist/services/ToolRegistry.d.ts +78 -54
- package/dist/services/ToolRegistry.js +107 -105
- package/dist/services/ToolkitCoordinator.d.ts +261 -175
- package/dist/services/ToolkitCoordinator.js +667 -433
- package/dist/services/audio-handoff.d.ts +6 -7
- package/dist/services/audio-handoff.js +20 -14
- package/dist/services/catalog-owner.d.ts +11 -1
- package/dist/services/catalog-owner.js +25 -0
- package/dist/services/create-tools-config.d.ts +2 -2
- package/dist/services/framework-error-bus.d.ts +3 -6
- package/dist/services/framework-error-bus.js +3 -6
- package/dist/services/framework-error.d.ts +30 -8
- package/dist/services/framework-error.js +9 -7
- package/dist/services/interfaces.d.ts +158 -129
- package/dist/services/section-controller-types.d.ts +15 -0
- package/dist/services/section-session-assignment.d.ts +21 -0
- package/dist/services/section-session-assignment.js +82 -0
- package/dist/services/spoken-audio-cards.d.ts +0 -2
- package/dist/services/spoken-audio-cards.js +1 -3
- package/dist/services/tool-config-defaults.d.ts +3 -3
- package/dist/services/tool-config-defaults.js +3 -3
- package/dist/services/tool-config-validation.d.ts +14 -4
- package/dist/services/tool-config-validation.js +63 -71
- package/dist/services/tool-context.d.ts +0 -8
- package/dist/services/tool-context.js +29 -25
- package/dist/services/tool-icons.d.ts +4 -3
- package/dist/services/tool-icons.js +4 -3
- package/dist/services/tool-instance-id.d.ts +0 -1
- package/dist/services/tool-instance-id.js +0 -6
- package/dist/services/tool-providers/TTSToolProvider.d.ts +41 -80
- package/dist/services/tool-providers/TTSToolProvider.js +81 -40
- package/dist/services/tool-providers/ToolProviderApi.d.ts +10 -4
- package/dist/services/tool-providers/ToolProviderRegistry.d.ts +32 -28
- package/dist/services/tool-providers/ToolProviderRegistry.js +121 -107
- package/dist/services/tool-providers/index.d.ts +0 -9
- package/dist/services/tool-providers/index.js +2 -4
- package/dist/services/tool-request.d.ts +27 -12
- package/dist/services/tool-request.js +24 -14
- package/dist/services/toolbar-decided-tools.d.ts +15 -0
- package/dist/services/toolbar-decided-tools.js +28 -0
- package/dist/services/tools-config-normalizer.d.ts +37 -23
- package/dist/services/tools-config-normalizer.js +2 -10
- package/dist/services/tts/browser-provider.d.ts +38 -2
- package/dist/services/tts/browser-provider.js +276 -140
- package/dist/services/tts/catalog-span-alignment.d.ts +2 -17
- package/dist/services/tts/catalog-span-alignment.js +17 -142
- package/dist/services/tts/flat-tree.d.ts +71 -0
- package/dist/services/tts/flat-tree.js +269 -0
- package/dist/services/tts/generated-speech/assemble-plan.d.ts +3 -1
- package/dist/services/tts/generated-speech/assemble-plan.js +3 -1
- package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +2 -0
- package/dist/services/tts/generated-speech/dom/build-dom-plan.js +1 -1
- package/dist/services/tts/generated-speech/math-speech-cache.d.ts +3 -1
- package/dist/services/tts/generated-speech/math-speech-cache.js +5 -1
- package/dist/services/tts/generated-speech/types.d.ts +0 -12
- package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +12 -62
- package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +38 -5
- package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +3 -6
- package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +6 -4
- package/dist/services/tts/highlight-pipeline/readable-region.js +3 -2
- package/dist/services/tts/highlight-pipeline/types.d.ts +5 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +12 -0
- package/dist/services/tts/highlight-pipeline/visible-map-range.js +73 -7
- package/dist/services/tts/math-alignment/index.d.ts +0 -5
- package/dist/services/tts/math-alignment/index.js +5 -25
- package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +9 -1
- package/dist/services/tts/math-alignment/speech-tokenizer.js +38 -36
- package/dist/services/tts/math-alignment/types.d.ts +4 -2
- package/dist/services/tts/math-aware-text-processing.d.ts +5 -1
- package/dist/services/tts/math-aware-text-processing.js +74 -18
- package/dist/services/tts/math-control-names.d.ts +21 -0
- package/dist/services/tts/math-control-names.js +142 -0
- package/dist/services/tts/math-speech.d.ts +16 -1
- package/dist/services/tts/math-speech.js +55 -19
- package/dist/services/tts/provider-options.d.ts +41 -0
- package/dist/services/tts/provider-options.js +1 -0
- package/dist/services/tts/sre-engine.d.ts +7 -0
- package/dist/services/tts/sre-engine.js +11 -0
- package/dist/services/tts/sre-locales.d.ts +14 -0
- package/dist/services/tts/sre-locales.js +57 -0
- package/dist/services/tts/ssml/spoken-text.d.ts +2 -26
- package/dist/services/tts/ssml/spoken-text.js +29 -0
- package/dist/services/tts/start-failure.d.ts +11 -0
- package/dist/services/tts/start-failure.js +19 -0
- package/dist/services/tts/text-processing.d.ts +9 -1
- package/dist/services/tts/text-processing.js +46 -34
- package/dist/services/tts/text-segmentation.d.ts +7 -0
- package/dist/services/tts/text-segmentation.js +47 -0
- package/dist/services/tts-runtime-config.d.ts +44 -18
- package/dist/services/tts-runtime-config.js +22 -93
- package/dist/tools/content-capability-resolution.d.ts +7 -9
- package/dist/tools/content-capability-resolution.js +8 -45
- package/dist/tools/registration.d.ts +58 -0
- package/dist/tools/registration.js +54 -0
- package/dist/tools/registrations/toolbar-registration-helpers.js +2 -2
- package/dist/tools/tool-surface-host.d.ts +6 -10
- package/dist/tools/tool-surface-host.js +44 -42
- package/dist/tools/tool-tag-map.d.ts +5 -15
- package/dist/tools/tool-tag-map.js +6 -20
- package/dist/utils/structural-equality.d.ts +11 -0
- package/dist/utils/structural-equality.js +47 -0
- package/package.json +24 -59
- package/dist/attempt/adapters/activity-to-test-attempt-session.d.ts +0 -49
- package/dist/attempt/adapters/activity-to-test-attempt-session.js +0 -138
- package/dist/components/chunks/ItemToolBar-byq80z42.js +0 -40
- package/dist/components/chunks/ItemToolBar-d36g3aw4.js +0 -49
- package/dist/context/runtime-context-consumer.d.ts +0 -13
- package/dist/context/runtime-context-consumer.js +0 -51
- package/dist/item-loader.d.ts +0 -44
- package/dist/item-loader.js +0 -61
- package/dist/policy/core/engine-context.d.ts +0 -29
- package/dist/policy/core/engine-context.js +0 -27
- package/dist/policy/core/policy-source-tag.d.ts +0 -31
- package/dist/policy/core/policy-source-tag.js +0 -28
- package/dist/policy/internal.d.ts +0 -25
- package/dist/policy/internal.js +0 -25
- package/dist/runtime/adapter/coordinator-bridge.d.ts +0 -92
- package/dist/runtime/adapter/coordinator-bridge.js +0 -126
- package/dist/runtime/adapter/framework-error-bridge.d.ts +0 -36
- package/dist/runtime/adapter/framework-error-bridge.js +0 -46
- package/dist/runtime/adapter/instrumentation-bridge.d.ts +0 -31
- package/dist/runtime/adapter/instrumentation-bridge.js +0 -38
- package/dist/runtime/core/engine-stage-derivation.d.ts +0 -18
- package/dist/runtime/core/engine-stage-derivation.js +0 -29
- package/dist/runtime/engine-context.d.ts +0 -39
- package/dist/runtime/engine-context.js +0 -31
- package/dist/runtime/internal.d.ts +0 -43
- package/dist/runtime/internal.js +0 -45
- package/dist/runtime/section-runtime-engine-host-context.d.ts +0 -80
- package/dist/runtime/section-runtime-engine-host-context.js +0 -95
- package/dist/runtime/stage-emit-gate.d.ts +0 -92
- package/dist/runtime/stage-emit-gate.js +0 -82
- package/dist/services/I18nService.d.ts +0 -47
- package/dist/services/I18nService.js +0 -84
- package/dist/services/ThemeProvider.d.ts +0 -95
- package/dist/services/ThemeProvider.js +0 -334
- package/dist/services/ToolConfigResolver.d.ts +0 -161
- package/dist/services/ToolConfigResolver.js +0 -229
- package/dist/services/catalog-media.d.ts +0 -56
- package/dist/services/catalog-media.js +0 -190
- package/dist/services/createDefaultToolRegistry.d.ts +0 -53
- package/dist/services/createDefaultToolRegistry.js +0 -45
- package/dist/services/defaultPersonalNeedsProfile.d.ts +0 -21
- package/dist/services/defaultPersonalNeedsProfile.js +0 -26
- package/dist/services/pnp-standard-features.d.ts +0 -238
- package/dist/services/pnp-standard-features.js +0 -338
- package/dist/services/tool-providers/CortexToolProvider.d.ts +0 -13
- package/dist/services/tool-providers/CortexToolProvider.js +0 -32
- package/dist/services/tool-providers/DesmosToolProvider.d.ts +0 -14
- package/dist/services/tool-providers/DesmosToolProvider.js +0 -32
- package/dist/services/tool-providers/GeoGebraToolProvider.d.ts +0 -16
- package/dist/services/tool-providers/GeoGebraToolProvider.js +0 -32
- package/dist/services/tool-providers/LazyCalculatorToolProvider.d.ts +0 -35
- package/dist/services/tool-providers/LazyCalculatorToolProvider.js +0 -95
- package/dist/services/tts/visual-line-ranges.d.ts +0 -18
- package/dist/services/tts/visual-line-ranges.js +0 -226
- package/dist/tools/client.d.ts +0 -12
- package/dist/tools/client.js +0 -14
- package/dist/tools/index.d.ts +0 -8
- package/dist/tools/index.js +0 -10
- package/dist/tools/internal.d.ts +0 -43
- package/dist/tools/internal.js +0 -41
- package/dist/tools/response-discovery.d.ts +0 -61
- package/dist/tools/response-discovery.js +0 -113
- package/dist/tools/types.d.ts +0 -337
- package/dist/tools/types.js +0 -12
- package/dist/tools/variant-resolver.d.ts +0 -41
- package/dist/tools/variant-resolver.js +0 -207
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { flatQuerySelector, flatTreeChildNodes, flatTreeParentElement, rangeIntersectsComposedNode, } from "./flat-tree.js";
|
|
1
2
|
import { canonicalizeMathML } from "./mathml-sanitization.js";
|
|
2
|
-
import { collectVisibleTextAndMap, isNodeExcludedFromSpeech, normalizeTextForSpeech, shouldInsertWordBoundarySpace, } from "./text-processing.js";
|
|
3
|
+
import { collectVisibleTextAndMap, isNodeExcludedFromSpeech, normalizeTextForSpeech, rangeHoldsSpeakableElement, shouldInsertWordBoundarySpace, } from "./text-processing.js";
|
|
3
4
|
const createAccumulator = (options) => ({
|
|
4
5
|
chars: [],
|
|
5
6
|
map: new Map(),
|
|
@@ -21,7 +22,7 @@ const getDataMathML = (element) => {
|
|
|
21
22
|
return value && value.trim() ? value : null;
|
|
22
23
|
};
|
|
23
24
|
const findAssistiveMathML = (element) => {
|
|
24
|
-
const assistive = element
|
|
25
|
+
const assistive = flatQuerySelector(element, "mjx-assistive-mml math");
|
|
25
26
|
return assistive?.outerHTML || null;
|
|
26
27
|
};
|
|
27
28
|
const findCanonicalMathML = (element) => {
|
|
@@ -44,7 +45,6 @@ const findCanonicalMathML = (element) => {
|
|
|
44
45
|
}
|
|
45
46
|
return null;
|
|
46
47
|
};
|
|
47
|
-
const isMathContainer = (element) => Boolean(findCanonicalMathML(element));
|
|
48
48
|
const TEXT_CHUNK_SOURCE_TAGS = new Set([
|
|
49
49
|
"P",
|
|
50
50
|
"H1",
|
|
@@ -73,7 +73,7 @@ const TEXT_CHUNK_SOURCE_ROLES = new Set([
|
|
|
73
73
|
"rowheader",
|
|
74
74
|
]);
|
|
75
75
|
const resolveTextChunkSourceElement = (textNode, root) => {
|
|
76
|
-
let current = textNode
|
|
76
|
+
let current = flatTreeParentElement(textNode);
|
|
77
77
|
let best = null;
|
|
78
78
|
while (current && current !== root) {
|
|
79
79
|
const role = (current.getAttribute("role") || "").toLowerCase();
|
|
@@ -83,12 +83,12 @@ const resolveTextChunkSourceElement = (textNode, root) => {
|
|
|
83
83
|
best = current;
|
|
84
84
|
break;
|
|
85
85
|
}
|
|
86
|
-
current = current
|
|
86
|
+
current = flatTreeParentElement(current);
|
|
87
87
|
}
|
|
88
88
|
return best || root;
|
|
89
89
|
};
|
|
90
90
|
const hasMathCandidate = (element) => Boolean(typeof element.querySelector === "function" &&
|
|
91
|
-
element
|
|
91
|
+
flatQuerySelector(element, "math, [data-mathml], .MathJax, mjx-container, mjx-assistive-mml")) ||
|
|
92
92
|
element.tagName?.toLowerCase() === "math" ||
|
|
93
93
|
element.hasAttribute?.("data-mathml") ||
|
|
94
94
|
isMathJaxElement(element);
|
|
@@ -122,10 +122,11 @@ const shouldInsertBoundarySpace = (acc, nextCharacter) => {
|
|
|
122
122
|
// visible-text collector, instead of an ASCII-only heuristic.
|
|
123
123
|
return shouldInsertWordBoundarySpace(previous, nextCharacter, acc.options);
|
|
124
124
|
};
|
|
125
|
-
|
|
125
|
+
/** `start` and `end` bound the part of the node appended, for a selection. */
|
|
126
|
+
const appendTextNode = (acc, textNode, start = 0, end = (textNode.textContent || "").length) => {
|
|
126
127
|
const raw = textNode.textContent || "";
|
|
127
128
|
let appendedNonWhitespaceInNode = false;
|
|
128
|
-
for (let i =
|
|
129
|
+
for (let i = start; i < end; i++) {
|
|
129
130
|
const character = raw[i];
|
|
130
131
|
const isWhitespace = /\s/.test(character);
|
|
131
132
|
if (acc.inLeadingWhitespace) {
|
|
@@ -223,6 +224,49 @@ const textFallbackFromMathML = (mathml) => {
|
|
|
223
224
|
visit(parsed.documentElement);
|
|
224
225
|
return normalizeTextForSpeech(parts.join(" "));
|
|
225
226
|
};
|
|
227
|
+
// MathJax CHTML paints its glyphs from CSS, so a typeset equation's only text
|
|
228
|
+
// nodes are the source MathML it keeps, clipped, in mjx-assistive-mml. Mapping
|
|
229
|
+
// the fallback text onto them places a catalog word inside its equation, where
|
|
230
|
+
// the rendered-math resolver finds the glyph. Any divergence between the two
|
|
231
|
+
// texts leaves the fallback unmapped.
|
|
232
|
+
const mapFallbackToAssistiveMath = (element, fallbackText) => {
|
|
233
|
+
const map = new Map();
|
|
234
|
+
const sourceMath = isMathJaxElement(element)
|
|
235
|
+
? flatQuerySelector(element, "mjx-assistive-mml math")
|
|
236
|
+
: null;
|
|
237
|
+
if (!sourceMath)
|
|
238
|
+
return map;
|
|
239
|
+
const characters = [];
|
|
240
|
+
const visit = (node) => {
|
|
241
|
+
if (node.nodeType === 3) {
|
|
242
|
+
const text = node.textContent || "";
|
|
243
|
+
for (let offset = 0; offset < text.length; offset++) {
|
|
244
|
+
if (!/\s/.test(text[offset])) {
|
|
245
|
+
characters.push({ node: node, offset });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (node.nodeType !== 1 || isAnnotationElement(node))
|
|
251
|
+
return;
|
|
252
|
+
for (const child of flatTreeChildNodes(node)) {
|
|
253
|
+
visit(child);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
visit(sourceMath);
|
|
257
|
+
let next = 0;
|
|
258
|
+
for (let index = 0; index < fallbackText.length; index++) {
|
|
259
|
+
if (/\s/.test(fallbackText[index]))
|
|
260
|
+
continue;
|
|
261
|
+
const mapping = characters[next];
|
|
262
|
+
if (mapping?.node.textContent?.[mapping.offset] !== fallbackText[index]) {
|
|
263
|
+
return new Map();
|
|
264
|
+
}
|
|
265
|
+
map.set(index, mapping);
|
|
266
|
+
next++;
|
|
267
|
+
}
|
|
268
|
+
return next === characters.length ? map : new Map();
|
|
269
|
+
};
|
|
226
270
|
const collectVisibleMathFallback = (element, canonicalMathML, options) => {
|
|
227
271
|
const mathAcc = createAccumulator(options);
|
|
228
272
|
const visit = (node) => {
|
|
@@ -239,7 +283,7 @@ const collectVisibleMathFallback = (element, canonicalMathML, options) => {
|
|
|
239
283
|
isAnnotationElement(childElement)) {
|
|
240
284
|
return;
|
|
241
285
|
}
|
|
242
|
-
for (const child of
|
|
286
|
+
for (const child of flatTreeChildNodes(childElement)) {
|
|
243
287
|
visit(child);
|
|
244
288
|
}
|
|
245
289
|
};
|
|
@@ -255,7 +299,7 @@ const collectVisibleMathFallback = (element, canonicalMathML, options) => {
|
|
|
255
299
|
}
|
|
256
300
|
return {
|
|
257
301
|
text: fallbackText,
|
|
258
|
-
map:
|
|
302
|
+
map: mapFallbackToAssistiveMath(element, fallbackText),
|
|
259
303
|
};
|
|
260
304
|
};
|
|
261
305
|
const trimTrailingWhitespace = (acc) => {
|
|
@@ -265,7 +309,7 @@ const trimTrailingWhitespace = (acc) => {
|
|
|
265
309
|
acc.map.delete(acc.position);
|
|
266
310
|
}
|
|
267
311
|
};
|
|
268
|
-
const collectMathAware = (root, options) => {
|
|
312
|
+
const collectMathAware = (root, options, range) => {
|
|
269
313
|
const acc = createAccumulator(options);
|
|
270
314
|
const chunks = [];
|
|
271
315
|
let textChunkStart = 0;
|
|
@@ -286,22 +330,28 @@ const collectMathAware = (root, options) => {
|
|
|
286
330
|
const processNode = (node) => {
|
|
287
331
|
if (isNodeExcludedFromSpeech(node, root))
|
|
288
332
|
return;
|
|
289
|
-
if (
|
|
333
|
+
if (range && !rangeIntersectsComposedNode(range, node))
|
|
334
|
+
return;
|
|
335
|
+
if (node.nodeType === 3) {
|
|
290
336
|
const sourceElement = resolveTextChunkSourceElement(node, root);
|
|
291
337
|
if (textChunkSourceElement && sourceElement !== textChunkSourceElement) {
|
|
292
338
|
flushTextChunk();
|
|
293
339
|
}
|
|
294
340
|
textChunkSourceElement = sourceElement;
|
|
295
|
-
|
|
341
|
+
const textNode = node;
|
|
342
|
+
appendTextNode(acc, textNode, range?.startContainer === textNode ? range.startOffset : undefined, range?.endContainer === textNode ? range.endOffset : undefined);
|
|
296
343
|
return;
|
|
297
344
|
}
|
|
298
|
-
if (node.nodeType !==
|
|
345
|
+
if (node.nodeType !== 1)
|
|
299
346
|
return;
|
|
300
347
|
const element = node;
|
|
301
348
|
if (isAssistiveMathElement(element))
|
|
302
349
|
return;
|
|
303
350
|
const canonicalMathML = findCanonicalMathML(element);
|
|
304
|
-
|
|
351
|
+
// An equation is spoken as math only when the selection holds all of it;
|
|
352
|
+
// a part of one reads as the text selected.
|
|
353
|
+
if (canonicalMathML &&
|
|
354
|
+
(!range || rangeHoldsSpeakableElement(range, element, root))) {
|
|
305
355
|
flushTextChunk();
|
|
306
356
|
const collected = collectVisibleMathFallback(element, canonicalMathML, options);
|
|
307
357
|
appendCollectedText(acc, collected);
|
|
@@ -316,11 +366,11 @@ const collectMathAware = (root, options) => {
|
|
|
316
366
|
containsMathMarkup = true;
|
|
317
367
|
return;
|
|
318
368
|
}
|
|
319
|
-
for (const child of
|
|
369
|
+
for (const child of flatTreeChildNodes(element)) {
|
|
320
370
|
processNode(child);
|
|
321
371
|
}
|
|
322
372
|
};
|
|
323
|
-
for (const child of
|
|
373
|
+
for (const child of flatTreeChildNodes(root)) {
|
|
324
374
|
processNode(child);
|
|
325
375
|
}
|
|
326
376
|
trimTrailingWhitespace(acc);
|
|
@@ -336,7 +386,13 @@ const collectMathAware = (root, options) => {
|
|
|
336
386
|
containsMathMarkup,
|
|
337
387
|
};
|
|
338
388
|
};
|
|
339
|
-
|
|
389
|
+
/**
|
|
390
|
+
* With `range`, only the part of `element` the range selects: text it selects
|
|
391
|
+
* and equations it holds whole.
|
|
392
|
+
*/
|
|
393
|
+
export const collectMathAwareTextAndMap = (element, options, range) => {
|
|
394
|
+
if (range)
|
|
395
|
+
return collectMathAware(element, options, range);
|
|
340
396
|
if (!hasMathCandidate(element)) {
|
|
341
397
|
const { text, map } = collectVisibleTextAndMap(element, options);
|
|
342
398
|
return {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ResolveMathSpeechOptions, type SREMathSpeechOptions } from "./math-speech.js";
|
|
2
|
+
export interface MathControlNamesOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The host's math speech options. Names take only `engineOptions`, where
|
|
5
|
+
* SRE's locale tables load from: their domain and style are fixed.
|
|
6
|
+
*/
|
|
7
|
+
getMathSpeech?: () => SREMathSpeechOptions | undefined;
|
|
8
|
+
/** The host's `content-language`, read when an expression is named. */
|
|
9
|
+
getContentLanguage?: () => string | null | undefined;
|
|
10
|
+
loadSre?: ResolveMathSpeechOptions["loadSre"];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Labels each typeset expression inside a control under `root`, open shadow
|
|
14
|
+
* roots included, with SRE's speech, as MathJax adds it and again whenever
|
|
15
|
+
* MathJax replaces it. An `aria-label` on the role-less `mjx-container` puts
|
|
16
|
+
* that speech in the name of the control around it, among the control's other
|
|
17
|
+
* text. English reads ClearSpeak, other languages MathSpeak, in the language of
|
|
18
|
+
* the content. The label replaces the elements' own; it stays when SRE cannot
|
|
19
|
+
* speak the math. Returns the function that stops observing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function observeMathControlNames(root: Element, options?: MathControlNamesOptions): () => void;
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { resolveContentLanguage } from "../../runtime/content-language.js";
|
|
2
|
+
import { flatQuerySelector, flatTreeClosest, walkFlatTree, } from "./flat-tree.js";
|
|
3
|
+
import { resolveMathSpeechFromChunks, } from "./math-speech.js";
|
|
4
|
+
import { canonicalizeMathML } from "./mathml-sanitization.js";
|
|
5
|
+
/**
|
|
6
|
+
* Elements that take their accessible name from their content. Browsers leave
|
|
7
|
+
* MathML out of that name, so a choice labelled with math is named from its
|
|
8
|
+
* bare symbols ("4 12" for 4 over 12). The elements' renderer labels the math
|
|
9
|
+
* in the same set of controls with its own English speech.
|
|
10
|
+
*/
|
|
11
|
+
const CONTROLS = [
|
|
12
|
+
"a[href]",
|
|
13
|
+
"button",
|
|
14
|
+
"label",
|
|
15
|
+
"summary",
|
|
16
|
+
...[
|
|
17
|
+
"button",
|
|
18
|
+
"checkbox",
|
|
19
|
+
"combobox",
|
|
20
|
+
"link",
|
|
21
|
+
"menuitem",
|
|
22
|
+
"menuitemcheckbox",
|
|
23
|
+
"menuitemradio",
|
|
24
|
+
"option",
|
|
25
|
+
"radio",
|
|
26
|
+
"switch",
|
|
27
|
+
"tab",
|
|
28
|
+
"treeitem",
|
|
29
|
+
].map((role) => `[role="${role}"]`),
|
|
30
|
+
].join(", ");
|
|
31
|
+
const TOKENS = new Set(["mi", "mn", "mtext"]);
|
|
32
|
+
const isToken = (element) => TOKENS.has(element.localName) ||
|
|
33
|
+
(element.localName === "mrow" &&
|
|
34
|
+
element.children.length === 1 &&
|
|
35
|
+
TOKENS.has(element.children[0].localName));
|
|
36
|
+
/**
|
|
37
|
+
* ClearSpeak's fraction preference for a name. "4 over 12" keeps the numerals
|
|
38
|
+
* as written, where the default says "four fourths" for 4 over 4 (WCAG 2.5.3:
|
|
39
|
+
* a name does not put words in place of the formula). "Over" between longer
|
|
40
|
+
* parts is ambiguous, so an expression holding such a fraction reads every
|
|
41
|
+
* fraction by numerator and denominator.
|
|
42
|
+
*/
|
|
43
|
+
const fractionStyle = (math) => Array.from(math.getElementsByTagName("mfrac")).every((fraction) => Array.from(fraction.children).every(isToken))
|
|
44
|
+
? "Fraction_Over"
|
|
45
|
+
: "Fraction_General";
|
|
46
|
+
const isEnglish = (language) => !language || language.toLowerCase().split("-")[0] === "en";
|
|
47
|
+
/**
|
|
48
|
+
* Labels each typeset expression inside a control under `root`, open shadow
|
|
49
|
+
* roots included, with SRE's speech, as MathJax adds it and again whenever
|
|
50
|
+
* MathJax replaces it. An `aria-label` on the role-less `mjx-container` puts
|
|
51
|
+
* that speech in the name of the control around it, among the control's other
|
|
52
|
+
* text. English reads ClearSpeak, other languages MathSpeak, in the language of
|
|
53
|
+
* the content. The label replaces the elements' own; it stays when SRE cannot
|
|
54
|
+
* speak the math. Returns the function that stops observing.
|
|
55
|
+
*/
|
|
56
|
+
export function observeMathControlNames(root, options = {}) {
|
|
57
|
+
if (typeof MutationObserver === "undefined")
|
|
58
|
+
return () => { };
|
|
59
|
+
const speech = new Map();
|
|
60
|
+
let stopped = false;
|
|
61
|
+
const speak = async (mathml, math, language) => {
|
|
62
|
+
const result = await resolveMathSpeechFromChunks([{ type: "math", mathml, fallbackText: "" }], {
|
|
63
|
+
language,
|
|
64
|
+
loadSre: options.loadSre,
|
|
65
|
+
mathSpeech: {
|
|
66
|
+
...(isEnglish(language) ? { style: fractionStyle(math) } : {}),
|
|
67
|
+
engineOptions: options.getMathSpeech?.()?.engineOptions,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return result.usedMathSpeech ? result.speechText : null;
|
|
71
|
+
};
|
|
72
|
+
const name = (container) => {
|
|
73
|
+
if (!flatTreeClosest(container, CONTROLS))
|
|
74
|
+
return;
|
|
75
|
+
const math = flatQuerySelector(container, "math");
|
|
76
|
+
const mathml = math && canonicalizeMathML(math.outerHTML);
|
|
77
|
+
if (!math || !mathml)
|
|
78
|
+
return;
|
|
79
|
+
const language = resolveContentLanguage(container, {
|
|
80
|
+
contentLanguage: options.getContentLanguage?.(),
|
|
81
|
+
});
|
|
82
|
+
const key = `${language ?? ""}\u0000${mathml}`;
|
|
83
|
+
let pending = speech.get(key);
|
|
84
|
+
if (!pending) {
|
|
85
|
+
pending = speak(mathml, math, language);
|
|
86
|
+
speech.set(key, pending);
|
|
87
|
+
}
|
|
88
|
+
void pending.then((label) => {
|
|
89
|
+
if (stopped || !label || !container.isConnected)
|
|
90
|
+
return;
|
|
91
|
+
if (container.getAttribute("aria-label") !== label) {
|
|
92
|
+
container.setAttribute("aria-label", label);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
};
|
|
96
|
+
// A shadow root is a tree of its own, which an observer on `root` does not
|
|
97
|
+
// see into, so each one found is observed as well. One attached after its
|
|
98
|
+
// host was visited is not found until something is added around the host.
|
|
99
|
+
const observed = new WeakSet();
|
|
100
|
+
const observeTree = (tree) => {
|
|
101
|
+
if (observed.has(tree))
|
|
102
|
+
return;
|
|
103
|
+
observed.add(tree);
|
|
104
|
+
observer.observe(tree, { childList: true, subtree: true });
|
|
105
|
+
};
|
|
106
|
+
const visit = (node) => {
|
|
107
|
+
if (node.nodeType !== 1)
|
|
108
|
+
return;
|
|
109
|
+
const element = node;
|
|
110
|
+
const container = flatTreeClosest(element, "mjx-container");
|
|
111
|
+
if (container) {
|
|
112
|
+
name(container);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (element.shadowRoot)
|
|
116
|
+
observeTree(element.shadowRoot);
|
|
117
|
+
walkFlatTree(element, (descendant) => {
|
|
118
|
+
if (descendant.nodeType !== 1)
|
|
119
|
+
return "skip";
|
|
120
|
+
const found = descendant;
|
|
121
|
+
if (found.localName === "mjx-container") {
|
|
122
|
+
name(found);
|
|
123
|
+
return "skip";
|
|
124
|
+
}
|
|
125
|
+
if (found.shadowRoot)
|
|
126
|
+
observeTree(found.shadowRoot);
|
|
127
|
+
return undefined;
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const observer = new MutationObserver((records) => {
|
|
131
|
+
for (const record of records) {
|
|
132
|
+
for (const node of record.addedNodes)
|
|
133
|
+
visit(node);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
observeTree(root);
|
|
137
|
+
visit(root);
|
|
138
|
+
return () => {
|
|
139
|
+
stopped = true;
|
|
140
|
+
observer.disconnect();
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -25,7 +25,11 @@ export interface SREMathSpeechOptions {
|
|
|
25
25
|
domain?: string;
|
|
26
26
|
/** Raw SRE style/preference string. ClearSpeak preferences are ":"-joined. */
|
|
27
27
|
style?: string;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Extra SRE setupEngine options for hosts that need a newly exposed SRE knob.
|
|
30
|
+
* In a browser, SRE's locale tables load from the host's bundle unless `json`
|
|
31
|
+
* (a base URL or path) or `custom` (a loader) names another source.
|
|
32
|
+
*/
|
|
29
33
|
engineOptions?: Record<string, unknown>;
|
|
30
34
|
}
|
|
31
35
|
export interface ResolvedMathSpeech {
|
|
@@ -39,6 +43,17 @@ export interface ResolvedMathSpeech {
|
|
|
39
43
|
*/
|
|
40
44
|
ssml?: string;
|
|
41
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Loads the speech-rule-engine once per page. A failed load is dropped, so the
|
|
48
|
+
* next call tries again; meanwhile math reads as its visible text. The first
|
|
49
|
+
* failure warns, later ones log at debug.
|
|
50
|
+
*/
|
|
51
|
+
export declare const createSreLoader: (importEngine: () => Promise<SpeechRuleEngineApi>) => ((engineOptions?: Record<string, unknown>) => Promise<SpeechRuleEngineApi>);
|
|
52
|
+
/**
|
|
53
|
+
* One speech-rule-engine load for a whole read: its equations share the
|
|
54
|
+
* attempt, so a failed load costs a read one attempt.
|
|
55
|
+
*/
|
|
56
|
+
export declare const createReadSreLoad: (mathSpeech?: SREMathSpeechOptions) => (() => Promise<SpeechRuleEngineApi>);
|
|
42
57
|
export declare const normalizeSREMathSpeechOptions: (options: unknown) => SREMathSpeechOptions | undefined;
|
|
43
58
|
export declare const resolveMathSpeechFromChunks: (chunks: MathAwareSpeechChunk[], options?: ResolveMathSpeechOptions) => Promise<ResolvedMathSpeech>;
|
|
44
59
|
export {};
|
|
@@ -1,19 +1,53 @@
|
|
|
1
|
+
import { createPieLogger, isTtsDebugEnabled, } from "@pie-players/pie-players-shared/pie";
|
|
2
|
+
import { setSreStartupLocaleSource, sreLocaleSource } from "./sre-locales.js";
|
|
1
3
|
import { normalizeTextForSpeech } from "./text-processing.js";
|
|
2
|
-
|
|
4
|
+
// Debug lines need PIE_TTS_DEBUG=1 or globalThis.__PIE_TTS_DEBUG__ = true.
|
|
5
|
+
const logger = createPieLogger("tts-math-speech", isTtsDebugEnabled);
|
|
3
6
|
let sreOperationQueue = Promise.resolve();
|
|
4
7
|
const normalizeLocale = (language) => (language || "en").split("-")[0].toLowerCase() || "en";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Loads the speech-rule-engine once per page. A failed load is dropped, so the
|
|
10
|
+
* next call tries again; meanwhile math reads as its visible text. The first
|
|
11
|
+
* failure warns, later ones log at debug.
|
|
12
|
+
*/
|
|
13
|
+
export const createSreLoader = (importEngine) => {
|
|
14
|
+
let load = null;
|
|
15
|
+
let warned = false;
|
|
16
|
+
return (engineOptions) => {
|
|
17
|
+
if (!load) {
|
|
18
|
+
setSreStartupLocaleSource(engineOptions);
|
|
19
|
+
const attempt = importEngine();
|
|
20
|
+
load = attempt;
|
|
21
|
+
attempt.catch((error) => {
|
|
22
|
+
if (load === attempt)
|
|
23
|
+
load = null;
|
|
24
|
+
if (warned) {
|
|
25
|
+
logger.debug("speech-rule-engine failed to load again", error);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
warned = true;
|
|
29
|
+
logger.warn("speech-rule-engine failed to load; math reads as its visible text until a later read loads it.", error);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return load;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
const defaultLoadSre = createSreLoader(() => import("./sre-engine.js").then(({ default: candidate }) => {
|
|
36
|
+
if (!candidate || typeof candidate.toSpeech !== "function") {
|
|
37
|
+
throw new Error("speech-rule-engine did not expose toSpeech");
|
|
15
38
|
}
|
|
16
|
-
return
|
|
39
|
+
return candidate;
|
|
40
|
+
}));
|
|
41
|
+
/**
|
|
42
|
+
* One speech-rule-engine load for a whole read: its equations share the
|
|
43
|
+
* attempt, so a failed load costs a read one attempt.
|
|
44
|
+
*/
|
|
45
|
+
export const createReadSreLoad = (mathSpeech) => {
|
|
46
|
+
let attempt = null;
|
|
47
|
+
return () => {
|
|
48
|
+
attempt ??= defaultLoadSre(normalizeSREMathSpeechOptions(mathSpeech)?.engineOptions);
|
|
49
|
+
return attempt;
|
|
50
|
+
};
|
|
17
51
|
};
|
|
18
52
|
// SRE's ClearSpeak rule set is English-only; MathSpeak is localized to many more
|
|
19
53
|
// locales. Use ClearSpeak for English (highest-quality math prose) and MathSpeak
|
|
@@ -41,13 +75,15 @@ export const normalizeSREMathSpeechOptions = (options) => {
|
|
|
41
75
|
const setupSre = async (sre, language, markup, mathSpeech) => {
|
|
42
76
|
const locale = normalizeLocale(language);
|
|
43
77
|
const normalizedMathSpeech = normalizeSREMathSpeechOptions(mathSpeech);
|
|
78
|
+
const engineOptions = normalizedMathSpeech?.engineOptions || {};
|
|
44
79
|
await sre.setupEngine?.({
|
|
45
|
-
...(
|
|
80
|
+
...sreLocaleSource(engineOptions),
|
|
81
|
+
...engineOptions,
|
|
46
82
|
locale,
|
|
47
83
|
domain: normalizedMathSpeech?.domain || domainForLocale(locale),
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
84
|
+
// SRE keeps a style it was given until it is given another, so a call
|
|
85
|
+
// without one resets it: math control names set ClearSpeak preferences.
|
|
86
|
+
style: normalizedMathSpeech?.style || "default",
|
|
51
87
|
modality: "speech",
|
|
52
88
|
markup,
|
|
53
89
|
});
|
|
@@ -78,7 +114,7 @@ const generateMathSsml = async (sre, mathml, language, mathSpeech) => {
|
|
|
78
114
|
return ssml.includes("<speak") ? ssml : null;
|
|
79
115
|
}
|
|
80
116
|
catch (error) {
|
|
81
|
-
|
|
117
|
+
logger.debug("math SSML generation failed; using plain", {
|
|
82
118
|
message: error instanceof Error ? error.message : String(error),
|
|
83
119
|
});
|
|
84
120
|
return null;
|
|
@@ -97,7 +133,7 @@ export const resolveMathSpeechFromChunks = async (chunks, options = {}) => {
|
|
|
97
133
|
let sre = null;
|
|
98
134
|
let usedMathSpeech = false;
|
|
99
135
|
let usedFallback = false;
|
|
100
|
-
const loadSre = options.loadSre ||
|
|
136
|
+
const loadSre = options.loadSre || createReadSreLoad(options.mathSpeech);
|
|
101
137
|
const speechParts = [];
|
|
102
138
|
for (const chunk of chunks) {
|
|
103
139
|
if (chunk.type === "text") {
|
|
@@ -119,7 +155,7 @@ export const resolveMathSpeechFromChunks = async (chunks, options = {}) => {
|
|
|
119
155
|
}
|
|
120
156
|
catch (error) {
|
|
121
157
|
usedFallback = true;
|
|
122
|
-
|
|
158
|
+
logger.debug("math speech generation failed; using visible fallback", {
|
|
123
159
|
message: error instanceof Error ? error.message : String(error),
|
|
124
160
|
});
|
|
125
161
|
speechParts.push(chunk.fallbackText);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TTSConfig, TTSProviderOptions } from "@pie-players/pie-tts";
|
|
2
|
+
import type { SREMathSpeechOptions } from "./math-speech.js";
|
|
3
|
+
import type { BoundarySpacingMode } from "./text-processing.js";
|
|
4
|
+
/**
|
|
5
|
+
* The provider options the toolkit reads, on top of the ones every provider
|
|
6
|
+
* receives. Hosts configure them as free-form options, so readers still check
|
|
7
|
+
* each value at runtime.
|
|
8
|
+
*/
|
|
9
|
+
export interface ToolkitTTSProviderOptions extends TTSProviderOptions {
|
|
10
|
+
/** Highlight words or sentences while reading. Defaults by provider capability. */
|
|
11
|
+
highlightMode?: "word" | "sentence";
|
|
12
|
+
/** Locale for text processing; the toolkit sets it per speak. */
|
|
13
|
+
locale?: string;
|
|
14
|
+
/** Speech-rule-engine options for math speech. */
|
|
15
|
+
mathSpeech?: SREMathSpeechOptions;
|
|
16
|
+
/** Sentence and word segmentation of the browser provider. */
|
|
17
|
+
segmenter?: {
|
|
18
|
+
/** `regexOnly` segments without `Intl.Segmenter`. */
|
|
19
|
+
mode?: "regexOnly";
|
|
20
|
+
locale?: string;
|
|
21
|
+
};
|
|
22
|
+
/** Pauses at structural boundaries: `baseMs` scaled by a strength unit, clamped. */
|
|
23
|
+
structuralPauses?: {
|
|
24
|
+
baseMs?: number | string;
|
|
25
|
+
units?: {
|
|
26
|
+
minor?: number | string;
|
|
27
|
+
section?: number | string;
|
|
28
|
+
major?: number | string;
|
|
29
|
+
};
|
|
30
|
+
minMs?: number | string;
|
|
31
|
+
maxMs?: number | string;
|
|
32
|
+
};
|
|
33
|
+
textNormalization?: {
|
|
34
|
+
boundarySpacingMode?: BoundarySpacingMode;
|
|
35
|
+
locale?: string;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** A provider configuration as the toolkit reads it. */
|
|
39
|
+
export interface ToolkitTTSConfig extends TTSConfig {
|
|
40
|
+
providerOptions?: ToolkitTTSProviderOptions;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as speechRuleEngine from "speech-rule-engine";
|
|
2
|
+
import { sreStartupLocaleSource } from "./sre-locales.js";
|
|
3
|
+
const sre = speechRuleEngine.default ?? speechRuleEngine;
|
|
4
|
+
// SRE starts itself up while this module's import of it evaluates, then loads
|
|
5
|
+
// `base` and `en` a few microtasks later from whatever locale source is set by
|
|
6
|
+
// then. In a bundle this call runs synchronously after that evaluation, so it
|
|
7
|
+
// lands first; configuring SRE after awaiting an `import()` of it lands after
|
|
8
|
+
// those loads. Bun drains microtasks between evaluating a CommonJS module and
|
|
9
|
+
// its importer, so there SRE reads `base` from its own package directory.
|
|
10
|
+
void sre.setupEngine(sreStartupLocaleSource());
|
|
11
|
+
export default sre;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `setupEngine` options naming where SRE reads locale tables from: the
|
|
3
|
+
* host's `json` (a base URL or path) or `custom` (a loader) when its
|
|
4
|
+
* `engineOptions` set either, otherwise the packaged tables wherever SRE would
|
|
5
|
+
* fetch them. SRE prefers `custom` over `json`, so the packaged loader is left
|
|
6
|
+
* out whenever the host names a source.
|
|
7
|
+
*/
|
|
8
|
+
export declare const sreLocaleSource: (engineOptions?: Record<string, unknown>) => Record<string, unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Sets the source SRE loads its start-up locales from; see `./sre-engine.ts`.
|
|
11
|
+
* Only a call made before that module first loads has any effect.
|
|
12
|
+
*/
|
|
13
|
+
export declare const setSreStartupLocaleSource: (engineOptions?: Record<string, unknown>) => void;
|
|
14
|
+
export declare const sreStartupLocaleSource: () => Record<string, unknown>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SRE loads each locale's rule tables at runtime, from a public CDN unless it is
|
|
2
|
+
// handed a source. An import per table lets the host's bundler emit each as a
|
|
3
|
+
// lazy chunk served from the host's own origin, but a build that inlines every
|
|
4
|
+
// `import()` into one file carries every table listed here, at 0.2 to 0.4 MB
|
|
5
|
+
// each. So only `base`, which every locale builds on, and the `en` and `es`
|
|
6
|
+
// locales are packaged; any other locale needs a source from the host's
|
|
7
|
+
// `engineOptions`. No `with { type: "json" }`: bundlers infer JSON from the
|
|
8
|
+
// extension, while Vite's dev server and Bun's bundler turn a table into
|
|
9
|
+
// JavaScript and keep the attribute, which a browser then rejects.
|
|
10
|
+
const SRE_LOCALE_TABLES = {
|
|
11
|
+
base: () => import("speech-rule-engine/lib/mathmaps/base.json"),
|
|
12
|
+
en: () => import("speech-rule-engine/lib/mathmaps/en.json"),
|
|
13
|
+
es: () => import("speech-rule-engine/lib/mathmaps/es.json"),
|
|
14
|
+
};
|
|
15
|
+
// SRE's `custom` loader contract: called with a locale name, resolving to that
|
|
16
|
+
// locale's table as a JSON string or an already-parsed object. SRE falls back
|
|
17
|
+
// to English when a table fails to load.
|
|
18
|
+
const loadPackagedSreLocale = async (locale) => {
|
|
19
|
+
if (!Object.hasOwn(SRE_LOCALE_TABLES, locale)) {
|
|
20
|
+
throw new Error(`no speech-rule-engine locale table is packaged for "${locale}"; name a source with mathSpeech.engineOptions.json or .custom`);
|
|
21
|
+
}
|
|
22
|
+
return (await SRE_LOCALE_TABLES[locale]()).default;
|
|
23
|
+
};
|
|
24
|
+
// SRE's own test for fetching tables over HTTP (`common/system_external.js`).
|
|
25
|
+
// Elsewhere it reads them from its package directory, which Node's ESM loader
|
|
26
|
+
// could not replace with the imports above: it requires the attribute.
|
|
27
|
+
const sreFetchesTables = () => (typeof window !== "undefined" && typeof window.document !== "undefined") ||
|
|
28
|
+
typeof globalThis
|
|
29
|
+
.DedicatedWorkerGlobalScope !== "undefined";
|
|
30
|
+
/**
|
|
31
|
+
* The `setupEngine` options naming where SRE reads locale tables from: the
|
|
32
|
+
* host's `json` (a base URL or path) or `custom` (a loader) when its
|
|
33
|
+
* `engineOptions` set either, otherwise the packaged tables wherever SRE would
|
|
34
|
+
* fetch them. SRE prefers `custom` over `json`, so the packaged loader is left
|
|
35
|
+
* out whenever the host names a source.
|
|
36
|
+
*/
|
|
37
|
+
export const sreLocaleSource = (engineOptions = {}) => {
|
|
38
|
+
const { json, custom } = engineOptions;
|
|
39
|
+
if (json || custom) {
|
|
40
|
+
return { ...(json ? { json } : {}), ...(custom ? { custom } : {}) };
|
|
41
|
+
}
|
|
42
|
+
return sreFetchesTables() ? { custom: loadPackagedSreLocale } : {};
|
|
43
|
+
};
|
|
44
|
+
// Resolved on first read: a call at module scope would keep the table imports
|
|
45
|
+
// in every bundle that includes this module, math speech used or not.
|
|
46
|
+
let startupLocaleSource;
|
|
47
|
+
/**
|
|
48
|
+
* Sets the source SRE loads its start-up locales from; see `./sre-engine.ts`.
|
|
49
|
+
* Only a call made before that module first loads has any effect.
|
|
50
|
+
*/
|
|
51
|
+
export const setSreStartupLocaleSource = (engineOptions) => {
|
|
52
|
+
startupLocaleSource = sreLocaleSource(engineOptions);
|
|
53
|
+
};
|
|
54
|
+
export const sreStartupLocaleSource = () => {
|
|
55
|
+
startupLocaleSource ??= sreLocaleSource();
|
|
56
|
+
return startupLocaleSource;
|
|
57
|
+
};
|