initial import

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

View File

@@ -0,0 +1,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();
}

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

Binary file not shown.

Binary file not shown.

View 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

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

Binary file not shown.

Binary file not shown.

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

View File

@@ -0,0 +1,5 @@
package inf101.v18.gfx.gfxmode;
public enum Gravity {
NORTH, NORTHWEST, WEST, SOUTHWEST, SOUTH, SOUTHEAST, EAST, NORTHEAST, CENTER
}

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

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

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

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

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

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

View File

@@ -0,0 +1,200 @@
package inf101.v18.gfx.textmode;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.BiFunction;
public class BlocksAndBoxes {
public static final String[] unicodeBlocks = { " ", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "" };
public static final int[] unicodeBlocks_NumPixels = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 2 };
public static final String unicodeBlocksString = String.join("", unicodeBlocks);
public static final String BLOCK_EMPTY = " ";
public static final String BLOCK_BOTTOM_RIGHT = "";
public static final String BLOCK_BOTTOM_LEFT = "";
public static final String BLOCK_BOTTOM = "";
public static final String BLOCK_TOP_RIGHT = "";
public static final String BLOCK_RIGHT = "";
public static final String BLOCK_DIAG_FORWARD = "";
public static final String BLOCK_REVERSE_TOP_LEFT = "";
public static final String BLOCK_TOP_LEFT = "";
public static final String BLOCK_DIAG_BACKWARD = "";
public static final String BLOCK_LEFT = "";
public static final String BLOCK_REVERSE_TOP_RIGHT = "";
public static final String BLOCK_TOP = "";
public static final String BLOCK_REVERSE_BOTTOM_LEFT = "";
public static final String BLOCK_REVERSE_BOTTOM_RIGHT = "";
public static final String BLOCK_FULL = "";
public static final String BLOCK_HALF = "";
/**
* Convert a string into a Unicode block graphics character.
*
* <p>
* The block characters corresponds to 2x2-pixel images, and can be used, e.g.,
* to draw a 80x44 pixel image on a 40x22 character text screen.
*
* <p>
* The blocks are specified by four-character strings of spaces and asterisks,
* with space indicating an open space and asterisk indicating a filled "pixel",
* with the pixels arranged in a left-to-right, top-to-bottom order:
*
* <pre>
* 01
* 23
* </pre>
*
* <p>
* So <code>"* **"</code> corresponds to the block character <code>"▚"</code>,
* with this layout:
*
* <pre>
* *
* *
* </pre>
*
* <p>
* The special codes <code>"++++"</code> and <code>"+"</code> corresponds to the
* "grey" block <code>"▒"</code>, and <code>"*"</code> corresponds to the
* "black" block <code>"█"</code>.
*
* @param s
* A four character string, indicating which block character to
* select.
* @return A Unicode block character
* @throws IllegalArgumentException
* if the string isn't of the expected form
*/
public static String blockChar(String s) {
switch (s.replaceAll("\n", s)) {
case " ":
return unicodeBlocks[0];
case " *":
return unicodeBlocks[1];
case " * ":
return unicodeBlocks[2];
case " **":
return unicodeBlocks[3];
case " * ":
return unicodeBlocks[4];
case " * *":
return unicodeBlocks[5];
case " ** ":
return unicodeBlocks[6];
case " ***":
return unicodeBlocks[7];
case "* ":
return unicodeBlocks[8];
case "* *":
return unicodeBlocks[9];
case "* * ":
return unicodeBlocks[10];
case "* **":
return unicodeBlocks[11];
case "** ":
return unicodeBlocks[12];
case "** *":
return unicodeBlocks[13];
case "*** ":
return unicodeBlocks[14];
case "****":
return unicodeBlocks[15];
case "++++":
return unicodeBlocks[16];
case ".":
return BLOCK_BOTTOM_LEFT;
case "_":
return BLOCK_BOTTOM;
case "/":
return BLOCK_DIAG_FORWARD;
case "\\":
return BLOCK_DIAG_BACKWARD;
case "|":
return BLOCK_LEFT;
case "#":
return BLOCK_FULL;
case "`":
return BLOCK_TOP_LEFT;
case "'":
return BLOCK_TOP_RIGHT;
}
throw new IllegalArgumentException(
"Expected length 4 string of \" \" and \"*\", or \"++++\", got \"" + s + "\"");
};
public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
int i1 = unicodeBlocksString.indexOf(b1);
if (i1 < 0)
throw new IllegalArgumentException("Not a block character: " + b1);
int i2 = unicodeBlocksString.indexOf(b2);
if (i2 < 0)
throw new IllegalArgumentException("Not a block character: " + b1);
if (i1 == 16 || i2 == 16)
return b2;
else
return unicodeBlocks[op.apply(i1, i2)];
}
public static String blockComposeOrOverwrite(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
int i1 = unicodeBlocksString.indexOf(b1);
int i2 = unicodeBlocksString.indexOf(b2);
if (i1 < 0 || i2 < 0 || i1 == 16 || i2 == 16)
return b2;
else
return unicodeBlocks[op.apply(i1, i2)];
}
public enum PixelOrder implements Iterable<Integer> {
LEFT_TO_RIGHT(8, 4, 2, 1), RIGHT_TO_LEFT(4, 8, 1, 2), LEFT_TO_RIGHT_UPWARDS(2, 1, 8,
4), RIGHT_TO_LEFT_UPWARDS(1, 2, 4, 8);
private List<Integer> order;
private PixelOrder(int a, int b, int c, int d) {
order = Arrays.asList(a, b, c, d);
}
@Override
public Iterator<Integer> iterator() {
return order.iterator();
}
}
public static String blockAddOne(String s, PixelOrder order) {
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
if (i >= 0) {
for (int bit : order) {
if ((i & bit) == 0)
return unicodeBlocks[i | bit];
}
}
return s;
}
public static String blockRemoveOne(String s, PixelOrder order) {
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
if (i >= 0) {
for (int bit : order) {
if ((i & bit) != 0)
return unicodeBlocks[i & ~bit];
}
}
return s;
}
public static String blockCompact(String s) {
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
if (i > 0) {
int lower = i & 3;
int upper = (i >> 2) & 3;
i = (lower | upper) | ((lower & upper) << 2);
// System.out.println("Compact: " + s + " -> " + BlocksAndBoxes.unicodeBlocks[i]
// + "\n");
return BlocksAndBoxes.unicodeBlocks[i];
}
return s;
}
}

View File

