From efbdc806ea3626e8d8d829d9af31e35cd482545f Mon Sep 17 00:00:00 2001 From: minjaesong Date: Thu, 16 May 2024 17:07:36 +0900 Subject: [PATCH] btex: img tag with src attrib --- assets/mods/basegame/books/btex.xml | 27 ++++++-- assets/mods/basegame/books/btex_ko.xml | 43 ++++++++---- btexdoc.dtd | 12 +++- src/net/torvald/btex/BTeXDocument.kt | 6 +- src/net/torvald/btex/BTeXParser.kt | 79 +++++++++++++++++++++- src/net/torvald/terrarum/tests/BTeXTest.kt | 4 +- 6 files changed, 142 insertions(+), 29 deletions(-) diff --git a/assets/mods/basegame/books/btex.xml b/assets/mods/basegame/books/btex.xml index 11d948bb0..18989fcdf 100644 --- a/assets/mods/basegame/books/btex.xml +++ b/assets/mods/basegame/books/btex.xml @@ -162,18 +162,19 @@
Figures
-

Figures, or external images can be inserted using the self-closing img tag. - This tag inserts the image starting from the current line; if the size is taller than the - remaining lines, the image will be printed onto the next page. Its syntax is as follows:

+

Figures, or external images can be inserted using the self-closing img tag. + This tag inserts the image at the centre of the page, starting from the current line; + if the size is taller than the remaining lines, the image will be printed onto the next page. + Its syntax is as follows:

<img src="web URL" height="8"/>
<img src="http(s) or file URL" height="8"/>
<img fromgame="basegame:gui/small.png" height="4"/>
<img gameitem="basegame:33" height="1"/>

The height attribute specifies the height of the image in the number of lines, - rather than pixels.

+ rather than pixels, the width is calculated automatically. Image width wider than the text width will cause an error.

Supported image formats: JPEG, PNG, BMP or TGA

@@ -295,6 +296,22 @@ + Non-laymen Zone + + Why Wait for the Books to be Printed? Why Can’t I just Print Them by Myself? + +

The engine is not fast; it takes at least a few seconds to print a book. The “waiting” + system is there because the book is being printed in the background on separate threads + (yes, they are multi-threaded!) to not interfere with the normal gameplay, or else the players will + encounter the freezing every time the book is being printed, and this would be a huge minus towards + the gameplay experience. If the process exits without any error, the mailing system will be + notified and will send the mail containing finished books to the player; if the process exits + with errors, the mail containing details of the errors will be sent instead.

+ +

For this reason the “printing press” is not exposed to the player, they only get to interact with it + indirectly through the “publisher” via mail.

+ + diff --git a/assets/mods/basegame/books/btex_ko.xml b/assets/mods/basegame/books/btex_ko.xml index d673f82f7..967be8ba4 100644 --- a/assets/mods/basegame/books/btex_ko.xml +++ b/assets/mods/basegame/books/btex_ko.xml @@ -48,11 +48,11 @@

(발음: [비ː텍])은 과 유사하게 설계된 XML 기반의 마크업 언어이다. 은 세세한 스타일 정의문과 조판 설정을 추상화하여, 글을 쓰면서 매크로를 디버깅하는 등의 일 없이 글쓰기에 집중할 수 있도록 설계되었다. 다만 이러한 이유로 미리 정의된 스타일을 뜯어고치는 것은 어렵다.

문서는 다음의 다섯 부분으로 나뉘어진다: 스타일 정의문·표지 정의문·목차 정의문·원고·색인 페이지. + -->스타일 정의문·표지 정의문·목차 정의문·원고·색인 페이지. 이 중 스타일 정의문과 원고는 필수로 존재하여야 하는 부분이다.

@@ -61,9 +61,9 @@

스타일 정의문은 문서의 가장 첫 부분이다. 스타일 정의문의 구조는 다음과 같다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE btexdoc SYSTEM "btexdoc.dtd">
<btexdoc cover="hardcover" inner="standard" papersize="standard"> + --><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE btexdoc SYSTEM "btexdoc.dtd">
<btexdoc cover="hardcover" inner="standard" papersize="standard">

btexdoc 태그는 다음의 속성을 지원한다.

@@ -77,10 +77,10 @@

cover 태그는 책의 표지를 지정한다. 표지가 없는 책이라면 이 부분은 생략 가능하다. 표지 정의문의 구조는 다음과 같다.

<cover hue="358">
-   <title>책 제목&zwsp;</title>
-   <subtitle>필요한 경우 부제목&zwsp;</subtitle>
-   <author>누가 이 책을 집필하였는가&zwsp;</author>
-   <edition>필요한 경우 판본 정보&zwsp;</edition>
+ <title>책 제목&zwsp;</title>
+ <subtitle>필요한 경우 부제목&zwsp;</subtitle>
+ <author>누가 이 책을 집필하였는가&zwsp;</author>
+ <edition>필요한 경우 판본 정보&zwsp;</edition>
</cover>

