Skip to content

Commit

Permalink
feat(cellbuf): add common sequence decoding utilities (#342)
Browse files Browse the repository at this point in the history
* feat(cellbuf): add common sequence decoding utilities

This abstracts the decoding of Select Graphic Rendition (SGR) escape
sequences and hyperlink escape sequences from the cellbuf package. This
will allow other packages to use these utilities without having to
duplicate the code.

* fix: add missing max util for go < 1.21

* chore: remove unused
  • Loading branch information
aymanbagabas authored Jan 24, 2025
1 parent 62850e9 commit e24d5c3
Show file tree
Hide file tree
Showing 6 changed files with 699 additions and 662 deletions.
170 changes: 0 additions & 170 deletions ansi/color.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,173 +194,3 @@ func toRGBA(r, g, b uint32) (uint32, uint32, uint32, uint32) {
b |= b << 8
return r, g, b, 0xffff
}

// DecodeColor decodes a color from a slice of parameters. It returns the
// number of parameters read and the color. This function is used to read SGR
// color parameters following the ITU T.416 standard.
//
// It supports reading the following color types:
// - 0: implementation defined
// - 1: transparent
// - 2: RGB direct color
// - 3: CMY direct color
// - 4: CMYK direct color
// - 5: indexed color
// - 6: RGBA direct color (WezTerm extension)
//
// The parameters can be separated by semicolons (;) or colons (:). Mixing
// separators is not allowed.
//
// The specs supports defining a color space id, a color tolerance value, and a
// tolerance color space id. However, these values have no effect on the
// returned color and will be ignored.
//
// This implementation includes a few modifications to the specs:
// 1. Support for legacy color values separated by semicolons (;) with respect to RGB, and indexed colors
// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
// 4. Support reading RGBA colors
func DecodeColor(params []Parameter) (n int, co Color) {
if len(params) < 2 { // Need at least SGR type and color type
return 0, nil
}

// First parameter indicates one of 38, 48, or 58 (foreground, background, or underline)
s := params[0]
p := params[1]
colorType := p.Param(0)
n = 2

paramsfn := func() (p1, p2, p3, p4 int) {
// Where should we start reading the color?
switch {
case s.HasMore() && p.HasMore() && len(params) > 8 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && params[5].HasMore() && params[6].HasMore() && params[7].HasMore():
// We have color space id, a 6th parameter, a tolerance value, and a tolerance color space
n += 7
return params[3].Param(0), params[4].Param(0), params[5].Param(0), params[6].Param(0)
case s.HasMore() && p.HasMore() && len(params) > 7 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && params[5].HasMore() && params[6].HasMore():
// We have color space id, a 6th parameter, and a tolerance value
n += 6
return params[3].Param(0), params[4].Param(0), params[5].Param(0), params[6].Param(0)
case s.HasMore() && p.HasMore() && len(params) > 6 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && params[5].HasMore():
// We have color space id and a 6th parameter
// 48 : 4 : : 1 : 2 : 3 :4
n += 5
return params[3].Param(0), params[4].Param(0), params[5].Param(0), params[6].Param(0)
case s.HasMore() && p.HasMore() && len(params) > 5 && params[2].HasMore() && params[3].HasMore() && params[4].HasMore() && !params[5].HasMore():
// We have color space
// 48 : 3 : : 1 : 2 : 3
n += 4
return params[3].Param(0), params[4].Param(0), params[5].Param(0), -1
case s.HasMore() && p.HasMore() && p.Param(0) == 2 && params[2].HasMore() && params[3].HasMore() && !params[4].HasMore():
// We have color values separated by colons (:)
// 48 : 2 : 1 : 2 : 3
fallthrough
case !s.HasMore() && !p.HasMore() && p.Param(0) == 2 && !params[2].HasMore() && !params[3].HasMore() && !params[4].HasMore():
// Support legacy color values separated by semicolons (;)
// 48 ; 2 ; 1 ; 2 ; 3
n += 3
return params[2].Param(0), params[3].Param(0), params[4].Param(0), -1
}
// Ambiguous SGR color
return -1, -1, -1, -1
}

switch colorType {
case 0: // implementation defined
return 2, nil
case 1: // transparent
return 2, color.Transparent
case 2: // RGB direct color
if len(params) < 5 {
return 0, nil
}

r, g, b, _ := paramsfn()
if r == -1 || g == -1 || b == -1 {
return 0, nil
}

co = color.RGBA{
R: uint8(r), //nolint:gosec
G: uint8(g), //nolint:gosec
B: uint8(b), //nolint:gosec
A: 0xff,
}
return

case 3: // CMY direct color
if len(params) < 5 {
return 0, nil
}

c, m, y, _ := paramsfn()
if c == -1 || m == -1 || y == -1 {
return 0, nil
}

co = color.CMYK{
C: uint8(c), //nolint:gosec
M: uint8(m), //nolint:gosec
Y: uint8(y), //nolint:gosec
K: 0,
}
return

case 4: // CMYK direct color
if len(params) < 6 {
return 0, nil
}

c, m, y, k := paramsfn()
if c == -1 || m == -1 || y == -1 || k == -1 {
return 0, nil
}

co = color.CMYK{
C: uint8(c), //nolint:gosec
M: uint8(m), //nolint:gosec
Y: uint8(y), //nolint:gosec
K: uint8(k), //nolint:gosec
}
return

case 5: // indexed color
if len(params) < 3 {
return 0, nil
}
switch {
case s.HasMore() && p.HasMore() && !params[2].HasMore():
// Colon separated indexed color
// 38 : 5 : 234
case !s.HasMore() && !p.HasMore() && !params[2].HasMore():
// Legacy semicolon indexed color
// 38 ; 5 ; 234
default:
return 0, nil
}
co = ExtendedColor(params[2].Param(0)) //nolint:gosec
return 3, co

case 6: // RGBA direct color
if len(params) < 6 {
return 0, nil
}

r, g, b, a := paramsfn()
if r == -1 || g == -1 || b == -1 || a == -1 {
return 0, nil
}

co = color.RGBA{
R: uint8(r), //nolint:gosec
G: uint8(g), //nolint:gosec
B: uint8(b), //nolint:gosec
A: uint8(a), //nolint:gosec
}
return

default:
return 0, nil
}
}
Loading

0 comments on commit e24d5c3

Please sign in to comment.