| /* |
| * 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.ImageFlavor; |
| 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.ResourceEventProducer; |
| import org.apache.fop.apps.FOUserAgent; |
| import org.apache.fop.fo.Constants; |
| import org.apache.fop.fonts.FontInfo; |
| import org.apache.fop.fonts.FontTriplet; |
| import org.apache.fop.render.ImageHandler; |
| import org.apache.fop.render.ImageHandlerRegistry; |
| import org.apache.fop.render.ImageHandlerUtil; |
| 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<T extends IFDocumentHandler> 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; |
| |
| private final T documentHandler; |
| |
| /** |
| * Default constructor. |
| */ |
| public AbstractIFPainter(T documentHandler) { |
| this.documentHandler = documentHandler; |
| } |
| |
| protected String getFontKey(FontTriplet triplet) throws IFException { |
| String key = getFontInfo().getInternalFontKey(triplet); |
| if (key == null) { |
| throw new IFException("The font triplet is not available: \"" + triplet + "\" " |
| + "for the MIME type: \"" + documentHandler.getMimeType() + "\""); |
| } |
| return key; |
| } |
| |
| /** |
| * Returns the intermediate format context object. |
| * @return the context object |
| */ |
| public IFContext getContext() { |
| return documentHandler.getContext(); |
| } |
| |
| protected FontInfo getFontInfo() { |
| return documentHandler.getFontInfo(); |
| } |
| |
| protected T getDocumentHandler() { |
| return documentHandler; |
| } |
| |
| /** |
| * Returns the user agent. |
| * @return the user agent |
| */ |
| protected FOUserAgent getUserAgent() { |
| return getContext().getUserAgent(); |
| } |
| |
| private AffineTransform combine(AffineTransform[] transforms) { |
| AffineTransform at = new AffineTransform(); |
| for (AffineTransform transform : transforms) { |
| at.concatenate(transform); |
| } |
| 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, String layer) throws IFException { |
| startGroup(combine(transforms), layer); |
| } |
| |
| /** |
| * 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 = getUserAgent().getImageManager(); |
| ImageSessionContext sessionContext = getUserAgent().getImageSessionContext(); |
| ImageHandlerRegistry imageHandlerRegistry = getUserAgent().getImageHandlerRegistry(); |
| |
| //Load and convert the image to a supported format |
| RenderingContext context = createRenderingContext(); |
| Map hints = createDefaultImageProcessingHints(sessionContext); |
| context.putHints(hints); |
| |
| ImageFlavor[] flavors = imageHandlerRegistry.getSupportedFlavors(context); |
| info.getCustomObjects().put("warningincustomobject", true); |
| org.apache.xmlgraphics.image.loader.Image img = manager.getImage( |
| info, flavors, |
| hints, sessionContext); |
| |
| if (info.getCustomObjects().get("warning") != null) { |
| ResourceEventProducer eventProducer = ResourceEventProducer.Provider.get( |
| getUserAgent().getEventBroadcaster()); |
| eventProducer.imageWarning(this, (String)info.getCustomObjects().get("warning")); |
| } |
| |
| 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) { |
| Map hints = ImageUtil.getDefaultHints(sessionContext); |
| |
| //Transfer common foreign attributes to hints |
| Object conversionMode = getContext().getForeignAttribute(ImageHandlerUtil.CONVERSION_MODE); |
| if (conversionMode != null) { |
| hints.put(ImageHandlerUtil.CONVERSION_MODE, conversionMode); |
| } |
| hints.put("page-number", documentHandler.getContext().getPageNumber()); |
| return hints; |
| } |
| |
| /** |
| * 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 = getUserAgent().getImageManager(); |
| ImageHandlerRegistry imageHandlerRegistry = getUserAgent().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()); |
| } |
| context.putHint("fontinfo", getFontInfo()); |
| 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 = getUserAgent().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 = getUserAgent().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 = getUserAgent().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 top, BorderProps bottom, |
| BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException { |
| if (top != null) { |
| Rectangle b = new Rectangle( |
| rect.x, rect.y, |
| rect.width, top.width); |
| fillRect(b, top.color); |
| } |
| if (right != null) { |
| Rectangle b = new Rectangle( |
| rect.x + rect.width - right.width, rect.y, |
| right.width, rect.height); |
| fillRect(b, right.color); |
| } |
| if (bottom != null) { |
| Rectangle b = new Rectangle( |
| rect.x, rect.y + rect.height - bottom.width, |
| rect.width, bottom.width); |
| fillRect(b, bottom.color); |
| } |
| if (left != null) { |
| Rectangle b = new Rectangle( |
| rect.x, rect.y, |
| left.width, rect.height); |
| fillRect(b, left.color); |
| } |
| } |
| |
| /** |
| * Indicates whether the given border segments (if present) have only solid borders, i.e. |
| * could be painted in a simplified fashion keeping the output file smaller. |
| * @param top the border segment on the top edge |
| * @param bottom the border segment on the bottom edge |
| * @param left the border segment on the left edge |
| * @param right the border segment on the right edge |
| * @return true if any border segment has a non-solid border style |
| */ |
| protected boolean hasOnlySolidBorders(BorderProps top, BorderProps bottom, |
| BorderProps left, BorderProps right) { |
| if (top != null && top.style != Constants.EN_SOLID) { |
| return false; |
| } |
| if (bottom != null && bottom.style != Constants.EN_SOLID) { |
| return false; |
| } |
| if (left != null && left.style != Constants.EN_SOLID) { |
| return false; |
| } |
| if (right != null && right.style != Constants.EN_SOLID) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** {@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); |
| } |
| if (variant != null) { |
| state.setFontVariant(variant); |
| } |
| if (size != null) { |
| state.setFontSize(size); |
| } |
| 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); |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean isBackgroundRequired(BorderProps bpsBefore, BorderProps bpsAfter, |
| BorderProps bpsStart, BorderProps bpsEnd) { |
| return true; |
| } |
| |
| public void drawText(int x, int y, int letterSpacing, int wordSpacing, |
| int[][] dp, String text, boolean nextIsSpace) throws IFException { |
| drawText(x, y, letterSpacing, wordSpacing, dp, text); |
| } |
| } |