]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla#52089: Allow JPEG images to be embedded in an AFP document as
authorPeter Hancock <phancock@apache.org>
Tue, 1 Nov 2011 12:20:21 +0000 (12:20 +0000)
committerPeter Hancock <phancock@apache.org>
Tue, 1 Nov 2011 12:20:21 +0000 (12:20 +0000)
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

35 files changed:
src/documentation/content/xdocs/trunk/output.xml
src/java/META-INF/services/org.apache.fop.render.ImageHandler
src/java/org/apache/fop/afp/AFPDataObjectFactory.java
src/java/org/apache/fop/afp/AFPDitheredRectanglePainter.java
src/java/org/apache/fop/afp/AFPImageObjectInfo.java
src/java/org/apache/fop/afp/AFPObjectAreaInfo.java
src/java/org/apache/fop/afp/AFPPaintingState.java
src/java/org/apache/fop/afp/AFPResourceManager.java
src/java/org/apache/fop/afp/fonts/AFPFontInfo.java
src/java/org/apache/fop/afp/ioca/ImageContent.java
src/java/org/apache/fop/afp/modca/AbstractDataObject.java
src/java/org/apache/fop/afp/modca/GraphicsObject.java
src/java/org/apache/fop/afp/modca/ObjectContainer.java
src/java/org/apache/fop/afp/modca/Registry.java
src/java/org/apache/fop/render/ImageHandlerRegistry.java
src/java/org/apache/fop/render/afp/AFPCustomizable.java
src/java/org/apache/fop/render/afp/AFPDocumentHandler.java
src/java/org/apache/fop/render/afp/AFPImageHandler.java
src/java/org/apache/fop/render/afp/AFPImageHandlerRawCCITTFax.java
src/java/org/apache/fop/render/afp/AFPImageHandlerRawJPEG.java [new file with mode: 0644]
src/java/org/apache/fop/render/afp/AFPImageHandlerRawStream.java
src/java/org/apache/fop/render/afp/AFPImageHandlerRenderedImage.java
src/java/org/apache/fop/render/afp/AFPRendererConfigurator.java
src/java/org/apache/fop/render/afp/AFPSVGHandler.java
src/java/org/apache/fop/render/afp/AbstractAFPImageHandlerRawStream.java
status.xml
test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/AFPPaintingStateTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/AFPResourceManagerTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/afp/AFPTestSuite.java
test/java/org/apache/fop/render/afp/AFPRendererConfiguratorTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/render/afp/AFPTestSuite.java
test/resources/conf/afp/bitmap_encode_quality.xconf [new file with mode: 0644]
test/resources/conf/afp/can_embed_jpeg.xconf [new file with mode: 0644]
test/resources/conf/afp/no_image_config.xconf [new file with mode: 0644]

index 7b245eb07bfec75d393f5825d1ada5b59afe51c6..cf88bd321429b1a194979d02fc6f223207bb02bb 100644 (file)
@@ -796,6 +796,23 @@ Note that the value of the encoding attribute in the example is the double-byte
         </p>
         <source><![CDATA[
       <images mode="b+w" bits-per-pixel="1" dithering-quality="maximum"/>]]></source>
+        <p>
+          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:
+        </p>
+        <source><![CDATA[
+      <images mode="color" cmyk="true">
+        <jpeg allow-embedding="false" bitmap-encoding-quality="0.8"/>
+      </images>]]></source>
       </section>
       <section id="afp-goca-config">
         <title>GOCA (Vector Graphics)</title>
index efa40ebf6413819fe7ccabeb5d1582f0abf059da..f9b890c8eb8335d0288b8981f40e87d7af92138f 100644 (file)
@@ -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
index 615c22b17a486304e85b68b77a1db422162c8381..17114ebbedb840651f24d900c8090d0273afe142 100644 (file)
@@ -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);
index 79e4979fdc3578748d604579293ff4e05c9beca9..d1c693afb3183c90ee62cb130d457888766ac2c5 100644 (file)
@@ -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
index 7aee3cda81e3a4256cf42b97a0df4088dd4b64d4..0dca97513c56dd6aa075bd0851fabe26015f6a1a 100644 (file)
@@ -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
index 963424470653077e12bd4b26878359d88d84ad53..1ecffc2df5236d946206d1099103e66c318be859 100644 (file)
@@ -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;
     }
 
 }
index 2be3a85ac6a7cea6105042533fd87d8fde05c23e..60ad157f02d8ce2ba1c99db28a483c9f8e8ee69f 100644 (file)
@@ -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
      *
index 5371ee3730d6fdf7a7b63bbe3b699bb35afc7e1e..89341588c60712125a8d157e332405604964f12e 100644 (file)
@@ -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);
     }
 
