diff --git a/assets/mods/basegame/books/btex.xml b/assets/mods/basegame/books/btex.xml index b3625e784..6d582cb8f 100644 --- a/assets/mods/basegame/books/btex.xml +++ b/assets/mods/basegame/books/btex.xml @@ -107,7 +107,7 @@

This is the part where you actually write your body texts in. The body text can have the following tags:

-

By default, parts use majuscule Roman numerals and others use Arabic. Alternative styling for the part and the chapter can be defined using the Macro Definition.

+

By default, parts use majuscule Roman numerals and others use Arabic. Alternative styling for the part and the chapter can be defined using the Macro Definition.

Paragraph Styling
@@ -311,7 +311,7 @@ notified and will send the mail containing finished books to the player; if the process exits with errors, the mail containing details of the errors will be sent instead.

-

For this reason the “printing press” is not exposed to the player, they only get to interact with it +

For this reason, the “printing press” is not exposed to the player, they only get to interact with it indirectly through the “publisher” via mail.

diff --git a/assets/mods/basegame/books/btex_ko.xml b/assets/mods/basegame/books/btex_ko.xml index 475b3a959..ac4526f59 100644 --- a/assets/mods/basegame/books/btex_ko.xml +++ b/assets/mods/basegame/books/btex_ko.xml @@ -103,7 +103,7 @@

원고는 책의 진짜 본문이 담긴 부분을 말한다. 본문에는 다음의 태그를 사용할 수 있다.

-

기본값은, 부는 로마 숫자 대문자, 장·절은 아라비아 숫자를 사용한다. 부의 경우 영어로 “Part I”과 같이 찍히고, 이를 “제1절”로 변경하는 등의 심화된 스타일은 매크로 정의문에서 정의할 수 있다.

+

기본값은, 부는 로마 숫자 대문자, 장·절은 아라비아 숫자를 사용한다. 부의 경우 영어로 “Part I”과 같이 찍히고, 이를 “제1절”로 변경하는 등의 심화된 스타일은 매크로 정의문에서 정의할 수 있다.

문단 스타일
diff --git a/src/net/torvald/btex/BTeXDocViewer.kt b/src/net/torvald/btex/BTeXDocViewer.kt new file mode 100644 index 000000000..c717b9531 --- /dev/null +++ b/src/net/torvald/btex/BTeXDocViewer.kt @@ -0,0 +1,110 @@ +package net.torvald.btex + +import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.graphics.g2d.SpriteBatch +import net.torvald.terrarum.btex.BTeXDocument +import net.torvald.terrarum.ceilToInt +import net.torvald.terrarum.inUse + +/** + * Created by minjaesong on 2024-05-17. + */ +class BTeXDocViewer(val doc: BTeXDocument) { + + private val pageGap = 6 + private var currentPage = 0 + private val isTome = (doc.context == "tome") + + val pageCount = doc.pageIndices.endInclusive + 1 + + fun gotoPage(page: Int) { + val page = page.coerceIn(doc.pageIndices) + if (isTome) + currentPage = (page / 2) * 2 + else + currentPage = page + } + fun gotoIndex(id: String) { + gotoPage(doc.indexTable[id]!!) + } + + fun currentPageStr(): String { + // TODO non-tome + if (isTome) { + return if (currentPage == 0) + "1" + else + "${currentPage}-${currentPage+1}" + } + else { + return (currentPage + 1).toString() + } + } + + + /** + * @param x top-centre + * @param y top-centre + */ + fun render(batch: SpriteBatch, x: Float, y: Float) { + val x1 = if (isTome) + x.toInt() - pageGap/2 - doc.pageDimensionWidth + else + x.toInt() - doc.pageDimensionWidth / 2 + + val x2 = if (isTome) + x.toInt() + pageGap/2 + else + 0 + + val y = y.toInt() + + if (doc.isFinalised || doc.fromArchive) { + if (isTome) { + batch.color = Color.WHITE + + if (currentPage - 1 in doc.pageIndices) + doc.render(0f, batch, currentPage - 1, x1, y) + if (currentPage in doc.pageIndices) + doc.render(0f, batch, currentPage, x2, y) + } + else { + batch.color = Color.WHITE + + if (currentPage in doc.pageIndices) + doc.render(0f, batch, currentPage, x1, y) + } + } + } + + fun prevPage() { + if (isTome) { + currentPage = (currentPage - 2).coerceAtLeast(0) + } + else { + currentPage = (currentPage - 1).coerceAtLeast(0) + } + } + + fun nextPage() { + if (isTome) { + currentPage = (currentPage + 2).coerceAtMost(doc.pageIndices.endInclusive.toFloat().div(2f).ceilToInt().times(2)) + } + else { + currentPage = (currentPage + 1).coerceAtLeast(doc.pageIndices.endInclusive) + } + } + + fun gotoFirstPage() { + gotoPage(0) + } + + fun gotoLastPage() { + if (isTome) { + currentPage = doc.pageIndices.endInclusive.toFloat().div(2f).ceilToInt().times(2) + } + else { + gotoPage(doc.pageIndices.endInclusive) + } + } +} \ No newline at end of file diff --git a/src/net/torvald/btex/BTeXDocument.kt b/src/net/torvald/btex/BTeXDocument.kt index b03d115d8..ac9967825 100644 --- a/src/net/torvald/btex/BTeXDocument.kt +++ b/src/net/torvald/btex/BTeXDocument.kt @@ -6,6 +6,7 @@ import com.badlogic.gdx.graphics.* import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.utils.Disposable +import net.torvald.btex.BTeXDocViewer import net.torvald.terrarum.ceilToInt import net.torvald.terrarum.concurrent.ThreadExecutor import net.torvald.terrarum.imagefont.TinyAlphNum @@ -55,6 +56,8 @@ class BTeXDocument : Disposable { var endOfPageStart = 2147483647 var tocPageStart = 2 + val indexTable = HashMap() + companion object { val DEFAULT_PAGE_BACK = Color(0xe0dfdb_ff.toInt()) // val DEFAULT_PAGE_FORE = Color(0x0a0706_ff) @@ -260,12 +263,6 @@ class BTeXDocument : Disposable { * * `currentLine` *will* be updated automatically. */ - fun appendDrawCall(drawCall: BTeXDrawCall) { - pages.last().appendDrawCall(drawCall) - - linesPrintedOnPage[linesPrintedOnPage.lastIndex] += drawCall.lineCount - } - fun appendDrawCall(page: BTeXPage, drawCall: BTeXDrawCall) { page.appendDrawCall(drawCall) @@ -273,6 +270,10 @@ class BTeXDocument : Disposable { linesPrintedOnPage[pagenum] += drawCall.lineCount } + fun appendClickable(page: BTeXPage, clickable: BTeXClickable) { + page.appendClickable(clickable) + } + fun render(frameDelta: Float, batch: SpriteBatch, page: Int, x: Int, y: Int) { batch.color = Color.WHITE @@ -330,16 +331,26 @@ class BTeXDocument : Disposable { } } +data class BTeXClickable( + val posX: Int, val posY: Int, val width: Int, val height: Int, + val onClick: (BTeXDocViewer) -> Unit, +// val onHover: () -> Unit = {} +) + class BTeXPage( val back: Color, val width: Int, val height: Int, ) { internal val drawCalls = ArrayList() + internal val clickableElements = ArrayList() fun appendDrawCall(drawCall: BTeXDrawCall) { if (drawCall.isNotBlank()) drawCalls.add(drawCall) } + fun appendClickable(clickable: BTeXClickable) { + clickableElements.add(clickable) + } private var prerender = false @@ -357,9 +368,24 @@ class BTeXPage( } } + fun touchDown(viewer: BTeXDocViewer, screenX: Int, screenY: Int, pointer: Int, button: Int) { + val pageRelX = screenX - drawX + val pageRelY = screenY - drawY + // filter clickable elements that are under the cursor + clickableElements.filter { + pageRelX in it.posX until it.posX+it.width && + pageRelY in it.posY until it.posY+it.height + }.forEach { it.onClick(viewer) } + } + fun isEmpty() = drawCalls.isEmpty() fun isNotEmpty() = drawCalls.isNotEmpty() + + private var drawX = 0 + private var drawY = 0 + fun renderToPixmap(pixmap: Pixmap, x: Int, y: Int, marginH: Int, marginV: Int) { + drawX = x; drawY = y drawCalls.sortedBy { if (it.text != null) 16 else 0 }.let { drawCalls -> val backCol = back.cpy().also { it.a = 0.93f } pixmap.setColor(backCol) diff --git a/src/net/torvald/btex/BTeXParser.kt b/src/net/torvald/btex/BTeXParser.kt index d289c073e..da1763f24 100644 --- a/src/net/torvald/btex/BTeXParser.kt +++ b/src/net/torvald/btex/BTeXParser.kt @@ -12,11 +12,8 @@ import com.jme3.math.FastMath.DEG_TO_RAD import net.torvald.colourutil.OKLch import net.torvald.colourutil.tosRGB import net.torvald.terrarum.* -import net.torvald.terrarum.btex.BTeXBatchDrawCall -import net.torvald.terrarum.btex.BTeXDocument +import net.torvald.terrarum.btex.* import net.torvald.terrarum.btex.BTeXDocument.Companion.DEFAULT_ORNAMENTS_COL -import net.torvald.terrarum.btex.BTeXDrawCall -import net.torvald.terrarum.btex.TypesetDrawCall import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.ui.Toolkit @@ -88,6 +85,7 @@ object BTeXParser { private var tagHistory = ArrayList() private var currentHrefId: String? = null // any Unicode string that is not empty + private var oldHrefTarget: String? = null private var currentTheme = "" @@ -104,6 +102,7 @@ object BTeXParser { } private val objDict = HashMap BTeXBatchDrawCall>() + private val hrefDict = HashMap() private val objWidthDict = HashMap() private var lastTagAtDepth = Array(24) { "" } @@ -119,7 +118,6 @@ object BTeXParser { private val cptSectStack = ArrayList() - private val indexMap = HashMap() // id to pagenum private val cptSectMap = ArrayList() private var tocPage: Int? = null @@ -335,10 +333,6 @@ object BTeXParser { // printdbg(" End element \t($popped)") } - private var oldSpanColour: String? = null - private var oldCodeMode = false - private var oldHrefMode = false - private var oldBucksMode = false private val CODE_TAG_MARGIN = 2 private val CODEMODE_BEGIN = "${spacingBlockToString(CODE_TAG_MARGIN)}$ccCode${TerrarumSansBitmap.charsetOverrideCodestyle}" @@ -365,12 +359,50 @@ object BTeXParser { ) } + private val REGEX_WHITESPACES = Regex("\\s+") + override fun characters(ch: CharArray, start: Int, length: Int) { var str = String(ch.sliceArray(start until start + length)).replace('\n', ' ').replace(Regex(" +"), " ")//.trim() if (str.isNotEmpty()) { -// printdbg("Characters [col:${spanColour}] \t\"$str\"") + + + // rising/falling edge of the hrefId + if (currentHrefId != oldHrefTarget) { + // rising edge + if (currentHrefId != null) { + printdbg("Href IN($currentHrefId) \t\"$str\"") + + // put OBJ on every word, separated by whitespaces + // transform the word such that: + // word1 word2 -> [OBJ:XXX]word1 [OBJ:YYY]word2 + str = str.trim().split(" ").map { + val wordWidth = getFont().getWidth(it) + val btexObjName = "HREF@${makeRandomObjName()}" + + hrefDict[btexObjName] = currentHrefId!! + + objectMarkerWithWidth(btexObjName, 0) + it + }.joinToString(" ") + + } + // falling edge + else { + printdbg("Href OUT(null) \t\"$str\"") + } + } + // hrefId held high + else if (currentHrefId != null) { + printdbg("Href($currentHrefId) \t\"$str\"") + } + else { + printdbg("String \t\"$str\"") + } + + + + oldHrefTarget = currentHrefId paragraphBuffer.append(str) } } @@ -725,6 +757,9 @@ object BTeXParser { @CloseTag fun closeElemA(handler: BTeXHandler, doc: BTeXDocument, uri: String, siblingIndex: Int) { paragraphBuffer.append(HREF_END) + + + currentHrefId = null } @@ -776,8 +811,8 @@ object BTeXParser { // prepare contents val pageWidth = doc.textWidth - indexMap.keys.toList().sorted().forEach { key -> - typesetTOCline("", key, indexMap[key]!! - 1, handler) + doc.indexTable.keys.toList().sorted().forEach { key -> + typesetTOCline("", key, doc.indexTable[key]!! - 1, handler) } } @@ -792,7 +827,7 @@ object BTeXParser { @CloseTag fun closeElemMANUSCRIPT(handler: BTeXHandler, doc: BTeXDocument, uri: String, siblingIndex: Int) { - // if tocPage != null, estimate TOC page size, renumber indexMap and cptSectMap if needed, then typeset the toc + // if tocPage != null, estimate TOC page size, renumber doc.indexTable and cptSectMap if needed, then typeset the toc if (tocPage != null) { // estimate the number of TOC pages // TOC page always takes up a full paper, therefore tocSizeInPages is always multiple of 2 @@ -800,13 +835,13 @@ object BTeXParser { if (tocSizeInPages == 0) tocSizeInPages = 2 if (tocSizeInPages % 2 == 1) tocSizeInPages += 1 - println("TOC number of entries: ${cptSectMap.size}, estimated page count: $tocSizeInPages") +// printdbg("TOC number of entries: ${cptSectMap.size}, estimated page count: $tocSizeInPages") // renumber things if (tocSizeInPages > 1) { val pageDelta = tocSizeInPages - 1 - indexMap.keys.forEach { - indexMap[it] = indexMap[it]!! + pageDelta + doc.indexTable.keys.forEach { + doc.indexTable[it] = doc.indexTable[it]!! + pageDelta } cptSectMap.forEach { it.pagenum += pageDelta } @@ -841,7 +876,7 @@ object BTeXParser { @OpenTag // reflective access is impossible with 'private' fun processElemINDEX(handler: BTeXHandler, doc: BTeXDocument, uri: String, attribs: HashMap) { attribs["id"]?.let { - indexMap[it] = doc.currentPage + 1 + doc.indexTable[it] = doc.currentPage + 1 } } @@ -1614,6 +1649,18 @@ object BTeXParser { doc.appendDrawCall(doc.pages[pageNum], it); drawCalls.add(it) } } + val hrefs = parseAndGetHref(textDrawCalls[0], font, handler, posYline, slugs, subset.first, subset.second) + hrefs.forEach { + // search for: + // ..... [OBJ:RSETNFAOON]word setaf + // get width of "word" + val searchStr = slugs.typesettedSlugs.subList(subset.first, subset.first + subset.second) + printdbg("HREF searchStr: ${searchStr.joinToString { it.toReadable() }}") +// val clickable = BTeXClickable(it.x, it.y, undefined, doc.lineHeightInPx) { viewer -> +// viewer.gotoIndex(it.hrefTarget) +// } +// doc.appendClickable(doc.pages[pageNum], it) + } linesOut += remainder slugHeight -= remainder @@ -1634,6 +1681,13 @@ object BTeXParser { doc.appendDrawCall(doc.pages[pageNum], it); drawCalls.add(it) } } + // >>> HREF code here!! <<< + val hrefs = parseAndGetHref(textDrawCalls[0], font, handler, posYline, slugs, subset.first, subset.second) + hrefs.forEach { + val searchStr = slugs.typesettedSlugs.subList(subset.first, subset.first + subset.second) + printdbg("HREF searchStr: ${searchStr.joinToString { it.toReadable() }}") + } + // >>> HREF code ends here!! <<< linesOut += remainder slugHeight -= remainder @@ -1652,10 +1706,7 @@ object BTeXParser { private fun textToDrawCall(handler: BTeXHandler, posYline: Int, slugs: MovableType, lineStart: Int, lineCount: Int): List { return listOf( BTeXDrawCall( - doc, - 0, - posYline * doc.lineHeightInPx, - currentTheme, + doc, 0, posYline * doc.lineHeightInPx, currentTheme, TypesetDrawCall(slugs, lineStart, lineCount) ) ) @@ -1688,16 +1739,51 @@ object BTeXParser { c += 1 } - val extraDrawCall = BTeXDrawCall( - doc, - x, - y, - currentTheme, - cmd = objDict[idbuf.toString()]?.invoke(textDrawCall) ?: throw NullPointerException("No OBJ with id '$idbuf' exists"), - font = font, - ) + if (!idbuf.startsWith("HREF@")) { + out.add(BTeXDrawCall( + doc, x, y, currentTheme, + cmd = objDict[idbuf.toString()]?.invoke(textDrawCall) + ?: throw NullPointerException("No OBJ with id '$idbuf' exists"), + font = font, + )) + } + } + } - out.add(extraDrawCall) + return out + } + + private fun parseAndGetHref( + textDrawCall: BTeXDrawCall, + font: TerrarumSansBitmap, + handler: BTeXHandler, + posYline: Int, + slugs: MovableType, + lineStart: Int, + lineCount: Int + ): List<_HrefObject> { + val out = ArrayList<_HrefObject>() + + slugs.typesettedSlugs.subList(lineStart, lineStart + lineCount).forEachIndexed { lineNumCnt, line -> + line.mapIndexed { i, c -> i to c }.filter { it.second == OBJ }.map { it.first }.forEach { xIndex -> + val x = font.getWidthNormalised(CodepointSequence(line.subList(0, xIndex))) + val y = (posYline + lineNumCnt) * doc.lineHeightInPx + + // get OBJ id + val idbuf = StringBuilder() + + var c = xIndex + 1 + while (true) { + val codepoint = line[c] + if (codepoint == 0xFFF9F) break + idbuf.append(codepointToObjIdChar(codepoint)) + c += 1 + } + + if (idbuf.startsWith("HREF@")) { + val id = hrefDict[idbuf.toString()]!! + out.add(_HrefObject(x, y, id)) + } } } @@ -1788,6 +1874,7 @@ object BTeXParser { doc.addNewPage() } + private data class _HrefObject(val x: Int, val y: Int, val hrefTarget: String) companion object { init { diff --git a/src/net/torvald/terrarum/tests/BTeXTest.kt b/src/net/torvald/terrarum/tests/BTeXTest.kt index 5534b533f..8717590d1 100644 --- a/src/net/torvald/terrarum/tests/BTeXTest.kt +++ b/src/net/torvald/terrarum/tests/BTeXTest.kt @@ -10,6 +10,7 @@ import com.badlogic.gdx.graphics.OrthographicCamera import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.graphics.glutils.ShaderProgram +import net.torvald.btex.BTeXDocViewer import net.torvald.btex.BTeXParser import net.torvald.terrarum.* import net.torvald.terrarum.btex.BTeXDocument @@ -94,42 +95,44 @@ class BTeXTest : ApplicationAdapter() { } } - private var scroll = 0 + var init = false + private lateinit var viewer: BTeXDocViewer - val pageGap = 6 + private val drawY = 24 override fun render() { Gdx.graphics.setTitle("BTeXTest $EMDASH F: ${Gdx.graphics.framesPerSecond}") gdxClearAndEnableBlend(.063f, .070f, .086f, 1f) + if (::document.isInitialized) { - if (document.isFinalised || document.fromArchive) { - val drawX = (1280 - (pageGap + document.pageDimensionWidth * 2)) / 2 - val drawY = 24 - + if (!init) { + init = true + viewer = BTeXDocViewer(document) + } + else { batch.inUse { - batch.color = Color.WHITE batch.draw(bg, 0f, 0f) + viewer.render(batch, 640f, drawY.toFloat()) - if (scroll - 1 in document.pageIndices) - document.render(0f, batch, scroll - 1, drawX, drawY) - if (scroll in document.pageIndices) - document.render(0f, batch, scroll, drawX + (6 + document.pageDimensionWidth), drawY) + batch.color = Color.WHITE + val pageText = "${viewer.currentPageStr()}/${viewer.pageCount}" + Toolkit.drawTextCentered( + batch, TinyAlphNum, pageText, + 1280, 0, drawY + document.pageDimensionHeight + 12 + ) } - + // control if (Gdx.input.isKeyJustPressed(Input.Keys.LEFT)) - scroll = (scroll - 2).coerceAtLeast(0) + viewer.prevPage() else if (Gdx.input.isKeyJustPressed(Input.Keys.RIGHT)) - scroll = - (scroll + 2).coerceAtMost( - document.pageIndices.endInclusive.toFloat().div(2f).ceilToInt().times(2) - ) + viewer.nextPage() else if (Gdx.input.isKeyJustPressed(Input.Keys.PAGE_UP)) - scroll = 0 + viewer.gotoFirstPage() else if (Gdx.input.isKeyJustPressed(Input.Keys.PAGE_DOWN)) - scroll = document.pageIndices.endInclusive.toFloat().div(2f).ceilToInt().times(2) + viewer.gotoLastPage() } } }