Last active
October 21, 2024 13:07
-
-
Save bric3/f14f40025de2351079a96dda98d413fa to your computer and use it in GitHub Desktop.
SVG Patcher for gradle using Stax Approach (does not maintain order)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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