react-native-marcus 0.1.0 → 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
|
@@ -1,27 +1,49 @@
|
|
|
1
1
|
package app.getbullet.marcus
|
|
2
2
|
|
|
3
3
|
import android.content.res.AssetManager
|
|
4
|
+
import android.graphics.Color
|
|
4
5
|
import android.os.Trace
|
|
5
6
|
import android.text.SpannableStringBuilder
|
|
6
7
|
import android.text.Spanned
|
|
8
|
+
import android.text.TextDirectionHeuristics
|
|
7
9
|
import android.text.TextPaint
|
|
10
|
+
import android.text.style.AbsoluteSizeSpan
|
|
8
11
|
import android.text.style.MetricAffectingSpan
|
|
9
|
-
import
|
|
12
|
+
import androidx.annotation.ColorInt
|
|
13
|
+
import app.getbullet.marcus.spans.MarkdownAbsoluteLineHeightSpan
|
|
10
14
|
import app.getbullet.marcus.spans.MarkdownBackgroundSpan
|
|
11
15
|
import app.getbullet.marcus.spans.MarkdownBlockIndentSpan
|
|
12
|
-
import app.getbullet.marcus.spans.
|
|
16
|
+
import app.getbullet.marcus.spans.MarkdownBulletSpan
|
|
17
|
+
import app.getbullet.marcus.spans.MarkdownCodeBlockSpan
|
|
13
18
|
import app.getbullet.marcus.spans.MarkdownFontFamilySpan
|
|
14
19
|
import app.getbullet.marcus.spans.MarkdownFontSizeSpan
|
|
20
|
+
import app.getbullet.marcus.spans.MarkdownFontStyleSpan
|
|
21
|
+
import app.getbullet.marcus.spans.MarkdownFontWeightSpan
|
|
15
22
|
import app.getbullet.marcus.spans.MarkdownForegroundColorSpan
|
|
16
23
|
import app.getbullet.marcus.spans.MarkdownGapSpan
|
|
17
|
-
import app.getbullet.marcus.spans.
|
|
24
|
+
import app.getbullet.marcus.spans.MarkdownLetterSpacingSpan
|
|
18
25
|
import app.getbullet.marcus.spans.MarkdownLineHeightSpan
|
|
19
26
|
import app.getbullet.marcus.spans.MarkdownSpan
|
|
20
27
|
import app.getbullet.marcus.spans.MarkdownStrikethroughSpan
|
|
28
|
+
import app.getbullet.marcus.spans.MarkdownTextShadowSpan
|
|
21
29
|
import app.getbullet.marcus.spans.MarkdownUnderlineSpan
|
|
22
30
|
import com.facebook.react.uimanager.PixelUtil
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
import kotlin.math.ceil
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param backgroundPass set for an input, where the view paints the markdown
|
|
35
|
+
* backgrounds itself so that the caret stays visible, and handed on to the
|
|
36
|
+
* spans that would otherwise paint themselves. Null for a `Text`.
|
|
37
|
+
* @param display whether the list markers are rendered or shown. A `Text` draws
|
|
38
|
+
* them -- a bullet for an unordered item, the number at its own scale for an
|
|
39
|
+
* ordered one -- where an input shows the marker you typed, in the base font,
|
|
40
|
+
* because there it is text being edited rather than a rendering of it.
|
|
41
|
+
*/
|
|
42
|
+
class MarkdownFormatter(
|
|
43
|
+
private val assetManager: AssetManager,
|
|
44
|
+
private val backgroundPass: MarkdownBackgroundPass? = null,
|
|
45
|
+
private val display: Boolean = false
|
|
46
|
+
) {
|
|
25
47
|
|
|
26
48
|
fun format(
|
|
27
49
|
ssb: SpannableStringBuilder,
|
|
@@ -60,8 +82,11 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
60
82
|
Trace.beginSection("applyRanges")
|
|
61
83
|
// Containers arrive per line, outermost first, each preceded by the run of
|
|
62
84
|
// text its own marker takes up on that line.
|
|
63
|
-
val line = BlockLayout(markdownStyle, textPaint)
|
|
85
|
+
val line = BlockLayout(markdownStyle, textPaint, display)
|
|
64
86
|
var prefix: MarkdownRange? = null
|
|
87
|
+
// Boxed once the walk is over: a block's own indent is whatever its lines
|
|
88
|
+
// ended up with, and the containers around it are still being placed here.
|
|
89
|
+
val codeBlocks = mutableListOf<MarkdownRange>()
|
|
65
90
|
|
|
66
91
|
for (markdownRange in markdownRanges) {
|
|
67
92
|
if (markdownRange.type == "block-prefix") {
|
|
@@ -69,34 +94,248 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
69
94
|
continue
|
|
70
95
|
}
|
|
71
96
|
|
|
97
|
+
if (markdownRange.type == "codeblock") {
|
|
98
|
+
codeBlocks.add(markdownRange)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
|
|
72
102
|
val gutter = gutterOf(markdownRange, markdownStyle)
|
|
73
103
|
|
|
74
104
|
if (gutter != null) {
|
|
75
105
|
line.add(ssb, markdownRange, gutter, prefix)
|
|
76
106
|
prefix = null
|
|
107
|
+
|
|
108
|
+
// The text of a quoted block, as opposed to the bar beside it.
|
|
109
|
+
// Applied here rather than in `applyRange` because a quote is a
|
|
110
|
+
// container and so never reaches it. It covers the line's markers as
|
|
111
|
+
// well as its prose, which is why it goes on before the `syntax`
|
|
112
|
+
// ranges that arrive later and paint over it.
|
|
113
|
+
if (markdownRange.type == "blockquote") {
|
|
114
|
+
applyTextStyle(ssb, markdownStyle.blockquote, markdownRange.start, markdownRange.end)
|
|
115
|
+
}
|
|
116
|
+
|
|
77
117
|
continue
|
|
78
118
|
}
|
|
79
119
|
|
|
80
120
|
applyRange(ssb, markdownRange, markdownStyle)
|
|
81
121
|
}
|
|
122
|
+
|
|
123
|
+
for (codeBlock in codeBlocks) {
|
|
124
|
+
box(ssb, codeBlock, markdownStyle)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Last, so that a marker's own style wins over the `syntax` range that
|
|
128
|
+
// also covers it -- a display keeps that one range of markup and draws it,
|
|
129
|
+
// where everything else the colour is for has been stripped out. Only the
|
|
130
|
+
// scale reaches the width the text was indented past, so a marker style
|
|
131
|
+
// that changes the face or the size changes how the marker looks and not
|
|
132
|
+
// how much room it was given.
|
|
133
|
+
for ((start, end) in line.markerRuns) {
|
|
134
|
+
applyTextStyle(ssb, markdownStyle.orderedList.marker, start, end)
|
|
135
|
+
}
|
|
82
136
|
} finally {
|
|
83
137
|
Trace.endSection()
|
|
84
138
|
}
|
|
85
139
|
}
|
|
86
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Boxes one fenced or indented block: the room its background takes up, and
|
|
143
|
+
* the background itself.
|
|
144
|
+
*
|
|
145
|
+
* Run after the container walk rather than during it, because a block is not a
|
|
146
|
+
* container -- its range covers every line at once, where a container arrives
|
|
147
|
+
* one line at a time -- and because what it indents by is whatever the
|
|
148
|
+
* containers around it left its lines at.
|
|
149
|
+
*
|
|
150
|
+
* The indent is added to the spans those containers already left on the block's
|
|
151
|
+
* lines, rather than laid on top as a second one: a line has one authority for
|
|
152
|
+
* how far in it starts, and a quote's ribbons are drawn from that same span.
|
|
153
|
+
*/
|
|
154
|
+
private fun box(
|
|
155
|
+
ssb: SpannableStringBuilder,
|
|
156
|
+
markdownRange: MarkdownRange,
|
|
157
|
+
markdownStyle: MarkdownStyle
|
|
158
|
+
) {
|
|
159
|
+
val (start, end) = body(ssb, markdownRange) ?: return
|
|
160
|
+
|
|
161
|
+
// Horizontally the room is an indent, vertically it is space held open
|
|
162
|
+
// above and below the block, so the two axes are worked out apart.
|
|
163
|
+
val insetX = PixelUtil.toPixelFromDIP(
|
|
164
|
+
markdownStyle.pre.paddingHorizontal + markdownStyle.pre.marginHorizontal
|
|
165
|
+
)
|
|
166
|
+
val marginHorizontal = PixelUtil.toPixelFromDIP(markdownStyle.pre.marginHorizontal)
|
|
167
|
+
|
|
168
|
+
// Where the box's near edge goes: the least far in any of the block's own
|
|
169
|
+
// lines start, taken before they are moved over to make room for the padding.
|
|
170
|
+
// A line that opens with a marker starts at its marker, so the box covers
|
|
171
|
+
// that -- the `>` of a quoted block is inside the block being quoted.
|
|
172
|
+
//
|
|
173
|
+
// An indent is measured from the edge the line begins at, so this is too, and
|
|
174
|
+
// `rightToLeft` below is what says which edge that is.
|
|
175
|
+
val indents = ssb.getSpans(start, end, MarkdownBlockIndentSpan::class.java)
|
|
176
|
+
var left = 0f
|
|
177
|
+
|
|
178
|
+
if (indents.isEmpty()) {
|
|
179
|
+
setSpan(
|
|
180
|
+
ssb,
|
|
181
|
+
MarkdownBlockIndentSpan().also {
|
|
182
|
+
it.firstLineIndent = insetX
|
|
183
|
+
it.indent = insetX
|
|
184
|
+
},
|
|
185
|
+
start,
|
|
186
|
+
end
|
|
187
|
+
)
|
|
188
|
+
} else {
|
|
189
|
+
left = indents.minOf { it.firstLineIndent }
|
|
190
|
+
for (indent in indents) {
|
|
191
|
+
indent.firstLineIndent += insetX
|
|
192
|
+
indent.indent += insetX
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setSpan(
|
|
197
|
+
ssb,
|
|
198
|
+
MarkdownCodeBlockSpan(
|
|
199
|
+
markdownStyle.pre.backgroundColor ?: Color.TRANSPARENT,
|
|
200
|
+
PixelUtil.toPixelFromDIP(markdownStyle.pre.borderRadius),
|
|
201
|
+
PixelUtil.toPixelFromDIP(markdownStyle.pre.paddingVertical),
|
|
202
|
+
marginHorizontal,
|
|
203
|
+
PixelUtil.toPixelFromDIP(markdownStyle.pre.marginVertical),
|
|
204
|
+
left + marginHorizontal,
|
|
205
|
+
rightToLeft(ssb, start, end),
|
|
206
|
+
start,
|
|
207
|
+
end,
|
|
208
|
+
// A border that names no colour takes the run's own, which is the one
|
|
209
|
+
// colour the style is certain to have an opinion about.
|
|
210
|
+
markdownStyle.pre.borderColor ?: markdownStyle.pre.color,
|
|
211
|
+
PixelUtil.toPixelFromDIP(markdownStyle.pre.borderWidth),
|
|
212
|
+
backgroundPass
|
|
213
|
+
),
|
|
214
|
+
start,
|
|
215
|
+
end
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Whether a run of the text reads right to left, which is what says which edge
|
|
221
|
+
* the shapes drawn beside it hang off.
|
|
222
|
+
*
|
|
223
|
+
* The framework's own first-strong heuristic, and deliberately so: it is the
|
|
224
|
+
* same rule a `TextView` resolves a paragraph with by default, and so the same
|
|
225
|
+
* answer `MarkdownBlockIndentSpan` is handed as `dir` when it draws a quote's
|
|
226
|
+
* ribbons on the very same line. Reimplementing it here would be one more
|
|
227
|
+
* place for the bars and the box to disagree. iOS has to work the rule out
|
|
228
|
+
* itself, in `MarkdownWritingDirection`, only because UIKit keeps its answer.
|
|
229
|
+
*
|
|
230
|
+
* `FIRSTSTRONG_LTR` rather than the view's resolved heuristic, which the
|
|
231
|
+
* formatter has no view to ask for: the two differ only over text with no
|
|
232
|
+
* strong character at all -- code that is punctuation and digits throughout --
|
|
233
|
+
* and only in an app whose own layout direction is right to left.
|
|
234
|
+
*/
|
|
235
|
+
private fun rightToLeft(ssb: SpannableStringBuilder, start: Int, end: Int): Boolean =
|
|
236
|
+
TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(ssb, start, end - start)
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Boxes one inline run -- a mention, or a run of code: the room its background
|
|
240
|
+
* takes up, and the background itself.
|
|
241
|
+
*
|
|
242
|
+
* The room is held open by widening the characters the run starts and ends
|
|
243
|
+
* with, which is the one way a line can be made to leave space around a run
|
|
244
|
+
* of itself: a paragraph span would move the whole line, and a background
|
|
245
|
+
* colour would simply sit under whatever is next to it. Only the horizontal
|
|
246
|
+
* lengths are held open that way; vertically the box grows into the line's own
|
|
247
|
+
* spacing instead -- one run cannot push the lines around it apart.
|
|
248
|
+
*
|
|
249
|
+
* The lengths arrive in dp, as they are written in a style.
|
|
250
|
+
*/
|
|
251
|
+
private fun chip(
|
|
252
|
+
ssb: SpannableStringBuilder,
|
|
253
|
+
start: Int,
|
|
254
|
+
end: Int,
|
|
255
|
+
style: MarkdownBoxStyle
|
|
256
|
+
) {
|
|
257
|
+
val padding = PixelUtil.toPixelFromDIP(style.paddingHorizontal)
|
|
258
|
+
val margin = PixelUtil.toPixelFromDIP(style.marginHorizontal)
|
|
259
|
+
|
|
260
|
+
setSpan(
|
|
261
|
+
ssb,
|
|
262
|
+
MarkdownBackgroundSpan(
|
|
263
|
+
style.backgroundColor ?: Color.TRANSPARENT,
|
|
264
|
+
PixelUtil.toPixelFromDIP(style.borderRadius),
|
|
265
|
+
start,
|
|
266
|
+
end,
|
|
267
|
+
PixelUtil.toPixelFromDIP(style.paddingVertical),
|
|
268
|
+
margin,
|
|
269
|
+
// A border that names no colour takes the run's own, which is the one
|
|
270
|
+
// colour the style is certain to have an opinion about.
|
|
271
|
+
style.borderColor ?: style.color,
|
|
272
|
+
PixelUtil.toPixelFromDIP(style.borderWidth),
|
|
273
|
+
backgroundPass
|
|
274
|
+
),
|
|
275
|
+
start,
|
|
276
|
+
end
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
val gap = padding + margin
|
|
280
|
+
if (gap <= 0f) {
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// The gap spans stand in for the characters they cover, so they draw them
|
|
285
|
+
// too and need the colour the run is drawn in.
|
|
286
|
+
val color = style.color ?: Color.BLACK
|
|
287
|
+
|
|
288
|
+
// Whole code points, so a gap never lands on half a surrogate pair.
|
|
289
|
+
val afterFirst = Character.offsetByCodePoints(ssb, start, 1)
|
|
290
|
+
val beforeLast = Character.offsetByCodePoints(ssb, end, -1)
|
|
291
|
+
|
|
292
|
+
if (afterFirst >= end) {
|
|
293
|
+
// One character long: it opens and closes the run at once.
|
|
294
|
+
setSpan(ssb, MarkdownGapSpan(gap, gap, color), start, end)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
setSpan(ssb, MarkdownGapSpan(leading = gap, color = color), start, afterFirst)
|
|
299
|
+
setSpan(ssb, MarkdownGapSpan(trailing = gap, color = color), beforeLast, end)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* The lines a block actually occupies: its range with the line breaks it opens
|
|
304
|
+
* and closes with taken off.
|
|
305
|
+
*
|
|
306
|
+
* A display renderer removes the opening fence and leaves the break that ended
|
|
307
|
+
* it, so a block's range there begins on the line above its first line of code.
|
|
308
|
+
* Boxing that break would paint a box around the line above.
|
|
309
|
+
*/
|
|
310
|
+
private fun body(ssb: SpannableStringBuilder, markdownRange: MarkdownRange): Pair<Int, Int>? {
|
|
311
|
+
var start = markdownRange.start
|
|
312
|
+
var end = markdownRange.end
|
|
313
|
+
|
|
314
|
+
while (start < end && isLineBreak(ssb[start])) {
|
|
315
|
+
start++
|
|
316
|
+
}
|
|
317
|
+
while (end > start && isLineBreak(ssb[end - 1])) {
|
|
318
|
+
end--
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return if (end > start) start to end else null
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private fun isLineBreak(character: Char): Boolean = character == '\n' || character == '\r'
|
|
325
|
+
|
|
87
326
|
/** The gutter a container reserves for itself, or null if it is not one. */
|
|
88
327
|
private fun gutterOf(markdownRange: MarkdownRange, markdownStyle: MarkdownStyle): Float? {
|
|
89
328
|
val step = when (markdownRange.type) {
|
|
90
329
|
"blockquote" ->
|
|
91
|
-
markdownStyle.
|
|
92
|
-
markdownStyle.
|
|
93
|
-
markdownStyle.
|
|
330
|
+
markdownStyle.blockquote.marginStart +
|
|
331
|
+
markdownStyle.blockquote.borderWidth +
|
|
332
|
+
markdownStyle.blockquote.paddingStart
|
|
94
333
|
|
|
95
334
|
"list-ordered" ->
|
|
96
|
-
markdownStyle.
|
|
335
|
+
markdownStyle.orderedList.marginStart + markdownStyle.orderedList.paddingStart
|
|
97
336
|
|
|
98
337
|
"list-unordered" ->
|
|
99
|
-
markdownStyle.
|
|
338
|
+
markdownStyle.unorderedList.marginStart + markdownStyle.unorderedList.paddingStart
|
|
100
339
|
|
|
101
340
|
else -> return null
|
|
102
341
|
}
|
|
@@ -119,13 +358,28 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
119
358
|
*/
|
|
120
359
|
private class BlockLayout(
|
|
121
360
|
private val markdownStyle: MarkdownStyle,
|
|
122
|
-
private val textPaint: TextPaint
|
|
361
|
+
private val textPaint: TextPaint?,
|
|
362
|
+
private val display: Boolean
|
|
123
363
|
) {
|
|
124
364
|
/** Scratch paint, reset from [textPaint] before each measurement. */
|
|
125
365
|
private val measurePaint = TextPaint()
|
|
126
366
|
|
|
367
|
+
/** The size the wrapped `Text` draws at, in pixels; -1 until it is asked for. */
|
|
368
|
+
private var baseSize = -1f
|
|
369
|
+
|
|
127
370
|
/** Last marker seen for each container type, for the lines that continue it. */
|
|
128
371
|
private val markers = HashMap<String, MarkdownRange>()
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* The ordered markers this walk drew, styled once it is over. See
|
|
375
|
+
* [applyRanges].
|
|
376
|
+
*
|
|
377
|
+
* Unordered ones are left out: an unordered marker has no glyph left to
|
|
378
|
+
* style -- its character is drawn clear and a circle put in its advance, in
|
|
379
|
+
* the same colour this would have applied -- and painting a foreground
|
|
380
|
+
* colour back over it would bring the character out of hiding.
|
|
381
|
+
*/
|
|
382
|
+
val markerRuns = mutableListOf<Pair<Int, Int>>()
|
|
129
383
|
private var lineStart = -1
|
|
130
384
|
/** Offset reached so far, from the line's own left edge. */
|
|
131
385
|
private var offset = 0f
|
|
@@ -150,13 +404,14 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
150
404
|
textStart = -1f
|
|
151
405
|
padded = null
|
|
152
406
|
span = MarkdownBlockIndentSpan(
|
|
153
|
-
markdownStyle.
|
|
154
|
-
PixelUtil.toPixelFromDIP(markdownStyle.
|
|
407
|
+
markdownStyle.blockquote.borderColor ?: Color.TRANSPARENT,
|
|
408
|
+
PixelUtil.toPixelFromDIP(markdownStyle.blockquote.borderWidth),
|
|
155
409
|
PixelUtil.toPixelFromDIP(
|
|
156
|
-
markdownStyle.
|
|
157
|
-
markdownStyle.
|
|
158
|
-
markdownStyle.
|
|
159
|
-
)
|
|
410
|
+
markdownStyle.blockquote.marginStart +
|
|
411
|
+
markdownStyle.blockquote.borderWidth +
|
|
412
|
+
markdownStyle.blockquote.paddingStart
|
|
413
|
+
),
|
|
414
|
+
PixelUtil.toPixelFromDIP(markdownStyle.blockquote.borderRadius)
|
|
160
415
|
).also {
|
|
161
416
|
ssb.setSpan(it, markdownRange.start, markdownRange.end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
162
417
|
}
|
|
@@ -164,22 +419,24 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
164
419
|
|
|
165
420
|
val indent = span ?: return
|
|
166
421
|
|
|
167
|
-
padded?.let {
|
|
168
|
-
ssb.setSpan(
|
|
169
|
-
MarkdownGapSpan(gutter, markdownStyle.syntaxColor),
|
|
170
|
-
it.end - 1,
|
|
171
|
-
it.end,
|
|
172
|
-
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
173
|
-
)
|
|
174
|
-
}
|
|
422
|
+
padded?.let { hold(ssb, it, gutter) }
|
|
175
423
|
|
|
176
424
|
if (markdownRange.type == "blockquote") {
|
|
177
|
-
indent.ribbonOffset =
|
|
425
|
+
indent.ribbonOffset =
|
|
426
|
+
offset + PixelUtil.toPixelFromDIP(markdownStyle.blockquote.marginStart)
|
|
178
427
|
indent.depth = markdownRange.depth
|
|
179
428
|
}
|
|
180
429
|
|
|
181
430
|
offset += gutter
|
|
182
431
|
|
|
432
|
+
// The room either side of a list marker is indent on the way in -- the
|
|
433
|
+
// marker moves right with the text -- and held open with a gap on the way
|
|
434
|
+
// out, which is the only thing that opens space after a character.
|
|
435
|
+
val marker = prefix ?: markers[markdownRange.type]
|
|
436
|
+
val padding = if (marker == null) 0f else markerPadding(markdownRange.type)
|
|
437
|
+
|
|
438
|
+
offset += padding
|
|
439
|
+
|
|
183
440
|
if (prefix != null) {
|
|
184
441
|
markers[markdownRange.type] = prefix
|
|
185
442
|
if (textStart < 0f) {
|
|
@@ -187,8 +444,15 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
187
444
|
}
|
|
188
445
|
}
|
|
189
446
|
|
|
190
|
-
(
|
|
191
|
-
|
|
447
|
+
if (marker != null) {
|
|
448
|
+
// Only decorated on the line the marker is written on: a line continuing
|
|
449
|
+
// the block reuses the one above, which has been drawn already.
|
|
450
|
+
offset += placeMarker(ssb, marker, markdownRange.type, prefix != null)
|
|
451
|
+
offset += padding
|
|
452
|
+
|
|
453
|
+
if (prefix != null) {
|
|
454
|
+
hold(ssb, marker, padding)
|
|
455
|
+
}
|
|
192
456
|
}
|
|
193
457
|
|
|
194
458
|
padded = prefix
|
|
@@ -196,6 +460,195 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
196
460
|
indent.indent = offset
|
|
197
461
|
}
|
|
198
462
|
|
|
463
|
+
/**
|
|
464
|
+
* Holds `amount` of space open after a marker, adding to whatever is already
|
|
465
|
+
* holding some.
|
|
466
|
+
*
|
|
467
|
+
* The padding around a marker and the gutter of the container nested inside
|
|
468
|
+
* it land on the same character, and a second replacement span there would
|
|
469
|
+
* replace the first rather than add to it -- only one of them is ever asked
|
|
470
|
+
* for a width.
|
|
471
|
+
*/
|
|
472
|
+
private fun hold(ssb: SpannableStringBuilder, marker: MarkdownRange, amount: Float) {
|
|
473
|
+
if (amount <= 0f) {
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
val slot = marker.end - 1
|
|
478
|
+
|
|
479
|
+
ssb.getSpans(slot, marker.end, MarkdownBulletSpan::class.java)
|
|
480
|
+
.firstOrNull { ssb.getSpanStart(it) == slot }
|
|
481
|
+
?.let {
|
|
482
|
+
it.trailing += amount
|
|
483
|
+
return
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
ssb.getSpans(slot, marker.end, MarkdownGapSpan::class.java)
|
|
487
|
+
.firstOrNull { ssb.getSpanStart(it) == slot }
|
|
488
|
+
?.let {
|
|
489
|
+
it.trailing += amount
|
|
490
|
+
return
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
ssb.setSpan(
|
|
494
|
+
MarkdownGapSpan(trailing = amount, color = markdownStyle.syntax.color ?: Color.BLACK),
|
|
495
|
+
slot,
|
|
496
|
+
marker.end,
|
|
497
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Room held open either side of a list's marker, or nothing at all for a
|
|
503
|
+
* container whose marker is shown rather than rendered.
|
|
504
|
+
*/
|
|
505
|
+
private fun markerPadding(type: String): Float {
|
|
506
|
+
if (!display) {
|
|
507
|
+
return 0f
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return PixelUtil.toPixelFromDIP(
|
|
511
|
+
when (type) {
|
|
512
|
+
"list-ordered" -> markdownStyle.orderedList.markerPadding
|
|
513
|
+
"list-unordered" -> markdownStyle.unorderedList.markerPadding
|
|
514
|
+
else -> return 0f
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* The width a prefix's glyphs take up, drawing whatever a display draws its
|
|
521
|
+
* marker as on the way past.
|
|
522
|
+
*
|
|
523
|
+
* A display renders the marker rather than showing it: an unordered item's
|
|
524
|
+
* `-` becomes a circle, an ordered item's `1.` stays the number it means but
|
|
525
|
+
* at its own scale. Both change how wide the prefix comes out, and the
|
|
526
|
+
* containers nested inside it start after that width, so the two are worked
|
|
527
|
+
* out in one place.
|
|
528
|
+
*/
|
|
529
|
+
private fun placeMarker(
|
|
530
|
+
ssb: SpannableStringBuilder,
|
|
531
|
+
marker: MarkdownRange,
|
|
532
|
+
type: String,
|
|
533
|
+
decorate: Boolean
|
|
534
|
+
): Float {
|
|
535
|
+
val width = measure(ssb, marker.start, marker.end)
|
|
536
|
+
|
|
537
|
+
if (!display) {
|
|
538
|
+
return width
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
val run = markerRun(ssb, marker) ?: return width
|
|
542
|
+
val base = baseTextSize(ssb)
|
|
543
|
+
|
|
544
|
+
if (base <= 0f) {
|
|
545
|
+
return width
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return when (type) {
|
|
549
|
+
"list-unordered" -> {
|
|
550
|
+
val diameter = base * markdownStyle.unorderedList.markerScale
|
|
551
|
+
// A whole pixel, because that is what the span can report as an
|
|
552
|
+
// advance, and the indent has to agree with it exactly.
|
|
553
|
+
val box = ceil(diameter).toInt()
|
|
554
|
+
|
|
555
|
+
if (decorate) {
|
|
556
|
+
ssb.setSpan(
|
|
557
|
+
MarkdownBulletSpan(
|
|
558
|
+
markdownStyle.unorderedList.marker.color ?: Color.BLACK,
|
|
559
|
+
diameter,
|
|
560
|
+
box
|
|
561
|
+
),
|
|
562
|
+
run.first,
|
|
563
|
+
run.second,
|
|
564
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// The marker's own glyph is not drawn, so the circle's width stands in
|
|
569
|
+
// for it and the rest of the prefix -- an indent in front, the space
|
|
570
|
+
// behind -- measures as it always did.
|
|
571
|
+
width - measure(ssb, run.first, run.second) + box
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
"list-ordered" -> {
|
|
575
|
+
if (!decorate) {
|
|
576
|
+
// The scaling is already on the string, so the width above is the
|
|
577
|
+
// one the number is drawn at.
|
|
578
|
+
return width
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
ssb.setSpan(
|
|
582
|
+
MarkdownFontSizeSpan(
|
|
583
|
+
PixelUtil.toDIPFromPixel(base * markdownStyle.orderedList.markerScale)
|
|
584
|
+
),
|
|
585
|
+
run.first,
|
|
586
|
+
run.second,
|
|
587
|
+
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
588
|
+
)
|
|
589
|
+
markerRuns.add(run)
|
|
590
|
+
|
|
591
|
+
measure(ssb, marker.start, marker.end)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
else -> width
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* The marker at the end of a prefix: the `-` of a bullet or the `1.` of a
|
|
600
|
+
* numbered item, without the indent in front of it or the space after it.
|
|
601
|
+
*
|
|
602
|
+
* Read off the text rather than taken from the `syntax` range covering it,
|
|
603
|
+
* because a prefix runs from wherever the previous container's marker ended
|
|
604
|
+
* and so can carry another container's marker with it -- the ordered list in
|
|
605
|
+
* `- > 1. ` is handed `> 1. `. The last run of non-blanks is this
|
|
606
|
+
* container's own.
|
|
607
|
+
*/
|
|
608
|
+
private fun markerRun(ssb: SpannableStringBuilder, prefix: MarkdownRange): Pair<Int, Int>? {
|
|
609
|
+
var end = prefix.end
|
|
610
|
+
while (end > prefix.start && isBlank(ssb[end - 1])) {
|
|
611
|
+
end--
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
var start = end
|
|
615
|
+
while (start > prefix.start && !isBlank(ssb[start - 1])) {
|
|
616
|
+
start--
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return if (end > start) start to end else null
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private fun isBlank(character: Char): Boolean = character == ' ' || character == '\t'
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* The size the wrapped `Text` draws at, which is what a marker is drawn in
|
|
626
|
+
* proportion to.
|
|
627
|
+
*
|
|
628
|
+
* The view's own paint knows it; the measure path has no view, and takes it
|
|
629
|
+
* from the size React Native left over the whole string. Only a span
|
|
630
|
+
* covering all of it will do -- one covering part of it is a nested `Text`
|
|
631
|
+
* or a heading, neither of which is the base.
|
|
632
|
+
*/
|
|
633
|
+
private fun baseTextSize(ssb: SpannableStringBuilder): Float {
|
|
634
|
+
if (baseSize >= 0f) {
|
|
635
|
+
return baseSize
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
baseSize = textPaint?.textSize
|
|
639
|
+
?: ssb.getSpans(0, ssb.length, AbsoluteSizeSpan::class.java)
|
|
640
|
+
.firstOrNull {
|
|
641
|
+
it !is MarkdownSpan &&
|
|
642
|
+
ssb.getSpanStart(it) == 0 &&
|
|
643
|
+
ssb.getSpanEnd(it) == ssb.length
|
|
644
|
+
}
|
|
645
|
+
?.size
|
|
646
|
+
?.toFloat()
|
|
647
|
+
?: 0f
|
|
648
|
+
|
|
649
|
+
return baseSize
|
|
650
|
+
}
|
|
651
|
+
|
|
199
652
|
/**
|
|
200
653
|
* Width a marker renders at.
|
|
201
654
|
*
|
|
@@ -219,6 +672,28 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
219
672
|
}
|
|
220
673
|
}
|
|
221
674
|
|
|
675
|
+
/**
|
|
676
|
+
* The entry a type is styled from, or null for a type that carries no text
|
|
677
|
+
* attributes of its own.
|
|
678
|
+
*
|
|
679
|
+
* A fenced block's language is its own type so that something can read it, but
|
|
680
|
+
* in an input it is part of the fence you are typing and reads as markup, so it
|
|
681
|
+
* is styled like the rest of it.
|
|
682
|
+
*/
|
|
683
|
+
private fun textStyleOf(type: String, markdownStyle: MarkdownStyle): MarkdownTextStyle? =
|
|
684
|
+
when (type) {
|
|
685
|
+
"syntax", "language" -> markdownStyle.syntax
|
|
686
|
+
"link" -> markdownStyle.link
|
|
687
|
+
"strong" -> markdownStyle.strong
|
|
688
|
+
"emphasis" -> markdownStyle.emphasis
|
|
689
|
+
"strikethrough" -> markdownStyle.strikethrough
|
|
690
|
+
"heading" -> markdownStyle.heading
|
|
691
|
+
"code" -> markdownStyle.code
|
|
692
|
+
"pre" -> markdownStyle.pre
|
|
693
|
+
"mention" -> markdownStyle.mention
|
|
694
|
+
else -> null
|
|
695
|
+
}
|
|
696
|
+
|
|
222
697
|
private fun applyRange(
|
|
223
698
|
ssb: SpannableStringBuilder,
|
|
224
699
|
markdownRange: MarkdownRange,
|
|
@@ -226,104 +701,168 @@ class MarkdownFormatter(private val assetManager: AssetManager) {
|
|
|
226
701
|
) {
|
|
227
702
|
val start = markdownRange.start
|
|
228
703
|
val end = markdownRange.end
|
|
229
|
-
when (markdownRange.type) {
|
|
230
|
-
"bold" -> setSpan(ssb, MarkdownBoldSpan(), start, end)
|
|
231
704
|
|
|
232
|
-
|
|
705
|
+
// Before the text style, because the size span it ends with has to be set
|
|
706
|
+
// after any line-height span or the line jumps as it is measured again.
|
|
707
|
+
if (markdownRange.type == "heading") {
|
|
708
|
+
applyHeadingLineHeight(ssb, markdownRange, markdownStyle, start, end)
|
|
709
|
+
}
|
|
233
710
|
|
|
234
|
-
|
|
711
|
+
textStyleOf(markdownRange.type, markdownStyle)?.let { style ->
|
|
712
|
+
applyTextStyle(
|
|
713
|
+
ssb,
|
|
714
|
+
style,
|
|
715
|
+
start,
|
|
716
|
+
end,
|
|
717
|
+
// A heading's size is the one thing a style cannot state outright: level
|
|
718
|
+
// N is the base size scaled N-1 times, and the level only arrives here.
|
|
719
|
+
fontSize = if (markdownRange.type == "heading") {
|
|
720
|
+
headingFontSize(markdownRange.depth, markdownStyle.heading)
|
|
721
|
+
} else {
|
|
722
|
+
null
|
|
723
|
+
}
|
|
724
|
+
)
|
|
725
|
+
}
|
|
235
726
|
|
|
727
|
+
when (markdownRange.type) {
|
|
236
728
|
"emoji" -> {
|
|
237
|
-
|
|
238
|
-
ssb,
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
setSpan(ssb, MarkdownFontSizeSpan(markdownStyle.emojiFontSize), start, end)
|
|
729
|
+
markdownStyle.emoji.fontFamily?.let {
|
|
730
|
+
setSpan(ssb, MarkdownFontFamilySpan(it, assetManager), start, end)
|
|
731
|
+
}
|
|
732
|
+
if (markdownStyle.emoji.fontSize > 0f) {
|
|
733
|
+
setSpan(ssb, MarkdownFontSizeSpan(markdownStyle.emoji.fontSize), start, end)
|
|
734
|
+
}
|
|
244
735
|
}
|
|
245
736
|
|
|
246
|
-
"mention
|
|
247
|
-
setSpan(ssb, MarkdownForegroundColorSpan(markdownStyle.mentionHereColor), start, end)
|
|
248
|
-
setSpan(
|
|
249
|
-
ssb,
|
|
250
|
-
MarkdownBackgroundSpan(
|
|
251
|
-
markdownStyle.mentionHereBackgroundColor,
|
|
252
|
-
markdownStyle.mentionHereBorderRadius,
|
|
253
|
-
start,
|
|
254
|
-
end
|
|
255
|
-
),
|
|
256
|
-
start,
|
|
257
|
-
end
|
|
258
|
-
)
|
|
259
|
-
}
|
|
737
|
+
"mention" -> chip(ssb, start, end, markdownStyle.mention)
|
|
260
738
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
MarkdownBackgroundSpan(
|
|
267
|
-
markdownStyle.mentionUserBackgroundColor,
|
|
268
|
-
markdownStyle.mentionUserBorderRadius,
|
|
269
|
-
start,
|
|
270
|
-
end
|
|
271
|
-
),
|
|
272
|
-
start,
|
|
273
|
-
end
|
|
274
|
-
)
|
|
275
|
-
}
|
|
739
|
+
// `pre` gets only its text attributes: what a block is drawn on is a box
|
|
740
|
+
// behind the whole of it, applied from the `codeblock` range in box().
|
|
741
|
+
"code" -> chip(ssb, start, end, markdownStyle.code)
|
|
742
|
+
}
|
|
743
|
+
}
|
|
276
744
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
end
|
|
286
|
-
),
|
|
287
|
-
start,
|
|
288
|
-
end
|
|
289
|
-
)
|
|
290
|
-
}
|
|
745
|
+
/**
|
|
746
|
+
* Level N is the base size scaled N-1 times, so a single pair of style values
|
|
747
|
+
* covers all six. Null when the style names no size, which reads as unset.
|
|
748
|
+
*/
|
|
749
|
+
private fun headingFontSize(depth: Int, style: MarkdownHeadingStyle): Float? {
|
|
750
|
+
if (style.fontSize <= 0f) {
|
|
751
|
+
return null
|
|
752
|
+
}
|
|
291
753
|
|
|
292
|
-
|
|
754
|
+
var fontSize = style.fontSize
|
|
755
|
+
repeat(maxOf(depth, 1) - 1) { fontSize *= style.scale }
|
|
756
|
+
return fontSize
|
|
757
|
+
}
|
|
293
758
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
759
|
+
/**
|
|
760
|
+
* Grows a heading's line so that the larger type has somewhere to sit.
|
|
761
|
+
*
|
|
762
|
+
* Only where the style has not asked for a line height of its own: an explicit
|
|
763
|
+
* `heading.lineHeight` is the author saying what the line should be, and two
|
|
764
|
+
* line-height spans on one line would fight.
|
|
765
|
+
*/
|
|
766
|
+
private fun applyHeadingLineHeight(
|
|
767
|
+
ssb: SpannableStringBuilder,
|
|
768
|
+
markdownRange: MarkdownRange,
|
|
769
|
+
markdownStyle: MarkdownStyle,
|
|
770
|
+
start: Int,
|
|
771
|
+
end: Int
|
|
772
|
+
) {
|
|
773
|
+
val style = markdownStyle.heading
|
|
774
|
+
val fontSize = headingFontSize(markdownRange.depth, style) ?: return
|
|
298
775
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
setSpan(ssb, MarkdownForegroundColorSpan(markdownStyle.codeColor), start, end)
|
|
303
|
-
setSpan(ssb, MarkdownBackgroundColorSpan(markdownStyle.codeBackgroundColor), start, end)
|
|
304
|
-
}
|
|
776
|
+
if (style.lineHeight > 0f) {
|
|
777
|
+
return
|
|
778
|
+
}
|
|
305
779
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
780
|
+
val lineHeight = ReactLineHeight.find(ssb)
|
|
781
|
+
if (lineHeight >= 0) {
|
|
782
|
+
setSpan(
|
|
783
|
+
ssb,
|
|
784
|
+
MarkdownLineHeightSpan(lineHeight * (fontSize / style.fontSize) * 1.5f),
|
|
785
|
+
start,
|
|
786
|
+
end
|
|
787
|
+
)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
312
790
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
791
|
+
/**
|
|
792
|
+
* Puts one text style on one run.
|
|
793
|
+
*
|
|
794
|
+
* One function rather than a case per type, because every styled type now
|
|
795
|
+
* carries the same set of attributes: what tells a mention from a heading is
|
|
796
|
+
* the box around it and the size it is given, not which properties it
|
|
797
|
+
* understands. `MarkdownFormatter.swift` applies the same set as attributed
|
|
798
|
+
* string attributes, and `blockUtils` writes it as CSS.
|
|
799
|
+
*
|
|
800
|
+
* Everything unset is left alone rather than written as a zero, so a run
|
|
801
|
+
* inherits whatever the prose around it is drawn in.
|
|
802
|
+
*/
|
|
803
|
+
private fun applyTextStyle(
|
|
804
|
+
ssb: SpannableStringBuilder,
|
|
805
|
+
style: MarkdownTextStyle,
|
|
806
|
+
start: Int,
|
|
807
|
+
end: Int,
|
|
808
|
+
fontSize: Float? = null
|
|
809
|
+
) {
|
|
810
|
+
if (end <= start) {
|
|
811
|
+
return
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
style.fontFamily?.let { setSpan(ssb, MarkdownFontFamilySpan(it, assetManager), start, end) }
|
|
815
|
+
style.fontWeight?.let { setSpan(ssb, MarkdownFontWeightSpan(it), start, end) }
|
|
816
|
+
style.fontStyle?.let { setSpan(ssb, MarkdownFontStyleSpan(it), start, end) }
|
|
817
|
+
style.color?.let { setSpan(ssb, MarkdownForegroundColorSpan(it), start, end) }
|
|
818
|
+
|
|
819
|
+
if (style.letterSpacing != 0f) {
|
|
820
|
+
setSpan(ssb, MarkdownLetterSpacingSpan(style.letterSpacing), start, end)
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
applyDecoration(ssb, style, start, end)
|
|
326
824
|
|
|
825
|
+
if (style.textShadowColor != null ||
|
|
826
|
+
style.textShadowRadius > 0f ||
|
|
827
|
+
style.textShadowOffsetX != 0f ||
|
|
828
|
+
style.textShadowOffsetY != 0f
|
|
829
|
+
) {
|
|
830
|
+
setSpan(
|
|
831
|
+
ssb,
|
|
832
|
+
MarkdownTextShadowSpan(
|
|
833
|
+
style.textShadowColor ?: Color.BLACK,
|
|
834
|
+
style.textShadowOffsetX,
|
|
835
|
+
style.textShadowOffsetY,
|
|
836
|
+
style.textShadowRadius
|
|
837
|
+
),
|
|
838
|
+
start,
|
|
839
|
+
end
|
|
840
|
+
)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// The line height goes on before the size, so that a line already told how
|
|
844
|
+
// tall to be is not measured again from the larger type and made to jump.
|
|
845
|
+
if (style.lineHeight > 0f) {
|
|
846
|
+
setSpan(ssb, MarkdownAbsoluteLineHeightSpan(style.lineHeight), start, end)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
val size = fontSize ?: style.fontSize.takeIf { it > 0f }
|
|
850
|
+
size?.let { setSpan(ssb, MarkdownFontSizeSpan(it), start, end) }
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
private fun applyDecoration(
|
|
854
|
+
ssb: SpannableStringBuilder,
|
|
855
|
+
style: MarkdownTextStyle,
|
|
856
|
+
start: Int,
|
|
857
|
+
end: Int
|
|
858
|
+
) {
|
|
859
|
+
val line = style.textDecorationLine ?: return
|
|
860
|
+
|
|
861
|
+
if (line.contains("underline")) {
|
|
862
|
+
setSpan(ssb, MarkdownUnderlineSpan(), start, end)
|
|
863
|
+
}
|
|
864
|
+
if (line.contains("line-through")) {
|
|
865
|
+
setSpan(ssb, MarkdownStrikethroughSpan(), start, end)
|
|
327
866
|
}
|
|
328
867
|
}
|
|
329
868
|
|