564 lines
16 KiB
Java
564 lines
16 KiB
Java
package example;
|
|
|
|
import com.artifex.mupdf.fitz.*;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.*;
|
|
import java.awt.image.*;
|
|
|
|
import java.io.File;
|
|
import java.io.FilenameFilter;
|
|
import java.lang.reflect.Field;
|
|
import java.util.Vector;
|
|
import java.util.Date;
|
|
|
|
public class Viewer extends Frame implements WindowListener, ActionListener, ItemListener, TextListener
|
|
{
|
|
protected Document doc;
|
|
|
|
protected ScrollPane pageScroll;
|
|
protected Panel pageHolder;
|
|
protected ImageCanvas pageCanvas;
|
|
protected Matrix pageCTM;
|
|
|
|
protected Button firstButton, prevButton, nextButton, lastButton;
|
|
protected TextField pageField;
|
|
protected Label pageLabel;
|
|
protected Button zoomInButton, zoomOutButton;
|
|
protected Choice zoomChoice;
|
|
protected Button fontIncButton, fontDecButton;
|
|
protected Label fontSizeLabel;
|
|
|
|
protected TextField searchField;
|
|
protected Button searchPrevButton, searchNextButton;
|
|
protected int searchHitPage = -1;
|
|
protected Quad[] searchHits;
|
|
|
|
protected List outlineList;
|
|
protected Vector<Outline> flatOutline;
|
|
|
|
protected int pageCount;
|
|
protected int pageNumber = 0;
|
|
protected int zoomLevel = 5;
|
|
protected int layoutWidth = 450;
|
|
protected int layoutHeight = 600;
|
|
protected int layoutEm = 12;
|
|
protected float pixelScale;
|
|
|
|
protected static final int[] zoomList = {
|
|
18, 24, 36, 54, 72, 96, 120, 144, 180, 216, 288
|
|
};
|
|
|
|
protected static BufferedImage imageFromPixmap(Pixmap pixmap) {
|
|
int w = pixmap.getWidth();
|
|
int h = pixmap.getHeight();
|
|
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
|
|
image.setRGB(0, 0, w, h, pixmap.getPixels(), 0, w);
|
|
return image;
|
|
}
|
|
|
|
protected static BufferedImage imageFromPage(Page page, Matrix ctm) {
|
|
Rect bbox = page.getBounds().transform(ctm);
|
|
Pixmap pixmap = new Pixmap(ColorSpace.DeviceBGR, bbox, true);
|
|
pixmap.clear(255);
|
|
|
|
DrawDevice dev = new DrawDevice(pixmap);
|
|
page.run(dev, ctm, null);
|
|
dev.close();
|
|
dev.destroy();
|
|
|
|
BufferedImage image = imageFromPixmap(pixmap);
|
|
pixmap.destroy();
|
|
return image;
|
|
}
|
|
|
|
protected static void messageBox(Frame owner, String title, String message) {
|
|
final Dialog box = new Dialog(owner, title, true);
|
|
box.add(new Label(message), BorderLayout.CENTER);
|
|
Panel buttonPane = new Panel(new FlowLayout());
|
|
Button okayButton = new Button("Okay");
|
|
okayButton.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent event) {
|
|
box.setVisible(false);
|
|
}
|
|
});
|
|
buttonPane.add(okayButton);
|
|
box.add(buttonPane, BorderLayout.SOUTH);
|
|
box.pack();
|
|
box.setVisible(true);
|
|
box.dispose();
|
|
}
|
|
|
|
protected static String passwordDialog(Frame owner) {
|
|
final Dialog box = new Dialog(owner, "Password", true);
|
|
final TextField textField = new TextField(20);
|
|
textField.setEchoChar('*');
|
|
Panel buttonPane = new Panel(new FlowLayout());
|
|
Button cancelButton = new Button("Cancel");
|
|
Button okayButton = new Button("Okay");
|
|
cancelButton.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent event) {
|
|
textField.setText("");
|
|
box.setVisible(false);
|
|
}
|
|
});
|
|
okayButton.addActionListener(new ActionListener() {
|
|
public void actionPerformed(ActionEvent event) {
|
|
box.setVisible(false);
|
|
}
|
|
});
|
|
box.add(new Label("Password:"), BorderLayout.NORTH);
|
|
box.add(textField, BorderLayout.CENTER);
|
|
buttonPane.add(cancelButton);
|
|
buttonPane.add(okayButton);
|
|
box.add(buttonPane, BorderLayout.SOUTH);
|
|
box.pack();
|
|
box.setVisible(true);
|
|
box.dispose();
|
|
String pwd = textField.getText();
|
|
if (pwd.length() == 0)
|
|
return null;
|
|
return pwd;
|
|
}
|
|
|
|
protected class ImageCanvas extends Canvas
|
|
{
|
|
protected BufferedImage image;
|
|
|
|
public void setImage(BufferedImage image_) {
|
|
image = image_;
|
|
repaint();
|
|
}
|
|
|
|
public Dimension getPreferredSize() {
|
|
return new Dimension(image.getWidth(), image.getHeight());
|
|
}
|
|
|
|
public void paint(Graphics g) {
|
|
float imageScale = 1 / pixelScale;
|
|
final Graphics2D g2d = (Graphics2D)g.create(0, 0, image.getWidth(), image.getHeight());
|
|
g2d.scale(imageScale, imageScale);
|
|
g2d.drawImage(image, 0, 0, null);
|
|
|
|
if (searchHitPage == pageNumber && searchHits != null && searchHits.length > 0) {
|
|
g2d.setColor(new Color(1, 0, 0, 0.4f));
|
|
for (int i = 0; i < searchHits.length; ++i) {
|
|
Rect hit = new Rect(searchHits[i]).transform(pageCTM);
|
|
g2d.fillRect((int)hit.x0, (int)hit.y0, (int)(hit.x1-hit.x0), (int)(hit.y1-hit.y0));
|
|
}
|
|
}
|
|
|
|
g2d.dispose();
|
|
}
|
|
|
|
public Dimension getMinimumSize() { return getPreferredSize(); }
|
|
public Dimension getMaximumSize() { return getPreferredSize(); }
|
|
public void update(Graphics g) { paint(g); }
|
|
}
|
|
|
|
public Viewer(Document doc_) {
|
|
this.doc = doc_;
|
|
|
|
pixelScale = getRetinaScale();
|
|
setTitle("MuPDF: " + doc.getMetaData(Document.META_INFO_TITLE));
|
|
doc.layout(layoutWidth, layoutHeight, layoutEm);
|
|
pageCount = doc.countPages();
|
|
|
|
Panel rightPanel = new Panel(new BorderLayout());
|
|
{
|
|
Panel toolpane = new Panel(new GridBagLayout());
|
|
{
|
|
GridBagConstraints c = new GridBagConstraints();
|
|
c.fill = GridBagConstraints.HORIZONTAL;
|
|
c.anchor = GridBagConstraints.WEST;
|
|
|
|
Panel toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
|
|
{
|
|
firstButton = new Button("|<");
|
|
firstButton.addActionListener(this);
|
|
prevButton = new Button("<");
|
|
prevButton.addActionListener(this);
|
|
nextButton = new Button(">");
|
|
nextButton.addActionListener(this);
|
|
lastButton = new Button(">|");
|
|
lastButton.addActionListener(this);
|
|
pageField = new TextField(4);
|
|
pageField.addActionListener(this);
|
|
pageLabel = new Label("/ " + pageCount);
|
|
|
|
toolbar.add(firstButton);
|
|
toolbar.add(prevButton);
|
|
toolbar.add(pageField);
|
|
toolbar.add(pageLabel);
|
|
toolbar.add(nextButton);
|
|
toolbar.add(lastButton);
|
|
}
|
|
c.gridy = 0;
|
|
toolpane.add(toolbar, c);
|
|
|
|
toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
|
|
{
|
|
zoomOutButton = new Button("Zoom-");
|
|
zoomOutButton.addActionListener(this);
|
|
zoomInButton = new Button("Zoom+");
|
|
zoomInButton.addActionListener(this);
|
|
|
|
zoomChoice = new Choice();
|
|
for (int i = 0; i < zoomList.length; ++i)
|
|
zoomChoice.add(String.valueOf(zoomList[i]));
|
|
zoomChoice.select(zoomLevel);
|
|
zoomChoice.addItemListener(this);
|
|
|
|
toolbar.add(zoomOutButton);
|
|
toolbar.add(zoomChoice);
|
|
toolbar.add(zoomInButton);
|
|
}
|
|
c.gridy += 1;
|
|
toolpane.add(toolbar, c);
|
|
|
|
if (doc.isReflowable()) {
|
|
toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
|
|
{
|
|
fontDecButton = new Button("Font-");
|
|
fontDecButton.addActionListener(this);
|
|
fontIncButton = new Button("Font+");
|
|
fontIncButton.addActionListener(this);
|
|
fontSizeLabel = new Label(String.valueOf(layoutEm));
|
|
|
|
toolbar.add(fontDecButton);
|
|
toolbar.add(fontSizeLabel);
|
|
toolbar.add(fontIncButton);
|
|
}
|
|
c.gridy += 1;
|
|
toolpane.add(toolbar, c);
|
|
}
|
|
|
|
toolbar = new Panel(new FlowLayout(FlowLayout.LEFT));
|
|
{
|
|
searchField = new TextField(20);
|
|
searchField.addActionListener(this);
|
|
searchField.addTextListener(this);
|
|
searchPrevButton = new Button("<");
|
|
searchPrevButton.addActionListener(this);
|
|
searchNextButton = new Button(">");
|
|
searchNextButton.addActionListener(this);
|
|
|
|
toolbar.add(searchField);
|
|
toolbar.add(searchPrevButton);
|
|
toolbar.add(searchNextButton);
|
|
}
|
|
c.gridy += 1;
|
|
toolpane.add(toolbar, c);
|
|
|
|
}
|
|
rightPanel.add(toolpane, BorderLayout.NORTH);
|
|
|
|
outlineList = new List();
|
|
outlineList.addItemListener(this);
|
|
rightPanel.add(outlineList, BorderLayout.CENTER);
|
|
}
|
|
this.add(rightPanel, BorderLayout.EAST);
|
|
|
|
pageScroll = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED);
|
|
{
|
|
pageHolder = new Panel(new GridBagLayout());
|
|
{
|
|
pageHolder.setBackground(Color.gray);
|
|
pageCanvas = new ImageCanvas();
|
|
pageHolder.add(pageCanvas);
|
|
}
|
|
pageScroll.add(pageHolder);
|
|
}
|
|
this.add(pageScroll, BorderLayout.CENTER);
|
|
|
|
addWindowListener(this);
|
|
|
|
updateOutline();
|
|
updatePageCanvas();
|
|
|
|
pack();
|
|
}
|
|
|
|
protected void addOutline(Outline[] outline, String indent) {
|
|
for (int i = 0; i < outline.length; ++i) {
|
|
Outline node = outline[i];
|
|
if (node.title != null) {
|
|
flatOutline.add(node);
|
|
outlineList.add(indent + node.title);
|
|
}
|
|
if (node.down != null)
|
|
addOutline(node.down, indent + " ");
|
|
}
|
|
}
|
|
|
|
protected void updateOutline() {
|
|
Outline[] outline;
|
|
try {
|
|
outline = doc.loadOutline();
|
|
} catch (Exception ex) {
|
|
outline = null;
|
|
}
|
|
outlineList.removeAll();
|
|
if (outline != null) {
|
|
flatOutline = new Vector<Outline>();
|
|
addOutline(outline, "");
|
|
outlineList.setVisible(true);
|
|
} else {
|
|
outlineList.setVisible(false);
|
|
}
|
|
}
|
|
|
|
protected void updatePageCanvas() {
|
|
pageField.setText(String.valueOf(pageNumber + 1));
|
|
|
|
pageCTM = new Matrix().scale(zoomList[zoomLevel] / 72.0f * pixelScale);
|
|
BufferedImage image = imageFromPage(doc.loadPage(pageNumber), pageCTM);
|
|
pageCanvas.setImage(image);
|
|
|
|
Dimension size = pageHolder.getPreferredSize();
|
|
size.width += 40;
|
|
size.height += 40;
|
|
pageScroll.setPreferredSize(size);
|
|
pageCanvas.invalidate();
|
|
validate();
|
|
}
|
|
|
|
protected void doSearch(int direction) {
|
|
int searchPage;
|
|
if (searchHitPage == -1)
|
|
searchPage = pageNumber;
|
|
else
|
|
searchPage = pageNumber + direction;
|
|
searchHitPage = -1;
|
|
String needle = searchField.getText();
|
|
while (searchPage >= 0 && searchPage < pageCount) {
|
|
Page page = doc.loadPage(searchPage);
|
|
searchHits = page.search(needle);
|
|
page.destroy();
|
|
if (searchHits != null && searchHits.length > 0) {
|
|
searchHitPage = searchPage;
|
|
pageNumber = searchPage;
|
|
break;
|
|
}
|
|
searchPage += direction;
|
|
}
|
|
}
|
|
|
|
public void textValueChanged(TextEvent event) {
|
|
Object source = event.getSource();
|
|
if (source == searchField) {
|
|
searchHitPage = -1;
|
|
}
|
|
}
|
|
|
|
public void actionPerformed(ActionEvent event) {
|
|
Object source = event.getSource();
|
|
int oldPageNumber = pageNumber;
|
|
int oldLayoutEm = layoutEm;
|
|
int oldZoomLevel = zoomLevel;
|
|
Quad[] oldSearchHits = searchHits;
|
|
|
|
if (source == firstButton)
|
|
pageNumber = 0;
|
|
|
|
if (source == lastButton)
|
|
pageNumber = pageCount - 1;
|
|
|
|
if (source == prevButton) {
|
|
pageNumber = pageNumber - 1;
|
|
if (pageNumber < 0)
|
|
pageNumber = 0;
|
|
}
|
|
|
|
if (source == nextButton) {
|
|
pageNumber = pageNumber + 1;
|
|
if (pageNumber >= pageCount)
|
|
pageNumber = pageCount - 1;
|
|
}
|
|
|
|
if (source == pageField) {
|
|
pageNumber = Integer.parseInt(pageField.getText()) - 1;
|
|
if (pageNumber < 0)
|
|
pageNumber = 0;
|
|
if (pageNumber >= pageCount)
|
|
pageNumber = pageCount - 1;
|
|
pageField.setText(String.valueOf(pageNumber));
|
|
}
|
|
|
|
if (source == searchField)
|
|
doSearch(1);
|
|
if (source == searchNextButton)
|
|
doSearch(1);
|
|
if (source == searchPrevButton)
|
|
doSearch(-1);
|
|
|
|
if (source == fontIncButton && doc.isReflowable()) {
|
|
layoutEm += 1;
|
|
if (layoutEm > 36)
|
|
layoutEm = 36;
|
|
fontSizeLabel.setText(String.valueOf(layoutEm));
|
|
}
|
|
|
|
if (source == fontDecButton && doc.isReflowable()) {
|
|
layoutEm -= 1;
|
|
if (layoutEm < 6)
|
|
layoutEm = 6;
|
|
fontSizeLabel.setText(String.valueOf(layoutEm));
|
|
}
|
|
|
|
if (source == zoomOutButton) {
|
|
zoomLevel -= 1;
|
|
if (zoomLevel < 0)
|
|
zoomLevel = 0;
|
|
zoomChoice.select(zoomLevel);
|
|
}
|
|
|
|
if (source == zoomInButton) {
|
|
zoomLevel += 1;
|
|
if (zoomLevel >= zoomList.length)
|
|
zoomLevel = zoomList.length - 1;
|
|
zoomChoice.select(zoomLevel);
|
|
}
|
|
|
|
if (layoutEm != oldLayoutEm) {
|
|
long mark = doc.makeBookmark(doc.locationFromPageNumber(pageNumber));
|
|
doc.layout(layoutWidth, layoutHeight, layoutEm);
|
|
updateOutline();
|
|
pageCount = doc.countPages();
|
|
pageLabel.setText("/ " + pageCount);
|
|
pageNumber = doc.pageNumberFromLocation(doc.findBookmark(mark));
|
|
}
|
|
|
|
if (zoomLevel != oldZoomLevel || pageNumber != oldPageNumber || layoutEm != oldLayoutEm || searchHits != oldSearchHits)
|
|
updatePageCanvas();
|
|
}
|
|
|
|
public void itemStateChanged(ItemEvent event) {
|
|
Object source = event.getSource();
|
|
if (source == zoomChoice) {
|
|
int oldZoomLevel = zoomLevel;
|
|
zoomLevel = zoomChoice.getSelectedIndex();
|
|
if (zoomLevel != oldZoomLevel)
|
|
updatePageCanvas();
|
|
}
|
|
if (source == outlineList) {
|
|
int i = outlineList.getSelectedIndex();
|
|
Outline node = flatOutline.elementAt(i);
|
|
int linkPage = doc.pageNumberFromLocation(doc.resolveLink(node));
|
|
if (linkPage >= 0) {
|
|
if (linkPage != pageNumber) {
|
|
pageNumber = linkPage;
|
|
updatePageCanvas();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void windowClosing(WindowEvent event) { dispose(); }
|
|
public void windowActivated(WindowEvent event) { }
|
|
public void windowDeactivated(WindowEvent event) { }
|
|
public void windowIconified(WindowEvent event) { }
|
|
public void windowDeiconified(WindowEvent event) { }
|
|
public void windowOpened(WindowEvent event) { }
|
|
public void windowClosed(WindowEvent event) { }
|
|
|
|
protected static String getAcceleratorPath(String documentPath) {
|
|
String acceleratorPath = documentPath.substring(1);
|
|
acceleratorPath = acceleratorPath.replace(File.separatorChar, '%');
|
|
acceleratorPath = acceleratorPath.replace('\\', '%');
|
|
acceleratorPath = acceleratorPath.replace(':', '%');
|
|
|
|
String tmpdir = System.getProperty("java.io.tmpdir");
|
|
return new StringBuffer(tmpdir).append(File.separatorChar).append(acceleratorPath).append(".accel").toString();
|
|
}
|
|
|
|
protected static boolean acceleratorValid(File documentFile, File acceleratorFile) {
|
|
long documentModified = documentFile.lastModified();
|
|
long acceleratorModified = acceleratorFile.lastModified();
|
|
|
|
return acceleratorModified != 0 && acceleratorModified > documentModified;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
File selectedFile;
|
|
|
|
if (args.length <= 0) {
|
|
FileDialog fileDialog = new FileDialog((Frame)null, "MuPDF Open File", FileDialog.LOAD);
|
|
fileDialog.setDirectory(System.getProperty("user.dir"));
|
|
fileDialog.setFilenameFilter(new FilenameFilter() {
|
|
public boolean accept(File dir, String name) {
|
|
return Document.recognize(name);
|
|
}
|
|
});
|
|
fileDialog.setVisible(true);
|
|
if (fileDialog.getFile() == null)
|
|
System.exit(0);
|
|
selectedFile = new File(fileDialog.getDirectory(), fileDialog.getFile());
|
|
fileDialog.dispose();
|
|
} else {
|
|
selectedFile = new File(args[0]);
|
|
}
|
|
|
|
try {
|
|
String documentPath = selectedFile.getAbsolutePath();
|
|
String acceleratorPath = getAcceleratorPath(documentPath);
|
|
|
|
Document doc;
|
|
if (acceleratorValid(selectedFile, new File(acceleratorPath)))
|
|
doc = Document.openDocument(documentPath, acceleratorPath);
|
|
else
|
|
doc = Document.openDocument(documentPath);
|
|
|
|
if (doc.needsPassword()) {
|
|
String pwd;
|
|
do {
|
|
pwd = passwordDialog(null);
|
|
if (pwd == null)
|
|
System.exit(1);
|
|
} while (!doc.authenticatePassword(pwd));
|
|
}
|
|
|
|
doc.countPages();
|
|
doc.saveAccelerator(acceleratorPath);
|
|
|
|
Viewer app = new Viewer(doc);
|
|
app.setVisible(true);
|
|
return;
|
|
} catch (Exception e) {
|
|
messageBox(null, "MuPDF Error", "Cannot open \"" + selectedFile + "\": " + e.getMessage() + ".");
|
|
System.exit(1);
|
|
}
|
|
}
|
|
|
|
public float getRetinaScale() {
|
|
// first try Oracle's VM (we should also test for 1.7.0_40 or higher)
|
|
final String vendor = System.getProperty("java.vm.vendor");
|
|
boolean isOracle = vendor != null && vendor.toLowerCase().contains("Oracle".toLowerCase());
|
|
if (isOracle) {
|
|
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
|
final GraphicsDevice device = env.getDefaultScreenDevice();
|
|
try {
|
|
Field field = device.getClass().getDeclaredField("scale");
|
|
if (field != null) {
|
|
field.setAccessible(true);
|
|
Object scale = field.get(device);
|
|
if (scale instanceof Integer && ((Integer)scale).intValue() == 2) {
|
|
return 2.0f;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ignore) {
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
// try Apple VM
|
|
final Float scaleFactor = (Float)Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor");
|
|
if (scaleFactor != null && scaleFactor.intValue() == 2) {
|
|
return 2.0f;
|
|
}
|
|
|
|
return 1.0f;
|
|
}
|
|
}
|