blob: 5af417ed53906b043325aa2eb604e6f8bc98d905 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.render.rtf.rtflib.rtfdoc;
/*
* This file is part of the RTF library of the FOP project, which was originally
* created by Bertrand Delacretaz <bdelacretaz@codeconsult.ch> and by other
* contributors to the jfor project (www.jfor.org), who agreed to donate jfor to
* the FOP project.
*/
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import org.apache.commons.io.IOUtils;
import org.apache.fop.render.rtf.rtflib.tools.ImageConstants;
import org.apache.fop.render.rtf.rtflib.tools.ImageUtil;
/**
* <p>Creates an RTF image from an external graphic file.
* This class belongs to the <fo:external-graphic> tag processing.</p>
*
* <p>Supports relative path like "../test.gif", too (01-08-24)</p>
*
* <p>Limitations:</p>
* <ul>
* <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)
* </ul>
*
* <p>Known Bugs:</p>
* <ul>
* <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
* </ul>
*
* <p>This work was authored by Andreas Putz (a.putz@skynamics.com) and
* Gianugo Rabellino (gianugo@rabellino.it).</p>
*/
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);
}
}
//////////////////////////////////////////////////
// Supported Formats
//////////////////////////////////////////////////
private static class FormatBase {
/**
* Determines whether the image is in the according format.
*
* @param data Image
*
* @return
* true If according type\n
* false Other type
*/
public static boolean isFormat(byte[] data) {
return false;
}
/**
* Convert image data if necessary - for example when format is not supported by rtf.
*
* @param format Format type
* @param data Image
*/
public FormatBase convert(FormatBase format, byte[] data) {
return format;
}
/**
* Determine image file format.
*
* @param data Image
*
* @return Image format class
*/
public static FormatBase determineFormat(byte[] data) {
if (FormatPNG.isFormat(data)) {
return new FormatPNG();
} else if (FormatJPG.isFormat(data)) {
return new FormatJPG();
} else if (FormatEMF.isFormat(data)) {
return new FormatEMF();
} else if (FormatGIF.isFormat(data)) {
return new FormatGIF();
} else if (FormatBMP.isFormat(data)) {
return new FormatBMP();
} else {
return null;
}
}
/**
* Get image type.
*
* @return Image format class
*/
public int getType() {
return ImageConstants.I_NOT_SUPPORTED;
}
/**
* Get rtf tag.
*
* @return Rtf tag for image format.
*/
public String getRtfTag() {
return "";
}
}
private static class FormatGIF extends FormatBase {
public static boolean isFormat(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);
}
public int getType() {
return ImageConstants.I_GIF;
}
}
private static class FormatEMF extends FormatBase {
public static boolean isFormat(byte[] data) {
// No offical Indentifier known
byte [] pattern = new byte [] {(byte) 0x01, (byte) 0x00, (byte) 0x00};
return ImageUtil.compareHexValues(pattern, data, 0, true);
}
public int getType() {
return ImageConstants.I_EMF;
}
public String getRtfTag() {
return "emfblip";
}
}
private static class FormatBMP extends FormatBase {
public static boolean isFormat(byte[] data) {
byte [] pattern = new byte [] {(byte) 0x42, (byte) 0x4D};
return ImageUtil.compareHexValues(pattern, data, 0, true);
}
public int getType() {
return ImageConstants.I_BMP;
}
}
private static class FormatJPG extends FormatBase {
public static boolean isFormat(byte[] data) {
// Indentifier "0xFFD8" on position 0
byte [] pattern = new byte [] {(byte) 0xFF, (byte) 0xD8};
return ImageUtil.compareHexValues(pattern, data, 0, true);
}
public int getType() {
return ImageConstants.I_JPG;
}
public String getRtfTag() {
return "jpegblip";
}
}
private static class FormatPNG extends FormatBase {
public static boolean isFormat(byte[] data) {
// Indentifier "PNG" on position 1
byte [] pattern = new byte [] {(byte) 0x50, (byte) 0x4E, (byte) 0x47};
return ImageUtil.compareHexValues(pattern, data, 1, true);
}
public int getType() {
return ImageConstants.I_PNG;
}
public String getRtfTag() {
return "pngblip";
}
}
//////////////////////////////////////////////////
// @@ Members
//////////////////////////////////////////////////
/**
* The url of the image
*/
protected URL url;
/**
* The height of the image (in pixels)
*/
protected int height = -1;
/**
* The desired height (in twips)
*/
protected int heightDesired = -1;
/**
* Flag whether the desired height is a percentage
*/
protected boolean perCentH;
/**
* The width of the image (in pixels)
*/
protected int width = -1;
/**
* The desired width (in twips)
*/
protected int widthDesired = -1;
/**
* Flag whether the desired width is a percentage
*/
protected boolean perCentW;
/**
* Flag whether the image size shall be adjusted
*/
protected boolean scaleUniform;
/** cropping on left/top/right/bottom edges for \piccrop*N */
private int[] cropValues = new int[4];
/**
* Graphic compression rate
*/
protected int graphicCompressionRate = 80;
/** The image data */
private byte[] imagedata;
/** The image format */
private FormatBase imageformat;
//////////////////////////////////////////////////
// @@ 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
* @throws IOException for I/O problems
*/
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
* @throws IOException for I/O problems
*/
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
* @throws IOException for I/O problems
*/
protected void writeRtfContent() throws IOException {
try {
writeRtfContentWithException();
} catch (ExternalGraphicException ie) {
writeExceptionInRtf(ie);
}
}
/**
* Writes the RTF content to m_writer - this one throws ExternalGraphicExceptions
*
* @exception IOException On error
*/
protected void writeRtfContentWithException() throws IOException {
if (writer == null) {
return;
}
if (url == null && imagedata == null) {
throw new ExternalGraphicException(
"No image data is available (neither URL, nor in-memory)");
}
String linkToRoot = System.getProperty("jfor_link_to_root");
if (url != null && linkToRoot != null) {
writer.write("{\\field {\\* \\fldinst { INCLUDEPICTURE \"");
writer.write(linkToRoot);
File urlFile = new File(url.getFile());
writer.write(urlFile.getName());
writer.write("\" \\\\* MERGEFORMAT \\\\d }}}");
return;
}
// getRtfFile ().getLog ().logInfo ("Writing image '" + url + "'.");
if (imagedata == null) {
try {
final InputStream in = url.openStream();
try {
imagedata = IOUtils.toByteArray(url.openStream());
} finally {
IOUtils.closeQuietly(in);
}
} catch (Exception e) {
throw new ExternalGraphicException("The attribute 'src' of "
+ "<fo:external-graphic> has a invalid value: '"
+ url + "' (" + e + ")");
}
}
if (imagedata == null) {
return;
}
// Determine image file format
String file = (url != null ? url.getFile() : "<unknown>");
imageformat = FormatBase.determineFormat(imagedata);
if (imageformat != null) {
imageformat = imageformat.convert(imageformat, imagedata);
}
if (imageformat == null
|| imageformat.getType() == ImageConstants.I_NOT_SUPPORTED
|| "".equals(imageformat.getRtfTag())) {
throw new ExternalGraphicException("The tag <fo:external-graphic> "
+ "does not support "
+ file.substring(file.lastIndexOf(".") + 1)
+ " - image type.");
}
// Writes the beginning of the rtf image
writeGroupMark(true);
writeStarControlWord("shppict");
writeGroupMark(true);
writeControlWord("pict");
StringBuffer buf = new StringBuffer(imagedata.length * 3);
writeControlWord(imageformat.getRtfTag());
computeImageSize();
writeSizeInfo();
writeAttributes(getRtfAttributes(), null);
for (int i = 0; i < imagedata.length; i++) {
int iData = imagedata [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);
writer.write(chars);
// Writes the end of RTF image
writeGroupMark(false);
writeGroupMark(false);
}
private void computeImageSize() {
if (imageformat.getType() == ImageConstants.I_PNG) {
width = ImageUtil.getIntFromByteArray(imagedata, 16, 4, true);
height = ImageUtil.getIntFromByteArray(imagedata, 20, 4, true);
} else if (imageformat.getType() == ImageConstants.I_JPG) {
int basis = -1;
byte ff = (byte) 0xff;
byte c0 = (byte) 0xc0;
for (int i = 0; i < imagedata.length; i++) {
byte b = imagedata[i];
if (b != ff) {
continue;
}
if (i == imagedata.length - 1) {
continue;
}
b = imagedata[i + 1];
if (b != c0) {
continue;
}
basis = i + 5;
break;
}
if (basis != -1) {
width = ImageUtil.getIntFromByteArray(imagedata, basis + 2, 2, true);
height = ImageUtil.getIntFromByteArray(imagedata, basis, 2, true);
}
} else if (imageformat.getType() == ImageConstants.I_EMF) {
int i = 0;
i = ImageUtil.getIntFromByteArray(imagedata, 151, 4, false);
if (i != 0) {
width = i;
}
i = ImageUtil.getIntFromByteArray(imagedata, 155, 4, false);
if (i != 0) {
height = i;
}
}
}
private void writeSizeInfo() throws IOException {
// 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);
}
}
if (this.cropValues[0] != 0) {
writeOneAttribute("piccropl", new Integer(this.cropValues[0]));
}
if (this.cropValues[1] != 0) {
writeOneAttribute("piccropt", new Integer(this.cropValues[1]));
}
if (this.cropValues[2] != 0) {
writeOneAttribute("piccropr", new Integer(this.cropValues[2]));
}
if (this.cropValues[3] != 0) {
writeOneAttribute("piccropb", new Integer(this.cropValues[3]));
}
}
//////////////////////////////////////////////////
// @@ Member access
//////////////////////////////////////////////////
/**
* Sets the desired height of the image.
*
* @param theHeight The desired image height (as a string in twips or as a percentage)
*/
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 (as a string in twips or as a percentage)
*/
public void setWidth(String theWidth) {
this.widthDesired = ImageUtil.getInt(theWidth);
this.perCentW = ImageUtil.isPercent(theWidth);
}
/**
* Sets the desired width of the image.
* @param twips The desired image width (in twips)
*/
public void setWidthTwips(int twips) {
this.widthDesired = twips;
this.perCentW = false;
}
/**
* Sets the desired height of the image.
* @param twips The desired image height (in twips)
*/
public void setHeightTwips(int twips) {
this.heightDesired = twips;
this.perCentH = false;
}
/**
* 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) {
setUniformScaling("uniform".equalsIgnoreCase(value));
}
/**
* Sets the flag whether the image size shall be adjusted.
*
* @param uniform
* true image width or height shall be adjusted automatically\n
* false no adjustment
*/
public void setUniformScaling(boolean uniform) {
this.scaleUniform = uniform;
}
/**
* Sets cropping values for all four edges for the \piccrop*N commands.
* A positive value crops toward the center of the picture;
* a negative value crops away from the center, adding a space border around the picture
* @param left left cropping value (in twips)
* @param top top cropping value (in twips)
* @param right right cropping value (in twips)
* @param bottom bottom cropping value (in twips)
*/
public void setCropping(int left, int top, int right, int bottom) {
this.cropValues[0] = left;
this.cropValues[1] = top;
this.cropValues[2] = right;
this.cropValues[3] = bottom;
}
/**
* Sets the binary imagedata of the image.
*
* @param data binary imagedata as read from file.
* @throws IOException On error
*/
public void setImageData(byte[] data) throws IOException {
this.imagedata = data;
}
/**
* 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).toURI().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 if the compression rate is valid (0..100), false if invalid
*/
public boolean setCompressionRate(int percent) {
if (percent < 1 || percent > 100) {
return false;
}
graphicCompressionRate = percent;
return true;
}
//////////////////////////////////////////////////
// @@ Helpers
//////////////////////////////////////////////////
/**
* @return true if this element would generate no "useful" RTF content
*/
public boolean isEmpty() {
return url == null;
}
}