From 3965b0a1b4b681e8e43b0f4ecad62cb1d5e97a9a Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Fri, 30 Aug 2024 15:58:05 +0200 Subject: [PATCH 1/3] - simplify Coil3 intrinsic size logic - relates to: https://github.com/mikepenz/multiplatform-markdown-renderer/issues/168#issuecomment-2321197857 --- .../markdown/coil3/Coil3ImageTransformerImpl.kt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt b/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt index 71a6b23..333b969 100644 --- a/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt +++ b/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt @@ -1,12 +1,6 @@ package com.mikepenz.markdown.coil3 -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.painter.Painter import coil3.compose.AsyncImagePainter @@ -33,13 +27,11 @@ object Coil3ImageTransformerImpl : ImageTransformer { var size by remember(painter) { mutableStateOf(painter.intrinsicSize) } if (painter is AsyncImagePainter) { val painterState = painter.state.collectAsState() - LaunchedEffect(painterState) { - painterState.value.painter?.let { - size = it.intrinsicSize - } + val intrinsicSize = painterState.value.painter?.intrinsicSize + if (intrinsicSize != null) { + size = intrinsicSize } } - return size } } \ No newline at end of file From c1d88f7decd2ef6d8914800563497f686f34a781 Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Fri, 30 Aug 2024 22:47:04 +0200 Subject: [PATCH 2/3] - further simplify retrieval of intrinsic size --- .../mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt b/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt index 333b969..7334009 100644 --- a/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt +++ b/multiplatform-markdown-renderer-coil3/src/commonMain/kotlin/com/mikepenz/markdown/coil3/Coil3ImageTransformerImpl.kt @@ -13,7 +13,7 @@ import com.mikepenz.markdown.model.ImageTransformer object Coil3ImageTransformerImpl : ImageTransformer { @Composable - override fun transform(link: String): ImageData? { + override fun transform(link: String): ImageData { return rememberAsyncImagePainter( model = ImageRequest.Builder(LocalPlatformContext.current) .data(link) @@ -28,9 +28,7 @@ object Coil3ImageTransformerImpl : ImageTransformer { if (painter is AsyncImagePainter) { val painterState = painter.state.collectAsState() val intrinsicSize = painterState.value.painter?.intrinsicSize - if (intrinsicSize != null) { - size = intrinsicSize - } + intrinsicSize?.also { size = it } } return size } From a90b49532f7e1fb939e6c208e6ce37026317122a Mon Sep 17 00:00:00 2001 From: Mike Penz Date: Fri, 30 Aug 2024 22:48:33 +0200 Subject: [PATCH 3/3] - move computation of placeholder size to `ImageTransformer` offering more flexibility - add new PlaceholderConfig which contains the information on placeholder size and vertical align - expand `ImageData` with `contentScale`, `alpha`, `colorFilter` allowing additional configuration options --- .../compose/elements/MarkdownImage.kt | 5 ++- .../markdown/compose/elements/MarkdownText.kt | 37 ++++++++++------ .../markdown/model/ImageTransformer.kt | 44 ++++++++++++++++++- .../markdown/model/MarkdownImageState.kt | 36 ++++----------- 4 files changed, 79 insertions(+), 43 deletions(-) diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt index cd1ebfe..82b88a5 100644 --- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt +++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownImage.kt @@ -17,8 +17,11 @@ fun MarkdownImage(content: String, node: ASTNode) { Image( painter = imageData.painter, contentDescription = imageData.contentDescription, + modifier = imageData.modifier, alignment = imageData.alignment, - modifier = imageData.modifier + contentScale = imageData.contentScale, + alpha = imageData.alpha, + colorFilter = imageData.colorFilter ) } } diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt index 6b5b88a..0019e28 100644 --- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt +++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt @@ -6,15 +6,15 @@ import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.foundation.text.InlineTextContent -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalUriHandler -import androidx.compose.ui.text.* +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.Placeholder +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.sp import com.mikepenz.markdown.compose.* import com.mikepenz.markdown.compose.elements.material.MarkdownBasicText @@ -110,6 +110,16 @@ fun MarkdownText( } } else modifier + + val transformer = LocalImageTransformer.current + val placeholderState by derivedStateOf { + transformer.placeholderConfig( + imageState.density, + imageState.containerSize, + imageState.intrinsicImageSize + ) + } + MarkdownBasicText( text = content, modifier = textModifier @@ -123,31 +133,30 @@ fun MarkdownText( color = LocalMarkdownColors.current.text, inlineContent = mapOf(MARKDOWN_TAG_IMAGE_URL to InlineTextContent( Placeholder( - width = imageState.imageSize.width.sp, - height = imageState.imageSize.height.sp, - placeholderVerticalAlign = PlaceholderVerticalAlign.Bottom + width = placeholderState.size.width.sp, + height = placeholderState.size.height.sp, + placeholderVerticalAlign = placeholderState.verticalAlign ) ) { link -> - val transformer = LocalImageTransformer.current - transformer.transform(link)?.let { imageData -> val intrinsicSize = transformer.intrinsicSize(imageData.painter) - LaunchedEffect(intrinsicSize) { imageState.setImageSize(intrinsicSize) } - Image( painter = imageData.painter, contentDescription = imageData.contentDescription, + modifier = imageData.modifier, alignment = imageData.alignment, - modifier = imageData.modifier + contentScale = imageData.contentScale, + alpha = imageData.alpha, + colorFilter = imageData.colorFilter ) } }), onTextLayout = { layoutResult.value = it - onTextLayout?.invoke(it) + onTextLayout.invoke(it) } ) } diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt index 3214e6c..5cf6836 100644 --- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt +++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/ImageTransformer.kt @@ -6,22 +6,64 @@ import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size +import androidx.compose.ui.geometry.isUnspecified +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.PlaceholderVerticalAlign +import androidx.compose.ui.unit.Density interface ImageTransformer { + /** + * Will retrieve the [ImageData] from an image link/url + */ @Composable fun transform(link: String): ImageData? + /** + * Returns the detected intrinsic size of the painter + */ @Composable fun intrinsicSize(painter: Painter): Size { return painter.intrinsicSize } + + /** + * The expected placeholderSize. Note: The same size is shared for all inline images within a single MarkdownText item. + */ + fun placeholderConfig(density: Density, containerSize: Size, intrinsicImageSize: Size): PlaceholderConfig { + return PlaceholderConfig(with(density) { + if (containerSize.isUnspecified) { + Size(180f, 180f) + } else if (intrinsicImageSize.isUnspecified) { + Size(containerSize.width.toSp().value, 180f) + } else { + val width = minOf(intrinsicImageSize.width, containerSize.width) + val height = if (intrinsicImageSize.width < containerSize.width) { + intrinsicImageSize.height + } else { + (intrinsicImageSize.height * containerSize.width) / intrinsicImageSize.width + } + Size(width.toSp().value, height.toSp().value) + } + }) + } } +@Immutable +data class PlaceholderConfig( + val size: Size, + val verticalAlign: PlaceholderVerticalAlign = PlaceholderVerticalAlign.Bottom, +) + @Immutable data class ImageData( val painter: Painter, + val modifier: Modifier = Modifier.fillMaxWidth(), val contentDescription: String? = "Image", val alignment: Alignment = Alignment.CenterStart, - val modifier: Modifier = Modifier.fillMaxWidth() + val contentScale: ContentScale = ContentScale.Fit, + val alpha: Float = DefaultAlpha, + val colorFilter: ColorFilter? = null, ) \ No newline at end of file diff --git a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownImageState.kt b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownImageState.kt index e56ed15..2a3a691 100644 --- a/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownImageState.kt +++ b/multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownImageState.kt @@ -2,45 +2,27 @@ package com.mikepenz.markdown.model import androidx.compose.runtime.* import androidx.compose.ui.geometry.Size -import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.toSize internal interface MarkdownImageState { - val imageSize: Size + val density: Density + val containerSize: Size + val intrinsicImageSize: Size fun setContainerSize(intSize: IntSize) fun setImageSize(size: Size) } -internal class MarkdownImageStateImpl(private val density: Density) : MarkdownImageState { - - private var parentSize by mutableStateOf(Size.Unspecified) - - private var intrinsicImageSize by mutableStateOf(Size.Unspecified) - - override val imageSize by derivedStateOf { - with(density) { - if (parentSize.isUnspecified) { - Size(180f, 180f) - } else if (intrinsicImageSize.isUnspecified) { - Size(parentSize.width.toSp().value, 180f) - } else { - val width = minOf(intrinsicImageSize.width, parentSize.width) - - val height = if (intrinsicImageSize.width < parentSize.width) { - intrinsicImageSize.height - } else { - (intrinsicImageSize.height * parentSize.width) / intrinsicImageSize.width - } - Size(width.toSp().value, height.toSp().value) - } - } - } +internal class MarkdownImageStateImpl(override val density: Density) : MarkdownImageState { + + override var containerSize by mutableStateOf(Size.Unspecified) + + override var intrinsicImageSize by mutableStateOf(Size.Unspecified) override fun setContainerSize(intSize: IntSize) { - parentSize = intSize.toSize() + containerSize = intSize.toSize() } override fun setImageSize(size: Size) {