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
2 changes: 1 addition & 1 deletion coil-svg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ val imageLoader = ImageLoader.Builder(context)
.build()
```

The `ImageLoader` will automatically detect and decode any SVGs if the request's MIME type is `image/svg+xml`. The MIME type is inferred using the HTTP `content-type` header, a URI's suffix, or a file's extension. If you need to force a specific request to use the `SvgDecoder`, you can [set the `Fetcher` explicitly](../api/coil-base/coil.request/-image-request/-builder/fetcher/) for the request.
The `ImageLoader` will automatically detect and decode any SVGs. Coil detects SVGs by looking for the `<svg ` marker in the first 1 KB of the file, which should cover most cases. If the SVG is not automatically detected, you can [set the `Decoder` explicitly](../api/coil-base/coil.request/-image-request/-builder/decoder/) to `SvgDecoder` for the request.
25 changes: 25 additions & 0 deletions coil-svg/src/androidTest/java/coil/decode/SvgDecoderTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import okio.buffer
import okio.source
import org.junit.Before
import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class SvgDecoderTest {
Expand All @@ -28,6 +29,30 @@ class SvgDecoderTest {
decoder = SvgDecoder(context)
}

@Test
fun handlesMimeType() {
var source = context.assets.open("coil_logo.svg").source().buffer()
assertTrue(decoder.handles(source, "image/svg+xml"))

source = context.assets.open("coil_logo_250.png").source().buffer()
assertFalse(decoder.handles(source, "image/png"))
}

@Test
fun handlesSource() {
var source = context.assets.open("coil_logo.svg").source().buffer()
assertTrue(decoder.handles(source, null))

source = context.assets.open("coil_logo_250.png").source().buffer()
assertFalse(decoder.handles(source, null))

source = context.assets.open("instacart_logo.svg").source().buffer()
assertTrue(decoder.handles(source, null))

source = context.assets.open("instacart_logo_326.png").source().buffer()
assertFalse(decoder.handles(source, null))
}

@Test
fun basic() {
val source = context.assets.open("coil_logo.svg").source().buffer()
Expand Down
14 changes: 13 additions & 1 deletion coil-svg/src/main/java/coil/decode/SvgDecoder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,25 @@ import coil.bitmap.BitmapPool
import coil.size.OriginalSize
import coil.size.PixelSize
import coil.size.Size
import coil.util.indexOf
import com.caverock.androidsvg.SVG
import okio.BufferedSource
import okio.ByteString.Companion.encodeUtf8
import okio.buffer

/**
* A [Decoder] that uses [AndroidSVG](https://bigbadaboom.github.io/androidsvg/) to decode SVG files.
*/
class SvgDecoder(private val context: Context) : Decoder {

override fun handles(source: BufferedSource, mimeType: String?) = mimeType == MIME_TYPE_SVG
override fun handles(source: BufferedSource, mimeType: String?): Boolean {
return mimeType == MIME_TYPE_SVG || containsSvgTag(source)
}

private fun containsSvgTag(source: BufferedSource): Boolean {
return source.rangeEquals(0, LEFT_ANGLE_BRACKET) &&
source.indexOf(SVG_TAG, 0, SVG_TAG_SEARCH_THRESHOLD_BYTES) != -1L
}

override suspend fun decode(
pool: BitmapPool,
Expand Down Expand Up @@ -87,5 +96,8 @@ class SvgDecoder(private val context: Context) : Decoder {
private companion object {
private const val MIME_TYPE_SVG = "image/svg+xml"
private const val DEFAULT_SIZE = 512
private const val SVG_TAG_SEARCH_THRESHOLD_BYTES = 1024L
private val SVG_TAG = "<svg ".encodeUtf8()
private val LEFT_ANGLE_BRACKET = "<".encodeUtf8()
}
}
21 changes: 21 additions & 0 deletions coil-svg/src/main/java/coil/util/Extensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@file:JvmName("-SvgExtensions")

package coil.util

import okio.BufferedSource
import okio.ByteString

internal fun BufferedSource.indexOf(bytes: ByteString, fromIndex: Long, toIndex: Long): Long {
require(bytes.size > 0) { "bytes is empty" }

val firstByte = bytes[0]
var currentIndex = fromIndex
while (currentIndex < toIndex) {
currentIndex = indexOf(firstByte, currentIndex)
if (currentIndex == -1L || rangeEquals(currentIndex, bytes)) {
return currentIndex
}
currentIndex++
}
return -1
}