/*
* Copyright (c) 1998-2017 by Richard A. Wilkes. All rights reserved.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, version 2.0. If a copy of the MPL was not distributed with
* this file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, version 2.0.
*/
package com.trollworks.gcs.pdfview;
import com.trollworks.toolkit.ui.GraphicsUtilities;
import com.trollworks.toolkit.ui.UIUtilities;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import javax.swing.JPanel;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDPageLabels;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
/** A panel that will display a single page of a PDF. */
public class PdfPanel extends JPanel implements KeyListener, MouseListener, Scrollable {
public static final float[] SCALES = { 0.33f, 0.5f, 0.75f, 1f, 1.25f, 1.5f, 1.75f, 2f };
private PdfDockable mOwner;
private PDDocument mPdf;
private int mPageIndex;
private int mScaleIndex = Arrays.binarySearch(SCALES, 1f);
private String mHighlight;
private BufferedImage mImg;
private int mWidth;
private int mHeight;
private boolean mNeedLoad;
private boolean mIgnorePageChange;
public PdfPanel(PdfDockable owner, PDDocument pdf, PdfRef pdfRef, int page, String highlight) {
mOwner = owner;
mPdf = pdf;
setFocusable(true);
addMouseListener(this);
addKeyListener(this);
goToPage(pdfRef, page, highlight);
}
public void goToPage(PdfRef pdfRef, int page, String highlight) {
if (!mIgnorePageChange && mPdf != null) {
int lastPageIndex = mPageIndex;
mPageIndex = page;
try {
PDPageLabels pageLabels = mPdf.getDocumentCatalog().getPageLabels();
if (pageLabels != null) {
Integer result = pageLabels.getPageIndicesByLabels().get(Integer.toString(page));
if (result != null) {
mPageIndex = result.intValue();
}
}
} catch (Exception exception) {
// Had no catalog... we will just use the original page number
}
mPageIndex += pdfRef.getPageToIndexOffset();
if (mPageIndex != lastPageIndex || isHighlightNew(highlight)) {
mHighlight = highlight;
markPageForLoading();
}
}
}
private boolean isHighlightNew(String highlight) {
return mHighlight == null ? highlight != null : !mHighlight.equals(highlight);
}
public int goToPageIndex(int pageIndex, String highlight) {
if (!mIgnorePageChange && mPdf != null && (mPageIndex != pageIndex || isHighlightNew(highlight)) && pageIndex >= 0 && pageIndex < mPdf.getNumberOfPages()) {
mPageIndex = pageIndex;
mHighlight = highlight;
markPageForLoading();
}
return mPageIndex;
}
public void previousPage() {
if (!mIgnorePageChange && mPdf != null && mPageIndex > 0) {
mPageIndex--;
mHighlight = null;
markPageForLoading();
}
}
public void nextPage() {
if (!mIgnorePageChange && mPdf != null && mPageIndex < mPdf.getNumberOfPages()) {
mPageIndex++;
mHighlight = null;
markPageForLoading();
}
}
public void zoomIn() {
if (mPdf != null && mScaleIndex < SCALES.length - 1) {
mScaleIndex++;
markPageForLoading();
}
}
public void zoomOut() {
if (mPdf != null && mScaleIndex > 0) {
mScaleIndex--;
markPageForLoading();
}
}
public void actualSize() {
if (mPdf != null) {
int actualSizeIndex = Arrays.binarySearch(SCALES, 1f);
if (actualSizeIndex != mScaleIndex) {
mScaleIndex = actualSizeIndex;
markPageForLoading();
}
}
}
private void markPageForLoading() {
int numberOfPages = mPdf.getNumberOfPages();
if (mPageIndex >= 0 && mPageIndex == numberOfPages) {
mPageIndex = numberOfPages - 1;
}
if (mPageIndex >= 0 && mPageIndex < numberOfPages) {
PDPage page = mPdf.getPage(mPageIndex);
PDRectangle cropBox = page.getCropBox();
float scale = SCALES[mScaleIndex] * Toolkit.getDefaultToolkit().getScreenResolution();
mWidth = (int) Math.ceil(cropBox.getWidth() / 72 * scale);
mHeight = (int) Math.ceil(cropBox.getHeight() / 72 * scale);
mImg = null;
mNeedLoad = true;
Dimension size = new Dimension(mWidth, mHeight);
UIUtilities.setOnlySize(this, size);
setSize(size);
repaint();
mIgnorePageChange = true;
mOwner.updateStatus(mPageIndex, numberOfPages, SCALES[mScaleIndex]);
mIgnorePageChange = false;
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (mNeedLoad && mPdf != null) {
mImg = PdfRenderer.create(mPdf, mPageIndex, SCALES[mScaleIndex] * (GraphicsUtilities.isRetinaDisplay(g) ? 2 : 1), mHighlight);
mNeedLoad = false;
}
if (mImg != null) {
g.drawImage(mImg, 0, 0, mWidth, mHeight, this);
}
}
@Override
public void mouseClicked(MouseEvent event) {
requestFocusInWindow();
}
@Override
public void mousePressed(MouseEvent event) {
// Unused
}
@Override
public void mouseReleased(MouseEvent event) {
// Unused
}
@Override
public void mouseEntered(MouseEvent event) {
// Unused
}
@Override
public void mouseExited(MouseEvent event) {
// Unused
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return getPreferredSize();
}
@Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 16;
}
@Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return Math.max(orientation == SwingConstants.HORIZONTAL ? visibleRect.width - 5 : visibleRect.height - 5, 5);
}
@Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
@Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
@Override
public void keyTyped(KeyEvent event) {
if (!event.isConsumed()) {
char ch = event.getKeyChar();
switch (ch) {
case '-':
zoomOut();
break;
case '=':
zoomIn();
break;
case '1':
actualSize();
break;
default:
return;
}
event.consume();
}
}
@Override
public void keyPressed(KeyEvent event) {
if (!event.isConsumed()) {
switch (event.getKeyCode()) {
case KeyEvent.VK_UP:
previousPage();
break;
case KeyEvent.VK_DOWN:
nextPage();
break;
default:
return;
}
event.consume();
}
}
@Override
public void keyReleased(KeyEvent event) {
// Unused
}
}