diff --git a/assets/mods/basegame/books/btex.xml b/assets/mods/basegame/books/btex.xml index d67ad18e9..ca1a08e30 100644 --- a/assets/mods/basegame/books/btex.xml +++ b/assets/mods/basegame/books/btex.xml @@ -5,7 +5,7 @@ Test Edition - + diff --git a/lib/TerrarumSansBitmap.jar b/lib/TerrarumSansBitmap.jar index 93568aa88..8a3c1b61a 100644 --- a/lib/TerrarumSansBitmap.jar +++ b/lib/TerrarumSansBitmap.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73281a8cb58252a6b66b7a97c26a9d962609b0c25abee0df0d9acc2407008e30 -size 189535 +oid sha256:8a412868241bc2802e116302d28a934b0d7421d442a8423aa53d341a7d82b9f9 +size 189694 diff --git a/src/net/torvald/btex/BTeXDocument.kt b/src/net/torvald/btex/BTeXDocument.kt index c510be69f..06da76e03 100644 --- a/src/net/torvald/btex/BTeXDocument.kt +++ b/src/net/torvald/btex/BTeXDocument.kt @@ -73,7 +73,7 @@ class BTeXPage( val width: Int, val height: Int, ) { - private val drawCalls = ArrayList() + internal val drawCalls = ArrayList() fun appendDrawCall(drawCall: BTeXDrawCall) { if (drawCall.isNotBlank()) drawCalls.add(drawCall) @@ -150,7 +150,16 @@ class BTeXDrawCall( return true } - internal var extraDrawFun: (SpriteBatch, Float, Float) -> Unit = { _,_,_ ->} + internal val width: Int + get() = if (text != null) + if (text is MovableTypeDrawCall) + text.movableType.width + else + TODO() + else + texture!!.regionWidth + + internal var extraDrawFun: (SpriteBatch, Float, Float) -> Unit = { _, _, _ ->} internal val lineCount = if (text != null) text.rowEnd - text.rowStart else diff --git a/src/net/torvald/btex/BTeXParser.kt b/src/net/torvald/btex/BTeXParser.kt index 36e535a53..7fe5a39e1 100644 --- a/src/net/torvald/btex/BTeXParser.kt +++ b/src/net/torvald/btex/BTeXParser.kt @@ -9,11 +9,15 @@ import net.torvald.terrarum.App import net.torvald.terrarum.btex.BTeXDocument import net.torvald.terrarum.btex.BTeXDocument.Companion.DEFAULT_PAGE_FORE import net.torvald.terrarum.btex.BTeXDrawCall +import net.torvald.terrarum.btex.BTeXPage import net.torvald.terrarum.btex.MovableTypeDrawCall +import net.torvald.terrarum.ceilToFloat import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.ui.Toolkit import net.torvald.terrarumsansbitmap.MovableType +import net.torvald.terrarumsansbitmap.gdx.CodepointSequence import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap +import net.torvald.unicode.toUTF8Bytes import org.xml.sax.Attributes import org.xml.sax.InputSource import org.xml.sax.helpers.DefaultHandler @@ -21,6 +25,7 @@ import java.io.File import java.io.FileInputStream import java.io.StringReader import javax.xml.parsers.SAXParserFactory +import kotlin.math.absoluteValue import kotlin.reflect.KFunction import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.findAnnotation @@ -84,14 +89,18 @@ object BTeXParser { private var lastTagAtDepth = Array(24) { "" } private var pTagCntAtDepth = IntArray(24) + private val indexMap = HashMap() // id to pagenum + private val cptSectMap = ArrayList>() // type ("chapter", "section"), name, pagenum in zero-based index + private var tocPage: BTeXPage? = null + init { BTeXHandler::class.declaredFunctions.filter { it.findAnnotation() != null }.forEach { - println("Tag opener: ${it.name}") +// println("Tag opener: ${it.name}") elemOpeners[it.name] = it } BTeXHandler::class.declaredFunctions.filter { it.findAnnotation() != null }.forEach { - println("Tag closer: ${it.name}") +// println("Tag closer: ${it.name}") elemClosers[it.name] = it } } @@ -419,6 +428,11 @@ object BTeXParser { "examination" to 18, ) + private val ccEmph = TerrarumSansBitmap.toColorCode(0xfd44) + private val ccItemName = TerrarumSansBitmap.toColorCode(0xf37d) + private val ccTargetName = TerrarumSansBitmap.toColorCode(0xf3c4) + private val ccReset = TerrarumSansBitmap.toColorCode(0) + @OpenTag // reflective access is impossible with 'private' fun processElemBTEXDOC(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { @@ -458,17 +472,30 @@ object BTeXParser { } @OpenTag // reflective access is impossible with 'private' - fun processElemSPAN(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { - attribs["span"]?.let { - spanColour = it - } + fun processElemEMPH(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + handler.paragraphBuffer.append(ccEmph) } @OpenTag // reflective access is impossible with 'private' - fun processElemTABLEOFCONTENTS(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { - // TODO add post-parsing hook to the handler + fun processElemITEMNAME(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + handler.paragraphBuffer.append(ccItemName) } + @OpenTag // reflective access is impossible with 'private' + fun processElemTARGETNAME(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + handler.paragraphBuffer.append(ccTargetName) + } + + @CloseTag + fun closeElemTARGETNAME(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) = closeElemEMPH(handler, doc, theTag, uri, siblingIndex) + @CloseTag + fun closeElemITEMNAME(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) = closeElemEMPH(handler, doc, theTag, uri, siblingIndex) + @CloseTag + fun closeElemEMPH(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { + handler.paragraphBuffer.append(ccReset) + } + + @OpenTag // reflective access is impossible with 'private' fun processElemBTEX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { handler.paragraphBuffer.append("BTeX") @@ -484,8 +511,61 @@ object BTeXParser { } @OpenTag // reflective access is impossible with 'private' - fun processElemTOC(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + fun processElemTOCPAGE(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { doc.addNewPage() + typesetChapterHeading("Table of Contents", handler, 16) + } + + @OpenTag // reflective access is impossible with 'private' + fun processElemINDEXPAGE(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + doc.addNewPage() + typesetChapterHeading("Index", handler, 16) + } + + @OpenTag // reflective access is impossible with 'private' + fun processElemTABLEOFCONTENTS(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + tocPage = doc.currentPageObj + + handler.paragraphBuffer.clear() + typesetParagraphs("// TODO", handler) + handler.paragraphBuffer.clear() + } + + @OpenTag // reflective access is impossible with 'private' + fun processElemTABLEOFINDICES(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + handler.paragraphBuffer.clear() + + // prepare contents + val par = ArrayList() + val pageWidth = doc.textWidth + + indexMap.keys.toList().sorted().forEach { key -> + val pageNum = indexMap[key]!!.plus(1).toString() + val pageNumWidth = getFont().getWidth(pageNum) + + println("pageWidth=$pageWidth, pageNum=$pageNum, pageNumWidth=$pageNumWidth") + + typesetParagraphs(key, handler).let { + it.last().let { call -> + call.extraDrawFun = { batch, x, y -> + val font = getFont() + val dotGap = 10 + + var dotCursor = (x + call.width + dotGap/2).div(dotGap).ceilToFloat() * dotGap + while (dotCursor < x + pageWidth - pageNumWidth - dotGap/2) { + font.draw(batch, "ยท", dotCursor, y) + dotCursor += dotGap + } + + font.draw(batch, pageNum, x + pageWidth - pageNumWidth.toFloat(), y) + } + } + } + } + } + + @CloseTag + fun closeElemTABLEOFINDICES(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { } @OpenTag // reflective access is impossible with 'private' @@ -493,6 +573,22 @@ object BTeXParser { doc.addNewPage() } + @CloseTag + fun closeElemMANUSCRIPT(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { + // if tocPage != null, estimate TOC page size, renumber indexMap and cptSectMap if needed, then typeset the toc + } + + @OpenTag // reflective access is impossible with 'private' + fun processElemCHAPTER(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + } + + @OpenTag // reflective access is impossible with 'private' + fun processElemINDEX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + attribs["id"]?.let { + indexMap[it] = doc.currentPage + } + } + @@ -511,6 +607,28 @@ object BTeXParser { @CloseTag // reflective access is impossible with 'private' fun closeElemFULLPAGEBOX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { + doc.currentPageObj.let { page -> + val yStart = page.drawCalls.minOf { it.posY } + val yEnd = page.drawCalls.maxOf { it.posY + it.lineCount * doc.lineHeightInPx } + val pageHeight = doc.textHeight + + val newYpos = (pageHeight - (yEnd - yStart)) / 2 + val yDelta = newYpos - yStart + + val xStart = page.drawCalls.minOf { it.posX } + val xEnd = page.drawCalls.maxOf { it.posX + it.width } + val pageWidth = doc.textWidth + + val newXpos = (pageWidth - (xEnd - xStart)) / 2 + val xDelta = newXpos - xStart + + page.drawCalls.forEach { + it.posX += xDelta + it.posY += yDelta + } + + } + doc.addNewPage() } @@ -531,12 +649,44 @@ object BTeXParser { fun closeElemEDITION(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) = closeElemP(handler, doc, theTag, uri, siblingIndex) @CloseTag // reflective access is impossible with 'private' fun closeElemCHAPTER(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { + // insert new page for second+ chapters + if (siblingIndex > 0) doc.addNewPage() + + val thePar = handler.paragraphBuffer.toString().trim() + typesetChapterHeading(thePar, handler, 16) + + cptSectMap.add(Triple("chapter", thePar, doc.currentPage)) + + handler.paragraphBuffer.clear() + } + @CloseTag // reflective access is impossible with 'private' + fun closeElemSECTION(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { // if current line is the last line, proceed to the next page if (doc.currentLine == doc.pageLines - 1) doc.addNewPage() - val indent = 16 - val thePar = "\n" + handler.paragraphBuffer.toString().trim() - typesetParagraphs(thePar, handler, doc.textWidth - indent).also { + val thePar = handler.paragraphBuffer.toString().trim() + typesetSectionHeading(thePar, handler, 8) + + cptSectMap.add(Triple("section", thePar, doc.currentPage)) + + handler.paragraphBuffer.clear() + } + + @CloseTag // reflective access is impossible with 'private' + fun closeElemP(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { + val thePar = (if (siblingIndex > 1) "\u3000" else "") + handler.paragraphBuffer.toString().trim() // indent the strictly non-first pars + typesetParagraphs(thePar, handler) + handler.paragraphBuffer.clear() + } + + @CloseTag // reflective access is impossible with 'private' + fun closeElemSPAN(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { + spanColour = null + } + + + private fun typesetChapterHeading(thePar: String, handler: BTeXHandler, indent: Int = 16, width: Int = doc.textWidth) { + typesetParagraphs("\n"+thePar, handler, width - indent).also { // add indents and adjust text y pos it.forEach { it.posX += indent @@ -549,39 +699,18 @@ object BTeXParser { } } } - handler.paragraphBuffer.clear() } - @CloseTag // reflective access is impossible with 'private' - fun closeElemSECTION(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { - // if current line is the last line, proceed to the next page - if (doc.currentLine == doc.pageLines - 1) doc.addNewPage() - val indent = 8 - val thePar = "\n" + handler.paragraphBuffer.toString().trim() - typesetParagraphs(thePar, handler, doc.textWidth - indent).also { + private fun typesetSectionHeading(thePar: String, handler: BTeXHandler, indent: Int = 16, width: Int = doc.textWidth) { + typesetParagraphs("\n"+thePar, handler, width - indent).also { // add indents and adjust text y pos it.forEach { it.posX += indent it.posY -= doc.lineHeightInPx / 2 } } - handler.paragraphBuffer.clear() } - @CloseTag // reflective access is impossible with 'private' - fun closeElemP(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { - val thePar = (if (siblingIndex > 1) "\u3000" else "") + handler.paragraphBuffer.toString().trim() // indent the strictly non-first pars - typesetParagraphs(thePar, handler) - handler.paragraphBuffer.clear() - } - - - @CloseTag // reflective access is impossible with 'private' - fun closeElemSPAN(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { - spanColour = null - } - - private fun typesetParagraphs(thePar: String, handler: BTeXHandler, width: Int = doc.textWidth): List { val font = getFont() val slugs = MovableType(font, thePar, width) @@ -592,7 +721,7 @@ object BTeXParser { var slugHeight = slugs.height var linesOut = 0 - printdbg("Page: ${doc.currentPage+1}, Line: ${doc.currentLine}") +// printdbg("Page: ${doc.currentPage+1}, Line: ${doc.currentLine}") if (slugHeight > remainder) { val subset = linesOut to linesOut + remainder @@ -642,6 +771,45 @@ object BTeXParser { return drawCalls } + + companion object { + private const val ZWSP = 0x200B + private const val SHY = 0xAD + private const val NBSP = 0xA0 + private const val GLUE_POSITIVE_ONE = 0xFFFF0 + private const val GLUE_POSITIVE_SIXTEEN = 0xFFFFF + private const val GLUE_NEGATIVE_ONE = 0xFFFE0 + private const val GLUE_NEGATIVE_SIXTEEN = 0xFFFEF + + fun glueToString(glue: Int): String { + val tokens = CodepointSequence() + + if (glue == 0) + tokens.add(ZWSP) + else if (glue.absoluteValue <= 16) + if (glue > 0) + tokens.add(GLUE_POSITIVE_ONE + (glue - 1)) + else + tokens.add(GLUE_NEGATIVE_ONE + (glue.absoluteValue - 1)) + else { + val fullGlues = glue.absoluteValue / 16 + val smallGlues = glue.absoluteValue % 16 + if (glue > 0) + tokens.addAll( + List(fullGlues) { GLUE_POSITIVE_SIXTEEN } + + listOf(GLUE_POSITIVE_ONE + (smallGlues - 1)) + ) + else + tokens.addAll( + List(fullGlues) { GLUE_NEGATIVE_SIXTEEN } + + listOf(GLUE_NEGATIVE_ONE + (smallGlues - 1)) + ) + } + + return tokens.toUTF8Bytes().toString(Charsets.UTF_8) + } + } + } diff --git a/src/net/torvald/terrarum/tests/BTeXTest.kt b/src/net/torvald/terrarum/tests/BTeXTest.kt index 641aeacee..e18dfc809 100644 --- a/src/net/torvald/terrarum/tests/BTeXTest.kt +++ b/src/net/torvald/terrarum/tests/BTeXTest.kt @@ -34,7 +34,7 @@ class BTeXTest : ApplicationAdapter() { Test Edition - + @@ -52,9 +52,7 @@ class BTeXTest : ApplicationAdapter() { -

- this page is intentionally left blank -

+

text emph and item on target