diff --git a/pom.xml b/pom.xml index aa350a3..8c063a5 100644 --- a/pom.xml +++ b/pom.xml @@ -25,6 +25,11 @@ io.vavr vavr + + org.apache.pdfbox + pdfbox + 2.0.20 + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/ch/fritteli/labyrinth/Main.java b/src/main/java/ch/fritteli/labyrinth/Main.java index ff3821a..e760bb7 100644 --- a/src/main/java/ch/fritteli/labyrinth/Main.java +++ b/src/main/java/ch/fritteli/labyrinth/Main.java @@ -7,8 +7,8 @@ import java.nio.file.Paths; public class Main { public static void main(@NonNull final String[] args) { - int width = 80; - int height = 40; + int width = 20; + int height = 20; final Labyrinth labyrinth = new Labyrinth(width, height); final TextRenderer textRenderer = TextRenderer.newInstance(); final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); @@ -18,6 +18,8 @@ public class Main { .setTargetSolutionFile(userHome.resolve("labyrinth-solution.txt")); final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance() .setTargetFile(userHome.resolve("labyrinth.html")); + final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance().setTargetFile(userHome.resolve("labyrinth.pdf")); + // Render Labyrinth to stdout System.out.println(textRenderer.render(labyrinth)); // Render Labyrinth solution to stdout @@ -28,5 +30,7 @@ public class Main { System.out.println(textFileRenderer.render(labyrinth)); // Render HTML to file System.out.println(htmlFileRenderer.render(labyrinth)); + // Render PDF to file + System.out.println(pdfFileRenderer.render(labyrinth)); } } diff --git a/src/main/java/ch/fritteli/labyrinth/PDFFileRenderer.java b/src/main/java/ch/fritteli/labyrinth/PDFFileRenderer.java new file mode 100644 index 0000000..7772f9b --- /dev/null +++ b/src/main/java/ch/fritteli/labyrinth/PDFFileRenderer.java @@ -0,0 +1,56 @@ +package ch.fritteli.labyrinth; + +import lombok.NonNull; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class PDFFileRenderer implements Renderer { + @NonNull + private static final PDFRenderer PDF_RENDERER = PDFRenderer.newInstance(); + private Path targetFile; + + private PDFFileRenderer() { + try { + this.targetFile = Files.createTempFile("labyrinth_", ".pdf"); + } catch (IOException e) { + System.err.println("Unable to set default target file."); + e.printStackTrace(); + } + } + + @NonNull + public static PDFFileRenderer newInstance() { + return new PDFFileRenderer(); + } + + public boolean isTargetFileDefinedAndWritable() { + return this.targetFile != null && this.targetFile.toFile().canWrite(); + } + + @NonNull + public PDFFileRenderer setTargetFile(@NonNull final Path targetFile) { + this.targetFile = targetFile; + return this; + } + + @Override + public Path render(@NonNull final Labyrinth labyrinth) { + if (!this.isTargetFileDefinedAndWritable()) { + try { + Files.createFile(this.targetFile); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot write to target file.", e); + } + } + final byte[] bytes = PDF_RENDERER.render(labyrinth); + try { + Files.write(this.targetFile, bytes); + } catch (IOException e) { + System.err.println("Failed writing to file " + this.targetFile.normalize().toString()); + e.printStackTrace(); + } + return this.targetFile; + } +} diff --git a/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java b/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java new file mode 100644 index 0000000..abce7a9 --- /dev/null +++ b/src/main/java/ch/fritteli/labyrinth/PDFRenderer.java @@ -0,0 +1,142 @@ +package ch.fritteli.labyrinth; + +import lombok.NonNull; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; + +import java.awt.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class PDFRenderer implements Renderer { + private PDFRenderer() { + + } + + @NonNull + public static PDFRenderer newInstance() { + return new PDFRenderer(); + } + + @Override + @NonNull + public byte[] render(@NonNull final Labyrinth labyrinth) { + final PDDocument pdDocument = new PDDocument(); + final PDPage page = new PDPage(); + pdDocument.addPage(page); + try (PDPageContentStream pdPageContentStream = new PDPageContentStream(pdDocument, page)) { + pdPageContentStream.setLineCapStyle(BasicStroke.CAP_BUTT); + pdPageContentStream.setLineJoinStyle(BasicStroke.JOIN_MITER); + pdPageContentStream.setLineWidth(1.0f); + pdPageContentStream.setStrokingColor(Color.BLACK); + pdPageContentStream.setNonStrokingColor(Color.BLACK); + final float scale = 10; + final float margin = 5; + this.drawHorizonzalLines(labyrinth, pdPageContentStream, margin, scale); + this.drawVerticalLines(labyrinth, pdPageContentStream, margin, scale); + } 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 drawHorizonzalLines(@NonNull final Labyrinth labyrinth, @NonNull final PDPageContentStream pdPageContentStream, final float margin, final float scale) throws IOException { + 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, y * scale + margin); + isPainting = true; + } + } else { + if (isPainting) { + pdPageContentStream.lineTo(x * scale + margin, y * scale + margin); + pdPageContentStream.stroke(); + isPainting = false; + } + } + } + if (isPainting) { + pdPageContentStream.lineTo(labyrinth.getWidth() * scale + margin, y * scale + margin); + pdPageContentStream.stroke(); + } + } + boolean isPainting = false; + int y = labyrinth.getHeight(); + for (int x = 0; x < labyrinth.getWidth(); x++) { + final Tile currentTile = labyrinth.getTileAt(x, y - 1); + if (currentTile.hasWallAt(Direction.BOTTOM)) { + if (!isPainting) { + pdPageContentStream.moveTo(x * scale + margin, y * scale + margin); + isPainting = true; + } + } else { + if (isPainting) { + pdPageContentStream.lineTo(x * scale + margin, y * scale + margin); + pdPageContentStream.stroke(); + isPainting = false; + } + } + } + if (isPainting) { + pdPageContentStream.lineTo(labyrinth.getWidth() * scale + margin, 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 { + 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, y * scale + margin); + isPainting = true; + } + } else { + if (isPainting) { + pdPageContentStream.lineTo(x * scale + margin, y * scale + margin); + pdPageContentStream.stroke(); + isPainting = false; + } + } + } + if (isPainting) { + pdPageContentStream.lineTo(x * scale + margin, labyrinth.getHeight() * scale + margin); + pdPageContentStream.stroke(); + } + } + boolean isPainting = false; + int x = labyrinth.getWidth(); + for (int y = 0; y < labyrinth.getHeight(); y++) { + final Tile currentTile = labyrinth.getTileAt(x - 1, y); + if (currentTile.hasWallAt(Direction.RIGHT)) { + if (!isPainting) { + pdPageContentStream.moveTo(x * scale + margin, y * scale + margin); + isPainting = true; + } + } else { + if (isPainting) { + pdPageContentStream.lineTo(x * scale + margin, y * scale + margin); + pdPageContentStream.stroke(); + isPainting = false; + } + } + } + if (isPainting) { + pdPageContentStream.lineTo(labyrinth.getWidth() * scale + margin, labyrinth.getHeight() * scale + margin); + pdPageContentStream.stroke(); + } + } +}