diff options
Diffstat (limited to 'src/java/org/apache/fop/svg')
14 files changed, 1255 insertions, 423 deletions
diff --git a/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java new file mode 100644 index 000000000..ae4d67516 --- /dev/null +++ b/src/java/org/apache/fop/svg/AbstractFOPBridgeContext.java @@ -0,0 +1,142 @@ +/* + * 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.svg; + +import java.awt.geom.AffineTransform; +import java.lang.reflect.Constructor; + +import org.apache.batik.bridge.Bridge; +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.DocumentLoader; +import org.apache.batik.bridge.UserAgent; +import org.apache.fop.fonts.FontInfo; +import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.xmlgraphics.image.loader.ImageSessionContext; + +/** + * A FOP base implementation of a Batik BridgeContext. + */ +public abstract class AbstractFOPBridgeContext extends BridgeContext { + + /** The font list. */ + protected final FontInfo fontInfo; + + protected final ImageManager imageManager; + protected final ImageSessionContext imageSessionContext; + + protected final AffineTransform linkTransform; + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param loader the Document Loader to use for referenced documents. + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param linkTransform AffineTransform to properly place links, + * may be null + * @param imageManager an image manager + * @param imageSessionContext an image session context + * @param linkTransform AffineTransform to properly place links, + * may be null + */ + public AbstractFOPBridgeContext(UserAgent userAgent, + DocumentLoader loader, + FontInfo fontInfo, + ImageManager imageManager, + ImageSessionContext imageSessionContext, + AffineTransform linkTransform) { + super(userAgent, loader); + this.fontInfo = fontInfo; + this.imageManager = imageManager; + this.imageSessionContext = imageSessionContext; + this.linkTransform = linkTransform; + } + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + * @param linkTransform AffineTransform to properly place links, + * may be null + */ + public AbstractFOPBridgeContext(UserAgent userAgent, + FontInfo fontInfo, + ImageManager imageManager, + ImageSessionContext imageSessionContext, + AffineTransform linkTransform) { + super(userAgent); + this.fontInfo = fontInfo; + this.imageManager = imageManager; + this.imageSessionContext = imageSessionContext; + this.linkTransform = linkTransform; + } + + /** + * Constructs a new bridge context. + * @param userAgent the user agent + * @param fontInfo the font list for the text painter, may be null + * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + */ + public AbstractFOPBridgeContext(UserAgent userAgent, + FontInfo fontInfo, + ImageManager imageManager, + ImageSessionContext imageSessionContext) { + this(userAgent, fontInfo, imageManager, imageSessionContext, null); + } + + /** + * Returns the ImageManager to be used by the ImageElementBridge. + * @return the image manager + */ + public ImageManager getImageManager() { + return this.imageManager; + } + + /** + * Returns the ImageSessionContext to be used by the ImageElementBridge. + * @return the image session context + */ + public ImageSessionContext getImageSessionContext() { + return this.imageSessionContext; + } + + protected void putElementBridgeConditional(String className, String testFor) { + try { + Class.forName(testFor); + //if we get here the test class is available + + Class clazz = Class.forName(className); + Constructor constructor = clazz.getConstructor(new Class[] {FontInfo.class}); + putBridge((Bridge)constructor.newInstance(new Object[] {fontInfo})); + } catch (Throwable t) { + //simply ignore (bridges instantiated over this method are optional) + } + } + + // Make sure any 'sub bridge contexts' also have our bridges. + //TODO There's no matching method in the super-class here + public abstract BridgeContext createBridgeContext(); + +} diff --git a/src/java/org/apache/fop/svg/AbstractFOPImageElementBridge.java b/src/java/org/apache/fop/svg/AbstractFOPImageElementBridge.java new file mode 100644 index 000000000..31895cebe --- /dev/null +++ b/src/java/org/apache/fop/svg/AbstractFOPImageElementBridge.java @@ -0,0 +1,284 @@ +/* + * 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.svg; + +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Rectangle2D; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.SVGImageElementBridge; +import org.apache.batik.gvt.AbstractGraphicsNode; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.util.ParsedURL; +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.impl.ImageGraphics2D; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; +import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; +import org.apache.xmlgraphics.java2d.Graphics2DImagePainter; +import org.w3c.dom.Element; +import org.w3c.dom.svg.SVGDocument; + +/** + * Bridge class for the <image> element when jpeg images. + * + * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> + */ +public abstract class AbstractFOPImageElementBridge extends SVGImageElementBridge { + + /** + * Constructs a new bridge for the <image> element. + */ + public AbstractFOPImageElementBridge() { } + + /** + * Create the raster image node. + * THis checks if it is a jpeg file and creates a jpeg node + * so the jpeg can be inserted directly into the pdf document. + * @param ctx the bridge context + * @param imageElement the svg element for the image + * @param purl the parsed url for the image resource + * @return a new graphics node + */ + protected GraphicsNode createImageGraphicsNode + (BridgeContext ctx, Element imageElement, ParsedURL purl) { + AbstractFOPBridgeContext bridgeCtx = (AbstractFOPBridgeContext)ctx; + + ImageManager manager = bridgeCtx.getImageManager(); + ImageSessionContext sessionContext = bridgeCtx.getImageSessionContext(); + try { + ImageInfo info = manager.getImageInfo(purl.toString(), sessionContext); + ImageFlavor[] supportedFlavors = getSupportedFlavours(); + Image image = manager.getImage(info, supportedFlavors, sessionContext); + + //TODO color profile overrides aren't handled, yet! + //ICCColorSpaceExt colorspaceOverride = extractColorSpace(e, ctx); + AbstractGraphicsNode specializedNode = null; + if (image instanceof ImageXMLDOM) { + ImageXMLDOM xmlImage = (ImageXMLDOM)image; + if (xmlImage.getDocument() instanceof SVGDocument) { + return createSVGImageNode(ctx, imageElement, + (SVGDocument)xmlImage.getDocument()); + } else { + //Convert image to Graphics2D + image = manager.convertImage(xmlImage, + new ImageFlavor[] {ImageFlavor.GRAPHICS2D}); + } + } + if (image instanceof ImageRawJPEG) { + specializedNode = createLoaderImageNode(image, ctx, imageElement, purl); + } else if (image instanceof ImageRawCCITTFax) { + specializedNode = createLoaderImageNode(image, ctx, imageElement, purl); + } else if (image instanceof ImageGraphics2D) { + ImageGraphics2D g2dImage = (ImageGraphics2D)image; + specializedNode = new Graphics2DNode(g2dImage); + } else { + ctx.getUserAgent().displayError( + new ImageException("Cannot convert an image to a usable format: " + purl)); + } + + Rectangle2D imgBounds = getImageBounds(ctx, imageElement); + Rectangle2D bounds = specializedNode.getPrimitiveBounds(); + float [] vb = new float[4]; + vb[0] = 0; // x + vb[1] = 0; // y + vb[2] = (float) bounds.getWidth(); // width + vb[3] = (float) bounds.getHeight(); // height + + // handles the 'preserveAspectRatio', 'overflow' and 'clip' + // and sets the appropriate AffineTransform to the image node + initializeViewport(ctx, imageElement, specializedNode, vb, imgBounds); + return specializedNode; + } catch (Exception e) { + ctx.getUserAgent().displayError(e); + } + + return superCreateGraphicsNode(ctx, imageElement, purl); + } + + /** + * Calls the superclass' createImageGraphicNode() method to create the normal GraphicsNode. + * @param ctx the bridge context + * @param imageElement the image element + * @param purl the parsed URL + * @return the newly created graphics node + * @see org.apache.batik.bridge.SVGImageElementBridge#createGraphicsNode(BridgeContext, Element) + */ + protected GraphicsNode superCreateGraphicsNode + (BridgeContext ctx, Element imageElement, ParsedURL purl) { + return super.createImageGraphicsNode(ctx, imageElement, purl); + } + + /** + * Returns an array of supported image flavours + * + * @return an array of supported image flavours + */ + protected abstract ImageFlavor[] getSupportedFlavours(); + + /** + * Creates a loader image node implementation + * @param purl the parsed url + * @param imageElement the image element + * @param ctx the batik bridge context + * @param image the image + * + * @return a loader image node implementation + */ + protected LoaderImageNode createLoaderImageNode( + Image image, BridgeContext ctx, Element imageElement, ParsedURL purl) { + return new LoaderImageNode(image, ctx, imageElement, purl); + } + + /** + * An image node for natively handled Image instance. + * This holds a natively handled image so that it can be drawn into + * the PDFGraphics2D. + */ + public class LoaderImageNode extends AbstractGraphicsNode { + + protected final Image image; + protected final BridgeContext ctx; + protected final Element imageElement; + protected final ParsedURL purl; + protected GraphicsNode origGraphicsNode = null; + + /** + * Create a new image node for drawing natively handled images + * into PDF graphics. + * @param image the JPEG image + * @param ctx the bridge context + * @param imageElement the SVG image element + * @param purl the URL to the image + */ + public LoaderImageNode(Image image, BridgeContext ctx, + Element imageElement, ParsedURL purl) { + this.image = image; + this.ctx = ctx; + this.imageElement = imageElement; + this.purl = purl; + } + + /** {@inheritDoc} */ + public Shape getOutline() { + return getPrimitiveBounds(); + } + + /** {@inheritDoc} */ + public void primitivePaint(Graphics2D g2d) { + if (g2d instanceof NativeImageHandler) { + NativeImageHandler nativeImageHandler = (NativeImageHandler) g2d; + float x = 0; + float y = 0; + try { + float width = image.getSize().getWidthPx(); + float height = image.getSize().getHeightPx(); + nativeImageHandler.addNativeImage(image, x, y, width, height); + } catch (Exception e) { + ctx.getUserAgent().displayError(e); + } + } else { + // Not going directly into PDF so use + // original implementation so filters etc work. + if (origGraphicsNode == null) { + // Haven't constructed base class Graphics Node, + // so do so now. + origGraphicsNode + = superCreateGraphicsNode(ctx, imageElement, purl); + } + origGraphicsNode.primitivePaint(g2d); + } + } + + /** {@inheritDoc} */ + public Rectangle2D getGeometryBounds() { + return getPrimitiveBounds(); + } + + /** {@inheritDoc} */ + public Rectangle2D getPrimitiveBounds() { + return new Rectangle2D.Double(0, 0, + image.getSize().getWidthPx(), + image.getSize().getHeightPx()); + } + + /** {@inheritDoc} */ + public Rectangle2D getSensitiveBounds() { + //No interactive features, just return primitive bounds + return getPrimitiveBounds(); + } + + } + + /** + * A node that holds a Graphics2D image. + */ + public class Graphics2DNode extends AbstractGraphicsNode { + + private final ImageGraphics2D image; + + /** + * Create a new Graphics2D node. + * @param g2d the Graphics2D image + */ + public Graphics2DNode(ImageGraphics2D g2d) { + this.image = g2d; + } + + /** {@inheritDoc} */ + public Shape getOutline() { + return getPrimitiveBounds(); + } + + /** {@inheritDoc} */ + public void primitivePaint(Graphics2D g2d) { + int width = image.getSize().getWidthPx(); + int height = image.getSize().getHeightPx(); + Rectangle2D area = new Rectangle2D.Double(0, 0, width, height); + Graphics2DImagePainter painter = image.getGraphics2DImagePainter(); + painter.paint(g2d, area); + } + + /** {@inheritDoc} */ + public Rectangle2D getGeometryBounds() { + return getPrimitiveBounds(); + } + + /** {@inheritDoc} */ + public Rectangle2D getPrimitiveBounds() { + return new Rectangle2D.Double(0, 0, + image.getSize().getWidthPx(), + image.getSize().getHeightPx()); + } + + /** {@inheritDoc} */ + public Rectangle2D getSensitiveBounds() { + //No interactive features, just return primitive bounds + return getPrimitiveBounds(); + } + + } +} diff --git a/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java new file mode 100644 index 000000000..53b8e2ad5 --- /dev/null +++ b/src/java/org/apache/fop/svg/AbstractFOPTextElementBridge.java @@ -0,0 +1,113 @@ +/* + * 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.svg; + +import org.apache.batik.bridge.BridgeContext; +import org.apache.batik.bridge.SVGTextElementBridge; +import org.apache.batik.gvt.GraphicsNode; +import org.apache.batik.gvt.TextNode; +import org.apache.batik.gvt.TextPainter; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Bridge class for the <text> element. + * This bridge will use the direct text painter if the text + * for the element is simple. + * + * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> + */ +public abstract class AbstractFOPTextElementBridge extends SVGTextElementBridge { + + /** text painter */ + protected TextPainter textPainter; + + /** + * Main constructor + * + * @param textPainter the text painter + */ + public AbstractFOPTextElementBridge(TextPainter textPainter) { + this.textPainter = textPainter; + } + + /** + * Create a text element bridge. + * + * This set the text painter on the node if the text is simple. + * @param ctx the bridge context + * @param e the svg element + * @return the text graphics node created by the super class + */ + public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { + GraphicsNode node = super.createGraphicsNode(ctx, e); + if (node != null) { + //Set our own text painter + ((TextNode)node).setTextPainter(textPainter); + } + return node; + } + + /** + * Check if text element contains simple text. + * This checks the children of the text element to determine + * if the text is simple. The text is simple if it can be rendered + * with basic text drawing algorithms. This means there are no + * alternate characters, the font is known and there are no effects + * applied to the text. + * + * @param ctx the bridge context + * @param element the svg text element + * @param node the graphics node + * @return true if this text is simple of false if it cannot be + * easily rendered using normal drawString on the Graphics2D + */ + protected boolean isSimple(BridgeContext ctx, Element element, GraphicsNode node) { + for (Node n = element.getFirstChild(); + n != null; + n = n.getNextSibling()) { + + switch (n.getNodeType()) { + case Node.ELEMENT_NODE: + + if (n.getLocalName().equals(SVG_TSPAN_TAG) + || n.getLocalName().equals(SVG_ALT_GLYPH_TAG)) { + return false; + } else if (n.getLocalName().equals(SVG_TEXT_PATH_TAG)) { + return false; + } else if (n.getLocalName().equals(SVG_TREF_TAG)) { + return false; + } + break; + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + default: + } + } + + /*if (CSSUtilities.convertFilter(element, node, ctx) != null) { + return false; + }*/ + + return true; + } + +} + diff --git a/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java b/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java new file mode 100644 index 000000000..560c76cb5 --- /dev/null +++ b/src/java/org/apache/fop/svg/AbstractFOPTextPainter.java @@ -0,0 +1,528 @@ +/* + * 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.svg; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Paint; +import java.awt.Shape; +import java.awt.font.TextAttribute; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.io.IOException; +import java.text.AttributedCharacterIterator; +import java.text.CharacterIterator; +import java.util.Iterator; +import java.util.List; + +import org.apache.batik.dom.svg.SVGOMTextElement; +import org.apache.batik.gvt.TextNode; +import org.apache.batik.gvt.TextPainter; +import org.apache.batik.gvt.font.GVTFontFamily; +import org.apache.batik.gvt.renderer.StrokingTextPainter; +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator; +import org.apache.batik.gvt.text.Mark; +import org.apache.batik.gvt.text.TextPaintInfo; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.fop.afp.AFPGraphics2D; +import org.apache.fop.fonts.Font; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontTriplet; + + +/** + * Renders the attributed character iterator of a <tt>TextNode</tt>. + * This class draws the text directly into the Graphics2D so that + * the text is not drawn using shapes. + * If the text is simple enough to draw then it sets the font and calls + * drawString. If the text is complex or the cannot be translated + * into a simple drawString the StrokingTextPainter is used instead. + */ +public abstract class AbstractFOPTextPainter implements TextPainter { + + /** the logger for this class */ + protected Log log = LogFactory.getLog(AbstractFOPTextPainter.class); + + private final FOPTextHandler nativeTextHandler; + + /** + * Use the stroking text painter to get the bounds and shape. + * Also used as a fallback to draw the string with strokes. + */ + protected static final TextPainter + PROXY_PAINTER = StrokingTextPainter.getInstance(); + + /** + * Create a new PS text painter with the given font information. + * @param nativeTextHandler the NativeTextHandler instance used for text painting + */ + public AbstractFOPTextPainter(FOPTextHandler nativeTextHandler) { + this.nativeTextHandler = nativeTextHandler; + } + + /** + * Paints the specified attributed character iterator using the + * specified Graphics2D and context and font context. + * + * @param node the TextNode to paint + * @param g2d the Graphics2D to use + */ + public void paint(TextNode node, Graphics2D g2d) { + Point2D loc = node.getLocation(); + log.debug("painting text node " + node); + if (hasUnsupportedAttributes(node)) { + log.debug("hasUnsuportedAttributes"); + PROXY_PAINTER.paint(node, g2d); + } else { + log.debug("allAttributesSupported"); + paintTextRuns(node.getTextRuns(), g2d, loc); + } + } + + private boolean hasUnsupportedAttributes(TextNode node) { + Iterator iter = node.getTextRuns().iterator(); + while (iter.hasNext()) { + StrokingTextPainter.TextRun + run = (StrokingTextPainter.TextRun)iter.next(); + AttributedCharacterIterator aci = run.getACI(); + boolean hasUnsupported = hasUnsupportedAttributes(aci); + if (hasUnsupported) { + return true; + } + } + return false; + } + + private boolean hasUnsupportedAttributes(AttributedCharacterIterator aci) { + boolean hasUnsupported = false; + + Font font = getFont(aci); + String text = getText(aci); + if (hasUnsupportedGlyphs(text, font)) { + log.trace("-> Unsupported glyphs found"); + hasUnsupported = true; + } + + TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); + if ((tpi != null) + && ((tpi.strokeStroke != null && tpi.strokePaint != null) + || (tpi.strikethroughStroke != null) + || (tpi.underlineStroke != null) + || (tpi.overlineStroke != null))) { + log.trace("-> under/overlines etc. found"); + hasUnsupported = true; + } + + //Alpha is not supported + Paint foreground = (Paint) aci.getAttribute(TextAttribute.FOREGROUND); + if (foreground instanceof Color) { + Color col = (Color)foreground; + if (col.getAlpha() != 255) { + log.trace("-> transparency found"); + hasUnsupported = true; + } + } + + Object letSpace = aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING); + if (letSpace != null) { + log.trace("-> letter spacing found"); + hasUnsupported = true; + } + + Object wordSpace = aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING); + if (wordSpace != null) { + log.trace("-> word spacing found"); + hasUnsupported = true; + } + + Object lengthAdjust = aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST); + if (lengthAdjust != null) { + log.trace("-> length adjustments found"); + hasUnsupported = true; + } + + Object writeMod = aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE); + if (writeMod != null + && !GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_LTR.equals( + writeMod)) { + log.trace("-> Unsupported writing modes found"); + hasUnsupported = true; + } + + Object vertOr = aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION); + if (GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_ANGLE.equals( + vertOr)) { + log.trace("-> vertical orientation found"); + hasUnsupported = true; + } + + Object rcDel = aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.TEXT_COMPOUND_DELIMITER); + //Batik 1.6 returns null here which makes it impossible to determine whether this can + //be painted or not, i.e. fall back to stroking. :-( + if (rcDel != null && !(rcDel instanceof SVGOMTextElement)) { + log.trace("-> spans found"); + hasUnsupported = true; //Filter spans + } + + if (hasUnsupported) { + log.trace("Unsupported attributes found in ACI, using StrokingTextPainter"); + } + return hasUnsupported; + } + + /** + * Paint a list of text runs on the Graphics2D at a given location. + * @param textRuns the list of text runs + * @param g2d the Graphics2D to paint to + * @param loc the current location of the "cursor" + */ + protected void paintTextRuns(List textRuns, Graphics2D g2d, Point2D loc) { + Point2D currentloc = loc; + Iterator i = textRuns.iterator(); + while (i.hasNext()) { + StrokingTextPainter.TextRun + run = (StrokingTextPainter.TextRun)i.next(); + currentloc = paintTextRun(run, g2d, currentloc); + } + } + + /** + * Paint a single text run on the Graphics2D at a given location. + * @param run the text run to paint + * @param g2d the Graphics2D to paint to + * @param loc the current location of the "cursor" + * @return the new location of the "cursor" after painting the text run + */ + protected Point2D paintTextRun(StrokingTextPainter.TextRun run, Graphics2D g2d, Point2D loc) { + AttributedCharacterIterator aci = run.getACI(); + aci.first(); + + updateLocationFromACI(aci, loc); + AffineTransform at = g2d.getTransform(); + loc = at.transform(loc, null); + + // font + Font font = getFont(aci); + if (font != null) { + nativeTextHandler.setOverrideFont(font); + } + + // color + TextPaintInfo tpi = (TextPaintInfo) aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.PAINT_INFO); + if (tpi == null) { + return loc; + } + Paint foreground = tpi.fillPaint; + if (foreground instanceof Color) { + Color col = (Color)foreground; + g2d.setColor(col); + } + g2d.setPaint(foreground); + + // text + String txt = getText(aci); + float advance = getStringWidth(txt, font); + float tx = 0; + TextNode.Anchor anchor = (TextNode.Anchor)aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.ANCHOR_TYPE); + if (anchor != null) { + switch (anchor.getType()) { + case TextNode.Anchor.ANCHOR_MIDDLE: + tx = -advance / 2; + break; + case TextNode.Anchor.ANCHOR_END: + tx = -advance; + break; + default: //nop + } + } + + // draw string + double x = loc.getX(); + double y = loc.getY(); + try { + try { + nativeTextHandler.drawString(g2d, txt, (float)x + tx, (float)y); + } catch (IOException ioe) { + if (g2d instanceof AFPGraphics2D) { + ((AFPGraphics2D)g2d).handleIOException(ioe); + } + } + } finally { + nativeTextHandler.setOverrideFont(null); + } + loc.setLocation(loc.getX() + advance, loc.getY()); + return loc; + } + + /** + * Extract the raw text from an ACI. + * @param aci ACI to inspect + * @return the extracted text + */ + protected String getText(AttributedCharacterIterator aci) { + StringBuffer sb = new StringBuffer(aci.getEndIndex() - aci.getBeginIndex()); + for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) { + sb.append(c); + } + return sb.toString(); + } + + private void updateLocationFromACI( + AttributedCharacterIterator aci, + Point2D loc) { + //Adjust position of span + Float xpos = (Float)aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.X); + Float ypos = (Float)aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.Y); + Float dxpos = (Float)aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.DX); + Float dypos = (Float)aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.DY); + if (xpos != null) { + loc.setLocation(xpos.doubleValue(), loc.getY()); + } + if (ypos != null) { + loc.setLocation(loc.getX(), ypos.doubleValue()); + } + if (dxpos != null) { + loc.setLocation(loc.getX() + dxpos.doubleValue(), loc.getY()); + } + if (dypos != null) { + loc.setLocation(loc.getX(), loc.getY() + dypos.doubleValue()); + } + } + + private String getStyle(AttributedCharacterIterator aci) { + Float posture = (Float)aci.getAttribute(TextAttribute.POSTURE); + return ((posture != null) && (posture.floatValue() > 0.0)) + ? Font.STYLE_ITALIC + : Font.STYLE_NORMAL; + } + + private int getWeight(AttributedCharacterIterator aci) { + Float taWeight = (Float)aci.getAttribute(TextAttribute.WEIGHT); + return ((taWeight != null) && (taWeight.floatValue() > 1.0)) + ? Font.WEIGHT_BOLD + : Font.WEIGHT_NORMAL; + } + + private Font getFont(AttributedCharacterIterator aci) { + Float fontSize = (Float)aci.getAttribute(TextAttribute.SIZE); + String style = getStyle(aci); + int weight = getWeight(aci); + + FontInfo fontInfo = nativeTextHandler.getFontInfo(); + String fontFamily = null; + List gvtFonts = (List) aci.getAttribute( + GVTAttributedCharacterIterator.TextAttribute.GVT_FONT_FAMILIES); + if (gvtFonts != null) { + Iterator i = gvtFonts.iterator(); + while (i.hasNext()) { + GVTFontFamily fam = (GVTFontFamily) i.next(); + /* (todo) Enable SVG Font painting + if (fam instanceof SVGFontFamily) { + PROXY_PAINTER.paint(node, g2d); + return; + }*/ + fontFamily = fam.getFamilyName(); + if (fontInfo.hasFont(fontFamily, style, weight)) { + FontTriplet triplet = fontInfo.fontLookup( + fontFamily, style, weight); + int fsize = (int)(fontSize.floatValue() * 1000); + return fontInfo.getFontInstance(triplet, fsize); + } + } + } + FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL); + int fsize = (int)(fontSize.floatValue() * 1000); + return fontInfo.getFontInstance(triplet, fsize); + } + + private float getStringWidth(String str, Font font) { + float wordWidth = 0; + float whitespaceWidth = font.getWidth(font.mapChar(' ')); + + for (int i = 0; i < str.length(); i++) { + float charWidth; + char c = str.charAt(i); + if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { + charWidth = font.getWidth(font.mapChar(c)); + if (charWidth <= 0) { + charWidth = whitespaceWidth; + } + } else { + charWidth = whitespaceWidth; + } + wordWidth += charWidth; + } + return wordWidth / 1000f; + } + + private boolean hasUnsupportedGlyphs(String str, Font font) { + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (!((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t'))) { + if (!font.hasChar(c)) { + return true; + } + } + } + return false; + } + + /** + * Get the outline shape of the text characters. + * This uses the StrokingTextPainter to get the outline + * shape since in theory it should be the same. + * + * @param node the text node + * @return the outline shape of the text characters + */ + public Shape getOutline(TextNode node) { + return PROXY_PAINTER.getOutline(node); + } + + /** + * Get the bounds. + * This uses the StrokingTextPainter to get the bounds + * since in theory it should be the same. + * + * @param node the text node + * @return the bounds of the text + */ + public Rectangle2D getBounds2D(TextNode node) { + /* (todo) getBounds2D() is too slow + * because it uses the StrokingTextPainter. We should implement this + * method ourselves. */ + return PROXY_PAINTER.getBounds2D(node); + } + + /** + * Get the geometry bounds. + * This uses the StrokingTextPainter to get the bounds + * since in theory it should be the same. + * + * @param node the text node + * @return the bounds of the text + */ + public Rectangle2D getGeometryBounds(TextNode node) { + return PROXY_PAINTER.getGeometryBounds(node); + } + + // Methods that have no purpose for PS + + /** + * Get the mark. + * This does nothing since the output is AFP and not interactive. + * + * @param node the text node + * @param pos the position + * @param all select all + * @return null + */ + public Mark getMark(TextNode node, int pos, boolean all) { + return null; + } + + /** + * Select at. + * This does nothing since the output is AFP and not interactive. + * + * @param x the x position + * @param y the y position + * @param node the text node + * @return null + */ + public Mark selectAt(double x, double y, TextNode node) { + return null; + } + + /** + * Select to. + * This does nothing since the output is AFP and not interactive. + * + * @param x the x position + * @param y the y position + * @param beginMark the start mark + * @return null + */ + public Mark selectTo(double x, double y, Mark beginMark) { + return null; + } + + /** + * Selec first. + * This does nothing since the output is AFP and not interactive. + * + * @param node the text node + * @return null + */ + public Mark selectFirst(TextNode node) { + return null; + } + + /** + * Select last. + * This does nothing since the output is AFP and not interactive. + * + * @param node the text node + * @return null + */ + public Mark selectLast(TextNode node) { + return null; + } + + /** + * Get selected. + * This does nothing since the output is AFP and not interactive. + * + * @param start the start mark + * @param finish the finish mark + * @return null + */ + public int[] getSelected(Mark start, Mark finish) { + return null; + } + + /** + * Get the highlighted shape. + * This does nothing since the output is AFP and not interactive. + * + * @param beginMark the start mark + * @param endMark the end mark + * @return null + */ + public Shape getHighlightShape(Mark beginMark, Mark endMark) { + return null; + } + +} diff --git a/src/java/org/apache/fop/svg/FOPTextHandler.java b/src/java/org/apache/fop/svg/FOPTextHandler.java new file mode 100644 index 000000000..8fa9eeedd --- /dev/null +++ b/src/java/org/apache/fop/svg/FOPTextHandler.java @@ -0,0 +1,27 @@ +/* + * 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.svg; + +public interface FOPTextHandler extends org.apache.xmlgraphics.java2d.TextHandler { + + void setOverrideFont(org.apache.fop.fonts.Font font); + + org.apache.fop.fonts.FontInfo getFontInfo(); +} diff --git a/src/java/org/apache/fop/svg/GraphicsConfiguration.java b/src/java/org/apache/fop/svg/GraphicsConfiguration.java index ca3b3363c..881096b9a 100644 --- a/src/java/org/apache/fop/svg/GraphicsConfiguration.java +++ b/src/java/org/apache/fop/svg/GraphicsConfiguration.java @@ -17,7 +17,6 @@ /* $Id$ */ - package org.apache.fop.svg; import java.awt.image.VolatileImage; diff --git a/src/java/org/apache/fop/svg/NativeImageHandler.java b/src/java/org/apache/fop/svg/NativeImageHandler.java new file mode 100644 index 000000000..8e74cba1d --- /dev/null +++ b/src/java/org/apache/fop/svg/NativeImageHandler.java @@ -0,0 +1,40 @@ +/* + * 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.svg; + +public interface NativeImageHandler { + + /** + * Add a natively handled image directly to the document. + * This is used by the ImageElementBridge to draw a natively handled image + * (like JPEG or CCITT images) + * directly into the document rather than converting the image into + * a bitmap and increasing the size. + * + * @param image the image to draw + * @param x the x position + * @param y the y position + * @param width the width to draw the image + * @param height the height to draw the image + */ + void addNativeImage(org.apache.xmlgraphics.image.loader.Image image, float x, float y, + float width, float height); + +} diff --git a/src/java/org/apache/fop/svg/PDFBridgeContext.java b/src/java/org/apache/fop/svg/PDFBridgeContext.java index fdf83784f..364c7a6f3 100644 --- a/src/java/org/apache/fop/svg/PDFBridgeContext.java +++ b/src/java/org/apache/fop/svg/PDFBridgeContext.java @@ -20,30 +20,20 @@ package org.apache.fop.svg; import java.awt.geom.AffineTransform; -import java.lang.reflect.Constructor; -import org.apache.batik.bridge.Bridge; import org.apache.batik.bridge.BridgeContext; import org.apache.batik.bridge.DocumentLoader; +import org.apache.batik.bridge.SVGTextElementBridge; import org.apache.batik.bridge.UserAgent; - +import org.apache.batik.gvt.TextPainter; +import org.apache.fop.fonts.FontInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; -import org.apache.fop.fonts.FontInfo; - /** * BridgeContext which registers the custom bridges for PDF output. */ -public class PDFBridgeContext extends BridgeContext { - - /** The font list. */ - private final FontInfo fontInfo; - - private final ImageManager imageManager; - private final ImageSessionContext imageSessionContext; - - private AffineTransform linkTransform; +public class PDFBridgeContext extends AbstractFOPBridgeContext { /** * Constructs a new bridge context. @@ -53,18 +43,16 @@ public class PDFBridgeContext extends BridgeContext { * in which case text is painted as shapes * @param linkTransform AffineTransform to properly place links, * may be null + * @param imageManager an image manager + * @param imageSessionContext an image session context + * @param linkTransform AffineTransform to properly place links, + * may be null */ - public PDFBridgeContext(UserAgent userAgent, - DocumentLoader loader, - FontInfo fontInfo, - ImageManager imageManager, - ImageSessionContext imageSessionContext, - AffineTransform linkTransform) { - super(userAgent, loader); - this.fontInfo = fontInfo; - this.imageManager = imageManager; - this.imageSessionContext = imageSessionContext; - this.linkTransform = linkTransform; + public PDFBridgeContext(UserAgent userAgent, DocumentLoader documentLoader, + FontInfo fontInfo, ImageManager imageManager, + ImageSessionContext imageSessionContext, + AffineTransform linkTransform) { + super(userAgent, documentLoader, fontInfo, imageManager, imageSessionContext, linkTransform); } /** @@ -72,19 +60,12 @@ public class PDFBridgeContext extends BridgeContext { * @param userAgent the user agent * @param fontInfo the font list for the text painter, may be null * in which case text is painted as shapes - * @param linkTransform AffineTransform to properly place links, - * may be null + * @param imageManager an image manager + * @param imageSessionContext an image session context */ - public PDFBridgeContext(UserAgent userAgent, - FontInfo fontInfo, - ImageManager imageManager, - ImageSessionContext imageSessionContext, - AffineTransform linkTransform) { - super(userAgent); - this.fontInfo = fontInfo; - this.imageManager = imageManager; - this.imageSessionContext = imageSessionContext; - this.linkTransform = linkTransform; + public PDFBridgeContext(UserAgent userAgent, FontInfo fontInfo, + ImageManager imageManager, ImageSessionContext imageSessionContext) { + super(userAgent, fontInfo, imageManager, imageSessionContext); } /** @@ -92,41 +73,15 @@ public class PDFBridgeContext extends BridgeContext { * @param userAgent the user agent * @param fontInfo the font list for the text painter, may be null * in which case text is painted as shapes + * @param imageManager an image manager + * @param imageSessionContext an image session context + * @param linkTransform AffineTransform to properly place links, + * may be null */ - public PDFBridgeContext(UserAgent userAgent, - FontInfo fontInfo, - ImageManager imageManager, - ImageSessionContext imageSessionContext) { - this(userAgent, fontInfo, imageManager, imageSessionContext, null); - } - - /** - * Returns the ImageManager to be used by the ImageElementBridge. - * @return the image manager - */ - public ImageManager getImageManager() { - return this.imageManager; - } - - /** - * Returns the ImageSessionContext to be used by the ImageElementBridge. - * @return the image session context - */ - public ImageSessionContext getImageSessionContext() { - return this.imageSessionContext; - } - - private void putPDFElementBridgeConditional(String className, String testFor) { - try { - Class.forName(testFor); - //if we get here the test class is available - - Class clazz = Class.forName(className); - Constructor constructor = clazz.getConstructor(new Class[] {FontInfo.class}); - putBridge((Bridge)constructor.newInstance(new Object[] {fontInfo})); - } catch (Throwable t) { - //simply ignore (bridges instantiated over this method are optional) - } + public PDFBridgeContext(SVGUserAgent userAgent, FontInfo fontInfo, + ImageManager imageManager, ImageSessionContext imageSessionContext, + AffineTransform linkTransform) { + super(userAgent, fontInfo, imageManager, imageSessionContext, linkTransform); } /** {@inheritDoc} */ @@ -134,23 +89,24 @@ public class PDFBridgeContext extends BridgeContext { super.registerSVGBridges(); if (fontInfo != null) { - PDFTextElementBridge textElementBridge = new PDFTextElementBridge(fontInfo); + TextPainter textPainter = new PDFTextPainter(fontInfo); + SVGTextElementBridge textElementBridge = new PDFTextElementBridge(textPainter); putBridge(textElementBridge); //Batik flow text extension (may not always be available) //putBridge(new PDFBatikFlowTextElementBridge(fontInfo); - putPDFElementBridgeConditional( + putElementBridgeConditional( "org.apache.fop.svg.PDFBatikFlowTextElementBridge", "org.apache.batik.extension.svg.BatikFlowTextElementBridge"); //SVG 1.2 flow text support //putBridge(new PDFSVG12TextElementBridge(fontInfo)); //-->Batik 1.7 - putPDFElementBridgeConditional( + putElementBridgeConditional( "org.apache.fop.svg.PDFSVG12TextElementBridge", "org.apache.batik.bridge.svg12.SVG12TextElementBridge"); //putBridge(new PDFSVGFlowRootElementBridge(fontInfo)); - putPDFElementBridgeConditional( + putElementBridgeConditional( "org.apache.fop.svg.PDFSVGFlowRootElementBridge", "org.apache.batik.bridge.svg12.SVGFlowRootElementBridge"); } diff --git a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java index dd13df1c4..cc6e06978 100644 --- a/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFDocumentGraphics2D.java @@ -19,30 +19,30 @@ package org.apache.fop.svg; -import org.apache.fop.Version; -import org.apache.fop.pdf.PDFDocument; -import org.apache.fop.pdf.PDFFilterList; -import org.apache.fop.pdf.PDFPage; -import org.apache.fop.pdf.PDFStream; -import org.apache.fop.pdf.PDFState; -import org.apache.fop.pdf.PDFNumber; -import org.apache.fop.pdf.PDFResources; -import org.apache.fop.pdf.PDFColor; -import org.apache.fop.pdf.PDFAnnotList; -import org.apache.fop.fonts.FontInfo; -import org.apache.fop.fonts.FontSetup; - -import java.awt.Graphics; -import java.awt.Font; import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; -import java.io.OutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.StringWriter; +import org.apache.fop.Version; +import org.apache.fop.fonts.FontInfo; +import org.apache.fop.fonts.FontSetup; +import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFColor; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFFilterList; +import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFPaintingState; +import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFStream; + /** * This class is a wrapper for the <tt>PDFGraphics2D</tt> that * is used to create a full document around the pdf rendering from @@ -52,7 +52,7 @@ import java.io.StringWriter; */ public class PDFDocumentGraphics2D extends PDFGraphics2D { - private PDFContext pdfContext; + private final PDFContext pdfContext; private int width; private int height; @@ -296,7 +296,7 @@ public class PDFDocumentGraphics2D extends PDFGraphics2D { throw new IllegalStateException("Close page first before starting another"); } //Start page - graphicsState = new PDFState(); + paintingState = new PDFPaintingState(); if (this.initialTransform == null) { //Save initial transformation matrix this.initialTransform = getTransform(); @@ -322,7 +322,7 @@ public class PDFDocumentGraphics2D extends PDFGraphics2D { pageRef = page.referencePDF(); AffineTransform at = new AffineTransform(1.0, 0.0, 0.0, -1.0, - 0.0, (double)height); + 0.0, height); currentStream.write("1 0 0 -1 0 " + height + " cm\n"); if (svgWidth != 0) { double scaleX = width / svgWidth; @@ -340,7 +340,7 @@ public class PDFDocumentGraphics2D extends PDFGraphics2D { scale(1 / s, 1 / s); } // Remember the transform we installed. - graphicsState.concatenate(at); + paintingState.concatenate(at); pdfContext.increasePageCount(); } diff --git a/src/java/org/apache/fop/svg/PDFGraphics2D.java b/src/java/org/apache/fop/svg/PDFGraphics2D.java index 08655fd47..554d26798 100644 --- a/src/java/org/apache/fop/svg/PDFGraphics2D.java +++ b/src/java/org/apache/fop/svg/PDFGraphics2D.java @@ -59,19 +59,9 @@ import org.apache.batik.ext.awt.RadialGradientPaint; import org.apache.batik.ext.awt.RenderingHintsKeyExt; import org.apache.batik.gvt.GraphicsNode; import org.apache.batik.gvt.PatternPaint; - -import org.apache.xmlgraphics.image.loader.ImageInfo; -import org.apache.xmlgraphics.image.loader.ImageSize; -import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; -import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; -import org.apache.xmlgraphics.image.loader.impl.ImageRendered; -import org.apache.xmlgraphics.java2d.AbstractGraphics2D; -import org.apache.xmlgraphics.java2d.GraphicContext; - import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontSetup; -import org.apache.fop.fonts.FontTriplet; import org.apache.fop.pdf.BitmapImage; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFColor; @@ -83,16 +73,23 @@ import org.apache.fop.pdf.PDFImage; import org.apache.fop.pdf.PDFImageXObject; import org.apache.fop.pdf.PDFLink; import org.apache.fop.pdf.PDFNumber; +import org.apache.fop.pdf.PDFPaintingState; import org.apache.fop.pdf.PDFPattern; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; -import org.apache.fop.pdf.PDFState; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.pdf.ImageRawCCITTFaxAdapter; import org.apache.fop.render.pdf.ImageRawJPEGAdapter; import org.apache.fop.render.pdf.ImageRenderedAdapter; import org.apache.fop.util.ColorExt; +import org.apache.xmlgraphics.image.loader.ImageInfo; +import org.apache.xmlgraphics.image.loader.ImageSize; +import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; +import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; +import org.apache.xmlgraphics.image.loader.impl.ImageRendered; +import org.apache.xmlgraphics.java2d.AbstractGraphics2D; +import org.apache.xmlgraphics.java2d.GraphicContext; /** * PDF Graphics 2D. @@ -103,8 +100,7 @@ import org.apache.fop.util.ColorExt; * @version $Id$ * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D */ -public class PDFGraphics2D extends AbstractGraphics2D { - +public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** The number of decimal places. */ @@ -129,9 +125,9 @@ public class PDFGraphics2D extends AbstractGraphics2D { protected String pageRef; /** - * the current state of the pdf graphics + * The PDF painting state */ - protected PDFState graphicsState; + protected PDFPaintingState paintingState; /** * The PDF graphics state level that this svg is being drawn into. @@ -200,7 +196,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { currentFontSize = size; fontInfo = fi; pageRef = pref; - graphicsState = new PDFState(); + paintingState = new PDFPaintingState(); } /** @@ -226,7 +222,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { this.currentFontSize = g.currentFontSize; this.fontInfo = g.fontInfo; this.pageRef = g.pageRef; - this.graphicsState = g.graphicsState; + this.paintingState = g.paintingState; this.currentStream = g.currentStream; this.nativeCount = g.nativeCount; this.outputStream = g.outputStream; @@ -266,9 +262,9 @@ public class PDFGraphics2D extends AbstractGraphics2D { * * @param state the PDF state */ - public void setPDFState(PDFState state) { - graphicsState = state; - baseLevel = graphicsState.getStackLevel(); + public void setPaintingState(PDFPaintingState state) { + paintingState = state; + baseLevel = paintingState.getStackLevel(); } /** @@ -369,7 +365,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { * @return the transformation matrix that established the basic user space for this document */ protected AffineTransform getBaseTransform() { - AffineTransform at = new AffineTransform(graphicsState.getTransform()); + AffineTransform at = new AffineTransform(paintingState.getTransform()); return at; } @@ -417,7 +413,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { * @param width the width to draw the image * @param height the height to draw the image */ - void addNativeImage(org.apache.xmlgraphics.image.loader.Image image, float x, float y, + public void addNativeImage(org.apache.xmlgraphics.image.loader.Image image, float x, float y, float width, float height) { preparePainting(); String key = image.getInfo().getOriginalURI(); @@ -522,10 +518,14 @@ public class PDFGraphics2D extends AbstractGraphics2D { g.setBackground(new Color(1, 1, 1, 0)); g.setPaint(new Color(1, 1, 1, 0)); g.fillRect(0, 0, width, height); - g.clip(new Rectangle(0, 0, buf.getWidth(), buf.getHeight())); + + int imageWidth = buf.getWidth(); + int imageHeight = buf.getHeight(); + g.clip(new Rectangle(0, 0, imageWidth, imageHeight)); g.setComposite(gc.getComposite()); - if (!g.drawImage(img, 0, 0, buf.getWidth(), buf.getHeight(), observer)) { + boolean drawn = g.drawImage(img, 0, 0, imageWidth, imageHeight, observer); + if (!drawn) { return false; } g.dispose(); @@ -606,13 +606,13 @@ public class PDFGraphics2D extends AbstractGraphics2D { trans.getMatrix(tranvals); Shape imclip = getClip(); - boolean newClip = graphicsState.checkClip(imclip); - boolean newTransform = graphicsState.checkTransform(trans) + boolean newClip = paintingState.checkClip(imclip); + boolean newTransform = paintingState.checkTransform(trans) && !trans.isIdentity(); if (newClip || newTransform) { currentStream.write("q\n"); - graphicsState.push(); + paintingState.save(); if (newTransform) { concatMatrix(tranvals); } @@ -629,7 +629,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { applyColor(c, true); Paint paint = getPaint(); - if (graphicsState.setPaint(paint)) { + if (paintingState.setPaint(paint)) { if (!applyPaint(paint, false)) { // Stroke the shape and use it to 'clip' // the paint contents. @@ -638,7 +638,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { if (newClip || newTransform) { currentStream.write("Q\n"); - graphicsState.pop(); + paintingState.restore(); } return; } @@ -650,7 +650,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { doDrawing(false, true, false); if (newClip || newTransform) { currentStream.write("Q\n"); - graphicsState.pop(); + paintingState.restore(); } } @@ -1304,7 +1304,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { if (ovFontState == null) { java.awt.Font gFont = getFont(); fontTransform = gFont.getTransform(); - fontState = getInternalFontForAWTFont(gFont); + fontState = fontInfo.getFontInstanceForAWTFont(gFont); } else { fontState = fontInfo.getFontInstance( ovFontState.getFontTriplet(), ovFontState.getFontSize()); @@ -1360,7 +1360,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { if (!useMultiByte) { if (ch > 127) { currentStream.write("\\"); - currentStream.write(Integer.toOctalString((int)ch)); + currentStream.write(Integer.toOctalString(ch)); } else { switch (ch) { case '(': @@ -1377,8 +1377,8 @@ public class PDFGraphics2D extends AbstractGraphics2D { } if (kerningAvailable && (i + 1) < l) { - addKerning(currentStream, (new Integer((int)ch)), - (new Integer((int)fontState.mapChar(s.charAt(i + 1)))), + addKerning(currentStream, (new Integer(ch)), + (new Integer(fontState.mapChar(s.charAt(i + 1)))), kerning, startText, endText); } @@ -1406,7 +1406,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { vals.put(PDFGState.GSTATE_ALPHA_STROKE, new Float(strokeAlpha / 255f)); } PDFGState gstate = pdfDoc.getFactory().makeGState( - vals, graphicsState.getGState()); + vals, paintingState.getGState()); resourceContext.addGState(gstate); currentStream.write("/" + gstate.getName() + " gs\n"); } @@ -1418,7 +1418,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { */ protected void updateCurrentFont(Font font) { String name = font.getFontName(); - float size = (float)font.getFontSize() / 1000f; + float size = font.getFontSize() / 1000f; //Only update if necessary if ((!name.equals(this.currentFontName)) @@ -1433,19 +1433,10 @@ public class PDFGraphics2D extends AbstractGraphics2D { * Returns a suitable internal font given an AWT Font instance. * @param awtFont the AWT font * @return the internal Font + * @deprecated use FontInfo.getFontInstanceForAWTFont(java.awt.Font awtFont) instead */ protected Font getInternalFontForAWTFont(java.awt.Font awtFont) { - Font fontState; - String n = awtFont.getFamily(); - if (n.equals("sanserif")) { - n = "sans-serif"; - } - float siz = awtFont.getSize2D(); - String style = awtFont.isItalic() ? "italic" : "normal"; - int weight = awtFont.isBold() ? Font.WEIGHT_BOLD : Font.WEIGHT_NORMAL; - FontTriplet triplet = fontInfo.fontLookup(n, style, weight); - fontState = fontInfo.getFontInstance(triplet, (int)(siz * 1000 + 0.5)); - return fontState; + return fontInfo.getFontInstanceForAWTFont(awtFont); } /** @@ -1597,13 +1588,13 @@ public class PDFGraphics2D extends AbstractGraphics2D { trans.getMatrix(tranvals); Shape imclip = getClip(); - boolean newClip = graphicsState.checkClip(imclip); - boolean newTransform = graphicsState.checkTransform(trans) + boolean newClip = paintingState.checkClip(imclip); + boolean newTransform = paintingState.checkTransform(trans) && !trans.isIdentity(); if (newClip || newTransform) { currentStream.write("q\n"); - graphicsState.push(); + paintingState.save(); if (newTransform) { concatMatrix(tranvals); } @@ -1620,14 +1611,14 @@ public class PDFGraphics2D extends AbstractGraphics2D { applyColor(c, false); Paint paint = getPaint(); - if (graphicsState.setPaint(paint)) { + if (paintingState.setPaint(paint)) { if (!applyPaint(paint, true)) { // Use the shape to 'clip' the paint contents. applyUnknownPaint(paint, s); if (newClip || newTransform) { currentStream.write("Q\n"); - graphicsState.pop(); + paintingState.restore(); } return; } @@ -1640,7 +1631,7 @@ public class PDFGraphics2D extends AbstractGraphics2D { iter.getWindingRule() == PathIterator.WIND_EVEN_ODD); if (newClip || newTransform) { currentStream.write("Q\n"); - graphicsState.pop(); + paintingState.restore(); } } diff --git a/src/java/org/apache/fop/svg/PDFGraphicsConfiguration.java b/src/java/org/apache/fop/svg/PDFGraphicsConfiguration.java index 83a431d5e..0204a2756 100644 --- a/src/java/org/apache/fop/svg/PDFGraphicsConfiguration.java +++ b/src/java/org/apache/fop/svg/PDFGraphicsConfiguration.java @@ -26,6 +26,7 @@ import java.awt.image.ColorModel; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; + /** * Our implementation of the class that returns information about * roughly what we can handle and want to see (alpha for example). diff --git a/src/java/org/apache/fop/svg/PDFImageElementBridge.java b/src/java/org/apache/fop/svg/PDFImageElementBridge.java index 7eb89d2b1..1a17aa410 100644 --- a/src/java/org/apache/fop/svg/PDFImageElementBridge.java +++ b/src/java/org/apache/fop/svg/PDFImageElementBridge.java @@ -19,36 +19,14 @@ package org.apache.fop.svg; -import java.awt.Graphics2D; -import java.awt.Shape; -import java.awt.geom.Rectangle2D; - -import org.w3c.dom.Element; -import org.w3c.dom.svg.SVGDocument; - -import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.bridge.SVGImageElementBridge; -import org.apache.batik.gvt.AbstractGraphicsNode; -import org.apache.batik.gvt.GraphicsNode; -import org.apache.batik.util.ParsedURL; - -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.impl.ImageGraphics2D; -import org.apache.xmlgraphics.image.loader.impl.ImageRawCCITTFax; -import org.apache.xmlgraphics.image.loader.impl.ImageRawJPEG; -import org.apache.xmlgraphics.image.loader.impl.ImageXMLDOM; /** - * Bridge class for the <image> element when jpeg images. + * PDF Image Element Bridge class for the <image> element when jpeg images. * * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> */ -public class PDFImageElementBridge extends SVGImageElementBridge { +public class PDFImageElementBridge extends AbstractFOPImageElementBridge { /** * Constructs a new bridge for the <image> element. @@ -60,210 +38,9 @@ public class PDFImageElementBridge extends SVGImageElementBridge { ImageFlavor.RAW_CCITTFAX, ImageFlavor.GRAPHICS2D, ImageFlavor.XML_DOM}; - /** - * Create the raster image node. - * THis checks if it is a jpeg file and creates a jpeg node - * so the jpeg can be inserted directly into the pdf document. - * @param ctx the bridge context - * @param imageElement the svg element for the image - * @param purl the parsed url for the image resource - * @return a new graphics node - */ - protected GraphicsNode createImageGraphicsNode - (BridgeContext ctx, Element imageElement, ParsedURL purl) { - PDFBridgeContext pdfCtx = (PDFBridgeContext)ctx; - - ImageManager manager = pdfCtx.getImageManager(); - ImageSessionContext sessionContext = pdfCtx.getImageSessionContext(); - try { - ImageInfo info = manager.getImageInfo(purl.toString(), sessionContext); - Image image = manager.getImage(info, supportedFlavors, sessionContext); - - //TODO color profile overrides aren't handled, yet! - //ICCColorSpaceExt colorspaceOverride = extractColorSpace(e, ctx); - AbstractGraphicsNode specializedNode = null; - if (image instanceof ImageXMLDOM) { - ImageXMLDOM xmlImage = (ImageXMLDOM)image; - if (xmlImage.getDocument() instanceof SVGDocument) { - return createSVGImageNode(ctx, imageElement, - (SVGDocument)xmlImage.getDocument()); - } else { - //Convert image to Graphics2D - image = manager.convertImage(xmlImage, - new ImageFlavor[] {ImageFlavor.GRAPHICS2D}); - } - } - if (image instanceof ImageRawJPEG) { - specializedNode = new LoaderImageNode(image, ctx, imageElement, purl); - } else if (image instanceof ImageRawCCITTFax) { - specializedNode = new LoaderImageNode(image, ctx, imageElement, purl); - } else if (image instanceof ImageGraphics2D) { - ImageGraphics2D g2dImage = (ImageGraphics2D)image; - specializedNode = new Graphics2DNode(g2dImage); - } else { - ctx.getUserAgent().displayError( - new ImageException("Cannot convert an image to a usable format: " + purl)); - } - - Rectangle2D imgBounds = getImageBounds(ctx, imageElement); - Rectangle2D bounds = specializedNode.getPrimitiveBounds(); - float [] vb = new float[4]; - vb[0] = 0; // x - vb[1] = 0; // y - vb[2] = (float) bounds.getWidth(); // width - vb[3] = (float) bounds.getHeight(); // height - - // handles the 'preserveAspectRatio', 'overflow' and 'clip' - // and sets the appropriate AffineTransform to the image node - initializeViewport(ctx, imageElement, specializedNode, vb, imgBounds); - return specializedNode; - } catch (Exception e) { - ctx.getUserAgent().displayError(e); - } - - return superCreateGraphicsNode(ctx, imageElement, purl); - } - - /** - * Calls the superclass' createImageGraphicNode() method to create the normal GraphicsNode. - * @param ctx the bridge context - * @param imageElement the image element - * @param purl the parsed URL - * @return the newly created graphics node - * @see org.apache.batik.bridge.SVGImageElementBridge#createGraphicsNode(BridgeContext, Element) - */ - protected GraphicsNode superCreateGraphicsNode - (BridgeContext ctx, Element imageElement, ParsedURL purl) { - return super.createImageGraphicsNode(ctx, imageElement, purl); - } - - - /** - * An image node for natively handled Image instance. - * This holds a natively handled image so that it can be drawn into - * the PDFGraphics2D. - */ - public class LoaderImageNode extends AbstractGraphicsNode { - - private Image image; - private BridgeContext ctx; - private Element imageElement; - private ParsedURL purl; - private GraphicsNode origGraphicsNode = null; - - /** - * Create a new image node for drawing natively handled images - * into PDF graphics. - * @param image the JPEG image - * @param ctx the bridge context - * @param imageElement the SVG image element - * @param purl the URL to the image - */ - public LoaderImageNode(Image image, BridgeContext ctx, - Element imageElement, ParsedURL purl) { - this.image = image; - this.ctx = ctx; - this.imageElement = imageElement; - this.purl = purl; - } - - /** {@inheritDoc} */ - public Shape getOutline() { - return getPrimitiveBounds(); - } - - /** {@inheritDoc} */ - public void primitivePaint(Graphics2D g2d) { - if (g2d instanceof PDFGraphics2D) { - PDFGraphics2D pdfg = (PDFGraphics2D) g2d; - float x = 0; - float y = 0; - try { - float width = image.getSize().getWidthPx(); - float height = image.getSize().getHeightPx(); - pdfg.addNativeImage(image, x, y, width, height); - } catch (Exception e) { - ctx.getUserAgent().displayError(e); - } - } else { - // Not going directly into PDF so use - // original implementation so filters etc work. - if (origGraphicsNode == null) { - // Haven't constructed baseclass Graphics Node, - // so do so now. - origGraphicsNode - = PDFImageElementBridge.this.superCreateGraphicsNode - (ctx, imageElement, purl); - } - origGraphicsNode.primitivePaint(g2d); - } - } - - /** {@inheritDoc} */ - public Rectangle2D getGeometryBounds() { - return getPrimitiveBounds(); - } - - /** {@inheritDoc} */ - public Rectangle2D getPrimitiveBounds() { - return new Rectangle2D.Double(0, 0, - image.getSize().getWidthPx(), - image.getSize().getHeightPx()); - } - - /** {@inheritDoc} */ - public Rectangle2D getSensitiveBounds() { - //No interactive features, just return primitive bounds - return getPrimitiveBounds(); - } - - } - - /** - * A node that holds a Graphics2D image. - */ - public class Graphics2DNode extends AbstractGraphicsNode { - - private ImageGraphics2D image; - - /** - * Create a new Graphics2D node. - * @param g2d the Graphics2D image - */ - public Graphics2DNode(ImageGraphics2D g2d) { - this.image = g2d; - } - - /** {@inheritDoc} */ - public Shape getOutline() { - return getPrimitiveBounds(); - } - - /** {@inheritDoc} */ - public void primitivePaint(Graphics2D g2d) { - int width = image.getSize().getWidthPx(); - int height = image.getSize().getHeightPx(); - Rectangle2D area = new Rectangle2D.Double(0, 0, width, height); - image.getGraphics2DImagePainter().paint(g2d, area); - } - - /** {@inheritDoc} */ - public Rectangle2D getGeometryBounds() { - return getPrimitiveBounds(); - } - - /** {@inheritDoc} */ - public Rectangle2D getPrimitiveBounds() { - return new Rectangle2D.Double(0, 0, - image.getSize().getWidthPx(), - image.getSize().getHeightPx()); - } - - /** {@inheritDoc} */ - public Rectangle2D getSensitiveBounds() { - //No interactive features, just return primitive bounds - return getPrimitiveBounds(); - } + /** {@inheritDoc} */ + protected ImageFlavor[] getSupportedFlavours() { + return supportedFlavors; } } diff --git a/src/java/org/apache/fop/svg/PDFTextElementBridge.java b/src/java/org/apache/fop/svg/PDFTextElementBridge.java index 4c11aa97e..c983d2b45 100644 --- a/src/java/org/apache/fop/svg/PDFTextElementBridge.java +++ b/src/java/org/apache/fop/svg/PDFTextElementBridge.java @@ -19,13 +19,7 @@ package org.apache.fop.svg; -import org.apache.batik.bridge.BridgeContext; -import org.apache.batik.bridge.SVGTextElementBridge; -import org.apache.batik.gvt.GraphicsNode; -import org.apache.batik.gvt.TextNode; import org.apache.batik.gvt.TextPainter; -import org.apache.fop.fonts.FontInfo; -import org.w3c.dom.Element; /** * Bridge class for the <text> element. @@ -34,41 +28,15 @@ import org.w3c.dom.Element; * * @author <a href="mailto:keiron@aftexsw.com">Keiron Liddle</a> */ -public class PDFTextElementBridge extends SVGTextElementBridge { - - private PDFTextPainter pdfTextPainter; +public class PDFTextElementBridge extends AbstractFOPTextElementBridge { /** * Constructs a new bridge for the <text> element. - * @param fi the font information - */ - public PDFTextElementBridge(FontInfo fi) { - pdfTextPainter = new PDFTextPainter(fi); - } - - /** - * Create a text element bridge. - * This set the text painter on the node if the text is simple. - * @param ctx the bridge context - * @param e the svg element - * @return the text graphics node created by the super class + * + * @param textPainter the text painter to use */ - public GraphicsNode createGraphicsNode(BridgeContext ctx, Element e) { - GraphicsNode node = super.createGraphicsNode(ctx, e); - if (node != null) { - //Set our own text painter - ((TextNode)node).setTextPainter(getTextPainter()); - } - return node; + public PDFTextElementBridge(TextPainter textPainter) { + super(textPainter); } - - /** - * Returns the TextPainter instance used by this bridge. - * @return the text painter - */ - public TextPainter getTextPainter() { - return pdfTextPainter; - } - } diff --git a/src/java/org/apache/fop/svg/PDFTextPainter.java b/src/java/org/apache/fop/svg/PDFTextPainter.java index 06fea54cc..85447a4f9 100644 --- a/src/java/org/apache/fop/svg/PDFTextPainter.java +++ b/src/java/org/apache/fop/svg/PDFTextPainter.java @@ -63,8 +63,8 @@ public class PDFTextPainter extends StrokingTextPainter { private static final boolean DEBUG = false; - private boolean strokeText = false; - private FontInfo fontInfo; + private final boolean strokeText = false; + private final FontInfo fontInfo; /** * Create a new PDF text painter with the given font information. @@ -280,12 +280,12 @@ public class PDFTextPainter extends StrokingTextPainter { Float fontSize = (Float) aci.getAttribute(TextAttribute.SIZE); String style = ((posture != null) && (posture.floatValue() > 0.0)) - ? "italic" : "normal"; + ? Font.STYLE_ITALIC : Font.STYLE_NORMAL; int weight = ((taWeight != null) && (taWeight.floatValue() > 1.0)) ? Font.WEIGHT_BOLD : Font.WEIGHT_NORMAL; - String fontFamily = null; + String firstFontFamily = null; //GVT_FONT can sometimes be different from the fonts in GVT_FONT_FAMILIES //or GVT_FONT_FAMILIES can even be empty and only GVT_FONT is set @@ -307,6 +307,7 @@ public class PDFTextPainter extends StrokingTextPainter { int fsize = (int)(fontSize.floatValue() * 1000); fonts.add(fontInfo.getFontInstance(triplet, fsize)); } + firstFontFamily = gvtFontFamily; } catch (Exception e) { //Most likely NoSuchMethodError here when using Batik 1.6 //Just skip this section in this case @@ -320,7 +321,7 @@ public class PDFTextPainter extends StrokingTextPainter { if (fam instanceof SVGFontFamily) { return null; //Let Batik paint this text! } - fontFamily = fam.getFamilyName(); + String fontFamily = fam.getFamilyName(); if (DEBUG) { System.out.print(fontFamily + ", "); } @@ -330,15 +331,20 @@ public class PDFTextPainter extends StrokingTextPainter { int fsize = (int)(fontSize.floatValue() * 1000); fonts.add(fontInfo.getFontInstance(triplet, fsize)); } + if (firstFontFamily == null) { + firstFontFamily = fontFamily; + } } } if (fonts.size() == 0) { - FontTriplet triplet = fontInfo.fontLookup("any", style, Font.WEIGHT_NORMAL); + if (firstFontFamily == null) { + //This will probably never happen. Just to be on the safe side. + firstFontFamily = "any"; + } + //lookup with fallback possibility (incl. substitution notification) + FontTriplet triplet = fontInfo.fontLookup(firstFontFamily, style, weight); int fsize = (int)(fontSize.floatValue() * 1000); fonts.add(fontInfo.getFontInstance(triplet, fsize)); - if (DEBUG) { - System.out.print("fallback to 'any' font"); - } } if (DEBUG) { System.out.println(); |