package org.pdfclown.samples.gui;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.pdfclown.bytes.IBuffer;
import org.pdfclown.documents.Page;
import org.pdfclown.documents.contents.Contents;
import org.pdfclown.documents.contents.objects.CompositeObject;
import org.pdfclown.documents.contents.objects.ContentObject;
import org.pdfclown.documents.contents.objects.GenericOperation;
import org.pdfclown.documents.contents.objects.Operation;
import org.pdfclown.files.File;
import org.pdfclown.objects.PdfArray;
import org.pdfclown.objects.PdfDataObject;
import org.pdfclown.objects.PdfDictionary;
import org.pdfclown.objects.PdfDirectObject;
import org.pdfclown.objects.PdfName;
import org.pdfclown.objects.PdfStream;
import org.pdfclown.tokens.FileFormatException;
/**
PDF content stream's object model inspector.
<p>This is a proof of concept that exploits the object-oriented content stream model
implemented by PDF Clown.</p>
*/
public class PdfInspectorSample
extends JPanel
{
private static final long serialVersionUID = 1L;
// <class>
// <classes>
private static class CellRenderer
extends JTextArea
implements TableCellRenderer
{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(
JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row,
int column
)
{
setWrapStyleWord(false);
setLineWrap(true);
setText((String)value);
return this;
}
}
private static class ContentNodeValue
extends NodeValue
{
private ContentObject content;
private static String getName(
ContentObject content
)
{
if(content instanceof GenericOperation)
return ((GenericOperation)content).getOperator();
else
return content.getClass().getSimpleName();
}
public ContentNodeValue(
ContentObject content
)
{
super(getName(content));
this.content = content;
}
public ContentObject getContent(
)
{return content;}
}
private static class NodeValue
{
private String name;
public NodeValue(
String name
)
{this.name = name;}
@Override
public String toString(
)
{return name;}
}
private static class PageNodeValue
extends NodeValue
{
private Page page;
public PageNodeValue(
Page page
)
{
super("Page " + (page.getIndex() + 1));
this.page = page;
}
public Page getPage(
)
{return page;}
}
// </classes>
// <fields>
private File file;
private final JTable table;
private final JTree tree;
// </fields>
// <constructors>
public PdfInspectorSample(
)
{
super(new GridLayout(1,0));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
{
Dimension minimumSize = new Dimension(200, 100);
JScrollPane treeScrollPane;
{
tree = new JTree();
{
tree.setModel(new DefaultTreeModel(null));
tree.getSelectionModel().setSelectionMode(
TreeSelectionModel.SINGLE_TREE_SELECTION
);
tree.addTreeSelectionListener(
new TreeSelectionListener()
{
@Override
public void valueChanged(TreeSelectionEvent event)
{
// Clear the attributes table!
DefaultTableModel model = (DefaultTableModel)table.getModel();
model.setRowCount(0);
DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
if(node == null)
return;
// Show the current node attributes into the table!
Object nodeValue = node.getUserObject();
if(nodeValue instanceof ContentNodeValue)
{showContentAttributes(((ContentNodeValue)nodeValue).getContent(), model);}
else if(nodeValue instanceof PageNodeValue)
{showPageAttributes(((PageNodeValue)nodeValue).getPage().getContents(), model);}
pack(table);
}
}
);
tree.addTreeWillExpandListener(
new TreeWillExpandListener()
{
@Override
public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException
{/* NOOP */}
@Override
public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode)event.getPath().getLastPathComponent();
Object nodeValue = node.getUserObject();
if(nodeValue instanceof PageNodeValue)
{
// Content placeholder?
if(((DefaultMutableTreeNode)node.getFirstChild()).getUserObject() == null)
{
// Remove the content placeholder!
node.removeAllChildren();
// Create the page content nodes!
createContentNodes(
((PageNodeValue)nodeValue).getPage().getContents(),
node
);
}
}
}
}
);
}
treeScrollPane = new JScrollPane(tree);
treeScrollPane.setMinimumSize(minimumSize);
}
splitPane.setLeftComponent(treeScrollPane);
JScrollPane tableScrollPane;
{
table = new JTable(new DefaultTableModel(null, new String[]{"Attribute","Value"}));
{
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
TableColumn column0 = table.getColumnModel().getColumn(0);
{
column0.setCellRenderer(new CellRenderer());
column0.setMinWidth(200);
column0.setMaxWidth(200);
}
TableColumn column1 = table.getColumnModel().getColumn(1);
{
column1.setCellRenderer(new CellRenderer());
}
table.getColumnModel().addColumnModelListener(
new TableColumnModelListener()
{
@Override
public void columnAdded(TableColumnModelEvent event)
{/* NOOP */}
@Override
public void columnMarginChanged(ChangeEvent event)
{pack(table);}
@Override
public void columnMoved(TableColumnModelEvent event)
{/* NOOP */}
@Override
public void columnRemoved(TableColumnModelEvent event)
{/* NOOP */}
@Override
public void columnSelectionChanged(ListSelectionEvent event)
{/* NOOP */}
}
);
}
tableScrollPane = new JScrollPane(table);
tableScrollPane.setMinimumSize(minimumSize);
}
splitPane.setRightComponent(tableScrollPane);
}
add(splitPane);
}
// </constructors>
// <interface>
// <public>
public void open(
java.io.File file
)
{
// Open the PDF file!
try
{this.file = new File(file.getAbsolutePath());}
catch(FileFormatException e)
{throw new RuntimeException(file.getAbsolutePath() + " file has a bad file format.",e);}
catch(Exception e)
{throw new RuntimeException(file.getAbsolutePath() + " file access error.",e);}
// Create the root node!
DefaultMutableTreeNode fileNode = new DefaultMutableTreeNode(file.getName());
((DefaultTreeModel)tree.getModel()).setRoot(fileNode);
// Create the page nodes!
createPageNodes(fileNode);
// Expand the tree to show the page nodes!
tree.expandPath(new TreePath(fileNode));
}
// </public>
// <private>
private void createContentNodes(
List<ContentObject> contents,
DefaultMutableTreeNode parentNode
)
{
for(ContentObject content : contents)
{
DefaultMutableTreeNode contentNode = new DefaultMutableTreeNode(
new ContentNodeValue(content)
);
parentNode.add(contentNode);
if(content instanceof CompositeObject)
{
// Create inner content nodes!
createContentNodes(
((CompositeObject)content).getObjects(),
contentNode
);
}
}
}
private void createPageNodes(
DefaultMutableTreeNode fileNode
)
{
for(Page page : file.getDocument().getPages())
{
DefaultMutableTreeNode pageNode = new DefaultMutableTreeNode(
new PageNodeValue(page)
);
fileNode.add(pageNode);
// Add a placeholder node to postpone content parsing!
DefaultMutableTreeNode contentPlaceholderNode = new DefaultMutableTreeNode();
pageNode.add(contentPlaceholderNode);
}
}
private void pack(
JTable table
)
{
for(
int rowIndex = 0,
rowCount = table.getRowCount();
rowIndex < rowCount;
rowIndex++
)
{
int height = 0;
for(
int columnIndex = 0,
columnCount = table.getColumnCount();
columnIndex < columnCount;
columnIndex++
)
{
height = Math.max(
height,
table.prepareRenderer(
table.getCellRenderer(rowIndex, columnIndex),
rowIndex,
columnIndex
).getPreferredSize().height
);
}
if(table.getRowHeight(rowIndex) < height)
{table.setRowHeight(rowIndex, height);}
}
}
private void showContentAttributes(
ContentObject content,
DefaultTableModel model
)
{
Operation operation;
if(content instanceof Operation)
{operation = (Operation)content;}
else if(content instanceof CompositeObject)
{operation = ((CompositeObject)content).getHeader();}
else
{operation = null;}
if(operation == null)
return;
model.addRow(
new Object[]
{
"(operator)",
operation.getOperator()
}
);
List<PdfDirectObject> operands = operation.getOperands();
if(operands != null)
{
for(int index = 0, length = operands.size(); index < length; index++)
{
PdfDirectObject operand = operands.get(index);
if(operand instanceof PdfArray)
{
PdfArray operandElements = (PdfArray)operand;
int operandElementIndex = -1;
for(PdfDirectObject operandElement : operandElements)
{
model.addRow(
new Object[]
{
"(operand " + index + "." + (++operandElementIndex) + ")",
operandElement.toString()
}
);
}
}
else if(operand instanceof PdfDictionary)
{
PdfDictionary operandEntries = (PdfDictionary)operand;
int operandEntryIndex = -1;
for(Map.Entry<PdfName,PdfDirectObject> operandEntry : operandEntries.entrySet())
{
model.addRow(
new Object[]
{
"(operand " + index + "." + (++operandEntryIndex) + ") " + operandEntry.getKey().toString(),
operandEntry.getValue().toString()
}
);
}
}
else
{
model.addRow(
new Object[]
{
"(operand " + index + ")",
operand.toString()
}
);
}
}
}
}
@SuppressWarnings("unchecked")
private void showPageAttributes(
Contents contents,
DefaultTableModel model
)
{
List<PdfDataObject> streamObjects = new ArrayList<PdfDataObject>();
PdfDataObject contentsDataObject = contents.getBaseDataObject();
if(contentsDataObject instanceof PdfArray)
{streamObjects.addAll((List<PdfDirectObject>)contentsDataObject);}
else
{streamObjects.add(contentsDataObject);}
int streamIndex = -1;
for(PdfDataObject streamObject : streamObjects)
{
PdfStream stream = (PdfStream)File.resolve(streamObject);
IBuffer streamBody = stream.getBody();
model.addRow(
new Object[]
{
"(stream " + (++streamIndex) + ")",
streamBody.getString(0, (int)streamBody.getLength())
}
);
}
}
// </private>
// </interface>
// </class>
}