initial import
This commit is contained in:
200
src/inf101/v18/gfx/textmode/BlocksAndBoxes.java
Normal file
200
src/inf101/v18/gfx/textmode/BlocksAndBoxes.java
Normal file
@@ -0,0 +1,200 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class BlocksAndBoxes {
|
||||
public static final String[] unicodeBlocks = { " ", "▗", "▖", "▄", "▝", "▐", "▞", "▟", "▘", "▚", "▌", "▙", "▀", "▜",
|
||||
"▛", "█", "▒" };
|
||||
public static final int[] unicodeBlocks_NumPixels = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 2 };
|
||||
|
||||
public static final String unicodeBlocksString = String.join("", unicodeBlocks);
|
||||
public static final String BLOCK_EMPTY = " ";
|
||||
public static final String BLOCK_BOTTOM_RIGHT = "▗";
|
||||
public static final String BLOCK_BOTTOM_LEFT = "▖";
|
||||
public static final String BLOCK_BOTTOM = "▄";
|
||||
public static final String BLOCK_TOP_RIGHT = "▝";
|
||||
public static final String BLOCK_RIGHT = "▐";
|
||||
public static final String BLOCK_DIAG_FORWARD = "▞";
|
||||
public static final String BLOCK_REVERSE_TOP_LEFT = "▟";
|
||||
public static final String BLOCK_TOP_LEFT = "▘";
|
||||
public static final String BLOCK_DIAG_BACKWARD = "▚";
|
||||
public static final String BLOCK_LEFT = "▌";
|
||||
public static final String BLOCK_REVERSE_TOP_RIGHT = "▙";
|
||||
public static final String BLOCK_TOP = "▀";
|
||||
public static final String BLOCK_REVERSE_BOTTOM_LEFT = "▜";
|
||||
public static final String BLOCK_REVERSE_BOTTOM_RIGHT = "▛";
|
||||
public static final String BLOCK_FULL = "█";
|
||||
public static final String BLOCK_HALF = "▒";
|
||||
|
||||
/**
|
||||
* Convert a string into a Unicode block graphics character.
|
||||
*
|
||||
* <p>
|
||||
* The block characters corresponds to 2x2-pixel images, and can be used, e.g.,
|
||||
* to draw a 80x44 pixel image on a 40x22 character text screen.
|
||||
*
|
||||
* <p>
|
||||
* The blocks are specified by four-character strings of spaces and asterisks,
|
||||
* with space indicating an open space and asterisk indicating a filled "pixel",
|
||||
* with the pixels arranged in a left-to-right, top-to-bottom order:
|
||||
*
|
||||
* <pre>
|
||||
* 01
|
||||
* 23
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* So <code>"* **"</code> corresponds to the block character <code>"▚"</code>,
|
||||
* with this layout:
|
||||
*
|
||||
* <pre>
|
||||
* *
|
||||
* *
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* The special codes <code>"++++"</code> and <code>"+"</code> corresponds to the
|
||||
* "grey" block <code>"▒"</code>, and <code>"*"</code> corresponds to the
|
||||
* "black" block <code>"█"</code>.
|
||||
*
|
||||
* @param s
|
||||
* A four character string, indicating which block character to
|
||||
* select.
|
||||
* @return A Unicode block character
|
||||
* @throws IllegalArgumentException
|
||||
* if the string isn't of the expected form
|
||||
*/
|
||||
public static String blockChar(String s) {
|
||||
switch (s.replaceAll("\n", s)) {
|
||||
case " ":
|
||||
return unicodeBlocks[0];
|
||||
case " *":
|
||||
return unicodeBlocks[1];
|
||||
case " * ":
|
||||
return unicodeBlocks[2];
|
||||
case " **":
|
||||
return unicodeBlocks[3];
|
||||
case " * ":
|
||||
return unicodeBlocks[4];
|
||||
case " * *":
|
||||
return unicodeBlocks[5];
|
||||
case " ** ":
|
||||
return unicodeBlocks[6];
|
||||
case " ***":
|
||||
return unicodeBlocks[7];
|
||||
case "* ":
|
||||
return unicodeBlocks[8];
|
||||
case "* *":
|
||||
return unicodeBlocks[9];
|
||||
case "* * ":
|
||||
return unicodeBlocks[10];
|
||||
case "* **":
|
||||
return unicodeBlocks[11];
|
||||
case "** ":
|
||||
return unicodeBlocks[12];
|
||||
case "** *":
|
||||
return unicodeBlocks[13];
|
||||
case "*** ":
|
||||
return unicodeBlocks[14];
|
||||
case "****":
|
||||
return unicodeBlocks[15];
|
||||
case "++++":
|
||||
return unicodeBlocks[16];
|
||||
case ".":
|
||||
return BLOCK_BOTTOM_LEFT;
|
||||
case "_":
|
||||
return BLOCK_BOTTOM;
|
||||
case "/":
|
||||
return BLOCK_DIAG_FORWARD;
|
||||
case "\\":
|
||||
return BLOCK_DIAG_BACKWARD;
|
||||
case "|":
|
||||
return BLOCK_LEFT;
|
||||
case "#":
|
||||
return BLOCK_FULL;
|
||||
case "`":
|
||||
return BLOCK_TOP_LEFT;
|
||||
case "'":
|
||||
return BLOCK_TOP_RIGHT;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Expected length 4 string of \" \" and \"*\", or \"++++\", got \"" + s + "\"");
|
||||
};
|
||||
|
||||
public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
||||
int i1 = unicodeBlocksString.indexOf(b1);
|
||||
if (i1 < 0)
|
||||
throw new IllegalArgumentException("Not a block character: " + b1);
|
||||
int i2 = unicodeBlocksString.indexOf(b2);
|
||||
if (i2 < 0)
|
||||
throw new IllegalArgumentException("Not a block character: " + b1);
|
||||
if (i1 == 16 || i2 == 16)
|
||||
return b2;
|
||||
else
|
||||
return unicodeBlocks[op.apply(i1, i2)];
|
||||
}
|
||||
|
||||
public static String blockComposeOrOverwrite(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
||||
int i1 = unicodeBlocksString.indexOf(b1);
|
||||
int i2 = unicodeBlocksString.indexOf(b2);
|
||||
if (i1 < 0 || i2 < 0 || i1 == 16 || i2 == 16)
|
||||
return b2;
|
||||
else
|
||||
return unicodeBlocks[op.apply(i1, i2)];
|
||||
}
|
||||
|
||||
public enum PixelOrder implements Iterable<Integer> {
|
||||
LEFT_TO_RIGHT(8, 4, 2, 1), RIGHT_TO_LEFT(4, 8, 1, 2), LEFT_TO_RIGHT_UPWARDS(2, 1, 8,
|
||||
4), RIGHT_TO_LEFT_UPWARDS(1, 2, 4, 8);
|
||||
|
||||
private List<Integer> order;
|
||||
|
||||
private PixelOrder(int a, int b, int c, int d) {
|
||||
order = Arrays.asList(a, b, c, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return order.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public static String blockAddOne(String s, PixelOrder order) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i >= 0) {
|
||||
for (int bit : order) {
|
||||
if ((i & bit) == 0)
|
||||
return unicodeBlocks[i | bit];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String blockRemoveOne(String s, PixelOrder order) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i >= 0) {
|
||||
for (int bit : order) {
|
||||
if ((i & bit) != 0)
|
||||
return unicodeBlocks[i & ~bit];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String blockCompact(String s) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i > 0) {
|
||||
int lower = i & 3;
|
||||
int upper = (i >> 2) & 3;
|
||||
i = (lower | upper) | ((lower & upper) << 2);
|
||||
// System.out.println("Compact: " + s + " -> " + BlocksAndBoxes.unicodeBlocks[i]
|
||||
// + "\n");
|
||||
return BlocksAndBoxes.unicodeBlocks[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
271
src/inf101/v18/gfx/textmode/ControlSequences.java
Normal file
271
src/inf101/v18/gfx/textmode/ControlSequences.java
Normal file
@@ -0,0 +1,271 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class ControlSequences {
|
||||
public static class CsiPattern {
|
||||
public static CsiPattern compile0(String pat, String desc, Consumer<Printer> handler) {
|
||||
CsiPattern csiPattern = new CsiPattern(pat, 0, 0, desc, handler, null, null);
|
||||
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
||||
return csiPattern;
|
||||
}
|
||||
|
||||
public static CsiPattern compile1(String pat, int defaultArg, String desc,
|
||||
BiConsumer<Printer, Integer> handler) {
|
||||
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, 1, desc, null, handler, null);
|
||||
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
||||
return csiPattern;
|
||||
}
|
||||
|
||||
public static CsiPattern compileN(String pat, int defaultArg, int numArgs, String desc,
|
||||
BiConsumer<Printer, List<Integer>> handler) {
|
||||
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, numArgs, desc, null, null, handler);
|
||||
patterns.put(csiPattern.getCommandLetter(), csiPattern);
|
||||
return csiPattern;
|
||||
}
|
||||
|
||||
private String patStr;
|
||||
private Pattern pattern;
|
||||
private int defaultArg = 0;
|
||||
private String desc;
|
||||
private Consumer<Printer> handler0;
|
||||
|
||||
private BiConsumer<Printer, Integer> handler1;
|
||||
|
||||
private BiConsumer<Printer, List<Integer>> handlerN;
|
||||
|
||||
private int numArgs;
|
||||
|
||||
public CsiPattern(String pat, int defaultArg, int numArgs, String desc, Consumer<Printer> handler0,
|
||||
BiConsumer<Printer, Integer> handler1, BiConsumer<Printer, List<Integer>> handlerN) {
|
||||
this.patStr = pat;
|
||||
this.pattern = Pattern.compile(pat);
|
||||
this.defaultArg = defaultArg;
|
||||
this.numArgs = numArgs;
|
||||
this.desc = desc;
|
||||
this.handler0 = handler0;
|
||||
this.handler1 = handler1;
|
||||
this.handlerN = handlerN;
|
||||
}
|
||||
|
||||
public String getCommandLetter() {
|
||||
return patStr.substring(patStr.length() - 1);
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
public boolean match(Printer printer, String input) {
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
if (matcher.matches()) {
|
||||
String argStr = matcher.groupCount() > 0 ? matcher.group(1) : "";
|
||||
String[] args = argStr.split(";");
|
||||
if (handler0 != null) {
|
||||
System.out.println("Handling " + getDescription() + ".");
|
||||
handler0.accept(printer);
|
||||
} else if (handler1 != null) {
|
||||
int arg = args.length > 0 && !args[0].equals("") ? Integer.valueOf(args[0]) : defaultArg;
|
||||
System.out.println("Handling " + getDescription() + ": " + arg);
|
||||
handler1.accept(printer, arg);
|
||||
} else if (handlerN != null) {
|
||||
List<Integer> argList = new ArrayList<>();
|
||||
for (String s : args) {
|
||||
if (s.equals(""))
|
||||
argList.add(defaultArg);
|
||||
else
|
||||
argList.add(Integer.valueOf(s));
|
||||
}
|
||||
while (argList.size() < numArgs) {
|
||||
argList.add(defaultArg);
|
||||
}
|
||||
System.out.println("Handling " + getDescription() + ": " + argList);
|
||||
handlerN.accept(printer, argList);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static final Map<String, CsiPattern> patterns = new HashMap<>();
|
||||
public static final CsiPattern CUU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)A", 1, "cursor up",
|
||||
(Printer p, Integer i) -> {
|
||||
p.move(0, -i);
|
||||
});
|
||||
public static final CsiPattern CUD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)B", 1, "cursor down",
|
||||
(Printer p, Integer i) -> {
|
||||
p.move(0, i);
|
||||
});
|
||||
public static final CsiPattern CUF = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)C", 1, "cursor forward",
|
||||
(Printer p, Integer i) -> {
|
||||
p.move(i, 0);
|
||||
});
|
||||
public static final CsiPattern CUB = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)D", 1, "cursor back",
|
||||
(Printer p, Integer i) -> {
|
||||
p.move(-i, 0);
|
||||
});
|
||||
public static final CsiPattern CNL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)E", 1, "cursor next line",
|
||||
(Printer p, Integer i) -> {
|
||||
p.move(0, i);
|
||||
p.beginningOfLine();
|
||||
});
|
||||
public static final CsiPattern CPL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)F", 1, "cursor previous line",
|
||||
(Printer p, Integer i) -> {
|
||||
p.move(0, -i);
|
||||
p.beginningOfLine();
|
||||
});
|
||||
public static final CsiPattern CHA = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)G", 1,
|
||||
"cursor horizontal absolute", (Printer p, Integer i) -> {
|
||||
p.moveTo(i, p.getY());
|
||||
});
|
||||
public static final CsiPattern CUP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)H", 1, 2, "cursor position",
|
||||
(Printer p, List<Integer> i) -> {
|
||||
p.moveTo(i.get(1), i.get(0));
|
||||
});
|
||||
public static final CsiPattern ED = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)J", 0, "erase in display",
|
||||
(Printer p, Integer i) -> {
|
||||
if (i == 2 || i == 3)
|
||||
p.clear();
|
||||
else
|
||||
System.err.println("Unimplemented: ED");
|
||||
});
|
||||
public static final CsiPattern EK = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)K", 0, "erase in line",
|
||||
(Printer p, Integer i) -> {
|
||||
System.err.println("Unimplemented: EK");
|
||||
});
|
||||
public static final CsiPattern SU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)S", 1, "scroll up",
|
||||
(Printer p, Integer i) -> {
|
||||
p.scroll(i);
|
||||
});
|
||||
public static final CsiPattern SD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)T", 1, "scroll down",
|
||||
(Printer p, Integer i) -> {
|
||||
p.scroll(-i);
|
||||
});
|
||||
public static final CsiPattern HVP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)f", 1, 2,
|
||||
"horizontal vertical position", (Printer p, List<Integer> l) -> {
|
||||
p.moveTo(l.get(1), l.get(0));
|
||||
});
|
||||
public static final CsiPattern AUX_ON = CsiPattern.compile0("\u001b\\\u005b5i", "aux port on", (Printer p) -> {
|
||||
System.err.println("Unimplemented: AUX on");
|
||||
});
|
||||
public static final CsiPattern AUX_OFF = CsiPattern.compile0("\u001b\\\u005b4i", "aux port off", (Printer p) -> {
|
||||
System.err.println("Unimplemented: AUX off");
|
||||
});
|
||||
public static final CsiPattern DSR = CsiPattern.compile0("\u001b\\\u005b6n", "device status report",
|
||||
(Printer p) -> {
|
||||
System.out.println("ESC[" + p.getY() + ";" + p.getX() + "R");
|
||||
});
|
||||
public static final CsiPattern SCP = CsiPattern.compile0("\u001b\\\u005bs", "save cursor position", (Printer p) -> {
|
||||
p.saveCursor();
|
||||
});
|
||||
public static final CsiPattern RCP = CsiPattern.compile0("\u001b\\\u005bu", "restore cursor position",
|
||||
(Printer p) -> {
|
||||
p.restoreCursor();
|
||||
});
|
||||
public static final int F = 0xFF, H = 0xAA, L = 0x55, OFF = 0x00;
|
||||
public static final Color[] PALETTE_CGA = { //
|
||||
Color.rgb(0, 0, 0), Color.rgb(0, 0, H), Color.rgb(0, H, 0), Color.rgb(0, H, H), //
|
||||
Color.rgb(H, 0, 0), Color.rgb(H, 0, H), Color.rgb(H, L, 0), Color.rgb(H, H, H), //
|
||||
Color.rgb(L, L, L), Color.rgb(L, L, F), Color.rgb(L, F, L), Color.rgb(L, F, F), //
|
||||
Color.rgb(F, L, L), Color.rgb(F, L, F), Color.rgb(F, F, L), Color.rgb(F, F, F), };
|
||||
public static final Color[] PALETTE_VGA = { //
|
||||
Color.rgb(0, 0, 0), Color.rgb(H, 0, 0), Color.rgb(0, H, 0), Color.rgb(H, H, 0), //
|
||||
Color.rgb(0, 0, H), Color.rgb(H, 0, H), Color.rgb(0, H, H), Color.rgb(H, H, H), //
|
||||
Color.rgb(L, L, L), Color.rgb(F, L, L), Color.rgb(L, F, L), Color.rgb(F, F, L), //
|
||||
Color.rgb(L, L, F), Color.rgb(F, L, F), Color.rgb(L, F, F), Color.rgb(F, F, F), };
|
||||
|
||||
public static final CsiPattern SGR = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)m", 0, -1,
|
||||
"select graphics rendition", (Printer p, List<Integer> l) -> {
|
||||
if (l.size() == 0) {
|
||||
l.add(0);
|
||||
}
|
||||
int[] attrs = { 0, TextFont.ATTR_BRIGHT, TextFont.ATTR_FAINT, TextFont.ATTR_ITALIC,
|
||||
TextFont.ATTR_UNDERLINE, TextFont.ATTR_BLINK, TextFont.ATTR_BLINK, TextFont.ATTR_INVERSE, 0,
|
||||
TextFont.ATTR_LINE_THROUGH };
|
||||
|
||||
Iterator<Integer> it = l.iterator();
|
||||
while (it.hasNext()) {
|
||||
int i = it.next();
|
||||
if (i == 0) {
|
||||
p.setVideoAttrs(0);
|
||||
p.setInk(PALETTE_VGA[7]);
|
||||
p.setBackground(PALETTE_VGA[0]);
|
||||
} else if (i < 10) {
|
||||
p.setVideoAttrEnabled(attrs[i]);
|
||||
} else if (i >= 20 && i < 30) {
|
||||
p.setVideoAttrDisabled(attrs[i] - 20);
|
||||
} else if (i >= 30 && i < 38) {
|
||||
p.setInk(PALETTE_VGA[i - 30]);
|
||||
} else if (i == 38) {
|
||||
p.setInk(decode256(it));
|
||||
} else if (i == 29) {
|
||||
p.setInk(Color.WHITE);
|
||||
} else if (i >= 40 && i < 48) {
|
||||
p.setBackground(PALETTE_VGA[i - 40]);
|
||||
} else if (i == 48) {
|
||||
p.setInk(decode256(it));
|
||||
} else if (i == 49) {
|
||||
p.setBackground(Color.BLACK);
|
||||
} else if (i >= 90 && i < 98) {
|
||||
p.setInk(PALETTE_VGA[8 + i - 90]);
|
||||
} else if (i >= 100 && i < 108) {
|
||||
p.setBackground(PALETTE_VGA[8 + i - 100]);
|
||||
} else if (i == 53) {
|
||||
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
|
||||
} else if (i == 55) {
|
||||
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public static boolean applyCsi(Printer printer, String csi) {
|
||||
CsiPattern csiPattern = patterns.get(csi.substring(csi.length() - 1));
|
||||
// System.out.println("Applying CSI: " + csi.replaceAll("\u001b", "ESC"));
|
||||
|
||||
if (csiPattern != null) {
|
||||
if (csiPattern.match(printer, csi))
|
||||
return true;
|
||||
else
|
||||
System.err.println("Handler failed for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
|
||||
|
||||
} else {
|
||||
System.err.println("No handler for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Color decode256(Iterator<Integer> it) {
|
||||
int i;
|
||||
try {
|
||||
i = it.next();
|
||||
if (i == 5) {
|
||||
i = it.next();
|
||||
if (i < 16)
|
||||
return PALETTE_VGA[i];
|
||||
else if (i < 232)
|
||||
return Color.rgb(i / 36, (i / 6) % 6, i % 6);
|
||||
else
|
||||
return Color.gray((i - 232) / 23.0);
|
||||
} else if (i == 2) {
|
||||
int r = it.next();
|
||||
int g = it.next();
|
||||
int b = it.next();
|
||||
return Color.rgb(r, g, b);
|
||||
}
|
||||
} catch (NoSuchElementException e) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
154
src/inf101/v18/gfx/textmode/DemoPages.java
Normal file
154
src/inf101/v18/gfx/textmode/DemoPages.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class DemoPages {
|
||||
public static void printAnsiArt(Printer printer) {
|
||||
printer.moveTo(1, 1);
|
||||
printer.setAutoScroll(false);
|
||||
printer.clear();
|
||||
|
||||
try (InputStream stream = DemoPages.class.getResourceAsStream("flower.txt")) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
|
||||
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
|
||||
printer.println(s);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void printBlockPlotting(Printer printer) {
|
||||
printer.clear();
|
||||
printer.setAutoScroll(false);
|
||||
printer.setVideoAttrs(0);
|
||||
int topLine = 8;
|
||||
for (int x = 0; x < 16; x += 1) {
|
||||
if ((x & 1) > 0)
|
||||
printer.plot(4 * x + 1, 1 + (topLine - 1) * 2);
|
||||
if ((x & 2) > 0)
|
||||
printer.plot(4 * x, 1 + (topLine - 1) * 2);
|
||||
if ((x & 4) > 0)
|
||||
printer.plot(4 * x + 1, (topLine - 1) * 2);
|
||||
if ((x & 8) > 0)
|
||||
printer.plot(4 * x, (topLine - 1) * 2);
|
||||
printer.printAt(1 + 2 * x, topLine + 2, BlocksAndBoxes.unicodeBlocks[x]);
|
||||
printer.printAt(1 + 2 * x, topLine + 4, BlocksAndBoxes.unicodeBlocks[15]);
|
||||
printer.printAt(1 + 2 * x, topLine + 6, BlocksAndBoxes.unicodeBlocks[~x & +0xf]);
|
||||
printer.printAt(1 + 2 * x, topLine + 7, String.format("%X", x));
|
||||
if ((x & 1) > 0)
|
||||
printer.unplot(4 * x + 1, 1 + (4 + topLine - 1) * 2);
|
||||
if ((x & 2) > 0)
|
||||
printer.unplot(4 * x, 1 + (4 + topLine - 1) * 2);
|
||||
if ((x & 4) > 0)
|
||||
printer.unplot(4 * x + 1, (4 + topLine - 1) * 2);
|
||||
if ((x & 8) > 0)
|
||||
printer.unplot(4 * x, (4 + topLine - 1) * 2);
|
||||
}
|
||||
printer.printAt(1, 1,
|
||||
"Plotting with Unicode Block Elements\n(ZX81-like Graphics)\n\nThe plot/print and unplot/inverse\nlines should be equal:");
|
||||
printer.printAt(33, topLine, "plot");
|
||||
printer.printAt(33, topLine + 2, "print");
|
||||
printer.printAt(33, topLine + 4, "unplot");
|
||||
printer.printAt(33, topLine + 6, "inverse");
|
||||
printer.printAt(0, topLine + 9, String.format("Full blocks:\n Clear[%s] Shaded[%s] Opaque[%s]",
|
||||
BlocksAndBoxes.unicodeBlocks[0], BlocksAndBoxes.unicodeBlocks[16], BlocksAndBoxes.unicodeBlocks[15]));
|
||||
printer.printAt(41, topLine + 9, "(ZX81 inverted shade and half block");
|
||||
printer.printAt(41, topLine + 10, "shades are missing in Unicode and");
|
||||
printer.printAt(41, topLine + 11, "therefore not supported)");
|
||||
printer.println();
|
||||
}
|
||||
|
||||
public static void printBoxDrawing(Printer printer) {
|
||||
printer.clear();
|
||||
printer.setAutoScroll(false);
|
||||
printer.println(" Latin-1 Boxes & Blocks");
|
||||
printer.println(" U+0000..00FF U+2500..257F..259F");
|
||||
printer.println(" ");
|
||||
printer.println(" 0123456789ABCDEF 0123456789ABCDEF");
|
||||
for (int y = 0; y < 16; y++) {
|
||||
printer.print(String.format(" %X", y));
|
||||
int c = 0x00 + y * 0x010;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
printer.print(c >= 0x20 ? Character.toString((char) (c + x)) : " ");
|
||||
}
|
||||
printer.print(" ");
|
||||
|
||||
if (y < 10) {
|
||||
printer.print(String.format("%X", y));
|
||||
c = 0x2500 + y * 0x010;
|
||||
for (int x = 0; x < 16; x++) {
|
||||
printer.print(Character.toString((char) (c + x)));
|
||||
}
|
||||
}
|
||||
printer.println();
|
||||
}
|
||||
}
|
||||
|
||||
public static void printVideoAttributes(Printer printer) {
|
||||
printer.clear();
|
||||
printer.setAutoScroll(false);
|
||||
printer.setVideoAttrs(0);
|
||||
printer.setInk(Color.BLACK);
|
||||
printer.setStroke(Color.WHITE);
|
||||
|
||||
String demoLine = "Lorem=ipsum-dolor$sit.ametÆØÅå*,|▞&Jumps Over\\the?fLat Dog{}()#\"!";
|
||||
printer.println("RIBU|" + demoLine);
|
||||
for (int i = 1; i < 16; i++) {
|
||||
printer.setVideoAttrs(i);
|
||||
String s = (i & 1) != 0 ? "X" : " ";
|
||||
s += (i & 2) != 0 ? "X" : " ";
|
||||
s += (i & 4) != 0 ? "X" : " ";
|
||||
s += (i & 8) != 0 ? "X" : " ";
|
||||
printer.println(s + "|" + demoLine);
|
||||
}
|
||||
printer.setVideoAttrs(0);
|
||||
printer.println();
|
||||
printer.println("Lines: under, through, over");
|
||||
printer.setVideoAttrs(TextFont.ATTR_UNDERLINE);
|
||||
printer.println(" " + demoLine + " ");
|
||||
printer.setVideoAttrs(TextFont.ATTR_LINE_THROUGH);
|
||||
printer.println(" " + demoLine + " ");
|
||||
printer.setVideoAttrs(TextFont.ATTR_OVERLINE);
|
||||
printer.println(" " + demoLine + " ");
|
||||
printer.setVideoAttrs(0);
|
||||
|
||||
}
|
||||
|
||||
public static void printZX(Printer printer) {
|
||||
printer.moveTo(1, 1);
|
||||
printer.setAutoScroll(false);
|
||||
printer.clear();
|
||||
printer.println(" ▄▄▄ ▄ ▄ ▄");
|
||||
printer.println(" █ █ █ █ █ █ █");
|
||||
printer.println(" █ █ █ █ █ █ █");
|
||||
printer.println(" █ █ █ █ █ █ █");
|
||||
printer.println(" ▀ ▀ ▀ ▀ ▀▀▀");
|
||||
printer.println(" ▄▄▄ ▄▄");
|
||||
printer.println(" █ █");
|
||||
printer.println(" █ █");
|
||||
printer.println(" █ █");
|
||||
printer.println(" ▀▀▀ ▀▀");
|
||||
printer.println(" ▄▄ ▄ ▄ ▄");
|
||||
printer.println(" █ █ █ █ █ █");
|
||||
printer.println(" █ █ █ █ █ █");
|
||||
printer.println(" █ █ █ █ █ █");
|
||||
printer.println(" ▀▀ ▀ ▀ ▀▀▀");
|
||||
printer.println("ON █████ █ █ ███ █");
|
||||
printer.println("THE █ █ █ █ █ ██");
|
||||
printer.println("SINCLAIR █ ███ █");
|
||||
printer.println(" █ █ █ █ █ WITH");
|
||||
printer.println(" █ █ █ █ █ █ 16K");
|
||||
printer.println(" █████ █ █ ███ ███ RAM");
|
||||
printer.moveTo(1, 1);
|
||||
printer.setAutoScroll(true);
|
||||
}
|
||||
|
||||
}
|
680
src/inf101/v18/gfx/textmode/Printer.java
Normal file
680
src/inf101/v18/gfx/textmode/Printer.java
Normal file
@@ -0,0 +1,680 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import inf101.v18.gfx.IPaintLayer;
|
||||
import inf101.v18.gfx.Screen;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.effect.BlendMode;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
|
||||
public class Printer implements IPaintLayer {
|
||||
private static class Char {
|
||||
public int mode;
|
||||
public String s;
|
||||
public Color fill;
|
||||
public Color stroke;
|
||||
public Paint bg;
|
||||
|
||||
public Char(String s, Color fill, Color stroke, Paint bg, int mode) {
|
||||
this.s = s;
|
||||
this.fill = fill;
|
||||
this.stroke = stroke;
|
||||
this.bg = bg;
|
||||
this.mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
public static final TextFont FONT_MONOSPACED = new TextFont("Monospaced", 27.00, TextMode.CHAR_BOX_SIZE, 3.4000,
|
||||
-6.7000, 1.5000, 1.0000, true);
|
||||
public static final TextFont FONT_LMMONO = new TextFont("lmmono10-regular.otf", 30.00, TextMode.CHAR_BOX_SIZE,
|
||||
4.0000, -8.5000, 1.5000, 1.0000, true);
|
||||
public static final TextFont FONT_ZXSPECTRUM7 = new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE,
|
||||
3.1000, -3.8000, 1.0000, 1.0000, true);
|
||||
/**
|
||||
* TTF file can be found here:
|
||||
* http://www.kreativekorp.com/software/fonts/c64.shtml
|
||||
*/
|
||||
public static final TextFont FONT_GIANA = new TextFont("Giana.ttf", 25.00, TextMode.CHAR_BOX_SIZE, 4.6000, -5.0000,
|
||||
1.0000, 1.0000, true);
|
||||
|
||||
/**
|
||||
* TTF file can be found here:
|
||||
* http://www.kreativekorp.com/software/fonts/c64.shtml
|
||||
*/
|
||||
public static final TextFont FONT_C64 = new TextFont("PetMe64.ttf", 31.50, TextMode.CHAR_BOX_SIZE, 0.0000, -4.000,
|
||||
1.0000, 1.0000, true);
|
||||
Color DEFAULT_FILL = Color.BLACK;
|
||||
Color DEFAULT_STROKE = Color.TRANSPARENT;
|
||||
private static final Paint DEFAULT_BACKGROUND = Color.TRANSPARENT;
|
||||
private static final TextMode DEFAULT_MODE = TextMode.MODE_40X22;
|
||||
|
||||
public static String center(String s, int width) {
|
||||
for (; s.length() < width; s = " " + s + " ")
|
||||
;
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String repeat(String s, int width) {
|
||||
String r = s;
|
||||
for (; r.length() < width; r += s)
|
||||
;
|
||||
return r;
|
||||
}
|
||||
|
||||
private TextMode textMode;
|
||||
private Color fill;
|
||||
private Color stroke;
|
||||
private Paint background;
|
||||
private Screen screen;
|
||||
private List<Char[]> lineBuffer = new ArrayList<>();
|
||||
private boolean autoscroll = true;
|
||||
private final Canvas textPage;
|
||||
private int x = 1, y = 1, savedX = 1, savedY = 1;
|
||||
// private int pageWidth = LINE_WIDTHS[resMode], pageHeight =
|
||||
// PAGE_HEIGHTS[resMode];
|
||||
private int leftMargin = 1, topMargin = 1;
|
||||
private TextFont font = FONT_LMMONO;
|
||||
private int videoAttrs = 0;
|
||||
|
||||
private String csiSeq = null;
|
||||
|
||||
private boolean csiEnabled = true;
|
||||
|
||||
private int csiMode = 0;
|
||||
|
||||
public Printer(Screen screen, Canvas page) {
|
||||
this.screen = screen;
|
||||
this.textPage = page;
|
||||
for (int i = 0; i < TextMode.PAGE_HEIGHT_MAX; i++) {
|
||||
lineBuffer.add(new Char[TextMode.LINE_WIDTH_MAX]);
|
||||
}
|
||||
resetFull();
|
||||
}
|
||||
|
||||
public void addToCharBuffer(String string) {
|
||||
string.codePoints().mapToObj((int i) -> String.valueOf(Character.toChars(i))).forEach((String s) -> {
|
||||
if (csiMode != 0) {
|
||||
s = addToCsiBuffer(s);
|
||||
}
|
||||
switch (s) {
|
||||
case "\r":
|
||||
moveTo(leftMargin, y);
|
||||
break;
|
||||
case "\n":
|
||||
moveTo(leftMargin, y + 1);
|
||||
break;
|
||||
case "\f":
|
||||
GraphicsContext context = textPage.getGraphicsContext2D();
|
||||
moveTo(leftMargin, topMargin);
|
||||
for (Char[] line : lineBuffer)
|
||||
Arrays.fill(line, null);
|
||||
if (background != null && background != Color.TRANSPARENT) {
|
||||
context.setFill(background);
|
||||
context.fillRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
|
||||
} else
|
||||
context.clearRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
|
||||
break;
|
||||
case "\b":
|
||||
moveHoriz(-1);
|
||||
break;
|
||||
case "\t":
|
||||
moveTo((x + 8) % 8, y);
|
||||
break;
|
||||
case "\u001b":
|
||||
if (csiEnabled) {
|
||||
csiSeq = s;
|
||||
csiMode = 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (s.length() > 0 && s.codePointAt(0) >= 0x20) {
|
||||
drawChar(x, y, setChar(x, y, s));
|
||||
moveHoriz(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void drawChar(int x, int y, Char c) {
|
||||
if (c != null) {
|
||||
GraphicsContext context = textPage.getGraphicsContext2D();
|
||||
|
||||
context.setFill(c.fill);
|
||||
context.setStroke(c.stroke);
|
||||
font.drawTextAt(context, (x - 1) * getCharWidth(), y * getCharHeight(), c.s,
|
||||
textMode.getCharWidth() / textMode.getCharBoxSize(), c.mode, c.bg);
|
||||
}
|
||||
}
|
||||
|
||||
private String addToCsiBuffer(String s) {
|
||||
if (csiMode == 1) {
|
||||
switch (s) {
|
||||
case "[":
|
||||
csiMode = 2;
|
||||
csiSeq += s;
|
||||
break;
|
||||
case "c":
|
||||
csiMode = 0;
|
||||
resetFull();
|
||||
break;
|
||||
default:
|
||||
csiReset();
|
||||
return s;
|
||||
}
|
||||
} else if (csiMode == 2) {
|
||||
int c = s.codePointAt(0);
|
||||
if (c >= 0x30 && c <= 0x3f) {
|
||||
csiSeq += s;
|
||||
} else if (c >= 0x20 && c <= 0x2f) {
|
||||
csiMode = 3;
|
||||
csiSeq += s;
|
||||
} else if (c >= 0x40 && c <= 0x7e) {
|
||||
csiSeq += s;
|
||||
csiFinish();
|
||||
} else {
|
||||
csiReset();
|
||||
return s;
|
||||
}
|
||||
|
||||
} else if (csiMode == 3) {
|
||||
int c = s.codePointAt(0);
|
||||
if (c >= 0x20 && c <= 0x2f) {
|
||||
csiSeq += s;
|
||||
} else if (c >= 0x40 && c <= 0x7e) {
|
||||
csiSeq += s;
|
||||
csiFinish();
|
||||
} else {
|
||||
csiReset();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public void beginningOfLine() {
|
||||
x = leftMargin;
|
||||
}
|
||||
|
||||
public void beginningOfPage() {
|
||||
x = leftMargin;
|
||||
y = topMargin;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
print("\f");
|
||||
}
|
||||
|
||||
public void clearAt(int x, int y) {
|
||||
printAt(x, y, " ");
|
||||
}
|
||||
|
||||
private int constrainX(int x) {
|
||||
return x; // Math.min(LINE_WIDTH_HIRES, Math.max(1, x));
|
||||
}
|
||||
|
||||
public int constrainY(int y) {
|
||||
return y; // Math.min(pageHeight, Math.max(1, y));
|
||||
}
|
||||
|
||||
public int constrainYOrScroll(int y) {
|
||||
if (autoscroll) {
|
||||
if (y < 1) {
|
||||
scroll(y - 1);
|
||||
return 1;
|
||||
} else if (y > getPageHeight()) {
|
||||
scroll(y - getPageHeight());
|
||||
return getPageHeight();
|
||||
}
|
||||
}
|
||||
|
||||
return y;// Math.min(pageHeight, Math.max(1, y));
|
||||
}
|
||||
|
||||
private void csiFinish() {
|
||||
ControlSequences.applyCsi(this, csiSeq);
|
||||
csiReset();
|
||||
}
|
||||
|
||||
private void csiReset() {
|
||||
csiMode = 0;
|
||||
csiSeq = null;
|
||||
}
|
||||
|
||||
public void cycleMode(boolean adjustDisplayAspect) {
|
||||
textMode = textMode.nextMode();
|
||||
if (adjustDisplayAspect)
|
||||
screen.setAspect(textMode.getAspect());
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public void drawCharCells() {
|
||||
GraphicsContext context = screen.getBackgroundContext();
|
||||
screen.clearBackground();
|
||||
double w = getCharWidth();
|
||||
double h = getCharHeight();
|
||||
context.save();
|
||||
context.setGlobalBlendMode(BlendMode.EXCLUSION);
|
||||
context.setFill(Color.WHITE.deriveColor(0.0, 1.0, 1.0, 0.3));
|
||||
for (int x = 0; x < getLineWidth(); x++) {
|
||||
for (int y = 0; y < getPageHeight(); y++) {
|
||||
if ((x + y) % 2 == 0)
|
||||
context.fillRect(x * w, y * h, w, h);
|
||||
}
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
|
||||
public boolean getBold() {
|
||||
return (videoAttrs & TextFont.ATTR_BRIGHT) != 0;
|
||||
}
|
||||
|
||||
public String getChar(int x, int y) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null)
|
||||
return c.s;
|
||||
else
|
||||
return " ";
|
||||
}
|
||||
|
||||
public double getCharHeight() {
|
||||
return textMode.getCharHeight();
|
||||
}
|
||||
|
||||
public double getCharWidth() {
|
||||
return textMode.getCharWidth();
|
||||
}
|
||||
|
||||
public Color getColor(int x, int y) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null)
|
||||
return c.fill;
|
||||
else
|
||||
return fill;
|
||||
}
|
||||
|
||||
public Color getBackground(int x, int y) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
Color bg = Color.TRANSPARENT;
|
||||
if (c != null && c.bg instanceof Color)
|
||||
bg = (Color) c.bg;
|
||||
else if (background instanceof Color)
|
||||
bg = (Color) background;
|
||||
return bg;
|
||||
}
|
||||
|
||||
public void setBackground(int x, int y, Paint bg) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null) {
|
||||
c.bg = bg;
|
||||
drawChar(x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(int x, int y, Color fill) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null) {
|
||||
c.fill = fill;
|
||||
drawChar(x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
public TextFont getFont() {
|
||||
return font;
|
||||
}
|
||||
|
||||
public boolean getItalics() {
|
||||
return (videoAttrs & TextFont.ATTR_ITALIC) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the leftMargin
|
||||
*/
|
||||
public int getLeftMargin() {
|
||||
return leftMargin;
|
||||
}
|
||||
|
||||
public int getLineWidth() {
|
||||
return textMode.getLineWidth();
|
||||
}
|
||||
|
||||
public int getPageHeight() {
|
||||
return textMode.getPageHeight();
|
||||
}
|
||||
|
||||
public boolean getReverseVideo() {
|
||||
return (videoAttrs & TextFont.ATTR_INVERSE) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the topMargin
|
||||
*/
|
||||
public int getTopMargin() {
|
||||
return topMargin;
|
||||
}
|
||||
|
||||
public int getVideoMode() {
|
||||
return videoAttrs;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public boolean isFilled(int x, int y) {
|
||||
return !getChar(x, y).equals(" ");
|
||||
}
|
||||
|
||||
public void move(int deltaX, int deltaY) {
|
||||
x = constrainX(x + deltaX);
|
||||
y = constrainYOrScroll(y + deltaY);
|
||||
}
|
||||
|
||||
public void moveHoriz(int dist) {
|
||||
x = constrainX(x + dist);
|
||||
}
|
||||
|
||||
public void moveTo(int newX, int newY) {
|
||||
x = constrainX(newX);
|
||||
y = constrainYOrScroll(newY);
|
||||
}
|
||||
|
||||
public void moveVert(int dist) {
|
||||
y = constrainYOrScroll(y + dist);
|
||||
}
|
||||
|
||||
public void plot(int x, int y) {
|
||||
plot(x, y, (a, b) -> a | b);
|
||||
}
|
||||
|
||||
public void plot(int x, int y, BiFunction<Integer, Integer, Integer> op) {
|
||||
int textX = (x) / 2 + 1;
|
||||
int textY = (y) / 2 + 1;
|
||||
int bitPos = (x + 1) % 2 + ((y + 1) % 2) * 2;
|
||||
String blockChar = BlocksAndBoxes.unicodeBlocks[1 << bitPos];
|
||||
// System.out.println(blockChar + ", " + bitPos + ", ("+ (x) + ", " + (y) + ")"+
|
||||
// ", (" + (textX) + ", " + (textY) + ")");
|
||||
String s = BlocksAndBoxes.blockComposeOrOverwrite(getChar(textX, textY), blockChar, op);
|
||||
// System.out.println("Merge '" + getChar(textX, textY) + "' + '" + blockChar +
|
||||
// "' = '" + s + "'");
|
||||
printAt(textX, textY, s);
|
||||
}
|
||||
|
||||
public void print(String s) {
|
||||
addToCharBuffer(s);
|
||||
}
|
||||
|
||||
public void print(String s, Color paint) {
|
||||
Color tmp = fill;
|
||||
fill = paint;
|
||||
addToCharBuffer(s);
|
||||
fill = tmp;
|
||||
}
|
||||
|
||||
public void printAt(int atX, int atY, String s) {
|
||||
moveTo(atX, atY);
|
||||
print(s);
|
||||
}
|
||||
|
||||
public void printAt(int atX, int atY, String s, Color ink) {
|
||||
moveTo(atX, atY);
|
||||
print(s, ink);
|
||||
}
|
||||
|
||||
public void println() {
|
||||
print("\n");
|
||||
}
|
||||
|
||||
public void println(String s) {
|
||||
print(s);
|
||||
print("\n");
|
||||
}
|
||||
|
||||
public void redrawTextPage() {
|
||||
/*
|
||||
* System.out.printf("redrawTextPage benchmark");
|
||||
* System.out.printf(" %5s %5s %7s %4s %5s %5s %5s%n", "ms", "chars",
|
||||
* "ms/char", "mode", "indir", "inv", "fake"); for (int m = -1; m < 8; m++) {
|
||||
* long t0 = System.currentTimeMillis(); int n = 0;
|
||||
*/
|
||||
GraphicsContext context = textPage.getGraphicsContext2D();
|
||||
|
||||
if (background != null && background != Color.TRANSPARENT) {
|
||||
context.setFill(background);
|
||||
context.fillRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
|
||||
} else
|
||||
context.clearRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
|
||||
for (int tmpY = 1; tmpY <= getPageHeight(); tmpY++) {
|
||||
Char[] line = lineBuffer.get(tmpY - 1);
|
||||
for (int tmpX = 1; tmpX <= getLineWidth(); tmpX++) {
|
||||
Char c = line[tmpX - 1];
|
||||
if (c != null) {
|
||||
context.save();
|
||||
context.setFill(c.fill);
|
||||
context.setStroke(c.stroke);
|
||||
font.drawTextAt(context, (tmpX - 1) * getCharWidth(), tmpY * getCharHeight(), c.s,
|
||||
textMode.getCharWidth() / textMode.getCharBoxSize(), c.mode/* m */, c.bg);
|
||||
context.restore();
|
||||
// n++;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
* long t = System.currentTimeMillis() - t0; if (m >= 0)
|
||||
* System.out.printf(" %5d %5d %7.4f %4d %5b %5b %5b%n", t, n, ((double) t) /
|
||||
* n, m, (m & 3) != 0, (m & 1) != 0, (m & 4) != 0); } System.out.println();
|
||||
*/
|
||||
}
|
||||
|
||||
public void resetAttrs() {
|
||||
this.fill = DEFAULT_FILL;
|
||||
this.stroke = DEFAULT_STROKE;
|
||||
this.background = DEFAULT_BACKGROUND;
|
||||
this.videoAttrs = 0;
|
||||
this.csiSeq = null;
|
||||
this.csiMode = 0;
|
||||
}
|
||||
|
||||
public void resetFull() {
|
||||
resetAttrs();
|
||||
beginningOfPage();
|
||||
this.autoscroll = true;
|
||||
this.textMode = DEFAULT_MODE;
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public void restoreCursor() {
|
||||
x = savedX;
|
||||
y = savedY;
|
||||
}
|
||||
|
||||
public void saveCursor() {
|
||||
savedX = x;
|
||||
savedY = y;
|
||||
}
|
||||
|
||||
void scroll(int i) {
|
||||
while (i < 0) {
|
||||
scrollDown();
|
||||
i++;
|
||||
}
|
||||
while (i > 0) {
|
||||
scrollUp();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollDown() {
|
||||
Char[] remove = lineBuffer.remove(lineBuffer.size() - 1);
|
||||
Arrays.fill(remove, null);
|
||||
lineBuffer.add(0, remove);
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public void scrollUp() {
|
||||
Char[] remove = lineBuffer.remove(0);
|
||||
Arrays.fill(remove, null);
|
||||
lineBuffer.add(remove);
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public boolean setAutoScroll(boolean autoScroll) {
|
||||
boolean old = autoscroll;
|
||||
autoscroll = autoScroll;
|
||||
return old;
|
||||
}
|
||||
|
||||
public void setBackground(Paint bgColor) {
|
||||
this.background = bgColor != null ? bgColor : DEFAULT_BACKGROUND;
|
||||
}
|
||||
|
||||
public void setBold(boolean enabled) {
|
||||
if (enabled)
|
||||
videoAttrs |= TextFont.ATTR_BRIGHT;
|
||||
else
|
||||
videoAttrs &= ~TextFont.ATTR_BRIGHT;
|
||||
}
|
||||
|
||||
public Char setChar(int x, int y, String s) {
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
Char c = new Char(s, fill, stroke, background, videoAttrs);
|
||||
lineBuffer.get(y - 1)[x - 1] = c;
|
||||
return c;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setFill(Color fill) {
|
||||
this.fill = fill != null ? fill : DEFAULT_FILL;
|
||||
}
|
||||
|
||||
public void setFont(TextFont font) {
|
||||
this.font = font;
|
||||
}
|
||||
|
||||
public void setInk(Color ink) {
|
||||
fill = ink != null ? ink : DEFAULT_FILL;
|
||||
stroke = ink != null ? ink : DEFAULT_STROKE;
|
||||
}
|
||||
|
||||
public void setItalics(boolean enabled) {
|
||||
if (enabled)
|
||||
videoAttrs |= TextFont.ATTR_ITALIC;
|
||||
else
|
||||
videoAttrs &= ~TextFont.ATTR_ITALIC;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public void setLeftMargin() {
|
||||
this.leftMargin = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param leftMargin
|
||||
* the leftMargin to set
|
||||
*/
|
||||
public void setLeftMargin(int leftMargin) {
|
||||
this.leftMargin = constrainX(leftMargin);
|
||||
}
|
||||
|
||||
public void setReverseVideo(boolean enabled) {
|
||||
if (enabled)
|
||||
videoAttrs |= TextFont.ATTR_INVERSE;
|
||||
else
|
||||
videoAttrs &= ~TextFont.ATTR_INVERSE;
|
||||
}
|
||||
|
||||
public void setStroke(Color stroke) {
|
||||
this.stroke = stroke != null ? stroke : DEFAULT_STROKE;
|
||||
}
|
||||
|
||||
public void setTopMargin() {
|
||||
this.topMargin = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param topMargin
|
||||
* the topMargin to set
|
||||
*/
|
||||
public void setTopMargin(int topMargin) {
|
||||
this.topMargin = constrainY(topMargin);
|
||||
}
|
||||
|
||||
public void setVideoAttrs(int attr) {
|
||||
videoAttrs = attr;
|
||||
}
|
||||
|
||||
public void setVideoAttrDisabled(int attr) {
|
||||
videoAttrs &= ~attr;
|
||||
}
|
||||
|
||||
public void setVideoAttrEnabled(int attr) {
|
||||
videoAttrs |= attr;
|
||||
}
|
||||
|
||||
public void setTextMode(TextMode mode) {
|
||||
setTextMode(mode, false);
|
||||
}
|
||||
|
||||
public void setTextMode(TextMode mode, boolean adjustDisplayAspect) {
|
||||
if (mode == null)
|
||||
throw new IllegalArgumentException();
|
||||
textMode = mode;
|
||||
if (adjustDisplayAspect)
|
||||
screen.setAspect(textMode.getAspect());
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public TextMode getTextMode() {
|
||||
return textMode;
|
||||
}
|
||||
|
||||
public void unplot(int x, int y) {
|
||||
plot(x, y, (a, b) -> a & ~b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToFront() {
|
||||
screen.moveToFront(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToBack() {
|
||||
screen.moveToBack(this);
|
||||
}
|
||||
|
||||
public void clearLine(int y) {
|
||||
y = constrainY(y);
|
||||
if (y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
Arrays.fill(lineBuffer.get(y - 1), null);
|
||||
redrawTextPage();
|
||||
}
|
||||
}
|
||||
}
|
980
src/inf101/v18/gfx/textmode/TextFont.java
Normal file
980
src/inf101/v18/gfx/textmode/TextFont.java
Normal file
@@ -0,0 +1,980 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javafx.geometry.Point2D;
|
||||
import javafx.scene.SnapshotParameters;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.PixelReader;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.transform.Affine;
|
||||
|
||||
/**
|
||||
* TextFont – for grid-based text / character graphics
|
||||
* <p>
|
||||
* TextFonts are used for drawing text and character graphics. Each character is
|
||||
* assumed to be uniform in size, fitting within a square with sides of length
|
||||
* {@link #getSquareSize()}. The given font file should contain a monospaced
|
||||
* font of a type suitable for JavaFX, such as OpenType or TrueType.
|
||||
*
|
||||
* <p>
|
||||
* Additional horizontal and vertical positioning and scaling can be used to
|
||||
* make the font fit with the square-shaped concept.
|
||||
*
|
||||
* <p>
|
||||
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
|
||||
* context for writing with the font, or
|
||||
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
|
||||
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
|
||||
*
|
||||
*/
|
||||
public class TextFont {
|
||||
public static final int ATTR_INVERSE = 0x01;
|
||||
public static final int ATTR_ITALIC = 0x02;
|
||||
public static final int ATTR_BOLD = 0x04;
|
||||
public static final int ATTR_OUTLINE = 0x08;
|
||||
public static final int ATTR_UNDERLINE = 0x10;
|
||||
public static final int ATTR_OVERLINE = 0x20;
|
||||
public static final int ATTR_LINE_THROUGH = 0x40;
|
||||
public static final int ATTR_OVERSTRIKE = 0x80;
|
||||
public static final int ATTR_CLIP = 0x80;
|
||||
public static final int ATTR_NO_FAKE_CHARS = 0x100;
|
||||
public static final int ATTR_BLINK = 0x200; // NOT IMPLEMENTED
|
||||
public static final int ATTR_FAINT = 0x400; // NOT IMPLEMENTED
|
||||
public static final int ATTR_BRIGHT = 0x800;
|
||||
|
||||
private static final String[] searchPath = { "", "../", "../fonts/" };
|
||||
private static final Map<String, String> loadedFonts = new HashMap<>();
|
||||
private static final double thin = 2.0, thick = 4.0;
|
||||
private static final String[] boxDrawingShapes = { // lines
|
||||
"--..", "**..", "..--", "..**",
|
||||
// dashed lines
|
||||
"3--..", "3**..", "3..--", "3..**", "4--..", "4**..", "4..--", "4..**",
|
||||
// corners
|
||||
".-.-", ".*.-", ".-.*", ".*.*", "-..-", "*..-", "-..*", "*..*", ".--.", ".*-.", ".-*.", ".**.", "-.-.",
|
||||
"*.-.", "-.*.", "*.*.",
|
||||
// |-
|
||||
".---", ".*--", ".-*- ", ".--* ", ".-**", ".**-", ".*-*", ".***", "-.--", "*.--", "-.*-", "-.-*", "-.**",
|
||||
"*.*-", "*.-*", "*.**",
|
||||
// T
|
||||
"--.-", "*-.-", "-*.-", "**.-", "--.*", "*-.*", "-*.*", "**.*", "---.", "*--. ", "-*-.", "**-.", "--*.",
|
||||
"*-*.", "-**.", "***.",
|
||||
// +
|
||||
"----", "*---", "-*--", "**--", "--*-", "---*", "--**", "*-*-", "-**-", "*--*", "-*-*", "***-", "**-*",
|
||||
"*-**", "-***", "****",
|
||||
// dashes
|
||||
"2--..", "2**..", "2..--", "2..**",
|
||||
// double lines
|
||||
"==..", "..==", ".=.-", ".-.=", ".N.N", "=..-", "-..=", "M..M", ".=-.", ".-=.", ".MM.", "=.-.", "-.=.",
|
||||
"N.N.", ".=--", ".s==", ".zMN", "=.--", "s.==", "z.NM", "==.s", "--.=", "MN.z", "==s.", "--=.", "NMz.",
|
||||
"==--", "--==", "zzzz",
|
||||
// round corners
|
||||
"0.-.-", "0-..-", "0-.-.", "0.--.",
|
||||
// diagonals
|
||||
"////", "\\\\\\\\", "//\\\\\\",
|
||||
// short lines
|
||||
"-...", "..-.", ".-..", "...-", "*...", "..*.", ".*..", "...*",
|
||||
// thin/thick lines
|
||||
"-*..", "..-*", "*-..", "..*-" };
|
||||
|
||||
public static void drawMasked(GraphicsContext ctx, Image src, Image mask, double x, double y, boolean invert,
|
||||
boolean blend) {
|
||||
int w = (int) src.getWidth();
|
||||
int h = (int) src.getHeight();
|
||||
|
||||
PixelReader pixelSrc = src.getPixelReader();
|
||||
PixelReader pixelMask = mask.getPixelReader();
|
||||
PixelWriter pixelWriter = ctx.getPixelWriter();
|
||||
Affine transform = ctx.getTransform();
|
||||
Point2D point = transform.transform(x, y);
|
||||
int dx = (int) point.getX(), dy = (int) point.getY();
|
||||
|
||||
for (int px = 0; px < w; px++) {
|
||||
for (int py = 0; py < h; py++) {
|
||||
int a = pixelMask.getArgb(px, py) >>> 24;
|
||||
int rgb = pixelSrc.getArgb(px, py);
|
||||
if (invert)
|
||||
a = ~a & 0xff;
|
||||
if (blend)
|
||||
a = ((rgb >>> 24) * a) >>> 8;
|
||||
pixelWriter.setArgb(px + dx, py + dy, (a << 24) | rgb);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void fillInverse(GraphicsContext ctx, Image img, double x, double y) {
|
||||
int w = (int) img.getWidth();
|
||||
int h = (int) img.getHeight();
|
||||
PixelReader pixelReader = img.getPixelReader();
|
||||
PixelWriter pixelWriter = ctx.getPixelWriter();
|
||||
Affine transform = ctx.getTransform();
|
||||
Point2D point = transform.transform(x, y);
|
||||
int dx = (int) point.getX(), dy = (int) point.getY();
|
||||
Color c = ctx.getFill() instanceof Color ? (Color) ctx.getFill() : Color.BLACK;
|
||||
int rgb = ((int) (c.getRed() * 255)) << 16 | ((int) (c.getGreen() * 255)) << 8 | ((int) (c.getBlue() * 255));
|
||||
|
||||
for (int px = 0; px < w; px++) {
|
||||
for (int py = 0; py < h; py++) {
|
||||
int a = (~pixelReader.getArgb(px, py) & 0xff000000);
|
||||
// if(a != 0)
|
||||
pixelWriter.setArgb(px + dx, py + dy, a | rgb);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a font.
|
||||
*
|
||||
* Will first try to use the provided string as a file name, and load the font
|
||||
* from a font file (should be in a format supported by JavaFX). If loading from
|
||||
* file fails, we assume we have been given the name of a font which is passed
|
||||
* directly to JavaFX for loading. A fallback font is used if this fails.
|
||||
*
|
||||
* When looking for files, his method will search for the file in a search path
|
||||
* starting with the folder containing the {@link TextFont} class file (using
|
||||
* Java's standard {@link Class#getResourceAsStream(String)} system). The search
|
||||
* path also includes ".." (parent directory) and "../fonts".
|
||||
*
|
||||
* The loaded font will be cached, so that additional calls with the same file
|
||||
* name will not cause the file to be loaded again.
|
||||
*
|
||||
* If the font cannot be loaded, a default font will be substituted. You may
|
||||
* check which font you got using {@link Font#getName()} or
|
||||
* {@link Font#getFamily()}.
|
||||
*
|
||||
* @param name
|
||||
* Font name, or relative path to the file
|
||||
* @param size
|
||||
* Desired point size of the font
|
||||
* @return A JavaFX font
|
||||
*/
|
||||
public static final Font findFont(String name, double size) {
|
||||
return findFont(name, size, TextFont.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a font.
|
||||
*
|
||||
* Will first try to use the provided string as a file name, and load the font
|
||||
* from a font file (should be in a format supported by JavaFX). If loading from
|
||||
* file fails, we assume we have been given the name of a font which is passed
|
||||
* directly to JavaFX for loading. A fallback font is used if this fails.
|
||||
*
|
||||
* When looking for files, this method will search for the file in a search path
|
||||
* starting with the folder containing the provided Java class (using Java's
|
||||
* standard {@link Class#getResourceAsStream(String)} system). The search path
|
||||
* also includes ".." (parent directory) and "../fonts".
|
||||
*
|
||||
* The loaded font will be cached, so that additional calls with the same file
|
||||
* name will not cause the file to be loaded again.
|
||||
*
|
||||
* If the font cannot be loaded, a default font will be substituted. You may
|
||||
* check which font you got using {@link Font#getName()} or
|
||||
* {@link Font#getFamily()}.
|
||||
*
|
||||
* @param name
|
||||
* Name of a font, or relative path to the font file
|
||||
* @param size
|
||||
* Desired point size of the font
|
||||
* @param clazz
|
||||
* A class for finding files relative to
|
||||
* @return A JavaFX font
|
||||
*/
|
||||
public static final Font findFont(String name, double size, Class<?> clazz) {
|
||||
if (name == null || size < 0)
|
||||
throw new IllegalArgumentException();
|
||||
|
||||
if (loadedFonts.containsKey(name))
|
||||
return Font.font(loadedFonts.get(name), size);
|
||||
|
||||
for (String path : searchPath) {
|
||||
try (InputStream stream = clazz.getResourceAsStream(path + name)) {
|
||||
Font font = Font.loadFont(stream, size);
|
||||
if (font != null) {
|
||||
loadedFonts.put(name, font.getName());
|
||||
// System.err.println("Found: " + font.getName());
|
||||
return font;
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
// we'll just try the next alternative in the search path
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Font font = Font.font(name, size);
|
||||
if (font == null)
|
||||
font = Font.font(size);
|
||||
if (font == null)
|
||||
throw new RuntimeException("Even the default font seems to be unavailable – this shouldn't happen! :(");
|
||||
if (font.getName().equals(Font.getDefault().getName())) {
|
||||
System.err.println("TextFont: Default font '" + font.getName() + "' substituted for '" + name + "'");
|
||||
}
|
||||
// System.err.println("Found: " + name + "=" + font.getName());
|
||||
|
||||
loadedFonts.put(name, name);
|
||||
return font;
|
||||
}
|
||||
|
||||
private Canvas tmpCanvas;
|
||||
private final WritableImage img;
|
||||
|
||||
/**
|
||||
* The JavaFX font
|
||||
*/
|
||||
private Font font;
|
||||
/** Font size */
|
||||
private final double size;
|
||||
|
||||
/**
|
||||
* Horizontal positioning of letters.
|
||||
*
|
||||
* Each letter should be approximately centered within its available
|
||||
* square-shaped space.
|
||||
*/
|
||||
private final double xTranslate;
|
||||
|
||||
/**
|
||||
* Vertical positioning of letters.
|
||||
*
|
||||
* Each letter should be positioned on the baseline so that ascenders and
|
||||
* descenders fall within its available square-shaped space.
|
||||
*/
|
||||
private final double yTranslate;
|
||||
|
||||
/**
|
||||
* Horizontal scaling factor (1.0 means no scaling)
|
||||
*
|
||||
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit
|
||||
* a square shape.
|
||||
*/
|
||||
private final double xScale;
|
||||
|
||||
/**
|
||||
* Vertical scaling factor (1.0 means no scaling)
|
||||
*/
|
||||
private final double yScale;
|
||||
|
||||
/**
|
||||
* Width and height of the square-shaped space each letter should fit within
|
||||
*/
|
||||
private double squareSize;
|
||||
|
||||
private String fileName;
|
||||
|
||||
SnapshotParameters snapshotParameters = new SnapshotParameters();
|
||||
|
||||
{
|
||||
snapshotParameters.setFill(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TextFont.
|
||||
*
|
||||
* <p>
|
||||
* TextFonts are used for drawing text and character graphics. Each character is
|
||||
* assumed to be uniform in size, fitting within a square with sides of length
|
||||
* {@link #getSquareSize()}. The given font file should contain a monospaced
|
||||
* font of a type suitable for JavaFX, such as OpenType or TrueType.
|
||||
*
|
||||
* <p>
|
||||
* Additional horizontal and vertical positioning and scaling can be used to
|
||||
* make the font fit with the square-shaped concept.
|
||||
*
|
||||
* <p>
|
||||
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
|
||||
* context for writing with the font, or
|
||||
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
|
||||
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
|
||||
*
|
||||
*
|
||||
* @param font
|
||||
* Name of the font file. Will search for the file in the same folder
|
||||
* as the TextFont class, as well as ".." and "../fonts".
|
||||
* @param size
|
||||
* Point size of the font.
|
||||
* @param squareSize
|
||||
* The width and height of a square defining the bounds of letters
|
||||
* @param xTranslate
|
||||
* Horizontal positioning of letters
|
||||
* @param yTranslate
|
||||
* Vertical positioning of letters
|
||||
* @param xScale
|
||||
* Horizontal scaling factor
|
||||
* @param yScale
|
||||
* Vertical scaling factor
|
||||
*/
|
||||
public TextFont(Font font, double squareSize, double xTranslate, double yTranslate, double xScale, double yScale) {
|
||||
super();
|
||||
this.fileName = font.getName();
|
||||
this.font = font;
|
||||
this.size = font.getSize();
|
||||
this.squareSize = squareSize;
|
||||
this.xTranslate = xTranslate;
|
||||
this.yTranslate = yTranslate;
|
||||
this.xScale = xScale;
|
||||
this.yScale = yScale;
|
||||
this.img = new WritableImage((int) squareSize, (int) squareSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TextFont.
|
||||
*
|
||||
* <p>
|
||||
* TextFonts are used for drawing text and character graphics. Each character is
|
||||
* assumed to be uniform in size, fitting within a square with sides of length
|
||||
* {@link #getSquareSize()}. The given font file should contain a monospaced
|
||||
* font of a type suitable for JavaFX, such as OpenType or TrueType.
|
||||
*
|
||||
* <p>
|
||||
* Additional horizontal and vertical positioning and scaling can be used to
|
||||
* make the font fit with the square-shaped concept.
|
||||
*
|
||||
* <p>
|
||||
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
|
||||
* context for writing with the font, or
|
||||
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
|
||||
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
|
||||
*
|
||||
*
|
||||
* @param font
|
||||
* Name of the font file. Will search for the file in the same folder
|
||||
* as the TextFont class, as well as ".." and "../fonts".
|
||||
* @param size
|
||||
* Point size of the font.
|
||||
* @param squareSize
|
||||
* The width and height of a square defining the bounds of letters
|
||||
* @param xTranslate
|
||||
* Horizontal positioning of letters
|
||||
* @param yTranslate
|
||||
* Vertical positioning of letters
|
||||
* @param xScale
|
||||
* Horizontal scaling factor
|
||||
* @param yScale
|
||||
* Vertical scaling factor
|
||||
*/
|
||||
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
|
||||
double xScale, double yScale) {
|
||||
super();
|
||||
this.fileName = fileName;
|
||||
this.font = findFont(fileName, size);
|
||||
this.size = size;
|
||||
this.squareSize = squareSize;
|
||||
this.xTranslate = xTranslate;
|
||||
this.yTranslate = yTranslate;
|
||||
this.xScale = xScale;
|
||||
this.yScale = yScale;
|
||||
this.img = new WritableImage((int) squareSize, (int) squareSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new TextFont.
|
||||
*
|
||||
* <p>
|
||||
* TextFonts are used for drawing text and character graphics. Each character is
|
||||
* assumed to be uniform in size, fitting within a square with sides of length
|
||||
* {@link #getSquareSize()}. The given font file should contain a monospaced
|
||||
* font of a type suitable for JavaFX, such as OpenType or TrueType.
|
||||
*
|
||||
* <p>
|
||||
* Additional horizontal and vertical positioning and scaling can be used to
|
||||
* make the font fit with the square-shaped concept.
|
||||
*
|
||||
* <p>
|
||||
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
|
||||
* context for writing with the font, or
|
||||
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
|
||||
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
|
||||
*
|
||||
*
|
||||
* @param font
|
||||
* Name of the font file. Will search for the file in the same folder
|
||||
* as the TextFont class, as well as ".." and "../fonts".
|
||||
* @param size
|
||||
* Point size of the font.
|
||||
* @param squareSize
|
||||
* The width and height of a square defining the bounds of letters
|
||||
* @param xTranslate
|
||||
* Horizontal positioning of letters
|
||||
* @param yTranslate
|
||||
* Vertical positioning of letters
|
||||
* @param xScale
|
||||
* Horizontal scaling factor
|
||||
* @param yScale
|
||||
* Vertical scaling factor
|
||||
* @param deferLoading
|
||||
* True if the font file shouldn't be loaded before the font is
|
||||
* actually used
|
||||
*/
|
||||
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
|
||||
double xScale, double yScale, boolean deferLoading) {
|
||||
super();
|
||||
this.fileName = fileName;
|
||||
this.font = deferLoading ? null : findFont(fileName, size);
|
||||
this.size = size;
|
||||
this.squareSize = squareSize;
|
||||
this.xTranslate = xTranslate;
|
||||
this.yTranslate = yTranslate;
|
||||
this.xScale = xScale;
|
||||
this.yScale = yScale;
|
||||
this.img = new WritableImage((int) squareSize, (int) squareSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of this font, with the given adjustments to the translation and
|
||||
* scaling.
|
||||
*
|
||||
* @param deltaXTranslate
|
||||
* @param deltaYTranslate
|
||||
* @param deltaXScale
|
||||
* @param deltaYScale
|
||||
* @return
|
||||
*/
|
||||
public TextFont adjust(double size, double deltaXTranslate, double deltaYTranslate, double deltaXScale,
|
||||
double deltaYScale) {
|
||||
if (size == 0.0) {
|
||||
return new TextFont(fileName, this.size, squareSize, xTranslate + deltaXTranslate,
|
||||
yTranslate + deltaYTranslate, xScale + deltaXScale, yScale + deltaYScale);
|
||||
} else {
|
||||
return new TextFont(fileName, this.size + size, squareSize, xTranslate + deltaXTranslate,
|
||||
yTranslate + deltaYTranslate, xScale + deltaXScale, yScale + deltaYScale);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given text at position (0,0).
|
||||
*
|
||||
* The <code>ctx</code> should normally be translated to the appropriate text
|
||||
* position before calling this method.
|
||||
*
|
||||
* Text will be clipped so each character fits its expected square-shaped area,
|
||||
* and the area will be cleared to transparency before drwaing.
|
||||
*
|
||||
* The graphics context's current path will be overwritten.
|
||||
*
|
||||
* @param ctx
|
||||
* a grapics context
|
||||
* @param text
|
||||
* string to be printed
|
||||
*/
|
||||
public void drawText(GraphicsContext ctx, String text) {
|
||||
textAt(ctx, 0.0, 0.0, text, 1.0, true, true, ctx.getFill(), null, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given text at position (0,0), with horizontal scaling.
|
||||
*
|
||||
* The <code>ctx</code> should normally be translated to the appropriate text
|
||||
* position before calling this method.
|
||||
*
|
||||
* Text will be clipped so each character fits its expected square-shaped area,
|
||||
* and the area will be cleared to transparency before drwaing.
|
||||
*
|
||||
* The graphics context's current path will be overwritten.
|
||||
*
|
||||
* @param ctx
|
||||
* a grapics context
|
||||
* @param text
|
||||
* string to be printed
|
||||
* @param xScaleFactor
|
||||
* a horizontal scaling factor
|
||||
*/
|
||||
public void drawText(GraphicsContext ctx, String text, double xScaleFactor) {
|
||||
textAt(ctx, 0.0, 0.0, text, xScaleFactor, true, false, ctx.getFill(), null, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given text at position (x,y).
|
||||
*
|
||||
* Text will be clipped so each character fits its expected square-shaped area,
|
||||
* and the area will be cleared to transparency before drwaing.
|
||||
*
|
||||
* The graphics context's current path will be overwritten.
|
||||
*
|
||||
* @param ctx
|
||||
* a grapics context
|
||||
* @param x
|
||||
* X-position of the lower left corner of the text
|
||||
* @param y
|
||||
* Y-position of the lower left corner of the text
|
||||
* @param text
|
||||
* string to be printed
|
||||
*/
|
||||
public void drawTextAt(GraphicsContext ctx, double x, double y, String text) {
|
||||
textAt(ctx, x, y, text, 1.0, true, false, ctx.getFill(), null, 0, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the given text at position (x, y), with horizontal scaling.
|
||||
*
|
||||
* Text will be clipped so each character fits its expected square-shaped area,
|
||||
* and the area will be cleared to transparency before drwaing.
|
||||
*
|
||||
* The graphics context's current path will be overwritten.
|
||||
*
|
||||
* @param ctx
|
||||
* a grapics context
|
||||
* @param x
|
||||
* X-position of the lower left corner of the text
|
||||
* @param y
|
||||
* Y-position of the lower left corner of the text
|
||||
* @param text
|
||||
* string to be printed
|
||||
* @param xScaleFactor
|
||||
* a horizontal scaling factor
|
||||
*/
|
||||
public void drawTextAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode,
|
||||
Paint bg) {
|
||||
textAt(ctx, x, y, text, xScaleFactor, true, false, ctx.getFill(), null, mode, bg);
|
||||
}
|
||||
|
||||
private void fakeBlockElement(GraphicsContext ctx, String text, double xScaleFactor) {
|
||||
text.codePoints().forEach((int c) -> {
|
||||
// System.out.printf("%s %x%n", text, c);
|
||||
if (c >= 0x2596 && c <= 0x259f) { // quadrants
|
||||
int bits = BlocksAndBoxes.unicodeBlocksString.indexOf(c);
|
||||
if ((bits & 1) > 0) { // lower right
|
||||
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize / 2, (squareSize / 2) * xScaleFactor,
|
||||
squareSize / 2);
|
||||
}
|
||||
if ((bits & 2) > 0) { // lower left
|
||||
ctx.fillRect(0, -squareSize / 2, (squareSize / 2) * xScaleFactor, squareSize / 2);
|
||||
}
|
||||
if ((bits & 4) > 0) { // upper right
|
||||
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize, (squareSize / 2) * xScaleFactor,
|
||||
squareSize / 2);
|
||||
}
|
||||
if ((bits & 8) > 0) { // upper left
|
||||
ctx.fillRect(0, -squareSize, (squareSize / 2) * xScaleFactor, squareSize / 2);
|
||||
}
|
||||
} else if (c == 0x2580) { // upper half
|
||||
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize / 2);
|
||||
} else if (c > 0x2580 && c <= 0x2588) { // x/8 block
|
||||
int height = c - 0x2580;
|
||||
ctx.fillRect(0, -(height * squareSize) / 8, (squareSize) * xScaleFactor, (height * squareSize) / 8);
|
||||
} else if (c >= 0x2589 && c <= 0x258f) { // x/8 block
|
||||
int width = 8 - (c - 0x2588);
|
||||
ctx.fillRect(0, -squareSize, ((width * squareSize) / 8) * xScaleFactor, squareSize);
|
||||
} else if (c == 0x2590) { // right half
|
||||
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize, (squareSize / 2) * xScaleFactor, squareSize);
|
||||
} else if (c == 0x2591) { // light shade
|
||||
ctx.save();
|
||||
ctx.setGlobalAlpha(0.25);
|
||||
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
|
||||
ctx.restore();
|
||||
} else if (c == 0x2592) { // medium shade
|
||||
ctx.save();
|
||||
ctx.setGlobalAlpha(0.5);
|
||||
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
|
||||
ctx.restore();
|
||||
} else if (c == 0x2593) { // dark shade
|
||||
ctx.save();
|
||||
ctx.setGlobalAlpha(0.75);
|
||||
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
|
||||
ctx.restore();
|
||||
} else if (c == 0x2594) { // upper eighth
|
||||
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize / 8);
|
||||
} else if (c == 0x2595) { // right eighth
|
||||
ctx.fillRect(((7 * squareSize) / 8) * xScaleFactor, -squareSize, (squareSize / 8) * xScaleFactor,
|
||||
squareSize);
|
||||
} else if (c == 0x2571) {
|
||||
ctx.save();
|
||||
ctx.setLineWidth(2.0);
|
||||
ctx.strokeLine(0, 0, squareSize * xScaleFactor, -squareSize);
|
||||
ctx.restore();
|
||||
} else if (c == 0x2572) {
|
||||
ctx.save();
|
||||
ctx.setLineWidth(2.0);
|
||||
ctx.strokeLine(0, -squareSize, squareSize * xScaleFactor, 0);
|
||||
ctx.restore();
|
||||
} else if (c == 0x2573) {
|
||||
ctx.save();
|
||||
ctx.setLineWidth(2.0);
|
||||
ctx.strokeLine(0, 0, squareSize * xScaleFactor, -squareSize);
|
||||
ctx.strokeLine(0, -squareSize, squareSize * xScaleFactor, 0);
|
||||
ctx.restore();
|
||||
} else if (c >= 0x2500 && c <= 0x257f) {
|
||||
ctx.save();
|
||||
ctx.setLineCap(StrokeLineCap.BUTT);
|
||||
String spec = boxDrawingShapes[c - 0x2500];
|
||||
int i = 0;
|
||||
double extraThickness = 0.0;
|
||||
if (Character.isDigit(spec.charAt(i))) {
|
||||
extraThickness = setContextFromChar(ctx, spec.charAt(i++));
|
||||
}
|
||||
char s;
|
||||
double hThickness = Math.max(setContextFromChar(ctx, spec.charAt(i)),
|
||||
setContextFromChar(ctx, spec.charAt(i + 1))) + extraThickness;
|
||||
double vThickness = Math.max(setContextFromChar(ctx, spec.charAt(i + 2)),
|
||||
setContextFromChar(ctx, spec.charAt(i + 3))) + extraThickness;
|
||||
if (c >= 0x2550 || spec.charAt(i) != spec.charAt(i + 1)) {
|
||||
s = spec.charAt(i++);
|
||||
setContextFromChar(ctx, s);
|
||||
strokeHalfLine(ctx, xScaleFactor, -1, 0, s, vThickness);
|
||||
s = spec.charAt(i++);
|
||||
setContextFromChar(ctx, s);
|
||||
strokeHalfLine(ctx, xScaleFactor, 1, 0, s, vThickness);
|
||||
} else {
|
||||
s = spec.charAt(i++);
|
||||
s = spec.charAt(i++);
|
||||
setContextFromChar(ctx, s);
|
||||
strokeFullLine(ctx, xScaleFactor, 1, 0, s);
|
||||
}
|
||||
if (c >= 0x2550 || spec.charAt(i) != spec.charAt(i + 1)) {
|
||||
s = spec.charAt(i++);
|
||||
setContextFromChar(ctx, s);
|
||||
strokeHalfLine(ctx, xScaleFactor, 0, -1, s, hThickness);
|
||||
s = spec.charAt(i++);
|
||||
setContextFromChar(ctx, s);
|
||||
strokeHalfLine(ctx, xScaleFactor, 0, 1, s, hThickness);
|
||||
} else {
|
||||
s = spec.charAt(i++);
|
||||
s = spec.charAt(i++);
|
||||
setContextFromChar(ctx, s);
|
||||
strokeFullLine(ctx, xScaleFactor, 0, 1, s);
|
||||
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the font
|
||||
*/
|
||||
public Font getFont() {
|
||||
if (font == null)
|
||||
font = findFont(fileName, size);
|
||||
return font;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size
|
||||
*/
|
||||
public double getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Width and height of the square-shaped space each letter should fit within
|
||||
*
|
||||
* @return the squareSize
|
||||
*/
|
||||
public double getSquareSize() {
|
||||
return squareSize;
|
||||
}
|
||||
|
||||
private Canvas getTmpCanvas() {
|
||||
if (tmpCanvas == null) {
|
||||
tmpCanvas = new Canvas(squareSize, squareSize);
|
||||
} else {
|
||||
tmpCanvas.getGraphicsContext2D().clearRect(0, 0, squareSize, squareSize);
|
||||
}
|
||||
return tmpCanvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal scaling factor (1.0 means no scaling)
|
||||
*
|
||||
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit
|
||||
* a square shape.
|
||||
*
|
||||
* @return the xScale
|
||||
*/
|
||||
public double getxScale() {
|
||||
return xScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal positioning of letters.
|
||||
*
|
||||
* Each letter should be approximately centered within its available
|
||||
* square-shaped space.
|
||||
*
|
||||
* @return the xTranslate
|
||||
*/
|
||||
public double getxTranslate() {
|
||||
return xTranslate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertical scaling factor (1.0 means no scaling)
|
||||
*
|
||||
* @return the yScale
|
||||
*/
|
||||
public double getyScale() {
|
||||
return yScale;
|
||||
}
|
||||
|
||||
/**
|
||||
* /** Vertical positioning of letters.
|
||||
*
|
||||
* Each letter should be positioned on the baseline so that ascenders and
|
||||
* descenders fall within its available square-shaped space.
|
||||
*
|
||||
* @return the yTranslate
|
||||
*/
|
||||
public double getyTranslate() {
|
||||
return yTranslate;
|
||||
}
|
||||
|
||||
private double setContextFromChar(GraphicsContext ctx, char c) {
|
||||
switch (c) {
|
||||
case '0':
|
||||
return -2 * thin;
|
||||
case '2':
|
||||
ctx.setLineDashes(new double[] { 14.75, 2.5 });
|
||||
break;
|
||||
case '3':
|
||||
ctx.setLineDashes(new double[] { 9, 2.5 });
|
||||
break;
|
||||
case '4':
|
||||
ctx.setLineDashes(new double[] { 6.125, 2.5 });
|
||||
break;
|
||||
case '.':
|
||||
return 0.0;
|
||||
case '-':
|
||||
case 's':
|
||||
ctx.setLineWidth(thin);
|
||||
return thin;
|
||||
case '*':
|
||||
ctx.setLineWidth(thick);
|
||||
return thick;
|
||||
case '=':
|
||||
case 'N':
|
||||
case 'M':
|
||||
case 'z':
|
||||
ctx.setLineWidth(thin);
|
||||
return thin;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a graphics context for drawing with this font.
|
||||
*
|
||||
* Caller should call {@link GraphicsContext#save()} first, and then
|
||||
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
|
||||
* transformation matrix (i.e., translation, scaling).
|
||||
*
|
||||
* The GraphicsContext should be translated to the coordinates where the text
|
||||
* should appear <em>before</em> calling this method, since this method will
|
||||
* modify the coordinate system.
|
||||
*
|
||||
* @param ctx
|
||||
* A GraphicsContext
|
||||
*/
|
||||
public void setGraphicsContext(GraphicsContext ctx) {
|
||||
ctx.setFont(getFont());
|
||||
ctx.translate(xTranslate, yTranslate);
|
||||
ctx.scale(xScale, yScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a graphics context for drawing with this font.
|
||||
*
|
||||
* Caller should call {@link GraphicsContext#save()} first, and then
|
||||
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
|
||||
* transformation matrix (i.e., translation, scaling).
|
||||
*
|
||||
* The GraphicsContext should be translated to the coordinates where the text
|
||||
* should appear <em>before</em> calling this method, since this method will
|
||||
* modify the coordinate system.
|
||||
*
|
||||
* @param ctx
|
||||
* A GraphicsContext
|
||||
* @param xScaleFactor
|
||||
* Additional horizontal scaling, normally 0.5 (for half-width
|
||||
* characters)
|
||||
*/
|
||||
public void setGraphicsContext(GraphicsContext ctx, double xScaleFactor) {
|
||||
ctx.setFont(getFont());
|
||||
ctx.translate(xTranslate * xScaleFactor, yTranslate);
|
||||
ctx.scale(xScale * xScaleFactor, yScale);
|
||||
}
|
||||
|
||||
private void strokeFullLine(GraphicsContext ctx, double xScaleFactor, double xDir, double yDir, char c) {
|
||||
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
|
||||
if (c != '.') {
|
||||
// System.out.printf("(%4.1f %4.1f %4.1f %4.1f) w=%1.4f%n", x * yDir, y * xDir,
|
||||
// x * yDir + xDir * squareSize,
|
||||
// y * xDir + yDir * squareSize, ctx.getLineWidth());
|
||||
if (c == '=') {
|
||||
ctx.setLineWidth(2.0);
|
||||
ctx.strokeLine((x - 2) * yDir, (y - 2) * xDir, (x - 2) * yDir + xDir * squareSize * xScaleFactor,
|
||||
(y - 2) * xDir + yDir * -squareSize);
|
||||
ctx.strokeLine((x + 2) * yDir, (y + 2) * xDir, (x + 2) * yDir + xDir * squareSize * xScaleFactor,
|
||||
(y + 2) * xDir + yDir * -squareSize);
|
||||
} else {
|
||||
ctx.strokeLine(x * yDir, y * xDir, x * yDir + xDir * squareSize * xScaleFactor,
|
||||
y * xDir + yDir * -squareSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void strokeHalfLine(GraphicsContext ctx, double xScaleFactor, double xDir, double yDir, char c,
|
||||
double otherWidth) {
|
||||
if (c != '.') {
|
||||
double factor = 1.0, dblFactor = 0.0;
|
||||
if (c == 's' || c == 'z')
|
||||
factor = -1;
|
||||
if (c == 'N')
|
||||
dblFactor = -2;
|
||||
if (c == 'M')
|
||||
dblFactor = 2;
|
||||
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
|
||||
x -= xDir * factor * (otherWidth / 2);
|
||||
y -= yDir * factor * (otherWidth / 2);
|
||||
|
||||
if (c == '=' || c == 'z' || c == 'N' || c == 'M') {
|
||||
ctx.setLineWidth(2.0);
|
||||
double x0 = x - 2 * yDir;
|
||||
double y0 = y - 2 * xDir;
|
||||
x0 += dblFactor * xDir * (otherWidth / 2);
|
||||
y0 += dblFactor * yDir * (otherWidth / 2);
|
||||
ctx.strokeLine(x0, y0, x0 + xDir * squareSize * xScaleFactor, y0 + yDir * squareSize);
|
||||
double x1 = x + 2 * yDir;
|
||||
double y1 = y + 2 * xDir;
|
||||
x1 -= dblFactor * xDir * (otherWidth / 2);
|
||||
y1 -= dblFactor * yDir * (otherWidth / 2);
|
||||
ctx.strokeLine(x1, y1, x1 + xDir * squareSize * xScaleFactor, y1 + yDir * squareSize);
|
||||
} else {
|
||||
ctx.strokeLine(x, y, x + xDir * squareSize * xScaleFactor, y + yDir * squareSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw text at the given position.
|
||||
*
|
||||
* For most cases, the simpler {@link #drawText(GraphicsContext, String)} or
|
||||
* {@link #drawText(GraphicsContext, String, double)} will be easier to use.
|
||||
*
|
||||
* If <code>clip</code> is true, the graphics context's current path will be
|
||||
* overwritten.
|
||||
*
|
||||
* @param ctx
|
||||
* A GraphicsContext
|
||||
* @param x
|
||||
* X-position of the lower left corner of the text
|
||||
* @param y
|
||||
* Y-position of the lower left corner of the text
|
||||
* @param text
|
||||
* The text to be printed
|
||||
* @param xScaleFactor
|
||||
* Horizontal scaling factor, normally 1.0 (full width) or 0.5 (half
|
||||
* width)
|
||||
* @param clear
|
||||
* True if the area should be cleared (to transparency) before
|
||||
* drawing; normally true.
|
||||
* @param clip
|
||||
* True if the text drawing should be clipped to fit the expected
|
||||
* printing area; normally true.
|
||||
* @param fill
|
||||
* True if the letter shapes should be filled; normally true.
|
||||
* @param stroke
|
||||
* True if the letter shapes should be stroked (outlined); normally
|
||||
* false.
|
||||
*/
|
||||
public void textAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, boolean clear,
|
||||
boolean clip, Paint fill, Paint stroke, int mode, Paint bg) {
|
||||
if ((mode & ATTR_BRIGHT) != 0) {
|
||||
fill = fill instanceof Color ? ((Color) fill).deriveColor(0.0, 1.0, 3.0, 1.0) : fill;
|
||||
stroke = stroke instanceof Color ? ((Color) stroke).deriveColor(0.0, 1.0, 3.0, 1.0) : stroke;
|
||||
}
|
||||
if ((mode & ATTR_FAINT) != 0) {
|
||||
fill = fill instanceof Color ? ((Color) fill).deriveColor(0.0, 1.0, 1.0, .5) : fill;
|
||||
stroke = stroke instanceof Color ? ((Color) stroke).deriveColor(0.0, 1.0, 1.0, .5) : stroke;
|
||||
}
|
||||
GraphicsContext target = ctx;
|
||||
int width = text.codePointCount(0, text.length());
|
||||
ctx.save(); // save 1
|
||||
ctx.setFill(fill);
|
||||
ctx.setStroke(stroke);
|
||||
ctx.translate(x, y);
|
||||
if (clear && (mode & ATTR_INVERSE) == 0 && (mode & ATTR_OVERSTRIKE) == 0) {
|
||||
ctx.clearRect(0 + 0.5, -squareSize + 0.5, squareSize * width * xScaleFactor - 1, squareSize - 1);
|
||||
}
|
||||
if (bg != null && bg != Color.TRANSPARENT) {
|
||||
ctx.save(); // save 2
|
||||
ctx.setFill(bg);
|
||||
ctx.fillRect(0, -squareSize, squareSize * width * xScaleFactor, squareSize);
|
||||
ctx.restore(); // restore 2
|
||||
}
|
||||
boolean drawIndirect = clip || (mode & (ATTR_INVERSE | ATTR_CLIP)) != 0;
|
||||
Canvas tmpCanvas = getTmpCanvas();
|
||||
if (drawIndirect) {
|
||||
target = tmpCanvas.getGraphicsContext2D();
|
||||
target.save(); // save 2 if drawIndirect
|
||||
target.translate(0, squareSize);
|
||||
target.setFill((mode & ATTR_INVERSE) != 0 ? Color.BLACK : fill);
|
||||
target.setStroke((mode & ATTR_INVERSE) != 0 ? Color.BLACK : stroke);
|
||||
}
|
||||
if (text.length() > 0 && text.charAt(0) >= 0x2500 && text.charAt(0) <= 0x259f
|
||||
&& (mode & ATTR_NO_FAKE_CHARS) == 0) {
|
||||
target.save(); // save 3
|
||||
target.setStroke(target.getFill());
|
||||
fakeBlockElement(target, text, xScaleFactor);
|
||||
target.restore(); // restore 3
|
||||
} else {
|
||||
target.save(); // save 3
|
||||
if ((mode & ATTR_ITALIC) != 0) {
|
||||
target.translate(-0.2, 0);
|
||||
target.transform(new Affine(Affine.shear(-0.2, 0)));
|
||||
}
|
||||
setGraphicsContext(target, xScaleFactor);
|
||||
if (fill != null)
|
||||
target.fillText(text, 0.0, 0.0);
|
||||
if ((mode & ATTR_BOLD) != 0) {
|
||||
// System.err.println("stroke2");
|
||||
target.save(); // save 4
|
||||
target.setLineWidth(thin);
|
||||
target.setStroke(target.getFill());
|
||||
target.strokeText(text, 0.0, 0.0);
|
||||
target.restore(); // restore 4
|
||||
}
|
||||
if (stroke != null || (mode & ATTR_OUTLINE) != 0) {
|
||||
// System.err.println("stroke2");
|
||||
target.setLineWidth(((mode & ATTR_BOLD) != 0) ? thin : thin / 2);
|
||||
target.strokeText(text, 0.0, 0.0);
|
||||
}
|
||||
target.restore(); // restore 3
|
||||
}
|
||||
|
||||
if ((mode & ATTR_UNDERLINE) != 0) {
|
||||
target.fillRect(0, yTranslate - 2, width * squareSize * xScaleFactor, thin);
|
||||
}
|
||||
if ((mode & ATTR_OVERLINE) != 0) {
|
||||
target.fillRect(0, -squareSize + 2, width * squareSize * xScaleFactor, thin);
|
||||
}
|
||||
if ((mode & ATTR_LINE_THROUGH) != 0) {
|
||||
target.fillRect(0, -squareSize / 2 + 2, width * squareSize * xScaleFactor, thin);
|
||||
}
|
||||
|
||||
if (drawIndirect) {
|
||||
target.restore(); // restore 2 if drawIndirect
|
||||
tmpCanvas.snapshot(snapshotParameters, img);
|
||||
if ((mode & ATTR_INVERSE) != 0) {
|
||||
fillInverse(ctx, img, 0, -squareSize);
|
||||
} else
|
||||
ctx.drawImage(img, 0, -squareSize);
|
||||
}
|
||||
|
||||
ctx.restore(); // restore 1
|
||||
}
|
||||
}
|
235
src/inf101/v18/gfx/textmode/TextFontAdjuster.java
Normal file
235
src/inf101/v18/gfx/textmode/TextFontAdjuster.java
Normal file
@@ -0,0 +1,235 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import inf101.v18.gfx.Screen;
|
||||
import inf101.v18.gfx.textmode.Printer;
|
||||
import javafx.application.Application;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class TextFontAdjuster extends Application {
|
||||
private static final String FONT_NAME = "PetMe64.ttf";
|
||||
// new TextFont(FONT_NAME, 22.2, TextModes.CHAR_BOX_SIZE, 0.0, 0.0, 1.0, 1.0);
|
||||
private static TextFontAdjuster demo;
|
||||
|
||||
public static TextFontAdjuster getInstance() {
|
||||
return demo;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
private TextFont textFont = new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE, 3.1000, -3.8000, 1.0000,
|
||||
1.0000, true);
|
||||
private Screen screen;
|
||||
|
||||
private boolean paused;
|
||||
private Printer printer;
|
||||
|
||||
private boolean grid = true;
|
||||
|
||||
private double adjustAmount = 0.1;
|
||||
|
||||
private double adjustX(KeyCode code) {
|
||||
switch (code) {
|
||||
case LEFT:
|
||||
return -1 * adjustAmount;
|
||||
case RIGHT:
|
||||
return 1 * adjustAmount;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private double adjustY(KeyCode code) {
|
||||
switch (code) {
|
||||
case UP:
|
||||
return 1 * adjustAmount;
|
||||
case DOWN:
|
||||
return -1 * adjustAmount;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawBackgroundGrid() {
|
||||
if (grid) {
|
||||
printer.drawCharCells();
|
||||
/*
|
||||
* painter.turnTo(0); for (int y = 0; y < printer.getPageHeight(); y++) {
|
||||
* painter.jumpTo(0, y * printer.getCharHeight()); for (int x = 0; x <
|
||||
* printer.getLineWidth(); x++) { painter.setInk( (x + y) % 2 == 0 ?
|
||||
* Color.CORNFLOWERBLUE : Color.CORNFLOWERBLUE.brighter().brighter());
|
||||
* painter.fillRectangle(printer.getCharWidth(), printer.getCharHeight());
|
||||
* painter.jump(printer.getCharWidth()); } }
|
||||
*/
|
||||
} else {
|
||||
screen.clearBackground();
|
||||
}
|
||||
}
|
||||
|
||||
private void printHelp() {
|
||||
printer.moveTo(1, 1);
|
||||
printer.setAutoScroll(false);
|
||||
printer.println(" " + Printer.center("TextFontAdjuster", 36) + " ");
|
||||
printer.println(" ");
|
||||
printer.println(" ");
|
||||
printer.println(" ");
|
||||
printer.println("________________________________________");
|
||||
printer.println("Adjust letter parameters: ");
|
||||
printer.println(" Font size: CTRL +, CTRL - ");
|
||||
printer.println(" Position: LEFT, RIGHT, UP, DOWN ");
|
||||
printer.println(" Scaling: CTRL-(LEFT, RIGHT, UP, DOWN)");
|
||||
printer.println("Commands / options (with CTRL key): ");
|
||||
printer.println(" Hires (R) – Grid (G) – Fullscreen (F) ");
|
||||
printer.println(" Help (H) – Quit (Q) ");
|
||||
printer.println("Write text with any other key. ");
|
||||
printer.println("_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-");
|
||||
printer.println(" ");
|
||||
printer.println("Sample text: ");
|
||||
printer.println("the quick brown fox jumps over the lazy ");
|
||||
printer.println(" dog, THE QUICK BROWN FOX JUMPS OVER THE");
|
||||
printer.println("LAZY DOG den vågale røde reven værer den");
|
||||
printer.println("sinte hunden DEN VÅGALE RØDE REVEN VÆRER");
|
||||
printer.println("DEN SINTE HUNDEN !\"#%&/()?,._-@£${[]}?|^");
|
||||
|
||||
// printer.print(" ");
|
||||
printer.moveTo(1, 15);
|
||||
printer.setAutoScroll(true);
|
||||
|
||||
}
|
||||
|
||||
private void printInfo() {
|
||||
printer.moveTo(1, 3);
|
||||
printer.println(String.format("Font: %s at %1.1fpt ", textFont.getFont().getName(),
|
||||
textFont.getFont().getSize()));
|
||||
printer.println(String.format(" xTr=%-1.1f yTr=%-1.1f xSc=%-1.1f ySc=%-1.1f ", textFont.getxTranslate(),
|
||||
textFont.getyTranslate(), textFont.getxScale(), textFont.getyScale()));
|
||||
// System.out.printf("new TextFont(\"%s\", %1.2f, Printer.CHAR_HEIGHT, %1.4f,
|
||||
// %1.4f, %1.4f, %1.4f)%n", FONT_NAME,
|
||||
// textFont.getSize(), textFont.getxTranslate(), textFont.getyTranslate(),
|
||||
// textFont.getxScale(),
|
||||
// textFont.getyScale());
|
||||
|
||||
printer.moveTo(1, 15);
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
drawBackgroundGrid();
|
||||
printHelp();
|
||||
printInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage stage) {
|
||||
demo = this;
|
||||
|
||||
screen = Screen.startPaintScene(stage);
|
||||
|
||||
printer = screen.createPrinter();
|
||||
printer.setInk(Color.BLACK);
|
||||
printer.setFont(textFont);
|
||||
screen.setKeyOverride((KeyEvent event) -> {
|
||||
KeyCode code = event.getCode();
|
||||
// System.out.println(event);
|
||||
if (event.isControlDown() || event.isShortcutDown()) {
|
||||
if (code == KeyCode.Q) {
|
||||
System.exit(0);
|
||||
} else if (code == KeyCode.P) {
|
||||
paused = !paused;
|
||||
return true;
|
||||
} else if (code == KeyCode.R) {
|
||||
printer.cycleMode(true);
|
||||
drawBackgroundGrid();
|
||||
return true;
|
||||
} else if (code == KeyCode.S) {
|
||||
if (event.isAltDown())
|
||||
screen.fitScaling();
|
||||
else
|
||||
screen.zoomCycle();
|
||||
drawBackgroundGrid();
|
||||
return true;
|
||||
} else if (code == KeyCode.A) {
|
||||
screen.cycleAspect();
|
||||
return true;
|
||||
} else if (code == KeyCode.G) {
|
||||
grid = !grid;
|
||||
drawBackgroundGrid();
|
||||
return true;
|
||||
} else if (code == KeyCode.H) {
|
||||
printHelp();
|
||||
printInfo();
|
||||
return true;
|
||||
} else if (code == KeyCode.F) {
|
||||
stage.setFullScreen(!stage.isFullScreen());
|
||||
return true;
|
||||
} else if (code == KeyCode.M) {
|
||||
printer.print("\r");
|
||||
return true;
|
||||
} else if (code == KeyCode.L) {
|
||||
printer.redrawTextPage();
|
||||
return true;
|
||||
} else if (code == KeyCode.DIGIT1) {
|
||||
DemoPages.printBoxDrawing(printer);
|
||||
return true;
|
||||
} else if (code == KeyCode.DIGIT2) {
|
||||
DemoPages.printZX(printer);
|
||||
return true;
|
||||
} else if (code == KeyCode.DIGIT3) {
|
||||
DemoPages.printBlockPlotting(printer);
|
||||
return true;
|
||||
} else if (code == KeyCode.DIGIT4) {
|
||||
DemoPages.printVideoAttributes(printer);
|
||||
return true;
|
||||
} else if (code == KeyCode.DIGIT5) {
|
||||
DemoPages.printAnsiArt(printer);
|
||||
return true;
|
||||
} else if (code == KeyCode.PLUS) {
|
||||
textFont = textFont.adjust(adjustAmount, 0.0, 0.0, 0.0, 0.0);
|
||||
printer.setFont(textFont);
|
||||
printer.redrawTextPage();
|
||||
printInfo();
|
||||
return true;
|
||||
} else if (code == KeyCode.MINUS) {
|
||||
textFont = textFont.adjust(-adjustAmount, 0.0, 0.0, 0.0, 0.0);
|
||||
printer.setFont(textFont);
|
||||
printer.redrawTextPage();
|
||||
printInfo();
|
||||
return true;
|
||||
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP
|
||||
|| code == KeyCode.DOWN) {
|
||||
textFont = textFont.adjust(0.0, 0.0, 0.0, adjustX(code), adjustY(code));
|
||||
printer.setFont(textFont);
|
||||
printer.redrawTextPage();
|
||||
printInfo();
|
||||
return true;
|
||||
}
|
||||
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP || code == KeyCode.DOWN) {
|
||||
textFont = textFont.adjust(0.0, adjustX(code), adjustY(code), 0.0, 0.0);
|
||||
printer.setFont(textFont);
|
||||
printer.redrawTextPage();
|
||||
printInfo();
|
||||
return true;
|
||||
} else if (code == KeyCode.ENTER) {
|
||||
|
||||
printer.print("\n");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
screen.setKeyTypedHandler((KeyEvent event) -> {
|
||||
if (event.getCharacter() != KeyEvent.CHAR_UNDEFINED) {
|
||||
printer.print(event.getCharacter());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
setup();
|
||||
|
||||
stage.show();
|
||||
|
||||
}
|
||||
|
||||
}
|
158
src/inf101/v18/gfx/textmode/TextMode.java
Normal file
158
src/inf101/v18/gfx/textmode/TextMode.java
Normal file
@@ -0,0 +1,158 @@
|
||||
package inf101.v18.gfx.textmode;
|
||||
|
||||
import inf101.v18.gfx.Screen;
|
||||
|
||||
public enum TextMode {
|
||||
/** Low resolution, wide screen (20:11, fits 16:9) text mode 40x22 */
|
||||
MODE_40X22(Constants.H40, Constants.V22, Screen.ASPECT_WIDE),
|
||||
/** Low resolution, 16:10 aspect text mode 40x25 */
|
||||
MODE_40X25(Constants.H40, Constants.V25, Screen.ASPECT_MEDIUM),
|
||||
/** Low resolution, 4:3 aspect text mode 40x30 */
|
||||
MODE_40X30(Constants.H40, Constants.V30, Screen.ASPECT_CLASSIC),
|
||||
/** High resolution, wide screen (20:11, fits 16:9) text mode 80x22 */
|
||||
MODE_80X22(Constants.H80, Constants.V22, Screen.ASPECT_WIDE),
|
||||
/** High resolution, 16:10 aspect text mode 80x25 */
|
||||
MODE_80X25(Constants.H80, Constants.V25, Screen.ASPECT_MEDIUM),
|
||||
/** High resolution, 4:3 aspect text mode 80x30 */
|
||||
MODE_80X30(Constants.H80, Constants.V30, Screen.ASPECT_CLASSIC);
|
||||
|
||||
protected static class Constants {
|
||||
protected static final int H40 = 0, H80 = 1;
|
||||
protected static final int[] HREZ = { 40, 80 };
|
||||
protected static final int V22 = 0, V25 = 1, V30 = 2;
|
||||
protected static final int[] VREZ = { 22, 25, 30 };
|
||||
private static TextMode[] MODES = null;
|
||||
|
||||
// work around initialization order for statics and enums
|
||||
protected static TextMode getMode(int i) {
|
||||
if (MODES == null)
|
||||
MODES = new TextMode[] { MODE_40X22, MODE_40X25, MODE_40X30, MODE_80X22, MODE_80X25, MODE_80X30 };
|
||||
return MODES[(i + MODES.length) % MODES.length];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Size of the square-shaped "box" bounds of character cells.
|
||||
*
|
||||
* For "high" resolution, characters will be squeezed horizontally to fit half
|
||||
* the width.
|
||||
*/
|
||||
public static final double CHAR_BOX_SIZE = 32;
|
||||
/**
|
||||
* Maximum length of a line in any resolution mode
|
||||
*/
|
||||
public static final int LINE_WIDTH_MAX = Constants.HREZ[Constants.HREZ.length - 1];
|
||||
/**
|
||||
* Maximum height of a page in any resolution mode
|
||||
*/
|
||||
public static final int PAGE_HEIGHT_MAX = Constants.VREZ[Constants.VREZ.length - 1];
|
||||
private int aspect;
|
||||
private int w;
|
||||
|
||||
private int h;
|
||||
|
||||
private int hIndex;
|
||||
|
||||
private int vIndex;
|
||||
|
||||
private TextMode(int w, int h, int aspect) {
|
||||
this.hIndex = w;
|
||||
this.vIndex = h;
|
||||
this.aspect = aspect;
|
||||
this.w = Constants.HREZ[w];
|
||||
this.h = Constants.VREZ[h];
|
||||
}
|
||||
|
||||
private TextMode findMode(int hIndex, int vIndex) {
|
||||
hIndex = (hIndex + Constants.HREZ.length) % Constants.HREZ.length;
|
||||
vIndex = (vIndex + Constants.VREZ.length) % Constants.VREZ.length;
|
||||
return Constants.getMode(vIndex + hIndex * Constants.VREZ.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aspect ration descriptor for use with {@link Screen#setAspect()}
|
||||
*
|
||||
* @return One of {@link Screen#ASPECT_WIDE}, {@link Screen#ASPECT_MEDIUM} or
|
||||
* {@link Screen#ASPECT_CLASSIC}
|
||||
*/
|
||||
public int getAspect() {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
public double getCharBoxSize() {
|
||||
return CHAR_BOX_SIZE;
|
||||
}
|
||||
|
||||
public double getCharHeight() {
|
||||
return CHAR_BOX_SIZE;
|
||||
}
|
||||
|
||||
public double getCharWidth() {
|
||||
return w == 80 ? CHAR_BOX_SIZE / 2 : CHAR_BOX_SIZE;
|
||||
}
|
||||
|
||||
public int getLineWidth() {
|
||||
return w;
|
||||
}
|
||||
|
||||
public int getPageHeight() {
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through horizontal modes
|
||||
*
|
||||
* @return Next available horizontal mode (vertical resolution unchanged)
|
||||
*/
|
||||
public TextMode nextHorizMode() {
|
||||
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through modes
|
||||
*
|
||||
* @return Next available mode
|
||||
*/
|
||||
public TextMode nextMode() {
|
||||
int m = vIndex + hIndex * Constants.VREZ.length;
|
||||
return Constants.getMode(m + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through vertical modes
|
||||
*
|
||||
* @return Next available vertical mode (horizontal resolution unchanged)
|
||||
*/
|
||||
public TextMode nextVertMode() {
|
||||
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through horizontal modes
|
||||
*
|
||||
* @return Previous available horizontal mode (vertical resolution unchanged)
|
||||
*/
|
||||
public TextMode prevHorizMode() {
|
||||
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through modes
|
||||
*
|
||||
* @return Previous available mode
|
||||
*/
|
||||
public TextMode prevMode() {
|
||||
int m = vIndex + hIndex * Constants.VREZ.length;
|
||||
return Constants.getMode(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycle through vertical modes
|
||||
*
|
||||
* @return Previous available vertical mode (horizontal resolution unchanged)
|
||||
*/
|
||||
public TextMode prevVertMode() {
|
||||
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
|
||||
}
|
||||
|
||||
}
|
95
src/inf101/v18/gfx/textmode/flower.txt
Normal file
95
src/inf101/v18/gfx/textmode/flower.txt
Normal file
@@ -0,0 +1,95 @@
|
||||
[40m[2J[23B[0;1m [1D[23;78H[0;30;44m [2D [2D [2D [2D [2Dl[2De[2Da[2Dh[2Dc[2Di[2DM[2D [2D[1;32m [40m[s
|
||||
[u[44m[2D [3D[42m [2D▓[2D█[2D█[2D█[2D█[3D[0;30;42m [2D[44m▄[2D▀[3D[1;32;42m [2D [2D [2D▓[2D█[2D[40m[s
|
||||
[u[42m█[2D█[2D█[3D[0;30;44m [2D▄[2D▄[3D[1;32;42m [2D[40m▀[2D▀[9D▀[2D▀[2D[42m█[2D█[2D[0;30;44m▐[40m[s
|
||||
[u[44m[3D[1;32;42m [2D█[2D█[2D█[2D█[3D[0;30;44m▀[2D[32m [3D[40m▀[2D▀[3D[30;44m▀[2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [22;78H [2D [2D:[2Dy[2Db[2D [2Dn[2De[2De[2Dr[2Dc[2DS[2D[1;32m [2D [40m[s
|
||||
[u[44m[3D[42m [2D▓[2D[40m█[2D█[2D█[2D█[3D[42m [3D[44m [3D[42m [2D [2D [2D[40m▓[2D[42m█[2D[40m█[2D[s
|
||||
[u█[2D█[2D▄[3D[0;30;44m [2D [2D▀[3D[1;32;42m [2D[40m█[2D█[2D█[2D█[3D[0;30;42m [2D░[3D[1;32m [2D▓[40m[s
|
||||
[u[42m[2D█[2D█[2D█[2D[0;30;44m▐[2D▄[3D[1;32;42m [2D▓[2D█[2D█[2D█[3D[0;30;44m▀[2D▄[3D[32;40m▄[2D█[2D▀[s
|
||||
[u[3D[30;44m▀[2D▀[2D[1;32m [2D [2D [2D [2D [2D [21;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [3D[42m [2D▓[2D[40m█[2D█[2D█[3D[0;30;44m▄[2D[42m [3D[44m▄[2D▀[2D[42m█[2D[1;32m [40m[s
|
||||
[u[42m[2D [2D[40m▓[2D[42m█[2D[40m█[2D█[2D█[3D[0;30;44m▄[2D [2D▀[2D▀[3D[1;32;42m [2D[40m█[2D█[2D█[2D[s
|
||||
[u▄[4D[0;30;42m [2D░[3D[1;32m [2D▓[2D█[2D█[2D█[3D[0;30;44m▀[2D▄[3D[1;32;42m [2D▓[2D█[2D█[2D█[3D[40m[s
|
||||
[u[0;30;44m▀[2D [2D▄[3D[32;40m▄[2D█[2D█[2D▀[2D▀[3D[30;44m▀[2D[1;32m [2D [2D [20;78H [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [3D[42m [2D▓[2D[40m█[2D█[2D█[3D[0;30;44m [2D[42m [3D[44m[40m[s
|
||||
[u[44m▄[2D[1;32m [3D[42m [2D [2D▓[2D█[2D█[2D[40m█[3D[0;30;44m▄[2D▄[2D▀[3D[32;40m▀[2D[1;42m▀[2D▀[40m[s
|
||||
[u[42m[2D[40m▄[2D▄[4D[0;30;44m [3D[42m [2D░[2D░[2D█[2D[44m▌[2D[1;32;42m [2D▓[2D█[2D█[2D█[3D[40m[s
|
||||
[u[0;30;44m▀[2D▄[3D[1;32;42m [2D▓[2D▓[2D█[2D█[3D[0;30;44m▀[2D [2D [2D▄[3D[32;40m█[2D[1;33;42m▀[2D[40m[s
|
||||
[u[40m█[2D█[2D▀[3D[0;30;44m▀[2D [19;79H█[2D [2D [2D [2D [2D [2D▀[2D▀[2D▀[2D [2D▀[2D▀[3D[32;40m▀[2D[s
|
||||
[u[1;42m▓[2D[40m█[2D█[2D█[3D[44m [3D[42m [3D[44m [2D [3D[42m [2D▓[2D█[2D█[2D█[3D[0;30;44m▄[2D[40m[s
|
||||
[u[44m[32m [3D[40m▀[2D[1;42m▀[2D[40m▄[3D[0;30;44m▄[2D▄[2D [2D [2D [3D[42m▄[2D [2D░[2D[32m [2D [40m[s
|
||||
[u[42m[3D[40m█[2D[1;42m█[2D[40m▄[2D[33m▀[2D[32m▄[2D[42m█[3D[0;30;44m [2D▄[2D▄[3D[1;32;42m [2D▓[40m[s
|
||||
[u[42m[2D█[2D█[3D[0;30;44m▀[2D [2D [2D▄[3D[1;33;40m▄[2D▄[2D█[2D█[2D█[2D▀[18;78H[32;44m [2D [2D [40m[s
|
||||
[u[44m[2D [2D [3D[40m▄[2D▀[2D▀[2D[0;32m▀[2D[1;42m▀[2D▀[2D[40m█[2D█[2D█[3D[0;30;44m [2D▐[3D[42m [40m[s
|
||||
[u[42m[2D [3D[44m▐[3D[42m▄[2D[1;32m [2D▓[2D█[2D█[3D[0;30;44m▄[2D[1;32m [2D [3D[40m█[3D[0;30;44m▄[40m[s
|
||||
[u[44m[2D[1;33m [3D[40m▀[3D[0;30;44m▀[2D▀[3D[42m▀[2D░[2D░[2D▀[2D█[2D[1;33;40m▀[2D▀[2D▀[2D█[2D█[3D[s
|
||||
[u[32m█[3D[0;30;44m [2D [2D [2D▄[3D[1;32;42m [2D▓[2D█[2D█[3D[0;30;44m [2D [2D [2D [2D [2D▄[2D▄[40m[s
|
||||
[u[44m[3D[1;33;40m▄[2D▄[2D▄[17;78H[0;30;44m [2D [2D [2D [2D [2D [2D▄[3D[1;32;40m▄[2D▄[2D▄[2D▄[2D▄[3D[s
|
||||
[u[0;30;44m▄[2D▌[2D▌[2D[1;33m [3D[43m▒[2D▓[2D[0;30;42m [2D[44m▐[3D[1;32;42m [2D[40m▄[2D▄[2D▄[3D[s
|
||||
[u[0;30;44m▄[2D [2D [2D [2D▄[2D▄[2D▄[2D[1;33m [2D [3D[40m▄[2D█[2D█[2D█[2D█[2D█[2D▀[2D▀[2D█[2D█[2D█[s
|
||||
[u[2D█[2D█[2D█[4D[32m▄[2D[33m▀[2D▀[2D▀[2D▀[2D▀[4D[0;32m▄[2D[1m▄[2D[42m█[3D[0;30;44m [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [16;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D▐[2D▌[2D[1;33m [3D[43m▒[2D▓[2D▓[2D[0;30;44m▐[3D[1;32;42m [2D[40m▐[2D[33m█[2D█[2D▀[s
|
||||
[u[3D[0;30;44m▀[2D▀[2D▀[2D [2D▀[2D▀[3D[1;33;40m▀[2D▀[2D▀[2D▀[2D▄[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D[s
|
||||
[u[42m▓[2D▓[2D▓[3D[40m▀[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D▀[2D▀[2D▀[3D[0;30;44m [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [15;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D▌[2D [2D▐[2D[1;33m [3D[43m▒[2D▒[2D▓[3D[0;30;42m▄[2D[1;32m [2D█[3D[33;40m█[2D█[2D█[2D█[s
|
||||
[u[2D█[2D▀[2D▀[2D▀[2D[47m█[2D█[2D█[2D[42m▓[2D▓[2D[47m█[2D[40m█[2D▀[2D▀[2D▄[2D█[2D█[2D[42m▓[2D[40m[s
|
||||
[u[42m▓[2D▓[3D[40m▀[2D▀[2D[42m▓[2D▓[2D▓[2D▓[2D▓[2D[40m█[2D█[2D█[2D█[2D█[2D█[2D▄[2D▄[3D[32m▀[3D[s
|
||||
[u[0;30;44m▀[2D [2D [2D [2D [2D [2D [2D [2D [14;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D▌[2D [2D▌[2D[1;33m [2D [3D[43m▓[2D▓[3D[0;30;44m▌[2D[1;32;42m [2D█[2D█[2D[40m▀[4D[33m█[s
|
||||
[u[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D[47m█[2D█[2D[40m█[2D[42m▓[2D▓[2D▓[2D▓[2D▓[3D[40m▄[2D▄[3D▀[2D▀[2D[s
|
||||
[u[42m▓[2D▓[2D▓[2D▓[2D[40m█[2D█[2D█[2D█[2D▄[2D▄[3D[0;30;44m▄[2D▄[2D▄[3D[1;32;42m█[2D█[2D[40m[s
|
||||
[u[0;30;42m [3D[44m▀[2D [2D [2D [2D [2D [2D [2D [13;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D▐[2D [2D [2D▄[3D[1;33;43m▓[3D[0;30;44m▌[2D[1;32;42m [2D▓[2D█[6D[33;40m▄[2D▄[s
|
||||
[u[2D▄[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D▄[3D▀[2D█[3D▄[2D▄[2D▄[2D█[2D█[2D█[2D▄[2D▄[2D▄[3D[s
|
||||
[u▀[2D▀[2D█[2D▀[3D[0;30;44m▀[2D [2D▄[3D[1;32;42m█[2D█[2D[0;30;42m [3D[44m▀[2D▀[2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [12;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D▌[2D [2D [2D [2D [2D▄[40m[s
|
||||
[u[44m[2D▄[3D[42m▄[2D░[2D[1;32m▓[2D█[5D[33;40m▀[2D█[2D█[2D▀[2D▀[2D▀[3D▄[2D▄[2D▄[2D▄[2D▄[3D▀[2D▀[2D[s
|
||||
[u[42m▓[2D▓[2D[40m█[2D█[2D█[2D█[2D▀[3D▀[2D▀[2D▀[2D[42m▓[2D▓[2D▓[2D▓[2D▓[2D[40m█[2D█[2D█[3D[s
|
||||
[u[0;30;44m▀[2D [2D▄[2D▄[3D[1;32;42m█[2D▀[2D▀[2D[40m▀[2D▀[2D▀[3D[0;30;44m▀[2D▀[11;78H [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D▀[3D[32;40m▄[2D[1m▄[2D▄[s
|
||||
[u[2D[33m▀[2D▀[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D[42m▓[2D▓[2D▓[2D▓[2D▓[3D█[2D▓[2D▓[2D▓[2D▓[2D[40m[s
|
||||
[u[42m▓[2D▓[2D[40m█[2D█[2D█[2D▀[3D█[2D█[2D█[2D[42m█[2D▓[2D▓[2D▓[2D▓[2D█[2D[40m█[2D█[3D[s
|
||||
[u[0;30;44m▀[2D▀[2D [2D▄[2D▄[2D▄[2D▄[2D▄[2D▄[3D[1;32;40m▄[2D▄[2D[0;30;44m█[10;78H[1;33m [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[40m▄[2D█[2D█[2D█[2D[s
|
||||
[u█[2D█[2D█[2D█[2D█[2D█[2D█[2D[42m▓[2D▓[2D▓[2D▓[2D▓[2D[40m█[2D█[2D█[2D▀[3D[42m█[2D▓[2D▓[2D▓[40m[s
|
||||
[u[42m[2D▓[2D▓[2D▓[2D[47m█[2D[40m█[2D█[2D▀[3D▄[2D▄[2D▄[2D▄[2D█[2D█[2D█[2D[42m█[2D█[2D[40m█[2D█[2D[s
|
||||
[u█[2D▀[3D[0;30;44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [9;78H [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[42m▄[2D[1;32m [2D[40m▀[2D▀[2D[33m▄[2D█[2D█[2D█[s
|
||||
[u[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D▄[2D▄[2D[0;30;44m▐[2D▌[2D[1;33;40m▀[2D█[2D▄[2D▀[2D▀[2D▀[2D[s
|
||||
[u▀[2D▀[2D▀[2D▄[2D▄[2D█[2D▀[2D[0;30;44m▐[2D [2D [2D▄[3D[32;40m▀[2D[1m▀[2D[33m▄[2D▄[2D▄[2D█[2D█[2D█[s
|
||||
[u[2D▀[3D[0;30;44m▀[2D[1;32m [2D [2D [2D [2D [2D [2D [2D [8;78H [2D [2D [2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[42m [2D█[2D█[3D[0;30;44m▄[2D▄[2D▄[3D[40m[s
|
||||
[u[1;33;40m▄[2D▄[2D▄[2D▄[2D▄[2D▄[3D[0;30;44m▄[2D▄[2D▀[2D▀[2D▀[2D[1;33;40m▀[2D▀[2D▀[2D█[2D[42m▄[40m[s
|
||||
[u[42m[2D [2D▀[2D▀[2D▀[2D▄[2D█[2D[40m█[2D▀[2D▀[2D▀[2D▀[3D[0;30;44m▀[2D[1;32m [3D[42m [2D█[2D█[40m[s
|
||||
[u[42m[3D[0;30;44m▄[2D▄[3D[1;33;40m▄[2D█[3D[0;30;44m [2D [2D [2D [2D [2D [2D [2D [7;78H [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D▀[3D[1;32;42m [2D█[2D█[3D[40m[s
|
||||
[u[0;30;44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D▀[3D[1;33;40m▀[2D[42m▄[2D▄[2D▄[2D▀[2D▀[40m[s
|
||||
[u[42m[2D▀[2D█[2D▓[2D[47m▐[2D▐[2D█[2D[42m▓[2D▀[2D▀[2D▀[2D▀[2D▄[2D▄[2D▄[2D[40m▀[2D▀[4D[32;42m [40m[s
|
||||
[u[42m[2D█[3D[0;30;44m▀[2D [2D [2D▄[2D▄[2D▄[2D[1;32m [2D [2D [2D [2D [2D [2D [2D [6;78H [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[42m [2D█[2D█[3D[40m[s
|
||||
[u[0;30;44m▄[2D[1;33m [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[40m▀[2D[42m▄[2D▀[2D[40m█[2D[42m▄[40m[s
|
||||
[u[42m[2D[40m█[2D█[2D[47m█[2D[42m▓[2D▓[2D[47m█[2D▌[2D▌[2D▐[2D█[2D[42m▓[2D[40m█[2D█[2D█[2D█[2D█[2D[s
|
||||
[u█[2D[42m▀[2D▄[2D[40m█[2D[32;44m [3D[42m [2D█[3D[44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [5;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[40m[s
|
||||
[u[42m [2D█[2D█[2D[0;30;44m█[2D[1;33m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [3D[40m▄[2D[42m▀[40m[s
|
||||
[u[42m[2D▄[2D▄[2D[40m█[2D█[2D█[2D█[2D[47m█[2D█[2D▐[2D█[2D▒[2D[40m█[2D[47m▐[2D[40m█[2D█[2D█[2D█[2D[s
|
||||
[u█[2D█[2D[47m█[2D[42m▄[2D▀[2D[40m▄[2D[32;44m [3D[42m [2D█[3D[0;30;44m▀[2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [4;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [3D[42m▄[2D[1;32m█[2D█[3D[0;30;44m▄[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D▄[3D[1;33;40m▄[2D█[2D[42m▀[2D▄[2D▄[2D▄[2D[40m█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D█[2D[s
|
||||
[u[42m▄[2D▄[2D▄[2D▀[2D[40m▄[3D[0;30;44m [2D [2D▄[3D[1;32;42m [2D█[3D[0;30;44m▀[2D[1;32m [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [3;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [3D[42m▌[2D█[3D[0;30;44m▄[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D▄[2D▄[3D[1;33;40m▄[2D▄[2D█[2D[42m▀[2D▄[2D▄[2D▄[2D[40m█[2D█[2D[42m▄[2D▄[40m[s
|
||||
[u[42m[2D▄[2D▀[2D[40m█[2D▄[2D▄[3D[0;30;44m▄[2D [2D [2D [2D [2D▄[2D▄[3D[1;32;42m█[3D[44m [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [2D [2D [2D [2;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [3D[42m▌[3D[0;30;44m▄[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [2D [2D [2D▄[2D▄[3D[1;33;40m▄[2D▄[2D▄[2D[0;30;44m▄[2D▄[2D[1;33;40m▄[2D▄[s
|
||||
[u[2D▄[3D[0;30;44m▄[2D▄[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D▄[2D▄[2D▄[2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [;78H [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D▄[2D▄[2D▄[2D[1;37m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [40m[s
|
||||
[u[44m[2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D[40m[s
|
||||
[u[44m [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [2D [0m[255D
|
||||
|
Reference in New Issue
Block a user