/* * 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.intermediate; import java.awt.Color; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import javax.xml.transform.dom.DOMSource; import org.w3c.dom.Document; 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.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.util.ImageUtil; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FopFactory; import org.apache.fop.events.ResourceEventProducer; import org.apache.fop.render.ImageHandler; import org.apache.fop.render.ImageHandlerRegistry; import org.apache.fop.render.RenderingContext; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; /** * Abstract base class for IFPainter implementations. */ public abstract class AbstractIFPainter implements IFPainter { /** logging instance */ private static Log log = LogFactory.getLog(AbstractIFPainter.class); /** non-URI that can be used in feedback messages that an image is an instream-object */ protected static final String INSTREAM_OBJECT_URI = "(instream-object)"; /** Holds the intermediate format state */ protected IFState state; /** * Default constructor. */ public AbstractIFPainter() { } /** * Returns the user agent. * @return the user agent */ protected abstract FOUserAgent getUserAgent(); /** * Returns the FOP factory. * @return the FOP factory. */ protected FopFactory getFopFactory() { return getUserAgent().getFactory(); } private AffineTransform combine(AffineTransform[] transforms) { AffineTransform at = new AffineTransform(); for (int i = 0, c = transforms.length; i < c; i++) { at.concatenate(transforms[i]); } return at; } /** {@inheritDoc} */ public void startViewport(AffineTransform[] transforms, Dimension size, Rectangle clipRect) throws IFException { startViewport(combine(transforms), size, clipRect); } /** {@inheritDoc} */ public void startGroup(AffineTransform[] transforms) throws IFException { startGroup(combine(transforms)); } /** * Creates a new RenderingContext instance. * @return the new rendering context. */ protected abstract RenderingContext createRenderingContext(); /** * Loads a preloaded image and draws it using a suitable image handler. * @param info the information object of the preloaded image * @param rect the rectangle in which to paint the image * @throws ImageException if there's an error while processing the image * @throws IOException if there's an I/O error while loading the image */ protected void drawImageUsingImageHandler(ImageInfo info, Rectangle rect) throws ImageException, IOException { ImageManager manager = getFopFactory().getImageManager(); ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); ImageHandlerRegistry imageHandlerRegistry = getFopFactory().getImageHandlerRegistry(); //Load and convert the image to a supported format RenderingContext context = createRenderingContext(); Map hints = createDefaultImageProcessingHints(sessionContext); context.putHints(hints); org.apache.xmlgraphics.image.loader.Image img = manager.getImage( info, imageHandlerRegistry.getSupportedFlavors(context), hints, sessionContext); try { drawImage(img, rect, context); } catch (IOException ioe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageWritingError(this, ioe); } } /** * Creates the default map of processing hints for the image loading framework. * @param sessionContext the session context for access to resolution information * @return the default processing hints */ protected Map createDefaultImageProcessingHints(ImageSessionContext sessionContext) { return ImageUtil.getDefaultHints(sessionContext); } /** * Draws an image using a suitable image handler. * @param image the image to be painted (it needs to of a supported image flavor) * @param rect the rectangle in which to paint the image * @param context a suitable rendering context * @throws IOException in case of an I/O error while handling/writing the image * @throws ImageException if an error occurs while converting the image to a suitable format */ protected void drawImage(Image image, Rectangle rect, RenderingContext context) throws IOException, ImageException { drawImage(image, rect, context, false, null); } /** * Draws an image using a suitable image handler. * @param image the image to be painted (it needs to of a supported image flavor) * @param rect the rectangle in which to paint the image * @param context a suitable rendering context * @param convert true to run the image through image conversion if that is necessary * @param additionalHints additional image processing hints * @throws IOException in case of an I/O error while handling/writing the image * @throws ImageException if an error occurs while converting the image to a suitable format */ protected void drawImage(Image image, Rectangle rect, RenderingContext context, boolean convert, Map additionalHints) throws IOException, ImageException { ImageManager manager = getFopFactory().getImageManager(); ImageHandlerRegistry imageHandlerRegistry = getFopFactory().getImageHandlerRegistry(); Image effImage; context.putHints(additionalHints); if (convert) { Map hints = createDefaultImageProcessingHints(getUserAgent().getImageSessionContext()); if (additionalHints != null) { hints.putAll(additionalHints); } effImage = manager.convertImage(image, imageHandlerRegistry.getSupportedFlavors(context), hints); } else { effImage = image; } //First check for a dynamically registered handler ImageHandler handler = imageHandlerRegistry.getHandler(context, effImage); if (handler == null) { throw new UnsupportedOperationException( "No ImageHandler available for image: " + effImage.getInfo() + " (" + effImage.getClass().getName() + ")"); } if (log.isTraceEnabled()) { log.trace("Using ImageHandler: " + handler.getClass().getName()); } //TODO foreign attributes handler.handleImage(context, effImage, rect); } /** * Returns an ImageInfo instance for the given URI. If there's an error, null is returned. * The caller can assume that any exceptions have already been handled properly. The caller * simply skips painting anything in this case. * @param uri the URI identifying the image * @return the ImageInfo instance or null if there has been an error. */ protected ImageInfo getImageInfo(String uri) { ImageManager manager = getFopFactory().getImageManager(); try { ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); return manager.getImageInfo(uri, sessionContext); } catch (ImageException ie) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageError(this, uri, ie, null); } catch (FileNotFoundException fe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageNotFound(this, uri, fe, null); } catch (IOException ioe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageIOError(this, uri, ioe, null); } return null; } /** * Default drawing method for handling an image referenced by a URI. * @param uri the image's URI * @param rect the rectangle in which to paint the image */ protected void drawImageUsingURI(String uri, Rectangle rect) { ImageManager manager = getFopFactory().getImageManager(); ImageInfo info = null; try { ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); info = manager.getImageInfo(uri, sessionContext); drawImageUsingImageHandler(info, rect); } catch (ImageException ie) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageError(this, (info != null ? info.toString() : uri), ie, null); } catch (FileNotFoundException fe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageNotFound(this, (info != null ? info.toString() : uri), fe, null); } catch (IOException ioe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageIOError(this, (info != null ? info.toString() : uri), ioe, null); } } /** * Default drawing method for handling a foreign object in the form of a DOM document. * @param doc the DOM document containing the foreign object * @param rect the rectangle in which to paint the image */ protected void drawImageUsingDocument(Document doc, Rectangle rect) { ImageManager manager = getFopFactory().getImageManager(); ImageInfo info = null; try { info = manager.preloadImage(null, new DOMSource(doc)); drawImageUsingImageHandler(info, rect); } catch (ImageException ie) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageError(this, (info != null ? info.toString() : INSTREAM_OBJECT_URI), ie, null); } catch (FileNotFoundException fe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageNotFound(this, (info != null ? info.toString() : INSTREAM_OBJECT_URI), fe, null); } catch (IOException ioe) { ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( getUserAgent().getEventBroadcaster()); eventProducer.imageIOError(this, (info != null ? info.toString() : INSTREAM_OBJECT_URI), ioe, null); } } /** {@inheritDoc} */ public void drawBorderRect(Rectangle rect, BorderProps before, BorderProps after, BorderProps start, BorderProps end) throws IFException { if (before != null) { Rectangle b = new Rectangle( rect.x, rect.y, rect.width, before.width); fillRect(b, before.color); } if (end != null) { Rectangle b = new Rectangle( rect.x + rect.width - end.width, rect.y, end.width, rect.height); fillRect(b, end.color); } if (after != null) { Rectangle b = new Rectangle( rect.x, rect.y + rect.height - after.width, rect.width, after.width); fillRect(b, after.color); } if (start != null) { Rectangle b = new Rectangle( rect.x, rect.y, start.width, rect.height); fillRect(b, start.color); } } /** {@inheritDoc} */ public void drawLine(Point start, Point end, int width, Color color, RuleStyle style) throws IFException { Rectangle rect = getLineBoundingBox(start, end, width); fillRect(rect, color); } /** * Calculates the bounding box for a line. Currently, only horizontal and vertical lines * are needed and supported. * @param start the starting point of the line (coordinates in mpt) * @param end the ending point of the line (coordinates in mpt) * @param width the line width (in mpt) * @return the bounding box (coordinates in mpt) */ protected Rectangle getLineBoundingBox(Point start, Point end, int width) { if (start.y == end.y) { int topy = start.y - width / 2; return new Rectangle( start.x, topy, end.x - start.x, width); } else if (start.x == end.y) { int leftx = start.x - width / 2; return new Rectangle( leftx, start.x, width, end.y - start.y); } else { throw new IllegalArgumentException( "Only horizontal or vertical lines are supported at the moment."); } } /** {@inheritDoc} */ public void setFont(String family, String style, Integer weight, String variant, Integer size, Color color) throws IFException { if (family != null) { state.setFontFamily(family); } if (style != null) { state.setFontStyle(style); } if (weight != null) { state.setFontWeight(weight.intValue()); } if (variant != null) { state.setFontVariant(variant); } if (size != null) { state.setFontSize(size.intValue()); } if (color != null) { state.setTextColor(color); } } /** * Converts a transformation matrix from millipoints to points. * @param transform the transformation matrix (in millipoints) * @return the converted transformation matrix (in points) */ public static AffineTransform toPoints(AffineTransform transform) { final double[] matrix = new double[6]; transform.getMatrix(matrix); //Convert from millipoints to points matrix[4] /= 1000; matrix[5] /= 1000; return new AffineTransform(matrix); } }