RTFExternalGraphic fixed to attempt to load image resource from classpath if all else fails

Joy Alamgir <[email protected]> Tue, 14 Dec 2004 12:26:38 -0500
Newsgroups gmane.text.xml.jfor.cvs,gmane.text.xml.jfor.devel
Message-ID <[email protected]>
--=====================_58979337==_
Content-Type: text/plain; charset="us-ascii"; format=flowed


Attached file.

Helpful for the following reason:

Code right now appends current directory to the file location mentioned in 
an XSL file.

Current directory is dependent on the shell/batch script that ran the 
application server, not necessarily the root of the application server, or 
webapps/application directory

in order to work around it, its useful to try to load the image resource 
from the classpath as a last resort

kindly please consider for inclusion within jfor project

regs,

joy



Joy Alamgir
email: [email protected] 
--=====================_58979337==_
Content-Type: text/plain; charset="us-ascii"
Content-Disposition: attachment; filename="RtfExternalGraphic.java"

package org.jfor.jfor.rtflib.rtfdoc;

import org.jfor.jfor.rtflib.rtfdoc.RtfElement;
import org.jfor.jfor.rtflib.rtfdoc.RtfContainer;
import org.jfor.jfor.rtflib.rtfdoc.RtfAttributes;

import org.jfor.jfor.tools.ImageConstants;
import org.jfor.jfor.tools.ImageUtil;
import org.jfor.jfor.tools.jpeg.JpegEncoderFactory;
import org.jfor.jfor.tools.jpeg.JPEGException;
import org.jfor.jfor.tools.jpeg.IJpegEncoder;

import java.io.BufferedInputStream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Writer;

import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;

/*-----------------------------------------------------------------------------
 * jfor - Open-Source XSL-FO to RTF converter - see www.jfor.org
 *
 * ====================================================================
 * jfor Apache-Style Software License.
 * Copyright (c) 2002 by the jfor project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in
 * the documentation and/or other materials provided with the
 * distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 * if any, must include the following acknowledgment:
 * "This product includes software developed
 * by the jfor project (http://www.jfor.org)."
 * Alternately, this acknowledgment may appear in the software itself,
 * if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The name "jfor" must not be used to endorse
 * or promote products derived from this software without prior written
 * permission.  For written permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "jfor",
 * nor may "jfor" appear in their name, without prior written
 * permission of [email protected].
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE JFOR PROJECT OR ITS CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 * Contributor(s):
 *  @author Gianugo Rabellino [email protected]
-----------------------------------------------------------------------------*/

/**
 * Creates an RTF image from an external graphic file.
 * This class belongs to the <fo:external-graphic> tag processing. <br>
 *
 * Supports relative path like "../test.gif", too (01-08-24) <br>
 * 12/13/2004: Extended to support retrieval of files from classpath <br>
 *
 * Limitations:
 * <li>    Only the image types PNG, JPEG and EMF are supported
 * <li>    The GIF is supported, too, but will be converted to JPG
 * <li>    Only the attributes SRC (required), WIDTH, HEIGHT, SCALING are supported
 * <li>    The SCALING attribute supports (uniform | non-uniform)
 *
 * Known Bugs:
 * <li>    If the emf image has a desired size, the image will be clipped
 * <li>    The emf, jpg & png image will not be displayed in correct size
 *
 *  @author <a href="mailto:[email protected]">Andreas Putz</a>
 */

public class RtfExternalGraphic extends RtfElement
{
	/** Exception thrown when an image file/URL cannot be read */
	public static class ExternalGraphicException extends IOException
	{
		ExternalGraphicException(String reason)
		{
			super(reason);
		}
	}

	//////////////////////////////////////////////////
	// @@ Members
	//////////////////////////////////////////////////


	/**
	 * The url of the image
	 */
	protected URL url = null;

	/**
	 * The height of the image
	 */
	protected int height = -1;

	/**
	 * The desired percent value of the height
	 */
	protected int heightPercent = -1;

	/**
	 * The desired height
	 */
	protected int heightDesired = -1;

	/**
	 * Flag whether the desired height is a percentage
	 */
	protected boolean perCentH = false;

	/**
	 * The width of the image
	 */
	protected int width = -1;

	/**
	 * The desired percent value of the width
	 */
	protected int widthPercent = -1;

	/**
	 * The desired width
	 */
	protected int widthDesired = -1;

	/**
	 * Flag whether the desired width is a percentage
	 */
	protected boolean perCentW = false;

	/**
	 * Flag whether the image size shall be adjusted
	 */
	protected boolean scaleUniform = false;

	/**
	 * Graphic compression rate
	 */
	 protected int graphicCompressionRate = 80;


	//////////////////////////////////////////////////
	// @@ Construction
	//////////////////////////////////////////////////


