/* * 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.Point2D; 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.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.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.PDFResourceContext; import org.apache.fop.pdf.PDFResources; 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; /** *

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.

* *

This work was authored by Keiron Liddle (keiron@aftexsw.com).

* * @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 String 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 = 0; /** * The count of natively handled images added to document so they receive * unique keys. */ protected int nativeCount = 0; /** * 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 = null; /** * 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 = null; /** * 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, String pref, String font, float size) { this(textAsShapes); pdfDoc = doc; this.colorHandler = new PDFColorHandler(doc.getResources()); resourceContext = page; currentFontName = font; currentFontSize = size; fontInfo = fi; pageRef = pref; paintingState = new PDFPaintingState(); } /** * 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; } /** * Creates a new Graphics object that is * a copy of this Graphics 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(); } /** * 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 String 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(), 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) { 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 * (xy) in this graphics context's coordinate * space. Transparent pixels in the image do not affect whatever * pixels are already there. *

* 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. *

* If the image has not yet been completely loaded, then * drawImage returns false. 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 x coordinate. * @param y the y 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.getPDFResources().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 Graphics object cannot be used after * disposehas been called. *

* When a Java program runs, a large number of Graphics * 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. *

* Graphics objects which are provided as arguments to the * paint and update methods * of components are automatically released by the system when * those methods return. For efficiency, programmers should * call dispose when finished using * a Graphics object only if it was created * directly from a component or another Graphics 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 Shape using the settings of the * current Graphics2D context. The rendering attributes * applied include the Clip, Transform, * Paint, Composite and * Stroke attributes. * @param s the Shape 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) { LinearGradientPaint gp = (LinearGradientPaint)paint; // This code currently doesn't support 'repeat'. // For linear gradients it is possible to construct // a 'tile' that is repeated with a PDF pattern, but // it would be very tricky as you would have to rotate // the coordinate system so the repeat was axially // aligned. At this point I'm just going to rasterize it. MultipleGradientPaint.CycleMethodEnum cycle = gp.getCycleMethod(); if (cycle != MultipleGradientPaint.NO_CYCLE) { return false; } Color[] cols = gp.getColors(); float[] fractions = gp.getFractions(); // Build proper transform from gradient space to page space // ('Patterns' don't get userspace transform). AffineTransform transform; transform = new AffineTransform(getBaseTransform()); transform.concatenate(getTransform()); transform.concatenate(gp.getTransform()); List theMatrix = new java.util.ArrayList(); double [] mat = new double[6]; transform.getMatrix(mat); for (int idx = 0; idx < mat.length; idx++) { theMatrix.add(new Double(mat[idx])); } Point2D p1 = gp.getStartPoint(); Point2D p2 = gp.getEndPoint(); List theCoords = new java.util.ArrayList(); theCoords.add(new Double(p1.getX())); theCoords.add(new Double(p1.getY())); theCoords.add(new Double(p2.getX())); theCoords.add(new Double(p2.getY())); List theExtend = new java.util.ArrayList(); theExtend.add(Boolean.TRUE); theExtend.add(Boolean.TRUE); List theDomain = new java.util.ArrayList(); theDomain.add(new Double(0)); theDomain.add(new Double(1)); List theEncode = new java.util.ArrayList(); theEncode.add(new Double(0)); theEncode.add(new Double(1)); theEncode.add(new Double(0)); theEncode.add(new Double(1)); List theBounds = new java.util.ArrayList(); List someColors = new java.util.ArrayList(); for (int count = 0; count < cols.length; count++) { Color c1 = cols[count]; if (c1.getAlpha() != 255) { return false; // PDF can't do alpha } //PDFColor color1 = new PDFColor(c1.getRed(), c1.getGreen(), // c1.getBlue()); someColors.add(c1); if (count > 0 && count < cols.length - 1) { theBounds.add(new Double(fractions[count])); } } //Gradients are currently restricted to sRGB PDFDeviceColorSpace aColorSpace; aColorSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); PDFPattern myPat = pdfDoc.getFactory().makeGradient( resourceContext, false, aColorSpace, someColors, theBounds, theCoords, theMatrix); currentStream.write(myPat.getColorSpaceOut(fill)); return true; } if (paint instanceof RadialGradientPaint) { RadialGradientPaint rgp = (RadialGradientPaint)paint; // There is essentially no way to support repeats // in PDF for radial gradients (the one option would // be to 'grow' the outer circle until it fully covered // 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). so we rasterize. MultipleGradientPaint.CycleMethodEnum cycle = rgp.getCycleMethod(); if (cycle != MultipleGradientPaint.NO_CYCLE) { return false; } AffineTransform transform; transform = new AffineTransform(getBaseTransform()); transform.concatenate(getTransform()); transform.concatenate(rgp.getTransform()); List theMatrix = new java.util.ArrayList(); double [] mat = new double[6]; transform.getMatrix(mat); for (int idx = 0; idx < mat.length; idx++) { theMatrix.add(new Double(mat[idx])); } double ar = rgp.getRadius(); Point2D ac = rgp.getCenterPoint(); Point2D af = rgp.getFocusPoint(); List theCoords = new java.util.ArrayList(); double dx = af.getX() - ac.getX(); double dy = af.getY() - ac.getY(); double d = Math.sqrt(dx * dx + dy * dy); if (d > ar) { // the center point af must be within the circle with // radius ar centered at ac so limit it to that. double scale = (ar * .9999) / d; dx = dx * scale; dy = dy * scale; } theCoords.add(new Double(ac.getX() + dx)); // Fx theCoords.add(new Double(ac.getY() + dy)); // Fy theCoords.add(new Double(0)); theCoords.add(new Double(ac.getX())); theCoords.add(new Double(ac.getY())); theCoords.add(new Double(ar)); Color[] cols = rgp.getColors(); List someColors = new java.util.ArrayList(); for (int count = 0; count < cols.length; count++) { Color cc = cols[count]; if (cc.getAlpha() != 255) { return false; // PDF can't do alpha } someColors.add(cc); } float[] fractions = rgp.getFractions(); List theBounds = new java.util.ArrayList(); for (int count = 1; count < fractions.length - 1; count++) { float offset = fractions[count]; theBounds.add(new Double(offset)); } PDFDeviceColorSpace colSpace; colSpace = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB); PDFPattern myPat = pdfDoc.getFactory().makeGradient( resourceContext, true, colSpace, someColors, theBounds, theCoords, theMatrix); currentStream.write(myPat.getColorSpaceOut(fill)); return true; } if (paint instanceof PatternPaint) { PatternPaint pp = (PatternPaint)paint; return createPattern(pp, fill); } return false; // unknown paint } 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); 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 bbox = new java.util.ArrayList(); bbox.add(new Double(rect.getX())); bbox.add(new Double(rect.getHeight() + rect.getY())); bbox.add(new Double(rect.getWidth() + rect.getX())); bbox.add(new Double(rect.getY())); AffineTransform transform; transform = new AffineTransform(getBaseTransform()); transform.concatenate(getTransform()); transform.concatenate(pp.getPatternTransform()); List theMatrix = new java.util.ArrayList(); double [] mat = new double[6]; transform.getMatrix(mat); for (int idx = 0; idx < mat.length; idx++) { theMatrix.add(new Double(mat[idx])); } /** @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.getPDFResources().addXObject(imageInfo); } else { Raster r = pctx.getRaster(devX, devY, devW, devH); 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); } } } String 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.referencePDF(); 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.getPDFResources().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 String, * using the current Font and Paint attributes * in the Graphics2D context. * The baseline of the first character is at position * (xy) in the User Space. * The rendering attributes applied include the Clip, * Transform, Paint, Font and * Composite 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 String to be rendered * @param x the coordinate where the String * should be rendered * @param y the coordinate where the String * 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> kerning = fontState.getKerning(); boolean kerningAvailable = (kerning != null && !kerning.isEmpty()); boolean useMultiByte = isMultiByteFont(currentFontName); // String startText = 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, (Integer.valueOf(ch)), (Integer.valueOf(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) { checkTransparencyAllowed(); Map vals = new java.util.HashMap(); if (fillAlpha != OPAQUE) { vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, new Float(fillAlpha / 255f)); } if (strokeAlpha != OPAQUE) { vals.put(PDFGState.GSTATE_ALPHA_STROKE, new Float(strokeAlpha / 255f)); } PDFGState gstate = pdfDoc.getFactory().makeGState( vals, paintingState.getGState()); resourceContext.addGState(gstate); currentStream.write("/" + gstate.getName() + " gs\n"); } } /** * 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> kerning, String startText, String endText) { preparePainting(); Map kernPair = kerning.get(ch1); if (kernPair != null) { Integer width = kernPair.get(ch2); if (width != null) { currentStream.write(endText + (-width.intValue()) + " " + startText); } } } /** * Renders the text of the specified iterator, using the * Graphics2D context's current Paint. The * iterator must specify a font * for each character. The baseline of the * first character is at position (xy) in the * User Space. * The rendering attributes applied include the Clip, * Transform, Paint, and * Composite 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 Shape using the settings of the * Graphics2D context. The rendering attributes applied * include the Clip, Transform, * Paint, and Composite. * @param s the Shape 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 void checkTransparencyAllowed() { pdfDoc.getProfile().verifyTransparencyAllowed("Java2D graphics"); } /** * 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 * Graphics2D. * * @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. *

* When drawing operations are performed, pixels which are the * current color are changed to the specified color, and vice versa. *

* 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 * dx and dy. From the point specified * by x and y, 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 * dx or dy. * If a portion of the source rectangle lies outside the bounds * of the component, or is obscured by another window or component, * copyArea will be unable to copy the associated * pixels. The area that is omitted can be refreshed by calling * the component's paint method. * @param x the x coordinate of the source rectangle. * @param y the y 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 } }