Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiplatform support to coil-svg #1965

Merged
merged 23 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
24 changes: 24 additions & 0 deletions buildSrc/src/main/kotlin/coil3/skikoJvm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package coil3

import org.gradle.api.Project

fun Project.skikoAwtRuntimeDependency(version: String): String {
val osName = System.getProperty("os.name")
val targetOs = when {
osName == "Mac OS X" -> "macos"
osName.startsWith("Win") -> "windows"
osName.startsWith("Linux") -> "linux"
else -> error("Unsupported OS: $osName")
}

val osArch = System.getProperty("os.arch")
val targetArch = when (osArch) {
"x86_64", "amd64" -> "x64"
"aarch64" -> "arm64"
else -> error("Unsupported arch: $osArch")
}

val target = "${targetOs}-${targetArch}"

return "org.jetbrains.skiko:skiko-awt-runtime-$target:$version"
}
27 changes: 27 additions & 0 deletions coil-svg/api/android/coil-svg.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
public final class coil3/decode/SvgDecodeUtilsKt {
public static final fun isSvg (Lcoil3/decode/DecodeUtils;Lokio/BufferedSource;)Z
}

public final class coil3/decode/SvgDecoder : coil3/decode/Decoder {
public fun <init> (Lcoil3/decode/ImageSource;Lcoil3/request/Options;)V
public fun <init> (Lcoil3/decode/ImageSource;Lcoil3/request/Options;Z)V
public synthetic fun <init> (Lcoil3/decode/ImageSource;Lcoil3/request/Options;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getUseViewBoundsAsIntrinsicSize ()Z
}

public final class coil3/decode/SvgDecoder$Factory : coil3/decode/Decoder$Factory {
public fun <init> ()V
public fun <init> (Z)V
public synthetic fun <init> (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/Decoder;
public final fun getUseViewBoundsAsIntrinsicSize ()Z
}

public final class coil3/request/SvgsKt {
public static final fun css (Lcoil3/request/ImageRequest$Builder;Ljava/lang/String;)Lcoil3/request/ImageRequest$Builder;
public static final fun getCss (Lcoil3/Extras$Key$Companion;)Lcoil3/Extras$Key;
public static final fun getCss (Lcoil3/request/ImageRequest;)Ljava/lang/String;
public static final fun getCss (Lcoil3/request/Options;)Ljava/lang/String;
}

26 changes: 26 additions & 0 deletions coil-svg/api/jvm/coil-svg.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
public final class coil3/decode/SvgDecodeUtilsKt {
public static final fun isSvg (Lcoil3/decode/DecodeUtils;Lokio/BufferedSource;)Z
}

public final class coil3/decode/SvgDecoder : coil3/decode/Decoder {
public fun <init> (Lcoil3/decode/ImageSource;Lcoil3/request/Options;Z)V
public synthetic fun <init> (Lcoil3/decode/ImageSource;Lcoil3/request/Options;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun decode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getUseViewBoundsAsIntrinsicSize ()Z
}

public final class coil3/decode/SvgDecoder$Factory : coil3/decode/Decoder$Factory {
public fun <init> ()V
public fun <init> (Z)V
public synthetic fun <init> (ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun create (Lcoil3/fetch/SourceFetchResult;Lcoil3/request/Options;Lcoil3/ImageLoader;)Lcoil3/decode/Decoder;
public final fun getUseViewBoundsAsIntrinsicSize ()Z
}

public final class coil3/request/SvgsKt {
public static final fun css (Lcoil3/request/ImageRequest$Builder;Ljava/lang/String;)Lcoil3/request/ImageRequest$Builder;
public static final fun getCss (Lcoil3/Extras$Key$Companion;)Lcoil3/Extras$Key;
public static final fun getCss (Lcoil3/request/ImageRequest;)Ljava/lang/String;
public static final fun getCss (Lcoil3/request/Options;)Ljava/lang/String;
}

63 changes: 51 additions & 12 deletions coil-svg/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,60 @@
import coil3.addAllMultiplatformTargets
import coil3.androidInstrumentedTest
import coil3.androidLibrary
import coil3.androidUnitTest
import coil3.skikoAwtRuntimeDependency

plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-multiplatform")
id("kotlinx-atomicfu")
}

addAllMultiplatformTargets()
androidLibrary(name = "coil3.svg")

dependencies {
api(projects.coilCore)

implementation(libs.androidx.core)
implementation(libs.svg)

testImplementation(projects.internal.testUtils)
testImplementation(libs.bundles.test.jvm)

androidTestImplementation(projects.internal.testUtils)
androidTestImplementation(libs.bundles.test.android)
kotlin {
sourceSets {
commonMain {
dependencies {
api(projects.coilCore)
implementation(libs.coroutines.core)
}
}
commonTest {
dependencies {
implementation(projects.internal.testUtils)
implementation(libs.bundles.test.common)
}
}
named("nonAndroidMain") {
dependencies {
api(libs.skiko)
}
}
androidMain {
dependencies {
implementation(libs.androidx.core)
implementation(libs.svg)
}
}
androidUnitTest {
dependencies {
implementation(projects.internal.testUtils)
implementation(libs.bundles.test.jvm)
}
}
androidInstrumentedTest {
dependencies {
implementation(projects.internal.testUtils)
implementation(libs.bundles.test.android)
}
}
jvmTest {
dependencies {
implementation(projects.internal.testUtils)
implementation(skikoAwtRuntimeDependency(libs.versions.skiko.get()))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package coil3.decode

import coil3.test.utils.AbstractSvgDecoderTest

class SvgDecoderTestAndroid : AbstractSvgDecoderTest(SvgDecoder.Factory())
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ import kotlinx.coroutines.runInterruptible
* @param useViewBoundsAsIntrinsicSize If true, uses the SVG's view bounds as the intrinsic size for
* the SVG. If false, uses the SVG's width/height as the intrinsic size for the SVG.
*/
class SvgDecoder @JvmOverloads constructor(
actual class SvgDecoder @JvmOverloads actual constructor(
private val source: ImageSource,
private val options: Options,
val useViewBoundsAsIntrinsicSize: Boolean = true,
val useViewBoundsAsIntrinsicSize: Boolean,
) : Decoder {

override suspend fun decode() = runInterruptible {
Expand Down Expand Up @@ -93,8 +93,8 @@ class SvgDecoder @JvmOverloads constructor(
}
}

class Factory @JvmOverloads constructor(
val useViewBoundsAsIntrinsicSize: Boolean = true,
actual class Factory @JvmOverloads actual constructor(
val useViewBoundsAsIntrinsicSize: Boolean,
) : Decoder.Factory {

override fun create(
Expand All @@ -110,9 +110,4 @@ class SvgDecoder @JvmOverloads constructor(
return result.mimeType == MIME_TYPE_SVG || DecodeUtils.isSvg(result.source.source())
}
}

companion object {
private const val MIME_TYPE_SVG = "image/svg+xml"
private const val DEFAULT_SIZE = 512f
}
}
12 changes: 12 additions & 0 deletions coil-svg/src/androidMain/kotlin/coil3/util/svgUtils.android.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package coil3.util

import android.graphics.Bitmap
import android.os.Build.VERSION.SDK_INT

internal val Bitmap.Config.isHardware: Boolean
get() = SDK_INT >= 26 && this == Bitmap.Config.HARDWARE

/** Convert null and [Bitmap.Config.HARDWARE] configs to [Bitmap.Config.ARGB_8888]. */
internal fun Bitmap.Config?.toSoftware(): Bitmap.Config {
return if (this == null || isHardware) Bitmap.Config.ARGB_8888 else this
}
25 changes: 25 additions & 0 deletions coil-svg/src/commonMain/kotlin/coil3/decode/SvgDecoder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package coil3.decode

import coil3.request.Options

/**
* A [Decoder] that uses [AndroidSVG](https://bigbadaboom.github.io/androidsvg/)
* and [SVGDOM](https://api.skia.org/classSkSVGDOM.html/) to decode SVG
* files.
*
* @param useViewBoundsAsIntrinsicSize If true, uses the SVG's view bounds as the intrinsic size for
* the SVG. If false, uses the SVG's width/height as the intrinsic size for the SVG.
*/
expect class SvgDecoder(
source: ImageSource,
options: Options,
useViewBoundsAsIntrinsicSize: Boolean = true,
) : Decoder {

class Factory(
useViewBoundsAsIntrinsicSize: Boolean = true
) : Decoder.Factory
}

internal const val MIME_TYPE_SVG = "image/svg+xml"
internal const val DEFAULT_SIZE = 512f
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package coil3.util

import android.graphics.Bitmap
import android.os.Build.VERSION.SDK_INT
import coil3.size.Dimension
import coil3.size.Scale
import coil3.size.Size
import coil3.size.isOriginal
import okio.BufferedSource
import okio.ByteString

Expand All @@ -23,14 +23,6 @@ internal fun BufferedSource.indexOf(bytes: ByteString, fromIndex: Long, toIndex:
return -1
}

internal val Bitmap.Config.isHardware: Boolean
get() = SDK_INT >= 26 && this == Bitmap.Config.HARDWARE

/** Convert null and [Bitmap.Config.HARDWARE] configs to [Bitmap.Config.ARGB_8888]. */
internal fun Bitmap.Config?.toSoftware(): Bitmap.Config {
return if (this == null || isHardware) Bitmap.Config.ARGB_8888 else this
}

internal fun Dimension.toPx(scale: Scale): Float {
if (this is Dimension.Pixels) {
return px.toFloat()
Expand All @@ -41,3 +33,11 @@ internal fun Dimension.toPx(scale: Scale): Float {
}
}
}

internal inline fun Size.widthPx(scale: Scale, original: () -> Float): Float {
return if (isOriginal) original() else width.toPx(scale)
}

internal inline fun Size.heightPx(scale: Scale, original: () -> Float): Float {
return if (isOriginal) original() else height.toPx(scale)
}
23 changes: 23 additions & 0 deletions coil-svg/src/commonTest/kotlin/coil3/decode/SvgDecoderTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package coil3.decode

import kotlin.test.Test
import kotlin.test.assertTrue
import okio.Buffer

class SvgDecoderTest {

/** Regression test: https://github.com/coil-kt/coil/issues/1154 */
@Test
fun isSvg_newLine() {
val text = "<svg\n" +
" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n" +
" xmlns:cc=\"http://creativecommons.org/ns#\"\n" +
" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n" +
" xmlns:svg=\"http://www.w3.org/2000/svg\"\n" +
" xmlns=\"http://www.w3.org/2000/svg\"\n" +
" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"/>"
val buffer = Buffer().apply { writeUtf8(text) }

assertTrue(DecodeUtils.isSvg(buffer))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package coil3.decode

import coil3.test.utils.AbstractSvgDecoderTest

class SvgDecoderTestJvm : AbstractSvgDecoderTest(SvgDecoder.Factory())
Binary file added coil-svg/src/jvmTest/resources/coil_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions coil-svg/src/jvmTest/resources/coil_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
489 changes: 489 additions & 0 deletions coil-svg/src/jvmTest/resources/document.xml

Large diffs are not rendered by default.

Binary file added coil-svg/src/jvmTest/resources/instacart_logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions coil-svg/src/jvmTest/resources/instacart_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading