btex: img tag with fromgame attrib

This commit is contained in:
minjaesong
2024-05-16 22:16:46 +09:00
parent efbdc806ea
commit c7ca5a61c8
6 changed files with 74 additions and 50 deletions

View File

@@ -169,12 +169,12 @@
<callout align="left" class="code"><!-- <callout align="left" class="code"><!--
-->&lt;img src="http(s) or file URL" height="8"/&gt;<br/><!-- -->&lt;img src="http(s) or file URL" height="8"/&gt;<br/><!--
-->&lt;img fromgame="basegame:gui/small.png" height="4"/&gt;<br/><!-- -->&lt;img fromgame="basegame:gui/small.png" height="4"/&gt;
-->&lt;img gameitem="basegame:33" height="1"/&gt;
</callout> </callout>
<p>The <code>height</code> attribute specifies the height of the image <emph>in the number of lines</emph>, <p>The <code>height</code> attribute specifies the height of the image <emph>in the number of lines</emph>,
rather than pixels, the width is calculated automatically. Image width wider than the text width will cause an error.</p> rather than pixels, the width is calculated automatically; image width wider than the text width
will cause an error. The tag optionally takes caption attribute which prints a text below the image.</p>
<p>Supported image formats: JPEG, PNG, BMP or TGA</p> <p>Supported image formats: JPEG, PNG, BMP or TGA</p>
@@ -304,7 +304,9 @@
system is there because the book is being printed in the background on separate threads 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 (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 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 the gameplay experience.</p>
<p>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 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.</p> with errors, the mail containing details of the errors will be sent instead.</p>

View File

@@ -142,7 +142,7 @@
<li><code>left</code> — 문단을 좌측정렬함</li> <li><code>left</code> — 문단을 좌측정렬함</li>
<li><code>right</code> — 문단을 우측정렬함</li> <li><code>right</code> — 문단을 우측정렬함</li>
<li><code>center</code> — 문단을 중앙정렬함</li> <li><code>center</code> — 문단을 중앙정렬함</li>
<li><code>justify</code> — 문단을 양정렬함. 기본값</li> <li><code>justify</code> — 문단을 양정렬함. 기본값</li>
</ul> </ul>
<p><code>p</code>, <code>span</code>, <code>callout</code> 태그는 추가로 <code>class="code"</code> 속성을 지원한다. 이 속성이 적용된 문구는 <span class="code">코드꼴(code font)</span>을 사용하여 인자된다.</p> <p><code>p</code>, <code>span</code>, <code>callout</code> 태그는 추가로 <code>class="code"</code> 속성을 지원한다. 이 속성이 적용된 문구는 <span class="code">코드꼴(code font)</span>을 사용하여 인자된다.</p>
@@ -162,11 +162,10 @@
<callout align="left" class="code"><!-- <callout align="left" class="code"><!--
-->&lt;img src="http(s)나 file URL" height="8"/&gt;<br/><!-- -->&lt;img src="http(s)나 file URL" height="8"/&gt;<br/><!--
-->&lt;img fromgame="basegame:gui/small.png" height="4"/&gt;<br/><!-- -->&lt;img fromgame="basegame:gui/small.png" height="4"/&gt;
-->&lt;img gameitem="basegame:33" height="1"/&gt;
</callout> </callout>
<p><code>height</code> 속성은 이미지의 높이를 <emph>픽셀 단위가 아닌 줄의 개수</emph>로 지정하어야 한다. 이미지의 너비는 자동으로 계산된다. 텍스트의 너비보다 넓다면 오류가 발생한다.</p> <p><code>height</code> 속성은 이미지의 높이를 <emph>픽셀 단위가 아닌 줄의 개수</emph>로 지정하어야 한다. 이미지의 너비는 자동으로 계산되며, 텍스트의 너비보다 넓다면 오류가 발생한다. 이미지 하단에 문구를 출력하려면 <code>caption</code> 속성을 사용할 수 있다.</p>
<p>지원하는 이미지 포맷: JPEG·PNG·BMP·TGA</p> <p>지원하는 이미지 포맷: JPEG·PNG·BMP·TGA</p>
@@ -268,8 +267,15 @@
-->[koKR] 원고가 담긴 <itemname>홀로테이프를</itemname> 출판사로 우편을 통해 보내야 책이 인쇄됩니다.</callout> -->[koKR] 원고가 담긴 <itemname>홀로테이프를</itemname> 출판사로 우편을 통해 보내야 책이 인쇄됩니다.</callout>
<part>일반인 출입금지</part>
<chapter>왜 책이 인쇄될 때까지 기다려야 하나요? 인쇄기를 직접 사용할 수는 없나요?</chapter>
<p><btex/> 엔진은 빠른 물건이 아니다. 책을 하나 조판하려면 최소 몇 초가 필요하다. “대기 시스템”은 별도의 스레드 풀에서 (멀티스레딩으로 작동함) 책이 인쇄되도록 하여 게임플레이를 방해하지 않도록 도입되었다. 이것이 없다면 책이 한번 인쇄될 때마다 게임이 ‘응답 없음’ 상태가 될 것이고, 이는 인게임 경험에 매우 부정적으로 작용한다.</p>
<p>인쇄 프로세스가 오류 없이 종료되었다면, 우편 시스템을 통해 플레이어에게 완성된 책을 배송한다. 오류가 있다면 오류 정보를 담은 우편이 대신 배송된다.</p>
<p>상기와 같은 이유로 “인쇄기”는 플레이어에게 직접적으로 제공되지 않고, “출판사”와 우편을 통해 간접적으로 이용하는 형태로 제공된다.</p>
<newpage/> <newpage/>

View File

@@ -48,6 +48,7 @@
"src CDATA #IMPLIED "src CDATA #IMPLIED
fromgame CDATA #IMPLIED fromgame CDATA #IMPLIED
gameitem CDATA #IMPLIED gameitem CDATA #IMPLIED
caption %Text; #IMPLIED
height %Number; #REQUIRED"> height %Number; #REQUIRED">
<!ENTITY % coreattrs <!ENTITY % coreattrs
"id CDATA #IMPLIED "id CDATA #IMPLIED

View File

@@ -99,7 +99,8 @@ class BTeXDocument : Disposable {
Clustfile(DOM, "/${page}.png").also { Clustfile(DOM, "/${page}.png").also {
if (!it.exists()) throw IllegalStateException("No file '${page}.png' on the archive") if (!it.exists()) throw IllegalStateException("No file '${page}.png' on the archive")
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 // val tempFile = newTempFile("btex-import") // must create new file descriptor for every page, or else every page will share a single file descriptor which cause problems
val tempFile = Gdx.files.external("./btex-import.tmp") // tmpfs not working???
it.exportFileTo(tempFile.file()) it.exportFileTo(tempFile.file())
val texture = TextureRegion(Texture(tempFile)) val texture = TextureRegion(Texture(tempFile))
doc.pageTextures[page] = texture doc.pageTextures[page] = texture

View File

@@ -928,21 +928,27 @@ object BTeXParser {
val imgHeight = doc.lineHeightInPx * heightInLines - 6 val imgHeight = doc.lineHeightInPx * heightInLines - 6
val btexObjName = "IMG@${makeRandomObjName()}" val btexObjName = "IMG@${makeRandomObjName()}"
val img = attribs["src"] val img = attribs["src"]
val fromgame = attribs["fromgame"]
val caption = attribs["caption"]
if (listOf(img, fromgame).count { it != null } != 1) {
throw IllegalArgumentException()
}
// image overflowing? // image overflowing?
if (doc.pageLines - doc.linesPrintedOnPage.last() < heightInLines) if (doc.pageLines - doc.linesPrintedOnPage.last() < heightInLines)
doc.addNewPage() doc.addNewPage()
if (img != null) { val tempFile = FileHandle.tempFile("btex_$btexObjName")
val tempFile = FileHandle.tempFile("btex_$btexObjName") try {
try {
val inputPixmap = if (img.startsWith("file://")) { val inputPixmap = if (img != null) {
// printdbg("Using local file ${img.substring(7)}") if (img.startsWith("file://")) {
// printdbg("Using local file ${img.substring(7)}")
Pixmap(Gdx.files.absolute(img.substring(7))) Pixmap(Gdx.files.absolute(img.substring(7)))
} }
else { else {
// printdbg("Downloading image $img") // printdbg("Downloading image $img")
BufferedInputStream(URL(img).openStream()).use { `in` -> BufferedInputStream(URL(img).openStream()).use { `in` ->
FileOutputStream(tempFile.file()).use { fileOutputStream -> FileOutputStream(tempFile.file()).use { fileOutputStream ->
val dataBuffer = ByteArray(1024) val dataBuffer = ByteArray(1024)
@@ -954,39 +960,47 @@ object BTeXParser {
} }
Pixmap(tempFile).also { App.disposables.add(it) } 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) { } else if (fromgame != null) {
catch (e: GdxRuntimeException) { } val moduleName = fromgame.substringBefore(':')
finally { val modulePath = fromgame.substringAfter(':')
tempFile.delete() Pixmap(ModMgr.getGdxFile(moduleName, modulePath))
} }
else throw InternalError()
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")
if (caption != null)
typesetParagraphs("$ccDefault$caption", handler, align = "center")
}
catch (e: IOException) { }
catch (e: GdxRuntimeException) { }
finally {
tempFile.delete()
} }
} }
@@ -1697,7 +1711,7 @@ object BTeXParser {
val dotGap = 10 val dotGap = 10
val dotPosEnd = typeWidth - pageNumWidth - dotGap*1.5f val dotPosEnd = typeWidth - pageNumWidth - dotGap*1.5f
typesetParagraphs("$ccDefault$heading$name", handler, typeWidth, startingPage = pageToWrite ?: doc.currentPage, align = "justify").let { typesetParagraphs("$ccDefault$heading$name", handler, typeWidth - pageNumWidth - dotGap, startingPage = pageToWrite ?: doc.currentPage, align = "justify").let {
it.forEach { it.forEach {
it.posX += indentation it.posX += indentation

View File

@@ -27,11 +27,11 @@ import kotlin.system.measureTimeMillis
class BTeXTest : ApplicationAdapter() { class BTeXTest : ApplicationAdapter() {
// val filePath = "btex.xml" // val filePath = "btex.xml"
// val filePath = "btex_ko.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/en/daniel_defoe_robinson_crusoe.xml"
// val filePath = "literature/ruRU/anton_chekhov_palata_no_6.xml" // val filePath = "literature/ruRU/anton_chekhov_palata_no_6.xml"
// val filePath = "literature/koKR/yisang_nalgae.btxbook" // val filePath = "literature/koKR/yisang_nalgae.xml"
private lateinit var document: BTeXDocument private lateinit var document: BTeXDocument