From fb4cfb6e6d07a849833a9b3b6732f2ea480693aa Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sat, 4 May 2024 02:50:00 +0900 Subject: [PATCH] raggedright typesetting --- PUA_allocation_chart.xlsx | Bin 17892 -> 17950 bytes .../torvald/terrarumsansbitmap/MovableType.kt | 222 +++++++++++------- .../gdx/TerrarumSansBitmap.kt | 16 +- 3 files changed, 151 insertions(+), 87 deletions(-) diff --git a/PUA_allocation_chart.xlsx b/PUA_allocation_chart.xlsx index 6217719afb28ab2c8221470aefa90a863e3ae4e5..55e5aa7559694e9c64d829fb4d8388ff2191ec19 100755 GIT binary patch delta 11457 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 E2g27ioB#j- delta 11463 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 diff --git a/src/net/torvald/terrarumsansbitmap/MovableType.kt b/src/net/torvald/terrarumsansbitmap/MovableType.kt index fdce691..36afe31 100644 --- a/src/net/torvald/terrarumsansbitmap/MovableType.kt +++ b/src/net/torvald/terrarumsansbitmap/MovableType.kt @@ -7,10 +7,13 @@ import net.torvald.terrarumsansbitmap.gdx.CodePoint import net.torvald.terrarumsansbitmap.gdx.CodepointSequence import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap.Companion.getHash -import java.lang.Math.pow import kotlin.math.* import kotlin.properties.Delegates +enum class TypesettingStrategy { + JUSTIFIED, RAGGED_RIGHT +} + /** * Despite "CJK" texts needing their own typesetting rule, in this code Korean texts are typesetted much like * the western texts minus the hyphenation rule (it does hyphenate just like the western texts, but omits the @@ -22,9 +25,22 @@ class MovableType( val font: TerrarumSansBitmap, val inputText: CodepointSequence, textWidth: Int, - internal val isNull: Boolean = false + strategy: TypesettingStrategy = TypesettingStrategy.JUSTIFIED ): Disposable { + + private var isNull = false + + internal constructor( + font: TerrarumSansBitmap, + inputText: CodepointSequence, + textWidth: Int, + strategy: TypesettingStrategy = TypesettingStrategy.JUSTIFIED, + isNull: Boolean + ) : this(font, inputText, textWidth, strategy) { + this.isNull = isNull + } + var height = 0; private set internal val hash: Long = inputText.getHash() private var disposed = false @@ -168,35 +184,52 @@ class MovableType( // if adding the box would cause overflow if (slugWidthForOverflowCalc + box.width > paperWidth) { - // text overflow occured; set the width to the max value - width = paperWidth + // if adding the box would cause overflow (justified) + if (strategy == TypesettingStrategy.JUSTIFIED) { + // text overflow occured; set the width to the max value + width = paperWidth - val initialGlueCount = slug.getGlueSizeSum(font) + val initialGlueCount = slug.getGlueSizeSum(font) - // badness: always positive and weighted - // widthDelta: can be positive or negative - var (badnessW, widthDeltaW, _) = getBadnessW(box, initialGlueCount) // widthDeltaW is always positive - var (badnessT, widthDeltaT, _) = getBadnessT(box, initialGlueCount) // widthDeltaT is always positive - var (badnessH, widthDeltaH, hyph) = getBadnessH(box, box.width - slugWidthForOverflowCalc, initialGlueCount) // widthDeltaH can be anything + // badness: always positive and weighted + // widthDelta: can be positive or negative + var (badnessW, widthDeltaW, _) = getBadnessW( + box, + initialGlueCount + ) // widthDeltaW is always positive + var (badnessT, widthDeltaT, _) = getBadnessT( + box, + initialGlueCount + ) // widthDeltaT is always positive + var (badnessH, widthDeltaH, hyph) = getBadnessH( + box, + box.width - slugWidthForOverflowCalc, + initialGlueCount + ) // widthDeltaH can be anything - badnessT -= 0.1 // try to break even - badnessH -= 0.01 // try to break even - val disableHyphThre = 5.0 + badnessT -= 0.1 // try to break even + badnessH -= 0.01 // try to break even + val disableHyphThre = 5.0 - // disable hyphenation if badness of others is lower than the threshold - if ((badnessW <= disableHyphThre || badnessT <= disableHyphThre)) { - badnessH = Double.POSITIVE_INFINITY - } + // disable hyphenation if badness of others is lower than the threshold + if ((badnessW <= disableHyphThre || badnessT <= disableHyphThre)) { + badnessH = Double.POSITIVE_INFINITY + } - // disable hyphenation if hyphenating a word is impossible - if (hyph == null) { - badnessH = Double.POSITIVE_INFINITY - } + // disable hyphenation if hyphenating a word is impossible + if (hyph == null) { + badnessH = Double.POSITIVE_INFINITY + } - if (badnessH.isInfinite() && badnessW.isInfinite() && badnessT.isInfinite()) { - throw Error("Typesetting failed: badness of all three strategies diverged to infinity\ntext (${slug.size} tokens): ${slug.map { it.block.text }.filter { it.isNotGlue() }.joinToString(" ") { it.toReadable() }}") - } + if (badnessH.isInfinite() && badnessW.isInfinite() && badnessT.isInfinite()) { + throw Error( + "Typesetting failed: badness of all three strategies diverged to infinity\ntext (${slug.size} tokens): ${ + slug.map { it.block.text }.filter { it.isNotGlue() } + .joinToString(" ") { it.toReadable() } + }" + ) + } // println("\nLine: ${slug.map { it.block.text }.filter { it.isNotGlue() }.joinToString(" ") { it.toReadable() }}") @@ -204,11 +237,11 @@ class MovableType( // println("T diff: $widthDeltaT, badness: $badnessT") // println("H diff: $widthDeltaH, badness: $badnessH") - val (selectedBadness, selectedWidthDelta, selectedStrat) = listOf( - Triple(badnessW, widthDeltaW, "Widen"), - Triple(badnessT, widthDeltaT, "Tighten"), - Triple(badnessH, widthDeltaH, "Hyphenate"), - ).minByOrNull { it.first }!! + val (selectedBadness, selectedWidthDelta, selectedStrat) = listOf( + Triple(badnessW, widthDeltaW, "Widen"), + Triple(badnessT, widthDeltaT, "Tighten"), + Triple(badnessH, widthDeltaH, "Hyphenate"), + ).minByOrNull { it.first }!! // if (selectedStrat == "Hyphenate") { @@ -221,61 +254,77 @@ class MovableType( // println(" Line ${typesettedSlugs.size + 1} Strat: $selectedStrat (badness $selectedBadness, delta $selectedWidthDelta; full badness WTH = $badnessW, $badnessT, $badnessH; full delta WTH = $widthDeltaW, $widthDeltaT, $widthDeltaH)") // println(" Interim Slug: [ ${slug.map { it.block.text.toReadable() }.joinToString(" | ")} ]") - when (selectedStrat) { - "Widen", "Tighten" -> { - // widen/tighten the spacing between blocks + when (selectedStrat) { + "Widen", "Tighten" -> { + // widen/tighten the spacing between blocks - // widen: 1, tighten: -1 - val operation = if (selectedStrat == "Widen") 1 else -1 + // widen: 1, tighten: -1 + val operation = if (selectedStrat == "Widen") 1 else -1 - // Widen: remove the trailing glue(s?) in the slug - if (selectedStrat == "Widen") { - while (slug.lastOrNull()?.block?.isGlue() == true) { - slug.removeLast() + // Widen: remove the trailing glue(s?) in the slug + if (selectedStrat == "Widen") { + while (slug.lastOrNull()?.block?.isGlue() == true) { + slug.removeLast() + } } - } - // Tighten: add the box to the slug - else { - addToSlug(box) - // remove glues on the upcoming blocks - while (boxes.firstOrNull()?.isGlue() == true) { - boxes.removeFirst() + // Tighten: add the box to the slug + else { + addToSlug(box) + // remove glues on the upcoming blocks + while (boxes.firstOrNull()?.isGlue() == true) { + boxes.removeFirst() + } + } + + moveSlugsToFitTheWidth(operation, slug, selectedWidthDelta.absoluteValue) + + // put the trailing word back into the upcoming words + if (selectedStrat == "Widen") { + addHyphenatedTail(box) + } + // if tightening leaves an empty line behind, signal the typesetter to discard that line + else if (selectedStrat == "Tighten" && boxes.isEmpty()) { + ignoreThisLine = true } } - moveSlugsToFitTheWidth(operation, slug, selectedWidthDelta.absoluteValue) + "Hyphenate" -> { + // insert hyphen-head to the slug + // widen/tighten the spacing between blocks using widthDeltaH + // insert hyphen-tail to the list of upcoming boxes - // put the trailing word back into the upcoming words - if (selectedStrat == "Widen") { - addHyphenatedTail(box) - } - // if tightening leaves an empty line behind, signal the typesetter to discard that line - else if (selectedStrat == "Tighten" && boxes.isEmpty()) { - ignoreThisLine = true + val (hyphHead, hyphTail) = hyph as Pair + + // widen: 1, tighten: -1 + val operation = widthDeltaH.sign + + // insert hyphHead into the slug + addToSlug(hyphHead) + + moveSlugsToFitTheWidth(operation, slug, selectedWidthDelta.absoluteValue) + + // put the tail into the upcoming words + addHyphenatedTail(hyphTail) } } - "Hyphenate" -> { - // insert hyphen-head to the slug - // widen/tighten the spacing between blocks using widthDeltaH - // insert hyphen-tail to the list of upcoming boxes - - val (hyphHead, hyphTail) = hyph as Pair - - // widen: 1, tighten: -1 - val operation = widthDeltaH.sign - - // insert hyphHead into the slug - addToSlug(hyphHead) - - moveSlugsToFitTheWidth(operation, slug, selectedWidthDelta.absoluteValue) - - // put the tail into the upcoming words - addHyphenatedTail(hyphTail) - } - } // println(" > Line ${typesettedSlugs.size + 1} Final Slug: [ ${slug.map { it.block.text.toReadable() }.joinToString(" | ")} ]") - dispatchSlug() + dispatchSlug() + } + // if adding the box would cause overflow (ragged right) + else if (strategy == TypesettingStrategy.RAGGED_RIGHT) { + // remove trailing glues + while (slug.lastOrNull()?.block?.isGlue() == true) { + slug.removeLast() + } + + addHyphenatedTail(box) + + dispatchSlug() + } + else { + throw UnsupportedOperationException("Unknown typesetting strategy: ${strategy.name}") + } } // typeset the boxes normally else { @@ -425,14 +474,7 @@ class MovableType( fun getControlHeader(row: Int, word: Int): CodepointSequence { val index = row * 65536 or word - -// println("GetControlHeader $row, $word -> $index") -// println(" ControlChars: ${controlCharList.joinToString()}") - val ret = CodepointSequence(controlCharList.filter { index > it.second }.map { it.first }) - -// println(" Filtered: ${ret.joinToString()}") - return ret } @@ -837,7 +879,7 @@ class MovableType( private val whitespaceGlues = hashMapOf( 0x20 to 4, 0x3000 to 16, - 0xf0520 to 9, + 0xF0520 to 7, // why???? ) private val cjpuncts = listOf(0x203c, 0x2047, 0x2048, 0x2049, 0x3001, 0x3002, 0x3006, 0x303b, 0x30a0, 0x30fb, 0x30fc, 0x301c, 0xff01, 0xff0c, 0xff0e, 0xff1a, 0xff1b, 0xff1f, 0xff5e, 0xff65).toSortedSet() private val cjparenStarts = listOf(0x3008, 0x300A, 0x300C, 0x300E, 0x3010, 0x3014, 0x3016, 0x3018, 0x301A, 0x30fb, 0xff65).toSortedSet() @@ -856,10 +898,14 @@ class MovableType( private const val GLUE_NEGATIVE_ONE = 0xFFFE0 private const val GLUE_NEGATIVE_SIXTEEN = 0xFFFEF + 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 CodepointSequence.toReadable() = this.joinToString("") { if (it in 0x00..0x1f) "${(0x2400 + it).toChar()}" - else if (it == 0x20) + else if (it == 0x20 || it == 0xF0520) "\u2423" else if (it == NBSP) "{NBSP}" @@ -869,6 +915,18 @@ class MovableType( "{ZWSP}" else if (it in GLUE_NEGATIVE_ONE..GLUE_POSITIVE_SIXTEEN) " " + 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 diff --git a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt index 68b96db..cc73881 100755 --- a/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt +++ b/src/net/torvald/terrarumsansbitmap/gdx/TerrarumSansBitmap.kt @@ -33,6 +33,7 @@ import com.badlogic.gdx.utils.GdxRuntimeException import net.torvald.terrarumsansbitmap.DiacriticsAnchor import net.torvald.terrarumsansbitmap.GlyphProps import net.torvald.terrarumsansbitmap.MovableType +import net.torvald.terrarumsansbitmap.TypesettingStrategy import java.io.BufferedOutputStream import java.io.FileOutputStream import java.util.* @@ -510,22 +511,27 @@ class TerrarumSansBitmap( * This method alone will NOT draw the text to the screen, use [MovableType.draw]. */ fun typesetParagraph(batch: Batch, charSeq: CharSequence, targetWidth: Int): MovableType = - typesetParagraphNormalised(batch, normaliseStringForMovableType(charSeq), targetWidth.toFloat()) + typesetParagraphNormalised(batch, normaliseStringForMovableType(charSeq), targetWidth.toFloat(), TypesettingStrategy.JUSTIFIED) /** * Typesets given string and returns the typesetted results, with which the desired text can be drawn on the screen. * This method alone will NOT draw the text to the screen, use [MovableType.draw]. */ fun typesetParagraph(batch: Batch, charSeq: CharSequence, targetWidth: Float): MovableType = - typesetParagraphNormalised(batch, normaliseStringForMovableType(charSeq), targetWidth) + typesetParagraphNormalised(batch, normaliseStringForMovableType(charSeq), targetWidth, TypesettingStrategy.JUSTIFIED) + + fun typesetParagraphRaggedRight(batch: Batch, charSeq: CharSequence, targetWidth: Int): MovableType = + typesetParagraphNormalised(batch, normaliseStringForMovableType(charSeq), targetWidth.toFloat(), TypesettingStrategy.RAGGED_RIGHT) + fun typesetParagraphRaggedRight(batch: Batch, charSeq: CharSequence, targetWidth: Float): MovableType = + typesetParagraphNormalised(batch, normaliseStringForMovableType(charSeq), targetWidth, TypesettingStrategy.RAGGED_RIGHT) - private val nullType = MovableType(this, "".toCodePoints(2), 0, true) + private val nullType = MovableType(this, "".toCodePoints(2), 0, isNull = true) /** * Typesets given string and returns the typesetted results, with which the desired text can be drawn on the screen. * This method alone will NOT draw the text to the screen, use [MovableType.draw]. */ - fun typesetParagraphNormalised(batch: Batch, codepoints: CodepointSequence, targetWidth: Float): MovableType { + fun typesetParagraphNormalised(batch: Batch, codepoints: CodepointSequence, targetWidth: Float, strategy: TypesettingStrategy): MovableType { val charSeqNotBlank = codepoints.size > 0 // determine emptiness BEFORE you hack a null chars in val newCodepoints = codepoints @@ -535,7 +541,7 @@ class TerrarumSansBitmap( var cacheObj = getTypesetCache(charSeqHash) if (cacheObj == null || flagFirstRun) { - cacheObj = MovableType(this, codepoints, targetWidth.toInt()) + cacheObj = MovableType(this, codepoints, targetWidth.toInt(), strategy) addToTypesetCache(cacheObj) }