latex.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. --- Code related to LaTeX generation.
  2. -- @module advtrains_doc_integration.latex
  3. local latex = {}
  4. local utils = advtrains_doc_integration.utils
  5. --- Escape string in LaTeX.
  6. -- @tparam string str The string to escape.
  7. -- @treturn string The escaped string.
  8. function latex.escape(str)
  9. return (string.gsub(str, ".", {
  10. ["&"] = [[\&]],
  11. ["%"] = [[\%]],
  12. ["$"] = [[\$]],
  13. ["#"] = [[\#]],
  14. ["_"] = [[\_]],
  15. ["{"] = [[\{]],
  16. ["}"] = [[\}]],
  17. ["~"] = [[\textasciitilde]],
  18. ["^"] = [[\textasciicircum]],
  19. ["\\"] = [[\textbackslash]],
  20. }))
  21. end
  22. --- Escape a translated string in LaTeX.
  23. -- @tparam string lang The language to use for server-side translation
  24. -- @tparam string str The string to escape.
  25. -- @treturn The escaped string.
  26. function latex.escape_translated(lang, str)
  27. return latex.escape(minetest.get_translated_string(lang, str))
  28. end
  29. local function SL(str)
  30. return latex.escape_translated("en", str)
  31. end
  32. --- Describe a color in LaTeX.
  33. -- @tparam string cstr The color string.
  34. -- @treturn string The string describing the color.
  35. function latex.describe_color(cstr)
  36. local color = string.match(cstr,"^#(%x%x%x%x%x%x)$")
  37. cstr = SL(cstr)
  38. if color then
  39. color = SL(string.upper(color))
  40. return string.format([[\tikz \definecolor{c}{HTML}{%s} \draw[fill=c] (0,0) rectangle (1em,1em); \texttt{%s}]], color, cstr)
  41. else
  42. return string.format([[\texttt{%s}]], cstr)
  43. end
  44. end
  45. --- Describe a coupler in LaTeX.
  46. -- @tparam string coupler The name of the coupler.
  47. -- @treturn string The string describing the coupler.
  48. function latex.describe_coupler(coupler)
  49. local name = utils.get_coupler_name(coupler)
  50. if name then
  51. return ([[%s (\texttt{%s})]]):format(SL(name), SL(coupler))
  52. else
  53. return ([[\texttt{%s}]]):format(SL(coupler))
  54. end
  55. end
  56. --- Describe a wagon prototype in LaTeX.
  57. -- @tparam string itemname The item name of the wagon prototype.
  58. -- @treturn string The description of the prototype.
  59. function latex.describe_wagon_prototype(itemname)
  60. local prototype = advtrains_doc_integration.prototypes[itemname]
  61. local wname = ItemStack(itemname):get_short_description()
  62. local st = {string.format([[
  63. \documentclass{article}
  64. \usepackage[a4paper,margin=1in,bottom=1.5in]{geometry}
  65. \usepackage[T1]{fontenc}
  66. \usepackage{tikz}
  67. \usepackage{booktabs,multirow,tabularx}
  68. \renewcommand{\arraystretch}{1.5}
  69. \usepackage{hyperref}
  70. \hypersetup{pdftitle={Wagon Datasheet: %s}}
  71. \title{Wagon Datasheet}
  72. \author{%s}
  73. \setlength{\parindent}{0pt}
  74. \begin{document}
  75. \maketitle
  76. ]], SL(wname), SL(wname))}
  77. table.insert(st, [[\section{Basic Information}]])
  78. if prototype.longdesc then
  79. table.insert(st, SL(prototype.longdesc) .. "\n")
  80. end
  81. table.insert(st, [[\begin{tabularx}{\textwidth}{l X}]])
  82. table.insert(st, string.format([[Itemstring & \texttt{%s}\\]], SL(itemname)))
  83. if prototype.drives_on then
  84. local i0 = #st+1
  85. local count = 0
  86. for k in pairs(prototype.drives_on) do
  87. table.insert(st, string.format([[& \texttt{%s}\\]], SL(k)))
  88. count = count + 1
  89. end
  90. if count > 0 then
  91. st[i0] = string.format([[Drives on %s]], st[i0])
  92. end
  93. end
  94. if prototype.wagon_span then
  95. table.insert(st, string.format([[Wagon span & %d mm\\]], prototype.wagon_span*2000))
  96. end
  97. if prototype.max_speed then
  98. table.insert(st, string.format([[Maximum speed & %d m/s\\]], prototype.max_speed))
  99. end
  100. table.insert(st, string.format([[Motive power & %s\\]], prototype.is_locomotive and "Present" or "Absent"))
  101. if prototype.horn_sound then
  102. table.insert(st, string.format([[Horn sound & \texttt{%s}\\]], SL(prototype.horn_sound.name)))
  103. else
  104. table.insert(st, [[Horn sound & Undefined\\]])
  105. end
  106. if prototype.mesh then
  107. table.insert(st, string.format([[Mesh & \texttt{%s}\\]], SL(prototype.mesh)))
  108. end
  109. if prototype.textures then
  110. local i0 = #st+1
  111. local count = 0
  112. for _, i in pairs(prototype.textures) do
  113. table.insert(st, string.format([[& \texttt{%s}\\]], SL(i)))
  114. count = count + 1
  115. end
  116. if count > 0 then
  117. st[i0] = string.format([[Textures %s]], st[i0])
  118. end
  119. end
  120. do
  121. local i0 = #st+1
  122. local count = 0
  123. for _, i in ipairs(prototype.drops or {}) do
  124. local item = ItemStack(i)
  125. if not item:is_empty() then
  126. local desc = string.format([[\texttt{%s}]], SL(item:get_name()))
  127. if item:is_known() then
  128. desc = SL(item:get_short_description())
  129. end
  130. table.insert(st, string.format([[& %s: %d\\]], desc, item:get_count()))
  131. count = count + 1
  132. end
  133. end
  134. if count > 0 then
  135. st[i0] = [[Drops ]] .. st[i0]
  136. else
  137. table.insert(st, [[Drops & Nothing \\]])
  138. end
  139. end
  140. table.insert(st, [[\end{tabularx}]])
  141. table.insert(st, [[\section{Coupler Compatibility}]])
  142. do
  143. local fcouplers = prototype.coupler_types_front
  144. local rcouplers = prototype.coupler_types_back
  145. local ccouplers = {}
  146. local lcouplers = {}
  147. local couplerid = {}
  148. local flim, rlim
  149. for k in pairs(fcouplers or {}) do
  150. flim = true
  151. ccouplers[k] = true
  152. end
  153. for k in pairs(rcouplers or {}) do
  154. rlim = true
  155. ccouplers[k] = true
  156. end
  157. for k in pairs(ccouplers) do
  158. local desc = latex.describe_coupler(k)
  159. table.insert(lcouplers, desc)
  160. couplerid[desc] = k
  161. end
  162. table.sort(lcouplers)
  163. table.insert(st, [[
  164. \begin{tabularx}{\textwidth}{X c c}
  165. \toprule
  166. \multirow[t]{2}{*}{\bfseries Coupler Type} & \multicolumn{2}{c}{\bfseries Compatibility}\\
  167. \cmidrule(lr){2-3}
  168. & {\bfseries Front Coupler} & {\bfseries Rear Coupler}\\\midrule
  169. ]])
  170. if not (fcouplers and rcouplers) then
  171. local fd = fcouplers and "" or [[$\bullet$]]
  172. local rd = rcouplers and "" or [[$\bullet$]]
  173. table.insert(st, string.format([[\textit{Universal}&%s&%s\\]], fd, rd))
  174. end
  175. for i = 1, #lcouplers do
  176. local cdesc = lcouplers[i]
  177. local cid = couplerid[cdesc]
  178. local fd, rd = "", ""
  179. if flim then
  180. fd = fcouplers[cid] and [[$\bullet$]] or ""
  181. elseif not fcouplers then
  182. fd = [[$\Uparrow$]]
  183. end
  184. if rlim then
  185. rd = rcouplers[cid] and [[$\bullet$]] or ""
  186. elseif not rcouplers then
  187. rd = [[$\Uparrow$]]
  188. end
  189. table.insert(st, string.format([[%s&%s&%s\\]], cdesc, fd, rd))
  190. end
  191. table.insert(st, [[\bottomrule]])
  192. table.insert(st, [[\end{tabularx}]])
  193. end
  194. local hasinv = prototype.has_inventory
  195. local hasseats = prototype.max_seats>0
  196. local taliquid = prototype.techage_liquid_capacity or 0
  197. if hasinv or hasseats or taliquid>0 then
  198. table.insert(st, [[\section{Wagon Capacity}]])
  199. if hasseats then
  200. table.insert(st, [[
  201. \begin{tabularx}{\textwidth}{X c c}
  202. \toprule
  203. {\bfseries Seat Group} & {\bfseries Driver Stand} & {\bfseries Seat Count}\\\midrule
  204. ]])
  205. for _, d in pairs(prototype.seat_groups) do
  206. table.insert(st, string.format([[%s & %s & %d\\]], SL(d.name), d.driving_ctrl_access and [[$\bullet$]] or "", d.count))
  207. end
  208. table.insert(st, [[\bottomrule]])
  209. table.insert(st, [[\end{tabularx}]])
  210. end
  211. if hasinv then
  212. if next(prototype.inventory_list_sizes or {}) ~= nil then
  213. table.insert(st, [[
  214. \begin{tabularx}{\textwidth}{X c}
  215. \toprule
  216. {\bfseries Inventory Name} & {\bfseries Capacity}\\\midrule
  217. ]])
  218. for k, v in pairs(prototype.inventory_list_sizes) do
  219. table.insert(st, string.format([[\texttt{%s} & %d\\]], SL(k), v))
  220. end
  221. table.insert(st, [[\bottomrule]])
  222. table.insert(st, [[\end{tabularx}]])
  223. else
  224. table.insert(st, [[This wagon has an inventory of unknown size.]])
  225. end
  226. end
  227. if taliquid > 0 then
  228. table.insert(st, string.format([[
  229. \begin{tabularx}{\textwidth}{X l}
  230. {Liquid Capacity (Techage)} & %d
  231. \end{tabularx}
  232. ]], taliquid))
  233. end
  234. end
  235. if prototype.set_livery then
  236. if prototype.livery_definition then
  237. table.insert(st, [[\section{Multi-Component Liveries}]])
  238. local components = prototype.livery_definition.components
  239. local presets = prototype.livery_definition.presets
  240. table.insert(st, [[\subsection*{Components}]])
  241. table.insert(st, [[\begin{itemize}]])
  242. for _, c in ipairs(components) do
  243. table.insert(st, string.format([[\item %s]], SL(c.description)))
  244. end
  245. table.insert(st, [[\end{itemize}]])
  246. for _, p in ipairs(presets) do
  247. table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(p.description)))
  248. table.insert(st, [[
  249. \begin{tabularx}{\textwidth}{X c}
  250. \toprule
  251. {\bfseries Component} & {\bfseries Color} \\\midrule
  252. ]])
  253. for _, c in ipairs(p.livery_stack.layers) do
  254. local cdesc = SL(components[c.component].description)
  255. table.insert(st, string.format([[%s & %s\\]], cdesc, latex.describe_color(c.color)))
  256. end
  257. table.insert(st, [[
  258. \bottomrule
  259. \end{tabularx}
  260. ]])
  261. end
  262. else
  263. table.insert(st, [[\section{Livery System (Bike Painter)}]])
  264. table.insert(st, [[This wagon can be painted by the bike painter.]])
  265. end
  266. end
  267. local dlxlivdef = prototype.dlxtrains_livery
  268. if dlxlivdef then
  269. table.insert(st, [[
  270. \section{DlxTrains Livery Sytem}
  271. This wagon can be customized with DlxTrains' livery system.
  272. ]])
  273. end
  274. local atlivdef = prototype.advtrains_livery_tools
  275. if atlivdef then
  276. table.insert(st, [[\section{Advtrains Livery Tool (Marnack)}]])
  277. for _, tname in ipairs(atlivdef.template_names) do
  278. local tdef = atlivdef.templates[tname]
  279. table.insert(st, string.format([[\subsection*{Template: %s}]], SL(tname)))
  280. table.insert(st, SL(tdef.notes))
  281. table.insert(st, "")
  282. if #tdef.overlays > 0 then
  283. table.insert(st, "This template contains the following components:")
  284. table.insert(st, [[\begin{itemize}]])
  285. for _, overlay in ipairs(tdef.overlays) do
  286. table.insert(st, string.format([[\item %s]], SL(overlay.name)))
  287. end
  288. table.insert(st, [[\end{itemize}]])
  289. else
  290. table.insert(st, "This template does not appear to contain any (customizable) component.")
  291. end
  292. end
  293. for _, lname in ipairs(atlivdef.livery_names) do
  294. local ldef = atlivdef.liveries[lname]
  295. local tname = ldef.livery_template_name
  296. table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(lname)))
  297. table.insert(st, string.format([[Template: %s]], SL(tname)))
  298. table.insert(st, "")
  299. table.insert(st, [[
  300. \begin{tabularx}{\textwidth}{X c}
  301. \toprule
  302. {\bfseries Component} & {\bfseries Color}\\\midrule]])
  303. for _, overlay in pairs(ldef.overlays) do
  304. local cname = atlivdef.templates[tname].overlays[overlay.id].name
  305. table.insert(st, string.format([[%s & %s\\]], SL(cname), latex.describe_color(overlay.color)))
  306. end
  307. table.insert(st, [[
  308. \bottomrule
  309. \end{tabularx}
  310. ]])
  311. end
  312. end
  313. table.insert(st, [[
  314. \end{document}
  315. ]])
  316. return table.concat(st, "\n")
  317. end
  318. return latex