about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--res/config.toml2
-rw-r--r--src/buffer/cell.nim284
-rw-r--r--src/display/pager.nim2
-rw-r--r--src/display/term.nim19
-rw-r--r--src/types/color.nim94
-rw-r--r--src/utils/twtstr.nim41
6 files changed, 229 insertions, 213 deletions
diff --git a/res/config.toml b/res/config.toml
index ae1d5984..10b246a4 100644
--- a/res/config.toml
+++ b/res/config.toml
@@ -27,7 +27,7 @@ emulate-overline = true
 alt-screen = "auto"
 highlight-color = "cyan"
 double-width-ambiguous = false
-minimum-contrast = 100
+minimum-contrast = 75
 force-clear = false
 set-title = true
 
diff --git a/src/buffer/cell.nim b/src/buffer/cell.nim
index a147a420..feaaf668 100644
--- a/src/buffer/cell.nim
+++ b/src/buffer/cell.nim
@@ -1,7 +1,5 @@
-import sequtils
-import streams
-import strutils
-import sugar
+import options
+import tables
 
 import css/stylednode
 import types/color
@@ -64,16 +62,23 @@ iterator items*(grid: FixedGrid): FixedCell {.inline.} =
 proc len*(grid: FixedGrid): int = grid.cells.len
 proc high*(grid: FixedGrid): int = grid.cells.high
 
