Skip to content

Commit

Permalink
Add new listener for PDF generation which automatically parses HTML h…
Browse files Browse the repository at this point in the history
…eader information and adds it as PDF properties (e.g. title, subject). Patch submitted by Tim Telcik in email. Thanks!
  • Loading branch information
pdoubleya committed Aug 3, 2009
1 parent c9c1d7a commit 22bfb27
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,34 @@
*/
package org.xhtmlrenderer.demo.browser;

import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.IOException;

import javax.swing.*;

import org.w3c.dom.Document;
import org.xhtmlrenderer.event.DocumentListener;
import org.xhtmlrenderer.layout.SharedContext;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.pdf.PDFCreationListener;
import org.xhtmlrenderer.pdf.util.XHtmlMetaToPdfInfoAdapter;
import org.xhtmlrenderer.resource.XMLResource;
import org.xhtmlrenderer.simple.FSScrollPane;
import org.xhtmlrenderer.swing.ScalableXHTMLPanel;
import org.xhtmlrenderer.swing.ImageResourceLoader;
import org.xhtmlrenderer.swing.ScalableXHTMLPanel;
import org.xhtmlrenderer.swing.SwingReplacedElementFactory;
import org.xhtmlrenderer.util.GeneralUtil;
import org.xhtmlrenderer.util.Uu;
import org.xhtmlrenderer.util.XRLog;
import org.xhtmlrenderer.util.XRRuntimeException;
import org.xhtmlrenderer.util.GeneralUtil;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.resource.XMLResource;

import com.lowagie.text.DocumentException;
import javax.swing.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
Expand Down Expand Up @@ -375,27 +377,34 @@ public void exportToPdf( String path )
try {
os = new FileOutputStream(path);
try {
ITextRenderer renderer = new ITextRenderer();

renderer.setDocument(manager.getBaseURL());
renderer.layout();

renderer.createPDF(os);
setStatus("Done export.");
} catch (Exception e) {
XRLog.general(Level.SEVERE, "Could not export PDF.", e);
e.printStackTrace();
setStatus("Error exporting to PDF.");
ITextRenderer renderer = new ITextRenderer();

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(manager.getBaseURL());

PDFCreationListener pdfCreationListener = new XHtmlMetaToPdfInfoAdapter( doc );
renderer.setListener( pdfCreationListener );

renderer.setDocument(manager.getBaseURL());
renderer.layout();

renderer.createPDF(os);
setStatus( "Done export." );
} catch (Exception e) {
XRLog.general(Level.SEVERE, "Could not export PDF.", e);
e.printStackTrace();
setStatus( "Error exporting to PDF." );
} finally {
try {
os.close();
} catch (IOException e) {
// swallow
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
e.printStackTrace();
}
}
}

