mirror of
https://github.com/AppleCommander/bastools.git
synced 2024-09-19 22:00:06 +00:00
Early work on shape tools and 'st' command.
This commit is contained in:
parent
6f3c68a0ee
commit
37683e1852
@ -0,0 +1,81 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class BitmapShape implements Shape {
|
||||
public final List<List<Boolean>> grid = new ArrayList<>();
|
||||
public final Point origin = new Point();
|
||||
|
||||
public BitmapShape() {
|
||||
this(0,0);
|
||||
}
|
||||
public BitmapShape(int height, int width) {
|
||||
while (grid.size() < height) {
|
||||
grid.add(newRow(width));
|
||||
}
|
||||
}
|
||||
|
||||
private List<Boolean> newRow(int width) {
|
||||
List<Boolean> row = new ArrayList<>();
|
||||
while (row.size() < width) {
|
||||
row.add(Boolean.FALSE);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
public void insertColumn() {
|
||||
origin.y++;
|
||||
for (List<Boolean> row : grid) {
|
||||
row.add(0, Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
public void addColumn() {
|
||||
for (List<Boolean> row : grid) {
|
||||
row.add(Boolean.FALSE);
|
||||
}
|
||||
}
|
||||
public void insertRow() {
|
||||
origin.x++;
|
||||
grid.add(0, newRow(getWidth()));
|
||||
}
|
||||
public void addRow() {
|
||||
grid.add(newRow(getWidth()));
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return grid.size();
|
||||
}
|
||||
public int getWidth() {
|
||||
return grid.isEmpty() ? 0 : grid.get(0).size();
|
||||
}
|
||||
|
||||
public void plot(int x, int y) {
|
||||
plot(x, y, Boolean.TRUE);
|
||||
}
|
||||
public void plot(int x, int y, Boolean pixel) {
|
||||
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
|
||||
return;
|
||||
}
|
||||
grid.get(y).set(x, pixel);
|
||||
}
|
||||
|
||||
public Boolean get(int x, int y) {
|
||||
if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
return grid.get(y).get(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmapShape toBitmap() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorShape toVector() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
public interface Shape {
|
||||
public BitmapShape toBitmap();
|
||||
public VectorShape toVector();
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.github.applecommander.bastools.api.shapes.exporters.TextShapeExporter;
|
||||
|
||||
public interface ShapeExporter {
|
||||
/** Export a single shape to the OutputStream. */
|
||||
public void export(Shape shape, OutputStream outputStream);
|
||||
/** Export a single shape to the File. */
|
||||
public default void export(Shape shape, File file) throws IOException {
|
||||
Objects.requireNonNull(shape);
|
||||
Objects.requireNonNull(file);
|
||||
export(shape, file.toPath());
|
||||
}
|
||||
/** Export a single shape to the Path. */
|
||||
public default void export(Shape shape, Path path) throws IOException {
|
||||
Objects.requireNonNull(shape);
|
||||
Objects.requireNonNull(path);
|
||||
try (OutputStream outputStream = Files.newOutputStream(path)) {
|
||||
export(shape, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/** Export the entire shape table to the OutputStream. */
|
||||
public void export(ShapeTable shapeTable, OutputStream outputStream);
|
||||
/** Export the entire shape table to the File. */
|
||||
public default void export(ShapeTable shapeTable, File file) throws IOException {
|
||||
Objects.requireNonNull(shapeTable);
|
||||
Objects.requireNonNull(file);
|
||||
export(shapeTable, file.toPath());
|
||||
}
|
||||
/** Export the entire shape table to the Path. */
|
||||
public default void export(ShapeTable shapeTable, Path path) throws IOException {
|
||||
Objects.requireNonNull(shapeTable);
|
||||
Objects.requireNonNull(path);
|
||||
try (OutputStream outputStream = Files.newOutputStream(path)) {
|
||||
export(shapeTable, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static TextShapeExporter.Builder text() {
|
||||
return new TextShapeExporter.Builder();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import io.github.applecommander.bastools.api.utils.Streams;
|
||||
|
||||
public class ShapeTable {
|
||||
public static ShapeTable read(byte[] data) {
|
||||
Objects.requireNonNull(data);
|
||||
ShapeTable shapeTable = new ShapeTable();
|
||||
ByteBuffer buf = ByteBuffer.wrap(data)
|
||||
.order(ByteOrder.LITTLE_ENDIAN);
|
||||
int count = Byte.toUnsignedInt(buf.get());
|
||||
// unused:
|
||||
buf.get();
|
||||
for (int i = 0; i < count; i++) {
|
||||
int offset = buf.getShort();
|
||||
// load empty shapes as empty...
|
||||
if (offset == 0) {
|
||||
shapeTable.shapes.add(new VectorShape());
|
||||
continue;
|
||||
}
|
||||
// defer to VectorShape to process bits
|
||||
buf.mark();
|
||||
buf.position(offset);
|
||||
shapeTable.shapes.add(VectorShape.from(buf));
|
||||
buf.reset();
|
||||
}
|
||||
return shapeTable;
|
||||
}
|
||||
|
||||
public static ShapeTable read(InputStream inputStream) throws IOException {
|
||||
Objects.requireNonNull(inputStream);
|
||||
return read(Streams.toByteArray(inputStream));
|
||||
}
|
||||
|
||||
public static ShapeTable read(File file) throws IOException {
|
||||
Objects.requireNonNull(file);
|
||||
return read(file.toPath());
|
||||
}
|
||||
|
||||
public static ShapeTable read(Path path) throws IOException {
|
||||
Objects.requireNonNull(path);
|
||||
return read(Files.readAllBytes(path));
|
||||
}
|
||||
|
||||
public final List<Shape> shapes = new ArrayList<>();
|
||||
|
||||
public void write(OutputStream outputStream) throws IOException {
|
||||
Objects.requireNonNull(outputStream);
|
||||
// TODO
|
||||
}
|
||||
|
||||
public void write(File file) throws IOException {
|
||||
Objects.requireNonNull(file);
|
||||
try (OutputStream outputStream = new FileOutputStream(file)) {
|
||||
write(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(Path path) throws IOException {
|
||||
Objects.requireNonNull(path);
|
||||
write(path.toFile());
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
/**
|
||||
* Represents all "plot vectors" available in an Applesoft shape table.
|
||||
*
|
||||
* @see <a href="https://archive.org/stream/Applesoft_BASIC_Programming_Reference_Manual_Apple_Computer#page/n103/mode/2up">
|
||||
* Applesoft BASIC Programming Reference Manual</a>
|
||||
*/
|
||||
public enum VectorCommand {
|
||||
// Order here is specific to the encoding within the shape itself
|
||||
MOVE_UP, MOVE_RIGHT, MOVE_DOWN, MOVE_LEFT,
|
||||
PLOT_UP, PLOT_RIGHT, PLOT_DOWN, PLOT_LEFT;
|
||||
|
||||
public final boolean plot;
|
||||
public final int xmove;
|
||||
public final int ymove;
|
||||
|
||||
private VectorCommand() {
|
||||
this.plot = (this.ordinal() & 0b100) != 0;
|
||||
// up 0b00
|
||||
// right 0b01
|
||||
// down 0b10
|
||||
// left 0b11
|
||||
if ((this.ordinal() & 0b001) == 1) {
|
||||
this.xmove = 2 - (this.ordinal() & 0b011);
|
||||
this.ymove = 0;
|
||||
} else {
|
||||
this.xmove = 0;
|
||||
this.ymove = (this.ordinal() & 0b011) - 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class VectorShape implements Shape {
|
||||
public static VectorShape from(ByteBuffer buf) {
|
||||
Objects.requireNonNull(buf);
|
||||
VectorShape shape = new VectorShape();
|
||||
|
||||
VectorCommand[] commands = VectorCommand.values();
|
||||
while (buf.hasRemaining()) {
|
||||
int code = Byte.toUnsignedInt(buf.get());
|
||||
if (code == 0) break;
|
||||
|
||||
int vector1 = code & 0b111;
|
||||
int vector2 = (code >> 3) & 0b111;
|
||||
int vector3 = (code >> 6) & 0b011; // Cannot plot
|
||||
|
||||
shape.vectors.add(commands[vector1]);
|
||||
|
||||
if (vector2 != 0 || vector3 != 0) {
|
||||
shape.vectors.add(commands[vector2]);
|
||||
|
||||
if (vector3 != 0) {
|
||||
shape.vectors.add(commands[vector3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
public final List<VectorCommand> vectors = new ArrayList<>();
|
||||
|
||||
public VectorShape moveUp() { return add(VectorCommand.MOVE_UP); }
|
||||
public VectorShape moveRight() { return add(VectorCommand.MOVE_RIGHT); }
|
||||
public VectorShape moveDown() { return add(VectorCommand.MOVE_DOWN); }
|
||||
public VectorShape moveLeft() { return add(VectorCommand.MOVE_LEFT); }
|
||||
public VectorShape plotUp() { return add(VectorCommand.PLOT_UP); }
|
||||
public VectorShape plotRight() { return add(VectorCommand.PLOT_RIGHT); }
|
||||
public VectorShape plotDown() { return add(VectorCommand.PLOT_DOWN); }
|
||||
public VectorShape plotLeft() { return add(VectorCommand.PLOT_LEFT); }
|
||||
|
||||
private VectorShape add(VectorCommand vectorCommand) {
|
||||
this.vectors.add(vectorCommand);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitmapShape toBitmap() {
|
||||
BitmapShape shape = new BitmapShape();
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
for (VectorCommand command : vectors) {
|
||||
if (command.plot) {
|
||||
while (y < 0) {
|
||||
shape.insertRow();
|
||||
y += 1;
|
||||
}
|
||||
while (y >= shape.getHeight()) {
|
||||
shape.addRow();
|
||||
}
|
||||
while (x < 0) {
|
||||
shape.insertColumn();
|
||||
x += 1;
|
||||
}
|
||||
while (x >= shape.getWidth()) {
|
||||
shape.addColumn();
|
||||
}
|
||||
shape.plot(x,y);
|
||||
}
|
||||
x += command.xmove;
|
||||
y += command.ymove;
|
||||
}
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorShape toVector() {
|
||||
return this;
|
||||
}
|
||||
}
|
@ -0,0 +1,245 @@
|
||||
package io.github.applecommander.bastools.api.shapes.exporters;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.github.applecommander.bastools.api.shapes.BitmapShape;
|
||||
import io.github.applecommander.bastools.api.shapes.Shape;
|
||||
import io.github.applecommander.bastools.api.shapes.ShapeExporter;
|
||||
import io.github.applecommander.bastools.api.shapes.ShapeTable;
|
||||
|
||||
public class TextShapeExporter implements ShapeExporter {
|
||||
private int maxWidth = 80;
|
||||
private BorderStrategy borderStrategy = BorderStrategy.BOX_DRAWING;
|
||||
|
||||
/** Use the {@code Builder} to create a TextShapeExporter. */
|
||||
private TextShapeExporter() { }
|
||||
|
||||
@Override
|
||||
public void export(Shape shape, OutputStream outputStream) {
|
||||
Objects.requireNonNull(shape);
|
||||
Objects.requireNonNull(outputStream);
|
||||
|
||||
BitmapShape b = shape.toBitmap();
|
||||
PrintWriter pw = new PrintWriter(outputStream);
|
||||
drawTopLine(pw, 1, b.getWidth());
|
||||
|
||||
Queue<BitmapShape> bqueue = new LinkedList<>(Arrays.asList(b));
|
||||
drawRow(pw, bqueue, 1, b.getHeight(), b.getWidth());
|
||||
|
||||
drawBottomLine(pw, 1, b.getWidth());
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void export(ShapeTable shapeTable, OutputStream outputStream) {
|
||||
Objects.requireNonNull(shapeTable);
|
||||
Objects.requireNonNull(outputStream);
|
||||
|
||||
List<BitmapShape> blist = shapeTable.shapes.stream()
|
||||
.map(Shape::toBitmap)
|
||||
.collect(Collectors.toList());
|
||||
int width = blist.stream().mapToInt(BitmapShape::getWidth).max().getAsInt();
|
||||
int height = blist.stream().mapToInt(BitmapShape::getHeight).max().getAsInt();
|
||||
|
||||
int columns = Math.max(1, this.maxWidth / width);
|
||||
|
||||
PrintWriter pw = new PrintWriter(outputStream);
|
||||
drawTopLine(pw, columns, width);
|
||||
|
||||
Queue<BitmapShape> bqueue = new LinkedList<>(blist);
|
||||
drawRow(pw, bqueue, columns, height, width);
|
||||
while (!bqueue.isEmpty()) {
|
||||
drawDividerLine(pw, columns, width);
|
||||
drawRow(pw, bqueue, columns, height, width);
|
||||
}
|
||||
|
||||
drawBottomLine(pw, columns, width);
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
private void drawTopLine(PrintWriter pw, int columns, int width) {
|
||||
borderStrategy.topLeftCorner(pw);
|
||||
borderStrategy.horizontalLine(pw, width);
|
||||
for (int i=1; i<columns; i++) {
|
||||
borderStrategy.topDivider(pw);
|
||||
borderStrategy.horizontalLine(pw, width);
|
||||
}
|
||||
borderStrategy.topRightCorner(pw);
|
||||
}
|
||||
private void drawDividerLine(PrintWriter pw, int columns, int width) {
|
||||
borderStrategy.dividerLeftEdge(pw);
|
||||
borderStrategy.dividerHorizontalLine(pw, width);
|
||||
for (int i=1; i<columns; i++) {
|
||||
borderStrategy.dividerMiddle(pw);
|
||||
borderStrategy.dividerHorizontalLine(pw, width);
|
||||
}
|
||||
borderStrategy.dividerRightEdge(pw);
|
||||
}
|
||||
private void drawBottomLine(PrintWriter pw, int columns, int width) {
|
||||
borderStrategy.bottomLeftCorner(pw);
|
||||
borderStrategy.horizontalLine(pw, width);
|
||||
for (int i=1; i<columns; i++) {
|
||||
borderStrategy.bottomDivider(pw);
|
||||
borderStrategy.horizontalLine(pw, width);
|
||||
}
|
||||
borderStrategy.bottomRightCorner(pw);
|
||||
}
|
||||
private void drawRow(PrintWriter pw, Queue<BitmapShape> bqueue, int columns, int height, int width) {
|
||||
BitmapShape[] bshapes = new BitmapShape[columns];
|
||||
for (int i=0; i<bshapes.length; i++) {
|
||||
bshapes[i] = bqueue.isEmpty() ? new BitmapShape() : bqueue.remove();
|
||||
}
|
||||
|
||||
for (int y=0; y<height; y++) {
|
||||
borderStrategy.verticalLine(pw);
|
||||
drawRowLine(pw, bshapes[0], width, y);
|
||||
for (int c=1; c<bshapes.length; c++) {
|
||||
borderStrategy.dividerVerticalLine(pw);
|
||||
drawRowLine(pw, bshapes[c], width, y);
|
||||
}
|
||||
borderStrategy.verticalLine(pw);
|
||||
pw.println();
|
||||
}
|
||||
}
|
||||
private void drawRowLine(PrintWriter pw, BitmapShape bshape, int width, int y) {
|
||||
List<Boolean> row = bshape.grid.size() > y ? bshape.grid.get(y) : new ArrayList<>();
|
||||
for (int x=0; x<width; x++) {
|
||||
Boolean plot = row.size() > x ? row.get(x) : Boolean.FALSE;
|
||||
if (bshape.origin.x == x && bshape.origin.y == y) {
|
||||
pw.printf("%c", plot ? '*' : '+');
|
||||
} else {
|
||||
pw.printf("%c", plot ? 'X' : '.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum BorderStrategy {
|
||||
/** No border but with spaces between shapes. Note the tricky newline in {@code dividerLeftEdge}. */
|
||||
NONE('\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', ' ', '\0', '\n', '\0', '\0'),
|
||||
/**
|
||||
* A border comprised of the box drawing characters.
|
||||
* @see <a href='https://en.wikipedia.org/wiki/Box-drawing_character'>Wikipedia article on box characters</a>
|
||||
*/
|
||||
BOX_DRAWING('\u2500', '\u2502', '\u250C', '\u2510', '\u2514', '\u2518', '\u252C', '\u2534',
|
||||
'\u2502', '\u2500', '\u251C', '\u2524', '\u253C'),
|
||||
/** A simple border based on plain ASCII characters. */
|
||||
ASCII_TEXT('-', '|', '+', '+', '+', '+', '+', '+', '|', '-', '+', '+', '+');
|
||||
|
||||
private final char horizontalLine;
|
||||
private final char verticalLine;
|
||||
private final char topLeftCorner;
|
||||
private final char topRightCorner;
|
||||
private final char bottomLeftCorner;
|
||||
private final char bottomRightCorner;
|
||||
private final char topDivider;
|
||||
private final char bottomDivider;
|
||||
private final char dividerVerticalLine;
|
||||
private final char dividerHorizontalLine;
|
||||
private final char dividerLeftEdge;
|
||||
private final char dividerRightEdge;
|
||||
private final char dividerMiddle;
|
||||
|
||||
private BorderStrategy(char horizontalLine, char verticalLine, char topLeftCorner, char topRightCorner,
|
||||
char bottomLeftCorner, char bottomRightCorner, char topDivider, char bottomDivider,
|
||||
char dividerVerticalLine, char dividerHorizontalLine, char dividerLeftEdge, char dividerRightEdge,
|
||||
char dividerMiddle) {
|
||||
this.horizontalLine = horizontalLine;
|
||||
this.verticalLine = verticalLine;
|
||||
this.topLeftCorner = topLeftCorner;
|
||||
this.topRightCorner = topRightCorner;
|
||||
this.bottomLeftCorner = bottomLeftCorner;
|
||||
this.bottomRightCorner = bottomRightCorner;
|
||||
this.topDivider = topDivider;
|
||||
this.bottomDivider = bottomDivider;
|
||||
this.dividerVerticalLine = dividerVerticalLine;
|
||||
this.dividerHorizontalLine = dividerHorizontalLine;
|
||||
this.dividerLeftEdge = dividerLeftEdge;
|
||||
this.dividerRightEdge = dividerRightEdge;
|
||||
this.dividerMiddle = dividerMiddle;
|
||||
}
|
||||
|
||||
private void print(Consumer<String> output, char ch) {
|
||||
print(output, ch, 1);
|
||||
}
|
||||
private void print(Consumer<String> output, char ch, int width) {
|
||||
if (ch != '\0') {
|
||||
output.accept(new String(new char[width]).replace('\0', ch));
|
||||
}
|
||||
}
|
||||
|
||||
public void horizontalLine(PrintWriter pw, int width) {
|
||||
print(pw::print, horizontalLine, width);
|
||||
}
|
||||
public void verticalLine(PrintWriter pw) {
|
||||
print(pw::print, verticalLine);
|
||||
}
|
||||
public void topLeftCorner(PrintWriter pw) {
|
||||
print(pw::print, topLeftCorner);
|
||||
}
|
||||
public void topRightCorner(PrintWriter pw) {
|
||||
print(pw::println, topRightCorner);
|
||||
}
|
||||
public void bottomLeftCorner(PrintWriter pw) {
|
||||
print(pw::print, bottomLeftCorner);
|
||||
}
|
||||
public void bottomRightCorner(PrintWriter pw) {
|
||||
print(pw::println, bottomRightCorner);
|
||||
}
|
||||
public void topDivider(PrintWriter pw) {
|
||||
print(pw::print, topDivider);
|
||||
}
|
||||
public void bottomDivider(PrintWriter pw) {
|
||||
print(pw::print, bottomDivider);
|
||||
}
|
||||
public void dividerVerticalLine(PrintWriter pw) {
|
||||
print(pw::print, dividerVerticalLine);
|
||||
}
|
||||
public void dividerHorizontalLine(PrintWriter pw, int width) {
|
||||
print(pw::print, dividerHorizontalLine, width);
|
||||
}
|
||||
public void dividerLeftEdge(PrintWriter pw) {
|
||||
print(pw::print, dividerLeftEdge);
|
||||
}
|
||||
public void dividerRightEdge(PrintWriter pw) {
|
||||
print(pw::println, dividerRightEdge);
|
||||
}
|
||||
public void dividerMiddle(PrintWriter pw) {
|
||||
print(pw::print, dividerMiddle);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private TextShapeExporter textShapeExporter = new TextShapeExporter();
|
||||
|
||||
public Builder maxWidth(int maxWidth) {
|
||||
textShapeExporter.maxWidth = maxWidth;
|
||||
return this;
|
||||
}
|
||||
public Builder noBorder() {
|
||||
return borderStrategy(BorderStrategy.NONE);
|
||||
}
|
||||
public Builder asciiTextBorder() {
|
||||
return borderStrategy(BorderStrategy.ASCII_TEXT);
|
||||
}
|
||||
public Builder boxDrawingBorder() {
|
||||
return borderStrategy(BorderStrategy.BOX_DRAWING);
|
||||
}
|
||||
public Builder borderStrategy(BorderStrategy borderStrategy) {
|
||||
textShapeExporter.borderStrategy = borderStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapeExporter build() {
|
||||
return textShapeExporter;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package io.github.applecommander.bastools.api.utils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class Streams {
|
||||
private Streams() { /* Prevent construction */ }
|
||||
|
||||
/** Utility method to read all bytes from an InputStream. */
|
||||
public static byte[] toByteArray(InputStream inputStream) throws IOException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
while (true) {
|
||||
byte[] buf = new byte[1024];
|
||||
int len = inputStream.read(buf);
|
||||
if (len == -1) break;
|
||||
outputStream.write(buf, 0, len);
|
||||
}
|
||||
outputStream.flush();
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class ShapesTest {
|
||||
/**
|
||||
* This shape is taken from the Applesoft BASIC Programmer's Reference Manual (1987), p146.
|
||||
*/
|
||||
public ShapeTable readStandardShapeTable() {
|
||||
final byte[] sample = { 0x01, 0x00, 0x04, 0x00, 0x12, 0x3F, 0x20, 0x64, 0x2d, 0x15, 0x36, 0x1e, 0x07, 0x00 };
|
||||
ShapeTable st = ShapeTable.read(sample);
|
||||
assertNotNull(st);
|
||||
assertNotNull(st.shapes);
|
||||
assertEquals(1, st.shapes.size());
|
||||
return st;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardShapeTableVectors() {
|
||||
ShapeTable st = readStandardShapeTable();
|
||||
|
||||
VectorShape expected = new VectorShape()
|
||||
.moveDown().moveDown()
|
||||
.plotLeft().plotLeft()
|
||||
.moveUp().plotUp().plotUp().plotUp()
|
||||
.moveRight().plotRight().plotRight().plotRight()
|
||||
.moveDown().plotDown().plotDown().plotDown()
|
||||
.moveLeft().plotLeft();
|
||||
|
||||
Shape s = st.shapes.get(0);
|
||||
assertNotNull(s);
|
||||
assertEquals(expected.vectors, s.toVector().vectors);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStandardShapeTableBitmap() {
|
||||
ShapeTable st = readStandardShapeTable();
|
||||
|
||||
BitmapShape expected = new BitmapShape(5, 5);
|
||||
for (int i=1; i<=3; i++) {
|
||||
expected.plot(i, 0);
|
||||
expected.plot(i, 4);
|
||||
expected.plot(0, i);
|
||||
expected.plot(4, i);
|
||||
}
|
||||
|
||||
Shape s = st.shapes.get(0);
|
||||
assertEquals(expected.grid, s.toBitmap().grid);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextShapeExporterNoBorder() {
|
||||
ShapeTable st = readStandardShapeTable();
|
||||
|
||||
final String expected = ".XXX.\n"
|
||||
+ "X...X\n"
|
||||
+ "X.+.X\n"
|
||||
+ "X...X\n"
|
||||
+ ".XXX.\n";
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ShapeExporter exp = ShapeExporter.text().noBorder().build();
|
||||
exp.export(st.shapes.get(0), outputStream);
|
||||
String actual = new String(outputStream.toByteArray());
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextShapeExporterAsciiBorder() {
|
||||
ShapeTable st = readStandardShapeTable();
|
||||
|
||||
final String expected = "+-----+\n"
|
||||
+ "|.XXX.|\n"
|
||||
+ "|X...X|\n"
|
||||
+ "|X.+.X|\n"
|
||||
+ "|X...X|\n"
|
||||
+ "|.XXX.|\n"
|
||||
+ "+-----+\n";
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ShapeExporter exp = ShapeExporter.text().asciiTextBorder().build();
|
||||
exp.export(st.shapes.get(0), outputStream);
|
||||
String actual = new String(outputStream.toByteArray());
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextShapeTableExporterNoBorder() {
|
||||
ShapeTable st = readStandardShapeTable();
|
||||
|
||||
// Simulate 4 of these identical shapes by adding 3 more
|
||||
st.shapes.add(st.shapes.get(0));
|
||||
st.shapes.add(st.shapes.get(0));
|
||||
st.shapes.add(st.shapes.get(0));
|
||||
|
||||
final String oneExpectedRow = ".XXX. .XXX.\n"
|
||||
+ "X...X X...X\n"
|
||||
+ "X.+.X X.+.X\n"
|
||||
+ "X...X X...X\n"
|
||||
+ ".XXX. .XXX.\n";
|
||||
String expected = oneExpectedRow + "\n" + oneExpectedRow;
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ShapeExporter exp = ShapeExporter.text().maxWidth(12).noBorder().build();
|
||||
exp.export(st, outputStream);
|
||||
String actual = new String(outputStream.toByteArray());
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTextShapeTableExporterAsciiBorder() {
|
||||
ShapeTable st = readStandardShapeTable();
|
||||
|
||||
// Simulate 4 of these identical shapes by adding 3 more
|
||||
st.shapes.add(st.shapes.get(0));
|
||||
st.shapes.add(st.shapes.get(0));
|
||||
st.shapes.add(st.shapes.get(0));
|
||||
|
||||
final String divider = "+-----+-----+\n";
|
||||
final String oneExpectedRow = divider
|
||||
+ "|.XXX.|.XXX.|\n"
|
||||
+ "|X...X|X...X|\n"
|
||||
+ "|X.+.X|X.+.X|\n"
|
||||
+ "|X...X|X...X|\n"
|
||||
+ "|.XXX.|.XXX.|\n";
|
||||
String expected = oneExpectedRow + oneExpectedRow + divider;
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ShapeExporter exp = ShapeExporter.text().maxWidth(12).asciiTextBorder().build();
|
||||
exp.export(st, outputStream);
|
||||
String actual = new String(outputStream.toByteArray());
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.github.applecommander.bastools.api.shapes;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class VectorCommandTest {
|
||||
@Test
|
||||
public void testDirections() {
|
||||
test(0, 1, VectorCommand.MOVE_DOWN, VectorCommand.PLOT_DOWN);
|
||||
test(0, -1, VectorCommand.MOVE_UP, VectorCommand.PLOT_UP);
|
||||
test(-1, 0, VectorCommand.MOVE_LEFT, VectorCommand.PLOT_LEFT);
|
||||
test(1, 0, VectorCommand.MOVE_RIGHT, VectorCommand.PLOT_RIGHT);
|
||||
}
|
||||
public void test(int xmove, int ymove, VectorCommand... commands) {
|
||||
for (VectorCommand command : commands) {
|
||||
assertEquals(xmove, command.xmove);
|
||||
assertEquals(ymove, command.ymove);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPlot() {
|
||||
test(false, VectorCommand.MOVE_DOWN, VectorCommand.MOVE_LEFT, VectorCommand.MOVE_RIGHT, VectorCommand.MOVE_UP);
|
||||
test(true, VectorCommand.PLOT_DOWN, VectorCommand.PLOT_LEFT, VectorCommand.PLOT_RIGHT, VectorCommand.PLOT_UP);
|
||||
}
|
||||
public void test(boolean plot, VectorCommand... commands) {
|
||||
for (VectorCommand command : commands) {
|
||||
assertEquals(plot, command.plot);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
include 'api'
|
||||
include 'tools:bt'
|
||||
include 'tools:st'
|
||||
|
||||
rootProject.name = 'bastools'
|
||||
project(":api").name = 'bastools-api'
|
||||
project(":tools").name = 'bastools-tools'
|
||||
project(":tools:bt").name = 'bastools-tools-bt'
|
||||
project(":tools:st").name = 'bastools-tools-st'
|
||||
|
26
tools/st/build.gradle
Normal file
26
tools/st/build.gradle
Normal file
@ -0,0 +1,26 @@
|
||||
plugins {
|
||||
id 'org.springframework.boot' version '2.0.2.RELEASE'
|
||||
}
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
apply plugin: 'application'
|
||||
|
||||
mainClassName = "io.github.applecommander.bastools.tools.st.Main"
|
||||
|
||||
bootJar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Implementation-Title': 'Shape Tools CLI',
|
||||
'Implementation-Version': "${version} (${new Date().format('yyyy-MM-dd HH:mm')})"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'info.picocli:picocli:3.0.2'
|
||||
compile 'net.sf.applecommander:applesingle-api:1.2.1'
|
||||
compile project(':bastools-api')
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package io.github.applecommander.bastools.tools.st;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import io.github.applecommander.bastools.api.shapes.Shape;
|
||||
import io.github.applecommander.bastools.api.shapes.ShapeExporter;
|
||||
import io.github.applecommander.bastools.api.shapes.ShapeTable;
|
||||
import io.github.applecommander.bastools.api.shapes.exporters.TextShapeExporter.BorderStrategy;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Help.Visibility;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
@Command(name = "extract", description = { "Extract shapes from shape table" },
|
||||
parameterListHeading = "%nParameters:%n",
|
||||
descriptionHeading = "%n",
|
||||
optionListHeading = "%nOptions:%n")
|
||||
public class ExtractCommand implements Callable<Void> {
|
||||
@Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true)
|
||||
private boolean helpFlag;
|
||||
|
||||
@Option(names = "--stdin", description = "Read from stdin")
|
||||
private boolean stdinFlag;
|
||||
|
||||
@Option(names = "--stdout", description = "Write to stdout")
|
||||
private boolean stdoutFlag;
|
||||
|
||||
@Option(names = { "-o", "--output" }, description = "Write to filename")
|
||||
private String filename;
|
||||
|
||||
@Option(names = "--border", description = "Set border style (none, simple, box)", showDefaultValue = Visibility.ALWAYS)
|
||||
private String borderStyle = "simple";
|
||||
|
||||
@Option(names = { "-w", "--width" }, description = "Set text width", showDefaultValue = Visibility.ALWAYS)
|
||||
private int textWidth = 80;
|
||||
|
||||
@Option(names = "--shape", description = "Extract specific shape")
|
||||
private int shapeNum = 0;
|
||||
|
||||
@Parameters(arity = "0..1", description = "File to process")
|
||||
private Path file;
|
||||
|
||||
private BorderStrategy borderStrategy;
|
||||
|
||||
@Override
|
||||
public Void call() throws IOException {
|
||||
validateArguments();
|
||||
|
||||
ShapeTable shapeTable = stdinFlag ? ShapeTable.read(System.in) : ShapeTable.read(file);
|
||||
|
||||
ShapeExporter exporter = ShapeExporter.text()
|
||||
.borderStrategy(borderStrategy)
|
||||
.maxWidth(textWidth)
|
||||
.build();
|
||||
|
||||
if (shapeNum > 0) {
|
||||
if (shapeNum <= shapeTable.shapes.size()) {
|
||||
Shape shape = shapeTable.shapes.get(shapeNum-1);
|
||||
if (stdoutFlag) {
|
||||
exporter.export(shape, System.out);
|
||||
} else {
|
||||
exporter.export(shape, Paths.get(filename));
|
||||
}
|
||||
} else {
|
||||
throw new IOException("Invalid shape number");
|
||||
}
|
||||
} else {
|
||||
if (stdoutFlag) {
|
||||
exporter.export(shapeTable, System.out);
|
||||
} else {
|
||||
exporter.export(shapeTable, Paths.get(filename));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void validateArguments() throws IOException {
|
||||
if (stdoutFlag && filename != null) {
|
||||
throw new IOException("Please choose one of stdout or output file");
|
||||
}
|
||||
if ((stdinFlag && file != null) || (!stdinFlag && file == null)) {
|
||||
throw new IOException("Please select ONE of stdin or file");
|
||||
}
|
||||
switch (borderStyle) {
|
||||
case "box":
|
||||
this.borderStrategy = BorderStrategy.BOX_DRAWING;
|
||||
break;
|
||||
case "simple":
|
||||
this.borderStrategy = BorderStrategy.ASCII_TEXT;
|
||||
break;
|
||||
case "none":
|
||||
this.borderStrategy = BorderStrategy.NONE;
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Please select a valid border strategy");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package io.github.applecommander.bastools.tools.st;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.HelpCommand;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
/**
|
||||
* Primary entry point into the Shape Tools utility.
|
||||
*/
|
||||
@Command(name = "st", mixinStandardHelpOptions = true, versionProvider = VersionProvider.class,
|
||||
descriptionHeading = "%n",
|
||||
commandListHeading = "%nCommands:%n",
|
||||
optionListHeading = "%nOptions:%n",
|
||||
description = "Shape Tools utility",
|
||||
subcommands = {
|
||||
ExtractCommand.class,
|
||||
HelpCommand.class,
|
||||
})
|
||||
public class Main implements Runnable {
|
||||
@Option(names = "--debug", description = "Dump full stack trackes if an error occurs")
|
||||
private static boolean debugFlag;
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
CommandLine.run(new Main(), args);
|
||||
} catch (Throwable t) {
|
||||
if (Main.debugFlag) {
|
||||
t.printStackTrace(System.err);
|
||||
} else {
|
||||
String message = t.getMessage();
|
||||
while (t != null) {
|
||||
message = t.getMessage();
|
||||
t = t.getCause();
|
||||
}
|
||||
System.err.printf("Error: %s\n", Optional.ofNullable(message).orElse("An error occurred."));
|
||||
}
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
CommandLine.usage(this, System.out);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package io.github.applecommander.bastools.tools.st;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle;
|
||||
import io.github.applecommander.bastools.api.BasTools;
|
||||
import picocli.CommandLine.IVersionProvider;
|
||||
|
||||
/** Display version information. Note that this is dependent on the Spring Boot Gradle plugin configuration. */
|
||||
public class VersionProvider implements IVersionProvider {
|
||||
public String[] getVersion() {
|
||||
return new String[] {
|
||||
String.format("%s: %s", Main.class.getPackage().getImplementationTitle(),
|
||||
Main.class.getPackage().getImplementationVersion()),
|
||||
String.format("%s: %s", BasTools.TITLE, BasTools.VERSION),
|
||||
String.format("AppleSingle API: %s", AppleSingle.VERSION)
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user