이 중 title 태그만이 필수 태그이다. 표지의 문구는 글자간 간격이 넓게 인쇄된다. 책 제목은 두배 큰 크기로 인쇄된다.

@@ -156,6 +156,21 @@ +
그림
+ +

외부 이미지는 스스로 닫는 img 태그를 사용해 넣을 수 있다. 그림은 세로로는 현재 줄에서부터, 가로로는 페이지의 중앙에 인쇄된다. 페이지에 남아 있는 줄보다 이미지의 높이가 더 크다면, 이미지는 새로운 페이지에 인쇄된다. img 태그의 구조는 다음과 같다.

+ + <img src="http(s)나 file URL" height="8"/>
<img fromgame="basegame:gui/small.png" height="4"/>
<img gameitem="basegame:33" height="1"/> +
+ +

height 속성은 이미지의 높이를 픽셀 단위가 아닌 줄의 개수로 지정하어야 한다. 이미지의 너비는 자동으로 계산된다. 텍스트의 너비보다 넓다면 오류가 발생한다.

+ +

지원하는 이미지 포맷: JPEG·PNG·BMP·TGA

+ + 색인 페이지

색인 페이지의 내용은 원고를 읽어 자동으로 채워진다. @@ -245,12 +260,12 @@

이 태그들은 다음과 같은 상황에 쓰일 수 있다. 아래와 같은 문서가 있다면,

[en] Send your <v fromgame="GAME_ITEM_HOLOTAPE"/> that contains the manuscript to the publisher via mail to have your books printed.
[koKR] 원고가 담긴 <veul fromgame="GAME_ITEM_HOLOTAPE"/> 출판사로 우편을 통해 보내야 책이 인쇄됩니다.
+ -->[koKR] 원고가 담긴 <veul fromgame="GAME_ITEM_HOLOTAPE"/> 출판사로 우편을 통해 보내야 책이 인쇄됩니다.

변수는 현재 게임 언어에 따라 대입되고, 그 다음 조사처리가 진행된다. 조판이 완료된 문구는 다음과 같다. (파란색은 해당 단어가 태그에 의해 대입되었음을 뜻한다)

