diff --git a/lib/TerrarumSansBitmap.jar b/lib/TerrarumSansBitmap.jar index 9704b6ba7..113957c3c 100644 --- a/lib/TerrarumSansBitmap.jar +++ b/lib/TerrarumSansBitmap.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84964d75518a4b8782cf75dfe2fc7bd43e3ce5a7ac2bdb0cac11789b9fbca2a3 -size 205481 +oid sha256:b4d2a294ba3782e26d9b833498916bf8ac5f89ff844f261c8f44496f5057746a +size 207149 diff --git a/src/net/torvald/btex/BTeXDocument.kt b/src/net/torvald/btex/BTeXDocument.kt index ac9967825..9dc2aad2f 100644 --- a/src/net/torvald/btex/BTeXDocument.kt +++ b/src/net/torvald/btex/BTeXDocument.kt @@ -7,6 +7,8 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.TextureRegion import com.badlogic.gdx.utils.Disposable import net.torvald.btex.BTeXDocViewer +import net.torvald.terrarum.btex.BTeXDocument.Companion.HREF_UNDERLINE +import net.torvald.terrarum.btex.BTeXDocument.Companion.UNDERLINE_Y import net.torvald.terrarum.ceilToInt import net.torvald.terrarum.concurrent.ThreadExecutor import net.torvald.terrarum.imagefont.TinyAlphNum @@ -62,8 +64,11 @@ class BTeXDocument : Disposable { val DEFAULT_PAGE_BACK = Color(0xe0dfdb_ff.toInt()) // val DEFAULT_PAGE_FORE = Color(0x0a0706_ff) val DEFAULT_ORNAMENTS_COL = Color(0x3f3c3b_ff) + val HREF_UNDERLINE = Color(0x0033BBff) val ccPagenum = TerrarumSansBitmap.toColorCode(0xf333) + const val UNDERLINE_Y = 22 + private fun String.escape() = this.replace("\"", "\\\"") private fun newTempFile(name: String) = FileHandle.tempFile(name) @@ -143,12 +148,12 @@ class BTeXDocument : Disposable { @Transient private val fontNum = TinyAlphNum fun addNewPage(back: Color = DEFAULT_PAGE_BACK) { - pages.add(BTeXPage(back, pageDimensionWidth, pageDimensionHeight)) + pages.add(BTeXPage(this, back, pageDimensionWidth, pageDimensionHeight)) linesPrintedOnPage.add(0) } fun addNewPageAt(index: Int, back: Color = DEFAULT_PAGE_BACK) { - pages.add(index, BTeXPage(back, pageDimensionWidth, pageDimensionHeight)) + pages.add(index, BTeXPage(this, back, pageDimensionWidth, pageDimensionHeight)) linesPrintedOnPage.add(index, 0) } @@ -332,12 +337,16 @@ class BTeXDocument : Disposable { } data class BTeXClickable( - val posX: Int, val posY: Int, val width: Int, val height: Int, + var posX: Int, var posY: Int, val width: Int, val height: Int, val onClick: (BTeXDocViewer) -> Unit, // val onHover: () -> Unit = {} -) +) { + var deltaX = 0 + var deltaY = 0 +} class BTeXPage( + val doc: BTeXDocument, val back: Color, val width: Int, val height: Int, @@ -360,8 +369,11 @@ class BTeXPage( drawCalls.sortBy { if (it.text != null) 16 else 0 } } + // paint background batch.color = back.cpy().also { it.a = 0.93f } Toolkit.fillArea(batch, x, y, width, height) + + // print texts batch.color = Color.WHITE drawCalls.forEach { it.draw(batch, x + marginH, y + marginV) @@ -387,10 +399,18 @@ class BTeXPage( fun renderToPixmap(pixmap: Pixmap, x: Int, y: Int, marginH: Int, marginV: Int) { drawX = x; drawY = y drawCalls.sortedBy { if (it.text != null) 16 else 0 }.let { drawCalls -> + // paint background val backCol = back.cpy().also { it.a = 0.93f } pixmap.setColor(backCol) pixmap.fill() + // debug underlines on clickableElements + clickableElements.forEach { + pixmap.setColor(HREF_UNDERLINE) + pixmap.drawRectangle(it.posX + doc.pageMarginH, it.posY + doc.pageMarginV, it.width, doc.lineHeightInPx) + } + + // print texts pixmap.setColor(Color.WHITE) drawCalls.forEach { it.drawToPixmap(pixmap, x + marginH, y + marginV) diff --git a/src/net/torvald/btex/BTeXParser.kt b/src/net/torvald/btex/BTeXParser.kt index 577406641..e6c006a88 100644 --- a/src/net/torvald/btex/BTeXParser.kt +++ b/src/net/torvald/btex/BTeXParser.kt @@ -12,11 +12,8 @@ import com.jme3.math.FastMath.DEG_TO_RAD import net.torvald.colourutil.OKLch import net.torvald.colourutil.tosRGB import net.torvald.terrarum.* -import net.torvald.terrarum.btex.BTeXBatchDrawCall -import net.torvald.terrarum.btex.BTeXDocument +import net.torvald.terrarum.btex.* import net.torvald.terrarum.btex.BTeXDocument.Companion.DEFAULT_ORNAMENTS_COL -import net.torvald.terrarum.btex.BTeXDrawCall -import net.torvald.terrarum.btex.TypesetDrawCall import net.torvald.terrarum.gameitems.ItemID import net.torvald.terrarum.langpack.Lang import net.torvald.terrarum.ui.Toolkit @@ -31,6 +28,7 @@ import org.xml.sax.Attributes import org.xml.sax.InputSource import org.xml.sax.SAXParseException import org.xml.sax.helpers.DefaultHandler +import java.awt.SystemColor.text import java.io.* import java.net.URL import javax.xml.parsers.SAXParserFactory @@ -343,7 +341,7 @@ object BTeXParser { private val CODEMODE_END = "${TerrarumSansBitmap.charsetOverrideDefault}$ccDefault${spacingBlockToString(CODE_TAG_MARGIN)}" private val HREF_BEGIN = "$ccHref" - private val HREF_END = "$ccDefault" + private val HREF_END = "${OBJ.codepointToString()}${OBJ_END.codepointToString()}$ccDefault" private val BUCKS_BEGIN = "$ccBucks" private val BUCKS_END = "$ccDefault" @@ -355,6 +353,10 @@ object BTeXParser { private val SPAN_END = "$ccDefault${TerrarumSansBitmap.charsetOverrideDefault}" + private fun CharArray.toSurrogatedString(): String = if (this.size == 1) "${this[0]}" else "${this[0]}${this[1]}" + private fun Int.codepointToString() = Character.toChars(this).toSurrogatedString() + + private fun Color.toCC(): String { return TerrarumSansBitmap.toColorCode( this.r.times(15f).roundToInt(), @@ -1121,58 +1123,57 @@ object BTeXParser { width = doc.textWidth - 2*MARGIN_PARBOX_H, height = doc.pageLines - 1, align = currentAlign - ).forEachIndexed { index, it -> - it.posX += MARGIN_PARBOX_H - it.deltaX += MARGIN_PARBOX_H - - it.posY += doc.lineHeightInPx / 2 - it.deltaY += doc.lineHeightInPx / 2 + ).let { + it.moveObjectsAround(MARGIN_PARBOX_H, doc.lineHeightInPx / 2) // add boxes - it.extraDrawFun = { batch, x, y -> - val width = doc.textWidth - 2 * MARGIN_PARBOX_H.toFloat() - val height = it.lineCount * doc.lineHeightInPx.toFloat() + it.first.forEach { + it.extraDrawFun = { batch, x, y -> + val width = doc.textWidth - 2 * MARGIN_PARBOX_H.toFloat() + val height = it.lineCount * doc.lineHeightInPx.toFloat() - if (height > 0) { - val oldcol = batch.color.cpy() - batch.color = Color(0xccccccff.toInt()) - Toolkit.fillArea( - batch, - x - MARGIN_PARBOX_H, - y - MARGIN_PARBOX_V, - width + 2 * MARGIN_PARBOX_H, - height + 2 * MARGIN_PARBOX_V - ) - batch.color = Color(0x999999ff.toInt()) - Toolkit.drawBoxBorder( - batch, - x - MARGIN_PARBOX_H, - y - MARGIN_PARBOX_V, - width + 2 * MARGIN_PARBOX_H, - height + 2 * MARGIN_PARBOX_V - ) - batch.color = oldcol + if (height > 0) { + val oldcol = batch.color.cpy() + batch.color = Color(0xccccccff.toInt()) + Toolkit.fillArea( + batch, + x - MARGIN_PARBOX_H, + y - MARGIN_PARBOX_V, + width + 2 * MARGIN_PARBOX_H, + height + 2 * MARGIN_PARBOX_V + ) + batch.color = Color(0x999999ff.toInt()) + Toolkit.drawBoxBorder( + batch, + x - MARGIN_PARBOX_H, + y - MARGIN_PARBOX_V, + width + 2 * MARGIN_PARBOX_H, + height + 2 * MARGIN_PARBOX_V + ) + batch.color = oldcol + } } - } - it.extraPixmapDrawFun = { pixmap, x, y -> - val width = doc.textWidth - 2 * MARGIN_PARBOX_H - val height = it.lineCount * doc.lineHeightInPx + it.extraPixmapDrawFun = { pixmap, x, y -> + val width = doc.textWidth - 2 * MARGIN_PARBOX_H + val height = it.lineCount * doc.lineHeightInPx - if (height > 0) { - pixmap.setColor(Color(0xccccccff.toInt())) - pixmap.fillRectangle( - x - MARGIN_PARBOX_H, - y - MARGIN_PARBOX_V, - width + 2 * MARGIN_PARBOX_H, - height + 2 * MARGIN_PARBOX_V - ) - pixmap.setColor(Color(0x999999ff.toInt())) - Toolkit.drawBoxBorderToPixmap(pixmap, - x - MARGIN_PARBOX_H, - y - MARGIN_PARBOX_V, - width + 2 * MARGIN_PARBOX_H, - height + 2 * MARGIN_PARBOX_V - ) + if (height > 0) { + pixmap.setColor(Color(0xccccccff.toInt())) + pixmap.fillRectangle( + x - MARGIN_PARBOX_H, + y - MARGIN_PARBOX_V, + width + 2 * MARGIN_PARBOX_H, + height + 2 * MARGIN_PARBOX_V + ) + pixmap.setColor(Color(0x999999ff.toInt())) + Toolkit.drawBoxBorderToPixmap( + pixmap, + x - MARGIN_PARBOX_H, + y - MARGIN_PARBOX_V, + width + 2 * MARGIN_PARBOX_H, + height + 2 * MARGIN_PARBOX_V + ) + } } } } @@ -1220,20 +1221,14 @@ object BTeXParser { width = doc.textWidth - 2*MARGIN_PARBOX_H, height = doc.pageLines - 1, align = currentAlign - ).forEachIndexed { index, it -> - it.posX += MARGIN_PARBOX_H - it.deltaX += MARGIN_PARBOX_H - - it.posY += doc.lineHeightInPx / 2 - it.deltaY += doc.lineHeightInPx / 2 - } + ).moveObjectsAround(MARGIN_PARBOX_H, doc.lineHeightInPx / 2) clearParBuffer() } @CloseTag fun closeElemANONBREAK(handler: BTeXHandler, doc: BTeXDocument, uri: String, siblingIndex: Int) { - typesetParagraphs("${ccDefault}――――――――――――", handler, align = "left").also {it.first().let { + typesetParagraphs("${ccDefault}――――――――――――", handler, align = "left").also {it.first.first().let { it.posX += (doc.textWidth - it.width) / 2 } } clearParBuffer() @@ -1435,7 +1430,7 @@ object BTeXParser { private fun typesetBookTitle(thePar: String, handler: BTeXHandler) { val label = "\n${TerrarumSansBitmap.toColorCode(15, 15, 15)}" + thePar - typesetParagraphs(getTitleFont(), label, handler, doc.textWidth - 2*MARGIN_TITLE_TEXTS, align = "left").also { + typesetParagraphs(getTitleFont(), label, handler, doc.textWidth - 2*MARGIN_TITLE_TEXTS, align = "left").first.also { val addedLines = it.sumOf { it.lineCount } doc.linesPrintedOnPage[doc.currentPage] += addedLines @@ -1453,7 +1448,7 @@ object BTeXParser { doc.textWidth - 2*MARGIN_TITLE_TEXTS, align = "left" ).also { - it.last().extraDrawFun = { batch, x, y -> + it.first.last().extraDrawFun = { batch, x, y -> val px = x val py = y + doc.lineHeightInPx - 1 val pw = doc.textWidth - 2f * MARGIN_TITLE_TEXTS @@ -1462,7 +1457,7 @@ object BTeXParser { batch.color = Color.WHITE Toolkit.fillArea(batch, px, py, pw, 1f) } - it.last().extraPixmapDrawFun = { pixmap, x, y -> + it.first.last().extraPixmapDrawFun = { pixmap, x, y -> val px = x val py = y + doc.lineHeightInPx - 1 val pw = doc.textWidth - 2 * MARGIN_TITLE_TEXTS @@ -1472,9 +1467,7 @@ object BTeXParser { pixmap.fillRectangle(px, py, pw, 1) } - it.forEach { - it.posX += MARGIN_TITLE_TEXTS - } + it.moveObjectsAround(MARGIN_TITLE_TEXTS, 0) } } @@ -1485,11 +1478,7 @@ object BTeXParser { handler, doc.textWidth - 2*MARGIN_TITLE_TEXTS, align = "left" - ).also { - it.forEach { - it.posX += MARGIN_TITLE_TEXTS - } - } + ).moveObjectsAround(MARGIN_TITLE_TEXTS, 0) } private fun typesetPartHeading(num: String, thePar: String, handler: BTeXHandler, indent: Int = PAR_INDENTATION, width: Int = doc.textWidth) { @@ -1545,12 +1534,11 @@ object BTeXParser { val header = if (num == null) thePar else "$num${spacingBlockToString(9)}$thePar" typesetParagraphs("\n$ccDefault$header", handler, width - indent, align = "left").also { // add indents and adjust text y pos - it.forEach { - it.posX += indent - it.posY -= doc.lineHeightInPx / 2 - } + it.moveObjectsAround(indent, -doc.lineHeightInPx / 2) + + // add ornamental column on the left - it.forEach { + it.first.forEach { it.extraDrawFun = { batch, x, y -> val oldCol = batch.color.cpy() batch.color = DEFAULT_ORNAMENTS_COL.cpy().also { it.a *= bodyTextShadowAlpha } @@ -1592,10 +1580,7 @@ object BTeXParser { private fun typesetSectionHeading(num: String, thePar: String, handler: BTeXHandler, indent: Int = 16, width: Int = doc.textWidth) { typesetParagraphs("\n$ccDefault$num${spacingBlockToString(9)}$thePar", handler, width - indent, align = "left").also { // add indents and adjust text y pos - it.forEach { - it.posX += indent - it.posY -= doc.lineHeightInPx / 2 - } + it.moveObjectsAround(indent, -doc.lineHeightInPx / 2) } } @@ -1606,7 +1591,7 @@ object BTeXParser { height: Int = doc.pageLines, startingPage: Int = doc.currentPage, align: String - ): List { + ): Pair, List> { return typesetParagraphs(getFont(), thePar, handler, width, height, startingPage, align) } @@ -1618,7 +1603,7 @@ object BTeXParser { height: Int = doc.pageLines, startingPage: Int = doc.currentPage, align: String - ): List { + ): Pair, List> { val strat = when (align) { "left" -> TypesettingStrategy.RAGGED_RIGHT "right" -> TypesettingStrategy.RAGGED_LEFT @@ -1630,6 +1615,7 @@ object BTeXParser { var pageNum = startingPage val drawCalls = ArrayList() + val clickables = ArrayList() var remainder = height - doc.linesPrintedOnPage.last() var slugHeight = slugs.height @@ -1653,18 +1639,57 @@ object BTeXParser { doc.appendDrawCall(doc.pages[pageNum], it); drawCalls.add(it) } } + // >>> HREF code here!! <<< val hrefs = parseAndGetHref(textDrawCalls[0], font, handler, posYline, slugs, subset.first, subset.second) - hrefs.forEach { + var objectIsSplit = false + hrefs.forEach { (hrefObj, objSeq) -> // search for: // ..... [OBJ:RSETNFAOON]word setaf // get width of "word" - val searchStr = slugs.typesettedSlugs.subList(subset.first, subset.first + subset.second) - printdbg("HREF searchStr: ${searchStr.joinToString { it.toReadable() }}") -// val clickable = BTeXClickable(it.x, it.y, undefined, doc.lineHeightInPx) { viewer -> -// viewer.gotoIndex(it.hrefTarget) -// } -// doc.appendClickable(doc.pages[pageNum], it) + val searchStrs = slugs.typesettedSlugs.subList(subset.first, subset.first + subset.second) + searchStrs.forEach { str -> + printdbg("1HREF searchStr: ${str.toReadable()}") + printdbg("1HREF object: ${objSeq.toReadable()} (id=${hrefDict[objSeq.toReadable()]})") + + val indexOfSequence = str.indexOfSequence(objSeq) + + val theIndex = if (objectIsSplit) -(objSeq.size + 4) else indexOfSequence + + theIndex?.let { index -> // we never know which line the object appears + val wordOffset = index + objSeq.size + 4 // must be index of starting NUL + // target word is on the current line + if (wordOffset < str.size) { + var wordEnd = wordOffset + 1 // will be right on the ending NUL + // find ending OBJ + while (!(wordEnd >= str.size || str[wordEnd] == OBJ)) { + wordEnd++ + } + val substr = CodepointSequence(str.subList(wordOffset + 1, wordEnd)) + + printdbg("1HREF word: ${substr.toReadable()}") + printdbg("1HREF hrefObj: ${hrefObj}") + + val hrefX = if (objectIsSplit) 0 else hrefObj.x + var hrefY = hrefObj.y; if (objectIsSplit) hrefY += doc.lineHeightInPx + + val clickable = BTeXClickable(hrefX, hrefY, getFont().getWidth(substr), doc.lineHeightInPx) { viewer -> + viewer.gotoIndex(hrefObj.hrefTarget) + } + doc.appendClickable(doc.pages[pageNum], clickable); clickables.add(clickable) + + objectIsSplit = false + } + // target word is on the next line (probably) + else { + printdbg("1HREF object was cut off by the linebreak") + objectIsSplit = true + } + } + + printdbg(" ") + } } + // >>> HREF code ends here!! <<< linesOut += remainder slugHeight -= remainder @@ -1687,9 +1712,53 @@ object BTeXParser { } // >>> HREF code here!! <<< val hrefs = parseAndGetHref(textDrawCalls[0], font, handler, posYline, slugs, subset.first, subset.second) - hrefs.forEach { - val searchStr = slugs.typesettedSlugs.subList(subset.first, subset.first + subset.second) - printdbg("HREF searchStr: ${searchStr.joinToString { it.toReadable() }}") + var objectIsSplit = false + hrefs.forEach { (hrefObj, objSeq) -> + // search for: + // ..... [OBJ:RSETNFAOON]word setaf + // get width of "word" + val searchStrs = slugs.typesettedSlugs.subList(subset.first, subset.first + subset.second) + searchStrs.forEach { str -> + printdbg("2HREF searchStr: ${str.toReadable()}") + printdbg("2HREF object: ${objSeq.toReadable()} (id=${hrefDict[objSeq.toReadable()]})") + + val indexOfSequence = str.indexOfSequence(objSeq) + + val theIndex = if (objectIsSplit) -(objSeq.size + 4) else indexOfSequence + + theIndex?.let { index -> // we never know which line the object appears + val wordOffset = index + objSeq.size + 4 // must be index of starting NUL + // target word is on the current line + if (wordOffset < str.size) { + var wordEnd = wordOffset + 1 // will be right on the ending NUL + // find ending OBJ + while (!(wordEnd >= str.size || str[wordEnd] == OBJ)) { + wordEnd++ + } + val substr = CodepointSequence(str.subList(wordOffset + 1, wordEnd)) + + printdbg("2HREF word: ${substr.toReadable()}") + printdbg("2HREF hrefObj: ${hrefObj}") + + val hrefX = if (objectIsSplit) 0 else hrefObj.x + var hrefY = hrefObj.y; if (objectIsSplit) hrefY += doc.lineHeightInPx + + val clickable = BTeXClickable(hrefX, hrefY, getFont().getWidth(substr), doc.lineHeightInPx) { viewer -> + viewer.gotoIndex(hrefObj.hrefTarget) + } + doc.appendClickable(doc.pages[pageNum], clickable); clickables.add(clickable) + + objectIsSplit = false + } + // target word is on the next line (probably) + else { + printdbg("2HREF object was cut off by the linebreak") + objectIsSplit = true + } + } + + printdbg(" ") + } } // >>> HREF code ends here!! <<< @@ -1704,7 +1773,7 @@ object BTeXParser { // if typesetting the paragraph leaves the first line of new page empty, move the "row cursor" back up 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 to clickables } private fun textToDrawCall(handler: BTeXHandler, posYline: Int, slugs: MovableType, lineStart: Int, lineCount: Int): List { @@ -1743,7 +1812,7 @@ object BTeXParser { c += 1 } - if (!idbuf.startsWith("HREF@")) { + if (idbuf.isNotBlank() && !idbuf.startsWith("HREF@")) { out.add(BTeXDrawCall( doc, x, y, currentTheme, cmd = objDict[idbuf.toString()]?.invoke(textDrawCall) @@ -1765,8 +1834,8 @@ object BTeXParser { slugs: MovableType, lineStart: Int, lineCount: Int - ): List<_HrefObject> { - val out = ArrayList<_HrefObject>() + ): List> { + val out = ArrayList>() slugs.typesettedSlugs.subList(lineStart, lineStart + lineCount).forEachIndexed { lineNumCnt, line -> line.mapIndexed { i, c -> i to c }.filter { it.second == OBJ }.map { it.first }.forEach { xIndex -> @@ -1775,18 +1844,20 @@ object BTeXParser { // get OBJ id val idbuf = StringBuilder() + val cpseq = CodepointSequence() var c = xIndex + 1 while (true) { val codepoint = line[c] if (codepoint == 0xFFF9F) break idbuf.append(codepointToObjIdChar(codepoint)) + cpseq.add(codepoint) c += 1 } - if (idbuf.startsWith("HREF@")) { + if (idbuf.isNotBlank() && idbuf.startsWith("HREF@")) { val id = hrefDict[idbuf.toString()]!! - out.add(_HrefObject(x, y, id)) + out.add(_HrefObject(x, y, id) to cpseq) } } } @@ -1802,13 +1873,9 @@ object BTeXParser { val dotPosEnd = typeWidth - pageNumWidth - dotGap*1.5f typesetParagraphs("$ccDefault$heading$name", handler, typeWidth - pageNumWidth - dotGap, startingPage = pageToWrite ?: doc.currentPage, align = "justify").let { - it.forEach { - it.posX += indentation + it.moveObjectsAround(indentation, 0) -// println("pos: (${it.posX}, ${it.posY})\tTOC: $name") - } - - it.last { it.text != null }.let { call -> + it.first.last { it.text != null }.let { call -> call.extraDrawFun = { batch, x, y -> val oldCol = batch.color.cpy() @@ -1916,10 +1983,12 @@ object BTeXParser { val ccItemName = TerrarumSansBitmap.toColorCode(0xf03b) val ccTargetName = TerrarumSansBitmap.toColorCode(0xf170) + private const val NUL = 0 private const val ZWSP = 0x200B private const val SHY = 0xAD private const val NBSP = 0xA0 private const val OBJ = 0xFFFC + private const val OBJ_END = 0xFFF9F private const val SPACING_BLOCK_ONE = 0xFFFD0 private const val SPACING_BLOCK_SIXTEEN = 0xFFFDF @@ -1966,7 +2035,10 @@ object BTeXParser { idstr.add(0xFFF9F) - return "\uFFFC" + idstr.toUTF8Bytes().toString(Charsets.UTF_8) + spacingBlockToString(width) + if (width != 0) + return "\uFFFC" + idstr.toUTF8Bytes().toString(Charsets.UTF_8) + spacingBlockToString(width) + else + return "\uFFFC" + idstr.toUTF8Bytes().toString(Charsets.UTF_8) } fun Int.toRomanNum(): String = when (this) { @@ -2004,7 +2076,46 @@ object BTeXParser { } } - private fun makeRandomObjName() = (0 until 12).joinToString("") { "${hashStrMap.random()}" } + private fun makeRandomObjName() = (0 until 16).joinToString("") { "${hashStrMap.random()}" } + + // KMP algorithm + internal fun CodepointSequence.indexOfSequence(pattern: CodepointSequence): Int? { + if (pattern.isEmpty()) + throw IllegalArgumentException("Pattern is empty") + if (this.isEmpty()) + throw IllegalArgumentException("Pattern is empty") + + // next[i] stores the index of the next best partial match + val next = IntArray(pattern.size + 1) + for (i in 1 until pattern.size) { + var j = next[i] + + while (j > 0 && this[j] != this[i]) { + j = next[j] + } + + if (j > 0 || this[j] == this[i]) { + next[i + 1] = j + 1 + } + } + + var i = 0 + var j = 0 + while (i < this.size) { + if (j < pattern.size && this[i] == pattern[j]) { + if (++j == pattern.size) { + return i - j + 1 + } + } + else if (j > 0) { + j = next[j] + i-- // since i will be incremented in the next iteration + } + i++ + } + + return null + } } } @@ -2013,6 +2124,20 @@ object BTeXParser { private annotation class CloseTag } +private fun Pair, List>.moveObjectsAround(x: Int, y: Int) { + this.first.forEachIndexed { index, it -> + it.posX += x + it.deltaX += x + it.posY += y + it.deltaY += y + } + this.second.forEachIndexed { index, it -> + it.posX += x + it.deltaX += x + it.posY += y + it.deltaY += y + } +} class BTeXParsingException(s: String) : RuntimeException(s) {