All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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())
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy