From 35b02d96dcaa86e0ebeeb4b19ae1c69cfb13840f Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sat, 14 Jul 2018 23:25:55 -0500 Subject: [PATCH] Adding ability to import external shape files via shape generator. --- .../api/shapes/ExternalShapeImporter.java | 98 ++++++++++++++++++ .../bastools/api/shapes/Shape.java | 8 ++ .../bastools/api/shapes/ShapeGenerator.java | 4 + .../bastools/api/shapes/ShapeTable.java | 5 + .../bastools/api/utils/Converters.java | 32 ++++++ .../bastools/api/utils/ConverterTest.java | 45 ++++++++ samples/imperator.bin | Bin 0 -> 1866 bytes samples/ships.st | 6 ++ 8 files changed, 198 insertions(+) create mode 100644 api/src/main/java/io/github/applecommander/bastools/api/shapes/ExternalShapeImporter.java create mode 100644 api/src/test/java/io/github/applecommander/bastools/api/utils/ConverterTest.java create mode 100644 samples/imperator.bin diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ExternalShapeImporter.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ExternalShapeImporter.java new file mode 100644 index 0000000..a96d419 --- /dev/null +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ExternalShapeImporter.java @@ -0,0 +1,98 @@ +package io.github.applecommander.bastools.api.shapes; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.IntStream; + +import io.github.applecommander.bastools.api.utils.Converters; + +/** + * Allow the import of an external shape. Processing is very dependent on + * being invoked in the "correct" manner! + *

+ * Prototype code: + *

+ * ; Read in external shape table: configure first and then "import" processes file.
+ * .external characters
+ *   type=bin
+ *   shapes=1-96
+ *   import=imperator.bin
+ * 
+ */ +public class ExternalShapeImporter { + private ShapeTable destination; + private String firstShapeLabel; + private Function importer = this::importShapeTableFromBinary; + private IntStream intStream = null; + + public ExternalShapeImporter(ShapeTable destination, String firstShapeLabel) { + this.destination = destination; + this.firstShapeLabel = firstShapeLabel; + } + + public void process(String line) { + Objects.requireNonNull(line); + String[] parts = line.split("="); + if (parts.length != 2) { + throw new RuntimeException(String.format(".external fields require an assignment for '%s'", line)); + } + switch (parts[0].toLowerCase()) { + case "type": + switch (parts[1].toLowerCase()) { + case "bin": + importer = this::importShapeTableFromBinary; + break; + case "src": + importer = this::importShapeTableFromSource; + break; + default: + throw new RuntimeException(String.format("Unknown import type specified: '%s'", line)); + } + break; + case "shapes": + intStream = Converters.toIntStream(parts[1]); + break; + case "import": + ShapeTable temp = importer.apply(parts[1]); + // Shapes in Applesoft are 1 based but Java List object is 0 based... + intStream.map(n -> n-1).mapToObj(temp.shapes::get).forEach(this::importShape); + break; + default: + throw new RuntimeException(String.format("Unknown assignment '%s' for .external", line)); + } + } + + public ShapeTable importShapeTableFromBinary(String filename) { + // FIXME May need access to Configuration for these nested files? + try { + Objects.requireNonNull(intStream, ".external requires that 'shapes' is specified"); + return ShapeTable.read(Paths.get(filename)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + public ShapeTable importShapeTableFromSource(String filename) { + // FIXME May need access to Configuration for these nested files? + try { + Objects.requireNonNull(intStream, ".external requires that 'shapes' is specified"); + return ShapeGenerator.generate(Paths.get(filename)); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + public void importShape(Shape shape) { + if (firstShapeLabel != null) { + VectorShape vshape = new VectorShape(firstShapeLabel); + vshape.vectors.addAll(shape.toVector().vectors); + destination.shapes.add(vshape); + firstShapeLabel = null; + } else { + destination.shapes.add(shape); + } + } +} diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/Shape.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/Shape.java index 23be689..13dd6d6 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/Shape.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/Shape.java @@ -1,5 +1,13 @@ package io.github.applecommander.bastools.api.shapes; +/** + * Represents a single Applesoft shape. Note that the interface is mostly useful to get at the + * bitmap or vector shapes. This also implies that these implementations need to transform between + * eachother! + * + * @see BitmapShape + * @see VectorShape + */ public interface Shape { /** Indicates if this shape is empty. */ public boolean isEmpty(); diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeGenerator.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeGenerator.java index 42fe888..aeafdbd 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeGenerator.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeGenerator.java @@ -44,6 +44,10 @@ public class ShapeGenerator { st.shapes.add(bitmapShape); shapeConsumer = bitmapShape::appendBitmapRow; break; + case ".external": + ExternalShapeImporter importer = new ExternalShapeImporter(st, label); + shapeConsumer = importer::process; + break; default: if (line.length() == 0) { // do nothing diff --git a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeTable.java b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeTable.java index 4e35332..20e36b1 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeTable.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/shapes/ShapeTable.java @@ -16,7 +16,12 @@ import java.util.stream.Collectors; import io.github.applecommander.bastools.api.utils.Streams; +/** + * Represents an Applesoft shape table. Note that this direct class is somewhat useless, + * except for the I/O routines. Access the individual shapes via the {@code #shapes} list. + */ public class ShapeTable { + /** Read an existing Applesoft shape table binary file. */ public static ShapeTable read(byte[] data) { Objects.requireNonNull(data); ShapeTable shapeTable = new ShapeTable(); diff --git a/api/src/main/java/io/github/applecommander/bastools/api/utils/Converters.java b/api/src/main/java/io/github/applecommander/bastools/api/utils/Converters.java index 5ea2114..038919a 100644 --- a/api/src/main/java/io/github/applecommander/bastools/api/utils/Converters.java +++ b/api/src/main/java/io/github/applecommander/bastools/api/utils/Converters.java @@ -1,5 +1,8 @@ package io.github.applecommander.bastools.api.utils; +import java.util.Arrays; +import java.util.stream.IntStream; + public class Converters { private Converters() { /* Prevent construction */ } @@ -19,10 +22,39 @@ public class Converters { } } + /** + * Convert a string to a boolean value allowing for "true" or "yes" to evaluate to Boolean.TRUE. + */ public static Boolean toBoolean(String value) { if (value == null) { return null; } return "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value); } + + + /** + * Supports entry of values in ranges or comma-separated lists and combinations thereof. + * + */ + public static IntStream toIntStream(String values) { + IntStream stream = IntStream.empty(); + for (String range : values.split(";")) { + if (range.contains("-")) { + String[] parts = range.split("-"); + int low = Integer.parseInt(parts[0]); + int high = Integer.parseInt(parts[1]); + stream = IntStream.concat(stream, IntStream.rangeClosed(low, high)); + } else { + stream = IntStream.concat(stream, + Arrays.asList(range.split(",")).stream().mapToInt(Integer::parseInt)); + } + } + return stream; + } } diff --git a/api/src/test/java/io/github/applecommander/bastools/api/utils/ConverterTest.java b/api/src/test/java/io/github/applecommander/bastools/api/utils/ConverterTest.java new file mode 100644 index 0000000..d5b364e --- /dev/null +++ b/api/src/test/java/io/github/applecommander/bastools/api/utils/ConverterTest.java @@ -0,0 +1,45 @@ +package io.github.applecommander.bastools.api.utils; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class ConverterTest { + @Test + public void testToInteger() { + assertEquals(0x1000, Converters.toInteger("0x1000").intValue()); + assertEquals(0x1000, Converters.toInteger("$1000").intValue()); + assertEquals(1000, Converters.toInteger("1000").intValue()); + } + + @Test + public void testToBoolean() { + assertTrue(Converters.toBoolean("true")); + assertTrue(Converters.toBoolean("True")); + assertTrue(Converters.toBoolean("YES")); + assertFalse(Converters.toBoolean("faLse")); + assertFalse(Converters.toBoolean("No")); + assertFalse(Converters.toBoolean("notreally")); + } + + @Test + public void testToIntStream_Range() { + final int[] expected = { 4, 5, 6, 7, 8 }; + assertArrayEquals(expected, Converters.toIntStream("4-8").toArray()); + } + + @Test + public void testToIntStream_List() { + final int[] expected314159 = { 3, 1, 4, 1, 5, 9 }; + assertArrayEquals(expected314159, Converters.toIntStream("3,1,4,1,5,9").toArray()); + + final int[] expected7 = { 7 }; + assertArrayEquals(expected7, Converters.toIntStream("7").toArray()); + } + + @Test + public void testToIntStream_Complex() { + final int[] expected = { 1, 5,6,7, 9, 2,3,4, 8 }; + assertArrayEquals(expected, Converters.toIntStream("1;5-7;9;2-4;8").toArray()); + } +} diff --git a/samples/imperator.bin b/samples/imperator.bin new file mode 100644 index 0000000000000000000000000000000000000000..7437a143f0417a0e46c8b17b7d7ce84cd5cb96d1 GIT binary patch literal 1866 zcmY*ZO^D<~6t2qOQerpGEX|BGcHYfA-3L6KMF&D*y z9&B+J3^JEF=yi6@aX>|g;>C-wbCg+T5D`HT+(Tdw%f|0jlHRd@lgg|2zVCbAt2g;A z_8a?y{li?o&VBwozsN7~D}0B4#25TazRwH(Grz_EChzhb{(zi)qH@7Xu(Z|x)dZ`*d(ou{3bo!6YVoDZEZoEy#$ z&ackz&c9CW^xbv$3HK@Y1vhbDcdxo1xSzWF?oId5{mDIYZ@YI~<~`zVc+Yt+dY8N_ z-rHX0eeUgh-+147zj(L3JKjC-v2*m^IJdzWUSg5wjO7n86{>6!B#gb2sya*$bHVQW zy+UgpNLkgTlrqvfOLqm+BAw_`7CMua*2!+n*h>0Lo%xkslh^8Sw#(UpUm(k*Oyh|j zXOd#L3Ms4bqfmu~?jx#?KNQwM!r3@p)YCq)B5hGmFUQyt#FOBf)*%o~gG2!6_9T>} z9l_!`5@UT&usr`PM6Ng%1JoaxZ$F6z8~bvA&8Z)q(vQ}C3G|sCWmG?u*z_Jk>S-b> zU8W0D2#a#Lm?(mTO+rXjm#II{rzk2%A=Z?Xiqfw@1A)MR0taSqFatQM3+93u=`vB3 zDkL4AZ9}ML7vi^TNSJhMa#cpziVlE?wU-r`S8-KVS&NGl_{%DNQbILJq+bc)u4_Z?@I~Q3q*Aqfe%&2896mV zy97pLBSS=PLI1Jcv1vXsxo;{HoKQyH2Z=7keiTHf=S?-! z9o1}t20;(jF%%ShWqJcVoTaHydQ4VUHOiq~;2`i|OxIHqgq+@kj;Kx#z}A?520X|} z_Q{N5gENGsJb^DE_EMV;d|y)8v*OS;VhRQnU28Zykx>mlp%3r~5Wt%O)DdjfFj{J1 zXfCxOfkrwbGu17Z9`PchA@7W8tWgFGXi39l6k%;n>}-s<*3^VC$-~$U#0oG-ICM@T z%ew~{nnCjg$^cId#^&jKexTRV!)-9ibK`QoLt{J7bF8sB;1E>8LCLy?EFFc2YKU`( z3g0(f?M8yav$=r|jfZB8;<&=t5_agb%&jq4sLI(Mv@>Hz#}^VZKe~>J%d66y8v}5w zz?DuXk_k~z9Gc^DRW>a*vXED>cMcF_j5aCVlAQ32X$kHhOiDBZj0HmZP literal 0 HcmV?d00001 diff --git a/samples/ships.st b/samples/ships.st index 365681e..81579ab 100644 --- a/samples/ships.st +++ b/samples/ships.st @@ -109,3 +109,9 @@ .xx x*x xx. + +; "]IMPERATOR" font from Beagle Bros. "Apple Mechanic" +.external characters + type=bin + shapes=1-96 + import=imperator.bin