From 969fb17f34a28374201b755d9f4cd3023825c40c Mon Sep 17 00:00:00 2001 From: Lanius Trolling Date: Sun, 14 Apr 2024 08:39:19 -0400 Subject: [PATCH] Improve SVG-handling code --- .../mechyrdia/lore/{fonts.kt => Fonts.kt} | 93 +++++++++++++------ 1 file changed, 63 insertions(+), 30 deletions(-) rename src/jvmMain/kotlin/info/mechyrdia/lore/{fonts.kt => Fonts.kt} (85%) diff --git a/src/jvmMain/kotlin/info/mechyrdia/lore/fonts.kt b/src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt similarity index 85% rename from src/jvmMain/kotlin/info/mechyrdia/lore/fonts.kt rename to src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt index ac5411e..9c36df5 100644 --- a/src/jvmMain/kotlin/info/mechyrdia/lore/fonts.kt +++ b/src/jvmMain/kotlin/info/mechyrdia/lore/Fonts.kt @@ -3,8 +3,7 @@ package info.mechyrdia.lore import com.jaredrummler.fontreader.truetype.FontFileReader import com.jaredrummler.fontreader.truetype.TTFFile import com.jaredrummler.fontreader.util.GlyphSequence -import info.mechyrdia.data.FileStorage -import info.mechyrdia.data.StoragePath +import info.mechyrdia.data.* import info.mechyrdia.route.KeyedEnumSerializer import info.mechyrdia.yieldThread import kotlinx.coroutines.Dispatchers @@ -46,12 +45,44 @@ enum class TextAlignment { object TextAlignmentSerializer : KeyedEnumSerializer(TextAlignment.entries) +data class SvgDoc( + val width: Double, + val height: Double, + val viewBoxX: Double, + val viewBoxY: Double, + val viewBoxW: Double, + val viewBoxH: Double, + val path: SvgPath +) + +data class SvgPath( + val d: String, + val fillRule: String +) + +fun > C.svg(svgDoc: SvgDoc) = declaration(standalone = false) + .defaultXmlns("http://www.w3.org/2000/svg") + .root( + "svg", + attributes = mapOf( + "width" to svgDoc.width.xmlValue, + "height" to svgDoc.height.xmlValue, + "viewBox" to listOf( + svgDoc.viewBoxX, + svgDoc.viewBoxY, + svgDoc.viewBoxW, + svgDoc.viewBoxH, + ).joinToString(separator = " ") { it.xmlValue } + ) + ) { "path"(attributes = mapOf("d" to svgDoc.path.d, "fill-rule" to svgDoc.path.fillRule)) } + object MechyrdiaSansFont { private val logger: Logger = LoggerFactory.getLogger(MechyrdiaSansFont::class.java) - suspend fun renderTextToSvg(text: String, bold: Boolean, italic: Boolean, align: TextAlignment): String { + suspend fun renderTextToSvg(text: String, bold: Boolean, italic: Boolean, align: TextAlignment): SvgDoc { val (file, font) = getFont(bold, italic) - return layoutText(text, file, font, align).toSvgDocument(80.0 / file.unitsPerEm, 12.0) + val shape = layoutText(text, file, font, align) + return createSvgDocument(shape, 80.0 / file.unitsPerEm, 12.0) } private val fontsRoot = StoragePath("fonts") @@ -209,27 +240,30 @@ object MechyrdiaSansFont { } } - private fun Shape.toSvgDocument(scale: Double, padding: Double = 0.0): String { - return buildString { - appendLine("") - - val viewBox = bounds2D - val vBoxPad = padding / scale - val sizePad = padding * 2 - - appendLine("") - appendLine(toSvgPath()) - appendLine("") - } + private fun createSvgDocument(shape: Shape, scale: Double, padding: Double = 0.0): SvgDoc { + val viewBox = shape.bounds2D + val vBoxPad = padding / scale + val sizePad = padding * 2 + + val path = shape.calculateSvgPath() + + return SvgDoc( + width = (viewBox.width * scale) + sizePad, + height = (viewBox.height * scale) + sizePad, + viewBoxX = viewBox.minX - vBoxPad, + viewBoxY = viewBox.minY - vBoxPad, + viewBoxW = viewBox.width + (vBoxPad * 2), + viewBoxH = viewBox.height + (vBoxPad * 2), + path = path + ) } - private fun Shape.toSvgPath(): String { - return buildString { - append(" append("evenodd") - PathIterator.WIND_NON_ZERO -> append("nonzero") - else -> error("Invalid winding rule $winding") - } - - append("\" />") } + + val fillRule = when (val winding = iterator.windingRule) { + PathIterator.WIND_EVEN_ODD -> "evenodd" + PathIterator.WIND_NON_ZERO -> "nonzero" + else -> error("Invalid winding rule $winding") + } + + return SvgPath(d, fillRule) } } -- 2.25.1