1 /*
2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25 package optkl.textmodel.terminal;
26
27 import optkl.textmodel.tokens.Token;
28
29 import java.io.PrintStream;
30 import java.util.Arrays;
31 import java.util.function.Consumer;
32 import java.util.function.Function;
33
34 public interface ANSI<T extends ANSI<T>> extends Function<String, T> {
35 T apply(String o);
36 default T apply(char ch){
37 return apply(""+ch);
38 }
39 default T apply(Token t) {
40 return apply(t.asString());
41 }
42
43 default T self() {
44 return (T) this;
45 }
46
47 default void skip(){
48 }
49
50 int BLACK = 30;
51 int RED = 31;
52 int GREEN = 32;
53 int YELLOW = 33;
54 int BLUE = 34;
55 int PURPLE = 35;
56 int CYAN = 36;
57 int WHITE = 37;
58
59 default T esc() {
60 return apply("\033");
61 }
62
63 default T color(int c, Consumer<T> cc) {
64 csi().ints(c).apply("m");
65 cc.accept(self());
66 return reset();
67 }
68
69 default T color(int c1, int c2, Consumer<T> cc) {
70 csi().ints(c1, c2).apply("m");
71 cc.accept(self());
72
73 return reset();
74 }
75 default T fg(int color, Consumer<T> cc) {
76 return color(0, color, cc);
77 }
78 default T fg(int color, Token token) {
79 return fg(color, $->$.apply(token));
80 }
81
82
83 default T bold(int color, Consumer<T> cc) {
84 return color(1, color, cc);
85 }
86
87
88 default T boldAndBright(int color, Consumer<T> cc) {
89 return color(0, color + 60, cc);
90 }
91
92
93
94 default T bright(int color, Consumer<T> cc) {
95 return color(1, color + 60, cc);
96 }
97
98
99
100 default T bg(int color,Consumer<T> cc) {
101 return color(color + 10, cc);
102 }
103 default T bg(int color,Token token) {
104 return bg(color, $->$.apply(token));
105 }
106
107 default T csi() {
108 return esc().apply("[");
109 }
110
111 default T home(){
112 return csi().ints(0,0).apply("H");
113 }
114
115 default T csiQuery() {
116 return csi().apply("?");
117 }
118
119 default T ints(int... n) {
120 apply(String.valueOf(n[0]));
121 for (int i = 1; i < n.length; i++) {
122 apply(";" + n[i]);
123 }
124 return self();
125 }
126
127
128 default T inv() {
129 return csi().ints(7).apply("m");
130 }
131
132 default T reset() {
133 return csi().ints(0).apply("m");
134 }
135
136 default T inv(Consumer<T> c) {
137 inv();
138 c.accept(self());
139 return reset();
140 }
141
142
143
144 record Adaptor(Consumer<String> consumer) implements ANSI<Adaptor> {
145 @Override
146 public Adaptor apply(String s) {
147 consumer.accept(s);
148 return self();
149 }
150 }
151
152 default T repeat(String s, int count) {
153 return apply(s.repeat(count));
154 }
155
156 default T fill(int cols, String s) {
157 return apply(s).repeat(" ", Math.max(0, cols - s.length()));
158 }
159
160 class IMPL implements ANSI<IMPL> {
161 private final PrintStream printStream;
162
163 @Override
164 public IMPL apply(String s) {
165 printStream.append(s);
166 return self();
167 }
168
169 IMPL(PrintStream printStream) {
170 this.printStream = printStream;
171 }
172 }
173
174 static IMPL of(PrintStream printStream) {
175 return new IMPL(printStream);
176 }
177
178
179 class DotImage {
180 final private ANSI<?> ansi;
181 final public int width;
182 final public int height;
183 final private byte[] bytes;
184 int charWidth;
185 int charHeight;
186 final private char[] chars;
187 public DotImage(ANSI<?> ansi, int width, int height){
188 this.ansi = ansi;
189 this.width = width;
190 this.height = height;
191 this.bytes = new byte[width*height];
192 this.charWidth = width/2;
193 this.charHeight = height/4;
194 this.chars = new char[charWidth*charHeight];
195 }
196 public void set(int x, int y){
197 bytes[(y*width)+x]=1;//0xff;
198 }
199 void reset(int x, int y){
200 bytes[(y*width)+x]=0;
201 }
202 int i(int x, int y){
203 byte b = bytes[(y*width)+x];
204 return (int)(b<0?b+256:b);
205 }
206 /**
207 See the unicode mapping table here
208 https://images.app.goo.gl/ntxis4mKzn7GmrGb7
209 */
210 char dotchar(int bytebits){
211 // int mapped = (bytebits&0x07)|(bytebits&0x70)>>1|(bytebits&0x08)<<3|(bytebits&0x80);
212 //char brail = (char)(0x2800+(bytebits&0x07)|(bytebits&0x70)>>1|(bytebits&0x08)<<3|(bytebits&0x80));
213 return (char)(0x2800+(bytebits&0x07)|(bytebits&0x70)>>1|(bytebits&0x08)<<3|(bytebits&0x80));
214 }
215 public DotImage home(){
216
217 ansi.home();
218 return this;
219 }
220 public DotImage delay(int ms){
221 try{ Thread.sleep(ms); }catch(Throwable t){ }
222 return this;
223 }
224
225 public DotImage clean(){
226 Arrays.fill(bytes,(byte)0);
227 Arrays.fill(chars,(char)' ');
228 return this;
229 }
230 public DotImage map(){
231 for (int cx = 0; cx<charWidth; cx++){
232 for (int cy = 0; cy<charHeight; cy++){
233 int bytebits=0;
234 for (int dx=0;dx<2;dx++){
235 for (int dy=0;dy<4;dy++){
236 bytebits|=i(cx*2+dx,cy*4+dy)<<(dx*4+dy);
237 }
238 }
239 chars[cy*charWidth+cx]= dotchar(bytebits);
240 }
241 }
242 return this;
243 }
244
245 public DotImage write(){
246 ansi.apply("+");
247 for (int i=0;i<charWidth; i++){
248 ansi.apply("-");
249 }
250 ansi.apply("+\n|");
251 for (int i=0;i<chars.length; i++){
252 if (i>0 && (i%charWidth)==0){
253 ansi.apply("|\n|");
254 }
255 ansi.apply(Character.toString(chars[i]));
256 }
257 ansi.apply("|\n+");
258 for (int i=0;i<charWidth; i++){
259 ansi.apply("-");
260 }
261 ansi.apply("+\n");
262 return this;
263 }
264 }
265 default DotImage img(int width, int height){
266 return new DotImage(this, width,height);
267 }
268
269 }