-const FormatCodes*: array[FormatFlags, tuple[s: int, e: int]] = [
-  FLAG_BOLD: (1, 22),
-  FLAG_ITALIC: (3, 23),
-  FLAG_UNDERLINE: (4, 24),
-  FLAG_REVERSE: (7, 27),
-  FLAG_STRIKE: (9, 29),
-  FLAG_OVERLINE: (53, 55),
-  FLAG_BLINK: (5, 25),
+const FormatCodes*: array[FormatFlags, tuple[s, e: uint8]] = [
+  FLAG_BOLD: (1u8, 22u8),
+  FLAG_ITALIC: (3u8, 23u8),
+  FLAG_UNDERLINE: (4u8, 24u8),
+  FLAG_REVERSE: (7u8, 27u8),
+  FLAG_STRIKE: (9u8, 29u8),
+  FLAG_OVERLINE: (53u8, 55u8),
+  FLAG_BLINK: (5u8, 25u8),
 ]
 
+const FormatCodeMap = block:
+  var res: Table[uint8, tuple[flag: FormatFlags, reverse: bool]]
+  for x in FormatFlags:
+    res[FormatCodes[x][0]] = (x, false)
+    res[FormatCodes[x][1]] = (x, true)
+  res
+
 template flag_template(format: Format, val: bool, flag: FormatFlags) =
   if val: format.flags.incl(flag)
   else: format.flags.excl(flag)
@@ -148,116 +153,7 @@ proc insertFormat*(line: var FlexibleLine, pos, i: int, format: Format, node: St
 proc addFormat*(line: var FlexibleLine, pos: int, format: Format, node: StyledNode = nil) =
   line.formats.add(FormatCell(format: format, node: node, pos: pos))
 
-template inc_check(i: int) =
-  inc i
-  if i >= buf.len:
-    return i
-
-proc handleAnsiCode(format: var Format, final: char, params: string) =
-  case final
-  of 'm':
-    if params.len == 0:
-      format = newFormat()
-    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:
-            format = newFormat()
-          of 1: format.bold = true
-          of 3: format.italic = true
-          of 4: format.underline = true
-          of 5: format.blink = true
-          of 7: format.reverse = true
-          of 9: format.strike = true
-          of 22: format.bold = false
-          of 23: format.italic = false
-          of 25: format.blink = false
-          of 27: format.reverse = false
-          of 29: format.strike = false
-          of 30..37: format.fgcolor = uint8(ip[pi]).cellColor()
-          of 38:
-            inc pi
-            if pi < ip.len:
-              if ip[pi] == 2:
-                inc pi
-                if pi + 2 < ip.len:
-                  let r = ip[pi]
-                  inc pi
-                  let g = ip[pi]
-                  inc pi
-                  let b = ip[pi]
-                  format.fgcolor = rgb(r, g, b).cellColor()
-              else:
-                #TODO
-                inc pi
-                continue
-            else:
-              break
-          of 39:
-            format.fgcolor = defaultColor
-          of 40..47:
-            format.bgcolor = uint8(ip[0]).cellColor()
-          of 48:
-            inc pi
-            if pi < ip.len:
-              if ip[pi] == 2:
-                inc pi
-                if pi + 2 < ip.len:
-                  let r = ip[pi]
-                  inc pi
-                  let g = ip[pi]
-                  inc pi
-                  let b = ip[pi]
-                  format.bgcolor = rgb(r, g, b).cellColor()
-              else:
-                #TODO
-                inc pi
-                continue
-            else:
-              break
-          of 49: format.bgcolor = defaultColor
-          of 53: format.overline = true
-          of 55: format.overline = false
-          else: discard
-          inc pi
-      except ValueError: discard
-  else: discard
-
-proc parseAnsiCode*(format: var Format, 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:
-    format.handleAnsiCode(final, params)
-
-  return i
-
+# https://www.ecma-international.org/wp-content/uploads/ECMA-48_5th_edition_june_1991.pdf
 type
   AnsiCodeParseState* = enum
     PARSE_START, PARSE_PARAMS, PARSE_INTERM, PARSE_FINAL, PARSE_DONE
@@ -266,11 +162,122 @@ type
     state*: AnsiCodeParseState
     params: string
 
+proc getParam(parser: AnsiCodeParser, i: var int, colon = false): string =
+  while i < parser.params.len and
+      not (parser.params[i] == ';' or colon and parser.params[i] == ':'):
+    result &= parser.params[i]
+    inc i
+  if i < parser.params.len:
+    inc i
+
+template getParamU8(parser: AnsiCodeParser, i: var int,
+    colon = false): uint8 =
+  if i < parser.params.len:
+    return false
+  let u = parseUInt8(parser.getParam(i))
+  if u.isNone:
+    return false
+  u.get
+
+proc parseSGRDefColor(parser: AnsiCodeParser, format: var Format,
+    i: var int, isfg: bool): bool =
+  let u = parser.getParamU8(i, colon = true)
+  if u == 2:
+    let param0 = parser.getParamU8(i, colon = true)
+    if i < parser.params.len:
+      let r = param0
+      let g = parser.getParamU8(i, colon = true)
+      let b = parser.getParamU8(i, colon = true)
+      if isfg:
+        format.fgcolor = cellColor(rgb(r, g, b))
+      else:
+        format.bgcolor = cellColor(rgb(r, g, b))
+    else:
+      let n = param0
+      if isfg:
+        format.fgcolor = cellColor(rgb(n, n, n))
+      else:
+        format.bgcolor = cellColor(rgb(n, n, n))
+  else:
+    if u in 0u8..7u8:
+      if isfg:
+        format.fgcolor = cellColor(u + 30)
+      else:
+        format.bgcolor = cellColor(u + 40)
+    elif u in 8u8..15u8:
+      format.bold = true
+      if isfg:
+        format.fgcolor = cellColor(u + 22)
+      else:
+        format.bgcolor = cellColor(u + 22)
+    elif u in 16u8..231u8:
+      #16 + 36 × r + 6 × g + b
+      discard
+    else:
+      discard
+
+proc parseSGRColor(parser: AnsiCodeParser, format: var Format,
+    i: var int, u: uint8): bool =
+  if u in 30u8..37u8:
+    format.fgcolor = u.cellColor()
+  elif u == 38:
+    return parser.parseSGRDefColor(format, i, isfg = true)
+  elif u == 39:
+    format.fgcolor = defaultColor
+  elif u in 40u8..47u8:
+    format.bgcolor = u.cellColor()
+  elif u == 48:
+    return parser.parseSGRDefColor(format, i, isfg = false)
+  elif u == 49:
+    format.bgcolor = defaultColor
+  elif u in 90u8..97u8:
+    format.fgcolor = cellColor(u - 60u8)
+    format.bold = true
+  elif u in 100u8..107u8:
+    format.bgcolor = cellColor(u - 60u8)
+    format.bold = true
+  else:
+    return false
+  return true
+
+proc parseSGRAspect(parser: AnsiCodeParser, format: var Format,
+    i: var int): bool =
+  let u = parser.getParamU8(i)
+  if u in FormatCodeMap:
+    let entry = FormatCodeMap[u]
+    if entry.reverse:
+      format.flags.excl(entry.flag)
+    else:
+      format.flags.incl(entry.flag)
+    return true
+  elif u == 0:
+    format = newFormat()
+  else:
+    return parser.parseSGRColor(format, i, u)
+
+proc parseSGR(parser: AnsiCodeParser, format: var Format) =
+  if parser.params.len == 0:
+    format = newFormat()
+  else:
+    var i = 0
+    while i < parser.params.len:
+      if not parser.parseSGRAspect(format, i):
+        break
+      inc i
+
+proc parseControlFunction(parser: var AnsiCodeParser, format: var Format,
+    f: char) =
+  case f
+  of 'm':
+    parser.parseSGR(format)
+  else: discard # unknown
+
 proc reset*(parser: var AnsiCodeParser) =
   parser.state = PARSE_START
   parser.params = ""
 
-proc parseAnsiCode*(parser: var AnsiCodeParser, format: var Format, c: char): bool =
+proc parseAnsiCode*(parser: var AnsiCodeParser, format: var Format,
+    c: char): bool =
   case parser.state
   of PARSE_START:
     if 0x40 <= int(c) and int(c) <= 0x5F:
@@ -297,36 +304,7 @@ proc parseAnsiCode*(parser: var AnsiCodeParser, format: var Format, c: char): bo
   of PARSE_FINAL:
     parser.state = PARSE_DONE
     if 0x40 <= int(c) and int(c) <= 0x7E:
-      format.handleAnsiCode(c, parser.params)
+      parser.parseControlFunction(format, c)
     else:
       return true
   of PARSE_DONE: discard
-
-proc parseAnsiCode*(format: var Format, stream: Stream) =
-  if stream.atEnd(): return
-  var c = stream.readChar()
-  if 0x40 <= int(c) and int(c) <= 0x5F:
-    if c != '[':
-      #C1, TODO?
-      return
-    if stream.atEnd(): return
-    c = stream.readChar()
-
-  var params = $c
-  #parameter bytes
-  while 0x30 <= int(c) and int(c) <= 0x3F:
-    params &= c
-    if stream.atEnd(): return
-    c = stream.readChar()
-
-  #intermediate bytes
-  #var interm = $c
-  while 0x20 <= int(c) and int(c) <= 0x2F:
-    #interm &= c
-    if stream.atEnd(): return
-    c = stream.readChar()
-
-  #final byte
-  if 0x40 <= int(c) and int(c) <= 0x7E:
-    let final = c
-    format.handleAnsiCode(final, params)
diff --git a/src/display/pager.nim b/src/display/pager.nim
index 6db13fac..adfe2f87 100644
--- a/src/display/pager.nim
+++ b/src/display/pager.nim
@@ -263,7 +263,7 @@ proc writeStatusMessage(pager: Pager, str: string,
   var i = start
   let e = min(start + maxwidth, pager.statusgrid.width)
   if i >= e:
-    return e
+    return i
   for r in str.runes:
     let pi = i
     i += r.twidth(i)
diff --git a/src/display/term.nim b/src/display/term.nim
index 51ed21eb..fa3c5e7f 100644
--- a/src/display/term.nim
+++ b/src/display/term.nim
@@ -211,13 +211,16 @@ proc getRGB(a: CellColor, bg: bool): RGBColor =
 
 # Use euclidian distance to quantize RGB colors.
 proc approximateANSIColor(rgb: RGBColor, exclude = -1): int =
-  var a = 0
+  var a = 0u16
   var n = -1
   for i in 0 .. ANSIColorMap.high:
     if i == exclude: continue
     let color = ANSIColorMap[i]
     if color == rgb: return i
-    let b = (color.r - rgb.r) ^  2 + (color.g - rgb.b) ^ 2 + (color.g - rgb.g) ^ 2
+    let x = uint16(color.r - rgb.r) ^ 2
+    let y = uint16(color.g - rgb.b) ^ 2
+    let z = uint16(color.g - rgb.g) ^ 2
+    let b = x + y + z
     if n == -1 or b < a:
       n = i
       a = b
@@ -228,14 +231,14 @@ proc correctContrast(bgcolor, fgcolor: CellColor, contrast: int): CellColor =
   let cfgcolor = fgcolor
   let bgcolor = getRGB(bgcolor, true)
   let fgcolor = getRGB(fgcolor, false)
-  let bgY = bgcolor.Y
-  let fgY = fgcolor.Y
+  let bgY = int(bgcolor.Y)
+  let fgY = int(fgcolor.Y)
   let diff = abs(bgY - fgY)
   if diff < contrast:
-    let newrgb = if bgY > contrast:
-      YUV(bgY - contrast, fgcolor.U, fgcolor.V)
-    else:
-      YUV(bgY + contrast, fgcolor.U, fgcolor.V)
+    let neg = max(bgY - contrast, 0)
+    let pos = min(bgY + contrast, 255)
+    let condiff = if abs(bgY - neg) < abs(bgY - pos): pos else: neg
+    let newrgb = YUV(cast[uint8](condiff), fgcolor.U, fgcolor.V)
     if cfgcolor.rgb:
       return newrgb.cellColor()
     return ColorsANSIFg[approximateANSIColor(newrgb)]
diff --git a/src/types/color.nim b/src/types/color.nim
index b9431340..1aa0c777 100644
--- a/src/types/color.nim
+++ b/src/types/color.nim
@@ -211,17 +211,17 @@ const ColorsRGB* = {
   "rebeccapurple": 0x663399,
 }.map((a) => (a[0], RGBColor(a[1]))).toTable()
 
-func r*(c: RGBAColor): int =
-  return int((uint32(c) shr 16) and 0xff)
+func r*(c: RGBAColor): uint8 =
+  return cast[uint8]((uint32(c) shr 16) and 0xff)
 
-func g*(c: RGBAColor): int =
-  return int((uint32(c) shr 8) and 0xff)
+func g*(c: RGBAColor): uint8 =
+  return cast[uint8]((uint32(c) shr 8) and 0xff)
 
-func b*(c: RGBAColor): int =
-  return int(uint32(c) and 0xff)
+func b*(c: RGBAColor): uint8 =
+  return cast[uint8](uint32(c) and 0xff)
 
-func a*(c: RGBAColor): int =
-  return int((uint32(c) shr 24) and 0xff)
+func a*(c: RGBAColor): uint8 =
+  return cast[uint8]((uint32(c) shr 24) and 0xff)
 
 proc `r=`*(c: var RGBAColor, r: uint8) =
   c = RGBAColor(uint32(c) or (uint32(r) shl 16))
@@ -285,7 +285,7 @@ func fastmul(c: RGBAColor, ca: uint32): uint32 =
 func fastmul1(c: RGBAColor, ca: uint32): uint32 =
   return fastmul1(uint32(c), ca)
 
-func rgba*(r, g, b, a: int): RGBAColor
+func rgba*(r, g, b, a: uint8): RGBAColor
 
 func premul(c: RGBAColor): RGBAColor =
   return RGBAColor(fastmul(c, uint32(c.a)))
@@ -302,17 +302,17 @@ proc straight*(c: RGBAColor): RGBAColor =
   let r = straightAlphaTable[c.a][c.r]
   let g = straightAlphaTable[c.a][c.g]
   let b = straightAlphaTable[c.a][c.b]
-  return rgba(int(r), int(g), int(b), int(c.a))
+  return rgba(r, g, b, c.a)
 
 func blend*(c0, c1: RGBAColor): RGBAColor =
   let pc0 = c0.premul()
   let pc1 = c1.premul()
   let k = 255 - pc1.a
   let mc = RGBAColor(fastmul1(pc0, uint32(k)))
-  let rr = pc1.r + mc.r
-  let rg = pc1.g + mc.g
-  let rb = pc1.b + mc.b
-  let ra = pc1.a + mc.a
+  let rr = cast[uint8](uint16(pc1.r) + uint16(mc.r))
+  let rg = cast[uint8](uint16(pc1.g) + uint16(mc.g))
+  let rb = cast[uint8](uint16(pc1.b) + uint16(mc.b))
+  let ra = cast[uint8](uint16(pc1.a) + uint16(mc.a))
   let pres = rgba(rr, rg, rb, ra)
   let res = straight(pres)
   return res
@@ -323,53 +323,55 @@ func blend*(c0, c1: RGBAColor): RGBAColor =
 #  let c1a = float64(c1.a) * norm
 #  let a0 = c0a + c1a * (1 - c0a)
 
-func rgb*(r, g, b: int): RGBColor =
-  return RGBColor((r shl 16) or (g shl 8) or b)
+func rgb*(r, g, b: uint8): RGBColor =
+  return RGBColor((uint32(r) shl 16) or (uint32(g) shl 8) or uint32(b))
 
-func rgb*(r, g, b: uint8): RGBColor {.inline.} =
-  return rgb(int(r), int(g), int(b))
+func r*(c: RGBColor): uint8 =
+  return cast[uint8]((uint32(c) shr 16) and 0xff)
 
-func r*(c: RGBColor): int =
-  return int(uint32(c) shr 16 and 0xff)
+func g*(c: RGBColor): uint8 =
+  return cast[uint8]((uint32(c) shr 8) and 0xff)
 
-func g*(c: RGBColor): int =
-  return int(uint32(c) shr 8 and 0xff)
-
-func b*(c: RGBColor): int =
-  return int(uint32(c) and 0xff)
+func b*(c: RGBColor): uint8 =
+  return cast[uint8](uint32(c) and 0xff)
 
 # see https://learn.microsoft.com/en-us/previous-versions/windows/embedded/ms893078(v=msdn.10)
-func Y*(c: RGBColor): int =
-  return ((66 * c.r + 129 * c.g + 25 * c.b + 128) shr 8) + 16
-
-func U*(c: RGBColor): int =
-  return ((-38 * c.r - 74 * c.g + 112 * c.b + 128) shr 8) + 128
-
-func V*(c: RGBColor): int =
-  return ((112 * c.r - 94 * c.g - 18 * c.b + 128) shr 8) + 128
-
-func YUV*(Y, U, V: int): RGBColor =
-  let C = Y - 16
-  let D = U - 128
-  let E = V - 128
+func Y*(c: RGBColor): uint8 =
+  let rmul = uint16(c.r) * 66u16
+  let gmul = uint16(c.g) * 129u16
+  let bmul = uint16(c.b) * 25u16
+  return cast[uint8](((rmul + gmul + bmul + 128) shr 8) + 16)
+
+func U*(c: RGBColor): uint8 =
+  let rmul = uint16(c.r) * 38u16
+  let gmul = uint16(c.g) * 74u16
+  let bmul = uint16(c.b) * 112u16
+  return cast[uint8](((128 + gmul - rmul - bmul) shr 8) + 128)
+
+func V*(c: RGBColor): uint8 =
+  let rmul = uint16(c.r) * 112u16
+  let gmul = uint16(c.g) * 94u16
+  let bmul = uint16(c.b) * 18u16
+  return cast[uint8](((128 + rmul - gmul - bmul) shr 8) + 128)
+
+func YUV*(Y, U, V: uint8): RGBColor =
+  let C = uint16(Y) - 16
+  let D = uint16(U) - 128
+  let E = uint16(V) - 128
   let r = max(min((298 * C + 409 * E + 128) shr 8, 255), 0)
   let g = max(min((298 * C - 100 * D - 208 * E + 128) shr 8, 255), 0)
   let b = max(min((298 * C + 516 * D + 128) shr 8, 255), 0)
-  return rgb(r, g, b)
-
-func rgba*(r, g, b, a: int): RGBAColor =
-  return RGBAColor((uint32(a) shl 24) or (uint32(r) shl 16) or
-    (uint32(g) shl 8) or uint32(b))
+  return rgb(cast[uint8](r), cast[uint8](g), cast[uint8](b))
 
 func rgba*(r, g, b, a: uint8): RGBAColor =
   return RGBAColor((uint32(a) shl 24) or (uint32(r) shl 16) or
     (uint32(g) shl 8) or uint32(b))
 
-func gray*(n: int): RGBColor =
-  return rgb(n, n, n) #TODO use yuv instead?
+func rgba*(r, g, b, a: int): RGBAColor =
+  return rgba(uint8(r), uint8(g), uint8(b), uint8(a))
 
 func gray*(n: uint8): RGBColor =
-  return gray(int(n))
+  return rgb(n, n, n) #TODO use yuv instead?
 
 template `$`*(rgbcolor: RGBColor): string =
   "rgb(" & $rgbcolor.r & ", " & $rgbcolor.g & ", " & $rgbcolor.b & ")"
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index c41a7e57..dd51eb2d 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -368,7 +368,6 @@ func japaneseNumber*(i: int): string =
     dec n
 
 # Implements https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#signed-integers
-#TODO TODO TODO handle overflow defects
 func parseInt32*(s: string): Option[int32] =
   var sign: int32 = 1
   var i = 0
@@ -382,8 +381,13 @@ func parseInt32*(s: string): Option[int32] =
   var integer = int32(decValue(s[i]))
   inc i
   while i < s.len and isDigit(s[i]):
+    if unlikely(integer != 0 and high(int32) div 10 < integer):
+      return none(int32) # overflow
     integer *= 10
-    integer += int32(decValue(s[i]))
+    let c = int32(decValue(s[i]))
+    if unlikely(high(int32) - c < integer):
+      return none(int32) # overflow
+    integer += c
     inc i
   return some(sign * integer)
 
@@ -400,11 +404,35 @@ func parseInt64*(s: string): Option[int64] =
   var integer = int64(decValue(s[i]))
   inc i
   while i < s.len and isDigit(s[i]):
+    if unlikely(integer != 0 and high(int64) div 10 < integer):
+      return none(int64) # overflow
     integer *= 10
-    integer += int64(decValue(s[i]))
+    let c = int64(decValue(s[i]))
+    if unlikely(high(int64) - c < integer):
+      return none(int64) # overflow
+    integer += c
     inc i
   return some(sign * integer)
 
+func parseUInt8*(s: string): Option[uint8] =
+  var i = 0
+  if i < s.len and s[i] == '+':
+    inc i
+  if i == s.len or s[i] notin AsciiDigit:
+    return none(uint8)
+  var integer = uint8(decValue(s[i]))
+  inc i
+  while i < s.len and isDigit(s[i]):
+    if unlikely(integer != 0 and high(uint8) div 10 < integer):
+      return none(uint8) # overflow
+    integer *= 10
+    let c = uint8(decValue(s[i]))
+    if unlikely(high(uint8) - c < integer):
+      return none(uint8) # overflow
+    integer += uint8(c)
+    inc i
+  return some(integer)
+
 func parseUInt32*(s: string): Option[uint32] =
   var i = 0
   if i < s.len and s[i] == '+':
@@ -414,8 +442,13 @@ func parseUInt32*(s: string): Option[uint32] =
   var integer = uint32(decValue(s[i]))
   inc i
   while i < s.len and isDigit(s[i]):
+    if unlikely(integer != 0 and high(uint32) div 10 < integer):
+      return none(uint32) # overflow
     integer *= 10
-    integer += uint32(decValue(s[i]))
+    let c = uint32(decValue(s[i]))
+    if unlikely(high(uint32) - c < integer):
+      return none(uint32) # overflow
+    integer += c
     inc i
   return some(integer)