#35 CJK font wip
[nikiroo-utils.git] / src / jexer / bits / Cell.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.bits;
30
31 import java.awt.Color;
32 import java.awt.image.BufferedImage;
33
34 /**
35 * This class represents a single text cell or bit of image on the screen.
36 */
37 public final class Cell extends CellAttributes {
38
39 // ------------------------------------------------------------------------
40 // Constants --------------------------------------------------------------
41 // ------------------------------------------------------------------------
42
43 /**
44 * How this cell needs to be displayed if it is part of a larger glyph.
45 */
46 public enum Width {
47 /**
48 * This cell is an entire glyph on its own.
49 */
50 SINGLE,
51
52 /**
53 * This cell is the left half of a wide glyph.
54 */
55 LEFT,
56
57 /**
58 * This cell is the right half of a wide glyph.
59 */
60 RIGHT,
61 }
62
63 /**
64 * The special "this cell is unset" (null) value. This is the Unicode
65 * "not a character" value.
66 */
67 private static final char UNSET_VALUE = (char) 65535;
68
69 // ------------------------------------------------------------------------
70 // Variables --------------------------------------------------------------
71 // ------------------------------------------------------------------------
72
73 /**
74 * The character at this cell.
75 */
76 private char ch;
77
78 /**
79 * The display width of this cell.
80 */
81 private Width width = Width.SINGLE;
82
83 /**
84 * The image at this cell.
85 */
86 private BufferedImage image = null;
87
88 /**
89 * The image at this cell, inverted.
90 */
91 private BufferedImage invertedImage = null;
92
93 /**
94 * The background color used for the area the image portion might not
95 * cover.
96 */
97 private Color background = null;
98
99 /**
100 * hashCode() needs to call image.hashCode(), which can get quite
101 * expensive.
102 */
103 private int imageHashCode = 0;
104
105 /**
106 * hashCode() needs to call background.hashCode(), which can get quite
107 * expensive.
108 */
109 private int backgroundHashCode = 0;
110
111 // ------------------------------------------------------------------------
112 // Constructors -----------------------------------------------------------
113 // ------------------------------------------------------------------------
114
115 /**
116 * Public constructor sets default values of the cell to blank.
117 *
118 * @see #isBlank()
119 * @see #reset()
120 */
121 public Cell() {
122 reset();
123 }
124
125 /**
126 * Public constructor sets the character. Attributes are the same as
127 * default.
128 *
129 * @param ch character to set to
130 * @see #reset()
131 */
132 public Cell(final char ch) {
133 reset();
134 this.ch = ch;
135 }
136
137 // ------------------------------------------------------------------------
138 // Cell -------------------------------------------------------------------
139 // ------------------------------------------------------------------------
140
141 /**
142 * Set the image data for this cell.
143 *
144 * @param image the image for this cell
145 */
146 public void setImage(final BufferedImage image) {
147 this.image = image;
148 imageHashCode = image.hashCode();
149 width = Width.SINGLE;
150 }
151
152 /**
153 * Get the image data for this cell.
154 *
155 * @return the image for this cell
156 */
157 public BufferedImage getImage() {
158 if (invertedImage != null) {
159 return invertedImage;
160 }
161 return image;
162 }
163
164 /**
165 * Get the bitmap image background color for this cell.
166 *
167 * @return the bitmap image background color
168 */
169 public Color getBackground() {
170 return background;
171 }
172
173 /**
174 * If true, this cell has image data.
175 *
176 * @return true if this cell is an image rather than a character with
177 * attributes
178 */
179 public boolean isImage() {
180 if (image != null) {
181 return true;
182 }
183 return false;
184 }
185
186 /**
187 * Restore the image in this cell to its normal version, if it has one.
188 */
189 public void restoreImage() {
190 invertedImage = null;
191 }
192
193 /**
194 * If true, this cell has image data, and that data is inverted.
195 *
196 * @return true if this cell is an image rather than a character with
197 * attributes, and the data is inverted
198 */
199 public boolean isInvertedImage() {
200 if ((image != null) && (invertedImage != null)) {
201 return true;
202 }
203 return false;
204 }
205
206 /**
207 * Invert the image in this cell, if it has one.
208 */
209 public void invertImage() {
210 if (image == null) {
211 return;
212 }
213 if (invertedImage == null) {
214 invertedImage = new BufferedImage(image.getWidth(),
215 image.getHeight(), BufferedImage.TYPE_INT_ARGB);
216
217 int [] rgbArray = image.getRGB(0, 0,
218 image.getWidth(), image.getHeight(), null, 0, image.getWidth());
219
220 for (int i = 0; i < rgbArray.length; i++) {
221 // Set the colors to fully inverted.
222 if (rgbArray[i] != 0x00FFFFFF) {
223 rgbArray[i] ^= 0x00FFFFFF;
224 }
225 // Also set alpha to non-transparent.
226 rgbArray[i] |= 0xFF000000;
227 }
228 invertedImage.setRGB(0, 0, image.getWidth(), image.getHeight(),
229 rgbArray, 0, image.getWidth());
230 }
231 }
232
233 /**
234 * Getter for cell character.
235 *
236 * @return cell character
237 */
238 public char getChar() {
239 return ch;
240 }
241
242 /**
243 * Setter for cell character.
244 *
245 * @param ch new cell character
246 */
247 public void setChar(final char ch) {
248 this.ch = ch;
249 }
250
251 /**
252 * Getter for cell width.
253 *
254 * @return Width.SINGLE, Width.LEFT, or Width.RIGHT
255 */
256 public Width getWidth() {
257 return width;
258 }
259
260 /**
261 * Setter for cell width.
262 *
263 * @param ch new cell width, one of Width.SINGLE, Width.LEFT, or
264 * Width.RIGHT
265 */
266 public void setWidth(final Width width) {
267 this.width = width;
268 }
269
270 /**
271 * Reset this cell to a blank.
272 */
273 @Override
274 public void reset() {
275 super.reset();
276 ch = ' ';
277 width = Width.SINGLE;
278 image = null;
279 imageHashCode = 0;
280 invertedImage = null;
281 background = Color.BLACK;
282 backgroundHashCode = 0;
283 }
284
285 /**
286 * UNset this cell. It will not be equal to any other cell until it has
287 * been assigned attributes and a character.
288 */
289 public void unset() {
290 super.reset();
291 ch = UNSET_VALUE;
292 width = Width.SINGLE;
293 image = null;
294 imageHashCode = 0;
295 invertedImage = null;
296 background = Color.BLACK;
297 backgroundHashCode = 0;
298 }
299
300 /**
301 * Check to see if this cell has default attributes: white foreground,
302 * black background, no bold/blink/reverse/underline/protect, and a
303 * character value of ' ' (space).
304 *
305 * @return true if this cell has default attributes.
306 */
307 public boolean isBlank() {
308 if ((ch == UNSET_VALUE) || (image != null)) {
309 return false;
310 }
311 if ((getForeColor().equals(Color.WHITE))
312 && (getBackColor().equals(Color.BLACK))
313 && !isBold()
314 && !isBlink()
315 && !isReverse()
316 && !isUnderline()
317 && !isProtect()
318 && !isRGB()
319 && !isImage()
320 && (width == Width.SINGLE)
321 && (ch == ' ')
322 ) {
323 return true;
324 }
325
326 return false;
327 }
328
329 /**
330 * Comparison check. All fields must match to return true.
331 *
332 * @param rhs another Cell instance
333 * @return true if all fields are equal
334 */
335 @Override
336 public boolean equals(final Object rhs) {
337 if (!(rhs instanceof Cell)) {
338 return false;
339 }
340
341 Cell that = (Cell) rhs;
342
343 // Unsetted cells can never be equal.
344 if ((ch == UNSET_VALUE) || (that.ch == UNSET_VALUE)) {
345 return false;
346 }
347
348 // If this or rhs has an image and the other doesn't, these are not
349 // equal.
350 if ((image != null) && (that.image == null)) {
351 return false;
352 }
353 if ((image == null) && (that.image != null)) {
354 return false;
355 }
356 // If this and rhs have images, both must match.
357 if ((image != null) && (that.image != null)) {
358 if ((invertedImage == null) && (that.invertedImage != null)) {
359 return false;
360 }
361 if ((invertedImage != null) && (that.invertedImage == null)) {
362 return false;
363 }
364 // Either both objects have their image inverted, or neither do.
365 // Now if the images are identical the cells are the same
366 // visually.
367 if (image.equals(that.image)
368 && (background.equals(that.background))
369 ) {
370 return true;
371 } else {
372 return false;
373 }
374 }
375
376 // Normal case: character and attributes must match.
377 if ((ch == that.ch) && (width == that.width)) {
378 return super.equals(rhs);
379 }
380 return false;
381 }
382
383 /**
384 * Hashcode uses all fields in equals().
385 *
386 * @return the hash
387 */
388 @Override
389 public int hashCode() {
390 int A = 13;
391 int B = 23;
392 int hash = A;
393 hash = (B * hash) + super.hashCode();
394 hash = (B * hash) + (int)ch;
395 hash = (B * hash) + width.hashCode();
396 if (image != null) {
397 /*
398 hash = (B * hash) + image.hashCode();
399 hash = (B * hash) + background.hashCode();
400 */
401 hash = (B * hash) + imageHashCode;
402 hash = (B * hash) + backgroundHashCode;
403 }
404 if (invertedImage != null) {
405 hash = (B * hash) + invertedImage.hashCode();
406 }
407 return hash;
408 }
409
410 /**
411 * Set my field values to that's field.
412 *
413 * @param rhs an instance of either Cell or CellAttributes
414 */
415 @Override
416 public void setTo(final Object rhs) {
417 // Let this throw a ClassCastException
418 CellAttributes thatAttr = (CellAttributes) rhs;
419 this.image = null;
420 this.imageHashCode = 0;
421 this.backgroundHashCode = 0;
422 this.width = Width.SINGLE;
423 super.setTo(thatAttr);
424
425 if (rhs instanceof Cell) {
426 Cell that = (Cell) rhs;
427 this.ch = that.ch;
428 this.width = that.width;
429 this.image = that.image;
430 this.invertedImage = that.invertedImage;
431 this.background = that.background;
432 this.imageHashCode = that.imageHashCode;
433 this.backgroundHashCode = that.backgroundHashCode;
434 }
435 }
436
437 /**
438 * Set my field attr values to that's field.
439 *
440 * @param that a CellAttributes instance
441 */
442 public void setAttr(final CellAttributes that) {
443 image = null;
444 super.setTo(that);
445 }
446
447 /**
448 * Make human-readable description of this Cell.
449 *
450 * @return displayable String
451 */
452 @Override
453 public String toString() {
454 return String.format("fore: %s back: %s bold: %s blink: %s ch %c",
455 getForeColor(), getBackColor(), isBold(), isBlink(), ch);
456 }
457 }