org.apache.fop.render.rtf.rtflib.rtfdoc.RtfExternalGraphic Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fop Show documentation
Show all versions of fop Show documentation
Apache FOP (Formatting Objects Processor) is the world's first print formatter driven by XSL formatting objects (XSL-FO) and the world's first output independent formatter. It is a Java application that reads a formatting object (FO) tree and renders the resulting pages to a specified output. Output formats currently supported include PDF, PCL, PS, AFP, TIFF, PNG, SVG, XML (area tree representation), Print, AWT and TXT. The primary output target is PDF.
/*
* 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: RtfExternalGraphic.java 1805173 2017-08-16 10:50:04Z ssteiner $ */
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 [email protected] 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;
/**
* Creates an RTF image from an external graphic file.
* This class belongs to the fo:external-graphic tag processing.
*
* Supports relative path like "../test.gif", too (01-08-24)
*
* Limitations:
* Only the image types PNG, JPEG and EMF are supported
* The GIF is supported, too, but will be converted to JPG
* Only the attributes SRC (required), WIDTH, HEIGHT, SCALING are supported
* The SCALING attribute supports (uniform | non-uniform)
*
* Known Bugs:
* If the emf image has a desired size, the image will be clipped
* The emf, jpg, png image will not be displayed in correct size
*
* This work was originally authored by Andreas Putz
* This work was originally authored by Gianugo Rabellino [email protected]
*/
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 RtfContainer
value
* @param writer a Writer
value
* @throws IOException for I/O problems
*/
public RtfExternalGraphic(RtfContainer container, Writer writer) throws IOException {
super(container, writer);
}
/**
* Default constructor.
*
* @param container a RtfContainer
value
* @param writer a Writer
value
* @param attributes a RtfAttributes
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 "
+ " has a invalid value: '"
+ url + "' (" + e + ")");
}
}
if (imagedata == null) {
return;
}
// Determine image file format
String file = (url != null ? url.getFile() : "");
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 "
+ "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 (byte anImagedata : imagedata) {
int iData = anImagedata;
// 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", this.cropValues[0]);
}
if (this.cropValues[1] != 0) {
writeOneAttribute("piccropt", this.cropValues[1]);
}
if (this.cropValues[2] != 0) {
writeOneAttribute("piccropr", this.cropValues[2]);
}
if (this.cropValues[3] != 0) {
writeOneAttribute("piccropb", 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 "
+ " 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;
}
}