	/**
	 * Default constructor.
	 * Create an RTF element as a child of given container.
	 *
	 * @param container a <code>RtfContainer</code> value
	 * @param writer a <code>Writer</code> value
	 */
	public RtfExternalGraphic(RtfContainer container, Writer writer) throws IOException {
		super (container, writer);
	}

	/**
	 * Default constructor.
	 *
	 * @param container a <code>RtfContainer</code> value
	 * @param writer a <code>Writer</code> value
	 * @param attributes a <code>RtfAttributes</code> value
	 */
	public RtfExternalGraphic(RtfContainer container, Writer writer,
	RtfAttributes attributes) throws IOException {
		super (container, writer, attributes);
	}


	//////////////////////////////////////////////////
	// @@ RtfElement implementation
	//////////////////////////////////////////////////

		/** RtfElement override - catches ExternalGraphicException and writes a warning
		 *  message to the document if image cannot be read
		 */
	protected void writeRtfContent() throws IOException {
			try {
				writeRtfContentWithException();
			} catch(ExternalGraphicException ie) {
				writeExceptionInRtf(ie);
			}
		}

	/**
	 * Writes the RTF content to m_writer - this one throws ExternalGraphicExceptions
     *
     * Modified by JA to handle loading of resources from classpath
	 *
	 * @exception IOException On error
	 */
	protected void writeRtfContentWithException() throws IOException {

		if (m_writer == null) {
			return;
		}


		if (url == null) {
			throw new ExternalGraphicException("The attribute 'url' of <fo:external-graphic> is null.");
		}

        String linkToRoot = System.getProperty( "jfor_link_to_root" );
        if (linkToRoot != null)
        {
            m_writer.write( "{\\field {\\* \\fldinst { INCLUDEPICTURE \"" );
            m_writer.write( linkToRoot );
            File urlFile = new File( url.getFile() );
            m_writer.write( urlFile.getName() );
            m_writer.write( "\" \\\\* MERGEFORMAT \\\\d }}}" );
            return;
        }

		getRtfFile ().getLog ().logInfo ("Writing image '" + url + "'.");


		byte[] data = null;
		try
		{
            // image reading patch provided by Michael Krause <[email protected]>
            final BufferedInputStream bin = new BufferedInputStream(url.openStream());
            final ByteArrayOutputStream bout = new ByteArrayOutputStream();
          	while (true) {
          		final int datum = bin.read();
             	if (datum == -1) break;
              	bout.write(datum);
             }
             bout.flush();
             data = bout.toByteArray();
		}
		catch (Exception e) {
            //try retrieving from classpath if URL retrieval failed
            boolean retrievedFromClassPath = false;
            System.out.println("Trying claspath retrieval complete url is:"+url.toString()+", protocol is:"+url.getProtocol());
            try{
                //load from classpath only if retrieving from a file
                if("file".equalsIgnoreCase(url.getProtocol())){
                    File basefl = new File("does_not_exist");
                    URL baseflUrl = basefl.toURL();
                    String baseflUrlString = baseflUrl.toString();
                    String commonPrefix = baseflUrlString.substring(0,baseflUrlString.indexOf("does_not_exist"));
                    System.out.println("common url prefix is:"+commonPrefix);

                    //retrieve the non-common part of the passed url
                    String nonCommonPartOfURL = url.toString().substring(commonPrefix.length());
                    System.out.println("nonCommonPartOfURL:"+nonCommonPartOfURL);

                    URL fileURL = this.getClass().getClassLoader().getResource(nonCommonPartOfURL);

                    // image reading patch provided by Joy Alamgir <[email protected]>
                    final BufferedInputStream bin = new BufferedInputStream(fileURL.openStream());
                    final ByteArrayOutputStream bout = new ByteArrayOutputStream();
                    while (true) {
                        final int datum = bin.read();
                        if (datum == -1) break;
                        bout.write(datum);
                     }
                     bout.flush();
                     data = bout.toByteArray();
                     retrievedFromClassPath = true;
                }
            }catch(Exception e2){
                System.out.println("URL retrieval from classpath failed for url"+url+", message:"+e.getMessage());
            }

            if(!retrievedFromClassPath){
			    throw new ExternalGraphicException("The attribute 'src' of <fo:external-graphic> has a invalid value: '" + url + "' (" + e + ")");
            }
		}

		if (data == null) {
			return;
		}

		// Determine image file format
		String file = url.getFile ();
		int type = determineImageType(data, file.substring(file.lastIndexOf(".") + 1));

		if (type >= ImageConstants.I_TO_CONVERT_BASIS) {
			// convert
			int to = ImageConstants.CONVERT_TO [type - ImageConstants.I_TO_CONVERT_BASIS];

			if (to == ImageConstants.I_JPG) {
				try {
					//convert to jpeg
					final IJpegEncoder jpgEncoder = new JpegEncoderFactory().getEncoder();
                    data = jpgEncoder.encodeJPEG(graphicCompressionRate,data);
					type = to;
				}
				catch (JPEGException e) {
                    throw new IOException("JPEG conversion error, src = '" + url + "' (" + e + ")");
				}
			}
			else {
				type = ImageConstants.I_NOT_SUPPORTED;
			}
		}


		if (type == ImageConstants.I_NOT_SUPPORTED) {
			throw new ExternalGraphicException("The tag <fo:external-graphic> does not support " + file.substring(file.lastIndexOf(".") + 1) + " - image type.");
		}

		String rtfImageCode = ImageConstants.RTF_TAGS [type];

		// Writes the beginning of the rtf image

		writeGroupMark(true);
		writeStarControlWord("shppict");
		writeGroupMark(true);
		writeControlWord("pict");

		StringBuffer buf = new StringBuffer(data.length * 3);

		writeControlWord(rtfImageCode);

		if (type == ImageConstants.I_PNG) {
			width = ImageUtil.getIntFromByteArray(data, 16, 4, true);
			height = ImageUtil.getIntFromByteArray(data, 20, 4, true);
		}
		else if (type == ImageConstants.I_JPG) {
			int basis = -1;
			byte ff = (byte) 0xff;
			byte c0 = (byte) 0xc0;
			for (int i = 0; i < data.length; i++) {
				byte b = data[i];
				if (b != ff)
					continue;
				if (i == data.length - 1)
					continue;
				b = data[i + 1];
				if (b != c0)
					continue;
				basis = i + 5;
				break;
			}

			if (basis != -1) {
				width = ImageUtil.getIntFromByteArray(data, basis + 2, 2, true);
				height = ImageUtil.getIntFromByteArray(data, basis, 2, true);
			}
		}
		else if (type == ImageConstants.I_EMF) {
			width = ImageUtil.getIntFromByteArray(data, 151, 4, false);
			height = ImageUtil.getIntFromByteArray(data, 155, 4, false);
		}

		// Set image size
		if (width != -1) {
			writeControlWord("picw" + width);
		}
		if (height != -1) {
			writeControlWord("pich" + height);
		}

		if (widthDesired != -1) {
			if (perCentW) {
				writeControlWord("picscalex" + widthDesired);
			}
			else {
				writeControlWord("picscalex" + widthDesired * 100 / width);
			}

			writeControlWord("picwgoal" + widthDesired);
		}
		else if (scaleUniform && heightDesired != -1) {
			if (perCentH) {
				writeControlWord("picscalex" + heightDesired);
			}
			else {
				writeControlWord("picscalex" + heightDesired * 100 / height);
			}
		}

		if (heightDesired != -1) {
			if (perCentH) {
				writeControlWord("picscaley" + heightDesired);
			}
			else {
				writeControlWord("picscaley" + heightDesired * 100 / height);
			}

			writeControlWord("pichgoal" + heightDesired);
		}
		else if (scaleUniform && widthDesired != -1) {
			if (perCentW) {
				writeControlWord("picscaley" + widthDesired);
			}
			else {
				writeControlWord("picscaley" + widthDesired * 100 / width);
			}
		}

		for (int i = 0; i < data.length; i++) {
			int iData = data [i];

			// Make positive byte
			if (iData < 0)
				iData += 256;

			if (iData < 16) {
				// Set leading zero and append
				buf.append('0');
			}

			buf.append(Integer.toHexString(iData));
		}

		int len = buf.length();
		char[] chars = new char[len];

		buf.getChars(0, len, chars, 0);
		m_writer.write(chars);

		// Writes the end of RTF image

		writeGroupMark(false);
		writeGroupMark(false);
	}


