123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- # author: Ethosa
- ## Provides TTF text rendering. Use SDL2_ttf.
- import
- ../thirdparty/sdl2,
- ../thirdparty/sdl2/ttf,
- ../thirdparty/opengl,
- image,
- vector2,
- anchor,
- color,
- nodes_os,
- exceptions,
- unicode
- type
- StyleUnicode* = ref object
- is_url*: bool
- style*: cint
- c*, url*: string
- color*: ColorRef
- StyleText* = ref object
- font*: FontPtr
- rendered*: bool
- spacing*: float
- max_lines*: int
- texture*: GlTextureObj
- chars*: seq[StyleUnicode]
- let URL_COLOR = Color(0.45, 0.45, 0.9)
- proc schar*(c: string, color: ColorRef = Color(1f, 1f, 1f),
- style: cint = TTF_STYLE_NORMAL, is_url: bool = false): StyleUnicode =
- StyleUnicode(c: c, color: color, style: style, is_url: is_url, url: "")
- proc stext*(text: string, color: ColorRef = Color(1f, 1f, 1f),
- style: cint = TTF_STYLE_NORMAL): StyleText =
- result = StyleText(texture: GlTextureObj(size: Vector2()), spacing: 2, max_lines: -1)
- for i in text.utf8():
- result.chars.add(schar(i, color, style))
- result.font = standard_font
- result.rendered = false
- # ------ Operators ------ #
- proc len*(text: StyleText): int =
- text.chars.len()
- proc `$`*(c: StyleUnicode): string =
- c.c
- proc `$`*(text: StyleText): string =
- for i in text.chars:
- result &= $i
- proc `&`*(text: StyleText, c: StyleUnicode): StyleText =
- result = text
- result.chars.add(c)
- proc `&`*(text, t: StyleText): StyleText =
- result = text
- for c in t.chars:
- result.chars.add(c)
- proc `&`*(text: StyleText, t: string): StyleText =
- text & stext(t)
- proc `&`*(text: string, c: StyleUnicode): string =
- text & $c
- proc `&`*(text: string, t: StyleText): string =
- text & $t
- proc `&=`*(text: var StyleText, c: StyleUnicode) =
- text = text & c
- proc `&=`*(text: var StyleText, t: StyleText) =
- text = text & t
- proc `&=`*(text: var string, c: StyleUnicode) =
- text = text & $c
- proc `&=`*(text: var string, t: StyleText) =
- text = text & $t
- proc `&=`*(text: var StyleText, t: string) =
- text &= stext(t)
- proc `[]`*(text: StyleText, index: int): StyleUnicode =
- text.chars[index]
- proc `[]`*[T, U](text: StyleText, slice: HSlice[T, U]): StyleText =
- result = stext""
- for i in text.chars[slice.a..slice.b]:
- result &= i
- # ------ Funcs ------ #
- proc applyStyle*(symbol: StyleUnicode, style: cint, enabled: bool = true) =
- if (symbol.style and style) == 0 and enabled:
- symbol.style = symbol.style or style
- elif (symbol.style and style) != 0 and not enabled:
- symbol.style = symbol.style xor style
- proc toUpper*(text: StyleText): StyleText =
- result = text.deepCopy()
- for i in result.chars:
- i.c = i.c.toUpper()
- proc toLower*(text: StyleText): StyleText =
- for i in text.chars:
- i.c = i.c.toLower()
- proc setColor*(c: StyleUnicode, color: ColorRef) =
- c.color = color
- proc setColor*(text: StyleText, color: ColorRef) =
- for i in text.chars:
- i.color = color
- proc setColor*(text: StyleText, index: int, color: ColorRef) =
- text.chars[index].color = color
- proc setColor*(text: StyleText, s, e: int, color: ColorRef) =
- for i in s..e:
- text.chars[i].color = color
- template styleFunc(setter, style_type: untyped): untyped =
- proc `setter`*(c: StyleUnicode, val: bool = true) =
- c.applyStyle(`style_type`, val)
- proc `setter`*(text: StyleText, val: bool) =
- for i in text.chars:
- i.`setter`(val)
- proc `setter`*(text: StyleText, index: int, val: bool) =
- text.chars[index].`setter`(val)
- proc `setter`*(text: StyleText, s, e: int, val: bool) =
- for i in s..e:
- text.chars[i].`setter`(val)
- styleFunc(setBold, TTF_STYLE_BOLD)
- styleFunc(setItalic, TTF_STYLE_ITALIC)
- styleFunc(setUnderline, TTF_STYLE_UNDERLINE)
- styleFunc(setStrikethrough, TTF_STYLE_STRIKETHROUGH)
- proc setURL*(text: StyleText, s, e: int, url: string) =
- for i in s..e:
- text.chars[i].is_url = true
- text.chars[i].url = url
- text.chars[i].color = URL_COLOR
- proc setFont*(text: StyleText, font: cstring, size: cint) =
- text.font = openFont(font, size)
- proc setFont*(text: StyleText, font: FontPtr) =
- text.font = font
- proc loadFont*(font: cstring, size: cint): FontPtr =
- openFont(font, size)
- # ------ Utils ------ #
- proc splitLines*(text: StyleText): seq[StyleText] =
- result = @[stext""]
- var line = 0
- for i in text.chars:
- if i.c != "\n":
- result[^1].chars.add(i)
- else:
- if text.max_lines == -1 or text.max_lines < line:
- result.add(stext"")
- inc line
- proc split*(text: StyleText, splitval: string): seq[StyleText] =
- result = @[stext""]
- let size = splitval.len
- var i = 0
- while i+size-1 < text.len:
- if $text[i..i+size-1] != splitval:
- result[^1].chars.add(text[i])
- inc i
- else:
- result.add(stext"")
- inc i, size
- proc getTextSize*(text: StyleText): Vector2Obj =
- result = Vector2()
- if not text.font.isNil():
- var
- lines = text.splitLines()
- w: cint
- h: cint
- for line in lines:
- discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
- if result.x < w.float:
- result.x = w.float
- result.y += text.spacing
- result.y += h.float
- result.y -= text.spacing
- proc getCaretPos*(text: StyleText, pos: uint32): tuple[a: Vector2Obj, b: uint16] =
- result = (a: Vector2(), b: 0'u16)
- var tmp = 0'u32
- if not text.font.isNil():
- var
- lines = text.splitLines()
- w: cint
- h: cint
- for line in lines:
- result[0].x = 0f
- result[0].y += text.spacing
- result[0].y += h.float
- for c in $line:
- discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
- result[0].x += w.float
- if result[1] == 0'u16:
- result[1] = h.uint16
- if tmp >= pos:
- result[0].x -= w.float
- return result
- inc tmp
- inc tmp
- if tmp >= pos:
- result[0].y -= text.spacing
- return result
- result[0].y -= text.spacing
- proc getPosUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj,
- text_align: AnchorObj = Anchor(0, 0, 0, 0)): uint32 =
- ## Returns caret position under mouse.
- if not text.font.isNil():
- let
- textsize = text.getTextSize()
- local_pos = global_pos - text_pos
- lines = text.splitLines()
- var
- w: cint
- h: cint
- x: float = 0f
- y: float = 0f
- position = Vector2(-1, -1)
- for line in lines:
- discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
- if local_pos.y >= y and local_pos.y <= y + h.float:
- position.y = y
- x = textsize.x*text_align.x1 - w.Glfloat*text_align.x2
- for c in line.chars:
- discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
- result += 1
- if local_pos.x >= x and local_pos.x <= x+w.float and position.y != -1f:
- position.x = x
- break
- x += w.float
- if position.x != -1f:
- break
- y += text.spacing + h.float
- result += 1
- if position.x == -1f:
- result = 0
- proc getCharUnderPoint*(text: StyleText, global_pos, text_pos: Vector2Obj,
- text_align: AnchorObj = Anchor(0, 0, 0, 0)): tuple[c: StyleUnicode, pos: uint32] =
- let pos = text.getPosUnderPoint(global_pos, text_pos, text_align)
- if pos > 0:
- (c: text.chars[pos-1], pos: pos-1)
- else:
- (c: text.chars[pos], pos: pos)
- # ------ Render ------ #
- proc renderSurface*(text: StyleText, align: AnchorObj): SurfacePtr =
- ## Renders the surface and returns it, if available.
- ##
- ## Arguments:
- ## - `align` -- text align.
- when defined(debug):
- if text.font.isNil():
- raise newException(ResourceError, "Font isn't loaded!")
- if not text.font.isNil() and $text != "":
- let
- lines = text.splitLines()
- textsize = text.getTextSize()
- var
- surface = createRGBSurface(
- 0, textsize.x.cint + 8, textsize.y.cint, 32,
- 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000u32)
- y: cint = 0
- w: cint
- h: cint
- for line in lines:
- discard text.font.sizeUtf8(($line).cstring, addr w, addr h)
- var x = (textsize.x * align.x1 - w.float * align.x2).cint
- for c in line.chars:
- text.font.setFontStyle(c.style)
- discard text.font.sizeUtf8(($c).cstring, addr w, addr h)
- var
- rendered = text.font.renderUtf8Blended(
- ($c).cstring,
- color(uint8(c.color.r * 255), uint8(c.color.g * 255), uint8(c.color.b * 255), uint8(c.color.a * 255)))
- r = rect(x, y, w, h)
- rendered.blitSurface(nil, surface, addr r)
- freeSurface(rendered)
- x += w
- y += h + text.spacing.cint
- return surface
- proc render*(text: StyleText, size: Vector2Obj, align: AnchorObj) =
- ## Translates SDL2 surface to OpenGL texture and frees surface memory.
- var surface = renderSurface(text, align)
- if not surface.isNil():
- text.texture.size.x = surface.w.float
- text.texture.size.y = surface.h.float
- # OpenGL:
- if text.texture.texture == 0'u32:
- glGenTextures(1, text.texture.texture.addr)
- glBindTexture(GL_TEXTURE_2D, text.texture.texture)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA.GLint, surface.w, surface.h,
- 0, GL_RGBA, GL_UNSIGNED_BYTE, surface.pixels)
- # free memory
- surface.freeSurface()
- surface = nil
- text.rendered = true
- proc renderTo*(text: StyleText, pos, size: Vector2Obj, align: AnchorObj) =
- # Show text
- if not text.rendered:
- text.render(size, align)
- var
- pos1 = Vector2(pos)
- size1 = Vector2(size)
- texcord: array[4, Glfloat] = [1f, 1f, 0f, 0f]
- let
- textsize = text.getTextSize()
- if textsize.x < size1.x:
- size1.x = textsize.x
- pos1.x += size.x*align.x1 - textsize.x*align.x2
- if textsize.y < size1.y:
- size1.y = textsize.y
- pos1.y -= size.y*align.y1 - textsize.y*align.y2
- if textsize.x > size1.x:
- let
- x1 = (size1.x*align.x1 - textsize.x*align.x2) / textsize.x
- x2 =
- if x1 > 0.5:
- 1f - ((size1.x*align.x1 - textsize.x*align.x2 + textsize.x) / textsize.x)
- else:
- x1 + (size1.x / textsize.x)
- texcord[0] = abs(x2)
- texcord[2] = abs(x1)
- if textsize.y > size1.y:
- let
- y1 = (size1.y*align.y1 - textsize.y*align.y2) / textsize.y
- y2 =
- if y1 > 0.5:
- 1f - ((size1.y*align.y1 - textsize.y*align.y2 + textsize.y) / textsize.y)
- else:
- y1 + (size1.y / textsize.y)
- texcord[1] = abs(y2)
- texcord[3] = abs(y1)
- glColor4f(1, 1, 1, 1)
- glBindTexture(GL_TEXTURE_2D, text.texture.texture)
- glEnable(GL_TEXTURE_2D)
- glBegin(GL_QUADS)
- glTexCoord2f(texcord[0], texcord[3])
- glVertex2f(pos1.x + size1.x, pos1.y)
- glTexCoord2f(texcord[0], texcord[1])
- glVertex2f(pos1.x + size1.x, pos1.y - size1.y)
- glTexCoord2f(texcord[2], texcord[1])
- glVertex2f(pos1.x, pos1.y - size1.y)
- glTexCoord2f(texcord[2], texcord[3])
- glVertex2f(pos1.x, pos1.y)
- glEnd()
- glDisable(GL_TEXTURE_2D)
- glBindTexture(GL_TEXTURE_2D, 0)
|