diff --git a/assets/mods/basegame/books/btex.xml b/assets/mods/basegame/books/btex.xml index ca1a08e30..0896e23ee 100644 --- a/assets/mods/basegame/books/btex.xml +++ b/assets/mods/basegame/books/btex.xml @@ -31,7 +31,7 @@ - Writing Book Using Pen and Papers + Writing a Book Using Pen and Papers

If you open a book on a writing table, you will be welcomed with a toolbar used to put other book elements, such as chapters and sections.

@@ -39,7 +39,7 @@ - Writing Book Using a Typewriter + Writing a Book Using a Typewriter

Typewriters can only write in a single style of font, chapters and sections are not available.

@@ -47,7 +47,7 @@ - Writing Book Using a Computer + Writing a Book Using a Computer

Writing book using a computer requires the use of the Book Typesetting Engine Extended, or .

diff --git a/lib/TerrarumSansBitmap.jar b/lib/TerrarumSansBitmap.jar index 8a3c1b61a..1e7491734 100644 --- a/lib/TerrarumSansBitmap.jar +++ b/lib/TerrarumSansBitmap.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8a412868241bc2802e116302d28a934b0d7421d442a8423aa53d341a7d82b9f9 -size 189694 +oid sha256:71c1734431ef9bd9cb8b1384f7033719168f26c5ca082f23d1ca69dd3d45cda7 +size 189735 diff --git a/src/net/torvald/btex/BTeXDocument.kt b/src/net/torvald/btex/BTeXDocument.kt index 07d759615..8257735ea 100644 --- a/src/net/torvald/btex/BTeXDocument.kt +++ b/src/net/torvald/btex/BTeXDocument.kt @@ -45,16 +45,16 @@ class BTeXDocument { val pageIndices: IntRange get() = pages.indices - val linesOnPage = ArrayList() + internal val linesPrintedOnPage = ArrayList() fun addNewPage(back: Color = DEFAULT_PAGE_BACK) { pages.add(BTeXPage(back, pageWidth, pageHeight)) - linesOnPage.add(0) + linesPrintedOnPage.add(0) } fun addNewPageAt(index: Int, back: Color = DEFAULT_PAGE_BACK) { pages.add(index, BTeXPage(back, pageWidth, pageHeight)) - linesOnPage.add(index, 0) + linesPrintedOnPage.add(index, 0) } /** @@ -66,14 +66,14 @@ class BTeXDocument { fun appendDrawCall(drawCall: BTeXDrawCall) { pages.last().appendDrawCall(drawCall) - linesOnPage[linesOnPage.lastIndex] += drawCall.lineCount + linesPrintedOnPage[linesPrintedOnPage.lastIndex] += drawCall.lineCount } fun appendDrawCall(page: BTeXPage, drawCall: BTeXDrawCall) { page.appendDrawCall(drawCall) val pagenum = pages.indexOf(page) - linesOnPage[pagenum] += drawCall.lineCount + linesPrintedOnPage[pagenum] += drawCall.lineCount } fun render(frameDelta: Float, batch: SpriteBatch, page: Int, x: Int, y: Int) { diff --git a/src/net/torvald/btex/BTeXParser.kt b/src/net/torvald/btex/BTeXParser.kt index 376ad683e..f86fe7cc5 100644 --- a/src/net/torvald/btex/BTeXParser.kt +++ b/src/net/torvald/btex/BTeXParser.kt @@ -9,7 +9,6 @@ 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 @@ -89,8 +88,11 @@ object BTeXParser { private var lastTagAtDepth = Array(24) { "" } private var pTagCntAtDepth = IntArray(24) + private data class CptSect(val type: String, var alt: String?, var pagenum: Int) private data class CptSectInfo(val type: String, var name: String, var pagenum: Int) + private val cptSectStack = ArrayList() + private val indexMap = HashMap() // id to pagenum private val cptSectMap = ArrayList() private var tocPage: Int? = null @@ -135,9 +137,9 @@ object BTeXParser { val theTag = tag.uppercase() if (tagStack.isNotEmpty() && tagStack.any { textTags.contains(it) } && textTags.contains(theTag)) - throw IllegalStateException("Text tag '$theTag' used inside of text tags (tag stack is ${tagStack.joinToString()})") + throw IllegalStateException("Text tag '$theTag' used inside of text tags (tag stack is ${tagStack.joinToString()}, $theTag)") 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()})") + 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 @@ -538,25 +540,7 @@ object BTeXParser { val pageWidth = doc.textWidth indexMap.keys.toList().sorted().forEach { key -> - val pageNum = indexMap[key]!!.plus(1).toString() - val pageNumWidth = getFont().getWidth(pageNum) - - 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) - } - } - } + typesetTOCline(key, indexMap[key]!!, handler) } } @@ -574,10 +558,7 @@ object BTeXParser { // if tocPage != null, estimate TOC page size, renumber indexMap and cptSectMap if needed, then typeset the toc if (tocPage != null) { // TODO estimate the number of TOC pages - var tocSizeInPages = 1 - - // TODO // - + val tocSizeInPages = (cptSectMap.size + 2) / doc.pageLines // renumber things if (tocSizeInPages > 1) { @@ -596,34 +577,12 @@ object BTeXParser { } cptSectMap.forEach { (type, name, pg) -> - val pageNum = pg.plus(1).toString() - val pageNumWidth = getFont().getWidth(pageNum) - - val key = if (type == "section") "\u3000$name" else name - typesetParagraphs(key, handler, startingPage = tocPage!!).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) - } - } - } + val indent = if (type == "subsection") 32 else if (type == "section") 16 else 0 + typesetTOCline(name, pg, handler, indent, tocPage) } } } - @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 { @@ -689,6 +648,16 @@ object BTeXParser { fun closeElemAUTHOR(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) = closeElemP(handler, doc, theTag, uri, siblingIndex) @CloseTag // reflective access is impossible with 'private' fun closeElemEDITION(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) = closeElemP(handler, doc, theTag, uri, siblingIndex) + + + @OpenTag // reflective access is impossible with 'private' + fun processElemCHAPTER(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + cptSectStack.add(CptSect("chapter", attribs["alt"], doc.currentPage)) + } + @OpenTag // reflective access is impossible with 'private' + fun processElemSECTION(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap, siblingIndex: Int) { + cptSectStack.add(CptSect("section", attribs["alt"], doc.currentPage)) + } @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 @@ -697,23 +666,27 @@ object BTeXParser { val thePar = handler.paragraphBuffer.toString().trim() typesetChapterHeading(thePar, handler, 16) - cptSectMap.add(CptSectInfo("chapter", thePar, doc.currentPage)) + val cptSectInfo = cptSectStack.removeLast() + cptSectMap.add(CptSectInfo("chapter", cptSectInfo.alt ?: thePar, cptSectInfo.pagenum)) 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.linesOnPage.last() == doc.pageLines - 1) doc.addNewPage() + if (doc.linesPrintedOnPage.last() == doc.pageLines - 1) doc.addNewPage() val thePar = handler.paragraphBuffer.toString().trim() typesetSectionHeading(thePar, handler, 8) - cptSectMap.add(CptSectInfo("section", thePar, doc.currentPage)) + val cptSectInfo = cptSectStack.removeLast() + cptSectMap.add(CptSectInfo("section", cptSectInfo.alt ?: thePar, cptSectInfo.pagenum)) 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 @@ -760,7 +733,7 @@ object BTeXParser { val drawCalls = ArrayList() - var remainder = doc.pageLines - doc.linesOnPage.last() + var remainder = doc.pageLines - doc.linesPrintedOnPage.last() var slugHeight = slugs.height var linesOut = 0 @@ -771,7 +744,7 @@ object BTeXParser { val drawCall = BTeXDrawCall( 0, - doc.linesOnPage[pageNum] * doc.lineHeightInPx, + doc.linesPrintedOnPage[pageNum] * doc.lineHeightInPx, handler.currentTheme, handler.getSpanColour(), MovableTypeDrawCall(slugs, subset.first, subset.second) @@ -792,7 +765,7 @@ object BTeXParser { val drawCall = BTeXDrawCall( 0, - doc.linesOnPage[pageNum] * doc.lineHeightInPx, + doc.linesPrintedOnPage[pageNum] * doc.lineHeightInPx, handler.currentTheme, handler.getSpanColour(), MovableTypeDrawCall(slugs, subset.first, subset.second) @@ -809,11 +782,47 @@ object BTeXParser { } // if typesetting the paragraph leaves the first line of new page empty, move the "row cursor" back up - if (doc.linesOnPage[pageNum] == 1 && doc.pages[pageNum].isEmpty()) doc.linesOnPage[pageNum] = 0 // '\n' adds empty draw call to the page, which makes isEmpty() to return false + if (doc.linesPrintedOnPage[pageNum] == 1 && doc.pages[pageNum].isEmpty()) doc.linesPrintedOnPage[pageNum] = 0 // '\n' adds empty draw call to the page, which makes isEmpty() to return false return drawCalls } + private fun typesetTOCline(name: String, pageNum: Int, handler: BTeXHandler, indentation: Int = 0, pageToWrite: Int? = null) { + val pageNum = pageNum.plus(1).toString() + val pageNumWidth = getFont().getWidth(pageNum) + val typeWidth = doc.textWidth - indentation + + typesetParagraphs(name, handler, typeWidth, pageToWrite ?: doc.currentPage).let { + it.forEach { + it.posX += indentation + } + + it.last().let { call -> + call.extraDrawFun = { batch, x, y -> + val font = getFont() + val dotGap = 10 + val y = y + (call.lineCount - 1).coerceAtLeast(0) * doc.lineHeightInPx + + val textWidth = if (call.text is MovableTypeDrawCall) { + call.text.movableType.typesettedSlugs.last().last().getEndPos() + } + /*else if (call.text is RaggedTypeDrawCall) { + call.text.raggedType.typesettedSlugs.last().last().getEndPos() + }*/ + else call.width + + var dotCursor = (x + textWidth + dotGap/2).div(dotGap).ceilToFloat() * dotGap + while (dotCursor < x + typeWidth - pageNumWidth - dotGap/2) { + font.draw(batch, "·", dotCursor, y) + dotCursor += dotGap + } + + font.draw(batch, pageNum, x + typeWidth - pageNumWidth.toFloat(), y) + } + } + } + } + companion object { private const val ZWSP = 0x200B diff --git a/src/net/torvald/terrarum/tests/BTeXTest.kt b/src/net/torvald/terrarum/tests/BTeXTest.kt index e18dfc809..6b5a7b60a 100644 --- a/src/net/torvald/terrarum/tests/BTeXTest.kt +++ b/src/net/torvald/terrarum/tests/BTeXTest.kt @@ -49,6 +49,10 @@ class BTeXTest : ApplicationAdapter() {

A book is a collection of texts printed in a special way that allows them to be read easily, with enumerable pages and insertion of other helpful resources, such as illustrations and hyperlinks.

+
BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB: Bad Recursion BRB RangeError: stack size exceeded
+ +

Noice.

+ @@ -58,7 +62,7 @@ class BTeXTest : ApplicationAdapter() { - Writing Book Using Pen and Papers + Writing a Book Using Pen and Papers

If you open a book on a writing table, you will be welcomed with a toolbar used to put other book elements, such as chapters and sections.

@@ -66,7 +70,7 @@ class BTeXTest : ApplicationAdapter() { - Writing Book Using a Typewriter + Writing a Book Using a Typewriter

Typewriters can only write in a single style of font, chapters and sections are not available.

@@ -74,7 +78,7 @@ class BTeXTest : ApplicationAdapter() { - Writing Book Using a Computer + Writing a Book Using a Computer

Writing book using a computer requires the use of the Book Typesetting Engine Extended, or .