diff --git a/examples/writer/writer.go b/examples/writer/writer.go index 151030e..c4ec800 100644 --- a/examples/writer/writer.go +++ b/examples/writer/writer.go @@ -12,6 +12,7 @@ import ( func main() { w := colorprofile.NewWriter(os.Stdout, os.Environ()) + w.SetHasDarkBackground(false) // Read from stdin and write to stdout bts, err := io.ReadAll(os.Stdin) diff --git a/writer.go b/writer.go index dd8a2a5..703c42b 100644 --- a/writer.go +++ b/writer.go @@ -29,15 +29,19 @@ func NewWriter(w io.Writer, environ []string) *Writer { // Writer represents a color profile writer that writes ANSI sequences to the // underlying writer. type Writer struct { - Forward io.Writer - Profile Profile + Forward io.Writer + Profile Profile + HasLightBackground bool +} + +// SetHasDarkBackground sets whether the background is dark or light. +func (w *Writer) SetHasDarkBackground(dark bool) { + w.HasLightBackground = !dark } // Write writes the given text to the underlying writer. func (w *Writer) Write(p []byte) (int, error) { switch w.Profile { - case TrueColor: - return w.Forward.Write(p) case NoTTY: return io.WriteString(w.Forward, ansi.Strip(string(p))) } @@ -75,7 +79,7 @@ func (w *Writer) Write(p []byte) (int, error) { style = style.ForegroundColor( w.Profile.Convert(ansi.BasicColor(param - 30))) //nolint:gosec case 38: // 16 or 24-bit foreground color - c := readColor(&i, parser.Params) + c := w.readColor(&i, parser.Params) if w.Profile > ANSI { continue } @@ -92,7 +96,7 @@ func (w *Writer) Write(p []byte) (int, error) { style = style.BackgroundColor( w.Profile.Convert(ansi.BasicColor(param - 40))) //nolint:gosec case 48: // 16 or 24-bit background color - c := readColor(&i, parser.Params) + c := w.readColor(&i, parser.Params) if w.Profile > ANSI { continue } @@ -103,7 +107,7 @@ func (w *Writer) Write(p []byte) (int, error) { } style = style.DefaultBackgroundColor() case 58: // 16 or 24-bit underline color - c := readColor(&i, parser.Params) + c := w.readColor(&i, parser.Params) if w.Profile > ANSI { continue } @@ -145,7 +149,7 @@ func (w *Writer) WriteString(s string) (n int, err error) { return w.Write([]byte(s)) } -func readColor(idxp *int, params []int) (c ansi.Color) { +func (w *Writer) readColor(idxp *int, params []int) (c ansi.Color) { i := *idxp paramsLen := len(params) if i > paramsLen-1 { @@ -170,6 +174,36 @@ func readColor(idxp *int, params []int) (c ansi.Color) { } c = ansi.ExtendedColor(ansi.Param(params[i+2])) //nolint:gosec *idxp += 2 + case 4: // lightDark(RGB) + if i > paramsLen-7 { + return + } + if w.HasLightBackground { + c = color.RGBA{ + R: uint8(ansi.Param(params[i+2])), //nolint:gosec + G: uint8(ansi.Param(params[i+3])), //nolint:gosec + B: uint8(ansi.Param(params[i+4])), //nolint:gosec + A: 0xff, + } + } else { + c = color.RGBA{ + R: uint8(ansi.Param(params[i+5])), //nolint:gosec + G: uint8(ansi.Param(params[i+6])), //nolint:gosec + B: uint8(ansi.Param(params[i+7])), //nolint:gosec + A: 0xff, + } + } + *idxp += 7 + case 10: // lightDark(256 colors) + if i > paramsLen-3 { + return + } + if w.HasLightBackground { + c = ansi.ExtendedColor(ansi.Param(params[i+2])) //nolint:gosec + } else { + c = ansi.ExtendedColor(ansi.Param(params[i+3])) //nolint:gosec + } + *idxp += 3 } return } diff --git a/writer_test.go b/writer_test.go index 86741bc..a43c7f2 100644 --- a/writer_test.go +++ b/writer_test.go @@ -9,11 +9,11 @@ import ( ) var writers = map[Profile]func(io.Writer) *Writer{ - TrueColor: func(w io.Writer) *Writer { return &Writer{w, TrueColor} }, - ANSI256: func(w io.Writer) *Writer { return &Writer{w, ANSI256} }, - ANSI: func(w io.Writer) *Writer { return &Writer{w, ANSI} }, - Ascii: func(w io.Writer) *Writer { return &Writer{w, Ascii} }, - NoTTY: func(w io.Writer) *Writer { return &Writer{w, NoTTY} }, + TrueColor: func(w io.Writer) *Writer { return &Writer{w, TrueColor, false} }, + ANSI256: func(w io.Writer) *Writer { return &Writer{w, ANSI256, false} }, + ANSI: func(w io.Writer) *Writer { return &Writer{w, ANSI, false} }, + Ascii: func(w io.Writer) *Writer { return &Writer{w, Ascii, false} }, + NoTTY: func(w io.Writer) *Writer { return &Writer{w, NoTTY, false} }, } var writer_cases = []struct { @@ -102,11 +102,19 @@ var writer_cases = []struct { { name: "simple ansi 256 color bg", input: "hello \x1b[48:5:196mworld\x1b[m", - expectedTrueColor: "hello \x1b[48:5:196mworld\x1b[m", + expectedTrueColor: "hello \x1b[48;5;196mworld\x1b[m", expectedANSI256: "hello \x1b[48;5;196mworld\x1b[m", expectedANSI: "hello \x1b[101mworld\x1b[m", expectedAscii: "hello \x1b[mworld\x1b[m", }, + { + name: "adaptive color", + input: "hello \x1b[38;10;255;55mworld\x1b[m", // #ff8537 + expectedTrueColor: "hello \x1b[38;5;55mworld\x1b[m", + expectedANSI256: "hello \x1b[38;5;55mworld\x1b[m", + expectedANSI: "hello \x1b[94mworld\x1b[m", + expectedAscii: "hello \x1b[mworld\x1b[m", + }, } func TestWriter(t *testing.T) { @@ -145,7 +153,7 @@ func TestNewWriterPanic(t *testing.T) { } func BenchmarkWriter(b *testing.B) { - w := &Writer{&bytes.Buffer{}, ANSI} + w := &Writer{&bytes.Buffer{}, ANSI, false} input := []byte("\x1b[1;3;59mhello\x1b[m \x1b[38;2;255;133;55mworld\x1b[m") for i := 0; i < b.N; i++ { _, _ = w.Write(input)