Added printing support for Component Preset in Parts Detail Report.
[debian/openrocket] / core / src / net / sf / openrocket / gui / print / visitor / PartsDetailVisitorStrategy.java
1 /*
2  * PartsDetailVisitorStrategy.java
3  */
4 package net.sf.openrocket.gui.print.visitor;
5
6 import com.itextpdf.text.Chunk;
7 import com.itextpdf.text.Document;
8 import com.itextpdf.text.DocumentException;
9 import com.itextpdf.text.Element;
10 import com.itextpdf.text.Font;
11 import com.itextpdf.text.Image;
12 import com.itextpdf.text.Paragraph;
13 import com.itextpdf.text.Phrase;
14 import com.itextpdf.text.Rectangle;
15 import com.itextpdf.text.pdf.PdfPCell;
16 import com.itextpdf.text.pdf.PdfPTable;
17 import com.itextpdf.text.pdf.PdfWriter;
18 import com.itextpdf.text.pdf.draw.VerticalPositionMark;
19 import net.sf.openrocket.gui.main.ComponentIcons;
20 import net.sf.openrocket.gui.print.ITextHelper;
21 import net.sf.openrocket.gui.print.PrintUtilities;
22 import net.sf.openrocket.gui.print.PrintableFinSet;
23 import net.sf.openrocket.logging.LogHelper;
24 import net.sf.openrocket.material.Material;
25 import net.sf.openrocket.preset.ComponentPreset;
26 import net.sf.openrocket.rocketcomponent.BodyComponent;
27 import net.sf.openrocket.rocketcomponent.BodyTube;
28 import net.sf.openrocket.rocketcomponent.Bulkhead;
29 import net.sf.openrocket.rocketcomponent.Coaxial;
30 import net.sf.openrocket.rocketcomponent.ExternalComponent;
31 import net.sf.openrocket.rocketcomponent.FinSet;
32 import net.sf.openrocket.rocketcomponent.InnerTube;
33 import net.sf.openrocket.rocketcomponent.LaunchLug;
34 import net.sf.openrocket.rocketcomponent.MassObject;
35 import net.sf.openrocket.rocketcomponent.NoseCone;
36 import net.sf.openrocket.rocketcomponent.Parachute;
37 import net.sf.openrocket.rocketcomponent.RadiusRingComponent;
38 import net.sf.openrocket.rocketcomponent.RecoveryDevice;
39 import net.sf.openrocket.rocketcomponent.RingComponent;
40 import net.sf.openrocket.rocketcomponent.RocketComponent;
41 import net.sf.openrocket.rocketcomponent.ShockCord;
42 import net.sf.openrocket.rocketcomponent.Stage;
43 import net.sf.openrocket.rocketcomponent.Streamer;
44 import net.sf.openrocket.rocketcomponent.Transition;
45 import net.sf.openrocket.startup.Application;
46 import net.sf.openrocket.unit.Unit;
47 import net.sf.openrocket.unit.UnitGroup;
48 import net.sf.openrocket.util.Coordinate;
49
50 import javax.swing.*;
51 import java.text.NumberFormat;
52 import java.util.Collection;
53 import java.util.List;
54 import java.util.Set;
55
56 /**
57  * A visitor strategy for creating documentation about parts details.
58  */
59 public class PartsDetailVisitorStrategy {
60
61     /**
62      * The logger.
63      */
64     private static final LogHelper log = Application.getLogger();
65
66     /**
67      * The number of columns in the table.
68      */
69     private static final int TABLE_COLUMNS = 7;
70
71     /**
72      * The parts detail is represented as an iText table.
73      */
74     PdfPTable grid;
75
76     /**
77      * The iText document.
78      */
79     protected Document document;
80
81     /**
82      * The direct iText writer.
83      */
84     protected PdfWriter writer;
85
86     /**
87      * The stages selected.
88      */
89     protected Set<Integer> stages;
90
91     /**
92      * State variable to track the level of hierarchy.
93      */
94     protected int level = 0;
95
96     private static final String LINES = "Lines: ";
97     private static final String MASS = "Mass: ";
98     private static final String LEN = "Len: ";
99     private static final String THICK = "Thick: ";
100     private static final String INNER = "in ";
101     private static final String DIAMETER = "Dia";
102     private static final String OUTER = "out";
103     private static final String WIDTH = "Width";
104     private static final String LENGTH = "Length";
105     private static final String SHROUD_LINES = "Shroud Lines";
106     private static final String AFT_DIAMETER = "Aft Dia: ";
107     private static final String FORE_DIAMETER = "Fore Dia: ";
108     private static final String PARTS_DETAIL = "Parts Detail";
109
110     /**
111      * Construct a strategy for visiting a parts hierarchy for the purposes of collecting details on those parts.
112      *
113      * @param doc              The iText document
114      * @param theWriter        The direct iText writer
115      * @param theStagesToVisit The stages to be visited by this strategy
116      */
117     public PartsDetailVisitorStrategy (Document doc, PdfWriter theWriter, Set<Integer> theStagesToVisit) {
118         document = doc;
119         writer = theWriter;
120         stages = theStagesToVisit;
121         PrintUtilities.addText(doc, PrintUtilities.BIG_BOLD, PARTS_DETAIL);
122     }
123
124     /**
125      * Print the parts detail.
126      *
127      * @param root  the root component
128      */
129     public void writeToDocument (final RocketComponent root) {
130         goDeep(root.getChildren());
131     }
132
133     /**
134      * Recurse through the given rocket component.
135      *
136      * @param theRc an array of rocket components; all children will be visited recursively
137      */
138     protected void goDeep (final List<RocketComponent> theRc) {
139         level++;
140         for (RocketComponent rocketComponent : theRc) {
141             handle(rocketComponent);
142         }
143         level--;
144     }
145
146     /**
147      * Add a line to the detail report based upon the type of the component.
148      *
149      * @param component  the component to print the detail for
150      */
151     private void handle (RocketComponent component) {
152         //This ugly if-then-else construct is not object oriented.  Originally it was an elegant, and very OO savy, design
153         //using the Visitor pattern.  Unfortunately, it was misunderstood and was removed.
154         if (component instanceof Stage) {
155             try {
156                 if (grid != null) {
157                     document.add(grid);
158                 }
159                 document.add(ITextHelper.createPhrase(component.getName()));
160                 grid = new PdfPTable(TABLE_COLUMNS);
161                 grid.setWidthPercentage(100);
162                 grid.setHorizontalAlignment(Element.ALIGN_LEFT);
163             }
164             catch (DocumentException e) {
165             }
166
167             List<RocketComponent> rc = component.getChildren();
168             goDeep(rc);
169         }
170         else if (component instanceof LaunchLug) {
171             LaunchLug ll = (LaunchLug) component;
172             grid.addCell(iconToImage(component));
173             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
174
175             grid.addCell(createMaterialCell(ll.getMaterial()));
176             grid.addCell(createOuterInnerDiaCell(ll));
177             grid.addCell(createLengthCell(component.getLength()));
178             grid.addCell(createMassCell(component.getMass()));
179         }
180         else if (component instanceof NoseCone) {
181             NoseCone nc = (NoseCone) component;
182             grid.addCell(iconToImage(component));
183             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
184             grid.addCell(createMaterialCell(nc.getMaterial()));
185             grid.addCell(ITextHelper.createCell(nc.getType().getName(), PdfPCell.BOTTOM));
186             grid.addCell(createLengthCell(component.getLength()));
187             grid.addCell(createMassCell(component.getMass()));
188             List<RocketComponent> rc = component.getChildren();
189             goDeep(rc);
190         }
191         else if (component instanceof Transition) {
192             Transition tran = (Transition) component;
193             grid.addCell(iconToImage(component));
194             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
195             grid.addCell(createMaterialCell(tran.getMaterial()));
196
197             Chunk fore = new Chunk(FORE_DIAMETER + toLength(tran.getForeRadius() * 2));
198             fore.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
199             Chunk aft = new Chunk(AFT_DIAMETER + toLength(tran.getAftRadius() * 2));
200             aft.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
201             final PdfPCell cell = ITextHelper.createCell();
202             cell.addElement(fore);
203             cell.addElement(aft);
204             grid.addCell(cell);
205             grid.addCell(createLengthCell(component.getLength()));
206             grid.addCell(createMassCell(component.getMass()));
207
208             List<RocketComponent> rc = component.getChildren();
209             goDeep(rc);
210         }
211         else if (component instanceof BodyTube) {
212             BodyTube bt = (BodyTube) component;
213             grid.addCell(iconToImage(component));
214             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
215             grid.addCell(createMaterialCell(bt.getMaterial()));
216             grid.addCell(createOuterInnerDiaCell(bt));
217             grid.addCell(createLengthCell(component.getLength()));
218             grid.addCell(createMassCell(component.getMass()));
219             List<RocketComponent> rc = component.getChildren();
220             goDeep(rc);
221         }
222         else if (component instanceof FinSet) {
223             handleFins((FinSet) component);
224         }
225         else if (component instanceof BodyComponent) {
226             grid.addCell(component.getName());
227             grid.completeRow();
228             List<RocketComponent> rc = component.getChildren();
229             goDeep(rc);
230         }
231         else if (component instanceof ExternalComponent) {
232             ExternalComponent ext = (ExternalComponent) component;
233             grid.addCell(iconToImage(component));
234             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
235
236             grid.addCell(createMaterialCell(ext.getMaterial()));
237             grid.addCell(ITextHelper.createCell());
238             grid.addCell(createLengthCell(component.getLength()));
239             grid.addCell(createMassCell(component.getMass()));
240
241             List<RocketComponent> rc = component.getChildren();
242             goDeep(rc);
243         }
244         else if (component instanceof InnerTube) {
245             InnerTube it = (InnerTube) component;
246             grid.addCell(iconToImage(component));
247             final PdfPCell pCell = createNameCell(component.getName(), true, component.getPresetComponent());
248             grid.addCell(pCell);
249             grid.addCell(createMaterialCell(it.getMaterial()));
250             grid.addCell(createOuterInnerDiaCell(it));
251             grid.addCell(createLengthCell(component.getLength()));
252             grid.addCell(createMassCell(component.getMass()));
253
254             List<RocketComponent> rc = component.getChildren();
255             goDeep(rc);
256         }
257         else if (component instanceof RadiusRingComponent) {
258             RadiusRingComponent rrc = (RadiusRingComponent) component;
259             grid.addCell(iconToImage(component));
260             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
261             grid.addCell(createMaterialCell(rrc.getMaterial()));
262             if (component instanceof Bulkhead) {
263                 grid.addCell(createDiaCell(rrc.getOuterRadius()*2));
264             }
265             else {
266                 grid.addCell(createOuterInnerDiaCell(rrc));
267             }
268             grid.addCell(createLengthCell(component.getLength()));
269             grid.addCell(createMassCell(component.getMass()));
270             List<RocketComponent> rc = component.getChildren();
271             goDeep(rc);
272         }
273         else if (component instanceof RingComponent) {
274             RingComponent ring = (RingComponent) component;
275             grid.addCell(iconToImage(component));
276             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
277             grid.addCell(createMaterialCell(ring.getMaterial()));
278             grid.addCell(createOuterInnerDiaCell(ring));
279             grid.addCell(createLengthCell(component.getLength()));
280             grid.addCell(createMassCell(component.getMass()));
281
282             List<RocketComponent> rc = component.getChildren();
283             goDeep(rc);
284         }
285         else if (component instanceof ShockCord) {
286             ShockCord ring = (ShockCord) component;
287             PdfPCell cell = ITextHelper.createCell();
288             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
289             cell.setPaddingBottom(12f);
290             grid.addCell(iconToImage(component));
291             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
292             grid.addCell(createMaterialCell(ring.getMaterial()));
293             grid.addCell(cell);
294             grid.addCell(createLengthCell(ring.getCordLength()));
295             grid.addCell(createMassCell(component.getMass()));
296         }
297         else if (component instanceof Parachute) {
298             Parachute chute = (Parachute) component;
299             PdfPCell cell = ITextHelper.createCell();
300             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
301             cell.setPaddingBottom(12f);
302             grid.addCell(iconToImage(component));
303             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
304             grid.addCell(createMaterialCell(chute.getMaterial()));
305 //            if (chute.hasSpillHole()) {
306 //                grid.addCell(createOuterInnerDiaCell(chute.getDiameter()/2, chute.getSpillHoleDiameter()/2));
307 //        }
308 //        else {
309             grid.addCell(createDiaCell(chute.getDiameter()));
310 //        }
311             grid.addCell(createLengthCell(component.getLength()));
312             grid.addCell(createMassCell(component.getMass()));
313
314             grid.addCell(iconToImage(null));
315             grid.addCell(createNameCell(SHROUD_LINES, true, component.getPresetComponent()));
316             grid.addCell(createMaterialCell(chute.getLineMaterial()));
317             grid.addCell(createLinesCell(chute.getLineCount()));
318             grid.addCell(createLengthCell(chute.getLineLength()));
319             grid.addCell(cell);
320         }
321         else if (component instanceof Streamer) {
322             Streamer ring = (Streamer) component;
323             PdfPCell cell = ITextHelper.createCell();
324             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
325             cell.setPaddingBottom(12f);
326             grid.addCell(iconToImage(component));
327             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
328             grid.addCell(createMaterialCell(ring.getMaterial()));
329             grid.addCell(createStrip(ring));
330             grid.addCell(createLengthCell(component.getLength()));
331             grid.addCell(createMassCell(component.getMass()));
332         }
333         else if (component instanceof RecoveryDevice) {
334             RecoveryDevice device = (RecoveryDevice) component;
335             PdfPCell cell = ITextHelper.createCell();
336             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
337             cell.setPaddingBottom(12f);
338             grid.addCell(iconToImage(component));
339             grid.addCell(createNameCell(component.getName(), true, component.getPresetComponent()));
340             grid.addCell(createMaterialCell(device.getMaterial()));
341             grid.addCell(cell);
342             grid.addCell(createLengthCell(component.getLength()));
343             grid.addCell(createMassCell(component.getMass()));
344         }
345         else if (component instanceof MassObject) {
346             PdfPCell cell = ITextHelper.createCell();
347             cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
348             cell.setPaddingBottom(12f);
349
350             grid.addCell(iconToImage(component));
351             final PdfPCell nameCell = createNameCell(component.getName(), true, component.getPresetComponent());
352             nameCell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
353             nameCell.setPaddingBottom(12f);
354             grid.addCell(nameCell);
355             grid.addCell(cell);
356             grid.addCell(createDiaCell(((MassObject) component).getRadius() * 2));
357             grid.addCell(cell);
358             grid.addCell(createMassCell(component.getMass()));
359         }
360     }
361
362     /**
363      * Close the strategy by adding the last grid to the document.
364      */
365     public void close () {
366         try {
367             if (grid != null) {
368                 document.add(grid);
369             }
370         }
371         catch (DocumentException e) {
372             log.error("Could not write last cell to document.", e);
373         }
374     }
375
376     /**
377      * Create a cell to document an outer 'diameter'.  This is used for components that have no inner diameter, such as
378      * a solid parachute or bulkhead.
379      *
380      * @param diameter  the diameter in default length units
381      *
382      * @return a formatted cell containing the diameter
383      */
384     private PdfPCell createDiaCell (final double diameter) {
385         PdfPCell result = new PdfPCell();
386         Phrase p = new Phrase();
387         p.setLeading(12f);
388         result.setVerticalAlignment(Element.ALIGN_TOP);
389         result.setBorder(Rectangle.BOTTOM);
390         Chunk c = new Chunk();
391         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
392         c.append(DIAMETER);
393         p.add(c);
394
395         c = new Chunk();
396         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
397         c.append(OUTER);
398         p.add(c);
399
400         c = new Chunk();
401         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
402         c.append(" " + toLength(diameter));
403         p.add(c);
404         result.addElement(p);
405         return result;
406     }
407
408     /**
409      * Create a PDF cell for a streamer.
410      *
411      * @param component  a component that is a Coaxial
412      * @return  the PDF cell that has the streamer documented
413      */
414     private PdfPCell createStrip (final Streamer component) {
415         PdfPCell result = new PdfPCell();
416         Phrase p = new Phrase();
417         p.setLeading(12f);
418         result.setVerticalAlignment(Element.ALIGN_TOP);
419         result.setBorder(Rectangle.BOTTOM);
420         Chunk c = new Chunk();
421         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
422         c.append(LENGTH);
423         p.add(c);
424
425         c = new Chunk();
426         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
427         c.append(" " + toLength(component.getStripLength()));
428         p.add(c);
429         result.addElement(p);
430
431         Phrase pw = new Phrase();
432         pw.setLeading(14f);
433         c = new Chunk();
434         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
435         c.append(WIDTH);
436         pw.add(c);
437
438         c = new Chunk();
439         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
440         c.append("  " + toLength(component.getStripWidth()));
441         pw.add(c);
442         result.addElement(pw);
443
444         return result;
445     }
446
447     /**
448      * Create a PDF cell that documents both an outer and an inner diameter of a component.
449      *
450      * @param component  a component that is a Coaxial
451      *
452      * @return  the PDF cell that has the outer and inner diameters documented
453      */
454     private PdfPCell createOuterInnerDiaCell (final Coaxial component) {
455         return createOuterInnerDiaCell(component, INNER);
456     }
457
458     /**
459      * Create a PDF cell that documents both an outer and an inner diameter of a component.
460      *
461      * @param component  a component that is a Coaxial
462      * @param innerLabel the label to use for the inner label subscript
463      *
464      * @return  the PDF cell that has the outer and inner diameters documented
465      */
466     private PdfPCell createOuterInnerDiaCell (final Coaxial component, final String innerLabel) {
467         return createOuterInnerDiaCell(component.getOuterRadius(), component.getInnerRadius(), innerLabel);
468     }
469
470     /**
471      * Create a PDF cell that documents both an outer and an inner diameter of a component.
472      *
473      * @param outerRadius the outer radius
474      * @param innerRadius the inner radius
475      * @param innerLabel the label to use for the inner label subscript
476      *
477      * @return  the PDF cell that has the outer and inner diameters documented
478      */
479     private PdfPCell createOuterInnerDiaCell (final double outerRadius, final double innerRadius, final String innerLabel) {
480
481         PdfPCell result = new PdfPCell();
482         Phrase p = new Phrase();
483         p.setLeading(12f);
484         result.setVerticalAlignment(Element.ALIGN_TOP);
485         result.setBorder(Rectangle.BOTTOM);
486         Chunk c = new Chunk();
487         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
488         c.append(DIAMETER);
489         p.add(c);
490
491         c = new Chunk();
492         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
493         c.append(OUTER);
494         p.add(c);
495
496         c = new Chunk();
497         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
498         c.append(" " + toLength(outerRadius * 2));
499         p.add(c);
500         createInnerDiaCell(innerRadius, result, innerLabel);
501         result.addElement(p);
502         return result;
503     }
504
505     /**
506      * Add inner diameter data to a cell.
507      *
508      * @param innerRadius the inner radius
509      * @param cell       the PDF cell to add the inner diameter data to
510      * @param innerLabel the label to use for the inner label subscript
511      */
512     private void createInnerDiaCell (final double innerRadius, PdfPCell cell, final String innerLabel) {
513         Phrase p = new Phrase();
514         p.setLeading(14f);
515         Chunk c = new Chunk();
516         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
517         c.append(DIAMETER);
518         p.add(c);
519
520         c = new Chunk();
521         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
522         c.append(innerLabel);
523         p.add(c);
524
525         c = new Chunk();
526         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
527         c.append("  " + toLength(innerRadius * 2));
528         p.add(c);
529         cell.addElement(p);
530     }
531
532     /**
533      * Add PDF cells for a fin set.
534      *
535      * @param theFinSet  the fin set
536      */
537     private void handleFins (FinSet theFinSet) {
538
539         Image img = null;
540         java.awt.Image awtImage = new PrintableFinSet(theFinSet).createImage();
541
542         Collection<Coordinate> x = theFinSet.getComponentBounds();
543
544         try {
545             img = Image.getInstance(writer, awtImage, 0.25f);
546         }
547         catch (Exception e) {
548             log.error("Could not write image to document.", e);
549         }
550
551         grid.addCell(iconToImage(theFinSet));
552         grid.addCell(createNameCell(theFinSet.getName() + " (" + theFinSet.getFinCount() + ")", true));
553         grid.addCell(createMaterialCell(theFinSet.getMaterial()));
554         grid.addCell(ITextHelper.createCell(THICK + toLength(theFinSet.getThickness()), PdfPCell.BOTTOM));
555         final PdfPCell pCell = new PdfPCell();
556         pCell.setBorder(Rectangle.BOTTOM);
557         pCell.addElement(img);
558
559         grid.addCell(ITextHelper.createCell());
560         grid.addCell(createMassCell(theFinSet.getMass()));
561
562         List<RocketComponent> rc = theFinSet.getChildren();
563         goDeep(rc);
564     }
565
566     /**
567      * Create a length formatted cell.
568      *
569      * @param length  the length, in default length units
570      *
571      * @return a PdfPCell that is formatted with the length
572      */
573     protected PdfPCell createLengthCell (double length) {
574         return ITextHelper.createCell(LEN + toLength(length), PdfPCell.BOTTOM);
575     }
576
577     /**
578      * Create a mass formatted cell.
579      *
580      * @param mass  the mass, in default mass units
581      *
582      * @return a PdfPCell that is formatted with the mass
583      */
584     protected PdfPCell createMassCell (double mass) {
585         return ITextHelper.createCell(MASS + toMass(mass), PdfPCell.BOTTOM);
586     }
587
588     /**
589      * Create a (shroud) line count formatted cell.
590      *
591      * @param count  the number of shroud lines
592      *
593      * @return a PdfPCell that is formatted with the line count
594      */
595     protected PdfPCell createLinesCell (int count) {
596         return ITextHelper.createCell(LINES + count, PdfPCell.BOTTOM);
597     }
598
599     /**
600      * Create a cell formatted for a name (or any string for that matter).
601      *
602      * @param v  the string to format into a PDF cell
603      * @param withIndent  if true, then an indention is made scaled to the level of the part in the parent hierarchy
604      *
605      * @return a PdfPCell that is formatted with the string <code>v</code>
606      */
607     protected PdfPCell createNameCell (String v, boolean withIndent) {
608         return createNameCell(v, withIndent, null);
609     }
610
611     /**
612      * Create a cell formatted for a name (or any string for that matter).
613      *
614      * @param v  the string to format into a PDF cell
615      * @param withIndent  if true, then an indention is made scaled to the level of the part in the parent hierarchy
616      * @param preset  the component's preset, if it has one
617      *
618      * @return a PdfPCell that is formatted with the string <code>v</code>
619      */
620     protected PdfPCell createNameCell (String v, boolean withIndent, ComponentPreset preset) {
621         PdfPCell result = new PdfPCell();
622         result.setColspan(2);
623         result.setBorder(Rectangle.BOTTOM);
624         Paragraph para = new Paragraph();
625         para.setLeading(12f, 0);
626         Chunk c = new Chunk();
627         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
628         Chunk tab1 =
629           new Chunk(new VerticalPositionMark(), (level - 2) * 10, true);
630
631         if (withIndent) {
632             para.add(new Chunk(tab1));
633         }
634         c.append(v);
635         para.add(c);
636
637         //Add the preset's manufacturer and part no in a subscript font.
638         if (preset != null) {
639             para.add(Chunk.NEWLINE);
640             c = new Chunk();
641             if (withIndent) {
642                 para.add(new Chunk(tab1));
643             }
644             c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
645             c.append(preset.toString());
646             para.add(c);
647         }
648         result.addElement(para);
649         return result;
650     }
651
652     /**
653      * Create a cell that describes a material.
654      *
655      * @param material  the material
656      *
657      * @return a PdfPCell that is formatted with a description of the material
658      */
659     protected PdfPCell createMaterialCell (Material material) {
660         PdfPCell cell = ITextHelper.createCell();
661         cell.setLeading(13f, 0);
662
663         Chunk c = new Chunk();
664         c.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.NORMAL_FONT_SIZE));
665         c.append(toMaterialName(material));
666         cell.addElement(c);
667         Chunk density = new Chunk();
668         density.setFont(new Font(Font.FontFamily.HELVETICA, PrintUtilities.SMALL_FONT_SIZE));
669         density.append(toMaterialDensity(material));
670         cell.addElement(density);
671         return cell;
672     }
673
674     /**
675      * Get the icon of the particular type of rocket component and conver it to an image in a PDF cell.
676      *
677      * @param visitable  the rocket component to create a cell with it's image
678      *
679      * @return a PdfPCell that is just an image that can be put into a PDF
680      */
681     protected PdfPCell iconToImage (final RocketComponent visitable) {
682         if (visitable != null) {
683             final ImageIcon icon = (ImageIcon) ComponentIcons.getLargeIcon(visitable.getClass());
684             try {
685                 if (icon != null) {
686                     Image im = Image.getInstance(icon.getImage(), null);
687                     if (im != null) {
688                         im.scaleToFit(icon.getIconWidth() * 0.6f, icon.getIconHeight() * 0.6f);
689                         PdfPCell cell = new PdfPCell(im);
690                         cell.setFixedHeight(icon.getIconHeight() * 0.6f);
691                         cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
692                         cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
693                         cell.setBorder(PdfPCell.NO_BORDER);
694                         return cell;
695                     }
696                 }
697             }
698             catch (Exception e) {
699             }
700         }
701         PdfPCell cell = new PdfPCell();
702         cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
703         cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
704         cell.setBorder(PdfPCell.NO_BORDER);
705         return cell;
706     }
707
708     /**
709      * Format the length as a displayable string.
710      *
711      * @param length the length (assumed to be in default length units)
712      *
713      * @return a string representation of the length with unit abbreviation
714      */
715     protected String toLength (double length) {
716         final Unit defaultUnit = UnitGroup.UNITS_LENGTH.getDefaultUnit();
717         return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(length)) + defaultUnit.toString();
718     }
719
720     /**
721      * Format the mass as a displayable string.
722      *
723      * @param mass  the mass (assumed to be in default mass units)
724      *
725      * @return a string representation of the mass with mass abbreviation
726      */
727     protected String toMass (double mass) {
728         final Unit defaultUnit = UnitGroup.UNITS_MASS.getDefaultUnit();
729         return NumberFormat.getNumberInstance().format(defaultUnit.toUnit(mass)) + defaultUnit.toString();
730     }
731
732     /**
733      * Get a displayable string of the material's name.
734      *
735      * @param material  the material to output
736      *
737      * @return the material name
738      */
739     protected String toMaterialName (Material material) {
740         return material.getName();
741     }
742
743     /**
744      * Format the material density as a displayable string.
745      *
746      * @param material  the material to output
747      *
748      * @return a string representation of the material density
749      */
750     protected String toMaterialDensity (Material material) {
751         return " (" + material.getType().getUnitGroup().getDefaultUnit().toStringUnit(material.getDensity()) + ")";
752     }
753
754 }