about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-02-24 18:14:00 +0100
committerbptato <nincsnevem662@gmail.com>2024-02-24 18:19:58 +0100
commitf11c942585446be074412b2cc270f30532351882 (patch)
tree5bc37a67f6bde5dccd8d685b47ef6f9602e33a26 /src
parentea3c192626b4a33d9a6201d82a1b4a1f306f58eb (diff)
downloadchawan-f11c942585446be074412b2cc270f30532351882.tar.gz
Allow non-RGB colors in CSS
The -cha-ansi color type now sets ANSI colors in CSS.

Also, color correction etc. has been improved a bit:

* don't completely reset output state in processFormat for new colors
* defaultColor is now separated from ANSI color type 0
* bright ANSI colors are no longer replaced with bold + dark variant
* replaced ANSI color map to match xterm defaults
Diffstat (limited to 'src')
-rw-r--r--src/css/cascade.nim6
-rw-r--r--src/css/values.nim144
-rw-r--r--src/display/term.nim191
-rw-r--r--src/html/dom.nim10
-rw-r--r--src/img/png.nim2
-rw-r--r--src/render/renderdocument.nim32
-rw-r--r--src/types/cell.nim11
-rw-r--r--src/types/color.nim36
8 files changed, 252 insertions, 180 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 13282b4f..84eb481c 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -131,7 +131,7 @@ func calcPresentationalHints(element: Element): CSSComputedValues =
     if s != "":
       let c = parseLegacyColor(s)
       if c.isSome:
-        set_cv "background-color", c.get
+        set_cv "background-color", c.get.cellColor()
   template map_size =
     let s = element.attrul(atSize)
     if s.isSome:
@@ -161,13 +161,13 @@ func calcPresentationalHints(element: Element): CSSComputedValues =
     if s != "":
       let c = parseLegacyColor(s)
       if c.isSome:
-        set_cv "color", c.get
+        set_cv "color", c.get.cellColor()
   template map_color =
     let s = element.attr(atColor)
     if s != "":
       let c = parseLegacyColor(s)
       if c.isSome:
-        set_cv "color", c.get
+        set_cv "color", c.get.cellColor()
   template map_colspan =
     let colspan = element.attrulgz(atColspan)
     if colspan.isSome:
diff --git a/src/css/values.nim b/src/css/values.nim
index bad8ef4a..a7296b8e 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -174,7 +174,7 @@ type
   CSSComputedValue* = ref object
     case v*: CSSValueType
     of VALUE_COLOR:
-      color*: RGBAColor
+      color*: CellColor
     of VALUE_LENGTH:
       length*: CSSLength
     of VALUE_FONT_STYLE:
@@ -592,66 +592,114 @@ func parseDimensionValues*(s: string): Option[CSSLength] =
   if s[i] == '%': return some(CSSLength(num: n, unit: UNIT_PERC))
   return some(CSSLength(num: n, unit: UNIT_PX))
 
-func skipWhitespace(vals: seq[CSSComponentValue], i: var int) =
+func skipWhitespace(vals: openArray[CSSComponentValue], i: var int) =
   while i < vals.len:
     if vals[i] != CSS_WHITESPACE_TOKEN:
       break
     inc i
 
-func cssColor*(val: CSSComponentValue): Opt[RGBAColor] =
+func parseRGBA(value: openArray[CSSComponentValue]): Opt[CellColor] =
+  var i = 0
+  var commaMode = false
+  template check_err(slash: bool) =
+    #TODO calc, percentages, etc (cssnumber function or something)
+    if not slash and i >= value.len or i < value.len and
+        value[i] != CSS_NUMBER_TOKEN:
+      return err()
+  template next_value(first = false, slash = false) =
+    inc i
+    value.skipWhitespace(i)
+    if i < value.len:
+      if value[i] == CSS_COMMA_TOKEN and (commaMode or first):
+        # legacy compatibility
+        inc i
+        value.skipWhitespace(i)
+        commaMode = true
+      elif commaMode:
+        return err()
+      elif slash:
+        let tok = value[i]
+        if tok != CSS_DELIM_TOKEN or CSSToken(tok).cvalue != '/':
+          return err()
+        inc i
+        value.skipWhitespace(i)
+    check_err slash
+  value.skipWhitespace(i)
+  check_err false
+  let r = CSSToken(value[i]).nvalue
+  next_value true
+  let g = CSSToken(value[i]).nvalue
+  next_value
+  let b = CSSToken(value[i]).nvalue
+  next_value false, true
+  let a = if i < value.len:
+    CSSToken(value[i]).nvalue
+  else:
+    1
+  value.skipWhitespace(i)
+  if i < value.len:
+    return err()
+  return ok(rgba(int(r), int(g), int(b), int(a * 255)).cellColor())
+
+# syntax: -cha-ansi( number | ident )
+# where number is an ANSI color (0..255)
+# and ident is in NameTable and may start with "bright-"
+func parseANSI(value: openArray[CSSComponentValue]): Opt[CellColor] =
+  var i = 0
+  value.skipWhitespace(i)
+  if i != value.high or not (value[i] of CSSToken): # only 1 param is valid
+    #TODO numeric functions
+    return err()
+  let tok = CSSToken(value[i])
+  if tok.tokenType == CSS_NUMBER_TOKEN:
+    if tok.nvalue notin 0..255:
+      return err() # invalid numeric ANSI color
+    return ok(ANSIColor(tok.nvalue).cellColor())
+  elif tok.tokenType == CSS_IDENT_TOKEN:
+    var name = tok.value
+    if name.equalsIgnoreCase("default"):
+      return ok(defaultColor)
+    var bright = false
+    if name.startsWithIgnoreCase("bright-"):
+      bright = true
+      name = name.substr("bright-".len)
+    const NameTable = [
+      "black",
+      "red",
+      "green",
+      "yellow",
+      "blue",
+      "magenta",
+      "cyan",
+      "white"
+    ]
+    for i, it in NameTable:
+      if it.equalsIgnoreCase(name):
+        var i = int(i)
+        if bright:
+          i += 8
+        return ok(ANSIColor(i).cellColor())
+  return err()
+
+func cssColor*(val: CSSComponentValue): Opt[CellColor] =
   if val of CSSToken:
     let tok = CSSToken(val)
     case tok.tokenType
     of CSS_HASH_TOKEN:
       let c = parseHexColor(tok.value)
       if c.isSome:
-        return ok(c.get)
+        return ok(c.get.cellColor())
     of CSS_IDENT_TOKEN:
-      let s = tok.value
+      let s = tok.value.toLowerAscii()
       if s in Colors:
-        return ok(Colors[s])
+        return ok(Colors[s].cellColor())
     else: discard
   elif val of CSSFunction:
     let f = CSSFunction(val)
-    var i = 0
-    var commaMode = false
-    template check_err(slash: bool) =
-      #TODO calc, percentages, etc (cssnumber function or something)
-      if not slash and i >= f.value.len or i < f.value.len and
-          f.value[i] != CSS_NUMBER_TOKEN:
-        return err()
-    template next_value(first = false, slash = false) =
-      inc i
-      f.value.skipWhitespace(i)
-      if i < f.value.len:
-        if f.value[i] == CSS_COMMA_TOKEN and (commaMode or first):
-          # legacy compatibility
-          inc i
-          f.value.skipWhitespace(i)
-          commaMode = true
-        elif commaMode:
-          return err()
-        elif slash:
-          let tok = f.value[i]
-          if tok != CSS_DELIM_TOKEN or CSSToken(tok).cvalue != '/':
-            return err()
-          inc i
-          f.value.skipWhitespace(i)
-      check_err slash
     if f.name.equalsIgnoreCase("rgb") or f.name.equalsIgnoreCase("rgba"):
-      f.value.skipWhitespace(i)
-      check_err false
-      let r = CSSToken(f.value[i]).nvalue
-      next_value true
-      let g = CSSToken(f.value[i]).nvalue
-      next_value
-      let b = CSSToken(f.value[i]).nvalue
-      next_value false, true
-      let a = if i < f.value.len:
-        CSSToken(f.value[i]).nvalue
-      else:
-        1
-      return ok(rgba(int(r), int(g), int(b), int(a * 255)))
+      return parseRGBA(f.value)
+    elif f.name.equalsIgnoreCase("-cha-ansi"):
+      return parseANSI(f.value)
   return err()
 
 func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken
