react-native-marcus 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -63
- package/RNMarcus.podspec +6 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusPackage.kt +2 -1
- package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorView.kt +168 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManager.kt +38 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusTextDecoratorViewManagerSpec.kt +19 -0
- package/android/src/main/java/app/getbullet/marcus/MarcusTextInputDecoratorView.kt +23 -1
- package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPainter.kt +147 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownBackgroundPass.kt +19 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownCopy.kt +87 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownFormatter.kt +652 -113
- package/android/src/main/java/app/getbullet/marcus/MarkdownRanges.kt +47 -0
- package/android/src/main/java/app/getbullet/marcus/MarkdownStyle.kt +140 -61
- package/android/src/main/java/app/getbullet/marcus/MarkdownUtils.kt +37 -7
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownAbsoluteLineHeightSpan.kt +48 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundSpan.kt +102 -48
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBlockIndentSpan.kt +22 -4
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBulletSpan.kt +78 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpan.kt +177 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontStyleSpan.kt +32 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownFontWeightSpan.kt +34 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownGapSpan.kt +24 -11
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownLetterSpacingSpan.kt +32 -0
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownTextShadowSpan.kt +32 -0
- package/android/src/main/new_arch/CMakeLists.txt +17 -0
- package/android/src/main/new_arch/RNMarcusSpec.h +1 -0
- package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.cpp +235 -0
- package/android/src/main/new_arch/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorShadowNode.h +62 -0
- package/android/src/reactNativeVersionPatch/CustomMountingManager/81/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
- package/android/src/reactNativeVersionPatch/CustomMountingManager/latest/app/getbullet/marcus/CustomFabricUIManager.java +22 -3
- package/android/src/test/java/app/getbullet/marcus/MarkdownCopyTest.kt +69 -0
- package/android/src/test/java/app/getbullet/marcus/MarkdownFormatterTest.kt +170 -25
- package/android/src/test/java/app/getbullet/marcus/RenderModel.kt +22 -5
- package/android/src/test/java/app/getbullet/marcus/spans/MarkdownCodeBlockSpanTest.kt +91 -0
- package/android/src/test/resources/render-model.txt +221 -16
- package/apple/MarcusSwizzle.h +25 -0
- package/apple/MarcusTextDecoratorComponentView.h +22 -0
- package/apple/MarcusTextDecoratorComponentView.mm +72 -0
- package/apple/MarcusTextDecoratorShadowNode.h +62 -0
- package/apple/MarcusTextDecoratorShadowNode.mm +192 -0
- package/apple/MarcusTextDecoratorViewManager.h +4 -0
- package/apple/MarcusTextDecoratorViewManager.mm +11 -0
- package/apple/MarcusTextInputDecoratorComponentView.mm +1 -1
- package/apple/MarcusTextInputDecoratorShadowNode.mm +1 -1
- package/apple/MarcusTextLayoutManager.h +40 -0
- package/apple/MarcusTextLayoutManager.mm +173 -0
- package/apple/MarkdownAttributes.h +23 -2
- package/apple/MarkdownAttributes.m +6 -0
- package/apple/MarkdownCopy.swift +63 -0
- package/apple/MarkdownDecorator.swift +2 -2
- package/apple/MarkdownFormatter+React.swift +23 -0
- package/apple/MarkdownFormatter.swift +869 -148
- package/apple/MarkdownParagraphLayoutManager.swift +455 -0
- package/apple/MarkdownSwiftInterop.h +0 -1
- package/apple/MarkdownTextBackground.swift +59 -1
- package/apple/MarkdownTextFieldObserver.swift +2 -2
- package/apple/MarkdownTextLayoutFragment.swift +159 -33
- package/apple/MarkdownTextLayoutManagerDelegate.swift +67 -3
- package/apple/MarkdownTextStorageDelegate.swift +2 -2
- package/apple/MarkdownTextViewObserver.swift +2 -2
- package/apple/MarkdownUtils.swift +38 -0
- package/apple/MarkdownWritingDirection.swift +132 -0
- package/apple/RCTMarcusStyle+Codegen.h +115 -9
- package/apple/RCTMarcusStyle.h +128 -31
- package/apple/RCTMarcusStyle.m +48 -0
- package/apple/RCTParagraphComponentView+Marcus.h +17 -0
- package/apple/RCTParagraphComponentView+Marcus.mm +38 -0
- package/apple/RCTTextInputComponentView+Marcus.mm +1 -25
- package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorState.h +37 -0
- package/cpp/react/renderer/components/RNMarcusSpec/MarcusTextDecoratorViewComponentDescriptor.h +17 -0
- package/lib/commonjs/MarcusTextDecoratorViewNativeComponent.ts +199 -0
- package/lib/commonjs/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
- package/lib/commonjs/MarkdownText.js +330 -0
- package/lib/commonjs/MarkdownText.js.map +1 -0
- package/lib/commonjs/MarkdownText.web.js +529 -0
- package/lib/commonjs/MarkdownText.web.js.map +1 -0
- package/lib/commonjs/MarkdownTextInput.js +22 -27
- package/lib/commonjs/MarkdownTextInput.js.map +1 -1
- package/lib/commonjs/MarkdownTextInput.web.js +12 -7
- package/lib/commonjs/MarkdownTextInput.web.js.map +1 -1
- package/lib/commonjs/atomUtils.js +112 -0
- package/lib/commonjs/atomUtils.js.map +1 -0
- package/lib/commonjs/childrenUtils.js +36 -0
- package/lib/commonjs/childrenUtils.js.map +1 -0
- package/lib/commonjs/index.js +8 -7
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/math/environment.js +69 -0
- package/lib/commonjs/math/environment.js.map +1 -0
- package/lib/commonjs/math/index.js +61 -0
- package/lib/commonjs/math/index.js.map +1 -0
- package/lib/commonjs/math/mathjax.js +548 -0
- package/lib/commonjs/math/mathjax.js.map +1 -0
- package/lib/commonjs/metro.js +172 -0
- package/lib/commonjs/metro.js.map +1 -0
- package/lib/commonjs/parser/index.d.ts +3 -0
- package/lib/commonjs/parser/index.js +450 -42
- package/lib/commonjs/parser/index.js.map +4 -4
- package/lib/commonjs/stripUtils.js +743 -0
- package/lib/commonjs/stripUtils.js.map +1 -0
- package/lib/commonjs/styleUtils.js +194 -23
- package/lib/commonjs/styleUtils.js.map +1 -1
- package/lib/commonjs/web/MarkdownElements.css +67 -0
- package/lib/commonjs/web/MarkdownTextInput.css +9 -7
- package/lib/commonjs/web/utils/blockLayout.js +95 -7
- package/lib/commonjs/web/utils/blockLayout.js.map +1 -1
- package/lib/commonjs/web/utils/blockUtils.js +327 -54
- package/lib/commonjs/web/utils/blockUtils.js.map +1 -1
- package/lib/commonjs/web/utils/elementUtils.js +80 -0
- package/lib/commonjs/web/utils/elementUtils.js.map +1 -0
- package/lib/commonjs/web/utils/parserUtils.js +84 -28
- package/lib/commonjs/web/utils/parserUtils.js.map +1 -1
- package/lib/commonjs/web/utils/treeUtils.js +6 -0
- package/lib/commonjs/web/utils/treeUtils.js.map +1 -1
- package/lib/commonjs/web/utils/webStyleUtils.js +25 -5
- package/lib/commonjs/web/utils/webStyleUtils.js.map +1 -1
- package/lib/module/MarcusTextDecoratorViewNativeComponent.ts +199 -0
- package/lib/module/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
- package/lib/module/MarkdownText.js +325 -0
- package/lib/module/MarkdownText.js.map +1 -0
- package/lib/module/MarkdownText.web.js +524 -0
- package/lib/module/MarkdownText.web.js.map +1 -0
- package/lib/module/MarkdownTextInput.js +24 -29
- package/lib/module/MarkdownTextInput.js.map +1 -1
- package/lib/module/MarkdownTextInput.web.js +12 -7
- package/lib/module/MarkdownTextInput.web.js.map +1 -1
- package/lib/module/atomUtils.js +106 -0
- package/lib/module/atomUtils.js.map +1 -0
- package/lib/module/childrenUtils.js +33 -0
- package/lib/module/childrenUtils.js.map +1 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/math/environment.js +67 -0
- package/lib/module/math/environment.js.map +1 -0
- package/lib/module/math/index.js +56 -0
- package/lib/module/math/index.js.map +1 -0
- package/lib/module/math/mathjax.js +541 -0
- package/lib/module/math/mathjax.js.map +1 -0
- package/lib/module/metro.js +169 -0
- package/lib/module/metro.js.map +1 -0
- package/lib/module/parser/index.d.ts +3 -0
- package/lib/module/parser/index.js +450 -42
- package/lib/module/parser/index.js.map +4 -4
- package/lib/module/stripUtils.js +739 -0
- package/lib/module/stripUtils.js.map +1 -0
- package/lib/module/styleUtils.js +196 -25
- package/lib/module/styleUtils.js.map +1 -1
- package/lib/module/web/MarkdownElements.css +67 -0
- package/lib/module/web/MarkdownTextInput.css +9 -7
- package/lib/module/web/utils/blockLayout.js +94 -8
- package/lib/module/web/utils/blockLayout.js.map +1 -1
- package/lib/module/web/utils/blockUtils.js +328 -56
- package/lib/module/web/utils/blockUtils.js.map +1 -1
- package/lib/module/web/utils/elementUtils.js +75 -0
- package/lib/module/web/utils/elementUtils.js.map +1 -0
- package/lib/module/web/utils/parserUtils.js +86 -30
- package/lib/module/web/utils/parserUtils.js.map +1 -1
- package/lib/module/web/utils/treeUtils.js +6 -0
- package/lib/module/web/utils/treeUtils.js.map +1 -1
- package/lib/module/web/utils/webStyleUtils.js +25 -5
- package/lib/module/web/utils/webStyleUtils.js.map +1 -1
- package/lib/typescript/commonjs/e2e/links.test.d.ts +2 -0
- package/lib/typescript/commonjs/e2e/links.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
- package/lib/typescript/commonjs/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
- package/lib/typescript/commonjs/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/MarkdownText.d.ts +127 -0
- package/lib/typescript/commonjs/src/MarkdownText.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarkdownText.web.d.ts +126 -0
- package/lib/typescript/commonjs/src/MarkdownText.web.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts +23 -1
- package/lib/typescript/commonjs/src/MarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts +26 -4
- package/lib/typescript/commonjs/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/atomUtils.d.ts +58 -0
- package/lib/typescript/commonjs/src/atomUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/childrenUtils.d.ts +12 -0
- package/lib/typescript/commonjs/src/childrenUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/commonTypes.d.ts +39 -3
- package/lib/typescript/commonjs/src/commonTypes.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +2 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/math/environment.d.ts +7 -0
- package/lib/typescript/commonjs/src/math/environment.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/math/index.d.ts +64 -0
- package/lib/typescript/commonjs/src/math/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/math/mathjax.d.ts +63 -0
- package/lib/typescript/commonjs/src/math/mathjax.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/metro.d.ts +59 -0
- package/lib/typescript/commonjs/src/metro.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/parser/index.d.ts +3 -3
- package/lib/typescript/commonjs/src/parser/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/parser/micromark-extension-mentions.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/rangeUtils.d.ts +1 -1
- package/lib/typescript/commonjs/src/stripUtils.d.ts +108 -0
- package/lib/typescript/commonjs/src/stripUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/styleUtils.d.ts +62 -4
- package/lib/typescript/commonjs/src/styleUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts +32 -4
- package/lib/typescript/commonjs/src/web/utils/blockLayout.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts +20 -8
- package/lib/typescript/commonjs/src/web/utils/blockUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts +37 -0
- package/lib/typescript/commonjs/src/web/utils/elementUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts +5 -5
- package/lib/typescript/commonjs/src/web/utils/parserUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts +6 -1
- package/lib/typescript/commonjs/src/web/utils/treeUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts +2 -2
- package/lib/typescript/commonjs/src/web/utils/webStyleUtils.d.ts.map +1 -1
- package/lib/typescript/module/e2e/links.test.d.ts +2 -0
- package/lib/typescript/module/e2e/links.test.d.ts.map +1 -0
- package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts +93 -0
- package/lib/typescript/module/src/MarcusTextDecoratorViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts +64 -51
- package/lib/typescript/module/src/MarcusTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/module/src/MarkdownText.d.ts +127 -0
- package/lib/typescript/module/src/MarkdownText.d.ts.map +1 -0
- package/lib/typescript/module/src/MarkdownText.web.d.ts +126 -0
- package/lib/typescript/module/src/MarkdownText.web.d.ts.map +1 -0
- package/lib/typescript/module/src/MarkdownTextInput.d.ts +23 -1
- package/lib/typescript/module/src/MarkdownTextInput.d.ts.map +1 -1
- package/lib/typescript/module/src/MarkdownTextInput.web.d.ts +26 -4
- package/lib/typescript/module/src/MarkdownTextInput.web.d.ts.map +1 -1
- package/lib/typescript/module/src/atomUtils.d.ts +58 -0
- package/lib/typescript/module/src/atomUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/childrenUtils.d.ts +12 -0
- package/lib/typescript/module/src/childrenUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/commonTypes.d.ts +39 -3
- package/lib/typescript/module/src/commonTypes.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +2 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/math/environment.d.ts +7 -0
- package/lib/typescript/module/src/math/environment.d.ts.map +1 -0
- package/lib/typescript/module/src/math/index.d.ts +64 -0
- package/lib/typescript/module/src/math/index.d.ts.map +1 -0
- package/lib/typescript/module/src/math/mathjax.d.ts +63 -0
- package/lib/typescript/module/src/math/mathjax.d.ts.map +1 -0
- package/lib/typescript/module/src/metro.d.ts +59 -0
- package/lib/typescript/module/src/metro.d.ts.map +1 -0
- package/lib/typescript/module/src/parser/index.d.ts +3 -3
- package/lib/typescript/module/src/parser/index.d.ts.map +1 -1
- package/lib/typescript/module/src/parser/micromark-extension-mentions.d.ts.map +1 -1
- package/lib/typescript/module/src/rangeUtils.d.ts +1 -1
- package/lib/typescript/module/src/stripUtils.d.ts +108 -0
- package/lib/typescript/module/src/stripUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/styleUtils.d.ts +62 -4
- package/lib/typescript/module/src/styleUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/blockLayout.d.ts +32 -4
- package/lib/typescript/module/src/web/utils/blockLayout.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/blockUtils.d.ts +20 -8
- package/lib/typescript/module/src/web/utils/blockUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/elementUtils.d.ts +37 -0
- package/lib/typescript/module/src/web/utils/elementUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/web/utils/parserUtils.d.ts +5 -5
- package/lib/typescript/module/src/web/utils/parserUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/treeUtils.d.ts +6 -1
- package/lib/typescript/module/src/web/utils/treeUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts +2 -2
- package/lib/typescript/module/src/web/utils/webStyleUtils.d.ts.map +1 -1
- package/package.json +29 -2
- package/react-native.config.js +8 -1
- package/src/MarcusTextDecoratorViewNativeComponent.ts +199 -0
- package/src/MarcusTextInputDecoratorViewNativeComponent.ts +145 -58
- package/src/MarkdownText.tsx +472 -0
- package/src/MarkdownText.web.tsx +668 -0
- package/src/MarkdownTextInput.tsx +42 -38
- package/src/MarkdownTextInput.web.tsx +36 -13
- package/src/atomUtils.ts +112 -0
- package/src/childrenUtils.ts +39 -0
- package/src/commonTypes.ts +69 -9
- package/src/index.tsx +2 -1
- package/src/math/environment.ts +68 -0
- package/src/math/index.ts +78 -0
- package/src/math/mathjax.ts +604 -0
- package/src/metro.ts +238 -0
- package/src/parser/index.ts +175 -10
- package/src/parser/micromark-extension-mentions.ts +104 -11
- package/src/stripUtils.ts +919 -0
- package/src/styleUtils.ts +290 -26
- package/src/web/MarkdownElements.css +67 -0
- package/src/web/MarkdownTextInput.css +9 -7
- package/src/web/utils/blockLayout.ts +124 -14
- package/src/web/utils/blockUtils.ts +365 -59
- package/src/web/utils/elementUtils.ts +87 -0
- package/src/web/utils/parserUtils.ts +119 -31
- package/src/web/utils/treeUtils.ts +6 -1
- package/src/web/utils/webStyleUtils.ts +32 -8
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBackgroundColorSpan.kt +0 -6
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownBoldSpan.kt +0 -6
- package/android/src/main/java/app/getbullet/marcus/spans/MarkdownItalicSpan.kt +0 -6
- package/apple/RCTMarcusStyle+Codegen.mm +0 -64
- package/apple/RCTMarcusUtils.h +0 -26
- package/apple/RCTMarcusUtils.mm +0 -26
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TeX -> SVG with MathJax 4, wired up the way React Native needs.
|
|
3
|
+
*
|
|
4
|
+
* Nothing imports this module. `react-native-marcus/metro`'s `withMath()`
|
|
5
|
+
* resolves `./index.ts` -- the stub beside it -- to this file, which is how an
|
|
6
|
+
* application opts into a dependency this size. See `./index.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Three things here are not the defaults:
|
|
9
|
+
*
|
|
10
|
+
* - **`liteAdaptor`**, so nothing wants a DOM. It runs the same in Hermes, in a
|
|
11
|
+
* browser and in Node, which is what makes one renderer serve all three
|
|
12
|
+
* platforms and draw the same glyphs on each.
|
|
13
|
+
* - **`fontCache: "none"`**, so each expression is a standalone SVG with its
|
|
14
|
+
* glyph outlines inline. `"local"`, the default, emits `<use>` against a
|
|
15
|
+
* `<defs>` block, which is smaller on a page full of maths and useless when
|
|
16
|
+
* every expression is a separate attachment in someone else's view tree.
|
|
17
|
+
* - **`linebreaks: { inline: false }`**. v4 breaks long inline maths into
|
|
18
|
+
* several `<svg>` fragments separated by `<mjx-break>`; one attachment is one
|
|
19
|
+
* SVG, and left on, the metrics read off the first fragment are silently
|
|
20
|
+
* wrong.
|
|
21
|
+
*
|
|
22
|
+
* Every specifier below names `mjs/` outright rather than going through
|
|
23
|
+
* MathJax's `js/*` export, and that is not cosmetic. That export maps to `mjs`
|
|
24
|
+
* for `import` and to `cjs` for `require`, so a module reached both ways --
|
|
25
|
+
* which is exactly what this file does, importing the machinery and requiring
|
|
26
|
+
* the font ranges -- is loaded twice, and the two copies each hold half of what
|
|
27
|
+
* is needed. The symptom is every `\mathbb`, `\mathcal`, `\mathfrak`,
|
|
28
|
+
* `\mathsf` and `\mathtt` coming out blank while everything else draws
|
|
29
|
+
* perfectly. `mjs/` goes through the package's `"./*": "./*"` fallback, which
|
|
30
|
+
* has no conditions to disagree about.
|
|
31
|
+
*/
|
|
32
|
+
import "./environment"
|
|
33
|
+
|
|
34
|
+
import { mathjax } from "@mathjax/src/mjs/mathjax.js"
|
|
35
|
+
import { TeX } from "@mathjax/src/mjs/input/tex.js"
|
|
36
|
+
import { SVG } from "@mathjax/src/mjs/output/svg.js"
|
|
37
|
+
import { liteAdaptor } from "@mathjax/src/mjs/adaptors/liteAdaptor.js"
|
|
38
|
+
import { RegisterHTMLHandler } from "@mathjax/src/mjs/handlers/html.js"
|
|
39
|
+
import { MathJaxNewcmFont } from "@mathjax/mathjax-newcm-font/mjs/svg.js"
|
|
40
|
+
import { SerializedMmlVisitor } from "@mathjax/src/mjs/core/MmlTree/SerializedMmlVisitor.js"
|
|
41
|
+
import { STATE } from "@mathjax/src/mjs/core/MathItem.js"
|
|
42
|
+
import type { MmlNode } from "@mathjax/src/mjs/core/MmlTree/MmlNode.js"
|
|
43
|
+
|
|
44
|
+
// Reached through the three modules that hold what is wanted rather than
|
|
45
|
+
// through SRE's own entry point, which drags in a command line parser, sets
|
|
46
|
+
// itself up ten milliseconds after import with a loader that cannot work here,
|
|
47
|
+
// and is written with an `export * as` that neither Metro's Babel preset nor
|
|
48
|
+
// Jest's will parse.
|
|
49
|
+
import { setupEngine } from "speech-rule-engine/js/api/control.js"
|
|
50
|
+
import { toSpeech } from "speech-rule-engine/js/api/string.js"
|
|
51
|
+
import { EnginePromise } from "speech-rule-engine/js/common/engine.js"
|
|
52
|
+
|
|
53
|
+
import "@mathjax/src/mjs/input/tex/base/BaseConfiguration.js"
|
|
54
|
+
import "@mathjax/src/mjs/input/tex/ams/AmsConfiguration.js"
|
|
55
|
+
import "@mathjax/src/mjs/input/tex/newcommand/NewcommandConfiguration.js"
|
|
56
|
+
import "@mathjax/src/mjs/input/tex/boldsymbol/BoldsymbolConfiguration.js"
|
|
57
|
+
|
|
58
|
+
import type { RenderedMath } from "./index"
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Every glyph range, each behind a literal `require` so a bundler can see it.
|
|
62
|
+
*
|
|
63
|
+
* MathJax 4 splits its font data into forty files it fetches on demand. The
|
|
64
|
+
* fetching has to be replaced -- no bundler can follow it -- but it cannot be
|
|
65
|
+
* skipped: `getChar` resolves a missing glyph *through* this loader, and if
|
|
66
|
+
* nothing comes of the call it puts back the placeholder it just took out, so
|
|
67
|
+
* it and `loadDynamicFileSync` hand that placeholder to each other until the
|
|
68
|
+
* stack overflows.
|
|
69
|
+
*
|
|
70
|
+
* So: a thunk per range, a synchronous loader, and the whole font in the
|
|
71
|
+
* bundle -- a few megabytes, and drawing every expression rather than a chosen
|
|
72
|
+
* subset is what they buy. `loadRanges` below runs them all before the first
|
|
73
|
+
* expression is drawn; MathJax still asks for each one the first time it wants
|
|
74
|
+
* a glyph from it, and finds the module already run.
|
|
75
|
+
*/
|
|
76
|
+
const RANGES: Record<string, () => unknown> = {
|
|
77
|
+
"PUA": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/PUA.js"),
|
|
78
|
+
"accents": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/accents.js"),
|
|
79
|
+
"accents-b-i": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/accents-b-i.js"),
|
|
80
|
+
"arabic": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/arabic.js"),
|
|
81
|
+
"arrows": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/arrows.js"),
|
|
82
|
+
"braille": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/braille.js"),
|
|
83
|
+
"braille-d": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/braille-d.js"),
|
|
84
|
+
"calligraphic": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/calligraphic.js"),
|
|
85
|
+
"cherokee": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/cherokee.js"),
|
|
86
|
+
"cyrillic": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/cyrillic.js"),
|
|
87
|
+
"cyrillic-ss": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/cyrillic-ss.js"),
|
|
88
|
+
"devanagari": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/devanagari.js"),
|
|
89
|
+
"double-struck": () =>
|
|
90
|
+
require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/double-struck.js"),
|
|
91
|
+
"fraktur": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/fraktur.js"),
|
|
92
|
+
"greek": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/greek.js"),
|
|
93
|
+
"greek-ss": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/greek-ss.js"),
|
|
94
|
+
"hebrew": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/hebrew.js"),
|
|
95
|
+
"latin": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/latin.js"),
|
|
96
|
+
"latin-b": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/latin-b.js"),
|
|
97
|
+
"latin-bi": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/latin-bi.js"),
|
|
98
|
+
"latin-i": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/latin-i.js"),
|
|
99
|
+
"marrows": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/marrows.js"),
|
|
100
|
+
"math": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/math.js"),
|
|
101
|
+
"monospace": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/monospace.js"),
|
|
102
|
+
"monospace-ex": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/monospace-ex.js"),
|
|
103
|
+
"monospace-l": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/monospace-l.js"),
|
|
104
|
+
"mshapes": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/mshapes.js"),
|
|
105
|
+
"phonetics": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/phonetics.js"),
|
|
106
|
+
"phonetics-ss": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/phonetics-ss.js"),
|
|
107
|
+
"sans-serif": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/sans-serif.js"),
|
|
108
|
+
"sans-serif-b": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/sans-serif-b.js"),
|
|
109
|
+
"sans-serif-bi": () =>
|
|
110
|
+
require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/sans-serif-bi.js"),
|
|
111
|
+
"sans-serif-ex": () =>
|
|
112
|
+
require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/sans-serif-ex.js"),
|
|
113
|
+
"sans-serif-i": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/sans-serif-i.js"),
|
|
114
|
+
"sans-serif-r": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/sans-serif-r.js"),
|
|
115
|
+
"script": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/script.js"),
|
|
116
|
+
"shapes": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/shapes.js"),
|
|
117
|
+
"symbols": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/symbols.js"),
|
|
118
|
+
"symbols-b-i": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/symbols-b-i.js"),
|
|
119
|
+
"variants": () => require("@mathjax/mathjax-newcm-font/mjs/svg/dynamic/variants.js"),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
;(mathjax as unknown as { asyncLoad: unknown }).asyncLoad = (name: string) => {
|
|
123
|
+
const range = String(name).replace(/^.*\//, "").replace(/\.js$/, "")
|
|
124
|
+
const load = RANGES[range]
|
|
125
|
+
|
|
126
|
+
if (!load) {
|
|
127
|
+
throw new Error(`[react-native-marcus] font range not bundled: ${range}`)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return load()
|
|
131
|
+
}
|
|
132
|
+
// Synchronous, so `getChar` resolves in place rather than throwing a retry only
|
|
133
|
+
// a promise-based caller could catch.
|
|
134
|
+
;(mathjax as unknown as { asyncIsSynchronous: boolean }).asyncIsSynchronous = true
|
|
135
|
+
|
|
136
|
+
const adaptor = liteAdaptor()
|
|
137
|
+
RegisterHTMLHandler(adaptor)
|
|
138
|
+
|
|
139
|
+
const font = new MathJaxNewcmFont()
|
|
140
|
+
|
|
141
|
+
const doc = mathjax.document("", {
|
|
142
|
+
InputJax: new TeX({ packages: ["base", "ams", "newcommand", "boldsymbol"] }),
|
|
143
|
+
OutputJax: new SVG({
|
|
144
|
+
fontCache: "none",
|
|
145
|
+
font,
|
|
146
|
+
linebreaks: { inline: false },
|
|
147
|
+
displayOverflow: "overflow",
|
|
148
|
+
}),
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Whether every range module has been run yet.
|
|
153
|
+
*
|
|
154
|
+
* MathJax pulls a range in the moment it first wants a glyph from it, which
|
|
155
|
+
* means it runs a font module from the middle of laying an expression out --
|
|
156
|
+
* and Hermes' stack does not have the room. It overflows, the expression
|
|
157
|
+
* fails, and only a second attempt succeeds, by which time the module has run.
|
|
158
|
+
* Twelve expressions across a dozen ranges cost a minute of throwing and
|
|
159
|
+
* retrying on an emulator; the same page draws in one pass once the modules
|
|
160
|
+
* have been run from here, where the stack is empty.
|
|
161
|
+
*
|
|
162
|
+
* This is deliberately *not* `font.loadDynamicFilesSync()`, which is MathJax's
|
|
163
|
+
* own way to do this and which quietly loses `\mathbb`, `\mathfrak`,
|
|
164
|
+
* `\mathsf` and `\mathtt`: it marks every range as resolved on the way past,
|
|
165
|
+
* so the four whose characters it does not manage to install can never be
|
|
166
|
+
* asked for again. Running the modules and leaving the bookkeeping alone keeps
|
|
167
|
+
* the normal path intact -- MathJax still resolves each range the first time it
|
|
168
|
+
* wants one, it just finds the module already run and does it in no time.
|
|
169
|
+
*
|
|
170
|
+
* Deferred to the first expression rather than done on import, so that an
|
|
171
|
+
* application which loads this module and draws nothing pays nothing.
|
|
172
|
+
*/
|
|
173
|
+
let loaded = false
|
|
174
|
+
|
|
175
|
+
function loadRanges() {
|
|
176
|
+
if (loaded) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Set first: one range that will not load must not cost this again.
|
|
181
|
+
loaded = true
|
|
182
|
+
|
|
183
|
+
for (const load of Object.values(RANGES)) {
|
|
184
|
+
try {
|
|
185
|
+
load()
|
|
186
|
+
} catch {
|
|
187
|
+
// Left to the on-demand path, which warns through MathJax's own loader.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The opening tag, which carries the metrics and the sizing. */
|
|
193
|
+
const ROOT = /<svg[^>]*>/
|
|
194
|
+
/** The root's own sizing, which has to go: see `attemptRender`. */
|
|
195
|
+
const SIZING = /\s(?:width|height)="[^"]*"|\sstyle="[^"]*"/g
|
|
196
|
+
/** The `<svg>` element on its own, without the `<mjx-container>` around it. */
|
|
197
|
+
const ELEMENT = /<svg[\s\S]*<\/svg>/
|
|
198
|
+
const EX = /width="([-\d.]+)ex"[^>]*height="([-\d.]+)ex"/
|
|
199
|
+
const VA = /vertical-align:\s*([-\d.]+)ex/
|
|
200
|
+
/** What MathJax paints in, which a view mounted in a `Text` inherits nothing of. */
|
|
201
|
+
const CURRENT_COLOR = /currentColor/g
|
|
202
|
+
/**
|
|
203
|
+
* MathJax's own annotations -- `data-mml-node`, `data-latex`, `data-c` and the
|
|
204
|
+
* rest. They are for its context menu and its accessibility walker, neither of
|
|
205
|
+
* which is here, and they are a third of the bytes. They also reach the DOM
|
|
206
|
+
* through an SVG renderer that hands unknown attributes to React, which
|
|
207
|
+
* complains about every one of them.
|
|
208
|
+
*/
|
|
209
|
+
const ANNOTATIONS = /\sdata-[a-zA-Z0-9-]+="[^"]*"/g
|
|
210
|
+
|
|
211
|
+
const available = true
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The drawing cache. Rendering is fast -- around a millisecond on a mid-range
|
|
215
|
+
* phone -- but a display redraws for all sorts of reasons that have nothing to
|
|
216
|
+
* do with its maths, and the same expression at the same size in the same
|
|
217
|
+
* colour is the same picture every time.
|
|
218
|
+
*/
|
|
219
|
+
const cache = new Map<string, RenderedMath | null>()
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* How many goes an expression gets before it is called undrawable.
|
|
223
|
+
*
|
|
224
|
+
* One, plus one. With the ranges loaded up front nothing should have to try
|
|
225
|
+
* twice; the second go is there because the failure it covers -- a glyph
|
|
226
|
+
* resolved from inside the layout, on a stack with no room left -- leaves the
|
|
227
|
+
* range loaded on its way out, so a retry is free and always the one that
|
|
228
|
+
* works.
|
|
229
|
+
*/
|
|
230
|
+
const MAX_ATTEMPTS = 2
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* TeX in, one standalone SVG and its box out.
|
|
234
|
+
*
|
|
235
|
+
* The box is read off the root element rather than measured: MathJax reports
|
|
236
|
+
* width, height and the depth below the baseline in `ex`, and an `ex` is half
|
|
237
|
+
* the font size by its own `exFactor`. So nothing has to be laid out to find
|
|
238
|
+
* out how much room to ask for -- which is exactly what a text layout needs to
|
|
239
|
+
* know before it can lay the line out.
|
|
240
|
+
*
|
|
241
|
+
* Returns nothing for an expression it cannot draw, rather than throwing. TeX
|
|
242
|
+
* that reaches for something the bundled fonts do not carry ends in a
|
|
243
|
+
* `RangeError`, and one bad equation should cost the equation rather than the
|
|
244
|
+
* message it is in.
|
|
245
|
+
*/
|
|
246
|
+
function renderMath(
|
|
247
|
+
tex: string,
|
|
248
|
+
display: boolean,
|
|
249
|
+
fontSize: number,
|
|
250
|
+
color: string | undefined,
|
|
251
|
+
): RenderedMath | null {
|
|
252
|
+
const key = `${display ? "D" : "I"}${fontSize}\u0000${color ?? ""}\u0000${tex}`
|
|
253
|
+
const hit = cache.get(key)
|
|
254
|
+
|
|
255
|
+
if (hit !== undefined) {
|
|
256
|
+
return hit
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
loadRanges()
|
|
260
|
+
|
|
261
|
+
let rendered: RenderedMath | null = null
|
|
262
|
+
|
|
263
|
+
// See MAX_ATTEMPTS: the second go is the one that succeeds if a glyph had to
|
|
264
|
+
// be resolved from inside the layout after all.
|
|
265
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS && rendered === null; attempt++) {
|
|
266
|
+
rendered = attemptRender(tex, display, fontSize, color)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
cache.set(key, rendered)
|
|
270
|
+
|
|
271
|
+
return rendered
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function attemptRender(
|
|
275
|
+
tex: string,
|
|
276
|
+
display: boolean,
|
|
277
|
+
fontSize: number,
|
|
278
|
+
color: string | undefined,
|
|
279
|
+
): RenderedMath | null {
|
|
280
|
+
try {
|
|
281
|
+
const node = doc.convert(tex, {
|
|
282
|
+
display,
|
|
283
|
+
em: fontSize,
|
|
284
|
+
ex: fontSize / 2,
|
|
285
|
+
containerWidth: 1e6,
|
|
286
|
+
})
|
|
287
|
+
// `outerHTML` wraps the picture in MathJax's own `<mjx-container>`, which is
|
|
288
|
+
// a custom element meant for a browser: an SVG renderer handed one has no
|
|
289
|
+
// idea what to do with it. Only the `<svg>` inside is wanted.
|
|
290
|
+
const svg = adaptor.outerHTML(node).match(ELEMENT)?.[0] ?? ""
|
|
291
|
+
const root = svg.match(ROOT)?.[0] ?? ""
|
|
292
|
+
const ex = EX.exec(root)
|
|
293
|
+
|
|
294
|
+
if (!ex) {
|
|
295
|
+
return null
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const va = VA.exec(root)
|
|
299
|
+
const unit = fontSize / 2
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
// Two things come off the root. Its `width`, `height` and
|
|
303
|
+
// `vertical-align` are stated in `ex`, and a browser resolves an `ex`
|
|
304
|
+
// from the real font's x-height while these numbers assume MathJax's own
|
|
305
|
+
// half-an-em: left in, the picture comes out a different width from the
|
|
306
|
+
// box reserved for it and the baseline shift is applied twice, once by
|
|
307
|
+
// the element and once by whatever is positioning it. The `viewBox`
|
|
308
|
+
// stays, so the picture scales to whatever size it is given. And
|
|
309
|
+
// `currentColor` inherits from nothing inside a `Text` on either
|
|
310
|
+
// platform, so the colour is painted in here rather than left to a prop
|
|
311
|
+
// the caller would have to know to pass.
|
|
312
|
+
svg: svg
|
|
313
|
+
.replace(ROOT, (tag) => tag.replace(SIZING, ""))
|
|
314
|
+
.replace(ANNOTATIONS, "")
|
|
315
|
+
.replace(CURRENT_COLOR, color ?? "currentColor"),
|
|
316
|
+
width: Number(ex[1]) * unit,
|
|
317
|
+
height: Number(ex[2]) * unit,
|
|
318
|
+
depth: va ? -Number(va[1]) * unit : 0,
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
// Swallowed: only `renderMath` above knows whether a second go is worth it.
|
|
322
|
+
return null
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Reading an expression out loud, which is a different problem from drawing it.
|
|
328
|
+
*
|
|
329
|
+
* The engine is MathJax's own -- SRE, which it depends on and drives in a web
|
|
330
|
+
* worker for the same purpose in a browser. There is no worker here, so it is
|
|
331
|
+
* driven directly: TeX is compiled to MathML (`STATE.COMPILED` stops one step
|
|
332
|
+
* short of typesetting, so this costs a parse and nothing else) and MathML is
|
|
333
|
+
* what SRE reads.
|
|
334
|
+
*
|
|
335
|
+
* `mathspeak` is the ruleset MathJax defaults to, and the one whose phrasing --
|
|
336
|
+
* "StartFraction a Over b EndFraction" -- screen reader users of maths on the
|
|
337
|
+
* web already know.
|
|
338
|
+
*/
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Every locale SRE can speak, behind thunks a bundler can see, the way the font
|
|
342
|
+
* ranges are. `base` carries everything language-independent and is always
|
|
343
|
+
* needed.
|
|
344
|
+
*
|
|
345
|
+
* The two rulesets SRE ships that are not languages are left out: `nemeth` and
|
|
346
|
+
* `euro` are braille codes rather than speech, and a braille cell read aloud is
|
|
347
|
+
* nobody's accessible name.
|
|
348
|
+
*
|
|
349
|
+
* They are all named here because a `require` has to be written down for a
|
|
350
|
+
* bundler to find it -- and then all but the ones an application asked for are
|
|
351
|
+
* resolved away to nothing by `withMath(config, { locales })`, which is what
|
|
352
|
+
* keeps a display that reads in English from carrying the four megabytes of
|
|
353
|
+
* rules for the twelve languages it never speaks. See `../metro.ts`.
|
|
354
|
+
*/
|
|
355
|
+
const MAPS: Record<string, () => unknown> = {
|
|
356
|
+
base: () => require("speech-rule-engine/lib/mathmaps/base.json"),
|
|
357
|
+
af: () => require("speech-rule-engine/lib/mathmaps/af.json"),
|
|
358
|
+
ca: () => require("speech-rule-engine/lib/mathmaps/ca.json"),
|
|
359
|
+
da: () => require("speech-rule-engine/lib/mathmaps/da.json"),
|
|
360
|
+
de: () => require("speech-rule-engine/lib/mathmaps/de.json"),
|
|
361
|
+
en: () => require("speech-rule-engine/lib/mathmaps/en.json"),
|
|
362
|
+
es: () => require("speech-rule-engine/lib/mathmaps/es.json"),
|
|
363
|
+
fr: () => require("speech-rule-engine/lib/mathmaps/fr.json"),
|
|
364
|
+
hi: () => require("speech-rule-engine/lib/mathmaps/hi.json"),
|
|
365
|
+
it: () => require("speech-rule-engine/lib/mathmaps/it.json"),
|
|
366
|
+
ko: () => require("speech-rule-engine/lib/mathmaps/ko.json"),
|
|
367
|
+
nb: () => require("speech-rule-engine/lib/mathmaps/nb.json"),
|
|
368
|
+
nn: () => require("speech-rule-engine/lib/mathmaps/nn.json"),
|
|
369
|
+
sv: () => require("speech-rule-engine/lib/mathmaps/sv.json"),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** What an expression is read in when the caller names no locale. */
|
|
373
|
+
const DEFAULT_LOCALE = "en"
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* The locales whose rules actually arrived.
|
|
377
|
+
*
|
|
378
|
+
* A locale resolved away to nothing comes back as an empty object, and once SRE
|
|
379
|
+
* has swallowed one there is no asking about it afterwards: a rule it cannot
|
|
380
|
+
* find for the locale it was asked for is answered out of the default locale's,
|
|
381
|
+
* so the expression is read out perfectly, in the wrong language. This is
|
|
382
|
+
* recorded on the way past, which is the last moment the difference is visible.
|
|
383
|
+
*/
|
|
384
|
+
const installed = new Set<string>()
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* How SRE is set up, and how it is told to load a locale.
|
|
388
|
+
*
|
|
389
|
+
* The loader returns a *synchronous* thenable rather than a promise, and that
|
|
390
|
+
* is the whole trick to getting speech on the first render. SRE calls
|
|
391
|
+
* `loader(locale).then(install)` directly on whatever comes back, so a `then`
|
|
392
|
+
* that calls its callback in place has the rules installed before the call
|
|
393
|
+
* returns -- where a real promise would install them a microtask later, by
|
|
394
|
+
* which time the label has already been built and handed to the platform.
|
|
395
|
+
*/
|
|
396
|
+
function features(locale: string) {
|
|
397
|
+
return {
|
|
398
|
+
mode: "sync",
|
|
399
|
+
locale,
|
|
400
|
+
domain: "mathspeak",
|
|
401
|
+
style: "default",
|
|
402
|
+
modality: "speech",
|
|
403
|
+
custom: (name: string) => ({
|
|
404
|
+
then: (resolve: (maps: unknown) => void) => {
|
|
405
|
+
const maps = MAPS[name]?.() ?? {}
|
|
406
|
+
|
|
407
|
+
if (Object.keys(maps as object).length > 0) {
|
|
408
|
+
installed.add(name)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
resolve(maps)
|
|
412
|
+
},
|
|
413
|
+
}),
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const visitor = new SerializedMmlVisitor()
|
|
418
|
+
|
|
419
|
+
/** Something every ruleset can say, used to check the engine came up. */
|
|
420
|
+
const PROBE = '<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>x</mi></math>'
|
|
421
|
+
|
|
422
|
+
/** Whether SRE has been brought as far as its language-independent rules. */
|
|
423
|
+
let started = false
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Three setups rather than one, because SRE loads in steps and each call
|
|
427
|
+
* carries it one step further: the first is the one it reserves for its own
|
|
428
|
+
* initialisation and which loads nothing, the second installs `base`, and the
|
|
429
|
+
* third installs the default locale.
|
|
430
|
+
*
|
|
431
|
+
* The third is not optional even when the default locale is not the one wanted.
|
|
432
|
+
* SRE reaches a locale by walking `base`, then the *default* locale, then the
|
|
433
|
+
* wanted one, and `select` can only make that walk synchronous for promises
|
|
434
|
+
* that already exist when it starts patching. Loading French first would create
|
|
435
|
+
* the English promise halfway through the walk, unpatched, and the walk would
|
|
436
|
+
* stop there -- English installed, French silently not, one microtask short.
|
|
437
|
+
*/
|
|
438
|
+
function start() {
|
|
439
|
+
if (started) {
|
|
440
|
+
return
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Set first: a setup that throws must not be attempted again on every span.
|
|
444
|
+
started = true
|
|
445
|
+
|
|
446
|
+
setupEngine(features(DEFAULT_LOCALE) as never)
|
|
447
|
+
setupEngine(features(DEFAULT_LOCALE) as never)
|
|
448
|
+
select(DEFAULT_LOCALE)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Points the engine at one locale, loading its rules if this is the first ask.
|
|
453
|
+
*
|
|
454
|
+
* Every promise SRE is holding is replaced, for the length of the call, by one
|
|
455
|
+
* that calls its callback in place. SRE walks a chain of them before it reaches
|
|
456
|
+
* the locale it was asked for -- `base` first, then the default locale, then
|
|
457
|
+
* the wanted one -- and a resolved promise still defers its callback by a
|
|
458
|
+
* microtask, which is a microtask longer than the render that wants the label.
|
|
459
|
+
* A thenable that calls back in place does not. They are all put back before
|
|
460
|
+
* anything else can see them.
|
|
461
|
+
*/
|
|
462
|
+
function select(locale: string) {
|
|
463
|
+
const held = { ...EnginePromise.promises }
|
|
464
|
+
|
|
465
|
+
for (const name of Object.keys(held)) {
|
|
466
|
+
EnginePromise.promises[name] = {
|
|
467
|
+
then: (resolve: (loaded: string) => void) => resolve(name),
|
|
468
|
+
} as unknown as Promise<string>
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
setupEngine(features(locale) as never)
|
|
473
|
+
} finally {
|
|
474
|
+
for (const [name, promise] of Object.entries(held)) {
|
|
475
|
+
EnginePromise.promises[name] = promise
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Which locale the engine is pointed at, and which locales can be spoken in. */
|
|
481
|
+
let current: string | null = null
|
|
482
|
+
const speakable = new Map<string, boolean>()
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Points the engine at `locale` and says whether anything can be said in it.
|
|
486
|
+
*
|
|
487
|
+
* All of this depends on internals that are not SRE's public API, so the result
|
|
488
|
+
* is checked rather than assumed: the rules have to have arrived and `PROBE`
|
|
489
|
+
* has to come out as speech. When it does not, or when any of this throws, that
|
|
490
|
+
* locale is written off and the caller falls back -- to the default locale, and
|
|
491
|
+
* from there to the TeX.
|
|
492
|
+
*/
|
|
493
|
+
function useLocale(locale: string): boolean {
|
|
494
|
+
const known = speakable.get(locale)
|
|
495
|
+
|
|
496
|
+
// The engine holds one locale at a time, so a locale that is already the one
|
|
497
|
+
// selected needs nothing done to it, and one already known to be unspeakable
|
|
498
|
+
// must not be selected -- it would leave the engine pointed somewhere it
|
|
499
|
+
// cannot read from.
|
|
500
|
+
if (current === locale) {
|
|
501
|
+
return known ?? false
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (known === false) {
|
|
505
|
+
return false
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
start()
|
|
510
|
+
select(locale)
|
|
511
|
+
current = locale
|
|
512
|
+
|
|
513
|
+
const speaks = installed.has(locale) && toSpeech(PROBE) !== ""
|
|
514
|
+
speakable.set(locale, speaks)
|
|
515
|
+
|
|
516
|
+
return speaks
|
|
517
|
+
} catch {
|
|
518
|
+
// Left mute: a display falls back to the TeX, which is worse than speech
|
|
519
|
+
// and a great deal better than an unreadable paragraph.
|
|
520
|
+
speakable.set(locale, false)
|
|
521
|
+
|
|
522
|
+
return false
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** Locales already complained about, so a document warns once rather than once a span. */
|
|
527
|
+
const warned = new Set<string>()
|
|
528
|
+
|
|
529
|
+
function warn(locale: string) {
|
|
530
|
+
if (warned.has(locale)) {
|
|
531
|
+
return
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
warned.add(locale)
|
|
535
|
+
|
|
536
|
+
const spoken = Object.keys(MAPS)
|
|
537
|
+
.filter((name) => name !== "base")
|
|
538
|
+
.join(", ")
|
|
539
|
+
|
|
540
|
+
console.warn(
|
|
541
|
+
locale in MAPS
|
|
542
|
+
? `[react-native-marcus] no speech rules for "${locale}" in this bundle. Pass ` +
|
|
543
|
+
`{ locales: ["${DEFAULT_LOCALE}", "${locale}"] } to withMath() in metro.config.js. ` +
|
|
544
|
+
`Maths is being read in "${DEFAULT_LOCALE}".`
|
|
545
|
+
: `[react-native-marcus] "${locale}" is not a locale the speech engine has. It speaks ` +
|
|
546
|
+
`${spoken}. Maths is being read in "${DEFAULT_LOCALE}".`,
|
|
547
|
+
)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** The speech cache. Speaking an expression costs about as much as drawing it. */
|
|
551
|
+
const speech = new Map<string, string | null>()
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* TeX in, the expression as a sentence out.
|
|
555
|
+
*
|
|
556
|
+
* Returns nothing for anything it cannot say, which includes TeX that does not
|
|
557
|
+
* parse and a document where the rules never loaded.
|
|
558
|
+
*/
|
|
559
|
+
function speakMath(tex: string, display: boolean, locale?: string): string | null {
|
|
560
|
+
const wanted = locale ?? DEFAULT_LOCALE
|
|
561
|
+
const key = `${wanted}\u0000${display ? "D" : "I"}${tex}`
|
|
562
|
+
const hit = speech.get(key)
|
|
563
|
+
|
|
564
|
+
if (hit !== undefined) {
|
|
565
|
+
return hit
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
let speaks = useLocale(wanted)
|
|
569
|
+
|
|
570
|
+
// A language nobody bundled is worth saying out loud once: the expression is
|
|
571
|
+
// still read out, in the default locale, and the fix is one line of Metro
|
|
572
|
+
// config away. Silence would leave a French reader hearing English with no
|
|
573
|
+
// hint as to why. Complained about only once the fallback has answered,
|
|
574
|
+
// because an engine that cannot speak at all is not a bundling mistake and
|
|
575
|
+
// saying it is would send someone to the wrong file.
|
|
576
|
+
if (!speaks && wanted !== DEFAULT_LOCALE) {
|
|
577
|
+
speaks = useLocale(DEFAULT_LOCALE)
|
|
578
|
+
|
|
579
|
+
if (speaks) {
|
|
580
|
+
warn(wanted)
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
let said: string | null = null
|
|
585
|
+
|
|
586
|
+
if (speaks) {
|
|
587
|
+
try {
|
|
588
|
+
const root = doc.convert(tex, { display, end: STATE.COMPILED }) as MmlNode
|
|
589
|
+
const spoken = toSpeech(visitor.visitTree(root))
|
|
590
|
+
|
|
591
|
+
said = spoken === "" ? null : spoken
|
|
592
|
+
} catch {
|
|
593
|
+
// Unsayable, which is not a reason to lose the drawing beside it.
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
speech.set(key, said)
|
|
598
|
+
|
|
599
|
+
return said
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
export type { RenderedMath }
|
|
603
|
+
|
|
604
|
+
export { available, renderMath, speakMath }
|