/* * Copyright (c) 2005 Matthew Hall 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: * Matthew Hall - initial API and implementation */ package org.eclipse.nebula.paperclips.core.page; import java.util.ArrayList; import java.util.List; import org.eclipse.nebula.paperclips.core.CompositeEntry; import org.eclipse.nebula.paperclips.core.CompositePiece; import org.eclipse.nebula.paperclips.core.PaperClips; import org.eclipse.nebula.paperclips.core.Print; import org.eclipse.nebula.paperclips.core.PrintIterator; import org.eclipse.nebula.paperclips.core.PrintPiece; import org.eclipse.nebula.paperclips.core.internal.util.PaperClipsUtil; import org.eclipse.nebula.paperclips.core.internal.util.PrintSizeStrategy; import org.eclipse.nebula.paperclips.core.internal.util.Util; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; /** * A decorator Print which displays page headers and footers around a document * body, with page numbering capabilities. * <p> * PagePrint is horizontally and vertically greedy. Greedy prints take up all * the available space on the page. * <p> * <b>Note:</b> Avoid wrapping PagePrint in prints with space-optimizing * semantics (e.g. ColumnPrint equalizes columns on the last page), as this may * cause the total page count to be incorrect on some pages. At this time there * is no known fix. If wrapping a PagePrint is unavoidable, consider using a * custom PageNumberFormat which does not display the total page count. * * @author Matthew Hall */ public class PagePrint implements Print { private static final int DEFAULT_GAP = 1; PageDecoration header; int headerGap = DEFAULT_GAP; // in points Print body; int footerGap = DEFAULT_GAP; // in points PageDecoration footer; /** * Constructs a PagePrint with the given header and body. * * @param header * a PageDecoration for creating the header. May be null. * @param headerGap * the gap between the header and body, in points. * @param body * the Print being decorated. */ public PagePrint(PageDecoration header, int headerGap, Print body) { this(header, headerGap, body, DEFAULT_GAP, null); } /** * Constructs a PagePrint with the given header and body. * * @param body * the Print being decorated. * @param header * a PageDecoration for creating the header. May be null. */ public PagePrint(PageDecoration header, Print body) { this(header, DEFAULT_GAP, body); } /** * Constructs a PagePrint with the given body. * * @param body * the Print being decorated. */ public PagePrint(Print body) { this(null, body, null); } /** * Constructs a PagePrint with the given body and footer. * * @param body * the Print being decorated. * @param footer * a PageDecoration for creating the footer. may be null. */ public PagePrint(Print body, PageDecoration footer) { this(body, DEFAULT_GAP, footer); } /** * Constructs a PagePrint with the given body, header and footer. * * @param body * the Print being decorated. * @param footerGap * the gap between the body and footer, in points. * @param footer * a PageDecoration for creating the footer. May be null. */ public PagePrint(Print body, int footerGap, PageDecoration footer) { this(null, DEFAULT_GAP, body, footerGap, footer); } /** * Constructs a PagePrint with the given body, header and footer. * * @param header * a PageDecoration for creating the header. May be null. * @param body * the Print being decorated. * @param footer * a PageDecoration for creating the footer. may be null. */ public PagePrint(PageDecoration header, Print body, PageDecoration footer) { this(header, DEFAULT_GAP, body, DEFAULT_GAP, footer); } /** * Constructs a PagePrint with the given body, header and footer. * * @param header * a PageDecoration for creating the header. May be null. * @param headerGap * the gap between the header and body, in points. * @param body * the Print being decorated. * @param footerGap * the gap between the body and footer, in points. * @param footer * a PageDecoration for creating the footer. May be null. */ public PagePrint(PageDecoration header, int headerGap, Print body, int footerGap, PageDecoration footer) { setHeader(header); setHeaderGap(headerGap); setBody(body); setFooterGap(footerGap); setFooter(footer); } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((body == null) ? 0 : body.hashCode()); result = prime * result + ((footer == null) ? 0 : footer.hashCode()); result = prime * result + footerGap; result = prime * result + ((header == null) ? 0 : header.hashCode()); result = prime * result + headerGap; return result; } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PagePrint other = (PagePrint) obj; if (body == null) { if (other.body != null) return false; } else if (!body.equals(other.body)) return false; if (footer == null) { if (other.footer != null) return false; } else if (!footer.equals(other.footer)) return false; if (footerGap != other.footerGap) return false; if (header == null) { if (other.header != null) return false; } else if (!header.equals(other.header)) return false; if (headerGap != other.headerGap) return false; return true; } /** * Returns the page header. * * @return the page header. */ public PageDecoration getHeader() { return header; } /** * Sets the page header to the argument. * * @param header * a PageDecoration which creates the header. May be null. */ public void setHeader(PageDecoration header) { this.header = header; } /** * Returns the gap between the header and body, expressed in points. * * @return the gap between the header and body, expressed in points. */ public int getHeaderGap() { return headerGap; } /** * Sets the gap between the header and body to the argument, expressed in * points. * * @param points * the new gap between the header and body, expressed in points. * 72 points = 1". */ public void setHeaderGap(int points) { this.headerGap = checkGap(points); } /** * Returns the page body. * * @return the page body. */ public Print getBody() { return body; } /** * Sets the page body to the argument. * * @param body * the new page body. */ public void setBody(Print body) { Util.notNull(body); this.body = body; } /** * Returns the page footer. * * @return the page footer. */ public PageDecoration getFooter() { return footer; } /** * Sets the page footer to the argument. * * @param footer * a PageDecoration which creates the footer. May be null. */ public void setFooter(PageDecoration footer) { this.footer = footer; } /** * Returns the gap between the body and footer, expressed in points. * * @return the gap between the body and footer, expressed in points. */ public int getFooterGap() { return footerGap; } /** * Sets the gap between the body and footer to the argument, expressed in * points. * * @param points * the new gap between the body and footer (if there is a * footer). */ public void setFooterGap(int points) { this.footerGap = checkGap(points); } private static int checkGap(int gap) { if (gap < 0) PaperClips.error(SWT.ERROR_INVALID_ARGUMENT, "Gap must be >= 0 (value is " + gap + ")"); //$NON-NLS-1$ //$NON-NLS-2$ return gap; } public PrintIterator iterator(Device device, GC gc) { if (header == null && footer == null) return body.iterator(device, gc); return new PageIterator(this, device, gc); } } class PageIterator implements PrintIterator { class PageNumberer { int pageCount = 0; synchronized PageNumber next() { return new InnerPageNumber(); } class InnerPageNumber implements PageNumber { final int pageNumber = pageCount++; // POST-increment public int getPageCount() { return pageCount; } public int getPageNumber() { return pageNumber; } } PageNumberer copy() { PageNumberer result = new PageNumberer(); result.pageCount = this.pageCount; return result; } } final Device device; final GC gc; final PageDecoration header; final int headerGap; // pixels final PrintIterator body; final int footerGap; // pixels final PageDecoration footer; final PageNumberer numberer; final Point minimumSize; final Point preferredSize; PageIterator(PagePrint print, Device device, GC gc) { this.device = device; this.gc = gc; Point dpi = device.getDPI(); body = print.body.iterator(device, gc); header = print.header; headerGap = header == null ? 0 : print.headerGap * dpi.y / 72; footer = print.footer; footerGap = footer == null ? 0 : print.footerGap * dpi.y / 72; this.numberer = new PageNumberer(); this.minimumSize = computeSize(PrintSizeStrategy.MINIMUM); this.preferredSize = computeSize(PrintSizeStrategy.PREFERRED); } PageIterator(PageIterator that) { this.device = that.device; this.gc = that.gc; this.body = that.body.copy(); this.header = that.header; this.headerGap = that.headerGap; this.footer = that.footer; this.footerGap = that.footerGap; // FIXME: Wrapping PagePrint in a class with space-optimizing semantics // (ColumnPrint) can fork the total // page count. i.e. if the copied PageIterator is chosen as the optimal // layout, then previous pages will // have a page number spawned from a different page numberer. Thus the // total page count for those // previous pages will no longer be incremented with each new page. this.numberer = that.numberer.copy(); this.pageNumber = that.pageNumber; this.minimumSize = that.minimumSize; this.preferredSize = that.preferredSize; } private Point computeSize(PrintSizeStrategy strategy) { Point size = strategy.computeSize(body); PageNumber samplePageNumber = new PageNumber() { public int getPageCount() { return 1; } public int getPageNumber() { return 0; } }; if (header != null) { Print headerPrint = header.createPrint(samplePageNumber); if (headerPrint != null) { PrintIterator iter = headerPrint.iterator(device, gc); size.y += headerGap; Point headerSize = strategy.computeSize(iter); size.x = Math.max(size.x, headerSize.x); size.y += headerSize.y; } } if (footer != null) { Print footerPrint = footer.createPrint(samplePageNumber); if (footerPrint != null) { PrintIterator iter = footerPrint.iterator(device, gc); size.y += footerGap; Point footerSize = strategy.computeSize(iter); size.x = Math.max(size.x, footerSize.x); size.y += footerSize.y; } } return size; } public boolean hasNext() { return body.hasNext(); } public Point minimumSize() { return new Point(minimumSize.x, minimumSize.y); } public Point preferredSize() { return new Point(preferredSize.x, preferredSize.y); } public PrintPiece next(int width, final int height) { PageNumber pageNumber = getCurrentPageNumber(); // HEADER PrintPiece headerPiece = null; int availableHeight = height; if (header != null) { Print headerPrint = header.createPrint(pageNumber); if (headerPrint != null) { headerPiece = getDecorationPrintPiece(headerPrint, width, availableHeight); if (headerPiece == null) return null; availableHeight -= (heightOf(headerPiece) + headerGap); } } // FOOTER PrintPiece footerPiece = null; if (footer != null) { Print footerPrint = footer.createPrint(pageNumber); if (footerPrint != null) { footerPiece = getDecorationPrintPiece(footerPrint, width, availableHeight); if (footerPiece == null) { PaperClipsUtil.dispose(headerPiece); return null; } availableHeight -= (heightOf(footerPiece) + footerGap); } } // BODY PrintPiece bodyPiece = PaperClips.next(body, width, availableHeight); if (bodyPiece == null) { PaperClipsUtil.dispose(headerPiece, footerPiece); return null; } PrintPiece result = createResult(height, headerPiece, bodyPiece, footerPiece); advancePageNumber(); return result; } private int heightOf(PrintPiece piece) { return piece.getSize().y; } PageNumber pageNumber; private void advancePageNumber() { // Null the pageNumber field so the next iteration advances to the next // page. pageNumber = null; } private PageNumber getCurrentPageNumber() { if (pageNumber == null) pageNumber = numberer.next(); return pageNumber; } private PrintPiece createResult(int height, PrintPiece headerPiece, PrintPiece bodyPiece, PrintPiece footerPiece) { if (headerPiece == null && footerPiece == null) return bodyPiece; List entries = new ArrayList(); if (headerPiece != null) entries.add(createEntry(headerPiece, 0)); int y = headerPiece == null ? 0 : heightOf(headerPiece) + headerGap; entries.add(createEntry(bodyPiece, y)); if (footerPiece != null) { y = height - heightOf(footerPiece); entries.add(createEntry(footerPiece, y)); } return new CompositePiece(entries); } private CompositeEntry createEntry(PrintPiece piece, int y) { return new CompositeEntry(piece, new Point(0, y)); } private PrintPiece getDecorationPrintPiece(Print decoration, int width, int height) { PrintIterator iterator = decoration.iterator(device, gc); PrintPiece piece = PaperClips.next(iterator, width, height); if (piece == null) return null; if (iterator.hasNext()) { piece.dispose(); return null; } return piece; } public PrintIterator copy() { return new PageIterator(this); } }