1 /*
2  * Copyright (c) 2016 Helmut Neemann
3  * Use of this source code is governed by the GPL v3 license
4  * that can be found in the LICENSE file.
5  */
6 package de.neemann.digital.draw.graphics;
7 
8 import de.neemann.digital.draw.graphics.text.formatter.GraphicsFormatter;
9 
10 import java.awt.font.FontRenderContext;
11 import java.awt.geom.Rectangle2D;
12 
13 import static de.neemann.digital.draw.graphics.GraphicSwing.getMirrorYOrientation;
14 
15 /**
16  * This class is used to determine the size of shapes or the whole circuit.
17  * You can draw the items to an instance of this class and then obtain the size
18  * by the getters getMin() and getMax().
19  */
20 public class GraphicMinMax extends Graphic {
21 
22     private final boolean includeText;
23     private final Graphic parent;
24     private Vector min;
25     private Vector max;
26 
27     /**
28      * Creates a new instance
29      */
GraphicMinMax()30     public GraphicMinMax() {
31         this(true, null);
32     }
33 
34     /**
35      * Creates a new instance
36      *
37      * @param parent oly used to provide the flags
38      */
GraphicMinMax(Graphic parent)39     public GraphicMinMax(Graphic parent) {
40         this(true, parent);
41     }
42 
43     /**
44      * Creates a new instance
45      *
46      * @param includeText true if text is included in measurement
47      * @param parent      oly used to provide the flags
48      */
GraphicMinMax(boolean includeText, Graphic parent)49     public GraphicMinMax(boolean includeText, Graphic parent) {
50         this.includeText = includeText;
51         this.parent = parent;
52     }
53 
54     @Override
drawLine(VectorInterface p1, VectorInterface p2, Style style)55     public void drawLine(VectorInterface p1, VectorInterface p2, Style style) {
56         check(p1);
57         check(p2);
58     }
59 
60     @Override
drawPolygon(Polygon p, Style style)61     public void drawPolygon(Polygon p, Style style) {
62         p.traverse(this::check);
63     }
64 
65     @Override
drawCircle(VectorInterface p1, VectorInterface p2, Style style)66     public void drawCircle(VectorInterface p1, VectorInterface p2, Style style) {
67         check(p1);
68         check(p2);
69     }
70 
71     /**
72      * Checks the given point and makes the bounding box larger if necessary.
73      *
74      * @param p the point to check
75      */
check(VectorInterface p)76     public void check(VectorInterface p) {
77         if (min == null || max == null) {
78             min = new Vector(p.getX(), p.getY());
79             max = new Vector(p.getX(), p.getY());
80         } else {
81             min = Vector.min(min, p);
82             max = Vector.max(max, p);
83         }
84     }
85 
86     @Override
drawText(VectorInterface p1, VectorInterface p2, VectorInterface p3, String text, Orientation orientation, Style style)87     public void drawText(VectorInterface p1, VectorInterface p2, VectorInterface p3, String text, Orientation orientation, Style style) {
88         if (includeText || style.mattersAlwaysForSize())
89             approxTextSize(this, p1, p2, p3, text, orientation, style);
90     }
91 
92     /**
93      * Approximation of text size
94      *
95      * @param gr          the Graphic instance to use
96      * @param p1          point to draw the text
97      * @param p2          at the left of p1, is used to determine the correct orientation of the text after transforming coordinates
98      * @param p3          at the top of p1, is used to determine the correct orientation of the text after transforming coordinates
99      * @param text        the text
100      * @param orientation the texts orientation
101      * @param style       the text style
102      */
approxTextSize(Graphic gr, VectorInterface p1, VectorInterface p2, VectorInterface p3, String text, Orientation orientation, Style style)103     public static void approxTextSize(Graphic gr, VectorInterface p1, VectorInterface p2, VectorInterface p3, String text, Orientation orientation, Style style) {
104         if (text != null && text.length() > 0) {
105             VectorFloat delta = p2.sub(p1).norm();
106             VectorFloat height = new VectorFloat(delta.getYFloat(), -delta.getXFloat()).mul(style.getFontSize());
107 
108             int textWidth = getTextWidth(text, style);
109             VectorFloat width = delta.mul(textWidth);
110 
111             VectorInterface p = p1;
112             if (orientation.getX() != 0) {
113                 p = p.sub(width.mul(orientation.getX()).div(2));
114             }
115 
116             int oy = getMirrorYOrientation(orientation, p1, p2, p3);
117             if (oy != 0) {
118                 p = p.sub(height.mul(oy).div(2));
119             } else
120                 p = p.sub(height.div(4));
121 
122             gr.drawPolygon(new Polygon(true)
123                     .add(p)
124                     .add(p.add(width))
125                     .add(p.add(width).add(height))
126                     .add(p.add(height)), Style.THIN);
127         }
128     }
129 
130     /**
131      * Returns a approximation of the width of the given text in the given style
132      *
133      * @param text  the text
134      * @param style the style
135      * @return the approximated text width
136      */
getTextWidth(String text, Style style)137     public static int getTextWidth(String text, Style style) {
138         final FontRenderContext fontRenderContext = new FontRenderContext(null, true, false);
139         GraphicsFormatter.Fragment f = GraphicsFormatter.createFragment((fragment, font, str) -> {
140             Rectangle2D rec = style.getFont().getStringBounds(str, fontRenderContext);
141             fragment.set((int) rec.getWidth(), (int) rec.getHeight(), 0);
142         }, style.getFont(), text);
143         return f.getWidth();
144     }
145 
146     /**
147      * @return the upper left corner of the circuit
148      */
getMin()149     public Vector getMin() {
150         return min;
151     }
152 
153     /**
154      * @return the lower right corner of the circuit
155      */
getMax()156     public Vector getMax() {
157         return max;
158     }
159 
160     @Override
isFlagSet(Flag flag)161     public boolean isFlagSet(Flag flag) {
162         if (parent == null)
163             return false;
164         else
165             return parent.isFlagSet(flag);
166     }
167 
168     /**
169      * @return true if this instance is valid
170      */
isValid()171     public boolean isValid() {
172         return min != null && max != null;
173     }
174 
175     @Override
toString()176     public String toString() {
177         return "GraphicMinMax{"
178                 + "min=" + min
179                 + ", max=" + max + '}';
180     }
181 }
182