4 Commits

19 changed files with 518 additions and 42 deletions

View File

@@ -1,11 +1,8 @@
# TODO
![](out.svg)
- [ ] Box selection (drag to select multiple shapes)
- [ ] Undo/redo stack
- [ ] Copy/paste functionality
- [ ] Text shapes
- [X] Text shapes
- [ ] Resize shapes
- [ ] Polygon shapes

View File

@@ -8,7 +8,6 @@ import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.ShapesView;
import ovh.gasser.newshapes.ui.listeners.MenuAddListener;
import ovh.gasser.newshapes.ui.listeners.MenuEditListener;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import javax.swing.*;
import java.awt.*;
@@ -39,12 +38,7 @@ public class App {
this.buildMenuBar(frame, view);
view.addSelectionChangeListener(new SelectionListener() {
@Override
public void onSelectionChanged(Iterable<Shape> selectedShapes) {
updateMenuState(selectedShapes);
}
});
view.addSelectionChangeListener(this::updateMenuState);
}
private void buildModel() {
@@ -79,11 +73,13 @@ public class App {
JMenu menuFile = new JMenu("File");
JMenuItem addRectItem = new JMenuItem("Add SRectangle");
JMenuItem addCircleItem = new JMenuItem("Add SCircle");
JMenuItem addTextItem = new JMenuItem("Add Text");
JMenuItem htmlExportItem = new JMenuItem("Export to HTML");
JMenuItem svgExportItem = new JMenuItem("Export to SVG");
JMenuItem exitItem = new JMenuItem("Exit");
addRectItem.addActionListener(new MenuAddListener("SRectangle", model, sview));
addCircleItem.addActionListener(new MenuAddListener("SCircle", model, sview));
addTextItem.addActionListener(evt -> sview.getController().enterTextMode());
htmlExportItem.addActionListener(evt -> {
try {
new HTMLExporter(model).export();
@@ -101,6 +97,7 @@ public class App {
exitItem.addActionListener(evt -> System.exit(0));
menuFile.add(addRectItem);
menuFile.add(addCircleItem);
menuFile.add(addTextItem);
menuFile.addSeparator();
menuFile.add(htmlExportItem);
menuFile.add(svgExportItem);
@@ -132,21 +129,24 @@ public class App {
}
private void updateMenuState(Iterable<Shape> selectedShapes) {
boolean hasAttributes = false;
boolean hasToggleableShapes = false;
boolean allFilled = true;
boolean allStroked = true;
for (Shape s : selectedShapes) {
if (s instanceof SText) {
continue;
}
ColorAttributes attrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID);
if (attrs != null) {
hasAttributes = true;
hasToggleableShapes = true;
allFilled = allFilled && attrs.filled;
allStroked = allStroked && attrs.stroked;
}
}
updateMenuItem(editFill, hasAttributes, allFilled);
updateMenuItem(editBorder, hasAttributes, allStroked);
updateMenuItem(editFill, hasToggleableShapes, allFilled);
updateMenuItem(editBorder, hasToggleableShapes, allStroked);
}
private void updateMenuItem(JCheckBoxMenuItem menuItem, boolean hasAttributes, boolean allSelected) {

View File

@@ -38,6 +38,10 @@ public class Selection implements Streamable<Shape> {
notifyListeners();
}
public boolean isEmpty() {
return selectedShapes.isEmpty();
}
public List<Shape> getSelectedShapes() {
return List.copyOf(selectedShapes);
}

View File

@@ -3,6 +3,7 @@ package ovh.gasser.newshapes;
import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SRectangle;
import ovh.gasser.newshapes.shapes.SText;
import ovh.gasser.newshapes.shapes.STriangle;
public interface ShapeVisitor {
@@ -13,4 +14,6 @@ public interface ShapeVisitor {
void visitCircle(SCircle sCircle);
void visitTriangle(STriangle sTriangle);
void visitText(SText sText);
}

View File

@@ -34,9 +34,48 @@ public abstract class AbstractShape implements Shape {
getBounds().translate(dx, dy);
}
@Override
public void resize(ResizeHandle handle, int dx, int dy) {
Rectangle bounds = getBounds();
switch (handle) {
case E -> bounds.width += dx;
case W -> {
bounds.x += dx;
bounds.width -= dx;
}
case S -> bounds.height += dy;
case N -> {
bounds.y += dy;
bounds.height -= dy;
}
case SE -> {
bounds.width += dx;
bounds.height += dy;
}
case SW -> {
bounds.x += dx;
bounds.width -= dx;
bounds.height += dy;
}
case NE -> {
bounds.width += dx;
bounds.y += dy;
bounds.height -= dy;
}
case NW -> {
bounds.x += dx;
bounds.width -= dx;
bounds.y += dy;
bounds.height -= dy;
}
}
if (bounds.width < 1) bounds.width = 1;
if (bounds.height < 1) bounds.height = 1;
}
@Override
public Rectangle getBounds() {
return this.bounds;
return new Rectangle(this.bounds);
}
@Override

View File

@@ -0,0 +1,24 @@
package ovh.gasser.newshapes.shapes;
import java.awt.Cursor;
public enum ResizeHandle {
NW(Cursor.NW_RESIZE_CURSOR),
N(Cursor.N_RESIZE_CURSOR),
NE(Cursor.NE_RESIZE_CURSOR),
E(Cursor.E_RESIZE_CURSOR),
SE(Cursor.SE_RESIZE_CURSOR),
S(Cursor.S_RESIZE_CURSOR),
SW(Cursor.SW_RESIZE_CURSOR),
W(Cursor.W_RESIZE_CURSOR);
private final int cursorType;
ResizeHandle(int cursorType) {
this.cursorType = cursorType;
}
public int getCursorType() {
return cursorType;
}
}

View File

@@ -8,7 +8,7 @@ import java.awt.*;
public class SCircle extends AbstractShape {
private final int radius;
private int radius;
private SCircle(int x, int y, int radius) {
super(new Rectangle(x, y, radius * 2, radius * 2));
@@ -20,10 +20,37 @@ public class SCircle extends AbstractShape {
visitor.visitCircle(this);
}
@Override
public void resize(ResizeHandle handle, int dx, int dy) {
Rectangle bounds = getBounds();
int newWidth = bounds.width;
int newHeight = bounds.height;
switch (handle) {
case E, W -> newWidth += dx;
case N, S -> newHeight += dy;
case SE, NW -> {
newWidth += dx;
newHeight += dy;
}
case NE, SW -> {
newWidth += dx;
newHeight += dy;
}
}
if (newWidth < 2) newWidth = 2;
if (newHeight < 2) newHeight = 2;
this.radius = Math.max(newWidth, newHeight) / 2;
bounds.setSize(this.radius * 2, this.radius * 2);
}
@Override
public Shape clone() {
var color = (ColorAttributes) getAttributes(ColorAttributes.ID);
return SCircle.create(super.getBounds().x, super.getBounds().y, this.radius, color.strokedColor);
Color strokeColor = color != null ? color.strokedColor : Color.BLACK;
return SCircle.create(super.getBounds().x, super.getBounds().y, this.radius, strokeColor);
}
public int getRadius() {

View File

@@ -39,7 +39,7 @@ public class SCollection extends AbstractShape implements Streamable<Shape> {
for (Shape s : children) bounds = bounds.union(s.getBounds());
return bounds;
} catch (IndexOutOfBoundsException e){
logger.error("getBounds(): {e}", e);
logger.error("getBounds(): {}", e);
throw new RuntimeException(e);
}
}

View File

@@ -27,7 +27,8 @@ public class SRectangle extends AbstractShape {
@Override
public Shape clone() {
var color = (ColorAttributes) this.getAttributes(ColorAttributes.ID);
return SRectangle.create(super.getBounds().x, super.getBounds().y, getBounds().width, getBounds().height, color.strokedColor);
Color strokeColor = color != null ? color.strokedColor : Color.BLACK;
return SRectangle.create(super.getBounds().x, super.getBounds().y, getBounds().width, getBounds().height, strokeColor);
}
public static SRectangle create(int x, int y, int width, int height) {

View File

@@ -0,0 +1,79 @@
package ovh.gasser.newshapes.shapes;
import ovh.gasser.newshapes.ShapeVisitor;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.attributes.SelectionAttributes;
import java.awt.*;
public class SText extends AbstractShape {
public static final String PLACEHOLDER_TEXT = "Text";
public static final int DEFAULT_FONT_SIZE = 16;
public static final String DEFAULT_FONT_NAME = "SansSerif";
public static final int DEFAULT_FONT_STYLE = Font.PLAIN;
private final String text;
private final int fontSize;
private final String fontName;
private final int fontStyle;
private SText(int x, int y, String text, int fontSize, String fontName, int fontStyle) {
super(new Rectangle(x, y, 0, 0));
this.text = normalizeText(text);
this.fontSize = fontSize;
this.fontName = fontName;
this.fontStyle = fontStyle;
}
public static SText create(int x, int y, String text) {
var shape = new SText(x, y, text, DEFAULT_FONT_SIZE, DEFAULT_FONT_NAME, DEFAULT_FONT_STYLE);
shape.addAttributes(new SelectionAttributes());
shape.addAttributes(new ColorAttributes(true, false, Color.BLACK, Color.BLACK));
return shape;
}
private static String normalizeText(String input) {
if (input == null || input.isBlank()) {
return PLACEHOLDER_TEXT;
}
return input;
}
public String getText() {
return text;
}
public int getFontSize() {
return fontSize;
}
public String getFontName() {
return fontName;
}
public int getFontStyle() {
return fontStyle;
}
public void updateMeasuredBounds(int width, int height) {
getBounds().setSize(Math.max(width, 0), Math.max(height, 0));
}
@Override
public void accept(ShapeVisitor visitor) {
visitor.visitText(this);
}
@Override
public Shape clone() {
var copy = new SText(getBounds().x, getBounds().y, text, fontSize, fontName, fontStyle);
copy.updateMeasuredBounds(getBounds().width, getBounds().height);
copy.addAttributes(new SelectionAttributes());
var attrs = (ColorAttributes) getAttributes(ColorAttributes.ID);
if (attrs != null) {
copy.addAttributes(new ColorAttributes(attrs.filled, attrs.stroked, attrs.filledColor, attrs.strokedColor));
}
return copy;
}
}

View File

@@ -16,10 +16,60 @@ public class STriangle extends AbstractShape {
visitor.visitTriangle(this);
}
@Override
public void resize(ResizeHandle handle, int dx, int dy) {
Rectangle bounds = getBounds();
int delta = Math.max(Math.abs(dx), Math.abs(dy));
boolean shrink = switch (handle) {
case SE -> (dx < 0 || dy < 0);
case NW -> (dx > 0 || dy > 0);
case NE -> (dx < 0);
case SW -> (dx > 0);
case E, W -> (dx < 0);
case N, S -> (dy < 0);
default -> false;
};
int sizeChange = shrink ? -delta : delta;
switch (handle) {
case SE, E, W -> {
bounds.width += sizeChange;
bounds.height += sizeChange;
}
case NW -> {
bounds.x -= sizeChange;
bounds.width += sizeChange;
bounds.y -= sizeChange;
bounds.height += sizeChange;
}
case NE -> {
bounds.y -= sizeChange;
bounds.width += sizeChange;
bounds.height += sizeChange;
}
case SW -> {
bounds.x -= sizeChange;
bounds.width += sizeChange;
bounds.height += sizeChange;
}
case N, S -> {
bounds.width += sizeChange;
bounds.height += sizeChange;
}
}
if (bounds.width < 1) bounds.width = 1;
if (bounds.height < 1) bounds.height = 1;
}
@Override
public Shape clone() {
var color = (ColorAttributes) getAttributes(ColorAttributes.ID);
return STriangle.create(super.getBounds().x, super.getBounds().y, super.getBounds().height, color.strokedColor, color.filledColor);
Color strokeColor = color != null ? color.strokedColor : Color.BLACK;
Color fillColor = color != null ? color.filledColor : Color.BLACK;
return STriangle.create(super.getBounds().x, super.getBounds().y, super.getBounds().height, fillColor, strokeColor);
}
public static STriangle create(int x, int y, int size, Color filledColor, Color strokedColor) {

View File

@@ -8,6 +8,7 @@ import java.awt.*;
public interface Shape {
void accept(ShapeVisitor visitor);
void translate(int dx, int dy);
void resize(ResizeHandle handle, int dx, int dy);
Attributes getAttributes(String key);
void addAttributes(Attributes attr);
Rectangle getBounds();

View File

@@ -5,10 +5,13 @@ import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.HTMLExporter;
import ovh.gasser.newshapes.Selection;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.ResizeHandle;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SText;
import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.listeners.SelectionListener;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
@@ -24,6 +27,11 @@ public class Controller {
private final Selection selection;
private Point lastMousePos;
private boolean addingText;
private boolean resizing;
private ResizeHandle activeHandle;
private Point resizeOrigin;
private boolean resizeMode;
Controller(ShapesView view, SCollection model) {
this.view = view;
@@ -40,6 +48,13 @@ public class Controller {
public void mouseDragged(MouseEvent evt) {
handleMouseDragged(evt);
}
@Override
public void mouseReleased(MouseEvent evt) {
resizing = false;
activeHandle = null;
resizeOrigin = null;
}
};
this.view.addMouseMotionListener(adapter);
this.view.addMouseListener(adapter);
@@ -52,16 +67,55 @@ public class Controller {
}
private void handleMouseDragged(MouseEvent evt) {
int dx = evt.getX() - lastMousePos.x;
int dy = evt.getY() - lastMousePos.y;
for (Shape shape : selection) {
shape.translate(dx, dy);
if (resizeMode && resizing && activeHandle != null) {
int dx = evt.getX() - resizeOrigin.x;
int dy = evt.getY() - resizeOrigin.y;
for (Shape shape : selection) {
shape.resize(activeHandle, dx, dy);
}
resizeOrigin = evt.getPoint();
} else if (resizeMode && !selection.isEmpty()) {
lastMousePos = evt.getPoint();
ResizeHandle handle = getHandleAt(evt.getPoint());
if (handle != null) {
resizing = true;
activeHandle = handle;
resizeOrigin = evt.getPoint();
}
} else {
resizing = false;
activeHandle = null;
int dx = evt.getX() - lastMousePos.x;
int dy = evt.getY() - lastMousePos.y;
for (Shape shape : selection) {
shape.translate(dx, dy);
}
lastMousePos = evt.getPoint();
}
lastMousePos = evt.getPoint();
view.repaint();
}
private void handleMousePressed(MouseEvent evt) {
if (addingText) {
placeTextAt(evt.getPoint());
return;
}
resizing = false;
activeHandle = null;
resizeOrigin = null;
if (resizeMode && !selection.isEmpty()) {
ResizeHandle handle = getHandleAt(evt.getPoint());
if (handle != null) {
resizing = true;
activeHandle = handle;
resizeOrigin = evt.getPoint();
view.repaint();
return;
}
}
getTarget(evt, this.model)
.ifPresentOrElse(
s -> {
@@ -77,8 +131,49 @@ public class Controller {
view.repaint();
}
<<<<<<< HEAD
public void enterTextMode() {
addingText = true;
}
private void placeTextAt(Point point) {
String input = JOptionPane.showInputDialog(view, "Enter text:", "Add text", JOptionPane.PLAIN_MESSAGE);
addingText = false;
if (input == null) {
return;
}
model.add(SText.create(point.x, point.y, input));
resetSelection();
view.repaint();
}
public void enterTextMode() {
addingText = true;
}
private ResizeHandle getHandleAt(Point point) {
final int handleSize = 5;
for (Shape shape : selection) {
Rectangle bounds = shape.getBounds();
int cx = bounds.x + bounds.width / 2;
int cy = bounds.y + bounds.height / 2;
if (point.x < cx && point.y < cy) return ResizeHandle.NW;
if (point.x > cx && point.y < cy) return ResizeHandle.NE;
if (point.x < cx && point.y > cy) return ResizeHandle.SW;
if (point.x > cx && point.y > cy) return ResizeHandle.SE;
if (point.y < cy) return ResizeHandle.N;
if (point.y > cy) return ResizeHandle.S;
if (point.x < cx) return ResizeHandle.W;
if (point.x > cx) return ResizeHandle.E;
}
return null;
}
private void handleKeyPressed(KeyEvent evt) {
switch (evt.getKeyCode()) {
case KeyEvent.VK_R -> toggleResizeMode();
case KeyEvent.VK_DELETE -> deleteSelected();
case KeyEvent.VK_C -> copySelection();
case KeyEvent.VK_A -> changeSelectionColor();
@@ -87,6 +182,14 @@ public class Controller {
}
}
private void toggleResizeMode() {
if (!selection.isEmpty()) {
resizeMode = !resizeMode;
view.setResizeMode(resizeMode);
view.repaint();
}
}
private void exportHtml() {
logger.info("Exporting view to html");
try {

View File

@@ -13,12 +13,16 @@ public class ShapeDraftman implements ShapeVisitor {
private static final ColorAttributes DEFAULT_COLOR_ATTRIBUTES =
new ColorAttributes(false, true, Color.BLACK, Color.BLACK);
private final Graphics2D g2d;
private boolean resizeMode;
public ShapeDraftman(Graphics graph) {
this.g2d = (Graphics2D) graph;
}
public void setResizeMode(boolean resizeMode) {
this.resizeMode = resizeMode;
}
@Override
public void visitRectangle(SRectangle rect) {
Rectangle r = rect.getBounds();
@@ -56,8 +60,10 @@ public class ShapeDraftman implements ShapeVisitor {
this.g2d.setColor(colAttrs.filledColor);
this.g2d.fillOval(bounds.x, bounds.y, 2 * circle.getRadius(), 2 * circle.getRadius());
}
if (colAttrs.stroked) this.g2d.setColor(colAttrs.strokedColor);
this.g2d.drawOval(bounds.x, bounds.y, 2 * circle.getRadius(), 2 * circle.getRadius());
if (colAttrs.stroked) {
this.g2d.setColor(colAttrs.strokedColor);
this.g2d.drawOval(bounds.x, bounds.y, 2 * circle.getRadius(), 2 * circle.getRadius());
}
drawHandlerIfSelected(circle);
}
@@ -88,13 +94,58 @@ public class ShapeDraftman implements ShapeVisitor {
drawHandlerIfSelected(tri);
}
@Override
public void visitText(SText text) {
ColorAttributes colAttrs = (ColorAttributes) text.getAttributes(ColorAttributes.ID);
Font previousFont = g2d.getFont();
Color previousColor = g2d.getColor();
Font textFont = new Font(text.getFontName(), Font.PLAIN, text.getFontSize());
g2d.setFont(textFont);
g2d.setColor(resolveTextColor(colAttrs));
FontMetrics metrics = g2d.getFontMetrics(textFont);
int width = metrics.stringWidth(text.getText());
int height = metrics.getHeight();
text.updateMeasuredBounds(width, height);
Rectangle bounds = text.getBounds();
g2d.drawString(text.getText(), bounds.x, bounds.y + metrics.getAscent());
g2d.setFont(previousFont);
g2d.setColor(previousColor);
drawHandlerIfSelected(text);
}
private Color resolveTextColor(ColorAttributes attrs) {
if (attrs == null) {
return Color.BLACK;
}
if (attrs.filledColor != null) {
return attrs.filledColor;
}
if (attrs.strokedColor != null) {
return attrs.strokedColor;
}
return Color.BLACK;
}
private void drawHandlerIfSelected(Shape s) {
SelectionAttributes selAttrs = (SelectionAttributes) s.getAttributes(SelectionAttributes.ID);
if ((selAttrs != null) && (selAttrs.selected)){
Rectangle bounds = s.getBounds();
this.g2d.setColor(Color.RED);
this.g2d.drawRect(bounds.x - 5, bounds.y - 5, 5, 5);
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height, 5, 5);
int handleSize = 5;
this.g2d.drawRect(bounds.x - handleSize, bounds.y - handleSize, handleSize, handleSize);
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height, handleSize, handleSize);
if (resizeMode) {
this.g2d.drawRect(bounds.x + bounds.width, bounds.y - handleSize, handleSize, handleSize);
this.g2d.drawRect(bounds.x - handleSize, bounds.y + bounds.height, handleSize, handleSize);
this.g2d.drawRect(bounds.x + bounds.width / 2 - handleSize / 2, bounds.y - handleSize, handleSize, handleSize);
this.g2d.drawRect(bounds.x + bounds.width / 2 - handleSize / 2, bounds.y + bounds.height, handleSize, handleSize);
this.g2d.drawRect(bounds.x - handleSize, bounds.y + bounds.height / 2 - handleSize / 2, handleSize, handleSize);
this.g2d.drawRect(bounds.x + bounds.width, bounds.y + bounds.height / 2 - handleSize / 2, handleSize, handleSize);
}
}
}

View File

@@ -12,6 +12,7 @@ public class ShapesView extends JPanel {
private final Shape model;
private final Controller controller;
private ShapeVisitor draftman;
private boolean resizeMode;
public ShapesView(SCollection model) {
this.model = model;
@@ -22,6 +23,7 @@ public class ShapesView extends JPanel {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
this.draftman = new ShapeDraftman(g);
((ShapeDraftman) this.draftman).setResizeMode(resizeMode);
model.accept(draftman);
}
@@ -32,4 +34,8 @@ public class ShapesView extends JPanel {
public void addSelectionChangeListener(SelectionListener listener) {
controller.addSelectionChangeListener(listener);
}
public void setResizeMode(boolean resizeMode) {
this.resizeMode = resizeMode;
}
}

View File

@@ -5,6 +5,7 @@ import org.slf4j.LoggerFactory;
import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.attributes.SelectionAttributes;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SText;
import ovh.gasser.newshapes.shapes.Shape;
import ovh.gasser.newshapes.ui.Controller;
import ovh.gasser.newshapes.ui.ShapesView;
@@ -61,6 +62,10 @@ public class MenuEditListener implements ActionListener {
logger.warn("No color attributes: {}", s);
continue;
}
if (s instanceof SText) {
s.addAttributes(new ColorAttributes(currentColAttrs.filled, currentColAttrs.stroked, filledColor, filledColor));
continue;
}
s.addAttributes(new ColorAttributes(true, currentColAttrs.stroked, filledColor, currentColAttrs.strokedColor));
}
}
@@ -74,6 +79,10 @@ public class MenuEditListener implements ActionListener {
logger.warn("No color attributes: {}", s);
continue;
}
if (s instanceof SText) {
s.addAttributes(new ColorAttributes(currentColAttrs.filled, currentColAttrs.stroked, strockedColor, strockedColor));
continue;
}
s.addAttributes(new ColorAttributes(currentColAttrs.filled, true, currentColAttrs.filledColor, strockedColor));
}
}
@@ -85,6 +94,7 @@ public class MenuEditListener implements ActionListener {
for (Shape s : model) {
SelectionAttributes selAttrs = (SelectionAttributes) s.getAttributes(SelectionAttributes.ID);
if ((selAttrs == null) || (!selAttrs.selected)) continue;
if (s instanceof SText) continue;
ColorAttributes colAttrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID);
if (colAttrs == null) {
logger.warn("No color attributes: {}", s);
@@ -102,6 +112,7 @@ public class MenuEditListener implements ActionListener {
for (Shape s : model) {
SelectionAttributes selAttrs = (SelectionAttributes) s.getAttributes(SelectionAttributes.ID);
if ((selAttrs == null) || (!selAttrs.selected)) continue;
if (s instanceof SText) continue;
ColorAttributes colAttrs = (ColorAttributes) s.getAttributes(ColorAttributes.ID);
if (colAttrs == null) {
logger.warn("No color attributes: {}", s);

View File

@@ -7,10 +7,7 @@ import ovh.gasser.newshapes.shapes.Shape;
import java.awt.*;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringJoiner;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class HTMLDraftman implements ShapeVisitor {
@@ -87,6 +84,27 @@ public class HTMLDraftman implements ShapeVisitor {
cssOutput.write(strBuilder);
}
@Override
public void visitText(SText text) {
int id = text.hashCode();
htmlOutput.printf("<div id=\"txt%d\">%s</div>\n", id, text.getText());
ColorAttributes attrs = (ColorAttributes) text.getAttributes(ColorAttributes.ID);
String color = formatCSSColor(resolveTextColor(attrs));
cssOutput.printf("#txt%d{\n", id);
cssOutput.println(" position: absolute;");
cssOutput.printf(" top:%dpx;%n", text.getBounds().y);
cssOutput.printf(" left:%dpx;%n", text.getBounds().x);
cssOutput.printf(" font-family:%s;%n", quoteCssString(text.getFontName()));
cssOutput.printf(" font-size:%dpx;%n", text.getFontSize());
cssOutput.printf(" font-style:%s;%n", (text.getFontStyle() & Font.ITALIC) != 0 ? "italic" : "normal");
cssOutput.printf(" font-weight:%s;%n", (text.getFontStyle() & Font.BOLD) != 0 ? "bold" : "normal");
cssOutput.printf(" color:%s;%n", color);
cssOutput.println(" white-space: nowrap;");
cssOutput.println("}");
}
private String attributesToCss(Shape shape) {
ColorAttributes attrs = (ColorAttributes) shape.getAttributes(ColorAttributes.ID);
String strokedColor = "#ffffff";
@@ -123,4 +141,21 @@ public class HTMLDraftman implements ShapeVisitor {
visitCollection(model);
htmlOutput.println(FOOTER_TEMPLATE);
}
private String quoteCssString(String raw) {
return "\"" + raw.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
}
private Color resolveTextColor(ColorAttributes attrs) {
if (attrs == null) {
return Color.BLACK;
}
if (attrs.filledColor != null) {
return attrs.filledColor;
}
if (attrs.strokedColor != null) {
return attrs.strokedColor;
}
return Color.BLACK;
}
}

View File

@@ -6,6 +6,7 @@ import ovh.gasser.newshapes.attributes.ColorAttributes;
import ovh.gasser.newshapes.shapes.SCircle;
import ovh.gasser.newshapes.shapes.SCollection;
import ovh.gasser.newshapes.shapes.SRectangle;
import ovh.gasser.newshapes.shapes.SText;
import ovh.gasser.newshapes.shapes.STriangle;
import java.awt.*;
@@ -71,6 +72,26 @@ public class SVGDraftman implements ShapeVisitor {
this.output.printf("<polygon points=\"%s\" style=\"%s\" />\n", points, style);
}
@Override
public void visitText(SText sText) {
Rectangle bounds = sText.getBounds();
ColorAttributes attrs = (ColorAttributes) sText.getAttributes(ColorAttributes.ID);
String color = colorToHex(resolveTextColor(attrs));
String fontStyle = (sText.getFontStyle() & Font.ITALIC) != 0 ? "italic" : "normal";
String fontWeight = (sText.getFontStyle() & Font.BOLD) != 0 ? "bold" : "normal";
this.output.printf(
"<text x=\"%d\" y=\"%d\" font-family=\"%s\" font-size=\"%d\" font-style=\"%s\" font-weight=\"%s\" fill=\"%s\">%s</text>\n",
bounds.x,
bounds.y + sText.getFontSize(),
sText.getFontName(),
sText.getFontSize(),
fontStyle,
fontWeight,
color,
sText.getText()
);
}
public void generateSVG(SCollection model) {
output.println(String.format(SVG_PRELUDE, App.WIN_SIZE.width, App.WIN_SIZE.height));
visitCollection(model);
@@ -99,4 +120,17 @@ public class SVGDraftman implements ShapeVisitor {
}
return params;
}
private Color resolveTextColor(ColorAttributes attrs) {
if (attrs == null) {
return Color.BLACK;
}
if (attrs.filledColor != null) {
return attrs.filledColor;
}
if (attrs.strokedColor != null) {
return attrs.strokedColor;
}
return Color.BLACK;
}
}

View File

@@ -1,7 +1,7 @@
.triangle110717522{
.triangle204514374{
position: absolute;
top: 162px;
left: 367px;
top: 169px;
left: 372px;
width: 0px;
height: 0px;
border: 0 solid transparent;
@@ -9,35 +9,35 @@
border-right-width: 30.0px;
border-bottom: 50px solid #ffff00;
}
#rec209793789{
#rec664997016{
position:absolute;
top:10px;
left:10px;
width:40px;
height:60px;
background:#ffffff;border:1px solid #ff0000; }
#rec365617083{
#rec2139424642{
position:absolute;
top:10px;
left:70px;
width:40px;
height:60px;
background:#ffffff;border:1px solid #000000; }
#rec1121327988{
#rec609258999{
position:absolute;
top:200px;
left:100px;
width:40px;
height:60px;
background:#ffffff;border:1px solid #ff00ff; }
#rec256914054{
#rec759898031{
position:absolute;
top:200px;
left:150px;
width:40px;
height:60px;
background:#ffffff;border:1px solid #ff00ff; }
.circle172224331{
.circle305332562{
position: absolute;
top:250px;
@@ -52,3 +52,14 @@ background:#ffffff;border:1px solid #ff00ff; }
background:#ffffff;border:1px solid #000000;
}
#txt1172155777{
position: absolute;
top:125px;
left:216px;
font-family:"SansSerif";
font-size:16px;
font-style:normal;
font-weight:normal;
color:#9999ff;
white-space: nowrap;
}