mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
IJPL-155932 SVG Image support via ImageReaderSpi (detect SVG)
closes #2850 GitOrigin-RevId: 1674cb6711fa8e2744c09dcdd58963051d331f3c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
783bbde096
commit
f9f1a0aa07
@@ -42,5 +42,6 @@
|
|||||||
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
|
<orderEntry type="library" name="kotlinx-serialization-json" level="project" />
|
||||||
<orderEntry type="module" module-name="intellij.xml.frontback" />
|
<orderEntry type="module" module-name="intellij.xml.frontback" />
|
||||||
<orderEntry type="module" module-name="intellij.platform.ui.jcef" />
|
<orderEntry type="module" module-name="intellij.platform.ui.jcef" />
|
||||||
|
<orderEntry type="library" name="jsvg" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -1 +1,2 @@
|
|||||||
org.intellij.images.util.imageio.CommonsImagingImageReaderSpi
|
org.intellij.images.util.imageio.CommonsImagingImageReaderSpi
|
||||||
|
org.intellij.images.util.imageio.svg.SvgImageReaderSpi
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package org.intellij.images.util.imageio.svg
|
||||||
|
|
||||||
|
import com.github.weisj.jsvg.attributes.font.SVGFont
|
||||||
|
import com.github.weisj.jsvg.geometry.size.Length
|
||||||
|
import com.github.weisj.jsvg.nodes.SVG
|
||||||
|
import com.intellij.ui.paint.PaintUtil
|
||||||
|
import com.intellij.ui.scale.ScaleContext
|
||||||
|
import com.intellij.ui.svg.createJSvgDocument
|
||||||
|
import com.intellij.util.ui.ImageUtil
|
||||||
|
import java.awt.image.BufferedImage
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import javax.imageio.ImageReadParam
|
||||||
|
import javax.imageio.ImageReader
|
||||||
|
import javax.imageio.ImageTypeSpecifier
|
||||||
|
import javax.imageio.stream.ImageInputStream
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class SvgImageReader(svgImageReaderSpi: SvgImageReaderSpi) : ImageReader(svgImageReaderSpi) {
|
||||||
|
private var height: Length? = null
|
||||||
|
private var width: Length? = null
|
||||||
|
private var jSvgDocument: SVG? = null
|
||||||
|
override fun setInput(input: Any?, seekForwardOnly: Boolean, ignoreMetadata: Boolean) {
|
||||||
|
super.setInput(input, seekForwardOnly, ignoreMetadata)
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWidth(imageIndex: Int): Int {
|
||||||
|
loadInfoIfNeeded()
|
||||||
|
return width?.raw()?.toInt() ?: throw IOException("SVG document not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getHeight(imageIndex: Int): Int {
|
||||||
|
loadInfoIfNeeded()
|
||||||
|
return height?.raw()?.toInt() ?: throw IOException("SVG document not loaded")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun loadInfoIfNeeded() {
|
||||||
|
val input = input
|
||||||
|
if (jSvgDocument == null && input is ImageInputStream) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Not using ByteArray to avoid potential humongous allocation
|
||||||
|
ImageInputStreamAdapter(input).buffered().use {
|
||||||
|
jSvgDocument = createJSvgDocument(it).also { svg ->
|
||||||
|
width = svg.width
|
||||||
|
height = svg.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
// could not read the SVG document
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun read(imageIndex: Int, param: ImageReadParam?): BufferedImage {
|
||||||
|
loadInfoIfNeeded()
|
||||||
|
jSvgDocument ?: throw IOException("SVG document not loaded")
|
||||||
|
|
||||||
|
val sourceRenderSize = param?.sourceRenderSize
|
||||||
|
|
||||||
|
val width = sourceRenderSize?.width?.toDouble() ?: width!!.raw().toDouble()
|
||||||
|
val height = sourceRenderSize?.height?.toDouble() ?: height!!.raw().toDouble()
|
||||||
|
|
||||||
|
// how to have an hidpi aware image?
|
||||||
|
val bi = ImageUtil.createImage(
|
||||||
|
ScaleContext.create(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
BufferedImage.TYPE_INT_ARGB,
|
||||||
|
PaintUtil.RoundingMode.ROUND
|
||||||
|
)
|
||||||
|
val g = bi.createGraphics()
|
||||||
|
|
||||||
|
ImageUtil.applyQualityRenderingHints(g)
|
||||||
|
|
||||||
|
jSvgDocument?.renderWithSize(
|
||||||
|
width.toFloat(),
|
||||||
|
height.toFloat(),
|
||||||
|
SVGFont.defaultFontSize(),
|
||||||
|
g,
|
||||||
|
)
|
||||||
|
return bi
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
jSvgDocument = null
|
||||||
|
height = null
|
||||||
|
width = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getNumImages(allowSearch: Boolean): Int {
|
||||||
|
return if (jSvgDocument != null) 1 else 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getImageTypes(imageIndex: Int): Iterator<ImageTypeSpecifier> {
|
||||||
|
return listOf<ImageTypeSpecifier>(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)).iterator()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStreamMetadata() = null
|
||||||
|
|
||||||
|
override fun getImageMetadata(imageIndex: Int) = null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImageInputStreamAdapter(private val imageInputStream: ImageInputStream) : InputStream() {
|
||||||
|
private var closed = false
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun close() {
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun read(): Int {
|
||||||
|
if(closed) throw IOException("stream closed")
|
||||||
|
return imageInputStream.read()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||||
|
if(closed) throw IOException("stream closed")
|
||||||
|
if (len <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return imageInputStream.read(b, off, max(len.toLong(), 0).toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun skip(n: Long): Long {
|
||||||
|
if(closed) throw IOException("stream closed")
|
||||||
|
if (n <= 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return imageInputStream.skipBytes(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package org.intellij.images.util.imageio.svg
|
||||||
|
|
||||||
|
import java.io.EOFException
|
||||||
|
import java.util.*
|
||||||
|
import javax.imageio.ImageReader
|
||||||
|
import javax.imageio.spi.ImageReaderSpi
|
||||||
|
import javax.imageio.stream.ImageInputStream
|
||||||
|
import kotlin.experimental.and
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageReaderSpi for SVG images.
|
||||||
|
*
|
||||||
|
* @author Brice Dutheil
|
||||||
|
*/
|
||||||
|
class SvgImageReaderSpi : ImageReaderSpi() {
|
||||||
|
init {
|
||||||
|
vendorName = "weisj/jsvg & JetBrains"
|
||||||
|
suffixes = arrayOf("svg")
|
||||||
|
MIMETypes = arrayOf("image/svg+xml")
|
||||||
|
names = arrayOf("SVG Image Reader")
|
||||||
|
pluginClassName = SvgImageReaderSpi::class.java.name
|
||||||
|
inputTypes = arrayOf<Class<*>>(ImageInputStream::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescription(locale: Locale?) = "SVG Image Reader"
|
||||||
|
|
||||||
|
override fun canDecodeInput(source: Any?): Boolean {
|
||||||
|
if (source !is ImageInputStream) return false
|
||||||
|
return canDecode(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canDecode(imageInputStream: ImageInputStream): Boolean {
|
||||||
|
// NOTE: This test is quite quick as it does not involve any parsing,
|
||||||
|
// however, it may not recognize all kinds of SVG documents.
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We need to read the first few bytes to determine if this is an SVG file,
|
||||||
|
// then reset the stream at the marked position
|
||||||
|
imageInputStream.mark()
|
||||||
|
|
||||||
|
// SVG file can starts with an XML declaration, then possibly followed by comments, whitespaces
|
||||||
|
// \w.*(<?xml version="1.0" encoding="UTF-8" ?>)?
|
||||||
|
// (\w|(<!--.*-->))*
|
||||||
|
|
||||||
|
// Then either the doctype gives the hint possibly surrounded by comments and/or whitespaces
|
||||||
|
// <!DOCTYPE svg ...
|
||||||
|
// (\w|(<!--.*-->))*
|
||||||
|
|
||||||
|
// Or the root tag is svg, possibly preceding comments and/or whitespaces
|
||||||
|
// <svg ...
|
||||||
|
|
||||||
|
// Handles first whitespaces if any
|
||||||
|
val lastReadByte = imageInputStream.readFirstAfterWhitespaces()
|
||||||
|
|
||||||
|
// The next byte should be '<' (comments, doctype XML declaration or the root tag)
|
||||||
|
if (lastReadByte != '<'.code) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val window = ByteArray(4)
|
||||||
|
while (true) {
|
||||||
|
imageInputStream.readFully(window)
|
||||||
|
|
||||||
|
when {
|
||||||
|
// `<?` Handles the XML declaration
|
||||||
|
window.startsWith('?') -> imageInputStream.skipUntil('?', '>')
|
||||||
|
//buffer[0] == '?'.code.toByte() -> imageInputStream.skipUntil('?', '>')
|
||||||
|
|
||||||
|
// `<!--` Handles a comment
|
||||||
|
window.startsWith('!', '-', '-') -> imageInputStream.skipUntil('-', '-', '>')
|
||||||
|
|
||||||
|
// `<!DOCTYPE` Handles the DOCTYPE declaration
|
||||||
|
window.startsWith('!', 'D', 'O', 'C') && imageInputStream.readNextEquals(charArrayOf('T', 'Y', 'P', 'E')) -> {
|
||||||
|
val lastReadChar = imageInputStream.readFirstAfterWhitespaces()
|
||||||
|
return lastReadChar == 's'.code && imageInputStream.readNextEquals(charArrayOf('v', 'g'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// `<svg` Handles the root tag
|
||||||
|
window.startsWith('s', 'v', 'g') && (Char(window[3].toUShort()).isWhitespace() || window[3] == ':'.code.toByte()) -> {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// not an SVG file or not handled
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
|
||||||
|
//while ((imageInputStream.readByte() and 0xFF.toByte()).toInt().toChar() != '<') {
|
||||||
|
// // Skip over, until next begin tag or EOF
|
||||||
|
//}
|
||||||
|
// Skip over, until next begin tag or EOF
|
||||||
|
imageInputStream.skipUntil('<')
|
||||||
|
}
|
||||||
|
} catch (ignore: EOFException) {
|
||||||
|
// Possible for small files...
|
||||||
|
ignore.printStackTrace()
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
imageInputStream.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImageInputStream.readFirstAfterWhitespaces(): Int {
|
||||||
|
var lastReadByte: Int
|
||||||
|
while ((read().also { lastReadByte = it }).toChar().isWhitespace()) {
|
||||||
|
// skip whitespaces if any
|
||||||
|
}
|
||||||
|
return lastReadByte
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImageInputStream.skipUntil(vararg expected: Char) {
|
||||||
|
while (!readNextEquals(expected)) {
|
||||||
|
// skip until expected chars or EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ImageInputStream.readNextEquals(expected: CharArray): Boolean {
|
||||||
|
// first char is read with readByte()
|
||||||
|
require(expected.isNotEmpty())
|
||||||
|
val first = (readByte() and 0xFF.toByte()).toInt().toChar()
|
||||||
|
if (first != expected[0]) return false
|
||||||
|
|
||||||
|
// next can be read with read()
|
||||||
|
expected.forEachIndexed { index, it ->
|
||||||
|
if (index == 0) return@forEachIndexed
|
||||||
|
val read = read()
|
||||||
|
if (read != it.code) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.startsWith(vararg expected: Char): Boolean {
|
||||||
|
expected.forEachIndexed { index, c ->
|
||||||
|
if (this[index] != c.code.toByte()) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createReaderInstance(extension: Any?): ImageReader {
|
||||||
|
return SvgImageReader(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package org.intellij.images.util.imageio.svg
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Assertions.*
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import javax.imageio.stream.MemoryCacheImageInputStream
|
||||||
|
|
||||||
|
class SvgImageReaderSpiTest {
|
||||||
|
@Test
|
||||||
|
fun `can read svg with xml header`() {
|
||||||
|
val svg = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cannot read svg with broken xml header`() {
|
||||||
|
val svg = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8" // broken
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertFalse(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cannot read svg with broken truncated file`() {
|
||||||
|
val svg = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8" // broken
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertFalse(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg with whitespace before xml header`() {
|
||||||
|
val svg = """
|
||||||
|
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun `can read svg with comment after xml header`() {
|
||||||
|
val svg = """
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!-- comment -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg with DOCTYPE after xml header`() {
|
||||||
|
val svg = """
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<!-- comment -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg with comment before DOCTYPE`() {
|
||||||
|
val svg = """
|
||||||
|
<!-- comment -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg starting with DOCTYPE`() {
|
||||||
|
val svg = """
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg starting with DOCTYPE having spaces`() {
|
||||||
|
val svg = """
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<!-- comment -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg starting with SVG root tag`() {
|
||||||
|
val svg = """
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `can read svg starting with comment then SVG root tag`() {
|
||||||
|
val svg = """
|
||||||
|
<!-- comment -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertTrue(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `cannot read svg with broken comment`() {
|
||||||
|
val svg = """
|
||||||
|
<!-- comment -- // broken
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertFalse(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun `cannot read svg with broken root tag`() {
|
||||||
|
val svg = """
|
||||||
|
<!-- comment -->
|
||||||
|
<sv xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"> </svg>
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
MemoryCacheImageInputStream(svg.byteInputStream()).use { stream ->
|
||||||
|
assertFalse(SvgImageReaderSpi().canDecodeInput(stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user