btex: img tag with src attrib

This commit is contained in:
minjaesong
2024-05-16 17:07:36 +09:00
parent e308e9a356
commit efbdc806ea
6 changed files with 142 additions and 29 deletions

View File

@@ -162,18 +162,19 @@
<section>Figures</section> <section>Figures</section>
<p>Figures, or external images can be inserted using the self-closing <code>img</code> tag. <p><index id="img (tag)"/>Figures, or external images can be inserted using the self-closing <code>img</code> tag.
This tag inserts the image starting from the current line; if the size is taller than the This tag inserts the image at the centre of the page, starting from the current line;
remaining lines, the image will be printed onto the next page. Its syntax is as follows:</p> if the size is taller than the remaining lines, the image will be printed onto the next page.
Its syntax is as follows:</p>
<callout align="left" class="code"><!-- <callout align="left" class="code"><!--
-->&lt;img src="web 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;<br/><!--
-->&lt;img gameitem="basegame:33" height="1"/&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.</p> rather than pixels, the width is calculated automatically. Image width wider than the text width will cause an error.</p>
<p>Supported image formats: JPEG, PNG, BMP or TGA</p> <p>Supported image formats: JPEG, PNG, BMP or TGA</p>
@@ -295,6 +296,22 @@
<part>Non-laymen Zone</part>
<chapter alt="The Waiting Process">Why Wait for the Books to be Printed? Why Cant I just Print Them by Myself?</chapter>
<p>The <btex/> 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.</p>
<p>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.</p>
<newpage/> <newpage/>

View File

@@ -48,11 +48,11 @@
<p><index id="btex 언어"/><btex/>(발음: [비ː텍])은 <latex/>과 유사하게 설계된 XML 기반의 마크업 언어이다. <p><index id="btex 언어"/><btex/>(발음: [비ː텍])은 <latex/>과 유사하게 설계된 XML 기반의 마크업 언어이다.
<btex/>은 세세한 스타일 정의문과 조판 설정을 추상화하여, 글을 쓰면서 <latex/> 매크로를 디버깅하는 등의 일 없이 글쓰기에 집중할 수 있도록 설계되었다. 다만 이러한 이유로 미리 정의된 스타일을 뜯어고치는 것은 어렵다.</p> <btex/>은 세세한 스타일 정의문과 조판 설정을 추상화하여, 글을 쓰면서 <latex/> 매크로를 디버깅하는 등의 일 없이 글쓰기에 집중할 수 있도록 설계되었다. 다만 이러한 이유로 미리 정의된 스타일을 뜯어고치는 것은 어렵다.</p>
<p><btex/> 문서는 다음의 다섯 부분으로 나뉘어진다: <!-- <p><btex/> 문서는 다음의 다섯 부분으로 나뉘어진다: <!--
--><a href="btexdoc">스타일 정의문</a>·<!-- --><a href="btexdoc">스타일 정의문</a>·<!--
--><a href="표지 정의문">표지 정의문</a>·<!-- --><a href="표지 정의문">표지 정의문</a>·<!--
--><a href="목차 정의문">목차 정의문</a>·<!-- --><a href="목차 정의문">목차 정의문</a>·<!--
--><a href="원고">원고</a>·<!-- --><a href="원고">원고</a>·<!--
--><a href="색인 페이지">색인 페이지</a>. --><a href="색인 페이지">색인 페이지</a>.
이 중 스타일 정의문과 원고는 필수로 존재하여야 하는 부분이다.</p> 이 중 스타일 정의문과 원고는 필수로 존재하여야 하는 부분이다.</p>
@@ -61,9 +61,9 @@
<p><index id="btexdoc"/>스타일 정의문은 <btex/> 문서의 가장 첫 부분이다. 스타일 정의문의 구조는 다음과 같다.</p> <p><index id="btexdoc"/>스타일 정의문은 <btex/> 문서의 가장 첫 부분이다. 스타일 정의문의 구조는 다음과 같다.</p>
<callout align="left" class="code"><index id="btexdoc (tag)"/><!-- <callout align="left" class="code"><index id="btexdoc (tag)"/><!--
-->&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br/><!-- -->&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br/><!--
-->&lt;!DOCTYPE btexdoc SYSTEM "btexdoc.dtd"&gt;<br/><!-- -->&lt;!DOCTYPE btexdoc SYSTEM "btexdoc.dtd"&gt;<br/><!--
-->&lt;btexdoc cover="hardcover" inner="standard" papersize="standard"&gt; -->&lt;btexdoc cover="hardcover" inner="standard" papersize="standard"&gt;
</callout> </callout>
<p><code>btexdoc</code> 태그는 다음의 속성을 지원한다.</p> <p><code>btexdoc</code> 태그는 다음의 속성을 지원한다.</p>
@@ -77,10 +77,10 @@
<p><index id="표지 정의문"/><code>cover</code> 태그는 책의 표지를 지정한다. 표지가 없는 책이라면 이 부분은 생략 가능하다. 표지 정의문의 구조는 다음과 같다.</p> <p><index id="표지 정의문"/><code>cover</code> 태그는 책의 표지를 지정한다. 표지가 없는 책이라면 이 부분은 생략 가능하다. 표지 정의문의 구조는 다음과 같다.</p>
<callout align="left" class="code"><index id="cover (태그)"/><index id="title (태그)"/><index id="subtitle (태그)"/><index id="author (태그)"/><index id="edition (태그)"/>&lt;cover hue="358"&gt;<br/> <callout align="left" class="code"><index id="cover (태그)"/><index id="title (태그)"/><index id="subtitle (태그)"/><index id="author (태그)"/><index id="edition (태그)"/>&lt;cover hue="358"&gt;<br/>
  &lt;title&gt;책 제목&zwsp;&lt;/title&gt;<br/> &lt;title&gt;책 제목&zwsp;&lt;/title&gt;<br/>
  &lt;subtitle&gt;필요한 경우 부제목&zwsp;&lt;/subtitle&gt;<br/> &lt;subtitle&gt;필요한 경우 부제목&zwsp;&lt;/subtitle&gt;<br/>
  &lt;author&gt;누가 이 책을 집필하였는가&zwsp;&lt;/author&gt;<br/> &lt;author&gt;누가 이 책을 집필하였는가&zwsp;&lt;/author&gt;<br/>
  &lt;edition&gt;필요한 경우 판본 정보&zwsp;&lt;/edition&gt;<br/> &lt;edition&gt;필요한 경우 판본 정보&zwsp;&lt;/edition&gt;<br/>
