diff options
author | Peter Hancock <phancock@apache.org> | 2011-11-01 12:20:21 +0000 |
---|---|---|
committer | Peter Hancock <phancock@apache.org> | 2011-11-01 12:20:21 +0000 |
commit | fd263a114c84c3756ead9c16b8b088531c5cab2c (patch) | |
tree | 4cd6ba7c8fe3a144629477ac911b2651707bdb03 | |
parent | 54a4751b616ab4e8957b97af4088bd725367ea6f (diff) | |
download | xmlgraphics-fop-fd263a114c84c3756ead9c16b8b088531c5cab2c.tar.gz xmlgraphics-fop-fd263a114c84c3756ead9c16b8b088531c5cab2c.zip |
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
35 files changed, 874 insertions, 139 deletions
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 </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> 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 @@ -220,6 +226,24 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState } /** + * 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 * converted to the selected color format. @@ -260,6 +284,25 @@ public class AFPPaintingState extends org.apache.fop.util.AbstractPaintingState } /** + * 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 * * @param 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/*<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; } 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/*<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); } /** 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<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()]); } /** 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 @@ -72,6 +72,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 * * @param 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 @@ -429,6 +429,11 @@ public class AFPDocumentHandler extends AbstractBinaryWritingIFDocumentHandler } /** {@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,11 +65,23 @@ 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) { AFPRenderingContext afpContext = (AFPRenderingContext)targetContext; 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 '<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) { 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. --> <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 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 <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()); + } +} 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 @@ +<?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 index 000000000..cd87ef07e --- /dev/null +++ b/test/resources/conf/afp/can_embed_jpeg.xconf @@ -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 index 000000000..ff1a089f4 --- /dev/null +++ b/test/resources/conf/afp/no_image_config.xconf @@ -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> |