import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
+import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
import java.io.CharArrayWriter;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import javax.imageio.ImageIO;
import jexer.TKeypress;
import jexer.backend.GlyphMaker;
/**
* The type of emulator to be.
*/
- private DeviceType type = DeviceType.VT102;
+ private final DeviceType type;
/**
* The scrollback buffer characters + attributes.
* Physical display width. We start at 80x24, but the user can resize us
* bigger/smaller.
*/
- private int width;
+ private int width = 80;
/**
* Physical display height. We start at 80x24, but the user can resize
* us bigger/smaller.
*/
- private int height;
+ private int height = 24;
/**
* Top margin of the scrolling region.
*/
- private int scrollRegionTop;
+ private int scrollRegionTop = 0;
/**
* Bottom margin of the scrolling region.
*/
- private int scrollRegionBottom;
+ private int scrollRegionBottom = height - 1;
/**
* Right margin column number. This can be selected by the remote side
* to be 80/132 (rightMargin values 79/131), or it can be (width - 1).
*/
- private int rightMargin;
+ private int rightMargin = 79;
/**
* Last character printed.
* 132), but the line does NOT wrap until another character is written to
* column 1 of the next line, after which the cursor moves to column 2.
*/
- private boolean wrapLineFlag;
+ private boolean wrapLineFlag = false;
/**
* VT220 single shift flag.
/**
* Non-csi collect buffer.
*/
- private StringBuilder collectBuffer;
+ private StringBuilder collectBuffer = new StringBuilder(128);
/**
* When true, use the G1 character set.
/**
* Sixel collection buffer.
*/
- private StringBuilder sixelParseBuffer;
+ private StringBuilder sixelParseBuffer = new StringBuilder(2048);
+
+ /**
+ * Sixel shared palette.
+ */
+ private HashMap<Integer, java.awt.Color> sixelPalette;
/**
* The width of a character cell in pixels.
this.inputStream = new TimeoutInputStream(inputStream, 2000);
}
if (type == DeviceType.XTERM) {
- this.input = new InputStreamReader(this.inputStream, "UTF-8");
+ this.input = new InputStreamReader(new BufferedInputStream(
+ this.inputStream, 1024 * 128), "UTF-8");
this.output = new OutputStreamWriter(new
BufferedOutputStream(outputStream), "UTF-8");
this.outputStream = null;
for (int i = 0; i < height; i++) {
display.add(new DisplayLine(currentState.attr));
}
+ assert (currentState.cursorY < height);
+ assert (currentState.cursorX < width);
// Spin up the input reader
readerThread = new Thread(this);
int ch = Character.codePointAt(readBufferUTF8,
i);
i += Character.charCount(ch);
- consume(ch);
+
+ // Special case for VT10x: 7-bit characters
+ // only.
+ if ((type == DeviceType.VT100)
+ || (type == DeviceType.VT102)
+ ) {
+ consume(ch & 0x7F);
+ } else {
+ consume(ch);
+ }
}
} else {
for (int i = 0; i < rc; i++) {
- consume(readBuffer[i]);
+ // Special case for VT10x: 7-bit characters
+ // only.
+ if ((type == DeviceType.VT100)
+ || (type == DeviceType.VT102)
+ ) {
+ consume(readBuffer[i] & 0x7F);
+ } else {
+ consume(readBuffer[i]);
+ }
}
}
}
case VT220:
case XTERM:
- // "I am a VT220" - 7 bit version
+ // "I am a VT220" - 7 bit version, with sixel and Jexer image
+ // support.
if (!s8c1t) {
- return "\033[?62;1;6;9;4c";
+ return "\033[?62;1;6;9;4;22;444c";
}
- // "I am a VT220" - 8 bit version
- return "\u009b?62;1;6;9;4c";
+ // "I am a VT220" - 8 bit version, with sixel and Jexer image
+ // support.
+ return "\u009b?62;1;6;9;4;22;444c";
default:
throw new IllegalArgumentException("Invalid device type: " + type);
}
int delta = height - this.height;
this.height = height;
scrollRegionBottom += delta;
- if (scrollRegionBottom < 0) {
- scrollRegionBottom = height;
+ if ((scrollRegionBottom < 0) || (scrollRegionTop > height - 1)) {
+ scrollRegionBottom = height - 1;
}
if (scrollRegionTop >= scrollRegionBottom) {
scrollRegionTop = 0;
*/
private void toGround() {
csiParams.clear();
- collectBuffer = new StringBuilder(8);
+ collectBuffer.setLength(0);
scanState = ScanState.GROUND;
}
currentState = new SaveableState();
savedState = new SaveableState();
scanState = ScanState.GROUND;
- width = 80;
- height = 24;
+ if (displayListener != null) {
+ width = displayListener.getDisplayWidth();
+ height = displayListener.getDisplayHeight();
+ } else {
+ width = 80;
+ height = 24;
+ }
scrollRegionTop = 0;
scrollRegionBottom = height - 1;
rightMargin = width - 1;
arrowKeyMode = ArrowKeyMode.ANSI;
keypadMode = KeypadMode.Numeric;
wrapLineFlag = false;
- if (displayListener != null) {
- width = displayListener.getDisplayWidth();
- height = displayListener.getDisplayHeight();
- rightMargin = width - 1;
- }
// Flags
shiftOut = false;
* Handle a linefeed.
*/
private void linefeed() {
-
if (currentState.cursorY < scrollRegionBottom) {
// Increment screen y
currentState.cursorY++;
if (mouseEncoding == MouseEncoding.SGR) {
sb.append((char) 0x1B);
sb.append("[<");
+ int buttons = 0;
if (mouse.isMouse1()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append("32;");
+ buttons = 32;
} else {
- sb.append("0;");
+ buttons = 0;
}
} else if (mouse.isMouse2()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append("33;");
+ buttons = 33;
} else {
- sb.append("1;");
+ buttons = 1;
}
} else if (mouse.isMouse3()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append("34;");
+ buttons = 34;
} else {
- sb.append("2;");
+ buttons = 2;
}
} else if (mouse.isMouseWheelUp()) {
- sb.append("64;");
+ buttons = 64;
} else if (mouse.isMouseWheelDown()) {
- sb.append("65;");
+ buttons = 65;
} else {
// This is motion with no buttons down.
- sb.append("35;");
+ buttons = 35;
+ }
+ if (mouse.isAlt()) {
+ buttons |= 0x08;
+ }
+ if (mouse.isCtrl()) {
+ buttons |= 0x10;
+ }
+ if (mouse.isShift()) {
+ buttons |= 0x04;
}
- sb.append(String.format("%d;%d", mouse.getX() + 1,
+ sb.append(String.format("%d;%d;%d", buttons, mouse.getX() + 1,
mouse.getY() + 1));
if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
sb.append((char) 0x1B);
sb.append('[');
sb.append('M');
+ int buttons = 0;
if (mouse.getType() == TMouseEvent.Type.MOUSE_UP) {
- sb.append((char) (0x03 + 32));
+ buttons = 0x03 + 32;
} else if (mouse.isMouse1()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append((char) (0x00 + 32 + 32));
+ buttons = 0x00 + 32 + 32;
} else {
- sb.append((char) (0x00 + 32));
+ buttons = 0x00 + 32;
}
} else if (mouse.isMouse2()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append((char) (0x01 + 32 + 32));
+ buttons = 0x01 + 32 + 32;
} else {
- sb.append((char) (0x01 + 32));
+ buttons = 0x01 + 32;
}
} else if (mouse.isMouse3()) {
if (mouse.getType() == TMouseEvent.Type.MOUSE_MOTION) {
- sb.append((char) (0x02 + 32 + 32));
+ buttons = 0x02 + 32 + 32;
} else {
- sb.append((char) (0x02 + 32));
+ buttons = 0x02 + 32;
}
} else if (mouse.isMouseWheelUp()) {
- sb.append((char) (0x04 + 64));
+ buttons = 0x04 + 64;
} else if (mouse.isMouseWheelDown()) {
- sb.append((char) (0x05 + 64));
+ buttons = 0x05 + 64;
} else {
// This is motion with no buttons down.
- sb.append((char) (0x03 + 32));
+ buttons = 0x03 + 32;
+ }
+ if (mouse.isAlt()) {
+ buttons |= 0x08;
+ }
+ if (mouse.isCtrl()) {
+ buttons |= 0x10;
+ }
+ if (mouse.isShift()) {
+ buttons |= 0x04;
}
+ sb.append((char) (buttons & 0xFF));
sb.append((char) (mouse.getX() + 33));
sb.append((char) (mouse.getY() + 33));
}
// Local echo for everything else
printCharacter(keypress.getChar());
}
+ if (displayListener != null) {
+ displayListener.displayChanged();
+ }
}
if ((newLineMode == true) && (keypress.equals(kbEnter))) {
*/
private void setToggle(final boolean value) {
boolean decPrivateModeFlag = false;
+
for (int i = 0; i < collectBuffer.length(); i++) {
if (collectBuffer.charAt(i) == '?') {
decPrivateModeFlag = true;
break;
+ case 80:
+ if (type == DeviceType.XTERM) {
+ if (decPrivateModeFlag == true) {
+ if (value == true) {
+ // Enable sixel scrolling (default).
+ // Not supported
+ } else {
+ // Disable sixel scrolling.
+ // Not supported
+ }
+ }
+ }
+
+ break;
+
case 1000:
if ((type == DeviceType.XTERM)
&& (decPrivateModeFlag == true)
}
break;
+ case 1070:
+ if (type == DeviceType.XTERM) {
+ if (decPrivateModeFlag == true) {
+ if (value == true) {
+ // Use private color registers for each sixel
+ // graphic (default).
+ sixelPalette = null;
+ } else {
+ // Use shared color registers for each sixel
+ // graphic.
+ sixelPalette = new HashMap<Integer, java.awt.Color>();
+ }
+ }
+ }
+ break;
+
default:
break;
case 8:
// Invisible
- // TODO
+ // Not supported
break;
case 90:
// DECSTBM
int top = getCsiParam(0, 1, 1, height) - 1;
int bottom = getCsiParam(1, height, 1, height) - 1;
+ if (bottom > height - 1) {
+ bottom = height - 1;
+ }
if (top > bottom) {
top = bottom;
private void oscPut(final char xtermChar) {
// System.err.println("oscPut: " + xtermChar);
+ boolean oscEnd = false;
+
+ if (xtermChar == 0x07) {
+ oscEnd = true;
+ }
+ if ((xtermChar == '\\')
+ && (collectBuffer.charAt(collectBuffer.length() - 1) == '\033')
+ ) {
+ oscEnd = true;
+ }
+
// Collect first
collectBuffer.append(xtermChar);
// Xterm cases...
- if ((xtermChar == 0x07)
- || (collectBuffer.toString().endsWith("\033\\"))
- ) {
+ if (oscEnd) {
String args = null;
if (xtermChar == 0x07) {
args = collectBuffer.substring(0, collectBuffer.length() - 1);
}
}
}
+
+ if (p[0].equals("10")) {
+ if (p[1].equals("?")) {
+ // Respond with foreground color.
+ java.awt.Color color = jexer.backend.SwingTerminal.attrToForegroundColor(currentState.attr);
+
+ writeRemote(String.format(
+ "\033]10;rgb:%04x/%04x/%04x\033\\",
+ color.getRed() << 8,
+ color.getGreen() << 8,
+ color.getBlue() << 8));
+ }
+ }
+
+ if (p[0].equals("11")) {
+ if (p[1].equals("?")) {
+ // Respond with background color.
+ java.awt.Color color = jexer.backend.SwingTerminal.attrToBackgroundColor(currentState.attr);
+
+ writeRemote(String.format(
+ "\033]11;rgb:%04x/%04x/%04x\033\\",
+ color.getRed() << 8,
+ color.getGreen() << 8,
+ color.getBlue() << 8));
+ }
+ }
+
+ if (p[0].equals("444")) {
+ if (p[1].equals("0") && (p.length == 6)) {
+ // Jexer image - RGB
+ parseJexerImageRGB(p[2], p[3], p[4], p[5]);
+ } else if (p[1].equals("1") && (p.length == 4)) {
+ // Jexer image - PNG
+ parseJexerImageFile(1, p[2], p[3]);
+ } else if (p[1].equals("2") && (p.length == 4)) {
+ // Jexer image - JPG
+ parseJexerImageFile(2, p[2], p[3]);
+ }
+ }
}
// Go to SCAN_GROUND state
private void pmPut(final char pmChar) {
// System.err.println("pmPut: " + pmChar);
+ boolean pmEnd = false;
+
+ if ((pmChar == '\\')
+ && (collectBuffer.charAt(collectBuffer.length() - 1) == '\033')
+ ) {
+ pmEnd = true;
+ }
+
// Collect first
collectBuffer.append(pmChar);
// Xterm cases...
- if (collectBuffer.toString().endsWith("\033\\")) {
+ if (pmEnd) {
String arg = null;
arg = collectBuffer.substring(0, collectBuffer.length() - 2);
}
}
+ /**
+ * Respond to xterm sixel query.
+ */
+ private void xtermSixelQuery() {
+ int item = getCsiParam(0, 0);
+ int action = getCsiParam(1, 0);
+ int value = getCsiParam(2, 0);
+
+ switch (item) {
+ case 1:
+ if (action == 1) {
+ // Report number of color registers.
+ writeRemote(String.format("\033[?%d;%d;%dS", item, 0, 1024));
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ // We will not support this option.
+ writeRemote(String.format("\033[?%d;%dS", item, action));
+ }
+
/**
* Run this input character through the ECMA48 state machine.
*
* @param ch character from the remote side
*/
- private void consume(int ch) {
+ private void consume(final int ch) {
// DEBUG
// System.err.printf("%c STATE = %s\n", ch, scanState);
- // Special case for VT10x: 7-bit characters only
- if ((type == DeviceType.VT100) || (type == DeviceType.VT102)) {
- ch = (ch & 0x7F);
- }
-
// Special "anywhere" states
// 18, 1A --> execute, then switch to SCAN_GROUND
case 'S':
// Scroll up X lines (default 1)
if (type == DeviceType.XTERM) {
- su();
+ boolean xtermPrivateModeFlag = false;
+ for (int i = 0; i < collectBuffer.length(); i++) {
+ if (collectBuffer.charAt(i) == '?') {
+ xtermPrivateModeFlag = true;
+ break;
+ }
+ }
+ if (xtermPrivateModeFlag) {
+ xtermSixelQuery();
+ } else {
+ su();
+ }
}
break;
case 'T':
case 'S':
// Scroll up X lines (default 1)
if (type == DeviceType.XTERM) {
- su();
+ boolean xtermPrivateModeFlag = false;
+ for (int i = 0; i < collectBuffer.length(); i++) {
+ if (collectBuffer.charAt(i) == '?') {
+ xtermPrivateModeFlag = true;
+ break;
+ }
+ }
+ if (xtermPrivateModeFlag) {
+ xtermSixelQuery();
+ } else {
+ su();
+ }
}
break;
case 'T':
// 0x71 goes to DCS_SIXEL
if (ch == 0x71) {
- sixelParseBuffer = new StringBuilder();
+ sixelParseBuffer.setLength(0);
scanState = ScanState.DCS_SIXEL;
} else if ((ch >= 0x40) && (ch <= 0x7E)) {
// 0x40-7E goes to DCS_PASSTHROUGH
// 0x71 goes to DCS_SIXEL
if (ch == 0x71) {
- sixelParseBuffer = new StringBuilder();
+ sixelParseBuffer.setLength(0);
scanState = ScanState.DCS_SIXEL;
} else if ((ch >= 0x40) && (ch <= 0x7E)) {
// 0x40-7E goes to DCS_PASSTHROUGH
}
// 00-17, 19, 1C-1F, 20-7E --> put
- // TODO
if (ch <= 0x17) {
+ // We ignore all DCS except sixel.
return;
}
if (ch == 0x19) {
+ // We ignore all DCS except sixel.
return;
}
if ((ch >= 0x1C) && (ch <= 0x1F)) {
+ // We ignore all DCS except sixel.
return;
}
if ((ch >= 0x20) && (ch <= 0x7E)) {
+ // We ignore all DCS except sixel.
return;
}
if (ch == 0x9C) {
parseSixel();
toGround();
+ return;
}
// 0x1B 0x5C goes to GROUND
if (ch == 0x1B) {
collect((char) ch);
+ return;
}
if (ch == 0x5C) {
if ((collectBuffer.length() > 0)
) {
parseSixel();
toGround();
+ return;
}
}
// 00-17, 19, 1C-1F, 20-7E --> put
- if (ch <= 0x17) {
- sixelParseBuffer.append((char) ch);
- return;
- }
- if (ch == 0x19) {
- sixelParseBuffer.append((char) ch);
- return;
- }
- if ((ch >= 0x1C) && (ch <= 0x1F)) {
+ if ((ch <= 0x17)
+ || (ch == 0x19)
+ || ((ch >= 0x1C) && (ch <= 0x1F))
+ || ((ch >= 0x20) && (ch <= 0x7E))
+ ) {
sixelParseBuffer.append((char) ch);
- return;
- }
- if ((ch >= 0x20) && (ch <= 0x7E)) {
- sixelParseBuffer.append((char) ch);
- return;
}
// 7F --> ignore
-
return;
case SOSPMAPC_STRING:
return mouseProtocol;
}
- // ------------------------------------------------------------------------
- // Sixel support ----------------------------------------------------------
- // ------------------------------------------------------------------------
+ /**
+ * Draw the left and right cells of a two-cell-wide (full-width) glyph.
+ *
+ * @param leftX the x position to draw the left half to
+ * @param leftY the y position to draw the left half to
+ * @param rightX the x position to draw the right half to
+ * @param rightY the y position to draw the right half to
+ * @param ch the character to draw
+ */
+ private void drawHalves(final int leftX, final int leftY,
+ final int rightX, final int rightY, final int ch) {
+
+ // System.err.println("drawHalves(): " + Integer.toHexString(ch));
+
+ if (lastTextHeight != textHeight) {
+ glyphMaker = GlyphMaker.getInstance(textHeight);
+ lastTextHeight = textHeight;
+ }
+
+ Cell cell = new Cell(ch, currentState.attr);
+ BufferedImage image = glyphMaker.getImage(cell, textWidth * 2,
+ textHeight);
+ BufferedImage leftImage = image.getSubimage(0, 0, textWidth,
+ textHeight);
+ BufferedImage rightImage = image.getSubimage(textWidth, 0, textWidth,
+ textHeight);
+
+ Cell left = new Cell(cell);
+ left.setImage(leftImage);
+ left.setWidth(Cell.Width.LEFT);
+ display.get(leftY).replace(leftX, left);
+
+ Cell right = new Cell(cell);
+ right.setImage(rightImage);
+ right.setWidth(Cell.Width.RIGHT);
+ display.get(rightY).replace(rightX, right);
+ }
/**
* Set the width of a character cell in pixels.
/*
System.err.println("parseSixel(): '" + sixelParseBuffer.toString()
+ "'");
- */
+ */
- Sixel sixel = new Sixel(sixelParseBuffer.toString());
+ Sixel sixel = new Sixel(sixelParseBuffer.toString(), sixelPalette);
BufferedImage image = sixel.getImage();
// System.err.println("parseSixel(): image " + image);
// Sixel data was malformed in some way, bail out.
return;
}
+ if ((image.getWidth() < 1)
+ || (image.getWidth() > 10000)
+ || (image.getHeight() < 1)
+ || (image.getHeight() > 10000)
+ ) {
+ return;
+ }
+
+ imageToCells(image, true);
+ }
+
+ /**
+ * Parse a "Jexer" RGB image string into a bitmap image, and overlay that
+ * image onto the text cells.
+ *
+ * @param pw width token
+ * @param ph height token
+ * @param ps scroll token
+ * @param data pixel data
+ */
+ private void parseJexerImageRGB(final String pw, final String ph,
+ final String ps, final String data) {
+
+ int imageWidth = 0;
+ int imageHeight = 0;
+ boolean scroll = false;
+ try {
+ imageWidth = Integer.parseInt(pw);
+ imageHeight = Integer.parseInt(ph);
+ } catch (NumberFormatException e) {
+ // SQUASH
+ return;
+ }
+ if ((imageWidth < 1)
+ || (imageWidth > 10000)
+ || (imageHeight < 1)
+ || (imageHeight > 10000)
+ ) {
+ return;
+ }
+ if (ps.equals("1")) {
+ scroll = true;
+ } else if (ps.equals("0")) {
+ scroll = false;
+ } else {
+ return;
+ }
+
+ byte [] bytes = StringUtils.fromBase64(data.getBytes());
+ if (bytes.length != (imageWidth * imageHeight * 3)) {
+ return;
+ }
+
+ BufferedImage image = new BufferedImage(imageWidth, imageHeight,
+ BufferedImage.TYPE_INT_ARGB);
+
+ for (int x = 0; x < imageWidth; x++) {
+ for (int y = 0; y < imageHeight; y++) {
+ int red = bytes[(y * imageWidth * 3) + (x * 3) ];
+ if (red < 0) {
+ red += 256;
+ }
+ int green = bytes[(y * imageWidth * 3) + (x * 3) + 1];
+ if (green < 0) {
+ green += 256;
+ }
+ int blue = bytes[(y * imageWidth * 3) + (x * 3) + 2];
+ if (blue < 0) {
+ blue += 256;
+ }
+ int rgb = 0xFF000000 | (red << 16) | (green << 8) | blue;
+ image.setRGB(x, y, rgb);
+ }
+ }
+
+ imageToCells(image, scroll);
+ }
+
+ /**
+ * Parse a "Jexer" PNG or JPG image string into a bitmap image, and
+ * overlay that image onto the text cells.
+ *
+ * @param type 1 for PNG, 2 for JPG
+ * @param ps scroll token
+ * @param data pixel data
+ */
+ private void parseJexerImageFile(final int type, final String ps,
+ final String data) {
+
+ int imageWidth = 0;
+ int imageHeight = 0;
+ boolean scroll = false;
+ BufferedImage image = null;
+ try {
+ byte [] bytes = StringUtils.fromBase64(data.getBytes());
+
+ switch (type) {
+ case 1:
+ if ((bytes[0] != (byte) 0x89)
+ || (bytes[1] != 'P')
+ || (bytes[2] != 'N')
+ || (bytes[3] != 'G')
+ || (bytes[4] != (byte) 0x0D)
+ || (bytes[5] != (byte) 0x0A)
+ || (bytes[6] != (byte) 0x1A)
+ || (bytes[7] != (byte) 0x0A)
+ ) {
+ // File does not have PNG header, bail out.
+ return;
+ }
+ break;
+
+ case 2:
+ if ((bytes[0] != (byte) 0XFF)
+ || (bytes[1] != (byte) 0xD8)
+ || (bytes[2] != (byte) 0xFF)
+ ) {
+ // File does not have JPG header, bail out.
+ return;
+ }
+ break;
+
+ default:
+ // Unsupported type, bail out.
+ return;
+ }
+
+ image = ImageIO.read(new ByteArrayInputStream(bytes));
+ } catch (IOException e) {
+ // SQUASH
+ return;
+ }
+ assert (image != null);
+ imageWidth = image.getWidth();
+ imageHeight = image.getHeight();
+ if ((imageWidth < 1)
+ || (imageWidth > 10000)
+ || (imageHeight < 1)
+ || (imageHeight > 10000)
+ ) {
+ return;
+ }
+ if (ps.equals("1")) {
+ scroll = true;
+ } else if (ps.equals("0")) {
+ scroll = false;
+ } else {
+ return;
+ }
+
+ imageToCells(image, scroll);
+ }
+
+ /**
+ * Break up an image into the cells at the current cursor.
+ *
+ * @param image the image to display
+ * @param scroll if true, scroll the image and move the cursor
+ */
+ private void imageToCells(final BufferedImage image, final boolean scroll) {
+ assert (image != null);
/*
* Procedure:
}
int x0 = currentState.cursorX;
+ int y0 = currentState.cursorY;
for (int y = 0; y < cellRows; y++) {
for (int x = 0; x < cellColumns; x++) {
- printCharacter(' ');
- cursorLeft(1, false);
- if ((x == cellColumns - 1) || (y == cellRows - 1)) {
- // TODO: render text of current cell first, then image
- // over it. For now, just copy the cell.
- DisplayLine line = display.get(currentState.cursorY);
- line.replace(currentState.cursorX, cells[x][y]);
- } else {
- // Copy the image cell into the display.
- DisplayLine line = display.get(currentState.cursorY);
- line.replace(currentState.cursorX, cells[x][y]);
+ assert (currentState.cursorX <= rightMargin);
+
+ // A real sixel terminal would render the text of the current
+ // cell first, then image over it (accounting for blank
+ // pixels). We do not support that. A cell is either text,
+ // or image, but not a mix of image-over-text.
+ DisplayLine line = display.get(currentState.cursorY);
+ line.replace(currentState.cursorX, cells[x][y]);
+
+ // If at the end of the visible screen, stop.
+ if (currentState.cursorX == rightMargin) {
+ break;
}
- cursorRight(1, false);
+ // Room for more image on the visible screen.
+ currentState.cursorX++;
}
- linefeed();
+ if (currentState.cursorY < scrollRegionBottom - 1) {
+ // Not at the bottom, down a line.
+ linefeed();
+ } else if (scroll == true) {
+ // At the bottom, scroll as needed.
+ linefeed();
+ } else {
+ // At the bottom, no more scrolling, done.
+ break;
+ }
+
cursorPosition(currentState.cursorY, x0);
}
- }
-
- /**
- * Draw the left and right cells of a two-cell-wide (full-width) glyph.
- *
- * @param leftX the x position to draw the left half to
- * @param leftY the y position to draw the left half to
- * @param rightX the x position to draw the right half to
- * @param rightY the y position to draw the right half to
- * @param ch the character to draw
- */
- private void drawHalves(final int leftX, final int leftY,
- final int rightX, final int rightY, final int ch) {
-
- // System.err.println("drawHalves(): " + Integer.toHexString(ch));
-
- if (lastTextHeight != textHeight) {
- glyphMaker = GlyphMaker.getInstance(textHeight);
- lastTextHeight = textHeight;
+ if (scroll == false) {
+ cursorPosition(y0, x0);
}
- Cell cell = new Cell(ch, currentState.attr);
- BufferedImage image = glyphMaker.getImage(cell, textWidth * 2,
- textHeight);
- BufferedImage leftImage = image.getSubimage(0, 0, textWidth,
- textHeight);
- BufferedImage rightImage = image.getSubimage(textWidth, 0, textWidth,
- textHeight);
-
- Cell left = new Cell(cell);
- left.setImage(leftImage);
- left.setWidth(Cell.Width.LEFT);
- display.get(leftY).replace(leftX, left);
-
- Cell right = new Cell(cell);
- right.setImage(rightImage);
- right.setWidth(Cell.Width.RIGHT);
- display.get(rightY).replace(rightX, right);
}
}