react-native-enriched-markdown 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -8
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +17 -2
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +6 -1
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/android/src/main/cpp/jni-adapter.cpp +28 -11
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +132 -15
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextLayoutManager.kt +1 -16
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +67 -13
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +241 -21
- 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/parser/MarkdownASTNode.kt +2 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/parser/Parser.kt +17 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +13 -18
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeBlockRenderer.kt +23 -24
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/CodeRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/DocumentRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/EmphasisRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/HeadingRenderer.kt +18 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ImageRenderer.kt +22 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LineBreakRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +3 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListItemRenderer.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ListRenderer.kt +16 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/NodeRenderer.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ParagraphRenderer.kt +23 -9
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/Renderer.kt +24 -10
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +1 -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 +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/TextRenderer.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/ThematicBreakRenderer.kt +1 -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/ImageSpan.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LineHeightSpan.kt +8 -17
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +19 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/MarginBottomSpan.kt +1 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/StrikethroughSpan.kt +12 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BaseBlockStyle.kt +1 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/BlockquoteStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/CodeBlockStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/HeadingStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ImageStyle.kt +3 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ListStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/ParagraphStyle.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StrikethroughStyle.kt +17 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +32 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleParser.kt +22 -5
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/TextAlignment.kt +32 -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/HTMLGenerator.kt +23 -5
- 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 +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/Utils.kt +58 -56
- package/android/src/main/jni/CMakeLists.txt +1 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.cpp +0 -13
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextShadowNode.h +2 -14
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/conversions.h +3 -0
- package/cpp/parser/MD4CParser.cpp +21 -8
- package/cpp/parser/MD4CParser.hpp +5 -1
- package/cpp/parser/MarkdownASTNode.hpp +2 -0
- package/ios/EnrichedMarkdownText.mm +356 -29
- package/ios/attachments/{ImageAttachment.h → EnrichedMarkdownImageAttachment.h} +1 -1
- package/ios/attachments/{ImageAttachment.m → EnrichedMarkdownImageAttachment.m} +4 -4
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.cpp +9 -0
- package/ios/generated/EnrichedMarkdownTextSpec/EventEmitters.h +6 -0
- package/ios/generated/EnrichedMarkdownTextSpec/Props.cpp +28 -3
- package/ios/generated/EnrichedMarkdownTextSpec/Props.h +225 -1
- package/ios/parser/MarkdownASTNode.h +2 -0
- package/ios/parser/MarkdownParser.h +9 -0
- package/ios/parser/MarkdownParser.mm +31 -2
- package/ios/parser/MarkdownParserBridge.mm +13 -3
- package/ios/renderer/AttributedRenderer.h +2 -0
- package/ios/renderer/AttributedRenderer.m +52 -19
- package/ios/renderer/BlockquoteRenderer.m +7 -6
- package/ios/renderer/CodeBlockRenderer.m +9 -8
- package/ios/renderer/HeadingRenderer.m +31 -24
- package/ios/renderer/ImageRenderer.m +31 -10
- package/ios/renderer/ListItemRenderer.m +51 -39
- package/ios/renderer/ListRenderer.m +21 -18
- package/ios/renderer/ParagraphRenderer.m +27 -16
- package/ios/renderer/RenderContext.h +17 -0
- package/ios/renderer/RenderContext.m +66 -2
- package/ios/renderer/RendererFactory.m +6 -0
- package/ios/renderer/StrikethroughRenderer.h +6 -0
- package/ios/renderer/StrikethroughRenderer.m +40 -0
- package/ios/renderer/UnderlineRenderer.h +6 -0
- package/ios/renderer/UnderlineRenderer.m +39 -0
- package/ios/styles/StyleConfig.h +46 -0
- package/ios/styles/StyleConfig.mm +351 -12
- package/ios/utils/AccessibilityInfo.h +35 -0
- package/ios/utils/AccessibilityInfo.m +24 -0
- package/ios/utils/CodeBlockBackground.m +4 -9
- package/ios/utils/FontUtils.h +5 -0
- package/ios/utils/FontUtils.m +14 -0
- package/ios/utils/HTMLGenerator.m +21 -7
- package/ios/utils/MarkdownAccessibilityElementBuilder.h +45 -0
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +323 -0
- package/ios/utils/MarkdownExtractor.m +18 -5
- package/ios/utils/ParagraphStyleUtils.h +10 -2
- package/ios/utils/ParagraphStyleUtils.m +57 -2
- package/ios/utils/PasteboardUtils.h +1 -1
- package/ios/utils/PasteboardUtils.m +3 -3
- package/lib/module/EnrichedMarkdownText.js +33 -2
- package/lib/module/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +58 -14
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownText.d.ts +85 -3
- package/lib/typescript/src/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +75 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownText.tsx +133 -5
- package/src/EnrichedMarkdownTextNativeComponent.ts +83 -3
- package/src/index.tsx +5 -2
- package/src/normalizeMarkdownStyle.ts +46 -0
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.cpp +0 -9
- package/android/src/main/jni/react/renderer/components/EnrichedMarkdownTextSpec/MarkdownTextState.h +0 -25
package/android/src/main/java/com/swmansion/enriched/markdown/renderer/StrikethroughRenderer.kt
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
5
|
+
import com.swmansion.enriched.markdown.spans.StrikethroughSpan
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
|
|
8
|
+
class StrikethroughRenderer(
|
|
9
|
+
private val config: RendererConfig,
|
|
10
|
+
) : NodeRenderer {
|
|
11
|
+
override fun render(
|
|
12
|
+
node: MarkdownASTNode,
|
|
13
|
+
builder: SpannableStringBuilder,
|
|
14
|
+
onLinkPress: ((String) -> Unit)?,
|
|
15
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
16
|
+
factory: RendererFactory,
|
|
17
|
+
) {
|
|
18
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle ->
|
|
19
|
+
builder.setSpan(
|
|
20
|
+
StrikethroughSpan(factory.styleCache.strikethroughColor),
|
|
21
|
+
start,
|
|
22
|
+
end,
|
|
23
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -12,9 +12,10 @@ class StrongRenderer(
|
|
|
12
12
|
node: MarkdownASTNode,
|
|
13
13
|
builder: SpannableStringBuilder,
|
|
14
14
|
onLinkPress: ((String) -> Unit)?,
|
|
15
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
15
16
|
factory: RendererFactory,
|
|
16
17
|
) {
|
|
17
|
-
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress) }) { start, end, blockStyle ->
|
|
18
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle ->
|
|
18
19
|
builder.setSpan(
|
|
19
20
|
StrongSpan(factory.styleCache, blockStyle),
|
|
20
21
|
start,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.renderer
|
|
2
|
+
|
|
3
|
+
import android.text.SpannableStringBuilder
|
|
4
|
+
import android.text.style.UnderlineSpan
|
|
5
|
+
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
|
|
6
|
+
import com.swmansion.enriched.markdown.utils.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE
|
|
7
|
+
|
|
8
|
+
class UnderlineRenderer(
|
|
9
|
+
private val config: RendererConfig,
|
|
10
|
+
) : NodeRenderer {
|
|
11
|
+
override fun render(
|
|
12
|
+
node: MarkdownASTNode,
|
|
13
|
+
builder: SpannableStringBuilder,
|
|
14
|
+
onLinkPress: ((String) -> Unit)?,
|
|
15
|
+
onLinkLongPress: ((String) -> Unit)?,
|
|
16
|
+
factory: RendererFactory,
|
|
17
|
+
) {
|
|
18
|
+
factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, _ ->
|
|
19
|
+
builder.setSpan(
|
|
20
|
+
UnderlineSpan(),
|
|
21
|
+
start,
|
|
22
|
+
end,
|
|
23
|
+
SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -2,35 +2,26 @@ package com.swmansion.enriched.markdown.spans
|
|
|
2
2
|
|
|
3
3
|
import android.graphics.Paint
|
|
4
4
|
import kotlin.math.ceil
|
|
5
|
-
import kotlin.math.
|
|
5
|
+
import kotlin.math.floor
|
|
6
6
|
import android.text.style.LineHeightSpan as AndroidLineHeightSpan
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Custom LineHeightSpan for Android API levels below 29.
|
|
10
|
-
* Matches LineHeightSpan.Standard behavior for consistent rendering across all API levels.
|
|
11
|
-
*/
|
|
12
8
|
class LineHeightSpan(
|
|
13
|
-
|
|
9
|
+
height: Float,
|
|
14
10
|
) : AndroidLineHeightSpan {
|
|
11
|
+
private val lineHeight: Int = ceil(height.toDouble()).toInt()
|
|
12
|
+
|
|
15
13
|
override fun chooseHeight(
|
|
16
14
|
text: CharSequence?,
|
|
17
15
|
start: Int,
|
|
18
16
|
end: Int,
|
|
19
17
|
spanstartv: Int,
|
|
20
|
-
|
|
18
|
+
v: Int,
|
|
21
19
|
fm: Paint.FontMetricsInt?,
|
|
22
20
|
) {
|
|
23
21
|
if (fm == null) return
|
|
24
22
|
|
|
25
|
-
val
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (originHeight <= 0) {
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
val ratio = targetHeight.toFloat() / originHeight
|
|
33
|
-
fm.descent = (fm.descent * ratio).roundToInt()
|
|
34
|
-
fm.ascent = fm.descent - targetHeight
|
|
23
|
+
val leading = lineHeight - ((-fm.ascent) + fm.descent)
|
|
24
|
+
fm.ascent -= ceil(leading / 2.0f).toInt()
|
|
25
|
+
fm.descent += floor(leading / 2.0f).toInt()
|
|
35
26
|
}
|
|
36
27
|
}
|
|
@@ -12,17 +12,31 @@ import com.swmansion.enriched.markdown.utils.applyBlockStyleFont
|
|
|
12
12
|
class LinkSpan(
|
|
13
13
|
val url: String,
|
|
14
14
|
private val onLinkPress: ((String) -> Unit)?,
|
|
15
|
+
private val onLinkLongPress: ((String) -> Unit)?,
|
|
15
16
|
private val styleCache: SpanStyleCache,
|
|
16
17
|
private val blockStyle: BlockStyle,
|
|
17
18
|
private val context: Context,
|
|
18
19
|
) : ClickableSpan() {
|
|
20
|
+
@Volatile
|
|
21
|
+
private var longPressTriggered = false
|
|
22
|
+
|
|
19
23
|
override fun onClick(widget: View) {
|
|
20
|
-
if (
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Emit event directly from view (enriched pattern)
|
|
24
|
-
widget.emitOnLinkPress(url)
|
|
24
|
+
if (longPressTriggered) {
|
|
25
|
+
longPressTriggered = false
|
|
26
|
+
return
|
|
25
27
|
}
|
|
28
|
+
|
|
29
|
+
onLinkPress?.invoke(url) ?: (widget as? EnrichedMarkdownText)?.emitOnLinkPress(url)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fun onLongClick(widget: View): Boolean {
|
|
33
|
+
longPressTriggered = true
|
|
34
|
+
|
|
35
|
+
(widget as? EnrichedMarkdownText)?.emitOnLinkLongPress(url)
|
|
36
|
+
|
|
37
|
+
onLinkLongPress?.invoke(url)
|
|
38
|
+
|
|
39
|
+
return true
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
override fun updateDrawState(textPaint: TextPaint) {
|
|
@@ -9,7 +9,7 @@ import android.text.style.LineHeightSpan
|
|
|
9
9
|
* For spacer lines (single newline), sets the line height to exactly marginBottom.
|
|
10
10
|
* For regular lines, adds marginBottom only at paragraph boundaries to preserve lineHeight.
|
|
11
11
|
*
|
|
12
|
-
* @param marginBottom The margin in pixels to add below the block (
|
|
12
|
+
* @param marginBottom The margin in pixels to add below the block (0 = no margin)
|
|
13
13
|
*/
|
|
14
14
|
class MarginBottomSpan(
|
|
15
15
|
val marginBottom: Float,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.spans
|
|
2
|
+
|
|
3
|
+
import android.text.TextPaint
|
|
4
|
+
import android.text.style.CharacterStyle
|
|
5
|
+
|
|
6
|
+
class StrikethroughSpan(
|
|
7
|
+
private val strikethroughColor: Int,
|
|
8
|
+
) : CharacterStyle() {
|
|
9
|
+
override fun updateDrawState(tp: TextPaint) {
|
|
10
|
+
tp.isStrikeThruText = true
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -7,6 +7,7 @@ data class BlockquoteStyle(
|
|
|
7
7
|
override val fontFamily: String,
|
|
8
8
|
override val fontWeight: String,
|
|
9
9
|
override val color: Int,
|
|
10
|
+
override val marginTop: Float,
|
|
10
11
|
override val marginBottom: Float,
|
|
11
12
|
override val lineHeight: Float,
|
|
12
13
|
val borderColor: Int,
|
|
@@ -23,6 +24,7 @@ data class BlockquoteStyle(
|
|
|
23
24
|
val fontFamily = parser.parseString(map, "fontFamily")
|
|
24
25
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
25
26
|
val color = parser.parseColor(map, "color")
|
|
27
|
+
val marginTop = parser.toPixelFromDIP(map.getDouble("marginTop").toFloat())
|
|
26
28
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
27
29
|
val lineHeightRaw = map.getDouble("lineHeight").toFloat()
|
|
28
30
|
val lineHeight = parser.toPixelFromSP(lineHeightRaw)
|
|
@@ -36,6 +38,7 @@ data class BlockquoteStyle(
|
|
|
36
38
|
fontFamily,
|
|
37
39
|
fontWeight,
|
|
38
40
|
color,
|
|
41
|
+
marginTop,
|
|
39
42
|
marginBottom,
|
|
40
43
|
lineHeight,
|
|
41
44
|
borderColor,
|
|
@@ -7,6 +7,7 @@ data class CodeBlockStyle(
|
|
|
7
7
|
override val fontFamily: String,
|
|
8
8
|
override val fontWeight: String,
|
|
9
9
|
override val color: Int,
|
|
10
|
+
override val marginTop: Float,
|
|
10
11
|
override val marginBottom: Float,
|
|
11
12
|
override val lineHeight: Float,
|
|
12
13
|
val backgroundColor: Int,
|
|
@@ -24,6 +25,7 @@ data class CodeBlockStyle(
|
|
|
24
25
|
val fontFamily = parser.parseString(map, "fontFamily")
|
|
25
26
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
26
27
|
val color = parser.parseColor(map, "color")
|
|
28
|
+
val marginTop = parser.toPixelFromDIP(map.getDouble("marginTop").toFloat())
|
|
27
29
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
28
30
|
val lineHeightRaw = map.getDouble("lineHeight").toFloat()
|
|
29
31
|
val lineHeight = parser.toPixelFromSP(lineHeightRaw)
|
|
@@ -38,6 +40,7 @@ data class CodeBlockStyle(
|
|
|
38
40
|
fontFamily,
|
|
39
41
|
fontWeight,
|
|
40
42
|
color,
|
|
43
|
+
marginTop,
|
|
41
44
|
marginBottom,
|
|
42
45
|
lineHeight,
|
|
43
46
|
backgroundColor,
|
|
@@ -7,8 +7,10 @@ data class HeadingStyle(
|
|
|
7
7
|
override val fontFamily: String,
|
|
8
8
|
override val fontWeight: String,
|
|
9
9
|
override val color: Int,
|
|
10
|
+
override val marginTop: Float,
|
|
10
11
|
override val marginBottom: Float,
|
|
11
12
|
override val lineHeight: Float,
|
|
13
|
+
val textAlign: TextAlignment,
|
|
12
14
|
) : BaseBlockStyle {
|
|
13
15
|
companion object {
|
|
14
16
|
fun fromReadableMap(
|
|
@@ -19,11 +21,13 @@ data class HeadingStyle(
|
|
|
19
21
|
val fontFamily = parser.parseString(map, "fontFamily")
|
|
20
22
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
21
23
|
val color = parser.parseColor(map, "color")
|
|
24
|
+
val marginTop = parser.toPixelFromDIP(map.getDouble("marginTop").toFloat())
|
|
22
25
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
23
26
|
val lineHeightRaw = map.getDouble("lineHeight").toFloat()
|
|
24
27
|
val lineHeight = parser.toPixelFromSP(lineHeightRaw)
|
|
28
|
+
val textAlign = parser.parseTextAlign(map, "textAlign")
|
|
25
29
|
|
|
26
|
-
return HeadingStyle(fontSize, fontFamily, fontWeight, color, marginBottom, lineHeight)
|
|
30
|
+
return HeadingStyle(fontSize, fontFamily, fontWeight, color, marginTop, marginBottom, lineHeight, textAlign)
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -5,6 +5,7 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
5
5
|
data class ImageStyle(
|
|
6
6
|
val height: Float,
|
|
7
7
|
val borderRadius: Float,
|
|
8
|
+
val marginTop: Float,
|
|
8
9
|
val marginBottom: Float,
|
|
9
10
|
) {
|
|
10
11
|
companion object {
|
|
@@ -14,8 +15,9 @@ data class ImageStyle(
|
|
|
14
15
|
): ImageStyle {
|
|
15
16
|
val height = parser.toPixelFromDIP(map.getDouble("height").toFloat())
|
|
16
17
|
val borderRadius = parser.toPixelFromDIP(map.getDouble("borderRadius").toFloat())
|
|
18
|
+
val marginTop = parser.toPixelFromDIP(map.getDouble("marginTop").toFloat())
|
|
17
19
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
18
|
-
return ImageStyle(height, borderRadius, marginBottom)
|
|
20
|
+
return ImageStyle(height, borderRadius, marginTop, marginBottom)
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
}
|
|
@@ -7,6 +7,7 @@ data class ListStyle(
|
|
|
7
7
|
override val fontFamily: String,
|
|
8
8
|
override val fontWeight: String,
|
|
9
9
|
override val color: Int,
|
|
10
|
+
override val marginTop: Float,
|
|
10
11
|
override val marginBottom: Float,
|
|
11
12
|
override val lineHeight: Float,
|
|
12
13
|
val bulletColor: Int,
|
|
@@ -25,6 +26,7 @@ data class ListStyle(
|
|
|
25
26
|
val fontFamily = parser.parseString(map, "fontFamily")
|
|
26
27
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
27
28
|
val color = parser.parseColor(map, "color")
|
|
29
|
+
val marginTop = parser.toPixelFromDIP(map.getDouble("marginTop").toFloat())
|
|
28
30
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
29
31
|
val lineHeightRaw = map.getDouble("lineHeight").toFloat()
|
|
30
32
|
val lineHeight = parser.toPixelFromSP(lineHeightRaw)
|
|
@@ -40,6 +42,7 @@ data class ListStyle(
|
|
|
40
42
|
fontFamily,
|
|
41
43
|
fontWeight,
|
|
42
44
|
color,
|
|
45
|
+
marginTop,
|
|
43
46
|
marginBottom,
|
|
44
47
|
lineHeight,
|
|
45
48
|
bulletColor,
|
|
@@ -7,8 +7,10 @@ data class ParagraphStyle(
|
|
|
7
7
|
override val fontFamily: String,
|
|
8
8
|
override val fontWeight: String,
|
|
9
9
|
override val color: Int,
|
|
10
|
+
override val marginTop: Float,
|
|
10
11
|
override val marginBottom: Float,
|
|
11
12
|
override val lineHeight: Float,
|
|
13
|
+
val textAlign: TextAlignment,
|
|
12
14
|
) : BaseBlockStyle {
|
|
13
15
|
companion object {
|
|
14
16
|
fun fromReadableMap(
|
|
@@ -19,11 +21,13 @@ data class ParagraphStyle(
|
|
|
19
21
|
val fontFamily = parser.parseString(map, "fontFamily")
|
|
20
22
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
21
23
|
val color = parser.parseColor(map, "color")
|
|
24
|
+
val marginTop = parser.toPixelFromDIP(map.getDouble("marginTop").toFloat())
|
|
22
25
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
23
26
|
val lineHeightRaw = map.getDouble("lineHeight").toFloat()
|
|
24
27
|
val lineHeight = parser.toPixelFromSP(lineHeightRaw)
|
|
28
|
+
val textAlign = parser.parseTextAlign(map, "textAlign")
|
|
25
29
|
|
|
26
|
-
return ParagraphStyle(fontSize, fontFamily, fontWeight, color, marginBottom, lineHeight)
|
|
30
|
+
return ParagraphStyle(fontSize, fontFamily, fontWeight, color, marginTop, marginBottom, lineHeight, textAlign)
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.styles
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
|
|
5
|
+
data class StrikethroughStyle(
|
|
6
|
+
val color: Int,
|
|
7
|
+
) {
|
|
8
|
+
companion object {
|
|
9
|
+
fun fromReadableMap(
|
|
10
|
+
map: ReadableMap,
|
|
11
|
+
parser: StyleParser,
|
|
12
|
+
): StrikethroughStyle {
|
|
13
|
+
val color = parser.parseColor(map, "color")
|
|
14
|
+
return StrikethroughStyle(color)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -16,8 +16,10 @@ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
|
|
|
16
16
|
class StyleConfig(
|
|
17
17
|
private val style: ReadableMap,
|
|
18
18
|
context: Context,
|
|
19
|
+
allowFontScaling: Boolean,
|
|
20
|
+
maxFontSizeMultiplier: Float,
|
|
19
21
|
) {
|
|
20
|
-
private val styleParser = StyleParser(context)
|
|
22
|
+
private val styleParser = StyleParser(context, allowFontScaling, maxFontSizeMultiplier)
|
|
21
23
|
private val assets: AssetManager = context.assets
|
|
22
24
|
|
|
23
25
|
val paragraphStyle: ParagraphStyle by lazy {
|
|
@@ -88,6 +90,22 @@ class StyleConfig(
|
|
|
88
90
|
EmphasisStyle.fromReadableMap(map, styleParser)
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
val strikethroughStyle: StrikethroughStyle by lazy {
|
|
94
|
+
val map =
|
|
95
|
+
requireNotNull(style.getMap("strikethrough")) {
|
|
96
|
+
"Strikethrough style not found. JS should always provide defaults."
|
|
97
|
+
}
|
|
98
|
+
StrikethroughStyle.fromReadableMap(map, styleParser)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
val underlineStyle: UnderlineStyle by lazy {
|
|
102
|
+
val map =
|
|
103
|
+
requireNotNull(style.getMap("underline")) {
|
|
104
|
+
"Underline style not found. JS should always provide defaults."
|
|
105
|
+
}
|
|
106
|
+
UnderlineStyle.fromReadableMap(map, styleParser)
|
|
107
|
+
}
|
|
108
|
+
|
|
91
109
|
val codeStyle: CodeStyle by lazy {
|
|
92
110
|
val map =
|
|
93
111
|
requireNotNull(style.getMap("code")) {
|
|
@@ -144,6 +162,15 @@ class StyleConfig(
|
|
|
144
162
|
ThematicBreakStyle.fromReadableMap(map, styleParser)
|
|
145
163
|
}
|
|
146
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Returns true if any paragraph or heading style uses justify alignment.
|
|
167
|
+
* Used to enable justification mode on the TextView (API 26+).
|
|
168
|
+
*/
|
|
169
|
+
val needsJustify: Boolean by lazy {
|
|
170
|
+
paragraphStyle.textAlign.needsJustify ||
|
|
171
|
+
headingStyles.filterNotNull().any { it.textAlign.needsJustify }
|
|
172
|
+
}
|
|
173
|
+
|
|
147
174
|
override fun equals(other: Any?): Boolean {
|
|
148
175
|
if (this === other) return true
|
|
149
176
|
if (other !is StyleConfig) return false
|
|
@@ -153,6 +180,8 @@ class StyleConfig(
|
|
|
153
180
|
linkStyle == other.linkStyle &&
|
|
154
181
|
strongStyle == other.strongStyle &&
|
|
155
182
|
emphasisStyle == other.emphasisStyle &&
|
|
183
|
+
strikethroughStyle == other.strikethroughStyle &&
|
|
184
|
+
underlineStyle == other.underlineStyle &&
|
|
156
185
|
codeStyle == other.codeStyle &&
|
|
157
186
|
imageStyle == other.imageStyle &&
|
|
158
187
|
inlineImageStyle == other.inlineImageStyle &&
|
|
@@ -168,6 +197,8 @@ class StyleConfig(
|
|
|
168
197
|
result = 31 * result + linkStyle.hashCode()
|
|
169
198
|
result = 31 * result + strongStyle.hashCode()
|
|
170
199
|
result = 31 * result + emphasisStyle.hashCode()
|
|
200
|
+
result = 31 * result + strikethroughStyle.hashCode()
|
|
201
|
+
result = 31 * result + underlineStyle.hashCode()
|
|
171
202
|
result = 31 * result + codeStyle.hashCode()
|
|
172
203
|
result = 31 * result + imageStyle.hashCode()
|
|
173
204
|
result = 31 * result + inlineImageStyle.hashCode()
|
|
@@ -5,12 +5,10 @@ import com.facebook.react.bridge.ColorPropConverter
|
|
|
5
5
|
import com.facebook.react.bridge.ReadableMap
|
|
6
6
|
import com.facebook.react.uimanager.PixelUtil
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* Helper class for parsing style values from ReadableMap.
|
|
10
|
-
* Provides common parsing utilities used by all style factory functions.
|
|
11
|
-
*/
|
|
12
8
|
class StyleParser(
|
|
13
9
|
private val context: Context,
|
|
10
|
+
private val allowFontScaling: Boolean,
|
|
11
|
+
private val maxFontSizeMultiplier: Float,
|
|
14
12
|
) {
|
|
15
13
|
fun parseOptionalColor(
|
|
16
14
|
map: ReadableMap,
|
|
@@ -69,7 +67,26 @@ class StyleParser(
|
|
|
69
67
|
default
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
fun toPixelFromSP(value: Float): Float
|
|
70
|
+
fun toPixelFromSP(value: Float): Float {
|
|
71
|
+
val metrics = context.resources.displayMetrics
|
|
72
|
+
val baseDensity = metrics.density
|
|
73
|
+
|
|
74
|
+
if (!allowFontScaling) {
|
|
75
|
+
return value * baseDensity
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var fontScale = metrics.scaledDensity / baseDensity
|
|
79
|
+
if (maxFontSizeMultiplier >= 1.0f && fontScale > maxFontSizeMultiplier) {
|
|
80
|
+
fontScale = maxFontSizeMultiplier
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return value * baseDensity * fontScale
|
|
84
|
+
}
|
|
73
85
|
|
|
74
86
|
fun toPixelFromDIP(value: Float): Float = PixelUtil.toPixelFromDIP(value)
|
|
87
|
+
|
|
88
|
+
fun parseTextAlign(
|
|
89
|
+
map: ReadableMap,
|
|
90
|
+
key: String,
|
|
91
|
+
): TextAlignment = TextAlignment.fromString(parseString(map, key, "left"))
|
|
75
92
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.styles
|
|
2
|
+
|
|
3
|
+
import android.text.Layout
|
|
4
|
+
|
|
5
|
+
enum class TextAlignment(
|
|
6
|
+
val layoutAlignment: Layout.Alignment,
|
|
7
|
+
val needsJustify: Boolean,
|
|
8
|
+
) {
|
|
9
|
+
LEFT(Layout.Alignment.ALIGN_NORMAL, false),
|
|
10
|
+
CENTER(Layout.Alignment.ALIGN_CENTER, false),
|
|
11
|
+
RIGHT(Layout.Alignment.ALIGN_OPPOSITE, false),
|
|
12
|
+
JUSTIFY(Layout.Alignment.ALIGN_NORMAL, true),
|
|
13
|
+
AUTO(Layout.Alignment.ALIGN_NORMAL, false),
|
|
14
|
+
;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Whether an AlignmentSpan is needed.
|
|
18
|
+
* Only CENTER and RIGHT need explicit spans; LEFT/AUTO use default, JUSTIFY is handled at TextView level.
|
|
19
|
+
*/
|
|
20
|
+
val needsAlignmentSpan: Boolean get() = this == CENTER || this == RIGHT
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
fun fromString(value: String): TextAlignment =
|
|
24
|
+
when (value.lowercase()) {
|
|
25
|
+
"center" -> CENTER
|
|
26
|
+
"right" -> RIGHT
|
|
27
|
+
"justify" -> JUSTIFY
|
|
28
|
+
"auto" -> AUTO
|
|
29
|
+
else -> LEFT
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.styles
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
|
|
5
|
+
data class UnderlineStyle(
|
|
6
|
+
val color: Int,
|
|
7
|
+
) {
|
|
8
|
+
companion object {
|
|
9
|
+
fun fromReadableMap(
|
|
10
|
+
map: ReadableMap,
|
|
11
|
+
parser: StyleParser,
|
|
12
|
+
): UnderlineStyle {
|
|
13
|
+
val color = parser.parseColor(map, "color")
|
|
14
|
+
return UnderlineStyle(color)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -2,7 +2,6 @@ package com.swmansion.enriched.markdown.utils
|
|
|
2
2
|
|
|
3
3
|
import android.graphics.Typeface
|
|
4
4
|
import android.text.Spannable
|
|
5
|
-
import android.text.style.StrikethroughSpan
|
|
6
5
|
import android.text.style.StyleSpan
|
|
7
6
|
import android.text.style.UnderlineSpan
|
|
8
7
|
import com.swmansion.enriched.markdown.spans.BlockquoteSpan
|
|
@@ -13,6 +12,7 @@ import com.swmansion.enriched.markdown.spans.HeadingSpan
|
|
|
13
12
|
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
14
13
|
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
15
14
|
import com.swmansion.enriched.markdown.spans.OrderedListSpan
|
|
15
|
+
import com.swmansion.enriched.markdown.spans.StrikethroughSpan
|
|
16
16
|
import com.swmansion.enriched.markdown.spans.StrongSpan
|
|
17
17
|
import com.swmansion.enriched.markdown.spans.UnorderedListSpan
|
|
18
18
|
import com.swmansion.enriched.markdown.styles.StyleConfig
|
|
@@ -68,9 +68,11 @@ object HTMLGenerator {
|
|
|
68
68
|
val linkColor: String
|
|
69
69
|
val linkUnderline: Boolean
|
|
70
70
|
|
|
71
|
-
// Strong/Emphasis
|
|
71
|
+
// Strong/Emphasis/Strikethrough/Underline
|
|
72
72
|
val strongColor: String?
|
|
73
73
|
val emphasisColor: String?
|
|
74
|
+
val strikethroughColor: String?
|
|
75
|
+
val underlineColor: String?
|
|
74
76
|
|
|
75
77
|
// Image
|
|
76
78
|
val imageMarginBottom: Int
|
|
@@ -135,11 +137,15 @@ object HTMLGenerator {
|
|
|
135
137
|
linkColor = colorToCSS(style.linkStyle.color)
|
|
136
138
|
linkUnderline = style.linkStyle.underline
|
|
137
139
|
|
|
138
|
-
// Strong/Emphasis (nullable for inherit)
|
|
140
|
+
// Strong/Emphasis/Strikethrough/Underline (nullable for inherit)
|
|
139
141
|
val sc = style.strongStyle.color
|
|
140
142
|
strongColor = if (sc != null && sc != 0) colorToCSS(sc) else null
|
|
141
143
|
val ec = style.emphasisStyle.color
|
|
142
144
|
emphasisColor = if (ec != null && ec != 0) colorToCSS(ec) else null
|
|
145
|
+
val strikeColor = style.strikethroughStyle.color
|
|
146
|
+
strikethroughColor = if (strikeColor != 0) colorToCSS(strikeColor) else null
|
|
147
|
+
val underline = style.underlineStyle.color
|
|
148
|
+
underlineColor = if (underline != 0) colorToCSS(underline) else null
|
|
143
149
|
|
|
144
150
|
// Image
|
|
145
151
|
val imgStyle = style.imageStyle
|
|
@@ -710,8 +716,20 @@ object HTMLGenerator {
|
|
|
710
716
|
}
|
|
711
717
|
}
|
|
712
718
|
|
|
713
|
-
if (isStrikethrough)
|
|
714
|
-
|
|
719
|
+
if (isStrikethrough) {
|
|
720
|
+
if (styles.strikethroughColor != null) {
|
|
721
|
+
html.append("<s style=\"text-decoration-color: ").append(styles.strikethroughColor).append(";\">")
|
|
722
|
+
} else {
|
|
723
|
+
html.append("<s>")
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
if (isUnderline && link == null) {
|
|
727
|
+
if (styles.underlineColor != null) {
|
|
728
|
+
html.append("<u style=\"text-decoration-color: ").append(styles.underlineColor).append(";\">")
|
|
729
|
+
} else {
|
|
730
|
+
html.append("<u>")
|
|
731
|
+
}
|
|
732
|
+
}
|
|
715
733
|
|
|
716
734
|
escapeHTMLTo(html, content.trimEnd('\n'))
|
|
717
735
|
|