@@ -0,0 +1,271 @@
package inf101.v18.gfx.textmode;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.scene.paint.Color;
public class ControlSequences {
public static class CsiPattern {
public static CsiPattern compile0(String pat, String desc, Consumer<Printer> handler) {
CsiPattern csiPattern = new CsiPattern(pat, 0, 0, desc, handler, null, null);
patterns.put(csiPattern.getCommandLetter(), csiPattern);
return csiPattern;
}
public static CsiPattern compile1(String pat, int defaultArg, String desc,
BiConsumer<Printer, Integer> handler) {
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, 1, desc, null, handler, null);
patterns.put(csiPattern.getCommandLetter(), csiPattern);
return csiPattern;
}
public static CsiPattern compileN(String pat, int defaultArg, int numArgs, String desc,
BiConsumer<Printer, List<Integer>> handler) {
CsiPattern csiPattern = new CsiPattern(pat, defaultArg, numArgs, desc, null, null, handler);
patterns.put(csiPattern.getCommandLetter(), csiPattern);
return csiPattern;
}
private String patStr;
private Pattern pattern;
private int defaultArg = 0;
private String desc;
private Consumer<Printer> handler0;
private BiConsumer<Printer, Integer> handler1;
private BiConsumer<Printer, List<Integer>> handlerN;
private int numArgs;
public CsiPattern(String pat, int defaultArg, int numArgs, String desc, Consumer<Printer> handler0,
BiConsumer<Printer, Integer> handler1, BiConsumer<Printer, List<Integer>> handlerN) {
this.patStr = pat;
this.pattern = Pattern.compile(pat);
this.defaultArg = defaultArg;
this.numArgs = numArgs;
this.desc = desc;
this.handler0 = handler0;
this.handler1 = handler1;
this.handlerN = handlerN;
}
public String getCommandLetter() {
return patStr.substring(patStr.length() - 1);
}
public String getDescription() {
return desc;
}
public boolean match(Printer printer, String input) {
Matcher matcher = pattern.matcher(input);
if (matcher.matches()) {
String argStr = matcher.groupCount() > 0 ? matcher.group(1) : "";
String[] args = argStr.split(";");
if (handler0 != null) {
System.out.println("Handling " + getDescription() + ".");
handler0.accept(printer);
} else if (handler1 != null) {
int arg = args.length > 0 && !args[0].equals("") ? Integer.valueOf(args[0]) : defaultArg;
System.out.println("Handling " + getDescription() + ": " + arg);
handler1.accept(printer, arg);
} else if (handlerN != null) {
List<Integer> argList = new ArrayList<>();
for (String s : args) {
if (s.equals(""))
argList.add(defaultArg);
else
argList.add(Integer.valueOf(s));
}
while (argList.size() < numArgs) {
argList.add(defaultArg);
}
System.out.println("Handling " + getDescription() + ": " + argList);
handlerN.accept(printer, argList);
}
return true;
}
return false;
}
}
public static final Map<String, CsiPattern> patterns = new HashMap<>();
public static final CsiPattern CUU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)A", 1, "cursor up",
(Printer p, Integer i) -> {
p.move(0, -i);
});
public static final CsiPattern CUD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)B", 1, "cursor down",
(Printer p, Integer i) -> {
p.move(0, i);
});
public static final CsiPattern CUF = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)C", 1, "cursor forward",
(Printer p, Integer i) -> {
p.move(i, 0);
});
public static final CsiPattern CUB = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)D", 1, "cursor back",
(Printer p, Integer i) -> {
p.move(-i, 0);
});
public static final CsiPattern CNL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)E", 1, "cursor next line",
(Printer p, Integer i) -> {
p.move(0, i);
p.beginningOfLine();
});
public static final CsiPattern CPL = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)F", 1, "cursor previous line",
(Printer p, Integer i) -> {
p.move(0, -i);
p.beginningOfLine();
});
public static final CsiPattern CHA = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)G", 1,
"cursor horizontal absolute", (Printer p, Integer i) -> {
p.moveTo(i, p.getY());
});
public static final CsiPattern CUP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)H", 1, 2, "cursor position",
(Printer p, List<Integer> i) -> {
p.moveTo(i.get(1), i.get(0));
});
public static final CsiPattern ED = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)J", 0, "erase in display",
(Printer p, Integer i) -> {
if (i == 2 || i == 3)
p.clear();
else
System.err.println("Unimplemented: ED");
});
public static final CsiPattern EK = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)K", 0, "erase in line",
(Printer p, Integer i) -> {
System.err.println("Unimplemented: EK");
});
public static final CsiPattern SU = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)S", 1, "scroll up",
(Printer p, Integer i) -> {
p.scroll(i);
});
public static final CsiPattern SD = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)T", 1, "scroll down",
(Printer p, Integer i) -> {
p.scroll(-i);
});
public static final CsiPattern HVP = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)f", 1, 2,
"horizontal vertical position", (Printer p, List<Integer> l) -> {
p.moveTo(l.get(1), l.get(0));
});
public static final CsiPattern AUX_ON = CsiPattern.compile0("\u001b\\\u005b5i", "aux port on", (Printer p) -> {
System.err.println("Unimplemented: AUX on");
});
public static final CsiPattern AUX_OFF = CsiPattern.compile0("\u001b\\\u005b4i", "aux port off", (Printer p) -> {
System.err.println("Unimplemented: AUX off");
});
public static final CsiPattern DSR = CsiPattern.compile0("\u001b\\\u005b6n", "device status report",
(Printer p) -> {
System.out.println("ESC[" + p.getY() + ";" + p.getX() + "R");
});
public static final CsiPattern SCP = CsiPattern.compile0("\u001b\\\u005bs", "save cursor position", (Printer p) -> {
p.saveCursor();
});
public static final CsiPattern RCP = CsiPattern.compile0("\u001b\\\u005bu", "restore cursor position",
(Printer p) -> {
p.restoreCursor();
});
public static final int F = 0xFF, H = 0xAA, L = 0x55, OFF = 0x00;
public static final Color[] PALETTE_CGA = { //
Color.rgb(0, 0, 0), Color.rgb(0, 0, H), Color.rgb(0, H, 0), Color.rgb(0, H, H), //
Color.rgb(H, 0, 0), Color.rgb(H, 0, H), Color.rgb(H, L, 0), Color.rgb(H, H, H), //
Color.rgb(L, L, L), Color.rgb(L, L, F), Color.rgb(L, F, L), Color.rgb(L, F, F), //
Color.rgb(F, L, L), Color.rgb(F, L, F), Color.rgb(F, F, L), Color.rgb(F, F, F), };
public static final Color[] PALETTE_VGA = { //
Color.rgb(0, 0, 0), Color.rgb(H, 0, 0), Color.rgb(0, H, 0), Color.rgb(H, H, 0), //
Color.rgb(0, 0, H), Color.rgb(H, 0, H), Color.rgb(0, H, H), Color.rgb(H, H, H), //
Color.rgb(L, L, L), Color.rgb(F, L, L), Color.rgb(L, F, L), Color.rgb(F, F, L), //
Color.rgb(L, L, F), Color.rgb(F, L, F), Color.rgb(L, F, F), Color.rgb(F, F, F), };
public static final CsiPattern SGR = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)m", 0, -1,
"select graphics rendition", (Printer p, List<Integer> l) -> {
if (l.size() == 0) {
l.add(0);
}
int[] attrs = { 0, TextFont.ATTR_BRIGHT, TextFont.ATTR_FAINT, TextFont.ATTR_ITALIC,
TextFont.ATTR_UNDERLINE, TextFont.ATTR_BLINK, TextFont.ATTR_BLINK, TextFont.ATTR_INVERSE, 0,
TextFont.ATTR_LINE_THROUGH };
Iterator<Integer> it = l.iterator();
while (it.hasNext()) {
int i = it.next();
if (i == 0) {
p.setVideoAttrs(0);
p.setInk(PALETTE_VGA[7]);
p.setBackground(PALETTE_VGA[0]);
} else if (i < 10) {
p.setVideoAttrEnabled(attrs[i]);
} else if (i >= 20 && i < 30) {
p.setVideoAttrDisabled(attrs[i] - 20);
} else if (i >= 30 && i < 38) {
p.setInk(PALETTE_VGA[i - 30]);
} else if (i == 38) {
p.setInk(decode256(it));
} else if (i == 29) {
p.setInk(Color.WHITE);
} else if (i >= 40 && i < 48) {
p.setBackground(PALETTE_VGA[i - 40]);
} else if (i == 48) {
p.setInk(decode256(it));
} else if (i == 49) {
p.setBackground(Color.BLACK);
} else if (i >= 90 && i < 98) {
p.setInk(PALETTE_VGA[8 + i - 90]);
} else if (i >= 100 && i < 108) {
p.setBackground(PALETTE_VGA[8 + i - 100]);
} else if (i == 53) {
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
} else if (i == 55) {
p.setVideoAttrEnabled(TextFont.ATTR_OVERLINE);
}
}
});
public static boolean applyCsi(Printer printer, String csi) {
CsiPattern csiPattern = patterns.get(csi.substring(csi.length() - 1));
// System.out.println("Applying CSI: " + csi.replaceAll("\u001b", "ESC"));
if (csiPattern != null) {
if (csiPattern.match(printer, csi))
return true;
else
System.err.println("Handler failed for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
} else {
System.err.println("No handler for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
}
return false;
}
private static Color decode256(Iterator<Integer> it) {
int i;
try {
i = it.next();
if (i == 5) {
i = it.next();
if (i < 16)
return PALETTE_VGA[i];
else if (i < 232)
return Color.rgb(i / 36, (i / 6) % 6, i % 6);
else
return Color.gray((i - 232) / 23.0);
} else if (i == 2) {
int r = it.next();
int g = it.next();
int b = it.next();
return Color.rgb(r, g, b);
}
} catch (NoSuchElementException e) {
}
return null;
}
}

View File

@@ -0,0 +1,154 @@
package inf101.v18.gfx.textmode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javafx.scene.paint.Color;
public class DemoPages {
public static void printAnsiArt(Printer printer) {
printer.moveTo(1, 1);
printer.setAutoScroll(false);
printer.clear();
try (InputStream stream = DemoPages.class.getResourceAsStream("flower.txt")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"));
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
printer.println(s);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void printBlockPlotting(Printer printer) {
printer.clear();
printer.setAutoScroll(false);
printer.setVideoAttrs(0);
int topLine = 8;
for (int x = 0; x < 16; x += 1) {
if ((x & 1) > 0)
printer.plot(4 * x + 1, 1 + (topLine - 1) * 2);
if ((x & 2) > 0)
printer.plot(4 * x, 1 + (topLine - 1) * 2);
if ((x & 4) > 0)
printer.plot(4 * x + 1, (topLine - 1) * 2);
if ((x & 8) > 0)
printer.plot(4 * x, (topLine - 1) * 2);
printer.printAt(1 + 2 * x, topLine + 2, BlocksAndBoxes.unicodeBlocks[x]);
printer.printAt(1 + 2 * x, topLine + 4, BlocksAndBoxes.unicodeBlocks[15]);
printer.printAt(1 + 2 * x, topLine + 6, BlocksAndBoxes.unicodeBlocks[~x & +0xf]);
printer.printAt(1 + 2 * x, topLine + 7, String.format("%X", x));
if ((x & 1) > 0)
printer.unplot(4 * x + 1, 1 + (4 + topLine - 1) * 2);
if ((x & 2) > 0)
printer.unplot(4 * x, 1 + (4 + topLine - 1) * 2);
if ((x & 4) > 0)
printer.unplot(4 * x + 1, (4 + topLine - 1) * 2);
if ((x & 8) > 0)
printer.unplot(4 * x, (4 + topLine - 1) * 2);
}
printer.printAt(1, 1,
"Plotting with Unicode Block Elements\n(ZX81-like Graphics)\n\nThe plot/print and unplot/inverse\nlines should be equal:");
printer.printAt(33, topLine, "plot");
printer.printAt(33, topLine + 2, "print");
printer.printAt(33, topLine + 4, "unplot");
printer.printAt(33, topLine + 6, "inverse");
printer.printAt(0, topLine + 9, String.format("Full blocks:\n Clear[%s] Shaded[%s] Opaque[%s]",
BlocksAndBoxes.unicodeBlocks[0], BlocksAndBoxes.unicodeBlocks[16], BlocksAndBoxes.unicodeBlocks[15]));
printer.printAt(41, topLine + 9, "(ZX81 inverted shade and half block");
printer.printAt(41, topLine + 10, "shades are missing in Unicode and");
printer.printAt(41, topLine + 11, "therefore not supported)");
printer.println();
}
public static void printBoxDrawing(Printer printer) {
printer.clear();
printer.setAutoScroll(false);
printer.println(" Latin-1 Boxes & Blocks");
printer.println(" U+0000..00FF U+2500..257F..259F");
printer.println(" ");
printer.println(" 0123456789ABCDEF 0123456789ABCDEF");
for (int y = 0; y < 16; y++) {
printer.print(String.format(" %X", y));
int c = 0x00 + y * 0x010;
for (int x = 0; x < 16; x++) {
printer.print(c >= 0x20 ? Character.toString((char) (c + x)) : " ");
}
printer.print(" ");
if (y < 10) {
printer.print(String.format("%X", y));
c = 0x2500 + y * 0x010;
for (int x = 0; x < 16; x++) {
printer.print(Character.toString((char) (c + x)));
}
}
printer.println();
}
}
public static void printVideoAttributes(Printer printer) {
printer.clear();
printer.setAutoScroll(false);
printer.setVideoAttrs(0);
printer.setInk(Color.BLACK);
printer.setStroke(Color.WHITE);
String demoLine = "Lorem=ipsum-dolor$sit.ametÆØÅå*,|▞&Jumps Over\\the?fLat Dog{}()#\"!";
printer.println("RIBU|" + demoLine);
for (int i = 1; i < 16; i++) {
printer.setVideoAttrs(i);
String s = (i & 1) != 0 ? "X" : " ";
s += (i & 2) != 0 ? "X" : " ";
s += (i & 4) != 0 ? "X" : " ";
s += (i & 8) != 0 ? "X" : " ";
printer.println(s + "|" + demoLine);
}
printer.setVideoAttrs(0);
printer.println();
printer.println("Lines: under, through, over");
printer.setVideoAttrs(TextFont.ATTR_UNDERLINE);
printer.println(" " + demoLine + " ");
printer.setVideoAttrs(TextFont.ATTR_LINE_THROUGH);
printer.println(" " + demoLine + " ");
printer.setVideoAttrs(TextFont.ATTR_OVERLINE);
printer.println(" " + demoLine + " ");
printer.setVideoAttrs(0);
}
public static void printZX(Printer printer) {
printer.moveTo(1, 1);
printer.setAutoScroll(false);
printer.clear();
printer.println(" ▄▄▄ ▄ ▄ ▄");
printer.println(" █ █ █ █ █ █ █");
printer.println(" █ █ █ █ █ █ █");
printer.println(" █ █ █ █ █ █ █");
printer.println(" ▀ ▀ ▀ ▀ ▀▀▀");
printer.println(" ▄▄▄ ▄▄");
printer.println(" █ █");
printer.println(" █ █");
printer.println(" █ █");
printer.println(" ▀▀▀ ▀▀");
printer.println(" ▄▄ ▄ ▄ ▄");
printer.println(" █ █ █ █ █ █");
printer.println(" █ █ █ █ █ █");
printer.println(" █ █ █ █ █ █");
printer.println(" ▀▀ ▀ ▀ ▀▀▀");
printer.println("ON █████ █ █ ███ █");
printer.println("THE █ █ █ █ █ ██");
printer.println("SINCLAIR █ ███ █");
printer.println(" █ █ █ █ █ WITH");
printer.println(" █ █ █ █ █ █ 16K");
printer.println(" █████ █ █ ███ ███ RAM");
printer.moveTo(1, 1);
printer.setAutoScroll(true);
}
}

View File

@@ -0,0 +1,680 @@
package inf101.v18.gfx.textmode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import inf101.v18.gfx.IPaintLayer;
import inf101.v18.gfx.Screen;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.effect.BlendMode;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
public class Printer implements IPaintLayer {
private static class Char {
public int mode;
public String s;
public Color fill;
public Color stroke;
public Paint bg;
public Char(String s, Color fill, Color stroke, Paint bg, int mode) {
this.s = s;
this.fill = fill;
this.stroke = stroke;
this.bg = bg;
this.mode = mode;
}
}
public static final TextFont FONT_MONOSPACED = new TextFont("Monospaced", 27.00, TextMode.CHAR_BOX_SIZE, 3.4000,
-6.7000, 1.5000, 1.0000, true);
public static final TextFont FONT_LMMONO = new TextFont("lmmono10-regular.otf", 30.00, TextMode.CHAR_BOX_SIZE,
4.0000, -8.5000, 1.5000, 1.0000, true);
public static final TextFont FONT_ZXSPECTRUM7 = new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE,
3.1000, -3.8000, 1.0000, 1.0000, true);
/**
* TTF file can be found here:
* http://www.kreativekorp.com/software/fonts/c64.shtml
*/
public static final TextFont FONT_GIANA = new TextFont("Giana.ttf", 25.00, TextMode.CHAR_BOX_SIZE, 4.6000, -5.0000,
1.0000, 1.0000, true);
/**
* TTF file can be found here:
* http://www.kreativekorp.com/software/fonts/c64.shtml
*/
public static final TextFont FONT_C64 = new TextFont("PetMe64.ttf", 31.50, TextMode.CHAR_BOX_SIZE, 0.0000, -4.000,
1.0000, 1.0000, true);
Color DEFAULT_FILL = Color.BLACK;
Color DEFAULT_STROKE = Color.TRANSPARENT;
private static final Paint DEFAULT_BACKGROUND = Color.TRANSPARENT;
private static final TextMode DEFAULT_MODE = TextMode.MODE_40X22;
public static String center(String s, int width) {
for (; s.length() < width; s = " " + s + " ")
;
return s;
}
public static String repeat(String s, int width) {
String r = s;
for (; r.length() < width; r += s)
;
return r;
}
private TextMode textMode;
private Color fill;
private Color stroke;
private Paint background;
private Screen screen;
private List<Char[]> lineBuffer = new ArrayList<>();
private boolean autoscroll = true;
private final Canvas textPage;
private int x = 1, y = 1, savedX = 1, savedY = 1;
// private int pageWidth = LINE_WIDTHS[resMode], pageHeight =
// PAGE_HEIGHTS[resMode];
private int leftMargin = 1, topMargin = 1;
private TextFont font = FONT_LMMONO;
private int videoAttrs = 0;
private String csiSeq = null;
private boolean csiEnabled = true;
private int csiMode = 0;
public Printer(Screen screen, Canvas page) {
this.screen = screen;
this.textPage = page;
for (int i = 0; i < TextMode.PAGE_HEIGHT_MAX; i++) {
lineBuffer.add(new Char[TextMode.LINE_WIDTH_MAX]);
}
resetFull();
}
public void addToCharBuffer(String string) {
string.codePoints().mapToObj((int i) -> String.valueOf(Character.toChars(i))).forEach((String s) -> {
if (csiMode != 0) {
s = addToCsiBuffer(s);
}
switch (s) {
case "\r":
moveTo(leftMargin, y);
break;
case "\n":
moveTo(leftMargin, y + 1);
break;
case "\f":
GraphicsContext context = textPage.getGraphicsContext2D();
moveTo(leftMargin, topMargin);
for (Char[] line : lineBuffer)
Arrays.fill(line, null);
if (background != null && background != Color.TRANSPARENT) {
context.setFill(background);
context.fillRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
} else
context.clearRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
break;
case "\b":
moveHoriz(-1);
break;
case "\t":
moveTo((x + 8) % 8, y);
break;
case "\u001b":
if (csiEnabled) {
csiSeq = s;
csiMode = 1;
}
break;
default:
if (s.length() > 0 && s.codePointAt(0) >= 0x20) {
drawChar(x, y, setChar(x, y, s));
moveHoriz(1);
}
break;
}
});
}
private void drawChar(int x, int y, Char c) {
if (c != null) {
GraphicsContext context = textPage.getGraphicsContext2D();
context.setFill(c.fill);
context.setStroke(c.stroke);
font.drawTextAt(context, (x - 1) * getCharWidth(), y * getCharHeight(), c.s,
textMode.getCharWidth() / textMode.getCharBoxSize(), c.mode, c.bg);
}
}
private String addToCsiBuffer(String s) {
if (csiMode == 1) {
switch (s) {
case "[":
csiMode = 2;
csiSeq += s;
break;
case "c":
csiMode = 0;
resetFull();
break;
default:
csiReset();
return s;
}
} else if (csiMode == 2) {
int c = s.codePointAt(0);
if (c >= 0x30 && c <= 0x3f) {
csiSeq += s;
} else if (c >= 0x20 && c <= 0x2f) {
csiMode = 3;
csiSeq += s;
} else if (c >= 0x40 && c <= 0x7e) {
csiSeq += s;
csiFinish();
} else {
csiReset();
return s;
}
} else if (csiMode == 3) {
int c = s.codePointAt(0);
if (c >= 0x20 && c <= 0x2f) {
csiSeq += s;
} else if (c >= 0x40 && c <= 0x7e) {
csiSeq += s;
csiFinish();
} else {
csiReset();
return s;
}
}
return "";
}
public void beginningOfLine() {
x = leftMargin;
}
public void beginningOfPage() {
x = leftMargin;
y = topMargin;
}
public void clear() {
print("\f");
}
public void clearAt(int x, int y) {
printAt(x, y, " ");
}
private int constrainX(int x) {
return x; // Math.min(LINE_WIDTH_HIRES, Math.max(1, x));
}
public int constrainY(int y) {
return y; // Math.min(pageHeight, Math.max(1, y));
}
public int constrainYOrScroll(int y) {
if (autoscroll) {
if (y < 1) {
scroll(y - 1);
return 1;
} else if (y > getPageHeight()) {
scroll(y - getPageHeight());
return getPageHeight();
}
}
return y;// Math.min(pageHeight, Math.max(1, y));
}
private void csiFinish() {
ControlSequences.applyCsi(this, csiSeq);
csiReset();
}
private void csiReset() {
csiMode = 0;
csiSeq = null;
}
public void cycleMode(boolean adjustDisplayAspect) {
textMode = textMode.nextMode();
if (adjustDisplayAspect)
screen.setAspect(textMode.getAspect());
redrawTextPage();
}
public void drawCharCells() {
GraphicsContext context = screen.getBackgroundContext();
screen.clearBackground();
double w = getCharWidth();
double h = getCharHeight();
context.save();
context.setGlobalBlendMode(BlendMode.EXCLUSION);
context.setFill(Color.WHITE.deriveColor(0.0, 1.0, 1.0, 0.3));
for (int x = 0; x < getLineWidth(); x++) {
for (int y = 0; y < getPageHeight(); y++) {
if ((x + y) % 2 == 0)
context.fillRect(x * w, y * h, w, h);
}
}
context.restore();
}
public boolean getBold() {
return (videoAttrs & TextFont.ATTR_BRIGHT) != 0;
}
public String getChar(int x, int y) {
Char c = null;
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1];
}
if (c != null)
return c.s;
else
return " ";
}
public double getCharHeight() {
return textMode.getCharHeight();
}
public double getCharWidth() {
return textMode.getCharWidth();
}
public Color getColor(int x, int y) {
Char c = null;
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1];
}
if (c != null)
return c.fill;
else
return fill;
}
public Color getBackground(int x, int y) {
Char c = null;
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1];
}
Color bg = Color.TRANSPARENT;
if (c != null && c.bg instanceof Color)
bg = (Color) c.bg;
else if (background instanceof Color)
bg = (Color) background;
return bg;
}
public void setBackground(int x, int y, Paint bg) {
Char c = null;
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1];
}
if (c != null) {
c.bg = bg;
drawChar(x, y, c);
}
}
public void setColor(int x, int y, Color fill) {
Char c = null;
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1];
}
if (c != null) {
c.fill = fill;
drawChar(x, y, c);
}
}
public TextFont getFont() {
return font;
}
public boolean getItalics() {
return (videoAttrs & TextFont.ATTR_ITALIC) != 0;
}
/**
* @return the leftMargin
*/
public int getLeftMargin() {
return leftMargin;
}
public int getLineWidth() {
return textMode.getLineWidth();
}
public int getPageHeight() {
return textMode.getPageHeight();
}
public boolean getReverseVideo() {
return (videoAttrs & TextFont.ATTR_INVERSE) != 0;
}
/**
* @return the topMargin
*/
public int getTopMargin() {
return topMargin;
}
public int getVideoMode() {
return videoAttrs;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public boolean isFilled(int x, int y) {
return !getChar(x, y).equals(" ");
}
public void move(int deltaX, int deltaY) {
x = constrainX(x + deltaX);
y = constrainYOrScroll(y + deltaY);
}
public void moveHoriz(int dist) {
x = constrainX(x + dist);
}
public void moveTo(int newX, int newY) {
x = constrainX(newX);
y = constrainYOrScroll(newY);
}
public void moveVert(int dist) {
y = constrainYOrScroll(y + dist);
}
public void plot(int x, int y) {
plot(x, y, (a, b) -> a | b);
}
public void plot(int x, int y, BiFunction<Integer, Integer, Integer> op) {
int textX = (x) / 2 + 1;
int textY = (y) / 2 + 1;
int bitPos = (x + 1) % 2 + ((y + 1) % 2) * 2;
String blockChar = BlocksAndBoxes.unicodeBlocks[1 << bitPos];
// System.out.println(blockChar + ", " + bitPos + ", ("+ (x) + ", " + (y) + ")"+
// ", (" + (textX) + ", " + (textY) + ")");
String s = BlocksAndBoxes.blockComposeOrOverwrite(getChar(textX, textY), blockChar, op);
// System.out.println("Merge '" + getChar(textX, textY) + "' + '" + blockChar +
// "' = '" + s + "'");
printAt(textX, textY, s);
}
public void print(String s) {
addToCharBuffer(s);
}
public void print(String s, Color paint) {
Color tmp = fill;
fill = paint;
addToCharBuffer(s);
fill = tmp;
}
public void printAt(int atX, int atY, String s) {
moveTo(atX, atY);
print(s);
}
public void printAt(int atX, int atY, String s, Color ink) {
moveTo(atX, atY);
print(s, ink);
}
public void println() {
print("\n");
}
public void println(String s) {
print(s);
print("\n");
}
public void redrawTextPage() {
/*
* System.out.printf("redrawTextPage benchmark");
* System.out.printf(" %5s %5s %7s %4s %5s %5s %5s%n", "ms", "chars",
* "ms/char", "mode", "indir", "inv", "fake"); for (int m = -1; m < 8; m++) {
* long t0 = System.currentTimeMillis(); int n = 0;
*/
GraphicsContext context = textPage.getGraphicsContext2D();
if (background != null && background != Color.TRANSPARENT) {
context.setFill(background);
context.fillRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
} else
context.clearRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
for (int tmpY = 1; tmpY <= getPageHeight(); tmpY++) {
Char[] line = lineBuffer.get(tmpY - 1);
for (int tmpX = 1; tmpX <= getLineWidth(); tmpX++) {
Char c = line[tmpX - 1];
if (c != null) {
context.save();
context.setFill(c.fill);
context.setStroke(c.stroke);
font.drawTextAt(context, (tmpX - 1) * getCharWidth(), tmpY * getCharHeight(), c.s,
textMode.getCharWidth() / textMode.getCharBoxSize(), c.mode/* m */, c.bg);
context.restore();
// n++;
}
}
}
/*
* long t = System.currentTimeMillis() - t0; if (m >= 0)
* System.out.printf(" %5d %5d %7.4f %4d %5b %5b %5b%n", t, n, ((double) t) /
* n, m, (m & 3) != 0, (m & 1) != 0, (m & 4) != 0); } System.out.println();
*/
}
public void resetAttrs() {
this.fill = DEFAULT_FILL;
this.stroke = DEFAULT_STROKE;
this.background = DEFAULT_BACKGROUND;
this.videoAttrs = 0;
this.csiSeq = null;
this.csiMode = 0;
}
public void resetFull() {
resetAttrs();
beginningOfPage();
this.autoscroll = true;
this.textMode = DEFAULT_MODE;
redrawTextPage();
}
public void restoreCursor() {
x = savedX;
y = savedY;
}
public void saveCursor() {
savedX = x;
savedY = y;
}
void scroll(int i) {
while (i < 0) {
scrollDown();
i++;
}
while (i > 0) {
scrollUp();
i--;
}
}
public void scrollDown() {
Char[] remove = lineBuffer.remove(lineBuffer.size() - 1);
Arrays.fill(remove, null);
lineBuffer.add(0, remove);
redrawTextPage();
}
public void scrollUp() {
Char[] remove = lineBuffer.remove(0);
Arrays.fill(remove, null);
lineBuffer.add(remove);
redrawTextPage();
}
public boolean setAutoScroll(boolean autoScroll) {
boolean old = autoscroll;
autoscroll = autoScroll;
return old;
}
public void setBackground(Paint bgColor) {
this.background = bgColor != null ? bgColor : DEFAULT_BACKGROUND;
}
public void setBold(boolean enabled) {
if (enabled)
videoAttrs |= TextFont.ATTR_BRIGHT;
else
videoAttrs &= ~TextFont.ATTR_BRIGHT;
}
public Char setChar(int x, int y, String s) {
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
Char c = new Char(s, fill, stroke, background, videoAttrs);
lineBuffer.get(y - 1)[x - 1] = c;
return c;
}
return null;
}
public void setFill(Color fill) {
this.fill = fill != null ? fill : DEFAULT_FILL;
}
public void setFont(TextFont font) {
this.font = font;
}
public void setInk(Color ink) {
fill = ink != null ? ink : DEFAULT_FILL;
stroke = ink != null ? ink : DEFAULT_STROKE;
}
public void setItalics(boolean enabled) {
if (enabled)
videoAttrs |= TextFont.ATTR_ITALIC;
else
videoAttrs &= ~TextFont.ATTR_ITALIC;
}
/**
*/
public void setLeftMargin() {
this.leftMargin = x;
}
/**
* @param leftMargin
* the leftMargin to set
*/
public void setLeftMargin(int leftMargin) {
this.leftMargin = constrainX(leftMargin);
}
public void setReverseVideo(boolean enabled) {
if (enabled)
videoAttrs |= TextFont.ATTR_INVERSE;
else
videoAttrs &= ~TextFont.ATTR_INVERSE;
}
public void setStroke(Color stroke) {
this.stroke = stroke != null ? stroke : DEFAULT_STROKE;
}
public void setTopMargin() {
this.topMargin = y;
}
/**
* @param topMargin
* the topMargin to set
*/
public void setTopMargin(int topMargin) {
this.topMargin = constrainY(topMargin);
}
public void setVideoAttrs(int attr) {
videoAttrs = attr;
}
public void setVideoAttrDisabled(int attr) {
videoAttrs &= ~attr;
}
public void setVideoAttrEnabled(int attr) {
videoAttrs |= attr;
}
public void setTextMode(TextMode mode) {
setTextMode(mode, false);
}
public void setTextMode(TextMode mode, boolean adjustDisplayAspect) {
if (mode == null)
throw new IllegalArgumentException();
textMode = mode;
if (adjustDisplayAspect)
screen.setAspect(textMode.getAspect());
redrawTextPage();
}
public TextMode getTextMode() {
return textMode;
}
public void unplot(int x, int y) {
plot(x, y, (a, b) -> a & ~b);
}
@Override
public void layerToFront() {
screen.moveToFront(this);
}
@Override
public void layerToBack() {
screen.moveToBack(this);
}
public void clearLine(int y) {
y = constrainY(y);
if (y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
Arrays.fill(lineBuffer.get(y - 1), null);
redrawTextPage();
}
}
}

