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