2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
31 import java
.awt
.image
.BufferedImage
;
33 import jexer
.backend
.GlyphMaker
;
34 import jexer
.bits
.Cell
;
35 import jexer
.bits
.CellAttributes
;
36 import jexer
.bits
.GraphicsChars
;
37 import jexer
.bits
.StringUtils
;
40 * A logical screen composed of a 2D array of Cells.
42 public class LogicalScreen
implements Screen
{
44 // ------------------------------------------------------------------------
45 // Variables --------------------------------------------------------------
46 // ------------------------------------------------------------------------
49 * Width of the visible window.
54 * Height of the visible window.
59 * Drawing offset for x.
64 * Drawing offset for y.
69 * Ignore anything drawn right of clipRight.
71 private int clipRight
;
74 * Ignore anything drawn below clipBottom.
76 private int clipBottom
;
79 * Ignore anything drawn left of clipLeft.
84 * Ignore anything drawn above clipTop.
89 * The physical screen last sent out on flush().
91 protected Cell
[][] physical
;
94 * The logical screen being rendered to.
96 protected Cell
[][] logical
;
99 * Set if the user explicitly wants to redraw everything starting with a
100 * ECMATerminal.clearAll().
102 protected boolean reallyCleared
;
105 * If true, the cursor is visible and should be placed onscreen at
106 * (cursorX, cursorY) during a call to flushPhysical().
108 protected boolean cursorVisible
;
111 * Cursor X position if visible.
113 protected int cursorX
;
116 * Cursor Y position if visible.
118 protected int cursorY
;
121 * The last used height of a character cell in pixels, only used for
124 private int lastTextHeight
= -1;
127 * The glyph drawer for full-width chars.
129 private GlyphMaker glyphMaker
= null;
131 // ------------------------------------------------------------------------
132 // Constructors -----------------------------------------------------------
133 // ------------------------------------------------------------------------
136 * Public constructor. Sets everything to not-bold, white-on-black.
138 protected LogicalScreen() {
145 reallocate(width
, height
);
148 // ------------------------------------------------------------------------
149 // Screen -----------------------------------------------------------------
150 // ------------------------------------------------------------------------
153 * Get the width of a character cell in pixels.
155 * @return the width in pixels of a character cell
157 public int getTextWidth() {
158 // Default width is 16 pixels.
163 * Get the height of a character cell in pixels.
165 * @return the height in pixels of a character cell
167 public int getTextHeight() {
168 // Default height is 20 pixels.
173 * Set drawing offset for x.
175 * @param offsetX new drawing offset
177 public final void setOffsetX(final int offsetX
) {
178 this.offsetX
= offsetX
;
182 * Set drawing offset for y.
184 * @param offsetY new drawing offset
186 public final void setOffsetY(final int offsetY
) {
187 this.offsetY
= offsetY
;
191 * Get right drawing clipping boundary.
193 * @return drawing boundary
195 public final int getClipRight() {
200 * Set right drawing clipping boundary.
202 * @param clipRight new boundary
204 public final void setClipRight(final int clipRight
) {
205 this.clipRight
= clipRight
;
209 * Get bottom drawing clipping boundary.
211 * @return drawing boundary
213 public final int getClipBottom() {
218 * Set bottom drawing clipping boundary.
220 * @param clipBottom new boundary
222 public final void setClipBottom(final int clipBottom
) {
223 this.clipBottom
= clipBottom
;
227 * Get left drawing clipping boundary.
229 * @return drawing boundary
231 public final int getClipLeft() {
236 * Set left drawing clipping boundary.
238 * @param clipLeft new boundary
240 public final void setClipLeft(final int clipLeft
) {
241 this.clipLeft
= clipLeft
;
245 * Get top drawing clipping boundary.
247 * @return drawing boundary
249 public final int getClipTop() {
254 * Set top drawing clipping boundary.
256 * @param clipTop new boundary
258 public final void setClipTop(final int clipTop
) {
259 this.clipTop
= clipTop
;
265 * @return if true, the logical screen is not in sync with the physical
268 public final boolean isDirty() {
269 for (int x
= 0; x
< width
; x
++) {
270 for (int y
= 0; y
< height
; y
++) {
271 if (!logical
[x
][y
].equals(physical
[x
][y
])) {
274 if (logical
[x
][y
].isBlink()) {
275 // Blinking screens are always dirty. There is
276 // opportunity for a Netscape blink tag joke here...
286 * Get the attributes at one location.
288 * @param x column coordinate. 0 is the left-most column.
289 * @param y row coordinate. 0 is the top-most row.
290 * @return attributes at (x, y)
292 public final CellAttributes
getAttrXY(final int x
, final int y
) {
293 CellAttributes attr
= new CellAttributes();
294 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
295 attr
.setTo(logical
[x
][y
]);
301 * Get the cell at one location.
303 * @param x column coordinate. 0 is the left-most column.
304 * @param y row coordinate. 0 is the top-most row.
305 * @return the character + attributes
307 public Cell
getCharXY(final int x
, final int y
) {
308 Cell cell
= new Cell();
309 if ((x
>= 0) && (x
< width
) && (y
>= 0) && (y
< height
)) {
310 cell
.setTo(logical
[x
][y
]);
316 * Set the attributes at one location.
318 * @param x column coordinate. 0 is the left-most column.
319 * @param y row coordinate. 0 is the top-most row.
320 * @param attr attributes to use (bold, foreColor, backColor)
322 public final void putAttrXY(final int x
, final int y
,
323 final CellAttributes attr
) {
325 putAttrXY(x
, y
, attr
, true);
329 * Set the attributes at one location.
331 * @param x column coordinate. 0 is the left-most column.
332 * @param y row coordinate. 0 is the top-most row.
333 * @param attr attributes to use (bold, foreColor, backColor)
334 * @param clip if true, honor clipping/offset
336 public final void putAttrXY(final int x
, final int y
,
337 final CellAttributes attr
, final boolean clip
) {
354 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
355 logical
[X
][Y
].setTo(attr
);
357 // If this happens to be the cursor position, make the position
359 if ((cursorX
== X
) && (cursorY
== Y
)) {
360 physical
[cursorX
][cursorY
].unset();
361 unsetImageRow(cursorY
);
367 * Fill the entire screen with one character with attributes.
369 * @param ch character to draw
370 * @param attr attributes to use (bold, foreColor, backColor)
372 public final void putAll(final int ch
, final CellAttributes attr
) {
374 for (int x
= 0; x
< width
; x
++) {
375 for (int y
= 0; y
< height
; y
++) {
376 putCharXY(x
, y
, ch
, attr
);
382 * Render one character with attributes.
384 * @param x column coordinate. 0 is the left-most column.
385 * @param y row coordinate. 0 is the top-most row.
386 * @param ch character + attributes to draw
388 public final void putCharXY(final int x
, final int y
, final Cell ch
) {
397 if ((StringUtils
.width(ch
.getChar()) == 2) && (!ch
.isImage())) {
398 putFullwidthCharXY(x
, y
, ch
);
405 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
407 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
409 // Do not put control characters on the display
411 assert (ch
.getChar() >= 0x20);
412 assert (ch
.getChar() != 0x7F);
414 logical
[X
][Y
].setTo(ch
);
416 // If this happens to be the cursor position, make the position
418 if ((cursorX
== X
) && (cursorY
== Y
)) {
419 physical
[cursorX
][cursorY
].unset();
420 unsetImageRow(cursorY
);
426 * Render one character with attributes.
428 * @param x column coordinate. 0 is the left-most column.
429 * @param y row coordinate. 0 is the top-most row.
430 * @param ch character to draw
431 * @param attr attributes to use (bold, foreColor, backColor)
433 public final void putCharXY(final int x
, final int y
, final int ch
,
434 final CellAttributes attr
) {
444 if (StringUtils
.width(ch
) == 2) {
445 putFullwidthCharXY(x
, y
, ch
, attr
);
452 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
454 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
456 // Do not put control characters on the display
460 logical
[X
][Y
].setTo(attr
);
461 logical
[X
][Y
].setChar(ch
);
463 // If this happens to be the cursor position, make the position
465 if ((cursorX
== X
) && (cursorY
== Y
)) {
466 physical
[cursorX
][cursorY
].unset();
467 unsetImageRow(cursorY
);
473 * Render one character without changing the underlying attributes.
475 * @param x column coordinate. 0 is the left-most column.
476 * @param y row coordinate. 0 is the top-most row.
477 * @param ch character to draw
479 public final void putCharXY(final int x
, final int y
, final int ch
) {
488 if (StringUtils
.width(ch
) == 2) {
489 putFullwidthCharXY(x
, y
, ch
);
496 // System.err.printf("putCharXY: %d, %d, %c\n", X, Y, ch);
498 if ((X
>= 0) && (X
< width
) && (Y
>= 0) && (Y
< height
)) {
499 logical
[X
][Y
].setChar(ch
);
501 // If this happens to be the cursor position, make the position
503 if ((cursorX
== X
) && (cursorY
== Y
)) {
504 physical
[cursorX
][cursorY
].unset();
505 unsetImageRow(cursorY
);
511 * Render a string. Does not wrap if the string exceeds the line.
513 * @param x column coordinate. 0 is the left-most column.
514 * @param y row coordinate. 0 is the top-most row.
515 * @param str string to draw
516 * @param attr attributes to use (bold, foreColor, backColor)
518 public final void putStringXY(final int x
, final int y
, final String str
,
519 final CellAttributes attr
) {
522 for (int j
= 0; j
< str
.length();) {
523 int ch
= str
.codePointAt(j
);
524 j
+= Character
.charCount(ch
);
525 putCharXY(i
, y
, ch
, attr
);
526 i
+= StringUtils
.width(ch
);
534 * Render a string without changing the underlying attribute. Does not
535 * wrap if the string exceeds the line.
537 * @param x column coordinate. 0 is the left-most column.
538 * @param y row coordinate. 0 is the top-most row.
539 * @param str string to draw
541 public final void putStringXY(final int x
, final int y
, final String str
) {
544 for (int j
= 0; j
< str
.length();) {
545 int ch
= str
.codePointAt(j
);
546 j
+= Character
.charCount(ch
);
548 i
+= StringUtils
.width(ch
);
556 * Draw a vertical line from (x, y) to (x, y + n).
558 * @param x column coordinate. 0 is the left-most column.
559 * @param y row coordinate. 0 is the top-most row.
560 * @param n number of characters to draw
561 * @param ch character to draw
562 * @param attr attributes to use (bold, foreColor, backColor)
564 public final void vLineXY(final int x
, final int y
, final int n
,
565 final int ch
, final CellAttributes attr
) {
567 for (int i
= y
; i
< y
+ n
; i
++) {
568 putCharXY(x
, i
, ch
, attr
);
573 * Draw a horizontal line from (x, y) to (x + n, y).
575 * @param x column coordinate. 0 is the left-most column.
576 * @param y row coordinate. 0 is the top-most row.
577 * @param n number of characters to draw
578 * @param ch character to draw
579 * @param attr attributes to use (bold, foreColor, backColor)
581 public final void hLineXY(final int x
, final int y
, final int n
,
582 final int ch
, final CellAttributes attr
) {
584 for (int i
= x
; i
< x
+ n
; i
++) {
585 putCharXY(i
, y
, ch
, attr
);
590 * Change the width. Everything on-screen will be destroyed and must be
593 * @param width new screen width
595 public final synchronized void setWidth(final int width
) {
596 reallocate(width
, this.height
);
600 * Change the height. Everything on-screen will be destroyed and must be
603 * @param height new screen height
605 public final synchronized void setHeight(final int height
) {
606 reallocate(this.width
, height
);
610 * Change the width and height. Everything on-screen will be destroyed
611 * and must be redrawn.
613 * @param width new screen width
614 * @param height new screen height
616 public final void setDimensions(final int width
, final int height
) {
617 reallocate(width
, height
);
622 * Resize the physical screen to match the logical screen dimensions.
624 public void resizeToScreen() {
625 // Subclasses are expected to override this.
631 * @return current screen height
633 public final synchronized int getHeight() {
640 * @return current screen width
642 public final synchronized int getWidth() {
647 * Reset screen to not-bold, white-on-black. Also flushes the offset and
650 public final synchronized void reset() {
651 for (int row
= 0; row
< height
; row
++) {
652 for (int col
= 0; col
< width
; col
++) {
653 logical
[col
][row
].reset();
660 * Flush the offset and clip variables.
662 public final void resetClipping() {
672 * Clear the logical screen.
674 public final void clear() {
679 * Draw a box with a border and empty background.
681 * @param left left column of box. 0 is the left-most column.
682 * @param top top row of the box. 0 is the top-most row.
683 * @param right right column of box
684 * @param bottom bottom row of the box
685 * @param border attributes to use for the border
686 * @param background attributes to use for the background
688 public final void drawBox(final int left
, final int top
,
689 final int right
, final int bottom
,
690 final CellAttributes border
, final CellAttributes background
) {
692 drawBox(left
, top
, right
, bottom
, border
, background
, 1, false);
696 * Draw a box with a border and empty background.
698 * @param left left column of box. 0 is the left-most column.
699 * @param top top row of the box. 0 is the top-most row.
700 * @param right right column of box
701 * @param bottom bottom row of the box
702 * @param border attributes to use for the border
703 * @param background attributes to use for the background
704 * @param borderType if 1, draw a single-line border; if 2, draw a
705 * double-line border; if 3, draw double-line top/bottom edges and
706 * single-line left/right edges (like Qmodem)
707 * @param shadow if true, draw a "shadow" on the box
709 public final void drawBox(final int left
, final int top
,
710 final int right
, final int bottom
,
711 final CellAttributes border
, final CellAttributes background
,
712 final int borderType
, final boolean shadow
) {
714 int boxWidth
= right
- left
;
715 int boxHeight
= bottom
- top
;
724 switch (borderType
) {
726 cTopLeft
= GraphicsChars
.ULCORNER
;
727 cTopRight
= GraphicsChars
.URCORNER
;
728 cBottomLeft
= GraphicsChars
.LLCORNER
;
729 cBottomRight
= GraphicsChars
.LRCORNER
;
730 cHSide
= GraphicsChars
.SINGLE_BAR
;
731 cVSide
= GraphicsChars
.WINDOW_SIDE
;
735 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP_DOUBLE
;
736 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP_DOUBLE
;
737 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM_DOUBLE
;
738 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM_DOUBLE
;
739 cHSide
= GraphicsChars
.DOUBLE_BAR
;
740 cVSide
= GraphicsChars
.WINDOW_SIDE_DOUBLE
;
744 cTopLeft
= GraphicsChars
.WINDOW_LEFT_TOP
;
745 cTopRight
= GraphicsChars
.WINDOW_RIGHT_TOP
;
746 cBottomLeft
= GraphicsChars
.WINDOW_LEFT_BOTTOM
;
747 cBottomRight
= GraphicsChars
.WINDOW_RIGHT_BOTTOM
;
748 cHSide
= GraphicsChars
.WINDOW_TOP
;
749 cVSide
= GraphicsChars
.WINDOW_SIDE
;
752 throw new IllegalArgumentException("Invalid border type: "
756 // Place the corner characters
757 putCharXY(left
, top
, cTopLeft
, border
);
758 putCharXY(left
+ boxWidth
- 1, top
, cTopRight
, border
);
759 putCharXY(left
, top
+ boxHeight
- 1, cBottomLeft
, border
);
760 putCharXY(left
+ boxWidth
- 1, top
+ boxHeight
- 1, cBottomRight
,
763 // Draw the box lines
764 hLineXY(left
+ 1, top
, boxWidth
- 2, cHSide
, border
);
765 vLineXY(left
, top
+ 1, boxHeight
- 2, cVSide
, border
);
766 hLineXY(left
+ 1, top
+ boxHeight
- 1, boxWidth
- 2, cHSide
, border
);
767 vLineXY(left
+ boxWidth
- 1, top
+ 1, boxHeight
- 2, cVSide
, border
);
769 // Fill in the interior background
770 for (int i
= 1; i
< boxHeight
- 1; i
++) {
771 hLineXY(1 + left
, i
+ top
, boxWidth
- 2, ' ', background
);
776 drawBoxShadow(left
, top
, right
, bottom
);
783 * @param left left column of box. 0 is the left-most column.
784 * @param top top row of the box. 0 is the top-most row.
785 * @param right right column of box
786 * @param bottom bottom row of the box
788 public final void drawBoxShadow(final int left
, final int top
,
789 final int right
, final int bottom
) {
793 int boxWidth
= right
- left
;
794 int boxHeight
= bottom
- top
;
795 CellAttributes shadowAttr
= new CellAttributes();
797 // Shadows do not honor clipping but they DO honor offset.
798 int oldClipRight
= clipRight
;
799 int oldClipBottom
= clipBottom
;
800 // When offsetX or offsetY go negative, we need to increase the clip
802 clipRight
= width
- offsetX
;
803 clipBottom
= height
- offsetY
;
805 for (int i
= 0; i
< boxHeight
; i
++) {
806 Cell cell
= getCharXY(offsetX
+ boxLeft
+ boxWidth
,
807 offsetY
+ boxTop
+ 1 + i
);
808 if (cell
.getWidth() == Cell
.Width
.SINGLE
) {
809 putAttrXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, shadowAttr
);
811 putCharXY(boxLeft
+ boxWidth
, boxTop
+ 1 + i
, ' ', shadowAttr
);
813 cell
= getCharXY(offsetX
+ boxLeft
+ boxWidth
+ 1,
814 offsetY
+ boxTop
+ 1 + i
);
815 if (cell
.getWidth() == Cell
.Width
.SINGLE
) {
816 putAttrXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, shadowAttr
);
818 putCharXY(boxLeft
+ boxWidth
+ 1, boxTop
+ 1 + i
, ' ',
822 for (int i
= 0; i
< boxWidth
; i
++) {
823 Cell cell
= getCharXY(offsetX
+ boxLeft
+ 2 + i
,
824 offsetY
+ boxTop
+ boxHeight
);
825 if (cell
.getWidth() == Cell
.Width
.SINGLE
) {
826 putAttrXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, shadowAttr
);
828 putCharXY(boxLeft
+ 2 + i
, boxTop
+ boxHeight
, ' ', shadowAttr
);
831 clipRight
= oldClipRight
;
832 clipBottom
= oldClipBottom
;
836 * Default implementation does nothing.
838 public void flushPhysical() {}
841 * Put the cursor at (x,y).
843 * @param visible if true, the cursor should be visible
844 * @param x column coordinate to put the cursor on
845 * @param y row coordinate to put the cursor on
847 public void putCursor(final boolean visible
, final int x
, final int y
) {
850 && (cursorY
<= height
- 1)
851 && (cursorX
<= width
- 1)
853 // Make the current cursor position dirty
854 physical
[cursorX
][cursorY
].unset();
855 unsetImageRow(cursorY
);
858 cursorVisible
= visible
;
866 public final void hideCursor() {
867 cursorVisible
= false;
871 * Get the cursor visibility.
873 * @return true if the cursor is visible
875 public boolean isCursorVisible() {
876 return cursorVisible
;
880 * Get the cursor X position.
882 * @return the cursor x column position
884 public int getCursorX() {
889 * Get the cursor Y position.
891 * @return the cursor y row position
893 public int getCursorY() {
898 * Set the window title. Default implementation does nothing.
900 * @param title the new title
902 public void setTitle(final String title
) {}
904 // ------------------------------------------------------------------------
905 // LogicalScreen ----------------------------------------------------------
906 // ------------------------------------------------------------------------
909 * Reallocate screen buffers.
911 * @param width new width
912 * @param height new height
914 private synchronized void reallocate(final int width
, final int height
) {
915 if (logical
!= null) {
916 for (int row
= 0; row
< this.height
; row
++) {
917 for (int col
= 0; col
< this.width
; col
++) {
918 logical
[col
][row
] = null;
923 logical
= new Cell
[width
][height
];
924 if (physical
!= null) {
925 for (int row
= 0; row
< this.height
; row
++) {
926 for (int col
= 0; col
< this.width
; col
++) {
927 physical
[col
][row
] = null;
932 physical
= new Cell
[width
][height
];
934 for (int row
= 0; row
< height
; row
++) {
935 for (int col
= 0; col
< width
; col
++) {
936 physical
[col
][row
] = new Cell();
937 logical
[col
][row
] = new Cell();
942 this.height
= height
;
949 reallyCleared
= true;
953 * Clear the physical screen.
955 public final void clearPhysical() {
956 for (int row
= 0; row
< height
; row
++) {
957 for (int col
= 0; col
< width
; col
++) {
958 physical
[col
][row
].unset();
964 * Unset every image cell on one row of the physical screen, forcing
965 * images on that row to be redrawn.
967 * @param y row coordinate. 0 is the top-most row.
969 public final void unsetImageRow(final int y
) {
970 if ((y
< 0) || (y
>= height
)) {
973 for (int x
= 0; x
< width
; x
++) {
974 if (logical
[x
][y
].isImage()) {
975 physical
[x
][y
].unset();
981 * Render one fullwidth cell.
983 * @param x column coordinate. 0 is the left-most column.
984 * @param y row coordinate. 0 is the top-most row.
985 * @param cell the cell to draw
987 public final void putFullwidthCharXY(final int x
, final int y
,
990 int cellWidth
= getTextWidth();
991 int cellHeight
= getTextHeight();
993 if (lastTextHeight
!= cellHeight
) {
994 glyphMaker
= GlyphMaker
.getInstance(cellHeight
);
995 lastTextHeight
= cellHeight
;
997 BufferedImage image
= glyphMaker
.getImage(cell
, cellWidth
* 2,
999 BufferedImage leftImage
= image
.getSubimage(0, 0, cellWidth
,
1001 BufferedImage rightImage
= image
.getSubimage(cellWidth
, 0, cellWidth
,
1004 Cell left
= new Cell(cell
);
1005 left
.setImage(leftImage
);
1006 left
.setWidth(Cell
.Width
.LEFT
);
1007 putCharXY(x
, y
, left
);
1009 Cell right
= new Cell(cell
);
1010 right
.setImage(rightImage
);
1011 right
.setWidth(Cell
.Width
.RIGHT
);
1012 putCharXY(x
+ 1, y
, right
);
1016 * Render one fullwidth character with attributes.
1018 * @param x column coordinate. 0 is the left-most column.
1019 * @param y row coordinate. 0 is the top-most row.
1020 * @param ch character to draw
1021 * @param attr attributes to use (bold, foreColor, backColor)
1023 public final void putFullwidthCharXY(final int x
, final int y
,
1024 final int ch
, final CellAttributes attr
) {
1026 Cell cell
= new Cell(ch
, attr
);
1027 putFullwidthCharXY(x
, y
, cell
);
1031 * Render one fullwidth character with attributes.
1033 * @param x column coordinate. 0 is the left-most column.
1034 * @param y row coordinate. 0 is the top-most row.
1035 * @param ch character to draw
1037 public final void putFullwidthCharXY(final int x
, final int y
,
1040 Cell cell
= new Cell(ch
);
1041 cell
.setAttr(getAttrXY(x
, y
));
1042 putFullwidthCharXY(x
, y
, cell
);