@pie-players/pie-assessment-toolkit 0.3.44 → 0.3.46

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.
Files changed (191) hide show
  1. package/README.md +59 -70
  2. package/dist/components/ItemToolBar.custom-element.js +776 -357
  3. package/dist/components/PieAssessmentToolkit.custom-element.js +19501 -3591
  4. package/dist/components/SectionToolBar.custom-element.js +1058 -540
  5. package/dist/index.d.ts +3 -6
  6. package/dist/index.js +1 -3
  7. package/dist/index.js.map +1 -1
  8. package/dist/policy/core/ToolPolicyEngine.d.ts +1 -2
  9. package/dist/policy/core/ToolPolicyEngine.js +1 -2
  10. package/dist/policy/core/ToolPolicyEngine.js.map +1 -1
  11. package/dist/policy/core/pnp-policy-inputs.d.ts +2 -3
  12. package/dist/policy/core/pnp-policy-inputs.js +2 -3
  13. package/dist/policy/core/pnp-policy-inputs.js.map +1 -1
  14. package/dist/policy/core/provenance.d.ts +7 -34
  15. package/dist/policy/core/provenance.js +4 -21
  16. package/dist/policy/core/provenance.js.map +1 -1
  17. package/dist/policy/sources/PnpPolicySource.d.ts +6 -19
  18. package/dist/policy/sources/PnpPolicySource.js +6 -19
  19. package/dist/policy/sources/PnpPolicySource.js.map +1 -1
  20. package/dist/runtime/SectionRuntimeEngine.d.ts +13 -28
  21. package/dist/runtime/SectionRuntimeEngine.js +13 -27
  22. package/dist/runtime/SectionRuntimeEngine.js.map +1 -1
  23. package/dist/runtime/adapter/SectionEngineAdapter.d.ts +2 -2
  24. package/dist/runtime/adapter/SectionEngineAdapter.js +2 -2
  25. package/dist/runtime/adapter/SectionEngineAdapter.js.map +1 -1
  26. package/dist/runtime/adapter/coordinator-bridge.d.ts +1 -1
  27. package/dist/runtime/adapter/coordinator-bridge.js +1 -1
  28. package/dist/runtime/adapter/coordinator-bridge.js.map +1 -1
  29. package/dist/runtime/adapter/dom-event-bridge.d.ts +1 -1
  30. package/dist/runtime/adapter/dom-event-bridge.js +1 -1
  31. package/dist/runtime/adapter/dom-event-bridge.js.map +1 -1
  32. package/dist/runtime/catalog-registration.d.ts +12 -0
  33. package/dist/runtime/catalog-registration.js +54 -0
  34. package/dist/runtime/catalog-registration.js.map +1 -0
  35. package/dist/runtime/core/engine-output.d.ts +2 -2
  36. package/dist/runtime/core/engine-output.js +2 -2
  37. package/dist/runtime/core/engine-output.js.map +1 -1
  38. package/dist/runtime/core/engine-readiness.d.ts +2 -2
  39. package/dist/runtime/core/engine-readiness.js.map +1 -1
  40. package/dist/runtime/core/engine-resolver.d.ts +13 -41
  41. package/dist/runtime/core/engine-resolver.js +19 -76
  42. package/dist/runtime/core/engine-resolver.js.map +1 -1
  43. package/dist/runtime/core/engine-transition.d.ts +1 -1
  44. package/dist/runtime/core/engine-transition.js +1 -1
  45. package/dist/runtime/core/engine-transition.js.map +1 -1
  46. package/dist/runtime/engine.d.ts +1 -1
  47. package/dist/runtime/engine.js.map +1 -1
  48. package/dist/runtime/section-runtime-engine-host-context.d.ts +22 -15
  49. package/dist/runtime/section-runtime-engine-host-context.js +10 -10
  50. package/dist/runtime/section-runtime-engine-host-context.js.map +1 -1
  51. package/dist/runtime/stage-emit-gate.d.ts +4 -4
  52. package/dist/runtime/stage-emit-gate.js +3 -3
  53. package/dist/runtime/stage-emit-gate.js.map +1 -1
  54. package/dist/services/AccessibilityCatalogResolver.d.ts +23 -0
  55. package/dist/services/AccessibilityCatalogResolver.js +200 -28
  56. package/dist/services/AccessibilityCatalogResolver.js.map +1 -1
  57. package/dist/services/HighlightCoordinator.d.ts +25 -0
  58. package/dist/services/HighlightCoordinator.js +125 -0
  59. package/dist/services/HighlightCoordinator.js.map +1 -1
  60. package/dist/services/SSMLExtractor.d.ts +16 -1
  61. package/dist/services/SSMLExtractor.js +86 -7
  62. package/dist/services/SSMLExtractor.js.map +1 -1
  63. package/dist/services/TTSService.d.ts +30 -4
  64. package/dist/services/TTSService.js +652 -159
  65. package/dist/services/TTSService.js.map +1 -1
  66. package/dist/services/ToolRegistry.d.ts +11 -2
  67. package/dist/services/ToolRegistry.js +1 -1
  68. package/dist/services/ToolRegistry.js.map +1 -1
  69. package/dist/services/ToolkitCoordinator.d.ts +16 -73
  70. package/dist/services/ToolkitCoordinator.js +34 -113
  71. package/dist/services/ToolkitCoordinator.js.map +1 -1
  72. package/dist/services/interfaces.d.ts +20 -2
  73. package/dist/services/interfaces.js.map +1 -1
  74. package/dist/services/tool-config-validation.d.ts +1 -1
  75. package/dist/services/tool-config-validation.js +7 -7
  76. package/dist/services/tool-config-validation.js.map +1 -1
  77. package/dist/services/tool-instance-id.js +1 -1
  78. package/dist/services/tool-instance-id.js.map +1 -1
  79. package/dist/services/tool-providers/TTSToolProvider.js +3 -3
  80. package/dist/services/tool-providers/TTSToolProvider.js.map +1 -1
  81. package/dist/services/tools-config-normalizer.d.ts +13 -2
  82. package/dist/services/tools-config-normalizer.js +3 -8
  83. package/dist/services/tools-config-normalizer.js.map +1 -1
  84. package/dist/services/tts/browser-provider.js +2 -0
  85. package/dist/services/tts/browser-provider.js.map +1 -1
  86. package/dist/services/tts/catalog-span-alignment.d.ts +39 -0
  87. package/dist/services/tts/catalog-span-alignment.js +300 -0
  88. package/dist/services/tts/catalog-span-alignment.js.map +1 -0
  89. package/dist/services/tts/generated-speech/assemble-plan.d.ts +33 -0
  90. package/dist/services/tts/generated-speech/assemble-plan.js +111 -0
  91. package/dist/services/tts/generated-speech/assemble-plan.js.map +1 -0
  92. package/dist/services/tts/generated-speech/dom/build-dom-plan.d.ts +25 -0
  93. package/dist/services/tts/generated-speech/dom/build-dom-plan.js +66 -0
  94. package/dist/services/tts/generated-speech/dom/build-dom-plan.js.map +1 -0
  95. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.d.ts +19 -0
  96. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js +71 -0
  97. package/dist/services/tts/generated-speech/dom/to-chunk-inputs.js.map +1 -0
  98. package/dist/services/tts/generated-speech/dom/types.d.ts +37 -0
  99. package/dist/services/tts/generated-speech/dom/types.js +2 -0
  100. package/dist/services/tts/generated-speech/dom/types.js.map +1 -0
  101. package/dist/services/tts/generated-speech/index.d.ts +17 -0
  102. package/dist/services/tts/generated-speech/index.js +17 -0
  103. package/dist/services/tts/generated-speech/index.js.map +1 -0
  104. package/dist/services/tts/generated-speech/math-speech-cache.d.ts +15 -0
  105. package/dist/services/tts/generated-speech/math-speech-cache.js +78 -0
  106. package/dist/services/tts/generated-speech/math-speech-cache.js.map +1 -0
  107. package/dist/services/tts/generated-speech/types.d.ts +104 -0
  108. package/dist/services/tts/generated-speech/types.js +18 -0
  109. package/dist/services/tts/generated-speech/types.js.map +1 -0
  110. package/dist/services/tts/highlight-pipeline/boundary-normalizer.d.ts +2 -0
  111. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js +106 -0
  112. package/dist/services/tts/highlight-pipeline/boundary-normalizer.js.map +1 -0
  113. package/dist/services/tts/highlight-pipeline/create-highlight-plan.d.ts +6 -0
  114. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js +217 -0
  115. package/dist/services/tts/highlight-pipeline/create-highlight-plan.js.map +1 -0
  116. package/dist/services/tts/highlight-pipeline/highlight-policy.d.ts +2 -0
  117. package/dist/services/tts/highlight-pipeline/highlight-policy.js +32 -0
  118. package/dist/services/tts/highlight-pipeline/highlight-policy.js.map +1 -0
  119. package/dist/services/tts/highlight-pipeline/index.d.ts +9 -0
  120. package/dist/services/tts/highlight-pipeline/index.js +9 -0
  121. package/dist/services/tts/highlight-pipeline/index.js.map +1 -0
  122. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.d.ts +3 -0
  123. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js +32 -0
  124. package/dist/services/tts/highlight-pipeline/math-semantic-resolver.js.map +1 -0
  125. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.d.ts +29 -0
  126. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js +44 -0
  127. package/dist/services/tts/highlight-pipeline/normalize-speech-chunks.js.map +1 -0
  128. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.d.ts +2 -0
  129. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js +20 -0
  130. package/dist/services/tts/highlight-pipeline/prose-boundary-resolver.js.map +1 -0
  131. package/dist/services/tts/highlight-pipeline/readable-region.d.ts +1 -0
  132. package/dist/services/tts/highlight-pipeline/readable-region.js +19 -0
  133. package/dist/services/tts/highlight-pipeline/readable-region.js.map +1 -0
  134. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.d.ts +6 -0
  135. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js +198 -0
  136. package/dist/services/tts/highlight-pipeline/rendered-math-target-resolver.js.map +1 -0
  137. package/dist/services/tts/highlight-pipeline/types.d.ts +72 -0
  138. package/dist/services/tts/highlight-pipeline/types.js +2 -0
  139. package/dist/services/tts/highlight-pipeline/types.js.map +1 -0
  140. package/dist/services/tts/highlight-pipeline/visible-map-range.d.ts +2 -0
  141. package/dist/services/tts/highlight-pipeline/visible-map-range.js +22 -0
  142. package/dist/services/tts/highlight-pipeline/visible-map-range.js.map +1 -0
  143. package/dist/services/tts/math-alignment/index.d.ts +30 -0
  144. package/dist/services/tts/math-alignment/index.js +66 -0
  145. package/dist/services/tts/math-alignment/index.js.map +1 -0
  146. package/dist/services/tts/math-alignment/mathml-tokenizer.d.ts +2 -0
  147. package/dist/services/tts/math-alignment/mathml-tokenizer.js +317 -0
  148. package/dist/services/tts/math-alignment/mathml-tokenizer.js.map +1 -0
  149. package/dist/services/tts/math-alignment/range-resolver.d.ts +10 -0
  150. package/dist/services/tts/math-alignment/range-resolver.js +13 -0
  151. package/dist/services/tts/math-alignment/range-resolver.js.map +1 -0
  152. package/dist/services/tts/math-alignment/sequence-aligner.d.ts +7 -0
  153. package/dist/services/tts/math-alignment/sequence-aligner.js +119 -0
  154. package/dist/services/tts/math-alignment/sequence-aligner.js.map +1 -0
  155. package/dist/services/tts/math-alignment/speech-tokenizer.d.ts +31 -0
  156. package/dist/services/tts/math-alignment/speech-tokenizer.js +143 -0
  157. package/dist/services/tts/math-alignment/speech-tokenizer.js.map +1 -0
  158. package/dist/services/tts/math-alignment/types.d.ts +58 -0
  159. package/dist/services/tts/math-alignment/types.js +2 -0
  160. package/dist/services/tts/math-alignment/types.js.map +1 -0
  161. package/dist/services/tts/math-aware-text-processing.d.ts +18 -0
  162. package/dist/services/tts/math-aware-text-processing.js +348 -0
  163. package/dist/services/tts/math-aware-text-processing.js.map +1 -0
  164. package/dist/services/tts/math-speech.d.ts +44 -0
  165. package/dist/services/tts/math-speech.js +147 -0
  166. package/dist/services/tts/math-speech.js.map +1 -0
  167. package/dist/services/tts/mathml-sanitization.d.ts +1 -0
  168. package/dist/services/tts/mathml-sanitization.js +170 -0
  169. package/dist/services/tts/mathml-sanitization.js.map +1 -0
  170. package/dist/services/tts/ssml/spoken-text.d.ts +33 -0
  171. package/dist/services/tts/ssml/spoken-text.js +159 -0
  172. package/dist/services/tts/ssml/spoken-text.js.map +1 -0
  173. package/dist/services/tts/text-processing.d.ts +3 -0
  174. package/dist/services/tts/text-processing.js +30 -4
  175. package/dist/services/tts/text-processing.js.map +1 -1
  176. package/dist/services/tts-runtime-config.d.ts +17 -1
  177. package/dist/services/tts-runtime-config.js +11 -1
  178. package/dist/services/tts-runtime-config.js.map +1 -1
  179. package/dist/tools/registrations/accessibility-tools.js +0 -1
  180. package/dist/tools/registrations/accessibility-tools.js.map +1 -1
  181. package/dist/tools/registrations/calculator.js +46 -5
  182. package/dist/tools/registrations/calculator.js.map +1 -1
  183. package/dist/tools/tool-tag-map.js +0 -1
  184. package/dist/tools/tool-tag-map.js.map +1 -1
  185. package/package.json +8 -11
  186. package/dist/services/PNPToolResolver.d.ts +0 -280
  187. package/dist/services/PNPToolResolver.js +0 -514
  188. package/dist/services/PNPToolResolver.js.map +0 -1
  189. package/dist/services/pnp-provenance.d.ts +0 -166
  190. package/dist/services/pnp-provenance.js +0 -231
  191. package/dist/services/pnp-provenance.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"HighlightCoordinator.js","sourceRoot":"","sources":["../../src/services/HighlightCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * HighlightCoordinator\n *\n * Manages separate highlight layers for TTS (temporary) and annotations (persistent).\n * Uses CSS Custom Highlight API - zero DOM mutation, preserves accessibility.\n *\n * Features:\n * - Separate layers prevent TTS clearing from removing student annotations\n * - CSS Custom Highlight API (no DOM mutation)\n * - Full accessibility support (screen readers see original text)\n * - Configurable colors and styles\n * - Annotation persistence via RangeSerializer\n *\n * Part of PIE Assessment Toolkit.\n *\n * Browser Support:\n * - Chrome/Edge 105+\n * - Safari 17.2+\n * - Firefox: Behind flag (not recommended for production)\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API\n */\n\nimport type { HighlightCoordinatorApi } from \"./interfaces.js\";\nimport { RangeSerializer, type SerializedRange } from \"./RangeSerializer.js\";\n\n/**\n * Highlight types\n */\nexport enum HighlightType {\n\tTTS_WORD = \"tts-word\",\n\tTTS_SENTENCE = \"tts-sentence\",\n\tANNOTATION = \"annotation\",\n}\n\n/**\n * Highlight colors for annotations\n */\nexport enum HighlightColor {\n\tYELLOW = \"yellow\",\n\tGREEN = \"green\",\n\tBLUE = \"blue\",\n\tPINK = \"pink\",\n\tORANGE = \"orange\",\n\tUNDERLINE = \"underline\",\n}\n\n/**\n * Annotation data\n */\nexport interface Annotation {\n\tid: string;\n\trange: Range;\n\ttype: HighlightType.ANNOTATION;\n\tcolor: HighlightColor;\n\ttimestamp: number;\n}\n\n/**\n * Configuration for HighlightCoordinator\n * Currently empty but allows future extension without breaking changes\n */\nexport type HighlightCoordinatorConfig = Record<string, never>;\n\nexport class HighlightCoordinator implements HighlightCoordinatorApi {\n\tprivate config: HighlightCoordinatorConfig;\n\tprivate ttsWordHighlight: Highlight | null = null;\n\tprivate ttsSentenceHighlight: Highlight | null = null;\n\tprivate annotations = new Map<string, Annotation>();\n\t// Shared highlights per color (one Highlight object per color, contains all ranges)\n\tprivate colorHighlights = new Map<HighlightColor, Highlight>();\n\tprivate nextAnnotationId = 1;\n\tprivate supported = false;\n\tprivate rangeSerializer: RangeSerializer;\n\tprivate themeObserver: MutationObserver | null = null;\n\tprivate explicitTTSColorOverride: { color: string; opacity: number } | null = null;\n\n\tconstructor(config: HighlightCoordinatorConfig = {}) {\n\t\tthis.config = config;\n\t\tthis.rangeSerializer = new RangeSerializer();\n\n\t\t// SSR guard\n\t\tif (typeof CSS === \"undefined\" || !(\"highlights\" in CSS)) {\n\t\t\tconsole.warn(\n\t\t\t\t\"CSS Custom Highlight API not supported (SSR or unsupported browser)\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.supported = true;\n\t\tthis.initializeHighlights();\n\t\tthis.registerStyles();\n\t\tthis.applyAdaptiveTTSStyle();\n\t\tthis.setupThemeObservation();\n\t}\n\n\tprivate setupThemeObservation(): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tif (typeof MutationObserver === \"undefined\") return;\n\n\t\tconst refresh = () => this.applyAdaptiveTTSStyle();\n\t\tthis.themeObserver = new MutationObserver(refresh);\n\n\t\tthis.themeObserver.observe(document.documentElement, {\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"style\", \"data-theme\", \"data-color-scheme\", \"class\"],\n\t\t});\n\n\t\tfor (const host of document.querySelectorAll(\"pie-theme\")) {\n\t\t\tthis.themeObserver.observe(host, {\n\t\t\t\tattributes: true,\n\t\t\t\tattributeFilter: [\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"scheme\",\n\t\t\t\t\t\"provider\",\n\t\t\t\t\t\"variables\",\n\t\t\t\t\t\"style\",\n\t\t\t\t\t\"data-theme\",\n\t\t\t\t\t\"data-color-scheme\",\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate parseColor(input: string | null | undefined): [number, number, number] | null {\n\t\tif (!input) return null;\n\t\tconst value = input.trim();\n\t\tif (!value) return null;\n\n\t\tconst hexMatch = /^#([a-f\\d]{3}|[a-f\\d]{6})$/i.exec(value);\n\t\tif (hexMatch) {\n\t\t\tconst hex = hexMatch[1];\n\t\t\tif (hex.length === 3) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt(hex[0] + hex[0], 16),\n\t\t\t\t\tparseInt(hex[1] + hex[1], 16),\n\t\t\t\t\tparseInt(hex[2] + hex[2], 16),\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\tparseInt(hex.slice(0, 2), 16),\n\t\t\t\tparseInt(hex.slice(2, 4), 16),\n\t\t\t\tparseInt(hex.slice(4, 6), 16),\n\t\t\t];\n\t\t}\n\n\t\tconst rgbMatch = /^rgba?\\((.+)\\)$/i.exec(value);\n\t\tif (rgbMatch) {\n\t\t\tconst normalized = rgbMatch[1].replace(/\\//g, \",\");\n\t\t\tconst parts = normalized\n\t\t\t\t.split(/[,\\s]+/)\n\t\t\t\t.map((part) => part.trim())\n\t\t\t\t.filter(Boolean);\n\t\t\tconst r = Number(parts[0]);\n\t\t\tconst g = Number(parts[1]);\n\t\t\tconst b = Number(parts[2]);\n\t\t\tif (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {\n\t\t\t\treturn [r, g, b];\n\t\t\t}\n\t\t}\n\n\t\t// Browser parser fallback (covers formats like oklch()).\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst parserEl = document.createElement(\"span\");\n\t\t\tparserEl.style.color = value;\n\t\t\tif (typeof parserEl.style.color === \"string\" && parserEl.style.color) {\n\t\t\t\tdocument.body?.appendChild(parserEl);\n\t\t\t\tconst resolved =\n\t\t\t\t\ttypeof getComputedStyle === \"function\"\n\t\t\t\t\t\t? getComputedStyle(parserEl).color\n\t\t\t\t\t\t: \"\";\n\t\t\t\tparserEl.remove();\n\t\t\t\tconst normalizedResolved = resolved.trim();\n\t\t\t\t// Guard against recursive loops when computed style returns the same\n\t\t\t\t// unresolved function syntax (observed with certain color formats).\n\t\t\t\tif (normalizedResolved && normalizedResolved !== value) {\n\t\t\t\t\treturn this.parseColor(resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate relativeLuminance([r, g, b]: [number, number, number]): number {\n\t\tconst toLinear = (channel: number) => {\n\t\t\tconst n = channel / 255;\n\t\t\treturn n <= 0.03928 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;\n\t\t};\n\t\tconst lr = toLinear(r);\n\t\tconst lg = toLinear(g);\n\t\tconst lb = toLinear(b);\n\t\treturn 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;\n\t}\n\n\tprivate contrastRatio(\n\t\ta: [number, number, number],\n\t\tb: [number, number, number],\n\t): number {\n\t\tconst la = this.relativeLuminance(a);\n\t\tconst lb = this.relativeLuminance(b);\n\t\tconst lighter = Math.max(la, lb);\n\t\tconst darker = Math.min(la, lb);\n\t\treturn (lighter + 0.05) / (darker + 0.05);\n\t}\n\n\tprivate blend(\n\t\tfg: [number, number, number],\n\t\tbg: [number, number, number],\n\t\talpha: number,\n\t): [number, number, number] {\n\t\treturn [\n\t\t\tMath.round(fg[0] * alpha + bg[0] * (1 - alpha)),\n\t\t\tMath.round(fg[1] * alpha + bg[1] * (1 - alpha)),\n\t\t\tMath.round(fg[2] * alpha + bg[2] * (1 - alpha)),\n\t\t];\n\t}\n\n\tprivate resolveAdaptiveTTSStyle(sourceEl?: Element | null): {\n\t\twordHighlight: string;\n\t\tsentenceHighlight: string;\n\t\twordUnderline: string;\n\t\twordShadow: string;\n\t} {\n\t\tconst fallbackMissing: [number, number, number] = [255, 235, 59];\n\t\tconst fallbackText: [number, number, number] = [17, 24, 39];\n\t\tconst fallbackBackground: [number, number, number] = [255, 255, 255];\n\n\t\tconst target =\n\t\t\tsourceEl ||\n\t\t\t(typeof document !== \"undefined\" ? document.documentElement : null);\n\t\tconst computed =\n\t\t\ttarget && typeof getComputedStyle === \"function\"\n\t\t\t\t? getComputedStyle(target)\n\t\t\t\t: null;\n\n\t\tconst background =\n\t\t\tthis.parseColor(\n\t\t\t\tcomputed?.getPropertyValue(\"--pie-background\") || computed?.backgroundColor,\n\t\t\t) || fallbackBackground;\n\t\tconst text =\n\t\t\tthis.parseColor(computed?.getPropertyValue(\"--pie-text\")) || fallbackText;\n\t\tconst accent = this.explicitTTSColorOverride\n\t\t\t? this.parseColor(this.explicitTTSColorOverride.color) || fallbackMissing\n\t\t\t: this.parseColor(computed?.getPropertyValue(\"--pie-missing\")) ||\n\t\t\t\tfallbackMissing;\n\n\t\tconst opacityCandidates = this.explicitTTSColorOverride\n\t\t\t? [Math.max(0.3, Math.min(0.95, this.explicitTTSColorOverride.opacity))]\n\t\t\t: [0.8, 0.72, 0.68, 0.62, 0.56, 0.5];\n\n\t\tlet selectedOpacity = opacityCandidates[opacityCandidates.length - 1];\n\t\tlet bestScore = -Infinity;\n\n\t\tfor (const opacity of opacityCandidates) {\n\t\t\tconst blended = this.blend(accent, background, opacity);\n\t\t\tconst backgroundDelta = this.contrastRatio(blended, background);\n\t\t\tconst textContrast = this.contrastRatio(blended, text);\n\t\t\tconst score = backgroundDelta * 1.2 + textContrast * 0.8;\n\t\t\tif (backgroundDelta >= 1.25 && textContrast >= 2.4) {\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestScore = score;\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t}\n\t\t}\n\n\t\tconst sentenceOpacity = Math.max(0.24, Math.min(0.85, selectedOpacity * 0.55));\n\t\tconst underlineOpacity = Math.max(0.55, Math.min(0.95, selectedOpacity + 0.2));\n\t\tconst shadowOpacity = Math.max(0.22, Math.min(0.6, selectedOpacity * 0.45));\n\n\t\tconst underlineColor = text;\n\t\tconst underlineBlend = this.blend(underlineColor, background, underlineOpacity);\n\t\tconst underlineDelta = this.contrastRatio(underlineBlend, background);\n\t\tconst fallbackUnderline: [number, number, number] =\n\t\t\tthis.relativeLuminance(background) > 0.45 ? [0, 0, 0] : [255, 255, 255];\n\t\tconst finalUnderline = underlineDelta >= 1.35 ? underlineColor : fallbackUnderline;\n\n\t\tconst wordHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${selectedOpacity})`;\n\t\tconst sentenceHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${sentenceOpacity})`;\n\t\tconst wordUnderline = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${underlineOpacity})`;\n\t\tconst wordShadow = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${shadowOpacity})`;\n\n\t\treturn {\n\t\t\twordHighlight,\n\t\t\tsentenceHighlight,\n\t\t\twordUnderline,\n\t\t\twordShadow,\n\t\t};\n\t}\n\n\tprivate applyAdaptiveTTSStyle(sourceEl?: Element | null): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tconst vars = this.resolveAdaptiveTTSStyle(sourceEl);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-highlight\",\n\t\t\tvars.wordHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-sentence-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-line-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-underline\",\n\t\t\tvars.wordUnderline,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-shadow\",\n\t\t\tvars.wordShadow,\n\t\t);\n\t}\n\n\t/**\n\t * Initialize CSS Custom Highlights\n\t */\n\tprivate initializeHighlights(): void {\n\t\tif (!this.supported) return;\n\n\t\t// Create highlight registries for TTS\n\t\tthis.ttsWordHighlight = new Highlight();\n\t\tthis.ttsSentenceHighlight = new Highlight();\n\n\t\t// Register TTS highlights with CSS\n\t\tCSS.highlights.set(\"tts-word\", this.ttsWordHighlight);\n\t\tCSS.highlights.set(\"tts-sentence\", this.ttsSentenceHighlight);\n\n\t\t// Create shared highlights for each annotation color\n\t\tfor (const color of Object.values(HighlightColor)) {\n\t\t\tconst highlight = new Highlight();\n\t\t\tthis.colorHighlights.set(color, highlight);\n\t\t\tCSS.highlights.set(`annotation-${color}`, highlight);\n\t\t}\n\t}\n\n\t/**\n\t * Register CSS styles for highlights\n\t */\n\tprivate registerStyles(): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return; // SSR guard\n\n\t\t// Check if styles already exist\n\t\tif (document.getElementById(\"pie-highlight-styles\")) return;\n\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.id = \"pie-highlight-styles\";\n\t\tstyle.textContent = `\n /* TTS highlights - temporary */\n ::highlight(tts-word) {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n color: inherit;\n }\n\n /* tts-sentence registry id: coarse read-along band (visual line boxes in layout) */\n ::highlight(tts-sentence) {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n color: inherit;\n }\n\n /* Annotation highlights - persistent */\n ::highlight(annotation-yellow) {\n background-color: color-mix(in srgb, var(--pie-missing, #ffff00) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-green) {\n background-color: color-mix(in srgb, var(--pie-correct, #90ee90) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-blue) {\n background-color: color-mix(in srgb, var(--pie-tertiary, #add8e6) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-pink) {\n background-color: color-mix(in srgb, var(--pie-secondary-light, #ffb6c1) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-orange) {\n background-color: color-mix(in srgb, var(--pie-incorrect, #ffa500) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-underline) {\n background-color: transparent;\n text-decoration: underline 2px solid var(--pie-primary, #0066cc);\n text-underline-offset: 2px;\n color: inherit;\n }\n `;\n\t\tdocument.head.appendChild(style);\n\t}\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t *\n\t * @param textNode Text node containing the word\n\t * @param startOffset Start position in text node\n\t * @param endOffset End position in text node\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(textNode.parentElement);\n\n\t\t// Clear previous word highlight\n\t\tthis.clearTTSWord();\n\n\t\t// Create range for word\n\t\tconst range = document.createRange();\n\t\trange.setStart(textNode, startOffset);\n\t\trange.setEnd(textNode, endOffset);\n\n\t\t// Add to highlight\n\t\tthis.ttsWordHighlight.add(range);\n\t}\n\n\t/**\n\t * Highlight the coarse TTS read-along band (temporary).\n\t * Usually one Range per visual line; CSS layer `tts-sentence` for compatibility.\n\t *\n\t * @param ranges Line (or sentence) ranges to paint with `--pie-tts-line-highlight`\n\t */\n\thighlightTTSSentence(ranges: Range[]): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tconst source = ranges[0]?.startContainer;\n\t\tconst sourceElement =\n\t\t\tsource?.nodeType === Node.ELEMENT_NODE\n\t\t\t\t? (source as Element)\n\t\t\t\t: source?.parentElement;\n\t\tthis.applyAdaptiveTTSStyle(sourceElement);\n\n\t\t// Clear previous sentence highlight\n\t\tthis.clearTTSSentence();\n\n\t\t// Add all ranges\n\t\tfor (const range of ranges) {\n\t\t\tthis.ttsSentenceHighlight.add(range);\n\t\t}\n\t}\n\n\t/**\n\t * Clear TTS word highlight\n\t */\n\tclearTTSWord(): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.ttsWordHighlight.clear();\n\t}\n\n\t/**\n\t * Clear TTS sentence highlight\n\t */\n\tclearTTSSentence(): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tthis.ttsSentenceHighlight.clear();\n\t}\n\n\t/**\n\t * Clear all TTS highlights\n\t */\n\tclearTTS(): void {\n\t\tthis.clearTTSWord();\n\t\tthis.clearTTSSentence();\n\t}\n\n\t/**\n\t * Add an annotation highlight (persistent)\n\t *\n\t * @param range Text range to annotate\n\t * @param color Highlight color\n\t * @returns Annotation ID for later removal\n\t */\n\taddAnnotation(\n\t\trange: Range,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): string {\n\t\tconst id = `annotation-${this.nextAnnotationId++}`;\n\n\t\t// Clone the range to store\n\t\tconst clonedRange = range.cloneRange();\n\n\t\t// Store annotation data\n\t\tconst annotation: Annotation = {\n\t\t\tid,\n\t\t\trange: clonedRange,\n\t\t\ttype: HighlightType.ANNOTATION,\n\t\t\tcolor,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tthis.annotations.set(id, annotation);\n\n\t\t// Add the SAME range object to the shared color highlight\n\t\t// This ensures we can later delete it by reference\n\t\tconst colorHighlight = this.colorHighlights.get(color);\n\t\tif (colorHighlight) {\n\t\t\tcolorHighlight.add(clonedRange);\n\t\t}\n\n\t\treturn id;\n\t}\n\n\t/**\n\t * Remove an annotation\n\t *\n\t * @param id Annotation ID\n\t */\n\tremoveAnnotation(id: string): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) {\n\t\t\tconsole.warn(`[HighlightCoordinator] Annotation ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Removing annotation ${id} (color: ${annotation.color})`,\n\t\t);\n\n\t\t// Remove range from the shared color highlight\n\t\tconst colorHighlight = this.colorHighlights.get(annotation.color);\n\t\tif (colorHighlight) {\n\t\t\tconst deleted = colorHighlight.delete(annotation.range);\n\t\t\tconsole.log(\n\t\t\t\t`[HighlightCoordinator] Highlight.delete() returned:`,\n\t\t\t\tdeleted,\n\t\t\t);\n\t\t}\n\n\t\t// Clean up\n\t\tthis.annotations.delete(id);\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Annotation ${id} removed from map. Remaining:`,\n\t\t\tthis.annotations.size,\n\t\t);\n\t}\n\n\t/**\n\t * Remove all annotations\n\t */\n\tclearAnnotations(): void {\n\t\t// Clear all color highlights\n\t\tfor (const colorHighlight of this.colorHighlights.values()) {\n\t\t\tcolorHighlight.clear();\n\t\t}\n\n\t\t// Clear annotation data\n\t\tthis.annotations.clear();\n\t}\n\n\t/**\n\t * Get all annotations\n\t */\n\tgetAnnotations(): Annotation[] {\n\t\treturn Array.from(this.annotations.values());\n\t}\n\n\t/**\n\t * Get annotation by ID\n\t */\n\tgetAnnotation(id: string): Annotation | null {\n\t\treturn this.annotations.get(id) ?? null;\n\t}\n\n\t/**\n\t * Change annotation color\n\t *\n\t * @param id Annotation ID\n\t * @param newColor New color\n\t */\n\tchangeAnnotationColor(id: string, newColor: HighlightColor): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) return;\n\n\t\tconst oldColor = annotation.color;\n\t\tif (oldColor === newColor) return;\n\n\t\t// Remove from old color highlight\n\t\tconst oldColorHighlight = this.colorHighlights.get(oldColor);\n\t\tif (oldColorHighlight) {\n\t\t\toldColorHighlight.delete(annotation.range);\n\t\t}\n\n\t\t// Update color\n\t\tannotation.color = newColor;\n\n\t\t// Add to new color highlight\n\t\tconst newColorHighlight = this.colorHighlights.get(newColor);\n\t\tif (newColorHighlight) {\n\t\t\tnewColorHighlight.add(annotation.range);\n\t\t}\n\t}\n\n\t/**\n\t * Export annotations as serializable data for persistence.\n\t * Uses RangeSerializer for robust DOM range serialization.\n\t *\n\t * @param root Root element for serialization (typically document.body or content container)\n\t * @returns Array of serialized annotations\n\t */\n\texportAnnotations(\n\t\troot: Element = document.body,\n\t): Array<\n\t\tSerializedRange & { id: string; color: HighlightColor; timestamp: number }\n\t> {\n\t\treturn this.getAnnotations().map((annotation) => ({\n\t\t\t...this.rangeSerializer.serialize(annotation.range, root),\n\t\t\tid: annotation.id,\n\t\t\tcolor: annotation.color,\n\t\t\ttimestamp: annotation.timestamp,\n\t\t}));\n\t}\n\n\t/**\n\t * Import annotations from serialized data.\n\t * Restores annotations after page navigation or refresh.\n\t *\n\t * @param data Array of serialized annotations\n\t * @param root Root element for deserialization (same as used in export)\n\t * @returns Number of successfully restored annotations\n\t */\n\timportAnnotations(\n\t\tdata: Array<\n\t\t\tSerializedRange & {\n\t\t\t\tid?: string;\n\t\t\t\tcolor: HighlightColor;\n\t\t\t\ttimestamp?: number;\n\t\t\t}\n\t\t>,\n\t\troot: Element = document.body,\n\t): number {\n\t\tlet restored = 0;\n\n\t\tfor (const item of data) {\n\t\t\tconst range = this.rangeSerializer.deserialize(item, root);\n\t\t\tif (range) {\n\t\t\t\tthis.addAnnotation(range, item.color);\n\t\t\t\trestored++;\n\t\t\t}\n\t\t}\n\n\t\treturn restored;\n\t}\n\n\t// ============================================================================\n\t// HighlightCoordinatorApi interface implementation\n\t// ============================================================================\n\n\t/**\n\t * Highlight a text range (interface method)\n\t *\n\t * Generic method that adapts to the specific highlight type.\n\t * For more control, use the specific methods like highlightTTSWord().\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\tcase HighlightType.TTS_SENTENCE: {\n\t\t\t\t// For TTS, add to appropriate highlight\n\t\t\t\tconst highlight =\n\t\t\t\t\ttype === HighlightType.TTS_WORD\n\t\t\t\t\t\t? this.ttsWordHighlight\n\t\t\t\t\t\t: this.ttsSentenceHighlight;\n\t\t\t\tif (highlight) {\n\t\t\t\t\thighlight.clear();\n\t\t\t\t\thighlight.add(range);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\t// For annotations, use the existing method\n\t\t\t\tthis.addAnnotation(range, color);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear highlights of a specific type (interface method)\n\t */\n\tclearHighlights(type: HighlightType): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\t\tthis.clearTTSWord();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.TTS_SENTENCE:\n\t\t\t\tthis.clearTTSSentence();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\tthis.clearAnnotations();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear all highlights (interface method)\n\t */\n\tclearAll(): void {\n\t\tif (!this.supported) return;\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\t}\n\n\t/**\n\t * Check if CSS Highlight API is supported (interface method)\n\t */\n\tisSupported(): boolean {\n\t\treturn this.supported;\n\t}\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t *\n\t * @param color CSS color value (e.g., '#ffeb3b')\n\t * @param opacity Opacity value (0.0 to 1.0)\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return;\n\n\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\tif (!styleEl) return;\n\n\t\t// Convert hex to rgba\n\t\tconst hexToRgb = (hex: string) => {\n\t\t\tconst result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n\t\t\treturn result\n\t\t\t\t? {\n\t\t\t\t\t\tr: parseInt(result[1], 16),\n\t\t\t\t\t\tg: parseInt(result[2], 16),\n\t\t\t\t\t\tb: parseInt(result[3], 16),\n\t\t\t\t\t}\n\t\t\t\t: null;\n\t\t};\n\n\t\tconst rgb = hexToRgb(color);\n\t\tif (!rgb) return;\n\n\t\tthis.explicitTTSColorOverride = {\n\t\t\tcolor: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n\t\t\topacity: Math.max(0.2, Math.min(0.95, opacity)),\n\t\t};\n\t\tthis.applyAdaptiveTTSStyle();\n\t}\n\n\t/**\n\t * Cleanup - remove all highlights\n\t */\n\tdestroy(): void {\n\t\tif (!this.supported) return;\n\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\n\t\t// Remove style element\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\t\tif (styleEl) {\n\t\t\t\tstyleEl.remove();\n\t\t\t}\n\t\t}\n\t\tthis.themeObserver?.disconnect();\n\t\tthis.themeObserver = null;\n\t}\n}\n"]}
