package at.ac.tuwien.dbai.pdfwrap.gui.elements; import at.ac.tuwien.dbai.pdfwrap.ProcessFile; import at.ac.tuwien.dbai.pdfwrap.analysis.PageProcessor; import at.ac.tuwien.dbai.pdfwrap.exceptions.DocumentProcessingException; import at.ac.tuwien.dbai.pdfwrap.gui.layer.Style; import at.ac.tuwien.dbai.pdfwrap.gui.layer.StyledSegment; import at.ac.tuwien.dbai.pdfwrap.gui.tools.OpenDocFileFilter; import at.ac.tuwien.dbai.pdfwrap.gui.tools.PDF_XMLSerializer; import at.ac.tuwien.dbai.pdfwrap.gui.tools.XMLLayerLoader; import at.ac.tuwien.dbai.pdfwrap.model.document.GenericSegment; import at.ac.tuwien.dbai.pdfwrap.model.document.Page; import at.ac.tuwien.dbai.pdfwrap.utils.Utils; import org.apache.pdfbox.pdmodel.PDDocument; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.filechooser.FileFilter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * The main frame of the pdfXtkGUI * * @author Timo Schleicher * */ @SuppressWarnings("serial") public class MainFrame extends JFrame { // private final String ghostScriptPath = "C:\\Program Files\\gs\\gs9.10\\bin\\gswin64.exe"; private final String ghostScriptPath = "gs"; private JSplitPane split; private PageSpinner pageSpinner; private SelectionPanel attributePanel; private JComboBox<String> resetViewBox; private PDFPanel pdfPanel; private PageProcessor pageProcessor; private HashMap<String, Style> styleMap; private HashMap<String, Integer> pageProcessorMap; private File curFile; private List<Page> pageList; /** * Main method * * @param args * @throws IllegalArgumentException */ public static void main(String[] args) throws IllegalArgumentException, URISyntaxException { URL url = MainFrame.class.getClassLoader().getResource("guiConfig.xml"); String configFile = new File( url.toURI() ).getAbsolutePath(); if( args.length > 0 ) { configFile = args[ 0 ]; } System.out.println( "loading config: " + configFile ); new MainFrame( configFile ); } /** * Constructor method for creating the main frame of the GUI * @param configFile */ public MainFrame( String configFile ) { //Reading the XML styling sheet try { styleMap = XMLLayerLoader.readXML( configFile ); } catch (Exception e) { JOptionPane.showMessageDialog(this, e.getMessage()+"\n System is shutting down. Please check XML files for correctness"); System.exit(1); } //Initialize the possible PageProcessor types and their textual appearance pageProcessorMap = new HashMap<String, Integer>(); pageProcessorMap.put("Text Fragments", PageProcessor.PP_FRAGMENT); pageProcessorMap.put("Indiv. Chars", PageProcessor.PP_CHAR); pageProcessorMap.put("Initial Lines", PageProcessor.PP_LINE); pageProcessorMap.put("Blocks", PageProcessor.PP_BLOCK); pageProcessorMap.put("Merged Lines", PageProcessor.PP_MERGED_LINES); //Default PageProcessor settings pageProcessor = new PageProcessor(); pageProcessor.setProcessType(PageProcessor.PP_FRAGMENT); pageProcessor.setProcessSpaces(false); pageProcessor.setRulingLines(false); pageProcessor.setNoIterations(0); initialize(); //Setting Tooltip disappear time ToolTipManager.sharedInstance().setDismissDelay(7000); //Set window settings this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); this.pack(); this.setVisible(true); } /** * This method initializes all of the components for the main frame */ private void initialize() { //Left component of the JSplitPane that contains different setting possibilities JPanel menu = new JPanel(); menu.setLayout(new BoxLayout(menu, BoxLayout.Y_AXIS)); menu.add(Box.createRigidArea(new Dimension(0, 15))); //Button for opening a new PDF file JButton openButton = new JButton("Open File"); openButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { final JFileChooser fc = new JFileChooser(); fc.setFileFilter(new OpenDocFileFilter()); int returnVal = fc.showOpenDialog(MainFrame.this); if (returnVal == JFileChooser.APPROVE_OPTION) { curFile = fc.getSelectedFile(); new PDFAnalysisThread(curFile).execute(); } } }); openButton.setAlignmentX(Component.CENTER_ALIGNMENT); menu.add(openButton); menu.add(Box.createRigidArea(new Dimension(0, 5))); //Button for saving JButton savePDFButton = new JButton("Save as XML"); savePDFButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { saveAsXML(); } }); savePDFButton.setAlignmentX(Component.CENTER_ALIGNMENT); menu.add(savePDFButton); menu.add(Box.createRigidArea(new Dimension(0, 15))); //Button for reloading the current page JButton reloadPDFButton = new JButton("Reload Document"); reloadPDFButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if (curFile != null) { new PDFAnalysisThread(curFile).execute(); } } }); reloadPDFButton.setAlignmentX(Component.CENTER_ALIGNMENT); menu.add(reloadPDFButton); menu.add(Box.createRigidArea(new Dimension(0, 15))); //Combo box for resetting the view frustum resetViewBox = new JComboBox<String>() { @Override public Dimension getMaximumSize() { //Need to override because of height resize bug Dimension max = super.getMaximumSize(); max.height = getPreferredSize().height; max.width = getPreferredSize().width; return max; } }; resetViewBox.addItem("Fit Width"); resetViewBox.addItem("Fit Height"); resetViewBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (pdfPanel != null) { if (resetViewBox.getSelectedItem().equals("Fit Width")) { pdfPanel.fitWindow(true); } else if (resetViewBox.getSelectedItem().equals("Fit Height")) { pdfPanel.fitWindow(false); } } } }); resetViewBox.setAlignmentX(Component.CENTER_ALIGNMENT); menu.add(resetViewBox); menu.add(Box.createRigidArea(new Dimension(0, 10))); //Spinner for page spinning pageSpinner = new PageSpinner(); pageSpinner.getPreviousButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if (curFile != null) { boolean successful = pageSpinner.decrease(); if(successful) { new PDFAnalysisThread(curFile, pageSpinner.getCurrentPage()).execute(); } } } }); pageSpinner.getNextButton().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { if (curFile != null) { boolean successful = pageSpinner.increase(); if(successful) { new PDFAnalysisThread(curFile, pageSpinner.getCurrentPage()).execute(); } } } }); pageSpinner.setAlignmentX(Component.CENTER_ALIGNMENT); menu.add(pageSpinner); menu.add(Box.createRigidArea(new Dimension(0, 15))); //Create settings components String[] processorTypes = pageProcessorMap.keySet().toArray(new String[0]); final JComboBox<String> processingTypeComboBox = new JComboBox<String>(processorTypes) { @Override public Dimension getMaximumSize() { //Need to override because of height resize bug Dimension max = super.getMaximumSize(); max.height = getPreferredSize().height; return max; } }; //Again set the type of the PageProcessor to make sure having initially set the right processor type pageProcessor.setProcessType(pageProcessorMap.get((String) processingTypeComboBox.getSelectedItem())); processingTypeComboBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String selectedType = (String) processingTypeComboBox.getSelectedItem(); pageProcessor.setProcessType(pageProcessorMap.get(selectedType)); } }); //Finally add the two check boxes for further options final JCheckBox rulingLinesCheckBox = new JCheckBox("Ruling Lines"); rulingLinesCheckBox.setAlignmentX(Component.CENTER_ALIGNMENT); rulingLinesCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { pageProcessor.setRulingLines(rulingLinesCheckBox.isSelected()); } }); final JCheckBox removeSpaceCheckBox = new JCheckBox("Remove Space"); removeSpaceCheckBox.setAlignmentX(Component.CENTER_ALIGNMENT); removeSpaceCheckBox.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { pageProcessor.setProcessSpaces(removeSpaceCheckBox.isSelected()); } }); //Create a JPanel to cover all the settings components JPanel processingOptionPanel = new JPanel(); processingOptionPanel.setLayout(new BoxLayout(processingOptionPanel , BoxLayout.Y_AXIS)); processingOptionPanel.setBorder(BorderFactory.createTitledBorder("Processing Options")); processingOptionPanel.setAlignmentX(Component.CENTER_ALIGNMENT); processingOptionPanel.add(processingTypeComboBox); processingOptionPanel.add(Box.createRigidArea(new Dimension(0, 10))); processingOptionPanel.add(removeSpaceCheckBox); processingOptionPanel.add(rulingLinesCheckBox); menu.add(processingOptionPanel); menu.add(Box.createRigidArea(new Dimension(0, 15))); //Create the panel for displaying attributes of selected segment attributePanel = new SelectionPanel(); //Create a list for the different kinds of layers CheckBoxList list = new CheckBoxList(styleMap, attributePanel); menu.add(list); menu.add(Box.createRigidArea(new Dimension(0, 15))); menu.add(attributePanel); Dimension fitWindow = getBestWindowSize(); menu.setPreferredSize(new Dimension(160, (int)fitWindow.getHeight())); //Right component of the JSplitPane that contains the PDF view JPanel PDFViewerPanel = new JPanel(); PDFViewerPanel.setPreferredSize(fitWindow); split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, menu, PDFViewerPanel); split.setEnabled(false); getContentPane().add(split); } /** * Saves the current PDF analysis pages to XML. */ private void saveAsXML() { if (curFile == null) { return; } JFileChooser fc = new JFileChooser(); //Create a filter for only display XML files fc.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } else { return f.getName().toLowerCase().endsWith(".xml"); } } @Override public String getDescription() { return "XML Documents (*.xml)"; } }); int returnVal = fc.showSaveDialog(MainFrame.this); if (returnVal == JFileChooser.APPROVE_OPTION) { try { //Actual saving process PDF_XMLSerializer.serialize(fc.getSelectedFile().getPath(), pageList, curFile); } catch(FileNotFoundException e) { JOptionPane.showMessageDialog(MainFrame.this, e.getMessage()); } catch (Exception e) { JOptionPane.showMessageDialog(MainFrame.this, "An error occurred during the saving process.\n" + e.getMessage()); } } } /** * Determine the best frame size according to the current user screen size * * @return best width and height for the GUI */ private Dimension getBestWindowSize() { Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); return new Dimension((int)(screen.getWidth()*(1.0/3.0)), (int)(screen.getHeight()*(2.0/3.0))); } /** * Inner class using SwingWorker to prevent the GUI from freezing while analyzing a PDF document * * @author Timo Schleicher * */ class PDFAnalysisThread extends SwingWorker<Object, Object> { private File f; private boolean loadNew; private int pageNo; /** * Constructor method for the PDF analysis thread based on SwingWorker. * Tries to only layout and print a PDF page, but uses previous analysis output. * * @param f The PDF or XML file * @param pageNo The page number you want to show */ public PDFAnalysisThread(File f, int pageNo) { this.pageNo = pageNo; this.loadNew = false; this.f = f; } /** * Constructor method for the PDF analysis thread based on SwingWorker. * Loads the PDF completely new and analyzes it. * * @param f The PDF or XML file */ public PDFAnalysisThread(File f) { this.loadNew = true; this.pageNo = 1; this.f = f; } @Override protected Object doInBackground() throws Exception { String pdfPath = (f.getPath().toLowerCase().endsWith(".pdf")) ? f.getPath() : PDF_XMLSerializer.getPDFPath(f); if (!new File(pdfPath).exists()) { JOptionPane.showMessageDialog(MainFrame.this, "Can not find the following file:\n" + pdfPath); return null; } if (loadNew) { List<Page> thePages = null; if (f.getPath().toLowerCase().endsWith(".pdf")) { try { byte[] inFile = ProcessFile.getBytesFromFile(f); thePages = ProcessFile.processPDF(inFile, pageProcessor, 1, Integer.MAX_VALUE, null, null, null, true); for (int i = 0; i < thePages.size(); i++) { thePages.get(i).setPageNo(i+1); } } catch (DocumentProcessingException e) { JOptionPane.showMessageDialog(MainFrame.this, e.getMessage()); return null; } } else if (f.getPath().toLowerCase().endsWith(".xml")) { thePages = PDF_XMLSerializer.deserializeAnalysis(f); } PDDocument pdf = PDDocument.load(pdfPath); pageSpinner.initNewSpinnerValues(pdf.getNumberOfPages()); pageList = thePages; if (pageList == null) { return null; } } List<GenericSegment> seg = pageList.get(pageNo-1).getItems(); ArrayList<StyledSegment> styl = new ArrayList<StyledSegment>(); //Link the different segments with the corresponding styling object for (Map.Entry<String, Style> entry : styleMap.entrySet()) { String xmlName = entry.getValue().getSource(); for (GenericSegment s : seg) { if (s.tagName().equals(xmlName)) { styl.add(new StyledSegment(s,entry.getValue())); } } } //Convert PDF to image by means of ghostscript String[] sa = new String[] {ghostScriptPath, "-dBATCH", "-sDEVICE=pngmono", "-r" + Utils.XML_RESOLUTION, "-dTextAlphaBits=4", "-dAlignToPixels=0", "-dSAFER", "-dNOPAUSE", "-dDOINTERPOLATE", "-dBATCH", "-dQUIET", "-dNOPAGEPROMPT", "-q", "-dNOPAUSE", "-DFirstPage=" + pageNo, "-DLastPage=" + pageNo, "-dUseCropBox", "-sOutputFile=" + Utils.getRootDir() + File.separator + "output.png", pdfPath}; Utils.executeCommand(sa, null, null); BufferedImage img = ImageIO.read(new File("output.png")); attributePanel.setSelectedElements(null); resetViewBox.setSelectedItem("Fit Width"); pdfPanel = new PDFPanel(split.getRightComponent().getSize(), img, styl, attributePanel, pageList.get(pageNo-1)); split.setRightComponent(pdfPanel); return null; } } }