From fd263a114c84c3756ead9c16b8b088531c5cab2c Mon Sep 17 00:00:00 2001
From: Peter Hancock
Date: Tue, 1 Nov 2011 12:20:21 +0000
Subject: [PATCH] Bugzilla#52089: Allow JPEG images to be embedded in an AFP
document as is, without being decoded and encoded. It also allows lossy JPEG
compression.
Patch by Jeremias Maerki and Mehdi Houshmand.
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1195952 13f79535-47bb-0310-9956-ffa450edef68
---
.../content/xdocs/trunk/output.xml | 17 ++
.../org.apache.fop.render.ImageHandler | 1 +
.../apache/fop/afp/AFPDataObjectFactory.java | 3 +
.../fop/afp/AFPDitheredRectanglePainter.java | 12 +-
.../apache/fop/afp/AFPImageObjectInfo.java | 3 +-
.../org/apache/fop/afp/AFPObjectAreaInfo.java | 91 ++++----
.../org/apache/fop/afp/AFPPaintingState.java | 43 ++++
.../apache/fop/afp/AFPResourceManager.java | 3 +-
.../org/apache/fop/afp/fonts/AFPFontInfo.java | 8 +-
.../org/apache/fop/afp/ioca/ImageContent.java | 20 +-
.../fop/afp/modca/AbstractDataObject.java | 4 +-
.../apache/fop/afp/modca/GraphicsObject.java | 2 +-
.../apache/fop/afp/modca/ObjectContainer.java | 2 +-
.../org/apache/fop/afp/modca/Registry.java | 8 +-
.../fop/render/ImageHandlerRegistry.java | 32 ++-
.../fop/render/afp/AFPCustomizable.java | 17 ++
.../fop/render/afp/AFPDocumentHandler.java | 10 +
.../fop/render/afp/AFPImageHandler.java | 11 +-
.../afp/AFPImageHandlerRawCCITTFax.java | 19 ++
.../render/afp/AFPImageHandlerRawJPEG.java | 205 ++++++++++++++++++
.../render/afp/AFPImageHandlerRawStream.java | 21 ++
.../afp/AFPImageHandlerRenderedImage.java | 37 +++-
.../render/afp/AFPRendererConfigurator.java | 44 +++-
.../apache/fop/render/afp/AFPSVGHandler.java | 17 +-
.../afp/AbstractAFPImageHandlerRawStream.java | 3 +-
status.xml | 4 +
.../fop/afp/AFPObjectAreaInfoTestCase.java | 76 +++++++
.../fop/afp/AFPPaintingStateTestCase.java | 61 ++++++
.../fop/afp/AFPResourceManagerTestCase.java | 89 ++++++++
.../java/org/apache/fop/afp/AFPTestSuite.java | 9 +-
.../afp/AFPRendererConfiguratorTestCase.java | 92 ++++++++
.../apache/fop/render/afp/AFPTestSuite.java | 4 +-
.../conf/afp/bitmap_encode_quality.xconf | 16 ++
test/resources/conf/afp/can_embed_jpeg.xconf | 16 ++
test/resources/conf/afp/no_image_config.xconf | 13 ++
35 files changed, 874 insertions(+), 139 deletions(-)
create mode 100644 src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java
create mode 100644 test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java
create mode 100644 test/java/org/apache/fop/afp/AFPPaintingStateTestCase.java
create mode 100644 test/java/org/apache/fop/afp/AFPResourceManagerTestCase.java
create mode 100644 test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java
create mode 100644 test/resources/conf/afp/bitmap_encode_quality.xconf
create mode 100644 test/resources/conf/afp/can_embed_jpeg.xconf
create mode 100644 test/resources/conf/afp/no_image_config.xconf
diff --git a/src/documentation/content/xdocs/trunk/output.xml b/src/documentation/content/xdocs/trunk/output.xml
index 7b245eb07..cf88bd321 100644
--- a/src/documentation/content/xdocs/trunk/output.xml
+++ b/src/documentation/content/xdocs/trunk/output.xml
@@ -796,6 +796,23 @@ Note that the value of the encoding attribute in the example is the double-byte
+
+ By default, JPEG images are rasterized to a bitmap and the bitmap is included in the AFP doc.
+ However it is possible to encode in a lossless way to maintain maximum quality. But due
+ to lack of support for compression schemes like LZW (patent concerns), bitmap data is currently
+ not compressed resulting in large AFP files. Using the "allow-embedding" attribute on jpeg child
+ element allows the user to pass the JPEG as is in the document. The default is set to "false" since
+ there are compatibility concerns as some AFP printers don't support JPEG decoding. Using the
+ "bitmap-encoding-quality" attribute it is possible to enable lossy compression (JPEG baseline
+ DCT). The default is "1.0" which means lossless encoding. Setting a value lower than 1.0, JPEG
+ compression is enabled and the setting is used as the quality setting when encoding bitmap data.
+ Note that this setting does not always have an effect. Bi-level (1 bit) bitmaps are not compressed
+ using JPEG. Example:
+
+
GOCA (Vector Graphics)
diff --git a/src/java/META-INF/services/org.apache.fop.render.ImageHandler b/src/java/META-INF/services/org.apache.fop.render.ImageHandler
index efa40ebf6..f9b890c8e 100644
--- a/src/java/META-INF/services/org.apache.fop.render.ImageHandler
+++ b/src/java/META-INF/services/org.apache.fop.render.ImageHandler
@@ -17,4 +17,5 @@ org.apache.fop.render.afp.AFPImageHandlerRenderedImage
org.apache.fop.render.afp.AFPImageHandlerGraphics2D
org.apache.fop.render.afp.AFPImageHandlerRawStream
org.apache.fop.render.afp.AFPImageHandlerRawCCITTFax
+org.apache.fop.render.afp.AFPImageHandlerRawJPEG
org.apache.fop.render.afp.AFPImageHandlerSVG
diff --git a/src/java/org/apache/fop/afp/AFPDataObjectFactory.java b/src/java/org/apache/fop/afp/AFPDataObjectFactory.java
index 615c22b17..17114ebbe 100644
--- a/src/java/org/apache/fop/afp/AFPDataObjectFactory.java
+++ b/src/java/org/apache/fop/afp/AFPDataObjectFactory.java
@@ -108,6 +108,9 @@ public class AFPDataObjectFactory {
case TIFFImage.COMP_FAX_G4_2D:
imageObj.setEncoding(ImageContent.COMPID_G3_MMR);
break;
+ case ImageContent.COMPID_JPEG:
+ imageObj.setEncoding((byte)compression);
+ break;
default:
throw new IllegalStateException(
"Invalid compression scheme: " + compression);
diff --git a/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java b/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java
index 79e4979fd..d1c693afb 100644
--- a/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java
+++ b/src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java
@@ -90,22 +90,18 @@ public class AFPDitheredRectanglePainter extends AbstractAFPPainter {
imageObjectInfo.setData(dither);
//Positioning
- AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo();
int rotation = paintingState.getRotation();
AffineTransform at = paintingState.getData().getTransform();
Point2D origin = at.transform(new Point2D.Float(
rectanglePaintInfo.getX() * 1000,
rectanglePaintInfo.getY() * 1000), null);
- objectAreaInfo.setX((int)Math.round(origin.getX()));
- objectAreaInfo.setY((int)Math.round(origin.getY()));
AFPUnitConverter unitConv = paintingState.getUnitConverter();
float width = unitConv.pt2units(rectanglePaintInfo.getWidth());
float height = unitConv.pt2units(rectanglePaintInfo.getHeight());
- objectAreaInfo.setWidth(Math.round(width));
- objectAreaInfo.setHeight(Math.round(height));
- objectAreaInfo.setHeightRes(resolution);
- objectAreaInfo.setWidthRes(resolution);
- objectAreaInfo.setRotation(rotation);
+ AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(
+ (int) Math.round(origin.getX()),
+ (int) Math.round(origin.getY()),
+ Math.round(width), Math.round(height), resolution, rotation);
imageObjectInfo.setObjectAreaInfo(objectAreaInfo);
//Create rectangle
diff --git a/src/java/org/apache/fop/afp/AFPImageObjectInfo.java b/src/java/org/apache/fop/afp/AFPImageObjectInfo.java
index 7aee3cda8..0dca97513 100644
--- a/src/java/org/apache/fop/afp/AFPImageObjectInfo.java
+++ b/src/java/org/apache/fop/afp/AFPImageObjectInfo.java
@@ -85,7 +85,7 @@ public class AFPImageObjectInfo extends AFPDataObjectInfo {
* @return true if this image uses compression
*/
public boolean hasCompression() {
- return compression > -1;
+ return compression != -1;
}
/**
@@ -123,6 +123,7 @@ public class AFPImageObjectInfo extends AFPDataObjectInfo {
}
/** {@inheritDoc} */
+ @Override
public String toString() {
return "AFPImageObjectInfo{" + super.toString()
+ ", compression=" + compression
diff --git a/src/java/org/apache/fop/afp/AFPObjectAreaInfo.java b/src/java/org/apache/fop/afp/AFPObjectAreaInfo.java
index 963424470..1ecffc2df 100644
--- a/src/java/org/apache/fop/afp/AFPObjectAreaInfo.java
+++ b/src/java/org/apache/fop/afp/AFPObjectAreaInfo.java
@@ -24,66 +24,60 @@ package org.apache.fop.afp;
* dimensions and resolutions of data objects.
*/
public class AFPObjectAreaInfo {
- private int x;
- private int y;
- private int width;
- private int height;
+ private final int x;
+ private final int y;
+ private final int width;
+ private final int height;
private int widthRes;
private int heightRes;
- private int rotation = 0;
+ private final int rotation;
/**
- * Sets the x position of the data object
+ * Constructor
*
- * @param x the x position of the data object
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param width the width
+ * @param height the height
+ * @param resolution the resolution (sets both width and height resolutions)
+ * @param rotation the rotation angle
*/
- public void setX(int x) {
+ public AFPObjectAreaInfo(int x, int y, int width, int height, int resolution, int rotation) {
this.x = x;
- }
-
- /**
- * Sets the y position of the data object
- *
- * @param y the y position of the data object
- */
- public void setY(int y) {
this.y = y;
- }
-
- /**
- * Sets the data object width
- *
- * @param width the width of the data object
- */
- public void setWidth(int width) {
this.width = width;
+ this.height = height;
+ this.rotation = rotation;
+ this.widthRes = resolution;
+ this.heightRes = resolution;
}
/**
- * Sets the data object height
+ * Sets both the width and the height resolutions.
*
- * @param height the height of the data object
+ * @param resolution the resolution
*/
- public void setHeight(int height) {
- this.height = height;
+ public void setResolution(int resolution) {
+ this.widthRes = resolution;
+ this.heightRes = resolution;
}
/**
- * Sets the width resolution
+ * Sets the width resolution.
*
- * @param widthRes the width resolution
+ * @param resolution the resolution
*/
- public void setWidthRes(int widthRes) {
- this.widthRes = widthRes;
+ public void setWidthRes(int resolution) {
+ this.widthRes = resolution;
}
/**
- * Sets the height resolution
+ * Sets the height resolution.
*
- * @param heightRes the height resolution
+ * @param resolution the resolution
*/
- public void setHeightRes(int heightRes) {
- this.heightRes = heightRes;
+ public void setHeightRes(int resolution) {
+ this.heightRes = resolution;
}
/**
@@ -125,7 +119,7 @@ public class AFPObjectAreaInfo {
/**
* Returns the width resolution of this data object
*
- * @return the width resolution of this data object
+ * @return the resolution of this data object
*/
public int getWidthRes() {
return widthRes;
@@ -134,7 +128,7 @@ public class AFPObjectAreaInfo {
/**
* Returns the height resolution of this data object
*
- * @return the height resolution of this data object
+ * @return the resolution of this data object
*/
public int getHeightRes() {
return heightRes;
@@ -149,24 +143,15 @@ public class AFPObjectAreaInfo {
return rotation;
}
- /**
- * Sets the data object rotation
- *
- * @param rotation the data object rotation
- */
- public void setRotation(int rotation) {
- this.rotation = rotation;
- }
-
/** {@inheritDoc} */
public String toString() {
return "x=" + x
- + ", y=" + y
- + ", width=" + width
- + ", height=" + height
- + ", widthRes=" + widthRes
- + ", heightRes=" + heightRes
- + ", rotation=" + rotation;
+ + ", y=" + y
+ + ", width=" + width
+ + ", height=" + height
+ + ", widthRes=" + widthRes
+ + ", heigtRes=" + heightRes
+ + ", rotation=" + rotation;
}
}
diff --git a/src/java/org/apache/fop/afp/AFPPaintingState.java b/src/java/org/apache/fop/afp/AFPPaintingState.java
index 2be3a85ac..60ad157f0 100644
--- a/src/java/org/apache/fop/afp/AFPPaintingState.java
+++ b/src/java/org/apache/fop/afp/AFPPaintingState.java
@@ -53,6 +53,9 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState
/** dithering quality setting (0.0f..1.0f) */
private float ditheringQuality;
+ /** image encoding quality setting (0.0f..1.0f) */
+ private float bitmapEncodingQuality;
+
/** color image handler */
private ColorConverter colorConverter = GrayScaleColorConverter.getInstance();
@@ -61,6 +64,9 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState
* format.
*/
private boolean nativeImagesSupported = false;
+
+ private boolean canEmbedJpeg = false;
+
/**
* true if CMYK images (requires IOCA FS45 suppport on the target platform)
* may be generated
@@ -219,6 +225,24 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState
return this.nativeImagesSupported;
}
+ /**
+ * Set whether or not JPEG images can be embedded within an AFP document.
+ *
+ * @param canEmbed true if the JPEG image can be embedded
+ */
+ public void setCanEmbedJpeg(boolean canEmbed) {
+ canEmbedJpeg = canEmbed;
+ }
+
+ /**
+ * Returns true if JPEGs can be embedded in an AFP document.
+ *
+ * @return true if JPEG embedding is allowed
+ */
+ public boolean canEmbedJpeg() {
+ return canEmbedJpeg;
+ }
+
/**
* Controls whether CMYK images (IOCA FS45) are enabled. By default, support
* is disabled for wider compatibility. When disabled, any CMYK image is
@@ -259,6 +283,25 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState
this.ditheringQuality = quality;
}
+ /**
+ * Gets the image encoding quality setting to use when encoding bitmap images.
+ * @return the encoding quality (a value between 0.0f and 1.0f, 1.0 meaning loss-less)
+ */
+ public float getBitmapEncodingQuality() {
+ return this.bitmapEncodingQuality;
+ }
+
+ /**
+ * Sets the image encoding quality setting to use when encoding bitmap images.
+ * @param quality Defines the desired quality level for the conversion.
+ * Valid values: a value between 0.0f (lowest) and 1.0f (best, loss-less)
+ */
+ public void setBitmapEncodingQuality(float quality) {
+ quality = Math.max(quality, 0.0f);
+ quality = Math.min(quality, 1.0f);
+ this.bitmapEncodingQuality = quality;
+ }
+
/**
* Sets the output/device resolution
*
diff --git a/src/java/org/apache/fop/afp/AFPResourceManager.java b/src/java/org/apache/fop/afp/AFPResourceManager.java
index 5371ee373..89341588c 100644
--- a/src/java/org/apache/fop/afp/AFPResourceManager.java
+++ b/src/java/org/apache/fop/afp/AFPResourceManager.java
@@ -242,8 +242,7 @@ public class AFPResourceManager {
private void includeObject(AFPDataObjectInfo dataObjectInfo,
String objectName) {
- IncludeObject includeObject
- = dataObjectFactory.createInclude(objectName, dataObjectInfo);
+ IncludeObject includeObject = dataObjectFactory.createInclude(objectName, dataObjectInfo);
dataStream.getCurrentPage().addObject(includeObject);
}
diff --git a/src/java/org/apache/fop/afp/fonts/AFPFontInfo.java b/src/java/org/apache/fop/afp/fonts/AFPFontInfo.java
index 02e542419..8e865d4e5 100644
--- a/src/java/org/apache/fop/afp/fonts/AFPFontInfo.java
+++ b/src/java/org/apache/fop/afp/fonts/AFPFontInfo.java
@@ -21,6 +21,8 @@ package org.apache.fop.afp.fonts;
import java.util.List;
+import org.apache.fop.fonts.FontTriplet;
+
/**
* FontInfo contains meta information on fonts
@@ -28,7 +30,7 @@ import java.util.List;
public class AFPFontInfo {
private AFPFont font;
- private List/**/ tripletList;
+ private List tripletList;
/**
* Main constructor
@@ -36,7 +38,7 @@ public class AFPFontInfo {
* @param afpFont The AFP Font
* @param tripletList List of font triplets to associate with this font
*/
- public AFPFontInfo(AFPFont afpFont, List/**/ tripletList) {
+ public AFPFontInfo(AFPFont afpFont, List tripletList) {
this.font = afpFont;
this.tripletList = tripletList;
}
@@ -55,7 +57,7 @@ public class AFPFontInfo {
*
* @return List of font triplets
*/
- public List/**/ getFontTriplets() {
+ public List getFontTriplets() {
return tripletList;
}
diff --git a/src/java/org/apache/fop/afp/ioca/ImageContent.java b/src/java/org/apache/fop/afp/ioca/ImageContent.java
index 9c06589e0..81b260bd4 100644
--- a/src/java/org/apache/fop/afp/ioca/ImageContent.java
+++ b/src/java/org/apache/fop/afp/ioca/ImageContent.java
@@ -53,6 +53,9 @@ public class ImageContent extends AbstractStructuredObject {
*/
public static final byte COMPID_G3_MMR = (byte)0x82;
+ /** JPEG algorithms (usually baseline DCT). */
+ public static final byte COMPID_JPEG = (byte)0x83;
+
/** the image size parameter */
private ImageSizeParameter imageSizeParameter = null;
@@ -66,7 +69,7 @@ public class ImageContent extends AbstractStructuredObject {
private byte ideSize = 1;
/** the image compression */
- private byte compression = (byte)0xC0;
+ private byte compression = (byte)0xC0; //Baseline DCT in case of JPEG compression
/** the image data */
private byte[] data;
@@ -147,6 +150,7 @@ public class ImageContent extends AbstractStructuredObject {
* @param color the IDE color model.
* @deprecated use {@link #setIDEStructureParameter(IDEStructureParameter)} instead
*/
+ @Deprecated
public void setImageIDEColorModel(byte color) {
needIDEStructureParameter().setColorModel(color);
}
@@ -156,6 +160,7 @@ public class ImageContent extends AbstractStructuredObject {
* @param subtractive true for subtractive mode, false for additive mode
* @deprecated use {@link #setIDEStructureParameter(IDEStructureParameter)} instead
*/
+ @Deprecated
public void setSubtractive(boolean subtractive) {
needIDEStructureParameter().setSubtractive(subtractive);
}
@@ -172,6 +177,7 @@ public class ImageContent extends AbstractStructuredObject {
private static final int MAX_DATA_LEN = 65535;
/** {@inheritDoc} */
+ @Override
protected void writeContent(OutputStream os) throws IOException {
if (imageSizeParameter != null) {
imageSizeParameter.writeToStream(os);
@@ -206,6 +212,7 @@ public class ImageContent extends AbstractStructuredObject {
}
/** {@inheritDoc} */
+ @Override
protected void writeStart(OutputStream os) throws IOException {
final byte[] startData = new byte[] {
(byte)0x91, // ID
@@ -216,6 +223,7 @@ public class ImageContent extends AbstractStructuredObject {
}
/** {@inheritDoc} */
+ @Override
protected void writeEnd(OutputStream os) throws IOException {
final byte[] endData = new byte[] {
(byte)0x93, // ID
@@ -234,7 +242,7 @@ public class ImageContent extends AbstractStructuredObject {
(byte)0x95, // ID
0x02, // Length
encoding,
- 0x01, // RECID
+ (byte)(encoding == COMPID_JPEG ? 0xFE : 0x01), // RECID
};
return encodingData;
}
@@ -245,17 +253,17 @@ public class ImageContent extends AbstractStructuredObject {
* @return byte[] The data stream.
*/
private byte[] getExternalAlgorithmParameter() {
- if (encoding == (byte)0x83 && compression != 0) {
+ if (encoding == COMPID_JPEG && compression != 0) {
final byte[] extAlgData = new byte[] {
- (byte)0x95, // ID
+ (byte)0x9F, // ID
0x00, // Length
0x10, // ALGTYPE = Compression Algorithm
0x00, // Reserved
- (byte)0x83, // COMPRID = JPEG
+ COMPID_JPEG, // COMPRID = JPEG
0x00, // Reserved
0x00, // Reserved
0x00, // Reserved
- compression, // MARKER
+ compression, // MARKER
0x00, // Reserved
0x00, // Reserved
0x00, // Reserved
diff --git a/src/java/org/apache/fop/afp/modca/AbstractDataObject.java b/src/java/org/apache/fop/afp/modca/AbstractDataObject.java
index d76de9259..a8bf09998 100644
--- a/src/java/org/apache/fop/afp/modca/AbstractDataObject.java
+++ b/src/java/org/apache/fop/afp/modca/AbstractDataObject.java
@@ -74,8 +74,8 @@ public abstract class AbstractDataObject extends AbstractNamedAFPObject
int height = objectAreaInfo.getHeight();
int widthRes = objectAreaInfo.getWidthRes();
int heightRes = objectAreaInfo.getHeightRes();
- ObjectAreaDescriptor objectAreaDescriptor
- = factory.createObjectAreaDescriptor(width, height, widthRes, heightRes);
+ ObjectAreaDescriptor objectAreaDescriptor = factory.createObjectAreaDescriptor(width,
+ height, widthRes, heightRes);
getObjectEnvironmentGroup().setObjectAreaDescriptor(objectAreaDescriptor);
// object area position
diff --git a/src/java/org/apache/fop/afp/modca/GraphicsObject.java b/src/java/org/apache/fop/afp/modca/GraphicsObject.java
index cf732c77a..6b2907807 100644
--- a/src/java/org/apache/fop/afp/modca/GraphicsObject.java
+++ b/src/java/org/apache/fop/afp/modca/GraphicsObject.java
@@ -96,7 +96,7 @@ public class GraphicsObject extends AbstractDataObject {
final int leftEdge = 0;
final int topEdge = 0;
GraphicsDataDescriptor graphicsDataDescriptor = factory.createGraphicsDataDescriptor(
- leftEdge, width, topEdge, height, widthRes, heightRes);
+ leftEdge, width, topEdge, height, widthRes, heightRes);
getObjectEnvironmentGroup().setDataDescriptor(graphicsDataDescriptor);
}
diff --git a/src/java/org/apache/fop/afp/modca/ObjectContainer.java b/src/java/org/apache/fop/afp/modca/ObjectContainer.java
index 9bc8dc594..50aabb03b 100644
--- a/src/java/org/apache/fop/afp/modca/ObjectContainer.java
+++ b/src/java/org/apache/fop/afp/modca/ObjectContainer.java
@@ -107,7 +107,7 @@ public class ObjectContainer extends AbstractDataObject {
ContainerDataDescriptor containerDataDescriptor
= factory.createContainerDataDescriptor(
- dataWidth, dataHeight, widthRes, heightRes);
+ dataWidth, dataHeight, widthRes, heightRes);
getObjectEnvironmentGroup().setDataDescriptor(containerDataDescriptor);
}
}
diff --git a/src/java/org/apache/fop/afp/modca/Registry.java b/src/java/org/apache/fop/afp/modca/Registry.java
index eade967ec..64afce3b5 100644
--- a/src/java/org/apache/fop/afp/modca/Registry.java
+++ b/src/java/org/apache/fop/afp/modca/Registry.java
@@ -20,6 +20,7 @@
package org.apache.fop.afp.modca;
import java.util.Collections;
+import java.util.HashMap;
import org.apache.xmlgraphics.util.MimeConstants;
@@ -43,9 +44,8 @@ public final class Registry {
private static final byte COMPID_TRUETYPE_OPENTYPE_FONT_COLLECTION_RESOURCE_OBJECT = 53;
/** mime type entry mapping */
- private final java.util.Map/**/ mimeObjectTypeMap
- = Collections.synchronizedMap(
- new java.util.HashMap/**/());
+ private final java.util.Map mimeObjectTypeMap
+ = Collections.synchronizedMap(new HashMap());
/** singleton instance */
private static Registry instance = null;
@@ -203,7 +203,7 @@ public final class Registry {
* @return the MOD:CA object type
*/
public ObjectType getObjectType(String mimeType) {
- return (ObjectType)mimeObjectTypeMap.get(mimeType);
+ return mimeObjectTypeMap.get(mimeType);
}
/**
diff --git a/src/java/org/apache/fop/render/ImageHandlerRegistry.java b/src/java/org/apache/fop/render/ImageHandlerRegistry.java
index 02a26265c..18e5b1272 100644
--- a/src/java/org/apache/fop/render/ImageHandlerRegistry.java
+++ b/src/java/org/apache/fop/render/ImageHandlerRegistry.java
@@ -42,18 +42,20 @@ public class ImageHandlerRegistry {
/** the logger */
private static Log log = LogFactory.getLog(ImageHandlerRegistry.class);
- private static final Comparator HANDLER_COMPARATOR = new Comparator() {
- public int compare(Object o1, Object o2) {
- ImageHandler h1 = (ImageHandler)o1;
- ImageHandler h2 = (ImageHandler)o2;
+ private static final Comparator HANDLER_COMPARATOR
+ = new Comparator() {
+ public int compare(ImageHandler o1, ImageHandler o2) {
+ ImageHandler h1 = o1;
+ ImageHandler h2 = o2;
return h1.getPriority() - h2.getPriority();
}
};
/** Map containing image handlers for various {@link Image} subclasses. */
- private Map handlers = new java.util.HashMap();
+ private Map, ImageHandler> handlers
+ = new java.util.HashMap, ImageHandler>();
/** List containing the same handlers as above but ordered by priority */
- private List handlerList = new java.util.LinkedList();
+ private List handlerList = new java.util.LinkedList();
private int handlerRegistrations;
@@ -94,14 +96,14 @@ public class ImageHandlerRegistry {
* @param handler the ImageHandler instance
*/
public synchronized void addHandler(ImageHandler handler) {
- Class imageClass = handler.getSupportedImageClass();
+ Class extends Image> imageClass = handler.getSupportedImageClass();
//List
this.handlers.put(imageClass, handler);
//Sorted insert (sort by priority)
- ListIterator iter = this.handlerList.listIterator();
+ ListIterator iter = this.handlerList.listIterator();
while (iter.hasNext()) {
- ImageHandler h = (ImageHandler)iter.next();
+ ImageHandler h = iter.next();
if (HANDLER_COMPARATOR.compare(handler, h) < 0) {
iter.previous();
break;
@@ -119,9 +121,7 @@ public class ImageHandlerRegistry {
* @return the image handler responsible for handling the image or null if none is available
*/
public ImageHandler getHandler(RenderingContext targetContext, Image image) {
- ListIterator iter = this.handlerList.listIterator();
- while (iter.hasNext()) {
- ImageHandler h = (ImageHandler)iter.next();
+ for (ImageHandler h : this.handlerList) {
if (h.isCompatible(targetContext, image)) {
//Return the first handler in the prioritized list that is compatible
return h;
@@ -138,10 +138,8 @@ public class ImageHandlerRegistry {
*/
public synchronized ImageFlavor[] getSupportedFlavors(RenderingContext context) {
//Extract all ImageFlavors into a single array
- List flavors = new java.util.ArrayList();
- Iterator iter = this.handlerList.iterator();
- while (iter.hasNext()) {
- ImageHandler handler = (ImageHandler)iter.next();
+ List flavors = new java.util.ArrayList();
+ for (ImageHandler handler : this.handlerList) {
if (handler.isCompatible(context, null)) {
ImageFlavor[] f = handler.getSupportedImageFlavors();
for (int i = 0; i < f.length; i++) {
@@ -149,7 +147,7 @@ public class ImageHandlerRegistry {
}
}
}
- return (ImageFlavor[])flavors.toArray(new ImageFlavor[flavors.size()]);
+ return flavors.toArray(new ImageFlavor[flavors.size()]);
}
/**
diff --git a/src/java/org/apache/fop/render/afp/AFPCustomizable.java b/src/java/org/apache/fop/render/afp/AFPCustomizable.java
index 04f3a6eeb..684ac057c 100644
--- a/src/java/org/apache/fop/render/afp/AFPCustomizable.java
+++ b/src/java/org/apache/fop/render/afp/AFPCustomizable.java
@@ -71,6 +71,16 @@ public interface AFPCustomizable {
*/
void setDitheringQuality(float quality);
+ /**
+ * Sets the image encoding quality setting to use when encoding bitmap images.
+ * The default setting is 1.0 which means loss-less encoding. Settings of less than 1.0
+ * allow loss-less encoding schemes like JPEG. The value serves as quality setting for
+ * the encoders in that case.
+ * @param quality Defines the desired quality level.
+ * Valid values: a value between 0.0f (lowest) and 1.0f (best, loss-less)
+ */
+ void setBitmapEncodingQuality(float quality);
+
/**
* Sets the output/device resolution
*
@@ -123,4 +133,11 @@ public interface AFPCustomizable {
*/
void setResourceLevelDefaults(AFPResourceLevelDefaults defaults);
+ /**
+ * Sets whether or not to JPEG images can be embedded in the AFP document.
+ *
+ * @param canEmbed whether or not to embed JPEG image
+ */
+ void canEmbedJpeg(boolean canEmbed);
+
}
diff --git a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java
index 3c3fc123f..29f689555 100644
--- a/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java
+++ b/src/java/org/apache/fop/render/afp/AFPDocumentHandler.java
@@ -428,6 +428,11 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
this.paintingState.setDitheringQuality(quality);
}
+ /** {@inheritDoc} */
+ public void setBitmapEncodingQuality(float quality) {
+ this.paintingState.setBitmapEncodingQuality(quality);
+ }
+
/** {@inheritDoc} */
public void setShadingMode(AFPShadingMode shadingMode) {
this.shadingMode = shadingMode;
@@ -483,4 +488,9 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler
return pageSegmentMap.get(uri);
}
+ /** {@inheritDoc} */
+ public void canEmbedJpeg(boolean canEmbed) {
+ paintingState.setCanEmbedJpeg(canEmbed);
+ }
+
}
diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandler.java b/src/java/org/apache/fop/render/afp/AFPImageHandler.java
index 244263213..118207d38 100644
--- a/src/java/org/apache/fop/render/afp/AFPImageHandler.java
+++ b/src/java/org/apache/fop/render/afp/AFPImageHandler.java
@@ -62,24 +62,17 @@ public abstract class AFPImageHandler implements ImageHandlerBase {
*/
public static AFPObjectAreaInfo createObjectAreaInfo(AFPPaintingState paintingState,
Rectangle targetRect) {
- AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo();
AFPUnitConverter unitConv = paintingState.getUnitConverter();
int[] coords = unitConv.mpts2units(new float[] {targetRect.x, targetRect.y});
- objectAreaInfo.setX(coords[X]);
- objectAreaInfo.setY(coords[Y]);
int width = Math.round(unitConv.mpt2units(targetRect.width));
- objectAreaInfo.setWidth(width);
int height = Math.round(unitConv.mpt2units(targetRect.height));
- objectAreaInfo.setHeight(height);
int resolution = paintingState.getResolution();
- objectAreaInfo.setHeightRes(resolution);
- objectAreaInfo.setWidthRes(resolution);
-
- objectAreaInfo.setRotation(paintingState.getRotation());
+ AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(coords[X], coords[Y], width,
+ height, resolution, paintingState.getRotation());
return objectAreaInfo;
}
diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java
index 83d41ba8c..85ef580f5 100644
--- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java
+++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java
@@ -19,6 +19,12 @@
package org.apache.fop.render.afp;
+import java.awt.Rectangle;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax;
@@ -38,7 +44,11 @@ public class AFPImageHandlerRawCCITTFax extends AbstractAFPImageHandlerRawStream
ImageFlavor.RAW_CCITTFAX,
};
+ /** logging instance */
+ private final Log log = LogFactory.getLog(AFPImageHandlerRawJPEG.class);
+
/** {@inheritDoc} */
+ @Override
protected void setAdditionalParameters(AFPDataObjectInfo dataObjectInfo,
ImageRawStream image) {
AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)dataObjectInfo;
@@ -54,6 +64,15 @@ public class AFPImageHandlerRawCCITTFax extends AbstractAFPImageHandlerRawStream
}
/** {@inheritDoc} */
+ @Override
+ public void handleImage(RenderingContext context, Image image, Rectangle pos)
+ throws IOException {
+ log.debug("Embedding undecoded CCITT data as data container...");
+ super.handleImage(context, image, pos);
+ }
+
+ /** {@inheritDoc} */
+ @Override
protected AFPDataObjectInfo createDataObjectInfo() {
return new AFPImageObjectInfo();
}
diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java
new file mode 100644
index 000000000..e318c49fb
--- /dev/null
+++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java
@@ -0,0 +1,205 @@
+/*
+ * 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.afp;
+
+import java.awt.Rectangle;
+import java.awt.color.ColorSpace;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.xmlgraphics.image.loader.Image;
+import org.apache.xmlgraphics.image.loader.ImageFlavor;
+import org.apache.xmlgraphics.image.loader.ImageSize;
+import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG;
+import org.apache.xmlgraphics.image.loader.impl.JPEGConstants;
+import org.apache.xmlgraphics.util.MimeConstants;
+
+import org.apache.fop.afp.AFPDataObjectInfo;
+import org.apache.fop.afp.AFPImageObjectInfo;
+import org.apache.fop.afp.AFPObjectAreaInfo;
+import org.apache.fop.afp.AFPPaintingState;
+import org.apache.fop.afp.AFPResourceInfo;
+import org.apache.fop.afp.AFPResourceManager;
+import org.apache.fop.afp.ioca.ImageContent;
+import org.apache.fop.afp.modca.ResourceObject;
+import org.apache.fop.render.ImageHandler;
+import org.apache.fop.render.RenderingContext;
+
+/**
+ * {@link ImageHandler} implementation which handles ImageRawJPEG instances. JPEG data is
+ * embedded directly (not decoded) into IOCA images (FS11 or FS45).
+ */
+public class AFPImageHandlerRawJPEG extends AFPImageHandler implements ImageHandler {
+
+ /** logging instance */
+ private final Log log = LogFactory.getLog(AFPImageHandlerRawJPEG.class);
+
+ private void setDefaultResourceLevel(AFPImageObjectInfo imageObjectInfo,
+ AFPResourceManager resourceManager) {
+ AFPResourceInfo resourceInfo = imageObjectInfo.getResourceInfo();
+ if (!resourceInfo.levelChanged()) {
+ resourceInfo.setLevel(resourceManager.getResourceLevelDefaults()
+ .getDefaultResourceLevel(ResourceObject.TYPE_IMAGE));
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected AFPDataObjectInfo createDataObjectInfo() {
+ return new AFPImageObjectInfo();
+ }
+
+ /** {@inheritDoc} */
+ public int getPriority() {
+ return 150;
+ }
+
+ /** {@inheritDoc} */
+ public Class> getSupportedImageClass() {
+ return ImageRawJPEG.class;
+ }
+
+ /** {@inheritDoc} */
+ public ImageFlavor[] getSupportedImageFlavors() {
+ return new ImageFlavor[] {ImageFlavor.RAW_JPEG};
+ }
+
+ /** {@inheritDoc} */
+ public void handleImage(RenderingContext context, Image image, Rectangle pos)
+ throws IOException {
+ AFPRenderingContext afpContext = (AFPRenderingContext)context;
+
+ AFPImageObjectInfo imageObjectInfo = (AFPImageObjectInfo)createDataObjectInfo();
+ AFPPaintingState paintingState = afpContext.getPaintingState();
+
+ // set resource information
+ setResourceInformation(imageObjectInfo,
+ image.getInfo().getOriginalURI(),
+ afpContext.getForeignAttributes());
+ setDefaultResourceLevel(imageObjectInfo, afpContext.getResourceManager());
+
+ // Positioning
+ imageObjectInfo.setObjectAreaInfo(createObjectAreaInfo(paintingState, pos));
+ updateIntrinsicSize(imageObjectInfo, paintingState, image.getSize());
+
+ // Image content
+ ImageRawJPEG jpeg = (ImageRawJPEG)image;
+ imageObjectInfo.setCompression(ImageContent.COMPID_JPEG);
+ ColorSpace cs = jpeg.getColorSpace();
+ switch (cs.getType()) {
+ case ColorSpace.TYPE_GRAY:
+ imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS11);
+ imageObjectInfo.setColor(false);
+ imageObjectInfo.setBitsPerPixel(8);
+ break;
+ case ColorSpace.TYPE_RGB:
+ imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS11);
+ imageObjectInfo.setColor(true);
+ imageObjectInfo.setBitsPerPixel(24);
+ break;
+ case ColorSpace.TYPE_CMYK:
+ imageObjectInfo.setMimeType(MimeConstants.MIME_AFP_IOCA_FS45);
+ imageObjectInfo.setColor(true);
+ imageObjectInfo.setBitsPerPixel(32);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Color space of JPEG image not supported: " + cs);
+ }
+
+ boolean included = afpContext.getResourceManager().tryIncludeObject(imageObjectInfo);
+ if (!included) {
+ log.debug("Embedding undecoded JPEG as IOCA image...");
+ InputStream inputStream = jpeg.createInputStream();
+ try {
+ imageObjectInfo.setData(IOUtils.toByteArray(inputStream));
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+
+ // Create image
+ afpContext.getResourceManager().createObject(imageObjectInfo);
+ }
+ }
+
+ private void updateIntrinsicSize(AFPImageObjectInfo imageObjectInfo,
+ AFPPaintingState paintingState, ImageSize targetSize) {
+ //Update image object info
+ imageObjectInfo.setDataHeightRes((int)Math.round(
+ targetSize.getDpiHorizontal() * 10));
+ imageObjectInfo.setDataWidthRes((int)Math.round(
+ targetSize.getDpiVertical() * 10));
+ imageObjectInfo.setDataHeight(targetSize.getHeightPx());
+ imageObjectInfo.setDataWidth(targetSize.getWidthPx());
+
+ // set object area info
+ int resolution = paintingState.getResolution();
+ AFPObjectAreaInfo objectAreaInfo = imageObjectInfo.getObjectAreaInfo();
+ objectAreaInfo.setResolution(resolution);
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible(RenderingContext targetContext, Image image) {
+ if (!(targetContext instanceof AFPRenderingContext)) {
+ return false; //AFP-specific image handler
+ }
+ AFPRenderingContext context = (AFPRenderingContext)targetContext;
+ AFPPaintingState paintingState = context.getPaintingState();
+ if (!paintingState.canEmbedJpeg()) {
+ return false;
+ }
+ if (paintingState.getBitsPerPixel() < 8) {
+ return false; //This would stand in the way of dithering and cause exceptions
+ }
+ if (image == null) {
+ return true; //Don't know the image format, yet
+ }
+ if (image instanceof ImageRawJPEG) {
+ ImageRawJPEG jpeg = (ImageRawJPEG)image;
+ ColorSpace cs = jpeg.getColorSpace();
+ switch (cs.getType()) {
+ case ColorSpace.TYPE_GRAY:
+ case ColorSpace.TYPE_RGB:
+ //ok
+ break;
+ case ColorSpace.TYPE_CMYK:
+ if (!paintingState.isCMYKImagesSupported()) {
+ return false; //CMYK is disabled
+ //Note: you may need to disable this image handler through configuration
+ //if you want to paint a CMYK JPEG on 24bit and less configurations.
+ }
+ break;
+ default:
+ return false; //not supported
+ }
+
+ if (jpeg.getSOFType() != JPEGConstants.SOF0) {
+ return false; //We'll let only baseline DCT through.
+ }
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java
index fcfc9c64c..f32f7305b 100644
--- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java
+++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java
@@ -19,6 +19,12 @@
package org.apache.fop.render.afp;
+import java.awt.Rectangle;
+import java.io.IOException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import org.apache.xmlgraphics.image.loader.Image;
import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.impl.ImageRawEPS;
@@ -40,6 +46,9 @@ public class AFPImageHandlerRawStream extends AbstractAFPImageHandlerRawStream {
ImageFlavor.RAW_EPS,
};
+ /** logging instance */
+ private final Log log = LogFactory.getLog(AFPImageHandlerRawJPEG.class);
+
/** {@inheritDoc} */
public int getPriority() {
return 200;
@@ -56,10 +65,22 @@ public class AFPImageHandlerRawStream extends AbstractAFPImageHandlerRawStream {
}
/** {@inheritDoc} */
+ @Override
protected AFPDataObjectInfo createDataObjectInfo() {
return new AFPDataObjectInfo();
}
+ /** {@inheritDoc} */
+ @Override
+ public void handleImage(RenderingContext context, Image image, Rectangle pos)
+ throws IOException {
+ if (log.isDebugEnabled()) {
+ log.debug("Embedding undecoded image data (" + image.getInfo().getMimeType()
+ + ") as data container...");
+ }
+ super.handleImage(context, image, pos);
+ }
+
/** {@inheritDoc} */
public boolean isCompatible(RenderingContext targetContext, Image image) {
if (targetContext instanceof AFPRenderingContext) {
diff --git a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java
index c516da702..0b4b6ea98 100644
--- a/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java
+++ b/src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java
@@ -28,10 +28,10 @@ import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import org.apache.commons.io.output.ByteArrayOutputStream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -40,6 +40,9 @@ import org.apache.xmlgraphics.image.loader.ImageFlavor;
import org.apache.xmlgraphics.image.loader.ImageInfo;
import org.apache.xmlgraphics.image.loader.ImageSize;
import org.apache.xmlgraphics.image.loader.impl.ImageRendered;
+import org.apache.xmlgraphics.image.writer.ImageWriter;
+import org.apache.xmlgraphics.image.writer.ImageWriterParams;
+import org.apache.xmlgraphics.image.writer.ImageWriterRegistry;
import org.apache.xmlgraphics.ps.ImageEncodingHelper;
import org.apache.xmlgraphics.util.MimeConstants;
import org.apache.xmlgraphics.util.UnitConv;
@@ -50,6 +53,7 @@ import org.apache.fop.afp.AFPObjectAreaInfo;
import org.apache.fop.afp.AFPPaintingState;
import org.apache.fop.afp.AFPResourceInfo;
import org.apache.fop.afp.AFPResourceManager;
+import org.apache.fop.afp.ioca.ImageContent;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.render.ImageHandler;
import org.apache.fop.render.RenderingContext;
@@ -284,7 +288,26 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima
functionSet = 45; //IOCA FS45 required for CMYK
}
- helper.encode(baos);
+ //Lossy or loss-less?
+ if (!paintingState.canEmbedJpeg()
+ && paintingState.getBitmapEncodingQuality() < 1.0f) {
+ try {
+ if (log.isDebugEnabled()) {
+ log.debug("Encoding using baseline DCT (JPEG, q="
+ + paintingState.getBitmapEncodingQuality() + ")...");
+ }
+ encodeToBaselineDCT(renderedImage,
+ paintingState.getBitmapEncodingQuality(),
+ paintingState.getResolution(),
+ baos);
+ imageObjectInfo.setCompression(ImageContent.COMPID_JPEG);
+ } catch (IOException ioe) {
+ //Some JPEG codecs cannot encode CMYK
+ helper.encode(baos);
+ }
+ } else {
+ helper.encode(baos);
+ }
imageData = baos.toByteArray();
}
}
@@ -393,6 +416,14 @@ public class AFPImageHandlerRenderedImage extends AFPImageHandler implements Ima
return false;
}
- }
+ private void encodeToBaselineDCT(RenderedImage image,
+ float quality, int resolution, OutputStream out) throws IOException {
+ ImageWriter writer = ImageWriterRegistry.getInstance().getWriterFor("image/jpeg");
+ ImageWriterParams params = new ImageWriterParams();
+ params.setJPEGQuality(quality, true);
+ params.setResolution(resolution);
+ writer.writeImage(image, out, params);
+ }
+ }
}
diff --git a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
index fc8d10508..25d684e42 100644
--- a/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
+++ b/src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
@@ -23,10 +23,12 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
+import java.util.ArrayList;
import java.util.List;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
+
import org.apache.fop.afp.AFPResourceLevel;
import org.apache.fop.afp.AFPResourceLevelDefaults;
import org.apache.fop.afp.fonts.AFPFont;
@@ -80,10 +82,10 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
log.error("Mandatory font configuration element ' clazz = Class.forName(
- "org.apache.fop.fonts.base14." + base14);
+ Class extends Typeface> clazz = Class.forName(
+ "org.apache.fop.fonts.base14." + base14).asSubclass(Typeface.class);
try {
- Typeface tf = (Typeface)clazz.newInstance();
+ Typeface tf = clazz.newInstance();
font.addCharacterSet(sizeMpt,
CharacterSetBuilder.getInstance()
.build(characterset, codepage, encoding, tf));
@@ -222,10 +224,10 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
String base14 = afpFontCfg.getAttribute("base14-font", null);
if (base14 != null) {
try {
- Class> clazz = Class.forName("org.apache.fop.fonts.base14."
- + base14);
+ Class extends Typeface> clazz = Class.forName("org.apache.fop.fonts.base14."
+ + base14).asSubclass(Typeface.class);
try {
- Typeface tf = (Typeface)clazz.newInstance();
+ Typeface tf = clazz.newInstance();
characterSet = CharacterSetBuilder.getInstance()
.build(characterset, codepage, encoding, tf);
} catch (Exception ie) {
@@ -319,7 +321,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
if (log.isDebugEnabled()) {
log.debug("Adding font " + afi.getAFPFont().getFontName());
}
- List/**/ fontTriplets = afi.getFontTriplets();
+ List fontTriplets = afi.getFontTriplets();
for (int j = 0; j < fontTriplets.size(); ++j) {
FontTriplet triplet = (FontTriplet) fontTriplets.get(j);
if (log.isDebugEnabled()) {
@@ -396,6 +398,24 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
boolean nativeImageSupport = imagesCfg.getAttributeAsBoolean("native", false);
customizable.setNativeImagesSupported(nativeImageSupport);
+ Configuration jpegConfig = imagesCfg.getChild("jpeg");
+ boolean allowEmbedding = false;
+ float ieq = 1.0f;
+ if (jpegConfig != null) {
+ allowEmbedding = jpegConfig.getAttributeAsBoolean("allow-embedding", false);
+ String bitmapEncodingQuality = jpegConfig.getAttribute("bitmap-encoding-quality", null);
+
+ if (bitmapEncodingQuality != null) {
+ try {
+ ieq = Float.parseFloat(bitmapEncodingQuality);
+ } catch (NumberFormatException nfe) {
+ //ignore and leave the default above
+ }
+ }
+ }
+ customizable.canEmbedJpeg(allowEmbedding);
+ customizable.setBitmapEncodingQuality(ieq);
+
// shading (filled rectangles)
Configuration shadingCfg = cfg.getChild("shading");
AFPShadingMode shadingMode = AFPShadingMode.valueOf(
@@ -480,7 +500,7 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
public void setupFontInfo(IFDocumentHandler documentHandler, FontInfo fontInfo)
throws FOPException {
FontManager fontManager = userAgent.getFactory().getFontManager();
- List fontCollections = new java.util.ArrayList();
+ List fontCollections = new ArrayList();
Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
if (cfg != null) {
diff --git a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java
index 7ade64006..48b82bbf3 100644
--- a/src/java/org/apache/fop/render/afp/AFPSVGHandler.java
+++ b/src/java/org/apache/fop/render/afp/AFPSVGHandler.java
@@ -154,23 +154,18 @@ public class AFPSVGHandler extends AbstractGenericSVGHandler {
private AFPObjectAreaInfo createObjectAreaInfo(AFPPaintingState paintingState,
int x, int y, int width, int height, int resolution) {
// set the data object parameters
- AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo();
AffineTransform at = paintingState.getData().getTransform();
at.translate(x, y);
- objectAreaInfo.setX((int)Math.round(at.getTranslateX()));
- objectAreaInfo.setY((int)Math.round(at.getTranslateY()));
-
- objectAreaInfo.setWidthRes(resolution);
- objectAreaInfo.setHeightRes(resolution);
-
AFPUnitConverter unitConv = paintingState.getUnitConverter();
- objectAreaInfo.setWidth(Math.round(unitConv.mpt2units(width)));
- objectAreaInfo.setHeight(Math.round(unitConv.mpt2units(height)));
int rotation = paintingState.getRotation();
- objectAreaInfo.setRotation(rotation);
-
+ int objX = (int) Math.round(at.getTranslateX());
+ int objY = (int) Math.round(at.getTranslateY());
+ int objWidth = Math.round(unitConv.mpt2units(width));
+ int objHeight = Math.round(unitConv.mpt2units(height));
+ AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(objX, objY, objWidth, objHeight,
+ resolution, rotation);
return objectAreaInfo;
}
diff --git a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java
index 5374c7051..b36646117 100644
--- a/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java
+++ b/src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java
@@ -103,8 +103,7 @@ public abstract class AbstractAFPImageHandlerRawStream extends AFPImageHandler
AFPPaintingState paintingState = afpContext.getPaintingState();
int resolution = paintingState.getResolution();
AFPObjectAreaInfo objectAreaInfo = dataObjectInfo.getObjectAreaInfo();
- objectAreaInfo.setWidthRes(resolution);
- objectAreaInfo.setHeightRes(resolution);
+ objectAreaInfo.setResolution(resolution);
// Image content
ImageRawStream imageStream = (ImageRawStream)image;
diff --git a/status.xml b/status.xml
index 118887d69..4d8fc1455 100644
--- a/status.xml
+++ b/status.xml
@@ -60,6 +60,10 @@
documents. Example: the fix of marks layering will be such a case when it's done.
-->
+
+ Allow JPEG images to be embedded in an AFP document as is, without being decoded and
+ encoded.
+
Simplification of the build: Reduced code duplication and layout engine tests.
diff --git a/test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java b/test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java
new file mode 100644
index 000000000..fc5f1825c
--- /dev/null
+++ b/test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java
@@ -0,0 +1,76 @@
+/*
+ * 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.afp;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test case for {@link AFPObjectAreaInfo}.
+ */
+public class AFPObjectAreaInfoTestCase {
+
+ private AFPObjectAreaInfo sut;
+
+ /**
+ * Instantiate the system under test
+ */
+ @Before
+ public void setUp() {
+ sut = new AFPObjectAreaInfo(1, 2, 3, 4, 5, 6);
+ }
+
+ /**
+ * Test the getter functions with arbitrary data.
+ */
+ @Test
+ public void testGetters() {
+ assertEquals(1, sut.getX());
+ assertEquals(2, sut.getY());
+ assertEquals(3, sut.getWidth());
+ assertEquals(4, sut.getHeight());
+ assertEquals(5, sut.getWidthRes());
+ assertEquals(5, sut.getHeightRes());
+ assertEquals(6, sut.getRotation());
+ }
+
+ /**
+ * Test the resolution setters with arbitrary data.
+ */
+ @Test
+ public void testSetters() {
+ assertEquals(5, sut.getWidthRes());
+ assertEquals(5, sut.getHeightRes());
+
+ sut.setResolution(20);
+ assertEquals(20, sut.getWidthRes());
+ assertEquals(20, sut.getHeightRes());
+
+ sut.setHeightRes(10);
+ assertEquals(20, sut.getWidthRes());
+ assertEquals(10, sut.getHeightRes());
+
+ sut.setWidthRes(9);
+ assertEquals(9, sut.getWidthRes());
+ assertEquals(10, sut.getHeightRes());
+ }
+}
diff --git a/test/java/org/apache/fop/afp/AFPPaintingStateTestCase.java b/test/java/org/apache/fop/afp/AFPPaintingStateTestCase.java
new file mode 100644
index 000000000..47c93064c
--- /dev/null
+++ b/test/java/org/apache/fop/afp/AFPPaintingStateTestCase.java
@@ -0,0 +1,61 @@
+/*
+ * 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.afp;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test case for {@link AFPPaintingState}.
+ */
+public class AFPPaintingStateTestCase {
+ private AFPPaintingState sut;
+
+ /**
+ * Set up the system under test
+ */
+ @Before
+ public void setUp() {
+ sut = new AFPPaintingState();
+ }
+
+ /**
+ * Test {get,set}BitmapEncodingQuality()
+ */
+ @Test
+ public void testGetSetBitmapEncodingQuality() {
+ sut.setBitmapEncodingQuality(0.5f);
+ assertEquals(0.5f, sut.getBitmapEncodingQuality(), 0.01f);
+
+ sut.setBitmapEncodingQuality(0.9f);
+ assertEquals(0.9f, sut.getBitmapEncodingQuality(), 0.01f);
+ }
+
+ /**
+ * Test {,set}CanEmbedJpeg
+ */
+ public void testGetSetCanEmbedJpeg() {
+ assertEquals(false, sut.canEmbedJpeg());
+ sut.setCanEmbedJpeg(true);
+ assertEquals(true, sut.canEmbedJpeg());
+ }
+}
diff --git a/test/java/org/apache/fop/afp/AFPResourceManagerTestCase.java b/test/java/org/apache/fop/afp/AFPResourceManagerTestCase.java
new file mode 100644
index 000000000..c9ea9a5f4
--- /dev/null
+++ b/test/java/org/apache/fop/afp/AFPResourceManagerTestCase.java
@@ -0,0 +1,89 @@
+/*
+ * 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.afp;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.apache.xmlgraphics.util.MimeConstants;
+
+/**
+ * Test case for {@link AFPResourceManager}.
+ */
+public class AFPResourceManagerTestCase {
+
+ private AFPResourceManager sut;
+
+ @Before
+ public void setUp() throws IOException {
+ sut = new AFPResourceManager();
+ AFPPaintingState paintingState = new AFPPaintingState();
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ DataStream stream = sut.createDataStream(paintingState, outStream);
+ stream.startPage(0, 0, 0, 10, 10);
+ }
+
+ /**
+ * Ensures that if tryIncludeObject() is called with a new object, it returns false suggesting
+ * that we have to create said object. However, if it is called with an object that has already
+ * been created, it returns true suggesting that we don't have to create that object again.
+ * Page-segment is false.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ public void testTryIncludeObjectWithPageSegFalse() throws IOException {
+ AFPDataObjectInfo dataInfo = createAFPDataObjectInfo();
+ // An empty object needs to be created every time!
+ assertFalse(sut.tryIncludeObject(dataInfo));
+ sut.createObject(dataInfo);
+ assertTrue(sut.tryIncludeObject(dataInfo));
+ }
+
+ /**
+ * {@code testTryIncludeObjectWithPageSegFalse()} but with page-segment true.
+ *
+ * @throws IOException if an I/O error occurs
+ */
+ @Test
+ public void testTryIncludeObjectWithPageSegTrue() throws IOException {
+ AFPDataObjectInfo dataInfo = createAFPDataObjectInfo();
+ dataInfo.setCreatePageSegment(true);
+ // An empty object needs to be created every time!
+ assertFalse(sut.tryIncludeObject(dataInfo));
+ sut.createObject(dataInfo);
+ assertTrue(sut.tryIncludeObject(dataInfo));
+ }
+
+ private AFPDataObjectInfo createAFPDataObjectInfo() {
+ AFPDataObjectInfo dataInfo = new AFPDataObjectInfo();
+ dataInfo.setMimeType(MimeConstants.MIME_TIFF);
+ dataInfo.setData(new byte[1]);
+ AFPObjectAreaInfo objectAreaInfo = new AFPObjectAreaInfo(0, 0, 10, 10, 1, 0);
+ dataInfo.setObjectAreaInfo(objectAreaInfo);
+ return dataInfo;
+ }
+}
diff --git a/test/java/org/apache/fop/afp/AFPTestSuite.java b/test/java/org/apache/fop/afp/AFPTestSuite.java
index 8fa3e819d..32d61cb35 100644
--- a/test/java/org/apache/fop/afp/AFPTestSuite.java
+++ b/test/java/org/apache/fop/afp/AFPTestSuite.java
@@ -19,18 +19,21 @@
package org.apache.fop.afp;
-import org.apache.fop.afp.modca.IncludeObjectTestCase;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
+import org.apache.fop.afp.modca.IncludeObjectTestCase;
+
/**
* Test suite for FOP's AFP classes.
*/
@RunWith(Suite.class)
@SuiteClasses({
- IncludeObjectTestCase.class,
- AFPResourceUtilTestCase.class
+ IncludeObjectTestCase.class,
+ AFPResourceUtilTestCase.class,
+ AFPObjectAreaInfoTestCase.class,
+ AFPPaintingStateTestCase.class
})
public class AFPTestSuite {
}
diff --git a/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java b/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java
new file mode 100644
index 000000000..7c08e6d99
--- /dev/null
+++ b/test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java
@@ -0,0 +1,92 @@
+/*
+ * 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.afp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import org.apache.fop.afp.AFPPaintingState;
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.apps.FOUserAgent;
+import org.apache.fop.apps.FopFactory;
+
+/**
+ * Test case for {@link AFPRendererConfigurator}.
+ */
+public class AFPRendererConfiguratorTestCase {
+ private static FOUserAgent userAgent;
+
+ private AFPRendererConfigurator sut;
+
+ /**
+ * The FOUserAgent only needs to be created once.
+ */
+ @BeforeClass
+ public static void createUserAgent() {
+ userAgent = FopFactory.newInstance().newFOUserAgent();
+ }
+
+ /**
+ * Assigns an FOUserAgen with a config file at uri
+ *
+ * @param uri the URI of the config file
+ */
+ private void setConfigFile(String uri) {
+ String confTestsDir = "test/resources/conf/afp/";
+ try {
+ userAgent.getFactory().setUserConfig(confTestsDir + uri);
+ sut = new AFPRendererConfigurator(userAgent);
+ } catch (IOException ioe) {
+ fail("IOException: " + ioe);
+ } catch (SAXException se) {
+ fail("SAXException: " + se);
+ }
+ }
+
+ /**
+ * Test several config files relating to JPEG images in AFP.
+ *
+ * @throws FOPException if an error is thrown
+ */
+ @Test
+ public void testJpegImageConfig() throws FOPException {
+ testJpegSettings("no_image_config.xconf", 1.0f, false);
+ testJpegSettings("can_embed_jpeg.xconf", 1.0f, true);
+ testJpegSettings("bitmap_encode_quality.xconf", 0.5f, false);
+ }
+
+ private void testJpegSettings(String uri, float bitmapEncodingQual, boolean canEmbed)
+ throws FOPException {
+ AFPDocumentHandler docHandler = new AFPDocumentHandler();
+
+ setConfigFile(uri);
+ sut.configure(docHandler);
+
+ AFPPaintingState paintingState = docHandler.getPaintingState();
+ assertEquals(bitmapEncodingQual, paintingState.getBitmapEncodingQuality(), 0.01f);
+ assertEquals(canEmbed, paintingState.canEmbedJpeg());
+ }
+}
diff --git a/test/java/org/apache/fop/render/afp/AFPTestSuite.java b/test/java/org/apache/fop/render/afp/AFPTestSuite.java
index 16b6651a6..117e7efcf 100644
--- a/test/java/org/apache/fop/render/afp/AFPTestSuite.java
+++ b/test/java/org/apache/fop/render/afp/AFPTestSuite.java
@@ -27,6 +27,8 @@ import org.junit.runners.Suite.SuiteClasses;
* Test suite for FOP's AFP output.
*/
@RunWith(Suite.class)
-@SuiteClasses({ NoOperationTestCase.class })
+@SuiteClasses({
+ NoOperationTestCase.class,
+ AFPRendererConfiguratorTestCase.class })
public class AFPTestSuite {
}
diff --git a/test/resources/conf/afp/bitmap_encode_quality.xconf b/test/resources/conf/afp/bitmap_encode_quality.xconf
new file mode 100644
index 000000000..540be7ecf
--- /dev/null
+++ b/test/resources/conf/afp/bitmap_encode_quality.xconf
@@ -0,0 +1,16 @@
+
+
+ .
+ 72
+ 72
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/resources/conf/afp/can_embed_jpeg.xconf b/test/resources/conf/afp/can_embed_jpeg.xconf
new file mode 100644
index 000000000..cd87ef07e
--- /dev/null
+++ b/test/resources/conf/afp/can_embed_jpeg.xconf
@@ -0,0 +1,16 @@
+
+
+ .
+ 72
+ 72
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/resources/conf/afp/no_image_config.xconf b/test/resources/conf/afp/no_image_config.xconf
new file mode 100644
index 000000000..ff1a089f4
--- /dev/null
+++ b/test/resources/conf/afp/no_image_config.xconf
@@ -0,0 +1,13 @@
+
+
+ .
+ 72
+ 72
+
+
+
+
+
+
+
+
--
2.39.5