1
+ {"version":3,"file":"HighlightCoordinator.js","sourceRoot":"","sources":["../../src/services/HighlightCoordinator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * HighlightCoordinator\n *\n * Manages separate highlight layers for TTS (temporary) and annotations (persistent).\n * Uses CSS Custom Highlight API - zero DOM mutation, preserves accessibility.\n *\n * Features:\n * - Separate layers prevent TTS clearing from removing student annotations\n * - CSS Custom Highlight API (no DOM mutation)\n * - Full accessibility support (screen readers see original text)\n * - Configurable colors and styles\n * - Annotation persistence via RangeSerializer\n *\n * Part of PIE Assessment Toolkit.\n *\n * Browser Support:\n * - Chrome/Edge 105+\n * - Safari 17.2+\n * - Firefox: Behind flag (not recommended for production)\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API\n */\n\nimport type { HighlightCoordinatorApi } from \"./interfaces.js\";\nimport { RangeSerializer, type SerializedRange } from \"./RangeSerializer.js\";\n\n/**\n * Highlight types\n */\nexport enum HighlightType {\n\tTTS_WORD = \"tts-word\",\n\tTTS_SENTENCE = \"tts-sentence\",\n\tANNOTATION = \"annotation\",\n}\n\n/**\n * Highlight colors for annotations\n */\nexport enum HighlightColor {\n\tYELLOW = \"yellow\",\n\tGREEN = \"green\",\n\tBLUE = \"blue\",\n\tPINK = \"pink\",\n\tORANGE = \"orange\",\n\tUNDERLINE = \"underline\",\n}\n\n/**\n * Annotation data\n */\nexport interface Annotation {\n\tid: string;\n\trange: Range;\n\ttype: HighlightType.ANNOTATION;\n\tcolor: HighlightColor;\n\ttimestamp: number;\n}\n\n/**\n * Configuration for HighlightCoordinator\n * Currently empty but allows future extension without breaking changes\n */\nexport type HighlightCoordinatorConfig = Record<string, never>;\n\nexport class HighlightCoordinator implements HighlightCoordinatorApi {\n\tprivate config: HighlightCoordinatorConfig;\n\tprivate ttsWordHighlight: Highlight | null = null;\n\tprivate ttsSentenceHighlight: Highlight | null = null;\n\tprivate ttsWordElementHighlights = new Set<Element>();\n\tprivate ttsSentenceElementHighlights = new Set<Element>();\n\tprivate annotations = new Map<string, Annotation>();\n\t// Shared highlights per color (one Highlight object per color, contains all ranges)\n\tprivate colorHighlights = new Map<HighlightColor, Highlight>();\n\tprivate nextAnnotationId = 1;\n\tprivate supported = false;\n\tprivate rangeSerializer: RangeSerializer;\n\tprivate themeObserver: MutationObserver | null = null;\n\tprivate explicitTTSColorOverride: { color: string; opacity: number } | null = null;\n\n\tconstructor(config: HighlightCoordinatorConfig = {}) {\n\t\tthis.config = config;\n\t\tthis.rangeSerializer = new RangeSerializer();\n\n\t\t// SSR guard\n\t\tif (typeof CSS === \"undefined\" || !(\"highlights\" in CSS)) {\n\t\t\tconsole.warn(\n\t\t\t\t\"CSS Custom Highlight API not supported (SSR or unsupported browser)\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.supported = true;\n\t\tthis.initializeHighlights();\n\t\tthis.registerStyles();\n\t\tthis.applyAdaptiveTTSStyle();\n\t\tthis.setupThemeObservation();\n\t}\n\n\tprivate setupThemeObservation(): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tif (typeof MutationObserver === \"undefined\") return;\n\n\t\tconst refresh = () => this.applyAdaptiveTTSStyle();\n\t\tthis.themeObserver = new MutationObserver(refresh);\n\n\t\tthis.themeObserver.observe(document.documentElement, {\n\t\t\tattributes: true,\n\t\t\tattributeFilter: [\"style\", \"data-theme\", \"data-color-scheme\", \"class\"],\n\t\t});\n\n\t\tfor (const host of document.querySelectorAll(\"pie-theme\")) {\n\t\t\tthis.themeObserver.observe(host, {\n\t\t\t\tattributes: true,\n\t\t\t\tattributeFilter: [\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"scheme\",\n\t\t\t\t\t\"provider\",\n\t\t\t\t\t\"variables\",\n\t\t\t\t\t\"style\",\n\t\t\t\t\t\"data-theme\",\n\t\t\t\t\t\"data-color-scheme\",\n\t\t\t\t],\n\t\t\t});\n\t\t}\n\t}\n\n\tprivate parseColor(input: string | null | undefined): [number, number, number] | null {\n\t\tif (!input) return null;\n\t\tconst value = input.trim();\n\t\tif (!value) return null;\n\n\t\tconst hexMatch = /^#([a-f\\d]{3}|[a-f\\d]{6})$/i.exec(value);\n\t\tif (hexMatch) {\n\t\t\tconst hex = hexMatch[1];\n\t\t\tif (hex.length === 3) {\n\t\t\t\treturn [\n\t\t\t\t\tparseInt(hex[0] + hex[0], 16),\n\t\t\t\t\tparseInt(hex[1] + hex[1], 16),\n\t\t\t\t\tparseInt(hex[2] + hex[2], 16),\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn [\n\t\t\t\tparseInt(hex.slice(0, 2), 16),\n\t\t\t\tparseInt(hex.slice(2, 4), 16),\n\t\t\t\tparseInt(hex.slice(4, 6), 16),\n\t\t\t];\n\t\t}\n\n\t\tconst rgbMatch = /^rgba?\\((.+)\\)$/i.exec(value);\n\t\tif (rgbMatch) {\n\t\t\tconst normalized = rgbMatch[1].replace(/\\//g, \",\");\n\t\t\tconst parts = normalized\n\t\t\t\t.split(/[,\\s]+/)\n\t\t\t\t.map((part) => part.trim())\n\t\t\t\t.filter(Boolean);\n\t\t\tconst r = Number(parts[0]);\n\t\t\tconst g = Number(parts[1]);\n\t\t\tconst b = Number(parts[2]);\n\t\t\tif (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {\n\t\t\t\treturn [r, g, b];\n\t\t\t}\n\t\t}\n\n\t\t// Browser parser fallback (covers formats like oklch()).\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst parserEl = document.createElement(\"span\");\n\t\t\tparserEl.style.color = value;\n\t\t\tif (typeof parserEl.style.color === \"string\" && parserEl.style.color) {\n\t\t\t\tdocument.body?.appendChild(parserEl);\n\t\t\t\tconst resolved =\n\t\t\t\t\ttypeof getComputedStyle === \"function\"\n\t\t\t\t\t\t? getComputedStyle(parserEl).color\n\t\t\t\t\t\t: \"\";\n\t\t\t\tparserEl.remove();\n\t\t\t\tconst normalizedResolved = resolved.trim();\n\t\t\t\t// Guard against recursive loops when computed style returns the same\n\t\t\t\t// unresolved function syntax (observed with certain color formats).\n\t\t\t\tif (normalizedResolved && normalizedResolved !== value) {\n\t\t\t\t\treturn this.parseColor(resolved);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tprivate relativeLuminance([r, g, b]: [number, number, number]): number {\n\t\tconst toLinear = (channel: number) => {\n\t\t\tconst n = channel / 255;\n\t\t\treturn n <= 0.03928 ? n / 12.92 : ((n + 0.055) / 1.055) ** 2.4;\n\t\t};\n\t\tconst lr = toLinear(r);\n\t\tconst lg = toLinear(g);\n\t\tconst lb = toLinear(b);\n\t\treturn 0.2126 * lr + 0.7152 * lg + 0.0722 * lb;\n\t}\n\n\tprivate contrastRatio(\n\t\ta: [number, number, number],\n\t\tb: [number, number, number],\n\t): number {\n\t\tconst la = this.relativeLuminance(a);\n\t\tconst lb = this.relativeLuminance(b);\n\t\tconst lighter = Math.max(la, lb);\n\t\tconst darker = Math.min(la, lb);\n\t\treturn (lighter + 0.05) / (darker + 0.05);\n\t}\n\n\tprivate blend(\n\t\tfg: [number, number, number],\n\t\tbg: [number, number, number],\n\t\talpha: number,\n\t): [number, number, number] {\n\t\treturn [\n\t\t\tMath.round(fg[0] * alpha + bg[0] * (1 - alpha)),\n\t\t\tMath.round(fg[1] * alpha + bg[1] * (1 - alpha)),\n\t\t\tMath.round(fg[2] * alpha + bg[2] * (1 - alpha)),\n\t\t];\n\t}\n\n\tprivate resolveAdaptiveTTSStyle(sourceEl?: Element | null): {\n\t\twordHighlight: string;\n\t\tsentenceHighlight: string;\n\t\twordUnderline: string;\n\t\twordShadow: string;\n\t} {\n\t\tconst fallbackMissing: [number, number, number] = [255, 235, 59];\n\t\tconst fallbackText: [number, number, number] = [17, 24, 39];\n\t\tconst fallbackBackground: [number, number, number] = [255, 255, 255];\n\n\t\tconst target =\n\t\t\tsourceEl ||\n\t\t\t(typeof document !== \"undefined\" ? document.documentElement : null);\n\t\tconst computed =\n\t\t\ttarget && typeof getComputedStyle === \"function\"\n\t\t\t\t? getComputedStyle(target)\n\t\t\t\t: null;\n\n\t\tconst background =\n\t\t\tthis.parseColor(\n\t\t\t\tcomputed?.getPropertyValue(\"--pie-background\") || computed?.backgroundColor,\n\t\t\t) || fallbackBackground;\n\t\tconst text =\n\t\t\tthis.parseColor(computed?.getPropertyValue(\"--pie-text\")) || fallbackText;\n\t\tconst accent = this.explicitTTSColorOverride\n\t\t\t? this.parseColor(this.explicitTTSColorOverride.color) || fallbackMissing\n\t\t\t: this.parseColor(computed?.getPropertyValue(\"--pie-missing\")) ||\n\t\t\t\tfallbackMissing;\n\n\t\tconst opacityCandidates = this.explicitTTSColorOverride\n\t\t\t? [Math.max(0.3, Math.min(0.95, this.explicitTTSColorOverride.opacity))]\n\t\t\t: [0.8, 0.72, 0.68, 0.62, 0.56, 0.5];\n\n\t\tlet selectedOpacity = opacityCandidates[opacityCandidates.length - 1];\n\t\tlet bestScore = -Infinity;\n\n\t\tfor (const opacity of opacityCandidates) {\n\t\t\tconst blended = this.blend(accent, background, opacity);\n\t\t\tconst backgroundDelta = this.contrastRatio(blended, background);\n\t\t\tconst textContrast = this.contrastRatio(blended, text);\n\t\t\tconst score = backgroundDelta * 1.2 + textContrast * 0.8;\n\t\t\tif (backgroundDelta >= 1.25 && textContrast >= 2.4) {\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (score > bestScore) {\n\t\t\t\tbestScore = score;\n\t\t\t\tselectedOpacity = opacity;\n\t\t\t}\n\t\t}\n\n\t\tconst sentenceOpacity = Math.max(0.24, Math.min(0.85, selectedOpacity * 0.55));\n\t\tconst underlineOpacity = Math.max(0.55, Math.min(0.95, selectedOpacity + 0.2));\n\t\tconst shadowOpacity = Math.max(0.22, Math.min(0.6, selectedOpacity * 0.45));\n\n\t\tconst underlineColor = text;\n\t\tconst underlineBlend = this.blend(underlineColor, background, underlineOpacity);\n\t\tconst underlineDelta = this.contrastRatio(underlineBlend, background);\n\t\tconst fallbackUnderline: [number, number, number] =\n\t\t\tthis.relativeLuminance(background) > 0.45 ? [0, 0, 0] : [255, 255, 255];\n\t\tconst finalUnderline = underlineDelta >= 1.35 ? underlineColor : fallbackUnderline;\n\n\t\tconst wordHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${selectedOpacity})`;\n\t\tconst sentenceHighlight = `rgba(${accent[0]}, ${accent[1]}, ${accent[2]}, ${sentenceOpacity})`;\n\t\tconst wordUnderline = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${underlineOpacity})`;\n\t\tconst wordShadow = `rgba(${finalUnderline[0]}, ${finalUnderline[1]}, ${finalUnderline[2]}, ${shadowOpacity})`;\n\n\t\treturn {\n\t\t\twordHighlight,\n\t\t\tsentenceHighlight,\n\t\t\twordUnderline,\n\t\t\twordShadow,\n\t\t};\n\t}\n\n\tprivate applyAdaptiveTTSStyle(sourceEl?: Element | null): void {\n\t\tif (typeof document === \"undefined\") return;\n\t\tconst vars = this.resolveAdaptiveTTSStyle(sourceEl);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-highlight\",\n\t\t\tvars.wordHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-sentence-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-line-highlight\",\n\t\t\tvars.sentenceHighlight,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-underline\",\n\t\t\tvars.wordUnderline,\n\t\t);\n\t\tdocument.documentElement.style.setProperty(\n\t\t\t\"--pie-tts-word-shadow\",\n\t\t\tvars.wordShadow,\n\t\t);\n\t}\n\n\t/**\n\t * Initialize CSS Custom Highlights\n\t */\n\tprivate initializeHighlights(): void {\n\t\tif (!this.supported) return;\n\n\t\t// Create highlight registries for TTS\n\t\tthis.ttsWordHighlight = new Highlight();\n\t\tthis.ttsSentenceHighlight = new Highlight();\n\n\t\t// Register TTS highlights with CSS\n\t\tCSS.highlights.set(\"tts-word\", this.ttsWordHighlight);\n\t\tCSS.highlights.set(\"tts-sentence\", this.ttsSentenceHighlight);\n\n\t\t// Create shared highlights for each annotation color\n\t\tfor (const color of Object.values(HighlightColor)) {\n\t\t\tconst highlight = new Highlight();\n\t\t\tthis.colorHighlights.set(color, highlight);\n\t\t\tCSS.highlights.set(`annotation-${color}`, highlight);\n\t\t}\n\t}\n\n\t/**\n\t * Register CSS styles for highlights\n\t */\n\tprivate registerStyles(): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return; // SSR guard\n\n\t\t// Check if styles already exist\n\t\tif (document.getElementById(\"pie-highlight-styles\")) return;\n\n\t\tconst style = document.createElement(\"style\");\n\t\tstyle.id = \"pie-highlight-styles\";\n\t\tstyle.textContent = `\n /* TTS highlights - temporary */\n ::highlight(tts-word) {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n color: inherit;\n }\n\n /* tts-sentence registry id: coarse read-along band (visual line boxes in layout) */\n ::highlight(tts-sentence) {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n color: inherit;\n }\n\n [data-pie-tts-sentence-element=\"true\"] {\n background-color: var(--pie-tts-line-highlight, var(--pie-tts-sentence-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 38%, transparent)));\n border-radius: 0.12em;\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n }\n\n [data-pie-tts-word-element=\"true\"] {\n background-color: var(--pie-tts-word-highlight, color-mix(in srgb, var(--pie-missing, #ffeb3b) 68%, transparent));\n text-decoration: underline 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n text-underline-offset: 2px;\n text-shadow: 0 0 1px var(--pie-tts-word-shadow, color-mix(in srgb, var(--pie-text, #111827) 35%, transparent));\n border-bottom: 2px solid var(--pie-tts-word-underline, color-mix(in srgb, var(--pie-text, #111827) 70%, transparent));\n padding-bottom: 1px;\n border-radius: 0.12em;\n box-decoration-break: clone;\n -webkit-box-decoration-break: clone;\n }\n\n /* Annotation highlights - persistent */\n ::highlight(annotation-yellow) {\n background-color: color-mix(in srgb, var(--pie-missing, #ffff00) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-green) {\n background-color: color-mix(in srgb, var(--pie-correct, #90ee90) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-blue) {\n background-color: color-mix(in srgb, var(--pie-tertiary, #add8e6) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-pink) {\n background-color: color-mix(in srgb, var(--pie-secondary-light, #ffb6c1) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-orange) {\n background-color: color-mix(in srgb, var(--pie-incorrect, #ffa500) 35%, transparent);\n color: inherit;\n }\n\n ::highlight(annotation-underline) {\n background-color: transparent;\n text-decoration: underline 2px solid var(--pie-primary, #0066cc);\n text-underline-offset: 2px;\n color: inherit;\n }\n `;\n\t\tdocument.head.appendChild(style);\n\t}\n\n\t/**\n\t * Highlight a word for TTS (temporary)\n\t *\n\t * @param textNode Text node containing the word\n\t * @param startOffset Start position in text node\n\t * @param endOffset End position in text node\n\t */\n\thighlightTTSWord(\n\t\ttextNode: Text,\n\t\tstartOffset: number,\n\t\tendOffset: number,\n\t): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(textNode.parentElement);\n\n\t\t// Clear previous word highlight\n\t\tthis.clearTTSWord();\n\n\t\t// Create range for word\n\t\tconst range = document.createRange();\n\t\trange.setStart(textNode, startOffset);\n\t\trange.setEnd(textNode, endOffset);\n\n\t\t// Add to highlight\n\t\tthis.ttsWordHighlight.add(range);\n\t}\n\n\t/**\n\t * Highlight a single element as the active TTS word (temporary).\n\t *\n\t * Unlike {@link highlightTTSWord} (which paints a CSS range over a text node)\n\t * this marks the element itself via `data-pie-tts-word-element`. It is used\n\t * for atomic targets that have no direct text node to range over — most\n\t * notably MathJax CHTML tokens (e.g. `<mjx-mi><mjx-c/></mjx-mi>`, whose glyph\n\t * lives in a font-driven pseudo-element a CSS range cannot paint) and\n\t * whole-expression fallbacks.\n\t *\n\t * The supplied element is painted exactly; we deliberately do NOT walk up to\n\t * a containing `<math>` / `<mjx-container>`. That escalation is what made\n\t * MathJax-rendered math highlight as a full block while native MathML tracked\n\t * per-token: a resolved single token must paint as a token, and only a\n\t * genuine expression fallback (where the element already is the container)\n\t * should paint the whole expression.\n\t */\n\thighlightTTSWordElement(element: Element): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.applyAdaptiveTTSStyle(element);\n\n\t\t// Clear previous word highlight (CSS range and any element attributes)\n\t\tthis.clearTTSWord();\n\n\t\telement.setAttribute(\"data-pie-tts-word-element\", \"true\");\n\t\tthis.ttsWordElementHighlights.add(element);\n\t}\n\n\t/**\n\t * Highlight the coarse TTS read-along band (temporary).\n\t * Usually one Range per visual line; CSS layer `tts-sentence` for compatibility.\n\t *\n\t * @param ranges Line (or sentence) ranges to paint with `--pie-tts-line-highlight`\n\t */\n\thighlightTTSSentence(ranges: Range[]): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tconst source = ranges[0]?.startContainer;\n\t\tconst sourceElement =\n\t\t\tsource?.nodeType === Node.ELEMENT_NODE\n\t\t\t\t? (source as Element)\n\t\t\t\t: source?.parentElement;\n\t\tthis.applyAdaptiveTTSStyle(sourceElement);\n\n\t\t// Clear previous sentence highlight\n\t\tthis.clearTTSSentence();\n\n\t\t// Add all ranges\n\t\tfor (const range of ranges) {\n\t\t\tthis.ttsSentenceHighlight.add(range);\n\t\t}\n\t\tthis.highlightTTSElementFallbacks(ranges);\n\t}\n\n\t// The read-along (sentence) band legitimately covers whole equations and\n\t// replaced elements, so it escalates to them.\n\tprivate static readonly SENTENCE_FALLBACK_SELECTOR =\n\t\t\"math, mjx-container, svg, img, canvas, [role='img']\";\n\n\t// The word layer must NOT escalate a multi-node range to the enclosing\n\t// `<math>` / `<mjx-container>`: that is what made an equation flash as a\n\t// whole block whenever a spoken word's visible range crossed several math\n\t// glyph nodes. Math is tracked per token (or held / region) by the highlight\n\t// pipeline instead. Replaced elements (svg/img/canvas) still need the element\n\t// fallback because a CSS range cannot paint them.\n\tprivate static readonly WORD_FALLBACK_SELECTOR =\n\t\t\"svg, img, canvas, [role='img']\";\n\n\tprivate highlightTTSElementFallbacks(ranges: Range[]): void {\n\t\tif (typeof Element === \"undefined\") return;\n\t\tthis.highlightElementFallbacks(\n\t\t\tranges,\n\t\t\t\"data-pie-tts-sentence-element\",\n\t\t\tthis.ttsSentenceElementHighlights,\n\t\t\tHighlightCoordinator.SENTENCE_FALLBACK_SELECTOR,\n\t\t);\n\t}\n\n\tprivate highlightTTSWordElementFallbacks(ranges: Range[]): void {\n\t\tif (typeof Element === \"undefined\") return;\n\t\tthis.highlightElementFallbacks(\n\t\t\tranges,\n\t\t\t\"data-pie-tts-word-element\",\n\t\t\tthis.ttsWordElementHighlights,\n\t\t\tHighlightCoordinator.WORD_FALLBACK_SELECTOR,\n\t\t);\n\t}\n\n\tprivate highlightElementFallbacks(\n\t\tranges: Range[],\n\t\tattributeName: string,\n\t\ttrackedElements: Set<Element>,\n\t\tselector: string,\n\t): void {\n\t\tfor (const range of ranges) {\n\t\t\tconst root =\n\t\t\t\trange.commonAncestorContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.commonAncestorContainer as Element)\n\t\t\t\t\t: range.commonAncestorContainer.parentElement;\n\t\t\tif (!root) continue;\n\t\t\tconst candidates: Element[] = [];\n\t\t\tconst rangeStartElement =\n\t\t\t\trange.startContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.startContainer as Element)\n\t\t\t\t\t: range.startContainer.parentElement;\n\t\t\tconst rangeEndElement =\n\t\t\t\trange.endContainer.nodeType === Node.ELEMENT_NODE\n\t\t\t\t\t? (range.endContainer as Element)\n\t\t\t\t\t: range.endContainer.parentElement;\n\t\t\tfor (const element of [rangeStartElement, rangeEndElement]) {\n\t\t\t\tlet current: Element | null = element;\n\t\t\t\twhile (current) {\n\t\t\t\t\tif (current.matches?.(selector)) candidates.push(current);\n\t\t\t\t\tcurrent = current.parentElement;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (root.matches?.(selector)) {\n\t\t\t\tcandidates.push(root);\n\t\t\t}\n\t\t\tcandidates.push(...Array.from(root.querySelectorAll(selector)));\n\t\t\tfor (const element of new Set(candidates)) {\n\t\t\t\ttry {\n\t\t\t\t\tif (\n\t\t\t\t\t\t!range.intersectsNode(element) &&\n\t\t\t\t\t\t!element.contains(rangeStartElement) &&\n\t\t\t\t\t\t!element.contains(rangeEndElement)\n\t\t\t\t\t) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\telement.setAttribute(attributeName, \"true\");\n\t\t\t\ttrackedElements.add(element);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Clear TTS word highlight\n\t */\n\tclearTTSWord(): void {\n\t\tif (!this.ttsWordHighlight) return;\n\t\tthis.ttsWordHighlight.clear();\n\t\tfor (const element of this.ttsWordElementHighlights) {\n\t\t\telement.removeAttribute(\"data-pie-tts-word-element\");\n\t\t}\n\t\tthis.ttsWordElementHighlights.clear();\n\t}\n\n\t/**\n\t * Clear TTS sentence highlight\n\t */\n\tclearTTSSentence(): void {\n\t\tif (!this.ttsSentenceHighlight) return;\n\t\tthis.ttsSentenceHighlight.clear();\n\t\tfor (const element of this.ttsSentenceElementHighlights) {\n\t\t\telement.removeAttribute(\"data-pie-tts-sentence-element\");\n\t\t}\n\t\tthis.ttsSentenceElementHighlights.clear();\n\t}\n\n\t/**\n\t * Clear all TTS highlights\n\t */\n\tclearTTS(): void {\n\t\tthis.clearTTSWord();\n\t\tthis.clearTTSSentence();\n\t}\n\n\t/**\n\t * Add an annotation highlight (persistent)\n\t *\n\t * @param range Text range to annotate\n\t * @param color Highlight color\n\t * @returns Annotation ID for later removal\n\t */\n\taddAnnotation(\n\t\trange: Range,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): string {\n\t\tconst id = `annotation-${this.nextAnnotationId++}`;\n\n\t\t// Clone the range to store\n\t\tconst clonedRange = range.cloneRange();\n\n\t\t// Store annotation data\n\t\tconst annotation: Annotation = {\n\t\t\tid,\n\t\t\trange: clonedRange,\n\t\t\ttype: HighlightType.ANNOTATION,\n\t\t\tcolor,\n\t\t\ttimestamp: Date.now(),\n\t\t};\n\t\tthis.annotations.set(id, annotation);\n\n\t\t// Add the SAME range object to the shared color highlight\n\t\t// This ensures we can later delete it by reference\n\t\tconst colorHighlight = this.colorHighlights.get(color);\n\t\tif (colorHighlight) {\n\t\t\tcolorHighlight.add(clonedRange);\n\t\t}\n\n\t\treturn id;\n\t}\n\n\t/**\n\t * Remove an annotation\n\t *\n\t * @param id Annotation ID\n\t */\n\tremoveAnnotation(id: string): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) {\n\t\t\tconsole.warn(`[HighlightCoordinator] Annotation ${id} not found`);\n\t\t\treturn;\n\t\t}\n\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Removing annotation ${id} (color: ${annotation.color})`,\n\t\t);\n\n\t\t// Remove range from the shared color highlight\n\t\tconst colorHighlight = this.colorHighlights.get(annotation.color);\n\t\tif (colorHighlight) {\n\t\t\tconst deleted = colorHighlight.delete(annotation.range);\n\t\t\tconsole.log(\n\t\t\t\t`[HighlightCoordinator] Highlight.delete() returned:`,\n\t\t\t\tdeleted,\n\t\t\t);\n\t\t}\n\n\t\t// Clean up\n\t\tthis.annotations.delete(id);\n\t\tconsole.log(\n\t\t\t`[HighlightCoordinator] Annotation ${id} removed from map. Remaining:`,\n\t\t\tthis.annotations.size,\n\t\t);\n\t}\n\n\t/**\n\t * Remove all annotations\n\t */\n\tclearAnnotations(): void {\n\t\t// Clear all color highlights\n\t\tfor (const colorHighlight of this.colorHighlights.values()) {\n\t\t\tcolorHighlight.clear();\n\t\t}\n\n\t\t// Clear annotation data\n\t\tthis.annotations.clear();\n\t}\n\n\t/**\n\t * Get all annotations\n\t */\n\tgetAnnotations(): Annotation[] {\n\t\treturn Array.from(this.annotations.values());\n\t}\n\n\t/**\n\t * Get annotation by ID\n\t */\n\tgetAnnotation(id: string): Annotation | null {\n\t\treturn this.annotations.get(id) ?? null;\n\t}\n\n\t/**\n\t * Change annotation color\n\t *\n\t * @param id Annotation ID\n\t * @param newColor New color\n\t */\n\tchangeAnnotationColor(id: string, newColor: HighlightColor): void {\n\t\tconst annotation = this.annotations.get(id);\n\t\tif (!annotation) return;\n\n\t\tconst oldColor = annotation.color;\n\t\tif (oldColor === newColor) return;\n\n\t\t// Remove from old color highlight\n\t\tconst oldColorHighlight = this.colorHighlights.get(oldColor);\n\t\tif (oldColorHighlight) {\n\t\t\toldColorHighlight.delete(annotation.range);\n\t\t}\n\n\t\t// Update color\n\t\tannotation.color = newColor;\n\n\t\t// Add to new color highlight\n\t\tconst newColorHighlight = this.colorHighlights.get(newColor);\n\t\tif (newColorHighlight) {\n\t\t\tnewColorHighlight.add(annotation.range);\n\t\t}\n\t}\n\n\t/**\n\t * Export annotations as serializable data for persistence.\n\t * Uses RangeSerializer for robust DOM range serialization.\n\t *\n\t * @param root Root element for serialization (typically document.body or content container)\n\t * @returns Array of serialized annotations\n\t */\n\texportAnnotations(\n\t\troot: Element = document.body,\n\t): Array<\n\t\tSerializedRange & { id: string; color: HighlightColor; timestamp: number }\n\t> {\n\t\treturn this.getAnnotations().map((annotation) => ({\n\t\t\t...this.rangeSerializer.serialize(annotation.range, root),\n\t\t\tid: annotation.id,\n\t\t\tcolor: annotation.color,\n\t\t\ttimestamp: annotation.timestamp,\n\t\t}));\n\t}\n\n\t/**\n\t * Import annotations from serialized data.\n\t * Restores annotations after page navigation or refresh.\n\t *\n\t * @param data Array of serialized annotations\n\t * @param root Root element for deserialization (same as used in export)\n\t * @returns Number of successfully restored annotations\n\t */\n\timportAnnotations(\n\t\tdata: Array<\n\t\t\tSerializedRange & {\n\t\t\t\tid?: string;\n\t\t\t\tcolor: HighlightColor;\n\t\t\t\ttimestamp?: number;\n\t\t\t}\n\t\t>,\n\t\troot: Element = document.body,\n\t): number {\n\t\tlet restored = 0;\n\n\t\tfor (const item of data) {\n\t\t\tconst range = this.rangeSerializer.deserialize(item, root);\n\t\t\tif (range) {\n\t\t\t\tthis.addAnnotation(range, item.color);\n\t\t\t\trestored++;\n\t\t\t}\n\t\t}\n\n\t\treturn restored;\n\t}\n\n\t// ============================================================================\n\t// HighlightCoordinatorApi interface implementation\n\t// ============================================================================\n\n\t/**\n\t * Highlight a text range (interface method)\n\t *\n\t * Generic method that adapts to the specific highlight type.\n\t * For more control, use the specific methods like highlightTTSWord().\n\t */\n\thighlightRange(\n\t\trange: Range,\n\t\ttype: HighlightType,\n\t\tcolor: HighlightColor = HighlightColor.YELLOW,\n\t): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\tcase HighlightType.TTS_SENTENCE: {\n\t\t\t\t// For TTS, add to appropriate highlight\n\t\t\t\tconst highlight =\n\t\t\t\t\ttype === HighlightType.TTS_WORD\n\t\t\t\t\t\t? this.ttsWordHighlight\n\t\t\t\t\t\t: this.ttsSentenceHighlight;\n\t\t\t\tif (highlight) {\n\t\t\t\t\thighlight.clear();\n\t\t\t\t\thighlight.add(range);\n\t\t\t\t\tif (type === HighlightType.TTS_WORD) {\n\t\t\t\t\t\tfor (const element of this.ttsWordElementHighlights) {\n\t\t\t\t\t\t\telement.removeAttribute(\"data-pie-tts-word-element\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthis.ttsWordElementHighlights.clear();\n\t\t\t\t\t\tthis.highlightTTSWordElementFallbacks([range]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\t// For annotations, use the existing method\n\t\t\t\tthis.addAnnotation(range, color);\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear highlights of a specific type (interface method)\n\t */\n\tclearHighlights(type: HighlightType): void {\n\t\tif (!this.supported) return;\n\n\t\tswitch (type) {\n\t\t\tcase HighlightType.TTS_WORD:\n\t\t\t\tthis.clearTTSWord();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.TTS_SENTENCE:\n\t\t\t\tthis.clearTTSSentence();\n\t\t\t\tbreak;\n\t\t\tcase HighlightType.ANNOTATION:\n\t\t\t\tthis.clearAnnotations();\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Clear all highlights (interface method)\n\t */\n\tclearAll(): void {\n\t\tif (!this.supported) return;\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\t}\n\n\t/**\n\t * Check if CSS Highlight API is supported (interface method)\n\t */\n\tisSupported(): boolean {\n\t\treturn this.supported;\n\t}\n\n\t/**\n\t * Update TTS highlight style dynamically\n\t *\n\t * @param color CSS color value (e.g., '#ffeb3b')\n\t * @param opacity Opacity value (0.0 to 1.0)\n\t */\n\tupdateTTSHighlightStyle(color: string, opacity: number): void {\n\t\tif (!this.supported) return;\n\t\tif (typeof document === \"undefined\") return;\n\n\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\tif (!styleEl) return;\n\n\t\t// Convert hex to rgba\n\t\tconst hexToRgb = (hex: string) => {\n\t\t\tconst result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n\t\t\treturn result\n\t\t\t\t? {\n\t\t\t\t\t\tr: parseInt(result[1], 16),\n\t\t\t\t\t\tg: parseInt(result[2], 16),\n\t\t\t\t\t\tb: parseInt(result[3], 16),\n\t\t\t\t\t}\n\t\t\t\t: null;\n\t\t};\n\n\t\tconst rgb = hexToRgb(color);\n\t\tif (!rgb) return;\n\n\t\tthis.explicitTTSColorOverride = {\n\t\t\tcolor: `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`,\n\t\t\topacity: Math.max(0.2, Math.min(0.95, opacity)),\n\t\t};\n\t\tthis.applyAdaptiveTTSStyle();\n\t}\n\n\t/**\n\t * Cleanup - remove all highlights\n\t */\n\tdestroy(): void {\n\t\tif (!this.supported) return;\n\n\t\tthis.clearTTS();\n\t\tthis.clearAnnotations();\n\n\t\t// Remove style element\n\t\tif (typeof document !== \"undefined\") {\n\t\t\tconst styleEl = document.getElementById(\"pie-highlight-styles\");\n\t\t\tif (styleEl) {\n\t\t\t\tstyleEl.remove();\n\t\t\t}\n\t\t}\n\t\tthis.themeObserver?.disconnect();\n\t\tthis.themeObserver = null;\n\t}\n}\n"]}
@@ -9,7 +9,7 @@
9
9
  * 1. Parses markup and finds all <speak> elements
