jvmMain.org.openrndr.svg.SVGWriter.kt Maven / Gradle / Ivy
package org.openrndr.svg
import org.jsoup.nodes.*
import org.openrndr.math.*
import org.openrndr.shape.*
import org.openrndr.shape.TextNode
import java.io.*
fun Composition.saveToFile(file: File) {
if (file.extension == "svg") {
val svg = writeSVG(this)
file.writeText(svg)
} else {
throw IllegalArgumentException("can only write svg files, the extension '${file.extension}' is not supported")
}
}
fun Composition.toSVG() = writeSVG(this)
private val CompositionNode.svgId: String
get() = when (val tempId = id) {
"" -> ""
null -> ""
else -> "id=\"$tempId\""
}
private val CompositionNode.svgAttributes: String
get() {
return attributes.map {
if (it.value != null && it.value != "") {
"${it.key}=\"${Entities.escape(it.value)}\""
} else {
it.key
}
}.joinToString(" ")
}
private fun Styleable.serialize(parentStyleable: Styleable? = null): String {
val sb = StringBuilder()
val filtered = this.properties.filter {
it.key != AttributeOrPropertyKey.SHADESTYLE
}
// Inheritance can't be checked without a parentStyleable
when (parentStyleable) {
null -> filtered.forEach { (t, u) ->
if (u.toString().isNotEmpty()) {
sb.append("$t=\"${u.toString()}\" ")
}
}
else -> filtered.forEach { (t, u) ->
if (u.toString().isNotEmpty() && !this.isInherited(parentStyleable, t)) {
sb.append("$t=\"${u.toString()}\" ")
}
}
}
return sb.trim().toString()
}
fun writeSVG(
composition: Composition,
topLevelId: String = "openrndr-svg"
): String {
val sb = StringBuilder()
sb.append("\n")
val defaultNamespaces = mapOf(
"xmlns" to "http://www.w3.org/2000/svg",
"xmlns:xlink" to "http://www.w3.org/1999/xlink"
)
val namespaces = (defaultNamespaces + composition.namespaces).map { (k, v) ->
"$k=\"$v\""
}.joinToString(" ")
val styleSer = composition.style.serialize()
val docStyleSer = composition.documentStyle.serialize()
sb.append("")
return sb.toString()
}
private val Shape.svg: String
get() {
val sb = StringBuilder()
contours.forEach {
it.segments.forEachIndexed { index, segment ->
if (index == 0) {
sb.append("M ${segment.start.x} ${segment.start.y}")
}
sb.append(
when (segment.control.size) {
1 -> "Q${segment.control[0].x} ${segment.control[0].y} ${segment.end.x} ${segment.end.y}"
2 -> "C${segment.control[0].x} ${segment.control[0].y} ${segment.control[1].x} ${segment.control[1].y} ${segment.end.x} ${segment.end.y}"
else -> "L${segment.end.x} ${segment.end.y}"
}
)
}
if (it.closed) {
sb.append("z")
}
}
return sb.toString()
}
private val ShapeContour.svg: String
get() {
val sb = StringBuilder()
segments.forEachIndexed { index, segment ->
if (index == 0) {
sb.append("M ${segment.start.x} ${segment.start.y}")
}
sb.append(
when (segment.control.size) {
1 -> "C${segment.control[0].x}, ${segment.control[0].y} ${segment.end.x} ${segment.end.y}"
2 -> "C${segment.control[0].x}, ${segment.control[0].y} ${segment.control[1].x} ${segment.control[1].y} ${segment.end.x} ${segment.end.y}"
else -> "L${segment.end.x} ${segment.end.y}"
}
)
}
if (closed) {
sb.append("z")
}
return sb.toString()
}
private enum class VisitStage {
PRE,
POST
}
private fun process(compositionNode: CompositionNode, visitor: CompositionNode.(stage: VisitStage) -> Unit) {
compositionNode.visitor(VisitStage.PRE)
if (compositionNode is GroupNode) {
compositionNode.children.forEach { process(it, visitor) }
}
compositionNode.visitor(VisitStage.POST)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy