more btex tests

This commit is contained in:
minjaesong
2024-04-25 01:29:32 +09:00
parent 76dd9a98e4
commit f154756439
5 changed files with 433 additions and 63 deletions

View File

@@ -1,10 +1,8 @@
package net.torvald.terrarum.btex
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.g2d.BitmapFont
import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureRegion
import net.torvald.terrarum.App
import net.torvald.terrarum.ui.Toolkit
import net.torvald.terrarumsansbitmap.MovableType
@@ -18,9 +16,9 @@ class BTeXDocument {
var papersize = "standard"
var pageWidth = 420
var lineHeight = 24
var lineHeightInPx = 24
var pageLines = 25
var pageHeight = pageLines * lineHeight
var pageHeight = pageLines * lineHeightInPx
companion object {
val DEFAULT_PAGE_BACK = Color(0xe1e1d7ff.toInt())
@@ -29,14 +27,26 @@ class BTeXDocument {
private val pages = ArrayList<BTeXPage>()
val currentPage: Int
get() = pages.size - 1
var currentLine: Int = 0
private set
fun addNewPage(back: Color = DEFAULT_PAGE_BACK) {
pages.add(BTeXPage(back, pageWidth, pageHeight))
currentLine = 0
}
/**
* Appends draw call to the list. The draw call must be prepared manually so that they would not overflow.
* Use `addNewPage` to append the overflowing text to the next page.
*
* `currentLine` *will* be updated automatically.
*/
fun appendDrawCall(drawCall: BTeXDrawCall) {
pages.last().appendDrawCall(drawCall)
currentLine += drawCall.lineCount
}
fun render(frameDelta: Float, batch: SpriteBatch, page: Int, x: Int, y: Int) {
@@ -64,21 +74,37 @@ class BTeXPage(
}
}
data class MovableTypeDrawCall(val movableType: MovableType, val rowStart: Int, val rowEnd: Int) {
fun draw(batch: SpriteBatch, x: Float, y: Float) {
interface BTeXTextDrawCall {
val rowStart: Int
val rowEnd: Int
fun draw(batch: SpriteBatch, x: Float, y: Float)
}
data class MovableTypeDrawCall(val movableType: MovableType, override val rowStart: Int, override val rowEnd: Int): BTeXTextDrawCall {
override fun draw(batch: SpriteBatch, x: Float, y: Float) {
movableType.draw(batch, x, y, rowStart, rowEnd)
}
}
/*data class RaggedLeftDrawCall(val raggedType: RaggedType, override val rowStart: Int, override val rowEnd: Int): BTeXTextDrawCall {
override fun draw(batch: SpriteBatch, x: Float, y: Float) {
raggedType.draw(batch, x, y, rowStart, rowEnd)
}
}*/
class BTeXDrawCall(
val posX: Int,
val posY: Int,
val posX: Int, // position relative to the page start (excluding page margin)
val posY: Int, // position relative to the page start (excluding page margin)
val theme: String,
val colour: Color,
val text: MovableTypeDrawCall? = null,
val text: BTeXTextDrawCall? = null,
val texture: TextureRegion? = null,
) {
init {
if (text != null && texture != null) throw IllegalArgumentException("Text and Texture are both non-null")
}
fun draw(batch: SpriteBatch, x: Int, y: Int) {
val px = (posX + x).toFloat()
val py = (posY + y).toFloat()
@@ -98,4 +124,9 @@ class BTeXDrawCall(
else throw Error("Text and Texture are both non-null")
}
internal val lineCount = if (text != null)
text.rowEnd - text.rowStart
else
TODO()
}

View File

@@ -4,7 +4,11 @@ import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color
import net.torvald.terrarum.App
import net.torvald.terrarum.btex.BTeXDocument
import net.torvald.terrarum.btex.BTeXDrawCall
import net.torvald.terrarum.btex.MovableTypeDrawCall
import net.torvald.terrarum.gameitems.ItemID
import net.torvald.terrarumsansbitmap.MovableType
import net.torvald.terrarumsansbitmap.gdx.TerrarumSansBitmap
import org.xml.sax.Attributes
import org.xml.sax.InputSource
import org.xml.sax.helpers.DefaultHandler
@@ -20,6 +24,9 @@ import kotlin.reflect.full.findAnnotation
*/
object BTeXParser {
internal val textTags = hashSetOf("P", "TITLE", "AUTHOR", "EDITION", "CHAPTER", "SECTION")
internal val textDecorTags = hashSetOf("SPAN", "CODE")
operator fun invoke(file: FileHandle) = invoke(file.file())
operator fun invoke(file: File): BTeXDocument {
@@ -39,7 +46,7 @@ object BTeXParser {
internal class BTeXHandler(val doc: BTeXDocument) : DefaultHandler() {
private val DEFAULT_FONTCOL = Color(0x222222ff)
private val LINE_HEIGHT = doc.lineHeight
private val LINE_HEIGHT = doc.lineHeightInPx
private var cover = ""
private var inner = ""
@@ -56,7 +63,7 @@ object BTeXParser {
private val tagStack = ArrayList<String>() // index zero should be "btex"
private var currentTheme = ""
private var spanColour: String? = null
@@ -66,6 +73,8 @@ object BTeXParser {
private val elemOpeners: HashMap<String, KFunction<*>> = HashMap()
private val elemClosers: HashMap<String, KFunction<*>> = HashMap()
private val paragraphBuffer = StringBuilder()
init {
BTeXHandler::class.declaredFunctions.filter { it.findAnnotation<OpenTag>() != null }.forEach {
println("Tag opener: ${it.name}")
@@ -101,14 +110,21 @@ object BTeXParser {
}
override fun startElement(uri: String, localName: String, qName: String, attributes: Attributes) {
val tag = qName; if (tagStack.isEmpty() && tag.lowercase() != "btexdoc") throw BTeXParsingException("Document is not BTeX")
tagStack.add(tag)
val tag = qName
if (tagStack.isEmpty() && tag.lowercase() != "btexdoc") throw BTeXParsingException("Document is not BTeX")
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()})")
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()})")
tagStack.add(theTag)
val attribs = HashMap<String, String>().also {
it.putAll((0 until attributes.length).map { attributes.getQName(it) to attributes.getValue(it) })
}
val theTag = tag.uppercase()
elemOpeners["processElem$theTag"].let {
if (it == null)
@@ -123,7 +139,7 @@ object BTeXParser {
}
}
// printdbg("Start element \t($tag)")
// printdbg("Start element \t($theTag)")
}
override fun endElement(uri: String, localName: String, qName: String) {
@@ -147,8 +163,9 @@ object BTeXParser {
val str =
String(ch.sliceArray(start until start + length)).replace('\n', ' ').replace(Regex(" +"), " ")//.trim()
if (str.isNotBlank()) {
printdbg("Characters \t\"$str\"")
if (str.isNotEmpty()) {
// printdbg("Characters \t\"$str\"")
paragraphBuffer.append(str)
}
}
@@ -170,16 +187,212 @@ object BTeXParser {
typeY += h
}
private lateinit var testFont: TerrarumSansBitmap
private fun getFont() = when (cover) {
"typewriter" -> TODO()
else -> App.fontGame
else -> App.fontGame ?: let {
if (!::testFont.isInitialized) testFont = TerrarumSansBitmap(App.FONT_DIR)
testFont
}
}
private fun getSpanColour(): Color = spanColourMap.getOrDefault(spanColour, DEFAULT_FONTCOL)
private val hexColRegexRGBshort = Regex("#[0-9a-fA-F]{3,3}")
private val hexColRegexRGB = Regex("#[0-9a-fA-F]{6,6}")
private fun getSpanColour(): Color = if (spanColour == null) DEFAULT_FONTCOL
else if (spanColour!!.matches(hexColRegexRGB)) {
val rs = spanColour!!.substring(1,3)
val gs = spanColour!!.substring(3,5)
val bs = spanColour!!.substring(5,7)
val r = rs.toInt(16) / 255f
val g = gs.toInt(16) / 255f
val b = bs.toInt(16) / 255f
Color(r, g, b, 1f)
}
else if (spanColour!!.matches(hexColRegexRGBshort)) {
val rs = spanColour!!.substring(1,2)
val gs = spanColour!!.substring(2,3)
val bs = spanColour!!.substring(3,4)
val r = rs.toInt(16) / 15f
val g = gs.toInt(16) / 15f
val b = bs.toInt(16) / 15f
Color(r, g, b, 1f)
}
else
spanColourMap.getOrDefault(spanColour, DEFAULT_FONTCOL)
// list of CSS named colours (list supports up to CSS Colors Level 4)
private val spanColourMap = hashMapOf(
"grey" to Color.LIGHT_GRAY
"black" to Color(0x000000ff.toInt()),
"silver" to Color(0xc0c0c0ff.toInt()),
"gray" to Color(0x808080ff.toInt()),
"white" to Color(0xffffffff.toInt()),
"maroon" to Color(0x800000ff.toInt()),
"red" to Color(0xff0000ff.toInt()),
"purple" to Color(0x800080ff.toInt()),
"fuchsia" to Color(0xff00ffff.toInt()),
"green" to Color(0x008000ff.toInt()),
"lime" to Color(0x00ff00ff.toInt()),
"olive" to Color(0x808000ff.toInt()),
"yellow" to Color(0xffff00ff.toInt()),
"navy" to Color(0x000080ff.toInt()),
"blue" to Color(0x0000ffff.toInt()),
"teal" to Color(0x008080ff.toInt()),
"aqua" to Color(0x00ffffff.toInt()),
"aliceblue" to Color(0xf0f8ffff.toInt()),
"antiquewhite" to Color(0xfaebd7ff.toInt()),
"aqua" to Color(0x00ffffff.toInt()),
"aquamarine" to Color(0x7fffd4ff.toInt()),
"azure" to Color(0xf0ffffff.toInt()),
"beige" to Color(0xf5f5dcff.toInt()),
"bisque" to Color(0xffe4c4ff.toInt()),
"black" to Color(0x000000ff.toInt()),
"blanchedalmond" to Color(0xffebcdff.toInt()),
"blue" to Color(0x0000ffff.toInt()),
"blueviolet" to Color(0x8a2be2ff.toInt()),
"brown" to Color(0xa52a2aff.toInt()),
"burlywood" to Color(0xdeb887ff.toInt()),
"cadetblue" to Color(0x5f9ea0ff.toInt()),
"chartreuse" to Color(0x7fff00ff.toInt()),
"chocolate" to Color(0xd2691eff.toInt()),
"coral" to Color(0xff7f50ff.toInt()),
"cornflowerblue" to Color(0x6495edff.toInt()),
"cornsilk" to Color(0xfff8dcff.toInt()),
"crimson" to Color(0xdc143cff.toInt()),
"cyan" to Color(0x00ffffff.toInt()),
"darkblue" to Color(0x00008bff.toInt()),
"darkcyan" to Color(0x008b8bff.toInt()),
"darkgoldenrod" to Color(0xb8860bff.toInt()),
"darkgray" to Color(0xa9a9a9ff.toInt()),
"darkgreen" to Color(0x006400ff.toInt()),
"darkgrey" to Color(0xa9a9a9ff.toInt()),
"darkkhaki" to Color(0xbdb76bff.toInt()),
"darkmagenta" to Color(0x8b008bff.toInt()),
"darkolivegreen" to Color(0x556b2fff.toInt()),
"darkorange" to Color(0xff8c00ff.toInt()),
"darkorchid" to Color(0x9932ccff.toInt()),
"darkred" to Color(0x8b0000ff.toInt()),
"darksalmon" to Color(0xe9967aff.toInt()),
"darkseagreen" to Color(0x8fbc8fff.toInt()),
"darkslateblue" to Color(0x483d8bff.toInt()),
"darkslategray" to Color(0x2f4f4fff.toInt()),
"darkslategrey" to Color(0x2f4f4fff.toInt()),
"darkturquoise" to Color(0x00ced1ff.toInt()),
"darkviolet" to Color(0x9400d3ff.toInt()),
"deeppink" to Color(0xff1493ff.toInt()),
"deepskyblue" to Color(0x00bfffff.toInt()),
"dimgray" to Color(0x696969ff.toInt()),
"dimgrey" to Color(0x696969ff.toInt()),
"dodgerblue" to Color(0x1e90ffff.toInt()),
"firebrick" to Color(0xb22222ff.toInt()),
"floralwhite" to Color(0xfffaf0ff.toInt()),
"forestgreen" to Color(0x228b22ff.toInt()),
"fuchsia" to Color(0xff00ffff.toInt()),
"gainsboro" to Color(0xdcdcdcff.toInt()),
"ghostwhite" to Color(0xf8f8ffff.toInt()),
"gold" to Color(0xffd700ff.toInt()),
"goldenrod" to Color(0xdaa520ff.toInt()),
"gray" to Color(0x808080ff.toInt()),
"green" to Color(0x008000ff.toInt()),
"greenyellow" to Color(0xadff2fff.toInt()),
"grey" to Color(0x808080ff.toInt()),
"honeydew" to Color(0xf0fff0ff.toInt()),
"hotpink" to Color(0xff69b4ff.toInt()),
"indianred" to Color(0xcd5c5cff.toInt()),
"indigo" to Color(0x4b0082ff.toInt()),
"ivory" to Color(0xfffff0ff.toInt()),
"khaki" to Color(0xf0e68cff.toInt()),
"lavender" to Color(0xe6e6faff.toInt()),
"lavenderblush" to Color(0xfff0f5ff.toInt()),
"lawngreen" to Color(0x7cfc00ff.toInt()),
"lemonchiffon" to Color(0xfffacdff.toInt()),
"lightblue" to Color(0xadd8e6ff.toInt()),
"lightcoral" to Color(0xf08080ff.toInt()),
"lightcyan" to Color(0xe0ffffff.toInt()),
"lightgoldenrodyellow" to Color(0xfafad2ff.toInt()),
"lightgray" to Color(0xd3d3d3ff.toInt()),
"lightgreen" to Color(0x90ee90ff.toInt()),
"lightgrey" to Color(0xd3d3d3ff.toInt()),
"lightpink" to Color(0xffb6c1ff.toInt()),
"lightsalmon" to Color(0xffa07aff.toInt()),
"lightseagreen" to Color(0x20b2aaff.toInt()),
"lightskyblue" to Color(0x87cefaff.toInt()),
"lightslategray" to Color(0x778899ff.toInt()),
"lightslategrey" to Color(0x778899ff.toInt()),
"lightsteelblue" to Color(0xb0c4deff.toInt()),
"lightyellow" to Color(0xffffe0ff.toInt()),
"lime" to Color(0x00ff00ff.toInt()),
"limegreen" to Color(0x32cd32ff.toInt()),
"linen" to Color(0xfaf0e6ff.toInt()),
"magenta" to Color(0xff00ffff.toInt()),
"maroon" to Color(0x800000ff.toInt()),
"mediumaquamarine" to Color(0x66cdaaff.toInt()),
"mediumblue" to Color(0x0000cdff.toInt()),
"mediumorchid" to Color(0xba55d3ff.toInt()),
"mediumpurple" to Color(0x9370dbff.toInt()),
"mediumseagreen" to Color(0x3cb371ff.toInt()),
"mediumslateblue" to Color(0x7b68eeff.toInt()),
"mediumspringgreen" to Color(0x00fa9aff.toInt()),
"mediumturquoise" to Color(0x48d1ccff.toInt()),
"mediumvioletred" to Color(0xc71585ff.toInt()),
"midnightblue" to Color(0x191970ff.toInt()),
"mintcream" to Color(0xf5fffaff.toInt()),
"mistyrose" to Color(0xffe4e1ff.toInt()),
"moccasin" to Color(0xffe4b5ff.toInt()),
"navajowhite" to Color(0xffdeadff.toInt()),
"navy" to Color(0x000080ff.toInt()),
"oldlace" to Color(0xfdf5e6ff.toInt()),
"olive" to Color(0x808000ff.toInt()),
"olivedrab" to Color(0x6b8e23ff.toInt()),
"orange" to Color(0xffa500ff.toInt()),
"orangered" to Color(0xff4500ff.toInt()),
"orchid" to Color(0xda70d6ff.toInt()),
"palegoldenrod" to Color(0xeee8aaff.toInt()),
"palegreen" to Color(0x98fb98ff.toInt()),
"paleturquoise" to Color(0xafeeeeff.toInt()),
"palevioletred" to Color(0xdb7093ff.toInt()),
"papayawhip" to Color(0xffefd5ff.toInt()),
"peachpuff" to Color(0xffdab9ff.toInt()),
"peru" to Color(0xcd853fff.toInt()),
"pink" to Color(0xffc0cbff.toInt()),
"plum" to Color(0xdda0ddff.toInt()),
"powderblue" to Color(0xb0e0e6ff.toInt()),
"purple" to Color(0x800080ff.toInt()),
"rebeccapurple" to Color(0x663399ff.toInt()),
"red" to Color(0xff0000ff.toInt()),
"rosybrown" to Color(0xbc8f8fff.toInt()),
"royalblue" to Color(0x4169e1ff.toInt()),
"saddlebrown" to Color(0x8b4513ff.toInt()),
"salmon" to Color(0xfa8072ff.toInt()),
"sandybrown" to Color(0xf4a460ff.toInt()),
"seagreen" to Color(0x2e8b57ff.toInt()),
"seashell" to Color(0xfff5eeff.toInt()),
"sienna" to Color(0xa0522dff.toInt()),
"silver" to Color(0xc0c0c0ff.toInt()),
"skyblue" to Color(0x87ceebff.toInt()),
"slateblue" to Color(0x6a5acdff.toInt()),
"slategray" to Color(0x708090ff.toInt()),
"slategrey" to Color(0x708090ff.toInt()),
"snow" to Color(0xfffafaff.toInt()),
"springgreen" to Color(0x00ff7fff.toInt()),
"steelblue" to Color(0x4682b4ff.toInt()),
"tan" to Color(0xd2b48cff.toInt()),
"teal" to Color(0x008080ff.toInt()),
"thistle" to Color(0xd8bfd8ff.toInt()),
"tomato" to Color(0xff6347ff.toInt()),
"transparent" to Color(0),
"turquoise" to Color(0x40e0d0ff.toInt()),
"violet" to Color(0xee82eeff.toInt()),
"wheat" to Color(0xf5deb3ff.toInt()),
"white" to Color(0xffffffff.toInt()),
"whitesmoke" to Color(0xf5f5f5ff.toInt()),
"yellow" to Color(0xffff00ff.toInt()),
"yellowgreen" to Color(0x9acd32ff.toInt())
)
private val pageWidthMap = hashMapOf(
@@ -239,14 +452,35 @@ object BTeXParser {
// TODO add post-parsing hook to the handler
}
@OpenTag // reflective access is impossible with 'private'
fun processElemBTEX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
handler.paragraphBuffer.append("BTeX")
}
@OpenTag // reflective access is impossible with 'private'
fun processElemBR(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
fun processElemCOVER(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
doc.addNewPage(Color(0x6f4a45ff))
}
@OpenTag // reflective access is impossible with 'private'
fun processElemTOC(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
doc.addNewPage()
}
@OpenTag // reflective access is impossible with 'private'
fun processElemMANUSCRIPT(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
doc.addNewPage()
}
@OpenTag // reflective access is impossible with 'private'
fun processElemBR(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
handler.paragraphBuffer.append("\n")
}
@OpenTag // reflective access is impossible with 'private'
@@ -256,12 +490,63 @@ object BTeXParser {
@OpenTag // reflective access is impossible with 'private'
fun processElemP(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>) {
}
@CloseTag // reflective access is impossible with 'private'
fun closeElemP(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String) {
printdbg("Par: ${handler.paragraphBuffer}")
val font = getFont()
val slugs = MovableType(font, handler.paragraphBuffer.toString(), doc.pageWidth)
var remainder = doc.pageLines - doc.currentLine
var slugHeight = slugs.height
var linesOut = 0
if (slugHeight > remainder) {
val subset = linesOut to linesOut + remainder
val drawCall = BTeXDrawCall(
0,
remainder * doc.pageLines,
handler.currentTheme,
handler.getSpanColour(),
MovableTypeDrawCall(slugs, subset.first, subset.second)
)
doc.appendDrawCall(drawCall)
linesOut += remainder
slugHeight -= remainder
doc.addNewPage()
}
while (slugHeight > 0) {
remainder = minOf(slugHeight, doc.pageLines)
val subset = linesOut to linesOut + remainder
val drawCall = BTeXDrawCall(
0,
0,
handler.currentTheme,
handler.getSpanColour(),
MovableTypeDrawCall(slugs, subset.first, subset.second)
)
doc.appendDrawCall(drawCall)
linesOut += remainder
slugHeight -= remainder
if (remainder == doc.pageLines) {
doc.addNewPage()
}
}
handler.paragraphBuffer.clear()
}
@OpenTag // reflective access is impossible with 'private'
@@ -275,6 +560,7 @@ object BTeXParser {
fun closeElemSPAN(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String) {
spanColour = null
}
}