&lt;/cover&gt; &lt;/cover&gt;
</callout> </callout>
<p>이 중 <code>title</code> 태그만이 필수 태그이다. 표지의 문구는 글자간 간격이 넓게 인쇄된다. 책 제목은 두배 큰 크기로 인쇄된다.</p> <p>이 중 <code>title</code> 태그만이 필수 태그이다. 표지의 문구는 글자간 간격이 넓게 인쇄된다. 책 제목은 두배 큰 크기로 인쇄된다.</p>
@@ -156,6 +156,21 @@
</ul> </ul>
<section>그림</section>
<p><index id="img (태그)"/>외부 이미지는 스스로 닫는 <code>img</code> 태그를 사용해 넣을 수 있다. 그림은 세로로는 현재 줄에서부터, 가로로는 페이지의 중앙에 인쇄된다. 페이지에 남아 있는 줄보다 이미지의 높이가 더 크다면, 이미지는 새로운 페이지에 인쇄된다. <code>img</code> 태그의 구조는 다음과 같다.</p>
<callout align="left" class="code"><!--
-->&lt;img src="http(s)나 file URL" height="8"/&gt;<br/><!--
-->&lt;img fromgame="basegame:gui/small.png" height="4"/&gt;<br/><!--
-->&lt;img gameitem="basegame:33" height="1"/&gt;
</callout>
<p><code>height</code> 속성은 이미지의 높이를 <emph>픽셀 단위가 아닌 줄의 개수</emph>로 지정하어야 한다. 이미지의 너비는 자동으로 계산된다. 텍스트의 너비보다 넓다면 오류가 발생한다.</p>
<p>지원하는 이미지 포맷: JPEG·PNG·BMP·TGA</p>
<chapter>색인 페이지</chapter> <chapter>색인 페이지</chapter>
<p><index id="색인 페이지"/>색인 페이지의 내용은 원고를 읽어 자동으로 채워진다. <p><index id="색인 페이지"/>색인 페이지의 내용은 원고를 읽어 자동으로 채워진다.
@@ -245,12 +260,12 @@
<p>이 태그들은 다음과 같은 상황에 쓰일 수 있다. 아래와 같은 <btex/> 문서가 있다면,</p> <p>이 태그들은 다음과 같은 상황에 쓰일 수 있다. 아래와 같은 <btex/> 문서가 있다면,</p>
<callout align="left" class="code">[en] Send your &lt;v fromgame="GAME_ITEM_HOLOTAPE"/&gt; that contains the manuscript to the publisher via mail to have your books printed.<br/><!-- <callout align="left" class="code">[en] Send your &lt;v fromgame="GAME_ITEM_HOLOTAPE"/&gt; that contains the manuscript to the publisher via mail to have your books printed.<br/><!--
-->[koKR] 원고가 담긴 &lt;veul fromgame="GAME_ITEM_HOLOTAPE"/&gt; 출판사로 우편을 통해 보내야 책이 인쇄됩니다.</callout> -->[koKR] 원고가 담긴 &lt;veul fromgame="GAME_ITEM_HOLOTAPE"/&gt; 출판사로 우편을 통해 보내야 책이 인쇄됩니다.</callout>
<p>변수는 현재 게임 언어에 따라 대입되고, 그 다음 조사처리가 진행된다. 조판이 완료된 문구는 다음과 같다. (파란색은 해당 단어가 태그에 의해 대입되었음을 뜻한다)</p> <p>변수는 현재 게임 언어에 따라 대입되고, 그 다음 조사처리가 진행된다. 조판이 완료된 문구는 다음과 같다. (파란색은 해당 단어가 태그에 의해 대입되었음을 뜻한다)</p>
<callout>[en] Send your <itemname>Holotape</itemname> that contains the manuscript to the publisher via mail to have your books printed.<br/><!-- <callout>[en] Send your <itemname>Holotape</itemname> that contains the manuscript to the publisher via mail to have your books printed.<br/><!--
-->[koKR] 원고가 담긴 <itemname>홀로테이프를</itemname> 출판사로 우편을 통해 보내야 책이 인쇄됩니다.</callout> -->[koKR] 원고가 담긴 <itemname>홀로테이프를</itemname> 출판사로 우편을 통해 보내야 책이 인쇄됩니다.</callout>

