mirror of
https://github.com/hneemann/Digital.git
synced 2025-09-17 17:04:42 -04:00
added a SVG importer, closes #39
This commit is contained in:
commit
c3b43b753a
@ -2,6 +2,7 @@ Release Notes
|
||||
|
||||
HEAD, planned as v0.21
|
||||
|
||||
- Added a simple SVG importer to define custom shapes.
|
||||
- Added an FSM editor, which allows to input a fsm, creating a associated
|
||||
truth table and finally allows to create a circuit which implements the FSM.
|
||||
- Added a divider component.
|
||||
|
@ -4,7 +4,7 @@
|
||||
<attributes>
|
||||
<entry>
|
||||
<string>shapeType</string>
|
||||
<de.neemann.digital.draw.shapes.CustomCircuitShapeType>CUSTOM</de.neemann.digital.draw.shapes.CustomCircuitShapeType>
|
||||
<shapeType>CUSTOM</shapeType>
|
||||
</entry>
|
||||
<entry>
|
||||
<string>Description</string>
|
||||
@ -85,7 +85,7 @@ bei der Speicheradressierung verwendet.}}</string>
|
||||
</pins>
|
||||
<drawables>
|
||||
<poly>
|
||||
<poly path="M 0.0 -30.0 L 80.0 10.0 L 80.0 90.0 L 0.0 130.0 L 0.0 65.0 L 30.0 50.0 L 0.0 35.0 z"/>
|
||||
<poly path="M 0,-30 L 80,10 L 80,90 L 0,130 L 0,65 L 30,50 L 0,35 Z" evenOdd="false"/>
|
||||
<thickness>1</thickness>
|
||||
<filled>true</filled>
|
||||
<color>
|
||||
@ -96,7 +96,7 @@ bei der Speicheradressierung verwendet.}}</string>
|
||||
</color>
|
||||
</poly>
|
||||
<poly>
|
||||
<poly path="M 0.0 -30.0 L 80.0 10.0 L 80.0 90.0 L 0.0 130.0 L 0.0 65.0 L 30.0 50.0 L 0.0 35.0 z"/>
|
||||
<poly reference="../../poly/poly"/>
|
||||
<thickness>4</thickness>
|
||||
<filled>false</filled>
|
||||
<color>
|
||||
|
@ -57,8 +57,7 @@ public class GraphicMinMax implements Graphic {
|
||||
|
||||
@Override
|
||||
public void drawPolygon(Polygon p, Style style) {
|
||||
for (VectorInterface v : p)
|
||||
check(v);
|
||||
p.traverse(this::check);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -6,10 +6,9 @@
|
||||
package de.neemann.digital.draw.graphics;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
import static java.lang.System.out;
|
||||
|
||||
/**
|
||||
* Used to create a SVG representation of the circuit.
|
||||
* Don't use this implementation directly. Use {@link GraphicSVGIndex} to create plain SVG or
|
||||
@ -61,7 +60,7 @@ public class GraphicSVG implements Graphic {
|
||||
@Override
|
||||
public Graphic setBoundingBox(VectorInterface min, VectorInterface max) {
|
||||
try {
|
||||
w = new BufferedWriter(new OutputStreamWriter(out, "utf-8"));
|
||||
w = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
|
||||
w.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
|
||||
+ "<!-- Created with Digital by H.Neemann -->\n");
|
||||
w.write("<!-- created: " + new Date() + " -->\n");
|
||||
@ -113,22 +112,11 @@ public class GraphicSVG implements Graphic {
|
||||
@Override
|
||||
public void drawPolygon(Polygon p, Style style) {
|
||||
try {
|
||||
//modification of loop variable i is intended!
|
||||
//CHECKSTYLE.OFF: ModifiedControlVariable
|
||||
w.write("<path d=\"M " + str(p.get(0)));
|
||||
for (int i = 1; i < p.size(); i++)
|
||||
if (p.isBezierStart(i)) {
|
||||
w.write(" C " + str(p.get(i)) + " " + str(p.get(i + 1)) + " " + str(p.get(i + 2)));
|
||||
i += 2;
|
||||
} else
|
||||
w.write(" L " + str(p.get(i)));
|
||||
//CHECKSTYLE.ON: ModifiedControlVariable
|
||||
|
||||
if (p.isClosed())
|
||||
w.write(" Z");
|
||||
|
||||
w.write("\"");
|
||||
w.write("<path d=\"" + p + "\"");
|
||||
addStrokeDash(w, style.getDash());
|
||||
if (p.getEvenOdd() && style.isFilled())
|
||||
w.write(" fill-rule=\"evenodd\"");
|
||||
|
||||
if (style.isFilled() && p.isClosed())
|
||||
w.write(" stroke=\"" + getColor(style) + "\" stroke-width=\"" + getStrokeWidth(style) + "\" fill=\"" + getColor(style) + "\" fill-opacity=\"" + getOpacity(style) + "\"/>\n");
|
||||
else
|
||||
|
@ -64,25 +64,7 @@ public class GraphicSwing implements Graphic {
|
||||
public void drawPolygon(Polygon p, Style style) {
|
||||
applyStyle(style);
|
||||
Path2D path = new GeneralPath();
|
||||
//modification of loop variable i is intended!
|
||||
//CHECKSTYLE.OFF: ModifiedControlVariable
|
||||
for (int i = 0; i < p.size(); i++) {
|
||||
if (i == 0) {
|
||||
path.moveTo(p.get(i).getXFloat(), p.get(i).getYFloat());
|
||||
} else {
|
||||
if (p.isBezierStart(i)) {
|
||||
path.curveTo(p.get(i).getXFloat(), p.get(i).getYFloat(),
|
||||
p.get(i + 1).getXFloat(), p.get(i + 1).getYFloat(),
|
||||
p.get(i + 2).getXFloat(), p.get(i + 2).getYFloat());
|
||||
i += 2;
|
||||
} else
|
||||
path.lineTo(p.get(i).getXFloat(), p.get(i).getYFloat());
|
||||
}
|
||||
}
|
||||
//CHECKSTYLE.ON: ModifiedControlVariable
|
||||
|
||||
if (p.isClosed())
|
||||
path.closePath();
|
||||
p.drawTo(path);
|
||||
|
||||
if (style.isFilled() && p.isClosed())
|
||||
gr.fill(path);
|
||||
|
@ -5,18 +5,19 @@
|
||||
*/
|
||||
package de.neemann.digital.draw.graphics;
|
||||
|
||||
import java.awt.geom.Path2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A polygon representation used by the {@link Graphic} interface.
|
||||
*/
|
||||
public class Polygon implements Iterable<VectorInterface> {
|
||||
public class Polygon implements Iterable<Polygon.PathElement> {
|
||||
|
||||
private final ArrayList<VectorInterface> points;
|
||||
private final HashSet<Integer> isBezierStart;
|
||||
private final ArrayList<PathElement> path;
|
||||
private boolean closed;
|
||||
private boolean hasSpecialElements = false;
|
||||
private boolean evenOdd;
|
||||
|
||||
/**
|
||||
* Creates e new closed polygon
|
||||
@ -41,9 +42,10 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
* @param closed true if polygon is closed
|
||||
*/
|
||||
public Polygon(ArrayList<VectorInterface> points, boolean closed) {
|
||||
this.points = points;
|
||||
this.closed = closed;
|
||||
isBezierStart = new HashSet<>();
|
||||
this.path = new ArrayList<>();
|
||||
for (VectorInterface p : points)
|
||||
add(p);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,10 +73,17 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
* @return this for chained calls
|
||||
*/
|
||||
public Polygon add(VectorInterface p) {
|
||||
points.add(p);
|
||||
if (path.isEmpty())
|
||||
add(new MoveTo(p));
|
||||
else
|
||||
add(new LineTo(p));
|
||||
return this;
|
||||
}
|
||||
|
||||
private void add(PathElement pe) {
|
||||
path.add(pe);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new cubic bezier curve to the polygon.
|
||||
*
|
||||
@ -84,45 +93,60 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
* @return this for chained calls
|
||||
*/
|
||||
public Polygon add(VectorInterface c1, VectorInterface c2, VectorInterface p) {
|
||||
if (points.size() == 0)
|
||||
if (path.size() == 0)
|
||||
throw new RuntimeException("cubic bezier curve is not allowed to be the first path element");
|
||||
isBezierStart.add(points.size());
|
||||
points.add(c1);
|
||||
points.add(c2);
|
||||
points.add(p);
|
||||
add(new CurveTo(c1, c2, p));
|
||||
hasSpecialElements = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the point with the given index is a bezier start point
|
||||
* Adds a new quadratic bezier curve to the polygon.
|
||||
*
|
||||
* @param n the index
|
||||
* @return true if point is bezier start
|
||||
* @param c the control point to add
|
||||
* @param p the end point to add
|
||||
* @return this for chained calls
|
||||
*/
|
||||
public boolean isBezierStart(int n) {
|
||||
return isBezierStart.contains(n);
|
||||
public Polygon add(VectorInterface c, VectorInterface p) {
|
||||
if (path.size() == 0)
|
||||
throw new RuntimeException("quadratic bezier curve is not allowed to be the first path element");
|
||||
add(new QuadTo(c, p));
|
||||
hasSpecialElements = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of points
|
||||
* Closes the actual path
|
||||
*/
|
||||
public int size() {
|
||||
return points.size();
|
||||
public void addClosePath() {
|
||||
add(new ClosePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one of the points
|
||||
* Adds a moveto to the path
|
||||
*
|
||||
* @param i the index
|
||||
* @return the i'th point
|
||||
* @param p the point to move to
|
||||
*/
|
||||
public VectorInterface get(int i) {
|
||||
return points.get(i);
|
||||
public void addMoveTo(VectorFloat p) {
|
||||
add(new MoveTo(p));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<VectorInterface> iterator() {
|
||||
return points.iterator();
|
||||
/**
|
||||
* @return true if filled in even odd mode
|
||||
*/
|
||||
public boolean getEvenOdd() {
|
||||
return evenOdd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the even odd mode used to fill the polygon
|
||||
*
|
||||
* @param evenOdd true is even odd mode is needed
|
||||
* @return this for chained calls
|
||||
*/
|
||||
public Polygon setEvenOdd(boolean evenOdd) {
|
||||
this.evenOdd = evenOdd;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -140,28 +164,45 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
}
|
||||
|
||||
private boolean check(VectorInterface p1, VectorInterface p2) {
|
||||
if (closed)
|
||||
return false;
|
||||
|
||||
if (p1.equals(getFirst())) {
|
||||
points.add(0, p2);
|
||||
if (p2.equals(getLast()))
|
||||
closed = true;
|
||||
else {
|
||||
removeInitialMoveTo();
|
||||
path.add(0, new MoveTo(p2));
|
||||
}
|
||||
return true;
|
||||
} else if (p1.equals(getLast())) {
|
||||
points.add(p2);
|
||||
if (p2.equals(getFirst()))
|
||||
closed = true;
|
||||
else
|
||||
path.add(new LineTo(p2));
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
private void removeInitialMoveTo() {
|
||||
if (!(path.get(0) instanceof MoveTo))
|
||||
throw new RuntimeException("initial path element is not a MoveTo!");
|
||||
path.set(0, new LineTo(path.get(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first point of the polygon
|
||||
*/
|
||||
public VectorInterface getFirst() {
|
||||
return points.get(0);
|
||||
return path.get(0).getPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the last point of the polygon
|
||||
*/
|
||||
public VectorInterface getLast() {
|
||||
return points.get(points.size() - 1);
|
||||
return path.get(path.size() - 1).getPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,10 +212,17 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
* @return this for chained calls
|
||||
*/
|
||||
public Polygon append(Polygon p2) {
|
||||
if (!p2.isBezierStart.isEmpty())
|
||||
if (hasSpecialElements || p2.hasSpecialElements)
|
||||
throw new RuntimeException("append of bezier not supported");
|
||||
for (int i = 1; i < p2.points.size(); i++)
|
||||
points.add(p2.points.get(i));
|
||||
|
||||
if (p2.getLast().equals(getFirst())) {
|
||||
for (int i = 1; i < p2.path.size() - 1; i++)
|
||||
add(p2.path.get(i).getPoint());
|
||||
closed = true;
|
||||
} else {
|
||||
for (int i = 1; i < p2.path.size(); i++)
|
||||
add(p2.path.get(i).getPoint());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -184,11 +232,11 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
* @return returns this polygon with reverse order of points
|
||||
*/
|
||||
public Polygon reverse() {
|
||||
if (!isBezierStart.isEmpty())
|
||||
throw new RuntimeException("reverse of bezier not supported");
|
||||
if (hasSpecialElements)
|
||||
throw new RuntimeException("append of bezier not supported");
|
||||
Polygon p = new Polygon(closed);
|
||||
for (int i = points.size() - 1; i >= 0; i--)
|
||||
p.add(points.get(i));
|
||||
for (int i = path.size() - 1; i >= 0; i--)
|
||||
p.add(path.get(i).getPoint());
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -202,43 +250,24 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
if (transform == Transform.IDENTITY)
|
||||
return this;
|
||||
|
||||
Polygon p = new Polygon(closed);
|
||||
for (VectorInterface v : points)
|
||||
p.add(v.transform(transform));
|
||||
p.isBezierStart.addAll(isBezierStart);
|
||||
Polygon p = new Polygon(closed).setEvenOdd(evenOdd);
|
||||
for (PathElement pe : path)
|
||||
p.add(pe.transform(transform));
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder("M ");
|
||||
VectorInterface v = points.get(0);
|
||||
sb.append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" ");
|
||||
//modification of loop variable i is intended!
|
||||
//CHECKSTYLE.OFF: ModifiedControlVariable
|
||||
for (int i = 1; i < points.size(); i++) {
|
||||
v = points.get(i);
|
||||
if (isBezierStart.contains(i)) {
|
||||
sb.append("C ").append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" ");
|
||||
v = points.get(i + 1);
|
||||
sb.append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" ");
|
||||
v = points.get(i + 2);
|
||||
sb.append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" ");
|
||||
i += 2;
|
||||
} else
|
||||
sb.append("L ").append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" ");
|
||||
}
|
||||
//CHECKSTYLE.ON: ModifiedControlVariable
|
||||
if (closed)
|
||||
sb.append("z");
|
||||
return sb.toString();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (PathElement pe : path) {
|
||||
if (sb.length() > 0)
|
||||
sb.append(' ');
|
||||
sb.append(pe.toString());
|
||||
}
|
||||
|
||||
private static String str(float f) {
|
||||
if (f == Math.round(f))
|
||||
return Integer.toString(Math.round(f));
|
||||
else
|
||||
return Float.toString(f);
|
||||
if (closed)
|
||||
sb.append(" Z");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,4 +287,260 @@ public class Polygon implements Iterable<VectorInterface> {
|
||||
void setClosed(boolean closed) {
|
||||
this.closed = closed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<PathElement> iterator() {
|
||||
return path.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw this polygon to a {@link Path2D} instance.
|
||||
*
|
||||
* @param path2d the Path2d instance.
|
||||
*/
|
||||
public void drawTo(Path2D path2d) {
|
||||
for (PathElement pe : path)
|
||||
pe.drawTo(path2d);
|
||||
if (closed)
|
||||
path2d.closePath();
|
||||
if (evenOdd)
|
||||
path2d.setWindingRule(Path2D.WIND_EVEN_ODD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses all points
|
||||
*
|
||||
* @param v the visitor to use
|
||||
*/
|
||||
public void traverse(PointVisitor v) {
|
||||
for (PathElement pe : path)
|
||||
pe.traverse(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visitor used to traverse all points
|
||||
*/
|
||||
public interface PointVisitor {
|
||||
/**
|
||||
* Called with every point
|
||||
*
|
||||
* @param p the point
|
||||
*/
|
||||
void visit(VectorInterface p);
|
||||
}
|
||||
|
||||
/**
|
||||
* A element of the path
|
||||
*/
|
||||
public interface PathElement {
|
||||
/**
|
||||
* @return the coordinate of this path element
|
||||
*/
|
||||
VectorInterface getPoint();
|
||||
|
||||
/**
|
||||
* Returns the transformed path element
|
||||
*
|
||||
* @param transform the transformation
|
||||
* @return the transormated path element
|
||||
*/
|
||||
PathElement transform(Transform transform);
|
||||
|
||||
/**
|
||||
* Draws this path element to a Path2D instance.
|
||||
*
|
||||
* @param path2d the a Path2D instance
|
||||
*/
|
||||
void drawTo(Path2D path2d);
|
||||
|
||||
/**
|
||||
* Traverses all points
|
||||
*
|
||||
* @param v the visitor to use
|
||||
*/
|
||||
void traverse(PointVisitor v);
|
||||
}
|
||||
|
||||
private static String str(float f) {
|
||||
if (f == Math.round(f))
|
||||
return Integer.toString(Math.round(f));
|
||||
else
|
||||
return Float.toString(f);
|
||||
}
|
||||
|
||||
private static String str(VectorInterface p) {
|
||||
return str(p.getXFloat()) + "," + str(p.getYFloat());
|
||||
}
|
||||
|
||||
//LineTo can not be final because its overridden. Maybe checkstyle has a bug?
|
||||
//CHECKSTYLE.OFF: FinalClass
|
||||
private static class LineTo implements PathElement {
|
||||
protected final VectorInterface p;
|
||||
|
||||
private LineTo(VectorInterface p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
private LineTo(PathElement pathElement) {
|
||||
this(pathElement.getPoint());
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorInterface getPoint() {
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathElement transform(Transform transform) {
|
||||
return new LineTo(p.transform(transform));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTo(Path2D path2d) {
|
||||
path2d.lineTo(p.getXFloat(), p.getYFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "L " + str(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void traverse(PointVisitor v) {
|
||||
v.visit(p);
|
||||
}
|
||||
}
|
||||
//CHECKSTYLE.ON: FinalClass
|
||||
|
||||
private static final class MoveTo extends LineTo {
|
||||
private MoveTo(VectorInterface p) {
|
||||
super(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "M " + str(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTo(Path2D path2d) {
|
||||
path2d.moveTo(p.getXFloat(), p.getYFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathElement transform(Transform transform) {
|
||||
return new MoveTo(p.transform(transform));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CurveTo implements PathElement {
|
||||
private final VectorInterface c1;
|
||||
private final VectorInterface c2;
|
||||
private final VectorInterface p;
|
||||
|
||||
private CurveTo(VectorInterface c1, VectorInterface c2, VectorInterface p) {
|
||||
this.c1 = c1;
|
||||
this.c2 = c2;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorInterface getPoint() {
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathElement transform(Transform transform) {
|
||||
return new CurveTo(
|
||||
c1.transform(transform),
|
||||
c2.transform(transform),
|
||||
p.transform(transform)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "C " + str(c1) + " " + str(c2) + " " + str(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTo(Path2D path2d) {
|
||||
path2d.curveTo(c1.getXFloat(), c1.getYFloat(),
|
||||
c2.getXFloat(), c2.getYFloat(),
|
||||
p.getXFloat(), p.getYFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void traverse(PointVisitor v) {
|
||||
v.visit(c1);
|
||||
v.visit(c2);
|
||||
v.visit(p);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class QuadTo implements PathElement {
|
||||
private final VectorInterface c;
|
||||
private final VectorInterface p;
|
||||
|
||||
private QuadTo(VectorInterface c, VectorInterface p) {
|
||||
this.c = c;
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VectorInterface getPoint() {
|
||||
return p;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathElement transform(Transform transform) {
|
||||
return new QuadTo(
|
||||
c.transform(transform),
|
||||
p.transform(transform)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Q " + str(c) + " " + str(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTo(Path2D path2d) {
|
||||
path2d.quadTo(c.getXFloat(), c.getYFloat(),
|
||||
p.getXFloat(), p.getYFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void traverse(PointVisitor v) {
|
||||
v.visit(c);
|
||||
v.visit(p);
|
||||
}
|
||||
}
|
||||
|
||||
private class ClosePath implements PathElement {
|
||||
@Override
|
||||
public VectorInterface getPoint() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PathElement transform(Transform transform) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawTo(Path2D path2d) {
|
||||
path2d.closePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Z";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void traverse(PointVisitor v) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,12 +25,17 @@ public class PolygonConverter implements Converter {
|
||||
public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext marshallingContext) {
|
||||
Polygon p = (Polygon) o;
|
||||
writer.addAttribute("path", p.toString());
|
||||
writer.addAttribute("evenOdd", Boolean.toString(p.getEvenOdd()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext unmarshallingContext) {
|
||||
String path = reader.getAttribute("path");
|
||||
return Polygon.createFromPath(path);
|
||||
boolean evenOdd = Boolean.parseBoolean(reader.getAttribute("evenOdd"));
|
||||
final Polygon polygon = Polygon.createFromPath(path);
|
||||
if (polygon != null)
|
||||
polygon.setEvenOdd(evenOdd);
|
||||
return polygon;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public class PolygonParser {
|
||||
*
|
||||
* @param path the path to parse
|
||||
*/
|
||||
PolygonParser(String path) {
|
||||
public PolygonParser(String path) {
|
||||
this.path = path;
|
||||
pos = 0;
|
||||
}
|
||||
@ -117,6 +117,7 @@ public class PolygonParser {
|
||||
public Polygon create() throws ParserException {
|
||||
Polygon p = new Polygon(false);
|
||||
Token tok;
|
||||
boolean closedPending = false;
|
||||
while ((tok = next()) != Token.EOF) {
|
||||
if (tok == Token.NUMBER) {
|
||||
unreadToken();
|
||||
@ -127,11 +128,19 @@ public class PolygonParser {
|
||||
}
|
||||
switch (command) {
|
||||
case 'M':
|
||||
p.add(nextVector());
|
||||
if (closedPending) {
|
||||
closedPending = false;
|
||||
p.addClosePath();
|
||||
}
|
||||
p.addMoveTo(nextVector());
|
||||
clearControl();
|
||||
break;
|
||||
case 'm':
|
||||
p.add(nextVectorInc());
|
||||
if (closedPending) {
|
||||
closedPending = false;
|
||||
p.addClosePath();
|
||||
}
|
||||
p.addMoveTo(nextVectorInc());
|
||||
clearControl();
|
||||
break;
|
||||
case 'V':
|
||||
@ -169,10 +178,10 @@ public class PolygonParser {
|
||||
p.add(nextVector(), setLastC3(nextVector()), nextVector());
|
||||
break;
|
||||
case 'q':
|
||||
addQuadratic(p, getCurrent(), setLastC2(nextVectorRel()), nextVectorInc());
|
||||
p.add(setLastC2(nextVectorRel()), nextVectorInc());
|
||||
break;
|
||||
case 'Q':
|
||||
addQuadratic(p, getCurrent(), setLastC2(nextVector()), nextVector());
|
||||
p.add(setLastC2(nextVector()), nextVector());
|
||||
break;
|
||||
case 's':
|
||||
addCubicWithReflect(p, getCurrent(), nextVectorRel(), nextVectorInc());
|
||||
@ -187,22 +196,24 @@ public class PolygonParser {
|
||||
addQuadraticWithReflect(p, getCurrent(), nextVector());
|
||||
break;
|
||||
case 'a':
|
||||
addArc(p, nextVectorInc(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc());
|
||||
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc());
|
||||
clearControl();
|
||||
break;
|
||||
case 'A':
|
||||
addArc(p, nextVector(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector());
|
||||
addArc(p, getCurrent(), nextValue(), nextValue(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector());
|
||||
clearControl();
|
||||
break;
|
||||
case 'Z':
|
||||
case 'z':
|
||||
p.setClosed(true);
|
||||
closedPending = true;
|
||||
clearControl();
|
||||
break;
|
||||
default:
|
||||
throw new ParserException("unsupported path command " + command);
|
||||
}
|
||||
}
|
||||
if (closedPending)
|
||||
p.setClosed(true);
|
||||
return p;
|
||||
}
|
||||
|
||||
@ -239,18 +250,107 @@ public class PolygonParser {
|
||||
return lastCubicControlPoint;
|
||||
}
|
||||
|
||||
private void addArc(Polygon p, VectorFloat rad, float rot, boolean large, boolean sweep, VectorFloat pos) {
|
||||
p.add(pos);
|
||||
/*
|
||||
* Substitutes the arc by a number of quadratic bezier curves
|
||||
*/
|
||||
//CHECKSTYLE.OFF: ParameterNumberCheck
|
||||
private void addArc(Polygon p, VectorInterface current, float rx, float ry, float rot, boolean large, boolean sweep, VectorFloat pos) {
|
||||
Transform tr = Transform.IDENTITY;
|
||||
if (rx != ry)
|
||||
tr = TransformMatrix.scale(1, rx / ry);
|
||||
|
||||
if (rot != 0)
|
||||
tr = Transform.mul(TransformMatrix.rotate(-rot), tr);
|
||||
|
||||
Transform invert = tr.invert();
|
||||
|
||||
VectorInterface p1 = current.transform(tr);
|
||||
VectorInterface p2 = pos.transform(tr);
|
||||
|
||||
// ellipse is transformed to a circle with radius r
|
||||
float r = rx;
|
||||
|
||||
double x1 = p1.getXFloat();
|
||||
double y1 = p1.getYFloat();
|
||||
double x2 = p2.getXFloat();
|
||||
double y2 = p2.getYFloat();
|
||||
|
||||
double x1q = x1 * x1;
|
||||
double y1q = y1 * y1;
|
||||
double x2q = x2 * x2;
|
||||
double y2q = y2 * y2;
|
||||
double rq = r * r;
|
||||
|
||||
double x0A = (r * (y1 - y2) * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * sign(x1 - x2) + r * (x1 + x2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q))) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
double y0A = (r * (y1 + y2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)) - r * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * abs(x1 - x2)) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
double x0B = (r * (x1 + x2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)) - r * (y1 - y2) * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * sign(x1 - x2)) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
double y0B = (r * sqrt(rq * (4 * rq - y1q + y2 * (2 * y1 - y2)) - rq * (x1q - 2 * x1 * x2 + x2q)) * abs(x1 - x2) + r * (y1 + y2) * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q))) / (2 * r * sqrt(rq * (y1q - 2 * y1 * y2 + y2q) + rq * (x1q - 2 * x1 * x2 + x2q)));
|
||||
|
||||
double startA = Math.atan2(y1 - y0A, x1 - x0A);
|
||||
double endA = Math.atan2(y2 - y0A, x2 - x0A);
|
||||
|
||||
double startB = Math.atan2(y1 - y0B, x1 - x0B);
|
||||
double endB = Math.atan2(y2 - y0B, x2 - x0B);
|
||||
|
||||
double delta = 2 * Math.PI / 12;
|
||||
if (!sweep) delta = -delta;
|
||||
|
||||
if (delta > 0) {
|
||||
if (endA < startA) endA += 2 * Math.PI;
|
||||
if (endB < startB) endB += 2 * Math.PI;
|
||||
} else {
|
||||
if (endA > startA) endA -= 2 * Math.PI;
|
||||
if (endB > startB) endB -= 2 * Math.PI;
|
||||
}
|
||||
|
||||
private void addQuadratic(Polygon poly, VectorInterface start, VectorInterface c, VectorInterface p) {
|
||||
c = c.mul(2.0f / 3);
|
||||
poly.add(start.mul(1f / 3).add(c), p.mul(1f / 3).add(c), p);
|
||||
double sizeA = Math.abs(startA - endA);
|
||||
double sizeB = Math.abs(startB - endB);
|
||||
|
||||
double start = startA;
|
||||
double end = endA;
|
||||
double x0 = x0A;
|
||||
double y0 = y0A;
|
||||
if (large ^ (sizeA > sizeB)) {
|
||||
start = startB;
|
||||
end = endB;
|
||||
x0 = x0B;
|
||||
y0 = y0B;
|
||||
}
|
||||
|
||||
double lastStart = start;
|
||||
start += delta;
|
||||
while (delta < 0 ^ start < end) {
|
||||
addArcPoint(p, lastStart, start, x0, y0, r, invert);
|
||||
lastStart = start;
|
||||
start += delta;
|
||||
}
|
||||
addArcPoint(p, lastStart, end, x0, y0, r, invert);
|
||||
}
|
||||
//CHECKSTYLE.ON: ParameterNumberCheck
|
||||
|
||||
private void addArcPoint(Polygon p, double alpha0, double alpha1, double x0, double y0, float r, Transform tr) {
|
||||
final double mean = (alpha0 + alpha1) / 2;
|
||||
double rLong = r / Math.cos(Math.abs(alpha0 - alpha1) / 2);
|
||||
final VectorInterface c = new VectorFloat((float) (x0 + rLong * Math.cos(mean)), (float) (y0 + rLong * Math.sin(mean)));
|
||||
final VectorInterface p1 = new VectorFloat((float) (x0 + r * Math.cos(alpha1)), (float) (y0 + r * Math.sin(alpha1)));
|
||||
p.add(c.transform(tr), p1.transform(tr));
|
||||
}
|
||||
|
||||
private static double sqrt(double x) {
|
||||
return Math.sqrt(x);
|
||||
}
|
||||
|
||||
private static double sign(double x) {
|
||||
return Math.signum(x);
|
||||
}
|
||||
|
||||
private static double abs(double x) {
|
||||
return Math.abs(x);
|
||||
}
|
||||
|
||||
private void addQuadraticWithReflect(Polygon poly, VectorInterface start, VectorInterface p) {
|
||||
VectorInterface c = start.add(start.sub(getLastC2()));
|
||||
addQuadratic(poly, start, setLastC2(c), p);
|
||||
poly.add(setLastC2(c), p);
|
||||
}
|
||||
|
||||
private void addCubicWithReflect(Polygon poly, VectorInterface start, VectorInterface c2, VectorInterface p) {
|
||||
@ -266,4 +366,32 @@ public class PolygonParser {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a polygon.
|
||||
*
|
||||
* @return the polygon
|
||||
* @throws ParserException ParserException
|
||||
*/
|
||||
public Polygon parsePolygon() throws ParserException {
|
||||
return parsePolygonPolyline(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a polyline.
|
||||
*
|
||||
* @return the polygon
|
||||
* @throws ParserException ParserException
|
||||
*/
|
||||
public Polygon parsePolyline() throws ParserException {
|
||||
return parsePolygonPolyline(false);
|
||||
}
|
||||
|
||||
private Polygon parsePolygonPolyline(boolean closed) throws ParserException {
|
||||
Polygon p = new Polygon(closed);
|
||||
while (next() != Token.EOF)
|
||||
p.add(new VectorFloat(value, nextValue()));
|
||||
return p;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,11 @@ public interface Transform {
|
||||
public TransformMatrix getMatrix() {
|
||||
return new TransformMatrix(1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform invert() {
|
||||
return IDENTITY;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -72,4 +77,11 @@ public interface Transform {
|
||||
* @return the transformed Transform
|
||||
*/
|
||||
TransformMatrix getMatrix();
|
||||
|
||||
/**
|
||||
* @return the inverse transform
|
||||
*/
|
||||
default Transform invert() {
|
||||
return getMatrix().invert();
|
||||
}
|
||||
}
|
||||
|
@ -13,17 +13,30 @@ public class TransformMatrix implements Transform {
|
||||
|
||||
/**
|
||||
* Creates a rotation.
|
||||
* Rotates in mathematically positive direction. Takes into account that
|
||||
* in Digital the y-axis goes downwards.
|
||||
*
|
||||
* @param w the angle in 360 grad
|
||||
* @param w the angle in 360 grad units
|
||||
* @return the transformation
|
||||
*/
|
||||
public static TransformMatrix rotate(float w) {
|
||||
public static TransformMatrix rotate(double w) {
|
||||
final double phi = w / 180 * Math.PI;
|
||||
float cos = (float) Math.cos(phi);
|
||||
float sin = (float) Math.sin(phi);
|
||||
return new TransformMatrix(cos, sin, -sin, cos, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scaling transformation
|
||||
*
|
||||
* @param sx scaling in x direction
|
||||
* @param sy scaling in y direction
|
||||
* @return the transformation
|
||||
*/
|
||||
public static TransformMatrix scale(float sx, float sy) {
|
||||
return new TransformMatrix(sx, 0, 0, sy, 0, 0);
|
||||
}
|
||||
|
||||
final float a;
|
||||
final float b;
|
||||
final float c;
|
||||
@ -66,7 +79,8 @@ public class TransformMatrix implements Transform {
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a direction vector
|
||||
* Transforms a direction vector.
|
||||
* Ignores the translation part of the transformation.
|
||||
*
|
||||
* @param v the vector to transform
|
||||
* @return the transformed vector
|
||||
@ -82,4 +96,37 @@ public class TransformMatrix implements Transform {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inverse transformation.
|
||||
*
|
||||
* @return the inverse transformation.
|
||||
*/
|
||||
public TransformMatrix invert() {
|
||||
float q = a * d - b * c;
|
||||
|
||||
return new TransformMatrix(d / q, -b / q, -c / q, a / q,
|
||||
(b * y - d * x) / q, (c * x - a * y) / q);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -54,4 +54,9 @@ public class TransformTranslate implements Transform {
|
||||
public TransformMatrix getMatrix() {
|
||||
return new TransformMatrix(1, 0, 0, 1, trans.getXFloat(), trans.getYFloat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Transform invert() {
|
||||
return new TransformTranslate(trans.div(-1));
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ public class GraphicLineCollector implements Graphic {
|
||||
|
||||
private void tryMerge(Polygon p1) {
|
||||
for (Polygon p2 : polyList)
|
||||
if (p1 != p2) {
|
||||
if (p1 != p2 && !p1.isClosed() && !p2.isClosed()) {
|
||||
if (p1.getLast().equals(p2.getFirst())) {
|
||||
p1.append(p2);
|
||||
polyList.remove(p2);
|
||||
|
@ -147,6 +147,13 @@ public class CustomShapeDescription implements Iterable<Drawable> {
|
||||
.addText(new Vector(20, -25), new Vector(21, -25), "Hi!", Orientation.LEFTCENTER, 20, Color.BLACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of pins in this shape
|
||||
*/
|
||||
public int getPinCount() {
|
||||
return pins.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a line.
|
||||
*/
|
||||
@ -167,6 +174,20 @@ public class CustomShapeDescription implements Iterable<Drawable> {
|
||||
public void drawTo(Graphic graphic, Style highLight) {
|
||||
graphic.drawLine(p1, p2, Style.NORMAL.deriveStyle(thickness, false, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return first coordinate
|
||||
*/
|
||||
public Vector getP1() {
|
||||
return p1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return second coordinate
|
||||
*/
|
||||
public Vector getP2() {
|
||||
return p2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,6 +235,13 @@ public class CustomShapeDescription implements Iterable<Drawable> {
|
||||
public void drawTo(Graphic graphic, Style highLight) {
|
||||
graphic.drawPolygon(poly, Style.NORMAL.deriveStyle(thickness, filled, color));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stored polygon
|
||||
*/
|
||||
public Polygon getPolygon() {
|
||||
return poly;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann.
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.shapes.custom.svg;
|
||||
|
||||
import de.neemann.digital.draw.graphics.*;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
class Context {
|
||||
|
||||
private static final HashMap<String, AttrParser> PARSER = new HashMap<>();
|
||||
|
||||
|
||||
static {
|
||||
PARSER.put("transform", Context::readTransform);
|
||||
PARSER.put("fill", (c, value) -> c.fill = getColorFromString(value));
|
||||
PARSER.put("stroke", (c, value) -> c.color = getColorFromString(value));
|
||||
PARSER.put("stroke-width", (c, value) -> c.thickness = getFloatFromString(value) + 1);
|
||||
PARSER.put("font-size", (c, value) -> c.fontSize = getFloatFromString(value) + 1);
|
||||
PARSER.put("style", Context::readStyle);
|
||||
PARSER.put("text-anchor", (c, value) -> c.textAnchor = value);
|
||||
PARSER.put("fill-rule", (c, value) -> c.fillRuleEvenOdd = value.equalsIgnoreCase("evenodd"));
|
||||
}
|
||||
|
||||
private Transform tr;
|
||||
private Color fill;
|
||||
private Color color;
|
||||
private float thickness;
|
||||
private float fontSize;
|
||||
private String textAnchor;
|
||||
private boolean fillRuleEvenOdd;
|
||||
|
||||
Context() {
|
||||
tr = Transform.IDENTITY;
|
||||
thickness = 1;
|
||||
color = Color.BLACK;
|
||||
}
|
||||
|
||||
private Context(Context parent) {
|
||||
tr = parent.tr;
|
||||
fill = parent.fill;
|
||||
color = parent.color;
|
||||
thickness = parent.thickness;
|
||||
fontSize = parent.fontSize;
|
||||
fillRuleEvenOdd = parent.fillRuleEvenOdd;
|
||||
}
|
||||
|
||||
Context(Context parent, Element element) throws SvgException {
|
||||
this(parent);
|
||||
final NamedNodeMap attributes = element.getAttributes();
|
||||
for (int i = 0; i < attributes.getLength(); i++) {
|
||||
final Node item = attributes.item(i);
|
||||
AttrParser p = PARSER.get(item.getNodeName());
|
||||
if (p != null)
|
||||
p.parse(this, item.getNodeValue().trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static void readStyle(Context context, String style) throws SvgException {
|
||||
StringTokenizer st = new StringTokenizer(style, ";");
|
||||
while (st.hasMoreTokens()) {
|
||||
String[] t = st.nextToken().split(":");
|
||||
if (t.length == 2) {
|
||||
AttrParser p = PARSER.get(t[0].trim());
|
||||
if (p != null)
|
||||
p.parse(context, t[1].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Transform getTransform() {
|
||||
return tr;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public Color getFilled() {
|
||||
return fill;
|
||||
}
|
||||
|
||||
public int getThickness() {
|
||||
return (int) thickness;
|
||||
}
|
||||
|
||||
public boolean isFillRuleEvenOdd() {
|
||||
return fillRuleEvenOdd;
|
||||
}
|
||||
|
||||
public Orientation getTextOrientation() {
|
||||
if (textAnchor == null)
|
||||
return Orientation.LEFTBOTTOM;
|
||||
|
||||
switch (textAnchor) {
|
||||
case "end":
|
||||
return Orientation.RIGHTBOTTOM;
|
||||
case "middle":
|
||||
return Orientation.CENTERBOTTOM;
|
||||
default:
|
||||
return Orientation.LEFTBOTTOM;
|
||||
}
|
||||
}
|
||||
|
||||
public VectorInterface tr(VectorInterface vector) {
|
||||
return vector.transform(tr);
|
||||
}
|
||||
|
||||
public VectorInterface v(float x, float y) {
|
||||
return new VectorFloat(x, y).transform(tr);
|
||||
}
|
||||
|
||||
public VectorInterface v(String xStr, String yStr) {
|
||||
float x = xStr.isEmpty() ? 0 : Float.parseFloat(xStr);
|
||||
float y = yStr.isEmpty() ? 0 : Float.parseFloat(yStr);
|
||||
return v(x, y);
|
||||
}
|
||||
|
||||
public float getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
|
||||
private interface AttrParser {
|
||||
void parse(Context c, String value) throws SvgException;
|
||||
}
|
||||
|
||||
private static void readTransform(Context c, String value) throws SvgException {
|
||||
StringTokenizer st = new StringTokenizer(value, "(),");
|
||||
Transform t = null;
|
||||
final String trans = st.nextToken();
|
||||
switch (trans) {
|
||||
case "translate":
|
||||
t = new TransformTranslate(new VectorFloat(Float.parseFloat(st.nextToken()), Float.parseFloat(st.nextToken())));
|
||||
break;
|
||||
case "scale":
|
||||
final float xs = Float.parseFloat(st.nextToken());
|
||||
final float ys = Float.parseFloat(st.nextToken());
|
||||
t = new TransformMatrix(xs, 0, 0, ys, 0, 0);
|
||||
break;
|
||||
case "matrix":
|
||||
t = new TransformMatrix(
|
||||
Float.parseFloat(st.nextToken()),
|
||||
Float.parseFloat(st.nextToken()),
|
||||
Float.parseFloat(st.nextToken()),
|
||||
Float.parseFloat(st.nextToken()),
|
||||
Float.parseFloat(st.nextToken()),
|
||||
Float.parseFloat(st.nextToken()));
|
||||
break;
|
||||
case "rotate":
|
||||
float w = Float.parseFloat(st.nextToken());
|
||||
if (st.hasMoreTokens()) {
|
||||
t = TransformMatrix.rotate(w);
|
||||
float xc = Float.parseFloat(st.nextToken());
|
||||
float yc = Float.parseFloat(st.nextToken());
|
||||
t = Transform.mul(new TransformTranslate(xc, yc), t);
|
||||
t = Transform.mul(t, new TransformTranslate(-xc, -yc));
|
||||
} else
|
||||
t = TransformMatrix.rotate(w);
|
||||
break;
|
||||
default:
|
||||
throw new SvgException("unknown transform: " + value, null);
|
||||
}
|
||||
c.tr = Transform.mul(c.tr, t);
|
||||
}
|
||||
|
||||
private static Color getColorFromString(String v) {
|
||||
if (v.equalsIgnoreCase("none"))
|
||||
return null;
|
||||
|
||||
if (v.startsWith("#"))
|
||||
return Color.decode(v);
|
||||
|
||||
try {
|
||||
return (Color) Color.class.getField(v).get(null);
|
||||
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
private static float getFloatFromString(String inp) {
|
||||
inp = inp.replaceAll("[^0-9.]", "");
|
||||
if (inp.isEmpty())
|
||||
return 1;
|
||||
return Float.parseFloat(inp);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann.
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.shapes.custom.svg;
|
||||
|
||||
/**
|
||||
* Exception thrown if svg could not be parsed.
|
||||
*/
|
||||
public class SvgException extends Exception {
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param message the message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public SvgException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,267 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann.
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.shapes.custom.svg;
|
||||
|
||||
import de.neemann.digital.draw.graphics.*;
|
||||
import de.neemann.digital.draw.shapes.custom.CustomShapeDescription;
|
||||
import de.neemann.digital.lang.Lang;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static de.neemann.digital.draw.shapes.GenericShape.SIZE;
|
||||
|
||||
/**
|
||||
* Helper to import an SVG file
|
||||
*/
|
||||
public class SvgImporter {
|
||||
private final Document svg;
|
||||
|
||||
/**
|
||||
* Create a new importer instance
|
||||
*
|
||||
* @param in the svg file to import
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public SvgImporter(InputStream in) throws IOException {
|
||||
try {
|
||||
svg = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(in);
|
||||
} catch (Exception e) {
|
||||
throw new IOException(Lang.get("err_parsingSVG"), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new importer instance
|
||||
*
|
||||
* @param svgFile the svg file to import
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
public SvgImporter(File svgFile) throws IOException {
|
||||
if (!svgFile.exists())
|
||||
throw new FileNotFoundException(svgFile.getPath());
|
||||
try {
|
||||
svg = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(svgFile);
|
||||
} catch (Exception e) {
|
||||
throw new IOException(Lang.get("err_parsingSVG"), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and draws the svg file.
|
||||
*
|
||||
* @return the custom shape description
|
||||
* @throws SvgException SvgException
|
||||
*/
|
||||
public CustomShapeDescription create() throws SvgException {
|
||||
NodeList gList = svg.getElementsByTagName("svg").item(0).getChildNodes();
|
||||
Context c = new Context();
|
||||
try {
|
||||
CustomShapeDescription csd = new CustomShapeDescription();
|
||||
create(csd, gList, c);
|
||||
return csd;
|
||||
} catch (RuntimeException e) {
|
||||
throw new SvgException(Lang.get("err_parsingSVG"), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void create(CustomShapeDescription csd, NodeList gList, Context c) throws SvgException {
|
||||
for (int i = 0; i < gList.getLength(); i++) {
|
||||
final Node node = gList.item(i);
|
||||
if (node instanceof Element)
|
||||
create(csd, (Element) node, c);
|
||||
}
|
||||
}
|
||||
|
||||
private void create(CustomShapeDescription csd, Element element, Context parent) throws SvgException {
|
||||
Context c = new Context(parent, element);
|
||||
switch (element.getNodeName()) {
|
||||
case "a":
|
||||
case "g":
|
||||
create(csd, element.getChildNodes(), c);
|
||||
break;
|
||||
case "line":
|
||||
csd.addLine(
|
||||
c.v(element.getAttribute("x1"), element.getAttribute("y1")).round(),
|
||||
c.v(element.getAttribute("x2"), element.getAttribute("y2")).round(),
|
||||
c.getThickness(), c.getColor());
|
||||
break;
|
||||
case "rect":
|
||||
drawRect(csd, element, c);
|
||||
break;
|
||||
case "path":
|
||||
try {
|
||||
final Polygon d = new PolygonParser(element.getAttribute("d")).create();
|
||||
if (d != null)
|
||||
d.setEvenOdd(c.isFillRuleEvenOdd());
|
||||
|
||||
drawPolygon(csd, c, d);
|
||||
} catch (PolygonParser.ParserException e) {
|
||||
throw new SvgException("invalid path", e);
|
||||
}
|
||||
break;
|
||||
case "polygon":
|
||||
try {
|
||||
drawPolygon(csd, c, new PolygonParser(element.getAttribute("points")).parsePolygon());
|
||||
} catch (PolygonParser.ParserException e) {
|
||||
throw new SvgException("invalid points", e);
|
||||
}
|
||||
break;
|
||||
case "polyline":
|
||||
try {
|
||||
drawPolygon(csd, c, new PolygonParser(element.getAttribute("points")).parsePolyline());
|
||||
} catch (PolygonParser.ParserException e) {
|
||||
throw new SvgException("invalid points", e);
|
||||
}
|
||||
break;
|
||||
case "circle":
|
||||
case "ellipse":
|
||||
drawCircle(csd, element, c);
|
||||
break;
|
||||
case "text":
|
||||
drawText(csd, c, element);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawPolygon(CustomShapeDescription csd, Context c, Polygon polygon) {
|
||||
if (c.getFilled() != null)
|
||||
csd.addPolygon(polygon.transform(c.getTransform()), c.getThickness(), c.getFilled(), true);
|
||||
if (c.getColor() != null)
|
||||
csd.addPolygon(polygon.transform(c.getTransform()), c.getThickness(), c.getColor(), false);
|
||||
}
|
||||
|
||||
private VectorFloat vec(String xStr, String yStr) {
|
||||
float x = xStr.isEmpty() ? 0 : Float.parseFloat(xStr);
|
||||
float y = yStr.isEmpty() ? 0 : Float.parseFloat(yStr);
|
||||
return new VectorFloat(x, y);
|
||||
}
|
||||
|
||||
private void drawRect(CustomShapeDescription csd, Element element, Context c) {
|
||||
VectorInterface size = vec(element.getAttribute("width"), element.getAttribute("height"));
|
||||
VectorInterface pos = vec(element.getAttribute("x"), element.getAttribute("y"));
|
||||
VectorInterface rad = vec(element.getAttribute("rx"), element.getAttribute("ry"));
|
||||
|
||||
final Polygon polygon;
|
||||
|
||||
float x = pos.getXFloat();
|
||||
float y = pos.getYFloat();
|
||||
float width = size.getXFloat();
|
||||
float height = size.getYFloat();
|
||||
if (rad.getXFloat() * rad.getYFloat() != 0) {
|
||||
float rx = rad.getXFloat();
|
||||
float ry = rad.getYFloat();
|
||||
float w = size.getXFloat() - 2 * rx;
|
||||
float h = size.getYFloat() - 2 * ry;
|
||||
|
||||
double f = 4 * (Math.sqrt(2) - 1) / 3;
|
||||
float cx = (float) (f * rx);
|
||||
float cy = (float) (f * ry);
|
||||
|
||||
polygon = new Polygon(true)
|
||||
.add(c.v(x + rx + w, y))
|
||||
.add(c.v(x + rx + w + cx, y), c.v(x + width, y + ry - cy), c.v(x + width, y + ry))
|
||||
.add(c.v(x + width, y + ry + h))
|
||||
.add(c.v(x + width, y + ry + h + cy), c.v(x + rx + w + cx, y + height), c.v(x + rx + w, y + height))
|
||||
.add(c.v(x + rx, y + height))
|
||||
.add(c.v(x + rx - cx, y + height), c.v(x, y + ry + h + cy), c.v(x, y + ry + h))
|
||||
.add(c.v(x, y + ry))
|
||||
.add(c.v(x, y + ry - cy), c.v(x + rx - cx, y), c.v(x + rx, y));
|
||||
} else
|
||||
polygon = new Polygon(true)
|
||||
.add(c.v(x, y))
|
||||
.add(c.v(x + width, y))
|
||||
.add(c.v(x + width, y + height))
|
||||
.add(c.v(x, y + height));
|
||||
|
||||
if (c.getFilled() != null)
|
||||
csd.addPolygon(polygon, c.getThickness(), c.getFilled(), true);
|
||||
if (c.getColor() != null)
|
||||
csd.addPolygon(polygon, c.getThickness(), c.getColor(), false);
|
||||
}
|
||||
|
||||
private void drawCircle(CustomShapeDescription csd, Element element, Context c) {
|
||||
if (element.hasAttribute("id")) {
|
||||
VectorInterface pos = c.v(element.getAttribute("cx"), element.getAttribute("cy"));
|
||||
String id = element.getAttribute("id");
|
||||
if (id.startsWith("pin:")) {
|
||||
csd.addPin(id.substring(4).trim(), toGrid(pos), false);
|
||||
return;
|
||||
} else if (id.startsWith("pin+:")) {
|
||||
csd.addPin(id.substring(5).trim(), toGrid(pos), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
VectorFloat r = null;
|
||||
if (element.hasAttribute("r")) {
|
||||
final String rad = element.getAttribute("r");
|
||||
r = vec(rad, rad);
|
||||
}
|
||||
if (element.hasAttribute("rx")) {
|
||||
r = vec(element.getAttribute("rx"), element.getAttribute("ry"));
|
||||
}
|
||||
if (r != null) {
|
||||
VectorFloat pos = vec(element.getAttribute("cx"), element.getAttribute("cy"));
|
||||
float x = pos.getXFloat();
|
||||
float y = pos.getYFloat();
|
||||
float rx = r.getXFloat();
|
||||
float ry = r.getYFloat();
|
||||
|
||||
double f = 4 * (Math.sqrt(2) - 1) / 3;
|
||||
float cx = (float) (f * rx);
|
||||
float cy = (float) (f * ry);
|
||||
|
||||
Polygon poly = new Polygon(true)
|
||||
.add(c.v(x - rx, y))
|
||||
.add(c.v(x - rx, y + cy), c.v(x - cx, y + ry), c.v(x, y + ry))
|
||||
.add(c.v(x + cx, y + ry), c.v(x + rx, y + cy), c.v(x + rx, y))
|
||||
.add(c.v(x + rx, y - cy), c.v(x + cx, y - ry), c.v(x, y - ry))
|
||||
.add(c.v(x - cx, y - ry), c.v(x - rx, y - cy), c.v(x - rx, y));
|
||||
|
||||
if (c.getFilled() != null)
|
||||
csd.addPolygon(poly, c.getThickness(), c.getFilled(), true);
|
||||
if (c.getColor() != null)
|
||||
csd.addPolygon(poly, c.getThickness(), c.getColor(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector toGrid(VectorInterface pos) {
|
||||
return new Vector(Math.round(pos.getXFloat() / SIZE) * SIZE, Math.round(pos.getYFloat() / SIZE) * SIZE);
|
||||
}
|
||||
|
||||
private void drawText(CustomShapeDescription csd, Context c, Element element) throws SvgException {
|
||||
VectorFloat p = vec(element.getAttribute("x"), element.getAttribute("y"));
|
||||
VectorInterface pos0 = p.transform(c.getTransform());
|
||||
VectorInterface pos1 = p.add(new VectorFloat(1, 0)).transform(c.getTransform());
|
||||
|
||||
drawTextElement(csd, c, element, pos0, pos1);
|
||||
}
|
||||
|
||||
private void drawTextElement(CustomShapeDescription csd, Context c, Element element, VectorInterface pos0, VectorInterface pos1) throws SvgException {
|
||||
NodeList nodes = element.getElementsByTagName("*");
|
||||
if (nodes.getLength() == 0) {
|
||||
String text = element.getTextContent();
|
||||
csd.addText(pos0.round(), pos1.round(), text, c.getTextOrientation(), (int) c.getFontSize(), c.getColor());
|
||||
} else {
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
Node n = nodes.item(i);
|
||||
if (n instanceof Element) {
|
||||
Element el = (Element) n;
|
||||
drawTextElement(csd, new Context(c, el), el, pos0, pos1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann.
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.shapes.custom.svg;
|
||||
|
||||
import de.neemann.digital.core.ObservableValues;
|
||||
import de.neemann.digital.core.element.Keys;
|
||||
import de.neemann.digital.core.element.PinDescription;
|
||||
import de.neemann.digital.draw.elements.Circuit;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static de.neemann.digital.draw.shapes.GenericShape.SIZE;
|
||||
|
||||
/**
|
||||
* Used to create a SVG template
|
||||
*/
|
||||
public class SvgTemplate implements Closeable {
|
||||
private final Writer w;
|
||||
private final PinDescription[] inputs;
|
||||
private final ObservableValues outputs;
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param file the file to create
|
||||
* @param circuit the circuit
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public SvgTemplate(File file, Circuit circuit) throws Exception {
|
||||
this(new FileOutputStream(file), circuit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*
|
||||
* @param outputStream the stream to write to
|
||||
* @param circuit the circuit
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public SvgTemplate(OutputStream outputStream, Circuit circuit) throws Exception {
|
||||
width = circuit.getAttributes().get(Keys.WIDTH) * SIZE;
|
||||
inputs = circuit.getInputNames();
|
||||
outputs = circuit.getOutputNames();
|
||||
height = Math.max(inputs.length, outputs.size()) * SIZE;
|
||||
int border = SIZE * 4;
|
||||
|
||||
|
||||
w = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||
w.write("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
|
||||
+ "<svg\n"
|
||||
+ " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"
|
||||
+ " xmlns:cc=\"http://creativecommons.org/ns#\"\n"
|
||||
+ " xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"
|
||||
+ " xmlns:svg=\"http://www.w3.org/2000/svg\"\n"
|
||||
+ " xmlns=\"http://www.w3.org/2000/svg\"\n"
|
||||
+ " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"
|
||||
+ " xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n"
|
||||
+ " viewBox=\"-" + border + " -" + border + " " + (width + border * 2) + " " + (height + border * 2) + "\"\n"
|
||||
+ " version=\"1.1\">\n"
|
||||
+ " <sodipodi:namedview showgrid=\"true\">\n"
|
||||
+ " <inkscape:grid\n"
|
||||
+ " type=\"xygrid\"\n"
|
||||
+ " empspacing=\"4\"\n"
|
||||
+ " spacingx=\"5\"\n"
|
||||
+ " spacingy=\"5\" />\n"
|
||||
+ " </sodipodi:namedview>\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the template
|
||||
*
|
||||
* @throws Exception Exception
|
||||
*/
|
||||
public void create() throws Exception {
|
||||
w.write(" <rect fill=\"none\" stroke=\"black\" stroke-width=\"3\" x=\"0\" y=\"-10\" width=\"" + width + "\" height=\"" + height + "\"/>\n");
|
||||
|
||||
int y = 0;
|
||||
for (PinDescription i : inputs) {
|
||||
w.write(" <circle fill=\"blue\" id=\"pin+:" + i.getName() + "\" cx=\"0\" cy=\"" + y + "\" r=\"3\"/>\n");
|
||||
y += 20;
|
||||
}
|
||||
y = 0;
|
||||
for (PinDescription o : outputs) {
|
||||
w.write(" <circle fill=\"red\" id=\"pin+:" + o.getName() + "\" cx=\"" + width + "\" cy=\"" + y + "\" r=\"3\"/>\n");
|
||||
y += 20;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
w.write("</svg>\n");
|
||||
w.close();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann.
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simple importer for svg files.
|
||||
* A pin is specified by a circle which has an id of the form "pin:[name]" or "pin+:[name]".
|
||||
* The later one enables the pin label in the shape.
|
||||
* In this case the circle itself is ignored.
|
||||
*/
|
||||
package de.neemann.digital.draw.shapes.custom.svg;
|
@ -65,7 +65,6 @@ public class CircuitComponent extends JComponent implements Circuit.ChangedListe
|
||||
static {
|
||||
ATTR_LIST.add(Keys.WIDTH);
|
||||
ATTR_LIST.add(Keys.SHAPE_TYPE);
|
||||
if (Main.isExperimentalMode())
|
||||
ATTR_LIST.add(Keys.CUSTOM_SHAPE);
|
||||
ATTR_LIST.add(Keys.HEIGHT);
|
||||
ATTR_LIST.add(Keys.PINCOUNT);
|
||||
|
@ -8,12 +8,21 @@ package de.neemann.digital.gui.components;
|
||||
import de.neemann.digital.core.element.ElementAttributes;
|
||||
import de.neemann.digital.core.element.Key;
|
||||
import de.neemann.digital.draw.shapes.custom.CustomShapeDescription;
|
||||
import de.neemann.digital.draw.shapes.custom.svg.SvgException;
|
||||
import de.neemann.digital.draw.shapes.custom.svg.SvgImporter;
|
||||
import de.neemann.digital.draw.shapes.custom.svg.SvgTemplate;
|
||||
import de.neemann.digital.gui.Main;
|
||||
import de.neemann.digital.gui.SaveAsHelper;
|
||||
import de.neemann.digital.lang.Lang;
|
||||
import de.neemann.gui.ErrorMessage;
|
||||
import de.neemann.gui.MyFileChooser;
|
||||
import de.neemann.gui.ToolTipAction;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Editor used to define a custom shape.
|
||||
@ -23,6 +32,8 @@ public class CustomShapeEditor extends EditorFactory.LabelEditor<CustomShapeDesc
|
||||
private CustomShapeDescription customShapeDescription;
|
||||
private ToolTipAction clear;
|
||||
private ToolTipAction load;
|
||||
private ToolTipAction template;
|
||||
private static File lastSVGFile;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
@ -44,13 +55,51 @@ public class CustomShapeEditor extends EditorFactory.LabelEditor<CustomShapeDesc
|
||||
}
|
||||
};
|
||||
panel.add(clear.createJButton());
|
||||
load = new ToolTipAction(Lang.get("btn_load")) {
|
||||
load = new ToolTipAction(Lang.get("btn_loadSvg")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
customShapeDescription = CustomShapeDescription.createDummy();
|
||||
File path = null;
|
||||
if (lastSVGFile != null)
|
||||
path = lastSVGFile.getParentFile();
|
||||
JFileChooser fc = new MyFileChooser(path);
|
||||
if (lastSVGFile != null)
|
||||
fc.setSelectedFile(lastSVGFile);
|
||||
if (fc.showOpenDialog(getAttributeDialog()) == JFileChooser.APPROVE_OPTION) {
|
||||
lastSVGFile = fc.getSelectedFile();
|
||||
try {
|
||||
customShapeDescription = new SvgImporter(fc.getSelectedFile()).create();
|
||||
} catch (IOException | SvgException e1) {
|
||||
new ErrorMessage(Lang.get("msg_errorImportingSvg")).addCause(e1).show(getAttributeDialog());
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}.setToolTip(Lang.get("btn_loadSvg_tt"));
|
||||
panel.add(load.createJButton());
|
||||
template = new ToolTipAction(Lang.get("btn_saveTemplate")) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
File path = null;
|
||||
if (lastSVGFile != null)
|
||||
path = lastSVGFile.getParentFile();
|
||||
JFileChooser fc = new MyFileChooser(path);
|
||||
|
||||
if (fc.showSaveDialog(getAttributeDialog()) == JFileChooser.APPROVE_OPTION) {
|
||||
try {
|
||||
final Main main = getAttributeDialog().getMain();
|
||||
if (main != null) {
|
||||
File file = SaveAsHelper.checkSuffix(fc.getSelectedFile(), "svg");
|
||||
lastSVGFile = file;
|
||||
try (SvgTemplate tc = new SvgTemplate(file, main.getCircuitComponent().getCircuit())) {
|
||||
tc.create();
|
||||
}
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
new ErrorMessage(Lang.get("msg_errorCreatingSvgTemplate")).addCause(e1).show(getAttributeDialog());
|
||||
}
|
||||
}
|
||||
}
|
||||
}.setToolTip(Lang.get("btn_saveTemplate_tt"));
|
||||
panel.add(template.createJButton());
|
||||
return panel;
|
||||
}
|
||||
|
||||
@ -59,6 +108,7 @@ public class CustomShapeEditor extends EditorFactory.LabelEditor<CustomShapeDesc
|
||||
super.setEnabled(enabled);
|
||||
load.setEnabled(enabled);
|
||||
clear.setEnabled(enabled);
|
||||
template.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -45,6 +45,11 @@
|
||||
<string name="btn_apply">Setzen</string>
|
||||
<string name="btn_editRom_tt">Bearbeitet den Inhalt des ausgewählten ROM/EEPROM.</string>
|
||||
<string name="btn_clearRom_tt">Entfernt die hier gespeicherten Daten des ausgewählten ROM. Es wird stattdessen der direkt im ROM gespeicherte Inhalt verwendet.</string>
|
||||
<string name="btn_saveTemplate">Template</string>
|
||||
<string name="btn_saveTemplate_tt">Ein SVG Template erzeugen, welches dann mit z.B. Inkscape bearbeitet werden kann.</string>
|
||||
<string name="btn_loadSvg">Import</string>
|
||||
<string name="btn_loadSvg_tt">Importieren einer SVG-Datei. Um eine geeignete SVG Datei zu erstellen, ist es
|
||||
am einfachsten zunächst ein Template zu erzeugen und dieses dann zu bearbeiten.</string>
|
||||
<string name="msg_warning">Warnung</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="digital">Digital</string>
|
||||
@ -957,6 +962,7 @@ Sind evtl. die Namen der Variablen nicht eindeutig?</string>
|
||||
<string name="err_noRomFound">Kein Programmspeicher im Modell gefunden! Ein Programmspeicher muss gewählt werden!</string>
|
||||
<string name="err_moreThenOneRomFound">Mehr als einen Programmspeicher gefunden. Es darf nur einen Programmspeicher geben.</string>
|
||||
<string name="err_errorLoadingRomData">Fehler beim Laden des Programmspeichers.</string>
|
||||
<string name="err_parsingSVG">Fehler beim Laden der SVG-Datei.</string>
|
||||
|
||||
<string name="key_AddrBits">Adress-Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
|
||||
<string name="key_AddrBits_tt">Anzahl der Adress-Bits, die verwendet werden.</string>
|
||||
@ -1582,6 +1588,8 @@ Stellen Sie sicher, dass der Flash-Vorgang abgeschlossen ist, bevor Sie diesen D
|
||||
<string name="msg_testPassed_N">{0} Testzeilen überprüft</string>
|
||||
<string name="msg_testFile">Testdatei</string>
|
||||
<string name="msg_truthTable">Wahrheitstabelle</string>
|
||||
<string name="msg_errorImportingSvg">Fehler beim Import der SVG-Datei.</string>
|
||||
<string name="msg_errorCreatingSvgTemplate">Fehler beim Erzeugen der SVG-Datei.</string>
|
||||
|
||||
<string name="ok">Ok</string>
|
||||
<string name="rot_0">0°</string>
|
||||
|
@ -45,6 +45,11 @@
|
||||
<string name="btn_apply">Apply</string>
|
||||
<string name="btn_editRom_tt">Edits the content of the selected ROM/EEPROM</string>
|
||||
<string name="btn_clearRom_tt">Removes the stored data for the selected ROM. The content which is stored in the ROM directly is used instead.</string>
|
||||
<string name="btn_saveTemplate">Template</string>
|
||||
<string name="btn_saveTemplate_tt">Creates an SVG template which can then be edited with Inkscape.</string>
|
||||
<string name="btn_loadSvg">Import</string>
|
||||
<string name="btn_loadSvg_tt">Import an SVG file. To create a suitable SVG file, it is easiest to first create
|
||||
a SVG template and then edit it.</string>
|
||||
<string name="msg_warning">Warning</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="digital">Digital</string>
|
||||
@ -954,6 +959,7 @@
|
||||
<string name="err_noRomFound">No program memory found! The program memory needs to be flagged as such.</string>
|
||||
<string name="err_moreThenOneRomFound">More then one program memories found! Only one program memory must be flages as such.</string>
|
||||
<string name="err_errorLoadingRomData">Error loading the program memory.</string>
|
||||
<string name="err_parsingSVG">Error while reading the SVG file.</string>
|
||||
|
||||
<string name="key_AddrBits">Address Bits</string><!-- ROM, RAMDualPort, RAMSinglePort, RAMSinglePortSel, EEPROM -->
|
||||
<string name="key_AddrBits_tt">Number of address bits used.</string>
|
||||
@ -1570,6 +1576,8 @@ Make sure the flash process is complete before closing this dialog!</string>
|
||||
<string name="msg_testPassed_N">{0} test rows passed</string>
|
||||
<string name="msg_testFile">File Tested</string>
|
||||
<string name="msg_truthTable">Truth Table</string>
|
||||
<string name="msg_errorImportingSvg">Error while importing the SVG file.</string>
|
||||
<string name="msg_errorCreatingSvgTemplate">Error creating the SVG template.</string>
|
||||
|
||||
<string name="ok">OK</string>
|
||||
<string name="rot_0">0°</string>
|
||||
|
@ -10,42 +10,46 @@ import junit.framework.TestCase;
|
||||
public class PolygonTest extends TestCase {
|
||||
|
||||
public void testPath() {
|
||||
checkLine(Polygon.createFromPath("m 10,10 L 20,10 20,20 10,20 z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 l 10,0 0,10 -10,0 z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 h 10 v 10 h -10 z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 H 20 V 20 H 10 z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 L 20,10 20,20 10,20 Z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 l 10,0 0,10 -10,0 Z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 h 10 v 10 h -10 Z"));
|
||||
checkLine(Polygon.createFromPath("m 10,10 H 20 V 20 H 10 Z"));
|
||||
}
|
||||
|
||||
private void checkLine(Polygon p) {
|
||||
checkCoor(p);
|
||||
assertEquals("M 10,10 L 20,10 L 20,20 L 10,20 z", p.toString());
|
||||
}
|
||||
|
||||
private void checkCoor(Polygon p) {
|
||||
assertEquals(4, p.size());
|
||||
assertEquals(new VectorFloat(10, 10), p.get(0));
|
||||
assertEquals(new VectorFloat(20, 10), p.get(1));
|
||||
assertEquals(new VectorFloat(20, 20), p.get(2));
|
||||
assertEquals(new VectorFloat(10, 20), p.get(3));
|
||||
assertNotNull(p);
|
||||
assertEquals("M 10,10 L 20,10 L 20,20 L 10,20 Z", p.toString());
|
||||
}
|
||||
|
||||
private void checkBezier(Polygon p) {
|
||||
checkCoor(p);
|
||||
assertEquals("M 10,10 C 20,10 20,20 10,20 z", p.toString());
|
||||
assertEquals("M 10,10 C 20,10 20,20 10,20 Z", p.toString());
|
||||
}
|
||||
|
||||
public void testBezierPath() {
|
||||
checkBezier(Polygon.createFromPath("m 10,10 C 20 10 20 20 10 20 z"));
|
||||
checkBezier(Polygon.createFromPath("m 10,10 c 10 0 10 10 0 10 z"));
|
||||
checkBezier(Polygon.createFromPath("m 10,10 C 20 10 20 20 10 20 Z"));
|
||||
checkBezier(Polygon.createFromPath("m 10,10 c 10 0 10 10 0 10 Z"));
|
||||
}
|
||||
|
||||
public void testCubicReflect() {
|
||||
assertEquals("M 0,0 C 0,10 10,10 10,0 C 10,-10 20,-10 20,0 C 20,10 30,10 30,0 z",
|
||||
Polygon.createFromPath("m 0,0 c 0,10 10,10 10,0 s 10,-10 10,0 s 10,10 10,0 z").toString());
|
||||
assertEquals("M 0,0 C 0,10 10,10 10,0 C 10,-10 20,-10 20,0 C 20,10 30,10 30,0 Z",
|
||||
Polygon.createFromPath("m 0,0 c 0,10 10,10 10,0 s 10,-10 10,0 s 10,10 10,0 Z").toString());
|
||||
}
|
||||
|
||||
public void testQuadraticReflect() {
|
||||
assertEquals("M 0,0 C 20,20 40,20 60,0 C 80,-20 100,-20 120,0 C 140,20 160,20 180,0 C 200,-20 220,-20 240,0 ",
|
||||
assertEquals("M 0,0 Q 30,30 60,0 Q 90,-30 120,0 Q 150,30 180,0 Q 210,-30 240,0",
|
||||
Polygon.createFromPath("m 0,0 Q 30,30 60,0 t 60,0 t 60,0 t 60,0").toString());
|
||||
}
|
||||
|
||||
public void testMultiPath() {
|
||||
assertEquals("M 0,0 Q 0,60 60,60 Q 120,60 120,0 Q 120,-60 60,-60 Q 0,-60 0,0 Z M 30,0 Q 30,30 60,30 Q 90,30 90,0 Q 90,-30 60,-30 Q 30,-30 30,0 Z",
|
||||
Polygon.createFromPath("M 0,0 Q 0,60 60,60 T 120,0 T 60,-60 T 0,0 z M 30,0 Q 30,30 60,30 T 90,0 T 60,-30 T 30,0 Z").toString());
|
||||
}
|
||||
|
||||
public void testAppend() {
|
||||
Polygon p1 = new Polygon(false).add(0, 0).add(0, 10).add(10, 10);
|
||||
Polygon p2 = new Polygon(false).add(10, 10).add(10, 0).add(0, 0);
|
||||
|
||||
assertEquals("M 0,0 L 0,10 L 10,10 L 10,0 Z", p1.append(p2).toString());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.graphics;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class TransformMatrixTest extends TestCase {
|
||||
|
||||
public void testScale() {
|
||||
TransformMatrix tr = TransformMatrix.scale(2, 3);
|
||||
VectorInterface p = new VectorFloat(3, 4).transform(tr);
|
||||
assertEquals(6, p.getXFloat(), 1e-4);
|
||||
assertEquals(12, p.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
public void testRotate() {
|
||||
TransformMatrix tr = TransformMatrix.rotate(45);
|
||||
VectorInterface p = new VectorFloat(2, 0).transform(tr);
|
||||
assertEquals(Math.sqrt(2), p.getXFloat(), 1e-4);
|
||||
assertEquals(-Math.sqrt(2), p.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
public void testRotateInverse() {
|
||||
Transform tr = new TransformRotate(new Vector(2,3),1);
|
||||
VectorInterface p = new VectorFloat(7, 8);
|
||||
|
||||
VectorInterface t = p.transform(tr).transform(tr.invert());
|
||||
assertEquals(p.getXFloat(), t.getXFloat(), 1e-4);
|
||||
assertEquals(p.getYFloat(), t.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
public void testInverse() {
|
||||
TransformMatrix tr = new TransformMatrix(1, 2, 3, 4, 5, 6);
|
||||
VectorInterface p = new VectorFloat(7, 8);
|
||||
|
||||
VectorInterface t = p.transform(tr).transform(tr.invert());
|
||||
assertEquals(p.getXFloat(), t.getXFloat(), 1e-4);
|
||||
assertEquals(p.getYFloat(), t.getYFloat(), 1e-4);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.graphics.linemerger;
|
||||
|
||||
import de.neemann.digital.draw.graphics.*;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class GraphicLineCollectorTest extends TestCase {
|
||||
|
||||
public void testOpenSquare() {
|
||||
GraphicLineCollector col = new GraphicLineCollector();
|
||||
col.drawLine(new Vector(0, 0), new Vector(10, 0), Style.NORMAL);
|
||||
col.drawLine(new Vector(0, 10), new Vector(10, 10), Style.NORMAL);
|
||||
|
||||
col.drawLine(new Vector(0, 0), new Vector(0, 10), Style.NORMAL);
|
||||
|
||||
ArrayList<Polygon> poly = new ArrayList<>();
|
||||
col.drawTo(new MyGraphic(poly));
|
||||
|
||||
assertEquals(1, poly.size());
|
||||
assertEquals("M 10,0 L 0,0 L 0,10 L 10,10", poly.get(0).toString());
|
||||
}
|
||||
|
||||
public void testClosedSquare() {
|
||||
GraphicLineCollector col = new GraphicLineCollector();
|
||||
col.drawLine(new Vector(0, 0), new Vector(10, 0), Style.NORMAL);
|
||||
col.drawLine(new Vector(0, 10), new Vector(10, 10), Style.NORMAL);
|
||||
|
||||
col.drawLine(new Vector(0, 0), new Vector(0, 10), Style.NORMAL);
|
||||
col.drawLine(new Vector(10, 0), new Vector(10, 10), Style.NORMAL);
|
||||
|
||||
ArrayList<Polygon> poly = new ArrayList<>();
|
||||
col.drawTo(new MyGraphic(poly));
|
||||
|
||||
assertEquals(1, poly.size());
|
||||
assertEquals("M 10,0 L 0,0 L 0,10 L 10,10 Z", poly.get(0).toString());
|
||||
}
|
||||
|
||||
public void testClosedSquare2() {
|
||||
GraphicLineCollector col = new GraphicLineCollector();
|
||||
col.drawLine(new Vector(0, 0), new Vector(10, 0), Style.NORMAL);
|
||||
col.drawLine(new Vector(0, 10), new Vector(10, 10), Style.NORMAL);
|
||||
|
||||
col.drawLine(new Vector(0, 0), new Vector(0, 10), Style.NORMAL);
|
||||
col.drawLine(new Vector(10, 0), new Vector(10, 10), Style.NORMAL);
|
||||
|
||||
ArrayList<Polygon> poly = new ArrayList<>();
|
||||
col.drawTo(new MyGraphic(poly));
|
||||
|
||||
assertEquals(1, poly.size());
|
||||
assertEquals("M 10,0 L 0,0 L 0,10 L 10,10 Z", poly.get(0).toString());
|
||||
}
|
||||
|
||||
private static class MyGraphic implements Graphic {
|
||||
private final ArrayList<Polygon> poly;
|
||||
|
||||
private MyGraphic(ArrayList<Polygon> poly) {
|
||||
this.poly = poly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawLine(VectorInterface p1, VectorInterface p2, Style style) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawPolygon(Polygon p, Style style) {
|
||||
poly.add(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawCircle(VectorInterface p1, VectorInterface p2, Style style) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawText(VectorInterface p1, VectorInterface p2, String text, Orientation orientation, Style style) {
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,444 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Helmut Neemann.
|
||||
* Use of this source code is governed by the GPL v3 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
package de.neemann.digital.draw.shapes.custom;
|
||||
|
||||
import de.neemann.digital.draw.elements.PinException;
|
||||
import de.neemann.digital.draw.graphics.Polygon;
|
||||
import de.neemann.digital.draw.graphics.PolygonParser;
|
||||
import de.neemann.digital.draw.graphics.VectorInterface;
|
||||
import de.neemann.digital.draw.shapes.Drawable;
|
||||
import de.neemann.digital.draw.shapes.custom.svg.SvgException;
|
||||
import de.neemann.digital.draw.shapes.custom.svg.SvgImporter;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SvgImporterTest extends TestCase {
|
||||
|
||||
public void testPin() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <path d=\"M 0,0 l 0,100 l 100,0 l 0,-100\" />\n" +
|
||||
" <circle\n" +
|
||||
" id=\"pin:test\"\n" +
|
||||
" cx=\"22\"\n" +
|
||||
" cy=\"19\"\n" +
|
||||
" r=\"6.0923076\" />\n" +
|
||||
" <circle\n" +
|
||||
" id=\"pin+:test2\"\n" +
|
||||
" cx=\"0\"\n" +
|
||||
" cy=\"45\"\n" +
|
||||
" r=\"6.0923076\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 0,100 L 100,100 L 100,0")
|
||||
.checkPin(20, 20, "test", false)
|
||||
.checkPin(0, 40, "test2", true)
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPath() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <path d=\"M 0,0 l 0,100 l 100,0 l 0,-100\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 0,100 L 100,100 L 100,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPathEvenOdd() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <path fill-rule=\"evenOdd\" d=\"M 0,0 l 0,100 l 100,0 l 0,-100\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 0,100 L 100,100 L 100,0", true)
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPathEvenOdd2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <path style=\"fill-rule:evenOdd\" d=\"M 0,0 l 0,100 l 100,0 l 0,-100\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 0,100 L 100,100 L 100,0", true)
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolyline() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <polyline points=\"0,0 0,100 100,100 100,0\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 0,100 L 100,100 L 100,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygon() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <polygon points=\"0,0 0,100 100,100 100,0\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 0,100 L 100,100 L 100,0 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygonTranslated() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <polygon transform=\"translate(10,20)\" points=\"0,0 0,100 100,100 100,0\" />\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 10,20 L 10,120 L 110,120 L 110,20 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygonTranslated2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <g transform=\"translate(10,20)\">\n" +
|
||||
" <polygon points=\"0,0 0,100 100,100 100,0\" />\n" +
|
||||
" </g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 10,20 L 10,120 L 110,120 L 110,20 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygonTranslated3() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
" <g transform=\"translate(0,20)\">\n" +
|
||||
" <polygon transform=\"translate(10,0)\" points=\"0,0 0,100 100,100 100,0\" />\n" +
|
||||
" </g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 10,20 L 10,120 L 110,120 L 110,20 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygonRotate() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<g transform=\"rotate(30)\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\"\n" +
|
||||
"d=\"M 0,0 L 0,100 L 100,100 L 100,0 Z\"/>\n" +
|
||||
"</g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L -50,86.60254 L 36.60254,136.60254 L 86.60254,50 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygonRotate2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<g transform=\"rotate(45, 50, 50)\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\"\n" +
|
||||
"d=\"M 0,0 L 0,100 L 100,100 L 100,0 Z\"/>\n" +
|
||||
"</g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 50,-20.710678 L -20.710678,50 L 50,120.71068 L 120.71068,50 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testPolygonMatrix() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<g transform=\"matrix(0.8,0.2,0.1,0.9,5,10)\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\"\n" +
|
||||
"d=\"M 0,0 L 0,100 L 100,100 L 100,0 Z\"/>\n" +
|
||||
"</g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 5,10 L 15,100 L 95,120 L 85,30 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testRect() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<rect fill=\"none\" stroke=\"black\" \n" +
|
||||
"x=\"10\" y=\"20\" width=\"70\" height=\"80\"/>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 10,20 L 80,20 L 80,100 L 10,100 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testRectRound() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<rect fill=\"none\" stroke=\"black\" \n" +
|
||||
"x=\"10\" y=\"20\" rx=\"10\" ry=\"20\" width=\"70\" height=\"80\"/>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 70,20 C 75.52285,20 80,28.954304 80,40 L 80,80 C 80,91.04569 75.52285,100 70,100 L 20,100 C 14.477152,100 10,91.04569 10,80 L 10,40 C 10,28.954304 14.477152,20 20,20 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testCircle() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<g transform=\"matrix(0.8,0.2,0.1,0.9,5,10)\">\n" +
|
||||
"<circle fill=\"none\" stroke=\"black\" \n" +
|
||||
"cx=\"50\" cy=\"60\" r=\"30\" />\n" +
|
||||
"</g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 27,68 C 28.656855,82.91169 40.745167,97.686295 54,101 C 67.25484,104.313705 76.65685,94.91169 75,80 C 73.34315,65.08831 61.254833,50.31371 48,47 C 34.745167,43.68629 25.343145,53.08831 27,68 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testScale() throws IOException, SvgException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<g transform=\"scale(2,3)\">\n" +
|
||||
"<line fill=\"none\" stroke=\"black\" \n" +
|
||||
"x1=\"10\" y1=\"20\" x2=\"80\" y2=\"70\" />\n" +
|
||||
"</g>\n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkLine(20, 60, 160, 210)
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,0,0,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 37.161327,16.337067 40.99908,25.612656 Q 44.83683,34.888245 54.32269,34.616966 Q 63.80854,34.345688 76.40078,24.600235 Q 88.27738,15.408623 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,1,0,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 27.571037,16.337067 18.5496,35.35811 Q 9.528162,54.37915 6.331539,70.987495 Q 3.1349144,87.59584 6.6196365,97.3413 Q 10.104355,107.086754 19.336689,107.358025 Q 28.569027,107.6293 41.075184,98.35372 Q 53.581345,89.078125 66.0103,72.74106 Q 78.43926,56.403996 87.46069,37.38295 Q 96.48214,18.36191 99.67876,1.7535629 Q 99.84891,0.86956406 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc3() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,0,1,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 52.42896,-16.33707 64.93511,-25.612656 Q 77.44127,-34.88825 86.67361,-34.616966 Q 95.905945,-34.345688 99.39067,-24.600235 Q 102.67735,-15.408623 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testArc4() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 0,0 L 40,0 A 31,80,30,1,1,100,0 L 140,0\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 0,0 L 40,0 Q 42.83867,-16.337069 51.593147,-35.35811 Q 60.347626,-54.379158 72.67216,-70.987495 Q 84.99669,-87.59584 97.58891,-97.3413 Q 110.18115,-107.086754 119.66701,-107.35804 Q 129.15286,-107.62932 132.99062,-98.353714 Q 136.82837,-89.07814 133.98969,-72.74107 Q 131.15103,-56.403996 122.396545,-37.382957 Q 113.64207,-18.361908 101.317535,-1.7535667 Q 100.661545,-0.86956406 100,0 L 140,0")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testInkscape1() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"m 40,-40 h 20 c 11.08,0 20,8.92 20,20 V 20 C 80,31.08 71.08,40 60,40 H 40 C 28.92,40 20,31.08 20,20 v -40 c 0,-11.08 8.92,-20 20,-20 z\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 40,-40 L 60,-40 C 71.08,-40 80,-31.08 80,-20 L 80,20 C 80,31.08 71.08,40 60,40 L 40,40 C 28.92,40 20,31.08 20,20 L 20,-20 C 20,-31.08 28.92,-40 40,-40 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testInkscape2() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 80,0 A 40,40 0 0 1 58.083689,35.678848 40,40 0 0 1 16.350991,32.26026 L 40,0 Z\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 80,0 Q 80,10.717968 74.641014,20 Q 69.282036,29.282032 60,34.641018 Q 59.05599,35.186043 58.08369,35.67885 Q 48.52357,40.52436 37.82151,39.940636 Q 27.119452,39.35691 18.143057,33.500362 Q 17.230127,32.904728 16.35099,32.26026 L 40,0 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
public void testInkscape3() throws IOException, SvgException, PolygonParser.ParserException, PinException {
|
||||
CustomShapeDescription custom = new SvgImporter(
|
||||
in("<svg viewBox=\"0 0 200 100\" xmlns=\"http://www.w3.org/2000/svg\">\n" +
|
||||
"<path fill=\"none\" stroke=\"black\" stroke-width=\"3\"\n" +
|
||||
" d=\"M 4.71136,11.590742 A 15,47.5 0 0 1 -3.5072565,53.959374 15,47.5 0 0 1 -19.157018,49.899801 l 8.868378,-38.309059 z\"" +
|
||||
" transform=\"rotate(-41.594188)\"/> \n" +
|
||||
"</svg>")).create();
|
||||
|
||||
new CSDChecker(custom)
|
||||
.checkPolygon("M 11.217981,5.540678 Q 19.667194,15.0592 25.481504,24.63657 Q 31.29581,34.213936 32.91727,41.2839 Q 33.082184,42.00294 33.19799,42.68264 Q 34.336685,49.36582 30.875134,51.51164 Q 27.413582,53.657448 20.279312,50.690926 Q 19.55373,50.389217 18.799128,50.03573 L -1.9073486E-6,15.498432 Z")
|
||||
.check();
|
||||
}
|
||||
|
||||
|
||||
//*****************************************************************************************************
|
||||
|
||||
|
||||
private InputStream in(String s) {
|
||||
return new ByteArrayInputStream(s.getBytes());
|
||||
}
|
||||
|
||||
private static class CSDChecker {
|
||||
private final CustomShapeDescription csd;
|
||||
private final ArrayList<Checker> checker;
|
||||
private final ArrayList<TestPin> pins;
|
||||
|
||||
private CSDChecker(CustomShapeDescription csd) {
|
||||
this.csd = csd;
|
||||
this.checker = new ArrayList<>();
|
||||
this.pins = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void check() throws PinException {
|
||||
checkShape();
|
||||
checkPins();
|
||||
}
|
||||
|
||||
private void checkPins() throws PinException {
|
||||
assertEquals("wrong number of pins", pins.size(), csd.getPinCount());
|
||||
for (TestPin tp : pins) {
|
||||
CustomShapeDescription.Pin p = csd.getPin(tp.name);
|
||||
assertEquals("wrong pin x coordinate", tp.x, p.getPos().x);
|
||||
assertEquals("wrong pin y coordinate", tp.y, p.getPos().y);
|
||||
assertEquals("wrong pin label", tp.showLabel, p.isShowLabel());
|
||||
}
|
||||
}
|
||||
|
||||
private void checkShape() {
|
||||
int i = 0;
|
||||
for (Drawable d : csd) {
|
||||
if (i >= checker.size())
|
||||
fail("to much elements found in the csd");
|
||||
checker.get(i).check(d);
|
||||
i++;
|
||||
}
|
||||
if (i != checker.size())
|
||||
fail("not enough elements found in the csd");
|
||||
}
|
||||
|
||||
private CSDChecker checkPolygon(String s) throws PolygonParser.ParserException {
|
||||
checker.add(new CheckPolygon(new PolygonParser(s).create()));
|
||||
return this;
|
||||
}
|
||||
|
||||
private CSDChecker checkPolygon(String s, boolean evenOdd) throws PolygonParser.ParserException {
|
||||
checker.add(new CheckPolygon(new PolygonParser(s).create().setEvenOdd(evenOdd)));
|
||||
return this;
|
||||
}
|
||||
|
||||
private CSDChecker checkPin(int x, int y, String name, boolean showLabel) {
|
||||
pins.add(new TestPin(x, y, name, showLabel));
|
||||
return this;
|
||||
}
|
||||
|
||||
private CSDChecker checkLine(int x1, int y1, int x2, int y2) {
|
||||
checker.add(new Checker() {
|
||||
@Override
|
||||
public void check(Drawable d) {
|
||||
assertTrue("element is no line", d instanceof CustomShapeDescription.LineHolder);
|
||||
CustomShapeDescription.LineHolder l = (CustomShapeDescription.LineHolder) d;
|
||||
assertEquals(x1, l.getP1().x);
|
||||
assertEquals(y1, l.getP1().y);
|
||||
assertEquals(x2, l.getP2().x);
|
||||
assertEquals(y2, l.getP2().y);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class TestPin {
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final String name;
|
||||
private final boolean showLabel;
|
||||
|
||||
private TestPin(int x, int y, String name, boolean showLabel) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.name = name;
|
||||
this.showLabel = showLabel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private interface Checker {
|
||||
void check(Drawable d);
|
||||
}
|
||||
|
||||
private static class CheckPolygon implements Checker {
|
||||
|
||||
private final Polygon should;
|
||||
|
||||
private CheckPolygon(Polygon should) {
|
||||
this.should = should;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void check(Drawable d) {
|
||||
assertTrue("no polygon found", d instanceof CustomShapeDescription.PolygonHolder);
|
||||
final Polygon polygon = ((CustomShapeDescription.PolygonHolder) d).getPolygon();
|
||||
assertEquals("wrong evanOdd mode", should.getEvenOdd(), polygon.getEvenOdd());
|
||||
|
||||
ArrayList<VectorInterface> shouldPoints = new ArrayList<>();
|
||||
should.traverse(shouldPoints::add);
|
||||
ArrayList<VectorInterface> isPoints = new ArrayList<>();
|
||||
polygon.traverse(isPoints::add);
|
||||
|
||||
//System.out.println(polygon);
|
||||
|
||||
assertEquals("not the correct polygon size", shouldPoints.size(), isPoints.size());
|
||||
for (int i = 0; i < shouldPoints.size(); i++) {
|
||||
VectorInterface sh = shouldPoints.get(i);
|
||||
VectorInterface is = isPoints.get(i);
|
||||
assertEquals("x coordinate " + i, sh.getXFloat(), is.getXFloat(), 1e-4);
|
||||
assertEquals("y coordinate " + i, sh.getYFloat(), is.getYFloat(), 1e-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -405,7 +405,7 @@
|
||||
</par>
|
||||
<par>
|
||||
Soll eine Schaltung auf einem BASYS3 Board betrieben werden, wird eine Vivado Projektdatei angelegt,
|
||||
die direkt mit Vivado geöffnet werden kann. Es läßt sich dann der Bitstream erzeugen und mit dem
|
||||
die direkt mit Vivado geöffnet werden kann. Es lässt sich dann der Bitstream erzeugen und mit dem
|
||||
Hardware-Manager kann dieser in ein BASYS3 Board übertragen werden.
|
||||
</par>
|
||||
<par>
|
||||
@ -414,6 +414,49 @@
|
||||
</par>
|
||||
</subchapter>
|
||||
</chapter>
|
||||
<chapter name="Benutzerdefinierter Schaltungssymbole">
|
||||
<par>
|
||||
Digital bietet zwar einige Optionen, die die Erscheinungsform einer Schaltung festlegen, wenn diese
|
||||
in eine andere eingebettet wird, in manchen Fällen kann es jedoch sinnvoll sein, ein spezielles
|
||||
eigenes Symbol für eine Teilschaltung zu verwenden.
|
||||
Ein Beispiel ist die Darstellung der ALU in dem Prozessor, welcher in den Beispielen enthalten ist.
|
||||
In diesem Kapitel wird erklärt, wie eine solches spezielles Symbol für eine Schaltung definiert wird.
|
||||
</par>
|
||||
<par>
|
||||
Digital bietet keinen Editor für das Erstellen eines speziellen Symbols an. Statt dessen ist ein kleiner
|
||||
Umweg für das Erstellen von Schaltungssymbolen erforderlich:
|
||||
Zunächst wird die Schaltung geöffnet, welche durch ein spezielles Symbol repräsentiert werden soll.
|
||||
Dann wird für diese Schaltung ein SVG-Template erstellt. In diesem Template wird die Schaltung durch
|
||||
ein einfaches Rechteck repräsentiert. Es finden sich darin auch alle Pins der Schaltung,
|
||||
repräsentiert durch blaue (Eingänge) und rote (Ausgänge) Kreise.
|
||||
Um zu sehen, welcher Kreis zu welchem Pin gehört, kann man sich in den Objekteigenschaften die ID des
|
||||
Kreises ansehen. Diese ID hat die Form <e>pin:[Name]</e> oder <e>pin+:[Name]</e>. Bei der letzteren
|
||||
Variante wird der Pin beim Reimport zu Digital mit einem Label versehen. Wünscht man kein solches Label,
|
||||
kann das <e>+</e> entfernt werden.
|
||||
</par>
|
||||
<par>
|
||||
Diese SVG-Datei kann jetzt bearbeitet werden. Am besten geeignet ist das Open-Source Programm
|
||||
<a href="https://inkscape.org/de/">Inkscape</a> welches kostenlos verfügbar ist.
|
||||
Die Pins können frei verschoben werden, werden jedoch beim Reimport auf den nächstgelegenen Rasterpunkt
|
||||
verschoben.
|
||||
</par>
|
||||
<par>
|
||||
Wenn schon existierende SVG Dateien verwendet werden sollen, ist es das einfachste das erstellte
|
||||
Template zu öffnen, und die bereits vorhandene Grafik per Copy&Paste in das Template einzufügen.
|
||||
</par>
|
||||
<par>
|
||||
Wenn die Datei gespeichert wurde, kann diese mit Digital importiert werden. Dabei wird die Datei
|
||||
eingelesen und alle notwendigen Informationen werden extrahiert und in der Schaltung gespeichert.
|
||||
Für die weitere Verwendung der Schaltung ist die SVG-Datei also nicht mehr erforderlich.
|
||||
</par>
|
||||
<par>
|
||||
Noch eine Anmerkung: SVG ist ein sehr mächtiges und flexibles Dateiformat. Es können extrem
|
||||
komplexe Grafiken damit beschrieben werden. Der in Digital integrierte Importer ist nicht in der
|
||||
Lage alle denkbaren SVG-Dateien fehlerfrei zu importieren. Wenn eine Datei nicht importiert werden kann,
|
||||
oder nicht so erscheint wie erwartet, ist evtl. einiges Experimentieren erforderlich, bevor das
|
||||
gewünschte Ergebnis erreicht ist.
|
||||
</par>
|
||||
</chapter>
|
||||
<chapter name="Häufig gestellte Fragen">
|
||||
<faq>
|
||||
<question>Wie kann ich eine Leitung verschieben?</question>
|
||||
|
@ -388,6 +388,44 @@
|
||||
</par>
|
||||
</subchapter>
|
||||
</chapter>
|
||||
<chapter name="Custom Shapes">
|
||||
<par>
|
||||
Although Digital has some options that determine the appearance of a circuit when it is embedded in
|
||||
another, in some cases it may be useful to use a very special shape for a subcircuit. An example is
|
||||
the representation of the ALU in the processor included in the examples. This chapter explains how to
|
||||
define such a special shape for a circuit.
|
||||
</par>
|
||||
<par>
|
||||
Digital does not provide an editor for creating a special shape. Instead, a small detour is required
|
||||
for creating circuit shapes: First, the circuit is opened, which is to be represented by a special shape.
|
||||
Then an SVG template is created for this circuit. In this template, the circuit is represented by a
|
||||
simple rectangle. It also contains all the pins of the circuit, represented by blue (inputs) and
|
||||
red (outputs) circuits. To see which circle belongs to which pin, you can look at the ID of the
|
||||
circle in the object properties. This ID has the form <e>pin:[name]</e> or <e>pin+:[name]</e>.
|
||||
In the latter variant, the pin is provided with a label if reimported to digital.
|
||||
If you do not want such a label, the <e>+</e> can be removed.
|
||||
</par>
|
||||
<par>
|
||||
This SVG file can now be edited. The most suitable is the open source program
|
||||
<a href="https://inkscape.org/en/">Inkscape</a> which is available for free.
|
||||
The pins can be moved freely, but are moved to the next grid point during the reimport.
|
||||
</par>
|
||||
<par>
|
||||
If existing SVG files are to be used, it is easiest to open the created template and paste the
|
||||
existing graphic into the template via Copy&Paste.
|
||||
</par>
|
||||
<par>
|
||||
If the file was saved, it can be imported with Digital. The file is read in and all necessary
|
||||
information is extracted and stored in the circuit. For further use of the circuit, the SVG
|
||||
file is no longer required.
|
||||
</par>
|
||||
<par>
|
||||
A final remark: SVG is a very powerful and flexible file format.
|
||||
It can be used to describe extremely complex graphics. The Digital importer is not able to import all
|
||||
possible SVG files without errors. If a file can not be imported, or does not appear as expected,
|
||||
some experimentation may be required before the desired result is achieved.
|
||||
</par>
|
||||
</chapter>
|
||||
<chapter name="Frequently asked Questions">
|
||||
<faq>
|
||||
<question>How to move a wire?</question>
|
||||
|
Loading…
x
Reference in New Issue
Block a user