	//////////////////////////////////////////////////
	// @@ Member access
	//////////////////////////////////////////////////

	/**
	 * Sets the desired height of the image.
	 *
	 * @param theHeight The desired image height
	 */
	public void setHeight(String theHeight) {
		this.heightDesired = ImageUtil.getInt(theHeight);
		this.perCentH = ImageUtil.isPercent(theHeight);
	}

	/**
	 * Sets the desired width of the image.
	 *
	 * @param theWidth The desired image width
	 */
	public void setWidth(String theWidth) {
		this.widthDesired = ImageUtil.getInt(theWidth);
		this.perCentW = ImageUtil.isPercent(theWidth);
	}

	/**
	 * Sets the flag whether the image size shall be adjusted.
	 *
	 * @param value
	 * true    image width or height shall be adjusted automatically\n
	 * false   no adjustment
	 */
	public void setScaling(String value) {
		if (value.equalsIgnoreCase("uniform")) {
			this.scaleUniform = true;
		}
	}

	/**
	 * Sets the url of the image.
	 *
	 * @param urlString Image url like "file://..."
	 * @throws IOException On error
	 */
	public void setURL(String urlString) throws IOException
	{
		URL tmpUrl = null;
		try
		{
			tmpUrl = new URL (urlString);
		}
		catch (MalformedURLException e)
		{
			try
			{
				tmpUrl = new File (urlString).toURL ();
			}
			catch (MalformedURLException ee)
			{
				throw new ExternalGraphicException("The attribute 'src' of <fo:external-graphic> has a invalid value: '" + urlString + "' (" + ee + ")");
			}
		}
		this.url = tmpUrl;
	}

