language selector, bug fixed
[debian/openrocket] / src / net / sf / openrocket / gui / print / DesignReport.java
index 36f9c200e5ef447bdd3741ab4b0dc6a6269af272..e131144477cdab6829b7db3cfc927c48216c7fd9 100644 (file)
@@ -1,40 +1,50 @@
 /*
- * DrawingsPrintable.java
+ * DesignReport.java
  */
 package net.sf.openrocket.gui.print;
 
-import com.itextpdf.text.Document;
-import com.itextpdf.text.DocumentException;
-import com.itextpdf.text.Element;
-import com.itextpdf.text.Paragraph;
-import com.itextpdf.text.Rectangle;
-import com.itextpdf.text.pdf.BaseFont;
-import com.itextpdf.text.pdf.DefaultFontMapper;
-import com.itextpdf.text.pdf.PdfContentByte;
-import com.itextpdf.text.pdf.PdfPCell;
-import com.itextpdf.text.pdf.PdfPTable;
-import com.itextpdf.text.pdf.PdfWriter;
+import java.awt.Graphics2D;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
 import net.sf.openrocket.document.OpenRocketDocument;
 import net.sf.openrocket.document.Simulation;
 import net.sf.openrocket.gui.figureelements.FigureElement;
 import net.sf.openrocket.gui.figureelements.RocketInfo;
-import net.sf.openrocket.gui.print.visitor.BaseVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.MotorMountVisitorStrategy;
-import net.sf.openrocket.gui.print.visitor.StageVisitorStrategy;
 import net.sf.openrocket.gui.scalefigure.RocketPanel;
+import net.sf.openrocket.logging.LogHelper;
+import net.sf.openrocket.masscalc.BasicMassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator;
+import net.sf.openrocket.masscalc.MassCalculator.MassCalcType;
 import net.sf.openrocket.motor.Motor;
-import net.sf.openrocket.rocketcomponent.ComponentVisitor;
 import net.sf.openrocket.rocketcomponent.Configuration;
+import net.sf.openrocket.rocketcomponent.MotorMount;
 import net.sf.openrocket.rocketcomponent.Rocket;
+import net.sf.openrocket.rocketcomponent.RocketComponent;
+import net.sf.openrocket.rocketcomponent.Stage;
 import net.sf.openrocket.simulation.FlightData;
+import net.sf.openrocket.simulation.exception.SimulationException;
+import net.sf.openrocket.startup.Application;
 import net.sf.openrocket.unit.Unit;
 import net.sf.openrocket.unit.UnitGroup;
+import net.sf.openrocket.util.Chars;
+import net.sf.openrocket.util.Coordinate;
 import net.sf.openrocket.util.Prefs;
 
-import java.awt.Graphics2D;
-import java.io.IOException;
-import java.text.DecimalFormat;
-import java.util.List;
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Element;
+import com.itextpdf.text.Paragraph;
+import com.itextpdf.text.Rectangle;
+import com.itextpdf.text.pdf.BaseFont;
+import com.itextpdf.text.pdf.DefaultFontMapper;
+import com.itextpdf.text.pdf.PdfContentByte;
+import com.itextpdf.text.pdf.PdfPCell;
+import com.itextpdf.text.pdf.PdfPTable;
+import com.itextpdf.text.pdf.PdfWriter;
 
 /**
  * <pre>
@@ -67,398 +77,495 @@ import java.util.List;
  * <p/>
  * </pre>
  */