View File

@@ -0,0 +1,980 @@
package inf101.v18.gfx.textmode;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javafx.geometry.Point2D;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.text.Font;
import javafx.scene.transform.Affine;
/**
* TextFont for grid-based text / character graphics
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*/
public class TextFont {
public static final int ATTR_INVERSE = 0x01;
public static final int ATTR_ITALIC = 0x02;
public static final int ATTR_BOLD = 0x04;
public static final int ATTR_OUTLINE = 0x08;
public static final int ATTR_UNDERLINE = 0x10;
public static final int ATTR_OVERLINE = 0x20;
public static final int ATTR_LINE_THROUGH = 0x40;
public static final int ATTR_OVERSTRIKE = 0x80;
public static final int ATTR_CLIP = 0x80;
public static final int ATTR_NO_FAKE_CHARS = 0x100;
public static final int ATTR_BLINK = 0x200; // NOT IMPLEMENTED
public static final int ATTR_FAINT = 0x400; // NOT IMPLEMENTED
public static final int ATTR_BRIGHT = 0x800;
private static final String[] searchPath = { "", "../", "../fonts/" };
private static final Map<String, String> loadedFonts = new HashMap<>();
private static final double thin = 2.0, thick = 4.0;
private static final String[] boxDrawingShapes = { // lines
"--..", "**..", "..--", "..**",
// dashed lines
"3--..", "3**..", "3..--", "3..**", "4--..", "4**..", "4..--", "4..**",
// corners
".-.-", ".*.-", ".-.*", ".*.*", "-..-", "*..-", "-..*", "*..*", ".--.", ".*-.", ".-*.", ".**.", "-.-.",
"*.-.", "-.*.", "*.*.",
// |-
".---", ".*--", ".-*- ", ".--* ", ".-**", ".**-", ".*-*", ".***", "-.--", "*.--", "-.*-", "-.-*", "-.**",
"*.*-", "*.-*", "*.**",
// T
"--.-", "*-.-", "-*.-", "**.-", "--.*", "*-.*", "-*.*", "**.*", "---.", "*--. ", "-*-.", "**-.", "--*.",
"*-*.", "-**.", "***.",
// +
"----", "*---", "-*--", "**--", "--*-", "---*", "--**", "*-*-", "-**-", "*--*", "-*-*", "***-", "**-*",
"*-**", "-***", "****",
// dashes
"2--..", "2**..", "2..--", "2..**",
// double lines
"==..", "..==", ".=.-", ".-.=", ".N.N", "=..-", "-..=", "M..M", ".=-.", ".-=.", ".MM.", "=.-.", "-.=.",
"N.N.", ".=--", ".s==", ".zMN", "=.--", "s.==", "z.NM", "==.s", "--.=", "MN.z", "==s.", "--=.", "NMz.",
"==--", "--==", "zzzz",
// round corners
"0.-.-", "0-..-", "0-.-.", "0.--.",
// diagonals
"////", "\\\\\\\\", "//\\\\\\",
// short lines
"-...", "..-.", ".-..", "...-", "*...", "..*.", ".*..", "...*",
// thin/thick lines
"-*..", "..-*", "*-..", "..*-" };
public static void drawMasked(GraphicsContext ctx, Image src, Image mask, double x, double y, boolean invert,
boolean blend) {
int w = (int) src.getWidth();
int h = (int) src.getHeight();
PixelReader pixelSrc = src.getPixelReader();
PixelReader pixelMask = mask.getPixelReader();
PixelWriter pixelWriter = ctx.getPixelWriter();
Affine transform = ctx.getTransform();
Point2D point = transform.transform(x, y);
int dx = (int) point.getX(), dy = (int) point.getY();
for (int px = 0; px < w; px++) {
for (int py = 0; py < h; py++) {
int a = pixelMask.getArgb(px, py) >>> 24;
int rgb = pixelSrc.getArgb(px, py);
if (invert)
a = ~a & 0xff;
if (blend)
a = ((rgb >>> 24) * a) >>> 8;
pixelWriter.setArgb(px + dx, py + dy, (a << 24) | rgb);
}
}
}
public static void fillInverse(GraphicsContext ctx, Image img, double x, double y) {
int w = (int) img.getWidth();
int h = (int) img.getHeight();
PixelReader pixelReader = img.getPixelReader();
PixelWriter pixelWriter = ctx.getPixelWriter();
Affine transform = ctx.getTransform();
Point2D point = transform.transform(x, y);
int dx = (int) point.getX(), dy = (int) point.getY();
Color c = ctx.getFill() instanceof Color ? (Color) ctx.getFill() : Color.BLACK;
int rgb = ((int) (c.getRed() * 255)) << 16 | ((int) (c.getGreen() * 255)) << 8 | ((int) (c.getBlue() * 255));
for (int px = 0; px < w; px++) {
for (int py = 0; py < h; py++) {
int a = (~pixelReader.getArgb(px, py) & 0xff000000);
// if(a != 0)
pixelWriter.setArgb(px + dx, py + dy, a | rgb);
}
}
}
/**
* Load a font.
*
* Will first try to use the provided string as a file name, and load the font
* from a font file (should be in a format supported by JavaFX). If loading from
* file fails, we assume we have been given the name of a font which is passed
* directly to JavaFX for loading. A fallback font is used if this fails.
*
* When looking for files, his method will search for the file in a search path
* starting with the folder containing the {@link TextFont} class file (using
* Java's standard {@link Class#getResourceAsStream(String)} system). The search
* path also includes ".." (parent directory) and "../fonts".
*
* The loaded font will be cached, so that additional calls with the same file
* name will not cause the file to be loaded again.
*
* If the font cannot be loaded, a default font will be substituted. You may
* check which font you got using {@link Font#getName()} or
* {@link Font#getFamily()}.
*
* @param name
* Font name, or relative path to the file
* @param size
* Desired point size of the font
* @return A JavaFX font
*/
public static final Font findFont(String name, double size) {
return findFont(name, size, TextFont.class);
}
/**
* Load a font.
*
* Will first try to use the provided string as a file name, and load the font
* from a font file (should be in a format supported by JavaFX). If loading from
* file fails, we assume we have been given the name of a font which is passed
* directly to JavaFX for loading. A fallback font is used if this fails.
*
* When looking for files, this method will search for the file in a search path
* starting with the folder containing the provided Java class (using Java's
* standard {@link Class#getResourceAsStream(String)} system). The search path
* also includes ".." (parent directory) and "../fonts".
*
* The loaded font will be cached, so that additional calls with the same file
* name will not cause the file to be loaded again.
*
* If the font cannot be loaded, a default font will be substituted. You may
* check which font you got using {@link Font#getName()} or
* {@link Font#getFamily()}.
*
* @param name
* Name of a font, or relative path to the font file
* @param size
* Desired point size of the font
* @param clazz
* A class for finding files relative to
* @return A JavaFX font
*/
public static final Font findFont(String name, double size, Class<?> clazz) {
if (name == null || size < 0)
throw new IllegalArgumentException();
if (loadedFonts.containsKey(name))
return Font.font(loadedFonts.get(name), size);
for (String path : searchPath) {
try (InputStream stream = clazz.getResourceAsStream(path + name)) {
Font font = Font.loadFont(stream, size);
if (font != null) {
loadedFonts.put(name, font.getName());
// System.err.println("Found: " + font.getName());
return font;
}
} catch (FileNotFoundException e) {
// we'll just try the next alternative in the search path
} catch (IOException e) {
e.printStackTrace();
}
}
Font font = Font.font(name, size);
if (font == null)
font = Font.font(size);
if (font == null)
throw new RuntimeException("Even the default font seems to be unavailable this shouldn't happen! :(");
if (font.getName().equals(Font.getDefault().getName())) {
System.err.println("TextFont: Default font '" + font.getName() + "' substituted for '" + name + "'");
}
// System.err.println("Found: " + name + "=" + font.getName());
loadedFonts.put(name, name);
return font;
}
private Canvas tmpCanvas;
private final WritableImage img;
/**
* The JavaFX font
*/
private Font font;
/** Font size */
private final double size;
/**
* Horizontal positioning of letters.
*
* Each letter should be approximately centered within its available
* square-shaped space.
*/
private final double xTranslate;
/**
* Vertical positioning of letters.
*
* Each letter should be positioned on the baseline so that ascenders and
* descenders fall within its available square-shaped space.
*/
private final double yTranslate;
/**
* Horizontal scaling factor (1.0 means no scaling)
*
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit
* a square shape.
*/
private final double xScale;
/**
* Vertical scaling factor (1.0 means no scaling)
*/
private final double yScale;
/**
* Width and height of the square-shaped space each letter should fit within
*/
private double squareSize;
private String fileName;
SnapshotParameters snapshotParameters = new SnapshotParameters();
{
snapshotParameters.setFill(Color.TRANSPARENT);
}
/**
* Create a new TextFont.
*
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize
* The width and height of a square defining the bounds of letters
* @param xTranslate
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
*/
public TextFont(Font font, double squareSize, double xTranslate, double yTranslate, double xScale, double yScale) {
super();
this.fileName = font.getName();
this.font = font;
this.size = font.getSize();
this.squareSize = squareSize;
this.xTranslate = xTranslate;
this.yTranslate = yTranslate;
this.xScale = xScale;
this.yScale = yScale;
this.img = new WritableImage((int) squareSize, (int) squareSize);
}
/**
* Create a new TextFont.
*
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize
* The width and height of a square defining the bounds of letters
* @param xTranslate
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
*/
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
double xScale, double yScale) {
super();
this.fileName = fileName;
this.font = findFont(fileName, size);
this.size = size;
this.squareSize = squareSize;
this.xTranslate = xTranslate;
this.yTranslate = yTranslate;
this.xScale = xScale;
this.yScale = yScale;
this.img = new WritableImage((int) squareSize, (int) squareSize);
}
/**
* Create a new TextFont.
*
* <p>
* TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType.
*
* <p>
* Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept.
*
* <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode).
*
*
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts".
* @param size
* Point size of the font.
* @param squareSize
* The width and height of a square defining the bounds of letters
* @param xTranslate
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
* @param deferLoading
* True if the font file shouldn't be loaded before the font is
* actually used
*/
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
double xScale, double yScale, boolean deferLoading) {
super();
this.fileName = fileName;
this.font = deferLoading ? null : findFont(fileName, size);
this.size = size;
this.squareSize = squareSize;
this.xTranslate = xTranslate;
this.yTranslate = yTranslate;
this.xScale = xScale;
this.yScale = yScale;
this.img = new WritableImage((int) squareSize, (int) squareSize);
}
/**
* Create a copy of this font, with the given adjustments to the translation and
* scaling.
*
* @param deltaXTranslate
* @param deltaYTranslate
* @param deltaXScale
* @param deltaYScale
* @return
*/
public TextFont adjust(double size, double deltaXTranslate, double deltaYTranslate, double deltaXScale,
double deltaYScale) {
if (size == 0.0) {
return new TextFont(fileName, this.size, squareSize, xTranslate + deltaXTranslate,
yTranslate + deltaYTranslate, xScale + deltaXScale, yScale + deltaYScale);
} else {
return new TextFont(fileName, this.size + size, squareSize, xTranslate + deltaXTranslate,
yTranslate + deltaYTranslate, xScale + deltaXScale, yScale + deltaYScale);
}
}
/**
* Draw the given text at position (0,0).
*
* The <code>ctx</code> should normally be translated to the appropriate text
* position before calling this method.
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param text
* string to be printed
*/
public void drawText(GraphicsContext ctx, String text) {
textAt(ctx, 0.0, 0.0, text, 1.0, true, true, ctx.getFill(), null, 0, null);
}
/**
* Draw the given text at position (0,0), with horizontal scaling.
*
* The <code>ctx</code> should normally be translated to the appropriate text
* position before calling this method.
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param text
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/
public void drawText(GraphicsContext ctx, String text, double xScaleFactor) {
textAt(ctx, 0.0, 0.0, text, xScaleFactor, true, false, ctx.getFill(), null, 0, null);
}
/**
* Draw the given text at position (x,y).
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param x
* X-position of the lower left corner of the text
* @param y
* Y-position of the lower left corner of the text
* @param text
* string to be printed
*/
public void drawTextAt(GraphicsContext ctx, double x, double y, String text) {
textAt(ctx, x, y, text, 1.0, true, false, ctx.getFill(), null, 0, null);
}
/**
* Draw the given text at position (x, y), with horizontal scaling.
*
* Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing.
*
* The graphics context's current path will be overwritten.
*
* @param ctx
* a grapics context
* @param x
* X-position of the lower left corner of the text
* @param y
* Y-position of the lower left corner of the text
* @param text
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/
public void drawTextAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode,
Paint bg) {
textAt(ctx, x, y, text, xScaleFactor, true, false, ctx.getFill(), null, mode, bg);
}
private void fakeBlockElement(GraphicsContext ctx, String text, double xScaleFactor) {
text.codePoints().forEach((int c) -> {
// System.out.printf("%s %x%n", text, c);
if (c >= 0x2596 && c <= 0x259f) { // quadrants
int bits = BlocksAndBoxes.unicodeBlocksString.indexOf(c);
if ((bits & 1) > 0) { // lower right
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize / 2, (squareSize / 2) * xScaleFactor,
squareSize / 2);
}
if ((bits & 2) > 0) { // lower left
ctx.fillRect(0, -squareSize / 2, (squareSize / 2) * xScaleFactor, squareSize / 2);
}
if ((bits & 4) > 0) { // upper right
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize, (squareSize / 2) * xScaleFactor,
squareSize / 2);
}
if ((bits & 8) > 0) { // upper left
ctx.fillRect(0, -squareSize, (squareSize / 2) * xScaleFactor, squareSize / 2);
}
} else if (c == 0x2580) { // upper half
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize / 2);
} else if (c > 0x2580 && c <= 0x2588) { // x/8 block
int height = c - 0x2580;
ctx.fillRect(0, -(height * squareSize) / 8, (squareSize) * xScaleFactor, (height * squareSize) / 8);
} else if (c >= 0x2589 && c <= 0x258f) { // x/8 block
int width = 8 - (c - 0x2588);
ctx.fillRect(0, -squareSize, ((width * squareSize) / 8) * xScaleFactor, squareSize);
} else if (c == 0x2590) { // right half
ctx.fillRect(squareSize * xScaleFactor / 2, -squareSize, (squareSize / 2) * xScaleFactor, squareSize);
} else if (c == 0x2591) { // light shade
ctx.save();
ctx.setGlobalAlpha(0.25);
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
ctx.restore();
} else if (c == 0x2592) { // medium shade
ctx.save();
ctx.setGlobalAlpha(0.5);
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
ctx.restore();
} else if (c == 0x2593) { // dark shade
ctx.save();
ctx.setGlobalAlpha(0.75);
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize);
ctx.restore();
} else if (c == 0x2594) { // upper eighth
ctx.fillRect(0, -squareSize, (squareSize) * xScaleFactor, squareSize / 8);
} else if (c == 0x2595) { // right eighth
ctx.fillRect(((7 * squareSize) / 8) * xScaleFactor, -squareSize, (squareSize / 8) * xScaleFactor,
squareSize);
} else if (c == 0x2571) {
ctx.save();
ctx.setLineWidth(2.0);
ctx.strokeLine(0, 0, squareSize * xScaleFactor, -squareSize);
ctx.restore();
} else if (c == 0x2572) {
ctx.save();
ctx.setLineWidth(2.0);
ctx.strokeLine(0, -squareSize, squareSize * xScaleFactor, 0);
ctx.restore();
} else if (c == 0x2573) {
ctx.save();
ctx.setLineWidth(2.0);
ctx.strokeLine(0, 0, squareSize * xScaleFactor, -squareSize);
ctx.strokeLine(0, -squareSize, squareSize * xScaleFactor, 0);
ctx.restore();
} else if (c >= 0x2500 && c <= 0x257f) {
ctx.save();
ctx.setLineCap(StrokeLineCap.BUTT);
String spec = boxDrawingShapes[c - 0x2500];
int i = 0;
double extraThickness = 0.0;
if (Character.isDigit(spec.charAt(i))) {
extraThickness = setContextFromChar(ctx, spec.charAt(i++));
}
char s;
double hThickness = Math.max(setContextFromChar(ctx, spec.charAt(i)),
setContextFromChar(ctx, spec.charAt(i + 1))) + extraThickness;
double vThickness = Math.max(setContextFromChar(ctx, spec.charAt(i + 2)),
setContextFromChar(ctx, spec.charAt(i + 3))) + extraThickness;
if (c >= 0x2550 || spec.charAt(i) != spec.charAt(i + 1)) {
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, -1, 0, s, vThickness);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, 1, 0, s, vThickness);
} else {
s = spec.charAt(i++);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeFullLine(ctx, xScaleFactor, 1, 0, s);
}
if (c >= 0x2550 || spec.charAt(i) != spec.charAt(i + 1)) {
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, 0, -1, s, hThickness);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeHalfLine(ctx, xScaleFactor, 0, 1, s, hThickness);
} else {
s = spec.charAt(i++);
s = spec.charAt(i++);
setContextFromChar(ctx, s);
strokeFullLine(ctx, xScaleFactor, 0, 1, s);
}
ctx.restore();
}
});
}
/**
* @return the font
*/
public Font getFont() {
if (font == null)
font = findFont(fileName, size);
return font;
}
/**
* @return the size
*/
public double getSize() {
return size;
}
/**
* Width and height of the square-shaped space each letter should fit within
*
* @return the squareSize
*/
public double getSquareSize() {
return squareSize;
}
private Canvas getTmpCanvas() {
if (tmpCanvas == null) {
tmpCanvas = new Canvas(squareSize, squareSize);
} else {
tmpCanvas.getGraphicsContext2D().clearRect(0, 0, squareSize, squareSize);
}
return tmpCanvas;
}
/**
* Horizontal scaling factor (1.0 means no scaling)
*
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit
* a square shape.
*
* @return the xScale
*/
public double getxScale() {
return xScale;
}
/**
* Horizontal positioning of letters.
*
* Each letter should be approximately centered within its available
* square-shaped space.
*
* @return the xTranslate
*/
public double getxTranslate() {
return xTranslate;
}
/**
* Vertical scaling factor (1.0 means no scaling)
*
* @return the yScale
*/
public double getyScale() {
return yScale;
}
/**
* /** Vertical positioning of letters.
*
* Each letter should be positioned on the baseline so that ascenders and
* descenders fall within its available square-shaped space.
*
* @return the yTranslate
*/
public double getyTranslate() {
return yTranslate;
}
private double setContextFromChar(GraphicsContext ctx, char c) {
switch (c) {
case '0':
return -2 * thin;
case '2':
ctx.setLineDashes(new double[] { 14.75, 2.5 });
break;
case '3':
ctx.setLineDashes(new double[] { 9, 2.5 });
break;
case '4':
ctx.setLineDashes(new double[] { 6.125, 2.5 });
break;
case '.':
return 0.0;
case '-':
case 's':
ctx.setLineWidth(thin);
return thin;
case '*':
ctx.setLineWidth(thick);
return thick;
case '=':
case 'N':
case 'M':
case 'z':
ctx.setLineWidth(thin);
return thin;
}
return 0.0;
}
/**
* Set up a graphics context for drawing with this font.
*
* Caller should call {@link GraphicsContext#save()} first, and then
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
* transformation matrix (i.e., translation, scaling).
*
* The GraphicsContext should be translated to the coordinates where the text
* should appear <em>before</em> calling this method, since this method will
* modify the coordinate system.
*
* @param ctx
* A GraphicsContext
*/
public void setGraphicsContext(GraphicsContext ctx) {
ctx.setFont(getFont());
ctx.translate(xTranslate, yTranslate);
ctx.scale(xScale, yScale);
}
/**
* Set up a graphics context for drawing with this font.
*
* Caller should call {@link GraphicsContext#save()} first, and then
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
* transformation matrix (i.e., translation, scaling).
*
* The GraphicsContext should be translated to the coordinates where the text
* should appear <em>before</em> calling this method, since this method will
* modify the coordinate system.
*
* @param ctx
* A GraphicsContext
* @param xScaleFactor
* Additional horizontal scaling, normally 0.5 (for half-width
* characters)
*/
public void setGraphicsContext(GraphicsContext ctx, double xScaleFactor) {
ctx.setFont(getFont());
ctx.translate(xTranslate * xScaleFactor, yTranslate);
ctx.scale(xScale * xScaleFactor, yScale);
}
private void strokeFullLine(GraphicsContext ctx, double xScaleFactor, double xDir, double yDir, char c) {
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
if (c != '.') {
// System.out.printf("(%4.1f %4.1f %4.1f %4.1f) w=%1.4f%n", x * yDir, y * xDir,
// x * yDir + xDir * squareSize,
// y * xDir + yDir * squareSize, ctx.getLineWidth());
if (c == '=') {
ctx.setLineWidth(2.0);
ctx.strokeLine((x - 2) * yDir, (y - 2) * xDir, (x - 2) * yDir + xDir * squareSize * xScaleFactor,
(y - 2) * xDir + yDir * -squareSize);
ctx.strokeLine((x + 2) * yDir, (y + 2) * xDir, (x + 2) * yDir + xDir * squareSize * xScaleFactor,
(y + 2) * xDir + yDir * -squareSize);
} else {
ctx.strokeLine(x * yDir, y * xDir, x * yDir + xDir * squareSize * xScaleFactor,
y * xDir + yDir * -squareSize);
}
}
}
private void strokeHalfLine(GraphicsContext ctx, double xScaleFactor, double xDir, double yDir, char c,
double otherWidth) {
if (c != '.') {
double factor = 1.0, dblFactor = 0.0;
if (c == 's' || c == 'z')
factor = -1;
if (c == 'N')
dblFactor = -2;
if (c == 'M')
dblFactor = 2;
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
x -= xDir * factor * (otherWidth / 2);
y -= yDir * factor * (otherWidth / 2);
if (c == '=' || c == 'z' || c == 'N' || c == 'M') {
ctx.setLineWidth(2.0);
double x0 = x - 2 * yDir;
double y0 = y - 2 * xDir;
x0 += dblFactor * xDir * (otherWidth / 2);
y0 += dblFactor * yDir * (otherWidth / 2);
ctx.strokeLine(x0, y0, x0 + xDir * squareSize * xScaleFactor, y0 + yDir * squareSize);
double x1 = x + 2 * yDir;
double y1 = y + 2 * xDir;
x1 -= dblFactor * xDir * (otherWidth / 2);
y1 -= dblFactor * yDir * (otherWidth / 2);
ctx.strokeLine(x1, y1, x1 + xDir * squareSize * xScaleFactor, y1 + yDir * squareSize);
} else {
ctx.strokeLine(x, y, x + xDir * squareSize * xScaleFactor, y + yDir * squareSize);
}
}
}
/**
* Draw text at the given position.
*
* For most cases, the simpler {@link #drawText(GraphicsContext, String)} or
* {@link #drawText(GraphicsContext, String, double)} will be easier to use.
*
* If <code>clip</code> is true, the graphics context's current path will be
* overwritten.
*
* @param ctx
* A GraphicsContext
* @param x
* X-position of the lower left corner of the text
* @param y
* Y-position of the lower left corner of the text
* @param text
* The text to be printed
* @param xScaleFactor
* Horizontal scaling factor, normally 1.0 (full width) or 0.5 (half
* width)
* @param clear
* True if the area should be cleared (to transparency) before
* drawing; normally true.
* @param clip
* True if the text drawing should be clipped to fit the expected
* printing area; normally true.
* @param fill
* True if the letter shapes should be filled; normally true.
* @param stroke
* True if the letter shapes should be stroked (outlined); normally
* false.
*/
public void textAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, boolean clear,
boolean clip, Paint fill, Paint stroke, int mode, Paint bg) {
if ((mode & ATTR_BRIGHT) != 0) {
fill = fill instanceof Color ? ((Color) fill).deriveColor(0.0, 1.0, 3.0, 1.0) : fill;
stroke = stroke instanceof Color ? ((Color) stroke).deriveColor(0.0, 1.0, 3.0, 1.0) : stroke;
}
if ((mode & ATTR_FAINT) != 0) {
fill = fill instanceof Color ? ((Color) fill).deriveColor(0.0, 1.0, 1.0, .5) : fill;
stroke = stroke instanceof Color ? ((Color) stroke).deriveColor(0.0, 1.0, 1.0, .5) : stroke;
}
GraphicsContext target = ctx;
int width = text.codePointCount(0, text.length());
ctx.save(); // save 1
ctx.setFill(fill);
ctx.setStroke(stroke);
ctx.translate(x, y);
if (clear && (mode & ATTR_INVERSE) == 0 && (mode & ATTR_OVERSTRIKE) == 0) {
ctx.clearRect(0 + 0.5, -squareSize + 0.5, squareSize * width * xScaleFactor - 1, squareSize - 1);
}
if (bg != null && bg != Color.TRANSPARENT) {
ctx.save(); // save 2
ctx.setFill(bg);
ctx.fillRect(0, -squareSize, squareSize * width * xScaleFactor, squareSize);
ctx.restore(); // restore 2
}
boolean drawIndirect = clip || (mode & (ATTR_INVERSE | ATTR_CLIP)) != 0;
Canvas tmpCanvas = getTmpCanvas();
if (drawIndirect) {
target = tmpCanvas.getGraphicsContext2D();
target.save(); // save 2 if drawIndirect
target.translate(0, squareSize);
target.setFill((mode & ATTR_INVERSE) != 0 ? Color.BLACK : fill);
target.setStroke((mode & ATTR_INVERSE) != 0 ? Color.BLACK : stroke);
}
if (text.length() > 0 && text.charAt(0) >= 0x2500 && text.charAt(0) <= 0x259f
&& (mode & ATTR_NO_FAKE_CHARS) == 0) {
target.save(); // save 3
target.setStroke(target.getFill());
fakeBlockElement(target, text, xScaleFactor);
target.restore(); // restore 3
} else {
target.save(); // save 3
if ((mode & ATTR_ITALIC) != 0) {
target.translate(-0.2, 0);
target.transform(new Affine(Affine.shear(-0.2, 0)));
}
setGraphicsContext(target, xScaleFactor);
if (fill != null)
target.fillText(text, 0.0, 0.0);
if ((mode & ATTR_BOLD) != 0) {
// System.err.println("stroke2");
target.save(); // save 4
target.setLineWidth(thin);
target.setStroke(target.getFill());
target.strokeText(text, 0.0, 0.0);
target.restore(); // restore 4
}
if (stroke != null || (mode & ATTR_OUTLINE) != 0) {
// System.err.println("stroke2");
target.setLineWidth(((mode & ATTR_BOLD) != 0) ? thin : thin / 2);
target.strokeText(text, 0.0, 0.0);
}
target.restore(); // restore 3
}
if ((mode & ATTR_UNDERLINE) != 0) {
target.fillRect(0, yTranslate - 2, width * squareSize * xScaleFactor, thin);
}
if ((mode & ATTR_OVERLINE) != 0) {
target.fillRect(0, -squareSize + 2, width * squareSize * xScaleFactor, thin);
}
if ((mode & ATTR_LINE_THROUGH) != 0) {
target.fillRect(0, -squareSize / 2 + 2, width * squareSize * xScaleFactor, thin);
}
if (drawIndirect) {
target.restore(); // restore 2 if drawIndirect
tmpCanvas.snapshot(snapshotParameters, img);
if ((mode & ATTR_INVERSE) != 0) {
fillInverse(ctx, img, 0, -squareSize);
} else
ctx.drawImage(img, 0, -squareSize);
}
ctx.restore(); // restore 1
}
}

