From bfa37f3b2ef87d39c15fad7d565c00cbabd92acf Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sun, 11 Aug 2019 15:10:07 -0500 Subject: [PATCH 01/16] #35 fix demo --- src/jexer/TApplication.java | 12 ++++++------ src/jexer/backend/GlyphMaker.java | 13 +++++++++++++ src/jexer/backend/LogicalScreen.java | 21 ++++++++++++--------- src/jexer/backend/SwingTerminal.java | 4 ++-- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index b21c066..ec93629 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -1723,12 +1723,12 @@ public class TApplication implements Runnable { } } } - assert (cell.getWidth() == Cell.Width.RIGHT); - - if (x > 0) { - Cell leftHalf = getScreen().getCharXY(x - 1, y); - if (leftHalf.getWidth() == Cell.Width.LEFT) { - invertCell(x - 1, y, true); + if (cell.getWidth() == Cell.Width.RIGHT) { + if (x > 0) { + Cell leftHalf = getScreen().getCharXY(x - 1, y); + if (leftHalf.getWidth() == Cell.Width.LEFT) { + invertCell(x - 1, y, true); + } } } } diff --git a/src/jexer/backend/GlyphMaker.java b/src/jexer/backend/GlyphMaker.java index 08cbee8..91bda6d 100644 --- a/src/jexer/backend/GlyphMaker.java +++ b/src/jexer/backend/GlyphMaker.java @@ -237,6 +237,11 @@ class GlyphMakerFont { glyphCache.put(key, image); } + /* + System.err.println("cellWidth " + cellWidth + + " cellHeight " + cellHeight + " image " + image); + */ + return image; } @@ -265,6 +270,13 @@ class GlyphMakerFont { textHeight = fontTextHeight + textAdjustHeight; textWidth = fontTextWidth + textAdjustWidth; + /* + System.err.println("font " + font); + System.err.println("fontTextWidth " + fontTextWidth); + System.err.println("fontTextHeight " + fontTextHeight); + System.err.println("textWidth " + textWidth); + System.err.println("textHeight " + textHeight); + */ gotFontDimensions = true; } @@ -345,6 +357,7 @@ public class GlyphMaker { * @param fontSize the size of these fonts in pixels */ private GlyphMaker(final int fontSize) { + assert (fontSize > 3); makerMono = new GlyphMakerFont(MONO, fontSize); // makerCJKhk = new GlyphMakerFont(CJKhk, fontSize); // makerCJKkr = new GlyphMakerFont(CJKkr, fontSize); diff --git a/src/jexer/backend/LogicalScreen.java b/src/jexer/backend/LogicalScreen.java index e8d2662..558fdc4 100644 --- a/src/jexer/backend/LogicalScreen.java +++ b/src/jexer/backend/LogicalScreen.java @@ -967,16 +967,19 @@ public class LogicalScreen implements Screen { public final void putFullwidthCharXY(final int x, final int y, final Cell cell) { - if (lastTextHeight != getTextHeight()) { - glyphMaker = GlyphMaker.getInstance(getTextHeight()); - lastTextHeight = getTextHeight(); + int cellWidth = getTextWidth(); + int cellHeight = getTextHeight(); + + if (lastTextHeight != cellHeight) { + glyphMaker = GlyphMaker.getInstance(cellHeight); + lastTextHeight = cellHeight; } - BufferedImage image = glyphMaker.getImage(cell, getTextWidth() * 2, - getTextHeight()); - BufferedImage leftImage = image.getSubimage(0, 0, getTextWidth(), - getTextHeight()); - BufferedImage rightImage = image.getSubimage(getTextWidth(), 0, - getTextWidth(), getTextHeight()); + BufferedImage image = glyphMaker.getImage(cell, cellWidth * 2, + cellHeight); + BufferedImage leftImage = image.getSubimage(0, 0, cellWidth, + cellHeight); + BufferedImage rightImage = image.getSubimage(cellWidth, 0, cellWidth, + cellHeight); Cell left = new Cell(cell); left.setImage(leftImage); diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java index d32cf1e..134eced 100644 --- a/src/jexer/backend/SwingTerminal.java +++ b/src/jexer/backend/SwingTerminal.java @@ -182,12 +182,12 @@ public class SwingTerminal extends LogicalScreen /** * Width of a character cell in pixels. */ - private int textWidth = 1; + private int textWidth = 16; /** * Height of a character cell in pixels. */ - private int textHeight = 1; + private int textHeight = 20; /** * Width of a character cell in pixels, as reported by font. -- 2.27.0 From 218d18dbda14a7bf482d6c07bed66f16bcd6a6ba Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Sun, 11 Aug 2019 22:14:42 -0500 Subject: [PATCH 02/16] #35 emoji font wip --- .gitignore | 3 ++ src/jexer/TWidget.java | 10 ++--- src/jexer/backend/GlyphMaker.java | 63 +++++++++++++--------------- src/jexer/backend/LogicalScreen.java | 25 +++++------ src/jexer/backend/MultiScreen.java | 10 ++--- src/jexer/backend/Screen.java | 10 ++--- src/jexer/backend/SwingTerminal.java | 5 +-- src/jexer/bits/Cell.java | 10 ++--- src/jexer/bits/MnemonicString.java | 18 ++++---- src/jexer/bits/StringUtils.java | 9 ++-- 10 files changed, 82 insertions(+), 81 deletions(-) diff --git a/.gitignore b/.gitignore index 9e8f22f..30d9f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ misc/** pmd.bash pmd-results.html examples/*.sh + +# Fonts for testing +fonts/** diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 633b149..2b9d5cc 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1501,7 +1501,7 @@ public abstract class TWidget implements Comparable { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - protected final void putAll(final char ch, final CellAttributes attr) { + protected final void putAll(final int ch, final CellAttributes attr) { getScreen().putAll(ch, attr); } @@ -1524,7 +1524,7 @@ public abstract class TWidget implements Comparable { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - protected final void putCharXY(final int x, final int y, final char ch, + protected final void putCharXY(final int x, final int y, final int ch, final CellAttributes attr) { getScreen().putCharXY(x, y, ch, attr); @@ -1537,7 +1537,7 @@ public abstract class TWidget implements Comparable { * @param y row coordinate. 0 is the top-most row. * @param ch character to draw */ - protected final void putCharXY(final int x, final int y, final char ch) { + protected final void putCharXY(final int x, final int y, final int ch) { getScreen().putCharXY(x, y, ch); } @@ -1577,7 +1577,7 @@ public abstract class TWidget implements Comparable { * @param attr attributes to use (bold, foreColor, backColor) */ protected final void vLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { getScreen().vLineXY(x, y, n, ch, attr); } @@ -1592,7 +1592,7 @@ public abstract class TWidget implements Comparable { * @param attr attributes to use (bold, foreColor, backColor) */ protected final void hLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { getScreen().hLineXY(x, y, n, ch, attr); } diff --git a/src/jexer/backend/GlyphMaker.java b/src/jexer/backend/GlyphMaker.java index 91bda6d..2a6610b 100644 --- a/src/jexer/backend/GlyphMaker.java +++ b/src/jexer/backend/GlyphMaker.java @@ -185,6 +185,11 @@ class GlyphMakerFont { getFontDimensions(); } + if (DEBUG && !font.canDisplay(cell.getChar())) { + System.err.println("font " + font + " has no glyph for " + + String.format("0x%x", cell.getChar())); + } + BufferedImage image = null; if (cell.isBlink() && !blinkVisible) { image = glyphCacheBlink.get(cell); @@ -218,9 +223,8 @@ class GlyphMakerFont { || (cell.isBlink() && blinkVisible) ) { gr2.setColor(SwingTerminal.attrToForegroundColor(cellColor)); - char [] chars = new char[1]; - chars[0] = cell.getChar(); - gr2.drawChars(chars, 0, 1, textAdjustX, + char [] chars = Character.toChars(cell.getChar()); + gr2.drawChars(chars, 0, chars.length, textAdjustX, cellHeight - maxDescent + textAdjustY); if (cell.isUnderline()) { @@ -299,19 +303,14 @@ public class GlyphMaker { private static final String MONO = "terminus-ttf-4.39/TerminusTTF-Bold-4.39.ttf"; /** - * The CJKhk font resource filename. + * The CJK font resource filename. */ - // private static final String CJKhk = "NotoSansMonoCJKhk-Regular.otf"; + private static final String cjkFontFilename = "NotoSansMonoCJKtc-Regular.otf"; /** - * The CJKkr font resource filename. + * The emoji font resource filename. */ - // private static final String CJKkr = "NotoSansMonoCJKkr-Regular.otf"; - - /** - * The CJKtc font resource filename. - */ - private static final String CJKtc = "NotoSansMonoCJKtc-Regular.otf"; + private static final String emojiFontFilename = "OpenSansEmoji.ttf"; // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- @@ -333,19 +332,14 @@ public class GlyphMaker { private GlyphMakerFont makerMono; /** - * The instance that has the CJKhk font. - */ - // private GlyphMakerFont makerCJKhk; - - /** - * The instance that has the CJKkr font. + * The instance that has the CJK font. */ - // private GlyphMakerFont makerCJKkr; + private GlyphMakerFont makerCjk; /** - * The instance that has the CJKtc font. + * The instance that has the emoji font. */ - private GlyphMakerFont makerCJKtc; + private GlyphMakerFont makerEmoji; // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- @@ -357,11 +351,15 @@ public class GlyphMaker { * @param fontSize the size of these fonts in pixels */ private GlyphMaker(final int fontSize) { - assert (fontSize > 3); makerMono = new GlyphMakerFont(MONO, fontSize); - // makerCJKhk = new GlyphMakerFont(CJKhk, fontSize); - // makerCJKkr = new GlyphMakerFont(CJKkr, fontSize); - makerCJKtc = new GlyphMakerFont(CJKtc, fontSize); + + String fontFilename = null; + fontFilename = System.getProperty("jexer.cjkFont.filename", + cjkFontFilename); + makerCjk = new GlyphMakerFont(fontFilename, fontSize); + fontFilename = System.getProperty("jexer.emojiFont.filename", + emojiFontFilename); + makerEmoji = new GlyphMakerFont(fontFilename, fontSize); } // ------------------------------------------------------------------------ @@ -411,17 +409,12 @@ public class GlyphMaker { public BufferedImage getImage(final Cell cell, final int cellWidth, final int cellHeight, final boolean blinkVisible) { - char ch = cell.getChar(); - /* - if ((ch >= 0x4e00) && (ch <= 0x9fff)) { - return makerCJKhk.getImage(cell, cellWidth, cellHeight, blinkVisible); - } - if ((ch >= 0x4e00) && (ch <= 0x9fff)) { - return makerCJKkr.getImage(cell, cellWidth, cellHeight, blinkVisible); - } - */ + int ch = cell.getChar(); if ((ch >= 0x2e80) && (ch <= 0x9fff)) { - return makerCJKtc.getImage(cell, cellWidth, cellHeight, blinkVisible); + return makerCjk.getImage(cell, cellWidth, cellHeight, blinkVisible); + } + if ((ch >= 0x1f004) && (ch <= 0x1f9c0)) { + return makerEmoji.getImage(cell, cellWidth, cellHeight, blinkVisible); } // When all else fails, use the default. diff --git a/src/jexer/backend/LogicalScreen.java b/src/jexer/backend/LogicalScreen.java index 558fdc4..b4e6214 100644 --- a/src/jexer/backend/LogicalScreen.java +++ b/src/jexer/backend/LogicalScreen.java @@ -369,7 +369,7 @@ public class LogicalScreen implements Screen { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - public final void putAll(final char ch, final CellAttributes attr) { + public final void putAll(final int ch, final CellAttributes attr) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { @@ -430,7 +430,7 @@ public class LogicalScreen implements Screen { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - public final void putCharXY(final int x, final int y, final char ch, + public final void putCharXY(final int x, final int y, final int ch, final CellAttributes attr) { if ((x < clipLeft) @@ -476,8 +476,7 @@ public class LogicalScreen implements Screen { * @param y row coordinate. 0 is the top-most row. * @param ch character to draw */ - public final void putCharXY(final int x, final int y, final char ch) { - + public final void putCharXY(final int x, final int y, final int ch) { if ((x < clipLeft) || (x >= clipRight) || (y < clipTop) @@ -520,8 +519,9 @@ public class LogicalScreen implements Screen { final CellAttributes attr) { int i = x; - for (int j = 0; j < str.length(); j++) { - char ch = str.charAt(j); + for (int j = 0; j < str.length();) { + int ch = str.codePointAt(j); + j += Character.charCount(ch); putCharXY(i, y, ch, attr); i += StringUtils.width(ch); if (i == width) { @@ -541,8 +541,9 @@ public class LogicalScreen implements Screen { public final void putStringXY(final int x, final int y, final String str) { int i = x; - for (int j = 0; j < str.length(); j++) { - char ch = str.charAt(j); + for (int j = 0; j < str.length();) { + int ch = str.codePointAt(j); + j += Character.charCount(ch); putCharXY(i, y, ch); i += StringUtils.width(ch); if (i == width) { @@ -561,7 +562,7 @@ public class LogicalScreen implements Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public final void vLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { for (int i = y; i < y + n; i++) { putCharXY(x, i, ch, attr); @@ -578,7 +579,7 @@ public class LogicalScreen implements Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public final void hLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { for (int i = x; i < x + n; i++) { putCharXY(i, y, ch, attr); @@ -1005,7 +1006,7 @@ public class LogicalScreen implements Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public final void putFullwidthCharXY(final int x, final int y, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { Cell cell = new Cell(ch, attr); putFullwidthCharXY(x, y, cell); @@ -1019,7 +1020,7 @@ public class LogicalScreen implements Screen { * @param ch character to draw */ public final void putFullwidthCharXY(final int x, final int y, - final char ch) { + final int ch) { Cell cell = new Cell(ch); cell.setAttr(getAttrXY(x, y)); diff --git a/src/jexer/backend/MultiScreen.java b/src/jexer/backend/MultiScreen.java index 0c3a289..9d66b69 100644 --- a/src/jexer/backend/MultiScreen.java +++ b/src/jexer/backend/MultiScreen.java @@ -241,7 +241,7 @@ public class MultiScreen implements Screen { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - public void putAll(final char ch, final CellAttributes attr) { + public void putAll(final int ch, final CellAttributes attr) { for (Screen screen: screens) { screen.putAll(ch, attr); } @@ -268,7 +268,7 @@ public class MultiScreen implements Screen { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - public void putCharXY(final int x, final int y, final char ch, + public void putCharXY(final int x, final int y, final int ch, final CellAttributes attr) { for (Screen screen: screens) { @@ -283,7 +283,7 @@ public class MultiScreen implements Screen { * @param y row coordinate. 0 is the top-most row. * @param ch character to draw */ - public void putCharXY(final int x, final int y, final char ch) { + public void putCharXY(final int x, final int y, final int ch) { for (Screen screen: screens) { screen.putCharXY(x, y, ch); } @@ -329,7 +329,7 @@ public class MultiScreen implements Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public void vLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { for (Screen screen: screens) { screen.vLineXY(x, y, n, ch, attr); @@ -346,7 +346,7 @@ public class MultiScreen implements Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public void hLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { for (Screen screen: screens) { screen.hLineXY(x, y, n, ch, attr); diff --git a/src/jexer/backend/Screen.java b/src/jexer/backend/Screen.java index 2d9cd65..2a71073 100644 --- a/src/jexer/backend/Screen.java +++ b/src/jexer/backend/Screen.java @@ -159,7 +159,7 @@ public interface Screen { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - public void putAll(final char ch, final CellAttributes attr); + public void putAll(final int ch, final CellAttributes attr); /** * Render one character with attributes. @@ -178,7 +178,7 @@ public interface Screen { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - public void putCharXY(final int x, final int y, final char ch, + public void putCharXY(final int x, final int y, final int ch, final CellAttributes attr); /** @@ -188,7 +188,7 @@ public interface Screen { * @param y row coordinate. 0 is the top-most row. * @param ch character to draw */ - public void putCharXY(final int x, final int y, final char ch); + public void putCharXY(final int x, final int y, final int ch); /** * Render a string. Does not wrap if the string exceeds the line. @@ -221,7 +221,7 @@ public interface Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public void vLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr); + final int ch, final CellAttributes attr); /** * Draw a horizontal line from (x, y) to (x + n, y). @@ -233,7 +233,7 @@ public interface Screen { * @param attr attributes to use (bold, foreColor, backColor) */ public void hLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr); + final int ch, final CellAttributes attr); /** * Change the width. Everything on-screen will be destroyed and must be diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java index 134eced..a3825b8 100644 --- a/src/jexer/backend/SwingTerminal.java +++ b/src/jexer/backend/SwingTerminal.java @@ -1229,9 +1229,8 @@ public class SwingTerminal extends LogicalScreen || (cell.isBlink() && cursorBlinkVisible) ) { gr2.setColor(attrToForegroundColor(cellColor)); - char [] chars = new char[1]; - chars[0] = cell.getChar(); - gr2.drawChars(chars, 0, 1, gr2x + textAdjustX, + char [] chars = Character.toChars(cell.getChar()); + gr2.drawChars(chars, 0, chars.length, gr2x + textAdjustX, gr2y + textHeight - maxDescent + textAdjustY); if (cell.isUnderline()) { diff --git a/src/jexer/bits/Cell.java b/src/jexer/bits/Cell.java index ff10dae..a8efa2b 100644 --- a/src/jexer/bits/Cell.java +++ b/src/jexer/bits/Cell.java @@ -73,7 +73,7 @@ public final class Cell extends CellAttributes { /** * The character at this cell. */ - private char ch = ' '; + private int ch = ' '; /** * The display width of this cell. @@ -129,7 +129,7 @@ public final class Cell extends CellAttributes { * @param ch character to set to * @see #reset() */ - public Cell(final char ch) { + public Cell(final int ch) { this.ch = ch; } @@ -148,7 +148,7 @@ public final class Cell extends CellAttributes { * @param ch character to set to * @param attr attributes to use */ - public Cell(final char ch, final CellAttributes attr) { + public Cell(final int ch, final CellAttributes attr) { super(attr); this.ch = ch; } @@ -263,7 +263,7 @@ public final class Cell extends CellAttributes { * * @return cell character */ - public char getChar() { + public int getChar() { return ch; } @@ -272,7 +272,7 @@ public final class Cell extends CellAttributes { * * @param ch new cell character */ - public void setChar(final char ch) { + public void setChar(final int ch) { this.ch = ch; } diff --git a/src/jexer/bits/MnemonicString.java b/src/jexer/bits/MnemonicString.java index 2d5dbc8..58575b5 100644 --- a/src/jexer/bits/MnemonicString.java +++ b/src/jexer/bits/MnemonicString.java @@ -43,7 +43,7 @@ public class MnemonicString { /** * Keyboard shortcut to activate this item. */ - private char shortcut; + private int shortcut; /** * Location of the highlighted character. @@ -74,23 +74,25 @@ public class MnemonicString { public MnemonicString(final String label) { // Setup the menu shortcut - String newLabel = ""; + StringBuilder newLabel = new StringBuilder(); boolean foundAmp = false; boolean foundShortcut = false; int scanShortcutIdx = 0; int scanScreenShortcutIdx = 0; - for (int i = 0; i < label.length(); i++) { - char c = label.charAt(i); + for (int i = 0; i < label.length();) { + int c = label.codePointAt(i); + i += Character.charCount(c); + if (c == '&') { if (foundAmp) { - newLabel += '&'; + newLabel.append('&'); scanShortcutIdx++; scanScreenShortcutIdx++; } else { foundAmp = true; } } else { - newLabel += c; + newLabel.append(Character.toChars(c)); if (foundAmp) { if (!foundShortcut) { shortcut = c; @@ -105,7 +107,7 @@ public class MnemonicString { } } } - this.rawLabel = newLabel; + this.rawLabel = newLabel.toString(); } // ------------------------------------------------------------------------ @@ -117,7 +119,7 @@ public class MnemonicString { * * @return the highlighted character */ - public char getShortcut() { + public int getShortcut() { return shortcut; } diff --git a/src/jexer/bits/StringUtils.java b/src/jexer/bits/StringUtils.java index f5e2d47..26d90a4 100644 --- a/src/jexer/bits/StringUtils.java +++ b/src/jexer/bits/StringUtils.java @@ -449,7 +449,8 @@ public class StringUtils { || ((ch >= 0xffe0) && (ch <= 0xffe6)) || ((ch >= 0x20000) && (ch <= 0x2fffd)) || ((ch >= 0x30000) && (ch <= 0x3fffd)) - // TODO: emoji / twemoji + // emoji + || ((ch >= 0x1f004) && (ch <= 0x1f9c0)) ) ) { return 2; @@ -466,8 +467,10 @@ public class StringUtils { */ public static int width(final String str) { int n = 0; - for (int i = 0; i < str.length(); i++) { - n += width(str.charAt(i)); + for (int i = 0; i < str.length();) { + int ch = str.codePointAt(i); + n += width(ch); + i += Character.charCount(ch); } return n; } -- 2.27.0 From afdec5e9d0416e1c571f1961572b8ededef81146 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Mon, 12 Aug 2019 07:37:18 -0500 Subject: [PATCH 03/16] #35 support emoji in tterminal --- src/jexer/TEditorWidget.java | 3 +- src/jexer/TField.java | 14 ++-- src/jexer/TKeypress.java | 8 +-- src/jexer/backend/GlyphMaker.java | 3 +- src/jexer/backend/SwingTerminal.java | 1 + src/jexer/bits/StringUtils.java | 2 +- src/jexer/event/TKeypressEvent.java | 2 +- src/jexer/tterminal/DisplayLine.java | 2 +- src/jexer/tterminal/ECMA48.java | 98 ++++++++++++++-------------- 9 files changed, 69 insertions(+), 64 deletions(-) diff --git a/src/jexer/TEditorWidget.java b/src/jexer/TEditorWidget.java index f65ba6b..204b575 100644 --- a/src/jexer/TEditorWidget.java +++ b/src/jexer/TEditorWidget.java @@ -265,7 +265,8 @@ public class TEditorWidget extends TWidget { && !keypress.getKey().isCtrl() ) { // Plain old keystroke, process it - document.addChar(keypress.getKey().getChar()); + // TODO: fix document to use ints, not chars + document.addChar((char) keypress.getKey().getChar()); alignCursor(); } else { // Pass other keys (tab etc.) on to TWidget diff --git a/src/jexer/TField.java b/src/jexer/TField.java index 7b17ba8..3d727ea 100644 --- a/src/jexer/TField.java +++ b/src/jexer/TField.java @@ -47,7 +47,7 @@ public class TField extends TWidget { /** * Background character for unfilled-in text. */ - protected char backgroundChar = GraphicsChars.HATCH; + protected int backgroundChar = GraphicsChars.HATCH; /** * Field text. @@ -412,7 +412,7 @@ public class TField extends TWidget { * * @return background character */ - public final char getBackgroundChar() { + public final int getBackgroundChar() { return backgroundChar; } @@ -421,7 +421,7 @@ public class TField extends TWidget { * * @param backgroundChar the background character */ - public void setBackgroundChar(final char backgroundChar) { + public void setBackgroundChar(final int backgroundChar) { this.backgroundChar = backgroundChar; } @@ -521,9 +521,9 @@ public class TField extends TWidget { /** * Append char to the end of the field. * - * @param ch = char to append + * @param ch char to append */ - protected void appendChar(final char ch) { + protected void appendChar(final int ch) { // Append the LAST character text += ch; position++; @@ -548,8 +548,8 @@ public class TField extends TWidget { * * @param ch char to append */ - protected void insertChar(final char ch) { - text = text.substring(0, position) + ch + text.substring(position); + protected void insertChar(final int ch) { + text = text.substring(0, position) + ((char) ch) + text.substring(position); position++; screenPosition += StringUtils.width(ch); if ((screenPosition - windowStart) == getWidth()) { diff --git a/src/jexer/TKeypress.java b/src/jexer/TKeypress.java index 7713e55..c965e7d 100644 --- a/src/jexer/TKeypress.java +++ b/src/jexer/TKeypress.java @@ -622,7 +622,7 @@ public class TKeypress { * Backspace as ^?. */ public static final TKeypress kbBackspaceDel = new TKeypress(false, - 0, (char)0x7F, false, false, false); + 0, (char) 0x7F, false, false, false); // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- @@ -656,7 +656,7 @@ public class TKeypress { /** * The character received. */ - private char ch; + private int ch; // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- @@ -672,7 +672,7 @@ public class TKeypress { * @param ctrl if true, CTRL was pressed with this keystroke * @param shift if true, SHIFT was pressed with this keystroke */ - public TKeypress(final boolean isKey, final int fnKey, final char ch, + public TKeypress(final boolean isKey, final int fnKey, final int ch, final boolean alt, final boolean ctrl, final boolean shift) { this.isFunctionKey = isKey; @@ -737,7 +737,7 @@ public class TKeypress { * * @return the character (only valid if isKey is false) */ - public char getChar() { + public int getChar() { return ch; } diff --git a/src/jexer/backend/GlyphMaker.java b/src/jexer/backend/GlyphMaker.java index 2a6610b..ef76969 100644 --- a/src/jexer/backend/GlyphMaker.java +++ b/src/jexer/backend/GlyphMaker.java @@ -413,7 +413,8 @@ public class GlyphMaker { if ((ch >= 0x2e80) && (ch <= 0x9fff)) { return makerCjk.getImage(cell, cellWidth, cellHeight, blinkVisible); } - if ((ch >= 0x1f004) && (ch <= 0x1f9c0)) { + if ((ch >= 0x1f004) && (ch <= 0x1fffd)) { + // System.err.println("emoji: " + String.format("0x%x", ch)); return makerEmoji.getImage(cell, cellWidth, cellHeight, blinkVisible); } diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java index a3825b8..65decfa 100644 --- a/src/jexer/backend/SwingTerminal.java +++ b/src/jexer/backend/SwingTerminal.java @@ -1828,6 +1828,7 @@ public class SwingTerminal extends LogicalScreen break; default: if (!alt && ctrl && !shift) { + // Control character, replace ch with 'A', 'B', etc. ch = KeyEvent.getKeyText(key.getKeyCode()).charAt(0); } // Not a special key, put it together diff --git a/src/jexer/bits/StringUtils.java b/src/jexer/bits/StringUtils.java index 26d90a4..12a1390 100644 --- a/src/jexer/bits/StringUtils.java +++ b/src/jexer/bits/StringUtils.java @@ -450,7 +450,7 @@ public class StringUtils { || ((ch >= 0x20000) && (ch <= 0x2fffd)) || ((ch >= 0x30000) && (ch <= 0x3fffd)) // emoji - || ((ch >= 0x1f004) && (ch <= 0x1f9c0)) + || ((ch >= 0x1f004) && (ch <= 0x1fffd)) ) ) { return 2; diff --git a/src/jexer/event/TKeypressEvent.java b/src/jexer/event/TKeypressEvent.java index b56f08b..79b28f2 100644 --- a/src/jexer/event/TKeypressEvent.java +++ b/src/jexer/event/TKeypressEvent.java @@ -67,7 +67,7 @@ public class TKeypressEvent extends TInputEvent { * @param ctrl if true, CTRL was pressed with this keystroke * @param shift if true, SHIFT was pressed with this keystroke */ - public TKeypressEvent(final boolean isKey, final int fnKey, final char ch, + public TKeypressEvent(final boolean isKey, final int fnKey, final int ch, final boolean alt, final boolean ctrl, final boolean shift) { this.key = new TKeypress(isKey, fnKey, ch, alt, ctrl, shift); diff --git a/src/jexer/tterminal/DisplayLine.java b/src/jexer/tterminal/DisplayLine.java index a32da9b..06a05a3 100644 --- a/src/jexer/tterminal/DisplayLine.java +++ b/src/jexer/tterminal/DisplayLine.java @@ -221,7 +221,7 @@ public class DisplayLine { * @param idx the character index * @param ch the new char */ - public void setChar(final int idx, final char ch) { + public void setChar(final int idx, final int ch) { chars[idx].setChar(ch); } diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 7ce95d6..9ff37a1 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -350,7 +350,7 @@ public class ECMA48 implements Runnable { /** * Last character printed. */ - private char repCh; + private int repCh; /** * VT100-style line wrapping: a character is placed in column 80 (or @@ -750,15 +750,17 @@ public class ECMA48 implements Runnable { } else { // Don't step on UI events synchronized (this) { - for (int i = 0; i < rc; i++) { - int ch = 0; - if (utf8) { - ch = readBufferUTF8[i]; - } else { - ch = readBuffer[i]; + if (utf8) { + for (int i = 0; i < rc;) { + int ch = Character.codePointAt(readBufferUTF8, + i); + i += Character.charCount(ch); + consume(ch); + } + } else { + for (int i = 0; i < rc; i++) { + consume(readBuffer[i]); } - - consume((char) ch); } } // Permit my enclosing UI to know that I updated. @@ -1426,7 +1428,7 @@ public class ECMA48 implements Runnable { * * @param ch character to display */ - private void printCharacter(final char ch) { + private void printCharacter(final int ch) { int rightMargin = this.rightMargin; if (StringUtils.width(ch) == 2) { @@ -1741,7 +1743,7 @@ public class ECMA48 implements Runnable { * the remote side. */ if (keypress.getChar() < 0x20) { - handleControlChar(keypress.getChar()); + handleControlChar((char) keypress.getChar()); } else { // Local echo for everything else printCharacter(keypress.getChar()); @@ -1756,17 +1758,17 @@ public class ECMA48 implements Runnable { // Handle control characters if ((keypress.isCtrl()) && (!keypress.isFnKey())) { StringBuilder sb = new StringBuilder(); - char ch = keypress.getChar(); + int ch = keypress.getChar(); ch -= 0x40; - sb.append(ch); + sb.append(Character.toChars(ch)); return sb.toString(); } // Handle alt characters if ((keypress.isAlt()) && (!keypress.isFnKey())) { StringBuilder sb = new StringBuilder("\033"); - char ch = keypress.getChar(); - sb.append(ch); + int ch = keypress.getChar(); + sb.append(Character.toChars(ch)); return sb.toString(); } @@ -2318,7 +2320,7 @@ public class ECMA48 implements Runnable { // Non-alt, non-ctrl characters if (!keypress.isFnKey()) { StringBuilder sb = new StringBuilder(); - sb.append(keypress.getChar()); + sb.append(Character.toChars(keypress.getChar())); return sb.toString(); } return ""; @@ -2333,7 +2335,7 @@ public class ECMA48 implements Runnable { * @param charsetGr character set defined for GR * @return character to display on the screen */ - private char mapCharacterCharset(final char ch, + private char mapCharacterCharset(final int ch, final CharacterSet charsetGl, final CharacterSet charsetGr) { @@ -2411,7 +2413,7 @@ public class ECMA48 implements Runnable { * @param ch either 8-bit or Unicode character from the remote side * @return character to display on the screen */ - private char mapCharacter(final char ch) { + private int mapCharacter(final int ch) { if (ch >= 0x100) { // Unicode character, just return it return ch; @@ -4732,14 +4734,14 @@ public class ECMA48 implements Runnable { * * @param ch character from the remote side */ - private void consume(char ch) { + private void consume(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 = (char)(ch & 0x7F); + ch = (ch & 0x7F); } // Special "anywhere" states @@ -4809,7 +4811,7 @@ public class ECMA48 implements Runnable { // 00-17, 19, 1C-1F --> execute // 80-8F, 91-9A, 9C --> execute if ((ch <= 0x1F) || ((ch >= 0x80) && (ch <= 0x9F))) { - handleControlChar(ch); + handleControlChar((char) ch); } // 20-7F --> print @@ -4836,13 +4838,13 @@ public class ECMA48 implements Runnable { case ESCAPE: // 00-17, 19, 1C-1F --> execute if (ch <= 0x1F) { - handleControlChar(ch); + handleControlChar((char) ch); return; } // 20-2F --> collect, then switch to ESCAPE_INTERMEDIATE if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); scanState = ScanState.ESCAPE_INTERMEDIATE; return; } @@ -5178,12 +5180,12 @@ public class ECMA48 implements Runnable { case ESCAPE_INTERMEDIATE: // 00-17, 19, 1C-1F --> execute if (ch <= 0x1F) { - handleControlChar(ch); + handleControlChar((char) ch); } // 20-2F --> collect if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); } // 30-7E --> dispatch, then switch to GROUND @@ -5785,12 +5787,12 @@ public class ECMA48 implements Runnable { case CSI_ENTRY: // 00-17, 19, 1C-1F --> execute if (ch <= 0x1F) { - handleControlChar(ch); + handleControlChar((char) ch); } // 20-2F --> collect, then switch to CSI_INTERMEDIATE if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); scanState = ScanState.CSI_INTERMEDIATE; } @@ -5806,7 +5808,7 @@ public class ECMA48 implements Runnable { // 3C-3F --> collect, then switch to CSI_PARAM if ((ch >= 0x3C) && (ch <= 0x3F)) { - collect(ch); + collect((char) ch); scanState = ScanState.CSI_PARAM; } @@ -6058,12 +6060,12 @@ public class ECMA48 implements Runnable { case CSI_PARAM: // 00-17, 19, 1C-1F --> execute if (ch <= 0x1F) { - handleControlChar(ch); + handleControlChar((char) ch); } // 20-2F --> collect, then switch to CSI_INTERMEDIATE if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); scanState = ScanState.CSI_INTERMEDIATE; } @@ -6312,12 +6314,12 @@ public class ECMA48 implements Runnable { case CSI_INTERMEDIATE: // 00-17, 19, 1C-1F --> execute if (ch <= 0x1F) { - handleControlChar(ch); + handleControlChar((char) ch); } // 20-2F --> collect if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); } // 0x30-3F goes to CSI_IGNORE @@ -6425,12 +6427,12 @@ public class ECMA48 implements Runnable { case CSI_IGNORE: // 00-17, 19, 1C-1F --> execute if (ch <= 0x1F) { - handleControlChar(ch); + handleControlChar((char) ch); } // 20-2F --> collect if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); } // 40-7E --> ignore, then switch to GROUND @@ -6451,7 +6453,7 @@ public class ECMA48 implements Runnable { // 0x1B 0x5C goes to GROUND if (ch == 0x1B) { - collect(ch); + collect((char) ch); } if (ch == 0x5C) { if ((collectBuffer.length() > 0) @@ -6463,7 +6465,7 @@ public class ECMA48 implements Runnable { // 20-2F --> collect, then switch to DCS_INTERMEDIATE if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); scanState = ScanState.DCS_INTERMEDIATE; } @@ -6479,7 +6481,7 @@ public class ECMA48 implements Runnable { // 3C-3F --> collect, then switch to DCS_PARAM if ((ch >= 0x3C) && (ch <= 0x3F)) { - collect(ch); + collect((char) ch); scanState = ScanState.DCS_PARAM; } @@ -6509,7 +6511,7 @@ public class ECMA48 implements Runnable { // 0x1B 0x5C goes to GROUND if (ch == 0x1B) { - collect(ch); + collect((char) ch); } if (ch == 0x5C) { if ((collectBuffer.length() > 0) @@ -6541,7 +6543,7 @@ public class ECMA48 implements Runnable { // 0x1B 0x5C goes to GROUND if (ch == 0x1B) { - collect(ch); + collect((char) ch); } if (ch == 0x5C) { if ((collectBuffer.length() > 0) @@ -6553,7 +6555,7 @@ public class ECMA48 implements Runnable { // 20-2F --> collect, then switch to DCS_INTERMEDIATE if ((ch >= 0x20) && (ch <= 0x2F)) { - collect(ch); + collect((char) ch); scanState = ScanState.DCS_INTERMEDIATE; } @@ -6593,7 +6595,7 @@ public class ECMA48 implements Runnable { // 0x1B 0x5C goes to GROUND if (ch == 0x1B) { - collect(ch); + collect((char) ch); } if (ch == 0x5C) { if ((collectBuffer.length() > 0) @@ -6641,7 +6643,7 @@ public class ECMA48 implements Runnable { // 0x1B 0x5C goes to GROUND if (ch == 0x1B) { - collect(ch); + collect((char) ch); } if (ch == 0x5C) { if ((collectBuffer.length() > 0) @@ -6679,11 +6681,11 @@ public class ECMA48 implements Runnable { // Special case for Jexer: PM can pass one control character if (ch == 0x1B) { - pmPut(ch); + pmPut((char) ch); } if ((ch >= 0x20) && (ch <= 0x7F)) { - pmPut(ch); + pmPut((char) ch); } // 0x9C goes to GROUND @@ -6696,14 +6698,14 @@ public class ECMA48 implements Runnable { case OSC_STRING: // Special case for Xterm: OSC can pass control characters if ((ch == 0x9C) || (ch == 0x07) || (ch == 0x1B)) { - oscPut(ch); + oscPut((char) ch); } // 00-17, 19, 1C-1F --> ignore // 20-7F --> osc_put if ((ch >= 0x20) && (ch <= 0x7F)) { - oscPut(ch); + oscPut((char) ch); } // 0x9C goes to GROUND @@ -6716,7 +6718,7 @@ public class ECMA48 implements Runnable { case VT52_DIRECT_CURSOR_ADDRESS: // This is a special case for the VT52 sequence "ESC Y l c" if (collectBuffer.length() == 0) { - collect(ch); + collect((char) ch); } else if (collectBuffer.length() == 1) { // We've got the two characters, one in the buffer and the // other in ch. @@ -6899,7 +6901,7 @@ public class ECMA48 implements Runnable { * @param ch the character to draw */ private void drawHalves(final int leftX, final int leftY, - final int rightX, final int rightY, final char ch) { + final int rightX, final int rightY, final int ch) { // System.err.println("drawHalves(): " + Integer.toHexString(ch)); -- 2.27.0 From 66edb445d03e2e780141f92e81a7b9a811dbfa9d Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 13 Aug 2019 04:54:22 -0500 Subject: [PATCH 04/16] #35 fallback font --- src/jexer/backend/GlyphMaker.java | 63 +++++++++++++++++++++++++++---- src/jexer/bits/StringUtils.java | 20 ++++++++++ 2 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/jexer/backend/GlyphMaker.java b/src/jexer/backend/GlyphMaker.java index ef76969..0da2918 100644 --- a/src/jexer/backend/GlyphMaker.java +++ b/src/jexer/backend/GlyphMaker.java @@ -38,6 +38,7 @@ import java.io.IOException; import java.util.HashMap; import jexer.bits.Cell; +import jexer.bits.StringUtils; /** * GlyphMakerFont creates glyphs as bitmaps from a font. @@ -135,6 +136,13 @@ class GlyphMakerFont { * @param fontSize the size of font to use */ public GlyphMakerFont(final String filename, final int fontSize) { + + if (filename.length() == 0) { + // Fallback font + font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize); + return; + } + Font fontRoot = null; try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); @@ -142,10 +150,13 @@ class GlyphMakerFont { fontRoot = Font.createFont(Font.TRUETYPE_FONT, in); font = fontRoot.deriveFont(Font.PLAIN, fontSize); } catch (java.awt.FontFormatException e) { - e.printStackTrace(); + // Ideally we would report an error here, either via System.err + // or TExceptionDialog. However, I do not want GlyphMaker to + // know about available backends, so we quietly fallback to + // whatever is available as MONO. font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize); } catch (java.io.IOException e) { - e.printStackTrace(); + // See comment above. font = new Font(Font.MONOSPACED, Font.PLAIN, fontSize); } } @@ -285,6 +296,17 @@ class GlyphMakerFont { gotFontDimensions = true; } + /** + * Checks if this maker's Font has a glyph for the specified character. + * + * @param codePoint the character (Unicode code point) for which a glyph + * is needed. + * @return true if this Font has a glyph for the character; false + * otherwise. + */ + public boolean canDisplay(final int codePoint) { + return font.canDisplay(codePoint); + } } /** @@ -312,6 +334,11 @@ public class GlyphMaker { */ private static final String emojiFontFilename = "OpenSansEmoji.ttf"; + /** + * The fallback font resource filename. + */ + private static final String fallbackFontFilename = ""; + // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ @@ -341,6 +368,11 @@ public class GlyphMaker { */ private GlyphMakerFont makerEmoji; + /** + * The instance that has the fallback font. + */ + private GlyphMakerFont makerFallback; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -360,6 +392,9 @@ public class GlyphMaker { fontFilename = System.getProperty("jexer.emojiFont.filename", emojiFontFilename); makerEmoji = new GlyphMakerFont(fontFilename, fontSize); + fontFilename = System.getProperty("jexer.fallbackFont.filename", + fallbackFontFilename); + makerFallback = new GlyphMakerFont(fontFilename, fontSize); } // ------------------------------------------------------------------------ @@ -410,16 +445,28 @@ public class GlyphMaker { final int cellHeight, final boolean blinkVisible) { int ch = cell.getChar(); - if ((ch >= 0x2e80) && (ch <= 0x9fff)) { - return makerCjk.getImage(cell, cellWidth, cellHeight, blinkVisible); + if (StringUtils.isCjk(ch)) { + if (makerCjk.canDisplay(ch)) { + return makerCjk.getImage(cell, cellWidth, cellHeight, + blinkVisible); + } } - if ((ch >= 0x1f004) && (ch <= 0x1fffd)) { - // System.err.println("emoji: " + String.format("0x%x", ch)); - return makerEmoji.getImage(cell, cellWidth, cellHeight, blinkVisible); + if (StringUtils.isEmoji(ch)) { + if (makerEmoji.canDisplay(ch)) { + // System.err.println("emoji: " + String.format("0x%x", ch)); + return makerEmoji.getImage(cell, cellWidth, cellHeight, + blinkVisible); + } } // When all else fails, use the default. - return makerMono.getImage(cell, cellWidth, cellHeight, blinkVisible); + if (makerMono.canDisplay(ch)) { + return makerMono.getImage(cell, cellWidth, cellHeight, + blinkVisible); + } + + return makerFallback.getImage(cell, cellWidth, cellHeight, + blinkVisible); } } diff --git a/src/jexer/bits/StringUtils.java b/src/jexer/bits/StringUtils.java index 12a1390..fffce20 100644 --- a/src/jexer/bits/StringUtils.java +++ b/src/jexer/bits/StringUtils.java @@ -475,4 +475,24 @@ public class StringUtils { return n; } + /** + * Check if character is in the CJK range. + * + * @param ch character to check + * @return true if this character is in the CJK range + */ + public static boolean isCjk(final int ch) { + return ((ch >= 0x2e80) && (ch <= 0x9fff)); + } + + /** + * Check if character is in the emoji range. + * + * @param ch character to check + * @return true if this character is in the emoji range + */ + public static boolean isEmoji(final int ch) { + return ((ch >= 0x1f004) && (ch <= 0x1fffd)); + } + } -- 2.27.0 From caa3b80873e1db6108388d1bfce423c2ddfb8363 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 13 Aug 2019 05:48:33 -0500 Subject: [PATCH 05/16] #35 oops --- src/jexer/backend/ECMA48Terminal.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jexer/backend/ECMA48Terminal.java b/src/jexer/backend/ECMA48Terminal.java index cb9724c..16af900 100644 --- a/src/jexer/backend/ECMA48Terminal.java +++ b/src/jexer/backend/ECMA48Terminal.java @@ -1865,7 +1865,7 @@ public class ECMA48Terminal extends LogicalScreen } // Emit the character - sb.append(lCell.getChar()); + sb.append(Character.toChars(lCell.getChar())); // Save the last rendered cell lastX = x; -- 2.27.0 From 772b8255e4ee92c9c026bd94df39d0c177d6a6bb Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 13 Aug 2019 07:25:24 -0500 Subject: [PATCH 06/16] #35 try to emit raw CJK --- src/jexer/backend/ECMA48Terminal.java | 43 ++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/jexer/backend/ECMA48Terminal.java b/src/jexer/backend/ECMA48Terminal.java index 16af900..191f4ca 100644 --- a/src/jexer/backend/ECMA48Terminal.java +++ b/src/jexer/backend/ECMA48Terminal.java @@ -167,6 +167,11 @@ public class ECMA48Terminal extends LogicalScreen */ 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. */ @@ -1373,6 +1378,14 @@ public class ECMA48Terminal extends LogicalScreen doRgbColor = false; } + // Default to using sixel 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; @@ -1694,7 +1707,11 @@ public class ECMA48Terminal extends LogicalScreen // 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 @@ -1705,7 +1722,16 @@ public class ECMA48Terminal extends LogicalScreen 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)); @@ -1865,7 +1891,13 @@ public class ECMA48Terminal extends LogicalScreen } // Emit the character - sb.append(Character.toChars(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; @@ -1917,7 +1949,10 @@ public class ECMA48Terminal extends LogicalScreen Cell lCell = logical[x][y]; Cell pCell = physical[x][y]; - if (!lCell.isImage()) { + if (!lCell.isImage() + || (!wideCharImages + && (lCell.getWidth() != Cell.Width.SINGLE)) + ) { continue; } -- 2.27.0 From 3fe82fa71d39d874691dc85e6a77250fd4953b17 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 13 Aug 2019 17:53:06 -0500 Subject: [PATCH 07/16] #35 cjk without sixel --- src/jexer/TApplication.java | 21 ++++++++++---------- src/jexer/TButton.java | 6 +++--- src/jexer/TCheckBox.java | 4 ++-- src/jexer/TLabel.java | 4 ++-- src/jexer/backend/LogicalScreen.java | 29 +++++++++++++++++++++------- src/jexer/menu/TMenuItem.java | 6 +++--- 6 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/jexer/TApplication.java b/src/jexer/TApplication.java index ec93629..8cbce90 100644 --- a/src/jexer/TApplication.java +++ b/src/jexer/TApplication.java @@ -1695,17 +1695,16 @@ public class TApplication implements Runnable { Cell cell = getScreen().getCharXY(x, y); if (cell.isImage()) { cell.invertImage(); + } + if (cell.getForeColorRGB() < 0) { + cell.setForeColor(cell.getForeColor().invert()); } else { - if (cell.getForeColorRGB() < 0) { - cell.setForeColor(cell.getForeColor().invert()); - } else { - cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff); - } - if (cell.getBackColorRGB() < 0) { - cell.setBackColor(cell.getBackColor().invert()); - } else { - cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff); - } + cell.setForeColorRGB(cell.getForeColorRGB() ^ 0x00ffffff); + } + if (cell.getBackColorRGB() < 0) { + cell.setBackColor(cell.getBackColor().invert()); + } else { + cell.setBackColorRGB(cell.getBackColorRGB() ^ 0x00ffffff); } getScreen().putCharXY(x, y, cell); if ((onlyThisCell == true) || (cell.getWidth() == Cell.Width.SINGLE)) { @@ -1856,7 +1855,7 @@ public class TApplication implements Runnable { menuColor); getScreen().putStringXY(x + 1, 0, menu.getTitle(), menuColor); // Draw the highlight character - getScreen().putCharXY(x + 1 + menu.getMnemonic().getShortcutIdx(), + getScreen().putCharXY(x + 1 + menu.getMnemonic().getScreenShortcutIdx(), 0, menu.getMnemonic().getShortcut(), menuMnemonicColor); if (menu.isActive()) { diff --git a/src/jexer/TButton.java b/src/jexer/TButton.java index 534c12a..82a6389 100644 --- a/src/jexer/TButton.java +++ b/src/jexer/TButton.java @@ -250,12 +250,12 @@ public class TButton extends TWidget { GraphicsChars.CP437[0xDF], shadowColor); } } - if (mnemonic.getShortcutIdx() >= 0) { + if (mnemonic.getScreenShortcutIdx() >= 0) { if (inButtonPress) { - putCharXY(2 + mnemonic.getShortcutIdx(), 0, + putCharXY(2 + mnemonic.getScreenShortcutIdx(), 0, mnemonic.getShortcut(), menuMnemonicColor); } else { - putCharXY(1 + mnemonic.getShortcutIdx(), 0, + putCharXY(1 + mnemonic.getScreenShortcutIdx(), 0, mnemonic.getShortcut(), menuMnemonicColor); } } diff --git a/src/jexer/TCheckBox.java b/src/jexer/TCheckBox.java index 1e86470..1f9a351 100644 --- a/src/jexer/TCheckBox.java +++ b/src/jexer/TCheckBox.java @@ -176,8 +176,8 @@ public class TCheckBox extends TWidget { } putCharXY(2, 0, ']', checkboxColor); putStringXY(4, 0, mnemonic.getRawLabel(), checkboxColor); - if (mnemonic.getShortcutIdx() >= 0) { - putCharXY(4 + mnemonic.getShortcutIdx(), 0, + if (mnemonic.getScreenShortcutIdx() >= 0) { + putCharXY(4 + mnemonic.getScreenShortcutIdx(), 0, mnemonic.getShortcut(), mnemonicColor); } } diff --git a/src/jexer/TLabel.java b/src/jexer/TLabel.java index 1d65976..38c014c 100644 --- a/src/jexer/TLabel.java +++ b/src/jexer/TLabel.java @@ -186,8 +186,8 @@ public class TLabel extends TWidget { mnemonicColor.setBackColor(background.getBackColor()); } putStringXY(0, 0, mnemonic.getRawLabel(), color); - if (mnemonic.getShortcutIdx() >= 0) { - putCharXY(mnemonic.getShortcutIdx(), 0, + if (mnemonic.getScreenShortcutIdx() >= 0) { + putCharXY(mnemonic.getScreenShortcutIdx(), 0, mnemonic.getShortcut(), mnemonicColor); } } diff --git a/src/jexer/backend/LogicalScreen.java b/src/jexer/backend/LogicalScreen.java index b4e6214..4e4aecc 100644 --- a/src/jexer/backend/LogicalScreen.java +++ b/src/jexer/backend/LogicalScreen.java @@ -803,11 +803,30 @@ public class LogicalScreen implements Screen { clipBottom = height - offsetY; for (int i = 0; i < boxHeight; i++) { - putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr); - putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr); + Cell cell = getCharXY(offsetX + boxLeft + boxWidth, + offsetY + boxTop + 1 + i); + if (cell.getWidth() == Cell.Width.SINGLE) { + putAttrXY(boxLeft + boxWidth, boxTop + 1 + i, shadowAttr); + } else { + putCharXY(boxLeft + boxWidth, boxTop + 1 + i, ' ', shadowAttr); + } + cell = getCharXY(offsetX + boxLeft + boxWidth + 1, + offsetY + boxTop + 1 + i); + if (cell.getWidth() == Cell.Width.SINGLE) { + putAttrXY(boxLeft + boxWidth + 1, boxTop + 1 + i, shadowAttr); + } else { + putCharXY(boxLeft + boxWidth + 1, boxTop + 1 + i, ' ', + shadowAttr); + } } for (int i = 0; i < boxWidth; i++) { - putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr); + Cell cell = getCharXY(offsetX + boxLeft + 2 + i, + offsetY + boxTop + boxHeight); + if (cell.getWidth() == Cell.Width.SINGLE) { + putAttrXY(boxLeft + 2 + i, boxTop + boxHeight, shadowAttr); + } else { + putCharXY(boxLeft + 2 + i, boxTop + boxHeight, ' ', shadowAttr); + } } clipRight = oldClipRight; clipBottom = oldClipBottom; @@ -985,15 +1004,11 @@ public class LogicalScreen implements Screen { Cell left = new Cell(cell); left.setImage(leftImage); left.setWidth(Cell.Width.LEFT); - // Blank out the char itself, so that shadows do not leave artifacts. - left.setChar(' '); putCharXY(x, y, left); Cell right = new Cell(cell); right.setImage(rightImage); right.setWidth(Cell.Width.RIGHT); - // Blank out the char itself, so that shadows do not leave artifacts. - right.setChar(' '); putCharXY(x + 1, y, right); } diff --git a/src/jexer/menu/TMenuItem.java b/src/jexer/menu/TMenuItem.java index eafa9e5..d9dfc2a 100644 --- a/src/jexer/menu/TMenuItem.java +++ b/src/jexer/menu/TMenuItem.java @@ -231,9 +231,9 @@ public class TMenuItem extends TWidget { putStringXY((getWidth() - StringUtils.width(keyLabel) - 2), 0, keyLabel, menuColor); } - if (mnemonic.getShortcutIdx() >= 0) { - putCharXY(2 + mnemonic.getShortcutIdx(), 0, mnemonic.getShortcut(), - menuMnemonicColor); + if (mnemonic.getScreenShortcutIdx() >= 0) { + putCharXY(2 + mnemonic.getScreenShortcutIdx(), 0, + mnemonic.getShortcut(), menuMnemonicColor); } if (checked) { assert (checkable); -- 2.27.0 From 84a34fdf1e9b8c2911744c0d0c39e27108c20b76 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 13 Aug 2019 19:08:11 -0500 Subject: [PATCH 08/16] #35 fix crash --- src/jexer/TField.java | 53 +++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/src/jexer/TField.java b/src/jexer/TField.java index 3d727ea..0a99840 100644 --- a/src/jexer/TField.java +++ b/src/jexer/TField.java @@ -220,7 +220,7 @@ public class TField extends TWidget { if (keypress.equals(kbLeft)) { if (position > 0) { if (position < text.length()) { - screenPosition -= StringUtils.width(text.charAt(position)); + screenPosition -= StringUtils.width(text.codePointAt(position)); } else { screenPosition--; } @@ -228,7 +228,7 @@ public class TField extends TWidget { } if (fixed == false) { if ((screenPosition == windowStart) && (windowStart > 0)) { - windowStart -= StringUtils.width(text.charAt( + windowStart -= StringUtils.width(text.codePointAt( screenToTextPosition(windowStart))); } } @@ -238,7 +238,7 @@ public class TField extends TWidget { if (keypress.equals(kbRight)) { if (position < text.length()) { - screenPosition += StringUtils.width(text.charAt(position)); + screenPosition += StringUtils.width(text.codePointAt(position)); position++; if (fixed == true) { if (screenPosition == getWidth()) { @@ -247,7 +247,7 @@ public class TField extends TWidget { } } else { if ((screenPosition - windowStart) >= getWidth()) { - windowStart += StringUtils.width(text.charAt( + windowStart += StringUtils.width(text.codePointAt( screenToTextPosition(windowStart))); } } @@ -295,7 +295,7 @@ public class TField extends TWidget { if ((screenPosition >= windowStart) && (windowStart > 0) ) { - windowStart -= StringUtils.width(text.charAt( + windowStart -= StringUtils.width(text.codePointAt( screenToTextPosition(windowStart))); } } @@ -321,10 +321,10 @@ public class TField extends TWidget { if (insertMode == false) { // Replace character text = text.substring(0, position) - + keypress.getKey().getChar() + + codePointString(keypress.getKey().getChar()) + text.substring(position + 1); - screenPosition += StringUtils.width(text.charAt(position)); - position++; + screenPosition += StringUtils.width(text.codePointAt(position)); + position += Character.charCount(keypress.getKey().getChar()); } else { // Insert character insertChar(keypress.getKey().getChar()); @@ -338,19 +338,19 @@ public class TField extends TWidget { } else if ((fixed == true) && (insertMode == false)) { // Overwrite the last character, maybe move position text = text.substring(0, position) - + keypress.getKey().getChar() + + codePointString(keypress.getKey().getChar()) + text.substring(position + 1); if (screenPosition < getWidth() - 1) { - screenPosition += StringUtils.width(text.charAt(position)); - position++; + screenPosition += StringUtils.width(text.codePointAt(position)); + position += Character.charCount(keypress.getKey().getChar()); } } else if ((fixed == false) && (insertMode == false)) { // Overwrite the last character, definitely move position text = text.substring(0, position) - + keypress.getKey().getChar() + + codePointString(keypress.getKey().getChar()) + text.substring(position + 1); - screenPosition += StringUtils.width(text.charAt(position)); - position++; + screenPosition += StringUtils.width(text.codePointAt(position)); + position += Character.charCount(keypress.getKey().getChar()); } else { if (position == text.length()) { // Append this character @@ -407,6 +407,18 @@ public class TField extends TWidget { // TField ----------------------------------------------------------------- // ------------------------------------------------------------------------ + /** + * Convert a char (codepoint) to a string. + * + * @param ch the char + * @return the string + */ + private String codePointString(final int ch) { + StringBuilder sb = new StringBuilder(1); + sb.append(Character.toChars(ch)); + return sb.toString(); + } + /** * Get field background character. * @@ -477,7 +489,7 @@ public class TField extends TWidget { int n = 0; for (int i = 0; i < text.length(); i++) { - n += StringUtils.width(text.charAt(i)); + n += StringUtils.width(text.codePointAt(i)); if (n >= screenPosition) { return i + 1; } @@ -525,15 +537,15 @@ public class TField extends TWidget { */ protected void appendChar(final int ch) { // Append the LAST character - text += ch; - position++; + text += codePointString(ch); + position += Character.charCount(ch); screenPosition += StringUtils.width(ch); assert (position == text.length()); if (fixed) { if (screenPosition >= getWidth()) { - position--; + position -= Character.charCount(ch); screenPosition -= StringUtils.width(ch); } } else { @@ -549,8 +561,9 @@ public class TField extends TWidget { * @param ch char to append */ protected void insertChar(final int ch) { - text = text.substring(0, position) + ((char) ch) + text.substring(position); - position++; + text = text.substring(0, position) + codePointString(ch) + + text.substring(position); + position += Character.charCount(ch); screenPosition += StringUtils.width(ch); if ((screenPosition - windowStart) == getWidth()) { assert (!fixed); -- 2.27.0 From 6b2633acf2b801ac7353785746dd03603195e3ca Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Tue, 13 Aug 2019 20:58:03 -0500 Subject: [PATCH 09/16] #35 fix --- src/jexer/TField.java | 49 ++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/jexer/TField.java b/src/jexer/TField.java index 0a99840..681ce7e 100644 --- a/src/jexer/TField.java +++ b/src/jexer/TField.java @@ -219,12 +219,13 @@ public class TField extends TWidget { if (keypress.equals(kbLeft)) { if (position > 0) { - if (position < text.length()) { - screenPosition -= StringUtils.width(text.codePointAt(position)); + if (position < codePointLength(text)) { + screenPosition -= StringUtils.width(text.codePointBefore(position)); + position -= Character.charCount(text.codePointBefore(position)); } else { screenPosition--; + position--; } - position--; } if (fixed == false) { if ((screenPosition == windowStart) && (windowStart > 0)) { @@ -237,13 +238,13 @@ public class TField extends TWidget { } if (keypress.equals(kbRight)) { - if (position < text.length()) { + if (position < codePointLength(text)) { screenPosition += StringUtils.width(text.codePointAt(position)); - position++; + position += Character.charCount(text.codePointAt(position)); if (fixed == true) { if (screenPosition == getWidth()) { screenPosition--; - position--; + position -= Character.charCount(text.codePointAt(position)); } } else { if ((screenPosition - windowStart) >= getWidth()) { @@ -275,7 +276,7 @@ public class TField extends TWidget { } if (keypress.equals(kbDel)) { - if ((text.length() > 0) && (position < text.length())) { + if ((codePointLength(text) > 0) && (position < codePointLength(text))) { text = text.substring(0, position) + text.substring(position + 1); screenPosition = StringUtils.width(text.substring(0, position)); @@ -286,7 +287,7 @@ public class TField extends TWidget { if (keypress.equals(kbBackspace) || keypress.equals(kbBackspaceDel)) { if (position > 0) { - position--; + position -= Character.charCount(text.codePointBefore(position)); text = text.substring(0, position) + text.substring(position + 1); screenPosition = StringUtils.width(text.substring(0, position)); @@ -309,12 +310,12 @@ public class TField extends TWidget { && !keypress.getKey().isCtrl() ) { // Plain old keystroke, process it - if ((position == text.length()) + if ((position == codePointLength(text)) && (StringUtils.width(text) < getWidth())) { // Append case appendChar(keypress.getKey().getChar()); - } else if ((position < text.length()) + } else if ((position < codePointLength(text)) && (StringUtils.width(text) < getWidth())) { // Overwrite or insert a character @@ -329,7 +330,7 @@ public class TField extends TWidget { // Insert character insertChar(keypress.getKey().getChar()); } - } else if ((position < text.length()) + } else if ((position < codePointLength(text)) && (StringUtils.width(text) >= getWidth())) { // Multiple cases here @@ -352,7 +353,7 @@ public class TField extends TWidget { screenPosition += StringUtils.width(text.codePointAt(position)); position += Character.charCount(keypress.getKey().getChar()); } else { - if (position == text.length()) { + if (position == codePointLength(text)) { // Append this character appendChar(keypress.getKey().getChar()); } else { @@ -419,6 +420,16 @@ public class TField extends TWidget { return sb.toString(); } + /** + * Get the number of codepoints in a string. + * + * @param str the string + * @return the number of codepoints + */ + private int codePointLength(final String str) { + return str.codePointCount(0, str.length()); + } + /** * Get field background character. * @@ -488,7 +499,7 @@ public class TField extends TWidget { } int n = 0; - for (int i = 0; i < text.length(); i++) { + for (int i = 0; i < codePointLength(text); i++) { n += StringUtils.width(text.codePointAt(i)); if (n >= screenPosition) { return i + 1; @@ -496,7 +507,7 @@ public class TField extends TWidget { } // screenPosition exceeds the available text length. throw new IndexOutOfBoundsException("screenPosition " + screenPosition + - " exceeds available text length " + text.length()); + " exceeds available text length " + codePointLength(text)); } /** @@ -541,7 +552,7 @@ public class TField extends TWidget { position += Character.charCount(ch); screenPosition += StringUtils.width(ch); - assert (position == text.length()); + assert (position == codePointLength(text)); if (fixed) { if (screenPosition >= getWidth()) { @@ -586,11 +597,11 @@ public class TField extends TWidget { * adjust the window start to show as much of the field as possible. */ public void end() { - position = text.length(); + position = codePointLength(text); screenPosition = StringUtils.width(text); if (fixed == true) { if (screenPosition >= getWidth()) { - position = text.length() - 1; + position = codePointLength(text) - 1; screenPosition = StringUtils.width(text) - 1; } } else { @@ -610,9 +621,9 @@ public class TField extends TWidget { * the available text */ public void setPosition(final int position) { - if ((position < 0) || (position >= text.length())) { + if ((position < 0) || (position >= codePointLength(text))) { throw new IndexOutOfBoundsException("Max length is " + - StringUtils.width(text) + ", requested position " + position); + codePointLength(text) + ", requested position " + position); } this.position = position; normalizeWindowStart(); -- 2.27.0 From ebb9a1e491ed62be0a1f0d9e5b45fe856d849e07 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Wed, 14 Aug 2019 17:50:04 -0500 Subject: [PATCH 10/16] #35 fixup --- src/jexer/TField.java | 52 +++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/src/jexer/TField.java b/src/jexer/TField.java index 681ce7e..6aaf591 100644 --- a/src/jexer/TField.java +++ b/src/jexer/TField.java @@ -219,13 +219,8 @@ public class TField extends TWidget { if (keypress.equals(kbLeft)) { if (position > 0) { - if (position < codePointLength(text)) { - screenPosition -= StringUtils.width(text.codePointBefore(position)); - position -= Character.charCount(text.codePointBefore(position)); - } else { - screenPosition--; - position--; - } + screenPosition -= StringUtils.width(text.codePointBefore(position)); + position -= Character.charCount(text.codePointBefore(position)); } if (fixed == false) { if ((screenPosition == windowStart) && (windowStart > 0)) { @@ -238,7 +233,7 @@ public class TField extends TWidget { } if (keypress.equals(kbRight)) { - if (position < codePointLength(text)) { + if (position < text.length()) { screenPosition += StringUtils.width(text.codePointAt(position)); position += Character.charCount(text.codePointAt(position)); if (fixed == true) { @@ -247,12 +242,16 @@ public class TField extends TWidget { position -= Character.charCount(text.codePointAt(position)); } } else { - if ((screenPosition - windowStart) >= getWidth()) { + while ((screenPosition - windowStart + + StringUtils.width(text.codePointAt(text.length() - 1))) + > getWidth() + ) { windowStart += StringUtils.width(text.codePointAt( screenToTextPosition(windowStart))); } } } + assert (position <= text.length()); return; } @@ -276,7 +275,7 @@ public class TField extends TWidget { } if (keypress.equals(kbDel)) { - if ((codePointLength(text) > 0) && (position < codePointLength(text))) { + if ((text.length() > 0) && (position < text.length())) { text = text.substring(0, position) + text.substring(position + 1); screenPosition = StringUtils.width(text.substring(0, position)); @@ -310,12 +309,12 @@ public class TField extends TWidget { && !keypress.getKey().isCtrl() ) { // Plain old keystroke, process it - if ((position == codePointLength(text)) + if ((position == text.length()) && (StringUtils.width(text) < getWidth())) { // Append case appendChar(keypress.getKey().getChar()); - } else if ((position < codePointLength(text)) + } else if ((position < text.length()) && (StringUtils.width(text) < getWidth())) { // Overwrite or insert a character @@ -330,7 +329,7 @@ public class TField extends TWidget { // Insert character insertChar(keypress.getKey().getChar()); } - } else if ((position < codePointLength(text)) + } else if ((position < text.length()) && (StringUtils.width(text) >= getWidth())) { // Multiple cases here @@ -353,7 +352,7 @@ public class TField extends TWidget { screenPosition += StringUtils.width(text.codePointAt(position)); position += Character.charCount(keypress.getKey().getChar()); } else { - if (position == codePointLength(text)) { + if (position == text.length()) { // Append this character appendChar(keypress.getKey().getChar()); } else { @@ -417,19 +416,10 @@ public class TField extends TWidget { private String codePointString(final int ch) { StringBuilder sb = new StringBuilder(1); sb.append(Character.toChars(ch)); + assert (Character.charCount(ch) == sb.length()); return sb.toString(); } - /** - * Get the number of codepoints in a string. - * - * @param str the string - * @return the number of codepoints - */ - private int codePointLength(final String str) { - return str.codePointCount(0, str.length()); - } - /** * Get field background character. * @@ -499,7 +489,7 @@ public class TField extends TWidget { } int n = 0; - for (int i = 0; i < codePointLength(text); i++) { + for (int i = 0; i < text.length(); i++) { n += StringUtils.width(text.codePointAt(i)); if (n >= screenPosition) { return i + 1; @@ -507,7 +497,7 @@ public class TField extends TWidget { } // screenPosition exceeds the available text length. throw new IndexOutOfBoundsException("screenPosition " + screenPosition + - " exceeds available text length " + codePointLength(text)); + " exceeds available text length " + text.length()); } /** @@ -552,7 +542,7 @@ public class TField extends TWidget { position += Character.charCount(ch); screenPosition += StringUtils.width(ch); - assert (position == codePointLength(text)); + assert (position == text.length()); if (fixed) { if (screenPosition >= getWidth()) { @@ -597,11 +587,11 @@ public class TField extends TWidget { * adjust the window start to show as much of the field as possible. */ public void end() { - position = codePointLength(text); + position = text.length(); screenPosition = StringUtils.width(text); if (fixed == true) { if (screenPosition >= getWidth()) { - position = codePointLength(text) - 1; + position -= Character.charCount(text.codePointBefore(position)); screenPosition = StringUtils.width(text) - 1; } } else { @@ -621,9 +611,9 @@ public class TField extends TWidget { * the available text */ public void setPosition(final int position) { - if ((position < 0) || (position >= codePointLength(text))) { + if ((position < 0) || (position >= text.length())) { throw new IndexOutOfBoundsException("Max length is " + - codePointLength(text) + ", requested position " + position); + text.length() + ", requested position " + position); } this.position = position; normalizeWindowStart(); -- 2.27.0 From 01fccda08cdbce0c3c0b76239ff685f2550d28da Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Wed, 14 Aug 2019 19:41:07 -0500 Subject: [PATCH 11/16] Update main screenshot --- README.md | 2 +- screenshots/new_demo1.png | Bin 0 -> 22972 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 screenshots/new_demo1.png diff --git a/README.md b/README.md index 7e21e98..fb39aac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ reminiscent of Borland's [Turbo Vision](http://en.wikipedia.org/wiki/Turbo_Vision) system. It looks like this: -![Several Windows Open Including A Terminal](/screenshots/screenshot1.png?raw=true "Several Windows Open Including A Terminal") +![Terminal, Image, Table](/screenshots/new_demo1.png?raw=true "Terminal, Image, Table") Jexer works on both Xterm-like terminals and Swing, and supports images in both Xterm and Swing. On Swing, images are true color: diff --git a/screenshots/new_demo1.png b/screenshots/new_demo1.png new file mode 100644 index 0000000000000000000000000000000000000000..675afeb974a042061013936b11cd816043b29805 GIT binary patch literal 22972 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 Date: Wed, 14 Aug 2019 20:31:27 -0500 Subject: [PATCH 12/16] #35 editor wip --- src/jexer/TEditorWidget.java | 2 +- src/jexer/teditor/Document.java | 2 +- src/jexer/teditor/Highlighter.java | 8 ++++--- src/jexer/teditor/Line.java | 35 ++++++++++++++++++++---------- src/jexer/teditor/Word.java | 15 +++++++------ 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/jexer/TEditorWidget.java b/src/jexer/TEditorWidget.java index 204b575..32263de 100644 --- a/src/jexer/TEditorWidget.java +++ b/src/jexer/TEditorWidget.java @@ -266,7 +266,7 @@ public class TEditorWidget extends TWidget { ) { // Plain old keystroke, process it // TODO: fix document to use ints, not chars - document.addChar((char) keypress.getKey().getChar()); + document.addChar(keypress.getKey().getChar()); alignCursor(); } else { // Pass other keys (tab etc.) on to TWidget diff --git a/src/jexer/teditor/Document.java b/src/jexer/teditor/Document.java index 9b04538..159a95f 100644 --- a/src/jexer/teditor/Document.java +++ b/src/jexer/teditor/Document.java @@ -430,7 +430,7 @@ public class Document { * * @param ch the character to replace or insert */ - public void addChar(final char ch) { + public void addChar(final int ch) { dirty = true; if (overwrite) { lines.get(lineNumber).replaceChar(ch); diff --git a/src/jexer/teditor/Highlighter.java b/src/jexer/teditor/Highlighter.java index 4f60155..a484194 100644 --- a/src/jexer/teditor/Highlighter.java +++ b/src/jexer/teditor/Highlighter.java @@ -69,11 +69,13 @@ public class Highlighter { * @param ch the character * @return true if the word should be split */ - public boolean shouldSplit(final char ch) { + public boolean shouldSplit(final int ch) { // For now, split on punctuation String punctuation = "'\"\\<>{}[]!@#$%^&*();:.,-+/*?"; - if (punctuation.indexOf(ch) != -1) { - return true; + if (ch < 0x100) { + if (punctuation.indexOf((char) ch) != -1) { + return true; + } } return false; } diff --git a/src/jexer/teditor/Line.java b/src/jexer/teditor/Line.java index 965c38f..345cfcd 100644 --- a/src/jexer/teditor/Line.java +++ b/src/jexer/teditor/Line.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.List; import jexer.bits.CellAttributes; +import jexer.bits.StringUtils; /** * A Line represents a single line of text on the screen, as a collection of @@ -145,7 +146,7 @@ public class Line { * @return the number of cells needed to display this line */ public int getDisplayLength() { - int n = rawText.length(); + int n = StringUtils.width(rawText.toString()); // For now just return the raw text length. if (n > 0) { @@ -172,8 +173,9 @@ public class Line { words.clear(); Word word = new Word(this.defaultColor, this.highlighter); words.add(word); - for (int i = 0; i < rawText.length(); i++) { - char ch = rawText.charAt(i); + for (int i = 0; i < rawText.length();) { + int ch = rawText.codePointAt(i); + i += Character.charCount(ch); Word newWord = word.addChar(ch); if (newWord != word) { words.add(newWord); @@ -194,7 +196,7 @@ public class Line { if (cursor == 0) { return false; } - cursor--; + cursor -= StringUtils.width(rawText.codePointAt(cursor - 1)); return true; } @@ -210,7 +212,11 @@ public class Line { if (cursor == getDisplayLength() - 1) { return false; } - cursor++; + if (cursor < getDisplayLength()) { + cursor += StringUtils.width(rawText.codePointAt(cursor)); + } else { + cursor++; + } return true; } @@ -250,7 +256,9 @@ public class Line { assert (words.size() > 0); if (cursor < getDisplayLength()) { - rawText.deleteCharAt(cursor); + for (int i = 0; i < Character.charCount(rawText.charAt(cursor)); i++) { + rawText.deleteCharAt(cursor); + } } // Re-scan the line to determine the new word boundaries. @@ -271,11 +279,11 @@ public class Line { * * @param ch the character to insert */ - public void addChar(final char ch) { + public void addChar(final int ch) { if (cursor < getDisplayLength() - 1) { - rawText.insert(cursor, ch); + rawText.insert(cursor, Character.toChars(ch)); } else { - rawText.append(ch); + rawText.append(Character.toChars(ch)); } scanLine(); cursor++; @@ -286,11 +294,14 @@ public class Line { * * @param ch the character to replace */ - public void replaceChar(final char ch) { + public void replaceChar(final int ch) { if (cursor < getDisplayLength() - 1) { - rawText.setCharAt(cursor, ch); + for (int i = 0; i < Character.charCount(rawText.charAt(cursor)); i++) { + rawText.deleteCharAt(cursor); + } + rawText.insert(cursor, Character.toChars(ch)); } else { - rawText.append(ch); + rawText.append(Character.toChars(ch)); } scanLine(); cursor++; diff --git a/src/jexer/teditor/Word.java b/src/jexer/teditor/Word.java index ffb11aa..eada29c 100644 --- a/src/jexer/teditor/Word.java +++ b/src/jexer/teditor/Word.java @@ -29,6 +29,7 @@ package jexer.teditor; import jexer.bits.CellAttributes; +import jexer.bits.StringUtils; /** * A Word represents text that was entered by the user. It can be either @@ -76,12 +77,12 @@ public class Word { * @param defaultColor the color for unhighlighted text * @param highlighter the highlighter to use */ - public Word(final char ch, final CellAttributes defaultColor, + public Word(final int ch, final CellAttributes defaultColor, final Highlighter highlighter) { this.defaultColor = defaultColor; this.highlighter = highlighter; - text.append(ch); + text.append(Character.toChars(ch)); } /** @@ -139,7 +140,7 @@ public class Word { // TODO: figure out how to handle the tab character. Do we have a // global tab stops list and current word position? - return text.length(); + return StringUtils.width(text.toString()); } /** @@ -184,9 +185,9 @@ public class Word { * @return either this word (if it was added), or a new word that * contains ch */ - public Word addChar(final char ch) { + public Word addChar(final int ch) { if (text.length() == 0) { - text.append(ch); + text.append(Character.toChars(ch)); return this; } @@ -205,14 +206,14 @@ public class Word { && Character.isWhitespace(ch) ) { // Adding to a whitespace word, keep at it. - text.append(ch); + text.append(Character.toChars(ch)); return this; } if (!Character.isWhitespace(text.charAt(0)) && !Character.isWhitespace(ch) ) { // Adding to a non-whitespace word, keep at it. - text.append(ch); + text.append(Character.toChars(ch)); return this; } -- 2.27.0 From 39e863978f5c6de901b05b3ecfaee18aa934663d Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 15 Aug 2019 08:16:06 -0500 Subject: [PATCH 13/16] #35 CJK in editor working --- src/jexer/TEditorWidget.java | 4 +- src/jexer/demos/DemoMainWindow.properties | 6 +- src/jexer/teditor/Document.java | 2 +- src/jexer/teditor/Line.java | 109 +++++++++++++++------- 4 files changed, 83 insertions(+), 38 deletions(-) diff --git a/src/jexer/TEditorWidget.java b/src/jexer/TEditorWidget.java index 32263de..8b105f8 100644 --- a/src/jexer/TEditorWidget.java +++ b/src/jexer/TEditorWidget.java @@ -247,7 +247,9 @@ public class TEditorWidget extends TWidget { } else if (keypress.equals(kbDel)) { document.del(); alignCursor(); - } else if (keypress.equals(kbBackspace)) { + } else if (keypress.equals(kbBackspace) + || keypress.equals(kbBackspaceDel) + ) { document.backspace(); alignTopLine(false); } else if (keypress.equals(kbTab)) { diff --git a/src/jexer/demos/DemoMainWindow.properties b/src/jexer/demos/DemoMainWindow.properties index cdaf79f..dba1cb0 100644 --- a/src/jexer/demos/DemoMainWindow.properties +++ b/src/jexer/demos/DemoMainWindow.properties @@ -18,10 +18,10 @@ editorLabel=Editor window editorButton1=&1 Widget editorButton2=&2 Window ttableLabel=Editable Table -ttableButton1=&3 Widget -ttableButton2=&4 Window +ttableButton1=&4 Widget +ttableButton2=&5 Window textAreaLabel=Text areas -textAreaButton=&Text +textAreaButton=&3 Text treeViewLabel=Tree views treeViewButton=Tree&View terminalLabel=Terminal diff --git a/src/jexer/teditor/Document.java b/src/jexer/teditor/Document.java index 159a95f..5659124 100644 --- a/src/jexer/teditor/Document.java +++ b/src/jexer/teditor/Document.java @@ -413,7 +413,7 @@ public class Document { */ public void enter() { dirty = true; - int cursor = lines.get(lineNumber).getCursor(); + int cursor = lines.get(lineNumber).getRawCursor(); String original = lines.get(lineNumber).getRawString(); String firstLine = original.substring(0, cursor); String secondLine = original.substring(cursor); diff --git a/src/jexer/teditor/Line.java b/src/jexer/teditor/Line.java index 345cfcd..f3168fd 100644 --- a/src/jexer/teditor/Line.java +++ b/src/jexer/teditor/Line.java @@ -60,9 +60,14 @@ public class Line { private Highlighter highlighter = null; /** - * The current cursor position on this line. + * The current edition position on this line. */ - private int cursor = 0; + private int position = 0; + + /** + * The current editing position screen column number. + */ + private int screenPosition = 0; /** * The raw text of this line, what is passed to Word to determine @@ -116,12 +121,21 @@ public class Line { } /** - * Get the current cursor position. + * Get the current cursor position in the text. + * + * @return the cursor position + */ + public int getRawCursor() { + return position; + } + + /** + * Get the current cursor position on screen. * * @return the cursor position */ public int getCursor() { - return cursor; + return screenPosition; } /** @@ -137,7 +151,8 @@ public class Line { throw new IndexOutOfBoundsException("Max length is " + getDisplayLength() + ", requested position " + cursor); } - this.cursor = cursor; + screenPosition = cursor; + position = screenToTextPosition(screenPosition); } /** @@ -148,10 +163,9 @@ public class Line { public int getDisplayLength() { int n = StringUtils.width(rawText.toString()); - // For now just return the raw text length. if (n > 0) { // If we have any visible characters, add one to the display so - // that the cursor is immediately after the data. + // that the position is immediately after the data. return n + 1; } return n; @@ -193,10 +207,11 @@ public class Line { * @return true if the cursor position changed */ public boolean left() { - if (cursor == 0) { + if (position == 0) { return false; } - cursor -= StringUtils.width(rawText.codePointAt(cursor - 1)); + screenPosition -= StringUtils.width(rawText.codePointBefore(position)); + position -= Character.charCount(rawText.codePointBefore(position)); return true; } @@ -209,14 +224,14 @@ public class Line { if (getDisplayLength() == 0) { return false; } - if (cursor == getDisplayLength() - 1) { + if (position == getDisplayLength() - 1) { return false; } - if (cursor < getDisplayLength()) { - cursor += StringUtils.width(rawText.codePointAt(cursor)); - } else { - cursor++; + if (position < rawText.length()) { + screenPosition += StringUtils.width(rawText.codePointAt(position)); + position += Character.charCount(rawText.codePointAt(position)); } + assert (position <= rawText.length()); return true; } @@ -226,8 +241,9 @@ public class Line { * @return true if the cursor position changed */ public boolean home() { - if (cursor > 0) { - cursor = 0; + if (position > 0) { + position = 0; + screenPosition = 0; return true; } return false; @@ -239,11 +255,9 @@ public class Line { * @return true if the cursor position changed */ public boolean end() { - if (cursor != getDisplayLength() - 1) { - cursor = getDisplayLength() - 1; - if (cursor < 0) { - cursor = 0; - } + if (position != getDisplayLength() - 1) { + position = rawText.length(); + screenPosition = StringUtils.width(rawText.toString()); return true; } return false; @@ -255,9 +269,10 @@ public class Line { public void del() { assert (words.size() > 0); - if (cursor < getDisplayLength()) { - for (int i = 0; i < Character.charCount(rawText.charAt(cursor)); i++) { - rawText.deleteCharAt(cursor); + if (position < getDisplayLength()) { + int n = Character.charCount(rawText.codePointAt(position)); + for (int i = 0; i < n; i++) { + rawText.deleteCharAt(position); } } @@ -280,13 +295,14 @@ public class Line { * @param ch the character to insert */ public void addChar(final int ch) { - if (cursor < getDisplayLength() - 1) { - rawText.insert(cursor, Character.toChars(ch)); + if (position < getDisplayLength() - 1) { + rawText.insert(position, Character.toChars(ch)); } else { rawText.append(Character.toChars(ch)); } + position += Character.charCount(ch); + screenPosition += StringUtils.width(ch); scanLine(); - cursor++; } /** @@ -295,16 +311,43 @@ public class Line { * @param ch the character to replace */ public void replaceChar(final int ch) { - if (cursor < getDisplayLength() - 1) { - for (int i = 0; i < Character.charCount(rawText.charAt(cursor)); i++) { - rawText.deleteCharAt(cursor); - } - rawText.insert(cursor, Character.toChars(ch)); + if (position < getDisplayLength() - 1) { + // Replace character + String oldText = rawText.toString(); + rawText = new StringBuilder(oldText.substring(0, position)); + rawText.append(Character.toChars(ch)); + rawText.append(oldText.substring(position + 1)); + screenPosition += StringUtils.width(rawText.codePointAt(position)); + position += Character.charCount(ch); } else { rawText.append(Character.toChars(ch)); + position += Character.charCount(ch); + screenPosition += StringUtils.width(ch); } scanLine(); - cursor++; + } + + /** + * Determine string position from screen position. + * + * @param screenPosition the position on screen + * @return the equivalent position in text + */ + protected int screenToTextPosition(final int screenPosition) { + if (screenPosition == 0) { + return 0; + } + + int n = 0; + for (int i = 0; i < rawText.length(); i++) { + n += StringUtils.width(rawText.codePointAt(i)); + if (n >= screenPosition) { + return i + 1; + } + } + // screenPosition exceeds the available text length. + throw new IndexOutOfBoundsException("screenPosition " + screenPosition + + " exceeds available text length " + rawText.length()); } } -- 2.27.0 From 070fba619e47056c00e53bdb4c7684c38e47dca6 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 15 Aug 2019 17:24:49 -0500 Subject: [PATCH 14/16] Fix status line for non-tiple-buffered case --- src/jexer/backend/SwingTerminal.java | 8 ++++---- src/jexer/tterminal/ECMA48.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java index 65decfa..ce26efe 100644 --- a/src/jexer/backend/SwingTerminal.java +++ b/src/jexer/backend/SwingTerminal.java @@ -1180,7 +1180,7 @@ public class SwingTerminal extends LogicalScreen /* System.err.println("drawGlyph(): " + xPixel + " " + yPixel + " " + cell); - */ + */ BufferedImage image = null; if (cell.isBlink() && !cursorBlinkVisible) { @@ -1348,7 +1348,7 @@ public class SwingTerminal extends LogicalScreen if (bounds != null) { // Only update what is in the bounds xCellMin = textColumn(bounds.x); - xCellMax = textColumn(bounds.x + bounds.width); + xCellMax = textColumn(bounds.x + bounds.width) + 1; if (xCellMax > width) { xCellMax = width; } @@ -1359,7 +1359,7 @@ public class SwingTerminal extends LogicalScreen xCellMin = 0; } yCellMin = textRow(bounds.y); - yCellMax = textRow(bounds.y + bounds.height); + yCellMax = textRow(bounds.y + bounds.height) + 1; if (yCellMax > height) { yCellMax = height; } @@ -1381,7 +1381,7 @@ public class SwingTerminal extends LogicalScreen /* System.err.printf("bounds %s X %d %d Y %d %d\n", bounds, xCellMin, xCellMax, yCellMin, yCellMax); - */ + */ for (int y = yCellMin; y < yCellMax; y++) { for (int x = xCellMin; x < xCellMax; x++) { diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 9ff37a1..93ae3cd 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -6656,19 +6656,19 @@ public class ECMA48 implements Runnable { // 00-17, 19, 1C-1F, 20-7E --> put if (ch <= 0x17) { - sixelParseBuffer.append(ch); + sixelParseBuffer.append((char) ch); return; } if (ch == 0x19) { - sixelParseBuffer.append(ch); + sixelParseBuffer.append((char) ch); return; } if ((ch >= 0x1C) && (ch <= 0x1F)) { - sixelParseBuffer.append(ch); + sixelParseBuffer.append((char) ch); return; } if ((ch >= 0x20) && (ch <= 0x7E)) { - sixelParseBuffer.append(ch); + sixelParseBuffer.append((char) ch); return; } -- 2.27.0 From 9cfd6704c01ba8c8dbd0a7c786902326e6db84bb Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 15 Aug 2019 19:11:08 -0500 Subject: [PATCH 15/16] #48 additional windowops --- src/jexer/tterminal/ECMA48.java | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/jexer/tterminal/ECMA48.java b/src/jexer/tterminal/ECMA48.java index 93ae3cd..7286c7d 100644 --- a/src/jexer/tterminal/ECMA48.java +++ b/src/jexer/tterminal/ECMA48.java @@ -4721,10 +4721,26 @@ public class ECMA48 implements Runnable { int i = getCsiParam(0, 0); if (!xtermPrivateModeFlag) { - if (i == 14) { - // Report xterm window in pixels as CSI 4 ; height ; width t + switch (i) { + case 14: + // Report xterm text area size in pixels as CSI 4 ; height ; + // width t writeRemote(String.format("\033[4;%d;%dt", textHeight * height, textWidth * width)); + break; + case 16: + // Report character size in pixels as CSI 6 ; height ; width + // t + writeRemote(String.format("\033[6;%d;%dt", textHeight, + textWidth)); + break; + case 18: + // Report the text are size in characters as CSI 8 ; height ; + // width t + writeRemote(String.format("\033[8;%d;%dt", height, width)); + break; + default: + break; } } } -- 2.27.0 From 5dccc93977b5f3cbde6e791404e5e43ae540ff54 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 15 Aug 2019 19:18:33 -0500 Subject: [PATCH 16/16] vertical bar cursor option --- src/jexer/backend/SwingTerminal.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/jexer/backend/SwingTerminal.java b/src/jexer/backend/SwingTerminal.java index ce26efe..283d1ae 100644 --- a/src/jexer/backend/SwingTerminal.java +++ b/src/jexer/backend/SwingTerminal.java @@ -117,7 +117,12 @@ public class SwingTerminal extends LogicalScreen /** * Use an outlined block for the cursor. */ - OUTLINE + OUTLINE, + + /** + * Use a vertical bar for the cursor. + */ + VERTICAL_BAR, } // ------------------------------------------------------------------------ @@ -648,6 +653,8 @@ public class SwingTerminal extends LogicalScreen cursorStyle = CursorStyle.OUTLINE; } else if (cursorStyleString.equals("block")) { cursorStyle = CursorStyle.BLOCK; + } else if (cursorStyleString.equals("verticalbar")) { + cursorStyle = CursorStyle.VERTICAL_BAR; } // Pull the system property for triple buffering. @@ -1302,6 +1309,9 @@ public class SwingTerminal extends LogicalScreen case OUTLINE: gr.drawRect(xPixel, yPixel, cursorWidth - 1, textHeight - 1); break; + case VERTICAL_BAR: + gr.fillRect(xPixel, yPixel, 2, textHeight); + break; } } } -- 2.27.0