Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.pdfbox.debugger; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FileDialog; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.WindowEvent; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.border.BevelBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.filechooser.FileFilter; import javax.swing.tree.TreePath; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSBoolean; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSFloat; import org.apache.pdfbox.cos.COSInteger; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSNull; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.cos.COSString; import org.apache.pdfbox.debugger.colorpane.CSArrayBased; import org.apache.pdfbox.debugger.colorpane.CSDeviceN; import org.apache.pdfbox.debugger.colorpane.CSIndexed; import org.apache.pdfbox.debugger.colorpane.CSSeparation; import org.apache.pdfbox.debugger.flagbitspane.FlagBitsPane; import org.apache.pdfbox.debugger.fontencodingpane.FontEncodingPaneController; import org.apache.pdfbox.debugger.pagepane.PagePane; import org.apache.pdfbox.debugger.streampane.StreamPane; import org.apache.pdfbox.debugger.stringpane.StringPane; import org.apache.pdfbox.debugger.treestatus.TreeStatus; import org.apache.pdfbox.debugger.treestatus.TreeStatusPane; import org.apache.pdfbox.debugger.ui.ArrayEntry; import org.apache.pdfbox.debugger.ui.DocumentEntry; import org.apache.pdfbox.debugger.ui.ErrorDialog; import org.apache.pdfbox.debugger.ui.ExtensionFileFilter; import org.apache.pdfbox.debugger.ui.FileOpenSaveDialog; import org.apache.pdfbox.debugger.ui.LogDialog; import org.apache.pdfbox.debugger.ui.MapEntry; import org.apache.pdfbox.debugger.ui.OSXAdapter; import org.apache.pdfbox.debugger.ui.PDFTreeCellRenderer; import org.apache.pdfbox.debugger.ui.PDFTreeModel; import org.apache.pdfbox.debugger.ui.PageEntry; import org.apache.pdfbox.debugger.ui.ReaderBottomPanel; import org.apache.pdfbox.debugger.ui.RecentFiles; import org.apache.pdfbox.debugger.ui.RotationMenu; import org.apache.pdfbox.debugger.ui.Tree; import org.apache.pdfbox.debugger.ui.ZoomMenu; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.common.PDPageLabels; import org.apache.pdfbox.pdmodel.encryption.InvalidPasswordException; import org.apache.pdfbox.printing.PDFPageable; /** * PDF Debugger. * * @author wurtz * @author Ben Litchfield * @author Khyrul Bashar */ public class PDFDebugger extends JFrame { private static final Set<COSName> SPECIALCOLORSPACES = new HashSet<COSName>( Arrays.asList(COSName.INDEXED, COSName.SEPARATION, COSName.DEVICEN)); private static final Set<COSName> OTHERCOLORSPACES = new HashSet<COSName>( Arrays.asList(COSName.ICCBASED, COSName.PATTERN, COSName.CALGRAY, COSName.CALRGB, COSName.LAB)); private static final String PASSWORD = "-password"; private static final String VIEW_STRUCTURE = "-viewstructure"; private static final int SHORCUT_KEY_MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); private static final String OS_NAME = System.getProperty("os.name").toLowerCase(); private static final boolean IS_MAC_OS = OS_NAME.startsWith("mac os x"); private final JPanel documentPanel = new JPanel(); private TreeStatusPane statusPane; private RecentFiles recentFiles; private boolean isPageMode; private PDDocument document; private String currentFilePath; private JScrollPane jScrollPane1; private JScrollPane jScrollPane2; private javax.swing.JSplitPane jSplitPane1; private javax.swing.JTextPane jTextPane1; private ReaderBottomPanel statusBar; private Tree tree; // file menu private JMenuItem saveAsMenuItem; private JMenuItem saveMenuItem; private JMenu recentFilesMenu; private JMenuItem printMenuItem; private JMenuItem reopenMenuItem; // edit > find menu private JMenu findMenu; private JMenuItem findMenuItem; private JMenuItem findNextMenuItem; private JMenuItem findPreviousMenuItem; // view menu private JMenuItem viewModeItem; public static JCheckBoxMenuItem showTextStripper; public static JCheckBoxMenuItem showTextStripperBeads; public static JCheckBoxMenuItem showFontBBox; public static JCheckBoxMenuItem showGlyphBounds; // configuration public static Properties configuration = new Properties(); /** * Constructor. */ public PDFDebugger() { this(false); } /** * Constructor. */ public PDFDebugger(boolean viewPages) { isPageMode = viewPages; loadConfiguration(); initComponents(); // use our custom logger LogDialog.init(this, statusBar.getLogLabel()); System.setProperty("org.apache.commons.logging.Log", "org.apache.pdfbox.debugger.ui.DebugLog"); } /** * Entry point. * * @param args the command line arguments * @throws Exception If anything goes wrong. */ public static void main(String[] args) throws Exception { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); if (System.getProperty("apple.laf.useScreenMenuBar") == null) { System.setProperty("apple.laf.useScreenMenuBar", "true"); } // handle uncaught exceptions Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { new ErrorDialog(throwable).setVisible(true); } }); // open file, if any String filename = null; String password = ""; boolean viewPages = true; for (int i = 0; i < args.length; i++) { if (args[i].equals(PASSWORD)) { i++; if (i >= args.length) { usage(); } password = args[i]; } else if (args[i].equals(VIEW_STRUCTURE)) { viewPages = false; } else { filename = args[i]; } } filename = "/home/data/work/pdfxx_m.pdf"; final PDFDebugger viewer = new PDFDebugger(viewPages); if (filename != null) { File file = new File(filename); if (file.exists()) { viewer.readPDFFile(filename, password); } } viewer.setVisible(true); } /** * This will print out a message telling how to use this utility. */ private static void usage() { String message = "Usage: java -jar pdfbox-app-x.y.z.jar PDFDebugger [options] <inputfile>\n" + "\nOptions:\n" + " -password <password> : Password to decrypt the document\n" + " -viewstructure : activate structure mode on startup\n" + " <inputfile> : The PDF document to be loaded\n"; System.err.println(message); System.exit(1); } /** * Loads the local configuration file, if any. */ private void loadConfiguration() { File file = new File("config.properties"); if (file.exists()) { try { InputStream is = new FileInputStream(file); configuration.load(is); is.close(); } catch (IOException e) { throw new RuntimeException(e); } } } /** * This method is called from within the constructor to initialize the form. */ private void initComponents() { jSplitPane1 = new javax.swing.JSplitPane(); jScrollPane1 = new JScrollPane(); tree = new Tree(this); jScrollPane2 = new JScrollPane(); jTextPane1 = new javax.swing.JTextPane(); tree.setCellRenderer(new PDFTreeCellRenderer()); tree.setModel(null); setTitle("Apache PDFBox Debugger"); addWindowListener(new java.awt.event.WindowAdapter() { @Override public void windowOpened(WindowEvent windowEvent) { tree.requestFocusInWindow(); super.windowOpened(windowEvent); } @Override public void windowClosing(WindowEvent evt) { exitForm(evt); } }); jScrollPane1.setBorder(new BevelBorder(BevelBorder.RAISED)); jScrollPane1.setPreferredSize(new Dimension(350, 500)); tree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent evt) { jTree1ValueChanged(evt); } }); jScrollPane1.setViewportView(tree); jSplitPane1.setRightComponent(jScrollPane2); jSplitPane1.setDividerSize(3); jScrollPane2.setPreferredSize(new Dimension(300, 500)); jScrollPane2.setViewportView(jTextPane1); jSplitPane1.setLeftComponent(jScrollPane1); JScrollPane documentScroller = new JScrollPane(); documentScroller.setViewportView(documentPanel); statusPane = new TreeStatusPane(tree); statusPane.getPanel().setBorder(new BevelBorder(BevelBorder.RAISED)); statusPane.getPanel().setPreferredSize(new Dimension(300, 25)); getContentPane().add(statusPane.getPanel(), BorderLayout.PAGE_START); getContentPane().add(jSplitPane1, BorderLayout.CENTER); statusBar = new ReaderBottomPanel(); getContentPane().add(statusBar, BorderLayout.SOUTH); // create menus JMenuBar menuBar = new JMenuBar(); menuBar.add(createFileMenu()); menuBar.add(createEditMenu()); menuBar.add(createViewMenu()); setJMenuBar(menuBar); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); int width = 1000; int height = 970; setBounds((screenSize.width - width) / 2, (screenSize.height - height) / 2, width, height); // drag and drop to open files setTransferHandler(new TransferHandler() { @Override public boolean canImport(TransferSupport transferSupport) { return transferSupport.isDataFlavorSupported(DataFlavor.javaFileListFlavor); } @Override @SuppressWarnings("unchecked") public boolean importData(TransferSupport transferSupport) { try { Transferable transferable = transferSupport.getTransferable(); List<File> files = (List<File>) transferable.getTransferData(DataFlavor.javaFileListFlavor); readPDFFile(files.get(0), ""); return true; } catch (IOException e) { new ErrorDialog(e).setVisible(true); return true; } catch (UnsupportedFlavorException e) { throw new RuntimeException(e); } } }); // Mac OS X file open/quit handler if (IS_MAC_OS) { try { Method osxOpenFiles = getClass().getDeclaredMethod("osxOpenFiles", String.class); osxOpenFiles.setAccessible(true); OSXAdapter.setFileHandler(this, osxOpenFiles); Method osxQuit = getClass().getDeclaredMethod("osxQuit"); osxQuit.setAccessible(true); OSXAdapter.setQuitHandler(this, osxQuit); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } } private JMenu createFileMenu() { JMenuItem openMenuItem = new JMenuItem("Open..."); openMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, SHORCUT_KEY_MASK)); openMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { openMenuItemActionPerformed(evt); } }); JMenu fileMenu = new JMenu("File"); fileMenu.add(openMenuItem); fileMenu.setMnemonic('F'); JMenuItem openUrlMenuItem = new JMenuItem("Open URL..."); openUrlMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, SHORCUT_KEY_MASK)); openUrlMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { String urlString = JOptionPane.showInputDialog("Enter an URL"); if (urlString == null || urlString.isEmpty()) { return; } try { readPDFurl(urlString, ""); } catch (IOException e) { throw new RuntimeException(e); } } }); fileMenu.add(openUrlMenuItem); reopenMenuItem = new JMenuItem("Reopen"); reopenMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, SHORCUT_KEY_MASK)); reopenMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { try { if (currentFilePath.startsWith("http")) { readPDFurl(currentFilePath, ""); } else { readPDFFile(currentFilePath, ""); } } catch (IOException e) { new ErrorDialog(e).setVisible(true); } } }); reopenMenuItem.setEnabled(false); fileMenu.add(reopenMenuItem); try { recentFiles = new RecentFiles(this.getClass(), 5); } catch (Exception e) { throw new RuntimeException(e); } recentFilesMenu = new JMenu("Open Recent"); recentFilesMenu.setEnabled(false); addRecentFileItems(); fileMenu.add(recentFilesMenu); printMenuItem = new JMenuItem("Print"); printMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, SHORCUT_KEY_MASK)); printMenuItem.setEnabled(false); printMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { printMenuItemActionPerformed(evt); } }); if (!IS_MAC_OS) { fileMenu.addSeparator(); fileMenu.add(printMenuItem); } JMenuItem exitMenuItem = new JMenuItem("Exit"); exitMenuItem.setAccelerator(KeyStroke.getKeyStroke("alt F4")); exitMenuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent evt) { exitMenuItemActionPerformed(evt); } }); if (!IS_MAC_OS) { fileMenu.addSeparator(); fileMenu.add(exitMenuItem); } return fileMenu; } private JMenu createEditMenu() { JMenu editMenu = new JMenu("Edit"); editMenu.setMnemonic('E'); JMenuItem cutMenuItem = new JMenuItem("Cut"); cutMenuItem.setEnabled(false); editMenu.add(cutMenuItem); JMenuItem copyMenuItem = new JMenuItem("Copy"); copyMenuItem.setEnabled(false); editMenu.add(copyMenuItem); JMenuItem pasteMenuItem = new JMenuItem("Paste"); pasteMenuItem.setEnabled(false); editMenu.add(pasteMenuItem); JMenuItem deleteMenuItem = new JMenuItem("Delete"); deleteMenuItem.setEnabled(false); editMenu.add(deleteMenuItem); editMenu.addSeparator(); editMenu.add(createFindMenu()); return editMenu; } private JMenu createViewMenu() { JMenu viewMenu = new JMenu("View"); viewMenu.setMnemonic('V'); if (isPageMode) { viewModeItem = new JMenuItem("Show Internal Structure"); } else { viewModeItem = new JMenuItem("Show Pages"); } viewModeItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { if (isPageMode) { viewModeItem.setText("Show Pages"); isPageMode = false; } else { viewModeItem.setText("Show Internal Structure"); isPageMode = true; } if (document != null) { initTree(); } } }); viewMenu.add(viewModeItem); ZoomMenu zoomMenu = ZoomMenu.getInstance(); zoomMenu.setEnableMenu(false); viewMenu.add(zoomMenu.getMenu()); RotationMenu rotationMenu = RotationMenu.getInstance(); rotationMenu.setEnableMenu(false); viewMenu.add(rotationMenu.getMenu()); viewMenu.addSeparator(); showTextStripper = new JCheckBoxMenuItem("Show TextStripper TextPositions"); showTextStripper.setEnabled(false); viewMenu.add(showTextStripper); showTextStripperBeads = new JCheckBoxMenuItem("Show TextStripper Beads"); showTextStripperBeads.setEnabled(false); viewMenu.add(showTextStripperBeads); showFontBBox = new JCheckBoxMenuItem("Show Approximate Text Bounds"); showFontBBox.setEnabled(false); viewMenu.add(showFontBBox); showGlyphBounds = new JCheckBoxMenuItem("Show Glyph Bounds"); showGlyphBounds.setEnabled(false); viewMenu.add(showGlyphBounds); return viewMenu; } private JMenu createFindMenu() { findMenu = new JMenu("Find"); findMenu.setEnabled(false); findMenuItem = new JMenuItem("Find..."); findMenuItem.setActionCommand("find"); findMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, SHORCUT_KEY_MASK)); findNextMenuItem = new JMenuItem("Find Next"); if (IS_MAC_OS) { findNextMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, SHORCUT_KEY_MASK)); } else { findNextMenuItem.setAccelerator(KeyStroke.getKeyStroke("F3")); } findPreviousMenuItem = new JMenuItem("Find Previous"); if (IS_MAC_OS) { findPreviousMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_G, SHORCUT_KEY_MASK | InputEvent.SHIFT_DOWN_MASK)); } else { findPreviousMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_DOWN_MASK)); } findMenu.add(findMenuItem); findMenu.add(findNextMenuItem); findMenu.add(findPreviousMenuItem); return findMenu; } /** * Returns the File menu. */ public JMenu getFindMenu() { return findMenu; } /** * Returns the Edit > Find > Find menu item. */ public JMenuItem getFindMenuItem() { return findMenuItem; } /** * Returns the Edit > Find > Find Next menu item. */ public JMenuItem getFindNextMenuItem() { return findNextMenuItem; } /** * Returns the Edit > Find > Find Previous menu item. */ public JMenuItem getFindPreviousMenuItem() { return findPreviousMenuItem; } /** * This method is called via reflection on Mac OS X. */ private void osxOpenFiles(String filename) { try { readPDFFile(filename, ""); } catch (IOException e) { throw new RuntimeException(e); } } /** * This method is called via reflection on Mac OS X. */ private void osxQuit() { exitMenuItemActionPerformed(null); } private void openMenuItemActionPerformed(ActionEvent evt) { try { if (IS_MAC_OS) { FileDialog openDialog = new FileDialog(this, "Open"); openDialog.setFilenameFilter(new FilenameFilter() { @Override public boolean accept(File file, String s) { return file.getName().toLowerCase().endsWith(".pdf"); } }); openDialog.setVisible(true); if (openDialog.getFile() != null) { readPDFFile(openDialog.getFile(), ""); } } else { String[] extensions = new String[] { "pdf", "PDF" }; FileFilter pdfFilter = new ExtensionFileFilter(extensions, "PDF Files (*.pdf)"); FileOpenSaveDialog openDialog = new FileOpenSaveDialog(this, pdfFilter); File file = openDialog.openFile(); if (file != null) { readPDFFile(file, ""); } } } catch (IOException e) { throw new RuntimeException(e); } } private void jTree1ValueChanged(TreeSelectionEvent evt) { TreePath path = tree.getSelectionPath(); if (path != null) { try { Object selectedNode = path.getLastPathComponent(); statusBar.getStatusLabel().setText(""); if (isPage(selectedNode)) { showPage(selectedNode); return; } if (isSpecialColorSpace(selectedNode) || isOtherColorSpace(selectedNode)) { showColorPane(selectedNode); return; } if (path.getParentPath() != null && isFlagNode(selectedNode, path.getParentPath().getLastPathComponent())) { Object parentNode = path.getParentPath().getLastPathComponent(); showFlagPane(parentNode, selectedNode); return; } if (isStream(selectedNode)) { showStream((COSStream) getUnderneathObject(selectedNode), path); return; } if (isFont(selectedNode)) { showFont(selectedNode, path); return; } if (isString(selectedNode)) { showString(selectedNode); return; } if (jSplitPane1.getRightComponent() == null || !jSplitPane1.getRightComponent().equals(jScrollPane2)) { replaceRightComponent(jScrollPane2); } jTextPane1.setText(convertToString(selectedNode)); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } } private boolean isSpecialColorSpace(Object selectedNode) { selectedNode = getUnderneathObject(selectedNode); if (selectedNode instanceof COSArray && ((COSArray) selectedNode).size() > 0) { COSBase arrayEntry = ((COSArray) selectedNode).get(0); if (arrayEntry instanceof COSName) { COSName name = (COSName) arrayEntry; return SPECIALCOLORSPACES.contains(name); } } return false; } private boolean isOtherColorSpace(Object selectedNode) { selectedNode = getUnderneathObject(selectedNode); if (selectedNode instanceof COSArray && ((COSArray) selectedNode).size() > 0) { COSBase arrayEntry = ((COSArray) selectedNode).get(0); if (arrayEntry instanceof COSName) { COSName name = (COSName) arrayEntry; return OTHERCOLORSPACES.contains(name); } } return false; } private boolean isPage(Object selectedNode) { selectedNode = getUnderneathObject(selectedNode); if (selectedNode instanceof COSDictionary) { COSDictionary dict = (COSDictionary) selectedNode; COSBase typeItem = dict.getItem(COSName.TYPE); if (COSName.PAGE.equals(typeItem)) { return true; } } else if (selectedNode instanceof PageEntry) { return true; } return false; } private boolean isFlagNode(Object selectedNode, Object parentNode) { if (selectedNode instanceof MapEntry) { Object key = ((MapEntry) selectedNode).getKey(); return (COSName.FLAGS.equals(key) && isFontDescriptor(parentNode)) || (COSName.F.equals(key) && isAnnot(parentNode)) || COSName.FF.equals(key) || COSName.PANOSE.equals(key) || COSName.SIG_FLAGS.equals(key) || (COSName.P.equals(key) && isEncrypt(parentNode)); } return false; } private boolean isEncrypt(Object obj) { if (obj instanceof MapEntry) { MapEntry entry = (MapEntry) obj; return COSName.ENCRYPT.equals(entry.getKey()) && entry.getValue() instanceof COSDictionary; } return false; } private boolean isFontDescriptor(Object obj) { Object underneathObject = getUnderneathObject(obj); return underneathObject instanceof COSDictionary && ((COSDictionary) underneathObject).containsKey(COSName.TYPE) && ((COSDictionary) underneathObject).getCOSName(COSName.TYPE).equals(COSName.FONT_DESC); } private boolean isAnnot(Object obj) { Object underneathObject = getUnderneathObject(obj); return underneathObject instanceof COSDictionary && ((COSDictionary) underneathObject).containsKey(COSName.TYPE) && ((COSDictionary) underneathObject).getCOSName(COSName.TYPE).equals(COSName.ANNOT); } private boolean isStream(Object selectedNode) { return getUnderneathObject(selectedNode) instanceof COSStream; } private boolean isString(Object selectedNode) { return getUnderneathObject(selectedNode) instanceof COSString; } private boolean isFont(Object selectedNode) { selectedNode = getUnderneathObject(selectedNode); if (selectedNode instanceof COSDictionary) { COSDictionary dic = (COSDictionary) selectedNode; return dic.containsKey(COSName.TYPE) && dic.getCOSName(COSName.TYPE).equals(COSName.FONT) && !isCIDFont(dic); } return false; } private boolean isCIDFont(COSDictionary dic) { return dic.containsKey(COSName.SUBTYPE) && (dic.getCOSName(COSName.SUBTYPE).equals(COSName.CID_FONT_TYPE0) || dic.getCOSName(COSName.SUBTYPE).equals(COSName.CID_FONT_TYPE2)); } /** * Show a Panel describing color spaces in more detail and interactive way. * * @param csNode the special color space containing node. */ private void showColorPane(Object csNode) { csNode = getUnderneathObject(csNode); if (csNode instanceof COSArray && ((COSArray) csNode).size() > 0) { COSArray array = (COSArray) csNode; COSBase arrayEntry = array.get(0); if (arrayEntry instanceof COSName) { COSName csName = (COSName) arrayEntry; if (csName.equals(COSName.SEPARATION)) { replaceRightComponent(new CSSeparation(array).getPanel()); } else if (csName.equals(COSName.DEVICEN)) { replaceRightComponent(new CSDeviceN(array).getPanel()); } else if (csName.equals(COSName.INDEXED)) { replaceRightComponent(new CSIndexed(array).getPanel()); } else if (OTHERCOLORSPACES.contains(csName)) { replaceRightComponent(new CSArrayBased(array).getPanel()); } } } } private void showPage(Object selectedNode) { selectedNode = getUnderneathObject(selectedNode); COSDictionary page; if (selectedNode instanceof COSDictionary) { page = (COSDictionary) selectedNode; } else { page = ((PageEntry) selectedNode).getDict(); } COSBase typeItem = page.getItem(COSName.TYPE); if (COSName.PAGE.equals(typeItem)) { PagePane pagePane = new PagePane(document, page, statusBar.getStatusLabel()); replaceRightComponent(new JScrollPane(pagePane.getPanel())); } } private void showFlagPane(Object parentNode, Object selectedNode) { parentNode = getUnderneathObject(parentNode); if (parentNode instanceof COSDictionary) { selectedNode = ((MapEntry) selectedNode).getKey(); selectedNode = getUnderneathObject(selectedNode); FlagBitsPane flagBitsPane = new FlagBitsPane((COSDictionary) parentNode, (COSName) selectedNode); replaceRightComponent(flagBitsPane.getPane()); } } private void showStream(COSStream stream, TreePath path) throws IOException { boolean isContentStream = false; boolean isThumb = false; COSName key = getNodeKey(path.getLastPathComponent()); COSName parentKey = getNodeKey(path.getParentPath().getLastPathComponent()); COSDictionary resourcesDic = null; if (COSName.CONTENTS.equals(key)) { Object pageObj = path.getParentPath().getLastPathComponent(); COSDictionary page = (COSDictionary) getUnderneathObject(pageObj); resourcesDic = (COSDictionary) page.getDictionaryObject(COSName.RESOURCES); isContentStream = true; } else if (COSName.CONTENTS.equals(parentKey) || COSName.CHAR_PROCS.equals(parentKey)) { Object pageObj = path.getParentPath().getParentPath().getLastPathComponent(); COSDictionary page = (COSDictionary) getUnderneathObject(pageObj); resourcesDic = (COSDictionary) page.getDictionaryObject(COSName.RESOURCES); isContentStream = true; } else if (COSName.FORM.equals(stream.getCOSName(COSName.SUBTYPE)) || COSName.PATTERN.equals(stream.getCOSName(COSName.TYPE)) || stream.getInt(COSName.PATTERN_TYPE) == 1) { if (stream.containsKey(COSName.RESOURCES)) { resourcesDic = (COSDictionary) stream.getDictionaryObject(COSName.RESOURCES); } isContentStream = true; } else if (COSName.THUMB.equals(key)) { resourcesDic = null; isThumb = true; } else if (COSName.IMAGE.equals((stream).getCOSName(COSName.SUBTYPE))) { // not to be used for /Thumb, even if it contains /Subtype /Image Object resourcesObj = path.getParentPath().getParentPath().getLastPathComponent(); resourcesDic = (COSDictionary) getUnderneathObject(resourcesObj); } StreamPane streamPane = new StreamPane(stream, isContentStream, isThumb, resourcesDic); replaceRightComponent(streamPane.getPanel()); } private void showFont(Object selectedNode, TreePath path) { COSName fontName = getNodeKey(selectedNode); COSDictionary resourceDic = (COSDictionary) getUnderneathObject( path.getParentPath().getParentPath().getLastPathComponent()); FontEncodingPaneController fontEncodingPaneController = new FontEncodingPaneController(fontName, resourceDic); JPanel pane = fontEncodingPaneController.getPane(); if (pane == null) { // unsupported font type replaceRightComponent(jScrollPane2); return; } replaceRightComponent(pane); } // replace the right component while keeping divider position private void replaceRightComponent(Component pane) { int div = jSplitPane1.getDividerLocation(); jSplitPane1.setRightComponent(pane); jSplitPane1.setDividerLocation(div); } private void showString(Object selectedNode) { COSString string = (COSString) getUnderneathObject(selectedNode); replaceRightComponent(new StringPane(string).getPane()); } private COSName getNodeKey(Object selectedNode) { if (selectedNode instanceof MapEntry) { return ((MapEntry) selectedNode).getKey(); } return null; } private Object getUnderneathObject(Object selectedNode) { if (selectedNode instanceof MapEntry) { selectedNode = ((MapEntry) selectedNode).getValue(); } else if (selectedNode instanceof ArrayEntry) { selectedNode = ((ArrayEntry) selectedNode).getValue(); } else if (selectedNode instanceof PageEntry) { selectedNode = ((PageEntry) selectedNode).getDict(); } if (selectedNode instanceof COSObject) { selectedNode = ((COSObject) selectedNode).getObject(); } return selectedNode; } private String convertToString(Object selectedNode) { String data = null; if (selectedNode instanceof COSBoolean) { data = "" + ((COSBoolean) selectedNode).getValue(); } else if (selectedNode instanceof COSFloat) { data = "" + ((COSFloat) selectedNode).floatValue(); } else if (selectedNode instanceof COSNull) { data = "null"; } else if (selectedNode instanceof COSInteger) { data = "" + ((COSInteger) selectedNode).intValue(); } else if (selectedNode instanceof COSName) { data = "" + ((COSName) selectedNode).getName(); } else if (selectedNode instanceof COSString) { String text = ((COSString) selectedNode).getString(); // display unprintable strings as hex for (char c : text.toCharArray()) { if (Character.isISOControl(c)) { text = "<" + ((COSString) selectedNode).toHexString() + ">"; break; } } data = "" + text; } else if (selectedNode instanceof COSStream) { try { COSStream stream = (COSStream) selectedNode; InputStream ioStream = stream.createInputStream(); ByteArrayOutputStream byteArray = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int amountRead; while ((amountRead = ioStream.read(buffer, 0, buffer.length)) != -1) { byteArray.write(buffer, 0, amountRead); } data = byteArray.toString(); } catch (IOException e) { throw new RuntimeException(e); } } else if (selectedNode instanceof MapEntry) { data = convertToString(((MapEntry) selectedNode).getValue()); } else if (selectedNode instanceof ArrayEntry) { data = convertToString(((ArrayEntry) selectedNode).getValue()); } return data; } private void exitMenuItemActionPerformed(ActionEvent evt) { if (document != null) { try { document.close(); if (!currentFilePath.startsWith("http")) { recentFiles.addFile(currentFilePath); } recentFiles.close(); } catch (IOException e) { throw new RuntimeException(e); } } System.exit(0); } private void printMenuItemActionPerformed(ActionEvent evt) { if (document != null) { try { PrinterJob job = PrinterJob.getPrinterJob(); job.setPageable(new PDFPageable(document)); if (job.printDialog()) { job.print(); } } catch (PrinterException e) { throw new RuntimeException(e); } } } /** * Exit the Application. */ private void exitForm(WindowEvent evt) { if (document != null) { try { document.close(); if (!currentFilePath.startsWith("http")) { recentFiles.addFile(currentFilePath); } recentFiles.close(); } catch (IOException e) { throw new RuntimeException(e); } } System.exit(0); } private void readPDFFile(String filePath, String password) throws IOException { File file = new File(filePath); readPDFFile(file, password); } private void readPDFFile(File file, String password) throws IOException { if (document != null) { document.close(); if (!currentFilePath.startsWith("http")) { recentFiles.addFile(currentFilePath); } } currentFilePath = file.getPath(); recentFiles.removeFile(file.getPath()); LogDialog.instance().clear(); parseDocument(file, password); initTree(); if (IS_MAC_OS) { setTitle(file.getName()); getRootPane().putClientProperty("Window.documentFile", file); } else { setTitle("PDF Debugger - " + file.getAbsolutePath()); } addRecentFileItems(); } private void readPDFurl(String urlString, String password) throws IOException { if (document != null) { document.close(); if (!currentFilePath.startsWith("http")) { recentFiles.addFile(currentFilePath); } } currentFilePath = urlString; URL url = new URL(urlString); LogDialog.instance().clear(); document = PDDocument.load(url.openStream(), password); printMenuItem.setEnabled(true); reopenMenuItem.setEnabled(true); initTree(); if (IS_MAC_OS) { setTitle(urlString); } else { setTitle("PDF Debugger - " + urlString); } addRecentFileItems(); } private void initTree() { TreeStatus treeStatus = new TreeStatus(document.getDocument().getTrailer()); statusPane.updateTreeStatus(treeStatus); if (isPageMode) { File file = new File(currentFilePath); DocumentEntry documentEntry = new DocumentEntry(document, file.getName()); ZoomMenu.getInstance().resetZoom(); tree.setModel(new PDFTreeModel(documentEntry)); // Root/Pages/Kids/[0] is not always the first page, so use the first row instead: tree.setSelectionPath(tree.getPathForRow(1)); } else { tree.setModel(new PDFTreeModel(document)); tree.setSelectionPath(treeStatus.getPathForString("Root")); } } /** * This will parse a document. * * @param file The file addressing the document. * @throws IOException If there is an error parsing the document. */ private void parseDocument(File file, String password) throws IOException { while (true) { try { document = PDDocument.load(file, password); } catch (InvalidPasswordException ipe) { // https://stackoverflow.com/questions/8881213/joptionpane-to-get-password JPanel panel = new JPanel(); JLabel label = new JLabel("Password:"); JPasswordField pass = new JPasswordField(10); panel.add(label); panel.add(pass); String[] options = new String[] { "OK", "Cancel" }; int option = JOptionPane.showOptionDialog(null, panel, "Enter password", JOptionPane.NO_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, ""); if (option == 0) { password = new String(pass.getPassword()); continue; } throw ipe; } break; } printMenuItem.setEnabled(true); reopenMenuItem.setEnabled(true); } private void addRecentFileItems() { Action recentMenuAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent actionEvent) { String filePath = (String) ((JComponent) actionEvent.getSource()).getClientProperty("path"); try { readPDFFile(filePath, ""); } catch (Exception e) { throw new RuntimeException(e); } } }; if (!recentFiles.isEmpty()) { recentFilesMenu.removeAll(); List<String> files = recentFiles.getFiles(); for (int i = files.size() - 1; i >= 0; i--) { String path = files.get(i); String name = new File(path).getName(); JMenuItem recentFileMenuItem = new JMenuItem(name); recentFileMenuItem.putClientProperty("path", path); recentFileMenuItem.addActionListener(recentMenuAction); recentFilesMenu.add(recentFileMenuItem); } recentFilesMenu.setEnabled(true); } } /** * Convenience method to get the page label if available. * * @param document * @param pageIndex 0-based page number. * @return a page label or null if not available. */ public static String getPageLabel(PDDocument document, int pageIndex) { PDPageLabels pageLabels; try { pageLabels = document.getDocumentCatalog().getPageLabels(); } catch (IOException ex) { return ex.getMessage(); } if (pageLabels != null) { String[] labels = pageLabels.getLabelsByPageIndices(); if (labels[pageIndex] != null) { return labels[pageIndex]; } } return null; } }