react-native-enriched-markdown 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/LICENSE +20 -0
- package/README.md +551 -0
- package/ReactNativeEnrichedMarkdown.podspec +27 -0
- package/android/build.gradle +101 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +54 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +26 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +1388 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h +20 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/baseline-prof.txt +65 -0
- package/android/src/main/cpp/jni-adapter.cpp +220 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +270 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +15 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +173 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextPackage.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +385 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/accessibility/MarkdownAccessibilityHelper.kt +279 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLongPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkPressEvent.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/MarkdownASTNode.kt +31 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +62 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockStyleContext.kt +166 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +84 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +104 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +70 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +68 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +16 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +29 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListContextManager.kt +105 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +59 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +103 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +80 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +109 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +86 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrongRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +30 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/UnderlineRenderer.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BaseListSpan.kt +136 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +135 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +180 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBlockSpan.kt +196 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/EmphasisSpan.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/HeadingSpan.kt +38 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ImageSpan.kt +321 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +27 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +76 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/OrderedListSpan.kt +87 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrongSpan.kt +37 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/TextSpan.kt +26 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/ThematicBreakSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/UnorderedListSpan.kt +69 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +11 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +54 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeStyle.kt +21 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/EmphasisStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineImageStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +19 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +57 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +33 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrongStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +211 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +92 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ThematicBreakStyle.kt +23 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/UnderlineStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/AsyncDrawable.kt +91 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/HTMLGenerator.kt +827 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/LinkLongPressMovementMethod.kt +121 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/MarkdownExtractor.kt +375 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/SelectionActionMode.kt +139 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +183 -0
- package/android/src/main/jni/CMakeLists.txt +70 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.cpp +21 -0
- package/android/src/main/jni/EnrichedMarkdownTextSpec.h +25 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextComponentDescriptor.h +29 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.cpp +45 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextMeasurementManager.h +21 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +20 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +37 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +22 -0
- package/cpp/md4c/md4c.c +6492 -0
- package/cpp/md4c/md4c.h +402 -0
- package/cpp/parser/MD4CParser.cpp +327 -0
- package/cpp/parser/MD4CParser.hpp +27 -0
- package/cpp/parser/MarkdownASTNode.hpp +51 -0
- package/ios/EnrichedMarkdownText.h +18 -0
- package/ios/EnrichedMarkdownText.mm +1401 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.h +23 -0
- package/ios/attachments/EnrichedMarkdownImageAttachment.m +185 -0
- package/ios/attachments/ThematicBreakAttachment.h +15 -0
- package/ios/attachments/ThematicBreakAttachment.m +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp +22 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ComponentDescriptors.h +24 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +33 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +31 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +82 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +1388 -0
- package/ios/generated/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +20 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.cpp +17 -0
- package/ios/generated/EnrichedMarkdownTextSpec/ShadowNodes.h +32 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.cpp +16 -0
- package/ios/generated/EnrichedMarkdownTextSpec/States.h +20 -0
- package/ios/internals/EnrichedMarkdownTextComponentDescriptor.h +19 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.h +43 -0
- package/ios/internals/EnrichedMarkdownTextShadowNode.mm +85 -0
- package/ios/internals/EnrichedMarkdownTextState.h +24 -0
- package/ios/parser/MarkdownASTNode.h +35 -0
- package/ios/parser/MarkdownASTNode.m +32 -0
- package/ios/parser/MarkdownParser.h +17 -0
- package/ios/parser/MarkdownParser.mm +42 -0
- package/ios/parser/MarkdownParserBridge.mm +120 -0
- package/ios/renderer/AttributedRenderer.h +11 -0
- package/ios/renderer/AttributedRenderer.m +152 -0
- package/ios/renderer/BlockquoteRenderer.h +7 -0
- package/ios/renderer/BlockquoteRenderer.m +160 -0
- package/ios/renderer/CodeBlockRenderer.h +10 -0
- package/ios/renderer/CodeBlockRenderer.m +90 -0
- package/ios/renderer/CodeRenderer.h +10 -0
- package/ios/renderer/CodeRenderer.m +60 -0
- package/ios/renderer/EmphasisRenderer.h +6 -0
- package/ios/renderer/EmphasisRenderer.m +96 -0
- package/ios/renderer/HeadingRenderer.h +7 -0
- package/ios/renderer/HeadingRenderer.m +105 -0
- package/ios/renderer/ImageRenderer.h +12 -0
- package/ios/renderer/ImageRenderer.m +83 -0
- package/ios/renderer/LinkRenderer.h +7 -0
- package/ios/renderer/LinkRenderer.m +69 -0
- package/ios/renderer/ListItemRenderer.h +16 -0
- package/ios/renderer/ListItemRenderer.m +103 -0
- package/ios/renderer/ListRenderer.h +13 -0
- package/ios/renderer/ListRenderer.m +70 -0
- package/ios/renderer/NodeRenderer.h +8 -0
- package/ios/renderer/ParagraphRenderer.h +7 -0
- package/ios/renderer/ParagraphRenderer.m +80 -0
- package/ios/renderer/RenderContext.h +105 -0
- package/ios/renderer/RenderContext.m +312 -0
- package/ios/renderer/RendererFactory.h +12 -0
- package/ios/renderer/RendererFactory.m +116 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/StrongRenderer.h +6 -0
- package/ios/renderer/StrongRenderer.m +83 -0
- package/ios/renderer/TextRenderer.h +6 -0
- package/ios/renderer/TextRenderer.m +16 -0
- package/ios/renderer/ThematicBreakRenderer.h +5 -0
- package/ios/renderer/ThematicBreakRenderer.m +53 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +274 -0
- package/ios/styles/StyleConfig.mm +1806 -0
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/BlockquoteBorder.h +20 -0
- package/ios/utils/BlockquoteBorder.m +92 -0
- package/ios/utils/CodeBackground.h +19 -0
- package/ios/utils/CodeBackground.m +191 -0
- package/ios/utils/CodeBlockBackground.h +17 -0
- package/ios/utils/CodeBlockBackground.m +82 -0
- package/ios/utils/EditMenuUtils.h +22 -0
- package/ios/utils/EditMenuUtils.m +118 -0
- package/ios/utils/FontUtils.h +25 -0
- package/ios/utils/FontUtils.m +27 -0
- package/ios/utils/HTMLGenerator.h +20 -0
- package/ios/utils/HTMLGenerator.m +793 -0
- package/ios/utils/LastElementUtils.h +53 -0
- package/ios/utils/ListMarkerDrawer.h +15 -0
- package/ios/utils/ListMarkerDrawer.m +127 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.h +17 -0
- package/ios/utils/MarkdownExtractor.m +308 -0
- package/ios/utils/ParagraphStyleUtils.h +21 -0
- package/ios/utils/ParagraphStyleUtils.m +111 -0
- package/ios/utils/PasteboardUtils.h +36 -0
- package/ios/utils/PasteboardUtils.m +134 -0
- package/ios/utils/RTFExportUtils.h +24 -0
- package/ios/utils/RTFExportUtils.m +297 -0
- package/ios/utils/RuntimeKeys.h +38 -0
- package/ios/utils/RuntimeKeys.m +11 -0
- package/ios/utils/TextViewLayoutManager.h +14 -0
- package/ios/utils/TextViewLayoutManager.mm +113 -0
- package/lib/module/EnrichedMarkdownText.js +65 -0
- package/lib/module/EnrichedMarkdownText.js.map +1 -0
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/normalizeMarkdownStyle.js +384 -0
- package/lib/module/normalizeMarkdownStyle.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +183 -0
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +185 -0
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts +6 -0
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -0
- package/package.json +186 -1
- package/react-native.config.js +13 -0
- package/src/EnrichedMarkdownText.tsx +280 -0
- package/src/EnrichedMarkdownTextNativeComponent.ts +210 -0
- package/src/index.tsx +10 -0
- package/src/normalizeMarkdownStyle.ts +423 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.facebook.react.bridge.ReadableMap
|
|
5
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
6
|
+
import com.facebook.react.uimanager.SimpleViewManager
|
|
7
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import com.facebook.react.uimanager.UIManagerHelper
|
|
9
|
+
import com.facebook.react.uimanager.ViewManagerDelegate
|
|
10
|
+
import com.facebook.react.uimanager.annotations.ReactProp
|
|
11
|
+
import com.facebook.react.viewmanagers.EnrichedMarkdownTextManagerDelegate
|
|
12
|
+
import com.facebook.react.viewmanagers.EnrichedMarkdownTextManagerInterface
|
|
13
|
+
import com.facebook.yoga.YogaMeasureMode
|
|
14
|
+
import com.swmansion.enriched.markdown.events.LinkLongPressEvent
|
|
15
|
+
import com.swmansion.enriched.markdown.events.LinkPressEvent
|
|
16
|
+
import com.swmansion.enriched.markdown.parser.Md4cFlags
|
|
17
|
+
|
|
18
|
+
@ReactModule(name = EnrichedMarkdownTextManager.NAME)
|
|
19
|
+
class EnrichedMarkdownTextManager :
|
|
20
|
+
SimpleViewManager<EnrichedMarkdownText>(),
|
|
21
|
+
EnrichedMarkdownTextManagerInterface<EnrichedMarkdownText> {
|
|
22
|
+
private val mDelegate: ViewManagerDelegate<EnrichedMarkdownText> = EnrichedMarkdownTextManagerDelegate(this)
|
|
23
|
+
|
|
24
|
+
override fun getDelegate(): ViewManagerDelegate<EnrichedMarkdownText>? = mDelegate
|
|
25
|
+
|
|
26
|
+
override fun getName(): String = NAME
|
|
27
|
+
|
|
28
|
+
override fun createViewInstance(reactContext: ThemedReactContext): EnrichedMarkdownText = EnrichedMarkdownText(reactContext)
|
|
29
|
+
|
|
30
|
+
override fun onDropViewInstance(view: EnrichedMarkdownText) {
|
|
31
|
+
super.onDropViewInstance(view)
|
|
32
|
+
MeasurementStore.clearFontScalingSettings(view.id)
|
|
33
|
+
view.layoutManager.releaseMeasurementStore()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
|
|
37
|
+
val map = mutableMapOf<String, Any>()
|
|
38
|
+
map.put(LinkPressEvent.EVENT_NAME, mapOf("registrationName" to LinkPressEvent.EVENT_NAME))
|
|
39
|
+
map.put(LinkLongPressEvent.EVENT_NAME, mapOf("registrationName" to LinkLongPressEvent.EVENT_NAME))
|
|
40
|
+
return map
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@ReactProp(name = "markdown")
|
|
44
|
+
override fun setMarkdown(
|
|
45
|
+
view: EnrichedMarkdownText?,
|
|
46
|
+
markdown: String?,
|
|
47
|
+
) {
|
|
48
|
+
view?.setOnLinkPressCallback { url ->
|
|
49
|
+
emitOnLinkPress(view, url)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
view?.setOnLinkLongPressCallback { url ->
|
|
53
|
+
emitOnLinkLongPress(view, url)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
view?.setMarkdownContent(markdown ?: "No markdown content")
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@ReactProp(name = "markdownStyle")
|
|
60
|
+
override fun setMarkdownStyle(
|
|
61
|
+
view: EnrichedMarkdownText?,
|
|
62
|
+
style: com.facebook.react.bridge.ReadableMap?,
|
|
63
|
+
) {
|
|
64
|
+
view?.setMarkdownStyle(style)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@ReactProp(name = "selectable", defaultBoolean = true)
|
|
68
|
+
override fun setSelectable(
|
|
69
|
+
view: EnrichedMarkdownText?,
|
|
70
|
+
selectable: Boolean,
|
|
71
|
+
) {
|
|
72
|
+
view?.setIsSelectable(selectable)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@ReactProp(name = "md4cFlags")
|
|
76
|
+
override fun setMd4cFlags(
|
|
77
|
+
view: EnrichedMarkdownText?,
|
|
78
|
+
flags: ReadableMap?,
|
|
79
|
+
) {
|
|
80
|
+
val md4cFlags =
|
|
81
|
+
Md4cFlags(
|
|
82
|
+
underline = flags?.getBoolean("underline") ?: false,
|
|
83
|
+
)
|
|
84
|
+
view?.setMd4cFlags(md4cFlags)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
@ReactProp(name = "allowFontScaling", defaultBoolean = true)
|
|
88
|
+
override fun setAllowFontScaling(
|
|
89
|
+
view: EnrichedMarkdownText?,
|
|
90
|
+
allowFontScaling: Boolean,
|
|
91
|
+
) {
|
|
92
|
+
view?.setAllowFontScaling(allowFontScaling)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@ReactProp(name = "maxFontSizeMultiplier", defaultFloat = 0f)
|
|
96
|
+
override fun setMaxFontSizeMultiplier(
|
|
97
|
+
view: EnrichedMarkdownText?,
|
|
98
|
+
maxFontSizeMultiplier: Float,
|
|
99
|
+
) {
|
|
100
|
+
view?.setMaxFontSizeMultiplier(maxFontSizeMultiplier)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@ReactProp(name = "allowTrailingMargin", defaultBoolean = false)
|
|
104
|
+
override fun setAllowTrailingMargin(
|
|
105
|
+
view: EnrichedMarkdownText?,
|
|
106
|
+
allowTrailingMargin: Boolean,
|
|
107
|
+
) {
|
|
108
|
+
view?.setAllowTrailingMargin(allowTrailingMargin)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@ReactProp(name = "enableLinkPreview", defaultBoolean = true)
|
|
112
|
+
override fun setEnableLinkPreview(
|
|
113
|
+
view: EnrichedMarkdownText?,
|
|
114
|
+
enableLinkPreview: Boolean,
|
|
115
|
+
) {
|
|
116
|
+
// This prop is only used on iOS (to control the system link preview on long press).
|
|
117
|
+
// Required by the codegen interface but is a no-op on Android.
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override fun setPadding(
|
|
121
|
+
view: EnrichedMarkdownText,
|
|
122
|
+
left: Int,
|
|
123
|
+
top: Int,
|
|
124
|
+
right: Int,
|
|
125
|
+
bottom: Int,
|
|
126
|
+
) {
|
|
127
|
+
super.setPadding(view, left, top, right, bottom)
|
|
128
|
+
view.setPadding(left, top, right, bottom)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun emitOnLinkPress(
|
|
132
|
+
view: EnrichedMarkdownText,
|
|
133
|
+
url: String,
|
|
134
|
+
) {
|
|
135
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
136
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
137
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
138
|
+
val event = LinkPressEvent(surfaceId, view.id, url)
|
|
139
|
+
|
|
140
|
+
eventDispatcher?.dispatchEvent(event)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private fun emitOnLinkLongPress(
|
|
144
|
+
view: EnrichedMarkdownText,
|
|
145
|
+
url: String,
|
|
146
|
+
) {
|
|
147
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
148
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
149
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
150
|
+
val event = LinkLongPressEvent(surfaceId, view.id, url)
|
|
151
|
+
|
|
152
|
+
eventDispatcher?.dispatchEvent(event)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
override fun measure(
|
|
156
|
+
context: Context,
|
|
157
|
+
localData: ReadableMap?,
|
|
158
|
+
props: ReadableMap?,
|
|
159
|
+
state: ReadableMap?,
|
|
160
|
+
width: Float,
|
|
161
|
+
widthMode: YogaMeasureMode?,
|
|
162
|
+
height: Float,
|
|
163
|
+
heightMode: YogaMeasureMode?,
|
|
164
|
+
attachmentsPositions: FloatArray?,
|
|
165
|
+
): Long {
|
|
166
|
+
val id = localData?.getInt("viewTag")
|
|
167
|
+
return MeasurementStore.getMeasureById(context, id, width, height, heightMode, props)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
companion object {
|
|
171
|
+
const val NAME = "EnrichedMarkdownText"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import java.util.ArrayList
|
|
8
|
+
|
|
9
|
+
class EnrichedMarkdownTextPackage : ReactPackage {
|
|
10
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
11
|
+
val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
|
|
12
|
+
viewManagers.add(EnrichedMarkdownTextManager())
|
|
13
|
+
return viewManagers
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = emptyList()
|
|
17
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Typeface
|
|
5
|
+
import android.graphics.text.LineBreaker
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.text.StaticLayout
|
|
8
|
+
import android.text.TextPaint
|
|
9
|
+
import android.util.Log
|
|
10
|
+
import com.facebook.react.bridge.ReadableMap
|
|
11
|
+
import com.facebook.react.uimanager.PixelUtil
|
|
12
|
+
import com.facebook.yoga.YogaMeasureMode
|
|
13
|
+
import com.facebook.yoga.YogaMeasureOutput
|
|
14
|
+
import com.swmansion.enriched.markdown.parser.Md4cFlags
|
|
15
|
+
import com.swmansion.enriched.markdown.parser.Parser
|
|
16
|
+
import com.swmansion.enriched.markdown.renderer.Renderer
|
|
17
|
+
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
18
|
+
import com.swmansion.enriched.markdown.utils.getBooleanOrDefault
|
|
19
|
+
import com.swmansion.enriched.markdown.utils.getMapOrNull
|
|
20
|
+
import com.swmansion.enriched.markdown.utils.getStringOrDefault
|
|
21
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
22
|
+
import kotlin.math.ceil
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Manages text measurements for ShadowNode layout.
|
|
26
|
+
* Parses and renders markdown to Spannable at measure time for accurate height calculation.
|
|
27
|
+
*/
|
|
28
|
+
object MeasurementStore {
|
|
29
|
+
private const val TAG = "MeasurementStore"
|
|
30
|
+
|
|
31
|
+
private data class PaintParams(
|
|
32
|
+
val typeface: Typeface,
|
|
33
|
+
val fontSize: Float,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
private data class MeasurementParams(
|
|
37
|
+
val cachedWidth: Float,
|
|
38
|
+
val cachedSize: Long,
|
|
39
|
+
val spannable: CharSequence?,
|
|
40
|
+
val paintParams: PaintParams,
|
|
41
|
+
val markdownHash: Int,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
private val data = ConcurrentHashMap<Int, MeasurementParams>()
|
|
45
|
+
|
|
46
|
+
// Store font scaling settings per view ID
|
|
47
|
+
private data class FontScalingSettings(
|
|
48
|
+
val allowFontScaling: Boolean = true,
|
|
49
|
+
val maxFontSizeMultiplier: Float = 0f,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
private val fontScalingSettings = ConcurrentHashMap<Int, FontScalingSettings>()
|
|
53
|
+
|
|
54
|
+
private fun resolveFontScalingSettings(
|
|
55
|
+
viewId: Int?,
|
|
56
|
+
props: ReadableMap?,
|
|
57
|
+
): FontScalingSettings {
|
|
58
|
+
val stored = viewId?.let { fontScalingSettings[it] }
|
|
59
|
+
return FontScalingSettings(
|
|
60
|
+
allowFontScaling =
|
|
61
|
+
props?.takeIf { it.hasKey("allowFontScaling") }?.getBoolean("allowFontScaling")
|
|
62
|
+
?: stored?.allowFontScaling
|
|
63
|
+
?: true,
|
|
64
|
+
maxFontSizeMultiplier =
|
|
65
|
+
props?.takeIf { it.hasKey("maxFontSizeMultiplier") }?.getDouble("maxFontSizeMultiplier")?.toFloat()
|
|
66
|
+
?: stored?.maxFontSizeMultiplier
|
|
67
|
+
?: 0f,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private val measurePaint = TextPaint()
|
|
72
|
+
private val measureRenderer = Renderer()
|
|
73
|
+
|
|
74
|
+
@Volatile
|
|
75
|
+
private var lastKnownFontScale: Float = 1.0f
|
|
76
|
+
|
|
77
|
+
/** Updates measurement with rendered Spannable. Returns true if height changed. */
|
|
78
|
+
fun store(
|
|
79
|
+
id: Int,
|
|
80
|
+
spannable: CharSequence?,
|
|
81
|
+
paint: TextPaint,
|
|
82
|
+
): Boolean {
|
|
83
|
+
val cached = data[id]
|
|
84
|
+
val width = cached?.cachedWidth ?: 0f
|
|
85
|
+
val oldSize = cached?.cachedSize ?: 0L
|
|
86
|
+
val existingHash = cached?.markdownHash ?: 0
|
|
87
|
+
val paintParams = PaintParams(paint.typeface ?: Typeface.DEFAULT, paint.textSize)
|
|
88
|
+
|
|
89
|
+
val newSize = measure(width, spannable, paint)
|
|
90
|
+
data[id] = MeasurementParams(width, newSize, spannable, paintParams, existingHash)
|
|
91
|
+
return oldSize != newSize
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fun release(id: Int) {
|
|
95
|
+
data.remove(id)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Main entry point for ShadowNode measurement. */
|
|
99
|
+
fun getMeasureById(
|
|
100
|
+
context: Context,
|
|
101
|
+
id: Int?,
|
|
102
|
+
width: Float,
|
|
103
|
+
height: Float,
|
|
104
|
+
heightMode: YogaMeasureMode?,
|
|
105
|
+
props: ReadableMap?,
|
|
106
|
+
): Long {
|
|
107
|
+
// Early exit for empty markdown
|
|
108
|
+
val markdown = props.getStringOrDefault("markdown", "")
|
|
109
|
+
if (markdown.isEmpty()) {
|
|
110
|
+
return YogaMeasureOutput.make(PixelUtil.toDIPFromPixel(width), 0f)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
val size = getMeasureByIdInternal(context, id, width, props)
|
|
114
|
+
val resultHeight = YogaMeasureOutput.getHeight(size)
|
|
115
|
+
|
|
116
|
+
if (heightMode === YogaMeasureMode.AT_MOST) {
|
|
117
|
+
val maxHeight = PixelUtil.toDIPFromPixel(height)
|
|
118
|
+
val finalHeight = resultHeight.coerceAtMost(maxHeight)
|
|
119
|
+
return YogaMeasureOutput.make(
|
|
120
|
+
YogaMeasureOutput.getWidth(size),
|
|
121
|
+
finalHeight,
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return size
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fun updateFontScalingSettings(
|
|
129
|
+
viewId: Int,
|
|
130
|
+
allowFontScaling: Boolean,
|
|
131
|
+
maxFontSizeMultiplier: Float,
|
|
132
|
+
) {
|
|
133
|
+
fontScalingSettings[viewId] = FontScalingSettings(allowFontScaling, maxFontSizeMultiplier)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fun clearFontScalingSettings(viewId: Int) {
|
|
137
|
+
fontScalingSettings.remove(viewId)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private fun getMeasureByIdInternal(
|
|
141
|
+
context: Context,
|
|
142
|
+
id: Int?,
|
|
143
|
+
width: Float,
|
|
144
|
+
props: ReadableMap?,
|
|
145
|
+
): Long {
|
|
146
|
+
val (allowFontScaling, maxFontSizeMultiplier) = resolveFontScalingSettings(id, props)
|
|
147
|
+
|
|
148
|
+
val fontScale = checkAndUpdateFontScale(context, allowFontScaling, maxFontSizeMultiplier)
|
|
149
|
+
|
|
150
|
+
val safeId = id ?: return measureAndCache(context, null, width, props, allowFontScaling, fontScale, maxFontSizeMultiplier)
|
|
151
|
+
val cached = data[safeId] ?: return measureAndCache(context, safeId, width, props, allowFontScaling, fontScale, maxFontSizeMultiplier)
|
|
152
|
+
|
|
153
|
+
val currentHash = computePropsHash(props, allowFontScaling, fontScale, maxFontSizeMultiplier)
|
|
154
|
+
|
|
155
|
+
if (cached.markdownHash != currentHash) {
|
|
156
|
+
return measureAndCache(context, safeId, width, props, allowFontScaling, fontScale, maxFontSizeMultiplier)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Width changed - re-measure with cached spannable
|
|
160
|
+
if (cached.cachedWidth != width) {
|
|
161
|
+
val newSize = measure(width, cached.spannable, cached.paintParams)
|
|
162
|
+
data[safeId] = cached.copy(cachedWidth = width, cachedSize = newSize)
|
|
163
|
+
return newSize
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return cached.cachedSize
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private fun computePropsHash(
|
|
170
|
+
props: ReadableMap?,
|
|
171
|
+
allowFontScaling: Boolean,
|
|
172
|
+
fontScale: Float,
|
|
173
|
+
maxFontSizeMultiplier: Float,
|
|
174
|
+
): Int {
|
|
175
|
+
val markdown = props.getStringOrDefault("markdown", "")
|
|
176
|
+
val styleMap = props.getMapOrNull("markdownStyle")
|
|
177
|
+
val md4cFlagsMap = props.getMapOrNull("md4cFlags")
|
|
178
|
+
val allowTrailingMargin = props.getBooleanOrDefault("allowTrailingMargin", false)
|
|
179
|
+
var result = markdown.hashCode()
|
|
180
|
+
result = 31 * result + (styleMap?.hashCode() ?: 0)
|
|
181
|
+
result = 31 * result + (md4cFlagsMap?.hashCode() ?: 0)
|
|
182
|
+
result = 31 * result + fontScale.toBits()
|
|
183
|
+
result = 31 * result + allowFontScaling.hashCode()
|
|
184
|
+
result = 31 * result + maxFontSizeMultiplier.toBits()
|
|
185
|
+
result = 31 * result + allowTrailingMargin.hashCode()
|
|
186
|
+
return result
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private fun checkAndUpdateFontScale(
|
|
190
|
+
context: Context,
|
|
191
|
+
allowFontScaling: Boolean,
|
|
192
|
+
maxFontSizeMultiplier: Float,
|
|
193
|
+
): Float {
|
|
194
|
+
if (!allowFontScaling) {
|
|
195
|
+
// Clear cache if we switched from scaling to non-scaling
|
|
196
|
+
if (lastKnownFontScale != 1.0f) {
|
|
197
|
+
lastKnownFontScale = 1.0f
|
|
198
|
+
data.clear()
|
|
199
|
+
}
|
|
200
|
+
return 1.0f
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
var currentFontScale = context.resources.configuration.fontScale
|
|
204
|
+
|
|
205
|
+
if (maxFontSizeMultiplier >= 1.0f && currentFontScale > maxFontSizeMultiplier) {
|
|
206
|
+
currentFontScale = maxFontSizeMultiplier
|
|
207
|
+
}
|
|
208
|
+
if (currentFontScale != lastKnownFontScale) {
|
|
209
|
+
lastKnownFontScale = currentFontScale
|
|
210
|
+
data.clear()
|
|
211
|
+
}
|
|
212
|
+
return currentFontScale
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private fun measureAndCache(
|
|
216
|
+
context: Context,
|
|
217
|
+
id: Int?,
|
|
218
|
+
width: Float,
|
|
219
|
+
props: ReadableMap?,
|
|
220
|
+
allowFontScaling: Boolean,
|
|
221
|
+
fontScale: Float,
|
|
222
|
+
maxFontSizeMultiplier: Float,
|
|
223
|
+
): Long {
|
|
224
|
+
// 1. Extract Props & Setup
|
|
225
|
+
val markdown = props.getStringOrDefault("markdown", "")
|
|
226
|
+
val styleMap = props.getMapOrNull("markdownStyle")
|
|
227
|
+
val md4cFlags = Md4cFlags(underline = props.getMapOrNull("md4cFlags").getBooleanOrDefault("underline", false))
|
|
228
|
+
|
|
229
|
+
val fontSize = getInitialFontSize(styleMap, context, allowFontScaling, fontScale, maxFontSizeMultiplier)
|
|
230
|
+
val propsHash = computePropsHash(props, allowFontScaling, fontScale, maxFontSizeMultiplier)
|
|
231
|
+
|
|
232
|
+
// 2. Render & Measure
|
|
233
|
+
val spannable = tryRenderMarkdown(markdown, styleMap, context, md4cFlags, allowFontScaling, maxFontSizeMultiplier)
|
|
234
|
+
val textToMeasure = spannable ?: markdown
|
|
235
|
+
val (size, _) = measureWithLayout(width, textToMeasure, measurePaint)
|
|
236
|
+
|
|
237
|
+
// 3. Calculate Margin
|
|
238
|
+
val allowTrailingMargin = props.getBooleanOrDefault("allowTrailingMargin", false)
|
|
239
|
+
val marginBottom =
|
|
240
|
+
if (allowTrailingMargin && spannable != null) {
|
|
241
|
+
PixelUtil.toDIPFromPixel(measureRenderer.getLastElementMarginBottom())
|
|
242
|
+
} else {
|
|
243
|
+
0f
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 4. Finalize Height
|
|
247
|
+
val currentWidth = YogaMeasureOutput.getWidth(size)
|
|
248
|
+
val currentHeight = YogaMeasureOutput.getHeight(size)
|
|
249
|
+
val adjustedSize = YogaMeasureOutput.make(currentWidth, currentHeight + marginBottom)
|
|
250
|
+
|
|
251
|
+
if (id != null) {
|
|
252
|
+
data[id] = MeasurementParams(width, adjustedSize, textToMeasure, PaintParams(Typeface.DEFAULT, fontSize), propsHash)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return adjustedSize
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private fun tryRenderMarkdown(
|
|
259
|
+
markdown: String,
|
|
260
|
+
styleMap: ReadableMap?,
|
|
261
|
+
context: Context,
|
|
262
|
+
md4cFlags: Md4cFlags,
|
|
263
|
+
allowFontScaling: Boolean,
|
|
264
|
+
maxFontSizeMultiplier: Float,
|
|
265
|
+
): CharSequence? {
|
|
266
|
+
if (styleMap == null) return null
|
|
267
|
+
|
|
268
|
+
return try {
|
|
269
|
+
val ast = Parser.shared.parseMarkdown(markdown, md4cFlags) ?: return null
|
|
270
|
+
val style = StyleConfig(styleMap, context, allowFontScaling, maxFontSizeMultiplier)
|
|
271
|
+
measureRenderer.configure(style, context)
|
|
272
|
+
measureRenderer.renderDocument(ast, null)
|
|
273
|
+
} catch (e: Exception) {
|
|
274
|
+
Log.w(TAG, "Failed to render markdown for measurement, falling back to raw text", e)
|
|
275
|
+
null
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private fun getInitialFontSize(
|
|
280
|
+
styleMap: ReadableMap?,
|
|
281
|
+
context: Context,
|
|
282
|
+
allowFontScaling: Boolean,
|
|
283
|
+
fontScale: Float,
|
|
284
|
+
maxFontSizeMultiplier: Float,
|
|
285
|
+
): Float {
|
|
286
|
+
val fontSizeSp = styleMap?.getMap("paragraph")?.getDouble("fontSize")?.toFloat() ?: 16f
|
|
287
|
+
val density = context.resources.displayMetrics.density
|
|
288
|
+
|
|
289
|
+
if (!allowFontScaling) {
|
|
290
|
+
return ceil(fontSizeSp * density)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
val cappedFontScale =
|
|
294
|
+
if (maxFontSizeMultiplier >= 1.0f && fontScale > maxFontSizeMultiplier) {
|
|
295
|
+
maxFontSizeMultiplier
|
|
296
|
+
} else {
|
|
297
|
+
fontScale
|
|
298
|
+
}
|
|
299
|
+
return ceil(fontSizeSp * cappedFontScale * density)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private fun measure(
|
|
303
|
+
maxWidth: Float,
|
|
304
|
+
text: CharSequence?,
|
|
305
|
+
paintParams: PaintParams,
|
|
306
|
+
): Long {
|
|
307
|
+
measurePaint.reset()
|
|
308
|
+
measurePaint.typeface = paintParams.typeface
|
|
309
|
+
measurePaint.textSize = paintParams.fontSize
|
|
310
|
+
return measure(maxWidth, text, measurePaint)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private fun measure(
|
|
314
|
+
maxWidth: Float,
|
|
315
|
+
text: CharSequence?,
|
|
316
|
+
paint: TextPaint,
|
|
317
|
+
): Long {
|
|
318
|
+
val content = text ?: ""
|
|
319
|
+
val safeWidth = ceil(maxWidth).toInt().coerceAtLeast(1)
|
|
320
|
+
|
|
321
|
+
val builder =
|
|
322
|
+
StaticLayout.Builder
|
|
323
|
+
.obtain(content, 0, content.length, paint, safeWidth)
|
|
324
|
+
.setIncludePad(false)
|
|
325
|
+
.setLineSpacing(0f, 1f)
|
|
326
|
+
|
|
327
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
328
|
+
builder.setBreakStrategy(LineBreaker.BREAK_STRATEGY_HIGH_QUALITY)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
332
|
+
builder.setUseLineSpacingFromFallbacks(true)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
val layout = builder.build()
|
|
336
|
+
val measuredHeight = layout.height.toFloat()
|
|
337
|
+
|
|
338
|
+
// Calculate actual content width (widest line)
|
|
339
|
+
val measuredWidth = (0 until layout.lineCount).maxOfOrNull { layout.getLineWidth(it) } ?: 0f
|
|
340
|
+
|
|
341
|
+
return YogaMeasureOutput.make(
|
|
342
|
+
PixelUtil.toDIPFromPixel(ceil(measuredWidth)),
|
|
343
|
+
PixelUtil.toDIPFromPixel(measuredHeight),
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Measures text and returns both the size and the layout for calculating last line descent.
|
|
349
|
+
*/
|
|
350
|
+
private fun measureWithLayout(
|
|
351
|
+
maxWidth: Float,
|
|
352
|
+
text: CharSequence?,
|
|
353
|
+
paint: TextPaint,
|
|
354
|
+
): Pair<Long, StaticLayout> {
|
|
355
|
+
val content = text ?: ""
|
|
356
|
+
val widthPx = ceil(maxWidth).toInt().coerceAtLeast(1)
|
|
357
|
+
|
|
358
|
+
val layout =
|
|
359
|
+
StaticLayout.Builder
|
|
360
|
+
.obtain(content, 0, content.length, paint, widthPx)
|
|
361
|
+
.setIncludePad(false)
|
|
362
|
+
.setLineSpacing(0f, 1f)
|
|
363
|
+
.apply {
|
|
364
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
365
|
+
setBreakStrategy(LineBreaker.BREAK_STRATEGY_HIGH_QUALITY)
|
|
366
|
+
}
|
|
367
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
368
|
+
setUseLineSpacingFromFallbacks(true)
|
|
369
|
+
}
|
|
370
|
+
}.build()
|
|
371
|
+
|
|
372
|
+
// Find the widest line to get the actual content width
|
|
373
|
+
val maxLineWidth =
|
|
374
|
+
(0 until layout.lineCount)
|
|
375
|
+
.maxOfOrNull { layout.getLineWidth(it) } ?: 0f
|
|
376
|
+
|
|
377
|
+
val size =
|
|
378
|
+
YogaMeasureOutput.make(
|
|
379
|
+
PixelUtil.toDIPFromPixel(ceil(maxLineWidth)),
|
|
380
|
+
PixelUtil.toDIPFromPixel(layout.height.toFloat()),
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
return size to layout
|
|
384
|
+
}
|
|
385
|
+
}
|