Refactor code and add interruptibility
authorLanius Trolling <lanius@laniustrolling.dev>
Sun, 4 Feb 2024 15:05:12 +0000 (10:05 -0500)
committerLanius Trolling <lanius@laniustrolling.dev>
Sun, 4 Feb 2024 15:05:12 +0000 (10:05 -0500)
src/jvmMain/kotlin/info/mechyrdia/BlockingCode.kt [new file with mode: 0644]
src/jvmMain/kotlin/info/mechyrdia/lore/fonts.kt

diff --git a/src/jvmMain/kotlin/info/mechyrdia/BlockingCode.kt b/src/jvmMain/kotlin/info/mechyrdia/BlockingCode.kt
new file mode 100644 (file)
index 0000000..b412225
--- /dev/null
@@ -0,0 +1,7 @@
+package info.mechyrdia
+
+fun yieldThread() {
+       if (Thread.interrupted()) {
+               throw InterruptedException()
+       }
+}
index 6ba65819b76349277b8821455257c15938050c6a..d858f52d23ff780d53999424649ab36417c14ac5 100644 (file)
@@ -1,5 +1,6 @@
 package info.mechyrdia.lore
 
+import info.mechyrdia.yieldThread
 import java.awt.Font
 import java.awt.RenderingHints
 import java.awt.Shape
@@ -13,20 +14,8 @@ object MechyrdiaSansFont {
                LEFT(0.0), CENTER(0.5), RIGHT(1.0),
        }
        
-       fun renderTextToSvg(text: String, bold: Boolean, italic: Boolean, align: Alignment, standalone: Boolean = true): String {
-               val font = if (bold) {
-                       if (italic)
-                               mechyrdiaSansBI
-                       else
-                               mechyrdiaSansB
-               } else
-                       if (italic)
-                               mechyrdiaSansI
-                       else
-                               mechyrdiaSans
-               
-               val shape = layoutText(text, font, align.amount)
-               return shape.toSvgDocument(standalone)
+       fun renderTextToSvg(text: String, bold: Boolean, italic: Boolean, align: Alignment): String {
+               return layoutText(text, getFont(bold, italic), align.amount).toSvgDocument()
        }
        
        private const val DEFAULT_FONT_SIZE = 48f
@@ -42,6 +31,11 @@ object MechyrdiaSansFont {
        private val mechyrdiaSansI = loadFont("mechyrdia-sans-italic")
        private val mechyrdiaSansBI = loadFont("mechyrdia-sans-bold-italic")
        
+       private val mechyrdiaSansFonts = listOf(mechyrdiaSans, mechyrdiaSansI, mechyrdiaSansB, mechyrdiaSansBI)
+       private fun getFont(bold: Boolean, italic: Boolean): Font {
+               return mechyrdiaSansFonts[(if (bold) 2 else 0) + (if (italic) 1 else 0)]
+       }
+       
        private fun layoutText(text: String, font: Font, alignAmount: Double): Shape {
                val img = BufferedImage(256, 160, BufferedImage.TYPE_INT_ARGB)
                val g2d = img.createGraphics()
@@ -54,12 +48,15 @@ object MechyrdiaSansFont {
                        val width = lines.maxOf { fontMetrics.stringWidth(it) }.toDouble()
                        var y = 0.0
                        
+                       yieldThread()
+                       
                        val shape = GeneralPath()
                        val tf = AffineTransform()
                        for (line in lines) {
                                if (line.isNotBlank()) {
                                        val x = (width - fontMetrics.stringWidth(line)) * alignAmount
                                        
+                                       // Mechyrdia Sans only supports the Latin alphabet, so we can ignore bidirectional text
                                        val glyphs = font.layoutGlyphVector(g2d.fontRenderContext, line.toCharArray(), 0, line.length, Font.LAYOUT_LEFT_TO_RIGHT)
                                        val textShape = glyphs.outline as GeneralPath
                                        
@@ -69,6 +66,8 @@ object MechyrdiaSansFont {
                                }
                                
                                y += fontMetrics.height
+                               
+                               yieldThread()
                        }
                        
                        return shape
@@ -77,10 +76,9 @@ object MechyrdiaSansFont {
                }
        }
        
-       private fun Shape.toSvgDocument(standalone: Boolean = true): String {
+       private fun Shape.toSvgDocument(): String {
                return buildString {
-                       if (standalone)
-                               appendLine("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
+                       appendLine("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>")
                        
                        val viewBox = bounds2D
                        appendLine("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"${viewBox.width * 2}\" height=\"${viewBox.height * 2}\" viewBox=\"${viewBox.minX} ${viewBox.minY} ${viewBox.width} ${viewBox.height}\">")
@@ -95,32 +93,40 @@ object MechyrdiaSansFont {
                        
                        val iterator = getPathIterator(null)
                        val coords = DoubleArray(6)
+                       var isInitial = true
                        while (!iterator.isDone) {
+                               if (isInitial)
+                                       isInitial = false
+                               else
+                                       append(' ')
+                               
                                when (val segment = iterator.currentSegment(coords)) {
                                        PathIterator.SEG_MOVETO -> {
-                                               append("M ${coords[0]},${coords[1]} ")
+                                               append("M ${coords[0]},${coords[1]}")
                                        }
                                        
                                        PathIterator.SEG_LINETO -> {
-                                               append("L ${coords[0]},${coords[1]} ")
+                                               append("L ${coords[0]},${coords[1]}")
                                        }
                                        
                                        PathIterator.SEG_QUADTO -> {
-                                               append("Q ${coords[0]},${coords[1]} ${coords[2]},${coords[3]} ")
+                                               append("Q ${coords[0]},${coords[1]} ${coords[2]},${coords[3]}")
                                        }
                                        
                                        PathIterator.SEG_CUBICTO -> {
-                                               append("C ${coords[0]},${coords[1]} ${coords[2]},${coords[3]} ${coords[4]},${coords[5]} ")
+                                               append("C ${coords[0]},${coords[1]} ${coords[2]},${coords[3]} ${coords[4]},${coords[5]}")
                                        }
                                        
                                        PathIterator.SEG_CLOSE -> {
-                                               append("Z ")
+                                               append("Z")
                                        }
                                        
                                        else -> error("Invalid segment type $segment")
                                }
                                
                                iterator.next()
+                               
+                               yieldThread()
                        }
                        
                        append("\" fill-rule=\"")