You can download the complete source code of the MultiplePagesOnSheet sample here.
When printing documents the more flexibility you have, the better. Using Java and Aspose.Words you can easily fine-tune the printing operation to implement your own custom logic that defines the look of the document on the printed page.
This sample demonstrates how to print several document pages on a single sheet of paper. To be as realistic as possible, the sample provides capabilities as close as possible to Word itself by enabling the choice of one, two, four, six, eight, nine and sixteen pages per sheet to be printed. This can be easily modified to support other variants as well.
The approach used here is quite generic – this code implements the Printable interface. This means the existing Java printing infrastructure can be used such as the print dialog which allows the user to pick their own printing settings.
The basic steps to follow to print multiple pages on a single sheet are as follows:
1. Create a class which implements the Printable interface.
a. Pass to the constructor an Aspose.Words Document object and an integer defining the number of the pages you want to print on one sheet.
b. Implement the print method to define the look of each page before sending to the printer. Here you should:
i. Calculate the size of the thumbnail placeholder for each page to fit on the sheet. This depends on the number of pages to appear on the sheet.
ii. Resize and render all the pages onto the sheet in the appropriate places using the Graphics object of the printer.
iii. Return Printable.PAGE_EXISTS if the specified page was rendered successfully or Printable.NO_SUCH_PAGE if there was an error during rendering or the specified page does not exist.
2. Create an instance of the newly defined MultipagePrintDocument class and initialize it with the appropriate Aspose.Words Document object.
3. Call print on the MultipagePrintDocument document object.
We will use the following Word document in this sample:
As you can see, it has four pages with various sizes and orientations.
The main idea of the implemented approach is both simple and generic. In Java, if you want to print a specific object or establish specific control over the printing process you need to implement your own class which implements the Printable and/or the Pageable interface. This is the common approach for this sort of task, and is the same technique that the AsposeWordsPrintDocument class uses.
In this sample the MultipagePrintDocument class implements the Printable interface and provides the ability of printing several pages on one sheet of paper.
Example
The custom class which implements the Printable interface.
[Java]
class MultipagePrintDocument implements Printable
For the purposes of illustration, it is enough to focus on a few methods only. The first of them is a constructor where you need to read and store the necessary parameters. The MultipagePrintDocument class accepts four parameters: the Aspose.Words document, the number of pages per sheet and the flag that indicates if the borders around the pages should be printed. Additionally printer attributes can be passed using the parmeter accepting the PrinterRequestAttributeSet object. These attributes are used to read the page range defined by user which specifies which pages of the document to render:
Example
The constructor of the custom PrintDocument class.
[Java]
public MultipagePrintDocument(Document document, int pagesPerSheet, boolean printPageBorders, AttributeSet attributes)
{
if (document == null)
throw new IllegalArgumentException("document");
mDocument = document;
mPagesPerSheet = pagesPerSheet;
mPrintPageBorders = printPageBorders;
mAttributeSet = attributes;
}
The constructor initializes the class fields and throws an exception if no document is passed. These are the fields defined in the MultipagePrintDocument class:
Example
The data and state fields of the custom Printable class.
[Java]
private Document mDocument;
private int mPagesPerSheet;
private boolean mPrintPageBorders;
private AttributeSet mAttributeSet;
The main method of the class is the print method. Here you can control the printing process itself. The rendering features of Aspose.Words are applied to the Graphics object from the printer’s context, which allows you to easily specify what should be printed and how exactly it should appear on paper.
One thing to note about this method is its main function. This method is the event handler which is called each time before a page prints. This means if you are going to print several pages, you need to be aware of what has already been printed and which page is the last to be printed. In the current sample the number of pages rendered is calculated based off the current index of the sheet being printed. The mAttributeSet object contains the start and end page attributes used to calculate when the printing should stop.
The print method also allows you to decide if you want the printing to continue or stop. It does this by using a simple technique - if the current index represents a valid page to render then the method should render it onto the Graphics object and return Printable.PAGE_EXISTS to signal that this page exists. If the current index is not valid and there are no futher pages to print then the method needs to return Printable.NO_SUCH_PAGE which signals that the printing should stop.
Here is the implementation of the print method from this sample. It renders the specified number of pages into the Graphics object with the appropriate scaling and positioning.
Example
Generates the printed page from the specified number of the document pages.
[Java]
public int print(Graphics g, PageFormat pf, int page)
{
// The page start and end indices as defined in the attribute set.
int[][] pageRanges = ((PageRanges)mAttributeSet.get(PageRanges.class)).getMembers();
int fromPage = pageRanges[0][0] - 1;
int toPage = pageRanges[0][1] - 1;
Dimension thumbCount = GetThumbCount(mPagesPerSheet, pf);
// Calculate the page index which is to be rendered next.
int pagesOnCurrentSheet = (int)(page * (thumbCount.getWidth() * thumbCount.getHeight()));
// If the page index is more than the total page range then there is nothing more to render.
if(pagesOnCurrentSheet > (toPage - fromPage))
return Printable.NO_SUCH_PAGE;
// Calculate the size of each thumbnail placeholder in points.
Point2D.Float thumbSize = new Point2D.Float(
(float)(pf.getImageableWidth() / thumbCount.getWidth()),
(float)(pf.getImageableHeight() / thumbCount.getHeight()));
// Calculate the number of the first page to be printed on this sheet of paper.
int startPage = pagesOnCurrentSheet + fromPage;
// Select the number of the last page to be printed on this sheet of paper.
int pageTo = Math.max(startPage + mPagesPerSheet - 1, toPage);
// Loop through the selected pages from the stored current page to calculated last page.
for (int pageIndex = startPage; pageIndex <= pageTo; pageIndex++)
{
// Calculate the column and row indices.
int rowIdx = (int)Math.floor((pageIndex - startPage) / thumbCount.getWidth());
int columnIdx = (int)Math.floor((pageIndex - startPage) % thumbCount.getWidth());
// Define the thumbnail location in world coordinates (points in this case).
float thumbLeft = columnIdx * thumbSize.x;
float thumbTop = rowIdx * thumbSize.y;
try{
// Calculate the left and top starting positions.
int leftPos = (int)(thumbLeft + pf.getImageableX());
int topPos = (int)(thumbTop + pf.getImageableY());
// Render the document page to the Graphics object using calculated coordinates and thumbnail placeholder size.
// The useful return value is the scale at which the page was rendered.
float scale = mDocument.renderToSize(pageIndex, (Graphics2D)g, leftPos, topPos, (int)thumbSize.x, (int)thumbSize.y);
// Draw the page borders (the page thumbnail could be smaller than the thumbnail placeholder size).
if (mPrintPageBorders)
{
// Get the real 100% size of the page in points.
Point2D.Float pageSize = mDocument.getPageInfo(pageIndex).getSizeInPoints();
// Draw the border around the scaled page using the known scale factor.
g.setColor(Color.black);
g.drawRect(leftPos, topPos, (int)(pageSize.x * scale), (int)(pageSize.y * scale));
// Draw the border around the thumbnail placeholder.
g.setColor(Color.red);
g.drawRect(leftPos, topPos, (int)thumbSize.x, (int)thumbSize.y);
}
}
catch(Exception e)
{
// If there are any errors that occur during rendering then do nothing.
// This will draw a blank page if there are any errors during rendering.
}
}
return Printable.PAGE_EXISTS;
}
An important task is to calculate the size of the thumbnail placeholders on the paper sheet. Since the number of thumbnails that should be printed on one sheet of paper is known, it is necessary to find the size of the area reserved for a single thumbnail on the paper. Once this is calculated the next task is to define the positions of all the thumbnail placeholders. The following method defines how many columns and rows are required on the sheet of paper to accommodate the number of thumbnails.
Example
Defines the number of columns and rows depending on the pagesPerSheet value and the page orientation.
[Java]
private Dimension GetThumbCount(int pagesPerSheet, PageFormat pf)
{
Dimension size;
// Define the number of the columns and rows on the sheet for the Landscape-oriented paper.
switch (pagesPerSheet)
{
case 16: size = new Dimension(4, 4); break;
case 9: size = new Dimension(3, 3); break;
case 8: size = new Dimension(4, 2); break;
case 6: size = new Dimension(3, 2); break;
case 4: size = new Dimension(2, 2); break;
case 2: size = new Dimension(2, 1); break;
default : size = new Dimension(1, 1); break;
}
// Swap the width and height if the paper is in the Portrait orientation.
if ((pf.getWidth() - pf.getImageableX()) < (pf.getHeight() - pf.getImageableY()))
return new Dimension((int)size.getHeight(), (int)size.getWidth());
return size;
}
The GetThumbCount method returns the predefined number of columns and rows which corresponds to the layout of the document pages onto the sheet. When appropriate, it divides the long side of the paper to more parts than the short one in order to maintain the right proportions of the thumbnail placeholders. Having the correctly oriented paper size, and the appropriate number of columns and rows, makes it easy to calculate the size of the thumbnail placeholders.
The rest of the code in the main print method is quite simple. The index is defined as the last page to be printed on this very sheet of paper. It will either be the index of the current page, appropriately increased by the specified number of pages per sheet, or the stored upper index from the print range, whichever one is smaller.
In the loop through the pages to be printed the position is calculated for each thumbnail, by moving across and down the sheet. Each thumbnail is rendered using the Document.RenderToSize method of the Document object. The Document.RenderToSize method is ideal for tasks like this as it allows the document to be rendered at a set size onto a Graphics object in any position you want. The Document.RenderToScale method provides similar functionality except it allows you to specify the scale of the resulting page rather than the size. In the current sample, using the Document.RendertoSize method makes more sense, because the size on the thumbnail placeholder is calculated already.
The loop then draws the borders for the page thumbnail and the thumbnail placeholder if this setting is enabled.
The rest of the code in the sample focuses on printing using the implemented MultipagePrintDocument class.
Example
The usage of the MultipagePrintDocument for printing.
[Java]
// Open the document.
Document doc = new Document(dataDir + "TestFile.doc");
// Create a print job to print our document with.
PrinterJob pj = PrinterJob.getPrinterJob();
// Initialize an attribute set with the number of pages in the document.
PrintRequestAttributeSet attributes = new HashPrintRequestAttributeSet();
attributes.add(new PageRanges(1, doc.getPageCount()));
// Pass the printer settings along with the other parameters to the print document.
MultipagePrintDocument awPrintDoc = new MultipagePrintDocument(doc, 4, true, attributes);
// Pass the document to be printed using the print job.
pj.setPrintable(awPrintDoc);
pj.print();
Running the sample will display the following results:
The four page document is rendered to print combined into one page. And borders are rendered between pages: