diff --git a/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java b/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java index 1680ef5..e0474a0 100644 --- a/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java +++ b/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java @@ -1,6 +1,7 @@ package ch.fritteli.labyrinth; import lombok.NonNull; +import lombok.RequiredArgsConstructor; import lombok.Value; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocumentInformation; @@ -26,63 +27,104 @@ 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 pageWidth = labyrinth.getWidth() * SCALE + 2 * MARGIN; - final float pageHeight = labyrinth.getHeight() * SCALE + 2 * MARGIN; - - final PDDocument pdDocument = new PDDocument(); - final PDDocumentInformation info = new PDDocumentInformation(); - info.setTitle("Labyrinth " + labyrinth.getWidth() + "x" + labyrinth.getHeight() + ", ID " + labyrinth.getRandomSeed()); - pdDocument.setDocumentInformation(info); - final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); - final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); - pdDocument.addPage(page); - pdDocument.addPage(solution); - try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page); - final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) { - setUpPageContentStream(labyrinthPageContentStream); - setUpPageContentStream(solutionPageContentStream); - this.drawHorizonzalLines(labyrinth, labyrinthPageContentStream, solutionPageContentStream); - this.drawVerticalLines(labyrinth, labyrinthPageContentStream, solutionPageContentStream); - this.drawSolution(labyrinth, solutionPageContentStream); - } catch (IOException e) { - e.printStackTrace(); - } - final ByteArrayOutputStream output = new ByteArrayOutputStream(); - try { - pdDocument.save(output); - pdDocument.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return output.toByteArray(); + final Generator generator = new Generator(labyrinth); + return generator.generate(); } - private void setUpPageContentStream(@NonNull final PDPageContentStream pageContentStream) throws IOException { - pageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND); - pageContentStream.setLineJoinStyle(BasicStroke.JOIN_ROUND); - pageContentStream.setLineWidth(1.0f); - pageContentStream.setStrokingColor(Color.BLACK); - pageContentStream.setNonStrokingColor(Color.BLACK); - } - private void drawHorizonzalLines(@NonNull final Labyrinth labyrinth, - @NonNull final PDPageContentStream... contentStreams) throws IOException { - // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. - Coordinate coordinate = new Coordinate(0f, 0f); - for (int y = 0; y < labyrinth.getHeight(); y++) { + @RequiredArgsConstructor + private static class Generator { + @NonNull + private final Labyrinth labyrinth; + + private static boolean isValid(@NonNull final Position position) { + return position.getX() >= 0 && position.getY() >= 0; + } + + public byte[] generate() { + final float pageWidth = this.labyrinth.getWidth() * SCALE + 2 * MARGIN; + final float pageHeight = this.labyrinth.getHeight() * SCALE + 2 * MARGIN; + + final PDDocument pdDocument = new PDDocument(); + final PDDocumentInformation info = new PDDocumentInformation(); + info.setTitle("Labyrinth " + this.labyrinth.getWidth() + "x" + this.labyrinth.getHeight() + ", ID " + this.labyrinth.getRandomSeed()); + pdDocument.setDocumentInformation(info); + final PDPage page = new PDPage(new PDRectangle(pageWidth, pageHeight)); + final PDPage solution = new PDPage(new PDRectangle(pageWidth, pageHeight)); + pdDocument.addPage(page); + pdDocument.addPage(solution); + try (final PDPageContentStream labyrinthPageContentStream = new PDPageContentStream(pdDocument, page); + final PDPageContentStream solutionPageContentStream = new PDPageContentStream(pdDocument, solution)) { + setUpPageContentStream(labyrinthPageContentStream); + setUpPageContentStream(solutionPageContentStream); + this.drawHorizonzalLines(labyrinthPageContentStream, solutionPageContentStream); + this.drawVerticalLines(labyrinthPageContentStream, solutionPageContentStream); + this.drawSolution(solutionPageContentStream); + } catch (IOException e) { + e.printStackTrace(); + } + final ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + pdDocument.save(output); + pdDocument.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return output.toByteArray(); + } + + private void setUpPageContentStream(@NonNull final PDPageContentStream pageContentStream) throws IOException { + pageContentStream.setLineCapStyle(BasicStroke.CAP_ROUND); + pageContentStream.setLineJoinStyle(BasicStroke.JOIN_ROUND); + pageContentStream.setLineWidth(1.0f); + pageContentStream.setStrokingColor(Color.BLACK); + pageContentStream.setNonStrokingColor(Color.BLACK); + } + + private void drawHorizonzalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { + // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. + Coordinate coordinate = new Coordinate(0f, 0f); + for (int y = 0; y < this.labyrinth.getHeight(); y++) { + boolean isPainting = false; + coordinate = coordinate.withY(y); + for (int x = 0; x < this.labyrinth.getWidth(); x++) { + final Tile currentTile = this.labyrinth.getTileAt(x, y); + coordinate = coordinate.withX(x); + if (currentTile.hasWallAt(Direction.TOP)) { + if (!isPainting) { + for (final PDPageContentStream contentStream : contentStreams) { + contentStream.moveTo(coordinate.getX(), coordinate.getY()); + } + isPainting = true; + } + } else { + if (isPainting) { + for (final PDPageContentStream contentStream : contentStreams) { + contentStream.lineTo(coordinate.getX(), coordinate.getY()); + contentStream.stroke(); + } + isPainting = false; + } + } + } + if (isPainting) { + coordinate = coordinate.withX(this.labyrinth.getWidth()); + for (final PDPageContentStream contentStream : contentStreams) { + contentStream.lineTo(coordinate.getX(), coordinate.getY()); + contentStream.stroke(); + } + } + } boolean isPainting = false; - coordinate = coordinate.withY(y, labyrinth); - for (int x = 0; x < labyrinth.getWidth(); x++) { - final Tile currentTile = labyrinth.getTileAt(x, y); + int y = this.labyrinth.getHeight(); + coordinate = coordinate.withY(this.labyrinth.getHeight()); + for (int x = 0; x < this.labyrinth.getWidth(); x++) { + final Tile currentTile = this.labyrinth.getTileAt(x, y - 1); coordinate = coordinate.withX(x); - if (currentTile.hasWallAt(Direction.TOP)) { + if (currentTile.hasWallAt(Direction.BOTTOM)) { if (!isPainting) { for (final PDPageContentStream contentStream : contentStreams) { contentStream.moveTo(coordinate.getX(), coordinate.getY()); @@ -100,57 +142,55 @@ public class PDFRenderer implements Renderer { } } if (isPainting) { - coordinate = coordinate.withX(labyrinth.getWidth()); + coordinate = coordinate.withX(this.labyrinth.getWidth()); for (final PDPageContentStream contentStream : contentStreams) { contentStream.lineTo(coordinate.getX(), coordinate.getY()); contentStream.stroke(); } } } - boolean isPainting = false; - int y = labyrinth.getHeight(); - coordinate = coordinate.withY(labyrinth.getHeight(), labyrinth); - for (int x = 0; x < labyrinth.getWidth(); x++) { - final Tile currentTile = labyrinth.getTileAt(x, y - 1); - coordinate = coordinate.withX(x); - if (currentTile.hasWallAt(Direction.BOTTOM)) { - if (!isPainting) { - for (final PDPageContentStream contentStream : contentStreams) { - contentStream.moveTo(coordinate.getX(), coordinate.getY()); + + private void drawVerticalLines(@NonNull final PDPageContentStream... contentStreams) throws IOException { + // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. + Coordinate coordinate = new Coordinate(0f, 0f); + for (int x = 0; x < this.labyrinth.getWidth(); x++) { + boolean isPainting = false; + coordinate = coordinate.withX(x); + for (int y = 0; y < this.labyrinth.getHeight(); y++) { + final Tile currentTile = this.labyrinth.getTileAt(x, y); + coordinate = coordinate.withY(y); + if (currentTile.hasWallAt(Direction.LEFT)) { + if (!isPainting) { + for (final PDPageContentStream contentStream : contentStreams) { + contentStream.moveTo(coordinate.getX(), coordinate.getY()); + } + isPainting = true; + } + } else { + if (isPainting) { + for (final PDPageContentStream contentStream : contentStreams) { + contentStream.lineTo(coordinate.getX(), coordinate.getY()); + contentStream.stroke(); + } + isPainting = false; + } } - isPainting = true; } - } else { if (isPainting) { + coordinate = coordinate.withY(this.labyrinth.getHeight()); for (final PDPageContentStream contentStream : contentStreams) { contentStream.lineTo(coordinate.getX(), coordinate.getY()); contentStream.stroke(); } - isPainting = false; } } - } - if (isPainting) { - coordinate = coordinate.withX(labyrinth.getWidth()); - for (final PDPageContentStream contentStream : contentStreams) { - contentStream.lineTo(coordinate.getX(), coordinate.getY()); - contentStream.stroke(); - } - } - } - - private void drawVerticalLines(@NonNull final Labyrinth labyrinth, - @NonNull final PDPageContentStream... contentStreams) throws IOException { - // PDF has the origin in the lower left corner. We want it in the upper left corner, hence some magic is required. - Coordinate coordinate = new Coordinate(0f, 0f); - final float labyrinthHeight = labyrinth.getHeight() * SCALE; - for (int x = 0; x < labyrinth.getWidth(); x++) { boolean isPainting = false; - coordinate = coordinate.withX(x); - for (int y = 0; y < labyrinth.getHeight(); y++) { - final Tile currentTile = labyrinth.getTileAt(x, y); - coordinate = coordinate.withY(y, labyrinth); - if (currentTile.hasWallAt(Direction.LEFT)) { + int x = this.labyrinth.getWidth(); + coordinate = coordinate.withX(this.labyrinth.getWidth()); + for (int y = 0; y < this.labyrinth.getHeight(); y++) { + final Tile currentTile = this.labyrinth.getTileAt(x - 1, y); + coordinate = coordinate.withY(y); + if (currentTile.hasWallAt(Direction.RIGHT)) { if (!isPainting) { for (final PDPageContentStream contentStream : contentStreams) { contentStream.moveTo(coordinate.getX(), coordinate.getY()); @@ -168,138 +208,94 @@ public class PDFRenderer implements Renderer { } } if (isPainting) { - coordinate = coordinate.withY(labyrinth.getHeight(), labyrinth); + coordinate = coordinate.withY(this.labyrinth.getHeight()); for (final PDPageContentStream contentStream : contentStreams) { contentStream.lineTo(coordinate.getX(), coordinate.getY()); contentStream.stroke(); } } } - boolean isPainting = false; - int x = labyrinth.getWidth(); - coordinate = coordinate.withX(labyrinth.getWidth()); - for (int y = 0; y < labyrinth.getHeight(); y++) { - final Tile currentTile = labyrinth.getTileAt(x - 1, y); - coordinate = coordinate.withY(y, labyrinth); - if (currentTile.hasWallAt(Direction.RIGHT)) { - if (!isPainting) { - for (final PDPageContentStream contentStream : contentStreams) { - contentStream.moveTo(coordinate.getX(), coordinate.getY()); + + private void drawSolution(@NonNull final PDPageContentStream pageContentStream) throws IOException { + // Draw the solution in red + pageContentStream.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 Position end = this.labyrinth.getEnd(); + Position currentPosition = this.labyrinth.getStart(); + Position previousPosition = null; + SolutionCoordinate coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY()); + pageContentStream.moveTo(coordinate.getX(), coordinate.getY()); + do { + Position newCurrent = this.findNextSolutionPosition(previousPosition, currentPosition); + previousPosition = currentPosition; + currentPosition = newCurrent; + coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY()); + pageContentStream.lineTo(coordinate.getX(), coordinate.getY()); + } while (!currentPosition.equals(end)); + pageContentStream.stroke(); + } + + @NonNull + private Position findNextSolutionPosition(@Nullable final Position previousPosition, @NonNull final Position currentPosition) { + final Tile currentTile = this.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; } - isPainting = true; - } - } else { - if (isPainting) { - for (final PDPageContentStream contentStream : contentStreams) { - contentStream.lineTo(coordinate.getX(), coordinate.getY()); - contentStream.stroke(); + if (this.labyrinth.getTileAt(position).isSolution()) { + return position; } - isPainting = false; } } + throw new IllegalStateException("We *SHOULD* never have gotten here. ... famous last words ..."); } - if (isPainting) { - coordinate = coordinate.withY(labyrinth.getHeight(), labyrinth); - for (final PDPageContentStream contentStream : contentStreams) { - contentStream.lineTo(coordinate.getX(), coordinate.getY()); - contentStream.stroke(); + + @Value + private class Coordinate { + float x; + float y; + + public Coordinate(final float x, final float y) { + this.x = x; + this.y = y; + } + + private float calcX(final int x) { + return x * SCALE + MARGIN; + } + + private float calcY(final int y) { + return (Generator.this.labyrinth.getHeight() - y) * SCALE + MARGIN; + } + + public Coordinate withX(final int x) { + return new Coordinate(calcX(x), this.y); + } + + public Coordinate withY(final int y) { + return new Coordinate(this.x, calcY(y)); } } - } - private void drawSolution(@NonNull final Labyrinth labyrinth, - @NonNull final PDPageContentStream pageContentStream) throws IOException { - // Draw the solution in red - pageContentStream.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 Position end = labyrinth.getEnd(); - Position currentPosition = labyrinth.getStart(); - Position previousPosition = null; - SolutionCoordinate coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY(), labyrinth); - pageContentStream.moveTo(coordinate.getX(), coordinate.getY()); - do { - Position newCurrent = this.findNextSolutionPosition(labyrinth, previousPosition, currentPosition); - previousPosition = currentPosition; - currentPosition = newCurrent; - coordinate = new SolutionCoordinate(currentPosition.getX(), currentPosition.getY(), labyrinth); - pageContentStream.lineTo(coordinate.getX(), coordinate.getY()); - } while (!currentPosition.equals(end)); - pageContentStream.stroke(); - } + @Value + private class SolutionCoordinate { + float x; + float y; - @NonNull - 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; - } + public SolutionCoordinate(final int x, final int y) { + this.x = calcX(x); + this.y = calcY(y); } - } - throw new IllegalStateException("We *SHOULD* never have gotten here. ... famous last words ..."); - } - @Value - private static class Coordinate { - float x; - float y; + private float calcX(final int x) { + return x * SCALE + SCALE / 2 + MARGIN; + } - public Coordinate(final float x, final float y) { - this.x = x; - this.y = y; - } - - private static float calcX(final int x) { - return x * SCALE + MARGIN; - } - - private static float calcY(final int y, @NonNull final Labyrinth labyrinth) { - return (labyrinth.getHeight() - y) * SCALE + MARGIN; - } - - public Coordinate withX(final int x) { - return new Coordinate(calcX(x), this.y); - } - - public Coordinate withY(final int y, @NonNull final Labyrinth labyrinth) { - return new Coordinate(this.x, calcY(y, labyrinth)); - } - } - - @Value - private static class SolutionCoordinate { - float x; - float y; - - public SolutionCoordinate(final float x, final float y) { - this.x = x; - this.y = y; - } - - public SolutionCoordinate(final int x, final int y, @NonNull final Labyrinth labyrinth) { - this.x = calcX(x); - this.y = calcY(y, labyrinth); - } - - private static float calcX(final int x) { - return x * SCALE + SCALE / 2 + MARGIN; - } - - private static float calcY(final int y, @NonNull final Labyrinth labyrinth) { - return (labyrinth.getHeight() - y) * SCALE - SCALE / 2 + MARGIN; - } - - public SolutionCoordinate withX(final int x) { - return new SolutionCoordinate(calcX(x), this.y); - } - - public SolutionCoordinate withY(final int y, @NonNull final Labyrinth labyrinth) { - return new SolutionCoordinate(this.x, calcY(y, labyrinth)); + private float calcY(final int y) { + return (Generator.this.labyrinth.getHeight() - y) * SCALE - SCALE / 2 + MARGIN; + } } } }