[en] Send your Holotape that contains the manuscript to the publisher via mail to have your books printed.
[koKR] 원고가 담긴 홀로테이프를 출판사로 우편을 통해 보내야 책이 인쇄됩니다.
+ -->[koKR] 원고가 담긴 홀로테이프를 출판사로 우편을 통해 보내야 책이 인쇄됩니다. diff --git a/btexdoc.dtd b/btexdoc.dtd index 5fcd17a4e..5ff3ee26b 100644 --- a/btexdoc.dtd +++ b/btexdoc.dtd @@ -8,9 +8,8 @@ - - + @@ -45,6 +44,11 @@ + - + @@ -111,6 +115,8 @@ + + diff --git a/src/net/torvald/btex/BTeXDocument.kt b/src/net/torvald/btex/BTeXDocument.kt index ec1e6a33a..4fc687c06 100644 --- a/src/net/torvald/btex/BTeXDocument.kt +++ b/src/net/torvald/btex/BTeXDocument.kt @@ -63,6 +63,8 @@ class BTeXDocument : Disposable { private fun String.escape() = this.replace("\"", "\\\"") + private fun newTempFile(name: String) = FileHandle.tempFile(name) + fun fromFile(fileHandle: FileHandle) = fromFile(fileHandle.file()) fun fromFile(file: File): BTeXDocument { @@ -97,7 +99,7 @@ class BTeXDocument : Disposable { Clustfile(DOM, "/${page}.png").also { if (!it.exists()) throw IllegalStateException("No file '${page}.png' on the archive") - val tempFile = Gdx.files.external("./.btex-import.png") // must create new file descriptor for every page, or else every page will share a single file descriptor which cause problems + val tempFile = newTempFile("btex-import.png") // must create new file descriptor for every page, or else every page will share a single file descriptor which cause problems it.exportFileTo(tempFile.file()) val texture = TextureRegion(Texture(tempFile)) doc.pageTextures[page] = texture @@ -236,7 +238,7 @@ class BTeXDocument : Disposable { pagePixmaps.forEachIndexed { index, pixmap -> Clustfile(DOM, "$index.png").also { file -> file.createNewFile() - val tempFile = Gdx.files.external("./.btex-export.png") + val tempFile = newTempFile("btex-export.png") PixmapIO.writePNG(tempFile, pixmap, Deflater.BEST_COMPRESSION, false) val outstream = ClustfileOutputStream(file) outstream.write(tempFile.readBytes()) diff --git a/src/net/torvald/btex/BTeXParser.kt b/src/net/torvald/btex/BTeXParser.kt index 999d2bd88..8357e9adb 100644 --- a/src/net/torvald/btex/BTeXParser.kt +++ b/src/net/torvald/btex/BTeXParser.kt @@ -1,10 +1,13 @@ package net.torvald.btex +import com.badlogic.gdx.Gdx import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Pixmap +import com.badlogic.gdx.graphics.Texture import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.utils.Disposable +import com.badlogic.gdx.utils.GdxRuntimeException import com.jme3.math.FastMath.DEG_TO_RAD import net.torvald.colourutil.OKLch import net.torvald.colourutil.tosRGB @@ -27,15 +30,15 @@ import org.xml.sax.Attributes import org.xml.sax.InputSource import org.xml.sax.SAXParseException import org.xml.sax.helpers.DefaultHandler -import java.io.File -import java.io.FileInputStream -import java.io.StringReader +import java.io.* +import java.net.URL import javax.xml.parsers.SAXParserFactory import kotlin.math.roundToInt import kotlin.reflect.KFunction import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.findAnnotation + /** * Created by minjaesong on 2023-10-28. */ @@ -919,6 +922,74 @@ object BTeXParser { paragraphBuffer.append(value) } + @OpenTag // reflective access is impossible with 'private' + fun processElemIMG(handler: BTeXHandler, doc: BTeXDocument, uri: String, attribs: HashMap) { + val heightInLines = attribs["height"]!!.toInt() + val imgHeight = doc.lineHeightInPx * heightInLines - 6 + val btexObjName = "IMG@${makeRandomObjName()}" + val img = attribs["src"] + + // image overflowing? + if (doc.pageLines - doc.linesPrintedOnPage.last() < heightInLines) + doc.addNewPage() + + if (img != null) { + val tempFile = FileHandle.tempFile("btex_$btexObjName") + try { + + val inputPixmap = if (img.startsWith("file://")) { +// printdbg("Using local file ${img.substring(7)}") + Pixmap(Gdx.files.absolute(img.substring(7))) + } + else { +// printdbg("Downloading image $img") + BufferedInputStream(URL(img).openStream()).use { `in` -> + FileOutputStream(tempFile.file()).use { fileOutputStream -> + val dataBuffer = ByteArray(1024) + var bytesRead: Int + while ((`in`.read(dataBuffer, 0, 1024).also { bytesRead = it }) != -1) { + fileOutputStream.write(dataBuffer, 0, bytesRead) + } + } + } + Pixmap(tempFile).also { App.disposables.add(it) } + } + + val imgWidth = (imgHeight.toFloat() / inputPixmap.height * inputPixmap.width).roundToInt() + + if (imgWidth > doc.textWidth) + throw RuntimeException("Image width ($imgWidth) is larger than the text width (${doc.textWidth})") + + val drawCallObj = { parentText: BTeXDrawCall -> object : BTeXBatchDrawCall(imgWidth, (heightInLines - 1).coerceAtLeast(0), parentText) { + private lateinit var inputTexture: Texture + override fun draw(doc: BTeXDocument, batch: SpriteBatch, x: Float, y: Float, font: TerrarumSansBitmap?) { + if (!::inputTexture.isInitialized) { + inputTexture = Texture(inputPixmap).also { App.disposables.add(it) } + } + val destX = (x + (doc.pageDimensionWidth - imgWidth) / 2) + val destY = y + 1 + batch.draw(inputTexture, destX, destY, imgWidth.toFloat(), imgHeight.toFloat()) + } + override fun drawToPixmap(doc: BTeXDocument, pixmap: Pixmap, x: Int, y: Int, font: TerrarumSansBitmap?) { + val destX = x + val destY = y + 1 + pixmap.drawPixmap(inputPixmap, 0, 0, inputPixmap.width, inputPixmap.height, destX, destY, imgWidth, imgHeight) + } + } } + + objDict[btexObjName] = { text: BTeXDrawCall -> drawCallObj(text) } + objWidthDict[btexObjName] = imgWidth + + typesetParagraphs(objectMarkerWithWidth(btexObjName, imgWidth), handler, align = "center") + } + catch (e: IOException) { } + catch (e: GdxRuntimeException) { } + finally { + tempFile.delete() + } + } + } + @@ -1827,6 +1898,8 @@ object BTeXParser { else -> throw IllegalArgumentException("Non-object ID char: $c") } } + + private fun makeRandomObjName() = (0 until 12).joinToString("") { "${hashStrMap.random()}" } } } diff --git a/src/net/torvald/terrarum/tests/BTeXTest.kt b/src/net/torvald/terrarum/tests/BTeXTest.kt index c0a7b81e9..4bdfdb6a3 100644 --- a/src/net/torvald/terrarum/tests/BTeXTest.kt +++ b/src/net/torvald/terrarum/tests/BTeXTest.kt @@ -26,9 +26,9 @@ import kotlin.system.measureTimeMillis */ class BTeXTest : ApplicationAdapter() { - val filePath = "btex.xml" +// val filePath = "btex.xml" // val filePath = "btex_ko.xml" -// val filePath = "test.xml" + val filePath = "test.xml" // val filePath = "literature/en/daniel_defoe_robinson_crusoe.xml" // val filePath = "literature/ruRU/anton_chekhov_palata_no_6.xml" // val filePath = "literature/koKR/yisang_nalgae.btxbook"