From 64c3b0c83a6cb4dda5e4150b9f09346590f84014 Mon Sep 17 00:00:00 2001 From: Jason Blood Date: Thu, 10 May 2012 02:48:13 +0000 Subject: [PATCH] Added PageFitPrintStrategy and related files to print multiple fins, transitions, and nosecones onto the same page(s) --- core/ChangeLog | 4 + .../print/AbstractPrintableTransition.java | 9 +- .../openrocket/gui/print/PrintController.java | 23 ++- .../gui/print/PrintableComponent.java | 96 ++++++++++ .../openrocket/gui/print/PrintableFinSet.java | 48 +---- .../gui/print/PrintableNoseCone.java | 6 +- .../gui/print/PrintableTransition.java | 4 + .../print/visitor/FinSetPrintStrategy.java | 28 ++- .../print/visitor/PageFitPrintStrategy.java | 171 ++++++++++++++++++ .../gui/print/visitor/TransitionStrategy.java | 15 +- 10 files changed, 328 insertions(+), 76 deletions(-) create mode 100644 core/src/net/sf/openrocket/gui/print/PrintableComponent.java create mode 100644 core/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java diff --git a/core/ChangeLog b/core/ChangeLog index f519be9fb..f9267cc12 100644 --- a/core/ChangeLog +++ b/core/ChangeLog @@ -1,3 +1,7 @@ +2012-05-09 Jason Blood + + * Add PageFitPrintStrategy and related files to print multiple fins, transitions, and nosecones onto the same page(s) + 2012-04-19 Sampo Niskanen * Allow opening recovery device on stage separation diff --git a/core/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java b/core/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java index 8e6042de9..bb1d12405 100644 --- a/core/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java +++ b/core/src/net/sf/openrocket/gui/print/AbstractPrintableTransition.java @@ -6,7 +6,7 @@ import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; -public abstract class AbstractPrintableTransition extends JPanel { +public abstract class AbstractPrintableTransition extends PrintableComponent { /** * The stroke of the transition arc. */ @@ -21,7 +21,7 @@ public abstract class AbstractPrintableTransition extends JPanel { * The Y margin. */ protected int marginY = (int) PrintUnit.INCHES.toPoints(0.25f); - + /** * Constructor. Initialize this printable with the component to be printed. * @@ -29,8 +29,6 @@ public abstract class AbstractPrintableTransition extends JPanel { * @param transition the component to be printed */ public AbstractPrintableTransition(boolean isDoubleBuffered, Transition transition) { - super(isDoubleBuffered); - setBackground(Color.white); init(transition); } @@ -49,7 +47,7 @@ public abstract class AbstractPrintableTransition extends JPanel { * @param g2 the graphics context */ protected abstract void draw(Graphics2D g2); - + /** * Returns a generated image of the transition. May then be used wherever AWT images can be used, or converted to * another image/picture format and used accordingly. @@ -80,6 +78,7 @@ public abstract class AbstractPrintableTransition extends JPanel { g2.setColor(Color.BLACK); g2.setStroke(thinStroke); + g2.translate(getOffsetX(), getOffsetY()); draw(g2); } diff --git a/core/src/net/sf/openrocket/gui/print/PrintController.java b/core/src/net/sf/openrocket/gui/print/PrintController.java index f6e6dba71..64d2b4d76 100644 --- a/core/src/net/sf/openrocket/gui/print/PrintController.java +++ b/core/src/net/sf/openrocket/gui/print/PrintController.java @@ -12,6 +12,7 @@ import com.itextpdf.text.pdf.PdfBoolean; import com.itextpdf.text.pdf.PdfName; import com.itextpdf.text.pdf.PdfWriter; import net.sf.openrocket.document.OpenRocketDocument; +import net.sf.openrocket.gui.print.visitor.PageFitPrintStrategy; import net.sf.openrocket.gui.print.visitor.FinMarkingGuideStrategy; import net.sf.openrocket.gui.print.visitor.FinSetPrintStrategy; import net.sf.openrocket.gui.print.visitor.PartsDetailVisitorStrategy; @@ -53,6 +54,10 @@ public class PrintController { Thread.sleep(1000); } catch (InterruptedException e) { } + + // Used to combine multiple components onto fewer sheets of paper + PageFitPrintStrategy pageFitPrint = new PageFitPrintStrategy(idoc, writer); + while (toBePrinted.hasNext()) { PrintableContext printableContext = toBePrinted.next(); @@ -65,23 +70,23 @@ public class PrintController { idoc.newPage(); break; case FIN_TEMPLATE: - final FinSetPrintStrategy finWriter = new FinSetPrintStrategy(idoc, writer, stages); + final FinSetPrintStrategy finWriter = new FinSetPrintStrategy(idoc, writer, stages, pageFitPrint); finWriter.writeToDocument(doc.getRocket()); break; - case PARTS_DETAIL: + case PARTS_DETAIL: final PartsDetailVisitorStrategy detailVisitor = new PartsDetailVisitorStrategy(idoc, writer, stages); - detailVisitor.writeToDocument(doc.getRocket()); - detailVisitor.close(); - idoc.newPage(); + detailVisitor.writeToDocument(doc.getRocket()); + detailVisitor.close(); + idoc.newPage(); break; case TRANSITION_TEMPLATE: - final TransitionStrategy tranWriter = new TransitionStrategy(idoc, writer, stages); + final TransitionStrategy tranWriter = new TransitionStrategy(idoc, writer, stages, pageFitPrint); tranWriter.writeToDocument(doc.getRocket(), false); idoc.newPage(); break; case NOSE_CONE_TEMPLATE: - final TransitionStrategy coneWriter = new TransitionStrategy(idoc, writer, stages); + final TransitionStrategy coneWriter = new TransitionStrategy(idoc, writer, stages, pageFitPrint); coneWriter.writeToDocument(doc.getRocket(), true); idoc.newPage(); break; @@ -93,6 +98,10 @@ public class PrintController { break; } } + // Write out parts that we are going to combine onto single sheets of paper + pageFitPrint.writeToDocument(doc.getRocket()); + idoc.newPage(); + //Stupid iText throws a really nasty exception if there is no data when close is called. if (writer.getCurrentDocumentSize() <= 140) { writer.setPageEmpty(false); diff --git a/core/src/net/sf/openrocket/gui/print/PrintableComponent.java b/core/src/net/sf/openrocket/gui/print/PrintableComponent.java new file mode 100644 index 000000000..384217a59 --- /dev/null +++ b/core/src/net/sf/openrocket/gui/print/PrintableComponent.java @@ -0,0 +1,96 @@ +/* + * PrintableComponent.java + */ +package net.sf.openrocket.gui.print; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; + +import javax.swing.JPanel; + +/** + * Common interface for components we want to print. Used by PageFitPrintStrategy + * + * @author Jason Blood + */ +public class PrintableComponent extends JPanel implements Printable { + + /** + * The printing offsets + */ + private int offsetX = 0; + private int offsetY = 0; + + /** + * Constructor. + */ + public PrintableComponent() { + super(false); + } + + /** + * From the java.awt.print.Printable interface. + *

+ * Prints the page at the specified index into the specified {@link java.awt.Graphics} context in the specified + * format. A PrinterJob calls the Printable interface to request that a page be rendered + * into the context specified by graphics. The format of the page to be drawn is specified by + * pageFormat. The zero based index of the requested page is specified by pageIndex. If + * the requested page does not exist then this method returns NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. The + * Graphics class or subclass implements the {@link java.awt.print.PrinterGraphics} interface to + * provide additional information. If the Printable object aborts the print job then it throws a + * {@link java.awt.print.PrinterException}. + *

+ * Note: This is not currently used in OpenRocket. It's only here for reference. + * + * @param graphics the context into which the page is drawn + * @param pageFormat the size and orientation of the page being drawn + * @param pageIndex the zero based index of the page to be drawn + * + * @return PAGE_EXISTS if the page is rendered successfully or NO_SUCH_PAGE if pageIndex specifies a + * non-existent page. + * + * @throws java.awt.print.PrinterException + * thrown when the print job is terminated. + */ + @Override + public int print (final Graphics graphics, final PageFormat pageFormat, final int pageIndex) + throws PrinterException { + + Graphics2D g2d = (Graphics2D) graphics; + PrintUtilities.translateToJavaOrigin(g2d, pageFormat); + PrintUtilities.disableDoubleBuffering(this); + paint(g2d); + PrintUtilities.enableDoubleBuffering(this); + return Printable.PAGE_EXISTS; + } + + /** + * Set the offset this component will be printed to the page + * @param x X offset to print at. + * @param x Y offset to print at. + */ + public void setPrintOffset(int x, int y) { + offsetX = x; + offsetY = y; + } + + /** + * Get the X offset this component will be printed to the page + * @return X offset to print at. + */ + public int getOffsetX() { + return offsetX; + } + + /** + * Get the Y offset this component will be printed to the page + * @return Y offset to print at. + */ + public int getOffsetY() { + return offsetY; + } +} diff --git a/core/src/net/sf/openrocket/gui/print/PrintableFinSet.java b/core/src/net/sf/openrocket/gui/print/PrintableFinSet.java index cf07cb13e..bed1fdf9d 100644 --- a/core/src/net/sf/openrocket/gui/print/PrintableFinSet.java +++ b/core/src/net/sf/openrocket/gui/print/PrintableFinSet.java @@ -19,7 +19,7 @@ import java.awt.print.PrinterException; * modified) and rendering it within a JPanel. The JPanel is not actually visualized on a display, but instead renders * it to a print device. */ -public class PrintableFinSet extends JPanel implements Printable { +public class PrintableFinSet extends PrintableComponent { /** * The object that represents the shape (outline) of the fin. This gets drawn onto the Swing component. @@ -58,9 +58,9 @@ public class PrintableFinSet extends JPanel implements Printable { * @param points an array of points. */ public PrintableFinSet (Coordinate[] points) { - super(false); + //super(false); init(points); - setBackground(Color.white); + //setBackground(Color.white); } /** @@ -108,42 +108,6 @@ public class PrintableFinSet extends JPanel implements Printable { return marginY; } - /** - * From the java.awt.print.Printable interface. - *

- * Prints the page at the specified index into the specified {@link java.awt.Graphics} context in the specified - * format. A PrinterJob calls the Printable interface to request that a page be rendered - * into the context specified by graphics. The format of the page to be drawn is specified by - * pageFormat. The zero based index of the requested page is specified by pageIndex. If - * the requested page does not exist then this method returns NO_SUCH_PAGE; otherwise PAGE_EXISTS is returned. The - * Graphics class or subclass implements the {@link java.awt.print.PrinterGraphics} interface to - * provide additional information. If the Printable object aborts the print job then it throws a - * {@link java.awt.print.PrinterException}. - *

- * Note: This is not currently used in OpenRocket. It's only here for reference. - * - * @param graphics the context into which the page is drawn - * @param pageFormat the size and orientation of the page being drawn - * @param pageIndex the zero based index of the page to be drawn - * - * @return PAGE_EXISTS if the page is rendered successfully or NO_SUCH_PAGE if pageIndex specifies a - * non-existent page. - * - * @throws java.awt.print.PrinterException - * thrown when the print job is terminated. - */ - @Override - public int print (final Graphics graphics, final PageFormat pageFormat, final int pageIndex) - throws PrinterException { - - Graphics2D g2d = (Graphics2D) graphics; - PrintUtilities.translateToJavaOrigin(g2d, pageFormat); - PrintUtilities.disableDoubleBuffering(this); - paint(g2d); - PrintUtilities.enableDoubleBuffering(this); - return Printable.PAGE_EXISTS; - } - /** * Returns a generated image of the fin set. May then be used wherever AWT images can be used, or converted to * another image/picture format and used accordingly. @@ -174,8 +138,7 @@ public class PrintableFinSet extends JPanel implements Printable { * @param g the Java2D graphics context */ @Override - public void paintComponent (Graphics g) { - super.paintComponent(g); + public void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; int x = 0; @@ -189,11 +152,10 @@ public class PrintableFinSet extends JPanel implements Printable { y = marginY + Math.abs(minY); } // Reset the origin. - g2d.translate(x, y); + g2d.translate(x + getOffsetX(), y + getOffsetY()); g2d.setPaint(TemplateProperties.getFillColor()); g2d.fill(polygon); g2d.setPaint(TemplateProperties.getLineColor()); g2d.draw(polygon); } - } diff --git a/core/src/net/sf/openrocket/gui/print/PrintableNoseCone.java b/core/src/net/sf/openrocket/gui/print/PrintableNoseCone.java index 78afe6f05..614aa35d7 100644 --- a/core/src/net/sf/openrocket/gui/print/PrintableNoseCone.java +++ b/core/src/net/sf/openrocket/gui/print/PrintableNoseCone.java @@ -1,8 +1,12 @@ package net.sf.openrocket.gui.print; +import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; import net.sf.openrocket.gui.rocketfigure.TransitionShapes; import net.sf.openrocket.rocketcomponent.NoseCone; @@ -48,7 +52,7 @@ public class PrintableNoseCone extends AbstractPrintableTransition { if (shapes != null && shapes.length > 0) { Rectangle r = shapes[0].getBounds(); - g2.translate(marginX + r.getHeight() / 2, marginY); + g2.translate(marginX + r.getHeight() / 2 + getOffsetX(), marginY + getOffsetY()); g2.rotate(Math.PI / 2); for (Shape shape : shapes) { g2.draw(shape); diff --git a/core/src/net/sf/openrocket/gui/print/PrintableTransition.java b/core/src/net/sf/openrocket/gui/print/PrintableTransition.java index a70703daf..fbe676922 100644 --- a/core/src/net/sf/openrocket/gui/print/PrintableTransition.java +++ b/core/src/net/sf/openrocket/gui/print/PrintableTransition.java @@ -1,12 +1,16 @@ package net.sf.openrocket.gui.print; import java.awt.BasicStroke; +import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.geom.Arc2D; import java.awt.geom.GeneralPath; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; import net.sf.openrocket.rocketcomponent.Transition; diff --git a/core/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java b/core/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java index 80e15a216..eb58ba462 100644 --- a/core/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java +++ b/core/src/net/sf/openrocket/gui/print/visitor/FinSetPrintStrategy.java @@ -9,6 +9,7 @@ import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfWriter; import net.sf.openrocket.gui.print.ITextHelper; +import net.sf.openrocket.gui.print.PrintUnit; import net.sf.openrocket.gui.print.PrintableFinSet; import net.sf.openrocket.logging.LogHelper; import net.sf.openrocket.rocketcomponent.FinSet; @@ -17,7 +18,9 @@ import net.sf.openrocket.startup.Application; import java.awt.*; import java.awt.image.BufferedImage; +import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; import java.util.Set; /** @@ -45,6 +48,11 @@ public class FinSetPrintStrategy { */ protected Set stages; + /** + * Strategy for fitting multiple components onto a page. + */ + protected PageFitPrintStrategy pageFitPrint; + /** * Constructor. * @@ -52,10 +60,11 @@ public class FinSetPrintStrategy { * @param theWriter The direct iText writer * @param theStages The stages to be printed by this strategy */ - public FinSetPrintStrategy(Document doc, PdfWriter theWriter, Set theStages) { + public FinSetPrintStrategy(Document doc, PdfWriter theWriter, Set theStages, PageFitPrintStrategy pageFit) { document = doc; writer = theWriter; stages = theStages; + pageFitPrint = pageFit; } /** @@ -98,14 +107,14 @@ public class FinSetPrintStrategy { java.awt.Dimension finSize = pfs.getSize(); final Dimension pageSize = getPageSize(); if (fitsOnOnePage(pageSize, finSize.getWidth(), finSize.getHeight())) { - printOnOnePage(pfs); + pageFitPrint.addComponent(pfs); } else { BufferedImage image = (BufferedImage) pfs.createImage(); ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()), document, writer, image); + document.newPage(); } - document.newPage(); } catch (DocumentException e) { log.error("Could not render fin.", e); @@ -153,19 +162,6 @@ public class FinSetPrintStrategy { return wRatio <= 1.0d && hRatio <= 1.0d; } - /** - * Print the fin set. - * - * @param thePfs the printable fin set - */ - private void printOnOnePage (final PrintableFinSet thePfs) { - Dimension d = getPageSize(); - PdfContentByte cb = writer.getDirectContent(); - Graphics2D g2 = cb.createGraphics(d.width, d.height); - thePfs.print(g2); - g2.dispose(); - } - /** * Get the dimensions of the paper page. * diff --git a/core/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java b/core/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java new file mode 100644 index 000000000..20ef3c19f --- /dev/null +++ b/core/src/net/sf/openrocket/gui/print/visitor/PageFitPrintStrategy.java @@ -0,0 +1,171 @@ +/* + * PageFitPrintStrategy.java + */ +package net.sf.openrocket.gui.print.visitor; + +import com.itextpdf.text.Document; +import com.itextpdf.text.pdf.PdfContentByte; +import com.itextpdf.text.pdf.PdfWriter; +import net.sf.openrocket.gui.print.PrintUnit; +import net.sf.openrocket.gui.print.PrintableComponent; +import net.sf.openrocket.logging.LogHelper; +import net.sf.openrocket.rocketcomponent.RocketComponent; +import net.sf.openrocket.startup.Application; + +import java.awt.*; +import java.util.ArrayList; +import java.util.ListIterator; +import java.util.Set; + +/** + * A strategy for drawing multiple rocket components onto as few pages as possible. + * + * @author Jason Blood + */ +public class PageFitPrintStrategy { + + /** + * The logger. + */ + private static final LogHelper log = Application.getLogger(); + + /** + * The iText document. + */ + protected Document document; + + /** + * The direct iText writer. + */ + protected PdfWriter writer; + + /** + * The stages selected. + */ + protected Set stages; + + protected ArrayList componentToPrint; + + /** + * Constructor. + * + * @param doc The iText document + * @param theWriter The direct iText writer + * @param theStages The stages to be printed by this strategy + */ + public PageFitPrintStrategy(Document doc, PdfWriter theWriter) { + document = doc; + writer = theWriter; + componentToPrint = new ArrayList(); + } + + /** + * Add a component we want to print. + * + * @param component The component to add for printing + */ + public void addComponent(PrintableComponent component) { + componentToPrint.add(component); + } + + /** + * Recurse through the given rocket component. + * + * @param root the root component; all children will be printed recursively + */ + public void writeToDocument (final RocketComponent root) { + fitPrintComponents(); + } + + /** + * Iterate through the components to print fitting them onto pages as best possible. + */ + private void fitPrintComponents() { + final Dimension pageSize = getPageSize(); + double wPage = pageSize.getWidth(); + double hPage = pageSize.getHeight(); + int marginX = (int)(PrintUnit.POINTS_PER_INCH * 0.3f); + int marginY = (int)(PrintUnit.POINTS_PER_INCH * 0.3f); + PdfContentByte cb = writer.getDirectContent(); + + while (componentToPrint.size() > 0) { + int pageY = 0; + Boolean anyAddedToRow; + + Graphics2D g2 = cb.createGraphics(pageSize.width, pageSize.height); + + do { + // Fill the row + int rowX = 0; + int rowY = pageY; + ListIterator entry = componentToPrint.listIterator(); + anyAddedToRow = false; + + while (entry.hasNext()) { + PrintableComponent component = entry.next(); + java.awt.Dimension dim = component.getSize(); + if ((rowX + dim.width < wPage) && (rowY + dim.height < hPage)) { + component.setPrintOffset(rowX, rowY); + rowX += dim.width + marginX; + if (rowY + dim.height + marginY > pageY) + pageY = rowY + dim.height + marginY; + entry.remove(); + component.print(g2); + anyAddedToRow = true; + } + } + } while (anyAddedToRow); + + g2.dispose(); + document.newPage(); + } + } + + /** + * Get the dimensions of the paper page. + * + * @return an internal Dimension + */ + protected Dimension getPageSize () { + return new Dimension(document.getPageSize().getWidth(), + document.getPageSize().getHeight()); + } + + /** + * Convenience class to model a dimension. + */ + class Dimension { + /** Width, in points. */ + public float width; + /** Height, in points. */ + public float height; + + /** + * Constructor. + * @param w width + * @param h height + */ + public Dimension (float w, float h) { + width = w; + height = h; + } + + /** + * Get the width. + * + * @return the width + */ + public float getWidth () { + return width; + } + + /** + * Get the height. + * + * @return the height + */ + public float getHeight () { + return height; + } + } +} diff --git a/core/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java b/core/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java index 1988ab8a3..bd4504558 100644 --- a/core/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java +++ b/core/src/net/sf/openrocket/gui/print/visitor/TransitionStrategy.java @@ -45,6 +45,11 @@ public class TransitionStrategy { */ protected Set stages; + /** + * Strategy for fitting multiple components onto a page. + */ + protected PageFitPrintStrategy pageFitPrint; + /** * Constructor. * @@ -52,10 +57,11 @@ public class TransitionStrategy { * @param theWriter The direct iText writer * @param theStagesToVisit The stages to be visited by this strategy */ - public TransitionStrategy(Document doc, PdfWriter theWriter, Set theStagesToVisit) { + public TransitionStrategy(Document doc, PdfWriter theWriter, Set theStagesToVisit, PageFitPrintStrategy pageFit) { document = doc; writer = theWriter; stages = theStagesToVisit; + pageFitPrint = pageFit; } /** @@ -107,7 +113,8 @@ public class TransitionStrategy { java.awt.Dimension size = pfs.getSize(); final Dimension pageSize = getPageSize(); if (fitsOnOnePage(pageSize, size.getWidth(), size.getHeight())) { - printOnOnePage(pfs); + pageFitPrint.addComponent(pfs); + //printOnOnePage(pfs); } else { BufferedImage image = (BufferedImage) pfs.createImage(); ITextHelper.renderImageAcrossPages(new Rectangle(pageSize.getWidth(), pageSize.getHeight()), @@ -141,14 +148,14 @@ public class TransitionStrategy { * * @param theTransition the printable transition */ - private void printOnOnePage(final AbstractPrintableTransition theTransition) { + /*private void printOnOnePage(final AbstractPrintableTransition theTransition) { Dimension d = getPageSize(); PdfContentByte cb = writer.getDirectContent(); Graphics2D g2 = cb.createGraphics(d.width, d.height); theTransition.print(g2); g2.dispose(); document.newPage(); - } + }*/ /** * Get the dimensions of the paper page.