Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,18 @@ import android.content.res.Configuration
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.text.SpannableString
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.widget.FrameLayout
import com.facebook.react.bridge.ReadableMap
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
import com.swmansion.enriched.markdown.parser.Md4cFlags
import com.swmansion.enriched.markdown.parser.Parser
import com.swmansion.enriched.markdown.renderer.Renderer
import com.swmansion.enriched.markdown.spans.ImageSpan
import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay
import com.swmansion.enriched.markdown.styles.StyleConfig
import com.swmansion.enriched.markdown.utils.common.FeatureFlags
import com.swmansion.enriched.markdown.utils.common.MarkdownSegment
import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer
import com.swmansion.enriched.markdown.utils.common.RenderedSegment
import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments
import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent
import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent
Expand All @@ -28,23 +25,6 @@ import com.swmansion.enriched.markdown.views.TableContainerView
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

private sealed interface RenderSegment {
data class Text(
val styledText: SpannableString,
val imageSpans: List<ImageSpan>,
val needsJustify: Boolean,
val lastElementMarginBottom: Float,
) : RenderSegment

data class Table(
val node: MarkdownASTNode,
) : RenderSegment

data class Math(
val latex: String,
) : RenderSegment
}

class EnrichedMarkdown
@JvmOverloads
constructor(
Expand Down Expand Up @@ -200,57 +180,43 @@ class EnrichedMarkdown
return@execute
}

val processedSegments =
splitASTIntoSegments(ast).map { segment ->
when (segment) {
is MarkdownSegment.Text -> renderTextSegment(segment.nodes, style)
is MarkdownSegment.Table -> RenderSegment.Table(segment.node)
is MarkdownSegment.Math -> RenderSegment.Math(segment.latex)
}
}

postToMain(renderId) { applyRenderedSegments(processedSegments, style) }
val segments = splitASTIntoSegments(ast)
val renderedSegments =
MarkdownSegmentRenderer.render(
segments,
style,
context,
onLinkPressCallback,
onLinkLongPressCallback,
)

postToMain(renderId) { applyRenderedSegments(renderedSegments, style) }
} catch (e: Exception) {
Log.e(TAG, "Render failed", e)
postToMain(renderId) { clearSegments() }
}
}
}

private fun renderTextSegment(
nodes: List<MarkdownASTNode>,
style: StyleConfig,
): RenderSegment.Text {
val documentWrapper = MarkdownASTNode(type = MarkdownASTNode.NodeType.Document, children = nodes)
val renderer = Renderer().apply { configure(style, context) }

return RenderSegment.Text(
styledText = renderer.renderDocument(documentWrapper, onLinkPressCallback, onLinkLongPressCallback),
imageSpans = renderer.getCollectedImageSpans().toList(),
needsJustify = style.needsJustify,
lastElementMarginBottom = renderer.getLastElementMarginBottom(),
)
}

private fun applyRenderedSegments(
renderedSegments: List<RenderSegment>,
renderedSegments: List<RenderedSegment>,
style: StyleConfig,
) {
clearSegments()
renderedSegments.forEach { segment ->
val view =
when (segment) {
is RenderSegment.Text -> createTextView(segment)
is RenderSegment.Table -> createTableView(segment, style)
is RenderSegment.Math -> createMathView(segment, style)
is RenderedSegment.Text -> createTextView(segment)
is RenderedSegment.Table -> createTableView(segment, style)
is RenderedSegment.Math -> createMathView(segment, style)
}
segmentViews.add(view)
addView(view)
}
layoutSegments()
}

private fun createTextView(segment: RenderSegment.Text) =
private fun createTextView(segment: RenderedSegment.Text) =
EnrichedMarkdownInternalText(context).apply {
spoilerOverlay = this@EnrichedMarkdown.spoilerOverlay
setIsSelectable(selectable)
Expand All @@ -271,7 +237,7 @@ class EnrichedMarkdown
}

private fun createTableView(
segment: RenderSegment.Table,
segment: RenderedSegment.Table,
style: StyleConfig,
) = TableContainerView(context, style).apply {
allowFontScaling = this@EnrichedMarkdown.allowFontScaling
Expand All @@ -282,7 +248,7 @@ class EnrichedMarkdown
}

