create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / gui / print / visitor / CenteringRingStrategy.java
1 package net.sf.openrocket.gui.print.visitor;
2
3 import com.itextpdf.text.Document;
4 import com.itextpdf.text.DocumentException;
5 import com.itextpdf.text.Rectangle;
6 import com.itextpdf.text.pdf.PdfWriter;
7 import net.sf.openrocket.gui.print.AbstractPrintable;
8 import net.sf.openrocket.gui.print.ITextHelper;
9 import net.sf.openrocket.gui.print.PrintUnit;
10 import net.sf.openrocket.gui.print.PrintableCenteringRing;
11 import net.sf.openrocket.logging.LogHelper;
12 import net.sf.openrocket.rocketcomponent.CenteringRing;
13 import net.sf.openrocket.rocketcomponent.InnerTube;
14 import net.sf.openrocket.rocketcomponent.RocketComponent;
15 import net.sf.openrocket.startup.Application;
16 import net.sf.openrocket.util.ArrayList;
17
18 import java.awt.image.BufferedImage;
19 import java.util.List;
20 import java.util.Set;
21
22 /**
23  * A strategy for printing a centering ring to iText.
24  */
25 public class CenteringRingStrategy {
26
27     /**
28      * The logger.
29      */
30     private static final LogHelper log = Application.getLogger();
31
32     /**
33      * The iText document.
34      */
35     protected Document document;
36
37     /**
38      * The direct iText writer.
39      */
40     protected PdfWriter writer;
41
42     /**
43      * The stages selected.
44      */
45     protected Set<Integer> stages;
46
47     /**
48      * Strategy for fitting multiple components onto a page.
49      */
50     protected PageFitPrintStrategy pageFitPrint;
51
52     /**
53      * Constructor.
54      *
55      * @param doc              The iText document
56      * @param theWriter        The direct iText writer
57      * @param theStagesToVisit The stages to be visited by this strategy
58      */
59     public CenteringRingStrategy(Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit, PageFitPrintStrategy pageFit) {
60         document = doc;
61         writer = theWriter;
62         stages = theStagesToVisit;
63         pageFitPrint = pageFit;
64     }
65
66     /**
67      * Recurse through the given rocket component.
68      *
69      * @param root the root component; all children will be visited recursively
70      */
71     public void writeToDocument(final RocketComponent root) {
72         List<RocketComponent> rc = root.getChildren();
73         goDeep(rc);
74     }
75
76
77     /**
78      * Recurse through the given rocket component.
79      *
80      * @param theRc an array of rocket components; all children will be visited recursively
81      */
82     protected void goDeep(final List<RocketComponent> theRc) {
83         for (RocketComponent rocketComponent : theRc) {
84             if (rocketComponent instanceof CenteringRing) {
85                 render((CenteringRing) rocketComponent);
86             }
87             else if (rocketComponent.getChildCount() > 0) {
88                 goDeep(rocketComponent.getChildren());
89             }
90         }
91     }
92
93     /**
94      * Find the inner tubes that are physically supported by the given centering ring.  Note that this only looks for
95      * motor mount tubes that are siblings to the centering ring.
96      *
97      * @param rc the centering ring, for which all motor mount tubes that run through it are located.
98      *
99      * @return the list of tubes found
100      */
101     private List<InnerTube> findMotorMount(CenteringRing rc) {
102         RocketComponent parent = rc.getParent();
103         List<RocketComponent> siblings = parent.getChildren();
104
105         List<InnerTube> mounts = new ArrayList<InnerTube>();
106         for (RocketComponent rocketComponents : siblings) {
107             if (rocketComponents != rc) {
108                 if (rocketComponents instanceof InnerTube) {
109                     InnerTube it = (InnerTube) rocketComponents;
110                     if (overlaps(rc, it)) {
111                         mounts.add(it);
112                     }
113                 }
114             }
115         }
116
117         return mounts;
118     }
119
120     /**
121      * Determine if the centering ring physically overlaps with the inner tube.
122      *
123      * @param one the centering ring
124      * @param two the inner body tube
125      *
126      * @return true if the two physically intersect, from which we infer that the centering ring supports the tube
127      */
128     private boolean overlaps(CenteringRing one, InnerTube two) {
129         final double crTopPosition = one.asPositionValue(RocketComponent.Position.ABSOLUTE, one.getParent());
130         final double mmTopPosition = two.asPositionValue(RocketComponent.Position.ABSOLUTE, two.getParent());
131         final double crBottomPosition = one.getLength() + crTopPosition;
132         final double mmBottomPosition = two.getLength() + mmTopPosition;
133
134         if (crTopPosition >= mmTopPosition && crTopPosition <= mmBottomPosition) {
135             return true;
136         }
137         if (crBottomPosition >= mmTopPosition && crBottomPosition <= mmBottomPosition) {
138             return true;
139         }
140         return false;
141     }
142
143     /**
144      * The core behavior of this visitor.
145      *
146      * @param component the object to extract info about; a graphical image of the centering ring shape is drawn to the
147      *                  document
148      */
149     private void render(final CenteringRing component) {
150         try {
151             AbstractPrintable pfs;
152             pfs = PrintableCenteringRing.create(component, findMotorMount(component));
153
154             java.awt.Dimension size = pfs.getSize();
155             final Dimension pageSize = getPageSize();
156             if (fitsOnOnePage(pageSize, size.getWidth(), size.getHeight())) {
157                 pageFitPrint.addComponent(pfs);
158             }
159             else {
160                 int off = (int) (PrintUnit.POINTS_PER_INCH * 0.3f);
161                 pfs.setPrintOffset(off, off);
162                 BufferedImage image = (BufferedImage) pfs.createImage();
163                 ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()),
164                         document, writer, image);
165                 document.newPage();
166             }
167         }
168         catch (DocumentException e) {
169             log.error("Could not render the centering ring.", e);
170         }
171     }
172
173     /**
174      * Determine if the image will fit on the given page.
175      *
176      * @param pageSize the page size
177      * @param wImage   the width of the thing to be printed
178      * @param hImage   the height of the thing to be printed
179      *
180      * @return true if the thing to be printed will fit on a single page
181      */
182     private boolean fitsOnOnePage(Dimension pageSize, double wImage, double hImage) {
183         double wPage = pageSize.getWidth();
184         double hPage = pageSize.getHeight();
185
186         int wRatio = (int) Math.ceil(wImage / wPage);
187         int hRatio = (int) Math.ceil(hImage / hPage);
188
189         return wRatio <= 1.0d && hRatio <= 1.0d;
190     }
191
192     /**
193      * Get the dimensions of the paper page.
194      *
195      * @return an internal Dimension
196      */
197     protected Dimension getPageSize() {
198         return new Dimension(document.getPageSize().getWidth(),
199                 document.getPageSize().getHeight());
200     }
201
202     /**
203      * Convenience class to model a dimension.
204      */
205     public static class Dimension {
206         /**
207          * Width, in points.
208          */
209         public float width;
210         /**
211          * Height, in points.
212          */
213         public float height;
214         /**
215          * Breadth, in points.
216          */
217         public float breadth = 0f;
218
219         /**
220          * Constructor.
221          *
222          * @param w width
223          * @param h height
224          */
225         public Dimension(float w, float h) {
226             width = w;
227             height = h;
228         }
229
230         /**
231          * Constructor.
232          *
233          * @param w width
234          * @param h height
235          * @param b breadth; optionally used to represent radius
236          */
237         public Dimension(float w, float h, float b) {
238             width = w;
239             height = h;
240             breadth = b;
241         }
242
243         /**
244          * Get the width.
245          *
246          * @return the width
247          */
248         public float getWidth() {
249             return width;
250         }
251
252         /**
253          * Get the height.
254          *
255          * @return the height
256          */
257         public float getHeight() {
258             return height;
259         }
260
261         /**
262          * Get the breadth.
263          *
264          * @return the breadth
265          */
266         public float getBreadth() {
267             return breadth;
268         }
269     }
270 }