init.lua 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. --[[
  2. lcdlib based on:
  3. font_lib mod for Minetest - Library to add font display capability
  4. to display_lib mod.
  5. (c) Pierre-Yves Rollo
  6. This program is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. --]]
  17. -- Global variables
  18. lcdlib = {}
  19. lcdlib.registered_fonts = {}
  20. -- Local functions
  21. ------------------
  22. -- Split multiline text into array of lines, with <maxlines> maximum lines.
  23. local function split_lines(text, maxlines)
  24. local splits = text:split("\n")
  25. if maxlines then
  26. local lines = {}
  27. for num = 1,maxlines do
  28. lines[num] = splits[num]
  29. end
  30. return lines
  31. else
  32. return splits
  33. end
  34. end
  35. -- Returns next char, managing ascii and unicode plane 0 (0000-FFFF).
  36. local function get_next_char(text, pos)
  37. pos = pos + 1
  38. local char = text:sub(pos, pos):byte()
  39. -- 1 byte char
  40. if char < 0x80 then
  41. return char, pos
  42. end
  43. -- 4 bytes char not managed
  44. if char >= 0xF0 then
  45. pos = pos + 3
  46. return 0, pos
  47. end
  48. -- 3 bytes char not managed
  49. if char >= 0xE0 then
  50. pos = pos + 2
  51. return 0, pos
  52. end
  53. -- 2 bytes char (little endian)
  54. if char >= 0xC2 then
  55. pos = pos + 1
  56. return (char - 0xC2) * 0x40 + text:sub(pos, pos):byte(), pos
  57. end
  58. -- Not an UTF char
  59. return 0, pos
  60. end
  61. -- Returns font properties to be used according to font_name
  62. local function get_font(font_name)
  63. local font = lcdlib.registered_fonts[font_name]
  64. if font == nil then
  65. local message
  66. if font_name == nil then
  67. message = "No font given"
  68. else
  69. message = "Font \""..font_name.."\" unregistered"
  70. end
  71. if lcdlib.fallback_font == nil then
  72. minetest.log("error", message.." and no other font registered.")
  73. else
  74. minetest.log("info", message..", using font \""..lcdlib.fallback_font.."\".")
  75. font = lcdlib.registered_fonts[lcdlib.fallback_font]
  76. end
  77. end
  78. return font
  79. end
  80. -- API functions
  81. ----------------
  82. -- Computes text size for a given font and text (ignores new lines)
  83. -- @param font_name Font to be used
  84. -- @param text Text to be rendered
  85. -- @return Rendered text (width, height)
  86. function lcdlib.get_text_size(font_name, text)
  87. local char
  88. local width = 0
  89. local pos = 0
  90. local font = get_font(font_name)
  91. if font == nil then
  92. return 0, 0
  93. else
  94. while pos < #text do
  95. char, pos = get_next_char(text, pos)
  96. -- Ignore chars with no texture
  97. if font.widths[char] ~= nil then
  98. width = width + font.widths[char]
  99. end
  100. end
  101. end
  102. return width, font.height
  103. end
  104. --- Builds texture part for a text line
  105. -- @param font_name Font to be used
  106. -- @param text Text to be rendered
  107. -- @param width Width of the texture (extra text is not rendered)
  108. -- @param x Starting x position in texture
  109. -- @param y Vertical position of the line in texture
  110. -- @return Texture string
  111. --> ADD ALIGN
  112. function lcdlib.make_line_texture(font_name, text, width, x, y)
  113. local texture = ""
  114. local char
  115. local pos = 0
  116. local font = get_font(font_name)
  117. if font ~= nil then
  118. while pos < #text do
  119. char, pos = get_next_char(text, pos)
  120. -- Ignore chars with no texture
  121. if font.widths[char] ~= nil then
  122. -- Add image only if it is visible (at least partly)
  123. if x + font.widths[char] >= 0 and x <= width then
  124. texture = texture..
  125. string.format(":%d,%d=font_%s_%04x.png",
  126. x, y, font.name, char)
  127. end
  128. x = x + font.widths[char]
  129. else
  130. print(string.format("Missing char %d (%04x)",char,char))
  131. end
  132. end
  133. end
  134. return texture
  135. end
  136. --- Builds texture for a multiline colored text
  137. -- @param font_name Font to be used
  138. -- @param text Text to be rendered
  139. -- @param texturew Width of the texture (extra text will be truncated)
  140. -- @param textureh Height of the texture
  141. -- @param maxlines Maximum number of lines
  142. -- @param valign Vertical text align ("top" or "center")
  143. -- @param color Color of the text
  144. -- @return Texture string
  145. function lcdlib.make_multiline_texture(font_name, text, width, height,
  146. maxlines, valign, color)
  147. local texture = ""
  148. local lines = {}
  149. local textheight = 0
  150. local y, w, h
  151. h = get_font(font_name).height
  152. for num, line in pairs(split_lines(text, maxlines)) do
  153. if line:byte(1) == 60 then -- '<'
  154. lines[num] = { text = line:sub(2,-1), width = width - 4, height = h, }
  155. else
  156. w, h = lcdlib.get_text_size(font_name, line)
  157. lines[num] = { text = line, width = w, height = h, }
  158. end
  159. textheight = textheight + h
  160. end
  161. if #lines then
  162. if valign == "top" then
  163. y = 0
  164. elseif valign == "bottom" then
  165. y = height - textheight
  166. else
  167. y = (height - textheight) / 2
  168. end
  169. end
  170. for _, line in pairs(lines) do
  171. texture = texture..
  172. lcdlib.make_line_texture(font_name, line.text, width,
  173. (width - line.width) / 2, y)
  174. y = y + line.height
  175. end
  176. texture = string.format("[combine:%dx%d", width, height)..texture
  177. if color then texture = texture.."^[colorize:"..color end
  178. return texture
  179. end
  180. --- Register a new font
  181. -- Textures corresponding to the font should be named after following patern :
  182. -- font_<name>_<code>.png
  183. -- <name> : name of the font
  184. -- <code> : 4 digit hexadecimal unicode of the char
  185. -- If registering different sizes, add size in the font name (e.g. times_10, times_12...)
  186. -- @param height Font height in pixels
  187. -- @param widths Array of character widths in pixel, indexed by unicode number.
  188. function lcdlib.register_font(font_name, height, widths)
  189. if lcdlib.registered_fonts[font_name] ~= nil then
  190. minetest.log("error", "Font \""..font_name.."\" already registered.")
  191. return
  192. end
  193. lcdlib.registered_fonts[font_name] =
  194. { name = font_name, height = height, widths = widths }
  195. -- If no fallback font, set it (so, first font registered will be the default fallback font)
  196. if lcdlib.fallback_font == nil then
  197. lcdlib.fallback_font = font_name
  198. end
  199. end
  200. --- Define the fallback font
  201. -- This font will be used instead of given font if not registered.
  202. -- @param font_name Name of the font to be used as fallback font (has to be registered).
  203. function lcdlib.set_fallback_font(font_name)
  204. if lcdlib.registered_fonts[font_name] == nil then
  205. minetest.log("error", "Fallback font \""..font_name.."\" not registered.")
  206. else
  207. lcdlib.fallback_font = font_name
  208. end
  209. end
  210. --- Standard on_display_update entity callback.
  211. -- Node should have a corresponding display_entity with size, resolution and maxlines fields and
  212. -- optionally valign and color fields
  213. -- @param pos Node position
  214. -- @param objref Object reference of entity
  215. function lcdlib.on_display_update(pos, objref)
  216. local meta = minetest.get_meta(pos)
  217. local text = meta:get_string("display_text")
  218. local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
  219. local entity = objref:get_luaentity()
  220. if entity and ndef.display_entities[entity.name] then
  221. local def = ndef.display_entities[entity.name]
  222. objref:set_properties({
  223. textures={lcdlib.make_multiline_texture(
  224. def.font_name, text, def.size.x*def.resolution.x, def.size.y*def.resolution.y,
  225. def.maxlines, def.valign, def.color)},
  226. visual_size = def.size
  227. })
  228. end
  229. end
  230. dofile(minetest.get_modpath("lcdlib").."/font_default.lua")
  231. dofile(minetest.get_modpath("lcdlib").."/display.lua")