Skip to content

Instantly share code, notes, and snippets.

@bric3
Last active October 21, 2024 13:07
Show Gist options
  • Save bric3/f14f40025de2351079a96dda98d413fa to your computer and use it in GitHub Desktop.
Save bric3/f14f40025de2351079a96dda98d413fa to your computer and use it in GitHub Desktop.
SVG Patcher for gradle using Stax Approach (does not maintain order)
import java.io.StringWriter
import javax.xml.namespace.QName
import javax.xml.stream.XMLEventFactory
import javax.xml.stream.XMLInputFactory
import javax.xml.stream.XMLOutputFactory
import javax.xml.stream.events.Attribute
import javax.xml.stream.events.StartElement
tasks.register<GenerateDarkIconVariant>("patchSVG")
abstract class GenerateDarkIconVariant @Inject constructor(project: Project) : DefaultTask() {
@InputFiles
val svgFiles = project.fileTree(project.file("src/main/resources/icons")) {
exclude("**/rectangle.svg", "**/base.svg")
include("**/*.svg")
}
@OutputFiles
val patchedFiles = svgFiles + svgFiles.files.map {
it.parentFile.resolve(it.name.replace(".svg", "_dark.svg"))
}.filter {
it.exists()
}
// TODO ensure width rules per folder / file
// - actions: 16
// - filetypes: 16
// - toolwindows: 13, newUI 20
// - editorgutter: 12
// - bookmarks: 12
@TaskAction
fun run() {
svgFiles.filter {
val darkVariant = it.parentFile.resolve(it.name.replace(".svg", "_dark.svg"))
!it.name.endsWith("_dark.svg") && !svgFiles.contains(darkVariant)
}.forEach {
logger.lifecycle(it.path)
val colorsLight = "#6E6E6E" to "black"
val colorsDark = "#AFB1B3" to "white"
patchFile(it, it.parentFile.resolve(it), colorsLight.first, colorsLight.second)
patchFile(it, it.parentFile.resolve("${it.nameWithoutExtension}_dark.svg"), colorsDark.first, colorsDark.second)
}
}
private fun patchFile(svgIn: File, svgOut: File, screenColor: String, windowColor: String) {
// ⚠️ StAX doesn't care about attribute order
val outputBuffer = StringWriter() // don't write to file until we're done
val xmlEventWriter = XMLOutputFactory.newInstance().createXMLEventWriter(outputBuffer)
val xmlEventFactory = XMLEventFactory.newInstance()
fun StartElement.copyWithFillAttribute(fillColor: String): StartElement {
return xmlEventFactory.createStartElement(
name,
(attributes as Iterator<Attribute>).asSequence().map { attr ->
if (attr.name.localPart == "fill") {
xmlEventFactory.createAttribute(attr.name, fillColor)
} else {
attr
}
}.iterator(),
namespaces
)
}
svgIn.bufferedReader().use {
val xmlInputFactory = XMLInputFactory.newInstance()
val reader = xmlInputFactory.createXMLEventReader(it)
while (reader.hasNext()) {
val event = when (val event = reader.nextEvent()) {
is StartElement -> {
val elementName = event.name.localPart
if (elementName == "rect"
&& event.getAttributeByName(QName("id"))?.value == "screen"
) {
println("got screen")
event.copyWithFillAttribute(screenColor)
} else {
println("got others")
event.copyWithFillAttribute(windowColor)
}
event
}
else -> event.also {
println(it::class.java.name)
if (it.isCharacters) {
println(it.asCharacters().data)
}
}
}
xmlEventWriter.add(event)
}
}
xmlEventWriter.close()
println("----------------- $svgOut")
println(outputBuffer)
println("-----------------")
// ⚠️ Doc doesn't care about attribute order
// val docFactory = DocumentBuilderFactory.newInstance()
// val doc = svgIn.bufferedReader().use {
// docFactory.newDocumentBuilder().parse(InputSource(it))
// }
// val elementById = doc.getElementById("rect")
// elementById?.setAttribute("fill", screenColor)
//
// val iterator = doc.documentElement.childNodes.iterator()
// iterator.forEachRemaining {
// if (it is Element) {
// val nodeName = it.nodeName
// when {
// nodeName == "rect" && it.getAttribute("id") == "screen" -> {
// logger.debug("found screen")
// it.setAttribute("fill", screenColor)
// }
// nodeName != "#text" && nodeName != "null" -> {
// logger.debug("found window")
// it.setAttribute("fill", windowColor)
// }
// }
// }
// }
//
//
//
// val transformerFactory = TransformerFactory.newInstance()
// val transformer = transformerFactory.newTransformer()
//
// svgOut.bufferedWriter().use {
// transformer.transform(DOMSource(doc), StreamResult(it))
// }
// val patched = svgIn.bufferedReader().useLines {
// it.joinToString {
//
// var line = it
// // Dirty try trick to parse XML
// // XML parser work but reorders attributes, which is expected since attribute order doesn't matter
// // But for humans it's easier if attributes are written in a sane order eg 'width=... height=...'
// if (line.trim().startsWith("<path")) {
// line = if (line.contains("fill")) {
// line.replace("fill=\".+?\"".toRegex(), "fill=\"$fillColor\"")
// } else {
// line.replaceFirst("<path", "<path fill=\"$fillColor\"")
// }
// }
// line
// }
// }
// svgOut.writeText(patched)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment