diff options
-rw-r--r-- | res/config.toml | 2 | ||||
-rw-r--r-- | src/buffer/cell.nim | 284 | ||||
-rw-r--r-- | src/display/pager.nim | 2 | ||||
-rw-r--r-- | src/display/term.nim | 19 | ||||
-rw-r--r-- | src/types/color.nim | 94 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 41 |
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) |