index 02e542419a65ad21436d54ec48bd6f52b78b5941..8e865d4e5834f0b6da96262b5c6de1f0eaaea5da 100644 (file)
@@ -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/*<FontTriplet>*/ tripletList;
+    private List<FontTriplet> 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/*<FontTriplet>*/ tripletList) {
+    public AFPFontInfo(AFPFont afpFont, List<FontTriplet> tripletList) {
         this.font = afpFont;
         this.tripletList = tripletList;
     }
@@ -55,7 +57,7 @@ public class AFPFontInfo {
      *
      * @return List of font triplets
      */
-    public List/*<FontTriplet>*/ getFontTriplets() {
+    public List<FontTriplet> getFontTriplets() {
         return tripletList;
     }
 
index 9c06589e0e69a42d5a500907d5198b91098f72f1..81b260bd48768f6651e5138ae16c50eb520aa7df 100644 (file)
@@ -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
index d76de925932509d5f89b2709eb19f62cdaed2ca3..a8bf099986aff1594d8ebd62baaa757f84b3382b 100644 (file)
@@ -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
index cf732c77ade1a738494545bbcc040350082ed072..6b2907807889255ca0eca96f7d8dca4255db7c91 100644 (file)
@@ -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);
     }
index 9bc8dc5941e7b1993b4bb90923eee1e49adab3d4..50aabb03bf90550d104a86f67bc01a50d313d4d5 100644 (file)
@@ -107,7 +107,7 @@ public class ObjectContainer extends AbstractDataObject {
 
             ContainerDataDescriptor containerDataDescriptor
                 = factory.createContainerDataDescriptor(
-                        dataWidth, dataHeight, widthRes, heightRes);
+                    dataWidth, dataHeight, widthRes, heightRes);
             getObjectEnvironmentGroup().setDataDescriptor(containerDataDescriptor);
         }
     }
index eade967ec9d886979d5e4b4f10a51b192f47d231..64afce3b5060dd69291bb54bf60076bc8c0c1d80 100644 (file)
@@ -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/*<String, ObjectType>*/ mimeObjectTypeMap
-        = Collections.synchronizedMap(
-                new java.util.HashMap/*<String, ObjectType>*/());
+    private final java.util.Map<String, ObjectType> mimeObjectTypeMap
+               = Collections.synchronizedMap(new HashMap<String, ObjectType>());
 
     /** 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);
     }
 
     /**
index 02a26265c039b8f21b31b265bb4afa4e5e880484..18e5b1272b297803bc3dcc4911d3e0a99281d537 100644 (file)
@@ -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<ImageHandler> HANDLER_COMPARATOR
+            = new Comparator<ImageHandler>() {
+        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<Class<? extends Image>, ImageHandler> handlers
+            = new java.util.HashMap<Class<? extends Image>, ImageHandler>();
     /** List containing the same handlers as above but ordered by priority */
-    private List handlerList = new java.util.LinkedList();
+    private List<ImageHandler> handlerList = new java.util.LinkedList<ImageHandler>();
 
     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<ImageHandler> 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<ImageFlavor> flavors = new java.util.ArrayList<ImageFlavor>();
+        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()]);
     }
 
     /**
index 04f3a6eebfcf5000dfa9b604d6f3d8e2f1d865e6..684ac057c5cc738be9c987314945435d04f5a789 100644 (file)
@@ -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);
+
 }
index 3c3fc123f590951fd7921752b9419da241ea14a2..29f68955564fc93a3164f291d0ef2744b709f941 100644 (file)
@@ -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);
+    }
+
 }
index 24426321312818fd72202559fb5e2e088ab63993..118207d38e136f5fabfaa355206b9257c94004d6 100644 (file)
@@ -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;
     }
 
index 83d41ba8c36d9c8c1d90ec0da99a1723285ee8ed..85ef580f5cfa02574da0d8d8395f5145982f49a6 100644 (file)
 
 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 (file)
index 0000000..e318c49
--- /dev/null
@@ -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;
+    }
+
+}
index fcfc9c64c30207c5aa685ccf7e59295922c88cb7..f32f7305b78575fa7a5b193d228122115c08a2ec 100644 (file)
 
 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) {
index c516da7024e9f1d05f48cf94ef784c0a592e0d30..0b4b6ea98fdf2b43fd414305a286eff910010d73 100644 (file)
@@ -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);
+        }
 
+    }
 }
index fc8d105083bd36fd12a9389416dc51f27c333227..25d684e42dd8bd686573d163913fc638a35509b1 100644 (file)
@@ -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 '<font-triplet...' is missing");
             return null;
         }
-        for (int j = 0; j < triple.length; j++) {
-            int weight = FontUtil.parseCSS2FontWeight(triple[j].getAttribute("weight"));
-            FontTriplet triplet = new FontTriplet(triple[j].getAttribute("name"),
-                    triple[j].getAttribute("style"),
+        for (Configuration config : triple) {
+            int weight = FontUtil.parseCSS2FontWeight(config.getAttribute("weight"));
+            FontTriplet triplet = new FontTriplet(config.getAttribute("name"),
+                    config.getAttribute("style"),
                     weight);
             tripletList.add(triplet);
         }
@@ -183,10 +185,10 @@ public class AFPRendererConfigurator extends PrintRendererConfigurator
 
                 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();
                             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/*<FontTriplet>*/ fontTriplets = afi.getFontTriplets();
