Color.java
/*
* Copyright (c) 2016, Stein Eldar Johnsen
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.strings.chr;
import net.morimekta.strings.EscapeUtil;
import java.util.Arrays;
import java.util.Objects;
import java.util.TreeSet;
/**
* Unix terminal color helper. The printed syntax of the color is:
* <pre>
* \033[${code}m
* \033[${code};${code}m
* </pre>
* etc.
*/
public class Color extends Control {
/** Clear all color settings. */
public static final Color CLEAR = new Color(0);
/** Use default foreground color. */
public static final Color DEFAULT = new Color(39);
/** Use black foreground color. */
public static final Color BLACK = new Color(30);
/** Use red foreground color. */
public static final Color RED = new Color(31);
/** Use green foreground color. */
public static final Color GREEN = new Color(32);
/** Use yellow foreground color. */
public static final Color YELLOW = new Color(33);
/** Use blue foreground color. */
public static final Color BLUE = new Color(34);
/** Use magenta foreground color. */
public static final Color MAGENTA = new Color(35);
/** Use cyan foreground color. */
public static final Color CYAN = new Color(36);
/** Use white foreground color. */
public static final Color WHITE = new Color(37);
/** Use default background color. */
public static final Color BG_DEFAULT = new Color(49);
/** Use black background color. */
public static final Color BG_BLACK = new Color(40);
/** Use red background color. */
public static final Color BG_RED = new Color(41);
/** Use green background color. */
public static final Color BG_GREEN = new Color(42);
/** Use yellow background color. */
public static final Color BG_YELLOW = new Color(43);
/** Use blue background color. */
public static final Color BG_BLUE = new Color(44);
/** Use magenta background color. */
public static final Color BG_MAGENTA = new Color(45);
/** Use cyan background color. */
public static final Color BG_CYAN = new Color(46);
/** Use white background color. */
public static final Color BG_WHITE = new Color(47);
/** Make foreground bold. */
public static final Color BOLD = new Color(1);
/** Make foreground dim. */
public static final Color DIM = new Color(2);
/** Make foreground underlined. */
public static final Color UNDERLINE = new Color(4);
/** Make foreground stroked over. */
public static final Color STROKE = new Color(9);
/** Invert foreground and background color. */
public static final Color INVERT = new Color(7);
/** Invert foreground same as background color. */
public static final Color HIDDEN = new Color(8);
/** Remove foreground bold modifier. */
public static final Color UNSET_BOLD = new Color(21);
/** Remove foreground dim modifier. */
public static final Color UNSET_DIM = new Color(22);
/** Remove foreground underline modifier. */
public static final Color UNSET_UNDERLINE = new Color(24);
/** Remove foreground stroke modifier. */
public static final Color UNSET_STROKE = new Color(29);
/** Remove foreground inversion modifier. */
public static final Color UNSET_INVERT = new Color(27);
/** Remove foreground hidden modifier. */
public static final Color UNSET_HIDDEN = new Color(28);
private final int[] mods;
/**
* Create a color with the given modifiers.
*
* @param values List of modifiers.
*/
public Color(int... values) {
this(mkModSet(values));
}
/**
* Combine the given colors.
*
* @param colors The colors to combine.
*/
public Color(Color... colors) {
this(mkModSet(colors));
}
/**
* Parse a char sequence as a color. The sequence is already assumed to
* have been recognized as a color control sequence, but the validity
* (and usability) of the sequence is not controlled.
*
* @param ctrl Control sequence to make a color instance from.
*/
Color(CharSequence ctrl) {
this(parseModSet(ctrl));
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || !o.getClass().equals(getClass())) {
return false;
}
Color other = (Color) o;
return Arrays.equals(mods, other.mods);
}
@Override
public int hashCode() {
return Objects.hash(Arrays.hashCode(mods));
}
private Color(TreeSet<Integer> mods) {
super(mkString(mods));
this.mods = new int[mods.size()];
int i = 0;
for (int v : mods) {
this.mods[i] = v;
++i;
}
}
/**
* Generate the color string.
*
* @param values The values combine into the color string.
* @return The shell color-settings string.
*/
private static String mkString(TreeSet<Integer> values) {
StringBuilder builder = new StringBuilder();
builder.append("\033["); // escape.
boolean first = true;
for (int i : values) {
if (first) {
first = false;
} else {
builder.append(';');
}
builder.append(i);
}
builder.append("m");
return builder.toString();
}
private static TreeSet<Integer> mkModSet(Color... colors) {
TreeSet<Integer> mods = new TreeSet<>();
int fg = 0;
int bg = 0;
all:
for (Color c : colors) {
for (int i : c.mods) {
if (i == 0) {
fg = 0;
bg = 0;
mods.clear();
mods.add(0);
break all;
} else if (i < 10) {
mods.add(i);
mods.remove(i + 20);
} else if (i < 30) {
mods.add(i);
mods.remove(i - 20);
} else if (i < 40) {
fg = i;
} else if (i < 50) {
bg = i;
}
}
}
if (fg != 0) {
mods.add(fg);
}
if (bg != 0) {
mods.add(bg);
}
return mods;
}
private static TreeSet<Integer> mkModSet(int... values) {
TreeSet<Integer> mods = new TreeSet<>();
int fg = 0;
int bg = 0;
for (int i : values) {
if (i == 0) {
fg = 0;
bg = 0;
mods.clear();
mods.add(0);
break;
} else if (i < 10) { // 1..9: modifiers
// Text modifier.
mods.add(i);
mods.remove(i + 20);
} else if (i < 30) { // 21..29, unset mods.
// Negative (unset) text modifier.
mods.add(i);
mods.remove(i - 20);
} else if (i < 40) { // 30..39, text color.
// Foreground (text) color.
fg = i;
} else if (i < 50) { // 40..49, background color.
// Background color.
bg = i;
}
}
if (fg != 0) {
mods.add(fg);
}
if (bg != 0) {
mods.add(bg);
}
return mods;
}
private static TreeSet<Integer> parseModSet(CharSequence ctrl) {
int v = 0;
int n = 0;
TreeSet<Integer> mods = new TreeSet<>();
for (int i = 2; i < ctrl.length(); ++i) {
char c = ctrl.charAt(i);
if ('0' <= c && c <= '9') {
v *= 10;
v += (c - '0');
++n;
} else if (n > 0) {
mods.add(v);
v = 0;
n = 0;
} else {
throw new IllegalArgumentException("Invalid color control sequence: \"" + EscapeUtil.javaEscape(ctrl) + "\"");
}
}
return mods;
}
}