Bug 491864 - Add support for printing multiple NatTable instances in one
job

Change-Id: I613e8644340ad9398890991ca654b5cc541a1f79
Signed-off-by: Dirk Fauth <dirk.fauth@googlemail.com>
diff --git a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/print/LayerPrinter.java b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/print/LayerPrinter.java
index 0e285ad..8b0c548 100644
--- a/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/print/LayerPrinter.java
+++ b/org.eclipse.nebula.widgets.nattable.core/src/org/eclipse/nebula/widgets/nattable/print/LayerPrinter.java
@@ -11,7 +11,9 @@
 package org.eclipse.nebula.widgets.nattable.print;
 
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 
 import org.eclipse.nebula.widgets.nattable.Messages;
 import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;
@@ -51,9 +53,21 @@
  */
 public class LayerPrinter {
 
-    private final IConfigRegistry configRegistry;
-    private final ILayer layer;
-    private final IClientAreaProvider originalClientAreaProvider;
+    private class PrintTarget {
+
+        private final IConfigRegistry configRegistry;
+        private final ILayer layer;
+        private final IClientAreaProvider originalClientAreaProvider;
+
+        PrintTarget(ILayer layer, IConfigRegistry configRegistry) {
+            this.layer = layer;
+            this.configRegistry = configRegistry;
+            this.originalClientAreaProvider = layer.getClientAreaProvider();
+        }
+    }
+
+    private final List<PrintTarget> printTargets = new ArrayList<PrintTarget>();
+
     public static final int FOOTER_HEIGHT_IN_PRINTER_DPI = 300;
 
     final SimpleDateFormat dateFormat;
@@ -79,9 +93,7 @@
      *            The ConfigRegistry needed for rendering to the print GC.
      */
     public LayerPrinter(ILayer layer, IConfigRegistry configRegistry) {
-        this.layer = layer;
-        this.configRegistry = configRegistry;
-        this.originalClientAreaProvider = layer.getClientAreaProvider();
+        this.printTargets.add(new PrintTarget(layer, configRegistry));
 
         // configure the footer height
         Integer fh = configRegistry.getConfigAttribute(
@@ -109,8 +121,27 @@
     }
 
     /**
+     * Adds the given {@link ILayer} as print target. It can be used to register
+     * multiple NatTable or layer stacks in one print job.
+     *
+     * @param layer
+     *            The {@link ILayer} that should be printed together with the
+     *            main {@link ILayer} registered via constructor.
+     * @param configRegistry
+     *            The {@link IConfigRegistry} that should be used to print the
+     *            given {@link ILayer}.
+     *
+     * @since 1.5
+     */
+    public void addPrintTarget(ILayer layer, IConfigRegistry configRegistry) {
+        this.printTargets.add(new PrintTarget(layer, configRegistry));
+    }
+
+    /**
      * Computes the scale factor to match the printer resolution.
      *
+     * @param layer
+     *            The layer for which the scale factor should be calculated.
      * @param printer
      *            The printer that will be used.
      * @param dpi
@@ -121,7 +152,7 @@
      * @return The amount to scale the screen resolution by, to match the
      *         printer the resolution.
      */
-    private float[] computeScaleFactor(Printer printer, boolean dpi) {
+    private float[] computeScaleFactor(ILayer layer, Printer printer, boolean dpi) {
         Point screenDPI = Display.getDefault().getDPI();
         Point printerDPI = printer.getDPI();
 
@@ -129,7 +160,7 @@
         float sfY = Float.valueOf(printerDPI.y) / Float.valueOf(screenDPI.y);
 
         if (!dpi && (this.fittingMode != Direction.NONE)) {
-            Rectangle total = getTotalArea();
+            Rectangle total = getTotalArea(layer);
             Rectangle print = computePrintArea(printer);
 
             float pixelX = Float.valueOf(print.width) / Float.valueOf(total.width);
@@ -149,36 +180,57 @@
     }
 
     /**
+     * @param layer
+     *            The layer for which the total area is requested.
      * @return The size of the layer to fit all the contents.
      */
-    private Rectangle getTotalArea() {
-        return new Rectangle(0, 0, this.layer.getWidth(), this.layer.getHeight());
+    private Rectangle getTotalArea(ILayer layer) {
+        return new Rectangle(0, 0, layer.getWidth(), layer.getHeight());
+    }
+
+    /**
+     * Calculates number of horizontal and vertical pages needed to print all
+     * registered layers.
+     *
+     * @param printer
+     *            The printer that will be used.
+     * @return The number of pages that are needed to print.
+     */
+    private int getPageCount(Printer printer) {
+        int result = 0;
+        for (PrintTarget target : this.printTargets) {
+            Point layerResult = getPageCount(target, printer);
+            result += (layerResult.x * layerResult.y);
+        }
+        return result;
     }
 
     /**
      * Calculates number of horizontal and vertical pages needed to print the
-     * entire layer.
+     * entire layer of the given print target.
      *
+     * @param target
+     *            The print target to print.
      * @param printer
      *            The printer that will be used.
      * @return The number of horizontal and vertical pages that are needed to
-     *         print the layer.
+     *         print the layer of the given print target.
      */
-    private Point getPageCount(Printer printer) {
+    private Point getPageCount(PrintTarget target, Printer printer) {
         Rectangle printArea = computePrintArea(printer);
-        float[] scaleFactor = computeScaleFactor(printer, false);
+        float[] scaleFactor = computeScaleFactor(target.layer, printer, false);
 
-        Integer[] gridLineWidth = getGridLineWidth();
+        Integer[] gridLineWidth = getGridLineWidth(target.configRegistry);
 
         // calculate pages based on non cut off columns/rows
         int numOfHorizontalPages = 0;
-        int pageWidth = Float.valueOf(printArea.width / scaleFactor[0]).intValue();
+        int pageWidth = Math.round(Float.valueOf(printArea.width / scaleFactor[0]));
         int endX = 0;
-        while (endX < this.layer.getWidth()) {
+        while (endX < target.layer.getWidth()) {
             endX += pageWidth;
-            int colPos = this.layer.getColumnPositionByX(endX);
+            int colPos = target.layer.getColumnPositionByX(endX);
             if (colPos >= 0) {
-                ILayerCell cell = findColumnCellForBounds(colPos);
+                ILayerCell cell = findColumnCellForBounds(target.layer, colPos);
                 if (cell != null) {
                     Rectangle cellBounds = cell.getBounds();
                     if (cellBounds.x < endX) {
@@ -187,20 +239,20 @@
                     }
                 }
             } else {
-                endX = this.layer.getWidth();
+                endX = target.layer.getWidth();
             }
 
             numOfHorizontalPages++;
         }
 
         int numOfVerticalPages = 0;
-        int pageHeight = Float.valueOf((printArea.height - getFooterHeightInPrinterDPI()) / scaleFactor[1]).intValue();
+        int pageHeight = Math.round(Float.valueOf((printArea.height - getFooterHeightInPrinterDPI()) / scaleFactor[1]));
         int endY = 0;
-        while (endY < this.layer.getHeight()) {
+        while (endY < target.layer.getHeight()) {
             endY += pageHeight;
-            int rowPos = this.layer.getRowPositionByY(endY);
+            int rowPos = target.layer.getRowPositionByY(endY);
             if (rowPos >= 0) {
-                ILayerCell cell = findRowCellForBounds(rowPos);
+                ILayerCell cell = findRowCellForBounds(target.layer, rowPos);
                 if (cell != null) {
                     Rectangle cellBounds = cell.getBounds();
                     if (cellBounds.y < endY) {
@@ -209,37 +261,37 @@
                     }
                 }
             } else {
-                endY = this.layer.getHeight();
+                endY = target.layer.getHeight();
             }
 
             numOfVerticalPages++;
         }
 
         if (gridLineWidth[0] == null) {
-            LayerPrinter.this.configRegistry.unregisterConfigAttribute(CellConfigAttributes.GRID_LINE_WIDTH);
+            target.configRegistry.unregisterConfigAttribute(CellConfigAttributes.GRID_LINE_WIDTH);
         }
 
         return new Point(numOfHorizontalPages, numOfVerticalPages);
     }
 
-    private ILayerCell findColumnCellForBounds(int colPos) {
+    private ILayerCell findColumnCellForBounds(ILayer layer, int colPos) {
         int rowPos = 0;
-        ILayerCell cell = this.layer.getCellByPosition(colPos, rowPos);
+        ILayerCell cell = layer.getCellByPosition(colPos, rowPos);
         while (cell != null && cell.isSpannedCell()) {
             // if the cell is spanned, check the cell at the next row
             rowPos++;
-            cell = this.layer.getCellByPosition(colPos, rowPos);
+            cell = layer.getCellByPosition(colPos, rowPos);
         }
         return cell;
     }
 
-    private ILayerCell findRowCellForBounds(int rowPos) {
+    private ILayerCell findRowCellForBounds(ILayer layer, int rowPos) {
         int colPos = 0;
-        ILayerCell cell = this.layer.getCellByPosition(colPos, rowPos);
+        ILayerCell cell = layer.getCellByPosition(colPos, rowPos);
         while (cell != null && cell.isSpannedCell()) {
             // if the cell is spanned, check the cell at the next column
             colPos++;
-            cell = this.layer.getCellByPosition(colPos, rowPos);
+            cell = layer.getCellByPosition(colPos, rowPos);
         }
         return cell;
     }
@@ -254,20 +306,23 @@
     }
 
     /**
+     * @param configRegistry
+     *            The {@link IConfigRegistry} to retrieve the grid line width
+     *            from.
      * @return Integer array that contains the original configured width at
      *         index 0 and the grid line width to use at index 1.
      * @since 1.5
      */
-    protected Integer[] getGridLineWidth() {
+    protected Integer[] getGridLineWidth(IConfigRegistry configRegistry) {
         // check if a grid line width is configured
-        Integer width = LayerPrinter.this.configRegistry.getConfigAttribute(
+        Integer width = configRegistry.getConfigAttribute(
                 CellConfigAttributes.GRID_LINE_WIDTH,
                 DisplayMode.NORMAL);
         Integer gridLineWidth = width;
         // if no explicit width is set, we temporary specify a grid line
         // width of 2 for optimized grid line printing
         if (width == null) {
-            LayerPrinter.this.configRegistry.registerConfigAttribute(
+            configRegistry.registerConfigAttribute(
                     CellConfigAttributes.GRID_LINE_WIDTH, 2);
             gridLineWidth = 2;
         }
@@ -284,7 +339,9 @@
     public void print(final Shell shell) {
         // turn viewport off to ensure calculation of the print pages for the
         // whole table
-        this.layer.doCommand(new TurnViewportOffCommand());
+        for (PrintTarget target : this.printTargets) {
+            target.layer.doCommand(new TurnViewportOffCommand());
+        }
 
         Printer printer = null;
         try {
@@ -294,7 +351,9 @@
             }
         } finally {
             // turn viewport on
-            this.layer.doCommand(new TurnViewportOnCommand());
+            for (PrintTarget target : this.printTargets) {
+                target.layer.doCommand(new TurnViewportOnCommand());
+            }
         }
 
         // Note: As we are operating on the same layer instance that is shown in
@@ -338,12 +397,12 @@
      */
     private Printer setupPrinter(final Shell shell) {
         Printer defaultPrinter = new Printer();
-        Point pageCount = getPageCount(defaultPrinter);
+        int pageCount = getPageCount(defaultPrinter);
         defaultPrinter.dispose();
 
         final PrintDialog printDialog = new PrintDialog(shell);
         printDialog.setStartPage(1);
-        printDialog.setEndPage(pageCount.x * pageCount.y);
+        printDialog.setEndPage(pageCount);
         printDialog.setScope(PrinterData.ALL_PAGES);
 
         PrinterData printerData = printDialog.open();
@@ -432,139 +491,153 @@
 
         @Override
         public void run() {
-            LayerPrinter.this.layer.doCommand(new TurnViewportOffCommand());
-            float[] scaleFactor = null;
-            float[] dpiFactor = null;
-            try {
-                scaleFactor = computeScaleFactor(this.printer, false);
-                dpiFactor = computeScaleFactor(this.printer, true);
-            } finally {
-                LayerPrinter.this.layer.doCommand(new TurnViewportOnCommand());
-            }
-
-            Integer[] gridLineWidth = getGridLineWidth();
-
-            // if pre-rendering is enabled, render in-memory to trigger content
-            // based auto-resizing
-            if (LayerPrinter.this.preRender) {
-                Transform tempTransform = new Transform(this.printer);
-                tempTransform.scale(scaleFactor[0], scaleFactor[1]);
-                AutoResizeHelper.autoResize(LayerPrinter.this.layer, LayerPrinter.this.configRegistry);
-                tempTransform.dispose();
-            }
-
             if (this.printer.startJob("NatTable")) { //$NON-NLS-1$
+                GC gc = new GC(this.printer);
                 try {
-                    // if a SummaryRowLayer is in the layer stack, we need to
-                    // ensure that the values are calculated
-                    LayerPrinter.this.layer.doCommand(new CalculateSummaryRowValuesCommand());
-
-                    // ensure that the viewport is turned off
-                    LayerPrinter.this.layer.doCommand(new TurnViewportOffCommand());
-
-                    // ensure that formula processing is performed in the
-                    // current thread
-                    LayerPrinter.this.layer.doCommand(new DisableFormulaCachingCommand());
-
-                    // set the size of the layer according to the print
-                    // settings made by the user
-                    setLayerSize(this.printer.getPrinterData());
-
-                    final Rectangle printerClientArea = computePrintArea(this.printer);
-                    final int printBoundsWidth = Float.valueOf(printerClientArea.width / scaleFactor[0]).intValue();
-                    int printBoundsHeight = Float.valueOf((printerClientArea.height - getFooterHeightInPrinterDPI()) / scaleFactor[1]).intValue();
-
-                    final Point pageCount = getPageCount(this.printer);
-                    GC gc = new GC(this.printer);
-
-                    // Print pages Left to Right and then Top to Down
                     int currentPage = 1;
 
-                    int startY = 0;
-                    for (int verticalPageNumber = 0; verticalPageNumber < pageCount.y; verticalPageNumber++) {
-
-                        int endY = startY + printBoundsHeight;
-                        int rowPos = LayerPrinter.this.layer.getRowPositionByY(endY);
-                        if (rowPos >= 0) {
-                            ILayerCell cell = findRowCellForBounds(rowPos);
-                            if (cell != null) {
-                                Rectangle cellBounds = cell.getBounds();
-                                if (cellBounds.y < endY) {
-                                    printBoundsHeight -= (endY - cellBounds.y);
-                                }
-                            }
+                    for (PrintTarget target : LayerPrinter.this.printTargets) {
+                        target.layer.doCommand(new TurnViewportOffCommand());
+                        float[] scaleFactor = null;
+                        float[] dpiFactor = null;
+                        try {
+                            scaleFactor = computeScaleFactor(target.layer, this.printer, false);
+                            dpiFactor = computeScaleFactor(target.layer, this.printer, true);
+                        } finally {
+                            target.layer.doCommand(new TurnViewportOnCommand());
                         }
 
-                        int startX = 0;
-                        for (int horizontalPageNumber = 0; horizontalPageNumber < pageCount.x; horizontalPageNumber++) {
+                        Integer[] gridLineWidth = getGridLineWidth(target.configRegistry);
 
-                            // Calculate bounds for the next page
-                            Rectangle printBounds = new Rectangle(
-                                    startX,
-                                    startY,
-                                    printBoundsWidth,
-                                    printBoundsHeight);
+                        // if pre-rendering is enabled, render in-memory to
+                        // trigger content based auto-resizing
+                        if (LayerPrinter.this.preRender) {
+                            Transform tempTransform = new Transform(this.printer);
+                            tempTransform.scale(scaleFactor[0], scaleFactor[1]);
+                            AutoResizeHelper.autoResize(target.layer, target.configRegistry);
+                            tempTransform.dispose();
+                        }
 
-                            int endX = startX + printBounds.width;
-                            int colPos = LayerPrinter.this.layer.getColumnPositionByX(endX);
-                            if (colPos >= 0) {
-                                ILayerCell cell = findColumnCellForBounds(colPos);
-                                if (cell != null) {
-                                    Rectangle cellBounds = cell.getBounds();
-                                    if (cellBounds.x < endX) {
-                                        printBounds.width -= (endX - cellBounds.x);
+                        try {
+                            // if a SummaryRowLayer is in the layer stack, we
+                            // need to ensure that the values are calculated
+                            target.layer.doCommand(new CalculateSummaryRowValuesCommand());
+
+                            // ensure that the viewport is turned off
+                            target.layer.doCommand(new TurnViewportOffCommand());
+
+                            // ensure that formula processing is performed in
+                            // the current thread
+                            target.layer.doCommand(new DisableFormulaCachingCommand());
+
+                            // set the size of the layer according to the print
+                            // settings made by the user
+                            setLayerSize(target, this.printer.getPrinterData());
+
+                            final Rectangle printerClientArea = computePrintArea(this.printer);
+                            final int printBoundsWidth = Math.round(Float.valueOf(printerClientArea.width / scaleFactor[0]));
+                            int printBoundsHeight = Math.round(Float.valueOf((printerClientArea.height - getFooterHeightInPrinterDPI()) / scaleFactor[1]));
+
+                            final Point pageCount = getPageCount(target, this.printer);
+
+                            // Print pages Left to Right and then Top to Down
+                            int startY = 0;
+                            for (int verticalPageNumber = 0; verticalPageNumber < pageCount.y; verticalPageNumber++) {
+
+                                int endY = startY + printBoundsHeight;
+                                // int rowPos =
+                                // target.layer.getRowPositionByY(endY);
+                                int rowPos = target.layer.getRowPositionByY(endY);
+                                if (rowPos >= 0) {
+                                    ILayerCell cell = findRowCellForBounds(target.layer, rowPos);
+                                    if (cell != null) {
+                                        Rectangle cellBounds = cell.getBounds();
+                                        if (cellBounds.y < endY) {
+                                            printBoundsHeight -= (endY - cellBounds.y);
+                                        }
                                     }
                                 }
+
+                                int startX = 0;
+                                for (int horizontalPageNumber = 0; horizontalPageNumber < pageCount.x; horizontalPageNumber++) {
+
+                                    // Calculate bounds for the next page
+                                    Rectangle printBounds = new Rectangle(
+                                            startX,
+                                            startY,
+                                            printBoundsWidth,
+                                            printBoundsHeight);
+
+                                    int endX = startX + printBounds.width;
+                                    int colPos = target.layer.getColumnPositionByX(endX);
+                                    if (colPos >= 0) {
+                                        ILayerCell cell = findColumnCellForBounds(target.layer, colPos);
+                                        if (cell != null) {
+                                            Rectangle cellBounds = cell.getBounds();
+                                            if (cellBounds.x < endX) {
+                                                printBounds.width -= (endX - cellBounds.x);
+                                            }
+                                        }
+                                    }
+
+                                    Rectangle footerBounds = new Rectangle(
+                                            Math.round(Float.valueOf((printerClientArea.width / dpiFactor[0]) * horizontalPageNumber)),
+                                            Math.round(Float.valueOf(((printerClientArea.height - getFooterHeightInPrinterDPI()) / dpiFactor[1]) * verticalPageNumber)),
+                                            Math.round(Float.valueOf(printerClientArea.width / dpiFactor[0])),
+                                            Math.round(Float.valueOf((printerClientArea.height - getFooterHeightInPrinterDPI()) / dpiFactor[1])));
+
+                                    if (shouldPrint(this.printer.getPrinterData(), currentPage)) {
+                                        this.printer.startPage();
+
+                                        Transform printerTransform = new Transform(this.printer);
+                                        Transform footerTransform = new Transform(this.printer);
+
+                                        // FIXME
+                                        Rectangle intersect = new Rectangle(
+                                                0,
+                                                0,
+                                                target.layer.getWidth(),
+                                                target.layer.getHeight());
+
+                                        intersect = printBounds.intersection(intersect);
+
+                                        configureScalingTransform(printerTransform, scaleFactor, printerClientArea, intersect);
+                                        configureScalingTransform(footerTransform, dpiFactor, printerClientArea, footerBounds);
+
+                                        gc.setTransform(printerTransform);
+                                        printLayer(target, gc, intersect);
+
+                                        gc.setTransform(footerTransform);
+                                        printFooter(gc, currentPage, footerBounds, target.configRegistry);
+
+                                        this.printer.endPage();
+                                        printerTransform.dispose();
+                                        footerTransform.dispose();
+                                    }
+                                    currentPage++;
+
+                                    startX += printBounds.width;
+                                }
+                                startY += printBoundsHeight;
                             }
 
-                            Rectangle footerBounds = new Rectangle(
-                                    Float.valueOf((printerClientArea.width / dpiFactor[0]) * horizontalPageNumber).intValue(),
-                                    Float.valueOf(((printerClientArea.height - getFooterHeightInPrinterDPI()) / dpiFactor[1]) * verticalPageNumber).intValue(),
-                                    Float.valueOf(printerClientArea.width / dpiFactor[0]).intValue(),
-                                    Float.valueOf((printerClientArea.height - getFooterHeightInPrinterDPI()) / dpiFactor[1]).intValue());
-
-                            if (shouldPrint(this.printer.getPrinterData(), currentPage)) {
-                                this.printer.startPage();
-
-                                Transform printerTransform = new Transform(this.printer);
-                                Transform footerTransform = new Transform(this.printer);
-
-                                configureScalingTransform(printerTransform, scaleFactor, printerClientArea, printBounds);
-                                configureScalingTransform(footerTransform, dpiFactor, printerClientArea, footerBounds);
-
-                                gc.setTransform(printerTransform);
-                                printLayer(gc, printBounds);
-
-                                gc.setTransform(footerTransform);
-                                printFooter(gc, currentPage, footerBounds);
-
-                                this.printer.endPage();
-                                printerTransform.dispose();
-                                footerTransform.dispose();
-                            }
-                            currentPage++;
-
-                            startX += printBounds.width - gridLineWidth[1];
+                        } finally {
+                            restoreLayerState(target);
                         }
-                        startY += printBoundsHeight - gridLineWidth[1];
+
+                        // there was no explicit width configured, so we
+                        // configured a temporary one for grid line printing.
+                        // this configuration needs to be removed again
+                        if (gridLineWidth[0] == null) {
+                            target.configRegistry.unregisterConfigAttribute(CellConfigAttributes.GRID_LINE_WIDTH);
+                        }
                     }
-
+                } finally {
                     this.printer.endJob();
-
                     gc.dispose();
                     this.printer.dispose();
-                } finally {
-                    restoreLayerState();
                 }
             }
-
-            // there was no explicit width configured, so we configured a
-            // temporary one for grid line printing. this configuration needs to
-            // be removed again
-            if (gridLineWidth[0] == null) {
-                LayerPrinter.this.configRegistry.unregisterConfigAttribute(CellConfigAttributes.GRID_LINE_WIDTH);
-            }
-
         }
 
         /**
@@ -606,17 +679,19 @@
          * size needs to be extended so that all the contents fit in the
          * viewport to ensure that we print the <i>entire</i> table.
          *
+         * @param target
+         *            The print target to print.
          * @param printerData
          *            The PrinterData that was configured by the user on the
          *            PrintDialog.
          */
-        private void setLayerSize(PrinterData printerData) {
+        private void setLayerSize(PrintTarget target, PrinterData printerData) {
             if (printerData != null && printerData.scope == PrinterData.SELECTION) {
-                LayerPrinter.this.layer.setClientAreaProvider(LayerPrinter.this.originalClientAreaProvider);
+                target.layer.setClientAreaProvider(target.originalClientAreaProvider);
             } else {
-                final Rectangle fullLayerSize = getTotalArea();
+                final Rectangle fullLayerSize = getTotalArea(target.layer);
 
-                LayerPrinter.this.layer.setClientAreaProvider(new IClientAreaProvider() {
+                target.layer.setClientAreaProvider(new IClientAreaProvider() {
                     @Override
                     public Rectangle getClientArea() {
                         return fullLayerSize;
@@ -626,21 +701,23 @@
                 // in case the whole layer should be printed or only the
                 // selected pages, we need to ensure to set the starting point
                 // to 0/0
-                LayerPrinter.this.layer.doCommand(new PrintEntireGridCommand());
+                target.layer.doCommand(new PrintEntireGridCommand());
             }
         }
 
         /**
          * Print the part of the layer that matches the given print bounds.
          *
+         * @param target
+         *            The print target to print
          * @param gc
          *            The print GC to render the layer to.
          * @param printBounds
          *            The bounds of the print page.
          */
-        private void printLayer(GC gc, Rectangle printBounds) {
-            LayerPrinter.this.layer.getLayerPainter().paintLayer(
-                    LayerPrinter.this.layer, gc, 0, 0, printBounds, LayerPrinter.this.configRegistry);
+        private void printLayer(PrintTarget target, GC gc, Rectangle printBounds) {
+            target.layer.getLayerPainter().paintLayer(
+                    target.layer, gc, 0, 0, printBounds, target.configRegistry);
         }
 
         /**
@@ -652,8 +729,11 @@
          *            The total number of pages that are printed.
          * @param printBounds
          *            The bounds of the print page.
+         * @param configRegistry
+         *            The {@link IConfigRegistry} needed to retrieve the footer
+         *            style.
          */
-        private void printFooter(GC gc, int totalPageCount, Rectangle printBounds) {
+        private void printFooter(GC gc, int totalPageCount, Rectangle printBounds, IConfigRegistry configRegistry) {
             Color oldForeground = gc.getForeground();
             Color oldBackground = gc.getBackground();
             Font oldFont = gc.getFont();
@@ -662,7 +742,7 @@
             Color footerBackground = null;
             Font footerFont = null;
 
-            IStyle style = LayerPrinter.this.configRegistry.getConfigAttribute(
+            IStyle style = configRegistry.getConfigAttribute(
                     PrintConfigAttributes.FOOTER_STYLE,
                     DisplayMode.NORMAL);
             if (style != null) {
@@ -699,11 +779,14 @@
          * Restores the layer state to match the display characteristics again.
          * This is done by resetting the client area provider, turning the
          * viewport on and enabling formula result caching again.
+         *
+         * @param target
+         *            The print target whose state should be restarted.
          */
-        private void restoreLayerState() {
-            LayerPrinter.this.layer.setClientAreaProvider(LayerPrinter.this.originalClientAreaProvider);
-            LayerPrinter.this.layer.doCommand(new TurnViewportOnCommand());
-            LayerPrinter.this.layer.doCommand(new EnableFormulaCachingCommand());
+        private void restoreLayerState(PrintTarget target) {
+            target.layer.setClientAreaProvider(target.originalClientAreaProvider);
+            target.layer.doCommand(new TurnViewportOnCommand());
+            target.layer.doCommand(new EnableFormulaCachingCommand());
         }
 
     }
diff --git a/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_700_AdditionalFunctions/_762_MultiPrintExample.java b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_700_AdditionalFunctions/_762_MultiPrintExample.java
new file mode 100644
index 0000000..07f869a
--- /dev/null
+++ b/org.eclipse.nebula.widgets.nattable.examples/src/org/eclipse/nebula/widgets/nattable/examples/_700_AdditionalFunctions/_762_MultiPrintExample.java
@@ -0,0 +1,223 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Dirk Fauth and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Dirk Fauth <dirk.fauth@googlemail.com> - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.nebula.widgets.nattable.examples._700_AdditionalFunctions;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.nebula.widgets.nattable.NatTable;
+import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
+import org.eclipse.nebula.widgets.nattable.data.ExtendedReflectiveColumnPropertyAccessor;
+import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
+import org.eclipse.nebula.widgets.nattable.data.IDataProvider;
+import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
+import org.eclipse.nebula.widgets.nattable.dataset.person.Person;
+import org.eclipse.nebula.widgets.nattable.dataset.person.PersonService;
+import org.eclipse.nebula.widgets.nattable.dataset.person.PersonWithAddress;
+import org.eclipse.nebula.widgets.nattable.examples.AbstractNatExample;
+import org.eclipse.nebula.widgets.nattable.examples.runner.StandaloneNatExampleRunner;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultBodyDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
+import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
+import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
+import org.eclipse.nebula.widgets.nattable.hideshow.ColumnHideShowLayer;
+import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
+import org.eclipse.nebula.widgets.nattable.layer.ILayer;
+import org.eclipse.nebula.widgets.nattable.print.LayerPrinter;
+import org.eclipse.nebula.widgets.nattable.print.command.PrintCommand;
+import org.eclipse.nebula.widgets.nattable.print.command.PrintCommandHandler;
+import org.eclipse.nebula.widgets.nattable.print.config.DefaultPrintBindings;
+import org.eclipse.nebula.widgets.nattable.reorder.ColumnReorderLayer;
+import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;
+import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+public class _762_MultiPrintExample extends AbstractNatExample {
+
+    public static void main(String[] args) throws Exception {
+        StandaloneNatExampleRunner.run(new _762_MultiPrintExample());
+    }
+
+    @Override
+    public String getDescription() {
+        return "This example shows how to trigger printing of multiple NatTable instances in one print job";
+    }
+
+    @Override
+    public Control createExampleControl(Composite parent) {
+        Composite panel = new Composite(parent, SWT.NONE);
+        GridLayout layout = new GridLayout();
+        layout.marginHeight = 0;
+        layout.marginWidth = 0;
+        panel.setLayout(layout);
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(panel);
+
+        Composite gridPanel = new Composite(panel, SWT.NONE);
+        gridPanel.setLayout(layout);
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(gridPanel);
+
+        Composite buttonPanel = new Composite(panel, SWT.NONE);
+        buttonPanel.setLayout(new GridLayout());
+        GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonPanel);
+
+        NatTable natTable = createSmallTable(gridPanel);
+        NatTable grid = createGrid(gridPanel);
+
+        // create a custom command handler for printing of multiple NatTable
+        // instances
+        PrintCommandHandler handler = new PrintCommandHandler(natTable.getLayer()) {
+            @Override
+            public boolean doCommand(PrintCommand command) {
+                LayerPrinter printer = new LayerPrinter(natTable.getLayer(), natTable.getConfigRegistry());
+                printer.addPrintTarget(grid.getLayer(), grid.getConfigRegistry());
+                printer.print(natTable.getShell());
+                return true;
+            };
+        };
+
+        // register the handler to both NatTable instances
+        natTable.getLayer().registerCommandHandler(handler);
+        grid.getLayer().registerCommandHandler(handler);
+
+        Button addColumnButton = new Button(buttonPanel, SWT.PUSH);
+        addColumnButton.setText("Print");
+        addColumnButton.addSelectionListener(new SelectionAdapter() {
+            @Override
+            public void widgetSelected(SelectionEvent e) {
+                natTable.doCommand(
+                        new PrintCommand(
+                                natTable.getConfigRegistry(),
+                                natTable.getShell()));
+            }
+        });
+
+        return panel;
+    }
+
+    private NatTable createSmallTable(Composite parent) {
+        // property names of the Person class
+        String[] propertyNames = { "firstName", "lastName", "gender",
+                "married", "birthday" };
+
+        // mapping from property to label, needed for column header labels
+        Map<String, String> propertyToLabelMap = new HashMap<String, String>();
+        propertyToLabelMap.put("firstName", "Firstname");
+        propertyToLabelMap.put("lastName", "Lastname");
+        propertyToLabelMap.put("gender", "Gender");
+        propertyToLabelMap.put("married", "Married");
+        propertyToLabelMap.put("birthday", "Birthday");
+
+        IDataProvider bodyDataProvider =
+                new DefaultBodyDataProvider<Person>(
+                        PersonService.getPersons(150), propertyNames);
+        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
+        SelectionLayer selectionLayer = new SelectionLayer(bodyDataLayer);
+        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
+
+        final NatTable natTable = new NatTable(parent, viewportLayer, false);
+
+        // adding this configuration adds the styles and the painters to use
+        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
+        natTable.addConfiguration(new DefaultPrintBindings());
+
+        natTable.configure();
+
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
+
+        return natTable;
+    }
+
+    private NatTable createGrid(Composite parent) {
+        // property names of the Person class
+        String[] propertyNames = { "firstName", "lastName", "gender",
+                "married", "birthday", "address.street", "address.housenumber",
+                "address.postalCode", "address.city" };
+
+        // mapping from property to label, needed for column header labels
+        Map<String, String> propertyToLabelMap = new HashMap<String, String>();
+        propertyToLabelMap.put("firstName", "Firstname");
+        propertyToLabelMap.put("lastName", "Lastname");
+        propertyToLabelMap.put("gender", "Gender");
+        propertyToLabelMap.put("married", "Married");
+        propertyToLabelMap.put("birthday", "Birthday");
+        propertyToLabelMap.put("address.street", "Street");
+        propertyToLabelMap.put("address.housenumber", "Housenumber");
+        propertyToLabelMap.put("address.postalCode", "Postal Code");
+        propertyToLabelMap.put("address.city", "City");
+
+        // build the body layer stack
+        // Usually you would create a new layer stack by extending
+        // AbstractIndexLayerTransform and
+        // setting the ViewportLayer as underlying layer. But in this case using
+        // the ViewportLayer
+        // directly as body layer is also working.
+        List<PersonWithAddress> data = PersonService.getPersonsWithAddress(100);
+
+        IColumnPropertyAccessor<PersonWithAddress> accessor =
+                new ExtendedReflectiveColumnPropertyAccessor<PersonWithAddress>(propertyNames);
+        IDataProvider bodyDataProvider =
+                new ListDataProvider<PersonWithAddress>(data, accessor);
+        DataLayer bodyDataLayer = new DataLayer(bodyDataProvider);
+        ColumnReorderLayer columnReorderLayer = new ColumnReorderLayer(bodyDataLayer);
+        ColumnHideShowLayer columnHideShowLayer = new ColumnHideShowLayer(columnReorderLayer);
+        SelectionLayer selectionLayer = new SelectionLayer(columnHideShowLayer);
+        ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);
+
+        // build the column header layer
+        IDataProvider columnHeaderDataProvider =
+                new DefaultColumnHeaderDataProvider(propertyNames, propertyToLabelMap);
+        DataLayer columnHeaderDataLayer =
+                new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);
+        ILayer columnHeaderLayer =
+                new ColumnHeaderLayer(columnHeaderDataLayer, viewportLayer, selectionLayer);
+
+        // build the row header layer
+        IDataProvider rowHeaderDataProvider =
+                new DefaultRowHeaderDataProvider(bodyDataProvider);
+        DataLayer rowHeaderDataLayer =
+                new DefaultRowHeaderDataLayer(rowHeaderDataProvider);
+        ILayer rowHeaderLayer =
+                new RowHeaderLayer(rowHeaderDataLayer, viewportLayer, selectionLayer);
+
+        // build the corner layer
+        IDataProvider cornerDataProvider =
+                new DefaultCornerDataProvider(columnHeaderDataProvider, rowHeaderDataProvider);
+        DataLayer cornerDataLayer =
+                new DataLayer(cornerDataProvider);
+        ILayer cornerLayer =
+                new CornerLayer(cornerDataLayer, rowHeaderLayer, columnHeaderLayer);
+
+        // build the grid layer
+        GridLayer gridLayer =
+                new GridLayer(viewportLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer);
+
+        NatTable natTable = new NatTable(parent, gridLayer);
+
+        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
+
+        return natTable;
+
+    }
+}