initial import
This commit is contained in:
27
src/inf101/v18/gfx/IPaintLayer.java
Normal file
27
src/inf101/v18/gfx/IPaintLayer.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package inf101.v18.gfx;
|
||||
|
||||
public interface IPaintLayer {
|
||||
/**
|
||||
* Clear the layer.
|
||||
*
|
||||
* <p>
|
||||
* Everything on the layer is removed, leaving only transparency.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Send this layer to the front, so it will be drawn on top of any other layers.
|
||||
*/
|
||||
void layerToFront();
|
||||
|
||||
/**
|
||||
* Send this layer to the back, so it will be drawn behind any other layers.
|
||||
*
|
||||
* <p>
|
||||
* There will still be background behind this layer. You may clear it or draw to
|
||||
* it using {@link Screen#clearBackground()},
|
||||
* {@link Screen#setBackground(javafx.scene.paint.Color)} and
|
||||
* {@link Screen#getBackgroundContext()}.
|
||||
*/
|
||||
void layerToBack();
|
||||
}
|
667
src/inf101/v18/gfx/Screen.java
Normal file
667
src/inf101/v18/gfx/Screen.java
Normal file
@@ -0,0 +1,667 @@
|
||||
package inf101.v18.gfx;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import inf101.v18.gfx.gfxmode.TurtlePainter;
|
||||
import inf101.v18.gfx.textmode.Printer;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.SubScene;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.Window;
|
||||
|
||||
public class Screen {
|
||||
private static final double STD_CANVAS_WIDTH = 1280;
|
||||
private static final List<Double> STD_ASPECTS = Arrays.asList(16.0 / 9.0, 16.0 / 10.0, 4.0 / 3.0);
|
||||
/** 16:9 */
|
||||
public static final int ASPECT_WIDE = 0;
|
||||
/** 16:10 */
|
||||
public static final int ASPECT_MEDIUM = 1;
|
||||
/** 4:3 */
|
||||
public static final int ASPECT_CLASSIC = 2;
|
||||
public static final int ASPECT_NATIVE = 2;
|
||||
private static final int CONFIG_ASPECT_SHIFT = 0;
|
||||
/** Screen's initial aspect ratio should be 16:9 */
|
||||
public static final int CONFIG_ASPECT_WIDE = 0 << CONFIG_ASPECT_SHIFT;
|
||||
/** Screen's initial aspect ratio should be 16:10 */
|
||||
public static final int CONFIG_ASPECT_MEDIUM = 1 << CONFIG_ASPECT_SHIFT;
|
||||
/** Screen's initial aspect ratio should be 4:3 */
|
||||
public static final int CONFIG_ASPECT_CLASSIC = 2 << CONFIG_ASPECT_SHIFT;
|
||||
/** Screen's initial aspect ratio should be the same as the device display. */
|
||||
public static final int CONFIG_ASPECT_DEVICE = 3 << CONFIG_ASPECT_SHIFT;
|
||||
private static final int CONFIG_ASPECT_MASK = 3 << CONFIG_ASPECT_SHIFT;
|
||||
|
||||
private static final int CONFIG_SCREEN_SHIFT = 2;
|
||||
/** Screen should start in a window. */
|
||||
public static final int CONFIG_SCREEN_WINDOWED = 0 << CONFIG_SCREEN_SHIFT;
|
||||
/** Screen should start in a borderless window. */
|
||||
public static final int CONFIG_SCREEN_BORDERLESS = 1 << CONFIG_SCREEN_SHIFT;
|
||||
/** Screen should start in a transparent window. */
|
||||
public static final int CONFIG_SCREEN_TRANSPARENT = 2 << CONFIG_SCREEN_SHIFT;
|
||||
/** Screen should start fullscreen. */
|
||||
public static final int CONFIG_SCREEN_FULLSCREEN = 3 << CONFIG_SCREEN_SHIFT;
|
||||
/**
|
||||
* Screen should start fullscreen, without showing a "Press ESC to exit
|
||||
* fullscreen" hint.
|
||||
*/
|
||||
public static final int CONFIG_SCREEN_FULLSCREEN_NO_HINT = 4 << CONFIG_SCREEN_SHIFT;
|
||||
private static final int CONFIG_SCREEN_MASK = 7 << CONFIG_SCREEN_SHIFT;
|
||||
|
||||
private static final int CONFIG_PIXELS_SHIFT = 5;
|
||||
/**
|
||||
* Canvas size / number of pixels should be determined the default way.
|
||||
*
|
||||
* The default is {@link #CONFIG_PIXELS_DEVICE} for
|
||||
* {@link #CONFIG_SCREEN_FULLSCREEN} and {@link #CONFIG_COORDS_DEVICE}, and
|
||||
* {@link #CONFIG_PIXELS_STEP_SCALED} otherwise.
|
||||
*/
|
||||
public static final int CONFIG_PIXELS_DEFAULT = 0 << CONFIG_PIXELS_SHIFT;
|
||||
/**
|
||||
* Canvas size / number of pixels will be an integer multiple or fraction of the
|
||||
* logical canvas size that fits the native display size.
|
||||
*
|
||||
* Scaling by whole integers makes it less likely that we get artifacts from
|
||||
* rounding errors or JavaFX's antialiasing (e.g., fuzzy lines).
|
||||
*/
|
||||
public static final int CONFIG_PIXELS_STEP_SCALED = 1 << CONFIG_PIXELS_SHIFT;
|
||||
/** Canvas size / number of pixels will the same as the native display size. */
|
||||
public static final int CONFIG_PIXELS_DEVICE = 2 << CONFIG_PIXELS_SHIFT;
|
||||
/**
|
||||
* Canvas size / number of pixels will the same as the logical canvas size
|
||||
* (typically 1280x960).
|
||||
*/
|
||||
public static final int CONFIG_PIXELS_LOGICAL = 3 << CONFIG_PIXELS_SHIFT;
|
||||
/**
|
||||
* Canvas size / number of pixels will be scaled to fit the native display size.
|
||||
*/
|
||||
public static final int CONFIG_PIXELS_SCALED = 4 << CONFIG_PIXELS_SHIFT;
|
||||
private static final int CONFIG_PIXELS_MASK = 7 << CONFIG_PIXELS_SHIFT;
|
||||
|
||||
private static final int CONFIG_COORDS_SHIFT = 8;
|
||||
/**
|
||||
* The logical canvas coordinate system will be in logical units (i.e., 1280
|
||||
* pixels wide regardless of how many pixels wide the screen actually is)
|
||||
*/
|
||||
public static final int CONFIG_COORDS_LOGICAL = 0 << CONFIG_COORDS_SHIFT;
|
||||
/** The logical canvas coordinate system will match the display. */
|
||||
public static final int CONFIG_COORDS_DEVICE = 1 << CONFIG_COORDS_SHIFT;
|
||||
private static final int CONFIG_COORDS_MASK = 1 << CONFIG_COORDS_SHIFT;
|
||||
|
||||
private static final int CONFIG_FLAG_SHIFT = 9;
|
||||
public static final int CONFIG_FLAG_HIDE_MOUSE = 1 << CONFIG_FLAG_SHIFT;
|
||||
public static final int CONFIG_FLAG_NO_AUTOHIDE_MOUSE = 2 << CONFIG_FLAG_SHIFT;
|
||||
public static final int CONFIG_FLAG_DEBUG = 4 << CONFIG_FLAG_SHIFT;
|
||||
private static final int CONFIG_FLAG_MASK = 7;
|
||||
private final double rawCanvasWidth;
|
||||
private final double rawCanvasHeight;
|
||||
private boolean logKeyEvents = false;
|
||||
private final SubScene subScene;
|
||||
private final List<Canvas> canvases = new ArrayList<>();
|
||||
private final Map<IPaintLayer, Canvas> layerCanvases = new IdentityHashMap<>();
|
||||
private final Canvas background;
|
||||
private final Group root;
|
||||
private Paint bgColor = Color.CORNFLOWERBLUE;
|
||||
private int aspect = 0;
|
||||
private double scaling = 0;
|
||||
private double currentScale = 1.0;
|
||||
private double currentFit = 1.0;
|
||||
private double resolutionScale = 1.0;
|
||||
private int maxScale = 1;
|
||||
private Predicate<KeyEvent> keyOverride = null;
|
||||
private Predicate<KeyEvent> keyPressedHandler = null;
|
||||
private Predicate<KeyEvent> keyTypedHandler = null;
|
||||
private Predicate<KeyEvent> keyReleasedHandler = null;
|
||||
private boolean debug = true;
|
||||
private List<Double> aspects;
|
||||
private boolean hideFullScreenMouseCursor = true;
|
||||
private Cursor oldCursor;
|
||||
|
||||
/** @return the keyTypedHandler */
|
||||
public Predicate<KeyEvent> getKeyTypedHandler() {
|
||||
return keyTypedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyTypedHandler
|
||||
* the keyTypedHandler to set
|
||||
*/
|
||||
public void setKeyTypedHandler(Predicate<KeyEvent> keyTypedHandler) {
|
||||
this.keyTypedHandler = keyTypedHandler;
|
||||
}
|
||||
|
||||
/** @return the keyReleasedHandler */
|
||||
public Predicate<KeyEvent> getKeyReleasedHandler() {
|
||||
return keyReleasedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyReleasedHandler
|
||||
* the keyReleasedHandler to set
|
||||
*/
|
||||
public void setKeyReleasedHandler(Predicate<KeyEvent> keyReleasedHandler) {
|
||||
this.keyReleasedHandler = keyReleasedHandler;
|
||||
}
|
||||
|
||||
/** @return the keyOverride */
|
||||
public Predicate<KeyEvent> getKeyOverride() {
|
||||
return keyOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyOverride
|
||||
* the keyOverride to set
|
||||
*/
|
||||
public void setKeyOverride(Predicate<KeyEvent> keyOverride) {
|
||||
this.keyOverride = keyOverride;
|
||||
}
|
||||
|
||||
/** @return the keyHandler */
|
||||
public Predicate<KeyEvent> getKeyPressedHandler() {
|
||||
return keyPressedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyHandler
|
||||
* the keyHandler to set
|
||||
*/
|
||||
public void setKeyPressedHandler(Predicate<KeyEvent> keyHandler) {
|
||||
this.keyPressedHandler = keyHandler;
|
||||
}
|
||||
|
||||
public Screen(double width, double height, double pixWidth, double pixHeight, double canvasWidth,
|
||||
double canvasHeight) {
|
||||
root = new Group();
|
||||
subScene = new SubScene(root, Math.floor(width), Math.floor(height));
|
||||
resolutionScale = pixWidth / canvasWidth;
|
||||
this.rawCanvasWidth = Math.floor(pixWidth);
|
||||
this.rawCanvasHeight = Math.floor(pixHeight);
|
||||
double aspectRatio = width / height;
|
||||
aspect = 0;
|
||||
for (double a : STD_ASPECTS)
|
||||
if (Math.abs(aspectRatio - a) < 0.01) {
|
||||
break;
|
||||
} else {
|
||||
aspect++;
|
||||
}
|
||||
aspects = new ArrayList<>(STD_ASPECTS);
|
||||
if (aspect >= STD_ASPECTS.size()) {
|
||||
aspects.add(aspectRatio);
|
||||
}
|
||||
background = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
background.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
setBackground(bgColor);
|
||||
clearBackground();
|
||||
root.getChildren().add(background);
|
||||
subScene.layoutBoundsProperty()
|
||||
.addListener((ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds bounds) -> {
|
||||
recomputeLayout(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void clearBackground() {
|
||||
getBackgroundContext().setFill(bgColor);
|
||||
getBackgroundContext().fillRect(0.0, 0.0, background.getWidth(), background.getHeight());
|
||||
}
|
||||
|
||||
public void cycleAspect() {
|
||||
aspect = (aspect + 1) % aspects.size();
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomCycle() {
|
||||
scaling++;
|
||||
if (scaling > maxScale)
|
||||
scaling = ((int) scaling) % maxScale;
|
||||
recomputeLayout(true);
|
||||
}
|
||||
|
||||
public void zoomIn() {
|
||||
scaling = Math.min(10, currentScale + 0.2);
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomOut() {
|
||||
scaling = Math.max(0.1, currentScale - 0.2);
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomFit() {
|
||||
scaling = 0;
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomOne() {
|
||||
scaling = 1;
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void fitScaling() {
|
||||
scaling = 0;
|
||||
recomputeLayout(true);
|
||||
}
|
||||
|
||||
public int getAspect() {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
public GraphicsContext getBackgroundContext() {
|
||||
return background.getGraphicsContext2D();
|
||||
}
|
||||
|
||||
public TurtlePainter createPainter() {
|
||||
Canvas canvas = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
canvas.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
canvases.add(canvas);
|
||||
root.getChildren().add(canvas);
|
||||
return new TurtlePainter(this, canvas);
|
||||
}
|
||||
|
||||
public Printer createPrinter() {
|
||||
Canvas canvas = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
canvas.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
canvases.add(canvas);
|
||||
root.getChildren().add(canvas);
|
||||
return new Printer(this, canvas);
|
||||
}
|
||||
|
||||
private void recomputeLayout(boolean resizeWindow) {
|
||||
double xScale = subScene.getWidth() / getRawWidth();
|
||||
double yScale = subScene.getHeight() / getRawHeight();
|
||||
double xMaxScale = getDisplayWidth() / getRawWidth();
|
||||
double yMaxScale = getDisplayHeight() / getRawHeight();
|
||||
currentFit = Math.min(xScale, yScale);
|
||||
maxScale = (int) Math.max(1, Math.ceil(Math.min(xMaxScale, yMaxScale)));
|
||||
currentScale = scaling == 0 ? currentFit : scaling;
|
||||
|
||||
if (resizeWindow) {
|
||||
Scene scene = subScene.getScene();
|
||||
Window window = scene.getWindow();
|
||||
double hBorder = window.getWidth() - scene.getWidth();
|
||||
double vBorder = window.getHeight() - scene.getHeight();
|
||||
double myWidth = getRawWidth() * currentScale;
|
||||
double myHeight = getRawHeight() * currentScale;
|
||||
if (debug)
|
||||
System.out.printf(
|
||||
"Resizing before: screen: %1.0fx%1.0f, screen: %1.0fx%1.0f, scene: %1.0fx%1.0f, window: %1.0fx%1.0f,%n border: %1.0fx%1.0f, new window size: %1.0fx%1.0f, canvas size: %1.0fx%1.0f%n", //
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
|
||||
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
|
||||
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
|
||||
// this.setWidth(myWidth);
|
||||
// this.setHeight(myHeight);
|
||||
window.setWidth(myWidth + hBorder);
|
||||
window.setHeight(myHeight + vBorder);
|
||||
if (debug)
|
||||
System.out.printf(
|
||||
"Resizing after : screen: %1.0fx%1.0f, screen: %1.0fx%1.0f, scene: %1.0fx%1.0f, window: %1.0fx%1.0f,%n border: %1.0fx%1.0f, new window size: %1.0fx%1.0f, canvas size: %1.0fx%1.0f%n",
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
|
||||
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
|
||||
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
|
||||
}
|
||||
|
||||
if (debug)
|
||||
System.out.printf("Rescaling: subscene %1.2fx%1.2f, scale %1.2f, aspect %.4f (%d), canvas %1.0fx%1.0f%n",
|
||||
subScene.getWidth(), subScene.getHeight(), currentScale, aspects.get(aspect), aspect, getRawWidth(),
|
||||
getRawHeight());
|
||||
for (Node n : root.getChildren()) {
|
||||
n.relocate(Math.floor(subScene.getWidth() / 2),
|
||||
Math.floor(subScene.getHeight() / 2 + (rawCanvasHeight - getRawHeight()) * currentScale / 2));
|
||||
n.setTranslateX(-Math.floor(rawCanvasWidth / 2));
|
||||
n.setTranslateY(-Math.floor(rawCanvasHeight / 2));
|
||||
if (debug)
|
||||
System.out.printf(" * layout %1.2fx%1.2f, translate %1.2fx%1.2f%n", n.getLayoutX(), n.getLayoutY(),
|
||||
n.getTranslateX(), n.getTranslateY());
|
||||
n.setScaleX(currentScale);
|
||||
n.setScaleY(currentScale);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAspect(int aspect) {
|
||||
this.aspect = (aspect) % aspects.size();
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void setBackground(Paint bgColor) {
|
||||
this.bgColor = bgColor;
|
||||
subScene.setFill(bgColor instanceof Color ? ((Color) bgColor).darker() : bgColor);
|
||||
}
|
||||
|
||||
public boolean minimalKeyHandler(KeyEvent event) {
|
||||
KeyCode code = event.getCode();
|
||||
if (event.isShortcutDown()) {
|
||||
if (code == KeyCode.Q) {
|
||||
System.exit(0);
|
||||
} else if (code == KeyCode.PLUS) {
|
||||
zoomIn();
|
||||
return true;
|
||||
} else if (code == KeyCode.MINUS) {
|
||||
zoomOut();
|
||||
return true;
|
||||
}
|
||||
} else if (!(event.isAltDown() || event.isControlDown() || event.isMetaDown() || event.isShiftDown())) {
|
||||
if (code == KeyCode.F11) {
|
||||
setFullScreen(!isFullScreen());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFullScreen() {
|
||||
Window window = subScene.getScene().getWindow();
|
||||
if (window instanceof Stage)
|
||||
return ((Stage) window).isFullScreen();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setFullScreen(boolean fullScreen) {
|
||||
Window window = subScene.getScene().getWindow();
|
||||
if (window instanceof Stage) {
|
||||
((Stage) window).setFullScreenExitHint("");
|
||||
((Stage) window).setFullScreen(fullScreen);
|
||||
if (hideFullScreenMouseCursor) {
|
||||
if (fullScreen) {
|
||||
oldCursor = subScene.getScene().getCursor();
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
} else if (oldCursor != null) {
|
||||
subScene.getScene().setCursor(oldCursor);
|
||||
oldCursor = null;
|
||||
} else {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native physical width of the screen, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This will not include such things as toolbars, menus and such (on a desktop),
|
||||
* or take pixel density into account (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Raw width of the display
|
||||
* @see javafx.stage.Screen#getBounds()
|
||||
*/
|
||||
public static double getRawDisplayWidth() {
|
||||
return javafx.stage.Screen.getPrimary().getBounds().getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native physical height of the screen, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This will not include such things as toolbars, menus and such (on a desktop),
|
||||
* or take pixel density into account (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Raw width of the display
|
||||
* @see javafx.stage.Screen#getBounds()
|
||||
*/
|
||||
public static double getRawDisplayHeight() {
|
||||
return javafx.stage.Screen.getPrimary().getBounds().getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the display, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This takes into account such things as toolbars, menus and such (on a
|
||||
* desktop), and pixel density (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Width of the display
|
||||
* @see javafx.stage.Screen#getVisualBounds()
|
||||
*/
|
||||
public static double getDisplayWidth() {
|
||||
return javafx.stage.Screen.getPrimary().getVisualBounds().getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of the display, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This takes into account such things as toolbars, menus and such (on a
|
||||
* desktop), and pixel density (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Height of the display
|
||||
* @see javafx.stage.Screen#getVisualBounds()
|
||||
*/
|
||||
public static double getDisplayHeight() {
|
||||
return javafx.stage.Screen.getPrimary().getVisualBounds().getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resolution of this screen, in DPI (pixels per inch).
|
||||
*
|
||||
* @return The primary display's DPI
|
||||
* @see javafx.stage.Screen#getDpi()
|
||||
*/
|
||||
public static double getDisplayDpi() {
|
||||
return javafx.stage.Screen.getPrimary().getDpi();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the paint display system.
|
||||
*
|
||||
* This will open a window on the screen, and set up background, text and paint
|
||||
* layers, and listener to handle keyboard input.
|
||||
*
|
||||
* @param stage
|
||||
* A JavaFX {@link javafx.stage.Stage}, typically obtained from the
|
||||
* {@link javafx.application.Application#start(Stage)} method
|
||||
* @return A screen for drawing on
|
||||
*/
|
||||
public static Screen startPaintScene(Stage stage) {
|
||||
return startPaintScene(stage, CONFIG_SCREEN_FULLSCREEN_NO_HINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the paint display system.
|
||||
*
|
||||
* This will open a window on the screen, and set up background, text and paint
|
||||
* layers, and listener to handle keyboard input.
|
||||
*
|
||||
* @param stage
|
||||
* A JavaFX {@link javafx.stage.Stage}, typically obtained from the
|
||||
* {@link javafx.application.Application#start(Stage)} method
|
||||
* @return A screen for drawing on
|
||||
*/
|
||||
public static Screen startPaintScene(Stage stage, int configuration) {
|
||||
int configAspect = (configuration & CONFIG_ASPECT_MASK);
|
||||
int configScreen = (configuration & CONFIG_SCREEN_MASK);
|
||||
int configPixels = (configuration & CONFIG_PIXELS_MASK);
|
||||
int configCoords = (configuration & CONFIG_COORDS_MASK);
|
||||
int configFlags = (configuration & CONFIG_FLAG_MASK);
|
||||
boolean debug = (configFlags & CONFIG_FLAG_DEBUG) != 0;
|
||||
if (configPixels == CONFIG_PIXELS_DEFAULT) {
|
||||
if (configCoords == CONFIG_COORDS_DEVICE || configScreen == CONFIG_SCREEN_FULLSCREEN)
|
||||
configPixels = CONFIG_PIXELS_DEVICE;
|
||||
else
|
||||
configPixels = CONFIG_PIXELS_STEP_SCALED;
|
||||
}
|
||||
double rawWidth = getRawDisplayWidth();
|
||||
double rawHeight = getRawDisplayHeight();
|
||||
double width = getDisplayWidth() - 40;
|
||||
double height = getDisplayHeight() - 100;
|
||||
double canvasAspect = configAspect == CONFIG_ASPECT_DEVICE ? rawWidth / rawHeight
|
||||
: STD_ASPECTS.get(configAspect);
|
||||
double xScale = (height * canvasAspect) / Screen.STD_CANVAS_WIDTH;
|
||||
double yScale = (width / canvasAspect) / (Screen.STD_CANVAS_WIDTH / canvasAspect);
|
||||
double scale = Math.min(xScale, yScale);
|
||||
if (configPixels == CONFIG_PIXELS_STEP_SCALED) {
|
||||
if (scale > 1.0)
|
||||
scale = Math.max(1, Math.floor(scale));
|
||||
else if (scale < 1.0)
|
||||
scale = 1 / Math.max(1, Math.floor(1 / scale));
|
||||
}
|
||||
double winWidth = Math.floor(Screen.STD_CANVAS_WIDTH * scale);
|
||||
double winHeight = Math.floor((Screen.STD_CANVAS_WIDTH / canvasAspect) * scale);
|
||||
double canvasWidth = Screen.STD_CANVAS_WIDTH;
|
||||
double canvasHeight = Math.floor(3 * Screen.STD_CANVAS_WIDTH / 4);
|
||||
double pixWidth = canvasWidth;
|
||||
double pixHeight = canvasHeight;
|
||||
if (configPixels == CONFIG_PIXELS_SCALED || configPixels == CONFIG_PIXELS_STEP_SCALED) {
|
||||
pixWidth *= scale;
|
||||
pixHeight *= scale;
|
||||
} else if (configPixels == CONFIG_PIXELS_DEVICE) {
|
||||
pixWidth = rawWidth;
|
||||
pixHeight = rawHeight;
|
||||
}
|
||||
if (configCoords == CONFIG_COORDS_DEVICE) {
|
||||
canvasWidth = pixWidth;
|
||||
canvasHeight = pixHeight;
|
||||
}
|
||||
if (debug) {
|
||||
System.out.printf("Screen setup:%n");
|
||||
System.out.printf(" Display: %.0fx%.0f (raw %.0fx%.0f)%n", width, height, rawWidth, rawHeight);
|
||||
System.out.printf(" Window: %.0fx%.0f%n", winWidth, winHeight);
|
||||
System.out.printf(" Canvas: physical %.0fx%.0f, logical %.0fx%.0f%n", pixWidth, pixHeight, canvasWidth,
|
||||
canvasHeight);
|
||||
System.out.printf(" Aspect: %.5f Scale: %.5f%n", canvasAspect, scale);
|
||||
}
|
||||
Group root = new Group();
|
||||
Scene scene = new Scene(root, winWidth, winHeight, Color.BLACK);
|
||||
stage.setScene(scene);
|
||||
if ((configFlags & CONFIG_FLAG_HIDE_MOUSE) != 0) {
|
||||
scene.setCursor(Cursor.NONE);
|
||||
}
|
||||
|
||||
Screen pScene = new Screen(scene.getWidth(), scene.getHeight(), //
|
||||
pixWidth, pixHeight, //
|
||||
canvasWidth, canvasHeight);
|
||||
pScene.subScene.widthProperty().bind(scene.widthProperty());
|
||||
pScene.subScene.heightProperty().bind(scene.heightProperty());
|
||||
pScene.debug = debug;
|
||||
pScene.hideFullScreenMouseCursor = (configFlags & CONFIG_FLAG_NO_AUTOHIDE_MOUSE) == 0;
|
||||
root.getChildren().add(pScene.subScene);
|
||||
|
||||
boolean[] suppressKeyTyped = { false };
|
||||
|
||||
switch (configScreen) {
|
||||
case CONFIG_SCREEN_WINDOWED:
|
||||
break;
|
||||
case CONFIG_SCREEN_BORDERLESS:
|
||||
stage.initStyle(StageStyle.UNDECORATED);
|
||||
break;
|
||||
case CONFIG_SCREEN_TRANSPARENT:
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
break;
|
||||
case CONFIG_SCREEN_FULLSCREEN_NO_HINT:
|
||||
stage.setFullScreenExitHint("");
|
||||
// fall-through
|
||||
case CONFIG_SCREEN_FULLSCREEN:
|
||||
stage.setFullScreen(true);
|
||||
break;
|
||||
}
|
||||
scene.setOnKeyPressed((KeyEvent event) -> {
|
||||
if (!event.isConsumed() && pScene.keyOverride != null && pScene.keyOverride.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
if (!event.isConsumed() && pScene.minimalKeyHandler(event)) {
|
||||
event.consume();
|
||||
}
|
||||
if (!event.isConsumed() && pScene.keyPressedHandler != null && pScene.keyPressedHandler.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
if (pScene.logKeyEvents)
|
||||
System.err.println(event);
|
||||
suppressKeyTyped[0] = event.isConsumed();
|
||||
});
|
||||
scene.setOnKeyTyped((KeyEvent event) -> {
|
||||
if (suppressKeyTyped[0]) {
|
||||
suppressKeyTyped[0] = false;
|
||||
event.consume();
|
||||
}
|
||||
if (!event.isConsumed() && pScene.keyTypedHandler != null && pScene.keyTypedHandler.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
if (pScene.logKeyEvents)
|
||||
System.err.println(event);
|
||||
});
|
||||
scene.setOnKeyReleased((KeyEvent event) -> {
|
||||
suppressKeyTyped[0] = false;
|
||||
if (!event.isConsumed() && pScene.keyReleasedHandler != null && pScene.keyReleasedHandler.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
if (pScene.logKeyEvents)
|
||||
System.err.println(event);
|
||||
});
|
||||
return pScene;
|
||||
}
|
||||
|
||||
public double getRawWidth() {
|
||||
return rawCanvasWidth;
|
||||
}
|
||||
|
||||
public double getRawHeight() {
|
||||
return Math.floor(rawCanvasWidth / aspects.get(aspect));
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return Math.floor(getRawWidth() / resolutionScale);
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return Math.floor(getRawHeight() / resolutionScale);
|
||||
}
|
||||
|
||||
public void moveToFront(IPaintLayer layer) {
|
||||
Canvas canvas = layerCanvases.get(layer);
|
||||
if (canvas != null) {
|
||||
canvas.toFront();
|
||||
}
|
||||
}
|
||||
|
||||
public void moveToBack(IPaintLayer layer) {
|
||||
Canvas canvas = layerCanvases.get(layer);
|
||||
if (canvas != null) {
|
||||
canvas.toBack();
|
||||
background.toBack();
|
||||
}
|
||||
}
|
||||
|
||||
public void hideMouseCursor() {
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
}
|
||||
|
||||
public void showMouseCursor() {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
public void setMouseCursor(Cursor cursor) {
|
||||
subScene.getScene().setCursor(cursor);
|
||||
}
|
||||
|
||||
public void setHideFullScreenMouseCursor(boolean hideIt) {
|
||||
if (hideIt != hideFullScreenMouseCursor && isFullScreen()) {
|
||||
if (hideIt) {
|
||||
oldCursor = subScene.getScene().getCursor();
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
} else if (oldCursor != null) {
|
||||
subScene.getScene().setCursor(oldCursor);
|
||||
oldCursor = null;
|
||||
} else {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
hideFullScreenMouseCursor = hideIt;
|
||||
}
|
||||
}
|
BIN
src/inf101/v18/gfx/fonts/Giana.ttf
Normal file
BIN
src/inf101/v18/gfx/fonts/Giana.ttf
Normal file
Binary file not shown.
BIN
src/inf101/v18/gfx/fonts/PetMe64.ttf
Normal file
BIN
src/inf101/v18/gfx/fonts/PetMe64.ttf
Normal file
Binary file not shown.
39
src/inf101/v18/gfx/fonts/README_ZXSpectrum-7.md
Normal file
39
src/inf101/v18/gfx/fonts/README_ZXSpectrum-7.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# ZX Spectrum 7 Font
|
||||
|
||||
By Sizenko Alexander, [Style-7](http://www.styleseven.com), December 2012. From http://www.styleseven.com/php/get_product.php?product=ZX-Spectrum-7
|
||||
|
||||
Minor metrics adjustments by Anya Helene Bagge, October 2017.
|
||||
|
||||
* ZXSpectrum-7.otf – monospaced regular
|
||||
|
||||
# True Type Font: ZX Spectrum-7 version 1.0
|
||||
|
||||
|
||||
## EULA
|
||||
|
||||
The font ZX Spectrum-7 is freeware.
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
This font has been created to mark the 30th anniversary of the great computer ZX-Spectrum. Font based on native ZX-Spectrum symbols. Latin and Cyrillic code pages are supported.
|
||||
|
||||
Files in zx_spectrum-7.zip:
|
||||
* readme.txt this file;
|
||||
* zx_spectrum-7.ttf regular style;
|
||||
* zx_spectrum-7_bold.ttf bold style;
|
||||
* zx_spectrum-7_screen.png preview image.
|
||||
|
||||
Please visit http://www.styleseven.com/ for download our other products as freeware as shareware.
|
||||
We will welcome any useful suggestions and comments; please send them to ms-7@styleseven.com
|
||||
|
||||
|
||||
## WHAT'S NEW?
|
||||
* Version 1.01 (December 27 2012):
|
||||
* Fixed bug for Cyrillic code page.
|
||||
|
||||
|
||||
## AUTHOR
|
||||
Sizenko Alexander
|
||||
Style-7
|
||||
http://www.styleseven.com
|
||||
Created: December 24 2012
|
171
src/inf101/v18/gfx/fonts/README_lmmono10-regular.md
Normal file
171
src/inf101/v18/gfx/fonts/README_lmmono10-regular.md
Normal file
@@ -0,0 +1,171 @@
|
||||
This package was debianized by Florent Rougon <f.rougon@free.fr> on
|
||||
Tue, 27 Jan 2004 20:12:21 +0100.
|
||||
|
||||
The following TeX Live packages were downloaded from
|
||||
http://www.ctan.org/tex-archive/systems/texlive/tlnet/archive/
|
||||
and merged into one orig.tar.gz file:
|
||||
lm.tar.xz
|
||||
lm.doc.tar.xz
|
||||
lm-math.tar.xz
|
||||
lm-math.doc.tar.xz
|
||||
|
||||
|
||||
Upstream work
|
||||
-------------
|
||||
|
||||
The upstream README-Latin-Modern.txt file says:
|
||||
|
||||
Font: The Latin Modern Family of Fonts
|
||||
Designer (Computer Modern Family of Fonts): Donald E. Knuth
|
||||
Author: Bogus\l{}aw Jackowski and Janusz M. Nowacki
|
||||
Version: 2.003
|
||||
Date: 16 IX 2009
|
||||
Downloads: http://www.gust.org.pl/projects/e-foundry/latin-modern/
|
||||
License:
|
||||
% Copyright 2003--2009 by B. Jackowski and J.M. Nowacki
|
||||
% (on behalf of TeX Users Groups).
|
||||
% This work is released under the GUST Font License
|
||||
% -- see GUST-FONT-LICENSE.txt.
|
||||
% This work has the LPPL maintenance status "maintained".
|
||||
% The Current Maintainer of this work is Bogus\l{}aw Jackowski
|
||||
% and Janusz M. Nowacki.
|
||||
% This work consists of the files listed in the MANIFEST.txt file.
|
||||
|
||||
[...]
|
||||
|
||||
The current LM package contains the most recent version
|
||||
of the Latin Modern family of fonts in the PostScript Type 1 and
|
||||
OpenType format. The fonts are based on Donald E. Knuth's Computer Modern
|
||||
fonts in the PostScript Type 1 format, released into public domain by the
|
||||
American Mathematical Society (for the history of the outline version of
|
||||
the CM fonts see, e.g., http://www.math.utah.edu/~beebe/fonts/bluesky.html ).
|
||||
The project is supported by TeX users groups: CSTUG, DANTE eV, GUST,
|
||||
GUTenberg, NTG, and TUG.
|
||||
|
||||
|
||||
The current README-Latin-Modern-Math.txt says
|
||||
License:
|
||||
% Copyright 2012--2014 for the Latin Modern math extensions by B. Jackowski,
|
||||
% P. Strzelczyk and P. Pianowski (on behalf of TeX Users Groups).
|
||||
%
|
||||
% This work can be freely used and distributed under
|
||||
% the GUST Font License (GFL -- see GUST-FONT-LICENSE.txt)
|
||||
% which is actually an instance of the LaTeX Project Public License
|
||||
% (LPPL -- see http://www.latex-project.org/lppl.txt).
|
||||
%
|
||||
% This work has the maintenance status "maintained". The Current Maintainer
|
||||
% of this work is Bogus\l{}aw Jackowski, Piotr Strzelczyk and Piotr Pianowski.
|
||||
%
|
||||
% This work consists of the files listed
|
||||
% in the MANIFEST-Latin-Modern-Math.txt file.
|
||||
|
||||
|
||||
See the appendix B for the GUST Font License.
|
||||
|
||||
Please read the appendix A below if you want to examine the licensing terms
|
||||
for the Computer Modern fonts in Type 1 format on which the Latin Modern fonts
|
||||
are based.
|
||||
|
||||
|
||||
Debian packaging
|
||||
----------------
|
||||
|
||||
Copyright (c) 2004-2007 Florent Rougon
|
||||
Copyright (c) 2005-2015 Norbert Preining
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; version 2 dated June, 1991.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; see the file COPYING. If not, write to the
|
||||
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
Boston, MA 02110-1301 USA.
|
||||
|
||||
On Debian systems, the complete text of the GNU General Public License version
|
||||
2 can be found in `/usr/share/common-licenses/GPL-2'.
|
||||
|
||||
|
||||
Appendix A -- Licensing terms for the Computer Modern fonts in Type 1 format
|
||||
on which the Latin Modern fonts are based.
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Running t1disasm (from the t1utils package) on
|
||||
/usr/share/texmf/fonts/type1/bluesky/cm/*.pfb and
|
||||
/usr/share/texmf/fonts/type1/bluesky/cmextra/*.pfb yields:
|
||||
|
||||
Copyright (C) 1997 American Mathematical Society. All Rights Reserved
|
||||
|
||||
[ Florent Rougon's note: well, for
|
||||
/usr/share/texmf/fonts/type1/bluesky/cm/cmb{,sy}10.pfb, you will only get
|
||||
one space between "Society." and "All" (with tetex-extra 2.0.2-5.1). ;-) ]
|
||||
|
||||
The precise distribution conditions for these fonts can be found in
|
||||
/usr/share/doc/texmf/fonts/bluesky/README from the tetex-doc package. I will
|
||||
duplicate here the relevant excerpt for your convenience:
|
||||
|
||||
The PostScript Type 1 implementation of the Computer Modern fonts produced
|
||||
by and previously distributed by Blue Sky Research and Y&Y, Inc. are now
|
||||
freely available for general use. This has been accomplished through the
|
||||
cooperation of a consortium of scientific publishers with Blue Sky Research
|
||||
and Y&Y. Members of this consortium include:
|
||||
|
||||
Elsevier Science
|
||||
IBM Corporation
|
||||
Society for Industrial and Applied Mathematics (SIAM)
|
||||
Springer-Verlag
|
||||
American Mathematical Society (AMS)
|
||||
|
||||
In order to assure the authenticity of these fonts, copyright will be held
|
||||
by the American Mathematical Society. This is not meant to restrict in any
|
||||
way the legitimate use of the fonts, such as (but not limited to) electronic
|
||||
distribution of documents containing these fonts, inclusion of these fonts
|
||||
into other public domain or commercial font collections or computer
|
||||
applications, use of the outline data to create derivative fonts and/or
|
||||
faces, etc. However, the AMS does require that the AMS copyright notice be
|
||||
removed from any derivative versions of the fonts which have been altered in
|
||||
any way. In addition, to ensure the fidelity of TeX documents using Computer
|
||||
Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
|
||||
has requested that any alterations which yield different font metrics be
|
||||
given a different name.
|
||||
|
||||
|
||||
Appendix B -- GUST Font License
|
||||
-------------------------------
|
||||
|
||||
What follows is the exact contents of GUST-FONT-LICENSE.txt from the
|
||||
upstream distribution of the Latin Modern fonts.
|
||||
|
||||
% This is version 1.0, dated 22 June 2009, of the GUST Font License.
|
||||
% (GUST is the Polish TeX Users Group, http://www.gust.org.pl)
|
||||
%
|
||||
% For the most recent version of this license see
|
||||
% http://www.gust.org.pl/fonts/licenses/GUST-FONT-LICENSE.txt
|
||||
% or
|
||||
% http://tug.org/fonts/licenses/GUST-FONT-LICENSE.txt
|
||||
%
|
||||
% This work may be distributed and/or modified under the conditions
|
||||
% of the LaTeX Project Public License, either version 1.3c of this
|
||||
% license or (at your option) any later version.
|
||||
%
|
||||
% Please also observe the following clause:
|
||||
% 1) it is requested, but not legally required, that derived works be
|
||||
% distributed only after changing the names of the fonts comprising this
|
||||
% work and given in an accompanying "manifest", and that the
|
||||
% files comprising the Work, as listed in the manifest, also be given
|
||||
% new names. Any exceptions to this request are also given in the
|
||||
% manifest.
|
||||
%
|
||||
% We recommend the manifest be given in a separate file named
|
||||
% MANIFEST-<fontid>.txt, where <fontid> is some unique identification
|
||||
% of the font family. If a separate "readme" file accompanies the Work,
|
||||
% we recommend a name of the form README-<fontid>.txt.
|
||||
%
|
||||
% The latest version of the LaTeX Project Public License is in
|
||||
% http://www.latex-project.org/lppl.txt and version 1.3c or later
|
||||
% is part of all distributions of LaTeX version 2006/05/20 or later.
|
BIN
src/inf101/v18/gfx/fonts/ZXSpectrum-7.otf
Normal file
BIN
src/inf101/v18/gfx/fonts/ZXSpectrum-7.otf
Normal file
Binary file not shown.
BIN
src/inf101/v18/gfx/fonts/lmmono10-regular.otf
Normal file
BIN
src/inf101/v18/gfx/fonts/lmmono10-regular.otf
Normal file
Binary file not shown.
204
src/inf101/v18/gfx/gfxmode/Direction.java
Normal file
204
src/inf101/v18/gfx/gfxmode/Direction.java
Normal file
@@ -0,0 +1,204 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
/**
|
||||
* @author anya
|
||||
*
|
||||
*/
|
||||
public class Direction {
|
||||
/**
|
||||
* Construct direction from an angle
|
||||
*
|
||||
* @param degrees
|
||||
* Angle in degrees, where 0 is (1,0)
|
||||
*/
|
||||
public static Direction fromDegrees(double degrees) {
|
||||
return new Direction(degrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct direction from a vector
|
||||
*
|
||||
* @param x
|
||||
* X direction
|
||||
* @param y
|
||||
* Y direction
|
||||
*/
|
||||
public static Direction fromVector(double x, double y) {
|
||||
return new Direction(x, y);
|
||||
}
|
||||
|
||||
private double xDir;
|
||||
|
||||
private double yDir;
|
||||
|
||||
/**
|
||||
* Create a new direction.
|
||||
*
|
||||
* The direction vector will be normalised to a vector of length 1.
|
||||
*
|
||||
* @param degrees
|
||||
* Angle of direction in degrees
|
||||
*/
|
||||
public Direction(double degrees) {
|
||||
double radians = Math.toRadians(degrees);
|
||||
this.xDir = Math.cos(radians);
|
||||
this.yDir = Math.sin(radians);
|
||||
normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new direction.
|
||||
*
|
||||
* The direction vector will be normalised to a vector of length 1.
|
||||
*
|
||||
* @param xDir
|
||||
* X-component of direction vector
|
||||
* @param yDir
|
||||
* Y-component of direction vector
|
||||
*/
|
||||
public Direction(double xDir, double yDir) {
|
||||
this.xDir = xDir;
|
||||
this.yDir = yDir;
|
||||
normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiply direction by distance
|
||||
*
|
||||
* @param distance
|
||||
* @return Position delta
|
||||
*/
|
||||
public Point getMovement(double distance) {
|
||||
return new Point(xDir * distance, -yDir * distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return X-component of direction vector
|
||||
*
|
||||
* Same as the Math.cos(toRadians())
|
||||
*/
|
||||
public double getX() {
|
||||
return xDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Y-component of direction vector
|
||||
*
|
||||
* Same as the Math.sin(toRadians())
|
||||
*/
|
||||
public double getY() {
|
||||
return yDir;
|
||||
}
|
||||
|
||||
private void normalize() {
|
||||
double l = Math.sqrt(xDir * xDir + yDir * yDir);
|
||||
if (l >= 0.00001) {
|
||||
xDir = xDir / l;
|
||||
yDir = yDir / l;
|
||||
} else if (xDir > 0) {
|
||||
xDir = 1;
|
||||
yDir = 0;
|
||||
} else if (xDir < 0) {
|
||||
xDir = -1;
|
||||
yDir = 0;
|
||||
} else if (yDir > 0) {
|
||||
xDir = 0;
|
||||
yDir = 1;
|
||||
} else if (yDir < 0) {
|
||||
xDir = 0;
|
||||
yDir = -1;
|
||||
} else {
|
||||
xDir = 1;
|
||||
yDir = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate to angle (in degrees)
|
||||
*
|
||||
* @return Angle in degrees, -180 .. 180
|
||||
*/
|
||||
public double toDegrees() {
|
||||
return Math.toDegrees(Math.atan2(yDir, xDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate to angle (in radians)
|
||||
*
|
||||
* @return Angle in radians, -2π .. 2π
|
||||
*/
|
||||
public double toRadians() {
|
||||
return Math.atan2(yDir, xDir);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%.2f", toDegrees());
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn (relative)
|
||||
*
|
||||
* @param deltaDir
|
||||
*/
|
||||
public Direction turn(Direction deltaDir) {
|
||||
return new Direction(xDir + deltaDir.xDir, yDir + deltaDir.yDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn angle degrees
|
||||
*
|
||||
* @param angle
|
||||
*/
|
||||
public Direction turn(double angle) {
|
||||
return turnTo(toDegrees() + angle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn around 180 degrees
|
||||
*/
|
||||
public Direction turnBack() {
|
||||
return turn(180.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn left 90 degrees
|
||||
*/
|
||||
public Direction turnLeft() {
|
||||
return turn(90.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn right 90 degrees
|
||||
*/
|
||||
public Direction turnRight() {
|
||||
return turn(-90.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute turn
|
||||
*
|
||||
* @param degrees
|
||||
* Angle in degrees, where 0 is (1,0)
|
||||
*/
|
||||
public Direction turnTo(double degrees) {
|
||||
return new Direction(degrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn slightly towards a directions
|
||||
*
|
||||
* @param dir
|
||||
* A direction
|
||||
* @param percent
|
||||
* How much to turn (100.0 is the same as turnTo())
|
||||
*/
|
||||
public Direction turnTowards(Direction dir, double percent) {
|
||||
return new Direction(xDir * (1.00 - percent / 100.0) + dir.xDir * (percent / 100.0),
|
||||
yDir * (1.00 - percent / 100.0) + dir.yDir * (percent / 100.0));
|
||||
// double thisAngle = toAngle();
|
||||
// double otherAngle = dir.toAngle();
|
||||
// turnTo(thisAngle*(1.00 - percent/100.0) +
|
||||
// otherAngle*(percent/100.0));
|
||||
}
|
||||
}
|
5
src/inf101/v18/gfx/gfxmode/Gravity.java
Normal file
5
src/inf101/v18/gfx/gfxmode/Gravity.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
public enum Gravity {
|
||||
NORTH, NORTHWEST, WEST, SOUTHWEST, SOUTH, SOUTHEAST, EAST, NORTHEAST, CENTER
|
||||
}
|
17
src/inf101/v18/gfx/gfxmode/IPainter.java
Normal file
17
src/inf101/v18/gfx/gfxmode/IPainter.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
import inf101.v18.gfx.IPaintLayer;
|
||||
import javafx.scene.paint.Paint;
|
||||
|
||||
public interface IPainter extends IPaintLayer {
|
||||
|
||||
IShape shape();
|
||||
|
||||
ITurtle turtle();
|
||||
|
||||
IPainter restore();
|
||||
|
||||
IPainter save();
|
||||
|
||||
IPainter setInk(Paint ink);
|
||||
}
|
239
src/inf101/v18/gfx/gfxmode/IShape.java
Normal file
239
src/inf101/v18/gfx/gfxmode/IShape.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Shape;
|
||||
|
||||
public interface IShape {
|
||||
|
||||
void draw();
|
||||
|
||||
void draw(GraphicsContext context);
|
||||
|
||||
Shape toFXShape();
|
||||
|
||||
String toSvg();
|
||||
|
||||
/**
|
||||
* Set the (x,y)-coordinates of the next draw command
|
||||
*
|
||||
* @param p
|
||||
* Coordinates
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape at(Point p);
|
||||
|
||||
/**
|
||||
* Set the x-coordinate of the next draw command
|
||||
*
|
||||
* @param x
|
||||
* Coordinate
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape x(double x);
|
||||
|
||||
/**
|
||||
* Set the y-coordinate of the next draw command
|
||||
*
|
||||
* @param y
|
||||
* Coordinate
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape y(double y);
|
||||
|
||||
/**
|
||||
* Set gravity for the subsequent draw commands
|
||||
*
|
||||
* Gravity determines the point on the shape that will be used for positioning
|
||||
* and rotation.
|
||||
*
|
||||
* @param g
|
||||
* The gravity
|
||||
* @return
|
||||
*/
|
||||
IShape gravity(Gravity g);
|
||||
|
||||
/**
|
||||
* Sets rotation for subsequent draw commands.
|
||||
*
|
||||
* <p>
|
||||
* Shapes will be rotate around the {@link #gravity(Gravity)} point.
|
||||
*
|
||||
* @param angle
|
||||
* Rotation in degrees
|
||||
* @return
|
||||
*/
|
||||
IShape rotation(double angle);
|
||||
|
||||
/**
|
||||
* Add another point to the line path
|
||||
*
|
||||
* @param xy
|
||||
* @return
|
||||
*/
|
||||
IShape addPoint(Point xy);
|
||||
|
||||
/**
|
||||
* Add another point to the line path
|
||||
*
|
||||
* @param xy
|
||||
* @return
|
||||
*/
|
||||
IShape addPoint(double x, double y);
|
||||
|
||||
/**
|
||||
* Close the line path, turning it into a polygon.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
IShape close();
|
||||
|
||||
/**
|
||||
* Draw an ellipse with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #width(double)}, {@link #height(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape ellipse();
|
||||
|
||||
/**
|
||||
* Draw a rectangle with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #width(double)}, {@link #height(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape rectangle();
|
||||
|
||||
/**
|
||||
* Draw an arc with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #length(double)}
|
||||
* <li>{@link #angle(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape arc();
|
||||
|
||||
/**
|
||||
* Draw a line with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #length(double)}
|
||||
* <li>{@link #angle(double)}
|
||||
* <li>{@link #gravity(Gravity)} (flattened to the horizontal axis, so, e.g.,
|
||||
* {@link Gravity#NORTH} = {@link Gravity#SOUTH} = {@link Gravity#CENTER})
|
||||
* <li>{@link #stroke(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape line();
|
||||
|
||||
/**
|
||||
* Set the arc angle for the subsequent draw commands
|
||||
*
|
||||
* <p>
|
||||
* For use with {@link #arc()}
|
||||
*
|
||||
* @param a
|
||||
* The angle, in degrees
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape angle(double a);
|
||||
|
||||
/**
|
||||
* Set fill colour for the subsequent draw commands
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape fillPaint(Paint p);
|
||||
|
||||
/**
|
||||
* Fill the current shape
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape fill();
|
||||
|
||||
/**
|
||||
* Set the length of the following draw commands
|
||||
*
|
||||
* <p>
|
||||
* For use with {@link #line()} and {@link #arc()}
|
||||
*
|
||||
* @param l
|
||||
* The length
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape length(double l);
|
||||
|
||||
/**
|
||||
* Stroke the current shape
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape stroke();
|
||||
|
||||
/**
|
||||
* Set stroke colour for the subsequent draw commands
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape strokePaint(Paint p);
|
||||
|
||||
/**
|
||||
* Set the width of the next draw command
|
||||
*
|
||||
* @param w
|
||||
* The width
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape width(double w);
|
||||
|
||||
/**
|
||||
* Set the height of the next draw command
|
||||
*
|
||||
* @param h
|
||||
* The height
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape height(double h);
|
||||
|
||||
}
|
240
src/inf101/v18/gfx/gfxmode/ITurtle.java
Normal file
240
src/inf101/v18/gfx/gfxmode/ITurtle.java
Normal file
@@ -0,0 +1,240 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
|
||||
public interface ITurtle extends IPainter {
|
||||
|
||||
void debugTurtle();
|
||||
|
||||
/**
|
||||
* Start drawing a shape at the current turtle position.
|
||||
*
|
||||
* <p>
|
||||
* The shape's default origin and rotation will be set to the turtle's current
|
||||
* position and direction, but can be modified with {@link IShape#at(Point)} and
|
||||
* {@link IShape#rotation(double)}.
|
||||
* <p>
|
||||
* The turtle's position and attributes are unaffected by drawing the shape.
|
||||
*
|
||||
* @return An IDrawParams object for setting up and drawing the shape
|
||||
*/
|
||||
IShape shape();
|
||||
|
||||
/**
|
||||
* Draw a line from the current position to the given position.
|
||||
*
|
||||
* <p>
|
||||
* This method does not change the turtle position.
|
||||
*
|
||||
* @param to
|
||||
* Other end-point of the line
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle line(Point to);
|
||||
|
||||
/**
|
||||
* @return The current angle of the turtle, with 0° pointing to the right and
|
||||
* 90° pointing up. Same as {@link #getDirection()}.getAngle()
|
||||
*/
|
||||
double getAngle();
|
||||
|
||||
/**
|
||||
* @return The current direction of the turtle. Same as calling
|
||||
* <code>new Direction(getAngle())</code>
|
||||
*/
|
||||
Direction getDirection();
|
||||
|
||||
/**
|
||||
* @return The current position of the turtle.
|
||||
*/
|
||||
Point getPos();
|
||||
|
||||
/**
|
||||
* Move a distance without drawing.
|
||||
*
|
||||
* @param dist
|
||||
* Distance to move
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle jump(double dist);
|
||||
|
||||
/**
|
||||
* Move a position without drawing.
|
||||
*
|
||||
* @param x
|
||||
* X position to move to
|
||||
* @param y
|
||||
* Y position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle jumpTo(double x, double y);
|
||||
|
||||
/**
|
||||
* Move a position without drawing.
|
||||
*
|
||||
* @param to
|
||||
* X,Y position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle jumpTo(Point to);
|
||||
|
||||
/**
|
||||
* Move forward the given distance while drawing a line
|
||||
*
|
||||
* @param dist
|
||||
* Distance to move
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle draw(double dist);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a line
|
||||
*
|
||||
* @param x
|
||||
* X-position to move to
|
||||
* @param y
|
||||
* Y-position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle drawTo(double x, double y);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a line
|
||||
*
|
||||
* @param to
|
||||
* Position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle drawTo(Point to);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a curve
|
||||
*
|
||||
* <p>
|
||||
* The resulting curve is a cubic Bézier curve with the control points located
|
||||
* at <code>getPos().move(getDirection, startControl)</code> and
|
||||
* <code>to.move(Direction.fromDegrees(endAngle+180), endControl)</code>.
|
||||
* <p>
|
||||
* The turtle is left at point <code>to</code>, facing <code>endAngle</code>.
|
||||
* <p>
|
||||
* The turtle will start out moving in its current direction, aiming for a point
|
||||
* <code>startControl</code> pixels away, then smoothly turning towards its
|
||||
* goal. It will approach the <code>to</code> point moving in the direction
|
||||
* <code>endAngle</code> (an absolute bearing, with 0° pointing right and 90°
|
||||
* pointing up).
|
||||
*
|
||||
* @param to
|
||||
* Position to move to
|
||||
* @param startControl
|
||||
* Distance to the starting control point.
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle curveTo(Point to, double startControl, double endAngle, double endControl);
|
||||
|
||||
/**
|
||||
* Set the size of the turtle's pen
|
||||
*
|
||||
* @param pixels
|
||||
* Line width, in pixels
|
||||
* @return {@code this}, for sending more draw commands
|
||||
* @requires pixels >= 0
|
||||
*/
|
||||
ITurtle setPenSize(double pixels);
|
||||
|
||||
/**
|
||||
* Change direction the given number of degrees (relative to the current
|
||||
* direction).
|
||||
*
|
||||
* <p>
|
||||
* Positive degrees turn <em>left</em> while negative degrees turn
|
||||
* <em>right</em>.
|
||||
*
|
||||
* @param degrees
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle turn(double degrees);
|
||||
|
||||
/**
|
||||
* Turn 180°.
|
||||
*
|
||||
* <p>
|
||||
* Same as <code>turn(180)</code> and <code>turn(-180)</code>.
|
||||
*
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle turnAround();
|
||||
|
||||
/**
|
||||
* Turn left 90°.
|
||||
*
|
||||
* <p>
|
||||
* Same as <code>turn(90)</code>.
|
||||
*
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle turnLeft();
|
||||
|
||||
/**
|
||||
* Turn left.
|
||||
*
|
||||
* <p>
|
||||
* Same as <code>turn(degrees)</code>.
|
||||
*
|
||||
* @return {@code this}, for sending more draw commands
|
||||
* @requires degrees >= 0
|
||||
*/
|
||||
ITurtle turnLeft(double degrees);
|
||||
|
||||
/**
|
||||
* Turn right 90°.
|
||||
*
|
||||
* <p>
|
||||
* Same as <code>turn(-90)</code>.
|
||||
*
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle turnRight();
|
||||
|
||||
/**
|
||||
* Turn left.
|
||||
*
|
||||
* <p>
|
||||
* Same as <code>turn(-degrees)</code>.
|
||||
*
|
||||
* @return {@code this}, for sending more draw commands
|
||||
* @requires degrees >= 0
|
||||
*/
|
||||
ITurtle turnRight(double degrees);
|
||||
|
||||
/**
|
||||
* Turn to the given bearing.
|
||||
*
|
||||
* <p>
|
||||
* 0° is due right, 90° is up.
|
||||
*
|
||||
* @param degrees
|
||||
* Bearing, in degrees
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle turnTo(double degrees);
|
||||
|
||||
/**
|
||||
* Turn towards the given bearing.
|
||||
*
|
||||
* <p>
|
||||
* Use this method to turn slightly towards something.
|
||||
*
|
||||
* <p>
|
||||
* 0° is due right, 90° is up.
|
||||
*
|
||||
* @param degrees
|
||||
* Bearing, in degrees
|
||||
* @param percent
|
||||
* How far to turn, in degrees.
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle turnTowards(double degrees, double percent);
|
||||
|
||||
<T> T as(Class<T> class1);
|
||||
|
||||
}
|
94
src/inf101/v18/gfx/gfxmode/Point.java
Normal file
94
src/inf101/v18/gfx/gfxmode/Point.java
Normal file
@@ -0,0 +1,94 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
public class Point {
|
||||
private final double x;
|
||||
private final double y;
|
||||
|
||||
public Point(double x, double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate direction towards other position
|
||||
*
|
||||
* @param otherPos
|
||||
* @return
|
||||
*/
|
||||
public Direction directionTo(Point otherPos) {
|
||||
return new Direction(otherPos.x - x, otherPos.y - y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate distance to other position
|
||||
*
|
||||
* @param otherPos
|
||||
* @return
|
||||
*/
|
||||
public double distanceTo(Point otherPos) {
|
||||
return Math.sqrt(Math.pow(x - otherPos.x, 2) + Math.pow(y - otherPos.y, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The X coordinate
|
||||
*/
|
||||
public double getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The Y coordinate
|
||||
*/
|
||||
public double getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relative move
|
||||
*
|
||||
* @param dir
|
||||
* Direction
|
||||
* @param distance
|
||||
* Distance to move
|
||||
*/
|
||||
public Point move(Direction dir, double distance) {
|
||||
return new Point(x + dir.getX() * distance, y - dir.getY() * distance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relative move
|
||||
*
|
||||
* @param deltaX
|
||||
* @param deltaY
|
||||
* @return A new point at x+deltaX, y+deltaY
|
||||
*/
|
||||
public Point move(double deltaX, double deltaY) {
|
||||
return new Point(x + deltaX, y + deltaY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Relative move
|
||||
*
|
||||
* @param deltaPos
|
||||
*/
|
||||
public Point move(Point deltaPos) {
|
||||
return new Point(x + deltaPos.x, y + deltaPos.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change position
|
||||
*
|
||||
* @param newX
|
||||
* the new X coordinate
|
||||
* @param newY
|
||||
* the new Y coordinate
|
||||
* @return A new point at newX, newY
|
||||
*/
|
||||
public Point moveTo(double newX, double newY) {
|
||||
return new Point(newX, newY);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("(%.2f,%.2f)", x, y);
|
||||
}
|
||||
}
|
301
src/inf101/v18/gfx/gfxmode/ShapePainter.java
Normal file
301
src/inf101/v18/gfx/gfxmode/ShapePainter.java
Normal file
@@ -0,0 +1,301 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Shape;
|
||||
|
||||
public class ShapePainter implements IShape {
|
||||
private double x = 0, y = 0, w = 0, h = 0, rot = 0, strokeWidth = 0;
|
||||
private List<Double> lineSegments = null;
|
||||
private Paint fill = null;
|
||||
private Paint stroke = null;
|
||||
private Gravity gravity = Gravity.CENTER;
|
||||
private DrawCommand cmd = null;
|
||||
private boolean closed = false;
|
||||
private final GraphicsContext context;
|
||||
|
||||
public ShapePainter(GraphicsContext context) {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ShapePainter at(Point p) {
|
||||
if (p != null) {
|
||||
this.x = p.getX();
|
||||
this.y = p.getY();
|
||||
} else {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter x(double x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter y(double y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter width(double w) {
|
||||
this.w = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter height(double h) {
|
||||
this.h = h;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape ellipse() {
|
||||
cmd = new DrawEllipse();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape rectangle() {
|
||||
cmd = new DrawRectangle();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape arc() {
|
||||
// TODO Auto-generated method stub
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape line() {
|
||||
cmd = new DrawLine();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter length(double l) {
|
||||
w = l;
|
||||
h = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter angle(double a) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter fill() {
|
||||
if (cmd != null)
|
||||
cmd.fill(context, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter stroke() {
|
||||
if (cmd != null)
|
||||
cmd.stroke(context, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter fillPaint(Paint p) {
|
||||
fill = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter strokePaint(Paint p) {
|
||||
stroke = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter gravity(Gravity g) {
|
||||
gravity = g;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter rotation(double angle) {
|
||||
rot = angle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
draw(context);
|
||||
}
|
||||
|
||||
public Shape toFXShape() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toSvg() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
private abstract static class DrawCommand {
|
||||
public void stroke(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.save();
|
||||
ctx.setStroke(p.stroke);
|
||||
if (p.strokeWidth != 0)
|
||||
ctx.setLineWidth(p.strokeWidth);
|
||||
ctx.translate(p.x, p.y);
|
||||
if (p.rot != 0)
|
||||
ctx.rotate(-p.rot);
|
||||
strokeIt(ctx, p);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
public void fill(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.save();
|
||||
ctx.setFill(p.fill);
|
||||
ctx.translate(p.x, p.y);
|
||||
if (p.rot != 0)
|
||||
ctx.rotate(-p.rot);
|
||||
fillIt(ctx, p);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
protected abstract void strokeIt(GraphicsContext ctx, ShapePainter p);
|
||||
|
||||
protected abstract void fillIt(GraphicsContext ctx, ShapePainter p);
|
||||
|
||||
// public abstract Shape toFXShape(DrawParams p);
|
||||
//
|
||||
// public abstract String toSvg(DrawParams p);
|
||||
|
||||
protected double calcX(Gravity g, double w) {
|
||||
switch (g) {
|
||||
default:
|
||||
case CENTER:
|
||||
return w / 2;
|
||||
case EAST:
|
||||
return w;
|
||||
case NORTH:
|
||||
return w / 2;
|
||||
case NORTHEAST:
|
||||
return w;
|
||||
case NORTHWEST:
|
||||
return 0;
|
||||
case SOUTH:
|
||||
return w / 2;
|
||||
case SOUTHEAST:
|
||||
return w;
|
||||
case SOUTHWEST:
|
||||
return 0;
|
||||
case WEST:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected double calcY(Gravity g, double h) {
|
||||
switch (g) {
|
||||
default:
|
||||
case CENTER:
|
||||
return h / 2;
|
||||
case EAST:
|
||||
return h / 2;
|
||||
case NORTH:
|
||||
return 0;
|
||||
case NORTHEAST:
|
||||
return 0;
|
||||
case NORTHWEST:
|
||||
return 0;
|
||||
case SOUTH:
|
||||
return h;
|
||||
case SOUTHEAST:
|
||||
return h;
|
||||
case SOUTHWEST:
|
||||
return h;
|
||||
case WEST:
|
||||
return h / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawRectangle extends DrawCommand {
|
||||
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.strokeRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.fillRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawEllipse extends DrawCommand {
|
||||
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.strokeOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.fillOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawLine extends DrawCommand {
|
||||
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
if (p.lineSegments == null) {
|
||||
double x = -calcX(p.gravity, p.w);
|
||||
double y = -calcY(p.gravity, p.h);
|
||||
ctx.strokeLine(x, y, x + p.w, y + p.h);
|
||||
} else {
|
||||
int nPoints = (p.lineSegments.size() / 2) + 1;
|
||||
double xs[] = new double[nPoints];
|
||||
double ys[] = new double[nPoints];
|
||||
xs[0] = -calcX(p.gravity, p.w);
|
||||
ys[0] = -calcY(p.gravity, p.h);
|
||||
for (int i = 0; i < p.lineSegments.size(); i++) {
|
||||
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
||||
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
||||
}
|
||||
if (p.closed)
|
||||
ctx.strokePolygon(xs, ys, nPoints);
|
||||
else
|
||||
ctx.strokePolyline(xs, ys, nPoints);
|
||||
}
|
||||
}
|
||||
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
if (p.lineSegments != null) {
|
||||
int nPoints = (p.lineSegments.size() / 2) + 1;
|
||||
double xs[] = new double[nPoints];
|
||||
double ys[] = new double[nPoints];
|
||||
xs[0] = -calcX(p.gravity, p.w);
|
||||
ys[0] = -calcY(p.gravity, p.h);
|
||||
for (int i = 0; i < p.lineSegments.size(); i++) {
|
||||
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
||||
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
||||
}
|
||||
ctx.fillPolygon(xs, ys, nPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(GraphicsContext context) {
|
||||
if (cmd != null) {
|
||||
if (fill != null)
|
||||
cmd.fill(context, this);
|
||||
if (stroke != null)
|
||||
cmd.stroke(context, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape addPoint(Point xy) {
|
||||
lineSegments.add(xy.getX());
|
||||
lineSegments.add(xy.getY());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape addPoint(double x, double y) {
|
||||
lineSegments.add(x);
|
||||
lineSegments.add(y);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape close() {
|
||||
closed = true;
|
||||
return this;
|
||||
}
|
||||
}
|
273
src/inf101/v18/gfx/gfxmode/TurtlePainter.java
Normal file
273
src/inf101/v18/gfx/gfxmode/TurtlePainter.java
Normal file
@@ -0,0 +1,273 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import inf101.v18.gfx.IPaintLayer;
|
||||
import inf101.v18.gfx.Screen;
|
||||
import javafx.scene.canvas.Canvas;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.Paint;
|
||||
|
||||
public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
|
||||
static class TurtleState {
|
||||
protected Point pos;
|
||||
protected Direction dir;
|
||||
protected Direction inDir;
|
||||
protected double penSize = 1.0;
|
||||
protected Paint ink = Color.BLACK;
|
||||
|
||||
public TurtleState() {
|
||||
}
|
||||
|
||||
public TurtleState(TurtleState s) {
|
||||
pos = s.pos;
|
||||
dir = s.dir;
|
||||
inDir = s.inDir;
|
||||
penSize = s.penSize;
|
||||
ink = s.ink;
|
||||
}
|
||||
}
|
||||
|
||||
private final Screen screen;
|
||||
private final GraphicsContext context;
|
||||
private final List<TurtleState> stateStack = new ArrayList<>();
|
||||
|
||||
private TurtleState state = new TurtleState();
|
||||
private final Canvas canvas;
|
||||
private boolean path = false;
|
||||
|
||||
public TurtlePainter(Screen screen, Canvas canvas) {
|
||||
this.screen = screen;
|
||||
this.canvas = canvas;
|
||||
this.context = canvas.getGraphicsContext2D();
|
||||
stateStack.add(new TurtleState());
|
||||
state.dir = new Direction(1.0, 0.0);
|
||||
state.pos = new Point(screen.getWidth() / 2, screen.getHeight() / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
context.clearRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugTurtle() {
|
||||
System.err.println("[" + state.pos + " " + state.dir + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape shape() {
|
||||
ShapePainter s = new ShapePainter(context);
|
||||
return s.at(getPos()).rotation(getAngle()).strokePaint(state.ink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle line(Point to) {
|
||||
// context.save();
|
||||
context.setStroke(state.ink);
|
||||
context.setLineWidth(state.penSize);
|
||||
context.strokeLine(state.pos.getX(), state.pos.getY(), to.getX(), to.getY());
|
||||
// context.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAngle() {
|
||||
return state.dir.toDegrees();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getDirection() {
|
||||
return state.dir;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return screen.getHeight();
|
||||
}
|
||||
|
||||
public Screen getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPos() {
|
||||
return state.pos;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return screen.getWidth();
|
||||
}
|
||||
|
||||
public ITurtle curveTo(Point to, double startControl, double endAngle, double endControl) {
|
||||
Point c1 = state.pos.move(state.dir, startControl);
|
||||
Point c2 = to.move(Direction.fromDegrees(endAngle + 180), endControl);
|
||||
if (!path) {
|
||||
// context.save();
|
||||
context.setStroke(state.ink);
|
||||
context.setLineWidth(state.penSize);
|
||||
context.beginPath();
|
||||
context.moveTo(state.pos.getX(), state.pos.getY());
|
||||
}
|
||||
context.bezierCurveTo(c1.getX(), c1.getY(), c2.getX(), c2.getY(), to.getX(), to.getY());
|
||||
state.inDir = state.dir;
|
||||
state.pos = to;
|
||||
state.dir = Direction.fromDegrees(endAngle);
|
||||
|
||||
if (!path) {
|
||||
context.stroke();
|
||||
// context.restore();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle jump(double dist) {
|
||||
state.inDir = state.dir;
|
||||
state.pos = state.pos.move(state.dir, dist);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle jumpTo(double x, double y) {
|
||||
state.inDir = state.dir;
|
||||
state.pos = new Point(x, y);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle jumpTo(Point to) {
|
||||
state.inDir = state.dir;
|
||||
state.pos = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle draw(double dist) {
|
||||
Point to = state.pos.move(state.dir, dist);
|
||||
return drawTo(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle drawTo(double x, double y) {
|
||||
Point to = new Point(x, y);
|
||||
return drawTo(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle drawTo(Point to) {
|
||||
if (path) {
|
||||
context.lineTo(to.getX(), to.getY());
|
||||
} else {
|
||||
line(to);
|
||||
}
|
||||
state.inDir = state.dir;
|
||||
state.pos = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPainter restore() {
|
||||
if (stateStack.size() > 0) {
|
||||
state = stateStack.remove(stateStack.size() - 1);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPainter save() {
|
||||
stateStack.add(new TurtleState(state));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPainter setInk(Paint ink) {
|
||||
state.ink = ink;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle setPenSize(double pixels) {
|
||||
if (pixels < 0)
|
||||
throw new IllegalArgumentException("Negative: " + pixels);
|
||||
state.penSize = pixels;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turn(double degrees) {
|
||||
state.dir = state.dir.turn(degrees);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnAround() {
|
||||
return turn(180);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnLeft() {
|
||||
return turn(90);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnLeft(double degrees) {
|
||||
if (degrees < 0)
|
||||
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
|
||||
state.dir = state.dir.turn(degrees);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnRight() {
|
||||
return turn(-90);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnRight(double degrees) {
|
||||
if (degrees < 0)
|
||||
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
|
||||
state.dir = state.dir.turn(-degrees);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnTo(double degrees) {
|
||||
state.dir = state.dir.turnTo(degrees);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turnTowards(double degrees, double percent) {
|
||||
state.dir = state.dir.turnTowards(new Direction(degrees), percent);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToFront() {
|
||||
screen.moveToFront(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToBack() {
|
||||
screen.moveToBack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turtle() {
|
||||
TurtlePainter painter = new TurtlePainter(screen, canvas);
|
||||
painter.stateStack.set(0, new TurtleState(state));
|
||||
return painter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T as(Class<T> clazz) {
|
||||
if (clazz == GraphicsContext.class)
|
||||
return (T) context;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
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