-public class DesignReport extends BaseVisitorStrategy {
-
-    /**
-     * The OR Document.
-     */
-    private OpenRocketDocument rocketDocument;
-
-    /**
-     * A panel used for rendering of the design diagram.
-     */
-    final RocketPanel panel;
-
-    /**
-     * A stage visitor.
-     */
-    private StageVisitorStrategy svs = new StageVisitorStrategy();
-
-    /**
-     * Constructor.
-     *
-     * @param theRocDoc the OR document
-     * @param theIDoc   the iText document
-     */
-    public DesignReport (OpenRocketDocument theRocDoc, Document theIDoc) {
-        super(theIDoc, null);
-        rocketDocument = theRocDoc;
-        panel = new RocketPanel(rocketDocument);
-    }
-
-    /**
-     * Main entry point.  Prints the rocket drawing and design data.
-     *
-     * @param writer a direct byte writer
-     */
-    public void print (PdfWriter writer) {
-        if (writer == null) {
-            return;
-        }
-        com.itextpdf.text.Rectangle pageSize = document.getPageSize();
-        int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
-        int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
-
-        PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, "Rocket Design");
-
-        Rocket rocket = rocketDocument.getRocket();
-        final Configuration configuration = rocket.getDefaultConfiguration();
-        configuration.setAllStages();
-        PdfContentByte canvas = writer.getDirectContent();
-
-        final PrintFigure figure = new PrintFigure(configuration);
-
-        FigureElement cp = panel.getExtraCP();
-        FigureElement cg = panel.getExtraCG();
-        RocketInfo text = panel.getExtraText();
-
-        double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
-
-        canvas.beginText();
-        try {
-            canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
-                                                      BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
-        }
-        catch (DocumentException e) {
-            e.printStackTrace();
-        }
-        catch (IOException e) {
-            e.printStackTrace();
-        }
-        int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
-                .toPoints(1)));
-        final int diagramHeight = pageImageableHeight * 2 - 70 - (int) (figHeightPts);
-        canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
-        canvas.moveTextWithLeading(0, -16);
-
-        float initialY = canvas.getYTLM();
-
-        canvas.showText(rocketDocument.getRocket().getName());
-
-        canvas.newlineShowText("Stages: ");
-        canvas.showText("" + rocket.getStageCount());
-
-
-        if (configuration.hasMotors()) {
-            canvas.newlineShowText("Mass (with motor" + ((configuration.getStageCount() > 1) ? "s): " : "): "));
-        }
-        else {
-            canvas.newlineShowText("Mass (Empty): ");
-        }
-        canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
-
-        canvas.newlineShowText("Stability: ");
-        canvas.showText(text.getStability());
-
-        canvas.newlineShowText("Cg: ");
-        canvas.showText(text.getCg());
-
-        canvas.newlineShowText("Cp: ");
-        canvas.showText(text.getCp());
-        canvas.endText();
-
-        try {
-            //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
-            Paragraph paragraph = new Paragraph();
-            float finalY = canvas.getYTLM();
-            int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
-
-            paragraph.setSpacingAfter(heightOfDiagramAndText);
-            document.add(paragraph);
-
-            String[] mids = rocket.getMotorConfigurationIDs();
-
-            List<Double> stages = getStageWeights(rocket);
-
-
-            for (int j = 0; j < mids.length; j++) {
-                String mid = mids[j];
-                if (mid != null) {
-                    MotorMountVisitorStrategy mmvs = new MotorMountVisitorStrategy(document, mid);
-                    rocket.accept(new ComponentVisitor(mmvs));
-                    PdfPTable parent = new PdfPTable(2);
-                    parent.setWidthPercentage(100);
-                    parent.setHorizontalAlignment(Element.ALIGN_LEFT);
-                    parent.setSpacingBefore(0);
-                    parent.setWidths(new int[]{1, 3});
-                    int leading = 0;
-                    //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
-                    if (j > 1) {
-                        leading = 25;
-                    }
-                    addFlightData(rocket, mid, parent, leading);
-                    addMotorData(mmvs.getMotors(), parent, stages);
-                    document.add(parent);
-                }
-            }
-        }
-        catch (DocumentException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Paint a diagram of the rocket into the PDF document.
-     *
-     * @param thePageImageableWidth  the number of points in the width of the page available for drawing
-     * @param thePageImageableHeight the number of points in the height of the page available for drawing
-     * @param theCanvas              the direct byte writer
-     * @param theFigure              the print figure
-     * @param theCp                  the center of pressure figure element
-     * @param theCg                  the center of gravity figure element
-     *
-     * @return the scale of the diagram
-     */
-    private double paintRocketDiagram (final int thePageImageableWidth, final int thePageImageableHeight, final PdfContentByte theCanvas, final PrintFigure theFigure, final FigureElement theCp, final FigureElement theCg) {
-        theFigure.clearAbsoluteExtra();
-        theFigure.clearRelativeExtra();
-        theFigure.addRelativeExtra(theCp);
-        theFigure.addRelativeExtra(theCg);
-        theFigure.updateFigure();
-
-        double scale =
-                (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
-
-        theFigure.setScale(scale);
-        /*
-         * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
-         */
-        theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
-        theFigure.updateFigure();
-
-
-        final DefaultFontMapper mapper = new DefaultFontMapper();
-        Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
-        g2d.translate(20, 120);
-
-        g2d.scale(0.4d, 0.4d);
-        theFigure.paint(g2d);
-        g2d.dispose();
-        return scale;
-    }
-
-    /**
-     * Add the motor data for a motor configuration to the table.
-     *
-     * @param motors       a motor configuration's list of motors
-     * @param parent       the parent to which the motor data will be added
-     * @param stageWeights the stageWeights of each stage, in order
-     */
-    private void addMotorData (List<Motor> motors, final PdfPTable parent, List<Double> stageWeights) {
-
-        PdfPTable motorTable = new PdfPTable(8);
-        motorTable.setWidthPercentage(68);
-        motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
-
-        final PdfPCell motorCell = ITextHelper.createCell("Motor", PdfPCell.BOTTOM);
-        final int mPad = 10;
-        motorCell.setPaddingLeft(mPad);
-        motorTable.addCell(motorCell);
-        motorTable.addCell(ITextHelper.createCell("Avg Thrust", PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell("Burn Time", PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell("Max Thrust", PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell("Total Impulse", PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell("Thrust to Wt", PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell("Propellant Wt", PdfPCell.BOTTOM));
-        motorTable.addCell(ITextHelper.createCell("Size", PdfPCell.BOTTOM));
-
-        DecimalFormat df = new DecimalFormat("#,##0.0#");
-        for (int i = 0; i < motors.size(); i++) {
-            int border = Rectangle.BOTTOM;
-            if (i == motors.size() - 1) {
-                border = Rectangle.NO_BORDER;
-            }
-            Motor motor = motors.get(i);
-            double motorWeight = (motor.getLaunchCG().weight - motor.getEmptyCG().weight) * 1000;  //convert to grams
-
-            final PdfPCell motorVCell = ITextHelper.createCell(motor.getDesignation(), border);
-            motorVCell.setPaddingLeft(mPad);
-            motorTable.addCell(motorVCell);
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getAverageThrustEstimate()) + " " + UnitGroup
-                    .UNITS_FORCE
-                    .getDefaultUnit().toString(), border));
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getBurnTimeEstimate()) + " " + UnitGroup
-                    .UNITS_FLIGHT_TIME
-                    .getDefaultUnit().toString(), border));
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getMaxThrustEstimate()) + " " + UnitGroup
-                    .UNITS_FORCE.getDefaultUnit()
-                    .toString(), border));
-            motorTable.addCell(ITextHelper.createCell(df.format(motor.getTotalImpulseEstimate()) + " " + UnitGroup
-                    .UNITS_IMPULSE
-                    .getDefaultUnit().toString(), border));
-            double ttw = motor.getAverageThrustEstimate() / (getStageWeight(stageWeights, i) + (motor
-                    .getLaunchCG().weight * 9.80665));
-            motorTable.addCell(ITextHelper.createCell(df.format(ttw) + ":1", border));
-
-            motorTable.addCell(ITextHelper.createCell(df.format(motorWeight) + " " + UnitGroup.UNITS_MASS
-                    .getDefaultUnit().toString(), border));
-
-            final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS
-                    .getDefaultUnit();
-            motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
-                                                      "/" +
-                                                      motorUnit.toString(motor.getLength()) + " " +
-                                                      motorUnit.toString(), border));
-        }
-        PdfPCell c = new PdfPCell(motorTable);
-        c.setBorder(PdfPCell.LEFT);
-        c.setBorderWidthTop(0f);
-        parent.addCell(c);
-    }
-
-
-    /**
-     * Add the motor data for a motor configuration to the table.
-     *
-     * @param theRocket the rocket
-     * @param mid       a motor configuration id
-     * @param parent    the parent to which the motor data will be added
-     * @param leading   the number of points for the leading
-     */
-    private void addFlightData (final Rocket theRocket, final String mid, final PdfPTable parent, int leading) {
-        FlightData flight = null;
-        if (theRocket.getMotorConfigurationIDs().length > 1) {
-            Rocket duplicate = theRocket.copy();
-            Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
-            simulation.getConditions().setMotorConfigurationID(mid);
-
-            flight = PrintSimulationWorker.doit(simulation);
-
-            if (flight != null) {
-                try {
-                    final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
-                    final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
-                    final Unit flightUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
-
-                    PdfPTable labelTable = new PdfPTable(2);
-                    labelTable.setWidths(new int[]{3, 2});
-                    final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
-                            theRocket.getMotorConfigurationNameOrDescription(mid)), PrintUtilities.BOLD);
-                    chunk.setLeading(leading);
-                    chunk.setSpacingAfter(3f);
-
-                    document.add(chunk);
-
-                    DecimalFormat df = new DecimalFormat("#,##0.0#");
-
-                    final PdfPCell cell = ITextHelper.createCell("Altitude", 2, 2);
-                    cell.setUseBorderPadding(false);
-                    cell.setBorderWidthTop(0f);
-                    labelTable.addCell(cell);
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxAltitude()) + " " + distanceUnit,
-                                                              2, 2));
-
-                    labelTable.addCell(ITextHelper.createCell("Flight Time", 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getFlightTime()) + " " + flightUnit, 2,
-                                                              2));
-
-                    labelTable.addCell(ITextHelper.createCell("Time to Apogee", 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getTimeToApogee()) + " " + flightUnit, 2,
-                                                              2));
-
-                    labelTable.addCell(ITextHelper.createCell("Velocity off Pad", 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(
-                            flight.getLaunchRodVelocity()) + " " + velocityUnit, 2, 2));
-
-                    labelTable.addCell(ITextHelper.createCell("Max Velocity", 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(flight.getMaxVelocity()) + " " + velocityUnit,
-                                                              2, 2));
-
-                    labelTable.addCell(ITextHelper.createCell("Landing Velocity", 2, 2));
-                    labelTable.addCell(ITextHelper.createCell(df.format(
-                            flight.getGroundHitVelocity()) + " " + velocityUnit, 2, 2));
-
-                    //Add the table to the parent; have to wrap it in a cell
-                    PdfPCell c = new PdfPCell(labelTable);
-                    c.setBorder(PdfPCell.RIGHT);
-                    c.setBorderWidthTop(0);
-                    c.setTop(0);
-                    parent.addCell(c);
-                }
-                catch (DocumentException e) {
-                    e.printStackTrace();
-                }
-            }
-        }
-    }
-
-    /**
-     * Strip [] brackets from a string.
-     *
-     * @param target the original string
-     *
-     * @return target with [] removed
-     */
-    private String stripBrackets (String target) {
-        return stripLeftBracket(stripRightBracket(target));
-    }
-
-    /**
-     * Strip [ from a string.
-     *
-     * @param target the original string
-     *
-     * @return target with [ removed
-     */
-    private String stripLeftBracket (String target) {
-        return target.replace("[", "");
-    }
-
-    /**
-     * Strip ] from a string.
-     *
-     * @param target the original string
-     *
-     * @return target with ] removed
-     */
-    private String stripRightBracket (String target) {
-        return target.replace("]", "");
-    }
-
-    /**
-     * Use a visitor to get the sorted list of Stage references, then from those get the stage masses and convert to
-     * weight.
-     *
-     * @param rocket the rocket
-     *
-     * @return a sorted list of Stage weights (mass * gravity), in Newtons
-     */
-    private List<Double> getStageWeights (Rocket rocket) {
-        rocket.accept(new ComponentVisitor(svs));
-        svs.close();
-        List<Double> stages = svs.getStages();
-        for (int i = 0; i < stages.size(); i++) {
-            Double stage = stages.get(i);
-            stages.set(i, stage * 9.80665);
-        }
-        return stages;
-    }
-
-    /**
-     * Compute the total stage weight from a list of stage weights.  This sums up the weight of the given stage plus all
-     * stages that sit atop it (depend upon it for thrust).
-     *
-     * @param weights the list of stage weights, in Newtons
-     * @param stage   a stage number, 0 being topmost stage
-     *
-     * @return the total weight of the stage and all stages sitting atop the given stage, in Newtons
-     */
-    private double getStageWeight (List<Double> weights, int stage) {
-
-        double result = 0d;
-        for (int i = 0; i <= stage; i++) {
-            result += weights.get(i);
-        }
-        return result;
-    }
+public class DesignReport {
+       
+       /**
+        * The logger.
+        */
+       private static final LogHelper log = Application.getLogger();
+       
+       /**
+        * The OR Document.
+        */
+       private OpenRocketDocument rocketDocument;
+       
+       /**
+        * A panel used for rendering of the design diagram.
+        */
+       final RocketPanel panel;
+       
+       /**
+        * The iText document.
+        */
+       protected Document document;
+       
+       /** The displayed strings. */
+       private static final String STAGES = "Stages: ";
+       private static final String MASS_WITH_MOTORS = "Mass (with motors): ";
+       private static final String MASS_WITH_MOTOR = "Mass (with motor): ";
+       private static final String MASS_EMPTY = "Mass (Empty): ";
+       private static final String STABILITY = "Stability: ";
+       private static final String CG = "CG: ";
+       private static final String CP = "CP: ";
+       private static final String MOTOR = "Motor";
+       private static final String AVG_THRUST = "Avg Thrust";
+       private static final String BURN_TIME = "Burn Time";
+       private static final String MAX_THRUST = "Max Thrust";
+       private static final String TOTAL_IMPULSE = "Total Impulse";
+       private static final String THRUST_TO_WT = "Thrust to Wt";
+       private static final String PROPELLANT_WT = "Propellant Wt";
+       private static final String SIZE = "Size";
+       private static final String ALTITUDE = "Altitude";
+       private static final String FLIGHT_TIME = "Flight Time";
+       private static final String TIME_TO_APOGEE = "Time to Apogee";
+       private static final String VELOCITY_OFF_PAD = "Velocity off Pad";
+       private static final String MAX_VELOCITY = "Max Velocity";
+       private static final String LANDING_VELOCITY = "Landing Velocity";
+       private static final String ROCKET_DESIGN = "Rocket Design";
+       private static final double GRAVITY_CONSTANT = 9.80665d;
+       
+       /**
+        * Constructor.
+        *
+        * @param theRocDoc the OR document
+        * @param theIDoc   the iText document
+        */
+       public DesignReport(OpenRocketDocument theRocDoc, Document theIDoc) {
+               document = theIDoc;
+               rocketDocument = theRocDoc;
+               panel = new RocketPanel(rocketDocument);
+       }
+       
+       /**
+        * Main entry point.  Prints the rocket drawing and design data.
+        *
+        * @param writer a direct byte writer
+        */
+       public void writeToDocument(PdfWriter writer) {
+               if (writer == null) {
+                       return;
+               }
+               com.itextpdf.text.Rectangle pageSize = document.getPageSize();
+               int pageImageableWidth = (int) pageSize.getWidth() - (int) pageSize.getBorderWidth() * 2;
+               int pageImageableHeight = (int) pageSize.getHeight() / 2 - (int) pageSize.getBorderWidthTop();
+               
+               PrintUtilities.addText(document, PrintUtilities.BIG_BOLD, ROCKET_DESIGN);
+               
+               Rocket rocket = rocketDocument.getRocket();
+               final Configuration configuration = rocket.getDefaultConfiguration();
+               configuration.setAllStages();
+               PdfContentByte canvas = writer.getDirectContent();
+               
+               final PrintFigure figure = new PrintFigure(configuration);
+               
+               FigureElement cp = panel.getExtraCP();
+               FigureElement cg = panel.getExtraCG();
+               RocketInfo text = panel.getExtraText();
+               
+               double scale = paintRocketDiagram(pageImageableWidth, pageImageableHeight, canvas, figure, cp, cg);
+               
+               canvas.beginText();
+               try {
+                       canvas.setFontAndSize(BaseFont.createFont(PrintUtilities.NORMAL.getFamilyname(), BaseFont.CP1252,
+                                                                                                               BaseFont.EMBEDDED), PrintUtilities.NORMAL_FONT_SIZE);
+               } catch (DocumentException e) {
+                       log.error("Could not set font.", e);
+               } catch (IOException e) {
+                       log.error("Could not create font.", e);
+               }
+               int figHeightPts = (int) (PrintUnit.METERS.toPoints(figure.getFigureHeight()) * 0.4 * (scale / PrintUnit.METERS
+                               .toPoints(1)));
+               final int diagramHeight = pageImageableHeight * 2 - 70 - (figHeightPts);
+               canvas.moveText(document.leftMargin() + pageSize.getBorderWidthLeft(), diagramHeight);
+               canvas.moveTextWithLeading(0, -16);
+               
+               float initialY = canvas.getYTLM();
+               
+               canvas.showText(rocketDocument.getRocket().getName());
+               
+               canvas.newlineShowText(STAGES);
+               canvas.showText("" + rocket.getStageCount());
+               
+
+               if (configuration.hasMotors()) {
+                       if (configuration.getStageCount() > 1) {
+                               canvas.newlineShowText(MASS_WITH_MOTORS);
+                       } else {
+                               canvas.newlineShowText(MASS_WITH_MOTOR);
+                       }
+               } else {
+                       canvas.newlineShowText(MASS_EMPTY);
+               }
+               canvas.showText(text.getMass(UnitGroup.UNITS_MASS.getDefaultUnit()));
+               
+               canvas.newlineShowText(STABILITY);
+               canvas.showText(text.getStability());
+               
+               canvas.newlineShowText(CG);
+               canvas.showText(text.getCg());
+               
+               canvas.newlineShowText(CP);
+               canvas.showText(text.getCp());
+               canvas.endText();
+               
+               try {
+                       //Move the internal pointer of the document below that of what was just written using the direct byte buffer.
+                       Paragraph paragraph = new Paragraph();
+                       float finalY = canvas.getYTLM();
+                       int heightOfDiagramAndText = (int) (pageSize.getHeight() - (finalY - initialY + diagramHeight));
+                       
+                       paragraph.setSpacingAfter(heightOfDiagramAndText);
+                       document.add(paragraph);
+                       
+                       String[] motorIds = rocket.getMotorConfigurationIDs();
+                       
+                       List<Double> stageMasses = getStageMasses(rocket);
+                       
+                       for (int j = 0; j < motorIds.length; j++) {
+                               String motorId = motorIds[j];
+                               if (motorId != null) {
+                                       PdfPTable parent = new PdfPTable(2);
+                                       parent.setWidthPercentage(100);
+                                       parent.setHorizontalAlignment(Element.ALIGN_LEFT);
+                                       parent.setSpacingBefore(0);
+                                       parent.setWidths(new int[] { 1, 3 });
+                                       int leading = 0;
+                                       //The first motor config is always null.  Skip it and the top-most motor, then set the leading.
+                                       if (j > 1) {
+                                               leading = 25;
+                                       }
+                                       addFlightData(rocket, motorId, parent, leading);
+                                       addMotorData(rocket, motorId, parent);
+                                       document.add(parent);
+                               }
+                       }
+               } catch (DocumentException e) {
+                       log.error("Could not modify document.", e);
+               }
+       }
+       
+       /**
+        * Get the motor list for all motor mounts.
+        *
+        * @param theRocket the rocket object
+        * @param theMid    the motor id
+        *
+        * @return a list of Motor
+        */
+       private List<Motor> getMotorList(final Rocket theRocket, final String theMid) {
+               Iterator<RocketComponent> components = theRocket.iterator();
+               final List<Motor> motorList = new ArrayList<Motor>();
+               while (components.hasNext()) {
+                       RocketComponent rocketComponent = components.next();
+                       if (rocketComponent instanceof MotorMount) {
+                               MotorMount mm = (MotorMount) rocketComponent;
+                               final Motor motor = mm.getMotor(theMid);
+                               if (motor != null) {
+                                       motorList.add(motor);
+                               }
+                       }
+               }
+               return motorList;
+       }
+       
+       /**
+        * Paint a diagram of the rocket into the PDF document.
+        *
+        * @param thePageImageableWidth  the number of points in the width of the page available for drawing
+        * @param thePageImageableHeight the number of points in the height of the page available for drawing
+        * @param theCanvas              the direct byte writer
+        * @param theFigure              the print figure
+        * @param theCp                  the center of pressure figure element
+        * @param theCg                  the center of gravity figure element
+        *
+        * @return the scale of the diagram
+        */
+       private double paintRocketDiagram(final int thePageImageableWidth, final int thePageImageableHeight,
+                                                                               final PdfContentByte theCanvas, final PrintFigure theFigure,
+                                                                               final FigureElement theCp, final FigureElement theCg) {
+               theFigure.clearAbsoluteExtra();
+               theFigure.clearRelativeExtra();
+               theFigure.addRelativeExtra(theCp);
+               theFigure.addRelativeExtra(theCg);
+               theFigure.updateFigure();
+               
+               double scale =
+                               (thePageImageableWidth * 2.2) / theFigure.getFigureWidth();
+               
+               theFigure.setScale(scale);
+               /*
+                * page dimensions are in points-per-inch, which, in Java2D, are the same as pixels-per-inch; thus we don't need any conversion
+                */
+               theFigure.setSize(thePageImageableWidth, thePageImageableHeight);
+               theFigure.updateFigure();
+               
+
+               final DefaultFontMapper mapper = new DefaultFontMapper();
+               Graphics2D g2d = theCanvas.createGraphics(thePageImageableWidth, thePageImageableHeight * 2, mapper);
+               g2d.translate(20, 120);
+               
+               g2d.scale(0.4d, 0.4d);
+               theFigure.paint(g2d);
+               g2d.dispose();
+               return scale;
+       }
+       
+       /**
+        * Add the motor data for a motor configuration to the table.
+        *
+        * @param rocket        the rocket
+        * @param motorId       the motor ID to output
+        * @param parent        the parent to which the motor data will be added
+        */
+       private void addMotorData(Rocket rocket, String motorId, final PdfPTable parent) {
+               
+               PdfPTable motorTable = new PdfPTable(8);
+               motorTable.setWidthPercentage(68);
+               motorTable.setHorizontalAlignment(Element.ALIGN_LEFT);
+               
+               final PdfPCell motorCell = ITextHelper.createCell(MOTOR, PdfPCell.BOTTOM);
+               final int mPad = 10;
+               motorCell.setPaddingLeft(mPad);
+               motorTable.addCell(motorCell);
+               motorTable.addCell(ITextHelper.createCell(AVG_THRUST, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(BURN_TIME, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(MAX_THRUST, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(TOTAL_IMPULSE, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(THRUST_TO_WT, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(PROPELLANT_WT, PdfPCell.BOTTOM));
+               motorTable.addCell(ITextHelper.createCell(SIZE, PdfPCell.BOTTOM));
+               
+               DecimalFormat ttwFormat = new DecimalFormat("0.00");
+               
+               MassCalculator massCalc = new BasicMassCalculator();
+               
+               Configuration config = new Configuration(rocket);
+               config.setMotorConfigurationID(motorId);
+               
+               int totalMotorCount = 0;
+               double totalPropMass = 0;
+               double totalImpulse = 0;
+               double totalTTW = 0;
+               
+               int stage = 0;
+               double stageMass = 0;
+               
+               boolean topBorder = false;
+               for (RocketComponent c : rocket) {
+                       
+                       if (c instanceof Stage) {
+                               config.setToStage(stage);
+                               stage++;
+                               stageMass = massCalc.getCG(config, MassCalcType.LAUNCH_MASS).weight;
+                               // Calculate total thrust-to-weight from only lowest stage motors
+                               totalTTW = 0;
+                               topBorder = true;
+                       }
+                       
+                       if (c instanceof MotorMount && ((MotorMount) c).isMotorMount()) {
+                               MotorMount mount = (MotorMount) c;
+                               
+                               if (mount.isMotorMount() && mount.getMotor(motorId) != null) {
+                                       Motor motor = mount.getMotor(motorId);
+                                       int motorCount = c.toAbsolute(Coordinate.NUL).length;
+                                       
+
+                                       int border = Rectangle.NO_BORDER;
+                                       if (topBorder) {
+                                               border = Rectangle.TOP;
+                                               topBorder = false;
+                                       }
+                                       
+                                       String name = motor.getDesignation();
+                                       if (motorCount > 1) {
+                                               name += " (" + Chars.TIMES + motorCount + ")";
+                                       }
+                                       
+                                       final PdfPCell motorVCell = ITextHelper.createCell(name, border);
+                                       motorVCell.setPaddingLeft(mPad);
+                                       motorTable.addCell(motorVCell);
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getAverageThrustEstimate()), border));
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit().toStringUnit(motor.getBurnTimeEstimate()), border));
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_FORCE.getDefaultUnit().toStringUnit(motor.getMaxThrustEstimate()), border));
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(motor.getTotalImpulseEstimate()), border));
+                                       
+                                       double ttw = motor.getAverageThrustEstimate() / (stageMass * GRAVITY_CONSTANT);
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       ttwFormat.format(ttw) + ":1", border));
+                                       
+                                       double propMass = (motor.getLaunchCG().weight - motor.getEmptyCG().weight);
+                                       motorTable.addCell(ITextHelper.createCell(
+                                                       UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(propMass), border));
+                                       
+                                       final Unit motorUnit = UnitGroup.UNITS_MOTOR_DIMENSIONS.getDefaultUnit();
+                                       motorTable.addCell(ITextHelper.createCell(motorUnit.toString(motor.getDiameter()) +
+                                                                                                                               "/" +
+                                                                                                                               motorUnit.toString(motor.getLength()) + " " +
+                                                                                                                               motorUnit.toString(), border));
+                                       
+                                       // Sum up total count
+                                       totalMotorCount += motorCount;
+                                       totalPropMass += propMass * motorCount;
+                                       totalImpulse += motor.getTotalImpulseEstimate() * motorCount;
+                                       totalTTW += ttw * motorCount;
+                               }
+                       }
+               }
+               
+               if (totalMotorCount > 1) {
+                       int border = Rectangle.TOP;
+                       final PdfPCell motorVCell = ITextHelper.createCell("Total:", border);
+                       motorVCell.setPaddingLeft(mPad);
+                       motorTable.addCell(motorVCell);
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       motorTable.addCell(ITextHelper.createCell(
+                                               UnitGroup.UNITS_IMPULSE.getDefaultUnit().toStringUnit(totalImpulse), border));
+                       motorTable.addCell(ITextHelper.createCell(
+                                       ttwFormat.format(totalTTW) + ":1", border));
+                       motorTable.addCell(ITextHelper.createCell(
+                                               UnitGroup.UNITS_MASS.getDefaultUnit().toStringUnit(totalPropMass), border));
+                       motorTable.addCell(ITextHelper.createCell("", border));
+                       
+               }
+               
+               PdfPCell c = new PdfPCell(motorTable);
+               c.setBorder(PdfPCell.LEFT);
+               c.setBorderWidthTop(0f);
+               parent.addCell(c);
+       }
+       
+       
+       /**
+        * Add the motor data for a motor configuration to the table.
+        *
+        * @param theRocket the rocket
+        * @param motorId   a motor configuration id
+        * @param parent    the parent to which the motor data will be added
+        * @param leading   the number of points for the leading
+        */
+       private void addFlightData(final Rocket theRocket, final String motorId, final PdfPTable parent, int leading) {
+               
+               // Perform flight simulation
+               Rocket duplicate = theRocket.copyWithOriginalID();
+               FlightData flight = null;
+               try {
+                       Simulation simulation = Prefs.getBackgroundSimulation(duplicate);
+                       simulation.getConditions().setMotorConfigurationID(motorId);
+                       simulation.simulate();
+                       flight = simulation.getSimulatedData();
+               } catch (SimulationException e1) {
+                       // Ignore
+               }
+               
+               // Output the flight data
+               if (flight != null) {
+                       try {
+                               final Unit distanceUnit = UnitGroup.UNITS_DISTANCE.getDefaultUnit();
+                               final Unit velocityUnit = UnitGroup.UNITS_VELOCITY.getDefaultUnit();
+                               final Unit flightTimeUnit = UnitGroup.UNITS_FLIGHT_TIME.getDefaultUnit();
+                               
+                               PdfPTable labelTable = new PdfPTable(2);
+                               labelTable.setWidths(new int[] { 3, 2 });
+                               final Paragraph chunk = ITextHelper.createParagraph(stripBrackets(
+                                                       theRocket.getMotorConfigurationNameOrDescription(motorId)), PrintUtilities.BOLD);
+                               chunk.setLeading(leading);
+                               chunk.setSpacingAfter(3f);
+                               
+                               document.add(chunk);
+                               
+                               final PdfPCell cell = ITextHelper.createCell(ALTITUDE, 2, 2);
+                               cell.setUseBorderPadding(false);
+                               cell.setBorderWidthTop(0f);
+                               labelTable.addCell(cell);
+                               labelTable.addCell(ITextHelper.createCell(distanceUnit.toStringUnit(flight.getMaxAltitude()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(FLIGHT_TIME, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getFlightTime()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(TIME_TO_APOGEE, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(flightTimeUnit.toStringUnit(flight.getTimeToApogee()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(VELOCITY_OFF_PAD, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getLaunchRodVelocity()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(MAX_VELOCITY, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getMaxVelocity()), 2, 2));
+                               
+                               labelTable.addCell(ITextHelper.createCell(LANDING_VELOCITY, 2, 2));
+                               labelTable.addCell(ITextHelper.createCell(velocityUnit.toStringUnit(flight.getGroundHitVelocity()), 2, 2));
+                               
+                               //Add the table to the parent; have to wrap it in a cell
+                               PdfPCell c = new PdfPCell(labelTable);
+                               c.setBorder(PdfPCell.RIGHT);
+                               c.setBorderWidthTop(0);
+                               c.setTop(0);
+                               parent.addCell(c);
+                       } catch (DocumentException e) {
+                               log.error("Could not add flight data to document.", e);
+                       }
+               }
+       }
+       
+       /**
+        * Strip [] brackets from a string.
+        *
+        * @param target the original string
+        *
+        * @return target with [] removed
+        */
+       private String stripBrackets(String target) {
+               return stripLeftBracket(stripRightBracket(target));
+       }
+       
+       /**
+        * Strip [ from a string.
+        *
+        * @param target the original string
+        *
+        * @return target with [ removed
+        */
+       private String stripLeftBracket(String target) {
+               return target.replace("[", "");
+       }
+       
+       /**
+        * Strip ] from a string.
+        *
+        * @param target the original string
+        *
+        * @return target with ] removed
+        */
+       private String stripRightBracket(String target) {
+               return target.replace("]", "");
+       }
+       
+       
+       /**
+        * Return a list of cumulative stage masses.  The latter masses include the mass
+        * of the upper stages as well.
+        *
+        * @param rocket        the rocket
+        * @return                      a list containing the cumulative stage masses
+        */
+       private List<Double> getStageMasses(final Rocket rocket) {
+               List<Double> masses = new ArrayList<Double>();
+               int stages = rocket.getStageCount();
+               
+               Configuration config = new Configuration(rocket);
+               MassCalculator calc = new BasicMassCalculator();
+               
+               for (int i = 0; i < stages; i++) {
+                       config.setToStage(i);
+                       masses.add(calc.getCG(config, MassCalcType.NO_MOTORS).weight);
+               }
+               
+               return masses;
+       }
+       
 }