Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.icepdf.ri.common.views.AbstractPageViewComponent Maven / Gradle / Ivy
/*
* Copyright 2006-2017 ICEsoft Technologies Canada Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS
* IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.icepdf.ri.common.views;
import org.icepdf.core.events.PaintPageEvent;
import org.icepdf.core.events.PaintPageListener;
import org.icepdf.core.pobjects.Page;
import org.icepdf.core.pobjects.PageTree;
import org.icepdf.core.util.*;
import org.icepdf.ri.common.views.listeners.DefaultPageViewLoadingListener;
import org.icepdf.ri.common.views.listeners.PageViewLoadingListener;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.lang.ref.SoftReference;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class contains all the functionality for showing a pages content. This view works closely with the clip
* provided by a parent JScrollPane component to optimize memory usage. Page content is painted to a back buffer
* which is painted by the component when ready. The back buffer is scaled on subsequent paints to show content and
* is later replaced with a new buffer that is painted with the current page properties.
*/
public abstract class AbstractPageViewComponent
extends JLayeredPane
implements PageViewComponent {
private static final Logger logger =
Logger.getLogger(AbstractPageViewComponent.class.toString());
protected static final int PAGE_BOUNDARY_BOX = Page.BOUNDARY_CROPBOX;
private static Color pageColor;
protected static int pageBufferPadding = 250;
protected static boolean progressivePaint = true;
static {
try {
String color = Defs.sysProperty(
"org.icepdf.core.views.page.paper.color", "#FFFFFF");
int colorValue = ColorUtil.convertColor(color);
pageColor =
new Color(colorValue >= 0 ? colorValue :
Integer.parseInt("FFFFFF", 16));
} catch (NumberFormatException e) {
logger.warning("Error reading page paper color.");
}
// buffer size padding in pixels
pageBufferPadding = Defs.intProperty("org.icepdf.core.views.bufferpadding", 250);
// progressive paint of first page loat.
progressivePaint = Defs.booleanProperty("org.icepdf.core.views.page.progressivePaint", true);
}
// flags for painting annotations and text highlights.
protected boolean paintAnnotations = true;
protected boolean paintSearchHighlight = false;
// view mvc parents
protected DocumentView parentDocumentView;
protected DocumentViewModel documentViewModel;
protected DocumentViewController documentViewController;
// scrollPane is very important for optimization of multiple page views.
protected JScrollPane parentScrollPane;
protected PageTree pageTree;
protected int pageIndex;
// page properties for a given view state.
protected Rectangle pageSize;
protected float pageZoom, pageRotation;
protected int pageBoundaryBox;
protected PageBufferStore pageBufferStore;
// systems graphics configuration for creating a pages back buffer.
protected GraphicsConfiguration graphicsConfiguration;
// Main worker task.
protected FutureTask pageImageCaptureTask;
public AbstractPageViewComponent(DocumentViewModel documentViewModel, PageTree pageTree,
final int pageIndex, JScrollPane parentScrollPane, int width, int height) {
// needed to propagate mouse events.
this.documentViewModel = documentViewModel;
this.parentScrollPane = parentScrollPane;
this.pageTree = pageTree;
this.pageIndex = pageIndex;
// current state.
if (documentViewModel != null) {
pageZoom = documentViewModel.getViewZoom();
pageRotation = documentViewModel.getViewRotation();
pageBoundaryBox = documentViewModel.getPageBoundary();
} else {
pageZoom = 1.0f;
pageRotation = 0;
pageBoundaryBox = PAGE_BOUNDARY_BOX;
}
// grab a reference to the graphics configuration via the AWT thread, if we get it on the worker thread
// it sometimes return null.
graphicsConfiguration = parentScrollPane.getGraphicsConfiguration();
// setup the store for the pageBufferPadding and current clip
pageBufferStore = new PageBufferStore();
// initialize page size
pageSize = new Rectangle();
if (documentViewModel != null && width == 0 && height == 0) {
calculatePageSize(pageSize, documentViewModel.getViewRotation(), documentViewModel.getViewZoom());
} else {
pageSize.setSize(width, height);
}
}
public Dimension getPreferredSize() {
return pageSize.getSize();
}
public Dimension getSize() {
return pageSize.getSize();
}
public void clearSelectedText() {
// on mouse click clear the currently selected sprints
Page currentPage = getPage();
// clear selected text.
if (currentPage.isInitiated()) {
try {
if (currentPage.getViewText() != null) {
currentPage.getViewText().clearSelected();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Sets the text that is contained in the specified rectangle and the
* given mouse pointer. The cursor and selection rectangle must be in
* in page space.
*
* @param cursorLocation location of cursor or mouse.
* @param selection rectangle of text to include in selection.
*/
public void setSelectionRectangle(Point cursorLocation, Rectangle selection) {
}
/**
* Clear any internal data structures that represent selected text and
* repaint the component.
*/
public void clearSelectionRectangle() {
}
public int getPageIndex() {
return pageIndex;
}
public Page getPage() {
return pageTree.getPage(pageIndex);
}
public void setDocumentViewCallback(DocumentView parentDocumentView) {
this.parentDocumentView = parentDocumentView;
documentViewController = this.parentDocumentView.getParentViewController();
}
public static boolean isAnnotationTool(final int displayTool) {
return displayTool == DocumentViewModel.DISPLAY_TOOL_SELECTION ||
displayTool == DocumentViewModel.DISPLAY_TOOL_LINK_ANNOTATION ||
displayTool == DocumentViewModel.DISPLAY_TOOL_HIGHLIGHT_ANNOTATION ||
displayTool == DocumentViewModel.DISPLAY_TOOL_SQUIGGLY_ANNOTATION ||
displayTool == DocumentViewModel.DISPLAY_TOOL_STRIKEOUT_ANNOTATION ||
displayTool == DocumentViewModel.DISPLAY_TOOL_UNDERLINE_ANNOTATION;
}
/**
* Called from parent controls when a UI control has manipulated the view, property
* change is picked up and the view is updated accordingly. Responds to
* PropertyConstants.DOCUMENT_VIEW_ROTATION_CHANGE and
* PropertyConstants.DOCUMENT_VIEW_ZOOM_CHANGE. If the worker is currently working
* is is cancel with interrupts.
*
* @param propertyConstant document view change property.
* @param oldValue old value
* @param newValue new value
*/
public void updateView(String propertyConstant, Object oldValue, Object newValue) {
if (pageImageCaptureTask != null && !pageImageCaptureTask.isDone()) {
pageImageCaptureTask.cancel(true);
}
if (PropertyConstants.DOCUMENT_VIEW_ROTATION_CHANGE.equals(propertyConstant)) {
pageRotation = (Float) newValue;
} else if (PropertyConstants.DOCUMENT_VIEW_ZOOM_CHANGE.equals(propertyConstant)) {
pageZoom = (Float) newValue;
} else if (PropertyConstants.DOCUMENT_VIEW_REFRESH_CHANGE.equals(propertyConstant)) {
// nothing to do but repaint
} else if (PropertyConstants.DOCUMENT_VIEW_DEMO_MODE_CHANGE.equals(propertyConstant)) {
// re-initialized the page.
pageBufferStore.setDirty(true);
Page page = getPage();
page.getLibrary().disposeFontResources();
page.resetInitializedState();
}
calculatePageSize(pageSize, pageRotation, pageZoom);
pageBufferStore.setDirty(true);
}
/**
* Checks if this page intersects the viewport
*
* @return true if page is visible in viewport, false otherwise.
* @throws NullPointerException if the parent scrollPane is null.
*/
private boolean isPageIntersectViewport() {
Rectangle pageBounds = documentViewModel != null ? documentViewModel.getPageBounds(pageIndex) : getBounds();
return pageBounds != null && this.isShowing() &&
pageBounds.intersects(parentScrollPane.getViewport().getViewRect());
}
/**
* Calculates the page size for the rotation and zoom. The new values are assigned to the pageSize.
*
* @param pageSize rectangle to update, new rectangle will not be created.
* @param rotation rotation of page.
* @param zoom zoom of page
*/
protected void calculatePageSize(Rectangle pageSize, float rotation, float zoom) {
if (pageTree != null) {
Page currentPage = pageTree.getPage(pageIndex);
if (currentPage != null) {
pageSize.setSize(currentPage.getSize(pageBoundaryBox,
rotation, zoom).toDimension());
}
}
}
@Override
protected void paintComponent(Graphics g) {
// create a copy so we can set our own state with out affecting the parent graphics conttent.
Graphics2D g2d = (Graphics2D) g.create(0, 0, pageSize.width, pageSize.height);
GraphicsRenderingHints grh = GraphicsRenderingHints.getDefault();
g2d.setRenderingHints(grh.getRenderingHints(GraphicsRenderingHints.SCREEN));
// page location in the the entire view.
calculateBufferLocation();
// paint the paper
g2d.setColor(pageColor);
g2d.fillRect(0, 0, pageSize.width, pageSize.height);
// paint the pageBufferPadding, but get the latest copy encase it was returned extra quick
BufferedImage pageImage = pageBufferStore.getImageReference();
if (pageImage != null) {
Rectangle paintingClip = pageBufferStore.getImageLocation();
// check if we should scale and rotate the current capture
if (pageZoom != pageBufferStore.getPageZoom() ||
pageRotation != pageBufferStore.getPageRotation()) {
g2d.transform(calculateBufferAffineTransform());
pageBufferStore.setDirty(true);
}
g2d.drawImage(pageImage, paintingClip.x, paintingClip.y, null);
}
g2d.dispose();
}
/**
* Calculates where we should be painting the new buffer and kicks off the the worker if the buffer
* is deemed dirty. The Parent scrollpane viewport is taken into account to setup the clipping.
*/
protected void calculateBufferLocation() {
// page location in the the entire view.
Rectangle pageLocation = documentViewModel != null ?
documentViewModel.getPageBounds(pageIndex) : new Rectangle(pageSize);
Rectangle viewPort = parentScrollPane.getViewport().getViewRect();
Rectangle imageLocation;
Rectangle imageClipLocation;
if (pageLocation.width < viewPort.width || pageLocation.height < viewPort.height) {
// if page is smaller then viewport then we use the full page size.
imageLocation = new Rectangle(0, 0, pageLocation.width, pageLocation.height);
imageClipLocation = new Rectangle(imageLocation);
} else {
// otherwise we create a pageBufferPadding based on the viewport size plus some padding
imageClipLocation = viewPort.intersection(pageLocation);
// move the clip relative to page coordinates
imageClipLocation.setLocation(
imageClipLocation.x - pageLocation.x, imageClipLocation.y - pageLocation.y);
// we want the image to be a bit bigger to make scrolling look a little smoother.
imageLocation = new Rectangle(imageClipLocation.x - pageBufferPadding,
imageClipLocation.y - pageBufferPadding,
imageClipLocation.width + pageBufferPadding * 2,
imageClipLocation.height + pageBufferPadding * 2);
// we're using the AWT thread to check for scroll repaints,
if (pageImageCaptureTask != null && pageBufferStore.getImageLocation() != null) {
Rectangle imageAbsoluteLocation = new Rectangle(pageBufferStore.getImageLocation());
imageAbsoluteLocation.setLocation(imageAbsoluteLocation.x + pageLocation.x,
imageAbsoluteLocation.y + pageLocation.y);
if (!imageAbsoluteLocation.contains(viewPort.intersection(pageLocation))) {
pageBufferStore.setDirty(true);
}
}
}
// check if we need create or refresh the back pageBufferPadding.
if (pageBufferStore.isDirty() || pageBufferStore.getImageReference() == null) {
// start future task to paint back pageBufferPadding
if (pageImageCaptureTask == null || pageImageCaptureTask.isDone() || pageImageCaptureTask.isCancelled()) {
pageImageCaptureTask = new FutureTask(
new PageImageCaptureTask(this, imageLocation, imageClipLocation,
pageZoom,
pageRotation));
Library.execute(pageImageCaptureTask);
}
}
}
/**
* Calculates the affine transform that paints the old buffered image using the current scale and rotation. This
* avoid the back buffer flicker. Once the worker captures the new buffer we swap in the new buffer.
* todo, still needs some work with regards to rotation of the buffer.
*
* @return transform needed to paint the previous out of sync buffer in the correct place.
*/
private AffineTransform calculateBufferAffineTransform() {
AffineTransform at = new AffineTransform();
if (pageZoom != pageBufferStore.getPageZoom()) {
double pageScale = pageZoom / (double) pageBufferStore.getPageZoom();
at.scale(pageScale, pageScale);
}
// get the page size of the currently painted image we are trying to scale or rotate.
if (pageRotation != pageBufferStore.getPageRotation()) {
double rotation = 0;
rotation = pageBufferStore.getPageRotation() - pageRotation;
if (rotation < 0) {
rotation += 360;
}
Rectangle imageLocation = pageBufferStore.getPageSize();
if (rotation == 90) {
at.translate(imageLocation.height, 0);
} else if (rotation == 180) {
at.translate(imageLocation.width, 0);
} else if (rotation == 270) {
at.translate(imageLocation.height, -imageLocation.width);
}
double theta = rotation * Math.PI / 180.0;
at.rotate(theta);
}
return at;
}
/**
* The worker of any successful page paint. The worker takes a snapshot of the given page state
* and paint the desired image to buffer. One completed the the new buffer is stuffed into
* the pageBufferStore instance with properties so that it can be painted in the correct thread
* when the component is repainted.
*/
public class PageImageCaptureTask implements Callable, PaintPageListener {
private float zoom;
private float rotation;
private Rectangle imageLocation;
private Rectangle imageClipLocation;
private JComponent parent;
public PageImageCaptureTask(JComponent parent, Rectangle imageLocation, Rectangle imageClipLocation,
float zoom, float rotation) {
this.zoom = zoom;
this.rotation = rotation;
this.parent = parent;
this.imageLocation = imageLocation;
this.imageClipLocation = imageClipLocation;
}
public Object call() throws Exception {
if (!isPageIntersectViewport()) {
pageTeardownCallback();
return null;
}
// paint page.
Page page = pageTree.getPage(pageIndex);
// page loading progress
PageViewLoadingListener pageLoadingListener = new DefaultPageViewLoadingListener(parent, documentViewController);
boolean isFirstProgressivePaint = false;
try {
if (documentViewController != null) page.addPageProcessingListener(pageLoadingListener);
// page init, interruptible
page.init();
pageInitializedCallback(page);
Rectangle pageSize = new Rectangle();
calculatePageSize(pageSize, rotation, zoom);
BufferedImage pageBufferImage = graphicsConfiguration.createCompatibleImage(
imageLocation.width, imageLocation.height,
BufferedImage.TYPE_INT_ARGB);
Graphics g2d = pageBufferImage.createGraphics();
// if we don't have a soft reference then we are likely on a first clean paint at which
// point we can kick off the animated paint.
if (progressivePaint && pageBufferStore.getImageReference() == null) {
page.addPaintPageListener(this);
isFirstProgressivePaint = true;
pageBufferStore.setState(pageBufferImage, imageLocation, imageClipLocation, pageSize,
zoom, rotation, true);
}
g2d.setClip(0, 0, imageLocation.width, imageLocation.height);
g2d.translate(-imageLocation.x, -imageLocation.y);
// paint page interruptible
page.paint(g2d, GraphicsRenderingHints.SCREEN, pageBoundaryBox, rotation, zoom,
paintAnnotations, paintSearchHighlight);
g2d.dispose();
// init and paint thread went under interrupted, we can move the back pageBufferPadding to the front.
pageBufferStore.setState(pageBufferImage, imageLocation, imageClipLocation, pageSize,
zoom, rotation, false);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.finer("Interrupted page capture task: " + e.getMessage() + " " + pageIndex);
// flush the buffer if this is our first paint.
if (isFirstProgressivePaint) pageBufferStore.setImageReference(null);
} catch (Throwable e) {
logger.log(Level.WARNING, "Error during page capture task: " + e.getMessage() + " " + pageIndex, e);
// avoid a repaint as we'll likely get caught in an infinite loop.
} finally {
page.removePaintPageListener(this);
page.removePageProcessingListener(pageLoadingListener);
}
// queue a repaint, regardless of outcome
SwingUtilities.invokeLater(new Runnable() {
public void run() {
repaint();
}
});
notifyAll();
return null;
}
public void paintPage(PaintPageEvent event) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
repaint();
}
});
}
}
/**
* Synchronized page buffer property store, insures that a page capture occurs using the correct properties.
*/
protected class PageBufferStore {
// last page buffer store,
private SoftReference imageReference;
// paint location if buffer is clipped to be smaller then the page size.
private Rectangle imageLocation;
// location of the current clip, generally the viewport intersection with the page bounds.
private Rectangle imageClipLocation;
private float pageZoom, pageRotation;
// page size at the given zoom and location.
private Rectangle pageSize;
// dirty flag.
private boolean isDirty;
private final Object objectLock = new Object();
PageBufferStore() {
imageReference = new SoftReference(null);
}
void setState(BufferedImage pageBufferImage, Rectangle imageLocation, Rectangle imageClipLocation,
Rectangle pageSize, float pageZoom, float pageRotation, boolean isDirty) {
synchronized (objectLock) {
this.imageReference = new SoftReference(pageBufferImage);
this.imageLocation = imageLocation;
this.imageClipLocation = imageClipLocation;
this.pageSize = pageSize;
this.pageZoom = pageZoom;
this.pageRotation = pageRotation;
this.isDirty = isDirty;
}
}
void setImageReference(BufferedImage bufferedImage) {
synchronized (objectLock) {
this.imageReference = new SoftReference(bufferedImage);
}
}
public BufferedImage getImageReference() {
synchronized (objectLock) {
return imageReference.get();
}
}
Rectangle getImageLocation() {
synchronized (objectLock) {
return imageLocation;
}
}
Rectangle getImageClipLocation() {
synchronized (objectLock) {
return imageClipLocation;
}
}
Rectangle getPageSize() {
synchronized (objectLock) {
return pageSize;
}
}
float getPageZoom() {
synchronized (objectLock) {
return pageZoom;
}
}
float getPageRotation() {
synchronized (objectLock) {
return pageRotation;
}
}
public boolean isDirty() {
synchronized (objectLock) {
return isDirty;
}
}
public void setDirty(boolean dirty) {
synchronized (objectLock) {
this.isDirty = dirty;
}
}
}
}