package org.docear.plugin.pdfutilities.pdf;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.docear.plugin.core.features.AnnotationID;
import org.docear.plugin.core.util.Tools;
import org.docear.plugin.pdfutilities.PdfUtilitiesController;
import org.docear.plugin.pdfutilities.features.AnnotationModel;
import org.docear.plugin.pdfutilities.features.IAnnotation;
import org.docear.plugin.pdfutilities.features.IAnnotation.AnnotationType;
import org.docear.plugin.pdfutilities.map.AnnotationController;
import org.docear.plugin.pdfutilities.map.IAnnotationImporter;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.util.LogUtils;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.mode.Controller;
import de.intarsys.pdf.cds.CDSNameTreeEntry;
import de.intarsys.pdf.cds.CDSNameTreeNode;
import de.intarsys.pdf.cos.COSArray;
import de.intarsys.pdf.cos.COSCatalog;
import de.intarsys.pdf.cos.COSDictionary;
import de.intarsys.pdf.cos.COSName;
import de.intarsys.pdf.cos.COSNull;
import de.intarsys.pdf.cos.COSObject;
import de.intarsys.pdf.cos.COSRuntimeException;
import de.intarsys.pdf.cos.COSString;
import de.intarsys.pdf.parser.COSLoadException;
import de.intarsys.pdf.pd.PDAnnotation;
import de.intarsys.pdf.pd.PDAnyAnnotation;
import de.intarsys.pdf.pd.PDDocument;
import de.intarsys.pdf.pd.PDExplicitDestination;
import de.intarsys.pdf.pd.PDHighlightAnnotation;
import de.intarsys.pdf.pd.PDOutline;
import de.intarsys.pdf.pd.PDOutlineItem;
import de.intarsys.pdf.pd.PDOutlineNode;
import de.intarsys.pdf.pd.PDPage;
import de.intarsys.pdf.pd.PDTextAnnotation;
import de.intarsys.pdf.pd.PDTextMarkupAnnotation;
import de.intarsys.tools.locator.FileLocator;
public class PdfAnnotationImporter implements IAnnotationImporter {
private URI currentFile;
private boolean importAll = false;
private boolean setPDObject = false;
public PdfAnnotationImporter(){
//AnnotationController.addAnnotationImporter(this);
}
public Map<URI, List<AnnotationModel>> importAnnotations(List<URI> files) throws IOException, COSLoadException, COSRuntimeException{
Map<URI, List<AnnotationModel>> annotationMap = new HashMap<URI, List<AnnotationModel>>();
for(URI file : files){
annotationMap.put(file, this.importAnnotations(file));
}
return annotationMap;
}
public List<AnnotationModel> importAnnotations(URI uri) throws IOException, COSLoadException, COSRuntimeException{
List<AnnotationModel> annotations = new ArrayList<AnnotationModel>();
this.currentFile = uri;
PDDocument document = getPDDocument(uri);
if(document == null){
return annotations;
}
try{
annotations.addAll(this.importAnnotations(document));
annotations.addAll(this.importBookmarks(document.getOutline()));
} catch(ClassCastException e){
try{
//LogUtils.warn("first try: "+ e.getMessage()+" -> " +uri);
//LogUtils.warn(e);
PDOutlineItem outline = (PDOutlineItem)PDOutline.META.createFromCos(document.getCatalog().cosGetOutline());
annotations.addAll(this.importBookmarks(outline));
} catch(Exception ex){
LogUtils.warn("org.docear.plugin.pdfutilities.pdf.PdfAnnotationImporter.importAnnotations: " + ex.getMessage()+" -> " +uri);
return annotations;
}
} catch(Exception e){
LogUtils.warn(e);
return annotations;
} finally {
if(document != null){
document.close();
document = null;
}
}
return annotations;
}
public AnnotationModel importPdf(URI uri) throws IOException, COSLoadException, COSRuntimeException{
Collection<AnnotationModel> importedAnnotations = new ArrayList<AnnotationModel>();
try{
importedAnnotations = importAnnotations(uri);
} catch(IOException e){
LogUtils.info("IOexception during update file: "+ uri); //$NON-NLS-1$
} catch(COSRuntimeException e){
LogUtils.info("COSRuntimeException during update file: "+ uri); //$NON-NLS-1$
} catch(COSLoadException e){
LogUtils.info("COSLoadException during update file: "+ uri); //$NON-NLS-1$
}
AnnotationModel root = new AnnotationModel(new AnnotationID(Tools.getAbsoluteUri(uri), 0), AnnotationType.PDF_FILE);
root.setTitle(Tools.getFilefromUri(Tools.getAbsoluteUri(uri)).getName());
root.getChildren().addAll(importedAnnotations);
return root;
}
public boolean renameAnnotation(IAnnotation annotation, String newTitle) throws COSRuntimeException, IOException, COSLoadException{
List<AnnotationModel> annotations = new ArrayList<AnnotationModel>();
boolean ret = false;
this.currentFile = annotation.getUri();
PDDocument document = getPDDocument(annotation.getUri());
if(document == null){
ret = false;
}
try{
this.setImportAll(true);
this.setPDObject(true);
annotations.addAll(this.importAnnotations(document));
annotations.addAll(this.importBookmarks(document.getOutline()));
} catch(ClassCastException e){
try{
PDOutlineItem outline = (PDOutlineItem)PDOutline.META.createFromCos(document.getCatalog().cosGetOutline());
annotations.addAll(this.importBookmarks(outline));
} catch(Exception ex){
LogUtils.warn(ex);
}
} catch(Exception e){
LogUtils.warn(e);
} finally {
this.setImportAll(true);
this.setPDObject(false);
AnnotationModel result = this.searchAnnotation(annotations, annotation);
if(result != null){
Object annotationObject = result.getAnnotationObject();
if(annotationObject != null && annotationObject instanceof PDOutlineItem){
((PDOutlineItem)annotationObject).setTitle(newTitle);
ret = true;
}
if(annotationObject != null && annotationObject instanceof PDAnnotation){
((PDAnnotation)annotationObject).setContents(newTitle);
ret = true;
}
document.save();
}
if(document != null)
document.close();
}
return ret;
}
private void setPDObject(boolean b) {
setPDObject = b;
}
private boolean setPDObject() {
return setPDObject;
}
public PDDocument getPDDocument(URI uri) throws IOException, COSLoadException, COSRuntimeException {
MapModel map = Controller.getCurrentController().getMap();
if(uri == null || Tools.getFilefromUri(Tools.getAbsoluteUri(uri, map)) == null || !Tools.exists(uri, map) || !new PdfFileFilter().accept(uri)){
return null;
}
File file = Tools.getFilefromUri(Tools.getAbsoluteUri(uri, map));
FileLocator locator = new FileLocator(file);
PDDocument document = PDDocument.createFromLocator(locator);
locator = null;
return document;
}
private List<AnnotationModel> importBookmarks(PDOutlineNode parent) throws IOException, COSLoadException, COSRuntimeException{
List<AnnotationModel> annotations = new ArrayList<AnnotationModel>();
if(!this.importAll && !ResourceController.getResourceController().getBooleanProperty(PdfUtilitiesController.IMPORT_BOOKMARKS_KEY)){
return annotations;
}
if(parent == null) return annotations;
@SuppressWarnings("unchecked")
List<PDOutlineItem> children = parent.getChildren();
for(PDOutlineItem child : children){
Integer objectNumber = child.cosGetObject().getIndirectObject().getObjectNumber();
AnnotationModel annotation = new AnnotationModel(new AnnotationID(this.currentFile, objectNumber));
if(this.setPDObject()){
annotation.setAnnotationObject(child);
}
annotation.setTitle(child.getTitle());
annotation.setAnnotationType(getAnnotationType(child));
annotation.setGenerationNumber(child.cosGetObject().getIndirectObject().getGenerationNumber());
annotation.getChildren().addAll(this.importBookmarks(child));
if(annotation.getAnnotationType() == AnnotationType.BOOKMARK_WITH_URI){
annotation.setDestinationUri(this.getAnnotationDestinationUri(child));
}
if(annotation.getAnnotationType() == AnnotationType.BOOKMARK){
annotation.setPage(this.getAnnotationDestinationPage(child));
}
annotations.add(annotation);
}
return annotations;
}
private URI getAnnotationDestinationUri(PDOutlineItem bookmark) {
if(bookmark != null && !(bookmark.cosGetField(PDOutlineItem.DK_A) instanceof COSNull)){
COSDictionary cosDictionary = (COSDictionary)bookmark.cosGetField(PDOutlineItem.DK_A);
if(!(cosDictionary.get(COSName.create("URI")) instanceof COSNull)){ //$NON-NLS-1$
COSObject destination = cosDictionary.get(COSName.create("URI")); //$NON-NLS-1$
if(destination instanceof COSString && destination.getValueString(null) != null && destination.getValueString(null).length() > 0){
try {
return new URI(destination.getValueString(null));
} catch (URISyntaxException e) {
LogUtils.warn("Bookmark Destination Uri Syntax incorrect.", e); //$NON-NLS-1$
}
}
}
}
return null;
}
private AnnotationType getAnnotationType(PDOutlineItem bookmark) {
if(bookmark != null && (bookmark.cosGetField(PDOutlineItem.DK_A) instanceof COSNull)){
Integer page = null;
try {
page = getAnnotationDestinationPage(bookmark);
}
catch (Exception e) {
}
if (page == null) {
return AnnotationType.BOOKMARK_WITHOUT_DESTINATION;
}
}
if(bookmark != null && !(bookmark.cosGetField(PDOutlineItem.DK_A) instanceof COSNull)){
COSDictionary cosDictionary = (COSDictionary)bookmark.cosGetField(PDOutlineItem.DK_A);
if(!(cosDictionary.get(COSName.create("URI")) instanceof COSNull)){ //$NON-NLS-1$
return AnnotationType.BOOKMARK_WITH_URI;
}
}
return AnnotationType.BOOKMARK;
}
private List<AnnotationModel> importAnnotations(PDDocument document){
List<AnnotationModel> annotations = new ArrayList<AnnotationModel>();
boolean importComments = false;
boolean importHighlightedTexts = false;
if(this.importAll){
importComments = true;
importHighlightedTexts = true;
}
else{
importComments = ResourceController.getResourceController().getBooleanProperty(PdfUtilitiesController.IMPORT_COMMENTS_KEY);
importHighlightedTexts = ResourceController.getResourceController().getBooleanProperty(PdfUtilitiesController.IMPORT_HIGHLIGHTED_TEXTS_KEY);
}
String lastString = ""; //$NON-NLS-1$
// Process page at a time pages always have a page number annotations dont have to record the page
for (PDPage pdPage = document.getPageTree().getFirstPage(); pdPage != null; pdPage = pdPage.getNextPage() )
{
//@SuppressWarnings("unchecked")
List<PDAnnotation> pdAnnotations = pdPage.getAnnotations();
// if there are annotation on this page
if (pdAnnotations != null) {
for(PDAnnotation annotation : pdAnnotations){
// Avoid empty entries
// support repligo highlights
if (annotation.getClass() == PDHighlightAnnotation.class) {
// ignore Highlight if Subject is "Highlight" and Contents is ""
if (((PDHighlightAnnotation) annotation).getSubject() != null &&
((PDHighlightAnnotation) annotation).getSubject().length() > 0 &&
((PDHighlightAnnotation) annotation).getSubject().equals("Highlight") &&
annotation.getContents().equals("") )
continue;
} else {
// ignore annotations with Contents is ""
if (annotation.getContents().equals("")
/* && !annotation.isMarkupAnnotation() */
)
continue;
//$NON-NLS-1$
// Avoid double entries (Foxit Reader)
if (annotation.getContents().equals(lastString)/*
* &&
* !annotation.
* isMarkupAnnotation
* ()
*/)
continue;
lastString = annotation.getContents();
}
if((annotation.getClass() == PDAnyAnnotation.class ||annotation.getClass() == PDTextAnnotation.class) &&
importComments){
Integer objectNumber = annotation.cosGetObject().getIndirectObject().getObjectNumber();
AnnotationModel pdfAnnotation = new AnnotationModel(new AnnotationID(this.currentFile, objectNumber));
pdfAnnotation.setTitle(annotation.getContents());
if(this.setPDObject()){
pdfAnnotation.setAnnotationObject(annotation);
}
pdfAnnotation.setAnnotationType(AnnotationType.COMMENT);
pdfAnnotation.setGenerationNumber(annotation.cosGetObject().getIndirectObject().getGenerationNumber());
pdfAnnotation.setPage(pdPage.getNodeIndex()+1);
annotations.add(pdfAnnotation);
}
if((annotation.getClass() == PDTextMarkupAnnotation.class || annotation.getClass() == PDHighlightAnnotation.class) && importHighlightedTexts){
Integer objectNumber = annotation.cosGetObject().getIndirectObject().getObjectNumber();
AnnotationModel pdfAnnotation = new AnnotationModel(new AnnotationID(this.currentFile, objectNumber));
// prefer Title from Contents (So updates work)
if (annotation.getContents() != null &&
annotation.getContents().length() > 0) {
pdfAnnotation.setTitle(annotation.getContents());
} else {
// support repligo highlights
// set Title to Subject per repligo
if (annotation.getClass() == PDHighlightAnnotation.class) {
String subject = ((PDHighlightAnnotation) annotation).getSubject();
if (subject != null && subject.length() > 0) {
if (!subject.equalsIgnoreCase("Highlight") && !subject.equalsIgnoreCase("Hervorheben"))
pdfAnnotation.setTitle(((PDHighlightAnnotation) annotation).getSubject());
else
continue;
}
}
}
if(this.setPDObject()){
pdfAnnotation.setAnnotationObject(annotation);
}
pdfAnnotation.setAnnotationType(AnnotationType.HIGHLIGHTED_TEXT);
pdfAnnotation.setGenerationNumber(annotation.cosGetObject().getIndirectObject().getGenerationNumber());
pdfAnnotation.setPage(pdPage.getNodeIndex()+1);
annotations.add(pdfAnnotation);
}
}
}
}
return annotations;
}
public Integer getAnnotationDestination(PDAnnotation pdAnnotation) {
if(pdAnnotation != null){
PDPage page = pdAnnotation.getPage();
if(page != null)
return page.getNodeIndex()+1;
}
return null;
}
public Integer getAnnotationDestinationPage(PDOutlineItem bookmark) throws IOException, COSLoadException {
PDDocument document = bookmark.getDoc();
if(document == null || bookmark == null){
return null;
}
if(bookmark != null && bookmark.getDestination() != null){
PDExplicitDestination destination = bookmark.getDestination().getResolvedDestination(document);
if(destination != null){
PDPage page = destination.getPage(document);
return page.getNodeIndex()+1;
}
}
if(bookmark != null && !(bookmark.cosGetField(PDOutlineItem.DK_A) instanceof COSNull)){
COSDictionary cosDictionary = (COSDictionary)bookmark.cosGetField(PDOutlineItem.DK_A);
COSArray destination = getCOSArrayFromDestination(cosDictionary);
return getPageFromCOSArray(document, (COSArray)destination);
}
return null;
}
private COSArray getCOSArrayFromDestination(COSDictionary cosDictionary) {
COSObject cosObject = cosDictionary.get(COSName.create("D")); //$NON-NLS-1$
if(cosObject instanceof COSArray){
return (COSArray)cosObject;
}
if(cosObject instanceof COSString){
String destinationName = cosObject.getValueString(null);
if(destinationName == null || destinationName.length() <= 0){
return null;
}
COSDictionary dests = cosDictionary.getDoc().getCatalog().cosGetDests();
if (dests != null) {
for (Iterator<?> i = dests.keySet().iterator(); i.hasNext();) {
COSName key = (COSName) i.next();
if(key.stringValue().equals(destinationName)){
cosDictionary = (COSDictionary)dests.get(key);
cosObject = cosDictionary.get(COSName.create("D")); //$NON-NLS-1$
if(cosObject instanceof COSArray){
return (COSArray)cosObject;
}
}
}
}
COSDictionary names = cosDictionary.getDoc().getCatalog().cosGetNames();
if (names != null) {
COSDictionary destsDict = names.get(COSCatalog.DK_Dests).asDictionary();
if (destsDict != null) {
CDSNameTreeNode destsTree = CDSNameTreeNode.createFromCos(destsDict);
for (Iterator<?> i = destsTree.iterator(); i.hasNext();) {
CDSNameTreeEntry entry = (CDSNameTreeEntry) i.next();
if(entry.getName().stringValue().equals(destinationName)){
if(entry.getValue() instanceof COSDictionary){
cosDictionary = (COSDictionary)entry.getValue();
cosObject = cosDictionary.get(COSName.create("D")); //$NON-NLS-1$
if(cosObject instanceof COSArray){
return (COSArray)cosObject;
}
}
else if(entry.getValue() instanceof COSArray){
return (COSArray)entry.getValue();
}
}
}
}
}
}
return null;
}
private Integer getPageFromCOSArray(PDDocument document, COSArray destination) {
//DOCEAR: fallback if no etry was found
if(destination == null) {
return 1;
}
Iterator<?> it = destination.iterator();
while(it.hasNext()){
COSObject o = (COSObject)it.next();
if(o.isIndirect()){ //the page is indirect referenced
o.dereference();
}
PDPage page = document.getPageTree().getFirstPage();
while(page != null){
if(page.cosGetObject().equals(o)){
return page.getNodeIndex() + 1;
}
page = page.getNextPage();
}
}
return null;
}
public AnnotationModel searchAnnotation(URI uri, NodeModel node) throws Exception {
this.currentFile = uri;
if(!this.isImportAll()) this.setImportAll(true);
List<AnnotationModel> annotations = this.importAnnotations(uri);
this.setImportAll(false);
return searchAnnotation(annotations, node);
}
public AnnotationModel searchAnnotation(List<AnnotationModel> annotations, NodeModel node) {
for(AnnotationModel annotation : annotations){
AnnotationModel extensionModel = AnnotationController.getModel(node, false);
if(extensionModel == null){
if(annotation.getTitle().equals(node.getText())){
//TODO: DOCEAR is Update nodeModel good here??
//TODO: DOCEAR How to deal with nodes without extension(and object number) and changed annotation title ??
AnnotationController.setModel(node, annotation);
return annotation;
}
else{
AnnotationModel searchResult = searchAnnotation(annotation.getChildren(), node);
if(searchResult != null) return searchResult;
}
}
else{
return searchAnnotation(annotations, extensionModel);
}
}
return null;
}
public AnnotationModel searchAnnotation(List<AnnotationModel> annotations, IAnnotation target) {
for(AnnotationModel annotation : annotations){
if(annotation.getObjectNumber().equals(target.getObjectNumber())){
return annotation;
}
else{
AnnotationModel searchResult = searchAnnotation(annotation.getChildren(), target);
if(searchResult != null) return searchResult;
}
}
return null;
}
public boolean isImportAll() {
return importAll;
}
public void setImportAll(boolean importAll) {
this.importAll = importAll;
}
}