@@ -1205,14 +1253,14 @@ proc getValueFromDecl(val: CSSComputedValue, d: CSSDeclaration,
     discard
   return ok()
 
-func getInitialColor(t: CSSPropertyType): RGBAColor =
+func getInitialColor(t: CSSPropertyType): CellColor =
   case t
   of PROPERTY_COLOR:
-    return Colors["white"]
+    return Colors["white"].cellColor()
   of PROPERTY_BACKGROUND_COLOR:
-    return Colors["transparent"]
+    return Colors["transparent"].cellColor()
   else:
-    return Colors["black"]
+    return Colors["black"].cellColor()
 
 func getInitialLength(t: CSSPropertyType): CSSLength =
   case t
diff --git a/src/display/term.nim b/src/display/term.nim
index afe84db6..588951cd 100644
--- a/src/display/term.nim
+++ b/src/display/term.nim
@@ -134,15 +134,24 @@ else:
 template SGR*(s: varargs[string, `$`]): string =
   CSI(s) & "m"
 
+#TODO a) this should be customizable b) these defaults sucks
 const ANSIColorMap = [
-  ColorsRGB["black"],
-  ColorsRGB["red"],
-  ColorsRGB["green"],
-  ColorsRGB["yellow"],
-  ColorsRGB["blue"],
-  ColorsRGB["magenta"],
-  ColorsRGB["cyan"],
-  ColorsRGB["white"],
+  rgb(0, 0, 0),
+  rgb(205, 0, 0),
+  rgb(0, 205, 0),
+  rgb(205, 205, 0),
+  rgb(0, 0, 238),
+  rgb(205, 0, 205),
+  rgb(0, 205, 205),
+  rgb(229, 229, 229),
+  rgb(127, 127, 127),
+  rgb(255, 0, 0),
+  rgb(0, 255, 0),
+  rgb(255, 255, 0),
+  rgb(92, 92, 255),
+  rgb(255, 0, 255),
+  rgb(0, 255, 255),
+  rgb(255, 255, 255)
 ]
 
 proc flush*(term: Terminal) =
@@ -218,24 +227,28 @@ proc disableAltScreen(term: Terminal): string =
 func mincontrast(term: Terminal): int32 =
   return term.config.display.minimum_contrast
 
-proc getRGB(a: CellColor, bg: bool): RGBColor =
-  if a.rgb:
+proc getRGB(a: CellColor, termDefault: RGBColor): RGBColor =
+  case a.t
+  of ctNone:
+    return termDefault
+  of ctANSI:
+    if a.color >= 16:
+      return EightBitColor(a.color).toRGB()
+    return ANSIColorMap[a.color]
+  of ctRGB:
     return a.rgbcolor
-  elif a.color >= 16:
-    return eightBitToRGB(EightBitColor(a.color))
-  return ANSIColorMap[a.color]
 
 # Use euclidian distance to quantize RGB colors.
 proc approximateANSIColor(rgb, termDefault: RGBColor): CellColor =
   var a = 0i32
   var n = -1
   for i in -1 .. ANSIColorMap.high:
-    let color = if i > 0:
+    let color = if i >= 0:
       ANSIColorMap[i]
     else:
       termDefault
     if color == rgb:
-      return if i == -1: defaultColor else: cellColor(ANSIColor(i))
+      return if i == -1: defaultColor else: ANSIColor(i).cellColor()
     let x = int32(color.r) - int32(rgb.r)
     let y = int32(color.g) - int32(rgb.g)
     let z = int32(color.b) - int32(rgb.b)
@@ -243,23 +256,17 @@ proc approximateANSIColor(rgb, termDefault: RGBColor): CellColor =
     let yy = y * y
     let zz = z * z
     let b = xx + yy + zz
-    if n == -1 or b < a:
+    if i == -1 or b < a:
       n = i
       a = b
-  return if n == -1: defaultColor else: cellColor(ANSIColor(n))
+  return if n == -1: defaultColor else: ANSIColor(n).cellColor()
 
 # Return a fgcolor contrasted to the background by term.mincontrast.
 proc correctContrast(term: Terminal, bgcolor, fgcolor: CellColor): CellColor =
   let contrast = term.mincontrast
   let cfgcolor = fgcolor
-  let bgcolor = if bgcolor == defaultColor:
-    term.defaultBackground
-  else:
-    getRGB(bgcolor, true)
-  let fgcolor = if fgcolor == defaultColor:
-    term.defaultForeground
-  else:
-    getRGB(fgcolor, false)
+  let bgcolor = getRGB(bgcolor, term.defaultBackground)
+  let fgcolor = getRGB(fgcolor, term.defaultForeground)
   let bgY = int(bgcolor.Y)
   var fgY = int(fgcolor.Y)
   let diff = abs(bgY - fgY)
@@ -283,91 +290,95 @@ proc correctContrast(term: Terminal, bgcolor, fgcolor: CellColor): CellColor =
     of ANSI:
       return approximateANSIColor(newrgb, term.defaultForeground)
     of EIGHT_BIT:
-      return cellColor(rgbToEightBit(newrgb))
+      return cellColor(newrgb.toEightBit())
     of MONOCHROME:
       doAssert false
   return cfgcolor
 
+template ansiSGR(n: uint8, bgmod: int): string =
+  if n < 8:
+    SGR(30 + bgmod + n)
+  else:
+    SGR(82 + bgmod + n)
+
+template eightBitSGR(n: uint8, bgmod: int): string =
+  if n < 16:
+    ansiSGR(n, bgmod)
+  else:
+    SGR(38 + bgmod, 5, n)
+
+template rgbSGR(rgb: RGBColor, bgmod: int): string =
+  SGR(38 + bgmod, 2, rgb.r, rgb.g, rgb.b)
+
 proc processFormat*(term: Terminal, format: var Format, cellf: Format): string =
   for flag in FormatFlags:
     if flag in term.formatmode:
       if flag in format.flags and flag notin cellf.flags:
         result &= term.endFormat(flag)
-
+      if flag notin format.flags and flag in cellf.flags:
+        result &= term.startFormat(flag)
   var cellf = cellf
   case term.colormode
   of ANSI:
-    if not cellf.bgcolor.rgb and cellf.bgcolor.color > 15:
-      let color = cellf.bgcolor.eightbit
-      cellf.bgcolor = cellColor(eightBitToRGB(color))
-    if not cellf.fgcolor.rgb and cellf.fgcolor.color > 15:
-      let color = cellf.fgcolor.eightbit
-      cellf.fgcolor = cellColor(eightBitToRGB(color))
-    if cellf.bgcolor.rgb:
+    # quantize
+    if cellf.bgcolor.t == ctANSI and cellf.bgcolor.color > 15:
+      cellf.bgcolor = cellf.fgcolor.eightbit.toRGB().cellColor()
+    if cellf.bgcolor.t == ctRGB:
       cellf.bgcolor = approximateANSIColor(cellf.bgcolor.rgbcolor,
         term.defaultBackground)
-    if cellf.fgcolor.rgb:
-      if cellf.bgcolor == defaultColor:
+    if cellf.fgcolor.t == ctANSI and cellf.fgcolor.color > 15:
+      cellf.fgcolor = cellf.fgcolor.eightbit.toRGB().cellColor()
+    if cellf.fgcolor.t == ctRGB:
+      if cellf.bgcolor.t == ctNone:
         cellf.fgcolor = approximateANSIColor(cellf.fgcolor.rgbcolor,
           term.defaultForeground)
       else:
-        # ANSI non-default fgcolor AND bgcolor at the same time is assumed
-        # to be broken.
+        # ANSI fgcolor + bgcolor at the same time is broken
         cellf.fgcolor = defaultColor
+    # correct
+    cellf.fgcolor = term.correctContrast(cellf.bgcolor, cellf.fgcolor)
+    # print
+    case cellf.fgcolor.t
+    of ctNone: result &= SGR(39)
+    of ctANSI: result &= ansiSGR(cellf.fgcolor.color, 0)
+    else: assert false
+    case cellf.bgcolor.t
+    of ctNone: result &= SGR(49)
+    of ctANSI: result &= ansiSGR(cellf.bgcolor.color, 10)
+    else: assert false
   of EIGHT_BIT:
-    if cellf.bgcolor.rgb:
-      cellf.bgcolor = cellColor(rgbToEightBit(cellf.bgcolor.rgbcolor))
-    if cellf.fgcolor.rgb:
-      cellf.fgcolor = cellColor(rgbToEightBit(cellf.fgcolor.rgbcolor))
-  of MONOCHROME:
-    cellf.fgcolor = defaultColor
-    cellf.bgcolor = defaultColor
-  of TRUE_COLOR: discard
-
-  if term.colormode != MONOCHROME:
+    # quantize
+    if cellf.bgcolor.t == ctRGB:
+      cellf.bgcolor = cellf.bgcolor.rgbcolor.toEightBit().cellColor()
+    if cellf.fgcolor.t == ctRGB:
+      cellf.fgcolor = cellf.fgcolor.rgbcolor.toEightBit().cellColor()
+    # correct
     cellf.fgcolor = term.correctContrast(cellf.bgcolor, cellf.fgcolor)
-  if cellf.fgcolor != format.fgcolor and cellf.fgcolor == defaultColor or
-      cellf.bgcolor != format.bgcolor and cellf.bgcolor == defaultColor:
-    result &= term.resetFormat()
-    format = Format()
-
-  if cellf.fgcolor != format.fgcolor:
-    var color = cellf.fgcolor
-    if color.rgb:
-      assert term.colormode == TRUE_COLOR
-      let rgb = color.rgbcolor
-      result &= SGR(38, 2, rgb.r, rgb.g, rgb.b)
-    elif color == defaultColor:
-      discard
-    else:
-      let n = color.color
-      if n < 8:
-        result &= SGR(30 + n)
-      else:
-        assert term.colormode in {TRUE_COLOR, EIGHT_BIT}
-        result &= SGR(38, 5, n)
-
-  if cellf.bgcolor != format.bgcolor:
-    var color = cellf.bgcolor
-    if color.rgb:
-      assert term.colormode == TRUE_COLOR
-      let rgb = color.rgbcolor
-      result &= SGR(48, 2, rgb.r, rgb.g, rgb.b)
-    elif color == defaultColor:
-      discard
-    else:
-      let n = color.color
-      if n < 8:
-        result &= SGR(40 + n)
-      else:
-        assert term.colormode in {TRUE_COLOR, EIGHT_BIT}
-        result &= SGR(48, 5, n)
-
-  for flag in FormatFlags:
-    if flag in term.formatmode:
-      if flag notin format.flags and flag in cellf.flags:
-        result &= term.startFormat(flag)
-
+    # print
+    case cellf.fgcolor.t
+    of ctNone: result &= SGR(39)
+    of ctANSI: result &= eightBitSGR(cellf.fgcolor.color, 0)
+    of ctRGB: assert false
+    case cellf.bgcolor.t
+    of ctNone: result &= SGR(49)
+    of ctANSI: result &= eightBitSGR(cellf.bgcolor.color, 10)
+    of ctRGB: assert false
+  of TRUE_COLOR:
+    # correct
+    cellf.fgcolor = term.correctContrast(cellf.bgcolor, cellf.fgcolor)
+    # print
+    if cellf.fgcolor != format.fgcolor:
+      case cellf.fgcolor.t
+      of ctNone: result &= SGR(39)
+      of ctANSI: result &= eightBitSGR(cellf.fgcolor.color, 0)
+      of ctRGB: result &= rgbSGR(cellf.fgcolor.rgbcolor, 0)
+    if cellf.bgcolor != format.bgcolor:
+      case cellf.bgcolor.t
+      of ctNone: result &= SGR(49)
+      of ctANSI: result &= eightBitSGR(cellf.bgcolor.color, 10)
+      of ctRGB: result &= rgbSGR(cellf.bgcolor.rgbcolor, 10)
+  of MONOCHROME:
+    discard # nothing to do
   format = cellf
 
 proc windowChange*(term: Terminal, attrs: WindowAttributes) =
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 597d34b9..39a80681 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -2288,12 +2288,18 @@ isDefaultPassive = func (eventTarget: EventTarget): bool =
 
 proc parseColor(element: Element, s: string): RGBAColor =
   let cval = parseComponentValue(newStringStream(s))
-  #TODO TODO TODO return element style
+  #TODO return element style
   # For now we just use white.
   let ec = rgb(255, 255, 255)
   if cval.isErr:
     return ec
-  return cssColor(cval.get).get(ec)
+  let color0 = cssColor(cval.get)
+  if color0.isErr:
+    return ec
+  let color = color0.get
+  if color.t != ctRGB:
+    return ec
+  return color.rgbacolor
 
 #TODO ??
 func target0*(element: Element): string =
diff --git a/src/img/png.nim b/src/img/png.nim
index 8239c35f..047a080f 100644
--- a/src/img/png.nim
+++ b/src/img/png.nim
@@ -108,7 +108,7 @@ type PNGReader = object
   i: int
   bitDepth: uint8
   colorType: PNGColorType
-  background: RGBColor
+  background: RGBAColor
   isend: bool
   idatBuf: seq[uint8]
   uprow: seq[uint8]
diff --git a/src/render/renderdocument.nim b/src/render/renderdocument.nim
index ed0bc809..7526b111 100644
--- a/src/render/renderdocument.nim
+++ b/src/render/renderdocument.nim
@@ -14,7 +14,6 @@ import utils/strwidth
 func toFormat(computed: CSSComputedValues): Format =
   if computed == nil:
     return Format()
-  let fgcolor = computed{"color"}.cellColor()
   var flags: set[FormatFlags]
   if computed{"font-style"} in {FONT_STYLE_ITALIC, FONT_STYLE_OBLIQUE}:
     flags.incl(FLAG_ITALIC)
@@ -29,7 +28,7 @@ func toFormat(computed: CSSComputedValues): Format =
   if TEXT_DECORATION_BLINK in computed{"text-decoration"}:
     flags.incl(FLAG_BLINK)
   return Format(
-    fgcolor: fgcolor,
+    fgcolor: computed{"color"},
     flags: flags
   )
 
@@ -319,8 +318,9 @@ proc paintInlineFragment(grid: var FlexibleGrid; fragment: InlineFragment;
 proc renderInlineFragment(grid: var FlexibleGrid; state: var RenderState,
     fragment: InlineFragment; offset: Offset; attrs: WindowAttributes) =
   assert fragment.atoms.len == 0 or fragment.children.len == 0
-  if fragment.computed{"background-color"}.a > 0: # TODO color blending
-    let bgcolor = fragment.computed{"background-color"}.cellColor()
+  let bgcolor = fragment.computed{"background-color"}
+  if bgcolor.t == ctANSI or bgcolor.t == ctRGB and bgcolor.rgbacolor.a > 0:
+    #TODO color blending
     grid.paintInlineFragment(fragment, offset, bgcolor, attrs)
   if fragment.atoms.len > 0:
     let format = fragment.computed.toFormat()
@@ -380,18 +380,18 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState;
       stack.add((nil, Offset(x: -1, y: -1)))
 
     if box.computed{"visibility"} == VISIBILITY_VISIBLE:
-      if box.computed{"-cha-bgcolor-is-canvas"} and
-          state.bgcolor == defaultColor:
-        #TODO bgimage
-        if box.computed{"background-color"}.a != 0: #TODO color blending
-          state.bgcolor = box.computed{"background-color"}.cellColor()
-      if box.computed{"background-color"}.a != 0: #TODO color blending
-          let ix = toInt(offset.x)
-          let iy = toInt(offset.y)
-          let iex = toInt(offset.x + box.size.w)
-          let iey = toInt(offset.y + box.size.h)
-          let color = box.computed{"background-color"}.cellColor()
-          grid.paintBackground(color, ix, iy, iex, iey, box.node, attrs)
+      let bgcolor = box.computed{"background-color"}
+      if bgcolor.t == ctANSI or bgcolor.t == ctRGB and bgcolor.rgbacolor.a > 0:
+        if box.computed{"-cha-bgcolor-is-canvas"} and
+            state.bgcolor == defaultColor:
+          #TODO bgimage
+          state.bgcolor = bgcolor
+        #TODO color blending
+        let ix = toInt(offset.x)
+        let iy = toInt(offset.y)
+        let iex = toInt(offset.x + box.size.w)
+        let iey = toInt(offset.y + box.size.h)
+        grid.paintBackground(bgcolor, ix, iy, iex, iey, box.node, attrs)
       if box.computed{"background-image"}.t == CONTENT_IMAGE and
           box.computed{"background-image"}.s != "":
         # ugly hack for background-image display... TODO actually display images
diff --git a/src/types/cell.nim b/src/types/cell.nim
index f3ffd919..2f2e5203 100644
--- a/src/types/cell.nim
+++ b/src/types/cell.nim
@@ -204,11 +204,8 @@ proc parseSGRDefColor(parser: AnsiCodeParser, format: var Format,
       set_color cellColor(gray(param0))
   elif u == 5:
     let param0 = parser.getParamU8(i, colon = true)
-    if param0 in 0u8..7u8:
+    if param0 in 0u8..15u8:
       set_color cellColor(ANSIColor(param0))
-    elif param0 in 8u8..15u8:
-      format.bold = true
-      set_color cellColor(ANSIColor(param0 - 8))
     elif param0 in 16u8..255u8:
       set_color cellColor(EightBitColor(param0))
   else:
@@ -229,11 +226,9 @@ proc parseSGRColor(parser: AnsiCodeParser, format: var Format,
   elif u == 49:
     format.bgcolor = defaultColor
   elif u in 90u8..97u8:
-    format.fgcolor = cellColor(ANSIColor(u - 90u8))
-    format.bold = true
+    format.fgcolor = cellColor(ANSIColor(u - 82))
   elif u in 100u8..107u8:
-    format.bgcolor = cellColor(ANSIColor(u - 90u8))
-    format.bold = true
+    format.bgcolor = cellColor(ANSIColor(u - 92))
   else:
     return false
   return true
diff --git a/src/types/color.nim b/src/types/color.nim
index 1a93792d..2f3f7310 100644
--- a/src/types/color.nim
+++ b/src/types/color.nim
@@ -19,11 +19,17 @@ type
 
   EightBitColor* = distinct uint8
 
+  # ctNone: default color (intentionally 0), n is unused
+  # ctANSI: ANSI color, as selected by SGR 38/48
+  # ctRGB: RGB color
+  ColorTag* {.size: sizeof(uint32).} = enum
+    ctNone, ctANSI, ctRGB
+
   CellColor* = object
-    rgb*: bool
+    t*: ColorTag
     n: uint32
 
-converter toRGBColor*(i: RGBAColor): RGBColor =
+func toRGBColor*(i: RGBAColor): RGBColor =
   return RGBColor(uint32(i) and 0xFFFFFFu32)
 
 converter toRGBAColor*(i: RGBColor): RGBAColor =
@@ -36,6 +42,9 @@ func `==`*(a, b: ANSIColor): bool {.borrow.}
 func rgbcolor*(color: CellColor): RGBColor =
   cast[RGBColor](color.n)
 
+func rgbacolor*(color: CellColor): RGBAColor =
+  cast[RGBAColor](color.n)
+
 func color*(color: CellColor): uint8 =
   cast[uint8](color.n)
 
@@ -43,17 +52,20 @@ func eightbit*(color: CellColor): EightBitColor =
   EightBitColor(color.color)
 
 func cellColor*(rgb: RGBColor): CellColor =
-  return CellColor(rgb: true, n: uint32(rgb))
+  return CellColor(t: ctRGB, n: uint32(rgb) or 0xFF000000u32)
 
-func cellColor*(c: ANSIColor): CellColor =
-  return CellColor(rgb: false, n: uint32(c) mod 10)
+func cellColor*(rgba: RGBAColor): CellColor =
+  return CellColor(t: ctRGB, n: uint32(rgba))
 
-#TODO maybe bright ANSI colors? (8..15)
+#TODO bright ANSI colors (8..15)
+
+func cellColor*(c: ANSIColor): CellColor =
+  return CellColor(t: ctANSI, n: uint32(c))
 
 func cellColor*(c: EightBitColor): CellColor =
-  return CellColor(rgb: false, n: uint32(c))
+  return CellColor(t: ctANSI, n: uint32(c))
 
-const defaultColor* = CellColor(rgb: false, n: 0)
+const defaultColor* = CellColor(t: ctNone, n: 0)
 
 const
   ANSI_BLACK* = ANSIColor(0u8)
@@ -374,7 +386,7 @@ func gray*(n: uint8): RGBColor =
   return rgb(n, n, n) #TODO use yuv instead?
 
 # NOTE: this assumes n notin 0..15 (which would be ANSI 4-bit)
-func eightBitToRGB*(param0: EightBitColor): RGBColor =
+func toRGB*(param0: EightBitColor): RGBColor =
   doAssert uint8(param0) notin 0u8..15u8
   let u = uint8(param0)
   if u in 16u8..231u8:
@@ -389,7 +401,7 @@ func eightBitToRGB*(param0: EightBitColor): RGBColor =
     let n = (u - 232) * 10 + 8
     return gray(n)
 
-func rgbToEightBit*(rgb: RGBColor): EightBitColor =
+func toEightBit*(rgb: RGBColor): EightBitColor =
   let r = int(rgb.r)
   let g = int(rgb.g)
   let b = int(rgb.b)
@@ -410,8 +422,8 @@ template `$`*(rgbcolor: RGBColor): string =
   "rgb(" & $rgbcolor.r & ", " & $rgbcolor.g & ", " & $rgbcolor.b & ")"
 
 template `$`*(color: CellColor): string =
-  if color.rgb:
-    $color.rgbcolor
+  if color.t == ctRGB:
+    $color.rgbacolor
   else:
     "tcolor" & $color.n