ra4king / CircuitSim

Basic Circuit Simulator
https://ra4king.github.io/CircuitSim
BSD 3-Clause "New" or "Revised" License
76 stars 27 forks source link

Horrifying hacks for printing from CircuitSim #78

Closed ausbin closed 1 year ago

ausbin commented 1 year ago

I covered 2110 lecture today and did the entire lecture in CircuitSim. (It worked great!)

I didn't want students to copypasta directly from the lecture "notes" .sim file, and taking screenshots of each circuit is a little gross, so I did something even more gross and added support for printing from CircuitSim. So I can print to a .ps file and then use ps2pdf to convert that to a PDF.

This is in a horribly messy state and should never be merged as-is, but I wanted to check if this sounds useful, Roi, before I cleaned it up

Here is the result: 2022-09-13.pdf

ausbin commented 1 year ago

I forgot to say: for this to work, it seems you have to click all the per-Circuit tabs to actually get them to initially paint their Canvases. And of course I am hardcoding making the background white which is funny

ausbin commented 1 year ago

This produces a pretty pixelated PDF on my system, so as a workaround, I figured out a way to export each circuit as a PNG and them stitch them together into a PDF with ImageMagick. The patch to do that is beyond horrific considering it pulls in AWT and Swing, but I couldn't find a better way to save a Node to a PNG file in JavaFX. Here it is, relative to this PR (it's so bad I can't even make it a meme PR like this one, but I'll drop it here in case it's useful for someone someday):

diff --git a/build.gradle b/build.gradle
index 133f6d6..b8dcdd4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -35,6 +35,7 @@ dependencies {
     implementation "org.openjfx:javafx-base:14.0.2.1:${platform}"
     implementation "org.openjfx:javafx-controls:14.0.2.1:${platform}"
     implementation "org.openjfx:javafx-graphics:14.0.2.1:${platform}"
+    implementation "org.openjfx:javafx-swing:14.0.2.1:${platform}"
 }

 jar {
diff --git a/src/main/java/com/ra4king/circuitsim/gui/CircuitSim.java b/src/main/java/com/ra4king/circuitsim/gui/CircuitSim.java
index 967383b..e365e0f 100644
--- a/src/main/java/com/ra4king/circuitsim/gui/CircuitSim.java
+++ b/src/main/java/com/ra4king/circuitsim/gui/CircuitSim.java
@@ -1,5 +1,6 @@
 package com.ra4king.circuitsim.gui;

+import java.awt.image.RenderedImage;
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -35,6 +36,7 @@ import java.util.jar.JarFile;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
+import javax.imageio.ImageIO;

 import com.google.gson.JsonSyntaxException;
 import com.ra4king.circuitsim.gui.ComponentManager.ComponentCreator;
@@ -63,6 +65,7 @@ import javafx.animation.AnimationTimer;
 import javafx.application.Application;
 import javafx.application.Platform;
 import javafx.collections.ObservableList;
+import javafx.embed.swing.SwingFXUtils;
 import javafx.geometry.Orientation;
 import javafx.geometry.Pos;
 import javafx.geometry.Side;
@@ -2311,6 +2314,29 @@ public class CircuitSim extends Application {
            }
            System.err.println("Done job");
        });
+
+       MenuItem exportImages = new MenuItem("Export Images");
+       exportImages.setOnAction(event -> {
+           circuitManagers.values()
+                          .stream()
+                          .map(Pair::getValue)
+                          .forEach(circuitManager -> {
+                              String name = circuitManager.getName();
+                              Canvas canvas = circuitManager.getCanvas();
+                              Image image = canvas.snapshot(null, null);
+                              RenderedImage rendered = SwingFXUtils.fromFXImage(image, null);
+
+                              ObservableList<Tab> tabs = canvasTabPane.getTabs();
+                              int idx = IntStream.range(0, tabs.size()).filter(ix -> tabs.get(ix).getText().equals(name)).findFirst().orElse(-1);
+                              String fileName = String.format("%02d-%s.png", idx, name);
+                              try {
+                                  ImageIO.write(rendered, "png", new File(fileName));
+                              } catch (IOException e) {
+                                  System.err.println("Error writing " + fileName + ":");
+                                  e.printStackTrace();
+                              }
+                          });
+       });

        MenuItem exit = new MenuItem("Exit");
        exit.setOnAction(event -> {
@@ -2324,7 +2350,7 @@ public class CircuitSim extends Application {
            .getItems()
            .addAll(newInstance, clear, new SeparatorMenuItem(),
                    load, save, saveAs, new SeparatorMenuItem(),
-                   print, new SeparatorMenuItem(),
+                   print, exportImages, new SeparatorMenuItem(),
                    exit);

        // EDIT Menu
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index f0ca04e..86c7dc4 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,5 +1,7 @@
 module Project.com.ra4king.circuitsim.gui {
+   requires java.desktop;
    requires javafx.controls;
+   requires javafx.swing;
    requires com.google.gson;
    exports com.ra4king.circuitsim.gui;
 }

This adds File -> Export Images which creates a PNG file in the current working directory for each circuit. Then the ImageMagick command to stitch together all the per-circuit pngs into beauty.pdf is:

$ convert *.png -trim -bordercolor white -border 32 +repage beauty.pdf

Here's the result: 2022-09-13.pdf

ra4king commented 1 year ago

How is this easier than screenshots? 😆

This code is too horrific to be checked in. I do like the idea of adding a printing mechanism though, and I've thought about it before but I didn't know it was this easy to do (JavaFX has a PrintJob?!) I'll play around with this.

ra4king commented 1 year ago

The JavaFX PrinterJob doesn't work well for me at all. It leads to all sorts of internal JavaFX rendering engine exceptions. The ImageIO mechanism worked really well, so I'm going to go that route. I'll add a dialog to select the file names and everything. Thanks!

ausbin commented 1 year ago

Appreciate you taking a look at this horrifying stuff sir. Don't want to make a habit of dumping low-quality code on you

ra4king commented 1 year ago

Submitted my own fix for this, thanks!