From 911aa35577d36e5eeeacbdcbf23e72fba0c5a345 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Sat, 3 Oct 2020 23:09:50 +0200 Subject: [PATCH] Add a second page with the solution to the PDF. --- .../java/ch/fritteli/labyrinth/Labyrinth.java | 2 + .../ch/fritteli/labyrinth/PDFRenderer.java | 102 ++++++++++++++---- 2 files changed, 82 insertions(+), 22 deletions(-) diff --git a/src/main/java/ch/fritteli/labyrinth/Labyrinth.java b/src/main/java/ch/fritteli/labyrinth/Labyrinth.java index 5e5be59..b5c6244 100644 --- a/src/main/java/ch/fritteli/labyrinth/Labyrinth.java +++ b/src/main/java/ch/fritteli/labyrinth/Labyrinth.java @@ -13,7 +13,9 @@ public class Labyrinth { private final int width; @Getter private final int height; + @Getter private final Position start; + @Getter private final Position end; public Labyrinth(final int width, final int height) { diff --git a/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java b/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java index 5dad450..7b2d6ae 100644 --- a/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java @@ -5,12 +5,16 @@ import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.jetbrains.annotations.Nullable; import java.awt.*; import java.io.ByteArrayOutputStream; import java.io.IOException; public class PDFRenderer implements Renderer { + private static final float MARGIN = 10; + private static final float SCALE = 10; + private PDFRenderer() { } @@ -20,13 +24,15 @@ public class PDFRenderer implements Renderer { return new PDFRenderer(); } + private static boolean isValid(@NonNull final Position position) { + return position.getX() >= 0 && position.getY() >= 0; + } + @Override @NonNull public byte[] render(@NonNull final Labyrinth labyrinth) { - final float scale = 10; - final float margin = 5; - final float pageWidth = labyrinth.getWidth() * scale + 2 * margin; - final float pageHeight = labyrinth.getHeight() * scale + 2 * margin; + final float pageWidth = labyrinth.getWidth() * SCALE + 2 * MARGIN; + final float pageHeight = labyrinth.getHeight() * SCALE + 2 * MARGIN; final PDDocument pdDocument = new PDDocument(); final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); @@ -37,8 +43,22 @@ public class PDFRenderer implements Renderer { pdPageContentStream.setLineWidth(1.0f); pdPageContentStream.setStrokingColor(Color.BLACK); pdPageContentStream.setNonStrokingColor(Color.BLACK); - this.drawHorizonzalLines(labyrinth, pdPageContentStream, margin, scale); - this.drawVerticalLines(labyrinth, pdPageContentStream, margin, scale); + this.drawHorizonzalLines(labyrinth, pdPageContentStream); + this.drawVerticalLines(labyrinth, pdPageContentStream); + } catch (IOException e) { + e.printStackTrace(); + } + final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); + pdDocument.addPage(solution); + try (PDPageContentStream pdPageContentStream = new PDPageContentStream(pdDocument, solution)) { + pdPageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND); + pdPageContentStream.setLineJoinStyle(BasicStroke.JOIN_MITER); + pdPageContentStream.setLineWidth(1.0f); + pdPageContentStream.setStrokingColor(Color.BLACK); + pdPageContentStream.setNonStrokingColor(Color.BLACK); + this.drawHorizonzalLines(labyrinth, pdPageContentStream); + this.drawVerticalLines(labyrinth, pdPageContentStream); + this.drawSolution(labyrinth, pdPageContentStream); } catch (IOException e) { e.printStackTrace(); } @@ -52,28 +72,29 @@ public class PDFRenderer implements Renderer { return output.toByteArray(); } - private void drawHorizonzalLines(@NonNull final Labyrinth labyrinth, @NonNull final PDPageContentStream pdPageContentStream, final float margin, final float scale) throws IOException { + private void drawHorizonzalLines(@NonNull final Labyrinth labyrinth, + @NonNull final PDPageContentStream pdPageContentStream) throws IOException { // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. - final float labyrinthHeight = labyrinth.getHeight() * scale; + final float labyrinthHeight = labyrinth.getHeight() * SCALE; for (int y = 0; y < labyrinth.getHeight(); y++) { boolean isPainting = false; for (int x = 0; x < labyrinth.getWidth(); x++) { final Tile currentTile = labyrinth.getTileAt(x, y); if (currentTile.hasWallAt(Direction.TOP)) { if (!isPainting) { - pdPageContentStream.moveTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); isPainting = true; } } else { if (isPainting) { - pdPageContentStream.lineTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); pdPageContentStream.stroke(); isPainting = false; } } } if (isPainting) { - pdPageContentStream.lineTo(labyrinth.getWidth() * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); pdPageContentStream.stroke(); } } @@ -83,45 +104,46 @@ public class PDFRenderer implements Renderer { final Tile currentTile = labyrinth.getTileAt(x, y - 1); if (currentTile.hasWallAt(Direction.BOTTOM)) { if (!isPainting) { - pdPageContentStream.moveTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); isPainting = true; } } else { if (isPainting) { - pdPageContentStream.lineTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); pdPageContentStream.stroke(); isPainting = false; } } } if (isPainting) { - pdPageContentStream.lineTo(labyrinth.getWidth() * scale + margin, labyrinthHeight - labyrinth.getHeight() * scale + margin); + pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN); pdPageContentStream.stroke(); } } - private void drawVerticalLines(@NonNull final Labyrinth labyrinth, @NonNull final PDPageContentStream pdPageContentStream, final float margin, final float scale) throws IOException { + private void drawVerticalLines(@NonNull final Labyrinth labyrinth, + @NonNull final PDPageContentStream pdPageContentStream) throws IOException { // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. - final float labyrinthHeight = labyrinth.getHeight() * scale; + final float labyrinthHeight = labyrinth.getHeight() * SCALE; for (int x = 0; x < labyrinth.getWidth(); x++) { boolean isPainting = false; for (int y = 0; y < labyrinth.getHeight(); y++) { final Tile currentTile = labyrinth.getTileAt(x, y); if (currentTile.hasWallAt(Direction.LEFT)) { if (!isPainting) { - pdPageContentStream.moveTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); isPainting = true; } } else { if (isPainting) { - pdPageContentStream.lineTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); pdPageContentStream.stroke(); isPainting = false; } } } if (isPainting) { - pdPageContentStream.lineTo(x * scale + margin, labyrinthHeight - labyrinth.getHeight() * scale + margin); + pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN); pdPageContentStream.stroke(); } } @@ -131,20 +153,56 @@ public class PDFRenderer implements Renderer { final Tile currentTile = labyrinth.getTileAt(x - 1, y); if (currentTile.hasWallAt(Direction.RIGHT)) { if (!isPainting) { - pdPageContentStream.moveTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.moveTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); isPainting = true; } } else { if (isPainting) { - pdPageContentStream.lineTo(x * scale + margin, labyrinthHeight - y * scale + margin); + pdPageContentStream.lineTo(x * SCALE + MARGIN, labyrinthHeight - y * SCALE + MARGIN); pdPageContentStream.stroke(); isPainting = false; } } } if (isPainting) { - pdPageContentStream.lineTo(labyrinth.getWidth() * scale + margin, labyrinthHeight - labyrinth.getHeight() * scale + margin); + pdPageContentStream.lineTo(labyrinth.getWidth() * SCALE + MARGIN, labyrinthHeight - labyrinth.getHeight() * SCALE + MARGIN); pdPageContentStream.stroke(); } } + + private void drawSolution(@NonNull final Labyrinth labyrinth, + @NonNull final PDPageContentStream pdPageContentStream) throws IOException { + // Draw the solution in red + pdPageContentStream.setStrokingColor(Color.RED); + // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. + final float labyrinthHeight = labyrinth.getHeight() * SCALE; + final Position end = labyrinth.getEnd(); + Position currentPosition = labyrinth.getStart(); + Position previousPosition = null; + pdPageContentStream.moveTo(MARGIN + currentPosition.getX() * SCALE + SCALE / 2, MARGIN + labyrinthHeight - (currentPosition.getY() * SCALE + SCALE / 2)); + do { + Position newCurrent = this.findNextSolutionPosition(labyrinth, previousPosition, currentPosition); + previousPosition = currentPosition; + currentPosition = newCurrent; + pdPageContentStream.lineTo(MARGIN + currentPosition.getX() * SCALE + SCALE / 2, MARGIN + labyrinthHeight - (currentPosition.getY() * SCALE + SCALE / 2)); + } while (!currentPosition.equals(end)); + pdPageContentStream.stroke(); + } + + private Position findNextSolutionPosition(@NonNull final Labyrinth labyrinth, @Nullable final Position previousPosition, @NonNull final Position currentPosition) { + final Tile currentTile = labyrinth.getTileAt(currentPosition); + for (final Direction direction : Direction.values()) { + if (!currentTile.hasWallAt(direction)) { + final Position position = direction.translate(currentPosition); + if (position.equals(previousPosition) || !isValid(position)) { + continue; + } + if (labyrinth.getTileAt(position).isSolution()) { + return position; + } + } + } + // We *SHOULD* never get here. ... famous last words ... + return null; + } }