/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ package org.lobobrowser.primary.gui.pdf; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Rectangle; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import javax.swing.JPanel; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.sun.pdfview.PDFPage; /** * A Swing-based panel that displays a PDF page image. If the zoom tool is in * use, allows the user to select a particular region of the image to be zoomed. */ public class PagePanel extends JPanel implements ImageObserver, MouseListener, MouseMotionListener { /** The Constant logger. */ private static final Logger logger = LogManager.getLogger(PagePanel.class); /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The image of the rendered PDF page being displayed. */ Image currentImage; /** The current PDFPage that was rendered into currentImage. */ PDFPage currentPage; /** The current xform. */ /* the current transform from device space to page space */ AffineTransform currentXform; /** The horizontal offset of the image from the left edge of the panel. */ int offx; /** The vertical offset of the image from the top of the panel. */ int offy; /** the current clip, in device space. */ Rectangle2D clip; /** the clipping region used for the image. */ Rectangle2D prevClip; /** the size of the image. */ Dimension prevSize; /** the zooming marquee. */ Rectangle zoomRect; /** whether the zoom tool is enabled. */ boolean useZoom = false; // /** a listener for page changes */ // PageChangeListener listener; /** a flag indicating whether the current page is done or not. */ Flag flag = new Flag(); // Color boxcolor= new Color(255,200,200); /** * Create a new PagePanel, with a default size of 800 by 600 pixels. */ public PagePanel() { super(); setPreferredSize(new Dimension(800, 600)); setFocusable(true); addMouseListener(this); addMouseMotionListener(this); } /** * Stop the generation of any previous page, and draw the new one. * * @param page * the PDFPage to draw. */ public synchronized void showPage(PDFPage page) { // stop drawing the previous page if (currentPage != null && prevSize != null) { currentPage.stop(prevSize.width, prevSize.height, prevClip); } // set up the new page currentPage = page; if (page == null) { // no page currentImage = null; clip = null; currentXform = null; repaint(); } else { // start drawing -- clear the flag to indicate we're in progress. flag.clear(); Dimension sz = getSize(); if (sz.width + sz.height == 0) { // no image to draw. return; } // calculate the clipping rectangle in page space from the // desired clip in screen space. Rectangle2D useClip = clip; if (clip != null && currentXform != null) { useClip = currentXform.createTransformedShape(clip).getBounds2D(); } Dimension pageSize = page.getUnstretchedSize(sz.width, sz.height, useClip); // get the new image currentImage = page.getImage(pageSize.width, pageSize.height, useClip, this); // calculate the transform from screen to page space currentXform = page.getInitialTransform(pageSize.width, pageSize.height, useClip); try { currentXform = currentXform.createInverse(); } catch (NoninvertibleTransformException nte) { logger.log(Level.ERROR, nte); } prevClip = useClip; prevSize = pageSize; repaint(); } } /** * @deprecated */ @Deprecated public synchronized void flush() { // images.clear(); // lruPages.clear(); // nextPage= null; // nextImage= null; } /** * Draw the image. */ @Override public void paint(Graphics g) { Dimension sz = getSize(); g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); if (currentImage == null) { // No image -- draw an empty box // [[MW: remove the scary red X]] // g.setColor(Color.red); // g.drawLine(0, 0, getWidth(), getHeight()); // g.drawLine(0, getHeight(), getWidth(), 0); g.setColor(Color.black); g.drawString("No page selected", getWidth() / 2 - 30, getHeight() / 2); } else { // draw the image int imwid = currentImage.getWidth(null); int imhgt = currentImage.getHeight(null); // draw it centered within the panel offx = (sz.width - imwid) / 2; offy = (sz.height - imhgt) / 2; if ((imwid == sz.width && imhgt <= sz.height) || (imhgt == sz.height && imwid <= sz.width)) { g.drawImage(currentImage, offx, offy, this); } else { // the image is bogus. try again, or give up. flush(); if (currentPage != null) { showPage(currentPage); } g.setColor(Color.red); g.drawLine(0, 0, getWidth(), getHeight()); g.drawLine(0, getHeight(), getWidth(), 0); } } // draw the zoomrect if there is one. if (zoomRect != null) { g.setColor(Color.red); g.drawRect(zoomRect.x, zoomRect.y, zoomRect.width, zoomRect.height); } // debugging: draw a rectangle around the portion that just changed. // g.setColor(boxColor); // Rectangle r= g.getClipBounds(); // g.drawRect(r.x, r.y, r.width-1, r.height-1); } /** * Gets the page. * * @return the page */ public PDFPage getPage() { return currentPage; } /** * Gets the cur size. * * @return the cur size */ public Dimension getCurSize() { return prevSize; } /** * Gets the cur clip. * * @return the cur clip */ public Rectangle2D getCurClip() { return prevClip; } /** * Waits until the page is either complete or had an error. */ public void waitForCurrentPage() { flag.waitForFlag(); } /** * Handles notification of the fact that some part of the image changed. * Repaints that portion. * * @return true if more updates are desired. */ @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { if ((infoflags & (SOMEBITS | ALLBITS)) != 0) { // [[MW: dink this rectangle by 1 to handle antialias issues]] repaint(x + offx, y + offy, width, height); } if ((infoflags & (ALLBITS | ERROR | ABORT)) != 0) { flag.set(); return false; } else { return true; } } // public void addPageChangeListener(PageChangeListener pl) { // listener= pl; // } // public void removePageChangeListener(PageChangeListener pl) { // listener= null; // } /** * Turns the zoom tool on or off. If on, mouse drags will draw the zooming * marquee. If off, mouse drags are ignored. */ public void useZoomTool(boolean use) { useZoom = use; } /** * Sets the current clip, in device space. * * @param clip * the new current clip, in device space */ public void setClip(Rectangle2D clip) { this.clip = clip; showPage(currentPage); } /** x location of the mouse-down event. */ int downx; /** y location of the mouse-down event. */ int downy; /** Handles a mousePressed event */ @Override public void mousePressed(MouseEvent evt) { downx = evt.getX(); downy = evt.getY(); } /** * Handles a mouseReleased event. If zooming is turned on and there's a * valid zoom rectangle, set the image clip to the zoom rect. */ @Override public void mouseReleased(MouseEvent evt) { // calculate new clip if (!useZoom || zoomRect == null || zoomRect.width == 0 || zoomRect.height == 0) { zoomRect = null; return; } setClip(new Rectangle2D.Double(zoomRect.x - offx, zoomRect.y - offy, zoomRect.width, zoomRect.height)); zoomRect = null; } @Override public void mouseClicked(MouseEvent evt) { } @Override public void mouseEntered(MouseEvent evt) { } @Override public void mouseExited(MouseEvent evt) { } @Override public void mouseMoved(MouseEvent evt) { } /** * Handles a mouseDragged event. Constrains the zoom rect to the aspect * ratio of the panel unless the shift key is down. */ @Override public void mouseDragged(MouseEvent evt) { if (useZoom) { int x = evt.getX(); int y = evt.getY(); int dx = Math.abs(x - downx); int dy = Math.abs(y - downy); // constrain to the aspect ratio of the panel if ((evt.getModifiers() & InputEvent.SHIFT_MASK) == 0) { float aspect = (float) dx / (float) dy; float waspect = (float) getWidth() / (float) getHeight(); if (aspect > waspect) { dy = (int) (dx / waspect); } else { dx = (int) (dy * waspect); } } if (x < downx) { x = downx - dx; } if (y < downy) { y = downy - dy; } Rectangle old = zoomRect; // ignore small rectangles if (dx < 5 || dy < 5) { zoomRect = null; } else { zoomRect = new Rectangle(Math.min(downx, x), Math.min(downy, y), dx, dy); } // calculate the repaint region. Should be the union of the // old zoom rect and the new one, with an extra pixel on the // bottom and right because of the way rectangles are drawn. if (zoomRect != null) { if (old != null) { old.add(zoomRect); } else { old = new Rectangle(zoomRect); } } if (old != null) { old.width++; old.height++; } if (old != null) { repaint(old); } } } }