/* * 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.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.Paint; import java.awt.PaintContext; import java.awt.Rectangle; import java.awt.Shape; import java.awt.Stroke; import java.awt.color.ColorSpace; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.DirectColorModel; import java.awt.image.ImageObserver; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.io.IOException; import java.io.OutputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.batik.ext.awt.LinearGradientPaint; import org.apache.batik.ext.awt.MultipleGradientPaint; 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.GraphicsConstants; 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.xmlgraphics.java2d.GraphicsConfigurationWithTransparency; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontSetup; import org.apache.fop.pdf.BitmapImage; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.PDFColor; import org.apache.fop.pdf.PDFColorHandler; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFDeviceColorSpace; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFunction; import org.apache.fop.pdf.PDFGState; 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.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFShading; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.gradient.Function; import org.apache.fop.render.gradient.GradientMaker; import org.apache.fop.render.gradient.Pattern; import org.apache.fop.render.gradient.Shading; import org.apache.fop.render.pdf.ImageRawCCITTFaxAdapter; import org.apache.fop.render.pdf.ImageRawJPEGAdapter; import org.apache.fop.render.pdf.ImageRenderedAdapter; /** * <p>PDF Graphics 2D. * Used for drawing into a pdf document as if it is a graphics object. * This takes a pdf document and draws into it.</p> * * <p>This work was authored by Keiron Liddle (keiron@aftexsw.com).</p> * * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D */ public class PDFGraphics2D extends AbstractGraphics2D implements NativeImageHandler { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** The number of decimal places. */ private static final int DEC = 8; /** Convenience constant for full opacity */ static final int OPAQUE = 255; /** * the PDF Document being created */ protected PDFDocument pdfDoc; /** * The current resource context for adding fonts, patterns etc. */ protected PDFResourceContext resourceContext; /** * The PDF reference of the current page. */ protected PDFReference pageRef; /** * The PDF painting state */ protected PDFPaintingState paintingState; /** the PDF color handler */ protected PDFColorHandler colorHandler; /** * The PDF graphics state level that this svg is being drawn into. */ protected int baseLevel; /** * The count of natively handled images added to document so they receive * unique keys. */ protected int nativeCount; /** * The current font information. */ protected FontInfo fontInfo; /** * The override font state used when drawing text and the font cannot be * set using java fonts. */ protected Font ovFontState; /** * the current stream to add PDF commands to */ protected StringWriter currentStream = new StringWriter(); /** * the current (internal) font name */ protected String currentFontName; /** * the current font size in millipoints */ protected float currentFontSize; /** * The output stream for the pdf document. * If this is set then it can progressively output * the pdf document objects to reduce memory. * Especially with images. */ protected OutputStream outputStream; private TransparencyIgnoredEventListener transparencyIgnoredEventListener; /** * May be used to give proper feedback to the user when a particular PDF profile is * being used that disallows transparency. */ public interface TransparencyIgnoredEventListener { void transparencyIgnored(Object pdfProfile); } /** * Create a new PDFGraphics2D with the given pdf document info. * This is used to create a Graphics object for use inside an already * existing document. * * @param textAsShapes if true then draw text as shapes * @param fi the current font information * @param doc the pdf document for creating pdf objects * @param page the current resource context or page * @param pref the PDF reference of the current page * @param font the current font name * @param size the current font size */ public PDFGraphics2D(boolean textAsShapes, FontInfo fi, PDFDocument doc, PDFResourceContext page, PDFReference pref, String font, float size, TransparencyIgnoredEventListener listener) { this(textAsShapes); pdfDoc = doc; this.colorHandler = new PDFColorHandler(doc.getResources()); resourceContext = page; currentFontName = font; currentFontSize = size; fontInfo = fi; pageRef = pref; paintingState = new PDFPaintingState(); this.transparencyIgnoredEventListener = listener; } /** * Create a new PDFGraphics2D. * * @param textAsShapes true if drawing text as shapes */ protected PDFGraphics2D(boolean textAsShapes) { super(textAsShapes); } /** * This constructor supports the create method. * This is not implemented properly. * * @param g the PDF graphics to make a copy of */ public PDFGraphics2D(PDFGraphics2D g) { super(g); this.pdfDoc = g.pdfDoc; this.colorHandler = g.colorHandler; this.resourceContext = g.resourceContext; this.currentFontName = g.currentFontName; this.currentFontSize = g.currentFontSize; this.fontInfo = g.fontInfo; this.pageRef = g.pageRef; this.paintingState = g.paintingState; this.currentStream = g.currentStream; this.nativeCount = g.nativeCount; this.outputStream = g.outputStream; this.ovFontState = g.ovFontState; this.transparencyIgnoredEventListener = g.transparencyIgnoredEventListener; } /** * Creates a new <code>Graphics</code> object that is * a copy of this <code>Graphics</code> object. * @return a new graphics context that is a copy of * this graphics context. */ @Override public Graphics create() { return new PDFGraphics2D(this); } /** * Central handler for IOExceptions for this class. * @param ioe IOException to handle */ protected void handleIOException(IOException ioe) { //TODO Surely, there's a better way to do this. ioe.printStackTrace(); } /** * This method is used by PDFDocumentGraphics2D to prepare a new page if * necessary. */ protected void preparePainting() { //nop, used by PDFDocumentGraphics2D } /** * Set the PDF state to use when starting to draw * into the PDF graphics. * * @param state the PDF state */ public void setPaintingState(PDFPaintingState state) { paintingState = state; baseLevel = paintingState.getStackLevel(); // @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") } /** * Set the output stream that this PDF document is * being drawn to. This is so that it can progressively * use the PDF document to output data such as images. * This results in a significant saving on memory. * * @param os the output stream that is being used for the PDF document */ public void setOutputStream(OutputStream os) { outputStream = os; } /** * Get the string containing all the commands written into this * Graphics. * @return the string containing the PDF markup */ public String getString() { return currentStream.toString(); } /** * Get the string buffer from the currentStream, containing all * the commands written into this Graphics so far. * @return the StringBuffer containing the PDF markup */ public StringBuffer getBuffer() { return currentStream.getBuffer(); } /** * Gets the PDF reference of the current page. * @return the PDF reference of the current page */ public PDFReference getPageReference() { return this.pageRef; } /** * Set the Graphics context. * @param c the graphics context to use */ public void setGraphicContext(GraphicContext c) { gc = c; setPrivateHints(); } private void setPrivateHints() { setRenderingHint(RenderingHintsKeyExt.KEY_AVOID_TILE_PAINTING, RenderingHintsKeyExt.VALUE_AVOID_TILE_PAINTING_ON); } /** * Set the override font state for drawing text. * This is used by the PDF text painter so that it can temporarily * set the font state when a java font cannot be used. * The next drawString will use this font state. * * @param infont the font state to use */ public void setOverrideFontState(Font infont) { ovFontState = infont; } /** * Restore the PDF graphics state to the starting state level. */ /* seems not to be used public void restorePDFState() { for (int count = graphicsState.getStackLevel(); count > baseLevel; count--) { currentStream.write("Q\n"); } graphicsState.restoreLevel(baseLevel); }*/ private void concatMatrix(double[] matrix) { currentStream.write(PDFNumber.doubleOut(matrix[0], DEC) + " " + PDFNumber.doubleOut(matrix[1], DEC) + " " + PDFNumber.doubleOut(matrix[2], DEC) + " " + PDFNumber.doubleOut(matrix[3], DEC) + " " + PDFNumber.doubleOut(matrix[4], DEC) + " " + PDFNumber.doubleOut(matrix[5], DEC) + " cm\n"); } private void concatMatrix(AffineTransform transform) { if (!transform.isIdentity()) { double[] matrix = new double[6]; transform.getMatrix(matrix); concatMatrix(matrix); } } /** * This is mainly used for shading patterns which use the document-global coordinate system * instead of the local one. * @return the transformation matrix that established the basic user space for this document */ protected AffineTransform getBaseTransform() { AffineTransform at = new AffineTransform(paintingState.getTransform()); return at; } /** * This is a pdf specific method used to add a link to the * pdf document. * * @param bounds the bounds of the link in user coordinates * @param trans the transform of the current drawing position * @param dest the PDF destination * @param linkType the type of link, internal or external */ public void addLink(Rectangle2D bounds, AffineTransform trans, String dest, int linkType) { if (!pdfDoc.getProfile().isAnnotationAllowed()) { return; } preparePainting(); AffineTransform at = getTransform(); Shape b = at.createTransformedShape(bounds); b = trans.createTransformedShape(b); if (b != null) { Rectangle rect = b.getBounds(); if (linkType != PDFLink.EXTERNAL) { String pdfdest = "/FitR " + dest; resourceContext.addAnnotation( pdfDoc.getFactory().makeLink(rect, getPageReference().toString(), pdfdest)); } else { resourceContext.addAnnotation( pdfDoc.getFactory().makeLink(rect, dest, linkType, 0)); } } } /** * Add a natively handled image directly to the PDF document. * This is used by the PDFImageElementBridge to draw a natively handled image * (like JPEG or CCITT images) * directly into the PDF 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 */ public void addNativeImage(org.apache.xmlgraphics.image.loader.Image image, float x, float y, float width, float height) { preparePainting(); String key = image.getInfo().getOriginalURI(); if (key == null) { // Need to include hash code as when invoked from FO you // may have several 'independent' PDFGraphics2D so the // count is not enough. key = "__AddNative_" + hashCode() + "_" + nativeCount; nativeCount++; } PDFImage pdfImage; if (image instanceof ImageRawJPEG) { pdfImage = new ImageRawJPEGAdapter((ImageRawJPEG)image, key); } else if (image instanceof ImageRawCCITTFax) { pdfImage = new ImageRawCCITTFaxAdapter((ImageRawCCITTFax)image, key); } else { throw new IllegalArgumentException( "Unsupported Image subclass: " + image.getClass().getName()); } PDFXObject xObject = this.pdfDoc.addImage(resourceContext, pdfImage); flushPDFDocument(); AffineTransform at = new AffineTransform(); at.translate(x, y); useXObject(xObject, at, width, height); } private void flushPDFDocument() { if (outputStream != null && !pdfDoc.isLinearizationEnabled()) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { // ignore exception, will be thrown again later } } } /** * Draws as much of the specified image as is currently available. * The image is drawn with its top-left corner at * (<i>x</i>, <i>y</i>) in this graphics context's coordinate * space. Transparent pixels in the image do not affect whatever * pixels are already there. * <p> * This method returns immediately in all cases, even if the * complete image has not yet been loaded, and it has not been dithered * and converted for the current output device. * <p> * If the image has not yet been completely loaded, then * <code>drawImage</code> returns <code>false</code>. As more of * the image becomes available, the process that draws the image notifies * the specified image observer. * @param img the specified image to be drawn. * @param x the <i>x</i> coordinate. * @param y the <i>y</i> coordinate. * @param observer object to be notified as more of * the image is converted. * @return true if the image was drawn * @see java.awt.Image * @see java.awt.image.ImageObserver * @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int, int, int, int) */ @Override public boolean drawImage(Image img, int x, int y, ImageObserver observer) { preparePainting(); int width = img.getWidth(observer); int height = img.getHeight(observer); if (width == -1 || height == -1) { return false; } return drawImage(img, x, y, width, height, observer); } private BufferedImage buildBufferedImage(Dimension size) { return new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); } /** {@inheritDoc} */ @Override public boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer) { preparePainting(); // first we look to see if we've already added this image to // the pdf document. If so, we just reuse the reference; // otherwise we have to build a FopImage and add it to the pdf // document String key = "TempImage:" + img.toString(); PDFXObject xObject = pdfDoc.getXObject(key); if (xObject == null) { // OK, have to build and add a PDF image Dimension size = new Dimension(width, height); BufferedImage buf = buildBufferedImage(size); java.awt.Graphics2D g = buf.createGraphics(); g.setComposite(AlphaComposite.SrcOver); g.setBackground(new Color(1, 1, 1, 0)); g.setPaint(new Color(1, 1, 1, 0)); g.fillRect(0, 0, width, height); int imageWidth = buf.getWidth(); int imageHeight = buf.getHeight(); g.clip(new Rectangle(0, 0, imageWidth, imageHeight)); g.setComposite(gc.getComposite()); boolean drawn = g.drawImage(img, 0, 0, imageWidth, imageHeight, observer); if (!drawn) { return false; } g.dispose(); xObject = addRenderedImage(key, buf); } else { resourceContext.addXObject(xObject); } AffineTransform at = new AffineTransform(); at.translate(x, y); useXObject(xObject, at, width, height); return true; } /** * Disposes of this graphics context and releases * any system resources that it is using. * A <code>Graphics</code> object cannot be used after * <code>dispose</code>has been called. * <p> * When a Java program runs, a large number of <code>Graphics</code> * objects can be created within a short time frame. * Although the finalization process of the garbage collector * also disposes of the same system resources, it is preferable * to manually free the associated resources by calling this * method rather than to rely on a finalization process which * may not run to completion for a long period of time. * <p> * Graphics objects which are provided as arguments to the * <code>paint</code> and <code>update</code> methods * of components are automatically released by the system when * those methods return. For efficiency, programmers should * call <code>dispose</code> when finished using * a <code>Graphics</code> object only if it was created * directly from a component or another <code>Graphics</code> object. * @see java.awt.Graphics#finalize * @see java.awt.Component#paint * @see java.awt.Component#update * @see java.awt.Component#getGraphics * @see java.awt.Graphics#create */ @Override public void dispose() { pdfDoc = null; fontInfo = null; currentStream = null; currentFontName = null; } /** * Strokes the outline of a <code>Shape</code> using the settings of the * current <code>Graphics2D</code> context. The rendering attributes * applied include the <code>Clip</code>, <code>Transform</code>, * <code>Paint</code>, <code>Composite</code> and * <code>Stroke</code> attributes. * @param s the <code>Shape</code> to be rendered * @see #setStroke * @see #setPaint * @see java.awt.Graphics#setColor * @see #transform * @see #setTransform * @see #clip * @see #setClip * @see #setComposite */ @Override public void draw(Shape s) { preparePainting(); //Transparency shortcut Color c; c = getColor(); if (c.getAlpha() == 0) { return; } AffineTransform trans = getTransform(); double[] tranvals = new double[6]; trans.getMatrix(tranvals); Shape imclip = getClip(); boolean newClip = paintingState.checkClip(imclip); boolean newTransform = paintingState.checkTransform(trans) && !trans.isIdentity(); if (newClip || newTransform) { saveGraphicsState(); if (newTransform) { concatMatrix(tranvals); } if (newClip) { writeClip(imclip); } } applyAlpha(OPAQUE, c.getAlpha()); c = getColor(); applyColor(c, false); c = getBackground(); applyColor(c, true); Paint paint = getPaint(); if (paintingState.setPaint(paint)) { if (!applyPaint(paint, false)) { // Stroke the shape and use it to 'clip' // the paint contents. Shape ss = getStroke().createStrokedShape(s); applyUnknownPaint(paint, ss); if (newClip || newTransform) { restoreGraphicsState(); } return; } } applyStroke(getStroke()); PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); processPathIterator(iter); doDrawing(false, true, false); if (newClip || newTransform) { restoreGraphicsState(); } } /* // in theory we could set the clip using these methods // it doesn't seem to improve the file sizes much // and makes everything more complicated Shape lastClip = null; public void clip(Shape cl) { super.clip(cl); Shape newClip = getClip(); if (newClip == null || lastClip == null || !(new Area(newClip).equals(new Area(lastClip)))) { graphicsState.setClip(newClip); writeClip(newClip); } lastClip = newClip; } public void setClip(Shape cl) { super.setClip(cl); Shape newClip = getClip(); if (newClip == null || lastClip == null || !(new Area(newClip).equals(new Area(lastClip)))) { for (int count = graphicsState.getStackLevel(); count > baseLevel; count--) { currentStream.write("Q\n"); } graphicsState.restoreLevel(baseLevel); currentStream.write("q\n"); graphicsState.push(); if (newClip != null) { graphicsState.setClip(newClip); } writeClip(newClip); } lastClip = newClip; } */ /** * Set the clipping shape for future PDF drawing in the current graphics state. * This sets creates and writes a clipping shape that will apply * to future drawings in the current graphics state. * * @param s the clipping shape */ protected void writeClip(Shape s) { if (s == null) { return; } PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); if (iter.isDone()) { // no segments available. Not worth doing anything return; } preparePainting(); processPathIterator(iter); // clip area currentStream.write("W\n"); currentStream.write("n\n"); } /** * Apply the java Color to PDF. * This converts the java colour to a PDF colour and * sets it for the next drawing. * * @param col the java colour * @param fill true if the colour will be used for filling */ protected void applyColor(Color col, boolean fill) { preparePainting(); //TODO Handle this in PDFColorHandler by automatically converting the color. //This won't work properly anyway after the redesign of ColorExt if (col.getColorSpace().getType() == ColorSpace.TYPE_CMYK) { if (pdfDoc.getProfile().getPDFAMode().isPart1()) { //See PDF/A-1, ISO 19005:1:2005(E), 6.2.3.3 //FOP is currently restricted to DeviceRGB if PDF/A-1 is active. throw new PDFConformanceException( "PDF/A-1 does not allow mixing DeviceRGB and DeviceCMYK."); } } boolean doWrite = false; if (fill) { if (paintingState.setBackColor(col)) { doWrite = true; } } else { if (paintingState.setColor(col)) { doWrite = true; } } if (doWrite) { StringBuffer sb = new StringBuffer(); colorHandler.establishColor(sb, col, fill); currentStream.write(sb.toString()); } } /** * Apply the java paint to the PDF. * This takes the java paint sets up the appropraite PDF commands * for the drawing with that paint. * Currently this supports the gradients and patterns from batik. * * @param paint the paint to convert to PDF * @param fill true if the paint should be set for filling * @return true if the paint is handled natively, false if the paint should be rasterized */ protected boolean applyPaint(Paint paint, boolean fill) { preparePainting(); if (paint instanceof Color) { return true; } // convert java.awt.GradientPaint to LinearGradientPaint to avoid rasterization if (paint instanceof GradientPaint) { GradientPaint gpaint = (GradientPaint) paint; paint = new LinearGradientPaint( (float) gpaint.getPoint1().getX(), (float) gpaint.getPoint1().getY(), (float) gpaint.getPoint2().getX(), (float) gpaint.getPoint2().getY(), new float[] {0, 1}, new Color[] {gpaint.getColor1(), gpaint.getColor2()}, gpaint.isCyclic() ? LinearGradientPaint.REPEAT : LinearGradientPaint.NO_CYCLE); } if (paint instanceof LinearGradientPaint && gradientSupported((LinearGradientPaint) paint)) { Pattern pattern = GradientMaker.makeLinearGradient((LinearGradientPaint) paint, getBaseTransform(), getTransform()); PDFPattern pdfPattern = createPDFPattern(pattern); currentStream.write(pdfPattern.getColorSpaceOut(fill)); return true; } if (paint instanceof RadialGradientPaint && gradientSupported((RadialGradientPaint) paint)) { Pattern pattern = GradientMaker.makeRadialGradient((RadialGradientPaint) paint, getBaseTransform(), getTransform()); PDFPattern pdfPattern = createPDFPattern(pattern); currentStream.write(pdfPattern.getColorSpaceOut(fill)); return true; } if (paint instanceof PatternPaint) { PatternPaint pp = (PatternPaint)paint; return createPattern(pp, fill); } return false; // unknown paint } private PDFPattern createPDFPattern(Pattern pattern) { Shading shading = pattern.getShading(); Function function = shading.getFunction(); List<PDFFunction> pdfFunctions = new ArrayList<PDFFunction>(function.getFunctions().size()); for (Function f : function.getFunctions()) { pdfFunctions.add(registerFunction(new PDFFunction(f))); } PDFFunction pdfFunction = registerFunction(new PDFFunction(function, pdfFunctions)); PDFShading pdfShading = new PDFShading(shading.getShadingType(), shading.getColorSpace(), shading.getCoords(), pdfFunction); pdfShading = registerShading(pdfShading); PDFPattern pdfPattern = new PDFPattern(pattern.getPatternType(), pdfShading, null, null, pattern.getMatrix()); return registerPattern(pdfPattern); } private boolean gradientSupported(MultipleGradientPaint gradient) { return !(gradientContainsTransparency(gradient) || gradientIsRepeated(gradient)); } private boolean gradientContainsTransparency(MultipleGradientPaint gradient) { for (Color color : gradient.getColors()) { if (color.getAlpha() != 255) { return true; } } return false; } private boolean gradientIsRepeated(MultipleGradientPaint gradient) { // For linear gradients it is possible to construct a 'tile' that is repeated with // a PDF pattern, but it would be very tricky as the coordinate system would have // to be rotated so the repeat is axially aligned. // For radial gradients there is essentially no way to support repeats in PDF (the // one option would be to 'grow' the outer circle until it fully covers the // bounds and then grow the stops accordingly, the problem is that this may // require an extremely large number of stops for cases where the focus is near // the edge of the outer circle). return (gradient.getCycleMethod() != MultipleGradientPaint.NO_CYCLE); } private boolean createPattern(PatternPaint pp, boolean fill) { preparePainting(); FontInfo specialFontInfo = new FontInfo(); boolean base14Kerning = false; FontSetup.setup(specialFontInfo, base14Kerning); PDFResources res = pdfDoc.getFactory().makeResources(); PDFResourceContext context = new PDFResourceContext(res); PDFGraphics2D pattGraphic = new PDFGraphics2D(textAsShapes, specialFontInfo, pdfDoc, context, getPageReference(), "", 0, transparencyIgnoredEventListener); pattGraphic.setGraphicContext(new GraphicContext()); pattGraphic.gc.validateTransformStack(); pattGraphic.setRenderingHints(this.getRenderingHints()); pattGraphic.setOutputStream(outputStream); GraphicsNode gn = pp.getGraphicsNode(); //Rectangle2D gnBBox = gn.getBounds(); Rectangle2D rect = pp.getPatternRect(); // if (!pp.getOverflow()) { gn.paint(pattGraphic); // } else { // /* Commented out until SVN version of Batik is included */ // // For overflow we need to paint the content from // // all the tiles who's overflow will intersect one // // tile (left->right, top->bottom). Then we can // // simply replicate that tile as normal. // double gnMinX = gnBBox.getX(); // double gnMaxX = gnBBox.getX() + gnBBox.getWidth(); // double gnMinY = gnBBox.getY(); // double gnMaxY = gnBBox.getY() + gnBBox.getHeight(); // double patMaxX = rect.getX() + rect.getWidth(); // double patMaxY = rect.getY() + rect.getHeight(); // double stepX = rect.getWidth(); // double stepY = rect.getHeight(); // // int startX = (int)((rect.getX() - gnMaxX)/stepX); // int startY = (int)((rect.getY() - gnMaxY)/stepY); // // int endX = (int)((patMaxX - gnMinX)/stepX); // int endY = (int)((patMaxY - gnMinY)/stepY); // // pattGraphic.translate(startX*stepX, startY*stepY); // for (int yIdx=startY; yIdx<=endY; yIdx++) { // for (int xIdx=startX; xIdx<=endX; xIdx++) { // gn.paint(pattGraphic); // pattGraphic.translate(stepX,0); // } // pattGraphic.translate(-(endX-startX+1)*stepX, stepY); // } // } List<Double> bbox = new java.util.ArrayList<Double>(); bbox.add(rect.getX()); bbox.add(rect.getHeight() + rect.getY()); bbox.add(rect.getWidth() + rect.getX()); bbox.add(rect.getY()); AffineTransform transform; transform = new AffineTransform(getBaseTransform()); transform.concatenate(getTransform()); transform.concatenate(pp.getPatternTransform()); List<Double> theMatrix = new java.util.ArrayList<Double>(); double [] mat = new double[6]; transform.getMatrix(mat); for (double aMat : mat) { theMatrix.add(aMat); } /** @todo see if pdfDoc and res can be linked here, (currently res <> PDFDocument's resources) so addFonts() can be moved to PDFDocument class */ res.addFonts(pdfDoc, specialFontInfo); PDFPattern myPat = pdfDoc.getFactory().makePattern( resourceContext, 1, res, 1, 1, bbox, rect.getWidth(), rect.getHeight(), theMatrix, null, pattGraphic.getBuffer()); currentStream.write(myPat.getColorSpaceOut(fill)); PDFAnnotList annots = context.getAnnotations(); if (annots != null) { this.pdfDoc.addObject(annots); } flushPDFDocument(); return true; } /** * @param paint some paint * @param shape a shape * @return true (always) */ protected boolean applyUnknownPaint(Paint paint, Shape shape) { preparePainting(); Shape clip = getClip(); Rectangle2D usrClipBounds; Rectangle2D usrBounds; usrBounds = shape.getBounds2D(); if (clip != null) { usrClipBounds = clip.getBounds2D(); if (!usrClipBounds.intersects(usrBounds)) { return true; } Rectangle2D.intersect(usrBounds, usrClipBounds, usrBounds); } double usrX = usrBounds.getX(); double usrY = usrBounds.getY(); double usrW = usrBounds.getWidth(); double usrH = usrBounds.getHeight(); Rectangle devShapeBounds; Rectangle devClipBounds; Rectangle devBounds; AffineTransform at = getTransform(); devShapeBounds = at.createTransformedShape(shape).getBounds(); if (clip != null) { devClipBounds = at.createTransformedShape(clip).getBounds(); if (!devClipBounds.intersects(devShapeBounds)) { return true; } devBounds = devShapeBounds.intersection(devClipBounds); } else { devBounds = devShapeBounds; } int devX = devBounds.x; int devY = devBounds.y; int devW = devBounds.width; int devH = devBounds.height; ColorSpace rgbCS = ColorSpace.getInstance(ColorSpace.CS_sRGB); ColorModel rgbCM = new DirectColorModel( rgbCS, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000, false, DataBuffer.TYPE_BYTE); PaintContext pctx = paint.createContext(rgbCM, devBounds, usrBounds, at, getRenderingHints()); PDFXObject imageInfo = pdfDoc.getXObject( "TempImage:" + pctx.toString()); if (imageInfo != null) { resourceContext.addXObject(imageInfo); } else { Raster r = pctx.getRaster(devX, devY, devW, devH); assert (r instanceof WritableRaster); WritableRaster wr = (WritableRaster) r; wr = wr.createWritableTranslatedChild(0, 0); ColorModel pcm = pctx.getColorModel(); BufferedImage bi = new BufferedImage( pcm, wr, pcm.isAlphaPremultiplied(), null); final byte[] rgb = new byte[devW * devH * 3]; final int[] line = new int[devW]; final byte[] mask; int x; int y; int val; int rgbIdx = 0; if (pcm.hasAlpha()) { mask = new byte[devW * devH]; int maskIdx = 0; for (y = 0; y < devH; y++) { bi.getRGB(0, y, devW, 1, line, 0, devW); for (x = 0; x < devW; x++) { val = line[x]; mask[maskIdx++] = (byte)(val >>> 24); rgb[rgbIdx++] = (byte)((val >> 16) & 0x0FF); rgb[rgbIdx++] = (byte)((val >> 8) & 0x0FF); rgb[rgbIdx++] = (byte)(val & 0x0FF); } } } else { mask = null; for (y = 0; y < devH; y++) { bi.getRGB(0, y, devW, 1, line, 0, devW); for (x = 0; x < devW; x++) { val = line[x]; rgb[rgbIdx++] = (byte)((val >> 16) & 0x0FF); rgb[rgbIdx++] = (byte)((val >> 8) & 0x0FF); rgb[rgbIdx++] = (byte)(val & 0x0FF); } } } PDFReference maskRef = null; if (mask != null) { BitmapImage fopimg = new BitmapImage( "TempImageMask:" + pctx.toString(), devW, devH, mask, null); fopimg.setColorSpace(new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_GRAY)); PDFImageXObject xobj = pdfDoc.addImage(resourceContext, fopimg); maskRef = xobj.makeReference(); flushPDFDocument(); } BitmapImage fopimg; fopimg = new BitmapImage("TempImage:" + pctx.toString(), devW, devH, rgb, maskRef); fopimg.setTransparent(new PDFColor(255, 255, 255)); imageInfo = pdfDoc.addImage(resourceContext, fopimg); flushPDFDocument(); } currentStream.write("q\n"); writeClip(shape); currentStream.write("" + PDFNumber.doubleOut(usrW) + " 0 0 " + PDFNumber.doubleOut(-usrH) + " " + PDFNumber.doubleOut(usrX) + " " + PDFNumber.doubleOut(usrY + usrH) + " cm\n" + imageInfo.getName() + " Do\nQ\n"); return true; } /** * Apply the stroke to the PDF. * This takes the java stroke and outputs the appropriate settings * to the PDF so that the stroke attributes are handled. * * @param stroke the java stroke */ protected void applyStroke(Stroke stroke) { preparePainting(); if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke)stroke; float[] da = bs.getDashArray(); if (da != null) { currentStream.write("["); for (int count = 0; count < da.length; count++) { currentStream.write(PDFNumber.doubleOut(da[count])); if (count < da.length - 1) { currentStream.write(" "); } } currentStream.write("] "); float offset = bs.getDashPhase(); currentStream.write(PDFNumber.doubleOut(offset) + " d\n"); } else { currentStream.write("[] 0 d\n"); } int ec = bs.getEndCap(); switch (ec) { case BasicStroke.CAP_BUTT: currentStream.write(0 + " J\n"); break; case BasicStroke.CAP_ROUND: currentStream.write(1 + " J\n"); break; case BasicStroke.CAP_SQUARE: currentStream.write(2 + " J\n"); break; default: break; } int lj = bs.getLineJoin(); switch (lj) { case BasicStroke.JOIN_MITER: currentStream.write(0 + " j\n"); break; case BasicStroke.JOIN_ROUND: currentStream.write(1 + " j\n"); break; case BasicStroke.JOIN_BEVEL: currentStream.write(2 + " j\n"); break; default: break; } float lw = bs.getLineWidth(); currentStream.write(PDFNumber.doubleOut(lw) + " w\n"); float ml = Math.max(1.0f, bs.getMiterLimit()); currentStream.write(PDFNumber.doubleOut(ml) + " M\n"); } } /** {@inheritDoc} */ @Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) { String key = "TempImage:" + img.toString(); drawInnerRenderedImage(key, img, xform); } /** * @param key a key * @param img an image * @param xform a transform */ public void drawInnerRenderedImage(String key, RenderedImage img, AffineTransform xform) { preparePainting(); PDFXObject xObject = pdfDoc.getXObject(key); if (xObject == null) { xObject = addRenderedImage(key, img); } else { resourceContext.addXObject(xObject); } useXObject(xObject, xform, img.getWidth(), img.getHeight()); } private void useXObject(PDFXObject xObject, AffineTransform xform, float width, float height) { // now do any transformation required and add the actual image // placement instance currentStream.write("q\n"); concatMatrix(getTransform()); Shape imclip = getClip(); writeClip(imclip); concatMatrix(xform); String w = PDFNumber.doubleOut(width, DEC); String h = PDFNumber.doubleOut(height, DEC); currentStream.write("" + w + " 0 0 -" + h + " 0 " + h + " cm\n" + xObject.getName() + " Do\nQ\n"); } private PDFXObject addRenderedImage(String key, RenderedImage img) { ImageInfo info = new ImageInfo(null, "image/unknown"); ImageSize size = new ImageSize(img.getWidth(), img.getHeight(), GraphicsConstants.DEFAULT_DPI); info.setSize(size); ImageRendered imgRend = new ImageRendered(info, img, null); ImageRenderedAdapter adapter = new ImageRenderedAdapter(imgRend, key); PDFXObject xObject = pdfDoc.addImage(resourceContext, adapter); flushPDFDocument(); return xObject; } /** {@inheritDoc} */ @Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) { //TODO Check if this is good enough drawRenderedImage(img.createDefaultRendering(), xform); } /** * Renders the text specified by the specified <code>String</code>, * using the current <code>Font</code> and <code>Paint</code> attributes * in the <code>Graphics2D</code> context. * The baseline of the first character is at position * (<i>x</i>, <i>y</i>) in the User Space. * The rendering attributes applied include the <code>Clip</code>, * <code>Transform</code>, <code>Paint</code>, <code>Font</code> and * <code>Composite</code> attributes. For characters in script systems * such as Hebrew and Arabic, the glyphs can be rendered from right to * left, in which case the coordinate supplied is the location of the * leftmost character on the baseline. * @param s the <code>String</code> to be rendered * @param x the coordinate where the <code>String</code> * should be rendered * @param y the coordinate where the <code>String</code> * should be rendered * @see #setPaint * @see java.awt.Graphics#setColor * @see java.awt.Graphics#setFont * @see #setTransform * @see #setComposite * @see #setClip */ @Override public void drawString(String s, float x, float y) { preparePainting(); Font fontState; AffineTransform fontTransform = null; if (ovFontState == null) { java.awt.Font gFont = getFont(); fontTransform = gFont.getTransform(); fontState = fontInfo.getFontInstanceForAWTFont(gFont); } else { fontState = fontInfo.getFontInstance( ovFontState.getFontTriplet(), ovFontState.getFontSize()); ovFontState = null; } updateCurrentFont(fontState); saveGraphicsState(); Color c = getColor(); applyColor(c, true); applyPaint(getPaint(), true); applyAlpha(c.getAlpha(), OPAQUE); Map<Integer, Map<Integer, Integer>> kerning = fontState.getKerning(); boolean kerningAvailable = (kerning != null && !kerning.isEmpty()); boolean useMultiByte = isMultiByteFont(currentFontName); // String startText = useMultiByte ? "<FEFF" : "("; String startText = useMultiByte ? "<" : "("; String endText = useMultiByte ? "> " : ") "; AffineTransform trans = getTransform(); //trans.translate(x, y); double[] vals = new double[6]; trans.getMatrix(vals); concatMatrix(vals); Shape imclip = getClip(); writeClip(imclip); currentStream.write("BT\n"); AffineTransform localTransform = new AffineTransform(); localTransform.translate(x, y); if (fontTransform != null) { localTransform.concatenate(fontTransform); } localTransform.scale(1, -1); double[] lt = new double[6]; localTransform.getMatrix(lt); currentStream.write(PDFNumber.doubleOut(lt[0]) + " " + PDFNumber.doubleOut(lt[1]) + " " + PDFNumber.doubleOut(lt[2]) + " " + PDFNumber.doubleOut(lt[3]) + " " + PDFNumber.doubleOut(lt[4]) + " " + PDFNumber.doubleOut(lt[5]) + " Tm [" + startText); int l = s.length(); for (int i = 0; i < l; i++) { char ch = fontState.mapChar(s.charAt(i)); if (!useMultiByte) { if (ch > 127) { currentStream.write("\\"); currentStream.write(Integer.toOctalString(ch)); } else { switch (ch) { case '(': case ')': case '\\': currentStream.write("\\"); break; default: } currentStream.write(ch); } } else { currentStream.write(PDFText.toUnicodeHex(ch)); } if (kerningAvailable && (i + 1) < l) { addKerning(currentStream, ((int) ch), ((int) fontState.mapChar(s.charAt(i + 1))), kerning, startText, endText); } } currentStream.write(endText); currentStream.write("] TJ\n"); currentStream.write("ET\n"); restoreGraphicsState(); } /** * Applies the given alpha values for filling and stroking. * @param fillAlpha A value between 0 and 255 (=OPAQUE) for filling * @param strokeAlpha A value between 0 and 255 (=OPAQUE) for stroking */ protected void applyAlpha(int fillAlpha, int strokeAlpha) { if (fillAlpha != OPAQUE || strokeAlpha != OPAQUE) { Object profile = isTransparencyAllowed(); if (profile == null) { Map<String, Float> vals = new java.util.HashMap<String, Float>(); if (fillAlpha != OPAQUE) { vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, fillAlpha / 255f); } if (strokeAlpha != OPAQUE) { vals.put(PDFGState.GSTATE_ALPHA_STROKE, strokeAlpha / 255f); } PDFGState gstate = pdfDoc.getFactory().makeGState(vals, paintingState.getGState()); resourceContext.addGState(gstate); currentStream.write("/" + gstate.getName() + " gs\n"); } else if (transparencyIgnoredEventListener != null) { transparencyIgnoredEventListener.transparencyIgnored(profile); } } } /** * Updates the currently selected font. * @param font the new font to use */ protected void updateCurrentFont(Font font) { String name = font.getFontName(); float size = font.getFontSize() / 1000f; //Only update if necessary if ((!name.equals(this.currentFontName)) || (size != this.currentFontSize)) { this.currentFontName = name; this.currentFontSize = size; currentStream.write("/" + name + " " + size + " Tf\n"); } } /** * 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 */ @Deprecated protected Font getInternalFontForAWTFont(java.awt.Font awtFont) { return fontInfo.getFontInstanceForAWTFont(awtFont); } /** * Determines whether the font with the given name is a multi-byte font. * @param name the name of the font * @return true if it's a multi-byte font */ protected boolean isMultiByteFont(String name) { // This assumes that *all* CIDFonts use a /ToUnicode mapping org.apache.fop.fonts.Typeface f = fontInfo.getFonts().get(name); return f.isMultiByte(); } private void addKerning(StringWriter buf, Integer ch1, Integer ch2, Map<Integer, Map<Integer, Integer>> kerning, String startText, String endText) { preparePainting(); Map<Integer, Integer> kernPair = kerning.get(ch1); if (kernPair != null) { Integer width = kernPair.get(ch2); if (width != null) { currentStream.write(endText + (-width) + " " + startText); } } } /** * Renders the text of the specified iterator, using the * <code>Graphics2D</code> context's current <code>Paint</code>. The * iterator must specify a font * for each character. The baseline of the * first character is at position (<i>x</i>, <i>y</i>) in the * User Space. * The rendering attributes applied include the <code>Clip</code>, * <code>Transform</code>, <code>Paint</code>, and * <code>Composite</code> attributes. * For characters in script systems such as Hebrew and Arabic, * the glyphs can be rendered from right to left, in which case the * coordinate supplied is the location of the leftmost character * on the baseline. * @param iterator the iterator whose text is to be rendered * @param x the coordinate where the iterator's text is to be * rendered * @param y the coordinate where the iterator's text is to be * rendered * @see #setPaint * @see java.awt.Graphics#setColor * @see #setTransform * @see #setComposite * @see #setClip *//* TODO Reimplement for higher efficiency similar to the way it was done in PDFTextPainter public void drawString(AttributedCharacterIterator iterator, float x, float y) { preparePainting(); Font fontState = null; Shape imclip = getClip(); writeClip(imclip); Color c = getColor(); applyColor(c, true); applyPaint(getPaint(), true); boolean fill = true; boolean stroke = false; if (true) { Stroke currentStroke = getStroke(); stroke = true; applyStroke(currentStroke); applyColor(c, false); applyPaint(getPaint(), false); } currentStream.write("BT\n"); // set text rendering mode: // 0 - fill, 1 - stroke, 2 - fill then stroke int textr = 0; if (fill && stroke) { textr = 2; } else if (stroke) { textr = 1; } currentStream.write(textr + " Tr\n"); AffineTransform trans = getTransform(); trans.translate(x, y); double[] vals = new double[6]; trans.getMatrix(vals); for (char ch = iterator.first(); ch != CharacterIterator.DONE; ch = iterator.next()) { //Map attr = iterator.getAttributes(); String name = fontState.getFontName(); int size = fontState.getFontSize(); if ((!name.equals(this.currentFontName)) || (size != this.currentFontSize)) { this.currentFontName = name; this.currentFontSize = size; currentStream.write("/" + name + " " + (size / 1000) + " Tf\n"); } currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " " + PDFNumber.doubleOut(vals[2], DEC) + " " + PDFNumber.doubleOut(vals[3], DEC) + " " + PDFNumber.doubleOut(vals[4], DEC) + " " + PDFNumber.doubleOut(vals[5], DEC) + " Tm (" + ch + ") Tj\n"); } currentStream.write("ET\n"); }*/ /** * Fills the interior of a <code>Shape</code> using the settings of the * <code>Graphics2D</code> context. The rendering attributes applied * include the <code>Clip</code>, <code>Transform</code>, * <code>Paint</code>, and <code>Composite</code>. * @param s the <code>Shape</code> to be filled * @see #setPaint * @see java.awt.Graphics#setColor * @see #transform * @see #setTransform * @see #setComposite * @see #clip * @see #setClip */ @Override public void fill(Shape s) { preparePainting(); //Transparency shortcut Color c; c = getBackground(); if (c.getAlpha() == 0) { c = getColor(); if (c.getAlpha() == 0) { return; } } AffineTransform trans = getTransform(); double[] tranvals = new double[6]; trans.getMatrix(tranvals); Shape imclip = getClip(); boolean newClip = paintingState.checkClip(imclip); boolean newTransform = paintingState.checkTransform(trans) && !trans.isIdentity(); if (newClip || newTransform) { saveGraphicsState(); if (newTransform) { concatMatrix(tranvals); } if (newClip) { writeClip(imclip); } } applyAlpha(c.getAlpha(), OPAQUE); c = getColor(); applyColor(c, true); c = getBackground(); applyColor(c, false); Paint paint = getPaint(); if (paintingState.setPaint(paint)) { if (!applyPaint(paint, true)) { // Use the shape to 'clip' the paint contents. applyUnknownPaint(paint, s); if (newClip || newTransform) { restoreGraphicsState(); } return; } } if (s instanceof Rectangle2D) { Rectangle2D rect = (Rectangle2D)s; currentStream.write(PDFNumber.doubleOut(rect.getMinX(), DEC) + " " + PDFNumber.doubleOut(rect.getMinY(), DEC) + " "); currentStream.write(PDFNumber.doubleOut(rect.getWidth(), DEC) + " " + PDFNumber.doubleOut(rect.getHeight(), DEC) + " re "); doDrawing(true, false, false); } else { PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); processPathIterator(iter); doDrawing(true, false, iter.getWindingRule() == PathIterator.WIND_EVEN_ODD); } if (newClip || newTransform) { restoreGraphicsState(); } } void saveGraphicsState() { currentStream.write("q\n"); paintingState.save(); } void restoreGraphicsState() { currentStream.write("Q\n"); paintingState.restore(); } /** Checks whether the use of transparency is allowed. */ protected Object isTransparencyAllowed() { return pdfDoc.getProfile().isTransparencyAllowed(); } /** * Processes a path iterator generating the necessary painting operations. * @param iter PathIterator to process */ public void processPathIterator(PathIterator iter) { double lastX = 0.0; double lastY = 0.0; while (!iter.isDone()) { double[] vals = new double[6]; int type = iter.currentSegment(vals); switch (type) { case PathIterator.SEG_CUBICTO: lastX = vals[4]; lastY = vals[5]; currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " " + PDFNumber.doubleOut(vals[2], DEC) + " " + PDFNumber.doubleOut(vals[3], DEC) + " " + PDFNumber.doubleOut(vals[4], DEC) + " " + PDFNumber.doubleOut(vals[5], DEC) + " c\n"); break; case PathIterator.SEG_LINETO: lastX = vals[0]; lastY = vals[1]; currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " l\n"); break; case PathIterator.SEG_MOVETO: lastX = vals[0]; lastY = vals[1]; currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " m\n"); break; case PathIterator.SEG_QUADTO: double controlPointAX = lastX + ((2.0 / 3.0) * (vals[0] - lastX)); double controlPointAY = lastY + ((2.0 / 3.0) * (vals[1] - lastY)); double controlPointBX = vals[2] + ((2.0 / 3.0) * (vals[0] - vals[2])); double controlPointBY = vals[3] + ((2.0 / 3.0) * (vals[1] - vals[3])); currentStream.write(PDFNumber.doubleOut(controlPointAX, DEC) + " " + PDFNumber.doubleOut(controlPointAY, DEC) + " " + PDFNumber.doubleOut(controlPointBX, DEC) + " " + PDFNumber.doubleOut(controlPointBY, DEC) + " " + PDFNumber.doubleOut(vals[2], DEC) + " " + PDFNumber.doubleOut(vals[3], DEC) + " c\n"); lastX = vals[2]; lastY = vals[3]; break; case PathIterator.SEG_CLOSE: currentStream.write("h\n"); break; default: break; } iter.next(); } } /** * Do the PDF drawing command. * This does the PDF drawing command according to fill * stroke and winding rule. * * @param fill true if filling the path * @param stroke true if stroking the path * @param nonzero true if using the non-zero winding rule */ protected void doDrawing(boolean fill, boolean stroke, boolean nonzero) { preparePainting(); if (fill) { if (stroke) { if (nonzero) { currentStream.write("B*\n"); } else { currentStream.write("B\n"); } } else { if (nonzero) { currentStream.write("f*\n"); } else { currentStream.write("f\n"); } } } else { // if (stroke) currentStream.write("S\n"); } } /** * Returns the device configuration associated with this * <code>Graphics2D</code>. * * @return the PDF graphics configuration */ @Override public GraphicsConfiguration getDeviceConfiguration() { return new GraphicsConfigurationWithTransparency(); } /** * Used to create proper font metrics */ private Graphics2D fmg; { BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); fmg = bi.createGraphics(); } /** * Gets the font metrics for the specified font. * @return the font metrics for the specified font. * @param f the specified font * @see java.awt.Graphics#getFont * @see java.awt.FontMetrics * @see java.awt.Graphics#getFontMetrics() */ @Override public java.awt.FontMetrics getFontMetrics(java.awt.Font f) { return fmg.getFontMetrics(f); } /** * Sets the paint mode of this graphics context to alternate between * this graphics context's current color and the new specified color. * This specifies that logical pixel operations are performed in the * XOR mode, which alternates pixels between the current color and * a specified XOR color. * <p> * When drawing operations are performed, pixels which are the * current color are changed to the specified color, and vice versa. * <p> * Pixels that are of colors other than those two colors are changed * in an unpredictable but reversible manner; if the same figure is * drawn twice, then all pixels are restored to their original values. * @param c1 the XOR alternation color */ @Override public void setXORMode(Color c1) { //NYI } /** * Copies an area of the component by a distance specified by * <code>dx</code> and <code>dy</code>. From the point specified * by <code>x</code> and <code>y</code>, this method * copies downwards and to the right. To copy an area of the * component to the left or upwards, specify a negative value for * <code>dx</code> or <code>dy</code>. * If a portion of the source rectangle lies outside the bounds * of the component, or is obscured by another window or component, * <code>copyArea</code> will be unable to copy the associated * pixels. The area that is omitted can be refreshed by calling * the component's <code>paint</code> method. * @param x the <i>x</i> coordinate of the source rectangle. * @param y the <i>y</i> coordinate of the source rectangle. * @param width the width of the source rectangle. * @param height the height of the source rectangle. * @param dx the horizontal distance to copy the pixels. * @param dy the vertical distance to copy the pixels. */ @Override public void copyArea(int x, int y, int width, int height, int dx, int dy) { //NYI } /** * Registers a function object against the output format document * @param function The function object to register * @return Returns either the function which has already been registered * or the current new registered object. */ public PDFFunction registerFunction(PDFFunction function) { return pdfDoc.getFactory().registerFunction(function); } /** * Registers a shading object against the otuput format document * @param shading The shading object to register * @return Returs either the shading which has already been registered * or the current new registered object */ public PDFShading registerShading(PDFShading shading) { return pdfDoc.getFactory().registerShading(resourceContext, shading); } /** * Registers a pattern object against the output format document * @param pattern The pattern object to register * @return Returns either the pattern which has already been registered * or the current new registered object */ public PDFPattern registerPattern(PDFPattern pattern) { return pdfDoc.getFactory().registerPattern(resourceContext, pattern); } }