10
10
  * 2. Extracts SSML content with language metadata
11
11
  * 3. Generates accessibility catalogs with unique IDs
12
- * 4. Cleans visual markup (removes SSML, adds data-catalog-id)
12
+ * 4. Cleans visual markup (removes SSML, adds data-catalog-idref)
13
13
  *
14
14
  * This enables proper pronunciation, emphasis, and pacing for TTS without
15
15
  * requiring authors to maintain separate catalog files.
@@ -24,6 +24,21 @@ export interface ExtractionResult {
24
24
  catalogs: AccessibilityCatalog[];
25
25
  cleanedConfig: ConfigEntity;
26
26
  }
27
+ /**
28
+ * Sanitize an SSML subtree in place: remove disallowed attributes and
29
+ * replace disallowed elements with their text content (so phrasing order
30
+ * is preserved but no scripts / iframes / unknown tags survive).
31
+ */
32
+ export declare function sanitizeSsmlElement(element: Element): void;
33
+ /**
34
+ * Serialize a sanitized `<speak>` element back to a minimal SSML string.
35
+ * Uses a manual walker instead of `outerHTML` so that any namespace
36
+ * quirks (e.g. `xmlns` attributes added by the HTML parser) are
37
+ * normalised and disallowed descendants cannot reappear through quirks
38
+ * in the serializer.
39
+ */
40
+ export declare function serializeSsmlElement(element: Element): string;
41
+ export declare function sanitizeSsmlString(input: string): string;
27
42
  /**
28
43
  * SSMLExtractor Service
29
44
  *
@@ -9,7 +9,7 @@
9
9
  * 1. Parses markup and finds all <speak> elements
10
10
  * 2. Extracts SSML content with language metadata
11
11
  * 3. Generates accessibility catalogs with unique IDs
12
- * 4. Cleans visual markup (removes SSML, adds data-catalog-id)
12
+ * 4. Cleans visual markup (removes SSML, adds data-catalog-idref)
13
13
  *
14
14
  * This enables proper pronunciation, emphasis, and pacing for TTS without
15
15
  * requiring authors to maintain separate catalog files.
@@ -76,6 +76,40 @@ const SSML_ALLOWED_ATTRS = new Set([
76
76
  "gender",
77
77
  "src",
78
78
  ]);
79
+ /**
80
+ * SSML elements that are empty in practice (no child content). Authors
81
+ * typically write them as self-closing (`<break time="200ms"/>`). HTML's
82
+ * parser does NOT recognize them as void elements and silently drops the
83
+ * `/`, then nests every subsequent sibling as a child — producing
84
+ * malformed SSML like `<break>…rest of paragraph…</break>` when
85
+ * round-tripped. Pre-processing self-closing forms to explicit
86
+ * open+close pairs before HTML parsing avoids the reflow, and the
87
+ * serializer re-emits them as self-closing so providers see valid SSML.
88
+ *
89
+ * `audio` is intentionally excluded — SSML <audio> may carry fallback
90
+ * children (the spec's "alternate content"), so treating it as void
91
+ * would lose author intent.
92
+ */
93
+ const SSML_VOID_TAGS = new Set(["break", "mark", "lexicon"]);
94
+ /**
95
+ * Normalize self-closing forms of SSML void elements to explicit
96
+ * open+close pairs BEFORE handing the string to an HTML parser. The
97
+ * HTML5 parser ignores the `/` on non-void tags, so `<break/>` becomes
98
+ * a plain `<break>` open tag that swallows every following sibling.
99
+ * Rewriting to `<break></break>` preserves the empty-element shape
100
+ * across the HTML round-trip.
101
+ */
102
+ function expandSelfClosingVoidTags(input) {
103
+ let next = input;
104
+ for (const tag of SSML_VOID_TAGS) {
105
+ const selfClosing = new RegExp(`<${tag}\\b([^>/]*?)\\s*/>`, "gi");
106
+ next = next.replace(selfClosing, (_match, attrs) => {
107
+ const attrText = attrs ? attrs.replace(/\s+$/, "") : "";
108
+ return `<${tag}${attrText}></${tag}>`;
109
+ });
110
+ }
111
+ return next;
112
+ }
79
113
  /**
80
114
  * Hosts that must never appear inside an SSML `<audio src>` because they
81
115
  * would cause the server-side TTS provider to fetch a private / metadata
@@ -119,7 +153,7 @@ function isSafeSsmlAudioSrc(raw) {
119
153
  * replace disallowed elements with their text content (so phrasing order
120
154
  * is preserved but no scripts / iframes / unknown tags survive).
121
155
  */