View File

@@ -8,9 +8,8 @@
<!ENTITY % OLStyle "CDATA"> <!ENTITY % OLStyle "CDATA">
<!ENTITY % counters "(i|I|1|a|A)"> <!ENTITY % counters "(i|I|1|a|A)">
<!ENTITY % special.extra "img">
<!ENTITY % special.basic "br | span | newpage"> <!ENTITY % special.basic "br | span | newpage">
<!ENTITY % special "%special.basic; | %special.extra;"> <!ENTITY % special "%special.basic;">
<!ENTITY % fontstyle.extra "big | small | font | basefont"> <!ENTITY % fontstyle.extra "big | small | font | basefont">
<!ENTITY % fontstyle.basic "tt | i | b | u | s | strike "> <!ENTITY % fontstyle.basic "tt | i | b | u | s | strike ">
<!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;"> <!ENTITY % fontstyle "%fontstyle.basic; | %fontstyle.extra;">
@@ -45,6 +44,11 @@
<!ENTITY % key-value <!ENTITY % key-value
"key CDATA #REQUIRED "key CDATA #REQUIRED
value CDATA #REQUIRED"> value CDATA #REQUIRED">
<!ENTITY % imgAttrs
"src CDATA #IMPLIED
fromgame CDATA #IMPLIED
gameitem CDATA #IMPLIED
height %Number; #REQUIRED">
<!ENTITY % coreattrs <!ENTITY % coreattrs
"id CDATA #IMPLIED "id CDATA #IMPLIED
class CDATA #IMPLIED class CDATA #IMPLIED
@@ -55,7 +59,7 @@
<!ENTITY % heading "part | chapter | section | subsection"> <!ENTITY % heading "part | chapter | section | subsection">
<!ENTITY % lists "ul | ol"> <!ENTITY % lists "ul | ol">
<!ENTITY % blocktext "pre | anonbreak | callout | center | fullpagebox"> <!ENTITY % blocktext "pre | anonbreak | callout | center | fullpagebox">
<!ENTITY % block "p | %heading; | %lists; | %blocktext;"> <!ENTITY % block "p | %heading; | %lists; | %blocktext; | img">
<!ENTITY % Flow "(#PCDATA | %block; | %inline;)*"> <!ENTITY % Flow "(#PCDATA | %block; | %inline;)*">
<!ELEMENT btexdoc (macrodef?,cover?,tocpage?,manuscript,indexpage?)> <!ELEMENT btexdoc (macrodef?,cover?,tocpage?,manuscript,indexpage?)>
@@ -111,6 +115,8 @@
<!ATTLIST pair %key-value;> <!ATTLIST pair %key-value;>
<!ELEMENT index EMPTY> <!ELEMENT index EMPTY>
<!ATTLIST index %id-only;> <!ATTLIST index %id-only;>
<!ELEMENT img EMPTY>
<!ATTLIST img %imgAttrs;>
<!-- inherited from HTML --> <!-- inherited from HTML -->
<!ELEMENT p %Inline;> <!ELEMENT p %Inline;>

