From 162b7862bcbb39f8cb6a0b7ad3ffc2be7cb75412 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Wed, 8 May 2024 19:10:31 +0900 Subject: [PATCH] btex: ul/ol impl --- assets/mods/basegame/books/btex.xml | 2 +- assets/mods/basegame/books/btex_ko.xml | 2 +- assets/mods/basegame/books/test.xml | 16 +- src/net/torvald/btex/BTeXParser.kt | 182 +++++++++++++++++---- src/net/torvald/terrarum/tests/BTeXTest.kt | 4 +- 5 files changed, 172 insertions(+), 34 deletions(-) diff --git a/assets/mods/basegame/books/btex.xml b/assets/mods/basegame/books/btex.xml index a0b3c5be4..c3ac5abce 100644 --- a/assets/mods/basegame/books/btex.xml +++ b/assets/mods/basegame/books/btex.xml @@ -109,7 +109,7 @@
  • emph — is a special case of the span tag. The resulting text will be red
  • itemname — is a special case of the span tag used to highlight the name of the ingame item. The resulting text will be blue
  • targetname — is a special case of the span tag used to highlight the name of an arbitrary target or goals. The resulting text will be green
  • -
  • code — is a special case of the span tag used to highlight the code element in-line. The resulting text will be surrounded in a grey box
  • +
  • code — is a special case of the span tag used to highlight the code element in-line. The resulting text will be teal and monospaced
  • br — self-closing tag; inserts an anonymous line break
  • newpage — self-closing tag; inserts an anonymous page break
  • anonbreak — self-closing tag; inserts a paragraph break in the text. The break will be in a form of a long straight line on the centre of the text. Useful for typesetting novels
  • diff --git a/assets/mods/basegame/books/btex_ko.xml b/assets/mods/basegame/books/btex_ko.xml index caf211f24..cb4157c90 100644 --- a/assets/mods/basegame/books/btex_ko.xml +++ b/assets/mods/basegame/books/btex_ko.xml @@ -99,7 +99,7 @@
  • emph — is a special case of the span tag. The resulting text will be red
  • itemname — is a special case of the span tag used to highlight the name of the ingame item. The resulting text will be blue
  • targetname — is a special case of the span tag used to highlight the name of an arbitrary target or goals. The resulting text will be green
  • -
  • code — is a special case of the span tag used to highlight the code element in-line. The resulting text will be surrounded in a grey box
  • +
  • code — is a special case of the span tag used to highlight the code element in-line. The resulting text will be teal and monospaced
  • br — self-closing tag; inserts an anonymous line break
  • newpage — self-closing tag; inserts an anonymous page break
  • anonbreak — self-closing tag; inserts a paragraph break in the text. The break will be in a form of a long straight line on the centre of the text. Useful for typesetting novels
  • diff --git a/assets/mods/basegame/books/test.xml b/assets/mods/basegame/books/test.xml index e67006a2e..d88c554e5 100644 --- a/assets/mods/basegame/books/test.xml +++ b/assets/mods/basegame/books/test.xml @@ -2,7 +2,7 @@ The Style Declaration -

    Terrarum version . The transaction fee is

    +

    Terrarum version . The transaction fee is

    The Style Declaration is the very first line of a document. Its syntax is as follows:

    The btexdoc tag takes one attribute: cover.

    The btexdoc tag takes two attributes: cover and inner.

    @@ -15,6 +15,20 @@ Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph Long callout paragraph

    Yet another paragraph, another Lorem Ipsum. Eget duis at tellus at urna. Eget sit amet tellus cras adipiscing enim eu. Sit amet tellus cras adipiscing enim eu turpis egestas. Aliquam faucibus purus in massa tempor. Eget nullam non nisi est sit amet. Eu ultrices vitae auctor eu augue ut lectus. Tortor at auctor urna nunc. Quam lacus suspendisse faucibus interdum posuere lorem ipsum.

    +
      +
    • li
    • +
    • lii
    • +
    • liii
    • +
    • liiii
    • +
    +

    Peeeeee

    +
      +
    1. li
    2. +
    3. lii
    4. +
    5. liii
    6. +
    7. liiii
    8. +
    +
    diff --git a/src/net/torvald/btex/BTeXParser.kt b/src/net/torvald/btex/BTeXParser.kt index 7a4c355ce..91cb2ef91 100644 --- a/src/net/torvald/btex/BTeXParser.kt +++ b/src/net/torvald/btex/BTeXParser.kt @@ -100,7 +100,11 @@ object BTeXParser { private val objWidthDict = HashMap() private var lastTagAtDepth = Array(24) { "" } - private var pTagCntAtDepth = IntArray(24) + private var tagCntAtDepth: HashMap = HashMap().also { map -> + siblingAwareTags.forEach { tag -> + map[tag] = IntArray(24) + } + } private data class CptSect(val type: String, var alt: String?, var style: String, var start: Int? = null) private data class CptSectInfo(val type: String, var name: String, var pagenum: Int, @@ -252,10 +256,14 @@ object BTeXParser { if (tagStack.isNotEmpty() && !textTags.contains(tagStack.last()) && textDecorTags.contains(theTag)) throw IllegalStateException("Text decoration tag '$theTag' used outside of a text tag (tag stack is ${tagStack.joinToString()}, $theTag)") - if (lastTagAtDepth[tagStack.size] != "P") pTagCntAtDepth[tagStack.size] = 0 - if (theTag == "P") pTagCntAtDepth[tagStack.size] += 1 + + siblingAwareTags.forEach { tag -> + if (lastTagAtDepth[tagStack.size] != tag) tagCntAtDepth[tag]!![tagStack.size] = 0 + if (theTag == tag) tagCntAtDepth[tag]!![tagStack.size] += 1 + } lastTagAtDepth[tagStack.size] = theTag + tagStack.add(theTag) tagHistory.add(theTag) @@ -290,7 +298,8 @@ object BTeXParser { elemClosers["closeElem$theTag"].let { try { - it?.call(this, this, doc, uri, pTagCntAtDepth[tagStack.size]) + val siblingIndex = tagCntAtDepth[theTag]?.get(tagStack.size) ?: -1 + it?.call(this, this, doc, uri, siblingIndex) } catch (e: Throwable) { throw BTeXParsingException(e.stackTraceToString()) @@ -306,8 +315,8 @@ object BTeXParser { private var oldBucksMode = false private val CODE_TAG_MARGIN = 2 - private val CODEMODE_BEGIN = "${glueToString(CODE_TAG_MARGIN)}$ccCode${TerrarumSansBitmap.charsetOverrideCodestyle}" - private val CODEMODE_END = "${TerrarumSansBitmap.charsetOverrideDefault}$ccDefault${glueToString(CODE_TAG_MARGIN)}" + private val CODEMODE_BEGIN = "${spacingBlockToString(CODE_TAG_MARGIN)}$ccCode${TerrarumSansBitmap.charsetOverrideCodestyle}" + private val CODEMODE_END = "${TerrarumSansBitmap.charsetOverrideDefault}$ccDefault${spacingBlockToString(CODE_TAG_MARGIN)}" private val HREF_BEGIN = "$ccHref" private val HREF_END = "$ccDefault" @@ -788,11 +797,11 @@ object BTeXParser { val heading = if (part == null && cpt == null && sect == null) "" else if (part != null && cpt == null && sect == null) - "${invokeMacro("thepart", part)}${glueToString(HEADING_NUM_TITLE_GAP)}" + "${invokeMacro("thepart", part)}${spacingBlockToString(HEADING_NUM_TITLE_GAP)}" else if (cpt != null && sect == null) - "${invokeMacro("thechapter", cpt)}${glueToString(HEADING_NUM_TITLE_GAP)}" + "${invokeMacro("thechapter", cpt)}${spacingBlockToString(HEADING_NUM_TITLE_GAP)}" else - "$cpt.$sect${glueToString(HEADING_NUM_TITLE_GAP)}" + "$cpt.$sect${spacingBlockToString(HEADING_NUM_TITLE_GAP)}" typesetTOCline("$heading", name, pg, handler, indent, tocPage) } @@ -955,7 +964,13 @@ object BTeXParser { doc.linesPrintedOnPage[doc.currentPage] += 2 - typesetParagraphs("$ccDefault$thePar", handler, width = doc.textWidth - 2*MARGIN_PARBOX_H, align = currentAlign).forEachIndexed { index, it -> + typesetParagraphs( + "$ccDefault$thePar", + handler, + width = doc.textWidth - 2*MARGIN_PARBOX_H, + height = doc.pageLines - 1, + align = currentAlign + ).forEachIndexed { index, it -> it.posX += MARGIN_PARBOX_H it.deltaX += MARGIN_PARBOX_H @@ -990,8 +1005,56 @@ object BTeXParser { } } - if (doc.linesPrintedOnPage[doc.currentPage] < doc.pageLines) - doc.linesPrintedOnPage[doc.currentPage] += 1 + insertOneEmptyLineOrAddNewPage() + + clearParBuffer() + } + + @OpenTag // reflective access is impossible with 'private' + fun processElemUL(handler: BTeXHandler, doc: BTeXDocument, uri: String, attribs: HashMap) { + clearParBuffer() + } + @OpenTag // reflective access is impossible with 'private' + fun processElemOL(handler: BTeXHandler, doc: BTeXDocument, uri: String, attribs: HashMap) { + clearParBuffer() + } + + @CloseTag + fun closeElemUL(handler: BTeXHandler, doc: BTeXDocument, uri: String, siblingIndex: Int) { + insertOneEmptyLineOrAddNewPage() + } + @CloseTag + fun closeElemOL(handler: BTeXHandler, doc: BTeXDocument, uri: String, siblingIndex: Int) { + insertOneEmptyLineOrAddNewPage() + } + + @CloseTag // reflective access is impossible with 'private' + fun closeElemLI(handler: BTeXHandler, doc: BTeXDocument, uri: String, siblingIndex: Int) { + // if this P is a very first P without chapters, leave two lines before typesetting + val mode = tagStack.last() + val thePar = paragraphBuffer.toString().trim() + + if (mode != "UL" && mode != "OL") throw IllegalStateException("Unknown mode for LI: $mode") + + val heading = if (mode == "UL") + "•${spacingBlockToString(9)}" + else { + "$siblingIndex.${spacingBlockToString(9)}" + } + + typesetParagraphs( + "$ccDefault$heading$thePar", + handler, + width = doc.textWidth - 2*MARGIN_PARBOX_H, + height = doc.pageLines - 1, + align = currentAlign + ).forEachIndexed { index, it -> + it.posX += MARGIN_PARBOX_H + it.deltaX += MARGIN_PARBOX_H + + it.posY += doc.lineHeightInPx / 2 + it.deltaY += doc.lineHeightInPx / 2 + } clearParBuffer() } @@ -1195,7 +1258,13 @@ object BTeXParser { } private fun typesetBookAuthor(thePar: String, handler: BTeXHandler) { - typesetParagraphs(getSubtitleFont(), "\n\n${TerrarumSansBitmap.toColorCode(15, 15, 15)}" + thePar, handler, doc.textWidth - 2*MARGIN_TITLE_TEXTS, align = "left").also { + typesetParagraphs( + getSubtitleFont(), + "\n\n${TerrarumSansBitmap.toColorCode(15, 15, 15)}" + thePar, + handler, + doc.textWidth - 2*MARGIN_TITLE_TEXTS, + align = "left" + ).also { it.last().extraDrawFun = { batch, x, y -> val px = x val py = y + doc.lineHeightInPx - 1 @@ -1213,7 +1282,13 @@ object BTeXParser { } private fun typesetBookEdition(thePar: String, handler: BTeXHandler) { - typesetParagraphs(getSubtitleFont(), "${TerrarumSansBitmap.toColorCode(15, 15, 15)}" + thePar, handler, doc.textWidth - 2*MARGIN_TITLE_TEXTS, align = "left").also { + typesetParagraphs( + getSubtitleFont(), + "${TerrarumSansBitmap.toColorCode(15, 15, 15)}" + thePar, + handler, + doc.textWidth - 2*MARGIN_TITLE_TEXTS, + align = "left" + ).also { it.forEach { it.posX += MARGIN_TITLE_TEXTS } @@ -1270,7 +1345,7 @@ object BTeXParser { } private fun typesetChapterHeading(num: String?, thePar: String, handler: BTeXHandler, indent: Int = 16, width: Int = doc.textWidth) { - val header = if (num == null) thePar else "$num${glueToString(9)}$thePar" + val header = if (num == null) thePar else "$num${spacingBlockToString(9)}$thePar" typesetParagraphs("\n$ccDefault$header", handler, width - indent, align = "left").also { // add indents and adjust text y pos it.forEach { @@ -1282,9 +1357,19 @@ object BTeXParser { it.extraDrawFun = { batch, x, y -> val oldCol = batch.color.cpy() batch.color = DEFAULT_ORNAMENTS_COL.cpy().also { it.a *= bodyTextShadowAlpha } - Toolkit.fillArea(batch, x - (indent - 2), y + doc.lineHeightInPx, 7f, 1 + (it.lineCount - 1).coerceAtLeast(1) * doc.lineHeightInPx.toFloat()) + Toolkit.fillArea(batch, + x - (indent - 2), + y + doc.lineHeightInPx, + 7f, + 1 + (it.lineCount - 1).coerceAtLeast(1) * doc.lineHeightInPx.toFloat() + ) batch.color = DEFAULT_ORNAMENTS_COL - Toolkit.fillArea(batch, x - (indent - 2), y + doc.lineHeightInPx, 6f, (it.lineCount - 1).coerceAtLeast(1) * doc.lineHeightInPx.toFloat()) + Toolkit.fillArea(batch, + x - (indent - 2), + y + doc.lineHeightInPx, + 6f, + (it.lineCount - 1).coerceAtLeast(1) * doc.lineHeightInPx.toFloat() + ) batch.color = oldCol } } @@ -1292,7 +1377,7 @@ object BTeXParser { } private fun typesetSectionHeading(num: String, thePar: String, handler: BTeXHandler, indent: Int = 16, width: Int = doc.textWidth) { - typesetParagraphs("\n$ccDefault$num${glueToString(9)}$thePar", handler, width - indent, align = "left").also { + typesetParagraphs("\n$ccDefault$num${spacingBlockToString(9)}$thePar", handler, width - indent, align = "left").also { // add indents and adjust text y pos it.forEach { it.posX += indent @@ -1301,11 +1386,26 @@ object BTeXParser { } } - private fun typesetParagraphs(thePar: String, handler: BTeXHandler, width: Int = doc.textWidth, startingPage: Int = doc.currentPage, align: String): List { - return typesetParagraphs(getFont(), thePar, handler, width, startingPage, align) + private fun typesetParagraphs( + thePar: String, + handler: BTeXHandler, + width: Int = doc.textWidth, + height: Int = doc.pageLines, + startingPage: Int = doc.currentPage, + align: String + ): List { + return typesetParagraphs(getFont(), thePar, handler, width, height, startingPage, align) } - private fun typesetParagraphs(font: TerrarumSansBitmap, thePar: String, handler: BTeXHandler, width: Int = doc.textWidth, startingPage: Int = doc.currentPage, align: String): List { + private fun typesetParagraphs( + font: TerrarumSansBitmap, + thePar: String, + handler: BTeXHandler, + width: Int = doc.textWidth, + height: Int = doc.pageLines, + startingPage: Int = doc.currentPage, + align: String + ): List { val strat = when (align) { "left" -> TypesettingStrategy.RAGGED_RIGHT "justify" -> TypesettingStrategy.JUSTIFIED @@ -1316,16 +1416,21 @@ object BTeXParser { val drawCalls = ArrayList() - var remainder = doc.pageLines - doc.linesPrintedOnPage.last() + var remainder = height - doc.linesPrintedOnPage.last() var slugHeight = slugs.height var linesOut = 0 // printdbg("Page: ${doc.currentPage+1}, Line: ${doc.currentLine}") - if (slugHeight > remainder) { + if (remainder <= 0) { + doc.addNewPage(); pageNum += 1 + } + else if (slugHeight > remainder) { val subset = linesOut to remainder val posYline = doc.linesPrintedOnPage[pageNum] + println("typeset par slugHeight=$slugHeight, remainder=$remainder, linesOut=$linesOut") + val textDrawCalls = textToDrawCall(handler, posYline, slugs, subset.first, subset.second) val objectDrawCalls = parseAndGetObjDrawCalls(textDrawCalls[0], font, handler, posYline, slugs, subset.first, subset.second) (textDrawCalls + objectDrawCalls).let { @@ -1341,7 +1446,7 @@ object BTeXParser { } while (slugHeight > 0) { - remainder = minOf(slugHeight, doc.pageLines) + remainder = minOf(slugHeight, height) val subset = linesOut to remainder val posYline = doc.linesPrintedOnPage[pageNum] @@ -1357,7 +1462,7 @@ object BTeXParser { linesOut += remainder slugHeight -= remainder - if (remainder == doc.pageLines) { + if (remainder == height) { doc.addNewPage(); pageNum += 1 } } @@ -1380,7 +1485,15 @@ object BTeXParser { ) } - private fun parseAndGetObjDrawCalls(textDrawCall: BTeXDrawCall, font: TerrarumSansBitmap, handler: BTeXHandler, posYline: Int, slugs: MovableType, lineStart: Int, lineCount: Int): List { + private fun parseAndGetObjDrawCalls( + textDrawCall: BTeXDrawCall, + font: TerrarumSansBitmap, + handler: BTeXHandler, + posYline: Int, + slugs: MovableType, + lineStart: Int, + lineCount: Int + ): List { val out = ArrayList() slugs.typesettedSlugs.subList(lineStart, lineStart + lineCount).forEachIndexed { lineNumCnt, line -> @@ -1439,7 +1552,7 @@ object BTeXParser { val dotGap = 10 val dotPosEnd = typeWidth - pageNumWidth - dotGap*1.5f - typesetParagraphs("$ccDefault$heading$name", handler, typeWidth, pageToWrite ?: doc.currentPage, align = "justify").let { + typesetParagraphs("$ccDefault$heading$name", handler, typeWidth, startingPage = pageToWrite ?: doc.currentPage, align = "justify").let { it.forEach { it.posX += indentation @@ -1478,8 +1591,19 @@ object BTeXParser { } } + private fun insertOneEmptyLineOrAddNewPage() { + if (doc.linesPrintedOnPage[doc.currentPage] < doc.pageLines) + doc.linesPrintedOnPage[doc.currentPage] += 1 + else + doc.addNewPage() + } + companion object { + private val siblingAwareTags = arrayOf( + "PART","CHAPTER","SECTION","SUBSECTION","P","I","LI" + ) + private const val MARGIN_PARBOX_V = 4 private const val MARGIN_PARBOX_H = 12 private const val MARGIN_TITLE_TEXTS = 8 @@ -1501,7 +1625,7 @@ object BTeXParser { private const val SPACING_BLOCK_ONE = 0xFFFD0 private const val SPACING_BLOCK_SIXTEEN = 0xFFFDF - fun glueToString(glue: Int): String { + fun spacingBlockToString(glue: Int): String { val tokens = CodepointSequence() if (glue < 0) @@ -1544,7 +1668,7 @@ object BTeXParser { idstr.add(0xFFF9F) - return "\uFFFC" + idstr.toUTF8Bytes().toString(Charsets.UTF_8) + glueToString(width) + return "\uFFFC" + idstr.toUTF8Bytes().toString(Charsets.UTF_8) + spacingBlockToString(width) } fun Int.toRomanNum(): String = when (this) { diff --git a/src/net/torvald/terrarum/tests/BTeXTest.kt b/src/net/torvald/terrarum/tests/BTeXTest.kt index 4382a941a..7309134b4 100644 --- a/src/net/torvald/terrarum/tests/BTeXTest.kt +++ b/src/net/torvald/terrarum/tests/BTeXTest.kt @@ -24,8 +24,8 @@ import kotlin.system.measureTimeMillis */ class BTeXTest : ApplicationAdapter() { -// val filePath = "btex.xml" - val filePath = "test.xml" + val filePath = "btex.xml" +// val filePath = "test.xml" // val filePath = "literature/en/daniel_defoe_robinson_crusoe.xml" // val filePath = "literature/ruRU/anton_chekhov_palata_no_6.xml" // val filePath = "literature/koKR/yisang_nalgae.xml"