Implement SVG export

This commit is contained in:
2026-03-19 19:06:12 +01:00
parent ad45eddbf5
commit b8ecf9859b
5 changed files with 130 additions and 4 deletions

View File

@@ -15,7 +15,6 @@ import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.util.Optional;
public class App { public class App {
private final static Logger logger = LoggerFactory.getLogger(App.class); private final static Logger logger = LoggerFactory.getLogger(App.class);
@@ -82,6 +81,7 @@ public class App {
JMenuItem addRectItem = new JMenuItem("Add SRectangle"); JMenuItem addRectItem = new JMenuItem("Add SRectangle");
JMenuItem addCircleItem = new JMenuItem("Add SCircle"); JMenuItem addCircleItem = new JMenuItem("Add SCircle");
JMenuItem htmlExportItem = new JMenuItem("Export to HTML"); JMenuItem htmlExportItem = new JMenuItem("Export to HTML");
JMenuItem svgExportItem = new JMenuItem("Export to SVG");
JMenuItem exitItem = new JMenuItem("Exit"); JMenuItem exitItem = new JMenuItem("Exit");
addRectItem.addActionListener(new MenuAddListener("SRectangle", model, sview)); addRectItem.addActionListener(new MenuAddListener("SRectangle", model, sview));
addCircleItem.addActionListener(new MenuAddListener("SCircle", model, sview)); addCircleItem.addActionListener(new MenuAddListener("SCircle", model, sview));
@@ -92,11 +92,19 @@ public class App {
logger.error("Could not export as html: {}", e.getMessage()); logger.error("Could not export as html: {}", e.getMessage());
} }
}); });
svgExportItem.addActionListener(evt -> {
try {
new SVGExporter(model).export();
} catch (FileNotFoundException e) {
logger.error("Could not export as html: {}", e.getMessage());
}
});
exitItem.addActionListener(evt -> System.exit(0)); exitItem.addActionListener(evt -> System.exit(0));
menuFile.add(addRectItem); menuFile.add(addRectItem);
menuFile.add(addCircleItem); menuFile.add(addCircleItem);
menuFile.addSeparator(); menuFile.addSeparator();
menuFile.add(htmlExportItem); menuFile.add(htmlExportItem);
menuFile.add(svgExportItem);
menuFile.add(exitItem); menuFile.add(exitItem);
return menuFile; return menuFile;
} }

View File

@@ -2,7 +2,7 @@ package ovh.gasser.newshapes;
import ovh.gasser.newshapes.shapes.SCollection; import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SRectangle; import ovh.gasser.newshapes.shapes.SRectangle;
import ovh.gasser.newshapes.ui.html.*; import ovh.gasser.newshapes.ui.visitors.*;
import java.awt.*; import java.awt.*;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -12,7 +12,7 @@ public class HTMLExporter {
private final SCollection model; private final SCollection model;
private HTMLExporter() throws FileNotFoundException { private HTMLExporter() {
this(SCollection.of( this(SCollection.of(
SRectangle.create(10, 10, 40, 60, Color.RED), SRectangle.create(10, 10, 40, 60, Color.RED),
SRectangle.create(70, 10, 40, 60), SRectangle.create(70, 10, 40, 60),

View File

@@ -0,0 +1,36 @@
package ovh.gasser.newshapes;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.ui.visitors.SVGDraftman;
import java.awt.*;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
public class SVGExporter {
private final SCollection model;
public SVGExporter() {
SCircle circle = SCircle.create(200, 100, 50);
circle.addAttributes(new ColorAttributes(true, true, Color.LIGHT_GRAY, Color.BLACK));
this.model = SCollection.of(circle);
}
public SVGExporter(SCollection model) {
this.model = model;
}
public void export() throws FileNotFoundException {
try (final PrintWriter svg = new PrintWriter("out.svg")) {
SVGDraftman draftman = new SVGDraftman(svg);
draftman.generateSVG(this.model);
}
}
public static void main(String[] args) throws FileNotFoundException {
new SVGExporter().export();
}
}

View File

@@ -1,4 +1,4 @@
package ovh.gasser.newshapes.ui.html; package ovh.gasser.newshapes.ui.visitors;
import ovh.gasser.newshapes.ShapeVisitor; import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.attributes.ColorAttributes; import ovh.gasser.newshapes.attributes.ColorAttributes;

View File

@@ -0,0 +1,82 @@
package ovh.gasser.newshapes.ui.visitors;
import ovh.gasser.newshapes.App;
import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SRectangle;
import java.awt.*;
import java.io.PrintWriter;
public class SVGDraftman implements ShapeVisitor {
private static final String SVG_PRELUDE = """
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="%s" height="%s">
""";
private static final String SVG_POSTLUDE = "</svg>";
private final PrintWriter output;
public SVGDraftman(PrintWriter output) {
this.output = output;
}
@Override
public void visitRectangle(SRectangle sRectangle) {
int x = sRectangle.getBounds().x;
int y = sRectangle.getBounds().y;
int w = sRectangle.getBounds().width;
int h = sRectangle.getBounds().height;
ColorAttributes attrs = (ColorAttributes) sRectangle.getAttributes(ColorAttributes.ID);
this.output.println(String.format("""
<rect width="%d" height="%d" x="%d" y="%d" %s />""",
w, h, x, y, buildColorParameters(attrs)));
}
@Override
public void visitCollection(SCollection collection) {
collection.stream().forEach(shape -> shape.accept(this));
}
@Override
public void visitCircle(SCircle sCircle) {
int r = sCircle.getRadius();
int x = sCircle.getBounds().x + r;
int y = sCircle.getBounds().y + r;
var attrs = (ColorAttributes) sCircle.getAttributes(ColorAttributes.ID);
this.output.println(String.format("""
<circle cx="%d" cy="%d" r="%d" %s />""",
x, y, r, buildColorParameters(attrs)));
}
public void generateSVG(SCollection model) {
output.println(String.format(SVG_PRELUDE, App.WIN_SIZE.width, App.WIN_SIZE.height));
visitCollection(model);
output.println(SVG_POSTLUDE);
}
private String colorToHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
return String.format("#%02x%02x%02x", r, g, b);
}
private StringBuilder buildColorParameters(ColorAttributes attrs){
var params = new StringBuilder();
if (attrs.stroked) {
params.append(String.format("stroke=\"%s\" stroke-width=\"1\"", colorToHex(attrs.strokedColor)));
params.append(" ");
}
if (attrs.filled) {
params.append(String.format("fill=\"%s\"", colorToHex(attrs.filledColor)));
params.append(" ");
} else {
params.append(String.format("fill=\"%s\"", "none"));
params.append(" ");
}
return params;
}
}