diff --git a/src/main/java/de/neemann/digital/draw/elements/Circuit.java b/src/main/java/de/neemann/digital/draw/elements/Circuit.java
index a309ba8ef..740c78750 100644
--- a/src/main/java/de/neemann/digital/draw/elements/Circuit.java
+++ b/src/main/java/de/neemann/digital/draw/elements/Circuit.java
@@ -99,10 +99,15 @@ public class Circuit implements Drawable {
dotsPresent = true;
}
+ graphic.openGroup();
for (Wire w : wires)
w.drawTo(graphic);
- for (VisualElement p : visualElements)
+ graphic.closeGroup();
+ for (VisualElement p : visualElements) {
+ graphic.openGroup();
p.drawTo(graphic);
+ graphic.closeGroup();
+ }
}
public void add(VisualElement visualElement) {
diff --git a/src/main/java/de/neemann/digital/draw/graphics/Exporter.java b/src/main/java/de/neemann/digital/draw/graphics/Exporter.java
new file mode 100644
index 000000000..bb6d802ee
--- /dev/null
+++ b/src/main/java/de/neemann/digital/draw/graphics/Exporter.java
@@ -0,0 +1,11 @@
+package de.neemann.digital.draw.graphics;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author hneemann
+ */
+public interface Exporter {
+ Graphic create(File file, Vector min, Vector max) throws IOException;
+}
diff --git a/src/main/java/de/neemann/digital/draw/graphics/Graphic.java b/src/main/java/de/neemann/digital/draw/graphics/Graphic.java
index 2c90e86ea..fe876f769 100644
--- a/src/main/java/de/neemann/digital/draw/graphics/Graphic.java
+++ b/src/main/java/de/neemann/digital/draw/graphics/Graphic.java
@@ -12,4 +12,12 @@ public interface Graphic {
void drawCircle(Vector p1, Vector p2, Style style);
void drawText(Vector p1, Vector p2, String text, Orientation orientation, Style style);
+
+ default void openGroup() {
+ }
+
+ default void closeGroup() {
+ }
+
+ ;
}
diff --git a/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java b/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java
new file mode 100644
index 000000000..cd9ea1bab
--- /dev/null
+++ b/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java
@@ -0,0 +1,212 @@
+package de.neemann.digital.draw.graphics;
+
+import java.io.*;
+import java.util.Date;
+
+/**
+ * @author hneemann
+ */
+public class GraphicSVG implements Graphic, Closeable {
+ private static final int TEXTSIZE = 20;
+ private static final int DEF_SCALE = 100;
+ private final BufferedWriter w;
+
+ public GraphicSVG(File file, Vector min, Vector max) throws IOException {
+ this(file, min, max, null, DEF_SCALE);
+ }
+
+ public GraphicSVG(File file, Vector min, Vector max, File source) throws IOException {
+ this(file, min, max, source, DEF_SCALE);
+ }
+
+ public GraphicSVG(File file, Vector min, Vector max, File source, int svgScale) throws IOException {
+ w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf-8"));
+ w.write("\n" +
+ "\n");
+ w.write("\n");
+ if (source != null) {
+ w.write("\n");
+ }
+ w.write("\n" +
+ "");
+ w.close();
+ }
+
+ @Override
+ public void drawLine(Vector p1, Vector p2, Style style) {
+ try {
+ w.write("\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void drawPolygon(Polygon p, Style style) {
+ try {
+ w.write("\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static double getStrokeWidth(Style style) {
+ return style.getThickness() * 0.7;
+ }
+
+ @Override
+ public void drawCircle(Vector p1, Vector p2, Style style) {
+ try {
+ Vector c = p1.add(p2).div(2);
+ double r = Math.abs(p2.sub(p1).x) / 2;
+ if (style.isFilled())
+ w.write("\n");
+ else {
+ w.write("\n");
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void drawText(Vector p1, Vector p2, String text, Orientation orientation, Style style) {
+ if (text != null && text.length() > 0)
+ try {
+ text = escapeXML(text);
+
+ boolean rotateText = false;
+ if (p1.y == p2.y) { // 0 and 180 deg
+ if (p1.x > p2.x) // 180
+ orientation = orientation.rot(2);
+ } else {
+ if (p1.y < p2.y) // 270
+ orientation = orientation.rot(2);
+ else // 90
+ orientation = orientation.rot(0);
+ rotateText = true;
+ }
+
+ Vector p = new Vector(p1);
+ switch (orientation.getY()) {
+ case 1:
+ p = p.add(0, style.getFontSize() / 2 - style.getFontSize() / 8);
+ break;
+ case 2:
+ p = p.add(0, style.getFontSize() * 3 / 4);
+ break;
+ case 0:
+ //p = p.add(0, -style.getFontSize() / 4);
+ break;
+ }
+
+ if (rotateText)
+ w.write("" + text + "\n");
+ else
+ w.write("" + text + "\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private String escapeXML(String text) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ switch (c) {
+ case '&':
+ sb.append("&");
+ break;
+ case '<':
+ sb.append("<");
+ break;
+ case '>':
+ sb.append(">");
+ break;
+ case '"':
+ sb.append(""");
+ break;
+ default:
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String getAchor(int x) {
+ switch (x) {
+ case 1:
+ return "middle";
+ case 2:
+ return "end";
+ default:
+ return "start";
+ }
+ }
+
+
+ @Override
+ public void openGroup() {
+ try {
+ w.write("\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void closeGroup() {
+ try {
+ w.write("\n");
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ private static void addStrokeDash(Writer w, int[] dashArray) throws IOException {
+ w.write(" stroke-dasharray=\"");
+ for (int i = 0; i < dashArray.length; i++) {
+ if (i != 0) w.write(',');
+ w.write(Integer.toString(dashArray[i]));
+ }
+ w.write('"');
+ }
+
+ private String str(Vector p) {
+ return p.x + "," + p.y;
+ }
+
+}
diff --git a/src/main/java/de/neemann/digital/draw/graphics/Polygon.java b/src/main/java/de/neemann/digital/draw/graphics/Polygon.java
index cbad97013..108877d10 100644
--- a/src/main/java/de/neemann/digital/draw/graphics/Polygon.java
+++ b/src/main/java/de/neemann/digital/draw/graphics/Polygon.java
@@ -43,4 +43,13 @@ public class Polygon {
public ArrayList getPoints() {
return points;
}
+
+
+ public Vector get(int i) {
+ return points.get(i);
+ }
+
+ public int size() {
+ return points.size();
+ }
}
diff --git a/src/main/java/de/neemann/digital/gui/Main.java b/src/main/java/de/neemann/digital/gui/Main.java
index 0a2f7dd34..6d37097d2 100644
--- a/src/main/java/de/neemann/digital/gui/Main.java
+++ b/src/main/java/de/neemann/digital/gui/Main.java
@@ -8,6 +8,10 @@ import de.neemann.digital.core.wiring.Clock;
import de.neemann.digital.draw.elements.Circuit;
import de.neemann.digital.draw.elements.PinException;
import de.neemann.digital.draw.elements.PinOrder;
+import de.neemann.digital.draw.graphics.Exporter;
+import de.neemann.digital.draw.graphics.Graphic;
+import de.neemann.digital.draw.graphics.GraphicMinMax;
+import de.neemann.digital.draw.graphics.GraphicSVG;
import de.neemann.digital.draw.library.ElementLibrary;
import de.neemann.digital.draw.model.ModelDescription;
import de.neemann.digital.draw.model.RealTimeClock;
@@ -21,6 +25,7 @@ import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.prefs.Preferences;
@@ -151,6 +156,11 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave {
}
};
+
+ JMenu export = new JMenu(Lang.get("menu_export"));
+ export.add(new ExportAction(Lang.get("menu_exportSVG"), "svg", GraphicSVG::new));
+
+
JMenu file = new JMenu(Lang.get("menu_file"));
bar.add(file);
file.add(newFile);
@@ -158,6 +168,7 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave {
file.add(openWin);
file.add(save);
file.add(saveas);
+ file.add(export);
JMenu edit = new JMenu(Lang.get("menu_edit"));
bar.add(edit);
@@ -483,4 +494,48 @@ public class Main extends JFrame implements ClosingWindowListener.ConfirmSave {
doStep.setEnabled(model.needsUpdate());
}
}
+
+ private class ExportAction extends ToolTipAction {
+ private final String name;
+ private final String suffix;
+ private final Exporter exporter;
+
+ public ExportAction(String name, String suffix, Exporter exporter) {
+ super(name);
+ this.name = name;
+ this.suffix = suffix;
+ this.exporter = exporter;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JFileChooser fc = new JFileChooser();
+ if (filename != null) {
+ String name = filename.getName();
+ int p = name.lastIndexOf('.');
+ if (p >= 0)
+ name = name.substring(0, p);
+ File f = new File(filename.getParentFile(), name + "." + suffix);
+ fc.setSelectedFile(f);
+ }
+ fc.addChoosableFileFilter(new FileNameExtensionFilter(name, suffix));
+ if (fc.showSaveDialog(Main.this) == JFileChooser.APPROVE_OPTION) {
+ Circuit circuit = circuitComponent.getCircuit();
+ GraphicMinMax minMax = new GraphicMinMax();
+ circuit.drawTo(minMax);
+ try {
+ Graphic gr = null;
+ try {
+ gr = exporter.create(fc.getSelectedFile(), minMax.getMin(), minMax.getMax());
+ circuit.drawTo(gr);
+ } finally {
+ if (gr instanceof Closeable)
+ ((Closeable) gr).close();
+ }
+ } catch (IOException e1) {
+ new ErrorMessage(Lang.get("msg_errorWritingFile")).addCause(e1).show(Main.this);
+ }
+ }
+ }
+ }
}
diff --git a/src/main/resources/lang/lang_de.properties b/src/main/resources/lang/lang_de.properties
index a6e12d7ec..513a78dd1 100644
--- a/src/main/resources/lang/lang_de.properties
+++ b/src/main/resources/lang/lang_de.properties
@@ -123,7 +123,8 @@ menu_editAttributes=Model Attribute Bearbeiten
menu_editAttributes_tt=Diese Attribute beeinflussen das Modell, wenn es in andere Modelle eingebettet wird.
menu_fast=Schneller Lauf
menu_fast_tt=F\u00FChrt das Modell aus, bis ein Stopsignal \u00FCber ein BRK-Element detektiert wird.
-
+menu_export=Export
+menu_exportSVG=Export SVG
menu_about=\u00DCber Digital
win_saveChanges=Ver\u00E4nderungen speichern?
diff --git a/src/main/resources/lang/lang_en.properties b/src/main/resources/lang/lang_en.properties
index 2c0f907f7..f4cb41f9d 100644
--- a/src/main/resources/lang/lang_en.properties
+++ b/src/main/resources/lang/lang_en.properties
@@ -124,6 +124,8 @@ menu_editAttributes=Edit Models Attributes
menu_editAttributes_tt=These attributes effect the behavior is the model is included in other models.
menu_fast=Run Fast
menu_fast_tt=Runs the model until a break is detected by the BRK element.
+menu_export=Export
+menu_exportSVG=Export SVG
win_saveChanges=Save Changes?