blob: 3d2f4b629bbfb136baa56b2e0e3b11cd66013d7d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012, 2016 Original authors 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:
* Original authors and others - initial API and implementation
******************************************************************************/
package org.eclipse.nebula.widgets.nattable.print;
import java.text.MessageFormat;
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;
import org.eclipse.nebula.widgets.nattable.config.Direction;
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
import org.eclipse.nebula.widgets.nattable.formula.command.DisableFormulaCachingCommand;
import org.eclipse.nebula.widgets.nattable.formula.command.EnableFormulaCachingCommand;
import org.eclipse.nebula.widgets.nattable.layer.ILayer;
import org.eclipse.nebula.widgets.nattable.layer.cell.ILayerCell;
import org.eclipse.nebula.widgets.nattable.print.command.PrintEntireGridCommand;
import org.eclipse.nebula.widgets.nattable.print.command.TurnViewportOffCommand;
import org.eclipse.nebula.widgets.nattable.print.command.TurnViewportOnCommand;
import org.eclipse.nebula.widgets.nattable.print.config.PrintConfigAttributes;
import org.eclipse.nebula.widgets.nattable.resize.AutoResizeHelper;
import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
import org.eclipse.nebula.widgets.nattable.style.IStyle;
import org.eclipse.nebula.widgets.nattable.summaryrow.command.CalculateSummaryRowValuesCommand;
import org.eclipse.nebula.widgets.nattable.util.GUIHelper;
import org.eclipse.nebula.widgets.nattable.util.IClientAreaProvider;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Transform;
import org.eclipse.swt.printing.PrintDialog;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
/**
* This class is used to print a layer. Usually you create an instance by using
* the top most layer in the layer stack. For grids this is the GridLayer,
* otherwise the ViewportLayer is a good choice.
*/
public class LayerPrinter {
private class PrintTarget {
private final IConfigRegistry configRegistry;
private final ILayer layer;
private final IClientAreaProvider originalClientAreaProvider;
private final boolean repeat;
PrintTarget(ILayer layer, IConfigRegistry configRegistry, boolean repeat) {
this.layer = layer;
this.configRegistry = configRegistry;
this.originalClientAreaProvider = layer.getClientAreaProvider();
this.repeat = repeat;
}
}
private final List<PrintTarget> printTargets = new ArrayList<PrintTarget>();
public static final int FOOTER_HEIGHT_IN_PRINTER_DPI = 300;
final SimpleDateFormat dateFormat;
private final String footerDate;
private final String footerPagePattern;
private final int footerHeight;
/**
* @since 1.4
*/
protected boolean preRender = true;
private final Direction fittingMode;
private final boolean stretch;
private int headerHeight = 0;
private ILayer repeatHeaderLayer;
private boolean join = false;
/**
*
* @param layer
* The layer to print. Usually the top most layer in the layer
* stack. For grids this should be the GridLayer, for custom
* CompositeLayer compositions the CompositeLayer, otherwise the
* ViewportLayer is a good choice.
* @param configRegistry
* The ConfigRegistry needed for rendering to the print GC.
*/
public LayerPrinter(ILayer layer, IConfigRegistry configRegistry) {
this(layer, configRegistry, false);
}
/**
*
* @param layer
* The layer to print. Usually the top most layer in the layer
* stack. For grids this should be the GridLayer, for custom
* CompositeLayer compositions the CompositeLayer, otherwise the
* ViewportLayer is a good choice.
* @param configRegistry
* The ConfigRegistry needed for rendering to the print GC.
* @param repeat
* Flag to configure whether the given layer should be printed on
* every page. Needed for example in case an additional header
* layer is used outside the main table.
* @since 1.5
*/
public LayerPrinter(ILayer layer, IConfigRegistry configRegistry, boolean repeat) {
this.printTargets.add(new PrintTarget(layer, configRegistry, repeat));
// configure the footer height
Integer fh = configRegistry.getConfigAttribute(
PrintConfigAttributes.FOOTER_HEIGHT,
DisplayMode.NORMAL);
this.footerHeight = (fh != null) ? fh : FOOTER_HEIGHT_IN_PRINTER_DPI;
String pagePattern = configRegistry.getConfigAttribute(
PrintConfigAttributes.FOOTER_PAGE_PATTERN,
DisplayMode.NORMAL);
this.footerPagePattern = (pagePattern != null) ? pagePattern : Messages.getString("Printer.page"); //$NON-NLS-1$
// configure the footer date
String configuredFormat = configRegistry.getConfigAttribute(
PrintConfigAttributes.DATE_FORMAT,
DisplayMode.NORMAL);
if (configuredFormat != null) {
this.dateFormat = new SimpleDateFormat(configuredFormat);
} else {
this.dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm a"); //$NON-NLS-1$
}
this.footerDate = this.dateFormat.format(new Date());
// configure the fitting mode
Direction configuredFittingMode = configRegistry.getConfigAttribute(
PrintConfigAttributes.FITTING_MODE,
DisplayMode.NORMAL);
this.fittingMode = (configuredFittingMode != null) ? configuredFittingMode : Direction.NONE;
Boolean configureStretching = configRegistry.getConfigAttribute(
PrintConfigAttributes.STRETCH,
DisplayMode.NORMAL);
this.stretch = (configureStretching != null) ? configureStretching : false;
}
/**
* 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, false));
}
/**
* Configure whether multiple print targets should be joined for printing or
* not.
*
* @param join
* <code>true</code> if the print targets should be printed
* consecutively, <code>false</code> if every print target should
* start with a new page. Default is <code>false</code>.
*
* @since 1.5
*/
public void joinPrintTargets(boolean join) {
this.join = join;
}
/**
* Configure printing to repeat the header layer on every print page. For
* this it is necessary to specify the header layer that should be repeated
* in order to calculate the necessary header height on printing.
* <p>
* <b>Note:</b> The layer that is set as repeat layer via this method needs
* to be part of the print layer, e.g. the column header layer of a given
* grid layer.
* </p>
*
* @param layer
* The {@link ILayer} that should be repeated as header on every
* print page.
* @since 1.5
*/
public void repeatHeaderLayer(ILayer layer) {
this.repeatHeaderLayer = layer;
if (this.repeatHeaderLayer != null) {
this.headerHeight = this.repeatHeaderLayer.getHeight();
} else {
this.headerHeight = 0;
}
}
/**
*
* @return The height of the print target that should be repeated on every
* page. Typically only the first registered one in case of multiple
* print targets. If there is only one print target or no print
* target should be repeated it returns 0.
*/
private int getRepeatPrintTargetHeight() {
int result = 0;
for (PrintTarget target : this.printTargets) {
if (target.repeat) {
result += target.layer.getHeight();
}
}
return result;
}
/**
* @param printer
* The printer that will be used for printing.
* @return The scale factor used for the scaling of the repeat print target
* or [0, 0] of there is no print target configured for repeating.
*/
private float[] getRepeatPrintTargetScaleFactor(Printer printer) {
float[] result = new float[] { 0, 0 };
// currently we only support repeating the first of the configured multi
// print targets
if (this.printTargets.get(0).repeat) {
result = computeLayerScaleFactor(this.printTargets.get(0).layer, printer);
}
return result;
}
/**
* 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
* <code>true</code> if in any case the dpi scaling factor should
* be returned, <code>false</code> if the calculation properties
* need to be checked whether the scaling factor to match a page
* should be returned
* @return The amount to scale the screen resolution by, to match the
* printer the resolution.
*/
private float[] computeScaleFactor(ILayer layer, Printer printer, boolean dpi) {
Point screenDPI = Display.getDefault().getDPI();
Point printerDPI = printer.getDPI();
float sfX = Float.valueOf(printerDPI.x) / Float.valueOf(screenDPI.x);
float sfY = Float.valueOf(printerDPI.y) / Float.valueOf(screenDPI.y);
if (!dpi && (this.fittingMode != Direction.NONE)) {
Rectangle total = getTotalArea(layer);
if (this.join || this.printTargets.get(0).repeat) {
// calculate the total height of all targets
total.height = 0;
for (PrintTarget target : this.printTargets) {
total.height += target.layer.getHeight();
}
}
Rectangle print = computePrintArea(printer);
float pixelX = Float.valueOf(print.width) / Float.valueOf(total.width);
float pixelY = (Float.valueOf(print.height) - getFooterHeightInPrinterDPI()) / Float.valueOf(total.height);
// only support down-scaling, no stretching
// stretching could cause serious issues, e.g. vertical
// stretching for one row could cause a really long running
// operation because the width gets really really big
if (pixelX > sfX && !this.stretch) {
pixelX = sfX;
}
if (pixelY > sfY) {
pixelY = sfY;
}
switch (this.fittingMode) {
case HORIZONTAL:
return new float[] { pixelX, pixelX };
case VERTICAL:
return new float[] { pixelY, pixelY };
case BOTH:
return new float[] { pixelX, pixelY };
}
}
return new float[] { sfX, sfY };
}
/**
* Computes the scale factor to match the printer resolution. In case of
* fitting mode configurations and multiple print targets, the common
* scaling factor will be calculated and returned.
*
* @param layer
* The layer for which the scale factor should be calculated.
* @param printer
* The printer that will be used.
* @return The amount to scale the screen resolution by, to match the
* printer the resolution.
*/
private float[] computeLayerScaleFactor(ILayer layer, Printer printer) {
float[] scaleFactor = null;
if (this.fittingMode == Direction.NONE
|| (!this.join && !this.printTargets.get(0).repeat)
|| this.stretch) {
scaleFactor = computeScaleFactor(layer, printer, false);
} else {
// search for the common scaling factor
for (PrintTarget tempTarget : this.printTargets) {
float[] tempFactor = computeScaleFactor(tempTarget.layer, printer, false);
if (scaleFactor == null) {
scaleFactor = tempFactor;
} else {
scaleFactor[0] = Math.min(scaleFactor[0], tempFactor[0]);
scaleFactor[1] = Math.min(scaleFactor[1], tempFactor[1]);
}
}
}
return scaleFactor;
}
/**
* @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(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;
int available = -1;
float[] prevScaleFactor = null;
for (PrintTarget target : this.printTargets) {
if (!target.repeat) {
int[] layerResult = getPageCount(target, printer, available, prevScaleFactor);
result += (layerResult[0] * layerResult[1]);
// as the print targets should be joined and the print was
// started on an existing page, we need to reduce the page count
if (this.join && available > 0) {
result--;
}
available = layerResult[2];
if (available >= 0) {
prevScaleFactor = computeLayerScaleFactor(target.layer, printer);
}
}
}
return result;
}
/**
* Calculates number of horizontal and vertical pages needed to print the
* entire layer of the given print target.
*
* @param target
* The print target to print.
* @param printer
* The printer that will be used.
* @param available
* The remaining available space in pixel on a page after the
* target is printed in case the print targets should be glued,
* -1 otherwise.
* @param prevScaleFactor
* The scale factor of the previous table in case the tables
* should be joined. Needed to calculate the available space
* correctly
* @return The number of horizontal and vertical pages that are needed to
* print the layer of the given print target and the remaining space
* on a page if the print targets should be glued.
*/
private int[] getPageCount(PrintTarget target, Printer printer, int available, float[] prevScaleFactor) {
Rectangle printArea = computePrintArea(printer);
float[] scaleFactor = computeLayerScaleFactor(target.layer, printer);
Integer[] gridLineWidth = getGridLineWidth(target.configRegistry);
// calculate pages based on non cut off columns/rows
int numOfHorizontalPages = 0;
int pageWidth = Math.round(Float.valueOf(printArea.width / scaleFactor[0]));
int endX = 0;
while (endX < target.layer.getWidth()) {
endX += pageWidth;
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) {
endX -= (endX - cellBounds.x);
endX -= gridLineWidth[1];
}
}
} else {
endX = target.layer.getWidth();
}
numOfHorizontalPages++;
}
int numOfVerticalPages = 0;
int repeatPrintTargetHeightInDpi = Math.round(Float.valueOf(getRepeatPrintTargetHeight() * getRepeatPrintTargetScaleFactor(printer)[1]));
int headerHeightInDpi = (this.headerHeight != 0 && this.repeatHeaderLayer != null)
? Math.round(Float.valueOf((this.headerHeight * scaleFactor[1]))) : 0;
int pageHeight = Math.round(Float.valueOf(
(printArea.height - repeatPrintTargetHeightInDpi - headerHeightInDpi - getFooterHeightInPrinterDPI()) / scaleFactor[1]));
int firstPageHeight = (available < 0)
? Math.round(Float.valueOf((printArea.height - repeatPrintTargetHeightInDpi - getFooterHeightInPrinterDPI()) / scaleFactor[1]))
: Math.round(Float.valueOf(Math.round(Float.valueOf(available * prevScaleFactor[1])) / scaleFactor[1]));
int endY = 0;
int added = 0;
int remaining = -1;
while (endY < (target.layer.getHeight() - this.headerHeight)) {
// on the first page we don't need to take care of the repeat
// header height
added = (numOfVerticalPages == 0) ? firstPageHeight : pageHeight;
endY += added;
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) {
endY -= (endY - cellBounds.y);
endY -= gridLineWidth[1];
}
}
} else {
// in case the print targets should be glued we need to
// calculate the remaining space for the following print target
if (this.join) {
remaining = ((numOfVerticalPages == 0) ? firstPageHeight : pageHeight) - (target.layer.getHeight() - (endY - added));
}
endY = target.layer.getHeight();
}
numOfVerticalPages++;
}
if (gridLineWidth[0] == null) {
target.configRegistry.unregisterConfigAttribute(CellConfigAttributes.GRID_LINE_WIDTH);
}
return new int[] { numOfHorizontalPages, numOfVerticalPages, remaining };
}
private ILayerCell findColumnCellForBounds(ILayer layer, int colPos) {
int rowPos = 0;
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 = layer.getCellByPosition(colPos, rowPos);
}
return cell;
}
private ILayerCell findRowCellForBounds(ILayer layer, int rowPos) {
int colPos = 0;
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 = layer.getCellByPosition(colPos, rowPos);
}
return cell;
}
/**
* @return The footer height in printer DPI that should be used to render
* the footer.
* @since 1.5
*/
protected int getFooterHeightInPrinterDPI() {
return this.footerHeight;
}
/**
* @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(IConfigRegistry configRegistry) {
// check if a grid line width is configured
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) {
configRegistry.registerConfigAttribute(
CellConfigAttributes.GRID_LINE_WIDTH, 2);
gridLineWidth = 2;
}
return new Integer[] { width, gridLineWidth };
}
/**
* Will first open the PrintDialog to let a user configure the print job and
* then starts the print job.
*
* @param shell
* The shell which should be the parent of the PrintDialog.
*/
public void print(final Shell shell) {
// turn viewport off to ensure calculation of the print pages for the
// whole table
for (PrintTarget target : this.printTargets) {
target.layer.doCommand(new TurnViewportOffCommand());
}
Printer printer = null;
try {
printer = setupPrinter(shell);
if (printer == null) {
return;
}
} finally {
// turn viewport on
for (PrintTarget target : this.printTargets) {
target.layer.doCommand(new TurnViewportOnCommand());
}
}
// Note: As we are operating on the same layer instance that is shown in
// the UI executing the print job asynchronously will not cause a real
// asynchronous execution. The UI will hang until the print job is done,
// because we access the information to print from the same instance.
// For further developments we need to ensure that for printing a deep
// copy of the layer needs to be performed instead of operating on the
// same instance.
Display.getDefault().asyncExec(new PrintJob(printer));
}
/**
* Checks if a given page number should be printed. Page is allowed to print
* if: User asked to print all pages or Page in a specified range
*
* @param printerData
* The printer settings made by the user. Needed to determine if
* a page should be printed dependent to the scope
* @param currentPage
* The page that should be checked
* @return <code>true</code> if the given page should be printed,
* <code>false</code> if not
*/
private boolean shouldPrint(PrinterData printerData, int totalPageCount) {
if (printerData.scope == PrinterData.PAGE_RANGE) {
return totalPageCount >= printerData.startPage
&& totalPageCount <= printerData.endPage;
}
return true;
}
/**
* Opens the PrintDialog to let the user specify the printer and print
* configurations to use.
*
* @param shell
* The Shell which should be the parent for the PrintDialog
* @return The selected printer with the print configuration made by the
* user.
*/
private Printer setupPrinter(final Shell shell) {
Printer defaultPrinter = new Printer();
int pageCount = getPageCount(defaultPrinter);
defaultPrinter.dispose();
final PrintDialog printDialog = new PrintDialog(shell);
printDialog.setStartPage(1);
printDialog.setEndPage(pageCount);
printDialog.setScope(PrinterData.ALL_PAGES);
PrinterData printerData = printDialog.open();
if (printerData == null) {
return null;
}
return new Printer(printerData);
}
/**
* Computes the print area, including margins
*
* @param printer
* The printer that will be used.
* @return The print area that will be used to render the table.
*/
private Rectangle computePrintArea(Printer printer) {
// Get the printable area
Rectangle rect = printer.getClientArea();
// Compute the trim
Rectangle trim = printer.computeTrim(0, 0, 0, 0);
// Get the printer's DPI
Point dpi = printer.getDPI();
dpi.x = dpi.x / 2;
dpi.y = dpi.y / 2;
// Calculate the printable area, using 1 inch margins
int left = trim.x + dpi.x;
if (left < rect.x)
left = rect.x;
int right = (rect.width + trim.x + trim.width) - dpi.x;
if (right > rect.width)
right = rect.width;
int top = trim.y + dpi.y;
if (top < rect.y)
top = rect.y;
int bottom = (rect.height + trim.y + trim.height) - dpi.y;
if (bottom > rect.height)
bottom = rect.height;
return new Rectangle(left, top, right - left, bottom - top);
}
/**
* Enable in-memory pre-rendering. This is necessary in case content
* painters are used that are configured for content based auto-resizing.
*
* @since 1.4
*/
public void enablePreRendering() {
this.preRender = true;
}
/**
* Disable in-memory pre-rendering. You should consider to disable
* pre-rendering if no content painters are used that are configured for
* content based auto-resizing.
*
* @since 1.4
*/
public void disablePreRendering() {
this.preRender = false;
}
/**
* The job for printing the layer.
*/
private class PrintJob implements Runnable {
/**
* The printer that will be used.
*/
private final Printer printer;
/**
* @param printer
* The printer that will be used.
*/
private PrintJob(Printer printer) {
this.printer = printer;
}
@Override
public void run() {
if (this.printer.startJob("NatTable")) { //$NON-NLS-1$
GC gc = new GC(this.printer);
// turn the viewport for all targets off to ensure everything is
// taken into account for calculation and printing
for (PrintTarget target : LayerPrinter.this.printTargets) {
target.layer.doCommand(new TurnViewportOffCommand());
}
try {
int currentPage = 1;
int totalPageCount = getPageCount(this.printer);
Integer[] repeatHeaderGridLineWidth = null;
float[] repeatScaleFactor = null;
int available = -1;
float[] prevScaleFactor = null;
boolean newPage = true;
boolean pageStarted = false;
for (PrintTarget target : LayerPrinter.this.printTargets) {
if (target.repeat) {
// we do not render the repeat print target directly
// as it is handled on every page while printing
repeatHeaderGridLineWidth = getGridLineWidth(target.configRegistry);
repeatScaleFactor = computeLayerScaleFactor(target.layer, this.printer);
continue;
}
// if pre-rendering is enabled, render in-memory to
// trigger content based auto-resizing
if (LayerPrinter.this.preRender) {
try {
AutoResizeHelper.autoResize(target.layer, target.configRegistry);
} finally {
// the AutoResizeHelper also toggles the
// viewport state we need to ensure that it is
// still turned off afterwards
target.layer.doCommand(new TurnViewportOffCommand());
}
if (LayerPrinter.this.repeatHeaderLayer != null) {
LayerPrinter.this.headerHeight = LayerPrinter.this.repeatHeaderLayer.getHeight();
}
}
float[] scaleFactor = computeLayerScaleFactor(target.layer, this.printer);
float[] dpiFactor = computeScaleFactor(target.layer, this.printer, true);
int availablePixel = available;
if (available > 0) {
int prevDPI = Math.round(Float.valueOf(available * prevScaleFactor[1]));
availablePixel = Math.round(Float.valueOf(prevDPI / scaleFactor[1]));
}
Integer[] gridLineWidth = getGridLineWidth(target.configRegistry);
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 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 repeatPrintTargetHeight = getRepeatPrintTargetHeight();
int repeatPrintTargetHeightInDpi = Math.round(Float.valueOf(repeatPrintTargetHeight * ((repeatScaleFactor != null) ? repeatScaleFactor[1] : 0)));
int headerHeightDPI = Math.round(Float.valueOf((LayerPrinter.this.headerHeight * scaleFactor[1])));
int printBoundsHeight = Math.round(Float.valueOf((printerClientArea.height - repeatPrintTargetHeightInDpi - headerHeightDPI - getFooterHeightInPrinterDPI()) / scaleFactor[1]));
int firstPagePrintBoundsHeight = (available < 0)
? Math.round(Float.valueOf((printerClientArea.height - repeatPrintTargetHeightInDpi - getFooterHeightInPrinterDPI()) / scaleFactor[1]))
: availablePixel;
final int[] pageCount = getPageCount(target, this.printer, available, prevScaleFactor);
// Print pages Left to Right and then Top to Down
int startY = 0;
for (int verticalPageNumber = 0; verticalPageNumber < pageCount[1]; verticalPageNumber++) {
// on the first page we don't need to take care
// of the repeat header height
int pbh = (verticalPageNumber == 0 ? firstPagePrintBoundsHeight : printBoundsHeight);
int endY = startY + pbh;
// 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) {
pbh -= (endY - cellBounds.y);
}
}
}
int startX = 0;
for (int horizontalPageNumber = 0; horizontalPageNumber < pageCount[0]; horizontalPageNumber++) {
// Calculate bounds for the next page
Rectangle printBounds = new Rectangle(
startX,
startY,
printBoundsWidth,
pbh);
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)) {
// end a page that was previously
// started
if (pageStarted) {
this.printer.endPage();
newPage = true;
}
// start a new page
if (newPage) {
this.printer.startPage();
pageStarted = true;
newPage = false;
}
// ensure there is a next page
// will be set to false afterwards again
// if multiple targets fit on one page
if (!pageStarted && !newPage) {
pageStarted = true;
}
Transform printerTransform = new Transform(this.printer);
Transform repeatTransform = new Transform(this.printer);
Transform headerTransform = new Transform(this.printer);
Transform footerTransform = new Transform(this.printer);
Rectangle intersect = new Rectangle(
0,
0,
target.layer.getWidth(),
target.layer.getHeight());
intersect = printBounds.intersection(intersect);
configureScalingTransform(printerTransform, scaleFactor, printerClientArea, intersect);
configureScalingTransform(repeatTransform, (repeatScaleFactor != null ? repeatScaleFactor : scaleFactor), printerClientArea, intersect);
configureScalingTransform(headerTransform, scaleFactor, printerClientArea, intersect);
if (repeatPrintTargetHeight > 0) {
repeatTransform.translate(0, startY);
gc.setTransform(repeatTransform);
Rectangle repeatIntersect = new Rectangle(
0,
0,
LayerPrinter.this.printTargets.get(0).layer.getWidth(),
LayerPrinter.this.printTargets.get(0).layer.getHeight());
repeatIntersect = printBounds.intersection(repeatIntersect);
printLayer(LayerPrinter.this.printTargets.get(0), gc, new Rectangle(repeatIntersect.x, 0, repeatIntersect.width, repeatPrintTargetHeight));
printerTransform.translate(0, Math.round(Float.valueOf(Math.round(Float.valueOf(repeatPrintTargetHeight * repeatScaleFactor[1])) / scaleFactor[1])));
}
if (LayerPrinter.this.repeatHeaderLayer != null && verticalPageNumber != 0) {
headerTransform.translate(0, startY + Math.round(Float.valueOf(Math.round(Float.valueOf(repeatPrintTargetHeight * ((repeatScaleFactor != null) ? repeatScaleFactor[1] : 0))) / scaleFactor[1])));
gc.setTransform(headerTransform);
printLayer(target, gc, new Rectangle(printBounds.x, 0, intersect.width, LayerPrinter.this.headerHeight));
printerTransform.translate(0, LayerPrinter.this.headerHeight);
}
// on joining print targets we need to
// transform for rendering the first
// page on the same page as the previous
// target
if (LayerPrinter.this.join && available > 0 && verticalPageNumber == 0) {
printerTransform.translate(0, (printBoundsHeight + LayerPrinter.this.headerHeight) - availablePixel);
}
gc.setTransform(printerTransform);
printLayer(target, gc, intersect);
configureScalingTransform(footerTransform, dpiFactor, printerClientArea, footerBounds);
gc.setTransform(footerTransform);
printFooter(gc, currentPage, totalPageCount, footerBounds, target.configRegistry);
printerTransform.dispose();
repeatTransform.dispose();
headerTransform.dispose();
footerTransform.dispose();
}
currentPage++;
startX += printBounds.width;
}
startY += pbh;
available = pageCount[2];
prevScaleFactor = scaleFactor;
}
if (LayerPrinter.this.join && available > 0) {
currentPage--;
}
// after last page is printed, check if the page
// needs to end or if it should be used for the next
// print target
if (!LayerPrinter.this.join || available < 0) {
this.printer.endPage();
newPage = true;
pageStarted = false;
} else if (LayerPrinter.this.join && available > 0) {
newPage = false;
pageStarted = false;
}
} finally {
restoreLayerState(target);
}
// 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);
}
}
if (repeatHeaderGridLineWidth != null && repeatHeaderGridLineWidth[0] == null) {
LayerPrinter.this.printTargets.get(0).configRegistry.unregisterConfigAttribute(CellConfigAttributes.GRID_LINE_WIDTH);
}
} finally {
this.printer.endJob();
gc.dispose();
this.printer.dispose();
// turn viewport on
for (PrintTarget target : LayerPrinter.this.printTargets) {
target.layer.doCommand(new TurnViewportOnCommand());
}
}
}
}
/**
* Configure the given {@link Transform} in order to support scaling
* correctly on printing.
*
* @param transform
* The {@link Transform} to configure
* @param scaleFactor
* The scale factor to set and to be used for translation
* @param printerClientArea
* The client area of the printer
* @param printBounds
* The print bounds
*/
private void configureScalingTransform(
Transform transform, float[] scaleFactor,
Rectangle printerClientArea, Rectangle printBounds) {
// Adjust for DPI difference between display and printer
transform.scale(scaleFactor[0], scaleFactor[1]);
// Adjust for margins
transform.translate(
printerClientArea.x / scaleFactor[0],
printerClientArea.y / scaleFactor[1]);
// Grid will not automatically print the pages
// at the left margin.
// Example: page 1 will print at x = 0, page 2
// at x = 100, page 3 at x = 300
// Adjust to print from the left page margin.
// i.e x = 0
transform.translate(-1 * printBounds.x, -1 * printBounds.y);
}
/**
* Set the client area of the layer so it matches the print settings
* made by the user. In case a user selected to print everything, the
* 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(PrintTarget target, PrinterData printerData) {
if (printerData != null && printerData.scope == PrinterData.SELECTION) {
target.layer.setClientAreaProvider(target.originalClientAreaProvider);
} else {
final Rectangle fullLayerSize = getTotalArea(target.layer);
target.layer.setClientAreaProvider(new IClientAreaProvider() {
@Override
public Rectangle getClientArea() {
return fullLayerSize;
}
});
// 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
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(PrintTarget target, GC gc, Rectangle printBounds) {
target.layer.getLayerPainter().paintLayer(
target.layer, gc, 0, 0, printBounds, target.configRegistry);
}
/**
* Print the footer to the page.
*
* @param gc
* The print GC to render the footer to.
* @param totalPageCount
* 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 currentPage, int totalPageCount, Rectangle printBounds, IConfigRegistry configRegistry) {
Color oldForeground = gc.getForeground();
Color oldBackground = gc.getBackground();
Font oldFont = gc.getFont();
Color footerForeground = null;
Color footerBackground = null;
Font footerFont = null;
IStyle style = configRegistry.getConfigAttribute(
PrintConfigAttributes.FOOTER_STYLE,
DisplayMode.NORMAL);
if (style != null) {
footerForeground = style.getAttributeValue(CellStyleAttributes.FOREGROUND_COLOR);
footerBackground = style.getAttributeValue(CellStyleAttributes.BACKGROUND_COLOR);
footerFont = style.getAttributeValue(CellStyleAttributes.FONT);
}
gc.setForeground(footerForeground != null ? footerForeground : GUIHelper.COLOR_BLACK);
gc.setBackground(footerBackground != null ? footerBackground : GUIHelper.COLOR_WHITE);
gc.setFont(footerFont != null ? footerFont : GUIHelper.DEFAULT_FONT);
gc.drawLine(
printBounds.x,
printBounds.y + printBounds.height + 10,
printBounds.x + printBounds.width,
printBounds.y + printBounds.height + 10);
gc.drawText(
MessageFormat.format(LayerPrinter.this.footerPagePattern, currentPage, totalPageCount),
printBounds.x,
printBounds.y + printBounds.height + 15);
gc.drawText(
LayerPrinter.this.footerDate,
printBounds.x + printBounds.width - gc.textExtent(LayerPrinter.this.footerDate).x,
printBounds.y + printBounds.height + 15);
gc.setForeground(oldForeground);
gc.setBackground(oldBackground);
gc.setFont(oldFont);
}
/**
* 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(PrintTarget target) {
target.layer.setClientAreaProvider(target.originalClientAreaProvider);
target.layer.doCommand(new EnableFormulaCachingCommand());
}
}
}