View File

@@ -0,0 +1,235 @@
package inf101.v18.gfx.textmode;
import inf101.v18.gfx.Screen;
import inf101.v18.gfx.textmode.Printer;
import javafx.application.Application;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class TextFontAdjuster extends Application {
private static final String FONT_NAME = "PetMe64.ttf";
// new TextFont(FONT_NAME, 22.2, TextModes.CHAR_BOX_SIZE, 0.0, 0.0, 1.0, 1.0);
private static TextFontAdjuster demo;
public static TextFontAdjuster getInstance() {
return demo;
}
public static void main(String[] args) {
launch(args);
}
private TextFont textFont = new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE, 3.1000, -3.8000, 1.0000,
1.0000, true);
private Screen screen;
private boolean paused;
private Printer printer;
private boolean grid = true;
private double adjustAmount = 0.1;
private double adjustX(KeyCode code) {
switch (code) {
case LEFT:
return -1 * adjustAmount;
case RIGHT:
return 1 * adjustAmount;
default:
return 0;
}
}
private double adjustY(KeyCode code) {
switch (code) {
case UP:
return 1 * adjustAmount;
case DOWN:
return -1 * adjustAmount;
default:
return 0;
}
}
private void drawBackgroundGrid() {
if (grid) {
printer.drawCharCells();
/*
* painter.turnTo(0); for (int y = 0; y < printer.getPageHeight(); y++) {
* painter.jumpTo(0, y * printer.getCharHeight()); for (int x = 0; x <
* printer.getLineWidth(); x++) { painter.setInk( (x + y) % 2 == 0 ?
* Color.CORNFLOWERBLUE : Color.CORNFLOWERBLUE.brighter().brighter());
* painter.fillRectangle(printer.getCharWidth(), printer.getCharHeight());
* painter.jump(printer.getCharWidth()); } }
*/
} else {
screen.clearBackground();
}
}
private void printHelp() {
printer.moveTo(1, 1);
printer.setAutoScroll(false);
printer.println(" " + Printer.center("TextFontAdjuster", 36) + " ");
printer.println(" ");
printer.println(" ");
printer.println(" ");
printer.println("________________________________________");
printer.println("Adjust letter parameters: ");
printer.println(" Font size: CTRL +, CTRL - ");
printer.println(" Position: LEFT, RIGHT, UP, DOWN ");
printer.println(" Scaling: CTRL-(LEFT, RIGHT, UP, DOWN)");
printer.println("Commands / options (with CTRL key): ");
printer.println(" Hires (R) Grid (G) Fullscreen (F) ");
printer.println(" Help (H) Quit (Q) ");
printer.println("Write text with any other key. ");
printer.println("_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-_-*-");
printer.println(" ");
printer.println("Sample text: ");
printer.println("the quick brown fox jumps over the lazy ");
printer.println(" dog, THE QUICK BROWN FOX JUMPS OVER THE");
printer.println("LAZY DOG den vågale røde reven værer den");
printer.println("sinte hunden DEN VÅGALE RØDE REVEN VÆRER");
printer.println("DEN SINTE HUNDEN !\"#%&/()?,._-@£${[]}?|^");
// printer.print(" ");
printer.moveTo(1, 15);
printer.setAutoScroll(true);
}
private void printInfo() {
printer.moveTo(1, 3);
printer.println(String.format("Font: %s at %1.1fpt ", textFont.getFont().getName(),
textFont.getFont().getSize()));
printer.println(String.format(" xTr=%-1.1f yTr=%-1.1f xSc=%-1.1f ySc=%-1.1f ", textFont.getxTranslate(),
textFont.getyTranslate(), textFont.getxScale(), textFont.getyScale()));
// System.out.printf("new TextFont(\"%s\", %1.2f, Printer.CHAR_HEIGHT, %1.4f,
// %1.4f, %1.4f, %1.4f)%n", FONT_NAME,
// textFont.getSize(), textFont.getxTranslate(), textFont.getyTranslate(),
// textFont.getxScale(),
// textFont.getyScale());
printer.moveTo(1, 15);
}
private void setup() {
drawBackgroundGrid();
printHelp();
printInfo();
}
@Override
public void start(Stage stage) {
demo = this;
screen = Screen.startPaintScene(stage);
printer = screen.createPrinter();
printer.setInk(Color.BLACK);
printer.setFont(textFont);
screen.setKeyOverride((KeyEvent event) -> {
KeyCode code = event.getCode();
// System.out.println(event);
if (event.isControlDown() || event.isShortcutDown()) {
if (code == KeyCode.Q) {
System.exit(0);
} else if (code == KeyCode.P) {
paused = !paused;
return true;
} else if (code == KeyCode.R) {
printer.cycleMode(true);
drawBackgroundGrid();
return true;
} else if (code == KeyCode.S) {
if (event.isAltDown())
screen.fitScaling();
else
screen.zoomCycle();
drawBackgroundGrid();
return true;
} else if (code == KeyCode.A) {
screen.cycleAspect();
return true;
} else if (code == KeyCode.G) {
grid = !grid;
drawBackgroundGrid();
return true;
} else if (code == KeyCode.H) {
printHelp();
printInfo();
return true;
} else if (code == KeyCode.F) {
stage.setFullScreen(!stage.isFullScreen());
return true;
} else if (code == KeyCode.M) {
printer.print("\r");
return true;
} else if (code == KeyCode.L) {
printer.redrawTextPage();
return true;
} else if (code == KeyCode.DIGIT1) {
DemoPages.printBoxDrawing(printer);
return true;
} else if (code == KeyCode.DIGIT2) {
DemoPages.printZX(printer);
return true;
} else if (code == KeyCode.DIGIT3) {
DemoPages.printBlockPlotting(printer);
return true;
} else if (code == KeyCode.DIGIT4) {
DemoPages.printVideoAttributes(printer);
return true;
} else if (code == KeyCode.DIGIT5) {
DemoPages.printAnsiArt(printer);
return true;
} else if (code == KeyCode.PLUS) {
textFont = textFont.adjust(adjustAmount, 0.0, 0.0, 0.0, 0.0);
printer.setFont(textFont);
printer.redrawTextPage();
printInfo();
return true;
} else if (code == KeyCode.MINUS) {
textFont = textFont.adjust(-adjustAmount, 0.0, 0.0, 0.0, 0.0);
printer.setFont(textFont);
printer.redrawTextPage();
printInfo();
return true;
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP
|| code == KeyCode.DOWN) {
textFont = textFont.adjust(0.0, 0.0, 0.0, adjustX(code), adjustY(code));
printer.setFont(textFont);
printer.redrawTextPage();
printInfo();
return true;
}
} else if (code == KeyCode.LEFT || code == KeyCode.RIGHT || code == KeyCode.UP || code == KeyCode.DOWN) {
textFont = textFont.adjust(0.0, adjustX(code), adjustY(code), 0.0, 0.0);
printer.setFont(textFont);
printer.redrawTextPage();
printInfo();
return true;
} else if (code == KeyCode.ENTER) {
printer.print("\n");
return true;
}
return false;
});
screen.setKeyTypedHandler((KeyEvent event) -> {
if (event.getCharacter() != KeyEvent.CHAR_UNDEFINED) {
printer.print(event.getCharacter());
return true;
}
return false;
});
setup();
stage.show();
}
}