Expand Down Expand Up @@ -508,11 +517,8 @@ public void onRenderException(Throwable t) {
* $Id$
*
* $Log$
* Revision 1.40 2009/05/15 16:28:14 pdoubleya
* Integrate async image loading, starting point is DelegatingUserAgentCallback. AWT images are now always buffered, but screen-compatible. RootPanel now supports a repaint mechanism, with optional layout, with some attempt to control how often one or the other actually takes place when many images have been loaded.
*
* Revision 1.39 2009/05/09 15:16:43 pdoubleya
* FindBugs: proper disposal of IO resources
* Revision 1.41 2009/08/03 19:36:29 pdoubleya
* Add new listener for PDF generation which automatically parses HTML header information and adds it as PDF properties (e.g. title, subject). Patch submitted by Tim Telcik in email. Thanks!
*
* Revision 1.38 2008/09/06 18:44:29 peterbrant
* Add PDF export to browser (patch from Mykola Gurov)
Expand Down
6 changes: 5 additions & 1 deletion demos/browser/xhtml/formatted-text.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>Flying Saucer: CSS Formatted Text Support</title>
<link rel="stylesheet" type="text/css" href="general.css" title="Style" media="screen" />
<meta name="creator" content="Tim Telcik &lt;tim.telcik@permeance.com.au&gt;" />
<meta name="keywords" content="test xhtml pdf meta adapter" />
<meta name="subject" content="CSS Formatted Text Demo Page" />

<link rel="stylesheet" type="text/css" href="general.css" title="Style" media="screen" />
<link rel="stylesheet" type="text/css" href="print.css" title="Style" media="print" />
<style type="text/css">
.smallcaps { font-variant: small-caps }
Expand Down
243 changes: 243 additions & 0 deletions src/java/org/xhtmlrenderer/pdf/util/XHtmlMetaToPdfInfoAdapter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* Copyright (C) 2008 Permeance Technologies Pty Ltd. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/

package org.xhtmlrenderer.pdf.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xhtmlrenderer.pdf.DefaultPDFCreationListener;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.xhtmlrenderer.pdf.PDFCreationListener;
import org.xhtmlrenderer.util.XRLog;

import com.lowagie.text.pdf.PdfName;
import com.lowagie.text.pdf.PdfObject;
import com.lowagie.text.pdf.PdfString;


/**
* <h1>Description</h1>
* <p>
* This PDF Creation Listener parses meta data elements from an (X)HTML document and appends them to
* the info dictionary of a PDF document.
* </p>
*
* <p>
* The XHTML document is parsed for relevant PDF meta data during construction, then adds the meta
* data to the PDF document when the PDF document is closed by the calling ITextRenderer.
* </p>
*
* <p>
* Valid (X)HTML tags are:
* <ul>
* <li>TITLE</li>
* </ul>
* </p>
*
* <p>
* Valid (X)HTML meta tag attribute names are:
* <ul>
* <li>TITLE (optional), DC.TITLE</li>
* <li>CREATOR, AUTHOR, DC.CREATOR</li>
* <li>SUBJECT, DC.SUBJECT</li>
* <li>KEYWORDS</li>
* </ul>
* </p>
*
* <p>
* Valid PDF meta names are defined in Adobe's PDF Reference (Sixth Edition),
* section "10.2.1 - Document Information Dictionary", table 10.2, pg.844
* http://www.adobe.com/devnet/pdf/pdf_reference.html
* </p>
*
* <h1>Usage</h1>
* <pre>
* // Setup output stream
* OutputStream outputStream = ...
*
* // Create W3C document model
* Document doc = ...
*
* // Create new PDF renderer
* ITextRenderer renderer = new ITextRenderer();
*
* // Add PDF creation listener
* PDFCreationListener pdfCreationListener = new XHtmlMetaToPdfInfoAdapter( doc );
* renderer.setListener( pdfCreationListener);
*
* // Add W3C document to renderer
* renderer.setDocument( doc, null );
*
* // Layout PDF document
* renderer.layout();
*
* // Write PDF document
* renderer.createPDF( outputStream, true );
* </pre>
*
* <h1>Notes</h1>
* This class was derived from a sample PDF creation listener
* at "http://markmail.org/message/46t3bw7q6mbhvra2"
* by Jesse Keller <jesse.keller@roche.com>.
*
* @author Tim Telcik <tim.telcik@permeance.com.au>
*
* @see DefaultPDFCreationListener
* @see PDFCreationListener
* @see ITextRenderer
* @see http://markmail.org/message/46t3bw7q6mbhvra2
* @see http://www.adobe.com/devnet/pdf/pdf_reference.html
* @see http://www.seoconsultants.com/meta-tags/dublin/
*/
public class XHtmlMetaToPdfInfoAdapter extends DefaultPDFCreationListener {
private static final String HTML_TAG_TITLE = "title";
private static final String HTML_TAG_HEAD = "head";
private static final String HTML_TAG_META = "meta";
private static final String HTML_META_KEY_TITLE = "title";
private static final String HTML_META_KEY_DC_TITLE = "DC.title";
private static final String HTML_META_KEY_CREATOR = "creator";
private static final String HTML_META_KEY_DC_CREATOR = "DC.creator";
private static final String HTML_META_KEY_SUBJECT = "subject";
private static final String HTML_META_KEY_DC_SUBJECT = "DC.subject";
private static final String HTML_META_KEY_KEYWORDS = "keywords";
private static final String HTML_META_ATTR_NAME = "name";
private static final String HTML_META_ATTR_CONTENT = "content";

private java.util.Map pdfInfoValues = new HashMap();


/**
* Creates a new adapter from the given XHTML document.
*
* @param doc XHTML document
*/
public XHtmlMetaToPdfInfoAdapter( Document doc ) {
parseHtmlTags( doc );
}

/**
* PDFCreationListener onClose event handler.
*
* @see PDFCreationListener
*/
public void onClose( ITextRenderer renderer ) {
XRLog.render(Level.FINEST, "handling onClose event ..." );
addPdfMetaValuesToPdfDocument( renderer );
}

private void parseHtmlTags( Document doc ) {
XRLog.render(Level.FINEST, "parsing (X)HTML tags ..." );
parseHtmlTitleTag( doc );
parseHtmlMetaTags( doc );
if ( XRLog.isLoggingEnabled() ) {
XRLog.render(Level.FINEST, "PDF info map = " + pdfInfoValues );
}
}

private void parseHtmlTitleTag( Document doc ) {

NodeList headNodeList = doc.getDocumentElement().getElementsByTagName( HTML_TAG_HEAD );
XRLog.render(Level.FINEST, "headNodeList=" + headNodeList );
Element rootHeadNodeElement = (Element) headNodeList.item( 0 );
NodeList titleNodeList = rootHeadNodeElement.getElementsByTagName( HTML_TAG_TITLE );
XRLog.render(Level.FINEST, "titleNodeList=" + titleNodeList );
Element titleElement = (Element) titleNodeList.item( 0 );
if ( titleElement != null ) {
XRLog.render(Level.FINEST, "titleElement=" + titleElement );
XRLog.render(Level.FINEST, "titleElement.name=" + titleElement.getTagName() );
XRLog.render(Level.FINEST, "titleElement.value=" + titleElement.getNodeValue() );
XRLog.render(Level.FINEST, "titleElement.content=" + titleElement.getTextContent() );
String titleContent = titleElement.getTextContent();
PdfName pdfName = PdfName.TITLE;
PdfString pdfString = new PdfString( titleContent );
this.pdfInfoValues.put( pdfName, pdfString );
}
}

private void parseHtmlMetaTags( Document doc ) {

NodeList headNodeList = doc.getDocumentElement().getElementsByTagName( HTML_TAG_HEAD );
XRLog.render(Level.FINEST, "headNodeList=" + headNodeList );
Element rootHeadNodeElement = (Element) headNodeList.item( 0 );
NodeList metaNodeList = rootHeadNodeElement.getElementsByTagName( HTML_TAG_META );
XRLog.render(Level.FINEST, "metaNodeList=" + metaNodeList );

for (int inode = 0; inode < metaNodeList.getLength(); ++inode) {
XRLog.render(Level.FINEST, "node " + inode + " = "+ metaNodeList.item( inode ).getNodeName() );
Element thisNode = (Element) metaNodeList.item( inode );
XRLog.render(Level.FINEST, "node " + thisNode );
String metaName = thisNode.getAttribute( HTML_META_ATTR_NAME );
String metaContent = thisNode.getAttribute( HTML_META_ATTR_CONTENT );
XRLog.render(Level.FINEST, "metaName=" + metaName + ", metaContent=" + metaContent );
if (metaName.length() != 0 && metaContent.length() != 0) {

PdfName pdfName = null;
PdfString pdfString = null;
if ( HTML_META_KEY_TITLE.equalsIgnoreCase( metaName )
|| HTML_META_KEY_DC_TITLE.equalsIgnoreCase( metaName ) ) {
pdfName = PdfName.TITLE;
pdfString = new PdfString( metaContent, PdfObject.TEXT_UNICODE );
this.pdfInfoValues.put( pdfName, pdfString );

} else if ( HTML_META_KEY_CREATOR.equalsIgnoreCase( metaName )
|| HTML_META_KEY_DC_CREATOR.equalsIgnoreCase( metaName ) ) {
pdfName = PdfName.AUTHOR;
pdfString = new PdfString( metaContent, PdfObject.TEXT_UNICODE );
this.pdfInfoValues.put( pdfName, pdfString );

} else if ( HTML_META_KEY_SUBJECT.equalsIgnoreCase( metaName )
|| HTML_META_KEY_DC_SUBJECT.equalsIgnoreCase( metaName ) ) {
pdfName = PdfName.SUBJECT;
pdfString = new PdfString( metaContent, PdfObject.TEXT_UNICODE );
this.pdfInfoValues.put( pdfName, pdfString );

} else if ( HTML_META_KEY_KEYWORDS.equalsIgnoreCase( metaName ) ) {
pdfName = PdfName.KEYWORDS;
pdfString = new PdfString( metaContent, PdfObject.TEXT_UNICODE );
this.pdfInfoValues.put( pdfName, pdfString );
}
}
}
}

/**
* Add PDF meta values to the target PDF document.
*/
private void addPdfMetaValuesToPdfDocument( ITextRenderer renderer ) {

Iterator pdfNameIter = this.pdfInfoValues.keySet().iterator();

while (pdfNameIter.hasNext()) {
PdfName pdfName = (PdfName) pdfNameIter.next();
PdfString pdfString = (PdfString) pdfInfoValues.get( pdfName );
XRLog.render(Level.FINEST, "pdfName=" + pdfName + ", pdfString=" + pdfString );
renderer.getOutputDevice().getWriter().getInfo().put( pdfName, pdfString );
}
if ( XRLog.isLoggingEnabled() ) {
XRLog.render(Level.FINEST, "added " + renderer.getOutputDevice().getWriter().getInfo().getKeys() );
}
}

}

0 comments on commit 22bfb27

Please sign in to comment.