122
- function sanitizeSsmlElement(element) {
156
+ export function sanitizeSsmlElement(element) {
123
157
  // Walk children snapshot first because we may replace nodes as we go.
124
158
  const children = Array.from(element.children);
125
159
  for (const child of children) {
@@ -159,12 +193,19 @@ function sanitizeSsmlElement(element) {
159
193
  * normalised and disallowed descendants cannot reappear through quirks
160
194
  * in the serializer.
161
195
  */
162
- function serializeSsmlElement(element) {
196
+ export function serializeSsmlElement(element) {
163
197
  const tagName = element.tagName.toLowerCase();
164
198
  const attrs = Array.from(element.attributes)
165
199
  .filter((attr) => SSML_ALLOWED_ATTRS.has(attr.name.toLowerCase()))
166
200
  .map((attr) => ` ${attr.name}="${escapeXmlAttr(attr.value)}"`)
167
201
  .join("");
202
+ // SSML void elements (e.g. <break>, <mark>) must be emitted as
203
+ // self-closing so providers like AWS Polly accept them. Emitting
204
+ // `<break></break>` parses fine on the SSML side but is wasteful;
205
+ // emitting `<break>` (open without close) is invalid SSML.
206
+ if (SSML_VOID_TAGS.has(tagName)) {
207
+ return `<${tagName}${attrs}/>`;
208
+ }
168
209
  const childPieces = [];
169
210
  for (const node of Array.from(element.childNodes)) {
170
211
  if (node.nodeType === 3 /* text */) {
@@ -186,6 +227,40 @@ function serializeSsmlElement(element) {
186
227
  }
187
228
  return `<${tagName}${attrs}>${childPieces.join("")}</${tagName}>`;
188
229
  }
230
+ export function sanitizeSsmlString(input) {
231
+ const trimmed = String(input || "").trim();
232
+ if (!trimmed || !trimmed.includes("<"))
233
+ return input;
234
+ if (typeof DOMParser === "undefined")
235
+ return input;
236
+ const parser = new DOMParser();
237
+ const doc = parser.parseFromString(expandSelfClosingVoidTags(trimmed), "text/html");
238
+ const speakElement = doc.querySelector("speak");
239
+ if (speakElement) {
240
+ sanitizeSsmlElement(speakElement);
241
+ return serializeSsmlElement(speakElement);
242
+ }
243
+ const wrapper = doc.createElement("speak");
244
+ for (const node of Array.from(doc.body.childNodes)) {
245
+ wrapper.appendChild(node);
246
+ }
247
+ sanitizeSsmlElement(wrapper);
248
+ return Array.from(wrapper.childNodes)
249
+ .map((node) => {
250
+ if (node.nodeType === 3 /* text */) {
251
+ return escapeXmlText(node.nodeValue ?? "");
252
+ }
253
+ if (node.nodeType === 1 /* element */) {
254
+ const child = node;
255
+ if (SSML_ALLOWED_TAGS.has(child.tagName.toLowerCase())) {
256
+ return serializeSsmlElement(child);
257
+ }
258
+ return escapeXmlText(child.textContent ?? "");
259
+ }
260
+ return "";
261
+ })
262
+ .join("");
263
+ }
189
264
  function escapeXmlText(value) {
190
265
  return value
191
266
  .replace(/&/g, "&amp;")
@@ -214,7 +289,9 @@ export class SSMLExtractor {
214
289
  */
215
290
  extractFromItemConfig(config) {
216
291
  const allCatalogs = [];
217
- // Deep clone config to avoid mutating original
292
+ // Shallow clone of config + a fresh models array. The per-model/choice
293
+ // objects are replaced (not mutated in place) by the `.map` below, so the
294
+ // caller's original config and its models are never mutated.
218
295
  const cleanedConfig = {
219
296
  ...config,
220
297
  models: config.models ? [...config.models] : [],
@@ -278,7 +355,7 @@ export class SSMLExtractor {
278
355
  const catalogs = [];
279
356
  try {
280
357
  const parser = new DOMParser();
281
- const doc = parser.parseFromString(markup, "text/html");
358
+ const doc = parser.parseFromString(expandSelfClosingVoidTags(markup), "text/html");
282
359
  // Find all <speak> elements
283
360
  const speakElements = Array.from(doc.querySelectorAll("speak"));
284
361
  if (speakElements.length === 0) {
@@ -316,9 +393,11 @@ export class SSMLExtractor {
316
393
  // Just remove <speak> - visual content should be in sibling element
317
394
  speakEl.remove();
318
395
  }
319
- // Add catalog ID to wrapper element
396
+ // Tag the wrapper with the QTI-style catalog reference so the
397
+ // runtime can resolve this region's spoken content. Same attribute
398
+ // authored content uses (`data-catalog-idref`) — one canonical name.
320
399
  if (wrapper) {
321
- wrapper.setAttribute("data-catalog-id", catalogId);
400
+ wrapper.setAttribute("data-catalog-idref", catalogId);
322
401
  }
323
402
  // Create catalog entry
324
403
  catalogs.push({
@@ -1 +1 @@
1
- {"version":3,"file":"SSMLExtractor.js","sourceRoot":"","sources":["../../src/services/SSMLExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SSMLExtractor\n *\n * Service for extracting embedded SSML from PIE item content and converting\n * it into QTI 3.0 accessibility catalogs.\n *\n * Authors can embed <speak> SSML tags directly in content for convenience.\n * The extractor automatically:\n * 1. Parses markup and finds all <speak> elements\n * 2. Extracts SSML content with language metadata\n * 3. Generates accessibility catalogs with unique IDs\n * 4. Cleans visual markup (removes SSML, adds data-catalog-id)\n *\n * This enables proper pronunciation, emphasis, and pacing for TTS without\n * requiring authors to maintain separate catalog files.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type {\n\tAccessibilityCatalog,\n\tConfigEntity,\n\tPieModel,\n} from \"@pie-players/pie-players-shared/types\";\n\n/**\n * Result from extracting SSML from markup\n */\ninterface MarkupExtractionResult {\n\tcatalogs: AccessibilityCatalog[];\n\tcleanedMarkup: string;\n}\n\n/**\n * Result from extracting SSML from entire item config\n */\nexport interface ExtractionResult {\n\tcatalogs: AccessibilityCatalog[];\n\tcleanedConfig: ConfigEntity;\n}\n\n/**\n * Allow-listed SSML elements (from the SSML 1.1 spec subset we forward to\n * TTS providers). Anything outside this list is unwrapped during\n * sanitization so we never leak author-supplied scripts, iframes, or\n * arbitrary HTML into the TTS pipeline.\n */\nconst SSML_ALLOWED_TAGS = new Set([\n\t\"speak\",\n\t\"break\",\n\t\"prosody\",\n\t\"emphasis\",\n\t\"say-as\",\n\t\"sub\",\n\t\"mark\",\n\t\"phoneme\",\n\t\"lang\",\n\t\"p\",\n\t\"s\",\n\t\"voice\",\n\t\"audio\",\n\t\"lexicon\",\n]);\n\n/**\n * Allow-listed attributes across SSML elements. Attribute allow-listing is\n * global (not per-tag) because the union is small and consistent across\n * providers.\n */\n/**\n * Tags whose content must be dropped entirely (rather than unwrapped to\n * preserve phrasing text). These are dangerous containers whose text\n * payload could be re-interpreted downstream.\n */\nconst SSML_DROP_FULLY_TAGS = new Set([\n\t\"script\",\n\t\"style\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"foreignobject\",\n\t\"noscript\",\n\t\"template\",\n]);\n\nconst SSML_ALLOWED_ATTRS = new Set([\n\t\"alphabet\",\n\t\"ph\",\n\t\"time\",\n\t\"strength\",\n\t\"rate\",\n\t\"pitch\",\n\t\"volume\",\n\t\"name\",\n\t\"interpret-as\",\n\t\"format\",\n\t\"detail\",\n\t\"language\",\n\t\"xml:lang\",\n\t\"lang\",\n\t\"gender\",\n\t\"src\",\n]);\n\n/**\n * Hosts that must never appear inside an SSML `<audio src>` because they\n * would cause the server-side TTS provider to fetch a private / metadata\n * endpoint on behalf of the request. Mirrors the SchoolCity provider's\n * defense-in-depth check; the SSML-side block is cheap and belt-and-braces.\n */\nconst SSML_PRIVATE_AUDIO_HOSTS: RegExp[] = [\n\t/^localhost$/i,\n\t/^127(?:\\.\\d{1,3}){3}$/,\n\t/^0(?:\\.\\d{1,3}){3}$/,\n\t/^10(?:\\.\\d{1,3}){3}$/,\n\t/^192\\.168(?:\\.\\d{1,3}){2}$/,\n\t/^172\\.(?:1[6-9]|2\\d|3[01])(?:\\.\\d{1,3}){2}$/,\n\t/^169\\.254(?:\\.\\d{1,3}){2}$/,\n\t/^::1$/,\n\t/^fe80:/i,\n\t/^fc00:/i,\n\t/^fd[0-9a-f]{2}:/i,\n\t/^metadata\\.google\\.internal$/i,\n\t/^metadata\\.azure\\.internal$/i,\n\t/^metadata\\.packet\\.net$/i,\n];\n\nfunction isSafeSsmlAudioSrc(raw: string): boolean {\n\tif (typeof raw !== \"string\" || raw.length === 0) return false;\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\treturn false;\n\t}\n\tconst hostname = parsed.hostname.replace(/^\\[|\\]$/g, \"\").replace(/\\.$/, \"\");\n\treturn !SSML_PRIVATE_AUDIO_HOSTS.some((pattern) => pattern.test(hostname));\n}\n\n/**\n * Sanitize an SSML subtree in place: remove disallowed attributes and\n * replace disallowed elements with their text content (so phrasing order\n * is preserved but no scripts / iframes / unknown tags survive).\n */\nfunction sanitizeSsmlElement(element: Element): void {\n\t// Walk children snapshot first because we may replace nodes as we go.\n\tconst children = Array.from(element.children);\n\tfor (const child of children) {\n\t\tconst tagName = child.tagName.toLowerCase();\n\t\tif (SSML_DROP_FULLY_TAGS.has(tagName)) {\n\t\t\tchild.remove();\n\t\t\tcontinue;\n\t\t}\n\t\tsanitizeSsmlElement(child);\n\t\tif (!SSML_ALLOWED_TAGS.has(tagName)) {\n\t\t\tconst textNode = element.ownerDocument.createTextNode(\n\t\t\t\tchild.textContent ?? \"\",\n\t\t\t);\n\t\t\tchild.replaceWith(textNode);\n\t\t}\n\t}\n\t// Strip disallowed attributes on the surviving node.\n\tconst attrNames = Array.from(element.attributes).map((attr) => attr.name);\n\tfor (const attrName of attrNames) {\n\t\tif (!SSML_ALLOWED_ATTRS.has(attrName.toLowerCase())) {\n\t\t\telement.removeAttribute(attrName);\n\t\t}\n\t}\n\t// Belt-and-braces: even though `src` is allow-listed, SSML `<audio>`\n\t// sends its URL to a server-side TTS provider that will dereference\n\t// it. Refuse non-http(s) schemes and private / cloud-metadata hosts\n\t// at the SSML boundary so we never forward an SSRF primitive.\n\tif (element.tagName.toLowerCase() === \"audio\") {\n\t\tconst src = element.getAttribute(\"src\");\n\t\tif (src !== null && !isSafeSsmlAudioSrc(src)) {\n\t\t\telement.removeAttribute(\"src\");\n\t\t}\n\t}\n}\n\n/**\n * Serialize a sanitized `<speak>` element back to a minimal SSML string.\n * Uses a manual walker instead of `outerHTML` so that any namespace\n * quirks (e.g. `xmlns` attributes added by the HTML parser) are\n * normalised and disallowed descendants cannot reappear through quirks\n * in the serializer.\n */\nfunction serializeSsmlElement(element: Element): string {\n\tconst tagName = element.tagName.toLowerCase();\n\tconst attrs = Array.from(element.attributes)\n\t\t.filter((attr) => SSML_ALLOWED_ATTRS.has(attr.name.toLowerCase()))\n\t\t.map((attr) => ` ${attr.name}=\"${escapeXmlAttr(attr.value)}\"`)\n\t\t.join(\"\");\n\tconst childPieces: string[] = [];\n\tfor (const node of Array.from(element.childNodes)) {\n\t\tif (node.nodeType === 3 /* text */) {\n\t\t\tchildPieces.push(escapeXmlText(node.nodeValue ?? \"\"));\n\t\t} else if (node.nodeType === 1 /* element */) {\n\t\t\tconst childElement = node as Element;\n\t\t\tconst childTag = childElement.tagName.toLowerCase();\n\t\t\tif (SSML_DROP_FULLY_TAGS.has(childTag)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (SSML_ALLOWED_TAGS.has(childTag)) {\n\t\t\t\tchildPieces.push(serializeSsmlElement(childElement));\n\t\t\t} else {\n\t\t\t\tchildPieces.push(escapeXmlText(childElement.textContent ?? \"\"));\n\t\t\t}\n\t\t}\n\t}\n\treturn `<${tagName}${attrs}>${childPieces.join(\"\")}</${tagName}>`;\n}\n\nfunction escapeXmlText(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\");\n}\n\nfunction escapeXmlAttr(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\");\n}\n\n/**\n * SSMLExtractor Service\n *\n * Extracts embedded SSML from PIE content and generates accessibility catalogs.\n */\nexport class SSMLExtractor {\n\tprivate catalogCounter = 0;\n\n\t/**\n\t * Extract SSML from entire item config (markup + models)\n\t *\n\t * @param config Item configuration with potential embedded SSML\n\t * @returns Extracted catalogs and cleaned config\n\t */\n\textractFromItemConfig(config: ConfigEntity): ExtractionResult {\n\t\tconst allCatalogs: AccessibilityCatalog[] = [];\n\n\t\t// Deep clone config to avoid mutating original\n\t\tconst cleanedConfig: ConfigEntity = {\n\t\t\t...config,\n\t\t\tmodels: config.models ? [...config.models] : [],\n\t\t};\n\n\t\t// 1. Extract from top-level markup (passages)\n\t\tif (config.markup) {\n\t\t\tconst markupResult = this.extractFromMarkup(config.markup, \"markup\");\n\t\t\tallCatalogs.push(...markupResult.catalogs);\n\t\t\tcleanedConfig.markup = markupResult.cleanedMarkup;\n\t\t}\n\n\t\t// 2. Extract from models (prompts, choices, etc.)\n\t\tif (config.models && config.models.length > 0) {\n\t\t\tcleanedConfig.models = config.models.map((model) => {\n\t\t\t\tconst cleanedModel = { ...model };\n\t\t\t\tconst modelCatalogs: AccessibilityCatalog[] = [];\n\n\t\t\t\t// Extract from prompt\n\t\t\t\tif (model.prompt && typeof model.prompt === \"string\") {\n\t\t\t\t\tconst promptResult = this.extractFromMarkup(\n\t\t\t\t\t\tmodel.prompt,\n\t\t\t\t\t\t`prompt-${model.id}`,\n\t\t\t\t\t);\n\t\t\t\t\tmodelCatalogs.push(...promptResult.catalogs);\n\t\t\t\t\tcleanedModel.prompt = promptResult.cleanedMarkup;\n\t\t\t\t}\n\n\t\t\t\t// Extract from choices\n\t\t\t\tif (model.choices && Array.isArray(model.choices)) {\n\t\t\t\t\tcleanedModel.choices = model.choices.map((choice) => {\n\t\t\t\t\t\tif (choice.label && typeof choice.label === \"string\") {\n\t\t\t\t\t\t\tconst choiceResult = this.extractFromMarkup(\n\t\t\t\t\t\t\t\tchoice.label,\n\t\t\t\t\t\t\t\t`choice-${model.id}-${choice.value}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tmodelCatalogs.push(...choiceResult.catalogs);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t...choice,\n\t\t\t\t\t\t\t\tlabel: choiceResult.cleanedMarkup,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn choice;\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tallCatalogs.push(...modelCatalogs);\n\t\t\t\treturn cleanedModel;\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tcatalogs: allCatalogs,\n\t\t\tcleanedConfig,\n\t\t};\n\t}\n\n\t/**\n\t * Extract SSML from markup string\n\t *\n\t * @param markup HTML markup potentially containing <speak> elements\n\t * @param idPrefix Prefix for generating catalog IDs\n\t * @returns Extracted catalogs and cleaned markup\n\t */\n\tprivate extractFromMarkup(\n\t\tmarkup: string,\n\t\tidPrefix: string,\n\t): MarkupExtractionResult {\n\t\tif (!markup || markup.trim() === \"\") {\n\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t}\n\n\t\t// Check if running in browser environment\n\t\tif (typeof window === \"undefined\" || typeof DOMParser === \"undefined\") {\n\t\t\tconsole.warn(\n\t\t\t\t\"[SSMLExtractor] DOMParser not available (SSR?), skipping extraction\",\n\t\t\t);\n\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t}\n\n\t\tconst catalogs: AccessibilityCatalog[] = [];\n\n\t\ttry {\n\t\t\tconst parser = new DOMParser();\n\t\t\tconst doc = parser.parseFromString(markup, \"text/html\");\n\n\t\t\t// Find all <speak> elements\n\t\t\tconst speakElements = Array.from(doc.querySelectorAll(\"speak\"));\n\n\t\t\tif (speakElements.length === 0) {\n\t\t\t\t// No SSML found, return original markup\n\t\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t\t}\n\n\t\t\t// Process each <speak> element\n\t\t\tfor (const speakEl of speakElements) {\n\t\t\t\ttry {\n\t\t\t\t\t// Generate unique catalog ID\n\t\t\t\t\tconst catalogId = this.generateCatalogId(idPrefix);\n\n\t\t\t\t\t// Sanitize before serializing so the SSML forwarded to the TTS\n\t\t\t\t\t// provider only contains spec-allowed tags/attributes. This\n\t\t\t\t\t// closes an author-content -> TTS-server pipeline that would\n\t\t\t\t\t// otherwise accept arbitrary HTML.\n\t\t\t\t\tsanitizeSsmlElement(speakEl);\n\t\t\t\t\tconst ssmlContent = serializeSsmlElement(speakEl);\n\n\t\t\t\t\t// Extract language (check both xml:lang and lang attributes)\n\t\t\t\t\tconst language =\n\t\t\t\t\t\tspeakEl.getAttribute(\"xml:lang\") ||\n\t\t\t\t\t\tspeakEl.getAttribute(\"lang\") ||\n\t\t\t\t\t\t\"en-US\";\n\n\t\t\t\t\t// Get plain text for visual display\n\t\t\t\t\tconst plainText = speakEl.textContent || \"\";\n\n\t\t\t\t\t// Find parent element or create wrapper\n\t\t\t\t\tlet wrapper = speakEl.parentElement;\n\n\t\t\t\t\tif (!wrapper || wrapper.tagName === \"BODY\") {\n\t\t\t\t\t\t// SPEAK is at root level - create span wrapper\n\t\t\t\t\t\tconst span = doc.createElement(\"span\");\n\t\t\t\t\t\tspeakEl.parentNode?.insertBefore(span, speakEl);\n\t\t\t\t\t\tspan.appendChild(doc.createTextNode(plainText));\n\t\t\t\t\t\twrapper = span;\n\t\t\t\t\t\tspeakEl.remove();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Just remove <speak> - visual content should be in sibling element\n\t\t\t\t\t\tspeakEl.remove();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Add catalog ID to wrapper element\n\t\t\t\t\tif (wrapper) {\n\t\t\t\t\t\twrapper.setAttribute(\"data-catalog-id\", catalogId);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create catalog entry\n\t\t\t\t\tcatalogs.push({\n\t\t\t\t\t\tidentifier: catalogId,\n\t\t\t\t\t\tcards: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcatalog: \"spoken\",\n\t\t\t\t\t\t\t\tlanguage,\n\t\t\t\t\t\t\t\tcontent: ssmlContent,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"[SSMLExtractor] Error processing <speak> element:\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t\t// Continue with other elements\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Return cleaned markup\n\t\t\tconst cleanedMarkup = doc.body.innerHTML;\n\n\t\t\treturn { catalogs, cleanedMarkup };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[SSMLExtractor] Error parsing markup:\", error);\n\t\t\t// Return original markup if parsing fails\n\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t}\n\t}\n\n\t/**\n\t * Generate unique catalog ID\n\t *\n\t * @param prefix Context prefix (e.g., 'prompt-q1', 'choice-q1-a')\n\t * @returns Unique catalog ID (e.g., 'auto-prompt-q1-0')\n\t */\n\tprivate generateCatalogId(prefix: string): string {\n\t\tconst id = `auto-${prefix}-${this.catalogCounter}`;\n\t\tthis.catalogCounter++;\n\t\treturn id;\n\t}\n\n\t/**\n\t * Reset counter (useful for testing or new extraction contexts)\n\t */\n\treset(): void {\n\t\tthis.catalogCounter = 0;\n\t}\n}\n"]}
1
+ {"version":3,"file":"SSMLExtractor.js","sourceRoot":"","sources":["../../src/services/SSMLExtractor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * SSMLExtractor\n *\n * Service for extracting embedded SSML from PIE item content and converting\n * it into QTI 3.0 accessibility catalogs.\n *\n * Authors can embed <speak> SSML tags directly in content for convenience.\n * The extractor automatically:\n * 1. Parses markup and finds all <speak> elements\n * 2. Extracts SSML content with language metadata\n * 3. Generates accessibility catalogs with unique IDs\n * 4. Cleans visual markup (removes SSML, adds data-catalog-idref)\n *\n * This enables proper pronunciation, emphasis, and pacing for TTS without\n * requiring authors to maintain separate catalog files.\n *\n * Part of PIE Assessment Toolkit.\n */\n\nimport type {\n\tAccessibilityCatalog,\n\tConfigEntity,\n\tPieModel,\n} from \"@pie-players/pie-players-shared/types\";\n\n/**\n * Result from extracting SSML from markup\n */\ninterface MarkupExtractionResult {\n\tcatalogs: AccessibilityCatalog[];\n\tcleanedMarkup: string;\n}\n\n/**\n * Result from extracting SSML from entire item config\n */\nexport interface ExtractionResult {\n\tcatalogs: AccessibilityCatalog[];\n\tcleanedConfig: ConfigEntity;\n}\n\n/**\n * Allow-listed SSML elements (from the SSML 1.1 spec subset we forward to\n * TTS providers). Anything outside this list is unwrapped during\n * sanitization so we never leak author-supplied scripts, iframes, or\n * arbitrary HTML into the TTS pipeline.\n */\nconst SSML_ALLOWED_TAGS = new Set([\n\t\"speak\",\n\t\"break\",\n\t\"prosody\",\n\t\"emphasis\",\n\t\"say-as\",\n\t\"sub\",\n\t\"mark\",\n\t\"phoneme\",\n\t\"lang\",\n\t\"p\",\n\t\"s\",\n\t\"voice\",\n\t\"audio\",\n\t\"lexicon\",\n]);\n\n/**\n * Allow-listed attributes across SSML elements. Attribute allow-listing is\n * global (not per-tag) because the union is small and consistent across\n * providers.\n */\n/**\n * Tags whose content must be dropped entirely (rather than unwrapped to\n * preserve phrasing text). These are dangerous containers whose text\n * payload could be re-interpreted downstream.\n */\nconst SSML_DROP_FULLY_TAGS = new Set([\n\t\"script\",\n\t\"style\",\n\t\"iframe\",\n\t\"object\",\n\t\"embed\",\n\t\"foreignobject\",\n\t\"noscript\",\n\t\"template\",\n]);\n\nconst SSML_ALLOWED_ATTRS = new Set([\n\t\"alphabet\",\n\t\"ph\",\n\t\"time\",\n\t\"strength\",\n\t\"rate\",\n\t\"pitch\",\n\t\"volume\",\n\t\"name\",\n\t\"interpret-as\",\n\t\"format\",\n\t\"detail\",\n\t\"language\",\n\t\"xml:lang\",\n\t\"lang\",\n\t\"gender\",\n\t\"src\",\n]);\n\n/**\n * SSML elements that are empty in practice (no child content). Authors\n * typically write them as self-closing (`<break time=\"200ms\"/>`). HTML's\n * parser does NOT recognize them as void elements and silently drops the\n * `/`, then nests every subsequent sibling as a child — producing\n * malformed SSML like `<break>…rest of paragraph…</break>` when\n * round-tripped. Pre-processing self-closing forms to explicit\n * open+close pairs before HTML parsing avoids the reflow, and the\n * serializer re-emits them as self-closing so providers see valid SSML.\n *\n * `audio` is intentionally excluded — SSML <audio> may carry fallback\n * children (the spec's \"alternate content\"), so treating it as void\n * would lose author intent.\n */\nconst SSML_VOID_TAGS = new Set([\"break\", \"mark\", \"lexicon\"]);\n\n/**\n * Normalize self-closing forms of SSML void elements to explicit\n * open+close pairs BEFORE handing the string to an HTML parser. The\n * HTML5 parser ignores the `/` on non-void tags, so `<break/>` becomes\n * a plain `<break>` open tag that swallows every following sibling.\n * Rewriting to `<break></break>` preserves the empty-element shape\n * across the HTML round-trip.\n */\nfunction expandSelfClosingVoidTags(input: string): string {\n\tlet next = input;\n\tfor (const tag of SSML_VOID_TAGS) {\n\t\tconst selfClosing = new RegExp(`<${tag}\\\\b([^>/]*?)\\\\s*/>`, \"gi\");\n\t\tnext = next.replace(selfClosing, (_match, attrs: string) => {\n\t\t\tconst attrText = attrs ? attrs.replace(/\\s+$/, \"\") : \"\";\n\t\t\treturn `<${tag}${attrText}></${tag}>`;\n\t\t});\n\t}\n\treturn next;\n}\n\n/**\n * Hosts that must never appear inside an SSML `<audio src>` because they\n * would cause the server-side TTS provider to fetch a private / metadata\n * endpoint on behalf of the request. Mirrors the SchoolCity provider's\n * defense-in-depth check; the SSML-side block is cheap and belt-and-braces.\n */\nconst SSML_PRIVATE_AUDIO_HOSTS: RegExp[] = [\n\t/^localhost$/i,\n\t/^127(?:\\.\\d{1,3}){3}$/,\n\t/^0(?:\\.\\d{1,3}){3}$/,\n\t/^10(?:\\.\\d{1,3}){3}$/,\n\t/^192\\.168(?:\\.\\d{1,3}){2}$/,\n\t/^172\\.(?:1[6-9]|2\\d|3[01])(?:\\.\\d{1,3}){2}$/,\n\t/^169\\.254(?:\\.\\d{1,3}){2}$/,\n\t/^::1$/,\n\t/^fe80:/i,\n\t/^fc00:/i,\n\t/^fd[0-9a-f]{2}:/i,\n\t/^metadata\\.google\\.internal$/i,\n\t/^metadata\\.azure\\.internal$/i,\n\t/^metadata\\.packet\\.net$/i,\n];\n\nfunction isSafeSsmlAudioSrc(raw: string): boolean {\n\tif (typeof raw !== \"string\" || raw.length === 0) return false;\n\tlet parsed: URL;\n\ttry {\n\t\tparsed = new URL(raw);\n\t} catch {\n\t\treturn false;\n\t}\n\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n\t\treturn false;\n\t}\n\tconst hostname = parsed.hostname.replace(/^\\[|\\]$/g, \"\").replace(/\\.$/, \"\");\n\treturn !SSML_PRIVATE_AUDIO_HOSTS.some((pattern) => pattern.test(hostname));\n}\n\n/**\n * Sanitize an SSML subtree in place: remove disallowed attributes and\n * replace disallowed elements with their text content (so phrasing order\n * is preserved but no scripts / iframes / unknown tags survive).\n */\nexport function sanitizeSsmlElement(element: Element): void {\n\t// Walk children snapshot first because we may replace nodes as we go.\n\tconst children = Array.from(element.children);\n\tfor (const child of children) {\n\t\tconst tagName = child.tagName.toLowerCase();\n\t\tif (SSML_DROP_FULLY_TAGS.has(tagName)) {\n\t\t\tchild.remove();\n\t\t\tcontinue;\n\t\t}\n\t\tsanitizeSsmlElement(child);\n\t\tif (!SSML_ALLOWED_TAGS.has(tagName)) {\n\t\t\tconst textNode = element.ownerDocument.createTextNode(\n\t\t\t\tchild.textContent ?? \"\",\n\t\t\t);\n\t\t\tchild.replaceWith(textNode);\n\t\t}\n\t}\n\t// Strip disallowed attributes on the surviving node.\n\tconst attrNames = Array.from(element.attributes).map((attr) => attr.name);\n\tfor (const attrName of attrNames) {\n\t\tif (!SSML_ALLOWED_ATTRS.has(attrName.toLowerCase())) {\n\t\t\telement.removeAttribute(attrName);\n\t\t}\n\t}\n\t// Belt-and-braces: even though `src` is allow-listed, SSML `<audio>`\n\t// sends its URL to a server-side TTS provider that will dereference\n\t// it. Refuse non-http(s) schemes and private / cloud-metadata hosts\n\t// at the SSML boundary so we never forward an SSRF primitive.\n\tif (element.tagName.toLowerCase() === \"audio\") {\n\t\tconst src = element.getAttribute(\"src\");\n\t\tif (src !== null && !isSafeSsmlAudioSrc(src)) {\n\t\t\telement.removeAttribute(\"src\");\n\t\t}\n\t}\n}\n\n/**\n * Serialize a sanitized `<speak>` element back to a minimal SSML string.\n * Uses a manual walker instead of `outerHTML` so that any namespace\n * quirks (e.g. `xmlns` attributes added by the HTML parser) are\n * normalised and disallowed descendants cannot reappear through quirks\n * in the serializer.\n */\nexport function serializeSsmlElement(element: Element): string {\n\tconst tagName = element.tagName.toLowerCase();\n\tconst attrs = Array.from(element.attributes)\n\t\t.filter((attr) => SSML_ALLOWED_ATTRS.has(attr.name.toLowerCase()))\n\t\t.map((attr) => ` ${attr.name}=\"${escapeXmlAttr(attr.value)}\"`)\n\t\t.join(\"\");\n\t// SSML void elements (e.g. <break>, <mark>) must be emitted as\n\t// self-closing so providers like AWS Polly accept them. Emitting\n\t// `<break></break>` parses fine on the SSML side but is wasteful;\n\t// emitting `<break>` (open without close) is invalid SSML.\n\tif (SSML_VOID_TAGS.has(tagName)) {\n\t\treturn `<${tagName}${attrs}/>`;\n\t}\n\tconst childPieces: string[] = [];\n\tfor (const node of Array.from(element.childNodes)) {\n\t\tif (node.nodeType === 3 /* text */) {\n\t\t\tchildPieces.push(escapeXmlText(node.nodeValue ?? \"\"));\n\t\t} else if (node.nodeType === 1 /* element */) {\n\t\t\tconst childElement = node as Element;\n\t\t\tconst childTag = childElement.tagName.toLowerCase();\n\t\t\tif (SSML_DROP_FULLY_TAGS.has(childTag)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (SSML_ALLOWED_TAGS.has(childTag)) {\n\t\t\t\tchildPieces.push(serializeSsmlElement(childElement));\n\t\t\t} else {\n\t\t\t\tchildPieces.push(escapeXmlText(childElement.textContent ?? \"\"));\n\t\t\t}\n\t\t}\n\t}\n\treturn `<${tagName}${attrs}>${childPieces.join(\"\")}</${tagName}>`;\n}\n\nexport function sanitizeSsmlString(input: string): string {\n\tconst trimmed = String(input || \"\").trim();\n\tif (!trimmed || !trimmed.includes(\"<\")) return input;\n\tif (typeof DOMParser === \"undefined\") return input;\n\tconst parser = new DOMParser();\n\tconst doc = parser.parseFromString(expandSelfClosingVoidTags(trimmed), \"text/html\");\n\tconst speakElement = doc.querySelector(\"speak\");\n\tif (speakElement) {\n\t\tsanitizeSsmlElement(speakElement);\n\t\treturn serializeSsmlElement(speakElement);\n\t}\n\tconst wrapper = doc.createElement(\"speak\");\n\tfor (const node of Array.from(doc.body.childNodes)) {\n\t\twrapper.appendChild(node);\n\t}\n\tsanitizeSsmlElement(wrapper);\n\treturn Array.from(wrapper.childNodes)\n\t\t.map((node) => {\n\t\t\tif (node.nodeType === 3 /* text */) {\n\t\t\t\treturn escapeXmlText(node.nodeValue ?? \"\");\n\t\t\t}\n\t\t\tif (node.nodeType === 1 /* element */) {\n\t\t\t\tconst child = node as Element;\n\t\t\t\tif (SSML_ALLOWED_TAGS.has(child.tagName.toLowerCase())) {\n\t\t\t\t\treturn serializeSsmlElement(child);\n\t\t\t\t}\n\t\t\t\treturn escapeXmlText(child.textContent ?? \"\");\n\t\t\t}\n\t\t\treturn \"\";\n\t\t})\n\t\t.join(\"\");\n}\n\nfunction escapeXmlText(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\");\n}\n\nfunction escapeXmlAttr(value: string): string {\n\treturn value\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\");\n}\n\n/**\n * SSMLExtractor Service\n *\n * Extracts embedded SSML from PIE content and generates accessibility catalogs.\n */\nexport class SSMLExtractor {\n\tprivate catalogCounter = 0;\n\n\t/**\n\t * Extract SSML from entire item config (markup + models)\n\t *\n\t * @param config Item configuration with potential embedded SSML\n\t * @returns Extracted catalogs and cleaned config\n\t */\n\textractFromItemConfig(config: ConfigEntity): ExtractionResult {\n\t\tconst allCatalogs: AccessibilityCatalog[] = [];\n\n\t\t// Shallow clone of config + a fresh models array. The per-model/choice\n\t\t// objects are replaced (not mutated in place) by the `.map` below, so the\n\t\t// caller's original config and its models are never mutated.\n\t\tconst cleanedConfig: ConfigEntity = {\n\t\t\t...config,\n\t\t\tmodels: config.models ? [...config.models] : [],\n\t\t};\n\n\t\t// 1. Extract from top-level markup (passages)\n\t\tif (config.markup) {\n\t\t\tconst markupResult = this.extractFromMarkup(config.markup, \"markup\");\n\t\t\tallCatalogs.push(...markupResult.catalogs);\n\t\t\tcleanedConfig.markup = markupResult.cleanedMarkup;\n\t\t}\n\n\t\t// 2. Extract from models (prompts, choices, etc.)\n\t\tif (config.models && config.models.length > 0) {\n\t\t\tcleanedConfig.models = config.models.map((model) => {\n\t\t\t\tconst cleanedModel = { ...model };\n\t\t\t\tconst modelCatalogs: AccessibilityCatalog[] = [];\n\n\t\t\t\t// Extract from prompt\n\t\t\t\tif (model.prompt && typeof model.prompt === \"string\") {\n\t\t\t\t\tconst promptResult = this.extractFromMarkup(\n\t\t\t\t\t\tmodel.prompt,\n\t\t\t\t\t\t`prompt-${model.id}`,\n\t\t\t\t\t);\n\t\t\t\t\tmodelCatalogs.push(...promptResult.catalogs);\n\t\t\t\t\tcleanedModel.prompt = promptResult.cleanedMarkup;\n\t\t\t\t}\n\n\t\t\t\t// Extract from choices\n\t\t\t\tif (model.choices && Array.isArray(model.choices)) {\n\t\t\t\t\tcleanedModel.choices = model.choices.map((choice) => {\n\t\t\t\t\t\tif (choice.label && typeof choice.label === \"string\") {\n\t\t\t\t\t\t\tconst choiceResult = this.extractFromMarkup(\n\t\t\t\t\t\t\t\tchoice.label,\n\t\t\t\t\t\t\t\t`choice-${model.id}-${choice.value}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tmodelCatalogs.push(...choiceResult.catalogs);\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\t...choice,\n\t\t\t\t\t\t\t\tlabel: choiceResult.cleanedMarkup,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn choice;\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tallCatalogs.push(...modelCatalogs);\n\t\t\t\treturn cleanedModel;\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tcatalogs: allCatalogs,\n\t\t\tcleanedConfig,\n\t\t};\n\t}\n\n\t/**\n\t * Extract SSML from markup string\n\t *\n\t * @param markup HTML markup potentially containing <speak> elements\n\t * @param idPrefix Prefix for generating catalog IDs\n\t * @returns Extracted catalogs and cleaned markup\n\t */\n\tprivate extractFromMarkup(\n\t\tmarkup: string,\n\t\tidPrefix: string,\n\t): MarkupExtractionResult {\n\t\tif (!markup || markup.trim() === \"\") {\n\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t}\n\n\t\t// Check if running in browser environment\n\t\tif (typeof window === \"undefined\" || typeof DOMParser === \"undefined\") {\n\t\t\tconsole.warn(\n\t\t\t\t\"[SSMLExtractor] DOMParser not available (SSR?), skipping extraction\",\n\t\t\t);\n\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t}\n\n\t\tconst catalogs: AccessibilityCatalog[] = [];\n\n\t\ttry {\n\t\t\tconst parser = new DOMParser();\n\t\t\tconst doc = parser.parseFromString(\n\t\t\t\texpandSelfClosingVoidTags(markup),\n\t\t\t\t\"text/html\",\n\t\t\t);\n\n\t\t\t// Find all <speak> elements\n\t\t\tconst speakElements = Array.from(doc.querySelectorAll(\"speak\"));\n\n\t\t\tif (speakElements.length === 0) {\n\t\t\t\t// No SSML found, return original markup\n\t\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t\t}\n\n\t\t\t// Process each <speak> element\n\t\t\tfor (const speakEl of speakElements) {\n\t\t\t\ttry {\n\t\t\t\t\t// Generate unique catalog ID\n\t\t\t\t\tconst catalogId = this.generateCatalogId(idPrefix);\n\n\t\t\t\t\t// Sanitize before serializing so the SSML forwarded to the TTS\n\t\t\t\t\t// provider only contains spec-allowed tags/attributes. This\n\t\t\t\t\t// closes an author-content -> TTS-server pipeline that would\n\t\t\t\t\t// otherwise accept arbitrary HTML.\n\t\t\t\t\tsanitizeSsmlElement(speakEl);\n\t\t\t\t\tconst ssmlContent = serializeSsmlElement(speakEl);\n\n\t\t\t\t\t// Extract language (check both xml:lang and lang attributes)\n\t\t\t\t\tconst language =\n\t\t\t\t\t\tspeakEl.getAttribute(\"xml:lang\") ||\n\t\t\t\t\t\tspeakEl.getAttribute(\"lang\") ||\n\t\t\t\t\t\t\"en-US\";\n\n\t\t\t\t\t// Get plain text for visual display\n\t\t\t\t\tconst plainText = speakEl.textContent || \"\";\n\n\t\t\t\t\t// Find parent element or create wrapper\n\t\t\t\t\tlet wrapper = speakEl.parentElement;\n\n\t\t\t\t\tif (!wrapper || wrapper.tagName === \"BODY\") {\n\t\t\t\t\t\t// SPEAK is at root level - create span wrapper\n\t\t\t\t\t\tconst span = doc.createElement(\"span\");\n\t\t\t\t\t\tspeakEl.parentNode?.insertBefore(span, speakEl);\n\t\t\t\t\t\tspan.appendChild(doc.createTextNode(plainText));\n\t\t\t\t\t\twrapper = span;\n\t\t\t\t\t\tspeakEl.remove();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Just remove <speak> - visual content should be in sibling element\n\t\t\t\t\t\tspeakEl.remove();\n\t\t\t\t\t}\n\n\t\t\t\t\t// Tag the wrapper with the QTI-style catalog reference so the\n\t\t\t\t\t// runtime can resolve this region's spoken content. Same attribute\n\t\t\t\t\t// authored content uses (`data-catalog-idref`) — one canonical name.\n\t\t\t\t\tif (wrapper) {\n\t\t\t\t\t\twrapper.setAttribute(\"data-catalog-idref\", catalogId);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create catalog entry\n\t\t\t\t\tcatalogs.push({\n\t\t\t\t\t\tidentifier: catalogId,\n\t\t\t\t\t\tcards: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcatalog: \"spoken\",\n\t\t\t\t\t\t\t\tlanguage,\n\t\t\t\t\t\t\t\tcontent: ssmlContent,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\"[SSMLExtractor] Error processing <speak> element:\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t\t// Continue with other elements\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Return cleaned markup\n\t\t\tconst cleanedMarkup = doc.body.innerHTML;\n\n\t\t\treturn { catalogs, cleanedMarkup };\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[SSMLExtractor] Error parsing markup:\", error);\n\t\t\t// Return original markup if parsing fails\n\t\t\treturn { catalogs: [], cleanedMarkup: markup };\n\t\t}\n\t}\n\n\t/**\n\t * Generate unique catalog ID\n\t *\n\t * @param prefix Context prefix (e.g., 'prompt-q1', 'choice-q1-a')\n\t * @returns Unique catalog ID (e.g., 'auto-prompt-q1-0')\n\t */\n\tprivate generateCatalogId(prefix: string): string {\n\t\tconst id = `auto-${prefix}-${this.catalogCounter}`;\n\t\tthis.catalogCounter++;\n\t\treturn id;\n\t}\n\n\t/**\n\t * Reset counter (useful for testing or new extraction contexts)\n\t */\n\treset(): void {\n\t\tthis.catalogCounter = 0;\n\t}\n}\n"]}
@@ -16,7 +16,7 @@
16
16
  * Part of PIE Assessment Toolkit.
17
17
  */
18
18
  import type { ITTSProvider, TTSConfig, TTSProviderCapabilities } from "@pie-players/pie-tts";
19
- import type { AccessibilityCatalogResolver } from "./AccessibilityCatalogResolver.js";
19
+ import type { AccessibilityCatalogResolver, CatalogLookupContext } from "./AccessibilityCatalogResolver.js";
20
20
  import type { HighlightCoordinatorApi } from "./interfaces.js";
21
21
  export type { ITTSProvider, ITTSProviderImplementation, TTSConfig, TTSFeature, TTSProviderCapabilities, } from "@pie-players/pie-tts";
22
22
  /**
@@ -32,6 +32,8 @@ export declare enum PlaybackState {
32
32
  type HighlightMode = "word" | "sentence";
33
33
  interface SpeakOptions {
34
34
  catalogId?: string;
35
+ catalogContext?: CatalogLookupContext;
36
+ ignoreCatalogs?: boolean;
35
37
  language?: string;
36
38
  contentElement?: Element;
37
39
  wordBoundaryOffset?: number;
@@ -62,8 +64,12 @@ export declare class TTSService {
62
64
  private currentSeekSegmentIndex;
63
65
  private activeSentenceStartOffset;
64
66
  private activeHighlightMode;
67
+ private lastRenderedRegionTarget;
65
68
  private telemetryReporter;
69
+ private readonly generatedMathSpeechResolver;
70
+ private static readonly debugEnabled;
66
71
  constructor();
72
+ private debugLog;
67
73
  private emitTelemetry;
68
74
  /**
69
75
  * Initialize TTS service with a provider
@@ -103,13 +109,13 @@ export declare class TTSService {
103
109
  * This handles the complexity of whitespace normalization
104
110
  */
105
111
  private getTextProcessingOptions;
112
+ private getMathSpeechOptions;
106
113
  private buildPositionMap;
107
114
  private getStructuralPauseProfile;
108
115
  private resolvePauseMsFromUnits;
109
116
  private resolveHighlightMode;
110
117
  private hasExplicitBreakSemantics;
111
118
  private isElementHidden;
112
- private extractVisibleText;
113
119
  private getBoundaryStrength;
114
120
  private getBoundaryAnchor;
115
121
  private createSpeechPlan;
@@ -140,16 +146,36 @@ export declare class TTSService {
140
146
  * @param options Optional catalog ID, language, and content element for highlighting
141
147
  */
142
148
  speak(text: string, options?: SpeakOptions): Promise<void>;
143
- private shouldAttemptBrowserFallback;
144
149
  private switchProviderToBrowser;
145
150
  private isBrowserSpeechFallbackAvailable;
146
- private retryWithBrowserFallback;
147
151
  private applyLanguageSettings;
148
152
  private resolveSpeechContent;
153
+ private resolveCatalogComposedSpeechContent;
154
+ private collectCatalogSpeechChunks;
155
+ /**
156
+ * Decide the playback format for the generated (no authored catalog) math
157
+ * path. SSML is only emitted to providers that report `supportsSSML`: the
158
+ * browser Web Speech API speaks tags literally (capability `false`), and the
159
+ * server bridge reports `true` only for SSML-reliable backends (Polly,
160
+ * Google) — the `custom`/SchoolCity transport stays plain.
161
+ *
162
+ * A speak-time plain fallback (`SpeechCompositionChunk.plainFallback`) is the
163
+ * defensive net if a capable provider still rejects a given SSML payload.
164
+ */
165
+ private resolveGeneratedPlaybackFormat;
166
+ private resolveGeneratedSpeechContent;
149
167
  private logResolvedSpeechContent;
150
168
  private initializeSpeakTracking;
151
169
  private prepareHighlightsForSpeak;
152
170
  private configureWordBoundaryHighlighting;
171
+ private clearWordBoundaryHighlighting;
172
+ private highlightCatalogRegion;
173
+ private highlightCatalogActiveRange;
174
+ private highlightRenderableRegionTarget;
175
+ private highlightRenderableActiveTarget;
176
+ private renderHighlightDecision;
177
+ private speakCatalogChunk;
178
+ private speakCatalogChunkOnce;
153
179
  private executeSpeakPlayback;
154
180
  private clearHighlightsAndTracking;
155
181
  /**