initial import

This commit is contained in:
Anya Helene Bagge
2018-02-28 10:22:33 +01:00
commit 6c2103c9ca
79 changed files with 8843 additions and 0 deletions

View 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;
}
}

View 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;
}
}

View 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);
}
}

View 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();
}
}
}

View 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
}
}

View 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();
}
}

View 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);
}
}

View File

@@ -0,0 +1,95 @@
      leahciM  
  ▓████ ▄▀   ▓█
███ ▄▄ ▀▀▀▀██▐
 ████▀ ▀▀▀    
       :yb neercS  
 ▓████     ▓██
██▄  ▀ ████ ░ ▓
███▐▄ ▓███▀▄▄█▀
▀▀                 
   ▓███▄ ▄▀█ 
 ▓████▄ ▀▀ ███
▄ ░ ▓███▀▄ ▓███
▀ ▄▄██▀▀▀       
         ▓███  
▄   ▓███▄▄▀▀▀▀
▄▄  ░░█▌ ▓███
▀▄ ▓▓██▀  ▄█▀
██▀▀ █     ▀▀▀ ▀▀▀
▓███     ▓███▄
 ▀▀▄▄▄   ▄ ░  
██▄▀▄█ ▄▄ ▓
██▀  ▄▄▄███▀   
  ▄▀▀▀▀▀███ ▐ 
 ▐▄ ▓██▄  █▄
 ▀▀▀▀░░▀█▀▀▀██
█   ▄ ▓██     ▄▄
▄▄▄      ▄▄▄▄▄▄
▄▌▌ ▒▓ ▐ ▄▄▄
▄   ▄▄▄  ▄█████▀▀███
███▄▀▀▀▀▀▄▄█   
                     
  ▐▌ ▒▓▓▐ ▐██▀
▀▀▀ ▀▀▀▀▀▀▄███████
▓▓▓▀██████████▀▀▀  
                     
 ▌ ▐ ▒▒▓▄ █████
█▀▀▀███▓▓██▀▀▄██▓
▓▓▀▀▓▓▓▓▓██████▄▄▀
▀                   
  ▌ ▌  ▓▓▌ ██▀█
██████████▓▓▓▓▓▄▄▀▀
▓▓▓▓████▄▄▄▄▄██
 ▀                 
     ▐  ▄▓▌ ▓█▄▄
▄██████████▄▀█▄▄▄███▄▄▄
▀▀█▀▀ ▄██ ▀▀    
               ▌    ▄
▄▄░▓█▀██▀▀▀▄▄▄▄▄▀▀
▓▓████▀▀▀▀▓▓▓▓▓███
▀ ▄▄█▀▀▀▀▀▀▀   
                 ▀▄▄▄
▀▀████████▓▓▓▓▓█▓▓▓▓
▓▓███▀████▓▓▓▓███
▀▀ ▄▄▄▄▄▄▄▄█   
                 ▄███
███████▓▓▓▓▓███▀█▓▓▓
▓▓▓███▀▄▄▄▄███████
█▀                   
          ▄ ▀▀▄███
█████████▄▄▐▌▀█▄▀▀▀
▀▀▀▄▄█▀▐  ▄▀▀▄▄▄███
▀▀                
            ██▄▄▄
▄▄▄▄▄▄▄▄▀▀▀▀▀▀█▄
 ▀▀▀▄██▀▀▀▀▀  ██
▄▄▄█           
               ▀ ██
           ▀▀▄▄▄▀▀
▀█▓▐▐█▓▀▀▀▀▄▄▄▀▀ 
█▀  ▄▄▄          
                 ██
▄          ▀▄▀█▄
███▓▓█▌▌▐█▓█████
█▀▄█  █           
                    
 ███           ▄▀
▄▄██████▐█▒█▐████
███▄▀▄  █▀      
                     
  ▄██▄           
 ▄▄█▀▄▄▄██████████
▄▄▄▀▄  ▄ █▀   
                     
    ▌█▄           
    ▄▄▄▄█▀▄▄▄██▄▄
▄▀█▄▄▄    ▄▄█  
                      
    ▌▄            
        ▄▄▄▄▄▄▄▄▄
▄▄▄         ▄▄▄     
                      
 ▄▄▄