View File

@@ -0,0 +1,158 @@
package inf101.v18.gfx.textmode;
import inf101.v18.gfx.Screen;
public enum TextMode {
/** Low resolution, wide screen (20:11, fits 16:9) text mode 40x22 */
MODE_40X22(Constants.H40, Constants.V22, Screen.ASPECT_WIDE),
/** Low resolution, 16:10 aspect text mode 40x25 */
MODE_40X25(Constants.H40, Constants.V25, Screen.ASPECT_MEDIUM),
/** Low resolution, 4:3 aspect text mode 40x30 */
MODE_40X30(Constants.H40, Constants.V30, Screen.ASPECT_CLASSIC),
/** High resolution, wide screen (20:11, fits 16:9) text mode 80x22 */
MODE_80X22(Constants.H80, Constants.V22, Screen.ASPECT_WIDE),
/** High resolution, 16:10 aspect text mode 80x25 */
MODE_80X25(Constants.H80, Constants.V25, Screen.ASPECT_MEDIUM),
/** High resolution, 4:3 aspect text mode 80x30 */
MODE_80X30(Constants.H80, Constants.V30, Screen.ASPECT_CLASSIC);
protected static class Constants {
protected static final int H40 = 0, H80 = 1;
protected static final int[] HREZ = { 40, 80 };
protected static final int V22 = 0, V25 = 1, V30 = 2;
protected static final int[] VREZ = { 22, 25, 30 };
private static TextMode[] MODES = null;
// work around initialization order for statics and enums
protected static TextMode getMode(int i) {
if (MODES == null)
MODES = new TextMode[] { MODE_40X22, MODE_40X25, MODE_40X30, MODE_80X22, MODE_80X25, MODE_80X30 };
return MODES[(i + MODES.length) % MODES.length];
}
}
/**
* Size of the square-shaped "box" bounds of character cells.
*
* For "high" resolution, characters will be squeezed horizontally to fit half
* the width.
*/
public static final double CHAR_BOX_SIZE = 32;
/**
* Maximum length of a line in any resolution mode
*/
public static final int LINE_WIDTH_MAX = Constants.HREZ[Constants.HREZ.length - 1];
/**
* Maximum height of a page in any resolution mode
*/
public static final int PAGE_HEIGHT_MAX = Constants.VREZ[Constants.VREZ.length - 1];
private int aspect;
private int w;
private int h;
private int hIndex;
private int vIndex;
private TextMode(int w, int h, int aspect) {
this.hIndex = w;
this.vIndex = h;
this.aspect = aspect;
this.w = Constants.HREZ[w];
this.h = Constants.VREZ[h];
}
private TextMode findMode(int hIndex, int vIndex) {
hIndex = (hIndex + Constants.HREZ.length) % Constants.HREZ.length;
vIndex = (vIndex + Constants.VREZ.length) % Constants.VREZ.length;
return Constants.getMode(vIndex + hIndex * Constants.VREZ.length);
}
/**
* Get aspect ration descriptor for use with {@link Screen#setAspect()}
*
* @return One of {@link Screen#ASPECT_WIDE}, {@link Screen#ASPECT_MEDIUM} or
* {@link Screen#ASPECT_CLASSIC}
*/
public int getAspect() {
return aspect;
}
public double getCharBoxSize() {
return CHAR_BOX_SIZE;
}
public double getCharHeight() {
return CHAR_BOX_SIZE;
}
public double getCharWidth() {
return w == 80 ? CHAR_BOX_SIZE / 2 : CHAR_BOX_SIZE;
}
public int getLineWidth() {
return w;
}
public int getPageHeight() {
return h;
}
/**
* Cycle through horizontal modes
*
* @return Next available horizontal mode (vertical resolution unchanged)
*/
public TextMode nextHorizMode() {
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
}
/**
* Cycle through modes
*
* @return Next available mode
*/
public TextMode nextMode() {
int m = vIndex + hIndex * Constants.VREZ.length;
return Constants.getMode(m + 1);
}
/**
* Cycle through vertical modes
*
* @return Next available vertical mode (horizontal resolution unchanged)
*/
public TextMode nextVertMode() {
return findMode((hIndex + 1) % Constants.HREZ.length, vIndex);
}
/**
* Cycle through horizontal modes
*
* @return Previous available horizontal mode (vertical resolution unchanged)
*/
public TextMode prevHorizMode() {
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
}
/**
* Cycle through modes
*
* @return Previous available mode
*/
public TextMode prevMode() {
int m = vIndex + hIndex * Constants.VREZ.length;
return Constants.getMode(m);
}
/**
* Cycle through vertical modes
*
* @return Previous available vertical mode (horizontal resolution unchanged)
*/
public TextMode prevVertMode() {
return findMode((hIndex - 1) % Constants.HREZ.length, vIndex);
}
}

View File

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