+                List<FontTriplet> 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<FontCollection> fontCollections = new java.util.ArrayList<FontCollection>();
+        List<AFPFontCollection> fontCollections = new ArrayList<AFPFontCollection>();
 
         Configuration cfg = super.getRendererConfig(documentHandler.getMimeType());
         if (cfg != null) {
index 7ade64006d67033f9d871e8a73b0c74a64d168c2..48b82bbf34ea93d98769df08b4b3ee41147100cb 100644 (file)
@@ -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;
     }
 
index 5374c7051a4a3c181e017ded20ef5b5c5e6dae6b..b3664611729d9d15cc5f4182523c488083d8719c 100644 (file)
@@ -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;
index 118887d69b8ce2d32d5394e026b56c259ac6dd58..4d8fc1455c49ecd50dd85263412a01606cab0ccd 100644 (file)
       documents. Example: the fix of marks layering will be such a case when it's done.
     -->
     <release version="FOP Trunk" date="TBD">
+      <action context="Code" dev="PH" type="add" fixes-bug="52089" due-to="JM, Mehdi Houshmand">
+        Allow JPEG images to be embedded in an AFP document as is, without being decoded and 
+        encoded.
+      </action>
       <action context="Code" dev="PH" type="add" fixes-bug="52010" due-to="Mehdi Houshmand">
         Simplification of the build: Reduced code duplication and layout engine tests.
       </action>
diff --git a/test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java b/test/java/org/apache/fop/afp/AFPObjectAreaInfoTestCase.java
new file mode 100644 (file)
index 0000000..fc5f182
--- /dev/null
@@ -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 (file)
index 0000000..47c9306
--- /dev/null
@@ -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 (file)
index 0000000..c9ea9a5
--- /dev/null
@@ -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;
+    }
+}
index 8fa3e819d5aa92283f49357bc7cfb1ab51c48417..32d61cb3530ffcc16f9439b31f2297283b75f455 100644 (file)
 
 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 (file)
index 0000000..7c08e6d
--- /dev/null
@@ -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 <code>uri</code>
+     *
+     * @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());
+    }
+}
index 16b6651a65df547678f4bc8b7b69d467c77d24c4..117e7efcf7f39fe59cac561b2ca48eabbd260410 100644 (file)
@@ -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 (file)
index 0000000..540be7e
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+  <base>.</base>
+  <source-resolution>72</source-resolution>
+  <target-resolution>72</target-resolution>
+  <image-loading>
+  </image-loading>
+  <default-page-settings height="11in" width="8.26in"/>
+  <renderers>
+    <renderer mime="application/x-afp">
+      <images>
+        <jpeg allow-embedding="false" bitmap-encoding-quality="0.5"/>
+      </images>
+    </renderer>
+  </renderers>
+</fop>
diff --git a/test/resources/conf/afp/can_embed_jpeg.xconf b/test/resources/conf/afp/can_embed_jpeg.xconf
new file mode 100644 (file)
index 0000000..cd87ef0
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+  <base>.</base>
+  <source-resolution>72</source-resolution>
+  <target-resolution>72</target-resolution>
+  <image-loading>
+  </image-loading>
+  <default-page-settings height="11in" width="8.26in"/>
+  <renderers>
+    <renderer mime="application/x-afp">
+      <images>
+        <jpeg allow-embedding="true"/>
+      </images>
+    </renderer>
+  </renderers>
+</fop>
diff --git a/test/resources/conf/afp/no_image_config.xconf b/test/resources/conf/afp/no_image_config.xconf
new file mode 100644 (file)
index 0000000..ff1a089
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<fop version="1.0">
+  <base>.</base>
+  <source-resolution>72</source-resolution>
+  <target-resolution>72</target-resolution>
+  <image-loading>
+  </image-loading>
+  <default-page-settings height="11in" width="8.26in"/>
+  <renderers>
+    <renderer mime="application/x-afp">
+    </renderer>
+  </renderers>
+</fop>