fix: quirks with getting width of blocks and typesetting

This commit is contained in:
minjaesong
2024-05-21 16:47:06 +09:00
parent 7c8a1be3e5
commit 3500f17e08
3 changed files with 183 additions and 91 deletions

View File

@@ -8,7 +8,12 @@ import net.torvald.terrarumsansbitmap.gdx.CodePoint
import net.torvald.terrarumsansbitmap.gdx.CodepointSequence import net.torvald.terrarumsansbitmap.gdx.CodepointSequence
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.FIXED_BLOCK_1 import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.FIXED_BLOCK_1
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.NBSP
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.OBJ
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.SHY
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.ZWSP
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.getHash import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.getHash
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.glueCharToGlueSize
import kotlin.math.* import kotlin.math.*
import kotlin.properties.Delegates import kotlin.properties.Delegates
@@ -87,7 +92,7 @@ class MovableType(
fun dequeue() = boxes.removeFirst() fun dequeue() = boxes.removeFirst()
fun addHyphenatedTail(box: NoTexGlyphLayout) = boxes.add(0, box) fun addHyphenatedTail(box: NoTexGlyphLayout) = boxes.add(0, box)
fun addToSlug(box: NoTexGlyphLayout) { fun addToSlug(box: NoTexGlyphLayout) {
val nextPosX = (slug.lastOrNull()?.getEndPos() ?: 0) val nextPosX = slug.getSlugEndPos()
slug.add(Block(nextPosX, box)) slug.add(Block(nextPosX, box))
slugWidth += box.width slugWidth += box.width
@@ -140,6 +145,9 @@ class MovableType(
slug = ArrayList() slug = ArrayList()
slugWidth = 0 slugWidth = 0
// println("Frozen slug: ${frozen.toReadable()}")
} }
/////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -153,7 +161,7 @@ class MovableType(
slug.removeLastOrNull() slug.removeLastOrNull()
} }
var slugWidth = (slug.lastOrNull()?.getEndPos() ?: 0) - exdentSize var slugWidth = slug.getSlugEndPos() - exdentSize
if (slug.isNotEmpty() && slug.last().block.penultimateCharOrNull != null && hangable.contains(slug.last().block.penultimateCharOrNull)) if (slug.isNotEmpty() && slug.last().block.penultimateCharOrNull != null && hangable.contains(slug.last().block.penultimateCharOrNull))
slugWidth -= hangWidth slugWidth -= hangWidth
else if (slug.isNotEmpty() && slug.last().block.penultimateCharOrNull != null && hangableFW.contains(slug.last().block.penultimateCharOrNull)) else if (slug.isNotEmpty() && slug.last().block.penultimateCharOrNull != null && hangableFW.contains(slug.last().block.penultimateCharOrNull))
@@ -174,7 +182,7 @@ class MovableType(
}*/ }*/
// add the box to the slug copy // add the box to the slug copy
val nextPosX = (slug.lastOrNull()?.getEndPos() ?: 0) val nextPosX = slug.getSlugEndPos()
slug.add(Block(nextPosX, box)) slug.add(Block(nextPosX, box))
var slugWidth = slugWidth + box.width - exdentSize var slugWidth = slugWidth + box.width - exdentSize
@@ -198,7 +206,7 @@ class MovableType(
// - the word is too short (5 chars or less) // - the word is too short (5 chars or less)
// - the word is pre-hyphenated (ends with hyphen-null) // - the word is pre-hyphenated (ends with hyphen-null)
val glyphCount = box.text.count { it in 32..0xFFF6F && it !in 0xFFF0..0xFFFF } val glyphCount = box.text.count { it in 32..0xFFF6F && it !in 0xFFF0..0xFFFF }
if (glyphCount <= (if (paperWidth < 350) 4 else if (paperWidth < 480) 5 else 6) || box.text.penultimate() == 0x2D) if (glyphCount <= (if (paperWidth < 350) 3 else if (paperWidth < 480) 4 else 5) || box.text.penultimate() == 0x2D)
return Triple(Double.POSITIVE_INFINITY, 2147483647, null) return Triple(Double.POSITIVE_INFINITY, 2147483647, null)
val slug = slug.toMutableList() // ends with a glue val slug = slug.toMutableList() // ends with a glue
@@ -222,7 +230,7 @@ class MovableType(
return Triple(Double.POSITIVE_INFINITY, 2147483647, null) return Triple(Double.POSITIVE_INFINITY, 2147483647, null)
// add the hyphHead to the slug copy // add the hyphHead to the slug copy
val nextPosX = (slug.lastOrNull()?.getEndPos() ?: 0) val nextPosX = slug.getSlugEndPos()
slug.add(Block(nextPosX, hyphHead)) // now ends with 'word-' (but not in Hangul) slug.add(Block(nextPosX, hyphHead)) // now ends with 'word-' (but not in Hangul)
val hasHyphen = hyphHead.penultimateCharOrNull == 0x2D val hasHyphen = hyphHead.penultimateCharOrNull == 0x2D
@@ -322,7 +330,7 @@ class MovableType(
if (badnessH.isInfinite() && badnessW.isInfinite() && badnessT.isInfinite()) { if (badnessH.isInfinite() && badnessW.isInfinite() && badnessT.isInfinite()) {
throw Error( throw Error(
"Typesetting failed: badness of all three strategies diverged to infinity\ntext (${slug.size} tokens): ${ "Typesetting failed: badness of all three strategies diverged to infinity. Try adding white spaces to the text.\nThe text (${slug.size} tokens): ${
slug.map { it.block.text }.filter { it.isNotGlue() } slug.map { it.block.text }.filter { it.isNotGlue() }
.joinToString(" ") { it.toReadable() } .joinToString(" ") { it.toReadable() }
}" }"
@@ -586,9 +594,9 @@ class MovableType(
var cM: CodePoint? = null var cM: CodePoint? = null
var glue = 0 var glue = 0
fun getControlHeader(row: Int, word: Int): CodepointSequence { fun getControlHeader(row: Int, word: Int): List<Int> {
val index = row * 65536 or word val index = row * 65536 or word
val ret = CodepointSequence(controlCharList.filter { index > it.second }.map { it.first }) val ret = controlCharList.filter { index > it.second }.map { it.first }
return ret return ret
} }
@@ -832,13 +840,6 @@ class MovableType(
return lines return lines
} }
private fun <E> java.util.ArrayList<E>.penultimate(): E {
return this[this.size - 2]
}
private fun <E> java.util.ArrayList<E>.penultimateOrNull(): E? {
return this.getOrNull(this.size - 2)
}
private fun penaliseWidening(score: Int, availableGlues: Double): Double = private fun penaliseWidening(score: Int, availableGlues: Double): Double =
100.0 * (score / availableGlues).pow(3.0) 100.0 * (score / availableGlues).pow(3.0)
// pow(score.toDouble(), 2.0) // pow(score.toDouble(), 2.0)
@@ -873,16 +874,6 @@ class MovableType(
private fun CodePoint?.isThaiConso() = if (this == null) false else this in 0x0E01..0x0E2F private fun CodePoint?.isThaiConso() = if (this == null) false else this in 0x0E01..0x0E2F
private fun CodePoint?.isThaiVowel() = if (this == null) false else (this in 0x0E30..0x0E3E || this in 0x0E40..0x0E4E) private fun CodePoint?.isThaiVowel() = if (this == null) false else (this in 0x0E30..0x0E3E || this in 0x0E40..0x0E4E)
private fun CodepointSequence.isGlue() = this.size == 1 && (this[0] == ZWSP || this[0] in 0xFFFE0..0xFFFFF)
private fun CodepointSequence.isNotGlue() = !this.isGlue()
private fun CodepointSequence.isZeroGlue() = this.size == 1 && (this[0] == ZWSP)
private fun CodePoint.glueCharToGlueSize() = when (this) {
ZWSP -> 0
in 0xFFFE0..0xFFFEF -> -(this - 0xFFFE0 + 1)
in 0xFFFF0..0xFFFFF -> this - 0xFFFF0 + 1
else -> throw IllegalArgumentException()
}
private fun CodePoint?.isWesternPunctOrQuotes() = if (this == null) false else (westernPuncts.contains(this) || quots.contains(this)) private fun CodePoint?.isWesternPunctOrQuotes() = if (this == null) false else (westernPuncts.contains(this) || quots.contains(this))
private fun CodePoint?.isParens() = if (this == null) false else parens.contains(this) private fun CodePoint?.isParens() = if (this == null) false else parens.contains(this)
private fun CodePoint?.isParenOpen() = if (this == null) false else parenOpen.contains(this) private fun CodePoint?.isParenOpen() = if (this == null) false else parenOpen.contains(this)
@@ -893,7 +884,7 @@ class MovableType(
/** /**
* Hyphenates the word at the middle ("paragraph" -> "para-graph") * Hyphenates the word at the middle ("paragraph" -> "para-graph")
* *
* @return left word ("para-"), right word ("graph") * @return left word ("para-"), right word ("graph")
*/ */
private fun CodepointSequence.hyphenate(font: TerrarumSansBitmap, optimalCuttingPointInPx: Int): Pair<CodepointSequence, CodepointSequence> { private fun CodepointSequence.hyphenate(font: TerrarumSansBitmap, optimalCuttingPointInPx: Int): Pair<CodepointSequence, CodepointSequence> {
@@ -997,7 +988,7 @@ class MovableType(
0x20 to 4, 0x20 to 4,
0x2009 to 2, 0x2009 to 2,
0x200A to 1, 0x200A to 1,
0x200B to 0, ZWSP to 0,
0x3000 to 16, 0x3000 to 16,
0xF0520 to 7, // why???? 0xF0520 to 7, // why????
) )
@@ -1010,10 +1001,6 @@ class MovableType(
private val parenOpen = listOf(0x28,0x5B,0x7B).toSortedSet().also { it.addAll(cjparenStarts) } private val parenOpen = listOf(0x28,0x5B,0x7B).toSortedSet().also { it.addAll(cjparenStarts) }
private val parenClose = listOf(0x29,0x5D,0x7D).toSortedSet().also { it.addAll(cjparenEnds) } private val parenClose = listOf(0x29,0x5D,0x7D).toSortedSet().also { it.addAll(cjparenEnds) }
const val ZWSP = 0x200B
const val SHY = 0xAD
const val NBSP = 0xA0
const val OBJ = 0xFFFC
const val GLUE_POSITIVE_ONE = 0xFFFF0 const val GLUE_POSITIVE_ONE = 0xFFFF0
const val GLUE_POSITIVE_SIXTEEN = 0xFFFFF const val GLUE_POSITIVE_SIXTEEN = 0xFFFFF
const val GLUE_NEGATIVE_ONE = 0xFFFE0 const val GLUE_NEGATIVE_ONE = 0xFFFE0
@@ -1023,54 +1010,6 @@ class MovableType(
private inline fun Int.codepointToString() = Character.toChars(this).toSurrogatedString() private inline fun Int.codepointToString() = Character.toChars(this).toSurrogatedString()
fun CodepointSequence.toReadable() = this.joinToString("") {
if (it in 0x00..0x1f)
"${(0x2400 + it).toChar()}"
else if (it == 0x20 || it == 0xF0520)
"\u2423"
else if (it == NBSP)
"{NBSP}"
else if (it == SHY)
"{SHY}"
else if (it == ZWSP)
"{ZWSP}"
else if (it == OBJ)
"{OBJ:"
else if (it in FIXED_BLOCK_1..FIXED_BLOCK_1+15)
" <block ${it - FIXED_BLOCK_1 + 1}>"
else if (it in GLUE_NEGATIVE_ONE..GLUE_POSITIVE_SIXTEEN)
" <glue ${it.glueCharToGlueSize()}> "
else if (it == 0x100000)
"{CC:null}"
else if (it in 0x10F000..0x10FFFF) {
val r = ((it and 0xF00) ushr 8).toString(16).toUpperCase()
val g = ((it and 0x0F0) ushr 4).toString(16).toUpperCase()
val b = ((it and 0x00F) ushr 0).toString(16).toUpperCase()
"{CC:#$r$g$b}"
}
else if (it in 0xFFF70..0xFFF79)
(it - 0xFFF70 + 0x30).codepointToString()
else if (it == 0xFFF7D)
"-"
else if (it in 0xFFF80..0xFFF9A)
(it - 0xFFF80 + 0x40).codepointToString()
else if (it == 0xFFF9F)
"}"
else if (it in 0xF0541..0xF055A)
(it - 0xF0541 + 0x1D670).codepointToString()
else if (it in 0xF0561..0xF057A)
(it - 0xF0561 + 0x1D68A).codepointToString()
else if (it in 0xF0530..0xF0539)
(it - 0xF0530 + 0x1D7F6).codepointToString()
else if (it in 0xF0520..0xF057F)
(it - 0xF0520 + 0x20).codepointToString()
else if (it >= 0xF0000)
it.toHex() + " "
else
Character.toString(it.toChar())
}
private fun List<ArrayList<CodepointSequence>>.debugprint() { private fun List<ArrayList<CodepointSequence>>.debugprint() {
println("Tokenised (${this.size} lines):") println("Tokenised (${this.size} lines):")
this.forEach { this.forEach {
@@ -1139,13 +1078,15 @@ class MovableType(
val input = this.filter { it.block.text.isNotGlue() } val input = this.filter { it.block.text.isNotGlue() }
if (input.isEmpty()) return out if (input.isEmpty()) return out
// println("freezeIntoCodepointSequence ${input.joinToString { "${it.posX}..${it.getEndPos()}" }}")
// process line indents // process line indents
if (input.first().posX > 0) if (input.first().posX > 0)
out.addAll(input.first().posX.glueSizeToGlueChars()) out.addAll(input.first().posX.glueSizeToGlueChars())
// process blocks // process blocks
input.forEachIndexed { index, it -> input.forEachIndexed { index, it ->
val posX = it.posX + 1 - font.interchar * 2 val posX = it.posX - font.interchar * 2
val prevEndPos = if (index == 0) 0 else input[index-1].getEndPos() val prevEndPos = if (index == 0) 0 else input[index-1].getEndPos()
if (index > 0 && posX != prevEndPos) { if (index > 0 && posX != prevEndPos) {
out.addAll((posX - prevEndPos).glueSizeToGlueChars()) out.addAll((posX - prevEndPos).glueSizeToGlueChars())
@@ -1187,7 +1128,7 @@ class MovableType(
// process blocks // process blocks
input.forEachIndexed { index, it -> input.forEachIndexed { index, it ->
val posX = it.posX + 1 - font.interchar * 2 val posX = it.posX - font.interchar * 2
val prevEndPos = if (index == 0) 0 else input[index-1].getEndPos() val prevEndPos = if (index == 0) 0 else input[index-1].getEndPos()
if (index > 0 && posX != prevEndPos) { if (index > 0 && posX != prevEndPos) {
out += posX - prevEndPos out += posX - prevEndPos
@@ -1199,6 +1140,10 @@ class MovableType(
inline fun Boolean.toInt(shift: Int = 0) = if (this) 1.shl(shift) else 0 inline fun Boolean.toInt(shift: Int = 0) = if (this) 1.shl(shift) else 0
private fun List<Block>.getSlugEndPos(): Int {
return this.lastOrNull()?.getEndPos() ?: 0
}
} // end of companion object } // end of companion object
} }

View File

@@ -33,18 +33,133 @@ import com.badlogic.gdx.utils.GdxRuntimeException
import net.torvald.terrarumsansbitmap.DiacriticsAnchor import net.torvald.terrarumsansbitmap.DiacriticsAnchor
import net.torvald.terrarumsansbitmap.GlyphProps import net.torvald.terrarumsansbitmap.GlyphProps
import net.torvald.terrarumsansbitmap.MovableType import net.torvald.terrarumsansbitmap.MovableType
import net.torvald.terrarumsansbitmap.MovableType.Companion.GLUE_NEGATIVE_ONE
import net.torvald.terrarumsansbitmap.MovableType.Companion.GLUE_POSITIVE_SIXTEEN
import net.torvald.terrarumsansbitmap.TypesettingStrategy import net.torvald.terrarumsansbitmap.TypesettingStrategy
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.FIXED_BLOCK_1
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.NBSP
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.OBJ
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.SHY
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.ZWSP
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.glueCharToGlueSize
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.util.* import java.util.*
import java.util.zip.CRC32 import java.util.zip.CRC32
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.math.sign import kotlin.math.sign
typealias CodepointSequence = ArrayList<CodePoint> class CodepointSequence {
private val data = ArrayList<CodePoint>()
constructor()
constructor(chars: Collection<CodePoint>) {
data.addAll(chars)
}
val size; get() = data.size
val indices; get() = data.indices
val lastIndex; get() = data.lastIndex
fun forEach(action: (CodePoint) -> Unit) = data.forEach(action)
fun forEachIndexed(action: (Int, CodePoint) -> Unit) = data.forEachIndexed(action)
fun map(action: (CodePoint) -> Any?) = data.map(action)
fun mapInxeded(action: (Int, CodePoint) -> Any?) = data.mapIndexed(action)
fun first() = data.first()
fun firstOrNull() = data.firstOrNull()
fun first(predicate: (CodePoint) -> Boolean) = data.first(predicate)
fun firstOrNull(predicate: (CodePoint) -> Boolean) = data.firstOrNull(predicate)
fun last() = data.last()
fun lastOrNull() = data.lastOrNull()
fun last(predicate: (CodePoint) -> Boolean) = data.last(predicate)
fun lastOrNull(predicate: (CodePoint) -> Boolean) = data.lastOrNull(predicate)
fun filter(predicate: (CodePoint) -> Boolean) = data.filter(predicate)
fun add(index: Int, char: CodePoint) = data.add(index, char)
operator fun set(index: Int, char: CodePoint) {
data[index] = char
}
fun add(char: CodePoint) = data.add(char)
fun addAll(chars: Collection<CodePoint>) = data.addAll(chars)
fun addAll(cs: CodepointSequence) = data.addAll(cs.data)
fun removeAt(index: Int) = data.removeAt(index)
fun remove(char: CodePoint) = data.remove(char)
operator fun get(index: Int) = data[index]
fun getOrNull(index: Int) = data.getOrNull(index)
fun getOrElse(index: Int, action: (Int) -> CodePoint) = data.getOrElse(index, action)
fun isEmpty() = data.isEmpty()
fun isNotEmpty() = data.isNotEmpty()
fun count(predicate: (CodePoint) -> Boolean) = data.count(predicate)
fun addAll(index: Int, elements: Collection<CodePoint>) = data.addAll(index, elements)
fun addAll(index: Int, elements: CodepointSequence) = data.addAll(index, elements.data)
fun subList(fromIndex: Int, toIndex: Int) = data.subList(fromIndex, toIndex)
fun slice(indices: IntRange) = data.slice(indices)
fun penultimate() = data[data.size - 2]
fun penultimateOrNull() = data.getOrNull(data.size - 2)
fun toArray() = data.toArray()
fun toList() = data.toList()
fun isGlue() = data.size == 1 && (data[0] == ZWSP || data[0] in 0xFFFE0..0xFFFFF)
fun isNotGlue() = !isGlue()
fun isZeroGlue() = data.size == 1 && (data[0] == ZWSP)
private fun CharArray.toSurrogatedString(): String = if (this.size == 1) "${this[0]}" else "${this[0]}${this[1]}"
private inline fun Int.codepointToString() = Character.toChars(this).toSurrogatedString()
private fun CodePoint.toHex() = "U+${this.toString(16).padStart(4, '0').toUpperCase()}"
fun toHexes() = data.joinToString(" ") { it.toHex() }
fun toReadable() = data.joinToString("") {
if (it in 0x00..0x1f)
"${(0x2400 + it).toChar()}"
else if (it == 0x20 || it == 0xF0520)
"\u2423"
else if (it == NBSP)
"{NBSP}"
else if (it == SHY)
"{SHY}"
else if (it == ZWSP)
"{ZWSP}"
else if (it == OBJ)
"{OBJ:"
else if (it in FIXED_BLOCK_1..FIXED_BLOCK_1 +15)
" <block ${it - FIXED_BLOCK_1 + 1}>"
else if (it in GLUE_NEGATIVE_ONE..GLUE_POSITIVE_SIXTEEN)
" <glue ${it.glueCharToGlueSize()}> "
else if (it == 0x100000)
"{CC:null}"
else if (it in 0x10F000..0x10FFFF) {
val r = ((it and 0xF00) ushr 8).toString(16).toUpperCase()
val g = ((it and 0x0F0) ushr 4).toString(16).toUpperCase()
val b = ((it and 0x00F) ushr 0).toString(16).toUpperCase()
"{CC:#$r$g$b}"
}
else if (it in 0xFFF70..0xFFF79)
(it - 0xFFF70 + 0x30).codepointToString()
else if (it == 0xFFF7D)
"-"
else if (it in 0xFFF80..0xFFF9A)
(it - 0xFFF80 + 0x40).codepointToString()
else if (it == 0xFFF9F)
"}"
else if (it in 0xF0541..0xF055A)
(it - 0xF0541 + 0x1D670).codepointToString()
else if (it in 0xF0561..0xF057A)
(it - 0xF0561 + 0x1D68A).codepointToString()
else if (it in 0xF0530..0xF0539)
(it - 0xF0530 + 0x1D7F6).codepointToString()
else if (it in 0xF0520..0xF057F)
(it - 0xF0520 + 0x20).codepointToString()
else if (it >= 0xF0000)
it.toHex() + " "
else
Character.toString(it.toChar())
}
}
internal typealias CodePoint = Int internal typealias CodePoint = Int
internal typealias ARGB8888 = Int internal typealias ARGB8888 = Int
internal typealias Hash = Long internal typealias Hash = Long
@@ -330,7 +445,7 @@ class TerrarumSansBitmap(
private val offsetCustomSym = (H - SIZE_CUSTOM_SYM) / 2 private val offsetCustomSym = (H - SIZE_CUSTOM_SYM) / 2
private var flagFirstRun = true private var flagFirstRun = true
private var textBuffer = CodepointSequence(256) private var textBuffer = CodepointSequence()
private lateinit var tempLinotype: Texture private lateinit var tempLinotype: Texture
@@ -918,19 +1033,38 @@ class TerrarumSansBitmap(
for (i in 0xFFF70..0xFFF9F) { for (i in 0xFFF70..0xFFF9F) {
glyphProps[i] = GlyphProps(0) glyphProps[i] = GlyphProps(0)
} }
glyphProps[ZWNJ] = GlyphProps(0)
glyphProps[ZWJ] = GlyphProps(0)
glyphProps[ZWSP] = GlyphProps(0)
glyphProps[SHY] = GlyphProps(0)
glyphProps[OBJ] = GlyphProps(0)
} }
private fun setupDynamicTextReplacer() { private fun setupDynamicTextReplacer() {
// replace NBSP into a block of same width // replace NBSP into a block of same width
val spaceWidth = glyphProps[32]?.width ?: throw IllegalStateException() val spaceWidth = glyphProps[32]?.width ?: throw IllegalStateException()
if (spaceWidth > 16) throw InternalError("Space (U+0020) character is too wide ($spaceWidth)") if (spaceWidth > 16) throw InternalError("Space (U+0020) character is too wide ($spaceWidth)")
textReplaces[0xA0] = FIXED_BLOCK_1 + (spaceWidth - 1) textReplaces[NBSP] = FIXED_BLOCK_1 + (spaceWidth - 1)
} }
fun getWidth(text: String) = getWidthNormalised(text.toCodePoints()) fun getWidth(text: String) = getWidthNormalised(text.toCodePoints())
fun getWidth(s: CodepointSequence) = getWidthNormalised(s.normalise()) fun getWidth(s: CodepointSequence) = getWidthNormalised(s.normalise())
fun getWidthNormalised(s: CodepointSequence): Int { fun getWidthNormalised(s: CodepointSequence): Int {
if (s.isEmpty())
return 0
if (s.size == 1) {
return glyphProps[s.first()]?.width ?: (
if (errorOnUnknownChar)
throw InternalError("No GlyphProps for char '${s.first().toHex()}' " +
"(${s.first().charInfo()})")
else
0
)
}
val cacheObj = getCache(s.getHash()) val cacheObj = getCache(s.getHash())
if (cacheObj != null) { if (cacheObj != null) {
@@ -949,7 +1083,7 @@ class TerrarumSansBitmap(
* @return Pair of X-positions and Y-positions, of which the X-position's size is greater than the string * @return Pair of X-positions and Y-positions, of which the X-position's size is greater than the string
* and the last element marks the width of entire string. * and the last element marks the width of entire string.
*/ */
private fun buildPosMap(str: List<Int>): Posmap { private fun buildPosMap(str: CodepointSequence): Posmap {
val posXbuffer = IntArray(str.size + 1) { 0 } val posXbuffer = IntArray(str.size + 1) { 0 }
val posYbuffer = IntArray(str.size) { 0 } val posYbuffer = IntArray(str.size) { 0 }
@@ -1130,7 +1264,7 @@ class TerrarumSansBitmap(
val penultCharProp = glyphProps[str[nonDiacriticCounter]] ?: val penultCharProp = glyphProps[str[nonDiacriticCounter]] ?:
(if (errorOnUnknownChar) throw throw InternalError("No GlyphProps for char '${str[nonDiacriticCounter]}' " + (if (errorOnUnknownChar) throw throw InternalError("No GlyphProps for char '${str[nonDiacriticCounter]}' " +
"(${str[nonDiacriticCounter].charInfo()})") else nullProp) "(${str[nonDiacriticCounter].charInfo()})") else nullProp)
posXbuffer[posXbuffer.lastIndex] = 1 + posXbuffer[posXbuffer.lastIndex - 1] + // adding 1 to house the shadow posXbuffer[posXbuffer.lastIndex] = posXbuffer[posXbuffer.lastIndex - 1] + // DON'T add 1 to house the shadow, it totally breaks stuffs
if (lastCharProp != null && lastCharProp.writeOnTop >= 0) { if (lastCharProp != null && lastCharProp.writeOnTop >= 0) {
val realDiacriticWidth = if (lastCharProp.alignWhere == GlyphProps.ALIGN_CENTRE) { val realDiacriticWidth = if (lastCharProp.alignWhere == GlyphProps.ALIGN_CENTRE) {
(lastCharProp.width).div(2) + penultCharProp.diacriticsAnchors[0].x (lastCharProp.width).div(2) + penultCharProp.diacriticsAnchors[0].x
@@ -2221,6 +2355,13 @@ class TerrarumSansBitmap(
companion object { companion object {
internal fun CodePoint.glueCharToGlueSize() = when (this) {
ZWSP -> 0
in 0xFFFE0..0xFFFEF -> -(this - 0xFFFE0 + 1)
in 0xFFFF0..0xFFFFF -> this - 0xFFFF0 + 1
else -> throw IllegalArgumentException()
}
const internal val linotypePaddingX = 16 const internal val linotypePaddingX = 16
const internal val linotypePaddingY = 10 const internal val linotypePaddingY = 10
@@ -2467,8 +2608,12 @@ class TerrarumSansBitmap(
internal fun Int.charInfo() = "U+${this.toString(16).padStart(4, '0').toUpperCase()}: ${Character.getName(this)}" internal fun Int.charInfo() = "U+${this.toString(16).padStart(4, '0').toUpperCase()}: ${Character.getName(this)}"
private const val ZWNJ = 0x200C const val ZWNJ = 0x200C
private const val ZWJ = 0x200D const val ZWJ = 0x200D
const val ZWSP = 0x200B
const val SHY = 0xAD
const val NBSP = 0xA0
const val OBJ = 0xFFFC
private val tamilLigatingConsonants = listOf('க','ங','ச','ஞ','ட','ண','த','ந','ன','ப','ம','ய','ர','ற','ல','ள','ழ','வ').map { it.toInt() }.toIntArray() // this is the only thing that .indexOf() is called against, so NO HASHSET private val tamilLigatingConsonants = listOf('க','ங','ச','ஞ','ட','ண','த','ந','ன','ப','ம','ய','ர','ற','ல','ள','ழ','வ').map { it.toInt() }.toIntArray() // this is the only thing that .indexOf() is called against, so NO HASHSET
private const val TAMIL_KSSA = 0xF00ED private const val TAMIL_KSSA = 0xF00ED

View File

@@ -278,7 +278,7 @@ class TerrarumTypewriterBitmap(
private val pixmapOffsetY = 10 private val pixmapOffsetY = 10
private val linotypePad = 16 private val linotypePad = 16
private var flagFirstRun = true private var flagFirstRun = true
private @Volatile var textBuffer = CodepointSequence(256) private @Volatile var textBuffer = CodepointSequence()
private @Volatile lateinit var tempLinotype: Texture private @Volatile lateinit var tempLinotype: Texture
private var nullProp = GlyphProps(15) private var nullProp = GlyphProps(15)
@@ -396,6 +396,8 @@ class TerrarumTypewriterBitmap(
} }
private fun buildPosMap(str: CodepointSequence) = buildPosMap(str.toList())
/** /**
* posXbuffer's size is greater than the string, last element marks the width of entire string. * posXbuffer's size is greater than the string, last element marks the width of entire string.
*/ */