btex: better toc and chapter/section alt texts

This commit is contained in:
minjaesong
2024-04-26 23:16:20 +09:00
parent 6264e0d963
commit 1137afebac
5 changed files with 83 additions and 70 deletions

View File

@@ -31,7 +31,7 @@
<chapter>Writing Book Using Pen and Papers</chapter> <chapter>Writing a Book Using Pen and Papers</chapter>
<p><index id="pen and paper" />If you open a book on a writing table, you will be welcomed with a <p><index id="pen and paper" />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.</p> toolbar used to put other book elements, such as chapters and sections.</p>
@@ -39,7 +39,7 @@
<chapter>Writing Book Using a Typewriter</chapter> <chapter>Writing a Book Using a Typewriter</chapter>
<p><index id="typewriter" />Typewriters can only write in a single style of font, chapters and <p><index id="typewriter" />Typewriters can only write in a single style of font, chapters and
sections are not available.</p> sections are not available.</p>
@@ -47,7 +47,7 @@
<chapter>Writing Book Using a Computer</chapter> <chapter>Writing a Book Using a Computer</chapter>
<p>Writing book using a computer requires the use of the Book Typesetting Engine Extended, or <btex />.</p> <p>Writing book using a computer requires the use of the Book Typesetting Engine Extended, or <btex />.</p>

Binary file not shown.

View File

