/* * Copyright (c) 2006 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; import org.eclipse.nebula.paperclips.core.internal.util.Util; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Region; /** * A wrapper for prints whose minimum size is too large to fit on one page. The * target's content is divided across multiple pages like a spreadsheet. Pages * are printed in order left-to-right, then top-to-bottom. * <p> * <em>Note that this print lays out content under the assumption that every page will have the same * pixel width and height.</em> If a BigPrint is wrapped in a print that * violates this expectation, it is likely that the output will skip and/or * repeat certain portions of the target's content. Some examples of this * behavior: * <ul> * <li>BorderPrint changes the available page height of the target, depending on * whether the top and bottom borders are open or closed. * <li>ColumnPrint often changes the width from column to column, if the total * width is not evenly divisible by the number of columns. * </ul> * * @author Matthew Hall */ public final class BigPrint implements Print { private final Print target; /** * Constructs a BigPrint. * * @param target */ public BigPrint(Print target) { Util.notNull(target); this.target = target; } public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((target == null) ? 0 : target.hashCode()); return result; } public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; BigPrint other = (BigPrint) obj; if (target == null) { if (other.target != null) return false; } else if (!target.equals(other.target)) return false; return true; } /** * Returns the wrapped print which is being split across pages. * * @return the wrapped print which is being split across pages. */ public Print getTarget() { return target; } public PrintIterator iterator(Device device, GC gc) { return new BigIterator(target, device, gc); } } class BigIterator implements PrintIterator { private final PrintIterator target; private final Device device; private PrintPiece currentPiece; private int xOffset; private int yOffset; BigIterator(Print target, Device device, GC gc) { Util.notNull(device, gc, target); this.target = target.iterator(device, gc); this.device = device; currentPiece = null; xOffset = 0; yOffset = 0; } BigIterator(BigIterator that) { this.target = that.target.copy(); this.device = that.device; this.currentPiece = that.currentPiece; this.xOffset = that.xOffset; this.yOffset = that.yOffset; } public Point minimumSize() { return target.minimumSize(); } public Point preferredSize() { return target.preferredSize(); } public boolean hasNext() { return currentPiece != null || target.hasNext(); } // Returns a point whose x and y fields represent the required pages wide // and tall, respectively private Point estimatePagesRequired(int width, int height) { if (width <= 0 || height <= 0) return new Point(0, 0); Point pref = target.preferredSize(); Point prefPages = new Point(pref.x / width, pref.y / height); Point min = target.minimumSize(); // Adding width-1 rounds up page count w/out floating point op // Same goes for adding height-1 Point minPages = new Point(Math.max((min.x + width - 1) / width, 1), Math.max((min.y + height - 1) / height, 1)); return new Point(Math.max(prefPages.x, minPages.x), Math.max( prefPages.y, minPages.y)); } public PrintPiece next(int width, int height) { if (!hasNext()) PaperClips.error("No more content"); //$NON-NLS-1$ if (currentPiece == null) { Point pages = estimatePagesRequired(width, height); currentPiece = PaperClips.next(target, width * pages.x, height * pages.y); if (currentPiece == null) return null; // Iteration fails // Reset the offset for the new piece. xOffset = 0; yOffset = 0; } PrintPiece result = new BigPiece(currentPiece, new Point(width, height), xOffset, yOffset); // Advance cursor on current piece. xOffset += width; if (xOffset >= currentPiece.getSize().x) { xOffset = 0; yOffset += height; } if (yOffset >= currentPiece.getSize().y) { currentPiece = null; } return result; } public PrintIterator copy() { return new BigIterator(this); } } class BigPiece implements PrintPiece { private final PrintPiece target; private final Point size; private final Point offset; BigPiece(PrintPiece target, Point size, int xOffset, int yOffset) { Util.notNull(target, size); this.target = target; this.size = new Point(size.x, size.y); this.offset = new Point(xOffset, yOffset); } public Point getSize() { return new Point(size.x, size.y); } public void paint(GC gc, int x, int y) { // Remember clipping region Region region = new Region(); gc.getClipping(region); // Set clipping region so only the portion of the target we want is // printed. gc.setClipping(x, y, size.x, size.y); // Paint the target. target.paint(gc, x - offset.x, y - offset.y); // Restore clipping region gc.setClipping(region); region.dispose(); } public void dispose() { target.dispose(); } }