Skip to content

Commit a0532d8

Browse files
committed
Add inline base64 image rendering
1 parent 6a6b705 commit a0532d8

7 files changed

Lines changed: 52 additions & 16 deletions

File tree

android-sample/src/main/java/com/zachklipp/richtext/sample/MarkdownSample.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,10 @@ private val sampleMarkdown = """
328328
![random image][logo]
329329
330330
[logo]: https://picsum.photos/seed/picsum2/400/400 "Text 2"
331+
332+
Base64 Inline
333+
334+
![][image1]
331335
332336
---
333337
@@ -417,4 +421,6 @@ private val sampleMarkdown = """
417421
[arbitrary case-insensitive reference text]: https://www.mozilla.org
418422
[1]: http://slashdot.org
419423
[link text itself]: http://www.reddit.com
424+
425+
[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbkAAAChCAIAAADVxFY9AAAIb0lEQVR4Xu3aa4jVRRjH8R8JkiRFZWlXSZQVxRAkaUlcCivT7EahKIYiiEuLkiRdba0Mw1AMQ5CkJUmMytpuGok3FEOQpCVJkrQytRLDUAzD2Fh3znicoX1OL2bP+Tffz8t583/zzJeH4S8BACrRDgD4d7QSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBsVWul+zBQjfED/quqDesFdwV5W/B385b2LeGIALXEDWt4nN4FdwV563FWze3N4YgAtcQNa3icnr8nPXWml0731snLdOIKHb9Kv/XT0ev084368SYdGKj9ddo3RHuHqW249ozQ7pHaVa+do7S9QVvv0KY79cVYbRivT+/TRw/qg4f13kS9M1lrpmr1NLXM0KqZWtmoFU1aPkfL5mrJPC1+Soue1cLn9cILen6hnl2kpxZr3hLNXaY5y9W0Qo0rNXOVZrRo2mpNXaPJ72jie3r4Az34ke77VOM3aOwXunOT7tiqhu0atVP1uzRyt0bs0fA2DdurIftUt18DD+imH3Xjz7ruqPr9pquO64oTuuykep9WrzPqeVY92hsazl6kv3rq9MU61VsnLtXvl+tYH/3aV4ev0aEbdLC/vh+g7wZq32B9M0RtN+ur4do9Qrtu0Zf12n6btjVo8+3aOEaf36X14/TxvWp9QO8/pHcf0dpJWjNFb01Vy3S9MUMrZ2rFLL3epGWztXSuFj+hV57Uy0/rpflqbtb8l/TMy3ryFT2xWHOXavYyNb2uWSs0c6VmrNL0Fk19S1PWaOJaPfKuHnpfD7Tq3o81br3u+lxjNur2TWrYptu2q/5LjdylEbs1/Cvd3KYh32jwPg38TgO+V/8fdMMhXXNYfX/Vlcd0+e+69IQfAV1EK1Hz3LCGx+n5e5J1K3voTE/92UunLtEfpVb+0ldHrtWh6/VDZysH6ds67R2qtmHa09nKkdpZrx2jtHV0qZV367Nx+mSCWu/XulIr356i1Y+qZZpWdbayUcsf02tztPRxvdrZymf04nwtWKDnXiy18lU9vlRzXlPTcjWWWjmtRY+u1pS3Namzlet0/4ea8InGfaa7O1u5WaO3adQO3bqz1Mo9GtamoXs1+FsNKrXy+kO69oj6/qI+na38w48AeyUKwA1reJyevyeZt9Lvla6VV0Z75aDze6VrZdleuanUSr9XulZOjPbKxvN7pWtl2V75dKmVfq90rXwj2ivXnd8rXSvL9spbSq30e6Vr5cFor6SVKBQ3rOFxev6eZN7Kzr3yZPleeXW0Vw7u2Cu/Lt8rb+3YK7dFe+WH5Xvl5GivbOrYK5eU75XPdeyV86O98rHyvfLNaK9s7dgr7ynfK7d27JX10V5ZV75X/sReiQJzwxoep+fvSdatjN8r472yy/dKt1fG75XxXtnle6XbK+P3yniv7PK90u2V8XtlvFfyXolCccMaHqfn70nWrazwvfLcXhm/V8Z7pfFeeW6vjN8r473SeK88t1fG75XxXsl7Jf433LCGx+n5e5J5K3mvFK1EEbhhDY/T8/ck81byXilaiSJwwxoep+fvSdatjN8r+b8SqEluWMPj9Pw9ybqVFb5X8n8lUG1uWMPj9Pw9ybyVvFeKVqII3LCGx+n5e5J5K3mvFK1EEbhhDY/T8/ck61bG75XxXtnleyX/VwLdww1reJyevydZt7LC90r+rwSqzQ1reJyevyeZt5L3StFKFIEb1vA4PX9PMm8l75WilSgCN6zhcXr+nmTdyvi9kv8rgZrkhjU8Ts/fk6xbWeF7Jf9XAtXmhjU8Ts/fk8xbyXulaCWKwA1reJyevyeZt5L3StFKFIEb1vA4PX9Psm5l/F4Z75VdvlfyfyXQPdywhsfp+XuSdSsrfK/k/0qg2tywhsfp+XuSeSt5rxStRBG4YQ2P0/P3JPNW8l4pWokicMMaHqfn70nWrYzfK/m/EqhJbljD4/T8Pcm6lRW+V/J/JVBtbljD4/T8Pcm8lbxXilaiCNywhsfp+XuSeSt5rxStRBG4YQ2P0/P3JOtWxu+V8V7Z5Xsl/1cC3cMNa3icnr8nWbeywvdK/q8Eqs0Na3icnr8nmbeS90rRShSBG9bwOD1/TzJvJe+VopUoAjes4XF6/p5k3cr4vZL/K4Ga5IY1PE7P35OsW1nheyX/VwLV5oY1PE7P35PMW8l7pWglisANa3icnr8nmbeS90rRShSBG9bwOD1/T7JuZfxeGe+VXb5X8n8l0D3csIbH6Z2/KMgeeyVqnxvW8Di9C+4K8rbg7+Yt7VvCEQFqiRvW8Di9C+4K8hYOB1B7GFYAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALC5VgIAuvYP8v0NLroTl6oAAAAASUVORK5CYII=>
420426
""".trimIndent()

desktop-sample/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ dependencies {
1010
implementation(project(":richtext-commonmark"))
1111
implementation(project(":richtext-ui-material"))
1212
implementation(compose.desktop.currentOs)
13+
implementation(compose.materialIconsExtended)
1314
}
1415

1516
compose.desktop {

desktop-sample/src/main/kotlin/com/halilibo/richtext/desktop/MarkdownSampleApp.kt

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,19 +267,16 @@ private val sampleMarkdown = """
267267
##### Header 5
268268
###### Header 6
269269
---
270-
271-
## Full-bleed Image
272270
271+
## Full-bleed Image
273272
![](https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1920px-Image_created_with_a_mobile_phone.png)
274-
273+
275274
## Images smaller than the width should center
276275
![](https://cdn.nostr.build/p/4a84.png)
277-
276+
278277
On LineHeight bug, the image below goes over this text.
279278
![](https://cdn.nostr.build/p/PxZ0.jpg)
280279
281-
---
282-
283280
## Emphasis
284281
285282
Emphasis, aka italics, with *asterisks* or _underscores_.
@@ -307,6 +304,16 @@ private val sampleMarkdown = """
307304
* Unordered list can use asterisks
308305
- Or minuses
309306
+ Or pluses
307+
<!-- -->
308+
2. Ordered list starting with `2.`
309+
3. Another item
310+
<!-- -->
311+
0. Ordered list starting with `0.`
312+
<!-- -->
313+
003. Ordered list starting with `003.`
314+
<!-- -->
315+
-1. Starting with `-1.` should not be list
316+
310317
311318
---
312319
@@ -388,7 +395,9 @@ private val sampleMarkdown = """
388395
389396
## Images
390397
391-
Inline-style:
398+
Inline-style:
399+
400+
![random image](https://picsum.photos/seed/picsum/400/400)
392401
393402
![random image](https://picsum.photos/seed/picsum/400/400 "Text 1")
394403
@@ -397,6 +406,10 @@ private val sampleMarkdown = """
397406
![random image][logo]
398407
399408
[logo]: https://picsum.photos/seed/picsum2/400/400 "Text 2"
409+
410+
Base64 Inline
411+
412+
![][image1]
400413
401414
---
402415
@@ -486,4 +499,6 @@ private val sampleMarkdown = """
486499
[arbitrary case-insensitive reference text]: https://www.mozilla.org
487500
[1]: http://slashdot.org
488501
[link text itself]: http://www.reddit.com
502+
503+
[image1]: <data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAbkAAAChCAIAAADVxFY9AAAIb0lEQVR4Xu3aa4jVRRjH8R8JkiRFZWlXSZQVxRAkaUlcCivT7EahKIYiiEuLkiRdba0Mw1AMQ5CkJUmMytpuGok3FEOQpCVJkrQytRLDUAzD2Fh3znicoX1OL2bP+Tffz8t583/zzJeH4S8BACrRDgD4d7QSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBstBIAbLQSAGy0EgBsVWul+zBQjfED/quqDesFdwV5W/B385b2LeGIALXEDWt4nN4FdwV563FWze3N4YgAtcQNa3icnr8nPXWml0731snLdOIKHb9Kv/XT0ev084368SYdGKj9ddo3RHuHqW249ozQ7pHaVa+do7S9QVvv0KY79cVYbRivT+/TRw/qg4f13kS9M1lrpmr1NLXM0KqZWtmoFU1aPkfL5mrJPC1+Soue1cLn9cILen6hnl2kpxZr3hLNXaY5y9W0Qo0rNXOVZrRo2mpNXaPJ72jie3r4Az34ke77VOM3aOwXunOT7tiqhu0atVP1uzRyt0bs0fA2DdurIftUt18DD+imH3Xjz7ruqPr9pquO64oTuuykep9WrzPqeVY92hsazl6kv3rq9MU61VsnLtXvl+tYH/3aV4ev0aEbdLC/vh+g7wZq32B9M0RtN+ur4do9Qrtu0Zf12n6btjVo8+3aOEaf36X14/TxvWp9QO8/pHcf0dpJWjNFb01Vy3S9MUMrZ2rFLL3epGWztXSuFj+hV57Uy0/rpflqbtb8l/TMy3ryFT2xWHOXavYyNb2uWSs0c6VmrNL0Fk19S1PWaOJaPfKuHnpfD7Tq3o81br3u+lxjNur2TWrYptu2q/5LjdylEbs1/Cvd3KYh32jwPg38TgO+V/8fdMMhXXNYfX/Vlcd0+e+69IQfAV1EK1Hz3LCGx+n5e5J1K3voTE/92UunLtEfpVb+0ldHrtWh6/VDZysH6ds67R2qtmHa09nKkdpZrx2jtHV0qZV367Nx+mSCWu/XulIr356i1Y+qZZpWdbayUcsf02tztPRxvdrZymf04nwtWKDnXiy18lU9vlRzXlPTcjWWWjmtRY+u1pS3Namzlet0/4ea8InGfaa7O1u5WaO3adQO3bqz1Mo9GtamoXs1+FsNKrXy+kO69oj6/qI+na38w48AeyUKwA1reJyevyeZt9Lvla6VV0Z75aDze6VrZdleuanUSr9XulZOjPbKxvN7pWtl2V75dKmVfq90rXwj2ivXnd8rXSvL9spbSq30e6Vr5cFor6SVKBQ3rOFxev6eZN7Kzr3yZPleeXW0Vw7u2Cu/Lt8rb+3YK7dFe+WH5Xvl5GivbOrYK5eU75XPdeyV86O98rHyvfLNaK9s7dgr7ynfK7d27JX10V5ZV75X/sReiQJzwxoep+fvSdatjN8r472yy/dKt1fG75XxXtnle6XbK+P3yniv7PK90u2V8XtlvFfyXolCccMaHqfn70nWrazwvfLcXhm/V8Z7pfFeeW6vjN8r473SeK88t1fG75XxXsl7Jf433LCGx+n5e5J5K3mvFK1EEbhhDY/T8/ck81byXilaiSJwwxoep+fvSdatjN8r+b8SqEluWMPj9Pw9ybqVFb5X8n8lUG1uWMPj9Pw9ybyVvFeKVqII3LCGx+n5e5J5K3mvFK1EEbhhDY/T8/ck61bG75XxXtnleyX/VwLdww1reJyevydZt7LC90r+rwSqzQ1reJyevyeZt5L3StFKFIEb1vA4PX9PMm8l75WilSgCN6zhcXr+nmTdyvi9kv8rgZrkhjU8Ts/fk6xbWeF7Jf9XAtXmhjU8Ts/fk8xbyXulaCWKwA1reJyevyeZt5L3StFKFIEb1vA4PX9Psm5l/F4Z75VdvlfyfyXQPdywhsfp+XuSdSsrfK/k/0qg2tywhsfp+XuSeSt5rxStRBG4YQ2P0/P3JPNW8l4pWokicMMaHqfn70nWrYzfK/m/EqhJbljD4/T8Pcm6lRW+V/J/JVBtbljD4/T8Pcm8lbxXilaiCNywhsfp+XuSeSt5rxStRBG4YQ2P0/P3JOtWxu+V8V7Z5Xsl/1cC3cMNa3icnr8nWbeywvdK/q8Eqs0Na3icnr8nmbeS90rRShSBG9bwOD1/TzJvJe+VopUoAjes4XF6/p5k3cr4vZL/K4Ga5IY1PE7P35OsW1nheyX/VwLV5oY1PE7P35PMW8l7pWglisANa3icnr8nmbeS90rRShSBG9bwOD1/T7JuZfxeGe+VXb5X8n8l0D3csIbH6Z2/KMgeeyVqnxvW8Di9C+4K8rbg7+Yt7VvCEQFqiRvW8Di9C+4K8hYOB1B7GFYAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALDRSgCw0UoAsNFKALC5VgIAuvYP8v0NLroTl6oAAAAASUVORK5CYII=>
489504
""".trimIndent()

richtext-markdown/src/androidMain/kotlin/com/halilibo/richtext/markdown/RemoteImage.kt renamed to richtext-markdown/src/androidMain/kotlin/com/halilibo/richtext/markdown/MarkdownImage.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.halilibo.richtext.markdown
22

3+
import android.annotation.SuppressLint
4+
import android.util.Base64
35
import androidx.compose.foundation.Image
46
import androidx.compose.foundation.layout.BoxWithConstraints
57
import androidx.compose.foundation.layout.size
@@ -21,25 +23,32 @@ import coil.size.Size
2123
private val DEFAULT_IMAGE_SIZE = 64.dp
2224

2325
/**
24-
* Implementation of RemoteImage by using Coil library for Android.
26+
* Implementation of MarkdownImage by using Coil library for Android.
2527
*/
2628
@Composable
27-
internal actual fun RemoteImage(
29+
internal actual fun MarkdownImage(
2830
url: String,
2931
contentDescription: String?,
3032
modifier: Modifier,
3133
contentScale: ContentScale
3234
) {
35+
val data = if (url.startsWith("data:image") && url.contains("base64")) {
36+
val base64ImageString = url.substringAfter("base64,")
37+
Base64.decode(base64ImageString, Base64.DEFAULT)
38+
} else {
39+
url
40+
}
3341
val painter = rememberAsyncImagePainter(
3442
ImageRequest.Builder(LocalContext.current)
35-
.data(data = url)
43+
.data(data = data)
3644
.size(Size.ORIGINAL)
3745
.crossfade(true)
3846
.build()
3947
)
4048

4149
val density = LocalDensity.current
4250

51+
@SuppressLint("UnusedBoxWithConstraintsScope")
4352
BoxWithConstraints(modifier, contentAlignment = Alignment.Center) {
4453
val sizeModifier by remember(density, painter) {
4554
derivedStateOf {

richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/RemoteImage.kt renamed to richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownImage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import androidx.compose.ui.layout.ContentScale
1010
* way to show images but it doesn't exist in desktop.
1111
*/
1212
@Composable
13-
internal expect fun RemoteImage(
13+
internal expect fun MarkdownImage(
1414
url: String,
1515
contentDescription: String?,
1616
modifier: Modifier = Modifier,

richtext-markdown/src/commonMain/kotlin/com/halilibo/richtext/markdown/MarkdownRichText.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ private fun computeRichTextString(astNode: AstNode): RichTextString {
100100
IntSize(128.dp.roundToPx(), 128.dp.roundToPx())
101101
}
102102
) {
103-
RemoteImage(
103+
MarkdownImage(
104104
url = currentNodeType.destination,
105105
contentDescription = currentNodeType.title,
106106
modifier = Modifier.fillMaxWidth(),

richtext-markdown/src/jvmMain/kotlin/com/halilibo/richtext/markdown/RemoteImage.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue
66
import androidx.compose.runtime.produceState
77
import androidx.compose.ui.Modifier
88
import androidx.compose.ui.graphics.ImageBitmap
9-
import androidx.compose.ui.graphics.asImageBitmap
109
import androidx.compose.ui.graphics.toComposeImageBitmap
1110
import androidx.compose.ui.layout.ContentScale
1211
import kotlinx.coroutines.Dispatchers
@@ -17,18 +16,24 @@ import java.io.ByteArrayOutputStream
1716
import java.io.InputStream
1817
import java.net.HttpURLConnection
1918
import java.net.URL
19+
import java.util.Base64
2020
import javax.imageio.ImageIO
2121

2222
@Composable
23-
internal actual fun RemoteImage(
23+
internal actual fun MarkdownImage(
2424
url: String,
2525
contentDescription: String?,
2626
modifier: Modifier,
2727
contentScale: ContentScale
2828
) {
2929
val image by produceState<ImageBitmap?>(null, url) {
30-
loadFullImage(url)?.let {
31-
value = makeFromEncoded(toByteArray(it)).toComposeImageBitmap()
30+
if (url.startsWith("data:image") && url.contains("base64")) {
31+
val base64ImageString = url.substringAfter("base64,")
32+
value = makeFromEncoded(Base64.getDecoder().decode(base64ImageString)).toComposeImageBitmap()
33+
} else {
34+
loadFullImage(url)?.let {
35+
value = makeFromEncoded(toByteArray(it)).toComposeImageBitmap()
36+
}
3237
}
3338
}
3439

0 commit comments

Comments
 (0)