@@ -45,16 +45,16 @@ class BTeXDocument {
val pageIndices: IntRange val pageIndices: IntRange
get() = pages.indices get() = pages.indices
val linesOnPage = ArrayList<Int>() internal val linesPrintedOnPage = ArrayList<Int>()
fun addNewPage(back: Color = DEFAULT_PAGE_BACK) { fun addNewPage(back: Color = DEFAULT_PAGE_BACK) {
pages.add(BTeXPage(back, pageWidth, pageHeight)) pages.add(BTeXPage(back, pageWidth, pageHeight))
linesOnPage.add(0) linesPrintedOnPage.add(0)
} }
fun addNewPageAt(index: Int, back: Color = DEFAULT_PAGE_BACK) { fun addNewPageAt(index: Int, back: Color = DEFAULT_PAGE_BACK) {
pages.add(index, BTeXPage(back, pageWidth, pageHeight)) 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) { fun appendDrawCall(drawCall: BTeXDrawCall) {
pages.last().appendDrawCall(drawCall) pages.last().appendDrawCall(drawCall)
linesOnPage[linesOnPage.lastIndex] += drawCall.lineCount linesPrintedOnPage[linesPrintedOnPage.lastIndex] += drawCall.lineCount
} }
fun appendDrawCall(page: BTeXPage, drawCall: BTeXDrawCall) { fun appendDrawCall(page: BTeXPage, drawCall: BTeXDrawCall) {
page.appendDrawCall(drawCall) page.appendDrawCall(drawCall)
val pagenum = pages.indexOf(page) 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) { fun render(frameDelta: Float, batch: SpriteBatch, page: Int, x: Int, y: Int) {

View File

@@ -9,7 +9,6 @@ import net.torvald.terrarum.App
import net.torvald.terrarum.btex.BTeXDocument import net.torvald.terrarum.btex.BTeXDocument
import net.torvald.terrarum.btex.BTeXDocument.Companion.DEFAULT_PAGE_FORE import net.torvald.terrarum.btex.BTeXDocument.Companion.DEFAULT_PAGE_FORE
import net.torvald.terrarum.btex.BTeXDrawCall import net.torvald.terrarum.btex.BTeXDrawCall
import net.torvald.terrarum.btex.BTeXPage
import net.torvald.terrarum.btex.MovableTypeDrawCall import net.torvald.terrarum.btex.MovableTypeDrawCall
import net.torvald.terrarum.ceilToFloat import net.torvald.terrarum.ceilToFloat
import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.gameitems.ItemID
@@ -89,8 +88,11 @@ object BTeXParser {
private var lastTagAtDepth = Array(24) { "" } private var lastTagAtDepth = Array(24) { "" }
private var pTagCntAtDepth = IntArray(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 data class CptSectInfo(val type: String, var name: String, var pagenum: Int)
private val cptSectStack = ArrayList<CptSect>()
private val indexMap = HashMap<String, Int>() // id to pagenum private val indexMap = HashMap<String, Int>() // id to pagenum
private val cptSectMap = ArrayList<CptSectInfo>() private val cptSectMap = ArrayList<CptSectInfo>()
private var tocPage: Int? = null private var tocPage: Int? = null
@@ -135,9 +137,9 @@ object BTeXParser {
val theTag = tag.uppercase() val theTag = tag.uppercase()
if (tagStack.isNotEmpty() && tagStack.any { textTags.contains(it) } && textTags.contains(theTag)) 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)) 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 (lastTagAtDepth[tagStack.size] != "P") pTagCntAtDepth[tagStack.size] = 0
if (theTag == "P") pTagCntAtDepth[tagStack.size] += 1 if (theTag == "P") pTagCntAtDepth[tagStack.size] += 1
@@ -538,25 +540,7 @@ object BTeXParser {
val pageWidth = doc.textWidth val pageWidth = doc.textWidth
indexMap.keys.toList().sorted().forEach { key -> indexMap.keys.toList().sorted().forEach { key ->
val pageNum = indexMap[key]!!.plus(1).toString() typesetTOCline(key, indexMap[key]!!, handler)
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)
}
}
}
} }
} }
@@ -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, estimate TOC page size, renumber indexMap and cptSectMap if needed, then typeset the toc
if (tocPage != null) { if (tocPage != null) {
// TODO estimate the number of TOC pages // TODO estimate the number of TOC pages
var tocSizeInPages = 1 val tocSizeInPages = (cptSectMap.size + 2) / doc.pageLines
// TODO //
// renumber things // renumber things
if (tocSizeInPages > 1) { if (tocSizeInPages > 1) {
@@ -596,34 +577,12 @@ object BTeXParser {
} }
cptSectMap.forEach { (type, name, pg) -> cptSectMap.forEach { (type, name, pg) ->
val pageNum = pg.plus(1).toString() val indent = if (type == "subsection") 32 else if (type == "section") 16 else 0
val pageNumWidth = getFont().getWidth(pageNum) typesetTOCline(name, pg, handler, indent, tocPage)
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)
}
}
}
} }
} }
} }
@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' @OpenTag // reflective access is impossible with 'private'
fun processElemINDEX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>, siblingIndex: Int) { fun processElemINDEX(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, attribs: HashMap<String, String>, siblingIndex: Int) {
attribs["id"]?.let { 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) 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' @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) 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<String, String>, 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<String, String>, siblingIndex: Int) {
cptSectStack.add(CptSect("section", attribs["alt"], doc.currentPage))
}
@CloseTag // reflective access is impossible with 'private' @CloseTag // reflective access is impossible with 'private'
fun closeElemCHAPTER(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { fun closeElemCHAPTER(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) {
// insert new page for second+ chapters // insert new page for second+ chapters
@@ -697,23 +666,27 @@ object BTeXParser {
val thePar = handler.paragraphBuffer.toString().trim() val thePar = handler.paragraphBuffer.toString().trim()
typesetChapterHeading(thePar, handler, 16) 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() handler.paragraphBuffer.clear()
} }
@CloseTag // reflective access is impossible with 'private' @CloseTag // reflective access is impossible with 'private'
fun closeElemSECTION(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { 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 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() val thePar = handler.paragraphBuffer.toString().trim()
typesetSectionHeading(thePar, handler, 8) 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() handler.paragraphBuffer.clear()
} }
@CloseTag // reflective access is impossible with 'private' @CloseTag // reflective access is impossible with 'private'
fun closeElemP(handler: BTeXHandler, doc: BTeXDocument, theTag: String, uri: String, siblingIndex: Int) { 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 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<BTeXDrawCall>() val drawCalls = ArrayList<BTeXDrawCall>()
var remainder = doc.pageLines - doc.linesOnPage.last() var remainder = doc.pageLines - doc.linesPrintedOnPage.last()
var slugHeight = slugs.height var slugHeight = slugs.height
var linesOut = 0 var linesOut = 0
@@ -771,7 +744,7 @@ object BTeXParser {
val drawCall = BTeXDrawCall( val drawCall = BTeXDrawCall(
0, 0,
doc.linesOnPage[pageNum] * doc.lineHeightInPx, doc.linesPrintedOnPage[pageNum] * doc.lineHeightInPx,
handler.currentTheme, handler.currentTheme,
handler.getSpanColour(), handler.getSpanColour(),
MovableTypeDrawCall(slugs, subset.first, subset.second) MovableTypeDrawCall(slugs, subset.first, subset.second)
@@ -792,7 +765,7 @@ object BTeXParser {
val drawCall = BTeXDrawCall( val drawCall = BTeXDrawCall(
0, 0,
doc.linesOnPage[pageNum] * doc.lineHeightInPx, doc.linesPrintedOnPage[pageNum] * doc.lineHeightInPx,
handler.currentTheme, handler.currentTheme,
handler.getSpanColour(), handler.getSpanColour(),
MovableTypeDrawCall(slugs, subset.first, subset.second) 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 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 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 { companion object {
private const val ZWSP = 0x200B private const val ZWSP = 0x200B

View File

@@ -49,6 +49,10 @@ class BTeXTest : ApplicationAdapter() {
<p>A book is a collection of texts printed in a special way that allows them to be read easily, with <p>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 <a href="btex language">hyperlinks</a>.</p> enumerable pages and insertion of other helpful resources, such as illustrations and <a href="btex language">hyperlinks</a>.</p>
<section alt="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: Bad Recursion BRB RangeError: stack size exceeded</section>
<p>Noice.</p>
<newpage /> <newpage />
<fullpagebox> <fullpagebox>
@@ -58,7 +62,7 @@ class BTeXTest : ApplicationAdapter() {
<chapter>Writing Book Using Pen and Papers</chapter> <chapter>Writing a Book Using Pen and Papers</chapter>
<p><index id="pen and paper" />If you open a book on a writing table, you will be welcomed with a <p><index id="pen and paper" />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.</p> toolbar used to put other book elements, such as chapters and sections.</p>
@@ -66,7 +70,7 @@ class BTeXTest : ApplicationAdapter() {
<chapter>Writing Book Using a Typewriter</chapter> <chapter>Writing a Book Using a Typewriter</chapter>
<p><index id="typewriter" />Typewriters can only write in a single style of font, chapters and <p><index id="typewriter" />Typewriters can only write in a single style of font, chapters and
sections are not available.</p> sections are not available.</p>
@@ -74,7 +78,7 @@ class BTeXTest : ApplicationAdapter() {
<chapter>Writing Book Using a Computer</chapter> <chapter>Writing a Book Using a Computer</chapter>
<p>Writing book using a computer requires the use of the Book Typesetting Engine Extended, or <btex />.</p> <p>Writing book using a computer requires the use of the Book Typesetting Engine Extended, or <btex />.</p>