	/**
	 * Gets  the compression rate for the image in percent.
	 * @return Compression rate
	 */
	public int getCompressionRate ()
	{
		return graphicCompressionRate;
	}

	/**
	 * Sets the compression rate for the image in percent.
	 *
	 * @param percent Compression rate
	 * @return
	 *  true:   The compression rate is valid (0..100)\n
	 *  false:  The compression rate is invalid
	 */
	public boolean setCompressionRate (int percent)
	{
		if (percent < 1 || percent > 100)
			return false;

		graphicCompressionRate = percent;
		return true;
	}


	//////////////////////////////////////////////////
	// @@ Helpers
	//////////////////////////////////////////////////


	/**
	 * Determines wheter the image is a jpeg.
	 *
	 * @param data Image
	 *
	 * @return
	 * true    If JPEG type\n
	 * false   Other type
	 */
	private boolean isJPEG(byte[] data) {
		// Indentifier "0xFFD8" on position 0
		byte [] pattern = new byte [] {(byte) 0xFF, (byte) 0xD8};

		return ImageUtil.compareHexValues(pattern, data, 0, true);
	}

	/**
	 * Determines wheter the image is a png.
	 *
	 * @param data Image
	 *
	 * @return
	 * true    If PNG type\n
	 * false   Other type
	 */
	private boolean isPNG(byte[] data) {
		// Indentifier "PNG" on position 1
		byte [] pattern = new byte [] {(byte) 0x50, (byte) 0x4E, (byte) 0x47};

		return ImageUtil.compareHexValues(pattern, data, 1, true);
	}

	/**
	 * Determines wheter the image is a emf.
	 *
	 * @param data Image
	 *
	 * @return
	 * true    If EMF type\n
	 * false   Other type
	 */
	private boolean isEMF(byte[] data) {
		// No offical Indentifier known
		byte [] pattern = new byte [] {(byte) 0x01, (byte) 0x00, (byte) 0x00};

		return ImageUtil.compareHexValues(pattern, data, 0, true);
	}

	/**
	 * Determines wheter the image is a gif.
	 *
	 * @param data Image
	 *
	 * @return
	 * true    If GIF type\n
	 * false   Other type
	 */
	private boolean isGIF(byte[] data) {
		// Indentifier "GIF8" on position 0
		byte [] pattern = new byte [] {(byte) 0x47, (byte) 0x49, (byte) 0x46, (byte) 0x38};

		return ImageUtil.compareHexValues(pattern, data, 0, true);
	}

	/**
	 * Determines wheter the image is a gif.
	 *
	 * @param data Image
	 *
	 * @return
	 * true    If BMP type\n
	 * false   Other type
	 */
	private boolean isBMP(byte[] data) {
		// Indentifier "BM" on position 0
		byte [] pattern = new byte [] {(byte) 0x42, (byte) 0x4D};

		return ImageUtil.compareHexValues(pattern, data, 0, true);
	}

	/**
	 * Determine image file format.
	 *
	 * @param data Image
	 * @param ext Image extension
	 *
	 * @return Image type by ImageConstants.java
	 */
	private int determineImageType(byte [] data, String ext) {
		int type = ImageConstants.I_NOT_SUPPORTED;

		if (isPNG(data)) {
			type = ImageConstants.I_PNG;
		}
		else if (isJPEG(data)) {
			type = ImageConstants.I_JPG_C;
		}
		else if (isEMF(data)) {
			type = ImageConstants.I_EMF;
		}
		else if (isGIF(data)) {
			type = ImageConstants.I_GIF;
		}
		else {
			Object tmp = ImageConstants.SUPPORTED_IMAGE_TYPES.get(ext.toLowerCase());
			if (tmp != null) {
				type = ((Integer) tmp).intValue();
			}
		}

		return type;
	}

	/** true if this element would generate no "useful" RTF content */
	public boolean isEmpty()
	{
		return url==null;
	}
}

--=====================_58979337==_--




-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users.
Discover which products truly live up to the hype. Start reading now. 
http://productguide.itmanagersjournal.com/