package info.mechyrdia.lore
+import info.mechyrdia.yieldThread
import java.awt.Font
import java.awt.RenderingHints
import java.awt.Shape
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
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()
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
}
y += fontMetrics.height
+
+ yieldThread()
}
return shape
}
}
- 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}\">")
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=\"")