private fun createMathView(
segment: RenderSegment.Math,
segment: RenderedSegment.Math,
style: StyleConfig,
): android.view.View {
if (!FeatureFlags.IS_MATH_ENABLED) return android.view.View(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.facebook.react.bridge.ReadableMap
import com.facebook.react.uimanager.PixelUtil
import com.facebook.yoga.YogaMeasureMode
import com.facebook.yoga.YogaMeasureOutput
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
import com.swmansion.enriched.markdown.parser.Md4cFlags
import com.swmansion.enriched.markdown.parser.Parser
import com.swmansion.enriched.markdown.renderer.Renderer
Expand All @@ -21,7 +20,8 @@ import com.swmansion.enriched.markdown.spans.MathMetrics
import com.swmansion.enriched.markdown.spans.MathRenderMode
import com.swmansion.enriched.markdown.styles.StyleConfig
import com.swmansion.enriched.markdown.utils.common.FeatureFlags
import com.swmansion.enriched.markdown.utils.common.MarkdownSegment
import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer
import com.swmansion.enriched.markdown.utils.common.RenderedSegment
import com.swmansion.enriched.markdown.utils.common.getBooleanOrDefault
import com.swmansion.enriched.markdown.utils.common.getMapOrNull
import com.swmansion.enriched.markdown.utils.common.getStringOrDefault
Expand Down Expand Up @@ -307,12 +307,13 @@ object MeasurementStore {

val style = StyleConfig(styleMap, context, allowFontScaling, maxFontSizeMultiplier)
val segments = splitASTIntoSegments(ast)
val renderedSegments = MarkdownSegmentRenderer.render(segments, style, context, null, null)

val mathHeightByIndex = HashMap<Int, Float>()
val mathSegmentIndices = mutableListOf<Int>()
val mathRequests = mutableListOf<MathMeasureRequest>()
for ((i, segment) in segments.withIndex()) {
if (segment is MarkdownSegment.Math) {
for ((i, segment) in renderedSegments.withIndex()) {
if (segment is RenderedSegment.Math) {
mathSegmentIndices.add(i)
mathRequests.add(
MathMeasureRequest(
Expand All @@ -333,33 +334,30 @@ object MeasurementStore {
}

val widthPx = width.toInt().coerceAtLeast(1)
val lastIndex = segments.lastIndex
val lastIndex = renderedSegments.lastIndex
var totalHeightPx = 0f
var maxContentWidthPx = 0f

for ((index, segment) in segments.withIndex()) {
for ((index, segment) in renderedSegments.withIndex()) {
val isLastSegment = index == lastIndex
val includeBottomMargin = if (isLastSegment) allowTrailingMargin else true

when (segment) {
is MarkdownSegment.Text -> {
val segmentRenderer = Renderer().apply { configure(style, context) }
val tempDoc = MarkdownASTNode(type = MarkdownASTNode.NodeType.Document, children = segment.nodes)
val styledText = segmentRenderer.renderDocument(tempDoc, null)
styledText.replaceMathSpansWithPlaceholders(context)
is RenderedSegment.Text -> {
segment.styledText.replaceMathSpansWithPlaceholders(context)

val layout = createStaticLayout(styledText, fontSize, widthPx)
val layout = createStaticLayout(segment.styledText, fontSize, widthPx)
totalHeightPx += layout.height

val segmentMaxLineWidth = (0 until layout.lineCount).maxOfOrNull { layout.getLineWidth(it) } ?: 0f
maxContentWidthPx = maxOf(maxContentWidthPx, ceil(segmentMaxLineWidth))

if (includeBottomMargin) {
totalHeightPx += segmentRenderer.getLastElementMarginBottom()
totalHeightPx += segment.lastElementMarginBottom
}
}

is MarkdownSegment.Table -> {
is RenderedSegment.Table -> {
totalHeightPx += style.tableStyle.marginTop
totalHeightPx += TableContainerView.measureTableNodeHeight(segment.node, style, context)
maxContentWidthPx = width
Expand All @@ -368,7 +366,7 @@ object MeasurementStore {
}
}

is MarkdownSegment.Math -> {
is RenderedSegment.Math -> {
totalHeightPx += style.mathStyle.marginTop
totalHeightPx += mathHeightByIndex[index] ?: 0f
maxContentWidthPx = width
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.swmansion.enriched.markdown.utils.common

import android.content.Context
import android.text.SpannableString
import com.swmansion.enriched.markdown.parser.MarkdownASTNode
import com.swmansion.enriched.markdown.renderer.Renderer
import com.swmansion.enriched.markdown.spans.ImageSpan
import com.swmansion.enriched.markdown.styles.StyleConfig

sealed interface RenderedSegment {
data class Text(
val styledText: SpannableString,
val imageSpans: List<ImageSpan>,
val needsJustify: Boolean,
val lastElementMarginBottom: Float,
) : RenderedSegment

data class Table(
val node: MarkdownASTNode,
) : RenderedSegment

data class Math(
val latex: String,
) : RenderedSegment
}

object MarkdownSegmentRenderer {
fun render(
segments: List<MarkdownSegment>,
style: StyleConfig,
context: Context,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
): List<RenderedSegment> =
segments.map { segment ->
when (segment) {
is MarkdownSegment.Text -> renderTextSegment(segment.nodes, style, context, onLinkPress, onLinkLongPress)
is MarkdownSegment.Table -> RenderedSegment.Table(segment.node)
is MarkdownSegment.Math -> RenderedSegment.Math(segment.latex)
}
}

private fun renderTextSegment(
nodes: List<MarkdownASTNode>,
style: StyleConfig,
context: Context,
onLinkPress: ((String) -> Unit)?,
onLinkLongPress: ((String) -> Unit)?,
): RenderedSegment.Text {
val documentWrapper = MarkdownASTNode(type = MarkdownASTNode.NodeType.Document, children = nodes)
val renderer = Renderer().apply { configure(style, context) }

return RenderedSegment.Text(
styledText = renderer.renderDocument(documentWrapper, onLinkPress, onLinkLongPress),
imageSpans = renderer.getCollectedImageSpans().toList(),
needsJustify = style.needsJustify,
lastElementMarginBottom = renderer.getLastElementMarginBottom(),
)
}
}
Loading