import java.awt.image.BufferedImage;
import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import javax.imageio.ImageIO;
import jexer.TImage;
import jexer.bits.Cell;
MOUSE_SGR,
}
- /**
- * Number of colors in the sixel palette. Xterm 335 defines the max as
- * 1024.
- */
- private static final int MAX_COLOR_REGISTERS = 1024;
- // Black-and-white is possible too.
- // private static final int MAX_COLOR_REGISTERS = 2;
-
// ------------------------------------------------------------------------
// Variables --------------------------------------------------------------
// ------------------------------------------------------------------------
*/
private TResizeEvent windowResize = null;
+ /**
+ * If true, emit wide-char (CJK/Emoji) characters as sixel images.
+ */
+ private boolean wideCharImages = true;
+
/**
* Window width in pixels. Used for sixel support.
*/
/**
* The sixel post-rendered string cache.
*/
- private SixelCache sixelCache = null;
+ private ImageCache sixelCache = null;
+
+ /**
+ * Number of colors in the sixel palette. Xterm 335 defines the max as
+ * 1024. Valid values are: 2 (black and white), 256, 512, 1024, and
+ * 2048.
+ */
+ private int sixelPaletteSize = 1024;
+
+ /**
+ * If true, emit image data via iTerm2 image protocol.
+ */
+ private boolean iterm2Images = false;
+
+ /**
+ * The iTerm2 post-rendered string cache.
+ */
+ private ImageCache iterm2Cache = null;
+
+ /**
+ * If true, emit image data via Jexer image protocol.
+ */
+ private boolean jexerImages = false;
+
+ /**
+ * The Jexer post-rendered string cache.
+ */
+ private ImageCache jexerCache = null;
+
+ /**
+ * Base64 encoder used by iTerm2 and Jexer images.
+ */
+ private java.util.Base64.Encoder base64 = null;
/**
* If true, then we changed System.in and need to change it back.
*/
- private boolean setRawMode;
+ private boolean setRawMode = false;
+
+ /**
+ * If true, '?' was seen in terminal response.
+ */
+ private boolean decPrivateModeFlag = false;
/**
* The terminal's input. If an InputStream is not specified in the
*/
private Object listener;
+ // Colors to map DOS colors to AWT colors.
+ private static java.awt.Color MYBLACK;
+ private static java.awt.Color MYRED;
+ private static java.awt.Color MYGREEN;
+ private static java.awt.Color MYYELLOW;
+ private static java.awt.Color MYBLUE;
+ private static java.awt.Color MYMAGENTA;
+ private static java.awt.Color MYCYAN;
+ private static java.awt.Color MYWHITE;
+ private static java.awt.Color MYBOLD_BLACK;
+ private static java.awt.Color MYBOLD_RED;
+ private static java.awt.Color MYBOLD_GREEN;
+ private static java.awt.Color MYBOLD_YELLOW;
+ private static java.awt.Color MYBOLD_BLUE;
+ private static java.awt.Color MYBOLD_MAGENTA;
+ private static java.awt.Color MYBOLD_CYAN;
+ private static java.awt.Color MYBOLD_WHITE;
+
/**
* SixelPalette is used to manage the conversion of images between 24-bit
- * RGB color and a palette of MAX_COLOR_REGISTERS colors.
+ * RGB color and a palette of sixelPaletteSize colors.
*/
private class SixelPalette {
* Map of color palette index for sixel output, from the order it was
* generated by makePalette() to rgbColors.
*/
- private int [] rgbSortedIndex = new int[MAX_COLOR_REGISTERS];
+ private int [] rgbSortedIndex = new int[sixelPaletteSize];
/**
* The color palette, organized by hue, saturation, and luminance.
int green = (color >>> 8) & 0xFF;
int blue = color & 0xFF;
- if (MAX_COLOR_REGISTERS == 2) {
+ if (sixelPaletteSize == 2) {
if (((red * red) + (green * green) + (blue * blue)) < 35568) {
// Black
return 0;
((255 - blue) * (255 - blue))) < diff) {
// White is a closer match.
- idx = MAX_COLOR_REGISTERS - 1;
+ idx = sixelPaletteSize - 1;
}
assert (idx != -1);
return idx;
}
/**
- * Dither an image to a MAX_COLOR_REGISTERS palette. The dithered
+ * Dither an image to a sixelPaletteSize palette. The dithered
* image cells will contain indexes into the palette.
*
* @param image the image to dither
imageY) & 0xFFFFFF;
int colorIdx = matchColor(oldPixel);
assert (colorIdx >= 0);
- assert (colorIdx < MAX_COLOR_REGISTERS);
+ assert (colorIdx < sixelPaletteSize);
int newPixel = rgbColors.get(colorIdx);
ditheredImage.setRGB(imageX, imageY, colorIdx);
private void makePalette() {
// Generate the sixel palette. Because we have no idea at this
// layer which image(s) will be shown, we have to use a common
- // palette with MAX_COLOR_REGISTERS colors for everything, and
+ // palette with sixelPaletteSize colors for everything, and
// map the BufferedImage colors to their nearest neighbor in RGB
// space.
- if (MAX_COLOR_REGISTERS == 2) {
+ if (sixelPaletteSize == 2) {
rgbColors.add(0);
rgbColors.add(0xFFFFFF);
rgbSortedIndex[0] = 0;
satBits = 2;
lumBits = 1;
- assert (MAX_COLOR_REGISTERS >= 256);
- assert ((MAX_COLOR_REGISTERS == 256)
- || (MAX_COLOR_REGISTERS == 512)
- || (MAX_COLOR_REGISTERS == 1024)
- || (MAX_COLOR_REGISTERS == 2048));
+ assert (sixelPaletteSize >= 256);
+ assert ((sixelPaletteSize == 256)
+ || (sixelPaletteSize == 512)
+ || (sixelPaletteSize == 1024)
+ || (sixelPaletteSize == 2048));
- switch (MAX_COLOR_REGISTERS) {
+ switch (sixelPaletteSize) {
case 512:
hueBits = 5;
satBits = 2;
}
// System.err.printf("\n</body></html>\n");
- assert (rgbColors.size() == MAX_COLOR_REGISTERS);
+ assert (rgbColors.size() == sixelPaletteSize);
/*
* We need to sort rgbColors, so that toSixel() can know where
Collections.sort(rgbColors);
HashMap<Integer, Integer> rgbColorIndices = null;
rgbColorIndices = new HashMap<Integer, Integer>();
- for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ for (int i = 0; i < sixelPaletteSize; i++) {
rgbColorIndices.put(rgbColors.get(i), i);
}
- for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ for (int i = 0; i < sixelPaletteSize; i++) {
int rawColor = rawRgbList.get(i);
rgbSortedIndex[i] = rgbColorIndices.get(rawColor);
}
if (DEBUG) {
- for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ for (int i = 0; i < sixelPaletteSize; i++) {
assert (rawRgbList != null);
int idx = rgbSortedIndex[i];
int rgbColor = rgbColors.get(idx);
- if ((idx != 0) && (idx != MAX_COLOR_REGISTERS - 1)) {
+ if ((idx != 0) && (idx != sixelPaletteSize - 1)) {
/*
System.err.printf("%d %06x --> %d %06x\n",
i, rawRgbList.get(i), idx, rgbColors.get(idx));
// Set the dimmest color as true black, and the brightest as true
// white.
rgbColors.set(0, 0);
- rgbColors.set(MAX_COLOR_REGISTERS - 1, 0xFFFFFF);
+ rgbColors.set(sixelPaletteSize - 1, 0xFFFFFF);
/*
System.err.printf("<html><body>\n");
public String emitPalette(final StringBuilder sb,
final boolean [] used) {
- for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ for (int i = 0; i < sixelPaletteSize; i++) {
if (((used != null) && (used[i] == true)) || (used == null)) {
int rgbColor = rgbColors.get(i);
sb.append(String.format("#%d;2;%d;%d;%d", i,
}
/**
- * SixelCache is a least-recently-used cache that hangs on to the
- * post-rendered sixel string for a particular set of cells.
+ * ImageCache is a least-recently-used cache that hangs on to the
+ * post-rendered sixel or iTerm2 string for a particular set of cells.
*/
- private class SixelCache {
+ private class ImageCache {
/**
* Maximum size of the cache.
*
* @param maxSize the maximum size of the cache
*/
- public SixelCache(final int maxSize) {
+ public ImageCache(final int maxSize) {
this.maxSize = maxSize;
cache = new HashMap<String, CacheEntry>();
}
// ------------------------------------------------------------------------
/**
- * Constructor sets up state for getEvent().
+ * Constructor sets up state for getEvent(). If either windowWidth or
+ * windowHeight are less than 1, the terminal is not resized.
*
* @param listener the object this backend needs to wake up when new
* input comes in
* @param input an InputStream connected to the remote user, or null for
* System.in. If System.in is used, then on non-Windows systems it will
- * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
- * mode. input is always converted to a Reader with UTF-8 encoding.
+ * be put in raw mode; closeTerminal() will (blindly!) put System.in in
+ * cooked mode. input is always converted to a Reader with UTF-8
+ * encoding.
* @param output an OutputStream connected to the remote user, or null
* for System.out. output is always converted to a Writer with UTF-8
* encoding.
// Send dtterm/xterm sequences, which will probably not work because
// allowWindowOps is defaulted to false.
- String resizeString = String.format("\033[8;%d;%dt", windowHeight,
- windowWidth);
- this.output.write(resizeString);
- this.output.flush();
+ if ((windowWidth > 0) && (windowHeight > 0)) {
+ String resizeString = String.format("\033[8;%d;%dt", windowHeight,
+ windowWidth);
+ this.output.write(resizeString);
+ this.output.flush();
+ }
}
/**
* input comes in
* @param input an InputStream connected to the remote user, or null for
* System.in. If System.in is used, then on non-Windows systems it will
- * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
- * mode. input is always converted to a Reader with UTF-8 encoding.
+ * be put in raw mode; closeTerminal() will (blindly!) put System.in in
+ * cooked mode. input is always converted to a Reader with UTF-8
+ * encoding.
* @param output an OutputStream connected to the remote user, or null
* for System.out. output is always converted to a Writer with UTF-8
* encoding.
"UTF-8"));
}
- // Request xterm report window dimensions in pixels
- this.output.printf("%s", xtermReportWindowPixelDimensions());
+ // Request Device Attributes
+ this.output.printf("\033[c");
+
+ // Request xterm report window/cell dimensions in pixels
+ this.output.printf("%s", xtermReportPixelDimensions());
// Enable mouse reporting and metaSendsEscape
this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
this.output.flush();
+ // Request xterm use the sixel settings we want
+ this.output.printf("%s", xtermSetSixelSettings());
+
// Query the screen size
sessionInfo.queryWindowSize();
setDimensions(sessionInfo.getWindowWidth(),
this.output = writer;
- // Request xterm report window dimensions in pixels
- this.output.printf("%s", xtermReportWindowPixelDimensions());
+ // Request Device Attributes
+ this.output.printf("\033[c");
+
+ // Request xterm report window/cell dimensions in pixels
+ this.output.printf("%s", xtermReportPixelDimensions());
// Enable mouse reporting and metaSendsEscape
this.output.printf("%s%s", mouse(true), xtermMetaSendsEscape(true));
this.output.flush();
+ // Request xterm use the sixel settings we want
+ this.output.printf("%s", xtermSetSixelSettings());
+
// Query the screen size
sessionInfo.queryWindowSize();
setDimensions(sessionInfo.getWindowWidth(),
*/
public void closeTerminal() {
- // System.err.println("=== shutdown() ==="); System.err.flush();
+ // System.err.println("=== closeTerminal() ==="); System.err.flush();
// Tell the reader thread to stop looking at input
stopReaderThread = true;
// Disable mouse reporting and show cursor. Defensive null check
// here in case closeTerminal() is called twice.
if (output != null) {
- output.printf("%s%s%s", mouse(false), cursor(true), normal());
+ output.printf("%s%s%s%s", mouse(false), cursor(true),
+ defaultColor(), xtermResetSixelSettings());
output.flush();
}
doRgbColor = false;
}
+ // Default to using images for full-width characters.
+ if (System.getProperty("jexer.ECMA48.wideCharImages",
+ "true").equals("true")) {
+ wideCharImages = true;
+ } else {
+ wideCharImages = false;
+ }
+
// Pull the system properties for sixel output.
if (System.getProperty("jexer.ECMA48.sixel", "true").equals("true")) {
sixel = true;
} else {
sixel = false;
}
+
+ // Palette size
+ int paletteSize = 1024;
+ try {
+ paletteSize = Integer.parseInt(System.getProperty(
+ "jexer.ECMA48.sixelPaletteSize", "1024"));
+ switch (paletteSize) {
+ case 2:
+ case 256:
+ case 512:
+ case 1024:
+ case 2048:
+ sixelPaletteSize = paletteSize;
+ break;
+ default:
+ // Ignore value
+ break;
+ }
+ } catch (NumberFormatException e) {
+ // SQUASH
+ }
+
+ // Default to using images for full-width characters.
+ if (System.getProperty("jexer.ECMA48.iTerm2Images",
+ "false").equals("true")) {
+ iterm2Images = true;
+ } else {
+ iterm2Images = false;
+ }
+
+ // Set custom colors
+ setCustomSystemColors();
}
// ------------------------------------------------------------------------
// Image cell: bypass the rest of the loop, it is not
// rendered here.
- if (lCell.isImage()) {
+ if ((wideCharImages && lCell.isImage())
+ || (!wideCharImages
+ && lCell.isImage()
+ && (lCell.getWidth() == Cell.Width.SINGLE))
+ ) {
hasImage = true;
// Save the last rendered cell
continue;
}
- assert (!lCell.isImage());
+ assert ((wideCharImages && !lCell.isImage())
+ || (!wideCharImages
+ && (!lCell.isImage()
+ || (lCell.isImage()
+ && (lCell.getWidth() != Cell.Width.SINGLE)))));
+
+ if (!wideCharImages && (lCell.getWidth() == Cell.Width.RIGHT)) {
+ continue;
+ }
+
if (hasImage) {
hasImage = false;
sb.append(gotoXY(x, y));
}
// Emit the character
- sb.append(lCell.getChar());
+ if (wideCharImages
+ // Don't emit the right-half of full-width chars.
+ || (!wideCharImages
+ && (lCell.getWidth() != Cell.Width.RIGHT))
+ ) {
+ sb.append(Character.toChars(lCell.getChar()));
+ }
// Save the last rendered cell
lastX = x;
}
/*
- * For sixel support, draw all of the sixel output first, and then
+ * For images support, draw all of the image output first, and then
* draw everything else afterwards. This works OK, but performance
* is still a drag on larger pictures.
*/
Cell lCell = logical[x][y];
Cell pCell = physical[x][y];
- if (!lCell.isImage()) {
+ if (!lCell.isImage()
+ || (!wideCharImages
+ && (lCell.getWidth() != Cell.Width.SINGLE))
+ ) {
continue;
}
physical[x + i][y].setTo(lCell);
}
if (cellsToDraw.size() > 0) {
- sb.append(toSixel(x, y, cellsToDraw));
+ if (iterm2Images) {
+ sb.append(toIterm2Image(x, y, cellsToDraw));
+ } else if (jexerImages) {
+ sb.append(toJexerImage(x, y, cellsToDraw));
+ } else {
+ sb.append(toSixel(x, y, cellsToDraw));
+ }
}
x = right;
params = new ArrayList<String>();
params.clear();
params.add("");
+ decPrivateModeFlag = false;
}
/**
getTextWidth() + " x " + getTextHeight());
}
- this.output.printf("%s", xtermReportWindowPixelDimensions());
+ this.output.printf("%s", xtermReportPixelDimensions());
this.output.flush();
TResizeEvent event = new TResizeEvent(TResizeEvent.Type.SCREEN,
// Mouse position, SGR (1006) coordinates
state = ParseState.MOUSE_SGR;
return;
+ case '?':
+ // DEC private mode flag
+ decPrivateModeFlag = true;
+ return;
default:
break;
}
events.add(new TKeypressEvent(kbEnd, alt, ctrl, shift));
resetParser();
return;
+ case 'c':
+ // Device Attributes
+ if (decPrivateModeFlag == false) {
+ break;
+ }
+ for (String x: params) {
+ if (x.equals("4")) {
+ // Terminal reports sixel support
+ if (debugToStderr) {
+ System.err.println("Device Attributes: sixel");
+ }
+ }
+ if (x.equals("444")) {
+ // Terminal reports Jexer images support
+ if (debugToStderr) {
+ System.err.println("Device Attributes: Jexer images");
+ }
+ jexerImages = true;
+ }
+ }
+ return;
case 't':
// windowOps
if ((params.size() > 2) && (params.get(0).equals("4"))) {
heightPixels = 400;
}
}
+ if ((params.size() > 2) && (params.get(0).equals("6"))) {
+ if (debugToStderr) {
+ System.err.printf("windowOp text cell pixels: " +
+ "height %s width %s\n",
+ params.get(1), params.get(2));
+ }
+ try {
+ widthPixels = width * Integer.parseInt(params.get(2));
+ heightPixels = height * Integer.parseInt(params.get(1));
+ } catch (NumberFormatException e) {
+ if (debugToStderr) {
+ e.printStackTrace();
+ }
+ }
+ if (widthPixels <= 0) {
+ widthPixels = 640;
+ }
+ if (heightPixels <= 0) {
+ heightPixels = 400;
+ }
+ }
resetParser();
return;
default:
}
/**
- * Request (u)xterm to report the current window size dimensions.
+ * Request (u)xterm to use the sixel settings we need:
+ *
+ * - enable sixel scrolling
+ *
+ * - disable private color registers (so that we can use one common
+ * palette)
+ *
+ * @return the string to emit to xterm
+ */
+ private String xtermSetSixelSettings() {
+ return "\033[?80h\033[?1070l";
+ }
+
+ /**
+ * Restore (u)xterm its default sixel settings:
+ *
+ * - enable sixel scrolling
+ *
+ * - enable private color registers
+ *
+ * @return the string to emit to xterm
+ */
+ private String xtermResetSixelSettings() {
+ return "\033[?80h\033[?1070h";
+ }
+
+ /**
+ * Request (u)xterm to report the current window and cell size dimensions
+ * in pixels.
*
* @return the string to emit to xterm
*/
- private String xtermReportWindowPixelDimensions() {
- return "\033[14t";
+ private String xtermReportPixelDimensions() {
+ // We will ask for both window and text cell dimensions, and
+ // hopefully one of them will work.
+ return "\033[14t\033[16t";
}
/**
// Sixel output support ---------------------------------------------------
// ------------------------------------------------------------------------
+ /**
+ * Get the number of colors in the sixel palette.
+ *
+ * @return the palette size
+ */
+ public int getSixelPaletteSize() {
+ return sixelPaletteSize;
+ }
+
+ /**
+ * Set the number of colors in the sixel palette.
+ *
+ * @param paletteSize the new palette size
+ */
+ public void setSixelPaletteSize(final int paletteSize) {
+ if (paletteSize == sixelPaletteSize) {
+ return;
+ }
+
+ switch (paletteSize) {
+ case 2:
+ case 256:
+ case 512:
+ case 1024:
+ case 2048:
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported sixel palette " +
+ " size: " + paletteSize);
+ }
+
+ // Don't step on the screen refresh thread.
+ synchronized (this) {
+ sixelPaletteSize = paletteSize;
+ palette = null;
+ sixelCache = null;
+ clearPhysical();
+ }
+ }
+
/**
* Start a sixel string for display one row's worth of bitmap data.
*
if (palette == null) {
palette = new SixelPalette();
+ // TODO: make this an option (shared palette or not)
+ palette.emitPalette(sb, null);
}
return sb.toString();
return sb.toString();
}
+ if (y == height - 1) {
+ // We are on the bottom row. If scrolling mode is enabled
+ // (default), then VT320/xterm will scroll the entire screen if
+ // we draw any pixels here.
+
+ // TODO: support sixel scrolling mode disabled as an option.
+ sb.append(normal());
+ sb.append(gotoXY(x, y));
+ for (int j = 0; j < cells.size(); j++) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
if (sixelCache == null) {
- sixelCache = new SixelCache(height * 10);
+ sixelCache = new ImageCache(height * 10);
}
// Save and get rows to/from the cache that do NOT have inverted
int [] rgbArray;
for (int i = 0; i < cells.size() - 1; i++) {
- if (cells.get(i).isInvertedImage()) {
+ int tileWidth = Math.min(cells.get(i).getImage().getWidth(),
+ imageWidth);
+ int tileHeight = Math.min(cells.get(i).getImage().getHeight(),
+ imageHeight);
+
+ if (false && cells.get(i).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do
+ // that anymore.
rgbArray = new int[imageWidth * imageHeight];
for (int j = 0; j < rgbArray.length; j++) {
rgbArray[j] = 0xFFFFFF;
}
} else {
- rgbArray = cells.get(i).getImage().getRGB(0, 0,
- imageWidth, imageHeight, null, 0, imageWidth);
+ try {
+ rgbArray = cells.get(i).getImage().getRGB(0, 0,
+ tileWidth, tileHeight, null, 0, tileWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight +
+ "tile " + tileWidth + "x" +
+ tileHeight +
+ " cells.get(i).getImage() " +
+ cells.get(i).getImage() +
+ " i " + i +
+ " fullWidth " + fullWidth +
+ " fullHeight " + fullHeight, e);
+ }
}
/*
fullWidth, fullHeight, cells.size(), getTextWidth());
*/
- image.setRGB(i * imageWidth, 0, imageWidth, imageHeight,
- rgbArray, 0, imageWidth);
- if (imageHeight < fullHeight) {
+ image.setRGB(i * imageWidth, 0, tileWidth, tileHeight,
+ rgbArray, 0, tileWidth);
+ if (tileHeight < fullHeight) {
int backgroundColor = cells.get(i).getBackground().getRGB();
for (int imageX = 0; imageX < image.getWidth(); imageX++) {
for (int imageY = imageHeight; imageY < fullHeight;
}
}
totalWidth -= ((cells.size() - 1) * imageWidth);
- if (cells.get(cells.size() - 1).isInvertedImage()) {
+ if (false && cells.get(cells.size() - 1).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do that
+ // anymore.
rgbArray = new int[totalWidth * imageHeight];
for (int j = 0; j < rgbArray.length; j++) {
rgbArray[j] = 0xFFFFFF;
}
} else {
- rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
- totalWidth, imageHeight, null, 0, totalWidth);
+ try {
+ rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
+ totalWidth, imageHeight, null, 0, totalWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight + " cells.get(cells.size() - 1).getImage() " +
+ cells.get(cells.size() - 1).getImage(), e);
+ }
}
image.setRGB((cells.size() - 1) * imageWidth, 0, totalWidth,
imageHeight, rgbArray, 0, totalWidth);
// Dither the image. It is ok to lose the original here.
if (palette == null) {
palette = new SixelPalette();
+ // TODO: make this an option (shared palette or not)
+ palette.emitPalette(sb, null);
}
image = palette.ditherImage(image);
+ // Collect the raster information
+ int rasterHeight = 0;
+ int rasterWidth = image.getWidth();
+
+ /*
+
+ // TODO: make this an option (shared palette or not)
+
// Emit the palette, but only for the colors actually used by these
// cells.
- boolean [] usedColors = new boolean[MAX_COLOR_REGISTERS];
+ boolean [] usedColors = new boolean[sixelPaletteSize];
for (int imageX = 0; imageX < image.getWidth(); imageX++) {
for (int imageY = 0; imageY < image.getHeight(); imageY++) {
usedColors[image.getRGB(imageX, imageY)] = true;
}
}
palette.emitPalette(sb, usedColors);
+ */
// Render the entire row of cells.
for (int currentRow = 0; currentRow < fullHeight; currentRow += 6) {
int colorIdx = image.getRGB(imageX, imageY + currentRow);
assert (colorIdx >= 0);
- assert (colorIdx < MAX_COLOR_REGISTERS);
+ assert (colorIdx < sixelPaletteSize);
sixels[imageX][imageY] = colorIdx;
}
}
- for (int i = 0; i < MAX_COLOR_REGISTERS; i++) {
+ for (int i = 0; i < sixelPaletteSize; i++) {
boolean isUsed = false;
for (int imageX = 0; imageX < image.getWidth(); imageX++) {
for (int j = 0; j < 6; j++) {
// colored pixels, and select the color.
sb.append(String.format("$#%d", i));
+ int oldData = -1;
+ int oldDataCount = 0;
for (int imageX = 0; imageX < image.getWidth(); imageX++) {
// Add up all the pixels that match this color.
data += 32;
break;
}
+ if ((currentRow + j + 1) > rasterHeight) {
+ rasterHeight = currentRow + j + 1;
+ }
}
}
assert (data >= 0);
- assert (data < 127);
+ assert (data < 64);
data += 63;
- sb.append((char) data);
+
+ if (data == oldData) {
+ oldDataCount++;
+ } else {
+ if (oldDataCount == 1) {
+ sb.append((char) oldData);
+ } else if (oldDataCount > 1) {
+ sb.append(String.format("!%d", oldDataCount));
+ sb.append((char) oldData);
+ }
+ oldDataCount = 1;
+ oldData = data;
+ }
+
} // for (int imageX = 0; imageX < image.getWidth(); imageX++)
- } // for (int i = 0; i < MAX_COLOR_REGISTERS; i++)
+
+ // Emit the last sequence.
+ if (oldDataCount == 1) {
+ sb.append((char) oldData);
+ } else if (oldDataCount > 1) {
+ sb.append(String.format("!%d", oldDataCount));
+ sb.append((char) oldData);
+ }
+
+ } // for (int i = 0; i < sixelPaletteSize; i++)
// Advance to the next scan line.
sb.append("-");
// Kill the very last "-", because it is unnecessary.
sb.deleteCharAt(sb.length() - 1);
+ // Add the raster information
+ sb.insert(0, String.format("\"1;1;%d;%d", rasterWidth, rasterHeight));
+
if (saveInCache) {
// This row is OK to save into the cache.
sixelCache.put(cells, sb.toString());
return (startSixel(x, y) + sb.toString() + endSixel());
}
+ /**
+ * Get the sixel support flag.
+ *
+ * @return true if this terminal is emitting sixel
+ */
+ public boolean hasSixel() {
+ return sixel;
+ }
+
// ------------------------------------------------------------------------
// End sixel output support -----------------------------------------------
// ------------------------------------------------------------------------
+ // ------------------------------------------------------------------------
+ // iTerm2 image output support --------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Create an iTerm2 images string representing a row of several cells
+ * containing bitmap data.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param cells the cells containing the bitmap data
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String toIterm2Image(final int x, final int y,
+ final ArrayList<Cell> cells) {
+
+ StringBuilder sb = new StringBuilder();
+
+ assert (cells != null);
+ assert (cells.size() > 0);
+ assert (cells.get(0).getImage() != null);
+
+ if (iterm2Images == false) {
+ sb.append(normal());
+ sb.append(gotoXY(x, y));
+ for (int i = 0; i < cells.size(); i++) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ if (iterm2Cache == null) {
+ iterm2Cache = new ImageCache(height * 10);
+ base64 = java.util.Base64.getEncoder();
+ }
+
+ // Save and get rows to/from the cache that do NOT have inverted
+ // cells.
+ boolean saveInCache = true;
+ for (Cell cell: cells) {
+ if (cell.isInvertedImage()) {
+ saveInCache = false;
+ }
+ }
+ if (saveInCache) {
+ String cachedResult = iterm2Cache.get(cells);
+ if (cachedResult != null) {
+ // System.err.println("CACHE HIT");
+ sb.append(gotoXY(x, y));
+ sb.append(cachedResult);
+ return sb.toString();
+ }
+ // System.err.println("CACHE MISS");
+ }
+
+ int imageWidth = cells.get(0).getImage().getWidth();
+ int imageHeight = cells.get(0).getImage().getHeight();
+
+ // cells.get(x).getImage() has a dithered bitmap containing indexes
+ // into the color palette. Piece these together into one larger
+ // image for final rendering.
+ int totalWidth = 0;
+ int fullWidth = cells.size() * getTextWidth();
+ int fullHeight = getTextHeight();
+ for (int i = 0; i < cells.size(); i++) {
+ totalWidth += cells.get(i).getImage().getWidth();
+ }
+
+ BufferedImage image = new BufferedImage(fullWidth,
+ fullHeight, BufferedImage.TYPE_INT_ARGB);
+
+ int [] rgbArray;
+ for (int i = 0; i < cells.size() - 1; i++) {
+ int tileWidth = Math.min(cells.get(i).getImage().getWidth(),
+ imageWidth);
+ int tileHeight = Math.min(cells.get(i).getImage().getHeight(),
+ imageHeight);
+ if (false && cells.get(i).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do
+ // that anymore.
+ rgbArray = new int[imageWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ try {
+ rgbArray = cells.get(i).getImage().getRGB(0, 0,
+ tileWidth, tileHeight, null, 0, tileWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight +
+ "tile " + tileWidth + "x" +
+ tileHeight +
+ " cells.get(i).getImage() " +
+ cells.get(i).getImage() +
+ " i " + i +
+ " fullWidth " + fullWidth +
+ " fullHeight " + fullHeight, e);
+ }
+ }
+
+ /*
+ System.err.printf("calling image.setRGB(): %d %d %d %d %d\n",
+ i * imageWidth, 0, imageWidth, imageHeight,
+ 0, imageWidth);
+ System.err.printf(" fullWidth %d fullHeight %d cells.size() %d textWidth %d\n",
+ fullWidth, fullHeight, cells.size(), getTextWidth());
+ */
+
+ image.setRGB(i * imageWidth, 0, tileWidth, tileHeight,
+ rgbArray, 0, tileWidth);
+ if (tileHeight < fullHeight) {
+ int backgroundColor = cells.get(i).getBackground().getRGB();
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int imageY = imageHeight; imageY < fullHeight;
+ imageY++) {
+
+ image.setRGB(imageX, imageY, backgroundColor);
+ }
+ }
+ }
+ }
+ totalWidth -= ((cells.size() - 1) * imageWidth);
+ if (false && cells.get(cells.size() - 1).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do that
+ // anymore.
+ rgbArray = new int[totalWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ try {
+ rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
+ totalWidth, imageHeight, null, 0, totalWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight + " cells.get(cells.size() - 1).getImage() " +
+ cells.get(cells.size() - 1).getImage(), e);
+ }
+ }
+ image.setRGB((cells.size() - 1) * imageWidth, 0, totalWidth,
+ imageHeight, rgbArray, 0, totalWidth);
+
+ if (totalWidth < getTextWidth()) {
+ int backgroundColor = cells.get(cells.size() - 1).getBackground().getRGB();
+
+ for (int imageX = image.getWidth() - totalWidth;
+ imageX < image.getWidth(); imageX++) {
+
+ for (int imageY = 0; imageY < fullHeight; imageY++) {
+ image.setRGB(imageX, imageY, backgroundColor);
+ }
+ }
+ }
+
+ /*
+ * From https://iterm2.com/documentation-images.html:
+ *
+ * Protocol
+ *
+ * iTerm2 extends the xterm protocol with a set of proprietary escape
+ * sequences. In general, the pattern is:
+ *
+ * ESC ] 1337 ; key = value ^G
+ *
+ * Whitespace is shown here for ease of reading: in practice, no
+ * spaces should be used.
+ *
+ * For file transfer and inline images, the code is:
+ *
+ * ESC ] 1337 ; File = [optional arguments] : base-64 encoded file contents ^G
+ *
+ * The optional arguments are formatted as key=value with a semicolon
+ * between each key-value pair. They are described below:
+ *
+ * Key Description of value
+ * name base-64 encoded filename. Defaults to "Unnamed file".
+ * size File size in bytes. Optional; this is only used by the
+ * progress indicator.
+ * width Width to render. See notes below.
+ * height Height to render. See notes below.
+ * preserveAspectRatio If set to 0, then the image's inherent aspect
+ * ratio will not be respected; otherwise, it
+ * will fill the specified width and height as
+ * much as possible without stretching. Defaults
+ * to 1.
+ * inline If set to 1, the file will be displayed inline. Otherwise,
+ * it will be downloaded with no visual representation in the
+ * terminal session. Defaults to 0.
+ *
+ * The width and height are given as a number followed by a unit, or
+ * the word "auto".
+ *
+ * N: N character cells.
+ * Npx: N pixels.
+ * N%: N percent of the session's width or height.
+ * auto: The image's inherent size will be used to determine an
+ * appropriate dimension.
+ *
+ */
+
+ // File contents can be several image formats. We will use PNG.
+ ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream(1024);
+ try {
+ if (!ImageIO.write(image.getSubimage(0, 0, image.getWidth(),
+ Math.min(image.getHeight(), fullHeight)),
+ "PNG", pngOutputStream)
+ ) {
+ // We failed to render image, bail out.
+ return "";
+ }
+ } catch (IOException e) {
+ // We failed to render image, bail out.
+ return "";
+ }
+
+ // iTerm2 does not advance the cursor automatically, so place it
+ // myself.
+ sb.append("\033]1337;File=");
+ /*
+ sb.append(String.format("width=$d;height=1;preserveAspectRatio=1;",
+ cells.size()));
+ */
+ /*
+ sb.append(String.format("width=$dpx;height=%dpx;preserveAspectRatio=1;",
+ image.getWidth(), Math.min(image.getHeight(),
+ getTextHeight())));
+ */
+ sb.append("inline=1:");
+ sb.append(base64.encodeToString(pngOutputStream.toByteArray()));
+ sb.append("\007");
+
+ if (saveInCache) {
+ // This row is OK to save into the cache.
+ iterm2Cache.put(cells, sb.toString());
+ }
+
+ return (gotoXY(x, y) + sb.toString());
+ }
+
+ /**
+ * Get the iTerm2 images support flag.
+ *
+ * @return true if this terminal is emitting iTerm2 images
+ */
+ public boolean hasIterm2Images() {
+ return iterm2Images;
+ }
+
+ // ------------------------------------------------------------------------
+ // End iTerm2 image output support ----------------------------------------
+ // ------------------------------------------------------------------------
+
+ // ------------------------------------------------------------------------
+ // Jexer image output support ---------------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Create a Jexer images string representing a row of several cells
+ * containing bitmap data.
+ *
+ * @param x column coordinate. 0 is the left-most column.
+ * @param y row coordinate. 0 is the top-most row.
+ * @param cells the cells containing the bitmap data
+ * @return the string to emit to an ANSI / ECMA-style terminal
+ */
+ private String toJexerImage(final int x, final int y,
+ final ArrayList<Cell> cells) {
+
+ StringBuilder sb = new StringBuilder();
+
+ assert (cells != null);
+ assert (cells.size() > 0);
+ assert (cells.get(0).getImage() != null);
+
+ if (jexerImages == false) {
+ sb.append(normal());
+ sb.append(gotoXY(x, y));
+ for (int i = 0; i < cells.size(); i++) {
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ if (jexerCache == null) {
+ jexerCache = new ImageCache(height * 10);
+ base64 = java.util.Base64.getEncoder();
+ }
+
+ // Save and get rows to/from the cache that do NOT have inverted
+ // cells.
+ boolean saveInCache = true;
+ for (Cell cell: cells) {
+ if (cell.isInvertedImage()) {
+ saveInCache = false;
+ }
+ }
+ if (saveInCache) {
+ String cachedResult = jexerCache.get(cells);
+ if (cachedResult != null) {
+ // System.err.println("CACHE HIT");
+ sb.append(gotoXY(x, y));
+ sb.append(cachedResult);
+ return sb.toString();
+ }
+ // System.err.println("CACHE MISS");
+ }
+
+ int imageWidth = cells.get(0).getImage().getWidth();
+ int imageHeight = cells.get(0).getImage().getHeight();
+
+ // cells.get(x).getImage() has a dithered bitmap containing indexes
+ // into the color palette. Piece these together into one larger
+ // image for final rendering.
+ int totalWidth = 0;
+ int fullWidth = cells.size() * getTextWidth();
+ int fullHeight = getTextHeight();
+ for (int i = 0; i < cells.size(); i++) {
+ totalWidth += cells.get(i).getImage().getWidth();
+ }
+
+ BufferedImage image = new BufferedImage(fullWidth,
+ fullHeight, BufferedImage.TYPE_INT_ARGB);
+
+ int [] rgbArray;
+ for (int i = 0; i < cells.size() - 1; i++) {
+ int tileWidth = Math.min(cells.get(i).getImage().getWidth(),
+ imageWidth);
+ int tileHeight = Math.min(cells.get(i).getImage().getHeight(),
+ imageHeight);
+ if (false && cells.get(i).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do
+ // that anymore.
+ rgbArray = new int[imageWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ try {
+ rgbArray = cells.get(i).getImage().getRGB(0, 0,
+ tileWidth, tileHeight, null, 0, tileWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight +
+ "tile " + tileWidth + "x" +
+ tileHeight +
+ " cells.get(i).getImage() " +
+ cells.get(i).getImage() +
+ " i " + i +
+ " fullWidth " + fullWidth +
+ " fullHeight " + fullHeight, e);
+ }
+ }
+
+ /*
+ System.err.printf("calling image.setRGB(): %d %d %d %d %d\n",
+ i * imageWidth, 0, imageWidth, imageHeight,
+ 0, imageWidth);
+ System.err.printf(" fullWidth %d fullHeight %d cells.size() %d textWidth %d\n",
+ fullWidth, fullHeight, cells.size(), getTextWidth());
+ */
+
+ image.setRGB(i * imageWidth, 0, tileWidth, tileHeight,
+ rgbArray, 0, tileWidth);
+ if (tileHeight < fullHeight) {
+ int backgroundColor = cells.get(i).getBackground().getRGB();
+ for (int imageX = 0; imageX < image.getWidth(); imageX++) {
+ for (int imageY = imageHeight; imageY < fullHeight;
+ imageY++) {
+
+ image.setRGB(imageX, imageY, backgroundColor);
+ }
+ }
+ }
+ }
+ totalWidth -= ((cells.size() - 1) * imageWidth);
+ if (false && cells.get(cells.size() - 1).isInvertedImage()) {
+ // I used to put an all-white cell over the cursor, don't do that
+ // anymore.
+ rgbArray = new int[totalWidth * imageHeight];
+ for (int j = 0; j < rgbArray.length; j++) {
+ rgbArray[j] = 0xFFFFFF;
+ }
+ } else {
+ try {
+ rgbArray = cells.get(cells.size() - 1).getImage().getRGB(0, 0,
+ totalWidth, imageHeight, null, 0, totalWidth);
+ } catch (Exception e) {
+ throw new RuntimeException("image " + imageWidth + "x" +
+ imageHeight + " cells.get(cells.size() - 1).getImage() " +
+ cells.get(cells.size() - 1).getImage(), e);
+ }
+ }
+ image.setRGB((cells.size() - 1) * imageWidth, 0, totalWidth,
+ imageHeight, rgbArray, 0, totalWidth);
+
+ if (totalWidth < getTextWidth()) {
+ int backgroundColor = cells.get(cells.size() - 1).getBackground().getRGB();
+
+ for (int imageX = image.getWidth() - totalWidth;
+ imageX < image.getWidth(); imageX++) {
+
+ for (int imageY = 0; imageY < fullHeight; imageY++) {
+ image.setRGB(imageX, imageY, backgroundColor);
+ }
+ }
+ }
+
+ sb.append(String.format("\033]444;%d;%d;0;", image.getWidth(),
+ Math.min(image.getHeight(), fullHeight)));
+
+ byte [] bytes = new byte[image.getWidth() * image.getHeight() * 3];
+ int stride = image.getWidth();
+ for (int px = 0; px < stride; px++) {
+ for (int py = 0; py < image.getHeight(); py++) {
+ int rgb = image.getRGB(px, py);
+ bytes[(py * stride * 3) + (px * 3)] = (byte) ((rgb >>> 16) & 0xFF);
+ bytes[(py * stride * 3) + (px * 3) + 1] = (byte) ((rgb >>> 8) & 0xFF);
+ bytes[(py * stride * 3) + (px * 3) + 2] = (byte) ( rgb & 0xFF);
+ }
+ }
+ sb.append(base64.encodeToString(bytes));
+ sb.append("\007");
+
+ if (saveInCache) {
+ // This row is OK to save into the cache.
+ jexerCache.put(cells, sb.toString());
+ }
+
+ return (gotoXY(x, y) + sb.toString());
+ }
+
+ /**
+ * Get the Jexer images support flag.
+ *
+ * @return true if this terminal is emitting Jexer images
+ */
+ public boolean hasJexerImages() {
+ return jexerImages;
+ }
+
+ // ------------------------------------------------------------------------
+ // End Jexer image output support -----------------------------------------
+ // ------------------------------------------------------------------------
+
+ /**
+ * Setup system colors to match DOS color palette.
+ */
+ private void setDOSColors() {
+ MYBLACK = new java.awt.Color(0x00, 0x00, 0x00);
+ MYRED = new java.awt.Color(0xa8, 0x00, 0x00);
+ MYGREEN = new java.awt.Color(0x00, 0xa8, 0x00);
+ MYYELLOW = new java.awt.Color(0xa8, 0x54, 0x00);
+ MYBLUE = new java.awt.Color(0x00, 0x00, 0xa8);
+ MYMAGENTA = new java.awt.Color(0xa8, 0x00, 0xa8);
+ MYCYAN = new java.awt.Color(0x00, 0xa8, 0xa8);
+ MYWHITE = new java.awt.Color(0xa8, 0xa8, 0xa8);
+ MYBOLD_BLACK = new java.awt.Color(0x54, 0x54, 0x54);
+ MYBOLD_RED = new java.awt.Color(0xfc, 0x54, 0x54);
+ MYBOLD_GREEN = new java.awt.Color(0x54, 0xfc, 0x54);
+ MYBOLD_YELLOW = new java.awt.Color(0xfc, 0xfc, 0x54);
+ MYBOLD_BLUE = new java.awt.Color(0x54, 0x54, 0xfc);
+ MYBOLD_MAGENTA = new java.awt.Color(0xfc, 0x54, 0xfc);
+ MYBOLD_CYAN = new java.awt.Color(0x54, 0xfc, 0xfc);
+ MYBOLD_WHITE = new java.awt.Color(0xfc, 0xfc, 0xfc);
+ }
+
+ /**
+ * Setup ECMA48 colors to match those provided in system properties.
+ */
+ private void setCustomSystemColors() {
+ setDOSColors();
+
+ MYBLACK = getCustomColor("jexer.ECMA48.color0", MYBLACK);
+ MYRED = getCustomColor("jexer.ECMA48.color1", MYRED);
+ MYGREEN = getCustomColor("jexer.ECMA48.color2", MYGREEN);
+ MYYELLOW = getCustomColor("jexer.ECMA48.color3", MYYELLOW);
+ MYBLUE = getCustomColor("jexer.ECMA48.color4", MYBLUE);
+ MYMAGENTA = getCustomColor("jexer.ECMA48.color5", MYMAGENTA);
+ MYCYAN = getCustomColor("jexer.ECMA48.color6", MYCYAN);
+ MYWHITE = getCustomColor("jexer.ECMA48.color7", MYWHITE);
+ MYBOLD_BLACK = getCustomColor("jexer.ECMA48.color8", MYBOLD_BLACK);
+ MYBOLD_RED = getCustomColor("jexer.ECMA48.color9", MYBOLD_RED);
+ MYBOLD_GREEN = getCustomColor("jexer.ECMA48.color10", MYBOLD_GREEN);
+ MYBOLD_YELLOW = getCustomColor("jexer.ECMA48.color11", MYBOLD_YELLOW);
+ MYBOLD_BLUE = getCustomColor("jexer.ECMA48.color12", MYBOLD_BLUE);
+ MYBOLD_MAGENTA = getCustomColor("jexer.ECMA48.color13", MYBOLD_MAGENTA);
+ MYBOLD_CYAN = getCustomColor("jexer.ECMA48.color14", MYBOLD_CYAN);
+ MYBOLD_WHITE = getCustomColor("jexer.ECMA48.color15", MYBOLD_WHITE);
+ }
+
+ /**
+ * Setup one system color to match the RGB value provided in system
+ * properties.
+ *
+ * @param key the system property key
+ * @param defaultColor the default color to return if key is not set, or
+ * incorrect
+ * @return a color from the RGB string, or defaultColor
+ */
+ private java.awt.Color getCustomColor(final String key,
+ final java.awt.Color defaultColor) {
+
+ String rgb = System.getProperty(key);
+ if (rgb == null) {
+ return defaultColor;
+ }
+ if (rgb.startsWith("#")) {
+ rgb = rgb.substring(1);
+ }
+ int rgbInt = 0;
+ try {
+ rgbInt = Integer.parseInt(rgb, 16);
+ } catch (NumberFormatException e) {
+ return defaultColor;
+ }
+ java.awt.Color color = new java.awt.Color((rgbInt & 0xFF0000) >>> 16,
+ (rgbInt & 0x00FF00) >>> 8,
+ (rgbInt & 0x0000FF));
+
+ return color;
+ }
+
+ /**
+ * Create a T.416 RGB parameter sequence for a custom system color.
+ *
+ * @param color one of the MYBLACK, MYBOLD_BLUE, etc. colors
+ * @return the color portion of the string to emit to an ANSI /
+ * ECMA-style terminal
+ */
+ private String systemColorRGB(final java.awt.Color color) {
+ return String.format("%d;%d;%d", color.getRed(), color.getGreen(),
+ color.getBlue());
+ }
+
/**
* Create a SGR parameter sequence for a single color change.
*
// Bold implies foreground only
sb.append("38;2;");
if (color.equals(Color.BLACK)) {
- sb.append("84;84;84");
+ sb.append(systemColorRGB(MYBOLD_BLACK));
} else if (color.equals(Color.RED)) {
- sb.append("252;84;84");
+ sb.append(systemColorRGB(MYBOLD_RED));
} else if (color.equals(Color.GREEN)) {
- sb.append("84;252;84");
+ sb.append(systemColorRGB(MYBOLD_GREEN));
} else if (color.equals(Color.YELLOW)) {
- sb.append("252;252;84");
+ sb.append(systemColorRGB(MYBOLD_YELLOW));
} else if (color.equals(Color.BLUE)) {
- sb.append("84;84;252");
+ sb.append(systemColorRGB(MYBOLD_BLUE));
} else if (color.equals(Color.MAGENTA)) {
- sb.append("252;84;252");
+ sb.append(systemColorRGB(MYBOLD_MAGENTA));
} else if (color.equals(Color.CYAN)) {
- sb.append("84;252;252");
+ sb.append(systemColorRGB(MYBOLD_CYAN));
} else if (color.equals(Color.WHITE)) {
- sb.append("252;252;252");
+ sb.append(systemColorRGB(MYBOLD_WHITE));
}
} else {
if (foreground) {
sb.append("48;2;");
}
if (color.equals(Color.BLACK)) {
- sb.append("0;0;0");
+ sb.append(systemColorRGB(MYBLACK));
} else if (color.equals(Color.RED)) {
- sb.append("168;0;0");
+ sb.append(systemColorRGB(MYRED));
} else if (color.equals(Color.GREEN)) {
- sb.append("0;168;0");
+ sb.append(systemColorRGB(MYGREEN));
} else if (color.equals(Color.YELLOW)) {
- sb.append("168;84;0");
+ sb.append(systemColorRGB(MYYELLOW));
} else if (color.equals(Color.BLUE)) {
- sb.append("0;0;168");
+ sb.append(systemColorRGB(MYBLUE));
} else if (color.equals(Color.MAGENTA)) {
- sb.append("168;0;168");
+ sb.append(systemColorRGB(MYMAGENTA));
} else if (color.equals(Color.CYAN)) {
- sb.append("0;168;168");
+ sb.append(systemColorRGB(MYCYAN));
} else if (color.equals(Color.WHITE)) {
- sb.append("168;168;168");
+ sb.append(systemColorRGB(MYWHITE));
}
}
sb.append("m");
}
/**
- * Create a SGR parameter sequence to reset to defaults.
+ * Create a SGR parameter sequence to reset to VT100 defaults.
*
* @return the string to emit to an ANSI / ECMA-style terminal,
* e.g. "\033[0m"
return normal(true) + rgbColor(false, Color.WHITE, Color.BLACK);
}
+ /**
+ * Create a SGR parameter sequence to reset to ECMA-48 default
+ * foreground/background.
+ *
+ * @return the string to emit to an ANSI / ECMA-style terminal,
+ * e.g. "\033[0m"
+ */
+ private String defaultColor() {
+ /*
+ * VT100 normal.
+ * Normal (neither bold nor faint).
+ * Not italicized.
+ * Not underlined.
+ * Steady (not blinking).
+ * Positive (not inverse).
+ * Visible (not hidden).
+ * Not crossed-out.
+ * Default foreground color.
+ * Default background color.
+ */
+ return "\033[0;22;23;24;25;27;28;29;39;49m";
+ }
+
/**
* Create a SGR parameter sequence to reset to defaults.
*