I started tinkering with using Flying Saucer to replace cfDocument in openbluedragon, I don't really have time to keep working on it right now so I thought I'd post what I accomplished in case others would like to continue the work, Notes follow and source is attached,
This replacement uses Flying Saucer, aka xhtmlrenderer, available from https://xhtmlrenderer.dev.java.net, in order to compile it you will need to build FS from cvs source as I had to contribute a patch to allow finer grained control of the PDF output encryption, it's been applied to trunk but they haven't made a release since then. The hooks for cfdocument are actually still in the openbluedragon source code, so you have to change the build scripts to bring in the iText and xhtmlrenderer jars, but not the actual tag parser code, see src/com/naryx/tagfusion/cfm/tag/tagChecker.java if you want the details on that, you'll either want to update the path there or place the attached source file at src/com/naryx/tagfusion/cfm/document/cfDOCUMENT.java (which is what I'd recommend) you'll need xhtmlrenderer and the version of iText it uses (2.0.8 I think) and you'll need to update your build.xml to include them in the final build and in the generated war file (There may be a better way to do this, I was fairly unfamiliar with the use of external libraries in openbluedragon), look in build.xml for the section <path id="project.class.build"> and add the iText/flyingsaucer jars there Yes I know there was some talk of using PDFBox for this purpose, I took a quick peek at it and didn't like what I saw for some reason, I don't remember why now, so feel free to discard this mail entirely if you'd prefer PDFBox Certain elements that are not supported in the tag attributes, can be set through css properties, most notably the orientation currently cannot be set in the attributed because xhtmlrenderer does not allow that fine grained of a control over the pdf rendered output, it should however support using css with @page { size: landscape; } to set a landscape mode. As well, because xhtmlrenderer supports drawing to a java swing surface, it should be fairly trivial to support outputting to any image format supported by the java libraries. The most important note is that somewhere the paths are screwy. referencing a uri at "./" will not work as I expected it to, and I never did sort out exactly where it thought it's paths were, I don't know enough about the BlueDragon internals to figure out how to resolve paths properly. so to reference css files or images or files, you'll either need to fiddle with the paths, or maybe try using external paths. Someone who knows more about the bluedragon internals should be able to figure this out without too much trouble. I wrote this about a month ago over two weekends, and haven't touched it since, I'm too busy to keep working on it, I'll answer any questions I can, but if this is going to become any more useful than it is, it needs to be finished by someone else. I just copied the gpl3 boilerplate from another file and put my name on it, feel free to fix any issues with it but please leave my name there. Hope this is useful to someone, Enjoy :) - Josh --~--~---------~--~----~------------~-------~--~----~ Open BlueDragon Public Mailing List http://groups.google.com/group/openbd?hl=en official blog @ http://blog.openbluedragon.org/ !! save a network - trim replies before posting !! -~----------~----~----~----~------~----~------~--~---
/* * Copyright (C) 2008 Josh Hayes-Sheen <[EMAIL PROTECTED]> * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbluedragon.org/ */ package com.naryx.tagfusion.cfm.document; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.xhtmlrenderer.pdf.ITextRenderer; import org.xhtmlrenderer.pdf.PDFEncryption; import org.xml.sax.SAXException; import com.lowagie.text.DocumentException; import com.lowagie.text.pdf.PdfWriter; import com.naryx.tagfusion.cfm.engine.cfBinaryData; import com.naryx.tagfusion.cfm.engine.cfSession; import com.naryx.tagfusion.cfm.engine.cfmBadFileException; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; import com.naryx.tagfusion.cfm.engine.variableStore; import com.naryx.tagfusion.cfm.tag.cfTag; import com.naryx.tagfusion.cfm.tag.cfTagReturnType; public class cfDOCUMENT extends cfTag implements Serializable { private static final long serialVersionUID = 1L; public boolean doesTagHaveEmbeddedPoundSigns() { return true; } public String getEndMarker() { return "</CFDOCUMENT>"; } protected void defaultParameters( String _tag ) throws cfmBadFileException { defaultAttribute( "OVERWRITE", "NO" ); defaultAttribute( "PAGETYPE", "LETTER" ); // Not yet Supported defaultAttribute( "ORIENTATION", "PORTRAIT" ); // Not yet Supported defaultAttribute( "UNIT", "IN" ); // Not yet Supported defaultAttribute( "ENCRYPTION", "NONE" ); // Supports 40, 128 and new value of AES defaultAttribute( "FONTEMBED", "NO" ); // Not yet supported defaultAttribute( "BACKGROUNDVISIBLE", "NO" ); // Not yet supported defaultAttribute( "MIMETYPE", "TEXT/HTML" ); // Not yet supported parseTagHeader(_tag); requiredAttribute( "FORMAT" ); // Only accepts PDF // setFlushable( false ); } /* I have to imagine there's a better way to do this :-\ */ public String getURI(cfSession _Session) { StringBuilder URIb = new StringBuilder(""); URIb.append("file://"); // URIb.append( _Session.getQualifiedData(variableStore.CGI_SCOPE).getData("http_host").toString() ); URIb.append( _Session.getQualifiedData(variableStore.CGI_SCOPE).getData("context_path").toString() + "/"); return URIb.toString(); } public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException { ITextRenderer renderer = new ITextRenderer(); if ( !getDynamic( _Session, "FORMAT" ).toString().equalsIgnoreCase("PDF") ) { throw newRunTimeException( "Output format \"" + getDynamic( _Session, "FORMAT" ).toString() + "\"is Unsupported!" ); } if ( renderToString( _Session ).getOutput().trim().equals("") ) { throw newRunTimeException( "Cannot create a PDF from an empty document!" ); } try { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); InputStream is = new ByteArrayInputStream(renderToString( _Session ).getOutput().getBytes()); Document doc = builder.parse( is ); renderer.setDocument( doc, getURI(_Session) ); renderer.layout(); ByteArrayOutputStream mOutputPDF = new ByteArrayOutputStream(); if ( ( getDynamic( _Session, "ENCRYPTION" ).toString().equals("128") || getDynamic( _Session, "ENCRYPTION" ).toString().equals("40") || getDynamic( _Session, "ENCRYPTION" ).toString().equals("AES") ) || containsAttribute("OWNERPASSWORD") || containsAttribute("USERPASSWORD") || containsAttribute("PERMISSIONS") ) { PDFEncryption mEnc = new PDFEncryption(); // The password required to make permission changes to the document if ( containsAttribute("OWNERPASSWORD") ) mEnc.setOwnerPassword(getDynamic( _Session, "OWNERPASSWORD" ).toString().getBytes()); else mEnc.setOwnerPassword(null); // The password required to open the document if ( containsAttribute("USERPASSWORD") ) mEnc.setUserPassword(getDynamic( _Session, "USERPASSWORD" ).toString().getBytes()); else mEnc.setUserPassword(null); int PermMask = 0; if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWPRINTING") ) { PermMask |= PdfWriter.ALLOW_PRINTING; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWMODIFYCONTENTS") ) { PermMask |= PdfWriter.ALLOW_MODIFY_CONTENTS; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWCOPY") ) { PermMask |= PdfWriter.ALLOW_COPY; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWMODIFYANNOTATIONS") ) { PermMask |= PdfWriter.ALLOW_MODIFY_ANNOTATIONS; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWFILLIN") ) { PermMask |= PdfWriter.ALLOW_FILL_IN; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWSCREENREADERS") ) { PermMask |= PdfWriter.ALLOW_SCREENREADERS; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWASSEMBLY") ) { PermMask |= PdfWriter.ALLOW_ASSEMBLY; } if ( getDynamic( _Session, "PERMISSIONS" ).toString().toUpperCase().contains("ALLOWDEGRADEDPRINTING") ) { PermMask |= PdfWriter.ALLOW_DEGRADED_PRINTING; } // If the user set permissions, set them, otherwise just leave the defaults if ( PermMask != 0 ) mEnc.setAllowedPrivileges(PermMask); if ( getDynamic( _Session, "ENCRYPTION" ).toString().equals("40") ) { mEnc.setEncryptionType(PdfWriter.STANDARD_ENCRYPTION_40); } else if ( getDynamic( _Session, "ENCRYPTION" ).toString().equals("128") ) { mEnc.setEncryptionType(PdfWriter.STANDARD_ENCRYPTION_128); } else if ( getDynamic( _Session, "ENCRYPTION" ).toString().equals("AES") ) { mEnc.setEncryptionType(PdfWriter.ENCRYPTION_AES_128); } else if ( containsAttribute("OWNERPASSWORD") || containsAttribute("USERPASSWORD") || containsAttribute("PERMISSIONS") ) { // Default to RC40 (The most universally compatible PDF encryption scheme) if security attributes are set // but no encryption type is chosen. mEnc.setEncryptionType(PdfWriter.STANDARD_ENCRYPTION_40); } renderer.setPDFEncryption( mEnc ); } renderer.createPDF(mOutputPDF); if ( containsAttribute("FILENAME") ) // Write to the Filesystem { File localFile = new File(getDynamic( _Session, "FILENAME" ).getString()); if ( localFile.exists() && !getDynamic( _Session, "OVERWRITE" ).getBoolean() ) { throw new IOException("File \"" + localFile.getCanonicalPath() + "\" Already Exists"); } FileOutputStream mFile = new FileOutputStream(localFile); mFile.write(mOutputPDF.toByteArray()); mFile.close(); } else if ( containsAttribute("NAME") ) // Store to a variable { String NAME = getDynamic( _Session, "NAME").getString().trim(); if ( NAME.length() == 0 ) throw newRunTimeException( "Invalid NAME attribute" ); _Session.setData( NAME, new cfBinaryData( mOutputPDF.toByteArray() ) ); } else // Output to the Browser { _Session.resetBuffer(); _Session.setContentType( "APPLICATION/PDF" ); _Session.write(mOutputPDF.toByteArray()); _Session.pageFlush(); _Session.abortPageProcessing(); } } catch (IOException e) { throw newRunTimeException( e.toString() ); } catch (DocumentException e) { throw newRunTimeException( e.toString() ); } catch (ParserConfigurationException e) { throw newRunTimeException( e.toString() ); } catch (SAXException e) { throw newRunTimeException( e.toString() ); } return cfTagReturnType.NORMAL; } }
