btex: working index page

This commit is contained in:
minjaesong
2024-04-26 19:12:03 +09:00
parent 2534ca4ff7
commit f16944d000
5 changed files with 220 additions and 45 deletions

View File

@@ -5,7 +5,7 @@
<edition>Test Edition</edition>
</cover>
<toc><tableofcontents /></toc>
<tocpage><tableofcontents /></tocpage>
<manuscript>

Binary file not shown.

View File

@@ -73,7 +73,7 @@ class BTeXPage(
val width: Int,
val height: Int,
) {
private val drawCalls = ArrayList<BTeXDrawCall>()
internal val drawCalls = ArrayList<BTeXDrawCall>()
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

View File

@@ -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<String, Int>() // id to pagenum
private val cptSectMap = ArrayList<Triple<String, String, Int>>() // type ("chapter", "section"), name, pagenum in zero-based index
private var tocPage: BTeXPage? = null
init {
BTeXHandler::class.declaredFunctions.filter { it.findAnnotation<OpenTag>() != null }.forEach {
println("Tag opener: ${it.name}")
// println("Tag opener: ${it.name}")
elemOpeners[it.name] = it
}
BTeXHandler::class.declaredFunctions.filter { it.findAnnotation<CloseTag>() != 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<String, String>, 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<String, String>, siblingIndex: Int) {
attribs["span"]?.let {
spanColour = it
}
fun processElemEMPH(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>, 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<String, String>, siblingIndex: Int) {
// TODO add post-parsing hook to the handler
fun processElemITEMNAME(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>, 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<String, String>, 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<String, String>, 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<String, String>, siblingIndex: Int) {
fun processElemTOCPAGE(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>, 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<String, String>, 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<String, String>, 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<String, String>, siblingIndex: Int) {
handler.paragraphBuffer.clear()
// prepare contents
val par = ArrayList<String>()
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<String, String>, siblingIndex: Int) {
}
@OpenTag // reflective access is impossible with 'private'
fun processElemINDEX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>, 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<BTeXDrawCall> {
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)
}
}
}

View File

@@ -34,7 +34,7 @@ class BTeXTest : ApplicationAdapter() {
<edition>Test Edition</edition>
</cover>
<toc><tableofcontents /></toc>
<tocpage><tableofcontents /></tocpage>
<manuscript>
@@ -52,9 +52,7 @@ class BTeXTest : ApplicationAdapter() {
<newpage />
<fullpagebox>
<p><span colour="grey">
this page is intentionally left blank
</span></p>
<p>text <emph>emph</emph> and <itemname>item</itemname> on <targetname>target</targetname></p>
</fullpagebox>