commonMain.ru.casperix.multiplatform.text.impl.TextLayoutBuilder.kt Maven / Gradle / Ivy
package ru.casperix.multiplatform.text.impl
import ru.casperix.multiplatform.font.localize.CharacterSets
import ru.casperix.multiplatform.font.pixel.PixelFont
import ru.casperix.multiplatform.font.pixel.PixelFontSymbol
import ru.casperix.math.axis_aligned.float32.Box2f
import ru.casperix.math.vector.float32.Vector2f
import ru.casperix.multiplatform.text.SymbolLayout
import ru.casperix.multiplatform.text.TextLayout
import ru.casperix.multiplatform.text.TextRenderConfig
@ExperimentalUnsignedTypes
object TextLayoutBuilder {
fun layout(text: String, areaSize: Vector2f, font: PixelFont): TextLayout {
val blocks = TextProcessor.splitByGroups(text)
val blockLayouts = blocks.map { word ->
layoutBlock(font, word, font.symbolMap, TextRenderConfig.textMissingSymbolBehaviour)
}
val symbols = placeSymbols(font, blockLayouts, areaSize)
return TextLayout(font.metrics, text, symbols)
}
private fun placeSymbols(font: PixelFont, layouts: List, areaSize: Vector2f): List {
var baselineOffset = Vector2f(0f, font.metrics.ascent)
val lineHeight = font.lineHeight
val layoutOffsets = layouts.map { block ->
val requestArea = block.getArea()
val isCarriageReturn = block.text == "\n"
val lastPosition = baselineOffset
if (isCarriageReturn || (baselineOffset.x > 0f && baselineOffset.x + requestArea.dimension.x > areaSize.x)) {
baselineOffset = Vector2f(0f, baselineOffset.y + lineHeight)
}
val blockPosition = if (isCarriageReturn) lastPosition else baselineOffset
baselineOffset += requestArea.dimension.xAxis
Pair(blockPosition, block)
}
val symbols = layoutOffsets.flatMap { (offset, layout) ->
layout.symbols.map {
it.copy(pivot = it.pivot + offset)
}
}
return symbols
}
private fun layoutBlock(
font: PixelFont,
text: String,
fontSymbols: Map,
missingSymbolBehaviour: MissingSymbolBehaviour
): TextLayout {
var offset = Vector2f.ZERO
val metrics = font.metrics
val symbols = text.flatMap { char ->
generateGlyphList(char, fontSymbols, missingSymbolBehaviour)
}.map { symbol ->
val symbolWidth = symbol.size.x.toFloat()
val position = offset
offset += Vector2f(symbolWidth, 0f)
val symbolArea = Box2f(
-Vector2f(0f, metrics.ascent),
Vector2f(0f, metrics.descent) + symbol.size.xAxis.toVector2f()
)
SymbolLayout(position, symbol, symbolArea)
}
return TextLayout(font.metrics, text, symbols)
}
private fun generateGlyphList(
char: Char,
symbolMap: Map,
missingSymbolBehaviour: MissingSymbolBehaviour
): List {
val symbol = symbolMap[char]
return if (symbol != null) {
listOf(symbol)
} else if (char == '\n') {
emptyList()
} else {
when (missingSymbolBehaviour) {
MissingSymbolBehaviour.IGNORE -> emptyList()
MissingSymbolBehaviour.SHOW_CHAR_CODE -> ("#" + char.code.toString() + ";").mapNotNull {
symbolMap[it]
}
MissingSymbolBehaviour.SHOW_WHITE_SQUARE -> listOfNotNull(symbolMap[CharacterSets.WHITE_QUAD])
}
}
}
}