View File

@@ -63,6 +63,8 @@ class BTeXDocument : Disposable {
private fun String.escape() = this.replace("\"", "\\\"") private fun String.escape() = this.replace("\"", "\\\"")
private fun newTempFile(name: String) = FileHandle.tempFile(name)
fun fromFile(fileHandle: FileHandle) = fromFile(fileHandle.file()) fun fromFile(fileHandle: FileHandle) = fromFile(fileHandle.file())
fun fromFile(file: File): BTeXDocument { fun fromFile(file: File): BTeXDocument {
@@ -97,7 +99,7 @@ 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 = 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()) it.exportFileTo(tempFile.file())
val texture = TextureRegion(Texture(tempFile)) val texture = TextureRegion(Texture(tempFile))
doc.pageTextures[page] = texture doc.pageTextures[page] = texture
@@ -236,7 +238,7 @@ class BTeXDocument : Disposable {
pagePixmaps.forEachIndexed { index, pixmap -> pagePixmaps.forEachIndexed { index, pixmap ->
Clustfile(DOM, "$index.png").also { file -> Clustfile(DOM, "$index.png").also { file ->
file.createNewFile() file.createNewFile()
val tempFile = Gdx.files.external("./.btex-export.png") val tempFile = newTempFile("btex-export.png")
PixmapIO.writePNG(tempFile, pixmap, Deflater.BEST_COMPRESSION, false) PixmapIO.writePNG(tempFile, pixmap, Deflater.BEST_COMPRESSION, false)
val outstream = ClustfileOutputStream(file) val outstream = ClustfileOutputStream(file)
outstream.write(tempFile.readBytes()) outstream.write(tempFile.readBytes())

View File

@@ -1,10 +1,13 @@
package net.torvald.btex package net.torvald.btex
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.graphics.Pixmap import com.badlogic.gdx.graphics.Pixmap
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.graphics.g2d.SpriteBatch import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.utils.Disposable import com.badlogic.gdx.utils.Disposable
import com.badlogic.gdx.utils.GdxRuntimeException
import com.jme3.math.FastMath.DEG_TO_RAD import com.jme3.math.FastMath.DEG_TO_RAD
import net.torvald.colourutil.OKLch import net.torvald.colourutil.OKLch
import net.torvald.colourutil.tosRGB import net.torvald.colourutil.tosRGB
@@ -27,15 +30,15 @@ import org.xml.sax.Attributes
import org.xml.sax.InputSource import org.xml.sax.InputSource
import org.xml.sax.SAXParseException import org.xml.sax.SAXParseException
import org.xml.sax.helpers.DefaultHandler import org.xml.sax.helpers.DefaultHandler
import java.io.File import java.io.*
import java.io.FileInputStream import java.net.URL
import java.io.StringReader
import javax.xml.parsers.SAXParserFactory import javax.xml.parsers.SAXParserFactory
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.full.declaredFunctions import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.findAnnotation
/** /**
* Created by minjaesong on 2023-10-28. * Created by minjaesong on 2023-10-28.
*/ */
@@ -919,6 +922,74 @@ object BTeXParser {
paragraphBuffer.append(value) paragraphBuffer.append(value)
} }
@OpenTag // reflective access is impossible with 'private'
fun processElemIMG(handler: BTeXHandler, doc: BTeXDocument, uri: String, attribs: HashMap<String, String>) {
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") else -> throw IllegalArgumentException("Non-object ID char: $c")
} }
} }
private fun makeRandomObjName() = (0 until 12).joinToString("") { "${hashStrMap.random()}" }
} }
} }

View File

@@ -26,9 +26,9 @@ 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.btxbook"