import unicode
import strutils
import sequtils
import sugar
import types/color
import utils/twtstr
import html/dom
type
Formatting* = object
fgcolor*: CellColor
bgcolor*: CellColor
italic*: bool
bold*: bool
underline*: bool
strike*: bool
overline*: bool
Cell* = object of RootObj
formatting*: Formatting
nodes*: seq[Node]
FlexibleCell* = object of Cell
rune*: Rune
FormattingCell = object of Cell
pos: int
FlexibleLine* = object
str*: string
formats: seq[FormattingCell]
FlexibleGrid* = seq[FlexibleLine]
FixedCell* = object of Cell
runes*: seq[Rune]
FixedGrid* = seq[FixedCell]
func newFixedGrid*(w: int, h: int = 1): FixedGrid =
return newSeq[FixedCell](w * h)
func width*(line: FlexibleLine): int =
return line.str.width()
func width*(cell: FixedCell): int =
return cell.runes.width()
func len*(line: FlexibleLine): int =
return line.str.runeLen()
func newFormatting*(): Formatting =
return Formatting(fgcolor: defaultColor, bgcolor: defaultColor)
func `[]`*(line: FlexibleLine, pos: int): FlexibleCell =
result.rune = line.str.runeAtPos(pos)
var i = 0
while i < line.formats.len:
if line.formats[i].pos > pos:
break
inc i
dec i
if i != -1:
result.formatting = line.formats[i].formatting
else:
result.formatting = newFormatting()
func subLine*(line: FlexibleLine, i: int): FlexibleLine =
for f in line.formats:
if f.pos >= i:
result.formats.add(f)
result.str = line.str.runeSubstr(i)
proc setLen*(line: var FlexibleLine, i: int) =
var nf = newSeq[FormattingCell]()
for f in line.formats:
if f.pos < i:
nf.add(f)
line.str = line.str.runeSubstr(0, i)
proc add*(a: var FlexibleLine, b: FlexibleLine) =
let l = a.len
a.formats.add(b.formats.map((x) => FormattingCell(formatting: x.formatting, pos: l + x.pos)))
a.str &= b.str
proc addLine*(grid: var FlexibleGrid) =
grid.add(FlexibleLine())
proc addFormat*(grid: var FlexibleGrid, y: int, format: Formatting) =
grid[y].formats.add(FormattingCell(formatting: format, pos: grid[y].len))
proc addCell*(grid: var FlexibleGrid, y: int, r: Rune) =
grid[y].str &= $r
proc addCell*(grid: var FlexibleGrid, y: int, r: Rune, format: Formatting) =
if grid[y].formats.len == 0 or grid[y].formats[^1].formatting != format:
grid[y].formats.add(FormattingCell(formatting: format, pos: grid[y].len))
grid[y].str &= $r
proc addCell*(grid: var FlexibleGrid, r: Rune, format: Formatting) =
grid.addCell(grid.len - 1, r, format)
template inc_check(i: int) =
inc i
if i >= buf.len:
return i
proc parseAnsiCode*(formatting: var Formatting, buf: string, fi: int): int =
var i = fi
if buf[i] != '\e':
return i
inc_check i
if 0x40 <= int(buf[i]) and int(buf[i]) <= 0x5F:
if buf[i] != '[':
#C1, TODO?
return
inc_check i
let sp = i
#parameter bytes
while 0x30 <= int(buf[i]) and int(buf[i]) <= 0x3F:
inc_check i
let params = buf.substr(sp, i - 1)
let si = i
#intermediate bytes
while 0x20 <= int(buf[i]) and int(buf[i]) <= 0x2F:
inc_check i
let interm = buf.substr(si, i)
let final = buf[i]
#final byte
if 0x40 <= int(buf[i]) and int(buf[i]) <= 0x7E:
inc_check i
case final
of 'm':
if params.len == 0:
formatting = newFormatting()
else:
let sparams = params.split(';')
try:
let ip = sparams.map((x) => parseInt(x))
var pi = 0
while pi < ip.len:
case ip[pi]
of 0:
formatting = newFormatting()
of 1:
formatting.bold = true
of 3:
formatting.italic = true
of 4:
formatting.underline = true
of 9:
formatting.strike = true
of 22:
formatting.bold = false
of 23:
formatting.italic = false
of 29:
formatting.strike = false
of 30..37:
formatting.fgcolor = CellColor(rgb: false, color: uint8(ip[pi]))
of 38:
inc pi
if pi < ip.len:
if ip[pi] == 2:
inc pi
if pi + 2 < ip.len:
let r = uint8(ip[pi])
inc pi
let g = uint8(ip[pi])
inc pi
let b = uint8(ip[pi])
formatting.fgcolor = CellColor(rgb: true, rgbcolor: (r: r, g: g, b: b))
else:
#TODO
inc pi
continue
else:
break
of 39:
formatting.fgcolor = defaultColor
of 40..47:
formatting.bgcolor = CellColor(rgb: false, color: uint8(ip[0]))
of 48:
inc pi
if pi < ip.len:
if ip[pi] == 2:
inc pi
if pi + 2 < ip.len:
let r = uint8(ip[pi])
inc pi
let g = uint8(ip[pi])
inc pi
let b = uint8(ip[pi])
formatting.bgcolor = CellColor(rgb: true, rgbcolor: (r: r, g: g, b: b))
else:
#TODO
inc pi
continue
else:
break
of 49:
formatting.bgcolor = defaultColor
of 53:
formatting.overline = true
of 55:
formatting.overline = false
else: discard
inc pi
except ValueError: discard
else: discard
return i
proc processFormatting*(formatting: var Formatting, cellf: Formatting): string =
if formatting.bold and not cellf.bold:
result &= SGR(22)
if formatting.italic and not cellf.italic:
result &= SGR(23)
if formatting.underline and not cellf.underline:
result &= SGR(24)
if formatting.strike and not cellf.strike:
result &= SGR(29)
if formatting.overline and not cellf.overline:
result &= SGR(55)
if cellf.fgcolor != formatting.fgcolor:
var color = cellf.fgcolor
if color.rgb:
let rgb = color.rgbcolor
result &= SGR(38, 2, rgb.r, rgb.g, rgb.b)
elif color == defaultColor:
result &= SGR()
formatting = newFormatting()
else:
result &= SGR(color.color)
if cellf.bgcolor != formatting.bgcolor:
var color = cellf.bgcolor
if color.rgb:
let rgb = color.rgbcolor
result &= SGR(48, 2, rgb.r, rgb.g, rgb.b)
elif color == defaultColor:
result &= SGR()
formatting = newFormatting()
else:
result &= SGR(color.color)
if not formatting.bold and cellf.bold:
result &= SGR(1)
if not formatting.italic and cellf.italic:
result &= SGR(3)
if not formatting.underline and cellf.underline:
result &= SGR(4)
if not formatting.strike and cellf.strike:
result &= SGR(9)
if not formatting.overline and cellf.overline:
result &= SGR(53)
formatting = cellf