jvmMain.ru.casperix.multiplatform.text.JvmTextGraphicProcessor.kt Maven / Gradle / Ivy
The newest version!
package ru.casperix.multiplatform.text
import ru.casperix.math.axis_aligned.dimensionOf
import ru.casperix.math.axis_aligned.float32.Box2f
import ru.casperix.math.color.Color
import ru.casperix.math.vector.float32.Vector2f
import ru.casperix.math.vector.toQuad
import ru.casperix.misc.ceilToInt
import ru.casperix.multiplatform.font.FontReference
import ru.casperix.multiplatform.font.FontWeight
import ru.casperix.multiplatform.loader.JvmImageConverter
import ru.casperix.multiplatform.text.TextRenderConfig.useAntiAlias
import ru.casperix.multiplatform.text.TextRenderConfig.useBicubicInterpolation
import ru.casperix.multiplatform.text.TextRenderConfig.useSubpixelRender
import ru.casperix.multiplatform.text.impl.TextScheme
import ru.casperix.renderer.material.SimpleMaterial
import ru.casperix.renderer.material.Texture2D
import ru.casperix.renderer.material.TextureConfig
import ru.casperix.renderer.vector.GeometryBuilder
import ru.casperix.renderer.vector.VectorGraphic
import ru.casperix.renderer.vector.VectorShape
import java.awt.*
import java.awt.font.TextAttribute
import java.awt.image.BufferedImage
object JvmTextGraphicProcessor : TextGraphicProcessor {
private val right = setOf(
CharDirectionality.RIGHT_TO_LEFT,
CharDirectionality.RIGHT_TO_LEFT_ARABIC,
CharDirectionality.RIGHT_TO_LEFT_EMBEDDING,
CharDirectionality.RIGHT_TO_LEFT_OVERRIDE
)
private val left = setOf(
CharDirectionality.LEFT_TO_RIGHT,
CharDirectionality.LEFT_TO_RIGHT_EMBEDDING,
CharDirectionality.LEFT_TO_RIGHT_OVERRIDE
)
private val tempGraphic = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics()
class JvmFontInfo(
val reference: FontReference,
val awtFont: Font,
val awtMetrics: FontMetrics,
val metrics: ru.casperix.multiplatform.font.FontMetrics
)
override fun isLeftToRight(line: String): Boolean {
line.forEach {
if (it.isLetter()) {
if (left.contains(it.directionality)) return true
if (right.contains(it.directionality)) return false
}
}
return true
}
override fun getFontMetrics(font: FontReference): ru.casperix.multiplatform.font.FontMetrics {
val fontInfo = getFontInfo(font)
return fontInfo.metrics
}
override fun getStringMetrics(font: FontReference, line: String): StringMetrics {
return getStringMetrics(getFontInfo(font), line)
}
private fun getStringMetrics(fontInfo: JvmFontInfo, line: String): StringMetrics {
val chars = line.toCharArray()
return StringMetrics.calculate(line, {
fontInfo.awtMetrics.charsWidth(chars, it, 1).toFloat()
}, fontInfo.metrics.textHeight)
}
override fun create(scheme: TextScheme): VectorGraphic {
val value = scheme.summaryArea.value
val minX = value.min.x
val minY = value.min.y
val maxX = value.max.x
val maxY = value.max.y
val imageWidth = (maxX - minX).ceilToInt()
val imageHeight = (maxY - minY).ceilToInt()
if (imageWidth == 0 || imageHeight == 0) {
return VectorGraphic.EMPTY
}
val image = BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB)
val graphic = image.createGraphics()
applyHints(graphic)
scheme.elements.forEach {
val fontInfo = getFontInfo(it.font)
val position = Vector2f(
it.textArea.min.x - minX,
it.textArea.min.y - minY + fontInfo.metrics.ascent
).run { if (TextRenderConfig.textRoundToPixel) round() else this }
graphic.font = fontInfo.awtFont
graphic.paint = it.foreground.toAwtColor()
graphic.drawString(it.part.text, position.x, position.y)
}
graphic.dispose()
val pixelMap = JvmImageConverter.createPixelMap(image, "")
val area = Box2f.byDimension(Vector2f(minX, minY), dimensionOf(imageWidth, imageHeight).toVector2f()).toQuad()
return VectorGraphic(
listOf(
VectorShape(
SimpleMaterial(Texture2D(pixelMap, TextureConfig(useMipMap = false))),
GeometryBuilder.texturedQuad(area)
)
)
)
}
private fun getFontInfo(reference: FontReference): JvmFontInfo {
applyHints(tempGraphic)
val boldBase = if (reference.weight.value <= 500) Font.PLAIN else Font.BOLD
val awtFont = Font(reference.name, boldBase, reference.size).deriveFont(
mapOf(
Pair(
TextAttribute.WEIGHT,
reference.weight.value.toFloat() / FontWeight.NORMAL.value.toFloat()
)
)
)
val awtMetrics = tempGraphic.getFontMetrics(awtFont)
val lineMetrics = awtMetrics.getLineMetrics("A", tempGraphic)
val ascent = lineMetrics.ascent
val descent = lineMetrics.descent
val leading = lineMetrics.leading
return JvmFontInfo(
reference,
awtFont,
awtMetrics,
ru.casperix.multiplatform.font.FontMetrics(ascent, descent, leading)
)
}
private fun applyHints(graphic: Graphics2D) = graphic.apply {
if (useAntiAlias) {
setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
}
if (useBicubicInterpolation) {
setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)
}
setRenderingHint(
RenderingHints.KEY_FRACTIONALMETRICS, if (useSubpixelRender) {
RenderingHints.VALUE_FRACTIONALMETRICS_ON
} else {
RenderingHints.VALUE_FRACTIONALMETRICS_OFF
}
)
}
private fun Color.toAwtColor(): java.awt.Color {
return toColor4b().run {
Color(red.toInt(), green.toInt(), blue.toInt(), alpha.toInt())
}
}
}