font.nim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. # author: Ethosa
  2. ## Provides TTF text rendering. Use SDL2_ttf.
  3. import
  4. ../thirdparty/sdl2,
  5. ../thirdparty/sdl2/ttf,
  6. ../thirdparty/opengl,
  7. image,
  8. vector2,
  9. anchor,
  10. color,
  11. nodes_os,
  12. exceptions,
  13. unicode
  14. type
  15. StyleUnicode* = ref object
  16. is_url*: bool
  17. style*: cint
  18. c*, url*: string
  19. color*: ColorRef
  20. StyleText* = ref object
  21. font*: FontPtr
  22. rendered*: bool
  23. spacing*: float
  24. max_lines*: int
  25. texture*: GlTextureObj
  26. chars*: seq[StyleUnicode]
  27. let URL_COLOR = Color(0.45, 0.45, 0.9)
  28. proc schar*(c: string, color: ColorRef = Color(1f, 1f, 1f),
  29. style: cint = TTF_STYLE_NORMAL, is_url: bool = false): StyleUnicode =
  30. StyleUnicode(c: c, color: color, style: style, is_url: is_url, url: "")
  31. proc stext*(text: string, color: ColorRef = Color(1f, 1f, 1f),
  32. style: cint = TTF_STYLE_NORMAL): StyleText =
  33. result = StyleText(texture: GlTextureObj(size: Vector2()), spacing: 2, max_lines: -1)
  34. for i in text.utf8():
  35. result.chars.add(schar(i, color, style))
  36. result.font = standard_font
  37. result.rendered = false
  38. # ------ Operators ------ #
  39. proc len*(text: StyleText): int =
  40. text.chars.len()
  41. proc `$`*(c: StyleUnicode): string =
  42. c.c
  43. proc `$`*(text: StyleText): string =
  44. for i in text.chars:
  45. result &= $i
  46. proc `&`*(text: StyleText, c: StyleUnicode): StyleText =
  47. result = text
  48. result.chars.add(c)
  49. proc `&`*(text, t: StyleText): StyleText =
  50. result = text
  51. for c in t.chars:
  52. result.chars.add(c)
  53. proc `&`*(text: StyleText, t: string): StyleText =
  54. text & stext(t)
  55. proc `&`*(text: string, c: StyleUnicode): string =
  56. text & $c
  57. proc `&`*(text: string, t: StyleText): string =
  58. text & $t
  59. proc `&=`*(text: var StyleText, c: StyleUnicode) =
  60. text = text & c
  61. proc `&=`*(text: var StyleText, t: StyleText) =
  62. text = text & t
  63. proc `&=`*(text: var string, c: StyleUnicode) =
  64. text = text & $c
  65. proc `&=`*(text: var string, t: StyleText) =
  66. text = text & $t
  67. proc `&=`*(text: var StyleText, t: string) =
  68. text &= stext(t)
  69. proc `[]`*(text: StyleText, index: int): StyleUnicode =
  70. text.chars[index]
  71. proc `[]`*[T, U](text: StyleText, slice: HSlice[T, U]): StyleText =
  72. result = stext""
  73. for i in text.chars[slice.a..slice.b]:
  74. result &= i
  75. # ------ Funcs ------ #
  76. proc applyStyle*(symbol: StyleUnicode, style: cint, enabled: bool = true) =
  77. if (symbol.style and style) == 0 and enabled:
  78. symbol.style = symbol.style or style
  79. elif (symbol.style and style) != 0 and not enabled:
  80. symbol.style = symbol.style xor style
  81. proc toUpper*(text: StyleText): StyleText =
  82. result = text.deepCopy()
  83. for i in result.chars:
  84. i.c = i.c.toUpper()
  85. proc toLower*(text: StyleText): StyleText =
  86. for i in text.chars:
  87. i.c = i.c.toLower()
  88. proc setColor*(c: StyleUnicode, color: ColorRef) =
  89. c.color = color
  90. proc setColor*(text: StyleText, color: ColorRef) =
  91. for i in text.chars:
  92. i.color = color
  93. proc setColor*(text: StyleText, index: int, color: ColorRef) =
  94. text.chars[index].color = color
  95. proc setColor*(text: StyleText, s, e: int, color: ColorRef) =
  96. for i in s..e:
  97. text.chars[i].color = color
  98. template styleFunc(setter, style_type: untyped): untyped =
  99. proc `setter`*(c: StyleUnicode, val: bool = true) =
  100. c.applyStyle(`style_type`, val)
  101. proc `setter`*(text: StyleText, val: bool) =
  102. for i in text.chars:
  103. i.`setter`(val)
  104. proc `setter`*(text: StyleText, index: int, val: bool) =
  105. text.chars[index].`setter`(val)
  106. proc `setter`*(text: StyleText, s, e: int, val: bool) =
  107. for i in s..e:
  108. text.chars[i].`setter`(val)
  109. styleFunc(setBold, TTF_STYLE_BOLD)
  110. styleFunc(setItalic, TTF_STYLE_ITALIC)
  111. styleFunc(setUnderline, TTF_STYLE_UNDERLINE)
  112. styleFunc(setStrikethrough, TTF_STYLE_STRIKETHROUGH)
  113. proc setURL*(text: StyleText, s, e: int, url: string) =
  114. for i in s..e:
  115. text.chars[i].is_url = true
  116. text.chars[i].url = url
  117. text.chars[i].color = URL_COLOR
  118. proc setFont*(text: StyleText, font: cstring, size: cint) =
  119. text.font = openFont(font, size)
  120. proc setFont*(text: StyleText, font: FontPtr) =
  121. text.font = font
  122. proc loadFont*(font: cstring, size: cint): FontPtr =
  123. openFont(font, size)
  124. # ------ Utils ------ #
  125. proc splitLines*(text: StyleText): seq[StyleText] =
  126. result = @[stext""]
  127. var line = 0
  128. for i in text.chars:
  129. if i.c != "\n":
  130. result[^1].chars.add(i)
  131. else:
  132. if text.max_lines == -1 or text.max_lines < line:
  133. result.add(stext"")
  134. inc line
  135. proc split*(text: StyleText, splitval: string): seq[StyleText] =
  136. result = @[stext""]
  137. let size = splitval.len
  138. var i = 0
  139. while i+size-1 < text.len:
  140. if $text[i..i+size-1] != splitval:
  141. result[^1].chars.add(text[i])
  142. inc i
  143. else:
  144. result.add(stext"")
  145. inc i, size
  146. proc getTextSize*(text: StyleText): Vector2Obj =
  147. result = Vector2()
  148. if not text.font.isNil():
  149. var
  150. lines = text.splitLines()
  151. w: cint
  152. h: cint
  153. for line in lines:
  154. discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
  155. if result.x < w.float:
  156. result.x = w.float
  157. result.y += text.spacing
  158. result.y += h.float
  159. result.y -= text.spacing
  160. proc getCaretPos*(text: StyleText, pos: uint32): tuple[a: Vector2Obj, b: uint16] =
  161. result = (a: Vector2(), b: 0'u16)
  162. var tmp = 0'u32
  163. if not text.font.isNil():
  164. var
  165. lines = text.splitLines()
  166. w: cint
  167. h: cint
  168. for line in lines:
  169. result[0].x = 0f
  170. result[0].y += text.spacing
  171. result[0].y += h.float
  172. for c in $line:
  173. discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
  174. result[0].x += w.float
  175. if result[1] == 0'u16:
  176. result[1] = h.uint16
  177. if tmp >= pos:
  178. result[0].x -= w.float
  179. return result
  180. inc tmp
  181. inc tmp
  182. if tmp >= pos:
  183. result[0].y -= text.spacing
  184. return result
  185. result[0].y -= text.spacing
  186. proc getPosUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj,
  187. text_align: AnchorObj = Anchor(0, 0, 0, 0)): uint32 =
  188. ## Returns caret position under mouse.
  189. if not text.font.isNil():
  190. let
  191. textsize = text.getTextSize()
  192. local_pos = global_pos - text_pos
  193. lines = text.splitLines()
  194. var
  195. w: cint
  196. h: cint
  197. x: float = 0f
  198. y: float = 0f
  199. position = Vector2(-1, -1)
  200. for line in lines:
  201. discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
  202. if local_pos.y >= y and local_pos.y <= y + h.float:
  203. position.y = y
  204. x = textsize.x*text_align.x1 - w.Glfloat*text_align.x2
  205. for c in line.chars:
  206. discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
  207. result += 1
  208. if local_pos.x >= x and local_pos.x <= x+w.float and position.y != -1f:
  209. position.x = x
  210. break
  211. x += w.float
  212. if position.x != -1f:
  213. break
  214. y += text.spacing + h.float
  215. result += 1
  216. if position.x == -1f:
  217. result = 0
  218. proc getCharUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj,
  219. text_align: AnchorObj = Anchor(0, 0, 0, 0)): tuple[c: StyleUnicode, pos: uint32] =
  220. let pos = text.getPosUnderPoint(global_pos, text_pos, text_align)
  221. if pos > 0:
  222. (c: text.chars[pos-1], pos: pos-1)
  223. else:
  224. (c: text.chars[pos], pos: pos)
  225. # ------ Render ------ #
  226. proc renderSurface*(text: StyleText, align: AnchorObj): SurfacePtr =
  227. ## Renders the surface and returns it, if available.
  228. ##
  229. ## Arguments:
  230. ## - `align` -- text align.
  231. when defined(debug):
  232. if text.font.isNil():
  233. raise newException(ResourceError, "Font isn't loaded!")
  234. if not text.font.isNil() and $text != "":
  235. let
  236. lines = text.splitLines()
  237. textsize = text.getTextSize()
  238. var
  239. surface = createRGBSurface(
  240. 0, textsize.x.cint + 8, textsize.y.cint, 32,
  241. 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000u32)
  242. y: cint = 0
  243. w: cint
  244. h: cint
  245. for line in lines:
  246. discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
  247. var x = (textsize.x * align.x1 - w.float * align.x2).cint
  248. for c in line.chars:
  249. text.font.setFontStyle(c.style)
  250. discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
  251. var
  252. rendered = text.font.renderUtf8Blended(
  253. ($c).cstring,
  254. color(uint8(c.color.r * 255), uint8(c.color.g * 255), uint8(c.color.b * 255), uint8(c.color.a * 255)))
  255. r = rect(x, y, w, h)
  256. rendered.blitSurface(nil, surface, addr r)
  257. freeSurface(rendered)
  258. x += w
  259. y += h + text.spacing.cint
  260. return surface
  261. proc render*(text: StyleText, size: Vector2Obj, align: AnchorObj) =
  262. ## Translates SDL2 surface to OpenGL texture and frees surface memory.
  263. var surface = renderSurface(text, align)
  264. if not surface.isNil():
  265. text.texture.size.x = surface.w.float
  266. text.texture.size.y = surface.h.float
  267. # OpenGL:
  268. if text.texture.texture == 0'u32:
  269. glGenTextures(1, text.texture.texture.addr)
  270. glBindTexture(GL_TEXTURE_2D, text.texture.texture)
  271. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
  272. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
  273. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
  274. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
  275. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA.GLint, surface.w, surface.h,
  276. 0, GL_RGBA, GL_UNSIGNED_BYTE, surface.pixels)
  277. # free memory
  278. surface.freeSurface()
  279. surface = nil
  280. text.rendered = true
  281. proc renderTo*(text: StyleText, pos, size: Vector2Obj, align: AnchorObj) =
  282. # Show text
  283. if not text.rendered:
  284. text.render(size, align)
  285. var
  286. pos1 = Vector2(pos)
  287. size1 = Vector2(size)
  288. texcord: array[4, Glfloat] = [1f, 1f, 0f, 0f]
  289. let
  290. textsize = text.getTextSize()
  291. if textsize.x < size1.x:
  292. size1.x = textsize.x
  293. pos1.x += size.x*align.x1 - textsize.x*align.x2
  294. if textsize.y < size1.y:
  295. size1.y = textsize.y
  296. pos1.y -= size.y*align.y1 - textsize.y*align.y2
  297. if textsize.x > size1.x:
  298. let
  299. x1 = (size1.x*align.x1 - textsize.x*align.x2) / textsize.x
  300. x2 =
  301. if x1 > 0.5:
  302. 1f - ((size1.x*align.x1 - textsize.x*align.x2 + textsize.x) / textsize.x)
  303. else:
  304. x1 + (size1.x / textsize.x)
  305. texcord[0] = abs(x2)
  306. texcord[2] = abs(x1)
  307. if textsize.y > size1.y:
  308. let
  309. y1 = (size1.y*align.y1 - textsize.y*align.y2) / textsize.y
  310. y2 =
  311. if y1 > 0.5:
  312. 1f - ((size1.y*align.y1 - textsize.y*align.y2 + textsize.y) / textsize.y)
  313. else:
  314. y1 + (size1.y / textsize.y)
  315. texcord[1] = abs(y2)
  316. texcord[3] = abs(y1)
  317. glColor4f(1, 1, 1, 1)
  318. glBindTexture(GL_TEXTURE_2D, text.texture.texture)
  319. glEnable(GL_TEXTURE_2D)
  320. glBegin(GL_QUADS)
  321. glTexCoord2f(texcord[0], texcord[3])
  322. glVertex2f(pos1.x + size1.x, pos1.y)
  323. glTexCoord2f(texcord[0], texcord[1])
  324. glVertex2f(pos1.x + size1.x, pos1.y - size1.y)
  325. glTexCoord2f(texcord[2], texcord[1])
  326. glVertex2f(pos1.x, pos1.y - size1.y)
  327. glTexCoord2f(texcord[2], texcord[3])
  328. glVertex2f(pos1.x, pos1.y)
  329. glEnd()
  330. glDisable(GL_TEXTURE_2D)
  331. glBindTexture(GL_TEXTURE_2D, 0)