Skip to content

Commit

Permalink
Support automatic SVG detection using the source contents. (#654)
Browse files Browse the repository at this point in the history
  • Loading branch information
colinrtwhite authored Feb 4, 2021
1 parent 3274083 commit 446b3a3
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 2 deletions.
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
}

0 comments on commit 446b3a3

Please sign in to comment.